@wrongstack/core 0.287.0 → 0.291.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/agent-status-tracker.d.ts +13 -0
- package/dist/agent-status-tracker.d.ts.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/chronicle/context.d.ts +13 -0
- package/dist/chronicle/context.d.ts.map +1 -0
- package/dist/chronicle/decision-adapter.d.ts +13 -0
- package/dist/chronicle/decision-adapter.d.ts.map +1 -0
- package/dist/chronicle/domain-adapter.d.ts +13 -0
- package/dist/chronicle/domain-adapter.d.ts.map +1 -0
- package/dist/chronicle/file-observer.d.ts +20 -0
- package/dist/chronicle/file-observer.d.ts.map +1 -0
- package/dist/chronicle/health-monitor.d.ts +11 -0
- package/dist/chronicle/health-monitor.d.ts.map +1 -0
- package/dist/chronicle/identity.d.ts +18 -0
- package/dist/chronicle/identity.d.ts.map +1 -0
- package/dist/chronicle/index.d.ts +16 -0
- package/dist/chronicle/index.d.ts.map +1 -0
- package/dist/chronicle/index.js +2555 -0
- package/dist/chronicle/index.js.map +7 -0
- package/dist/chronicle/journal.d.ts +81 -0
- package/dist/chronicle/journal.d.ts.map +1 -0
- package/dist/chronicle/process-adapter.d.ts +14 -0
- package/dist/chronicle/process-adapter.d.ts.map +1 -0
- package/dist/chronicle/prompt-manifest.d.ts +34 -0
- package/dist/chronicle/prompt-manifest.d.ts.map +1 -0
- package/dist/chronicle/provider-adapter.d.ts +13 -0
- package/dist/chronicle/provider-adapter.d.ts.map +1 -0
- package/dist/chronicle/query.d.ts +108 -0
- package/dist/chronicle/query.d.ts.map +1 -0
- package/dist/chronicle/rollup-adapter.d.ts +14 -0
- package/dist/chronicle/rollup-adapter.d.ts.map +1 -0
- package/dist/chronicle/stream-adapter.d.ts +13 -0
- package/dist/chronicle/stream-adapter.d.ts.map +1 -0
- package/dist/chronicle/tool-adapter.d.ts +15 -0
- package/dist/chronicle/tool-adapter.d.ts.map +1 -0
- package/dist/chronicle/types.d.ts +82 -0
- package/dist/chronicle/types.d.ts.map +1 -0
- package/dist/coordination/agent-monitor.d.ts +40 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -1
- package/dist/coordination/agent-subagent-runner.d.ts +7 -1
- package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
- package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
- package/dist/coordination/autonomous-coordinator.d.ts +2 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
- package/dist/coordination/brain-monitor.d.ts +1 -1
- package/dist/coordination/brain.d.ts +3 -2
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/collab-debug.d.ts +0 -9
- package/dist/coordination/collab-debug.d.ts.map +1 -1
- package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
- package/dist/coordination/dep-watcher.d.ts +2 -1
- package/dist/coordination/dep-watcher.d.ts.map +1 -1
- package/dist/coordination/director/director-collab.d.ts +1 -1
- package/dist/coordination/director/director-collab.d.ts.map +1 -1
- package/dist/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +10 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/file-author-tracker.d.ts.map +1 -1
- package/dist/coordination/fleet-bus.d.ts.map +1 -1
- package/dist/coordination/fleet-event-validation.d.ts +0 -7
- package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
- package/dist/coordination/fleet-spawn.d.ts.map +1 -1
- package/dist/coordination/fleet-supervisor.d.ts +1 -1
- package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +0 -44
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +7 -1
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +5 -3
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2426 -299
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +2 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/large-answer-store.d.ts +0 -6
- package/dist/coordination/large-answer-store.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts +2 -2
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-hooks.d.ts +2 -0
- package/dist/coordination/mailbox-hooks.d.ts.map +1 -1
- package/dist/coordination/mailbox-http-router.d.ts +43 -0
- package/dist/coordination/mailbox-http-router.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +35 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-tool.d.ts +1 -1
- package/dist/coordination/mailbox-tool.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +170 -10
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/mailbox.d.ts +1 -1
- package/dist/coordination/mailbox.d.ts.map +1 -1
- package/dist/coordination/package-author-tracker.d.ts.map +1 -1
- package/dist/coordination/provider-status-tracker.d.ts +236 -0
- package/dist/coordination/provider-status-tracker.d.ts.map +1 -0
- package/dist/coordination/subagent-budget.d.ts +2 -1
- package/dist/coordination/subagent-budget.d.ts.map +1 -1
- package/dist/core/agent-loop.d.ts +8 -3
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +5 -4
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent.d.ts +4 -2
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/context.d.ts +55 -1
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts +14 -8
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/fallback-profile-manager.d.ts +118 -0
- package/dist/core/fallback-profile-manager.d.ts.map +1 -0
- package/dist/core/fleet-pulse.d.ts +2 -1
- package/dist/core/fleet-pulse.d.ts.map +1 -1
- package/dist/core/iteration-limit.d.ts +2 -1
- package/dist/core/iteration-limit.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +16 -1
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/model-availability-calendar.d.ts +27 -0
- package/dist/core/model-availability-calendar.d.ts.map +1 -0
- package/dist/core/provider-runner.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +2 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.js +1920 -598
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +3 -2
- 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 +28 -49
- package/dist/execution/council-brain.d.ts.map +1 -1
- package/dist/execution/council-orchestrator.d.ts +30 -0
- package/dist/execution/council-orchestrator.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +8 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +2 -2
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/eternal-autonomy.d.ts.map +1 -1
- package/dist/execution/index.js +2600 -1903
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +0 -16
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/model-runtime.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts +9 -1
- package/dist/execution/one-shot-llm.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +36 -0
- package/dist/execution/prompt-enhancer.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.js +158 -10
- package/dist/execution/prompt-enhancer.js.map +2 -2
- package/dist/execution/retry-policy.d.ts +0 -13
- package/dist/execution/retry-policy.d.ts.map +1 -1
- package/dist/execution/skill-loader.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/goal/checkpoint.d.ts.map +1 -0
- package/dist/{autophase/auto-phase-planner.d.ts → goal/goal-planner.d.ts} +8 -8
- package/dist/goal/goal-planner.d.ts.map +1 -0
- package/dist/{autophase/auto-phase-runner.d.ts → goal/goal-runner.d.ts} +10 -10
- package/dist/goal/goal-runner.d.ts.map +1 -0
- package/dist/{autophase → goal}/index.d.ts +3 -3
- package/dist/goal/index.d.ts.map +1 -0
- package/dist/goal/phase-graph-builder.d.ts.map +1 -0
- package/dist/{autophase → goal}/phase-orchestrator.d.ts +2 -2
- package/dist/goal/phase-orchestrator.d.ts.map +1 -0
- package/dist/goal/phase-store.d.ts.map +1 -0
- package/dist/{autophase → goal}/types.d.ts +2 -2
- package/dist/goal/types.d.ts.map +1 -0
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/commands.d.ts +2 -1
- package/dist/hq/commands.d.ts.map +1 -1
- package/dist/hq/exposure.d.ts +66 -0
- package/dist/hq/exposure.d.ts.map +1 -0
- package/dist/hq/index.d.ts +1 -0
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +190 -21
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/mailbox-mapper.d.ts +13 -1
- package/dist/hq/mailbox-mapper.d.ts.map +1 -1
- package/dist/hq/protocol/browser.d.ts +9 -1
- package/dist/hq/protocol/browser.d.ts.map +1 -1
- package/dist/hq/protocol/client.d.ts +2 -2
- package/dist/hq/protocol/client.d.ts.map +1 -1
- package/dist/hq/protocol/core.d.ts.map +1 -1
- package/dist/hq/protocol/mailbox.d.ts +6 -0
- package/dist/hq/protocol/mailbox.d.ts.map +1 -1
- package/dist/hq/protocol/session.d.ts +4 -0
- package/dist/hq/protocol/session.d.ts.map +1 -1
- package/dist/hq/publisher.d.ts.map +1 -1
- package/dist/index.d.ts +34 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30287 -22214
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +60 -5
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/kernel/events/agent-events.d.ts +33 -1
- package/dist/kernel/events/agent-events.d.ts.map +1 -1
- package/dist/kernel/events/file-events.d.ts +65 -0
- package/dist/kernel/events/file-events.d.ts.map +1 -0
- package/dist/kernel/events/memory-events.d.ts +36 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/network-events.d.ts +60 -0
- package/dist/kernel/events/network-events.d.ts.map +1 -0
- package/dist/kernel/events/process-events.d.ts +44 -0
- package/dist/kernel/events/process-events.d.ts.map +1 -0
- package/dist/kernel/events/provider-events.d.ts +89 -0
- package/dist/kernel/events/provider-events.d.ts.map +1 -1
- package/dist/kernel/events/tool-events.d.ts +10 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -1
- package/dist/kernel/events/worktree-events.d.ts +1 -1
- package/dist/kernel/events.d.ts +14 -1
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js +2 -1
- package/dist/kernel/index.js.map +2 -2
- package/dist/kernel/tokens.d.ts +3 -1
- package/dist/kernel/tokens.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/models/index.js.map +2 -2
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +278 -0
- package/dist/observability/index.js.map +4 -4
- package/dist/observability/network-telemetry.d.ts +15 -0
- package/dist/observability/network-telemetry.d.ts.map +1 -0
- package/dist/observability/process-telemetry.d.ts +18 -0
- package/dist/observability/process-telemetry.d.ts.map +1 -0
- package/dist/observability/redact-command.d.ts +20 -0
- package/dist/observability/redact-command.d.ts.map +1 -0
- package/dist/plugins/auto-review-plugin.d.ts +56 -3
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
- package/dist/plugins/chimera-plugin.d.ts +164 -5
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/plugins/review-context-builder.d.ts +52 -0
- package/dist/plugins/review-context-builder.d.ts.map +1 -0
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +186 -9
- package/dist/security/index.js.map +4 -4
- package/dist/security/kanban-boundary.d.ts +10 -0
- package/dist/security/kanban-boundary.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +1 -0
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/session-registry.d.ts +80 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/storage/config-loader.d.ts +7 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/file-session-writer.d.ts +20 -7
- package/dist/storage/file-session-writer.d.ts.map +1 -1
- package/dist/storage/goal-coordination.d.ts +40 -0
- package/dist/storage/goal-coordination.d.ts.map +1 -0
- package/dist/storage/goal-store.d.ts +9 -5
- package/dist/storage/goal-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1066 -226
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +49 -4
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/memory-graph-backend.d.ts +11 -0
- package/dist/storage/memory-graph-backend.d.ts.map +1 -1
- package/dist/storage/provider-config-watcher.d.ts +19 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -1
- package/dist/storage/session-event-bridge.d.ts +2 -2
- package/dist/storage/session-event-bridge.d.ts.map +1 -1
- package/dist/storage/session-resume-validation.d.ts +10 -0
- package/dist/storage/session-resume-validation.d.ts.map +1 -1
- package/dist/storage/session-rewind-apply.d.ts +53 -0
- package/dist/storage/session-rewind-apply.d.ts.map +1 -0
- package/dist/storage/session-rewinder.d.ts.map +1 -1
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/tasking/index.js.map +1 -1
- package/dist/tasking/task-store.d.ts +1 -1
- package/dist/tools/council-tool.d.ts +3 -0
- package/dist/tools/council-tool.d.ts.map +1 -1
- package/dist/tools/fallback-manage-tools.d.ts +82 -0
- package/dist/tools/fallback-manage-tools.d.ts.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3423 -192
- package/dist/tools/index.js.map +4 -4
- package/dist/tools/one-shot-llm-tool.d.ts +2 -0
- package/dist/tools/one-shot-llm-tool.d.ts.map +1 -1
- package/dist/types/config.d.ts +76 -7
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/file-event-record.d.ts +68 -0
- package/dist/types/file-event-record.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +13 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/memory.d.ts +12 -0
- package/dist/types/memory.d.ts.map +1 -1
- package/dist/types/multi-agent.d.ts +12 -1
- package/dist/types/multi-agent.d.ts.map +1 -1
- package/dist/types/one-shot-llm.d.ts +16 -7
- package/dist/types/one-shot-llm.d.ts.map +1 -1
- package/dist/types/provider.d.ts +1 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/session.d.ts +68 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/types/tool.d.ts +5 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/index.d.ts +10 -10
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +497 -254
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/message-invariants.d.ts +9 -0
- package/dist/utils/message-invariants.d.ts.map +1 -1
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.d.ts.map +1 -1
- package/dist/utils/term.d.ts +279 -0
- package/dist/utils/term.d.ts.map +1 -1
- package/dist/utils/tool-wire-compact.d.ts +9 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +12 -2
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/dist/worktree/worktree-manager.d.ts +1 -1
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/instructions/agents/ios.md +113 -0
- package/instructions/agents/search.md +1 -1
- package/instructions/llm/chimera-review.md +66 -9
- package/instructions/llm/memory-consolidator.md +4 -12
- package/instructions/llm/prompt-enhancer.md +3 -1
- package/instructions/sections/tool/common-patterns.md +3 -3
- package/instructions/system.md +40 -5
- package/package.json +7 -3
- package/skills/auto-review/SKILL.md +176 -0
- package/skills/mnemosyne/SKILL.md +156 -0
- package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
- package/dist/autophase/auto-phase-planner.d.ts.map +0 -1
- package/dist/autophase/auto-phase-runner.d.ts.map +0 -1
- package/dist/autophase/checkpoint.d.ts.map +0 -1
- package/dist/autophase/index.d.ts.map +0 -1
- package/dist/autophase/phase-graph-builder.d.ts.map +0 -1
- package/dist/autophase/phase-orchestrator.d.ts.map +0 -1
- package/dist/autophase/phase-store.d.ts.map +0 -1
- package/dist/autophase/types.d.ts.map +0 -1
- /package/dist/{autophase → goal}/checkpoint.d.ts +0 -0
- /package/dist/{autophase → goal}/phase-graph-builder.d.ts +0 -0
- /package/dist/{autophase → goal}/phase-store.d.ts +0 -0
- /package/instructions/{autophase → goal}/phase-planner.md +0 -0
package/dist/defaults/index.js
CHANGED
|
@@ -36,8 +36,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
let mode;
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
40
|
-
mode =
|
|
39
|
+
const stat13 = await fs.stat(targetPath);
|
|
40
|
+
mode = stat13.mode & 511;
|
|
41
41
|
} catch {
|
|
42
42
|
mode = opts.mode;
|
|
43
43
|
}
|
|
@@ -66,7 +66,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
66
66
|
const dir = path.dirname(targetPath);
|
|
67
67
|
await fs.mkdir(dir, { recursive: true });
|
|
68
68
|
const lockPath = path.join(dir, `.${path.basename(targetPath)}.lock`);
|
|
69
|
-
const timeoutMs = opts.timeoutMs ??
|
|
69
|
+
const timeoutMs = opts.timeoutMs ?? 15e3;
|
|
70
70
|
const staleMs = opts.staleMs ?? 3e4;
|
|
71
71
|
const started = Date.now();
|
|
72
72
|
let handle;
|
|
@@ -76,6 +76,13 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
76
76
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
77
77
|
break;
|
|
78
78
|
} catch (err) {
|
|
79
|
+
if (handle) {
|
|
80
|
+
await handle.close().catch(() => {
|
|
81
|
+
});
|
|
82
|
+
await fs.unlink(lockPath).catch(() => {
|
|
83
|
+
});
|
|
84
|
+
handle = void 0;
|
|
85
|
+
}
|
|
79
86
|
const code = err.code;
|
|
80
87
|
if (code === "ENOENT") {
|
|
81
88
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -83,8 +90,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
83
90
|
}
|
|
84
91
|
if (code !== "EEXIST" && code !== "EPERM") throw err;
|
|
85
92
|
try {
|
|
86
|
-
const
|
|
87
|
-
if (Date.now() -
|
|
93
|
+
const stat13 = await fs.stat(lockPath);
|
|
94
|
+
if (Date.now() - stat13.mtimeMs > staleMs) {
|
|
88
95
|
await fs.unlink(lockPath);
|
|
89
96
|
continue;
|
|
90
97
|
}
|
|
@@ -120,13 +127,13 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
120
127
|
const parentDir = path.dirname(lockPath);
|
|
121
128
|
const lockName = path.basename(lockPath);
|
|
122
129
|
const intervalMs = Math.min(remainingMs, 100);
|
|
123
|
-
return new Promise((
|
|
130
|
+
return new Promise((resolve14) => {
|
|
124
131
|
let settled = false;
|
|
125
132
|
let watcher = null;
|
|
126
133
|
const timer = setTimeout(() => {
|
|
127
134
|
settled = true;
|
|
128
135
|
watcher?.close();
|
|
129
|
-
|
|
136
|
+
resolve14();
|
|
130
137
|
}, intervalMs);
|
|
131
138
|
try {
|
|
132
139
|
watcher = watchDir(parentDir, (eventType, filename) => {
|
|
@@ -135,14 +142,14 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
135
142
|
settled = true;
|
|
136
143
|
clearTimeout(timer);
|
|
137
144
|
watcher?.close();
|
|
138
|
-
|
|
145
|
+
resolve14();
|
|
139
146
|
}
|
|
140
147
|
});
|
|
141
148
|
} catch {
|
|
142
149
|
clearTimeout(timer);
|
|
143
150
|
if (!settled) {
|
|
144
151
|
settled = true;
|
|
145
|
-
setTimeout(
|
|
152
|
+
setTimeout(resolve14, Math.min(remainingMs, 25));
|
|
146
153
|
}
|
|
147
154
|
return;
|
|
148
155
|
}
|
|
@@ -154,7 +161,7 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
154
161
|
settled = true;
|
|
155
162
|
clearTimeout(timer);
|
|
156
163
|
watcher?.close();
|
|
157
|
-
|
|
164
|
+
resolve14();
|
|
158
165
|
}
|
|
159
166
|
}
|
|
160
167
|
);
|
|
@@ -178,7 +185,7 @@ async function renameWithRetry(from, to) {
|
|
|
178
185
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
179
186
|
throw err;
|
|
180
187
|
}
|
|
181
|
-
await new Promise((
|
|
188
|
+
await new Promise((resolve14) => setTimeout(resolve14, delays[i]));
|
|
182
189
|
}
|
|
183
190
|
}
|
|
184
191
|
throw lastErr;
|
|
@@ -336,6 +343,12 @@ function writeTo(s, stream) {
|
|
|
336
343
|
function writeErr(s, stream = process.stderr) {
|
|
337
344
|
return writeTo(s, stream);
|
|
338
345
|
}
|
|
346
|
+
var ESCAPE_TERMINATOR = Object.freeze({
|
|
347
|
+
BEL: "\x07",
|
|
348
|
+
// OSC 0 / OSC 2 (title)
|
|
349
|
+
ST: "\x1B\\"
|
|
350
|
+
// DCS / strict OSC
|
|
351
|
+
});
|
|
339
352
|
|
|
340
353
|
// src/utils/color.ts
|
|
341
354
|
var isColorTty = () => {
|
|
@@ -618,8 +631,8 @@ async function expandGlob(pattern) {
|
|
|
618
631
|
for (const e of entries) {
|
|
619
632
|
const full = `${dir}${SEP}${e}`;
|
|
620
633
|
try {
|
|
621
|
-
const
|
|
622
|
-
if (
|
|
634
|
+
const stat13 = await fsp.stat(full);
|
|
635
|
+
if (stat13.isDirectory()) await walk3(full, rest);
|
|
623
636
|
} catch {
|
|
624
637
|
}
|
|
625
638
|
}
|
|
@@ -636,8 +649,8 @@ async function expandGlob(pattern) {
|
|
|
636
649
|
if (entries.includes(seg)) {
|
|
637
650
|
const full = `${dir}${SEP}${seg}`;
|
|
638
651
|
try {
|
|
639
|
-
const
|
|
640
|
-
if (
|
|
652
|
+
const stat13 = await fsp.stat(full);
|
|
653
|
+
if (stat13.isDirectory()) await walk3(full, rest);
|
|
641
654
|
} catch {
|
|
642
655
|
}
|
|
643
656
|
}
|
|
@@ -828,10 +841,10 @@ function validateAgainstSchema(value, schema) {
|
|
|
828
841
|
return { ok: errors.length === 0, errors };
|
|
829
842
|
}
|
|
830
843
|
var MAX_SCHEMA_DEPTH = 64;
|
|
831
|
-
function walk(value, schema,
|
|
844
|
+
function walk(value, schema, path29, errors, depth) {
|
|
832
845
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
833
846
|
errors.push({
|
|
834
|
-
path:
|
|
847
|
+
path: path29 || "<root>",
|
|
835
848
|
message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
|
|
836
849
|
});
|
|
837
850
|
return;
|
|
@@ -839,7 +852,7 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
839
852
|
if (schema.enum !== void 0) {
|
|
840
853
|
if (!enumIncludes(schema.enum, value)) {
|
|
841
854
|
errors.push({
|
|
842
|
-
path:
|
|
855
|
+
path: path29 || "<root>",
|
|
843
856
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
844
857
|
});
|
|
845
858
|
return;
|
|
@@ -848,7 +861,7 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
848
861
|
if (typeof schema.type === "string") {
|
|
849
862
|
if (!checkType(value, schema.type)) {
|
|
850
863
|
errors.push({
|
|
851
|
-
path:
|
|
864
|
+
path: path29 || "<root>",
|
|
852
865
|
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
853
866
|
});
|
|
854
867
|
return;
|
|
@@ -860,7 +873,7 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
860
873
|
if (!(req in obj)) {
|
|
861
874
|
const expected = schema.properties?.[req]?.type;
|
|
862
875
|
errors.push({
|
|
863
|
-
path: joinPath(
|
|
876
|
+
path: joinPath(path29, req),
|
|
864
877
|
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
865
878
|
});
|
|
866
879
|
}
|
|
@@ -868,14 +881,14 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
868
881
|
if (schema.properties) {
|
|
869
882
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
870
883
|
if (key in obj) {
|
|
871
|
-
walk(obj[key], subSchema, joinPath(
|
|
884
|
+
walk(obj[key], subSchema, joinPath(path29, key), errors, depth + 1);
|
|
872
885
|
}
|
|
873
886
|
}
|
|
874
887
|
}
|
|
875
888
|
}
|
|
876
889
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
877
890
|
for (let i = 0; i < value.length; i++) {
|
|
878
|
-
walk(value[i], schema.items, `${
|
|
891
|
+
walk(value[i], schema.items, `${path29}[${i}]`, errors, depth + 1);
|
|
879
892
|
}
|
|
880
893
|
}
|
|
881
894
|
}
|
|
@@ -1069,6 +1082,16 @@ function stripUndefined(obj) {
|
|
|
1069
1082
|
}
|
|
1070
1083
|
|
|
1071
1084
|
// src/utils/message-invariants.ts
|
|
1085
|
+
function hasMeaningfulContent(content) {
|
|
1086
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
1087
|
+
return content.some((block) => {
|
|
1088
|
+
if (block.type === "text") return block.text.trim().length > 0;
|
|
1089
|
+
if (block.type === "thinking") {
|
|
1090
|
+
return block.thinking.trim().length > 0 || Boolean(block.signature);
|
|
1091
|
+
}
|
|
1092
|
+
return true;
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1072
1095
|
function repairToolUseAdjacency(messages) {
|
|
1073
1096
|
const removedToolUses = [];
|
|
1074
1097
|
const removedToolResults = [];
|
|
@@ -1156,8 +1179,7 @@ function mapContent(msg, fn) {
|
|
|
1156
1179
|
return { ...msg, content: next };
|
|
1157
1180
|
}
|
|
1158
1181
|
function isEmptyMessage(msg) {
|
|
1159
|
-
|
|
1160
|
-
return msg.content.length === 0;
|
|
1182
|
+
return !hasMeaningfulContent(msg.content);
|
|
1161
1183
|
}
|
|
1162
1184
|
|
|
1163
1185
|
// src/utils/regex-guard.ts
|
|
@@ -1198,7 +1220,7 @@ function compileUserRegex(pattern, flags) {
|
|
|
1198
1220
|
|
|
1199
1221
|
// src/utils/safe-json.ts
|
|
1200
1222
|
function safeParse(input, maxBytes = 5e6) {
|
|
1201
|
-
if (input
|
|
1223
|
+
if (Buffer.byteLength(input, "utf8") > maxBytes) {
|
|
1202
1224
|
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
1203
1225
|
}
|
|
1204
1226
|
try {
|
|
@@ -1254,84 +1276,21 @@ function invalid(sessionId) {
|
|
|
1254
1276
|
});
|
|
1255
1277
|
}
|
|
1256
1278
|
|
|
1279
|
+
// src/utils/sleep.ts
|
|
1280
|
+
function sleep(ms) {
|
|
1281
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1257
1284
|
// src/utils/slug.ts
|
|
1258
1285
|
function slugify(name, fallback = "prompt", maxLen = 64) {
|
|
1259
1286
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
|
|
1260
1287
|
}
|
|
1261
1288
|
|
|
1262
|
-
// src/utils/ulid.ts
|
|
1263
|
-
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1264
|
-
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
1265
|
-
var ENCODING_LEN = ENCODING.length;
|
|
1266
|
-
var TIME_LEN = 10;
|
|
1267
|
-
var RANDOM_LEN = 16;
|
|
1268
|
-
function encodeTime(now, len) {
|
|
1269
|
-
let mod;
|
|
1270
|
-
let str = "";
|
|
1271
|
-
for (let i = len - 1; i >= 0; i--) {
|
|
1272
|
-
mod = now % ENCODING_LEN;
|
|
1273
|
-
str = ENCODING[mod] + str;
|
|
1274
|
-
now = (now - mod) / ENCODING_LEN;
|
|
1275
|
-
}
|
|
1276
|
-
return str;
|
|
1277
|
-
}
|
|
1278
|
-
function encodeRandom(len) {
|
|
1279
|
-
const bytes = randomBytes2(len);
|
|
1280
|
-
let str = "";
|
|
1281
|
-
for (let i = 0; i < len; i++) {
|
|
1282
|
-
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
1283
|
-
}
|
|
1284
|
-
return str;
|
|
1285
|
-
}
|
|
1286
|
-
function ulid(seedTime = Date.now()) {
|
|
1287
|
-
return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
// src/utils/sleep.ts
|
|
1291
|
-
function sleep(ms) {
|
|
1292
|
-
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
1289
|
// src/utils/string.ts
|
|
1296
1290
|
function truncate(s, max) {
|
|
1297
1291
|
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1298
1292
|
}
|
|
1299
1293
|
|
|
1300
|
-
// src/utils/tool-subject.ts
|
|
1301
|
-
var GLOB_METACHARACTERS = /[*?[\]]/g;
|
|
1302
|
-
function escapeGlobSubject(value) {
|
|
1303
|
-
return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
|
|
1304
|
-
}
|
|
1305
|
-
function normalizePathSubject(value) {
|
|
1306
|
-
return escapeGlobSubject(value.replace(/\\/g, "/"));
|
|
1307
|
-
}
|
|
1308
|
-
function isPathSubjectKey(subjectKey) {
|
|
1309
|
-
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
1310
|
-
}
|
|
1311
|
-
function subjectForToolInput(toolName, input, subjectKey) {
|
|
1312
|
-
if (!input || typeof input !== "object") return void 0;
|
|
1313
|
-
const obj = input;
|
|
1314
|
-
if (subjectKey) {
|
|
1315
|
-
const value = obj[subjectKey];
|
|
1316
|
-
if (typeof value === "string") {
|
|
1317
|
-
return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
if (toolName === "bash" && typeof obj.command === "string") {
|
|
1321
|
-
return escapeGlobSubject(obj.command);
|
|
1322
|
-
}
|
|
1323
|
-
if (typeof obj.path === "string") {
|
|
1324
|
-
return normalizePathSubject(obj.path);
|
|
1325
|
-
}
|
|
1326
|
-
if (typeof obj.url === "string") {
|
|
1327
|
-
return escapeGlobSubject(obj.url);
|
|
1328
|
-
}
|
|
1329
|
-
if (typeof obj.name === "string") {
|
|
1330
|
-
return escapeGlobSubject(obj.name);
|
|
1331
|
-
}
|
|
1332
|
-
return void 0;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
1294
|
// src/utils/tool-wire-compact.ts
|
|
1336
1295
|
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
1337
1296
|
var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
|
|
@@ -1348,9 +1307,11 @@ function compactToolDefinitionForWire(tool, opts = {}) {
|
|
|
1348
1307
|
tool.description ?? "",
|
|
1349
1308
|
opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
|
|
1350
1309
|
),
|
|
1351
|
-
inputSchema:
|
|
1352
|
-
|
|
1353
|
-
|
|
1310
|
+
inputSchema: normalizeTopLevelToolSchema(
|
|
1311
|
+
compactSchemaDescriptions(
|
|
1312
|
+
tool.inputSchema,
|
|
1313
|
+
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
1314
|
+
)
|
|
1354
1315
|
)
|
|
1355
1316
|
};
|
|
1356
1317
|
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
@@ -1358,6 +1319,44 @@ function compactToolDefinitionForWire(tool, opts = {}) {
|
|
|
1358
1319
|
}
|
|
1359
1320
|
return compact;
|
|
1360
1321
|
}
|
|
1322
|
+
function normalizeTopLevelToolSchema(schema) {
|
|
1323
|
+
const combinators = ["oneOf", "anyOf", "allOf"].map((keyword) => ({ keyword, branches: schema[keyword] })).filter(
|
|
1324
|
+
(entry) => Array.isArray(entry.branches)
|
|
1325
|
+
);
|
|
1326
|
+
if (combinators.length === 0) return schema;
|
|
1327
|
+
const out = { ...schema, type: "object" };
|
|
1328
|
+
delete out["oneOf"];
|
|
1329
|
+
delete out["anyOf"];
|
|
1330
|
+
delete out["allOf"];
|
|
1331
|
+
const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
|
|
1332
|
+
let required = stringSet(schema["required"]);
|
|
1333
|
+
for (const { keyword, branches } of combinators) {
|
|
1334
|
+
const objectBranches = branches.filter(isRecord);
|
|
1335
|
+
for (const branch of objectBranches) {
|
|
1336
|
+
if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
|
|
1337
|
+
}
|
|
1338
|
+
const branchRequired = objectBranches.map((branch) => stringSet(branch["required"]));
|
|
1339
|
+
if (keyword === "allOf") {
|
|
1340
|
+
for (const fields of branchRequired) for (const field of fields) required.add(field);
|
|
1341
|
+
} else if (branchRequired.length > 0) {
|
|
1342
|
+
const common = new Set(
|
|
1343
|
+
[...branchRequired[0]].filter(
|
|
1344
|
+
(field) => branchRequired.slice(1).every((fields) => fields.has(field))
|
|
1345
|
+
)
|
|
1346
|
+
);
|
|
1347
|
+
required = /* @__PURE__ */ new Set([...required, ...common]);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
out["properties"] = properties;
|
|
1351
|
+
if (required.size > 0) out["required"] = [...required];
|
|
1352
|
+
else delete out["required"];
|
|
1353
|
+
return out;
|
|
1354
|
+
}
|
|
1355
|
+
function stringSet(value) {
|
|
1356
|
+
return new Set(
|
|
1357
|
+
Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1361
1360
|
function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
|
|
1362
1361
|
const compact = compactSchemaNode(schema, maxDescriptionChars);
|
|
1363
1362
|
return isRecord(compact) ? compact : { type: "object", properties: {} };
|
|
@@ -2252,6 +2251,69 @@ function resolveToolResultRenderMode(modes, toolName) {
|
|
|
2252
2251
|
return normalizeToolResultRenderMode(modes?.[toolName]) ?? DEFAULT_TOOL_RESULT_RENDER_MODE;
|
|
2253
2252
|
}
|
|
2254
2253
|
|
|
2254
|
+
// src/utils/tool-subject.ts
|
|
2255
|
+
var GLOB_METACHARACTERS = /[*?[\]]/g;
|
|
2256
|
+
function escapeGlobSubject(value) {
|
|
2257
|
+
return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
|
|
2258
|
+
}
|
|
2259
|
+
function normalizePathSubject(value) {
|
|
2260
|
+
return escapeGlobSubject(value.replace(/\\/g, "/"));
|
|
2261
|
+
}
|
|
2262
|
+
function isPathSubjectKey(subjectKey) {
|
|
2263
|
+
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
2264
|
+
}
|
|
2265
|
+
function subjectForToolInput(toolName, input, subjectKey) {
|
|
2266
|
+
if (!input || typeof input !== "object") return void 0;
|
|
2267
|
+
const obj = input;
|
|
2268
|
+
if (subjectKey) {
|
|
2269
|
+
const value = obj[subjectKey];
|
|
2270
|
+
if (typeof value === "string") {
|
|
2271
|
+
return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
if (toolName === "bash" && typeof obj.command === "string") {
|
|
2275
|
+
return escapeGlobSubject(obj.command);
|
|
2276
|
+
}
|
|
2277
|
+
if (typeof obj.path === "string") {
|
|
2278
|
+
return normalizePathSubject(obj.path);
|
|
2279
|
+
}
|
|
2280
|
+
if (typeof obj.url === "string") {
|
|
2281
|
+
return escapeGlobSubject(obj.url);
|
|
2282
|
+
}
|
|
2283
|
+
if (typeof obj.name === "string") {
|
|
2284
|
+
return escapeGlobSubject(obj.name);
|
|
2285
|
+
}
|
|
2286
|
+
return void 0;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// src/utils/ulid.ts
|
|
2290
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2291
|
+
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
2292
|
+
var ENCODING_LEN = ENCODING.length;
|
|
2293
|
+
var TIME_LEN = 10;
|
|
2294
|
+
var RANDOM_LEN = 16;
|
|
2295
|
+
function encodeTime(now, len) {
|
|
2296
|
+
let mod;
|
|
2297
|
+
let str = "";
|
|
2298
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
2299
|
+
mod = now % ENCODING_LEN;
|
|
2300
|
+
str = ENCODING[mod] + str;
|
|
2301
|
+
now = (now - mod) / ENCODING_LEN;
|
|
2302
|
+
}
|
|
2303
|
+
return str;
|
|
2304
|
+
}
|
|
2305
|
+
function encodeRandom(len) {
|
|
2306
|
+
const bytes = randomBytes2(len);
|
|
2307
|
+
let str = "";
|
|
2308
|
+
for (let i = 0; i < len; i++) {
|
|
2309
|
+
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
2310
|
+
}
|
|
2311
|
+
return str;
|
|
2312
|
+
}
|
|
2313
|
+
function ulid(seedTime = Date.now()) {
|
|
2314
|
+
return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2255
2317
|
// src/utils/wstack-paths.ts
|
|
2256
2318
|
import { createHash } from "node:crypto";
|
|
2257
2319
|
import * as fs2 from "node:fs";
|
|
@@ -2281,8 +2343,8 @@ function projectHash(absRoot) {
|
|
|
2281
2343
|
function projectSlug(absRoot) {
|
|
2282
2344
|
const identityRoot = canonicalProjectRoot(absRoot);
|
|
2283
2345
|
const base = slugify2(path3.basename(identityRoot));
|
|
2284
|
-
const
|
|
2285
|
-
return `${base}-${
|
|
2346
|
+
const hash2 = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
2347
|
+
return `${base}-${hash2}`;
|
|
2286
2348
|
}
|
|
2287
2349
|
function slugify2(name) {
|
|
2288
2350
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
@@ -2295,7 +2357,7 @@ function wstackGlobalRoot() {
|
|
|
2295
2357
|
function resolveWstackPaths(opts) {
|
|
2296
2358
|
const globalRoot = opts.globalRoot ?? (opts.userHome ? path3.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
2297
2359
|
const homeDir = opts.userHome ?? os.homedir();
|
|
2298
|
-
const
|
|
2360
|
+
const hash2 = projectHash(opts.projectRoot);
|
|
2299
2361
|
const slug = projectSlug(opts.projectRoot);
|
|
2300
2362
|
const projectDir = path3.join(globalRoot, "projects", slug);
|
|
2301
2363
|
return {
|
|
@@ -2304,6 +2366,11 @@ function resolveWstackPaths(opts) {
|
|
|
2304
2366
|
homeDir,
|
|
2305
2367
|
configDir: globalRoot,
|
|
2306
2368
|
globalConfig: path3.join(globalRoot, "config.json"),
|
|
2369
|
+
profilesDir: path3.join(globalRoot, "profiles"),
|
|
2370
|
+
profileConfig: (name) => {
|
|
2371
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2372
|
+
return path3.join(globalRoot, "profiles", safe || "default", "config.json");
|
|
2373
|
+
},
|
|
2307
2374
|
secretsKey: path3.join(globalRoot, ".key"),
|
|
2308
2375
|
globalMemory: path3.join(globalRoot, "memory.md"),
|
|
2309
2376
|
globalSkills: path3.join(globalRoot, "skills"),
|
|
@@ -2332,7 +2399,7 @@ function resolveWstackPaths(opts) {
|
|
|
2332
2399
|
inProjectInstructions: path3.join(opts.projectRoot, ".wrongstack", "instructions"),
|
|
2333
2400
|
inProjectDesignKits: path3.join(opts.projectRoot, ".wrongstack", "design-kits"),
|
|
2334
2401
|
inProjectWorktrees: path3.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
2335
|
-
projectHash:
|
|
2402
|
+
projectHash: hash2,
|
|
2336
2403
|
projectSlug: slug,
|
|
2337
2404
|
projectGoal: path3.join(projectDir, "goal.json"),
|
|
2338
2405
|
projectInputHistory: path3.join(projectDir, "input-history.json"),
|
|
@@ -2636,7 +2703,7 @@ var InMemoryAgentBridge = class {
|
|
|
2636
2703
|
});
|
|
2637
2704
|
}
|
|
2638
2705
|
this.inflightGuards.add(correlationId);
|
|
2639
|
-
return new Promise((
|
|
2706
|
+
return new Promise((resolve14, reject) => {
|
|
2640
2707
|
const timer = setTimeout(() => {
|
|
2641
2708
|
this.inflightGuards.delete(correlationId);
|
|
2642
2709
|
this.pendingRequests.delete(correlationId);
|
|
@@ -2655,7 +2722,7 @@ var InMemoryAgentBridge = class {
|
|
|
2655
2722
|
return;
|
|
2656
2723
|
}
|
|
2657
2724
|
this.pendingRequests.set(correlationId, {
|
|
2658
|
-
resolve:
|
|
2725
|
+
resolve: resolve14,
|
|
2659
2726
|
reject,
|
|
2660
2727
|
timer
|
|
2661
2728
|
});
|
|
@@ -2898,7 +2965,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2898
2965
|
*
|
|
2899
2966
|
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
2900
2967
|
*/
|
|
2901
|
-
checkLimits(
|
|
2968
|
+
checkLimits(elapsedMs2) {
|
|
2902
2969
|
const exceeded = [];
|
|
2903
2970
|
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
2904
2971
|
exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
|
|
@@ -2913,14 +2980,14 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2913
2980
|
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
2914
2981
|
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
2915
2982
|
}
|
|
2916
|
-
if (
|
|
2983
|
+
if (elapsedMs2 !== void 0) {
|
|
2917
2984
|
const idle = this.idleMs();
|
|
2918
2985
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
2919
2986
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
2920
2987
|
}
|
|
2921
2988
|
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
2922
|
-
if (this.limits.timeoutMs !== void 0 &&
|
|
2923
|
-
exceeded.push({ kind: "timeout", used:
|
|
2989
|
+
if (this.limits.timeoutMs !== void 0 && elapsedMs2 > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
2990
|
+
exceeded.push({ kind: "timeout", used: elapsedMs2, limit: this.limits.timeoutMs });
|
|
2924
2991
|
}
|
|
2925
2992
|
}
|
|
2926
2993
|
if (exceeded.length === 0) return [];
|
|
@@ -3004,13 +3071,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3004
3071
|
if (!bus?.hasListenerFor("budget.threshold_reached")) {
|
|
3005
3072
|
return Promise.resolve("stop");
|
|
3006
3073
|
}
|
|
3007
|
-
return new Promise((
|
|
3074
|
+
return new Promise((resolve14) => {
|
|
3008
3075
|
let resolved = false;
|
|
3009
3076
|
const respond = (d) => {
|
|
3010
3077
|
if (resolved) return;
|
|
3011
3078
|
resolved = true;
|
|
3012
3079
|
clearTimeout(fallback);
|
|
3013
|
-
|
|
3080
|
+
resolve14(d);
|
|
3014
3081
|
};
|
|
3015
3082
|
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
3016
3083
|
const sessionId = this.currentSessionId();
|
|
@@ -3316,8 +3383,8 @@ function stringArray(value) {
|
|
|
3316
3383
|
|
|
3317
3384
|
// src/coordination/agent-subagent-runner.ts
|
|
3318
3385
|
function withDisabledToolFiltering(factory) {
|
|
3319
|
-
return async (config) => {
|
|
3320
|
-
const result = await factory(config);
|
|
3386
|
+
return async (config, task) => {
|
|
3387
|
+
const result = await factory(config, task);
|
|
3321
3388
|
const disabled = config.disabledTools ?? [];
|
|
3322
3389
|
if (disabled.length === 0) return result;
|
|
3323
3390
|
const registry = result.agent.tools;
|
|
@@ -3333,10 +3400,36 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3333
3400
|
const format = opts.formatTaskInput ?? defaultFormatTaskInput;
|
|
3334
3401
|
return async (task, ctx) => {
|
|
3335
3402
|
const taskStartedAt = Date.now();
|
|
3336
|
-
const factoryResult = await opts.factory(ctx.config);
|
|
3403
|
+
const factoryResult = await opts.factory(ctx.config, task);
|
|
3337
3404
|
const { agent, events } = factoryResult;
|
|
3405
|
+
const taskContext = task.context ?? {};
|
|
3406
|
+
const hasExplicitTelemetryTask = typeof taskContext["telemetryTaskId"] === "string";
|
|
3407
|
+
const telemetryTaskId = hasExplicitTelemetryTask ? taskContext["telemetryTaskId"] : task.id;
|
|
3408
|
+
const telemetryRunId = typeof taskContext["telemetryRunId"] === "string" ? taskContext["telemetryRunId"] : void 0;
|
|
3409
|
+
const telemetryBoardId = typeof taskContext["telemetryBoardId"] === "string" ? taskContext["telemetryBoardId"] : void 0;
|
|
3338
3410
|
const agentContext = agent.ctx;
|
|
3339
|
-
if (agentContext?.meta)
|
|
3411
|
+
if (agentContext?.meta) {
|
|
3412
|
+
agentContext.meta["subagentTaskId"] = task.id;
|
|
3413
|
+
const kanbanContext = taskContext["kanban"];
|
|
3414
|
+
if (kanbanContext && typeof kanbanContext === "object") {
|
|
3415
|
+
agentContext.meta["kanban"] = kanbanContext;
|
|
3416
|
+
}
|
|
3417
|
+
if (hasExplicitTelemetryTask) {
|
|
3418
|
+
agentContext.meta["telemetryTaskId"] = telemetryTaskId;
|
|
3419
|
+
if (telemetryRunId) agentContext.meta["telemetryRunId"] = telemetryRunId;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
if (hasExplicitTelemetryTask) {
|
|
3423
|
+
agentContext?.setCurrentKanbanTask?.(telemetryTaskId, telemetryBoardId);
|
|
3424
|
+
} else {
|
|
3425
|
+
const kanbanContext = taskContext["kanban"];
|
|
3426
|
+
if (kanbanContext && typeof kanbanContext === "object") {
|
|
3427
|
+
const kanban = kanbanContext;
|
|
3428
|
+
const boardId = typeof kanban["boardId"] === "string" ? kanban["boardId"] : void 0;
|
|
3429
|
+
const taskId = typeof kanban["taskId"] === "string" ? kanban["taskId"] : void 0;
|
|
3430
|
+
if (taskId) agentContext?.setCurrentKanbanTask?.(taskId, boardId);
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3340
3433
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
3341
3434
|
const aborter = new AbortController();
|
|
3342
3435
|
ctx.budget._events = events;
|
|
@@ -3361,6 +3454,27 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3361
3454
|
const unsub = [];
|
|
3362
3455
|
unsub.push(
|
|
3363
3456
|
events.on("tool.executed", (e) => {
|
|
3457
|
+
if (hasExplicitTelemetryTask) {
|
|
3458
|
+
opts.hostEvents?.emit("subagent.tool_executed", {
|
|
3459
|
+
sessionId: e.sessionId,
|
|
3460
|
+
subagentId: ctx.subagentId,
|
|
3461
|
+
agentSessionId: e.sessionId,
|
|
3462
|
+
agentName: e.agentName ?? ctx.config.name,
|
|
3463
|
+
taskId: telemetryTaskId,
|
|
3464
|
+
runId: telemetryRunId,
|
|
3465
|
+
traceId: e.traceId,
|
|
3466
|
+
id: e.id,
|
|
3467
|
+
name: e.name,
|
|
3468
|
+
durationMs: e.durationMs,
|
|
3469
|
+
ok: e.ok,
|
|
3470
|
+
// Raw tool inputs may contain credentials or sensitive arguments.
|
|
3471
|
+
// Task telemetry crosses from the isolated worker bus to the shared
|
|
3472
|
+
// host bus, so expose only execution metadata at this boundary.
|
|
3473
|
+
outputBytes: e.outputBytes,
|
|
3474
|
+
outputTokens: e.outputTokens,
|
|
3475
|
+
outputLines: e.outputLines
|
|
3476
|
+
});
|
|
3477
|
+
}
|
|
3364
3478
|
try {
|
|
3365
3479
|
ctx.budget.recordToolCall();
|
|
3366
3480
|
} catch (eb) {
|
|
@@ -3372,6 +3486,17 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3372
3486
|
lastToolFailed = null;
|
|
3373
3487
|
}
|
|
3374
3488
|
}),
|
|
3489
|
+
events.on("file.event", (e) => {
|
|
3490
|
+
if (!hasExplicitTelemetryTask) return;
|
|
3491
|
+
if (e.runId === telemetryRunId && e.taskId === telemetryTaskId) return;
|
|
3492
|
+
opts.hostEvents?.emit("file.event", {
|
|
3493
|
+
...e,
|
|
3494
|
+
scope: "task",
|
|
3495
|
+
taskId: telemetryTaskId,
|
|
3496
|
+
boardId: telemetryBoardId,
|
|
3497
|
+
runId: telemetryRunId
|
|
3498
|
+
});
|
|
3499
|
+
}),
|
|
3375
3500
|
events.on("provider.response", (e) => {
|
|
3376
3501
|
try {
|
|
3377
3502
|
ctx.budget.recordUsage(e.usage);
|
|
@@ -4665,6 +4790,40 @@ var DOMAIN_AGENTS = [
|
|
|
4665
4790
|
"information architecture"
|
|
4666
4791
|
]
|
|
4667
4792
|
}
|
|
4793
|
+
},
|
|
4794
|
+
{
|
|
4795
|
+
config: {
|
|
4796
|
+
id: "ios",
|
|
4797
|
+
name: "iOS",
|
|
4798
|
+
role: "ios",
|
|
4799
|
+
tools: [...TOOLS.build, "fetch"],
|
|
4800
|
+
prompt: agentPrompt("ios")
|
|
4801
|
+
},
|
|
4802
|
+
budget: HEAVY_BUDGET,
|
|
4803
|
+
capability: {
|
|
4804
|
+
phase: "domain",
|
|
4805
|
+
summary: "Apple-platform app development in Swift against the latest Xcode/iOS-SDK line: SwiftUI, UIKit, SwiftData, concurrency, accessibility, and App Store submission.",
|
|
4806
|
+
keywords: [
|
|
4807
|
+
"ios",
|
|
4808
|
+
"iphone",
|
|
4809
|
+
"ipad",
|
|
4810
|
+
"ipados",
|
|
4811
|
+
"watchos",
|
|
4812
|
+
"tvos",
|
|
4813
|
+
"visionos",
|
|
4814
|
+
"macos",
|
|
4815
|
+
"swift",
|
|
4816
|
+
"swiftui",
|
|
4817
|
+
"uikit",
|
|
4818
|
+
"xcode",
|
|
4819
|
+
"swiftdata",
|
|
4820
|
+
"app store",
|
|
4821
|
+
"app intents",
|
|
4822
|
+
"swift package manager",
|
|
4823
|
+
"cocoapods",
|
|
4824
|
+
"foundation models"
|
|
4825
|
+
]
|
|
4826
|
+
}
|
|
4668
4827
|
}
|
|
4669
4828
|
];
|
|
4670
4829
|
|
|
@@ -5239,7 +5398,11 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
5239
5398
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
5240
5399
|
maxIterations: 1e3,
|
|
5241
5400
|
maxToolCalls: 2500,
|
|
5242
|
-
|
|
5401
|
+
// Monitoring passes routinely include a large system prompt plus fleet
|
|
5402
|
+
// context. 30k was below observed single-pass usage and therefore triggered
|
|
5403
|
+
// a post-response negotiation every run; 96k leaves headroom while the cost
|
|
5404
|
+
// and tool/iteration caps still bound runaway work.
|
|
5405
|
+
maxTokens: 96e3,
|
|
5243
5406
|
maxCostUsd: 0.5
|
|
5244
5407
|
},
|
|
5245
5408
|
...Object.fromEntries(
|
|
@@ -5642,7 +5805,7 @@ function createDelegateTool(opts) {
|
|
|
5642
5805
|
};
|
|
5643
5806
|
}
|
|
5644
5807
|
async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abortSignal) {
|
|
5645
|
-
return new Promise((
|
|
5808
|
+
return new Promise((resolve14) => {
|
|
5646
5809
|
let settled = false;
|
|
5647
5810
|
let timer;
|
|
5648
5811
|
let offAbort = () => {
|
|
@@ -5655,7 +5818,7 @@ async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abo
|
|
|
5655
5818
|
offIter();
|
|
5656
5819
|
offProgress();
|
|
5657
5820
|
offAbort();
|
|
5658
|
-
|
|
5821
|
+
resolve14(value);
|
|
5659
5822
|
};
|
|
5660
5823
|
const arm = () => {
|
|
5661
5824
|
if (timer) clearTimeout(timer);
|
|
@@ -6351,7 +6514,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
6351
6514
|
}
|
|
6352
6515
|
for (const filePath of allFiles) {
|
|
6353
6516
|
try {
|
|
6354
|
-
const [content,
|
|
6517
|
+
const [content, stat13] = await Promise.all([
|
|
6355
6518
|
fsp4.readFile(filePath, "utf8"),
|
|
6356
6519
|
fsp4.stat(filePath)
|
|
6357
6520
|
]);
|
|
@@ -6361,8 +6524,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
6361
6524
|
path: filePath,
|
|
6362
6525
|
content,
|
|
6363
6526
|
language,
|
|
6364
|
-
snapshotMtimeMs:
|
|
6365
|
-
snapshotSizeBytes:
|
|
6527
|
+
snapshotMtimeMs: stat13.mtimeMs,
|
|
6528
|
+
snapshotSizeBytes: stat13.size
|
|
6366
6529
|
});
|
|
6367
6530
|
} catch {
|
|
6368
6531
|
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
@@ -6612,7 +6775,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
6612
6775
|
level: "warning" /* WARNING */,
|
|
6613
6776
|
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
6614
6777
|
budgetKind: payload.kind,
|
|
6615
|
-
|
|
6778
|
+
// `used` is elapsed milliseconds for timeout kinds. `timeoutMs` is the
|
|
6779
|
+
// negotiation response deadline (normally 60s), not agent runtime.
|
|
6780
|
+
elapsedMs: payload.kind === "timeout" || payload.kind === "idle_timeout" ? payload.used : void 0,
|
|
6616
6781
|
limit: payload.limit,
|
|
6617
6782
|
btwNotes
|
|
6618
6783
|
};
|
|
@@ -6636,12 +6801,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
6636
6801
|
return;
|
|
6637
6802
|
}
|
|
6638
6803
|
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
6639
|
-
const newLimit = Math.min(
|
|
6640
|
-
Math.ceil((payload.timeoutMs ?? payload.limit) * 2),
|
|
6641
|
-
24 * 60 * 6e4
|
|
6642
|
-
);
|
|
6804
|
+
const newLimit = Math.min(Math.ceil(payload.limit * 2), 24 * 60 * 6e4);
|
|
6643
6805
|
setImmediate(() => {
|
|
6644
|
-
payload.
|
|
6806
|
+
const field = payload.kind === "timeout" ? "timeoutMs" : "idleTimeoutMs";
|
|
6807
|
+
payload.extend({ [field]: newLimit });
|
|
6645
6808
|
});
|
|
6646
6809
|
return;
|
|
6647
6810
|
}
|
|
@@ -6781,9 +6944,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
6781
6944
|
for (const file of this.snapshot.files) {
|
|
6782
6945
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
6783
6946
|
try {
|
|
6784
|
-
const
|
|
6785
|
-
const mtimeChanged = file.snapshotMtimeMs !== void 0 &&
|
|
6786
|
-
const sizeChanged = file.snapshotSizeBytes !== void 0 &&
|
|
6947
|
+
const stat13 = await fsp4.stat(file.path);
|
|
6948
|
+
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat13.mtimeMs > file.snapshotMtimeMs + 1;
|
|
6949
|
+
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat13.size !== file.snapshotSizeBytes;
|
|
6787
6950
|
if (mtimeChanged || sizeChanged) {
|
|
6788
6951
|
warnings.push(`${file.path} changed after the collab snapshot was captured.`);
|
|
6789
6952
|
}
|
|
@@ -7046,6 +7209,8 @@ function rosterSummaryFromConfigs(roster) {
|
|
|
7046
7209
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
7047
7210
|
import {
|
|
7048
7211
|
claimReadyTask,
|
|
7212
|
+
describeKanbanBoundary,
|
|
7213
|
+
heartbeatTaskAssignment,
|
|
7049
7214
|
listReadyTasks,
|
|
7050
7215
|
updateTaskAssignment
|
|
7051
7216
|
} from "@wrongstack/kanban";
|
|
@@ -7800,13 +7965,19 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7800
7965
|
const projectRoot = ctx.projectRoot;
|
|
7801
7966
|
if (!projectRoot) return { error: "kanban_queue requires ctx.projectRoot." };
|
|
7802
7967
|
const maxTasks = Math.max(1, Math.min(20, Math.floor(i.maxTasks ?? 1)));
|
|
7803
|
-
const
|
|
7968
|
+
const baseLeaseTtlMs = Math.max(1e3, Math.floor(i.leaseTtlMs ?? 5 * 60 * 1e3));
|
|
7969
|
+
const leaseTtlMs = i.awaitCompletion === true && i.timeoutMs !== void 0 ? Math.max(baseLeaseTtlMs, i.timeoutMs + 6e4) : baseLeaseTtlMs;
|
|
7970
|
+
const heartbeatIntervalMs = Math.max(
|
|
7971
|
+
5e3,
|
|
7972
|
+
i.heartbeatIntervalMs ?? Math.floor(leaseTtlMs / 2)
|
|
7973
|
+
);
|
|
7974
|
+
const effectiveLeaseTtlMs = Math.max(leaseTtlMs, heartbeatIntervalMs * 2);
|
|
7804
7975
|
const claimedAt = nowIso();
|
|
7805
7976
|
const leaseSeeding = {
|
|
7806
7977
|
leaseId: randomUUID4(),
|
|
7807
7978
|
claimedAt,
|
|
7808
7979
|
heartbeatAt: claimedAt,
|
|
7809
|
-
leaseExpiresAt: new Date(Date.now() +
|
|
7980
|
+
leaseExpiresAt: new Date(Date.now() + effectiveLeaseTtlMs).toISOString()
|
|
7810
7981
|
};
|
|
7811
7982
|
const candidateTaskIds = i.taskId !== void 0 ? [i.taskId] : i.query ? (await listReadyTasks(projectRoot, {
|
|
7812
7983
|
...i.boardId !== void 0 ? { boardId: i.boardId } : {}
|
|
@@ -7814,9 +7985,11 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7814
7985
|
const dispatches = [];
|
|
7815
7986
|
const errors = [];
|
|
7816
7987
|
const resultFailures = [];
|
|
7988
|
+
const budgetRejectedTaskIds = /* @__PURE__ */ new Set();
|
|
7817
7989
|
for (let index = 0; index < maxTasks; index++) {
|
|
7818
7990
|
const candidateTaskId = candidateTaskIds?.[index];
|
|
7819
7991
|
if (candidateTaskIds && !candidateTaskId) break;
|
|
7992
|
+
if (candidateTaskId && budgetRejectedTaskIds.has(candidateTaskId)) continue;
|
|
7820
7993
|
const claim = await claimReadyTask(projectRoot, {
|
|
7821
7994
|
...i.boardId !== void 0 ? { boardId: i.boardId } : {},
|
|
7822
7995
|
...candidateTaskId !== void 0 ? { taskId: candidateTaskId } : {},
|
|
@@ -7841,14 +8014,22 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7841
8014
|
if (costCeiling !== void 0) {
|
|
7842
8015
|
const remaining = director.getRemainingBudgetUsd();
|
|
7843
8016
|
if (remaining !== void 0 && remaining < costCeiling) {
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
8017
|
+
budgetRejectedTaskIds.add(claim.task.id);
|
|
8018
|
+
const budgetError = `Cost ceiling ${costCeiling} exceeds remaining budget ${remaining.toFixed(4)}`;
|
|
8019
|
+
await updateTaskAssignment(
|
|
8020
|
+
projectRoot,
|
|
8021
|
+
claim.board.id,
|
|
8022
|
+
claim.task.id,
|
|
8023
|
+
{
|
|
8024
|
+
...claim.task.assignment ?? {},
|
|
8025
|
+
status: "failed",
|
|
8026
|
+
error: budgetError
|
|
8027
|
+
},
|
|
8028
|
+
{ expectedLeaseId: leaseSeeding.leaseId }
|
|
8029
|
+
);
|
|
7849
8030
|
errors.push({
|
|
7850
8031
|
taskId: claim.task.id,
|
|
7851
|
-
error:
|
|
8032
|
+
error: budgetError
|
|
7852
8033
|
});
|
|
7853
8034
|
continue;
|
|
7854
8035
|
}
|
|
@@ -7856,31 +8037,75 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7856
8037
|
try {
|
|
7857
8038
|
const config = buildKanbanSubagentConfig(claim.task, i, roster);
|
|
7858
8039
|
subagentId = await director.spawn(config);
|
|
7859
|
-
|
|
7860
|
-
|
|
8040
|
+
const dispatchTaskId = randomUUID4();
|
|
8041
|
+
const taskSpec = {
|
|
8042
|
+
id: dispatchTaskId,
|
|
7861
8043
|
subagentId,
|
|
7862
8044
|
description: buildKanbanFleetTaskPrompt(claim.board, claim.task, {
|
|
7863
|
-
|
|
7864
|
-
|
|
8045
|
+
// The worker must heartbeat against the lease it actually holds.
|
|
8046
|
+
// effectiveLeaseTtlMs is the real lease window (raised from the
|
|
8047
|
+
// requested TTL when needed so the first heartbeat always lands
|
|
8048
|
+
// before expiry), and the computed heartbeatIntervalMs is always
|
|
8049
|
+
// strictly shorter than that window. Passing the raw requested
|
|
8050
|
+
// values here would give the worker a 60s cadence against a
|
|
8051
|
+
// 10s lease, letting the lease expire before its first renewal.
|
|
8052
|
+
heartbeatIntervalMs,
|
|
8053
|
+
leaseTtlMs: effectiveLeaseTtlMs,
|
|
7865
8054
|
leaseId: leaseSeeding.leaseId,
|
|
7866
8055
|
leaseExpiresAt: leaseSeeding.leaseExpiresAt
|
|
7867
8056
|
}),
|
|
7868
8057
|
...i.maxToolCalls !== void 0 ? { maxToolCalls: i.maxToolCalls } : {},
|
|
7869
8058
|
...i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : {},
|
|
7870
8059
|
context: {
|
|
8060
|
+
...claim.task.type !== void 0 ? { taskType: claim.task.type } : {},
|
|
7871
8061
|
kanban: {
|
|
7872
8062
|
boardId: claim.board.id,
|
|
7873
8063
|
taskId: claim.task.id,
|
|
7874
|
-
origin: claim.task.origin
|
|
8064
|
+
origin: claim.task.origin,
|
|
8065
|
+
projectRoot
|
|
7875
8066
|
}
|
|
7876
8067
|
}
|
|
7877
|
-
}
|
|
7878
|
-
await updateTaskAssignment(
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
8068
|
+
};
|
|
8069
|
+
const dispatched = await updateTaskAssignment(
|
|
8070
|
+
projectRoot,
|
|
8071
|
+
claim.board.id,
|
|
8072
|
+
claim.task.id,
|
|
8073
|
+
{
|
|
8074
|
+
...claim.task.assignment ?? {},
|
|
8075
|
+
status: "running",
|
|
8076
|
+
subagentId,
|
|
8077
|
+
runTaskId: dispatchTaskId,
|
|
8078
|
+
// Renew the lease from the actual dispatch moment, not from claim
|
|
8079
|
+
// time. The claim-time lease starts before spawn + queueing, so
|
|
8080
|
+
// `timeoutMs + 60_000` can expire while the worker is still
|
|
8081
|
+
// queued or running, letting recover_stale reclaim and duplicate
|
|
8082
|
+
// the assignment (see auto-extend.ts: timeout extensions can push
|
|
8083
|
+
// execution well past the original timeoutMs). Both heartbeatAt
|
|
8084
|
+
// and leaseExpiresAt are stamped from now so the worker has a
|
|
8085
|
+
// full lease window from the moment it was actually dispatched.
|
|
8086
|
+
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8087
|
+
leaseExpiresAt: new Date(Date.now() + effectiveLeaseTtlMs).toISOString()
|
|
8088
|
+
},
|
|
8089
|
+
{ expectedLeaseId: leaseSeeding.leaseId }
|
|
8090
|
+
);
|
|
8091
|
+
if (dispatched === null) {
|
|
8092
|
+
try {
|
|
8093
|
+
await director.terminate(subagentId);
|
|
8094
|
+
} catch (cleanupErr) {
|
|
8095
|
+
errors.push({
|
|
8096
|
+
taskId: claim.task.id,
|
|
8097
|
+
error: `Lease lost during dispatch; orphan subagent ${subagentId} termination failed: ${toErrorMessage(cleanupErr)}`
|
|
8098
|
+
});
|
|
8099
|
+
continue;
|
|
8100
|
+
}
|
|
8101
|
+
errors.push({
|
|
8102
|
+
taskId: claim.task.id,
|
|
8103
|
+
error: `Lease lost during dispatch (reassigned by recovery); subagent ${subagentId} terminated.`
|
|
8104
|
+
});
|
|
8105
|
+
continue;
|
|
8106
|
+
}
|
|
8107
|
+
await director.assign(taskSpec);
|
|
8108
|
+
runTaskId = dispatchTaskId;
|
|
7884
8109
|
dispatches.push({
|
|
7885
8110
|
boardId: claim.board.id,
|
|
7886
8111
|
taskId: claim.task.id,
|
|
@@ -7899,28 +8124,97 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7899
8124
|
message = `${message}; cleanup failed for spawned subagent ${subagentId}: ${toErrorMessage(cleanupErr)}`;
|
|
7900
8125
|
}
|
|
7901
8126
|
}
|
|
7902
|
-
await updateTaskAssignment(
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
8127
|
+
await updateTaskAssignment(
|
|
8128
|
+
projectRoot,
|
|
8129
|
+
claim.board.id,
|
|
8130
|
+
claim.task.id,
|
|
8131
|
+
{
|
|
8132
|
+
...claim.task.assignment ?? {},
|
|
8133
|
+
status: "failed",
|
|
8134
|
+
...subagentId !== void 0 ? { subagentId } : {},
|
|
8135
|
+
...runTaskId !== void 0 ? { runTaskId } : {},
|
|
8136
|
+
error: message
|
|
8137
|
+
},
|
|
8138
|
+
{ expectedLeaseId: leaseSeeding.leaseId }
|
|
8139
|
+
);
|
|
7909
8140
|
errors.push({ taskId: claim.task.id, error: message });
|
|
7910
8141
|
}
|
|
7911
8142
|
}
|
|
8143
|
+
if (!i.awaitCompletion && dispatches.length > 0 && typeof director.fleet?.subscribe === "function") {
|
|
8144
|
+
const ourLeaseId = leaseSeeding.leaseId;
|
|
8145
|
+
const disposers = /* @__PURE__ */ new Map();
|
|
8146
|
+
const renewalStartedAt = Date.now();
|
|
8147
|
+
const maxRenewalWindowMs = 24 * 60 * 6e4;
|
|
8148
|
+
const stopAll = () => {
|
|
8149
|
+
clearInterval(renewalTimer);
|
|
8150
|
+
for (const dispose of disposers.values()) dispose();
|
|
8151
|
+
disposers.clear();
|
|
8152
|
+
};
|
|
8153
|
+
const renewalTimer = setInterval(() => {
|
|
8154
|
+
if (disposers.size === 0 || Date.now() - renewalStartedAt > maxRenewalWindowMs) {
|
|
8155
|
+
stopAll();
|
|
8156
|
+
return;
|
|
8157
|
+
}
|
|
8158
|
+
const refreshedExpiry = new Date(Date.now() + effectiveLeaseTtlMs).toISOString();
|
|
8159
|
+
for (const dispatch of dispatches) {
|
|
8160
|
+
if (!disposers.has(dispatch.runTaskId)) continue;
|
|
8161
|
+
void heartbeatTaskAssignment(projectRoot, dispatch.boardId, dispatch.taskId, {
|
|
8162
|
+
leaseExpiresAt: refreshedExpiry,
|
|
8163
|
+
expectedLeaseId: ourLeaseId
|
|
8164
|
+
}).catch(() => {
|
|
8165
|
+
});
|
|
8166
|
+
}
|
|
8167
|
+
}, heartbeatIntervalMs);
|
|
8168
|
+
renewalTimer.unref?.();
|
|
8169
|
+
for (const dispatch of dispatches) {
|
|
8170
|
+
const dispose = director.fleet.subscribe(dispatch.subagentId, (event) => {
|
|
8171
|
+
if (event.type !== "subagent.completed" && event.type !== "subagent.stopped") return;
|
|
8172
|
+
if (event.type === "subagent.completed" && event.taskId !== dispatch.runTaskId) return;
|
|
8173
|
+
disposers.get(dispatch.runTaskId)?.();
|
|
8174
|
+
disposers.delete(dispatch.runTaskId);
|
|
8175
|
+
if (disposers.size === 0) clearInterval(renewalTimer);
|
|
8176
|
+
});
|
|
8177
|
+
disposers.set(dispatch.runTaskId, dispose);
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
7912
8180
|
let results;
|
|
7913
8181
|
if (i.awaitCompletion && dispatches.length > 0) {
|
|
7914
|
-
|
|
8182
|
+
const ourLeaseId = leaseSeeding.leaseId;
|
|
8183
|
+
const runTaskIds = new Set(dispatches.map((dispatch) => dispatch.runTaskId));
|
|
8184
|
+
const heartbeatTimer = setInterval(async () => {
|
|
8185
|
+
const refreshedExpiry = new Date(Date.now() + effectiveLeaseTtlMs).toISOString();
|
|
8186
|
+
for (const dispatch of dispatches) {
|
|
8187
|
+
if (!runTaskIds.has(dispatch.runTaskId)) continue;
|
|
8188
|
+
try {
|
|
8189
|
+
await heartbeatTaskAssignment(projectRoot, dispatch.boardId, dispatch.taskId, {
|
|
8190
|
+
leaseExpiresAt: refreshedExpiry,
|
|
8191
|
+
expectedLeaseId: ourLeaseId
|
|
8192
|
+
});
|
|
8193
|
+
} catch {
|
|
8194
|
+
}
|
|
8195
|
+
}
|
|
8196
|
+
}, heartbeatIntervalMs);
|
|
8197
|
+
try {
|
|
8198
|
+
results = await director.awaitTasks(dispatches.map((dispatch) => dispatch.runTaskId));
|
|
8199
|
+
} finally {
|
|
8200
|
+
clearInterval(heartbeatTimer);
|
|
8201
|
+
}
|
|
7915
8202
|
for (const result of results) {
|
|
7916
8203
|
const dispatch = dispatches.find((candidate) => candidate.runTaskId === result.taskId);
|
|
7917
8204
|
if (!dispatch) continue;
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
8205
|
+
runTaskIds.delete(dispatch.runTaskId);
|
|
8206
|
+
await updateTaskAssignment(
|
|
8207
|
+
projectRoot,
|
|
8208
|
+
dispatch.boardId,
|
|
8209
|
+
dispatch.taskId,
|
|
8210
|
+
{
|
|
8211
|
+
status: result.status === "success" ? "completed" : "failed",
|
|
8212
|
+
subagentId: result.subagentId,
|
|
8213
|
+
runTaskId: result.taskId,
|
|
8214
|
+
...result.status === "success" ? { lastResult: resultToText(result) } : { error: resultErrorText(result) }
|
|
8215
|
+
},
|
|
8216
|
+
{ expectedLeaseId: ourLeaseId }
|
|
8217
|
+
);
|
|
7924
8218
|
if (result.status !== "success") {
|
|
7925
8219
|
resultFailures.push({
|
|
7926
8220
|
taskId: dispatch.taskId,
|
|
@@ -8028,6 +8322,16 @@ function buildKanbanFleetTaskPrompt(board, task, lease) {
|
|
|
8028
8322
|
task.origin.phaseId ? `phaseId: ${task.origin.phaseId}` : "",
|
|
8029
8323
|
task.origin.taskId ? `sourceTaskId: ${task.origin.taskId}` : ""
|
|
8030
8324
|
].filter(Boolean).join("\n") : "";
|
|
8325
|
+
const boundaries = [
|
|
8326
|
+
board.boundary?.enabled ? `board: ${describeKanbanBoundary(board.boundary)}` : "",
|
|
8327
|
+
task.boundary?.enabled ? `task: ${describeKanbanBoundary(task.boundary)}` : "",
|
|
8328
|
+
...(board.boundary?.allow ?? []).map(
|
|
8329
|
+
(selector) => `board allow ${selector.access} ${selector.kind}:${selector.path}`
|
|
8330
|
+
),
|
|
8331
|
+
...(task.boundary?.allow ?? []).map(
|
|
8332
|
+
(selector) => `task allow ${selector.access} ${selector.kind}:${selector.path}`
|
|
8333
|
+
)
|
|
8334
|
+
].filter(Boolean);
|
|
8031
8335
|
return [
|
|
8032
8336
|
"You are processing a claimed WrongStack Kanban task.",
|
|
8033
8337
|
"",
|
|
@@ -8050,6 +8354,8 @@ ${checks}` : "",
|
|
|
8050
8354
|
metrics ? `Goal metrics:
|
|
8051
8355
|
${metrics}` : "",
|
|
8052
8356
|
task.labels?.length ? `Labels: ${task.labels.join(", ")}` : "",
|
|
8357
|
+
boundaries.length ? `BOUNDARY CONTRACT (enforced by the tool runtime; do not attempt to bypass):
|
|
8358
|
+
${boundaries.map((line) => `- ${line}`).join("\n")}` : "",
|
|
8053
8359
|
"Lease contract (Sprint 1):",
|
|
8054
8360
|
`- leaseId: ${lease.leaseId}`,
|
|
8055
8361
|
`- claimedAt: ${task.assignment?.claimedAt ?? "<unknown>"}`,
|
|
@@ -8062,10 +8368,24 @@ ${metrics}` : "",
|
|
|
8062
8368
|
`- maxAttempts: ${task.assignment?.maxAttempts ?? "<unset>"}`,
|
|
8063
8369
|
`- costCeilingUsd: ${task.assignment?.costCeilingUsd ?? task.costCeilingUsd ?? "<unset>"}`,
|
|
8064
8370
|
"",
|
|
8065
|
-
"Work this task end-to-end.
|
|
8066
|
-
`
|
|
8067
|
-
|
|
8068
|
-
|
|
8371
|
+
"Work this task end-to-end. Treat the board as a live plan, not a frozen assignment snapshot.",
|
|
8372
|
+
`Before material work and whenever evidence changes the plan, call kanban with action "get_board" and boardId "${board.id}" to reassess current tasks, dependencies, priorities, and peer changes.`,
|
|
8373
|
+
"You may use kanban add_task, update_task, split_task, merge_tasks, move_task, delete_task, or dependency actions when reassessment shows the plan should change. Preserve traceability and do not keep obsolete work merely because it existed at dispatch time.",
|
|
8374
|
+
"",
|
|
8375
|
+
"FIELD MAINTENANCE \u2014 keep every task detail current:",
|
|
8376
|
+
'- Call "update_task" to refresh description, priority, type, labels, or estimatedHours as the work evolves.',
|
|
8377
|
+
'- Call "add_note" with author and content to record progress, decisions, or roadblock context.',
|
|
8378
|
+
'- Call "add_check" / "update_check" with description and status (pending/passed/failed) to track acceptance criteria.',
|
|
8379
|
+
'- Call "add_goal_metric" / "update_goal_metric" with name and current/target to report measurable progress.',
|
|
8380
|
+
'- Call "add_link" with url and type (issue/pr/doc/commit/design/file/url/other) to attach evidence.',
|
|
8381
|
+
'- Call "update_task" to set actualHours when you have a final time estimate.',
|
|
8382
|
+
"Do not leave stale or placeholder field values \u2014 the kanban board is the shared source of truth for every stakeholder.",
|
|
8383
|
+
"",
|
|
8384
|
+
"LEASE & LIFECYCLE:",
|
|
8385
|
+
"Every successful board mutation updates shared pending work and notifies the owning session. Re-read the board after mutations and adapt to changes made by other agents; never assume your initial todo list remains authoritative.",
|
|
8386
|
+
`When you start, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", assignmentStatus "running", and expectedLeaseId "${lease.leaseId}". Include subagentId and runTaskId when you have them. The expectedLeaseId fence makes the write a safe no-op if recover_stale already reassigned the task because your lease expired.`,
|
|
8387
|
+
`To stay alive in the queue, call kanban with action "heartbeat_assignment", boardId "${board.id}", taskId "${task.id}", heartbeatAt set to the current time, and expectedLeaseId "${lease.leaseId}". Cadence must be <= heartbeatIntervalMs and well before leaseExpiresAt. The expectedLeaseId fence makes the heartbeat a safe no-op if the lease was reassigned, so a stale worker cannot renew a successor's lease.`,
|
|
8388
|
+
`When you finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", assignmentStatus "completed" or "failed", and expectedLeaseId "${lease.leaseId}". Include lastResult or error. Populate every relevant detail field before the terminal mark_assignment so the card's final state is complete.`,
|
|
8069
8389
|
`If you cannot finish in time, call kanban with action "heartbeat_assignment" to extend the lease, or with action "release_task" to release so another worker can claim. Do NOT silently abandon the assignment.`,
|
|
8070
8390
|
'On failure the host may call kanban with action "recover_stale" (mode: retry/release/fail); respect its decisions and do not duplicate work in parallel.',
|
|
8071
8391
|
"When finished, report what changed, what you verified, and any remaining blockers."
|
|
@@ -8549,25 +8869,32 @@ var FleetBus = class {
|
|
|
8549
8869
|
}
|
|
8550
8870
|
emit(event) {
|
|
8551
8871
|
const byId = this.byId.get(event.subagentId);
|
|
8552
|
-
if (byId)
|
|
8553
|
-
|
|
8872
|
+
if (byId && byId.size > 0) {
|
|
8873
|
+
const snapshot = [...byId];
|
|
8874
|
+
for (const h of snapshot) {
|
|
8554
8875
|
try {
|
|
8555
8876
|
h(event);
|
|
8556
8877
|
} catch {
|
|
8557
8878
|
}
|
|
8558
8879
|
}
|
|
8880
|
+
}
|
|
8559
8881
|
const byType = this.byType.get(event.type);
|
|
8560
|
-
if (byType)
|
|
8561
|
-
|
|
8882
|
+
if (byType && byType.size > 0) {
|
|
8883
|
+
const snapshot = [...byType];
|
|
8884
|
+
for (const h of snapshot) {
|
|
8562
8885
|
try {
|
|
8563
8886
|
h(event);
|
|
8564
8887
|
} catch {
|
|
8565
8888
|
}
|
|
8566
8889
|
}
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8890
|
+
}
|
|
8891
|
+
if (this.any.size > 0) {
|
|
8892
|
+
const snapshot = [...this.any];
|
|
8893
|
+
for (const h of snapshot) {
|
|
8894
|
+
try {
|
|
8895
|
+
h(event);
|
|
8896
|
+
} catch {
|
|
8897
|
+
}
|
|
8571
8898
|
}
|
|
8572
8899
|
}
|
|
8573
8900
|
}
|
|
@@ -8749,21 +9076,28 @@ function hashStr(s) {
|
|
|
8749
9076
|
return (h >>> 0).toString(36);
|
|
8750
9077
|
}
|
|
8751
9078
|
|
|
9079
|
+
// src/types/blocks.ts
|
|
9080
|
+
function isTextBlock(b) {
|
|
9081
|
+
return b.type === "text";
|
|
9082
|
+
}
|
|
9083
|
+
|
|
8752
9084
|
// src/types/provider.ts
|
|
8753
9085
|
var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
|
|
8754
9086
|
var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
|
|
9087
|
+
var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit/i;
|
|
8755
9088
|
function classifyProviderError(status, body, message) {
|
|
8756
9089
|
const type = body?.type;
|
|
9090
|
+
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
8757
9091
|
if (status === 0) return "network";
|
|
8758
9092
|
if (status === 408) return "timeout";
|
|
8759
9093
|
if (status === 599) return "stream_hang";
|
|
9094
|
+
if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
|
|
8760
9095
|
if (type === "rate_limit_error" || status === 429) return "rate_limit";
|
|
8761
9096
|
if (type === "overloaded_error" || status === 529) return "overloaded";
|
|
8762
9097
|
if (status >= 500) return "server";
|
|
8763
9098
|
if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
|
|
8764
9099
|
return "auth";
|
|
8765
9100
|
}
|
|
8766
|
-
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
8767
9101
|
if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
|
|
8768
9102
|
if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
|
|
8769
9103
|
return "context_overflow";
|
|
@@ -8776,6 +9110,7 @@ function isRetryableKind(kind) {
|
|
|
8776
9110
|
}
|
|
8777
9111
|
var RETRYABLE_BY_KIND = {
|
|
8778
9112
|
rate_limit: true,
|
|
9113
|
+
quota_exhausted: false,
|
|
8779
9114
|
overloaded: true,
|
|
8780
9115
|
server: true,
|
|
8781
9116
|
timeout: true,
|
|
@@ -8854,6 +9189,7 @@ var KIND_TO_CODE = {
|
|
|
8854
9189
|
network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
8855
9190
|
timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
8856
9191
|
rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
9192
|
+
quota_exhausted: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
8857
9193
|
auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
|
|
8858
9194
|
overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
|
|
8859
9195
|
context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
|
|
@@ -8953,6 +9289,7 @@ function classifySubagentError(err, hints = {}) {
|
|
|
8953
9289
|
function providerErrorToSubagentError(err, message, cause) {
|
|
8954
9290
|
switch (err.kind) {
|
|
8955
9291
|
case "rate_limit":
|
|
9292
|
+
case "quota_exhausted":
|
|
8956
9293
|
return {
|
|
8957
9294
|
kind: "provider_rate_limit",
|
|
8958
9295
|
message,
|
|
@@ -9360,7 +9697,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9360
9697
|
taskIds.map((id) => {
|
|
9361
9698
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
9362
9699
|
if (cached) return cached;
|
|
9363
|
-
return new Promise((
|
|
9700
|
+
return new Promise((resolve14, reject) => {
|
|
9364
9701
|
const timeout = setTimeout(() => {
|
|
9365
9702
|
this.off("task.completed", handler);
|
|
9366
9703
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -9369,7 +9706,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9369
9706
|
if (result.taskId === id) {
|
|
9370
9707
|
clearTimeout(timeout);
|
|
9371
9708
|
this.off("task.completed", handler);
|
|
9372
|
-
|
|
9709
|
+
resolve14(result);
|
|
9373
9710
|
}
|
|
9374
9711
|
};
|
|
9375
9712
|
this.on("task.completed", handler);
|
|
@@ -9394,13 +9731,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9394
9731
|
const done = new Set(completed.map((r) => r.taskId));
|
|
9395
9732
|
return { completed, pending: taskIds.filter((id) => !done.has(id)) };
|
|
9396
9733
|
}
|
|
9397
|
-
return new Promise((
|
|
9734
|
+
return new Promise((resolve14) => {
|
|
9398
9735
|
let timer;
|
|
9399
9736
|
const handler = ({ result }) => {
|
|
9400
9737
|
if (!ids.has(result.taskId)) return;
|
|
9401
9738
|
if (timer) clearTimeout(timer);
|
|
9402
9739
|
this.off("task.completed", handler);
|
|
9403
|
-
|
|
9740
|
+
resolve14({
|
|
9404
9741
|
completed: [result],
|
|
9405
9742
|
pending: taskIds.filter((id) => id !== result.taskId)
|
|
9406
9743
|
});
|
|
@@ -9408,7 +9745,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9408
9745
|
if (opts?.timeoutMs !== void 0) {
|
|
9409
9746
|
timer = setTimeout(() => {
|
|
9410
9747
|
this.off("task.completed", handler);
|
|
9411
|
-
|
|
9748
|
+
resolve14({ completed: [], pending: [...taskIds], timedOut: true });
|
|
9412
9749
|
}, opts.timeoutMs);
|
|
9413
9750
|
}
|
|
9414
9751
|
this.on("task.completed", handler);
|
|
@@ -9690,12 +10027,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9690
10027
|
}
|
|
9691
10028
|
return new Promise((resolveDecision) => {
|
|
9692
10029
|
let settled = false;
|
|
9693
|
-
const
|
|
10030
|
+
const resolve14 = (d) => {
|
|
9694
10031
|
if (settled) return;
|
|
9695
10032
|
settled = true;
|
|
9696
10033
|
resolveDecision(d);
|
|
9697
10034
|
};
|
|
9698
|
-
const fallback = setTimeout(() =>
|
|
10035
|
+
const fallback = setTimeout(() => resolve14("stop"), DECISION_TIMEOUT_MS);
|
|
9699
10036
|
const sessionId = this.currentSessionId();
|
|
9700
10037
|
budget._events?.emit("budget.threshold_reached", {
|
|
9701
10038
|
...sessionId ? { sessionId } : {},
|
|
@@ -9713,11 +10050,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9713
10050
|
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
9714
10051
|
extend: (extra) => {
|
|
9715
10052
|
clearTimeout(fallback);
|
|
9716
|
-
queueMicrotask(() =>
|
|
10053
|
+
queueMicrotask(() => resolve14({ extend: extra }));
|
|
9717
10054
|
},
|
|
9718
10055
|
deny: () => {
|
|
9719
10056
|
clearTimeout(fallback);
|
|
9720
|
-
|
|
10057
|
+
resolve14("stop");
|
|
9721
10058
|
}
|
|
9722
10059
|
});
|
|
9723
10060
|
});
|
|
@@ -10340,6 +10677,8 @@ var Director = class _Director {
|
|
|
10340
10677
|
collab;
|
|
10341
10678
|
/** Prevents large `ask_subagent` answers from bloating the leader's context window. */
|
|
10342
10679
|
largeAnswerStore;
|
|
10680
|
+
/** Shared provider/model status tracker, or undefined. */
|
|
10681
|
+
statusTracker;
|
|
10343
10682
|
constructor(opts) {
|
|
10344
10683
|
this.id = opts.config.coordinatorId || randomUUID6();
|
|
10345
10684
|
this.brain = opts.brain;
|
|
@@ -10378,6 +10717,7 @@ var Director = class _Director {
|
|
|
10378
10717
|
opts.checkpointDebounceMs ?? 250
|
|
10379
10718
|
) : null;
|
|
10380
10719
|
this.fleetManager = opts.fleetManager;
|
|
10720
|
+
this.statusTracker = opts.statusTracker;
|
|
10381
10721
|
this.logger = opts.logger;
|
|
10382
10722
|
if (this.sharedScratchpadPath) {
|
|
10383
10723
|
void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
@@ -10500,6 +10840,14 @@ var Director = class _Director {
|
|
|
10500
10840
|
title
|
|
10501
10841
|
}
|
|
10502
10842
|
);
|
|
10843
|
+
if (failed) {
|
|
10844
|
+
void this.appendSessionEvent({
|
|
10845
|
+
type: "agent_error",
|
|
10846
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10847
|
+
agentId: r.subagentId,
|
|
10848
|
+
error: errorString ?? r.status
|
|
10849
|
+
});
|
|
10850
|
+
}
|
|
10503
10851
|
if (this.fleetManager) {
|
|
10504
10852
|
void this.fleetManager.flushManifest();
|
|
10505
10853
|
} else {
|
|
@@ -10582,53 +10930,53 @@ var Director = class _Director {
|
|
|
10582
10930
|
payload.extend(extra);
|
|
10583
10931
|
});
|
|
10584
10932
|
};
|
|
10585
|
-
if (this.brain) {
|
|
10586
|
-
|
|
10587
|
-
id: `director-budget-${e.subagentId}-${payload.kind}`,
|
|
10588
|
-
sessionId: this.currentSessionId(),
|
|
10589
|
-
source: "director",
|
|
10590
|
-
question: `Should the director extend the ${payload.kind} budget for subagent ${e.subagentId}?`,
|
|
10591
|
-
context: [
|
|
10592
|
-
e.taskId ? `Task id: ${e.taskId}` : void 0,
|
|
10593
|
-
`Used: ${payload.used}`,
|
|
10594
|
-
`Limit: ${payload.limit}`,
|
|
10595
|
-
`Prior extensions for this kind: ${prior}`
|
|
10596
|
-
].filter(Boolean).join("\n"),
|
|
10597
|
-
risk: payload.kind === "cost" ? "high" : "medium",
|
|
10598
|
-
fallback: "continue",
|
|
10599
|
-
options: [
|
|
10600
|
-
{
|
|
10601
|
-
id: "extend",
|
|
10602
|
-
label: "Grant the director default budget extension",
|
|
10603
|
-
consequence: "The subagent continues with a larger per-kind budget.",
|
|
10604
|
-
risk: payload.kind === "cost" ? "high" : "medium",
|
|
10605
|
-
recommended: true
|
|
10606
|
-
},
|
|
10607
|
-
{
|
|
10608
|
-
id: "stop",
|
|
10609
|
-
label: "Stop this subagent at the current budget limit",
|
|
10610
|
-
consequence: "The current task will fail or stop due to budget pressure.",
|
|
10611
|
-
risk: "low"
|
|
10612
|
-
}
|
|
10613
|
-
]
|
|
10614
|
-
}).then((decision) => {
|
|
10615
|
-
if (decision.type === "deny") {
|
|
10616
|
-
payload.deny();
|
|
10617
|
-
return;
|
|
10618
|
-
}
|
|
10619
|
-
if (decision.type === "ask_human") {
|
|
10620
|
-
payload.deny();
|
|
10621
|
-
return;
|
|
10622
|
-
}
|
|
10623
|
-
if (decision.optionId === "stop") {
|
|
10624
|
-
payload.deny();
|
|
10625
|
-
return;
|
|
10626
|
-
}
|
|
10627
|
-
grantExtension();
|
|
10628
|
-
}).catch(() => payload.deny());
|
|
10933
|
+
if (payload.kind !== "cost" || !this.brain) {
|
|
10934
|
+
grantExtension();
|
|
10629
10935
|
return;
|
|
10630
10936
|
}
|
|
10631
|
-
|
|
10937
|
+
void this.brain.decide({
|
|
10938
|
+
id: `director-budget-${e.subagentId}-${payload.kind}`,
|
|
10939
|
+
sessionId: this.currentSessionId(),
|
|
10940
|
+
source: "director",
|
|
10941
|
+
question: `Should the director extend the ${payload.kind} budget for subagent ${e.subagentId}?`,
|
|
10942
|
+
context: [
|
|
10943
|
+
e.taskId ? `Task id: ${e.taskId}` : void 0,
|
|
10944
|
+
`Used: ${payload.used}`,
|
|
10945
|
+
`Limit: ${payload.limit}`,
|
|
10946
|
+
`Prior extensions for this kind: ${prior}`
|
|
10947
|
+
].filter(Boolean).join("\n"),
|
|
10948
|
+
risk: "high",
|
|
10949
|
+
fallback: "continue",
|
|
10950
|
+
options: [
|
|
10951
|
+
{
|
|
10952
|
+
id: "extend",
|
|
10953
|
+
label: "Grant the director default budget extension",
|
|
10954
|
+
consequence: "The subagent continues with a larger cost budget.",
|
|
10955
|
+
risk: "high",
|
|
10956
|
+
recommended: true
|
|
10957
|
+
},
|
|
10958
|
+
{
|
|
10959
|
+
id: "stop",
|
|
10960
|
+
label: "Stop this subagent at the current budget limit",
|
|
10961
|
+
consequence: "The current task will fail or stop due to budget pressure.",
|
|
10962
|
+
risk: "low"
|
|
10963
|
+
}
|
|
10964
|
+
]
|
|
10965
|
+
}).then((decision) => {
|
|
10966
|
+
if (decision.type === "deny") {
|
|
10967
|
+
payload.deny();
|
|
10968
|
+
return;
|
|
10969
|
+
}
|
|
10970
|
+
if (decision.type === "ask_human") {
|
|
10971
|
+
payload.deny();
|
|
10972
|
+
return;
|
|
10973
|
+
}
|
|
10974
|
+
if (decision.optionId === "stop") {
|
|
10975
|
+
payload.deny();
|
|
10976
|
+
return;
|
|
10977
|
+
}
|
|
10978
|
+
grantExtension();
|
|
10979
|
+
}).catch(() => payload.deny());
|
|
10632
10980
|
});
|
|
10633
10981
|
}
|
|
10634
10982
|
/**
|
|
@@ -10886,7 +11234,18 @@ var Director = class _Director {
|
|
|
10886
11234
|
if (entry?.model) {
|
|
10887
11235
|
config.model = entry.model;
|
|
10888
11236
|
if (entry.provider) config.provider = entry.provider;
|
|
11237
|
+
if (entry.fallbackProfile) config.fallbackProfile = entry.fallbackProfile;
|
|
10889
11238
|
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
11239
|
+
} else if (entry?.fallbackProfile) {
|
|
11240
|
+
config.fallbackProfile = entry.fallbackProfile;
|
|
11241
|
+
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
11242
|
+
}
|
|
11243
|
+
}
|
|
11244
|
+
if (this.statusTracker && config.provider && config.model) {
|
|
11245
|
+
if (!this.statusTracker.isAvailable(config.provider, config.model)) {
|
|
11246
|
+
this.logger?.warn(
|
|
11247
|
+
`spawn: resolved model "${config.provider}/${config.model}" for role "${config.role ?? "?"}" is blocked by the status tracker. The subagent will attempt its fallback chain.`
|
|
11248
|
+
);
|
|
10890
11249
|
}
|
|
10891
11250
|
}
|
|
10892
11251
|
}
|
|
@@ -11291,11 +11650,11 @@ var Director = class _Director {
|
|
|
11291
11650
|
if (cached) return cached;
|
|
11292
11651
|
const existing = this.taskWaiters.get(id);
|
|
11293
11652
|
if (existing) return existing.promise;
|
|
11294
|
-
let
|
|
11653
|
+
let resolve14;
|
|
11295
11654
|
const promise = new Promise((res) => {
|
|
11296
|
-
|
|
11655
|
+
resolve14 = res;
|
|
11297
11656
|
});
|
|
11298
|
-
this.taskWaiters.set(id, { promise, resolve:
|
|
11657
|
+
this.taskWaiters.set(id, { promise, resolve: resolve14 });
|
|
11299
11658
|
return promise;
|
|
11300
11659
|
})
|
|
11301
11660
|
);
|
|
@@ -11324,10 +11683,10 @@ var Director = class _Director {
|
|
|
11324
11683
|
pending: taskIds.filter((id) => !done.has(id))
|
|
11325
11684
|
});
|
|
11326
11685
|
}
|
|
11327
|
-
return new Promise((
|
|
11686
|
+
return new Promise((resolve14) => {
|
|
11328
11687
|
const entry = {
|
|
11329
11688
|
ids: new Set(taskIds),
|
|
11330
|
-
resolve: (r) =>
|
|
11689
|
+
resolve: (r) => resolve14({
|
|
11331
11690
|
completed: [r],
|
|
11332
11691
|
pending: taskIds.filter((id) => id !== r.taskId)
|
|
11333
11692
|
})
|
|
@@ -11335,7 +11694,7 @@ var Director = class _Director {
|
|
|
11335
11694
|
if (opts?.timeoutMs !== void 0) {
|
|
11336
11695
|
entry.timer = setTimeout(() => {
|
|
11337
11696
|
this.anyWaiters.delete(entry);
|
|
11338
|
-
|
|
11697
|
+
resolve14({ completed: [], pending: [...taskIds], timedOut: true });
|
|
11339
11698
|
}, opts.timeoutMs);
|
|
11340
11699
|
}
|
|
11341
11700
|
this.anyWaiters.add(entry);
|
|
@@ -11396,6 +11755,11 @@ var Director = class _Director {
|
|
|
11396
11755
|
}
|
|
11397
11756
|
async remove(subagentId) {
|
|
11398
11757
|
this.clearSubagentIdleRetirement(subagentId);
|
|
11758
|
+
void this.appendSessionEvent({
|
|
11759
|
+
type: "agent_stopped",
|
|
11760
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11761
|
+
agentId: subagentId
|
|
11762
|
+
});
|
|
11399
11763
|
await this.coordinator.remove(subagentId);
|
|
11400
11764
|
const bridge = this.subagentBridges.get(subagentId);
|
|
11401
11765
|
if (bridge) {
|
|
@@ -11770,24 +12134,24 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
11770
12134
|
/**
|
|
11771
12135
|
* Scrub secrets out of conversation-turn events before they are observed
|
|
11772
12136
|
* for the summary, written to the JSONL log, or surfaced on resume. Only
|
|
11773
|
-
*
|
|
11774
|
-
*
|
|
11775
|
-
*
|
|
11776
|
-
* also strips transient token-cache fields even when no scrubber is set.
|
|
12137
|
+
* Conversation events carry free-form user/model text. Snapshot and exact
|
|
12138
|
+
* message-journal normalization also strips transient token-cache fields
|
|
12139
|
+
* even when no scrubber is set.
|
|
11777
12140
|
*/
|
|
11778
12141
|
scrubEvent(event) {
|
|
11779
12142
|
const s = this.secretScrubber;
|
|
11780
|
-
|
|
12143
|
+
const persistMessage = (message) => {
|
|
12144
|
+
const { _estTokens: _ignored, ...persisted } = message;
|
|
11781
12145
|
return {
|
|
11782
|
-
...
|
|
11783
|
-
|
|
11784
|
-
const { _estTokens: _ignored, ...persisted } = message;
|
|
11785
|
-
return {
|
|
11786
|
-
...persisted,
|
|
11787
|
-
content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
|
|
11788
|
-
};
|
|
11789
|
-
})
|
|
12146
|
+
...persisted,
|
|
12147
|
+
content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
|
|
11790
12148
|
};
|
|
12149
|
+
};
|
|
12150
|
+
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
12151
|
+
return { ...event, messages: event.messages.map(persistMessage) };
|
|
12152
|
+
}
|
|
12153
|
+
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
12154
|
+
return { ...event, message: persistMessage(event.message) };
|
|
11791
12155
|
}
|
|
11792
12156
|
if (!s) return event;
|
|
11793
12157
|
if (event.type === "user_input") {
|
|
@@ -11842,7 +12206,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
11842
12206
|
this.bufferSynchronousEvent(event);
|
|
11843
12207
|
}
|
|
11844
12208
|
recordFileObservation(input) {
|
|
11845
|
-
if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs))
|
|
12209
|
+
if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs))
|
|
12210
|
+
return;
|
|
11846
12211
|
this.bufferSynchronousEvent({
|
|
11847
12212
|
type: "file_observation",
|
|
11848
12213
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12004,12 +12369,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12004
12369
|
const now = Date.now();
|
|
12005
12370
|
if (now - this.lastAppendWarnAt > 5e3) {
|
|
12006
12371
|
const suppressed = this.appendFailCount - 1;
|
|
12007
|
-
|
|
12008
|
-
console.warn(
|
|
12009
|
-
"[session] flush failed:",
|
|
12010
|
-
toErrorMessage(err),
|
|
12011
|
-
tail
|
|
12012
|
-
);
|
|
12372
|
+
console.warn(JSON.stringify({ level: "warn", event: "session.flush_failed", sessionId: this.id, message: toErrorMessage(err), ...suppressed > 0 ? { suppressed } : {}, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
12013
12373
|
this.lastAppendWarnAt = now;
|
|
12014
12374
|
this.appendFailCount = 0;
|
|
12015
12375
|
}
|
|
@@ -12029,6 +12389,43 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12029
12389
|
});
|
|
12030
12390
|
}
|
|
12031
12391
|
}
|
|
12392
|
+
/**
|
|
12393
|
+
* Rebuild every accumulated summary counter by replaying the JSONL as it now
|
|
12394
|
+
* stands on disk.
|
|
12395
|
+
*
|
|
12396
|
+
* Only truncation needs this: the counters are fed by `observeForSummary` as
|
|
12397
|
+
* events go by and cannot un-count. Reading the whole file is fine here —
|
|
12398
|
+
* rewind is a rare, explicitly user-driven operation, and the alternative
|
|
12399
|
+
* (reversing each counter per discarded event) has to stay in lockstep with
|
|
12400
|
+
* observeForSummary forever.
|
|
12401
|
+
*
|
|
12402
|
+
* `title` is deliberately re-derived too: the first surviving user_input may
|
|
12403
|
+
* differ once earlier prompts are gone.
|
|
12404
|
+
*/
|
|
12405
|
+
async recomputeSummaryFromDisk() {
|
|
12406
|
+
if (!this.filePath) return;
|
|
12407
|
+
const raw = await fsp6.readFile(this.filePath, "utf8").catch(() => null);
|
|
12408
|
+
if (raw === null) return;
|
|
12409
|
+
this.iterationCount = 0;
|
|
12410
|
+
this.toolCallCount = 0;
|
|
12411
|
+
this.toolErrorCount = 0;
|
|
12412
|
+
this.toolBreakdown = {};
|
|
12413
|
+
this.fileChangeCount = 0;
|
|
12414
|
+
this.compactionCount = 0;
|
|
12415
|
+
this.outcome = void 0;
|
|
12416
|
+
this.openToolUses = /* @__PURE__ */ new Set();
|
|
12417
|
+
this.tokenIn = 0;
|
|
12418
|
+
this.tokenOut = 0;
|
|
12419
|
+
this.summary = { ...this.summary, title: "(empty session)", tokenTotal: 0 };
|
|
12420
|
+
for (const line of raw.split("\n")) {
|
|
12421
|
+
const trimmed = line.trim();
|
|
12422
|
+
if (trimmed.length === 0) continue;
|
|
12423
|
+
try {
|
|
12424
|
+
this.observeForSummary(JSON.parse(trimmed));
|
|
12425
|
+
} catch {
|
|
12426
|
+
}
|
|
12427
|
+
}
|
|
12428
|
+
}
|
|
12032
12429
|
observeForSummary(event) {
|
|
12033
12430
|
if (event.type === "llm_response") {
|
|
12034
12431
|
for (const block of event.content) {
|
|
@@ -12079,10 +12476,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12079
12476
|
}
|
|
12080
12477
|
async doClose() {
|
|
12081
12478
|
if (this.pendingFileSnapshots.length > 0) {
|
|
12082
|
-
await this.writeFileSnapshot(
|
|
12083
|
-
this.activePromptIndex ?? 0,
|
|
12084
|
-
[...this.pendingFileSnapshots]
|
|
12085
|
-
);
|
|
12479
|
+
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
12086
12480
|
this.pendingFileSnapshots = [];
|
|
12087
12481
|
}
|
|
12088
12482
|
this.closed = true;
|
|
@@ -12202,7 +12596,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12202
12596
|
* so post-checkpoint content is never read or parsed — O(1) memory instead
|
|
12203
12597
|
* of O(N) JSON.parse calls over the full file.
|
|
12204
12598
|
*/
|
|
12205
|
-
async truncateToCheckpoint(targetPromptIndex) {
|
|
12599
|
+
async truncateToCheckpoint(targetPromptIndex, revertedFiles = []) {
|
|
12206
12600
|
if (!this.filePath) return 0;
|
|
12207
12601
|
if (this.flushTimer) {
|
|
12208
12602
|
clearTimeout(this.flushTimer);
|
|
@@ -12213,7 +12607,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12213
12607
|
const CHUNK_SIZE = 65536;
|
|
12214
12608
|
let fd;
|
|
12215
12609
|
let fileOffset = 0;
|
|
12216
|
-
let
|
|
12610
|
+
let pendingLine = Buffer.alloc(0);
|
|
12217
12611
|
let checkpointByteOffset = -1;
|
|
12218
12612
|
let removedCount = 0;
|
|
12219
12613
|
let targetCheckpointSeen = false;
|
|
@@ -12223,17 +12617,18 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12223
12617
|
const buf = Buffer.alloc(CHUNK_SIZE);
|
|
12224
12618
|
const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
|
|
12225
12619
|
if (bytesRead === 0) break;
|
|
12620
|
+
const carriedBytes = pendingLine.length;
|
|
12621
|
+
const chunk = carriedBytes === 0 ? buf.subarray(0, bytesRead) : Buffer.concat([pendingLine, buf.subarray(0, bytesRead)]);
|
|
12622
|
+
const chunkFileOffset = fileOffset - carriedBytes;
|
|
12226
12623
|
let chunkPos = 0;
|
|
12227
|
-
while (chunkPos <
|
|
12228
|
-
const idx =
|
|
12229
|
-
if (idx === -1)
|
|
12230
|
-
|
|
12231
|
-
break;
|
|
12232
|
-
}
|
|
12624
|
+
while (chunkPos < chunk.length) {
|
|
12625
|
+
const idx = chunk.indexOf("\n", chunkPos);
|
|
12626
|
+
if (idx === -1) break;
|
|
12627
|
+
const lineStartOffset = chunkFileOffset + chunkPos;
|
|
12233
12628
|
if (checkpointByteOffset !== -1) {
|
|
12234
12629
|
removedCount++;
|
|
12235
12630
|
} else {
|
|
12236
|
-
const lineBytes =
|
|
12631
|
+
const lineBytes = chunk.subarray(chunkPos, idx);
|
|
12237
12632
|
const line = new TextDecoder("utf-8", { fatal: false }).decode(lineBytes);
|
|
12238
12633
|
if (line.trim()) {
|
|
12239
12634
|
try {
|
|
@@ -12259,12 +12654,9 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12259
12654
|
}
|
|
12260
12655
|
}
|
|
12261
12656
|
chunkPos = idx + 1;
|
|
12262
|
-
lineStartOffset = fileOffset + chunkPos;
|
|
12263
12657
|
}
|
|
12658
|
+
pendingLine = chunk.subarray(chunkPos);
|
|
12264
12659
|
fileOffset += bytesRead;
|
|
12265
|
-
if (chunkPos >= bytesRead) {
|
|
12266
|
-
lineStartOffset = fileOffset;
|
|
12267
|
-
}
|
|
12268
12660
|
}
|
|
12269
12661
|
} finally {
|
|
12270
12662
|
await fd?.close();
|
|
@@ -12344,17 +12736,19 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12344
12736
|
this.handle = await fsp6.open(this.filePath, "a", 384).catch(() => this.handle);
|
|
12345
12737
|
throw err;
|
|
12346
12738
|
}
|
|
12739
|
+
await this.recomputeSummaryFromDisk();
|
|
12740
|
+
const reverted = [...revertedFiles];
|
|
12347
12741
|
await this.append({
|
|
12348
12742
|
type: "rewound",
|
|
12349
12743
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12350
12744
|
toPromptIndex: targetPromptIndex,
|
|
12351
|
-
revertedFiles:
|
|
12745
|
+
revertedFiles: reverted
|
|
12352
12746
|
});
|
|
12353
12747
|
this.activePromptIndex = targetPromptIndex;
|
|
12354
12748
|
this.events?.emit("session.rewound", {
|
|
12355
12749
|
sessionId: this.id,
|
|
12356
12750
|
toPromptIndex: targetPromptIndex,
|
|
12357
|
-
revertedFiles:
|
|
12751
|
+
revertedFiles: reverted,
|
|
12358
12752
|
removedEvents: removedCount
|
|
12359
12753
|
});
|
|
12360
12754
|
return removedCount;
|
|
@@ -12395,7 +12789,11 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12395
12789
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12396
12790
|
context
|
|
12397
12791
|
});
|
|
12398
|
-
this.events?.emit("in_flight.started", {
|
|
12792
|
+
this.events?.emit("in_flight.started", {
|
|
12793
|
+
sessionId: this.id,
|
|
12794
|
+
context,
|
|
12795
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
12796
|
+
});
|
|
12399
12797
|
}
|
|
12400
12798
|
/**
|
|
12401
12799
|
* Close the in-flight marker. Idempotent in spirit
|
|
@@ -12406,10 +12804,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12406
12804
|
*/
|
|
12407
12805
|
async clearInFlightMarker(reason) {
|
|
12408
12806
|
if (this.pendingFileSnapshots.length > 0) {
|
|
12409
|
-
await this.writeFileSnapshot(
|
|
12410
|
-
this.activePromptIndex ?? 0,
|
|
12411
|
-
[...this.pendingFileSnapshots]
|
|
12412
|
-
);
|
|
12807
|
+
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
12413
12808
|
this.pendingFileSnapshots = [];
|
|
12414
12809
|
}
|
|
12415
12810
|
await this.append({
|
|
@@ -12417,7 +12812,11 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12417
12812
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12418
12813
|
reason
|
|
12419
12814
|
});
|
|
12420
|
-
this.events?.emit("in_flight.ended", {
|
|
12815
|
+
this.events?.emit("in_flight.ended", {
|
|
12816
|
+
sessionId: this.id,
|
|
12817
|
+
reason,
|
|
12818
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
12819
|
+
});
|
|
12421
12820
|
}
|
|
12422
12821
|
};
|
|
12423
12822
|
|
|
@@ -12456,8 +12855,8 @@ function sha256(content) {
|
|
|
12456
12855
|
return createHash2("sha256").update(content).digest("hex");
|
|
12457
12856
|
}
|
|
12458
12857
|
function isInside(root, target) {
|
|
12459
|
-
const
|
|
12460
|
-
return
|
|
12858
|
+
const relative5 = path9.relative(root, target);
|
|
12859
|
+
return relative5 === "" || !relative5.startsWith("..") && !path9.isAbsolute(relative5);
|
|
12461
12860
|
}
|
|
12462
12861
|
function normalizeRelative(input) {
|
|
12463
12862
|
if (!input || path9.isAbsolute(input)) return null;
|
|
@@ -12469,8 +12868,8 @@ function normalizeRelative(input) {
|
|
|
12469
12868
|
function parseNulPaths(output) {
|
|
12470
12869
|
return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
|
|
12471
12870
|
}
|
|
12472
|
-
function isWrongStackWorktreePath(
|
|
12473
|
-
return
|
|
12871
|
+
function isWrongStackWorktreePath(relative5) {
|
|
12872
|
+
return relative5 === ".wrongstack/worktrees" || relative5.startsWith(".wrongstack/worktrees/");
|
|
12474
12873
|
}
|
|
12475
12874
|
var SessionCheckpointCas = class {
|
|
12476
12875
|
rootDir;
|
|
@@ -12496,60 +12895,60 @@ var SessionCheckpointCas = class {
|
|
|
12496
12895
|
}
|
|
12497
12896
|
const relativePaths = [
|
|
12498
12897
|
.../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
|
|
12499
|
-
].filter((
|
|
12898
|
+
].filter((relative5) => !isWrongStackWorktreePath(relative5)).sort();
|
|
12500
12899
|
const unresolved = [];
|
|
12501
12900
|
let scheduledBytes = 0;
|
|
12502
12901
|
const captured = await mapWithConcurrency(
|
|
12503
12902
|
relativePaths,
|
|
12504
12903
|
CAPTURE_CONCURRENCY,
|
|
12505
|
-
async (
|
|
12506
|
-
const absolute = path9.resolve(this.projectRoot, ...
|
|
12904
|
+
async (relative5) => {
|
|
12905
|
+
const absolute = path9.resolve(this.projectRoot, ...relative5.split("/"));
|
|
12507
12906
|
if (!isInside(this.projectRoot, absolute)) {
|
|
12508
|
-
unresolved.push({ path:
|
|
12907
|
+
unresolved.push({ path: relative5, reason: "path escapes project root" });
|
|
12509
12908
|
return null;
|
|
12510
12909
|
}
|
|
12511
12910
|
try {
|
|
12512
|
-
const
|
|
12513
|
-
if (
|
|
12911
|
+
const stat13 = await fsp7.lstat(absolute);
|
|
12912
|
+
if (stat13.isSymbolicLink()) {
|
|
12514
12913
|
const linkTarget = await fsp7.readlink(absolute);
|
|
12515
12914
|
const resolvedLink = path9.resolve(path9.dirname(absolute), linkTarget);
|
|
12516
12915
|
if (path9.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
12517
12916
|
unresolved.push({
|
|
12518
|
-
path:
|
|
12917
|
+
path: relative5,
|
|
12519
12918
|
reason: "symlink target escapes project root"
|
|
12520
12919
|
});
|
|
12521
12920
|
return null;
|
|
12522
12921
|
}
|
|
12523
|
-
return { path:
|
|
12922
|
+
return { path: relative5, state: "symlink", linkTarget };
|
|
12524
12923
|
}
|
|
12525
|
-
if (!
|
|
12526
|
-
unresolved.push({ path:
|
|
12924
|
+
if (!stat13.isFile()) {
|
|
12925
|
+
unresolved.push({ path: relative5, reason: "changed path is not a regular file" });
|
|
12527
12926
|
return null;
|
|
12528
12927
|
}
|
|
12529
|
-
if (
|
|
12928
|
+
if (stat13.size > MAX_BLOB_BYTES) {
|
|
12530
12929
|
unresolved.push({
|
|
12531
|
-
path:
|
|
12930
|
+
path: relative5,
|
|
12532
12931
|
reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
|
|
12533
12932
|
});
|
|
12534
12933
|
return null;
|
|
12535
12934
|
}
|
|
12536
|
-
if (scheduledBytes +
|
|
12935
|
+
if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
|
|
12537
12936
|
unresolved.push({
|
|
12538
|
-
path:
|
|
12937
|
+
path: relative5,
|
|
12539
12938
|
reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
|
|
12540
12939
|
});
|
|
12541
12940
|
return null;
|
|
12542
12941
|
}
|
|
12543
|
-
scheduledBytes +=
|
|
12942
|
+
scheduledBytes += stat13.size;
|
|
12544
12943
|
const content = await fsp7.readFile(absolute);
|
|
12545
12944
|
const blobHash = sha256(content);
|
|
12546
12945
|
await this.putBlob(blobHash, content);
|
|
12547
|
-
return { path:
|
|
12946
|
+
return { path: relative5, state: "file", blobHash, mode: stat13.mode & 511 };
|
|
12548
12947
|
} catch (err) {
|
|
12549
12948
|
if (err.code === "ENOENT") {
|
|
12550
|
-
return { path:
|
|
12949
|
+
return { path: relative5, state: "absent" };
|
|
12551
12950
|
}
|
|
12552
|
-
unresolved.push({ path:
|
|
12951
|
+
unresolved.push({ path: relative5, reason: toErrorMessage(err) });
|
|
12553
12952
|
return null;
|
|
12554
12953
|
}
|
|
12555
12954
|
}
|
|
@@ -12661,20 +13060,20 @@ var SessionCheckpointCas = class {
|
|
|
12661
13060
|
}
|
|
12662
13061
|
return { targetRoot: target, writtenFiles, deletedFiles, errors };
|
|
12663
13062
|
}
|
|
12664
|
-
objectPath(
|
|
12665
|
-
if (!HASH_RE.test(
|
|
12666
|
-
return path9.join(this.rootDir, "objects",
|
|
13063
|
+
objectPath(hash2) {
|
|
13064
|
+
if (!HASH_RE.test(hash2)) throw new Error(`Invalid CAS object hash: ${hash2}`);
|
|
13065
|
+
return path9.join(this.rootDir, "objects", hash2.slice(0, 2), hash2.slice(2));
|
|
12667
13066
|
}
|
|
12668
|
-
manifestPath(
|
|
12669
|
-
if (!HASH_RE.test(
|
|
12670
|
-
return path9.join(this.rootDir, "manifests", `${
|
|
13067
|
+
manifestPath(hash2) {
|
|
13068
|
+
if (!HASH_RE.test(hash2)) throw new Error(`Invalid checkpoint manifest hash: ${hash2}`);
|
|
13069
|
+
return path9.join(this.rootDir, "manifests", `${hash2}.json`);
|
|
12671
13070
|
}
|
|
12672
|
-
async putBlob(
|
|
12673
|
-
const target = this.objectPath(
|
|
13071
|
+
async putBlob(hash2, content) {
|
|
13072
|
+
const target = this.objectPath(hash2);
|
|
12674
13073
|
await fsp7.mkdir(path9.dirname(target), { recursive: true });
|
|
12675
13074
|
try {
|
|
12676
13075
|
const existing = await fsp7.readFile(target);
|
|
12677
|
-
if (sha256(existing) !==
|
|
13076
|
+
if (sha256(existing) !== hash2) throw new Error(`Corrupt CAS object collision: ${hash2}`);
|
|
12678
13077
|
return;
|
|
12679
13078
|
} catch (err) {
|
|
12680
13079
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -12695,33 +13094,33 @@ var SessionCheckpointCas = class {
|
|
|
12695
13094
|
} catch (err) {
|
|
12696
13095
|
if (err.code !== "EEXIST") throw err;
|
|
12697
13096
|
const existing = await fsp7.readFile(target);
|
|
12698
|
-
if (sha256(existing) !==
|
|
13097
|
+
if (sha256(existing) !== hash2) throw new Error(`Corrupt CAS object collision: ${hash2}`);
|
|
12699
13098
|
}
|
|
12700
13099
|
} finally {
|
|
12701
13100
|
await handle?.close().catch(() => void 0);
|
|
12702
13101
|
await fsp7.unlink(temp).catch(() => void 0);
|
|
12703
13102
|
}
|
|
12704
13103
|
}
|
|
12705
|
-
async putManifest(
|
|
12706
|
-
const target = this.manifestPath(
|
|
13104
|
+
async putManifest(hash2, encoded) {
|
|
13105
|
+
const target = this.manifestPath(hash2);
|
|
12707
13106
|
const existing = await fsp7.readFile(target).catch(() => null);
|
|
12708
13107
|
if (existing) {
|
|
12709
|
-
if (sha256(existing) !==
|
|
13108
|
+
if (sha256(existing) !== hash2) throw new Error(`Corrupt checkpoint manifest collision: ${hash2}`);
|
|
12710
13109
|
return;
|
|
12711
13110
|
}
|
|
12712
13111
|
await atomicWrite(target, encoded, { mode: 384 });
|
|
12713
13112
|
}
|
|
12714
|
-
async readBlob(
|
|
12715
|
-
const content = await fsp7.readFile(this.objectPath(
|
|
12716
|
-
if (sha256(content) !==
|
|
13113
|
+
async readBlob(hash2) {
|
|
13114
|
+
const content = await fsp7.readFile(this.objectPath(hash2));
|
|
13115
|
+
if (sha256(content) !== hash2) throw new Error(`CAS blob integrity check failed: ${hash2}`);
|
|
12717
13116
|
return content;
|
|
12718
13117
|
}
|
|
12719
|
-
async loadManifest(
|
|
12720
|
-
const raw = await fsp7.readFile(this.manifestPath(
|
|
12721
|
-
if (sha256(raw) !==
|
|
13118
|
+
async loadManifest(hash2) {
|
|
13119
|
+
const raw = await fsp7.readFile(this.manifestPath(hash2));
|
|
13120
|
+
if (sha256(raw) !== hash2) throw new Error(`Checkpoint manifest integrity check failed: ${hash2}`);
|
|
12722
13121
|
const parsed = JSON.parse(raw.toString("utf8"));
|
|
12723
13122
|
if (parsed.version !== 1 || typeof parsed.baseHead !== "string" || parsed.coverage !== "git-head-plus-dirty" || !Array.isArray(parsed.entries) || !Array.isArray(parsed.unresolved)) {
|
|
12724
|
-
throw new Error(`Invalid workspace checkpoint manifest: ${
|
|
13123
|
+
throw new Error(`Invalid workspace checkpoint manifest: ${hash2}`);
|
|
12725
13124
|
}
|
|
12726
13125
|
const entriesValid = parsed.entries.every((entry) => {
|
|
12727
13126
|
if (!entry || typeof entry !== "object") return false;
|
|
@@ -12735,12 +13134,12 @@ var SessionCheckpointCas = class {
|
|
|
12735
13134
|
(entry) => entry !== null && typeof entry === "object" && typeof entry.path === "string" && typeof entry.reason === "string"
|
|
12736
13135
|
);
|
|
12737
13136
|
if (!entriesValid || !unresolvedValid) {
|
|
12738
|
-
throw new Error(`Invalid workspace checkpoint manifest entries: ${
|
|
13137
|
+
throw new Error(`Invalid workspace checkpoint manifest entries: ${hash2}`);
|
|
12739
13138
|
}
|
|
12740
13139
|
return parsed;
|
|
12741
13140
|
}
|
|
12742
|
-
async safeOutputPath(target, realTarget,
|
|
12743
|
-
const normalized = normalizeRelative(
|
|
13141
|
+
async safeOutputPath(target, realTarget, relative5) {
|
|
13142
|
+
const normalized = normalizeRelative(relative5);
|
|
12744
13143
|
if (!normalized) throw new Error("invalid relative path");
|
|
12745
13144
|
const output = path9.resolve(target, ...normalized.split("/"));
|
|
12746
13145
|
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
@@ -12760,7 +13159,7 @@ var SessionCheckpointCas = class {
|
|
|
12760
13159
|
}
|
|
12761
13160
|
};
|
|
12762
13161
|
function defaultRunGit(args, cwd) {
|
|
12763
|
-
return new Promise((
|
|
13162
|
+
return new Promise((resolve14) => {
|
|
12764
13163
|
const stdoutChunks = [];
|
|
12765
13164
|
const stderrChunks = [];
|
|
12766
13165
|
let stdoutBytes = 0;
|
|
@@ -12803,11 +13202,18 @@ function defaultRunGit(args, cwd) {
|
|
|
12803
13202
|
stdoutTruncated,
|
|
12804
13203
|
stderrTruncated
|
|
12805
13204
|
});
|
|
12806
|
-
child.on("error", (err) =>
|
|
12807
|
-
child.on("close", (code) =>
|
|
13205
|
+
child.on("error", (err) => resolve14(result(1, err.message)));
|
|
13206
|
+
child.on("close", (code) => resolve14(result(code ?? 1)));
|
|
12808
13207
|
});
|
|
12809
13208
|
}
|
|
12810
13209
|
|
|
13210
|
+
// src/storage/session-id.ts
|
|
13211
|
+
function generateSessionId(startedAt, _model) {
|
|
13212
|
+
const date = startedAt.slice(0, 10);
|
|
13213
|
+
const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);
|
|
13214
|
+
return `${date}/sess_${ulid(seedTime)}`;
|
|
13215
|
+
}
|
|
13216
|
+
|
|
12811
13217
|
// src/storage/session-resume-validation.ts
|
|
12812
13218
|
import { createHash as createHash3 } from "node:crypto";
|
|
12813
13219
|
import * as fsp8 from "node:fs/promises";
|
|
@@ -12816,8 +13222,8 @@ var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
|
12816
13222
|
var VALIDATION_CONCURRENCY = 8;
|
|
12817
13223
|
var NOTICE_PATH_LIMIT = 20;
|
|
12818
13224
|
function isInside2(root, target) {
|
|
12819
|
-
const
|
|
12820
|
-
return
|
|
13225
|
+
const relative5 = path10.relative(root, target);
|
|
13226
|
+
return relative5 === "" || !relative5.startsWith("..") && !path10.isAbsolute(relative5);
|
|
12821
13227
|
}
|
|
12822
13228
|
function errno(err) {
|
|
12823
13229
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -12865,11 +13271,11 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
12865
13271
|
};
|
|
12866
13272
|
}
|
|
12867
13273
|
try {
|
|
12868
|
-
const
|
|
12869
|
-
if (!
|
|
13274
|
+
const stat13 = await fsp8.stat(realFile);
|
|
13275
|
+
if (!stat13.isFile()) {
|
|
12870
13276
|
return { ...base, status: "unreadable", detail: "Path is no longer a regular file." };
|
|
12871
13277
|
}
|
|
12872
|
-
if (
|
|
13278
|
+
if (stat13.size > MAX_REVALIDATE_BYTES) {
|
|
12873
13279
|
return {
|
|
12874
13280
|
...base,
|
|
12875
13281
|
status: "unreadable",
|
|
@@ -12904,8 +13310,8 @@ function formatResumeValidationNotice(validation, projectRoot) {
|
|
|
12904
13310
|
if (validation.staleFiles.length === 0) return null;
|
|
12905
13311
|
const root = path10.resolve(projectRoot);
|
|
12906
13312
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
12907
|
-
const
|
|
12908
|
-
const display = isInside2(root, entry.path) ?
|
|
13313
|
+
const relative5 = path10.relative(root, entry.path);
|
|
13314
|
+
const display = isInside2(root, entry.path) ? relative5 || "." : entry.path;
|
|
12909
13315
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
12910
13316
|
});
|
|
12911
13317
|
const omitted = validation.staleFiles.length - shown.length;
|
|
@@ -12917,12 +13323,14 @@ function formatResumeValidationNotice(validation, projectRoot) {
|
|
|
12917
13323
|
...shown
|
|
12918
13324
|
].join("\n");
|
|
12919
13325
|
}
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
13326
|
+
function formatInterruptedToolNotice(pendingToolUseCount) {
|
|
13327
|
+
if (!Number.isFinite(pendingToolUseCount) || pendingToolUseCount <= 0) return null;
|
|
13328
|
+
const plural = pendingToolUseCount === 1 ? "tool call was" : "tool calls were";
|
|
13329
|
+
return [
|
|
13330
|
+
"[SESSION RESUME INTERRUPTED WORK]",
|
|
13331
|
+
`The previous run left ${pendingToolUseCount} ${plural} in flight \u2014 no result was recorded before it stopped (crash or interrupt).`,
|
|
13332
|
+
"Those interrupted tool calls were removed from the restored conversation and were NOT re-executed. Re-run the work if it still needs doing."
|
|
13333
|
+
].join("\n");
|
|
12926
13334
|
}
|
|
12927
13335
|
|
|
12928
13336
|
// src/storage/session-summary.ts
|
|
@@ -12968,18 +13376,27 @@ function isReplayableMessage(value) {
|
|
|
12968
13376
|
const candidate = value;
|
|
12969
13377
|
return (candidate.role === "user" || candidate.role === "assistant" || candidate.role === "system") && (typeof candidate.content === "string" || Array.isArray(candidate.content));
|
|
12970
13378
|
}
|
|
13379
|
+
function trackMessageToolState(message, openToolUses) {
|
|
13380
|
+
if (!Array.isArray(message.content)) return;
|
|
13381
|
+
for (const block of message.content) {
|
|
13382
|
+
if (block.type === "tool_use") openToolUses.add(block.id);
|
|
13383
|
+
else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
|
|
13384
|
+
}
|
|
13385
|
+
}
|
|
13386
|
+
function replayableMessage(value, fallbackTs) {
|
|
13387
|
+
if (!isReplayableMessage(value)) return null;
|
|
13388
|
+
const { _estTokens: _ignored, ...message } = value;
|
|
13389
|
+
return message.ts === void 0 && fallbackTs ? { ...message, ts: fallbackTs } : message;
|
|
13390
|
+
}
|
|
12971
13391
|
function applyContextSnapshot(target, openToolUses, snapshot) {
|
|
12972
13392
|
if (!Array.isArray(snapshot) || !snapshot.every(isReplayableMessage)) return false;
|
|
12973
13393
|
target.length = 0;
|
|
12974
13394
|
openToolUses.clear();
|
|
12975
13395
|
for (const raw of snapshot) {
|
|
12976
|
-
const
|
|
13396
|
+
const message = replayableMessage(raw);
|
|
13397
|
+
if (!message) return false;
|
|
12977
13398
|
target.push(message);
|
|
12978
|
-
|
|
12979
|
-
for (const block of message.content) {
|
|
12980
|
-
if (block.type === "tool_use") openToolUses.add(block.id);
|
|
12981
|
-
else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
|
|
12982
|
-
}
|
|
13399
|
+
trackMessageToolState(message, openToolUses);
|
|
12983
13400
|
}
|
|
12984
13401
|
return true;
|
|
12985
13402
|
}
|
|
@@ -13132,10 +13549,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13132
13549
|
handle = await fsp9.open(file, "a", 384);
|
|
13133
13550
|
} catch (err) {
|
|
13134
13551
|
this.emitError(id, file, "create", toErrorMessage(err), false);
|
|
13135
|
-
throw new Error(
|
|
13136
|
-
`Failed to open session file: ${toErrorMessage(err)}`,
|
|
13137
|
-
{ cause: err }
|
|
13138
|
-
);
|
|
13552
|
+
throw new Error(`Failed to open session file: ${toErrorMessage(err)}`, { cause: err });
|
|
13139
13553
|
}
|
|
13140
13554
|
try {
|
|
13141
13555
|
const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
@@ -13148,10 +13562,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13148
13562
|
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
13149
13563
|
return writer;
|
|
13150
13564
|
} catch (err) {
|
|
13151
|
-
await handle.close().catch(
|
|
13152
|
-
"Session handle close failed",
|
|
13153
|
-
|
|
13154
|
-
|
|
13565
|
+
await handle.close().catch(
|
|
13566
|
+
(e) => this.logWarn("Session handle close failed", {
|
|
13567
|
+
event: "session_store.handle_close_failed",
|
|
13568
|
+
message: e instanceof Error ? e.message : String(e)
|
|
13569
|
+
})
|
|
13570
|
+
);
|
|
13155
13571
|
this.emitError(id, file, "create", toErrorMessage(err), true);
|
|
13156
13572
|
throw err;
|
|
13157
13573
|
}
|
|
@@ -13170,9 +13586,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13170
13586
|
}
|
|
13171
13587
|
}
|
|
13172
13588
|
if (boundary === -1) {
|
|
13173
|
-
throw new Error(
|
|
13174
|
-
`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`
|
|
13175
|
-
);
|
|
13589
|
+
throw new Error(`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`);
|
|
13176
13590
|
}
|
|
13177
13591
|
}
|
|
13178
13592
|
const parentPrefix = parent.events.slice(0, boundary + 1);
|
|
@@ -13219,7 +13633,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13219
13633
|
}
|
|
13220
13634
|
async materializeWorkspaceCheckpoint(checkpoint, targetRoot) {
|
|
13221
13635
|
if (!this.checkpointCas) {
|
|
13222
|
-
throw new Error(
|
|
13636
|
+
throw new Error(
|
|
13637
|
+
"Workspace checkpoint materialization requires a projectRoot-aware session store"
|
|
13638
|
+
);
|
|
13223
13639
|
}
|
|
13224
13640
|
return this.checkpointCas.materialize(checkpoint, targetRoot);
|
|
13225
13641
|
}
|
|
@@ -13227,34 +13643,44 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13227
13643
|
const file = this.sessionPath(id, ".jsonl");
|
|
13228
13644
|
const t0 = Date.now();
|
|
13229
13645
|
const data = await this.load(id);
|
|
13230
|
-
|
|
13646
|
+
const noticeMessages = [];
|
|
13647
|
+
let resumeValidation;
|
|
13231
13648
|
if (this.projectRoot) {
|
|
13232
13649
|
try {
|
|
13233
|
-
|
|
13650
|
+
resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);
|
|
13234
13651
|
const notice = formatResumeValidationNotice(resumeValidation, this.projectRoot);
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
]
|
|
13243
|
-
} : {}
|
|
13244
|
-
};
|
|
13652
|
+
if (notice) {
|
|
13653
|
+
noticeMessages.push({
|
|
13654
|
+
role: "system",
|
|
13655
|
+
content: notice,
|
|
13656
|
+
ts: resumeValidation.checkedAt
|
|
13657
|
+
});
|
|
13658
|
+
}
|
|
13245
13659
|
} catch (err) {
|
|
13246
13660
|
this.emitError(id, file, "resume_validation", toErrorMessage(err), true);
|
|
13247
13661
|
}
|
|
13248
13662
|
}
|
|
13663
|
+
const interruptedNotice = formatInterruptedToolNotice(data.pendingToolUseCount ?? 0);
|
|
13664
|
+
if (interruptedNotice) {
|
|
13665
|
+
noticeMessages.push({
|
|
13666
|
+
role: "system",
|
|
13667
|
+
content: interruptedNotice,
|
|
13668
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
13669
|
+
});
|
|
13670
|
+
}
|
|
13671
|
+
const resumedData = {
|
|
13672
|
+
...data,
|
|
13673
|
+
...resumeValidation ? { resumeValidation } : {},
|
|
13674
|
+
...noticeMessages.length > 0 ? { messages: [...data.messages, ...noticeMessages] } : {}
|
|
13675
|
+
};
|
|
13249
13676
|
let handle;
|
|
13250
13677
|
try {
|
|
13251
13678
|
handle = await fsp9.open(file, "a", 384);
|
|
13252
13679
|
} catch (err) {
|
|
13253
13680
|
this.emitError(id, file, "resume", toErrorMessage(err), false);
|
|
13254
|
-
throw new Error(
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
);
|
|
13681
|
+
throw new Error(`Failed to open session "${id}" for append: ${toErrorMessage(err)}`, {
|
|
13682
|
+
cause: err
|
|
13683
|
+
});
|
|
13258
13684
|
}
|
|
13259
13685
|
try {
|
|
13260
13686
|
const writer = new FileSessionWriter(
|
|
@@ -13282,10 +13708,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13282
13708
|
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
13283
13709
|
return { writer, data: resumedData };
|
|
13284
13710
|
} catch (err) {
|
|
13285
|
-
await handle.close().catch(
|
|
13286
|
-
"Session handle close failed",
|
|
13287
|
-
|
|
13288
|
-
|
|
13711
|
+
await handle.close().catch(
|
|
13712
|
+
(e) => this.logWarn("Session handle close failed", {
|
|
13713
|
+
event: "session_store.handle_close_failed",
|
|
13714
|
+
message: e instanceof Error ? e.message : String(e)
|
|
13715
|
+
})
|
|
13716
|
+
);
|
|
13289
13717
|
this.emitError(id, file, "resume", toErrorMessage(err), true);
|
|
13290
13718
|
throw err;
|
|
13291
13719
|
}
|
|
@@ -13318,9 +13746,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13318
13746
|
let cacheHit = false;
|
|
13319
13747
|
try {
|
|
13320
13748
|
const s = await fsp9.stat(file);
|
|
13321
|
-
const
|
|
13749
|
+
const stat13 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
13322
13750
|
const cached = this._loadCache.get(id);
|
|
13323
|
-
if (cached && cached.mtimeMs ===
|
|
13751
|
+
if (cached && cached.mtimeMs === stat13.mtimeMs && cached.size === stat13.size) {
|
|
13324
13752
|
cacheHit = true;
|
|
13325
13753
|
this._loadCache.delete(id);
|
|
13326
13754
|
this._loadCache.set(id, cached);
|
|
@@ -13336,6 +13764,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13336
13764
|
let sessionForkedEvent;
|
|
13337
13765
|
const messages = mode.full ? [] : void 0;
|
|
13338
13766
|
const openToolUses = mode.full ? /* @__PURE__ */ new Set() : void 0;
|
|
13767
|
+
let exactJournalActive = false;
|
|
13339
13768
|
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
13340
13769
|
const stream = createReadStream(file, { encoding: "utf8" });
|
|
13341
13770
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -13360,20 +13789,59 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13360
13789
|
sessionForkedEvent = ev;
|
|
13361
13790
|
}
|
|
13362
13791
|
if (mode.full && messages !== void 0 && openToolUses !== void 0) {
|
|
13363
|
-
if (ev.type === "
|
|
13792
|
+
if (ev.type === "message_appended" && ev.version === 1) {
|
|
13793
|
+
const message = replayableMessage(ev.message, ev.ts);
|
|
13794
|
+
if (message) {
|
|
13795
|
+
if (!exactJournalActive) {
|
|
13796
|
+
messages.length = 0;
|
|
13797
|
+
openToolUses.clear();
|
|
13798
|
+
exactJournalActive = true;
|
|
13799
|
+
}
|
|
13800
|
+
messages.push(message);
|
|
13801
|
+
trackMessageToolState(message, openToolUses);
|
|
13802
|
+
} else {
|
|
13803
|
+
this.events?.emit("session.damaged", {
|
|
13804
|
+
sessionId: id,
|
|
13805
|
+
detail: "Ignored malformed message_appended event"
|
|
13806
|
+
});
|
|
13807
|
+
}
|
|
13808
|
+
} else if (ev.type === "message_updated" && ev.version === 1) {
|
|
13809
|
+
const message = replayableMessage(ev.message, ev.ts);
|
|
13810
|
+
if (message && exactJournalActive && ev.index >= 0 && ev.index < messages.length) {
|
|
13811
|
+
messages[ev.index] = message;
|
|
13812
|
+
openToolUses.clear();
|
|
13813
|
+
for (const current of messages) trackMessageToolState(current, openToolUses);
|
|
13814
|
+
} else {
|
|
13815
|
+
this.events?.emit("session.damaged", {
|
|
13816
|
+
sessionId: id,
|
|
13817
|
+
detail: `Ignored malformed message_updated event at index ${ev.index}`
|
|
13818
|
+
});
|
|
13819
|
+
}
|
|
13820
|
+
} else if (ev.type === "messages_replaced" && ev.version === 1) {
|
|
13821
|
+
if (applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
13822
|
+
exactJournalActive = true;
|
|
13823
|
+
} else {
|
|
13824
|
+
this.events?.emit("session.damaged", {
|
|
13825
|
+
sessionId: id,
|
|
13826
|
+
detail: "Ignored malformed messages_replaced event"
|
|
13827
|
+
});
|
|
13828
|
+
}
|
|
13829
|
+
} else if (ev.type === "context_snapshot") {
|
|
13364
13830
|
if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
13365
13831
|
this.events?.emit("session.damaged", {
|
|
13366
13832
|
sessionId: id,
|
|
13367
13833
|
detail: "Ignored malformed context_snapshot event"
|
|
13368
13834
|
});
|
|
13369
13835
|
}
|
|
13370
|
-
} else if (ev.type === "user_input") {
|
|
13836
|
+
} else if (!exactJournalActive && ev.type === "user_input") {
|
|
13371
13837
|
openToolUses.clear();
|
|
13372
13838
|
messages.push({ role: "user", content: ev.content, ts: ev.ts });
|
|
13373
13839
|
} else if (ev.type === "llm_response") {
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13840
|
+
if (!exactJournalActive) {
|
|
13841
|
+
messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
|
|
13842
|
+
for (const b of ev.content) {
|
|
13843
|
+
if (b.type === "tool_use") openToolUses.add(b.id);
|
|
13844
|
+
}
|
|
13377
13845
|
}
|
|
13378
13846
|
usage = {
|
|
13379
13847
|
input: usage.input + (ev.usage.input ?? 0),
|
|
@@ -13381,7 +13849,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13381
13849
|
cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
|
|
13382
13850
|
cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
|
|
13383
13851
|
};
|
|
13384
|
-
} else if (ev.type === "tool_result") {
|
|
13852
|
+
} else if (!exactJournalActive && ev.type === "tool_result") {
|
|
13385
13853
|
if (!openToolUses.has(ev.id)) {
|
|
13386
13854
|
this.events?.emit("session.damaged", {
|
|
13387
13855
|
sessionId: id,
|
|
@@ -13453,7 +13921,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13453
13921
|
} : void 0
|
|
13454
13922
|
};
|
|
13455
13923
|
const toolCallEnds = extractToolCallEnds(events);
|
|
13456
|
-
const
|
|
13924
|
+
const pendingToolUseCount = openToolUses && openToolUses.size > 0 ? openToolUses.size : void 0;
|
|
13925
|
+
const data = {
|
|
13926
|
+
metadata: meta,
|
|
13927
|
+
events,
|
|
13928
|
+
messages: finalMessages,
|
|
13929
|
+
usage,
|
|
13930
|
+
toolCallEnds,
|
|
13931
|
+
...pendingToolUseCount !== void 0 ? { pendingToolUseCount } : {}
|
|
13932
|
+
};
|
|
13457
13933
|
if (mode.full) {
|
|
13458
13934
|
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
13459
13935
|
const oldest = this._loadCache.keys().next().value;
|
|
@@ -13461,7 +13937,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13461
13937
|
this._loadCache.delete(oldest);
|
|
13462
13938
|
}
|
|
13463
13939
|
}
|
|
13464
|
-
this._loadCache.set(id, { mtimeMs:
|
|
13940
|
+
this._loadCache.set(id, { mtimeMs: stat13.mtimeMs, size: stat13.size, data });
|
|
13465
13941
|
}
|
|
13466
13942
|
return data;
|
|
13467
13943
|
} catch (err) {
|
|
@@ -13469,7 +13945,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13469
13945
|
errorMsg = toErrorMessage(err);
|
|
13470
13946
|
throw err;
|
|
13471
13947
|
} finally {
|
|
13472
|
-
this.emitRead(
|
|
13948
|
+
this.emitRead(
|
|
13949
|
+
id,
|
|
13950
|
+
file,
|
|
13951
|
+
mode.full ? "load" : "load_events_only",
|
|
13952
|
+
outcome,
|
|
13953
|
+
Date.now() - t0,
|
|
13954
|
+
errorMsg
|
|
13955
|
+
);
|
|
13473
13956
|
if (cacheHit) {
|
|
13474
13957
|
this.events?.emit("storage.cache_hit", {
|
|
13475
13958
|
sessionId: id,
|
|
@@ -13503,14 +13986,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13503
13986
|
const limit = opts?.limit;
|
|
13504
13987
|
const signal = opts?.signal;
|
|
13505
13988
|
const out = [];
|
|
13506
|
-
let
|
|
13989
|
+
let stat13;
|
|
13507
13990
|
try {
|
|
13508
|
-
|
|
13991
|
+
stat13 = await fsp9.stat(file);
|
|
13509
13992
|
} catch (err) {
|
|
13510
13993
|
if (err.code === "ENOENT") return [];
|
|
13511
13994
|
throw err;
|
|
13512
13995
|
}
|
|
13513
|
-
if (
|
|
13996
|
+
if (stat13.size === 0) return [];
|
|
13514
13997
|
let fh;
|
|
13515
13998
|
try {
|
|
13516
13999
|
fh = await fsp9.open(file, "r");
|
|
@@ -13671,7 +14154,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13671
14154
|
outcome = "failure";
|
|
13672
14155
|
errorMsg = toErrorMessage(err);
|
|
13673
14156
|
} finally {
|
|
13674
|
-
this.emitWrite(
|
|
14157
|
+
this.emitWrite(
|
|
14158
|
+
"~compact~",
|
|
14159
|
+
this.indexFile,
|
|
14160
|
+
"compact",
|
|
14161
|
+
outcome,
|
|
14162
|
+
Date.now() - t0,
|
|
14163
|
+
void 0,
|
|
14164
|
+
errorMsg
|
|
14165
|
+
);
|
|
13675
14166
|
}
|
|
13676
14167
|
}
|
|
13677
14168
|
/**
|
|
@@ -13693,15 +14184,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13693
14184
|
* Returns empty array when the index doesn't exist or is corrupt.
|
|
13694
14185
|
*/
|
|
13695
14186
|
async readIndex() {
|
|
13696
|
-
let
|
|
14187
|
+
let stat13;
|
|
13697
14188
|
try {
|
|
13698
14189
|
const s = await fsp9.stat(this.indexFile);
|
|
13699
|
-
|
|
14190
|
+
stat13 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
13700
14191
|
} catch {
|
|
13701
14192
|
this._indexCache = null;
|
|
13702
14193
|
return [];
|
|
13703
14194
|
}
|
|
13704
|
-
if (this._indexCache !== null && this._indexCache.mtimeMs ===
|
|
14195
|
+
if (this._indexCache !== null && this._indexCache.mtimeMs === stat13.mtimeMs && this._indexCache.size === stat13.size) {
|
|
13705
14196
|
return [...this._indexCache.summaries];
|
|
13706
14197
|
}
|
|
13707
14198
|
let raw;
|
|
@@ -13734,7 +14225,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13734
14225
|
if (a.startedAt > b.startedAt) return -1;
|
|
13735
14226
|
return a.id.localeCompare(b.id);
|
|
13736
14227
|
});
|
|
13737
|
-
this._indexCache = { ...
|
|
14228
|
+
this._indexCache = { ...stat13, summaries };
|
|
13738
14229
|
return [...summaries];
|
|
13739
14230
|
}
|
|
13740
14231
|
/**
|
|
@@ -13743,7 +14234,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13743
14234
|
*/
|
|
13744
14235
|
async rebuildIndex() {
|
|
13745
14236
|
const ids = await this.collectSessionIds(this.dir);
|
|
13746
|
-
const summaries = await Promise.all(
|
|
14237
|
+
const summaries = await Promise.all(
|
|
14238
|
+
ids.map((id) => this.summaryFor(id).catch(() => null))
|
|
14239
|
+
);
|
|
13747
14240
|
const valid = summaries.filter((s) => s !== null);
|
|
13748
14241
|
const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
13749
14242
|
await withFileLock(this.indexFile, async () => {
|
|
@@ -13784,7 +14277,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13784
14277
|
const shardKeys = [""];
|
|
13785
14278
|
for (const entry of entries) {
|
|
13786
14279
|
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
13787
|
-
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
14280
|
+
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
14281
|
+
continue;
|
|
13788
14282
|
if (entry.isDirectory()) shardKeys.push(entry.name);
|
|
13789
14283
|
}
|
|
13790
14284
|
return shardKeys;
|
|
@@ -13901,15 +14395,16 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13901
14395
|
if (fromManifest) return fromManifest;
|
|
13902
14396
|
try {
|
|
13903
14397
|
const full = this.sessionPath(id, ".jsonl");
|
|
13904
|
-
const
|
|
13905
|
-
const summary = await this.summarize(id,
|
|
14398
|
+
const stat13 = await fsp9.stat(full);
|
|
14399
|
+
const summary = await this.summarize(id, stat13.mtime.toISOString());
|
|
13906
14400
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
13907
14401
|
const msg = toErrorMessage(err);
|
|
13908
14402
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
13909
|
-
this.logWarn(
|
|
13910
|
-
|
|
13911
|
-
|
|
13912
|
-
|
|
14403
|
+
this.logWarn("Session manifest write failed", {
|
|
14404
|
+
event: "session_store.manifest_write_failed",
|
|
14405
|
+
sessionId: id,
|
|
14406
|
+
message: msg
|
|
14407
|
+
});
|
|
13913
14408
|
});
|
|
13914
14409
|
outcome = "failure";
|
|
13915
14410
|
errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
|
|
@@ -13942,18 +14437,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13942
14437
|
async summaryHeaderFor(ref) {
|
|
13943
14438
|
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
13944
14439
|
try {
|
|
13945
|
-
const
|
|
13946
|
-
if (!
|
|
14440
|
+
const stat13 = await fsp9.stat(ref.filePath);
|
|
14441
|
+
if (!stat13.isFile()) {
|
|
13947
14442
|
return {
|
|
13948
14443
|
id: ref.id,
|
|
13949
14444
|
title: "(damaged)",
|
|
13950
|
-
startedAt:
|
|
14445
|
+
startedAt: stat13.mtime.toISOString(),
|
|
13951
14446
|
model: "unknown",
|
|
13952
14447
|
provider: "unknown",
|
|
13953
14448
|
tokenTotal: 0
|
|
13954
14449
|
};
|
|
13955
14450
|
}
|
|
13956
|
-
mtime =
|
|
14451
|
+
mtime = stat13.mtime.toISOString();
|
|
13957
14452
|
} catch {
|
|
13958
14453
|
return null;
|
|
13959
14454
|
}
|
|
@@ -14016,24 +14511,28 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14016
14511
|
if (r.status === "rejected") {
|
|
14017
14512
|
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
14018
14513
|
if (r.reason?.code !== "ENOENT") {
|
|
14019
|
-
console.warn(
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14024
|
-
|
|
14025
|
-
|
|
14514
|
+
console.warn(
|
|
14515
|
+
JSON.stringify({
|
|
14516
|
+
level: "warn",
|
|
14517
|
+
event: "session_store.delete_failed",
|
|
14518
|
+
sessionId: id,
|
|
14519
|
+
message: msg,
|
|
14520
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14521
|
+
})
|
|
14522
|
+
);
|
|
14026
14523
|
}
|
|
14027
14524
|
}
|
|
14028
14525
|
}
|
|
14029
14526
|
await fsp9.rm(sessDir, { recursive: true, force: true }).catch((err) => {
|
|
14030
|
-
console.warn(
|
|
14031
|
-
|
|
14032
|
-
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
|
|
14527
|
+
console.warn(
|
|
14528
|
+
JSON.stringify({
|
|
14529
|
+
level: "warn",
|
|
14530
|
+
event: "session_store.rmdir_failed",
|
|
14531
|
+
sessionId: id,
|
|
14532
|
+
message: toErrorMessage(err),
|
|
14533
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14534
|
+
})
|
|
14535
|
+
);
|
|
14037
14536
|
});
|
|
14038
14537
|
await this.writeTombstone(id);
|
|
14039
14538
|
}
|
|
@@ -14109,8 +14608,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14109
14608
|
const pruneFile = async (dir, name, prefix) => {
|
|
14110
14609
|
const jsonlPath = path11.join(dir, name);
|
|
14111
14610
|
try {
|
|
14112
|
-
const
|
|
14113
|
-
if (
|
|
14611
|
+
const stat13 = await fsp9.stat(jsonlPath);
|
|
14612
|
+
if (stat13.mtimeMs >= cutoff) return;
|
|
14114
14613
|
} catch {
|
|
14115
14614
|
return;
|
|
14116
14615
|
}
|
|
@@ -14998,11 +15497,6 @@ ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first ite
|
|
|
14998
15497
|
};
|
|
14999
15498
|
}
|
|
15000
15499
|
|
|
15001
|
-
// src/types/blocks.ts
|
|
15002
|
-
function isTextBlock(b) {
|
|
15003
|
-
return b.type === "text";
|
|
15004
|
-
}
|
|
15005
|
-
|
|
15006
15500
|
// src/execution/compaction-core.ts
|
|
15007
15501
|
var FAILURE_PATTERN = /(error|fail|exception|timeout|enonet|eacces|eperm|enoent|abort)/i;
|
|
15008
15502
|
var CORRECTION_PATTERN = /\b(wrong|no\b|stop\b|don'?t\b|actually|fix that|undo|revert|forget|ignore|skip)\b/i;
|
|
@@ -15110,6 +15604,14 @@ function hasMatchingToolPair(resultContent, useContent) {
|
|
|
15110
15604
|
}
|
|
15111
15605
|
function eliseOldToolResults(messages, opts) {
|
|
15112
15606
|
const preserveStart = findPreserveStart(messages, opts.preserveK);
|
|
15607
|
+
const tokenCache = /* @__PURE__ */ new Map();
|
|
15608
|
+
const tokensFor = (b) => {
|
|
15609
|
+
const cached = tokenCache.get(b);
|
|
15610
|
+
if (cached !== void 0) return cached;
|
|
15611
|
+
const t = b.type === "tool_result" ? estimateToolResultTokens(b.content) : b.type === "tool_use" ? estimateToolInputTokens(b.input) : 0;
|
|
15612
|
+
tokenCache.set(b, t);
|
|
15613
|
+
return t;
|
|
15614
|
+
};
|
|
15113
15615
|
let hasOversized = false;
|
|
15114
15616
|
let firstOversizedIndex = -1;
|
|
15115
15617
|
let fastPathIterations = 0;
|
|
@@ -15120,7 +15622,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
15120
15622
|
if (!msg || !Array.isArray(msg.content)) continue;
|
|
15121
15623
|
for (const b of msg.content) {
|
|
15122
15624
|
fastPathInnerIterations++;
|
|
15123
|
-
const oversized =
|
|
15625
|
+
const oversized = tokensFor(b) >= opts.eliseThreshold;
|
|
15124
15626
|
if (oversized) {
|
|
15125
15627
|
hasOversized = true;
|
|
15126
15628
|
firstOversizedIndex = i;
|
|
@@ -15158,7 +15660,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
15158
15660
|
const b = original[idx];
|
|
15159
15661
|
if (!b) continue;
|
|
15160
15662
|
if (b.type === "tool_use") {
|
|
15161
|
-
const tokens2 =
|
|
15663
|
+
const tokens2 = tokensFor(b);
|
|
15162
15664
|
if (tokens2 < opts.eliseThreshold) continue;
|
|
15163
15665
|
const elidedInput = summarizeToolUseInputElision(b, tokens2);
|
|
15164
15666
|
saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
|
|
@@ -15167,7 +15669,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
15167
15669
|
continue;
|
|
15168
15670
|
}
|
|
15169
15671
|
if (b.type !== "tool_result") continue;
|
|
15170
|
-
const tokens =
|
|
15672
|
+
const tokens = tokensFor(b);
|
|
15171
15673
|
if (tokens < opts.eliseThreshold) continue;
|
|
15172
15674
|
saved += tokens;
|
|
15173
15675
|
const elided = {
|
|
@@ -16095,12 +16597,12 @@ function verifyFiles(tokens, files) {
|
|
|
16095
16597
|
const violations = [];
|
|
16096
16598
|
let onPalette = 0;
|
|
16097
16599
|
let offPalette = 0;
|
|
16098
|
-
for (const { path:
|
|
16600
|
+
for (const { path: path29, text } of files) {
|
|
16099
16601
|
const lines = text.split("\n");
|
|
16100
16602
|
lines.forEach((lineText, i) => {
|
|
16101
16603
|
const lineNo = i + 1;
|
|
16102
16604
|
const flag = (snippet, reason) => {
|
|
16103
|
-
violations.push({ file:
|
|
16605
|
+
violations.push({ file: path29, line: lineNo, snippet: snippet.slice(0, 80), reason });
|
|
16104
16606
|
};
|
|
16105
16607
|
for (const re of [HEX_RE, FUNC_COLOR_RE]) {
|
|
16106
16608
|
re.lastIndex = 0;
|
|
@@ -16609,16 +17111,20 @@ function buildRecoveryStrategies(opts) {
|
|
|
16609
17111
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
16610
17112
|
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
16611
17113
|
if (ctx?.signal) {
|
|
16612
|
-
await new Promise((
|
|
17114
|
+
await new Promise((resolve14) => {
|
|
16613
17115
|
if (ctx.signal.aborted) {
|
|
16614
|
-
|
|
17116
|
+
resolve14();
|
|
16615
17117
|
return;
|
|
16616
17118
|
}
|
|
16617
|
-
const timer = setTimeout(
|
|
16618
|
-
ctx.signal.addEventListener(
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
17119
|
+
const timer = setTimeout(resolve14, delay);
|
|
17120
|
+
ctx.signal.addEventListener(
|
|
17121
|
+
"abort",
|
|
17122
|
+
() => {
|
|
17123
|
+
clearTimeout(timer);
|
|
17124
|
+
resolve14();
|
|
17125
|
+
},
|
|
17126
|
+
{ once: true }
|
|
17127
|
+
);
|
|
16622
17128
|
});
|
|
16623
17129
|
} else {
|
|
16624
17130
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -16709,6 +17215,7 @@ function buildRecoveryStrategies(opts) {
|
|
|
16709
17215
|
var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
|
|
16710
17216
|
var HANDLER_KIND_BY_PROVIDER_KIND = {
|
|
16711
17217
|
rate_limit: "rate_limit",
|
|
17218
|
+
quota_exhausted: "rate_limit",
|
|
16712
17219
|
overloaded: "overloaded",
|
|
16713
17220
|
server: "server",
|
|
16714
17221
|
stream_hang: "server",
|
|
@@ -16756,6 +17263,217 @@ var DefaultErrorHandler = class {
|
|
|
16756
17263
|
import { execFile } from "node:child_process";
|
|
16757
17264
|
import { promisify } from "node:util";
|
|
16758
17265
|
|
|
17266
|
+
// src/storage/goal-coordination.ts
|
|
17267
|
+
import { updateTask } from "@wrongstack/kanban";
|
|
17268
|
+
|
|
17269
|
+
// src/storage/goal-kanban.ts
|
|
17270
|
+
import {
|
|
17271
|
+
addColumn,
|
|
17272
|
+
addTask,
|
|
17273
|
+
createBoard,
|
|
17274
|
+
getBoard,
|
|
17275
|
+
listBoards,
|
|
17276
|
+
removeBoard
|
|
17277
|
+
} from "@wrongstack/kanban";
|
|
17278
|
+
var GOAL_BOARD_TAG_PREFIX = "goal:";
|
|
17279
|
+
async function findGoalKanbanBoard(projectRoot, boardId) {
|
|
17280
|
+
if (!projectRoot || !boardId) return null;
|
|
17281
|
+
try {
|
|
17282
|
+
return await getBoard(projectRoot, boardId);
|
|
17283
|
+
} catch {
|
|
17284
|
+
return null;
|
|
17285
|
+
}
|
|
17286
|
+
}
|
|
17287
|
+
async function findGoalBoardByTag(projectRoot, goalFile) {
|
|
17288
|
+
if (!projectRoot) return null;
|
|
17289
|
+
const tag = goalTag(goalFile);
|
|
17290
|
+
const boards = await listBoards(projectRoot);
|
|
17291
|
+
const matched = boards.find((b) => b.tags?.includes(tag));
|
|
17292
|
+
if (!matched) return null;
|
|
17293
|
+
return getBoard(projectRoot, matched.id).catch(() => null);
|
|
17294
|
+
}
|
|
17295
|
+
function goalTag(goalFile) {
|
|
17296
|
+
const goal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, "-").slice(0, 24).toLowerCase();
|
|
17297
|
+
return `${GOAL_BOARD_TAG_PREFIX}${goal}`;
|
|
17298
|
+
}
|
|
17299
|
+
|
|
17300
|
+
// src/storage/goal-coordination.ts
|
|
17301
|
+
var DONE_MARKER = /\[done:\s*([^\]]+)\]/gi;
|
|
17302
|
+
var DONE_PREFIX = /^\s*(?:✅|\[[x✓]\]|\(done\))\s*/i;
|
|
17303
|
+
function parseCompletedGoalDeliverables(finalText, deliverables) {
|
|
17304
|
+
if (!finalText || deliverables.length === 0) return [];
|
|
17305
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17306
|
+
const completed = [];
|
|
17307
|
+
for (const match of finalText.matchAll(DONE_MARKER)) {
|
|
17308
|
+
const raw = match[1]?.trim();
|
|
17309
|
+
if (!raw) continue;
|
|
17310
|
+
const numeric = /^\d+$/.test(raw) ? Number.parseInt(raw, 10) : void 0;
|
|
17311
|
+
let index = numeric === void 0 ? -1 : numeric === 0 ? 0 : numeric - 1;
|
|
17312
|
+
if (index < 0 || index >= deliverables.length) {
|
|
17313
|
+
const prefix = raw.toLowerCase();
|
|
17314
|
+
index = deliverables.findIndex(
|
|
17315
|
+
(item) => stripGoalDeliverableMarker(item).toLowerCase().startsWith(prefix)
|
|
17316
|
+
);
|
|
17317
|
+
}
|
|
17318
|
+
if (index < 0 || index >= deliverables.length || seen.has(index)) continue;
|
|
17319
|
+
const source = deliverables[index] ?? "";
|
|
17320
|
+
if (isGoalDeliverableComplete(source)) continue;
|
|
17321
|
+
seen.add(index);
|
|
17322
|
+
completed.push({ index, text: stripGoalDeliverableMarker(source) });
|
|
17323
|
+
}
|
|
17324
|
+
return completed;
|
|
17325
|
+
}
|
|
17326
|
+
function isGoalDeliverableComplete(value) {
|
|
17327
|
+
return DONE_PREFIX.test(value);
|
|
17328
|
+
}
|
|
17329
|
+
function stripGoalDeliverableMarker(value) {
|
|
17330
|
+
return value.replace(DONE_PREFIX, "").trim();
|
|
17331
|
+
}
|
|
17332
|
+
function applyGoalDeliverableCompletions(goal, completed) {
|
|
17333
|
+
if (completed.length === 0) return recomputeGoalProgress(goal);
|
|
17334
|
+
const completedIndices = new Set(completed.map((item) => item.index));
|
|
17335
|
+
let next = {
|
|
17336
|
+
...goal,
|
|
17337
|
+
deliverables: (goal.deliverables ?? []).map(
|
|
17338
|
+
(item, index) => completedIndices.has(index) && !isGoalDeliverableComplete(item) ? `\u2705 ${item}` : item
|
|
17339
|
+
)
|
|
17340
|
+
};
|
|
17341
|
+
for (const item of completed) {
|
|
17342
|
+
next = appendJournal(next, {
|
|
17343
|
+
source: "deliverable",
|
|
17344
|
+
task: `Deliverable completed: ${item.text}`.slice(0, 240),
|
|
17345
|
+
status: "success",
|
|
17346
|
+
note: `[DONE: ${item.index + 1}]`
|
|
17347
|
+
});
|
|
17348
|
+
}
|
|
17349
|
+
return recomputeGoalProgress(next);
|
|
17350
|
+
}
|
|
17351
|
+
function recomputeGoalProgress(goal) {
|
|
17352
|
+
const deliverables = goal.deliverables ?? [];
|
|
17353
|
+
if (deliverables.length === 0) return goal;
|
|
17354
|
+
const done = deliverables.filter(isGoalDeliverableComplete).length;
|
|
17355
|
+
const progress = Math.round(done / deliverables.length * 100);
|
|
17356
|
+
return recordProgress(goal, progress, `${done}/${deliverables.length} deliverables complete`);
|
|
17357
|
+
}
|
|
17358
|
+
async function coordinateGoalIteration(options) {
|
|
17359
|
+
const current = await loadGoal(options.goalPath, options.events);
|
|
17360
|
+
if (!current) return null;
|
|
17361
|
+
const completed = parseCompletedGoalDeliverables(
|
|
17362
|
+
options.finalText,
|
|
17363
|
+
current.deliverables ?? []
|
|
17364
|
+
);
|
|
17365
|
+
let goal = applyGoalDeliverableCompletions(current, completed);
|
|
17366
|
+
await saveGoal(options.goalPath, goal, options.events);
|
|
17367
|
+
const kanbanUpdatedTaskIds = await refreshGoalKanban(
|
|
17368
|
+
options.projectRoot,
|
|
17369
|
+
goal,
|
|
17370
|
+
completed
|
|
17371
|
+
);
|
|
17372
|
+
goal = await recomputeGoalProgressFromKanban(options.projectRoot, goal);
|
|
17373
|
+
let reached = false;
|
|
17374
|
+
const allComplete = (goal.deliverables?.length ?? 0) > 0 && goal.deliverables?.every(isGoalDeliverableComplete) === true;
|
|
17375
|
+
if (allComplete && options.brain) {
|
|
17376
|
+
const decision = await options.brain.decide({
|
|
17377
|
+
id: `goal-reached-${goal.iterations}`,
|
|
17378
|
+
sessionId: options.sessionId,
|
|
17379
|
+
source: "system",
|
|
17380
|
+
question: "Have all goal deliverables been reached and verified?",
|
|
17381
|
+
context: [
|
|
17382
|
+
`Goal: ${goal.refinedGoal ?? goal.goal}`,
|
|
17383
|
+
`Progress: ${goal.progress ?? 0}%`,
|
|
17384
|
+
"Completed deliverables:",
|
|
17385
|
+
...(goal.deliverables ?? []).map(
|
|
17386
|
+
(item, index) => `${index + 1}. ${stripGoalDeliverableMarker(item)}`
|
|
17387
|
+
)
|
|
17388
|
+
].join("\n"),
|
|
17389
|
+
risk: "high",
|
|
17390
|
+
fallback: "continue",
|
|
17391
|
+
options: [
|
|
17392
|
+
{
|
|
17393
|
+
id: "goal_reached",
|
|
17394
|
+
label: "Goal reached",
|
|
17395
|
+
consequence: "Persist the completed goal and stop the eternal loop."
|
|
17396
|
+
},
|
|
17397
|
+
{
|
|
17398
|
+
id: "keep_working",
|
|
17399
|
+
label: "Keep working",
|
|
17400
|
+
consequence: "Leave the goal active for another iteration."
|
|
17401
|
+
}
|
|
17402
|
+
]
|
|
17403
|
+
});
|
|
17404
|
+
reached = decision.type === "answer" && decision.optionId === "goal_reached";
|
|
17405
|
+
}
|
|
17406
|
+
if (reached) {
|
|
17407
|
+
const reachedAt = (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
17408
|
+
goal = appendJournal(
|
|
17409
|
+
{
|
|
17410
|
+
...goal,
|
|
17411
|
+
goalState: "completed",
|
|
17412
|
+
engineState: "stopped",
|
|
17413
|
+
progress: 100,
|
|
17414
|
+
progressNote: "goal reached",
|
|
17415
|
+
reachedAt,
|
|
17416
|
+
reachedNote: "goal reached"
|
|
17417
|
+
},
|
|
17418
|
+
{
|
|
17419
|
+
source: "deliverable",
|
|
17420
|
+
task: "GOAL REACHED",
|
|
17421
|
+
status: "success",
|
|
17422
|
+
note: "goal reached"
|
|
17423
|
+
}
|
|
17424
|
+
);
|
|
17425
|
+
}
|
|
17426
|
+
await saveGoal(options.goalPath, goal, options.events);
|
|
17427
|
+
return { goal, completed, kanbanUpdatedTaskIds, reached };
|
|
17428
|
+
}
|
|
17429
|
+
async function resolveGoalBoard(projectRoot, goal) {
|
|
17430
|
+
const boardId = goal.kanbanBoardId;
|
|
17431
|
+
if (boardId) {
|
|
17432
|
+
const board = await findGoalKanbanBoard(projectRoot, boardId);
|
|
17433
|
+
if (board) return board;
|
|
17434
|
+
}
|
|
17435
|
+
return findGoalBoardByTag(projectRoot, goal);
|
|
17436
|
+
}
|
|
17437
|
+
async function refreshGoalKanban(projectRoot, goal, completed) {
|
|
17438
|
+
if (completed.length === 0) return [];
|
|
17439
|
+
const board = await resolveGoalBoard(projectRoot, goal);
|
|
17440
|
+
if (!board) return [];
|
|
17441
|
+
const doneColumn = board.columns.find((column) => column.title.toLowerCase() === "done");
|
|
17442
|
+
if (!doneColumn) return [];
|
|
17443
|
+
const completedTitles = new Set(completed.map((item) => normalizeTitle(item.text)));
|
|
17444
|
+
const updated = [];
|
|
17445
|
+
for (const task of board.tasks) {
|
|
17446
|
+
if (!completedTitles.has(normalizeTitle(task.title))) continue;
|
|
17447
|
+
const result = await updateTask(
|
|
17448
|
+
projectRoot,
|
|
17449
|
+
board.id,
|
|
17450
|
+
task.id,
|
|
17451
|
+
{ columnId: doneColumn.id, status: "completed" },
|
|
17452
|
+
{
|
|
17453
|
+
actor: "goal-coordinator",
|
|
17454
|
+
note: "deliverable-completed: Updated from [DONE:] marker."
|
|
17455
|
+
}
|
|
17456
|
+
);
|
|
17457
|
+
if (result) updated.push(task.id);
|
|
17458
|
+
}
|
|
17459
|
+
return updated;
|
|
17460
|
+
}
|
|
17461
|
+
async function recomputeGoalProgressFromKanban(projectRoot, goal) {
|
|
17462
|
+
const board = await resolveGoalBoard(projectRoot, goal);
|
|
17463
|
+
if (!board || !goal.deliverables?.length) return recomputeGoalProgress(goal);
|
|
17464
|
+
const completedTitles = new Set(
|
|
17465
|
+
board.tasks.filter((task) => task.status === "completed").map((task) => normalizeTitle(task.title))
|
|
17466
|
+
);
|
|
17467
|
+
const deliverables = goal.deliverables.map((item) => {
|
|
17468
|
+
if (isGoalDeliverableComplete(item)) return item;
|
|
17469
|
+
return completedTitles.has(normalizeTitle(item)) ? `\u2705 ${item}` : item;
|
|
17470
|
+
});
|
|
17471
|
+
return recomputeGoalProgress({ ...goal, deliverables });
|
|
17472
|
+
}
|
|
17473
|
+
function normalizeTitle(value) {
|
|
17474
|
+
return stripGoalDeliverableMarker(value).replace(/\s+/g, " ").trim().toLowerCase();
|
|
17475
|
+
}
|
|
17476
|
+
|
|
16759
17477
|
// src/execution/autonomy-brain.ts
|
|
16760
17478
|
function formatDecisionSummary(decision, request) {
|
|
16761
17479
|
const question = request.question.length > 80 ? request.question.slice(0, 77) + "\u2026" : request.question;
|
|
@@ -16773,6 +17491,7 @@ function formatDecisionSummary(decision, request) {
|
|
|
16773
17491
|
var execFileP = promisify(execFile);
|
|
16774
17492
|
var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
|
|
16775
17493
|
var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]*complete\]\s*$/im;
|
|
17494
|
+
var DONE_DELIVERABLE_MARKER = /\[done:\s*[^\]]+\]/i;
|
|
16776
17495
|
var GOAL_CLEAR_MARKER = /^\s*\[\/?goal\s*clear\]\s*$/im;
|
|
16777
17496
|
var EternalAutonomyEngine = class {
|
|
16778
17497
|
constructor(opts) {
|
|
@@ -17027,6 +17746,24 @@ var EternalAutonomyEngine = class {
|
|
|
17027
17746
|
const cycleGapMs = this.opts.cycleGapMs ?? 1e3;
|
|
17028
17747
|
emit({ phase: "sleep", ms: cycleGapMs });
|
|
17029
17748
|
await sleep(cycleGapMs);
|
|
17749
|
+
let coordinatedProgress;
|
|
17750
|
+
if (DONE_DELIVERABLE_MARKER.test(finalText)) {
|
|
17751
|
+
const coordinated = await coordinateGoalIteration({
|
|
17752
|
+
projectRoot: this.opts.projectRoot,
|
|
17753
|
+
goalPath: this.goalPath,
|
|
17754
|
+
finalText,
|
|
17755
|
+
brain: this.opts.brain,
|
|
17756
|
+
sessionId: this.opts.agent.ctx.session?.id,
|
|
17757
|
+
events: this.opts.events,
|
|
17758
|
+
now: this.opts.now
|
|
17759
|
+
});
|
|
17760
|
+
coordinatedProgress = coordinated?.goal.progress;
|
|
17761
|
+
if (coordinated?.reached) {
|
|
17762
|
+
this.stopRequested = true;
|
|
17763
|
+
this.opts.onEternalStop?.();
|
|
17764
|
+
return true;
|
|
17765
|
+
}
|
|
17766
|
+
}
|
|
17030
17767
|
if (GOAL_COMPLETE_MARKER.test(finalText)) {
|
|
17031
17768
|
await this.clearGoalManually(finalText);
|
|
17032
17769
|
this.stopRequested = true;
|
|
@@ -17039,8 +17776,9 @@ var EternalAutonomyEngine = class {
|
|
|
17039
17776
|
}
|
|
17040
17777
|
const parsed = parseProgressFromText(finalText);
|
|
17041
17778
|
if (parsed) {
|
|
17042
|
-
|
|
17043
|
-
|
|
17779
|
+
const progress = Math.max(parsed.progress, coordinatedProgress ?? 0);
|
|
17780
|
+
await this.updateProgress(progress, parsed.note);
|
|
17781
|
+
if (parsed.progress >= 100 && coordinatedProgress === void 0) {
|
|
17044
17782
|
await this.markGoalCompleted(
|
|
17045
17783
|
{ source: action.source, task: action.task, directive: action.directive },
|
|
17046
17784
|
`progress reached 100%: ${parsed.note ?? "goal complete"}`
|
|
@@ -17605,7 +18343,7 @@ ${summaryText}` : summaryText;
|
|
|
17605
18343
|
timeoutMs: 3e4,
|
|
17606
18344
|
maxTokens: 1024,
|
|
17607
18345
|
signal: ctx.signal,
|
|
17608
|
-
|
|
18346
|
+
fallbackModels: ["/summary"]
|
|
17609
18347
|
});
|
|
17610
18348
|
if (result.text && !result.error) return result.text;
|
|
17611
18349
|
} else {
|
|
@@ -18372,8 +19110,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
18372
19110
|
});
|
|
18373
19111
|
await Promise.race([
|
|
18374
19112
|
drainPromise,
|
|
18375
|
-
new Promise((
|
|
18376
|
-
drainTimer = setTimeout(
|
|
19113
|
+
new Promise((resolve14) => {
|
|
19114
|
+
drainTimer = setTimeout(resolve14, STREAM_DRAIN_TIMEOUT_MS);
|
|
18377
19115
|
})
|
|
18378
19116
|
]);
|
|
18379
19117
|
} finally {
|
|
@@ -18396,7 +19134,145 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
18396
19134
|
return buildResponse(state);
|
|
18397
19135
|
}
|
|
18398
19136
|
|
|
19137
|
+
// src/chronicle/prompt-manifest.ts
|
|
19138
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
19139
|
+
function createChroniclePromptManifest(request) {
|
|
19140
|
+
const roleCounts = {}, blockCounts = {};
|
|
19141
|
+
const messages = request.messages.map((message, index) => messageSummary(message, index, roleCounts, blockCounts));
|
|
19142
|
+
const systemText = (request.system ?? []).map((block) => block.text).join("\n");
|
|
19143
|
+
const toolRecords = (request.tools ?? []).map((tool) => ({
|
|
19144
|
+
name: tool.name,
|
|
19145
|
+
schemaHash: hash(stable(tool.inputSchema)),
|
|
19146
|
+
permission: tool.permission,
|
|
19147
|
+
mutating: tool.mutating,
|
|
19148
|
+
riskTier: tool.riskTier,
|
|
19149
|
+
capabilities: tool.capabilities,
|
|
19150
|
+
estimatedTokens: tool._estDefTokens ?? 0
|
|
19151
|
+
}));
|
|
19152
|
+
const core = {
|
|
19153
|
+
systemHash: hash(systemText),
|
|
19154
|
+
messages: messages.map(({ hash: contentHash, ...rest }) => ({ ...rest, contentHash })),
|
|
19155
|
+
tools: toolRecords,
|
|
19156
|
+
model: request.model,
|
|
19157
|
+
maxTokens: request.maxTokens,
|
|
19158
|
+
temperature: request.temperature,
|
|
19159
|
+
topP: request.topP,
|
|
19160
|
+
topK: request.topK,
|
|
19161
|
+
seed: request.seed,
|
|
19162
|
+
toolChoice: request.toolChoice,
|
|
19163
|
+
reasoning: request.reasoning,
|
|
19164
|
+
cache: request.cache,
|
|
19165
|
+
responseFormat: request.responseFormat?.type
|
|
19166
|
+
};
|
|
19167
|
+
return {
|
|
19168
|
+
manifestId: `prompt_${hash(stable(core))}`,
|
|
19169
|
+
messageCount: messages.length,
|
|
19170
|
+
estimatedMessageTokens: messages.reduce((sum, message) => sum + (message.estimatedTokens ?? 0), 0),
|
|
19171
|
+
contentBytes: messages.reduce((sum, message) => sum + message.bytes, 0) + Buffer.byteLength(systemText),
|
|
19172
|
+
roleCounts,
|
|
19173
|
+
blockCounts,
|
|
19174
|
+
system: { blockCount: request.system?.length ?? 0, bytes: Buffer.byteLength(systemText), hash: hash(systemText) },
|
|
19175
|
+
messages,
|
|
19176
|
+
tools: {
|
|
19177
|
+
count: toolRecords.length,
|
|
19178
|
+
estimatedDefinitionTokens: toolRecords.reduce((sum, tool) => sum + tool.estimatedTokens, 0),
|
|
19179
|
+
manifestHash: hash(stable(toolRecords)),
|
|
19180
|
+
names: toolRecords.map((tool) => tool.name),
|
|
19181
|
+
mutating: toolRecords.filter((tool) => tool.mutating).length,
|
|
19182
|
+
destructive: toolRecords.filter((tool) => tool.riskTier === "destructive").length
|
|
19183
|
+
},
|
|
19184
|
+
request: {
|
|
19185
|
+
maxTokens: request.maxTokens,
|
|
19186
|
+
temperature: request.temperature,
|
|
19187
|
+
topP: request.topP,
|
|
19188
|
+
topK: request.topK,
|
|
19189
|
+
frequencyPenalty: request.frequencyPenalty,
|
|
19190
|
+
presencePenalty: request.presencePenalty,
|
|
19191
|
+
seed: request.seed,
|
|
19192
|
+
candidateCount: request.candidateCount,
|
|
19193
|
+
logprobs: request.logprobs,
|
|
19194
|
+
topLogprobs: request.topLogprobs,
|
|
19195
|
+
stopSequenceCount: request.stopSequences?.length ?? 0,
|
|
19196
|
+
toolChoice: request.toolChoice,
|
|
19197
|
+
reasoning: request.reasoning,
|
|
19198
|
+
cache: request.cache,
|
|
19199
|
+
responseFormat: request.responseFormat?.type,
|
|
19200
|
+
safetySettingCount: request.safetySettings?.length ?? 0,
|
|
19201
|
+
userHash: request.user ? hash(request.user) : void 0
|
|
19202
|
+
}
|
|
19203
|
+
};
|
|
19204
|
+
}
|
|
19205
|
+
function messageSummary(message, index, roles, totals) {
|
|
19206
|
+
roles[message.role] = (roles[message.role] ?? 0) + 1;
|
|
19207
|
+
const blocks = typeof message.content === "string" ? { text: 1 } : countBlocks(message.content);
|
|
19208
|
+
for (const [type, count] of Object.entries(blocks)) totals[type] = (totals[type] ?? 0) + count;
|
|
19209
|
+
const content = contentIdentity(message.content);
|
|
19210
|
+
return {
|
|
19211
|
+
index,
|
|
19212
|
+
role: message.role,
|
|
19213
|
+
bytes: content.bytes,
|
|
19214
|
+
...message._estTokens !== void 0 ? { estimatedTokens: message._estTokens } : {},
|
|
19215
|
+
hash: content.hash,
|
|
19216
|
+
blocks
|
|
19217
|
+
};
|
|
19218
|
+
}
|
|
19219
|
+
function countBlocks(blocks) {
|
|
19220
|
+
const counts = {};
|
|
19221
|
+
for (const block of blocks) counts[block.type] = (counts[block.type] ?? 0) + 1;
|
|
19222
|
+
return counts;
|
|
19223
|
+
}
|
|
19224
|
+
function hash(value) {
|
|
19225
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
19226
|
+
}
|
|
19227
|
+
function contentIdentity(content) {
|
|
19228
|
+
if (typeof content === "string") return { hash: hash(content), bytes: Buffer.byteLength(content) };
|
|
19229
|
+
const digest = createHash5("sha256");
|
|
19230
|
+
let bytes = 0;
|
|
19231
|
+
const add = (value) => {
|
|
19232
|
+
if (!value) return;
|
|
19233
|
+
digest.update(value);
|
|
19234
|
+
bytes += Buffer.byteLength(value);
|
|
19235
|
+
};
|
|
19236
|
+
for (const block of content) {
|
|
19237
|
+
add(block.type);
|
|
19238
|
+
if (block.type === "text") add(block.text);
|
|
19239
|
+
else if (block.type === "thinking") {
|
|
19240
|
+
add(block.thinking);
|
|
19241
|
+
add(block.signature);
|
|
19242
|
+
} else if (block.type === "tool_use") {
|
|
19243
|
+
add(block.id);
|
|
19244
|
+
add(block.name);
|
|
19245
|
+
add(stable(block.input));
|
|
19246
|
+
} else if (block.type === "tool_result") {
|
|
19247
|
+
add(block.tool_use_id);
|
|
19248
|
+
add(block.name);
|
|
19249
|
+
add(block.content);
|
|
19250
|
+
add(String(block.is_error ?? false));
|
|
19251
|
+
} else if (block.type === "image") {
|
|
19252
|
+
add(block.source.type);
|
|
19253
|
+
add(block.source.media_type);
|
|
19254
|
+
add(block.source.url);
|
|
19255
|
+
add(block.source.data);
|
|
19256
|
+
}
|
|
19257
|
+
}
|
|
19258
|
+
return { hash: digest.digest("hex"), bytes };
|
|
19259
|
+
}
|
|
19260
|
+
function stable(value) {
|
|
19261
|
+
if (value === void 0) return "undefined";
|
|
19262
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
19263
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
19264
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => `${JSON.stringify(key)}:${stable(child)}`).join(",")}}`;
|
|
19265
|
+
}
|
|
19266
|
+
|
|
19267
|
+
// src/observability/network-telemetry.ts
|
|
19268
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19269
|
+
var storage = new AsyncLocalStorage();
|
|
19270
|
+
function runWithNetworkTelemetry(context, run) {
|
|
19271
|
+
return storage.run(context, run);
|
|
19272
|
+
}
|
|
19273
|
+
|
|
18399
19274
|
// src/core/provider-runner.ts
|
|
19275
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
18400
19276
|
function providerLogCtx(p, r) {
|
|
18401
19277
|
return {
|
|
18402
19278
|
providerId: p.id,
|
|
@@ -18408,8 +19284,31 @@ function providerLogCtx(p, r) {
|
|
|
18408
19284
|
}
|
|
18409
19285
|
async function runProviderWithRetry(opts) {
|
|
18410
19286
|
const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
|
|
19287
|
+
const logicalRequestId = randomUUID10();
|
|
19288
|
+
const promptManifest = createChroniclePromptManifest(request);
|
|
18411
19289
|
let attempt = 0;
|
|
18412
19290
|
for (; ; ) {
|
|
19291
|
+
const attemptId = randomUUID10();
|
|
19292
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19293
|
+
const startedNs = process.hrtime.bigint();
|
|
19294
|
+
const correlation = {
|
|
19295
|
+
sessionId: ctx.session.id,
|
|
19296
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
19297
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
19298
|
+
logicalRequestId,
|
|
19299
|
+
attemptId,
|
|
19300
|
+
attempt,
|
|
19301
|
+
providerId: provider.id,
|
|
19302
|
+
model: request.model
|
|
19303
|
+
};
|
|
19304
|
+
events.emit("provider.attempt.started", {
|
|
19305
|
+
...correlation,
|
|
19306
|
+
streaming: provider.capabilities.streaming,
|
|
19307
|
+
messageCount: request.messages.length,
|
|
19308
|
+
toolCount: request.tools?.length ?? 0,
|
|
19309
|
+
promptManifest,
|
|
19310
|
+
startedAt
|
|
19311
|
+
});
|
|
18413
19312
|
const span = tracer?.startSpan("provider.complete", {
|
|
18414
19313
|
"provider.id": provider.id,
|
|
18415
19314
|
"provider.model": request.model,
|
|
@@ -18418,11 +19317,27 @@ async function runProviderWithRetry(opts) {
|
|
|
18418
19317
|
});
|
|
18419
19318
|
logger.debug(`Provider attempt ${attempt + 1} starting`, providerLogCtx(provider, request));
|
|
18420
19319
|
try {
|
|
18421
|
-
const res =
|
|
19320
|
+
const res = await runWithNetworkTelemetry({
|
|
19321
|
+
events,
|
|
19322
|
+
sessionId: ctx.session.id,
|
|
19323
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
19324
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
19325
|
+
attemptId,
|
|
19326
|
+
initiator: "provider",
|
|
19327
|
+
operationName: `${provider.id}.complete`
|
|
19328
|
+
}, () => provider.capabilities.streaming ? streamProviderToResponse(provider, request, signal, ctx, events, logger) : provider.complete(request, { signal }));
|
|
18422
19329
|
span?.setAttribute("provider.stopReason", res.stopReason);
|
|
18423
19330
|
span?.setAttribute("provider.usage_in", res.usage.input);
|
|
18424
19331
|
span?.setAttribute("provider.usage_out", res.usage.output);
|
|
18425
19332
|
span?.end();
|
|
19333
|
+
events.emit("provider.attempt.completed", {
|
|
19334
|
+
...correlation,
|
|
19335
|
+
startedAt,
|
|
19336
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19337
|
+
durationMs: elapsedMs(startedNs),
|
|
19338
|
+
stopReason: res.stopReason,
|
|
19339
|
+
usage: res.usage
|
|
19340
|
+
});
|
|
18426
19341
|
logger.debug("Provider call succeeded", {
|
|
18427
19342
|
...providerLogCtx(provider, request),
|
|
18428
19343
|
stopReason: res.stopReason,
|
|
@@ -18441,16 +19356,28 @@ async function runProviderWithRetry(opts) {
|
|
|
18441
19356
|
const errAsErr = err instanceof Error ? err : new Error(String(err));
|
|
18442
19357
|
const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
|
|
18443
19358
|
const description = isProviderErr ? err.describe() : errAsErr.message;
|
|
19359
|
+
const delay = canRetry ? Math.round(retry.delayMs(attempt, isProviderErr ? err : errAsErr)) : void 0;
|
|
19360
|
+
events.emit("provider.attempt.failed", {
|
|
19361
|
+
...correlation,
|
|
19362
|
+
startedAt,
|
|
19363
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19364
|
+
durationMs: elapsedMs(startedNs),
|
|
19365
|
+
status: isProviderErr ? err.status : 0,
|
|
19366
|
+
failureKind: isProviderErr ? err.kind : "unknown",
|
|
19367
|
+
description,
|
|
19368
|
+
retryable: canRetry,
|
|
19369
|
+
retryScheduled: canRetry,
|
|
19370
|
+
...delay !== void 0 ? { retryDelayMs: delay } : {},
|
|
19371
|
+
...isProviderErr && err.body?.requestId ? { providerRequestId: err.body.requestId } : {}
|
|
19372
|
+
});
|
|
18444
19373
|
if (!canRetry) {
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
});
|
|
18453
|
-
}
|
|
19374
|
+
events.emit("provider.error", {
|
|
19375
|
+
sessionId: ctx.session.id,
|
|
19376
|
+
providerId: isProviderErr ? err.providerId : provider.id,
|
|
19377
|
+
status: isProviderErr ? err.status : 0,
|
|
19378
|
+
description,
|
|
19379
|
+
retryable: false
|
|
19380
|
+
});
|
|
18454
19381
|
logger.error(`Provider call failed after ${attempt + 1} attempt(s) \u2014 ${description}`, {
|
|
18455
19382
|
...providerLogCtx(provider, request),
|
|
18456
19383
|
attempts: attempt + 1,
|
|
@@ -18461,9 +19388,6 @@ async function runProviderWithRetry(opts) {
|
|
|
18461
19388
|
});
|
|
18462
19389
|
throw toWrongStackError(err);
|
|
18463
19390
|
}
|
|
18464
|
-
const delay = Math.round(
|
|
18465
|
-
retry.delayMs(attempt, isProviderErr ? err : errAsErr)
|
|
18466
|
-
);
|
|
18467
19391
|
const attemptNum = attempt + 1;
|
|
18468
19392
|
const maxAttempts = retry.maxAttempts(isProviderErr ? err : errAsErr);
|
|
18469
19393
|
logger.warn(`Provider retry ${attemptNum}/${maxAttempts} in ${delay}ms \u2014 ${description}`, {
|
|
@@ -18474,17 +19398,15 @@ async function runProviderWithRetry(opts) {
|
|
|
18474
19398
|
errorDescription: description,
|
|
18475
19399
|
status: isProviderErr ? err.status : void 0
|
|
18476
19400
|
});
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
|
|
18480
|
-
|
|
18481
|
-
|
|
18482
|
-
|
|
18483
|
-
|
|
18484
|
-
|
|
18485
|
-
|
|
18486
|
-
}
|
|
18487
|
-
await new Promise((resolve12, reject) => {
|
|
19401
|
+
events.emit("provider.retry", {
|
|
19402
|
+
sessionId: ctx.session.id,
|
|
19403
|
+
providerId: isProviderErr ? err.providerId : provider.id,
|
|
19404
|
+
attempt: attemptNum,
|
|
19405
|
+
delayMs: delay,
|
|
19406
|
+
status: isProviderErr ? err.status : 0,
|
|
19407
|
+
description
|
|
19408
|
+
});
|
|
19409
|
+
await new Promise((resolve14, reject) => {
|
|
18488
19410
|
let settled = false;
|
|
18489
19411
|
const cleanup = () => {
|
|
18490
19412
|
clearTimeout(t);
|
|
@@ -18500,7 +19422,7 @@ async function runProviderWithRetry(opts) {
|
|
|
18500
19422
|
if (settled) return;
|
|
18501
19423
|
settled = true;
|
|
18502
19424
|
cleanup();
|
|
18503
|
-
|
|
19425
|
+
resolve14();
|
|
18504
19426
|
}, delay);
|
|
18505
19427
|
if (signal.aborted) {
|
|
18506
19428
|
onAbort();
|
|
@@ -18512,6 +19434,9 @@ async function runProviderWithRetry(opts) {
|
|
|
18512
19434
|
}
|
|
18513
19435
|
}
|
|
18514
19436
|
}
|
|
19437
|
+
function elapsedMs(startedNs) {
|
|
19438
|
+
return Number(process.hrtime.bigint() - startedNs) / 1e6;
|
|
19439
|
+
}
|
|
18515
19440
|
|
|
18516
19441
|
// src/execution/provider-runner-impl.ts
|
|
18517
19442
|
var DefaultProviderRunner = class {
|
|
@@ -18525,6 +19450,7 @@ import { randomInt } from "node:crypto";
|
|
|
18525
19450
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
18526
19451
|
var MAX_ATTEMPTS_BY_KIND = {
|
|
18527
19452
|
rate_limit: 5,
|
|
19453
|
+
quota_exhausted: 0,
|
|
18528
19454
|
stream_hang: 5,
|
|
18529
19455
|
// transient, worth retrying aggressively
|
|
18530
19456
|
overloaded: 3,
|
|
@@ -19263,7 +20189,9 @@ var DefaultSkillLoader = class {
|
|
|
19263
20189
|
const seen = /* @__PURE__ */ new Set();
|
|
19264
20190
|
for (const { dir, source, originTool } of this.dirs) {
|
|
19265
20191
|
try {
|
|
19266
|
-
const entries = await fs5.readdir(dir, { withFileTypes: true })
|
|
20192
|
+
const entries = (await fs5.readdir(dir, { withFileTypes: true })).sort(
|
|
20193
|
+
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
20194
|
+
);
|
|
19267
20195
|
for (const e of entries) {
|
|
19268
20196
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
19269
20197
|
const skillFile = path15.join(dir, e.name, "SKILL.md");
|
|
@@ -19890,10 +20818,178 @@ function readPolicy(ctx) {
|
|
|
19890
20818
|
}
|
|
19891
20819
|
|
|
19892
20820
|
// src/execution/tool-executor.ts
|
|
19893
|
-
import { randomUUID as
|
|
20821
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
20822
|
+
|
|
20823
|
+
// src/observability/process-telemetry.ts
|
|
20824
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
20825
|
+
var storage2 = new AsyncLocalStorage2();
|
|
20826
|
+
function runWithProcessTelemetry(context, run) {
|
|
20827
|
+
return storage2.run(context, run);
|
|
20828
|
+
}
|
|
20829
|
+
|
|
20830
|
+
// src/execution/tool-executor.ts
|
|
19894
20831
|
import { isDeepStrictEqual } from "node:util";
|
|
19895
20832
|
import * as fs8 from "node:fs/promises";
|
|
20833
|
+
import * as path19 from "node:path";
|
|
20834
|
+
|
|
20835
|
+
// src/security/kanban-boundary.ts
|
|
20836
|
+
import { realpath as realpath3 } from "node:fs/promises";
|
|
19896
20837
|
import * as path18 from "node:path";
|
|
20838
|
+
import {
|
|
20839
|
+
evaluateKanbanBoundaryOpaque,
|
|
20840
|
+
evaluateKanbanBoundaryPath,
|
|
20841
|
+
readBoard,
|
|
20842
|
+
resolveKanbanBoundaryLayers
|
|
20843
|
+
} from "@wrongstack/kanban";
|
|
20844
|
+
var PATH_KEYS = /* @__PURE__ */ new Set([
|
|
20845
|
+
"path",
|
|
20846
|
+
"paths",
|
|
20847
|
+
"file",
|
|
20848
|
+
"files",
|
|
20849
|
+
"directory",
|
|
20850
|
+
"dir",
|
|
20851
|
+
"cwd",
|
|
20852
|
+
"root",
|
|
20853
|
+
"baseDir",
|
|
20854
|
+
"out",
|
|
20855
|
+
"outputPath",
|
|
20856
|
+
"sourcePath",
|
|
20857
|
+
"targetPath",
|
|
20858
|
+
"destinationPath",
|
|
20859
|
+
"fromPath",
|
|
20860
|
+
"toPath",
|
|
20861
|
+
"worktreePath"
|
|
20862
|
+
]);
|
|
20863
|
+
var TOOL_PATH_KEYS = {
|
|
20864
|
+
// `target` is an identifier in tools such as plan/task/document, but a
|
|
20865
|
+
// filesystem target in language_info. Keep ambiguous names tool-specific.
|
|
20866
|
+
language_info: ["target"]
|
|
20867
|
+
};
|
|
20868
|
+
async function evaluateToolKanbanBoundary(tool, input, ctx) {
|
|
20869
|
+
const identity = resolveKanbanIdentity(ctx);
|
|
20870
|
+
if (!identity.boardId) return { decision: "allow" };
|
|
20871
|
+
const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
|
|
20872
|
+
if (!board) return { decision: "allow" };
|
|
20873
|
+
const task = identity.taskId ? board.tasks.find((candidate) => candidate.id === identity.taskId) : void 0;
|
|
20874
|
+
const layers = resolveKanbanBoundaryLayers(board, task);
|
|
20875
|
+
if (layers.length === 0) return { decision: "allow" };
|
|
20876
|
+
const caps = tool.capabilities ?? [];
|
|
20877
|
+
const access2 = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
|
|
20878
|
+
const shellLike = caps.some((capability) => capability.startsWith("shell."));
|
|
20879
|
+
const candidates = access2 ? await extractCandidatePaths(tool.name, input, ctx) : [];
|
|
20880
|
+
let evaluation;
|
|
20881
|
+
if (shellLike || access2 && candidates.length === 0) {
|
|
20882
|
+
evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
|
|
20883
|
+
} else if (access2) {
|
|
20884
|
+
const results = candidates.map(
|
|
20885
|
+
(candidate) => evaluateKanbanBoundaryPath(layers, candidate, access2)
|
|
20886
|
+
);
|
|
20887
|
+
evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
|
|
20888
|
+
} else {
|
|
20889
|
+
evaluation = { decision: "allow" };
|
|
20890
|
+
}
|
|
20891
|
+
return {
|
|
20892
|
+
...evaluation,
|
|
20893
|
+
boardId: board.id,
|
|
20894
|
+
...task ? { taskId: task.id } : {}
|
|
20895
|
+
};
|
|
20896
|
+
}
|
|
20897
|
+
function resolveKanbanIdentity(ctx) {
|
|
20898
|
+
const metaKanban = ctx.meta["kanban"];
|
|
20899
|
+
const record = metaKanban && typeof metaKanban === "object" ? metaKanban : void 0;
|
|
20900
|
+
const boardId = ctx.currentKanbanBoardId ?? (typeof record?.["boardId"] === "string" ? record["boardId"] : void 0);
|
|
20901
|
+
const taskId = ctx.currentKanbanTaskId ?? (typeof record?.["taskId"] === "string" ? record["taskId"] : void 0);
|
|
20902
|
+
const policyRoot = typeof record?.["projectRoot"] === "string" ? record["projectRoot"] : void 0;
|
|
20903
|
+
return {
|
|
20904
|
+
...boardId ? { boardId } : {},
|
|
20905
|
+
...taskId ? { taskId } : {},
|
|
20906
|
+
...policyRoot ? { policyRoot } : {}
|
|
20907
|
+
};
|
|
20908
|
+
}
|
|
20909
|
+
async function extractCandidatePaths(toolName, input, ctx) {
|
|
20910
|
+
if (toolName === "patch" && typeof input["patch"] === "string") {
|
|
20911
|
+
const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
|
|
20912
|
+
const directory = path18.isAbsolute(directoryInput) ? directoryInput : path18.resolve(ctx.workingDir, directoryInput);
|
|
20913
|
+
const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
|
|
20914
|
+
const targets = extractPatchTargets(input["patch"], strip).map(
|
|
20915
|
+
(target) => relativeToProject(path18.resolve(directory, target), ctx.projectRoot)
|
|
20916
|
+
);
|
|
20917
|
+
return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
|
|
20918
|
+
}
|
|
20919
|
+
const values = [];
|
|
20920
|
+
const pathKeys = new Set(PATH_KEYS);
|
|
20921
|
+
for (const key of TOOL_PATH_KEYS[toolName] ?? []) pathKeys.add(key);
|
|
20922
|
+
collectPathValues(input, values, pathKeys);
|
|
20923
|
+
if (toolName === "scaffold" && typeof input["name"] === "string") {
|
|
20924
|
+
const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
|
|
20925
|
+
values.push(path18.join(cwd, input["name"]));
|
|
20926
|
+
}
|
|
20927
|
+
const candidates = [
|
|
20928
|
+
...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
|
|
20929
|
+
];
|
|
20930
|
+
return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
|
|
20931
|
+
}
|
|
20932
|
+
async function canonicalizeCandidatePath(candidate, ctx) {
|
|
20933
|
+
if (path18.isAbsolute(candidate)) return candidate;
|
|
20934
|
+
const absolute = path18.resolve(ctx.projectRoot, candidate);
|
|
20935
|
+
const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
20936
|
+
let probe = absolute;
|
|
20937
|
+
const missingSegments = [];
|
|
20938
|
+
while (true) {
|
|
20939
|
+
try {
|
|
20940
|
+
const canonical = path18.join(await realpath3(probe), ...missingSegments);
|
|
20941
|
+
return relativeToProject(canonical, canonicalRoot);
|
|
20942
|
+
} catch (cause) {
|
|
20943
|
+
const code = cause.code;
|
|
20944
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
|
|
20945
|
+
const parent = path18.dirname(probe);
|
|
20946
|
+
if (parent === probe) return absolute;
|
|
20947
|
+
missingSegments.unshift(path18.basename(probe));
|
|
20948
|
+
probe = parent;
|
|
20949
|
+
}
|
|
20950
|
+
}
|
|
20951
|
+
}
|
|
20952
|
+
function collectPathValues(value, output, pathKeys, key) {
|
|
20953
|
+
if (typeof value === "string") {
|
|
20954
|
+
if (key && pathKeys.has(key)) output.push(value);
|
|
20955
|
+
return;
|
|
20956
|
+
}
|
|
20957
|
+
if (Array.isArray(value)) {
|
|
20958
|
+
for (const item of value) collectPathValues(item, output, pathKeys, key);
|
|
20959
|
+
return;
|
|
20960
|
+
}
|
|
20961
|
+
if (!value || typeof value !== "object") return;
|
|
20962
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
20963
|
+
collectPathValues(child, output, pathKeys, childKey);
|
|
20964
|
+
}
|
|
20965
|
+
}
|
|
20966
|
+
function splitPathList(value) {
|
|
20967
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
20968
|
+
}
|
|
20969
|
+
function resolveInputPath(value, ctx) {
|
|
20970
|
+
const absolute = path18.isAbsolute(value) ? value : path18.resolve(ctx.workingDir, value);
|
|
20971
|
+
return relativeToProject(absolute, ctx.projectRoot);
|
|
20972
|
+
}
|
|
20973
|
+
function relativeToProject(absolute, projectRoot) {
|
|
20974
|
+
const relative5 = path18.relative(projectRoot, absolute).replace(/\\/g, "/");
|
|
20975
|
+
return relative5.startsWith("../") || path18.isAbsolute(relative5) ? absolute : relative5 || ".";
|
|
20976
|
+
}
|
|
20977
|
+
function extractPatchTargets(patchText, strip) {
|
|
20978
|
+
const targets = [];
|
|
20979
|
+
for (const match of patchText.matchAll(/^\+\+\+\s+([^\t\r\n]+)/gm)) {
|
|
20980
|
+
const raw = match[1]?.trim();
|
|
20981
|
+
if (!raw || raw === "/dev/null") continue;
|
|
20982
|
+
const parts = raw.replace(/\\/g, "/").split("/").filter((part) => part && part !== ".");
|
|
20983
|
+
if (parts.length > strip) targets.push(parts.slice(strip).join("/"));
|
|
20984
|
+
}
|
|
20985
|
+
return targets;
|
|
20986
|
+
}
|
|
20987
|
+
function stringValue(value) {
|
|
20988
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
20989
|
+
}
|
|
20990
|
+
function numericValue(value) {
|
|
20991
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
20992
|
+
}
|
|
19897
20993
|
|
|
19898
20994
|
// src/types/tool-executor.ts
|
|
19899
20995
|
var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
|
|
@@ -20114,6 +21210,17 @@ ${errorDetails}`,
|
|
|
20114
21210
|
return { result, tool, durationMs: Date.now() - start };
|
|
20115
21211
|
}
|
|
20116
21212
|
}
|
|
21213
|
+
const boundary = await evaluateToolKanbanBoundary(tool, use.input, ctx);
|
|
21214
|
+
if (boundary.decision === "block") {
|
|
21215
|
+
const result = {
|
|
21216
|
+
type: "tool_result",
|
|
21217
|
+
tool_use_id: use.id,
|
|
21218
|
+
content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
|
|
21219
|
+
is_error: true
|
|
21220
|
+
};
|
|
21221
|
+
budget = this.budgetForString(result.content, budget);
|
|
21222
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
21223
|
+
}
|
|
20117
21224
|
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
20118
21225
|
let effectivePermission = decision.permission;
|
|
20119
21226
|
const policy = this.opts.permissionPolicy;
|
|
@@ -20122,27 +21229,31 @@ ${errorDetails}`,
|
|
|
20122
21229
|
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
|
|
20123
21230
|
effectivePermission = "confirm";
|
|
20124
21231
|
}
|
|
21232
|
+
if (boundary.decision === "confirm" && effectivePermission !== "deny") {
|
|
21233
|
+
effectivePermission = "confirm";
|
|
21234
|
+
}
|
|
20125
21235
|
if (effectivePermission === "deny") {
|
|
20126
21236
|
const result = this.deniedResult(use, decision.reason);
|
|
20127
21237
|
budget = this.budgetForString(result.content, budget);
|
|
20128
21238
|
return { result, tool, durationMs: Date.now() - start };
|
|
20129
21239
|
}
|
|
20130
21240
|
if (effectivePermission === "confirm") {
|
|
21241
|
+
const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
20131
21242
|
if (this.opts.confirmAwaiter) {
|
|
20132
21243
|
const awaiter = this.opts.confirmAwaiter;
|
|
20133
21244
|
const choice = await new Promise(
|
|
20134
|
-
(
|
|
21245
|
+
(resolve14, reject) => {
|
|
20135
21246
|
const signal = ctx.signal;
|
|
20136
|
-
const onAbort = () =>
|
|
21247
|
+
const onAbort = () => resolve14("abort");
|
|
20137
21248
|
if (signal.aborted) {
|
|
20138
|
-
|
|
21249
|
+
resolve14("abort");
|
|
20139
21250
|
return;
|
|
20140
21251
|
}
|
|
20141
21252
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
20142
|
-
awaiter(tool, use.input, use.id,
|
|
21253
|
+
awaiter(tool, use.input, use.id, suggestedPattern).then(
|
|
20143
21254
|
(c) => {
|
|
20144
21255
|
signal.removeEventListener("abort", onAbort);
|
|
20145
|
-
|
|
21256
|
+
resolve14(c);
|
|
20146
21257
|
},
|
|
20147
21258
|
(e) => {
|
|
20148
21259
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -20162,7 +21273,6 @@ ${errorDetails}`,
|
|
|
20162
21273
|
return { result, tool, durationMs: Date.now() - start };
|
|
20163
21274
|
}
|
|
20164
21275
|
} else {
|
|
20165
|
-
const suggestedPattern = subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
20166
21276
|
const pending = {
|
|
20167
21277
|
type: "tool_confirm_pending",
|
|
20168
21278
|
toolUseId: use.id,
|
|
@@ -20170,7 +21280,8 @@ ${errorDetails}`,
|
|
|
20170
21280
|
input: use.input,
|
|
20171
21281
|
suggestedPattern,
|
|
20172
21282
|
decisionSource: decision.source,
|
|
20173
|
-
riskTier: decision.riskTier ?? tool.riskTier
|
|
21283
|
+
riskTier: decision.riskTier ?? tool.riskTier,
|
|
21284
|
+
...boundary.decision === "confirm" && boundary.reason ? { boundaryReason: boundary.reason } : {}
|
|
20174
21285
|
};
|
|
20175
21286
|
return { result: pending, tool, durationMs: Date.now() - start };
|
|
20176
21287
|
}
|
|
@@ -20184,6 +21295,17 @@ ${errorDetails}`,
|
|
|
20184
21295
|
"tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
|
|
20185
21296
|
});
|
|
20186
21297
|
try {
|
|
21298
|
+
const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
|
|
21299
|
+
const caps = tool.capabilities ?? [];
|
|
21300
|
+
const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
|
|
21301
|
+
const absPath = hasFileCapability && typeof inputPath === "string" ? path19.isAbsolute(inputPath) ? inputPath : path19.resolve(ctx.projectRoot, inputPath) : void 0;
|
|
21302
|
+
let writeTargetExisted;
|
|
21303
|
+
if (tool.name === "write" && caps.includes("fs.write") && absPath) {
|
|
21304
|
+
writeTargetExisted = await fs8.stat(absPath).then(
|
|
21305
|
+
(stat13) => stat13.isFile(),
|
|
21306
|
+
(error2) => error2.code === "ENOENT" ? false : void 0
|
|
21307
|
+
);
|
|
21308
|
+
}
|
|
20187
21309
|
const producedText = await this.produceToolOutput(tool, use, ctx, budget);
|
|
20188
21310
|
let { block: result, bytes } = this.settleToolOutput(tool, use, producedText, budget);
|
|
20189
21311
|
budget -= bytes;
|
|
@@ -20212,6 +21334,35 @@ ${post.additionalContext}`;
|
|
|
20212
21334
|
span?.setAttribute("tool.is_error", !!result.is_error);
|
|
20213
21335
|
span?.setAttribute("tool.output_bytes", outputChars);
|
|
20214
21336
|
this.logToolSuccess(ctx, use, tool.name, Date.now() - start, outputChars);
|
|
21337
|
+
if (!result.is_error && typeof inputPath === "string" && absPath) {
|
|
21338
|
+
const operation = tool.name === "read" ? "read" : caps.includes("fs.write") && tool.name === "write" ? writeTargetExisted === false ? "create" : "update" : caps.includes("fs.write") ? "update" : "read";
|
|
21339
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
21340
|
+
ctx.recordFileEvent?.({
|
|
21341
|
+
operation,
|
|
21342
|
+
filePath: inputPath,
|
|
21343
|
+
absPath,
|
|
21344
|
+
toolName: tool.name,
|
|
21345
|
+
toolUseId: use.id,
|
|
21346
|
+
durationMs: Date.now() - start
|
|
21347
|
+
});
|
|
21348
|
+
this.opts.events?.emit("file.event", {
|
|
21349
|
+
operation,
|
|
21350
|
+
filePath: inputPath,
|
|
21351
|
+
absPath,
|
|
21352
|
+
sessionId: ctx.session.id,
|
|
21353
|
+
agentId: ctx.agentId,
|
|
21354
|
+
agentName: ctx.agentName,
|
|
21355
|
+
provider: typeof ctx.provider === "object" ? ctx.provider.id : String(ctx.provider),
|
|
21356
|
+
model: ctx.model,
|
|
21357
|
+
toolName: tool.name,
|
|
21358
|
+
toolUseId: use.id,
|
|
21359
|
+
scope: ctx.currentKanbanTaskId ? "task" : "session",
|
|
21360
|
+
taskId: ctx.currentKanbanTaskId,
|
|
21361
|
+
boardId: ctx.currentKanbanBoardId,
|
|
21362
|
+
timestamp: ts,
|
|
21363
|
+
durationMs: Date.now() - start
|
|
21364
|
+
});
|
|
21365
|
+
}
|
|
20215
21366
|
return { result, tool, durationMs: Date.now() - start };
|
|
20216
21367
|
} catch (err) {
|
|
20217
21368
|
if (isWrongStackError(err)) {
|
|
@@ -20309,7 +21460,7 @@ ${post.additionalContext}`;
|
|
|
20309
21460
|
const bridge = async (toolName, input) => {
|
|
20310
21461
|
const nestedUse = {
|
|
20311
21462
|
type: "tool_use",
|
|
20312
|
-
id: `nested-${
|
|
21463
|
+
id: `nested-${randomUUID11()}`,
|
|
20313
21464
|
name: toolName,
|
|
20314
21465
|
input
|
|
20315
21466
|
};
|
|
@@ -20363,6 +21514,9 @@ ${post.additionalContext}`;
|
|
|
20363
21514
|
async produceToolOutput(tool, use, ctx, budgetHint) {
|
|
20364
21515
|
this.opts.events?.emit("tool.started", {
|
|
20365
21516
|
sessionId: ctx.session.id,
|
|
21517
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21518
|
+
agentId: ctx.agentId,
|
|
21519
|
+
agentName: ctx.agentName,
|
|
20366
21520
|
name: tool.name,
|
|
20367
21521
|
id: use.id,
|
|
20368
21522
|
input: use.input
|
|
@@ -20413,11 +21567,31 @@ ${post.additionalContext}`;
|
|
|
20413
21567
|
)
|
|
20414
21568
|
]);
|
|
20415
21569
|
let output;
|
|
20416
|
-
const
|
|
21570
|
+
const execute = () => typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
|
|
21571
|
+
const telemetryToolCallId = toolUseId ?? `nested-${randomUUID11()}`;
|
|
21572
|
+
const toolPromise = this.opts.events ? runWithNetworkTelemetry({
|
|
21573
|
+
events: this.opts.events,
|
|
21574
|
+
sessionId: ctx.session.id,
|
|
21575
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21576
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
21577
|
+
toolCallId: telemetryToolCallId,
|
|
21578
|
+
initiator: "tool",
|
|
21579
|
+
operationName: tool.name
|
|
21580
|
+
}, () => runWithProcessTelemetry(
|
|
21581
|
+
{
|
|
21582
|
+
events: this.opts.events,
|
|
21583
|
+
sessionId: ctx.session.id,
|
|
21584
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21585
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
21586
|
+
toolCallId: telemetryToolCallId,
|
|
21587
|
+
toolName: tool.name
|
|
21588
|
+
},
|
|
21589
|
+
execute
|
|
21590
|
+
)) : execute();
|
|
20417
21591
|
toolPromise.catch(() => {
|
|
20418
21592
|
});
|
|
20419
21593
|
try {
|
|
20420
|
-
output = await new Promise((
|
|
21594
|
+
output = await new Promise((resolve14, reject) => {
|
|
20421
21595
|
const onAbort = () => {
|
|
20422
21596
|
setTimeout(() => reject(abortReasonToError(combined.reason)), 0);
|
|
20423
21597
|
};
|
|
@@ -20425,7 +21599,7 @@ ${post.additionalContext}`;
|
|
|
20425
21599
|
toolPromise.then(
|
|
20426
21600
|
(v) => {
|
|
20427
21601
|
combined.removeEventListener("abort", onAbort);
|
|
20428
|
-
|
|
21602
|
+
resolve14(v);
|
|
20429
21603
|
},
|
|
20430
21604
|
(e) => {
|
|
20431
21605
|
combined.removeEventListener("abort", onAbort);
|
|
@@ -20471,6 +21645,9 @@ ${post.additionalContext}`;
|
|
|
20471
21645
|
const emitProgress = (ev) => {
|
|
20472
21646
|
this.opts.events?.emit("tool.progress", {
|
|
20473
21647
|
sessionId: ctx.session.id,
|
|
21648
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21649
|
+
agentId: ctx.agentId,
|
|
21650
|
+
agentName: ctx.agentName,
|
|
20474
21651
|
name: tool.name,
|
|
20475
21652
|
id: toolUseId ?? "<unknown>",
|
|
20476
21653
|
event: ev
|
|
@@ -20685,13 +21862,25 @@ function httpStatusToCategory(status) {
|
|
|
20685
21862
|
return { category: "transient" /* TRANSIENT */, retryable: true, detail: `HTTP ${status}` };
|
|
20686
21863
|
}
|
|
20687
21864
|
if (status === 404 || status === 410) {
|
|
20688
|
-
return {
|
|
21865
|
+
return {
|
|
21866
|
+
category: "not_found" /* NOT_FOUND */,
|
|
21867
|
+
retryable: false,
|
|
21868
|
+
detail: `HTTP ${status}`
|
|
21869
|
+
};
|
|
20689
21870
|
}
|
|
20690
21871
|
if (status === 401 || status === 403) {
|
|
20691
|
-
return {
|
|
21872
|
+
return {
|
|
21873
|
+
category: "permission" /* PERMISSION */,
|
|
21874
|
+
retryable: false,
|
|
21875
|
+
detail: `HTTP ${status}`
|
|
21876
|
+
};
|
|
20692
21877
|
}
|
|
20693
21878
|
if (status === 400) {
|
|
20694
|
-
return {
|
|
21879
|
+
return {
|
|
21880
|
+
category: "validation" /* VALIDATION */,
|
|
21881
|
+
retryable: false,
|
|
21882
|
+
detail: `HTTP ${status}`
|
|
21883
|
+
};
|
|
20695
21884
|
}
|
|
20696
21885
|
return { category: "fatal" /* FATAL */, retryable: false, detail: `HTTP ${status}` };
|
|
20697
21886
|
}
|
|
@@ -20701,11 +21890,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
20701
21890
|
return content;
|
|
20702
21891
|
}
|
|
20703
21892
|
try {
|
|
20704
|
-
const dir =
|
|
21893
|
+
const dir = path19.join(wstackGlobalRoot(), "tool-output");
|
|
20705
21894
|
await fs8.mkdir(dir, { recursive: true });
|
|
20706
21895
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
20707
21896
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
20708
|
-
const filePath =
|
|
21897
|
+
const filePath = path19.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
|
|
20709
21898
|
await fs8.writeFile(filePath, content, "utf8");
|
|
20710
21899
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
20711
21900
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
@@ -21017,7 +22206,7 @@ var contextManagerTool = createContextManagerTool();
|
|
|
21017
22206
|
|
|
21018
22207
|
// src/infrastructure/logger.ts
|
|
21019
22208
|
import * as fsp11 from "node:fs/promises";
|
|
21020
|
-
import * as
|
|
22209
|
+
import * as path20 from "node:path";
|
|
21021
22210
|
var LEVEL_RANK2 = {
|
|
21022
22211
|
error: 0,
|
|
21023
22212
|
warn: 1,
|
|
@@ -21081,7 +22270,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
21081
22270
|
this.stderr = opts.stderr !== false;
|
|
21082
22271
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
21083
22272
|
if (this.file) {
|
|
21084
|
-
const dir =
|
|
22273
|
+
const dir = path20.dirname(this.file);
|
|
21085
22274
|
this._tail = this._tail.then(async () => {
|
|
21086
22275
|
await fsp11.mkdir(dir, { recursive: true });
|
|
21087
22276
|
}).catch(() => void 0);
|
|
@@ -21403,27 +22592,27 @@ function codexModelMeta(id) {
|
|
|
21403
22592
|
|
|
21404
22593
|
// src/models/mode-store.ts
|
|
21405
22594
|
import * as fs9 from "node:fs/promises";
|
|
21406
|
-
import * as
|
|
22595
|
+
import * as path22 from "node:path";
|
|
21407
22596
|
|
|
21408
22597
|
// src/types/mode-prompts.ts
|
|
21409
22598
|
import { readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
|
|
21410
|
-
import * as
|
|
22599
|
+
import * as path21 from "node:path";
|
|
21411
22600
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
21412
22601
|
function modePrompt(id) {
|
|
21413
22602
|
for (const dir of modePromptDirCandidates()) {
|
|
21414
22603
|
try {
|
|
21415
|
-
return readFileSync4(
|
|
22604
|
+
return readFileSync4(path21.join(dir, `${id}.md`), "utf8").trimEnd();
|
|
21416
22605
|
} catch {
|
|
21417
22606
|
}
|
|
21418
22607
|
}
|
|
21419
22608
|
return "";
|
|
21420
22609
|
}
|
|
21421
22610
|
function modePromptDirCandidates() {
|
|
21422
|
-
const here =
|
|
22611
|
+
const here = path21.dirname(fileURLToPath4(import.meta.url));
|
|
21423
22612
|
const candidates = [
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
22613
|
+
path21.resolve(here, "../../instructions/modes"),
|
|
22614
|
+
path21.resolve(here, "../instructions/modes"),
|
|
22615
|
+
path21.resolve(here, "instructions/modes")
|
|
21427
22616
|
];
|
|
21428
22617
|
return candidates.sort((a, b) => Number(!isDirectory3(a)) - Number(!isDirectory3(b)));
|
|
21429
22618
|
}
|
|
@@ -21655,7 +22844,7 @@ var DefaultModeStore = class {
|
|
|
21655
22844
|
}
|
|
21656
22845
|
async loadActiveMode() {
|
|
21657
22846
|
try {
|
|
21658
|
-
const configPath =
|
|
22847
|
+
const configPath = path22.join(this.configDir, "mode.json");
|
|
21659
22848
|
const content = await fs9.readFile(configPath, "utf8");
|
|
21660
22849
|
const data = JSON.parse(content);
|
|
21661
22850
|
this.activeModeId = data.activeMode ?? null;
|
|
@@ -21666,7 +22855,7 @@ var DefaultModeStore = class {
|
|
|
21666
22855
|
async saveActiveMode() {
|
|
21667
22856
|
try {
|
|
21668
22857
|
await fs9.mkdir(this.configDir, { recursive: true });
|
|
21669
|
-
const configPath =
|
|
22858
|
+
const configPath = path22.join(this.configDir, "mode.json");
|
|
21670
22859
|
await atomicWrite(
|
|
21671
22860
|
configPath,
|
|
21672
22861
|
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
@@ -21681,11 +22870,11 @@ async function loadProjectModes(modesDir) {
|
|
|
21681
22870
|
const entries = await fs9.readdir(modesDir);
|
|
21682
22871
|
for (const entry of entries) {
|
|
21683
22872
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
21684
|
-
const filePath =
|
|
21685
|
-
const
|
|
21686
|
-
if (!
|
|
22873
|
+
const filePath = path22.join(modesDir, entry);
|
|
22874
|
+
const stat13 = await fs9.stat(filePath);
|
|
22875
|
+
if (!stat13.isFile()) continue;
|
|
21687
22876
|
const content = await fs9.readFile(filePath, "utf8");
|
|
21688
|
-
const id =
|
|
22877
|
+
const id = path22.basename(entry, path22.extname(entry));
|
|
21689
22878
|
modes.push({
|
|
21690
22879
|
id,
|
|
21691
22880
|
name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
@@ -21701,7 +22890,7 @@ async function loadProjectModes(modesDir) {
|
|
|
21701
22890
|
async function loadUserModes(modesDir) {
|
|
21702
22891
|
const modes = [];
|
|
21703
22892
|
try {
|
|
21704
|
-
const manifestPath =
|
|
22893
|
+
const manifestPath = path22.join(modesDir, "modes.json");
|
|
21705
22894
|
const content = await fs9.readFile(manifestPath, "utf8");
|
|
21706
22895
|
const manifest = JSON.parse(content);
|
|
21707
22896
|
for (const mode of manifest.modes) {
|
|
@@ -21714,7 +22903,7 @@ async function loadUserModes(modesDir) {
|
|
|
21714
22903
|
|
|
21715
22904
|
// src/models/models-registry.ts
|
|
21716
22905
|
import * as fs10 from "node:fs/promises";
|
|
21717
|
-
import * as
|
|
22906
|
+
import * as path23 from "node:path";
|
|
21718
22907
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
21719
22908
|
var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
|
|
21720
22909
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -21790,7 +22979,7 @@ var DefaultModelsRegistry = class {
|
|
|
21790
22979
|
this.overlay = opts.overlay;
|
|
21791
22980
|
this.overlayUrl = opts.overlayUrl;
|
|
21792
22981
|
this.overlayFile = opts.overlayFile;
|
|
21793
|
-
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ?
|
|
22982
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path23.join(path23.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
21794
22983
|
}
|
|
21795
22984
|
async load(opts = {}) {
|
|
21796
22985
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -22042,7 +23231,7 @@ var DefaultModelsRegistry = class {
|
|
|
22042
23231
|
}
|
|
22043
23232
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
22044
23233
|
cacheLocation() {
|
|
22045
|
-
return
|
|
23234
|
+
return path23.resolve(this.cacheFile);
|
|
22046
23235
|
}
|
|
22047
23236
|
};
|
|
22048
23237
|
function formatAge(seconds) {
|
|
@@ -22280,9 +23469,9 @@ var DefaultHealthRegistry = class {
|
|
|
22280
23469
|
}
|
|
22281
23470
|
async runOne(check) {
|
|
22282
23471
|
let timer = null;
|
|
22283
|
-
const timeout = new Promise((
|
|
23472
|
+
const timeout = new Promise((resolve14) => {
|
|
22284
23473
|
timer = setTimeout(
|
|
22285
|
-
() =>
|
|
23474
|
+
() => resolve14({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
22286
23475
|
this.timeoutMs
|
|
22287
23476
|
);
|
|
22288
23477
|
});
|
|
@@ -22466,7 +23655,7 @@ async function startMetricsServer(opts) {
|
|
|
22466
23655
|
const tls = opts.tls;
|
|
22467
23656
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
22468
23657
|
const host = opts.host ?? "127.0.0.1";
|
|
22469
|
-
const
|
|
23658
|
+
const path29 = opts.path ?? "/metrics";
|
|
22470
23659
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
22471
23660
|
const healthRegistry = opts.healthRegistry;
|
|
22472
23661
|
const listener = (req, res) => {
|
|
@@ -22476,7 +23665,7 @@ async function startMetricsServer(opts) {
|
|
|
22476
23665
|
return;
|
|
22477
23666
|
}
|
|
22478
23667
|
const url = req.url.split("?")[0];
|
|
22479
|
-
if (url ===
|
|
23668
|
+
if (url === path29) {
|
|
22480
23669
|
let body;
|
|
22481
23670
|
try {
|
|
22482
23671
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -22522,14 +23711,14 @@ async function startMetricsServer(opts) {
|
|
|
22522
23711
|
const { createServer } = await import("node:http");
|
|
22523
23712
|
server = createServer(listener);
|
|
22524
23713
|
}
|
|
22525
|
-
await new Promise((
|
|
23714
|
+
await new Promise((resolve14, reject) => {
|
|
22526
23715
|
const onError = (err) => {
|
|
22527
23716
|
server.off("listening", onListening);
|
|
22528
23717
|
reject(err);
|
|
22529
23718
|
};
|
|
22530
23719
|
const onListening = () => {
|
|
22531
23720
|
server.off("error", onError);
|
|
22532
|
-
|
|
23721
|
+
resolve14();
|
|
22533
23722
|
};
|
|
22534
23723
|
server.once("error", onError);
|
|
22535
23724
|
server.once("listening", onListening);
|
|
@@ -22540,9 +23729,9 @@ async function startMetricsServer(opts) {
|
|
|
22540
23729
|
const protocol = useHttps ? "https" : "http";
|
|
22541
23730
|
return {
|
|
22542
23731
|
port: boundPort,
|
|
22543
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
22544
|
-
close: () => new Promise((
|
|
22545
|
-
server.close((err) => err ? reject(err) :
|
|
23732
|
+
url: `${protocol}://${host}:${boundPort}${path29}`,
|
|
23733
|
+
close: () => new Promise((resolve14, reject) => {
|
|
23734
|
+
server.close((err) => err ? reject(err) : resolve14());
|
|
22546
23735
|
})
|
|
22547
23736
|
};
|
|
22548
23737
|
}
|
|
@@ -22857,19 +24046,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
|
|
|
22857
24046
|
function isRecord5(value) {
|
|
22858
24047
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22859
24048
|
}
|
|
22860
|
-
function error(diagnostics, code,
|
|
22861
|
-
diagnostics.push({ severity: "error", code, path:
|
|
24049
|
+
function error(diagnostics, code, path29, message) {
|
|
24050
|
+
diagnostics.push({ severity: "error", code, path: path29, message });
|
|
22862
24051
|
}
|
|
22863
|
-
function validatePatterns(value,
|
|
24052
|
+
function validatePatterns(value, path29, diagnostics) {
|
|
22864
24053
|
if (!Array.isArray(value)) {
|
|
22865
|
-
error(diagnostics, "invalid_pattern_list",
|
|
24054
|
+
error(diagnostics, "invalid_pattern_list", path29, "must be an array of strings");
|
|
22866
24055
|
return void 0;
|
|
22867
24056
|
}
|
|
22868
24057
|
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
22869
24058
|
error(
|
|
22870
24059
|
diagnostics,
|
|
22871
24060
|
"too_many_patterns",
|
|
22872
|
-
|
|
24061
|
+
path29,
|
|
22873
24062
|
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
22874
24063
|
);
|
|
22875
24064
|
return void 0;
|
|
@@ -22877,7 +24066,7 @@ function validatePatterns(value, path28, diagnostics) {
|
|
|
22877
24066
|
const patterns = [];
|
|
22878
24067
|
const seen = /* @__PURE__ */ new Set();
|
|
22879
24068
|
for (const [index, pattern] of value.entries()) {
|
|
22880
|
-
const itemPath = `${
|
|
24069
|
+
const itemPath = `${path29}[${index}]`;
|
|
22881
24070
|
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
22882
24071
|
error(
|
|
22883
24072
|
diagnostics,
|
|
@@ -22989,8 +24178,8 @@ var LruCache = class {
|
|
|
22989
24178
|
this.capacity = capacity;
|
|
22990
24179
|
}
|
|
22991
24180
|
get(key) {
|
|
24181
|
+
if (!this.store.has(key)) return void 0;
|
|
22992
24182
|
const value = this.store.get(key);
|
|
22993
|
-
if (value === void 0) return void 0;
|
|
22994
24183
|
this.store.delete(key);
|
|
22995
24184
|
this.store.set(key, value);
|
|
22996
24185
|
return value;
|
|
@@ -23247,9 +24436,21 @@ var DefaultPermissionPolicy = class {
|
|
|
23247
24436
|
this._evalCache.clear();
|
|
23248
24437
|
this.loaded = true;
|
|
23249
24438
|
}
|
|
24439
|
+
_logDeny(tool, subject, reason) {
|
|
24440
|
+
console.warn(JSON.stringify({
|
|
24441
|
+
level: "warn",
|
|
24442
|
+
event: "permission.denied",
|
|
24443
|
+
message: `Permission denied: ${tool}${subject ? ` (subject: ${subject})` : ""} \u2014 ${reason}`,
|
|
24444
|
+
tool,
|
|
24445
|
+
subject,
|
|
24446
|
+
reason,
|
|
24447
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24448
|
+
}));
|
|
24449
|
+
}
|
|
23250
24450
|
async evaluate(tool, input, ctx) {
|
|
23251
24451
|
if (!this.loaded) await this.reload();
|
|
23252
24452
|
if (this.policyInvalid) {
|
|
24453
|
+
this._logDeny(tool.name, void 0, "trust policy is invalid");
|
|
23253
24454
|
return {
|
|
23254
24455
|
permission: "deny",
|
|
23255
24456
|
source: "deny",
|
|
@@ -23265,6 +24466,7 @@ var DefaultPermissionPolicy = class {
|
|
|
23265
24466
|
if (cached !== void 0) return cached;
|
|
23266
24467
|
}
|
|
23267
24468
|
if (this.sessionDenied.has(cacheKey)) {
|
|
24469
|
+
this._logDeny(tool.name, subject, "session soft deny (user pressed no)");
|
|
23268
24470
|
const decision = { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" };
|
|
23269
24471
|
this._evalCache.set(cacheKey, decision);
|
|
23270
24472
|
return decision;
|
|
@@ -23279,11 +24481,13 @@ var DefaultPermissionPolicy = class {
|
|
|
23279
24481
|
return decision;
|
|
23280
24482
|
}
|
|
23281
24483
|
if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
|
|
24484
|
+
this._logDeny(tool.name, subject, "matched deny pattern");
|
|
23282
24485
|
const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
|
|
23283
24486
|
this._evalCache.set(cacheKey, decision);
|
|
23284
24487
|
return decision;
|
|
23285
24488
|
}
|
|
23286
24489
|
if (tool.permission === "deny") {
|
|
24490
|
+
this._logDeny(tool.name, subject, "tool default deny");
|
|
23287
24491
|
const decision = { permission: "deny", source: "default", reason: "tool default deny" };
|
|
23288
24492
|
this._evalCache.set(cacheKey, decision);
|
|
23289
24493
|
return decision;
|
|
@@ -23311,6 +24515,7 @@ var DefaultPermissionPolicy = class {
|
|
|
23311
24515
|
}
|
|
23312
24516
|
if (userDecision === "deny") {
|
|
23313
24517
|
await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
|
|
24518
|
+
this._logDeny(tool.name, subject, "user denied sensitive read");
|
|
23314
24519
|
return { permission: "deny", source: "user", reason: "user denied sensitive read" };
|
|
23315
24520
|
}
|
|
23316
24521
|
return {
|
|
@@ -23363,6 +24568,7 @@ var DefaultPermissionPolicy = class {
|
|
|
23363
24568
|
}
|
|
23364
24569
|
if (decision === "deny") {
|
|
23365
24570
|
await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
|
|
24571
|
+
this._logDeny(tool.name, subject, "user denied");
|
|
23366
24572
|
return { permission: "deny", source: "user", reason: "user denied" };
|
|
23367
24573
|
}
|
|
23368
24574
|
return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
|
|
@@ -23681,7 +24887,7 @@ var DefaultSecretScrubber = class {
|
|
|
23681
24887
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, scryptSync } from "node:crypto";
|
|
23682
24888
|
import * as fs12 from "node:fs";
|
|
23683
24889
|
import * as fsp12 from "node:fs/promises";
|
|
23684
|
-
import * as
|
|
24890
|
+
import * as path24 from "node:path";
|
|
23685
24891
|
|
|
23686
24892
|
// src/types/secret-vault.ts
|
|
23687
24893
|
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
@@ -23821,8 +25027,8 @@ function checkKeyFilePermissions(keyFile, opts) {
|
|
|
23821
25027
|
if (process.platform === "win32") return;
|
|
23822
25028
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
23823
25029
|
try {
|
|
23824
|
-
const
|
|
23825
|
-
const actualMode =
|
|
25030
|
+
const stat13 = fs12.statSync(keyFile);
|
|
25031
|
+
const actualMode = stat13.mode & 511;
|
|
23826
25032
|
if (actualMode !== KEY_FILE_MODE) {
|
|
23827
25033
|
warn(
|
|
23828
25034
|
`Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`
|
|
@@ -23918,7 +25124,7 @@ var DefaultSecretVault = class {
|
|
|
23918
25124
|
const oldVersion = this._keyVersion;
|
|
23919
25125
|
const newKey = randomBytes4(KEY_BYTES);
|
|
23920
25126
|
const newVersion = oldVersion + 1;
|
|
23921
|
-
fs12.mkdirSync(
|
|
25127
|
+
fs12.mkdirSync(path24.dirname(this.keyFile), { recursive: true });
|
|
23922
25128
|
const passphrase = getVaultPassphrase();
|
|
23923
25129
|
if (passphrase) {
|
|
23924
25130
|
fs12.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
|
|
@@ -24002,7 +25208,7 @@ var DefaultSecretVault = class {
|
|
|
24002
25208
|
} catch (err) {
|
|
24003
25209
|
if (err.code !== "ENOENT") throw err;
|
|
24004
25210
|
}
|
|
24005
|
-
fs12.mkdirSync(
|
|
25211
|
+
fs12.mkdirSync(path24.dirname(this.keyFile), { recursive: true });
|
|
24006
25212
|
const key = randomBytes4(KEY_BYTES);
|
|
24007
25213
|
const passphrase = getVaultPassphrase();
|
|
24008
25214
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
@@ -24060,7 +25266,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
24060
25266
|
}
|
|
24061
25267
|
const merged = deepMerge(current, patch ?? {});
|
|
24062
25268
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
24063
|
-
await fsp12.mkdir(
|
|
25269
|
+
await fsp12.mkdir(path24.dirname(configPath), { recursive: true });
|
|
24064
25270
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
24065
25271
|
await restrictFilePermissions(configPath);
|
|
24066
25272
|
}
|
|
@@ -24144,7 +25350,7 @@ function walkCount(node, vault, counter) {
|
|
|
24144
25350
|
// src/storage/attachment-store.ts
|
|
24145
25351
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
24146
25352
|
import * as fsp13 from "node:fs/promises";
|
|
24147
|
-
import * as
|
|
25353
|
+
import * as path25 from "node:path";
|
|
24148
25354
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
24149
25355
|
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
|
|
24150
25356
|
var DefaultAttachmentStore = class {
|
|
@@ -24165,7 +25371,7 @@ var DefaultAttachmentStore = class {
|
|
|
24165
25371
|
let data = input.data;
|
|
24166
25372
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
24167
25373
|
await fsp13.mkdir(this.spoolDir, { recursive: true });
|
|
24168
|
-
spooledPath =
|
|
25374
|
+
spooledPath = path25.join(this.spoolDir, `${id}.bin`);
|
|
24169
25375
|
await atomicWrite(spooledPath, input.data, {
|
|
24170
25376
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
24171
25377
|
});
|
|
@@ -24285,7 +25491,7 @@ function mergeAdjacentText(blocks) {
|
|
|
24285
25491
|
|
|
24286
25492
|
// src/storage/config-loader.ts
|
|
24287
25493
|
import * as fs13 from "node:fs/promises";
|
|
24288
|
-
import * as
|
|
25494
|
+
import * as path26 from "node:path";
|
|
24289
25495
|
|
|
24290
25496
|
// src/types/config.ts
|
|
24291
25497
|
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
@@ -24545,6 +25751,7 @@ var defaultIndexing = {
|
|
|
24545
25751
|
};
|
|
24546
25752
|
var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
24547
25753
|
"version",
|
|
25754
|
+
"activeProfile",
|
|
24548
25755
|
"model",
|
|
24549
25756
|
"cwd",
|
|
24550
25757
|
"context",
|
|
@@ -24567,6 +25774,7 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
24567
25774
|
"fallbackProfiles",
|
|
24568
25775
|
"favoriteModels",
|
|
24569
25776
|
"favoriteModelsOnly",
|
|
25777
|
+
"modelAvailabilitySchedule",
|
|
24570
25778
|
"fallbackAuto",
|
|
24571
25779
|
"models",
|
|
24572
25780
|
"modelMatrix",
|
|
@@ -24602,6 +25810,7 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
24602
25810
|
];
|
|
24603
25811
|
var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
24604
25812
|
"version",
|
|
25813
|
+
"activeProfile",
|
|
24605
25814
|
"provider",
|
|
24606
25815
|
"model",
|
|
24607
25816
|
"apiKey",
|
|
@@ -24618,6 +25827,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
24618
25827
|
"fallbackProfiles",
|
|
24619
25828
|
"favoriteModels",
|
|
24620
25829
|
"favoriteModelsOnly",
|
|
25830
|
+
"modelAvailabilitySchedule",
|
|
24621
25831
|
"fallbackAuto",
|
|
24622
25832
|
"hooks",
|
|
24623
25833
|
"plugins",
|
|
@@ -24739,9 +25949,9 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
24739
25949
|
}
|
|
24740
25950
|
const outSuperMemory = out["superMemory"];
|
|
24741
25951
|
if (outSuperMemory && typeof outSuperMemory === "object") {
|
|
24742
|
-
const
|
|
24743
|
-
if (
|
|
24744
|
-
const clonedStorage = { ...
|
|
25952
|
+
const storage3 = outSuperMemory["storage"];
|
|
25953
|
+
if (storage3 && typeof storage3 === "object" && "directory" in storage3) {
|
|
25954
|
+
const clonedStorage = { ...storage3 };
|
|
24745
25955
|
delete clonedStorage["directory"];
|
|
24746
25956
|
out["superMemory"] = {
|
|
24747
25957
|
...outSuperMemory,
|
|
@@ -24765,8 +25975,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
24765
25975
|
return out;
|
|
24766
25976
|
}
|
|
24767
25977
|
function samePath(a, b) {
|
|
24768
|
-
let ra =
|
|
24769
|
-
let rb =
|
|
25978
|
+
let ra = path26.resolve(a);
|
|
25979
|
+
let rb = path26.resolve(b);
|
|
24770
25980
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
24771
25981
|
ra = ra.toLowerCase();
|
|
24772
25982
|
rb = rb.toLowerCase();
|
|
@@ -24822,12 +26032,17 @@ var DefaultConfigLoader = class {
|
|
|
24822
26032
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
24823
26033
|
await this.ensureGlobalDefaults();
|
|
24824
26034
|
const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
|
|
24825
|
-
const [
|
|
26035
|
+
const [bootstrap, local, inProject] = await Promise.all([
|
|
24826
26036
|
this.readJson(this.paths.globalConfig),
|
|
24827
26037
|
this.readJson(this.paths.projectLocalConfig),
|
|
24828
26038
|
inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
|
|
24829
26039
|
]);
|
|
24830
|
-
cfg = deepMerge2(cfg,
|
|
26040
|
+
cfg = deepMerge2(cfg, bootstrap);
|
|
26041
|
+
const profileName = bootstrap.activeProfile ?? "default";
|
|
26042
|
+
const profileCfg = await this.readJson(this.paths.profileConfig(profileName));
|
|
26043
|
+
if (Object.keys(profileCfg).length > 0) {
|
|
26044
|
+
cfg = deepMerge2(cfg, profileCfg);
|
|
26045
|
+
}
|
|
24831
26046
|
cfg = deepMerge2(cfg, local);
|
|
24832
26047
|
cfg = deepMerge2(
|
|
24833
26048
|
cfg,
|
|
@@ -24853,10 +26068,11 @@ var DefaultConfigLoader = class {
|
|
|
24853
26068
|
cfg = deepMerge2(cfg, patch);
|
|
24854
26069
|
}
|
|
24855
26070
|
} catch (err) {
|
|
24856
|
-
this.logWarn(
|
|
24857
|
-
|
|
24858
|
-
|
|
24859
|
-
|
|
26071
|
+
this.logWarn("Config source load failed", {
|
|
26072
|
+
event: "config.source_load_failed",
|
|
26073
|
+
source: src.name,
|
|
26074
|
+
message: toErrorMessage(err)
|
|
26075
|
+
});
|
|
24860
26076
|
}
|
|
24861
26077
|
}
|
|
24862
26078
|
if (opts.cliFlags) {
|
|
@@ -24895,6 +26111,7 @@ var DefaultConfigLoader = class {
|
|
|
24895
26111
|
try {
|
|
24896
26112
|
await withFileLock(fp, async () => {
|
|
24897
26113
|
let parsed;
|
|
26114
|
+
let fileExisted = true;
|
|
24898
26115
|
try {
|
|
24899
26116
|
const raw = await fs13.readFile(fp, "utf8");
|
|
24900
26117
|
const result = safeParse(raw);
|
|
@@ -24915,25 +26132,115 @@ var DefaultConfigLoader = class {
|
|
|
24915
26132
|
durationMs: Date.now() - t0,
|
|
24916
26133
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24917
26134
|
});
|
|
24918
|
-
this.logWarn(
|
|
24919
|
-
|
|
24920
|
-
|
|
24921
|
-
|
|
26135
|
+
this.logWarn("Config defaults read failed", {
|
|
26136
|
+
event: "config.defaults_read_failed",
|
|
26137
|
+
path: fp,
|
|
26138
|
+
message: toErrorMessage(err)
|
|
26139
|
+
});
|
|
24922
26140
|
return;
|
|
24923
26141
|
}
|
|
26142
|
+
fileExisted = false;
|
|
24924
26143
|
parsed = {};
|
|
24925
26144
|
}
|
|
24926
|
-
const
|
|
24927
|
-
|
|
24928
|
-
|
|
24929
|
-
|
|
24930
|
-
|
|
24931
|
-
|
|
26145
|
+
const bootstrap = {
|
|
26146
|
+
version: 1,
|
|
26147
|
+
activeProfile: parsed.activeProfile ?? "default"
|
|
26148
|
+
};
|
|
26149
|
+
let needsBootstrapWrite = false;
|
|
26150
|
+
if (parsed.version !== 1) {
|
|
26151
|
+
needsBootstrapWrite = true;
|
|
26152
|
+
}
|
|
26153
|
+
if (parsed.activeProfile === void 0) {
|
|
26154
|
+
needsBootstrapWrite = true;
|
|
26155
|
+
}
|
|
26156
|
+
if (needsBootstrapWrite) {
|
|
26157
|
+
await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
|
|
26158
|
+
this.events?.emit("storage.write", {
|
|
26159
|
+
sessionId: "~config~",
|
|
26160
|
+
store: "config",
|
|
26161
|
+
filePath: fp,
|
|
26162
|
+
operation: "ensure_defaults",
|
|
26163
|
+
outcome: "success",
|
|
26164
|
+
durationMs: Date.now() - t0,
|
|
26165
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26166
|
+
});
|
|
26167
|
+
}
|
|
26168
|
+
const profileName = bootstrap.activeProfile ?? "default";
|
|
26169
|
+
const profileFp = this.paths.profileConfig(profileName);
|
|
26170
|
+
await this.ensureProfileConfig(profileFp, parsed, fileExisted);
|
|
26171
|
+
});
|
|
26172
|
+
} catch (err) {
|
|
26173
|
+
this.events?.emit("storage.error", {
|
|
26174
|
+
sessionId: "~config~",
|
|
26175
|
+
store: "config",
|
|
26176
|
+
filePath: fp,
|
|
26177
|
+
operation: "ensure_defaults",
|
|
26178
|
+
outcome: "failure",
|
|
26179
|
+
error: storageErrorString(err),
|
|
26180
|
+
recoverable: false,
|
|
26181
|
+
durationMs: Date.now() - t0,
|
|
26182
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26183
|
+
});
|
|
26184
|
+
this.logWarn("Config defaults write failed", {
|
|
26185
|
+
event: "config.defaults_write_failed",
|
|
26186
|
+
path: fp,
|
|
26187
|
+
message: toErrorMessage(err)
|
|
26188
|
+
});
|
|
26189
|
+
}
|
|
26190
|
+
}
|
|
26191
|
+
/**
|
|
26192
|
+
* Ensure a profile config file exists at the given path.
|
|
26193
|
+
* On first boot: migrate content from the old flat global config, or seed
|
|
26194
|
+
* with behavior defaults. Subsequent boots: fill any missing keys from
|
|
26195
|
+
* BEHAVIOR_DEFAULTS (keeping user settings intact).
|
|
26196
|
+
*/
|
|
26197
|
+
async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
|
|
26198
|
+
const t0 = Date.now();
|
|
26199
|
+
try {
|
|
26200
|
+
await withFileLock(profileFp, async () => {
|
|
26201
|
+
let parsed;
|
|
26202
|
+
let existed = true;
|
|
26203
|
+
try {
|
|
26204
|
+
const raw = await fs13.readFile(profileFp, "utf8");
|
|
26205
|
+
const result = safeParse(raw);
|
|
26206
|
+
if (!result.ok || !isPlainRecord(result.value)) {
|
|
26207
|
+
this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
|
|
26208
|
+
event: "config.profile_parse_failed",
|
|
26209
|
+
path: profileFp
|
|
26210
|
+
});
|
|
26211
|
+
parsed = {};
|
|
26212
|
+
} else {
|
|
26213
|
+
parsed = result.value;
|
|
26214
|
+
}
|
|
26215
|
+
} catch (err) {
|
|
26216
|
+
if (err.code !== "ENOENT") {
|
|
26217
|
+
this.logWarn("Profile config read failed", {
|
|
26218
|
+
event: "config.profile_read_failed",
|
|
26219
|
+
path: profileFp,
|
|
26220
|
+
message: toErrorMessage(err)
|
|
26221
|
+
});
|
|
26222
|
+
return;
|
|
26223
|
+
}
|
|
26224
|
+
existed = false;
|
|
26225
|
+
parsed = {};
|
|
26226
|
+
}
|
|
26227
|
+
let seed;
|
|
26228
|
+
if (!existed && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
|
|
26229
|
+
seed = { ...oldGlobalParsed };
|
|
26230
|
+
delete seed["activeProfile"];
|
|
26231
|
+
const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
|
|
26232
|
+
seed = filled.value;
|
|
26233
|
+
} else {
|
|
26234
|
+
const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
26235
|
+
if (!filled.changed) return;
|
|
26236
|
+
seed = filled.value;
|
|
26237
|
+
}
|
|
26238
|
+
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
24932
26239
|
this.events?.emit("storage.write", {
|
|
24933
26240
|
sessionId: "~config~",
|
|
24934
26241
|
store: "config",
|
|
24935
|
-
filePath:
|
|
24936
|
-
operation: "
|
|
26242
|
+
filePath: profileFp,
|
|
26243
|
+
operation: "ensure_profile_defaults",
|
|
24937
26244
|
outcome: "success",
|
|
24938
26245
|
durationMs: Date.now() - t0,
|
|
24939
26246
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
@@ -24943,18 +26250,19 @@ var DefaultConfigLoader = class {
|
|
|
24943
26250
|
this.events?.emit("storage.error", {
|
|
24944
26251
|
sessionId: "~config~",
|
|
24945
26252
|
store: "config",
|
|
24946
|
-
filePath:
|
|
24947
|
-
operation: "
|
|
26253
|
+
filePath: profileFp,
|
|
26254
|
+
operation: "ensure_profile_defaults",
|
|
24948
26255
|
outcome: "failure",
|
|
24949
26256
|
error: storageErrorString(err),
|
|
24950
26257
|
recoverable: false,
|
|
24951
26258
|
durationMs: Date.now() - t0,
|
|
24952
26259
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24953
26260
|
});
|
|
24954
|
-
this.logWarn(
|
|
24955
|
-
|
|
24956
|
-
|
|
24957
|
-
|
|
26261
|
+
this.logWarn("Profile config defaults write failed", {
|
|
26262
|
+
event: "config.profile_write_failed",
|
|
26263
|
+
path: profileFp,
|
|
26264
|
+
message: toErrorMessage(err)
|
|
26265
|
+
});
|
|
24958
26266
|
}
|
|
24959
26267
|
}
|
|
24960
26268
|
/**
|
|
@@ -25056,10 +26364,10 @@ var DefaultConfigLoader = class {
|
|
|
25056
26364
|
error: storageErrorString(err),
|
|
25057
26365
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25058
26366
|
});
|
|
25059
|
-
this.logWarn(
|
|
25060
|
-
|
|
25061
|
-
|
|
25062
|
-
);
|
|
26367
|
+
this.logWarn("Config sync load failed", {
|
|
26368
|
+
event: "config.sync_load_failed",
|
|
26369
|
+
message: toErrorMessage(err)
|
|
26370
|
+
});
|
|
25063
26371
|
return null;
|
|
25064
26372
|
}
|
|
25065
26373
|
}
|
|
@@ -25067,8 +26375,8 @@ var DefaultConfigLoader = class {
|
|
|
25067
26375
|
const t0 = Date.now();
|
|
25068
26376
|
let mtimeMs = null;
|
|
25069
26377
|
try {
|
|
25070
|
-
const
|
|
25071
|
-
mtimeMs =
|
|
26378
|
+
const stat13 = await fs13.stat(file);
|
|
26379
|
+
mtimeMs = stat13.mtimeMs;
|
|
25072
26380
|
const cached = this.jsonCache.get(file);
|
|
25073
26381
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
25074
26382
|
return structuredClone(cached.value);
|
|
@@ -25088,10 +26396,11 @@ var DefaultConfigLoader = class {
|
|
|
25088
26396
|
error: storageErrorString(err),
|
|
25089
26397
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25090
26398
|
});
|
|
25091
|
-
this.logWarn(
|
|
25092
|
-
|
|
25093
|
-
|
|
25094
|
-
|
|
26399
|
+
this.logWarn("Config read failed (stat)", {
|
|
26400
|
+
event: "config.read_failed",
|
|
26401
|
+
path: file,
|
|
26402
|
+
message: toErrorMessage(err)
|
|
26403
|
+
});
|
|
25095
26404
|
return {};
|
|
25096
26405
|
}
|
|
25097
26406
|
let raw;
|
|
@@ -25109,10 +26418,11 @@ var DefaultConfigLoader = class {
|
|
|
25109
26418
|
error: storageErrorString(err),
|
|
25110
26419
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25111
26420
|
});
|
|
25112
|
-
this.logWarn(
|
|
25113
|
-
|
|
25114
|
-
|
|
25115
|
-
|
|
26421
|
+
this.logWarn("Config read failed (read)", {
|
|
26422
|
+
event: "config.read_failed",
|
|
26423
|
+
path: file,
|
|
26424
|
+
message: toErrorMessage(err)
|
|
26425
|
+
});
|
|
25116
26426
|
}
|
|
25117
26427
|
this.jsonCache.set(file, { mtimeMs: null, value: {} });
|
|
25118
26428
|
return {};
|
|
@@ -25129,10 +26439,10 @@ var DefaultConfigLoader = class {
|
|
|
25129
26439
|
error: "parse error or empty file",
|
|
25130
26440
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25131
26441
|
});
|
|
25132
|
-
this.logWarn(
|
|
25133
|
-
|
|
25134
|
-
|
|
25135
|
-
);
|
|
26442
|
+
this.logWarn("Config parse failed \u2014 falling back to defaults", {
|
|
26443
|
+
event: "config.parse_failed",
|
|
26444
|
+
path: file
|
|
26445
|
+
});
|
|
25136
26446
|
return {};
|
|
25137
26447
|
}
|
|
25138
26448
|
this.jsonCache.set(file, { mtimeMs, value: structuredClone(parsed.value) });
|
|
@@ -25328,7 +26638,7 @@ function deepFreeze(obj) {
|
|
|
25328
26638
|
|
|
25329
26639
|
// src/storage/plan-store.ts
|
|
25330
26640
|
import * as fsp14 from "node:fs/promises";
|
|
25331
|
-
import { randomUUID as
|
|
26641
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
25332
26642
|
async function loadPlan(filePath, events) {
|
|
25333
26643
|
const t0 = Date.now();
|
|
25334
26644
|
let raw;
|
|
@@ -25426,7 +26736,7 @@ function emptyPlan(sessionId, title) {
|
|
|
25426
26736
|
function addPlanItem(plan, title, details) {
|
|
25427
26737
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25428
26738
|
const item = {
|
|
25429
|
-
id: `plan_${Date.now()}_${
|
|
26739
|
+
id: `plan_${Date.now()}_${randomUUID12().slice(0, 6)}`,
|
|
25430
26740
|
title,
|
|
25431
26741
|
details,
|
|
25432
26742
|
status: "open",
|
|
@@ -25498,7 +26808,7 @@ function deriveTodosFromPlanItem(plan, idOrIndex, subtasks) {
|
|
|
25498
26808
|
if (subtasks && subtasks.length > 0) {
|
|
25499
26809
|
for (const st of subtasks) {
|
|
25500
26810
|
todos.push({
|
|
25501
|
-
id: `todo_${Date.now()}_${
|
|
26811
|
+
id: `todo_${Date.now()}_${randomUUID12().slice(0, 6)}`,
|
|
25502
26812
|
content: st,
|
|
25503
26813
|
status: "pending",
|
|
25504
26814
|
promotedFromPlan: item.id
|
|
@@ -25626,7 +26936,7 @@ ${cat}:`);
|
|
|
25626
26936
|
|
|
25627
26937
|
// src/storage/queue-store.ts
|
|
25628
26938
|
import * as fsp15 from "node:fs/promises";
|
|
25629
|
-
import * as
|
|
26939
|
+
import * as path27 from "node:path";
|
|
25630
26940
|
var QueueStore = class {
|
|
25631
26941
|
file;
|
|
25632
26942
|
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
@@ -25635,7 +26945,7 @@ var QueueStore = class {
|
|
|
25635
26945
|
traceId;
|
|
25636
26946
|
logger;
|
|
25637
26947
|
constructor(opts) {
|
|
25638
|
-
this.file =
|
|
26948
|
+
this.file = path27.join(opts.dir, "queue.json");
|
|
25639
26949
|
this.events = opts.events;
|
|
25640
26950
|
this.traceId = opts.traceId;
|
|
25641
26951
|
this.logger = opts.logger;
|
|
@@ -25793,7 +27103,7 @@ function isPersistedQueueItem(v) {
|
|
|
25793
27103
|
// src/storage/recovery-lock.ts
|
|
25794
27104
|
import * as fsp16 from "node:fs/promises";
|
|
25795
27105
|
import * as os3 from "node:os";
|
|
25796
|
-
import * as
|
|
27106
|
+
import * as path28 from "node:path";
|
|
25797
27107
|
var LOCK_FILE = "active.json";
|
|
25798
27108
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
25799
27109
|
var RecoveryLock = class {
|
|
@@ -25804,7 +27114,7 @@ var RecoveryLock = class {
|
|
|
25804
27114
|
sessionStore;
|
|
25805
27115
|
probe;
|
|
25806
27116
|
constructor(opts) {
|
|
25807
|
-
this.file =
|
|
27117
|
+
this.file = path28.join(opts.dir, LOCK_FILE);
|
|
25808
27118
|
this.pid = opts.pid ?? process.pid;
|
|
25809
27119
|
this.hostname = opts.hostname ?? os3.hostname();
|
|
25810
27120
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -25865,7 +27175,7 @@ var RecoveryLock = class {
|
|
|
25865
27175
|
* null return before calling this.
|
|
25866
27176
|
*/
|
|
25867
27177
|
async write(sessionId) {
|
|
25868
|
-
await ensureDir(
|
|
27178
|
+
await ensureDir(path28.dirname(this.file));
|
|
25869
27179
|
const lock = {
|
|
25870
27180
|
v: 1,
|
|
25871
27181
|
sessionId,
|
|
@@ -26040,6 +27350,9 @@ var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
|
|
|
26040
27350
|
"user_input",
|
|
26041
27351
|
"llm_response",
|
|
26042
27352
|
"tool_result",
|
|
27353
|
+
"message_appended",
|
|
27354
|
+
"message_updated",
|
|
27355
|
+
"messages_replaced",
|
|
26043
27356
|
"context_snapshot",
|
|
26044
27357
|
"checkpoint",
|
|
26045
27358
|
"file_snapshot",
|
|
@@ -26060,11 +27373,22 @@ var STANDARD_AUDIT_EVENTS = /* @__PURE__ */ new Set([
|
|
|
26060
27373
|
"provider_retry",
|
|
26061
27374
|
"provider_error",
|
|
26062
27375
|
"skill_activated",
|
|
26063
|
-
"skill_deactivated"
|
|
26064
|
-
|
|
26065
|
-
|
|
26066
|
-
|
|
27376
|
+
"skill_deactivated",
|
|
27377
|
+
// Resume markers and fleet lifecycle. These reach disk today only because
|
|
27378
|
+
// every producer appends to the raw SessionWriter and bypasses this bridge —
|
|
27379
|
+
// routed through it while absent from both sets, `isAllowed` would drop them
|
|
27380
|
+
// at the DEFAULT level and the resume timeline would lose them silently.
|
|
27381
|
+
"mode_changed",
|
|
27382
|
+
"agent_spawned",
|
|
27383
|
+
"agent_stopped",
|
|
27384
|
+
"agent_error",
|
|
27385
|
+
"task_created",
|
|
27386
|
+
"task_updated",
|
|
27387
|
+
"task_completed",
|
|
27388
|
+
"task_failed",
|
|
27389
|
+
"side_effect"
|
|
26067
27390
|
]);
|
|
27391
|
+
var FULL_ONLY_EVENTS = /* @__PURE__ */ new Set(["tool_progress"]);
|
|
26068
27392
|
function isAllowed(type, level) {
|
|
26069
27393
|
if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;
|
|
26070
27394
|
if (level === "minimal") return false;
|
|
@@ -26119,9 +27443,7 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
|
26119
27443
|
async appendBatch(events) {
|
|
26120
27444
|
const target = resolveWriter();
|
|
26121
27445
|
if (!target || events.length === 0) return;
|
|
26122
|
-
const allowed = events.filter(
|
|
26123
|
-
(e) => isAllowed(e.type, currentLevel) && shouldSample(e)
|
|
26124
|
-
);
|
|
27446
|
+
const allowed = events.filter((e) => isAllowed(e.type, currentLevel) && shouldSample(e));
|
|
26125
27447
|
if (allowed.length === 0) return;
|
|
26126
27448
|
try {
|
|
26127
27449
|
await target.appendBatch(allowed);
|
|
@@ -26170,8 +27492,8 @@ var DefaultSessionReader = class _DefaultSessionReader {
|
|
|
26170
27492
|
const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
|
|
26171
27493
|
let mtimeMs = null;
|
|
26172
27494
|
try {
|
|
26173
|
-
const
|
|
26174
|
-
mtimeMs =
|
|
27495
|
+
const stat13 = await fs14.stat(sessionPath);
|
|
27496
|
+
mtimeMs = stat13.mtimeMs;
|
|
26175
27497
|
} catch {
|
|
26176
27498
|
this.eventCache.delete(sessionId);
|
|
26177
27499
|
this.eventCacheMtimes.delete(sessionId);
|