chapterhouse 0.5.2 → 0.7.0
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/.pr-types.json +14 -0
- package/README.md +6 -0
- package/dist/api/agent-edit-access.js +11 -0
- package/dist/api/agents.api.test.js +48 -0
- package/dist/api/server.js +182 -11
- package/dist/api/server.test.js +334 -3
- package/dist/config.test.js +29 -0
- package/dist/copilot/agent-event-bus.js +1 -0
- package/dist/copilot/agents.js +114 -46
- package/dist/copilot/agents.mcp-servers.test.js +87 -0
- package/dist/copilot/agents.parse.test.js +69 -0
- package/dist/copilot/agents.test.js +125 -1
- package/dist/copilot/memory-coordinator.js +234 -0
- package/dist/copilot/memory-coordinator.test.js +257 -0
- package/dist/copilot/orchestrator.js +81 -221
- package/dist/copilot/orchestrator.test.js +238 -1
- package/dist/copilot/pr-title.js +92 -0
- package/dist/copilot/pr-title.test.js +54 -0
- package/dist/copilot/router.test.js +30 -0
- package/dist/copilot/session-manager.js +34 -0
- package/dist/copilot/threat-model.js +50 -0
- package/dist/copilot/threat-model.test.js +129 -0
- package/dist/copilot/tools.js +61 -37
- package/dist/copilot/tools.wiki.test.js +15 -6
- package/dist/setup.js +15 -5
- package/dist/setup.test.js +20 -3
- package/dist/sprint-merge.js +168 -0
- package/dist/sprint-merge.test.js +131 -0
- package/dist/store/db.js +63 -0
- package/dist/store/db.test.js +279 -0
- package/dist/test/setup-env.js +2 -1
- package/dist/test/setup-env.test.js +8 -1
- package/package.json +8 -1
- package/web/dist/assets/index-DuKYxMIR.css +10 -0
- package/web/dist/assets/index-DytB69KC.js +223 -0
- package/web/dist/assets/index-DytB69KC.js.map +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-CPaILy2j.js +0 -223
- package/web/dist/assets/index-CPaILy2j.js.map +0 -1
- package/web/dist/assets/index-Cs7AGeaL.css +0 -10
|
@@ -3,13 +3,9 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { approveAll } from "@github/copilot-sdk";
|
|
4
4
|
import { createTools } from "./tools.js";
|
|
5
5
|
import { getOrchestratorSystemMessage } from "./system-message.js";
|
|
6
|
-
import { renderHotTierForActiveScope } from "../memory/hot-tier.js";
|
|
7
|
-
import { getHotTierEntries, renderHotTierXML } from "../memory/hot-tier.js";
|
|
8
6
|
import { getActiveScope, withActiveScope } from "../memory/active-scope.js";
|
|
9
7
|
import { getScope } from "../memory/scopes.js";
|
|
10
|
-
import {
|
|
11
|
-
import { isHousekeepingInFlight, runHousekeeping } from "../memory/housekeeping.js";
|
|
12
|
-
import { runEndOfTaskMemoryHook } from "../memory/eot.js";
|
|
8
|
+
import { MemoryCoordinator } from "./memory-coordinator.js";
|
|
13
9
|
import { CHAPTERHOUSE_VERSION } from "../version.js";
|
|
14
10
|
import { config, DEFAULT_MODEL } from "../config.js";
|
|
15
11
|
import { loadMcpConfig } from "./mcp-config.js";
|
|
@@ -20,7 +16,7 @@ import { maybeWriteEpisode } from "./episode-writer.js";
|
|
|
20
16
|
import { getWikiSummary } from "../wiki/context.js";
|
|
21
17
|
import { SESSIONS_DIR } from "../paths.js";
|
|
22
18
|
import { resolveModel } from "./router.js";
|
|
23
|
-
import { loadAgents, ensureDefaultAgents, clearActiveTasks, getAgentRegistry, setActiveAgent, parseAtMention, buildAgentRoster, getActiveTasks, getAgent, composeAgentSystemMessage, filterToolsForAgent, withToolTaskContext, } from "./agents.js";
|
|
19
|
+
import { loadAgents, ensureDefaultAgents, clearActiveTasks, getAgentRegistry, setActiveAgent, parseAtMention, buildAgentRoster, getActiveTasks, getAgent, composeAgentSystemMessage, filterMcpServersForAgent, filterToolsForAgent, withToolTaskContext, } from "./agents.js";
|
|
24
20
|
import * as agentsModule from "./agents.js";
|
|
25
21
|
import { childLogger } from "../util/logger.js";
|
|
26
22
|
import { agentEventBus } from "./agent-event-bus.js";
|
|
@@ -77,157 +73,15 @@ let currentUserContext;
|
|
|
77
73
|
let currentAuthenticatedUser;
|
|
78
74
|
let currentAuthorizationHeader;
|
|
79
75
|
let lastRouteResult;
|
|
80
|
-
|
|
81
|
-
const checkpointTurnsBySession = new Map();
|
|
82
|
-
const housekeepingTurnsBySession = new Map();
|
|
83
|
-
const MAX_CHECKPOINT_CHARS_PER_SIDE = 4_000;
|
|
76
|
+
let memoryCoordinator;
|
|
84
77
|
export function getLastRouteResult() {
|
|
85
78
|
return lastRouteResult;
|
|
86
79
|
}
|
|
87
|
-
function truncateCheckpointText(value) {
|
|
88
|
-
const trimmed = value.trim();
|
|
89
|
-
if (trimmed.length <= MAX_CHECKPOINT_CHARS_PER_SIDE) {
|
|
90
|
-
return trimmed;
|
|
91
|
-
}
|
|
92
|
-
return `${trimmed.slice(0, MAX_CHECKPOINT_CHARS_PER_SIDE)}…`;
|
|
93
|
-
}
|
|
94
|
-
function getCheckpointTracker(sessionKey) {
|
|
95
|
-
let tracker = checkpointTrackers.get(sessionKey);
|
|
96
|
-
if (!tracker) {
|
|
97
|
-
tracker = new CheckpointTracker();
|
|
98
|
-
checkpointTrackers.set(sessionKey, tracker);
|
|
99
|
-
}
|
|
100
|
-
return tracker;
|
|
101
|
-
}
|
|
102
80
|
export function resetCheckpointSessionState(sessionKey) {
|
|
103
|
-
|
|
104
|
-
checkpointTurnsBySession.delete(sessionKey);
|
|
105
|
-
housekeepingTurnsBySession.delete(sessionKey);
|
|
106
|
-
}
|
|
107
|
-
function appendCheckpointTurn(sessionKey, turn) {
|
|
108
|
-
const turns = checkpointTurnsBySession.get(sessionKey) ?? [];
|
|
109
|
-
turns.push(turn);
|
|
110
|
-
const overflow = turns.length - config.memoryCheckpointTurns;
|
|
111
|
-
if (overflow > 0) {
|
|
112
|
-
turns.splice(0, overflow);
|
|
113
|
-
}
|
|
114
|
-
checkpointTurnsBySession.set(sessionKey, turns);
|
|
115
|
-
return turns;
|
|
116
|
-
}
|
|
117
|
-
function scheduleCheckpointExtraction(sessionKey, prompt, finalContent, source) {
|
|
118
|
-
if (source.type === "background") {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const tracker = getCheckpointTracker(sessionKey);
|
|
122
|
-
const turns = appendCheckpointTurn(sessionKey, {
|
|
123
|
-
user: truncateCheckpointText(prompt),
|
|
124
|
-
assistant: truncateCheckpointText(finalContent),
|
|
125
|
-
});
|
|
126
|
-
if (!config.memoryCheckpointEnabled) {
|
|
127
|
-
log.info({ sessionKey }, "memory.checkpoint.disabled");
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
tracker.tickOrchestratorTurn();
|
|
131
|
-
if (!tracker.shouldFire()) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
tracker.markFired();
|
|
135
|
-
if (isCheckpointInFlight(sessionKey)) {
|
|
136
|
-
log.info({ sessionKey }, "memory.checkpoint.in_flight_skip");
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (!copilotClient) {
|
|
140
|
-
log.error({ sessionKey }, "memory.checkpoint.error");
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const activeScope = getMemoryScopeForSession(sessionKey);
|
|
144
|
-
void runCheckpointExtraction({
|
|
145
|
-
sessionKey,
|
|
146
|
-
turns: turns.slice(-config.memoryCheckpointTurns),
|
|
147
|
-
activeScope,
|
|
148
|
-
copilotClient,
|
|
149
|
-
trigger: "cadence",
|
|
150
|
-
}).catch((error) => {
|
|
151
|
-
log.error({ err: error, sessionKey }, "memory.checkpoint.error");
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
function scheduleHousekeeping(sessionKey, source) {
|
|
155
|
-
if (source.type === "background") {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (!config.memoryHousekeepingEnabled) {
|
|
159
|
-
log.info({ sessionKey }, "memory.housekeeping.disabled");
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const turns = (housekeepingTurnsBySession.get(sessionKey) ?? 0) + 1;
|
|
163
|
-
if (turns < config.memoryHousekeepingTurns) {
|
|
164
|
-
housekeepingTurnsBySession.set(sessionKey, turns);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
housekeepingTurnsBySession.set(sessionKey, 0);
|
|
168
|
-
const activeScope = getMemoryScopeForSession(sessionKey);
|
|
169
|
-
if (!activeScope) {
|
|
170
|
-
log.info({ sessionKey }, "memory.housekeeping.no_active_scope");
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const scopeIds = [activeScope.id];
|
|
174
|
-
if (isHousekeepingInFlight(scopeIds)) {
|
|
175
|
-
log.info({ sessionKey, scope_ids: scopeIds }, "memory.housekeeping.in_flight_skip");
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
void runHousekeeping({ scopeIds }).catch((error) => {
|
|
179
|
-
log.error({ err: error, sessionKey, scope_ids: scopeIds }, "memory.housekeeping.error");
|
|
180
|
-
});
|
|
81
|
+
memoryCoordinator?.reset(sessionKey);
|
|
181
82
|
}
|
|
182
83
|
export function maybeScheduleScopeChangeCheckpoint(sessionKey, previousScope, nextScope) {
|
|
183
|
-
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (!config.memoryCheckpointOnScopeChange) {
|
|
187
|
-
log.info({ sessionKey, scope: previousScope.slug }, "memory.checkpoint.scope_change_disabled");
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const tracker = getCheckpointTracker(sessionKey);
|
|
191
|
-
const turnsSinceLast = tracker.turnsSinceLastFire();
|
|
192
|
-
if (turnsSinceLast < config.memoryCheckpointMinTurnsForScopeFire) {
|
|
193
|
-
log.info({
|
|
194
|
-
sessionKey,
|
|
195
|
-
scope: previousScope.slug,
|
|
196
|
-
turns_since_last: turnsSinceLast,
|
|
197
|
-
min_required: config.memoryCheckpointMinTurnsForScopeFire,
|
|
198
|
-
}, "memory.checkpoint.scope_change_skip");
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (isCheckpointInFlight(sessionKey)) {
|
|
202
|
-
log.info({ sessionKey, trigger: "scope_change" }, "memory.checkpoint.in_flight_skip");
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (!copilotClient) {
|
|
206
|
-
log.error({ sessionKey }, "memory.checkpoint.error");
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const turns = checkpointTurnsBySession.get(sessionKey) ?? [];
|
|
210
|
-
if (turns.length === 0) {
|
|
211
|
-
log.info({
|
|
212
|
-
sessionKey,
|
|
213
|
-
scope: previousScope.slug,
|
|
214
|
-
turns_since_last: turnsSinceLast,
|
|
215
|
-
min_required: config.memoryCheckpointMinTurnsForScopeFire,
|
|
216
|
-
}, "memory.checkpoint.scope_change_skip");
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
tracker.markScopeChangeFire();
|
|
220
|
-
void runCheckpointExtraction({
|
|
221
|
-
sessionKey,
|
|
222
|
-
turns: turns.slice(-config.memoryCheckpointTurns),
|
|
223
|
-
activeScope: previousScope,
|
|
224
|
-
copilotClient,
|
|
225
|
-
trigger: "scope_change",
|
|
226
|
-
scopeChangeContext: {
|
|
227
|
-
from: previousScope.slug,
|
|
228
|
-
to: nextScope?.slug ?? "no active scope",
|
|
229
|
-
},
|
|
230
|
-
}).catch((error) => {
|
|
84
|
+
void memoryCoordinator?.onScopeChange(sessionKey, previousScope?.slug ?? "", nextScope?.slug ?? "").catch((error) => {
|
|
231
85
|
log.error({ err: error, sessionKey }, "memory.checkpoint.error");
|
|
232
86
|
});
|
|
233
87
|
}
|
|
@@ -325,6 +179,13 @@ function getSessionConfig() {
|
|
|
325
179
|
function agentSlugFromSessionKey(sessionKey) {
|
|
326
180
|
return sessionKey.startsWith("agent:") ? sessionKey.slice("agent:".length) : undefined;
|
|
327
181
|
}
|
|
182
|
+
function persistentAgentSessionKey(slug) {
|
|
183
|
+
const agent = getAgent(slug);
|
|
184
|
+
if (!agent?.persistent) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return `agent:${agent.slug}`;
|
|
188
|
+
}
|
|
328
189
|
function getPersistentAgentForSessionKey(sessionKey) {
|
|
329
190
|
const slug = agentSlugFromSessionKey(sessionKey);
|
|
330
191
|
if (!slug)
|
|
@@ -339,39 +200,11 @@ function getMemoryScopeForSession(sessionKey) {
|
|
|
339
200
|
}
|
|
340
201
|
return getActiveScope();
|
|
341
202
|
}
|
|
342
|
-
function
|
|
343
|
-
if (!config.memoryInjectEnabled || !scope) {
|
|
344
|
-
return undefined;
|
|
345
|
-
}
|
|
346
|
-
const hotTierXml = renderHotTierXML(getHotTierEntries(scope.id));
|
|
347
|
-
return hotTierXml ? hotTierXml.trimEnd() : undefined;
|
|
348
|
-
}
|
|
349
|
-
function buildHotTierContext() {
|
|
350
|
-
if (!config.memoryInjectEnabled) {
|
|
351
|
-
return undefined;
|
|
352
|
-
}
|
|
353
|
-
const hotTierXml = renderHotTierForActiveScope();
|
|
354
|
-
if (!hotTierXml) {
|
|
355
|
-
return undefined;
|
|
356
|
-
}
|
|
357
|
-
return hotTierXml.trimEnd();
|
|
358
|
-
}
|
|
359
|
-
function buildPerTurnMemoryHooks(sessionKey) {
|
|
360
|
-
if (!config.memoryInjectEnabled) {
|
|
361
|
-
return undefined;
|
|
362
|
-
}
|
|
363
|
-
return {
|
|
364
|
-
onUserPromptSubmitted: () => {
|
|
365
|
-
const hotTierXml = buildScopedHotTierContext(getMemoryScopeForSession(sessionKey));
|
|
366
|
-
return hotTierXml ? { additionalContext: hotTierXml } : undefined;
|
|
367
|
-
},
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
function getSystemMessageOptions(memorySummary) {
|
|
203
|
+
function getSystemMessageOptions(memorySummary, hotTierXml) {
|
|
371
204
|
return {
|
|
372
205
|
selfEditEnabled: config.selfEditEnabled,
|
|
373
206
|
memorySummary: memorySummary || undefined,
|
|
374
|
-
hotTierXml
|
|
207
|
+
hotTierXml,
|
|
375
208
|
agentRoster: buildAgentRoster(),
|
|
376
209
|
userContext: currentUserContext,
|
|
377
210
|
};
|
|
@@ -407,15 +240,9 @@ function updateRequestContext(source) {
|
|
|
407
240
|
}
|
|
408
241
|
}
|
|
409
242
|
export function feedAgentResult(taskId, agentSlug, result) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
finalResult: result,
|
|
414
|
-
copilotClient,
|
|
415
|
-
}).catch((error) => {
|
|
416
|
-
log.error({ err: error, taskId }, "memory.eot.error");
|
|
417
|
-
});
|
|
418
|
-
}
|
|
243
|
+
void memoryCoordinator?.onAgentTaskComplete(taskId, result).catch((error) => {
|
|
244
|
+
log.error({ err: error, taskId }, "memory.eot.error");
|
|
245
|
+
});
|
|
419
246
|
const sessionKey = getTaskSessionKey(taskId) || "default";
|
|
420
247
|
const agentTurnId = randomUUID();
|
|
421
248
|
const agentDisplayName = getAgentRegistry().find((agent) => agent.slug === agentSlug)?.name ?? agentSlug;
|
|
@@ -509,13 +336,12 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
509
336
|
let { tools, mcpServers, skillDirectories } = baseConfig;
|
|
510
337
|
const isProjectSession = sessionKey.startsWith("project:");
|
|
511
338
|
const persistentAgent = getPersistentAgentForSessionKey(sessionKey);
|
|
512
|
-
const agentScope = persistentAgent?.scope ? getScope(persistentAgent.scope) ?? null : null;
|
|
513
339
|
const infiniteSessions = {
|
|
514
340
|
enabled: true,
|
|
515
341
|
backgroundCompactionThreshold: 0.80,
|
|
516
342
|
bufferExhaustionThreshold: 0.95,
|
|
517
343
|
};
|
|
518
|
-
const memoryHooks =
|
|
344
|
+
const memoryHooks = memoryCoordinator?.buildPerTurnHooks(sessionKey);
|
|
519
345
|
let model = config.copilotModel;
|
|
520
346
|
let systemMessageContent;
|
|
521
347
|
let sessionMode = isProjectSession ? "project" : "default";
|
|
@@ -525,7 +351,8 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
525
351
|
client: copilotClient,
|
|
526
352
|
onAgentTaskComplete: feedAgentResult,
|
|
527
353
|
})));
|
|
528
|
-
|
|
354
|
+
mcpServers = filterMcpServersForAgent(persistentAgent, mcpServers);
|
|
355
|
+
const scopedHotTier = (await memoryCoordinator?.buildHotTierContext(sessionKey)) || undefined;
|
|
529
356
|
const channelNote = `You are in your persistent Chapterhouse channel (${sessionKey}). Your memory scope is ${persistentAgent.scope}.`;
|
|
530
357
|
systemMessageContent = [
|
|
531
358
|
composeAgentSystemMessage(persistentAgent),
|
|
@@ -536,8 +363,9 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
536
363
|
}
|
|
537
364
|
else {
|
|
538
365
|
const memorySummary = getWikiSummary();
|
|
366
|
+
const hotTierXml = (await memoryCoordinator?.buildHotTierContext(sessionKey)) || undefined;
|
|
539
367
|
systemMessageContent = getOrchestratorSystemMessage({
|
|
540
|
-
...getSystemMessageOptions(memorySummary),
|
|
368
|
+
...getSystemMessageOptions(memorySummary, hotTierXml),
|
|
541
369
|
version: CHAPTERHOUSE_VERSION,
|
|
542
370
|
});
|
|
543
371
|
}
|
|
@@ -559,7 +387,7 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
559
387
|
infiniteSessions,
|
|
560
388
|
});
|
|
561
389
|
log.info({ sessionKey }, "Session resumed successfully");
|
|
562
|
-
|
|
390
|
+
memoryCoordinator?.reset(sessionKey);
|
|
563
391
|
upsertCopilotSession(sessionKey, sessionMode, session.sessionId, projectRoot, model);
|
|
564
392
|
const mgr = registry?.get(sessionKey);
|
|
565
393
|
if (mgr)
|
|
@@ -586,7 +414,7 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
586
414
|
infiniteSessions,
|
|
587
415
|
});
|
|
588
416
|
log.info({ sessionKey, sessionId: session.sessionId.slice(0, 8) }, "Session created");
|
|
589
|
-
|
|
417
|
+
memoryCoordinator?.reset(sessionKey);
|
|
590
418
|
upsertCopilotSession(sessionKey, sessionMode, session.sessionId, projectRoot, model);
|
|
591
419
|
if (sessionKey === "default")
|
|
592
420
|
setState(ORCHESTRATOR_SESSION_KEY, session.sessionId);
|
|
@@ -597,6 +425,12 @@ async function createOrResumeSession(sessionKey, projectRoot) {
|
|
|
597
425
|
}
|
|
598
426
|
export async function initOrchestrator(client) {
|
|
599
427
|
copilotClient = client;
|
|
428
|
+
memoryCoordinator?.shutdown();
|
|
429
|
+
memoryCoordinator = new MemoryCoordinator({
|
|
430
|
+
getCopilotClient: () => copilotClient,
|
|
431
|
+
config,
|
|
432
|
+
resolveScopeForSession: getMemoryScopeForSession,
|
|
433
|
+
});
|
|
600
434
|
// Initialize per-task ring buffer — subscribes to agentEventBus for session:tool_call events.
|
|
601
435
|
initTaskEventLog();
|
|
602
436
|
// (Re-)create the registry — supports multiple initOrchestrator calls in tests
|
|
@@ -893,12 +727,8 @@ async function executeOnSession(manager, item) {
|
|
|
893
727
|
spawnArgsMap.delete(taskId);
|
|
894
728
|
activeSubagentTaskIds.delete(taskId);
|
|
895
729
|
db.prepare(`UPDATE agent_tasks SET status = 'completed', result = ?, completed_at = CURRENT_TIMESTAMP WHERE task_id = ?`).run(finalResult?.slice(0, 10000) ?? null, taskId);
|
|
896
|
-
if (
|
|
897
|
-
void
|
|
898
|
-
taskId,
|
|
899
|
-
finalResult,
|
|
900
|
-
copilotClient,
|
|
901
|
-
}).catch((error) => {
|
|
730
|
+
if (finalResult) {
|
|
731
|
+
void memoryCoordinator?.onAgentTaskComplete(taskId, finalResult).catch((error) => {
|
|
902
732
|
log.error({ err: error, taskId }, "memory.eot.error");
|
|
903
733
|
});
|
|
904
734
|
}
|
|
@@ -1249,8 +1079,9 @@ export async function sendToOrchestrator(prompt, source, callback, attachments,
|
|
|
1249
1079
|
logConversation("assistant", finalContent, logSource, sessionKey, { turnId });
|
|
1250
1080
|
}
|
|
1251
1081
|
catch { /* best-effort */ }
|
|
1252
|
-
|
|
1253
|
-
|
|
1082
|
+
void memoryCoordinator?.onTurnComplete(sessionKey, prompt, finalContent, source.type).catch((error) => {
|
|
1083
|
+
log.error({ err: error, sessionKey }, "memory.turn_complete.error");
|
|
1084
|
+
});
|
|
1254
1085
|
if (copilotClient) {
|
|
1255
1086
|
maybeWriteEpisode(copilotClient).catch((err) => {
|
|
1256
1087
|
log.error({ err: err instanceof Error ? err.message : err }, "Episode write failed (non-fatal)");
|
|
@@ -1350,8 +1181,9 @@ export async function interruptCurrentTurn(sessionKey, newPrompt, source, callba
|
|
|
1350
1181
|
logConversation("assistant", finalContent, sourceLabel, sessionKey, { turnId });
|
|
1351
1182
|
}
|
|
1352
1183
|
catch { /* best-effort */ }
|
|
1353
|
-
|
|
1354
|
-
|
|
1184
|
+
void memoryCoordinator?.onTurnComplete(sessionKey, newPrompt, finalContent, source.type).catch((error) => {
|
|
1185
|
+
log.error({ err: error, sessionKey }, "memory.turn_complete.error");
|
|
1186
|
+
});
|
|
1355
1187
|
if (copilotClient) {
|
|
1356
1188
|
maybeWriteEpisode(copilotClient).catch((err) => {
|
|
1357
1189
|
log.error({ err: err instanceof Error ? err.message : err }, "Episode write failed (non-fatal)");
|
|
@@ -1385,19 +1217,18 @@ export async function interruptCurrentTurn(sessionKey, newPrompt, source, callba
|
|
|
1385
1217
|
await manager.abortCurrentTurn();
|
|
1386
1218
|
log.info({ sessionKey, replacementTurnId: turnId }, "turn.interrupted");
|
|
1387
1219
|
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Enqueue a turn for the new POST→SSE chat path (#130).
|
|
1390
|
-
*
|
|
1391
|
-
* Unlike `sendToOrchestrator`, this function:
|
|
1392
|
-
* - Returns the `turnId` immediately without waiting for the turn to complete.
|
|
1393
|
-
* - Routes through the shared lifecycle emitter in sendToOrchestrator.
|
|
1394
|
-
* - Does NOT write to sseClients — the SSE channel delivers events via subscribeSession().
|
|
1395
|
-
* - Supports interrupt: true which calls interruptCurrentTurn under the hood.
|
|
1396
|
-
*
|
|
1397
|
-
* @returns turnId (UUID)
|
|
1398
|
-
*/
|
|
1399
1220
|
export function enqueueForSse(opts) {
|
|
1221
|
+
if (opts.type === "agent_saved") {
|
|
1222
|
+
agentEventBus.emit({
|
|
1223
|
+
type: "agent_saved",
|
|
1224
|
+
payload: { slug: opts.slug ?? "" },
|
|
1225
|
+
});
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1400
1228
|
const { sessionKey, prompt, attachments, authUser, authHeader, interrupt } = opts;
|
|
1229
|
+
if (!sessionKey || !prompt) {
|
|
1230
|
+
throw new Error("Missing sessionKey or prompt for SSE turn enqueue");
|
|
1231
|
+
}
|
|
1401
1232
|
const turnId = randomUUID();
|
|
1402
1233
|
// sse-web carries auth and enables onQueued — unlike "background" (Fixes 2 & 3).
|
|
1403
1234
|
const source = { type: "sse-web", sessionKey, user: authUser, authorizationHeader: authHeader };
|
|
@@ -1464,6 +1295,37 @@ export async function interruptSessionTurn(sessionKey) {
|
|
|
1464
1295
|
}
|
|
1465
1296
|
return aborted;
|
|
1466
1297
|
}
|
|
1298
|
+
export function getPersistentAgentSessionState(slug) {
|
|
1299
|
+
const sessionKey = persistentAgentSessionKey(slug);
|
|
1300
|
+
if (!sessionKey) {
|
|
1301
|
+
return "none";
|
|
1302
|
+
}
|
|
1303
|
+
const manager = registry?.get(sessionKey);
|
|
1304
|
+
if (!manager) {
|
|
1305
|
+
return "none";
|
|
1306
|
+
}
|
|
1307
|
+
return manager.isProcessing ? "in_flight" : "idle";
|
|
1308
|
+
}
|
|
1309
|
+
export async function reloadPersistentAgent(slug, onReloaded) {
|
|
1310
|
+
const sessionKey = persistentAgentSessionKey(slug);
|
|
1311
|
+
if (!sessionKey || !registry) {
|
|
1312
|
+
return "none";
|
|
1313
|
+
}
|
|
1314
|
+
let manager = registry.get(sessionKey);
|
|
1315
|
+
if (!manager) {
|
|
1316
|
+
manager = registry.getOrCreate(sessionKey);
|
|
1317
|
+
await manager.ensureSession();
|
|
1318
|
+
onReloaded?.();
|
|
1319
|
+
return "reloaded";
|
|
1320
|
+
}
|
|
1321
|
+
if (manager.isProcessing) {
|
|
1322
|
+
manager.requestSessionReload(onReloaded);
|
|
1323
|
+
return "scheduled";
|
|
1324
|
+
}
|
|
1325
|
+
await manager.restartSession();
|
|
1326
|
+
onReloaded?.();
|
|
1327
|
+
return "reloaded";
|
|
1328
|
+
}
|
|
1467
1329
|
/** Switch the model on the live default orchestrator session without destroying it. */
|
|
1468
1330
|
export function switchSessionModel(newModel) {
|
|
1469
1331
|
const manager = registry?.get("default");
|
|
@@ -1491,15 +1353,13 @@ export function getAgentInfo() {
|
|
|
1491
1353
|
}
|
|
1492
1354
|
/** Clean up on shutdown/restart. */
|
|
1493
1355
|
export async function shutdownAgents() {
|
|
1356
|
+
memoryCoordinator?.shutdown();
|
|
1357
|
+
memoryCoordinator = undefined;
|
|
1494
1358
|
if (!registry) {
|
|
1495
|
-
checkpointTrackers.clear();
|
|
1496
|
-
checkpointTurnsBySession.clear();
|
|
1497
1359
|
await clearActiveTasks();
|
|
1498
1360
|
return;
|
|
1499
1361
|
}
|
|
1500
1362
|
await registry.shutdown();
|
|
1501
|
-
checkpointTrackers.clear();
|
|
1502
|
-
checkpointTurnsBySession.clear();
|
|
1503
1363
|
await clearActiveTasks();
|
|
1504
1364
|
}
|
|
1505
1365
|
//# sourceMappingURL=orchestrator.js.map
|