@wrongstack/core 0.286.0 → 0.287.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/dist/boot.d.ts.map +1 -1
- package/dist/coordination/brain-monitor.d.ts +15 -0
- package/dist/coordination/brain-monitor.d.ts.map +1 -1
- package/dist/coordination/delegate-tool.d.ts +8 -14
- package/dist/coordination/delegate-tool.d.ts.map +1 -1
- package/dist/coordination/director/director-btw-notes.d.ts +20 -0
- package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
- package/dist/coordination/director/director-collab.d.ts +40 -0
- package/dist/coordination/director/director-collab.d.ts.map +1 -0
- package/dist/coordination/director.d.ts +27 -8
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-manager.d.ts +21 -0
- package/dist/coordination/fleet-manager.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +2 -3
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +36 -22
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/icoordinator.d.ts +1 -3
- package/dist/coordination/icoordinator.d.ts.map +1 -1
- package/dist/coordination/index.js +708 -442
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-message-codec.d.ts +21 -1
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +42 -1
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +10 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent.d.ts +8 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/context.d.ts +25 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/conversation-state.d.ts +6 -0
- package/dist/core/conversation-state.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +12 -5
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +14 -6
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.d.ts +0 -1
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +834 -1465
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +8 -1
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/council-brain.d.ts.map +1 -1
- package/dist/execution/council-orchestrator.d.ts +32 -0
- package/dist/execution/council-orchestrator.d.ts.map +1 -0
- package/dist/execution/council-personas.d.ts +19 -0
- package/dist/execution/council-personas.d.ts.map +1 -0
- package/dist/execution/council-profiles.d.ts +34 -0
- package/dist/execution/council-profiles.d.ts.map +1 -0
- package/dist/execution/council-prompts.d.ts +21 -0
- package/dist/execution/council-prompts.d.ts.map +1 -0
- package/dist/execution/council-resolution.d.ts +57 -0
- package/dist/execution/council-resolution.d.ts.map +1 -0
- package/dist/execution/design-kit-loader.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +35 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -0
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.d.ts +6 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1738 -161
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +18 -7
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts +52 -0
- package/dist/execution/one-shot-llm.d.ts.map +1 -0
- package/dist/execution/prompt-enhancer.d.ts +23 -4
- package/dist/execution/prompt-enhancer.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.js +24 -6
- package/dist/execution/prompt-enhancer.js.map +2 -2
- package/dist/execution/selective-compactor.d.ts +4 -4
- package/dist/execution/strategy-compactor.d.ts +7 -0
- package/dist/execution/strategy-compactor.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts +19 -5
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hooks/registry.d.ts.map +1 -1
- package/dist/hooks/runner.d.ts.map +1 -1
- package/dist/hooks/shell-executor.d.ts.map +1 -1
- package/dist/hq/alerts.d.ts +5 -0
- package/dist/hq/alerts.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts +17 -7
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/commands.d.ts +8 -4
- package/dist/hq/commands.d.ts.map +1 -1
- package/dist/hq/cost-bridge.d.ts +5 -3
- package/dist/hq/cost-bridge.d.ts.map +1 -1
- package/dist/hq/fleet-bridge.d.ts.map +1 -1
- package/dist/hq/index.d.ts +0 -1
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +471 -294
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/persistence.d.ts +42 -0
- package/dist/hq/persistence.d.ts.map +1 -1
- package/dist/hq/protocol/brain.d.ts +24 -0
- package/dist/hq/protocol/brain.d.ts.map +1 -0
- package/dist/hq/protocol/browser.d.ts +12 -0
- package/dist/hq/protocol/browser.d.ts.map +1 -0
- package/dist/hq/protocol/client.d.ts +69 -0
- package/dist/hq/protocol/client.d.ts.map +1 -0
- package/dist/hq/protocol/core.d.ts +130 -0
- package/dist/hq/protocol/core.d.ts.map +1 -0
- package/dist/hq/protocol/fleet.d.ts +73 -0
- package/dist/hq/protocol/fleet.d.ts.map +1 -0
- package/dist/hq/protocol/mailbox.d.ts +76 -0
- package/dist/hq/protocol/mailbox.d.ts.map +1 -0
- package/dist/hq/protocol/mcp.d.ts +51 -0
- package/dist/hq/protocol/mcp.d.ts.map +1 -0
- package/dist/hq/protocol/project.d.ts +26 -0
- package/dist/hq/protocol/project.d.ts.map +1 -0
- package/dist/hq/protocol/session.d.ts +153 -0
- package/dist/hq/protocol/session.d.ts.map +1 -0
- package/dist/hq/protocol/tool.d.ts +31 -0
- package/dist/hq/protocol/tool.d.ts.map +1 -0
- package/dist/hq/protocol.d.ts +10 -611
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4540 -2283
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +13 -11
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/infrastructure/token-counter.d.ts.map +1 -1
- package/dist/kernel/events/agent-events.d.ts +283 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -0
- package/dist/kernel/events/brain-events.d.ts +176 -0
- package/dist/kernel/events/brain-events.d.ts.map +1 -0
- package/dist/kernel/events/fleet-events.d.ts +39 -0
- package/dist/kernel/events/fleet-events.d.ts.map +1 -0
- package/dist/kernel/events/memory-events.d.ts +165 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -0
- package/dist/kernel/events/provider-events.d.ts +98 -0
- package/dist/kernel/events/provider-events.d.ts.map +1 -0
- package/dist/kernel/events/sdd-events.d.ts +118 -0
- package/dist/kernel/events/sdd-events.d.ts.map +1 -0
- package/dist/kernel/events/session-events.d.ts +227 -0
- package/dist/kernel/events/session-events.d.ts.map +1 -0
- package/dist/kernel/events/tool-events.d.ts +185 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -0
- package/dist/kernel/events/worktree-events.d.ts +58 -0
- package/dist/kernel/events/worktree-events.d.ts.map +1 -0
- package/dist/kernel/events.d.ts +10 -1298
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js +22 -2
- package/dist/kernel/index.js.map +2 -2
- package/dist/kernel/run-controller.d.ts +15 -0
- package/dist/kernel/run-controller.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/models/index.js +19 -6
- package/dist/models/index.js.map +2 -2
- package/dist/models/llm-selector.d.ts +7 -0
- package/dist/models/llm-selector.d.ts.map +1 -1
- package/dist/plugins/auto-review-plugin.d.ts +36 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
- package/dist/plugins/chimera-plugin.d.ts +8 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.js +7 -2
- package/dist/security/index.js.map +2 -2
- package/dist/security/secret-scrubber.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/goal-kanban.d.ts +69 -0
- package/dist/storage/goal-kanban.d.ts.map +1 -0
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +425 -717
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +9 -1
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/tools/council-tool.d.ts +25 -0
- package/dist/tools/council-tool.d.ts.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1884 -12
- package/dist/tools/index.js.map +4 -4
- package/dist/tools/one-shot-llm-tool.d.ts +51 -0
- package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
- package/dist/types/config.d.ts +27 -3
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/context-window.d.ts.map +1 -1
- package/dist/types/council.d.ts +134 -0
- package/dist/types/council.d.ts.map +1 -0
- package/dist/types/default-config.d.ts +3 -3
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +29 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/one-shot-llm.d.ts +128 -0
- package/dist/types/one-shot-llm.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +6 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/provider.d.ts +17 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/system-prompt.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +10 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/index.js +75 -8
- package/dist/utils/index.js.map +3 -3
- package/dist/utils/tool-wire-compact.d.ts +1 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +20 -4
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/instructions/cli/next-task-predictor.md +5 -3
- package/instructions/coordination/subagent-baseline.md +8 -7
- package/instructions/leader-after-task.md +18 -9
- package/instructions/llm/council-judge.md +19 -0
- package/instructions/llm/council-voter.md +22 -0
- package/instructions/modes/architect.md +14 -9
- package/instructions/modes/audit-lite.md +9 -7
- package/instructions/modes/brief-system.md +12 -12
- package/instructions/modes/brief.md +7 -5
- package/instructions/modes/code-auditor.md +13 -8
- package/instructions/modes/code-reviewer.md +13 -8
- package/instructions/modes/debug-lite.md +7 -6
- package/instructions/modes/debugger.md +13 -9
- package/instructions/modes/devops.md +13 -9
- package/instructions/modes/plan-lite.md +7 -6
- package/instructions/modes/refactor-lite.md +7 -6
- package/instructions/modes/refactorer.md +14 -9
- package/instructions/modes/research-lite.md +8 -6
- package/instructions/modes/research-web.md +18 -55
- package/instructions/modes/review-lite.md +7 -6
- package/instructions/modes/teach-system.md +15 -15
- package/instructions/modes/teach.md +13 -12
- package/instructions/modes/test-lite.md +6 -5
- package/instructions/modes/tester.md +13 -9
- package/instructions/modes/ui-design.md +14 -8
- package/instructions/sections/tool/commit-hygiene.md +1 -7
- package/instructions/sections/tool/common-patterns.md +5 -5
- package/instructions/sections/tool/delegation-full.md +3 -31
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +6 -22
- package/instructions/system.md +278 -39
- package/package.json +2 -2
- package/skills/mailbox-bridge/SKILL.md +16 -7
- package/skills/output-standards/SKILL.md +16 -16
- package/skills/wrongstack-mailbox/SKILL.md +6 -0
- package/dist/coordination/director-construction.d.ts +0 -90
- package/dist/coordination/director-construction.d.ts.map +0 -1
- package/dist/hq/agent-bridge.d.ts +0 -27
- package/dist/hq/agent-bridge.d.ts.map +0 -1
- package/dist/storage/memory-store.d.ts +0 -119
- package/dist/storage/memory-store.d.ts.map +0 -1
package/dist/defaults/index.js
CHANGED
|
@@ -13,6 +13,7 @@ function assertNever(x, message) {
|
|
|
13
13
|
// src/utils/atomic-write.ts
|
|
14
14
|
import { randomBytes } from "node:crypto";
|
|
15
15
|
import * as fs from "node:fs/promises";
|
|
16
|
+
import { watch as watchDir } from "node:fs";
|
|
16
17
|
import * as path from "node:path";
|
|
17
18
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
18
19
|
const dir = path.dirname(targetPath);
|
|
@@ -35,8 +36,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
35
36
|
}
|
|
36
37
|
let mode;
|
|
37
38
|
try {
|
|
38
|
-
const
|
|
39
|
-
mode =
|
|
39
|
+
const stat12 = await fs.stat(targetPath);
|
|
40
|
+
mode = stat12.mode & 511;
|
|
40
41
|
} catch {
|
|
41
42
|
mode = opts.mode;
|
|
42
43
|
}
|
|
@@ -80,17 +81,18 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
80
81
|
await fs.mkdir(dir, { recursive: true });
|
|
81
82
|
continue;
|
|
82
83
|
}
|
|
83
|
-
if (code !== "EEXIST") throw err;
|
|
84
|
+
if (code !== "EEXIST" && code !== "EPERM") throw err;
|
|
84
85
|
try {
|
|
85
|
-
const
|
|
86
|
-
if (Date.now() -
|
|
86
|
+
const stat12 = await fs.stat(lockPath);
|
|
87
|
+
if (Date.now() - stat12.mtimeMs > staleMs) {
|
|
87
88
|
await fs.unlink(lockPath);
|
|
88
89
|
continue;
|
|
89
90
|
}
|
|
90
91
|
} catch {
|
|
91
92
|
continue;
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
+
const elapsed = Date.now() - started;
|
|
95
|
+
if (elapsed >= timeoutMs) {
|
|
94
96
|
throw new FsError({
|
|
95
97
|
message: `Timed out waiting for file lock: ${targetPath}`,
|
|
96
98
|
code: "FS_ATOMIC_WRITE_FAILED",
|
|
@@ -98,7 +100,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
98
100
|
context: { timeoutMs }
|
|
99
101
|
});
|
|
100
102
|
}
|
|
101
|
-
await
|
|
103
|
+
await waitForLockRelease(lockPath, timeoutMs - elapsed);
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
try {
|
|
@@ -114,6 +116,50 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
}
|
|
119
|
+
async function waitForLockRelease(lockPath, remainingMs) {
|
|
120
|
+
const parentDir = path.dirname(lockPath);
|
|
121
|
+
const lockName = path.basename(lockPath);
|
|
122
|
+
const intervalMs = Math.min(remainingMs, 100);
|
|
123
|
+
return new Promise((resolve12) => {
|
|
124
|
+
let settled = false;
|
|
125
|
+
let watcher = null;
|
|
126
|
+
const timer = setTimeout(() => {
|
|
127
|
+
settled = true;
|
|
128
|
+
watcher?.close();
|
|
129
|
+
resolve12();
|
|
130
|
+
}, intervalMs);
|
|
131
|
+
try {
|
|
132
|
+
watcher = watchDir(parentDir, (eventType, filename) => {
|
|
133
|
+
if (settled) return;
|
|
134
|
+
if (filename === lockName && (eventType === "rename" || eventType === "change")) {
|
|
135
|
+
settled = true;
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
watcher?.close();
|
|
138
|
+
resolve12();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
if (!settled) {
|
|
144
|
+
settled = true;
|
|
145
|
+
setTimeout(resolve12, Math.min(remainingMs, 25));
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
fs.access(lockPath).then(
|
|
150
|
+
() => {
|
|
151
|
+
},
|
|
152
|
+
() => {
|
|
153
|
+
if (!settled) {
|
|
154
|
+
settled = true;
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
watcher?.close();
|
|
157
|
+
resolve12();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
117
163
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
118
164
|
async function renameWithRetry(from, to) {
|
|
119
165
|
if (process.platform !== "win32") {
|
|
@@ -572,8 +618,8 @@ async function expandGlob(pattern) {
|
|
|
572
618
|
for (const e of entries) {
|
|
573
619
|
const full = `${dir}${SEP}${e}`;
|
|
574
620
|
try {
|
|
575
|
-
const
|
|
576
|
-
if (
|
|
621
|
+
const stat12 = await fsp.stat(full);
|
|
622
|
+
if (stat12.isDirectory()) await walk3(full, rest);
|
|
577
623
|
} catch {
|
|
578
624
|
}
|
|
579
625
|
}
|
|
@@ -590,8 +636,8 @@ async function expandGlob(pattern) {
|
|
|
590
636
|
if (entries.includes(seg)) {
|
|
591
637
|
const full = `${dir}${SEP}${seg}`;
|
|
592
638
|
try {
|
|
593
|
-
const
|
|
594
|
-
if (
|
|
639
|
+
const stat12 = await fsp.stat(full);
|
|
640
|
+
if (stat12.isDirectory()) await walk3(full, rest);
|
|
595
641
|
} catch {
|
|
596
642
|
}
|
|
597
643
|
}
|
|
@@ -782,10 +828,10 @@ function validateAgainstSchema(value, schema) {
|
|
|
782
828
|
return { ok: errors.length === 0, errors };
|
|
783
829
|
}
|
|
784
830
|
var MAX_SCHEMA_DEPTH = 64;
|
|
785
|
-
function walk(value, schema,
|
|
831
|
+
function walk(value, schema, path28, errors, depth) {
|
|
786
832
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
787
833
|
errors.push({
|
|
788
|
-
path:
|
|
834
|
+
path: path28 || "<root>",
|
|
789
835
|
message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
|
|
790
836
|
});
|
|
791
837
|
return;
|
|
@@ -793,7 +839,7 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
793
839
|
if (schema.enum !== void 0) {
|
|
794
840
|
if (!enumIncludes(schema.enum, value)) {
|
|
795
841
|
errors.push({
|
|
796
|
-
path:
|
|
842
|
+
path: path28 || "<root>",
|
|
797
843
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
798
844
|
});
|
|
799
845
|
return;
|
|
@@ -802,7 +848,7 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
802
848
|
if (typeof schema.type === "string") {
|
|
803
849
|
if (!checkType(value, schema.type)) {
|
|
804
850
|
errors.push({
|
|
805
|
-
path:
|
|
851
|
+
path: path28 || "<root>",
|
|
806
852
|
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
807
853
|
});
|
|
808
854
|
return;
|
|
@@ -814,7 +860,7 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
814
860
|
if (!(req in obj)) {
|
|
815
861
|
const expected = schema.properties?.[req]?.type;
|
|
816
862
|
errors.push({
|
|
817
|
-
path: joinPath(
|
|
863
|
+
path: joinPath(path28, req),
|
|
818
864
|
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
819
865
|
});
|
|
820
866
|
}
|
|
@@ -822,14 +868,14 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
822
868
|
if (schema.properties) {
|
|
823
869
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
824
870
|
if (key in obj) {
|
|
825
|
-
walk(obj[key], subSchema, joinPath(
|
|
871
|
+
walk(obj[key], subSchema, joinPath(path28, key), errors, depth + 1);
|
|
826
872
|
}
|
|
827
873
|
}
|
|
828
874
|
}
|
|
829
875
|
}
|
|
830
876
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
831
877
|
for (let i = 0; i < value.length; i++) {
|
|
832
|
-
walk(value[i], schema.items, `${
|
|
878
|
+
walk(value[i], schema.items, `${path28}[${i}]`, errors, depth + 1);
|
|
833
879
|
}
|
|
834
880
|
}
|
|
835
881
|
}
|
|
@@ -1287,8 +1333,8 @@ function subjectForToolInput(toolName, input, subjectKey) {
|
|
|
1287
1333
|
}
|
|
1288
1334
|
|
|
1289
1335
|
// src/utils/tool-wire-compact.ts
|
|
1290
|
-
var TOOL_DESCRIPTION_MAX_CHARS =
|
|
1291
|
-
var SCHEMA_DESCRIPTION_MAX_CHARS =
|
|
1336
|
+
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
1337
|
+
var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
|
|
1292
1338
|
var compactCache = /* @__PURE__ */ new WeakMap();
|
|
1293
1339
|
function compactToolDefinitionForWire(tool, opts = {}) {
|
|
1294
1340
|
const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
|
|
@@ -2208,14 +2254,34 @@ function resolveToolResultRenderMode(modes, toolName) {
|
|
|
2208
2254
|
|
|
2209
2255
|
// src/utils/wstack-paths.ts
|
|
2210
2256
|
import { createHash } from "node:crypto";
|
|
2257
|
+
import * as fs2 from "node:fs";
|
|
2211
2258
|
import * as os from "node:os";
|
|
2212
2259
|
import * as path3 from "node:path";
|
|
2260
|
+
function canonicalProjectRoot(absRoot) {
|
|
2261
|
+
const checkoutRoot = path3.resolve(absRoot);
|
|
2262
|
+
const dotGit = path3.join(checkoutRoot, ".git");
|
|
2263
|
+
try {
|
|
2264
|
+
if (!fs2.statSync(dotGit).isFile()) return checkoutRoot;
|
|
2265
|
+
const gitDirLine = fs2.readFileSync(dotGit, "utf8").trim();
|
|
2266
|
+
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
2267
|
+
if (!match?.[1]) return checkoutRoot;
|
|
2268
|
+
const gitDir = path3.resolve(checkoutRoot, match[1].trim());
|
|
2269
|
+
const commonDirFile = path3.join(gitDir, "commondir");
|
|
2270
|
+
if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
2271
|
+
const commonDir = path3.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
|
|
2272
|
+
if (path3.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
2273
|
+
return path3.dirname(commonDir);
|
|
2274
|
+
} catch {
|
|
2275
|
+
return checkoutRoot;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2213
2278
|
function projectHash(absRoot) {
|
|
2214
|
-
return createHash("sha256").update(
|
|
2279
|
+
return createHash("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
|
|
2215
2280
|
}
|
|
2216
2281
|
function projectSlug(absRoot) {
|
|
2217
|
-
const
|
|
2218
|
-
const
|
|
2282
|
+
const identityRoot = canonicalProjectRoot(absRoot);
|
|
2283
|
+
const base = slugify2(path3.basename(identityRoot));
|
|
2284
|
+
const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
2219
2285
|
return `${base}-${hash}`;
|
|
2220
2286
|
}
|
|
2221
2287
|
function slugify2(name) {
|
|
@@ -3526,7 +3592,7 @@ var TOOLS = {
|
|
|
3526
3592
|
};
|
|
3527
3593
|
|
|
3528
3594
|
// src/coordination/agents/agent-prompts.ts
|
|
3529
|
-
import { readFileSync, statSync } from "node:fs";
|
|
3595
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
3530
3596
|
import * as os2 from "node:os";
|
|
3531
3597
|
import * as path4 from "node:path";
|
|
3532
3598
|
import { fileURLToPath } from "node:url";
|
|
@@ -3541,7 +3607,7 @@ function agentPrompt(id) {
|
|
|
3541
3607
|
let resolved = "";
|
|
3542
3608
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
3543
3609
|
try {
|
|
3544
|
-
resolved =
|
|
3610
|
+
resolved = readFileSync2(path4.join(dir, fileName), "utf8").trimEnd();
|
|
3545
3611
|
break;
|
|
3546
3612
|
} catch {
|
|
3547
3613
|
}
|
|
@@ -3571,7 +3637,7 @@ function agentPromptDirCandidates(envDir) {
|
|
|
3571
3637
|
}
|
|
3572
3638
|
function isDirectory(candidate) {
|
|
3573
3639
|
try {
|
|
3574
|
-
return
|
|
3640
|
+
return statSync2(candidate).isDirectory();
|
|
3575
3641
|
} catch {
|
|
3576
3642
|
return false;
|
|
3577
3643
|
}
|
|
@@ -5162,19 +5228,19 @@ var FLEET_ROSTER = {
|
|
|
5162
5228
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
5163
5229
|
)
|
|
5164
5230
|
};
|
|
5165
|
-
var DEFAULT_IDLE_TIMEOUT_MS =
|
|
5231
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
5166
5232
|
var FLEET_ROSTER_BUDGETS = {
|
|
5167
|
-
"audit-log": { timeoutMs:
|
|
5168
|
-
"bug-hunter": { timeoutMs:
|
|
5169
|
-
"refactor-planner": { timeoutMs:
|
|
5170
|
-
"security-scanner": { timeoutMs:
|
|
5171
|
-
critic: { timeoutMs:
|
|
5233
|
+
"audit-log": { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2500, maxToolCalls: 7500 },
|
|
5234
|
+
"bug-hunter": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
|
|
5235
|
+
"refactor-planner": { timeoutMs: 4 * 60 * 60 * 1e3, maxIterations: 3e3, maxToolCalls: 9e3 },
|
|
5236
|
+
"security-scanner": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
|
|
5237
|
+
critic: { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2e3, maxToolCalls: 6e3 },
|
|
5172
5238
|
"shadow-agent": {
|
|
5173
5239
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
5174
|
-
maxIterations:
|
|
5175
|
-
maxToolCalls:
|
|
5176
|
-
maxTokens:
|
|
5177
|
-
maxCostUsd:
|
|
5240
|
+
maxIterations: 1e3,
|
|
5241
|
+
maxToolCalls: 2500,
|
|
5242
|
+
maxTokens: 3e4,
|
|
5243
|
+
maxCostUsd: 0.5
|
|
5178
5244
|
},
|
|
5179
5245
|
...Object.fromEntries(
|
|
5180
5246
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
@@ -5182,8 +5248,8 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
5182
5248
|
};
|
|
5183
5249
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
5184
5250
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
5185
|
-
maxIterations:
|
|
5186
|
-
maxToolCalls:
|
|
5251
|
+
maxIterations: 2500,
|
|
5252
|
+
maxToolCalls: 7500
|
|
5187
5253
|
};
|
|
5188
5254
|
function applyRosterBudget(cfg) {
|
|
5189
5255
|
const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
|
|
@@ -5239,72 +5305,71 @@ function createDelegateTool(opts) {
|
|
|
5239
5305
|
properties: {
|
|
5240
5306
|
task: {
|
|
5241
5307
|
type: "string",
|
|
5242
|
-
description: "What the subagent should do \u2014 natural language, complete sentence(s).
|
|
5308
|
+
description: "What the subagent should do \u2014 natural language, complete sentence(s)."
|
|
5243
5309
|
},
|
|
5244
5310
|
role: {
|
|
5245
5311
|
type: "string",
|
|
5246
|
-
description: rosterIds.length > 0 ?
|
|
5247
|
-
enum: rosterIds.length > 0 ? rosterIds : void 0
|
|
5312
|
+
description: rosterIds.length > 0 ? "Roster role id. Common: bug-hunter, security-scanner, refactor-planner, critic, audit-log, executor, shadow-agent, architect." : "No roster configured \u2014 pass `name` instead."
|
|
5248
5313
|
},
|
|
5249
5314
|
name: {
|
|
5250
5315
|
type: "string",
|
|
5251
|
-
description: "Display name for free-form subagents (
|
|
5316
|
+
description: "Display name for free-form subagents (no roster role). Required when `role` is omitted."
|
|
5252
5317
|
},
|
|
5253
5318
|
provider: {
|
|
5254
5319
|
type: "string",
|
|
5255
|
-
description: 'Provider id (e.g. "anthropic", "openai"). Defaults to
|
|
5320
|
+
description: 'Provider id (e.g. "anthropic", "openai"). Defaults to host provider.'
|
|
5256
5321
|
},
|
|
5257
5322
|
model: {
|
|
5258
5323
|
type: "string",
|
|
5259
|
-
description: "Model id within the provider. Defaults to
|
|
5324
|
+
description: "Model id within the provider. Defaults to host model."
|
|
5260
5325
|
},
|
|
5261
5326
|
systemPromptOverride: {
|
|
5262
5327
|
type: "string",
|
|
5263
|
-
description: "
|
|
5328
|
+
description: "Extra prompt text appended to the role baseline."
|
|
5264
5329
|
},
|
|
5265
5330
|
timeoutMs: {
|
|
5266
5331
|
type: "number",
|
|
5267
5332
|
minimum: 1,
|
|
5268
|
-
description: `Wall-clock budget
|
|
5333
|
+
description: `Wall-clock budget in ms (default ${Math.round(defaultTimeoutMs / 1e3 / 60)} min). No hard cap \u2014 set as high as the task needs.`
|
|
5269
5334
|
},
|
|
5270
5335
|
maxIterations: {
|
|
5271
5336
|
type: "number",
|
|
5272
5337
|
minimum: 1,
|
|
5273
|
-
description: "Maximum LLM iterations
|
|
5338
|
+
description: "Maximum LLM iterations. Unset = role default. Raise for deep multi-step tasks."
|
|
5274
5339
|
},
|
|
5275
5340
|
maxToolCalls: {
|
|
5276
5341
|
type: "number",
|
|
5277
5342
|
minimum: 1,
|
|
5278
|
-
description: "Maximum
|
|
5343
|
+
description: "Maximum tool invocations. Unset = role default. Raise for file-heavy tasks."
|
|
5279
5344
|
},
|
|
5280
5345
|
idleTimeoutMs: {
|
|
5281
5346
|
type: "number",
|
|
5282
5347
|
minimum: 1,
|
|
5283
|
-
description: "Idle timeout in ms
|
|
5348
|
+
description: "Idle timeout in ms. Resets on activity. Unset = role default."
|
|
5284
5349
|
},
|
|
5285
5350
|
maxTokens: {
|
|
5286
5351
|
type: "number",
|
|
5287
5352
|
minimum: 1,
|
|
5288
|
-
description: "
|
|
5353
|
+
description: "Max total tokens (input+output). Unset = role default."
|
|
5289
5354
|
},
|
|
5290
5355
|
maxCostUsd: {
|
|
5291
5356
|
type: "number",
|
|
5292
5357
|
minimum: 0,
|
|
5293
|
-
description: "
|
|
5358
|
+
description: "Max estimated USD cost. Unset = role default."
|
|
5294
5359
|
},
|
|
5295
5360
|
maxHandoffs: {
|
|
5296
5361
|
type: "number",
|
|
5297
5362
|
minimum: 0,
|
|
5298
5363
|
maximum: 8,
|
|
5299
|
-
description: "
|
|
5364
|
+
description: "Max fresh-worker continuations after budget exhaustion. Default 1. Each gets the prior partial report."
|
|
5300
5365
|
}
|
|
5301
5366
|
},
|
|
5302
5367
|
required: ["task"]
|
|
5303
5368
|
};
|
|
5304
5369
|
return {
|
|
5305
5370
|
name: "delegate",
|
|
5306
|
-
description: "Hand a
|
|
5307
|
-
usageHint: "Set `task` to a complete instruction.
|
|
5371
|
+
description: "Hand a piece of work to a subagent and wait for its result. Has own context, LLM call, auto-extending budget. Can continue remaining work with a fresh subagent on partial completion (maxHandoffs, default 1). Workers cannot recursively spawn. Use roster roles when possible; otherwise pass `name` + `task`.",
|
|
5372
|
+
usageHint: "Set `task` to a complete instruction. Pick `role` from roster or pass `name` for free-form. Override `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
|
|
5308
5373
|
permission: "auto",
|
|
5309
5374
|
mutating: false,
|
|
5310
5375
|
managesOwnTimeout: true,
|
|
@@ -5331,10 +5396,9 @@ function createDelegateTool(opts) {
|
|
|
5331
5396
|
director = await opts.host.promoteToDirector();
|
|
5332
5397
|
}
|
|
5333
5398
|
if (!director) {
|
|
5334
|
-
const reason = opts.host.getPromotionBlockReason?.();
|
|
5335
5399
|
return {
|
|
5336
5400
|
ok: false,
|
|
5337
|
-
error:
|
|
5401
|
+
error: "Director could not be activated \u2014 fleet orchestration is unavailable."
|
|
5338
5402
|
};
|
|
5339
5403
|
}
|
|
5340
5404
|
const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
|
|
@@ -6014,6 +6078,63 @@ function resolveMaxSpawnDepth(requested) {
|
|
|
6014
6078
|
return Math.max(0, Math.min(HARD_MAX_SPAWN_DEPTH, Math.floor(requested)));
|
|
6015
6079
|
}
|
|
6016
6080
|
|
|
6081
|
+
// src/coordination/director/director-errors.ts
|
|
6082
|
+
var FleetSpawnBudgetError = class extends Error {
|
|
6083
|
+
kind;
|
|
6084
|
+
limit;
|
|
6085
|
+
observed;
|
|
6086
|
+
constructor(kind, limit, observed, message) {
|
|
6087
|
+
const defaultMsg = kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`;
|
|
6088
|
+
super(message ?? defaultMsg);
|
|
6089
|
+
this.name = "FleetSpawnBudgetError";
|
|
6090
|
+
this.kind = kind;
|
|
6091
|
+
this.limit = limit;
|
|
6092
|
+
this.observed = observed;
|
|
6093
|
+
}
|
|
6094
|
+
};
|
|
6095
|
+
var FleetCostCapError = class extends Error {
|
|
6096
|
+
kind;
|
|
6097
|
+
limit;
|
|
6098
|
+
observed;
|
|
6099
|
+
constructor(limit, observed) {
|
|
6100
|
+
super(
|
|
6101
|
+
`Director cost cap exceeded: total fleet spend ${observed.toFixed(4)} exceeds maxCostUsd ${limit.toFixed(4)}`
|
|
6102
|
+
);
|
|
6103
|
+
this.name = "FleetCostCapError";
|
|
6104
|
+
this.kind = "max_cost_usd";
|
|
6105
|
+
this.limit = limit;
|
|
6106
|
+
this.observed = observed;
|
|
6107
|
+
}
|
|
6108
|
+
};
|
|
6109
|
+
var FleetTokenCapError = class extends Error {
|
|
6110
|
+
kind;
|
|
6111
|
+
limit;
|
|
6112
|
+
observed;
|
|
6113
|
+
constructor(limit, observed) {
|
|
6114
|
+
super(
|
|
6115
|
+
`Director token cap exceeded: total fleet usage ${Math.round(observed)} tokens meets or exceeds maxTokens ${Math.round(limit)}`
|
|
6116
|
+
);
|
|
6117
|
+
this.name = "FleetTokenCapError";
|
|
6118
|
+
this.kind = "max_tokens";
|
|
6119
|
+
this.limit = limit;
|
|
6120
|
+
this.observed = observed;
|
|
6121
|
+
}
|
|
6122
|
+
};
|
|
6123
|
+
var FleetContextOverflowError = class extends Error {
|
|
6124
|
+
kind;
|
|
6125
|
+
limit;
|
|
6126
|
+
observed;
|
|
6127
|
+
constructor(limit, observed) {
|
|
6128
|
+
super(
|
|
6129
|
+
`Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
|
|
6130
|
+
);
|
|
6131
|
+
this.name = "FleetContextOverflowError";
|
|
6132
|
+
this.kind = "max_context_load";
|
|
6133
|
+
this.limit = limit;
|
|
6134
|
+
this.observed = observed;
|
|
6135
|
+
}
|
|
6136
|
+
};
|
|
6137
|
+
|
|
6017
6138
|
// src/coordination/collab-debug.ts
|
|
6018
6139
|
import { EventEmitter } from "node:events";
|
|
6019
6140
|
import * as fsp4 from "node:fs/promises";
|
|
@@ -6230,7 +6351,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
6230
6351
|
}
|
|
6231
6352
|
for (const filePath of allFiles) {
|
|
6232
6353
|
try {
|
|
6233
|
-
const [content,
|
|
6354
|
+
const [content, stat12] = await Promise.all([
|
|
6234
6355
|
fsp4.readFile(filePath, "utf8"),
|
|
6235
6356
|
fsp4.stat(filePath)
|
|
6236
6357
|
]);
|
|
@@ -6240,8 +6361,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
6240
6361
|
path: filePath,
|
|
6241
6362
|
content,
|
|
6242
6363
|
language,
|
|
6243
|
-
snapshotMtimeMs:
|
|
6244
|
-
snapshotSizeBytes:
|
|
6364
|
+
snapshotMtimeMs: stat12.mtimeMs,
|
|
6365
|
+
snapshotSizeBytes: stat12.size
|
|
6245
6366
|
});
|
|
6246
6367
|
} catch {
|
|
6247
6368
|
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
@@ -6660,9 +6781,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
6660
6781
|
for (const file of this.snapshot.files) {
|
|
6661
6782
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
6662
6783
|
try {
|
|
6663
|
-
const
|
|
6664
|
-
const mtimeChanged = file.snapshotMtimeMs !== void 0 &&
|
|
6665
|
-
const sizeChanged = file.snapshotSizeBytes !== void 0 &&
|
|
6784
|
+
const stat12 = await fsp4.stat(file.path);
|
|
6785
|
+
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat12.mtimeMs > file.snapshotMtimeMs + 1;
|
|
6786
|
+
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat12.size !== file.snapshotSizeBytes;
|
|
6666
6787
|
if (mtimeChanged || sizeChanged) {
|
|
6667
6788
|
warnings.push(`${file.path} changed after the collab snapshot was captured.`);
|
|
6668
6789
|
}
|
|
@@ -6737,65 +6858,86 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
6737
6858
|
}
|
|
6738
6859
|
};
|
|
6739
6860
|
|
|
6740
|
-
// src/coordination/director/director-
|
|
6741
|
-
var
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
observed;
|
|
6745
|
-
constructor(kind, limit, observed, message) {
|
|
6746
|
-
const defaultMsg = kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`;
|
|
6747
|
-
super(message ?? defaultMsg);
|
|
6748
|
-
this.name = "FleetSpawnBudgetError";
|
|
6749
|
-
this.kind = kind;
|
|
6750
|
-
this.limit = limit;
|
|
6751
|
-
this.observed = observed;
|
|
6861
|
+
// src/coordination/director/director-collab.ts
|
|
6862
|
+
var DirectorCollabController = class {
|
|
6863
|
+
constructor(deps) {
|
|
6864
|
+
this.deps = deps;
|
|
6752
6865
|
}
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6866
|
+
deps;
|
|
6867
|
+
/** Active collab sessions tracked by sessionId (see spawn).
|
|
6868
|
+
* The tuple holds the session and its Director-registered listener unsubs.
|
|
6869
|
+
* Calling the unsubs on cancel/premature-cleanup prevents listener accumulation
|
|
6870
|
+
* on CollabSession (EventEmitter) across many spawn() calls. */
|
|
6871
|
+
activeSessions = /* @__PURE__ */ new Map();
|
|
6872
|
+
async spawn(options) {
|
|
6873
|
+
const session = new CollabSession(this.deps.director, this.deps.fleet, {
|
|
6874
|
+
...options,
|
|
6875
|
+
onBudgetWarning: (alert) => {
|
|
6876
|
+
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
6877
|
+
}
|
|
6878
|
+
});
|
|
6879
|
+
const doneHandler = () => this.activeSessions.delete(session.sessionId);
|
|
6880
|
+
const errorHandler = () => this.activeSessions.delete(session.sessionId);
|
|
6881
|
+
session.on("session.done", doneHandler);
|
|
6882
|
+
session.on("session.error", errorHandler);
|
|
6883
|
+
const unsubs = [
|
|
6884
|
+
() => session.off("session.done", doneHandler),
|
|
6885
|
+
() => session.off("session.error", errorHandler)
|
|
6886
|
+
];
|
|
6887
|
+
this.activeSessions.set(session.sessionId, { session, unsubs });
|
|
6888
|
+
return session.start();
|
|
6766
6889
|
}
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6890
|
+
cancel(sessionId, reason = "Director cancelled") {
|
|
6891
|
+
const entry = this.activeSessions.get(sessionId);
|
|
6892
|
+
if (!entry || entry.session.isCancelled()) return;
|
|
6893
|
+
for (const unsub of entry.unsubs) unsub();
|
|
6894
|
+
entry.session.cancel(reason);
|
|
6895
|
+
for (const [_role, subagentId] of entry.session.getSubagentIds()) {
|
|
6896
|
+
this.deps.coordinator.stop(subagentId).catch((err) => {
|
|
6897
|
+
this.deps.logger?.debug(
|
|
6898
|
+
`stop subagent ${subagentId} failed (may have already completed)`,
|
|
6899
|
+
{
|
|
6900
|
+
subagentId,
|
|
6901
|
+
err: err instanceof Error ? err.message : String(err)
|
|
6902
|
+
}
|
|
6903
|
+
);
|
|
6904
|
+
});
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
onAlert(handler) {
|
|
6908
|
+
return this.deps.fleet.filter("collab.warning", (e) => {
|
|
6909
|
+
handler(e.payload);
|
|
6910
|
+
});
|
|
6911
|
+
}
|
|
6912
|
+
activeSessionIds() {
|
|
6913
|
+
return Array.from(this.activeSessions.keys());
|
|
6780
6914
|
}
|
|
6781
6915
|
};
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
);
|
|
6790
|
-
this.
|
|
6791
|
-
this.
|
|
6792
|
-
|
|
6793
|
-
|
|
6916
|
+
|
|
6917
|
+
// src/coordination/director/director-btw-notes.ts
|
|
6918
|
+
var DirectorBtwNotes = class {
|
|
6919
|
+
notes = [];
|
|
6920
|
+
/** Stash a note (trimmed; empty ignored). Returns the pending count. Cap 20. */
|
|
6921
|
+
add(note) {
|
|
6922
|
+
const trimmed = note.trim();
|
|
6923
|
+
if (!trimmed) return this.notes.length;
|
|
6924
|
+
this.notes = [...this.notes, trimmed].slice(-20);
|
|
6925
|
+
return this.notes.length;
|
|
6926
|
+
}
|
|
6927
|
+
/** Read and clear all pending notes, in FIFO order (empty array when none). */
|
|
6928
|
+
drain() {
|
|
6929
|
+
const notes = this.notes;
|
|
6930
|
+
this.notes = [];
|
|
6931
|
+
return notes;
|
|
6932
|
+
}
|
|
6933
|
+
/** Peek at pending notes without consuming them. */
|
|
6934
|
+
peek() {
|
|
6935
|
+
return [...this.notes];
|
|
6794
6936
|
}
|
|
6795
6937
|
};
|
|
6796
6938
|
|
|
6797
6939
|
// src/utils/instruction-file.ts
|
|
6798
|
-
import { readFileSync as
|
|
6940
|
+
import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
6799
6941
|
import * as path6 from "node:path";
|
|
6800
6942
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6801
6943
|
var textCache = /* @__PURE__ */ new Map();
|
|
@@ -6806,7 +6948,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
6806
6948
|
let resolved = "";
|
|
6807
6949
|
for (const root of instructionRootCandidates()) {
|
|
6808
6950
|
try {
|
|
6809
|
-
resolved =
|
|
6951
|
+
resolved = readFileSync3(path6.join(root, relativePath), "utf8").trimEnd();
|
|
6810
6952
|
break;
|
|
6811
6953
|
} catch {
|
|
6812
6954
|
}
|
|
@@ -6833,7 +6975,7 @@ function instructionRootCandidates() {
|
|
|
6833
6975
|
}
|
|
6834
6976
|
function isDirectory2(candidate) {
|
|
6835
6977
|
try {
|
|
6836
|
-
return
|
|
6978
|
+
return statSync3(candidate).isDirectory();
|
|
6837
6979
|
} catch {
|
|
6838
6980
|
return false;
|
|
6839
6981
|
}
|
|
@@ -10190,13 +10332,12 @@ var Director = class _Director {
|
|
|
10190
10332
|
* coordinator from keeping workers alive for tasks that will never arrive.
|
|
10191
10333
|
*/
|
|
10192
10334
|
workCompleteFlag = false;
|
|
10193
|
-
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote).
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
*
|
|
10198
|
-
|
|
10199
|
-
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
10335
|
+
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote).
|
|
10336
|
+
* Owned by DirectorBtwNotes (R4); the public btw methods delegate to it. */
|
|
10337
|
+
btwNotes = new DirectorBtwNotes();
|
|
10338
|
+
/** Owns active collab-debug sessions (spawn/cancel/alert/list). Extracted
|
|
10339
|
+
* from Director in R4; the public collab methods below delegate to it. */
|
|
10340
|
+
collab;
|
|
10200
10341
|
/** Prevents large `ask_subagent` answers from bloating the leader's context window. */
|
|
10201
10342
|
largeAnswerStore;
|
|
10202
10343
|
constructor(opts) {
|
|
@@ -10273,92 +10414,103 @@ var Director = class _Director {
|
|
|
10273
10414
|
);
|
|
10274
10415
|
this.coordinator.setFleetBus(this.fleet);
|
|
10275
10416
|
this.fleetManager?.setCoordinator(this.coordinator);
|
|
10276
|
-
this.taskCompletedListener = (payload) =>
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10417
|
+
this.taskCompletedListener = (payload) => this.handleTaskCompleted(payload);
|
|
10418
|
+
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
10419
|
+
this.wireBudgetPolicy();
|
|
10420
|
+
this.largeAnswerStore = new LargeAnswerStore(2e3);
|
|
10421
|
+
this.collab = new DirectorCollabController({
|
|
10422
|
+
director: this,
|
|
10423
|
+
fleet: this.fleet,
|
|
10424
|
+
coordinator: this.coordinator,
|
|
10425
|
+
logger: this.logger
|
|
10426
|
+
});
|
|
10427
|
+
}
|
|
10428
|
+
handleTaskCompleted(payload) {
|
|
10429
|
+
const r = payload.result;
|
|
10430
|
+
const internalTask = this.internalTaskIds.delete(r.taskId);
|
|
10431
|
+
if (!internalTask) {
|
|
10432
|
+
this.completed.set(r.taskId, r);
|
|
10433
|
+
if (this.completed.size > _Director.MAX_COMPLETED) {
|
|
10434
|
+
const toDelete = this.completed.size - _Director.MAX_COMPLETED;
|
|
10435
|
+
const keys = [...this.completed.keys()].slice(0, toDelete);
|
|
10436
|
+
for (const k of keys) this.completed.delete(k);
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10439
|
+
const waiter = this.taskWaiters.get(r.taskId);
|
|
10440
|
+
if (waiter) {
|
|
10441
|
+
waiter.resolve(r);
|
|
10442
|
+
this.taskWaiters.delete(r.taskId);
|
|
10443
|
+
}
|
|
10444
|
+
let anyConsumed = false;
|
|
10445
|
+
for (const aw of [...this.anyWaiters]) {
|
|
10446
|
+
if (!aw.ids.has(r.taskId)) continue;
|
|
10447
|
+
if (aw.timer) clearTimeout(aw.timer);
|
|
10448
|
+
this.anyWaiters.delete(aw);
|
|
10449
|
+
aw.resolve(r);
|
|
10450
|
+
anyConsumed = true;
|
|
10451
|
+
}
|
|
10452
|
+
const consumedInBand = !!waiter || anyConsumed;
|
|
10453
|
+
if (internalTask) return;
|
|
10454
|
+
const title = this.taskDescriptions.get(r.taskId) ?? payload.task.description ?? r.taskId;
|
|
10455
|
+
if (!consumedInBand && this.taskResultNotifier) {
|
|
10456
|
+
const resultText = typeof r.result === "string" ? r.result : r.result !== void 0 ? safeStringify(r.result) : void 0;
|
|
10457
|
+
try {
|
|
10458
|
+
void Promise.resolve(
|
|
10459
|
+
this.taskResultNotifier({
|
|
10460
|
+
taskId: r.taskId,
|
|
10461
|
+
title,
|
|
10462
|
+
status: r.status,
|
|
10463
|
+
subagentId: r.subagentId,
|
|
10464
|
+
subagentName: this.manifestEntries.get(r.subagentId)?.name,
|
|
10465
|
+
resultText,
|
|
10466
|
+
errorText: r.error ? `${r.error.kind}: ${r.error.message}` : void 0,
|
|
10467
|
+
partialText: r.partial?.text,
|
|
10468
|
+
report: r.report,
|
|
10469
|
+
iterations: r.iterations,
|
|
10470
|
+
toolCalls: r.toolCalls,
|
|
10471
|
+
durationMs: r.durationMs
|
|
10472
|
+
})
|
|
10473
|
+
).catch(() => {
|
|
10474
|
+
});
|
|
10475
|
+
} catch {
|
|
10325
10476
|
}
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
);
|
|
10351
|
-
if (this.fleetManager) {
|
|
10352
|
-
void this.fleetManager.flushManifest();
|
|
10353
|
-
} else {
|
|
10354
|
-
this.scheduleManifest();
|
|
10477
|
+
}
|
|
10478
|
+
const failed = r.status !== "success";
|
|
10479
|
+
const errorString = r.error ? `${r.error.kind}: ${r.error.message}` : void 0;
|
|
10480
|
+
this.stateCheckpoint?.recordTaskStatus(r.taskId, {
|
|
10481
|
+
status: failed ? r.status : "completed",
|
|
10482
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10483
|
+
iterations: r.iterations,
|
|
10484
|
+
toolCalls: r.toolCalls,
|
|
10485
|
+
durationMs: r.durationMs,
|
|
10486
|
+
error: errorString
|
|
10487
|
+
});
|
|
10488
|
+
this.stateCheckpoint?.setUsage(this.usage.snapshot());
|
|
10489
|
+
void this.appendSessionEvent(
|
|
10490
|
+
failed ? {
|
|
10491
|
+
type: "task_failed",
|
|
10492
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10493
|
+
taskId: r.taskId,
|
|
10494
|
+
title,
|
|
10495
|
+
error: errorString ?? r.status
|
|
10496
|
+
} : {
|
|
10497
|
+
type: "task_completed",
|
|
10498
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10499
|
+
taskId: r.taskId,
|
|
10500
|
+
title
|
|
10355
10501
|
}
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10502
|
+
);
|
|
10503
|
+
if (this.fleetManager) {
|
|
10504
|
+
void this.fleetManager.flushManifest();
|
|
10505
|
+
} else {
|
|
10506
|
+
this.scheduleManifest();
|
|
10507
|
+
}
|
|
10508
|
+
this.armSubagentIdleRetirement(
|
|
10509
|
+
r.subagentId,
|
|
10510
|
+
this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleTimeoutMs
|
|
10511
|
+
);
|
|
10512
|
+
}
|
|
10513
|
+
wireBudgetPolicy() {
|
|
10362
10514
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
10363
10515
|
const progressBySubagent = /* @__PURE__ */ new Map();
|
|
10364
10516
|
const lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
@@ -10478,7 +10630,6 @@ var Director = class _Director {
|
|
|
10478
10630
|
}
|
|
10479
10631
|
grantExtension();
|
|
10480
10632
|
});
|
|
10481
|
-
this.largeAnswerStore = new LargeAnswerStore(2e3);
|
|
10482
10633
|
}
|
|
10483
10634
|
/**
|
|
10484
10635
|
* Record a granted budget extension and broadcast it on the FleetBus so
|
|
@@ -10536,10 +10687,7 @@ var Director = class _Director {
|
|
|
10536
10687
|
* programmatically without going through the slash-command path.
|
|
10537
10688
|
*/
|
|
10538
10689
|
setLeaderBtwNote(note) {
|
|
10539
|
-
|
|
10540
|
-
if (!trimmed) return this._leaderBtwNotes.length;
|
|
10541
|
-
this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
|
|
10542
|
-
return this._leaderBtwNotes.length;
|
|
10690
|
+
return this.btwNotes.add(note);
|
|
10543
10691
|
}
|
|
10544
10692
|
/**
|
|
10545
10693
|
* Read and clear all pending /btw notes the leader has stashed.
|
|
@@ -10550,16 +10698,14 @@ var Director = class _Director {
|
|
|
10550
10698
|
* to cancel the collab session or let it continue.
|
|
10551
10699
|
*/
|
|
10552
10700
|
getLeaderBtwNotes() {
|
|
10553
|
-
|
|
10554
|
-
this._leaderBtwNotes = [];
|
|
10555
|
-
return notes;
|
|
10701
|
+
return this.btwNotes.drain();
|
|
10556
10702
|
}
|
|
10557
10703
|
/**
|
|
10558
10704
|
* Peek at pending /btw notes without consuming them.
|
|
10559
10705
|
* Useful for UI to show "N pending notes" without clearing them.
|
|
10560
10706
|
*/
|
|
10561
10707
|
peekLeaderBtwNotes() {
|
|
10562
|
-
return
|
|
10708
|
+
return this.btwNotes.peek();
|
|
10563
10709
|
}
|
|
10564
10710
|
/**
|
|
10565
10711
|
* Drain (read + clear) all /btw notes in one call.
|
|
@@ -10579,18 +10725,7 @@ var Director = class _Director {
|
|
|
10579
10725
|
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
10580
10726
|
*/
|
|
10581
10727
|
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
10582
|
-
|
|
10583
|
-
if (!entry || entry.session.isCancelled()) return;
|
|
10584
|
-
for (const unsub of entry.unsubs) unsub();
|
|
10585
|
-
entry.session.cancel(reason);
|
|
10586
|
-
for (const [_role, subagentId] of entry.session.getSubagentIds()) {
|
|
10587
|
-
this.coordinator.stop(subagentId).catch((err) => {
|
|
10588
|
-
this.logger?.debug(`stop subagent ${subagentId} failed (may have already completed)`, {
|
|
10589
|
-
subagentId,
|
|
10590
|
-
err: err instanceof Error ? err.message : String(err)
|
|
10591
|
-
});
|
|
10592
|
-
});
|
|
10593
|
-
}
|
|
10728
|
+
this.collab.cancel(sessionId, reason);
|
|
10594
10729
|
}
|
|
10595
10730
|
/**
|
|
10596
10731
|
* Subscribe a callback to be notified whenever a collab session raises
|
|
@@ -10599,9 +10734,7 @@ var Director = class _Director {
|
|
|
10599
10734
|
* Returns an unsubscribe function.
|
|
10600
10735
|
*/
|
|
10601
10736
|
onCollabAlert(handler) {
|
|
10602
|
-
return this.
|
|
10603
|
-
handler(e.payload);
|
|
10604
|
-
});
|
|
10737
|
+
return this.collab.onAlert(handler);
|
|
10605
10738
|
}
|
|
10606
10739
|
/**
|
|
10607
10740
|
* Returns all active (not yet settled) collab session ids.
|
|
@@ -10609,7 +10742,7 @@ var Director = class _Director {
|
|
|
10609
10742
|
* the host to know what can be cancelled.
|
|
10610
10743
|
*/
|
|
10611
10744
|
activeCollabSessions() {
|
|
10612
|
-
return
|
|
10745
|
+
return this.collab.activeSessionIds();
|
|
10613
10746
|
}
|
|
10614
10747
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
10615
10748
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
@@ -10675,17 +10808,90 @@ var Director = class _Director {
|
|
|
10675
10808
|
);
|
|
10676
10809
|
}
|
|
10677
10810
|
const config = { ...callerConfig };
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10811
|
+
this.resolveSpawnModel(config);
|
|
10812
|
+
this.enforceSpawnCaps(config);
|
|
10813
|
+
let result;
|
|
10814
|
+
this.assignSpawnNickname(config);
|
|
10815
|
+
this.applySpawnLineage(config);
|
|
10816
|
+
result = await this.coordinator.spawn(config);
|
|
10817
|
+
if (this.fleetManager) {
|
|
10818
|
+
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
10819
|
+
} else {
|
|
10820
|
+
this.spawnCount += 1;
|
|
10821
|
+
this.subagentMeta.set(result.subagentId, {
|
|
10822
|
+
provider: config.provider,
|
|
10823
|
+
model: config.model
|
|
10824
|
+
});
|
|
10825
|
+
if (priceLookup && config.provider && config.model) {
|
|
10826
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
10686
10827
|
}
|
|
10687
10828
|
}
|
|
10688
|
-
|
|
10829
|
+
const subagentBridge = new InMemoryAgentBridge(
|
|
10830
|
+
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
10831
|
+
this.transport
|
|
10832
|
+
);
|
|
10833
|
+
this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
|
|
10834
|
+
this.subagentBridges.set(result.subagentId, subagentBridge);
|
|
10835
|
+
this.fleet.emit({
|
|
10836
|
+
subagentId: result.subagentId,
|
|
10837
|
+
ts: Date.now(),
|
|
10838
|
+
type: "subagent.spawned",
|
|
10839
|
+
payload: {
|
|
10840
|
+
subagentId: result.subagentId,
|
|
10841
|
+
taskId: "",
|
|
10842
|
+
// taskId will be set when assign() is called
|
|
10843
|
+
name: config.name,
|
|
10844
|
+
role: config.role,
|
|
10845
|
+
provider: config.provider,
|
|
10846
|
+
model: config.model
|
|
10847
|
+
}
|
|
10848
|
+
});
|
|
10849
|
+
if (!this.fleetManager) {
|
|
10850
|
+
this.manifestEntries.set(result.subagentId, {
|
|
10851
|
+
subagentId: result.subagentId,
|
|
10852
|
+
name: config.name,
|
|
10853
|
+
role: config.role,
|
|
10854
|
+
provider: config.provider,
|
|
10855
|
+
model: config.model,
|
|
10856
|
+
taskIds: []
|
|
10857
|
+
});
|
|
10858
|
+
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10859
|
+
this.stateCheckpoint?.recordSpawn(
|
|
10860
|
+
{
|
|
10861
|
+
id: result.subagentId,
|
|
10862
|
+
name: config.name,
|
|
10863
|
+
role: config.role,
|
|
10864
|
+
provider: config.provider,
|
|
10865
|
+
model: config.model,
|
|
10866
|
+
spawnedAt
|
|
10867
|
+
},
|
|
10868
|
+
this.spawnCount
|
|
10869
|
+
);
|
|
10870
|
+
void this.appendSessionEvent({
|
|
10871
|
+
type: "agent_spawned",
|
|
10872
|
+
ts: spawnedAt,
|
|
10873
|
+
agentId: result.subagentId,
|
|
10874
|
+
role: config.role ?? config.name
|
|
10875
|
+
});
|
|
10876
|
+
this.scheduleManifest();
|
|
10877
|
+
}
|
|
10878
|
+
this.armSubagentIdleRetirement(result.subagentId, this.subagentIdleTimeoutMs);
|
|
10879
|
+
return result.subagentId;
|
|
10880
|
+
}
|
|
10881
|
+
resolveSpawnModel(config) {
|
|
10882
|
+
if (!config.model && this.modelMatrix) {
|
|
10883
|
+
const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
|
|
10884
|
+
const resolution = resolveModelMatrixResolution(matrix, config.role);
|
|
10885
|
+
const entry = resolution?.source === "default" && roleNeedsIndependentReviewModel(config.role) ? void 0 : resolution?.entry;
|
|
10886
|
+
if (entry?.model) {
|
|
10887
|
+
config.model = entry.model;
|
|
10888
|
+
if (entry.provider) config.provider = entry.provider;
|
|
10889
|
+
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
10890
|
+
}
|
|
10891
|
+
}
|
|
10892
|
+
}
|
|
10893
|
+
enforceSpawnCaps(config) {
|
|
10894
|
+
if (this.fleetManager) {
|
|
10689
10895
|
const rejection = this.fleetManager.canSpawn(config);
|
|
10690
10896
|
if (rejection) {
|
|
10691
10897
|
if (rejection.kind === "max_spawn_depth")
|
|
@@ -10727,7 +10933,8 @@ var Director = class _Director {
|
|
|
10727
10933
|
}
|
|
10728
10934
|
}
|
|
10729
10935
|
}
|
|
10730
|
-
|
|
10936
|
+
}
|
|
10937
|
+
assignSpawnNickname(config) {
|
|
10731
10938
|
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
10732
10939
|
if (needsNickname) {
|
|
10733
10940
|
const role = config.role ?? "subagent";
|
|
@@ -10739,6 +10946,8 @@ var Director = class _Director {
|
|
|
10739
10946
|
this._usedNicknames.add(key);
|
|
10740
10947
|
}
|
|
10741
10948
|
}
|
|
10949
|
+
}
|
|
10950
|
+
applySpawnLineage(config) {
|
|
10742
10951
|
const budget = this.fleetManager ? this.fleetManager.budgetSnapshot() : (() => {
|
|
10743
10952
|
const total = this.usage.snapshot().total;
|
|
10744
10953
|
const usedTokens = (total?.input ?? 0) + (total?.output ?? 0);
|
|
@@ -10767,70 +10976,6 @@ var Director = class _Director {
|
|
|
10767
10976
|
...Number.isFinite(budget.remainingCostUsd) ? { remainingCostUsd: budget.remainingCostUsd } : {}
|
|
10768
10977
|
}
|
|
10769
10978
|
};
|
|
10770
|
-
result = await this.coordinator.spawn(config);
|
|
10771
|
-
if (this.fleetManager) {
|
|
10772
|
-
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
10773
|
-
} else {
|
|
10774
|
-
this.spawnCount += 1;
|
|
10775
|
-
this.subagentMeta.set(result.subagentId, {
|
|
10776
|
-
provider: config.provider,
|
|
10777
|
-
model: config.model
|
|
10778
|
-
});
|
|
10779
|
-
if (priceLookup && config.provider && config.model) {
|
|
10780
|
-
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
10781
|
-
}
|
|
10782
|
-
}
|
|
10783
|
-
const subagentBridge = new InMemoryAgentBridge(
|
|
10784
|
-
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
10785
|
-
this.transport
|
|
10786
|
-
);
|
|
10787
|
-
this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
|
|
10788
|
-
this.subagentBridges.set(result.subagentId, subagentBridge);
|
|
10789
|
-
this.fleet.emit({
|
|
10790
|
-
subagentId: result.subagentId,
|
|
10791
|
-
ts: Date.now(),
|
|
10792
|
-
type: "subagent.spawned",
|
|
10793
|
-
payload: {
|
|
10794
|
-
subagentId: result.subagentId,
|
|
10795
|
-
taskId: "",
|
|
10796
|
-
// taskId will be set when assign() is called
|
|
10797
|
-
name: config.name,
|
|
10798
|
-
role: config.role,
|
|
10799
|
-
provider: config.provider,
|
|
10800
|
-
model: config.model
|
|
10801
|
-
}
|
|
10802
|
-
});
|
|
10803
|
-
if (!this.fleetManager) {
|
|
10804
|
-
this.manifestEntries.set(result.subagentId, {
|
|
10805
|
-
subagentId: result.subagentId,
|
|
10806
|
-
name: config.name,
|
|
10807
|
-
role: config.role,
|
|
10808
|
-
provider: config.provider,
|
|
10809
|
-
model: config.model,
|
|
10810
|
-
taskIds: []
|
|
10811
|
-
});
|
|
10812
|
-
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10813
|
-
this.stateCheckpoint?.recordSpawn(
|
|
10814
|
-
{
|
|
10815
|
-
id: result.subagentId,
|
|
10816
|
-
name: config.name,
|
|
10817
|
-
role: config.role,
|
|
10818
|
-
provider: config.provider,
|
|
10819
|
-
model: config.model,
|
|
10820
|
-
spawnedAt
|
|
10821
|
-
},
|
|
10822
|
-
this.spawnCount
|
|
10823
|
-
);
|
|
10824
|
-
void this.appendSessionEvent({
|
|
10825
|
-
type: "agent_spawned",
|
|
10826
|
-
ts: spawnedAt,
|
|
10827
|
-
agentId: result.subagentId,
|
|
10828
|
-
role: config.role ?? config.name
|
|
10829
|
-
});
|
|
10830
|
-
this.scheduleManifest();
|
|
10831
|
-
}
|
|
10832
|
-
this.armSubagentIdleRetirement(result.subagentId, this.subagentIdleTimeoutMs);
|
|
10833
|
-
return result.subagentId;
|
|
10834
10979
|
}
|
|
10835
10980
|
/**
|
|
10836
10981
|
* Synchronously ask a subagent something via the bridge. Sends a
|
|
@@ -10955,6 +11100,23 @@ var Director = class _Director {
|
|
|
10955
11100
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
10956
11101
|
return this.manifestPath;
|
|
10957
11102
|
}
|
|
11103
|
+
/**
|
|
11104
|
+
* Cancel the pending manifest debounce timer and drain any in-flight
|
|
11105
|
+
* manifest write so that, once this resolves, no Director-owned manifest
|
|
11106
|
+
* write is armed or running.
|
|
11107
|
+
*
|
|
11108
|
+
* Unlike `shutdown()`, this does NOT tear down the coordinator, bridges,
|
|
11109
|
+
* or waiters — it is the lightweight quiesce that `MultiAgentHost.stopAll()`
|
|
11110
|
+
* needs. `stopAll()` flushes the FleetManager's manifest but never touched
|
|
11111
|
+
* the Director's own writer, leaving its armed debounce timer to fire ~2s
|
|
11112
|
+
* later. Under CPU starvation that late, un-awaited `atomicWrite` (temp
|
|
11113
|
+
* sibling + rename) races a caller that deletes the manifest directory,
|
|
11114
|
+
* producing an `ENOTEMPTY` on the parent `rmdir` (Windows especially).
|
|
11115
|
+
*/
|
|
11116
|
+
async quiesceManifest() {
|
|
11117
|
+
this.clearManifestTimer();
|
|
11118
|
+
await this.manifestWriteChain.catch(() => void 0);
|
|
11119
|
+
}
|
|
10958
11120
|
/**
|
|
10959
11121
|
* Tear down the director: stop every subagent, close every bridge
|
|
10960
11122
|
* endpoint, and (when configured) write the final manifest. Idempotent
|
|
@@ -10966,8 +11128,17 @@ var Director = class _Director {
|
|
|
10966
11128
|
this.coordinator.off("task.completed", this.taskCompletedListener);
|
|
10967
11129
|
this.taskCompletedListener = null;
|
|
10968
11130
|
}
|
|
10969
|
-
for (const aw of this.anyWaiters) {
|
|
11131
|
+
for (const aw of [...this.anyWaiters]) {
|
|
10970
11132
|
if (aw.timer) clearTimeout(aw.timer);
|
|
11133
|
+
const firstId = [...aw.ids][0];
|
|
11134
|
+
aw.resolve({
|
|
11135
|
+
taskId: firstId ?? "unknown",
|
|
11136
|
+
subagentId: "director",
|
|
11137
|
+
status: "stopped",
|
|
11138
|
+
iterations: 0,
|
|
11139
|
+
toolCalls: 0,
|
|
11140
|
+
durationMs: 0
|
|
11141
|
+
});
|
|
10971
11142
|
}
|
|
10972
11143
|
this.anyWaiters.clear();
|
|
10973
11144
|
if (this.toolExecFilter) {
|
|
@@ -10981,6 +11152,17 @@ var Director = class _Director {
|
|
|
10981
11152
|
for (const timer of this.subagentIdleTimers.values()) clearTimeout(timer);
|
|
10982
11153
|
this.subagentIdleTimers.clear();
|
|
10983
11154
|
await this.coordinator.stopAll();
|
|
11155
|
+
for (const [id, waiter] of this.taskWaiters) {
|
|
11156
|
+
waiter.resolve({
|
|
11157
|
+
taskId: id,
|
|
11158
|
+
subagentId: "director",
|
|
11159
|
+
status: "stopped",
|
|
11160
|
+
iterations: 0,
|
|
11161
|
+
toolCalls: 0,
|
|
11162
|
+
durationMs: 0
|
|
11163
|
+
});
|
|
11164
|
+
}
|
|
11165
|
+
this.taskWaiters.clear();
|
|
10984
11166
|
for (const b of this.subagentBridges.values()) {
|
|
10985
11167
|
await b.stop().catch((err) => this.logShutdownError("subagent_bridge_stop", err));
|
|
10986
11168
|
}
|
|
@@ -10988,6 +11170,7 @@ var Director = class _Director {
|
|
|
10988
11170
|
await this.bridge.stop().catch((err) => this.logShutdownError("director_bridge_stop", err));
|
|
10989
11171
|
if (this.fleetManager) {
|
|
10990
11172
|
await this.fleetManager.flushManifest().catch((err) => this.logShutdownError("fleet_manifest_flush", err));
|
|
11173
|
+
await this.manifestWriteChain.catch(() => void 0);
|
|
10991
11174
|
} else if (this.manifestPath) {
|
|
10992
11175
|
await this.writeManifest().catch((err) => this.logShutdownError("manifest_write", err));
|
|
10993
11176
|
}
|
|
@@ -11453,22 +11636,7 @@ var Director = class _Director {
|
|
|
11453
11636
|
* three agents complete or the session times out.
|
|
11454
11637
|
*/
|
|
11455
11638
|
async spawnCollab(options) {
|
|
11456
|
-
|
|
11457
|
-
...options,
|
|
11458
|
-
onBudgetWarning: (alert) => {
|
|
11459
|
-
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
11460
|
-
}
|
|
11461
|
-
});
|
|
11462
|
-
const doneHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
11463
|
-
const errorHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
11464
|
-
session.on("session.done", doneHandler);
|
|
11465
|
-
session.on("session.error", errorHandler);
|
|
11466
|
-
const unsubs = [
|
|
11467
|
-
() => session.off("session.done", doneHandler),
|
|
11468
|
-
() => session.off("session.error", errorHandler)
|
|
11469
|
-
];
|
|
11470
|
-
this._activeCollabSessions.set(session.sessionId, { session, unsubs });
|
|
11471
|
-
return session.start();
|
|
11639
|
+
return this.collab.spawn(options);
|
|
11472
11640
|
}
|
|
11473
11641
|
/**
|
|
11474
11642
|
* Resume from a prior checkpoint snapshot (loaded via
|
|
@@ -12341,8 +12509,8 @@ var SessionCheckpointCas = class {
|
|
|
12341
12509
|
return null;
|
|
12342
12510
|
}
|
|
12343
12511
|
try {
|
|
12344
|
-
const
|
|
12345
|
-
if (
|
|
12512
|
+
const stat12 = await fsp7.lstat(absolute);
|
|
12513
|
+
if (stat12.isSymbolicLink()) {
|
|
12346
12514
|
const linkTarget = await fsp7.readlink(absolute);
|
|
12347
12515
|
const resolvedLink = path9.resolve(path9.dirname(absolute), linkTarget);
|
|
12348
12516
|
if (path9.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
@@ -12354,29 +12522,29 @@ var SessionCheckpointCas = class {
|
|
|
12354
12522
|
}
|
|
12355
12523
|
return { path: relative4, state: "symlink", linkTarget };
|
|
12356
12524
|
}
|
|
12357
|
-
if (!
|
|
12525
|
+
if (!stat12.isFile()) {
|
|
12358
12526
|
unresolved.push({ path: relative4, reason: "changed path is not a regular file" });
|
|
12359
12527
|
return null;
|
|
12360
12528
|
}
|
|
12361
|
-
if (
|
|
12529
|
+
if (stat12.size > MAX_BLOB_BYTES) {
|
|
12362
12530
|
unresolved.push({
|
|
12363
12531
|
path: relative4,
|
|
12364
12532
|
reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
|
|
12365
12533
|
});
|
|
12366
12534
|
return null;
|
|
12367
12535
|
}
|
|
12368
|
-
if (scheduledBytes +
|
|
12536
|
+
if (scheduledBytes + stat12.size > MAX_CHECKPOINT_BYTES) {
|
|
12369
12537
|
unresolved.push({
|
|
12370
12538
|
path: relative4,
|
|
12371
12539
|
reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
|
|
12372
12540
|
});
|
|
12373
12541
|
return null;
|
|
12374
12542
|
}
|
|
12375
|
-
scheduledBytes +=
|
|
12543
|
+
scheduledBytes += stat12.size;
|
|
12376
12544
|
const content = await fsp7.readFile(absolute);
|
|
12377
12545
|
const blobHash = sha256(content);
|
|
12378
12546
|
await this.putBlob(blobHash, content);
|
|
12379
|
-
return { path: relative4, state: "file", blobHash, mode:
|
|
12547
|
+
return { path: relative4, state: "file", blobHash, mode: stat12.mode & 511 };
|
|
12380
12548
|
} catch (err) {
|
|
12381
12549
|
if (err.code === "ENOENT") {
|
|
12382
12550
|
return { path: relative4, state: "absent" };
|
|
@@ -12697,11 +12865,11 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
12697
12865
|
};
|
|
12698
12866
|
}
|
|
12699
12867
|
try {
|
|
12700
|
-
const
|
|
12701
|
-
if (!
|
|
12868
|
+
const stat12 = await fsp8.stat(realFile);
|
|
12869
|
+
if (!stat12.isFile()) {
|
|
12702
12870
|
return { ...base, status: "unreadable", detail: "Path is no longer a regular file." };
|
|
12703
12871
|
}
|
|
12704
|
-
if (
|
|
12872
|
+
if (stat12.size > MAX_REVALIDATE_BYTES) {
|
|
12705
12873
|
return {
|
|
12706
12874
|
...base,
|
|
12707
12875
|
status: "unreadable",
|
|
@@ -13150,9 +13318,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13150
13318
|
let cacheHit = false;
|
|
13151
13319
|
try {
|
|
13152
13320
|
const s = await fsp9.stat(file);
|
|
13153
|
-
const
|
|
13321
|
+
const stat12 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
13154
13322
|
const cached = this._loadCache.get(id);
|
|
13155
|
-
if (cached && cached.mtimeMs ===
|
|
13323
|
+
if (cached && cached.mtimeMs === stat12.mtimeMs && cached.size === stat12.size) {
|
|
13156
13324
|
cacheHit = true;
|
|
13157
13325
|
this._loadCache.delete(id);
|
|
13158
13326
|
this._loadCache.set(id, cached);
|
|
@@ -13293,7 +13461,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13293
13461
|
this._loadCache.delete(oldest);
|
|
13294
13462
|
}
|
|
13295
13463
|
}
|
|
13296
|
-
this._loadCache.set(id, { mtimeMs:
|
|
13464
|
+
this._loadCache.set(id, { mtimeMs: stat12.mtimeMs, size: stat12.size, data });
|
|
13297
13465
|
}
|
|
13298
13466
|
return data;
|
|
13299
13467
|
} catch (err) {
|
|
@@ -13335,14 +13503,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13335
13503
|
const limit = opts?.limit;
|
|
13336
13504
|
const signal = opts?.signal;
|
|
13337
13505
|
const out = [];
|
|
13338
|
-
let
|
|
13506
|
+
let stat12;
|
|
13339
13507
|
try {
|
|
13340
|
-
|
|
13508
|
+
stat12 = await fsp9.stat(file);
|
|
13341
13509
|
} catch (err) {
|
|
13342
13510
|
if (err.code === "ENOENT") return [];
|
|
13343
13511
|
throw err;
|
|
13344
13512
|
}
|
|
13345
|
-
if (
|
|
13513
|
+
if (stat12.size === 0) return [];
|
|
13346
13514
|
let fh;
|
|
13347
13515
|
try {
|
|
13348
13516
|
fh = await fsp9.open(file, "r");
|
|
@@ -13525,15 +13693,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13525
13693
|
* Returns empty array when the index doesn't exist or is corrupt.
|
|
13526
13694
|
*/
|
|
13527
13695
|
async readIndex() {
|
|
13528
|
-
let
|
|
13696
|
+
let stat12;
|
|
13529
13697
|
try {
|
|
13530
13698
|
const s = await fsp9.stat(this.indexFile);
|
|
13531
|
-
|
|
13699
|
+
stat12 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
13532
13700
|
} catch {
|
|
13533
13701
|
this._indexCache = null;
|
|
13534
13702
|
return [];
|
|
13535
13703
|
}
|
|
13536
|
-
if (this._indexCache !== null && this._indexCache.mtimeMs ===
|
|
13704
|
+
if (this._indexCache !== null && this._indexCache.mtimeMs === stat12.mtimeMs && this._indexCache.size === stat12.size) {
|
|
13537
13705
|
return [...this._indexCache.summaries];
|
|
13538
13706
|
}
|
|
13539
13707
|
let raw;
|
|
@@ -13566,7 +13734,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13566
13734
|
if (a.startedAt > b.startedAt) return -1;
|
|
13567
13735
|
return a.id.localeCompare(b.id);
|
|
13568
13736
|
});
|
|
13569
|
-
this._indexCache = { ...
|
|
13737
|
+
this._indexCache = { ...stat12, summaries };
|
|
13570
13738
|
return [...summaries];
|
|
13571
13739
|
}
|
|
13572
13740
|
/**
|
|
@@ -13733,8 +13901,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13733
13901
|
if (fromManifest) return fromManifest;
|
|
13734
13902
|
try {
|
|
13735
13903
|
const full = this.sessionPath(id, ".jsonl");
|
|
13736
|
-
const
|
|
13737
|
-
const summary = await this.summarize(id,
|
|
13904
|
+
const stat12 = await fsp9.stat(full);
|
|
13905
|
+
const summary = await this.summarize(id, stat12.mtime.toISOString());
|
|
13738
13906
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
13739
13907
|
const msg = toErrorMessage(err);
|
|
13740
13908
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
@@ -13774,18 +13942,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13774
13942
|
async summaryHeaderFor(ref) {
|
|
13775
13943
|
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
13776
13944
|
try {
|
|
13777
|
-
const
|
|
13778
|
-
if (!
|
|
13945
|
+
const stat12 = await fsp9.stat(ref.filePath);
|
|
13946
|
+
if (!stat12.isFile()) {
|
|
13779
13947
|
return {
|
|
13780
13948
|
id: ref.id,
|
|
13781
13949
|
title: "(damaged)",
|
|
13782
|
-
startedAt:
|
|
13950
|
+
startedAt: stat12.mtime.toISOString(),
|
|
13783
13951
|
model: "unknown",
|
|
13784
13952
|
provider: "unknown",
|
|
13785
13953
|
tokenTotal: 0
|
|
13786
13954
|
};
|
|
13787
13955
|
}
|
|
13788
|
-
mtime =
|
|
13956
|
+
mtime = stat12.mtime.toISOString();
|
|
13789
13957
|
} catch {
|
|
13790
13958
|
return null;
|
|
13791
13959
|
}
|
|
@@ -13941,8 +14109,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13941
14109
|
const pruneFile = async (dir, name, prefix) => {
|
|
13942
14110
|
const jsonlPath = path11.join(dir, name);
|
|
13943
14111
|
try {
|
|
13944
|
-
const
|
|
13945
|
-
if (
|
|
14112
|
+
const stat12 = await fsp9.stat(jsonlPath);
|
|
14113
|
+
if (stat12.mtimeMs >= cutoff) return;
|
|
13946
14114
|
} catch {
|
|
13947
14115
|
return;
|
|
13948
14116
|
}
|
|
@@ -14158,6 +14326,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
14158
14326
|
failureMode;
|
|
14159
14327
|
policyProvider;
|
|
14160
14328
|
sessionBridge;
|
|
14329
|
+
onCompact;
|
|
14161
14330
|
/**
|
|
14162
14331
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
14163
14332
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -14204,6 +14373,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
14204
14373
|
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
14205
14374
|
this.policyProvider = opts.policyProvider;
|
|
14206
14375
|
this.sessionBridge = opts.sessionBridge;
|
|
14376
|
+
this.onCompact = opts.onCompact;
|
|
14207
14377
|
}
|
|
14208
14378
|
/** Allow callers (e.g. model-switch in WebUI) to update the context window
|
|
14209
14379
|
* denominator when the active model changes. */
|
|
@@ -14326,6 +14496,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
14326
14496
|
try {
|
|
14327
14497
|
const report = await this.compactor.compact(ctx, { aggressive });
|
|
14328
14498
|
this.recordAttempt(pressure.level, pressure.tokens, report);
|
|
14499
|
+
this.onCompact?.(report);
|
|
14329
14500
|
this.events?.emit("compaction.fired", {
|
|
14330
14501
|
sessionId: ctx.session.id,
|
|
14331
14502
|
level: pressure.level,
|
|
@@ -14354,7 +14525,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
14354
14525
|
});
|
|
14355
14526
|
ctx.clearFileTracking();
|
|
14356
14527
|
const afterTokens = report.fullRequestTokensAfter ?? report.after;
|
|
14357
|
-
const
|
|
14528
|
+
const afterBudget = computeContextWindowBudget(ctx, afterTokens, this._maxContext);
|
|
14529
|
+
const afterLoad = afterBudget.load;
|
|
14358
14530
|
const stillHard = afterLoad >= pressure.hardThreshold;
|
|
14359
14531
|
const fatal = stillHard && (this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard");
|
|
14360
14532
|
if (stillHard) {
|
|
@@ -15009,7 +15181,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
15009
15181
|
}
|
|
15010
15182
|
if (newContent) {
|
|
15011
15183
|
next ??= messages.slice();
|
|
15012
|
-
next[i] = { ...msg, content: newContent };
|
|
15184
|
+
next[i] = { ...msg, content: newContent, _estTokens: void 0 };
|
|
15013
15185
|
changed = true;
|
|
15014
15186
|
}
|
|
15015
15187
|
if (compactionDebugEnabled()) {
|
|
@@ -15269,7 +15441,7 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
15269
15441
|
descriptionMode: Object.freeze({}),
|
|
15270
15442
|
disabledTools: Object.freeze([]),
|
|
15271
15443
|
autoExtendLimit: true,
|
|
15272
|
-
restrictToProjectRoot:
|
|
15444
|
+
restrictToProjectRoot: true,
|
|
15273
15445
|
loopDetection: Object.freeze({
|
|
15274
15446
|
mode: "steer-then-cut",
|
|
15275
15447
|
steerThreshold: 3,
|
|
@@ -15279,8 +15451,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
15279
15451
|
})
|
|
15280
15452
|
});
|
|
15281
15453
|
var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
15282
|
-
preserveK:
|
|
15283
|
-
eliseThreshold:
|
|
15454
|
+
preserveK: 8,
|
|
15455
|
+
eliseThreshold: 1e3
|
|
15284
15456
|
});
|
|
15285
15457
|
var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
|
|
15286
15458
|
autoProceedDelayMs: 45e3
|
|
@@ -15443,7 +15615,7 @@ function isDesignStack(v) {
|
|
|
15443
15615
|
|
|
15444
15616
|
// src/execution/design-kit-loader.ts
|
|
15445
15617
|
import { existsSync } from "node:fs";
|
|
15446
|
-
import * as
|
|
15618
|
+
import * as fs3 from "node:fs/promises";
|
|
15447
15619
|
import * as path13 from "node:path";
|
|
15448
15620
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
15449
15621
|
var KIT_FILE = "KIT.md";
|
|
@@ -15483,17 +15655,17 @@ function parseKitFrontmatter(raw) {
|
|
|
15483
15655
|
else out[key] = items;
|
|
15484
15656
|
};
|
|
15485
15657
|
for (const line of lines) {
|
|
15486
|
-
const
|
|
15487
|
-
if (listKey &&
|
|
15488
|
-
const parsed = parseList(
|
|
15658
|
+
const bulletMatch = /^\s*-\s+(.*)$/.exec(line);
|
|
15659
|
+
if (listKey && bulletMatch) {
|
|
15660
|
+
const parsed = parseList(bulletMatch[1] ?? "");
|
|
15489
15661
|
if (listKey === "stacks") out.stacks.push(...parsed.filter(isDesignStack));
|
|
15490
15662
|
else out[listKey].push(...parsed);
|
|
15491
15663
|
continue;
|
|
15492
15664
|
}
|
|
15493
|
-
const
|
|
15494
|
-
if (!
|
|
15495
|
-
const key =
|
|
15496
|
-
const rest = (
|
|
15665
|
+
const kvMatch = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
|
|
15666
|
+
if (!kvMatch) continue;
|
|
15667
|
+
const key = kvMatch[1] ?? "";
|
|
15668
|
+
const rest = (kvMatch[2] ?? "").trim();
|
|
15497
15669
|
if (key === "tags" || key === "stacks" || key === "themes") {
|
|
15498
15670
|
if (rest) {
|
|
15499
15671
|
setList(key, parseList(rest));
|
|
@@ -15510,24 +15682,23 @@ function parseKitFrontmatter(raw) {
|
|
|
15510
15682
|
}
|
|
15511
15683
|
function narrowStackSections(body, stack) {
|
|
15512
15684
|
if (!stack) return body;
|
|
15513
|
-
const
|
|
15514
|
-
const
|
|
15515
|
-
let
|
|
15516
|
-
while ((
|
|
15517
|
-
|
|
15518
|
-
}
|
|
15519
|
-
if (
|
|
15520
|
-
const
|
|
15521
|
-
let result = body.slice(0,
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
if (cur.id === stack) {
|
|
15685
|
+
const stackHeading = /^##\s+Stack:\s*([a-z-]+)\s*$/gim;
|
|
15686
|
+
const sections = [];
|
|
15687
|
+
let match;
|
|
15688
|
+
while ((match = stackHeading.exec(body)) !== null) {
|
|
15689
|
+
sections.push({ id: (match[1] ?? "").trim(), start: match.index });
|
|
15690
|
+
}
|
|
15691
|
+
if (sections.length === 0) return body;
|
|
15692
|
+
const preambleEnd = sections[0].start;
|
|
15693
|
+
let result = body.slice(0, preambleEnd).trimEnd();
|
|
15694
|
+
let sectionEnd;
|
|
15695
|
+
for (let i = 0; i < sections.length; i++) {
|
|
15696
|
+
const current = sections[i];
|
|
15697
|
+
sectionEnd = i + 1 < sections.length ? sections[i + 1].start : body.length;
|
|
15698
|
+
if (current.id === stack) {
|
|
15528
15699
|
result += `
|
|
15529
15700
|
|
|
15530
|
-
${body.slice(
|
|
15701
|
+
${body.slice(current.start, sectionEnd).trimEnd()}`;
|
|
15531
15702
|
}
|
|
15532
15703
|
}
|
|
15533
15704
|
return `${result}
|
|
@@ -15552,7 +15723,7 @@ var DefaultDesignKitLoader = class {
|
|
|
15552
15723
|
for (const { dir, source } of this.dirs) {
|
|
15553
15724
|
let entries;
|
|
15554
15725
|
try {
|
|
15555
|
-
entries = await
|
|
15726
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
15556
15727
|
} catch {
|
|
15557
15728
|
continue;
|
|
15558
15729
|
}
|
|
@@ -15560,7 +15731,7 @@ var DefaultDesignKitLoader = class {
|
|
|
15560
15731
|
if (!e.isDirectory()) continue;
|
|
15561
15732
|
const kitFile = path13.join(dir, e.name, KIT_FILE);
|
|
15562
15733
|
try {
|
|
15563
|
-
const raw = await
|
|
15734
|
+
const raw = await fs3.readFile(kitFile, "utf8");
|
|
15564
15735
|
const fm = parseKitFrontmatter(raw);
|
|
15565
15736
|
const id = fm.id ?? e.name;
|
|
15566
15737
|
if (!fm.name) continue;
|
|
@@ -15621,7 +15792,7 @@ var DefaultDesignKitLoader = class {
|
|
|
15621
15792
|
if (cached !== void 0) return cached;
|
|
15622
15793
|
const m = await this.find(id);
|
|
15623
15794
|
if (!m) throw new Error(`Design kit "${id}" not found`);
|
|
15624
|
-
const raw = await
|
|
15795
|
+
const raw = await fs3.readFile(m.path, "utf8");
|
|
15625
15796
|
const body = narrowStackSections(stripFrontmatter(raw), stack);
|
|
15626
15797
|
this.bodyCache.set(key, body);
|
|
15627
15798
|
return body;
|
|
@@ -15634,7 +15805,7 @@ var DefaultDesignKitLoader = class {
|
|
|
15634
15805
|
if (m) {
|
|
15635
15806
|
const tokensPath = path13.join(path13.dirname(m.path), TOKENS_FILE);
|
|
15636
15807
|
try {
|
|
15637
|
-
const raw = await
|
|
15808
|
+
const raw = await fs3.readFile(tokensPath, "utf8");
|
|
15638
15809
|
const parsed = JSON.parse(raw);
|
|
15639
15810
|
tokens = parsed;
|
|
15640
15811
|
} catch {
|
|
@@ -15692,7 +15863,7 @@ function _resetDesignKitLoaderMemo() {
|
|
|
15692
15863
|
|
|
15693
15864
|
// src/execution/design-project-store.ts
|
|
15694
15865
|
import { existsSync as existsSync2 } from "node:fs";
|
|
15695
|
-
import * as
|
|
15866
|
+
import * as fs4 from "node:fs/promises";
|
|
15696
15867
|
import * as path14 from "node:path";
|
|
15697
15868
|
var DESIGN_DIR = ".design";
|
|
15698
15869
|
function designProjectDir(projectRoot) {
|
|
@@ -15705,7 +15876,7 @@ async function loadProjectDesignRules(projectRoot) {
|
|
|
15705
15876
|
let rules;
|
|
15706
15877
|
for (const name of RULE_FILES) {
|
|
15707
15878
|
try {
|
|
15708
|
-
const txt = await
|
|
15879
|
+
const txt = await fs4.readFile(path14.join(designProjectDir(projectRoot), name), "utf8");
|
|
15709
15880
|
if (txt.trim()) {
|
|
15710
15881
|
rules = txt.trim();
|
|
15711
15882
|
break;
|
|
@@ -15726,7 +15897,7 @@ function parseOverrides(value) {
|
|
|
15726
15897
|
}
|
|
15727
15898
|
async function loadActiveKit(projectRoot) {
|
|
15728
15899
|
try {
|
|
15729
|
-
const raw = await
|
|
15900
|
+
const raw = await fs4.readFile(path14.join(designProjectDir(projectRoot), "active.json"), "utf8");
|
|
15730
15901
|
const parsed = JSON.parse(raw);
|
|
15731
15902
|
if (parsed && typeof parsed.kit === "string") {
|
|
15732
15903
|
return {
|
|
@@ -15759,11 +15930,11 @@ function applyTokenOverrides(tokens, overrides) {
|
|
|
15759
15930
|
}
|
|
15760
15931
|
async function ensureDesignDir(projectRoot) {
|
|
15761
15932
|
const dir = designProjectDir(projectRoot);
|
|
15762
|
-
await
|
|
15933
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
15763
15934
|
const gi = path14.join(dir, ".gitignore");
|
|
15764
15935
|
if (!existsSync2(gi)) {
|
|
15765
15936
|
try {
|
|
15766
|
-
await
|
|
15937
|
+
await fs4.writeFile(gi, "*\n");
|
|
15767
15938
|
} catch {
|
|
15768
15939
|
}
|
|
15769
15940
|
}
|
|
@@ -15774,11 +15945,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
|
|
|
15774
15945
|
const dir = await ensureDesignDir(projectRoot);
|
|
15775
15946
|
const record = { kit, stack: stack ?? null };
|
|
15776
15947
|
if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
|
|
15777
|
-
await
|
|
15948
|
+
await fs4.writeFile(path14.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
15778
15949
|
`);
|
|
15779
15950
|
const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
|
|
15780
15951
|
`;
|
|
15781
|
-
await
|
|
15952
|
+
await fs4.appendFile(path14.join(dir, "decisions.md"), line);
|
|
15782
15953
|
} catch {
|
|
15783
15954
|
}
|
|
15784
15955
|
}
|
|
@@ -15794,10 +15965,10 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
15794
15965
|
const dir = await ensureDesignDir(projectRoot);
|
|
15795
15966
|
const record = { kit: active.kit, stack: active.stack ?? null };
|
|
15796
15967
|
if (Object.keys(merged).length > 0) record.overrides = merged;
|
|
15797
|
-
await
|
|
15968
|
+
await fs4.writeFile(path14.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
15798
15969
|
`);
|
|
15799
15970
|
const keys = Object.keys(patch).join(",");
|
|
15800
|
-
await
|
|
15971
|
+
await fs4.appendFile(
|
|
15801
15972
|
path14.join(dir, "decisions.md"),
|
|
15802
15973
|
`- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
|
|
15803
15974
|
`
|
|
@@ -15808,7 +15979,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
15808
15979
|
}
|
|
15809
15980
|
async function clearPersistedActiveKit(projectRoot) {
|
|
15810
15981
|
try {
|
|
15811
|
-
await
|
|
15982
|
+
await fs4.rm(path14.join(designProjectDir(projectRoot), "active.json"), { force: true });
|
|
15812
15983
|
} catch {
|
|
15813
15984
|
}
|
|
15814
15985
|
}
|
|
@@ -15924,12 +16095,12 @@ function verifyFiles(tokens, files) {
|
|
|
15924
16095
|
const violations = [];
|
|
15925
16096
|
let onPalette = 0;
|
|
15926
16097
|
let offPalette = 0;
|
|
15927
|
-
for (const { path:
|
|
16098
|
+
for (const { path: path28, text } of files) {
|
|
15928
16099
|
const lines = text.split("\n");
|
|
15929
16100
|
lines.forEach((lineText, i) => {
|
|
15930
16101
|
const lineNo = i + 1;
|
|
15931
16102
|
const flag = (snippet, reason) => {
|
|
15932
|
-
violations.push({ file:
|
|
16103
|
+
violations.push({ file: path28, line: lineNo, snippet: snippet.slice(0, 80), reason });
|
|
15933
16104
|
};
|
|
15934
16105
|
for (const re of [HEX_RE, FUNC_COLOR_RE]) {
|
|
15935
16106
|
re.lastIndex = 0;
|
|
@@ -16433,11 +16604,25 @@ function buildRecoveryStrategies(opts) {
|
|
|
16433
16604
|
},
|
|
16434
16605
|
{
|
|
16435
16606
|
label: "rate_limit_backoff",
|
|
16436
|
-
async attempt(err) {
|
|
16607
|
+
async attempt(err, ctx) {
|
|
16437
16608
|
if (!(err instanceof ProviderError) || err.kind !== "rate_limit") return null;
|
|
16438
16609
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
16439
16610
|
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
16440
|
-
|
|
16611
|
+
if (ctx?.signal) {
|
|
16612
|
+
await new Promise((resolve12) => {
|
|
16613
|
+
if (ctx.signal.aborted) {
|
|
16614
|
+
resolve12();
|
|
16615
|
+
return;
|
|
16616
|
+
}
|
|
16617
|
+
const timer = setTimeout(resolve12, delay);
|
|
16618
|
+
ctx.signal.addEventListener("abort", () => {
|
|
16619
|
+
clearTimeout(timer);
|
|
16620
|
+
resolve12();
|
|
16621
|
+
}, { once: true });
|
|
16622
|
+
});
|
|
16623
|
+
} else {
|
|
16624
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
16625
|
+
}
|
|
16441
16626
|
return { action: "retry", reason: "rate_limit_backoff" };
|
|
16442
16627
|
}
|
|
16443
16628
|
},
|
|
@@ -17170,8 +17355,8 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
17170
17355
|
});
|
|
17171
17356
|
await saveGoal(this.goalPath, withEntry, this.opts.events);
|
|
17172
17357
|
try {
|
|
17173
|
-
const { unlink:
|
|
17174
|
-
await
|
|
17358
|
+
const { unlink: unlink11 } = await import("node:fs/promises");
|
|
17359
|
+
await unlink11(this.goalPath);
|
|
17175
17360
|
} catch {
|
|
17176
17361
|
}
|
|
17177
17362
|
this.opts.onEternalStop?.();
|
|
@@ -17188,8 +17373,8 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
17188
17373
|
await saveGoal(this.goalPath, abandoned, this.opts.events);
|
|
17189
17374
|
}
|
|
17190
17375
|
try {
|
|
17191
|
-
const { unlink:
|
|
17192
|
-
await
|
|
17376
|
+
const { unlink: unlink11 } = await import("node:fs/promises");
|
|
17377
|
+
await unlink11(this.goalPath);
|
|
17193
17378
|
} catch {
|
|
17194
17379
|
}
|
|
17195
17380
|
this.opts.onEternalStop?.();
|
|
@@ -17318,16 +17503,18 @@ var IntelligentCompactor = class {
|
|
|
17318
17503
|
eliseThreshold;
|
|
17319
17504
|
summarizerPrompt;
|
|
17320
17505
|
summarizerModel;
|
|
17506
|
+
oneShotOrchestrator;
|
|
17321
17507
|
constructor(opts) {
|
|
17322
17508
|
this.provider = opts.provider;
|
|
17323
|
-
this.warnThreshold = opts.warnThreshold ?? 0.
|
|
17324
|
-
this.softThreshold = opts.softThreshold ?? 0.
|
|
17325
|
-
this.hardThreshold = opts.hardThreshold ?? 0.
|
|
17509
|
+
this.warnThreshold = opts.warnThreshold ?? 0.5;
|
|
17510
|
+
this.softThreshold = opts.softThreshold ?? 0.65;
|
|
17511
|
+
this.hardThreshold = opts.hardThreshold ?? 0.8;
|
|
17326
17512
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
17327
17513
|
this.preserveK = opts.preserveK ?? 4;
|
|
17328
|
-
this.eliseThreshold = opts.eliseThreshold ??
|
|
17514
|
+
this.eliseThreshold = opts.eliseThreshold ?? 300;
|
|
17329
17515
|
this.summarizerPrompt = opts.summarizerPrompt ?? readBundledInstructionText("llm/intelligent-compactor-summarizer.md");
|
|
17330
17516
|
this.summarizerModel = opts.summarizerModel;
|
|
17517
|
+
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
17331
17518
|
}
|
|
17332
17519
|
async compact(ctx, opts = {}) {
|
|
17333
17520
|
const beforeTokens = estimateMessages(ctx.messages);
|
|
@@ -17340,7 +17527,7 @@ var IntelligentCompactor = class {
|
|
|
17340
17527
|
let collapsedDigest;
|
|
17341
17528
|
if (aggressive) {
|
|
17342
17529
|
const phase2 = await this.summarizeAncientTurns(ctx);
|
|
17343
|
-
|
|
17530
|
+
reductions.push({ phase: "summary", saved: Math.max(0, phase2.saved) });
|
|
17344
17531
|
collapsedDigest = phase2.digest;
|
|
17345
17532
|
} else if (load >= this.warnThreshold) {
|
|
17346
17533
|
const saved2 = this.elide(ctx);
|
|
@@ -17394,9 +17581,15 @@ var IntelligentCompactor = class {
|
|
|
17394
17581
|
} catch {
|
|
17395
17582
|
summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
17396
17583
|
}
|
|
17584
|
+
const smartDigest = buildSmartDigest(toSummarize);
|
|
17585
|
+
const enriched = smartDigest ? `[CRITICAL]
|
|
17586
|
+
${smartDigest}
|
|
17587
|
+
|
|
17588
|
+
[SUMMARY]
|
|
17589
|
+
${summaryText}` : summaryText;
|
|
17397
17590
|
const summaryMsg = {
|
|
17398
17591
|
role: "system",
|
|
17399
|
-
content: `[prior_turns_summary: ${
|
|
17592
|
+
content: `[prior_turns_summary: ${enriched}]`
|
|
17400
17593
|
};
|
|
17401
17594
|
const summaryTokens = estimateMessages([summaryMsg]);
|
|
17402
17595
|
const tail = ctx.messages.slice(boundary);
|
|
@@ -17404,27 +17597,41 @@ var IntelligentCompactor = class {
|
|
|
17404
17597
|
return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
|
|
17405
17598
|
}
|
|
17406
17599
|
async callSummarizer(messages, ctx) {
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17600
|
+
if (this.oneShotOrchestrator) {
|
|
17601
|
+
const result = await this.oneShotOrchestrator.call({
|
|
17602
|
+
system: this.summarizerPrompt,
|
|
17603
|
+
messages: [{ role: "user", content: this.messagesToSummary(messages) }],
|
|
17604
|
+
model: this.summarizerModel ?? "deepseek-chat",
|
|
17605
|
+
timeoutMs: 3e4,
|
|
17606
|
+
maxTokens: 1024,
|
|
17607
|
+
signal: ctx.signal,
|
|
17608
|
+
fallbackProfile: "summary"
|
|
17609
|
+
});
|
|
17610
|
+
if (result.text && !result.error) return result.text;
|
|
17611
|
+
} else {
|
|
17612
|
+
const prompt = [
|
|
17613
|
+
{ type: "text", text: this.summarizerPrompt },
|
|
17614
|
+
{ type: "text", text: "\n\nConversation to summarize:\n" },
|
|
17615
|
+
...this.messagesToText(messages)
|
|
17616
|
+
];
|
|
17617
|
+
const req = {
|
|
17618
|
+
model: this.summarizerModel ?? ctx.model,
|
|
17619
|
+
system: prompt,
|
|
17620
|
+
messages: [],
|
|
17621
|
+
maxTokens: 1024
|
|
17622
|
+
};
|
|
17623
|
+
const ac = ctx.signal ? void 0 : new AbortController();
|
|
17624
|
+
const signal = ctx.signal ?? ac?.signal;
|
|
17625
|
+
let res;
|
|
17626
|
+
try {
|
|
17627
|
+
res = await this.provider.complete(req, { signal });
|
|
17628
|
+
} finally {
|
|
17629
|
+
ac?.abort();
|
|
17630
|
+
}
|
|
17631
|
+
const textBlocks = res.content.filter(isTextBlock);
|
|
17632
|
+
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
17425
17633
|
}
|
|
17426
|
-
|
|
17427
|
-
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
17634
|
+
return "(empty summary)";
|
|
17428
17635
|
}
|
|
17429
17636
|
messagesToText(messages) {
|
|
17430
17637
|
const lines = [];
|
|
@@ -17441,6 +17648,22 @@ var IntelligentCompactor = class {
|
|
|
17441
17648
|
}
|
|
17442
17649
|
return [{ type: "text", text: lines.join("\n") }];
|
|
17443
17650
|
}
|
|
17651
|
+
/** Compact string summary of messages (no TextBlock wrapper). */
|
|
17652
|
+
messagesToSummary(messages) {
|
|
17653
|
+
const lines = [];
|
|
17654
|
+
for (const m of messages) {
|
|
17655
|
+
const role = m.role.padEnd(10, " ");
|
|
17656
|
+
if (typeof m.content === "string") {
|
|
17657
|
+
lines.push(`[${role}]: ${m.content.slice(0, 500)}`);
|
|
17658
|
+
} else if (Array.isArray(m.content)) {
|
|
17659
|
+
const textParts = m.content.filter(isTextBlock).map((b) => b.text);
|
|
17660
|
+
if (textParts.length > 0) {
|
|
17661
|
+
lines.push(`[${role}]: ${textParts.join(" ").slice(0, 500)}`);
|
|
17662
|
+
}
|
|
17663
|
+
}
|
|
17664
|
+
}
|
|
17665
|
+
return lines.join("\n");
|
|
17666
|
+
}
|
|
17444
17667
|
};
|
|
17445
17668
|
|
|
17446
17669
|
// src/execution/parallel-eternal-engine.ts
|
|
@@ -18396,6 +18619,7 @@ var LLMSelector = class {
|
|
|
18396
18619
|
maxContextTokens;
|
|
18397
18620
|
systemPrompt;
|
|
18398
18621
|
maxOutputTokens;
|
|
18622
|
+
oneShotOrchestrator;
|
|
18399
18623
|
constructor(opts) {
|
|
18400
18624
|
this.provider = opts.provider;
|
|
18401
18625
|
this.model = opts.model ?? "unknown";
|
|
@@ -18407,6 +18631,7 @@ var LLMSelector = class {
|
|
|
18407
18631
|
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
18408
18632
|
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
18409
18633
|
this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
|
|
18634
|
+
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
18410
18635
|
}
|
|
18411
18636
|
async select(messages, maxToKeep) {
|
|
18412
18637
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
@@ -18430,12 +18655,23 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
18430
18655
|
let raw;
|
|
18431
18656
|
const ac = new AbortController();
|
|
18432
18657
|
try {
|
|
18433
|
-
|
|
18434
|
-
|
|
18435
|
-
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18658
|
+
if (this.oneShotOrchestrator) {
|
|
18659
|
+
const result = await this.oneShotOrchestrator.call({
|
|
18660
|
+
system: systemText + budgetInstruction,
|
|
18661
|
+
userPrompt: historyText,
|
|
18662
|
+
maxTokens: this.maxOutputTokens,
|
|
18663
|
+
timeoutMs: 3e4
|
|
18664
|
+
});
|
|
18665
|
+
if (result.error) throw new Error(result.error);
|
|
18666
|
+
raw = result.text;
|
|
18667
|
+
} else {
|
|
18668
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
18669
|
+
const res = await this.provider.complete(req, {
|
|
18670
|
+
signal: AbortSignal.any([ac.signal, timeoutSignal])
|
|
18671
|
+
});
|
|
18672
|
+
const textBlocks = res.content.filter(isTextBlock);
|
|
18673
|
+
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
18674
|
+
}
|
|
18439
18675
|
} catch (err) {
|
|
18440
18676
|
if (err instanceof Error) {
|
|
18441
18677
|
console.warn(
|
|
@@ -18555,12 +18791,12 @@ var SelectiveCompactor = class {
|
|
|
18555
18791
|
constructor(opts) {
|
|
18556
18792
|
this.provider = opts.provider;
|
|
18557
18793
|
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
|
|
18558
|
-
this.warnThreshold = opts.warnThreshold ?? 0.
|
|
18559
|
-
this.softThreshold = opts.softThreshold ?? 0.
|
|
18560
|
-
this.hardThreshold = opts.hardThreshold ?? 0.
|
|
18794
|
+
this.warnThreshold = opts.warnThreshold ?? 0.5;
|
|
18795
|
+
this.softThreshold = opts.softThreshold ?? 0.65;
|
|
18796
|
+
this.hardThreshold = opts.hardThreshold ?? 0.8;
|
|
18561
18797
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
18562
18798
|
this.preserveK = opts.preserveK ?? 4;
|
|
18563
|
-
this.eliseThreshold = opts.eliseThreshold ??
|
|
18799
|
+
this.eliseThreshold = opts.eliseThreshold ?? 300;
|
|
18564
18800
|
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
|
|
18565
18801
|
if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
18566
18802
|
console.warn(
|
|
@@ -18753,7 +18989,7 @@ Summarize the following message range:`;
|
|
|
18753
18989
|
};
|
|
18754
18990
|
|
|
18755
18991
|
// src/execution/skill-loader.ts
|
|
18756
|
-
import * as
|
|
18992
|
+
import * as fs5 from "node:fs/promises";
|
|
18757
18993
|
import * as path15 from "node:path";
|
|
18758
18994
|
|
|
18759
18995
|
// src/skills/foreign-sources.ts
|
|
@@ -18984,7 +19220,7 @@ async function entryIsDirectory(dir, entry) {
|
|
|
18984
19220
|
if (entry.isDirectory()) return true;
|
|
18985
19221
|
if (entry.isSymbolicLink()) {
|
|
18986
19222
|
try {
|
|
18987
|
-
return (await
|
|
19223
|
+
return (await fs5.stat(path15.join(dir, entry.name))).isDirectory();
|
|
18988
19224
|
} catch {
|
|
18989
19225
|
return false;
|
|
18990
19226
|
}
|
|
@@ -19027,12 +19263,12 @@ var DefaultSkillLoader = class {
|
|
|
19027
19263
|
const seen = /* @__PURE__ */ new Set();
|
|
19028
19264
|
for (const { dir, source, originTool } of this.dirs) {
|
|
19029
19265
|
try {
|
|
19030
|
-
const entries = await
|
|
19266
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
19031
19267
|
for (const e of entries) {
|
|
19032
19268
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
19033
19269
|
const skillFile = path15.join(dir, e.name, "SKILL.md");
|
|
19034
19270
|
try {
|
|
19035
|
-
const raw = await
|
|
19271
|
+
const raw = await fs5.readFile(skillFile, "utf8");
|
|
19036
19272
|
const fm = parseSkillFrontmatter(raw);
|
|
19037
19273
|
if (!fm.name || !fm.description) continue;
|
|
19038
19274
|
if (!isValidSkillNameFormat(fm.name)) continue;
|
|
@@ -19104,7 +19340,7 @@ var DefaultSkillLoader = class {
|
|
|
19104
19340
|
if (cached !== void 0) return cached;
|
|
19105
19341
|
const m = await this.find(name);
|
|
19106
19342
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
19107
|
-
const body = await
|
|
19343
|
+
const body = await fs5.readFile(m.path, "utf8");
|
|
19108
19344
|
this.bodyCache.set(key, body);
|
|
19109
19345
|
return body;
|
|
19110
19346
|
}
|
|
@@ -19117,9 +19353,9 @@ var DefaultSkillLoader = class {
|
|
|
19117
19353
|
const savePath = path15.join(path15.dirname(m.path), "SKILL.save.md");
|
|
19118
19354
|
let result;
|
|
19119
19355
|
try {
|
|
19120
|
-
result = await
|
|
19356
|
+
result = await fs5.readFile(savePath, "utf8");
|
|
19121
19357
|
} catch {
|
|
19122
|
-
const full = await
|
|
19358
|
+
const full = await fs5.readFile(m.path, "utf8");
|
|
19123
19359
|
const body = stripFrontmatter2(full);
|
|
19124
19360
|
const compact = compactSkillBody(body);
|
|
19125
19361
|
if (compact) {
|
|
@@ -19147,11 +19383,11 @@ function parseDescriptionFromText(desc) {
|
|
|
19147
19383
|
}
|
|
19148
19384
|
|
|
19149
19385
|
// src/execution/prompt-loader.ts
|
|
19150
|
-
import * as
|
|
19386
|
+
import * as fs7 from "node:fs/promises";
|
|
19151
19387
|
import * as path17 from "node:path";
|
|
19152
19388
|
|
|
19153
19389
|
// src/storage/prompt-store.ts
|
|
19154
|
-
import * as
|
|
19390
|
+
import * as fs6 from "node:fs/promises";
|
|
19155
19391
|
import * as path16 from "node:path";
|
|
19156
19392
|
var SCHEMA_VERSION = 2;
|
|
19157
19393
|
function migratePromptEntry(raw) {
|
|
@@ -19213,12 +19449,12 @@ var DefaultPromptStore = class {
|
|
|
19213
19449
|
await ensureDir(this.dir);
|
|
19214
19450
|
const entries = [];
|
|
19215
19451
|
try {
|
|
19216
|
-
const files = await
|
|
19452
|
+
const files = await fs6.readdir(this.dir);
|
|
19217
19453
|
for (const file of files) {
|
|
19218
19454
|
if (!file.endsWith(".json")) continue;
|
|
19219
19455
|
try {
|
|
19220
19456
|
const raw = JSON.parse(
|
|
19221
|
-
await
|
|
19457
|
+
await fs6.readFile(path16.join(this.dir, file), "utf8")
|
|
19222
19458
|
);
|
|
19223
19459
|
const migrated = migratePromptEntry(raw.entry);
|
|
19224
19460
|
if (migrated) entries.push(migrated);
|
|
@@ -19234,7 +19470,7 @@ var DefaultPromptStore = class {
|
|
|
19234
19470
|
async get(id) {
|
|
19235
19471
|
const file = path16.join(this.dir, `${id}.json`);
|
|
19236
19472
|
try {
|
|
19237
|
-
const raw = JSON.parse(await
|
|
19473
|
+
const raw = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
19238
19474
|
return migratePromptEntry(raw.entry);
|
|
19239
19475
|
} catch {
|
|
19240
19476
|
return null;
|
|
@@ -19249,7 +19485,7 @@ var DefaultPromptStore = class {
|
|
|
19249
19485
|
async delete(id) {
|
|
19250
19486
|
const file = path16.join(this.dir, `${id}.json`);
|
|
19251
19487
|
try {
|
|
19252
|
-
await
|
|
19488
|
+
await fs6.unlink(file);
|
|
19253
19489
|
return true;
|
|
19254
19490
|
} catch {
|
|
19255
19491
|
return false;
|
|
@@ -19449,7 +19685,7 @@ var DefaultPromptLoader = class {
|
|
|
19449
19685
|
const files = await walkJson(dir);
|
|
19450
19686
|
for (const file of files) {
|
|
19451
19687
|
try {
|
|
19452
|
-
const parsed = JSON.parse(await
|
|
19688
|
+
const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
19453
19689
|
const migrated = migratePromptEntry(parsed);
|
|
19454
19690
|
if (migrated) out.push({ ...migrated, source: "builtin" });
|
|
19455
19691
|
} catch {
|
|
@@ -19463,7 +19699,7 @@ async function walkJson(dir) {
|
|
|
19463
19699
|
const out = [];
|
|
19464
19700
|
let entries;
|
|
19465
19701
|
try {
|
|
19466
|
-
entries = await
|
|
19702
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
19467
19703
|
} catch {
|
|
19468
19704
|
return out;
|
|
19469
19705
|
}
|
|
@@ -19640,7 +19876,8 @@ var ProviderBackedCompactor = class {
|
|
|
19640
19876
|
}
|
|
19641
19877
|
return new IntelligentCompactor({
|
|
19642
19878
|
...common,
|
|
19643
|
-
summarizerModel: this.opts.summarizerModel
|
|
19879
|
+
summarizerModel: this.opts.summarizerModel,
|
|
19880
|
+
oneShotOrchestrator: this.opts.oneShotOrchestrator
|
|
19644
19881
|
});
|
|
19645
19882
|
}
|
|
19646
19883
|
};
|
|
@@ -19655,9 +19892,12 @@ function readPolicy(ctx) {
|
|
|
19655
19892
|
// src/execution/tool-executor.ts
|
|
19656
19893
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
19657
19894
|
import { isDeepStrictEqual } from "node:util";
|
|
19658
|
-
import * as
|
|
19895
|
+
import * as fs8 from "node:fs/promises";
|
|
19659
19896
|
import * as path18 from "node:path";
|
|
19660
19897
|
|
|
19898
|
+
// src/types/tool-executor.ts
|
|
19899
|
+
var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
|
|
19900
|
+
|
|
19661
19901
|
// src/types/tool-markers.ts
|
|
19662
19902
|
var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
|
|
19663
19903
|
|
|
@@ -19774,6 +20014,18 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
19774
20014
|
* Returns the execution results and the remaining output budget.
|
|
19775
20015
|
*/
|
|
19776
20016
|
async executeBatch(toolUses, ctx, strategy) {
|
|
20017
|
+
return this.withGovernedExecutionBridge(
|
|
20018
|
+
ctx,
|
|
20019
|
+
() => this.executeBatchInternal(toolUses, ctx, strategy)
|
|
20020
|
+
);
|
|
20021
|
+
}
|
|
20022
|
+
/**
|
|
20023
|
+
* Internal batch implementation. Nested meta-tool calls enter here through
|
|
20024
|
+
* the governed bridge so they traverse the same validation, hooks,
|
|
20025
|
+
* permission/capability, timeout, scrubbing, and audit path without
|
|
20026
|
+
* replacing the bridge that is already active for the outer call.
|
|
20027
|
+
*/
|
|
20028
|
+
async executeBatchInternal(toolUses, ctx, strategy) {
|
|
19777
20029
|
let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
|
|
19778
20030
|
const runOne = async (use0) => {
|
|
19779
20031
|
const start = Date.now();
|
|
@@ -19944,8 +20196,9 @@ ${errorDetails}`,
|
|
|
19944
20196
|
);
|
|
19945
20197
|
if (post.additionalContext) {
|
|
19946
20198
|
if (post.contextAs === "separate") {
|
|
19947
|
-
|
|
19948
|
-
|
|
20199
|
+
ctx.pendingPostToolContext = ctx.pendingPostToolContext ? `${ctx.pendingPostToolContext}
|
|
20200
|
+
|
|
20201
|
+
${post.additionalContext}` : post.additionalContext;
|
|
19949
20202
|
} else {
|
|
19950
20203
|
const appended = `
|
|
19951
20204
|
|
|
@@ -20045,14 +20298,56 @@ ${post.additionalContext}`;
|
|
|
20045
20298
|
};
|
|
20046
20299
|
}
|
|
20047
20300
|
/**
|
|
20048
|
-
*
|
|
20049
|
-
*
|
|
20050
|
-
*
|
|
20051
|
-
*
|
|
20301
|
+
* Install the executor-owned bridge used by meta-tools for nested calls.
|
|
20302
|
+
* The bridge deliberately returns the governed, scrubbed ToolResultBlock
|
|
20303
|
+
* content rather than the raw tool value. This prevents a meta-tool from
|
|
20304
|
+
* bypassing output controls while preserving structured success/failure.
|
|
20305
|
+
*/
|
|
20306
|
+
async withGovernedExecutionBridge(ctx, run) {
|
|
20307
|
+
const previous = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
20308
|
+
if (typeof previous === "function") return run();
|
|
20309
|
+
const bridge = async (toolName, input) => {
|
|
20310
|
+
const nestedUse = {
|
|
20311
|
+
type: "tool_use",
|
|
20312
|
+
id: `nested-${randomUUID10()}`,
|
|
20313
|
+
name: toolName,
|
|
20314
|
+
input
|
|
20315
|
+
};
|
|
20316
|
+
const batch = await this.executeBatchInternal([nestedUse], ctx, "sequential");
|
|
20317
|
+
const output = batch.outputs[0];
|
|
20318
|
+
if (!output) {
|
|
20319
|
+
return { success: false, error: `tool "${toolName}" produced no result` };
|
|
20320
|
+
}
|
|
20321
|
+
if (output.result.type === "tool_confirm_pending") {
|
|
20322
|
+
return {
|
|
20323
|
+
success: false,
|
|
20324
|
+
error: `tool "${toolName}" requires separate confirmation; call it directly`
|
|
20325
|
+
};
|
|
20326
|
+
}
|
|
20327
|
+
if (output.result.is_error) {
|
|
20328
|
+
return { success: false, error: String(output.result.content) };
|
|
20329
|
+
}
|
|
20330
|
+
return { success: true, result: output.result.content };
|
|
20331
|
+
};
|
|
20332
|
+
ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY] = bridge;
|
|
20333
|
+
try {
|
|
20334
|
+
return await run();
|
|
20335
|
+
} finally {
|
|
20336
|
+
if (previous === void 0) delete ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
20337
|
+
else ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY] = previous;
|
|
20338
|
+
}
|
|
20339
|
+
}
|
|
20340
|
+
/**
|
|
20341
|
+
* Execute a single tool with timeout and output capping after its permission
|
|
20342
|
+
* decision has already been resolved by executeBatch/the confirmation flow.
|
|
20343
|
+
* Also installs the governed bridge because confirmed tools are re-run
|
|
20344
|
+
* through this method after a pending confirmation.
|
|
20052
20345
|
*/
|
|
20053
20346
|
async executeTool(tool, use, ctx, budget) {
|
|
20054
|
-
|
|
20055
|
-
|
|
20347
|
+
return this.withGovernedExecutionBridge(ctx, async () => {
|
|
20348
|
+
const text = await this.produceToolOutput(tool, use, ctx, budget);
|
|
20349
|
+
return this.settleToolOutput(tool, use, text, budget);
|
|
20350
|
+
});
|
|
20056
20351
|
}
|
|
20057
20352
|
/**
|
|
20058
20353
|
* Async "produce" phase: run the tool, serialize, scrub, and spill an
|
|
@@ -20407,11 +20702,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
20407
20702
|
}
|
|
20408
20703
|
try {
|
|
20409
20704
|
const dir = path18.join(wstackGlobalRoot(), "tool-output");
|
|
20410
|
-
await
|
|
20705
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
20411
20706
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
20412
20707
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
20413
20708
|
const filePath = path18.join(dir, `${stamp}-${safeTool}-${randomUUID10()}.log`);
|
|
20414
|
-
await
|
|
20709
|
+
await fs8.writeFile(filePath, content, "utf8");
|
|
20415
20710
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
20416
20711
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
20417
20712
|
const previewBytes = Math.min(
|
|
@@ -20467,9 +20762,9 @@ function roughEstimate(messages) {
|
|
|
20467
20762
|
}
|
|
20468
20763
|
function createContextManagerTool(opts = {}) {
|
|
20469
20764
|
const minCompactThreshold = opts.minCompactThreshold ?? 0;
|
|
20470
|
-
const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ??
|
|
20765
|
+
const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 1e3;
|
|
20471
20766
|
const configuredMaxContext = opts.maxContext;
|
|
20472
|
-
const compactThresholdFraction = opts.compactThresholdFraction ?? 0.
|
|
20767
|
+
const compactThresholdFraction = opts.compactThresholdFraction ?? 0.4;
|
|
20473
20768
|
let lastNoopTokens = 0;
|
|
20474
20769
|
return {
|
|
20475
20770
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
@@ -21107,17 +21402,17 @@ function codexModelMeta(id) {
|
|
|
21107
21402
|
}
|
|
21108
21403
|
|
|
21109
21404
|
// src/models/mode-store.ts
|
|
21110
|
-
import * as
|
|
21405
|
+
import * as fs9 from "node:fs/promises";
|
|
21111
21406
|
import * as path21 from "node:path";
|
|
21112
21407
|
|
|
21113
21408
|
// src/types/mode-prompts.ts
|
|
21114
|
-
import { readFileSync as
|
|
21409
|
+
import { readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
|
|
21115
21410
|
import * as path20 from "node:path";
|
|
21116
21411
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
21117
21412
|
function modePrompt(id) {
|
|
21118
21413
|
for (const dir of modePromptDirCandidates()) {
|
|
21119
21414
|
try {
|
|
21120
|
-
return
|
|
21415
|
+
return readFileSync4(path20.join(dir, `${id}.md`), "utf8").trimEnd();
|
|
21121
21416
|
} catch {
|
|
21122
21417
|
}
|
|
21123
21418
|
}
|
|
@@ -21134,7 +21429,7 @@ function modePromptDirCandidates() {
|
|
|
21134
21429
|
}
|
|
21135
21430
|
function isDirectory3(candidate) {
|
|
21136
21431
|
try {
|
|
21137
|
-
return
|
|
21432
|
+
return statSync4(candidate).isDirectory();
|
|
21138
21433
|
} catch {
|
|
21139
21434
|
return false;
|
|
21140
21435
|
}
|
|
@@ -21361,7 +21656,7 @@ var DefaultModeStore = class {
|
|
|
21361
21656
|
async loadActiveMode() {
|
|
21362
21657
|
try {
|
|
21363
21658
|
const configPath = path21.join(this.configDir, "mode.json");
|
|
21364
|
-
const content = await
|
|
21659
|
+
const content = await fs9.readFile(configPath, "utf8");
|
|
21365
21660
|
const data = JSON.parse(content);
|
|
21366
21661
|
this.activeModeId = data.activeMode ?? null;
|
|
21367
21662
|
} catch {
|
|
@@ -21370,7 +21665,7 @@ var DefaultModeStore = class {
|
|
|
21370
21665
|
}
|
|
21371
21666
|
async saveActiveMode() {
|
|
21372
21667
|
try {
|
|
21373
|
-
await
|
|
21668
|
+
await fs9.mkdir(this.configDir, { recursive: true });
|
|
21374
21669
|
const configPath = path21.join(this.configDir, "mode.json");
|
|
21375
21670
|
await atomicWrite(
|
|
21376
21671
|
configPath,
|
|
@@ -21383,13 +21678,13 @@ var DefaultModeStore = class {
|
|
|
21383
21678
|
async function loadProjectModes(modesDir) {
|
|
21384
21679
|
const modes = [];
|
|
21385
21680
|
try {
|
|
21386
|
-
const entries = await
|
|
21681
|
+
const entries = await fs9.readdir(modesDir);
|
|
21387
21682
|
for (const entry of entries) {
|
|
21388
21683
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
21389
21684
|
const filePath = path21.join(modesDir, entry);
|
|
21390
|
-
const
|
|
21391
|
-
if (!
|
|
21392
|
-
const content = await
|
|
21685
|
+
const stat12 = await fs9.stat(filePath);
|
|
21686
|
+
if (!stat12.isFile()) continue;
|
|
21687
|
+
const content = await fs9.readFile(filePath, "utf8");
|
|
21393
21688
|
const id = path21.basename(entry, path21.extname(entry));
|
|
21394
21689
|
modes.push({
|
|
21395
21690
|
id,
|
|
@@ -21407,7 +21702,7 @@ async function loadUserModes(modesDir) {
|
|
|
21407
21702
|
const modes = [];
|
|
21408
21703
|
try {
|
|
21409
21704
|
const manifestPath = path21.join(modesDir, "modes.json");
|
|
21410
|
-
const content = await
|
|
21705
|
+
const content = await fs9.readFile(manifestPath, "utf8");
|
|
21411
21706
|
const manifest = JSON.parse(content);
|
|
21412
21707
|
for (const mode of manifest.modes) {
|
|
21413
21708
|
modes.push(mode);
|
|
@@ -21418,7 +21713,7 @@ async function loadUserModes(modesDir) {
|
|
|
21418
21713
|
}
|
|
21419
21714
|
|
|
21420
21715
|
// src/models/models-registry.ts
|
|
21421
|
-
import * as
|
|
21716
|
+
import * as fs10 from "node:fs/promises";
|
|
21422
21717
|
import * as path22 from "node:path";
|
|
21423
21718
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
21424
21719
|
var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
|
|
@@ -21660,7 +21955,7 @@ var DefaultModelsRegistry = class {
|
|
|
21660
21955
|
async readOverlayFile() {
|
|
21661
21956
|
if (!this.overlayFile) return void 0;
|
|
21662
21957
|
try {
|
|
21663
|
-
const raw = await
|
|
21958
|
+
const raw = await fs10.readFile(this.overlayFile, "utf8");
|
|
21664
21959
|
return JSON.parse(raw);
|
|
21665
21960
|
} catch {
|
|
21666
21961
|
return void 0;
|
|
@@ -21739,7 +22034,7 @@ var DefaultModelsRegistry = class {
|
|
|
21739
22034
|
}
|
|
21740
22035
|
async readCacheAt(file) {
|
|
21741
22036
|
try {
|
|
21742
|
-
const raw = await
|
|
22037
|
+
const raw = await fs10.readFile(file, "utf8");
|
|
21743
22038
|
return JSON.parse(raw);
|
|
21744
22039
|
} catch {
|
|
21745
22040
|
return void 0;
|
|
@@ -22171,7 +22466,7 @@ async function startMetricsServer(opts) {
|
|
|
22171
22466
|
const tls = opts.tls;
|
|
22172
22467
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
22173
22468
|
const host = opts.host ?? "127.0.0.1";
|
|
22174
|
-
const
|
|
22469
|
+
const path28 = opts.path ?? "/metrics";
|
|
22175
22470
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
22176
22471
|
const healthRegistry = opts.healthRegistry;
|
|
22177
22472
|
const listener = (req, res) => {
|
|
@@ -22181,7 +22476,7 @@ async function startMetricsServer(opts) {
|
|
|
22181
22476
|
return;
|
|
22182
22477
|
}
|
|
22183
22478
|
const url = req.url.split("?")[0];
|
|
22184
|
-
if (url ===
|
|
22479
|
+
if (url === path28) {
|
|
22185
22480
|
let body;
|
|
22186
22481
|
try {
|
|
22187
22482
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -22218,9 +22513,9 @@ async function startMetricsServer(opts) {
|
|
|
22218
22513
|
let server;
|
|
22219
22514
|
if (useHttps && tls) {
|
|
22220
22515
|
const { createServer } = await import("node:https");
|
|
22221
|
-
const { readFileSync:
|
|
22516
|
+
const { readFileSync: readFileSync6 } = await import("node:fs");
|
|
22222
22517
|
server = createServer(
|
|
22223
|
-
{ cert:
|
|
22518
|
+
{ cert: readFileSync6(tls.cert), key: readFileSync6(tls.key) },
|
|
22224
22519
|
listener
|
|
22225
22520
|
);
|
|
22226
22521
|
} else {
|
|
@@ -22245,7 +22540,7 @@ async function startMetricsServer(opts) {
|
|
|
22245
22540
|
const protocol = useHttps ? "https" : "http";
|
|
22246
22541
|
return {
|
|
22247
22542
|
port: boundPort,
|
|
22248
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
22543
|
+
url: `${protocol}://${host}:${boundPort}${path28}`,
|
|
22249
22544
|
close: () => new Promise((resolve12, reject) => {
|
|
22250
22545
|
server.close((err) => err ? reject(err) : resolve12());
|
|
22251
22546
|
})
|
|
@@ -22517,7 +22812,7 @@ function startOtlpTraceExporter(opts) {
|
|
|
22517
22812
|
}
|
|
22518
22813
|
|
|
22519
22814
|
// src/security/permission-policy.ts
|
|
22520
|
-
import * as
|
|
22815
|
+
import * as fs11 from "node:fs/promises";
|
|
22521
22816
|
|
|
22522
22817
|
// src/security/permission-policy-schema.ts
|
|
22523
22818
|
var TRUST_POLICY_LIMITS = Object.freeze({
|
|
@@ -22562,19 +22857,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
|
|
|
22562
22857
|
function isRecord5(value) {
|
|
22563
22858
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22564
22859
|
}
|
|
22565
|
-
function error(diagnostics, code,
|
|
22566
|
-
diagnostics.push({ severity: "error", code, path:
|
|
22860
|
+
function error(diagnostics, code, path28, message) {
|
|
22861
|
+
diagnostics.push({ severity: "error", code, path: path28, message });
|
|
22567
22862
|
}
|
|
22568
|
-
function validatePatterns(value,
|
|
22863
|
+
function validatePatterns(value, path28, diagnostics) {
|
|
22569
22864
|
if (!Array.isArray(value)) {
|
|
22570
|
-
error(diagnostics, "invalid_pattern_list",
|
|
22865
|
+
error(diagnostics, "invalid_pattern_list", path28, "must be an array of strings");
|
|
22571
22866
|
return void 0;
|
|
22572
22867
|
}
|
|
22573
22868
|
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
22574
22869
|
error(
|
|
22575
22870
|
diagnostics,
|
|
22576
22871
|
"too_many_patterns",
|
|
22577
|
-
|
|
22872
|
+
path28,
|
|
22578
22873
|
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
22579
22874
|
);
|
|
22580
22875
|
return void 0;
|
|
@@ -22582,7 +22877,7 @@ function validatePatterns(value, path29, diagnostics) {
|
|
|
22582
22877
|
const patterns = [];
|
|
22583
22878
|
const seen = /* @__PURE__ */ new Set();
|
|
22584
22879
|
for (const [index, pattern] of value.entries()) {
|
|
22585
|
-
const itemPath = `${
|
|
22880
|
+
const itemPath = `${path28}[${index}]`;
|
|
22586
22881
|
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
22587
22882
|
error(
|
|
22588
22883
|
diagnostics,
|
|
@@ -22906,7 +23201,7 @@ var DefaultPermissionPolicy = class {
|
|
|
22906
23201
|
this.policyDiagnostics = [];
|
|
22907
23202
|
this.policyInvalid = false;
|
|
22908
23203
|
try {
|
|
22909
|
-
const raw = await
|
|
23204
|
+
const raw = await fs11.readFile(this.trustFile, "utf8");
|
|
22910
23205
|
const parsed = safeParse(raw);
|
|
22911
23206
|
if (!parsed.ok) {
|
|
22912
23207
|
this.policy = {};
|
|
@@ -23306,7 +23601,12 @@ function hasCredentialAnchors(text) {
|
|
|
23306
23601
|
text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
|
|
23307
23602
|
text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
|
|
23308
23603
|
text.includes("_PASSWORD=") || // DB_PASSWORD=, ROOT_PASSWORD=, ...
|
|
23309
|
-
text.includes("mongodb://") || text.includes("mongodb+srv://") || text.includes("postgres://") || text.includes("postgresql://") || text.includes("mysql://") || text.includes("redis://")
|
|
23604
|
+
text.includes("mongodb://") || text.includes("mongodb+srv://") || text.includes("postgres://") || text.includes("postgresql://") || text.includes("mysql://") || text.includes("redis://") || // JSON-style credential keys: tool outputs often serialise objects as
|
|
23605
|
+
// raw JSON strings rather than parsed objects. The value may not start
|
|
23606
|
+
// with a recognisable prefix (sk-, ghp_, etc.), so we anchor on common
|
|
23607
|
+
// key names. The `"` prefix avoids matching prose that happens to mention
|
|
23608
|
+
// these words — JSON serialisation always quotes string keys.
|
|
23609
|
+
text.includes('"apiKey"') || text.includes('"api_key"') || text.includes('"token"') || text.includes('"secret"') || text.includes('"password"') || text.includes('"authorization"') || text.includes('"bearer"') || text.includes('"private_key"') || text.includes('"access_token"') || text.includes('"refresh_token"') || text.includes('"client_secret"');
|
|
23310
23610
|
}
|
|
23311
23611
|
var DefaultSecretScrubber = class {
|
|
23312
23612
|
scrub(text) {
|
|
@@ -23379,7 +23679,7 @@ var DefaultSecretScrubber = class {
|
|
|
23379
23679
|
|
|
23380
23680
|
// src/security/secret-vault.ts
|
|
23381
23681
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, scryptSync } from "node:crypto";
|
|
23382
|
-
import * as
|
|
23682
|
+
import * as fs12 from "node:fs";
|
|
23383
23683
|
import * as fsp12 from "node:fs/promises";
|
|
23384
23684
|
import * as path23 from "node:path";
|
|
23385
23685
|
|
|
@@ -23424,7 +23724,7 @@ function walk2(node, vault, transform) {
|
|
|
23424
23724
|
}
|
|
23425
23725
|
return out;
|
|
23426
23726
|
}
|
|
23427
|
-
var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;
|
|
23727
|
+
var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\b)/i;
|
|
23428
23728
|
var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
23429
23729
|
function isSecretField(name) {
|
|
23430
23730
|
const lc = name.toLowerCase();
|
|
@@ -23521,8 +23821,8 @@ function checkKeyFilePermissions(keyFile, opts) {
|
|
|
23521
23821
|
if (process.platform === "win32") return;
|
|
23522
23822
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
23523
23823
|
try {
|
|
23524
|
-
const
|
|
23525
|
-
const actualMode =
|
|
23824
|
+
const stat12 = fs12.statSync(keyFile);
|
|
23825
|
+
const actualMode = stat12.mode & 511;
|
|
23526
23826
|
if (actualMode !== KEY_FILE_MODE) {
|
|
23527
23827
|
warn(
|
|
23528
23828
|
`Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`
|
|
@@ -23618,16 +23918,16 @@ var DefaultSecretVault = class {
|
|
|
23618
23918
|
const oldVersion = this._keyVersion;
|
|
23619
23919
|
const newKey = randomBytes4(KEY_BYTES);
|
|
23620
23920
|
const newVersion = oldVersion + 1;
|
|
23621
|
-
|
|
23921
|
+
fs12.mkdirSync(path23.dirname(this.keyFile), { recursive: true });
|
|
23622
23922
|
const passphrase = getVaultPassphrase();
|
|
23623
23923
|
if (passphrase) {
|
|
23624
|
-
|
|
23924
|
+
fs12.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
|
|
23625
23925
|
} else {
|
|
23626
23926
|
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
23627
23927
|
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
23628
23928
|
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
23629
23929
|
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
23630
|
-
|
|
23930
|
+
fs12.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
23631
23931
|
}
|
|
23632
23932
|
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23633
23933
|
this.key = newKey;
|
|
@@ -23645,7 +23945,7 @@ var DefaultSecretVault = class {
|
|
|
23645
23945
|
const passphrase = getVaultPassphrase();
|
|
23646
23946
|
if (!passphrase || !this.key) return;
|
|
23647
23947
|
try {
|
|
23648
|
-
|
|
23948
|
+
fs12.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
|
|
23649
23949
|
mode: 384
|
|
23650
23950
|
});
|
|
23651
23951
|
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
@@ -23655,7 +23955,7 @@ var DefaultSecretVault = class {
|
|
|
23655
23955
|
loadOrCreateKey() {
|
|
23656
23956
|
if (this.key) return this.key;
|
|
23657
23957
|
try {
|
|
23658
|
-
const buf =
|
|
23958
|
+
const buf = fs12.readFileSync(this.keyFile);
|
|
23659
23959
|
if (isWrappedKeyFile(buf)) {
|
|
23660
23960
|
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
23661
23961
|
this.key = key2;
|
|
@@ -23702,15 +24002,15 @@ var DefaultSecretVault = class {
|
|
|
23702
24002
|
} catch (err) {
|
|
23703
24003
|
if (err.code !== "ENOENT") throw err;
|
|
23704
24004
|
}
|
|
23705
|
-
|
|
24005
|
+
fs12.mkdirSync(path23.dirname(this.keyFile), { recursive: true });
|
|
23706
24006
|
const key = randomBytes4(KEY_BYTES);
|
|
23707
24007
|
const passphrase = getVaultPassphrase();
|
|
23708
24008
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
23709
24009
|
try {
|
|
23710
|
-
|
|
24010
|
+
fs12.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
|
|
23711
24011
|
} catch (err) {
|
|
23712
24012
|
if (err.code !== "EEXIST") throw err;
|
|
23713
|
-
const buf =
|
|
24013
|
+
const buf = fs12.readFileSync(this.keyFile);
|
|
23714
24014
|
if (isWrappedKeyFile(buf)) {
|
|
23715
24015
|
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
23716
24016
|
this.key = winnerKey;
|
|
@@ -23984,24 +24284,24 @@ function mergeAdjacentText(blocks) {
|
|
|
23984
24284
|
}
|
|
23985
24285
|
|
|
23986
24286
|
// src/storage/config-loader.ts
|
|
23987
|
-
import * as
|
|
24287
|
+
import * as fs13 from "node:fs/promises";
|
|
23988
24288
|
import * as path25 from "node:path";
|
|
23989
24289
|
|
|
23990
24290
|
// src/types/config.ts
|
|
23991
24291
|
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
23992
24292
|
|
|
23993
24293
|
// src/types/context-window.ts
|
|
23994
|
-
var DEFAULT_CONTEXT_WINDOW_MODE_ID = "
|
|
24294
|
+
var DEFAULT_CONTEXT_WINDOW_MODE_ID = "frugal";
|
|
23995
24295
|
var CONTEXT_WINDOW_MODES = Object.freeze([
|
|
23996
24296
|
{
|
|
23997
24297
|
id: "balanced",
|
|
23998
24298
|
name: "Balanced",
|
|
23999
24299
|
description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
|
|
24000
|
-
thresholds: { warn: 0.
|
|
24300
|
+
thresholds: { warn: 0.5, soft: 0.65, hard: 0.8 },
|
|
24001
24301
|
aggressiveOn: "soft",
|
|
24002
|
-
preserveK:
|
|
24003
|
-
eliseThreshold:
|
|
24004
|
-
targetLoad: 0.
|
|
24302
|
+
preserveK: 8,
|
|
24303
|
+
eliseThreshold: 1e3,
|
|
24304
|
+
targetLoad: 0.55
|
|
24005
24305
|
},
|
|
24006
24306
|
{
|
|
24007
24307
|
id: "frugal",
|
|
@@ -24115,12 +24415,11 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
24115
24415
|
mcpServers: {},
|
|
24116
24416
|
fallbackAuto: true,
|
|
24117
24417
|
maxConcurrent: 4,
|
|
24118
|
-
// YOLO
|
|
24119
|
-
// prompt
|
|
24120
|
-
//
|
|
24121
|
-
//
|
|
24122
|
-
|
|
24123
|
-
yolo: true,
|
|
24418
|
+
// YOLO opt-in: users who want auto-approve behaviour set `yolo: true`.
|
|
24419
|
+
// Off by default so prompt injection in repository content, fetched
|
|
24420
|
+
// content, or mailbox messages cannot induce arbitrary shell calls or
|
|
24421
|
+
// write operations without human confirmation.
|
|
24422
|
+
yolo: false,
|
|
24124
24423
|
nextPrediction: false,
|
|
24125
24424
|
hints: true,
|
|
24126
24425
|
debugStream: false,
|
|
@@ -24145,7 +24444,7 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
24145
24444
|
terminalTitleAnimation: true,
|
|
24146
24445
|
// Mirrored from the top-level yolo default so the autonomy subsystem
|
|
24147
24446
|
// (which reads autonomy.yolo) stays consistent with config.yolo.
|
|
24148
|
-
yolo:
|
|
24447
|
+
yolo: false,
|
|
24149
24448
|
streamFleet: true,
|
|
24150
24449
|
chime: false,
|
|
24151
24450
|
confirmExit: true,
|
|
@@ -24597,7 +24896,7 @@ var DefaultConfigLoader = class {
|
|
|
24597
24896
|
await withFileLock(fp, async () => {
|
|
24598
24897
|
let parsed;
|
|
24599
24898
|
try {
|
|
24600
|
-
const raw = await
|
|
24899
|
+
const raw = await fs13.readFile(fp, "utf8");
|
|
24601
24900
|
const result = safeParse(raw);
|
|
24602
24901
|
if (!result.ok || !isPlainRecord(result.value)) {
|
|
24603
24902
|
return;
|
|
@@ -24706,7 +25005,7 @@ var DefaultConfigLoader = class {
|
|
|
24706
25005
|
const fp = this.paths.syncConfig;
|
|
24707
25006
|
const t0 = Date.now();
|
|
24708
25007
|
try {
|
|
24709
|
-
const raw = await
|
|
25008
|
+
const raw = await fs13.readFile(fp, "utf8");
|
|
24710
25009
|
const parsed = safeParse(raw);
|
|
24711
25010
|
if (!parsed.ok || !parsed.value) {
|
|
24712
25011
|
this.events?.emit("storage.read", {
|
|
@@ -24768,8 +25067,8 @@ var DefaultConfigLoader = class {
|
|
|
24768
25067
|
const t0 = Date.now();
|
|
24769
25068
|
let mtimeMs = null;
|
|
24770
25069
|
try {
|
|
24771
|
-
const
|
|
24772
|
-
mtimeMs =
|
|
25070
|
+
const stat12 = await fs13.stat(file);
|
|
25071
|
+
mtimeMs = stat12.mtimeMs;
|
|
24773
25072
|
const cached = this.jsonCache.get(file);
|
|
24774
25073
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
24775
25074
|
return structuredClone(cached.value);
|
|
@@ -24797,7 +25096,7 @@ var DefaultConfigLoader = class {
|
|
|
24797
25096
|
}
|
|
24798
25097
|
let raw;
|
|
24799
25098
|
try {
|
|
24800
|
-
raw = await
|
|
25099
|
+
raw = await fs13.readFile(file, "utf8");
|
|
24801
25100
|
} catch (err) {
|
|
24802
25101
|
if (err.code !== "ENOENT") {
|
|
24803
25102
|
this.events?.emit("storage.read", {
|
|
@@ -25027,938 +25326,9 @@ function deepFreeze(obj) {
|
|
|
25027
25326
|
return Object.freeze(obj);
|
|
25028
25327
|
}
|
|
25029
25328
|
|
|
25030
|
-
// src/storage/
|
|
25329
|
+
// src/storage/plan-store.ts
|
|
25330
|
+
import * as fsp14 from "node:fs/promises";
|
|
25031
25331
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
25032
|
-
import * as fs13 from "node:fs/promises";
|
|
25033
|
-
import * as path26 from "node:path";
|
|
25034
|
-
|
|
25035
|
-
// src/types/memory.ts
|
|
25036
|
-
var MEMORY_TYPE_LABELS = {
|
|
25037
|
-
fact: "Fact",
|
|
25038
|
-
decision: "Decision",
|
|
25039
|
-
convention: "Convention",
|
|
25040
|
-
preference: "Preference",
|
|
25041
|
-
reference: "Reference",
|
|
25042
|
-
anti_pattern: "Anti-pattern"
|
|
25043
|
-
};
|
|
25044
|
-
|
|
25045
|
-
// src/storage/memory-backend.ts
|
|
25046
|
-
var TYPE_PRIORITY_RE = /^\[(\w+)\|(\w+)\]\s+/;
|
|
25047
|
-
var TAG_RE = /#([\w-]+)/g;
|
|
25048
|
-
var MAX_MEMORY_CONSOLIDATE_BACKUPS = 5;
|
|
25049
|
-
function formatMetadata(entry) {
|
|
25050
|
-
const parts = [];
|
|
25051
|
-
if (entry.type && entry.priority) {
|
|
25052
|
-
parts.push(`[${entry.type}|${entry.priority}]`);
|
|
25053
|
-
} else if (entry.type) {
|
|
25054
|
-
parts.push(`[${entry.type}]`);
|
|
25055
|
-
} else if (entry.priority) {
|
|
25056
|
-
parts.push(`[${entry.priority}]`);
|
|
25057
|
-
}
|
|
25058
|
-
if (entry.tags && entry.tags.length > 0) {
|
|
25059
|
-
parts.push(entry.tags.map((t) => `#${t}`).join(" "));
|
|
25060
|
-
}
|
|
25061
|
-
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
25062
|
-
}
|
|
25063
|
-
function lineToEntry(line, scope) {
|
|
25064
|
-
const trimmed = line.trim();
|
|
25065
|
-
if (!trimmed.startsWith("- [")) return null;
|
|
25066
|
-
const tsMatch = trimmed.match(/^-\s*\[([^\]]+)\]/);
|
|
25067
|
-
if (!tsMatch) return null;
|
|
25068
|
-
const ts = tsMatch[1] ?? "";
|
|
25069
|
-
let rest = trimmed.slice(tsMatch[0].length).trim();
|
|
25070
|
-
let type;
|
|
25071
|
-
let priority;
|
|
25072
|
-
const tpMatch = rest.match(TYPE_PRIORITY_RE);
|
|
25073
|
-
if (tpMatch) {
|
|
25074
|
-
const a = tpMatch[1] ?? "";
|
|
25075
|
-
const b = tpMatch[2] ?? "";
|
|
25076
|
-
if (isMemoryType(a)) {
|
|
25077
|
-
type = a;
|
|
25078
|
-
priority = isPriority(b) ? b : void 0;
|
|
25079
|
-
} else if (isPriority(a)) {
|
|
25080
|
-
priority = a;
|
|
25081
|
-
}
|
|
25082
|
-
rest = rest.slice(tpMatch[0].length).trim();
|
|
25083
|
-
}
|
|
25084
|
-
const idMatch = rest.match(/^mem_\d+_\w+\s+/);
|
|
25085
|
-
let text;
|
|
25086
|
-
if (idMatch) {
|
|
25087
|
-
text = rest.slice(idMatch[0].length).trim();
|
|
25088
|
-
} else {
|
|
25089
|
-
text = rest.trim();
|
|
25090
|
-
}
|
|
25091
|
-
const tags = [];
|
|
25092
|
-
let tagMatch;
|
|
25093
|
-
TAG_RE.lastIndex = 0;
|
|
25094
|
-
while ((tagMatch = TAG_RE.exec(text)) !== null) {
|
|
25095
|
-
tags.push(tagMatch[1] ?? "");
|
|
25096
|
-
}
|
|
25097
|
-
const cleanText = text.replace(TAG_RE, "").replace(/\s{2,}/g, " ").trim();
|
|
25098
|
-
if (!cleanText) return null;
|
|
25099
|
-
return {
|
|
25100
|
-
scope,
|
|
25101
|
-
text: cleanText,
|
|
25102
|
-
ts,
|
|
25103
|
-
type,
|
|
25104
|
-
priority,
|
|
25105
|
-
tags: tags.length > 0 ? tags : void 0
|
|
25106
|
-
};
|
|
25107
|
-
}
|
|
25108
|
-
function isMemoryType(s) {
|
|
25109
|
-
return s in MEMORY_TYPE_LABELS;
|
|
25110
|
-
}
|
|
25111
|
-
function isPriority(s) {
|
|
25112
|
-
return s === "critical" || s === "high" || s === "medium" || s === "low";
|
|
25113
|
-
}
|
|
25114
|
-
function buildInvertedIndex(entries) {
|
|
25115
|
-
const wordMap = /* @__PURE__ */ new Map();
|
|
25116
|
-
const tagMap = /* @__PURE__ */ new Map();
|
|
25117
|
-
const indexed = new Array(entries.length);
|
|
25118
|
-
for (let i = 0; i < entries.length; i++) {
|
|
25119
|
-
const e = entries[i];
|
|
25120
|
-
const words = e.text.toLowerCase().split(/\s+/).filter((w) => w.length > 0);
|
|
25121
|
-
const tags = (e.tags ?? []).map((t) => t.toLowerCase());
|
|
25122
|
-
indexed[i] = { entry: e, words, tags };
|
|
25123
|
-
for (const w of words) {
|
|
25124
|
-
const arr = wordMap.get(w);
|
|
25125
|
-
if (arr) arr.push(i);
|
|
25126
|
-
else wordMap.set(w, [i]);
|
|
25127
|
-
}
|
|
25128
|
-
for (const t of tags) {
|
|
25129
|
-
const arr = tagMap.get(t);
|
|
25130
|
-
if (arr) arr.push(i);
|
|
25131
|
-
else tagMap.set(t, [i]);
|
|
25132
|
-
}
|
|
25133
|
-
}
|
|
25134
|
-
return { wordMap, tagMap, entries: indexed, mtimeMs: 0 };
|
|
25135
|
-
}
|
|
25136
|
-
var MIN_SUBSTRING_NEEDLE_LEN = 3;
|
|
25137
|
-
function searchIndex(index, query, limit) {
|
|
25138
|
-
const needles = query.toLowerCase().split(/\s+/).filter((n) => n.length > 0);
|
|
25139
|
-
if (needles.length === 0) return [];
|
|
25140
|
-
const scores = /* @__PURE__ */ new Map();
|
|
25141
|
-
for (const n of needles) {
|
|
25142
|
-
let matched = false;
|
|
25143
|
-
const wordExact = index.wordMap.get(n);
|
|
25144
|
-
if (wordExact) {
|
|
25145
|
-
for (const idx of wordExact) scores.set(idx, (scores.get(idx) ?? 0) + 1);
|
|
25146
|
-
matched = true;
|
|
25147
|
-
}
|
|
25148
|
-
const tagExact = index.tagMap.get(n);
|
|
25149
|
-
if (tagExact) {
|
|
25150
|
-
for (const idx of tagExact) scores.set(idx, (scores.get(idx) ?? 0) + 2);
|
|
25151
|
-
matched = true;
|
|
25152
|
-
}
|
|
25153
|
-
if (matched || n.length < MIN_SUBSTRING_NEEDLE_LEN) continue;
|
|
25154
|
-
for (const [word, indices] of index.wordMap) {
|
|
25155
|
-
if (word.includes(n) || n.includes(word)) {
|
|
25156
|
-
for (const idx of indices) {
|
|
25157
|
-
scores.set(idx, (scores.get(idx) ?? 0) + 1);
|
|
25158
|
-
}
|
|
25159
|
-
}
|
|
25160
|
-
}
|
|
25161
|
-
for (const [tag, indices] of index.tagMap) {
|
|
25162
|
-
if (tag.includes(n) || n.includes(tag)) {
|
|
25163
|
-
for (const idx of indices) {
|
|
25164
|
-
scores.set(idx, (scores.get(idx) ?? 0) + 2);
|
|
25165
|
-
}
|
|
25166
|
-
}
|
|
25167
|
-
}
|
|
25168
|
-
}
|
|
25169
|
-
if (scores.size === 0) return [];
|
|
25170
|
-
const scored = Array.from(scores.entries());
|
|
25171
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
25172
|
-
const result = [];
|
|
25173
|
-
const max = limit ? Math.min(limit, scored.length) : scored.length;
|
|
25174
|
-
for (let i = 0; i < max; i++) {
|
|
25175
|
-
result.push(index.entries[scored[i][0]].entry);
|
|
25176
|
-
}
|
|
25177
|
-
return result;
|
|
25178
|
-
}
|
|
25179
|
-
var FileMemoryBackend = class {
|
|
25180
|
-
kind = "file";
|
|
25181
|
-
files;
|
|
25182
|
-
/** Cache of parsed entries per file path. */
|
|
25183
|
-
entryCache = /* @__PURE__ */ new Map();
|
|
25184
|
-
/** Inverted index per file path. */
|
|
25185
|
-
indexCache = /* @__PURE__ */ new Map();
|
|
25186
|
-
/** File mtime cache for invalidation. */
|
|
25187
|
-
mtimeCache = /* @__PURE__ */ new Map();
|
|
25188
|
-
constructor(opts) {
|
|
25189
|
-
this.files = {
|
|
25190
|
-
"project-agents": opts.paths.inProjectAgentsFile,
|
|
25191
|
-
"project-memory": opts.paths.projectMemory,
|
|
25192
|
-
"user-memory": opts.paths.globalMemory
|
|
25193
|
-
};
|
|
25194
|
-
}
|
|
25195
|
-
resolveFile(filePath, scope) {
|
|
25196
|
-
return filePath || this.files[scope];
|
|
25197
|
-
}
|
|
25198
|
-
async getMtime(file) {
|
|
25199
|
-
try {
|
|
25200
|
-
const stat13 = await fs13.stat(file);
|
|
25201
|
-
return stat13.mtimeMs;
|
|
25202
|
-
} catch {
|
|
25203
|
-
return 0;
|
|
25204
|
-
}
|
|
25205
|
-
}
|
|
25206
|
-
invalidateCache(file) {
|
|
25207
|
-
this.entryCache.delete(file);
|
|
25208
|
-
this.indexCache.delete(file);
|
|
25209
|
-
this.mtimeCache.delete(file);
|
|
25210
|
-
}
|
|
25211
|
-
/**
|
|
25212
|
-
* Load (and cache) the parsed entries for a file. Callers that have already
|
|
25213
|
-
* stat'd the file this tick (e.g. `getIndex`) can pass the known `mtime` to
|
|
25214
|
-
* avoid a redundant `fs.stat` — otherwise it's fetched here.
|
|
25215
|
-
*/
|
|
25216
|
-
async loadEntries(file, scope, mtime) {
|
|
25217
|
-
const resolvedMtime = mtime ?? await this.getMtime(file);
|
|
25218
|
-
const cachedMtime = this.mtimeCache.get(file);
|
|
25219
|
-
if (cachedMtime === resolvedMtime && this.entryCache.has(file)) {
|
|
25220
|
-
return this.entryCache.get(file);
|
|
25221
|
-
}
|
|
25222
|
-
const raw = await this.readAll(scope, file);
|
|
25223
|
-
if (!raw.trim()) {
|
|
25224
|
-
this.entryCache.set(file, []);
|
|
25225
|
-
this.mtimeCache.set(file, resolvedMtime);
|
|
25226
|
-
return [];
|
|
25227
|
-
}
|
|
25228
|
-
const entries = parseEntries(raw, scope);
|
|
25229
|
-
this.entryCache.set(file, entries);
|
|
25230
|
-
this.mtimeCache.set(file, resolvedMtime);
|
|
25231
|
-
return entries;
|
|
25232
|
-
}
|
|
25233
|
-
async getIndex(file, scope) {
|
|
25234
|
-
const mtime = await this.getMtime(file);
|
|
25235
|
-
const cached = this.indexCache.get(file);
|
|
25236
|
-
if (cached && cached.mtimeMs === mtime) {
|
|
25237
|
-
return cached;
|
|
25238
|
-
}
|
|
25239
|
-
const entries = await this.loadEntries(file, scope, mtime);
|
|
25240
|
-
const index = buildInvertedIndex(entries);
|
|
25241
|
-
index.mtimeMs = mtime;
|
|
25242
|
-
this.indexCache.set(file, index);
|
|
25243
|
-
return index;
|
|
25244
|
-
}
|
|
25245
|
-
async remember(scope, entry, filePath) {
|
|
25246
|
-
const file = this.resolveFile(filePath, scope);
|
|
25247
|
-
await ensureDir(path26.dirname(file));
|
|
25248
|
-
let existing = "";
|
|
25249
|
-
try {
|
|
25250
|
-
existing = await fs13.readFile(file, "utf8");
|
|
25251
|
-
} catch {
|
|
25252
|
-
}
|
|
25253
|
-
const id = `mem_${Date.now()}_${randomUUID11().slice(0, 8)}`;
|
|
25254
|
-
const meta = formatMetadata(entry);
|
|
25255
|
-
const line = `
|
|
25256
|
-
- [${entry.ts}] ${id}${meta} ${entry.text.replace(/\n/g, " ")}
|
|
25257
|
-
`;
|
|
25258
|
-
const next = existing.trim() ? existing.replace(/\n+$/, "") + line : `# Agent Memory
|
|
25259
|
-
${line}`;
|
|
25260
|
-
await atomicWrite(file, next);
|
|
25261
|
-
this.invalidateCache(file);
|
|
25262
|
-
}
|
|
25263
|
-
async forget(scope, query, filePath) {
|
|
25264
|
-
const file = this.resolveFile(filePath, scope);
|
|
25265
|
-
return withFileLock(file, async () => {
|
|
25266
|
-
let existing;
|
|
25267
|
-
try {
|
|
25268
|
-
existing = await fs13.readFile(file, "utf8");
|
|
25269
|
-
} catch {
|
|
25270
|
-
return 0;
|
|
25271
|
-
}
|
|
25272
|
-
const needle = query.toLowerCase();
|
|
25273
|
-
const idMatcher = /mem_\d+_\w+/;
|
|
25274
|
-
let removed = 0;
|
|
25275
|
-
const lines = existing.split("\n").filter((line) => {
|
|
25276
|
-
const trimmed = line.trim();
|
|
25277
|
-
if (!trimmed.startsWith("- ")) return true;
|
|
25278
|
-
if (idMatcher.test(query)) {
|
|
25279
|
-
const entryIdMatch = /mem_\d+_\w+/.exec(trimmed);
|
|
25280
|
-
if (entryIdMatch && entryIdMatch[0] === query) {
|
|
25281
|
-
removed++;
|
|
25282
|
-
return false;
|
|
25283
|
-
}
|
|
25284
|
-
}
|
|
25285
|
-
if (trimmed.toLowerCase().includes(needle)) {
|
|
25286
|
-
removed++;
|
|
25287
|
-
return false;
|
|
25288
|
-
}
|
|
25289
|
-
return true;
|
|
25290
|
-
});
|
|
25291
|
-
if (removed > 0) {
|
|
25292
|
-
if (lines.length === 0 || lines.length === 1 && !lines[0]?.trim()) {
|
|
25293
|
-
await atomicWrite(file, "");
|
|
25294
|
-
} else {
|
|
25295
|
-
await atomicWrite(file, lines.join("\n"));
|
|
25296
|
-
}
|
|
25297
|
-
}
|
|
25298
|
-
this.invalidateCache(file);
|
|
25299
|
-
return removed;
|
|
25300
|
-
});
|
|
25301
|
-
}
|
|
25302
|
-
async readAll(scope, filePath) {
|
|
25303
|
-
const file = this.resolveFile(filePath, scope);
|
|
25304
|
-
try {
|
|
25305
|
-
return await fs13.readFile(file, "utf8");
|
|
25306
|
-
} catch {
|
|
25307
|
-
return "";
|
|
25308
|
-
}
|
|
25309
|
-
}
|
|
25310
|
-
async list(scope, filePath, limit) {
|
|
25311
|
-
const file = this.resolveFile(filePath, scope);
|
|
25312
|
-
const entries = await this.loadEntries(file, scope);
|
|
25313
|
-
return limit ? entries.slice(0, limit) : entries;
|
|
25314
|
-
}
|
|
25315
|
-
async search(scope, query, filePath, limit) {
|
|
25316
|
-
const file = this.resolveFile(filePath, scope);
|
|
25317
|
-
const index = await this.getIndex(file, scope);
|
|
25318
|
-
return searchIndex(index, query, limit);
|
|
25319
|
-
}
|
|
25320
|
-
async clear(scope, filePath) {
|
|
25321
|
-
const file = this.resolveFile(filePath, scope);
|
|
25322
|
-
await atomicWrite(file, "");
|
|
25323
|
-
this.invalidateCache(file);
|
|
25324
|
-
}
|
|
25325
|
-
async consolidate(scope, filePath) {
|
|
25326
|
-
const file = this.resolveFile(filePath, scope);
|
|
25327
|
-
let existing;
|
|
25328
|
-
try {
|
|
25329
|
-
existing = await fs13.readFile(file, "utf8");
|
|
25330
|
-
} catch {
|
|
25331
|
-
return 0;
|
|
25332
|
-
}
|
|
25333
|
-
const seen = /* @__PURE__ */ new Set();
|
|
25334
|
-
let removed = 0;
|
|
25335
|
-
const lines = existing.split("\n").filter((line) => {
|
|
25336
|
-
const trimmed = line.trim();
|
|
25337
|
-
if (!trimmed.startsWith("- ")) return true;
|
|
25338
|
-
const norm = trimmed.replace(/\[[^\]]+\]/g, "").replace(/\bmem_\d+_\w+\s*/, "").replace(/#[\w-]+/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
25339
|
-
if (seen.has(norm)) {
|
|
25340
|
-
removed++;
|
|
25341
|
-
return false;
|
|
25342
|
-
}
|
|
25343
|
-
seen.add(norm);
|
|
25344
|
-
return true;
|
|
25345
|
-
});
|
|
25346
|
-
const next = lines.join("\n");
|
|
25347
|
-
const backup = `${file}.bak.${Date.now()}`;
|
|
25348
|
-
try {
|
|
25349
|
-
await fs13.copyFile(file, backup);
|
|
25350
|
-
await pruneConsolidateBackups(file);
|
|
25351
|
-
} catch {
|
|
25352
|
-
}
|
|
25353
|
-
try {
|
|
25354
|
-
await atomicWrite(file, next);
|
|
25355
|
-
} catch {
|
|
25356
|
-
return 0;
|
|
25357
|
-
}
|
|
25358
|
-
this.invalidateCache(file);
|
|
25359
|
-
return removed;
|
|
25360
|
-
}
|
|
25361
|
-
};
|
|
25362
|
-
async function pruneConsolidateBackups(file) {
|
|
25363
|
-
const dir = path26.dirname(file);
|
|
25364
|
-
const base = path26.basename(file);
|
|
25365
|
-
const prefix = `${base}.bak.`;
|
|
25366
|
-
const backups = (await fs13.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
|
|
25367
|
-
await Promise.all(
|
|
25368
|
-
backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {
|
|
25369
|
-
try {
|
|
25370
|
-
await fs13.unlink(path26.join(dir, name));
|
|
25371
|
-
} catch {
|
|
25372
|
-
}
|
|
25373
|
-
})
|
|
25374
|
-
);
|
|
25375
|
-
}
|
|
25376
|
-
function parseEntries(raw, scope = "project-memory") {
|
|
25377
|
-
const entries = [];
|
|
25378
|
-
for (const line of raw.split("\n")) {
|
|
25379
|
-
const entry = lineToEntry(line, scope);
|
|
25380
|
-
if (entry) entries.push(entry);
|
|
25381
|
-
}
|
|
25382
|
-
return entries.reverse();
|
|
25383
|
-
}
|
|
25384
|
-
|
|
25385
|
-
// src/storage/memory-store.ts
|
|
25386
|
-
var MAX_BYTES_TOTAL = 32e3;
|
|
25387
|
-
var DefaultMemoryStore = class {
|
|
25388
|
-
files;
|
|
25389
|
-
events;
|
|
25390
|
-
traceId;
|
|
25391
|
-
backend;
|
|
25392
|
-
/**
|
|
25393
|
-
* Per-scope serialization queue. `remember` / `forget` / `consolidate` /
|
|
25394
|
-
* `clear` are read-modify-write against a single file; without a lock,
|
|
25395
|
-
* two concurrent calls on the same scope can read the same baseline and
|
|
25396
|
-
* the later write silently drops the earlier entry. We chain each
|
|
25397
|
-
* mutation onto the prior promise for the same scope so they run in
|
|
25398
|
-
* issue order. Different scopes still proceed in parallel.
|
|
25399
|
-
*
|
|
25400
|
-
* The chain tracks only the last pending write. If a write fails, its
|
|
25401
|
-
* error is caught and swallowed so the chain stays alive for subsequent
|
|
25402
|
-
* calls. The error is stored in `writeErrors` so callers can learn about
|
|
25403
|
-
* it on the next read operation.
|
|
25404
|
-
*/
|
|
25405
|
-
writeChain = /* @__PURE__ */ new Map();
|
|
25406
|
-
/** Last write error per scope — surfaced as warnings on the next readAll(). */
|
|
25407
|
-
writeErrors = /* @__PURE__ */ new Map();
|
|
25408
|
-
/**
|
|
25409
|
-
* When the global root is a temporary directory (opencode, CI sandboxes),
|
|
25410
|
-
* memory files are also mirrored to the project tree so they survive
|
|
25411
|
-
* session cleanup. The primary path stays in the temp root for isolation;
|
|
25412
|
-
* this backup ensures memory is never lost.
|
|
25413
|
-
*/
|
|
25414
|
-
persistBackup;
|
|
25415
|
-
backupDir;
|
|
25416
|
-
/**
|
|
25417
|
-
* Per-scope tracked byte sizes — incremented on `remember()`, decremented on
|
|
25418
|
-
* `forget()`, recalculated after `consolidate()`. Eliminates the redundant
|
|
25419
|
-
* readAll() call that previously checked the file size after every write.
|
|
25420
|
-
*/
|
|
25421
|
-
_trackedByteSizes = {};
|
|
25422
|
-
/** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
|
|
25423
|
-
_scoreCache = /* @__PURE__ */ new Map();
|
|
25424
|
-
/**
|
|
25425
|
-
* Per-entry cached lowercase strings — lazily allocated once and reused
|
|
25426
|
-
* across scoreRelevant() calls so repeated scoring of the same entries skips
|
|
25427
|
-
* re-lowercasing. Keyed by entry object identity; cleared (set to null) on
|
|
25428
|
-
* every mutation (remember/forget/consolidate/clear) via invalidateScoreCaches().
|
|
25429
|
-
*/
|
|
25430
|
-
_cachedLower = null;
|
|
25431
|
-
constructor(opts) {
|
|
25432
|
-
this.files = {
|
|
25433
|
-
"project-agents": opts.paths.inProjectAgentsFile,
|
|
25434
|
-
"project-memory": opts.paths.projectMemory,
|
|
25435
|
-
"user-memory": opts.paths.globalMemory
|
|
25436
|
-
};
|
|
25437
|
-
this.events = opts.events;
|
|
25438
|
-
this.backend = opts.backend ?? new FileMemoryBackend({ paths: opts.paths });
|
|
25439
|
-
const root = opts.paths.globalRoot.toLowerCase();
|
|
25440
|
-
this.persistBackup = /[/\\](tmp|temp|cache)[/\\]/.test(root);
|
|
25441
|
-
this.backupDir = this.persistBackup ? opts.paths.inProjectAgentsFile.replace(/AGENTS\.md$/, "memory-persist") : "";
|
|
25442
|
-
}
|
|
25443
|
-
/** Expose the backend for plugin introspection and advanced queries. */
|
|
25444
|
-
getBackend() {
|
|
25445
|
-
return this.backend;
|
|
25446
|
-
}
|
|
25447
|
-
async runSerialized(scope, work) {
|
|
25448
|
-
const prior = this.writeChain.get(scope) ?? Promise.resolve();
|
|
25449
|
-
const next = prior.catch((err) => {
|
|
25450
|
-
this.writeErrors.set(scope, err);
|
|
25451
|
-
}).then(() => work());
|
|
25452
|
-
this.writeChain.set(scope, next);
|
|
25453
|
-
try {
|
|
25454
|
-
return await next;
|
|
25455
|
-
} catch (err) {
|
|
25456
|
-
this.writeErrors.set(scope, err);
|
|
25457
|
-
throw err;
|
|
25458
|
-
} finally {
|
|
25459
|
-
if (this.writeChain.get(scope) === next) {
|
|
25460
|
-
this.writeChain.delete(scope);
|
|
25461
|
-
}
|
|
25462
|
-
}
|
|
25463
|
-
}
|
|
25464
|
-
/**
|
|
25465
|
-
* Recalculate the tracked byte size for a scope by re-reading the file and
|
|
25466
|
-
* summing the serialized byte length of each line. Called after consolidate()
|
|
25467
|
-
* (which modifies the file) to keep the tracker accurate.
|
|
25468
|
-
*/
|
|
25469
|
-
async _recalcTrackedByteSize(scope) {
|
|
25470
|
-
const raw = await this.backend.readAll(scope, this.files[scope]);
|
|
25471
|
-
let total = 0;
|
|
25472
|
-
for (const line of raw.split("\n")) {
|
|
25473
|
-
if (line.trim()) total += Buffer.byteLength(line, "utf8");
|
|
25474
|
-
}
|
|
25475
|
-
this._trackedByteSizes[scope] = total;
|
|
25476
|
-
return total;
|
|
25477
|
-
}
|
|
25478
|
-
async readAll() {
|
|
25479
|
-
const parts = [];
|
|
25480
|
-
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
25481
|
-
const writeErr2 = this.writeErrors.get(scope);
|
|
25482
|
-
if (writeErr2) {
|
|
25483
|
-
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
|
|
25484
|
-
}
|
|
25485
|
-
const t0 = Date.now();
|
|
25486
|
-
const filePath = this.files[scope];
|
|
25487
|
-
try {
|
|
25488
|
-
const body = await this.backend.readAll(scope, filePath);
|
|
25489
|
-
const dur = Date.now() - t0;
|
|
25490
|
-
this.events?.emit("storage.read", {
|
|
25491
|
-
sessionId: "~memory~",
|
|
25492
|
-
store: "memory",
|
|
25493
|
-
filePath,
|
|
25494
|
-
operation: "readAll",
|
|
25495
|
-
outcome: "success",
|
|
25496
|
-
durationMs: dur,
|
|
25497
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25498
|
-
});
|
|
25499
|
-
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
25500
|
-
|
|
25501
|
-
${body.trim()}`);
|
|
25502
|
-
} catch (err) {
|
|
25503
|
-
const dur = Date.now() - t0;
|
|
25504
|
-
this.events?.emit("storage.read", {
|
|
25505
|
-
sessionId: "~memory~",
|
|
25506
|
-
store: "memory",
|
|
25507
|
-
filePath,
|
|
25508
|
-
operation: "readAll",
|
|
25509
|
-
outcome: "failure",
|
|
25510
|
-
durationMs: dur,
|
|
25511
|
-
error: toErrorMessage(err),
|
|
25512
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25513
|
-
});
|
|
25514
|
-
throw err;
|
|
25515
|
-
}
|
|
25516
|
-
}
|
|
25517
|
-
return parts.join("\n\n");
|
|
25518
|
-
}
|
|
25519
|
-
async read(scope) {
|
|
25520
|
-
const t0 = Date.now();
|
|
25521
|
-
const filePath = this.files[scope];
|
|
25522
|
-
try {
|
|
25523
|
-
const body = await this.backend.readAll(scope, filePath);
|
|
25524
|
-
const dur = Date.now() - t0;
|
|
25525
|
-
this.events?.emit("storage.read", {
|
|
25526
|
-
sessionId: "~memory~",
|
|
25527
|
-
store: "memory",
|
|
25528
|
-
filePath,
|
|
25529
|
-
operation: "read",
|
|
25530
|
-
outcome: "success",
|
|
25531
|
-
durationMs: dur,
|
|
25532
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25533
|
-
});
|
|
25534
|
-
return body;
|
|
25535
|
-
} catch (err) {
|
|
25536
|
-
const dur = Date.now() - t0;
|
|
25537
|
-
this.events?.emit("storage.read", {
|
|
25538
|
-
sessionId: "~memory~",
|
|
25539
|
-
store: "memory",
|
|
25540
|
-
filePath,
|
|
25541
|
-
operation: "read",
|
|
25542
|
-
outcome: "failure",
|
|
25543
|
-
durationMs: dur,
|
|
25544
|
-
error: toErrorMessage(err),
|
|
25545
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25546
|
-
});
|
|
25547
|
-
throw err;
|
|
25548
|
-
}
|
|
25549
|
-
}
|
|
25550
|
-
/**
|
|
25551
|
-
* List entries from a scope, newest first. Delegates to the backend
|
|
25552
|
-
* so graph/semantic backends can return enriched or filtered results.
|
|
25553
|
-
*/
|
|
25554
|
-
async list(scope = "project-memory", limit) {
|
|
25555
|
-
return this.backend.list(scope, this.files[scope], limit);
|
|
25556
|
-
}
|
|
25557
|
-
/**
|
|
25558
|
-
* Find memories related to the given text via graph traversal.
|
|
25559
|
-
* Falls back to content search when no graph backend is available.
|
|
25560
|
-
*/
|
|
25561
|
-
async findRelated(text, scope = "project-memory", limit = 5) {
|
|
25562
|
-
if (this.backend.findRelated) {
|
|
25563
|
-
return this.backend.findRelated(scope, this.files[scope], text, limit);
|
|
25564
|
-
}
|
|
25565
|
-
return this.search(text, scope, limit);
|
|
25566
|
-
}
|
|
25567
|
-
async search(query, scope = "project-memory", limit) {
|
|
25568
|
-
return this.backend.search(scope, query, this.files[scope], limit);
|
|
25569
|
-
}
|
|
25570
|
-
async remember(text, scope = "project-memory", metadata) {
|
|
25571
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
25572
|
-
return this.runSerialized(scope, async () => {
|
|
25573
|
-
const entry = { scope, text, ts, ...metadata };
|
|
25574
|
-
const filePath = this.files[scope];
|
|
25575
|
-
const t0 = Date.now();
|
|
25576
|
-
try {
|
|
25577
|
-
await this.backend.remember(scope, entry, filePath);
|
|
25578
|
-
this.invalidateScoreCaches();
|
|
25579
|
-
const dur = Date.now() - t0;
|
|
25580
|
-
this.events?.emit("storage.write", {
|
|
25581
|
-
sessionId: "~memory~",
|
|
25582
|
-
store: "memory",
|
|
25583
|
-
filePath,
|
|
25584
|
-
operation: "remember",
|
|
25585
|
-
outcome: "success",
|
|
25586
|
-
durationMs: dur,
|
|
25587
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25588
|
-
});
|
|
25589
|
-
} catch (err) {
|
|
25590
|
-
const dur = Date.now() - t0;
|
|
25591
|
-
this.events?.emit("storage.write", {
|
|
25592
|
-
sessionId: "~memory~",
|
|
25593
|
-
store: "memory",
|
|
25594
|
-
filePath,
|
|
25595
|
-
operation: "remember",
|
|
25596
|
-
outcome: "failure",
|
|
25597
|
-
durationMs: dur,
|
|
25598
|
-
error: toErrorMessage(err),
|
|
25599
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25600
|
-
});
|
|
25601
|
-
throw err;
|
|
25602
|
-
}
|
|
25603
|
-
let trackedSize = this._trackedByteSizes[scope];
|
|
25604
|
-
if (trackedSize === void 0) {
|
|
25605
|
-
trackedSize = await this._recalcTrackedByteSize(scope);
|
|
25606
|
-
}
|
|
25607
|
-
if (trackedSize > MAX_BYTES_TOTAL) {
|
|
25608
|
-
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
25609
|
-
if (removed > 0) {
|
|
25610
|
-
this.events?.emit("memory.consolidated", {
|
|
25611
|
-
scope,
|
|
25612
|
-
removed
|
|
25613
|
-
});
|
|
25614
|
-
await this._recalcTrackedByteSize(scope);
|
|
25615
|
-
}
|
|
25616
|
-
}
|
|
25617
|
-
this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
|
|
25618
|
-
await this.mirrorBackup(scope);
|
|
25619
|
-
this.events?.emit("memory.remembered", {
|
|
25620
|
-
scope,
|
|
25621
|
-
text,
|
|
25622
|
-
ts,
|
|
25623
|
-
type: entry.type,
|
|
25624
|
-
tags: entry.tags,
|
|
25625
|
-
priority: entry.priority
|
|
25626
|
-
});
|
|
25627
|
-
});
|
|
25628
|
-
}
|
|
25629
|
-
/**
|
|
25630
|
-
* Drop the relevance caches after a mutation. Both the per-context score
|
|
25631
|
-
* cache and the per-entry lowercase cache are keyed on entry objects that a
|
|
25632
|
-
* mutation invalidates, so they must be cleared together when entries change.
|
|
25633
|
-
*/
|
|
25634
|
-
invalidateScoreCaches() {
|
|
25635
|
-
this._scoreCache.clear();
|
|
25636
|
-
this._cachedLower = null;
|
|
25637
|
-
}
|
|
25638
|
-
/**
|
|
25639
|
-
* Score and rank memories by relevance to the current context.
|
|
25640
|
-
* Returns entries with score >= MIN_RELEVANCE_SCORE, sorted highest first.
|
|
25641
|
-
*/
|
|
25642
|
-
async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
|
|
25643
|
-
const all = await this.list(scope);
|
|
25644
|
-
if (all.length === 0) return [];
|
|
25645
|
-
const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
|
|
25646
|
-
const now = Date.now();
|
|
25647
|
-
const TTL_MS = 3e4;
|
|
25648
|
-
const cached = this._scoreCache.get(ctxHash);
|
|
25649
|
-
if (cached && cached.expiresAt > now && cached.entries === all) {
|
|
25650
|
-
return cached.scored.slice(0, Math.min(limit, 15));
|
|
25651
|
-
}
|
|
25652
|
-
const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
25653
|
-
const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
|
|
25654
|
-
const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
|
|
25655
|
-
this._cachedLower ??= /* @__PURE__ */ new WeakMap();
|
|
25656
|
-
const lowerCache = this._cachedLower;
|
|
25657
|
-
const scored = [];
|
|
25658
|
-
for (const entry of all) {
|
|
25659
|
-
let score = 0;
|
|
25660
|
-
const reasons = [];
|
|
25661
|
-
let cachedLower = lowerCache.get(entry);
|
|
25662
|
-
if (!cachedLower) {
|
|
25663
|
-
cachedLower = {
|
|
25664
|
-
textLower: entry.text.toLowerCase(),
|
|
25665
|
-
tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
|
|
25666
|
-
};
|
|
25667
|
-
lowerCache.set(entry, cachedLower);
|
|
25668
|
-
}
|
|
25669
|
-
const { textLower, tagsLower } = cachedLower;
|
|
25670
|
-
let taskHits = 0;
|
|
25671
|
-
for (const w of taskWords) {
|
|
25672
|
-
if (textLower.includes(w)) {
|
|
25673
|
-
taskHits++;
|
|
25674
|
-
score += 2;
|
|
25675
|
-
}
|
|
25676
|
-
if (tagsLower.some((t) => t.includes(w))) {
|
|
25677
|
-
taskHits++;
|
|
25678
|
-
score += 3;
|
|
25679
|
-
}
|
|
25680
|
-
}
|
|
25681
|
-
if (taskHits > 0) reasons.push(`task match (${taskHits})`);
|
|
25682
|
-
let skillHits = 0;
|
|
25683
|
-
for (const w of skillWords) {
|
|
25684
|
-
if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
|
|
25685
|
-
skillHits++;
|
|
25686
|
-
}
|
|
25687
|
-
}
|
|
25688
|
-
score += skillHits;
|
|
25689
|
-
if (skillHits > 0) reasons.push(`skill match (${skillHits})`);
|
|
25690
|
-
for (const w of toolWords) {
|
|
25691
|
-
if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
|
|
25692
|
-
score += 1;
|
|
25693
|
-
reasons.push(`tool mention: ${w}`);
|
|
25694
|
-
}
|
|
25695
|
-
}
|
|
25696
|
-
switch (entry.priority) {
|
|
25697
|
-
case "critical":
|
|
25698
|
-
score += 5;
|
|
25699
|
-
reasons.push("critical");
|
|
25700
|
-
break;
|
|
25701
|
-
case "high":
|
|
25702
|
-
score += 3;
|
|
25703
|
-
reasons.push("high priority");
|
|
25704
|
-
break;
|
|
25705
|
-
case "medium":
|
|
25706
|
-
score += 1;
|
|
25707
|
-
break;
|
|
25708
|
-
case "low":
|
|
25709
|
-
score -= 2;
|
|
25710
|
-
reasons.push("low priority");
|
|
25711
|
-
break;
|
|
25712
|
-
}
|
|
25713
|
-
switch (entry.type) {
|
|
25714
|
-
case "decision":
|
|
25715
|
-
score += 2;
|
|
25716
|
-
reasons.push("decision");
|
|
25717
|
-
break;
|
|
25718
|
-
case "convention":
|
|
25719
|
-
score += 2;
|
|
25720
|
-
reasons.push("convention");
|
|
25721
|
-
break;
|
|
25722
|
-
case "anti_pattern":
|
|
25723
|
-
score += 3;
|
|
25724
|
-
reasons.push("anti-pattern");
|
|
25725
|
-
break;
|
|
25726
|
-
case "preference":
|
|
25727
|
-
score += 1;
|
|
25728
|
-
reasons.push("preference");
|
|
25729
|
-
break;
|
|
25730
|
-
case "reference":
|
|
25731
|
-
break;
|
|
25732
|
-
case "fact":
|
|
25733
|
-
break;
|
|
25734
|
-
}
|
|
25735
|
-
const ageDays = (now - new Date(entry.ts).getTime()) / (1e3 * 60 * 60 * 24);
|
|
25736
|
-
if (ageDays < 1) score += 1;
|
|
25737
|
-
else if (ageDays > 30) score -= 1;
|
|
25738
|
-
if (entry.confidence !== void 0 && entry.confidence < 0.5) {
|
|
25739
|
-
score -= 2;
|
|
25740
|
-
reasons.push("low confidence");
|
|
25741
|
-
}
|
|
25742
|
-
if (entry.lastAccessed) {
|
|
25743
|
-
const hoursSinceAccess = (now - new Date(entry.lastAccessed).getTime()) / (1e3 * 60 * 60);
|
|
25744
|
-
if (hoursSinceAccess < 1) score -= 1;
|
|
25745
|
-
}
|
|
25746
|
-
if (score > 0) {
|
|
25747
|
-
scored.push({
|
|
25748
|
-
...entry,
|
|
25749
|
-
score,
|
|
25750
|
-
matchReason: reasons.join(", ") || "keyword match"
|
|
25751
|
-
});
|
|
25752
|
-
}
|
|
25753
|
-
}
|
|
25754
|
-
scored.sort((a, b) => b.score - a.score);
|
|
25755
|
-
const threshold = 2;
|
|
25756
|
-
const relevant = scored.filter(
|
|
25757
|
-
(s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
|
|
25758
|
-
);
|
|
25759
|
-
this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
|
|
25760
|
-
return relevant.slice(0, Math.min(limit, 15));
|
|
25761
|
-
}
|
|
25762
|
-
async forget(query, scope = "project-memory") {
|
|
25763
|
-
return this.runSerialized(scope, async () => {
|
|
25764
|
-
const filePath = this.files[scope];
|
|
25765
|
-
const t0 = Date.now();
|
|
25766
|
-
let removed = 0;
|
|
25767
|
-
try {
|
|
25768
|
-
removed = await this.backend.forget(scope, query, filePath);
|
|
25769
|
-
this.invalidateScoreCaches();
|
|
25770
|
-
const dur = Date.now() - t0;
|
|
25771
|
-
this.events?.emit("storage.write", {
|
|
25772
|
-
sessionId: "~memory~",
|
|
25773
|
-
store: "memory",
|
|
25774
|
-
filePath,
|
|
25775
|
-
operation: "forget",
|
|
25776
|
-
outcome: "success",
|
|
25777
|
-
durationMs: dur,
|
|
25778
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25779
|
-
});
|
|
25780
|
-
} catch (err) {
|
|
25781
|
-
const dur = Date.now() - t0;
|
|
25782
|
-
this.events?.emit("storage.write", {
|
|
25783
|
-
sessionId: "~memory~",
|
|
25784
|
-
store: "memory",
|
|
25785
|
-
filePath,
|
|
25786
|
-
operation: "forget",
|
|
25787
|
-
outcome: "failure",
|
|
25788
|
-
durationMs: dur,
|
|
25789
|
-
error: toErrorMessage(err),
|
|
25790
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25791
|
-
});
|
|
25792
|
-
throw err;
|
|
25793
|
-
}
|
|
25794
|
-
if (removed > 0) {
|
|
25795
|
-
this.events?.emit("memory.forgotten", {
|
|
25796
|
-
scope,
|
|
25797
|
-
query,
|
|
25798
|
-
removed
|
|
25799
|
-
});
|
|
25800
|
-
await this.mirrorBackup(scope);
|
|
25801
|
-
await this._recalcTrackedByteSize(scope);
|
|
25802
|
-
}
|
|
25803
|
-
return removed;
|
|
25804
|
-
});
|
|
25805
|
-
}
|
|
25806
|
-
async consolidate(scope) {
|
|
25807
|
-
return this.runSerialized(scope, async () => {
|
|
25808
|
-
const filePath = this.files[scope];
|
|
25809
|
-
const t0 = Date.now();
|
|
25810
|
-
let removed = 0;
|
|
25811
|
-
try {
|
|
25812
|
-
removed = await this.backend.consolidate(scope, filePath);
|
|
25813
|
-
this.invalidateScoreCaches();
|
|
25814
|
-
const dur = Date.now() - t0;
|
|
25815
|
-
this.events?.emit("storage.write", {
|
|
25816
|
-
sessionId: "~memory~",
|
|
25817
|
-
store: "memory",
|
|
25818
|
-
filePath,
|
|
25819
|
-
operation: "consolidate",
|
|
25820
|
-
outcome: "success",
|
|
25821
|
-
durationMs: dur,
|
|
25822
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25823
|
-
});
|
|
25824
|
-
} catch (err) {
|
|
25825
|
-
const dur = Date.now() - t0;
|
|
25826
|
-
this.events?.emit("storage.write", {
|
|
25827
|
-
sessionId: "~memory~",
|
|
25828
|
-
store: "memory",
|
|
25829
|
-
filePath,
|
|
25830
|
-
operation: "consolidate",
|
|
25831
|
-
outcome: "failure",
|
|
25832
|
-
durationMs: dur,
|
|
25833
|
-
error: toErrorMessage(err),
|
|
25834
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25835
|
-
});
|
|
25836
|
-
throw err;
|
|
25837
|
-
}
|
|
25838
|
-
if (removed > 0) {
|
|
25839
|
-
this.events?.emit("memory.consolidated", {
|
|
25840
|
-
scope,
|
|
25841
|
-
removed
|
|
25842
|
-
});
|
|
25843
|
-
await this.mirrorBackup(scope);
|
|
25844
|
-
await this._recalcTrackedByteSize(scope);
|
|
25845
|
-
}
|
|
25846
|
-
});
|
|
25847
|
-
}
|
|
25848
|
-
async clear(scope) {
|
|
25849
|
-
if (scope) {
|
|
25850
|
-
await this.runSerialized(scope, async () => {
|
|
25851
|
-
const filePath = this.files[scope];
|
|
25852
|
-
const t0 = Date.now();
|
|
25853
|
-
try {
|
|
25854
|
-
await this.backend.clear(scope, filePath);
|
|
25855
|
-
this.invalidateScoreCaches();
|
|
25856
|
-
const dur = Date.now() - t0;
|
|
25857
|
-
this.events?.emit("storage.write", {
|
|
25858
|
-
sessionId: "~memory~",
|
|
25859
|
-
store: "memory",
|
|
25860
|
-
filePath,
|
|
25861
|
-
operation: "clear",
|
|
25862
|
-
outcome: "success",
|
|
25863
|
-
durationMs: dur,
|
|
25864
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25865
|
-
});
|
|
25866
|
-
} catch (err) {
|
|
25867
|
-
const dur = Date.now() - t0;
|
|
25868
|
-
this.events?.emit("storage.write", {
|
|
25869
|
-
sessionId: "~memory~",
|
|
25870
|
-
store: "memory",
|
|
25871
|
-
filePath,
|
|
25872
|
-
operation: "clear",
|
|
25873
|
-
outcome: "failure",
|
|
25874
|
-
durationMs: dur,
|
|
25875
|
-
error: toErrorMessage(err),
|
|
25876
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25877
|
-
});
|
|
25878
|
-
throw err;
|
|
25879
|
-
}
|
|
25880
|
-
this.events?.emit("memory.cleared", { scope });
|
|
25881
|
-
await this.mirrorBackup(scope);
|
|
25882
|
-
this._trackedByteSizes[scope] = 0;
|
|
25883
|
-
});
|
|
25884
|
-
return;
|
|
25885
|
-
}
|
|
25886
|
-
await Promise.all(
|
|
25887
|
-
["project-agents", "project-memory", "user-memory"].map(
|
|
25888
|
-
async (s) => this.runSerialized(s, async () => {
|
|
25889
|
-
const filePath = this.files[s];
|
|
25890
|
-
const t0 = Date.now();
|
|
25891
|
-
try {
|
|
25892
|
-
await this.backend.clear(s, filePath);
|
|
25893
|
-
const dur = Date.now() - t0;
|
|
25894
|
-
this.events?.emit("storage.write", {
|
|
25895
|
-
sessionId: "~memory~",
|
|
25896
|
-
store: "memory",
|
|
25897
|
-
filePath,
|
|
25898
|
-
operation: "clear",
|
|
25899
|
-
outcome: "success",
|
|
25900
|
-
durationMs: dur,
|
|
25901
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25902
|
-
});
|
|
25903
|
-
} catch (err) {
|
|
25904
|
-
const dur = Date.now() - t0;
|
|
25905
|
-
this.events?.emit("storage.write", {
|
|
25906
|
-
sessionId: "~memory~",
|
|
25907
|
-
store: "memory",
|
|
25908
|
-
filePath,
|
|
25909
|
-
operation: "clear",
|
|
25910
|
-
outcome: "failure",
|
|
25911
|
-
durationMs: dur,
|
|
25912
|
-
error: toErrorMessage(err),
|
|
25913
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25914
|
-
});
|
|
25915
|
-
throw err;
|
|
25916
|
-
}
|
|
25917
|
-
this.events?.emit("memory.cleared", { scope: s });
|
|
25918
|
-
await this.mirrorBackup(s);
|
|
25919
|
-
})
|
|
25920
|
-
)
|
|
25921
|
-
);
|
|
25922
|
-
this.invalidateScoreCaches();
|
|
25923
|
-
}
|
|
25924
|
-
/**
|
|
25925
|
-
* Return a new MemoryStore proxy that carries `traceId` on every storage
|
|
25926
|
-
* event. The original store is left unchanged — callers that need a
|
|
25927
|
-
* trace-decorated view (e.g. session-run tools) receive the proxy while
|
|
25928
|
-
* the singleton remains trace-free for boot-time use.
|
|
25929
|
-
*
|
|
25930
|
-
* The proxy implements the full `MemoryStore` interface; all other
|
|
25931
|
-
* properties (backend, etc.) are delegated to the original store.
|
|
25932
|
-
*/
|
|
25933
|
-
withTraceId(traceId) {
|
|
25934
|
-
this.traceId = traceId;
|
|
25935
|
-
return this;
|
|
25936
|
-
}
|
|
25937
|
-
async mirrorBackup(scope) {
|
|
25938
|
-
if (!this.persistBackup || scope === "project-agents") return;
|
|
25939
|
-
try {
|
|
25940
|
-
const content = await this.backend.readAll(scope, this.files[scope]);
|
|
25941
|
-
const { writeFile: writeFile7, mkdir: mkdir10 } = await import("node:fs/promises");
|
|
25942
|
-
await mkdir10(this.backupDir, { recursive: true });
|
|
25943
|
-
await writeFile7(`${this.backupDir}/${scope}.md`, content, "utf8");
|
|
25944
|
-
} catch {
|
|
25945
|
-
}
|
|
25946
|
-
}
|
|
25947
|
-
};
|
|
25948
|
-
function labelOf(scope) {
|
|
25949
|
-
switch (scope) {
|
|
25950
|
-
case "project-agents":
|
|
25951
|
-
return "Project AGENTS.md";
|
|
25952
|
-
case "project-memory":
|
|
25953
|
-
return "Project memory";
|
|
25954
|
-
case "user-memory":
|
|
25955
|
-
return "User memory";
|
|
25956
|
-
}
|
|
25957
|
-
}
|
|
25958
|
-
|
|
25959
|
-
// src/storage/plan-store.ts
|
|
25960
|
-
import * as fsp14 from "node:fs/promises";
|
|
25961
|
-
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
25962
25332
|
async function loadPlan(filePath, events) {
|
|
25963
25333
|
const t0 = Date.now();
|
|
25964
25334
|
let raw;
|
|
@@ -26056,7 +25426,7 @@ function emptyPlan(sessionId, title) {
|
|
|
26056
25426
|
function addPlanItem(plan, title, details) {
|
|
26057
25427
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26058
25428
|
const item = {
|
|
26059
|
-
id: `plan_${Date.now()}_${
|
|
25429
|
+
id: `plan_${Date.now()}_${randomUUID11().slice(0, 6)}`,
|
|
26060
25430
|
title,
|
|
26061
25431
|
details,
|
|
26062
25432
|
status: "open",
|
|
@@ -26128,7 +25498,7 @@ function deriveTodosFromPlanItem(plan, idOrIndex, subtasks) {
|
|
|
26128
25498
|
if (subtasks && subtasks.length > 0) {
|
|
26129
25499
|
for (const st of subtasks) {
|
|
26130
25500
|
todos.push({
|
|
26131
|
-
id: `todo_${Date.now()}_${
|
|
25501
|
+
id: `todo_${Date.now()}_${randomUUID11().slice(0, 6)}`,
|
|
26132
25502
|
content: st,
|
|
26133
25503
|
status: "pending",
|
|
26134
25504
|
promotedFromPlan: item.id
|
|
@@ -26256,7 +25626,7 @@ ${cat}:`);
|
|
|
26256
25626
|
|
|
26257
25627
|
// src/storage/queue-store.ts
|
|
26258
25628
|
import * as fsp15 from "node:fs/promises";
|
|
26259
|
-
import * as
|
|
25629
|
+
import * as path26 from "node:path";
|
|
26260
25630
|
var QueueStore = class {
|
|
26261
25631
|
file;
|
|
26262
25632
|
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
@@ -26265,7 +25635,7 @@ var QueueStore = class {
|
|
|
26265
25635
|
traceId;
|
|
26266
25636
|
logger;
|
|
26267
25637
|
constructor(opts) {
|
|
26268
|
-
this.file =
|
|
25638
|
+
this.file = path26.join(opts.dir, "queue.json");
|
|
26269
25639
|
this.events = opts.events;
|
|
26270
25640
|
this.traceId = opts.traceId;
|
|
26271
25641
|
this.logger = opts.logger;
|
|
@@ -26423,7 +25793,7 @@ function isPersistedQueueItem(v) {
|
|
|
26423
25793
|
// src/storage/recovery-lock.ts
|
|
26424
25794
|
import * as fsp16 from "node:fs/promises";
|
|
26425
25795
|
import * as os3 from "node:os";
|
|
26426
|
-
import * as
|
|
25796
|
+
import * as path27 from "node:path";
|
|
26427
25797
|
var LOCK_FILE = "active.json";
|
|
26428
25798
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
26429
25799
|
var RecoveryLock = class {
|
|
@@ -26434,7 +25804,7 @@ var RecoveryLock = class {
|
|
|
26434
25804
|
sessionStore;
|
|
26435
25805
|
probe;
|
|
26436
25806
|
constructor(opts) {
|
|
26437
|
-
this.file =
|
|
25807
|
+
this.file = path27.join(opts.dir, LOCK_FILE);
|
|
26438
25808
|
this.pid = opts.pid ?? process.pid;
|
|
26439
25809
|
this.hostname = opts.hostname ?? os3.hostname();
|
|
26440
25810
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -26495,7 +25865,7 @@ var RecoveryLock = class {
|
|
|
26495
25865
|
* null return before calling this.
|
|
26496
25866
|
*/
|
|
26497
25867
|
async write(sessionId) {
|
|
26498
|
-
await ensureDir(
|
|
25868
|
+
await ensureDir(path27.dirname(this.file));
|
|
26499
25869
|
const lock = {
|
|
26500
25870
|
v: 1,
|
|
26501
25871
|
sessionId,
|
|
@@ -26800,8 +26170,8 @@ var DefaultSessionReader = class _DefaultSessionReader {
|
|
|
26800
26170
|
const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
|
|
26801
26171
|
let mtimeMs = null;
|
|
26802
26172
|
try {
|
|
26803
|
-
const
|
|
26804
|
-
mtimeMs =
|
|
26173
|
+
const stat12 = await fs14.stat(sessionPath);
|
|
26174
|
+
mtimeMs = stat12.mtimeMs;
|
|
26805
26175
|
} catch {
|
|
26806
26176
|
this.eventCache.delete(sessionId);
|
|
26807
26177
|
this.eventCacheMtimes.delete(sessionId);
|
|
@@ -27319,7 +26689,6 @@ export {
|
|
|
27319
26689
|
DefaultErrorHandler,
|
|
27320
26690
|
DefaultHealthRegistry,
|
|
27321
26691
|
DefaultLogger,
|
|
27322
|
-
DefaultMemoryStore,
|
|
27323
26692
|
DefaultModeStore,
|
|
27324
26693
|
DefaultModelsRegistry,
|
|
27325
26694
|
DefaultMultiAgentCoordinator,
|