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,155 @@
|
|
|
1
|
+
import { describe, test, expect, afterEach } from "bun:test"
|
|
2
|
+
import { mkdtemp, writeFile, rm } from "node:fs/promises"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { WorkflowStore, WorkflowParseError } from "./workflow-store"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
let tmpDirs: string[] = []
|
|
9
|
+
|
|
10
|
+
async function makeTmpDir(): Promise<string> {
|
|
11
|
+
const dir = await mkdtemp(join(tmpdir(), "wf-store-test-"))
|
|
12
|
+
tmpDirs.push(dir)
|
|
13
|
+
return dir
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function writeYaml(dir: string, filename: string, content: string): Promise<void> {
|
|
17
|
+
await writeFile(join(dir, filename), content, "utf-8")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
for (const dir of tmpDirs) {
|
|
22
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {})
|
|
23
|
+
}
|
|
24
|
+
tmpDirs = []
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe("WorkflowStore", () => {
|
|
28
|
+
test("list() returns all .yaml files parsed and sorted by name", async () => {
|
|
29
|
+
const dir = await makeTmpDir()
|
|
30
|
+
await writeYaml(dir, "beta.yaml", `name: Beta\nsteps:\n - mcp_tool: do_thing`)
|
|
31
|
+
await writeYaml(dir, "alpha.yaml", `name: Alpha\nsteps:\n - mcp_tool: do_other`)
|
|
32
|
+
// non-yaml file should be ignored
|
|
33
|
+
await writeFile(join(dir, "readme.txt"), "not a workflow")
|
|
34
|
+
|
|
35
|
+
const store = new WorkflowStore(dir)
|
|
36
|
+
const results = await store.list()
|
|
37
|
+
|
|
38
|
+
expect(results).toHaveLength(2)
|
|
39
|
+
expect(results[0].name).toBe("Alpha")
|
|
40
|
+
expect(results[0].id).toBe("alpha")
|
|
41
|
+
expect(results[1].name).toBe("Beta")
|
|
42
|
+
expect(results[1].id).toBe("beta")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("get(id) returns specific workflow by id", async () => {
|
|
46
|
+
const dir = await makeTmpDir()
|
|
47
|
+
await writeYaml(dir, "deploy.yaml", `name: Deploy\nsteps:\n - mcp_tool: run_deploy`)
|
|
48
|
+
|
|
49
|
+
const store = new WorkflowStore(dir)
|
|
50
|
+
const wf = await store.get("deploy")
|
|
51
|
+
|
|
52
|
+
expect(wf).not.toBeNull()
|
|
53
|
+
expect(wf!.id).toBe("deploy")
|
|
54
|
+
expect(wf!.name).toBe("Deploy")
|
|
55
|
+
expect(wf!.steps[0].mcp_tool).toBe("run_deploy")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("get(id) returns null for non-existent workflow", async () => {
|
|
59
|
+
const dir = await makeTmpDir()
|
|
60
|
+
const store = new WorkflowStore(dir)
|
|
61
|
+
const wf = await store.get("nope")
|
|
62
|
+
expect(wf).toBeNull()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("parses valid YAML with manual trigger", async () => {
|
|
66
|
+
const dir = await makeTmpDir()
|
|
67
|
+
await writeYaml(dir, "m.yaml", `name: Manual\ntrigger: manual\nsteps:\n - mcp_tool: x`)
|
|
68
|
+
|
|
69
|
+
const store = new WorkflowStore(dir)
|
|
70
|
+
const wf = await store.get("m")
|
|
71
|
+
expect(wf!.trigger).toBe("manual")
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("parses valid YAML with cron trigger", async () => {
|
|
75
|
+
const dir = await makeTmpDir()
|
|
76
|
+
await writeYaml(dir, "c.yaml", `name: Cron\ntrigger:\n cron: "*/5 * * * *"\nsteps:\n - mcp_tool: x`)
|
|
77
|
+
|
|
78
|
+
const store = new WorkflowStore(dir)
|
|
79
|
+
const wf = await store.get("c")
|
|
80
|
+
expect(wf!.trigger).toEqual({ cron: "*/5 * * * *" })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("parses valid YAML with on_event trigger", async () => {
|
|
84
|
+
const dir = await makeTmpDir()
|
|
85
|
+
await writeYaml(dir, "e.yaml", `name: Event\ntrigger:\n on_event: push\nsteps:\n - mcp_tool: x`)
|
|
86
|
+
|
|
87
|
+
const store = new WorkflowStore(dir)
|
|
88
|
+
const wf = await store.get("e")
|
|
89
|
+
expect(wf!.trigger).toEqual({ on_event: "push" })
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("rejects YAML missing name", async () => {
|
|
93
|
+
const dir = await makeTmpDir()
|
|
94
|
+
await writeYaml(dir, "bad.yaml", `steps:\n - mcp_tool: x`)
|
|
95
|
+
|
|
96
|
+
const store = new WorkflowStore(dir)
|
|
97
|
+
await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("rejects YAML missing steps", async () => {
|
|
101
|
+
const dir = await makeTmpDir()
|
|
102
|
+
await writeYaml(dir, "bad.yaml", `name: Bad`)
|
|
103
|
+
|
|
104
|
+
const store = new WorkflowStore(dir)
|
|
105
|
+
await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("rejects YAML with empty steps array", async () => {
|
|
109
|
+
const dir = await makeTmpDir()
|
|
110
|
+
await writeYaml(dir, "bad.yaml", `name: Bad\nsteps: []`)
|
|
111
|
+
|
|
112
|
+
const store = new WorkflowStore(dir)
|
|
113
|
+
await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("rejects YAML with invalid on_failure value", async () => {
|
|
117
|
+
const dir = await makeTmpDir()
|
|
118
|
+
await writeYaml(dir, "bad.yaml", `name: Bad\nsteps:\n - mcp_tool: x\non_failure: explode`)
|
|
119
|
+
|
|
120
|
+
const store = new WorkflowStore(dir)
|
|
121
|
+
await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("applies default values: trigger=manual, target=all, on_failure=stop, params={}", async () => {
|
|
125
|
+
const dir = await makeTmpDir()
|
|
126
|
+
await writeYaml(dir, "defaults.yaml", `name: Defaults\nsteps:\n - mcp_tool: foo`)
|
|
127
|
+
|
|
128
|
+
const store = new WorkflowStore(dir)
|
|
129
|
+
const wf = await store.get("defaults")
|
|
130
|
+
|
|
131
|
+
expect(wf!.trigger).toBe("manual")
|
|
132
|
+
expect(wf!.target).toBe("all")
|
|
133
|
+
expect(wf!.on_failure).toBe("stop")
|
|
134
|
+
expect(wf!.steps[0].params).toEqual({})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test("list() skips files that fail parsing and warns", async () => {
|
|
138
|
+
const dir = await makeTmpDir()
|
|
139
|
+
await writeYaml(dir, "good.yaml", `name: Good\nsteps:\n - mcp_tool: x`)
|
|
140
|
+
await writeYaml(dir, "bad.yaml", `steps: []`) // missing name AND empty steps
|
|
141
|
+
|
|
142
|
+
const warnings: string[] = []
|
|
143
|
+
const origWarn = console.warn
|
|
144
|
+
console.warn = (...args: unknown[]) => { warnings.push(String(args[0])) }
|
|
145
|
+
|
|
146
|
+
const store = new WorkflowStore(dir)
|
|
147
|
+
const results = await store.list()
|
|
148
|
+
|
|
149
|
+
console.warn = origWarn
|
|
150
|
+
|
|
151
|
+
expect(results).toHaveLength(1)
|
|
152
|
+
expect(results[0].name).toBe("Good")
|
|
153
|
+
expect(warnings.length).toBeGreaterThan(0)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises"
|
|
2
|
+
import { join, basename } from "node:path"
|
|
3
|
+
import yaml from "js-yaml"
|
|
4
|
+
import type { WorkflowDefinition, WorkflowStep, WorkflowTrigger } from "../shared/workflow-types"
|
|
5
|
+
|
|
6
|
+
const LOG_PREFIX = "[workflow-store]"
|
|
7
|
+
|
|
8
|
+
export class WorkflowParseError extends Error {
|
|
9
|
+
constructor(
|
|
10
|
+
public readonly workflowId: string,
|
|
11
|
+
public readonly field: string,
|
|
12
|
+
message: string,
|
|
13
|
+
) {
|
|
14
|
+
super(`[${workflowId}] ${field}: ${message}`)
|
|
15
|
+
this.name = "WorkflowParseError"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class WorkflowStore {
|
|
20
|
+
constructor(private readonly workflowsDir: string) {}
|
|
21
|
+
|
|
22
|
+
async list(): Promise<WorkflowDefinition[]> {
|
|
23
|
+
let entries: string[]
|
|
24
|
+
try {
|
|
25
|
+
entries = await readdir(this.workflowsDir)
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.warn(`${LOG_PREFIX} Cannot read workflows dir: ${err instanceof Error ? err.message : String(err)}`)
|
|
28
|
+
return []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const yamlFiles = entries.filter((f) => f.endsWith(".yaml")).sort()
|
|
32
|
+
const results: WorkflowDefinition[] = []
|
|
33
|
+
|
|
34
|
+
for (const file of yamlFiles) {
|
|
35
|
+
const id = basename(file, ".yaml")
|
|
36
|
+
try {
|
|
37
|
+
const content = await readFile(join(this.workflowsDir, file), "utf-8")
|
|
38
|
+
const raw = yaml.load(content)
|
|
39
|
+
results.push(this.parseYaml(id, raw))
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.warn(`${LOG_PREFIX} Skipping ${file}: ${err instanceof Error ? err.message : String(err)}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return results.sort((a, b) => a.name.localeCompare(b.name))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async get(id: string): Promise<WorkflowDefinition | null> {
|
|
49
|
+
const filePath = join(this.workflowsDir, `${id}.yaml`)
|
|
50
|
+
let content: string
|
|
51
|
+
try {
|
|
52
|
+
content = await readFile(filePath, "utf-8")
|
|
53
|
+
} catch {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
const raw = yaml.load(content)
|
|
57
|
+
return this.parseYaml(id, raw)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private parseYaml(id: string, raw: unknown): WorkflowDefinition {
|
|
61
|
+
if (typeof raw !== "object" || raw === null) {
|
|
62
|
+
throw new WorkflowParseError(id, "root", "must be an object")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const obj = raw as Record<string, unknown>
|
|
66
|
+
|
|
67
|
+
// name
|
|
68
|
+
if (typeof obj.name !== "string" || obj.name.trim() === "") {
|
|
69
|
+
throw new WorkflowParseError(id, "name", "must be a non-empty string")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// trigger
|
|
73
|
+
let trigger: WorkflowTrigger = "manual"
|
|
74
|
+
if (obj.trigger !== undefined) {
|
|
75
|
+
if (obj.trigger === "manual") {
|
|
76
|
+
trigger = "manual"
|
|
77
|
+
} else if (typeof obj.trigger === "object" && obj.trigger !== null) {
|
|
78
|
+
const t = obj.trigger as Record<string, unknown>
|
|
79
|
+
if (typeof t.cron === "string") {
|
|
80
|
+
trigger = { cron: t.cron }
|
|
81
|
+
} else if (typeof t.on_event === "string") {
|
|
82
|
+
trigger = { on_event: t.on_event }
|
|
83
|
+
} else {
|
|
84
|
+
throw new WorkflowParseError(id, "trigger", "must be 'manual', {cron: string}, or {on_event: string}")
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
throw new WorkflowParseError(id, "trigger", "must be 'manual', {cron: string}, or {on_event: string}")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// target
|
|
92
|
+
let target: string = "all"
|
|
93
|
+
if (obj.target !== undefined) {
|
|
94
|
+
if (typeof obj.target !== "string" || obj.target.trim() === "") {
|
|
95
|
+
throw new WorkflowParseError(id, "target", "must be 'all' or a non-empty string")
|
|
96
|
+
}
|
|
97
|
+
target = obj.target
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// steps
|
|
101
|
+
if (!Array.isArray(obj.steps) || obj.steps.length === 0) {
|
|
102
|
+
throw new WorkflowParseError(id, "steps", "must be a non-empty array")
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const steps: WorkflowStep[] = obj.steps.map((s: unknown, i: number) => {
|
|
106
|
+
if (typeof s !== "object" || s === null) {
|
|
107
|
+
throw new WorkflowParseError(id, `steps[${i}]`, "must be an object")
|
|
108
|
+
}
|
|
109
|
+
const step = s as Record<string, unknown>
|
|
110
|
+
if (typeof step.mcp_tool !== "string") {
|
|
111
|
+
throw new WorkflowParseError(id, `steps[${i}].mcp_tool`, "must be a string")
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
mcp_tool: step.mcp_tool,
|
|
115
|
+
params: (typeof step.params === "object" && step.params !== null ? step.params : {}) as Record<string, unknown>,
|
|
116
|
+
...(typeof step.label === "string" ? { label: step.label } : {}),
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// on_failure
|
|
121
|
+
const validFailures = ["stop", "continue", "rollback"] as const
|
|
122
|
+
let onFailure: "stop" | "continue" | "rollback" = "stop"
|
|
123
|
+
if (obj.on_failure !== undefined) {
|
|
124
|
+
if (!validFailures.includes(obj.on_failure as typeof validFailures[number])) {
|
|
125
|
+
throw new WorkflowParseError(id, "on_failure", `must be one of: ${validFailures.join(", ")}`)
|
|
126
|
+
}
|
|
127
|
+
onFailure = obj.on_failure as "stop" | "continue" | "rollback"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
id,
|
|
132
|
+
name: obj.name,
|
|
133
|
+
trigger,
|
|
134
|
+
target,
|
|
135
|
+
steps,
|
|
136
|
+
on_failure: onFailure,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, expect, test, afterEach } from "bun:test"
|
|
2
|
+
import { SessionIndex } from "./session-index"
|
|
3
|
+
import { EventStore } from "./event-store"
|
|
4
|
+
import { TranscriptSearchIndex } from "./transcript-search"
|
|
5
|
+
import { WorkspaceAgent } from "./workspace-agent"
|
|
6
|
+
import { createWorkspaceAgentRouter } from "./workspace-agent-routes"
|
|
7
|
+
import type { TranscriptEntry } from "../shared/types"
|
|
8
|
+
import type { StoreState, ChatRecord } from "./events"
|
|
9
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
10
|
+
import { tmpdir } from "node:os"
|
|
11
|
+
import path from "node:path"
|
|
12
|
+
|
|
13
|
+
let tempDirs: string[] = []
|
|
14
|
+
|
|
15
|
+
function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
|
|
16
|
+
entry: T,
|
|
17
|
+
): TranscriptEntry {
|
|
18
|
+
return { _id: crypto.randomUUID(), createdAt: Date.now(), ...entry } as TranscriptEntry
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function createIntegration() {
|
|
22
|
+
const dir = await mkdtemp(path.join(tmpdir(), "pa-integration-test-"))
|
|
23
|
+
tempDirs.push(dir)
|
|
24
|
+
const store = new EventStore(dir)
|
|
25
|
+
await store.initialize()
|
|
26
|
+
const sessions = new SessionIndex()
|
|
27
|
+
const search = new TranscriptSearchIndex()
|
|
28
|
+
const project = await store.openProject("/tmp/test-integration", "Integration Test")
|
|
29
|
+
const workspaceId = project.id
|
|
30
|
+
const agent = new WorkspaceAgent({ sessions, store, search, workspaceId })
|
|
31
|
+
const router = createWorkspaceAgentRouter(agent)
|
|
32
|
+
return { store, sessions, search, agent, router, workspaceId }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
for (const d of tempDirs) await rm(d, { recursive: true, force: true })
|
|
37
|
+
tempDirs = []
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function makeState(workspaceId: string): StoreState {
|
|
41
|
+
const workspacesById = new Map([[workspaceId, { id: workspaceId, localPath: "/tmp/p", title: "Test", createdAt: 0, updatedAt: 0 }]])
|
|
42
|
+
const workspaceIdsByPath = new Map<string, string>()
|
|
43
|
+
const chatsById = new Map<string, ChatRecord>([
|
|
44
|
+
["c1", {
|
|
45
|
+
id: "c1", workspaceId, repoId: null, title: "Chat 1", createdAt: Date.now(), updatedAt: Date.now(),
|
|
46
|
+
unread: false, provider: "claude", planMode: false, sessionToken: null, lastTurnOutcome: null,
|
|
47
|
+
}],
|
|
48
|
+
["c2", {
|
|
49
|
+
id: "c2", workspaceId, repoId: null, title: "Chat 2", createdAt: Date.now(), updatedAt: Date.now(),
|
|
50
|
+
unread: false, provider: "codex", planMode: false, sessionToken: null, lastTurnOutcome: null,
|
|
51
|
+
}],
|
|
52
|
+
])
|
|
53
|
+
return { workspacesById, workspaceIdsByPath, independentWorkspacesById: new Map(), chatsById, queuedTurnsByChat: new Map(), coordinationByWorkspace: new Map(), agentConfigsByWorkspace: new Map(), reposById: new Map(), reposByPath: new Map(), workflowRunsByWorkspace: new Map(), sandboxByWorkspace: new Map(), providerProfiles: new Map(), workspaceProfileOverrides: new Map(), extensionPreferences: new Map(), teamMembers: new Map(), runnerLabels: new Map() }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("project agent integration", () => {
|
|
57
|
+
test("end-to-end: messages → indexes → query via HTTP routes", async () => {
|
|
58
|
+
const { sessions, search, router, workspaceId } = await createIntegration()
|
|
59
|
+
const state = makeState(workspaceId)
|
|
60
|
+
|
|
61
|
+
// Simulate two sessions sending messages
|
|
62
|
+
const e1 = timestamped({ kind: "user_prompt", content: "implement auth middleware with JWT" })
|
|
63
|
+
const e2 = timestamped({ kind: "user_prompt", content: "fix CSS styling on the sidebar component" })
|
|
64
|
+
sessions.onMessageAppended("c1", e1, state)
|
|
65
|
+
sessions.onMessageAppended("c2", e2, state)
|
|
66
|
+
search.addEntry("c1", e1)
|
|
67
|
+
search.addEntry("c2", e2)
|
|
68
|
+
|
|
69
|
+
// Query sessions via HTTP
|
|
70
|
+
const sessionsRes = await router(new Request(`http://localhost/api/workspace/sessions?workspaceId=${workspaceId}`))
|
|
71
|
+
const sessionsBody = await sessionsRes.json() as Array<Record<string, unknown>>
|
|
72
|
+
expect(sessionsRes.status).toBe(200)
|
|
73
|
+
expect(sessionsBody.length).toBe(2)
|
|
74
|
+
|
|
75
|
+
// Search transcripts via HTTP
|
|
76
|
+
const searchRes = await router(new Request("http://localhost/api/workspace/search", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify({ query: "auth JWT middleware", limit: 5 }),
|
|
80
|
+
}))
|
|
81
|
+
const searchBody = await searchRes.json() as Array<Record<string, unknown>>
|
|
82
|
+
expect(searchRes.status).toBe(200)
|
|
83
|
+
expect(searchBody.length).toBeGreaterThanOrEqual(1)
|
|
84
|
+
expect(searchBody[0].chatId).toBe("c1")
|
|
85
|
+
|
|
86
|
+
// Claim task via HTTP
|
|
87
|
+
const claimRes = await router(new Request("http://localhost/api/workspace/claim", {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "Content-Type": "application/json" },
|
|
90
|
+
body: JSON.stringify({ description: "implement auth middleware", session: "c1", branch: "feat/auth" }),
|
|
91
|
+
}))
|
|
92
|
+
const claimBody = await claimRes.json() as Record<string, unknown>
|
|
93
|
+
expect(claimRes.status).toBe(200)
|
|
94
|
+
expect(claimBody.status).toBe("claimed")
|
|
95
|
+
|
|
96
|
+
// List tasks via HTTP
|
|
97
|
+
const tasksRes = await router(new Request("http://localhost/api/workspace/tasks"))
|
|
98
|
+
const tasksBody = await tasksRes.json() as Array<Record<string, unknown>>
|
|
99
|
+
expect(tasksBody.length).toBe(1)
|
|
100
|
+
|
|
101
|
+
// Delegate via HTTP
|
|
102
|
+
const delegateRes = await router(new Request("http://localhost/api/workspace/delegate", {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify({ request: "who is working on auth?" }),
|
|
106
|
+
}))
|
|
107
|
+
const delegateBody = await delegateRes.json() as Record<string, unknown>
|
|
108
|
+
expect(delegateBody.status).toBe("ok")
|
|
109
|
+
expect((delegateBody.message as string).toLowerCase()).toContain("auth")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("tasks survive EventStore reload (durability)", async () => {
|
|
113
|
+
const dir = await mkdtemp(path.join(tmpdir(), "durable-"))
|
|
114
|
+
tempDirs.push(dir)
|
|
115
|
+
|
|
116
|
+
// First store instance: create a task
|
|
117
|
+
const store1 = new EventStore(dir)
|
|
118
|
+
await store1.initialize()
|
|
119
|
+
const project1 = await store1.openProject("/tmp/durable", "Durable Test")
|
|
120
|
+
const agent1 = new WorkspaceAgent({
|
|
121
|
+
sessions: new SessionIndex(),
|
|
122
|
+
store: store1,
|
|
123
|
+
search: new TranscriptSearchIndex(),
|
|
124
|
+
workspaceId: project1.id,
|
|
125
|
+
})
|
|
126
|
+
const task = await agent1.claimTask("durable task", "session-1", null)
|
|
127
|
+
|
|
128
|
+
// Second store instance: reload from same directory
|
|
129
|
+
const store2 = new EventStore(dir)
|
|
130
|
+
await store2.initialize()
|
|
131
|
+
const agent2 = new WorkspaceAgent({
|
|
132
|
+
sessions: new SessionIndex(),
|
|
133
|
+
store: store2,
|
|
134
|
+
search: new TranscriptSearchIndex(),
|
|
135
|
+
workspaceId: project1.id,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const tasks = agent2.listTasks()
|
|
139
|
+
expect(tasks.length).toBe(1)
|
|
140
|
+
expect(tasks[0].description).toBe("durable task")
|
|
141
|
+
expect(tasks[0].status).toBe("claimed")
|
|
142
|
+
expect(tasks[0].id).toBe(task.id)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("complete task lifecycle via HTTP", async () => {
|
|
146
|
+
const { router } = await createIntegration()
|
|
147
|
+
|
|
148
|
+
// Claim
|
|
149
|
+
const claimRes = await router(new Request("http://localhost/api/workspace/claim", {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: { "Content-Type": "application/json" },
|
|
152
|
+
body: JSON.stringify({ description: "setup database", session: "c1", branch: null }),
|
|
153
|
+
}))
|
|
154
|
+
const claimed = await claimRes.json() as Record<string, unknown>
|
|
155
|
+
expect(claimed.status).toBe("claimed")
|
|
156
|
+
|
|
157
|
+
// Complete
|
|
158
|
+
const completeRes = await router(new Request("http://localhost/api/workspace/complete", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: { "Content-Type": "application/json" },
|
|
161
|
+
body: JSON.stringify({ taskId: claimed.id, outputs: ["migrations/001.sql"] }),
|
|
162
|
+
}))
|
|
163
|
+
const completed = await completeRes.json() as Record<string, unknown>
|
|
164
|
+
expect(completed.status).toBe("complete")
|
|
165
|
+
expect(completed.outputs).toEqual(["migrations/001.sql"])
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/server/project-agent-routes.test.ts
|
|
2
|
+
import { describe, expect, test, afterEach } from "bun:test"
|
|
3
|
+
import { createWorkspaceAgentRouter } from "./workspace-agent-routes"
|
|
4
|
+
import { SessionIndex } from "./session-index"
|
|
5
|
+
import { EventStore } from "./event-store"
|
|
6
|
+
import { TranscriptSearchIndex } from "./transcript-search"
|
|
7
|
+
import { WorkspaceAgent } from "./workspace-agent"
|
|
8
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
9
|
+
import { tmpdir } from "node:os"
|
|
10
|
+
import path from "node:path"
|
|
11
|
+
|
|
12
|
+
let tempDirs: string[] = []
|
|
13
|
+
|
|
14
|
+
async function createRouter() {
|
|
15
|
+
const dir = await mkdtemp(path.join(tmpdir(), "pa-routes-test-"))
|
|
16
|
+
tempDirs.push(dir)
|
|
17
|
+
const store = new EventStore(dir)
|
|
18
|
+
await store.initialize()
|
|
19
|
+
const sessions = new SessionIndex()
|
|
20
|
+
const search = new TranscriptSearchIndex()
|
|
21
|
+
const project = await store.openProject("/tmp/test", "Test Project")
|
|
22
|
+
const workspaceId = project.id
|
|
23
|
+
const agent = new WorkspaceAgent({ sessions, store, search, workspaceId })
|
|
24
|
+
const router = createWorkspaceAgentRouter(agent)
|
|
25
|
+
return { router, agent, sessions, store, search, workspaceId }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
for (const d of tempDirs) await rm(d, { recursive: true, force: true })
|
|
30
|
+
tempDirs = []
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe("project-agent-routes", () => {
|
|
34
|
+
test("GET /api/workspace/sessions returns JSON array", async () => {
|
|
35
|
+
const { router, workspaceId } = await createRouter()
|
|
36
|
+
const req = new Request(`http://localhost/api/workspace/sessions?workspaceId=${workspaceId}`)
|
|
37
|
+
const res = await router(req)
|
|
38
|
+
expect(res.status).toBe(200)
|
|
39
|
+
const body = await res.json()
|
|
40
|
+
expect(Array.isArray(body)).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test("POST /api/workspace/search returns results", async () => {
|
|
44
|
+
const { router, search } = await createRouter()
|
|
45
|
+
search.addEntry("c1", { _id: "1", createdAt: Date.now(), kind: "user_prompt", content: "auth setup" } as never)
|
|
46
|
+
|
|
47
|
+
const req = new Request("http://localhost/api/workspace/search", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { "Content-Type": "application/json" },
|
|
50
|
+
body: JSON.stringify({ query: "auth", limit: 10 }),
|
|
51
|
+
})
|
|
52
|
+
const res = await router(req)
|
|
53
|
+
expect(res.status).toBe(200)
|
|
54
|
+
const body = await res.json()
|
|
55
|
+
expect(Array.isArray(body)).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("GET /api/workspace/tasks returns task list", async () => {
|
|
59
|
+
const { router, agent } = await createRouter()
|
|
60
|
+
await agent.claimTask("test task", "c1", null)
|
|
61
|
+
|
|
62
|
+
const req = new Request("http://localhost/api/workspace/tasks")
|
|
63
|
+
const res = await router(req)
|
|
64
|
+
expect(res.status).toBe(200)
|
|
65
|
+
const body = await res.json()
|
|
66
|
+
expect(body.length).toBe(1)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("POST /api/workspace/claim creates a task", async () => {
|
|
70
|
+
const { router } = await createRouter()
|
|
71
|
+
const req = new Request("http://localhost/api/workspace/claim", {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "Content-Type": "application/json" },
|
|
74
|
+
body: JSON.stringify({ description: "implement auth", session: "c1", branch: "feat/auth" }),
|
|
75
|
+
})
|
|
76
|
+
const res = await router(req)
|
|
77
|
+
expect(res.status).toBe(200)
|
|
78
|
+
const body = await res.json()
|
|
79
|
+
expect(body.status).toBe("claimed")
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("POST /api/workspace/complete marks task done", async () => {
|
|
83
|
+
const { router, agent } = await createRouter()
|
|
84
|
+
const task = await agent.claimTask("task", "c1", null)
|
|
85
|
+
|
|
86
|
+
const req = new Request("http://localhost/api/workspace/complete", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: { "Content-Type": "application/json" },
|
|
89
|
+
body: JSON.stringify({ taskId: task.id, outputs: ["file.ts"] }),
|
|
90
|
+
})
|
|
91
|
+
const res = await router(req)
|
|
92
|
+
expect(res.status).toBe(200)
|
|
93
|
+
const body = await res.json()
|
|
94
|
+
expect(body.status).toBe("complete")
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test("POST /api/workspace/delegate returns delegation result", async () => {
|
|
98
|
+
const { router } = await createRouter()
|
|
99
|
+
const req = new Request("http://localhost/api/workspace/delegate", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "Content-Type": "application/json" },
|
|
102
|
+
body: JSON.stringify({ request: "what is going on?" }),
|
|
103
|
+
})
|
|
104
|
+
const res = await router(req)
|
|
105
|
+
expect(res.status).toBe(200)
|
|
106
|
+
const body = await res.json()
|
|
107
|
+
expect(body.status).toBe("ok")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("returns 404 for unknown routes", async () => {
|
|
111
|
+
const { router } = await createRouter()
|
|
112
|
+
const req = new Request("http://localhost/api/workspace/nonexistent")
|
|
113
|
+
const res = await router(req)
|
|
114
|
+
expect(res.status).toBe(404)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test("returns 400 for missing required fields", async () => {
|
|
118
|
+
const { router } = await createRouter()
|
|
119
|
+
const req = new Request("http://localhost/api/workspace/claim", {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: { "Content-Type": "application/json" },
|
|
122
|
+
body: JSON.stringify({}),
|
|
123
|
+
})
|
|
124
|
+
const res = await router(req)
|
|
125
|
+
expect(res.status).toBe(400)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/server/project-agent-routes.ts
|
|
2
|
+
import { LOG_PREFIX } from "../shared/branding"
|
|
3
|
+
import type { WorkspaceAgent } from "./workspace-agent"
|
|
4
|
+
|
|
5
|
+
function jsonResponse(data: unknown, status = 200): Response {
|
|
6
|
+
return new Response(JSON.stringify(data), {
|
|
7
|
+
status,
|
|
8
|
+
headers: { "Content-Type": "application/json" },
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function errorResponse(error: string, code: number, detail?: string): Response {
|
|
13
|
+
return jsonResponse({ error, code, detail }, code)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function readBody(req: Request): Promise<Record<string, unknown>> {
|
|
17
|
+
try {
|
|
18
|
+
return (await req.json()) as Record<string, unknown>
|
|
19
|
+
} catch (err: unknown) {
|
|
20
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
21
|
+
console.warn(`${LOG_PREFIX} readBody: invalid JSON — ${message}`)
|
|
22
|
+
return {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createWorkspaceAgentRouter(agent: WorkspaceAgent): (req: Request) => Promise<Response> {
|
|
27
|
+
return async (req: Request): Promise<Response> => {
|
|
28
|
+
const url = new URL(req.url)
|
|
29
|
+
const path = url.pathname.replace(/^\/api\/workspace/, "")
|
|
30
|
+
|
|
31
|
+
if (req.method === "GET" && path === "/sessions") {
|
|
32
|
+
const workspaceId = url.searchParams.get("workspaceId") ?? ""
|
|
33
|
+
return jsonResponse(agent.querySessions(workspaceId))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (req.method === "GET" && path.startsWith("/sessions/")) {
|
|
37
|
+
const chatId = path.replace("/sessions/", "")
|
|
38
|
+
const session = agent.getSessionSummary(chatId)
|
|
39
|
+
return session ? jsonResponse(session) : errorResponse("Session not found", 404)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (req.method === "POST" && path === "/search") {
|
|
43
|
+
const body = await readBody(req)
|
|
44
|
+
const query = body.query as string | undefined
|
|
45
|
+
if (!query) return errorResponse("Missing 'query'", 400)
|
|
46
|
+
const rawLimit = body.limit
|
|
47
|
+
const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? rawLimit : 10
|
|
48
|
+
return jsonResponse(agent.searchWork(query, limit))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (req.method === "GET" && path === "/tasks") {
|
|
52
|
+
return jsonResponse(agent.listTasks())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (req.method === "GET" && path.startsWith("/tasks/")) {
|
|
56
|
+
const taskId = path.replace("/tasks/", "")
|
|
57
|
+
const task = agent.getTask(taskId)
|
|
58
|
+
return task ? jsonResponse(task) : errorResponse("Task not found", 404)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (req.method === "POST" && path === "/claim") {
|
|
62
|
+
const body = await readBody(req)
|
|
63
|
+
const description = body.description as string | undefined
|
|
64
|
+
const session = body.session as string | undefined
|
|
65
|
+
if (!description || !session) return errorResponse("Missing 'description' or 'session'", 400)
|
|
66
|
+
const branch = (body.branch as string) ?? null
|
|
67
|
+
return jsonResponse(await agent.claimTask(description, session, branch))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (req.method === "POST" && path === "/complete") {
|
|
71
|
+
const body = await readBody(req)
|
|
72
|
+
const taskId = body.taskId as string | undefined
|
|
73
|
+
if (!taskId) return errorResponse("Missing 'taskId'", 400)
|
|
74
|
+
const outputs = Array.isArray(body.outputs) ? (body.outputs as string[]) : []
|
|
75
|
+
const task = await agent.completeTask(taskId, outputs)
|
|
76
|
+
return task ? jsonResponse(task) : errorResponse("Task not found", 404)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (req.method === "POST" && path === "/delegate") {
|
|
80
|
+
const body = await readBody(req)
|
|
81
|
+
const request = body.request as string | undefined
|
|
82
|
+
if (!request) return errorResponse("Missing 'request'", 400)
|
|
83
|
+
const result = await agent.delegate(request)
|
|
84
|
+
return jsonResponse(result)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return errorResponse("Not found", 404)
|
|
88
|
+
}
|
|
89
|
+
}
|