@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.
- package/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/automation/scheduler.d.ts +13 -7
- package/dist/automation/scheduler.js +116 -37
- package/dist/capability-control/service.d.ts +2 -0
- package/dist/capability-control/service.js +4 -2
- package/dist/cc-orchestrator/agent-adapter.d.ts +4 -0
- package/dist/cc-orchestrator/agent-adapter.js +11 -1
- package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
- package/dist/cc-orchestrator/codex-session-history.js +65 -5
- package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
- package/dist/cc-orchestrator/cwd-normalize.js +19 -0
- package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
- package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
- package/dist/cc-orchestrator/external-agent-changes.js +22 -5
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +265 -50
- package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
- package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
- package/dist/cc-orchestrator/session-history.d.ts +35 -0
- package/dist/cc-orchestrator/session-history.js +98 -15
- package/dist/cli/agent-server-stdio.js +9 -2
- package/dist/context/compaction.d.ts +8 -1
- package/dist/context/compaction.js +49 -4
- package/dist/context/manager.d.ts +16 -2
- package/dist/context/manager.js +103 -19
- package/dist/credentials/access.d.ts +57 -0
- package/dist/credentials/access.js +185 -0
- package/dist/credentials/index.d.ts +3 -1
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +30 -11
- package/dist/credentials/oauth.d.ts +20 -0
- package/dist/credentials/oauth.js +114 -0
- package/dist/credentials/store.d.ts +1 -0
- package/dist/credentials/store.js +3 -1
- package/dist/credentials/types.d.ts +49 -3
- package/dist/credentials/use-credential-tool.d.ts +9 -1
- package/dist/credentials/use-credential-tool.js +58 -45
- package/dist/engine/engine.d.ts +23 -3
- package/dist/engine/engine.js +380 -255
- package/dist/engine/goal.d.ts +17 -0
- package/dist/engine/goal.js +16 -6
- package/dist/engine/image-policy.d.ts +6 -0
- package/dist/engine/image-policy.js +17 -6
- package/dist/engine/input-attachments.d.ts +13 -0
- package/dist/engine/input-attachments.js +398 -0
- package/dist/engine/model-facade.d.ts +5 -2
- package/dist/engine/model-facade.js +4 -4
- package/dist/engine/parse-task.d.ts +10 -0
- package/dist/engine/parse-task.js +5 -0
- package/dist/engine/run-image-input.d.ts +22 -0
- package/dist/engine/run-image-input.js +195 -0
- package/dist/engine/steer-queue.d.ts +3 -1
- package/dist/engine/steer-queue.js +10 -2
- package/dist/engine/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +37 -2
- package/dist/engine/turn-loop.js +228 -43
- package/dist/engine/types.d.ts +8 -0
- package/dist/git/worktree/crud.d.ts +69 -0
- package/dist/git/worktree/crud.js +206 -0
- package/dist/git/worktree/diff.d.ts +14 -0
- package/dist/git/worktree/diff.js +82 -0
- package/dist/git/worktree/git-exec.d.ts +7 -0
- package/dist/git/worktree/git-exec.js +51 -0
- package/dist/git/worktree/index.d.ts +5 -0
- package/dist/git/worktree/index.js +5 -0
- package/dist/git/worktree/query.d.ts +42 -0
- package/dist/git/worktree/query.js +121 -0
- package/dist/git/worktree/slug.d.ts +11 -0
- package/dist/git/worktree/slug.js +58 -0
- package/dist/git/worktree.d.ts +1 -84
- package/dist/git/worktree.js +5 -230
- package/dist/hooks/goal-stop-hook.d.ts +33 -1
- package/dist/hooks/goal-stop-hook.js +202 -34
- package/dist/index.d.ts +31 -28
- package/dist/index.js +27 -25
- package/dist/logging/logger.js +6 -6
- package/dist/logging/sanitize-messages.d.ts +10 -2
- package/dist/logging/sanitize-messages.js +21 -6
- package/dist/plugins/installer/checkUpdate.d.ts +4 -1
- package/dist/plugins/installer/checkUpdate.js +4 -2
- package/dist/plugins/installer/install.js +2 -0
- package/dist/plugins/installer/installFromSource.js +9 -1
- package/dist/plugins/installer/parseSource.d.ts +4 -1
- package/dist/plugins/installer/parseSource.js +28 -10
- package/dist/plugins/installer/sourcePath.d.ts +9 -0
- package/dist/plugins/installer/sourcePath.js +50 -0
- package/dist/plugins/installer/update.d.ts +4 -1
- package/dist/plugins/installer/update.js +5 -3
- package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
- package/dist/plugins/parseMarketplaceInput.js +4 -3
- package/dist/plugins/pluginInstaller.js +24 -22
- package/dist/preset/index.d.ts +1 -0
- package/dist/preset/index.js +30 -13
- package/dist/prompt/sections/base.md +1 -1
- package/dist/protocol/chat-session-manager.d.ts +2 -0
- package/dist/protocol/chat-session-manager.js +38 -2
- package/dist/protocol/chat-session.d.ts +3 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/client.d.ts +9 -4
- package/dist/protocol/client.js +18 -1
- package/dist/protocol/server.d.ts +30 -0
- package/dist/protocol/server.js +247 -47
- package/dist/protocol/types.d.ts +49 -0
- package/dist/protocol/types.js +6 -0
- package/dist/run/FileRunStore.js +10 -1
- package/dist/run/Heartbeat.js +12 -0
- package/dist/run/RunApprovalBackend.d.ts +3 -0
- package/dist/run/RunApprovalBackend.js +41 -6
- package/dist/run/RunLock.js +2 -0
- package/dist/run/RunManager.d.ts +2 -0
- package/dist/run/RunManager.js +64 -24
- package/dist/run/ids.d.ts +2 -0
- package/dist/run/ids.js +23 -0
- package/dist/runtime/background-shell.d.ts +1 -0
- package/dist/runtime/background-shell.js +23 -13
- package/dist/runtime/spawn-common.js +10 -0
- package/dist/services/auto-dream.d.ts +15 -4
- package/dist/services/auto-dream.js +20 -20
- package/dist/services/dream-consolidation.d.ts +2 -3
- package/dist/services/dream-consolidation.js +65 -15
- package/dist/services/extract-memories.d.ts +14 -5
- package/dist/services/extract-memories.js +20 -3
- package/dist/services/global-dream-promotion.d.ts +23 -0
- package/dist/services/global-dream-promotion.js +112 -0
- package/dist/services/memory-orchestrator.js +347 -34
- package/dist/session/memory.d.ts +61 -18
- package/dist/session/memory.js +342 -79
- package/dist/session/session-manager.d.ts +35 -1
- package/dist/session/session-manager.js +224 -4
- package/dist/session/transcript.d.ts +1 -1
- package/dist/session/transcript.js +17 -5
- package/dist/settings/manager.d.ts +1 -0
- package/dist/settings/manager.js +87 -37
- package/dist/settings/schema-export.d.ts +2 -3
- package/dist/settings/schema-export.js +2 -3
- package/dist/settings/schema.d.ts +21 -0
- package/dist/settings/schema.js +12 -0
- package/dist/skills/scanner.d.ts +3 -2
- package/dist/skills/scanner.js +12 -9
- package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
- package/dist/tool-system/builtin/agent-notifications.js +19 -7
- package/dist/tool-system/builtin/background-jobs.d.ts +35 -6
- package/dist/tool-system/builtin/background-jobs.js +116 -7
- package/dist/tool-system/builtin/background-work.d.ts +17 -18
- package/dist/tool-system/builtin/background-work.js +51 -5
- package/dist/tool-system/builtin/config.d.ts +2 -1
- package/dist/tool-system/builtin/config.js +16 -11
- package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
- package/dist/tool-system/builtin/cron-list.definition.js +6 -0
- package/dist/tool-system/builtin/cron.d.ts +1 -2
- package/dist/tool-system/builtin/cron.js +9 -7
- package/dist/tool-system/builtin/drive-claude-code.d.ts +23 -2
- package/dist/tool-system/builtin/drive-claude-code.js +589 -48
- package/dist/tool-system/builtin/edit.js +5 -2
- package/dist/tool-system/builtin/generate-video.d.ts +1 -0
- package/dist/tool-system/builtin/generate-video.js +13 -4
- package/dist/tool-system/builtin/index.d.ts +8 -3
- package/dist/tool-system/builtin/index.js +50 -22
- package/dist/tool-system/builtin/lsp.d.ts +2 -1
- package/dist/tool-system/builtin/lsp.js +6 -3
- package/dist/tool-system/builtin/memory.js +40 -8
- package/dist/tool-system/builtin/notebook-edit.js +5 -2
- package/dist/tool-system/builtin/powershell.d.ts +5 -2
- package/dist/tool-system/builtin/powershell.js +11 -7
- package/dist/tool-system/builtin/read.js +118 -6
- package/dist/tool-system/builtin/sleep.d.ts +1 -2
- package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
- package/dist/tool-system/builtin/sleep.definition.js +28 -0
- package/dist/tool-system/builtin/sleep.js +1 -22
- package/dist/tool-system/builtin/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/view-image.js +109 -19
- package/dist/tool-system/builtin/worktree.d.ts +4 -4
- package/dist/tool-system/builtin/worktree.js +297 -74
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +41 -1
- package/dist/tool-system/executor.d.ts +1 -5
- package/dist/tool-system/executor.js +94 -115
- package/dist/tool-system/mcp-manager.d.ts +31 -6
- package/dist/tool-system/mcp-manager.js +193 -78
- package/dist/tool-system/path-policy.d.ts +2 -0
- package/dist/tool-system/path-policy.js +41 -12
- package/dist/tool-system/permission.d.ts +28 -7
- package/dist/tool-system/permission.js +130 -49
- package/dist/tool-system/registry.js +11 -4
- package/dist/tool-system/tool-result-redaction.d.ts +7 -0
- package/dist/tool-system/tool-result-redaction.js +48 -0
- package/dist/tool-system/workspace-bridge.d.ts +11 -0
- package/dist/tool-system/workspace-bridge.js +1 -0
- package/dist/types.d.ts +78 -11
- package/dist/utils/toolDisplay.js +1 -1
- 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.
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
let
|
|
65
|
-
let
|
|
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
|
|
68
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
+
}
|
package/dist/session/memory.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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?:
|
|
61
|
+
origin?: MemoryOrigin;
|
|
53
62
|
/**
|
|
54
|
-
* Recall lifecycle (召回 TTL). `
|
|
55
|
-
* hits this entry; `
|
|
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
|
|
59
|
-
*
|
|
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.
|
|
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"
|
|
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
|
|
140
|
-
* of the SAME memory root
|
|
141
|
-
* approval
|
|
142
|
-
*
|
|
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
|
|
145
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
257
|
+
private generateId;
|
|
258
|
+
private availableFileNameForId;
|
|
259
|
+
private fileNameForId;
|
|
260
|
+
private parseSyntheticLegacyId;
|
|
218
261
|
}
|