@cjhyy/code-shell-core 0.6.0-rc.8 → 0.7.0-beta.1
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 +2 -0
- package/dist/cc-orchestrator/agent-adapter.js +7 -1
- package/dist/cc-orchestrator/codex-session-history.js +1 -1
- 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-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +102 -51
- 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.js +2 -2
- package/dist/cli/agent-server-stdio.js +9 -2
- package/dist/context/compaction.d.ts +38 -1
- package/dist/context/compaction.js +138 -0
- package/dist/context/manager.d.ts +34 -2
- package/dist/context/manager.js +254 -58
- package/dist/context/token-counter.js +13 -0
- package/dist/credentials/access.d.ts +56 -0
- package/dist/credentials/access.js +183 -0
- package/dist/credentials/index.d.ts +1 -0
- package/dist/credentials/index.js +1 -0
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +30 -11
- package/dist/credentials/types.d.ts +2 -2
- 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 +38 -12
- package/dist/engine/engine.js +477 -163
- 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 +255 -0
- package/dist/engine/model-facade.d.ts +5 -2
- package/dist/engine/model-facade.js +6 -16
- package/dist/engine/parse-task.d.ts +10 -0
- package/dist/engine/parse-task.js +5 -0
- package/dist/engine/query.js +2 -0
- package/dist/engine/session-usage.d.ts +12 -0
- package/dist/engine/session-usage.js +56 -0
- package/dist/engine/steer-queue.d.ts +2 -1
- package/dist/engine/steer-queue.js +2 -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 +34 -2
- package/dist/engine/turn-loop.js +269 -52
- 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/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 +16 -9
- 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 +5 -0
- package/dist/protocol/chat-session.js +2 -0
- package/dist/protocol/client.d.ts +13 -5
- package/dist/protocol/client.js +26 -3
- package/dist/protocol/server.d.ts +43 -12
- package/dist/protocol/server.js +326 -104
- package/dist/protocol/types.d.ts +51 -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/safe-spawn.js +74 -11
- 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 +197 -4
- package/dist/session/transcript.d.ts +5 -1
- package/dist/session/transcript.js +38 -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/background-jobs.d.ts +10 -1
- package/dist/tool-system/builtin/background-jobs.js +12 -1
- 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/drive-claude-code.d.ts +16 -2
- package/dist/tool-system/builtin/drive-claude-code.js +301 -47
- 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 +37 -21
- 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/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 +23 -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 +18 -5
- package/dist/tool-system/mcp-manager.js +157 -74
- package/dist/tool-system/mcp-stdio-diagnostics.d.ts +9 -0
- package/dist/tool-system/mcp-stdio-diagnostics.js +93 -0
- 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 +86 -5
- package/dist/utils/toolDisplay.js +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { MemoryManager, resolveMemoryBaseDir } from "../session/memory.js";
|
|
2
|
+
export function detectUserDirectGlobalPreference(transcript) {
|
|
3
|
+
const userText = transcript
|
|
4
|
+
.filter((m) => m.role === "user")
|
|
5
|
+
.map((m) => m.content)
|
|
6
|
+
.join("\n")
|
|
7
|
+
.toLowerCase();
|
|
8
|
+
return (/(?:全局记住|全局保存|所有项目|全部项目|以后所有项目|今后所有项目|以后都|以后默认|我偏好|我的偏好)/u.test(userText) ||
|
|
9
|
+
/(?:remember globally|global memory|all projects|every project|my preference|i prefer|always remember)/i.test(userText));
|
|
10
|
+
}
|
|
11
|
+
export function applyGlobalDreamPromotionGate(input) {
|
|
12
|
+
const baseDir = resolveMemoryBaseDir(input.baseDir);
|
|
13
|
+
const now = input.now ?? new Date();
|
|
14
|
+
const nowIso = now.toISOString();
|
|
15
|
+
const promotionReason = input.userDirectGlobal
|
|
16
|
+
? "user-direct global preference suggested global dream approval"
|
|
17
|
+
: "candidate suggested global dream approval";
|
|
18
|
+
const projectEvidence = input.projectDir
|
|
19
|
+
? saveProjectDreamCandidate({
|
|
20
|
+
baseDir,
|
|
21
|
+
projectDir: input.projectDir,
|
|
22
|
+
candidate: input.candidate,
|
|
23
|
+
nowIso,
|
|
24
|
+
promotionReason,
|
|
25
|
+
})
|
|
26
|
+
: undefined;
|
|
27
|
+
const originProjects = projectEvidence?.originProjects ?? (input.projectDir ? [input.projectDir] : []);
|
|
28
|
+
const rejected = projectEvidence?.promotionStatus === "rejected";
|
|
29
|
+
const pendingSuggested = !rejected &&
|
|
30
|
+
savePendingGlobalSuggestion({
|
|
31
|
+
baseDir,
|
|
32
|
+
projectDir: input.projectDir,
|
|
33
|
+
candidate: input.candidate,
|
|
34
|
+
source: projectEvidence,
|
|
35
|
+
originProjects,
|
|
36
|
+
nowIso,
|
|
37
|
+
promotionReason,
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
promoted: false,
|
|
41
|
+
pendingSuggested,
|
|
42
|
+
originProjects,
|
|
43
|
+
evidenceCount: originProjects.length,
|
|
44
|
+
projectEvidenceSaved: Boolean(projectEvidence),
|
|
45
|
+
promotionReason,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function saveProjectDreamCandidate(input) {
|
|
49
|
+
const mm = new MemoryManager({
|
|
50
|
+
baseDir: input.baseDir,
|
|
51
|
+
projectDir: input.projectDir,
|
|
52
|
+
scope: "dream",
|
|
53
|
+
});
|
|
54
|
+
const existing = mm
|
|
55
|
+
.loadAll()
|
|
56
|
+
.find((entry) => entry.name === input.candidate.name &&
|
|
57
|
+
(entry.origin === "auto" || entry.origin === "dream"));
|
|
58
|
+
const originProjects = unique([...(existing?.originProjects ?? []), input.projectDir]);
|
|
59
|
+
const promotionStatus = existing?.promotionStatus === "rejected" ? "rejected" : "pending";
|
|
60
|
+
const fileName = mm.save({
|
|
61
|
+
id: existing?.id,
|
|
62
|
+
name: input.candidate.name,
|
|
63
|
+
description: input.candidate.description,
|
|
64
|
+
type: input.candidate.type,
|
|
65
|
+
content: input.candidate.content,
|
|
66
|
+
origin: existing?.origin ?? "auto",
|
|
67
|
+
pinned: existing?.pinned,
|
|
68
|
+
createdAt: existing?.createdAt,
|
|
69
|
+
useCount: existing?.useCount,
|
|
70
|
+
updateCount: existing?.updateCount,
|
|
71
|
+
lastUsedAt: existing?.lastUsedAt,
|
|
72
|
+
originProject: input.projectDir,
|
|
73
|
+
originProjects,
|
|
74
|
+
evidenceCount: originProjects.length,
|
|
75
|
+
firstSeenAt: existing?.firstSeenAt ?? existing?.createdAt ?? input.nowIso,
|
|
76
|
+
lastSeenAt: input.nowIso,
|
|
77
|
+
promotionReason: input.promotionReason,
|
|
78
|
+
promotionStatus,
|
|
79
|
+
}, { forceOrigin: existing?.origin ?? "auto" });
|
|
80
|
+
return mm.find(fileName);
|
|
81
|
+
}
|
|
82
|
+
function savePendingGlobalSuggestion(input) {
|
|
83
|
+
const globalDream = new MemoryManager({ baseDir: input.baseDir, scope: "dream" });
|
|
84
|
+
if (globalDream.loadAll().some((entry) => entry.name === input.candidate.name))
|
|
85
|
+
return false;
|
|
86
|
+
const pending = new MemoryManager({ baseDir: input.baseDir, scope: "pending" });
|
|
87
|
+
const duplicate = pending
|
|
88
|
+
.loadAll()
|
|
89
|
+
.some((entry) => entry.name === input.candidate.name &&
|
|
90
|
+
(entry.originProject ?? "") === (input.projectDir ?? ""));
|
|
91
|
+
if (duplicate)
|
|
92
|
+
return false;
|
|
93
|
+
pending.save({
|
|
94
|
+
name: input.candidate.name,
|
|
95
|
+
description: input.candidate.description,
|
|
96
|
+
type: input.candidate.type,
|
|
97
|
+
content: input.candidate.content,
|
|
98
|
+
origin: "dream",
|
|
99
|
+
originProject: input.projectDir,
|
|
100
|
+
originProjects: input.originProjects,
|
|
101
|
+
evidenceCount: input.originProjects.length,
|
|
102
|
+
firstSeenAt: input.source?.firstSeenAt ?? input.source?.createdAt ?? input.nowIso,
|
|
103
|
+
lastSeenAt: input.nowIso,
|
|
104
|
+
promotionReason: input.promotionReason,
|
|
105
|
+
promotionStatus: "pending",
|
|
106
|
+
promotionSourceId: input.source?.id,
|
|
107
|
+
}, { forceOrigin: "dream" });
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
function unique(values) {
|
|
111
|
+
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
112
|
+
}
|
|
@@ -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
|
+
}
|