airaknit 1.1.2-rc.9
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/LICENSE +84 -0
- package/README.md +202 -0
- package/bin/airaknit +9 -0
- package/bin/airaknit-project +14 -0
- package/bin/kanna +9 -0
- package/dist/client/assets/CompactSummaryMessage-Yw0BDWEJ.js +1 -0
- package/dist/client/assets/ExitPlanModeMessage-DIdkQ4uF.js +1 -0
- package/dist/client/assets/LocalFilePreviewDialog-DQx2eiCc.js +3 -0
- package/dist/client/assets/LocalProjectsSection-C4xlWkgS.js +1 -0
- package/dist/client/assets/TextMessage-B5G39DEJ.js +1 -0
- package/dist/client/assets/UserMessage-CIkWk-0L.js +1 -0
- package/dist/client/assets/_basePickBy-CVrAFfnZ.js +1 -0
- package/dist/client/assets/_baseUniq-JL-aaF4P.js +1 -0
- package/dist/client/assets/arc-B07zg7ol.js +1 -0
- package/dist/client/assets/architecture-YZFGNWBL-PSLVJL3p.js +1 -0
- package/dist/client/assets/architectureDiagram-Q4EWVU46-DfWIF1G_.js +36 -0
- package/dist/client/assets/array-BGFCBI0e.js +1 -0
- package/dist/client/assets/blockDiagram-DXYQGD6D-CzwIeo_B.js +132 -0
- package/dist/client/assets/bundle-mjs-BDE2gWbQ.js +1 -0
- package/dist/client/assets/button-DO50qOGv.js +1 -0
- package/dist/client/assets/c4Diagram-AHTNJAMY-CR8DCQRE.js +10 -0
- package/dist/client/assets/channel-Dj-UUfaF.js +1 -0
- package/dist/client/assets/chunk-2KRD3SAO-dznP-cn8.js +1 -0
- package/dist/client/assets/chunk-336JU56O-Dqss5Vu6.js +2 -0
- package/dist/client/assets/chunk-426QAEUC-CEKis_0_.js +1 -0
- package/dist/client/assets/chunk-4BX2VUAB-BYOv0Gm1.js +1 -0
- package/dist/client/assets/chunk-4TB4RGXK-BxzubH5S.js +206 -0
- package/dist/client/assets/chunk-55IACEB6-BSlTj03a.js +1 -0
- package/dist/client/assets/chunk-5FUZZQ4R-9Au93Bi1.js +62 -0
- package/dist/client/assets/chunk-5PVQY5BW-BhksHFEZ.js +2 -0
- package/dist/client/assets/chunk-67CJDMHE-BFwhz-8t.js +1 -0
- package/dist/client/assets/chunk-7N4EOEYR-BDUPds87.js +1 -0
- package/dist/client/assets/chunk-AA7GKIK3-CEWTdyXO.js +1 -0
- package/dist/client/assets/chunk-BO2N2NFS-D0LvxnhU.js +103 -0
- package/dist/client/assets/chunk-BSJP7CBP-BNJnK6sq.js +1 -0
- package/dist/client/assets/chunk-Bj-mKKzh.js +1 -0
- package/dist/client/assets/chunk-CIAEETIT-CYhfoCeN.js +1 -0
- package/dist/client/assets/chunk-EDXVE4YY-C5ovJLc0.js +1 -0
- package/dist/client/assets/chunk-ENJZ2VHE-HOhYaeGr.js +10 -0
- package/dist/client/assets/chunk-FMBD7UC4-BLCiKcAQ.js +15 -0
- package/dist/client/assets/chunk-FOC6F5B3-B6GtY2ek.js +1 -0
- package/dist/client/assets/chunk-ICPOFSXX-DPoIZoC5.js +122 -0
- package/dist/client/assets/chunk-K5T4RW27-BsKN63rv.js +94 -0
- package/dist/client/assets/chunk-KGLVRYIC-BUGn9uuY.js +1 -0
- package/dist/client/assets/chunk-LIHQZDEY-DhaZyo03.js +1 -0
- package/dist/client/assets/chunk-ORNJ4GCN-DlpeeJyi.js +1 -0
- package/dist/client/assets/chunk-OYMX7WX6-Dc9q7aYA.js +231 -0
- package/dist/client/assets/chunk-QZHKN3VN-BEdrPoSb.js +1 -0
- package/dist/client/assets/chunk-U2HBQHQK-CIB3Bjjd.js +70 -0
- package/dist/client/assets/chunk-X2U36JSP-CtB-o8Yp.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-C6iHhX_8.js +32 -0
- package/dist/client/assets/chunk-YZCP3GAM-CTmKr6ZH.js +1 -0
- package/dist/client/assets/chunk-ZZ45TVLE-BgU8A2RF.js +1 -0
- package/dist/client/assets/classDiagram-6PBFFD2Q-Bqk5e679.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-6pSaZOkC.js +1 -0
- package/dist/client/assets/client-BrKWI4CM.js +1 -0
- package/dist/client/assets/client-CGgNRU9w.js +1 -0
- package/dist/client/assets/client-DMSLRzg9.js +6 -0
- package/dist/client/assets/clone-DWcL7whJ.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-CrV5wsV_.js +1 -0
- package/dist/client/assets/cytoscape.esm--aLzKuep.js +321 -0
- package/dist/client/assets/dagre-CuRxWcrj.js +1 -0
- package/dist/client/assets/dagre-KV5264BT-BIDiVnkA.js +4 -0
- package/dist/client/assets/defaultLocale-CRZydyG6.js +1 -0
- package/dist/client/assets/diagram-5BDNPKRD-i1kjKRCB.js +10 -0
- package/dist/client/assets/diagram-G4DWMVQ6-9ZSLuhbl.js +24 -0
- package/dist/client/assets/diagram-MMDJMWI5-B4_CUjgv.js +43 -0
- package/dist/client/assets/diagram-TYMM5635-Ct5eTGS8.js +24 -0
- package/dist/client/assets/dist-CuB4kiSK.js +1 -0
- package/dist/client/assets/erDiagram-SMLLAGMA-Cy38ercc.js +85 -0
- package/dist/client/assets/flowDiagram-DWJPFMVM-CZKuYl0V.js +162 -0
- package/dist/client/assets/ganttDiagram-T4ZO3ILL-DLPjCh7a.js +292 -0
- package/dist/client/assets/gitGraph-7Q5UKJZL-DqbrtEp9.js +1 -0
- package/dist/client/assets/gitGraphDiagram-UUTBAWPF-BoRBkDhQ.js +106 -0
- package/dist/client/assets/graphlib-BcQ6qlQh.js +1 -0
- package/dist/client/assets/highlighted-body-OFNGDK62-BEpBVDTX.js +1 -0
- package/dist/client/assets/index-CetCiuqP.js +105 -0
- package/dist/client/assets/index-N29Mip7A.css +1 -0
- package/dist/client/assets/info-OMHHGYJF-D98DRBJX.js +1 -0
- package/dist/client/assets/infoDiagram-42DDH7IO-BAcdTWbt.js +2 -0
- package/dist/client/assets/init-B8gtcn7T.js +1 -0
- package/dist/client/assets/isArrayLikeObject-D8SJFmkN.js +1 -0
- package/dist/client/assets/isEmpty-BF3YX5Jk.js +1 -0
- package/dist/client/assets/ishikawaDiagram-UXIWVN3A-Ynu2VKdC.js +70 -0
- package/dist/client/assets/journeyDiagram-VCZTEJTY-BjfhQaN3.js +139 -0
- package/dist/client/assets/jsx-runtime-CyI9ICYU.js +1 -0
- package/dist/client/assets/kanban-definition-6JOO6SKY-JLXH9zUJ.js +89 -0
- package/dist/client/assets/katex-B94qP8b6.js +265 -0
- package/dist/client/assets/lib--QVjyxmL.js +29 -0
- package/dist/client/assets/lib-B6rgJiZ9.js +1 -0
- package/dist/client/assets/line-DCrYfLBn.js +1 -0
- package/dist/client/assets/linear-_4upLmeo.js +1 -0
- package/dist/client/assets/mermaid-GHXKKRXX-rwJHYUmW.js +1 -0
- package/dist/client/assets/mermaid-parser.core-KZinfW8o.js +4 -0
- package/dist/client/assets/mermaid.core-QqY9gSNe.js +11 -0
- package/dist/client/assets/mindmap-definition-QFDTVHPH-TWgHDAzp.js +96 -0
- package/dist/client/assets/ordinal-CCj7PWgZ.js +1 -0
- package/dist/client/assets/packet-4T2RLAQJ-DEvfkn3F.js +1 -0
- package/dist/client/assets/path-DZF-JdEe.js +1 -0
- package/dist/client/assets/pie-ZZUOXDRM-72e6WVjb.js +1 -0
- package/dist/client/assets/pieDiagram-DEJITSTG-Cl8PCsoj.js +30 -0
- package/dist/client/assets/preload-helper-rov5CBGT.js +1 -0
- package/dist/client/assets/pty-client-DZ27IS00.js +1 -0
- package/dist/client/assets/ptyInstancesStore-D9ag7SYd.js +1 -0
- package/dist/client/assets/quadrantDiagram-34T5L4WZ-CHyVGp9E.js +7 -0
- package/dist/client/assets/radar-PYXPWWZC-Cp7xd_EY.js +1 -0
- package/dist/client/assets/react-CClhXMB2.js +1 -0
- package/dist/client/assets/react-Dd6D81m0.js +1 -0
- package/dist/client/assets/react-dom--G6_6fQ_.js +1 -0
- package/dist/client/assets/requirementDiagram-MS252O5E-DaanG2iM.js +84 -0
- package/dist/client/assets/rough.esm-BsmKo2S5.js +1 -0
- package/dist/client/assets/sankeyDiagram-XADWPNL6-B_fhLY36.js +10 -0
- package/dist/client/assets/sequenceDiagram-FGHM5R23-C5FNrveI.js +157 -0
- package/dist/client/assets/src-DeTlMJAU.js +1 -0
- package/dist/client/assets/stateDiagram-FHFEXIEX-nTTcdjjQ.js +1 -0
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Dw0632j_.js +1 -0
- package/dist/client/assets/timeline-definition-GMOUNBTQ-DkQV1yP8.js +120 -0
- package/dist/client/assets/treeView-SZITEDCU-ZLIgC7_K.js +1 -0
- package/dist/client/assets/treemap-W4RFUUIX-BqaMbB6N.js +1 -0
- package/dist/client/assets/uiIdentityOverlay-Ba7GNj7m.js +1 -0
- package/dist/client/assets/vennDiagram-DHZGUBPP-DbZ2xgs6.js +34 -0
- package/dist/client/assets/wardley-RL74JXVD-DXQS8zf4.js +1 -0
- package/dist/client/assets/wardleyDiagram-NUSXRM2D-BzCJ6MAu.js +20 -0
- package/dist/client/assets/xychartDiagram-5P7HB3ND-BSlFecop.js +7 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/favicon.svg +15 -0
- package/dist/client/fonts/body-medium.woff2 +0 -0
- package/dist/client/fonts/body-regular-italic.woff2 +0 -0
- package/dist/client/fonts/body-regular.woff2 +0 -0
- package/dist/client/fonts/body-semibold.woff2 +0 -0
- package/dist/client/index.html +31 -0
- package/dist/client/manifest.webmanifest +12 -0
- package/dist/client/sw.js +32 -0
- package/package.json +122 -0
- package/src/nats/auth-callout/callout-config.test.ts +93 -0
- package/src/nats/auth-callout/callout-config.ts +109 -0
- package/src/nats/auth-callout/callout.integration.test.ts +332 -0
- package/src/nats/auth-callout/keys.ts +103 -0
- package/src/nats/auth-callout/responder.ts +241 -0
- package/src/nats/auth-callout/scope-policy.test.ts +159 -0
- package/src/nats/auth-callout/scope-policy.ts +210 -0
- package/src/nats/auth-callout/token.test.ts +163 -0
- package/src/nats/auth-callout/token.ts +157 -0
- package/src/nats/nats-daemon-callout.ts +194 -0
- package/src/nats/nats-daemon.test.ts +77 -0
- package/src/nats/nats-daemon.ts +50 -0
- package/src/nats/nats-token.test.ts +61 -0
- package/src/nats/nats-token.ts +59 -0
- package/src/runner/coordination-mcp-integration.test.ts +134 -0
- package/src/runner/nats-coordination-client.test.ts +49 -0
- package/src/runner/nats-coordination-client.ts +94 -0
- package/src/runner/runner-agent.test.ts +469 -0
- package/src/runner/runner-agent.ts +453 -0
- package/src/runner/runner-credential.test.ts +93 -0
- package/src/runner/runner-credential.ts +82 -0
- package/src/runner/runner-nats.test.ts +495 -0
- package/src/runner/runner-nats.ts +323 -0
- package/src/runner/runner-pair.test.ts +107 -0
- package/src/runner/runner-pair.ts +81 -0
- package/src/runner/runner.test.ts +135 -0
- package/src/runner/runner.ts +212 -0
- package/src/runner/turn-factories.test.ts +97 -0
- package/src/runner/turn-factories.ts +475 -0
- package/src/server/agent-config-journey.test.ts +106 -0
- package/src/server/agent.ts +8 -0
- package/src/server/auto-continue/auth-error-detector.ts +66 -0
- package/src/server/auto-continue/limit-detector.ts +194 -0
- package/src/server/bm25.test.ts +92 -0
- package/src/server/bm25.ts +101 -0
- package/src/server/chat-events-jetstream.test.ts +135 -0
- package/src/server/claude-harness.ts +360 -0
- package/src/server/claude-pty/agent-normalizers.ts +309 -0
- package/src/server/claude-pty/auth.test.ts +38 -0
- package/src/server/claude-pty/auth.ts +32 -0
- package/src/server/claude-pty/claude-session-registry.adapter.ts +81 -0
- package/src/server/claude-pty/claude-session-registry.test.ts +149 -0
- package/src/server/claude-pty/driver.test.ts +902 -0
- package/src/server/claude-pty/driver.ts +807 -0
- package/src/server/claude-pty/jsonl-path.adapter.ts +57 -0
- package/src/server/claude-pty/jsonl-path.test.ts +114 -0
- package/src/server/claude-pty/jsonl-to-event.test.ts +241 -0
- package/src/server/claude-pty/jsonl-to-event.ts +174 -0
- package/src/server/claude-pty/output-ring.test.ts +35 -0
- package/src/server/claude-pty/output-ring.ts +25 -0
- package/src/server/claude-pty/parity-matrix.test.ts +227 -0
- package/src/server/claude-pty/pid-registry.adapter.ts +135 -0
- package/src/server/claude-pty/pid-registry.test.ts +122 -0
- package/src/server/claude-pty/preflight/binary-fingerprint.adapter.ts +20 -0
- package/src/server/claude-pty/preflight/binary-fingerprint.test.ts +32 -0
- package/src/server/claude-pty/pty-instance-registry.test.ts +177 -0
- package/src/server/claude-pty/pty-instance-registry.ts +166 -0
- package/src/server/claude-pty/pty-memory-sampler.adapter.test.ts +103 -0
- package/src/server/claude-pty/pty-memory-sampler.adapter.ts +85 -0
- package/src/server/claude-pty/pty-process.adapter.ts +66 -0
- package/src/server/claude-pty/pty-process.test.ts +49 -0
- package/src/server/claude-pty/resolve-binary.adapter.ts +106 -0
- package/src/server/claude-pty/resolve-binary.test.ts +118 -0
- package/src/server/claude-pty/runtime-dir.adapter.ts +19 -0
- package/src/server/claude-pty/settings-writer.adapter.ts +27 -0
- package/src/server/claude-pty/settings-writer.test.ts +22 -0
- package/src/server/claude-pty/smoke-test-io.adapter.ts +28 -0
- package/src/server/claude-pty/smoke-test.test.ts +191 -0
- package/src/server/claude-pty/smoke-test.ts +185 -0
- package/src/server/claude-pty/subagent-orchestrator.ts +887 -0
- package/src/server/claude-pty/tool-callback.ts +274 -0
- package/src/server/claude-pty/tui-control.test.ts +272 -0
- package/src/server/claude-pty/tui-control.ts +182 -0
- package/src/server/claude-pty/tui-source.adapter.test.ts +360 -0
- package/src/server/claude-pty/tui-source.adapter.ts +343 -0
- package/src/server/claude-pty/tunnel-gateway.ts +12 -0
- package/src/server/claude-pty-mcp/canonical-args.ts +15 -0
- package/src/server/claude-pty-mcp/fs-stat.adapter.ts +8 -0
- package/src/server/claude-pty-mcp/history-primer.ts +90 -0
- package/src/server/claude-pty-mcp/http-server.adapter.ts +33 -0
- package/src/server/claude-pty-mcp/mcp-http.ts +177 -0
- package/src/server/claude-pty-mcp/mcp.ts +412 -0
- package/src/server/claude-pty-mcp/mention-parser.ts +25 -0
- package/src/server/claude-pty-mcp/paths.ts +24 -0
- package/src/server/claude-pty-mcp/permission-gate.ts +243 -0
- package/src/server/claude-pty-mcp/terminal-pid-registry.adapter.ts +107 -0
- package/src/server/claude-pty-mcp/tools/ask-user-question.test.ts +119 -0
- package/src/server/claude-pty-mcp/tools/ask-user-question.ts +61 -0
- package/src/server/claude-pty-mcp/tools/bash.adapter.ts +76 -0
- package/src/server/claude-pty-mcp/tools/bash.test.ts +56 -0
- package/src/server/claude-pty-mcp/tools/delegate-subagent.test.ts +155 -0
- package/src/server/claude-pty-mcp/tools/delegate-subagent.ts +111 -0
- package/src/server/claude-pty-mcp/tools/edit.adapter.ts +95 -0
- package/src/server/claude-pty-mcp/tools/edit.test.ts +93 -0
- package/src/server/claude-pty-mcp/tools/exit-plan-mode.test.ts +61 -0
- package/src/server/claude-pty-mcp/tools/exit-plan-mode.ts +50 -0
- package/src/server/claude-pty-mcp/tools/glob.adapter.ts +86 -0
- package/src/server/claude-pty-mcp/tools/glob.test.ts +61 -0
- package/src/server/claude-pty-mcp/tools/grep.adapter.ts +126 -0
- package/src/server/claude-pty-mcp/tools/grep.test.ts +62 -0
- package/src/server/claude-pty-mcp/tools/read.adapter.ts +58 -0
- package/src/server/claude-pty-mcp/tools/read.test.ts +62 -0
- package/src/server/claude-pty-mcp/tools/tool-callback-shim.ts +42 -0
- package/src/server/claude-pty-mcp/tools/webfetch.test.ts +81 -0
- package/src/server/claude-pty-mcp/tools/webfetch.ts +82 -0
- package/src/server/claude-pty-mcp/tools/websearch.test.ts +40 -0
- package/src/server/claude-pty-mcp/tools/websearch.ts +42 -0
- package/src/server/claude-pty-mcp/tools/write.adapter.ts +60 -0
- package/src/server/claude-pty-mcp/tools/write.test.ts +52 -0
- package/src/server/claude-pty-mcp/uploads.adapter.ts +98 -0
- package/src/server/claude-pty-mcp/uploads.ts +38 -0
- package/src/server/claude-turn.test.ts +176 -0
- package/src/server/cli-runtime.test.ts +456 -0
- package/src/server/cli-runtime.ts +374 -0
- package/src/server/cli-supervisor.ts +81 -0
- package/src/server/cli.ts +78 -0
- package/src/server/client-log-forwarder.test.ts +74 -0
- package/src/server/client-log-forwarder.ts +75 -0
- package/src/server/codex-app-server-protocol.ts +449 -0
- package/src/server/codex-app-server.test.ts +2990 -0
- package/src/server/codex-app-server.ts +1713 -0
- package/src/server/coordination-integration.test.ts +63 -0
- package/src/server/coordination-mcp.test.ts +149 -0
- package/src/server/coordination-mcp.ts +197 -0
- package/src/server/delegation-coordinator.test.ts +675 -0
- package/src/server/delegation-coordinator.ts +454 -0
- package/src/server/discovery.test.ts +211 -0
- package/src/server/discovery.ts +301 -0
- package/src/server/event-store-agent-config.test.ts +124 -0
- package/src/server/event-store-coordination.test.ts +149 -0
- package/src/server/event-store-profile.test.ts +132 -0
- package/src/server/event-store-repo.test.ts +154 -0
- package/src/server/event-store-runner-team.test.ts +104 -0
- package/src/server/event-store.test.ts +342 -0
- package/src/server/event-store.ts +2208 -0
- package/src/server/events.ts +379 -0
- package/src/server/extension-router.test.ts +183 -0
- package/src/server/extension-router.ts +114 -0
- package/src/server/extensions/agents/server.test.ts +191 -0
- package/src/server/extensions/agents/server.ts +108 -0
- package/src/server/extensions/c3/server.test.ts +284 -0
- package/src/server/extensions/c3/server.ts +212 -0
- package/src/server/extensions/code/server.test.ts +200 -0
- package/src/server/extensions/code/server.ts +150 -0
- package/src/server/extensions.config.ts +10 -0
- package/src/server/external-open.ts +69 -0
- package/src/server/generate-fork-context.ts +58 -0
- package/src/server/generate-merge-context.test.ts +290 -0
- package/src/server/generate-merge-context.ts +141 -0
- package/src/server/generate-title.ts +36 -0
- package/src/server/git-clone-policy.test.ts +138 -0
- package/src/server/git-clone-policy.ts +27 -0
- package/src/server/harness-types.ts +1 -0
- package/src/server/journey-verification.test.ts +640 -0
- package/src/server/journey-verification.ts +195 -0
- package/src/server/machine-name.ts +22 -0
- package/src/server/nats-auth.test.ts +92 -0
- package/src/server/nats-auth.ts +6 -0
- package/src/server/nats-bind-guard.test.ts +71 -0
- package/src/server/nats-bind-guard.ts +42 -0
- package/src/server/nats-bridge.test.ts +141 -0
- package/src/server/nats-bridge.ts +111 -0
- package/src/server/nats-connector.test.ts +56 -0
- package/src/server/nats-connector.ts +71 -0
- package/src/server/nats-daemon-manager.test.ts +76 -0
- package/src/server/nats-daemon-manager.ts +174 -0
- package/src/server/nats-publisher.test.ts +356 -0
- package/src/server/nats-publisher.ts +271 -0
- package/src/server/nats-responders.test.ts +1018 -0
- package/src/server/nats-responders.ts +925 -0
- package/src/server/nats-streams.test.ts +112 -0
- package/src/server/nats-streams.ts +129 -0
- package/src/server/oauth-pool/oauth-responders.ts +185 -0
- package/src/server/oauth-pool/oauth-settings-store.ts +173 -0
- package/src/server/oauth-pool/oauth-token-pool.ts +303 -0
- package/src/server/orchestration.test.ts +1063 -0
- package/src/server/orchestration.ts +716 -0
- package/src/server/pairing-endpoints.test.ts +171 -0
- package/src/server/pairing-store.test.ts +154 -0
- package/src/server/pairing-store.ts +124 -0
- package/src/server/paths.ts +27 -0
- package/src/server/pr3-liveness.test.ts +252 -0
- package/src/server/process-utils.ts +10 -0
- package/src/server/project-cli.ts +180 -0
- package/src/server/provider-catalog.test.ts +177 -0
- package/src/server/provider-catalog.ts +146 -0
- package/src/server/pty-responders.ts +345 -0
- package/src/server/push-notifications.test.ts +234 -0
- package/src/server/push-notifications.ts +188 -0
- package/src/server/quick-response.test.ts +196 -0
- package/src/server/quick-response.ts +154 -0
- package/src/server/read-models-agent-config.test.ts +59 -0
- package/src/server/read-models-coordination.test.ts +69 -0
- package/src/server/read-models.test.ts +332 -0
- package/src/server/read-models.ts +258 -0
- package/src/server/repo-journey.test.ts +96 -0
- package/src/server/repo-manager.test.ts +118 -0
- package/src/server/repo-manager.ts +97 -0
- package/src/server/repo-status.test.ts +54 -0
- package/src/server/repo-status.ts +82 -0
- package/src/server/restart.test.ts +27 -0
- package/src/server/restart.ts +30 -0
- package/src/server/runner-incompatible-gate.test.ts +383 -0
- package/src/server/runner-manager.test.ts +72 -0
- package/src/server/runner-manager.ts +312 -0
- package/src/server/runner-pairing-urls.test.ts +59 -0
- package/src/server/runner-pairing-urls.ts +67 -0
- package/src/server/runner-proxy.test.ts +456 -0
- package/src/server/runner-proxy.ts +494 -0
- package/src/server/runner-router.test.ts +429 -0
- package/src/server/runner-router.ts +212 -0
- package/src/server/runner-routing.test.ts +584 -0
- package/src/server/runtime-registry.test.ts +436 -0
- package/src/server/runtime-registry.ts +307 -0
- package/src/server/sandbox-health.test.ts +127 -0
- package/src/server/sandbox-health.ts +94 -0
- package/src/server/sandbox-journey.test.ts +232 -0
- package/src/server/sandbox-manager.test.ts +146 -0
- package/src/server/sandbox-manager.ts +159 -0
- package/src/server/server.test.ts +287 -0
- package/src/server/server.ts +1108 -0
- package/src/server/session-discovery.test.ts +129 -0
- package/src/server/session-discovery.ts +475 -0
- package/src/server/session-index.test.ts +362 -0
- package/src/server/session-index.ts +119 -0
- package/src/server/session-seed.ts +288 -0
- package/src/server/share.test.ts +108 -0
- package/src/server/share.ts +113 -0
- package/src/server/skill-discovery.test.ts +201 -0
- package/src/server/skill-discovery.ts +77 -0
- package/src/server/storage/test-helpers.ts +67 -0
- package/src/server/terminal-manager.test.ts +309 -0
- package/src/server/terminal-manager.ts +354 -0
- package/src/server/transcript-consumer.test.ts +339 -0
- package/src/server/transcript-consumer.ts +162 -0
- package/src/server/transcript-search.test.ts +193 -0
- package/src/server/transcript-search.ts +83 -0
- package/src/server/transcript-utils.ts +52 -0
- package/src/server/update-manager.test.ts +107 -0
- package/src/server/update-manager.ts +230 -0
- package/src/server/workflow-engine.test.ts +251 -0
- package/src/server/workflow-engine.ts +169 -0
- package/src/server/workflow-mcp.ts +49 -0
- package/src/server/workflow-store.test.ts +155 -0
- package/src/server/workflow-store.ts +139 -0
- package/src/server/workspace-agent-integration.test.ts +167 -0
- package/src/server/workspace-agent-routes.test.ts +127 -0
- package/src/server/workspace-agent-routes.ts +89 -0
- package/src/server/workspace-agent.test.ts +103 -0
- package/src/server/workspace-agent.ts +102 -0
- package/src/server/workspace-cli.test.ts +79 -0
- package/src/server/workspace-config-manager.test.ts +109 -0
- package/src/server/workspace-config-manager.ts +83 -0
- package/src/server/workspace-directory-policy.test.ts +109 -0
- package/src/server/workspace-directory-policy.ts +56 -0
- package/src/shared/agent-config-types.ts +25 -0
- package/src/shared/branding.test.ts +42 -0
- package/src/shared/branding.ts +54 -0
- package/src/shared/compression.test.ts +85 -0
- package/src/shared/compression.ts +42 -0
- package/src/shared/coordination-store.test.ts +24 -0
- package/src/shared/coordination-store.ts +26 -0
- package/src/shared/dev-ports.test.ts +84 -0
- package/src/shared/dev-ports.ts +100 -0
- package/src/shared/extension-types.ts +45 -0
- package/src/shared/fork-presets.ts +54 -0
- package/src/shared/harness-types.test.ts +15 -0
- package/src/shared/harness-types.ts +21 -0
- package/src/shared/log-sink.test.ts +112 -0
- package/src/shared/log-sink.ts +185 -0
- package/src/shared/mention-pattern.ts +7 -0
- package/src/shared/merge-presets.ts +41 -0
- package/src/shared/nats-subjects.test.ts +61 -0
- package/src/shared/nats-subjects.ts +131 -0
- package/src/shared/permission-policy.ts +136 -0
- package/src/shared/ports.ts +3 -0
- package/src/shared/preset-types.ts +15 -0
- package/src/shared/profile-types.ts +49 -0
- package/src/shared/projectFileUrl.ts +36 -0
- package/src/shared/protocol.ts +153 -0
- package/src/shared/pty-instance.ts +43 -0
- package/src/shared/puggy/diagnostics/result.ts +18 -0
- package/src/shared/puggy/expressions/evaluate.ts +292 -0
- package/src/shared/puggy/index.test.ts +101 -0
- package/src/shared/puggy/index.ts +69 -0
- package/src/shared/puggy/parser/ast.ts +110 -0
- package/src/shared/puggy/parser/parser.ts +624 -0
- package/src/shared/puggy/renderer/html.ts +447 -0
- package/src/shared/runner-protocol.test.ts +277 -0
- package/src/shared/runner-protocol.ts +210 -0
- package/src/shared/runner-team-types.ts +73 -0
- package/src/shared/runtime-types.ts +48 -0
- package/src/shared/sandbox-types.ts +53 -0
- package/src/shared/tailwind-build.test.ts +12 -0
- package/src/shared/tinkaria-system-prompt.ts +101 -0
- package/src/shared/tools.test.ts +335 -0
- package/src/shared/tools.ts +397 -0
- package/src/shared/transcript-entries.ts +27 -0
- package/src/shared/transcript-render.test.ts +225 -0
- package/src/shared/transcript-render.ts +467 -0
- package/src/shared/types.ts +1031 -0
- package/src/shared/vite-config.test.ts +47 -0
- package/src/shared/web-context.test.ts +110 -0
- package/src/shared/web-context.ts +76 -0
- package/src/shared/workflow-types.ts +51 -0
- package/src/shared/workspace-types.ts +100 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import { afterEach, describe, test, expect } from "bun:test"
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { NatsServer } from "@lagz0ne/nats-embedded"
|
|
6
|
+
import { connect, type NatsConnection } from "@nats-io/transport-node"
|
|
7
|
+
import { Kvm } from "@nats-io/kv"
|
|
8
|
+
import {
|
|
9
|
+
DelegationCoordinator,
|
|
10
|
+
type DelegationStore,
|
|
11
|
+
type CreateDelegationArgs,
|
|
12
|
+
type TerminalOutcome,
|
|
13
|
+
} from "./delegation-coordinator"
|
|
14
|
+
import type { TranscriptEntry, AgentResultEntry, UserPromptEntry, AssistantTextEntry } from "../shared/types"
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
function createFakeDelegationStore(overrides?: Partial<DelegationStore>): DelegationStore & {
|
|
21
|
+
appendedMessages: Array<{ chatId: string; entry: TranscriptEntry }>
|
|
22
|
+
existingChats: Set<string>
|
|
23
|
+
workspacesByChatId: Map<string, string>
|
|
24
|
+
lastTurnOutcomes: Map<string, string>
|
|
25
|
+
} {
|
|
26
|
+
const appendedMessages: Array<{ chatId: string; entry: TranscriptEntry }> = []
|
|
27
|
+
const existingChats = new Set<string>(["parent-1", "child-1", "child-2"])
|
|
28
|
+
const workspacesByChatId = new Map<string, string>([
|
|
29
|
+
["parent-1", "ws-1"],
|
|
30
|
+
["child-1", "ws-1"],
|
|
31
|
+
["child-2", "ws-1"],
|
|
32
|
+
])
|
|
33
|
+
const lastTurnOutcomes = new Map<string, string>()
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
appendedMessages,
|
|
37
|
+
existingChats,
|
|
38
|
+
workspacesByChatId,
|
|
39
|
+
lastTurnOutcomes,
|
|
40
|
+
appendMessage(chatId, entry) {
|
|
41
|
+
appendedMessages.push({ chatId, entry })
|
|
42
|
+
overrides?.appendMessage?.(chatId, entry)
|
|
43
|
+
},
|
|
44
|
+
chatExists(chatId) {
|
|
45
|
+
return overrides?.chatExists?.(chatId) ?? existingChats.has(chatId)
|
|
46
|
+
},
|
|
47
|
+
getChatWorkspaceId(chatId) {
|
|
48
|
+
return overrides?.getChatWorkspaceId?.(chatId) ?? workspacesByChatId.get(chatId)
|
|
49
|
+
},
|
|
50
|
+
getLastTurnOutcome(chatId) {
|
|
51
|
+
return overrides?.getLastTurnOutcome?.(chatId) ?? lastTurnOutcomes.get(chatId)
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function baseArgs(overrides?: Partial<CreateDelegationArgs>): CreateDelegationArgs {
|
|
57
|
+
return {
|
|
58
|
+
workspaceId: "ws-1",
|
|
59
|
+
parentChatId: "parent-1",
|
|
60
|
+
childChatId: "child-1",
|
|
61
|
+
childProvider: "claude",
|
|
62
|
+
instructionPreview: "Do the thing",
|
|
63
|
+
mode: "blocking",
|
|
64
|
+
resume: "gate",
|
|
65
|
+
depth: 1,
|
|
66
|
+
...overrides,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function successOutcome(summary?: string): TerminalOutcome {
|
|
71
|
+
return { outcome: "success", resultSummary: summary ?? "All done" }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function failedOutcome(summary?: string): TerminalOutcome {
|
|
75
|
+
return { outcome: "failed", resultSummary: summary ?? "Something broke" }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function cancelledOutcome(): TerminalOutcome {
|
|
79
|
+
return { outcome: "cancelled" }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function makeUserEntry(content: string): UserPromptEntry {
|
|
83
|
+
return { _id: crypto.randomUUID(), createdAt: Date.now(), kind: "user_prompt", content }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function makeAssistantEntry(text: string): AssistantTextEntry {
|
|
87
|
+
return { _id: crypto.randomUUID(), createdAt: Date.now(), kind: "assistant_text", text }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Tests
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
describe("DelegationCoordinator", () => {
|
|
95
|
+
let natsServer: NatsServer | null = null
|
|
96
|
+
let nc: NatsConnection | null = null
|
|
97
|
+
let coordinator: DelegationCoordinator | null = null
|
|
98
|
+
let store: ReturnType<typeof createFakeDelegationStore> | null = null
|
|
99
|
+
let storeDir: string | null = null
|
|
100
|
+
|
|
101
|
+
async function setup(storeOverrides?: Partial<DelegationStore>) {
|
|
102
|
+
storeDir = await mkdtemp(join(tmpdir(), "nats-delegation-test-"))
|
|
103
|
+
natsServer = await NatsServer.start({ jetstream: true, storeDir })
|
|
104
|
+
nc = await connect({ servers: natsServer.url })
|
|
105
|
+
store = createFakeDelegationStore(storeOverrides)
|
|
106
|
+
coordinator = new DelegationCoordinator(nc, store)
|
|
107
|
+
await coordinator.initialize()
|
|
108
|
+
return { coordinator: coordinator!, store: store!, nc: nc! }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
afterEach(async () => {
|
|
112
|
+
coordinator = null
|
|
113
|
+
store = null
|
|
114
|
+
if (nc && !nc.isClosed()) await nc.drain()
|
|
115
|
+
nc = null
|
|
116
|
+
if (natsServer) await natsServer.stop()
|
|
117
|
+
natsServer = null
|
|
118
|
+
if (storeDir) {
|
|
119
|
+
await rm(storeDir, { recursive: true, force: true }).catch(() => {})
|
|
120
|
+
storeDir = null
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// -----------------------------------------------------------------------
|
|
125
|
+
// Initialization
|
|
126
|
+
// -----------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
test("creates KV bucket on init", async () => {
|
|
129
|
+
await setup()
|
|
130
|
+
const kvm = new Kvm(nc!)
|
|
131
|
+
const kv = await kvm.open("delegations")
|
|
132
|
+
const status = await kv.status()
|
|
133
|
+
expect(status.bucket).toBe("delegations")
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// -----------------------------------------------------------------------
|
|
137
|
+
// createDelegation
|
|
138
|
+
// -----------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
test("createDelegation persists record and returns delegationId", async () => {
|
|
141
|
+
const { coordinator } = await setup()
|
|
142
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
143
|
+
|
|
144
|
+
expect(typeof delegationId).toBe("string")
|
|
145
|
+
expect(delegationId.length).toBeGreaterThan(0)
|
|
146
|
+
|
|
147
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
148
|
+
expect(record).not.toBeNull()
|
|
149
|
+
expect(record!.status).toBe("active")
|
|
150
|
+
expect(record!.parentChatId).toBe("parent-1")
|
|
151
|
+
expect(record!.childChatId).toBe("child-1")
|
|
152
|
+
expect(record!.mode).toBe("blocking")
|
|
153
|
+
expect(record!.resume).toBe("gate")
|
|
154
|
+
expect(record!.depth).toBe(1)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test("createDelegation writes secondary index", async () => {
|
|
158
|
+
const { coordinator } = await setup()
|
|
159
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
160
|
+
|
|
161
|
+
const children = await coordinator.getDelegationsForChild("ws-1", "child-1")
|
|
162
|
+
expect(children).toHaveLength(1)
|
|
163
|
+
expect(children[0]!.delegationId).toBe(delegationId)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test("createDelegation stores resumeHint", async () => {
|
|
167
|
+
const { coordinator } = await setup()
|
|
168
|
+
const { delegationId } = await coordinator.createDelegation(
|
|
169
|
+
baseArgs({ resumeHint: "Context from parent" }),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
173
|
+
expect(record!.resumeHint).toBe("Context from parent")
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test("createDelegation truncates instructionPreview to 120 chars", async () => {
|
|
177
|
+
const { coordinator } = await setup()
|
|
178
|
+
const longInstruction = "A".repeat(200)
|
|
179
|
+
const { delegationId } = await coordinator.createDelegation(
|
|
180
|
+
baseArgs({ instructionPreview: longInstruction }),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
184
|
+
expect(record!.instructionPreview.length).toBe(120)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test("createDelegation rejects depth > 2", async () => {
|
|
188
|
+
const { coordinator } = await setup()
|
|
189
|
+
await expect(
|
|
190
|
+
coordinator.createDelegation(baseArgs({ depth: 3 })),
|
|
191
|
+
).rejects.toThrow(/depth 3 exceeds maximum 2/i)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// -----------------------------------------------------------------------
|
|
195
|
+
// getDelegation
|
|
196
|
+
// -----------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
test("getDelegation returns null for missing record", async () => {
|
|
199
|
+
const { coordinator } = await setup()
|
|
200
|
+
const result = await coordinator.getDelegation("ws-1", "nonexistent")
|
|
201
|
+
expect(result).toBeNull()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test("getDelegation returns stored record", async () => {
|
|
205
|
+
const { coordinator } = await setup()
|
|
206
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
207
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
208
|
+
expect(record).not.toBeNull()
|
|
209
|
+
expect(record!.delegationId).toBe(delegationId)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// -----------------------------------------------------------------------
|
|
213
|
+
// getDelegationsForChild
|
|
214
|
+
// -----------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
test("getDelegationsForChild returns all delegations for a child", async () => {
|
|
217
|
+
const { coordinator } = await setup()
|
|
218
|
+
await coordinator.createDelegation(baseArgs())
|
|
219
|
+
await coordinator.createDelegation(baseArgs({ parentChatId: "parent-1", mode: "background" }))
|
|
220
|
+
|
|
221
|
+
const children = await coordinator.getDelegationsForChild("ws-1", "child-1")
|
|
222
|
+
expect(children).toHaveLength(2)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test("getDelegationsForChild returns empty for unknown child", async () => {
|
|
226
|
+
const { coordinator } = await setup()
|
|
227
|
+
const children = await coordinator.getDelegationsForChild("ws-1", "no-such-child")
|
|
228
|
+
expect(children).toHaveLength(0)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// -----------------------------------------------------------------------
|
|
232
|
+
// getBlockingDelegationsForParent
|
|
233
|
+
// -----------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
test("getBlockingDelegationsForParent returns only active blocking delegations", async () => {
|
|
236
|
+
const { coordinator } = await setup()
|
|
237
|
+
// blocking active
|
|
238
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-1" }))
|
|
239
|
+
// background active — should NOT appear
|
|
240
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-2", mode: "background" }))
|
|
241
|
+
|
|
242
|
+
const blocking = await coordinator.getBlockingDelegationsForParent("ws-1", "parent-1")
|
|
243
|
+
expect(blocking).toHaveLength(1)
|
|
244
|
+
expect(blocking[0]!.mode).toBe("blocking")
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test("getBlockingDelegationsForParent excludes completed delegations", async () => {
|
|
248
|
+
const { coordinator } = await setup()
|
|
249
|
+
await coordinator.createDelegation(baseArgs())
|
|
250
|
+
// Complete it
|
|
251
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
252
|
+
|
|
253
|
+
const blocking = await coordinator.getBlockingDelegationsForParent("ws-1", "parent-1")
|
|
254
|
+
expect(blocking).toHaveLength(0)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// -----------------------------------------------------------------------
|
|
258
|
+
// reconcileChildTerminal
|
|
259
|
+
// -----------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
test("reconcileChildTerminal transitions active→completed", async () => {
|
|
262
|
+
const { coordinator } = await setup()
|
|
263
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
264
|
+
|
|
265
|
+
const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome("Done!"))
|
|
266
|
+
expect(result).not.toBeNull()
|
|
267
|
+
expect("alreadyReconciled" in result!).toBe(false)
|
|
268
|
+
|
|
269
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
270
|
+
expect(record!.status).toBe("completed")
|
|
271
|
+
expect(record!.resultSummary).toBe("Done!")
|
|
272
|
+
expect(record!.isError).toBe(false)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test("reconcileChildTerminal on failed child sets status failed + isError", async () => {
|
|
276
|
+
const { coordinator } = await setup()
|
|
277
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
278
|
+
|
|
279
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", failedOutcome("Boom"))
|
|
280
|
+
|
|
281
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
282
|
+
expect(record!.status).toBe("failed")
|
|
283
|
+
expect(record!.isError).toBe(true)
|
|
284
|
+
expect(record!.resultSummary).toBe("Boom")
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
test("reconcileChildTerminal on cancelled child sets status failed", async () => {
|
|
288
|
+
const { coordinator } = await setup()
|
|
289
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
290
|
+
|
|
291
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", cancelledOutcome())
|
|
292
|
+
|
|
293
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
294
|
+
expect(record!.status).toBe("failed")
|
|
295
|
+
expect(record!.isError).toBe(true)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test("duplicate terminal calls are no-ops (CAS idempotency)", async () => {
|
|
299
|
+
const { coordinator, store } = await setup()
|
|
300
|
+
await coordinator.createDelegation(baseArgs())
|
|
301
|
+
|
|
302
|
+
const first = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
303
|
+
const second = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
304
|
+
|
|
305
|
+
expect("alreadyReconciled" in first!).toBe(false)
|
|
306
|
+
expect(second).toEqual({ alreadyReconciled: true })
|
|
307
|
+
// appendMessage called only once
|
|
308
|
+
expect(store.appendedMessages).toHaveLength(1)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test("reconcileChildTerminal injects agent_result into parent transcript", async () => {
|
|
312
|
+
const { coordinator, store } = await setup()
|
|
313
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
314
|
+
|
|
315
|
+
const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome("All good"))
|
|
316
|
+
expect("injectedEntryId" in result!).toBe(true)
|
|
317
|
+
|
|
318
|
+
expect(store.appendedMessages).toHaveLength(1)
|
|
319
|
+
const { chatId, entry } = store.appendedMessages[0]!
|
|
320
|
+
expect(chatId).toBe("parent-1")
|
|
321
|
+
expect(entry.kind).toBe("agent_result")
|
|
322
|
+
const agentResult = entry as AgentResultEntry
|
|
323
|
+
expect(agentResult.delegationId).toBe(delegationId)
|
|
324
|
+
expect(agentResult.resultSummary).toBe("All good")
|
|
325
|
+
expect(agentResult.childChatId).toBe("child-1")
|
|
326
|
+
expect(agentResult.childProvider).toBe("claude")
|
|
327
|
+
expect(agentResult.isError).toBe(false)
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
test("reconcileChildTerminal returns null for unknown child", async () => {
|
|
331
|
+
const { coordinator } = await setup()
|
|
332
|
+
const result = await coordinator.reconcileChildTerminal("ws-1", "unknown-child", successOutcome())
|
|
333
|
+
expect(result).toBeNull()
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
// -----------------------------------------------------------------------
|
|
337
|
+
// Resume eligibility
|
|
338
|
+
// -----------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
test("gate: resumeEligible=false when one sibling still active", async () => {
|
|
341
|
+
const { coordinator } = await setup()
|
|
342
|
+
// Two blocking children under same parent
|
|
343
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-1" }))
|
|
344
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-2" }))
|
|
345
|
+
|
|
346
|
+
// Complete child-1 only
|
|
347
|
+
const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
348
|
+
expect("resumeEligible" in result!).toBe(true)
|
|
349
|
+
expect((result as { resumeEligible: boolean }).resumeEligible).toBe(false)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test("gate: resumeEligible=true when all children terminal", async () => {
|
|
353
|
+
const { coordinator } = await setup()
|
|
354
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-1" }))
|
|
355
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-2" }))
|
|
356
|
+
|
|
357
|
+
// Complete both
|
|
358
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
359
|
+
const result = await coordinator.reconcileChildTerminal("ws-1", "child-2", successOutcome())
|
|
360
|
+
expect((result as { resumeEligible: boolean }).resumeEligible).toBe(true)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
test("immediate: resumeEligible=true per-child", async () => {
|
|
364
|
+
const { coordinator } = await setup()
|
|
365
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-1", resume: "immediate" }))
|
|
366
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-2", resume: "immediate" }))
|
|
367
|
+
|
|
368
|
+
// Complete child-1 only — should already be eligible
|
|
369
|
+
const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
370
|
+
expect((result as { resumeEligible: boolean }).resumeEligible).toBe(true)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test("background: never resumeEligible", async () => {
|
|
374
|
+
const { coordinator } = await setup()
|
|
375
|
+
await coordinator.createDelegation(baseArgs({ mode: "background", childChatId: "child-1" }))
|
|
376
|
+
|
|
377
|
+
const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
378
|
+
expect((result as { resumeEligible: boolean }).resumeEligible).toBe(false)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// -----------------------------------------------------------------------
|
|
382
|
+
// generateResumeHint
|
|
383
|
+
// -----------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
test("generateResumeHint returns undefined for empty entries", async () => {
|
|
386
|
+
const { coordinator } = await setup()
|
|
387
|
+
const hint = coordinator.generateResumeHint([])
|
|
388
|
+
expect(hint).toBeUndefined()
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
test("generateResumeHint returns undefined for non-displayable entries", async () => {
|
|
392
|
+
const { coordinator } = await setup()
|
|
393
|
+
const entries: TranscriptEntry[] = [
|
|
394
|
+
{ _id: "1", createdAt: Date.now(), kind: "compact_boundary" },
|
|
395
|
+
]
|
|
396
|
+
const hint = coordinator.generateResumeHint(entries)
|
|
397
|
+
expect(hint).toBeUndefined()
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
test("generateResumeHint includes user and assistant entries", async () => {
|
|
401
|
+
const { coordinator } = await setup()
|
|
402
|
+
const entries: TranscriptEntry[] = [
|
|
403
|
+
makeUserEntry("Build the feature"),
|
|
404
|
+
makeAssistantEntry("I will build it now"),
|
|
405
|
+
]
|
|
406
|
+
|
|
407
|
+
const hint = coordinator.generateResumeHint(entries)
|
|
408
|
+
expect(hint).toBeDefined()
|
|
409
|
+
expect(hint).toContain("User: Build the feature")
|
|
410
|
+
expect(hint).toContain("Assistant: I will build it now")
|
|
411
|
+
expect(hint).toContain("Delegation context from parent chat:")
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
test("generateResumeHint truncates to MAX entries", async () => {
|
|
415
|
+
const { coordinator } = await setup()
|
|
416
|
+
// Generate 30 entries (MAX_RESUME_HINT_ENTRIES = 24)
|
|
417
|
+
const entries: TranscriptEntry[] = Array.from({ length: 30 }, (_, i) =>
|
|
418
|
+
makeUserEntry(`Message ${i}`),
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
const hint = coordinator.generateResumeHint(entries)!
|
|
422
|
+
expect(hint).toContain("Older transcript lines omitted:")
|
|
423
|
+
// Should only contain last 24 messages
|
|
424
|
+
expect(hint).toContain("Message 29")
|
|
425
|
+
expect(hint).toContain("Message 6")
|
|
426
|
+
expect(hint).not.toContain("Message 5")
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
test("generateResumeHint respects character budget", async () => {
|
|
430
|
+
const { coordinator } = await setup()
|
|
431
|
+
// Each line = ~500 chars, 24 entries = ~12000 chars → will hit MAX_RESUME_HINT_CHARS
|
|
432
|
+
const entries: TranscriptEntry[] = Array.from({ length: 24 }, (_, i) =>
|
|
433
|
+
makeUserEntry(`${"X".repeat(450)} msg-${i}`),
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
const hint = coordinator.generateResumeHint(entries)!
|
|
437
|
+
expect(hint.length).toBeLessThanOrEqual(12_000)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
// -----------------------------------------------------------------------
|
|
441
|
+
// bootReconciliation
|
|
442
|
+
// -----------------------------------------------------------------------
|
|
443
|
+
|
|
444
|
+
test("bootReconciliation: parent missing → orphaned", async () => {
|
|
445
|
+
const { coordinator, store } = await setup()
|
|
446
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
447
|
+
// Remove parent from known chats
|
|
448
|
+
store.existingChats.delete("parent-1")
|
|
449
|
+
|
|
450
|
+
await coordinator.bootReconciliation()
|
|
451
|
+
|
|
452
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
453
|
+
expect(record!.status).toBe("orphaned")
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
test("bootReconciliation: child missing → orphaned", async () => {
|
|
457
|
+
const { coordinator, store } = await setup()
|
|
458
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
459
|
+
store.existingChats.delete("child-1")
|
|
460
|
+
|
|
461
|
+
await coordinator.bootReconciliation()
|
|
462
|
+
|
|
463
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
464
|
+
expect(record!.status).toBe("orphaned")
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
test("bootReconciliation: child already terminal (success) → complete + inject", async () => {
|
|
468
|
+
const { coordinator, store } = await setup()
|
|
469
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
470
|
+
store.lastTurnOutcomes.set("child-1", "success")
|
|
471
|
+
|
|
472
|
+
await coordinator.bootReconciliation()
|
|
473
|
+
|
|
474
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
475
|
+
expect(record!.status).toBe("completed")
|
|
476
|
+
// Should inject agent_result into parent
|
|
477
|
+
expect(store.appendedMessages).toHaveLength(1)
|
|
478
|
+
expect(store.appendedMessages[0]!.chatId).toBe("parent-1")
|
|
479
|
+
expect(store.appendedMessages[0]!.entry.kind).toBe("agent_result")
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
test("bootReconciliation: child terminal (failed) → failed", async () => {
|
|
483
|
+
const { coordinator, store } = await setup()
|
|
484
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
485
|
+
store.lastTurnOutcomes.set("child-1", "failed")
|
|
486
|
+
|
|
487
|
+
await coordinator.bootReconciliation()
|
|
488
|
+
|
|
489
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
490
|
+
expect(record!.status).toBe("failed")
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
test("bootReconciliation: child still running → keep active", async () => {
|
|
494
|
+
const { coordinator } = await setup()
|
|
495
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
496
|
+
// No lastTurnOutcome set → child still active
|
|
497
|
+
|
|
498
|
+
await coordinator.bootReconciliation()
|
|
499
|
+
|
|
500
|
+
const record = await coordinator.getDelegation("ws-1", delegationId)
|
|
501
|
+
expect(record!.status).toBe("active")
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
test("bootReconciliation: old record → stale", async () => {
|
|
505
|
+
const { coordinator } = await setup()
|
|
506
|
+
// Create delegation and manually backdate it via KV
|
|
507
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
508
|
+
|
|
509
|
+
// Directly update createdAt to 25h ago via KV
|
|
510
|
+
const kvm = new Kvm(nc!)
|
|
511
|
+
const kv = await kvm.open("delegations")
|
|
512
|
+
const key = `delegation.ws-1.${delegationId}`
|
|
513
|
+
const entry = await kv.get(key)
|
|
514
|
+
const record = JSON.parse(new TextDecoder().decode(entry!.value))
|
|
515
|
+
record.createdAt = Date.now() - 25 * 60 * 60 * 1000
|
|
516
|
+
await kv.update(key, new TextEncoder().encode(JSON.stringify(record)), entry!.revision)
|
|
517
|
+
|
|
518
|
+
await coordinator.bootReconciliation()
|
|
519
|
+
|
|
520
|
+
const result = await coordinator.getDelegation("ws-1", delegationId)
|
|
521
|
+
expect(result!.status).toBe("stale")
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
test("bootReconciliation: rebuild missing secondary index", async () => {
|
|
525
|
+
const { coordinator } = await setup()
|
|
526
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
527
|
+
|
|
528
|
+
// Delete the secondary index key manually
|
|
529
|
+
const kvm = new Kvm(nc!)
|
|
530
|
+
const kv = await kvm.open("delegations")
|
|
531
|
+
const secKey = `delegation_by_child.ws-1.child-1.${delegationId}`
|
|
532
|
+
await kv.delete(secKey)
|
|
533
|
+
|
|
534
|
+
// Verify it's gone
|
|
535
|
+
const beforeChildren = await coordinator.getDelegationsForChild("ws-1", "child-1")
|
|
536
|
+
expect(beforeChildren).toHaveLength(0)
|
|
537
|
+
|
|
538
|
+
await coordinator.bootReconciliation()
|
|
539
|
+
|
|
540
|
+
// Should be rebuilt
|
|
541
|
+
const afterChildren = await coordinator.getDelegationsForChild("ws-1", "child-1")
|
|
542
|
+
expect(afterChildren).toHaveLength(1)
|
|
543
|
+
expect(afterChildren[0]!.delegationId).toBe(delegationId)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
test("bootReconciliation: stuck completing with entryId → completed", async () => {
|
|
547
|
+
const { coordinator } = await setup()
|
|
548
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
549
|
+
|
|
550
|
+
// Manually set to "completing" with agentResultEntryId via KV
|
|
551
|
+
const kvm = new Kvm(nc!)
|
|
552
|
+
const kv = await kvm.open("delegations")
|
|
553
|
+
const key = `delegation.ws-1.${delegationId}`
|
|
554
|
+
const entry = await kv.get(key)
|
|
555
|
+
const record = JSON.parse(new TextDecoder().decode(entry!.value))
|
|
556
|
+
record.status = "completing"
|
|
557
|
+
record.agentResultEntryId = "some-entry-id"
|
|
558
|
+
await kv.update(key, new TextEncoder().encode(JSON.stringify(record)), entry!.revision)
|
|
559
|
+
|
|
560
|
+
await coordinator.bootReconciliation()
|
|
561
|
+
|
|
562
|
+
const result = await coordinator.getDelegation("ws-1", delegationId)
|
|
563
|
+
expect(result!.status).toBe("completed")
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
test("bootReconciliation: stuck completing without entryId → failed", async () => {
|
|
567
|
+
const { coordinator } = await setup()
|
|
568
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
569
|
+
|
|
570
|
+
// Manually set to "completing" without agentResultEntryId
|
|
571
|
+
const kvm = new Kvm(nc!)
|
|
572
|
+
const kv = await kvm.open("delegations")
|
|
573
|
+
const key = `delegation.ws-1.${delegationId}`
|
|
574
|
+
const entry = await kv.get(key)
|
|
575
|
+
const record = JSON.parse(new TextDecoder().decode(entry!.value))
|
|
576
|
+
record.status = "completing"
|
|
577
|
+
await kv.update(key, new TextEncoder().encode(JSON.stringify(record)), entry!.revision)
|
|
578
|
+
|
|
579
|
+
await coordinator.bootReconciliation()
|
|
580
|
+
|
|
581
|
+
const result = await coordinator.getDelegation("ws-1", delegationId)
|
|
582
|
+
expect(result!.status).toBe("failed")
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
// -----------------------------------------------------------------------
|
|
586
|
+
// hasActiveBlockingDelegations (sync cache)
|
|
587
|
+
// -----------------------------------------------------------------------
|
|
588
|
+
|
|
589
|
+
test("hasActiveBlockingDelegations returns false with no delegations", async () => {
|
|
590
|
+
const { coordinator } = await setup()
|
|
591
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
test("hasActiveBlockingDelegations returns true after blocking delegation created", async () => {
|
|
595
|
+
const { coordinator } = await setup()
|
|
596
|
+
await coordinator.createDelegation(baseArgs({ mode: "blocking" }))
|
|
597
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
test("hasActiveBlockingDelegations returns false for background delegation", async () => {
|
|
601
|
+
const { coordinator } = await setup()
|
|
602
|
+
await coordinator.createDelegation(baseArgs({ mode: "background" }))
|
|
603
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
test("hasActiveBlockingDelegations returns false after delegation completes", async () => {
|
|
607
|
+
const { coordinator } = await setup()
|
|
608
|
+
await coordinator.createDelegation(baseArgs({ mode: "blocking" }))
|
|
609
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
|
|
610
|
+
|
|
611
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
612
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
test("hasActiveBlockingDelegations tracks multiple blocking delegations", async () => {
|
|
616
|
+
const { coordinator } = await setup()
|
|
617
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-1", mode: "blocking" }))
|
|
618
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-2", mode: "blocking" }))
|
|
619
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
|
|
620
|
+
|
|
621
|
+
// Complete one — still has active blocking
|
|
622
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
623
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
|
|
624
|
+
|
|
625
|
+
// Complete the other — now clear
|
|
626
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-2", successOutcome())
|
|
627
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
test("bootReconciliation rebuilds sync cache from KV", async () => {
|
|
631
|
+
const { coordinator } = await setup()
|
|
632
|
+
// Create two blocking delegations
|
|
633
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-1", mode: "blocking" }))
|
|
634
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-2", mode: "blocking" }))
|
|
635
|
+
expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
|
|
636
|
+
|
|
637
|
+
// Create a fresh coordinator sharing the same NATS + KV to simulate restart
|
|
638
|
+
const coordinator2 = new DelegationCoordinator(nc!, store!)
|
|
639
|
+
await coordinator2.initialize()
|
|
640
|
+
// Before boot reconciliation, cache is empty
|
|
641
|
+
expect(coordinator2.hasActiveBlockingDelegations("parent-1")).toBe(false)
|
|
642
|
+
|
|
643
|
+
await coordinator2.bootReconciliation()
|
|
644
|
+
// After boot, cache is rebuilt
|
|
645
|
+
expect(coordinator2.hasActiveBlockingDelegations("parent-1")).toBe(true)
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
test("bootReconciliation cache excludes completed delegations", async () => {
|
|
649
|
+
const { coordinator } = await setup()
|
|
650
|
+
await coordinator.createDelegation(baseArgs({ childChatId: "child-1", mode: "blocking" }))
|
|
651
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
652
|
+
|
|
653
|
+
// Fresh coordinator
|
|
654
|
+
const coordinator2 = new DelegationCoordinator(nc!, store!)
|
|
655
|
+
await coordinator2.initialize()
|
|
656
|
+
await coordinator2.bootReconciliation()
|
|
657
|
+
expect(coordinator2.hasActiveBlockingDelegations("parent-1")).toBe(false)
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
test("bootReconciliation skips already terminal records", async () => {
|
|
661
|
+
const { coordinator } = await setup()
|
|
662
|
+
const { delegationId } = await coordinator.createDelegation(baseArgs())
|
|
663
|
+
|
|
664
|
+
// Complete it first
|
|
665
|
+
await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
|
|
666
|
+
const before = await coordinator.getDelegation("ws-1", delegationId)
|
|
667
|
+
expect(before!.status).toBe("completed")
|
|
668
|
+
|
|
669
|
+
// Boot reconciliation should not touch it
|
|
670
|
+
await coordinator.bootReconciliation()
|
|
671
|
+
const after = await coordinator.getDelegation("ws-1", delegationId)
|
|
672
|
+
expect(after!.status).toBe("completed")
|
|
673
|
+
expect(after!.updatedAt).toBe(before!.updatedAt)
|
|
674
|
+
})
|
|
675
|
+
})
|