Merge remote-tracking branch 'origin/v0.2' into fix/310-artificer-workspace-cleanliness

This commit is contained in:
root
2026-07-12 09:20:22 +02:00
7 changed files with 132 additions and 26 deletions
+25 -5
View File
@@ -158,19 +158,33 @@ export class CodexStdioEventReducer {
private readonly assistantDeltaProgress = createAssistantDeltaProgressState();
private readonly completedAssistantMessages: CompletedAssistantMessage[] = [];
private readonly completedAssistantItemIds = new Set<string>();
private anonymousAssistantDeltaOpen = true;
private readonly deferredTerminalEvents: BackendEvent[] = [];
private readonly suppressedNotifications = createSuppressedNotificationSummary();
reduce(message: JsonRecord): CodexStdioReducedNotification {
const normalized = normalizeCodexNotification(message, this.suppressedNotifications);
const events = [...normalized.events];
if (normalized.startedAssistantMessage?.itemId === null) {
closeAssistantDeltaProgress(this.assistantDeltaProgress, null);
this.anonymousAssistantDeltaOpen = true;
}
if (normalized.assistantDelta) {
this.assistantText += normalized.assistantDelta.text;
const progress = recordAssistantDeltaProgress(this.assistantDeltaProgress, normalized.assistantDelta);
if (progress) events.push(progress);
const itemId = normalized.assistantDelta.itemId;
if (itemId && this.completedAssistantItemIds.has(itemId)) {
recordSuppressedNotification(this.suppressedNotifications, "item/agentMessage/delta:completed-item-replay", "agentMessage");
} else if (itemId === null && !this.anonymousAssistantDeltaOpen) {
recordSuppressedNotification(this.suppressedNotifications, "item/agentMessage/delta:completed-anonymous-replay", "agentMessage");
} else {
this.assistantText += normalized.assistantDelta.text;
const progress = recordAssistantDeltaProgress(this.assistantDeltaProgress, normalized.assistantDelta);
if (progress) events.push(progress);
}
}
if (normalized.completedAssistantMessage) {
const itemId = normalized.completedAssistantMessage.itemId;
closeAssistantDeltaProgress(this.assistantDeltaProgress, itemId);
if (itemId === null) this.anonymousAssistantDeltaOpen = false;
if (itemId && this.completedAssistantItemIds.has(itemId)) {
recordSuppressedNotification(this.suppressedNotifications, "item/completed:duplicate-agentMessage", "agentMessage");
} else {
@@ -1192,7 +1206,7 @@ function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
}
function normalizeCodexNotification(message: JsonRecord, suppressed: SuppressedNotificationSummary): { events: BackendEvent[]; terminalEvents?: BackendEvent[]; assistantDelta?: { itemId: string | null; text: string }; completedAssistantMessage?: CompletedAssistantMessage; threadId?: string; turnId?: string; terminal?: { status: TerminalStatus; failureKind: FailureKind | null; message: string | null } } {
function normalizeCodexNotification(message: JsonRecord, suppressed: SuppressedNotificationSummary): { events: BackendEvent[]; terminalEvents?: BackendEvent[]; assistantDelta?: { itemId: string | null; text: string }; startedAssistantMessage?: { itemId: string | null }; completedAssistantMessage?: CompletedAssistantMessage; threadId?: string; turnId?: string; terminal?: { status: TerminalStatus; failureKind: FailureKind | null; message: string | null } } {
const method = typeof message.method === "string" ? message.method : "unknown";
const params = asRecordAt(message, "params");
if (method === "thread/started") {
@@ -1216,6 +1230,7 @@ function normalizeCodexNotification(message: JsonRecord, suppressed: SuppressedN
if ((method === "item/started" || method === "item/completed") && asRecordAt(params, "item").type === "agentMessage") {
const item = asRecordAt(params, "item");
const itemId = stringAt(item, "id") ?? stringAt(params, "itemId");
if (method === "item/started") return { events: [], startedAssistantMessage: { itemId: itemId ?? null } };
const text = method === "item/completed" ? agentMessageText(item) : "";
const completedAssistantMessage = text.trim().length > 0 ? { itemId: itemId ?? null, text } : undefined;
return {
@@ -1349,8 +1364,9 @@ function finalAssistantMessageForTurn(completedMessages: CompletedAssistantMessa
function assistantFinalSealEvent(message: FinalAssistantMessage): BackendEvent {
return {
type: "assistant_message",
type: "backend_status",
payload: {
phase: "assistant-message-final-sealed",
itemId: message.itemId,
source: "assistant-message-final-seal",
messageIndex: message.messageIndex,
@@ -1378,6 +1394,10 @@ function createAssistantDeltaProgressState(): AssistantDeltaProgressState {
return new Map();
}
function closeAssistantDeltaProgress(state: AssistantDeltaProgressState, itemId: string | null): void {
state.delete(itemId ?? "default");
}
function recordAssistantDeltaProgress(state: AssistantDeltaProgressState, delta: { itemId: string | null; text: string }): BackendEvent | null {
if (!delta.text) return null;
const key = delta.itemId ?? "default";
+12 -5
View File
@@ -32,6 +32,12 @@ export function requireExternallyAppendableEventType(value: unknown): EventType
}
export function normalizeRunEventPayload(type: EventType, payload: JsonRecord): JsonRecord {
if (payload.finalSeal === true) {
if (type !== "backend_status" && type !== "assistant_message") {
throw new AgentRunError("schema-invalid", "assistant final seal must use backend_status or legacy assistant_message event type", { httpStatus: 400 });
}
return normalizeAssistantFinalSeal(payload);
}
if (type === "terminal_status") return normalizeTerminalStatusPayload(payload);
if (type === "command_output") return normalizeCommandOutputPayload(payload);
if (type === "user_message") return normalizeUserMessagePayload(payload);
@@ -123,7 +129,6 @@ function nonEmptyText(value: unknown): string | null {
function normalizeTextPayload(payload: JsonRecord): JsonRecord {
const { text: _text, delta: _delta, content: _content, summary: _summary, textBytes: _textBytes, textTruncated: _textTruncated, outputBytes: _outputBytes, outputTruncated: _outputTruncated, ...rest } = payload;
const value = typeof payload.text === "string" ? payload.text : typeof payload.delta === "string" ? payload.delta : typeof payload.content === "string" ? payload.content : "";
if (payload.finalSeal === true) return normalizeAssistantFinalSeal(rest, value);
if (payload.durableText === true) return normalizeDurableAssistantText(rest, value);
if (payload.replyAuthority === true || payload.final === true) {
const text = redactText(value);
@@ -153,14 +158,16 @@ function normalizeDurableAssistantText(payload: JsonRecord, body: string): JsonR
};
}
function normalizeAssistantFinalSeal(payload: JsonRecord, body: string): JsonRecord {
const replyRef = typeof payload.replyRef === "object" && payload.replyRef !== null && !Array.isArray(payload.replyRef) ? payload.replyRef as JsonRecord : null;
function normalizeAssistantFinalSeal(payload: JsonRecord): JsonRecord {
const { text: _text, delta: _delta, content: _content, summary: _summary, textBytes: _textBytes, textTruncated: _textTruncated, outputBytes: _outputBytes, outputTruncated: _outputTruncated, ...rest } = payload;
const body = typeof payload.text === "string" ? payload.text : typeof payload.delta === "string" ? payload.delta : typeof payload.content === "string" ? payload.content : "";
const replyRef = typeof rest.replyRef === "object" && rest.replyRef !== null && !Array.isArray(rest.replyRef) ? rest.replyRef as JsonRecord : null;
const textSha256 = replyRef && typeof replyRef.textSha256 === "string" ? replyRef.textSha256 : "";
if (body.length > 0) throw new AgentRunError("schema-invalid", "assistant final seal must not carry text, content, or delta", { httpStatus: 400 });
if (payload.replyAuthority !== true || payload.final !== true || replyRef?.eventType !== "assistant_message" || typeof replyRef.source !== "string" || !/^[0-9a-f]{64}$/u.test(textSha256)) {
if (rest.replyAuthority !== true || rest.final !== true || replyRef?.eventType !== "assistant_message" || typeof replyRef.source !== "string" || !/^[0-9a-f]{64}$/u.test(textSha256)) {
throw new AgentRunError("schema-invalid", "assistant final seal requires final replyAuthority and an assistant_message replyRef with source and textSha256", { httpStatus: 400 });
}
return { ...payload, replyRef: redactJson(replyRef), textBytes: 0, textTruncated: false, outputBytes: 0, outputTruncated: false };
return { ...rest, replyRef: redactJson(replyRef), textBytes: 0, textTruncated: false, outputBytes: 0, outputTruncated: false };
}
function normalizeToolCallPayload(payload: JsonRecord): JsonRecord {
+2 -2
View File
@@ -795,7 +795,7 @@ function resultFailureDetails(events: RunEvent[], terminal: TerminalStatus | nul
function assistantReply(events: RunEvent[]): AssistantReplySummary {
const assistantEvents = events.filter((event) => event.type === "assistant_message");
const sealedReply = assistantReplyFromFinalSeal(assistantEvents);
const sealedReply = assistantReplyFromFinalSeal(events);
if (sealedReply) return sealedReply;
const latestAuthoritative = [...assistantEvents].reverse().find((event) => (event.payload.replyAuthority === true || event.payload.final === true) && textPayload(event.payload).length > 0);
if (latestAuthoritative) return assistantReplySummary(latestAuthoritative);
@@ -824,7 +824,7 @@ function assistantReplyFromFinalSeal(events: RunEvent[]): AssistantReplySummary
}
function assistantReplyRefMatches(event: RunEvent, replyRef: JsonRecord): boolean {
if (event.payload.finalSeal === true || textPayload(event.payload).length === 0) return false;
if (event.type !== "assistant_message" || event.payload.finalSeal === true || textPayload(event.payload).length === 0) return false;
if (stringJsonValue(event.payload.source) !== stringJsonValue(replyRef.source)) return false;
if ((stringJsonValue(event.payload.itemId) ?? null) !== (stringJsonValue(replyRef.itemId) ?? null)) return false;
if ((numberJsonValue(event.payload.messageIndex) ?? null) !== (numberJsonValue(replyRef.messageIndex) ?? null)) return false;
+1 -1
View File
@@ -716,7 +716,7 @@ async function writeTerminalOutbox(runnerLog: RunnerLogSink, payload: JsonRecord
function shouldKeepTerminalOutboxEvent(event: BackendEvent): boolean {
if (event.type === "terminal_status") return true;
if (event.type === "backend_status") return event.payload.phase === "turn/completed";
if (event.type === "backend_status") return event.payload.phase === "turn/completed" || event.payload.finalSeal === true;
if (event.type !== "assistant_message") return false;
return event.payload.final === true || event.payload.replyAuthority === true;
}
@@ -36,10 +36,10 @@ const selfTest: SelfTestCase = async (context) => {
const reconstructed = reconstructAgentRunEventsFromStdioFrames(frames, { sessionId, traceId });
assert.equal(reconstructed.selectedFrameCount, 8);
assert.equal(reconstructed.reducerGroupCount, 1);
assert.equal(reconstructed.records.length, 10);
assert.equal(reconstructed.records.length, 9);
assert.equal((reconstructed.ignoredFrames as JsonRecord)["direction-not-stdout"], 1);
assert.equal((reconstructed.ignoredFrames as JsonRecord)["identity-mismatch"], 1);
assert.deepEqual(reconstructed.records.map((record) => record.value.sourceSeq), Array.from({ length: 10 }, (_, index) => index + 1));
assert.deepEqual(reconstructed.records.map((record) => record.value.sourceSeq), Array.from({ length: 9 }, (_, index) => index + 1));
for (const record of reconstructed.records) {
assert.equal(record.topic, DEFAULT_STDIO_RECONSTRUCTION_TOPIC);
assert.equal(record.key, sessionId);
@@ -57,10 +57,11 @@ const selfTest: SelfTestCase = async (context) => {
const finalSeal = reconstructed.records.find((record) => {
const event = record.value.event as JsonRecord;
const payload = event.payload as JsonRecord;
return event.type === "assistant_message" && payload.replyAuthority === true;
return event.type === "backend_status" && payload.finalSeal === true;
});
const finalSealPayload = (finalSeal?.value.event as JsonRecord).payload as JsonRecord;
assert.equal(finalSealPayload.finalSeal, true);
assert.equal(finalSealPayload.phase, "assistant-message-final-sealed");
assert.equal(Object.hasOwn(finalSealPayload, "text"), false);
const replyRef = finalSealPayload.replyRef as JsonRecord;
const finalAssistant = reconstructed.records.find((record) => {
@@ -221,7 +222,7 @@ async function assertOneShotPublisherOwnership(frames: ReturnType<typeof parseSt
reader: async () => ({ records: frames, scanned: frames.length, invalidJson: 0, timedOut: false, elapsedMs: 0 }),
});
assert.equal(producers.length, 2);
assert.deepEqual(producers[1], { connectCalls: 1, sendCalls: 10, disconnectCalls: 1 });
assert.deepEqual(producers[1], { connectCalls: 1, sendCalls: 9, disconnectCalls: 1 });
assert.equal(producers[0]?.disconnectCalls, 0);
await publishAgentRunKafkaMessage(managerConfig, managerMessage);
+54 -2
View File
@@ -31,6 +31,7 @@ const selfTest: SelfTestCase = async (context) => {
try {
const client = new ManagerClient(server.baseUrl);
await assertHistoryReplaySealedOnce(client, context, server.baseUrl);
await assertAnonymousGenerationFence(client, context, server.baseUrl);
await assertDeltaFallbackUsesBodylessSeal(client, context, server.baseUrl);
return {
name: "assistant-final-seal",
@@ -40,6 +41,9 @@ const selfTest: SelfTestCase = async (context) => {
"assistant-final-seal-late-final",
"assistant-final-seal-manager-result-pointer",
"assistant-final-seal-delta-fallback",
"assistant-progress-completed-item-fence",
"assistant-progress-anonymous-generation-fence",
"assistant-final-seal-control-event",
],
};
} finally {
@@ -47,6 +51,42 @@ const selfTest: SelfTestCase = async (context) => {
}
};
async function assertAnonymousGenerationFence(client: ManagerClient, context: SelfTestContext, managerUrl: string): Promise<void> {
const command = await createRunWithCommand(client, context, "anonymous assistant generation fence", "selftest-assistant-anonymous-generation-fence", 15_000);
const run = await runOnce({
managerUrl,
runId: command.runId,
codexCommand: context.fakeCodexCommand,
codexArgs: context.fakeCodexArgs,
codexHome: context.codexHome,
env: {
CODEX_HOME: context.codexHome,
AGENTRUN_FAKE_CODEX_MODE: "assistant-anonymous-generation-fence",
},
oneShot: true,
}) as JsonRecord;
assert.equal(run.terminalStatus, "completed");
const events = await listEvents(client, command.runId);
const completedMessages = events.filter((event) => event.type === "assistant_message" && event.payload.source === "completed-agent-message");
assert.deepEqual(completedMessages.map((event) => event.payload.text), ["Anonymous first.", "Anonymous second final."], "new anonymous item/started must open a distinct legal generation");
assert.ok(completedMessages.every((event) => event.payload.itemId === null));
const progressEvents = events.filter((event) => event.type === "assistant_message" && event.payload.source === "agent-message-delta-progress");
assert.equal(progressEvents.length, 1, "only the reopened anonymous generation's live progress must remain visible");
assert.equal(progressEvents[0]?.payload.progressFlush, false);
assert.ok(progressEvents[0]!.seq < completedMessages[1]!.seq);
const suppression = events.find((event) => event.type === "backend_status" && event.payload.phase === "codex-app-server-notifications-suppressed");
const replayDeltaMethod = asRecords(suppression?.payload.methods).find((item) => item.method === "item/agentMessage/delta:completed-anonymous-replay");
assert.equal(replayDeltaMethod?.count, 1, "late anonymous delta must stay closed until the next anonymous item/started");
const seal = events.find((event) => event.type === "backend_status" && event.payload.finalSeal === true);
assert.ok(seal);
assert.equal(asRecord(seal.payload.replyRef).itemId, null);
assert.equal(asRecord(seal.payload.replyRef).messageIndex, 2);
const result = await client.get(`/api/v1/runs/${command.runId}/commands/${command.commandId}/result`) as JsonRecord;
assert.equal(result.reply, "Anonymous second final.");
assert.equal(result.finalAssistantSeq, seal.seq);
}
async function assertHistoryReplaySealedOnce(client: ManagerClient, context: SelfTestContext, managerUrl: string): Promise<void> {
const lateFinalText = `Late unique final answer:${"x".repeat(5_000)} Authorization: Bearer REDACTED`;
const rawLateFinalSecret = "final-secret-token-value";
@@ -78,14 +118,23 @@ async function assertHistoryReplaySealedOnce(client: ManagerClient, context: Sel
for (const itemId of ["msg_replay_progress", "msg_same_text_a", "msg_same_text_b", "msg_late_final_seal"]) {
assert.equal(completedMessages.filter((event) => event.payload.itemId === itemId).length, 1, `item ${itemId} must be durable exactly once`);
}
const progressEvents = events.filter((event) => event.type === "assistant_message" && event.payload.source === "agent-message-delta-progress");
assert.ok(progressEvents.some((event) => event.payload.itemId === "msg_late_final_seal" && event.payload.progressFlush === false), "long assistant deltas must remain visible before completion");
assert.equal(progressEvents.some((event) => event.payload.progressFlush === true), false, "completed item delta buffers must not be replayed by finalize()");
for (const progress of progressEvents) {
const completed = completedMessages.find((event) => event.payload.itemId === progress.payload.itemId);
assert.ok(completed && progress.seq < completed.seq, `item ${String(progress.payload.itemId)} progress must precede its durable completion`);
}
assert.equal(events.some((event) => event.type === "assistant_message" && typeof event.payload.text !== "string"), false, "bodyless control events must not be encoded as assistant messages");
const lateFinal = completedMessages.find((event) => event.payload.itemId === "msg_late_final_seal");
assert.equal(lateFinal?.payload.text, lateFinalText, "the unique durable message body must remain untruncated for seal dereferencing");
assert.equal(lateFinal?.payload.outputTruncated, false);
assert.equal(JSON.stringify(events).includes(rawLateFinalSecret), false, "secret-like final text must be redacted before becoming durable");
const seals = events.filter((event) => event.type === "assistant_message" && event.payload.finalSeal === true);
const seals = events.filter((event) => event.type === "backend_status" && event.payload.finalSeal === true);
assert.equal(seals.length, 1, "one completed turn must have exactly one final seal");
const seal = seals[0]!;
assert.equal(seal.payload.phase, "assistant-message-final-sealed");
assert.equal(Object.hasOwn(seal.payload, "text"), false, "final seal must not duplicate the assistant body");
assert.equal(Object.hasOwn(seal.payload, "content"), false, "final seal must not duplicate the assistant body through content");
assert.equal(Object.hasOwn(seal.payload, "delta"), false, "final seal must not duplicate the assistant body through delta");
@@ -101,6 +150,8 @@ async function assertHistoryReplaySealedOnce(client: ManagerClient, context: Sel
assert.ok(suppression, "history replay suppression must remain visible without persisting duplicate bodies");
const replayMethod = asRecords(suppression?.payload.methods).find((item) => item.method === "item/completed:duplicate-agentMessage");
assert.equal(replayMethod?.count, 5);
const replayDeltaMethod = asRecords(suppression?.payload.methods).find((item) => item.method === "item/agentMessage/delta:completed-item-replay");
assert.equal(replayDeltaMethod?.count, 4);
const toolEvents = events.filter((event) => event.type === "tool_call" && event.payload.itemId === "tool_before_history_replay");
assert.equal(toolEvents.length, 2, "assistant replay suppression must neither replay nor drop the tool start/completion pair");
@@ -137,8 +188,9 @@ async function assertDeltaFallbackUsesBodylessSeal(client: ManagerClient, contex
const fallback = events.filter((event) => event.type === "assistant_message" && event.payload.source === "agent-message-delta-fallback");
assert.equal(fallback.length, 1, "delta-only turns must persist one full fallback body before sealing it");
assert.equal(fallback[0]?.payload.text, "fake codex stdio reply");
const seal = events.find((event) => event.type === "assistant_message" && event.payload.finalSeal === true);
const seal = events.find((event) => event.type === "backend_status" && event.payload.finalSeal === true);
assert.ok(seal);
assert.equal(seal.payload.phase, "assistant-message-final-sealed");
assert.equal(Object.hasOwn(seal.payload, "text"), false);
assert.equal(asRecord(seal.payload.replyRef).source, "agent-message-delta-fallback");
+33 -7
View File
@@ -242,29 +242,55 @@ for await (const line of rl) {
if (mode === "assistant-history-replay-final-seal") {
turnCounter += 1;
const turn = { id: `turn_selftest_${turnCounter}`, status: "completed" };
const replayText = "Progress before history replay.";
const sameText = "Legitimate identical assistant text.";
const lateFinalText = `Late unique final answer:${"x".repeat(5_000)} Authorization: Bearer final-secret-token-value`;
respond(message.id, { turn });
notify("turn/started", { turn });
notify("item/started", { item: { id: "tool_before_history_replay", type: "commandExecution", command: "echo durable-once", status: "running" } });
notify("item/completed", { item: { id: "tool_before_history_replay", type: "commandExecution", command: "echo durable-once", status: "completed", exitCode: 0 } });
notify("item/completed", { item: { id: "msg_replay_progress", type: "agentMessage", text: "Progress before history replay." } });
notify("item/completed", { item: { id: "msg_same_text_a", type: "agentMessage", text: "Legitimate identical assistant text." } });
notify("item/completed", { item: { id: "msg_same_text_b", type: "agentMessage", text: "Legitimate identical assistant text." } });
notify("item/completed", { item: { id: "msg_replay_progress", type: "agentMessage", text: "Progress before history replay." } });
notify("item/agentMessage/delta", { itemId: "msg_replay_progress", delta: replayText });
notify("item/completed", { item: { id: "msg_replay_progress", type: "agentMessage", text: replayText } });
notify("item/agentMessage/delta", { itemId: "msg_same_text_a", delta: sameText });
notify("item/completed", { item: { id: "msg_same_text_a", type: "agentMessage", text: sameText } });
notify("item/agentMessage/delta", { itemId: "msg_same_text_b", delta: sameText });
notify("item/completed", { item: { id: "msg_same_text_b", type: "agentMessage", text: sameText } });
notify("item/completed", { item: { id: "msg_replay_progress", type: "agentMessage", text: replayText } });
notify("turn/completed", { turn });
setTimeout(() => {
notify("item/completed", { item: { id: "msg_replay_progress", type: "agentMessage", text: "Progress before history replay." } });
notify("item/completed", { item: { id: "msg_same_text_a", type: "agentMessage", text: "Legitimate identical assistant text." } });
notify("item/completed", { item: { id: "msg_same_text_b", type: "agentMessage", text: "Legitimate identical assistant text." } });
notify("item/agentMessage/delta", { itemId: "msg_replay_progress", delta: replayText });
notify("item/completed", { item: { id: "msg_replay_progress", type: "agentMessage", text: replayText } });
notify("item/agentMessage/delta", { itemId: "msg_same_text_a", delta: sameText });
notify("item/completed", { item: { id: "msg_same_text_a", type: "agentMessage", text: sameText } });
notify("item/agentMessage/delta", { itemId: "msg_same_text_b", delta: sameText });
notify("item/completed", { item: { id: "msg_same_text_b", type: "agentMessage", text: sameText } });
}, 20).unref?.();
setTimeout(() => {
notify("item/agentMessage/delta", { itemId: "msg_late_final_seal", delta: lateFinalText });
notify("item/completed", { item: { id: "msg_late_final_seal", type: "agentMessage", text: lateFinalText } });
}, 40).unref?.();
setTimeout(() => {
notify("item/agentMessage/delta", { itemId: "msg_late_final_seal", delta: lateFinalText });
notify("item/completed", { item: { id: "msg_late_final_seal", type: "agentMessage", text: lateFinalText } });
}, 60).unref?.();
continue;
}
if (mode === "assistant-anonymous-generation-fence") {
turnCounter += 1;
const turn = { id: `turn_selftest_${turnCounter}`, status: "completed" };
const secondProgress = `Anonymous second progress:${"y".repeat(600)}`;
respond(message.id, { turn });
notify("turn/started", { turn });
notify("item/started", { item: { type: "agentMessage" } });
notify("item/agentMessage/delta", { delta: "Anonymous first." });
notify("item/completed", { item: { type: "agentMessage", text: "Anonymous first." } });
notify("item/agentMessage/delta", { delta: "Anonymous first." });
notify("item/started", { item: { type: "agentMessage" } });
notify("item/agentMessage/delta", { delta: secondProgress });
notify("item/completed", { item: { type: "agentMessage", text: "Anonymous second final." } });
notify("turn/completed", { turn });
continue;
}
if (mode === "web-search-progress") {
turnCounter += 1;
const turn = { id: `turn_selftest_${turnCounter}`, status: "completed" };