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,290 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import type { TranscriptEntry } from "../shared/types"
|
|
3
|
+
import {
|
|
4
|
+
allocateSessionBudgets,
|
|
5
|
+
buildBudgetedTranscriptExcerpt,
|
|
6
|
+
generateMergePromptForChats,
|
|
7
|
+
MERGE_FLOOR_PER_SESSION_CHARS,
|
|
8
|
+
MAX_MERGE_TOTAL_CHARS,
|
|
9
|
+
} from "./generate-merge-context"
|
|
10
|
+
import { MAX_MERGE_SESSIONS } from "../shared/merge-presets"
|
|
11
|
+
import type { QuickResponseAdapter, StructuredQuickResponseArgs } from "./quick-response"
|
|
12
|
+
|
|
13
|
+
const entryBase = () => ({ _id: crypto.randomUUID(), createdAt: Date.now() })
|
|
14
|
+
|
|
15
|
+
function userPrompt(content: string): TranscriptEntry {
|
|
16
|
+
return { ...entryBase(), kind: "user_prompt" as const, content }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function assistantText(text: string): TranscriptEntry {
|
|
20
|
+
return { ...entryBase(), kind: "assistant_text" as const, text }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function compactSummary(summary: string): TranscriptEntry {
|
|
24
|
+
return { ...entryBase(), kind: "compact_summary" as const, summary }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resultEntry(result: string, isError = false): TranscriptEntry {
|
|
28
|
+
return { ...entryBase(), kind: "result" as const, result, isError, subtype: isError ? "error" : "success", durationMs: 100 }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function contextCleared(): TranscriptEntry {
|
|
32
|
+
return { ...entryBase(), kind: "context_cleared" as const }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function statusEntry(status: string): TranscriptEntry {
|
|
36
|
+
return { ...entryBase(), kind: "status" as const, status }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── allocateSessionBudgets ──────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe("allocateSessionBudgets", () => {
|
|
42
|
+
test("gives floor + proportional remainder", () => {
|
|
43
|
+
const sessions = [
|
|
44
|
+
{ entries: [userPrompt("short")] },
|
|
45
|
+
{ entries: [userPrompt("a".repeat(200)), assistantText("b".repeat(300))] },
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const budgets = allocateSessionBudgets(sessions)
|
|
49
|
+
|
|
50
|
+
expect(budgets).toHaveLength(2)
|
|
51
|
+
// Both should get at least the floor
|
|
52
|
+
expect(budgets[0]!).toBeGreaterThanOrEqual(MERGE_FLOOR_PER_SESSION_CHARS)
|
|
53
|
+
expect(budgets[1]!).toBeGreaterThanOrEqual(MERGE_FLOOR_PER_SESSION_CHARS)
|
|
54
|
+
// Total should not exceed the max
|
|
55
|
+
const total = budgets.reduce((sum, b) => sum + b, 0)
|
|
56
|
+
expect(total).toBeLessThanOrEqual(MAX_MERGE_TOTAL_CHARS)
|
|
57
|
+
// Session with more content should get a larger budget
|
|
58
|
+
expect(budgets[1]!).toBeGreaterThan(budgets[0]!)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test("skips empty sessions (returns 0 for them)", () => {
|
|
62
|
+
const sessions = [
|
|
63
|
+
{ entries: [userPrompt("hello")] },
|
|
64
|
+
{ entries: [] },
|
|
65
|
+
{ entries: [userPrompt("world"), assistantText("reply")] },
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
const budgets = allocateSessionBudgets(sessions)
|
|
69
|
+
|
|
70
|
+
expect(budgets).toHaveLength(3)
|
|
71
|
+
expect(budgets[1]).toBe(0)
|
|
72
|
+
expect(budgets[0]!).toBeGreaterThan(0)
|
|
73
|
+
expect(budgets[2]!).toBeGreaterThan(0)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("throws if more than MAX_MERGE_SESSIONS sessions", () => {
|
|
77
|
+
const sessions = Array.from({ length: MAX_MERGE_SESSIONS + 1 }, () => ({
|
|
78
|
+
entries: [userPrompt("x")],
|
|
79
|
+
}))
|
|
80
|
+
|
|
81
|
+
expect(() => allocateSessionBudgets(sessions)).toThrow()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// ── buildBudgetedTranscriptExcerpt ──────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
describe("buildBudgetedTranscriptExcerpt", () => {
|
|
88
|
+
test("respects char budget", () => {
|
|
89
|
+
const entries = [
|
|
90
|
+
userPrompt("a".repeat(500)),
|
|
91
|
+
assistantText("b".repeat(500)),
|
|
92
|
+
userPrompt("c".repeat(500)),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const budget = 800
|
|
96
|
+
const result = buildBudgetedTranscriptExcerpt(entries, budget)
|
|
97
|
+
|
|
98
|
+
expect(result.length).toBeLessThanOrEqual(budget)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test("prefers entries after context_cleared", () => {
|
|
102
|
+
const entries = [
|
|
103
|
+
userPrompt("old context before clear"),
|
|
104
|
+
assistantText("old response"),
|
|
105
|
+
contextCleared(),
|
|
106
|
+
userPrompt("new context after clear"),
|
|
107
|
+
assistantText("new response"),
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
const result = buildBudgetedTranscriptExcerpt(entries, 5000)
|
|
111
|
+
|
|
112
|
+
expect(result).toContain("new context after clear")
|
|
113
|
+
expect(result).toContain("new response")
|
|
114
|
+
expect(result).not.toContain("old context before clear")
|
|
115
|
+
expect(result).not.toContain("old response")
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test("returns fallback message for empty entries", () => {
|
|
119
|
+
const result = buildBudgetedTranscriptExcerpt([], 5000)
|
|
120
|
+
|
|
121
|
+
expect(result).toContain("No prior transcript")
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("filters out non-transcript entry kinds", () => {
|
|
125
|
+
const entries = [
|
|
126
|
+
statusEntry("thinking"),
|
|
127
|
+
userPrompt("visible"),
|
|
128
|
+
statusEntry("more thinking"),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
const result = buildBudgetedTranscriptExcerpt(entries, 5000)
|
|
132
|
+
|
|
133
|
+
expect(result).toContain("visible")
|
|
134
|
+
expect(result).not.toContain("thinking")
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test("includes compact_summary entries", () => {
|
|
138
|
+
const entries = [compactSummary("This is a compact summary of prior work.")]
|
|
139
|
+
|
|
140
|
+
const result = buildBudgetedTranscriptExcerpt(entries, 5000)
|
|
141
|
+
|
|
142
|
+
expect(result).toContain("compact summary of prior work")
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("includes result entries with error flag", () => {
|
|
146
|
+
const entries = [resultEntry("something failed", true)]
|
|
147
|
+
|
|
148
|
+
const result = buildBudgetedTranscriptExcerpt(entries, 5000)
|
|
149
|
+
|
|
150
|
+
expect(result).toContain("something failed")
|
|
151
|
+
expect(result).toContain("error")
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// ── generateMergePromptForChats ─────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
describe("generateMergePromptForChats", () => {
|
|
158
|
+
function createMockAdapter(
|
|
159
|
+
respond?: (args: StructuredQuickResponseArgs<unknown>) => unknown | null,
|
|
160
|
+
) {
|
|
161
|
+
const capturedArgs: StructuredQuickResponseArgs<unknown>[] = []
|
|
162
|
+
|
|
163
|
+
const adapter = {
|
|
164
|
+
generateStructured: async <T>(args: StructuredQuickResponseArgs<T>): Promise<T | null> => {
|
|
165
|
+
capturedArgs.push(args as StructuredQuickResponseArgs<unknown>)
|
|
166
|
+
const result = respond ? respond(args as StructuredQuickResponseArgs<unknown>) : null
|
|
167
|
+
if (result === null) return null
|
|
168
|
+
return args.parse(result) as T | null
|
|
169
|
+
},
|
|
170
|
+
} as QuickResponseAdapter
|
|
171
|
+
|
|
172
|
+
return { adapter, getCapturedArgs: () => capturedArgs }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
test("analyzes intent, compacts each source, and labels the final brief by chat id", async () => {
|
|
176
|
+
const { adapter, getCapturedArgs } = createMockAdapter()
|
|
177
|
+
const sessions = [
|
|
178
|
+
{ chatId: "abc", entries: [userPrompt("session one context")] },
|
|
179
|
+
{ chatId: "def", entries: [userPrompt("session two context")] },
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
const result = await generateMergePromptForChats(
|
|
183
|
+
"Merge these sessions",
|
|
184
|
+
sessions,
|
|
185
|
+
"/tmp/test",
|
|
186
|
+
undefined,
|
|
187
|
+
adapter,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const args = getCapturedArgs()
|
|
191
|
+
expect(args).toHaveLength(3)
|
|
192
|
+
expect(args[0]!.task).toContain("analysis")
|
|
193
|
+
expect(args[1]!.task).toBe("session context compaction")
|
|
194
|
+
expect(args[2]!.task).toBe("session context compaction")
|
|
195
|
+
expect(args[1]!.prompt).toContain("session one context")
|
|
196
|
+
expect(args[2]!.prompt).toContain("session two context")
|
|
197
|
+
expect(result).toContain("### abc")
|
|
198
|
+
expect(result).toContain("### def")
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test("includes preset hint in the analysis call when provided", async () => {
|
|
202
|
+
const { adapter, getCapturedArgs } = createMockAdapter()
|
|
203
|
+
const sessions = [
|
|
204
|
+
{ chatId: "a", entries: [userPrompt("one")] },
|
|
205
|
+
{ chatId: "b", entries: [userPrompt("two")] },
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
await generateMergePromptForChats(
|
|
209
|
+
"Compare approaches",
|
|
210
|
+
sessions,
|
|
211
|
+
"/tmp/test",
|
|
212
|
+
"compare_decide",
|
|
213
|
+
adapter,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const args = getCapturedArgs()
|
|
217
|
+
expect(args[0]!.prompt).toContain("Selected preset: Compare & decide.")
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test("throws for empty sessions", async () => {
|
|
221
|
+
const { adapter } = createMockAdapter()
|
|
222
|
+
|
|
223
|
+
await expect(
|
|
224
|
+
generateMergePromptForChats("merge", [], "/tmp/test", undefined, adapter),
|
|
225
|
+
).rejects.toThrow()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test("throws for more than MAX_MERGE_SESSIONS sessions", async () => {
|
|
229
|
+
const { adapter } = createMockAdapter()
|
|
230
|
+
const sessions = Array.from({ length: MAX_MERGE_SESSIONS + 1 }, (_, i) => ({
|
|
231
|
+
chatId: `s${i}`,
|
|
232
|
+
entries: [userPrompt(`session ${i}`)],
|
|
233
|
+
}))
|
|
234
|
+
|
|
235
|
+
await expect(
|
|
236
|
+
generateMergePromptForChats("merge", sessions, "/tmp/test", undefined, adapter),
|
|
237
|
+
).rejects.toThrow()
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test("returns a composed fallback brief when adapter calls fail", async () => {
|
|
241
|
+
const { adapter } = createMockAdapter(() => null)
|
|
242
|
+
const sessions = [
|
|
243
|
+
{ chatId: "a", entries: [userPrompt("one")] },
|
|
244
|
+
{ chatId: "b", entries: [userPrompt("two")] },
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
const result = await generateMergePromptForChats(
|
|
248
|
+
"My merge intent",
|
|
249
|
+
sessions,
|
|
250
|
+
"/tmp/test",
|
|
251
|
+
undefined,
|
|
252
|
+
adapter,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
expect(result).toContain("## Objective\nMy merge intent")
|
|
256
|
+
expect(result).toContain("### a")
|
|
257
|
+
expect(result).toContain("### b")
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test("returns a generated brief on success", async () => {
|
|
261
|
+
const { adapter } = createMockAdapter((args) => {
|
|
262
|
+
if (args.task.includes("analysis")) {
|
|
263
|
+
return {
|
|
264
|
+
compactInstruction: "Keep only the verified findings.",
|
|
265
|
+
nextInstruction: "Resolve the best combined next step.",
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (args.prompt.includes("User: one")) {
|
|
269
|
+
return { summary: "Summary for source one." }
|
|
270
|
+
}
|
|
271
|
+
return { summary: "Summary for source two." }
|
|
272
|
+
})
|
|
273
|
+
const sessions = [
|
|
274
|
+
{ chatId: "a", entries: [userPrompt("one")] },
|
|
275
|
+
{ chatId: "b", entries: [userPrompt("two")] },
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
const result = await generateMergePromptForChats(
|
|
279
|
+
"Merge intent",
|
|
280
|
+
sessions,
|
|
281
|
+
"/tmp/test",
|
|
282
|
+
undefined,
|
|
283
|
+
adapter,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
expect(result).toContain("## Objective\nResolve the best combined next step.")
|
|
287
|
+
expect(result).toContain("### a\nSummary for source one.")
|
|
288
|
+
expect(result).toContain("### b\nSummary for source two.")
|
|
289
|
+
})
|
|
290
|
+
})
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { TranscriptEntry } from "../shared/types"
|
|
2
|
+
import { getMergePreset, MAX_MERGE_SESSIONS } from "../shared/merge-presets"
|
|
3
|
+
import { QuickResponseAdapter } from "./quick-response"
|
|
4
|
+
import { toTranscriptLine, truncateLine } from "./transcript-utils"
|
|
5
|
+
import { buildSessionSeedPrompt } from "./session-seed"
|
|
6
|
+
|
|
7
|
+
const LOG_PREFIX = "[generate-merge-context]"
|
|
8
|
+
|
|
9
|
+
export const MAX_MERGE_TOTAL_CHARS = 14_000
|
|
10
|
+
export const MERGE_FLOOR_PER_SESSION_CHARS = 500
|
|
11
|
+
const MAX_MERGE_LINE_CHARS = 700
|
|
12
|
+
|
|
13
|
+
/** Slice entries to only those after the last context_cleared boundary, if any. */
|
|
14
|
+
function sliceAfterLastContextCleared(entries: TranscriptEntry[]): TranscriptEntry[] {
|
|
15
|
+
let lastClearedIndex = -1
|
|
16
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
17
|
+
if (entries[i]!.kind === "context_cleared") {
|
|
18
|
+
lastClearedIndex = i
|
|
19
|
+
break
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return lastClearedIndex >= 0 ? entries.slice(lastClearedIndex + 1) : entries
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Build a bounded transcript excerpt that respects a custom character budget. */
|
|
26
|
+
export function buildBudgetedTranscriptExcerpt(
|
|
27
|
+
entries: TranscriptEntry[],
|
|
28
|
+
charBudget: number,
|
|
29
|
+
): string {
|
|
30
|
+
const sliced = sliceAfterLastContextCleared(entries)
|
|
31
|
+
|
|
32
|
+
const lines = sliced
|
|
33
|
+
.map((e) => toTranscriptLine(e, MAX_MERGE_LINE_CHARS))
|
|
34
|
+
.filter((line): line is string => Boolean(line))
|
|
35
|
+
|
|
36
|
+
if (lines.length === 0) return "No prior transcript context was available."
|
|
37
|
+
|
|
38
|
+
const header = "Recent source transcript excerpt:"
|
|
39
|
+
const headerCost = header.length + 1 // +1 for the newline
|
|
40
|
+
|
|
41
|
+
// Take from the end (most recent first) until budget is exhausted
|
|
42
|
+
const selected: string[] = []
|
|
43
|
+
let remaining = charBudget - headerCost
|
|
44
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
45
|
+
const line = lines[i]!
|
|
46
|
+
const cost = line.length + 1 // +1 for the newline separator
|
|
47
|
+
if (remaining - cost < 0) break
|
|
48
|
+
selected.unshift(line)
|
|
49
|
+
remaining -= cost
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (selected.length === 0) {
|
|
53
|
+
// Even a single line doesn't fit; truncate the last line to fit
|
|
54
|
+
const lastLine = lines[lines.length - 1]!
|
|
55
|
+
const maxLen = Math.max(0, charBudget - headerCost - 1)
|
|
56
|
+
if (maxLen > 0) {
|
|
57
|
+
selected.push(truncateLine(lastLine, maxLen))
|
|
58
|
+
} else {
|
|
59
|
+
return "No prior transcript context was available."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const omittedCount = lines.length - selected.length
|
|
64
|
+
const finalHeader = omittedCount > 0
|
|
65
|
+
? `Recent source transcript excerpt. Older relevant lines omitted: ${omittedCount}.`
|
|
66
|
+
: header
|
|
67
|
+
|
|
68
|
+
return [finalHeader, ...selected].join("\n")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Compute how many usable transcript characters each session contributes. */
|
|
72
|
+
function computeUsableLength(entries: TranscriptEntry[]): number {
|
|
73
|
+
const sliced = sliceAfterLastContextCleared(entries)
|
|
74
|
+
return sliced
|
|
75
|
+
.map((e) => toTranscriptLine(e, MAX_MERGE_LINE_CHARS))
|
|
76
|
+
.filter((line): line is string => Boolean(line))
|
|
77
|
+
.reduce((sum, line) => sum + line.length, 0)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Allocate character budgets across sessions proportionally to their content size.
|
|
81
|
+
* Empty sessions get 0. Every non-empty session gets at least MERGE_FLOOR_PER_SESSION_CHARS. */
|
|
82
|
+
export function allocateSessionBudgets(
|
|
83
|
+
sessions: { entries: TranscriptEntry[] }[],
|
|
84
|
+
): number[] {
|
|
85
|
+
if (sessions.length > MAX_MERGE_SESSIONS) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`${LOG_PREFIX} Cannot merge more than ${MAX_MERGE_SESSIONS} sessions (got ${sessions.length})`,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const usableLengths = sessions.map((s) => computeUsableLength(s.entries))
|
|
92
|
+
const nonEmptyCount = usableLengths.filter((len) => len > 0).length
|
|
93
|
+
|
|
94
|
+
if (nonEmptyCount === 0) {
|
|
95
|
+
return usableLengths.map(() => 0)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const totalFloor = nonEmptyCount * MERGE_FLOOR_PER_SESSION_CHARS
|
|
99
|
+
const remainder = Math.max(0, MAX_MERGE_TOTAL_CHARS - totalFloor)
|
|
100
|
+
const totalUsable = usableLengths.reduce((sum, len) => sum + len, 0)
|
|
101
|
+
|
|
102
|
+
return usableLengths.map((len) => {
|
|
103
|
+
if (len === 0) return 0
|
|
104
|
+
const proportional = totalUsable > 0
|
|
105
|
+
? Math.floor(remainder * (len / totalUsable))
|
|
106
|
+
: Math.floor(remainder / nonEmptyCount)
|
|
107
|
+
return MERGE_FLOOR_PER_SESSION_CHARS + proportional
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Generate a merge prompt that synthesizes context from multiple sessions.
|
|
112
|
+
* Requires 2–MAX_MERGE_SESSIONS sessions. Returns the generated prompt or the
|
|
113
|
+
* normalized intent as a fallback if the adapter fails. */
|
|
114
|
+
export async function generateMergePromptForChats(
|
|
115
|
+
mergeIntent: string,
|
|
116
|
+
sessions: { chatId: string; entries: TranscriptEntry[] }[],
|
|
117
|
+
cwd: string,
|
|
118
|
+
presetId?: string,
|
|
119
|
+
adapter: QuickResponseAdapter = new QuickResponseAdapter(),
|
|
120
|
+
): Promise<string> {
|
|
121
|
+
if (sessions.length < 1) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`${LOG_PREFIX} Merge requires at least 1 session (got ${sessions.length})`,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
if (sessions.length > MAX_MERGE_SESSIONS) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`${LOG_PREFIX} Cannot merge more than ${MAX_MERGE_SESSIONS} sessions (got ${sessions.length})`,
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const preset = getMergePreset(presetId)
|
|
133
|
+
return buildSessionSeedPrompt({
|
|
134
|
+
mode: "merge",
|
|
135
|
+
intent: mergeIntent,
|
|
136
|
+
preset,
|
|
137
|
+
sources: sessions,
|
|
138
|
+
cwd,
|
|
139
|
+
adapter,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { QuickResponseAdapter } from "./quick-response"
|
|
2
|
+
|
|
3
|
+
const TITLE_SCHEMA = {
|
|
4
|
+
type: "object",
|
|
5
|
+
properties: {
|
|
6
|
+
title: { type: "string" },
|
|
7
|
+
},
|
|
8
|
+
required: ["title"],
|
|
9
|
+
additionalProperties: false,
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
function normalizeGeneratedTitle(value: unknown): string | null {
|
|
13
|
+
if (typeof value !== "string") return null
|
|
14
|
+
const normalized = value.replace(/\s+/g, " ").trim().slice(0, 80)
|
|
15
|
+
if (!normalized || normalized === "New Chat") return null
|
|
16
|
+
return normalized
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function generateTitleForChat(
|
|
20
|
+
messageContent: string,
|
|
21
|
+
cwd: string,
|
|
22
|
+
adapter = new QuickResponseAdapter()
|
|
23
|
+
): Promise<string | null> {
|
|
24
|
+
const result = await adapter.generateStructured<string>({
|
|
25
|
+
cwd,
|
|
26
|
+
task: "conversation title generation",
|
|
27
|
+
prompt: `Generate a short, descriptive title (under 30 chars) for a conversation that starts with this message.\n\n${messageContent}`,
|
|
28
|
+
schema: TITLE_SCHEMA,
|
|
29
|
+
parse: (value) => {
|
|
30
|
+
const output = value && typeof value === "object" ? value as { title?: unknown } : {}
|
|
31
|
+
return normalizeGeneratedTitle(output.title)
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return result
|
|
36
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, test, expect, afterEach } from "bun:test"
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { $ } from "bun"
|
|
6
|
+
import { EventStore } from "./event-store"
|
|
7
|
+
import { RepoManager } from "./repo-manager"
|
|
8
|
+
import { GitClonePolicy } from "./git-clone-policy"
|
|
9
|
+
|
|
10
|
+
describe("GitClonePolicy", () => {
|
|
11
|
+
const tempDirs: string[] = []
|
|
12
|
+
|
|
13
|
+
async function makeTempDir(): Promise<string> {
|
|
14
|
+
const dir = await mkdtemp(path.join(tmpdir(), "git-clone-policy-"))
|
|
15
|
+
tempDirs.push(dir)
|
|
16
|
+
return dir
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
for (const dir of tempDirs) {
|
|
21
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {})
|
|
22
|
+
}
|
|
23
|
+
tempDirs.length = 0
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test("successful clone sets status to cloned with correct path and branch", async () => {
|
|
27
|
+
const dataDir = await makeTempDir()
|
|
28
|
+
const store = new EventStore(dataDir)
|
|
29
|
+
await store.initialize()
|
|
30
|
+
const repoManager = new RepoManager()
|
|
31
|
+
|
|
32
|
+
let stateChanged = false
|
|
33
|
+
const policy = new GitClonePolicy(store, repoManager, () => {
|
|
34
|
+
stateChanged = true
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Create a bare repo to clone from
|
|
38
|
+
const bareDir = await makeTempDir()
|
|
39
|
+
await $`git init --bare ${bareDir}`.quiet()
|
|
40
|
+
|
|
41
|
+
// Need at least one commit so branch exists
|
|
42
|
+
const seedDir = await makeTempDir()
|
|
43
|
+
await $`git clone ${bareDir} ${seedDir}`.quiet()
|
|
44
|
+
await $`git -C ${seedDir} -c user.email=ci@example.com -c user.name=CI commit --allow-empty -m "init"`.quiet()
|
|
45
|
+
await $`git -C ${seedDir} push origin main`.quiet().nothrow()
|
|
46
|
+
// Push whatever default branch was created
|
|
47
|
+
await $`git -C ${seedDir} push`.quiet()
|
|
48
|
+
|
|
49
|
+
const repoId = "repo-1"
|
|
50
|
+
const workspaceId = "ws-1"
|
|
51
|
+
const targetPath = path.join(await makeTempDir(), "cloned-repo")
|
|
52
|
+
|
|
53
|
+
await store.startRepoClone(repoId, workspaceId, bareDir, targetPath, "test-repo")
|
|
54
|
+
expect(store.state.reposById.get(repoId)?.status).toBe("pending")
|
|
55
|
+
|
|
56
|
+
await policy.onRepoCloneStarted(repoId, bareDir, targetPath)
|
|
57
|
+
|
|
58
|
+
const repo = store.state.reposById.get(repoId)
|
|
59
|
+
expect(repo).toBeDefined()
|
|
60
|
+
expect(repo!.status).toBe("cloned")
|
|
61
|
+
expect(repo!.localPath).toBe(targetPath)
|
|
62
|
+
expect(repo!.branch).toBeTruthy()
|
|
63
|
+
expect(store.state.reposByPath.has(targetPath)).toBe(true)
|
|
64
|
+
expect(stateChanged).toBe(true)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("onStateChange throwing does not prevent clone from completing", async () => {
|
|
68
|
+
const dataDir = await makeTempDir()
|
|
69
|
+
const store = new EventStore(dataDir)
|
|
70
|
+
await store.initialize()
|
|
71
|
+
const repoManager = new RepoManager()
|
|
72
|
+
|
|
73
|
+
const policy = new GitClonePolicy(store, repoManager, () => {
|
|
74
|
+
throw new Error("broadcast exploded")
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const bareDir = await makeTempDir()
|
|
78
|
+
await $`git init --bare ${bareDir}`.quiet()
|
|
79
|
+
const seedDir = await makeTempDir()
|
|
80
|
+
await $`git clone ${bareDir} ${seedDir}`.quiet()
|
|
81
|
+
await $`git -C ${seedDir} -c user.email=ci@example.com -c user.name=CI commit --allow-empty -m "init"`.quiet()
|
|
82
|
+
await $`git -C ${seedDir} push`.quiet()
|
|
83
|
+
|
|
84
|
+
const repoId = "repo-safe"
|
|
85
|
+
const targetPath = path.join(await makeTempDir(), "safe-clone")
|
|
86
|
+
await store.startRepoClone(repoId, "ws-safe", bareDir, targetPath, null)
|
|
87
|
+
|
|
88
|
+
await policy.onRepoCloneStarted(repoId, bareDir, targetPath)
|
|
89
|
+
|
|
90
|
+
expect(store.state.reposById.get(repoId)!.status).toBe("cloned")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test("onStateChange throwing does not prevent error status on failed clone", async () => {
|
|
94
|
+
const dataDir = await makeTempDir()
|
|
95
|
+
const store = new EventStore(dataDir)
|
|
96
|
+
await store.initialize()
|
|
97
|
+
const repoManager = new RepoManager()
|
|
98
|
+
|
|
99
|
+
const policy = new GitClonePolicy(store, repoManager, () => {
|
|
100
|
+
throw new Error("broadcast exploded")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const repoId = "repo-safe-fail"
|
|
104
|
+
const targetPath = path.join(await makeTempDir(), "should-fail")
|
|
105
|
+
await store.startRepoClone(repoId, "ws-safe", "/nonexistent/path", targetPath, null)
|
|
106
|
+
|
|
107
|
+
await policy.onRepoCloneStarted(repoId, "/nonexistent/path", targetPath)
|
|
108
|
+
|
|
109
|
+
expect(store.state.reposById.get(repoId)!.status).toBe("error")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("failed clone sets status to error", async () => {
|
|
113
|
+
const dataDir = await makeTempDir()
|
|
114
|
+
const store = new EventStore(dataDir)
|
|
115
|
+
await store.initialize()
|
|
116
|
+
const repoManager = new RepoManager()
|
|
117
|
+
|
|
118
|
+
let stateChanged = false
|
|
119
|
+
const policy = new GitClonePolicy(store, repoManager, () => {
|
|
120
|
+
stateChanged = true
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const repoId = "repo-2"
|
|
124
|
+
const workspaceId = "ws-2"
|
|
125
|
+
const badOrigin = "/nonexistent/repo/path"
|
|
126
|
+
const targetPath = path.join(await makeTempDir(), "should-fail")
|
|
127
|
+
|
|
128
|
+
await store.startRepoClone(repoId, workspaceId, badOrigin, targetPath, null)
|
|
129
|
+
expect(store.state.reposById.get(repoId)?.status).toBe("pending")
|
|
130
|
+
|
|
131
|
+
await policy.onRepoCloneStarted(repoId, badOrigin, targetPath)
|
|
132
|
+
|
|
133
|
+
const repo = store.state.reposById.get(repoId)
|
|
134
|
+
expect(repo).toBeDefined()
|
|
135
|
+
expect(repo!.status).toBe("error")
|
|
136
|
+
expect(stateChanged).toBe(true)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { EventStore } from "./event-store"
|
|
2
|
+
import type { RepoManager } from "./repo-manager"
|
|
3
|
+
|
|
4
|
+
const LOG_PREFIX = "[GitClonePolicy]"
|
|
5
|
+
|
|
6
|
+
export class GitClonePolicy {
|
|
7
|
+
constructor(
|
|
8
|
+
private store: EventStore,
|
|
9
|
+
private repoManager: RepoManager,
|
|
10
|
+
private onStateChange?: () => void,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
/** Fire-and-forget: clones repo, then emits repo_cloned or repo_clone_failed */
|
|
14
|
+
async onRepoCloneStarted(repoId: string, origin: string, targetPath: string): Promise<void> {
|
|
15
|
+
try {
|
|
16
|
+
await this.repoManager.clone(origin, targetPath)
|
|
17
|
+
const info = await this.repoManager.addLocal(targetPath)
|
|
18
|
+
await this.store.markRepoCloned(repoId, targetPath, info.branch)
|
|
19
|
+
try { this.onStateChange?.() } catch { /* broadcast failure is non-fatal */ }
|
|
20
|
+
} catch (err) {
|
|
21
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
22
|
+
console.warn(`${LOG_PREFIX} Clone failed for ${origin}:`, message)
|
|
23
|
+
await this.store.markRepoCloneFailed(repoId, message)
|
|
24
|
+
try { this.onStateChange?.() } catch { /* broadcast failure is non-fatal */ }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { HarnessEvent, HarnessToolRequest, HarnessTurn } from "../shared/harness-types"
|