chapterhouse 0.13.0 → 0.14.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 (118) hide show
  1. package/dist/api/route-coverage.test.js +1 -3
  2. package/dist/api/server.js +0 -2
  3. package/dist/api/server.test.js +0 -281
  4. package/dist/config.js +3 -85
  5. package/dist/config.test.js +5 -123
  6. package/dist/copilot/agents.js +25 -13
  7. package/dist/copilot/agents.test.js +10 -11
  8. package/dist/copilot/memory-coordinator.js +12 -227
  9. package/dist/copilot/memory-coordinator.test.js +31 -250
  10. package/dist/copilot/orchestrator.js +8 -66
  11. package/dist/copilot/orchestrator.test.js +9 -467
  12. package/dist/copilot/skills.js +15 -1
  13. package/dist/copilot/system-message.js +9 -15
  14. package/dist/copilot/system-message.test.js +9 -22
  15. package/dist/copilot/tools/index.js +3 -3
  16. package/dist/copilot/tools-deps.js +1 -1
  17. package/dist/copilot/tools.agent.test.js +6 -0
  18. package/dist/copilot/tools.inventory.test.js +1 -14
  19. package/dist/daemon.js +7 -9
  20. package/dist/memory/assets.js +33 -0
  21. package/dist/memory/domains.js +58 -0
  22. package/dist/memory/domains.test.js +47 -0
  23. package/dist/memory/git.js +66 -0
  24. package/dist/memory/git.test.js +32 -0
  25. package/dist/memory/history.js +19 -0
  26. package/dist/memory/hottier.js +32 -0
  27. package/dist/memory/hottier.test.js +33 -0
  28. package/dist/memory/index.js +5 -13
  29. package/dist/memory/instructions.js +17 -0
  30. package/dist/memory/manager.js +84 -0
  31. package/dist/memory/markdown.js +78 -0
  32. package/dist/memory/markdown.test.js +42 -0
  33. package/dist/memory/mutex.js +18 -0
  34. package/dist/memory/path-guard.js +26 -0
  35. package/dist/memory/path-guard.test.js +27 -0
  36. package/dist/memory/paths.js +12 -0
  37. package/dist/memory/reconcile.js +75 -0
  38. package/dist/memory/reconcile.test.js +50 -0
  39. package/dist/memory/scaffold.js +37 -0
  40. package/dist/memory/scaffold.test.js +52 -0
  41. package/dist/memory/tools/commit-wrapper.js +32 -0
  42. package/dist/memory/tools/domains.js +73 -0
  43. package/dist/memory/tools/domains.test.js +66 -0
  44. package/dist/memory/tools/git.js +52 -0
  45. package/dist/memory/tools/index.js +25 -0
  46. package/dist/memory/tools/read.js +101 -0
  47. package/dist/memory/tools/read.test.js +69 -0
  48. package/dist/memory/tools/search.js +103 -0
  49. package/dist/memory/tools/search.test.js +63 -0
  50. package/dist/memory/tools/sessions.js +45 -0
  51. package/dist/memory/tools/sessions.test.js +74 -0
  52. package/dist/memory/tools/shared.js +7 -0
  53. package/dist/memory/tools/write.js +116 -0
  54. package/dist/memory/tools/write.test.js +107 -0
  55. package/dist/memory/walk.js +39 -0
  56. package/dist/store/repositories/sessions.js +40 -0
  57. package/dist/wiki/consolidation.js +3 -31
  58. package/dist/wiki/consolidation.test.js +0 -19
  59. package/dist/wiki/frontmatter.js +18 -6
  60. package/dist/wiki/frontmatter.test.js +40 -0
  61. package/package.json +1 -1
  62. package/skills/system/evolve/SKILL.md +131 -0
  63. package/skills/system/foresight/SKILL.md +116 -0
  64. package/skills/system/history/SKILL.md +58 -0
  65. package/skills/system/housekeeping/SKILL.md +185 -0
  66. package/skills/system/reflect/SKILL.md +214 -0
  67. package/skills/system/scenario/SKILL.md +198 -0
  68. package/skills/system/setup/SKILL.md +113 -0
  69. package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
  70. package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
  71. package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
  72. package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
  73. package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
  74. package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
  75. package/web/dist/index.html +1 -1
  76. package/dist/api/routes/memory.js +0 -475
  77. package/dist/api/routes/memory.test.js +0 -108
  78. package/dist/copilot/tools/memory.js +0 -678
  79. package/dist/copilot/tools.memory.test.js +0 -590
  80. package/dist/memory/action-items.js +0 -100
  81. package/dist/memory/action-items.test.js +0 -83
  82. package/dist/memory/active-scope.js +0 -78
  83. package/dist/memory/active-scope.test.js +0 -80
  84. package/dist/memory/checkpoint-prompt.js +0 -71
  85. package/dist/memory/checkpoint.js +0 -274
  86. package/dist/memory/checkpoint.test.js +0 -275
  87. package/dist/memory/decisions.js +0 -54
  88. package/dist/memory/decisions.test.js +0 -92
  89. package/dist/memory/entities.js +0 -70
  90. package/dist/memory/entities.test.js +0 -65
  91. package/dist/memory/eot.js +0 -459
  92. package/dist/memory/eot.test.js +0 -949
  93. package/dist/memory/hooks.js +0 -149
  94. package/dist/memory/hooks.test.js +0 -325
  95. package/dist/memory/hot-tier.js +0 -283
  96. package/dist/memory/hot-tier.test.js +0 -275
  97. package/dist/memory/housekeeping-scheduler.js +0 -187
  98. package/dist/memory/housekeeping-scheduler.test.js +0 -236
  99. package/dist/memory/housekeeping.js +0 -497
  100. package/dist/memory/housekeeping.test.js +0 -410
  101. package/dist/memory/inbox.js +0 -83
  102. package/dist/memory/inbox.test.js +0 -178
  103. package/dist/memory/migration.js +0 -244
  104. package/dist/memory/migration.test.js +0 -108
  105. package/dist/memory/observations.js +0 -46
  106. package/dist/memory/observations.test.js +0 -86
  107. package/dist/memory/recall.js +0 -269
  108. package/dist/memory/recall.test.js +0 -265
  109. package/dist/memory/reflect.js +0 -273
  110. package/dist/memory/reflect.test.js +0 -256
  111. package/dist/memory/scope-lock.js +0 -26
  112. package/dist/memory/scope-lock.test.js +0 -118
  113. package/dist/memory/scopes.js +0 -89
  114. package/dist/memory/scopes.test.js +0 -176
  115. package/dist/memory/tiering.js +0 -223
  116. package/dist/memory/tiering.test.js +0 -323
  117. package/dist/memory/types.js +0 -2
  118. package/web/dist/assets/index-gAvLNEvJ.js.map +0 -1
