@wrongstack/core 0.284.1 → 0.286.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-status-tracker.d.ts +74 -0
- package/dist/agent-status-tracker.d.ts.map +1 -0
- package/dist/autophase/auto-phase-planner.d.ts +79 -0
- package/dist/autophase/auto-phase-planner.d.ts.map +1 -0
- package/dist/autophase/auto-phase-runner.d.ts +79 -0
- package/dist/autophase/auto-phase-runner.d.ts.map +1 -0
- package/dist/autophase/checkpoint.d.ts +49 -0
- package/dist/autophase/checkpoint.d.ts.map +1 -0
- package/dist/autophase/index.d.ts +9 -0
- package/dist/autophase/index.d.ts.map +1 -0
- package/dist/autophase/phase-graph-builder.d.ts +46 -0
- package/dist/autophase/phase-graph-builder.d.ts.map +1 -0
- package/dist/autophase/phase-orchestrator.d.ts +150 -0
- package/dist/autophase/phase-orchestrator.d.ts.map +1 -0
- package/dist/autophase/phase-store.d.ts +29 -0
- package/dist/autophase/phase-store.d.ts.map +1 -0
- package/dist/autophase/types.d.ts +329 -0
- package/dist/autophase/types.d.ts.map +1 -0
- package/dist/boot.d.ts +73 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/coordination/adaptive-concurrency.d.ts +63 -0
- package/dist/coordination/adaptive-concurrency.d.ts.map +1 -0
- package/dist/coordination/agent-bridge.d.ts +24 -0
- package/dist/coordination/agent-bridge.d.ts.map +1 -0
- package/dist/coordination/agent-monitor.d.ts +129 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -0
- package/dist/coordination/agent-subagent-runner.d.ts +74 -0
- package/dist/coordination/agent-subagent-runner.d.ts.map +1 -0
- package/dist/coordination/agents/agent-prompts.d.ts +2 -0
- package/dist/coordination/agents/agent-prompts.d.ts.map +1 -0
- package/dist/coordination/agents/index.d.ts +35 -0
- package/dist/coordination/agents/index.d.ts.map +1 -0
- package/dist/coordination/agents/phase1-discovery.d.ts +4 -0
- package/dist/coordination/agents/phase1-discovery.d.ts.map +1 -0
- package/dist/coordination/agents/phase2-planning.d.ts +4 -0
- package/dist/coordination/agents/phase2-planning.d.ts.map +1 -0
- package/dist/coordination/agents/phase3-build.d.ts +4 -0
- package/dist/coordination/agents/phase3-build.d.ts.map +1 -0
- package/dist/coordination/agents/phase3-techstack.d.ts +13 -0
- package/dist/coordination/agents/phase3-techstack.d.ts.map +1 -0
- package/dist/coordination/agents/phase4-verify.d.ts +4 -0
- package/dist/coordination/agents/phase4-verify.d.ts.map +1 -0
- package/dist/coordination/agents/phase5-review.d.ts +4 -0
- package/dist/coordination/agents/phase5-review.d.ts.map +1 -0
- package/dist/coordination/agents/phase6-domain.d.ts +4 -0
- package/dist/coordination/agents/phase6-domain.d.ts.map +1 -0
- package/dist/coordination/agents/phase7-knowledge.d.ts +4 -0
- package/dist/coordination/agents/phase7-knowledge.d.ts.map +1 -0
- package/dist/coordination/agents/phase8-delivery.d.ts +4 -0
- package/dist/coordination/agents/phase8-delivery.d.ts.map +1 -0
- package/dist/coordination/agents/phase9-meta.d.ts +4 -0
- package/dist/coordination/agents/phase9-meta.d.ts.map +1 -0
- package/dist/coordination/agents/types.d.ts +79 -0
- package/dist/coordination/agents/types.d.ts.map +1 -0
- package/dist/coordination/agents.d.ts +8 -0
- package/dist/coordination/agents.d.ts.map +1 -0
- package/dist/coordination/auto-extend.d.ts +46 -0
- package/dist/coordination/auto-extend.d.ts.map +1 -0
- package/dist/coordination/autonomous-brain.d.ts +152 -0
- package/dist/coordination/autonomous-brain.d.ts.map +1 -0
- package/dist/coordination/autonomous-coordinator.d.ts +243 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -0
- 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 +103 -0
- package/dist/coordination/brain-monitor.d.ts.map +1 -0
- package/dist/coordination/brain.d.ts +147 -0
- package/dist/coordination/brain.d.ts.map +1 -0
- package/dist/coordination/change-manager.d.ts +151 -0
- package/dist/coordination/change-manager.d.ts.map +1 -0
- package/dist/coordination/checkpoint-wiring.d.ts +61 -0
- package/dist/coordination/checkpoint-wiring.d.ts.map +1 -0
- package/dist/coordination/collab-bus.d.ts +132 -0
- package/dist/coordination/collab-bus.d.ts.map +1 -0
- package/dist/coordination/collab-debug.d.ts +302 -0
- package/dist/coordination/collab-debug.d.ts.map +1 -0
- package/dist/coordination/commit-safety.d.ts +72 -0
- package/dist/coordination/commit-safety.d.ts.map +1 -0
- package/dist/coordination/consensus-protocol.d.ts +106 -0
- package/dist/coordination/consensus-protocol.d.ts.map +1 -0
- package/dist/coordination/coordinator/error-classifier.d.ts +15 -0
- package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -0
- package/dist/coordination/delegate-tool.d.ts +101 -0
- package/dist/coordination/delegate-tool.d.ts.map +1 -0
- package/dist/coordination/dep-watcher-bridge.d.ts +47 -0
- package/dist/coordination/dep-watcher-bridge.d.ts.map +1 -0
- package/dist/coordination/dep-watcher.d.ts +74 -0
- package/dist/coordination/dep-watcher.d.ts.map +1 -0
- package/dist/coordination/director/director-errors.d.ts +29 -0
- package/dist/coordination/director/director-errors.d.ts.map +1 -0
- package/dist/coordination/director-construction.d.ts +90 -0
- package/dist/coordination/director-construction.d.ts.map +1 -0
- package/dist/coordination/director-prompts.d.ts +119 -0
- package/dist/coordination/director-prompts.d.ts.map +1 -0
- package/dist/coordination/director-session.d.ts +68 -0
- package/dist/coordination/director-session.d.ts.map +1 -0
- package/dist/coordination/director-tools.d.ts +65 -0
- package/dist/coordination/director-tools.d.ts.map +1 -0
- package/dist/coordination/director.d.ts +802 -0
- package/dist/coordination/director.d.ts.map +1 -0
- package/dist/coordination/dispatcher.d.ts +82 -0
- package/dist/coordination/dispatcher.d.ts.map +1 -0
- package/dist/coordination/file-author-tracker.d.ts +76 -0
- package/dist/coordination/file-author-tracker.d.ts.map +1 -0
- package/dist/coordination/fleet-bus.d.ts +122 -0
- package/dist/coordination/fleet-bus.d.ts.map +1 -0
- package/dist/coordination/fleet-event-validation.d.ts +10 -0
- package/dist/coordination/fleet-event-validation.d.ts.map +1 -0
- package/dist/coordination/fleet-manager.d.ts +234 -0
- package/dist/coordination/fleet-manager.d.ts.map +1 -0
- package/dist/coordination/fleet-spawn.d.ts +102 -0
- package/dist/coordination/fleet-spawn.d.ts.map +1 -0
- package/dist/coordination/fleet-status-tool.d.ts +36 -0
- package/dist/coordination/fleet-status-tool.d.ts.map +1 -0
- package/dist/coordination/fleet-supervisor.d.ts +157 -0
- package/dist/coordination/fleet-supervisor.d.ts.map +1 -0
- package/dist/coordination/fleet.d.ts +96 -0
- package/dist/coordination/fleet.d.ts.map +1 -0
- package/dist/coordination/global-mailbox.d.ts +295 -0
- package/dist/coordination/global-mailbox.d.ts.map +1 -0
- package/dist/coordination/icoordinator.d.ts +97 -0
- package/dist/coordination/icoordinator.d.ts.map +1 -0
- package/dist/coordination/ifleet-manager.d.ts +177 -0
- package/dist/coordination/ifleet-manager.d.ts.map +1 -0
- package/dist/coordination/in-memory-transport.d.ts +12 -0
- package/dist/coordination/in-memory-transport.d.ts.map +1 -0
- package/dist/coordination/index.d.ts +79 -2587
- package/dist/coordination/index.d.ts.map +1 -0
- package/dist/coordination/index.js +2262 -673
- package/dist/coordination/index.js.map +7 -1
- package/dist/coordination/knowledge-graph.d.ts +229 -0
- package/dist/coordination/knowledge-graph.d.ts.map +1 -0
- package/dist/coordination/large-answer-store.d.ts +61 -0
- package/dist/coordination/large-answer-store.d.ts.map +1 -0
- package/dist/coordination/mail-tools.d.ts +34 -0
- package/dist/coordination/mail-tools.d.ts.map +1 -0
- package/dist/coordination/mailbox-actions.d.ts +67 -0
- package/dist/coordination/mailbox-actions.d.ts.map +1 -0
- 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-health.d.ts +131 -0
- package/dist/coordination/mailbox-health.d.ts.map +1 -0
- package/dist/coordination/mailbox-hooks.d.ts +55 -0
- package/dist/coordination/mailbox-hooks.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +8 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
- package/dist/coordination/mailbox-tool.d.ts +75 -0
- package/dist/coordination/mailbox-tool.d.ts.map +1 -0
- package/dist/{mailbox-types-BGZWrYTJ.d.ts → coordination/mailbox-types.d.ts} +125 -26
- package/dist/coordination/mailbox-types.d.ts.map +1 -0
- package/dist/coordination/mailbox.d.ts +88 -0
- package/dist/coordination/mailbox.d.ts.map +1 -0
- package/dist/coordination/model-matrix.d.ts +98 -0
- package/dist/coordination/model-matrix.d.ts.map +1 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +202 -0
- package/dist/coordination/multi-agent-coordinator.d.ts.map +1 -0
- package/dist/coordination/null-fleet-bus.d.ts +10 -0
- package/dist/coordination/null-fleet-bus.d.ts.map +1 -0
- package/dist/coordination/package-author-tracker.d.ts +87 -0
- package/dist/coordination/package-author-tracker.d.ts.map +1 -0
- package/dist/coordination/package-outdated-watcher.d.ts +83 -0
- package/dist/coordination/package-outdated-watcher.d.ts.map +1 -0
- package/dist/coordination/single-instance-mailbox.d.ts +154 -0
- package/dist/coordination/single-instance-mailbox.d.ts.map +1 -0
- package/dist/coordination/spawn-budget.d.ts +6 -0
- package/dist/coordination/spawn-budget.d.ts.map +1 -0
- package/dist/coordination/subagent-budget.d.ts +323 -0
- package/dist/coordination/subagent-budget.d.ts.map +1 -0
- package/dist/coordination/subagent-nicknames.d.ts +42 -0
- package/dist/coordination/subagent-nicknames.d.ts.map +1 -0
- package/dist/coordination/subagent-result-tool.d.ts +20 -0
- package/dist/coordination/subagent-result-tool.d.ts.map +1 -0
- package/dist/coordination/task-auctioneer.d.ts +143 -0
- package/dist/coordination/task-auctioneer.d.ts.map +1 -0
- package/dist/coordination/task-dag.d.ts +147 -0
- package/dist/coordination/task-dag.d.ts.map +1 -0
- package/dist/coordination/techstack-mailbox-consumer.d.ts +50 -0
- package/dist/coordination/techstack-mailbox-consumer.d.ts.map +1 -0
- package/dist/coordination/transport.d.ts +2 -0
- package/dist/coordination/transport.d.ts.map +1 -0
- package/dist/coordination/worktree-task-runner.d.ts +45 -0
- package/dist/coordination/worktree-task-runner.d.ts.map +1 -0
- package/dist/core/agent-internals.d.ts +41 -0
- package/dist/core/agent-internals.d.ts.map +1 -0
- package/dist/core/agent-loop.d.ts +18 -0
- package/dist/core/agent-loop.d.ts.map +1 -0
- package/dist/core/agent-response.d.ts +21 -0
- package/dist/core/agent-response.d.ts.map +1 -0
- package/dist/core/agent-tools.d.ts +21 -0
- package/dist/core/agent-tools.d.ts.map +1 -0
- package/dist/core/agent-types.d.ts +95 -0
- package/dist/core/agent-types.d.ts.map +1 -0
- package/dist/core/agent.d.ts +54 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/btw.d.ts +40 -0
- package/dist/core/btw.d.ts.map +1 -0
- package/dist/core/context.d.ts +269 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/continue-intent.d.ts +92 -0
- package/dist/core/continue-intent.d.ts.map +1 -0
- package/dist/core/continue-to-next-iteration.d.ts +96 -0
- package/dist/core/continue-to-next-iteration.d.ts.map +1 -0
- package/dist/core/conversation-state.d.ts +96 -0
- package/dist/core/conversation-state.d.ts.map +1 -0
- package/dist/core/fallback-model.d.ts +94 -0
- package/dist/core/fallback-model.d.ts.map +1 -0
- package/dist/core/fleet-pulse.d.ts +42 -0
- package/dist/core/fleet-pulse.d.ts.map +1 -0
- package/dist/core/input-builder.d.ts +95 -0
- package/dist/core/input-builder.d.ts.map +1 -0
- package/dist/core/instruction-bundle.d.ts +22 -0
- package/dist/core/instruction-bundle.d.ts.map +1 -0
- package/dist/core/iteration-limit.d.ts +35 -0
- package/dist/core/iteration-limit.d.ts.map +1 -0
- package/dist/core/mailbox-loop.d.ts +63 -0
- package/dist/core/mailbox-loop.d.ts.map +1 -0
- package/dist/core/modes/brief.d.ts +2 -0
- package/dist/core/modes/brief.d.ts.map +1 -0
- package/dist/core/modes/default.d.ts +8 -0
- package/dist/core/modes/default.d.ts.map +1 -0
- package/dist/core/modes/teach.d.ts +2 -0
- package/dist/core/modes/teach.d.ts.map +1 -0
- package/dist/core/provider-runner.d.ts +24 -0
- package/dist/core/provider-runner.d.ts.map +1 -0
- package/dist/core/queued-messages.d.ts +55 -0
- package/dist/core/queued-messages.d.ts.map +1 -0
- package/dist/core/run-env.d.ts +49 -0
- package/dist/core/run-env.d.ts.map +1 -0
- package/dist/core/streaming-response-builder.d.ts +78 -0
- package/dist/core/streaming-response-builder.d.ts.map +1 -0
- package/dist/core/system-prompt-builder.d.ts +213 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -0
- package/dist/defaults/index.d.ts +68 -158
- package/dist/defaults/index.d.ts.map +1 -0
- package/dist/defaults/index.js +2061 -1300
- package/dist/defaults/index.js.map +7 -1
- package/dist/execution/auto-compaction-middleware.d.ts +123 -0
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -0
- package/dist/execution/autonomous-runner.d.ts +61 -0
- package/dist/execution/autonomous-runner.d.ts.map +1 -0
- package/dist/execution/autonomy-brain.d.ts +151 -0
- package/dist/execution/autonomy-brain.d.ts.map +1 -0
- package/dist/execution/autonomy-prompt-contributor.d.ts +39 -0
- package/dist/execution/autonomy-prompt-contributor.d.ts.map +1 -0
- 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 +103 -0
- package/dist/execution/compaction-core.d.ts.map +1 -0
- package/dist/execution/compactor.d.ts +56 -0
- package/dist/execution/compactor.d.ts.map +1 -0
- package/dist/execution/council-brain.d.ts +79 -0
- package/dist/execution/council-brain.d.ts.map +1 -0
- package/dist/execution/design-color.d.ts +31 -0
- package/dist/execution/design-color.d.ts.map +1 -0
- package/dist/execution/design-detect.d.ts +96 -0
- package/dist/execution/design-detect.d.ts.map +1 -0
- package/dist/execution/design-kit-loader.d.ts +53 -0
- package/dist/execution/design-kit-loader.d.ts.map +1 -0
- package/dist/execution/design-materialize.d.ts +32 -0
- package/dist/execution/design-materialize.d.ts.map +1 -0
- package/dist/execution/design-project-store.d.ts +57 -0
- package/dist/execution/design-project-store.d.ts.map +1 -0
- package/dist/execution/design-verify.d.ts +44 -0
- package/dist/execution/design-verify.d.ts.map +1 -0
- package/dist/execution/error-handler.d.ts +34 -0
- package/dist/execution/error-handler.d.ts.map +1 -0
- package/dist/{parallel-eternal-engine-HUrtePLd.d.ts → execution/eternal-autonomy.d.ts} +22 -225
- package/dist/execution/eternal-autonomy.d.ts.map +1 -0
- package/dist/execution/goal-preamble.d.ts +2 -0
- package/dist/execution/goal-preamble.d.ts.map +1 -0
- package/dist/execution/index.d.ts +25 -180
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +1748 -490
- package/dist/execution/index.js.map +7 -1
- package/dist/execution/intelligent-compactor.d.ts +85 -0
- package/dist/execution/intelligent-compactor.d.ts.map +1 -0
- package/dist/execution/model-runtime.d.ts +75 -0
- package/dist/execution/model-runtime.d.ts.map +1 -0
- package/dist/execution/parallel-eternal-engine.d.ts +142 -0
- package/dist/execution/parallel-eternal-engine.d.ts.map +1 -0
- package/dist/execution/prompt-enhancer.d.ts +12 -13
- package/dist/execution/prompt-enhancer.d.ts.map +1 -0
- package/dist/execution/prompt-enhancer.js +13 -7
- package/dist/execution/prompt-enhancer.js.map +7 -1
- package/dist/execution/prompt-loader.d.ts +52 -0
- package/dist/execution/prompt-loader.d.ts.map +1 -0
- package/dist/execution/provider-runner-impl.d.ts +14 -0
- package/dist/execution/provider-runner-impl.d.ts.map +1 -0
- package/dist/execution/regex-patterns.d.ts +7 -0
- package/dist/execution/regex-patterns.d.ts.map +1 -0
- package/dist/execution/retry-policy.d.ts +35 -0
- package/dist/execution/retry-policy.d.ts.map +1 -0
- package/dist/execution/selective-compactor.d.ts +94 -0
- package/dist/execution/selective-compactor.d.ts.map +1 -0
- package/dist/execution/skill-loader.d.ts +43 -0
- package/dist/execution/skill-loader.d.ts.map +1 -0
- package/dist/execution/strategy-compactor.d.ts +43 -0
- package/dist/execution/strategy-compactor.d.ts.map +1 -0
- package/dist/execution/tool-executor.d.ts +112 -0
- package/dist/execution/tool-executor.d.ts.map +1 -0
- package/dist/extension/extension-points.d.ts +121 -0
- package/dist/extension/extension-points.d.ts.map +1 -0
- package/dist/extension/index.d.ts +3 -10
- package/dist/extension/index.d.ts.map +1 -0
- package/dist/extension/index.js +59 -5
- package/dist/extension/index.js.map +7 -1
- package/dist/extension/registry.d.ts +86 -0
- package/dist/extension/registry.d.ts.map +1 -0
- package/dist/fleet-notifier.d.ts +28 -0
- package/dist/fleet-notifier.d.ts.map +1 -0
- package/dist/hooks/http-executor.d.ts +11 -0
- package/dist/hooks/http-executor.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/registry.d.ts +71 -0
- package/dist/hooks/registry.d.ts.map +1 -0
- package/dist/hooks/runner.d.ts +65 -0
- package/dist/hooks/runner.d.ts.map +1 -0
- package/dist/hooks/shell-executor.d.ts +33 -0
- package/dist/hooks/shell-executor.d.ts.map +1 -0
- package/dist/hooks/shell-hooks-equal.d.ts +15 -0
- package/dist/hooks/shell-hooks-equal.d.ts.map +1 -0
- package/dist/hq/agent-bridge.d.ts +27 -0
- package/dist/hq/agent-bridge.d.ts.map +1 -0
- package/dist/hq/alerts.d.ts +67 -0
- package/dist/hq/alerts.d.ts.map +1 -0
- package/dist/hq/auth-store.d.ts +179 -0
- package/dist/hq/auth-store.d.ts.map +1 -0
- package/dist/hq/brain-bridge.d.ts +37 -0
- package/dist/hq/brain-bridge.d.ts.map +1 -0
- package/dist/hq/commands.d.ts +122 -0
- package/dist/hq/commands.d.ts.map +1 -0
- package/dist/hq/cost-bridge.d.ts +36 -0
- package/dist/hq/cost-bridge.d.ts.map +1 -0
- package/dist/hq/factory.d.ts +69 -0
- package/dist/hq/factory.d.ts.map +1 -0
- package/dist/hq/fleet-bridge.d.ts +39 -0
- package/dist/hq/fleet-bridge.d.ts.map +1 -0
- package/dist/hq/index.d.ts +18 -816
- package/dist/hq/index.d.ts.map +1 -0
- package/dist/hq/index.js +1043 -192
- package/dist/hq/index.js.map +7 -1
- package/dist/hq/mailbox-mapper.d.ts +27 -0
- package/dist/hq/mailbox-mapper.d.ts.map +1 -0
- package/dist/hq/persistence.d.ts +121 -0
- package/dist/hq/persistence.d.ts.map +1 -0
- package/dist/hq/protocol.d.ts +612 -0
- package/dist/hq/protocol.d.ts.map +1 -0
- package/dist/hq/publisher.d.ts +181 -0
- package/dist/hq/publisher.d.ts.map +1 -0
- package/dist/hq/redaction.d.ts +30 -0
- package/dist/hq/redaction.d.ts.map +1 -0
- package/dist/hq/session-bridge.d.ts +29 -0
- package/dist/hq/session-bridge.d.ts.map +1 -0
- package/dist/hq/tool-bridge.d.ts +41 -0
- package/dist/hq/tool-bridge.d.ts.map +1 -0
- package/dist/hq/transcript-mapper.d.ts +36 -0
- package/dist/hq/transcript-mapper.d.ts.map +1 -0
- package/dist/hq/worktree-bridge.d.ts +34 -0
- package/dist/hq/worktree-bridge.d.ts.map +1 -0
- package/dist/index.d.ts +114 -2872
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6104 -2737
- package/dist/index.js.map +7 -1
- package/dist/infrastructure/context-manager.d.ts +92 -0
- package/dist/infrastructure/context-manager.d.ts.map +1 -0
- package/dist/infrastructure/index.d.ts +6 -64
- package/dist/infrastructure/index.d.ts.map +1 -0
- package/dist/infrastructure/index.js +39 -12
- package/dist/infrastructure/index.js.map +7 -1
- package/dist/infrastructure/logger.d.ts +99 -0
- package/dist/infrastructure/logger.d.ts.map +1 -0
- package/dist/infrastructure/mcp-servers.d.ts +83 -0
- package/dist/infrastructure/mcp-servers.d.ts.map +1 -0
- package/dist/{path-resolver-CMS5307d.d.ts → infrastructure/path-resolver.d.ts} +3 -5
- package/dist/infrastructure/path-resolver.d.ts.map +1 -0
- package/dist/infrastructure/token-counter.d.ts +57 -0
- package/dist/infrastructure/token-counter.d.ts.map +1 -0
- package/dist/kernel/container.d.ts +84 -0
- package/dist/kernel/container.d.ts.map +1 -0
- package/dist/{events-Bgnh43r9.d.ts → kernel/events.d.ts} +134 -210
- package/dist/kernel/events.d.ts.map +1 -0
- package/dist/kernel/index.d.ts +6 -107
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +56 -4
- package/dist/kernel/index.js.map +7 -1
- package/dist/kernel/pipeline.d.ts +105 -0
- package/dist/kernel/pipeline.d.ts.map +1 -0
- package/dist/kernel/run-controller.d.ts +55 -0
- package/dist/kernel/run-controller.d.ts.map +1 -0
- package/dist/kernel/tokens.d.ts +53 -0
- package/dist/kernel/tokens.d.ts.map +1 -0
- package/dist/mailbox-attach.d.ts +27 -0
- package/dist/mailbox-attach.d.ts.map +1 -0
- package/dist/middleware/collab-pause.d.ts +73 -0
- package/dist/middleware/collab-pause.d.ts.map +1 -0
- package/dist/models/codex-catalog.d.ts +41 -0
- package/dist/models/codex-catalog.d.ts.map +1 -0
- package/dist/models/index.d.ts +8 -163
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +150 -51
- package/dist/models/index.js.map +7 -1
- package/dist/models/llm-selector.d.ts +47 -0
- package/dist/models/llm-selector.d.ts.map +1 -0
- package/dist/models/mode-store.d.ts +22 -0
- package/dist/models/mode-store.d.ts.map +1 -0
- package/dist/models/model-intelligence.d.ts +56 -0
- package/dist/models/model-intelligence.d.ts.map +1 -0
- package/dist/models/model-router.d.ts +100 -0
- package/dist/models/model-router.d.ts.map +1 -0
- package/dist/models/models-registry.d.ts +106 -0
- package/dist/models/models-registry.d.ts.map +1 -0
- package/dist/models/provider-model-resolve.d.ts +69 -0
- package/dist/models/provider-model-resolve.d.ts.map +1 -0
- package/dist/observability/event-bridge.d.ts +10 -0
- package/dist/observability/event-bridge.d.ts.map +1 -0
- package/dist/observability/health.d.ts +18 -0
- package/dist/observability/health.d.ts.map +1 -0
- package/dist/observability/index.d.ts +9 -354
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +37 -23
- package/dist/observability/index.js.map +7 -1
- package/dist/observability/metrics.d.ts +26 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/otel-tracer.d.ts +41 -0
- package/dist/observability/otel-tracer.d.ts.map +1 -0
- package/dist/observability/otlp-metrics.d.ts +111 -0
- package/dist/observability/otlp-metrics.d.ts.map +1 -0
- package/dist/observability/otlp-traces.d.ts +95 -0
- package/dist/observability/otlp-traces.d.ts.map +1 -0
- package/dist/observability/prometheus.d.ts +49 -0
- package/dist/observability/prometheus.d.ts.map +1 -0
- package/dist/observability/tracer.d.ts +9 -0
- package/dist/observability/tracer.d.ts.map +1 -0
- package/dist/plugin/api.d.ts +226 -0
- package/dist/plugin/api.d.ts.map +1 -0
- package/dist/plugin/loader.d.ts +73 -0
- package/dist/plugin/loader.d.ts.map +1 -0
- package/dist/plugins/chimera-plugin.d.ts +37 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -0
- package/dist/plugins/prompts-plugin.d.ts +26 -0
- package/dist/plugins/prompts-plugin.d.ts.map +1 -0
- package/dist/plugins/security-plugin.d.ts +19 -0
- package/dist/plugins/security-plugin.d.ts.map +1 -0
- package/dist/plugins/skills-plugin.d.ts +42 -0
- package/dist/plugins/skills-plugin.d.ts.map +1 -0
- package/dist/plugins/sync-plugin.d.ts +22 -0
- package/dist/plugins/sync-plugin.d.ts.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/prompt-installer.d.ts +50 -0
- package/dist/prompts/prompt-installer.d.ts.map +1 -0
- package/dist/prompts/prompt-manifest-store.d.ts +18 -0
- package/dist/prompts/prompt-manifest-store.d.ts.map +1 -0
- package/dist/registry/provider-registry.d.ts +51 -0
- package/dist/registry/provider-registry.d.ts.map +1 -0
- package/dist/registry/slash-command-registry.d.ts +61 -0
- package/dist/registry/slash-command-registry.d.ts.map +1 -0
- package/dist/registry/tool-registry.d.ts +154 -0
- package/dist/registry/tool-registry.d.ts.map +1 -0
- package/dist/replay/hash.d.ts +31 -0
- package/dist/replay/hash.d.ts.map +1 -0
- package/dist/replay/replay-provider-runner.d.ts +53 -0
- package/dist/replay/replay-provider-runner.d.ts.map +1 -0
- package/dist/{index-CbyuOE5y.d.ts → security/capabilities.d.ts} +31 -34
- package/dist/security/capabilities.d.ts.map +1 -0
- package/dist/security/config-secrets.d.ts +25 -0
- package/dist/security/config-secrets.d.ts.map +1 -0
- package/dist/security/index.d.ts +7 -8
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +354 -401
- package/dist/security/index.js.map +7 -1
- 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 +158 -0
- package/dist/security/permission-policy.d.ts.map +1 -0
- package/dist/security/secret-scrubber.d.ts +14 -0
- package/dist/security/secret-scrubber.d.ts.map +1 -0
- package/dist/security/secret-vault.d.ts +96 -0
- package/dist/security/secret-vault.d.ts.map +1 -0
- package/dist/security/yolo-risk.d.ts +11 -0
- package/dist/security/yolo-risk.d.ts.map +1 -0
- package/dist/{session-registry-Dvg7i_IA.d.ts → session-registry.d.ts} +8 -9
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/skills/foreign-sources.d.ts +59 -0
- package/dist/skills/foreign-sources.d.ts.map +1 -0
- package/dist/skills/frontmatter.d.ts +44 -0
- package/dist/skills/frontmatter.d.ts.map +1 -0
- package/dist/skills/github-fetcher.d.ts +38 -0
- package/dist/skills/github-fetcher.d.ts.map +1 -0
- package/dist/skills/index.d.ts +11 -582
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +81 -43
- package/dist/skills/index.js.map +7 -1
- package/dist/skills/limits.d.ts +92 -0
- package/dist/skills/limits.d.ts.map +1 -0
- package/dist/skills/manifest-store.d.ts +42 -0
- package/dist/skills/manifest-store.d.ts.map +1 -0
- package/dist/skills/registry/github-direct-adapter.d.ts +17 -0
- package/dist/skills/registry/github-direct-adapter.d.ts.map +1 -0
- package/dist/skills/registry/registry-adapter.d.ts +84 -0
- package/dist/skills/registry/registry-adapter.d.ts.map +1 -0
- package/dist/skills/registry/skills-sh-adapter.d.ts +13 -0
- package/dist/skills/registry/skills-sh-adapter.d.ts.map +1 -0
- package/dist/skills/skill-generator.d.ts +82 -0
- package/dist/skills/skill-generator.d.ts.map +1 -0
- package/dist/skills/skill-installer.d.ts +124 -0
- package/dist/skills/skill-installer.d.ts.map +1 -0
- package/dist/storage/annotations-store.d.ts +118 -0
- package/dist/storage/annotations-store.d.ts.map +1 -0
- package/dist/storage/attachment-store.d.ts +31 -0
- package/dist/storage/attachment-store.d.ts.map +1 -0
- package/dist/storage/cloud-sync.d.ts +44 -0
- package/dist/storage/cloud-sync.d.ts.map +1 -0
- package/dist/storage/completed-work-checkpoint.d.ts +11 -0
- package/dist/storage/completed-work-checkpoint.d.ts.map +1 -0
- package/dist/storage/config-loader.d.ts +114 -0
- package/dist/storage/config-loader.d.ts.map +1 -0
- package/dist/storage/config-migration.d.ts +87 -0
- package/dist/storage/config-migration.d.ts.map +1 -0
- package/dist/storage/config-store.d.ts +22 -0
- package/dist/storage/config-store.d.ts.map +1 -0
- package/dist/{director-state-CMS_bMs4.d.ts → storage/director-state.d.ts} +28 -8
- package/dist/storage/director-state.d.ts.map +1 -0
- package/dist/storage/file-session-writer.d.ts +169 -0
- package/dist/storage/file-session-writer.d.ts.map +1 -0
- package/dist/{goal-store-Datpp-ar.d.ts → storage/goal-store.d.ts} +26 -19
- package/dist/storage/goal-store.d.ts.map +1 -0
- package/dist/storage/index.d.ts +39 -1031
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +783 -443
- package/dist/storage/index.js.map +7 -1
- package/dist/storage/input-history-store.d.ts +56 -0
- package/dist/storage/input-history-store.d.ts.map +1 -0
- package/dist/storage/memory-backend.d.ts +66 -0
- package/dist/storage/memory-backend.d.ts.map +1 -0
- package/dist/storage/memory-consolidator.d.ts +50 -0
- package/dist/storage/memory-consolidator.d.ts.map +1 -0
- package/dist/storage/memory-graph-backend.d.ts +117 -0
- package/dist/storage/memory-graph-backend.d.ts.map +1 -0
- package/dist/storage/memory-store.d.ts +119 -0
- package/dist/storage/memory-store.d.ts.map +1 -0
- package/dist/storage/plan-store.d.ts +78 -0
- package/dist/storage/plan-store.d.ts.map +1 -0
- package/dist/storage/plan-templates.d.ts +21 -0
- package/dist/storage/plan-templates.d.ts.map +1 -0
- package/dist/storage/prompt-store.d.ts +37 -0
- package/dist/storage/prompt-store.d.ts.map +1 -0
- package/dist/storage/prompt-usage-store.d.ts +29 -0
- package/dist/storage/prompt-usage-store.d.ts.map +1 -0
- package/dist/storage/provider-config-watcher.d.ts +34 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -0
- package/dist/storage/queue-store.d.ts +41 -0
- package/dist/storage/queue-store.d.ts.map +1 -0
- package/dist/storage/recovery-lock.d.ts +79 -0
- package/dist/storage/recovery-lock.d.ts.map +1 -0
- package/dist/storage/replay-log-store.d.ts +97 -0
- package/dist/storage/replay-log-store.d.ts.map +1 -0
- package/dist/storage/session-analyzer.d.ts +48 -0
- package/dist/storage/session-analyzer.d.ts.map +1 -0
- package/dist/storage/session-checkpoint-cas.d.ts +37 -0
- package/dist/storage/session-checkpoint-cas.d.ts.map +1 -0
- package/dist/{session-event-bridge-D_z_mBwk.d.ts → storage/session-event-bridge.d.ts} +14 -15
- package/dist/storage/session-event-bridge.d.ts.map +1 -0
- package/dist/storage/session-helpers.d.ts +9 -0
- package/dist/storage/session-helpers.d.ts.map +1 -0
- package/dist/storage/session-id.d.ts +18 -0
- package/dist/storage/session-id.d.ts.map +1 -0
- package/dist/storage/session-reader.d.ts +21 -0
- package/dist/storage/session-reader.d.ts.map +1 -0
- package/dist/storage/session-recovery.d.ts +93 -0
- package/dist/storage/session-recovery.d.ts.map +1 -0
- package/dist/storage/session-resume-validation.d.ts +6 -0
- package/dist/storage/session-resume-validation.d.ts.map +1 -0
- package/dist/storage/session-rewinder.d.ts +21 -0
- package/dist/storage/session-rewinder.d.ts.map +1 -0
- package/dist/storage/session-store.d.ts +219 -0
- package/dist/storage/session-store.d.ts.map +1 -0
- package/dist/storage/session-summary.d.ts +12 -0
- package/dist/storage/session-summary.d.ts.map +1 -0
- package/dist/storage/session-tool-call-ends.d.ts +5 -0
- package/dist/storage/session-tool-call-ends.d.ts.map +1 -0
- package/dist/storage/storage-concurrency.d.ts +2 -0
- package/dist/storage/storage-concurrency.d.ts.map +1 -0
- package/dist/storage/task-store.d.ts +33 -0
- package/dist/storage/task-store.d.ts.map +1 -0
- package/dist/storage/todos-checkpoint.d.ts +39 -0
- package/dist/storage/todos-checkpoint.d.ts.map +1 -0
- package/dist/storage/tool-audit-log.d.ts +141 -0
- package/dist/storage/tool-audit-log.d.ts.map +1 -0
- package/dist/tasking/index.d.ts +3 -124
- package/dist/tasking/index.d.ts.map +1 -0
- package/dist/tasking/index.js +57 -4
- package/dist/tasking/index.js.map +7 -1
- package/dist/tasking/task-store.d.ts +22 -0
- package/dist/tasking/task-store.d.ts.map +1 -0
- package/dist/tasking/task-tracker.d.ts +102 -0
- package/dist/tasking/task-tracker.d.ts.map +1 -0
- package/dist/tools/index.d.ts +2 -59
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +93 -14
- package/dist/tools/index.js.map +7 -1
- package/dist/tools/mcp-control.d.ts +53 -0
- package/dist/tools/mcp-control.d.ts.map +1 -0
- package/dist/tools/mcp-use.d.ts +24 -0
- package/dist/tools/mcp-use.d.ts.map +1 -0
- package/dist/types/agent-bridge.d.ts +42 -0
- package/dist/types/agent-bridge.d.ts.map +1 -0
- package/dist/types/attachment.d.ts +54 -0
- package/dist/types/attachment.d.ts.map +1 -0
- package/dist/types/autonomy.d.ts +5 -0
- package/dist/types/autonomy.d.ts.map +1 -0
- package/dist/types/blocks.d.ts +76 -0
- package/dist/types/blocks.d.ts.map +1 -0
- package/dist/{compactor-DQLL4HTo.d.ts → types/compactor.d.ts} +5 -7
- package/dist/types/compactor.d.ts.map +1 -0
- package/dist/{config-MRqn1V-u.d.ts → types/config.d.ts} +344 -355
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/context-evidence.d.ts +68 -0
- package/dist/types/context-evidence.d.ts.map +1 -0
- package/dist/types/context-window.d.ts +35 -0
- package/dist/types/context-window.d.ts.map +1 -0
- package/dist/{default-config-B79_gJYv.d.ts → types/default-config.d.ts} +10 -11
- package/dist/types/default-config.d.ts.map +1 -0
- package/dist/types/design-kit.d.ts +91 -0
- package/dist/types/design-kit.d.ts.map +1 -0
- package/dist/{retry-policy-CQ7KwXJa.d.ts → types/error-handler.d.ts} +5 -12
- package/dist/types/error-handler.d.ts.map +1 -0
- package/dist/types/errors.d.ts +277 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/hooks.d.ts +167 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/index.d.ts +40 -244
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +101 -12
- package/dist/types/index.js.map +7 -1
- package/dist/{input-reader-E-ffP2ee.d.ts → types/input-reader.d.ts} +3 -4
- package/dist/types/input-reader.d.ts.map +1 -0
- package/dist/{logger-B63L5bTg.d.ts → types/logger.d.ts} +3 -4
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/memory.d.ts +89 -0
- package/dist/types/memory.d.ts.map +1 -0
- package/dist/types/messages.d.ts +24 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/mode-prompts.d.ts +2 -0
- package/dist/types/mode-prompts.d.ts.map +1 -0
- package/dist/{mode-CZlO9iU1.d.ts → types/mode.d.ts} +6 -7
- package/dist/types/mode.d.ts.map +1 -0
- package/dist/types/models-registry.d.ts +105 -0
- package/dist/types/models-registry.d.ts.map +1 -0
- package/dist/types/multi-agent.d.ts +443 -0
- package/dist/types/multi-agent.d.ts.map +1 -0
- package/dist/{observability-D-HZN_mF.d.ts → types/observability.d.ts} +17 -13
- package/dist/types/observability.d.ts.map +1 -0
- package/dist/{path-resolver-CPRj4bFY.d.ts → types/path-resolver.d.ts} +2 -3
- package/dist/types/path-resolver.d.ts.map +1 -0
- package/dist/{permission-ByDKXEcG.d.ts → types/permission.d.ts} +8 -9
- package/dist/types/permission.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +385 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/prompt-registry.d.ts +81 -0
- package/dist/types/prompt-registry.d.ts.map +1 -0
- package/dist/{prompt-DLd35n4Q.d.ts → types/prompt.d.ts} +14 -15
- package/dist/types/prompt.d.ts.map +1 -0
- package/dist/{provider-runner-ChL-kVg6.d.ts → types/provider-runner.d.ts} +9 -10
- package/dist/types/provider-runner.d.ts.map +1 -0
- package/dist/types/provider.d.ts +387 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/renderer.d.ts +23 -0
- package/dist/types/renderer.d.ts.map +1 -0
- package/dist/types/retry-policy.d.ts +7 -0
- package/dist/types/retry-policy.d.ts.map +1 -0
- package/dist/types/secret-scrubber.d.ts +5 -0
- package/dist/types/secret-scrubber.d.ts.map +1 -0
- package/dist/{secret-vault-BAKpgFw_.d.ts → types/secret-vault.d.ts} +13 -7
- package/dist/types/secret-vault.d.ts.map +1 -0
- package/dist/{selector-D8O6B_Rm.d.ts → types/selector.d.ts} +4 -6
- package/dist/types/selector.d.ts.map +1 -0
- package/dist/types/session-reader.d.ts +81 -0
- package/dist/types/session-reader.d.ts.map +1 -0
- package/dist/{session-rewinder-C9HnMkhP.d.ts → types/session-rewinder.d.ts} +5 -6
- package/dist/types/session-rewinder.d.ts.map +1 -0
- package/dist/types/session.d.ts +650 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/side-effect.d.ts +34 -0
- package/dist/types/side-effect.d.ts.map +1 -0
- package/dist/{skill-DHniprNl.d.ts → types/skill.d.ts} +4 -5
- package/dist/types/skill.d.ts.map +1 -0
- package/dist/types/slash-command.d.ts +59 -0
- package/dist/types/slash-command.d.ts.map +1 -0
- package/dist/types/spec.d.ts +77 -0
- package/dist/types/spec.d.ts.map +1 -0
- package/dist/types/system-prompt-contributor.d.ts +20 -0
- package/dist/types/system-prompt-contributor.d.ts.map +1 -0
- package/dist/types/system-prompt.d.ts +51 -0
- package/dist/types/system-prompt.d.ts.map +1 -0
- package/dist/{task-graph-CH3acNQ7.d.ts → types/task-graph.d.ts} +21 -22
- package/dist/types/task-graph.d.ts.map +1 -0
- package/dist/types/token-counter.d.ts +42 -0
- package/dist/types/token-counter.d.ts.map +1 -0
- package/dist/types/tool-executor.d.ts +133 -0
- package/dist/types/tool-executor.d.ts.map +1 -0
- package/dist/types/tool-markers.d.ts +23 -0
- package/dist/types/tool-markers.d.ts.map +1 -0
- package/dist/types/tool.d.ts +252 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/utility-types.d.ts +31 -0
- package/dist/types/utility-types.d.ts.map +1 -0
- package/dist/utils/assert-never.d.ts +14 -0
- package/dist/utils/assert-never.d.ts.map +1 -0
- package/dist/utils/atomic-write.d.ts +12 -0
- package/dist/utils/atomic-write.d.ts.map +1 -0
- package/dist/utils/child-env.d.ts +67 -0
- package/dist/utils/child-env.d.ts.map +1 -0
- package/dist/utils/color.d.ts +20 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/config-json.d.ts +14 -0
- package/dist/utils/config-json.d.ts.map +1 -0
- package/dist/utils/context-evidence.d.ts +47 -0
- package/dist/utils/context-evidence.d.ts.map +1 -0
- package/dist/utils/deep-merge.d.ts +64 -0
- package/dist/utils/deep-merge.d.ts.map +1 -0
- package/dist/utils/diff.d.ts +11 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/error.d.ts +2 -3
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +3 -3
- package/dist/utils/error.js.map +7 -1
- package/dist/utils/expect-defined.d.ts +2 -3
- package/dist/utils/expect-defined.d.ts.map +1 -0
- package/dist/utils/expect-defined.js +3 -3
- package/dist/utils/expect-defined.js.map +7 -1
- package/dist/utils/glob-expand.d.ts +10 -0
- package/dist/utils/glob-expand.d.ts.map +1 -0
- package/dist/utils/glob-match.d.ts +4 -0
- package/dist/utils/glob-match.d.ts.map +1 -0
- 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 +38 -920
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +323 -36
- package/dist/utils/index.js.map +7 -1
- package/dist/utils/instruction-file.d.ts +3 -0
- package/dist/utils/instruction-file.d.ts.map +1 -0
- package/dist/utils/ip-guard.d.ts +34 -0
- package/dist/utils/ip-guard.d.ts.map +1 -0
- package/dist/utils/json-repair.d.ts +23 -0
- package/dist/utils/json-repair.d.ts.map +1 -0
- package/dist/utils/json-schema-validate.d.ts +42 -0
- package/dist/utils/json-schema-validate.d.ts.map +1 -0
- package/dist/utils/lru-cache.d.ts +29 -0
- package/dist/utils/lru-cache.d.ts.map +1 -0
- package/dist/utils/merge-custom-models.d.ts +12 -0
- package/dist/utils/merge-custom-models.d.ts.map +1 -0
- package/dist/utils/merge-models-payload.d.ts +20 -0
- package/dist/utils/merge-models-payload.d.ts.map +1 -0
- package/dist/utils/message-invariants.d.ts +22 -0
- package/dist/utils/message-invariants.d.ts.map +1 -0
- package/dist/utils/newline-normalize.d.ts +5 -0
- package/dist/utils/newline-normalize.d.ts.map +1 -0
- package/dist/utils/regex-guard.d.ts +21 -0
- package/dist/utils/regex-guard.d.ts.map +1 -0
- package/dist/utils/safe-json.d.ts +31 -0
- package/dist/utils/safe-json.d.ts.map +1 -0
- package/dist/utils/session-scoped-path.d.ts +13 -0
- package/dist/utils/session-scoped-path.d.ts.map +1 -0
- package/dist/utils/sleep.d.ts +5 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/slug.d.ts +13 -0
- package/dist/utils/slug.d.ts.map +1 -0
- package/dist/utils/string.d.ts +9 -0
- package/dist/utils/string.d.ts.map +1 -0
- package/dist/utils/task-format.d.ts +22 -0
- package/dist/utils/task-format.d.ts.map +1 -0
- package/dist/utils/term.d.ts +133 -0
- package/dist/utils/term.d.ts.map +1 -0
- package/dist/utils/todos-format.d.ts +30 -0
- package/dist/utils/todos-format.d.ts.map +1 -0
- package/dist/utils/token-estimate.d.ts +112 -0
- package/dist/utils/token-estimate.d.ts.map +1 -0
- package/dist/utils/tool-description-mode.d.ts +28 -0
- package/dist/utils/tool-description-mode.d.ts.map +1 -0
- package/dist/utils/tool-output-serializer.d.ts +40 -0
- package/dist/utils/tool-output-serializer.d.ts.map +1 -0
- package/dist/utils/tool-result-render-mode.d.ts +55 -0
- package/dist/utils/tool-result-render-mode.d.ts.map +1 -0
- package/dist/utils/tool-subject.d.ts +5 -0
- package/dist/utils/tool-subject.d.ts.map +1 -0
- package/dist/utils/tool-wire-compact.d.ts +28 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -0
- package/dist/utils/ulid.d.ts +11 -0
- package/dist/utils/ulid.d.ts.map +1 -0
- package/dist/{wstack-paths-C3K40Qst.d.ts → utils/wstack-paths.d.ts} +7 -8
- package/dist/utils/wstack-paths.d.ts.map +1 -0
- package/dist/worktree/index.d.ts +2 -0
- package/dist/worktree/index.d.ts.map +1 -0
- package/dist/{worktree-manager-DzwxKE35.d.ts → worktree/worktree-manager.d.ts} +32 -17
- package/dist/worktree/worktree-manager.d.ts.map +1 -0
- package/instructions/agents/browser.md +25 -16
- package/instructions/autonomy/active-mission.md +2 -4
- package/instructions/coordination/subagent-baseline.md +16 -2
- package/instructions/leader-after-task.md +3 -1
- package/instructions/sections/tool/common-patterns.md +7 -1
- package/instructions/sections/tool/delegation-full.md +1 -1
- package/instructions/system.md +47 -0
- package/package.json +4 -5
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/node-modern/SKILL.md +2 -2
- package/skills/plugin-author/SKILL.md +9 -16
- package/skills/wrongstack-mailbox/SKILL.md +51 -12
- package/dist/dispatcher-types.d-BBeXBQgS.d.ts +0 -66
- package/dist/global-mailbox-sZwyI6CS.d.ts +0 -959
- package/dist/index--Gm41aBJ.d.ts +0 -671
- package/dist/mcp-servers-DEV4s6Ks.d.ts +0 -259
- package/dist/multi-agent-coordinator-4RCRZKjN.d.ts +0 -1770
- package/dist/null-fleet-bus-CFoNVQEK.d.ts +0 -2127
- package/dist/pipeline-DGDyIohT.d.ts +0 -331
- package/dist/provider-model-resolve-o2hHLs79.d.ts +0 -263
- package/dist/secret-scrubber-3MHDDAtm.d.ts +0 -6
- package/dist/secret-vault-DxohkUBc.d.ts +0 -272
- package/dist/session-reader-7Esxrkpw.d.ts +0 -155
- package/dist/task-format-GX0B1mMC.d.ts +0 -23
- package/dist/todos-checkpoint-FY7PjmMx.d.ts +0 -850
- package/dist/tool-BNlnIJvo.d.ts +0 -2190
- package/dist/tool-executor-CckSCM5E.d.ts +0 -906
|
@@ -1,29 +1,22 @@
|
|
|
1
|
-
import { randomUUID, createHash, randomBytes } from 'crypto';
|
|
2
|
-
import * as fsp9 from 'fs/promises';
|
|
3
|
-
import * as path9 from 'path';
|
|
4
|
-
import { isAbsolute, resolve } from 'path';
|
|
5
|
-
import * as os from 'os';
|
|
6
|
-
import { hostname } from 'os';
|
|
7
|
-
import { readFileSync, statSync, createReadStream, openSync, writeSync, fsyncSync, closeSync } from 'fs';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { EventEmitter } from 'events';
|
|
10
|
-
import { listReadyTasks, claimReadyTask, updateTaskAssignment } from '@wrongstack/kanban';
|
|
11
|
-
import { createInterface } from 'readline';
|
|
12
|
-
import { execFileSync, spawn } from 'child_process';
|
|
13
|
-
|
|
14
1
|
// src/coordination/agent-bridge.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
// src/utils/atomic-write.ts
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import * as fs from "node:fs/promises";
|
|
7
|
+
import * as path from "node:path";
|
|
15
8
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
16
|
-
const dir =
|
|
17
|
-
await
|
|
18
|
-
const tmp =
|
|
9
|
+
const dir = path.dirname(targetPath);
|
|
10
|
+
await fs.mkdir(dir, { recursive: true });
|
|
11
|
+
const tmp = path.join(dir, `.${path.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
19
12
|
try {
|
|
20
13
|
if (typeof content === "string") {
|
|
21
|
-
await
|
|
14
|
+
await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
22
15
|
} else {
|
|
23
|
-
await
|
|
16
|
+
await fs.writeFile(tmp, content, { flag: "wx" });
|
|
24
17
|
}
|
|
25
18
|
try {
|
|
26
|
-
const fh = await
|
|
19
|
+
const fh = await fs.open(tmp, "r+");
|
|
27
20
|
try {
|
|
28
21
|
await fh.sync();
|
|
29
22
|
} finally {
|
|
@@ -33,56 +26,56 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
33
26
|
}
|
|
34
27
|
let mode;
|
|
35
28
|
try {
|
|
36
|
-
const stat9 = await
|
|
29
|
+
const stat9 = await fs.stat(targetPath);
|
|
37
30
|
mode = stat9.mode & 511;
|
|
38
31
|
} catch {
|
|
39
32
|
mode = opts.mode;
|
|
40
33
|
}
|
|
41
34
|
if (mode !== void 0) {
|
|
42
|
-
await
|
|
35
|
+
await fs.chmod(tmp, mode);
|
|
43
36
|
}
|
|
44
37
|
await renameWithRetry(tmp, targetPath);
|
|
45
38
|
if (mode !== void 0 && process.platform === "win32") {
|
|
46
39
|
try {
|
|
47
|
-
await
|
|
40
|
+
await fs.chmod(targetPath, mode);
|
|
48
41
|
} catch {
|
|
49
42
|
}
|
|
50
43
|
}
|
|
51
44
|
} catch (err) {
|
|
52
45
|
try {
|
|
53
|
-
await
|
|
46
|
+
await fs.unlink(tmp);
|
|
54
47
|
} catch {
|
|
55
48
|
}
|
|
56
49
|
throw err;
|
|
57
50
|
}
|
|
58
51
|
}
|
|
59
52
|
async function ensureDir(dir) {
|
|
60
|
-
await
|
|
53
|
+
await fs.mkdir(dir, { recursive: true });
|
|
61
54
|
}
|
|
62
55
|
async function withFileLock(targetPath, fn, opts = {}) {
|
|
63
|
-
const dir =
|
|
64
|
-
await
|
|
65
|
-
const lockPath =
|
|
56
|
+
const dir = path.dirname(targetPath);
|
|
57
|
+
await fs.mkdir(dir, { recursive: true });
|
|
58
|
+
const lockPath = path.join(dir, `.${path.basename(targetPath)}.lock`);
|
|
66
59
|
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
67
60
|
const staleMs = opts.staleMs ?? 3e4;
|
|
68
61
|
const started = Date.now();
|
|
69
62
|
let handle;
|
|
70
63
|
for (; ; ) {
|
|
71
64
|
try {
|
|
72
|
-
handle = await
|
|
65
|
+
handle = await fs.open(lockPath, "wx");
|
|
73
66
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
74
67
|
break;
|
|
75
68
|
} catch (err) {
|
|
76
69
|
const code = err.code;
|
|
77
70
|
if (code === "ENOENT") {
|
|
78
|
-
await
|
|
71
|
+
await fs.mkdir(dir, { recursive: true });
|
|
79
72
|
continue;
|
|
80
73
|
}
|
|
81
74
|
if (code !== "EEXIST") throw err;
|
|
82
75
|
try {
|
|
83
|
-
const stat9 = await
|
|
76
|
+
const stat9 = await fs.stat(lockPath);
|
|
84
77
|
if (Date.now() - stat9.mtimeMs > staleMs) {
|
|
85
|
-
await
|
|
78
|
+
await fs.unlink(lockPath);
|
|
86
79
|
continue;
|
|
87
80
|
}
|
|
88
81
|
} catch {
|
|
@@ -107,7 +100,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
107
100
|
} catch {
|
|
108
101
|
}
|
|
109
102
|
try {
|
|
110
|
-
await
|
|
103
|
+
await fs.unlink(lockPath);
|
|
111
104
|
} catch {
|
|
112
105
|
}
|
|
113
106
|
}
|
|
@@ -115,14 +108,14 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
115
108
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
116
109
|
async function renameWithRetry(from, to) {
|
|
117
110
|
if (process.platform !== "win32") {
|
|
118
|
-
await
|
|
111
|
+
await fs.rename(from, to);
|
|
119
112
|
return;
|
|
120
113
|
}
|
|
121
114
|
const delays = [10, 25, 60, 120, 250];
|
|
122
115
|
let lastErr;
|
|
123
116
|
for (let i = 0; i <= delays.length; i++) {
|
|
124
117
|
try {
|
|
125
|
-
await
|
|
118
|
+
await fs.rename(from, to);
|
|
126
119
|
return;
|
|
127
120
|
} catch (err) {
|
|
128
121
|
lastErr = err;
|
|
@@ -210,8 +203,9 @@ function sanitizeNodeOptions(value) {
|
|
|
210
203
|
}
|
|
211
204
|
return kept.join(" ");
|
|
212
205
|
}
|
|
206
|
+
var gitIdentity = null;
|
|
213
207
|
function buildChildEnv(optsOrSessionId) {
|
|
214
|
-
const opts = {};
|
|
208
|
+
const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
|
|
215
209
|
const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
|
|
216
210
|
const legacyHasOwn = Object.hasOwn(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
|
|
217
211
|
const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
|
|
@@ -249,6 +243,16 @@ function buildChildEnv(optsOrSessionId) {
|
|
|
249
243
|
out[k] = v;
|
|
250
244
|
}
|
|
251
245
|
}
|
|
246
|
+
if (gitIdentity) {
|
|
247
|
+
if (gitIdentity.name) {
|
|
248
|
+
out["GIT_AUTHOR_NAME"] = gitIdentity.name;
|
|
249
|
+
out["GIT_COMMITTER_NAME"] = gitIdentity.name;
|
|
250
|
+
}
|
|
251
|
+
if (gitIdentity.email) {
|
|
252
|
+
out["GIT_AUTHOR_EMAIL"] = gitIdentity.email;
|
|
253
|
+
out["GIT_COMMITTER_EMAIL"] = gitIdentity.email;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
252
256
|
if (opts.extra) {
|
|
253
257
|
Object.assign(out, opts.extra);
|
|
254
258
|
}
|
|
@@ -264,12 +268,16 @@ function toErrorMessage(err) {
|
|
|
264
268
|
// src/utils/expect-defined.ts
|
|
265
269
|
function expectDefined(value, label) {
|
|
266
270
|
if (value === null || value === void 0) {
|
|
267
|
-
const err = new Error("Expected value to be defined");
|
|
271
|
+
const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
|
|
268
272
|
err.name = "ExpectDefinedError";
|
|
269
273
|
throw err;
|
|
270
274
|
}
|
|
271
275
|
return value;
|
|
272
276
|
}
|
|
277
|
+
|
|
278
|
+
// src/utils/glob-expand.ts
|
|
279
|
+
import * as fsp from "node:fs/promises";
|
|
280
|
+
import { isAbsolute, resolve } from "node:path";
|
|
273
281
|
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
274
282
|
var IS_WINDOWS = process.platform === "win32";
|
|
275
283
|
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
@@ -321,9 +329,17 @@ function globToRegex(pat) {
|
|
|
321
329
|
return new RegExp(re + "$");
|
|
322
330
|
}
|
|
323
331
|
function baseDir(pat) {
|
|
324
|
-
let
|
|
325
|
-
|
|
326
|
-
|
|
332
|
+
let firstGlob = pat.length;
|
|
333
|
+
for (let i = 0; i < pat.length; i++) {
|
|
334
|
+
if (GLOB_CHARS.has(expectDefined(pat[i]))) {
|
|
335
|
+
firstGlob = i;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const cut = Math.max(
|
|
340
|
+
pat.lastIndexOf(SEP, firstGlob - 1),
|
|
341
|
+
pat.lastIndexOf("/", firstGlob - 1)
|
|
342
|
+
);
|
|
327
343
|
return cut < 0 ? "." : pat.slice(0, cut);
|
|
328
344
|
}
|
|
329
345
|
async function expandGlob(pattern) {
|
|
@@ -335,7 +351,7 @@ async function expandGlob(pattern) {
|
|
|
335
351
|
async function walk(dir, pat) {
|
|
336
352
|
let entries;
|
|
337
353
|
try {
|
|
338
|
-
entries = await
|
|
354
|
+
entries = await fsp.readdir(dir);
|
|
339
355
|
} catch {
|
|
340
356
|
return;
|
|
341
357
|
}
|
|
@@ -357,7 +373,7 @@ async function expandGlob(pattern) {
|
|
|
357
373
|
for (const e of entries) {
|
|
358
374
|
const full = `${dir}${SEP}${e}`;
|
|
359
375
|
try {
|
|
360
|
-
const stat9 = await
|
|
376
|
+
const stat9 = await fsp.stat(full);
|
|
361
377
|
if (stat9.isDirectory()) await walk(full, rest);
|
|
362
378
|
} catch {
|
|
363
379
|
}
|
|
@@ -375,7 +391,7 @@ async function expandGlob(pattern) {
|
|
|
375
391
|
if (entries.includes(seg)) {
|
|
376
392
|
const full = `${dir}${SEP}${seg}`;
|
|
377
393
|
try {
|
|
378
|
-
const stat9 = await
|
|
394
|
+
const stat9 = await fsp.stat(full);
|
|
379
395
|
if (stat9.isDirectory()) await walk(full, rest);
|
|
380
396
|
} catch {
|
|
381
397
|
}
|
|
@@ -513,13 +529,16 @@ function safeStringify(value, pretty = false) {
|
|
|
513
529
|
});
|
|
514
530
|
}
|
|
515
531
|
}
|
|
532
|
+
|
|
533
|
+
// src/utils/session-scoped-path.ts
|
|
534
|
+
import * as path2 from "node:path";
|
|
516
535
|
function sessionScopedPath(dir, sessionId, suffix) {
|
|
517
536
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
518
537
|
throw invalid(sessionId);
|
|
519
538
|
}
|
|
520
|
-
const resolved =
|
|
521
|
-
const rel =
|
|
522
|
-
if (rel.startsWith("..") ||
|
|
539
|
+
const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
|
|
540
|
+
const rel = path2.relative(path2.resolve(dir), resolved);
|
|
541
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
523
542
|
throw invalid(sessionId);
|
|
524
543
|
}
|
|
525
544
|
return resolved;
|
|
@@ -532,6 +551,9 @@ function invalid(sessionId) {
|
|
|
532
551
|
context: { reason: "path_traversal" }
|
|
533
552
|
});
|
|
534
553
|
}
|
|
554
|
+
|
|
555
|
+
// src/utils/ulid.ts
|
|
556
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
535
557
|
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
536
558
|
var ENCODING_LEN = ENCODING.length;
|
|
537
559
|
var TIME_LEN = 10;
|
|
@@ -547,7 +569,7 @@ function encodeTime(now, len) {
|
|
|
547
569
|
return str;
|
|
548
570
|
}
|
|
549
571
|
function encodeRandom(len) {
|
|
550
|
-
const bytes =
|
|
572
|
+
const bytes = randomBytes2(len);
|
|
551
573
|
let str = "";
|
|
552
574
|
for (let i = 0; i < len; i++) {
|
|
553
575
|
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
@@ -562,9 +584,14 @@ function ulid(seedTime = Date.now()) {
|
|
|
562
584
|
function truncate(s, max) {
|
|
563
585
|
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
564
586
|
}
|
|
587
|
+
|
|
588
|
+
// src/utils/wstack-paths.ts
|
|
589
|
+
import { createHash } from "node:crypto";
|
|
590
|
+
import * as os from "node:os";
|
|
591
|
+
import * as path3 from "node:path";
|
|
565
592
|
function projectSlug(absRoot) {
|
|
566
|
-
const base = slugify(
|
|
567
|
-
const hash = createHash("sha256").update(
|
|
593
|
+
const base = slugify(path3.basename(absRoot));
|
|
594
|
+
const hash = createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
568
595
|
return `${base}-${hash}`;
|
|
569
596
|
}
|
|
570
597
|
function slugify(name) {
|
|
@@ -572,8 +599,8 @@ function slugify(name) {
|
|
|
572
599
|
}
|
|
573
600
|
function wstackGlobalRoot() {
|
|
574
601
|
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
575
|
-
if (fromEnv && fromEnv.trim().length > 0) return
|
|
576
|
-
return
|
|
602
|
+
if (fromEnv && fromEnv.trim().length > 0) return path3.resolve(fromEnv);
|
|
603
|
+
return path3.join(os.homedir(), ".wrongstack");
|
|
577
604
|
}
|
|
578
605
|
|
|
579
606
|
// src/types/errors.ts
|
|
@@ -586,13 +613,53 @@ var ERROR_CODES = {
|
|
|
586
613
|
PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
|
|
587
614
|
PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
|
|
588
615
|
PROVIDER_CONTEXT_OVERFLOW: "PROVIDER_CONTEXT_OVERFLOW",
|
|
616
|
+
// Tool
|
|
617
|
+
TOOL_NOT_FOUND: "TOOL_NOT_FOUND",
|
|
618
|
+
TOOL_PERMISSION_DENIED: "TOOL_PERMISSION_DENIED",
|
|
619
|
+
TOOL_EXECUTION_FAILED: "TOOL_EXECUTION_FAILED",
|
|
620
|
+
TOOL_TIMEOUT: "TOOL_TIMEOUT",
|
|
621
|
+
TOOL_INPUT_INVALID: "TOOL_INPUT_INVALID",
|
|
622
|
+
// Config
|
|
623
|
+
CONFIG_INVALID: "CONFIG_INVALID",
|
|
624
|
+
CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
|
|
625
|
+
CONFIG_PARSE_FAILED: "CONFIG_PARSE_FAILED",
|
|
626
|
+
CONFIG_MIGRATION_NEEDED: "CONFIG_MIGRATION_NEEDED",
|
|
627
|
+
// Plugin
|
|
628
|
+
PLUGIN_LOAD_FAILED: "PLUGIN_LOAD_FAILED",
|
|
629
|
+
PLUGIN_API_MISMATCH: "PLUGIN_API_MISMATCH",
|
|
630
|
+
PLUGIN_MISSING_DEPENDENCY: "PLUGIN_MISSING_DEPENDENCY",
|
|
589
631
|
// Agent
|
|
590
632
|
AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
|
|
633
|
+
AGENT_CONTEXT_OVERFLOW: "AGENT_CONTEXT_OVERFLOW",
|
|
591
634
|
AGENT_ABORTED: "AGENT_ABORTED",
|
|
592
635
|
AGENT_RUN_FAILED: "AGENT_RUN_FAILED",
|
|
636
|
+
// Session
|
|
637
|
+
SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
|
|
638
|
+
SESSION_CORRUPTED: "SESSION_CORRUPTED",
|
|
639
|
+
SESSION_WRITE_FAILED: "SESSION_WRITE_FAILED",
|
|
640
|
+
// Container / Registry
|
|
641
|
+
CONTAINER_TOKEN_ALREADY_BOUND: "CONTAINER_TOKEN_ALREADY_BOUND",
|
|
642
|
+
CONTAINER_TOKEN_NOT_BOUND: "CONTAINER_TOKEN_NOT_BOUND",
|
|
643
|
+
CONTAINER_CIRCULAR_DEPENDENCY: "CONTAINER_CIRCULAR_DEPENDENCY",
|
|
644
|
+
REGISTRY_DUPLICATE: "REGISTRY_DUPLICATE",
|
|
645
|
+
REGISTRY_NOT_FOUND: "REGISTRY_NOT_FOUND",
|
|
646
|
+
REGISTRY_INVALID: "REGISTRY_INVALID",
|
|
593
647
|
// File system
|
|
594
648
|
FS_READ_FAILED: "FS_READ_FAILED",
|
|
595
|
-
|
|
649
|
+
FS_WRITE_FAILED: "FS_WRITE_FAILED",
|
|
650
|
+
FS_MKDIR_FAILED: "FS_MKDIR_FAILED",
|
|
651
|
+
FS_DELETE_FAILED: "FS_DELETE_FAILED",
|
|
652
|
+
FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
|
|
653
|
+
// SDD (Spec-Driven Development)
|
|
654
|
+
SDD_VALIDATION_FAILED: "SDD_VALIDATION_FAILED",
|
|
655
|
+
SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
|
|
656
|
+
SDD_INVALID_STATE: "SDD_INVALID_STATE",
|
|
657
|
+
SDD_NOT_READY: "SDD_NOT_READY",
|
|
658
|
+
// General
|
|
659
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
660
|
+
PARSE_FAILED: "PARSE_FAILED",
|
|
661
|
+
UNKNOWN: "UNKNOWN"
|
|
662
|
+
};
|
|
596
663
|
var WrongStackError = class extends Error {
|
|
597
664
|
code;
|
|
598
665
|
subsystem;
|
|
@@ -1271,10 +1338,30 @@ var ToolCapabilities = {
|
|
|
1271
1338
|
SHELL_RESTRICTED: "shell.restricted",
|
|
1272
1339
|
/** Can run a restricted project formatter/linter-style command. */
|
|
1273
1340
|
SHELL_EXEC: "shell.exec",
|
|
1341
|
+
/** Can read files inside the project (and possibly outside via symlinks if not guarded). */
|
|
1342
|
+
FS_READ: "fs.read",
|
|
1274
1343
|
/** Can write / modify / delete files inside the project. */
|
|
1275
1344
|
FS_WRITE: "fs.write",
|
|
1276
1345
|
/** Can write files outside the current project root (very high risk). */
|
|
1277
1346
|
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
1347
|
+
/** Can perform outbound network requests. */
|
|
1348
|
+
NET_OUTBOUND: "net.outbound",
|
|
1349
|
+
/** Can mutate in-memory session todos only. */
|
|
1350
|
+
SESSION_TODO: "session.todo",
|
|
1351
|
+
/** Can mutate in-memory session mode only. */
|
|
1352
|
+
SESSION_MODE: "session.mode",
|
|
1353
|
+
/** Can inspect registered tool metadata. */
|
|
1354
|
+
TOOL_META: "tool.meta",
|
|
1355
|
+
/** Can invoke arbitrary registered tools through a meta-tool. */
|
|
1356
|
+
TOOL_MUTATE_ANY: "tool.mutate.any",
|
|
1357
|
+
/** Can read persistent memory. */
|
|
1358
|
+
MEMORY_READ: "memory.read",
|
|
1359
|
+
/** Can write persistent memory. */
|
|
1360
|
+
MEMORY_WRITE: "memory.write",
|
|
1361
|
+
/** Can delete persistent memory. */
|
|
1362
|
+
MEMORY_DELETE: "memory.delete",
|
|
1363
|
+
/** Proxies tools from external MCP servers (unknown capability). */
|
|
1364
|
+
MCP_PROXY: "mcp.proxy",
|
|
1278
1365
|
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
1279
1366
|
SUBAGENT_SPAWN: "subagent.spawn",
|
|
1280
1367
|
/** Can inspect fleet/subagent coordination state without mutating it. */
|
|
@@ -1285,9 +1372,43 @@ var ToolCapabilities = {
|
|
|
1285
1372
|
COORDINATION_RESULT_SUBMIT: "coordination.result.submit",
|
|
1286
1373
|
/** Can read or write inter-agent mailbox messages. */
|
|
1287
1374
|
COORDINATION_MAIL: "coordination.mail",
|
|
1375
|
+
/** Can schedule, inspect, or cancel in-session cron jobs. */
|
|
1376
|
+
COORDINATION_CRON: "coordination.cron",
|
|
1377
|
+
/** Can mutate global or session configuration / trust state. */
|
|
1378
|
+
CONFIG_MUTATE: "config.mutate",
|
|
1288
1379
|
/** Can install packages or run package managers with side effects. */
|
|
1289
1380
|
PACKAGE_INSTALL: "package.install"
|
|
1290
1381
|
};
|
|
1382
|
+
var DANGEROUS_FOR_SUBAGENTS = [
|
|
1383
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
1384
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
1385
|
+
ToolCapabilities.SHELL_EXEC,
|
|
1386
|
+
ToolCapabilities.FS_WRITE,
|
|
1387
|
+
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
1388
|
+
ToolCapabilities.TOOL_MUTATE_ANY,
|
|
1389
|
+
ToolCapabilities.MEMORY_WRITE,
|
|
1390
|
+
ToolCapabilities.MEMORY_DELETE,
|
|
1391
|
+
ToolCapabilities.MCP_PROXY,
|
|
1392
|
+
ToolCapabilities.SUBAGENT_SPAWN,
|
|
1393
|
+
ToolCapabilities.CONFIG_MUTATE,
|
|
1394
|
+
ToolCapabilities.PACKAGE_INSTALL
|
|
1395
|
+
];
|
|
1396
|
+
var WIDE_SUBAGENT_CAPABILITIES = [
|
|
1397
|
+
ToolCapabilities.FS_READ,
|
|
1398
|
+
ToolCapabilities.FS_WRITE,
|
|
1399
|
+
ToolCapabilities.NET_OUTBOUND,
|
|
1400
|
+
ToolCapabilities.SESSION_TODO,
|
|
1401
|
+
ToolCapabilities.TOOL_META,
|
|
1402
|
+
ToolCapabilities.MEMORY_READ,
|
|
1403
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
1404
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
1405
|
+
ToolCapabilities.SHELL_EXEC,
|
|
1406
|
+
ToolCapabilities.PACKAGE_INSTALL,
|
|
1407
|
+
// Read-only fleet visibility (fleet_status) — peer awareness has no blast
|
|
1408
|
+
// radius and every fleet worker should coordinate around its peers.
|
|
1409
|
+
ToolCapabilities.COORDINATION_FLEET_READ,
|
|
1410
|
+
ToolCapabilities.COORDINATION_RESULT_SUBMIT
|
|
1411
|
+
];
|
|
1291
1412
|
|
|
1292
1413
|
// src/coordination/subagent-result-tool.ts
|
|
1293
1414
|
var SUBAGENT_STRUCTURED_REPORT_META_KEY = "subagentStructuredReport";
|
|
@@ -1304,9 +1425,11 @@ function normalizeSubagentStructuredReport(value) {
|
|
|
1304
1425
|
const filesExamined = stringArray(value["files_examined"]);
|
|
1305
1426
|
const nextSteps = stringArray(value["suggested_next_steps"]);
|
|
1306
1427
|
const confidence = value["confidence"];
|
|
1428
|
+
const completion = value["completion"];
|
|
1429
|
+
const remainingWork = optionalString(value["remaining_work"]);
|
|
1307
1430
|
if (!summary || summary.length > MAX_SUMMARY_CHARS || !findings || findings.length > MAX_FINDINGS || !filesExamined || filesExamined.length > MAX_FILES || !nextSteps || nextSteps.length > MAX_NEXT_STEPS || [...findings, ...filesExamined, ...nextSteps].some(
|
|
1308
1431
|
(item) => item.trim().length > MAX_ITEM_CHARS
|
|
1309
|
-
) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
|
1432
|
+
) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || completion !== void 0 && completion !== "complete" && completion !== "partial" || completion === "partial" && !remainingWork || remainingWork !== void 0 && remainingWork.length > MAX_SUMMARY_CHARS) {
|
|
1310
1433
|
return void 0;
|
|
1311
1434
|
}
|
|
1312
1435
|
const report = {
|
|
@@ -1314,7 +1437,9 @@ function normalizeSubagentStructuredReport(value) {
|
|
|
1314
1437
|
findings: findings.map((item) => item.trim()).filter(Boolean),
|
|
1315
1438
|
files_examined: filesExamined.map((item) => item.trim()).filter(Boolean),
|
|
1316
1439
|
confidence,
|
|
1317
|
-
suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean)
|
|
1440
|
+
suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean),
|
|
1441
|
+
...completion === "complete" || completion === "partial" ? { completion } : {},
|
|
1442
|
+
...remainingWork ? { remaining_work: remainingWork } : {}
|
|
1318
1443
|
};
|
|
1319
1444
|
return JSON.stringify(report).length <= MAX_SUBAGENT_STRUCTURED_REPORT_CHARS ? report : void 0;
|
|
1320
1445
|
}
|
|
@@ -1327,6 +1452,8 @@ function formatSubagentStructuredReport(report) {
|
|
|
1327
1452
|
const next = report.suggested_next_steps.length ? report.suggested_next_steps.map((item) => `- ${item}`).join("\n") : "- (none)";
|
|
1328
1453
|
return [
|
|
1329
1454
|
report.summary,
|
|
1455
|
+
report.completion ? `Completion: ${report.completion}` : void 0,
|
|
1456
|
+
report.remaining_work ? `Remaining work: ${report.remaining_work}` : void 0,
|
|
1330
1457
|
`
|
|
1331
1458
|
Findings:
|
|
1332
1459
|
${findings}`,
|
|
@@ -1336,7 +1463,7 @@ Files examined: ${files}`,
|
|
|
1336
1463
|
`
|
|
1337
1464
|
Suggested next steps:
|
|
1338
1465
|
${next}`
|
|
1339
|
-
].join("\n");
|
|
1466
|
+
].filter((line) => typeof line === "string").join("\n");
|
|
1340
1467
|
}
|
|
1341
1468
|
function makeSubagentResultTool() {
|
|
1342
1469
|
return {
|
|
@@ -1376,6 +1503,16 @@ function makeSubagentResultTool() {
|
|
|
1376
1503
|
maxItems: MAX_NEXT_STEPS,
|
|
1377
1504
|
items: { type: "string" },
|
|
1378
1505
|
description: "Concrete follow-ups; use an empty array when none remain."
|
|
1506
|
+
},
|
|
1507
|
+
completion: {
|
|
1508
|
+
type: "string",
|
|
1509
|
+
enum: ["complete", "partial"],
|
|
1510
|
+
description: "Use `partial` only when the task is intentionally stopping at a clean checkpoint and another worker should continue."
|
|
1511
|
+
},
|
|
1512
|
+
remaining_work: {
|
|
1513
|
+
type: "string",
|
|
1514
|
+
maxLength: MAX_SUMMARY_CHARS,
|
|
1515
|
+
description: "Concrete work left for a successor. Required by runtime validation when completion is `partial`."
|
|
1379
1516
|
}
|
|
1380
1517
|
},
|
|
1381
1518
|
required: ["summary", "findings", "files_examined", "confidence", "suggested_next_steps"],
|
|
@@ -1405,6 +1542,9 @@ function isRecord(value) {
|
|
|
1405
1542
|
function nonEmptyString(value) {
|
|
1406
1543
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1407
1544
|
}
|
|
1545
|
+
function optionalString(value) {
|
|
1546
|
+
return value === void 0 ? void 0 : nonEmptyString(value);
|
|
1547
|
+
}
|
|
1408
1548
|
function stringArray(value) {
|
|
1409
1549
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
1410
1550
|
}
|
|
@@ -1685,6 +1825,12 @@ var TOOLS = {
|
|
|
1685
1825
|
/** Web research. */
|
|
1686
1826
|
research: ["read", "grep", "glob", "search", "fetch", "mailbox"]
|
|
1687
1827
|
};
|
|
1828
|
+
|
|
1829
|
+
// src/coordination/agents/agent-prompts.ts
|
|
1830
|
+
import { readFileSync, statSync } from "node:fs";
|
|
1831
|
+
import * as os2 from "node:os";
|
|
1832
|
+
import * as path4 from "node:path";
|
|
1833
|
+
import { fileURLToPath } from "node:url";
|
|
1688
1834
|
var promptCache = /* @__PURE__ */ new Map();
|
|
1689
1835
|
var candidateCache = /* @__PURE__ */ new Map();
|
|
1690
1836
|
function agentPrompt(id) {
|
|
@@ -1696,7 +1842,7 @@ function agentPrompt(id) {
|
|
|
1696
1842
|
let resolved = "";
|
|
1697
1843
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
1698
1844
|
try {
|
|
1699
|
-
resolved = readFileSync(
|
|
1845
|
+
resolved = readFileSync(path4.join(dir, fileName), "utf8").trimEnd();
|
|
1700
1846
|
break;
|
|
1701
1847
|
} catch {
|
|
1702
1848
|
}
|
|
@@ -1705,20 +1851,20 @@ function agentPrompt(id) {
|
|
|
1705
1851
|
return resolved;
|
|
1706
1852
|
}
|
|
1707
1853
|
function agentPromptDirCandidates(envDir) {
|
|
1708
|
-
const globalRoot = process.env["WRONGSTACK_HOME"] ||
|
|
1854
|
+
const globalRoot = process.env["WRONGSTACK_HOME"] || path4.join(os2.homedir(), ".wrongstack");
|
|
1709
1855
|
const candKey = `${envDir}\0${globalRoot}`;
|
|
1710
1856
|
const cached = candidateCache.get(candKey);
|
|
1711
1857
|
if (cached !== void 0) return cached;
|
|
1712
|
-
const here =
|
|
1858
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
1713
1859
|
const explicitDir = envDir || void 0;
|
|
1714
1860
|
const candidates = [
|
|
1715
|
-
...explicitDir ? [
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1861
|
+
...explicitDir ? [path4.resolve(explicitDir)] : [],
|
|
1862
|
+
path4.join(globalRoot, "instructions", "agents"),
|
|
1863
|
+
path4.resolve(here, "../../../../instructions/agents"),
|
|
1864
|
+
path4.resolve(here, "../../../instructions/agents"),
|
|
1865
|
+
path4.resolve(here, "../../instructions/agents"),
|
|
1866
|
+
path4.resolve(here, "../instructions/agents"),
|
|
1867
|
+
path4.resolve(here, "instructions/agents")
|
|
1722
1868
|
];
|
|
1723
1869
|
const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
1724
1870
|
candidateCache.set(candKey, ordered);
|
|
@@ -3339,7 +3485,12 @@ var BrainDecisionQueue = class {
|
|
|
3339
3485
|
if (this.opts.timeoutMs && this.opts.timeoutMs > 0) {
|
|
3340
3486
|
entry.timer = setTimeout(() => {
|
|
3341
3487
|
this.pending.delete(request.id);
|
|
3342
|
-
resolve9(
|
|
3488
|
+
resolve9(
|
|
3489
|
+
this.opts.onTimeout?.(request) ?? {
|
|
3490
|
+
type: "deny",
|
|
3491
|
+
reason: "Brain human decision timed out."
|
|
3492
|
+
}
|
|
3493
|
+
);
|
|
3343
3494
|
}, this.opts.timeoutMs);
|
|
3344
3495
|
}
|
|
3345
3496
|
this.pending.set(request.id, entry);
|
|
@@ -3356,6 +3507,46 @@ var BrainDecisionQueue = class {
|
|
|
3356
3507
|
}
|
|
3357
3508
|
}
|
|
3358
3509
|
};
|
|
3510
|
+
function terminalPolicyDecision(request) {
|
|
3511
|
+
const recommended = request.options?.find((option) => option.recommended);
|
|
3512
|
+
if (recommended && (request.risk === "low" || request.risk === "medium")) {
|
|
3513
|
+
return {
|
|
3514
|
+
type: "answer",
|
|
3515
|
+
optionId: recommended.id,
|
|
3516
|
+
text: recommended.label,
|
|
3517
|
+
rationale: `Headless terminal policy: caller-recommended option accepted at ${request.risk} risk (no human available by configuration).`
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3520
|
+
if (request.fallback === "continue") {
|
|
3521
|
+
return {
|
|
3522
|
+
type: "answer",
|
|
3523
|
+
text: "Continue with the caller default.",
|
|
3524
|
+
rationale: "Headless terminal policy: request declares continue as its safe fallback."
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
return {
|
|
3528
|
+
type: "deny",
|
|
3529
|
+
reason: `Headless terminal policy: no safe automatic option for "${request.question}" (risk: ${request.risk}). The proposed action was not taken.`
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
var EscalationRoutingBrainArbiter = class {
|
|
3533
|
+
constructor(inner, queue, getMode) {
|
|
3534
|
+
this.inner = inner;
|
|
3535
|
+
this.queue = queue;
|
|
3536
|
+
this.getMode = getMode;
|
|
3537
|
+
}
|
|
3538
|
+
inner;
|
|
3539
|
+
queue;
|
|
3540
|
+
getMode;
|
|
3541
|
+
async decide(request) {
|
|
3542
|
+
const decision = await this.inner.decide(request);
|
|
3543
|
+
if (decision.type !== "ask_human") return decision;
|
|
3544
|
+
if (this.getMode() === "interactive" && this.queue) {
|
|
3545
|
+
return this.queue.requestHumanDecision(request);
|
|
3546
|
+
}
|
|
3547
|
+
return terminalPolicyDecision(request);
|
|
3548
|
+
}
|
|
3549
|
+
};
|
|
3359
3550
|
var HumanEscalatingBrainArbiter = class {
|
|
3360
3551
|
constructor(inner, queue) {
|
|
3361
3552
|
this.inner = inner;
|
|
@@ -3430,27 +3621,342 @@ function formatHumanPrompt(request) {
|
|
|
3430
3621
|
lines.push("", `Risk: ${request.risk}`);
|
|
3431
3622
|
return lines.join("\n");
|
|
3432
3623
|
}
|
|
3624
|
+
|
|
3625
|
+
// src/coordination/brain-ledger.ts
|
|
3626
|
+
import { appendFile, mkdir as mkdir2, readFile } from "node:fs/promises";
|
|
3627
|
+
import { dirname as dirname3 } from "node:path";
|
|
3628
|
+
var QUESTION_MAX = 200;
|
|
3629
|
+
var DETAIL_MAX = 240;
|
|
3630
|
+
var truncate2 = (s, max) => s === void 0 ? void 0 : s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
3631
|
+
function brainDecisionKey(source, question) {
|
|
3632
|
+
const normalized = question.toLowerCase().replace(/[^\s]*\d[^\s]*/g, "#").replace(/\s+/g, " ").trim().slice(0, 100);
|
|
3633
|
+
return `${source ?? "?"}:${normalized}`;
|
|
3634
|
+
}
|
|
3635
|
+
function fmtAge(ms) {
|
|
3636
|
+
const s = Math.max(0, Math.round(ms / 1e3));
|
|
3637
|
+
if (s < 60) return `${s}s ago`;
|
|
3638
|
+
if (s < 3600) return `${Math.round(s / 60)}m ago`;
|
|
3639
|
+
return `${Math.round(s / 3600)}h ago`;
|
|
3640
|
+
}
|
|
3641
|
+
function createLedgerGuardBrainArbiter(opts) {
|
|
3642
|
+
const denyAfter = opts.denyAfter ?? 3;
|
|
3643
|
+
return {
|
|
3644
|
+
async decide(request) {
|
|
3645
|
+
if (denyAfter > 0) {
|
|
3646
|
+
const streak = opts.failureStreakFor(request);
|
|
3647
|
+
if (streak >= denyAfter) {
|
|
3648
|
+
return {
|
|
3649
|
+
type: "deny",
|
|
3650
|
+
reason: `Ledger guard: the last ${streak} approved decisions of this kind all ended in observed failures \u2014 denying deterministically until a success is recorded. (${request.question.slice(0, 120)})`
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
return opts.inner.decide(request);
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
var BrainDecisionLedger = class {
|
|
3659
|
+
constructor(opts) {
|
|
3660
|
+
this.opts = opts;
|
|
3661
|
+
this.maxMemoryEntries = opts.maxMemoryEntries ?? 500;
|
|
3662
|
+
this.retryWindowMs = opts.interventionRetryWindowMs ?? 10 * 6e4;
|
|
3663
|
+
this.now = opts.now ?? Date.now;
|
|
3664
|
+
}
|
|
3665
|
+
opts;
|
|
3666
|
+
entries = [];
|
|
3667
|
+
outcomeByRequest = /* @__PURE__ */ new Map();
|
|
3668
|
+
/** subagentId → decision request ids awaiting that subagent's task result. */
|
|
3669
|
+
pendingBudgetOutcomes = /* @__PURE__ */ new Map();
|
|
3670
|
+
lastInterventionByKind = /* @__PURE__ */ new Map();
|
|
3671
|
+
unsubscribers = [];
|
|
3672
|
+
/** Serialized fire-and-forget writes — awaited by `stop()` (drain). */
|
|
3673
|
+
writeChain = Promise.resolve();
|
|
3674
|
+
dirReady = false;
|
|
3675
|
+
maxMemoryEntries;
|
|
3676
|
+
retryWindowMs;
|
|
3677
|
+
now;
|
|
3678
|
+
/**
|
|
3679
|
+
* Subscribe to the EventBus and seed the in-memory ring from the JSONL
|
|
3680
|
+
* tail. The returned promise resolves once the seed load finished
|
|
3681
|
+
* (best-effort — a missing/corrupt file is an empty history, not an error).
|
|
3682
|
+
*/
|
|
3683
|
+
async start() {
|
|
3684
|
+
const { events } = this.opts;
|
|
3685
|
+
this.unsubscribers.push(
|
|
3686
|
+
events.on("brain.decision_answered", (e) => {
|
|
3687
|
+
this.record({
|
|
3688
|
+
at: e.at,
|
|
3689
|
+
sessionId: e.sessionId,
|
|
3690
|
+
requestId: e.request.id,
|
|
3691
|
+
kind: "answered",
|
|
3692
|
+
source: e.request.source,
|
|
3693
|
+
risk: e.request.risk,
|
|
3694
|
+
question: truncate2(e.request.question, QUESTION_MAX),
|
|
3695
|
+
optionId: e.decision.type === "answer" ? e.decision.optionId : void 0,
|
|
3696
|
+
detail: e.decision.type === "answer" ? truncate2(e.decision.rationale ?? e.decision.text, DETAIL_MAX) : void 0
|
|
3697
|
+
});
|
|
3698
|
+
}),
|
|
3699
|
+
events.on("brain.decision_denied", (e) => {
|
|
3700
|
+
this.record({
|
|
3701
|
+
at: e.at,
|
|
3702
|
+
sessionId: e.sessionId,
|
|
3703
|
+
requestId: e.request.id,
|
|
3704
|
+
kind: "denied",
|
|
3705
|
+
source: e.request.source,
|
|
3706
|
+
risk: e.request.risk,
|
|
3707
|
+
question: truncate2(e.request.question, QUESTION_MAX),
|
|
3708
|
+
detail: e.decision.type === "deny" ? truncate2(e.decision.reason, DETAIL_MAX) : void 0
|
|
3709
|
+
});
|
|
3710
|
+
}),
|
|
3711
|
+
events.on("brain.decision_ask_human", (e) => {
|
|
3712
|
+
this.record({
|
|
3713
|
+
at: e.at,
|
|
3714
|
+
sessionId: e.sessionId,
|
|
3715
|
+
requestId: e.request.id,
|
|
3716
|
+
kind: "ask_human",
|
|
3717
|
+
source: e.request.source,
|
|
3718
|
+
risk: e.request.risk,
|
|
3719
|
+
question: truncate2(e.request.question, QUESTION_MAX)
|
|
3720
|
+
});
|
|
3721
|
+
}),
|
|
3722
|
+
events.on("brain.intervention", (e) => {
|
|
3723
|
+
this.record({
|
|
3724
|
+
at: e.at,
|
|
3725
|
+
sessionId: e.sessionId,
|
|
3726
|
+
requestId: e.request.id,
|
|
3727
|
+
kind: "intervention",
|
|
3728
|
+
source: e.request.source,
|
|
3729
|
+
risk: e.request.risk,
|
|
3730
|
+
question: truncate2(`[${e.kind}] ${e.request.question}`, QUESTION_MAX),
|
|
3731
|
+
detail: e.intervened ? "steered" : "observed only"
|
|
3732
|
+
});
|
|
3733
|
+
if (!e.intervened) return;
|
|
3734
|
+
const prev = this.lastInterventionByKind.get(e.kind);
|
|
3735
|
+
if (prev && e.at - prev.at <= this.retryWindowMs) {
|
|
3736
|
+
this.recordOutcome(
|
|
3737
|
+
prev.requestId,
|
|
3738
|
+
"failure",
|
|
3739
|
+
`same signal (${e.kind}) re-triggered ${fmtAge(e.at - prev.at).replace(" ago", " later")}`,
|
|
3740
|
+
e.sessionId
|
|
3741
|
+
);
|
|
3742
|
+
}
|
|
3743
|
+
this.lastInterventionByKind.set(e.kind, { requestId: e.request.id, at: e.at });
|
|
3744
|
+
}),
|
|
3745
|
+
// ── Outcome correlation: budget extensions ────────────────────────
|
|
3746
|
+
events.on("subagent.budget_extended", (e) => {
|
|
3747
|
+
const requestId = `director-budget-${e.subagentId}-${e.kind}`;
|
|
3748
|
+
if (!this.entries.some((entry) => entry.requestId === requestId)) return;
|
|
3749
|
+
let set = this.pendingBudgetOutcomes.get(e.subagentId);
|
|
3750
|
+
if (!set) {
|
|
3751
|
+
set = /* @__PURE__ */ new Set();
|
|
3752
|
+
this.pendingBudgetOutcomes.set(e.subagentId, set);
|
|
3753
|
+
}
|
|
3754
|
+
set.add(requestId);
|
|
3755
|
+
}),
|
|
3756
|
+
events.on("subagent.task_completed", (e) => {
|
|
3757
|
+
const pending = this.pendingBudgetOutcomes.get(e.subagentId);
|
|
3758
|
+
if (!pending) return;
|
|
3759
|
+
this.pendingBudgetOutcomes.delete(e.subagentId);
|
|
3760
|
+
const outcome = e.status === "success" ? "success" : "failure";
|
|
3761
|
+
for (const requestId of pending) {
|
|
3762
|
+
this.recordOutcome(
|
|
3763
|
+
requestId,
|
|
3764
|
+
outcome,
|
|
3765
|
+
`budget-extended subagent's task ${e.status}`,
|
|
3766
|
+
e.sessionId
|
|
3767
|
+
);
|
|
3768
|
+
}
|
|
3769
|
+
})
|
|
3770
|
+
);
|
|
3771
|
+
await this.load();
|
|
3772
|
+
}
|
|
3773
|
+
/** Unsubscribe and drain pending writes. Safe to call twice. */
|
|
3774
|
+
async stop() {
|
|
3775
|
+
for (const off of this.unsubscribers) off();
|
|
3776
|
+
this.unsubscribers.length = 0;
|
|
3777
|
+
await this.writeChain.catch(() => {
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3780
|
+
/**
|
|
3781
|
+
* Attach a real-world outcome to an earlier decision. Appended to the
|
|
3782
|
+
* JSONL, folded into future digests, and re-emitted as `brain.outcome`.
|
|
3783
|
+
*/
|
|
3784
|
+
recordOutcome(requestId, outcome, detail, sessionId) {
|
|
3785
|
+
this.outcomeByRequest.set(requestId, outcome);
|
|
3786
|
+
this.record({
|
|
3787
|
+
at: this.now(),
|
|
3788
|
+
sessionId,
|
|
3789
|
+
requestId,
|
|
3790
|
+
kind: "outcome",
|
|
3791
|
+
outcome,
|
|
3792
|
+
detail: truncate2(detail, DETAIL_MAX)
|
|
3793
|
+
});
|
|
3794
|
+
this.opts.events.emit("brain.outcome", {
|
|
3795
|
+
sessionId,
|
|
3796
|
+
requestId,
|
|
3797
|
+
outcome,
|
|
3798
|
+
detail,
|
|
3799
|
+
at: this.now()
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3802
|
+
/** Last `n` ledger rows, oldest first. */
|
|
3803
|
+
tail(n) {
|
|
3804
|
+
return this.entries.slice(-n);
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Consecutive most-recent FAILURE outcomes among answered decisions of
|
|
3808
|
+
* this request's group. Decisions whose outcome is still unknown are
|
|
3809
|
+
* skipped (they neither extend nor break the streak); the first known
|
|
3810
|
+
* SUCCESS ends it. Powers the deterministic ledger guard: "the last K
|
|
3811
|
+
* times we approved this, it went wrong — stop approving".
|
|
3812
|
+
*/
|
|
3813
|
+
failureStreakFor(request) {
|
|
3814
|
+
const key = brainDecisionKey(request.source, request.question);
|
|
3815
|
+
let streak = 0;
|
|
3816
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
3817
|
+
const e = this.entries[i];
|
|
3818
|
+
if (e?.kind !== "answered" || e.requestId === request.id || e.question === void 0 || brainDecisionKey(e.source, e.question) !== key) {
|
|
3819
|
+
continue;
|
|
3820
|
+
}
|
|
3821
|
+
const outcome = this.outcomeByRequest.get(e.requestId);
|
|
3822
|
+
if (outcome === void 0) continue;
|
|
3823
|
+
if (outcome === "success") break;
|
|
3824
|
+
streak += 1;
|
|
3825
|
+
}
|
|
3826
|
+
return streak;
|
|
3827
|
+
}
|
|
3828
|
+
/**
|
|
3829
|
+
* Short decision-history block for the LLM/council prompt: how similar
|
|
3830
|
+
* past decisions went and — when known — how they turned OUT. Returns
|
|
3831
|
+
* undefined when there is no relevant history (no prompt noise).
|
|
3832
|
+
*/
|
|
3833
|
+
digestFor(request) {
|
|
3834
|
+
const key = brainDecisionKey(request.source, request.question);
|
|
3835
|
+
const related = this.entries.filter(
|
|
3836
|
+
(e) => (e.kind === "answered" || e.kind === "denied") && e.requestId !== request.id && e.question !== void 0 && brainDecisionKey(e.source, e.question) === key
|
|
3837
|
+
);
|
|
3838
|
+
if (related.length === 0) return void 0;
|
|
3839
|
+
const recent = related.slice(-5).reverse();
|
|
3840
|
+
const now = this.now();
|
|
3841
|
+
const lines = recent.map((e) => {
|
|
3842
|
+
const what = e.kind === "denied" ? "denied" : e.optionId ? `chose [${e.optionId}]` : "answered";
|
|
3843
|
+
const outcome = this.outcomeByRequest.get(e.requestId);
|
|
3844
|
+
const suffix = outcome === "failure" ? " \u2192 later FAILED" : outcome === "success" ? " \u2192 later succeeded" : "";
|
|
3845
|
+
return `- ${fmtAge(now - e.at)}: ${what}${suffix}`;
|
|
3846
|
+
});
|
|
3847
|
+
const approved = related.filter((e) => e.kind === "answered");
|
|
3848
|
+
const failedAfter = approved.filter(
|
|
3849
|
+
(e) => this.outcomeByRequest.get(e.requestId) === "failure"
|
|
3850
|
+
).length;
|
|
3851
|
+
const summary = `Summary: ${related.length} similar decision(s); ${approved.length} answered${failedAfter > 0 ? ` (${failedAfter} later failed)` : ""}, ${related.length - approved.length} denied.`;
|
|
3852
|
+
return [...lines, summary].join("\n");
|
|
3853
|
+
}
|
|
3854
|
+
// ── Private ───────────────────────────────────────────────────────────
|
|
3855
|
+
record(entry) {
|
|
3856
|
+
this.entries.push(entry);
|
|
3857
|
+
if (this.entries.length > this.maxMemoryEntries) {
|
|
3858
|
+
const evictedRequestIds = /* @__PURE__ */ new Set();
|
|
3859
|
+
const evictCount = this.entries.length - this.maxMemoryEntries;
|
|
3860
|
+
const evicted = this.entries.splice(0, evictCount);
|
|
3861
|
+
for (const e of evicted) {
|
|
3862
|
+
if (e.requestId) evictedRequestIds.add(e.requestId);
|
|
3863
|
+
}
|
|
3864
|
+
if (evictedRequestIds.size > 0) {
|
|
3865
|
+
const remainingRequestIds = /* @__PURE__ */ new Set();
|
|
3866
|
+
for (const e of this.entries) {
|
|
3867
|
+
if (e.requestId) remainingRequestIds.add(e.requestId);
|
|
3868
|
+
}
|
|
3869
|
+
for (const id of evictedRequestIds) {
|
|
3870
|
+
if (!remainingRequestIds.has(id)) this.outcomeByRequest.delete(id);
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
this.append(entry);
|
|
3875
|
+
}
|
|
3876
|
+
append(entry) {
|
|
3877
|
+
this.writeChain = this.writeChain.then(async () => {
|
|
3878
|
+
if (!this.dirReady) {
|
|
3879
|
+
await mkdir2(dirname3(this.opts.filePath), { recursive: true });
|
|
3880
|
+
this.dirReady = true;
|
|
3881
|
+
}
|
|
3882
|
+
await appendFile(this.opts.filePath, `${JSON.stringify(entry)}
|
|
3883
|
+
`, "utf8");
|
|
3884
|
+
}).catch(() => {
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
/** Seed the in-memory ring from the JSONL tail (cross-session learning). */
|
|
3888
|
+
async load() {
|
|
3889
|
+
let raw;
|
|
3890
|
+
try {
|
|
3891
|
+
raw = await readFile(this.opts.filePath, "utf8");
|
|
3892
|
+
} catch {
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3895
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
3896
|
+
const seed = [];
|
|
3897
|
+
for (const line of lines.slice(-this.maxMemoryEntries)) {
|
|
3898
|
+
try {
|
|
3899
|
+
const parsed = JSON.parse(line);
|
|
3900
|
+
if (typeof parsed?.requestId === "string" && typeof parsed.at === "number") {
|
|
3901
|
+
seed.push(parsed);
|
|
3902
|
+
}
|
|
3903
|
+
} catch {
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
this.entries.unshift(...seed);
|
|
3907
|
+
if (this.entries.length > this.maxMemoryEntries) {
|
|
3908
|
+
this.entries.splice(0, this.entries.length - this.maxMemoryEntries);
|
|
3909
|
+
}
|
|
3910
|
+
for (const e of this.entries) {
|
|
3911
|
+
if (e.kind === "outcome" && e.outcome) this.outcomeByRequest.set(e.requestId, e.outcome);
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
};
|
|
3915
|
+
|
|
3916
|
+
// src/coordination/brain-monitor.ts
|
|
3917
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3918
|
+
var FILE_EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "patch", "multi_edit", "multiedit", "str_replace"]);
|
|
3919
|
+
function editedPath(input) {
|
|
3920
|
+
if (!input || typeof input !== "object") return void 0;
|
|
3921
|
+
const r = input;
|
|
3922
|
+
const candidate = r["file_path"] ?? r["path"] ?? r["filePath"] ?? r["file"];
|
|
3923
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
3924
|
+
}
|
|
3433
3925
|
var BrainMonitor = class {
|
|
3434
3926
|
constructor(opts) {
|
|
3435
3927
|
this.opts = opts;
|
|
3436
3928
|
this.toolFailureStreak = opts.toolFailureStreak ?? 3;
|
|
3437
3929
|
this.errorStormCount = opts.errorStormCount ?? 4;
|
|
3438
3930
|
this.errorStormWindowMs = opts.errorStormWindowMs ?? 6e4;
|
|
3931
|
+
this.stallMs = opts.stallMs ?? 3e5;
|
|
3932
|
+
this.stallCheckIntervalMs = opts.stallCheckIntervalMs ?? 3e4;
|
|
3933
|
+
this.fileChurnThreshold = opts.fileChurnThreshold ?? 5;
|
|
3934
|
+
this.fileChurnWindowMs = opts.fileChurnWindowMs ?? 6e5;
|
|
3439
3935
|
this.cooldownMs = opts.cooldownMs ?? 12e4;
|
|
3440
3936
|
}
|
|
3441
3937
|
opts;
|
|
3442
3938
|
failStreaks = /* @__PURE__ */ new Map();
|
|
3443
3939
|
errorTimestamps = [];
|
|
3940
|
+
editTimestamps = /* @__PURE__ */ new Map();
|
|
3444
3941
|
lastEngagedAt = /* @__PURE__ */ new Map();
|
|
3445
3942
|
unsubscribers = [];
|
|
3446
3943
|
engaging = false;
|
|
3944
|
+
activeRuns = 0;
|
|
3945
|
+
lastProgressAt = 0;
|
|
3946
|
+
stallTimer;
|
|
3447
3947
|
toolFailureStreak;
|
|
3448
3948
|
errorStormCount;
|
|
3449
3949
|
errorStormWindowMs;
|
|
3950
|
+
stallMs;
|
|
3951
|
+
stallCheckIntervalMs;
|
|
3952
|
+
fileChurnThreshold;
|
|
3953
|
+
fileChurnWindowMs;
|
|
3450
3954
|
cooldownMs;
|
|
3451
3955
|
start() {
|
|
3452
3956
|
this.unsubscribers.push(
|
|
3453
3957
|
this.opts.events.on("tool.executed", (e) => {
|
|
3958
|
+
this.lastProgressAt = Date.now();
|
|
3959
|
+
this.trackFileChurn(e.name, e.ok, e.input);
|
|
3454
3960
|
if (e.ok) {
|
|
3455
3961
|
this.failStreaks.delete(e.name);
|
|
3456
3962
|
return;
|
|
@@ -3470,6 +3976,38 @@ var BrainMonitor = class {
|
|
|
3470
3976
|
}
|
|
3471
3977
|
})
|
|
3472
3978
|
);
|
|
3979
|
+
if (this.stallMs > 0) {
|
|
3980
|
+
this.unsubscribers.push(
|
|
3981
|
+
this.opts.events.on("agent.run.started", () => {
|
|
3982
|
+
this.activeRuns += 1;
|
|
3983
|
+
this.lastProgressAt = Date.now();
|
|
3984
|
+
}),
|
|
3985
|
+
this.opts.events.on("agent.run.completed", () => {
|
|
3986
|
+
this.activeRuns = Math.max(0, this.activeRuns - 1);
|
|
3987
|
+
}),
|
|
3988
|
+
this.opts.events.on("agent.run.error", () => {
|
|
3989
|
+
this.activeRuns = Math.max(0, this.activeRuns - 1);
|
|
3990
|
+
}),
|
|
3991
|
+
this.opts.events.on("iteration.started", () => {
|
|
3992
|
+
this.lastProgressAt = Date.now();
|
|
3993
|
+
})
|
|
3994
|
+
);
|
|
3995
|
+
this.stallTimer = setInterval(() => {
|
|
3996
|
+
if (this.activeRuns === 0 || this.lastProgressAt === 0) return;
|
|
3997
|
+
const idleMs = Date.now() - this.lastProgressAt;
|
|
3998
|
+
if (idleMs < this.stallMs) return;
|
|
3999
|
+
this.lastProgressAt = Date.now();
|
|
4000
|
+
void this.engage("agent_stall", {
|
|
4001
|
+
question: `An active run has made no observable progress (no tool call or iteration) for ${Math.round(idleMs / 6e4)} minute(s). Should the agent be steered before more time is wasted?`,
|
|
4002
|
+
context: [
|
|
4003
|
+
`Active runs: ${this.activeRuns}`,
|
|
4004
|
+
`Idle for: ${Math.round(idleMs / 1e3)}s`,
|
|
4005
|
+
`Stall threshold: ${Math.round(this.stallMs / 1e3)}s`
|
|
4006
|
+
].join("\n")
|
|
4007
|
+
});
|
|
4008
|
+
}, this.stallCheckIntervalMs);
|
|
4009
|
+
this.stallTimer.unref?.();
|
|
4010
|
+
}
|
|
3473
4011
|
this.unsubscribers.push(
|
|
3474
4012
|
this.opts.events.on("error", (e) => {
|
|
3475
4013
|
const now = Date.now();
|
|
@@ -3494,6 +4032,37 @@ var BrainMonitor = class {
|
|
|
3494
4032
|
this.unsubscribers.length = 0;
|
|
3495
4033
|
this.failStreaks.clear();
|
|
3496
4034
|
this.errorTimestamps = [];
|
|
4035
|
+
this.editTimestamps.clear();
|
|
4036
|
+
if (this.stallTimer) {
|
|
4037
|
+
clearInterval(this.stallTimer);
|
|
4038
|
+
this.stallTimer = void 0;
|
|
4039
|
+
}
|
|
4040
|
+
this.activeRuns = 0;
|
|
4041
|
+
this.lastProgressAt = 0;
|
|
4042
|
+
}
|
|
4043
|
+
/** Sliding-window count of successful edits per file → churn signal. */
|
|
4044
|
+
trackFileChurn(toolName, ok, input) {
|
|
4045
|
+
if (!ok || !FILE_EDIT_TOOLS.has(toolName.toLowerCase())) return;
|
|
4046
|
+
const path20 = editedPath(input);
|
|
4047
|
+
if (!path20) return;
|
|
4048
|
+
const now = Date.now();
|
|
4049
|
+
const stamps = (this.editTimestamps.get(path20) ?? []).filter(
|
|
4050
|
+
(t) => now - t <= this.fileChurnWindowMs
|
|
4051
|
+
);
|
|
4052
|
+
stamps.push(now);
|
|
4053
|
+
if (stamps.length >= this.fileChurnThreshold) {
|
|
4054
|
+
this.editTimestamps.delete(path20);
|
|
4055
|
+
void this.engage("file_churn", {
|
|
4056
|
+
question: `The file "${path20}" has been edited ${stamps.length} times within ${Math.round(this.fileChurnWindowMs / 6e4)} minutes \u2014 the agent may be oscillating (edit/revert loop) instead of converging. Should it be steered?`,
|
|
4057
|
+
context: [
|
|
4058
|
+
`File: ${path20}`,
|
|
4059
|
+
`Edits in window: ${stamps.length}`,
|
|
4060
|
+
`Window: ${Math.round(this.fileChurnWindowMs / 1e3)}s`
|
|
4061
|
+
].join("\n")
|
|
4062
|
+
});
|
|
4063
|
+
return;
|
|
4064
|
+
}
|
|
4065
|
+
this.editTimestamps.set(path20, stamps);
|
|
3497
4066
|
}
|
|
3498
4067
|
async engage(kind, input) {
|
|
3499
4068
|
const last = this.lastEngagedAt.get(kind) ?? 0;
|
|
@@ -3502,7 +4071,7 @@ var BrainMonitor = class {
|
|
|
3502
4071
|
this.lastEngagedAt.set(kind, Date.now());
|
|
3503
4072
|
try {
|
|
3504
4073
|
const request = {
|
|
3505
|
-
id: `brainmon-${
|
|
4074
|
+
id: `brainmon-${randomUUID2()}`,
|
|
3506
4075
|
sessionId: this.opts.sessionId?.(),
|
|
3507
4076
|
source: "system",
|
|
3508
4077
|
question: input.question,
|
|
@@ -3565,6 +4134,11 @@ var BrainMonitor = class {
|
|
|
3565
4134
|
}
|
|
3566
4135
|
};
|
|
3567
4136
|
|
|
4137
|
+
// src/coordination/collab-debug.ts
|
|
4138
|
+
import { EventEmitter } from "node:events";
|
|
4139
|
+
import * as fsp2 from "node:fs/promises";
|
|
4140
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4141
|
+
|
|
3568
4142
|
// src/coordination/fleet-event-validation.ts
|
|
3569
4143
|
var ROLE_BY_EVENT = {
|
|
3570
4144
|
"bug.found": "bug-hunter",
|
|
@@ -3721,7 +4295,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
3721
4295
|
_timeoutTimer;
|
|
3722
4296
|
constructor(director, fleetBus, options) {
|
|
3723
4297
|
super();
|
|
3724
|
-
this.sessionId =
|
|
4298
|
+
this.sessionId = randomUUID3();
|
|
3725
4299
|
this.options = options;
|
|
3726
4300
|
this.director = director;
|
|
3727
4301
|
this.fleetBus = fleetBus;
|
|
@@ -3783,8 +4357,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
3783
4357
|
for (const filePath of allFiles) {
|
|
3784
4358
|
try {
|
|
3785
4359
|
const [content, stat9] = await Promise.all([
|
|
3786
|
-
|
|
3787
|
-
|
|
4360
|
+
fsp2.readFile(filePath, "utf8"),
|
|
4361
|
+
fsp2.stat(filePath)
|
|
3788
4362
|
]);
|
|
3789
4363
|
const ext = filePath.split(".").pop() ?? "";
|
|
3790
4364
|
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
@@ -3961,7 +4535,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
3961
4535
|
timeoutMs: budget.timeoutMs
|
|
3962
4536
|
};
|
|
3963
4537
|
const subagentId = await this.director.spawn(cfg);
|
|
3964
|
-
await this.director.assign({ id:
|
|
4538
|
+
await this.director.assign({ id: randomUUID3(), subagentId, description: taskBrief });
|
|
3965
4539
|
return subagentId;
|
|
3966
4540
|
}
|
|
3967
4541
|
buildBugHunterTask() {
|
|
@@ -4212,7 +4786,7 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
4212
4786
|
for (const file of this.snapshot.files) {
|
|
4213
4787
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
4214
4788
|
try {
|
|
4215
|
-
const stat9 = await
|
|
4789
|
+
const stat9 = await fsp2.stat(file.path);
|
|
4216
4790
|
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat9.mtimeMs > file.snapshotMtimeMs + 1;
|
|
4217
4791
|
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat9.size !== file.snapshotSizeBytes;
|
|
4218
4792
|
if (mtimeChanged || sizeChanged) {
|
|
@@ -4289,59 +4863,33 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
4289
4863
|
}
|
|
4290
4864
|
};
|
|
4291
4865
|
|
|
4866
|
+
// src/coordination/delegate-tool.ts
|
|
4867
|
+
import * as fsp3 from "node:fs/promises";
|
|
4868
|
+
import * as path5 from "node:path";
|
|
4869
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4870
|
+
|
|
4292
4871
|
// src/coordination/fleet.ts
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
var
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
prompt: agentPrompt("bug-hunter")
|
|
4309
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
4310
|
-
};
|
|
4311
|
-
var REFACTOR_PLANNER_AGENT = {
|
|
4312
|
-
id: "refactor-planner",
|
|
4313
|
-
name: "Refactor Planner",
|
|
4314
|
-
role: "refactor-planner",
|
|
4315
|
-
prompt: agentPrompt("refactor-planner")
|
|
4316
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
4317
|
-
};
|
|
4318
|
-
var SECURITY_SCANNER_AGENT = {
|
|
4319
|
-
id: "security-scanner",
|
|
4320
|
-
name: "Security Scanner",
|
|
4321
|
-
role: "security-scanner",
|
|
4322
|
-
prompt: agentPrompt("security-scanner")
|
|
4323
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
4324
|
-
};
|
|
4325
|
-
var SHADOW_AGENT = {
|
|
4326
|
-
id: "shadow-agent",
|
|
4327
|
-
name: "Shadow",
|
|
4328
|
-
role: "shadow-agent",
|
|
4329
|
-
prompt: agentPrompt("shadow-agent")
|
|
4330
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
4331
|
-
};
|
|
4332
|
-
var CRITIC_AGENT = {
|
|
4333
|
-
id: "critic",
|
|
4334
|
-
name: "Critic",
|
|
4335
|
-
role: "critic",
|
|
4336
|
-
prompt: agentPrompt("critic")
|
|
4337
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
4338
|
-
};
|
|
4872
|
+
function defineAgent(id, name, promptRole = id, provider) {
|
|
4873
|
+
return {
|
|
4874
|
+
id,
|
|
4875
|
+
name,
|
|
4876
|
+
role: id,
|
|
4877
|
+
prompt: agentPrompt(promptRole),
|
|
4878
|
+
...provider === void 0 ? {} : { provider }
|
|
4879
|
+
};
|
|
4880
|
+
}
|
|
4881
|
+
var AUDIT_LOG_AGENT = defineAgent("audit-log", "Audit Log");
|
|
4882
|
+
var BUG_HUNTER_AGENT = defineAgent("bug-hunter", "Bug Hunter");
|
|
4883
|
+
var REFACTOR_PLANNER_AGENT = defineAgent("refactor-planner", "Refactor Planner");
|
|
4884
|
+
var SECURITY_SCANNER_AGENT = defineAgent("security-scanner", "Security Scanner");
|
|
4885
|
+
var SHADOW_AGENT = defineAgent("shadow-agent", "Shadow");
|
|
4886
|
+
var CRITIC_AGENT = defineAgent("critic", "Critic");
|
|
4339
4887
|
var FLEET_ROSTER = {
|
|
4340
4888
|
"audit-log": AUDIT_LOG_AGENT,
|
|
4341
4889
|
"bug-hunter": BUG_HUNTER_AGENT,
|
|
4342
4890
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
4343
4891
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
4344
|
-
|
|
4892
|
+
critic: CRITIC_AGENT,
|
|
4345
4893
|
"shadow-agent": SHADOW_AGENT,
|
|
4346
4894
|
...Object.fromEntries(
|
|
4347
4895
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
@@ -4353,7 +4901,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
4353
4901
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
4354
4902
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
4355
4903
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
4356
|
-
|
|
4904
|
+
critic: { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
4357
4905
|
"shadow-agent": {
|
|
4358
4906
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
4359
4907
|
maxIterations: 2e3,
|
|
@@ -4390,41 +4938,11 @@ function applyRosterBudget(cfg) {
|
|
|
4390
4938
|
};
|
|
4391
4939
|
}
|
|
4392
4940
|
var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
|
|
4393
|
-
var CLINE_AGENT =
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
provider: "acp"
|
|
4399
|
-
};
|
|
4400
|
-
var GEMINI_CLI_AGENT = {
|
|
4401
|
-
id: "gemini-cli",
|
|
4402
|
-
name: "Gemini CLI",
|
|
4403
|
-
role: "gemini-cli",
|
|
4404
|
-
prompt: agentPrompt("acp-gemini-cli"),
|
|
4405
|
-
provider: "acp"
|
|
4406
|
-
};
|
|
4407
|
-
var COPILOT_AGENT = {
|
|
4408
|
-
id: "copilot",
|
|
4409
|
-
name: "GitHub Copilot",
|
|
4410
|
-
role: "copilot",
|
|
4411
|
-
prompt: agentPrompt("acp-copilot"),
|
|
4412
|
-
provider: "acp"
|
|
4413
|
-
};
|
|
4414
|
-
var OPENHANDS_AGENT = {
|
|
4415
|
-
id: "openhands",
|
|
4416
|
-
name: "OpenHands",
|
|
4417
|
-
role: "openhands",
|
|
4418
|
-
prompt: agentPrompt("acp-openhands"),
|
|
4419
|
-
provider: "acp"
|
|
4420
|
-
};
|
|
4421
|
-
var GOOSE_AGENT = {
|
|
4422
|
-
id: "goose",
|
|
4423
|
-
name: "Goose",
|
|
4424
|
-
role: "goose",
|
|
4425
|
-
prompt: agentPrompt("acp-goose"),
|
|
4426
|
-
provider: "acp"
|
|
4427
|
-
};
|
|
4941
|
+
var CLINE_AGENT = defineAgent("cline", "Cline", "acp-cline", "acp");
|
|
4942
|
+
var GEMINI_CLI_AGENT = defineAgent("gemini-cli", "Gemini CLI", "acp-gemini-cli", "acp");
|
|
4943
|
+
var COPILOT_AGENT = defineAgent("copilot", "GitHub Copilot", "acp-copilot", "acp");
|
|
4944
|
+
var OPENHANDS_AGENT = defineAgent("openhands", "OpenHands", "acp-openhands", "acp");
|
|
4945
|
+
var GOOSE_AGENT = defineAgent("goose", "Goose", "acp-goose", "acp");
|
|
4428
4946
|
var ACP_AGENTS = [
|
|
4429
4947
|
CLINE_AGENT,
|
|
4430
4948
|
GEMINI_CLI_AGENT,
|
|
@@ -4432,11 +4950,14 @@ var ACP_AGENTS = [
|
|
|
4432
4950
|
OPENHANDS_AGENT,
|
|
4433
4951
|
GOOSE_AGENT
|
|
4434
4952
|
];
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4953
|
+
var ACP_AGENT_BUDGET = {
|
|
4954
|
+
timeoutMs: 10 * 60 * 60 * 1e3,
|
|
4955
|
+
maxIterations: 8e3,
|
|
4956
|
+
maxToolCalls: 2e4
|
|
4957
|
+
};
|
|
4958
|
+
for (const agent of ACP_AGENTS) {
|
|
4959
|
+
FLEET_ROSTER_BUDGETS[agent.role] = { ...ACP_AGENT_BUDGET };
|
|
4960
|
+
}
|
|
4440
4961
|
var FLEET_ROSTER_WITHACP = {
|
|
4441
4962
|
...FLEET_ROSTER,
|
|
4442
4963
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
@@ -4444,7 +4965,7 @@ var FLEET_ROSTER_WITHACP = {
|
|
|
4444
4965
|
|
|
4445
4966
|
// src/coordination/delegate-tool.ts
|
|
4446
4967
|
function createDelegateTool(opts) {
|
|
4447
|
-
const defaultTimeoutMs = opts.defaultTimeoutMs ??
|
|
4968
|
+
const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
|
|
4448
4969
|
const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
|
|
4449
4970
|
const inputSchema = {
|
|
4450
4971
|
type: "object",
|
|
@@ -4503,24 +5024,39 @@ function createDelegateTool(opts) {
|
|
|
4503
5024
|
type: "number",
|
|
4504
5025
|
minimum: 0,
|
|
4505
5026
|
description: "Maximum estimated USD cost the subagent may incur. Unset = use the role/coordinator default."
|
|
5027
|
+
},
|
|
5028
|
+
maxHandoffs: {
|
|
5029
|
+
type: "number",
|
|
5030
|
+
minimum: 0,
|
|
5031
|
+
maximum: 8,
|
|
5032
|
+
description: "Maximum fresh-worker continuations after partial completion or budget exhaustion. Default 1; set 0 to disable. Each successor receives the prior structured/partial report and is told to continue only the remaining work."
|
|
4506
5033
|
}
|
|
4507
5034
|
},
|
|
4508
5035
|
required: ["task"]
|
|
4509
5036
|
};
|
|
4510
5037
|
return {
|
|
4511
5038
|
name: "delegate",
|
|
4512
|
-
description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and
|
|
4513
|
-
usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster
|
|
5039
|
+
description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and a generous, auto-extending budget. If it reports explicit partial completion or still exhausts a recoverable budget, delegate can continue the remaining work with a fresh subagent (bounded by `maxHandoffs`, default 1). Workers may ask the leader to spawn a parallel helper through the mailbox, but cannot recursively spawn agents themselves. Use specialized roster roles when possible; otherwise pass `name` + `task`. Call multiple delegates in parallel through the provider's parallel-tool-call surface for independent work.",
|
|
5040
|
+
usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster or pass `name` for a free-form coding agent. Defaults are intentionally generous; override `timeoutMs`, `maxIterations`, `maxToolCalls`, or `maxHandoffs` only when the task warrants it. Returns the final TaskResult plus a `handoffs` history when fresh workers continued partial work. Requires Director mode in normal CLI sessions.",
|
|
4514
5041
|
permission: "auto",
|
|
4515
5042
|
mutating: false,
|
|
5043
|
+
managesOwnTimeout: true,
|
|
4516
5044
|
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
4517
5045
|
inputSchema,
|
|
4518
|
-
async execute(input) {
|
|
5046
|
+
async execute(input, _ctx, execOpts) {
|
|
4519
5047
|
const sessionId = opts.directorRunId;
|
|
5048
|
+
const abortSignal = execOpts?.signal;
|
|
4520
5049
|
const i = input ?? {};
|
|
4521
5050
|
if (typeof i.task !== "string" || !i.task.trim()) {
|
|
4522
5051
|
return { ok: false, error: "`task` is required." };
|
|
4523
5052
|
}
|
|
5053
|
+
if (abortSignal?.aborted) {
|
|
5054
|
+
return {
|
|
5055
|
+
ok: false,
|
|
5056
|
+
stopReason: "aborted",
|
|
5057
|
+
error: "Delegation cancelled before spawn \u2014 the run was interrupted."
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
4524
5060
|
const target = i.role ?? i.name ?? "subagent";
|
|
4525
5061
|
try {
|
|
4526
5062
|
let director = await opts.host.ensureDirector();
|
|
@@ -4544,7 +5080,7 @@ function createDelegateTool(opts) {
|
|
|
4544
5080
|
error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
|
|
4545
5081
|
};
|
|
4546
5082
|
}
|
|
4547
|
-
cfg = instantiateRosterConfig(i.role, base);
|
|
5083
|
+
cfg = instantiateRosterConfig(i.role, base, i.timeoutMs, defaultTimeoutMs);
|
|
4548
5084
|
if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
|
|
4549
5085
|
if (i.provider) cfg.provider = i.provider;
|
|
4550
5086
|
if (i.model) cfg.model = i.model;
|
|
@@ -4583,136 +5119,175 @@ function createDelegateTool(opts) {
|
|
|
4583
5119
|
cfg.timeoutMs = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
|
|
4584
5120
|
}
|
|
4585
5121
|
opts.events?.emit("delegate.started", { sessionId, target, task: i.task });
|
|
4586
|
-
const subagentId = await director.spawn(cfg);
|
|
4587
|
-
const taskId = await director.assign({
|
|
4588
|
-
id: `${randomUUID()}`,
|
|
4589
|
-
description: i.task,
|
|
4590
|
-
subagentId
|
|
4591
|
-
});
|
|
4592
5122
|
const dir = director;
|
|
4593
|
-
const
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
resolve9(value);
|
|
4604
|
-
};
|
|
4605
|
-
const arm = () => {
|
|
4606
|
-
if (timer) clearTimeout(timer);
|
|
4607
|
-
timer = setTimeout(() => finish({ __timeout: true }), timeoutMs);
|
|
4608
|
-
};
|
|
4609
|
-
const bump = (e) => {
|
|
4610
|
-
if (e.subagentId === subagentId) arm();
|
|
4611
|
-
};
|
|
4612
|
-
const offTool = dir.fleet.filter("tool.executed", bump);
|
|
4613
|
-
const offIter = dir.fleet.filter("iteration.started", bump);
|
|
4614
|
-
const offProgress = dir.fleet.filter("tool.progress", bump);
|
|
4615
|
-
arm();
|
|
4616
|
-
dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __emptyResult: true })).catch(() => finish({ __timeout: true }));
|
|
4617
|
-
});
|
|
4618
|
-
if ("__timeout" in result) {
|
|
4619
|
-
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
4620
|
-
opts.events?.emit("delegate.completed", {
|
|
4621
|
-
sessionId,
|
|
4622
|
-
target,
|
|
4623
|
-
task: i.task,
|
|
4624
|
-
ok: false,
|
|
4625
|
-
status: "host_timeout",
|
|
4626
|
-
summary: `[${target}] timed out \u2014 no result within ${Math.round(timeoutMs / 1e3)}s`,
|
|
4627
|
-
durationMs: timeoutMs,
|
|
4628
|
-
iterations: partial2?.events ?? 0,
|
|
4629
|
-
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5123
|
+
const maxHandoffs = Math.min(8, Math.max(0, Math.floor(i.maxHandoffs ?? 1)));
|
|
5124
|
+
const handoffs = [];
|
|
5125
|
+
let delegatedTask = i.task;
|
|
5126
|
+
let handoffCount = 0;
|
|
5127
|
+
for (; ; ) {
|
|
5128
|
+
const attemptConfig = handoffCount === 0 ? cfg : freshHandoffConfig(cfg, i.role, handoffCount);
|
|
5129
|
+
const subagentId = await dir.spawn(attemptConfig);
|
|
5130
|
+
const taskId = await dir.assign({
|
|
5131
|
+
id: randomUUID4(),
|
|
5132
|
+
description: delegatedTask,
|
|
4630
5133
|
subagentId
|
|
4631
5134
|
});
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
stopReason: "host_timeout",
|
|
4635
|
-
error: `Subagent did not finish within ${timeoutMs}ms.`,
|
|
4636
|
-
hint: "Reduce scope of the next delegate, raise timeoutMs, or use spawn_subagent + await_tasks for long-running work.",
|
|
5135
|
+
const result = await awaitDelegateAttempt(
|
|
5136
|
+
dir,
|
|
4637
5137
|
subagentId,
|
|
4638
5138
|
taskId,
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
5139
|
+
timeoutMs,
|
|
5140
|
+
abortSignal
|
|
5141
|
+
);
|
|
5142
|
+
if ("__aborted" in result) {
|
|
5143
|
+
try {
|
|
5144
|
+
await dir.terminate(subagentId);
|
|
5145
|
+
} catch {
|
|
5146
|
+
}
|
|
5147
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5148
|
+
opts.events?.emit("delegate.completed", {
|
|
5149
|
+
sessionId,
|
|
5150
|
+
target,
|
|
5151
|
+
task: i.task,
|
|
5152
|
+
ok: false,
|
|
5153
|
+
status: "aborted",
|
|
5154
|
+
summary: `[${target}] aborted \u2014 the run was interrupted`,
|
|
5155
|
+
durationMs: 0,
|
|
5156
|
+
iterations: partial2?.events ?? 0,
|
|
5157
|
+
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5158
|
+
subagentId
|
|
5159
|
+
});
|
|
5160
|
+
return {
|
|
5161
|
+
ok: false,
|
|
5162
|
+
stopReason: "aborted",
|
|
5163
|
+
error: "Delegated task aborted \u2014 the run was interrupted.",
|
|
5164
|
+
subagentId,
|
|
5165
|
+
taskId,
|
|
5166
|
+
partial: partial2,
|
|
5167
|
+
...handoffs.length > 0 ? { handoffs } : {}
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
if ("__timeout" in result) {
|
|
5171
|
+
try {
|
|
5172
|
+
await dir.terminate(subagentId);
|
|
5173
|
+
} catch {
|
|
5174
|
+
}
|
|
5175
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5176
|
+
opts.events?.emit("delegate.completed", {
|
|
5177
|
+
sessionId,
|
|
5178
|
+
target,
|
|
5179
|
+
task: i.task,
|
|
5180
|
+
ok: false,
|
|
5181
|
+
status: "host_timeout",
|
|
5182
|
+
summary: `[${target}] timed out \u2014 no progress within ${Math.round(timeoutMs / 1e3)}s`,
|
|
5183
|
+
durationMs: timeoutMs,
|
|
5184
|
+
iterations: partial2?.events ?? 0,
|
|
5185
|
+
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5186
|
+
subagentId
|
|
5187
|
+
});
|
|
5188
|
+
return {
|
|
5189
|
+
ok: false,
|
|
5190
|
+
stopReason: "host_timeout",
|
|
5191
|
+
error: `Subagent timed out: it did not finish or report progress within ${timeoutMs}ms.`,
|
|
5192
|
+
hint: "Raise timeoutMs for unusually long single operations, or split the remaining work.",
|
|
5193
|
+
subagentId,
|
|
5194
|
+
taskId,
|
|
5195
|
+
partial: partial2,
|
|
5196
|
+
...handoffs.length > 0 ? { handoffs } : {}
|
|
5197
|
+
};
|
|
5198
|
+
}
|
|
5199
|
+
if ("__emptyResult" in result) {
|
|
5200
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5201
|
+
opts.events?.emit("delegate.completed", {
|
|
5202
|
+
sessionId,
|
|
5203
|
+
target,
|
|
5204
|
+
task: i.task,
|
|
5205
|
+
ok: false,
|
|
5206
|
+
status: "empty_result",
|
|
5207
|
+
summary: `[${target}] completed without a task result`,
|
|
5208
|
+
durationMs: 0,
|
|
5209
|
+
iterations: partial2?.events ?? 0,
|
|
5210
|
+
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5211
|
+
subagentId
|
|
5212
|
+
});
|
|
5213
|
+
return {
|
|
5214
|
+
ok: false,
|
|
5215
|
+
stopReason: "error",
|
|
5216
|
+
error: "Director returned no task result for the delegated task.",
|
|
5217
|
+
hint: "Check fleet state, then retry or reassign the task.",
|
|
5218
|
+
subagentId,
|
|
5219
|
+
taskId,
|
|
5220
|
+
partial: partial2,
|
|
5221
|
+
...handoffs.length > 0 ? { handoffs } : {}
|
|
5222
|
+
};
|
|
5223
|
+
}
|
|
5224
|
+
const partial = result.status === "success" ? void 0 : result.partial ? {
|
|
5225
|
+
lastAssistantText: result.partial.text,
|
|
5226
|
+
toolUsesObserved: result.toolCalls,
|
|
5227
|
+
events: result.iterations
|
|
5228
|
+
} : await readSubagentPartial(opts, subagentId);
|
|
5229
|
+
const continuation = continuationFor(result, partial, attemptConfig);
|
|
5230
|
+
if (continuation && handoffCount < maxHandoffs) {
|
|
5231
|
+
handoffs.push({
|
|
5232
|
+
fromSubagentId: result.subagentId,
|
|
5233
|
+
fromTaskId: result.taskId,
|
|
5234
|
+
status: result.status,
|
|
5235
|
+
errorKind: result.error?.kind,
|
|
5236
|
+
summary: continuation.summary,
|
|
5237
|
+
remainingWork: continuation.remainingWork
|
|
5238
|
+
});
|
|
5239
|
+
handoffCount += 1;
|
|
5240
|
+
delegatedTask = buildHandoffTask(i.task, continuation, handoffCount, maxHandoffs);
|
|
5241
|
+
continue;
|
|
5242
|
+
}
|
|
5243
|
+
const incomplete = result.report?.completion === "partial";
|
|
5244
|
+
const baseStopReason = incomplete ? "handoff_limit" : result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
|
|
5245
|
+
const errorKind = result.error?.kind;
|
|
5246
|
+
const retryable = result.error?.retryable;
|
|
5247
|
+
const backoffMs = result.error?.backoffMs;
|
|
5248
|
+
const summary = incomplete ? `[${target}] partial checkpoint \u2014 handoff limit reached after ${handoffCount} continuation(s)` : buildDelegateSummary(i.role, result);
|
|
5249
|
+
let costUsd;
|
|
5250
|
+
try {
|
|
5251
|
+
costUsd = dir.snapshot().perSubagent[result.subagentId]?.cost;
|
|
5252
|
+
} catch {
|
|
5253
|
+
costUsd = void 0;
|
|
5254
|
+
}
|
|
4644
5255
|
opts.events?.emit("delegate.completed", {
|
|
4645
5256
|
sessionId,
|
|
4646
5257
|
target,
|
|
4647
5258
|
task: i.task,
|
|
4648
|
-
ok:
|
|
4649
|
-
status: "
|
|
4650
|
-
summary
|
|
4651
|
-
durationMs:
|
|
4652
|
-
iterations:
|
|
4653
|
-
toolCalls:
|
|
4654
|
-
|
|
5259
|
+
ok: result.status === "success" && !incomplete,
|
|
5260
|
+
status: incomplete ? "partial" : result.status,
|
|
5261
|
+
summary,
|
|
5262
|
+
durationMs: result.durationMs,
|
|
5263
|
+
iterations: result.iterations,
|
|
5264
|
+
toolCalls: result.toolCalls,
|
|
5265
|
+
costUsd,
|
|
5266
|
+
subagentId: result.subagentId
|
|
4655
5267
|
});
|
|
4656
5268
|
return {
|
|
4657
|
-
ok:
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
5269
|
+
ok: result.status === "success" && !incomplete,
|
|
5270
|
+
status: incomplete ? "partial" : result.status,
|
|
5271
|
+
stopReason: baseStopReason,
|
|
5272
|
+
errorKind,
|
|
5273
|
+
retryable,
|
|
5274
|
+
backoffMs,
|
|
5275
|
+
subagentId: result.subagentId,
|
|
5276
|
+
taskId: result.taskId,
|
|
5277
|
+
result: result.result,
|
|
5278
|
+
report: result.report,
|
|
5279
|
+
error: result.error,
|
|
5280
|
+
iterations: result.iterations,
|
|
5281
|
+
toolCalls: result.toolCalls,
|
|
5282
|
+
durationMs: result.durationMs,
|
|
5283
|
+
...partial ? { partial } : {},
|
|
5284
|
+
...handoffs.length > 0 ? { handoffs } : {},
|
|
5285
|
+
...incomplete ? {
|
|
5286
|
+
hint: "A clean partial checkpoint remains. Reinvoke delegate with a larger maxHandoffs or assign report.remaining_work explicitly."
|
|
5287
|
+
} : hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {},
|
|
5288
|
+
summary
|
|
4664
5289
|
};
|
|
4665
5290
|
}
|
|
4666
|
-
const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
|
|
4667
|
-
const partial = result.status === "success" ? void 0 : result.partial ? {
|
|
4668
|
-
lastAssistantText: result.partial.text,
|
|
4669
|
-
toolUsesObserved: result.toolCalls,
|
|
4670
|
-
events: result.iterations
|
|
4671
|
-
} : await readSubagentPartial(opts, subagentId);
|
|
4672
|
-
const errorKind = result.error?.kind;
|
|
4673
|
-
const retryable = result.error?.retryable;
|
|
4674
|
-
const backoffMs = result.error?.backoffMs;
|
|
4675
|
-
const summary = buildDelegateSummary(i.role, result);
|
|
4676
|
-
let costUsd;
|
|
4677
|
-
try {
|
|
4678
|
-
costUsd = dir.snapshot().perSubagent[result.subagentId]?.cost;
|
|
4679
|
-
} catch {
|
|
4680
|
-
costUsd = void 0;
|
|
4681
|
-
}
|
|
4682
|
-
opts.events?.emit("delegate.completed", {
|
|
4683
|
-
sessionId,
|
|
4684
|
-
target,
|
|
4685
|
-
task: i.task,
|
|
4686
|
-
ok: result.status === "success",
|
|
4687
|
-
status: result.status,
|
|
4688
|
-
summary,
|
|
4689
|
-
durationMs: result.durationMs,
|
|
4690
|
-
iterations: result.iterations,
|
|
4691
|
-
toolCalls: result.toolCalls,
|
|
4692
|
-
costUsd,
|
|
4693
|
-
subagentId: result.subagentId
|
|
4694
|
-
});
|
|
4695
|
-
return {
|
|
4696
|
-
ok: result.status === "success",
|
|
4697
|
-
status: result.status,
|
|
4698
|
-
stopReason: baseStopReason,
|
|
4699
|
-
errorKind,
|
|
4700
|
-
retryable,
|
|
4701
|
-
backoffMs,
|
|
4702
|
-
subagentId: result.subagentId,
|
|
4703
|
-
taskId: result.taskId,
|
|
4704
|
-
result: result.result,
|
|
4705
|
-
report: result.report,
|
|
4706
|
-
error: result.error,
|
|
4707
|
-
iterations: result.iterations,
|
|
4708
|
-
toolCalls: result.toolCalls,
|
|
4709
|
-
durationMs: result.durationMs,
|
|
4710
|
-
...partial ? { partial } : {},
|
|
4711
|
-
...hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {},
|
|
4712
|
-
// Summary is included so callers (TUI, CLI renderer) can surface
|
|
4713
|
-
// it as a chat history line — the LLM also sees it for continuity.
|
|
4714
|
-
summary
|
|
4715
|
-
};
|
|
4716
5291
|
} catch (err) {
|
|
4717
5292
|
const message = toErrorMessage(err);
|
|
4718
5293
|
opts.events?.emit("delegate.completed", {
|
|
@@ -4735,13 +5310,106 @@ function createDelegateTool(opts) {
|
|
|
4735
5310
|
}
|
|
4736
5311
|
};
|
|
4737
5312
|
}
|
|
4738
|
-
function
|
|
5313
|
+
async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abortSignal) {
|
|
5314
|
+
return new Promise((resolve9) => {
|
|
5315
|
+
let settled = false;
|
|
5316
|
+
let timer;
|
|
5317
|
+
let offAbort = () => {
|
|
5318
|
+
};
|
|
5319
|
+
const finish = (value) => {
|
|
5320
|
+
if (settled) return;
|
|
5321
|
+
settled = true;
|
|
5322
|
+
if (timer) clearTimeout(timer);
|
|
5323
|
+
offTool();
|
|
5324
|
+
offIter();
|
|
5325
|
+
offProgress();
|
|
5326
|
+
offAbort();
|
|
5327
|
+
resolve9(value);
|
|
5328
|
+
};
|
|
5329
|
+
const arm = () => {
|
|
5330
|
+
if (timer) clearTimeout(timer);
|
|
5331
|
+
timer = setTimeout(() => finish({ __timeout: true }), timeoutMs);
|
|
5332
|
+
};
|
|
5333
|
+
const bump = (event) => {
|
|
5334
|
+
if (event.subagentId === subagentId) arm();
|
|
5335
|
+
};
|
|
5336
|
+
const offTool = director.fleet.filter("tool.executed", bump);
|
|
5337
|
+
const offIter = director.fleet.filter("iteration.started", bump);
|
|
5338
|
+
const offProgress = director.fleet.filter("tool.progress", bump);
|
|
5339
|
+
if (abortSignal) {
|
|
5340
|
+
const onAbort = () => finish({ __aborted: true });
|
|
5341
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
5342
|
+
offAbort = () => abortSignal.removeEventListener("abort", onAbort);
|
|
5343
|
+
if (abortSignal.aborted) onAbort();
|
|
5344
|
+
}
|
|
5345
|
+
arm();
|
|
5346
|
+
director.awaitTasks([taskId]).then((results) => finish(results[0] ?? { __emptyResult: true })).catch(() => finish({ __timeout: true }));
|
|
5347
|
+
});
|
|
5348
|
+
}
|
|
5349
|
+
function freshHandoffConfig(cfg, role, handoffCount) {
|
|
5350
|
+
return {
|
|
5351
|
+
...cfg,
|
|
5352
|
+
id: role ? `${role}-${randomUUID4().slice(0, 8)}` : `${cfg.name.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "subagent"}-handoff-${handoffCount}-${randomUUID4().slice(0, 6)}`
|
|
5353
|
+
};
|
|
5354
|
+
}
|
|
5355
|
+
function continuationFor(result, partial, config) {
|
|
5356
|
+
if (result.report?.completion === "partial" && result.report.remaining_work) {
|
|
5357
|
+
return {
|
|
5358
|
+
summary: result.report.summary,
|
|
5359
|
+
remainingWork: result.report.remaining_work,
|
|
5360
|
+
partialText: typeof result.result === "string" ? result.result : partial?.lastAssistantText
|
|
5361
|
+
};
|
|
5362
|
+
}
|
|
5363
|
+
const budgetKinds = /* @__PURE__ */ new Set([
|
|
5364
|
+
"budget_iterations",
|
|
5365
|
+
"budget_tool_calls",
|
|
5366
|
+
"budget_tokens",
|
|
5367
|
+
"budget_cost",
|
|
5368
|
+
"budget_timeout"
|
|
5369
|
+
]);
|
|
5370
|
+
const partialText = result.partial?.text ?? partial?.lastAssistantText;
|
|
5371
|
+
const mayHaveIsolatedWrites = config.worktree !== false && config.worktree !== "off" && (!config.tools || config.tools.some(
|
|
5372
|
+
(tool) => ["write", "edit", "replace", "patch", "bash", "exec", "install", "format"].includes(tool)
|
|
5373
|
+
));
|
|
5374
|
+
if (!mayHaveIsolatedWrites && result.status !== "success" && result.error?.kind && budgetKinds.has(result.error.kind) && partialText) {
|
|
5375
|
+
return {
|
|
5376
|
+
summary: `Prior worker stopped at ${result.error.kind} after ${result.iterations} iterations and ${result.toolCalls} tool calls.`,
|
|
5377
|
+
remainingWork: "Inspect the existing workspace and finish only the work that remains from the original task.",
|
|
5378
|
+
partialText
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
return void 0;
|
|
5382
|
+
}
|
|
5383
|
+
function buildHandoffTask(originalTask, continuation, handoffCount, maxHandoffs) {
|
|
5384
|
+
const partial = continuation.partialText?.trim().slice(-6e3);
|
|
5385
|
+
return [
|
|
5386
|
+
`Continue an oversized delegated task as fresh worker ${handoffCount} of ${maxHandoffs}.`,
|
|
5387
|
+
"Do not blindly repeat completed actions. Inspect the current workspace, git diff, tests, and any files named below before changing anything.",
|
|
5388
|
+
'If the remaining work is still too large, stop at a clean checkpoint and call submit_result with completion="partial" plus concrete remaining_work.',
|
|
5389
|
+
"If parallel help would materially improve the outcome and mail_send is available, send the leader an `ask` that names the exact helper task; do not spawn agents yourself.",
|
|
5390
|
+
`Original task:
|
|
5391
|
+
${originalTask}`,
|
|
5392
|
+
`Prior checkpoint summary:
|
|
5393
|
+
${continuation.summary}`,
|
|
5394
|
+
`Remaining work:
|
|
5395
|
+
${continuation.remainingWork}`,
|
|
5396
|
+
partial ? `Prior worker's last useful output:
|
|
5397
|
+
${partial}` : ""
|
|
5398
|
+
].filter(Boolean).join("\n\n");
|
|
5399
|
+
}
|
|
5400
|
+
function instantiateRosterConfig(role, base, requestedTimeoutMs, defaultTimeoutMs) {
|
|
4739
5401
|
const withBudget = applyRosterBudget({ ...base, role });
|
|
5402
|
+
const rosterTimeoutMs = FLEET_ROSTER_BUDGETS[role]?.timeoutMs;
|
|
4740
5403
|
return {
|
|
4741
5404
|
...withBudget,
|
|
5405
|
+
// Without an explicit host wait, apply the role's tuned multi-hour
|
|
5406
|
+
// wall-clock budget. With an explicit wait, leave this unset so the buffer
|
|
5407
|
+
// logic above derives a slightly shorter child budget without clamping the
|
|
5408
|
+
// role defaults used by ordinary calls.
|
|
5409
|
+
timeoutMs: requestedTimeoutMs === void 0 ? rosterTimeoutMs ?? defaultTimeoutMs : void 0,
|
|
4742
5410
|
// Give each spawn a fresh id so parallel or repeated delegates
|
|
4743
5411
|
// can use the same role safely.
|
|
4744
|
-
id: `${role}-${
|
|
5412
|
+
id: `${role}-${randomUUID4().slice(0, 8)}`
|
|
4745
5413
|
};
|
|
4746
5414
|
}
|
|
4747
5415
|
function hintForKind(kind, retryable, backoffMs, partial) {
|
|
@@ -4816,13 +5484,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
4816
5484
|
if (!opts.sessionsRoot) return void 0;
|
|
4817
5485
|
const candidates = [];
|
|
4818
5486
|
if (opts.directorRunId) {
|
|
4819
|
-
candidates.push(
|
|
5487
|
+
candidates.push(path5.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
4820
5488
|
} else {
|
|
4821
5489
|
try {
|
|
4822
|
-
const entries = await
|
|
5490
|
+
const entries = await fsp3.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
4823
5491
|
for (const entry of entries) {
|
|
4824
5492
|
if (entry.isDirectory()) {
|
|
4825
|
-
candidates.push(
|
|
5493
|
+
candidates.push(path5.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
4826
5494
|
}
|
|
4827
5495
|
}
|
|
4828
5496
|
} catch {
|
|
@@ -4832,7 +5500,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
4832
5500
|
for (const file of candidates) {
|
|
4833
5501
|
let raw;
|
|
4834
5502
|
try {
|
|
4835
|
-
raw = await
|
|
5503
|
+
raw = await fsp3.readFile(file, "utf8");
|
|
4836
5504
|
} catch {
|
|
4837
5505
|
continue;
|
|
4838
5506
|
}
|
|
@@ -4924,13 +5592,13 @@ function makeDependencyWatcherConfig(opts) {
|
|
|
4924
5592
|
const globPatterns = patterns.filter((p) => p.includes("*"));
|
|
4925
5593
|
const plainPatterns = patterns.filter((p) => !p.includes("*"));
|
|
4926
5594
|
function matchesPattern(filePath) {
|
|
4927
|
-
const
|
|
4928
|
-
if (plainPatterns.includes(
|
|
5595
|
+
const basename7 = filePath.split("/").pop()?.split("\\").pop() ?? "";
|
|
5596
|
+
if (plainPatterns.includes(basename7)) return true;
|
|
4929
5597
|
for (const gp of globPatterns) {
|
|
4930
5598
|
const regex = new RegExp(
|
|
4931
5599
|
"^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
|
|
4932
5600
|
);
|
|
4933
|
-
if (regex.test(
|
|
5601
|
+
if (regex.test(basename7)) return true;
|
|
4934
5602
|
}
|
|
4935
5603
|
return false;
|
|
4936
5604
|
}
|
|
@@ -5013,10 +5681,19 @@ function attachDepWatcherBridge(opts) {
|
|
|
5013
5681
|
unsub();
|
|
5014
5682
|
};
|
|
5015
5683
|
}
|
|
5684
|
+
|
|
5685
|
+
// src/coordination/director.ts
|
|
5686
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
5687
|
+
import * as fsp5 from "node:fs/promises";
|
|
5688
|
+
import * as path7 from "node:path";
|
|
5689
|
+
|
|
5690
|
+
// src/storage/director-state.ts
|
|
5691
|
+
import * as fsp4 from "node:fs/promises";
|
|
5692
|
+
import { hostname } from "node:os";
|
|
5016
5693
|
async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
5017
5694
|
let existing;
|
|
5018
5695
|
try {
|
|
5019
|
-
existing = await
|
|
5696
|
+
existing = await fsp4.readFile(lockPath, "utf8");
|
|
5020
5697
|
} catch {
|
|
5021
5698
|
}
|
|
5022
5699
|
if (existing) {
|
|
@@ -5040,7 +5717,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
|
5040
5717
|
}
|
|
5041
5718
|
async function releaseDirectorStateLock(lockPath) {
|
|
5042
5719
|
try {
|
|
5043
|
-
await
|
|
5720
|
+
await fsp4.unlink(lockPath);
|
|
5044
5721
|
} catch {
|
|
5045
5722
|
}
|
|
5046
5723
|
}
|
|
@@ -5260,6 +5937,11 @@ var FleetContextOverflowError = class extends Error {
|
|
|
5260
5937
|
this.observed = observed;
|
|
5261
5938
|
}
|
|
5262
5939
|
};
|
|
5940
|
+
|
|
5941
|
+
// src/utils/instruction-file.ts
|
|
5942
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
5943
|
+
import * as path6 from "node:path";
|
|
5944
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5263
5945
|
var textCache = /* @__PURE__ */ new Map();
|
|
5264
5946
|
var rootCandidates;
|
|
5265
5947
|
function readBundledInstructionText(relativePath) {
|
|
@@ -5268,7 +5950,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
5268
5950
|
let resolved = "";
|
|
5269
5951
|
for (const root of instructionRootCandidates()) {
|
|
5270
5952
|
try {
|
|
5271
|
-
resolved =
|
|
5953
|
+
resolved = readFileSync2(path6.join(root, relativePath), "utf8").trimEnd();
|
|
5272
5954
|
break;
|
|
5273
5955
|
} catch {
|
|
5274
5956
|
}
|
|
@@ -5284,18 +5966,18 @@ function renderInstructionTemplate(template, values) {
|
|
|
5284
5966
|
}
|
|
5285
5967
|
function instructionRootCandidates() {
|
|
5286
5968
|
if (rootCandidates !== void 0) return rootCandidates;
|
|
5287
|
-
const here =
|
|
5969
|
+
const here = path6.dirname(fileURLToPath2(import.meta.url));
|
|
5288
5970
|
const candidates = [
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5971
|
+
path6.resolve(here, "../../instructions"),
|
|
5972
|
+
path6.resolve(here, "../instructions"),
|
|
5973
|
+
path6.resolve(here, "instructions")
|
|
5292
5974
|
];
|
|
5293
5975
|
rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
|
|
5294
5976
|
return rootCandidates;
|
|
5295
5977
|
}
|
|
5296
5978
|
function isDirectory2(candidate) {
|
|
5297
5979
|
try {
|
|
5298
|
-
return
|
|
5980
|
+
return statSync2(candidate).isDirectory();
|
|
5299
5981
|
} catch {
|
|
5300
5982
|
return false;
|
|
5301
5983
|
}
|
|
@@ -5362,6 +6044,14 @@ function rosterSummaryFromConfigs(roster) {
|
|
|
5362
6044
|
return lines.join("\n");
|
|
5363
6045
|
}
|
|
5364
6046
|
|
|
6047
|
+
// src/coordination/director-tools.ts
|
|
6048
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
6049
|
+
import {
|
|
6050
|
+
claimReadyTask,
|
|
6051
|
+
listReadyTasks,
|
|
6052
|
+
updateTaskAssignment
|
|
6053
|
+
} from "@wrongstack/kanban";
|
|
6054
|
+
|
|
5365
6055
|
// src/coordination/dispatcher.ts
|
|
5366
6056
|
var DEFAULT_DISPATCH_ROLE = "executor";
|
|
5367
6057
|
var FALLBACK_DEFINITION = {
|
|
@@ -5621,7 +6311,7 @@ function instantiateRosterConfig2(role, base) {
|
|
|
5621
6311
|
...base,
|
|
5622
6312
|
// Roster entries are templates. A director may spawn several
|
|
5623
6313
|
// workers with the same role, so never reuse the template id.
|
|
5624
|
-
id: `${role}-${
|
|
6314
|
+
id: `${role}-${randomUUID5().slice(0, 8)}`
|
|
5625
6315
|
};
|
|
5626
6316
|
}
|
|
5627
6317
|
function makeQualityGateTool(director, roster) {
|
|
@@ -5720,7 +6410,7 @@ function makeQualityGateTool(director, roster) {
|
|
|
5720
6410
|
makeQualityGateSubagentConfig("verifier", roster, i.verifierWorktree ?? "auto")
|
|
5721
6411
|
);
|
|
5722
6412
|
const taskId = await director.assign({
|
|
5723
|
-
id:
|
|
6413
|
+
id: randomUUID5(),
|
|
5724
6414
|
subagentId,
|
|
5725
6415
|
description: buildVerifierTask(i, {
|
|
5726
6416
|
attempt,
|
|
@@ -5738,7 +6428,7 @@ function makeQualityGateTool(director, roster) {
|
|
|
5738
6428
|
makeQualityGateSubagentConfig("reviewer", roster, i.reviewerWorktree ?? "off")
|
|
5739
6429
|
);
|
|
5740
6430
|
const taskId = await director.assign({
|
|
5741
|
-
id:
|
|
6431
|
+
id: randomUUID5(),
|
|
5742
6432
|
subagentId,
|
|
5743
6433
|
description: buildReviewerTask(i, {
|
|
5744
6434
|
attempt,
|
|
@@ -5766,7 +6456,7 @@ function makeQualityGateTool(director, roster) {
|
|
|
5766
6456
|
};
|
|
5767
6457
|
}
|
|
5768
6458
|
const repairTaskId = await director.assign({
|
|
5769
|
-
id:
|
|
6459
|
+
id: randomUUID5(),
|
|
5770
6460
|
subagentId: i.repairSubagentId,
|
|
5771
6461
|
description: buildRepairTask(i, attempts[attempts.length - 1], attempt),
|
|
5772
6462
|
timeoutMs: i.timeoutMs
|
|
@@ -6034,7 +6724,7 @@ function makeAssignTool(director) {
|
|
|
6034
6724
|
async execute(input) {
|
|
6035
6725
|
const i = input;
|
|
6036
6726
|
const task = {
|
|
6037
|
-
id:
|
|
6727
|
+
id: randomUUID5(),
|
|
6038
6728
|
description: i.description,
|
|
6039
6729
|
subagentId: i.subagentId,
|
|
6040
6730
|
maxToolCalls: i.maxToolCalls,
|
|
@@ -6115,7 +6805,7 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
6115
6805
|
const leaseTtlMs = Math.max(1e3, Math.floor(i.leaseTtlMs ?? 5 * 60 * 1e3));
|
|
6116
6806
|
const claimedAt = nowIso();
|
|
6117
6807
|
const leaseSeeding = {
|
|
6118
|
-
leaseId:
|
|
6808
|
+
leaseId: randomUUID5(),
|
|
6119
6809
|
claimedAt,
|
|
6120
6810
|
heartbeatAt: claimedAt,
|
|
6121
6811
|
leaseExpiresAt: new Date(Date.now() + leaseTtlMs).toISOString()
|
|
@@ -6169,7 +6859,7 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
6169
6859
|
const config = buildKanbanSubagentConfig(claim.task, i, roster);
|
|
6170
6860
|
subagentId = await director.spawn(config);
|
|
6171
6861
|
runTaskId = await director.assign({
|
|
6172
|
-
id:
|
|
6862
|
+
id: randomUUID5(),
|
|
6173
6863
|
subagentId,
|
|
6174
6864
|
description: buildKanbanFleetTaskPrompt(claim.board, claim.task, {
|
|
6175
6865
|
heartbeatIntervalMs: i.heartbeatIntervalMs ?? 6e4,
|
|
@@ -7184,6 +7874,7 @@ function fallbackProfileChain(config, profileName) {
|
|
|
7184
7874
|
const chain = config.fallbackProfiles?.[profileName];
|
|
7185
7875
|
return Array.isArray(chain) ? chain.filter((ref) => parseModelRef(ref).model) : [];
|
|
7186
7876
|
}
|
|
7877
|
+
var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
|
|
7187
7878
|
|
|
7188
7879
|
// src/coordination/model-matrix.ts
|
|
7189
7880
|
var MATRIX_PHASE_KEYS = Object.keys(AGENTS_BY_PHASE);
|
|
@@ -7357,6 +8048,10 @@ function isValidMatrixKey(key) {
|
|
|
7357
8048
|
return matrixKeyKind(key) !== "unknown";
|
|
7358
8049
|
}
|
|
7359
8050
|
|
|
8051
|
+
// src/coordination/multi-agent-coordinator.ts
|
|
8052
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
8053
|
+
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
8054
|
+
|
|
7360
8055
|
// src/coordination/coordinator/error-classifier.ts
|
|
7361
8056
|
function classifySubagentError(err, hints = {}) {
|
|
7362
8057
|
if (err instanceof AgentError && err.cause) {
|
|
@@ -7551,7 +8246,7 @@ function formatRole(role) {
|
|
|
7551
8246
|
}
|
|
7552
8247
|
|
|
7553
8248
|
// src/coordination/multi-agent-coordinator.ts
|
|
7554
|
-
var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends
|
|
8249
|
+
var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends EventEmitter2 {
|
|
7555
8250
|
coordinatorId;
|
|
7556
8251
|
config;
|
|
7557
8252
|
runner;
|
|
@@ -7644,7 +8339,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7644
8339
|
return { ...subagent, name: display };
|
|
7645
8340
|
}
|
|
7646
8341
|
async spawn(subagent) {
|
|
7647
|
-
const id = subagent.id ||
|
|
8342
|
+
const id = subagent.id || randomUUID6();
|
|
7648
8343
|
const cfg = this.withNickname(subagent, id);
|
|
7649
8344
|
if (this.subagents.has(id)) {
|
|
7650
8345
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -8311,8 +9006,9 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
8311
9006
|
}
|
|
8312
9007
|
const subagent = this.subagents.get(result.subagentId);
|
|
8313
9008
|
if (subagent && subagent.status !== "stopped") {
|
|
8314
|
-
result.status === "failed" || result.status === "timeout";
|
|
9009
|
+
const failed = result.status === "failed" || result.status === "timeout";
|
|
8315
9010
|
subagent.status = "idle";
|
|
9011
|
+
void failed;
|
|
8316
9012
|
subagent.currentTask = void 0;
|
|
8317
9013
|
if (subagent.abortController.signal.aborted) {
|
|
8318
9014
|
subagent.abortController = new AbortController();
|
|
@@ -8701,6 +9397,9 @@ var Director = class _Director {
|
|
|
8701
9397
|
subagentBaseline;
|
|
8702
9398
|
/** See {@link DirectorOptions.taskResultNotifier}. */
|
|
8703
9399
|
taskResultNotifier;
|
|
9400
|
+
subagentIdleTimeoutMs;
|
|
9401
|
+
retireSubagentOnTaskComplete;
|
|
9402
|
+
subagentIdleTimers = /* @__PURE__ */ new Map();
|
|
8704
9403
|
/** Absolute path to the fleet's shared scratchpad directory, or null
|
|
8705
9404
|
* when none was configured. Exposed as a readonly getter for callers
|
|
8706
9405
|
* that need to surface the path to the user (e.g. the CLI logging
|
|
@@ -8796,13 +9495,15 @@ var Director = class _Director {
|
|
|
8796
9495
|
/** Prevents large `ask_subagent` answers from bloating the leader's context window. */
|
|
8797
9496
|
largeAnswerStore;
|
|
8798
9497
|
constructor(opts) {
|
|
8799
|
-
this.id = opts.config.coordinatorId ||
|
|
9498
|
+
this.id = opts.config.coordinatorId || randomUUID7();
|
|
8800
9499
|
this.brain = opts.brain;
|
|
8801
9500
|
this.manifestPath = opts.manifestPath;
|
|
8802
9501
|
this.roster = opts.roster;
|
|
8803
9502
|
this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
8804
9503
|
this.subagentBaseline = opts.subagentBaseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
8805
9504
|
this.taskResultNotifier = opts.taskResultNotifier;
|
|
9505
|
+
this.subagentIdleTimeoutMs = typeof opts.subagentIdleTimeoutMs === "number" && Number.isFinite(opts.subagentIdleTimeoutMs) && opts.subagentIdleTimeoutMs >= 0 ? opts.subagentIdleTimeoutMs : void 0;
|
|
9506
|
+
this.retireSubagentOnTaskComplete = opts.retireSubagentOnTaskComplete ?? true;
|
|
8806
9507
|
this.sharedScratchpadPath = opts.sharedScratchpadPath ?? null;
|
|
8807
9508
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
8808
9509
|
this.maxSpawnDepth = opts.fleetManager?.maxSpawnDepth ?? resolveMaxSpawnDepth(opts.maxSpawnDepth);
|
|
@@ -8813,7 +9514,7 @@ var Director = class _Director {
|
|
|
8813
9514
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
8814
9515
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
8815
9516
|
this.maxFleetTokens = opts.directorBudget?.maxTokens ?? Number.POSITIVE_INFINITY;
|
|
8816
|
-
this.maxBudgetExtensions = opts.maxBudgetExtensions ??
|
|
9517
|
+
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 12;
|
|
8817
9518
|
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
8818
9519
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
8819
9520
|
this.modelMatrix = opts.modelMatrix;
|
|
@@ -8833,7 +9534,7 @@ var Director = class _Director {
|
|
|
8833
9534
|
this.fleetManager = opts.fleetManager;
|
|
8834
9535
|
this.logger = opts.logger;
|
|
8835
9536
|
if (this.sharedScratchpadPath) {
|
|
8836
|
-
void
|
|
9537
|
+
void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
8837
9538
|
}
|
|
8838
9539
|
this.transport = new InMemoryBridgeTransport();
|
|
8839
9540
|
this.bridge = new InMemoryAgentBridge(
|
|
@@ -8947,6 +9648,10 @@ var Director = class _Director {
|
|
|
8947
9648
|
} else {
|
|
8948
9649
|
this.scheduleManifest();
|
|
8949
9650
|
}
|
|
9651
|
+
this.armSubagentIdleRetirement(
|
|
9652
|
+
r.subagentId,
|
|
9653
|
+
this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleTimeoutMs
|
|
9654
|
+
);
|
|
8950
9655
|
};
|
|
8951
9656
|
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
8952
9657
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
@@ -9058,7 +9763,7 @@ var Director = class _Director {
|
|
|
9058
9763
|
payload.deny();
|
|
9059
9764
|
return;
|
|
9060
9765
|
}
|
|
9061
|
-
if (decision.optionId === "stop"
|
|
9766
|
+
if (decision.optionId === "stop") {
|
|
9062
9767
|
payload.deny();
|
|
9063
9768
|
return;
|
|
9064
9769
|
}
|
|
@@ -9419,6 +10124,7 @@ var Director = class _Director {
|
|
|
9419
10124
|
});
|
|
9420
10125
|
this.scheduleManifest();
|
|
9421
10126
|
}
|
|
10127
|
+
this.armSubagentIdleRetirement(result.subagentId, this.subagentIdleTimeoutMs);
|
|
9422
10128
|
return result.subagentId;
|
|
9423
10129
|
}
|
|
9424
10130
|
/**
|
|
@@ -9441,7 +10147,7 @@ var Director = class _Director {
|
|
|
9441
10147
|
);
|
|
9442
10148
|
}
|
|
9443
10149
|
const msg = {
|
|
9444
|
-
id:
|
|
10150
|
+
id: randomUUID7(),
|
|
9445
10151
|
type: "task",
|
|
9446
10152
|
from: this.id,
|
|
9447
10153
|
to: subagentId,
|
|
@@ -9540,7 +10246,7 @@ var Director = class _Director {
|
|
|
9540
10246
|
})),
|
|
9541
10247
|
usage: this.usage.snapshot()
|
|
9542
10248
|
};
|
|
9543
|
-
await
|
|
10249
|
+
await fsp5.mkdir(path7.dirname(this.manifestPath), { recursive: true });
|
|
9544
10250
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
9545
10251
|
return this.manifestPath;
|
|
9546
10252
|
}
|
|
@@ -9567,6 +10273,8 @@ var Director = class _Director {
|
|
|
9567
10273
|
this.budgetFilter();
|
|
9568
10274
|
this.budgetFilter = null;
|
|
9569
10275
|
}
|
|
10276
|
+
for (const timer of this.subagentIdleTimers.values()) clearTimeout(timer);
|
|
10277
|
+
this.subagentIdleTimers.clear();
|
|
9570
10278
|
await this.coordinator.stopAll();
|
|
9571
10279
|
for (const b of this.subagentBridges.values()) {
|
|
9572
10280
|
await b.stop().catch((err) => this.logShutdownError("subagent_bridge_stop", err));
|
|
@@ -9583,6 +10291,7 @@ var Director = class _Director {
|
|
|
9583
10291
|
await this.stateCheckpoint.flush().catch((err) => this.logShutdownError("state_checkpoint_flush", err));
|
|
9584
10292
|
await this.stateCheckpoint.releaseLock().catch((err) => this.logShutdownError("state_checkpoint_lock_release", err));
|
|
9585
10293
|
}
|
|
10294
|
+
this.largeAnswerStore.clear();
|
|
9586
10295
|
}
|
|
9587
10296
|
/**
|
|
9588
10297
|
* Funnel for shutdown-phase errors. We can't throw — `shutdown()` is
|
|
@@ -9606,7 +10315,7 @@ var Director = class _Director {
|
|
|
9606
10315
|
* concurrency limit applies — the task may queue before running.
|
|
9607
10316
|
*/
|
|
9608
10317
|
async assign(task) {
|
|
9609
|
-
const taskWithId = task.id ? task : { ...task, id:
|
|
10318
|
+
const taskWithId = task.id ? task : { ...task, id: randomUUID7() };
|
|
9610
10319
|
if (this.workCompleteFlag) {
|
|
9611
10320
|
const synthetic = {
|
|
9612
10321
|
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
@@ -9670,7 +10379,7 @@ var Director = class _Director {
|
|
|
9670
10379
|
* rollups and persisted fleet task history.
|
|
9671
10380
|
*/
|
|
9672
10381
|
async assignInternal(task) {
|
|
9673
|
-
const taskWithId = task.id ? task : { ...task, id:
|
|
10382
|
+
const taskWithId = task.id ? task : { ...task, id: randomUUID7() };
|
|
9674
10383
|
this.internalTaskIds.add(taskWithId.id);
|
|
9675
10384
|
try {
|
|
9676
10385
|
await this.coordinator.assign(taskWithId);
|
|
@@ -9784,11 +10493,21 @@ var Director = class _Director {
|
|
|
9784
10493
|
}
|
|
9785
10494
|
async terminate(subagentId) {
|
|
9786
10495
|
await this.coordinator.stop(subagentId);
|
|
10496
|
+
void this.remove(subagentId).catch(
|
|
10497
|
+
(err) => this.logShutdownError("terminate_remove", err)
|
|
10498
|
+
);
|
|
9787
10499
|
}
|
|
9788
10500
|
async terminateAll() {
|
|
10501
|
+
const ids = this.status().subagents.map((s) => s.id);
|
|
9789
10502
|
await this.coordinator.stopAll();
|
|
10503
|
+
for (const id of ids) {
|
|
10504
|
+
void this.remove(id).catch(
|
|
10505
|
+
(err) => this.logShutdownError("terminate_all_remove", err)
|
|
10506
|
+
);
|
|
10507
|
+
}
|
|
9790
10508
|
}
|
|
9791
10509
|
async remove(subagentId) {
|
|
10510
|
+
this.clearSubagentIdleRetirement(subagentId);
|
|
9792
10511
|
await this.coordinator.remove(subagentId);
|
|
9793
10512
|
const bridge = this.subagentBridges.get(subagentId);
|
|
9794
10513
|
if (bridge) {
|
|
@@ -9809,6 +10528,34 @@ var Director = class _Director {
|
|
|
9809
10528
|
this.taskOwners.delete(subagentId);
|
|
9810
10529
|
this.taskDescriptions.delete(subagentId);
|
|
9811
10530
|
}
|
|
10531
|
+
clearSubagentIdleRetirement(subagentId) {
|
|
10532
|
+
const timer = this.subagentIdleTimers.get(subagentId);
|
|
10533
|
+
if (!timer) return;
|
|
10534
|
+
clearTimeout(timer);
|
|
10535
|
+
this.subagentIdleTimers.delete(subagentId);
|
|
10536
|
+
}
|
|
10537
|
+
/**
|
|
10538
|
+
* Arm one lifecycle timer per worker. The callback re-checks coordinator
|
|
10539
|
+
* state so a task assigned in the same turn always wins over retirement.
|
|
10540
|
+
*/
|
|
10541
|
+
armSubagentIdleRetirement(subagentId, delayMs) {
|
|
10542
|
+
this.clearSubagentIdleRetirement(subagentId);
|
|
10543
|
+
if (delayMs === void 0) return;
|
|
10544
|
+
const timer = setTimeout(() => {
|
|
10545
|
+
this.subagentIdleTimers.delete(subagentId);
|
|
10546
|
+
const entry = this.coordinator.getStatus().subagents.find((a) => a.id === subagentId);
|
|
10547
|
+
if (entry?.status !== "idle") return;
|
|
10548
|
+
if (this.coordinator.listPendingTasks().some((task) => task.subagentId === subagentId)) {
|
|
10549
|
+
this.armSubagentIdleRetirement(subagentId, this.subagentIdleTimeoutMs);
|
|
10550
|
+
return;
|
|
10551
|
+
}
|
|
10552
|
+
void this.remove(subagentId).catch(
|
|
10553
|
+
(err) => this.logShutdownError("subagent_idle_retirement", err)
|
|
10554
|
+
);
|
|
10555
|
+
}, delayMs);
|
|
10556
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
10557
|
+
this.subagentIdleTimers.set(subagentId, timer);
|
|
10558
|
+
}
|
|
9812
10559
|
status() {
|
|
9813
10560
|
const base = this.coordinator.getStatus();
|
|
9814
10561
|
return {
|
|
@@ -9860,10 +10607,10 @@ var Director = class _Director {
|
|
|
9860
10607
|
*/
|
|
9861
10608
|
async readSession(subagentId, tail) {
|
|
9862
10609
|
if (!this.sessionsRoot) return null;
|
|
9863
|
-
const filePath =
|
|
10610
|
+
const filePath = path7.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
9864
10611
|
let raw;
|
|
9865
10612
|
try {
|
|
9866
|
-
raw = await
|
|
10613
|
+
raw = await fsp5.readFile(filePath, "utf8");
|
|
9867
10614
|
} catch {
|
|
9868
10615
|
return null;
|
|
9869
10616
|
}
|
|
@@ -10028,6 +10775,21 @@ var Director = class _Director {
|
|
|
10028
10775
|
}
|
|
10029
10776
|
};
|
|
10030
10777
|
|
|
10778
|
+
// src/coordination/director-session.ts
|
|
10779
|
+
import * as path12 from "node:path";
|
|
10780
|
+
|
|
10781
|
+
// src/storage/session-store.ts
|
|
10782
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
10783
|
+
import { createReadStream } from "node:fs";
|
|
10784
|
+
import * as fsp9 from "node:fs/promises";
|
|
10785
|
+
import * as path11 from "node:path";
|
|
10786
|
+
import { createInterface } from "node:readline";
|
|
10787
|
+
|
|
10788
|
+
// src/storage/file-session-writer.ts
|
|
10789
|
+
import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
|
|
10790
|
+
import * as fsp6 from "node:fs/promises";
|
|
10791
|
+
import * as path8 from "node:path";
|
|
10792
|
+
|
|
10031
10793
|
// src/storage/session-helpers.ts
|
|
10032
10794
|
function userInputTitle(content) {
|
|
10033
10795
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -10043,7 +10805,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10043
10805
|
this.meta = meta;
|
|
10044
10806
|
this.events = events;
|
|
10045
10807
|
this.resumed = opts.resumed ?? false;
|
|
10046
|
-
this.manifestFile = opts.dir ?
|
|
10808
|
+
this.manifestFile = opts.dir ? path8.join(opts.dir, `${path8.basename(id)}.summary.json`) : "";
|
|
10047
10809
|
this.filePath = opts.filePath ?? "";
|
|
10048
10810
|
this.secretScrubber = opts.secretScrubber;
|
|
10049
10811
|
this.checkpointCas = opts.checkpointCas;
|
|
@@ -10583,7 +11345,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10583
11345
|
let removedCount = 0;
|
|
10584
11346
|
let targetCheckpointSeen = false;
|
|
10585
11347
|
try {
|
|
10586
|
-
fd = await
|
|
11348
|
+
fd = await fsp6.open(this.filePath, "r", 384);
|
|
10587
11349
|
while (true) {
|
|
10588
11350
|
const buf = Buffer.alloc(CHUNK_SIZE);
|
|
10589
11351
|
const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
|
|
@@ -10638,7 +11400,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10638
11400
|
await this.writeChain;
|
|
10639
11401
|
await this.handle.close();
|
|
10640
11402
|
const tmpPath = `${this.filePath}.rewind.tmp`;
|
|
10641
|
-
const src = await
|
|
11403
|
+
const src = await fsp6.open(this.filePath, "r", 384);
|
|
10642
11404
|
try {
|
|
10643
11405
|
const statResult = await src.stat();
|
|
10644
11406
|
const totalSize = statResult.size;
|
|
@@ -10654,7 +11416,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10654
11416
|
} else {
|
|
10655
11417
|
newlineAfterCheckpoint = totalSize;
|
|
10656
11418
|
}
|
|
10657
|
-
const writeFd = await
|
|
11419
|
+
const writeFd = await fsp6.open(tmpPath, "w", 384);
|
|
10658
11420
|
try {
|
|
10659
11421
|
let readOffset = 0;
|
|
10660
11422
|
while (readOffset < newlineAfterCheckpoint) {
|
|
@@ -10702,11 +11464,11 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10702
11464
|
await writeFd.close();
|
|
10703
11465
|
}
|
|
10704
11466
|
await src.close();
|
|
10705
|
-
await
|
|
10706
|
-
this.handle = await
|
|
11467
|
+
await fsp6.rename(tmpPath, this.filePath);
|
|
11468
|
+
this.handle = await fsp6.open(this.filePath, "a", 384);
|
|
10707
11469
|
} catch (err) {
|
|
10708
|
-
await
|
|
10709
|
-
this.handle = await
|
|
11470
|
+
await fsp6.unlink(tmpPath).catch(() => void 0);
|
|
11471
|
+
this.handle = await fsp6.open(this.filePath, "a", 384).catch(() => this.handle);
|
|
10710
11472
|
throw err;
|
|
10711
11473
|
}
|
|
10712
11474
|
await this.append({
|
|
@@ -10741,7 +11503,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10741
11503
|
provider: this.meta.provider ?? "unknown"
|
|
10742
11504
|
})}
|
|
10743
11505
|
`;
|
|
10744
|
-
await
|
|
11506
|
+
await fsp6.writeFile(this.filePath, record, "utf8");
|
|
10745
11507
|
this.activePromptIndex = null;
|
|
10746
11508
|
this.pendingFileSnapshots = [];
|
|
10747
11509
|
}
|
|
@@ -10786,6 +11548,12 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10786
11548
|
}
|
|
10787
11549
|
};
|
|
10788
11550
|
|
|
11551
|
+
// src/storage/session-checkpoint-cas.ts
|
|
11552
|
+
import { spawn } from "node:child_process";
|
|
11553
|
+
import { createHash as createHash2, randomUUID as randomUUID8 } from "node:crypto";
|
|
11554
|
+
import * as fsp7 from "node:fs/promises";
|
|
11555
|
+
import * as path9 from "node:path";
|
|
11556
|
+
|
|
10789
11557
|
// src/storage/storage-concurrency.ts
|
|
10790
11558
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
10791
11559
|
if (items.length === 0) return [];
|
|
@@ -10812,7 +11580,7 @@ var MAX_GIT_OUTPUT = 16 * 1024 * 1024;
|
|
|
10812
11580
|
var MAX_BLOB_BYTES = 64 * 1024 * 1024;
|
|
10813
11581
|
var MAX_CHECKPOINT_BYTES = 512 * 1024 * 1024;
|
|
10814
11582
|
function sha256(content) {
|
|
10815
|
-
return
|
|
11583
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
10816
11584
|
}
|
|
10817
11585
|
function isInside(root, target) {
|
|
10818
11586
|
const relative4 = path9.relative(root, target);
|
|
@@ -10868,9 +11636,9 @@ var SessionCheckpointCas = class {
|
|
|
10868
11636
|
return null;
|
|
10869
11637
|
}
|
|
10870
11638
|
try {
|
|
10871
|
-
const stat9 = await
|
|
11639
|
+
const stat9 = await fsp7.lstat(absolute);
|
|
10872
11640
|
if (stat9.isSymbolicLink()) {
|
|
10873
|
-
const linkTarget = await
|
|
11641
|
+
const linkTarget = await fsp7.readlink(absolute);
|
|
10874
11642
|
const resolvedLink = path9.resolve(path9.dirname(absolute), linkTarget);
|
|
10875
11643
|
if (path9.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
10876
11644
|
unresolved.push({
|
|
@@ -10900,7 +11668,7 @@ var SessionCheckpointCas = class {
|
|
|
10900
11668
|
return null;
|
|
10901
11669
|
}
|
|
10902
11670
|
scheduledBytes += stat9.size;
|
|
10903
|
-
const content = await
|
|
11671
|
+
const content = await fsp7.readFile(absolute);
|
|
10904
11672
|
const blobHash = sha256(content);
|
|
10905
11673
|
await this.putBlob(blobHash, content);
|
|
10906
11674
|
return { path: relative4, state: "file", blobHash, mode: stat9.mode & 511 };
|
|
@@ -10938,7 +11706,7 @@ var SessionCheckpointCas = class {
|
|
|
10938
11706
|
if (target === this.projectRoot) {
|
|
10939
11707
|
throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
|
|
10940
11708
|
}
|
|
10941
|
-
const targetStat = await
|
|
11709
|
+
const targetStat = await fsp7.stat(target);
|
|
10942
11710
|
if (!targetStat.isDirectory()) throw new Error(`Checkpoint target is not a directory: ${target}`);
|
|
10943
11711
|
const manifest = await this.loadManifest(checkpoint.manifestHash);
|
|
10944
11712
|
if (manifest.baseHead !== checkpoint.baseHead.toLowerCase() || manifest.coverage !== checkpoint.coverage || manifest.entries.length !== checkpoint.entryCount || manifest.unresolved.length !== checkpoint.unresolvedCount) {
|
|
@@ -10965,7 +11733,7 @@ var SessionCheckpointCas = class {
|
|
|
10965
11733
|
if (targetStatus.stdout.length > 0) {
|
|
10966
11734
|
throw new Error("Checkpoint target must be a clean checkout before materialization");
|
|
10967
11735
|
}
|
|
10968
|
-
const realTarget = await
|
|
11736
|
+
const realTarget = await fsp7.realpath(target);
|
|
10969
11737
|
const writtenFiles = [];
|
|
10970
11738
|
const deletedFiles = [];
|
|
10971
11739
|
const errors = [];
|
|
@@ -10995,24 +11763,24 @@ var SessionCheckpointCas = class {
|
|
|
10995
11763
|
for (const { entry, output, content } of prepared) {
|
|
10996
11764
|
try {
|
|
10997
11765
|
if (entry.state === "absent") {
|
|
10998
|
-
await
|
|
11766
|
+
await fsp7.unlink(output).catch((err) => {
|
|
10999
11767
|
if (err.code !== "ENOENT") throw err;
|
|
11000
11768
|
});
|
|
11001
11769
|
deletedFiles.push(entry.path);
|
|
11002
11770
|
continue;
|
|
11003
11771
|
}
|
|
11004
11772
|
if (entry.state === "symlink") {
|
|
11005
|
-
await
|
|
11773
|
+
await fsp7.unlink(output).catch((err) => {
|
|
11006
11774
|
if (err.code !== "ENOENT") throw err;
|
|
11007
11775
|
});
|
|
11008
|
-
await
|
|
11009
|
-
await
|
|
11776
|
+
await fsp7.mkdir(path9.dirname(output), { recursive: true });
|
|
11777
|
+
await fsp7.symlink(entry.linkTarget, output);
|
|
11010
11778
|
writtenFiles.push(entry.path);
|
|
11011
11779
|
continue;
|
|
11012
11780
|
}
|
|
11013
11781
|
if (!content) throw new Error("CAS blob was not prepared");
|
|
11014
11782
|
await atomicWrite(output, content, { mode: entry.mode });
|
|
11015
|
-
await
|
|
11783
|
+
await fsp7.chmod(output, entry.mode).catch(() => void 0);
|
|
11016
11784
|
writtenFiles.push(entry.path);
|
|
11017
11785
|
} catch (err) {
|
|
11018
11786
|
errors.push(`${entry.path}: ${toErrorMessage(err)}`);
|
|
@@ -11030,9 +11798,9 @@ var SessionCheckpointCas = class {
|
|
|
11030
11798
|
}
|
|
11031
11799
|
async putBlob(hash, content) {
|
|
11032
11800
|
const target = this.objectPath(hash);
|
|
11033
|
-
await
|
|
11801
|
+
await fsp7.mkdir(path9.dirname(target), { recursive: true });
|
|
11034
11802
|
try {
|
|
11035
|
-
const existing = await
|
|
11803
|
+
const existing = await fsp7.readFile(target);
|
|
11036
11804
|
if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
|
|
11037
11805
|
return;
|
|
11038
11806
|
} catch (err) {
|
|
@@ -11040,30 +11808,30 @@ var SessionCheckpointCas = class {
|
|
|
11040
11808
|
}
|
|
11041
11809
|
const temp = path9.join(
|
|
11042
11810
|
path9.dirname(target),
|
|
11043
|
-
`.${path9.basename(target)}.${process.pid}.${
|
|
11811
|
+
`.${path9.basename(target)}.${process.pid}.${randomUUID8()}.tmp`
|
|
11044
11812
|
);
|
|
11045
11813
|
let handle;
|
|
11046
11814
|
try {
|
|
11047
|
-
handle = await
|
|
11815
|
+
handle = await fsp7.open(temp, "wx", 384);
|
|
11048
11816
|
await handle.writeFile(content);
|
|
11049
11817
|
await handle.sync();
|
|
11050
11818
|
await handle.close();
|
|
11051
11819
|
handle = void 0;
|
|
11052
11820
|
try {
|
|
11053
|
-
await
|
|
11821
|
+
await fsp7.link(temp, target);
|
|
11054
11822
|
} catch (err) {
|
|
11055
11823
|
if (err.code !== "EEXIST") throw err;
|
|
11056
|
-
const existing = await
|
|
11824
|
+
const existing = await fsp7.readFile(target);
|
|
11057
11825
|
if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
|
|
11058
11826
|
}
|
|
11059
11827
|
} finally {
|
|
11060
11828
|
await handle?.close().catch(() => void 0);
|
|
11061
|
-
await
|
|
11829
|
+
await fsp7.unlink(temp).catch(() => void 0);
|
|
11062
11830
|
}
|
|
11063
11831
|
}
|
|
11064
11832
|
async putManifest(hash, encoded) {
|
|
11065
11833
|
const target = this.manifestPath(hash);
|
|
11066
|
-
const existing = await
|
|
11834
|
+
const existing = await fsp7.readFile(target).catch(() => null);
|
|
11067
11835
|
if (existing) {
|
|
11068
11836
|
if (sha256(existing) !== hash) throw new Error(`Corrupt checkpoint manifest collision: ${hash}`);
|
|
11069
11837
|
return;
|
|
@@ -11071,12 +11839,12 @@ var SessionCheckpointCas = class {
|
|
|
11071
11839
|
await atomicWrite(target, encoded, { mode: 384 });
|
|
11072
11840
|
}
|
|
11073
11841
|
async readBlob(hash) {
|
|
11074
|
-
const content = await
|
|
11842
|
+
const content = await fsp7.readFile(this.objectPath(hash));
|
|
11075
11843
|
if (sha256(content) !== hash) throw new Error(`CAS blob integrity check failed: ${hash}`);
|
|
11076
11844
|
return content;
|
|
11077
11845
|
}
|
|
11078
11846
|
async loadManifest(hash) {
|
|
11079
|
-
const raw = await
|
|
11847
|
+
const raw = await fsp7.readFile(this.manifestPath(hash));
|
|
11080
11848
|
if (sha256(raw) !== hash) throw new Error(`Checkpoint manifest integrity check failed: ${hash}`);
|
|
11081
11849
|
const parsed = JSON.parse(raw.toString("utf8"));
|
|
11082
11850
|
if (parsed.version !== 1 || typeof parsed.baseHead !== "string" || parsed.coverage !== "git-head-plus-dirty" || !Array.isArray(parsed.entries) || !Array.isArray(parsed.unresolved)) {
|
|
@@ -11106,7 +11874,7 @@ var SessionCheckpointCas = class {
|
|
|
11106
11874
|
let probe = output;
|
|
11107
11875
|
for (; ; ) {
|
|
11108
11876
|
try {
|
|
11109
|
-
const real = await
|
|
11877
|
+
const real = await fsp7.realpath(probe);
|
|
11110
11878
|
if (!isInside(realTarget, real)) throw new Error("path resolves through a symlink outside checkpoint target");
|
|
11111
11879
|
return output;
|
|
11112
11880
|
} catch (err) {
|
|
@@ -11166,12 +11934,17 @@ function defaultRunGit(args, cwd) {
|
|
|
11166
11934
|
child.on("close", (code) => resolve9(result(code ?? 1)));
|
|
11167
11935
|
});
|
|
11168
11936
|
}
|
|
11937
|
+
|
|
11938
|
+
// src/storage/session-resume-validation.ts
|
|
11939
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
11940
|
+
import * as fsp8 from "node:fs/promises";
|
|
11941
|
+
import * as path10 from "node:path";
|
|
11169
11942
|
var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
11170
11943
|
var VALIDATION_CONCURRENCY = 8;
|
|
11171
11944
|
var NOTICE_PATH_LIMIT = 20;
|
|
11172
11945
|
function isInside2(root, target) {
|
|
11173
|
-
const relative4 =
|
|
11174
|
-
return relative4 === "" || !relative4.startsWith("..") && !
|
|
11946
|
+
const relative4 = path10.relative(root, target);
|
|
11947
|
+
return relative4 === "" || !relative4.startsWith("..") && !path10.isAbsolute(relative4);
|
|
11175
11948
|
}
|
|
11176
11949
|
function errno(err) {
|
|
11177
11950
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -11182,7 +11955,7 @@ function latestObservations(events, projectRoot) {
|
|
|
11182
11955
|
if (event.type !== "file_observation" || typeof event.path !== "string" || event.path.length === 0 || typeof event.hash !== "string" || !/^[a-f\d]{64}$/i.test(event.hash)) {
|
|
11183
11956
|
continue;
|
|
11184
11957
|
}
|
|
11185
|
-
const normalized =
|
|
11958
|
+
const normalized = path10.resolve(projectRoot, event.path);
|
|
11186
11959
|
latest.set(normalized, {
|
|
11187
11960
|
path: normalized,
|
|
11188
11961
|
hash: event.hash.toLowerCase(),
|
|
@@ -11206,7 +11979,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
11206
11979
|
}
|
|
11207
11980
|
let realFile;
|
|
11208
11981
|
try {
|
|
11209
|
-
realFile = await
|
|
11982
|
+
realFile = await fsp8.realpath(observation.path);
|
|
11210
11983
|
} catch (err) {
|
|
11211
11984
|
if (errno(err) === "ENOENT") return { ...base, status: "deleted" };
|
|
11212
11985
|
return { ...base, status: "unreadable", detail: toErrorMessage(err) };
|
|
@@ -11219,7 +11992,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
11219
11992
|
};
|
|
11220
11993
|
}
|
|
11221
11994
|
try {
|
|
11222
|
-
const stat9 = await
|
|
11995
|
+
const stat9 = await fsp8.stat(realFile);
|
|
11223
11996
|
if (!stat9.isFile()) {
|
|
11224
11997
|
return { ...base, status: "unreadable", detail: "Path is no longer a regular file." };
|
|
11225
11998
|
}
|
|
@@ -11230,8 +12003,8 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
11230
12003
|
detail: `File now exceeds the ${MAX_REVALIDATE_BYTES}-byte validation limit.`
|
|
11231
12004
|
};
|
|
11232
12005
|
}
|
|
11233
|
-
const content = await
|
|
11234
|
-
const actualHash =
|
|
12006
|
+
const content = await fsp8.readFile(realFile, "utf8");
|
|
12007
|
+
const actualHash = createHash3("sha256").update(content, "utf8").digest("hex");
|
|
11235
12008
|
if (actualHash === observation.hash) return null;
|
|
11236
12009
|
return { ...base, status: "modified", actualHash };
|
|
11237
12010
|
} catch (err) {
|
|
@@ -11240,8 +12013,8 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
11240
12013
|
}
|
|
11241
12014
|
}
|
|
11242
12015
|
async function validateResumeFileObservations(events, projectRoot) {
|
|
11243
|
-
const lexicalRoot =
|
|
11244
|
-
const realRoot = await
|
|
12016
|
+
const lexicalRoot = path10.resolve(projectRoot);
|
|
12017
|
+
const realRoot = await fsp8.realpath(lexicalRoot).catch(() => lexicalRoot);
|
|
11245
12018
|
const observations = latestObservations(events, lexicalRoot);
|
|
11246
12019
|
const results = await mapWithConcurrency(
|
|
11247
12020
|
observations,
|
|
@@ -11256,9 +12029,9 @@ async function validateResumeFileObservations(events, projectRoot) {
|
|
|
11256
12029
|
}
|
|
11257
12030
|
function formatResumeValidationNotice(validation, projectRoot) {
|
|
11258
12031
|
if (validation.staleFiles.length === 0) return null;
|
|
11259
|
-
const root =
|
|
12032
|
+
const root = path10.resolve(projectRoot);
|
|
11260
12033
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
11261
|
-
const relative4 =
|
|
12034
|
+
const relative4 = path10.relative(root, entry.path);
|
|
11262
12035
|
const display = isInside2(root, entry.path) ? relative4 || "." : entry.path;
|
|
11263
12036
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
11264
12037
|
});
|
|
@@ -11363,6 +12136,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11363
12136
|
projectRoot;
|
|
11364
12137
|
checkpointCas;
|
|
11365
12138
|
isSessionInUse;
|
|
12139
|
+
logger;
|
|
11366
12140
|
/**
|
|
11367
12141
|
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
11368
12142
|
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
@@ -11380,14 +12154,26 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11380
12154
|
static LIST_SCAN_CONCURRENCY = 32;
|
|
11381
12155
|
constructor(opts) {
|
|
11382
12156
|
this.dir = opts.dir;
|
|
11383
|
-
this.projectRoot = opts.projectRoot ?
|
|
12157
|
+
this.projectRoot = opts.projectRoot ? path11.resolve(opts.projectRoot) : void 0;
|
|
11384
12158
|
this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
|
|
11385
|
-
rootDir:
|
|
12159
|
+
rootDir: path11.join(this.dir, "_cas"),
|
|
11386
12160
|
projectRoot: this.projectRoot
|
|
11387
12161
|
}) : void 0;
|
|
11388
12162
|
this.events = opts.events;
|
|
11389
12163
|
this.secretScrubber = opts.secretScrubber;
|
|
11390
12164
|
this.isSessionInUse = opts.isSessionInUse;
|
|
12165
|
+
this.logger = opts.logger;
|
|
12166
|
+
}
|
|
12167
|
+
/**
|
|
12168
|
+
* Emit a structured warning. Uses the configured Logger when available;
|
|
12169
|
+
* falls back to console.warn(JSON) so warnings are never silently dropped.
|
|
12170
|
+
*/
|
|
12171
|
+
logWarn(msg, ctx) {
|
|
12172
|
+
if (this.logger) {
|
|
12173
|
+
this.logger.warn(msg, ctx);
|
|
12174
|
+
} else {
|
|
12175
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
12176
|
+
}
|
|
11391
12177
|
}
|
|
11392
12178
|
/**
|
|
11393
12179
|
* Clear the load() cache. Useful for testing or when the caller knows
|
|
@@ -11436,17 +12222,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11436
12222
|
}
|
|
11437
12223
|
/** Absolute path to the session index file. */
|
|
11438
12224
|
get indexFile() {
|
|
11439
|
-
return
|
|
12225
|
+
return path11.join(this.dir, "_index.jsonl");
|
|
11440
12226
|
}
|
|
11441
12227
|
/** Join session ID to its absolute path within the store directory. */
|
|
11442
12228
|
sessionPath(id, ext) {
|
|
11443
12229
|
return sessionScopedPath(this.dir, id, ext);
|
|
11444
12230
|
}
|
|
11445
12231
|
shardManifestPath(shardKey) {
|
|
11446
|
-
return shardKey ?
|
|
12232
|
+
return shardKey ? path11.join(this.dir, shardKey, "_manifest.json") : path11.join(this.dir, "_manifest.json");
|
|
11447
12233
|
}
|
|
11448
12234
|
shardKeyForSessionId(id) {
|
|
11449
|
-
const dirName =
|
|
12235
|
+
const dirName = path11.dirname(id);
|
|
11450
12236
|
return dirName === "." ? "" : dirName;
|
|
11451
12237
|
}
|
|
11452
12238
|
invalidateShardManifestBySessionId(id) {
|
|
@@ -11458,7 +12244,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11458
12244
|
* subdirectory so sessions group naturally by day.
|
|
11459
12245
|
*/
|
|
11460
12246
|
async ensureShardDir(id) {
|
|
11461
|
-
const dirPath =
|
|
12247
|
+
const dirPath = path11.dirname(sessionScopedPath(this.dir, id, ""));
|
|
11462
12248
|
await ensureDir(dirPath);
|
|
11463
12249
|
return dirPath;
|
|
11464
12250
|
}
|
|
@@ -11489,12 +12275,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11489
12275
|
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
11490
12276
|
return writer;
|
|
11491
12277
|
} catch (err) {
|
|
11492
|
-
await handle.close().catch((e) =>
|
|
11493
|
-
|
|
11494
|
-
event: "session_store.handle_close_failed",
|
|
11495
|
-
|
|
11496
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11497
|
-
})));
|
|
12278
|
+
await handle.close().catch((e) => this.logWarn(
|
|
12279
|
+
"Session handle close failed",
|
|
12280
|
+
{ event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
|
|
12281
|
+
));
|
|
11498
12282
|
this.emitError(id, file, "create", toErrorMessage(err), true);
|
|
11499
12283
|
throw err;
|
|
11500
12284
|
}
|
|
@@ -11520,7 +12304,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11520
12304
|
}
|
|
11521
12305
|
const parentPrefix = parent.events.slice(0, boundary + 1);
|
|
11522
12306
|
const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;
|
|
11523
|
-
const checkpointHash =
|
|
12307
|
+
const checkpointHash = createHash4("sha256").update(parentPrefix.map((event) => JSON.stringify(event)).join("\n") + "\n", "utf8").digest("hex");
|
|
11524
12308
|
const inherited = parentPrefix.filter(inheritsIntoFork);
|
|
11525
12309
|
const writer = await this.create({
|
|
11526
12310
|
id: "",
|
|
@@ -11615,7 +12399,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11615
12399
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
11616
12400
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
11617
12401
|
// sessions root (where summaryFor() would never find it).
|
|
11618
|
-
dir:
|
|
12402
|
+
dir: path11.dirname(file),
|
|
11619
12403
|
filePath: file,
|
|
11620
12404
|
secretScrubber: this.secretScrubber,
|
|
11621
12405
|
checkpointCas: this.checkpointCas,
|
|
@@ -11625,12 +12409,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11625
12409
|
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
11626
12410
|
return { writer, data: resumedData };
|
|
11627
12411
|
} catch (err) {
|
|
11628
|
-
await handle.close().catch((e) =>
|
|
11629
|
-
|
|
11630
|
-
event: "session_store.handle_close_failed",
|
|
11631
|
-
|
|
11632
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11633
|
-
})));
|
|
12412
|
+
await handle.close().catch((e) => this.logWarn(
|
|
12413
|
+
"Session handle close failed",
|
|
12414
|
+
{ event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
|
|
12415
|
+
));
|
|
11634
12416
|
this.emitError(id, file, "resume", toErrorMessage(err), true);
|
|
11635
12417
|
throw err;
|
|
11636
12418
|
}
|
|
@@ -12170,7 +12952,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12170
12952
|
return entry;
|
|
12171
12953
|
}
|
|
12172
12954
|
async collectSessionFilesInShard(shardKey) {
|
|
12173
|
-
const dir = shardKey ?
|
|
12955
|
+
const dir = shardKey ? path11.join(this.dir, shardKey) : this.dir;
|
|
12174
12956
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
12175
12957
|
return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
|
|
12176
12958
|
}
|
|
@@ -12193,13 +12975,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12193
12975
|
if (entry.name === "_index.jsonl") continue;
|
|
12194
12976
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
12195
12977
|
const id = prefix ? `${prefix}/${base}` : base;
|
|
12196
|
-
files.push({ id, filePath:
|
|
12978
|
+
files.push({ id, filePath: path11.join(dir, entry.name) });
|
|
12197
12979
|
}
|
|
12198
12980
|
}
|
|
12199
12981
|
const childFileArrays = await Promise.all(
|
|
12200
12982
|
dirEntries.map((entry) => {
|
|
12201
12983
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
12202
|
-
return this.collectSessionFiles(
|
|
12984
|
+
return this.collectSessionFiles(path11.join(dir, entry.name), childPrefix, depth + 1);
|
|
12203
12985
|
})
|
|
12204
12986
|
);
|
|
12205
12987
|
return [...childFileArrays.flat(), ...files];
|
|
@@ -12232,7 +13014,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12232
13014
|
const childIdArrays = await Promise.all(
|
|
12233
13015
|
dirEntries.map((entry) => {
|
|
12234
13016
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
12235
|
-
return this.collectSessionIds(
|
|
13017
|
+
return this.collectSessionIds(path11.join(dir, entry.name), childPrefix, depth + 1);
|
|
12236
13018
|
})
|
|
12237
13019
|
);
|
|
12238
13020
|
return [...childIdArrays.flat(), ...fileIds];
|
|
@@ -12251,13 +13033,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12251
13033
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
12252
13034
|
const msg = toErrorMessage(err);
|
|
12253
13035
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
event: "session_store.manifest_write_failed",
|
|
12257
|
-
|
|
12258
|
-
message: msg,
|
|
12259
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12260
|
-
}));
|
|
13036
|
+
this.logWarn(
|
|
13037
|
+
"Session manifest write failed",
|
|
13038
|
+
{ event: "session_store.manifest_write_failed", sessionId: id, message: msg }
|
|
13039
|
+
);
|
|
12261
13040
|
});
|
|
12262
13041
|
outcome = "failure";
|
|
12263
13042
|
errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
|
|
@@ -12349,9 +13128,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12349
13128
|
async deleteSession(id) {
|
|
12350
13129
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
12351
13130
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
12352
|
-
const shardDir =
|
|
12353
|
-
const base =
|
|
12354
|
-
const sessDir =
|
|
13131
|
+
const shardDir = path11.dirname(jsonlPath);
|
|
13132
|
+
const base = path11.basename(id);
|
|
13133
|
+
const sessDir = path11.join(shardDir, base);
|
|
12355
13134
|
const deletions = [
|
|
12356
13135
|
fsp9.unlink(jsonlPath),
|
|
12357
13136
|
fsp9.unlink(summaryPath),
|
|
@@ -12392,7 +13171,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12392
13171
|
*/
|
|
12393
13172
|
async readActiveSessionId() {
|
|
12394
13173
|
try {
|
|
12395
|
-
const raw = await fsp9.readFile(
|
|
13174
|
+
const raw = await fsp9.readFile(path11.join(this.dir, "active.json"), "utf8");
|
|
12396
13175
|
const active = JSON.parse(raw);
|
|
12397
13176
|
return active.sessionId ?? null;
|
|
12398
13177
|
} catch {
|
|
@@ -12455,7 +13234,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12455
13234
|
const activeSessionId = await this.readActiveSessionId();
|
|
12456
13235
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
12457
13236
|
const pruneFile = async (dir, name, prefix) => {
|
|
12458
|
-
const jsonlPath =
|
|
13237
|
+
const jsonlPath = path11.join(dir, name);
|
|
12459
13238
|
try {
|
|
12460
13239
|
const stat9 = await fsp9.stat(jsonlPath);
|
|
12461
13240
|
if (stat9.mtimeMs >= cutoff) return;
|
|
@@ -12475,7 +13254,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12475
13254
|
continue;
|
|
12476
13255
|
}
|
|
12477
13256
|
if (!entry.isDirectory()) continue;
|
|
12478
|
-
const dateDir =
|
|
13257
|
+
const dateDir = path11.join(this.dir, entry.name);
|
|
12479
13258
|
const files = await fsp9.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
12480
13259
|
for (const file of files) {
|
|
12481
13260
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
@@ -12487,7 +13266,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12487
13266
|
}
|
|
12488
13267
|
for (const entry of entries) {
|
|
12489
13268
|
if (!entry.isDirectory()) continue;
|
|
12490
|
-
const dateDir =
|
|
13269
|
+
const dateDir = path11.join(this.dir, entry.name);
|
|
12491
13270
|
try {
|
|
12492
13271
|
const remaining = await fsp9.readdir(dateDir);
|
|
12493
13272
|
if (remaining.length === 0) {
|
|
@@ -12618,9 +13397,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
12618
13397
|
let dir;
|
|
12619
13398
|
if (opts.store) {
|
|
12620
13399
|
store = opts.store;
|
|
12621
|
-
dir = opts.sessionsRoot ?
|
|
13400
|
+
dir = opts.sessionsRoot ? path12.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
12622
13401
|
} else if (opts.sessionsRoot) {
|
|
12623
|
-
dir =
|
|
13402
|
+
dir = path12.join(opts.sessionsRoot, runId);
|
|
12624
13403
|
store = new DefaultSessionStore({ dir });
|
|
12625
13404
|
} else {
|
|
12626
13405
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -12642,6 +13421,11 @@ function makeDirectorSessionFactory(opts) {
|
|
|
12642
13421
|
}
|
|
12643
13422
|
};
|
|
12644
13423
|
}
|
|
13424
|
+
|
|
13425
|
+
// src/coordination/fleet-manager.ts
|
|
13426
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
13427
|
+
import * as fsp10 from "node:fs/promises";
|
|
13428
|
+
import * as path13 from "node:path";
|
|
12645
13429
|
var FleetManager = class {
|
|
12646
13430
|
/** The fleet-wide event bus. */
|
|
12647
13431
|
fleet;
|
|
@@ -12687,7 +13471,7 @@ var FleetManager = class {
|
|
|
12687
13471
|
maxContext;
|
|
12688
13472
|
constructor(opts = {}) {
|
|
12689
13473
|
this.manifestPath = opts.manifestPath;
|
|
12690
|
-
this.directorRunId = opts.directorRunId ??
|
|
13474
|
+
this.directorRunId = opts.directorRunId ?? randomUUID9();
|
|
12691
13475
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
12692
13476
|
this.maxSpawnDepth = resolveMaxSpawnDepth(opts.maxSpawnDepth);
|
|
12693
13477
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
@@ -12902,7 +13686,7 @@ var FleetManager = class {
|
|
|
12902
13686
|
})),
|
|
12903
13687
|
usage: this.usage.snapshot()
|
|
12904
13688
|
};
|
|
12905
|
-
await
|
|
13689
|
+
await fsp10.mkdir(path13.dirname(this.manifestPath), { recursive: true });
|
|
12906
13690
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
12907
13691
|
return this.manifestPath;
|
|
12908
13692
|
}
|
|
@@ -13039,6 +13823,153 @@ var FleetManager = class {
|
|
|
13039
13823
|
}
|
|
13040
13824
|
};
|
|
13041
13825
|
|
|
13826
|
+
// src/coordination/global-mailbox.ts
|
|
13827
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
13828
|
+
import * as fsp11 from "node:fs/promises";
|
|
13829
|
+
import * as path14 from "node:path";
|
|
13830
|
+
|
|
13831
|
+
// src/coordination/mailbox-constants.ts
|
|
13832
|
+
var AGENT_STALE_MS = 6e4;
|
|
13833
|
+
var CLIENT_STALE_MS = 6e4;
|
|
13834
|
+
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
13835
|
+
var REGISTRY_CACHE_TTL_MS = 2e3;
|
|
13836
|
+
var LINE_SEPARATOR = "\n";
|
|
13837
|
+
var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
|
|
13838
|
+
var AUTO_COMPACT_INTERVAL_MS = 3e5;
|
|
13839
|
+
var AUTO_COMPACT_READ_MAX_AGE_MS = 6e5;
|
|
13840
|
+
var AUTO_COMPACT_DEFAULT_TTL_MS = 864e5;
|
|
13841
|
+
|
|
13842
|
+
// src/coordination/mailbox-message-codec.ts
|
|
13843
|
+
var MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
13844
|
+
"note",
|
|
13845
|
+
"ask",
|
|
13846
|
+
"assign",
|
|
13847
|
+
"steer",
|
|
13848
|
+
"btw",
|
|
13849
|
+
"broadcast",
|
|
13850
|
+
"status",
|
|
13851
|
+
"result",
|
|
13852
|
+
"review",
|
|
13853
|
+
"control"
|
|
13854
|
+
]);
|
|
13855
|
+
var PRIORITIES = /* @__PURE__ */ new Set(["low", "normal", "high"]);
|
|
13856
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
13857
|
+
"pending",
|
|
13858
|
+
"in_progress",
|
|
13859
|
+
"completed",
|
|
13860
|
+
"failed",
|
|
13861
|
+
"idle",
|
|
13862
|
+
"running",
|
|
13863
|
+
"streaming",
|
|
13864
|
+
"waiting_user",
|
|
13865
|
+
"error",
|
|
13866
|
+
"offline",
|
|
13867
|
+
"busy"
|
|
13868
|
+
]);
|
|
13869
|
+
function isRecord3(value) {
|
|
13870
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13871
|
+
}
|
|
13872
|
+
function requiredString(record, key) {
|
|
13873
|
+
const value = record[key];
|
|
13874
|
+
if (typeof value !== "string") {
|
|
13875
|
+
throw new TypeError(`mailbox message field "${key}" must be a string`);
|
|
13876
|
+
}
|
|
13877
|
+
return value;
|
|
13878
|
+
}
|
|
13879
|
+
function optionalString2(record, key) {
|
|
13880
|
+
const value = record[key];
|
|
13881
|
+
if (value === void 0) return {};
|
|
13882
|
+
if (typeof value !== "string") {
|
|
13883
|
+
throw new TypeError(`mailbox message field "${key}" must be a string when present`);
|
|
13884
|
+
}
|
|
13885
|
+
return { [key]: value };
|
|
13886
|
+
}
|
|
13887
|
+
function parseMessageType(value) {
|
|
13888
|
+
if (value === "info") return "note";
|
|
13889
|
+
if (value === "task") return "assign";
|
|
13890
|
+
if (typeof value === "string" && MESSAGE_TYPES.has(value)) {
|
|
13891
|
+
return value;
|
|
13892
|
+
}
|
|
13893
|
+
throw new TypeError('mailbox message field "type" is invalid');
|
|
13894
|
+
}
|
|
13895
|
+
function normalizeMailboxMessageType(value) {
|
|
13896
|
+
return parseMessageType(value);
|
|
13897
|
+
}
|
|
13898
|
+
function parsePriority(value) {
|
|
13899
|
+
if (typeof value === "string" && PRIORITIES.has(value)) {
|
|
13900
|
+
return value;
|
|
13901
|
+
}
|
|
13902
|
+
if (typeof value === "string") return "normal";
|
|
13903
|
+
throw new TypeError('mailbox message field "priority" must be a string');
|
|
13904
|
+
}
|
|
13905
|
+
function parseReadReceipts(record, to) {
|
|
13906
|
+
const value = record["readBy"];
|
|
13907
|
+
if (value === void 0) {
|
|
13908
|
+
const legacyReadAt = record["readAt"];
|
|
13909
|
+
return record["read"] === true && typeof legacyReadAt === "string" ? { [to || "unknown"]: legacyReadAt } : {};
|
|
13910
|
+
}
|
|
13911
|
+
if (!isRecord3(value)) {
|
|
13912
|
+
throw new TypeError('mailbox message field "readBy" must be an object');
|
|
13913
|
+
}
|
|
13914
|
+
const receipts = {};
|
|
13915
|
+
for (const [agentId, timestamp] of Object.entries(value)) {
|
|
13916
|
+
if (typeof timestamp !== "string") {
|
|
13917
|
+
throw new TypeError("mailbox message read receipt timestamps must be strings");
|
|
13918
|
+
}
|
|
13919
|
+
receipts[agentId] = timestamp;
|
|
13920
|
+
}
|
|
13921
|
+
return receipts;
|
|
13922
|
+
}
|
|
13923
|
+
function parseTaskContext(value) {
|
|
13924
|
+
if (value === void 0) return void 0;
|
|
13925
|
+
if (!isRecord3(value)) {
|
|
13926
|
+
throw new TypeError('mailbox message field "taskContext" must be an object');
|
|
13927
|
+
}
|
|
13928
|
+
const status = value["status"];
|
|
13929
|
+
if (status !== void 0 && (typeof status !== "string" || !TASK_STATUSES.has(status))) {
|
|
13930
|
+
throw new TypeError("mailbox message taskContext status is invalid");
|
|
13931
|
+
}
|
|
13932
|
+
return {
|
|
13933
|
+
...optionalString2(value, "agentRole"),
|
|
13934
|
+
...optionalString2(value, "agentName"),
|
|
13935
|
+
...optionalString2(value, "taskId"),
|
|
13936
|
+
...status === void 0 ? {} : { status }
|
|
13937
|
+
};
|
|
13938
|
+
}
|
|
13939
|
+
function parseMailboxMessage(value) {
|
|
13940
|
+
if (!isRecord3(value)) throw new TypeError("mailbox message must be an object");
|
|
13941
|
+
const to = value["to"] === void 0 ? "" : requiredString(value, "to");
|
|
13942
|
+
const completed = value["completed"];
|
|
13943
|
+
if (typeof completed !== "boolean") {
|
|
13944
|
+
throw new TypeError('mailbox message field "completed" must be a boolean');
|
|
13945
|
+
}
|
|
13946
|
+
const taskContext = parseTaskContext(value["taskContext"]);
|
|
13947
|
+
return {
|
|
13948
|
+
id: requiredString(value, "id"),
|
|
13949
|
+
from: requiredString(value, "from"),
|
|
13950
|
+
to,
|
|
13951
|
+
type: parseMessageType(value["type"]),
|
|
13952
|
+
subject: requiredString(value, "subject"),
|
|
13953
|
+
body: requiredString(value, "body"),
|
|
13954
|
+
priority: parsePriority(value["priority"]),
|
|
13955
|
+
readBy: parseReadReceipts(value, to),
|
|
13956
|
+
completed,
|
|
13957
|
+
timestamp: requiredString(value, "timestamp"),
|
|
13958
|
+
...optionalString2(value, "completedBy"),
|
|
13959
|
+
...optionalString2(value, "outcome"),
|
|
13960
|
+
...optionalString2(value, "completedAt"),
|
|
13961
|
+
...optionalString2(value, "deletedAt"),
|
|
13962
|
+
...optionalString2(value, "deletedBy"),
|
|
13963
|
+
...optionalString2(value, "replyTo"),
|
|
13964
|
+
...optionalString2(value, "senderSessionId"),
|
|
13965
|
+
...optionalString2(value, "expiresAt"),
|
|
13966
|
+
...taskContext === void 0 ? {} : { taskContext }
|
|
13967
|
+
};
|
|
13968
|
+
}
|
|
13969
|
+
function parseMailboxMessageLine(line) {
|
|
13970
|
+
return parseMailboxMessage(JSON.parse(line));
|
|
13971
|
+
}
|
|
13972
|
+
|
|
13042
13973
|
// src/coordination/mailbox-types.ts
|
|
13043
13974
|
function normalizeRecipient(to) {
|
|
13044
13975
|
return to.trim().toLowerCase() === "all" ? "*" : to.trim();
|
|
@@ -13047,15 +13978,8 @@ function normalizeRecipient(to) {
|
|
|
13047
13978
|
// src/coordination/global-mailbox.ts
|
|
13048
13979
|
var MAILBOX_FILE = "_mailbox.jsonl";
|
|
13049
13980
|
var CLIENT_REGISTRY_FILE = "_mailbox.clients.json";
|
|
13050
|
-
var AGENT_STALE_MS = 6e4;
|
|
13051
|
-
var AGENT_PURGE_MS = 864e5;
|
|
13052
|
-
var CLIENT_STALE_MS = 6e4;
|
|
13053
|
-
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
13054
|
-
var REGISTRY_CACHE_TTL_MS = 2e3;
|
|
13055
|
-
var LINE_SEPARATOR = "\n";
|
|
13056
|
-
var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
|
|
13057
13981
|
function resolveProjectDir(projectRoot, globalRoot) {
|
|
13058
|
-
return
|
|
13982
|
+
return path14.join(globalRoot, "projects", projectSlug(projectRoot));
|
|
13059
13983
|
}
|
|
13060
13984
|
var GlobalMailbox = class {
|
|
13061
13985
|
/** Path to the JSONL message file. */
|
|
@@ -13068,6 +13992,12 @@ var GlobalMailbox = class {
|
|
|
13068
13992
|
_events;
|
|
13069
13993
|
/** Optional HQ publisher for cross-project command-center telemetry. */
|
|
13070
13994
|
_hqPublisher;
|
|
13995
|
+
/**
|
|
13996
|
+
* Optional SSE event emitter for real-time push to HTTP bridge clients.
|
|
13997
|
+
* When present, `send`/`ack`/`softDelete`/`restore` emit events that
|
|
13998
|
+
* connected SSE subscribers receive instantly.
|
|
13999
|
+
*/
|
|
14000
|
+
eventEmitter;
|
|
13071
14001
|
/**
|
|
13072
14002
|
* Local cache of the agent registry to avoid re-reading on every call.
|
|
13073
14003
|
* Time-bounded: the registry file is shared ACROSS PROCESSES (that's the
|
|
@@ -13113,20 +14043,47 @@ var GlobalMailbox = class {
|
|
|
13113
14043
|
* that follows the read. Pattern mirrors `DefaultMemoryStore.runSerialized`.
|
|
13114
14044
|
*/
|
|
13115
14045
|
_readChain = Promise.resolve([]);
|
|
14046
|
+
/**
|
|
14047
|
+
* Recipient → Set of indices into `_messageCache`. Maintained alongside
|
|
14048
|
+
* the cache so `query({ to })` and `unreadCount(agentId)` can skip the
|
|
14049
|
+
* O(N) full scan and iterate only the messages addressed to the target
|
|
14050
|
+
* recipient (plus broadcasts `*`).
|
|
14051
|
+
*
|
|
14052
|
+
* The index is rebuilt whenever the cache array is replaced
|
|
14053
|
+
* (`_setMessageCache`), extended when new messages are pushed
|
|
14054
|
+
* (`_pushToCache`, `_readNewMessagesOnly`), and cleared on `close()`.
|
|
14055
|
+
* It is `null` when the cache itself is null (> MESSAGE_CACHE_MAX_ENTRIES
|
|
14056
|
+
* or never populated).
|
|
14057
|
+
*
|
|
14058
|
+
* Safety: array indices are valid only as long as the cache array
|
|
14059
|
+
* reference hasn't changed. Since we rebuild the index in the same
|
|
14060
|
+
* synchronous step that replaces the cache, a reader that calls
|
|
14061
|
+
* `_readMessagesCached()` either gets the old array + old index, or
|
|
14062
|
+
* the new array + new index — never a mismatch.
|
|
14063
|
+
*/
|
|
14064
|
+
_recipientIndex = null;
|
|
14065
|
+
/**
|
|
14066
|
+
* Sender → Set of indices into `_messageCache`. Same lifecycle and
|
|
14067
|
+
* safety properties as `_recipientIndex`, but keyed on `msg.from`
|
|
14068
|
+
* so `query({ from })` can skip the full scan.
|
|
14069
|
+
*/
|
|
14070
|
+
_senderIndex = null;
|
|
13116
14071
|
/**
|
|
13117
14072
|
* @param projectDir — `~/.wrongstack/projects/<slug>/`
|
|
13118
14073
|
* @param events — optional EventBus for real-time TUI/WebUI notifications
|
|
13119
14074
|
* @param hqPublisher — optional HQ publisher, or getter, for cross-project telemetry
|
|
14075
|
+
* @param eventEmitter — optional SSE event emitter for HTTP bridge push
|
|
13120
14076
|
*/
|
|
13121
|
-
constructor(projectDir, events, hqPublisher) {
|
|
13122
|
-
this.messagePath =
|
|
13123
|
-
this.registryPath =
|
|
13124
|
-
this.clientRegistryPath =
|
|
14077
|
+
constructor(projectDir, events, hqPublisher, eventEmitter) {
|
|
14078
|
+
this.messagePath = path14.join(projectDir, MAILBOX_FILE);
|
|
14079
|
+
this.registryPath = path14.join(projectDir, "_mailbox.registry.json");
|
|
14080
|
+
this.clientRegistryPath = path14.join(projectDir, CLIENT_REGISTRY_FILE);
|
|
13125
14081
|
this._events = events;
|
|
13126
14082
|
this._hqPublisher = hqPublisher;
|
|
14083
|
+
this.eventEmitter = eventEmitter;
|
|
13127
14084
|
}
|
|
13128
14085
|
get hqMailboxId() {
|
|
13129
|
-
return `${
|
|
14086
|
+
return `${path14.basename(path14.dirname(this.messagePath))}:mailbox`;
|
|
13130
14087
|
}
|
|
13131
14088
|
get hqPublisher() {
|
|
13132
14089
|
return typeof this._hqPublisher === "function" ? this._hqPublisher() : this._hqPublisher;
|
|
@@ -13147,12 +14104,12 @@ var GlobalMailbox = class {
|
|
|
13147
14104
|
async send(input) {
|
|
13148
14105
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13149
14106
|
const msg = {
|
|
13150
|
-
id:
|
|
14107
|
+
id: randomUUID10(),
|
|
13151
14108
|
from: input.from,
|
|
13152
14109
|
// "all" is an accepted spelling of the broadcast address — canonical
|
|
13153
14110
|
// form on disk is '*' so every query/checker matches it.
|
|
13154
14111
|
to: normalizeRecipient(input.to),
|
|
13155
|
-
type: input.type,
|
|
14112
|
+
type: normalizeMailboxMessageType(input.type),
|
|
13156
14113
|
subject: input.subject,
|
|
13157
14114
|
body: input.body,
|
|
13158
14115
|
priority: input.priority ?? "normal",
|
|
@@ -13160,12 +14117,13 @@ var GlobalMailbox = class {
|
|
|
13160
14117
|
completed: false,
|
|
13161
14118
|
timestamp: now,
|
|
13162
14119
|
replyTo: input.replyTo,
|
|
13163
|
-
taskContext: input.taskContext
|
|
14120
|
+
taskContext: input.taskContext,
|
|
14121
|
+
expiresAt: input.ttlMs !== void 0 ? new Date(Date.now() + input.ttlMs).toISOString() : void 0
|
|
13164
14122
|
};
|
|
13165
14123
|
const line = JSON.stringify(msg) + LINE_SEPARATOR;
|
|
13166
|
-
await
|
|
14124
|
+
await fsp11.mkdir(path14.dirname(this.messagePath), { recursive: true });
|
|
13167
14125
|
await withFileLock(this.messagePath, async () => {
|
|
13168
|
-
await
|
|
14126
|
+
await fsp11.appendFile(this.messagePath, line, "utf8");
|
|
13169
14127
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
13170
14128
|
this._pushToCache(msg, mtimeMs, size);
|
|
13171
14129
|
});
|
|
@@ -13175,21 +14133,49 @@ var GlobalMailbox = class {
|
|
|
13175
14133
|
message: msg
|
|
13176
14134
|
});
|
|
13177
14135
|
this.publishHqMailboxSnapshot();
|
|
14136
|
+
this._events?.emitCustom("mailbox.message_sent", {
|
|
14137
|
+
messageId: msg.id,
|
|
14138
|
+
from: msg.from,
|
|
14139
|
+
to: msg.to,
|
|
14140
|
+
type: msg.type,
|
|
14141
|
+
subject: msg.subject
|
|
14142
|
+
});
|
|
14143
|
+
this.eventEmitter?.emit({
|
|
14144
|
+
type: "message.sent",
|
|
14145
|
+
messageId: msg.id,
|
|
14146
|
+
from: msg.from,
|
|
14147
|
+
to: msg.to,
|
|
14148
|
+
timestamp: now
|
|
14149
|
+
});
|
|
13178
14150
|
return msg;
|
|
13179
14151
|
}
|
|
13180
14152
|
async query(q) {
|
|
14153
|
+
const queryType = q.type === void 0 ? void 0 : normalizeMailboxMessageType(q.type);
|
|
13181
14154
|
const all = await this._readMessagesCached();
|
|
13182
14155
|
const limit = q.limit ?? 50;
|
|
13183
14156
|
const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
|
|
13184
14157
|
const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
|
|
13185
14158
|
const out = [];
|
|
13186
|
-
|
|
13187
|
-
|
|
14159
|
+
let candidates;
|
|
14160
|
+
if (q.to !== void 0 && this._recipientIndex !== null && q.from === void 0 && q.type === void 0 && q.minPriority === void 0) {
|
|
14161
|
+
const indices = /* @__PURE__ */ new Set();
|
|
14162
|
+
const direct = this._recipientIndex.get(q.to);
|
|
14163
|
+
if (direct !== void 0) for (const i of direct) indices.add(i);
|
|
14164
|
+
const broadcasts = this._recipientIndex.get("*");
|
|
14165
|
+
if (broadcasts !== void 0) for (const i of broadcasts) indices.add(i);
|
|
14166
|
+
candidates = [...indices].sort((a, b) => a - b).map((i) => all[i]);
|
|
14167
|
+
} else if (q.from !== void 0 && q.to === void 0 && this._senderIndex !== null && q.type === void 0 && q.minPriority === void 0) {
|
|
14168
|
+
const indices = this._senderIndex.get(q.from);
|
|
14169
|
+
candidates = indices !== void 0 ? [...indices].sort((a, b) => a - b).map((i) => all[i]) : [];
|
|
14170
|
+
} else {
|
|
14171
|
+
candidates = all;
|
|
14172
|
+
}
|
|
14173
|
+
for (const m of candidates) {
|
|
13188
14174
|
if (q.to !== void 0 && m.to !== q.to && m.to !== "*") continue;
|
|
13189
14175
|
if (q.from !== void 0 && m.from !== q.from) continue;
|
|
13190
14176
|
if (q.unreadBy !== void 0 && q.unreadBy in m.readBy) continue;
|
|
13191
14177
|
if (q.incompleteOnly && m.completed) continue;
|
|
13192
|
-
if (
|
|
14178
|
+
if (queryType !== void 0 && m.type !== queryType) continue;
|
|
13193
14179
|
if (order !== null && (order[m.priority] ?? 1) < minPriorityRank) {
|
|
13194
14180
|
continue;
|
|
13195
14181
|
}
|
|
@@ -13236,7 +14222,7 @@ var GlobalMailbox = class {
|
|
|
13236
14222
|
}
|
|
13237
14223
|
if (changed) {
|
|
13238
14224
|
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
13239
|
-
await
|
|
14225
|
+
await fsp11.writeFile(this.messagePath, serialized, "utf8");
|
|
13240
14226
|
}
|
|
13241
14227
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
13242
14228
|
this._setMessageCache(all, mtimeMs, size);
|
|
@@ -13247,6 +14233,13 @@ var GlobalMailbox = class {
|
|
|
13247
14233
|
action: message.completed ? "message.completed" : "message.read",
|
|
13248
14234
|
message
|
|
13249
14235
|
});
|
|
14236
|
+
this.eventEmitter?.emit({
|
|
14237
|
+
type: "message.acked",
|
|
14238
|
+
messageId: message.id,
|
|
14239
|
+
from: message.from,
|
|
14240
|
+
to: message.to,
|
|
14241
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14242
|
+
});
|
|
13250
14243
|
}
|
|
13251
14244
|
if (updated.length > 0) this.publishHqMailboxSnapshot();
|
|
13252
14245
|
return updated;
|
|
@@ -13254,10 +14247,22 @@ var GlobalMailbox = class {
|
|
|
13254
14247
|
async unreadCount(forAgentId) {
|
|
13255
14248
|
const all = await this._readMessagesCached();
|
|
13256
14249
|
let count = 0;
|
|
13257
|
-
|
|
13258
|
-
const
|
|
13259
|
-
|
|
13260
|
-
|
|
14250
|
+
if (this._recipientIndex !== null) {
|
|
14251
|
+
const indices = /* @__PURE__ */ new Set();
|
|
14252
|
+
const direct = this._recipientIndex.get(forAgentId);
|
|
14253
|
+
if (direct !== void 0) for (const i of direct) indices.add(i);
|
|
14254
|
+
const broadcasts = this._recipientIndex.get("*");
|
|
14255
|
+
if (broadcasts !== void 0) for (const i of broadcasts) indices.add(i);
|
|
14256
|
+
for (const i of indices) {
|
|
14257
|
+
const m = all[i];
|
|
14258
|
+
if (!(forAgentId in m.readBy) && !m.completed) count++;
|
|
14259
|
+
}
|
|
14260
|
+
} else {
|
|
14261
|
+
for (let i = 0; i < all.length; i++) {
|
|
14262
|
+
const m = all[i];
|
|
14263
|
+
if ((m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed) {
|
|
14264
|
+
count++;
|
|
14265
|
+
}
|
|
13261
14266
|
}
|
|
13262
14267
|
}
|
|
13263
14268
|
return count;
|
|
@@ -13267,6 +14272,7 @@ var GlobalMailbox = class {
|
|
|
13267
14272
|
await withFileLock(this.messagePath, async () => {
|
|
13268
14273
|
const all = await this._readMessagesFresh();
|
|
13269
14274
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14275
|
+
let changed = false;
|
|
13270
14276
|
for (const m of all) {
|
|
13271
14277
|
if (m.id !== mailId) continue;
|
|
13272
14278
|
if (m.deletedAt !== void 0) {
|
|
@@ -13276,19 +14282,30 @@ var GlobalMailbox = class {
|
|
|
13276
14282
|
m.deletedAt = now;
|
|
13277
14283
|
m.deletedBy = by;
|
|
13278
14284
|
updated = m;
|
|
14285
|
+
changed = true;
|
|
14286
|
+
}
|
|
14287
|
+
if (changed) {
|
|
14288
|
+
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
14289
|
+
await fsp11.writeFile(this.messagePath, serialized, "utf8");
|
|
13279
14290
|
}
|
|
13280
|
-
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
13281
|
-
await fsp9.writeFile(this.messagePath, serialized, "utf8");
|
|
13282
14291
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
13283
14292
|
this._setMessageCache(all, mtimeMs, size);
|
|
13284
14293
|
});
|
|
13285
14294
|
if (updated !== null) {
|
|
14295
|
+
const msg = updated;
|
|
13286
14296
|
this.publishHqMailboxEvent({
|
|
13287
14297
|
mailboxId: this.hqMailboxId,
|
|
13288
14298
|
action: "message.updated",
|
|
13289
|
-
message:
|
|
14299
|
+
message: msg
|
|
13290
14300
|
});
|
|
13291
14301
|
this.publishHqMailboxSnapshot();
|
|
14302
|
+
this.eventEmitter?.emit({
|
|
14303
|
+
type: "message.deleted",
|
|
14304
|
+
messageId: msg.id,
|
|
14305
|
+
from: msg.from,
|
|
14306
|
+
to: msg.to,
|
|
14307
|
+
timestamp: msg.deletedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
14308
|
+
});
|
|
13292
14309
|
}
|
|
13293
14310
|
return updated;
|
|
13294
14311
|
}
|
|
@@ -13296,24 +14313,40 @@ var GlobalMailbox = class {
|
|
|
13296
14313
|
let updated = null;
|
|
13297
14314
|
await withFileLock(this.messagePath, async () => {
|
|
13298
14315
|
const all = await this._readMessagesFresh();
|
|
14316
|
+
let changed = false;
|
|
13299
14317
|
for (const m of all) {
|
|
13300
14318
|
if (m.id !== mailId) continue;
|
|
14319
|
+
if (m.deletedAt === void 0 && m.deletedBy === void 0) {
|
|
14320
|
+
updated = m;
|
|
14321
|
+
continue;
|
|
14322
|
+
}
|
|
13301
14323
|
delete m.deletedAt;
|
|
13302
14324
|
delete m.deletedBy;
|
|
13303
14325
|
updated = m;
|
|
14326
|
+
changed = true;
|
|
14327
|
+
}
|
|
14328
|
+
if (changed) {
|
|
14329
|
+
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
14330
|
+
await fsp11.writeFile(this.messagePath, serialized, "utf8");
|
|
13304
14331
|
}
|
|
13305
|
-
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
13306
|
-
await fsp9.writeFile(this.messagePath, serialized, "utf8");
|
|
13307
14332
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
13308
14333
|
this._setMessageCache(all, mtimeMs, size);
|
|
13309
14334
|
});
|
|
13310
14335
|
if (updated !== null) {
|
|
14336
|
+
const msg = updated;
|
|
13311
14337
|
this.publishHqMailboxEvent({
|
|
13312
14338
|
mailboxId: this.hqMailboxId,
|
|
13313
14339
|
action: "message.updated",
|
|
13314
|
-
message:
|
|
14340
|
+
message: msg
|
|
13315
14341
|
});
|
|
13316
14342
|
this.publishHqMailboxSnapshot();
|
|
14343
|
+
this.eventEmitter?.emit({
|
|
14344
|
+
type: "message.restored",
|
|
14345
|
+
messageId: msg.id,
|
|
14346
|
+
from: msg.from,
|
|
14347
|
+
to: msg.to,
|
|
14348
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14349
|
+
});
|
|
13317
14350
|
}
|
|
13318
14351
|
return updated;
|
|
13319
14352
|
}
|
|
@@ -13446,8 +14479,19 @@ var GlobalMailbox = class {
|
|
|
13446
14479
|
}
|
|
13447
14480
|
async getAgentStatuses() {
|
|
13448
14481
|
await this._ensureRegistry();
|
|
13449
|
-
|
|
14482
|
+
let registry = await this._readRegistry();
|
|
14483
|
+
const before = registry.size;
|
|
13450
14484
|
this._pruneStaleInPlace(registry);
|
|
14485
|
+
if (registry.size < before) {
|
|
14486
|
+
await withFileLock(this.registryPath, async () => {
|
|
14487
|
+
const fresh = await this._readRegistry({ fresh: true });
|
|
14488
|
+
this._pruneStaleInPlace(fresh);
|
|
14489
|
+
this._registryCache = fresh;
|
|
14490
|
+
this._registryCacheAt = Date.now();
|
|
14491
|
+
await this._writeRegistry(fresh);
|
|
14492
|
+
registry = fresh;
|
|
14493
|
+
});
|
|
14494
|
+
}
|
|
13451
14495
|
const now = Date.now();
|
|
13452
14496
|
return Array.from(registry.values()).map((a) => ({
|
|
13453
14497
|
agentId: a.agentId,
|
|
@@ -13499,6 +14543,20 @@ var GlobalMailbox = class {
|
|
|
13499
14543
|
});
|
|
13500
14544
|
this.publishHqMailboxSnapshot();
|
|
13501
14545
|
}
|
|
14546
|
+
async deregisterClient(clientId) {
|
|
14547
|
+
await this._ensureClientRegistry();
|
|
14548
|
+
await withFileLock(this.clientRegistryPath, async () => {
|
|
14549
|
+
const registry = await this._readClientRegistry({ fresh: true });
|
|
14550
|
+
this._pruneStaleClientsInPlace(registry);
|
|
14551
|
+
registry.delete(clientId);
|
|
14552
|
+
this._clientRegistryCache = registry;
|
|
14553
|
+
this._clientRegistryCacheAt = Date.now();
|
|
14554
|
+
await this._writeClientRegistry(registry);
|
|
14555
|
+
});
|
|
14556
|
+
this._lastClientHeartbeat.delete(clientId);
|
|
14557
|
+
this._events?.emitCustom("mailbox.client_deregistered", { clientId });
|
|
14558
|
+
this.publishHqMailboxSnapshot();
|
|
14559
|
+
}
|
|
13502
14560
|
async clientHeartbeat(input) {
|
|
13503
14561
|
const last = this._lastClientHeartbeat.get(input.clientId) ?? 0;
|
|
13504
14562
|
const now = Date.now();
|
|
@@ -13528,7 +14586,11 @@ var GlobalMailbox = class {
|
|
|
13528
14586
|
async getClientStatuses() {
|
|
13529
14587
|
await this._ensureClientRegistry();
|
|
13530
14588
|
const registry = await this._readClientRegistry();
|
|
14589
|
+
const before = registry.size;
|
|
13531
14590
|
this._pruneStaleClientsInPlace(registry);
|
|
14591
|
+
if (registry.size < before) {
|
|
14592
|
+
await this._writeClientRegistry(registry);
|
|
14593
|
+
}
|
|
13532
14594
|
const now = Date.now();
|
|
13533
14595
|
return Array.from(registry.values()).map((c) => ({
|
|
13534
14596
|
clientId: c.clientId,
|
|
@@ -13540,6 +14602,32 @@ var GlobalMailbox = class {
|
|
|
13540
14602
|
pid: c.pid
|
|
13541
14603
|
})).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
|
|
13542
14604
|
}
|
|
14605
|
+
/**
|
|
14606
|
+
* Explicitly purge stale clients from the registry and write back to disk.
|
|
14607
|
+
* Removes client entries whose lastSeenAt is older than CLIENT_STALE_MS.
|
|
14608
|
+
* Returns the number of entries purged.
|
|
14609
|
+
*
|
|
14610
|
+
* Idempotent — safe to call regularly. The same pruning runs implicitly
|
|
14611
|
+
* inside registerClient, clientHeartbeat, and getClientStatuses, but if
|
|
14612
|
+
* no client registers or heartbeats for a long time, this public method
|
|
14613
|
+
* ensures the file gets cleaned up on demand.
|
|
14614
|
+
*/
|
|
14615
|
+
async purgeClients() {
|
|
14616
|
+
await this._ensureClientRegistry();
|
|
14617
|
+
let purged = 0;
|
|
14618
|
+
await withFileLock(this.clientRegistryPath, async () => {
|
|
14619
|
+
const registry = await this._readClientRegistry({ fresh: true });
|
|
14620
|
+
const before = registry.size;
|
|
14621
|
+
this._pruneStaleClientsInPlace(registry);
|
|
14622
|
+
purged = before - registry.size;
|
|
14623
|
+
if (purged > 0) {
|
|
14624
|
+
this._clientRegistryCache = registry;
|
|
14625
|
+
this._clientRegistryCacheAt = Date.now();
|
|
14626
|
+
await this._writeClientRegistry(registry);
|
|
14627
|
+
}
|
|
14628
|
+
});
|
|
14629
|
+
return purged;
|
|
14630
|
+
}
|
|
13543
14631
|
// ── Lifecycle ───────────────────────────────────────────────────────────
|
|
13544
14632
|
async close() {
|
|
13545
14633
|
this._registryCache = null;
|
|
@@ -13547,10 +14635,12 @@ var GlobalMailbox = class {
|
|
|
13547
14635
|
this._messageCache = null;
|
|
13548
14636
|
this._messageCacheMtime = -1;
|
|
13549
14637
|
this._messageCacheSize = -1;
|
|
14638
|
+
this._recipientIndex = null;
|
|
14639
|
+
this._senderIndex = null;
|
|
13550
14640
|
}
|
|
13551
14641
|
async clearAll() {
|
|
13552
14642
|
await withFileLock(this.messagePath, async () => {
|
|
13553
|
-
await
|
|
14643
|
+
await fsp11.writeFile(this.messagePath, "", "utf8");
|
|
13554
14644
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
13555
14645
|
this._setMessageCache([], mtimeMs, size);
|
|
13556
14646
|
});
|
|
@@ -13583,7 +14673,7 @@ var GlobalMailbox = class {
|
|
|
13583
14673
|
remaining = kept.length;
|
|
13584
14674
|
if (kept.length < all.length) {
|
|
13585
14675
|
const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
13586
|
-
await
|
|
14676
|
+
await fsp11.writeFile(this.messagePath, content, "utf8");
|
|
13587
14677
|
}
|
|
13588
14678
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
13589
14679
|
this._setMessageCache(kept, mtimeMs, size);
|
|
@@ -13595,6 +14685,100 @@ var GlobalMailbox = class {
|
|
|
13595
14685
|
remaining
|
|
13596
14686
|
};
|
|
13597
14687
|
}
|
|
14688
|
+
// ── Auto-compaction ─────────────────────────────────────────────────────
|
|
14689
|
+
_autoCompactTimer = null;
|
|
14690
|
+
async autoCompact(opts) {
|
|
14691
|
+
const readMaxAgeMs = opts?.readMaxAgeMs ?? AUTO_COMPACT_READ_MAX_AGE_MS;
|
|
14692
|
+
const defaultTtlMs = opts?.defaultTtlMs ?? AUTO_COMPACT_DEFAULT_TTL_MS;
|
|
14693
|
+
const completedMaxAgeMs = opts?.completedMaxAgeMs ?? 864e5;
|
|
14694
|
+
const incompleteMaxAgeMs = opts?.incompleteMaxAgeMs ?? 6048e5;
|
|
14695
|
+
let expiredPurged = 0;
|
|
14696
|
+
let readByAllPurged = 0;
|
|
14697
|
+
let completedPurged = 0;
|
|
14698
|
+
let incompletePurged = 0;
|
|
14699
|
+
let remaining = 0;
|
|
14700
|
+
let onlineAgentIds = null;
|
|
14701
|
+
try {
|
|
14702
|
+
const statuses = await this.getAgentStatuses();
|
|
14703
|
+
const online = statuses.filter((s) => s.online);
|
|
14704
|
+
onlineAgentIds = online.length > 0 ? new Set(online.map((s) => s.agentId)) : null;
|
|
14705
|
+
} catch {
|
|
14706
|
+
onlineAgentIds = null;
|
|
14707
|
+
}
|
|
14708
|
+
await withFileLock(this.messagePath, async () => {
|
|
14709
|
+
const all = await this._readMessagesFresh();
|
|
14710
|
+
const now = Date.now();
|
|
14711
|
+
const cutoffReadAge = now - readMaxAgeMs;
|
|
14712
|
+
const cutoffCompleted = now - completedMaxAgeMs;
|
|
14713
|
+
const cutoffIncomplete = now - incompleteMaxAgeMs;
|
|
14714
|
+
const cutoffDefaultTtl = now - defaultTtlMs;
|
|
14715
|
+
const kept = [];
|
|
14716
|
+
for (const msg of all) {
|
|
14717
|
+
const msgTime = new Date(msg.timestamp).getTime();
|
|
14718
|
+
if (msg.expiresAt !== void 0) {
|
|
14719
|
+
if (new Date(msg.expiresAt).getTime() < now) {
|
|
14720
|
+
expiredPurged++;
|
|
14721
|
+
continue;
|
|
14722
|
+
}
|
|
14723
|
+
} else if (msgTime < cutoffDefaultTtl) {
|
|
14724
|
+
expiredPurged++;
|
|
14725
|
+
continue;
|
|
14726
|
+
}
|
|
14727
|
+
if (onlineAgentIds !== null && !msg.completed) {
|
|
14728
|
+
const readByAll = onlineAgentIds.size > 0 && [...onlineAgentIds].every((id) => id in msg.readBy);
|
|
14729
|
+
if (readByAll) {
|
|
14730
|
+
const readTimes = Object.values(msg.readBy).map((t) => new Date(t).getTime());
|
|
14731
|
+
const latestRead = Math.max(...readTimes);
|
|
14732
|
+
if (latestRead < cutoffReadAge) {
|
|
14733
|
+
readByAllPurged++;
|
|
14734
|
+
continue;
|
|
14735
|
+
}
|
|
14736
|
+
}
|
|
14737
|
+
}
|
|
14738
|
+
const completedTime = msg.completedAt ? new Date(msg.completedAt).getTime() : 0;
|
|
14739
|
+
if (msg.completed && completedTime < cutoffCompleted) {
|
|
14740
|
+
completedPurged++;
|
|
14741
|
+
continue;
|
|
14742
|
+
}
|
|
14743
|
+
if (!msg.completed && msgTime < cutoffIncomplete) {
|
|
14744
|
+
incompletePurged++;
|
|
14745
|
+
continue;
|
|
14746
|
+
}
|
|
14747
|
+
kept.push(msg);
|
|
14748
|
+
}
|
|
14749
|
+
remaining = kept.length;
|
|
14750
|
+
if (kept.length < all.length) {
|
|
14751
|
+
const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
14752
|
+
await fsp11.writeFile(this.messagePath, content, "utf8");
|
|
14753
|
+
}
|
|
14754
|
+
const { mtimeMs, size } = await this._statMessageFile();
|
|
14755
|
+
this._setMessageCache(kept, mtimeMs, size);
|
|
14756
|
+
});
|
|
14757
|
+
const totalRemoved = expiredPurged + readByAllPurged + completedPurged + incompletePurged;
|
|
14758
|
+
return {
|
|
14759
|
+
expiredRemoved: expiredPurged,
|
|
14760
|
+
readByAllRemoved: readByAllPurged,
|
|
14761
|
+
stalePurged: completedPurged + incompletePurged,
|
|
14762
|
+
totalRemoved,
|
|
14763
|
+
remaining
|
|
14764
|
+
};
|
|
14765
|
+
}
|
|
14766
|
+
startAutoCompactTimer(opts) {
|
|
14767
|
+
if (this._autoCompactTimer !== null) {
|
|
14768
|
+
clearInterval(this._autoCompactTimer);
|
|
14769
|
+
}
|
|
14770
|
+
const intervalMs = opts?.intervalMs ?? AUTO_COMPACT_INTERVAL_MS;
|
|
14771
|
+
const timer = setInterval(() => {
|
|
14772
|
+
this.autoCompact(opts).catch(() => {
|
|
14773
|
+
});
|
|
14774
|
+
}, intervalMs);
|
|
14775
|
+
timer.unref?.();
|
|
14776
|
+
this._autoCompactTimer = timer;
|
|
14777
|
+
return () => {
|
|
14778
|
+
clearInterval(timer);
|
|
14779
|
+
this._autoCompactTimer = null;
|
|
14780
|
+
};
|
|
14781
|
+
}
|
|
13598
14782
|
// ── Internal ────────────────────────────────────────────────────────────
|
|
13599
14783
|
/**
|
|
13600
14784
|
* Read all messages from the JSONL file. Always reads + parses the file.
|
|
@@ -13604,7 +14788,7 @@ var GlobalMailbox = class {
|
|
|
13604
14788
|
*/
|
|
13605
14789
|
async _readMessages() {
|
|
13606
14790
|
try {
|
|
13607
|
-
const raw = await
|
|
14791
|
+
const raw = await fsp11.readFile(this.messagePath, "utf8");
|
|
13608
14792
|
return this._parseLines(raw);
|
|
13609
14793
|
} catch (err) {
|
|
13610
14794
|
if (err.code === "ENOENT") return [];
|
|
@@ -13627,44 +14811,59 @@ var GlobalMailbox = class {
|
|
|
13627
14811
|
const buf = Buffer.alloc(tailLen);
|
|
13628
14812
|
await fd.read(buf, 0, tailLen, oldSize);
|
|
13629
14813
|
const tail = buf.toString("utf8");
|
|
13630
|
-
|
|
14814
|
+
if (!tail.endsWith(LINE_SEPARATOR)) {
|
|
14815
|
+
const lastSeparatorIdx = tail.lastIndexOf(LINE_SEPARATOR);
|
|
14816
|
+
if (lastSeparatorIdx === -1) {
|
|
14817
|
+
return this._messageCache;
|
|
14818
|
+
}
|
|
14819
|
+
const completeTail = tail.slice(0, lastSeparatorIdx + 1);
|
|
14820
|
+
this._appendIncrementalLines(completeTail);
|
|
14821
|
+
this._messageCacheSize = oldSize + lastSeparatorIdx + 1;
|
|
14822
|
+
return this._messageCache;
|
|
14823
|
+
}
|
|
14824
|
+
this._appendIncrementalLines(tail);
|
|
14825
|
+
this._messageCacheSize = newSize;
|
|
14826
|
+
return this._messageCache;
|
|
14827
|
+
}
|
|
14828
|
+
_appendIncrementalLines(raw) {
|
|
14829
|
+
for (const line of raw.split(LINE_SEPARATOR)) {
|
|
13631
14830
|
if (!line.trim()) continue;
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
delete parsed["readAt"];
|
|
14831
|
+
const message = this._parseLine(line);
|
|
14832
|
+
if (message === null) continue;
|
|
14833
|
+
this._messageCache.push(message);
|
|
14834
|
+
const idx = this._messageCache.length - 1;
|
|
14835
|
+
if (this._recipientIndex !== null) {
|
|
14836
|
+
let set = this._recipientIndex.get(message.to);
|
|
14837
|
+
if (set === void 0) {
|
|
14838
|
+
set = /* @__PURE__ */ new Set();
|
|
14839
|
+
this._recipientIndex.set(message.to, set);
|
|
13642
14840
|
}
|
|
13643
|
-
|
|
13644
|
-
}
|
|
14841
|
+
set.add(idx);
|
|
14842
|
+
}
|
|
14843
|
+
if (this._senderIndex !== null) {
|
|
14844
|
+
let set = this._senderIndex.get(message.from);
|
|
14845
|
+
if (set === void 0) {
|
|
14846
|
+
set = /* @__PURE__ */ new Set();
|
|
14847
|
+
this._senderIndex.set(message.from, set);
|
|
14848
|
+
}
|
|
14849
|
+
set.add(idx);
|
|
13645
14850
|
}
|
|
13646
14851
|
}
|
|
13647
|
-
|
|
14852
|
+
}
|
|
14853
|
+
_parseLine(line) {
|
|
14854
|
+
try {
|
|
14855
|
+
return parseMailboxMessageLine(line);
|
|
14856
|
+
} catch {
|
|
14857
|
+
return null;
|
|
14858
|
+
}
|
|
13648
14859
|
}
|
|
13649
14860
|
/** Parse a JSONL string into MailboxMessage[], including migration. */
|
|
13650
14861
|
_parseLines(raw) {
|
|
13651
14862
|
const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
|
|
13652
14863
|
const messages = [];
|
|
13653
14864
|
for (const line of lines) {
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
if (!parsed["readBy"]) {
|
|
13657
|
-
const readBy = {};
|
|
13658
|
-
if (parsed["read"] && parsed["readAt"]) {
|
|
13659
|
-
readBy[parsed["to"]] = parsed["readAt"];
|
|
13660
|
-
}
|
|
13661
|
-
parsed["readBy"] = readBy;
|
|
13662
|
-
delete parsed["read"];
|
|
13663
|
-
delete parsed["readAt"];
|
|
13664
|
-
}
|
|
13665
|
-
messages.push(parsed);
|
|
13666
|
-
} catch {
|
|
13667
|
-
}
|
|
14865
|
+
const message = this._parseLine(line);
|
|
14866
|
+
if (message !== null) messages.push(message);
|
|
13668
14867
|
}
|
|
13669
14868
|
return messages;
|
|
13670
14869
|
}
|
|
@@ -13697,7 +14896,7 @@ var GlobalMailbox = class {
|
|
|
13697
14896
|
*/
|
|
13698
14897
|
async _statMessageFile() {
|
|
13699
14898
|
try {
|
|
13700
|
-
const st = await
|
|
14899
|
+
const st = await fsp11.stat(this.messagePath);
|
|
13701
14900
|
return { mtimeMs: st.mtimeMs, size: st.size };
|
|
13702
14901
|
} catch (err) {
|
|
13703
14902
|
if (err.code === "ENOENT") {
|
|
@@ -13743,16 +14942,15 @@ var GlobalMailbox = class {
|
|
|
13743
14942
|
*/
|
|
13744
14943
|
async _readMessagesCachedWork() {
|
|
13745
14944
|
try {
|
|
13746
|
-
const st = await
|
|
14945
|
+
const st = await fsp11.stat(this.messagePath);
|
|
13747
14946
|
if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
|
|
13748
14947
|
return this._messageCache;
|
|
13749
14948
|
}
|
|
13750
14949
|
if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
|
|
13751
|
-
const fd = await
|
|
14950
|
+
const fd = await fsp11.open(this.messagePath, "r");
|
|
13752
14951
|
try {
|
|
13753
14952
|
const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
|
|
13754
14953
|
this._messageCacheMtime = st.mtimeMs;
|
|
13755
|
-
this._messageCacheSize = st.size;
|
|
13756
14954
|
return updated;
|
|
13757
14955
|
} finally {
|
|
13758
14956
|
await fd.close();
|
|
@@ -13789,11 +14987,46 @@ var GlobalMailbox = class {
|
|
|
13789
14987
|
this._messageCache = null;
|
|
13790
14988
|
this._messageCacheMtime = -1;
|
|
13791
14989
|
this._messageCacheSize = -1;
|
|
14990
|
+
this._recipientIndex = null;
|
|
14991
|
+
this._senderIndex = null;
|
|
13792
14992
|
return;
|
|
13793
14993
|
}
|
|
13794
14994
|
this._messageCache = messages;
|
|
13795
14995
|
this._messageCacheMtime = mtime;
|
|
13796
14996
|
this._messageCacheSize = size;
|
|
14997
|
+
this._rebuildRecipientIndex();
|
|
14998
|
+
}
|
|
14999
|
+
/**
|
|
15000
|
+
* Build the recipient AND sender indexes from the current `_messageCache`.
|
|
15001
|
+
* Called only from `_setMessageCache` so both indexes are always in sync
|
|
15002
|
+
* with a freshly-replaced cache array.
|
|
15003
|
+
*/
|
|
15004
|
+
_rebuildRecipientIndex() {
|
|
15005
|
+
const cache = this._messageCache;
|
|
15006
|
+
if (cache === null) {
|
|
15007
|
+
this._recipientIndex = null;
|
|
15008
|
+
this._senderIndex = null;
|
|
15009
|
+
return;
|
|
15010
|
+
}
|
|
15011
|
+
const recIdx = /* @__PURE__ */ new Map();
|
|
15012
|
+
const sndIdx = /* @__PURE__ */ new Map();
|
|
15013
|
+
for (let i = 0; i < cache.length; i++) {
|
|
15014
|
+
const msg = cache[i];
|
|
15015
|
+
let rSet = recIdx.get(msg.to);
|
|
15016
|
+
if (rSet === void 0) {
|
|
15017
|
+
rSet = /* @__PURE__ */ new Set();
|
|
15018
|
+
recIdx.set(msg.to, rSet);
|
|
15019
|
+
}
|
|
15020
|
+
rSet.add(i);
|
|
15021
|
+
let sSet = sndIdx.get(msg.from);
|
|
15022
|
+
if (sSet === void 0) {
|
|
15023
|
+
sSet = /* @__PURE__ */ new Set();
|
|
15024
|
+
sndIdx.set(msg.from, sSet);
|
|
15025
|
+
}
|
|
15026
|
+
sSet.add(i);
|
|
15027
|
+
}
|
|
15028
|
+
this._recipientIndex = recIdx;
|
|
15029
|
+
this._senderIndex = sndIdx;
|
|
13797
15030
|
}
|
|
13798
15031
|
/**
|
|
13799
15032
|
* Append a single just-sent message to the in-memory cache without
|
|
@@ -13820,18 +15053,35 @@ var GlobalMailbox = class {
|
|
|
13820
15053
|
return;
|
|
13821
15054
|
}
|
|
13822
15055
|
this._messageCache.push(msg);
|
|
15056
|
+
const newIdx = this._messageCache.length - 1;
|
|
15057
|
+
if (this._recipientIndex !== null) {
|
|
15058
|
+
let set = this._recipientIndex.get(msg.to);
|
|
15059
|
+
if (set === void 0) {
|
|
15060
|
+
set = /* @__PURE__ */ new Set();
|
|
15061
|
+
this._recipientIndex.set(msg.to, set);
|
|
15062
|
+
}
|
|
15063
|
+
set.add(newIdx);
|
|
15064
|
+
}
|
|
15065
|
+
if (this._senderIndex !== null) {
|
|
15066
|
+
let set = this._senderIndex.get(msg.from);
|
|
15067
|
+
if (set === void 0) {
|
|
15068
|
+
set = /* @__PURE__ */ new Set();
|
|
15069
|
+
this._senderIndex.set(msg.from, set);
|
|
15070
|
+
}
|
|
15071
|
+
set.add(newIdx);
|
|
15072
|
+
}
|
|
13823
15073
|
this._messageCacheMtime = mtime;
|
|
13824
15074
|
this._messageCacheSize = size;
|
|
13825
15075
|
}
|
|
13826
15076
|
async _ensureRegistry() {
|
|
13827
|
-
await
|
|
15077
|
+
await fsp11.mkdir(path14.dirname(this.registryPath), { recursive: true });
|
|
13828
15078
|
}
|
|
13829
15079
|
async _readRegistry(opts) {
|
|
13830
15080
|
if (!opts?.fresh && this._registryCache && Date.now() - this._registryCacheAt < REGISTRY_CACHE_TTL_MS) {
|
|
13831
15081
|
return new Map(this._registryCache);
|
|
13832
15082
|
}
|
|
13833
15083
|
try {
|
|
13834
|
-
const raw = await
|
|
15084
|
+
const raw = await fsp11.readFile(this.registryPath, "utf8");
|
|
13835
15085
|
const data = JSON.parse(raw);
|
|
13836
15086
|
const map = /* @__PURE__ */ new Map();
|
|
13837
15087
|
for (const [id, agent] of Object.entries(data)) {
|
|
@@ -13851,21 +15101,12 @@ var GlobalMailbox = class {
|
|
|
13851
15101
|
}
|
|
13852
15102
|
}
|
|
13853
15103
|
_pruneStaleInPlace(registry) {
|
|
13854
|
-
const
|
|
13855
|
-
const purgeCutoff = Date.now() - AGENT_PURGE_MS;
|
|
13856
|
-
const toDelete = [];
|
|
15104
|
+
const cutoff = Date.now() - AGENT_STALE_MS;
|
|
13857
15105
|
for (const [id, agent] of registry) {
|
|
13858
|
-
const lastSeen =
|
|
13859
|
-
if (lastSeen <
|
|
13860
|
-
|
|
13861
|
-
continue;
|
|
15106
|
+
const lastSeen = Date.parse(agent.lastSeenAt);
|
|
15107
|
+
if (!Number.isFinite(lastSeen) || lastSeen < cutoff) {
|
|
15108
|
+
registry.delete(id);
|
|
13862
15109
|
}
|
|
13863
|
-
if (lastSeen < staleCutoff) {
|
|
13864
|
-
agent.status = "idle";
|
|
13865
|
-
}
|
|
13866
|
-
}
|
|
13867
|
-
for (const id of toDelete) {
|
|
13868
|
-
registry.delete(id);
|
|
13869
15110
|
}
|
|
13870
15111
|
}
|
|
13871
15112
|
async _writeRegistry(registry) {
|
|
@@ -13873,20 +15114,20 @@ var GlobalMailbox = class {
|
|
|
13873
15114
|
for (const [id, agent] of registry) {
|
|
13874
15115
|
obj[id] = agent;
|
|
13875
15116
|
}
|
|
13876
|
-
const tmp = `${this.registryPath}.${
|
|
13877
|
-
await
|
|
13878
|
-
await
|
|
15117
|
+
const tmp = `${this.registryPath}.${randomUUID10().slice(0, 8)}.tmp`;
|
|
15118
|
+
await fsp11.writeFile(tmp, JSON.stringify(obj), "utf8");
|
|
15119
|
+
await fsp11.rename(tmp, this.registryPath);
|
|
13879
15120
|
}
|
|
13880
15121
|
// ── Client registry internals ───────────────────────────────────────────
|
|
13881
15122
|
async _ensureClientRegistry() {
|
|
13882
|
-
await
|
|
15123
|
+
await fsp11.mkdir(path14.dirname(this.clientRegistryPath), { recursive: true });
|
|
13883
15124
|
}
|
|
13884
15125
|
async _readClientRegistry(opts) {
|
|
13885
15126
|
if (!opts?.fresh && this._clientRegistryCache && Date.now() - this._clientRegistryCacheAt < REGISTRY_CACHE_TTL_MS) {
|
|
13886
15127
|
return new Map(this._clientRegistryCache);
|
|
13887
15128
|
}
|
|
13888
15129
|
try {
|
|
13889
|
-
const raw = await
|
|
15130
|
+
const raw = await fsp11.readFile(this.clientRegistryPath, "utf8");
|
|
13890
15131
|
const data = JSON.parse(raw);
|
|
13891
15132
|
const map = /* @__PURE__ */ new Map();
|
|
13892
15133
|
for (const [id, client] of Object.entries(data)) {
|
|
@@ -13907,9 +15148,10 @@ var GlobalMailbox = class {
|
|
|
13907
15148
|
}
|
|
13908
15149
|
_pruneStaleClientsInPlace(registry) {
|
|
13909
15150
|
const cutoff = Date.now() - CLIENT_STALE_MS;
|
|
13910
|
-
for (const client of registry
|
|
13911
|
-
|
|
13912
|
-
|
|
15151
|
+
for (const [id, client] of registry) {
|
|
15152
|
+
const lastSeen = Date.parse(client.lastSeenAt);
|
|
15153
|
+
if (!Number.isFinite(lastSeen) || lastSeen < cutoff) {
|
|
15154
|
+
registry.delete(id);
|
|
13913
15155
|
}
|
|
13914
15156
|
}
|
|
13915
15157
|
}
|
|
@@ -13918,16 +15160,19 @@ var GlobalMailbox = class {
|
|
|
13918
15160
|
for (const [id, client] of registry) {
|
|
13919
15161
|
obj[id] = client;
|
|
13920
15162
|
}
|
|
13921
|
-
const tmp = `${this.clientRegistryPath}.${
|
|
13922
|
-
await
|
|
13923
|
-
await
|
|
15163
|
+
const tmp = `${this.clientRegistryPath}.${randomUUID10().slice(0, 8)}.tmp`;
|
|
15164
|
+
await fsp11.writeFile(tmp, JSON.stringify(obj), "utf8");
|
|
15165
|
+
await fsp11.rename(tmp, this.clientRegistryPath);
|
|
13924
15166
|
}
|
|
13925
15167
|
};
|
|
15168
|
+
|
|
15169
|
+
// src/coordination/mailbox-tool.ts
|
|
15170
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
13926
15171
|
function defaultResolveProjectDir(ctx) {
|
|
13927
15172
|
return resolveProjectDir(ctx.projectRoot, wstackGlobalRoot());
|
|
13928
15173
|
}
|
|
13929
15174
|
function mailboxSessionTag(sessionId) {
|
|
13930
|
-
return
|
|
15175
|
+
return createHash5("sha256").update(sessionId).digest("hex").slice(0, 8);
|
|
13931
15176
|
}
|
|
13932
15177
|
function resolveMailboxIdentity(ctx, fallbackBase = "leader") {
|
|
13933
15178
|
const fieldId = ctx.agentId && ctx.agentId !== "unknown" ? ctx.agentId : void 0;
|
|
@@ -13964,7 +15209,7 @@ function makeMailboxTool(opts = {}) {
|
|
|
13964
15209
|
description: "Which mailbox operation to perform."
|
|
13965
15210
|
},
|
|
13966
15211
|
to: { type: "string", description: "Recipient agent id, or '*' / 'all' for broadcast." },
|
|
13967
|
-
type: { type: "string", enum: ["note", "ask", "assign", "steer", "btw", "broadcast", "status", "result", "review"], description: "Message type." },
|
|
15212
|
+
type: { type: "string", enum: ["note", "ask", "assign", "steer", "btw", "broadcast", "status", "result", "review", "control"], description: "Message type." },
|
|
13968
15213
|
subject: { type: "string", description: "Short subject line." },
|
|
13969
15214
|
body: { type: "string", description: "Full message content." },
|
|
13970
15215
|
priority: { type: "string", enum: ["low", "normal", "high"] },
|
|
@@ -14273,6 +15518,9 @@ function makeFleetStatusTool(opts = {}) {
|
|
|
14273
15518
|
}
|
|
14274
15519
|
};
|
|
14275
15520
|
}
|
|
15521
|
+
|
|
15522
|
+
// src/coordination/fleet-supervisor.ts
|
|
15523
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
14276
15524
|
var COLLAB_ID_PREFIXES = ["bug-hunter-", "refactor-planner-", "critic-"];
|
|
14277
15525
|
var DEFAULTS = {
|
|
14278
15526
|
intervalMs: 2e4,
|
|
@@ -14514,7 +15762,7 @@ var FleetSupervisor = class {
|
|
|
14514
15762
|
*/
|
|
14515
15763
|
async decide(question, context, options, risk) {
|
|
14516
15764
|
const request = {
|
|
14517
|
-
id: `fleetsup-${
|
|
15765
|
+
id: `fleetsup-${randomUUID11()}`,
|
|
14518
15766
|
sessionId: this.opts.sessionId?.(),
|
|
14519
15767
|
source: "system",
|
|
14520
15768
|
question,
|
|
@@ -15002,6 +16250,11 @@ function makeMailInboxTool(opts = {}) {
|
|
|
15002
16250
|
}
|
|
15003
16251
|
};
|
|
15004
16252
|
}
|
|
16253
|
+
|
|
16254
|
+
// src/coordination/mailbox.ts
|
|
16255
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
16256
|
+
import * as fsp12 from "node:fs/promises";
|
|
16257
|
+
import * as path15 from "node:path";
|
|
15005
16258
|
var MAILBOX_FILE2 = "_mailbox.jsonl";
|
|
15006
16259
|
var LINE_SEPARATOR2 = "\n";
|
|
15007
16260
|
var MESSAGE_CACHE_MAX_ENTRIES2 = 1e4;
|
|
@@ -15010,14 +16263,18 @@ var DefaultMailbox = class {
|
|
|
15010
16263
|
_messageCache = null;
|
|
15011
16264
|
_messageCacheMtime = -1;
|
|
15012
16265
|
_messageCacheSize = -1;
|
|
16266
|
+
/** True when the cache is only the newest MESSAGE_CACHE_MAX_ENTRIES records. */
|
|
16267
|
+
_messageCacheTruncated = false;
|
|
15013
16268
|
/** Primary index: recipient → Set of messages (points into _messageCache). */
|
|
15014
16269
|
_byTo = /* @__PURE__ */ new Map();
|
|
15015
16270
|
/** Secondary index: sender → Set of messages (points into _messageCache). */
|
|
15016
16271
|
_byFrom = /* @__PURE__ */ new Map();
|
|
15017
16272
|
/** Counts malformed JSONL lines skipped during parsing for observability. */
|
|
15018
16273
|
_corruptionCount = 0;
|
|
15019
|
-
|
|
15020
|
-
|
|
16274
|
+
logger;
|
|
16275
|
+
constructor(sessionDir, opts) {
|
|
16276
|
+
this.filePath = path15.join(sessionDir, MAILBOX_FILE2);
|
|
16277
|
+
this.logger = opts?.logger;
|
|
15021
16278
|
}
|
|
15022
16279
|
get mailboxPath() {
|
|
15023
16280
|
return this.filePath;
|
|
@@ -15030,12 +16287,12 @@ var DefaultMailbox = class {
|
|
|
15030
16287
|
async send(input) {
|
|
15031
16288
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15032
16289
|
const msg = {
|
|
15033
|
-
id:
|
|
16290
|
+
id: randomUUID12(),
|
|
15034
16291
|
from: input.from,
|
|
15035
16292
|
// "all" is an accepted spelling of the broadcast address — canonical
|
|
15036
16293
|
// form on disk is '*' so every query/checker matches it.
|
|
15037
16294
|
to: normalizeRecipient(input.to),
|
|
15038
|
-
type: input.type,
|
|
16295
|
+
type: normalizeMailboxMessageType(input.type),
|
|
15039
16296
|
subject: input.subject,
|
|
15040
16297
|
body: input.body,
|
|
15041
16298
|
priority: input.priority ?? "normal",
|
|
@@ -15046,9 +16303,9 @@ var DefaultMailbox = class {
|
|
|
15046
16303
|
taskContext: input.taskContext
|
|
15047
16304
|
};
|
|
15048
16305
|
const line = JSON.stringify(msg) + LINE_SEPARATOR2;
|
|
15049
|
-
await
|
|
16306
|
+
await fsp12.mkdir(path15.dirname(this.filePath), { recursive: true });
|
|
15050
16307
|
await withFileLock(this.filePath, async () => {
|
|
15051
|
-
await
|
|
16308
|
+
await fsp12.appendFile(this.filePath, line, "utf8");
|
|
15052
16309
|
this._pushToCache(msg);
|
|
15053
16310
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
15054
16311
|
this._messageCacheMtime = mtime;
|
|
@@ -15058,12 +16315,14 @@ var DefaultMailbox = class {
|
|
|
15058
16315
|
}
|
|
15059
16316
|
// ── Query ─────────────────────────────────────────────────────────────
|
|
15060
16317
|
async query(q) {
|
|
16318
|
+
const queryType = q.type === void 0 ? void 0 : normalizeMailboxMessageType(q.type);
|
|
15061
16319
|
const needFullScan = q.unreadBy !== void 0 || q.since !== void 0;
|
|
16320
|
+
const cached = await this._readAllCached(false);
|
|
15062
16321
|
let candidates;
|
|
16322
|
+
let candidatesComplete = !this._messageCacheTruncated || cached !== this._messageCache;
|
|
15063
16323
|
if (needFullScan) {
|
|
15064
|
-
candidates =
|
|
16324
|
+
candidates = cached;
|
|
15065
16325
|
} else {
|
|
15066
|
-
await this._readAllCached();
|
|
15067
16326
|
if (q.to !== void 0) {
|
|
15068
16327
|
const direct = this._byTo.get(q.to);
|
|
15069
16328
|
const broadcast = this._byTo.get("*");
|
|
@@ -15071,10 +16330,12 @@ var DefaultMailbox = class {
|
|
|
15071
16330
|
if (direct) for (const m of direct) combined.set(m.id, m);
|
|
15072
16331
|
if (broadcast) for (const m of broadcast) combined.set(m.id, m);
|
|
15073
16332
|
candidates = Array.from(combined.values());
|
|
16333
|
+
candidatesComplete = !this._messageCacheTruncated;
|
|
15074
16334
|
} else if (q.from !== void 0) {
|
|
15075
16335
|
candidates = Array.from(this._byFrom.get(q.from) ?? []);
|
|
16336
|
+
candidatesComplete = !this._messageCacheTruncated;
|
|
15076
16337
|
} else {
|
|
15077
|
-
candidates =
|
|
16338
|
+
candidates = cached;
|
|
15078
16339
|
}
|
|
15079
16340
|
}
|
|
15080
16341
|
const limit = q.limit ?? 50;
|
|
@@ -15085,27 +16346,37 @@ var DefaultMailbox = class {
|
|
|
15085
16346
|
if (q.from !== void 0 && msg.from !== q.from) return false;
|
|
15086
16347
|
if (q.unreadBy !== void 0 && q.unreadBy in msg.readBy) return false;
|
|
15087
16348
|
if (q.incompleteOnly && msg.completed) return false;
|
|
15088
|
-
if (
|
|
16349
|
+
if (queryType !== void 0 && msg.type !== queryType) return false;
|
|
15089
16350
|
if (order !== null && (order[msg.priority] ?? 1) < minPriorityRank)
|
|
15090
16351
|
return false;
|
|
15091
16352
|
if (q.since !== void 0 && msg.timestamp <= q.since) return false;
|
|
15092
16353
|
return true;
|
|
15093
16354
|
};
|
|
15094
|
-
|
|
16355
|
+
const selectNewest = (source) => {
|
|
15095
16356
|
const out = [];
|
|
15096
|
-
for (let i =
|
|
15097
|
-
const msg =
|
|
16357
|
+
for (let i = source.length - 1; i >= 0 && out.length < limit; i--) {
|
|
16358
|
+
const msg = source[i];
|
|
15098
16359
|
if (passes(msg)) out.push(msg);
|
|
15099
16360
|
}
|
|
15100
16361
|
return out;
|
|
15101
|
-
}
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
}
|
|
15107
|
-
|
|
15108
|
-
|
|
16362
|
+
};
|
|
16363
|
+
if (order === null) {
|
|
16364
|
+
const out = selectNewest(candidates);
|
|
16365
|
+
if (out.length >= limit || candidatesComplete) return out;
|
|
16366
|
+
return selectNewest(await this._readAll());
|
|
16367
|
+
}
|
|
16368
|
+
const selectByPriority = (source) => {
|
|
16369
|
+
const filtered2 = [];
|
|
16370
|
+
for (let i = source.length - 1; i >= 0; i--) {
|
|
16371
|
+
const msg = source[i];
|
|
16372
|
+
if (passes(msg)) filtered2.push(msg);
|
|
16373
|
+
}
|
|
16374
|
+
filtered2.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
16375
|
+
return filtered2.slice(0, limit);
|
|
16376
|
+
};
|
|
16377
|
+
const filtered = selectByPriority(candidates);
|
|
16378
|
+
if (filtered.length >= limit || candidatesComplete) return filtered;
|
|
16379
|
+
return selectByPriority(await this._readAll());
|
|
15109
16380
|
}
|
|
15110
16381
|
// ── Ack ───────────────────────────────────────────────────────────────
|
|
15111
16382
|
async ack(input) {
|
|
@@ -15148,7 +16419,7 @@ var DefaultMailbox = class {
|
|
|
15148
16419
|
}
|
|
15149
16420
|
if (changed) {
|
|
15150
16421
|
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
|
|
15151
|
-
await
|
|
16422
|
+
await fsp12.writeFile(this.filePath, serialized, "utf8");
|
|
15152
16423
|
}
|
|
15153
16424
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
15154
16425
|
this._setMessageCache(all, mtime, size);
|
|
@@ -15162,7 +16433,7 @@ var DefaultMailbox = class {
|
|
|
15162
16433
|
for (const m of all) {
|
|
15163
16434
|
if (m.type !== "status") continue;
|
|
15164
16435
|
const existing = latest.get(m.from);
|
|
15165
|
-
if (existing && m.timestamp
|
|
16436
|
+
if (existing && m.timestamp < existing.lastActivityAt) continue;
|
|
15166
16437
|
latest.set(m.from, {
|
|
15167
16438
|
agentId: m.from,
|
|
15168
16439
|
name: m.taskContext?.agentName ?? m.from,
|
|
@@ -15204,12 +16475,13 @@ var DefaultMailbox = class {
|
|
|
15204
16475
|
this._messageCache = null;
|
|
15205
16476
|
this._messageCacheMtime = -1;
|
|
15206
16477
|
this._messageCacheSize = -1;
|
|
16478
|
+
this._messageCacheTruncated = false;
|
|
15207
16479
|
this._byTo.clear();
|
|
15208
16480
|
this._byFrom.clear();
|
|
15209
16481
|
}
|
|
15210
16482
|
async clearAll() {
|
|
15211
16483
|
await withFileLock(this.filePath, async () => {
|
|
15212
|
-
await
|
|
16484
|
+
await fsp12.writeFile(this.filePath, "", "utf8");
|
|
15213
16485
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
15214
16486
|
this._setMessageCache([], mtime, size);
|
|
15215
16487
|
});
|
|
@@ -15242,7 +16514,7 @@ var DefaultMailbox = class {
|
|
|
15242
16514
|
remaining = kept.length;
|
|
15243
16515
|
if (kept.length < all.length) {
|
|
15244
16516
|
const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
|
|
15245
|
-
await
|
|
16517
|
+
await fsp12.writeFile(this.filePath, content, "utf8");
|
|
15246
16518
|
}
|
|
15247
16519
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
15248
16520
|
this._setMessageCache(kept, mtime, size);
|
|
@@ -15255,17 +16527,36 @@ var DefaultMailbox = class {
|
|
|
15255
16527
|
};
|
|
15256
16528
|
}
|
|
15257
16529
|
// ── Client registry stubs (not applicable per-session) ─────────────────
|
|
16530
|
+
async autoCompact(_opts) {
|
|
16531
|
+
const purged = await this.purgeStale(_opts);
|
|
16532
|
+
return {
|
|
16533
|
+
readByAllRemoved: 0,
|
|
16534
|
+
expiredRemoved: 0,
|
|
16535
|
+
stalePurged: purged.totalPurged,
|
|
16536
|
+
totalRemoved: purged.totalPurged,
|
|
16537
|
+
remaining: purged.remaining
|
|
16538
|
+
};
|
|
16539
|
+
}
|
|
16540
|
+
startAutoCompactTimer(_opts) {
|
|
16541
|
+
return () => {
|
|
16542
|
+
};
|
|
16543
|
+
}
|
|
15258
16544
|
async registerClient(_input) {
|
|
15259
16545
|
}
|
|
15260
16546
|
async clientHeartbeat(_input) {
|
|
15261
16547
|
}
|
|
16548
|
+
async deregisterClient(_clientId) {
|
|
16549
|
+
}
|
|
15262
16550
|
async getClientStatuses() {
|
|
15263
16551
|
return [];
|
|
15264
16552
|
}
|
|
16553
|
+
async purgeClients() {
|
|
16554
|
+
return 0;
|
|
16555
|
+
}
|
|
15265
16556
|
// ── Internal ──────────────────────────────────────────────────────────
|
|
15266
16557
|
async _readAll() {
|
|
15267
16558
|
try {
|
|
15268
|
-
const raw = await
|
|
16559
|
+
const raw = await fsp12.readFile(this.filePath, "utf8");
|
|
15269
16560
|
return this._parseLines(raw);
|
|
15270
16561
|
} catch (err) {
|
|
15271
16562
|
if (err.code === "ENOENT") return [];
|
|
@@ -15275,60 +16566,62 @@ var DefaultMailbox = class {
|
|
|
15275
16566
|
/**
|
|
15276
16567
|
* Read only newly-appended bytes from the file and append them to the
|
|
15277
16568
|
* in-memory cache, avoiding a full re-read when the file only grew.
|
|
16569
|
+
*
|
|
16570
|
+
* Guards against a half-written tail: when the tail doesn't end with
|
|
16571
|
+
* LINE_SEPARATOR, another process may be mid-append. Parsing a partial
|
|
16572
|
+
* JSON line would throw (caught below), but worse: the NEXT incremental
|
|
16573
|
+
* read would skip that same line because the size tracker already advanced.
|
|
15278
16574
|
*/
|
|
15279
16575
|
async _readNewMessagesOnly(fd, oldSize, newSize) {
|
|
15280
16576
|
const tailLen = newSize - oldSize;
|
|
15281
16577
|
const buf = Buffer.alloc(tailLen);
|
|
15282
16578
|
await fd.read(buf, 0, tailLen, oldSize);
|
|
15283
16579
|
const tail = buf.toString("utf8");
|
|
15284
|
-
|
|
16580
|
+
if (!tail.endsWith(LINE_SEPARATOR2)) {
|
|
16581
|
+
const lastSeparatorIdx = tail.lastIndexOf(LINE_SEPARATOR2);
|
|
16582
|
+
if (lastSeparatorIdx === -1) {
|
|
16583
|
+
return this._messageCache;
|
|
16584
|
+
}
|
|
16585
|
+
const completeTail = tail.slice(0, lastSeparatorIdx + 1);
|
|
16586
|
+
this._appendIncrementalLines(completeTail);
|
|
16587
|
+
this._messageCacheSize = oldSize + lastSeparatorIdx + 1;
|
|
16588
|
+
return this._messageCache;
|
|
16589
|
+
}
|
|
16590
|
+
this._appendIncrementalLines(tail);
|
|
16591
|
+
this._messageCacheSize = newSize;
|
|
16592
|
+
return this._messageCache;
|
|
16593
|
+
}
|
|
16594
|
+
_appendIncrementalLines(raw) {
|
|
16595
|
+
for (const line of raw.split(LINE_SEPARATOR2)) {
|
|
15285
16596
|
if (!line.trim()) continue;
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
} catch (err) {
|
|
15301
|
-
this._corruptionCount++;
|
|
15302
|
-
console.debug(
|
|
15303
|
-
`[mailbox] skipped malformed line during incremental read: ${err.message}`
|
|
16597
|
+
const msg = this._parseLine(line, "incremental read");
|
|
16598
|
+
if (msg === null) continue;
|
|
16599
|
+
this._pushToCache(msg);
|
|
16600
|
+
}
|
|
16601
|
+
}
|
|
16602
|
+
_parseLine(line, source) {
|
|
16603
|
+
try {
|
|
16604
|
+
return parseMailboxMessageLine(line);
|
|
16605
|
+
} catch (err) {
|
|
16606
|
+
this._corruptionCount++;
|
|
16607
|
+
if (this.logger) {
|
|
16608
|
+
this.logger.debug(
|
|
16609
|
+
"Skipped malformed mailbox line",
|
|
16610
|
+
{ source, error: err.message, corruptionCount: this._corruptionCount }
|
|
15304
16611
|
);
|
|
16612
|
+
} else {
|
|
16613
|
+
console.debug(`[mailbox] skipped malformed line during ${source}: ${err.message}`);
|
|
15305
16614
|
}
|
|
16615
|
+
return null;
|
|
15306
16616
|
}
|
|
15307
|
-
return this._messageCache;
|
|
15308
16617
|
}
|
|
15309
16618
|
/** Parse a JSONL string into MailboxMessage[], including migration. */
|
|
15310
16619
|
_parseLines(raw) {
|
|
15311
16620
|
const lines = raw.split(LINE_SEPARATOR2).filter((l) => l.trim().length > 0);
|
|
15312
16621
|
const messages = [];
|
|
15313
16622
|
for (const line of lines) {
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
if (!parsed["readBy"]) {
|
|
15317
|
-
const readBy = {};
|
|
15318
|
-
if (parsed["read"] && parsed["readAt"]) {
|
|
15319
|
-
readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
|
|
15320
|
-
}
|
|
15321
|
-
parsed["readBy"] = readBy;
|
|
15322
|
-
delete parsed["read"];
|
|
15323
|
-
delete parsed["readAt"];
|
|
15324
|
-
}
|
|
15325
|
-
messages.push(parsed);
|
|
15326
|
-
} catch (err) {
|
|
15327
|
-
this._corruptionCount++;
|
|
15328
|
-
console.debug(
|
|
15329
|
-
`[mailbox] skipped malformed line during full parse: ${err.message}`
|
|
15330
|
-
);
|
|
15331
|
-
}
|
|
16623
|
+
const message = this._parseLine(line, "full parse");
|
|
16624
|
+
if (message !== null) messages.push(message);
|
|
15332
16625
|
}
|
|
15333
16626
|
return messages;
|
|
15334
16627
|
}
|
|
@@ -15340,7 +16633,7 @@ var DefaultMailbox = class {
|
|
|
15340
16633
|
*/
|
|
15341
16634
|
async _statUnderLockOrAbsent() {
|
|
15342
16635
|
try {
|
|
15343
|
-
const st = await
|
|
16636
|
+
const st = await fsp12.stat(this.filePath);
|
|
15344
16637
|
return { mtime: st.mtimeMs, size: st.size };
|
|
15345
16638
|
} catch (err) {
|
|
15346
16639
|
if (err.code === "ENOENT") {
|
|
@@ -15349,18 +16642,19 @@ var DefaultMailbox = class {
|
|
|
15349
16642
|
throw err;
|
|
15350
16643
|
}
|
|
15351
16644
|
}
|
|
15352
|
-
async _readAllCached() {
|
|
16645
|
+
async _readAllCached(requireComplete = true) {
|
|
15353
16646
|
try {
|
|
15354
|
-
const st = await
|
|
16647
|
+
const st = await fsp12.stat(this.filePath);
|
|
15355
16648
|
if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
|
|
16649
|
+
if (requireComplete && this._messageCacheTruncated) return this._readAll();
|
|
15356
16650
|
return this._messageCache;
|
|
15357
16651
|
}
|
|
15358
16652
|
if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
|
|
15359
|
-
const fd = await
|
|
16653
|
+
const fd = await fsp12.open(this.filePath, "r");
|
|
15360
16654
|
try {
|
|
15361
16655
|
const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
|
|
15362
16656
|
this._messageCacheMtime = st.mtimeMs;
|
|
15363
|
-
this.
|
|
16657
|
+
if (requireComplete && this._messageCacheTruncated) return this._readAll();
|
|
15364
16658
|
return updated;
|
|
15365
16659
|
} finally {
|
|
15366
16660
|
await fd.close();
|
|
@@ -15378,28 +16672,18 @@ var DefaultMailbox = class {
|
|
|
15378
16672
|
}
|
|
15379
16673
|
}
|
|
15380
16674
|
_setMessageCache(messages, mtime, size) {
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
this._messageCacheSize = -1;
|
|
15385
|
-
this._byTo.clear();
|
|
15386
|
-
this._byFrom.clear();
|
|
15387
|
-
return;
|
|
15388
|
-
}
|
|
15389
|
-
this._messageCache = messages;
|
|
15390
|
-
this._buildIndexes(messages);
|
|
16675
|
+
this._messageCacheTruncated = messages.length > MESSAGE_CACHE_MAX_ENTRIES2;
|
|
16676
|
+
this._messageCache = this._messageCacheTruncated ? messages.slice(-MESSAGE_CACHE_MAX_ENTRIES2) : messages;
|
|
16677
|
+
this._buildIndexes(this._messageCache);
|
|
15391
16678
|
this._messageCacheMtime = mtime;
|
|
15392
16679
|
this._messageCacheSize = size;
|
|
15393
16680
|
}
|
|
15394
16681
|
_pushToCache(msg) {
|
|
15395
16682
|
if (this._messageCache === null) return;
|
|
15396
16683
|
if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES2) {
|
|
15397
|
-
this._messageCache
|
|
15398
|
-
this.
|
|
15399
|
-
this.
|
|
15400
|
-
this._byTo.clear();
|
|
15401
|
-
this._byFrom.clear();
|
|
15402
|
-
return;
|
|
16684
|
+
const evicted = this._messageCache.shift();
|
|
16685
|
+
if (evicted !== void 0) this._unindexMsg(evicted);
|
|
16686
|
+
this._messageCacheTruncated = true;
|
|
15403
16687
|
}
|
|
15404
16688
|
this._messageCache.push(msg);
|
|
15405
16689
|
this._indexMsg(msg);
|
|
@@ -15427,6 +16711,15 @@ var DefaultMailbox = class {
|
|
|
15427
16711
|
this._byFrom.set(msg.from, /* @__PURE__ */ new Set([msg]));
|
|
15428
16712
|
}
|
|
15429
16713
|
}
|
|
16714
|
+
/** Remove an evicted message from both indexes without rebuilding them. */
|
|
16715
|
+
_unindexMsg(msg) {
|
|
16716
|
+
const toSet = this._byTo.get(msg.to);
|
|
16717
|
+
toSet?.delete(msg);
|
|
16718
|
+
if (toSet?.size === 0) this._byTo.delete(msg.to);
|
|
16719
|
+
const fromSet = this._byFrom.get(msg.from);
|
|
16720
|
+
fromSet?.delete(msg);
|
|
16721
|
+
if (fromSet?.size === 0) this._byFrom.delete(msg.from);
|
|
16722
|
+
}
|
|
15430
16723
|
};
|
|
15431
16724
|
|
|
15432
16725
|
// src/coordination/mailbox-actions.ts
|
|
@@ -15695,14 +16988,18 @@ function createMailboxHooks(opts) {
|
|
|
15695
16988
|
|
|
15696
16989
|
// src/coordination/null-fleet-bus.ts
|
|
15697
16990
|
var NULL_FLEET_BUS = new FleetBus();
|
|
16991
|
+
|
|
16992
|
+
// src/coordination/package-author-tracker.ts
|
|
16993
|
+
import * as fs2 from "node:fs/promises";
|
|
16994
|
+
import * as path16 from "node:path";
|
|
15698
16995
|
var DEFAULT_MAX_ENTRIES = 1e4;
|
|
15699
16996
|
var LOG_FILENAME = "package-authors.json";
|
|
15700
16997
|
function logPath(storageDir) {
|
|
15701
|
-
return
|
|
16998
|
+
return path16.join(storageDir, LOG_FILENAME);
|
|
15702
16999
|
}
|
|
15703
17000
|
async function loadLog(storageDir, projectRoot) {
|
|
15704
17001
|
try {
|
|
15705
|
-
const raw = await
|
|
17002
|
+
const raw = await fs2.readFile(logPath(storageDir), "utf-8");
|
|
15706
17003
|
const parsed = JSON.parse(raw);
|
|
15707
17004
|
if (!parsed.entries || !Array.isArray(parsed.entries)) {
|
|
15708
17005
|
return { projectRoot, entries: [] };
|
|
@@ -15716,13 +17013,13 @@ async function loadLog(storageDir, projectRoot) {
|
|
|
15716
17013
|
}
|
|
15717
17014
|
}
|
|
15718
17015
|
async function saveLog(storageDir, log) {
|
|
15719
|
-
await
|
|
17016
|
+
await fs2.mkdir(storageDir, { recursive: true });
|
|
15720
17017
|
const tmp = `${logPath(storageDir)}.tmp.${Date.now()}`;
|
|
15721
|
-
await
|
|
15722
|
-
await
|
|
17018
|
+
await fs2.writeFile(tmp, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
17019
|
+
await fs2.rename(tmp, logPath(storageDir));
|
|
15723
17020
|
}
|
|
15724
17021
|
function detectEcosystem(manifestPath) {
|
|
15725
|
-
const name =
|
|
17022
|
+
const name = path16.win32.basename(manifestPath).toLowerCase();
|
|
15726
17023
|
if (name === "package.json") return "npm";
|
|
15727
17024
|
if (name === "go.mod") return "go";
|
|
15728
17025
|
if (name === "cargo.toml") return "cargo";
|
|
@@ -15967,11 +17264,18 @@ mvn versions:use-latest-versions`;
|
|
|
15967
17264
|
return `# Update ${entry.name} to ${entry.latestVersion} using your package manager`;
|
|
15968
17265
|
}
|
|
15969
17266
|
}
|
|
17267
|
+
|
|
17268
|
+
// src/coordination/single-instance-mailbox.ts
|
|
17269
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
17270
|
+
import * as fsp13 from "node:fs/promises";
|
|
17271
|
+
import { execFileSync } from "node:child_process";
|
|
17272
|
+
import * as os3 from "node:os";
|
|
17273
|
+
import * as path17 from "node:path";
|
|
15970
17274
|
var MAILBOX_BRIDGE_LOCK_FILENAME = ".mailbox-bridge.lock";
|
|
15971
17275
|
var MAILBOX_BRIDGE_TOKEN_FILENAME = ".mailbox.token";
|
|
15972
17276
|
async function acquireOrJoin(opts) {
|
|
15973
|
-
const lockPath =
|
|
15974
|
-
const tokenPath =
|
|
17277
|
+
const lockPath = path17.join(opts.projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
17278
|
+
const tokenPath = path17.join(opts.projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
15975
17279
|
const inspected = await readLockForInspection(lockPath);
|
|
15976
17280
|
if (inspected.kind === "live") {
|
|
15977
17281
|
const existing = inspected.lock;
|
|
@@ -15981,10 +17285,10 @@ async function acquireOrJoin(opts) {
|
|
|
15981
17285
|
return { kind: "joined", lock: existing, tokenPath };
|
|
15982
17286
|
}
|
|
15983
17287
|
if (inspected.kind === "stale") {
|
|
15984
|
-
await
|
|
17288
|
+
await fsp13.unlink(lockPath).catch(() => void 0);
|
|
15985
17289
|
}
|
|
15986
17290
|
const generation = inspected.kind === "stale" ? inspected.lock.generation + 1 : 1;
|
|
15987
|
-
const token =
|
|
17291
|
+
const token = randomBytes3(32).toString("hex");
|
|
15988
17292
|
const tentative = {
|
|
15989
17293
|
pid: process.pid,
|
|
15990
17294
|
host: opts.host,
|
|
@@ -15999,34 +17303,34 @@ async function acquireOrJoin(opts) {
|
|
|
15999
17303
|
return { kind: "acquired", lock: tentative, tokenPath };
|
|
16000
17304
|
}
|
|
16001
17305
|
async function finalize(projectDir, tentative, boundPort) {
|
|
16002
|
-
const lockPath =
|
|
16003
|
-
const tokenPath =
|
|
17306
|
+
const lockPath = path17.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
17307
|
+
const tokenPath = path17.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
16004
17308
|
const finalized = {
|
|
16005
17309
|
...tentative,
|
|
16006
17310
|
port: boundPort,
|
|
16007
17311
|
url: `http://${tentative.host}:${boundPort}`
|
|
16008
17312
|
};
|
|
16009
17313
|
await atomicWriteJson(lockPath, finalized);
|
|
16010
|
-
await
|
|
17314
|
+
await fsp13.writeFile(tokenPath, finalized.token, { mode: 384 });
|
|
16011
17315
|
return finalized;
|
|
16012
17316
|
}
|
|
16013
17317
|
async function release(projectDir, generation) {
|
|
16014
|
-
const lockPath =
|
|
16015
|
-
const tokenPath =
|
|
17318
|
+
const lockPath = path17.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
17319
|
+
const tokenPath = path17.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
16016
17320
|
try {
|
|
16017
|
-
const raw = await
|
|
17321
|
+
const raw = await fsp13.readFile(lockPath, "utf-8");
|
|
16018
17322
|
const parsed = JSON.parse(raw);
|
|
16019
17323
|
if (parsed.generation !== generation) return;
|
|
16020
17324
|
if (parsed.pid !== process.pid) return;
|
|
16021
|
-
await
|
|
16022
|
-
await
|
|
17325
|
+
await fsp13.unlink(lockPath).catch(() => void 0);
|
|
17326
|
+
await fsp13.unlink(tokenPath).catch(() => void 0);
|
|
16023
17327
|
} catch {
|
|
16024
17328
|
}
|
|
16025
17329
|
}
|
|
16026
17330
|
async function readLockForInspection(lockPath) {
|
|
16027
17331
|
let raw;
|
|
16028
17332
|
try {
|
|
16029
|
-
raw = await
|
|
17333
|
+
raw = await fsp13.readFile(lockPath, "utf-8");
|
|
16030
17334
|
} catch {
|
|
16031
17335
|
return { kind: "absent" };
|
|
16032
17336
|
}
|
|
@@ -16034,7 +17338,7 @@ async function readLockForInspection(lockPath) {
|
|
|
16034
17338
|
try {
|
|
16035
17339
|
parsed = JSON.parse(raw);
|
|
16036
17340
|
} catch {
|
|
16037
|
-
await
|
|
17341
|
+
await fsp13.unlink(lockPath).catch(() => void 0);
|
|
16038
17342
|
return { kind: "absent" };
|
|
16039
17343
|
}
|
|
16040
17344
|
if (!isProcessAlive(parsed.pid)) {
|
|
@@ -16046,7 +17350,7 @@ async function readLockForInspection(lockPath) {
|
|
|
16046
17350
|
return { kind: "live", lock: parsed };
|
|
16047
17351
|
}
|
|
16048
17352
|
async function readLiveLock(projectDir) {
|
|
16049
|
-
const lockPath =
|
|
17353
|
+
const lockPath = path17.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
16050
17354
|
const result = await readLockForInspection(lockPath);
|
|
16051
17355
|
if (result.kind === "live") {
|
|
16052
17356
|
return { kind: "live", lock: result.lock };
|
|
@@ -16057,28 +17361,29 @@ async function readLiveLock(projectDir) {
|
|
|
16057
17361
|
return { kind: "absent" };
|
|
16058
17362
|
}
|
|
16059
17363
|
async function atomicWriteJson(targetPath, value) {
|
|
16060
|
-
const dir =
|
|
16061
|
-
await
|
|
16062
|
-
const tmp = `${targetPath}.tmp.${process.pid}.${
|
|
17364
|
+
const dir = path17.dirname(targetPath);
|
|
17365
|
+
await fsp13.mkdir(dir, { recursive: true });
|
|
17366
|
+
const tmp = `${targetPath}.tmp.${process.pid}.${randomBytes3(4).toString("hex")}`;
|
|
16063
17367
|
const body = JSON.stringify(value, null, 2) + "\n";
|
|
16064
|
-
await
|
|
17368
|
+
await fsp13.writeFile(tmp, body, { mode: 384 });
|
|
16065
17369
|
try {
|
|
16066
|
-
await
|
|
17370
|
+
await fsp13.rename(tmp, targetPath);
|
|
16067
17371
|
} catch (err) {
|
|
16068
|
-
await
|
|
17372
|
+
await fsp13.unlink(tmp).catch(() => void 0);
|
|
16069
17373
|
throw err;
|
|
16070
17374
|
}
|
|
16071
17375
|
}
|
|
16072
17376
|
function isProcessAlive(pid) {
|
|
16073
17377
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
16074
17378
|
if (pid === process.pid) return true;
|
|
16075
|
-
if (
|
|
17379
|
+
if (os3.platform() === "win32") {
|
|
16076
17380
|
try {
|
|
16077
17381
|
const out = execFileSync("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
16078
17382
|
encoding: "utf-8",
|
|
16079
17383
|
stdio: ["pipe", "pipe", "pipe"]
|
|
16080
17384
|
});
|
|
16081
|
-
|
|
17385
|
+
const match = /(?:^|\n)"[^"]*","(\d+)"/.exec(out);
|
|
17386
|
+
return match !== null && match[1] === String(pid);
|
|
16082
17387
|
} catch {
|
|
16083
17388
|
return false;
|
|
16084
17389
|
}
|
|
@@ -16279,7 +17584,11 @@ var AdaptiveConcurrencyController = class {
|
|
|
16279
17584
|
};
|
|
16280
17585
|
}
|
|
16281
17586
|
};
|
|
16282
|
-
|
|
17587
|
+
|
|
17588
|
+
// src/coordination/agent-monitor.ts
|
|
17589
|
+
import * as fs3 from "node:fs/promises";
|
|
17590
|
+
import * as path18 from "node:path";
|
|
17591
|
+
var AgentMonitorService = class _AgentMonitorService {
|
|
16283
17592
|
_fleetBus;
|
|
16284
17593
|
_events;
|
|
16285
17594
|
_sessionId;
|
|
@@ -16289,6 +17598,15 @@ var AgentMonitorService = class {
|
|
|
16289
17598
|
_onEntry;
|
|
16290
17599
|
/** Per-subagent virtual sessions. */
|
|
16291
17600
|
_sessions = /* @__PURE__ */ new Map();
|
|
17601
|
+
/**
|
|
17602
|
+
* Per-subagent OPEN streaming segment. Provider text/thinking deltas
|
|
17603
|
+
* arrive word-by-word; writing one transcript entry per delta fragments
|
|
17604
|
+
* every surface (ring, JSONL, HQ) into unreadable single-word lines.
|
|
17605
|
+
* Instead, consecutive same-kind/same-iteration deltas append into ONE
|
|
17606
|
+
* entry that is already in the ring (live views see it grow in place);
|
|
17607
|
+
* the JSONL line is written once, when the segment closes.
|
|
17608
|
+
*/
|
|
17609
|
+
_openStreams = /* @__PURE__ */ new Map();
|
|
16292
17610
|
/** Disposers for FleetBus subscriptions, keyed by subagentId. */
|
|
16293
17611
|
_subscriptions = /* @__PURE__ */ new Map();
|
|
16294
17612
|
/** Generic fleet-wide subscription disposer. */
|
|
@@ -16351,6 +17669,9 @@ var AgentMonitorService = class {
|
|
|
16351
17669
|
stop() {
|
|
16352
17670
|
if (!this._started) return;
|
|
16353
17671
|
this._started = false;
|
|
17672
|
+
for (const subagentId of [...this._openStreams.keys()]) {
|
|
17673
|
+
this._closeStream(subagentId);
|
|
17674
|
+
}
|
|
16354
17675
|
if (this._fleetDisposer) {
|
|
16355
17676
|
this._fleetDisposer();
|
|
16356
17677
|
this._fleetDisposer = void 0;
|
|
@@ -16417,6 +17738,14 @@ var AgentMonitorService = class {
|
|
|
16417
17738
|
task: session.task
|
|
16418
17739
|
});
|
|
16419
17740
|
}
|
|
17741
|
+
/** Drop a retired subagent from every live in-memory monitor surface. */
|
|
17742
|
+
removeSubagent(subagentId) {
|
|
17743
|
+
this._closeStream(subagentId);
|
|
17744
|
+
this._sessions.delete(subagentId);
|
|
17745
|
+
const dispose = this._subscriptions.get(subagentId);
|
|
17746
|
+
if (dispose) dispose();
|
|
17747
|
+
this._subscriptions.delete(subagentId);
|
|
17748
|
+
}
|
|
16420
17749
|
// ── Internal ───────────────────────────────────────────────────────
|
|
16421
17750
|
_routeEvent(subagentId, type, payload) {
|
|
16422
17751
|
const session = this._sessions.get(subagentId);
|
|
@@ -16425,31 +17754,13 @@ var AgentMonitorService = class {
|
|
|
16425
17754
|
case "provider.text_delta": {
|
|
16426
17755
|
const text = payload.text;
|
|
16427
17756
|
if (!text || text.length === 0) return;
|
|
16428
|
-
|
|
16429
|
-
this._addEntry(subagentId, {
|
|
16430
|
-
id: this._uid(),
|
|
16431
|
-
subagentId,
|
|
16432
|
-
agentName: session.agentName,
|
|
16433
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16434
|
-
kind: "text",
|
|
16435
|
-
content: text,
|
|
16436
|
-
iteration
|
|
16437
|
-
});
|
|
17757
|
+
this._appendStreamDelta(subagentId, session, "text", text, payload.iteration ?? 0);
|
|
16438
17758
|
break;
|
|
16439
17759
|
}
|
|
16440
17760
|
case "provider.thinking_delta": {
|
|
16441
17761
|
const text = payload.text;
|
|
16442
17762
|
if (!text || text.length === 0) return;
|
|
16443
|
-
|
|
16444
|
-
this._addEntry(subagentId, {
|
|
16445
|
-
id: this._uid(),
|
|
16446
|
-
subagentId,
|
|
16447
|
-
agentName: session.agentName,
|
|
16448
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16449
|
-
kind: "thinking",
|
|
16450
|
-
content: text,
|
|
16451
|
-
iteration
|
|
16452
|
-
});
|
|
17763
|
+
this._appendStreamDelta(subagentId, session, "thinking", text, payload.iteration ?? 0);
|
|
16453
17764
|
break;
|
|
16454
17765
|
}
|
|
16455
17766
|
case "tool.started": {
|
|
@@ -16506,15 +17817,66 @@ var AgentMonitorService = class {
|
|
|
16506
17817
|
}
|
|
16507
17818
|
}
|
|
16508
17819
|
}
|
|
17820
|
+
/** Max characters accumulated into one streaming segment before rolling over. */
|
|
17821
|
+
static _MAX_SEGMENT_CHARS = 2e4;
|
|
17822
|
+
/**
|
|
17823
|
+
* Fold a provider text/thinking delta into the subagent's open streaming
|
|
17824
|
+
* segment, or open a new one. A segment breaks on kind change, iteration
|
|
17825
|
+
* change, size cap, or any non-delta entry (see `_addEntry`). The entry
|
|
17826
|
+
* is pushed to the ring when the segment OPENS (live views poll the ring
|
|
17827
|
+
* and watch it grow in place); the JSONL line AND the timeline/HQ
|
|
17828
|
+
* announcement happen once, on close, with the COMPLETE segment — so
|
|
17829
|
+
* downstream consumers get one whole message instead of a word-per-event
|
|
17830
|
+
* firehose (or, worse, only the first word).
|
|
17831
|
+
*/
|
|
17832
|
+
_appendStreamDelta(subagentId, session, kind, text, iteration) {
|
|
17833
|
+
const open7 = this._openStreams.get(subagentId);
|
|
17834
|
+
if (open7 && open7.entry.kind === kind && open7.entry.iteration === iteration && open7.entry.content.length + text.length <= _AgentMonitorService._MAX_SEGMENT_CHARS) {
|
|
17835
|
+
open7.entry.content += text;
|
|
17836
|
+
return;
|
|
17837
|
+
}
|
|
17838
|
+
this._closeStream(subagentId);
|
|
17839
|
+
const entry = {
|
|
17840
|
+
id: this._uid(),
|
|
17841
|
+
subagentId,
|
|
17842
|
+
agentName: session.agentName,
|
|
17843
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17844
|
+
kind,
|
|
17845
|
+
content: text,
|
|
17846
|
+
iteration
|
|
17847
|
+
};
|
|
17848
|
+
this._openStreams.set(subagentId, { entry });
|
|
17849
|
+
session.transcript.push(entry);
|
|
17850
|
+
if (session.transcript.length > this._maxEntries) {
|
|
17851
|
+
session.transcript.splice(0, session.transcript.length - this._maxEntries);
|
|
17852
|
+
}
|
|
17853
|
+
}
|
|
17854
|
+
/**
|
|
17855
|
+
* Close the subagent's open streaming segment: persist it to JSONL and
|
|
17856
|
+
* announce the completed entry on the local bus + HQ callback.
|
|
17857
|
+
*/
|
|
17858
|
+
_closeStream(subagentId) {
|
|
17859
|
+
const open7 = this._openStreams.get(subagentId);
|
|
17860
|
+
if (!open7) return;
|
|
17861
|
+
this._openStreams.delete(subagentId);
|
|
17862
|
+
this._appendToFile(subagentId, open7.entry).catch(() => {
|
|
17863
|
+
});
|
|
17864
|
+
this._emitEntry(open7.entry);
|
|
17865
|
+
}
|
|
16509
17866
|
_addEntry(subagentId, entry) {
|
|
16510
17867
|
const session = this._sessions.get(subagentId);
|
|
16511
17868
|
if (!session) return;
|
|
17869
|
+
this._closeStream(subagentId);
|
|
16512
17870
|
session.transcript.push(entry);
|
|
16513
17871
|
if (session.transcript.length > this._maxEntries) {
|
|
16514
17872
|
session.transcript.splice(0, session.transcript.length - this._maxEntries);
|
|
16515
17873
|
}
|
|
16516
17874
|
this._appendToFile(subagentId, entry).catch(() => {
|
|
16517
17875
|
});
|
|
17876
|
+
this._emitEntry(entry);
|
|
17877
|
+
}
|
|
17878
|
+
/** Announce a transcript entry on the local bus + HQ callback. */
|
|
17879
|
+
_emitEntry(entry) {
|
|
16518
17880
|
this._events.emit("agent.timeline.message", {
|
|
16519
17881
|
sessionId: this._sessionId,
|
|
16520
17882
|
subagentId: entry.subagentId,
|
|
@@ -16529,11 +17891,11 @@ var AgentMonitorService = class {
|
|
|
16529
17891
|
this._onEntry?.(entry);
|
|
16530
17892
|
}
|
|
16531
17893
|
async _appendToFile(subagentId, entry) {
|
|
16532
|
-
const dir =
|
|
16533
|
-
await
|
|
16534
|
-
const filePath =
|
|
17894
|
+
const dir = path18.join(this._transcriptsDir, subagentId);
|
|
17895
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
17896
|
+
const filePath = path18.join(dir, "transcript.jsonl");
|
|
16535
17897
|
const line = JSON.stringify(entry) + "\n";
|
|
16536
|
-
await
|
|
17898
|
+
await fs3.appendFile(filePath, line, { encoding: "utf8" });
|
|
16537
17899
|
}
|
|
16538
17900
|
_uid() {
|
|
16539
17901
|
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -16541,8 +17903,11 @@ var AgentMonitorService = class {
|
|
|
16541
17903
|
_formatToolUse(name, input) {
|
|
16542
17904
|
if (input === void 0) return `${name}()`;
|
|
16543
17905
|
const body = this._stringifyForTimeline(input);
|
|
16544
|
-
|
|
16545
|
-
|
|
17906
|
+
if (!body) return `${name}()`;
|
|
17907
|
+
const inline = body.replace(/\s+/g, " ").trim();
|
|
17908
|
+
const head = inline.length <= 160 ? `${name}(${inline})` : `${name}(${inline.slice(0, 159)}\u2026)`;
|
|
17909
|
+
return `${head}
|
|
17910
|
+
${body}`;
|
|
16546
17911
|
}
|
|
16547
17912
|
_formatToolResult(summary, output, outputBytes) {
|
|
16548
17913
|
if (!output) return summary;
|
|
@@ -16568,6 +17933,9 @@ ${output}${suffix}`;
|
|
|
16568
17933
|
function createAgentMonitorService(opts) {
|
|
16569
17934
|
return new AgentMonitorService(opts);
|
|
16570
17935
|
}
|
|
17936
|
+
|
|
17937
|
+
// src/coordination/autonomous-brain.ts
|
|
17938
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
16571
17939
|
var AutonomousBrain = class {
|
|
16572
17940
|
graph;
|
|
16573
17941
|
// Fleet bus for emitting decisions — null-safe, no-op if not provided
|
|
@@ -16576,8 +17944,6 @@ var AutonomousBrain = class {
|
|
|
16576
17944
|
maxRetries;
|
|
16577
17945
|
consensusRiskThreshold;
|
|
16578
17946
|
selfImprove;
|
|
16579
|
-
/** Decision history for self-improvement and audit. */
|
|
16580
|
-
decisionHistory = [];
|
|
16581
17947
|
/** Tracks failure patterns for self-improvement. */
|
|
16582
17948
|
failurePatterns = /* @__PURE__ */ new Map();
|
|
16583
17949
|
RISK_ORDER = ["low", "medium", "high", "critical"];
|
|
@@ -16675,7 +18041,7 @@ var AutonomousBrain = class {
|
|
|
16675
18041
|
consequence: i === 0 ? `Spawn the most appropriate agent for: ${taskDescription.slice(0, 80)}` : `Spawn an alternative agent for the same task`
|
|
16676
18042
|
}));
|
|
16677
18043
|
return this.decideAuto({
|
|
16678
|
-
id:
|
|
18044
|
+
id: randomUUID13(),
|
|
16679
18045
|
source,
|
|
16680
18046
|
decisionType: "spawn",
|
|
16681
18047
|
question: `Should we spawn a subagent for this task?`,
|
|
@@ -16718,7 +18084,7 @@ var AutonomousBrain = class {
|
|
|
16718
18084
|
}
|
|
16719
18085
|
];
|
|
16720
18086
|
return this.decideAuto({
|
|
16721
|
-
id:
|
|
18087
|
+
id: randomUUID13(),
|
|
16722
18088
|
source,
|
|
16723
18089
|
decisionType: "approve_change",
|
|
16724
18090
|
question: `Should we approve the change "${change.title}"?`,
|
|
@@ -16777,7 +18143,7 @@ var AutonomousBrain = class {
|
|
|
16777
18143
|
consequence: "Break the task into smaller sub-tasks"
|
|
16778
18144
|
});
|
|
16779
18145
|
return this.decideAuto({
|
|
16780
|
-
id:
|
|
18146
|
+
id: randomUUID13(),
|
|
16781
18147
|
source,
|
|
16782
18148
|
decisionType: "escalate_task",
|
|
16783
18149
|
question: `Task failed: ${error.slice(0, 100)}. How should we proceed?`,
|
|
@@ -16889,7 +18255,6 @@ ${ctx.error}`);
|
|
|
16889
18255
|
madeAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16890
18256
|
context: input.context
|
|
16891
18257
|
});
|
|
16892
|
-
this.decisionHistory.push(node);
|
|
16893
18258
|
return node;
|
|
16894
18259
|
}
|
|
16895
18260
|
_inferRoles(task) {
|
|
@@ -16910,7 +18275,16 @@ ${ctx.error}`);
|
|
|
16910
18275
|
return "low";
|
|
16911
18276
|
}
|
|
16912
18277
|
};
|
|
16913
|
-
|
|
18278
|
+
|
|
18279
|
+
// src/coordination/autonomous-coordinator.ts
|
|
18280
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
18281
|
+
|
|
18282
|
+
// src/coordination/knowledge-graph.ts
|
|
18283
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
18284
|
+
import * as fsp14 from "node:fs/promises";
|
|
18285
|
+
import * as path19 from "node:path";
|
|
18286
|
+
var DEFAULT_MAX_NODES = 2e3;
|
|
18287
|
+
var KnowledgeGraph = class _KnowledgeGraph {
|
|
16914
18288
|
nodes = /* @__PURE__ */ new Map();
|
|
16915
18289
|
index = /* @__PURE__ */ new Map();
|
|
16916
18290
|
// tag/field → node ids
|
|
@@ -16918,6 +18292,7 @@ var KnowledgeGraph = class {
|
|
|
16918
18292
|
pendingDeliveries = /* @__PURE__ */ new Map();
|
|
16919
18293
|
filePath;
|
|
16920
18294
|
graphFilePath;
|
|
18295
|
+
maxNodes;
|
|
16921
18296
|
/**
|
|
16922
18297
|
* Stable per-node insertion sequence. `nodes` (a Map) preserves insertion
|
|
16923
18298
|
* order even across `update()` (Map.set on an existing key keeps its slot),
|
|
@@ -16936,9 +18311,10 @@ var KnowledgeGraph = class {
|
|
|
16936
18311
|
getIndex() {
|
|
16937
18312
|
return this.index;
|
|
16938
18313
|
}
|
|
16939
|
-
constructor(sessionDir) {
|
|
16940
|
-
this.filePath =
|
|
16941
|
-
this.graphFilePath =
|
|
18314
|
+
constructor(sessionDir, maxNodes = DEFAULT_MAX_NODES) {
|
|
18315
|
+
this.filePath = path19.join(sessionDir, "_knowledge_graph");
|
|
18316
|
+
this.graphFilePath = path19.join(this.filePath, "graph.jsonl");
|
|
18317
|
+
this.maxNodes = maxNodes;
|
|
16942
18318
|
}
|
|
16943
18319
|
// ── Write ──────────────────────────────────────────────────────────────
|
|
16944
18320
|
/**
|
|
@@ -16946,10 +18322,11 @@ var KnowledgeGraph = class {
|
|
|
16946
18322
|
* Returns the node with its assigned id.
|
|
16947
18323
|
*/
|
|
16948
18324
|
async add(node) {
|
|
16949
|
-
const full = { id:
|
|
18325
|
+
const full = { id: randomUUID14(), ...node };
|
|
16950
18326
|
this.nodes.set(full.id, full);
|
|
16951
18327
|
this._trackSeq(full.id);
|
|
16952
18328
|
this._addToIndex(full, this._indexKeys(full));
|
|
18329
|
+
this._prune();
|
|
16953
18330
|
await this._persist(full);
|
|
16954
18331
|
this._deliver(full);
|
|
16955
18332
|
return full;
|
|
@@ -16962,10 +18339,48 @@ var KnowledgeGraph = class {
|
|
|
16962
18339
|
const updated = { ...existing, ...patch };
|
|
16963
18340
|
this.nodes.set(id, updated);
|
|
16964
18341
|
this._addToIndex(updated, this._indexKeys(updated));
|
|
18342
|
+
this._prune();
|
|
16965
18343
|
this._deliver(updated);
|
|
16966
18344
|
await this._append(updated);
|
|
16967
18345
|
return updated;
|
|
16968
18346
|
}
|
|
18347
|
+
/**
|
|
18348
|
+
* True when the node is in a terminal / settled state — no future mutations
|
|
18349
|
+
* are expected. Such nodes are the first to be evicted when the in-memory
|
|
18350
|
+
* cap is reached. The JSONL file on disk retains the full history.
|
|
18351
|
+
*/
|
|
18352
|
+
static _isTerminal(node) {
|
|
18353
|
+
if (node.type === "goal") return node.status === "done" || node.status === "failed";
|
|
18354
|
+
if (node.type === "change") return node.status === "rejected" || node.status === "rolled_back";
|
|
18355
|
+
if (node.type === "vote") return true;
|
|
18356
|
+
return false;
|
|
18357
|
+
}
|
|
18358
|
+
/**
|
|
18359
|
+
* Evict oldest terminal-state nodes when the in-memory cap is exceeded.
|
|
18360
|
+
* The JSONL file is the authoritative record; eviction only affects memory.
|
|
18361
|
+
*/
|
|
18362
|
+
_prune() {
|
|
18363
|
+
if (this.nodes.size <= this.maxNodes) return;
|
|
18364
|
+
const toEvict = this.nodes.size - this.maxNodes;
|
|
18365
|
+
const terminal = [];
|
|
18366
|
+
for (const [id, node] of this.nodes) {
|
|
18367
|
+
if (_KnowledgeGraph._isTerminal(node)) terminal.push({ id, seq: this.seq.get(id) ?? 0 });
|
|
18368
|
+
}
|
|
18369
|
+
terminal.sort((a, b) => a.seq - b.seq);
|
|
18370
|
+
const evicted = new Set(terminal.slice(0, toEvict).map((e) => e.id));
|
|
18371
|
+
if (evicted.size === 0) return;
|
|
18372
|
+
for (const id of evicted) {
|
|
18373
|
+
const node = this.nodes.get(id);
|
|
18374
|
+
if (!node) continue;
|
|
18375
|
+
this._removeFromIndex(node, this._indexKeys(node));
|
|
18376
|
+
this.nodes.delete(id);
|
|
18377
|
+
this.seq.delete(id);
|
|
18378
|
+
for (const pending of this.pendingDeliveries.values()) {
|
|
18379
|
+
const idx = pending.findIndex((n) => n.id === id);
|
|
18380
|
+
if (idx >= 0) pending.splice(idx, 1);
|
|
18381
|
+
}
|
|
18382
|
+
}
|
|
18383
|
+
}
|
|
16969
18384
|
// ── Read ───────────────────────────────────────────────────────────────
|
|
16970
18385
|
get(id) {
|
|
16971
18386
|
return this.nodes.get(id);
|
|
@@ -17029,8 +18444,8 @@ var KnowledgeGraph = class {
|
|
|
17029
18444
|
* used to poll for new nodes since the last check.
|
|
17030
18445
|
*/
|
|
17031
18446
|
subscribe(agentId, filter) {
|
|
17032
|
-
const channel =
|
|
17033
|
-
const sub = { id:
|
|
18447
|
+
const channel = randomUUID14();
|
|
18448
|
+
const sub = { id: randomUUID14(), agentId, filter, channel };
|
|
17034
18449
|
this.subs.set(channel, sub);
|
|
17035
18450
|
this.pendingDeliveries.set(channel, []);
|
|
17036
18451
|
return channel;
|
|
@@ -17120,7 +18535,8 @@ var KnowledgeGraph = class {
|
|
|
17120
18535
|
const nodeTags = node.tags ?? node.tags ?? [];
|
|
17121
18536
|
if (!f.tags.some((t) => nodeTags.includes(t))) return false;
|
|
17122
18537
|
}
|
|
17123
|
-
if (f.since && node.id > f.since)
|
|
18538
|
+
if (f.since && node.id > f.since) {
|
|
18539
|
+
}
|
|
17124
18540
|
return true;
|
|
17125
18541
|
}
|
|
17126
18542
|
_deliver(node) {
|
|
@@ -17132,23 +18548,23 @@ var KnowledgeGraph = class {
|
|
|
17132
18548
|
}
|
|
17133
18549
|
}
|
|
17134
18550
|
async _persist(node) {
|
|
17135
|
-
await
|
|
18551
|
+
await fsp14.mkdir(this.filePath, { recursive: true });
|
|
17136
18552
|
const line = JSON.stringify(node) + "\n";
|
|
17137
18553
|
await withFileLock(this.graphFilePath, async () => {
|
|
17138
|
-
await
|
|
18554
|
+
await fsp14.appendFile(this.graphFilePath, line, "utf8");
|
|
17139
18555
|
});
|
|
17140
18556
|
}
|
|
17141
18557
|
async _append(node) {
|
|
17142
|
-
await
|
|
18558
|
+
await fsp14.mkdir(this.filePath, { recursive: true });
|
|
17143
18559
|
const line = JSON.stringify({ op: "update", node }) + "\n";
|
|
17144
18560
|
await withFileLock(this.graphFilePath, async () => {
|
|
17145
|
-
await
|
|
18561
|
+
await fsp14.appendFile(this.graphFilePath, line, "utf8");
|
|
17146
18562
|
});
|
|
17147
18563
|
}
|
|
17148
18564
|
/** Rebuild in-memory state from the log file. Call on startup. */
|
|
17149
18565
|
async load() {
|
|
17150
18566
|
try {
|
|
17151
|
-
const content = await
|
|
18567
|
+
const content = await fsp14.readFile(this.graphFilePath, "utf8");
|
|
17152
18568
|
const lines = content.split("\n").filter(Boolean);
|
|
17153
18569
|
for (const line of lines) {
|
|
17154
18570
|
try {
|
|
@@ -17470,6 +18886,9 @@ var TaskDAG = class {
|
|
|
17470
18886
|
return false;
|
|
17471
18887
|
}
|
|
17472
18888
|
};
|
|
18889
|
+
|
|
18890
|
+
// src/coordination/task-auctioneer.ts
|
|
18891
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
17473
18892
|
var TaskAuctioneer = class {
|
|
17474
18893
|
graph;
|
|
17475
18894
|
fleet;
|
|
@@ -17589,7 +19008,7 @@ var TaskAuctioneer = class {
|
|
|
17589
19008
|
const score = dispatchResult.confidence * (dispatchResult.role === agent.agentRole ? 1.2 : 1);
|
|
17590
19009
|
if (score < this.minConfidence) return false;
|
|
17591
19010
|
const bid = {
|
|
17592
|
-
id:
|
|
19011
|
+
id: randomUUID15(),
|
|
17593
19012
|
taskId,
|
|
17594
19013
|
agentId: agent.agentId,
|
|
17595
19014
|
agentName: agent.agentName,
|
|
@@ -18394,6 +19813,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
|
|
|
18394
19813
|
mailbox;
|
|
18395
19814
|
events;
|
|
18396
19815
|
onCoordinatorEvent;
|
|
19816
|
+
logger;
|
|
18397
19817
|
running = false;
|
|
18398
19818
|
iterationCount = 0;
|
|
18399
19819
|
lastSyncAt = 0;
|
|
@@ -18410,6 +19830,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
|
|
|
18410
19830
|
this.mailbox = opts.mailbox ?? void 0;
|
|
18411
19831
|
this.events = opts.events ?? void 0;
|
|
18412
19832
|
this.onCoordinatorEvent = opts.onCoordinatorEvent;
|
|
19833
|
+
this.logger = opts.logger;
|
|
18413
19834
|
this.graph = new KnowledgeGraph(opts.sessionDir);
|
|
18414
19835
|
this.dag = new TaskDAG();
|
|
18415
19836
|
this.auction = new TaskAuctioneer({
|
|
@@ -18515,7 +19936,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
|
|
|
18515
19936
|
break;
|
|
18516
19937
|
}
|
|
18517
19938
|
const decision = await this.brain.decideAuto({
|
|
18518
|
-
id:
|
|
19939
|
+
id: randomUUID16(),
|
|
18519
19940
|
source: "system",
|
|
18520
19941
|
decisionType: "prioritize_goals",
|
|
18521
19942
|
question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
|
|
@@ -18568,15 +19989,22 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
|
|
|
18568
19989
|
stop() {
|
|
18569
19990
|
if (!this.running) return;
|
|
18570
19991
|
this.running = false;
|
|
18571
|
-
|
|
18572
|
-
|
|
18573
|
-
|
|
18574
|
-
event: "autonomous_coordinator.stop_signal",
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
19992
|
+
if (this.logger) {
|
|
19993
|
+
this.logger.error(
|
|
19994
|
+
"Stop signal received \u2014 shutting down",
|
|
19995
|
+
{ event: "autonomous_coordinator.stop_signal", iteration: this.iterationCount }
|
|
19996
|
+
);
|
|
19997
|
+
} else {
|
|
19998
|
+
console.error(
|
|
19999
|
+
JSON.stringify({
|
|
20000
|
+
level: "error",
|
|
20001
|
+
event: "autonomous_coordinator.stop_signal",
|
|
20002
|
+
message: "stop signal received \u2014 shutting down",
|
|
20003
|
+
iteration: this.iterationCount,
|
|
20004
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
20005
|
+
})
|
|
20006
|
+
);
|
|
20007
|
+
}
|
|
18580
20008
|
}
|
|
18581
20009
|
/**
|
|
18582
20010
|
* Report that a terminal worker (not a Director subagent) completed a claimed
|
|
@@ -19010,7 +20438,168 @@ ${input.detail}`
|
|
|
19010
20438
|
this.onCoordinatorEvent?.(event);
|
|
19011
20439
|
}
|
|
19012
20440
|
};
|
|
19013
|
-
|
|
19014
|
-
|
|
20441
|
+
export {
|
|
20442
|
+
ACP_AGENTS,
|
|
20443
|
+
AGENTS_BY_PHASE,
|
|
20444
|
+
AGENT_CATALOG,
|
|
20445
|
+
TOOLS as AGENT_TOOL_PRESETS,
|
|
20446
|
+
ALL_AGENT_DEFINITIONS,
|
|
20447
|
+
ALL_FLEET_AGENTS,
|
|
20448
|
+
AUDIT_LOG_AGENT,
|
|
20449
|
+
AdaptiveConcurrencyController,
|
|
20450
|
+
AgentMonitorService,
|
|
20451
|
+
AutonomousBrain,
|
|
20452
|
+
AutonomousCoordinator,
|
|
20453
|
+
BUG_HUNTER_AGENT,
|
|
20454
|
+
BUILD_AGENTS,
|
|
20455
|
+
BrainDecisionLedger,
|
|
20456
|
+
BrainDecisionQueue,
|
|
20457
|
+
BrainMonitor,
|
|
20458
|
+
BudgetExceededError,
|
|
20459
|
+
BudgetThresholdSignal,
|
|
20460
|
+
ChangeManager,
|
|
20461
|
+
CollabSession,
|
|
20462
|
+
ConsensusProtocol,
|
|
20463
|
+
DECISION_TIMEOUT_MS,
|
|
20464
|
+
DEFAULT_DIRECTOR_PREAMBLE,
|
|
20465
|
+
DEFAULT_DISPATCH_ROLE,
|
|
20466
|
+
DEFAULT_MAX_FLEET_SPAWNS,
|
|
20467
|
+
DEFAULT_QUALITY_CHECKS,
|
|
20468
|
+
DEFAULT_SUBAGENT_BASELINE,
|
|
20469
|
+
DELIVERY_AGENTS,
|
|
20470
|
+
DEPENDENCY_FILE_PATTERNS,
|
|
20471
|
+
DISCOVERY_AGENTS,
|
|
20472
|
+
DOMAIN_AGENTS,
|
|
20473
|
+
DefaultBrainArbiter,
|
|
20474
|
+
DefaultMailbox,
|
|
20475
|
+
DefaultMultiAgentCoordinator,
|
|
20476
|
+
Director,
|
|
20477
|
+
DirectorAlertLevel,
|
|
20478
|
+
EscalationRoutingBrainArbiter,
|
|
20479
|
+
FLEET_ROSTER,
|
|
20480
|
+
FLEET_ROSTER_BUDGETS,
|
|
20481
|
+
FLEET_ROSTER_WITHACP,
|
|
20482
|
+
FleetBus,
|
|
20483
|
+
FleetCostCapError,
|
|
20484
|
+
FleetManager,
|
|
20485
|
+
FleetSpawnBudgetError,
|
|
20486
|
+
FleetSupervisor,
|
|
20487
|
+
FleetTokenCapError,
|
|
20488
|
+
FleetUsageAggregator,
|
|
20489
|
+
GlobalMailbox,
|
|
20490
|
+
HARD_MAX_SPAWN_DEPTH,
|
|
20491
|
+
HEAVY_BUDGET,
|
|
20492
|
+
HumanEscalatingBrainArbiter,
|
|
20493
|
+
InMemoryAgentBridge,
|
|
20494
|
+
InMemoryBridgeTransport,
|
|
20495
|
+
KNOWLEDGE_AGENTS,
|
|
20496
|
+
KnowledgeGraph,
|
|
20497
|
+
LIGHT_BUDGET,
|
|
20498
|
+
LargeAnswerStore,
|
|
20499
|
+
MAILBOX_BRIDGE_LOCK_FILENAME,
|
|
20500
|
+
MAILBOX_BRIDGE_TOKEN_FILENAME,
|
|
20501
|
+
MAILBOX_HEALTH_DEFAULT_FAILURE_THRESHOLD,
|
|
20502
|
+
MAILBOX_HEALTH_DEFAULT_FROM,
|
|
20503
|
+
MAILBOX_HEALTH_DEFAULT_INTERVAL_MS,
|
|
20504
|
+
MAILBOX_HEALTH_DEFAULT_TIMEOUT_MS,
|
|
20505
|
+
MATRIX_PHASE_KEYS,
|
|
20506
|
+
MAX_SUBAGENT_STRUCTURED_REPORT_CHARS,
|
|
20507
|
+
MEDIUM_BUDGET,
|
|
20508
|
+
META_AGENTS,
|
|
20509
|
+
MailboxHealthWatchdog,
|
|
20510
|
+
NULL_FLEET_BUS,
|
|
20511
|
+
ObservableBrainArbiter,
|
|
20512
|
+
PLANNING_AGENTS,
|
|
20513
|
+
REFACTOR_PLANNER_AGENT,
|
|
20514
|
+
REVIEW_AGENTS,
|
|
20515
|
+
SECURITY_SCANNER_AGENT,
|
|
20516
|
+
SUBAGENT_STRUCTURED_REPORT_META_KEY,
|
|
20517
|
+
SubagentBudget,
|
|
20518
|
+
TIMEOUT_PREEMPT_FRACTION,
|
|
20519
|
+
TaskAuctioneer,
|
|
20520
|
+
TaskDAG,
|
|
20521
|
+
VERIFY_AGENTS,
|
|
20522
|
+
WorktreeIntegrationError,
|
|
20523
|
+
acquireOrJoin,
|
|
20524
|
+
actionToAckInput,
|
|
20525
|
+
applyRosterBudget,
|
|
20526
|
+
attachAutoExtend,
|
|
20527
|
+
attachDepWatcherBridge,
|
|
20528
|
+
brainDecisionKey,
|
|
20529
|
+
buildDownAlert,
|
|
20530
|
+
buildRecoveryAlert,
|
|
20531
|
+
composeDirectorPrompt,
|
|
20532
|
+
composeSubagentPrompt,
|
|
20533
|
+
createAgentMonitorService,
|
|
20534
|
+
createDelegateTool,
|
|
20535
|
+
createLedgerGuardBrainArbiter,
|
|
20536
|
+
createMailboxHooks,
|
|
20537
|
+
createMessage,
|
|
20538
|
+
detectEcosystem,
|
|
20539
|
+
dispatchAgent,
|
|
20540
|
+
finalize,
|
|
20541
|
+
formatHumanPrompt,
|
|
20542
|
+
formatSubagentStructuredReport,
|
|
20543
|
+
getAgentDefinition,
|
|
20544
|
+
getFullPackageLog,
|
|
20545
|
+
getManifestPackages,
|
|
20546
|
+
getPackageAuthor,
|
|
20547
|
+
getPackagesByAgent,
|
|
20548
|
+
isValidMatrixKey,
|
|
20549
|
+
mailboxSessionTag,
|
|
20550
|
+
makeAgentSubagentRunner,
|
|
20551
|
+
makeAskResultTool,
|
|
20552
|
+
makeAskTool,
|
|
20553
|
+
makeAssignTool,
|
|
20554
|
+
makeAwaitTasksTool,
|
|
20555
|
+
makeCollabDebugTool,
|
|
20556
|
+
makeDependencyWatcherConfig,
|
|
20557
|
+
makeDirectorSessionFactory,
|
|
20558
|
+
makeFleetEmitTool,
|
|
20559
|
+
makeFleetTool as makeFleetHealthTool,
|
|
20560
|
+
makeFleetTool as makeFleetSessionTool,
|
|
20561
|
+
makeFleetStatusTool,
|
|
20562
|
+
makeFleetTool,
|
|
20563
|
+
makeFleetTool as makeFleetUsageTool,
|
|
20564
|
+
makeKanbanQueueTool,
|
|
20565
|
+
makeLLMClassifier,
|
|
20566
|
+
makeMailInboxTool,
|
|
20567
|
+
makeMailSendTool,
|
|
20568
|
+
makeMailboxTool,
|
|
20569
|
+
makeQualityGateTool,
|
|
20570
|
+
makeRollUpTool,
|
|
20571
|
+
makeSpawnTool,
|
|
20572
|
+
makeSubagentResultTool,
|
|
20573
|
+
makeTerminateAllTool,
|
|
20574
|
+
makeTerminateTool,
|
|
20575
|
+
makeWorkCompleteTool,
|
|
20576
|
+
matrixKeyKind,
|
|
20577
|
+
normalizeRecipient,
|
|
20578
|
+
normalizeSubagentStructuredReport,
|
|
20579
|
+
phaseForRole,
|
|
20580
|
+
readLiveLock,
|
|
20581
|
+
readSubagentStructuredReport,
|
|
20582
|
+
recordPackageAction,
|
|
20583
|
+
release,
|
|
20584
|
+
resolveImplementationModelTarget,
|
|
20585
|
+
resolveMailboxIdentity,
|
|
20586
|
+
resolveMaxSpawnDepth,
|
|
20587
|
+
resolveModelMatrix,
|
|
20588
|
+
resolveModelMatrixResolution,
|
|
20589
|
+
resolveModelTargetFromEntry,
|
|
20590
|
+
resolveProjectDir,
|
|
20591
|
+
resolveSubagentModelTarget,
|
|
20592
|
+
resolveSubagentWorktreeDecision,
|
|
20593
|
+
roleNeedsIndependentReviewModel,
|
|
20594
|
+
rosterSummaryFromConfigs,
|
|
20595
|
+
sameModelReference,
|
|
20596
|
+
scoreAgents,
|
|
20597
|
+
startPackageOutdatedWatcher,
|
|
20598
|
+
subagentNeedsWorktree,
|
|
20599
|
+
terminalPolicyDecision,
|
|
20600
|
+
updatePackageOutdatedStatus,
|
|
20601
|
+
validateWatchdogOptions,
|
|
20602
|
+
withDisabledToolFiltering,
|
|
20603
|
+
wrapSubagentRunnerWithWorktrees
|
|
20604
|
+
};
|
|
19015
20605
|
//# sourceMappingURL=index.js.map
|
|
19016
|
-
//# sourceMappingURL=index.js.map
|