[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,495 @@
import { getExternalAgentSdkBackend } from '../../managedAgents';
import {
runSdkAgentTurn,
steerSdkAgentTurn,
type SdkAgentCallbacks,
} from '../../sdkAgentAdapter';
import {
getNetcattyBridge,
generateId,
resolveUserSkillsContext,
isToolResultError,
} from '../../aiChatStreamingSupport';
import type { AgentActivity, AgentUsage, ChatMessage } from '../../types';
import type {
ExternalTurnInput,
TurnDriver,
TurnDriverContext,
TurnSteerInput,
TurnSteerResult,
} from './types';
import { resolveEstimatedUsageFallback, upsertAgentActivity } from './externalSdkEventState';
import {
clearCodebuddyElicitationsForChat,
completeCodebuddyElicitation,
registerCodebuddyElicitation,
} from '../../shared/codebuddyElicitations';
interface LiveExternalTurn {
requestId: string;
sessionId: string;
signal: AbortSignal;
agentConfig: ExternalTurnInput['agentConfig'];
steer(input: TurnSteerInput): Promise<TurnSteerResult>;
ended: boolean;
}
export class ExternalSdkTurnDriver implements TurnDriver {
readonly backend = 'external-sdk' as const;
private readonly liveTurns = new Map<string, LiveExternalTurn>();
async run(input: import('./types').TurnInput, ctx: TurnDriverContext): Promise<void> {
if (input.backend !== 'external-sdk') {
throw new Error('ExternalSdkTurnDriver received non-external input');
}
try {
await runExternalTurn(input, ctx, (liveTurn) => {
this.liveTurns.set(input.chatSessionId, liveTurn);
});
} finally {
this.liveTurns.delete(input.chatSessionId);
}
}
async steer(input: TurnSteerInput): Promise<TurnSteerResult> {
const liveTurn = this.liveTurns.get(input.chatSessionId);
if (!liveTurn || liveTurn.ended) return { status: 'inactive' };
if (
getExternalAgentSdkBackend(liveTurn.agentConfig) !== 'codex'
|| liveTurn.agentConfig.codexRuntime !== 'app-server'
) {
return { status: 'unsupported' };
}
return liveTurn.steer(input);
}
abort(): void {
// Abort is handled via AbortSignal on the turn input.
}
}
async function runExternalTurn(
input: ExternalTurnInput,
ctx: TurnDriverContext,
registerLiveTurn: (liveTurn: LiveExternalTurn) => void,
): Promise<void> {
const {
chatSessionId: sessionId,
assistantMsgId,
userText: trimmed,
signal,
agentConfig,
attachedImages,
context,
bridge,
ui,
} = input;
const netcattyBridge = bridge ?? getNetcattyBridge();
const sdkBackend = getExternalAgentSdkBackend(agentConfig);
if (!sdkBackend || !netcattyBridge) {
ui.reportStreamError(
sessionId,
signal,
'This agent has no SDK backend configured. Re-discover it in Settings -> AI.',
);
ui.setStreamingForScope(sessionId, false);
return;
}
const userSkillsContext = await resolveUserSkillsContext(
netcattyBridge,
trimmed,
context.selectedUserSkillSlugs,
);
const requestId = ctx.turnId;
ui.setStreamingForScope(sessionId, true);
if (netcattyBridge.aiMcpUpdateSessions) {
await netcattyBridge.aiMcpUpdateSessions(context.terminalSessions, sessionId);
}
let needsNewAssistantMsg = false;
let activeAssistantMessageId = assistantMsgId;
let steerInFlight = false;
let ended = false;
interface BufferedUiOperation {
operation: () => void;
flushBeforeSteerBoundary: boolean;
}
const bufferedUiOperations: BufferedUiOperation[] = [];
const runOrBufferUiOperation = (
operation: () => void,
options: { flushBeforeSteerBoundary?: boolean } = {},
) => {
if (steerInFlight) {
bufferedUiOperations.push({
operation,
flushBeforeSteerBoundary: options.flushBeforeSteerBoundary === true,
});
return;
}
operation();
};
const flushBufferedUiOperations = (
shouldFlush: (entry: BufferedUiOperation) => boolean = () => true,
) => {
const operations = bufferedUiOperations.splice(0);
operations.forEach((entry) => {
if (shouldFlush(entry)) {
entry.operation();
} else {
bufferedUiOperations.push(entry);
}
});
};
const maybeCreateAssistantMsg = () => {
if (!needsNewAssistantMsg) return;
needsNewAssistantMsg = false;
activeAssistantMessageId = generateId();
ui.addMessageToSession(sessionId, {
id: activeAssistantMessageId,
role: 'assistant',
content: '',
timestamp: Date.now(),
model: agentConfig.name || 'external',
});
};
const updateActiveAssistant = (updater: (message: ChatMessage) => ChatMessage) => {
maybeCreateAssistantMsg();
ui.updateMessageById(sessionId, activeAssistantMessageId, updater);
};
let pendingText = '';
let rafId: number | null = null;
const appendTextToActiveAssistant = (textChunk: string) => {
updateActiveAssistant(msg => ({
...msg,
content: msg.content + textChunk,
statusText: undefined,
thinkingDurationMs: msg.thinking && !msg.thinkingDurationMs
? Date.now() - msg.timestamp : msg.thinkingDurationMs,
}));
};
const flushPendingText = () => {
if (pendingText) {
const textChunk = pendingText;
pendingText = '';
runOrBufferUiOperation(() => appendTextToActiveAssistant(textChunk));
}
rafId = null;
};
const scheduleFrame = (cb: () => void): number => (
typeof requestAnimationFrame === 'function'
? requestAnimationFrame(cb)
: setTimeout(cb, 0) as unknown as number
);
const cancelFrame = (id: number): void => {
if (typeof cancelAnimationFrame === 'function') {
cancelAnimationFrame(id);
return;
}
clearTimeout(id);
};
const cancelPendingTextFlush = () => {
if (rafId !== null) {
cancelFrame(rafId);
rafId = null;
}
};
const enqueueTextDelta = (textChunk: string) => {
if (!textChunk) return;
// While steering buffers UI ops, skip rAF so text enters the buffer immediately.
if (steerInFlight) {
runOrBufferUiOperation(() => appendTextToActiveAssistant(textChunk));
return;
}
pendingText += textChunk;
if (rafId === null) {
rafId = scheduleFrame(flushPendingText);
}
};
const flushTextBeforeNonTextEvent = () => {
cancelPendingTextFlush();
flushPendingText();
};
const toolNamesByCallId = new Map<string, string>();
const toolCallMessageIds = new Map<string, string>();
const activityMessageIds = new Map<string, string>();
let actualUsageReported = false;
const updateActivity = (activity: AgentActivity) => {
const activityMessageId = activityMessageIds.get(activity.id);
if (activityMessageId) {
ui.updateMessageById(sessionId, activityMessageId, msg => ({
...msg,
agentActivities: upsertAgentActivity(msg.agentActivities, activity),
statusText: undefined,
}));
return;
}
maybeCreateAssistantMsg();
activityMessageIds.set(activity.id, activeAssistantMessageId);
ui.updateMessageById(sessionId, activeAssistantMessageId, msg => ({
...msg,
agentActivities: upsertAgentActivity(msg.agentActivities, activity),
statusText: undefined,
}));
};
const updateUsage = (usage: AgentUsage) => {
updateActiveAssistant(msg => ({ ...msg, usage }));
};
const callbacks: SdkAgentCallbacks = {
onTextDelta: (text: string) => {
enqueueTextDelta(text);
},
onThinkingDelta: (text: string) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
updateActiveAssistant(msg => ({
...msg,
thinking: (msg.thinking || '') + text,
}));
});
},
onThinkingDone: () => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
updateActiveAssistant(msg => ({
...msg,
thinkingDurationMs: msg.thinkingDurationMs || (Date.now() - msg.timestamp),
}));
});
},
onToolCall: (toolName: string, args: Record<string, unknown>, toolCallId?: string) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
const id = toolCallId || `tc_${Date.now()}`;
maybeCreateAssistantMsg();
toolNamesByCallId.set(id, toolName);
toolCallMessageIds.set(id, activeAssistantMessageId);
ui.updateMessageById(sessionId, activeAssistantMessageId, msg => ({
...msg,
toolCalls: [...(msg.toolCalls || []), { id, name: toolName, arguments: args }],
executionStatus: 'running',
statusText: undefined,
}));
});
},
onToolResult: (toolCallId: string, result: string, toolName?: string) => {
flushTextBeforeNonTextEvent();
const existingToolCallMessageId = toolCallMessageIds.get(toolCallId);
runOrBufferUiOperation(() => {
const effectiveToolName = toolName ?? toolNamesByCallId.get(toolCallId);
const toolCallMessageId = existingToolCallMessageId
?? toolCallMessageIds.get(toolCallId);
const updateToolCallOwner = (msg: ChatMessage) => {
if (msg.role !== 'assistant' || msg.executionStatus !== 'running') return msg;
const updatedToolCalls = effectiveToolName && !effectiveToolName.includes('sdk_agent_dynamic_tool') && msg.toolCalls
? msg.toolCalls.map(tc => tc.id === toolCallId && !tc.name ? { ...tc, name: effectiveToolName } : tc)
: msg.toolCalls;
return { ...msg, toolCalls: updatedToolCalls, executionStatus: 'completed', statusText: undefined };
};
if (toolCallMessageId) {
ui.updateMessageById(sessionId, toolCallMessageId, updateToolCallOwner);
} else {
updateActiveAssistant(updateToolCallOwner);
}
ui.addMessageToSession(sessionId, {
id: generateId(),
role: 'tool',
content: '',
toolResults: [{
toolCallId,
toolName: effectiveToolName,
content: result,
isError: isToolResultError(result),
}],
timestamp: Date.now(),
executionStatus: 'completed',
});
needsNewAssistantMsg = true;
}, {
// A result for a tool call already rendered before steering belongs to
// that original assistant segment. Commit it before adding the steer
// user/continuation boundary so tool-call history stays contiguous.
flushBeforeSteerBoundary: existingToolCallMessageId !== undefined,
});
},
onFileChange: (activity) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => updateActivity(activity));
},
onWebSearch: (activity) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => updateActivity(activity));
},
onPlanUpdate: (activity) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => updateActivity(activity));
},
onWarning: (activity) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => updateActivity(activity));
},
onUsage: (usage: AgentUsage) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
actualUsageReported = true;
updateUsage(usage);
});
},
onStatus: (message: string) => {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
updateActiveAssistant(msg => ({ ...msg, statusText: message }));
});
},
onHook: (hookEvent: string, payload: Record<string, unknown>) => {
// Surface lifecycle hooks as status text so the user sees tool activity.
const toolName = (payload.toolName as string) || '';
if (hookEvent === 'PreToolUse' && toolName) {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
updateActiveAssistant(msg => ({ ...msg, statusText: `Running ${toolName}` }));
});
} else if (hookEvent === 'Notification') {
const message = (payload.message as string) || '';
if (message) {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
updateActiveAssistant(msg => ({ ...msg, statusText: message }));
});
}
}
},
onElicitationCreate: (elicitationId: string, request: Record<string, unknown>) => {
registerCodebuddyElicitation({
elicitationId,
chatSessionId: sessionId,
request,
});
},
onElicitationComplete: (notification) => {
completeCodebuddyElicitation(notification);
},
onSessionId: (externalSessionId: string) => {
context.updateExternalSessionId?.(sessionId, externalSessionId);
},
onError: (error: string) => {
flushTextBeforeNonTextEvent();
ui.reportStreamError(sessionId, signal, error);
ui.setStreamingForScope(sessionId, false);
},
onDone: () => {
flushTextBeforeNonTextEvent();
},
};
const liveTurn: LiveExternalTurn = {
requestId,
sessionId,
signal,
agentConfig,
ended: false,
async steer(steerInput) {
if (steerInFlight) return { status: 'busy' };
if (ended || signal.aborted) return { status: 'cancelled' };
// Commit any rAF-batched text before steering buffers UI ops.
flushTextBeforeNonTextEvent();
steerInFlight = true;
const result = await steerSdkAgentTurn(
netcattyBridge,
requestId,
sessionId,
steerInput.prompt,
steerInput.attachedImages.length > 0 ? steerInput.attachedImages : undefined,
steerInput.userMessageId,
);
if (result.status === 'accepted' && !ended && !signal.aborted) {
flushBufferedUiOperations(entry => entry.flushBeforeSteerBoundary);
ui.addMessageToSession(sessionId, {
id: steerInput.userMessageId,
role: 'user',
content: steerInput.userText,
...(steerInput.attachments?.length ? { attachments: steerInput.attachments } : {}),
timestamp: Date.now(),
});
const continuationMessageId = generateId();
ui.addMessageToSession(sessionId, {
id: continuationMessageId,
role: 'assistant',
content: '',
timestamp: Date.now(),
model: agentConfig.name || 'external',
});
activeAssistantMessageId = continuationMessageId;
needsNewAssistantMsg = false;
steerInFlight = false;
flushBufferedUiOperations();
return { status: 'accepted', assistantMessageId: continuationMessageId };
}
steerInFlight = false;
flushBufferedUiOperations();
if (result.status === 'accepted') return { status: 'cancelled' };
return result;
},
};
registerLiveTurn(liveTurn);
try {
await runSdkAgentTurn(
netcattyBridge,
requestId,
sessionId,
agentConfig,
trimmed,
callbacks,
signal,
undefined,
context.selectedAgentModel,
context.existingSessionId,
context.historyMessages,
attachedImages.length > 0 ? attachedImages : undefined,
context.toolIntegrationMode,
context.defaultTargetSession,
userSkillsContext,
context.permissionMode,
{
traceSink: (event) => ctx.emit(event),
skipHarnessTrace: true,
},
);
const estimatedUsage = resolveEstimatedUsageFallback(trimmed, actualUsageReported);
if (estimatedUsage) {
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => updateUsage(estimatedUsage));
ctx.emit({
id: `usage-${ctx.turnId}`,
type: 'usage',
promptTokens: estimatedUsage.inputTokens,
completionTokens: estimatedUsage.outputTokens,
totalTokens: estimatedUsage.totalTokens,
estimated: true,
} as import('../types').AgentEvent);
}
} finally {
ended = true;
liveTurn.ended = true;
flushTextBeforeNonTextEvent();
clearCodebuddyElicitationsForChat(sessionId);
if (steerInFlight) {
steerInFlight = false;
flushBufferedUiOperations();
}
ui.setStreamingForScope(sessionId, false);
}
}
export const externalSdkTurnDriver = new ExternalSdkTurnDriver();