@cjhyy/code-shell-core 0.6.0-rc.9 → 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 (193) hide show
  1. package/THIRD_PARTY_NOTICES.md +206 -0
  2. package/dist/automation/scheduler.d.ts +13 -7
  3. package/dist/automation/scheduler.js +116 -37
  4. package/dist/capability-control/service.d.ts +2 -0
  5. package/dist/capability-control/service.js +4 -2
  6. package/dist/cc-orchestrator/agent-adapter.d.ts +4 -0
  7. package/dist/cc-orchestrator/agent-adapter.js +11 -1
  8. package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
  9. package/dist/cc-orchestrator/codex-session-history.js +65 -5
  10. package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
  11. package/dist/cc-orchestrator/cwd-normalize.js +19 -0
  12. package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
  13. package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
  14. package/dist/cc-orchestrator/external-agent-changes.js +22 -5
  15. package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
  16. package/dist/cc-orchestrator/external-agent-driver.js +265 -50
  17. package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
  18. package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
  19. package/dist/cc-orchestrator/session-history.d.ts +35 -0
  20. package/dist/cc-orchestrator/session-history.js +98 -15
  21. package/dist/cli/agent-server-stdio.js +9 -2
  22. package/dist/context/compaction.d.ts +8 -1
  23. package/dist/context/compaction.js +49 -4
  24. package/dist/context/manager.d.ts +16 -2
  25. package/dist/context/manager.js +103 -19
  26. package/dist/credentials/access.d.ts +57 -0
  27. package/dist/credentials/access.js +185 -0
  28. package/dist/credentials/index.d.ts +3 -1
  29. package/dist/credentials/index.js +2 -0
  30. package/dist/credentials/inject-credential-tool.d.ts +3 -1
  31. package/dist/credentials/inject-credential-tool.js +30 -11
  32. package/dist/credentials/oauth.d.ts +20 -0
  33. package/dist/credentials/oauth.js +114 -0
  34. package/dist/credentials/store.d.ts +1 -0
  35. package/dist/credentials/store.js +3 -1
  36. package/dist/credentials/types.d.ts +49 -3
  37. package/dist/credentials/use-credential-tool.d.ts +9 -1
  38. package/dist/credentials/use-credential-tool.js +58 -45
  39. package/dist/engine/engine.d.ts +23 -3
  40. package/dist/engine/engine.js +380 -255
  41. package/dist/engine/goal.d.ts +17 -0
  42. package/dist/engine/goal.js +16 -6
  43. package/dist/engine/image-policy.d.ts +6 -0
  44. package/dist/engine/image-policy.js +17 -6
  45. package/dist/engine/input-attachments.d.ts +13 -0
  46. package/dist/engine/input-attachments.js +398 -0
  47. package/dist/engine/model-facade.d.ts +5 -2
  48. package/dist/engine/model-facade.js +4 -4
  49. package/dist/engine/parse-task.d.ts +10 -0
  50. package/dist/engine/parse-task.js +5 -0
  51. package/dist/engine/run-image-input.d.ts +22 -0
  52. package/dist/engine/run-image-input.js +195 -0
  53. package/dist/engine/steer-queue.d.ts +3 -1
  54. package/dist/engine/steer-queue.js +10 -2
  55. package/dist/engine/streaming-tool-queue.d.ts +11 -7
  56. package/dist/engine/streaming-tool-queue.js +11 -7
  57. package/dist/engine/turn-loop.d.ts +37 -2
  58. package/dist/engine/turn-loop.js +228 -43
  59. package/dist/engine/types.d.ts +8 -0
  60. package/dist/git/worktree/crud.d.ts +69 -0
  61. package/dist/git/worktree/crud.js +206 -0
  62. package/dist/git/worktree/diff.d.ts +14 -0
  63. package/dist/git/worktree/diff.js +82 -0
  64. package/dist/git/worktree/git-exec.d.ts +7 -0
  65. package/dist/git/worktree/git-exec.js +51 -0
  66. package/dist/git/worktree/index.d.ts +5 -0
  67. package/dist/git/worktree/index.js +5 -0
  68. package/dist/git/worktree/query.d.ts +42 -0
  69. package/dist/git/worktree/query.js +121 -0
  70. package/dist/git/worktree/slug.d.ts +11 -0
  71. package/dist/git/worktree/slug.js +58 -0
  72. package/dist/git/worktree.d.ts +1 -84
  73. package/dist/git/worktree.js +5 -230
  74. package/dist/hooks/goal-stop-hook.d.ts +33 -1
  75. package/dist/hooks/goal-stop-hook.js +202 -34
  76. package/dist/index.d.ts +31 -28
  77. package/dist/index.js +27 -25
  78. package/dist/logging/logger.js +6 -6
  79. package/dist/logging/sanitize-messages.d.ts +10 -2
  80. package/dist/logging/sanitize-messages.js +21 -6
  81. package/dist/plugins/installer/checkUpdate.d.ts +4 -1
  82. package/dist/plugins/installer/checkUpdate.js +4 -2
  83. package/dist/plugins/installer/install.js +2 -0
  84. package/dist/plugins/installer/installFromSource.js +9 -1
  85. package/dist/plugins/installer/parseSource.d.ts +4 -1
  86. package/dist/plugins/installer/parseSource.js +28 -10
  87. package/dist/plugins/installer/sourcePath.d.ts +9 -0
  88. package/dist/plugins/installer/sourcePath.js +50 -0
  89. package/dist/plugins/installer/update.d.ts +4 -1
  90. package/dist/plugins/installer/update.js +5 -3
  91. package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
  92. package/dist/plugins/parseMarketplaceInput.js +4 -3
  93. package/dist/plugins/pluginInstaller.js +24 -22
  94. package/dist/preset/index.d.ts +1 -0
  95. package/dist/preset/index.js +30 -13
  96. package/dist/prompt/sections/base.md +1 -1
  97. package/dist/protocol/chat-session-manager.d.ts +2 -0
  98. package/dist/protocol/chat-session-manager.js +38 -2
  99. package/dist/protocol/chat-session.d.ts +3 -0
  100. package/dist/protocol/chat-session.js +1 -0
  101. package/dist/protocol/client.d.ts +9 -4
  102. package/dist/protocol/client.js +18 -1
  103. package/dist/protocol/server.d.ts +30 -0
  104. package/dist/protocol/server.js +247 -47
  105. package/dist/protocol/types.d.ts +49 -0
  106. package/dist/protocol/types.js +6 -0
  107. package/dist/run/FileRunStore.js +10 -1
  108. package/dist/run/Heartbeat.js +12 -0
  109. package/dist/run/RunApprovalBackend.d.ts +3 -0
  110. package/dist/run/RunApprovalBackend.js +41 -6
  111. package/dist/run/RunLock.js +2 -0
  112. package/dist/run/RunManager.d.ts +2 -0
  113. package/dist/run/RunManager.js +64 -24
  114. package/dist/run/ids.d.ts +2 -0
  115. package/dist/run/ids.js +23 -0
  116. package/dist/runtime/background-shell.d.ts +1 -0
  117. package/dist/runtime/background-shell.js +23 -13
  118. package/dist/runtime/spawn-common.js +10 -0
  119. package/dist/services/auto-dream.d.ts +15 -4
  120. package/dist/services/auto-dream.js +20 -20
  121. package/dist/services/dream-consolidation.d.ts +2 -3
  122. package/dist/services/dream-consolidation.js +65 -15
  123. package/dist/services/extract-memories.d.ts +14 -5
  124. package/dist/services/extract-memories.js +20 -3
  125. package/dist/services/global-dream-promotion.d.ts +23 -0
  126. package/dist/services/global-dream-promotion.js +112 -0
  127. package/dist/services/memory-orchestrator.js +347 -34
  128. package/dist/session/memory.d.ts +61 -18
  129. package/dist/session/memory.js +342 -79
  130. package/dist/session/session-manager.d.ts +35 -1
  131. package/dist/session/session-manager.js +224 -4
  132. package/dist/session/transcript.d.ts +1 -1
  133. package/dist/session/transcript.js +17 -5
  134. package/dist/settings/manager.d.ts +1 -0
  135. package/dist/settings/manager.js +87 -37
  136. package/dist/settings/schema-export.d.ts +2 -3
  137. package/dist/settings/schema-export.js +2 -3
  138. package/dist/settings/schema.d.ts +21 -0
  139. package/dist/settings/schema.js +12 -0
  140. package/dist/skills/scanner.d.ts +3 -2
  141. package/dist/skills/scanner.js +12 -9
  142. package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
  143. package/dist/tool-system/builtin/agent-notifications.js +19 -7
  144. package/dist/tool-system/builtin/background-jobs.d.ts +35 -6
  145. package/dist/tool-system/builtin/background-jobs.js +116 -7
  146. package/dist/tool-system/builtin/background-work.d.ts +17 -18
  147. package/dist/tool-system/builtin/background-work.js +51 -5
  148. package/dist/tool-system/builtin/config.d.ts +2 -1
  149. package/dist/tool-system/builtin/config.js +16 -11
  150. package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
  151. package/dist/tool-system/builtin/cron-list.definition.js +6 -0
  152. package/dist/tool-system/builtin/cron.d.ts +1 -2
  153. package/dist/tool-system/builtin/cron.js +9 -7
  154. package/dist/tool-system/builtin/drive-claude-code.d.ts +23 -2
  155. package/dist/tool-system/builtin/drive-claude-code.js +589 -48
  156. package/dist/tool-system/builtin/edit.js +5 -2
  157. package/dist/tool-system/builtin/generate-video.d.ts +1 -0
  158. package/dist/tool-system/builtin/generate-video.js +13 -4
  159. package/dist/tool-system/builtin/index.d.ts +8 -3
  160. package/dist/tool-system/builtin/index.js +50 -22
  161. package/dist/tool-system/builtin/lsp.d.ts +2 -1
  162. package/dist/tool-system/builtin/lsp.js +6 -3
  163. package/dist/tool-system/builtin/memory.js +40 -8
  164. package/dist/tool-system/builtin/notebook-edit.js +5 -2
  165. package/dist/tool-system/builtin/powershell.d.ts +5 -2
  166. package/dist/tool-system/builtin/powershell.js +11 -7
  167. package/dist/tool-system/builtin/read.js +118 -6
  168. package/dist/tool-system/builtin/sleep.d.ts +1 -2
  169. package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
  170. package/dist/tool-system/builtin/sleep.definition.js +28 -0
  171. package/dist/tool-system/builtin/sleep.js +1 -22
  172. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  173. package/dist/tool-system/builtin/view-image.js +109 -19
  174. package/dist/tool-system/builtin/worktree.d.ts +4 -4
  175. package/dist/tool-system/builtin/worktree.js +297 -74
  176. package/dist/tool-system/builtin/write.js +5 -3
  177. package/dist/tool-system/context.d.ts +41 -1
  178. package/dist/tool-system/executor.d.ts +1 -5
  179. package/dist/tool-system/executor.js +94 -115
  180. package/dist/tool-system/mcp-manager.d.ts +31 -6
  181. package/dist/tool-system/mcp-manager.js +193 -78
  182. package/dist/tool-system/path-policy.d.ts +2 -0
  183. package/dist/tool-system/path-policy.js +41 -12
  184. package/dist/tool-system/permission.d.ts +28 -7
  185. package/dist/tool-system/permission.js +130 -49
  186. package/dist/tool-system/registry.js +11 -4
  187. package/dist/tool-system/tool-result-redaction.d.ts +7 -0
  188. package/dist/tool-system/tool-result-redaction.js +48 -0
  189. package/dist/tool-system/workspace-bridge.d.ts +11 -0
  190. package/dist/tool-system/workspace-bridge.js +1 -0
  191. package/dist/types.d.ts +78 -11
  192. package/dist/utils/toolDisplay.js +1 -1
  193. package/package.json +4 -3
