[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
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:
@@ -0,0 +1,491 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import type { AISession, AgentActivity, ChatMessage, ExternalAgentConfig } from '../../types';
|
||||
import { SessionStateStore } from '../sessionState';
|
||||
import { ToolOutputStore } from '../toolOutputStore';
|
||||
import { ToolResultDedup } from '../toolResultDedup';
|
||||
import { externalSdkTurnDriver } from './externalSdkTurnDriver';
|
||||
import { resolveEstimatedUsageFallback, upsertAgentActivity } from './externalSdkEventState';
|
||||
import type { TurnUiCallbacks } from './types';
|
||||
|
||||
async function waitFor(predicate: () => boolean): Promise<void> {
|
||||
for (let index = 0; index < 50; index += 1) {
|
||||
if (predicate()) return;
|
||||
await new Promise<void>(resolve => setImmediate(resolve));
|
||||
}
|
||||
throw new Error('condition not reached');
|
||||
}
|
||||
|
||||
test('upsertAgentActivity replaces streaming updates by stable item id', () => {
|
||||
const running: AgentActivity = {
|
||||
id: 'plan-1',
|
||||
type: 'plan_update',
|
||||
status: 'running',
|
||||
items: [{ text: 'Map events', completed: false }],
|
||||
};
|
||||
const completed: AgentActivity = {
|
||||
...running,
|
||||
status: 'completed',
|
||||
items: [{ text: 'Map events', completed: true }],
|
||||
};
|
||||
|
||||
const first = upsertAgentActivity(undefined, running);
|
||||
const second = upsertAgentActivity(first, completed);
|
||||
|
||||
assert.equal(second.length, 1);
|
||||
assert.deepEqual(second[0], completed);
|
||||
});
|
||||
|
||||
test('upsertAgentActivity preserves the order of unrelated activities', () => {
|
||||
const search: AgentActivity = {
|
||||
id: 'search-1',
|
||||
type: 'web_search',
|
||||
status: 'completed',
|
||||
query: 'Codex events',
|
||||
};
|
||||
const warning: AgentActivity = {
|
||||
id: 'warning-1',
|
||||
type: 'warning',
|
||||
status: 'completed',
|
||||
message: 'Search result unavailable',
|
||||
};
|
||||
|
||||
assert.deepEqual(upsertAgentActivity([search], warning), [search, warning]);
|
||||
});
|
||||
|
||||
test('estimated usage is used only when the SDK did not report actual usage', () => {
|
||||
assert.deepEqual(resolveEstimatedUsageFallback('12345678', false), {
|
||||
inputTokens: 2,
|
||||
outputTokens: 0,
|
||||
totalTokens: 2,
|
||||
estimated: true,
|
||||
});
|
||||
assert.equal(resolveEstimatedUsageFallback('12345678', true), null);
|
||||
});
|
||||
|
||||
test('plan updates replace the same activity across tool message boundaries', async () => {
|
||||
let onEvent: ((event: Record<string, unknown>) => void) | undefined;
|
||||
let onDone: (() => void) | undefined;
|
||||
const bridge: Record<string, (...args: unknown[]) => unknown> = {
|
||||
onAiSdkAgentEvent: (_requestId, callback) => {
|
||||
onEvent = callback as (event: Record<string, unknown>) => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentDone: (_requestId, callback) => {
|
||||
onDone = callback as () => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentError: () => () => {},
|
||||
aiSdkAgentCancel: async () => ({ ok: true }),
|
||||
aiSdkAgentStream: async () => {
|
||||
queueMicrotask(() => {
|
||||
onEvent?.({
|
||||
type: 'plan-update',
|
||||
itemId: 'plan-1',
|
||||
status: 'running',
|
||||
items: [{ text: 'Run command', completed: false }],
|
||||
});
|
||||
onEvent?.({
|
||||
type: 'tool-call',
|
||||
toolName: 'shell',
|
||||
toolCallId: 'tool-1',
|
||||
args: { command: 'true' },
|
||||
});
|
||||
onEvent?.({
|
||||
type: 'tool-result',
|
||||
toolName: 'shell',
|
||||
toolCallId: 'tool-1',
|
||||
output: 'ok',
|
||||
});
|
||||
onEvent?.({
|
||||
type: 'plan-update',
|
||||
itemId: 'plan-1',
|
||||
status: 'completed',
|
||||
items: [{ text: 'Run command', completed: true }],
|
||||
});
|
||||
onDone?.();
|
||||
});
|
||||
return { ok: true };
|
||||
},
|
||||
};
|
||||
|
||||
const session: AISession = {
|
||||
id: 'chat-1',
|
||||
title: 'Test',
|
||||
agentId: 'codex',
|
||||
scope: { type: 'global' },
|
||||
messages: [{ id: 'assistant-1', role: 'assistant', content: '', timestamp: 1 }],
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
};
|
||||
const ui: TurnUiCallbacks = {
|
||||
addMessageToSession: (_sessionId, message) => {
|
||||
session.messages.push(message);
|
||||
},
|
||||
updateLastMessage: (_sessionId, updater) => {
|
||||
const lastIndex = session.messages.length - 1;
|
||||
session.messages[lastIndex] = updater(session.messages[lastIndex]);
|
||||
},
|
||||
updateMessageById: (_sessionId, messageId, updater) => {
|
||||
const messageIndex = session.messages.findIndex((message) => message.id === messageId);
|
||||
if (messageIndex >= 0) {
|
||||
session.messages[messageIndex] = updater(session.messages[messageIndex]);
|
||||
}
|
||||
},
|
||||
reportStreamError: () => {},
|
||||
setStreamingForScope: () => {},
|
||||
getLatestSession: () => session,
|
||||
};
|
||||
const agentConfig: ExternalAgentConfig = {
|
||||
id: 'codex',
|
||||
name: 'Codex',
|
||||
command: 'codex',
|
||||
enabled: true,
|
||||
sdkBackend: 'codex',
|
||||
};
|
||||
const controller = new AbortController();
|
||||
|
||||
await externalSdkTurnDriver.run({
|
||||
backend: 'external-sdk',
|
||||
chatSessionId: session.id,
|
||||
assistantMsgId: 'assistant-1',
|
||||
userText: 'Run the plan',
|
||||
signal: controller.signal,
|
||||
agentConfig,
|
||||
attachedImages: [],
|
||||
context: {
|
||||
terminalSessions: [],
|
||||
providers: [],
|
||||
toolIntegrationMode: 'mcp',
|
||||
selectedUserSkillSlugs: [],
|
||||
permissionMode: 'confirm',
|
||||
},
|
||||
bridge,
|
||||
ui,
|
||||
}, {
|
||||
turnId: 'turn-1',
|
||||
chatSessionId: session.id,
|
||||
sessionId: session.id,
|
||||
backend: 'external-sdk',
|
||||
signal: controller.signal,
|
||||
emit: () => {},
|
||||
toolOutputStore: new ToolOutputStore(),
|
||||
toolResultDedup: new ToolResultDedup(),
|
||||
sessionStateStore: new SessionStateStore(),
|
||||
});
|
||||
|
||||
const planActivities = session.messages
|
||||
.flatMap((message: ChatMessage) => message.agentActivities ?? [])
|
||||
.filter((activity) => activity.id === 'plan-1');
|
||||
|
||||
assert.equal(planActivities.length, 1);
|
||||
assert.equal(planActivities[0].status, 'completed');
|
||||
assert.deepEqual(planActivities[0].type === 'plan_update' ? planActivities[0].items : [], [
|
||||
{ text: 'Run command', completed: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('accepted steering persists a user bubble and routes buffered output to a continuation', async () => {
|
||||
let onEvent: ((event: Record<string, unknown>) => void) | undefined;
|
||||
let onDone: (() => void) | undefined;
|
||||
let releaseSteer: (() => void) | undefined;
|
||||
const bridge: Record<string, (...args: unknown[]) => unknown> = {
|
||||
onAiSdkAgentEvent: (_requestId, callback) => {
|
||||
onEvent = callback as (event: Record<string, unknown>) => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentDone: (_requestId, callback) => {
|
||||
onDone = callback as () => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentError: () => () => {},
|
||||
aiSdkAgentCancel: async () => ({ ok: true }),
|
||||
aiSdkAgentStream: async () => ({ ok: true }),
|
||||
aiSdkAgentSteer: async () => {
|
||||
onEvent?.({ type: 'text-delta', textDelta: 'after steer' });
|
||||
await new Promise<void>(resolve => { releaseSteer = resolve; });
|
||||
return { status: 'accepted' };
|
||||
},
|
||||
};
|
||||
const session: AISession = {
|
||||
id: 'chat-steer',
|
||||
title: 'Steer',
|
||||
agentId: 'codex',
|
||||
scope: { type: 'global' },
|
||||
messages: [{ id: 'assistant-initial', role: 'assistant', content: '', timestamp: 1 }],
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
};
|
||||
const ui: TurnUiCallbacks = {
|
||||
addMessageToSession: (_sessionId, message) => session.messages.push(message),
|
||||
updateLastMessage: () => { throw new Error('steering must not update the last message implicitly'); },
|
||||
updateMessageById: (_sessionId, messageId, updater) => {
|
||||
const index = session.messages.findIndex(message => message.id === messageId);
|
||||
if (index >= 0) session.messages[index] = updater(session.messages[index]);
|
||||
},
|
||||
reportStreamError: () => {},
|
||||
setStreamingForScope: () => {},
|
||||
getLatestSession: () => session,
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const run = externalSdkTurnDriver.run({
|
||||
backend: 'external-sdk',
|
||||
chatSessionId: session.id,
|
||||
assistantMsgId: 'assistant-initial',
|
||||
userText: 'initial prompt',
|
||||
signal: controller.signal,
|
||||
agentConfig: {
|
||||
id: 'codex',
|
||||
name: 'Codex',
|
||||
command: 'codex',
|
||||
enabled: true,
|
||||
sdkBackend: 'codex',
|
||||
codexRuntime: 'app-server',
|
||||
},
|
||||
attachedImages: [],
|
||||
context: {
|
||||
terminalSessions: [],
|
||||
providers: [],
|
||||
toolIntegrationMode: 'mcp',
|
||||
selectedUserSkillSlugs: [],
|
||||
permissionMode: 'confirm',
|
||||
},
|
||||
bridge,
|
||||
ui,
|
||||
}, {
|
||||
turnId: 'turn-steer',
|
||||
chatSessionId: session.id,
|
||||
sessionId: session.id,
|
||||
backend: 'external-sdk',
|
||||
signal: controller.signal,
|
||||
emit: () => {},
|
||||
toolOutputStore: new ToolOutputStore(),
|
||||
toolResultDedup: new ToolResultDedup(),
|
||||
sessionStateStore: new SessionStateStore(),
|
||||
});
|
||||
await waitFor(() => Boolean(onEvent));
|
||||
onEvent?.({ type: 'text-delta', textDelta: 'before steer' });
|
||||
|
||||
const steer = externalSdkTurnDriver.steer({
|
||||
chatSessionId: session.id,
|
||||
userMessageId: 'user-steer',
|
||||
userText: 'change direction',
|
||||
prompt: 'change direction',
|
||||
attachments: [],
|
||||
attachedImages: [],
|
||||
});
|
||||
await waitFor(() => Boolean(releaseSteer));
|
||||
assert.equal(session.messages.length, 1);
|
||||
releaseSteer?.();
|
||||
const result = await steer;
|
||||
|
||||
assert.equal(result.status, 'accepted');
|
||||
assert.deepEqual(session.messages.map(message => message.role), ['assistant', 'user', 'assistant']);
|
||||
assert.equal(session.messages[0].content, 'before steer');
|
||||
assert.equal(session.messages[1].content, 'change direction');
|
||||
assert.equal(session.messages[2].content, 'after steer');
|
||||
|
||||
onDone?.();
|
||||
await run;
|
||||
});
|
||||
|
||||
test('accepted steering keeps buffered results for existing tool calls on their original assistant message', async () => {
|
||||
let onEvent: ((event: Record<string, unknown>) => void) | undefined;
|
||||
let onDone: (() => void) | undefined;
|
||||
let releaseSteer: (() => void) | undefined;
|
||||
const bridge: Record<string, (...args: unknown[]) => unknown> = {
|
||||
onAiSdkAgentEvent: (_requestId, callback) => {
|
||||
onEvent = callback as (event: Record<string, unknown>) => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentDone: (_requestId, callback) => {
|
||||
onDone = callback as () => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentError: () => () => {},
|
||||
aiSdkAgentCancel: async () => ({ ok: true }),
|
||||
aiSdkAgentStream: async () => ({ ok: true }),
|
||||
aiSdkAgentSteer: async () => {
|
||||
onEvent?.({
|
||||
type: 'tool-result',
|
||||
toolName: 'shell',
|
||||
toolCallId: 'tool-before-steer',
|
||||
output: 'completed before steer acceptance',
|
||||
});
|
||||
await new Promise<void>(resolve => { releaseSteer = resolve; });
|
||||
return { status: 'accepted' };
|
||||
},
|
||||
};
|
||||
const session: AISession = {
|
||||
id: 'chat-steer-tool-result',
|
||||
title: 'Steer tool result',
|
||||
agentId: 'codex',
|
||||
scope: { type: 'global' },
|
||||
messages: [{ id: 'assistant-initial', role: 'assistant', content: '', timestamp: 1 }],
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
};
|
||||
const ui: TurnUiCallbacks = {
|
||||
addMessageToSession: (_sessionId, message) => session.messages.push(message),
|
||||
updateLastMessage: () => { throw new Error('steering must not update the last message implicitly'); },
|
||||
updateMessageById: (_sessionId, messageId, updater) => {
|
||||
const index = session.messages.findIndex(message => message.id === messageId);
|
||||
if (index >= 0) session.messages[index] = updater(session.messages[index]);
|
||||
},
|
||||
reportStreamError: () => {},
|
||||
setStreamingForScope: () => {},
|
||||
getLatestSession: () => session,
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const run = externalSdkTurnDriver.run({
|
||||
backend: 'external-sdk',
|
||||
chatSessionId: session.id,
|
||||
assistantMsgId: 'assistant-initial',
|
||||
userText: 'initial prompt',
|
||||
signal: controller.signal,
|
||||
agentConfig: {
|
||||
id: 'codex', name: 'Codex', command: 'codex', enabled: true,
|
||||
sdkBackend: 'codex', codexRuntime: 'app-server',
|
||||
},
|
||||
attachedImages: [],
|
||||
context: {
|
||||
terminalSessions: [], providers: [], toolIntegrationMode: 'mcp',
|
||||
selectedUserSkillSlugs: [], permissionMode: 'confirm',
|
||||
},
|
||||
bridge,
|
||||
ui,
|
||||
}, {
|
||||
turnId: 'turn-steer-tool-result', chatSessionId: session.id, sessionId: session.id,
|
||||
backend: 'external-sdk', signal: controller.signal, emit: () => {},
|
||||
toolOutputStore: new ToolOutputStore(), toolResultDedup: new ToolResultDedup(),
|
||||
sessionStateStore: new SessionStateStore(),
|
||||
});
|
||||
await waitFor(() => Boolean(onEvent));
|
||||
onEvent?.({
|
||||
type: 'tool-call',
|
||||
toolName: 'shell',
|
||||
toolCallId: 'tool-before-steer',
|
||||
args: { command: 'sleep 1' },
|
||||
});
|
||||
assert.equal(session.messages[0].executionStatus, 'running');
|
||||
|
||||
const steer = externalSdkTurnDriver.steer({
|
||||
chatSessionId: session.id,
|
||||
userMessageId: 'user-steer-tool-result',
|
||||
userText: 'change direction',
|
||||
prompt: 'change direction',
|
||||
attachments: [],
|
||||
attachedImages: [],
|
||||
});
|
||||
await waitFor(() => Boolean(releaseSteer));
|
||||
assert.equal(session.messages.length, 1);
|
||||
assert.equal(session.messages[0].executionStatus, 'running');
|
||||
|
||||
releaseSteer?.();
|
||||
const result = await steer;
|
||||
|
||||
assert.equal(result.status, 'accepted');
|
||||
assert.deepEqual(
|
||||
session.messages.map(message => message.role),
|
||||
['assistant', 'tool', 'user', 'assistant'],
|
||||
);
|
||||
assert.equal(session.messages[0].executionStatus, 'completed');
|
||||
assert.equal(session.messages[0].toolCalls?.[0]?.id, 'tool-before-steer');
|
||||
assert.equal(session.messages[1].toolResults?.[0]?.toolCallId, 'tool-before-steer');
|
||||
assert.equal(session.messages[2].content, 'change direction');
|
||||
assert.equal(session.messages[3].content, '');
|
||||
|
||||
onEvent?.({ type: 'text-delta', textDelta: 'continued after steer' });
|
||||
// Text deltas are rAF-batched like Catty; wait one frame before asserting.
|
||||
await new Promise<void>((resolve) => {
|
||||
if (typeof requestAnimationFrame === 'function') {
|
||||
requestAnimationFrame(() => resolve());
|
||||
return;
|
||||
}
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
assert.equal(session.messages[3].content, 'continued after steer');
|
||||
|
||||
onDone?.();
|
||||
await run;
|
||||
});
|
||||
|
||||
test('failed steering keeps buffered output on the original assistant message', async () => {
|
||||
let onEvent: ((event: Record<string, unknown>) => void) | undefined;
|
||||
let onDone: (() => void) | undefined;
|
||||
const bridge: Record<string, (...args: unknown[]) => unknown> = {
|
||||
onAiSdkAgentEvent: (_requestId, callback) => {
|
||||
onEvent = callback as (event: Record<string, unknown>) => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentDone: (_requestId, callback) => {
|
||||
onDone = callback as () => void;
|
||||
return () => {};
|
||||
},
|
||||
onAiSdkAgentError: () => () => {},
|
||||
aiSdkAgentCancel: async () => ({ ok: true }),
|
||||
aiSdkAgentStream: async () => ({ ok: true }),
|
||||
aiSdkAgentSteer: async () => {
|
||||
onEvent?.({ type: 'text-delta', textDelta: 'kept output' });
|
||||
return { status: 'not-steerable', turnKind: 'compact' };
|
||||
},
|
||||
};
|
||||
const session: AISession = {
|
||||
id: 'chat-steer-failed',
|
||||
title: 'Steer failed',
|
||||
agentId: 'codex',
|
||||
scope: { type: 'global' },
|
||||
messages: [{ id: 'assistant-initial', role: 'assistant', content: '', timestamp: 1 }],
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
};
|
||||
const ui: TurnUiCallbacks = {
|
||||
addMessageToSession: (_sessionId, message) => session.messages.push(message),
|
||||
updateLastMessage: () => { throw new Error('steering must not update the last message implicitly'); },
|
||||
updateMessageById: (_sessionId, messageId, updater) => {
|
||||
const index = session.messages.findIndex(message => message.id === messageId);
|
||||
if (index >= 0) session.messages[index] = updater(session.messages[index]);
|
||||
},
|
||||
reportStreamError: () => {},
|
||||
setStreamingForScope: () => {},
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const run = externalSdkTurnDriver.run({
|
||||
backend: 'external-sdk',
|
||||
chatSessionId: session.id,
|
||||
assistantMsgId: 'assistant-initial',
|
||||
userText: 'initial prompt',
|
||||
signal: controller.signal,
|
||||
agentConfig: {
|
||||
id: 'codex', name: 'Codex', command: 'codex', enabled: true,
|
||||
sdkBackend: 'codex', codexRuntime: 'app-server',
|
||||
},
|
||||
attachedImages: [],
|
||||
context: {
|
||||
terminalSessions: [], providers: [], toolIntegrationMode: 'mcp',
|
||||
selectedUserSkillSlugs: [], permissionMode: 'confirm',
|
||||
},
|
||||
bridge,
|
||||
ui,
|
||||
}, {
|
||||
turnId: 'turn-steer-failed', chatSessionId: session.id, sessionId: session.id,
|
||||
backend: 'external-sdk', signal: controller.signal, emit: () => {},
|
||||
toolOutputStore: new ToolOutputStore(), toolResultDedup: new ToolResultDedup(),
|
||||
sessionStateStore: new SessionStateStore(),
|
||||
});
|
||||
await waitFor(() => Boolean(onEvent));
|
||||
const result = await externalSdkTurnDriver.steer({
|
||||
chatSessionId: session.id,
|
||||
userMessageId: 'user-not-added',
|
||||
userText: 'cannot apply',
|
||||
prompt: 'cannot apply',
|
||||
attachments: [],
|
||||
attachedImages: [],
|
||||
});
|
||||
|
||||
assert.deepEqual(result, { status: 'not-steerable', turnKind: 'compact' });
|
||||
assert.equal(session.messages.length, 1);
|
||||
assert.equal(session.messages[0].content, 'kept output');
|
||||
onDone?.();
|
||||
await run;
|
||||
});
|
||||
Reference in New Issue
Block a user