chapterhouse 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -69
- package/dist/api/server.js +15 -157
- package/dist/api/server.test.js +1 -1
- package/dist/api/turn-sse.integration.test.js +36 -0
- package/dist/cli.js +0 -30
- package/dist/config.js +0 -3
- package/dist/copilot/agent-event-bus.js +41 -0
- package/dist/copilot/agent-event-bus.test.js +23 -0
- package/dist/copilot/agents.js +4 -59
- package/dist/copilot/orchestrator.js +60 -65
- package/dist/copilot/orchestrator.test.js +73 -158
- package/dist/copilot/task-event-log.js +5 -5
- package/dist/copilot/task-event-log.test.js +68 -142
- package/dist/copilot/tools.js +9 -85
- package/dist/daemon.js +0 -22
- package/dist/store/db.js +2 -50
- package/dist/store/db.test.js +0 -45
- package/package.json +1 -3
- package/web/dist/assets/index-BlIWCM11.js +217 -0
- package/web/dist/assets/index-BlIWCM11.js.map +1 -0
- package/web/dist/assets/{index-BtAcw3EP.css → index-lvHFM_ut.css} +1 -1
- package/web/dist/index.html +2 -2
- package/dist/api/ralph.js +0 -153
- package/dist/api/ralph.test.js +0 -101
- package/dist/copilot/agents.squad.test.js +0 -72
- package/dist/copilot/hooks.js +0 -157
- package/dist/copilot/hooks.test.js +0 -315
- package/dist/copilot/squad-event-bus.js +0 -27
- package/dist/copilot/tools.squad.test.js +0 -168
- package/dist/squad/charter.js +0 -125
- package/dist/squad/charter.test.js +0 -89
- package/dist/squad/context.js +0 -48
- package/dist/squad/context.test.js +0 -59
- package/dist/squad/discovery.js +0 -268
- package/dist/squad/discovery.test.js +0 -154
- package/dist/squad/index.js +0 -9
- package/dist/squad/init-cli.js +0 -109
- package/dist/squad/init.js +0 -395
- package/dist/squad/init.test.js +0 -351
- package/dist/squad/mirror.js +0 -83
- package/dist/squad/mirror.scheduler.js +0 -80
- package/dist/squad/mirror.scheduler.test.js +0 -197
- package/dist/squad/mirror.test.js +0 -172
- package/dist/squad/registry.js +0 -162
- package/dist/squad/registry.test.js +0 -31
- package/dist/squad/squad-coordinator-system-message.test.js +0 -190
- package/dist/squad/squad-session-routing.test.js +0 -260
- package/dist/squad/types.js +0 -4
- package/dist/squad/worktree.js +0 -295
- package/dist/squad/worktree.test.js +0 -189
- package/dist/store/squad-sessions.test.js +0 -341
- package/web/dist/assets/index-BR2cks94.js +0 -219
- package/web/dist/assets/index-BR2cks94.js.map +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { approveAll } from "@github/copilot-sdk";
|
|
4
|
-
import { initHookPipeline, createSessionHooks } from "./hooks.js";
|
|
5
4
|
import { createTools } from "./tools.js";
|
|
6
5
|
import { getOrchestratorSystemMessage } from "./system-message.js";
|
|
7
6
|
import { CHAPTERHOUSE_VERSION } from "../version.js";
|
|
@@ -9,16 +8,14 @@ import { config, DEFAULT_MODEL } from "../config.js";
|
|
|
9
8
|
import { loadMcpConfig } from "./mcp-config.js";
|
|
10
9
|
import { getSkillDirectories } from "./skills.js";
|
|
11
10
|
import { resetClient } from "./client.js";
|
|
12
|
-
import { logConversation, getState, setState, deleteState, getCopilotSession, upsertCopilotSession, getTaskSessionKey, getDb,
|
|
11
|
+
import { logConversation, getState, setState, deleteState, getCopilotSession, upsertCopilotSession, getTaskSessionKey, getDb, appendTaskEvent } from "../store/db.js";
|
|
13
12
|
import { maybeWriteEpisode } from "./episode-writer.js";
|
|
14
13
|
import { getWikiSummary } from "../wiki/context.js";
|
|
15
14
|
import { SESSIONS_DIR } from "../paths.js";
|
|
16
15
|
import { resolveModel } from "./router.js";
|
|
17
16
|
import { loadAgents, ensureDefaultAgents, clearActiveTasks, getAgentRegistry, setActiveAgent, parseAtMention, buildAgentRoster, getActiveTasks, } from "./agents.js";
|
|
18
|
-
import { normalizeProjectPath, setChannelProject } from "../squad/context.js";
|
|
19
|
-
import { getSquadCoordinatorSystemMessage } from "../squad/charter.js";
|
|
20
17
|
import { childLogger } from "../util/logger.js";
|
|
21
|
-
import {
|
|
18
|
+
import { agentEventBus } from "./agent-event-bus.js";
|
|
22
19
|
import { initTaskEventLog } from "./task-event-log.js";
|
|
23
20
|
import { emitTurnEvent, persistTurnEvents, scheduleClearTurnLog, } from "./turn-event-log.js";
|
|
24
21
|
import { SessionManager, SessionRegistry, SESSION_IDLE_TTL_MS, SESSION_MAX_ACTIVE, } from "./session-manager.js";
|
|
@@ -56,7 +53,7 @@ export function getLastRouteResult() {
|
|
|
56
53
|
return lastRouteResult;
|
|
57
54
|
}
|
|
58
55
|
export function subscribeTaskEvents(taskId, listener) {
|
|
59
|
-
return
|
|
56
|
+
return agentEventBus.subscribe("session:tool_call", (event) => {
|
|
60
57
|
if (event.sessionId !== taskId)
|
|
61
58
|
return;
|
|
62
59
|
const p = event.payload;
|
|
@@ -70,7 +67,7 @@ export function subscribeTaskEvents(taskId, listener) {
|
|
|
70
67
|
});
|
|
71
68
|
}
|
|
72
69
|
function emitTaskEvent(taskId, event) {
|
|
73
|
-
void
|
|
70
|
+
void agentEventBus.emit({
|
|
74
71
|
type: "session:tool_call",
|
|
75
72
|
sessionId: taskId,
|
|
76
73
|
payload: {
|
|
@@ -140,11 +137,11 @@ function getSessionConfig() {
|
|
|
140
137
|
const skillDirectories = getSkillDirectories();
|
|
141
138
|
return { tools, mcpServers, skillDirectories };
|
|
142
139
|
}
|
|
143
|
-
function getSystemMessageOptions(memorySummary
|
|
140
|
+
function getSystemMessageOptions(memorySummary) {
|
|
144
141
|
return {
|
|
145
142
|
selfEditEnabled: config.selfEditEnabled,
|
|
146
143
|
memorySummary: memorySummary || undefined,
|
|
147
|
-
agentRoster: buildAgentRoster(
|
|
144
|
+
agentRoster: buildAgentRoster(),
|
|
148
145
|
userContext: currentUserContext,
|
|
149
146
|
};
|
|
150
147
|
}
|
|
@@ -152,7 +149,7 @@ function sameUserContext(a, b) {
|
|
|
152
149
|
return a?.name === b?.name && a?.role === b?.role;
|
|
153
150
|
}
|
|
154
151
|
function updateUserContext(source) {
|
|
155
|
-
if (source.type !== "web")
|
|
152
|
+
if (source.type !== "web" && source.type !== "sse-web")
|
|
156
153
|
return;
|
|
157
154
|
const nextContext = source.user
|
|
158
155
|
? { name: source.user.name, role: source.user.role }
|
|
@@ -164,7 +161,7 @@ function updateUserContext(source) {
|
|
|
164
161
|
registry?.get("default")?.invalidateSession();
|
|
165
162
|
}
|
|
166
163
|
function updateRequestContext(source) {
|
|
167
|
-
if (source.type !== "web") {
|
|
164
|
+
if (source.type !== "web" && source.type !== "sse-web") {
|
|
168
165
|
currentAuthenticatedUser = undefined;
|
|
169
166
|
currentAuthorizationHeader = undefined;
|
|
170
167
|
return;
|
|
@@ -234,17 +231,11 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
234
231
|
backgroundCompactionThreshold: 0.80,
|
|
235
232
|
bufferExhaustionThreshold: 0.95,
|
|
236
233
|
};
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const memorySummary = getWikiSummary();
|
|
243
|
-
systemMessageContent = getOrchestratorSystemMessage({
|
|
244
|
-
...getSystemMessageOptions(memorySummary, isProjectSession ? projectRoot : undefined),
|
|
245
|
-
version: CHAPTERHOUSE_VERSION,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
234
|
+
const memorySummary = getWikiSummary();
|
|
235
|
+
const systemMessageContent = getOrchestratorSystemMessage({
|
|
236
|
+
...getSystemMessageOptions(memorySummary),
|
|
237
|
+
version: CHAPTERHOUSE_VERSION,
|
|
238
|
+
});
|
|
248
239
|
const stored = getCopilotSession(sessionKey);
|
|
249
240
|
const savedSessionId = stored?.copilotSessionId ?? (sessionKey === "default" ? getState(ORCHESTRATOR_SESSION_KEY) : undefined);
|
|
250
241
|
if (savedSessionId) {
|
|
@@ -259,7 +250,6 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
259
250
|
mcpServers,
|
|
260
251
|
skillDirectories,
|
|
261
252
|
onPermissionRequest: approveAll,
|
|
262
|
-
hooks: createSessionHooks("orchestrator"),
|
|
263
253
|
infiniteSessions,
|
|
264
254
|
});
|
|
265
255
|
log.info({ sessionKey }, "Session resumed successfully");
|
|
@@ -285,7 +275,6 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
285
275
|
mcpServers,
|
|
286
276
|
skillDirectories,
|
|
287
277
|
onPermissionRequest: approveAll,
|
|
288
|
-
hooks: createSessionHooks("orchestrator"),
|
|
289
278
|
infiniteSessions,
|
|
290
279
|
});
|
|
291
280
|
log.info({ sessionKey, sessionId: session.sessionId.slice(0, 8) }, "Session created");
|
|
@@ -299,9 +288,7 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
299
288
|
}
|
|
300
289
|
export async function initOrchestrator(client) {
|
|
301
290
|
copilotClient = client;
|
|
302
|
-
// Initialize
|
|
303
|
-
initHookPipeline();
|
|
304
|
-
// Initialize per-task ring buffer — subscribes to squadEventBus for session:tool_call events.
|
|
291
|
+
// Initialize per-task ring buffer — subscribes to agentEventBus for session:tool_call events.
|
|
305
292
|
initTaskEventLog();
|
|
306
293
|
// (Re-)create the registry — supports multiple initOrchestrator calls in tests
|
|
307
294
|
if (registry) {
|
|
@@ -450,7 +437,7 @@ async function executeOnSession(manager, item) {
|
|
|
450
437
|
.replace(/\s+/g, "-");
|
|
451
438
|
const resolvedDescription = (typeof spawnArgs?.description === "string"
|
|
452
439
|
? spawnArgs.description
|
|
453
|
-
: data.agentDescription || data.agentDisplayName || `
|
|
440
|
+
: data.agentDescription || data.agentDisplayName || `Agent dispatch: ${agentSlug}`).slice(0, 500);
|
|
454
441
|
item.onActivity({
|
|
455
442
|
kind: "subagent_started",
|
|
456
443
|
toolCallId: data.toolCallId,
|
|
@@ -521,10 +508,10 @@ async function executeOnSession(manager, item) {
|
|
|
521
508
|
.replace(/\s+/g, "-");
|
|
522
509
|
const description = (typeof spawnArgs?.description === "string"
|
|
523
510
|
? spawnArgs.description
|
|
524
|
-
: data.agentDescription || data.agentDisplayName || `
|
|
525
|
-
db.prepare(`INSERT OR IGNORE INTO agent_tasks (task_id, agent_slug, description, status, origin_channel, session_key, source) VALUES (?, ?, ?, 'running', ?, ?, '
|
|
511
|
+
: data.agentDescription || data.agentDisplayName || `Agent dispatch: ${agentSlug}`).slice(0, 500);
|
|
512
|
+
db.prepare(`INSERT OR IGNORE INTO agent_tasks (task_id, agent_slug, description, status, origin_channel, session_key, source) VALUES (?, ?, ?, 'running', ?, ?, 'adhoc')`).run(data.toolCallId, agentSlug, description, item.sourceChannel || null, sessionKey);
|
|
526
513
|
activeSubagentTaskIds.add(data.toolCallId);
|
|
527
|
-
void
|
|
514
|
+
void agentEventBus.emit({
|
|
528
515
|
type: "session:created",
|
|
529
516
|
sessionId: data.toolCallId,
|
|
530
517
|
agentName: agentSlug,
|
|
@@ -552,7 +539,7 @@ async function executeOnSession(manager, item) {
|
|
|
552
539
|
db.prepare(`UPDATE agent_tasks SET status = 'completed', completed_at = CURRENT_TIMESTAMP WHERE task_id = ?`).run(event.data.toolCallId);
|
|
553
540
|
const taskId = event.data.toolCallId;
|
|
554
541
|
const taskRow = db.prepare(`SELECT agent_slug FROM agent_tasks WHERE task_id = ?`).get(taskId);
|
|
555
|
-
void
|
|
542
|
+
void agentEventBus.emit({
|
|
556
543
|
type: "session:destroyed",
|
|
557
544
|
sessionId: taskId,
|
|
558
545
|
agentName: taskRow?.agent_slug,
|
|
@@ -580,7 +567,7 @@ async function executeOnSession(manager, item) {
|
|
|
580
567
|
activeSubagentTaskIds.delete(data.toolCallId);
|
|
581
568
|
db.prepare(`UPDATE agent_tasks SET status = 'error', result = ?, completed_at = CURRENT_TIMESTAMP WHERE task_id = ?`).run(data.error || "Subagent failed", data.toolCallId);
|
|
582
569
|
const taskRow = db.prepare(`SELECT agent_slug FROM agent_tasks WHERE task_id = ?`).get(data.toolCallId);
|
|
583
|
-
void
|
|
570
|
+
void agentEventBus.emit({
|
|
584
571
|
type: "session:error",
|
|
585
572
|
sessionId: data.toolCallId,
|
|
586
573
|
agentName: taskRow?.agent_slug,
|
|
@@ -749,30 +736,23 @@ function isRecoverableError(err) {
|
|
|
749
736
|
return false;
|
|
750
737
|
return /disconnect|connection|EPIPE|ECONNRESET|ECONNREFUSED|socket|closed|ENOENT|spawn|not found|expired|stale/i.test(msg);
|
|
751
738
|
}
|
|
752
|
-
export async function sendToOrchestrator(prompt, source, callback, attachments, onActivity, onQueued, onAdvance) {
|
|
739
|
+
export async function sendToOrchestrator(prompt, source, callback, attachments, onActivity, onQueued, onAdvance, externalTurnId) {
|
|
753
740
|
updateUserContext(source);
|
|
754
741
|
updateRequestContext(source);
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
const sourceLabel = source.type === "web" ? "web" : "background";
|
|
742
|
+
// Use the externally-supplied turnId if provided (POST→SSE path needs the ID
|
|
743
|
+
// returned to the client to match every emitted event — Fix 1 root cause).
|
|
744
|
+
const turnId = externalTurnId ?? randomUUID();
|
|
745
|
+
const sourceLabel = source.type === "background" ? "background" : "web";
|
|
760
746
|
logMessage("in", sourceLabel, prompt);
|
|
761
747
|
let sessionKey;
|
|
762
|
-
if (source.type === "
|
|
763
|
-
sessionKey = "project:" + normalizeProjectPath(source.projectPath);
|
|
764
|
-
setChannelProject(source.connectionId, normalizeProjectPath(source.projectPath));
|
|
765
|
-
bumpProjectLastUsed(normalizeProjectPath(source.projectPath));
|
|
766
|
-
}
|
|
767
|
-
else if (source.type === "background" && source.sessionKey) {
|
|
748
|
+
if ((source.type === "background" || source.type === "sse-web") && source.sessionKey) {
|
|
768
749
|
sessionKey = source.sessionKey;
|
|
769
750
|
}
|
|
770
751
|
else {
|
|
771
752
|
sessionKey = "default";
|
|
772
753
|
}
|
|
773
754
|
const channelKey = source.type === "web" ? source.connectionId : "default";
|
|
774
|
-
const
|
|
775
|
-
const mention = parseAtMention(prompt, projectRoot);
|
|
755
|
+
const mention = parseAtMention(prompt);
|
|
776
756
|
const targetAgent = mention?.agentSlug;
|
|
777
757
|
const routedPrompt = mention ? mention.message : prompt;
|
|
778
758
|
const taggedPrompt = source.type === "background"
|
|
@@ -782,8 +762,9 @@ export async function sendToOrchestrator(prompt, source, callback, attachments,
|
|
|
782
762
|
const sourceChannel = source.type === "web" ? "web" : undefined;
|
|
783
763
|
// Capture auth context at enqueue time — prevents cross-session contamination
|
|
784
764
|
// when concurrent sessions are processing simultaneously.
|
|
785
|
-
|
|
786
|
-
const
|
|
765
|
+
// sse-web carries user identity just like web (Fix 3).
|
|
766
|
+
const authUser = (source.type === "web" || source.type === "sse-web") ? source.user : undefined;
|
|
767
|
+
const authHeader = (source.type === "web" || source.type === "sse-web") ? source.authorizationHeader?.trim() || undefined : undefined;
|
|
787
768
|
const manager = registry.getOrCreate(sessionKey);
|
|
788
769
|
void (async () => {
|
|
789
770
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -797,9 +778,10 @@ export async function sendToOrchestrator(prompt, source, callback, attachments,
|
|
|
797
778
|
// type dependencies. orchestrator.ts always passes valid ActivityEvent objects.
|
|
798
779
|
onActivity: onActivity,
|
|
799
780
|
turnId,
|
|
800
|
-
// Background messages skip queue visibility — only
|
|
801
|
-
|
|
802
|
-
|
|
781
|
+
// Background messages skip queue visibility — only user-initiated messages need it.
|
|
782
|
+
// sse-web is user-initiated (Fix 2).
|
|
783
|
+
onQueued: source.type !== "background" ? onQueued : undefined,
|
|
784
|
+
onAdvance: source.type !== "background" ? onAdvance : undefined,
|
|
803
785
|
sourceChannel,
|
|
804
786
|
targetAgent,
|
|
805
787
|
channelKey,
|
|
@@ -864,19 +846,19 @@ export async function sendToOrchestrator(prompt, source, callback, attachments,
|
|
|
864
846
|
* replacement turn starts. Use to emit a `turn-interrupted` SSE event so the
|
|
865
847
|
* frontend can drop the partial in-flight bubble.
|
|
866
848
|
*/
|
|
867
|
-
export async function interruptCurrentTurn(sessionKey, newPrompt, source, callback, attachments, onActivity, onInterrupted) {
|
|
849
|
+
export async function interruptCurrentTurn(sessionKey, newPrompt, source, callback, attachments, onActivity, onInterrupted, externalTurnId) {
|
|
868
850
|
const manager = registry?.get(sessionKey);
|
|
869
851
|
// If no session exists or it isn't processing, fall back to a normal send.
|
|
870
852
|
if (!manager || !manager.isProcessing) {
|
|
871
|
-
return sendToOrchestrator(newPrompt, source, callback, attachments, onActivity);
|
|
853
|
+
return sendToOrchestrator(newPrompt, source, callback, attachments, onActivity, undefined, undefined, externalTurnId);
|
|
872
854
|
}
|
|
873
855
|
updateUserContext(source);
|
|
874
856
|
updateRequestContext(source);
|
|
875
|
-
const turnId = randomUUID();
|
|
876
|
-
const sourceLabel = source.type === "
|
|
857
|
+
const turnId = externalTurnId ?? randomUUID();
|
|
858
|
+
const sourceLabel = source.type === "background" ? "background" : "web";
|
|
877
859
|
const sourceChannel = source.type === "web" ? "web" : undefined;
|
|
878
|
-
const authUser = source.type === "web" ? source.user : undefined;
|
|
879
|
-
const authHeader = source.type === "web" ? source.authorizationHeader?.trim() || undefined : undefined;
|
|
860
|
+
const authUser = (source.type === "web" || source.type === "sse-web") ? source.user : undefined;
|
|
861
|
+
const authHeader = (source.type === "web" || source.type === "sse-web") ? source.authorizationHeader?.trim() || undefined : undefined;
|
|
880
862
|
const taggedPrompt = source.type === "background"
|
|
881
863
|
? newPrompt
|
|
882
864
|
: `[via ${sourceLabel}] ${newPrompt}`;
|
|
@@ -965,8 +947,8 @@ export async function interruptCurrentTurn(sessionKey, newPrompt, source, callba
|
|
|
965
947
|
export function enqueueForSse(opts) {
|
|
966
948
|
const { sessionKey, prompt, attachments, authUser, authHeader, interrupt } = opts;
|
|
967
949
|
const turnId = randomUUID();
|
|
968
|
-
|
|
969
|
-
const
|
|
950
|
+
// sse-web carries auth and enables onQueued — unlike "background" (Fixes 2 & 3).
|
|
951
|
+
const source = { type: "sse-web", sessionKey, user: authUser, authorizationHeader: authHeader };
|
|
970
952
|
// Emit turn:started immediately so the SSE client sees it before any delta
|
|
971
953
|
emitTurnEvent(sessionKey, { type: "turn:started", turnId, sessionKey, prompt, attachments });
|
|
972
954
|
const callback = (text, done, tid) => {
|
|
@@ -986,10 +968,12 @@ export function enqueueForSse(opts) {
|
|
|
986
968
|
scheduleClearTurnLog(abortedTurnId);
|
|
987
969
|
};
|
|
988
970
|
if (interrupt) {
|
|
989
|
-
|
|
971
|
+
// Pass the outer turnId so the replacement turn uses the same ID (Fix 1).
|
|
972
|
+
void interruptCurrentTurn(sessionKey, prompt, source, callback, attachments, undefined, onInterrupted, turnId);
|
|
990
973
|
}
|
|
991
974
|
else {
|
|
992
|
-
|
|
975
|
+
// Pass the outer turnId so sendToOrchestrator uses it instead of generating a new one (Fix 1).
|
|
976
|
+
void sendToOrchestrator(prompt, source, callback, attachments, undefined, onQueued, undefined, turnId);
|
|
993
977
|
}
|
|
994
978
|
return turnId;
|
|
995
979
|
}
|
|
@@ -998,15 +982,26 @@ export async function cancelCurrentMessage() {
|
|
|
998
982
|
if (!registry)
|
|
999
983
|
return false;
|
|
1000
984
|
let drained = 0;
|
|
985
|
+
// Capture (sessionKey, turnId) before aborting so we can emit terminal events.
|
|
1001
986
|
const aborts = [];
|
|
1002
|
-
for (const [, manager] of registry.getAll()) {
|
|
987
|
+
for (const [sessionKey, manager] of registry.getAll()) {
|
|
1003
988
|
drained += manager.cancelQueued();
|
|
1004
989
|
if (manager.isProcessing) {
|
|
1005
|
-
|
|
990
|
+
const turnId = manager.currentTurnId;
|
|
991
|
+
aborts.push({ promise: manager.abortCurrentTurn(), sessionKey, turnId });
|
|
1006
992
|
}
|
|
1007
993
|
}
|
|
1008
|
-
const results = await Promise.all(aborts);
|
|
994
|
+
const results = await Promise.all(aborts.map((a) => a.promise));
|
|
1009
995
|
const aborted = results.some(Boolean);
|
|
996
|
+
// Emit turn:interrupted on per-session SSE streams for any turn that was aborted (Fix 4).
|
|
997
|
+
for (let i = 0; i < aborts.length; i++) {
|
|
998
|
+
if (results[i] && aborts[i].turnId) {
|
|
999
|
+
const { sessionKey, turnId } = aborts[i];
|
|
1000
|
+
emitTurnEvent(sessionKey, { type: "turn:interrupted", turnId: turnId, sessionKey });
|
|
1001
|
+
persistTurnEvents(turnId, sessionKey);
|
|
1002
|
+
scheduleClearTurnLog(turnId);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1010
1005
|
return aborted || drained > 0;
|
|
1011
1006
|
}
|
|
1012
1007
|
/** Switch the model on the live default orchestrator session without destroying it. */
|