@@ -12,12 +12,13 @@
12
12
  * The caller supplies `runDream`, which owns the LLM client and tool registry
13
13
  * — orchestrator just decides WHEN to trigger.
14
14
  */
15
- import { MemoryManager } from "../session/memory.js";
16
- import { buildExtractionPrompt, parseExtractionResponse } from "./extract-memories.js";
15
+ import { MemoryManager, } from "../session/memory.js";
16
+ import { buildExtractionPrompt, parseExtractionResponse, } from "./extract-memories.js";
17
17
  import { saveSessionMemory, buildSessionMemoryPrompt } from "./session-memory.js";
18
18
  import { extractJSON } from "../utils/json.js";
19
19
  import { redactSecrets } from "../logging/sanitize-messages.js";
20
- import { shouldAutoDream, recordSession, recordDreamComplete, buildDreamSystemPrompt, buildDreamUserPrompt } from "./auto-dream.js";
20
+ import { shouldAutoDream, recordSession, recordDreamComplete, buildDreamSystemPrompt, buildDreamUserPrompt, } from "./auto-dream.js";
21
+ import { applyGlobalDreamPromotionGate, detectUserDirectGlobalPreference, } from "./global-dream-promotion.js";
21
22
  import { logger } from "../logging/logger.js";
22
23
  export class MemoryOrchestrator {
23
24
  options;
@@ -45,7 +46,7 @@ export class MemoryOrchestrator {
45
46
  else
46
47
  try {
47
48
  let t = Date.now();
48
- const existing = mm.loadAll();
49
+ const existing = collectExistingMemorySummaries(mm, this.options.projectDir);
49
50
  const loadMs = Date.now() - t;
50
51
  t = Date.now();
51
52
  const extractionPrompt = buildExtractionPrompt(transcript, existing);
@@ -57,45 +58,130 @@ export class MemoryOrchestrator {
57
58
  const entries = parseExtractionResponse(response, this.options.maxCount);
58
59
  const parseMs = Date.now() - t;
59
60
  t = Date.now();
60
- // Route by scope (用户拍板 审批门): "project" memories auto-land in the
61
- // per-project store (`mm`). "global" memories do NOT auto-land global —
62
- // they go to the GLOBAL pending scope and wait for the user to approve in
63
- // the settings panel. Nothing auto-writes the injected global store.
64
- let pendingMm = null;
65
- let globalCount = 0;
61
+ let globalDreamCount = 0;
62
+ let pendingGlobalCount = 0;
63
+ let projectDreamCount = 0;
64
+ let addCount = 0;
65
+ let updateCount = 0;
66
+ let noopCount = 0;
67
+ let deleteCount = 0;
68
+ let guardedManualCount = 0;
69
+ const userDirectGlobal = detectUserDirectGlobalPreference(transcript);
66
70
  for (const entry of entries) {
67
- const isGlobal = entry.scope === "global";
68
- const target = isGlobal
69
- ? (pendingMm ??= new MemoryManager({ scope: "pending" }))
70
- : mm;
71
- target.save({
72
- type: entry.type,
73
- name: entry.name,
74
- // Defense-in-depth: the extraction prompt forbids secrets, but a prompt
75
- // is not a guarantee. An auto-extracted memory is persisted with no user
76
- // review, so run the model-authored strings through the same
77
- // redactSecrets the logging path uses — a leaked key never lands on disk.
71
+ const redactedEntry = {
72
+ ...entry,
78
73
  description: redactSecrets(entry.description),
79
74
  content: redactSecrets(entry.content),
80
- // Provenance mark (feedback#18 方案 C): extractor writes are "auto"
81
- // so the UI can tell curated memories from extractor noise.
82
- origin: "auto",
83
- // 审批门: stamp the source project on pending entries so "不批准/降级"
84
- // can fall them back to the project they were extracted in.
85
- ...(isGlobal && this.options.projectDir
86
- ? { originProject: this.options.projectDir }
87
- : {}),
88
- });
89
- if (isGlobal)
90
- globalCount++;
75
+ };
76
+ const decision = await decideWriteAction(redactedEntry, existing, this.options.callLLM);
77
+ switch (decision.action) {
78
+ case "ADD": {
79
+ const isGlobal = redactedEntry.scope === "global";
80
+ if (isGlobal) {
81
+ const promotion = applyGlobalDreamPromotionGate({
82
+ projectDir: this.options.projectDir,
83
+ candidate: redactedEntry,
84
+ userDirectGlobal,
85
+ });
86
+ if (promotion.projectEvidenceSaved)
87
+ projectDreamCount++;
88
+ if (promotion.pendingSuggested)
89
+ pendingGlobalCount++;
90
+ if (promotion.promoted)
91
+ globalDreamCount++;
92
+ addCount++;
93
+ break;
94
+ }
95
+ const target = memoryManagerForDecision("project", "dream", this.options.projectDir);
96
+ target.save({
97
+ type: redactedEntry.type,
98
+ name: redactedEntry.name,
99
+ description: redactedEntry.description,
100
+ content: redactedEntry.content,
101
+ origin: "auto",
102
+ ...(isGlobal && this.options.projectDir
103
+ ? { originProject: this.options.projectDir }
104
+ : {}),
105
+ }, { forceOrigin: "auto" });
106
+ if (isGlobal)
107
+ globalDreamCount++;
108
+ else
109
+ projectDreamCount++;
110
+ addCount++;
111
+ break;
112
+ }
113
+ case "UPDATE": {
114
+ const targetId = decision.target?.id;
115
+ if (!targetId) {
116
+ noopCount++;
117
+ break;
118
+ }
119
+ const location = decision.target?.location ?? redactedEntry.scope;
120
+ const target = memoryManagerForDecision(location, "dream", this.options.projectDir);
121
+ const existingTarget = target.findById(targetId);
122
+ if (!existingTarget || !target.isOwnedBy(existingTarget, ["auto", "dream"])) {
123
+ noopCount++;
124
+ guardedManualCount += existingTarget?.origin === "manual" ? 1 : 0;
125
+ break;
126
+ }
127
+ target.save({
128
+ id: targetId,
129
+ type: redactedEntry.type,
130
+ name: redactedEntry.name,
131
+ description: redactedEntry.description,
132
+ content: redactedEntry.content,
133
+ origin: existingTarget.origin ?? "auto",
134
+ pinned: existingTarget.pinned,
135
+ createdAt: existingTarget.createdAt,
136
+ useCount: existingTarget.useCount,
137
+ lastUsedAt: existingTarget.lastUsedAt,
138
+ originProject: existingTarget.originProject,
139
+ }, { forceOrigin: existingTarget.origin ?? "auto" });
140
+ if (location === "global")
141
+ globalDreamCount++;
142
+ else
143
+ projectDreamCount++;
144
+ updateCount++;
145
+ break;
146
+ }
147
+ case "DELETE": {
148
+ const targetId = decision.target?.id;
149
+ const location = decision.target?.location ?? redactedEntry.scope;
150
+ const target = memoryManagerForDecision(location, "dream", this.options.projectDir);
151
+ if (!targetId) {
152
+ noopCount++;
153
+ break;
154
+ }
155
+ const deleteResult = target.deleteIfOwned(targetId, ["auto", "dream"]);
156
+ if (deleteResult === "deleted")
157
+ deleteCount++;
158
+ else {
159
+ noopCount++;
160
+ if (deleteResult === "protected")
161
+ guardedManualCount++;
162
+ }
163
+ break;
164
+ }
165
+ case "NOOP":
166
+ noopCount++;
167
+ if (decision.guardedManual)
168
+ guardedManualCount++;
169
+ break;
170
+ }
91
171
  }
92
172
  const saveMs = Date.now() - t;
93
173
  extracted = entries.length;
94
174
  logger.info("memory.extraction_done", {
95
175
  sessionId,
96
176
  extracted,
97
- pendingGlobalCount: globalCount, // global candidates queued for approval
98
- projectCount: extracted - globalCount,
177
+ globalDreamCount,
178
+ pendingGlobalCount,
179
+ projectDreamCount,
180
+ addCount,
181
+ updateCount,
182
+ noopCount,
183
+ deleteCount,
184
+ guardedManualCount,
99
185
  elapsedMs: Date.now() - startTime,
100
186
  loadMs,
101
187
  promptMs,
@@ -224,3 +310,230 @@ export class MemoryOrchestrator {
224
310
  return { extracted, dreamTriggered, pruned };
225
311
  }
226
312
  }
313
+ function memoryManagerForDecision(location, scope, projectDir) {
314
+ return new MemoryManager({
315
+ projectDir: location === "project" ? projectDir : undefined,
316
+ scope,
317
+ });
318
+ }
319
+ function collectExistingMemorySummaries(projectUserManager, projectDir) {
320
+ const seen = new Set();
321
+ const out = [];
322
+ const add = (entries, location, memoryScope) => {
323
+ for (const entry of entries) {
324
+ const key = `${location}:${memoryScope}:${entry.fileName}:${entry.id ?? ""}`;
325
+ if (seen.has(key))
326
+ continue;
327
+ seen.add(key);
328
+ out.push({
329
+ id: entry.id,
330
+ name: entry.name,
331
+ type: entry.type,
332
+ description: entry.description,
333
+ location,
334
+ memoryScope,
335
+ origin: entry.origin ?? "manual",
336
+ pinned: entry.pinned,
337
+ useCount: entry.useCount,
338
+ updateCount: entry.updateCount,
339
+ updatedAt: entry.updatedAt,
340
+ entry,
341
+ });
342
+ }
343
+ };
344
+ add(projectUserManager.loadScope("user"), "project", "user");
345
+ add(memoryManagerForDecision("project", "dream", projectDir).loadAll(), "project", "dream");
346
+ add(memoryManagerForDecision("global", "dream", projectDir).loadAll(), "global", "dream");
347
+ add(memoryManagerForDecision("global", "user", projectDir).loadAll(), "global", "user");
348
+ return out
349
+ .sort((a, b) => {
350
+ const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
351
+ const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
352
+ return bTime - aTime;
353
+ })
354
+ .slice(0, 120);
355
+ }
356
+ async function decideWriteAction(candidate, existing, callLLM) {
357
+ const fallback = fallbackWriteDecision(candidate, existing);
358
+ const related = rankRelated(candidate, existing).slice(0, 40);
359
+ const llmDecision = await askLLMForWriteDecision(candidate, related.map((r) => r.summary), callLLM);
360
+ if (!llmDecision)
361
+ return fallback;
362
+ const target = llmDecision.target?.id
363
+ ? existing.find((m) => m.id === llmDecision.target?.id)
364
+ : undefined;
365
+ if ((llmDecision.action === "UPDATE" || llmDecision.action === "DELETE") && target) {
366
+ if (target.origin === "manual") {
367
+ return {
368
+ action: "NOOP",
369
+ memory: candidate,
370
+ reason: "manual memory is protected",
371
+ guardedManual: true,
372
+ };
373
+ }
374
+ if (target.memoryScope === "dream" && (target.origin === "auto" || target.origin === "dream")) {
375
+ return {
376
+ ...llmDecision,
377
+ memory: candidate,
378
+ target: {
379
+ id: target.id,
380
+ location: target.location,
381
+ scope: "dream",
382
+ },
383
+ };
384
+ }
385
+ }
386
+ // Downgrade unsafe or duplicate ADDs using the deterministic guard.
387
+ if (llmDecision.action === "ADD" && fallback.action !== "ADD")
388
+ return fallback;
389
+ if (llmDecision.action === "NOOP")
390
+ return { ...llmDecision, memory: candidate };
391
+ if (llmDecision.action === "ADD")
392
+ return { ...llmDecision, memory: candidate };
393
+ return fallback;
394
+ }
395
+ async function askLLMForWriteDecision(candidate, related, callLLM) {
396
+ if (related.length === 0)
397
+ return null;
398
+ try {
399
+ const relatedText = related
400
+ .map((m) => `- id:${m.id ?? "(none)"} location:${m.location} scope:${m.memoryScope} origin:${m.origin} ` +
401
+ `[${m.type}] ${m.name}: ${m.description}`)
402
+ .join("\n");
403
+ const response = await callLLM("You are a memory write decision assistant. Output only one JSON object.", [
404
+ "Decide whether this automatic memory candidate should ADD, UPDATE, NOOP, or DELETE.",
405
+ "Manual memories are protected: if the target is origin:manual, choose NOOP.",
406
+ "Automatic extraction writes new entries only to dream. Reuse an existing auto/dream dream id for same-topic updates.",
407
+ "",
408
+ "Candidate:",
409
+ JSON.stringify(candidate),
410
+ "",
411
+ "Related existing memories:",
412
+ relatedText,
413
+ "",
414
+ 'Respond with JSON: {"action":"ADD|UPDATE|DELETE|NOOP","target":{"id":"...","location":"project|global","scope":"dream|user"},"reason":"short","confidence":"high|medium|low"}',
415
+ ].join("\n"));
416
+ const parsed = JSON.parse(extractJSON(response));
417
+ const action = typeof parsed.action === "string" ? parsed.action.toUpperCase() : "";
418
+ if (!["ADD", "UPDATE", "DELETE", "NOOP"].includes(action))
419
+ return null;
420
+ const location = parsed.target?.location === "global" || parsed.target?.location === "project"
421
+ ? parsed.target.location
422
+ : candidate.scope;
423
+ const scope = parsed.target?.scope === "user" ? "user" : "dream";
424
+ return {
425
+ action: action,
426
+ target: parsed.target?.id ? { id: String(parsed.target.id), location, scope } : undefined,
427
+ memory: candidate,
428
+ reason: typeof parsed.reason === "string" ? parsed.reason : "llm decision",
429
+ };
430
+ }
431
+ catch {
432
+ return null;
433
+ }
434
+ }
435
+ function fallbackWriteDecision(candidate, existing) {
436
+ const ranked = rankRelated(candidate, existing);
437
+ const strong = ranked.filter((m) => m.score >= 0.55 || m.canonicalMatch);
438
+ const manual = strong.find((m) => m.summary.origin === "manual");
439
+ if (manual) {
440
+ return {
441
+ action: "NOOP",
442
+ memory: candidate,
443
+ reason: `same topic as manual memory ${manual.summary.id ?? manual.summary.name}`,
444
+ guardedManual: true,
445
+ };
446
+ }
447
+ const ownedDream = strong.find((m) => m.summary.location === candidate.scope &&
448
+ m.summary.memoryScope === "dream" &&
449
+ (m.summary.origin === "auto" || m.summary.origin === "dream") &&
450
+ m.summary.id &&
451
+ !m.summary.id.startsWith("legacy:"));
452
+ if (ownedDream?.summary.id) {
453
+ return {
454
+ action: "UPDATE",
455
+ target: {
456
+ id: ownedDream.summary.id,
457
+ location: ownedDream.summary.location,
458
+ scope: "dream",
459
+ },
460
+ memory: candidate,
461
+ reason: `same topic as auto/dream memory ${ownedDream.summary.id}`,
462
+ };
463
+ }
464
+ return { action: "ADD", memory: candidate, reason: "new automatic candidate" };
465
+ }
466
+ function rankRelated(candidate, existing) {
467
+ const candidateText = `${candidate.name} ${candidate.description}`;
468
+ const candidateKey = canonicalKey(candidateText);
469
+ const candidateTokens = tokenSet(candidateText);
470
+ return existing
471
+ .map((summary) => {
472
+ const existingText = `${summary.name} ${summary.description}`;
473
+ const existingKey = canonicalKey(existingText);
474
+ const existingTokens = tokenSet(existingText);
475
+ return {
476
+ summary,
477
+ score: tokenOverlap(candidateTokens, existingTokens),
478
+ canonicalMatch: candidateKey.length > 0 && candidateKey === existingKey,
479
+ };
480
+ })
481
+ .sort((a, b) => {
482
+ if (a.canonicalMatch !== b.canonicalMatch)
483
+ return a.canonicalMatch ? -1 : 1;
484
+ if (a.score !== b.score)
485
+ return b.score - a.score;
486
+ const aOwned = a.summary.origin === "auto" || a.summary.origin === "dream" ? 1 : 0;
487
+ const bOwned = b.summary.origin === "auto" || b.summary.origin === "dream" ? 1 : 0;
488
+ if (aOwned !== bOwned)
489
+ return bOwned - aOwned;
490
+ const aTime = a.summary.updatedAt ? new Date(a.summary.updatedAt).getTime() : 0;
491
+ const bTime = b.summary.updatedAt ? new Date(b.summary.updatedAt).getTime() : 0;
492
+ return bTime - aTime;
493
+ });
494
+ }
495
+ function canonicalKey(text) {
496
+ return [...tokenSet(text)].sort().join(" ");
497
+ }
498
+ function tokenSet(text) {
499
+ const normalized = text
500
+ .toLowerCase()
501
+ .replace(/\b\d{4}-\d{2}-\d{2}\b/g, " ")
502
+ .replace(/\b\d{8}\b/g, " ")
503
+ .replace(/\b(today|yesterday|tomorrow|本轮|今天|昨天|明天)\b/g, " ")
504
+ .replace(/\bv\d+\b/g, " ")
505
+ .replace(/\b(batch|fix-batch)-\d+\b/g, " ")
506
+ .replace(/\b[a-f0-9]{7,}\b/g, " ")
507
+ .replace(/[\d]+/g, " ")
508
+ .replace(/[^a-z0-9\u4e00-\u9fff]+/g, " ");
509
+ const stop = new Set([
510
+ "the",
511
+ "and",
512
+ "for",
513
+ "with",
514
+ "that",
515
+ "this",
516
+ "from",
517
+ "memory",
518
+ "memories",
519
+ "should",
520
+ "must",
521
+ "不要",
522
+ "需要",
523
+ "已经",
524
+ ]);
525
+ return new Set(normalized
526
+ .split(/\s+/)
527
+ .map((t) => t.trim())
528
+ .filter((t) => t.length > 2 && !stop.has(t)));
529
+ }
530
+ function tokenOverlap(a, b) {
531
+ if (a.size === 0 || b.size === 0)
532
+ return 0;
533
+ let shared = 0;
534
+ for (const token of a) {
535
+ if (b.has(token))
536
+ shared++;
537
+ }
538
+ return shared / Math.min(a.size, b.size);
539
+ }
@@ -12,7 +12,11 @@
12
12
  * auto-extracted entries (extract-memories output). LLM may
13
13
  * only modify these through permission-gated tool calls.
14
14
  * - dream/ The dream pipeline's workspace. The LLM is free to add /
15
- * merge / delete entries here; user/ is read-only to dream.
15
+ * merge / delete entries here. The dream loop may ALSO maintain
16
+ * user/ entries whose origin is `auto` or `dream` (dedup/merge/
17
+ * improve), but origin:`manual` user entries — things the user
18
+ * explicitly asked to remember — are never touched by dream.
19
+ * See dream-consolidation.ts (the origin guard at ~:240).
16
20
  *
17
21
  * MEMORY.md is the index file (one per scope).
18
22
  *
@@ -29,15 +33,20 @@
29
33
  * panel. Keeps the global layer curated — nothing auto-lands global.
30
34
  */
31
35
  export type MemoryScope = "user" | "dream" | "pending";
36
+ export type MemoryOrigin = "auto" | "manual" | "dream";
32
37
  export interface MemoryEntry {
38
+ /** Stable identity. Legacy files without frontmatter id read as legacy:<scope>:<fileName>. */
39
+ id?: string;
33
40
  name: string;
34
41
  description: string;
35
42
  type: "user" | "feedback" | "project" | "reference";
36
43
  content: string;
37
44
  fileName: string;
38
45
  scope: MemoryScope;
46
+ /** Semantic last content update time from frontmatter. */
47
+ updatedAt?: string;
39
48
  /** File mtime in epoch ms — drives maxAge filtering (TODO 8.1). 0 if unknown. */
40
- updatedAt?: number;
49
+ mtimeMs?: number;
41
50
  /**
42
51
  * 固定/置顶 (feedback#18 方案 A): a pinned memory is exempt from maxAge
43
52
  * injection filtering and sorts first in the injected context. UI surfaces
@@ -49,15 +58,20 @@ export interface MemoryEntry {
49
58
  * "manual" (or absent — legacy files) = written by the user/UI. Lets the
50
59
  * UI distinguish curated memories from extractor noise.
51
60
  */
52
- origin?: "auto" | "manual";
61
+ origin?: MemoryOrigin;
53
62
  /**
54
- * Recall lifecycle (召回 TTL). `usageCount` increments each time MemoryRead
55
- * hits this entry; `lastUsed` is the ISO timestamp of that last hit; `created`
56
- * is set on first save and preserved across UPDATE. Drives recall-based TTL:
63
+ * Recall lifecycle (召回 TTL). `useCount` increments each time MemoryRead
64
+ * hits this entry; `lastUsedAt` is the ISO timestamp of that last hit;
65
+ * `createdAt` is set on first save and preserved across UPDATE. Drives recall-based TTL:
57
66
  * a `project`-type memory not read for N days is pruned. Stored in frontmatter
58
- * — no SQLite. Legacy files lacking these fields read back as
59
- * {usageCount:0, lastUsed/created mtime}, never hidden.
67
+ * — no SQLite. Legacy `usageCount/lastUsed/created` fields are still read and
68
+ * exposed through their old aliases for compatibility.
60
69
  */
70
+ useCount?: number;
71
+ updateCount?: number;
72
+ lastUsedAt?: string;
73
+ createdAt?: string;
74
+ /** Legacy compatibility aliases. New writes use useCount/lastUsedAt/createdAt. */
61
75
  usageCount?: number;
62
76
  lastUsed?: string;
63
77
  created?: string;
@@ -67,6 +81,15 @@ export interface MemoryEntry {
67
81
  * user store (not the current one). Absent → fall back to no-repo / global.
68
82
  */
69
83
  originProject?: string;
84
+ /** Global dream promotion metadata (P1). */
85
+ promotionKey?: string;
86
+ originProjects?: string[];
87
+ evidenceCount?: number;
88
+ firstSeenAt?: string;
89
+ lastSeenAt?: string;
90
+ promotionReason?: string;
91
+ promotionStatus?: "pending" | "rejected";
92
+ promotionSourceId?: string;
70
93
  }
71
94
  /**
72
95
  * Drop memories older than `maxAgeDays` (by file mtime) for context injection
@@ -75,6 +98,12 @@ export interface MemoryEntry {
75
98
  * just because we couldn't read its timestamp. Pure + testable.
76
99
  */
77
100
  export declare function filterByAge(entries: MemoryEntry[], maxAgeDays?: number, now?: number): MemoryEntry[];
101
+ export interface SaveMemoryOptions {
102
+ /** False for lifecycle-only writes such as recordRecall and pin/unpin. */
103
+ incrementUpdateCount?: boolean;
104
+ /** Force provenance regardless of model/tool args. Used by origin guards. */
105
+ forceOrigin?: MemoryOrigin;
106
+ }
78
107
  export interface MemoryManagerOptions {
79
108
  /** Project root path; when set, memories are scoped under projects/<hash>/memory. */
80
109
  projectDir?: string;
@@ -97,13 +126,18 @@ export declare class MemoryManager {
97
126
  getMemoryDir(): string;
98
127
  getScope(): MemoryScope;
99
128
  /**
100
- * Save a memory entry. Creates the file and updates the index.
129
+ * Save a memory entry. New entries get a stable id and `<id>.md`; entries
130
+ * carrying an existing id update that file even when name changes.
101
131
  */
102
- save(entry: Omit<MemoryEntry, "fileName" | "scope">): string;
132
+ save(entry: Omit<MemoryEntry, "fileName" | "scope">, opts?: SaveMemoryOptions): string;
103
133
  /**
104
134
  * Load all memory entries.
105
135
  */
106
136
  loadAll(): MemoryEntry[];
137
+ findById(id: string): MemoryEntry | undefined;
138
+ find(nameOrFileOrId: string): MemoryEntry | undefined;
139
+ isOwnedBy(entry: MemoryEntry | undefined, allowedOrigins: readonly MemoryOrigin[]): boolean;
140
+ deleteIfOwned(nameOrFileOrId: string, allowedOrigins: readonly MemoryOrigin[]): "deleted" | "not_found" | "protected";
107
141
  /**
108
142
  * Load a single memory file.
109
143
  */
@@ -136,13 +170,14 @@ export declare class MemoryManager {
136
170
  */
137
171
  pruneByRecall(ttlDays: number, now?: Date): string[];
138
172
  /**
139
- * Approve a pending memory: move it from the pending scope into the user scope
140
- * of the SAME memory root (pending only ever exists at the global root — the
141
- * approval only gates global writes). Must be called on a pending-scope
142
- * manager. Returns the new user filename, or null if not found / wrong scope.
173
+ * Approve a pending memory: move it from the pending scope into the global
174
+ * dream scope of the SAME memory root. Pending only ever exists at the global
175
+ * root; approval gates whether a suggested global dream becomes injected.
176
+ * Must be called on a pending-scope manager. Returns the new global dream
177
+ * filename, or null if not found / wrong scope.
143
178
  *
144
- * 审批门 (用户拍板): the only path that moves an auto-extracted memory into the
145
- * curated, injected global user store.
179
+ * 审批门 (用户拍板): the only path that moves a suggested global dream into the
180
+ * injected global dream store.
146
181
  */
147
182
  approvePending(nameOrFile: string): string | null;
148
183
  /**
@@ -150,7 +185,12 @@ export declare class MemoryManager {
150
185
  * user store (originProject)。无 originProject 时落全局 user(兜底,不丢)。
151
186
  */
152
187
  demotePending(nameOrFile: string): string | null;
153
- /** Shared move for approve(→global user)/demote(→origin-project user). */
188
+ /**
189
+ * Reject a pending global-dream suggestion. The project dream entry stays in
190
+ * place, but is marked so the same source is not suggested again.
191
+ */
192
+ rejectPending(nameOrFile: string): boolean;
193
+ /** Shared move for approve(→global dream)/demote(→origin-project user). */
154
194
  private movePending;
155
195
  /**
156
196
  * Promote a project-level user memory to the GLOBAL user store (用户手动点
@@ -214,5 +254,8 @@ export declare class MemoryManager {
214
254
  */
215
255
  private ensureIndexCache;
216
256
  private writeIndex;
217
- private slugify;
257
+ private generateId;
258
+ private availableFileNameForId;
259
+ private fileNameForId;
260
+ private parseSyntheticLegacyId;
218
261
  }