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,94 @@
|
|
|
1
|
+
import type { NatsConnection } from "@nats-io/transport-node"
|
|
2
|
+
import type { CoordinationStore } from "../shared/coordination-store"
|
|
3
|
+
import type { WorkspaceCoordinationSnapshot, TodoPriority } from "../shared/workspace-types"
|
|
4
|
+
import { commandSubject } from "../shared/nats-subjects"
|
|
5
|
+
import { decompressPayload } from "../shared/compression"
|
|
6
|
+
|
|
7
|
+
const encoder = new TextEncoder()
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* NATS-backed coordination store for the runner process.
|
|
11
|
+
* Delegates all mutations to the server via NATS request/reply,
|
|
12
|
+
* using the same command subjects that nats-responders.ts handles.
|
|
13
|
+
*
|
|
14
|
+
* State is a thin cache — the runner process has no in-process EventStore,
|
|
15
|
+
* so coordinationByWorkspace is always empty here. MCP read-backs go through
|
|
16
|
+
* the getSnapshot method which fetches from the server.
|
|
17
|
+
*/
|
|
18
|
+
export class NatsCoordinationClient implements CoordinationStore {
|
|
19
|
+
private readonly nc: NatsConnection
|
|
20
|
+
private readonly _state: CoordinationStore["state"] = {
|
|
21
|
+
coordinationByWorkspace: new Map(),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
constructor(nc: NatsConnection) {
|
|
25
|
+
this.nc = nc
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get state() {
|
|
29
|
+
return this._state
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private async sendCommand(type: string, payload: Record<string, unknown>): Promise<unknown> {
|
|
33
|
+
const subject = commandSubject(type)
|
|
34
|
+
const reply = await this.nc.request(
|
|
35
|
+
subject,
|
|
36
|
+
encoder.encode(JSON.stringify({ type, ...payload })),
|
|
37
|
+
{ timeout: 5_000 },
|
|
38
|
+
)
|
|
39
|
+
const decompressed = await decompressPayload(reply.data)
|
|
40
|
+
const response = JSON.parse(new TextDecoder().decode(decompressed)) as { ok: boolean; error?: string; result?: unknown }
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(response.error ?? `Coordination command ${type} failed`)
|
|
43
|
+
}
|
|
44
|
+
return response.result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async addTodo(workspaceId: string, todoId: string, description: string, priority: TodoPriority, createdBy: string): Promise<void> {
|
|
48
|
+
await this.sendCommand("workspace.todo.add", { workspaceId, todoId, description, priority, createdBy })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async claimTodo(workspaceId: string, todoId: string, sessionId: string): Promise<void> {
|
|
52
|
+
await this.sendCommand("workspace.todo.claim", { workspaceId, todoId, sessionId })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async completeTodo(workspaceId: string, todoId: string, outputs: string[]): Promise<void> {
|
|
56
|
+
await this.sendCommand("workspace.todo.complete", { workspaceId, todoId, outputs })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async abandonTodo(workspaceId: string, todoId: string): Promise<void> {
|
|
60
|
+
await this.sendCommand("workspace.todo.abandon", { workspaceId, todoId })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async createClaim(workspaceId: string, claimId: string, intent: string, files: string[], sessionId: string): Promise<void> {
|
|
64
|
+
await this.sendCommand("workspace.claim.create", { workspaceId, claimId, intent, files, sessionId })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async releaseClaim(workspaceId: string, claimId: string): Promise<void> {
|
|
68
|
+
await this.sendCommand("workspace.claim.release", { workspaceId, claimId })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async createWorktree(workspaceId: string, worktreeId: string, branch: string, baseBranch: string, _path: string): Promise<void> {
|
|
72
|
+
await this.sendCommand("workspace.worktree.create", { workspaceId, worktreeId, branch, baseBranch })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async assignWorktree(workspaceId: string, worktreeId: string, sessionId: string): Promise<void> {
|
|
76
|
+
await this.sendCommand("workspace.worktree.assign", { workspaceId, worktreeId, sessionId })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async removeWorktree(workspaceId: string, worktreeId: string): Promise<void> {
|
|
80
|
+
await this.sendCommand("workspace.worktree.remove", { workspaceId, worktreeId })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async setRule(workspaceId: string, ruleId: string, content: string, setBy: string): Promise<void> {
|
|
84
|
+
await this.sendCommand("workspace.rule.set", { workspaceId, ruleId, content, setBy })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async removeRule(workspaceId: string, ruleId: string): Promise<void> {
|
|
88
|
+
await this.sendCommand("workspace.rule.remove", { workspaceId, ruleId })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getSnapshot(workspaceId: string): Promise<WorkspaceCoordinationSnapshot> {
|
|
92
|
+
return this.sendCommand("workspace.coordination.snapshot", { workspaceId }) as Promise<WorkspaceCoordinationSnapshot>
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { describe, test, expect, afterEach, beforeEach } from "bun:test"
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs"
|
|
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 { jetstreamManager, RetentionPolicy, StorageType } from "@nats-io/jetstream"
|
|
8
|
+
import { RunnerAgent, type TurnFactory } from "./runner-agent"
|
|
9
|
+
import {
|
|
10
|
+
runnerEventsSubject,
|
|
11
|
+
RUNNER_EVENTS_STREAM,
|
|
12
|
+
ALL_RUNNER_EVENTS,
|
|
13
|
+
type RunnerTurnEvent,
|
|
14
|
+
type StartTurnCommand,
|
|
15
|
+
} from "../shared/runner-protocol"
|
|
16
|
+
import type { HarnessEvent, HarnessTurn } from "../shared/harness-types"
|
|
17
|
+
import type { TranscriptEntry } from "../shared/types"
|
|
18
|
+
|
|
19
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function ts<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(entry: T): TranscriptEntry {
|
|
22
|
+
return { _id: crypto.randomUUID(), createdAt: Date.now(), ...entry } as TranscriptEntry
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createMockTurn(events: HarnessEvent[]): HarnessTurn {
|
|
26
|
+
let interrupted = false
|
|
27
|
+
let closed = false
|
|
28
|
+
const stream = (async function* () {
|
|
29
|
+
for (const event of events) {
|
|
30
|
+
if (interrupted) return
|
|
31
|
+
yield event
|
|
32
|
+
}
|
|
33
|
+
})()
|
|
34
|
+
return {
|
|
35
|
+
provider: "claude",
|
|
36
|
+
stream,
|
|
37
|
+
interrupt: async () => { interrupted = true },
|
|
38
|
+
close: () => { closed = true },
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// startTurn now fails fast if the workspace dir doesn't exist on the runner, so the
|
|
43
|
+
// fixture must point at a real directory.
|
|
44
|
+
const TEST_WORKSPACE = mkdtempSync(join(tmpdir(), "runner-agent-ws-"))
|
|
45
|
+
|
|
46
|
+
function makeCmd(overrides?: Partial<StartTurnCommand>): StartTurnCommand {
|
|
47
|
+
return {
|
|
48
|
+
chatId: "chat-1",
|
|
49
|
+
provider: "claude",
|
|
50
|
+
content: "hello",
|
|
51
|
+
model: "test-model",
|
|
52
|
+
planMode: false,
|
|
53
|
+
appendUserPrompt: true,
|
|
54
|
+
workspaceLocalPath: TEST_WORKSPACE,
|
|
55
|
+
sessionToken: null,
|
|
56
|
+
chatTitle: "New Chat",
|
|
57
|
+
existingMessageCount: 0,
|
|
58
|
+
workspaceId: "p1",
|
|
59
|
+
...overrides,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Subscribe to runner events for a chat and collect into an array. */
|
|
64
|
+
function collectEvents(nc: NatsConnection, chatId: string): RunnerTurnEvent[] {
|
|
65
|
+
const events: RunnerTurnEvent[] = []
|
|
66
|
+
const sub = nc.subscribe(runnerEventsSubject(chatId))
|
|
67
|
+
void (async () => {
|
|
68
|
+
const decoder = new TextDecoder()
|
|
69
|
+
for await (const msg of sub) {
|
|
70
|
+
events.push(JSON.parse(decoder.decode(msg.data)) as RunnerTurnEvent)
|
|
71
|
+
}
|
|
72
|
+
})()
|
|
73
|
+
return events
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function ensureStream(nc: NatsConnection) {
|
|
77
|
+
const jsm = await jetstreamManager(nc)
|
|
78
|
+
await jsm.streams.add({
|
|
79
|
+
name: RUNNER_EVENTS_STREAM,
|
|
80
|
+
subjects: [ALL_RUNNER_EVENTS],
|
|
81
|
+
retention: RetentionPolicy.Limits,
|
|
82
|
+
storage: StorageType.File,
|
|
83
|
+
max_age: 5 * 60 * 1_000_000_000,
|
|
84
|
+
max_msgs: 10_000,
|
|
85
|
+
max_bytes: 64 * 1024 * 1024,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Tests ───────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
describe("RunnerAgent", () => {
|
|
92
|
+
let server: NatsServer
|
|
93
|
+
let nc: NatsConnection
|
|
94
|
+
let tmpDir: string | null = null
|
|
95
|
+
|
|
96
|
+
beforeEach(async () => {
|
|
97
|
+
tmpDir = mkdtempSync(join(tmpdir(), "runner-test-"))
|
|
98
|
+
server = await NatsServer.start({ jetstream: true, storeDir: tmpDir })
|
|
99
|
+
nc = await connect({ servers: server.url })
|
|
100
|
+
await ensureStream(nc)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
afterEach(async () => {
|
|
104
|
+
await nc?.drain()
|
|
105
|
+
await server?.stop()
|
|
106
|
+
if (tmpDir) {
|
|
107
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
108
|
+
tmpDir = null
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("startTurn publishes user_prompt, transcript, and turn_finished events", async () => {
|
|
113
|
+
const harnessEvents: HarnessEvent[] = [
|
|
114
|
+
{ type: "transcript", entry: ts({ kind: "system_init", provider: "claude", model: "test", tools: [], agents: [], slashCommands: [], mcpServers: [] }) },
|
|
115
|
+
{ type: "transcript", entry: ts({ kind: "assistant_text", text: "Hello world" }) },
|
|
116
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 100, result: "done" }) },
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
const turnFactory: TurnFactory = async () => createMockTurn(harnessEvents)
|
|
120
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
121
|
+
const collected = collectEvents(nc, "chat-1")
|
|
122
|
+
|
|
123
|
+
await agent.startTurn(makeCmd())
|
|
124
|
+
// Wait for async turn processing
|
|
125
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
126
|
+
|
|
127
|
+
const types = collected.map((e) => e.type)
|
|
128
|
+
|
|
129
|
+
// Must have user_prompt as a transcript event
|
|
130
|
+
const transcripts = collected.filter((e) => e.type === "transcript") as Array<RunnerTurnEvent & { type: "transcript" }>
|
|
131
|
+
const kinds = transcripts.map((e) => (e as any).entry?.kind)
|
|
132
|
+
expect(kinds).toContain("user_prompt")
|
|
133
|
+
expect(kinds).toContain("system_init")
|
|
134
|
+
expect(kinds).toContain("assistant_text")
|
|
135
|
+
expect(kinds).toContain("result")
|
|
136
|
+
|
|
137
|
+
// Must end with turn_finished
|
|
138
|
+
expect(types).toContain("turn_finished")
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test("startTurn fails fast (not a hang) when the workspace dir is missing on this runner", async () => {
|
|
142
|
+
let factoryCalled = false
|
|
143
|
+
const turnFactory: TurnFactory = async () => { factoryCalled = true; return createMockTurn([]) }
|
|
144
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
145
|
+
|
|
146
|
+
await expect(
|
|
147
|
+
agent.startTurn(makeCmd({ workspaceLocalPath: "/no/such/workspace-xyz-12345" })),
|
|
148
|
+
).rejects.toThrow(/does not exist on this runner/i)
|
|
149
|
+
// It must reject BEFORE invoking the turn factory (which would spawn the agent
|
|
150
|
+
// into a nonexistent cwd and hang — the original bug).
|
|
151
|
+
expect(factoryCalled).toBe(false)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test("startTurn without appendUserPrompt skips user_prompt", async () => {
|
|
155
|
+
const harnessEvents: HarnessEvent[] = [
|
|
156
|
+
{ type: "transcript", entry: ts({ kind: "assistant_text", text: "follow-up" }) },
|
|
157
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 50, result: "ok" }) },
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
const turnFactory: TurnFactory = async () => createMockTurn(harnessEvents)
|
|
161
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
162
|
+
const collected = collectEvents(nc, "chat-1")
|
|
163
|
+
|
|
164
|
+
await agent.startTurn(makeCmd({ appendUserPrompt: false }))
|
|
165
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
166
|
+
|
|
167
|
+
const transcripts = collected.filter((e) => e.type === "transcript") as any[]
|
|
168
|
+
const kinds = transcripts.map((e) => e.entry?.kind)
|
|
169
|
+
expect(kinds).not.toContain("user_prompt")
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test("delegatedContext is prepended for the harness without rewriting the visible user prompt", async () => {
|
|
173
|
+
let capturedContent = ""
|
|
174
|
+
const turnFactory: TurnFactory = async (args) => {
|
|
175
|
+
capturedContent = args.content
|
|
176
|
+
return createMockTurn([
|
|
177
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "ok" }) },
|
|
178
|
+
])
|
|
179
|
+
}
|
|
180
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
181
|
+
const collected = collectEvents(nc, "chat-1")
|
|
182
|
+
|
|
183
|
+
await agent.startTurn(makeCmd({
|
|
184
|
+
content: "Write the regression test",
|
|
185
|
+
delegatedContext: "Forked parent chat context:\nUser: Investigate the auth race condition",
|
|
186
|
+
isSpawned: true,
|
|
187
|
+
}))
|
|
188
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
189
|
+
|
|
190
|
+
expect(capturedContent).toBe(
|
|
191
|
+
"Forked parent chat context:\nUser: Investigate the auth race condition\n\nNew task:\nWrite the regression test",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
const transcripts = collected.filter((event) => event.type === "transcript") as Array<RunnerTurnEvent & { type: "transcript" }>
|
|
195
|
+
const userPrompt = transcripts.find((event) => event.entry.kind === "user_prompt")
|
|
196
|
+
expect(userPrompt?.entry.kind).toBe("user_prompt")
|
|
197
|
+
expect(userPrompt && "content" in userPrompt.entry ? userPrompt.entry.content : undefined).toBe("Write the regression test")
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test("session_token events are published", async () => {
|
|
201
|
+
const harnessEvents: HarnessEvent[] = [
|
|
202
|
+
{ type: "session_token", sessionToken: "session-abc" },
|
|
203
|
+
{ type: "transcript", entry: ts({ kind: "assistant_text", text: "hi" }) },
|
|
204
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "ok" }) },
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
const turnFactory: TurnFactory = async () => createMockTurn(harnessEvents)
|
|
208
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
209
|
+
const collected = collectEvents(nc, "chat-1")
|
|
210
|
+
|
|
211
|
+
await agent.startTurn(makeCmd())
|
|
212
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
213
|
+
|
|
214
|
+
const sessionTokenEvents = collected.filter((e) => e.type === "session_token")
|
|
215
|
+
expect(sessionTokenEvents).toHaveLength(1)
|
|
216
|
+
expect((sessionTokenEvents[0] as any).sessionToken).toBe("session-abc")
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test("cancel publishes interrupted + turn_cancelled", async () => {
|
|
220
|
+
// Turn that hangs forever (never yields result)
|
|
221
|
+
let interrupted = false
|
|
222
|
+
const turn: HarnessTurn = {
|
|
223
|
+
provider: "claude",
|
|
224
|
+
stream: (async function* () {
|
|
225
|
+
yield { type: "transcript" as const, entry: ts({ kind: "system_init", provider: "claude", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) }
|
|
226
|
+
// Block indefinitely until interrupted
|
|
227
|
+
await new Promise<void>((resolve) => {
|
|
228
|
+
const check = setInterval(() => {
|
|
229
|
+
if (interrupted) { clearInterval(check); resolve() }
|
|
230
|
+
}, 10)
|
|
231
|
+
})
|
|
232
|
+
})(),
|
|
233
|
+
interrupt: async () => { interrupted = true },
|
|
234
|
+
close: () => {},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const turnFactory: TurnFactory = async () => turn
|
|
238
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
239
|
+
const collected = collectEvents(nc, "chat-1")
|
|
240
|
+
|
|
241
|
+
await agent.startTurn(makeCmd())
|
|
242
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
243
|
+
|
|
244
|
+
await agent.cancel("chat-1")
|
|
245
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
246
|
+
|
|
247
|
+
const types = collected.map((e) => e.type)
|
|
248
|
+
expect(types).toContain("turn_cancelled")
|
|
249
|
+
|
|
250
|
+
// Check that interrupted entry was published as transcript
|
|
251
|
+
const transcripts = collected.filter((e) => e.type === "transcript") as any[]
|
|
252
|
+
const kinds = transcripts.map((e) => e.entry?.kind)
|
|
253
|
+
expect(kinds).toContain("interrupted")
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test("cancel returns promptly and suppresses late stream events after cancellation", async () => {
|
|
257
|
+
let releaseLateEvent: (() => void) | null = null
|
|
258
|
+
let closed = false
|
|
259
|
+
const turn: HarnessTurn = {
|
|
260
|
+
provider: "claude",
|
|
261
|
+
stream: (async function* () {
|
|
262
|
+
await new Promise<void>((resolve) => { releaseLateEvent = resolve })
|
|
263
|
+
yield { type: "transcript" as const, entry: ts({ kind: "system_init", provider: "claude", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) }
|
|
264
|
+
yield { type: "transcript" as const, entry: ts({ kind: "assistant_text", text: "late text" }) }
|
|
265
|
+
})(),
|
|
266
|
+
interrupt: async () => {
|
|
267
|
+
await new Promise<void>(() => {})
|
|
268
|
+
},
|
|
269
|
+
close: () => { closed = true },
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const turnFactory: TurnFactory = async () => turn
|
|
273
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
274
|
+
const collected = collectEvents(nc, "chat-1")
|
|
275
|
+
|
|
276
|
+
await agent.startTurn(makeCmd())
|
|
277
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
278
|
+
|
|
279
|
+
const cancelResult = await Promise.race([
|
|
280
|
+
agent.cancel("chat-1").then(() => "cancelled" as const),
|
|
281
|
+
new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 100)),
|
|
282
|
+
])
|
|
283
|
+
|
|
284
|
+
expect(cancelResult).toBe("cancelled")
|
|
285
|
+
releaseLateEvent?.()
|
|
286
|
+
await new Promise((r) => setTimeout(r, 150))
|
|
287
|
+
|
|
288
|
+
const cancelledIndex = collected.findIndex((event) => event.type === "turn_cancelled")
|
|
289
|
+
expect(cancelledIndex).toBeGreaterThanOrEqual(0)
|
|
290
|
+
const afterCancel = collected.slice(cancelledIndex + 1)
|
|
291
|
+
expect(afterCancel.some((event) => event.type === "status_change" && event.status === "running")).toBe(false)
|
|
292
|
+
expect(afterCancel.some((event) => event.type === "transcript" && event.entry?.kind === "assistant_text")).toBe(false)
|
|
293
|
+
expect(closed).toBe(true)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test("turn error publishes turn_failed", async () => {
|
|
297
|
+
const turn: HarnessTurn = {
|
|
298
|
+
provider: "claude",
|
|
299
|
+
stream: (async function* () {
|
|
300
|
+
yield { type: "transcript" as const, entry: ts({ kind: "system_init", provider: "claude", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) }
|
|
301
|
+
throw new Error("SDK crashed")
|
|
302
|
+
})(),
|
|
303
|
+
interrupt: async () => {},
|
|
304
|
+
close: () => {},
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const turnFactory: TurnFactory = async () => turn
|
|
308
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
309
|
+
const collected = collectEvents(nc, "chat-1")
|
|
310
|
+
|
|
311
|
+
await agent.startTurn(makeCmd())
|
|
312
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
313
|
+
|
|
314
|
+
const failed = collected.filter((e) => e.type === "turn_failed")
|
|
315
|
+
expect(failed).toHaveLength(1)
|
|
316
|
+
expect((failed[0] as any).error).toBe("SDK crashed")
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
test("status changes to running on system_init", async () => {
|
|
320
|
+
const harnessEvents: HarnessEvent[] = [
|
|
321
|
+
{ type: "transcript", entry: ts({ kind: "system_init", provider: "claude", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) },
|
|
322
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "ok" }) },
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
const turnFactory: TurnFactory = async () => createMockTurn(harnessEvents)
|
|
326
|
+
const agent = new RunnerAgent({ nc, createTurn: turnFactory })
|
|
327
|
+
const collected = collectEvents(nc, "chat-1")
|
|
328
|
+
|
|
329
|
+
await agent.startTurn(makeCmd())
|
|
330
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
331
|
+
|
|
332
|
+
const statusEvents = collected.filter((e) => e.type === "status_change") as any[]
|
|
333
|
+
const statuses = statusEvents.map((e) => e.status)
|
|
334
|
+
expect(statuses).toContain("starting")
|
|
335
|
+
expect(statuses).toContain("running")
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test("title generation publishes title_generated", async () => {
|
|
339
|
+
const harnessEvents: HarnessEvent[] = [
|
|
340
|
+
{ type: "transcript", entry: ts({ kind: "system_init", provider: "claude", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) },
|
|
341
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "ok" }) },
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
const turnFactory: TurnFactory = async () => createMockTurn(harnessEvents)
|
|
345
|
+
const agent = new RunnerAgent({
|
|
346
|
+
nc,
|
|
347
|
+
createTurn: turnFactory,
|
|
348
|
+
generateTitle: async () => "Test Title",
|
|
349
|
+
})
|
|
350
|
+
const collected = collectEvents(nc, "chat-1")
|
|
351
|
+
|
|
352
|
+
// New chat with no messages → should trigger title generation
|
|
353
|
+
await agent.startTurn(makeCmd({ chatTitle: "New Chat", existingMessageCount: 0, appendUserPrompt: true }))
|
|
354
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
355
|
+
|
|
356
|
+
const titleEvents = collected.filter((e) => e.type === "title_generated")
|
|
357
|
+
expect(titleEvents).toHaveLength(1)
|
|
358
|
+
expect((titleEvents[0] as any).title).toBe("Test Title")
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
test("getActiveStatuses reflects turn state", async () => {
|
|
362
|
+
// Turn that blocks
|
|
363
|
+
let unblock: (() => void) | null = null
|
|
364
|
+
const turn: HarnessTurn = {
|
|
365
|
+
provider: "claude",
|
|
366
|
+
stream: (async function* () {
|
|
367
|
+
yield { type: "transcript" as const, entry: ts({ kind: "system_init", provider: "claude", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) }
|
|
368
|
+
await new Promise<void>((r) => { unblock = r })
|
|
369
|
+
yield { type: "transcript" as const, entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "ok" }) }
|
|
370
|
+
})(),
|
|
371
|
+
interrupt: async () => { unblock?.() },
|
|
372
|
+
close: () => {},
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const agent = new RunnerAgent({ nc, createTurn: async () => turn })
|
|
376
|
+
|
|
377
|
+
await agent.startTurn(makeCmd())
|
|
378
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
379
|
+
|
|
380
|
+
const statuses = agent.getActiveStatuses()
|
|
381
|
+
expect(statuses.get("chat-1")).toBeDefined()
|
|
382
|
+
|
|
383
|
+
// Unblock and let it finish
|
|
384
|
+
unblock?.()
|
|
385
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
386
|
+
|
|
387
|
+
const finalStatuses = agent.getActiveStatuses()
|
|
388
|
+
expect(finalStatuses.has("chat-1")).toBe(false)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
test("follow-up turn runs before close() is called on the original turn", async () => {
|
|
392
|
+
let followUpStarted = false
|
|
393
|
+
let closeCalledBeforeFollowUp = false
|
|
394
|
+
let turnIndex = 0
|
|
395
|
+
|
|
396
|
+
const createTurn: TurnFactory = async (args) => {
|
|
397
|
+
turnIndex++
|
|
398
|
+
if (turnIndex === 1) {
|
|
399
|
+
// First turn: sends exit_plan_mode tool request via onToolRequest, then completes
|
|
400
|
+
return {
|
|
401
|
+
provider: "codex",
|
|
402
|
+
stream: (async function* () {
|
|
403
|
+
yield { type: "transcript" as const, entry: ts({ kind: "system_init", provider: "codex", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) }
|
|
404
|
+
// Trigger exit_plan_mode tool request through the harness callback
|
|
405
|
+
await args.onToolRequest({
|
|
406
|
+
tool: { toolId: "exit-plan", toolKind: "exit_plan_mode" as const, input: {} } as any,
|
|
407
|
+
})
|
|
408
|
+
yield { type: "transcript" as const, entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "ok" }) }
|
|
409
|
+
})(),
|
|
410
|
+
interrupt: async () => {},
|
|
411
|
+
close: () => {
|
|
412
|
+
if (!followUpStarted) {
|
|
413
|
+
closeCalledBeforeFollowUp = true
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Follow-up turn (turnIndex === 2)
|
|
419
|
+
followUpStarted = true
|
|
420
|
+
return createMockTurn([
|
|
421
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "follow-up done" }) },
|
|
422
|
+
])
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const agent = new RunnerAgent({ nc, createTurn })
|
|
426
|
+
await agent.startTurn(makeCmd({ provider: "codex", planMode: true }))
|
|
427
|
+
await new Promise((r) => setTimeout(r, 200))
|
|
428
|
+
|
|
429
|
+
// Respond to the exit_plan_mode tool with confirmed=true to trigger follow-up
|
|
430
|
+
await agent.respondTool("chat-1", "exit-plan", { confirmed: true })
|
|
431
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
432
|
+
|
|
433
|
+
// With current code (close before follow-up), closeCalledBeforeFollowUp is true.
|
|
434
|
+
// After the fix, it should be false.
|
|
435
|
+
expect(closeCalledBeforeFollowUp).toBe(false)
|
|
436
|
+
expect(followUpStarted).toBe(true)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test("passes coordinationStore to createTurn when provided", async () => {
|
|
440
|
+
const mockStore = {
|
|
441
|
+
state: { coordinationByWorkspace: new Map() },
|
|
442
|
+
addTodo: async () => {},
|
|
443
|
+
claimTodo: async () => {},
|
|
444
|
+
completeTodo: async () => {},
|
|
445
|
+
abandonTodo: async () => {},
|
|
446
|
+
createClaim: async () => {},
|
|
447
|
+
releaseClaim: async () => {},
|
|
448
|
+
createWorktree: async () => {},
|
|
449
|
+
assignWorktree: async () => {},
|
|
450
|
+
removeWorktree: async () => {},
|
|
451
|
+
setRule: async () => {},
|
|
452
|
+
removeRule: async () => {},
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
let capturedStore: unknown
|
|
456
|
+
const createTurn: TurnFactory = async (args) => {
|
|
457
|
+
capturedStore = args.store
|
|
458
|
+
return createMockTurn([
|
|
459
|
+
{ type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "done" }) },
|
|
460
|
+
])
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const agent = new RunnerAgent({ nc, createTurn, coordinationStore: mockStore })
|
|
464
|
+
await agent.startTurn(makeCmd())
|
|
465
|
+
await new Promise((r) => setTimeout(r, 200))
|
|
466
|
+
|
|
467
|
+
expect(capturedStore).toBe(mockStore)
|
|
468
|
+
})
|
|
469
|
+
})
|