@wrongstack/core 0.285.0 → 0.287.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autophase/phase-orchestrator.d.ts.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/coordination/agent-monitor.d.ts +29 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -1
- package/dist/coordination/autonomous-brain.d.ts +0 -2
- package/dist/coordination/autonomous-brain.d.ts.map +1 -1
- package/dist/coordination/autonomous-coordinator.d.ts +4 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
- package/dist/coordination/brain-ledger.d.ts +142 -0
- package/dist/coordination/brain-ledger.d.ts.map +1 -0
- package/dist/coordination/brain-monitor.d.ts +46 -2
- package/dist/coordination/brain-monitor.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +43 -0
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/delegate-tool.d.ts +8 -14
- package/dist/coordination/delegate-tool.d.ts.map +1 -1
- package/dist/coordination/director/director-btw-notes.d.ts +20 -0
- package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
- package/dist/coordination/director/director-collab.d.ts +40 -0
- package/dist/coordination/director/director-collab.d.ts.map +1 -0
- package/dist/coordination/director.d.ts +28 -9
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-manager.d.ts +21 -0
- package/dist/coordination/fleet-manager.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +6 -7
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +96 -21
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/icoordinator.d.ts +1 -3
- package/dist/coordination/icoordinator.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2176 -806
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +13 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-actions.d.ts +12 -13
- package/dist/coordination/mailbox-actions.d.ts.map +1 -1
- package/dist/coordination/mailbox-constants.d.ts +64 -0
- package/dist/coordination/mailbox-constants.d.ts.map +1 -0
- package/dist/coordination/mailbox-events.d.ts +32 -0
- package/dist/coordination/mailbox-events.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +28 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
- package/dist/coordination/mailbox-types.d.ts +130 -5
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/mailbox.d.ts +21 -2
- package/dist/coordination/mailbox.d.ts.map +1 -1
- package/dist/coordination/package-author-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +10 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent.d.ts +8 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/context.d.ts +25 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/conversation-state.d.ts +6 -0
- package/dist/core/conversation-state.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +12 -5
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +14 -6
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.d.ts +0 -1
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +1690 -1998
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +8 -1
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/autonomous-runner.d.ts +7 -1
- package/dist/execution/autonomous-runner.d.ts.map +1 -1
- package/dist/execution/autonomy-brain.d.ts +72 -11
- package/dist/execution/autonomy-brain.d.ts.map +1 -1
- package/dist/execution/brain-chain.d.ts +53 -0
- package/dist/execution/brain-chain.d.ts.map +1 -0
- package/dist/execution/brain-evaluation.d.ts +66 -0
- package/dist/execution/brain-evaluation.d.ts.map +1 -0
- package/dist/execution/brain-runtime.d.ts +171 -0
- package/dist/execution/brain-runtime.d.ts.map +1 -0
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/council-brain.d.ts +79 -0
- package/dist/execution/council-brain.d.ts.map +1 -0
- package/dist/execution/council-orchestrator.d.ts +32 -0
- package/dist/execution/council-orchestrator.d.ts.map +1 -0
- package/dist/execution/council-personas.d.ts +19 -0
- package/dist/execution/council-personas.d.ts.map +1 -0
- package/dist/execution/council-profiles.d.ts +34 -0
- package/dist/execution/council-profiles.d.ts.map +1 -0
- package/dist/execution/council-prompts.d.ts +21 -0
- package/dist/execution/council-prompts.d.ts.map +1 -0
- package/dist/execution/council-resolution.d.ts +57 -0
- package/dist/execution/council-resolution.d.ts.map +1 -0
- package/dist/execution/design-kit-loader.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +35 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -0
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/eternal-autonomy.d.ts +12 -0
- package/dist/execution/eternal-autonomy.d.ts.map +1 -1
- package/dist/execution/index.d.ts +11 -1
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +3076 -526
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +18 -7
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts +52 -0
- package/dist/execution/one-shot-llm.d.ts.map +1 -0
- package/dist/execution/parallel-eternal-engine.d.ts +12 -0
- package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +23 -4
- package/dist/execution/prompt-enhancer.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.js +24 -6
- package/dist/execution/prompt-enhancer.js.map +2 -2
- package/dist/execution/selective-compactor.d.ts +4 -4
- package/dist/execution/strategy-compactor.d.ts +7 -0
- package/dist/execution/strategy-compactor.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts +19 -5
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hooks/registry.d.ts.map +1 -1
- package/dist/hooks/runner.d.ts.map +1 -1
- package/dist/hooks/shell-executor.d.ts.map +1 -1
- package/dist/hq/alerts.d.ts +5 -0
- package/dist/hq/alerts.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts +38 -10
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/commands.d.ts +8 -4
- package/dist/hq/commands.d.ts.map +1 -1
- package/dist/hq/cost-bridge.d.ts +5 -3
- package/dist/hq/cost-bridge.d.ts.map +1 -1
- package/dist/hq/factory.d.ts +3 -0
- package/dist/hq/factory.d.ts.map +1 -1
- package/dist/hq/fleet-bridge.d.ts.map +1 -1
- package/dist/hq/index.d.ts +0 -1
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +1016 -283
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/persistence.d.ts +42 -0
- package/dist/hq/persistence.d.ts.map +1 -1
- package/dist/hq/protocol/brain.d.ts +24 -0
- package/dist/hq/protocol/brain.d.ts.map +1 -0
- package/dist/hq/protocol/browser.d.ts +12 -0
- package/dist/hq/protocol/browser.d.ts.map +1 -0
- package/dist/hq/protocol/client.d.ts +69 -0
- package/dist/hq/protocol/client.d.ts.map +1 -0
- package/dist/hq/protocol/core.d.ts +130 -0
- package/dist/hq/protocol/core.d.ts.map +1 -0
- package/dist/hq/protocol/fleet.d.ts +73 -0
- package/dist/hq/protocol/fleet.d.ts.map +1 -0
- package/dist/hq/protocol/mailbox.d.ts +76 -0
- package/dist/hq/protocol/mailbox.d.ts.map +1 -0
- package/dist/hq/protocol/mcp.d.ts +51 -0
- package/dist/hq/protocol/mcp.d.ts.map +1 -0
- package/dist/hq/protocol/project.d.ts +26 -0
- package/dist/hq/protocol/project.d.ts.map +1 -0
- package/dist/hq/protocol/session.d.ts +153 -0
- package/dist/hq/protocol/session.d.ts.map +1 -0
- package/dist/hq/protocol/tool.d.ts +31 -0
- package/dist/hq/protocol/tool.d.ts.map +1 -0
- package/dist/hq/protocol.d.ts +10 -559
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/hq/publisher.d.ts +4 -0
- package/dist/hq/publisher.d.ts.map +1 -1
- package/dist/index.d.ts +24 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7676 -3432
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +13 -11
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/infrastructure/token-counter.d.ts.map +1 -1
- package/dist/kernel/events/agent-events.d.ts +283 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -0
- package/dist/kernel/events/brain-events.d.ts +176 -0
- package/dist/kernel/events/brain-events.d.ts.map +1 -0
- package/dist/kernel/events/fleet-events.d.ts +39 -0
- package/dist/kernel/events/fleet-events.d.ts.map +1 -0
- package/dist/kernel/events/memory-events.d.ts +165 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -0
- package/dist/kernel/events/provider-events.d.ts +98 -0
- package/dist/kernel/events/provider-events.d.ts.map +1 -0
- package/dist/kernel/events/sdd-events.d.ts +118 -0
- package/dist/kernel/events/sdd-events.d.ts.map +1 -0
- package/dist/kernel/events/session-events.d.ts +227 -0
- package/dist/kernel/events/session-events.d.ts.map +1 -0
- package/dist/kernel/events/tool-events.d.ts +185 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -0
- package/dist/kernel/events/worktree-events.d.ts +58 -0
- package/dist/kernel/events/worktree-events.d.ts.map +1 -0
- package/dist/kernel/events.d.ts +12 -1282
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js +22 -2
- package/dist/kernel/index.js.map +2 -2
- package/dist/kernel/run-controller.d.ts +15 -0
- package/dist/kernel/run-controller.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/models/index.js +19 -6
- package/dist/models/index.js.map +2 -2
- package/dist/models/llm-selector.d.ts +7 -0
- package/dist/models/llm-selector.d.ts.map +1 -1
- package/dist/observability/index.js +16 -15
- package/dist/observability/index.js.map +2 -2
- package/dist/plugins/auto-review-plugin.d.ts +36 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
- package/dist/plugins/chimera-plugin.d.ts +8 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +260 -23
- package/dist/security/index.js.map +4 -4
- package/dist/security/permission-policy-schema.d.ts +77 -0
- package/dist/security/permission-policy-schema.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +5 -0
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/security/secret-scrubber.d.ts.map +1 -1
- package/dist/security/secret-vault.d.ts +9 -0
- package/dist/security/secret-vault.d.ts.map +1 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/storage/config-loader.d.ts +10 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/goal-kanban.d.ts +69 -0
- package/dist/storage/goal-kanban.d.ts.map +1 -0
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/goal-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +572 -879
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +9 -1
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/plan-store.d.ts.map +1 -1
- package/dist/storage/queue-store.d.ts +4 -0
- package/dist/storage/queue-store.d.ts.map +1 -1
- package/dist/storage/session-store.d.ts +9 -0
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/task-store.d.ts.map +1 -1
- package/dist/storage/todos-checkpoint.d.ts +2 -2
- package/dist/storage/todos-checkpoint.d.ts.map +1 -1
- package/dist/tools/council-tool.d.ts +25 -0
- package/dist/tools/council-tool.d.ts.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1884 -12
- package/dist/tools/index.js.map +4 -4
- package/dist/tools/one-shot-llm-tool.d.ts +51 -0
- package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
- package/dist/types/config.d.ts +236 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/context-window.d.ts.map +1 -1
- package/dist/types/council.d.ts +134 -0
- package/dist/types/council.d.ts.map +1 -0
- package/dist/types/default-config.d.ts +3 -3
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +40 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/multi-agent.d.ts +8 -0
- package/dist/types/multi-agent.d.ts.map +1 -1
- package/dist/types/observability.d.ts +5 -0
- package/dist/types/observability.d.ts.map +1 -1
- package/dist/types/one-shot-llm.d.ts +128 -0
- package/dist/types/one-shot-llm.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +6 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/provider.d.ts +17 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/system-prompt.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +10 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/types/tool.d.ts +7 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/glob-expand.d.ts.map +1 -1
- package/dist/utils/incoming-images.d.ts +39 -0
- package/dist/utils/incoming-images.d.ts.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +156 -11
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/tool-wire-compact.d.ts +1 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +20 -4
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/dist/worktree/worktree-manager.d.ts +13 -4
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/instructions/agents/browser.md +25 -16
- package/instructions/cli/next-task-predictor.md +5 -3
- package/instructions/coordination/subagent-baseline.md +17 -2
- package/instructions/leader-after-task.md +19 -8
- package/instructions/llm/council-judge.md +19 -0
- package/instructions/llm/council-voter.md +22 -0
- package/instructions/modes/architect.md +14 -9
- package/instructions/modes/audit-lite.md +9 -7
- package/instructions/modes/brief-system.md +12 -12
- package/instructions/modes/brief.md +7 -5
- package/instructions/modes/code-auditor.md +13 -8
- package/instructions/modes/code-reviewer.md +13 -8
- package/instructions/modes/debug-lite.md +7 -6
- package/instructions/modes/debugger.md +13 -9
- package/instructions/modes/devops.md +13 -9
- package/instructions/modes/plan-lite.md +7 -6
- package/instructions/modes/refactor-lite.md +7 -6
- package/instructions/modes/refactorer.md +14 -9
- package/instructions/modes/research-lite.md +8 -6
- package/instructions/modes/research-web.md +18 -55
- package/instructions/modes/review-lite.md +7 -6
- package/instructions/modes/teach-system.md +15 -15
- package/instructions/modes/teach.md +13 -12
- package/instructions/modes/test-lite.md +6 -5
- package/instructions/modes/tester.md +13 -9
- package/instructions/modes/ui-design.md +14 -8
- package/instructions/sections/tool/commit-hygiene.md +1 -7
- package/instructions/sections/tool/common-patterns.md +7 -1
- package/instructions/sections/tool/delegation-full.md +3 -31
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +6 -22
- package/instructions/system.md +294 -8
- package/package.json +2 -2
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/mailbox-bridge/SKILL.md +16 -7
- package/skills/output-standards/SKILL.md +16 -16
- package/skills/wrongstack-mailbox/SKILL.md +57 -12
- package/dist/coordination/director-construction.d.ts +0 -90
- package/dist/coordination/director-construction.d.ts.map +0 -1
- package/dist/hq/agent-bridge.d.ts +0 -27
- package/dist/hq/agent-bridge.d.ts.map +0 -1
- package/dist/plugins/git-plugin.d.ts +0 -15
- package/dist/plugins/git-plugin.d.ts.map +0 -1
- package/dist/plugins/observability-plugin.d.ts +0 -19
- package/dist/plugins/observability-plugin.d.ts.map +0 -1
- package/dist/plugins/plan-plugin.d.ts +0 -16
- package/dist/plugins/plan-plugin.d.ts.map +0 -1
- package/dist/storage/memory-store.d.ts +0 -119
- package/dist/storage/memory-store.d.ts.map +0 -1
package/dist/storage/index.js
CHANGED
|
@@ -7,7 +7,8 @@ import { createInterface } from "node:readline";
|
|
|
7
7
|
|
|
8
8
|
// src/utils/atomic-write.ts
|
|
9
9
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
10
|
-
import * as
|
|
10
|
+
import * as fs2 from "node:fs/promises";
|
|
11
|
+
import { watch as watchDir } from "node:fs";
|
|
11
12
|
import * as path3 from "node:path";
|
|
12
13
|
|
|
13
14
|
// src/utils/child-env.ts
|
|
@@ -453,14 +454,34 @@ function ulid(seedTime = Date.now()) {
|
|
|
453
454
|
|
|
454
455
|
// src/utils/wstack-paths.ts
|
|
455
456
|
import { createHash } from "node:crypto";
|
|
457
|
+
import * as fs from "node:fs";
|
|
456
458
|
import * as os from "node:os";
|
|
457
459
|
import * as path2 from "node:path";
|
|
460
|
+
function canonicalProjectRoot(absRoot) {
|
|
461
|
+
const checkoutRoot = path2.resolve(absRoot);
|
|
462
|
+
const dotGit = path2.join(checkoutRoot, ".git");
|
|
463
|
+
try {
|
|
464
|
+
if (!fs.statSync(dotGit).isFile()) return checkoutRoot;
|
|
465
|
+
const gitDirLine = fs.readFileSync(dotGit, "utf8").trim();
|
|
466
|
+
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
467
|
+
if (!match?.[1]) return checkoutRoot;
|
|
468
|
+
const gitDir = path2.resolve(checkoutRoot, match[1].trim());
|
|
469
|
+
const commonDirFile = path2.join(gitDir, "commondir");
|
|
470
|
+
if (!fs.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
471
|
+
const commonDir = path2.resolve(gitDir, fs.readFileSync(commonDirFile, "utf8").trim());
|
|
472
|
+
if (path2.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
473
|
+
return path2.dirname(commonDir);
|
|
474
|
+
} catch {
|
|
475
|
+
return checkoutRoot;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
458
478
|
function projectHash(absRoot) {
|
|
459
|
-
return createHash("sha256").update(
|
|
479
|
+
return createHash("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
|
|
460
480
|
}
|
|
461
481
|
function projectSlug(absRoot) {
|
|
462
|
-
const
|
|
463
|
-
const
|
|
482
|
+
const identityRoot = canonicalProjectRoot(absRoot);
|
|
483
|
+
const base = slugify2(path2.basename(identityRoot));
|
|
484
|
+
const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
464
485
|
return `${base}-${hash}`;
|
|
465
486
|
}
|
|
466
487
|
function slugify2(name) {
|
|
@@ -661,16 +682,16 @@ var FsError = class extends WrongStackError {
|
|
|
661
682
|
// src/utils/atomic-write.ts
|
|
662
683
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
663
684
|
const dir = path3.dirname(targetPath);
|
|
664
|
-
await
|
|
685
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
665
686
|
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
666
687
|
try {
|
|
667
688
|
if (typeof content === "string") {
|
|
668
|
-
await
|
|
689
|
+
await fs2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
669
690
|
} else {
|
|
670
|
-
await
|
|
691
|
+
await fs2.writeFile(tmp, content, { flag: "wx" });
|
|
671
692
|
}
|
|
672
693
|
try {
|
|
673
|
-
const fh = await
|
|
694
|
+
const fh = await fs2.open(tmp, "r+");
|
|
674
695
|
try {
|
|
675
696
|
await fh.sync();
|
|
676
697
|
} finally {
|
|
@@ -680,35 +701,35 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
680
701
|
}
|
|
681
702
|
let mode;
|
|
682
703
|
try {
|
|
683
|
-
const stat13 = await
|
|
704
|
+
const stat13 = await fs2.stat(targetPath);
|
|
684
705
|
mode = stat13.mode & 511;
|
|
685
706
|
} catch {
|
|
686
707
|
mode = opts.mode;
|
|
687
708
|
}
|
|
688
709
|
if (mode !== void 0) {
|
|
689
|
-
await
|
|
710
|
+
await fs2.chmod(tmp, mode);
|
|
690
711
|
}
|
|
691
712
|
await renameWithRetry(tmp, targetPath);
|
|
692
713
|
if (mode !== void 0 && process.platform === "win32") {
|
|
693
714
|
try {
|
|
694
|
-
await
|
|
715
|
+
await fs2.chmod(targetPath, mode);
|
|
695
716
|
} catch {
|
|
696
717
|
}
|
|
697
718
|
}
|
|
698
719
|
} catch (err) {
|
|
699
720
|
try {
|
|
700
|
-
await
|
|
721
|
+
await fs2.unlink(tmp);
|
|
701
722
|
} catch {
|
|
702
723
|
}
|
|
703
724
|
throw err;
|
|
704
725
|
}
|
|
705
726
|
}
|
|
706
727
|
async function ensureDir(dir) {
|
|
707
|
-
await
|
|
728
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
708
729
|
}
|
|
709
730
|
async function withFileLock(targetPath, fn, opts = {}) {
|
|
710
731
|
const dir = path3.dirname(targetPath);
|
|
711
|
-
await
|
|
732
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
712
733
|
const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
|
|
713
734
|
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
714
735
|
const staleMs = opts.staleMs ?? 3e4;
|
|
@@ -716,26 +737,27 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
716
737
|
let handle;
|
|
717
738
|
for (; ; ) {
|
|
718
739
|
try {
|
|
719
|
-
handle = await
|
|
740
|
+
handle = await fs2.open(lockPath, "wx");
|
|
720
741
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
721
742
|
break;
|
|
722
743
|
} catch (err) {
|
|
723
744
|
const code = err.code;
|
|
724
745
|
if (code === "ENOENT") {
|
|
725
|
-
await
|
|
746
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
726
747
|
continue;
|
|
727
748
|
}
|
|
728
|
-
if (code !== "EEXIST") throw err;
|
|
749
|
+
if (code !== "EEXIST" && code !== "EPERM") throw err;
|
|
729
750
|
try {
|
|
730
|
-
const stat13 = await
|
|
751
|
+
const stat13 = await fs2.stat(lockPath);
|
|
731
752
|
if (Date.now() - stat13.mtimeMs > staleMs) {
|
|
732
|
-
await
|
|
753
|
+
await fs2.unlink(lockPath);
|
|
733
754
|
continue;
|
|
734
755
|
}
|
|
735
756
|
} catch {
|
|
736
757
|
continue;
|
|
737
758
|
}
|
|
738
|
-
|
|
759
|
+
const elapsed = Date.now() - started;
|
|
760
|
+
if (elapsed >= timeoutMs) {
|
|
739
761
|
throw new FsError({
|
|
740
762
|
message: `Timed out waiting for file lock: ${targetPath}`,
|
|
741
763
|
code: "FS_ATOMIC_WRITE_FAILED",
|
|
@@ -743,7 +765,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
743
765
|
context: { timeoutMs }
|
|
744
766
|
});
|
|
745
767
|
}
|
|
746
|
-
await
|
|
768
|
+
await waitForLockRelease(lockPath, timeoutMs - elapsed);
|
|
747
769
|
}
|
|
748
770
|
}
|
|
749
771
|
try {
|
|
@@ -754,22 +776,66 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
754
776
|
} catch {
|
|
755
777
|
}
|
|
756
778
|
try {
|
|
757
|
-
await
|
|
779
|
+
await fs2.unlink(lockPath);
|
|
758
780
|
} catch {
|
|
759
781
|
}
|
|
760
782
|
}
|
|
761
783
|
}
|
|
784
|
+
async function waitForLockRelease(lockPath, remainingMs) {
|
|
785
|
+
const parentDir = path3.dirname(lockPath);
|
|
786
|
+
const lockName = path3.basename(lockPath);
|
|
787
|
+
const intervalMs = Math.min(remainingMs, 100);
|
|
788
|
+
return new Promise((resolve11) => {
|
|
789
|
+
let settled = false;
|
|
790
|
+
let watcher = null;
|
|
791
|
+
const timer = setTimeout(() => {
|
|
792
|
+
settled = true;
|
|
793
|
+
watcher?.close();
|
|
794
|
+
resolve11();
|
|
795
|
+
}, intervalMs);
|
|
796
|
+
try {
|
|
797
|
+
watcher = watchDir(parentDir, (eventType, filename) => {
|
|
798
|
+
if (settled) return;
|
|
799
|
+
if (filename === lockName && (eventType === "rename" || eventType === "change")) {
|
|
800
|
+
settled = true;
|
|
801
|
+
clearTimeout(timer);
|
|
802
|
+
watcher?.close();
|
|
803
|
+
resolve11();
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
} catch {
|
|
807
|
+
clearTimeout(timer);
|
|
808
|
+
if (!settled) {
|
|
809
|
+
settled = true;
|
|
810
|
+
setTimeout(resolve11, Math.min(remainingMs, 25));
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
fs2.access(lockPath).then(
|
|
815
|
+
() => {
|
|
816
|
+
},
|
|
817
|
+
() => {
|
|
818
|
+
if (!settled) {
|
|
819
|
+
settled = true;
|
|
820
|
+
clearTimeout(timer);
|
|
821
|
+
watcher?.close();
|
|
822
|
+
resolve11();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
762
828
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
763
829
|
async function renameWithRetry(from, to) {
|
|
764
830
|
if (process.platform !== "win32") {
|
|
765
|
-
await
|
|
831
|
+
await fs2.rename(from, to);
|
|
766
832
|
return;
|
|
767
833
|
}
|
|
768
834
|
const delays = [10, 25, 60, 120, 250];
|
|
769
835
|
let lastErr;
|
|
770
836
|
for (let i = 0; i <= delays.length; i++) {
|
|
771
837
|
try {
|
|
772
|
-
await
|
|
838
|
+
await fs2.rename(from, to);
|
|
773
839
|
return;
|
|
774
840
|
} catch (err) {
|
|
775
841
|
lastErr = err;
|
|
@@ -2137,6 +2203,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2137
2203
|
projectRoot;
|
|
2138
2204
|
checkpointCas;
|
|
2139
2205
|
isSessionInUse;
|
|
2206
|
+
logger;
|
|
2140
2207
|
/**
|
|
2141
2208
|
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
2142
2209
|
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
@@ -2162,6 +2229,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2162
2229
|
this.events = opts.events;
|
|
2163
2230
|
this.secretScrubber = opts.secretScrubber;
|
|
2164
2231
|
this.isSessionInUse = opts.isSessionInUse;
|
|
2232
|
+
this.logger = opts.logger;
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Emit a structured warning. Uses the configured Logger when available;
|
|
2236
|
+
* falls back to console.warn(JSON) so warnings are never silently dropped.
|
|
2237
|
+
*/
|
|
2238
|
+
logWarn(msg, ctx) {
|
|
2239
|
+
if (this.logger) {
|
|
2240
|
+
this.logger.warn(msg, ctx);
|
|
2241
|
+
} else {
|
|
2242
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
2243
|
+
}
|
|
2165
2244
|
}
|
|
2166
2245
|
/**
|
|
2167
2246
|
* Clear the load() cache. Useful for testing or when the caller knows
|
|
@@ -2263,12 +2342,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2263
2342
|
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
2264
2343
|
return writer;
|
|
2265
2344
|
} catch (err) {
|
|
2266
|
-
await handle.close().catch((e) =>
|
|
2267
|
-
|
|
2268
|
-
event: "session_store.handle_close_failed",
|
|
2269
|
-
|
|
2270
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2271
|
-
})));
|
|
2345
|
+
await handle.close().catch((e) => this.logWarn(
|
|
2346
|
+
"Session handle close failed",
|
|
2347
|
+
{ event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
|
|
2348
|
+
));
|
|
2272
2349
|
this.emitError(id, file, "create", toErrorMessage(err), true);
|
|
2273
2350
|
throw err;
|
|
2274
2351
|
}
|
|
@@ -2399,12 +2476,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2399
2476
|
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
2400
2477
|
return { writer, data: resumedData };
|
|
2401
2478
|
} catch (err) {
|
|
2402
|
-
await handle.close().catch((e) =>
|
|
2403
|
-
|
|
2404
|
-
event: "session_store.handle_close_failed",
|
|
2405
|
-
|
|
2406
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2407
|
-
})));
|
|
2479
|
+
await handle.close().catch((e) => this.logWarn(
|
|
2480
|
+
"Session handle close failed",
|
|
2481
|
+
{ event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
|
|
2482
|
+
));
|
|
2408
2483
|
this.emitError(id, file, "resume", toErrorMessage(err), true);
|
|
2409
2484
|
throw err;
|
|
2410
2485
|
}
|
|
@@ -3025,13 +3100,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3025
3100
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
3026
3101
|
const msg = toErrorMessage(err);
|
|
3027
3102
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
event: "session_store.manifest_write_failed",
|
|
3031
|
-
|
|
3032
|
-
message: msg,
|
|
3033
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3034
|
-
}));
|
|
3103
|
+
this.logWarn(
|
|
3104
|
+
"Session manifest write failed",
|
|
3105
|
+
{ event: "session_store.manifest_write_failed", sessionId: id, message: msg }
|
|
3106
|
+
);
|
|
3035
3107
|
});
|
|
3036
3108
|
outcome = "failure";
|
|
3037
3109
|
errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
|
|
@@ -3393,10 +3465,19 @@ var QueueStore = class {
|
|
|
3393
3465
|
// reject assigning an optional constructor parameter to these fields.
|
|
3394
3466
|
events;
|
|
3395
3467
|
traceId;
|
|
3468
|
+
logger;
|
|
3396
3469
|
constructor(opts) {
|
|
3397
3470
|
this.file = path8.join(opts.dir, "queue.json");
|
|
3398
3471
|
this.events = opts.events;
|
|
3399
3472
|
this.traceId = opts.traceId;
|
|
3473
|
+
this.logger = opts.logger;
|
|
3474
|
+
}
|
|
3475
|
+
logWarn(msg, ctx) {
|
|
3476
|
+
if (this.logger) {
|
|
3477
|
+
this.logger.warn(msg, ctx);
|
|
3478
|
+
} else {
|
|
3479
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
3480
|
+
}
|
|
3400
3481
|
}
|
|
3401
3482
|
async write(items) {
|
|
3402
3483
|
const t0 = Date.now();
|
|
@@ -3426,13 +3507,7 @@ var QueueStore = class {
|
|
|
3426
3507
|
recoverable: false,
|
|
3427
3508
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
3428
3509
|
});
|
|
3429
|
-
|
|
3430
|
-
level: "warn",
|
|
3431
|
-
event: "queue_store.write_failed",
|
|
3432
|
-
path: this.file,
|
|
3433
|
-
message: toErrorMessage(err),
|
|
3434
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3435
|
-
}));
|
|
3510
|
+
this.logWarn("Queue store write failed", { event: "queue_store.write_failed", path: this.file, message: toErrorMessage(err) });
|
|
3436
3511
|
}
|
|
3437
3512
|
}
|
|
3438
3513
|
async read() {
|
|
@@ -3464,13 +3539,7 @@ var QueueStore = class {
|
|
|
3464
3539
|
recoverable: true,
|
|
3465
3540
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
3466
3541
|
});
|
|
3467
|
-
|
|
3468
|
-
level: "warn",
|
|
3469
|
-
event: "queue_store.read_failed",
|
|
3470
|
-
path: this.file,
|
|
3471
|
-
message: toErrorMessage(err),
|
|
3472
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3473
|
-
}));
|
|
3542
|
+
this.logWarn("Queue store read failed", { event: "queue_store.read_failed", path: this.file, message: toErrorMessage(err) });
|
|
3474
3543
|
return [];
|
|
3475
3544
|
}
|
|
3476
3545
|
let parsed;
|
|
@@ -3543,13 +3612,7 @@ var QueueStore = class {
|
|
|
3543
3612
|
recoverable: true,
|
|
3544
3613
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
3545
3614
|
});
|
|
3546
|
-
|
|
3547
|
-
level: "warn",
|
|
3548
|
-
event: "queue_store.clear_failed",
|
|
3549
|
-
path: this.file,
|
|
3550
|
-
message: err.message,
|
|
3551
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3552
|
-
}));
|
|
3615
|
+
this.logWarn("Queue store clear failed", { event: "queue_store.clear_failed", path: this.file, message: err.message });
|
|
3553
3616
|
}
|
|
3554
3617
|
}
|
|
3555
3618
|
};
|
|
@@ -3703,7 +3766,7 @@ function mergeAdjacentText(blocks) {
|
|
|
3703
3766
|
|
|
3704
3767
|
// src/storage/memory-backend.ts
|
|
3705
3768
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3706
|
-
import * as
|
|
3769
|
+
import * as fs3 from "node:fs/promises";
|
|
3707
3770
|
import * as path10 from "node:path";
|
|
3708
3771
|
|
|
3709
3772
|
// src/types/memory.ts
|
|
@@ -3871,7 +3934,7 @@ var FileMemoryBackend = class {
|
|
|
3871
3934
|
}
|
|
3872
3935
|
async getMtime(file) {
|
|
3873
3936
|
try {
|
|
3874
|
-
const stat13 = await
|
|
3937
|
+
const stat13 = await fs3.stat(file);
|
|
3875
3938
|
return stat13.mtimeMs;
|
|
3876
3939
|
} catch {
|
|
3877
3940
|
return 0;
|
|
@@ -3921,7 +3984,7 @@ var FileMemoryBackend = class {
|
|
|
3921
3984
|
await ensureDir(path10.dirname(file));
|
|
3922
3985
|
let existing = "";
|
|
3923
3986
|
try {
|
|
3924
|
-
existing = await
|
|
3987
|
+
existing = await fs3.readFile(file, "utf8");
|
|
3925
3988
|
} catch {
|
|
3926
3989
|
}
|
|
3927
3990
|
const id = `mem_${Date.now()}_${randomUUID2().slice(0, 8)}`;
|
|
@@ -3939,7 +4002,7 @@ ${line}`;
|
|
|
3939
4002
|
return withFileLock(file, async () => {
|
|
3940
4003
|
let existing;
|
|
3941
4004
|
try {
|
|
3942
|
-
existing = await
|
|
4005
|
+
existing = await fs3.readFile(file, "utf8");
|
|
3943
4006
|
} catch {
|
|
3944
4007
|
return 0;
|
|
3945
4008
|
}
|
|
@@ -3976,7 +4039,7 @@ ${line}`;
|
|
|
3976
4039
|
async readAll(scope, filePath) {
|
|
3977
4040
|
const file = this.resolveFile(filePath, scope);
|
|
3978
4041
|
try {
|
|
3979
|
-
return await
|
|
4042
|
+
return await fs3.readFile(file, "utf8");
|
|
3980
4043
|
} catch {
|
|
3981
4044
|
return "";
|
|
3982
4045
|
}
|
|
@@ -4000,7 +4063,7 @@ ${line}`;
|
|
|
4000
4063
|
const file = this.resolveFile(filePath, scope);
|
|
4001
4064
|
let existing;
|
|
4002
4065
|
try {
|
|
4003
|
-
existing = await
|
|
4066
|
+
existing = await fs3.readFile(file, "utf8");
|
|
4004
4067
|
} catch {
|
|
4005
4068
|
return 0;
|
|
4006
4069
|
}
|
|
@@ -4020,7 +4083,7 @@ ${line}`;
|
|
|
4020
4083
|
const next = lines.join("\n");
|
|
4021
4084
|
const backup = `${file}.bak.${Date.now()}`;
|
|
4022
4085
|
try {
|
|
4023
|
-
await
|
|
4086
|
+
await fs3.copyFile(file, backup);
|
|
4024
4087
|
await pruneConsolidateBackups(file);
|
|
4025
4088
|
} catch {
|
|
4026
4089
|
}
|
|
@@ -4037,11 +4100,11 @@ async function pruneConsolidateBackups(file) {
|
|
|
4037
4100
|
const dir = path10.dirname(file);
|
|
4038
4101
|
const base = path10.basename(file);
|
|
4039
4102
|
const prefix = `${base}.bak.`;
|
|
4040
|
-
const backups = (await
|
|
4103
|
+
const backups = (await fs3.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
|
|
4041
4104
|
await Promise.all(
|
|
4042
4105
|
backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {
|
|
4043
4106
|
try {
|
|
4044
|
-
await
|
|
4107
|
+
await fs3.unlink(path10.join(dir, name));
|
|
4045
4108
|
} catch {
|
|
4046
4109
|
}
|
|
4047
4110
|
})
|
|
@@ -4056,582 +4119,8 @@ function parseEntries(raw, scope = "project-memory") {
|
|
|
4056
4119
|
return entries.reverse();
|
|
4057
4120
|
}
|
|
4058
4121
|
|
|
4059
|
-
// src/storage/memory-store.ts
|
|
4060
|
-
var MAX_BYTES_TOTAL = 32e3;
|
|
4061
|
-
var DefaultMemoryStore = class {
|
|
4062
|
-
files;
|
|
4063
|
-
events;
|
|
4064
|
-
traceId;
|
|
4065
|
-
backend;
|
|
4066
|
-
/**
|
|
4067
|
-
* Per-scope serialization queue. `remember` / `forget` / `consolidate` /
|
|
4068
|
-
* `clear` are read-modify-write against a single file; without a lock,
|
|
4069
|
-
* two concurrent calls on the same scope can read the same baseline and
|
|
4070
|
-
* the later write silently drops the earlier entry. We chain each
|
|
4071
|
-
* mutation onto the prior promise for the same scope so they run in
|
|
4072
|
-
* issue order. Different scopes still proceed in parallel.
|
|
4073
|
-
*
|
|
4074
|
-
* The chain tracks only the last pending write. If a write fails, its
|
|
4075
|
-
* error is caught and swallowed so the chain stays alive for subsequent
|
|
4076
|
-
* calls. The error is stored in `writeErrors` so callers can learn about
|
|
4077
|
-
* it on the next read operation.
|
|
4078
|
-
*/
|
|
4079
|
-
writeChain = /* @__PURE__ */ new Map();
|
|
4080
|
-
/** Last write error per scope — surfaced as warnings on the next readAll(). */
|
|
4081
|
-
writeErrors = /* @__PURE__ */ new Map();
|
|
4082
|
-
/**
|
|
4083
|
-
* When the global root is a temporary directory (opencode, CI sandboxes),
|
|
4084
|
-
* memory files are also mirrored to the project tree so they survive
|
|
4085
|
-
* session cleanup. The primary path stays in the temp root for isolation;
|
|
4086
|
-
* this backup ensures memory is never lost.
|
|
4087
|
-
*/
|
|
4088
|
-
persistBackup;
|
|
4089
|
-
backupDir;
|
|
4090
|
-
/**
|
|
4091
|
-
* Per-scope tracked byte sizes — incremented on `remember()`, decremented on
|
|
4092
|
-
* `forget()`, recalculated after `consolidate()`. Eliminates the redundant
|
|
4093
|
-
* readAll() call that previously checked the file size after every write.
|
|
4094
|
-
*/
|
|
4095
|
-
_trackedByteSizes = {};
|
|
4096
|
-
/** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
|
|
4097
|
-
_scoreCache = /* @__PURE__ */ new Map();
|
|
4098
|
-
/**
|
|
4099
|
-
* Per-entry cached lowercase strings — lazily allocated once and reused
|
|
4100
|
-
* across scoreRelevant() calls so repeated scoring of the same entries skips
|
|
4101
|
-
* re-lowercasing. Keyed by entry object identity; cleared (set to null) on
|
|
4102
|
-
* every mutation (remember/forget/consolidate/clear) via invalidateScoreCaches().
|
|
4103
|
-
*/
|
|
4104
|
-
_cachedLower = null;
|
|
4105
|
-
constructor(opts) {
|
|
4106
|
-
this.files = {
|
|
4107
|
-
"project-agents": opts.paths.inProjectAgentsFile,
|
|
4108
|
-
"project-memory": opts.paths.projectMemory,
|
|
4109
|
-
"user-memory": opts.paths.globalMemory
|
|
4110
|
-
};
|
|
4111
|
-
this.events = opts.events;
|
|
4112
|
-
this.backend = opts.backend ?? new FileMemoryBackend({ paths: opts.paths });
|
|
4113
|
-
const root = opts.paths.globalRoot.toLowerCase();
|
|
4114
|
-
this.persistBackup = /[/\\](tmp|temp|cache)[/\\]/.test(root);
|
|
4115
|
-
this.backupDir = this.persistBackup ? opts.paths.inProjectAgentsFile.replace(/AGENTS\.md$/, "memory-persist") : "";
|
|
4116
|
-
}
|
|
4117
|
-
/** Expose the backend for plugin introspection and advanced queries. */
|
|
4118
|
-
getBackend() {
|
|
4119
|
-
return this.backend;
|
|
4120
|
-
}
|
|
4121
|
-
async runSerialized(scope, work) {
|
|
4122
|
-
const prior = this.writeChain.get(scope) ?? Promise.resolve();
|
|
4123
|
-
const next = prior.catch((err) => {
|
|
4124
|
-
this.writeErrors.set(scope, err);
|
|
4125
|
-
}).then(() => work());
|
|
4126
|
-
this.writeChain.set(scope, next);
|
|
4127
|
-
try {
|
|
4128
|
-
return await next;
|
|
4129
|
-
} catch (err) {
|
|
4130
|
-
this.writeErrors.set(scope, err);
|
|
4131
|
-
throw err;
|
|
4132
|
-
} finally {
|
|
4133
|
-
if (this.writeChain.get(scope) === next) {
|
|
4134
|
-
this.writeChain.delete(scope);
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
}
|
|
4138
|
-
/**
|
|
4139
|
-
* Recalculate the tracked byte size for a scope by re-reading the file and
|
|
4140
|
-
* summing the serialized byte length of each line. Called after consolidate()
|
|
4141
|
-
* (which modifies the file) to keep the tracker accurate.
|
|
4142
|
-
*/
|
|
4143
|
-
async _recalcTrackedByteSize(scope) {
|
|
4144
|
-
const raw = await this.backend.readAll(scope, this.files[scope]);
|
|
4145
|
-
let total = 0;
|
|
4146
|
-
for (const line of raw.split("\n")) {
|
|
4147
|
-
if (line.trim()) total += Buffer.byteLength(line, "utf8");
|
|
4148
|
-
}
|
|
4149
|
-
this._trackedByteSizes[scope] = total;
|
|
4150
|
-
return total;
|
|
4151
|
-
}
|
|
4152
|
-
async readAll() {
|
|
4153
|
-
const parts = [];
|
|
4154
|
-
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
4155
|
-
const writeErr = this.writeErrors.get(scope);
|
|
4156
|
-
if (writeErr) {
|
|
4157
|
-
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
|
|
4158
|
-
}
|
|
4159
|
-
const t0 = Date.now();
|
|
4160
|
-
const filePath = this.files[scope];
|
|
4161
|
-
try {
|
|
4162
|
-
const body = await this.backend.readAll(scope, filePath);
|
|
4163
|
-
const dur = Date.now() - t0;
|
|
4164
|
-
this.events?.emit("storage.read", {
|
|
4165
|
-
sessionId: "~memory~",
|
|
4166
|
-
store: "memory",
|
|
4167
|
-
filePath,
|
|
4168
|
-
operation: "readAll",
|
|
4169
|
-
outcome: "success",
|
|
4170
|
-
durationMs: dur,
|
|
4171
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4172
|
-
});
|
|
4173
|
-
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
4174
|
-
|
|
4175
|
-
${body.trim()}`);
|
|
4176
|
-
} catch (err) {
|
|
4177
|
-
const dur = Date.now() - t0;
|
|
4178
|
-
this.events?.emit("storage.read", {
|
|
4179
|
-
sessionId: "~memory~",
|
|
4180
|
-
store: "memory",
|
|
4181
|
-
filePath,
|
|
4182
|
-
operation: "readAll",
|
|
4183
|
-
outcome: "failure",
|
|
4184
|
-
durationMs: dur,
|
|
4185
|
-
error: toErrorMessage(err),
|
|
4186
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4187
|
-
});
|
|
4188
|
-
throw err;
|
|
4189
|
-
}
|
|
4190
|
-
}
|
|
4191
|
-
return parts.join("\n\n");
|
|
4192
|
-
}
|
|
4193
|
-
async read(scope) {
|
|
4194
|
-
const t0 = Date.now();
|
|
4195
|
-
const filePath = this.files[scope];
|
|
4196
|
-
try {
|
|
4197
|
-
const body = await this.backend.readAll(scope, filePath);
|
|
4198
|
-
const dur = Date.now() - t0;
|
|
4199
|
-
this.events?.emit("storage.read", {
|
|
4200
|
-
sessionId: "~memory~",
|
|
4201
|
-
store: "memory",
|
|
4202
|
-
filePath,
|
|
4203
|
-
operation: "read",
|
|
4204
|
-
outcome: "success",
|
|
4205
|
-
durationMs: dur,
|
|
4206
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4207
|
-
});
|
|
4208
|
-
return body;
|
|
4209
|
-
} catch (err) {
|
|
4210
|
-
const dur = Date.now() - t0;
|
|
4211
|
-
this.events?.emit("storage.read", {
|
|
4212
|
-
sessionId: "~memory~",
|
|
4213
|
-
store: "memory",
|
|
4214
|
-
filePath,
|
|
4215
|
-
operation: "read",
|
|
4216
|
-
outcome: "failure",
|
|
4217
|
-
durationMs: dur,
|
|
4218
|
-
error: toErrorMessage(err),
|
|
4219
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4220
|
-
});
|
|
4221
|
-
throw err;
|
|
4222
|
-
}
|
|
4223
|
-
}
|
|
4224
|
-
/**
|
|
4225
|
-
* List entries from a scope, newest first. Delegates to the backend
|
|
4226
|
-
* so graph/semantic backends can return enriched or filtered results.
|
|
4227
|
-
*/
|
|
4228
|
-
async list(scope = "project-memory", limit) {
|
|
4229
|
-
return this.backend.list(scope, this.files[scope], limit);
|
|
4230
|
-
}
|
|
4231
|
-
/**
|
|
4232
|
-
* Find memories related to the given text via graph traversal.
|
|
4233
|
-
* Falls back to content search when no graph backend is available.
|
|
4234
|
-
*/
|
|
4235
|
-
async findRelated(text, scope = "project-memory", limit = 5) {
|
|
4236
|
-
if (this.backend.findRelated) {
|
|
4237
|
-
return this.backend.findRelated(scope, this.files[scope], text, limit);
|
|
4238
|
-
}
|
|
4239
|
-
return this.search(text, scope, limit);
|
|
4240
|
-
}
|
|
4241
|
-
async search(query, scope = "project-memory", limit) {
|
|
4242
|
-
return this.backend.search(scope, query, this.files[scope], limit);
|
|
4243
|
-
}
|
|
4244
|
-
async remember(text, scope = "project-memory", metadata) {
|
|
4245
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
4246
|
-
return this.runSerialized(scope, async () => {
|
|
4247
|
-
const entry = { scope, text, ts, ...metadata };
|
|
4248
|
-
const filePath = this.files[scope];
|
|
4249
|
-
const t0 = Date.now();
|
|
4250
|
-
try {
|
|
4251
|
-
await this.backend.remember(scope, entry, filePath);
|
|
4252
|
-
this.invalidateScoreCaches();
|
|
4253
|
-
const dur = Date.now() - t0;
|
|
4254
|
-
this.events?.emit("storage.write", {
|
|
4255
|
-
sessionId: "~memory~",
|
|
4256
|
-
store: "memory",
|
|
4257
|
-
filePath,
|
|
4258
|
-
operation: "remember",
|
|
4259
|
-
outcome: "success",
|
|
4260
|
-
durationMs: dur,
|
|
4261
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4262
|
-
});
|
|
4263
|
-
} catch (err) {
|
|
4264
|
-
const dur = Date.now() - t0;
|
|
4265
|
-
this.events?.emit("storage.write", {
|
|
4266
|
-
sessionId: "~memory~",
|
|
4267
|
-
store: "memory",
|
|
4268
|
-
filePath,
|
|
4269
|
-
operation: "remember",
|
|
4270
|
-
outcome: "failure",
|
|
4271
|
-
durationMs: dur,
|
|
4272
|
-
error: toErrorMessage(err),
|
|
4273
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4274
|
-
});
|
|
4275
|
-
throw err;
|
|
4276
|
-
}
|
|
4277
|
-
let trackedSize = this._trackedByteSizes[scope];
|
|
4278
|
-
if (trackedSize === void 0) {
|
|
4279
|
-
trackedSize = await this._recalcTrackedByteSize(scope);
|
|
4280
|
-
}
|
|
4281
|
-
if (trackedSize > MAX_BYTES_TOTAL) {
|
|
4282
|
-
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
4283
|
-
if (removed > 0) {
|
|
4284
|
-
this.events?.emit("memory.consolidated", {
|
|
4285
|
-
scope,
|
|
4286
|
-
removed
|
|
4287
|
-
});
|
|
4288
|
-
await this._recalcTrackedByteSize(scope);
|
|
4289
|
-
}
|
|
4290
|
-
}
|
|
4291
|
-
this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
|
|
4292
|
-
await this.mirrorBackup(scope);
|
|
4293
|
-
this.events?.emit("memory.remembered", {
|
|
4294
|
-
scope,
|
|
4295
|
-
text,
|
|
4296
|
-
ts,
|
|
4297
|
-
type: entry.type,
|
|
4298
|
-
tags: entry.tags,
|
|
4299
|
-
priority: entry.priority
|
|
4300
|
-
});
|
|
4301
|
-
});
|
|
4302
|
-
}
|
|
4303
|
-
/**
|
|
4304
|
-
* Drop the relevance caches after a mutation. Both the per-context score
|
|
4305
|
-
* cache and the per-entry lowercase cache are keyed on entry objects that a
|
|
4306
|
-
* mutation invalidates, so they must be cleared together when entries change.
|
|
4307
|
-
*/
|
|
4308
|
-
invalidateScoreCaches() {
|
|
4309
|
-
this._scoreCache.clear();
|
|
4310
|
-
this._cachedLower = null;
|
|
4311
|
-
}
|
|
4312
|
-
/**
|
|
4313
|
-
* Score and rank memories by relevance to the current context.
|
|
4314
|
-
* Returns entries with score >= MIN_RELEVANCE_SCORE, sorted highest first.
|
|
4315
|
-
*/
|
|
4316
|
-
async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
|
|
4317
|
-
const all = await this.list(scope);
|
|
4318
|
-
if (all.length === 0) return [];
|
|
4319
|
-
const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
|
|
4320
|
-
const now = Date.now();
|
|
4321
|
-
const TTL_MS = 3e4;
|
|
4322
|
-
const cached = this._scoreCache.get(ctxHash);
|
|
4323
|
-
if (cached && cached.expiresAt > now && cached.entries === all) {
|
|
4324
|
-
return cached.scored.slice(0, Math.min(limit, 15));
|
|
4325
|
-
}
|
|
4326
|
-
const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
4327
|
-
const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
|
|
4328
|
-
const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
|
|
4329
|
-
this._cachedLower ??= /* @__PURE__ */ new WeakMap();
|
|
4330
|
-
const lowerCache = this._cachedLower;
|
|
4331
|
-
const scored = [];
|
|
4332
|
-
for (const entry of all) {
|
|
4333
|
-
let score = 0;
|
|
4334
|
-
const reasons = [];
|
|
4335
|
-
let cachedLower = lowerCache.get(entry);
|
|
4336
|
-
if (!cachedLower) {
|
|
4337
|
-
cachedLower = {
|
|
4338
|
-
textLower: entry.text.toLowerCase(),
|
|
4339
|
-
tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
|
|
4340
|
-
};
|
|
4341
|
-
lowerCache.set(entry, cachedLower);
|
|
4342
|
-
}
|
|
4343
|
-
const { textLower, tagsLower } = cachedLower;
|
|
4344
|
-
let taskHits = 0;
|
|
4345
|
-
for (const w of taskWords) {
|
|
4346
|
-
if (textLower.includes(w)) {
|
|
4347
|
-
taskHits++;
|
|
4348
|
-
score += 2;
|
|
4349
|
-
}
|
|
4350
|
-
if (tagsLower.some((t) => t.includes(w))) {
|
|
4351
|
-
taskHits++;
|
|
4352
|
-
score += 3;
|
|
4353
|
-
}
|
|
4354
|
-
}
|
|
4355
|
-
if (taskHits > 0) reasons.push(`task match (${taskHits})`);
|
|
4356
|
-
let skillHits = 0;
|
|
4357
|
-
for (const w of skillWords) {
|
|
4358
|
-
if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
|
|
4359
|
-
skillHits++;
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
score += skillHits;
|
|
4363
|
-
if (skillHits > 0) reasons.push(`skill match (${skillHits})`);
|
|
4364
|
-
for (const w of toolWords) {
|
|
4365
|
-
if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
|
|
4366
|
-
score += 1;
|
|
4367
|
-
reasons.push(`tool mention: ${w}`);
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
switch (entry.priority) {
|
|
4371
|
-
case "critical":
|
|
4372
|
-
score += 5;
|
|
4373
|
-
reasons.push("critical");
|
|
4374
|
-
break;
|
|
4375
|
-
case "high":
|
|
4376
|
-
score += 3;
|
|
4377
|
-
reasons.push("high priority");
|
|
4378
|
-
break;
|
|
4379
|
-
case "medium":
|
|
4380
|
-
score += 1;
|
|
4381
|
-
break;
|
|
4382
|
-
case "low":
|
|
4383
|
-
score -= 2;
|
|
4384
|
-
reasons.push("low priority");
|
|
4385
|
-
break;
|
|
4386
|
-
}
|
|
4387
|
-
switch (entry.type) {
|
|
4388
|
-
case "decision":
|
|
4389
|
-
score += 2;
|
|
4390
|
-
reasons.push("decision");
|
|
4391
|
-
break;
|
|
4392
|
-
case "convention":
|
|
4393
|
-
score += 2;
|
|
4394
|
-
reasons.push("convention");
|
|
4395
|
-
break;
|
|
4396
|
-
case "anti_pattern":
|
|
4397
|
-
score += 3;
|
|
4398
|
-
reasons.push("anti-pattern");
|
|
4399
|
-
break;
|
|
4400
|
-
case "preference":
|
|
4401
|
-
score += 1;
|
|
4402
|
-
reasons.push("preference");
|
|
4403
|
-
break;
|
|
4404
|
-
case "reference":
|
|
4405
|
-
break;
|
|
4406
|
-
case "fact":
|
|
4407
|
-
break;
|
|
4408
|
-
}
|
|
4409
|
-
const ageDays = (now - new Date(entry.ts).getTime()) / (1e3 * 60 * 60 * 24);
|
|
4410
|
-
if (ageDays < 1) score += 1;
|
|
4411
|
-
else if (ageDays > 30) score -= 1;
|
|
4412
|
-
if (entry.confidence !== void 0 && entry.confidence < 0.5) {
|
|
4413
|
-
score -= 2;
|
|
4414
|
-
reasons.push("low confidence");
|
|
4415
|
-
}
|
|
4416
|
-
if (entry.lastAccessed) {
|
|
4417
|
-
const hoursSinceAccess = (now - new Date(entry.lastAccessed).getTime()) / (1e3 * 60 * 60);
|
|
4418
|
-
if (hoursSinceAccess < 1) score -= 1;
|
|
4419
|
-
}
|
|
4420
|
-
if (score > 0) {
|
|
4421
|
-
scored.push({
|
|
4422
|
-
...entry,
|
|
4423
|
-
score,
|
|
4424
|
-
matchReason: reasons.join(", ") || "keyword match"
|
|
4425
|
-
});
|
|
4426
|
-
}
|
|
4427
|
-
}
|
|
4428
|
-
scored.sort((a, b) => b.score - a.score);
|
|
4429
|
-
const threshold = 2;
|
|
4430
|
-
const relevant = scored.filter(
|
|
4431
|
-
(s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
|
|
4432
|
-
);
|
|
4433
|
-
this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
|
|
4434
|
-
return relevant.slice(0, Math.min(limit, 15));
|
|
4435
|
-
}
|
|
4436
|
-
async forget(query, scope = "project-memory") {
|
|
4437
|
-
return this.runSerialized(scope, async () => {
|
|
4438
|
-
const filePath = this.files[scope];
|
|
4439
|
-
const t0 = Date.now();
|
|
4440
|
-
let removed = 0;
|
|
4441
|
-
try {
|
|
4442
|
-
removed = await this.backend.forget(scope, query, filePath);
|
|
4443
|
-
this.invalidateScoreCaches();
|
|
4444
|
-
const dur = Date.now() - t0;
|
|
4445
|
-
this.events?.emit("storage.write", {
|
|
4446
|
-
sessionId: "~memory~",
|
|
4447
|
-
store: "memory",
|
|
4448
|
-
filePath,
|
|
4449
|
-
operation: "forget",
|
|
4450
|
-
outcome: "success",
|
|
4451
|
-
durationMs: dur,
|
|
4452
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4453
|
-
});
|
|
4454
|
-
} catch (err) {
|
|
4455
|
-
const dur = Date.now() - t0;
|
|
4456
|
-
this.events?.emit("storage.write", {
|
|
4457
|
-
sessionId: "~memory~",
|
|
4458
|
-
store: "memory",
|
|
4459
|
-
filePath,
|
|
4460
|
-
operation: "forget",
|
|
4461
|
-
outcome: "failure",
|
|
4462
|
-
durationMs: dur,
|
|
4463
|
-
error: toErrorMessage(err),
|
|
4464
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4465
|
-
});
|
|
4466
|
-
throw err;
|
|
4467
|
-
}
|
|
4468
|
-
if (removed > 0) {
|
|
4469
|
-
this.events?.emit("memory.forgotten", {
|
|
4470
|
-
scope,
|
|
4471
|
-
query,
|
|
4472
|
-
removed
|
|
4473
|
-
});
|
|
4474
|
-
await this.mirrorBackup(scope);
|
|
4475
|
-
await this._recalcTrackedByteSize(scope);
|
|
4476
|
-
}
|
|
4477
|
-
return removed;
|
|
4478
|
-
});
|
|
4479
|
-
}
|
|
4480
|
-
async consolidate(scope) {
|
|
4481
|
-
return this.runSerialized(scope, async () => {
|
|
4482
|
-
const filePath = this.files[scope];
|
|
4483
|
-
const t0 = Date.now();
|
|
4484
|
-
let removed = 0;
|
|
4485
|
-
try {
|
|
4486
|
-
removed = await this.backend.consolidate(scope, filePath);
|
|
4487
|
-
this.invalidateScoreCaches();
|
|
4488
|
-
const dur = Date.now() - t0;
|
|
4489
|
-
this.events?.emit("storage.write", {
|
|
4490
|
-
sessionId: "~memory~",
|
|
4491
|
-
store: "memory",
|
|
4492
|
-
filePath,
|
|
4493
|
-
operation: "consolidate",
|
|
4494
|
-
outcome: "success",
|
|
4495
|
-
durationMs: dur,
|
|
4496
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4497
|
-
});
|
|
4498
|
-
} catch (err) {
|
|
4499
|
-
const dur = Date.now() - t0;
|
|
4500
|
-
this.events?.emit("storage.write", {
|
|
4501
|
-
sessionId: "~memory~",
|
|
4502
|
-
store: "memory",
|
|
4503
|
-
filePath,
|
|
4504
|
-
operation: "consolidate",
|
|
4505
|
-
outcome: "failure",
|
|
4506
|
-
durationMs: dur,
|
|
4507
|
-
error: toErrorMessage(err),
|
|
4508
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4509
|
-
});
|
|
4510
|
-
throw err;
|
|
4511
|
-
}
|
|
4512
|
-
if (removed > 0) {
|
|
4513
|
-
this.events?.emit("memory.consolidated", {
|
|
4514
|
-
scope,
|
|
4515
|
-
removed
|
|
4516
|
-
});
|
|
4517
|
-
await this.mirrorBackup(scope);
|
|
4518
|
-
await this._recalcTrackedByteSize(scope);
|
|
4519
|
-
}
|
|
4520
|
-
});
|
|
4521
|
-
}
|
|
4522
|
-
async clear(scope) {
|
|
4523
|
-
if (scope) {
|
|
4524
|
-
await this.runSerialized(scope, async () => {
|
|
4525
|
-
const filePath = this.files[scope];
|
|
4526
|
-
const t0 = Date.now();
|
|
4527
|
-
try {
|
|
4528
|
-
await this.backend.clear(scope, filePath);
|
|
4529
|
-
this.invalidateScoreCaches();
|
|
4530
|
-
const dur = Date.now() - t0;
|
|
4531
|
-
this.events?.emit("storage.write", {
|
|
4532
|
-
sessionId: "~memory~",
|
|
4533
|
-
store: "memory",
|
|
4534
|
-
filePath,
|
|
4535
|
-
operation: "clear",
|
|
4536
|
-
outcome: "success",
|
|
4537
|
-
durationMs: dur,
|
|
4538
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4539
|
-
});
|
|
4540
|
-
} catch (err) {
|
|
4541
|
-
const dur = Date.now() - t0;
|
|
4542
|
-
this.events?.emit("storage.write", {
|
|
4543
|
-
sessionId: "~memory~",
|
|
4544
|
-
store: "memory",
|
|
4545
|
-
filePath,
|
|
4546
|
-
operation: "clear",
|
|
4547
|
-
outcome: "failure",
|
|
4548
|
-
durationMs: dur,
|
|
4549
|
-
error: toErrorMessage(err),
|
|
4550
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4551
|
-
});
|
|
4552
|
-
throw err;
|
|
4553
|
-
}
|
|
4554
|
-
this.events?.emit("memory.cleared", { scope });
|
|
4555
|
-
await this.mirrorBackup(scope);
|
|
4556
|
-
this._trackedByteSizes[scope] = 0;
|
|
4557
|
-
});
|
|
4558
|
-
return;
|
|
4559
|
-
}
|
|
4560
|
-
await Promise.all(
|
|
4561
|
-
["project-agents", "project-memory", "user-memory"].map(
|
|
4562
|
-
async (s) => this.runSerialized(s, async () => {
|
|
4563
|
-
const filePath = this.files[s];
|
|
4564
|
-
const t0 = Date.now();
|
|
4565
|
-
try {
|
|
4566
|
-
await this.backend.clear(s, filePath);
|
|
4567
|
-
const dur = Date.now() - t0;
|
|
4568
|
-
this.events?.emit("storage.write", {
|
|
4569
|
-
sessionId: "~memory~",
|
|
4570
|
-
store: "memory",
|
|
4571
|
-
filePath,
|
|
4572
|
-
operation: "clear",
|
|
4573
|
-
outcome: "success",
|
|
4574
|
-
durationMs: dur,
|
|
4575
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4576
|
-
});
|
|
4577
|
-
} catch (err) {
|
|
4578
|
-
const dur = Date.now() - t0;
|
|
4579
|
-
this.events?.emit("storage.write", {
|
|
4580
|
-
sessionId: "~memory~",
|
|
4581
|
-
store: "memory",
|
|
4582
|
-
filePath,
|
|
4583
|
-
operation: "clear",
|
|
4584
|
-
outcome: "failure",
|
|
4585
|
-
durationMs: dur,
|
|
4586
|
-
error: toErrorMessage(err),
|
|
4587
|
-
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
4588
|
-
});
|
|
4589
|
-
throw err;
|
|
4590
|
-
}
|
|
4591
|
-
this.events?.emit("memory.cleared", { scope: s });
|
|
4592
|
-
await this.mirrorBackup(s);
|
|
4593
|
-
})
|
|
4594
|
-
)
|
|
4595
|
-
);
|
|
4596
|
-
this.invalidateScoreCaches();
|
|
4597
|
-
}
|
|
4598
|
-
/**
|
|
4599
|
-
* Return a new MemoryStore proxy that carries `traceId` on every storage
|
|
4600
|
-
* event. The original store is left unchanged — callers that need a
|
|
4601
|
-
* trace-decorated view (e.g. session-run tools) receive the proxy while
|
|
4602
|
-
* the singleton remains trace-free for boot-time use.
|
|
4603
|
-
*
|
|
4604
|
-
* The proxy implements the full `MemoryStore` interface; all other
|
|
4605
|
-
* properties (backend, etc.) are delegated to the original store.
|
|
4606
|
-
*/
|
|
4607
|
-
withTraceId(traceId) {
|
|
4608
|
-
this.traceId = traceId;
|
|
4609
|
-
return this;
|
|
4610
|
-
}
|
|
4611
|
-
async mirrorBackup(scope) {
|
|
4612
|
-
if (!this.persistBackup || scope === "project-agents") return;
|
|
4613
|
-
try {
|
|
4614
|
-
const content = await this.backend.readAll(scope, this.files[scope]);
|
|
4615
|
-
const { writeFile: writeFile7, mkdir: mkdir7 } = await import("node:fs/promises");
|
|
4616
|
-
await mkdir7(this.backupDir, { recursive: true });
|
|
4617
|
-
await writeFile7(`${this.backupDir}/${scope}.md`, content, "utf8");
|
|
4618
|
-
} catch {
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
};
|
|
4622
|
-
function labelOf(scope) {
|
|
4623
|
-
switch (scope) {
|
|
4624
|
-
case "project-agents":
|
|
4625
|
-
return "Project AGENTS.md";
|
|
4626
|
-
case "project-memory":
|
|
4627
|
-
return "Project memory";
|
|
4628
|
-
case "user-memory":
|
|
4629
|
-
return "User memory";
|
|
4630
|
-
}
|
|
4631
|
-
}
|
|
4632
|
-
|
|
4633
4122
|
// src/storage/memory-graph-backend.ts
|
|
4634
|
-
import * as
|
|
4123
|
+
import * as fs4 from "node:fs/promises";
|
|
4635
4124
|
var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
4636
4125
|
kind = "graph";
|
|
4637
4126
|
file;
|
|
@@ -4799,7 +4288,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
|
4799
4288
|
this.loadedScope = scope;
|
|
4800
4289
|
this.loaded = true;
|
|
4801
4290
|
try {
|
|
4802
|
-
await
|
|
4291
|
+
await fs4.unlink(this.graphFile);
|
|
4803
4292
|
} catch {
|
|
4804
4293
|
}
|
|
4805
4294
|
}
|
|
@@ -4839,7 +4328,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
|
4839
4328
|
async loadGraph(scope) {
|
|
4840
4329
|
if (this.loaded && this.loadedScope === scope) return;
|
|
4841
4330
|
try {
|
|
4842
|
-
const raw = await
|
|
4331
|
+
const raw = await fs4.readFile(this.graphFile, "utf8");
|
|
4843
4332
|
const data = JSON.parse(raw);
|
|
4844
4333
|
this.nodes = new Map(data.nodes);
|
|
4845
4334
|
this.edges = data.edges;
|
|
@@ -4861,10 +4350,10 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
|
4861
4350
|
edges: this.edges
|
|
4862
4351
|
};
|
|
4863
4352
|
const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
|
|
4864
|
-
await
|
|
4353
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
4865
4354
|
const tmp = `${this.graphFile}.tmp`;
|
|
4866
|
-
await
|
|
4867
|
-
await
|
|
4355
|
+
await fs4.writeFile(tmp, JSON.stringify(data));
|
|
4356
|
+
await fs4.rename(tmp, this.graphFile);
|
|
4868
4357
|
} catch {
|
|
4869
4358
|
}
|
|
4870
4359
|
}
|
|
@@ -4982,7 +4471,7 @@ function simpleHash(s) {
|
|
|
4982
4471
|
}
|
|
4983
4472
|
|
|
4984
4473
|
// src/utils/instruction-file.ts
|
|
4985
|
-
import { readFileSync, statSync } from "node:fs";
|
|
4474
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
4986
4475
|
import * as path11 from "node:path";
|
|
4987
4476
|
import { fileURLToPath } from "node:url";
|
|
4988
4477
|
var textCache = /* @__PURE__ */ new Map();
|
|
@@ -4993,7 +4482,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
4993
4482
|
let resolved = "";
|
|
4994
4483
|
for (const root of instructionRootCandidates()) {
|
|
4995
4484
|
try {
|
|
4996
|
-
resolved =
|
|
4485
|
+
resolved = readFileSync2(path11.join(root, relativePath), "utf8").trimEnd();
|
|
4997
4486
|
break;
|
|
4998
4487
|
} catch {
|
|
4999
4488
|
}
|
|
@@ -5020,7 +4509,7 @@ function instructionRootCandidates() {
|
|
|
5020
4509
|
}
|
|
5021
4510
|
function isDirectory(candidate) {
|
|
5022
4511
|
try {
|
|
5023
|
-
return
|
|
4512
|
+
return statSync2(candidate).isDirectory();
|
|
5024
4513
|
} catch {
|
|
5025
4514
|
return false;
|
|
5026
4515
|
}
|
|
@@ -5046,12 +4535,14 @@ var SessionMemoryConsolidator = class {
|
|
|
5046
4535
|
model;
|
|
5047
4536
|
minIterations;
|
|
5048
4537
|
maxExistingEntries;
|
|
4538
|
+
oneShotOrchestrator;
|
|
5049
4539
|
constructor(opts) {
|
|
5050
4540
|
this.memoryStore = opts.memoryStore;
|
|
5051
4541
|
this.provider = opts.provider;
|
|
5052
4542
|
this.model = opts.model;
|
|
5053
4543
|
this.minIterations = opts.minIterations ?? 2;
|
|
5054
4544
|
this.maxExistingEntries = opts.maxExistingEntries ?? 15;
|
|
4545
|
+
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
5055
4546
|
}
|
|
5056
4547
|
afterRun = (ctx, result) => {
|
|
5057
4548
|
if (result.status !== "done") return;
|
|
@@ -5070,19 +4561,32 @@ var SessionMemoryConsolidator = class {
|
|
|
5070
4561
|
_iterations,
|
|
5071
4562
|
existingEntries
|
|
5072
4563
|
);
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
{
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
}
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
4564
|
+
let text;
|
|
4565
|
+
if (this.oneShotOrchestrator) {
|
|
4566
|
+
const oneShotResult = await this.oneShotOrchestrator.call({
|
|
4567
|
+
system: prompt,
|
|
4568
|
+
userPrompt: "Review the session and return memory operations as JSON.",
|
|
4569
|
+
model: _model ?? "deepseek-chat",
|
|
4570
|
+
maxTokens: 500,
|
|
4571
|
+
timeoutMs: 15e3,
|
|
4572
|
+
fallbackProfile: "memory"
|
|
4573
|
+
});
|
|
4574
|
+
text = oneShotResult.text;
|
|
4575
|
+
} else {
|
|
4576
|
+
const signal = AbortSignal.timeout(15e3);
|
|
4577
|
+
const response = await provider.complete(
|
|
4578
|
+
{
|
|
4579
|
+
model: _model,
|
|
4580
|
+
system: [{ type: "text", text: prompt }],
|
|
4581
|
+
messages: [
|
|
4582
|
+
{ role: "user", content: "Review the session and return memory operations as JSON." }
|
|
4583
|
+
],
|
|
4584
|
+
maxTokens: 500
|
|
4585
|
+
},
|
|
4586
|
+
{ signal }
|
|
4587
|
+
);
|
|
4588
|
+
text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
4589
|
+
}
|
|
5086
4590
|
if (!text) return;
|
|
5087
4591
|
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
5088
4592
|
if (!jsonMatch) return;
|
|
@@ -5212,7 +4716,7 @@ function deepFreeze(obj) {
|
|
|
5212
4716
|
|
|
5213
4717
|
// src/storage/provider-config-watcher.ts
|
|
5214
4718
|
import * as syncFs from "node:fs";
|
|
5215
|
-
import * as
|
|
4719
|
+
import * as fs5 from "node:fs/promises";
|
|
5216
4720
|
import * as path12 from "node:path";
|
|
5217
4721
|
|
|
5218
4722
|
// src/security/config-secrets.ts
|
|
@@ -5247,7 +4751,7 @@ function walk(node, vault, transform) {
|
|
|
5247
4751
|
}
|
|
5248
4752
|
return out;
|
|
5249
4753
|
}
|
|
5250
|
-
var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;
|
|
4754
|
+
var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\b)/i;
|
|
5251
4755
|
var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
5252
4756
|
function isSecretField(name) {
|
|
5253
4757
|
const lc = name.toLowerCase();
|
|
@@ -5259,7 +4763,7 @@ function isSecretField(name) {
|
|
|
5259
4763
|
async function readProviderSnapshot(configPath, vault, warn) {
|
|
5260
4764
|
let raw;
|
|
5261
4765
|
try {
|
|
5262
|
-
raw = await
|
|
4766
|
+
raw = await fs5.readFile(configPath, "utf8");
|
|
5263
4767
|
} catch (err) {
|
|
5264
4768
|
if (err.code !== "ENOENT") {
|
|
5265
4769
|
warn?.(`Could not read ${configPath}: ${err.message}`);
|
|
@@ -5348,24 +4852,24 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
|
5348
4852
|
}
|
|
5349
4853
|
|
|
5350
4854
|
// src/storage/config-loader.ts
|
|
5351
|
-
import * as
|
|
4855
|
+
import * as fs6 from "node:fs/promises";
|
|
5352
4856
|
import * as path13 from "node:path";
|
|
5353
4857
|
|
|
5354
4858
|
// src/types/config.ts
|
|
5355
4859
|
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
5356
4860
|
|
|
5357
4861
|
// src/types/context-window.ts
|
|
5358
|
-
var DEFAULT_CONTEXT_WINDOW_MODE_ID = "
|
|
4862
|
+
var DEFAULT_CONTEXT_WINDOW_MODE_ID = "frugal";
|
|
5359
4863
|
var CONTEXT_WINDOW_MODES = Object.freeze([
|
|
5360
4864
|
{
|
|
5361
4865
|
id: "balanced",
|
|
5362
4866
|
name: "Balanced",
|
|
5363
4867
|
description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
|
|
5364
|
-
thresholds: { warn: 0.
|
|
4868
|
+
thresholds: { warn: 0.5, soft: 0.65, hard: 0.8 },
|
|
5365
4869
|
aggressiveOn: "soft",
|
|
5366
|
-
preserveK:
|
|
5367
|
-
eliseThreshold:
|
|
5368
|
-
targetLoad: 0.
|
|
4870
|
+
preserveK: 8,
|
|
4871
|
+
eliseThreshold: 1e3,
|
|
4872
|
+
targetLoad: 0.55
|
|
5369
4873
|
},
|
|
5370
4874
|
{
|
|
5371
4875
|
id: "frugal",
|
|
@@ -5416,7 +4920,7 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
5416
4920
|
descriptionMode: Object.freeze({}),
|
|
5417
4921
|
disabledTools: Object.freeze([]),
|
|
5418
4922
|
autoExtendLimit: true,
|
|
5419
|
-
restrictToProjectRoot:
|
|
4923
|
+
restrictToProjectRoot: true,
|
|
5420
4924
|
loopDetection: Object.freeze({
|
|
5421
4925
|
mode: "steer-then-cut",
|
|
5422
4926
|
steerThreshold: 3,
|
|
@@ -5426,8 +4930,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
5426
4930
|
})
|
|
5427
4931
|
});
|
|
5428
4932
|
var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
5429
|
-
preserveK:
|
|
5430
|
-
eliseThreshold:
|
|
4933
|
+
preserveK: 8,
|
|
4934
|
+
eliseThreshold: 1e3
|
|
5431
4935
|
});
|
|
5432
4936
|
var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
|
|
5433
4937
|
autoProceedDelayMs: 45e3
|
|
@@ -5519,12 +5023,11 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
5519
5023
|
mcpServers: {},
|
|
5520
5024
|
fallbackAuto: true,
|
|
5521
5025
|
maxConcurrent: 4,
|
|
5522
|
-
// YOLO
|
|
5523
|
-
// prompt
|
|
5524
|
-
//
|
|
5525
|
-
//
|
|
5526
|
-
|
|
5527
|
-
yolo: true,
|
|
5026
|
+
// YOLO opt-in: users who want auto-approve behaviour set `yolo: true`.
|
|
5027
|
+
// Off by default so prompt injection in repository content, fetched
|
|
5028
|
+
// content, or mailbox messages cannot induce arbitrary shell calls or
|
|
5029
|
+
// write operations without human confirmation.
|
|
5030
|
+
yolo: false,
|
|
5528
5031
|
nextPrediction: false,
|
|
5529
5032
|
hints: true,
|
|
5530
5033
|
debugStream: false,
|
|
@@ -5549,7 +5052,7 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
5549
5052
|
terminalTitleAnimation: true,
|
|
5550
5053
|
// Mirrored from the top-level yolo default so the autonomy subsystem
|
|
5551
5054
|
// (which reads autonomy.yolo) stays consistent with config.yolo.
|
|
5552
|
-
yolo:
|
|
5055
|
+
yolo: false,
|
|
5553
5056
|
streamFleet: true,
|
|
5554
5057
|
chime: false,
|
|
5555
5058
|
confirmExit: true,
|
|
@@ -5696,6 +5199,10 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
5696
5199
|
key: "fleet",
|
|
5697
5200
|
reason: "Fleet supervision knobs: a repo-committed config could enable autonomous subagent spawning/steering/termination and mailbox traffic on the victim machine."
|
|
5698
5201
|
},
|
|
5202
|
+
{
|
|
5203
|
+
key: "brain",
|
|
5204
|
+
reason: "Brain decision-layer knobs: a repo-committed config could raise the autonomy risk ceiling, switch the Brain to headless (removing the human tier), or reroute Brain decisions to an attacker-chosen provider/model."
|
|
5205
|
+
},
|
|
5699
5206
|
{
|
|
5700
5207
|
key: "git",
|
|
5701
5208
|
reason: "Carries git.identity (GIT_AUTHOR_*/GIT_COMMITTER_* injection): a repo-committed config could spoof the author identity written into the victim's commit history (impersonation)."
|
|
@@ -5744,6 +5251,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
5744
5251
|
"extensions",
|
|
5745
5252
|
"acp",
|
|
5746
5253
|
"fleet",
|
|
5254
|
+
"brain",
|
|
5747
5255
|
"git"
|
|
5748
5256
|
]);
|
|
5749
5257
|
function assertInProjectAllowListComplete() {
|
|
@@ -5895,6 +5403,7 @@ var DefaultConfigLoader = class {
|
|
|
5895
5403
|
extraSources;
|
|
5896
5404
|
events;
|
|
5897
5405
|
traceId;
|
|
5406
|
+
logger;
|
|
5898
5407
|
jsonCache = /* @__PURE__ */ new Map();
|
|
5899
5408
|
constructor(opts) {
|
|
5900
5409
|
this.paths = opts.paths;
|
|
@@ -5903,6 +5412,19 @@ var DefaultConfigLoader = class {
|
|
|
5903
5412
|
this.extraSources = opts.sources ?? [];
|
|
5904
5413
|
this.events = opts.events;
|
|
5905
5414
|
this.traceId = opts.traceId;
|
|
5415
|
+
this.logger = opts.logger;
|
|
5416
|
+
}
|
|
5417
|
+
/**
|
|
5418
|
+
* Emit a structured warning. Uses the configured Logger when available;
|
|
5419
|
+
* falls back to console.warn(JSON) so warnings are never silently dropped
|
|
5420
|
+
* during early boot (before a Logger is constructed).
|
|
5421
|
+
*/
|
|
5422
|
+
logWarn(msg, ctx) {
|
|
5423
|
+
if (this.logger) {
|
|
5424
|
+
this.logger.warn(msg, ctx);
|
|
5425
|
+
} else {
|
|
5426
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
5427
|
+
}
|
|
5906
5428
|
}
|
|
5907
5429
|
async load(opts = {}) {
|
|
5908
5430
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
@@ -5915,7 +5437,14 @@ var DefaultConfigLoader = class {
|
|
|
5915
5437
|
]);
|
|
5916
5438
|
cfg = deepMerge2(cfg, global);
|
|
5917
5439
|
cfg = deepMerge2(cfg, local);
|
|
5918
|
-
cfg = deepMerge2(
|
|
5440
|
+
cfg = deepMerge2(
|
|
5441
|
+
cfg,
|
|
5442
|
+
stripUnsafeInProjectFields(
|
|
5443
|
+
inProject,
|
|
5444
|
+
this.paths.inProjectConfig,
|
|
5445
|
+
this.logger ? (msg) => this.logger.warn(msg) : void 0
|
|
5446
|
+
)
|
|
5447
|
+
);
|
|
5919
5448
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
5920
5449
|
const v = process.env[key];
|
|
5921
5450
|
if (v) fn(cfg, v);
|
|
@@ -5932,14 +5461,9 @@ var DefaultConfigLoader = class {
|
|
|
5932
5461
|
cfg = deepMerge2(cfg, patch);
|
|
5933
5462
|
}
|
|
5934
5463
|
} catch (err) {
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
event: "config.source_load_failed",
|
|
5939
|
-
source: src.name,
|
|
5940
|
-
message: toErrorMessage(err),
|
|
5941
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5942
|
-
})
|
|
5464
|
+
this.logWarn(
|
|
5465
|
+
"Config source load failed",
|
|
5466
|
+
{ event: "config.source_load_failed", source: src.name, message: toErrorMessage(err) }
|
|
5943
5467
|
);
|
|
5944
5468
|
}
|
|
5945
5469
|
}
|
|
@@ -5980,7 +5504,7 @@ var DefaultConfigLoader = class {
|
|
|
5980
5504
|
await withFileLock(fp, async () => {
|
|
5981
5505
|
let parsed;
|
|
5982
5506
|
try {
|
|
5983
|
-
const raw = await
|
|
5507
|
+
const raw = await fs6.readFile(fp, "utf8");
|
|
5984
5508
|
const result = safeParse(raw);
|
|
5985
5509
|
if (!result.ok || !isPlainRecord(result.value)) {
|
|
5986
5510
|
return;
|
|
@@ -5999,14 +5523,9 @@ var DefaultConfigLoader = class {
|
|
|
5999
5523
|
durationMs: Date.now() - t0,
|
|
6000
5524
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
6001
5525
|
});
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
event: "config.defaults_read_failed",
|
|
6006
|
-
path: fp,
|
|
6007
|
-
message: toErrorMessage(err),
|
|
6008
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6009
|
-
})
|
|
5526
|
+
this.logWarn(
|
|
5527
|
+
"Config defaults read failed",
|
|
5528
|
+
{ event: "config.defaults_read_failed", path: fp, message: toErrorMessage(err) }
|
|
6010
5529
|
);
|
|
6011
5530
|
return;
|
|
6012
5531
|
}
|
|
@@ -6040,14 +5559,9 @@ var DefaultConfigLoader = class {
|
|
|
6040
5559
|
durationMs: Date.now() - t0,
|
|
6041
5560
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
6042
5561
|
});
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
event: "config.defaults_write_failed",
|
|
6047
|
-
path: fp,
|
|
6048
|
-
message: toErrorMessage(err),
|
|
6049
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6050
|
-
})
|
|
5562
|
+
this.logWarn(
|
|
5563
|
+
"Config defaults write failed",
|
|
5564
|
+
{ event: "config.defaults_write_failed", path: fp, message: toErrorMessage(err) }
|
|
6051
5565
|
);
|
|
6052
5566
|
}
|
|
6053
5567
|
}
|
|
@@ -6099,7 +5613,7 @@ var DefaultConfigLoader = class {
|
|
|
6099
5613
|
const fp = this.paths.syncConfig;
|
|
6100
5614
|
const t0 = Date.now();
|
|
6101
5615
|
try {
|
|
6102
|
-
const raw = await
|
|
5616
|
+
const raw = await fs6.readFile(fp, "utf8");
|
|
6103
5617
|
const parsed = safeParse(raw);
|
|
6104
5618
|
if (!parsed.ok || !parsed.value) {
|
|
6105
5619
|
this.events?.emit("storage.read", {
|
|
@@ -6150,13 +5664,9 @@ var DefaultConfigLoader = class {
|
|
|
6150
5664
|
error: storageErrorString(err),
|
|
6151
5665
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
6152
5666
|
});
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
event: "config.sync_load_failed",
|
|
6157
|
-
message: toErrorMessage(err),
|
|
6158
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6159
|
-
})
|
|
5667
|
+
this.logWarn(
|
|
5668
|
+
"Config sync load failed",
|
|
5669
|
+
{ event: "config.sync_load_failed", message: toErrorMessage(err) }
|
|
6160
5670
|
);
|
|
6161
5671
|
return null;
|
|
6162
5672
|
}
|
|
@@ -6165,7 +5675,7 @@ var DefaultConfigLoader = class {
|
|
|
6165
5675
|
const t0 = Date.now();
|
|
6166
5676
|
let mtimeMs = null;
|
|
6167
5677
|
try {
|
|
6168
|
-
const stat13 = await
|
|
5678
|
+
const stat13 = await fs6.stat(file);
|
|
6169
5679
|
mtimeMs = stat13.mtimeMs;
|
|
6170
5680
|
const cached = this.jsonCache.get(file);
|
|
6171
5681
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
@@ -6186,20 +5696,15 @@ var DefaultConfigLoader = class {
|
|
|
6186
5696
|
error: storageErrorString(err),
|
|
6187
5697
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
6188
5698
|
});
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
event: "config.read_failed",
|
|
6193
|
-
path: file,
|
|
6194
|
-
message: toErrorMessage(err),
|
|
6195
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6196
|
-
})
|
|
5699
|
+
this.logWarn(
|
|
5700
|
+
"Config read failed (stat)",
|
|
5701
|
+
{ event: "config.read_failed", path: file, message: toErrorMessage(err) }
|
|
6197
5702
|
);
|
|
6198
5703
|
return {};
|
|
6199
5704
|
}
|
|
6200
5705
|
let raw;
|
|
6201
5706
|
try {
|
|
6202
|
-
raw = await
|
|
5707
|
+
raw = await fs6.readFile(file, "utf8");
|
|
6203
5708
|
} catch (err) {
|
|
6204
5709
|
if (err.code !== "ENOENT") {
|
|
6205
5710
|
this.events?.emit("storage.read", {
|
|
@@ -6212,14 +5717,9 @@ var DefaultConfigLoader = class {
|
|
|
6212
5717
|
error: storageErrorString(err),
|
|
6213
5718
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
6214
5719
|
});
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
event: "config.read_failed",
|
|
6219
|
-
path: file,
|
|
6220
|
-
message: toErrorMessage(err),
|
|
6221
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6222
|
-
})
|
|
5720
|
+
this.logWarn(
|
|
5721
|
+
"Config read failed (read)",
|
|
5722
|
+
{ event: "config.read_failed", path: file, message: toErrorMessage(err) }
|
|
6223
5723
|
);
|
|
6224
5724
|
}
|
|
6225
5725
|
this.jsonCache.set(file, { mtimeMs: null, value: {} });
|
|
@@ -6237,14 +5737,9 @@ var DefaultConfigLoader = class {
|
|
|
6237
5737
|
error: "parse error or empty file",
|
|
6238
5738
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
6239
5739
|
});
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
event: "config.parse_failed",
|
|
6244
|
-
path: file,
|
|
6245
|
-
message: "invalid JSON \u2014 falling back to defaults for this layer",
|
|
6246
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6247
|
-
})
|
|
5740
|
+
this.logWarn(
|
|
5741
|
+
"Config parse failed \u2014 falling back to defaults",
|
|
5742
|
+
{ event: "config.parse_failed", path: file }
|
|
6248
5743
|
);
|
|
6249
5744
|
return {};
|
|
6250
5745
|
}
|
|
@@ -6291,8 +5786,9 @@ var DefaultConfigLoader = class {
|
|
|
6291
5786
|
}
|
|
6292
5787
|
if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
|
|
6293
5788
|
const known = listContextWindowModes().map((m) => m.id).join(", ");
|
|
6294
|
-
|
|
6295
|
-
`
|
|
5789
|
+
this.logWarn(
|
|
5790
|
+
`Ignoring unknown context.mode "${c.mode}" \u2014 falling back to "${DEFAULT_CONTEXT_WINDOW_MODE_ID}"`,
|
|
5791
|
+
{ event: "config.unknown_context_mode", mode: c.mode, known }
|
|
6296
5792
|
);
|
|
6297
5793
|
c.mode = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
6298
5794
|
}
|
|
@@ -6510,7 +6006,7 @@ function defaultIsPidAlive(pid) {
|
|
|
6510
6006
|
}
|
|
6511
6007
|
|
|
6512
6008
|
// src/storage/session-reader.ts
|
|
6513
|
-
import * as
|
|
6009
|
+
import * as fs7 from "node:fs/promises";
|
|
6514
6010
|
var DefaultSessionReader = class _DefaultSessionReader {
|
|
6515
6011
|
store;
|
|
6516
6012
|
eventCache = /* @__PURE__ */ new Map();
|
|
@@ -6528,7 +6024,7 @@ var DefaultSessionReader = class _DefaultSessionReader {
|
|
|
6528
6024
|
const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
|
|
6529
6025
|
let mtimeMs = null;
|
|
6530
6026
|
try {
|
|
6531
|
-
const stat13 = await
|
|
6027
|
+
const stat13 = await fs7.stat(sessionPath);
|
|
6532
6028
|
mtimeMs = stat13.mtimeMs;
|
|
6533
6029
|
} catch {
|
|
6534
6030
|
this.eventCache.delete(sessionId);
|
|
@@ -6892,7 +6388,7 @@ function renderPlainText(meta, events) {
|
|
|
6892
6388
|
|
|
6893
6389
|
// src/storage/annotations-store.ts
|
|
6894
6390
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
6895
|
-
import * as
|
|
6391
|
+
import * as fs8 from "node:fs/promises";
|
|
6896
6392
|
var FILE_VERSION = 1;
|
|
6897
6393
|
var MAX_TEXT_LENGTH = 2e3;
|
|
6898
6394
|
var MAX_ANNOTATIONS = 1e3;
|
|
@@ -7119,7 +6615,7 @@ var AnnotationsStore = class {
|
|
|
7119
6615
|
const fp = this.filePath(sessionId);
|
|
7120
6616
|
let raw;
|
|
7121
6617
|
try {
|
|
7122
|
-
raw = await
|
|
6618
|
+
raw = await fs8.readFile(fp, "utf8");
|
|
7123
6619
|
} catch (err) {
|
|
7124
6620
|
if (err.code === "ENOENT") return null;
|
|
7125
6621
|
throw err;
|
|
@@ -7156,7 +6652,7 @@ var AnnotationsStore = class {
|
|
|
7156
6652
|
};
|
|
7157
6653
|
|
|
7158
6654
|
// src/storage/replay-log-store.ts
|
|
7159
|
-
import * as
|
|
6655
|
+
import * as fs9 from "node:fs/promises";
|
|
7160
6656
|
import * as path15 from "node:path";
|
|
7161
6657
|
|
|
7162
6658
|
// src/replay/hash.ts
|
|
@@ -7246,12 +6742,12 @@ var ReplayLogStore = class {
|
|
|
7246
6742
|
const line = JSON.stringify(entry) + "\n";
|
|
7247
6743
|
let offset2 = 0;
|
|
7248
6744
|
try {
|
|
7249
|
-
const stat13 = await
|
|
6745
|
+
const stat13 = await fs9.stat(fp);
|
|
7250
6746
|
offset2 = stat13.size;
|
|
7251
6747
|
} catch (err) {
|
|
7252
6748
|
if (err.code !== "ENOENT") throw err;
|
|
7253
6749
|
}
|
|
7254
|
-
await
|
|
6750
|
+
await fs9.appendFile(fp, line, "utf8");
|
|
7255
6751
|
cache.set(hash, { entry, offset: offset2, length: Buffer.byteLength(line, "utf8") });
|
|
7256
6752
|
this.diskCount.set(input.sessionId, currentCount + 1);
|
|
7257
6753
|
this.events?.emit("storage.write", {
|
|
@@ -7379,7 +6875,7 @@ var ReplayLogStore = class {
|
|
|
7379
6875
|
const scan = async (dir, prefix, depth) => {
|
|
7380
6876
|
let entries;
|
|
7381
6877
|
try {
|
|
7382
|
-
entries = await
|
|
6878
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
7383
6879
|
} catch (err) {
|
|
7384
6880
|
if (depth === 0 && err.code !== "ENOENT") {
|
|
7385
6881
|
console.warn(JSON.stringify({
|
|
@@ -7417,7 +6913,7 @@ var ReplayLogStore = class {
|
|
|
7417
6913
|
return sessionScopedPath(this.dir, sessionId, ".replay.jsonl");
|
|
7418
6914
|
}
|
|
7419
6915
|
async countEntries(filePath) {
|
|
7420
|
-
const handle = await
|
|
6916
|
+
const handle = await fs9.open(filePath, "r");
|
|
7421
6917
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
7422
6918
|
let count = 0;
|
|
7423
6919
|
let hasNonWhitespace2 = false;
|
|
@@ -7458,7 +6954,7 @@ var ReplayLogStore = class {
|
|
|
7458
6954
|
async readAll(sessionId) {
|
|
7459
6955
|
const fp = this.filePath(sessionId);
|
|
7460
6956
|
try {
|
|
7461
|
-
const raw = await
|
|
6957
|
+
const raw = await fs9.readFile(fp, "utf8");
|
|
7462
6958
|
const out = [];
|
|
7463
6959
|
for (const line of raw.split("\n")) {
|
|
7464
6960
|
if (!line.trim()) continue;
|
|
@@ -7494,7 +6990,7 @@ var ReplayLogStore = class {
|
|
|
7494
6990
|
const fp = this.filePath(sessionId);
|
|
7495
6991
|
cache = /* @__PURE__ */ new Map();
|
|
7496
6992
|
try {
|
|
7497
|
-
const handle = await
|
|
6993
|
+
const handle = await fs9.open(fp, "r");
|
|
7498
6994
|
const CHUNK = 64 * 1024;
|
|
7499
6995
|
const buffer = Buffer.alloc(CHUNK);
|
|
7500
6996
|
let leftover = "";
|
|
@@ -7545,7 +7041,7 @@ var ReplayLogStore = class {
|
|
|
7545
7041
|
async hydrateEntry(sessionId, hash, location) {
|
|
7546
7042
|
if (location.entry) return location.entry;
|
|
7547
7043
|
const fp = this.filePath(sessionId);
|
|
7548
|
-
const handle = await
|
|
7044
|
+
const handle = await fs9.open(fp, "r");
|
|
7549
7045
|
try {
|
|
7550
7046
|
const buffer = Buffer.alloc(location.length);
|
|
7551
7047
|
const { bytesRead } = await handle.read(buffer, 0, location.length, location.offset);
|
|
@@ -7572,7 +7068,7 @@ var ReplayLogStore = class {
|
|
|
7572
7068
|
};
|
|
7573
7069
|
|
|
7574
7070
|
// src/storage/session-recovery.ts
|
|
7575
|
-
import * as
|
|
7071
|
+
import * as fs10 from "node:fs/promises";
|
|
7576
7072
|
import * as path16 from "node:path";
|
|
7577
7073
|
var SessionRecovery = class {
|
|
7578
7074
|
constructor(dir) {
|
|
@@ -7599,7 +7095,7 @@ var SessionRecovery = class {
|
|
|
7599
7095
|
const fp = this.filePath(sessionId);
|
|
7600
7096
|
let stat13;
|
|
7601
7097
|
try {
|
|
7602
|
-
stat13 = await
|
|
7098
|
+
stat13 = await fs10.stat(fp);
|
|
7603
7099
|
} catch (err) {
|
|
7604
7100
|
if (err.code === "ENOENT") return null;
|
|
7605
7101
|
return null;
|
|
@@ -7633,7 +7129,7 @@ var SessionRecovery = class {
|
|
|
7633
7129
|
const fp = this.filePath(sessionId);
|
|
7634
7130
|
let raw;
|
|
7635
7131
|
try {
|
|
7636
|
-
raw = await
|
|
7132
|
+
raw = await fs10.readFile(fp, "utf8");
|
|
7637
7133
|
} catch (err) {
|
|
7638
7134
|
if (err.code === "ENOENT") return null;
|
|
7639
7135
|
return null;
|
|
@@ -7678,7 +7174,7 @@ var SessionRecovery = class {
|
|
|
7678
7174
|
const collect = async (dir, prefix, depth) => {
|
|
7679
7175
|
let entries;
|
|
7680
7176
|
try {
|
|
7681
|
-
entries = await
|
|
7177
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
7682
7178
|
} catch {
|
|
7683
7179
|
return;
|
|
7684
7180
|
}
|
|
@@ -7729,7 +7225,7 @@ function hasNonWhitespace(line) {
|
|
|
7729
7225
|
}
|
|
7730
7226
|
async function scanLatestLifecycleBoundary(filePath, size) {
|
|
7731
7227
|
const CHUNK_SIZE = 64 * 1024;
|
|
7732
|
-
const handle = await
|
|
7228
|
+
const handle = await fs10.open(filePath, "r");
|
|
7733
7229
|
let position = size;
|
|
7734
7230
|
let laterLineFragment = Buffer.alloc(0);
|
|
7735
7231
|
let latestBoundary = null;
|
|
@@ -7768,7 +7264,7 @@ async function scanLatestLifecycleBoundary(filePath, size) {
|
|
|
7768
7264
|
|
|
7769
7265
|
// src/storage/tool-audit-log.ts
|
|
7770
7266
|
import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
|
|
7771
|
-
import * as
|
|
7267
|
+
import * as fs11 from "node:fs/promises";
|
|
7772
7268
|
var GENESIS_PREV = "0".repeat(64);
|
|
7773
7269
|
var DEFAULT_FSYNC_EVERY = 100;
|
|
7774
7270
|
var ToolAuditLog = class {
|
|
@@ -7838,9 +7334,9 @@ var ToolAuditLog = class {
|
|
|
7838
7334
|
isError: input.isError,
|
|
7839
7335
|
index
|
|
7840
7336
|
};
|
|
7841
|
-
await
|
|
7337
|
+
await fs11.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
|
|
7842
7338
|
try {
|
|
7843
|
-
const st = await
|
|
7339
|
+
const st = await fs11.stat(fp);
|
|
7844
7340
|
this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
|
|
7845
7341
|
} catch {
|
|
7846
7342
|
}
|
|
@@ -7887,7 +7383,7 @@ var ToolAuditLog = class {
|
|
|
7887
7383
|
const cachedStat = this.tailStat.get(sessionId);
|
|
7888
7384
|
if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
|
|
7889
7385
|
try {
|
|
7890
|
-
const st = await
|
|
7386
|
+
const st = await fs11.stat(fp);
|
|
7891
7387
|
if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
|
|
7892
7388
|
return { prevHash: cachedHash, nextIndex: cachedIndex };
|
|
7893
7389
|
}
|
|
@@ -7908,7 +7404,7 @@ var ToolAuditLog = class {
|
|
|
7908
7404
|
this.tailHash.set(sessionId, prevHash);
|
|
7909
7405
|
this.tailIndex.set(sessionId, nextIndex);
|
|
7910
7406
|
try {
|
|
7911
|
-
const st = await
|
|
7407
|
+
const st = await fs11.stat(fp);
|
|
7912
7408
|
this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
|
|
7913
7409
|
} catch {
|
|
7914
7410
|
}
|
|
@@ -8029,7 +7525,7 @@ var ToolAuditLog = class {
|
|
|
8029
7525
|
async readAll(sessionId) {
|
|
8030
7526
|
const fp = this.filePath(sessionId);
|
|
8031
7527
|
try {
|
|
8032
|
-
const raw = await
|
|
7528
|
+
const raw = await fs11.readFile(fp, "utf8");
|
|
8033
7529
|
const out = [];
|
|
8034
7530
|
for (const line of raw.split("\n")) {
|
|
8035
7531
|
if (!line.trim()) continue;
|
|
@@ -8067,7 +7563,7 @@ var ToolAuditLog = class {
|
|
|
8067
7563
|
}
|
|
8068
7564
|
async sync(sessionId, fp) {
|
|
8069
7565
|
try {
|
|
8070
|
-
const fh = await
|
|
7566
|
+
const fh = await fs11.open(fp, "r+");
|
|
8071
7567
|
try {
|
|
8072
7568
|
await fh.sync();
|
|
8073
7569
|
} finally {
|
|
@@ -8205,14 +7701,15 @@ var SessionAnalyzer = class {
|
|
|
8205
7701
|
};
|
|
8206
7702
|
|
|
8207
7703
|
// src/session-registry.ts
|
|
8208
|
-
import * as
|
|
7704
|
+
import * as fs12 from "node:fs/promises";
|
|
8209
7705
|
import * as path17 from "node:path";
|
|
8210
7706
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
8211
7707
|
var REGISTRY_FILE = "session-registry.json";
|
|
8212
7708
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
8213
7709
|
var STALE_TIMEOUT_MS = 3e4;
|
|
8214
7710
|
var CLOSING_GRACE_MS = 15e3;
|
|
8215
|
-
var LOST_GRACE_MS =
|
|
7711
|
+
var LOST_GRACE_MS = 3e4;
|
|
7712
|
+
var AGENT_STALE_MS = 5 * 6e4;
|
|
8216
7713
|
var STALE_LOCK_MS = 1e4;
|
|
8217
7714
|
var STALE_TMP_MS = 6e4;
|
|
8218
7715
|
var MAX_STALE_TMP_FILES = 20;
|
|
@@ -8401,36 +7898,43 @@ var SessionRegistry = class {
|
|
|
8401
7898
|
}
|
|
8402
7899
|
async readAndPrune() {
|
|
8403
7900
|
try {
|
|
8404
|
-
const raw = await
|
|
7901
|
+
const raw = await fs12.readFile(this.filePath, "utf8");
|
|
8405
7902
|
const registry = parseRegistry(raw);
|
|
8406
7903
|
const now = Date.now();
|
|
8407
7904
|
let pruned = false;
|
|
8408
7905
|
for (const [id, entry] of Object.entries(registry)) {
|
|
8409
|
-
const
|
|
7906
|
+
const heartbeatAt = Date.parse(entry.lastHeartbeatAt);
|
|
7907
|
+
if (!Number.isFinite(heartbeatAt)) {
|
|
7908
|
+
delete registry[id];
|
|
7909
|
+
pruned = true;
|
|
7910
|
+
continue;
|
|
7911
|
+
}
|
|
7912
|
+
const heartbeatAge = now - heartbeatAt;
|
|
7913
|
+
const agents = Array.isArray(entry.agents) ? entry.agents : [];
|
|
7914
|
+
const liveAgents = agents.filter((agent) => {
|
|
7915
|
+
if (agent.id === "leader") return true;
|
|
7916
|
+
const lastActivityAt = Date.parse(agent.lastActivityAt);
|
|
7917
|
+
return Number.isFinite(lastActivityAt) && now - lastActivityAt <= AGENT_STALE_MS;
|
|
7918
|
+
});
|
|
7919
|
+
if (liveAgents.length !== agents.length || entry.agentCount !== liveAgents.length) {
|
|
7920
|
+
entry.agents = liveAgents;
|
|
7921
|
+
entry.agentCount = liveAgents.length;
|
|
7922
|
+
pruned = true;
|
|
7923
|
+
}
|
|
8410
7924
|
if (entry.status === "closing" && heartbeatAge > CLOSING_GRACE_MS) {
|
|
8411
7925
|
delete registry[id];
|
|
8412
7926
|
pruned = true;
|
|
8413
7927
|
continue;
|
|
8414
7928
|
}
|
|
8415
|
-
if (heartbeatAge
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
pruned = true;
|
|
8425
|
-
}
|
|
8426
|
-
} else {
|
|
8427
|
-
entry.status = "stale";
|
|
8428
|
-
const startedAge = now - new Date(entry.startedAt).getTime();
|
|
8429
|
-
if (startedAge > 5 * 6e4) {
|
|
8430
|
-
delete registry[id];
|
|
8431
|
-
pruned = true;
|
|
8432
|
-
}
|
|
8433
|
-
}
|
|
7929
|
+
if (heartbeatAge <= STALE_TIMEOUT_MS) continue;
|
|
7930
|
+
if (!pidAlive(entry.pid) || heartbeatAge > STALE_TIMEOUT_MS + LOST_GRACE_MS) {
|
|
7931
|
+
delete registry[id];
|
|
7932
|
+
pruned = true;
|
|
7933
|
+
continue;
|
|
7934
|
+
}
|
|
7935
|
+
if (entry.status !== "lost") {
|
|
7936
|
+
entry.status = "lost";
|
|
7937
|
+
pruned = true;
|
|
8434
7938
|
}
|
|
8435
7939
|
}
|
|
8436
7940
|
if (pruned) {
|
|
@@ -8447,11 +7951,11 @@ var SessionRegistry = class {
|
|
|
8447
7951
|
const retryDelayMs = 20;
|
|
8448
7952
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
8449
7953
|
try {
|
|
8450
|
-
await
|
|
8451
|
-
let lockHandle = await
|
|
7954
|
+
await fs12.mkdir(path17.dirname(this.filePath), { recursive: true });
|
|
7955
|
+
let lockHandle = await fs12.open(lockPath, "wx").catch(() => null);
|
|
8452
7956
|
if (!lockHandle) {
|
|
8453
7957
|
if (await this.breakStaleLock(lockPath)) {
|
|
8454
|
-
lockHandle = await
|
|
7958
|
+
lockHandle = await fs12.open(lockPath, "wx").catch(() => null);
|
|
8455
7959
|
}
|
|
8456
7960
|
if (!lockHandle) {
|
|
8457
7961
|
await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
|
|
@@ -8460,14 +7964,14 @@ var SessionRegistry = class {
|
|
|
8460
7964
|
}
|
|
8461
7965
|
try {
|
|
8462
7966
|
await lockHandle.writeFile(String(process.pid)).catch(() => void 0);
|
|
8463
|
-
const raw = await
|
|
7967
|
+
const raw = await fs12.readFile(this.filePath, "utf8").catch(() => "{}");
|
|
8464
7968
|
const registry = parseRegistry(raw);
|
|
8465
7969
|
fn(registry);
|
|
8466
7970
|
await this.writeAtomicLocked(registry);
|
|
8467
7971
|
return;
|
|
8468
7972
|
} finally {
|
|
8469
7973
|
await lockHandle.close();
|
|
8470
|
-
await
|
|
7974
|
+
await fs12.unlink(lockPath).catch(() => void 0);
|
|
8471
7975
|
}
|
|
8472
7976
|
} catch {
|
|
8473
7977
|
return;
|
|
@@ -8484,16 +7988,16 @@ var SessionRegistry = class {
|
|
|
8484
7988
|
async breakStaleLock(lockPath) {
|
|
8485
7989
|
try {
|
|
8486
7990
|
const [stat13, content] = await Promise.all([
|
|
8487
|
-
|
|
7991
|
+
fs12.stat(lockPath),
|
|
8488
7992
|
/* v8 ignore start -- best-effort lock-content read; .catch only fires if the lock vanished */
|
|
8489
|
-
|
|
7993
|
+
fs12.readFile(lockPath, "utf8").catch(() => "")
|
|
8490
7994
|
/* v8 ignore stop */
|
|
8491
7995
|
]);
|
|
8492
7996
|
const ageMs = Date.now() - stat13.mtimeMs;
|
|
8493
7997
|
const ownerPid = Number.parseInt(content.trim(), 10);
|
|
8494
7998
|
const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
|
|
8495
7999
|
if (ownerDead || ageMs > STALE_LOCK_MS) {
|
|
8496
|
-
await
|
|
8000
|
+
await fs12.unlink(lockPath).catch(() => void 0);
|
|
8497
8001
|
return true;
|
|
8498
8002
|
}
|
|
8499
8003
|
return false;
|
|
@@ -8516,16 +8020,16 @@ var SessionRegistry = class {
|
|
|
8516
8020
|
`.${path17.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
|
|
8517
8021
|
);
|
|
8518
8022
|
try {
|
|
8519
|
-
const handle = await
|
|
8023
|
+
const handle = await fs12.open(tmp, "w");
|
|
8520
8024
|
try {
|
|
8521
8025
|
await handle.writeFile(JSON.stringify(registry, null, 2), "utf8");
|
|
8522
8026
|
await handle.sync().catch(() => void 0);
|
|
8523
8027
|
} finally {
|
|
8524
8028
|
await handle.close();
|
|
8525
8029
|
}
|
|
8526
|
-
await
|
|
8030
|
+
await fs12.rename(tmp, this.filePath);
|
|
8527
8031
|
} catch (err) {
|
|
8528
|
-
await
|
|
8032
|
+
await fs12.unlink(tmp).catch(() => void 0);
|
|
8529
8033
|
throw err;
|
|
8530
8034
|
}
|
|
8531
8035
|
}
|
|
@@ -8535,17 +8039,17 @@ var SessionRegistry = class {
|
|
|
8535
8039
|
const base = path17.basename(this.filePath);
|
|
8536
8040
|
const now = Date.now();
|
|
8537
8041
|
const stale = [];
|
|
8538
|
-
for (const name of await
|
|
8042
|
+
for (const name of await fs12.readdir(dir)) {
|
|
8539
8043
|
const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
|
|
8540
8044
|
if (!isTemp) continue;
|
|
8541
|
-
const stat13 = await
|
|
8045
|
+
const stat13 = await fs12.stat(path17.join(dir, name)).catch(() => null);
|
|
8542
8046
|
if (!stat13) continue;
|
|
8543
8047
|
if (now - stat13.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat13.mtimeMs });
|
|
8544
8048
|
}
|
|
8545
8049
|
stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
8546
8050
|
await Promise.all(
|
|
8547
8051
|
stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
|
|
8548
|
-
await
|
|
8052
|
+
await fs12.unlink(path17.join(dir, name)).catch(() => void 0);
|
|
8549
8053
|
})
|
|
8550
8054
|
);
|
|
8551
8055
|
} catch {
|
|
@@ -8961,7 +8465,7 @@ var AgentStatusTracker = class {
|
|
|
8961
8465
|
};
|
|
8962
8466
|
|
|
8963
8467
|
// src/fleet-notifier.ts
|
|
8964
|
-
import * as
|
|
8468
|
+
import * as fs13 from "node:fs/promises";
|
|
8965
8469
|
import * as path18 from "node:path";
|
|
8966
8470
|
var INSTANCES_FILE = "webui-instances.json";
|
|
8967
8471
|
var DISCOVERY_TTL_MS = 2500;
|
|
@@ -9024,7 +8528,7 @@ var FleetNotifier = class {
|
|
|
9024
8528
|
}
|
|
9025
8529
|
async discover() {
|
|
9026
8530
|
try {
|
|
9027
|
-
const raw = await
|
|
8531
|
+
const raw = await fs13.readFile(path18.join(this.baseDir, INSTANCES_FILE), "utf8");
|
|
9028
8532
|
const data = JSON.parse(raw);
|
|
9029
8533
|
const list = Array.isArray(data?.instances) ? data.instances : [];
|
|
9030
8534
|
return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
|
|
@@ -9275,7 +8779,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
|
9275
8779
|
return null;
|
|
9276
8780
|
}
|
|
9277
8781
|
}
|
|
9278
|
-
async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
|
|
8782
|
+
async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId, warn) {
|
|
9279
8783
|
const t0 = Date.now();
|
|
9280
8784
|
const payload = {
|
|
9281
8785
|
version: 1,
|
|
@@ -9304,28 +8808,17 @@ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)
|
|
|
9304
8808
|
error: toErrorMessage(err),
|
|
9305
8809
|
recoverable: false
|
|
9306
8810
|
});
|
|
9307
|
-
console.warn(JSON.stringify({
|
|
9308
|
-
level: "warn",
|
|
9309
|
-
event: "todos_checkpoint.save_failed",
|
|
9310
|
-
message: toErrorMessage(err),
|
|
9311
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9312
|
-
}));
|
|
8811
|
+
(warn ?? ((m) => console.warn(JSON.stringify({ level: "warn", event: "todos_checkpoint.save_failed", message: m, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(toErrorMessage(err));
|
|
9313
8812
|
}
|
|
9314
8813
|
}
|
|
9315
|
-
function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
|
|
8814
|
+
function attachTodosCheckpoint(state, filePath, sessionId, events, traceId, warn) {
|
|
9316
8815
|
let timer = null;
|
|
9317
8816
|
let pending = null;
|
|
9318
8817
|
let writeChain = Promise.resolve();
|
|
9319
8818
|
const enqueueWrite = (todos) => {
|
|
9320
8819
|
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
|
|
9321
8820
|
const msg = toErrorMessage(err);
|
|
9322
|
-
console.error(JSON.stringify({
|
|
9323
|
-
level: "error",
|
|
9324
|
-
event: "todos_checkpoint.write_chain_failed",
|
|
9325
|
-
sessionId,
|
|
9326
|
-
message: msg,
|
|
9327
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9328
|
-
}));
|
|
8821
|
+
(warn ?? ((m) => console.error(JSON.stringify({ level: "error", event: "todos_checkpoint.write_chain_failed", sessionId, message: m, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(msg);
|
|
9329
8822
|
});
|
|
9330
8823
|
return writeChain;
|
|
9331
8824
|
};
|
|
@@ -9513,7 +9006,7 @@ async function loadPlan(filePath, events) {
|
|
|
9513
9006
|
return null;
|
|
9514
9007
|
}
|
|
9515
9008
|
}
|
|
9516
|
-
async function savePlan(filePath, plan, events) {
|
|
9009
|
+
async function savePlan(filePath, plan, events, warn) {
|
|
9517
9010
|
const t0 = Date.now();
|
|
9518
9011
|
try {
|
|
9519
9012
|
await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
|
|
@@ -9535,10 +9028,14 @@ async function savePlan(filePath, plan, events) {
|
|
|
9535
9028
|
error: toErrorMessage(err),
|
|
9536
9029
|
recoverable: false
|
|
9537
9030
|
});
|
|
9538
|
-
console.warn(
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9031
|
+
(warn ?? ((m) => console.warn(
|
|
9032
|
+
JSON.stringify({
|
|
9033
|
+
level: "warn",
|
|
9034
|
+
event: "plan_store.save_failed",
|
|
9035
|
+
message: m,
|
|
9036
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9037
|
+
})
|
|
9038
|
+
)))(toErrorMessage(err));
|
|
9542
9039
|
return false;
|
|
9543
9040
|
}
|
|
9544
9041
|
}
|
|
@@ -9579,9 +9076,7 @@ function setPlanItemStatus(plan, idOrIndex, status) {
|
|
|
9579
9076
|
const idx = matchIndex(plan, idOrIndex);
|
|
9580
9077
|
if (idx === -1) return plan;
|
|
9581
9078
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9582
|
-
const items = plan.items.map(
|
|
9583
|
-
(it, i) => i === idx ? { ...it, status, updatedAt: now } : it
|
|
9584
|
-
);
|
|
9079
|
+
const items = plan.items.map((it, i) => i === idx ? { ...it, status, updatedAt: now } : it);
|
|
9585
9080
|
return { ...plan, items, updatedAt: now };
|
|
9586
9081
|
}
|
|
9587
9082
|
function clearPlan(plan) {
|
|
@@ -9836,7 +9331,7 @@ async function loadTasks(filePath, events, traceId) {
|
|
|
9836
9331
|
return null;
|
|
9837
9332
|
}
|
|
9838
9333
|
}
|
|
9839
|
-
async function saveTasks(filePath, tasks, events, traceId) {
|
|
9334
|
+
async function saveTasks(filePath, tasks, events, traceId, warn) {
|
|
9840
9335
|
const t0 = Date.now();
|
|
9841
9336
|
try {
|
|
9842
9337
|
tasks.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9862,10 +9357,14 @@ async function saveTasks(filePath, tasks, events, traceId) {
|
|
|
9862
9357
|
recoverable: false,
|
|
9863
9358
|
...traceId !== void 0 && { traceId }
|
|
9864
9359
|
});
|
|
9865
|
-
console.warn(
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9360
|
+
(warn ?? ((m) => console.warn(
|
|
9361
|
+
JSON.stringify({
|
|
9362
|
+
level: "warn",
|
|
9363
|
+
event: "task_store.save_failed",
|
|
9364
|
+
message: m,
|
|
9365
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9366
|
+
})
|
|
9367
|
+
)))(toErrorMessage(err));
|
|
9869
9368
|
return false;
|
|
9870
9369
|
}
|
|
9871
9370
|
}
|
|
@@ -10093,7 +9592,7 @@ var MAX_JOURNAL_ENTRIES = 500;
|
|
|
10093
9592
|
function goalFilePath(projectRoot) {
|
|
10094
9593
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
10095
9594
|
}
|
|
10096
|
-
async function loadGoal(filePath, events) {
|
|
9595
|
+
async function loadGoal(filePath, events, warn) {
|
|
10097
9596
|
const t0 = Date.now();
|
|
10098
9597
|
let raw;
|
|
10099
9598
|
try {
|
|
@@ -10124,13 +9623,9 @@ async function loadGoal(filePath, events) {
|
|
|
10124
9623
|
try {
|
|
10125
9624
|
const parsed = JSON.parse(raw);
|
|
10126
9625
|
if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
|
|
10127
|
-
console.warn(JSON.stringify({
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
path: filePath,
|
|
10131
|
-
message: "invalid schema \u2014 consider deleting and re-creating",
|
|
10132
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10133
|
-
}));
|
|
9626
|
+
(warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.invalid_schema", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
|
|
9627
|
+
"invalid schema \u2014 consider deleting and re-creating"
|
|
9628
|
+
);
|
|
10134
9629
|
events?.emit("storage.read", {
|
|
10135
9630
|
sessionId: "~boot~",
|
|
10136
9631
|
store: "goal",
|
|
@@ -10152,13 +9647,9 @@ async function loadGoal(filePath, events) {
|
|
|
10152
9647
|
});
|
|
10153
9648
|
return parsed;
|
|
10154
9649
|
} catch {
|
|
10155
|
-
console.warn(JSON.stringify({
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
path: filePath,
|
|
10159
|
-
message: "JSON parse failed \u2014 consider deleting and re-creating",
|
|
10160
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10161
|
-
}));
|
|
9650
|
+
(warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.parse_failed", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
|
|
9651
|
+
"JSON parse failed \u2014 consider deleting and re-creating"
|
|
9652
|
+
);
|
|
10162
9653
|
events?.emit("storage.read", {
|
|
10163
9654
|
sessionId: "~boot~",
|
|
10164
9655
|
store: "goal",
|
|
@@ -10342,9 +9833,204 @@ function computeTrend(history) {
|
|
|
10342
9833
|
return "steady";
|
|
10343
9834
|
}
|
|
10344
9835
|
|
|
9836
|
+
// src/storage/goal-kanban.ts
|
|
9837
|
+
import {
|
|
9838
|
+
addColumn,
|
|
9839
|
+
addTask,
|
|
9840
|
+
createBoard,
|
|
9841
|
+
getBoard,
|
|
9842
|
+
listBoards,
|
|
9843
|
+
removeBoard
|
|
9844
|
+
} from "@wrongstack/kanban";
|
|
9845
|
+
var GOAL_BOARD_TAG_PREFIX = "goal:";
|
|
9846
|
+
var DEFAULT_COLUMNS = [
|
|
9847
|
+
{ title: "Backlog", order: 0 },
|
|
9848
|
+
{ title: "In Progress", order: 1 },
|
|
9849
|
+
{ title: "Done", order: 2 }
|
|
9850
|
+
];
|
|
9851
|
+
async function createGoalKanbanBoard(projectRoot, goalFile) {
|
|
9852
|
+
if (!projectRoot) return null;
|
|
9853
|
+
const existingId = goalFile.kanbanBoardId;
|
|
9854
|
+
if (existingId) {
|
|
9855
|
+
const existing = await getBoard(projectRoot, existingId).catch(() => null);
|
|
9856
|
+
if (existing) return existingId;
|
|
9857
|
+
}
|
|
9858
|
+
const displayGoal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, " ").trim();
|
|
9859
|
+
const titleSuffix = displayGoal.length > 80 ? displayGoal.slice(0, 77) + "\u2026" : displayGoal;
|
|
9860
|
+
const board = await createBoard(projectRoot, {
|
|
9861
|
+
title: `\u{1F3AF} ${titleSuffix}`,
|
|
9862
|
+
description: `Kanban board auto-created for goal: ${displayGoal}`,
|
|
9863
|
+
tags: [goalTag(goalFile)]
|
|
9864
|
+
});
|
|
9865
|
+
const backlogCol = board.columns.find(
|
|
9866
|
+
(c) => ["backlog", "todo", "open"].includes(c.id.toLowerCase())
|
|
9867
|
+
);
|
|
9868
|
+
const existingColumnIds = new Set(board.columns.map((c) => c.id));
|
|
9869
|
+
for (const col of DEFAULT_COLUMNS) {
|
|
9870
|
+
if (!existingColumnIds.has(col.title.toLowerCase().replace(/\s+/g, "-"))) {
|
|
9871
|
+
const result = await addColumn(projectRoot, board.id, { title: col.title });
|
|
9872
|
+
if (result?.column && backlogCol) {
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9876
|
+
const targetColumnId = board.columns[0]?.id ?? "backlog";
|
|
9877
|
+
if (goalFile.deliverables && goalFile.deliverables.length > 0) {
|
|
9878
|
+
for (const d of goalFile.deliverables) {
|
|
9879
|
+
const cleaned = d.replace(/^\[[x✓]\]|✅|\(done\)\s*/i, "").trim();
|
|
9880
|
+
if (cleaned) {
|
|
9881
|
+
await addTask(projectRoot, board.id, {
|
|
9882
|
+
title: cleaned,
|
|
9883
|
+
columnId: targetColumnId,
|
|
9884
|
+
description: `Deliverable for goal: ${displayGoal}`,
|
|
9885
|
+
priority: "medium"
|
|
9886
|
+
}).catch(() => {
|
|
9887
|
+
});
|
|
9888
|
+
}
|
|
9889
|
+
}
|
|
9890
|
+
}
|
|
9891
|
+
goalFile.kanbanBoardId = board.id;
|
|
9892
|
+
return board.id;
|
|
9893
|
+
}
|
|
9894
|
+
async function findGoalKanbanBoard(projectRoot, boardId) {
|
|
9895
|
+
if (!projectRoot || !boardId) return null;
|
|
9896
|
+
try {
|
|
9897
|
+
return await getBoard(projectRoot, boardId);
|
|
9898
|
+
} catch {
|
|
9899
|
+
return null;
|
|
9900
|
+
}
|
|
9901
|
+
}
|
|
9902
|
+
async function deleteGoalKanbanBoard(projectRoot, boardId) {
|
|
9903
|
+
if (!projectRoot || !boardId) return;
|
|
9904
|
+
await removeBoard(projectRoot, boardId).catch(() => {
|
|
9905
|
+
});
|
|
9906
|
+
}
|
|
9907
|
+
async function findGoalBoardByTag(projectRoot, goalFile) {
|
|
9908
|
+
if (!projectRoot) return null;
|
|
9909
|
+
const tag = goalTag(goalFile);
|
|
9910
|
+
const boards = await listBoards(projectRoot);
|
|
9911
|
+
const matched = boards.find((b) => b.tags?.includes(tag));
|
|
9912
|
+
if (!matched) return null;
|
|
9913
|
+
return getBoard(projectRoot, matched.id).catch(() => null);
|
|
9914
|
+
}
|
|
9915
|
+
function formatGoalKanbanPreview(goalFile, boardId, taskCount) {
|
|
9916
|
+
const lines = [];
|
|
9917
|
+
const displayGoal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, " ").trim();
|
|
9918
|
+
lines.push("");
|
|
9919
|
+
lines.push(color.bold(color.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")));
|
|
9920
|
+
lines.push(color.bold(color.cyan(" \u{1F3AF} GOAL KANBAN \u2014 Mission Board")));
|
|
9921
|
+
lines.push(color.bold(color.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")));
|
|
9922
|
+
lines.push("");
|
|
9923
|
+
lines.push(` ${color.bold("Mission:")} ${color.bold(displayGoal)}`);
|
|
9924
|
+
if (boardId) {
|
|
9925
|
+
lines.push(` ${color.dim(`Board: ${boardId.slice(0, 12)}\u2026`)}`);
|
|
9926
|
+
}
|
|
9927
|
+
if (typeof taskCount === "number") {
|
|
9928
|
+
lines.push(` ${color.dim(`${taskCount} task(s)`)}`);
|
|
9929
|
+
}
|
|
9930
|
+
lines.push("");
|
|
9931
|
+
const deliverables = goalFile.deliverables ?? [];
|
|
9932
|
+
const doneDeliverables = deliverables.filter(
|
|
9933
|
+
(d) => /^\[[x✓]\]|✅|\(done\)/i.test(d)
|
|
9934
|
+
).length;
|
|
9935
|
+
const totalDeliverables = deliverables.length;
|
|
9936
|
+
if (totalDeliverables > 0) {
|
|
9937
|
+
const backlog = deliverables.filter(
|
|
9938
|
+
(d) => !/^\[[x✓]\]|✅|\(done\)|🔄|in.progress/i.test(d)
|
|
9939
|
+
);
|
|
9940
|
+
const inProgress = deliverables.filter((d) => /🔄|in.progress/i.test(d));
|
|
9941
|
+
const done = deliverables.filter((d) => /^\[[x✓]\]|✅|\(done\)/i.test(d));
|
|
9942
|
+
const renderColumn = (title, items, icon, accent) => {
|
|
9943
|
+
if (items.length === 0) {
|
|
9944
|
+
lines.push(` ${accent(color.bold(`${icon} ${title}`))} ${color.dim("(empty)")}`);
|
|
9945
|
+
return;
|
|
9946
|
+
}
|
|
9947
|
+
lines.push(` ${accent(color.bold(`${icon} ${title} (${items.length})`))}`);
|
|
9948
|
+
for (const item of items) {
|
|
9949
|
+
const cleaned = item.replace(/^\[[x✓]\]|✅|\(done\)|🔄|in.progress\s*/gi, "").trim();
|
|
9950
|
+
lines.push(` \u25CB ${cleaned}`);
|
|
9951
|
+
}
|
|
9952
|
+
};
|
|
9953
|
+
renderColumn("Backlog", backlog, "\u{1F4CB}", (s) => color.dim(s));
|
|
9954
|
+
renderColumn("In Progress", inProgress, "\u{1F504}", (s) => color.cyan(s));
|
|
9955
|
+
renderColumn("Done", done, "\u2705", (s) => color.green(s));
|
|
9956
|
+
lines.push("");
|
|
9957
|
+
lines.push(
|
|
9958
|
+
` ${color.dim(`Progress: ${doneDeliverables}/${totalDeliverables} deliverables complete`)}`
|
|
9959
|
+
);
|
|
9960
|
+
}
|
|
9961
|
+
if (typeof goalFile.progress === "number") {
|
|
9962
|
+
const pct = Math.min(100, Math.max(0, Math.round(goalFile.progress)));
|
|
9963
|
+
const filled = Math.round(pct / 5);
|
|
9964
|
+
const empty = 20 - filled;
|
|
9965
|
+
const bar = color.green("\u2588".repeat(filled)) + color.dim("\u2591".repeat(empty));
|
|
9966
|
+
lines.push(` ${color.dim("Overall:")} ${bar} ${color.bold(`${pct}%`)}`);
|
|
9967
|
+
if (goalFile.progressNote) {
|
|
9968
|
+
lines.push(` ${color.dim(goalFile.progressNote)}`);
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
if (typeof goalFile.progress === "undefined" && totalDeliverables > 0) {
|
|
9972
|
+
const pct = Math.round(doneDeliverables / totalDeliverables * 100);
|
|
9973
|
+
const filled = Math.round(pct / 5);
|
|
9974
|
+
const empty = 20 - filled;
|
|
9975
|
+
const bar = (pct > 0 ? color.green("\u2588".repeat(filled)) : "") + color.dim("\u2591".repeat(empty));
|
|
9976
|
+
lines.push(` ${color.dim("Overall:")} ${bar} ${color.bold(`${pct}%`)}`);
|
|
9977
|
+
}
|
|
9978
|
+
lines.push("");
|
|
9979
|
+
lines.push(color.bold(color.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")));
|
|
9980
|
+
return lines.join("\n");
|
|
9981
|
+
}
|
|
9982
|
+
function formatGoalEvent(goalFile, boardId) {
|
|
9983
|
+
const lines = [];
|
|
9984
|
+
lines.push("");
|
|
9985
|
+
lines.push(color.bold(color.green("\u250C\u2500\u2500\u2500 Goal Event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")));
|
|
9986
|
+
lines.push(color.bold(color.green("\u2502")) + " \u{1F3AF} Mission locked and ready");
|
|
9987
|
+
if (boardId) {
|
|
9988
|
+
lines.push(
|
|
9989
|
+
color.bold(color.green("\u2502")) + ` \u{1F4CB} Kanban board created: ${color.cyan(boardId.slice(0, 12) + "\u2026")}`
|
|
9990
|
+
);
|
|
9991
|
+
}
|
|
9992
|
+
const deliverables = goalFile.deliverables ?? [];
|
|
9993
|
+
lines.push(
|
|
9994
|
+
color.bold(color.green("\u2502")) + ` \u{1F4E6} ${deliverables.length} deliverable${deliverables.length !== 1 ? "s" : ""} extracted`
|
|
9995
|
+
);
|
|
9996
|
+
if (typeof goalFile.progress === "number") {
|
|
9997
|
+
lines.push(
|
|
9998
|
+
color.bold(color.green("\u2502")) + ` \u{1F4CA} Progress: ${goalFile.progress}%`
|
|
9999
|
+
);
|
|
10000
|
+
}
|
|
10001
|
+
lines.push(color.bold(color.green("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")));
|
|
10002
|
+
return lines.join("\n");
|
|
10003
|
+
}
|
|
10004
|
+
function formatGoalAutonomyChoice() {
|
|
10005
|
+
const lines = [];
|
|
10006
|
+
lines.push("");
|
|
10007
|
+
lines.push(color.bold(color.magenta("\u2550\u2550\u2550 Choose Autonomy Mode \u2550\u2550\u2550")));
|
|
10008
|
+
lines.push("");
|
|
10009
|
+
lines.push(
|
|
10010
|
+
` ${color.bold("1")}. ${color.red("Autonomy Eternal")} ${color.dim("\u2014 Single-agent sense/decide/execute/reflect loop")}`
|
|
10011
|
+
);
|
|
10012
|
+
lines.push(
|
|
10013
|
+
` ${color.bold("2")}. ${color.magenta("Eternal Parallel")} ${color.dim("\u2014 Multi-agent fan-out (4-8 subagents per tick)")}`
|
|
10014
|
+
);
|
|
10015
|
+
lines.push("");
|
|
10016
|
+
lines.push(` ${color.dim("Enter 1 or 2 (or press Enter to skip):")}`);
|
|
10017
|
+
return lines.join("\n");
|
|
10018
|
+
}
|
|
10019
|
+
function parseAutonomyChoice(input) {
|
|
10020
|
+
const trimmed = input.trim().toLowerCase();
|
|
10021
|
+
if (trimmed === "1" || trimmed === "eternal" || trimmed === "e") return "eternal";
|
|
10022
|
+
if (trimmed === "2" || trimmed === "parallel" || trimmed === "p") return "eternal-parallel";
|
|
10023
|
+
if (trimmed === "" || trimmed === "skip" || trimmed === "s" || trimmed === "0") return null;
|
|
10024
|
+
return null;
|
|
10025
|
+
}
|
|
10026
|
+
function goalTag(goalFile) {
|
|
10027
|
+
const goal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, "-").slice(0, 24).toLowerCase();
|
|
10028
|
+
return `${GOAL_BOARD_TAG_PREFIX}${goal}`;
|
|
10029
|
+
}
|
|
10030
|
+
|
|
10345
10031
|
// src/storage/prompt-store.ts
|
|
10346
10032
|
import { createHash as createHash7 } from "node:crypto";
|
|
10347
|
-
import * as
|
|
10033
|
+
import * as fs14 from "node:fs/promises";
|
|
10348
10034
|
import * as path20 from "node:path";
|
|
10349
10035
|
var SCHEMA_VERSION = 2;
|
|
10350
10036
|
function promptChecksum(content) {
|
|
@@ -10409,12 +10095,12 @@ var DefaultPromptStore = class {
|
|
|
10409
10095
|
await ensureDir(this.dir);
|
|
10410
10096
|
const entries = [];
|
|
10411
10097
|
try {
|
|
10412
|
-
const files = await
|
|
10098
|
+
const files = await fs14.readdir(this.dir);
|
|
10413
10099
|
for (const file of files) {
|
|
10414
10100
|
if (!file.endsWith(".json")) continue;
|
|
10415
10101
|
try {
|
|
10416
10102
|
const raw = JSON.parse(
|
|
10417
|
-
await
|
|
10103
|
+
await fs14.readFile(path20.join(this.dir, file), "utf8")
|
|
10418
10104
|
);
|
|
10419
10105
|
const migrated = migratePromptEntry(raw.entry);
|
|
10420
10106
|
if (migrated) entries.push(migrated);
|
|
@@ -10430,7 +10116,7 @@ var DefaultPromptStore = class {
|
|
|
10430
10116
|
async get(id) {
|
|
10431
10117
|
const file = path20.join(this.dir, `${id}.json`);
|
|
10432
10118
|
try {
|
|
10433
|
-
const raw = JSON.parse(await
|
|
10119
|
+
const raw = JSON.parse(await fs14.readFile(file, "utf8"));
|
|
10434
10120
|
return migratePromptEntry(raw.entry);
|
|
10435
10121
|
} catch {
|
|
10436
10122
|
return null;
|
|
@@ -10445,7 +10131,7 @@ var DefaultPromptStore = class {
|
|
|
10445
10131
|
async delete(id) {
|
|
10446
10132
|
const file = path20.join(this.dir, `${id}.json`);
|
|
10447
10133
|
try {
|
|
10448
|
-
await
|
|
10134
|
+
await fs14.unlink(file);
|
|
10449
10135
|
return true;
|
|
10450
10136
|
} catch {
|
|
10451
10137
|
return false;
|
|
@@ -10484,7 +10170,7 @@ var DefaultPromptStore = class {
|
|
|
10484
10170
|
};
|
|
10485
10171
|
|
|
10486
10172
|
// src/storage/prompt-usage-store.ts
|
|
10487
|
-
import * as
|
|
10173
|
+
import * as fs15 from "node:fs/promises";
|
|
10488
10174
|
import * as path21 from "node:path";
|
|
10489
10175
|
var PromptUsageStore = class {
|
|
10490
10176
|
constructor(file) {
|
|
@@ -10493,7 +10179,7 @@ var PromptUsageStore = class {
|
|
|
10493
10179
|
file;
|
|
10494
10180
|
async load() {
|
|
10495
10181
|
try {
|
|
10496
|
-
const raw = JSON.parse(await
|
|
10182
|
+
const raw = JSON.parse(await fs15.readFile(this.file, "utf8"));
|
|
10497
10183
|
if (raw && typeof raw === "object" && raw.usage && typeof raw.usage === "object") {
|
|
10498
10184
|
return raw.usage;
|
|
10499
10185
|
}
|
|
@@ -10530,7 +10216,7 @@ var PromptUsageStore = class {
|
|
|
10530
10216
|
};
|
|
10531
10217
|
|
|
10532
10218
|
// src/storage/input-history-store.ts
|
|
10533
|
-
import * as
|
|
10219
|
+
import * as fs16 from "node:fs/promises";
|
|
10534
10220
|
import * as path22 from "node:path";
|
|
10535
10221
|
var INPUT_HISTORY_DEFAULT_MAX = 100;
|
|
10536
10222
|
var InputHistoryStore = class {
|
|
@@ -10553,7 +10239,7 @@ var InputHistoryStore = class {
|
|
|
10553
10239
|
*/
|
|
10554
10240
|
async load() {
|
|
10555
10241
|
try {
|
|
10556
|
-
const raw = JSON.parse(await
|
|
10242
|
+
const raw = JSON.parse(await fs16.readFile(this.file, "utf8"));
|
|
10557
10243
|
if (raw && typeof raw === "object" && Array.isArray(raw.entries) && raw.entries.every((e) => typeof e === "string")) {
|
|
10558
10244
|
return raw.entries.slice(0, this.maxEntries);
|
|
10559
10245
|
}
|
|
@@ -10610,7 +10296,7 @@ var InputHistoryStore = class {
|
|
|
10610
10296
|
};
|
|
10611
10297
|
|
|
10612
10298
|
// src/storage/cloud-sync.ts
|
|
10613
|
-
import * as
|
|
10299
|
+
import * as fs17 from "node:fs/promises";
|
|
10614
10300
|
import * as path23 from "node:path";
|
|
10615
10301
|
import { createHash as createHash8 } from "node:crypto";
|
|
10616
10302
|
var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
|
|
@@ -10694,7 +10380,7 @@ var CloudSync = class {
|
|
|
10694
10380
|
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10695
10381
|
localRev: rev
|
|
10696
10382
|
};
|
|
10697
|
-
await
|
|
10383
|
+
await fs17.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
|
|
10698
10384
|
this.state = syncState;
|
|
10699
10385
|
return {
|
|
10700
10386
|
ok: true,
|
|
@@ -10726,8 +10412,8 @@ var CloudSync = class {
|
|
|
10726
10412
|
const rel = segments.slice(2).join("/");
|
|
10727
10413
|
const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
|
|
10728
10414
|
const blobData = await this.getBlob(token, owner, repoName, entry.sha);
|
|
10729
|
-
await
|
|
10730
|
-
await
|
|
10415
|
+
await fs17.mkdir(path23.dirname(destPath), { recursive: true });
|
|
10416
|
+
await fs17.writeFile(destPath, Buffer.from(blobData, "base64"));
|
|
10731
10417
|
}
|
|
10732
10418
|
const localRev = await this.hashLocalCategories(cfg.categories);
|
|
10733
10419
|
const syncState = {
|
|
@@ -10736,7 +10422,7 @@ var CloudSync = class {
|
|
|
10736
10422
|
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10737
10423
|
localRev
|
|
10738
10424
|
};
|
|
10739
|
-
await
|
|
10425
|
+
await fs17.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
|
|
10740
10426
|
this.state = syncState;
|
|
10741
10427
|
return {
|
|
10742
10428
|
ok: true,
|
|
@@ -10755,7 +10441,7 @@ var CloudSync = class {
|
|
|
10755
10441
|
}
|
|
10756
10442
|
async loadState() {
|
|
10757
10443
|
try {
|
|
10758
|
-
const raw = await
|
|
10444
|
+
const raw = await fs17.readFile(this.statePath, "utf8");
|
|
10759
10445
|
this.state = JSON.parse(raw);
|
|
10760
10446
|
} catch {
|
|
10761
10447
|
this.state = null;
|
|
@@ -10833,17 +10519,17 @@ var CloudSync = class {
|
|
|
10833
10519
|
const localPath = this.categoryToPath(cat);
|
|
10834
10520
|
if (!localPath) continue;
|
|
10835
10521
|
try {
|
|
10836
|
-
const stat13 = await
|
|
10522
|
+
const stat13 = await fs17.stat(localPath);
|
|
10837
10523
|
if (stat13.isDirectory()) {
|
|
10838
10524
|
const files = await this.walkDir(localPath, localPath);
|
|
10839
10525
|
for (const file of files) {
|
|
10840
|
-
const content = await
|
|
10526
|
+
const content = await fs17.readFile(file, "utf8");
|
|
10841
10527
|
const rel = path23.relative(localPath, file).replace(/\\/g, "/");
|
|
10842
10528
|
entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
|
|
10843
10529
|
hashes.push(content);
|
|
10844
10530
|
}
|
|
10845
10531
|
} else {
|
|
10846
|
-
const content = await
|
|
10532
|
+
const content = await fs17.readFile(localPath, "utf8");
|
|
10847
10533
|
entries.push({ path: `data/${cat}`, content, mode: "100644" });
|
|
10848
10534
|
hashes.push(content);
|
|
10849
10535
|
}
|
|
@@ -10859,15 +10545,15 @@ var CloudSync = class {
|
|
|
10859
10545
|
const localPath = this.categoryToPath(cat);
|
|
10860
10546
|
if (!localPath) continue;
|
|
10861
10547
|
try {
|
|
10862
|
-
const stat13 = await
|
|
10548
|
+
const stat13 = await fs17.stat(localPath);
|
|
10863
10549
|
if (stat13.isDirectory()) {
|
|
10864
10550
|
const files = await this.walkDir(localPath, localPath);
|
|
10865
10551
|
for (const file of files) {
|
|
10866
|
-
const content = await
|
|
10552
|
+
const content = await fs17.readFile(file);
|
|
10867
10553
|
hashes.push(content.toString("base64") + file);
|
|
10868
10554
|
}
|
|
10869
10555
|
} else {
|
|
10870
|
-
const content = await
|
|
10556
|
+
const content = await fs17.readFile(localPath);
|
|
10871
10557
|
hashes.push(content.toString("base64") + localPath);
|
|
10872
10558
|
}
|
|
10873
10559
|
} catch {
|
|
@@ -10894,7 +10580,7 @@ var CloudSync = class {
|
|
|
10894
10580
|
}
|
|
10895
10581
|
async walkDir(dir, base) {
|
|
10896
10582
|
const results = [];
|
|
10897
|
-
const entries = await
|
|
10583
|
+
const entries = await fs17.readdir(dir, { withFileTypes: true });
|
|
10898
10584
|
for (const entry of entries) {
|
|
10899
10585
|
const full = path23.join(dir, entry.name);
|
|
10900
10586
|
if (entry.isDirectory()) {
|
|
@@ -11081,7 +10767,6 @@ export {
|
|
|
11081
10767
|
DefaultAttachmentStore,
|
|
11082
10768
|
DefaultConfigLoader,
|
|
11083
10769
|
DefaultConfigStore,
|
|
11084
|
-
DefaultMemoryStore,
|
|
11085
10770
|
DefaultPromptStore,
|
|
11086
10771
|
DefaultSessionReader,
|
|
11087
10772
|
DefaultSessionRewinder,
|
|
@@ -11110,12 +10795,19 @@ export {
|
|
|
11110
10795
|
attachPlanCheckpoint,
|
|
11111
10796
|
attachTodosCheckpoint,
|
|
11112
10797
|
clearPlan,
|
|
10798
|
+
createGoalKanbanBoard,
|
|
11113
10799
|
createSessionEventBridge,
|
|
10800
|
+
deleteGoalKanbanBoard,
|
|
11114
10801
|
deriveTodosFromPlanItem,
|
|
11115
10802
|
emptyGoal,
|
|
11116
10803
|
emptyPlan,
|
|
11117
10804
|
emptyTaskFile,
|
|
10805
|
+
findGoalBoardByTag,
|
|
10806
|
+
findGoalKanbanBoard,
|
|
11118
10807
|
formatGoal,
|
|
10808
|
+
formatGoalAutonomyChoice,
|
|
10809
|
+
formatGoalEvent,
|
|
10810
|
+
formatGoalKanbanPreview,
|
|
11119
10811
|
formatPlan,
|
|
11120
10812
|
formatPlanTemplates,
|
|
11121
10813
|
generateSessionId,
|
|
@@ -11133,6 +10825,7 @@ export {
|
|
|
11133
10825
|
migratePromptEntry,
|
|
11134
10826
|
mutatePlan,
|
|
11135
10827
|
mutateTasks,
|
|
10828
|
+
parseAutonomyChoice,
|
|
11136
10829
|
parseEntries,
|
|
11137
10830
|
parseProgressFromText,
|
|
11138
10831
|
promptChecksum,
|