@ashlr/hub 2.2.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/CHANGELOG.md +1158 -0
- package/LICENSE +21 -0
- package/README.md +833 -0
- package/bin/ashlr +8 -0
- package/dist/api/core.d.ts +28 -0
- package/dist/api/core.js +42 -0
- package/dist/api/core.js.map +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.js +9 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/plugin.d.ts +12 -0
- package/dist/api/plugin.js +13 -0
- package/dist/api/plugin.js.map +1 -0
- package/dist/api/types.d.ts +9 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.js +23 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/ask.d.ts +22 -0
- package/dist/cli/ask.js +240 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/audit.d.ts +26 -0
- package/dist/cli/audit.js +199 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/backlog.d.ts +26 -0
- package/dist/cli/backlog.js +322 -0
- package/dist/cli/backlog.js.map +1 -0
- package/dist/cli/completions.d.ts +18 -0
- package/dist/cli/completions.js +156 -0
- package/dist/cli/completions.js.map +1 -0
- package/dist/cli/daemon.d.ts +31 -0
- package/dist/cli/daemon.js +334 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/demo-sandbox.d.ts +78 -0
- package/dist/cli/demo-sandbox.js +227 -0
- package/dist/cli/demo-sandbox.js.map +1 -0
- package/dist/cli/demo.d.ts +76 -0
- package/dist/cli/demo.js +285 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/digest.d.ts +46 -0
- package/dist/cli/digest.js +352 -0
- package/dist/cli/digest.js.map +1 -0
- package/dist/cli/doctor-init.d.ts +28 -0
- package/dist/cli/doctor-init.js +335 -0
- package/dist/cli/doctor-init.js.map +1 -0
- package/dist/cli/genome.d.ts +62 -0
- package/dist/cli/genome.js +1061 -0
- package/dist/cli/genome.js.map +1 -0
- package/dist/cli/gh.d.ts +27 -0
- package/dist/cli/gh.js +322 -0
- package/dist/cli/gh.js.map +1 -0
- package/dist/cli/goals.d.ts +55 -0
- package/dist/cli/goals.js +667 -0
- package/dist/cli/goals.js.map +1 -0
- package/dist/cli/health.d.ts +56 -0
- package/dist/cli/health.js +407 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/help.d.ts +51 -0
- package/dist/cli/help.js +436 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/inbox.d.ts +28 -0
- package/dist/cli/inbox.js +589 -0
- package/dist/cli/inbox.js.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.js +1208 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/knowledge.d.ts +33 -0
- package/dist/cli/knowledge.js +575 -0
- package/dist/cli/knowledge.js.map +1 -0
- package/dist/cli/mcp.d.ts +26 -0
- package/dist/cli/mcp.js +487 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/models.d.ts +26 -0
- package/dist/cli/models.js +435 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/new.d.ts +32 -0
- package/dist/cli/new.js +565 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/notify.d.ts +20 -0
- package/dist/cli/notify.js +188 -0
- package/dist/cli/notify.js.map +1 -0
- package/dist/cli/onboard.d.ts +85 -0
- package/dist/cli/onboard.js +305 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/open.d.ts +38 -0
- package/dist/cli/open.js +86 -0
- package/dist/cli/open.js.map +1 -0
- package/dist/cli/orient.d.ts +14 -0
- package/dist/cli/orient.js +110 -0
- package/dist/cli/orient.js.map +1 -0
- package/dist/cli/picker.d.ts +21 -0
- package/dist/cli/picker.js +177 -0
- package/dist/cli/picker.js.map +1 -0
- package/dist/cli/plugins.d.ts +21 -0
- package/dist/cli/plugins.js +394 -0
- package/dist/cli/plugins.js.map +1 -0
- package/dist/cli/preflight.d.ts +29 -0
- package/dist/cli/preflight.js +131 -0
- package/dist/cli/preflight.js.map +1 -0
- package/dist/cli/pulse.d.ts +26 -0
- package/dist/cli/pulse.js +482 -0
- package/dist/cli/pulse.js.map +1 -0
- package/dist/cli/reflect.d.ts +42 -0
- package/dist/cli/reflect.js +381 -0
- package/dist/cli/reflect.js.map +1 -0
- package/dist/cli/run.d.ts +29 -0
- package/dist/cli/run.js +687 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/sandbox.d.ts +20 -0
- package/dist/cli/sandbox.js +345 -0
- package/dist/cli/sandbox.js.map +1 -0
- package/dist/cli/seams.d.ts +41 -0
- package/dist/cli/seams.js +167 -0
- package/dist/cli/seams.js.map +1 -0
- package/dist/cli/serve.d.ts +25 -0
- package/dist/cli/serve.js +249 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/ship.d.ts +33 -0
- package/dist/cli/ship.js +433 -0
- package/dist/cli/ship.js.map +1 -0
- package/dist/cli/spec.d.ts +19 -0
- package/dist/cli/spec.js +478 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/cli/swarm.d.ts +42 -0
- package/dist/cli/swarm.js +1365 -0
- package/dist/cli/swarm.js.map +1 -0
- package/dist/cli/telemetry.d.ts +26 -0
- package/dist/cli/telemetry.js +364 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/cli/tui.d.ts +17 -0
- package/dist/cli/tui.js +52 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +53 -0
- package/dist/cli/ui.js +66 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli/update.d.ts +38 -0
- package/dist/cli/update.js +682 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/vercel.d.ts +20 -0
- package/dist/cli/vercel.js +193 -0
- package/dist/cli/vercel.js.map +1 -0
- package/dist/cli/verify-safety.d.ts +96 -0
- package/dist/cli/verify-safety.js +509 -0
- package/dist/cli/verify-safety.js.map +1 -0
- package/dist/cli/wire.d.ts +22 -0
- package/dist/cli/wire.js +205 -0
- package/dist/cli/wire.js.map +1 -0
- package/dist/core/classify.d.ts +51 -0
- package/dist/core/classify.js +450 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/config.d.ts +63 -0
- package/dist/core/config.js +474 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/daemon/loop.d.ts +74 -0
- package/dist/core/daemon/loop.js +618 -0
- package/dist/core/daemon/loop.js.map +1 -0
- package/dist/core/daemon/state.d.ts +66 -0
- package/dist/core/daemon/state.js +197 -0
- package/dist/core/daemon/state.js.map +1 -0
- package/dist/core/dashboard.d.ts +40 -0
- package/dist/core/dashboard.js +463 -0
- package/dist/core/dashboard.js.map +1 -0
- package/dist/core/digest/build.d.ts +51 -0
- package/dist/core/digest/build.js +230 -0
- package/dist/core/digest/build.js.map +1 -0
- package/dist/core/digest/deliver.d.ts +47 -0
- package/dist/core/digest/deliver.js +230 -0
- package/dist/core/digest/deliver.js.map +1 -0
- package/dist/core/digest/store.d.ts +57 -0
- package/dist/core/digest/store.js +223 -0
- package/dist/core/digest/store.js.map +1 -0
- package/dist/core/doctor-fix.d.ts +21 -0
- package/dist/core/doctor-fix.js +440 -0
- package/dist/core/doctor-fix.js.map +1 -0
- package/dist/core/doctor.d.ts +18 -0
- package/dist/core/doctor.js +806 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/env-bridge.d.ts +64 -0
- package/dist/core/env-bridge.js +103 -0
- package/dist/core/env-bridge.js.map +1 -0
- package/dist/core/genome/capture.d.ts +49 -0
- package/dist/core/genome/capture.js +352 -0
- package/dist/core/genome/capture.js.map +1 -0
- package/dist/core/genome/consolidate.d.ts +38 -0
- package/dist/core/genome/consolidate.js +426 -0
- package/dist/core/genome/consolidate.js.map +1 -0
- package/dist/core/genome/export.d.ts +29 -0
- package/dist/core/genome/export.js +102 -0
- package/dist/core/genome/export.js.map +1 -0
- package/dist/core/genome/playbook.d.ts +33 -0
- package/dist/core/genome/playbook.js +320 -0
- package/dist/core/genome/playbook.js.map +1 -0
- package/dist/core/genome/recall.d.ts +45 -0
- package/dist/core/genome/recall.js +293 -0
- package/dist/core/genome/recall.js.map +1 -0
- package/dist/core/genome/store.d.ts +62 -0
- package/dist/core/genome/store.js +711 -0
- package/dist/core/genome/store.js.map +1 -0
- package/dist/core/git.d.ts +34 -0
- package/dist/core/git.js +115 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/goals/advance.d.ts +82 -0
- package/dist/core/goals/advance.js +263 -0
- package/dist/core/goals/advance.js.map +1 -0
- package/dist/core/goals/planner.d.ts +58 -0
- package/dist/core/goals/planner.js +233 -0
- package/dist/core/goals/planner.js.map +1 -0
- package/dist/core/goals/store.d.ts +128 -0
- package/dist/core/goals/store.js +442 -0
- package/dist/core/goals/store.js.map +1 -0
- package/dist/core/inbox/apply.d.ts +36 -0
- package/dist/core/inbox/apply.js +407 -0
- package/dist/core/inbox/apply.js.map +1 -0
- package/dist/core/inbox/notify-proposal.d.ts +13 -0
- package/dist/core/inbox/notify-proposal.js +21 -0
- package/dist/core/inbox/notify-proposal.js.map +1 -0
- package/dist/core/inbox/store.d.ts +67 -0
- package/dist/core/inbox/store.js +258 -0
- package/dist/core/inbox/store.js.map +1 -0
- package/dist/core/index-engine.d.ts +62 -0
- package/dist/core/index-engine.js +486 -0
- package/dist/core/index-engine.js.map +1 -0
- package/dist/core/integrations/desktop-notify.d.ts +18 -0
- package/dist/core/integrations/desktop-notify.js +42 -0
- package/dist/core/integrations/desktop-notify.js.map +1 -0
- package/dist/core/integrations/editors.d.ts +50 -0
- package/dist/core/integrations/editors.js +216 -0
- package/dist/core/integrations/editors.js.map +1 -0
- package/dist/core/integrations/github.d.ts +66 -0
- package/dist/core/integrations/github.js +332 -0
- package/dist/core/integrations/github.js.map +1 -0
- package/dist/core/integrations/identity.d.ts +29 -0
- package/dist/core/integrations/identity.js +359 -0
- package/dist/core/integrations/identity.js.map +1 -0
- package/dist/core/integrations/notify.d.ts +23 -0
- package/dist/core/integrations/notify.js +96 -0
- package/dist/core/integrations/notify.js.map +1 -0
- package/dist/core/integrations/vercel.d.ts +37 -0
- package/dist/core/integrations/vercel.js +192 -0
- package/dist/core/integrations/vercel.js.map +1 -0
- package/dist/core/knowledge/ask.d.ts +34 -0
- package/dist/core/knowledge/ask.js +325 -0
- package/dist/core/knowledge/ask.js.map +1 -0
- package/dist/core/knowledge/graph.d.ts +51 -0
- package/dist/core/knowledge/graph.js +564 -0
- package/dist/core/knowledge/graph.js.map +1 -0
- package/dist/core/knowledge/index.d.ts +74 -0
- package/dist/core/knowledge/index.js +586 -0
- package/dist/core/knowledge/index.js.map +1 -0
- package/dist/core/learn/playbooks.d.ts +84 -0
- package/dist/core/learn/playbooks.js +241 -0
- package/dist/core/learn/playbooks.js.map +1 -0
- package/dist/core/learn/reflect.d.ts +87 -0
- package/dist/core/learn/reflect.js +435 -0
- package/dist/core/learn/reflect.js.map +1 -0
- package/dist/core/learn/store.d.ts +51 -0
- package/dist/core/learn/store.js +165 -0
- package/dist/core/learn/store.js.map +1 -0
- package/dist/core/learn/tuning.d.ts +48 -0
- package/dist/core/learn/tuning.js +201 -0
- package/dist/core/learn/tuning.js.map +1 -0
- package/dist/core/lifecycle/scaffold.d.ts +43 -0
- package/dist/core/lifecycle/scaffold.js +260 -0
- package/dist/core/lifecycle/scaffold.js.map +1 -0
- package/dist/core/lifecycle/ship.d.ts +48 -0
- package/dist/core/lifecycle/ship.js +513 -0
- package/dist/core/lifecycle/ship.js.map +1 -0
- package/dist/core/lifecycle/templates.d.ts +20 -0
- package/dist/core/lifecycle/templates.js +605 -0
- package/dist/core/lifecycle/templates.js.map +1 -0
- package/dist/core/mcp-gateway.d.ts +59 -0
- package/dist/core/mcp-gateway.js +385 -0
- package/dist/core/mcp-gateway.js.map +1 -0
- package/dist/core/mcp-native.d.ts +53 -0
- package/dist/core/mcp-native.js +507 -0
- package/dist/core/mcp-native.js.map +1 -0
- package/dist/core/mcp-registry.d.ts +36 -0
- package/dist/core/mcp-registry.js +180 -0
- package/dist/core/mcp-registry.js.map +1 -0
- package/dist/core/observability/budget-alert.d.ts +18 -0
- package/dist/core/observability/budget-alert.js +90 -0
- package/dist/core/observability/budget-alert.js.map +1 -0
- package/dist/core/observability/estimate.d.ts +27 -0
- package/dist/core/observability/estimate.js +188 -0
- package/dist/core/observability/estimate.js.map +1 -0
- package/dist/core/observability/forecast.d.ts +19 -0
- package/dist/core/observability/forecast.js +101 -0
- package/dist/core/observability/forecast.js.map +1 -0
- package/dist/core/observability/governance.d.ts +28 -0
- package/dist/core/observability/governance.js +101 -0
- package/dist/core/observability/governance.js.map +1 -0
- package/dist/core/observability/otlp.d.ts +88 -0
- package/dist/core/observability/otlp.js +217 -0
- package/dist/core/observability/otlp.js.map +1 -0
- package/dist/core/observability/rollup.d.ts +35 -0
- package/dist/core/observability/rollup.js +311 -0
- package/dist/core/observability/rollup.js.map +1 -0
- package/dist/core/observability/telemetry-sink.d.ts +63 -0
- package/dist/core/observability/telemetry-sink.js +350 -0
- package/dist/core/observability/telemetry-sink.js.map +1 -0
- package/dist/core/observability/usage-source.d.ts +60 -0
- package/dist/core/observability/usage-source.js +347 -0
- package/dist/core/observability/usage-source.js.map +1 -0
- package/dist/core/onboard.d.ts +27 -0
- package/dist/core/onboard.js +288 -0
- package/dist/core/onboard.js.map +1 -0
- package/dist/core/orient.d.ts +23 -0
- package/dist/core/orient.js +135 -0
- package/dist/core/orient.js.map +1 -0
- package/dist/core/phantom.d.ts +23 -0
- package/dist/core/phantom.js +279 -0
- package/dist/core/phantom.js.map +1 -0
- package/dist/core/plugins/host-api.d.ts +29 -0
- package/dist/core/plugins/host-api.js +113 -0
- package/dist/core/plugins/host-api.js.map +1 -0
- package/dist/core/plugins/integrity.d.ts +36 -0
- package/dist/core/plugins/integrity.js +73 -0
- package/dist/core/plugins/integrity.js.map +1 -0
- package/dist/core/plugins/manifest.d.ts +31 -0
- package/dist/core/plugins/manifest.js +316 -0
- package/dist/core/plugins/manifest.js.map +1 -0
- package/dist/core/plugins/registry.d.ts +87 -0
- package/dist/core/plugins/registry.js +415 -0
- package/dist/core/plugins/registry.js.map +1 -0
- package/dist/core/plugins/types.d.ts +182 -0
- package/dist/core/plugins/types.js +40 -0
- package/dist/core/plugins/types.js.map +1 -0
- package/dist/core/plugins/wrappers.d.ts +40 -0
- package/dist/core/plugins/wrappers.js +229 -0
- package/dist/core/plugins/wrappers.js.map +1 -0
- package/dist/core/portfolio/backlog.d.ts +40 -0
- package/dist/core/portfolio/backlog.js +177 -0
- package/dist/core/portfolio/backlog.js.map +1 -0
- package/dist/core/portfolio/scanners.d.ts +21 -0
- package/dist/core/portfolio/scanners.js +600 -0
- package/dist/core/portfolio/scanners.js.map +1 -0
- package/dist/core/providers.d.ts +34 -0
- package/dist/core/providers.js +250 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/quality/conventions.d.ts +35 -0
- package/dist/core/quality/conventions.js +267 -0
- package/dist/core/quality/conventions.js.map +1 -0
- package/dist/core/quality/fixes.d.ts +56 -0
- package/dist/core/quality/fixes.js +209 -0
- package/dist/core/quality/fixes.js.map +1 -0
- package/dist/core/quality/health.d.ts +69 -0
- package/dist/core/quality/health.js +350 -0
- package/dist/core/quality/health.js.map +1 -0
- package/dist/core/quality/store.d.ts +56 -0
- package/dist/core/quality/store.js +195 -0
- package/dist/core/quality/store.js.map +1 -0
- package/dist/core/readiness.d.ts +112 -0
- package/dist/core/readiness.js +431 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core/run/agent-loop.d.ts +40 -0
- package/dist/core/run/agent-loop.js +291 -0
- package/dist/core/run/agent-loop.js.map +1 -0
- package/dist/core/run/budget.d.ts +47 -0
- package/dist/core/run/budget.js +113 -0
- package/dist/core/run/budget.js.map +1 -0
- package/dist/core/run/engines.d.ts +75 -0
- package/dist/core/run/engines.js +199 -0
- package/dist/core/run/engines.js.map +1 -0
- package/dist/core/run/model-manager.d.ts +64 -0
- package/dist/core/run/model-manager.js +339 -0
- package/dist/core/run/model-manager.js.map +1 -0
- package/dist/core/run/orchestrator.d.ts +100 -0
- package/dist/core/run/orchestrator.js +1515 -0
- package/dist/core/run/orchestrator.js.map +1 -0
- package/dist/core/run/provider-client.d.ts +46 -0
- package/dist/core/run/provider-client.js +796 -0
- package/dist/core/run/provider-client.js.map +1 -0
- package/dist/core/run/retry.d.ts +19 -0
- package/dist/core/run/retry.js +68 -0
- package/dist/core/run/retry.js.map +1 -0
- package/dist/core/run/router.d.ts +50 -0
- package/dist/core/run/router.js +257 -0
- package/dist/core/run/router.js.map +1 -0
- package/dist/core/run/self-heal.d.ts +52 -0
- package/dist/core/run/self-heal.js +181 -0
- package/dist/core/run/self-heal.js.map +1 -0
- package/dist/core/run/streaming.d.ts +31 -0
- package/dist/core/run/streaming.js +122 -0
- package/dist/core/run/streaming.js.map +1 -0
- package/dist/core/run/verify.d.ts +30 -0
- package/dist/core/run/verify.js +204 -0
- package/dist/core/run/verify.js.map +1 -0
- package/dist/core/sandbox/audit.d.ts +31 -0
- package/dist/core/sandbox/audit.js +169 -0
- package/dist/core/sandbox/audit.js.map +1 -0
- package/dist/core/sandbox/policy.d.ts +61 -0
- package/dist/core/sandbox/policy.js +211 -0
- package/dist/core/sandbox/policy.js.map +1 -0
- package/dist/core/sandbox/worktree.d.ts +175 -0
- package/dist/core/sandbox/worktree.js +673 -0
- package/dist/core/sandbox/worktree.js.map +1 -0
- package/dist/core/seams/backlog.d.ts +47 -0
- package/dist/core/seams/backlog.js +47 -0
- package/dist/core/seams/backlog.js.map +1 -0
- package/dist/core/seams/daemon-coordinator.d.ts +72 -0
- package/dist/core/seams/daemon-coordinator.js +76 -0
- package/dist/core/seams/daemon-coordinator.js.map +1 -0
- package/dist/core/seams/genome.d.ts +45 -0
- package/dist/core/seams/genome.js +53 -0
- package/dist/core/seams/genome.js.map +1 -0
- package/dist/core/seams/identity.d.ts +40 -0
- package/dist/core/seams/identity.js +44 -0
- package/dist/core/seams/identity.js.map +1 -0
- package/dist/core/seams/inbox.d.ts +60 -0
- package/dist/core/seams/inbox.js +66 -0
- package/dist/core/seams/inbox.js.map +1 -0
- package/dist/core/seams/index.d.ts +20 -0
- package/dist/core/seams/index.js +21 -0
- package/dist/core/seams/index.js.map +1 -0
- package/dist/core/seams/portfolio.d.ts +50 -0
- package/dist/core/seams/portfolio.js +61 -0
- package/dist/core/seams/portfolio.js.map +1 -0
- package/dist/core/seams/registry.d.ts +42 -0
- package/dist/core/seams/registry.js +128 -0
- package/dist/core/seams/registry.js.map +1 -0
- package/dist/core/seams/run-swarm.d.ts +66 -0
- package/dist/core/seams/run-swarm.js +74 -0
- package/dist/core/seams/run-swarm.js.map +1 -0
- package/dist/core/seams/types.d.ts +123 -0
- package/dist/core/seams/types.js +35 -0
- package/dist/core/seams/types.js.map +1 -0
- package/dist/core/spec/spec-store.d.ts +62 -0
- package/dist/core/spec/spec-store.js +359 -0
- package/dist/core/spec/spec-store.js.map +1 -0
- package/dist/core/swarm/gate.d.ts +45 -0
- package/dist/core/swarm/gate.js +114 -0
- package/dist/core/swarm/gate.js.map +1 -0
- package/dist/core/swarm/planner.d.ts +32 -0
- package/dist/core/swarm/planner.js +293 -0
- package/dist/core/swarm/planner.js.map +1 -0
- package/dist/core/swarm/rollback.d.ts +56 -0
- package/dist/core/swarm/rollback.js +266 -0
- package/dist/core/swarm/rollback.js.map +1 -0
- package/dist/core/swarm/runner.d.ts +62 -0
- package/dist/core/swarm/runner.js +1263 -0
- package/dist/core/swarm/runner.js.map +1 -0
- package/dist/core/swarm/sign.d.ts +71 -0
- package/dist/core/swarm/sign.js +362 -0
- package/dist/core/swarm/sign.js.map +1 -0
- package/dist/core/swarm/store.d.ts +52 -0
- package/dist/core/swarm/store.js +195 -0
- package/dist/core/swarm/store.js.map +1 -0
- package/dist/core/tidy.d.ts +32 -0
- package/dist/core/tidy.js +354 -0
- package/dist/core/tidy.js.map +1 -0
- package/dist/core/tools-registry.d.ts +16 -0
- package/dist/core/tools-registry.js +308 -0
- package/dist/core/tools-registry.js.map +1 -0
- package/dist/core/types.d.ts +2545 -0
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/web/api.d.ts +53 -0
- package/dist/core/web/api.js +698 -0
- package/dist/core/web/api.js.map +1 -0
- package/dist/core/web/public/app.js +1906 -0
- package/dist/core/web/public/index.html +721 -0
- package/dist/core/web/public/styles.css +2007 -0
- package/dist/core/web/server.d.ts +18 -0
- package/dist/core/web/server.js +122 -0
- package/dist/core/web/server.js.map +1 -0
- package/dist/core/web/static.d.ts +18 -0
- package/dist/core/web/static.js +122 -0
- package/dist/core/web/static.js.map +1 -0
- package/dist/tui/app.d.ts +33 -0
- package/dist/tui/app.js +350 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/render.d.ts +20 -0
- package/dist/tui/render.js +558 -0
- package/dist/tui/render.js.map +1 -0
- package/package.json +80 -0
- package/schema/config.schema.json +223 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/web/api.ts — M14 JSON API handler.
|
|
3
|
+
*
|
|
4
|
+
* Handles all /api/* routes for the local web dashboard. Returns true if it
|
|
5
|
+
* handled the request (so server.ts does NOT fall through to static serving),
|
|
6
|
+
* false otherwise.
|
|
7
|
+
*
|
|
8
|
+
* Read-only routes (always available, no auth):
|
|
9
|
+
* GET /api/snapshot -> buildSnapshot(cfg)
|
|
10
|
+
* GET /api/portfolio -> buildSnapshot(cfg).portfolio | null (read-only; M29)
|
|
11
|
+
* GET /api/runs -> listRuns()
|
|
12
|
+
* GET /api/run/:id -> loadRun(id) | 404
|
|
13
|
+
* GET /api/swarms -> listSwarms()
|
|
14
|
+
* GET /api/swarm/:id -> loadSwarm(id) | 404
|
|
15
|
+
* GET /api/pulse?window=7d -> buildRollup(window, cfg)
|
|
16
|
+
* GET /api/genome[?q=...] -> recall(q, cfg) | loadGenome(cfg)
|
|
17
|
+
* GET /api/inbox -> listProposals({status:'pending'}) (read-only; M23)
|
|
18
|
+
* GET /api/daemon -> loadDaemonState() (read-only; M24; no control endpoint)
|
|
19
|
+
* GET /api/events -> Server-Sent Events stream
|
|
20
|
+
*
|
|
21
|
+
* Mutating route (ONLY when ctx.allowDispatch === true + token header):
|
|
22
|
+
* POST /api/run -> runGoal (budget-capped, local-first)
|
|
23
|
+
*
|
|
24
|
+
* SECURITY:
|
|
25
|
+
* - Never throws (500 on internal error).
|
|
26
|
+
* - Metadata only — no secret values ever serialised.
|
|
27
|
+
* - No outward/SSRF calls.
|
|
28
|
+
* - POST /api/run: 404 when !allowDispatch; constant-time token compare;
|
|
29
|
+
* requires Content-Type: application/json (415 otherwise); budget clamped,
|
|
30
|
+
* allowCloud:false always.
|
|
31
|
+
* - SSE: bounded poll, capped concurrent connections, timers cleared on
|
|
32
|
+
* client disconnect AND on server close() via the returned cleanup registry
|
|
33
|
+
* (tracked by server.ts).
|
|
34
|
+
*/
|
|
35
|
+
import { timingSafeEqual, randomBytes } from 'node:crypto';
|
|
36
|
+
import { buildSnapshot } from '../dashboard.js';
|
|
37
|
+
import { listRuns, loadRun, runGoal } from '../run/orchestrator.js';
|
|
38
|
+
import { listSwarms, loadSwarm } from '../swarm/store.js';
|
|
39
|
+
import { buildRollup } from '../observability/rollup.js';
|
|
40
|
+
import { loadGenome } from '../genome/store.js';
|
|
41
|
+
import { recall } from '../genome/recall.js';
|
|
42
|
+
import { listProposals, loadProposal, setStatus } from '../inbox/store.js';
|
|
43
|
+
// M24: read-only daemon state endpoint — no control surface; start/stop stays CLI-only.
|
|
44
|
+
import { loadDaemonState } from '../daemon/state.js';
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// SSE registry — shared across all open SSE connections so server.ts can
|
|
47
|
+
// drain them on close(). Module-level so the close() callback in server.ts
|
|
48
|
+
// can import and call drainSseConnections().
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/** Active SSE cleanup functions, keyed by a random connection id. */
|
|
51
|
+
const _sseCleanups = new Map();
|
|
52
|
+
/**
|
|
53
|
+
* Register a cleanup callback for an SSE connection.
|
|
54
|
+
* Returns the id so it can be deregistered on close.
|
|
55
|
+
*/
|
|
56
|
+
function registerSse(cleanup) {
|
|
57
|
+
const id = randomBytes(8).toString('hex');
|
|
58
|
+
_sseCleanups.set(id, cleanup);
|
|
59
|
+
return id;
|
|
60
|
+
}
|
|
61
|
+
/** Remove a registered SSE cleanup. */
|
|
62
|
+
function deregisterSse(id) {
|
|
63
|
+
_sseCleanups.delete(id);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Drain all open SSE connections (called by server.ts close()).
|
|
67
|
+
* Each cleanup clears the interval and ends the response.
|
|
68
|
+
*/
|
|
69
|
+
export function drainSseConnections() {
|
|
70
|
+
for (const cleanup of _sseCleanups.values()) {
|
|
71
|
+
try {
|
|
72
|
+
cleanup();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Best-effort.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
_sseCleanups.clear();
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// SSE poll interval — bounded, not configurable by callers.
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
const SSE_POLL_MS = 1500;
|
|
84
|
+
/**
|
|
85
|
+
* Maximum concurrent SSE connections. Each connection holds a socket + a
|
|
86
|
+
* bounded poll timer; cap the total so a scripted local client (or non-browser
|
|
87
|
+
* process on the loopback interface) cannot open an unbounded number of
|
|
88
|
+
* EventSource connections for a local resource-exhaustion DoS. Browsers
|
|
89
|
+
* self-limit to ~6 per origin, but the server must not rely on that.
|
|
90
|
+
*/
|
|
91
|
+
const SSE_MAX_CONNECTIONS = 64;
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Helpers
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
/** Write a JSON response. Never throws. */
|
|
96
|
+
function sendJson(res, status, body) {
|
|
97
|
+
try {
|
|
98
|
+
const payload = JSON.stringify(body);
|
|
99
|
+
res.writeHead(status, {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
'Cache-Control': 'no-store',
|
|
102
|
+
'X-Content-Type-Options': 'nosniff',
|
|
103
|
+
});
|
|
104
|
+
res.end(payload);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
try {
|
|
108
|
+
if (!res.headersSent) {
|
|
109
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
110
|
+
}
|
|
111
|
+
res.end('{"error":"internal error"}');
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Socket already closed — swallow.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Write a 500 JSON error. Never throws. */
|
|
119
|
+
function send500(res, msg = 'internal error') {
|
|
120
|
+
sendJson(res, 500, { error: msg });
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Constant-time string comparison to defend against timing attacks.
|
|
124
|
+
* Returns true iff a === b (both strings, same bytes).
|
|
125
|
+
*/
|
|
126
|
+
function safeEqual(a, b) {
|
|
127
|
+
try {
|
|
128
|
+
// Both must be the same byte length for timingSafeEqual.
|
|
129
|
+
const ba = Buffer.from(a, 'utf8');
|
|
130
|
+
const bb = Buffer.from(b, 'utf8');
|
|
131
|
+
if (ba.length !== bb.length)
|
|
132
|
+
return false;
|
|
133
|
+
return timingSafeEqual(ba, bb);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Parse and validate the `window` query param for /api/pulse.
|
|
141
|
+
* Allowed: '1d' | '7d' | '30d'; default '7d'.
|
|
142
|
+
*/
|
|
143
|
+
function parseWindow(raw) {
|
|
144
|
+
if (raw === '1d' || raw === '7d' || raw === '30d')
|
|
145
|
+
return raw;
|
|
146
|
+
return '7d';
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Safely read the full request body as a string (bounded to 64 KB).
|
|
150
|
+
* Rejects on oversized or errored requests.
|
|
151
|
+
*/
|
|
152
|
+
function readBody(req) {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const MAX_BYTES = 65_536;
|
|
155
|
+
let buf = '';
|
|
156
|
+
let total = 0;
|
|
157
|
+
req.on('data', (chunk) => {
|
|
158
|
+
const s = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
159
|
+
total += Buffer.byteLength(s, 'utf8');
|
|
160
|
+
if (total > MAX_BYTES) {
|
|
161
|
+
reject(new Error('request body too large'));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
buf += s;
|
|
165
|
+
});
|
|
166
|
+
req.on('end', () => resolve(buf));
|
|
167
|
+
req.on('error', reject);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Extract the value of a query parameter from a URL (or URL path string).
|
|
172
|
+
* Returns undefined if not present.
|
|
173
|
+
*/
|
|
174
|
+
function getQueryParam(url, name) {
|
|
175
|
+
try {
|
|
176
|
+
// URL may be just a path+query — prepend a dummy base so URL() can parse.
|
|
177
|
+
const parsed = new URL(url, 'http://localhost');
|
|
178
|
+
const v = parsed.searchParams.get(name);
|
|
179
|
+
return v === null ? undefined : v;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extract the path from the request URL, without query string.
|
|
187
|
+
* Never throws; falls back to '/'.
|
|
188
|
+
*/
|
|
189
|
+
function reqPath(req) {
|
|
190
|
+
try {
|
|
191
|
+
const raw = req.url ?? '/';
|
|
192
|
+
const parsed = new URL(raw, 'http://localhost');
|
|
193
|
+
return parsed.pathname;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return '/';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// SSE handler
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
/** Build the current runs slice payload for SSE. */
|
|
203
|
+
function sseRunsPayload() {
|
|
204
|
+
return listRuns().slice(0, 20).map((r) => ({
|
|
205
|
+
id: r.id,
|
|
206
|
+
goal: r.goal,
|
|
207
|
+
status: r.status,
|
|
208
|
+
tokens: (r.usage?.tokensIn ?? 0) + (r.usage?.tokensOut ?? 0),
|
|
209
|
+
updatedAt: r.updatedAt,
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
/** Build the current swarms slice payload for SSE. */
|
|
213
|
+
function sseSwarmsPayload() {
|
|
214
|
+
return listSwarms().slice(0, 20).map((s) => ({
|
|
215
|
+
id: s.id,
|
|
216
|
+
goal: s.goal,
|
|
217
|
+
status: s.status,
|
|
218
|
+
tasksDone: s.tasks.filter((t) => t.status === 'done' || t.status === 'skipped').length,
|
|
219
|
+
tasksTotal: s.tasks.length,
|
|
220
|
+
updatedAt: s.updatedAt,
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Handle GET /api/events: stream run/swarm updates as Server-Sent Events.
|
|
225
|
+
*
|
|
226
|
+
* On each poll tick, re-reads listRuns() + listSwarms() and emits NAMED SSE
|
|
227
|
+
* events — `event: runs` and `event: swarms` — each carrying its own JSON
|
|
228
|
+
* payload. The named events match the client's EventSource listeners
|
|
229
|
+
* (es.addEventListener('runs'|'swarms', ...)) so live burndown patches the
|
|
230
|
+
* views without a full reload. (An unnamed `data:` frame would dispatch only
|
|
231
|
+
* to es.onmessage, which the client does not register.)
|
|
232
|
+
*
|
|
233
|
+
* The poll timer is bounded (SSE_POLL_MS) and cleared on:
|
|
234
|
+
* (a) client disconnect (req 'close' event)
|
|
235
|
+
* (b) server shutdown (drainSseConnections())
|
|
236
|
+
*
|
|
237
|
+
* Concurrent connections are capped (SSE_MAX_CONNECTIONS) to bound timer/
|
|
238
|
+
* socket growth; excess connections get a 503.
|
|
239
|
+
*/
|
|
240
|
+
function handleSseEvents(req, res) {
|
|
241
|
+
// Cap concurrent SSE connections to bound timer/socket growth.
|
|
242
|
+
if (_sseCleanups.size >= SSE_MAX_CONNECTIONS) {
|
|
243
|
+
sendJson(res, 503, { error: 'too many live connections' });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// SSE headers — no buffering, keep-alive.
|
|
247
|
+
res.writeHead(200, {
|
|
248
|
+
'Content-Type': 'text/event-stream',
|
|
249
|
+
'Cache-Control': 'no-cache, no-store',
|
|
250
|
+
'Connection': 'keep-alive',
|
|
251
|
+
'X-Content-Type-Options': 'nosniff',
|
|
252
|
+
});
|
|
253
|
+
// Write an initial comment to flush headers to client.
|
|
254
|
+
try {
|
|
255
|
+
res.write(': connected\n\n');
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return; // Socket already gone.
|
|
259
|
+
}
|
|
260
|
+
// Helper: send one NAMED SSE event so the client's per-name listeners fire.
|
|
261
|
+
function sendNamed(event, payload) {
|
|
262
|
+
try {
|
|
263
|
+
const line = `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
|
|
264
|
+
res.write(line);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Socket closed; cleanup will be triggered by 'close' event.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Emit one full update (runs, swarms, inbox, daemon slices).
|
|
271
|
+
function emitUpdate() {
|
|
272
|
+
sendNamed('runs', sseRunsPayload());
|
|
273
|
+
sendNamed('swarms', sseSwarmsPayload());
|
|
274
|
+
// M32: live inbox + daemon state for the web command center. Metadata
|
|
275
|
+
// only — the inbox event carries id/title/kind, never diffs.
|
|
276
|
+
try {
|
|
277
|
+
const pending = listProposals({ status: 'pending' });
|
|
278
|
+
sendNamed('inbox', {
|
|
279
|
+
pending: pending.length,
|
|
280
|
+
proposals: pending.slice(0, 20).map((p) => ({
|
|
281
|
+
id: p.id,
|
|
282
|
+
title: p.title,
|
|
283
|
+
kind: p.kind,
|
|
284
|
+
repo: p.repo,
|
|
285
|
+
origin: p.origin,
|
|
286
|
+
createdAt: p.createdAt,
|
|
287
|
+
})),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
catch { /* inbox slice is best-effort */ }
|
|
291
|
+
try {
|
|
292
|
+
sendNamed('daemon', loadDaemonState());
|
|
293
|
+
}
|
|
294
|
+
catch { /* daemon slice is best-effort */ }
|
|
295
|
+
}
|
|
296
|
+
// Send an initial snapshot immediately.
|
|
297
|
+
try {
|
|
298
|
+
emitUpdate();
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// If the initial read fails, the client gets no data until the next tick.
|
|
302
|
+
}
|
|
303
|
+
// Poll on a bounded interval.
|
|
304
|
+
const intervalId = setInterval(() => {
|
|
305
|
+
try {
|
|
306
|
+
emitUpdate();
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Socket may be gone; 'close' event will clean up.
|
|
310
|
+
}
|
|
311
|
+
}, SSE_POLL_MS);
|
|
312
|
+
// Cleanup: clear the interval and end the response.
|
|
313
|
+
const cleanup = () => {
|
|
314
|
+
clearInterval(intervalId);
|
|
315
|
+
deregisterSse(sseId);
|
|
316
|
+
try {
|
|
317
|
+
res.end();
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Already ended.
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const sseId = registerSse(cleanup);
|
|
324
|
+
// Clear on client disconnect.
|
|
325
|
+
req.on('close', cleanup);
|
|
326
|
+
req.on('error', cleanup);
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Dispatch route (token-guarded, ONLY when allowDispatch === true)
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
/**
|
|
332
|
+
* Handle POST /api/run — launch `runGoal` with budget-capped, local-first opts.
|
|
333
|
+
*
|
|
334
|
+
* Security:
|
|
335
|
+
* - Route does not exist (404) when ctx.allowDispatch is false.
|
|
336
|
+
* - Requires `x-ashlr-token` header equal to ctx.token (constant-time compare).
|
|
337
|
+
* Missing/wrong token -> 401.
|
|
338
|
+
* - Requires `Content-Type: application/json` -> 415 otherwise (defence in
|
|
339
|
+
* depth against simple-request form-POST CSRF; the token check is the
|
|
340
|
+
* actual control).
|
|
341
|
+
* - Body is JSON { goal, budget?, maxSteps?, parallel? }.
|
|
342
|
+
* - allowCloud is always false; maxTokens and maxSteps are clamped to sane
|
|
343
|
+
* local-first ceilings (never higher than the CLI defaults).
|
|
344
|
+
*/
|
|
345
|
+
async function handleDispatch(req, res, cfg, ctx) {
|
|
346
|
+
// Token check (constant-time).
|
|
347
|
+
const providedToken = Array.isArray(req.headers['x-ashlr-token'])
|
|
348
|
+
? (req.headers['x-ashlr-token'][0] ?? '')
|
|
349
|
+
: (req.headers['x-ashlr-token'] ?? '');
|
|
350
|
+
if (!safeEqual(providedToken, ctx.token)) {
|
|
351
|
+
sendJson(res, 401, { error: 'unauthorized: missing or invalid x-ashlr-token' });
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
// Content-Type check — require JSON. Blocks simple-request form-POST CSRF
|
|
355
|
+
// attempts before body parsing (defence in depth; token is the real gate).
|
|
356
|
+
const contentType = Array.isArray(req.headers['content-type'])
|
|
357
|
+
? (req.headers['content-type'][0] ?? '')
|
|
358
|
+
: (req.headers['content-type'] ?? '');
|
|
359
|
+
if (!contentType.toLowerCase().trim().startsWith('application/json')) {
|
|
360
|
+
sendJson(res, 415, { error: 'Content-Type must be application/json' });
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// Parse body.
|
|
364
|
+
let body;
|
|
365
|
+
try {
|
|
366
|
+
const raw = await readBody(req);
|
|
367
|
+
body = JSON.parse(raw);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
sendJson(res, 400, { error: 'invalid JSON body' });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
|
|
374
|
+
sendJson(res, 400, { error: 'body must be a JSON object' });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const obj = body;
|
|
378
|
+
const goal = typeof obj['goal'] === 'string' ? obj['goal'].trim() : '';
|
|
379
|
+
if (!goal) {
|
|
380
|
+
sendJson(res, 400, { error: '"goal" (string) is required' });
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
// Local-first budget ceilings — never elevated by caller input.
|
|
384
|
+
const MAX_TOKENS_CEILING = 200_000;
|
|
385
|
+
const MAX_STEPS_CEILING = 40;
|
|
386
|
+
const MAX_PARALLEL_CEILING = 4;
|
|
387
|
+
const rawMaxTokens = typeof obj['maxTokens'] === 'number' ? obj['maxTokens'] : MAX_TOKENS_CEILING;
|
|
388
|
+
const rawMaxSteps = typeof obj['maxSteps'] === 'number' ? obj['maxSteps'] : MAX_STEPS_CEILING;
|
|
389
|
+
const rawParallel = typeof obj['parallel'] === 'number' ? obj['parallel'] : 2;
|
|
390
|
+
const maxTokens = Math.min(Math.max(1, Math.floor(rawMaxTokens)), MAX_TOKENS_CEILING);
|
|
391
|
+
const maxSteps = Math.min(Math.max(1, Math.floor(rawMaxSteps)), MAX_STEPS_CEILING);
|
|
392
|
+
const parallel = Math.min(Math.max(1, Math.floor(rawParallel)), MAX_PARALLEL_CEILING);
|
|
393
|
+
// Run goal (local-first, never cloud).
|
|
394
|
+
try {
|
|
395
|
+
const runState = await runGoal(goal, cfg, {
|
|
396
|
+
budget: {
|
|
397
|
+
maxTokens,
|
|
398
|
+
maxSteps,
|
|
399
|
+
allowCloud: false, // NEVER allow cloud via the dispatch endpoint
|
|
400
|
+
},
|
|
401
|
+
parallel,
|
|
402
|
+
allowCloud: false,
|
|
403
|
+
json: true,
|
|
404
|
+
});
|
|
405
|
+
sendJson(res, 200, {
|
|
406
|
+
id: runState.id,
|
|
407
|
+
status: runState.status,
|
|
408
|
+
goal: runState.goal,
|
|
409
|
+
usage: runState.usage,
|
|
410
|
+
result: runState.result,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
415
|
+
send500(res, `run failed: ${msg}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
// Main handleApi export
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
/**
|
|
422
|
+
* Handle a single request if its URL matches an /api/* route.
|
|
423
|
+
*
|
|
424
|
+
* @returns true if the request was an /api/* route and a response was written;
|
|
425
|
+
* false otherwise (server.ts falls through to static serving).
|
|
426
|
+
*
|
|
427
|
+
* Never throws — all errors are caught and returned as JSON 500 responses.
|
|
428
|
+
*/
|
|
429
|
+
export async function handleApi(req, res, cfg, ctx) {
|
|
430
|
+
const path = reqPath(req);
|
|
431
|
+
// Only handle /api/* paths.
|
|
432
|
+
if (!path.startsWith('/api/') && path !== '/api') {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const method = (req.method ?? 'GET').toUpperCase();
|
|
436
|
+
try {
|
|
437
|
+
// ── GET /api/snapshot ───────────────────────────────────────────────────
|
|
438
|
+
if (path === '/api/snapshot' && method === 'GET') {
|
|
439
|
+
const snapshot = await buildSnapshot(cfg);
|
|
440
|
+
// M32: additive field so the frontend can show (not guess) whether the
|
|
441
|
+
// dispatch/approve surfaces exist on this server instance.
|
|
442
|
+
sendJson(res, 200, { ...snapshot, dispatchEnabled: ctx.allowDispatch });
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
// ── GET /api/portfolio ────────────────────────────────────────────────────
|
|
446
|
+
// M29: read-only org-level portfolio projection. Reuses buildSnapshot (the
|
|
447
|
+
// same enrollment/index-scoped read as /api/snapshot) and surfaces ONLY the
|
|
448
|
+
// optional `.portfolio` section, or null when it was not populated (older
|
|
449
|
+
// producer / empty enrollment). NO mutation endpoint — there is no apply/
|
|
450
|
+
// approve/dispatch here; aggregation only. Never throws (caught below).
|
|
451
|
+
if (path === '/api/portfolio' && method === 'GET') {
|
|
452
|
+
const snapshot = await buildSnapshot(cfg);
|
|
453
|
+
sendJson(res, 200, snapshot.portfolio ?? null);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
// ── GET /api/runs ────────────────────────────────────────────────────────
|
|
457
|
+
if (path === '/api/runs' && method === 'GET') {
|
|
458
|
+
const runs = listRuns();
|
|
459
|
+
sendJson(res, 200, runs);
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
// ── GET /api/run/:id ─────────────────────────────────────────────────────
|
|
463
|
+
if (path.startsWith('/api/run/') && method === 'GET') {
|
|
464
|
+
const id = path.slice('/api/run/'.length);
|
|
465
|
+
if (!id) {
|
|
466
|
+
sendJson(res, 400, { error: 'run id required' });
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
const run = loadRun(id);
|
|
470
|
+
if (!run) {
|
|
471
|
+
sendJson(res, 404, { error: `run not found: ${id}` });
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
sendJson(res, 200, run);
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
// ── GET /api/swarms ──────────────────────────────────────────────────────
|
|
478
|
+
if (path === '/api/swarms' && method === 'GET') {
|
|
479
|
+
const swarms = listSwarms();
|
|
480
|
+
sendJson(res, 200, swarms);
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
// ── GET /api/swarm/:id ───────────────────────────────────────────────────
|
|
484
|
+
if (path.startsWith('/api/swarm/') && method === 'GET') {
|
|
485
|
+
const id = path.slice('/api/swarm/'.length);
|
|
486
|
+
if (!id) {
|
|
487
|
+
sendJson(res, 400, { error: 'swarm id required' });
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
const swarm = loadSwarm(id);
|
|
491
|
+
if (!swarm) {
|
|
492
|
+
sendJson(res, 404, { error: `swarm not found: ${id}` });
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
sendJson(res, 200, swarm);
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
// ── GET /api/pulse ───────────────────────────────────────────────────────
|
|
499
|
+
if (path === '/api/pulse' && method === 'GET') {
|
|
500
|
+
const rawWindow = getQueryParam(req.url ?? '', 'window');
|
|
501
|
+
const window = parseWindow(rawWindow);
|
|
502
|
+
const rollup = buildRollup(window, cfg);
|
|
503
|
+
sendJson(res, 200, rollup);
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
// ── GET /api/genome ──────────────────────────────────────────────────────
|
|
507
|
+
if (path === '/api/genome' && method === 'GET') {
|
|
508
|
+
const q = getQueryParam(req.url ?? '', 'q');
|
|
509
|
+
if (q && q.trim()) {
|
|
510
|
+
// Query mode: use recall.
|
|
511
|
+
const hits = await recall(q, cfg);
|
|
512
|
+
sendJson(res, 200, hits);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
// List mode: load all genome entries.
|
|
516
|
+
const entries = loadGenome(cfg);
|
|
517
|
+
sendJson(res, 200, entries);
|
|
518
|
+
}
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
// ── GET /api/inbox ───────────────────────────────────────────────────────
|
|
522
|
+
// M23: read-only pending-proposals view. listProposals never throws.
|
|
523
|
+
if (path === '/api/inbox' && method === 'GET') {
|
|
524
|
+
const proposals = listProposals({ status: 'pending' });
|
|
525
|
+
sendJson(res, 200, {
|
|
526
|
+
pending: proposals.length,
|
|
527
|
+
proposals,
|
|
528
|
+
});
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
// ── GET /api/inbox/:id — full proposal detail incl. diff (read-only; M32).
|
|
532
|
+
if (path.startsWith('/api/inbox/') && method === 'GET') {
|
|
533
|
+
const id = path.slice('/api/inbox/'.length);
|
|
534
|
+
if (!id || id.includes('/')) {
|
|
535
|
+
sendJson(res, 400, { error: 'proposal id required' });
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
const proposal = loadProposal(id);
|
|
539
|
+
if (!proposal) {
|
|
540
|
+
sendJson(res, 404, { error: `proposal not found: ${id}` });
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
sendJson(res, 200, proposal);
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
// ── POST /api/inbox/:id/approve|reject (M32) ─────────────────────────
|
|
547
|
+
// The web approval surface. Gated IDENTICALLY to POST /api/run — the
|
|
548
|
+
// routes do not exist (404) unless `ashlr serve --allow-dispatch`, and
|
|
549
|
+
// every request needs the constant-time-compared x-ashlr-token + JSON
|
|
550
|
+
// Content-Type. Approve mirrors the CLI flow (src/cli/inbox.ts):
|
|
551
|
+
// setStatus('approved') → applyProposal(id, {confirmed:true}); apply
|
|
552
|
+
// failure is reported in the ApplyResult (apply.ts owns failed-state).
|
|
553
|
+
if (path.startsWith('/api/inbox/') && method === 'POST') {
|
|
554
|
+
if (!ctx.allowDispatch) {
|
|
555
|
+
sendJson(res, 404, { error: 'not found' });
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
const sub = path.slice('/api/inbox/'.length); // "<id>/approve" | "<id>/reject"
|
|
559
|
+
const parts = sub.split('/');
|
|
560
|
+
const id = parts[0] ?? '';
|
|
561
|
+
const action = parts[1] ?? '';
|
|
562
|
+
if (!id || (action !== 'approve' && action !== 'reject') || parts.length > 2) {
|
|
563
|
+
sendJson(res, 404, { error: `not found: ${method} ${path}` });
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
// Token check (constant-time) — same gate as handleDispatch.
|
|
567
|
+
const providedToken = Array.isArray(req.headers['x-ashlr-token'])
|
|
568
|
+
? (req.headers['x-ashlr-token'][0] ?? '')
|
|
569
|
+
: (req.headers['x-ashlr-token'] ?? '');
|
|
570
|
+
if (!safeEqual(providedToken, ctx.token)) {
|
|
571
|
+
sendJson(res, 401, { error: 'unauthorized: missing or invalid x-ashlr-token' });
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
const contentType = Array.isArray(req.headers['content-type'])
|
|
575
|
+
? (req.headers['content-type'][0] ?? '')
|
|
576
|
+
: (req.headers['content-type'] ?? '');
|
|
577
|
+
if (!contentType.toLowerCase().trim().startsWith('application/json')) {
|
|
578
|
+
sendJson(res, 415, { error: 'Content-Type must be application/json' });
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
const proposal = loadProposal(id);
|
|
582
|
+
if (!proposal) {
|
|
583
|
+
sendJson(res, 404, { error: `proposal not found: ${id}` });
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
if (proposal.status !== 'pending') {
|
|
587
|
+
sendJson(res, 409, { error: `proposal is ${proposal.status}, not pending` });
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
if (action === 'reject') {
|
|
591
|
+
setStatus(id, 'rejected');
|
|
592
|
+
sendJson(res, 200, { ok: true, id, status: 'rejected' });
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
// approve → apply (the ONLY outward path; applyProposal owns its gates:
|
|
596
|
+
// enrollment, kill switch, confirm — all still enforced inside).
|
|
597
|
+
setStatus(id, 'approved');
|
|
598
|
+
const { applyProposal } = await import('../inbox/apply.js');
|
|
599
|
+
const result = await applyProposal(id, { confirmed: true });
|
|
600
|
+
sendJson(res, result.ok ? 200 : 500, result);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
// ── GET /api/daemon ─────────────────────────────────────────────────────
|
|
604
|
+
// M24: read-only daemon state. No control endpoint — start/stop is CLI-only.
|
|
605
|
+
// loadDaemonState() never throws; returns zeroed state when not yet
|
|
606
|
+
// initialised (daemon/state.ts absent at runtime => caught below and 500'd,
|
|
607
|
+
// but the import is soft-guarded in state.ts itself).
|
|
608
|
+
if (path === '/api/daemon' && method === 'GET') {
|
|
609
|
+
const ds = loadDaemonState();
|
|
610
|
+
sendJson(res, 200, ds);
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
// ── GET /api/estimate ───────────────────────────────────────────────────
|
|
614
|
+
// M32: read-only pre-flight cost estimate (pure local computation over
|
|
615
|
+
// persisted history — no token needed; same class as /api/pulse).
|
|
616
|
+
if (path === '/api/estimate' && method === 'GET') {
|
|
617
|
+
const kind = getQueryParam(req.url ?? '', 'kind') ?? 'run';
|
|
618
|
+
const goal = getQueryParam(req.url ?? '', 'goal') ?? '';
|
|
619
|
+
const rawMax = getQueryParam(req.url ?? '', 'maxTokens');
|
|
620
|
+
const maxTokens = rawMax !== undefined && /^\d+$/.test(rawMax) ? Number(rawMax) : undefined;
|
|
621
|
+
if (!goal.trim()) {
|
|
622
|
+
sendJson(res, 400, { error: 'goal query parameter required' });
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
const { estimateRun, estimateSwarm } = await import('../observability/estimate.js');
|
|
626
|
+
const est = kind === 'swarm'
|
|
627
|
+
? await estimateSwarm(goal, { maxTokens }, cfg)
|
|
628
|
+
: await estimateRun(goal, { maxTokens }, cfg);
|
|
629
|
+
sendJson(res, 200, est);
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
// ── GET /api/orient ─────────────────────────────────────────────────────
|
|
633
|
+
// M31: read-only composite session-start context (genome + health +
|
|
634
|
+
// backlog + inbox + attention). Same no-auth read class as /api/snapshot.
|
|
635
|
+
if (path === '/api/orient' && method === 'GET') {
|
|
636
|
+
const { buildOrientation } = await import('../orient.js');
|
|
637
|
+
const repo = getQueryParam(req.url ?? '', 'repo');
|
|
638
|
+
const result = await buildOrientation(cfg, repo);
|
|
639
|
+
sendJson(res, 200, result);
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
// ── GET /api/health ─────────────────────────────────────────────────────
|
|
643
|
+
// M31: read-only — the latest PERSISTED health report (never re-scans).
|
|
644
|
+
if (path === '/api/health' && method === 'GET') {
|
|
645
|
+
const { loadPreviousReport } = await import('../quality/store.js');
|
|
646
|
+
const report = loadPreviousReport();
|
|
647
|
+
sendJson(res, 200, report ?? null);
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
// ── GET /api/backlog ────────────────────────────────────────────────────
|
|
651
|
+
// M31: read-only — the persisted backlog (never triggers a scan).
|
|
652
|
+
if (path === '/api/backlog' && method === 'GET') {
|
|
653
|
+
const { loadBacklog } = await import('../portfolio/backlog.js');
|
|
654
|
+
const backlog = loadBacklog();
|
|
655
|
+
sendJson(res, 200, backlog ?? null);
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
// ── GET /api/impact ─────────────────────────────────────────────────────
|
|
659
|
+
// M31: read-only knowledge-graph impact for ?target=<file|symbol>.
|
|
660
|
+
if (path === '/api/impact' && method === 'GET') {
|
|
661
|
+
const target = getQueryParam(req.url ?? '', 'target');
|
|
662
|
+
if (!target || !target.trim()) {
|
|
663
|
+
sendJson(res, 400, { error: 'target query parameter required' });
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
const { impact } = await import('../knowledge/graph.js');
|
|
667
|
+
sendJson(res, 200, impact(target));
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
// ── GET /api/events (SSE) ────────────────────────────────────────────────
|
|
671
|
+
if (path === '/api/events' && method === 'GET') {
|
|
672
|
+
handleSseEvents(req, res);
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
// ── POST /api/run ────────────────────────────────────────────────────────
|
|
676
|
+
if (path === '/api/run' && method === 'POST') {
|
|
677
|
+
if (!ctx.allowDispatch) {
|
|
678
|
+
// Route does not exist when dispatch is disabled.
|
|
679
|
+
sendJson(res, 404, { error: 'not found' });
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
await handleDispatch(req, res, cfg, ctx);
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
// ── Method not allowed on known /api/ routes ─────────────────────────────
|
|
686
|
+
// If path starts with /api/ but matched none of the above, it's either
|
|
687
|
+
// a wrong method or an unknown sub-path. Return 404.
|
|
688
|
+
sendJson(res, 404, { error: `not found: ${method} ${path}` });
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
catch (err) {
|
|
692
|
+
// Catch-all: never let an unhandled error escape.
|
|
693
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
694
|
+
send500(res, msg);
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
//# sourceMappingURL=api.js.map
|