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.
Files changed (40) hide show
  1. package/.pr-types.json +14 -0
  2. package/README.md +6 -0
  3. package/dist/api/agent-edit-access.js +11 -0
  4. package/dist/api/agents.api.test.js +48 -0
  5. package/dist/api/server.js +182 -11
  6. package/dist/api/server.test.js +334 -3
  7. package/dist/config.test.js +29 -0
  8. package/dist/copilot/agent-event-bus.js +1 -0
  9. package/dist/copilot/agents.js +114 -46
  10. package/dist/copilot/agents.mcp-servers.test.js +87 -0
  11. package/dist/copilot/agents.parse.test.js +69 -0
  12. package/dist/copilot/agents.test.js +125 -1
  13. package/dist/copilot/memory-coordinator.js +234 -0
  14. package/dist/copilot/memory-coordinator.test.js +257 -0
  15. package/dist/copilot/orchestrator.js +81 -221
  16. package/dist/copilot/orchestrator.test.js +238 -1
  17. package/dist/copilot/pr-title.js +92 -0
  18. package/dist/copilot/pr-title.test.js +54 -0
  19. package/dist/copilot/router.test.js +30 -0
  20. package/dist/copilot/session-manager.js +34 -0
  21. package/dist/copilot/threat-model.js +50 -0
  22. package/dist/copilot/threat-model.test.js +129 -0
  23. package/dist/copilot/tools.js +61 -37
  24. package/dist/copilot/tools.wiki.test.js +15 -6
  25. package/dist/setup.js +15 -5
  26. package/dist/setup.test.js +20 -3
  27. package/dist/sprint-merge.js +168 -0
  28. package/dist/sprint-merge.test.js +131 -0
  29. package/dist/store/db.js +63 -0
  30. package/dist/store/db.test.js +279 -0
  31. package/dist/test/setup-env.js +2 -1
  32. package/dist/test/setup-env.test.js +8 -1
  33. package/package.json +8 -1
  34. package/web/dist/assets/index-DuKYxMIR.css +10 -0
  35. package/web/dist/assets/index-DytB69KC.js +223 -0
  36. package/web/dist/assets/index-DytB69KC.js.map +1 -0
  37. package/web/dist/index.html +2 -2
  38. package/web/dist/assets/index-CPaILy2j.js +0 -223
  39. package/web/dist/assets/index-CPaILy2j.js.map +0 -1
  40. 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 { CheckpointTracker, isCheckpointInFlight, runCheckpointExtraction } from "../memory/checkpoint.js";
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
- const checkpointTrackers = new Map();
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
- getCheckpointTracker(sessionKey).reset();
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
- if (!previousScope) {
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 buildScopedHotTierContext(scope) {
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: buildHotTierContext(),
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
- if (copilotClient) {
411
- void runEndOfTaskMemoryHook({
412
- taskId,
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 = buildPerTurnMemoryHooks(sessionKey);
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
- const scopedHotTier = buildScopedHotTierContext(agentScope);
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
- resetCheckpointSessionState(sessionKey);
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
- resetCheckpointSessionState(sessionKey);
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 (copilotClient && finalResult) {
897
- void runEndOfTaskMemoryHook({
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
- scheduleCheckpointExtraction(sessionKey, prompt, finalContent, source);
1253
- scheduleHousekeeping(sessionKey, source);
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
- scheduleCheckpointExtraction(sessionKey, newPrompt, finalContent, source);
1354
- scheduleHousekeeping(sessionKey, source);
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