@@ -12,6 +12,7 @@ import { loadMcpConfig } from "./mcp-config.js";
12
12
  import { getCurrentDateSystemLine } from "./prompt-date.js";
13
13
  import { getSkillDirectories } from "./skills.js";
14
14
  import { EXCLUDED_BUILTIN_TOOLS } from "./builtin-tools.js";
15
+ import { memorySystemInstructions } from "../memory/index.js";
15
16
  import { childLogger } from "../util/logger.js";
16
17
  const log = childLogger("agents");
17
18
  const toolAgentContext = new AsyncLocalStorage();
@@ -23,6 +24,7 @@ const agentFrontmatterSchema = z.object({
23
24
  model: z.string().min(1),
24
25
  skills: z.array(z.string()).optional(),
25
26
  tools: z.array(z.string()).optional(),
27
+ management_tools: z.array(z.string()).optional(),
26
28
  mcpServers: z.array(z.string()).optional(),
27
29
  allowed_paths: z.array(z.string()).optional(),
28
30
  persistent: z.union([z.boolean(), z.string()]).optional().transform((value) => {
@@ -83,6 +85,7 @@ export function parseAgentMdOrThrow(content, slug) {
83
85
  scope: fm.scope,
84
86
  skills: fm.skills,
85
87
  tools: fm.tools,
88
+ managementTools: fm.management_tools,
86
89
  mcpServers: fm.mcpServers,
87
90
  allowedPaths: fm.allowed_paths,
88
91
  systemMessage: body,
@@ -295,12 +298,12 @@ function getAgentBasePrompt() {
295
298
  You are an agent within Chapterhouse, a team-level AI assistant for engineering teams. You run on the user's local machine.
296
299
 
297
300
  ### Agent Memory
298
- Chapterhouse agent memory follows a three-tier memory model: **read** with \`memory_recall\` and \`memory_list_action_items\`, **propose** with \`memory_propose\`, and **write** with orchestrator-only tools. Do not call \`memory_remember\` directly; when you discover something memory-worthy, use \`memory_propose\` instead. Proposals are processed automatically at end-of-task, so do not wait for confirmation. Examples: a durable fact is an \`observation\`, a settled implementation choice is a \`decision\`, a named system/tool/person can be proposed as an \`entity\`, and a reminder/follow-up can be proposed as an \`action_item\`.
301
+ Chapterhouse gives every agent a persistent, file-based memory tree under \`memory/\`, organized into domains. Read it with \`cog_read\`, \`cog_l0_scan\`, \`cog_l1_outline\`, \`cog_tree\`, and \`cog_search\`; write directly with \`cog_write\`, \`cog_edit\`, \`cog_append\`, and \`cog_move\` whenever you learn something worth remembering don't wait for end of task. Every write auto-commits to git. The full memory operating instructions are included below.
299
302
 
300
303
  ### Shared Wiki
301
304
  All agents share a wiki knowledge base for persistent memory. Use \`wiki_read\` and \`wiki_search\` to find existing knowledge, and \`wiki_update\` to save important findings.
302
305
 
303
- Invoke \`wiki-conventions\` before wiki writes or restructuring work. Treat \`wiki_update\` and \`wiki_ingest_source\` as write-sensitive workflows, and use \`memory_propose\` for agent-memory handoff. Before using wiki write tools, read \`pages/index.md\`, scan the last 20-30 entries of \`pages/_meta/log.md\`, and run \`wiki_search\` for the topic when the wiki is large or the topic is ambiguous.
306
+ Invoke \`wiki-conventions\` before wiki writes or restructuring work. Treat \`wiki_update\` and \`wiki_ingest_source\` as write-sensitive workflows. Before using wiki write tools, read \`pages/index.md\`, scan the last 20-30 entries of \`pages/_meta/log.md\`, and run \`wiki_search\` for the topic when the wiki is large or the topic is ambiguous.
304
307
 
305
308
  ### Communication
306
309
  - You receive tasks from @chapterhouse (the orchestrator) or directly from the user
@@ -319,11 +322,12 @@ export function composeAgentSystemMessage(agent, rosterInfo) {
319
322
  const agentPrompt = agent.systemMessage;
320
323
  const currentDateLine = getCurrentDateSystemLine();
321
324
  const currentDateBlock = currentDateLine ? `${currentDateLine}\n\n` : "";
325
+ const memoryBlock = memorySystemInstructions() ? `\n\n${memorySystemInstructions()}` : "";
322
326
  // For @chapterhouse, inject the agent roster
323
327
  if (agent.slug === "chapterhouse" && rosterInfo) {
324
- return `${currentDateBlock}${agentPrompt.replace("{agent_roster}", rosterInfo)}`;
328
+ return `${currentDateBlock}${agentPrompt.replace("{agent_roster}", rosterInfo)}${memoryBlock}`;
325
329
  }
326
- return `${currentDateBlock}${agentPrompt}\n\n${base}`;
330
+ return `${currentDateBlock}${agentPrompt}\n\n${base}${memoryBlock}`;
327
331
  }
328
332
  /** Build a roster description of all agents for @chapterhouse's system prompt. */
329
333
  export function buildAgentRoster() {
@@ -339,10 +343,13 @@ export function buildAgentRoster() {
339
343
  return "No agents registered.";
340
344
  return chLines.join("\n");
341
345
  }
342
- // The wiki tools that every agent gets regardless of tool config
343
- const WIKI_TOOL_NAMES = new Set([
346
+ // Tools every agent gets regardless of tool config: the shared wiki and the
347
+ // full file-based memory surface (all agents read and write memory directly).
348
+ const ALWAYS_AVAILABLE_TOOL_NAMES = new Set([
344
349
  "wiki_search", "wiki_read", "wiki_update", "wiki_reindex", "wiki_append_timeline", "wiki_ingest_source",
345
- "memory_recall", "memory_propose", "memory_list_action_items",
350
+ "cog_read", "cog_l0_scan", "cog_l1_outline", "cog_tree", "cog_search", "cog_stats",
351
+ "cog_write", "cog_edit", "cog_append", "cog_move",
352
+ "cog_git", "cog_domains", "cog_domain_create", "cog_sessions",
346
353
  ]);
347
354
  // Management tools that only @chapterhouse should have
348
355
  const MANAGEMENT_TOOL_NAMES = new Set([
@@ -351,8 +358,6 @@ const MANAGEMENT_TOOL_NAMES = new Set([
351
358
  "switch_model", "toggle_auto", "list_models",
352
359
  "restart_chapterhouse", "list_skills", "learn_skill", "uninstall_skill",
353
360
  "list_machine_sessions", "attach_machine_session",
354
- "memory_remember", "memory_set_scope", "memory_housekeep", "memory_reflect", "memory_promote", "memory_demote",
355
- "memory_add_action_item", "memory_complete_action_item", "memory_drop_action_item", "memory_snooze_action_item",
356
361
  ]);
357
362
  export function getCurrentToolAgentSlug() {
358
363
  return toolAgentContext.getStore();
@@ -373,14 +378,21 @@ export function bindToolsToAgent(agentSlug, allTools, taskId) {
373
378
  export function filterToolsForAgent(agent, allTools) {
374
379
  if (agent.tools && agent.tools.length > 0) {
375
380
  // Agent specifies an explicit allowlist — give those + wiki tools
376
- const allowed = new Set([...agent.tools, ...WIKI_TOOL_NAMES]);
377
- return allTools.filter((t) => allowed.has(t.name) && !(agent.persistent && MANAGEMENT_TOOL_NAMES.has(t.name)));
381
+ const allowed = new Set([...agent.tools, ...ALWAYS_AVAILABLE_TOOL_NAMES]);
382
+ return allTools.filter((t) => allowed.has(t.name));
378
383
  }
379
- // Default: all tools except management (only @chapterhouse gets those)
384
+ // Default: all tools except management (only @chapterhouse gets those by default)
380
385
  if (agent.slug === "chapterhouse") {
381
386
  return allTools;
382
387
  }
383
- return allTools.filter((t) => !MANAGEMENT_TOOL_NAMES.has(t.name));
388
+ const baseTools = allTools.filter((t) => !MANAGEMENT_TOOL_NAMES.has(t.name));
389
+ // Agents can opt into specific management tools via management_tools: in their config
390
+ if (agent.managementTools && agent.managementTools.length > 0) {
391
+ const optedIn = new Set(agent.managementTools);
392
+ const extra = allTools.filter((t) => MANAGEMENT_TOOL_NAMES.has(t.name) && optedIn.has(t.name));
393
+ return [...baseTools, ...extra];
394
+ }
395
+ return baseTools;
384
396
  }
385
397
  /** Filter MCP servers based on agent config. */
386
398
  export function filterMcpServersForAgent(agent, allMcpServers) {
@@ -50,18 +50,17 @@ test("composeAgentSystemMessage steers wiki-capable agents to wiki-conventions",
50
50
  for (const slug of ["coder", "general-purpose"]) {
51
51
  const message = composeAgentSystemMessage(makeAgent(slug));
52
52
  assert.match(message, /invoke `wiki-conventions` before wiki writes/i);
53
- assert.match(message, /wiki_update[\s\S]{0,120}wiki_ingest_source[\s\S]{0,120}memory_propose/i);
53
+ assert.match(message, /wiki_update[\s\S]{0,120}wiki_ingest_source/i);
54
54
  assert.doesNotMatch(message, /`remember`|`forget`|`wiki_ingest`(?!_source)|`wiki_lint`|`wiki_rebuild_index`/);
55
55
  assert.match(message, /read `pages\/index\.md`/i);
56
56
  assert.match(message, /scan the last 20-30 entries of `pages\/_meta\/log\.md`/i);
57
57
  }
58
58
  });
59
- test("composeAgentSystemMessage teaches subagents the three-tier memory model and directs them to memory_propose", () => {
59
+ test("composeAgentSystemMessage teaches subagents the file-based memory tools", () => {
60
60
  const message = composeAgentSystemMessage(makeAgent("coder"));
61
- assert.match(message, /three-tier memory model|read, propose, write/i);
62
- assert.match(message, /memory_recall/i);
63
- assert.match(message, /memory_propose/i);
64
- assert.match(message, /do not call `memory_remember` directly|should not call `memory_remember` directly/i);
61
+ assert.match(message, /cog_read/);
62
+ assert.match(message, /cog_write/);
63
+ assert.match(message, /write directly/i);
65
64
  });
66
65
  test("parseAgentMd detects persistent agent scope from charter frontmatter", () => {
67
66
  const agent = parseAgentMd([
@@ -103,22 +102,22 @@ test("korg charter is persistent and standardizes on the Summary heading", () =>
103
102
  assert.match(charter, /## Summary/);
104
103
  assert.doesNotMatch(charter, /## Compiled Truth/);
105
104
  });
106
- test("persistent agents cannot receive scope-changing management tools", () => {
105
+ test("persistent agents cannot receive management tools", () => {
107
106
  const agent = {
108
107
  ...makeAgent("bellonda"),
109
108
  persistent: true,
110
109
  scope: "infra",
111
110
  };
112
111
  const tools = [
113
- { name: "memory_recall" },
114
- { name: "memory_propose" },
115
- { name: "memory_set_scope" },
112
+ { name: "cog_read" },
113
+ { name: "cog_write" },
116
114
  { name: "delegate_to_agent" },
115
+ { name: "hire_agent" },
117
116
  { name: "bash" },
118
117
  ];
119
118
  const filtered = filterToolsForAgent(agent, tools);
120
119
  const names = filtered.map((tool) => tool.name);
121
- assert.deepEqual(names.sort(), ["bash", "memory_propose", "memory_recall"].sort());
120
+ assert.deepEqual(names.sort(), ["bash", "cog_read", "cog_write"].sort());
122
121
  });
123
122
  test("bindToolsToAgent uses the per-turn task context when no fixed task id is provided", async () => {
124
123
  const tools = bindToolsToAgent("bellonda", [{
@@ -1,234 +1,19 @@
1
- import { getActiveScope } from "../memory/active-scope.js";
2
- import { CheckpointTracker, isCheckpointInFlight, runCheckpointExtraction } from "../memory/checkpoint.js";
3
- import { runEndOfTaskMemoryHook } from "../memory/eot.js";
4
- import { getHotTierEntries, renderHotTierForActiveScope, renderHotTierXML } from "../memory/hot-tier.js";
5
- import { isHousekeepingInFlight, runHousekeeping } from "../memory/housekeeping.js";
6
- import { getScope } from "../memory/scopes.js";
7
- import { config as defaultConfig } from "../config.js";
8
- import { childLogger } from "../util/logger.js";
9
- const log = childLogger("memory-coordinator");
10
- const MAX_CHECKPOINT_CHARS_PER_SIDE = 4_000;
1
+ import { getMemoryManager } from "../memory/index.js";
2
+ /**
3
+ * Wires the file-based memory subsystem into the agent runtime. Its only job is
4
+ * to inject the always-loaded hot-tier block into each turn via the
5
+ * onUserPromptSubmitted hook all memory reads and writes are agent-driven via
6
+ * the cog_* tools.
7
+ */
11
8
  export class MemoryCoordinator {
12
- checkpointTrackers = new Map();
13
- checkpointTurnsBySession = new Map();
14
- housekeepingTurnsBySession = new Map();
15
- completedTaskIds = new Set();
16
- getCopilotClient;
17
- resolveScopeForSession;
18
- config;
19
- constructor(options) {
20
- this.getCopilotClient = options.getCopilotClient;
21
- this.resolveScopeForSession = options.resolveScopeForSession ?? (() => getActiveScope());
22
- this.config = options.config ?? defaultConfig;
23
- }
24
- async onTurnComplete(sessionKey, prompt, response, source) {
25
- const sourceType = this.normalizeSource(source);
26
- if (sourceType === "background") {
27
- return;
28
- }
29
- this.scheduleCheckpointExtraction(sessionKey, prompt, response);
30
- this.scheduleHousekeeping(sessionKey);
31
- }
32
- async onScopeChange(sessionKey, prev, next) {
33
- if (!prev) {
34
- return;
35
- }
36
- const previousScope = getScope(prev) ?? null;
37
- if (!previousScope) {
38
- return;
39
- }
40
- if (!this.config.memoryCheckpointOnScopeChange) {
41
- log.info({ sessionKey, scope: previousScope.slug }, "memory.checkpoint.scope_change_disabled");
42
- return;
43
- }
44
- const tracker = this.getCheckpointTracker(sessionKey);
45
- const turnsSinceLast = tracker.turnsSinceLastFire();
46
- if (turnsSinceLast < this.config.memoryCheckpointMinTurnsForScopeFire) {
47
- log.info({
48
- sessionKey,
49
- scope: previousScope.slug,
50
- turns_since_last: turnsSinceLast,
51
- min_required: this.config.memoryCheckpointMinTurnsForScopeFire,
52
- }, "memory.checkpoint.scope_change_skip");
53
- return;
54
- }
55
- if (isCheckpointInFlight(sessionKey)) {
56
- log.info({ sessionKey, trigger: "scope_change" }, "memory.checkpoint.in_flight_skip");
57
- return;
58
- }
59
- const copilotClient = this.getCopilotClient();
60
- if (!copilotClient) {
61
- log.error({ sessionKey }, "memory.checkpoint.error");
62
- return;
63
- }
64
- const turns = this.checkpointTurnsBySession.get(sessionKey) ?? [];
65
- if (turns.length === 0) {
66
- log.info({
67
- sessionKey,
68
- scope: previousScope.slug,
69
- turns_since_last: turnsSinceLast,
70
- min_required: this.config.memoryCheckpointMinTurnsForScopeFire,
71
- }, "memory.checkpoint.scope_change_skip");
72
- return;
73
- }
74
- tracker.markScopeChangeFire();
75
- const nextScope = next ? (getScope(next) ?? null) : null;
76
- void runCheckpointExtraction({
77
- sessionKey,
78
- turns: turns.slice(-this.config.memoryCheckpointTurns),
79
- activeScope: previousScope,
80
- copilotClient,
81
- trigger: "scope_change",
82
- scopeChangeContext: {
83
- from: previousScope.slug,
84
- to: nextScope?.slug ?? "no active scope",
85
- },
86
- }).catch((error) => {
87
- log.error({ err: error, sessionKey }, "memory.checkpoint.error");
88
- });
89
- }
90
- async buildHotTierContext(sessionKey) {
91
- if (!this.config.memoryInjectEnabled) {
92
- return "";
93
- }
94
- const scope = this.resolveScopeForSession(sessionKey);
95
- if (!scope) {
96
- return "";
97
- }
98
- const activeScope = getActiveScope();
99
- const hotTierXml = activeScope?.id === scope.id
100
- ? renderHotTierForActiveScope()
101
- : renderHotTierXML(getHotTierEntries(scope.id));
102
- return hotTierXml ? hotTierXml.trimEnd() : "";
103
- }
104
- buildPerTurnHooks(sessionKey) {
105
- if (!this.config.memoryInjectEnabled) {
106
- return undefined;
107
- }
108
- const hooks = {
9
+ /** Returns the per-turn hooks that inject the hot-tier memory block. */
10
+ buildPerTurnHooks() {
11
+ return {
109
12
  onUserPromptSubmitted: async () => {
110
- const hotTierXml = await this.buildHotTierContext(sessionKey);
111
- return hotTierXml ? { additionalContext: hotTierXml } : undefined;
13
+ const block = getMemoryManager().hotTier();
14
+ return block ? { additionalContext: block } : undefined;
112
15
  },
113
16
  };
114
- return hooks;
115
- }
116
- async onAgentTaskComplete(taskId, result) {
117
- if (this.completedTaskIds.has(taskId)) {
118
- log.info({ taskId }, "memory.eot.duplicate_skip");
119
- return;
120
- }
121
- this.completedTaskIds.add(taskId);
122
- const copilotClient = this.getCopilotClient();
123
- if (!copilotClient) {
124
- return;
125
- }
126
- const finalResult = typeof result === "string" ? result : result == null ? "" : String(result);
127
- await runEndOfTaskMemoryHook({
128
- taskId,
129
- finalResult,
130
- copilotClient,
131
- });
132
- }
133
- reset(sessionKey) {
134
- this.getCheckpointTracker(sessionKey).reset();
135
- this.checkpointTurnsBySession.delete(sessionKey);
136
- this.housekeepingTurnsBySession.delete(sessionKey);
137
- this.completedTaskIds.clear();
138
- }
139
- shutdown() {
140
- this.checkpointTrackers.clear();
141
- this.checkpointTurnsBySession.clear();
142
- this.housekeepingTurnsBySession.clear();
143
- this.completedTaskIds.clear();
144
- }
145
- normalizeSource(source) {
146
- return source === "background" ? "background" : source === "sse-web" ? "sse-web" : "web";
147
- }
148
- truncateCheckpointText(value) {
149
- const trimmed = value.trim();
150
- if (trimmed.length <= MAX_CHECKPOINT_CHARS_PER_SIDE) {
151
- return trimmed;
152
- }
153
- return `${trimmed.slice(0, MAX_CHECKPOINT_CHARS_PER_SIDE)}…`;
154
- }
155
- getCheckpointTracker(sessionKey) {
156
- let tracker = this.checkpointTrackers.get(sessionKey);
157
- if (!tracker) {
158
- tracker = new CheckpointTracker();
159
- this.checkpointTrackers.set(sessionKey, tracker);
160
- }
161
- return tracker;
162
- }
163
- appendCheckpointTurn(sessionKey, turn) {
164
- const turns = this.checkpointTurnsBySession.get(sessionKey) ?? [];
165
- turns.push(turn);
166
- const overflow = turns.length - this.config.memoryCheckpointTurns;
167
- if (overflow > 0) {
168
- turns.splice(0, overflow);
169
- }
170
- this.checkpointTurnsBySession.set(sessionKey, turns);
171
- return turns;
172
- }
173
- scheduleCheckpointExtraction(sessionKey, prompt, response) {
174
- const tracker = this.getCheckpointTracker(sessionKey);
175
- const turns = this.appendCheckpointTurn(sessionKey, {
176
- user: this.truncateCheckpointText(prompt),
177
- assistant: this.truncateCheckpointText(response),
178
- });
179
- if (!this.config.memoryCheckpointEnabled) {
180
- log.info({ sessionKey }, "memory.checkpoint.disabled");
181
- return;
182
- }
183
- tracker.tickOrchestratorTurn();
184
- if (!tracker.shouldFire()) {
185
- return;
186
- }
187
- tracker.markFired();
188
- if (isCheckpointInFlight(sessionKey)) {
189
- log.info({ sessionKey }, "memory.checkpoint.in_flight_skip");
190
- return;
191
- }
192
- const copilotClient = this.getCopilotClient();
193
- if (!copilotClient) {
194
- log.error({ sessionKey }, "memory.checkpoint.error");
195
- return;
196
- }
197
- const activeScope = this.resolveScopeForSession(sessionKey);
198
- void runCheckpointExtraction({
199
- sessionKey,
200
- turns: turns.slice(-this.config.memoryCheckpointTurns),
201
- activeScope,
202
- copilotClient,
203
- trigger: "cadence",
204
- }).catch((error) => {
205
- log.error({ err: error, sessionKey }, "memory.checkpoint.error");
206
- });
207
- }
208
- scheduleHousekeeping(sessionKey) {
209
- if (!this.config.memoryHousekeepingEnabled) {
210
- log.info({ sessionKey }, "memory.housekeeping.disabled");
211
- return;
212
- }
213
- const turns = (this.housekeepingTurnsBySession.get(sessionKey) ?? 0) + 1;
214
- if (turns < this.config.memoryHousekeepingTurns) {
215
- this.housekeepingTurnsBySession.set(sessionKey, turns);
216
- return;
217
- }
218
- this.housekeepingTurnsBySession.set(sessionKey, 0);
219
- const activeScope = this.resolveScopeForSession(sessionKey);
220
- if (!activeScope) {
221
- log.info({ sessionKey }, "memory.housekeeping.no_active_scope");
222
- return;
223
- }
224
- const scopeIds = [activeScope.id];
225
- if (isHousekeepingInFlight(scopeIds)) {
226
- log.info({ sessionKey, scope_ids: scopeIds }, "memory.housekeeping.in_flight_skip");
227
- return;
228
- }
229
- void runHousekeeping({ scopeIds }).catch((error) => {
230
- log.error({ err: error, sessionKey, scope_ids: scopeIds }, "memory.housekeeping.error");
231
- });
232
17
  }
233
18
  }
234
19
  //# sourceMappingURL=memory-coordinator.js.map