smol-symphony 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +41 -22
- package/DESIGN.md +494 -273
- package/README.md +109 -57
- package/SPEC.md +33 -24
- package/WORKFLOW.minimal.yaml +34 -0
- package/{WORKFLOW.template.md → WORKFLOW.template.yaml} +409 -256
- package/WORKFLOW.yaml +487 -0
- package/assets/skills/symphony-issues/SKILL.md +136 -0
- package/assets/symphony-mise.system.toml +68 -0
- package/dist/bin/symphony.js +22 -786
- package/dist/bin/symphony.js.map +1 -1
- package/dist/core/actions/context.js +109 -0
- package/dist/core/actions/context.js.map +1 -0
- package/dist/{actions/parsing.js → core/actions/parse.js} +33 -114
- package/dist/core/actions/parse.js.map +1 -0
- package/dist/core/actions/plan.js +197 -0
- package/dist/core/actions/plan.js.map +1 -0
- package/dist/core/actions/predicates.js +111 -0
- package/dist/core/actions/predicates.js.map +1 -0
- package/dist/core/actions/run-fold.js +248 -0
- package/dist/core/actions/run-fold.js.map +1 -0
- package/dist/core/actions/template.js +118 -0
- package/dist/core/actions/template.js.map +1 -0
- package/dist/core/cli/args.js +116 -0
- package/dist/core/cli/args.js.map +1 -0
- package/dist/core/coerce.js +75 -0
- package/dist/core/coerce.js.map +1 -0
- package/dist/core/credential/account-id.js +20 -0
- package/dist/core/credential/account-id.js.map +1 -0
- package/dist/core/credential/adapter-config.js +136 -0
- package/dist/core/credential/adapter-config.js.map +1 -0
- package/dist/core/credential/availability.js +98 -0
- package/dist/core/credential/availability.js.map +1 -0
- package/dist/core/credential/extract.js +228 -0
- package/dist/core/credential/extract.js.map +1 -0
- package/dist/core/credential/fake-creds.js +171 -0
- package/dist/core/credential/fake-creds.js.map +1 -0
- package/dist/core/credential/identity.js +125 -0
- package/dist/core/credential/identity.js.map +1 -0
- package/dist/core/credential/shape.js +230 -0
- package/dist/core/credential/shape.js.map +1 -0
- package/dist/core/credential/strings.js +15 -0
- package/dist/core/credential/strings.js.map +1 -0
- package/dist/core/doctor/checks.js +303 -0
- package/dist/core/doctor/checks.js.map +1 -0
- package/dist/core/git/result.js +107 -0
- package/dist/core/git/result.js.map +1 -0
- package/dist/core/http/decisions.js +225 -0
- package/dist/core/http/decisions.js.map +1 -0
- package/dist/{http.js → core/http/render.js} +472 -738
- package/dist/core/http/render.js.map +1 -0
- package/dist/{http-handlers.js → core/http/routes.js} +52 -87
- package/dist/core/http/routes.js.map +1 -0
- package/dist/core/http/views.js +181 -0
- package/dist/core/http/views.js.map +1 -0
- package/dist/core/image/managed-image.js +95 -0
- package/dist/core/image/managed-image.js.map +1 -0
- package/dist/core/issue/file.js +149 -0
- package/dist/core/issue/file.js.map +1 -0
- package/dist/core/issue/parse.js +210 -0
- package/dist/core/issue/parse.js.map +1 -0
- package/dist/core/mcp/dispatch.js +239 -0
- package/dist/core/mcp/dispatch.js.map +1 -0
- package/dist/core/mcp/post-move.js +92 -0
- package/dist/core/mcp/post-move.js.map +1 -0
- package/dist/core/mcp/protocol.js +293 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/url.js +162 -0
- package/dist/core/mcp/url.js.map +1 -0
- package/dist/core/path.js +63 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/reconcile/image-decide.js +48 -0
- package/dist/core/reconcile/image-decide.js.map +1 -0
- package/dist/core/reconcile/ledger.js +142 -0
- package/dist/core/reconcile/ledger.js.map +1 -0
- package/dist/core/reconcile/pr-classify.js +62 -0
- package/dist/core/reconcile/pr-classify.js.map +1 -0
- package/dist/{reconciler → core/reconcile}/pr-decide.js +25 -12
- package/dist/core/reconcile/pr-decide.js.map +1 -0
- package/dist/core/reconcile/pr-loop.js +161 -0
- package/dist/core/reconcile/pr-loop.js.map +1 -0
- package/dist/core/reconcile/pr-notes.js +35 -0
- package/dist/core/reconcile/pr-notes.js.map +1 -0
- package/dist/core/reconcile/vm-decide.js +70 -0
- package/dist/core/reconcile/vm-decide.js.map +1 -0
- package/dist/core/reconcile/vm-reap.js +207 -0
- package/dist/core/reconcile/vm-reap.js.map +1 -0
- package/dist/core/reconcile/workspace-decide.js +162 -0
- package/dist/core/reconcile/workspace-decide.js.map +1 -0
- package/dist/core/runlog/summary.js +231 -0
- package/dist/core/runlog/summary.js.map +1 -0
- package/dist/core/runner/dispatch-config.js +95 -0
- package/dist/core/runner/dispatch-config.js.map +1 -0
- package/dist/core/runner/injection.js +61 -0
- package/dist/core/runner/injection.js.map +1 -0
- package/dist/core/runner/mise.js +210 -0
- package/dist/core/runner/mise.js.map +1 -0
- package/dist/core/runner/prompt.js +720 -0
- package/dist/core/runner/prompt.js.map +1 -0
- package/dist/core/runner/turn.js +242 -0
- package/dist/core/runner/turn.js.map +1 -0
- package/dist/core/runner/vm-plan.js +390 -0
- package/dist/core/runner/vm-plan.js.map +1 -0
- package/dist/core/schedule/admission.js +123 -0
- package/dist/core/schedule/admission.js.map +1 -0
- package/dist/core/schedule/circuit-breaker.js +111 -0
- package/dist/core/schedule/circuit-breaker.js.map +1 -0
- package/dist/core/schedule/eligibility.js +83 -0
- package/dist/core/schedule/eligibility.js.map +1 -0
- package/dist/core/schedule/reconcile-issue.js +82 -0
- package/dist/core/schedule/reconcile-issue.js.map +1 -0
- package/dist/core/schedule/retry.js +96 -0
- package/dist/core/schedule/retry.js.map +1 -0
- package/dist/core/schedule/sleep-cycle.js +133 -0
- package/dist/core/schedule/sleep-cycle.js.map +1 -0
- package/dist/core/schedule/slots.js +124 -0
- package/dist/core/schedule/slots.js.map +1 -0
- package/dist/core/schedule/tick.js +553 -0
- package/dist/core/schedule/tick.js.map +1 -0
- package/dist/core/schedule/token-fold.js +181 -0
- package/dist/core/schedule/token-fold.js.map +1 -0
- package/dist/core/state-resolve.js +86 -0
- package/dist/core/state-resolve.js.map +1 -0
- package/dist/core/vm-guards.js +278 -0
- package/dist/core/vm-guards.js.map +1 -0
- package/dist/core/workflow/derive.js +107 -0
- package/dist/core/workflow/derive.js.map +1 -0
- package/dist/core/workflow/parse.js +687 -0
- package/dist/core/workflow/parse.js.map +1 -0
- package/dist/core/workflow/prompt-probe.js +78 -0
- package/dist/core/workflow/prompt-probe.js.map +1 -0
- package/dist/core/workflow/validate.js +189 -0
- package/dist/core/workflow/validate.js.map +1 -0
- package/dist/core/workspace-key.js +19 -0
- package/dist/core/workspace-key.js.map +1 -0
- package/dist/shell/actions-runner.js +356 -0
- package/dist/shell/actions-runner.js.map +1 -0
- package/dist/shell/adapter/adapter-registry.js +45 -0
- package/dist/shell/adapter/adapter-registry.js.map +1 -0
- package/dist/shell/adapter/clock-random.js +96 -0
- package/dist/shell/adapter/clock-random.js.map +1 -0
- package/dist/shell/adapter/gondolin-dispatch-helpers.js +158 -0
- package/dist/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
- package/dist/shell/adapter/gondolin-dispatch.js +385 -0
- package/dist/shell/adapter/gondolin-dispatch.js.map +1 -0
- package/dist/shell/adapter/gondolin-image-converter.js +233 -0
- package/dist/shell/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/shell/adapter/gondolin-image-fetch.js +180 -0
- package/dist/shell/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/shell/adapter/launcher-asset.js +57 -0
- package/dist/shell/adapter/launcher-asset.js.map +1 -0
- package/dist/shell/adapter/mise-config-asset.js +65 -0
- package/dist/shell/adapter/mise-config-asset.js.map +1 -0
- package/dist/shell/adapter/workflow-loader.js +304 -0
- package/dist/shell/adapter/workflow-loader.js.map +1 -0
- package/dist/shell/cli/doctor.js +268 -0
- package/dist/shell/cli/doctor.js.map +1 -0
- package/dist/shell/effect-interpreter-families.js +314 -0
- package/dist/shell/effect-interpreter-families.js.map +1 -0
- package/dist/shell/effect-interpreter.js +29 -0
- package/dist/shell/effect-interpreter.js.map +1 -0
- package/dist/shell/interp/acp-frame.js +137 -0
- package/dist/shell/interp/acp-frame.js.map +1 -0
- package/dist/shell/interp/acp-ws-conn.js +320 -0
- package/dist/shell/interp/acp-ws-conn.js.map +1 -0
- package/dist/shell/interp/acp-ws-frames.js +159 -0
- package/dist/shell/interp/acp-ws-frames.js.map +1 -0
- package/dist/shell/interp/acp-ws.js +197 -0
- package/dist/shell/interp/acp-ws.js.map +1 -0
- package/dist/shell/interp/acp.js +319 -0
- package/dist/shell/interp/acp.js.map +1 -0
- package/dist/shell/interp/credential-defaults.js +128 -0
- package/dist/shell/interp/credential-defaults.js.map +1 -0
- package/dist/shell/interp/credential-hooks.js +149 -0
- package/dist/shell/interp/credential-hooks.js.map +1 -0
- package/dist/shell/interp/credential-registry.js +226 -0
- package/dist/shell/interp/credential-registry.js.map +1 -0
- package/dist/shell/interp/credential.js +103 -0
- package/dist/shell/interp/credential.js.map +1 -0
- package/dist/shell/interp/gh.js +163 -0
- package/dist/shell/interp/gh.js.map +1 -0
- package/dist/shell/interp/git.js +28 -0
- package/dist/shell/interp/git.js.map +1 -0
- package/dist/shell/interp/log.js +213 -0
- package/dist/shell/interp/log.js.map +1 -0
- package/dist/shell/interp/process.js +178 -0
- package/dist/shell/interp/process.js.map +1 -0
- package/dist/shell/interp/runlog.js +193 -0
- package/dist/shell/interp/runlog.js.map +1 -0
- package/dist/shell/interp/timer.js +64 -0
- package/dist/shell/interp/timer.js.map +1 -0
- package/dist/shell/interp/tracker-disk.js +99 -0
- package/dist/shell/interp/tracker-disk.js.map +1 -0
- package/dist/shell/interp/tracker-parse.js +71 -0
- package/dist/shell/interp/tracker-parse.js.map +1 -0
- package/dist/shell/interp/tracker-scan.js +238 -0
- package/dist/shell/interp/tracker-scan.js.map +1 -0
- package/dist/shell/interp/tracker-write.js +91 -0
- package/dist/shell/interp/tracker-write.js.map +1 -0
- package/dist/shell/interp/tracker.js +41 -0
- package/dist/shell/interp/tracker.js.map +1 -0
- package/dist/shell/interp/tty.js +48 -0
- package/dist/shell/interp/tty.js.map +1 -0
- package/dist/shell/interp/vm.js +199 -0
- package/dist/shell/interp/vm.js.map +1 -0
- package/dist/shell/interp/workspace.js +310 -0
- package/dist/shell/interp/workspace.js.map +1 -0
- package/dist/shell/main-acp.js +78 -0
- package/dist/shell/main-acp.js.map +1 -0
- package/dist/shell/main-adapters.js +222 -0
- package/dist/shell/main-adapters.js.map +1 -0
- package/dist/shell/main-credential.js +122 -0
- package/dist/shell/main-credential.js.map +1 -0
- package/dist/shell/main-doctor.js +22 -0
- package/dist/shell/main-doctor.js.map +1 -0
- package/dist/shell/main-entry.js +46 -0
- package/dist/shell/main-entry.js.map +1 -0
- package/dist/shell/main-http-csrf.js +45 -0
- package/dist/shell/main-http-csrf.js.map +1 -0
- package/dist/shell/main-http-handler.js +389 -0
- package/dist/shell/main-http-handler.js.map +1 -0
- package/dist/shell/main-http-mcp.js +122 -0
- package/dist/shell/main-http-mcp.js.map +1 -0
- package/dist/shell/main-http-views.js +253 -0
- package/dist/shell/main-http-views.js.map +1 -0
- package/dist/shell/main-http.js +76 -0
- package/dist/shell/main-http.js.map +1 -0
- package/dist/shell/main-loops.js +130 -0
- package/dist/shell/main-loops.js.map +1 -0
- package/dist/shell/main-mcp.js +129 -0
- package/dist/shell/main-mcp.js.map +1 -0
- package/dist/shell/main-orchestrator.js +120 -0
- package/dist/shell/main-orchestrator.js.map +1 -0
- package/dist/shell/main-preflight.js +43 -0
- package/dist/shell/main-preflight.js.map +1 -0
- package/dist/shell/main-reconcilers-helpers.js +244 -0
- package/dist/shell/main-reconcilers-helpers.js.map +1 -0
- package/dist/shell/main-reconcilers-pr.js +148 -0
- package/dist/shell/main-reconcilers-pr.js.map +1 -0
- package/dist/shell/main-reconcilers.js +225 -0
- package/dist/shell/main-reconcilers.js.map +1 -0
- package/dist/shell/main-runner.js +355 -0
- package/dist/shell/main-runner.js.map +1 -0
- package/dist/shell/main-scaffold.js +116 -0
- package/dist/shell/main-scaffold.js.map +1 -0
- package/dist/shell/main-shutdown.js +115 -0
- package/dist/shell/main-shutdown.js.map +1 -0
- package/dist/shell/main-startup.js +48 -0
- package/dist/shell/main-startup.js.map +1 -0
- package/dist/shell/main-substrates.js +43 -0
- package/dist/shell/main-substrates.js.map +1 -0
- package/dist/shell/main.js +385 -0
- package/dist/shell/main.js.map +1 -0
- package/dist/shell/orchestrator-feedback.js +69 -0
- package/dist/shell/orchestrator-feedback.js.map +1 -0
- package/dist/shell/orchestrator-image.js +167 -0
- package/dist/shell/orchestrator-image.js.map +1 -0
- package/dist/shell/orchestrator-loop.js +468 -0
- package/dist/shell/orchestrator-loop.js.map +1 -0
- package/dist/shell/orchestrator-reconcile.js +36 -0
- package/dist/shell/orchestrator-reconcile.js.map +1 -0
- package/dist/shell/reconciler-loop.js +228 -0
- package/dist/shell/reconciler-loop.js.map +1 -0
- package/dist/shell/runner-loop-turn.js +301 -0
- package/dist/shell/runner-loop-turn.js.map +1 -0
- package/dist/shell/runner-loop.js +338 -0
- package/dist/shell/runner-loop.js.map +1 -0
- package/dist/shell/server/http.js +208 -0
- package/dist/shell/server/http.js.map +1 -0
- package/dist/shell/server/mcp-runtime-effects.js +237 -0
- package/dist/shell/server/mcp-runtime-effects.js.map +1 -0
- package/dist/shell/server/mcp-runtime.js +99 -0
- package/dist/shell/server/mcp-runtime.js.map +1 -0
- package/dist/shell/workspace-key.js +14 -0
- package/dist/shell/workspace-key.js.map +1 -0
- package/dist/types/acp.js +8 -0
- package/dist/types/acp.js.map +1 -0
- package/dist/types/actions/plan.js +6 -0
- package/dist/types/actions/plan.js.map +1 -0
- package/dist/types/actions/predicates.js +6 -0
- package/dist/types/actions/predicates.js.map +1 -0
- package/dist/types/actions/run-fold.js +8 -0
- package/dist/types/actions/run-fold.js.map +1 -0
- package/dist/types/actions.js +7 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/adapter/clock-random.js +4 -0
- package/dist/types/adapter/clock-random.js.map +1 -0
- package/dist/types/adapter/gondolin-image-converter.js +5 -0
- package/dist/types/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/types/adapter/gondolin-image-fetch.js +5 -0
- package/dist/types/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/types/adapter/workflow-loader.js +4 -0
- package/dist/types/adapter/workflow-loader.js.map +1 -0
- package/dist/types/cli/args.js +8 -0
- package/dist/types/cli/args.js.map +1 -0
- package/dist/types/config.js +8 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/credential-interp.js +6 -0
- package/dist/types/credential-interp.js.map +1 -0
- package/dist/types/credentials.js +10 -0
- package/dist/types/credentials.js.map +1 -0
- package/dist/types/doctor.js +7 -0
- package/dist/types/doctor.js.map +1 -0
- package/dist/types/domain.js +7 -0
- package/dist/types/domain.js.map +1 -0
- package/dist/types/effect.js +15 -0
- package/dist/types/effect.js.map +1 -0
- package/dist/types/errors.js +39 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/http/decisions.js +6 -0
- package/dist/types/http/decisions.js.map +1 -0
- package/dist/types/http/render.js +10 -0
- package/dist/types/http/render.js.map +1 -0
- package/dist/types/http/views.js +6 -0
- package/dist/types/http/views.js.map +1 -0
- package/dist/types/http.js +9 -0
- package/dist/types/http.js.map +1 -0
- package/dist/types/image/managed-image.js +7 -0
- package/dist/types/image/managed-image.js.map +1 -0
- package/dist/types/interp/effect-interpreter.js +8 -0
- package/dist/types/interp/effect-interpreter.js.map +1 -0
- package/dist/types/interp/tracker.js +7 -0
- package/dist/types/interp/tracker.js.map +1 -0
- package/dist/types/issue/file.js +6 -0
- package/dist/types/issue/file.js.map +1 -0
- package/dist/types/issue/parse.js +8 -0
- package/dist/types/issue/parse.js.map +1 -0
- package/dist/types/main-acp.js +13 -0
- package/dist/types/main-acp.js.map +1 -0
- package/dist/types/main-adapters.js +5 -0
- package/dist/types/main-adapters.js.map +1 -0
- package/dist/types/main-credential.js +21 -0
- package/dist/types/main-credential.js.map +1 -0
- package/dist/types/main-doctor.js +6 -0
- package/dist/types/main-doctor.js.map +1 -0
- package/dist/types/main-http-handler.js +12 -0
- package/dist/types/main-http-handler.js.map +1 -0
- package/dist/types/main-http.js +5 -0
- package/dist/types/main-http.js.map +1 -0
- package/dist/types/main-loops.js +5 -0
- package/dist/types/main-loops.js.map +1 -0
- package/dist/types/main-mcp.js +12 -0
- package/dist/types/main-mcp.js.map +1 -0
- package/dist/types/main-orchestrator.js +5 -0
- package/dist/types/main-orchestrator.js.map +1 -0
- package/dist/types/main-reconcilers.js +11 -0
- package/dist/types/main-reconcilers.js.map +1 -0
- package/dist/types/main-runner.js +13 -0
- package/dist/types/main-runner.js.map +1 -0
- package/dist/types/main-startup.js +5 -0
- package/dist/types/main-startup.js.map +1 -0
- package/dist/types/main-substrates.js +5 -0
- package/dist/types/main-substrates.js.map +1 -0
- package/dist/types/mcp/dispatch.js +4 -0
- package/dist/types/mcp/dispatch.js.map +1 -0
- package/dist/types/mcp/post-move.js +7 -0
- package/dist/types/mcp/post-move.js.map +1 -0
- package/dist/types/mcp.js +9 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/ports.js +12 -0
- package/dist/types/ports.js.map +1 -0
- package/dist/types/reconcile/image-decide.js +5 -0
- package/dist/types/reconcile/image-decide.js.map +1 -0
- package/dist/types/reconcile/ledger.js +7 -0
- package/dist/types/reconcile/ledger.js.map +1 -0
- package/dist/types/reconcile/pr-loop.js +8 -0
- package/dist/types/reconcile/pr-loop.js.map +1 -0
- package/dist/types/reconcile/vm-reap.js +8 -0
- package/dist/types/reconcile/vm-reap.js.map +1 -0
- package/dist/types/reconcile/workspace-decide.js +7 -0
- package/dist/types/reconcile/workspace-decide.js.map +1 -0
- package/dist/types/reconcile.js +9 -0
- package/dist/types/reconcile.js.map +1 -0
- package/dist/types/runlog.js +7 -0
- package/dist/types/runlog.js.map +1 -0
- package/dist/types/runner/actions-runner.js +12 -0
- package/dist/types/runner/actions-runner.js.map +1 -0
- package/dist/types/runner/gondolin-dispatch.js +5 -0
- package/dist/types/runner/gondolin-dispatch.js.map +1 -0
- package/dist/types/runner/injection.js +6 -0
- package/dist/types/runner/injection.js.map +1 -0
- package/dist/types/runner/runner-loop.js +5 -0
- package/dist/types/runner/runner-loop.js.map +1 -0
- package/dist/types/runner/turn.js +4 -0
- package/dist/types/runner/turn.js.map +1 -0
- package/dist/types/runner/vm-plan.js +4 -0
- package/dist/types/runner/vm-plan.js.map +1 -0
- package/dist/types/runtime.js +9 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/schedule/admission.js +7 -0
- package/dist/types/schedule/admission.js.map +1 -0
- package/dist/types/schedule/circuit-breaker.js +2 -0
- package/dist/types/schedule/circuit-breaker.js.map +1 -0
- package/dist/types/schedule/eligibility.js +9 -0
- package/dist/types/schedule/eligibility.js.map +1 -0
- package/dist/types/schedule/orchestrator-loop.js +10 -0
- package/dist/types/schedule/orchestrator-loop.js.map +1 -0
- package/dist/types/schedule/sleep-cycle.js +4 -0
- package/dist/types/schedule/sleep-cycle.js.map +1 -0
- package/dist/types/schedule/slots.js +8 -0
- package/dist/types/schedule/slots.js.map +1 -0
- package/dist/types/schedule/tick.js +9 -0
- package/dist/types/schedule/tick.js.map +1 -0
- package/dist/types/server/mcp-runtime.js +8 -0
- package/dist/types/server/mcp-runtime.js.map +1 -0
- package/dist/types/workflow/parse.js +4 -0
- package/dist/types/workflow/parse.js.map +1 -0
- package/package.json +22 -10
- package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
- package/prompts/Reflect.md +91 -0
- package/prompts/Review.md +97 -0
- package/prompts/Todo.md +96 -0
- package/prompts/_footer.md +41 -0
- package/prompts/_preamble.md +42 -0
- package/prompts-minimal/Todo.md +26 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/vm-agent.mjs +312 -90
- package/WORKFLOW.md +0 -744
- package/dist/acp-bridge.js +0 -324
- package/dist/acp-bridge.js.map +0 -1
- package/dist/actions/cache.js +0 -191
- package/dist/actions/cache.js.map +0 -1
- package/dist/actions/effects.js +0 -41
- package/dist/actions/effects.js.map +0 -1
- package/dist/actions/executor.js +0 -570
- package/dist/actions/executor.js.map +0 -1
- package/dist/actions/index.js +0 -13
- package/dist/actions/index.js.map +0 -1
- package/dist/actions/parsing.js.map +0 -1
- package/dist/actions/predicate-env.js +0 -27
- package/dist/actions/predicate-env.js.map +0 -1
- package/dist/actions/predicates.js +0 -49
- package/dist/actions/predicates.js.map +0 -1
- package/dist/actions/templating.js +0 -66
- package/dist/actions/templating.js.map +0 -1
- package/dist/actions/types.js +0 -15
- package/dist/actions/types.js.map +0 -1
- package/dist/agent/acp.js +0 -473
- package/dist/agent/acp.js.map +0 -1
- package/dist/agent/adapter-names.js +0 -159
- package/dist/agent/adapter-names.js.map +0 -1
- package/dist/agent/adapters.js +0 -511
- package/dist/agent/adapters.js.map +0 -1
- package/dist/agent/credential-extractors.js +0 -342
- package/dist/agent/credential-extractors.js.map +0 -1
- package/dist/agent/credential-secrets.js +0 -628
- package/dist/agent/credential-secrets.js.map +0 -1
- package/dist/agent/credential-ticker.js +0 -57
- package/dist/agent/credential-ticker.js.map +0 -1
- package/dist/agent/gondolin-creds-staging.js +0 -356
- package/dist/agent/gondolin-creds-staging.js.map +0 -1
- package/dist/agent/gondolin-dispatch.js +0 -375
- package/dist/agent/gondolin-dispatch.js.map +0 -1
- package/dist/agent/gondolin.js +0 -124
- package/dist/agent/gondolin.js.map +0 -1
- package/dist/agent/runner-decisions.js +0 -134
- package/dist/agent/runner-decisions.js.map +0 -1
- package/dist/agent/runner.js +0 -1456
- package/dist/agent/runner.js.map +0 -1
- package/dist/agent/tool-call-summary.js +0 -102
- package/dist/agent/tool-call-summary.js.map +0 -1
- package/dist/agent/vm-acp-mapping.js +0 -73
- package/dist/agent/vm-acp-mapping.js.map +0 -1
- package/dist/agent/vm-guards.js +0 -262
- package/dist/agent/vm-guards.js.map +0 -1
- package/dist/agent/vm-port.js +0 -22
- package/dist/agent/vm-port.js.map +0 -1
- package/dist/agent/vm-process-registry.js +0 -79
- package/dist/agent/vm-process-registry.js.map +0 -1
- package/dist/bin/cli-args.js +0 -105
- package/dist/bin/cli-args.js.map +0 -1
- package/dist/errors.js +0 -15
- package/dist/errors.js.map +0 -1
- package/dist/http-disk.js +0 -135
- package/dist/http-disk.js.map +0 -1
- package/dist/http-handlers.js.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/issues.js +0 -178
- package/dist/issues.js.map +0 -1
- package/dist/logging.js +0 -203
- package/dist/logging.js.map +0 -1
- package/dist/mcp.js +0 -706
- package/dist/mcp.js.map +0 -1
- package/dist/memory.js +0 -85
- package/dist/memory.js.map +0 -1
- package/dist/orchestrator-decisions.js +0 -331
- package/dist/orchestrator-decisions.js.map +0 -1
- package/dist/orchestrator.js +0 -1569
- package/dist/orchestrator.js.map +0 -1
- package/dist/prompt.js +0 -65
- package/dist/prompt.js.map +0 -1
- package/dist/reconciler/cache.js +0 -65
- package/dist/reconciler/cache.js.map +0 -1
- package/dist/reconciler/index.js +0 -448
- package/dist/reconciler/index.js.map +0 -1
- package/dist/reconciler/ledger.js +0 -131
- package/dist/reconciler/ledger.js.map +0 -1
- package/dist/reconciler/pr-adapters.js +0 -174
- package/dist/reconciler/pr-adapters.js.map +0 -1
- package/dist/reconciler/pr-decide.js.map +0 -1
- package/dist/reconciler/pr.js +0 -422
- package/dist/reconciler/pr.js.map +0 -1
- package/dist/reconciler/types.js +0 -12
- package/dist/reconciler/types.js.map +0 -1
- package/dist/reconciler/vm.js +0 -243
- package/dist/reconciler/vm.js.map +0 -1
- package/dist/reconciler/workspace-defaults.js +0 -83
- package/dist/reconciler/workspace-defaults.js.map +0 -1
- package/dist/reconciler/workspace.js +0 -272
- package/dist/reconciler/workspace.js.map +0 -1
- package/dist/runlog.js +0 -403
- package/dist/runlog.js.map +0 -1
- package/dist/scaffold.js +0 -165
- package/dist/scaffold.js.map +0 -1
- package/dist/trackers/local.js +0 -445
- package/dist/trackers/local.js.map +0 -1
- package/dist/trackers/types.js +0 -10
- package/dist/trackers/types.js.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/util/clock.js +0 -12
- package/dist/util/clock.js.map +0 -1
- package/dist/util/crypto.js +0 -25
- package/dist/util/crypto.js.map +0 -1
- package/dist/util/frontmatter.js +0 -70
- package/dist/util/frontmatter.js.map +0 -1
- package/dist/util/fs-issues.js +0 -22
- package/dist/util/fs-issues.js.map +0 -1
- package/dist/util/process.js +0 -152
- package/dist/util/process.js.map +0 -1
- package/dist/util/workspace-key.js +0 -10
- package/dist/util/workspace-key.js.map +0 -1
- package/dist/workflow-loader.js +0 -147
- package/dist/workflow-loader.js.map +0 -1
- package/dist/workflow.js +0 -822
- package/dist/workflow.js.map +0 -1
- package/dist/workspace-types.js +0 -8
- package/dist/workspace-types.js.map +0 -1
- package/dist/workspace.js +0 -443
- package/dist/workspace.js.map +0 -1
package/dist/mcp.js
DELETED
|
@@ -1,706 +0,0 @@
|
|
|
1
|
-
// Per-issue MCP server. Each active dispatch registers an entry here; the agent inside
|
|
2
|
-
// the Gondolin VM connects to /api/v1/issues/<id>/mcp with the per-dispatch bearer token and
|
|
3
|
-
// sees these tools:
|
|
4
|
-
//
|
|
5
|
-
// transition(to_state, notes?) ─ atomic file move into another declared state,
|
|
6
|
-
// optionally appending notes to the issue body
|
|
7
|
-
// first. Sets the `transitioned` flag so the runner
|
|
8
|
-
// exits cleanly on next post-turn check.
|
|
9
|
-
// request_human_steering(question, context?)
|
|
10
|
-
// ─ stashes the question on the RunningEntry, returns
|
|
11
|
-
// an ack to the agent. The runner then pauses the
|
|
12
|
-
// autonomous loop and awaits a human reply submitted
|
|
13
|
-
// via POST /api/v1/issues/<id>/steering-reply.
|
|
14
|
-
// propose_issue(title, ...) ─ drops a new issue file into the first declared
|
|
15
|
-
// holding state directory for operator triage.
|
|
16
|
-
//
|
|
17
|
-
// The URL is the capability: the agent only ever knows its own /<id>/mcp endpoint. The
|
|
18
|
-
// bearer token is belt-and-braces in case a non-agent caller can reach 8787.
|
|
19
|
-
//
|
|
20
|
-
// MCP wire format here is JSON-RPC 2.0 over HTTP (the "Streamable HTTP" transport's
|
|
21
|
-
// non-SSE subset). We implement only the subset our tools need: initialize, tools/list,
|
|
22
|
-
// tools/call, plus a polite notifications/initialized acknowledgement.
|
|
23
|
-
import { log } from './logging.js';
|
|
24
|
-
import { writeIssueFile, pickHoldingState, NoHoldingStateError } from './issues.js';
|
|
25
|
-
import { realClock, isoFromClock } from './util/clock.js';
|
|
26
|
-
import { realCrypto } from './util/crypto.js';
|
|
27
|
-
const PROTOCOL_VERSION = '2025-06-18';
|
|
28
|
-
const TOOL_LIST = [
|
|
29
|
-
{
|
|
30
|
-
name: 'request_human_steering',
|
|
31
|
-
description: 'Pause work and ask the human operator a question. Your current turn will end immediately after this returns; the human response will arrive as the prompt for your next turn. Use only when you cannot proceed without a decision a human must make.',
|
|
32
|
-
inputSchema: {
|
|
33
|
-
type: 'object',
|
|
34
|
-
properties: {
|
|
35
|
-
question: {
|
|
36
|
-
type: 'string',
|
|
37
|
-
description: 'The question to ask the human. Be specific about what decision they need to make.',
|
|
38
|
-
},
|
|
39
|
-
context: {
|
|
40
|
-
type: 'string',
|
|
41
|
-
description: 'Optional: relevant context the human needs to answer (file paths, options considered, etc.).',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
required: ['question'],
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: 'transition',
|
|
49
|
-
description: 'Move this issue into another declared state, optionally appending notes to its body for the next agent. Use this for handoffs — implementer → reviewer, reviewer → implementer (rework), or implementer → terminal Done — rather than only at the very end of the work. Notes are appended to the issue file BEFORE the state move, so the next dispatch sees them in `issue.description` along with everything the previous agents wrote. `to_state` must be one of the states declared in WORKFLOW.md; if the current state declares `allowed_transitions`, `to_state` must also be in that list. On a terminal target, the workspace is removed after your turn ends and no further turns will be dispatched. On a non-terminal target, the same workspace and `agent/<id>` git branch survive into the next state.',
|
|
50
|
-
inputSchema: {
|
|
51
|
-
type: 'object',
|
|
52
|
-
properties: {
|
|
53
|
-
to_state: {
|
|
54
|
-
type: 'string',
|
|
55
|
-
description: 'Declared state to transition into (case-insensitive match against `states:` in WORKFLOW.md). Examples: "Review", "Done", "Todo".',
|
|
56
|
-
},
|
|
57
|
-
notes: {
|
|
58
|
-
type: 'string',
|
|
59
|
-
description: 'Optional markdown notes to append to the issue body before the move. These become part of the issue description the next agent (in `to_state`) reads. Use this for review findings, rework instructions, or PR-body content on a terminal transition.',
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
required: ['to_state'],
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'propose_issue',
|
|
67
|
-
description: 'Propose a new issue for the human to triage. The orchestrator drops the proposal into a non-active "Triage" state directory and does NOT dispatch it — the operator approves (moves to the active queue) or discards it from the dashboard. Use this when you notice work that is out of scope for your current task: an unrelated bug, a follow-up the operator should size, a refactor a future agent could pick up. The parent issue you are working on is automatically recorded; do not paste it into the body.',
|
|
68
|
-
inputSchema: {
|
|
69
|
-
type: 'object',
|
|
70
|
-
properties: {
|
|
71
|
-
title: {
|
|
72
|
-
type: 'string',
|
|
73
|
-
description: 'Short single-line title in imperative voice (≤72 chars recommended). Example: "Fix race condition in workspace cleanup".',
|
|
74
|
-
},
|
|
75
|
-
description: {
|
|
76
|
-
type: 'string',
|
|
77
|
-
description: 'Optional multi-paragraph body for the issue. Explain what you observed, where (file paths), and why it is worth handling separately from your current task.',
|
|
78
|
-
},
|
|
79
|
-
labels: {
|
|
80
|
-
type: 'array',
|
|
81
|
-
items: { type: 'string' },
|
|
82
|
-
description: 'Optional list of label strings.',
|
|
83
|
-
},
|
|
84
|
-
priority: {
|
|
85
|
-
type: 'number',
|
|
86
|
-
description: 'Optional integer priority hint (tracker-defined meaning).',
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
required: ['title'],
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
export class McpRegistry {
|
|
94
|
-
tracker;
|
|
95
|
-
byIdentifier = new Map();
|
|
96
|
-
effectivePort = null;
|
|
97
|
-
// Live state-config map, kept in sync with the orchestrator's view via
|
|
98
|
-
// `updateStates`. Used by `transition` to validate `to_state` and to resolve
|
|
99
|
-
// the role of the target (terminal => set cleanup_workspace_on_exit). An
|
|
100
|
-
// empty map means every transition call lands in the "unknown_state" branch,
|
|
101
|
-
// which is the correct behaviour: without a declared states map, the agent
|
|
102
|
-
// has no valid target to name.
|
|
103
|
-
states = {};
|
|
104
|
-
// Issue 38 (generalised in issue 144): when the PR engine is enabled,
|
|
105
|
-
// transitions into the merge state do NOT flip `cleanup_workspace_on_exit`
|
|
106
|
-
// even though the target state's role is `terminal`. The pr resource owns
|
|
107
|
-
// the workspace lifecycle for those issues — it rebases inside the workspace
|
|
108
|
-
// and cleans it up after the PR merges/closes. Without this gate the
|
|
109
|
-
// terminal-cleanup path would reap the workspace before the engine could
|
|
110
|
-
// ever use it. The caller (bin/symphony.ts) derives `mergeState` from
|
|
111
|
-
// `derivePrRouting(cfg.states).mergeState` and `enabled` from `cfg.pr.enabled`.
|
|
112
|
-
prMerge = { enabled: false, mergeState: null };
|
|
113
|
-
// Injected wall clock. Tests pin time; production wires `Date.now` (or
|
|
114
|
-
// accepts the realClock default). Used to stamp `proposed_at` on
|
|
115
|
-
// propose_issue submissions so the core is deterministic under test.
|
|
116
|
-
now;
|
|
117
|
-
// Injected crypto port. Production wires `realCrypto` (randomBytes +
|
|
118
|
-
// timingSafeEqual); tests can pin deterministic tokens to assert wire
|
|
119
|
-
// format without sampling randomness.
|
|
120
|
-
crypto;
|
|
121
|
-
constructor(tracker, opts = {}) {
|
|
122
|
-
this.tracker = tracker;
|
|
123
|
-
if (opts.states)
|
|
124
|
-
this.states = opts.states;
|
|
125
|
-
if (opts.prMerge)
|
|
126
|
-
this.prMerge = opts.prMerge;
|
|
127
|
-
this.now = opts.now ?? realClock;
|
|
128
|
-
this.crypto = opts.crypto ?? realCrypto;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Push the latest state-config map (and optional PR merge-cleanup gate) in
|
|
132
|
-
* after a workflow reload. Tests that don't care about the PR engine can call
|
|
133
|
-
* with a single argument; production wires both so reloads that flip
|
|
134
|
-
* `pr.enabled` or move the merge state take effect on subsequent transitions.
|
|
135
|
-
*/
|
|
136
|
-
updateStates(states, prMerge) {
|
|
137
|
-
this.states = states;
|
|
138
|
-
if (prMerge !== undefined) {
|
|
139
|
-
this.prMerge = prMerge ?? { enabled: false, mergeState: null };
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/** Called once after the HTTP server binds, so URL construction uses the real port. */
|
|
143
|
-
setEffectivePort(port) {
|
|
144
|
-
this.effectivePort = port;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* The bound HTTP port, or null until the server binds. Exposed so the Gondolin
|
|
148
|
-
* dispatch can wire the guest→host MCP `tcp.hosts` tunnel to the real
|
|
149
|
-
* `mcp.host:effectivePort` while the guest dials a fixed synthetic host.
|
|
150
|
-
*/
|
|
151
|
-
getEffectivePort() {
|
|
152
|
-
return this.effectivePort;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Build the URL the ACP agent will be told to POST to. Returns null when no HTTP server
|
|
156
|
-
* is available and no explicit URL is configured; the runner uses that to skip MCP
|
|
157
|
-
* injection (with a warning) instead of advertising an unreachable endpoint.
|
|
158
|
-
*
|
|
159
|
-
* `baseOverride` (e.g. the Gondolin guest synthetic base `http://symphony-mcp:7001`)
|
|
160
|
-
* wins over both `explicit_host_url` and the real host:port: under Gondolin the
|
|
161
|
-
* guest cannot reach the host loopback directly, so the runner passes the synthetic
|
|
162
|
-
* base that a per-dispatch `tcp.hosts` entry tunnels to the real MCP server.
|
|
163
|
-
*/
|
|
164
|
-
buildUrl(identifier, mcp, baseOverride) {
|
|
165
|
-
const base = baseOverride
|
|
166
|
-
? baseOverride.replace(/\/+$/, '')
|
|
167
|
-
: mcp.explicit_host_url
|
|
168
|
-
? mcp.explicit_host_url.replace(/\/+$/, '')
|
|
169
|
-
: this.effectivePort === null
|
|
170
|
-
? null
|
|
171
|
-
: `http://${mcp.host}:${this.effectivePort}`;
|
|
172
|
-
if (!base)
|
|
173
|
-
return null;
|
|
174
|
-
return `${base}/api/v1/issues/${encodeURIComponent(identifier)}/mcp`;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Register an active dispatch. Returns the bearer token the runner injects into the
|
|
178
|
-
* agent's MCP server config via ACP's session/new mcpServers field.
|
|
179
|
-
*/
|
|
180
|
-
activate(entry) {
|
|
181
|
-
const token = this.crypto.newToken();
|
|
182
|
-
entry.mcp_token = token;
|
|
183
|
-
entry.transitioned = false;
|
|
184
|
-
entry.steering_requested = false;
|
|
185
|
-
entry.steering_question = null;
|
|
186
|
-
entry.steering_context = null;
|
|
187
|
-
// Carry the dispatch-time tracker-root snapshot through verbatim. Reading
|
|
188
|
-
// this.tracker.currentRoot() here would be wrong: activate runs AFTER
|
|
189
|
-
// workspace setup and Gondolin VM bring-up — a window during
|
|
190
|
-
// which a WORKFLOW.md reload can mutate tracker.root. The dispatch-time
|
|
191
|
-
// value is the only one that accurately reflects the world the run was
|
|
192
|
-
// started in.
|
|
193
|
-
const active = {
|
|
194
|
-
issueId: entry.issue_id,
|
|
195
|
-
identifier: entry.identifier,
|
|
196
|
-
entry,
|
|
197
|
-
token,
|
|
198
|
-
pendingReply: null,
|
|
199
|
-
trackerRootSnapshot: entry.tracker_root_at_dispatch,
|
|
200
|
-
};
|
|
201
|
-
this.byIdentifier.set(entry.identifier, active);
|
|
202
|
-
log.debug('mcp activated', {
|
|
203
|
-
issue_identifier: entry.identifier,
|
|
204
|
-
tracker_root: active.trackerRootSnapshot,
|
|
205
|
-
});
|
|
206
|
-
return token;
|
|
207
|
-
}
|
|
208
|
-
deactivate(identifier) {
|
|
209
|
-
const active = this.byIdentifier.get(identifier);
|
|
210
|
-
if (!active)
|
|
211
|
-
return;
|
|
212
|
-
// Reject any waiter so the runner unblocks instead of hanging.
|
|
213
|
-
if (active.pendingReply) {
|
|
214
|
-
active.pendingReply.reject(new Error('mcp deactivated while awaiting human reply'));
|
|
215
|
-
active.pendingReply = null;
|
|
216
|
-
}
|
|
217
|
-
this.byIdentifier.delete(identifier);
|
|
218
|
-
log.debug('mcp deactivated', { issue_identifier: identifier });
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Resolve a pending human reply. Returns false if no waiter exists (the agent hasn't
|
|
222
|
-
* requested steering, or the reply was already delivered).
|
|
223
|
-
*/
|
|
224
|
-
submitSteeringReply(identifier, text) {
|
|
225
|
-
const active = this.byIdentifier.get(identifier);
|
|
226
|
-
if (!active || !active.pendingReply)
|
|
227
|
-
return false;
|
|
228
|
-
const { resolve } = active.pendingReply;
|
|
229
|
-
active.pendingReply = null;
|
|
230
|
-
resolve(text);
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Block until a human reply arrives, the cancel signal trips, or the active entry is
|
|
235
|
-
* deactivated. Returns the reply text or null if cancelled.
|
|
236
|
-
*/
|
|
237
|
-
async awaitSteeringReply(identifier, cancelSignal) {
|
|
238
|
-
const active = this.byIdentifier.get(identifier);
|
|
239
|
-
if (!active)
|
|
240
|
-
return null;
|
|
241
|
-
if (active.pendingReply) {
|
|
242
|
-
throw new Error('steering reply already being awaited for this issue');
|
|
243
|
-
}
|
|
244
|
-
let timer = null;
|
|
245
|
-
try {
|
|
246
|
-
return await new Promise((resolve, reject) => {
|
|
247
|
-
active.pendingReply = {
|
|
248
|
-
resolve: (text) => resolve(text),
|
|
249
|
-
reject: (err) => reject(err),
|
|
250
|
-
};
|
|
251
|
-
// Poll cancellation every 250ms (same cadence as the runner's cancel-check timer).
|
|
252
|
-
timer = setInterval(() => {
|
|
253
|
-
if (cancelSignal.cancelled) {
|
|
254
|
-
if (active.pendingReply) {
|
|
255
|
-
active.pendingReply = null;
|
|
256
|
-
resolve(null);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}, 250);
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
finally {
|
|
263
|
-
if (timer)
|
|
264
|
-
clearInterval(timer);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Handle a single JSON-RPC envelope. The HTTP layer takes care of routing (identifier
|
|
269
|
-
* lookup, token check) and passes us the parsed body. Returns null for notifications
|
|
270
|
-
* (no `id`); the HTTP layer responds with 204 in that case.
|
|
271
|
-
*/
|
|
272
|
-
async handleJsonRpc(identifier, token, body) {
|
|
273
|
-
const active = this.byIdentifier.get(identifier);
|
|
274
|
-
if (!active) {
|
|
275
|
-
return makeError(getId(body), -32001, 'issue not active');
|
|
276
|
-
}
|
|
277
|
-
if (!this.crypto.constantTimeEqual(active.token, token)) {
|
|
278
|
-
return makeError(getId(body), -32002, 'invalid token');
|
|
279
|
-
}
|
|
280
|
-
if (!isRpcRequest(body)) {
|
|
281
|
-
return makeError(getId(body), -32600, 'invalid JSON-RPC request');
|
|
282
|
-
}
|
|
283
|
-
const id = body.id ?? null;
|
|
284
|
-
const isNotification = body.id === undefined;
|
|
285
|
-
try {
|
|
286
|
-
switch (body.method) {
|
|
287
|
-
case 'initialize': {
|
|
288
|
-
if (isNotification)
|
|
289
|
-
return null;
|
|
290
|
-
return {
|
|
291
|
-
jsonrpc: '2.0',
|
|
292
|
-
id,
|
|
293
|
-
result: {
|
|
294
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
295
|
-
capabilities: {
|
|
296
|
-
tools: { listChanged: false },
|
|
297
|
-
},
|
|
298
|
-
serverInfo: {
|
|
299
|
-
name: 'smol-symphony',
|
|
300
|
-
version: '0.1.0',
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
case 'notifications/initialized':
|
|
306
|
-
case 'notifications/cancelled':
|
|
307
|
-
case 'notifications/progress': {
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
case 'tools/list': {
|
|
311
|
-
if (isNotification)
|
|
312
|
-
return null;
|
|
313
|
-
return { jsonrpc: '2.0', id, result: { tools: TOOL_LIST } };
|
|
314
|
-
}
|
|
315
|
-
case 'tools/call': {
|
|
316
|
-
if (isNotification)
|
|
317
|
-
return null;
|
|
318
|
-
const params = (body.params ?? {});
|
|
319
|
-
const name = params.name;
|
|
320
|
-
const args = params.arguments ?? {};
|
|
321
|
-
if (name === 'transition') {
|
|
322
|
-
return await this.callTransition(active, id, args);
|
|
323
|
-
}
|
|
324
|
-
if (name === 'request_human_steering') {
|
|
325
|
-
return this.callRequestHumanSteering(active, id, args);
|
|
326
|
-
}
|
|
327
|
-
if (name === 'propose_issue') {
|
|
328
|
-
return await this.callProposeIssue(active, id, args);
|
|
329
|
-
}
|
|
330
|
-
return makeError(id, -32601, `unknown tool: ${name}`);
|
|
331
|
-
}
|
|
332
|
-
case 'ping': {
|
|
333
|
-
if (isNotification)
|
|
334
|
-
return null;
|
|
335
|
-
return { jsonrpc: '2.0', id, result: {} };
|
|
336
|
-
}
|
|
337
|
-
default:
|
|
338
|
-
if (isNotification)
|
|
339
|
-
return null;
|
|
340
|
-
return makeError(id, -32601, `unknown method: ${body.method}`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
catch (err) {
|
|
344
|
-
log.warn('mcp handler error', {
|
|
345
|
-
issue_identifier: identifier,
|
|
346
|
-
method: body.method,
|
|
347
|
-
error: err.message,
|
|
348
|
-
});
|
|
349
|
-
return makeError(id, -32603, err.message);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Shared file-move + flag-flip path used by `transition`. Returns the tracker's
|
|
354
|
-
* resolved {from, to, newPath} on success, throwing the underlying TrackerError
|
|
355
|
-
* on failure so the caller can wrap it in a tool-error response.
|
|
356
|
-
*
|
|
357
|
-
* Cleanup-on-exit is decided by the role of the canonical target state: terminal
|
|
358
|
-
* targets clean the workspace, active/holding targets preserve it so the same
|
|
359
|
-
* `agent/<id>` git branch survives across the handoff. Callers must have already
|
|
360
|
-
* resolved `toState` to its canonical declared name via `canonicalStateName`, so
|
|
361
|
-
* this routine assumes the lookup is in the states map.
|
|
362
|
-
*/
|
|
363
|
-
async performTransition(active, toState, notes, actor) {
|
|
364
|
-
if (!this.tracker.moveIssueToState) {
|
|
365
|
-
throw new Error('this tracker does not support state transitions');
|
|
366
|
-
}
|
|
367
|
-
const fromRoot = active.trackerRootSnapshot ?? undefined;
|
|
368
|
-
const fromState = active.entry.issue.state;
|
|
369
|
-
const result = await this.tracker.moveIssueToState(active.issueId, toState, {
|
|
370
|
-
fromRoot,
|
|
371
|
-
fromState,
|
|
372
|
-
notes: notes.length > 0 ? notes : undefined,
|
|
373
|
-
actor,
|
|
374
|
-
});
|
|
375
|
-
active.entry.transitioned = true;
|
|
376
|
-
// Look up the canonical declared name (preserving operator-supplied casing)
|
|
377
|
-
// so the role lookup matches the workflow's `states:` map.
|
|
378
|
-
const stateMap = this.states;
|
|
379
|
-
const canonicalName = canonicalStateName(stateMap, result.toState);
|
|
380
|
-
const targetIsTerminal = canonicalName !== null && stateMap[canonicalName].role === 'terminal';
|
|
381
|
-
// Issue 38 / 144: suppress terminal cleanup when the PR engine owns this
|
|
382
|
-
// state's workspace. The pr resource will reap the workspace once the
|
|
383
|
-
// PR has merged (or been closed). Other terminal states (Cancelled
|
|
384
|
-
// typically) still cleanup immediately — the close path only needs to talk
|
|
385
|
-
// to GitHub, not the workspace.
|
|
386
|
-
const suppressCleanupForMerge = targetIsTerminal &&
|
|
387
|
-
canonicalName !== null &&
|
|
388
|
-
this.prMerge.enabled &&
|
|
389
|
-
this.prMerge.mergeState !== null &&
|
|
390
|
-
canonicalName.toLowerCase() === this.prMerge.mergeState.toLowerCase();
|
|
391
|
-
active.entry.cleanup_workspace_on_exit = targetIsTerminal && !suppressCleanupForMerge;
|
|
392
|
-
// Mutate the entry's view of the issue's state so downstream code (runner's
|
|
393
|
-
// cleanup, orchestrator's terminal-state workspace removal) resolves the right
|
|
394
|
-
// state's `actions:`. Without this, the terminal-state cleanup actions would
|
|
395
|
-
// resolve against the pre-transition state and a terminal-state handoff
|
|
396
|
-
// (e.g. Done's push_branch + create_pr_if_missing) would never fire.
|
|
397
|
-
active.entry.issue.state = canonicalName ?? result.toState;
|
|
398
|
-
// Stash the transition so the orchestrator shell can fold it into the run
|
|
399
|
-
// log as a `transition` lifecycle event after the attempt unwinds (issue
|
|
400
|
-
// 123). Pure here — no IO; the run-log write happens host-side.
|
|
401
|
-
active.entry.last_transition = {
|
|
402
|
-
from_state: result.fromState,
|
|
403
|
-
to_state: canonicalName ?? result.toState,
|
|
404
|
-
notes,
|
|
405
|
-
actor,
|
|
406
|
-
terminal: targetIsTerminal,
|
|
407
|
-
};
|
|
408
|
-
return result;
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* `symphony.transition({ to_state, notes? })`: validate the target against the
|
|
412
|
-
* declared `states:` map + the current state's `allowed_transitions` list, then
|
|
413
|
-
* delegate to `performTransition` which owns the actual file move and flag flip.
|
|
414
|
-
*
|
|
415
|
-
* Validation failures return MCP tool-result errors (`isError: true`) with a
|
|
416
|
-
* human-readable text block AND a structured JSON block describing the error
|
|
417
|
-
* shape. This is NOT a JSON-RPC `error` envelope — the SDK delivers it as a
|
|
418
|
-
* normal tool result and the agent reads the structured payload to pick a valid
|
|
419
|
-
* target on its next call. `transitioned` stays false; no file is touched.
|
|
420
|
-
*/
|
|
421
|
-
async callTransition(active, id, args) {
|
|
422
|
-
const toStateRaw = typeof args.to_state === 'string' ? args.to_state.trim() : '';
|
|
423
|
-
const notes = typeof args.notes === 'string' ? args.notes : '';
|
|
424
|
-
if (!toStateRaw) {
|
|
425
|
-
return makeToolError(id, 'to_state is required and must be a non-empty string');
|
|
426
|
-
}
|
|
427
|
-
if (!this.tracker.moveIssueToState) {
|
|
428
|
-
return makeToolError(id, 'this tracker does not support state transitions');
|
|
429
|
-
}
|
|
430
|
-
const stateMap = this.states;
|
|
431
|
-
const declaredNames = Object.keys(stateMap);
|
|
432
|
-
const canonicalTarget = canonicalStateName(stateMap, toStateRaw);
|
|
433
|
-
if (canonicalTarget === null) {
|
|
434
|
-
const text = `state "${toStateRaw}" is not declared. declared: ${declaredNames.length > 0 ? declaredNames.join(', ') : '<none>'}`;
|
|
435
|
-
log.info('mcp transition rejected: unknown_state', {
|
|
436
|
-
issue_identifier: active.identifier,
|
|
437
|
-
requested_to_state: toStateRaw,
|
|
438
|
-
declared_states: declaredNames,
|
|
439
|
-
});
|
|
440
|
-
return makeStructuredToolError(id, text, {
|
|
441
|
-
error: 'unknown_state',
|
|
442
|
-
declared_states: declaredNames,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
const fromStateRaw = active.entry.issue.state;
|
|
446
|
-
const canonicalFrom = canonicalStateName(stateMap, fromStateRaw);
|
|
447
|
-
if (canonicalFrom !== null) {
|
|
448
|
-
// Per the brief: `allowed_transitions: null | undefined` => "any declared
|
|
449
|
-
// state is reachable"; a present array => restrict to that list (empty
|
|
450
|
-
// list => no transitions out, agent must wait for human cleanup).
|
|
451
|
-
const allowed = stateMap[canonicalFrom].allowed_transitions;
|
|
452
|
-
if (allowed) {
|
|
453
|
-
const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
|
|
454
|
-
if (!allowedLower.has(canonicalTarget.toLowerCase())) {
|
|
455
|
-
const text = `transition to "${canonicalTarget}" is not allowed from "${canonicalFrom}". allowed: ${allowed.length > 0 ? allowed.join(', ') : '<none>'}`;
|
|
456
|
-
log.info('mcp transition rejected: transition_not_allowed', {
|
|
457
|
-
issue_identifier: active.identifier,
|
|
458
|
-
from_state: canonicalFrom,
|
|
459
|
-
requested_to_state: canonicalTarget,
|
|
460
|
-
allowed_transitions: allowed,
|
|
461
|
-
});
|
|
462
|
-
return makeStructuredToolError(id, text, {
|
|
463
|
-
error: 'transition_not_allowed',
|
|
464
|
-
from_state: canonicalFrom,
|
|
465
|
-
requested_to_state: canonicalTarget,
|
|
466
|
-
allowed_transitions: allowed,
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const actor = active.entry.resolved_actor;
|
|
472
|
-
try {
|
|
473
|
-
const result = await this.performTransition(active, canonicalTarget, notes, actor);
|
|
474
|
-
log.info('mcp transition', {
|
|
475
|
-
issue_identifier: active.identifier,
|
|
476
|
-
from: result.fromState,
|
|
477
|
-
to: result.toState,
|
|
478
|
-
notes_len: notes.length,
|
|
479
|
-
actor,
|
|
480
|
-
cleanup: active.entry.cleanup_workspace_on_exit,
|
|
481
|
-
});
|
|
482
|
-
return {
|
|
483
|
-
jsonrpc: '2.0',
|
|
484
|
-
id,
|
|
485
|
-
result: {
|
|
486
|
-
content: [
|
|
487
|
-
{
|
|
488
|
-
type: 'text',
|
|
489
|
-
text: `Transitioned ${active.identifier} from ${result.fromState} to ${result.toState}${notes.length > 0 ? ` with notes (${notes.length} chars) appended` : ''}. End this turn now; the next dispatch will pick up under the new state.`,
|
|
490
|
-
},
|
|
491
|
-
],
|
|
492
|
-
isError: false,
|
|
493
|
-
structuredContent: {
|
|
494
|
-
from_state: result.fromState,
|
|
495
|
-
to_state: result.toState,
|
|
496
|
-
cleanup_workspace_on_exit: active.entry.cleanup_workspace_on_exit,
|
|
497
|
-
notes_appended: notes.length > 0,
|
|
498
|
-
},
|
|
499
|
-
},
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
catch (err) {
|
|
503
|
-
return makeToolError(id, `failed to transition: ${err.message}`);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
callRequestHumanSteering(active, id, args) {
|
|
507
|
-
const question = typeof args.question === 'string' ? args.question.trim() : '';
|
|
508
|
-
const context = typeof args.context === 'string' ? args.context.trim() : '';
|
|
509
|
-
if (!question) {
|
|
510
|
-
return makeToolError(id, 'question is required and must be a non-empty string');
|
|
511
|
-
}
|
|
512
|
-
if (active.entry.steering_requested) {
|
|
513
|
-
return makeToolError(id, 'a steering request is already pending for this issue; end the turn and wait for the response');
|
|
514
|
-
}
|
|
515
|
-
active.entry.steering_requested = true;
|
|
516
|
-
active.entry.steering_question = question;
|
|
517
|
-
active.entry.steering_context = context.length > 0 ? context : null;
|
|
518
|
-
log.info('mcp request_human_steering', {
|
|
519
|
-
issue_identifier: active.identifier,
|
|
520
|
-
question_chars: question.length,
|
|
521
|
-
has_context: context.length > 0,
|
|
522
|
-
});
|
|
523
|
-
return {
|
|
524
|
-
jsonrpc: '2.0',
|
|
525
|
-
id,
|
|
526
|
-
result: {
|
|
527
|
-
content: [
|
|
528
|
-
{
|
|
529
|
-
type: 'text',
|
|
530
|
-
text: 'Question queued. End this turn now. The human response will arrive as the prompt for your next turn.',
|
|
531
|
-
},
|
|
532
|
-
],
|
|
533
|
-
isError: false,
|
|
534
|
-
},
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Drop a new issue file into the tracker's Triage/ directory. The orchestrator
|
|
539
|
-
* never dispatches Triage entries because the state's role is `holding`, not
|
|
540
|
-
* `active`; the operator approves or discards from the dashboard. Parent issue (the active
|
|
541
|
-
* dispatch this MCP call came from) is stamped into the front-matter as
|
|
542
|
-
* `proposed_by` so provenance is visible in the file and the UI.
|
|
543
|
-
*
|
|
544
|
-
* Uses the dispatch-time tracker root snapshot — same rationale as `transition`:
|
|
545
|
-
* a WORKFLOW.md reload that mutates tracker.root mid-flight must not redirect
|
|
546
|
-
* an in-flight propose call to a different filesystem location.
|
|
547
|
-
*/
|
|
548
|
-
async callProposeIssue(active, id, args) {
|
|
549
|
-
const titleRaw = typeof args.title === 'string' ? args.title.trim() : '';
|
|
550
|
-
const description = typeof args.description === 'string' ? args.description : '';
|
|
551
|
-
if (!titleRaw) {
|
|
552
|
-
return makeToolError(id, 'title is required and must be a non-empty string');
|
|
553
|
-
}
|
|
554
|
-
if (titleRaw.includes('\n')) {
|
|
555
|
-
return makeToolError(id, 'title must be a single line (no embedded newlines)');
|
|
556
|
-
}
|
|
557
|
-
const labels = Array.isArray(args.labels)
|
|
558
|
-
? args.labels.filter((x) => typeof x === 'string')
|
|
559
|
-
: [];
|
|
560
|
-
const priority = typeof args.priority === 'number' && Number.isFinite(args.priority) ? args.priority : null;
|
|
561
|
-
// Resolve the tracker root: prefer the dispatch-time snapshot, fall back to the
|
|
562
|
-
// tracker's live root (e.g. for tests / trackers without a snapshot). Without a
|
|
563
|
-
// resolvable root we can't write the file, so surface a clean tool error.
|
|
564
|
-
const root = active.trackerRootSnapshot ??
|
|
565
|
-
(this.tracker.currentRoot ? this.tracker.currentRoot() : null);
|
|
566
|
-
if (!root) {
|
|
567
|
-
return makeToolError(id, 'tracker root is not available; cannot create issue files (is this a non-local tracker?)');
|
|
568
|
-
}
|
|
569
|
-
// Landing state: first declared `holding` state in declaration order. The
|
|
570
|
-
// workflow parser refuses configs without one, but if validation was
|
|
571
|
-
// bypassed we surface a structured `no_holding_state` error so the agent
|
|
572
|
-
// doesn't keep retrying against a misconfigured workflow.
|
|
573
|
-
let landingState;
|
|
574
|
-
try {
|
|
575
|
-
landingState = pickHoldingState(this.states);
|
|
576
|
-
}
|
|
577
|
-
catch (err) {
|
|
578
|
-
if (err instanceof NoHoldingStateError) {
|
|
579
|
-
return makeStructuredToolError(id, 'cannot propose issue: workflow has no holding-role state declared', {
|
|
580
|
-
error: 'no_holding_state',
|
|
581
|
-
declared_states: Object.keys(this.states),
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
throw err;
|
|
585
|
-
}
|
|
586
|
-
try {
|
|
587
|
-
const result = await writeIssueFile({
|
|
588
|
-
trackerRoot: root,
|
|
589
|
-
state: landingState,
|
|
590
|
-
title: titleRaw,
|
|
591
|
-
description,
|
|
592
|
-
priority,
|
|
593
|
-
labels,
|
|
594
|
-
now: realClock,
|
|
595
|
-
extra_front_matter: {
|
|
596
|
-
proposed_by: active.identifier,
|
|
597
|
-
proposed_at: isoFromClock(this.now),
|
|
598
|
-
},
|
|
599
|
-
});
|
|
600
|
-
log.info('mcp propose_issue', {
|
|
601
|
-
proposed_by: active.identifier,
|
|
602
|
-
identifier: result.identifier,
|
|
603
|
-
state: result.state,
|
|
604
|
-
title: titleRaw,
|
|
605
|
-
description_chars: description.length,
|
|
606
|
-
});
|
|
607
|
-
return {
|
|
608
|
-
jsonrpc: '2.0',
|
|
609
|
-
id,
|
|
610
|
-
result: {
|
|
611
|
-
content: [
|
|
612
|
-
{
|
|
613
|
-
type: 'text',
|
|
614
|
-
text: `Proposed issue ${result.identifier} in ${result.state}/. The operator will approve or discard from the dashboard; do not wait for it. Continue your current task.`,
|
|
615
|
-
},
|
|
616
|
-
],
|
|
617
|
-
isError: false,
|
|
618
|
-
// Structured data alongside the human-readable text — MCP clients that
|
|
619
|
-
// surface this make the identifier programmatically available without
|
|
620
|
-
// re-parsing the content string.
|
|
621
|
-
structuredContent: {
|
|
622
|
-
identifier: result.identifier,
|
|
623
|
-
state: result.state,
|
|
624
|
-
path: result.path,
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
catch (err) {
|
|
630
|
-
return makeToolError(id, `failed to propose issue: ${err.message}`);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
/** Snapshot of currently active issues, used by HTTP for routing lookups. */
|
|
634
|
-
isActive(identifier, token) {
|
|
635
|
-
const active = this.byIdentifier.get(identifier);
|
|
636
|
-
return !!active && this.crypto.constantTimeEqual(active.token, token);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
function getId(body) {
|
|
640
|
-
if (body && typeof body === 'object' && !Array.isArray(body)) {
|
|
641
|
-
const rec = body;
|
|
642
|
-
if (typeof rec.id === 'string' || typeof rec.id === 'number')
|
|
643
|
-
return rec.id;
|
|
644
|
-
}
|
|
645
|
-
return null;
|
|
646
|
-
}
|
|
647
|
-
function isRpcRequest(body) {
|
|
648
|
-
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
649
|
-
return false;
|
|
650
|
-
const rec = body;
|
|
651
|
-
return rec.jsonrpc === '2.0' && typeof rec.method === 'string';
|
|
652
|
-
}
|
|
653
|
-
function makeError(id, code, message) {
|
|
654
|
-
return { jsonrpc: '2.0', id, error: { code, message } };
|
|
655
|
-
}
|
|
656
|
-
function makeToolError(id, message) {
|
|
657
|
-
// MCP convention: tool errors return result.isError=true rather than JSON-RPC error.
|
|
658
|
-
return {
|
|
659
|
-
jsonrpc: '2.0',
|
|
660
|
-
id,
|
|
661
|
-
result: {
|
|
662
|
-
content: [{ type: 'text', text: message }],
|
|
663
|
-
isError: true,
|
|
664
|
-
},
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* MCP tool error with both a human-readable text block and a structured JSON
|
|
669
|
-
* block. Used by `symphony.transition` so agents can read the rejection's
|
|
670
|
-
* structured payload (`declared_states`, `allowed_transitions`, etc.) to pick a
|
|
671
|
-
* valid target on their next call without re-parsing the prose. Pairs with
|
|
672
|
-
* `makeToolError` (text-only) for everything else.
|
|
673
|
-
*/
|
|
674
|
-
function makeStructuredToolError(id, text, json) {
|
|
675
|
-
return {
|
|
676
|
-
jsonrpc: '2.0',
|
|
677
|
-
id,
|
|
678
|
-
result: {
|
|
679
|
-
// MCP 2025-06-18 `CallToolResult.content` is `ContentBlock[]` and does not
|
|
680
|
-
// define a `json` block type; the canonical home for machine-readable
|
|
681
|
-
// payloads is `structuredContent`. Keep only the text block in `content[]`
|
|
682
|
-
// for human display and put the structured shape on the SDK-recognised slot.
|
|
683
|
-
content: [{ type: 'text', text }],
|
|
684
|
-
isError: true,
|
|
685
|
-
structuredContent: json,
|
|
686
|
-
},
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* Resolve a caller-supplied state name to its canonical declared form (the
|
|
691
|
-
* casing the operator wrote in `states:`). Comparison is case-insensitive to
|
|
692
|
-
* mirror the rest of symphony (eligibility, reconciliation, the local-tracker
|
|
693
|
-
* directory scan all compare lowercase). Returns null when no declared name
|
|
694
|
-
* matches.
|
|
695
|
-
*/
|
|
696
|
-
function canonicalStateName(states, name) {
|
|
697
|
-
if (Object.prototype.hasOwnProperty.call(states, name))
|
|
698
|
-
return name;
|
|
699
|
-
const lower = name.toLowerCase();
|
|
700
|
-
for (const declared of Object.keys(states)) {
|
|
701
|
-
if (declared.toLowerCase() === lower)
|
|
702
|
-
return declared;
|
|
703
|
-
}
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
//# sourceMappingURL=mcp.js.map
|