@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
|
@@ -23,16 +23,16 @@
|
|
|
23
23
|
* worker discover and reap them (§难点1, `reapOrphansFromPidfiles`).
|
|
24
24
|
*/
|
|
25
25
|
import { spawn } from "node:child_process";
|
|
26
|
-
import { homedir } from "node:os";
|
|
27
26
|
import { join } from "node:path";
|
|
28
|
-
import { mkdirSync, writeFileSync, rmSync, existsSync, readdirSync, readFileSync
|
|
27
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync, readdirSync, readFileSync } from "node:fs";
|
|
29
28
|
import { StringDecoder } from "node:string_decoder";
|
|
30
29
|
import { utf8SafeCutLength } from "./utf8-cut.js";
|
|
31
|
-
import { resolveSpawnTarget, buildSandboxEnv, mergeShellEnv, killProcessGroup, groupAlive, defaultShellBinary } from "./spawn-common.js";
|
|
30
|
+
import { resolveSpawnTarget, buildSandboxEnv, mergeShellEnv, killProcessGroup, groupAlive, defaultShellBinary, } from "./spawn-common.js";
|
|
32
31
|
import { RingFile } from "./ring-file.js";
|
|
33
32
|
import { cleanOutput } from "./output-clean.js";
|
|
34
33
|
import { notificationQueue } from "../tool-system/builtin/agent-notifications.js";
|
|
35
34
|
import { logger } from "../logging/logger.js";
|
|
35
|
+
import { codeShellHome } from "../session/session-manager.js";
|
|
36
36
|
/** Per-session soft cap on background shells (design §7 — fork-bomb guard). */
|
|
37
37
|
export const MAX_SHELLS_PER_SESSION = 16;
|
|
38
38
|
/** Disk ring-file cap: keep the most recent 8MB of raw output (design §6). */
|
|
@@ -46,11 +46,10 @@ function nextShellId() {
|
|
|
46
46
|
// Short, collision-resistant enough within a process: counter + base36 of
|
|
47
47
|
// a monotonic-ish value. Avoids Date.now()/Math.random() (banned in some
|
|
48
48
|
// contexts and unnecessary here).
|
|
49
|
-
return `bg_${shellCounter.toString(36)}${(shellCounter * 2654435761 % 0xffffff).toString(36)}`;
|
|
49
|
+
return `bg_${shellCounter.toString(36)}${((shellCounter * 2654435761) % 0xffffff).toString(36)}`;
|
|
50
50
|
}
|
|
51
51
|
function bgShellsRoot() {
|
|
52
|
-
|
|
53
|
-
return join(base, ".code-shell", "bg-shells");
|
|
52
|
+
return join(codeShellHome(), "bg-shells");
|
|
54
53
|
}
|
|
55
54
|
const PORT_REGEXES = [
|
|
56
55
|
/localhost:(\d{2,5})/i,
|
|
@@ -78,7 +77,11 @@ export class BackgroundShellManager {
|
|
|
78
77
|
// the bg-shells root — refuse rather than trust the caller. Mirrors
|
|
79
78
|
// SessionManager.assertSafeSessionId without importing across the layer.
|
|
80
79
|
const sid = opts.sessionId;
|
|
81
|
-
if (typeof sid !== "string" ||
|
|
80
|
+
if (typeof sid !== "string" ||
|
|
81
|
+
sid.length === 0 ||
|
|
82
|
+
sid.includes("..") ||
|
|
83
|
+
sid.includes("/") ||
|
|
84
|
+
sid.includes("\\")) {
|
|
82
85
|
return { ok: false, error: `invalid sessionId for background shell` };
|
|
83
86
|
}
|
|
84
87
|
// Per-session soft cap (fork-bomb guard, §7).
|
|
@@ -220,9 +223,7 @@ export class BackgroundShellManager {
|
|
|
220
223
|
}
|
|
221
224
|
enqueueExitNotification(sh) {
|
|
222
225
|
const ok = sh.exitCode === 0;
|
|
223
|
-
const exitDesc = sh.signal
|
|
224
|
-
? `signal ${sh.signal}`
|
|
225
|
-
: `exit ${sh.exitCode ?? "?"}`;
|
|
226
|
+
const exitDesc = sh.signal ? `signal ${sh.signal}` : `exit ${sh.exitCode ?? "?"}`;
|
|
226
227
|
notificationQueue.enqueue({
|
|
227
228
|
agentId: sh.shellId,
|
|
228
229
|
// English description is the agent-facing wakeup text. The UI ignores
|
|
@@ -234,7 +235,9 @@ export class BackgroundShellManager {
|
|
|
234
235
|
status: ok ? "completed" : "failed",
|
|
235
236
|
workKind: "shell",
|
|
236
237
|
command: sh.command,
|
|
237
|
-
error: ok
|
|
238
|
+
error: ok
|
|
239
|
+
? undefined
|
|
240
|
+
: `Background shell ${sh.shellId} exited with ${exitDesc}. Use BashOutput("${sh.shellId}") to inspect.`,
|
|
238
241
|
enqueuedAt: Date.now(),
|
|
239
242
|
}, sh.sessionId);
|
|
240
243
|
}
|
|
@@ -267,6 +270,9 @@ export class BackgroundShellManager {
|
|
|
267
270
|
.filter((s) => s.sessionId === sessionId)
|
|
268
271
|
.map((s) => this.toPublic(s));
|
|
269
272
|
}
|
|
273
|
+
list() {
|
|
274
|
+
return [...this.shells.values()].map((s) => this.toPublic(s));
|
|
275
|
+
}
|
|
270
276
|
/**
|
|
271
277
|
* Read a shell's output. `mode=incremental` (default) returns text since the
|
|
272
278
|
* agent's last read; `mode=all` returns the full retained buffer. Output is
|
|
@@ -420,7 +426,9 @@ export class BackgroundShellManager {
|
|
|
420
426
|
try {
|
|
421
427
|
rmSync(path, { force: true });
|
|
422
428
|
}
|
|
423
|
-
catch {
|
|
429
|
+
catch {
|
|
430
|
+
/* ignore */
|
|
431
|
+
}
|
|
424
432
|
continue;
|
|
425
433
|
}
|
|
426
434
|
// Skip ones we already track in-process (this worker's own).
|
|
@@ -434,7 +442,9 @@ export class BackgroundShellManager {
|
|
|
434
442
|
try {
|
|
435
443
|
rmSync(path, { force: true });
|
|
436
444
|
}
|
|
437
|
-
catch {
|
|
445
|
+
catch {
|
|
446
|
+
/* ignore */
|
|
447
|
+
}
|
|
438
448
|
}
|
|
439
449
|
}
|
|
440
450
|
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { spawn } from "node:child_process";
|
|
37
37
|
import { StringDecoder } from "node:string_decoder";
|
|
38
|
-
import { resolveSpawnTarget, defaultShellBinary, killChildTree } from "./spawn-common.js";
|
|
38
|
+
import { resolveSpawnTarget, defaultShellBinary, killChildTree, killProcessGroup } from "./spawn-common.js";
|
|
39
39
|
export const DEFAULT_MAX_OUTPUT_BYTES = 1_000_000;
|
|
40
40
|
export const DEFAULT_IO_DRAIN_GRACE_MS = 100;
|
|
41
41
|
const TIMEOUT_SIGKILL_GRACE_MS = 2000;
|
|
@@ -71,9 +71,21 @@ export function safeSpawnShell(command, opts) {
|
|
|
71
71
|
shell,
|
|
72
72
|
sandbox: opts.sandbox,
|
|
73
73
|
});
|
|
74
|
-
|
|
74
|
+
// Shell commands run free-form LLM strings that routinely background
|
|
75
|
+
// grandchildren (`pytest &`, `sh → npm → node`). Spawn as a process-group
|
|
76
|
+
// leader (detached) so a timeout/abort kill reaches the WHOLE subtree, not
|
|
77
|
+
// just the direct shell — otherwise an orphaned grandchild keeps the
|
|
78
|
+
// inherited stdout pipe open and Node's `close` never fires (the hang).
|
|
79
|
+
return runLifecycle({
|
|
80
|
+
file,
|
|
81
|
+
args,
|
|
82
|
+
opts,
|
|
83
|
+
cleanup,
|
|
84
|
+
resolveMs: elapsedMs(resolveStartedAt),
|
|
85
|
+
detached: true,
|
|
86
|
+
});
|
|
75
87
|
}
|
|
76
|
-
function runLifecycle({ file, args, opts, cleanup, resolveMs }) {
|
|
88
|
+
function runLifecycle({ file, args, opts, cleanup, resolveMs, detached }) {
|
|
77
89
|
const maxBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
78
90
|
const abortGrace = opts.ioDrainGraceMs ?? DEFAULT_IO_DRAIN_GRACE_MS;
|
|
79
91
|
const lifecycleStartedAt = spawnProfileEnabled() ? performance.now() : 0;
|
|
@@ -84,10 +96,19 @@ function runLifecycle({ file, args, opts, cleanup, resolveMs }) {
|
|
|
84
96
|
}
|
|
85
97
|
return new Promise((resolve) => {
|
|
86
98
|
let settled = false;
|
|
99
|
+
// Declared before finish() so the spawn-failed early-return (which calls
|
|
100
|
+
// finish before these are assigned) doesn't hit a TDZ reference.
|
|
101
|
+
let timer;
|
|
102
|
+
let settleTimer;
|
|
103
|
+
let onAbort;
|
|
87
104
|
const finish = (result) => {
|
|
88
105
|
if (settled)
|
|
89
106
|
return;
|
|
90
107
|
settled = true;
|
|
108
|
+
if (timer)
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
if (settleTimer)
|
|
111
|
+
clearTimeout(settleTimer);
|
|
91
112
|
logSpawnProfile(file, args, resolveMs, elapsedMs(lifecycleStartedAt), result.reason);
|
|
92
113
|
// Always release backend-allocated resources, regardless of exit path.
|
|
93
114
|
// cleanup is best-effort — see seatbelt backend for rationale.
|
|
@@ -101,7 +122,7 @@ function runLifecycle({ file, args, opts, cleanup, resolveMs }) {
|
|
|
101
122
|
};
|
|
102
123
|
let child;
|
|
103
124
|
try {
|
|
104
|
-
child = spawn(file, args, { cwd: opts.cwd, env: opts.env });
|
|
125
|
+
child = spawn(file, args, { cwd: opts.cwd, env: opts.env, detached });
|
|
105
126
|
}
|
|
106
127
|
catch (err) {
|
|
107
128
|
finish(emptyResult({ reason: "spawn_failed", spawnFailed: true, error: err.message }));
|
|
@@ -115,21 +136,63 @@ function runLifecycle({ file, args, opts, cleanup, resolveMs }) {
|
|
|
115
136
|
let stderrTruncated = false;
|
|
116
137
|
let timedOut = false;
|
|
117
138
|
let aborted = false;
|
|
118
|
-
|
|
139
|
+
let lastExitCode = null;
|
|
140
|
+
let lastExitSignal = null;
|
|
141
|
+
// Terminate the child on timeout/abort. When detached (shell mode) the
|
|
142
|
+
// child leads its own process group, so kill the WHOLE group — this reaps
|
|
143
|
+
// backgrounded grandchildren (`pytest &`, `sh → npm → node`) that a bare
|
|
144
|
+
// child.kill() would orphan. After the kill, arm a settle deadline:
|
|
145
|
+
// Node's `close` waits for every inherited stdio pipe to close, and an
|
|
146
|
+
// orphaned grandchild can hold stdout open forever, so `close` may never
|
|
147
|
+
// fire. Force a resolve via the last-seen `exit` code once the kill grace
|
|
148
|
+
// has elapsed — the promise must never hang past terminate().
|
|
149
|
+
const terminate = (graceMs) => {
|
|
150
|
+
if (detached && typeof child.pid === "number") {
|
|
151
|
+
void killProcessGroup(child.pid, { graceMs });
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
killChildTree(child, graceMs);
|
|
155
|
+
}
|
|
156
|
+
if (settleTimer)
|
|
157
|
+
return;
|
|
158
|
+
settleTimer = setTimeout(() => {
|
|
159
|
+
finishFromExit();
|
|
160
|
+
}, graceMs + 500);
|
|
161
|
+
settleTimer.unref?.();
|
|
162
|
+
};
|
|
163
|
+
timer = setTimeout(() => {
|
|
119
164
|
timedOut = true;
|
|
120
|
-
|
|
121
|
-
// spawns node children that child.kill() alone would orphan). POSIX:
|
|
122
|
-
// SIGTERM → grace → SIGKILL. See killChildTree.
|
|
123
|
-
killChildTree(child, TIMEOUT_SIGKILL_GRACE_MS);
|
|
165
|
+
terminate(TIMEOUT_SIGKILL_GRACE_MS);
|
|
124
166
|
}, opts.timeoutMs);
|
|
125
|
-
let onAbort;
|
|
126
167
|
if (opts.signal) {
|
|
127
168
|
onAbort = () => {
|
|
128
169
|
aborted = true;
|
|
129
|
-
|
|
170
|
+
terminate(abortGrace);
|
|
130
171
|
};
|
|
131
172
|
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
132
173
|
}
|
|
174
|
+
// Fallback finish when `close` never arrives because a killed process's
|
|
175
|
+
// orphaned grandchild still holds a stdio pipe. Uses whatever `exit` code
|
|
176
|
+
// we saw (exit fires when the direct child dies, independent of stdio).
|
|
177
|
+
const finishFromExit = () => {
|
|
178
|
+
const reason = timedOut ? "timeout" : aborted ? "aborted" : "ok";
|
|
179
|
+
finish({
|
|
180
|
+
reason,
|
|
181
|
+
stdout,
|
|
182
|
+
stderr,
|
|
183
|
+
exitCode: lastExitCode,
|
|
184
|
+
signal: lastExitSignal,
|
|
185
|
+
stdoutTruncated,
|
|
186
|
+
stderrTruncated,
|
|
187
|
+
timedOut,
|
|
188
|
+
aborted,
|
|
189
|
+
spawnFailed: false,
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
child.on("exit", (code, sig) => {
|
|
193
|
+
lastExitCode = code;
|
|
194
|
+
lastExitSignal = sig;
|
|
195
|
+
});
|
|
133
196
|
child.stdout?.on("data", (chunk) => {
|
|
134
197
|
if (stdoutTruncated)
|
|
135
198
|
return;
|
|
@@ -159,6 +159,13 @@ export function resolveGitBash() {
|
|
|
159
159
|
const override = process.env.CODE_SHELL_GIT_BASH_PATH;
|
|
160
160
|
if (override && existsSync(override))
|
|
161
161
|
return (gitBashCache = override);
|
|
162
|
+
// Test-only hermetic switch: skip auto-discovery entirely. Clearing PATH is
|
|
163
|
+
// NOT enough on Windows — CreateProcess always searches System32, so `where`
|
|
164
|
+
// still spawns and a CI runner's preinstalled Git for Windows would resolve a
|
|
165
|
+
// real bash.exe and defeat the fallback tests. This env flag makes the
|
|
166
|
+
// "Git Bash absent" scenario reproducible on a real Windows host.
|
|
167
|
+
if (process.env.CODE_SHELL_NO_SHELL_DISCOVERY)
|
|
168
|
+
return (gitBashCache = null) ?? undefined;
|
|
162
169
|
const candidates = [];
|
|
163
170
|
// (2) derive from `where git`. Git installs git.exe under either \cmd\ or
|
|
164
171
|
// \bin\; bash.exe lives under the sibling \bin\. Walk up to the Git root.
|
|
@@ -194,6 +201,9 @@ export function resolvePowerShell() {
|
|
|
194
201
|
const override = process.env.CODE_SHELL_POWERSHELL_PATH;
|
|
195
202
|
if (override && existsSync(override))
|
|
196
203
|
return (powerShellCache = override);
|
|
204
|
+
// Test-only hermetic switch (see resolveGitBash for why PATH-clearing fails).
|
|
205
|
+
if (process.env.CODE_SHELL_NO_SHELL_DISCOVERY)
|
|
206
|
+
return (powerShellCache = null) ?? undefined;
|
|
197
207
|
const candidates = [];
|
|
198
208
|
for (const exe of ["pwsh", "powershell"]) {
|
|
199
209
|
try {
|
|
@@ -27,10 +27,9 @@ export declare function recordDreamComplete(): void;
|
|
|
27
27
|
/**
|
|
28
28
|
* System prompt for the auto-dream tool-call loop.
|
|
29
29
|
*
|
|
30
|
-
* Drives the LLM as a "memory consolidation assistant" that operates
|
|
31
|
-
*
|
|
32
|
-
* read-only
|
|
33
|
-
* dream pass cannot obtain (no UI on the call path).
|
|
30
|
+
* Drives the LLM as a "memory consolidation assistant" that operates in dream
|
|
31
|
+
* scope and may maintain dream-owned user memories. Manual user memories are
|
|
32
|
+
* read-only and protected by the dispatcher.
|
|
34
33
|
*/
|
|
35
34
|
export declare function buildDreamSystemPrompt(): string;
|
|
36
35
|
/**
|
|
@@ -39,15 +38,27 @@ export declare function buildDreamSystemPrompt(): string;
|
|
|
39
38
|
* body. The LLM uses MemoryRead to fetch bodies for entries it wants to act on.
|
|
40
39
|
*/
|
|
41
40
|
export declare function buildDreamUserPrompt(userMemories: Array<{
|
|
41
|
+
id?: string;
|
|
42
42
|
name: string;
|
|
43
43
|
type: string;
|
|
44
44
|
description: string;
|
|
45
|
+
origin?: string;
|
|
46
|
+
useCount?: number;
|
|
47
|
+
updateCount?: number;
|
|
45
48
|
}>, dreamMemories: Array<{
|
|
49
|
+
id?: string;
|
|
46
50
|
name: string;
|
|
47
51
|
type: string;
|
|
48
52
|
description: string;
|
|
53
|
+
origin?: string;
|
|
54
|
+
useCount?: number;
|
|
55
|
+
updateCount?: number;
|
|
49
56
|
}>, globalMemories?: Array<{
|
|
57
|
+
id?: string;
|
|
50
58
|
name: string;
|
|
51
59
|
type: string;
|
|
52
60
|
description: string;
|
|
61
|
+
origin?: string;
|
|
62
|
+
useCount?: number;
|
|
63
|
+
updateCount?: number;
|
|
53
64
|
}>): string;
|
|
@@ -71,34 +71,34 @@ export function recordDreamComplete() {
|
|
|
71
71
|
/**
|
|
72
72
|
* System prompt for the auto-dream tool-call loop.
|
|
73
73
|
*
|
|
74
|
-
* Drives the LLM as a "memory consolidation assistant" that operates
|
|
75
|
-
*
|
|
76
|
-
* read-only
|
|
77
|
-
* dream pass cannot obtain (no UI on the call path).
|
|
74
|
+
* Drives the LLM as a "memory consolidation assistant" that operates in dream
|
|
75
|
+
* scope and may maintain dream-owned user memories. Manual user memories are
|
|
76
|
+
* read-only and protected by the dispatcher.
|
|
78
77
|
*/
|
|
79
78
|
export function buildDreamSystemPrompt() {
|
|
80
79
|
return [
|
|
81
80
|
"You are a memory consolidation assistant for the CodeShell memory system.",
|
|
82
81
|
"",
|
|
83
|
-
"Your job: clean up the `dream` scope by deduplicating, merging, removing stale, and improving descriptions.",
|
|
82
|
+
"Your job: clean up the `dream` scope by deduplicating, merging, removing stale, and improving descriptions, and promote only durable lessons into dream-owned `user` entries.",
|
|
84
83
|
"",
|
|
85
84
|
"Tools available (each takes `location`: 'global' = cross-project store, 'project' = this repo; default project):",
|
|
86
85
|
"- MemoryList({ scope, location }): list memories in a scope/location",
|
|
87
86
|
"- MemoryRead({ scope, location, name }): read full content of an entry",
|
|
88
|
-
"- MemorySave({ scope: 'dream', location, ... }): create or
|
|
89
|
-
"- MemoryDelete({ scope: 'dream', location, name }): soft-delete
|
|
87
|
+
"- MemorySave({ scope: 'dream'|'user', location, id?, ... }): create or update an entry by id",
|
|
88
|
+
"- MemoryDelete({ scope: 'dream'|'user', location, name }): soft-delete an owned entry (recoverable from trash)",
|
|
90
89
|
"",
|
|
91
|
-
"Consolidate BOTH the project dream
|
|
90
|
+
"Consolidate BOTH the project dream workspace AND the global dream workspace (pass location accordingly). Global dream is a cross-project workspace, not a dumping ground for single-project progress.",
|
|
92
91
|
"",
|
|
93
92
|
"Rules — read carefully:",
|
|
94
|
-
"1. You may
|
|
95
|
-
"2. You may
|
|
96
|
-
"3. If
|
|
97
|
-
"4.
|
|
98
|
-
"5.
|
|
99
|
-
"6.
|
|
100
|
-
|
|
101
|
-
"8.
|
|
93
|
+
"1. You may Save/Delete dream-scope entries only when they are origin:auto or origin:dream. Never modify origin:manual; missing origin means manual.",
|
|
94
|
+
"2. You may Save user-scope entries only for durable conclusions you own. The dispatcher will force origin:dream. You may update existing user entries only when they are origin:dream or origin:auto. Never modify/delete origin:manual user entries.",
|
|
95
|
+
"3. Use MemoryList first. If the same topic already has an id, update that id instead of creating a date, version, batch, or progress variant.",
|
|
96
|
+
"4. Cluster duplicates without vectors: compare stable topic words after removing dates, versions, batch numbers, and progress/completed wording.",
|
|
97
|
+
"5. Time-sensitivity rule: entries with dates, today/yesterday, progress snapshots, completed fixes, review batches, or one-off task state should stay in dream, be merged, or be deleted. Do not promote them to user.",
|
|
98
|
+
"6. Durable lessons may be promoted to user only when they are reusable: user preferences, long-term project constraints, architecture decisions, root-cause lessons, non-obvious test/build traps, or stable references.",
|
|
99
|
+
'7. Archive COMPLETED work: dream entries that only record a finished fix/task ("已修", "已完成", "done") and carry no reusable lesson should be deleted, or merged into one compact topical entry. Keep any durable lesson by folding it into a date-free topic entry first.',
|
|
100
|
+
"8. Prefer fewer, higher-quality merged entries over many similar fragments. Be conservative: if uncertain whether two entries are truly duplicates, leave them alone.",
|
|
101
|
+
"9. When you're done, stop calling tools and respond with a one-paragraph summary of what you changed.",
|
|
102
102
|
].join("\n");
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
@@ -107,18 +107,18 @@ export function buildDreamSystemPrompt() {
|
|
|
107
107
|
* body. The LLM uses MemoryRead to fetch bodies for entries it wants to act on.
|
|
108
108
|
*/
|
|
109
109
|
export function buildDreamUserPrompt(userMemories, dreamMemories, globalMemories = []) {
|
|
110
|
-
const fmt = (m) => ` - [${m.type}] ${m.name}: ${m.description}`;
|
|
110
|
+
const fmt = (m) => ` - [${m.type}] ${m.name} (id:${m.id ?? "(none)"}, origin:${m.origin ?? "manual"}, use:${m.useCount ?? 0}, updates:${m.updateCount ?? 0}): ${m.description}`;
|
|
111
111
|
const listOrNone = (arr, noneMsg) => arr.length === 0 ? noneMsg : arr.map(fmt).join("\n");
|
|
112
112
|
const sections = [];
|
|
113
|
-
sections.push(`Project user-scope memories (READ-ONLY
|
|
113
|
+
sections.push(`Project user-scope memories (manual is READ-ONLY; origin:dream/auto may be maintained by id, ${userMemories.length} entries):`);
|
|
114
114
|
sections.push(listOrNone(userMemories, " (none)"));
|
|
115
115
|
sections.push("");
|
|
116
116
|
sections.push(`Project dream-scope memories (YOUR WORKSPACE — location:'project', ${dreamMemories.length} entries):`);
|
|
117
117
|
sections.push(listOrNone(dreamMemories, " (none — you may consolidate from user-scope by re-saving curated entries into dream)"));
|
|
118
118
|
sections.push("");
|
|
119
|
-
sections.push(`Global memories (cross-project — clean these too via location:'global', ${globalMemories.length} entries):`);
|
|
119
|
+
sections.push(`Global dream workspace memories (cross-project dream — clean these too via location:'global', ${globalMemories.length} entries):`);
|
|
120
120
|
sections.push(listOrNone(globalMemories, " (none)"));
|
|
121
121
|
sections.push("");
|
|
122
|
-
sections.push("Begin consolidation. Use MemoryRead to inspect any entries whose names suggest duplication or staleness, then MemorySave/MemoryDelete (with the right location) to clean up.");
|
|
122
|
+
sections.push("Begin consolidation. Use MemoryRead to inspect any entries whose names suggest duplication or staleness, then MemorySave/MemoryDelete (with the right id/location/scope) to clean up.");
|
|
123
123
|
return sections.join("\n");
|
|
124
124
|
}
|
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
* The loop is intentionally small and offline:
|
|
14
14
|
* - No streaming, no UI events — it runs in the background.
|
|
15
15
|
* - No permission prompts — there is no interactive backend on this path, so
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* freely.
|
|
16
|
+
* Save/Delete goes through an origin guard before dispatching. Dream may
|
|
17
|
+
* maintain origin:dream/auto entries but never touches origin:manual.
|
|
19
18
|
* - Capped at MAX_TURNS LLM round-trips and MAX_WRITES total mutations to
|
|
20
19
|
* bound damage on misbehavior.
|
|
21
20
|
*/
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
* The loop is intentionally small and offline:
|
|
14
14
|
* - No streaming, no UI events — it runs in the background.
|
|
15
15
|
* - No permission prompts — there is no interactive backend on this path, so
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* freely.
|
|
16
|
+
* Save/Delete goes through an origin guard before dispatching. Dream may
|
|
17
|
+
* maintain origin:dream/auto entries but never touches origin:manual.
|
|
19
18
|
* - Capped at MAX_TURNS LLM round-trips and MAX_WRITES total mutations to
|
|
20
19
|
* bound damage on misbehavior.
|
|
21
20
|
*/
|
|
22
21
|
import { MemoryManager } from "../session/memory.js";
|
|
23
|
-
import { buildDreamSystemPrompt, buildDreamUserPrompt
|
|
22
|
+
import { buildDreamSystemPrompt, buildDreamUserPrompt } from "./auto-dream.js";
|
|
24
23
|
import { logger } from "../logging/logger.js";
|
|
25
24
|
const MAX_TURNS = 8;
|
|
26
25
|
const MAX_WRITES = 10;
|
|
26
|
+
const FRESH_ENTRY_GRACE_MS = 10 * 60 * 1000;
|
|
27
27
|
const MEMORY_TOOL_NAMES = ["MemoryList", "MemoryRead", "MemorySave", "MemoryDelete"];
|
|
28
28
|
/**
|
|
29
29
|
* Drive the dream-scope consolidation tool-call loop.
|
|
@@ -37,9 +37,7 @@ const MEMORY_TOOL_NAMES = ["MemoryList", "MemoryRead", "MemorySave", "MemoryDele
|
|
|
37
37
|
*/
|
|
38
38
|
export async function runDreamConsolidation(input) {
|
|
39
39
|
const { llmClient, toolRegistry, projectDir, sessionId } = input;
|
|
40
|
-
const memoryTools = MEMORY_TOOL_NAMES
|
|
41
|
-
.map((n) => toolRegistry.getTool(n))
|
|
42
|
-
.filter((t) => t != null);
|
|
40
|
+
const memoryTools = MEMORY_TOOL_NAMES.map((n) => toolRegistry.getTool(n)).filter((t) => t != null);
|
|
43
41
|
if (memoryTools.length < MEMORY_TOOL_NAMES.length) {
|
|
44
42
|
logger.warn("memory.dream_missing_tools", {
|
|
45
43
|
sessionId,
|
|
@@ -52,8 +50,9 @@ export async function runDreamConsolidation(input) {
|
|
|
52
50
|
const mm = new MemoryManager({ projectDir });
|
|
53
51
|
const userMems = mm.loadScope("user");
|
|
54
52
|
const dreamMems = mm.loadScope("dream");
|
|
53
|
+
const globalDreamMems = projectDir ? new MemoryManager({ scope: "dream" }).loadAll() : [];
|
|
55
54
|
const systemPrompt = buildDreamSystemPrompt();
|
|
56
|
-
const userPrompt = buildDreamUserPrompt(userMems, dreamMems);
|
|
55
|
+
const userPrompt = buildDreamUserPrompt(userMems, dreamMems, globalDreamMems);
|
|
57
56
|
// Strip RegisteredTool down to the shape createMessage expects.
|
|
58
57
|
const toolDefs = memoryTools.map((t) => ({
|
|
59
58
|
name: t.name,
|
|
@@ -120,8 +119,8 @@ export async function runDreamConsolidation(input) {
|
|
|
120
119
|
* Execute one memory tool call inside the dream loop. Enforces the two
|
|
121
120
|
* dream-loop invariants the prompt also states:
|
|
122
121
|
* - Only the 4 memory tools are dispatchable.
|
|
123
|
-
* - Save/Delete
|
|
124
|
-
*
|
|
122
|
+
* - Save/Delete is allowed only for origin:auto/origin:dream owned entries.
|
|
123
|
+
* Missing origin is manual, and manual is always protected.
|
|
125
124
|
*/
|
|
126
125
|
async function dispatchDreamTool(tc, toolRegistry, ctx, consumeWriteBudget) {
|
|
127
126
|
const allowed = new Set(MEMORY_TOOL_NAMES);
|
|
@@ -130,17 +129,17 @@ async function dispatchDreamTool(tc, toolRegistry, ctx, consumeWriteBudget) {
|
|
|
130
129
|
}
|
|
131
130
|
const isWrite = tc.toolName === "MemorySave" || tc.toolName === "MemoryDelete";
|
|
132
131
|
if (isWrite) {
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
135
|
-
return
|
|
136
|
-
`User-scope changes require interactive permission, which is not available here.`);
|
|
132
|
+
const guard = checkDreamWriteGuard(tc, ctx);
|
|
133
|
+
if (!guard.ok) {
|
|
134
|
+
return guard.error;
|
|
137
135
|
}
|
|
138
136
|
if (!consumeWriteBudget()) {
|
|
139
137
|
return "Error: dream write budget exhausted — stop calling write tools and summarize instead.";
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
try {
|
|
143
|
-
const
|
|
141
|
+
const dreamCtx = { ...ctx, __dreamLoop: true };
|
|
142
|
+
const result = await toolRegistry.executeTool(tc.toolName, tc.args, { ctx: dreamCtx });
|
|
144
143
|
if (result.isError)
|
|
145
144
|
return result.error ?? `Error executing ${tc.toolName}`;
|
|
146
145
|
return result.result ?? "";
|
|
@@ -149,3 +148,54 @@ async function dispatchDreamTool(tc, toolRegistry, ctx, consumeWriteBudget) {
|
|
|
149
148
|
return `Error executing ${tc.toolName}: ${err.message}`;
|
|
150
149
|
}
|
|
151
150
|
}
|
|
151
|
+
function checkDreamWriteGuard(tc, ctx) {
|
|
152
|
+
const scope = tc.args?.scope;
|
|
153
|
+
if (scope !== "dream" && scope !== "user") {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error: `Error: dream loop may only write to scope "dream" or "user", got "${scope}".`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const location = tc.args?.location === "global" ? "global" : "project";
|
|
160
|
+
const mm = new MemoryManager({
|
|
161
|
+
projectDir: location === "project" ? ctx.cwd : undefined,
|
|
162
|
+
scope,
|
|
163
|
+
});
|
|
164
|
+
const id = typeof tc.args?.id === "string" ? tc.args.id : undefined;
|
|
165
|
+
const name = typeof tc.args?.name === "string" ? tc.args.name : undefined;
|
|
166
|
+
const targetById = id ? mm.findById(id) : undefined;
|
|
167
|
+
const targetByName = name ? mm.find(name) : undefined;
|
|
168
|
+
const targets = [targetById, targetByName].filter((target, index, all) => Boolean(target) && all.findIndex((item) => item?.id === target?.id) === index);
|
|
169
|
+
const protectedTarget = targets.find((target) => target.origin === "manual" || !target.origin);
|
|
170
|
+
if (protectedTarget) {
|
|
171
|
+
return {
|
|
172
|
+
ok: false,
|
|
173
|
+
error: `Error: dream loop cannot modify origin:manual memory "${protectedTarget.name}" ` +
|
|
174
|
+
`(${location}/${scope}/${protectedTarget.id ?? protectedTarget.fileName}).`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const target = targetById ?? targetByName;
|
|
178
|
+
if (!target)
|
|
179
|
+
return { ok: true };
|
|
180
|
+
if (target.origin !== "auto" && target.origin !== "dream") {
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
error: `Error: dream loop can only modify origin:auto or origin:dream memories; ` +
|
|
184
|
+
`"${target.name}" has origin:${target.origin}.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (tc.toolName === "MemoryDelete" && isFreshEntry(target.createdAt)) {
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
error: `Error: dream loop cannot delete freshly-created memory "${target.name}" yet; ` +
|
|
191
|
+
"leave it for the next consolidation pass.",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { ok: true };
|
|
195
|
+
}
|
|
196
|
+
function isFreshEntry(createdAt) {
|
|
197
|
+
if (!createdAt)
|
|
198
|
+
return false;
|
|
199
|
+
const createdMs = new Date(createdAt).getTime();
|
|
200
|
+
return Number.isFinite(createdMs) && Date.now() - createdMs < FRESH_ENTRY_GRACE_MS;
|
|
201
|
+
}
|
|
@@ -18,17 +18,26 @@ export interface ExtractedMemory {
|
|
|
18
18
|
description: string;
|
|
19
19
|
content: string;
|
|
20
20
|
}
|
|
21
|
+
export interface ExistingMemorySummary {
|
|
22
|
+
id?: string;
|
|
23
|
+
name: string;
|
|
24
|
+
type: string;
|
|
25
|
+
description: string;
|
|
26
|
+
location?: "project" | "global";
|
|
27
|
+
memoryScope?: "user" | "dream";
|
|
28
|
+
origin?: "manual" | "auto" | "dream";
|
|
29
|
+
pinned?: boolean;
|
|
30
|
+
useCount?: number;
|
|
31
|
+
updateCount?: number;
|
|
32
|
+
updatedAt?: string;
|
|
33
|
+
}
|
|
21
34
|
/**
|
|
22
35
|
* Build the prompt for extracting memories from a conversation.
|
|
23
36
|
*/
|
|
24
37
|
export declare function buildExtractionPrompt(transcript: Array<{
|
|
25
38
|
role: string;
|
|
26
39
|
content: string;
|
|
27
|
-
}>, existingMemories:
|
|
28
|
-
name: string;
|
|
29
|
-
type: string;
|
|
30
|
-
description: string;
|
|
31
|
-
}>): string;
|
|
40
|
+
}>, existingMemories: ExistingMemorySummary[]): string;
|
|
32
41
|
/** Max memories to accept from a single extraction pass. Code-side cap that
|
|
33
42
|
* enforces the same limit the prompt asks for — the model occasionally
|
|
34
43
|
* ignores prompt rules, but this guarantees the cap. */
|
|
@@ -12,7 +12,21 @@ export function buildExtractionPrompt(transcript, existingMemories) {
|
|
|
12
12
|
.map((m) => `[${m.role}]: ${m.content.slice(0, 3000)}`)
|
|
13
13
|
.join("\n\n");
|
|
14
14
|
const existingList = existingMemories.length > 0
|
|
15
|
-
? existingMemories
|
|
15
|
+
? existingMemories
|
|
16
|
+
.map((m) => {
|
|
17
|
+
const meta = [
|
|
18
|
+
m.id ? `id:${m.id}` : null,
|
|
19
|
+
m.location ? `location:${m.location}` : null,
|
|
20
|
+
m.memoryScope ? `scope:${m.memoryScope}` : null,
|
|
21
|
+
m.origin ? `origin:${m.origin}` : null,
|
|
22
|
+
typeof m.useCount === "number" ? `use:${m.useCount}` : null,
|
|
23
|
+
typeof m.updateCount === "number" ? `updates:${m.updateCount}` : null,
|
|
24
|
+
]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(", ");
|
|
27
|
+
return ` - [${m.type}] ${m.name}${meta ? ` (${meta})` : ""}: ${m.description}`;
|
|
28
|
+
})
|
|
29
|
+
.join("\n")
|
|
16
30
|
: " (none)";
|
|
17
31
|
return `Analyze the following conversation and extract any information worth remembering for future sessions.
|
|
18
32
|
|
|
@@ -23,7 +37,7 @@ ${existingList}
|
|
|
23
37
|
${conversationText.slice(0, 30000)}
|
|
24
38
|
|
|
25
39
|
## Instructions
|
|
26
|
-
Identify
|
|
40
|
+
Identify candidate information that may be saved as persistent memories. The extractor only proposes candidates; code will route automatic candidates into dream memory first, never directly into user memory. Categories (type):
|
|
27
41
|
- **user**: Information about the user's role, preferences, or expertise
|
|
28
42
|
- **feedback**: Guidance about how to approach work (corrections or confirmations)
|
|
29
43
|
- **project**: Non-obvious facts about ongoing work, goals, or decisions
|
|
@@ -39,7 +53,10 @@ Rules:
|
|
|
39
53
|
- Extract AT MOST 2 memories per session. Prefer 0 to a marginal one — most sessions are noise.
|
|
40
54
|
- AT MOST 1 of the extracted memories may be "global". The global layer is injected every session, so keep it tiny and high-value. If two candidates both seem global, keep the single most universal one global and make the other "project".
|
|
41
55
|
- Only extract information that would be useful in FUTURE conversations
|
|
42
|
-
- Do not duplicate existing memories (re-read the "Existing Memories" list above carefully)
|
|
56
|
+
- Do not duplicate existing memories (re-read the "Existing Memories" list above carefully).
|
|
57
|
+
- If an existing auto/dream memory has the same durable topic, reuse that topic in the candidate instead of creating a date-stamped variant; the write decision layer will update the existing id.
|
|
58
|
+
- Manual user memories are read-only curated memories. Do not re-extract the same topic just because the wording or date differs.
|
|
59
|
+
- Avoid dates, version stamps, batch names, and progress-snapshot suffixes in names. Names should be stable topic identifiers, not "today" variants.
|
|
43
60
|
- Do not extract code patterns, file structures, or git history (derivable from code)
|
|
44
61
|
- Do not extract ephemeral task details, progress snapshots, or in-flight work state — those belong in the conversation, not memory
|
|
45
62
|
- Do not extract one-off research products (news reports, AI industry summaries, slide deck content, daily progress dumps) — they're done and not "durable, reusable information"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ExtractedMemory } from "./extract-memories.js";
|
|
2
|
+
export interface GlobalDreamPromotionInput {
|
|
3
|
+
candidate: ExtractedMemory;
|
|
4
|
+
projectDir?: string;
|
|
5
|
+
baseDir?: string;
|
|
6
|
+
userDirectGlobal?: boolean;
|
|
7
|
+
now?: Date;
|
|
8
|
+
}
|
|
9
|
+
export interface GlobalDreamPromotionResult {
|
|
10
|
+
/** No automatic write to global dream is performed; approval is required. */
|
|
11
|
+
promoted: false;
|
|
12
|
+
/** True when this call created a new pending approval item. */
|
|
13
|
+
pendingSuggested: boolean;
|
|
14
|
+
originProjects: string[];
|
|
15
|
+
evidenceCount: number;
|
|
16
|
+
projectEvidenceSaved: boolean;
|
|
17
|
+
promotionReason: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function detectUserDirectGlobalPreference(transcript: Array<{
|
|
20
|
+
role: string;
|
|
21
|
+
content: string;
|
|
22
|
+
}>): boolean;
|
|
23
|
+
export declare function applyGlobalDreamPromotionGate(input: GlobalDreamPromotionInput): GlobalDreamPromotionResult;
|