Clean up cross-package build coupling

This commit is contained in:
weishu
2026-05-20 19:29:43 +08:00
parent 6759cf4657
commit 83795c0630
22 changed files with 557 additions and 343 deletions
@@ -0,0 +1,174 @@
type WrappedRecord = Record<string, unknown>;
type WrappedNotification = {
method: string;
params: WrappedRecord;
};
function asRecord(value: unknown): WrappedRecord | null {
return value && typeof value === 'object' ? value as WrappedRecord : null;
}
function asString(value: unknown): string | null {
return typeof value === 'string' && value.length > 0 ? value : null;
}
function asNumber(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null;
}
function scopeFrom(value: WrappedRecord): WrappedRecord {
const thread = asRecord(value.thread);
const turn = asRecord(value.turn);
const threadId = asString(value.thread_id ?? value.threadId ?? thread?.id ?? thread?.thread_id ?? thread?.threadId);
const turnId = asString(value.turn_id ?? value.turnId ?? turn?.id ?? turn?.turn_id ?? turn?.turnId);
return {
...(threadId ? { thread_id: threadId } : {}),
...(turnId ? { turn_id: turnId } : {})
};
}
function itemIdFrom(value: WrappedRecord, item: WrappedRecord | null = null): string | null {
return asString(value.item_id ?? value.itemId ?? value.id ?? item?.id ?? item?.item_id ?? item?.itemId);
}
export function isWrappedTerminalEventType(type: string): boolean {
return type === 'task_started'
|| type === 'task_complete'
|| type === 'turn_aborted'
|| type === 'task_failed';
}
export function buildWrappedTerminalEvent(msg: WrappedRecord, inheritedScope: WrappedRecord): WrappedRecord | null {
const type = asString(msg.type);
if (!type || !isWrappedTerminalEventType(type)) {
return null;
}
const scope = { ...inheritedScope, ...scopeFrom(msg) };
const turnId = asString(msg.turn_id ?? msg.turnId ?? scope.turn_id);
if ((type === 'task_complete' || type === 'turn_aborted' || type === 'task_failed') && !turnId) {
return null;
}
const threadId = asString(msg.thread_id ?? msg.threadId ?? scope.thread_id);
const errorRecord = asRecord(msg.error);
const error = asString(msg.error ?? msg.message ?? errorRecord?.message);
return {
type,
...(threadId ? { thread_id: threadId } : {}),
...(turnId ? { turn_id: turnId } : {}),
...(type === 'task_failed' && error ? { error } : {})
};
}
export function buildWrappedTextDeltaNotification(msg: WrappedRecord, inheritedScope: WrappedRecord): WrappedNotification | null {
const type = asString(msg.type);
const scope = { ...inheritedScope, ...scopeFrom(msg) };
if (type === 'agent_message_delta' || type === 'agent_message_content_delta') {
const itemId = itemIdFrom(msg);
const delta = asString(msg.delta ?? msg.text ?? msg.message);
if (!itemId || !delta) {
return null;
}
return {
method: 'item/agentMessage/delta',
params: { itemId, delta, ...scope }
};
}
if (type === 'exec_command_output_delta') {
const itemId = asString(msg.call_id ?? msg.callId) ?? itemIdFrom(msg);
const delta = asString(msg.delta ?? msg.output ?? msg.stdout ?? msg.text);
if (!itemId || !delta) {
return null;
}
return {
method: 'item/commandExecution/outputDelta',
params: { itemId, delta, ...scope }
};
}
return null;
}
export function buildWrappedReasoningSectionBreakNotification(msg: WrappedRecord, inheritedScope: WrappedRecord): WrappedNotification | null {
if (msg.type !== 'agent_reasoning_section_break') {
return null;
}
const itemId = itemIdFrom(msg);
if (!itemId) {
return null;
}
const summaryIndex = asNumber(msg.summary_index ?? msg.summaryIndex);
return {
method: 'item/reasoning/summaryPartAdded',
params: {
itemId,
...inheritedScope,
...scopeFrom(msg),
...(summaryIndex !== null ? { summaryIndex } : {})
}
};
}
export function buildWrappedItemNotification(msg: WrappedRecord, inheritedScope: WrappedRecord): WrappedNotification | null {
const type = asString(msg.type);
if (type !== 'item_started' && type !== 'item_completed') {
return null;
}
const item = asRecord(msg.item) ?? {};
const itemThread = asRecord(item.thread);
const itemTurn = asRecord(item.turn);
const scope = { ...inheritedScope, ...scopeFrom(msg) };
const threadId = asString(msg.thread_id ?? msg.threadId ?? scope.thread_id ?? item.thread_id ?? item.threadId ?? itemThread?.id);
const turnId = asString(msg.turn_id ?? msg.turnId ?? scope.turn_id ?? item.turn_id ?? item.turnId ?? itemTurn?.id);
return {
method: type === 'item_started' ? 'item/started' : 'item/completed',
params: {
...scope,
item,
...(itemIdFrom(msg, item) ? { itemId: itemIdFrom(msg, item) } : {}),
...(threadId ? { threadId } : {}),
...(turnId ? { turnId } : {})
}
};
}
export function isIgnoredWrappedCodexEventType(type: string): boolean {
return type === 'agent_message'
|| type === 'agent_reasoning_delta'
|| type === 'agent_reasoning'
|| type === 'mcp_startup_update'
|| type === 'mcp_startup_complete'
|| type === 'skills_update_available'
|| type === 'stream_error'
|| type === 'warning'
|| type === 'terminal_interaction'
|| type === 'user_message';
}
export function buildWrappedErrorEvent(msg: WrappedRecord): WrappedRecord | null {
if (msg.type !== undefined && msg.type !== 'error') {
return null;
}
const errorRecord = asRecord(msg.error);
const willRetry = msg.will_retry === true
|| msg.willRetry === true
|| errorRecord?.will_retry === true
|| errorRecord?.willRetry === true;
if (willRetry) {
return null;
}
const error = asString(msg.message ?? msg.reason ?? errorRecord?.message);
return error ? { type: 'task_failed', error } : null;
}
+5 -3
View File
@@ -28,12 +28,14 @@ export const hubCommand: CommandDefinition = {
const { host, port } = parseHubArgs(context.commandArgs)
if (host) {
process.env.WEBAPP_HOST = host
process.env.HAPI_LISTEN_HOST = host
}
if (port) {
process.env.WEBAPP_PORT = port
process.env.HAPI_LISTEN_PORT = port
}
await import('../../../hub/src/index')
const { startHub } = await import('hapi-hub/startHub')
await startHub({ args: context.commandArgs })
await new Promise(() => {})
} catch (error) {
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error')
if (process.env.DEBUG) {
+2 -2
View File
@@ -172,8 +172,8 @@ export function getTunwgPath(): string {
return join(runtimePath(), 'tools', 'tunwg', tunwgBinary);
}
// Development mode: use downloaded binary from hub/tools/tunwg
// Development mode: use downloaded binary from shared/tools/tunwg
const platformDir = getPlatformDir();
const devBinaryName = isWin ? `tunwg-${platformDir}.exe` : `tunwg-${platformDir}`;
return join(__dirname, '..', '..', '..', 'hub', 'tools', 'tunwg', devBinaryName);
return join(__dirname, '..', '..', '..', 'shared', 'tools', 'tunwg', devBinaryName);
}
+6 -6
View File
@@ -4,7 +4,7 @@ import difftasticArchiveLicense from '../../tools/archives/difftastic-LICENSE' a
import ripgrepArchiveLicense from '../../tools/archives/ripgrep-LICENSE' assert { type: 'file' };
import difftasticLicense from '../../tools/licenses/difftastic-LICENSE' assert { type: 'file' };
import ripgrepLicense from '../../tools/licenses/ripgrep-LICENSE' assert { type: 'file' };
import tunwgLicense from '../../../hub/tools/tunwg/LICENSE' assert { type: 'file' };
import tunwgLicense from '../../../shared/tools/tunwg/LICENSE' assert { type: 'file' };
export interface EmbeddedAsset {
relativePath: string;
@@ -35,7 +35,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
] = await Promise.all([
import('../../tools/archives/difftastic-arm64-darwin.tar.gz', { assert: { type: 'file' } }),
import('../../tools/archives/ripgrep-arm64-darwin.tar.gz', { assert: { type: 'file' } }),
import('../../../hub/tools/tunwg/tunwg-arm64-darwin', { assert: { type: 'file' } })
import('../../../shared/tools/tunwg/tunwg-arm64-darwin', { assert: { type: 'file' } })
]);
return [
...COMMON_ASSETS,
@@ -53,7 +53,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
] = await Promise.all([
import('../../tools/archives/difftastic-x64-darwin.tar.gz', { assert: { type: 'file' } }),
import('../../tools/archives/ripgrep-x64-darwin.tar.gz', { assert: { type: 'file' } }),
import('../../../hub/tools/tunwg/tunwg-x64-darwin', { assert: { type: 'file' } })
import('../../../shared/tools/tunwg/tunwg-x64-darwin', { assert: { type: 'file' } })
]);
return [
...COMMON_ASSETS,
@@ -71,7 +71,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
] = await Promise.all([
import('../../tools/archives/difftastic-arm64-linux.tar.gz', { assert: { type: 'file' } }),
import('../../tools/archives/ripgrep-arm64-linux.tar.gz', { assert: { type: 'file' } }),
import('../../../hub/tools/tunwg/tunwg-arm64-linux', { assert: { type: 'file' } })
import('../../../shared/tools/tunwg/tunwg-arm64-linux', { assert: { type: 'file' } })
]);
return [
...COMMON_ASSETS,
@@ -89,7 +89,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
] = await Promise.all([
import('../../tools/archives/difftastic-x64-linux.tar.gz', { assert: { type: 'file' } }),
import('../../tools/archives/ripgrep-x64-linux.tar.gz', { assert: { type: 'file' } }),
import('../../../hub/tools/tunwg/tunwg-x64-linux', { assert: { type: 'file' } })
import('../../../shared/tools/tunwg/tunwg-x64-linux', { assert: { type: 'file' } })
]);
return [
...COMMON_ASSETS,
@@ -107,7 +107,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
] = await Promise.all([
import('../../tools/archives/difftastic-x64-win32.tar.gz', { assert: { type: 'file' } }),
import('../../tools/archives/ripgrep-x64-win32.tar.gz', { assert: { type: 'file' } }),
import('../../../hub/tools/tunwg/tunwg-x64-win32.exe', { assert: { type: 'file' } })
import('../../../shared/tools/tunwg/tunwg-x64-win32.exe', { assert: { type: 'file' } })
]);
return [
...COMMON_ASSETS,