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,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR3 Stage 2 integration tests — heartbeat-TTL liveness states.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* 1. /health exposes state/protocolVersion/incompatible (boot a real server,
|
|
6
|
+
* assert the shape once a runner is healthy).
|
|
7
|
+
* 2. Discover path: KV entry with stale lastSeenAt → not adoptable; fresh → adoptable.
|
|
8
|
+
* 3. getReadiness() derives state from runnerLivenessState (unit, no NATS required).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
12
|
+
import { mkdtempSync, rmSync } from "node:fs"
|
|
13
|
+
import { join } from "node:path"
|
|
14
|
+
import { tmpdir } from "node:os"
|
|
15
|
+
import { NatsServer } from "@lagz0ne/nats-embedded"
|
|
16
|
+
import { connect, type NatsConnection } from "@nats-io/transport-node"
|
|
17
|
+
import { Kvm } from "@nats-io/kv"
|
|
18
|
+
import { startServer } from "./server"
|
|
19
|
+
import { RunnerManager } from "./runner-manager"
|
|
20
|
+
import { ensureRunnerEventsStream, ensureRunnerRegistryBucket } from "./nats-streams"
|
|
21
|
+
import {
|
|
22
|
+
RUNNER_REGISTRY_BUCKET,
|
|
23
|
+
LIVENESS_OFFLINE_MS,
|
|
24
|
+
type RunnerRegistration,
|
|
25
|
+
} from "../shared/runner-protocol"
|
|
26
|
+
|
|
27
|
+
const encoder = new TextEncoder()
|
|
28
|
+
|
|
29
|
+
// ── Unit: getReadiness derives liveness state from lastHeartbeatAt ────────────
|
|
30
|
+
|
|
31
|
+
describe("RunnerManager.getReadiness — liveness state derivation", () => {
|
|
32
|
+
let server: NatsServer
|
|
33
|
+
let nc: NatsConnection
|
|
34
|
+
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
server = await NatsServer.start({ jetstream: true })
|
|
37
|
+
nc = await connect({ servers: server.url })
|
|
38
|
+
await ensureRunnerEventsStream(nc)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
afterEach(async () => {
|
|
42
|
+
await nc?.drain()
|
|
43
|
+
await server?.stop()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("state is 'offline' when no heartbeat has been received", () => {
|
|
47
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url })
|
|
48
|
+
const r = mgr.getReadiness()
|
|
49
|
+
expect(r.state).toBe("offline")
|
|
50
|
+
expect(r.heartbeatFresh).toBe(false)
|
|
51
|
+
expect(r.ok).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("state is 'online' when heartbeat was just received (simulated via injected clock)", async () => {
|
|
55
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url })
|
|
56
|
+
// Access private field to inject a heartbeat timestamp directly.
|
|
57
|
+
const now = Date.now()
|
|
58
|
+
;(mgr as unknown as { lastHeartbeatAt: number }).lastHeartbeatAt = now
|
|
59
|
+
const r = mgr.getReadiness(now)
|
|
60
|
+
expect(r.state).toBe("online")
|
|
61
|
+
expect(r.heartbeatFresh).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("state is 'degraded' when heartbeat age is between 25s and 60s", () => {
|
|
65
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url })
|
|
66
|
+
const now = Date.now()
|
|
67
|
+
;(mgr as unknown as { lastHeartbeatAt: number }).lastHeartbeatAt = now - 30_000
|
|
68
|
+
const r = mgr.getReadiness(now)
|
|
69
|
+
expect(r.state).toBe("degraded")
|
|
70
|
+
expect(r.heartbeatFresh).toBe(false) // heartbeatFresh === (state === "online")
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("state is 'offline' when heartbeat is older than LIVENESS_OFFLINE_MS", () => {
|
|
74
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url })
|
|
75
|
+
const now = Date.now()
|
|
76
|
+
;(mgr as unknown as { lastHeartbeatAt: number }).lastHeartbeatAt = now - LIVENESS_OFFLINE_MS
|
|
77
|
+
const r = mgr.getReadiness(now)
|
|
78
|
+
expect(r.state).toBe("offline")
|
|
79
|
+
expect(r.heartbeatFresh).toBe(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("heartbeatFresh === (state === 'online') invariant holds for all states", () => {
|
|
83
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url })
|
|
84
|
+
const now = Date.now()
|
|
85
|
+
|
|
86
|
+
for (const [age, expectedState] of [
|
|
87
|
+
[0, "online"],
|
|
88
|
+
[30_000, "degraded"],
|
|
89
|
+
[LIVENESS_OFFLINE_MS, "offline"],
|
|
90
|
+
[null, "offline"],
|
|
91
|
+
] as [number | null, string][]) {
|
|
92
|
+
;(mgr as unknown as { lastHeartbeatAt: number | null }).lastHeartbeatAt =
|
|
93
|
+
age === null ? null : now - age
|
|
94
|
+
const r = mgr.getReadiness(now)
|
|
95
|
+
expect(r.state as string).toBe(expectedState)
|
|
96
|
+
expect(r.heartbeatFresh).toBe(r.state === "online")
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// ── Integration: discover path uses lastSeenAt TTL, not process.kill ─────────
|
|
102
|
+
|
|
103
|
+
describe("discover path — lastSeenAt TTL liveness", () => {
|
|
104
|
+
let server: NatsServer
|
|
105
|
+
let nc: NatsConnection
|
|
106
|
+
let tmpDir: string
|
|
107
|
+
|
|
108
|
+
beforeEach(async () => {
|
|
109
|
+
tmpDir = mkdtempSync(join(tmpdir(), "pr3-discover-"))
|
|
110
|
+
server = await NatsServer.start({ jetstream: true, storeDir: tmpDir })
|
|
111
|
+
nc = await connect({ servers: server.url })
|
|
112
|
+
await ensureRunnerEventsStream(nc)
|
|
113
|
+
await ensureRunnerRegistryBucket(nc)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
afterEach(async () => {
|
|
117
|
+
await nc?.drain()
|
|
118
|
+
await server?.stop()
|
|
119
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test("stale lastSeenAt (>60s ago) → discover treats entry as offline, not adoptable", async () => {
|
|
123
|
+
// Write a KV entry whose lastSeenAt is well past the offline threshold.
|
|
124
|
+
const kvm = new Kvm(nc)
|
|
125
|
+
const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
|
|
126
|
+
const staleReg: RunnerRegistration = {
|
|
127
|
+
runnerId: "stale-runner",
|
|
128
|
+
pid: 99999,
|
|
129
|
+
startedAt: Date.now() - 120_000,
|
|
130
|
+
providers: ["claude", "codex"],
|
|
131
|
+
protocolVersion: 1,
|
|
132
|
+
lastSeenAt: Date.now() - (LIVENESS_OFFLINE_MS + 5_000), // 65s ago — offline
|
|
133
|
+
}
|
|
134
|
+
await kvStore.put("stale-runner", encoder.encode(JSON.stringify(staleReg)))
|
|
135
|
+
|
|
136
|
+
// Discover mode should poll, find stale-runner, skip it (offline), and
|
|
137
|
+
// eventually time out (no live runner). We cap the timeout to 1s for speed.
|
|
138
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url, mode: "discover" })
|
|
139
|
+
await expect(
|
|
140
|
+
// Override the 15s deadline: wrap in a 1.5s race so the test completes fast.
|
|
141
|
+
Promise.race([
|
|
142
|
+
mgr.ensureRunner(),
|
|
143
|
+
new Promise<never>((_, reject) =>
|
|
144
|
+
setTimeout(() => reject(new Error("no live runner — stale skipped")), 1_500)
|
|
145
|
+
),
|
|
146
|
+
])
|
|
147
|
+
).rejects.toThrow(/no live runner|No external runner/)
|
|
148
|
+
}, 10_000)
|
|
149
|
+
|
|
150
|
+
test("fresh lastSeenAt (just now) → discover adopts the entry", async () => {
|
|
151
|
+
// Write a KV entry with a fresh lastSeenAt so discover considers it live.
|
|
152
|
+
const kvm = new Kvm(nc)
|
|
153
|
+
const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
|
|
154
|
+
const freshRunnerId = "fresh-runner"
|
|
155
|
+
const freshReg: RunnerRegistration = {
|
|
156
|
+
runnerId: freshRunnerId,
|
|
157
|
+
pid: process.pid, // real pid so adopt doesn't crash on heartbeat wait
|
|
158
|
+
startedAt: Date.now(),
|
|
159
|
+
providers: ["claude", "codex"],
|
|
160
|
+
protocolVersion: 1,
|
|
161
|
+
lastSeenAt: Date.now(), // fresh
|
|
162
|
+
}
|
|
163
|
+
await kvStore.put(freshRunnerId, encoder.encode(JSON.stringify(freshReg)))
|
|
164
|
+
|
|
165
|
+
// Discover picks the runner as adoptable. It then subscribes to heartbeats
|
|
166
|
+
// and waits 5s for one — which won't arrive (no actual runner process here).
|
|
167
|
+
// That's expected: what we're testing is that the *entry is picked at all*.
|
|
168
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url, mode: "discover" })
|
|
169
|
+
await expect(
|
|
170
|
+
Promise.race([
|
|
171
|
+
mgr.ensureRunner(),
|
|
172
|
+
new Promise<never>((_, reject) =>
|
|
173
|
+
setTimeout(() => reject(new Error("heartbeat timeout — runner adopted but no process")), 6_000)
|
|
174
|
+
),
|
|
175
|
+
])
|
|
176
|
+
).rejects.toThrow(/heartbeat|no process/)
|
|
177
|
+
// The key assertion: runnerId was set (entry was adopted before the heartbeat wait)
|
|
178
|
+
expect((mgr as unknown as { runnerId: string | null }).runnerId).toBe(freshRunnerId)
|
|
179
|
+
}, 15_000)
|
|
180
|
+
|
|
181
|
+
test("incompatible lastSeenAt-fresh entry → discover skips it", async () => {
|
|
182
|
+
const kvm = new Kvm(nc)
|
|
183
|
+
const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
|
|
184
|
+
const incompatReg: RunnerRegistration = {
|
|
185
|
+
runnerId: "incompat-runner",
|
|
186
|
+
pid: 88888,
|
|
187
|
+
startedAt: Date.now(),
|
|
188
|
+
providers: ["claude"],
|
|
189
|
+
protocolVersion: 999, // outside SUPPORTED_RANGE
|
|
190
|
+
lastSeenAt: Date.now(), // fresh — but incompatible
|
|
191
|
+
}
|
|
192
|
+
await kvStore.put("incompat-runner", encoder.encode(JSON.stringify(incompatReg)))
|
|
193
|
+
|
|
194
|
+
const mgr = new RunnerManager({ nc, natsUrl: server.url, mode: "discover" })
|
|
195
|
+
await expect(
|
|
196
|
+
Promise.race([
|
|
197
|
+
mgr.ensureRunner(),
|
|
198
|
+
new Promise<never>((_, reject) =>
|
|
199
|
+
setTimeout(() => reject(new Error("incompatible runner skipped")), 1_500)
|
|
200
|
+
),
|
|
201
|
+
])
|
|
202
|
+
).rejects.toThrow(/incompatible|No external runner/)
|
|
203
|
+
}, 10_000)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// ── Integration: /health exposes state/protocolVersion/incompatible ───────────
|
|
207
|
+
|
|
208
|
+
describe("/health runner shape (integration)", () => {
|
|
209
|
+
let started: Awaited<ReturnType<typeof startServer>> | null = null
|
|
210
|
+
|
|
211
|
+
afterEach(async () => {
|
|
212
|
+
await started?.stop()
|
|
213
|
+
started = null
|
|
214
|
+
delete process.env.NATS_DATA_DIR
|
|
215
|
+
delete process.env.RUNNER_PROTOCOL_VERSION
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test("/health runner object includes state, protocolVersion, incompatible", async () => {
|
|
219
|
+
const natsDataDir = mkdtempSync(join(tmpdir(), "pr3-health-"))
|
|
220
|
+
process.env.NATS_DATA_DIR = natsDataDir
|
|
221
|
+
try {
|
|
222
|
+
started = await startServer({ port: 4371, host: "127.0.0.1", strictPort: true })
|
|
223
|
+
const port = started.port
|
|
224
|
+
|
|
225
|
+
// Wait up to 15s for the runner to become healthy.
|
|
226
|
+
let health: Record<string, unknown> | null = null
|
|
227
|
+
const deadline = Date.now() + 15_000
|
|
228
|
+
while (Date.now() < deadline) {
|
|
229
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`)
|
|
230
|
+
const body = await res.json() as { ok: boolean; runner: Record<string, unknown> }
|
|
231
|
+
if (body.ok) {
|
|
232
|
+
health = body.runner
|
|
233
|
+
break
|
|
234
|
+
}
|
|
235
|
+
await new Promise((r) => setTimeout(r, 200))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
expect(health).not.toBeNull()
|
|
239
|
+
// state must be present and one of the liveness values
|
|
240
|
+
expect(["online", "degraded", "offline"]).toContain(health!.state as string)
|
|
241
|
+
expect(health!.state).toBe("online") // healthy runner → online
|
|
242
|
+
// heartbeatFresh back-compat: must equal (state === "online")
|
|
243
|
+
expect(health!.heartbeatFresh).toBe(health!.state === "online")
|
|
244
|
+
// version fields
|
|
245
|
+
expect(typeof health!.protocolVersion).toBe("number")
|
|
246
|
+
expect(health!.protocolVersion).toBe(1)
|
|
247
|
+
expect(health!.incompatible).toBe(false)
|
|
248
|
+
} finally {
|
|
249
|
+
rmSync(natsDataDir, { recursive: true, force: true })
|
|
250
|
+
}
|
|
251
|
+
}, 30_000)
|
|
252
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process"
|
|
2
|
+
|
|
3
|
+
export function spawnDetached(command: string, args: string[]) {
|
|
4
|
+
spawn(command, args, { stdio: "ignore", detached: true }).unref()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function hasCommand(command: string) {
|
|
8
|
+
const result = spawnSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" })
|
|
9
|
+
return result.status === 0
|
|
10
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/server/project-cli.ts
|
|
2
|
+
|
|
3
|
+
export interface CliCommand {
|
|
4
|
+
command: string
|
|
5
|
+
args: Record<string, string | undefined>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function parseProjectCliArgs(argv: string[]): CliCommand {
|
|
9
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
10
|
+
return { command: "help", args: {} }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const flags: Record<string, string> = {}
|
|
14
|
+
const positional: string[] = []
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17
|
+
if (argv[i].startsWith("--") && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
|
|
18
|
+
flags[argv[i].slice(2)] = argv[i + 1]
|
|
19
|
+
i++
|
|
20
|
+
} else if (!argv[i].startsWith("--")) {
|
|
21
|
+
positional.push(argv[i])
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const cmd = positional[0]
|
|
26
|
+
const rest = positional.slice(1)
|
|
27
|
+
// Normalize flag names to match API expectations
|
|
28
|
+
const normalized: Record<string, string> = {}
|
|
29
|
+
for (const [key, value] of Object.entries(flags)) {
|
|
30
|
+
if (key === "project") normalized["workspaceId"] = value
|
|
31
|
+
else normalized[key] = value
|
|
32
|
+
}
|
|
33
|
+
const args: Record<string, string | undefined> = { ...normalized }
|
|
34
|
+
|
|
35
|
+
switch (cmd) {
|
|
36
|
+
case "sessions":
|
|
37
|
+
if (rest.length > 0) return { command: "session-detail", args: { ...args, chatId: rest[0] } }
|
|
38
|
+
return { command: "sessions", args }
|
|
39
|
+
case "search":
|
|
40
|
+
return { command: "search", args: { ...args, query: rest.join(" ") } }
|
|
41
|
+
case "tasks":
|
|
42
|
+
if (rest.length > 0) return { command: "task-detail", args: { ...args, taskId: rest[0] } }
|
|
43
|
+
return { command: "tasks", args }
|
|
44
|
+
case "claim":
|
|
45
|
+
return { command: "claim", args: { ...args, description: rest.join(" ") } }
|
|
46
|
+
case "complete":
|
|
47
|
+
return { command: "complete", args: { ...args, taskId: rest[0] } }
|
|
48
|
+
case "delegate":
|
|
49
|
+
return { command: "delegate", args: { ...args, request: rest.join(" ") } }
|
|
50
|
+
default:
|
|
51
|
+
return { command: "help", args: {} }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function formatOutput(command: string, data: unknown, json: boolean): string {
|
|
56
|
+
if (json) return JSON.stringify(data, null, 2)
|
|
57
|
+
|
|
58
|
+
if (command === "sessions" && Array.isArray(data)) {
|
|
59
|
+
if (data.length === 0) return "No sessions found."
|
|
60
|
+
const rows = data.map((s: Record<string, unknown>) =>
|
|
61
|
+
`${s.chatId} ${String(s.status).padEnd(8)} ${String(s.provider).padEnd(6)} ${String(s.intent ?? "").slice(0, 50)}`
|
|
62
|
+
)
|
|
63
|
+
return ["CHAT_ID STATUS PROVIDER INTENT", ...rows].join("\n")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (command === "tasks" && Array.isArray(data)) {
|
|
67
|
+
if (data.length === 0) return "No tasks tracked."
|
|
68
|
+
const rows = data.map((t: Record<string, unknown>) =>
|
|
69
|
+
`${t.id} ${String(t.status).padEnd(12)} ${t.claimedBy ?? t.createdBy ?? "-"} ${String(t.description).slice(0, 50)}`
|
|
70
|
+
)
|
|
71
|
+
return ["ID STATUS CLAIMED_BY DESCRIPTION", ...rows].join("\n")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return JSON.stringify(data, null, 2)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getHelpText(): string {
|
|
78
|
+
return `tinkaria-project — Cross-session project agent CLI
|
|
79
|
+
|
|
80
|
+
Commands:
|
|
81
|
+
sessions List active/recent sessions with summaries
|
|
82
|
+
sessions <chat-id> Detailed summary of a specific session
|
|
83
|
+
search <query> Lexical search over project transcripts
|
|
84
|
+
tasks List all project tasks
|
|
85
|
+
tasks <task-id> Get task details
|
|
86
|
+
claim <description> Claim a new task for the current session
|
|
87
|
+
complete <task-id> Mark a task as complete
|
|
88
|
+
delegate <request> Submit a delegation request to the project agent
|
|
89
|
+
|
|
90
|
+
Flags:
|
|
91
|
+
--json Output as JSON (default when stdout is not a TTY)
|
|
92
|
+
--project <id> Target project (default: current)
|
|
93
|
+
--session <chat-id> Identify calling session (for claim/complete)
|
|
94
|
+
--port <port> Tinkaria server port (default: 3210)
|
|
95
|
+
--version CLI version
|
|
96
|
+
--help Show this help`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function executeCommand(
|
|
100
|
+
parsed: CliCommand,
|
|
101
|
+
baseUrl: string,
|
|
102
|
+
): Promise<{ output: string; exitCode: number }> {
|
|
103
|
+
const args = parsed.args
|
|
104
|
+
const json = "json" in args || !process.stdout.isTTY
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
let data: unknown
|
|
108
|
+
switch (parsed.command) {
|
|
109
|
+
case "help":
|
|
110
|
+
return { output: getHelpText(), exitCode: 0 }
|
|
111
|
+
case "sessions": {
|
|
112
|
+
const workspaceId = args.workspaceId ?? ""
|
|
113
|
+
const res = await fetch(`${baseUrl}/api/workspace/sessions?workspaceId=${encodeURIComponent(workspaceId)}`)
|
|
114
|
+
data = await res.json()
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
case "session-detail": {
|
|
118
|
+
const res = await fetch(`${baseUrl}/api/workspace/sessions/${args.chatId}`)
|
|
119
|
+
if (res.status === 404) return { output: formatOutput("error", { error: "Session not found" }, json), exitCode: 1 }
|
|
120
|
+
data = await res.json()
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
case "search": {
|
|
124
|
+
const res = await fetch(`${baseUrl}/api/workspace/search`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "Content-Type": "application/json" },
|
|
127
|
+
body: JSON.stringify({ query: args.query, limit: 10 }),
|
|
128
|
+
})
|
|
129
|
+
data = await res.json()
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
case "tasks": {
|
|
133
|
+
const res = await fetch(`${baseUrl}/api/workspace/tasks`)
|
|
134
|
+
data = await res.json()
|
|
135
|
+
break
|
|
136
|
+
}
|
|
137
|
+
case "task-detail": {
|
|
138
|
+
const res = await fetch(`${baseUrl}/api/workspace/tasks/${args.taskId}`)
|
|
139
|
+
if (res.status === 404) return { output: formatOutput("error", { error: "Task not found" }, json), exitCode: 1 }
|
|
140
|
+
data = await res.json()
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
case "claim": {
|
|
144
|
+
const res = await fetch(`${baseUrl}/api/workspace/claim`, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: { "Content-Type": "application/json" },
|
|
147
|
+
body: JSON.stringify({ description: args.description, session: args.session, branch: args.branch ?? null }),
|
|
148
|
+
})
|
|
149
|
+
if (res.status === 400) return { output: formatOutput("error", await res.json(), json), exitCode: 1 }
|
|
150
|
+
data = await res.json()
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
case "complete": {
|
|
154
|
+
const res = await fetch(`${baseUrl}/api/workspace/complete`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify({ taskId: args.taskId, outputs: [] }),
|
|
158
|
+
})
|
|
159
|
+
if (res.status === 404) return { output: formatOutput("error", { error: "Task not found" }, json), exitCode: 1 }
|
|
160
|
+
data = await res.json()
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
case "delegate": {
|
|
164
|
+
const res = await fetch(`${baseUrl}/api/workspace/delegate`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "Content-Type": "application/json" },
|
|
167
|
+
body: JSON.stringify({ request: args.request, workspaceId: args.workspaceId ?? "" }),
|
|
168
|
+
})
|
|
169
|
+
data = await res.json()
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
return { output: getHelpText(), exitCode: 1 }
|
|
174
|
+
}
|
|
175
|
+
return { output: formatOutput(parsed.command, data, json), exitCode: 0 }
|
|
176
|
+
} catch (err) {
|
|
177
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
178
|
+
return { output: JSON.stringify({ error: message, code: 2 }), exitCode: 2 }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import {
|
|
3
|
+
codexServiceTierFromModelOptions,
|
|
4
|
+
normalizeClaudeModelOptions,
|
|
5
|
+
normalizeCodexModelOptions,
|
|
6
|
+
normalizeServerModel,
|
|
7
|
+
deriveServerProviderCatalog,
|
|
8
|
+
SERVER_PROVIDERS,
|
|
9
|
+
} from "./provider-catalog"
|
|
10
|
+
import { resolveClaudeApiModelId } from "../shared/types"
|
|
11
|
+
import type { RuntimeCapabilities, DiscoveredModel } from "../shared/runtime-types"
|
|
12
|
+
|
|
13
|
+
describe("provider catalog normalization", () => {
|
|
14
|
+
test("maps legacy Claude effort into shared model options", () => {
|
|
15
|
+
expect(normalizeClaudeModelOptions("opus", undefined, "max")).toEqual({
|
|
16
|
+
reasoningEffort: "max",
|
|
17
|
+
contextWindow: "200k",
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("normalizes Claude context window only for supported models", () => {
|
|
22
|
+
expect(normalizeClaudeModelOptions("sonnet", {
|
|
23
|
+
claude: {
|
|
24
|
+
reasoningEffort: "medium",
|
|
25
|
+
contextWindow: "1m",
|
|
26
|
+
},
|
|
27
|
+
})).toEqual({
|
|
28
|
+
reasoningEffort: "medium",
|
|
29
|
+
contextWindow: "1m",
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expect(normalizeClaudeModelOptions("haiku", {
|
|
33
|
+
claude: {
|
|
34
|
+
reasoningEffort: "medium",
|
|
35
|
+
contextWindow: "1m",
|
|
36
|
+
},
|
|
37
|
+
})).toMatchObject({
|
|
38
|
+
reasoningEffort: "medium",
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("normalizes Codex model options and fast mode defaults", () => {
|
|
43
|
+
expect(normalizeCodexModelOptions(undefined)).toEqual({
|
|
44
|
+
reasoningEffort: "high",
|
|
45
|
+
fastMode: false,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const normalized = normalizeCodexModelOptions({
|
|
49
|
+
codex: {
|
|
50
|
+
reasoningEffort: "xhigh",
|
|
51
|
+
fastMode: true,
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
expect(normalized).toEqual({
|
|
56
|
+
reasoningEffort: "xhigh",
|
|
57
|
+
fastMode: true,
|
|
58
|
+
})
|
|
59
|
+
expect(codexServiceTierFromModelOptions(normalized)).toBe("fast")
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("resolves Claude API model ids for 1m context window", () => {
|
|
63
|
+
expect(resolveClaudeApiModelId("opus", "1m")).toBe("opus[1m]")
|
|
64
|
+
expect(resolveClaudeApiModelId("sonnet", "200k")).toBe("sonnet")
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const DISCOVERED_MODELS: DiscoveredModel[] = [
|
|
69
|
+
{
|
|
70
|
+
value: "default",
|
|
71
|
+
displayName: "Default (recommended)",
|
|
72
|
+
description: "Opus 4.6 with 1M context",
|
|
73
|
+
supportsEffort: true,
|
|
74
|
+
supportedEffortLevels: ["low", "medium", "high", "max"],
|
|
75
|
+
supportsFastMode: true,
|
|
76
|
+
supportsAutoMode: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: "sonnet",
|
|
80
|
+
displayName: "Sonnet",
|
|
81
|
+
description: "Sonnet 4.6 · Best for everyday tasks",
|
|
82
|
+
supportsEffort: true,
|
|
83
|
+
supportedEffortLevels: ["low", "medium", "high"],
|
|
84
|
+
supportsAutoMode: true,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
value: "haiku",
|
|
88
|
+
displayName: "Haiku",
|
|
89
|
+
description: "Haiku 4.5 · Fastest for quick answers",
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
function makeCapabilities(models: DiscoveredModel[] = DISCOVERED_MODELS): RuntimeCapabilities {
|
|
94
|
+
return { models, probedAt: Date.now(), runtimeVersion: "1.0.0" }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe("deriveServerProviderCatalog", () => {
|
|
98
|
+
test("returns static SERVER_PROVIDERS when no capabilities", () => {
|
|
99
|
+
const result = deriveServerProviderCatalog()
|
|
100
|
+
expect(result).toEqual(SERVER_PROVIDERS)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("returns static SERVER_PROVIDERS when capabilities have empty models", () => {
|
|
104
|
+
const result = deriveServerProviderCatalog(makeCapabilities([]))
|
|
105
|
+
expect(result).toEqual(SERVER_PROVIDERS)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("enriches static Claude models with discovered description and effort levels", () => {
|
|
109
|
+
const result = deriveServerProviderCatalog(makeCapabilities())
|
|
110
|
+
const claude = result.find((p) => p.id === "claude")!
|
|
111
|
+
const sonnet = claude.models.find((m) => m.id === "sonnet")!
|
|
112
|
+
expect(sonnet.description).toBe("Sonnet 4.6 · Best for everyday tasks")
|
|
113
|
+
expect(sonnet.supportedEffortLevels).toEqual(["low", "medium", "high"])
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("maps 'default' discovered model to static 'opus' entry", () => {
|
|
117
|
+
const result = deriveServerProviderCatalog(makeCapabilities())
|
|
118
|
+
const claude = result.find((p) => p.id === "claude")!
|
|
119
|
+
const opus = claude.models.find((m) => m.id === "opus")!
|
|
120
|
+
expect(opus.description).toBe("Opus 4.6 with 1M context")
|
|
121
|
+
expect(opus.supportedEffortLevels).toEqual(["low", "medium", "high", "max"])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("appends discovered models not in static list", () => {
|
|
125
|
+
const extraModel: DiscoveredModel = {
|
|
126
|
+
value: "claude-opus-4-7",
|
|
127
|
+
displayName: "Opus 4",
|
|
128
|
+
description: "Newer version available",
|
|
129
|
+
}
|
|
130
|
+
const result = deriveServerProviderCatalog(makeCapabilities([...DISCOVERED_MODELS, extraModel]))
|
|
131
|
+
const claude = result.find((p) => p.id === "claude")!
|
|
132
|
+
const extra = claude.models.find((m) => m.id === "claude-opus-4-7")
|
|
133
|
+
expect(extra).toBeDefined()
|
|
134
|
+
expect(extra!.description).toBe("Newer version available")
|
|
135
|
+
expect(extra!.label).toBe("Opus 4")
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test("preserves static model order — static first, discovered extras appended", () => {
|
|
139
|
+
const extraModel: DiscoveredModel = {
|
|
140
|
+
value: "claude-opus-4-7",
|
|
141
|
+
displayName: "Opus 4",
|
|
142
|
+
description: "Newer version",
|
|
143
|
+
}
|
|
144
|
+
const result = deriveServerProviderCatalog(makeCapabilities([...DISCOVERED_MODELS, extraModel]))
|
|
145
|
+
const claude = result.find((p) => p.id === "claude")!
|
|
146
|
+
const ids = claude.models.map((m) => m.id)
|
|
147
|
+
expect(ids).toEqual(["opus", "sonnet", "haiku", "claude-opus-4-7"])
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test("codex is always static regardless of capabilities", () => {
|
|
151
|
+
const result = deriveServerProviderCatalog(makeCapabilities())
|
|
152
|
+
const codex = result.find((p) => p.id === "codex")!
|
|
153
|
+
const staticCodex = SERVER_PROVIDERS.find((p) => p.id === "codex")!
|
|
154
|
+
expect(codex).toEqual(staticCodex)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe("normalizeServerModel with dynamic catalog", () => {
|
|
159
|
+
test("accepts full model id when present in dynamic catalog", () => {
|
|
160
|
+
const extraModel: DiscoveredModel = {
|
|
161
|
+
value: "claude-opus-4-7",
|
|
162
|
+
displayName: "Opus 4",
|
|
163
|
+
description: "Newer version",
|
|
164
|
+
}
|
|
165
|
+
const catalog = deriveServerProviderCatalog(makeCapabilities([...DISCOVERED_MODELS, extraModel]))
|
|
166
|
+
expect(normalizeServerModel("claude", "claude-opus-4-7", catalog)).toBe("claude-opus-4-7")
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test("falls back to default model when model not in catalog", () => {
|
|
170
|
+
const catalog = deriveServerProviderCatalog(makeCapabilities())
|
|
171
|
+
expect(normalizeServerModel("claude", "nonexistent", catalog)).toBe("sonnet")
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test("uses static catalog by default (backward compat)", () => {
|
|
175
|
+
expect(normalizeServerModel("claude", "sonnet")).toBe("sonnet")
|
|
176
|
+
})
|
|
177
|
+
})
|