@rryando/arcs 3.10.0 → 3.11.0
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/README.md +172 -146
- package/dist/cli/agent-registry.d.ts +12 -0
- package/dist/cli/agent-registry.d.ts.map +1 -0
- package/dist/cli/agent-registry.js +67 -0
- package/dist/cli/agent-registry.js.map +1 -0
- package/dist/cli/arcs-flash.d.ts +2 -0
- package/dist/cli/arcs-flash.d.ts.map +1 -0
- package/dist/cli/arcs-flash.js +68 -0
- package/dist/cli/arcs-flash.js.map +1 -0
- package/dist/cli/arcs-orchestrate-caveman.d.ts +2 -14
- package/dist/cli/arcs-orchestrate-caveman.d.ts.map +1 -1
- package/dist/cli/arcs-orchestrate-caveman.js +6 -97
- package/dist/cli/arcs-orchestrate-caveman.js.map +1 -1
- package/dist/cli/arcs-orchestrate.d.ts +1 -1
- package/dist/cli/arcs-orchestrate.d.ts.map +1 -1
- package/dist/cli/arcs-orchestrate.js +40 -383
- package/dist/cli/arcs-orchestrate.js.map +1 -1
- package/dist/cli/bundle-installer.d.ts +10 -4
- package/dist/cli/bundle-installer.d.ts.map +1 -1
- package/dist/cli/bundle-installer.js +99 -6
- package/dist/cli/bundle-installer.js.map +1 -1
- package/dist/cli/commands/batch.js +4 -0
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/hooks.d.ts +36 -0
- package/dist/cli/commands/hooks.d.ts.map +1 -0
- package/dist/cli/commands/hooks.js +298 -0
- package/dist/cli/commands/hooks.js.map +1 -0
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/project.js +27 -0
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/task.js +16 -1
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/cli/commands/web.d.ts +2 -0
- package/dist/cli/commands/web.d.ts.map +1 -0
- package/dist/cli/commands/web.js +56 -0
- package/dist/cli/commands/web.js.map +1 -0
- package/dist/cli/config.d.ts +32 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +56 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/instructions.d.ts +19 -6
- package/dist/cli/instructions.d.ts.map +1 -1
- package/dist/cli/instructions.js +62 -32
- package/dist/cli/instructions.js.map +1 -1
- package/dist/cli/orchestrator-shared-blocks.d.ts +31 -0
- package/dist/cli/orchestrator-shared-blocks.d.ts.map +1 -0
- package/dist/cli/orchestrator-shared-blocks.js +148 -0
- package/dist/cli/orchestrator-shared-blocks.js.map +1 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +159 -85
- package/dist/cli/setup.js.map +1 -1
- package/dist/retrieval/index-builder.d.ts.map +1 -1
- package/dist/retrieval/index-builder.js +29 -1
- package/dist/retrieval/index-builder.js.map +1 -1
- package/dist/utils/claude-code-hook-install.d.ts +80 -0
- package/dist/utils/claude-code-hook-install.d.ts.map +1 -0
- package/dist/utils/claude-code-hook-install.js +146 -0
- package/dist/utils/claude-code-hook-install.js.map +1 -0
- package/dist/utils/claude-transcript.d.ts +127 -0
- package/dist/utils/claude-transcript.d.ts.map +1 -0
- package/dist/utils/claude-transcript.js +353 -0
- package/dist/utils/claude-transcript.js.map +1 -0
- package/dist/utils/diagram-generator.d.ts +1 -1
- package/dist/utils/diagram-generator.d.ts.map +1 -1
- package/dist/utils/diagram-generator.js +3 -2
- package/dist/utils/diagram-generator.js.map +1 -1
- package/dist/utils/errors.d.ts +6 -2
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +12 -0
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/hook-token-store.d.ts +22 -0
- package/dist/utils/hook-token-store.d.ts.map +1 -0
- package/dist/utils/hook-token-store.js +43 -0
- package/dist/utils/hook-token-store.js.map +1 -0
- package/dist/utils/json-schemas.d.ts +186 -2
- package/dist/utils/json-schemas.d.ts.map +1 -1
- package/dist/utils/json-schemas.js +48 -4
- package/dist/utils/json-schemas.js.map +1 -1
- package/dist/utils/knowledge-store.d.ts +7 -1
- package/dist/utils/knowledge-store.d.ts.map +1 -1
- package/dist/utils/knowledge-store.js +97 -65
- package/dist/utils/knowledge-store.js.map +1 -1
- package/dist/utils/plan-store.d.ts +8 -0
- package/dist/utils/plan-store.d.ts.map +1 -1
- package/dist/utils/plan-store.js +98 -59
- package/dist/utils/plan-store.js.map +1 -1
- package/dist/utils/project-resolver.d.ts +8 -0
- package/dist/utils/project-resolver.d.ts.map +1 -1
- package/dist/utils/project-resolver.js +7 -2
- package/dist/utils/project-resolver.js.map +1 -1
- package/dist/utils/session-store.d.ts +113 -0
- package/dist/utils/session-store.d.ts.map +1 -0
- package/dist/utils/session-store.js +312 -0
- package/dist/utils/session-store.js.map +1 -0
- package/dist/utils/storage-utils.d.ts +31 -0
- package/dist/utils/storage-utils.d.ts.map +1 -1
- package/dist/utils/storage-utils.js +116 -5
- package/dist/utils/storage-utils.js.map +1 -1
- package/dist/utils/task-store.d.ts +7 -0
- package/dist/utils/task-store.d.ts.map +1 -1
- package/dist/utils/task-store.js +73 -23
- package/dist/utils/task-store.js.map +1 -1
- package/dist/web-client/assets/GraphCanvas-dNyZ458L.js +1 -0
- package/dist/web-client/assets/MarkdownEditor-BmU9mdkN.js +77 -0
- package/dist/web-client/assets/abnfDiagram-VRR7QNED-D1BFBoeF.js +1 -0
- package/dist/web-client/assets/arc-Bfaogrm1.js +1 -0
- package/dist/web-client/assets/architecture-TIHT7OUA-CJqI5wNI.js +1 -0
- package/dist/web-client/assets/architectureDiagram-ZJ3FMSHR-CA8hTWUK.js +36 -0
- package/dist/web-client/assets/array-BifhSqXX.js +1 -0
- package/dist/web-client/assets/blockDiagram-677ZJIJ3-CQpb_KwX.js +132 -0
- package/dist/web-client/assets/c4Diagram-LMCZKHZV-BhpHX84V.js +10 -0
- package/dist/web-client/assets/channel-C8DlmyVe.js +1 -0
- package/dist/web-client/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/web-client/assets/chunk-32BRIVSS-BUusQQa_.js +1 -0
- package/dist/web-client/assets/chunk-52WLFC77-nNYwlftl.js +10 -0
- package/dist/web-client/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/web-client/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- package/dist/web-client/assets/chunk-C7G6YPKG-D0a-yqnK.js +1 -0
- package/dist/web-client/assets/chunk-EX3LRPZG-IDuRMN-4.js +231 -0
- package/dist/web-client/assets/chunk-FWX5IMBZ-D_spTcqi.js +2 -0
- package/dist/web-client/assets/chunk-HOUHSVGY-C-bcxwZS.js +1 -0
- package/dist/web-client/assets/chunk-ICXQ74PX-CjmK2bAM.js +2 -0
- package/dist/web-client/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/web-client/assets/chunk-KEIR6QF5-DEvma7oi.js +161 -0
- package/dist/web-client/assets/chunk-MOJQB5TN-Bkc08KWY.js +88 -0
- package/dist/web-client/assets/chunk-OGEWGWER-BAAYYvG9.js +1 -0
- package/dist/web-client/assets/chunk-PUDLZKDR-WXbPY7NM.js +156 -0
- package/dist/web-client/assets/chunk-Q4XR5HBZ-DcbnjxQE.js +70 -0
- package/dist/web-client/assets/chunk-RYQCIY6F-np2RyIY1.js +1 -0
- package/dist/web-client/assets/chunk-V7JOEXUC-C6t75PAp.js +206 -0
- package/dist/web-client/assets/chunk-VAUOI2AC-DVrJ0Ic7.js +1 -0
- package/dist/web-client/assets/chunk-VR4S4FIN-MOOFvGS0.js +1 -0
- package/dist/web-client/assets/chunk-WYO6CB5R-sK7Y2NZD.js +125 -0
- package/dist/web-client/assets/chunk-XXDRQBXY-BvKO0v-W.js +1 -0
- package/dist/web-client/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/web-client/assets/chunk-ZGVPDNZ5-a13RQsku.js +62 -0
- package/dist/web-client/assets/chunk-ZIRB5QZD-BpeS1aZ7.js +32 -0
- package/dist/web-client/assets/classDiagram-OUVF2IWQ-p32N1P_G.js +1 -0
- package/dist/web-client/assets/classDiagram-v2-EOCWNBFH-p32N1P_G.js +1 -0
- package/dist/web-client/assets/cose-bilkent-JH36ORCC-BebHqzgp.js +1 -0
- package/dist/web-client/assets/cynefin-VYW2F7L2-D8xaH-wO.js +1 -0
- package/dist/web-client/assets/cynefinDiagram-TSTJHNR4-Bt__EqJW.js +62 -0
- package/dist/web-client/assets/cytoscape-fcose-peAzqedC.js +1 -0
- package/dist/web-client/assets/cytoscape.esm-C3UvhD5t.js +321 -0
- package/dist/web-client/assets/dagre-Buvkdvvj.js +1 -0
- package/dist/web-client/assets/dagre-VKFMJZFB-Bwgjwflz.js +4 -0
- package/dist/web-client/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/web-client/assets/diagram-FQU43EPY-CaPDVUq2.js +3 -0
- package/dist/web-client/assets/diagram-G47NLZAW-BQLB9YYA.js +24 -0
- package/dist/web-client/assets/diagram-NH7WQ7WH-BWo84w8Y.js +24 -0
- package/dist/web-client/assets/diagram-OA4YK3LP-bKn6Pz5s.js +30 -0
- package/dist/web-client/assets/diagram-WEI45ONY-BRqV5Oy6.js +41 -0
- package/dist/web-client/assets/dist-B_J_HbC0.js +1 -0
- package/dist/web-client/assets/ebnfDiagram-CCIWWBDH-DhUXL1-7.js +1 -0
- package/dist/web-client/assets/erDiagram-Q63AITRT-HXAQQ-_F.js +85 -0
- package/dist/web-client/assets/eventmodeling-45OFAUF4-Bj5P8mZJ.js +1 -0
- package/dist/web-client/assets/flowDiagram-23GEKE2U-37BztFri.js +1 -0
- package/dist/web-client/assets/ganttDiagram-NO4QXBWP-D-Ddf_Ii.js +292 -0
- package/dist/web-client/assets/gitGraph-TEB2WS4Q-BmHzs0uF.js +1 -0
- package/dist/web-client/assets/gitGraphDiagram-IHSO6WYX-YnQWrlh_.js +106 -0
- package/dist/web-client/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/web-client/assets/index-3mNPVkix.css +2 -0
- package/dist/web-client/assets/index-DCWxuIeQ.js +58 -0
- package/dist/web-client/assets/info-DKCQHKI2-DCT_B7RN.js +1 -0
- package/dist/web-client/assets/infoDiagram-FWYZ7A6U-D-le1Zhq.js +2 -0
- package/dist/web-client/assets/init-C-OQMol4.js +1 -0
- package/dist/web-client/assets/ishikawaDiagram-FXEZZL3T-Jr1x2VJB.js +70 -0
- package/dist/web-client/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web-client/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web-client/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web-client/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web-client/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web-client/assets/journeyDiagram-5HDEW3XC-BF9ELxj-.js +139 -0
- package/dist/web-client/assets/kanban-definition-HUTT4EX6-C4fJqAxu.js +89 -0
- package/dist/web-client/assets/katex-DolUETbr.js +257 -0
- package/dist/web-client/assets/line-7N7ikFxa.js +1 -0
- package/dist/web-client/assets/linear-DeUiD2wj.js +1 -0
- package/dist/web-client/assets/map-BaFkSB1l.js +1 -0
- package/dist/web-client/assets/mermaid-parser.core-QbC1icPt.js +7 -0
- package/dist/web-client/assets/mermaid.core-C26d_UJm.js +11 -0
- package/dist/web-client/assets/mindmap-definition-LN4V7U3C-D6TV1JDf.js +96 -0
- package/dist/web-client/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/web-client/assets/packet-7NZHBO7P-CR1vrGj3.js +1 -0
- package/dist/web-client/assets/path-COt_16Va.js +1 -0
- package/dist/web-client/assets/pegDiagram-2B236MQR-xOMBBtfV.js +1 -0
- package/dist/web-client/assets/pie-RZYD4A2V-BbWuhjwy.js +1 -0
- package/dist/web-client/assets/pieDiagram-ENE6RG2P-MsfnsqgW.js +39 -0
- package/dist/web-client/assets/quadrantDiagram-ABIIQ3AL-BoI7zKXF.js +7 -0
- package/dist/web-client/assets/radar-I7S5WNFK-CbYXKToJ.js +1 -0
- package/dist/web-client/assets/railroad-3IZDKUUU-6LxHDkLe.js +1 -0
- package/dist/web-client/assets/railroad-abnf-AHOZXSZD-2Dg9wu0J.js +1 -0
- package/dist/web-client/assets/railroad-ebnf-EBAXGLYW-C_E2ot0R.js +1 -0
- package/dist/web-client/assets/railroad-peg-LSFZ7HO6-Bs9UQR1b.js +1 -0
- package/dist/web-client/assets/railroadDiagram-RFXS5EU6-D6RUoUki.js +1 -0
- package/dist/web-client/assets/requirementDiagram-TGXJPOKE-B6k4BDpE.js +84 -0
- package/dist/web-client/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/dist/web-client/assets/rough.esm-By172zw-.js +1 -0
- package/dist/web-client/assets/sankeyDiagram-HTMAVEWB-BUDF-UFr.js +40 -0
- package/dist/web-client/assets/sequenceDiagram-DBY2YBRQ-D6GqcsUi.js +162 -0
- package/dist/web-client/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- package/dist/web-client/assets/src-oBChb5qS.js +1 -0
- package/dist/web-client/assets/stateDiagram-2N3HPSRC-WfJCQAK5.js +1 -0
- package/dist/web-client/assets/stateDiagram-v2-6OUMAXLB-BCMWGnsJ.js +1 -0
- package/dist/web-client/assets/swimlanes-5IMT3BWC-BtMo82mC.js +2 -0
- package/dist/web-client/assets/swimlanesDiagram-G3AALYLV-DmudmLcK.js +8 -0
- package/dist/web-client/assets/timeline-definition-FHXFAJF6-CoAmv2Sn.js +120 -0
- package/dist/web-client/assets/treeView-QDETBFTQ-BWsKzE1s.js +1 -0
- package/dist/web-client/assets/treemap-6X3UGDF4-i_qGtB3o.js +1 -0
- package/dist/web-client/assets/vennDiagram-L72KCM5P-DYkiLe-P.js +34 -0
- package/dist/web-client/assets/wardley-OPB4EBWU-Daaqr1Vp.js +1 -0
- package/dist/web-client/assets/wardleyDiagram-EHGQE667-Bofbsg3J.js +78 -0
- package/dist/web-client/assets/xychartDiagram-FW5EYKEG-KNF4VTfL.js +7 -0
- package/dist/web-client/index.html +15 -0
- package/dist/web-server/app.d.ts +13 -0
- package/dist/web-server/app.d.ts.map +1 -0
- package/dist/web-server/app.js +58 -0
- package/dist/web-server/app.js.map +1 -0
- package/dist/web-server/claude-runner.d.ts +86 -0
- package/dist/web-server/claude-runner.d.ts.map +1 -0
- package/dist/web-server/claude-runner.js +320 -0
- package/dist/web-server/claude-runner.js.map +1 -0
- package/dist/web-server/hook-auth.d.ts +12 -0
- package/dist/web-server/hook-auth.d.ts.map +1 -0
- package/dist/web-server/hook-auth.js +36 -0
- package/dist/web-server/hook-auth.js.map +1 -0
- package/dist/web-server/index.d.ts +23 -0
- package/dist/web-server/index.d.ts.map +1 -0
- package/dist/web-server/index.js +49 -0
- package/dist/web-server/index.js.map +1 -0
- package/dist/web-server/opencode-client.d.ts +123 -0
- package/dist/web-server/opencode-client.d.ts.map +1 -0
- package/dist/web-server/opencode-client.js +514 -0
- package/dist/web-server/opencode-client.js.map +1 -0
- package/dist/web-server/respond.d.ts +33 -0
- package/dist/web-server/respond.d.ts.map +1 -0
- package/dist/web-server/respond.js +79 -0
- package/dist/web-server/respond.js.map +1 -0
- package/dist/web-server/routes/collections.d.ts +11 -0
- package/dist/web-server/routes/collections.d.ts.map +1 -0
- package/dist/web-server/routes/collections.js +217 -0
- package/dist/web-server/routes/collections.js.map +1 -0
- package/dist/web-server/routes/discovery.d.ts +7 -0
- package/dist/web-server/routes/discovery.d.ts.map +1 -0
- package/dist/web-server/routes/discovery.js +159 -0
- package/dist/web-server/routes/discovery.js.map +1 -0
- package/dist/web-server/routes/events.d.ts +6 -0
- package/dist/web-server/routes/events.d.ts.map +1 -0
- package/dist/web-server/routes/events.js +25 -0
- package/dist/web-server/routes/events.js.map +1 -0
- package/dist/web-server/routes/hook-events.d.ts +19 -0
- package/dist/web-server/routes/hook-events.d.ts.map +1 -0
- package/dist/web-server/routes/hook-events.js +135 -0
- package/dist/web-server/routes/hook-events.js.map +1 -0
- package/dist/web-server/routes/projects.d.ts +7 -0
- package/dist/web-server/routes/projects.d.ts.map +1 -0
- package/dist/web-server/routes/projects.js +272 -0
- package/dist/web-server/routes/projects.js.map +1 -0
- package/dist/web-server/routes/sessions.d.ts +16 -0
- package/dist/web-server/routes/sessions.d.ts.map +1 -0
- package/dist/web-server/routes/sessions.js +442 -0
- package/dist/web-server/routes/sessions.js.map +1 -0
- package/dist/web-server/security.d.ts +7 -0
- package/dist/web-server/security.d.ts.map +1 -0
- package/dist/web-server/security.js +41 -0
- package/dist/web-server/security.js.map +1 -0
- package/dist/web-server/static.d.ts +10 -0
- package/dist/web-server/static.d.ts.map +1 -0
- package/dist/web-server/static.js +36 -0
- package/dist/web-server/static.js.map +1 -0
- package/dist/web-server/storage.d.ts +17 -0
- package/dist/web-server/storage.d.ts.map +1 -0
- package/dist/web-server/storage.js +42 -0
- package/dist/web-server/storage.js.map +1 -0
- package/dist/web-server/watcher.d.ts +37 -0
- package/dist/web-server/watcher.d.ts.map +1 -0
- package/dist/web-server/watcher.js +181 -0
- package/dist/web-server/watcher.js.map +1 -0
- package/opencode/arcs/bundle-runtime.json +5 -13
- package/opencode/arcs/manifest.json +213 -149
- package/opencode/arcs/prompts/arcs-docs.txt +53 -47
- package/opencode/arcs/prompts/arcs-flash.txt +190 -0
- package/opencode/arcs/prompts/arcs-orchestrate-caveman.txt +116 -422
- package/opencode/arcs/prompts/arcs-orchestrate.txt +114 -341
- package/opencode/arcs/prompts/code-reviewer.txt +37 -98
- package/opencode/arcs/prompts/devil-advocate.txt +44 -115
- package/opencode/arcs/prompts/graph-explorer.txt +31 -179
- package/opencode/arcs/prompts/software-engineer.txt +24 -6
- package/opencode/arcs/prompts/tech-architect.txt +25 -9
- package/opencode/arcs/skills/brainstorming/SKILL.md +65 -105
- package/opencode/arcs/skills/deep-pr-review/SKILL.md +5 -5
- package/opencode/arcs/skills/deep-pr-review/review-template.md +1 -1
- package/opencode/arcs/skills/enriching-codegraph-proposals/SKILL.md +17 -18
- package/opencode/arcs/skills/executing-plans/SKILL.md +26 -234
- package/opencode/arcs/skills/implementation/SKILL.md +61 -0
- package/opencode/arcs/skills/init-project/SKILL.md +16 -15
- package/opencode/arcs/skills/install-claude-code-hook/SKILL.md +143 -0
- package/opencode/arcs/skills/systematic-debugging/SKILL.md +7 -7
- package/opencode/arcs/skills/test-driven-development/SKILL.md +1 -1
- package/opencode/arcs/skills/to-diagram/SKILL.md +8 -3
- package/opencode/arcs/skills/writing-knowledge/SKILL.md +7 -6
- package/opencode/arcs/skills/writing-plans/SKILL.md +80 -130
- package/opencode/arcs/skills/writing-plans/plan-document-reviewer-prompt.md +38 -30
- package/package.json +14 -6
- package/scripts/build-opencode-bundle.mjs +22 -7
- package/scripts/claude-code-session-hook.mjs +125 -0
- package/scripts/deploy-claudecode-bundle.mjs +185 -132
- package/scripts/deploy-opencode-bundle.mjs +181 -5
- package/scripts/lib/bundle-helpers.mjs +26 -0
- package/scripts/lint-bundle.mjs +123 -3
- package/skills/explore-dag.md +5 -3
- package/skills/init-project.md +27 -26
- package/skills/orchestrate.md +29 -13
- package/skills/update-docs.md +8 -13
- package/opencode/arcs/prompts/docs-researcher.txt +0 -79
- package/opencode/arcs/prompts/oncall-ops.txt +0 -85
- package/opencode/arcs/skills/code-agent/SKILL.md +0 -63
- package/opencode/arcs/skills/executing-plans/code-quality-reviewer-prompt.md +0 -57
- package/opencode/arcs/skills/executing-plans/implementer-prompt.md +0 -152
- package/opencode/arcs/skills/executing-plans/spec-reviewer-prompt.md +0 -91
- package/opencode/arcs/skills/quick-dev/SKILL.md +0 -52
- package/opencode/arcs/skills/requesting-code-review/SKILL.md +0 -59
- package/opencode/arcs/skills/requesting-code-review/code-reviewer.md +0 -139
- package/opencode/arcs/skills/the-ladder/SKILL.md +0 -54
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session routes — full CRUD over the per-project session index.
|
|
3
|
+
*
|
|
4
|
+
* Sessions are runtime records for agent sessions (opencode, claude-code)
|
|
5
|
+
* attached to a project. All mutations go through the locked session-store,
|
|
6
|
+
* so the opencode discovery bridge and the web UI cannot clobber each other.
|
|
7
|
+
*
|
|
8
|
+
* `POST /sessions/:id/message` is the one route that reaches outside the store:
|
|
9
|
+
* it proxies a prompt into the live runtime (opencode) or queues it for the
|
|
10
|
+
* session to collect itself (claude-code). Every route here is browser-facing
|
|
11
|
+
* and therefore already behind the global loopback-only `secureLocalRequest`
|
|
12
|
+
* middleware — no per-route auth.
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { stat, unlink } from "node:fs/promises";
|
|
16
|
+
import { resolve } from "node:path";
|
|
17
|
+
import { Hono } from "hono";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { appendReferenceTurn, appendSessionTurn, mirrorSessionTranscript, readSessionTurns, sessionTranscriptPath, } from "../../utils/claude-transcript.js";
|
|
20
|
+
import { DagError } from "../../utils/errors.js";
|
|
21
|
+
import { readJsonSafe } from "../../utils/json.js";
|
|
22
|
+
import { createSession, deleteSession, enqueueSessionMessage, getSession, listSessions, SESSION_LINKED_NODE_TYPES, SESSION_RUNTIME_TYPES, SESSION_STATUSES, updateSession, upsertSession, } from "../../utils/session-store.js";
|
|
23
|
+
import { isRunLive, runClaudeJob } from "../claude-runner.js";
|
|
24
|
+
import { createOpencodeSession, readOpencodeConfig, sendOpencodeMessage, } from "../opencode-client.js";
|
|
25
|
+
import { parseBody, requireProjectDir, respond } from "../respond.js";
|
|
26
|
+
export const sessionsRoute = new Hono();
|
|
27
|
+
const createSessionSchema = z.object({
|
|
28
|
+
runtimeType: z.enum(SESSION_RUNTIME_TYPES),
|
|
29
|
+
runtimeSessionId: z.string().min(1),
|
|
30
|
+
status: z.enum(SESSION_STATUSES).optional(),
|
|
31
|
+
startedAt: z.string().optional(),
|
|
32
|
+
lastMessageAt: z.string().optional(),
|
|
33
|
+
userEmail: z.string().optional(),
|
|
34
|
+
metadata: z.record(z.unknown()).optional(),
|
|
35
|
+
});
|
|
36
|
+
const updateSessionSchema = z.object({
|
|
37
|
+
status: z.enum(SESSION_STATUSES).optional(),
|
|
38
|
+
lastMessageAt: z.string().nullable().optional(),
|
|
39
|
+
userEmail: z.string().nullable().optional(),
|
|
40
|
+
metadata: z.record(z.unknown()).nullable().optional(),
|
|
41
|
+
/** `null` on either linkage field unlinks the session entirely. */
|
|
42
|
+
linkedNodeType: z.enum(SESSION_LINKED_NODE_TYPES).nullable().optional(),
|
|
43
|
+
linkedNodeId: z.string().nullable().optional(),
|
|
44
|
+
});
|
|
45
|
+
/** A document section the caller is pointing the session at. When present, the
|
|
46
|
+
* delivery call is followed by an ARCS-authored reference turn in the session's
|
|
47
|
+
* transcript sidecar (see appendReferenceTurn). Shared by POST /message and
|
|
48
|
+
* POST /run so both routes accept byte-identical reference payloads. */
|
|
49
|
+
const sessionReferenceSchema = z.object({
|
|
50
|
+
section: z.object({
|
|
51
|
+
depth: z.number(),
|
|
52
|
+
text: z.string(),
|
|
53
|
+
id: z.string(),
|
|
54
|
+
startOffset: z.number(),
|
|
55
|
+
endOffset: z.number(),
|
|
56
|
+
}),
|
|
57
|
+
text: z.string(),
|
|
58
|
+
source: z.object({
|
|
59
|
+
kind: z.enum(["overview", "knowledge", "plan"]),
|
|
60
|
+
label: z.string(),
|
|
61
|
+
doc: z.string().optional(),
|
|
62
|
+
id: z.string().optional(),
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
const sendMessageSchema = z.object({
|
|
66
|
+
message: z.string().min(1),
|
|
67
|
+
reference: sessionReferenceSchema.optional(),
|
|
68
|
+
});
|
|
69
|
+
/** Payload for POST /sessions/:id/run — a headless `claude -p` targeting mode.
|
|
70
|
+
* `threadId` is the stable-mode thread to reuse; when absent (and the
|
|
71
|
+
* referenced session is not itself an ARCS-owned thread) one is minted. */
|
|
72
|
+
const runClaudeMessageSchema = z.object({
|
|
73
|
+
mode: z.enum(["resume", "oneshot", "stable"]),
|
|
74
|
+
message: z.string().min(1),
|
|
75
|
+
threadId: z.string().optional(),
|
|
76
|
+
reference: sessionReferenceSchema.optional(),
|
|
77
|
+
});
|
|
78
|
+
const createOpencodeSessionSchema = z.object({
|
|
79
|
+
title: z.string().min(1).optional(),
|
|
80
|
+
});
|
|
81
|
+
function sessionDirectory(session) {
|
|
82
|
+
const directory = session.metadata?.directory;
|
|
83
|
+
return typeof directory === "string" && directory ? directory : undefined;
|
|
84
|
+
}
|
|
85
|
+
function requireOpencodeConfig() {
|
|
86
|
+
const config = readOpencodeConfig();
|
|
87
|
+
if (!config) {
|
|
88
|
+
throw new DagError("OPENCODE_NOT_CONFIGURED", "No opencode endpoint configured — set OPENCODE_PORT (or ARCS_OPENCODE_URL) " +
|
|
89
|
+
"so ARCS can reach a running `opencode serve`.");
|
|
90
|
+
}
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The worktree a newly created session should run in.
|
|
95
|
+
*
|
|
96
|
+
* Guessing is not an option: opencode happily creates a session in its own
|
|
97
|
+
* working directory when no directory is supplied, which would silently point
|
|
98
|
+
* the agent at the wrong repository. An unregistered project is an error.
|
|
99
|
+
*/
|
|
100
|
+
async function primaryWorkspacePath(projectDir, slug) {
|
|
101
|
+
const meta = await readJsonSafe(resolve(projectDir, "meta.json"));
|
|
102
|
+
const directory = meta?.workspacePaths?.[0];
|
|
103
|
+
if (!directory) {
|
|
104
|
+
throw new DagError("PROJECT_WORKSPACE_UNSET", `Project "${slug}" has no registered workspace path, so there is no directory to ` +
|
|
105
|
+
`create a session in — run \`arcs project update-paths ${slug} --add <path>\` first.`);
|
|
106
|
+
}
|
|
107
|
+
return directory;
|
|
108
|
+
}
|
|
109
|
+
function parseFilters(status, runtimeType) {
|
|
110
|
+
const filters = {};
|
|
111
|
+
if (status && SESSION_STATUSES.includes(status)) {
|
|
112
|
+
filters.status = status;
|
|
113
|
+
}
|
|
114
|
+
if (runtimeType && SESSION_RUNTIME_TYPES.includes(runtimeType)) {
|
|
115
|
+
filters.runtimeType = runtimeType;
|
|
116
|
+
}
|
|
117
|
+
return filters;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Mode-1 write-back (T005): the callback the route registers on runClaudeJob,
|
|
121
|
+
* invoked by the runner after the headless child fully exits — on every outcome
|
|
122
|
+
* (success/error/timeout/killed).
|
|
123
|
+
*
|
|
124
|
+
* - resume: mirrors the resumed session's runtime transcript into its sidecar
|
|
125
|
+
* via the persisted metadata.transcriptPath (hook-events T004 persists it at
|
|
126
|
+
* its checkpoints). The path is re-read fresh from the store — it may have
|
|
127
|
+
* been updated between the 202 and the run's exit. An absent path is a no-op;
|
|
128
|
+
* mirrorSessionTranscript is offset-idempotent and never throws, so repeated
|
|
129
|
+
* write-backs never duplicate and failures are inert.
|
|
130
|
+
* - oneshot/stable: never mirror — their sidecars are appendSessionTurn-owned.
|
|
131
|
+
* On a success outcome the captured reply is appended as an assistant turn
|
|
132
|
+
* (every reply lands in the sidecar); error/timeout outcomes append nothing.
|
|
133
|
+
*
|
|
134
|
+
* Every path finalizes metadata.run with the settled record (pid/startedAt/
|
|
135
|
+
* mode plus endedAt/outcome/error/replyChars) so the panel shows the true
|
|
136
|
+
* result. Best-effort by contract: the runner swallows any error thrown here,
|
|
137
|
+
* so a failed write-back never surfaces on the accepted 202.
|
|
138
|
+
*/
|
|
139
|
+
async function writeBackRun(projectDir, ctx, record) {
|
|
140
|
+
if (ctx.mode === "resume") {
|
|
141
|
+
let target = ctx.writeTarget;
|
|
142
|
+
try {
|
|
143
|
+
target = await getSession(projectDir, ctx.writeTarget.normalizedId);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Session deleted mid-run — fall back to the write-target captured at 202.
|
|
147
|
+
}
|
|
148
|
+
const transcriptPath = target.metadata?.transcriptPath;
|
|
149
|
+
if (typeof transcriptPath === "string" && transcriptPath !== "") {
|
|
150
|
+
await mirrorSessionTranscript(projectDir, ctx.writeTarget.normalizedId, transcriptPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (record.outcome === "success" && record.replyText !== undefined) {
|
|
154
|
+
// Modes 2/3 on success: the captured reply lands in the sidecar as an
|
|
155
|
+
// assistant turn (minted in the shared negative id space after the user
|
|
156
|
+
// turn and any reference). Error/timeout outcomes append nothing.
|
|
157
|
+
await appendSessionTurn(projectDir, ctx.writeTarget.normalizedId, {
|
|
158
|
+
type: "assistant",
|
|
159
|
+
text: record.replyText,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const run = {
|
|
163
|
+
pid: record.pid,
|
|
164
|
+
startedAt: record.startedAt,
|
|
165
|
+
mode: ctx.mode,
|
|
166
|
+
...(record.endedAt !== undefined && { endedAt: record.endedAt }),
|
|
167
|
+
outcome: record.outcome,
|
|
168
|
+
...(record.error !== undefined && { error: record.error }),
|
|
169
|
+
...(record.replyChars !== undefined && { replyChars: record.replyChars }),
|
|
170
|
+
};
|
|
171
|
+
const runMetadata = { run };
|
|
172
|
+
if (ctx.mode === "stable" && ctx.firstStableSpawn && record.outcome === "success") {
|
|
173
|
+
runMetadata.threadInitialized = true;
|
|
174
|
+
}
|
|
175
|
+
await updateSession(projectDir, {
|
|
176
|
+
id: ctx.writeTarget.normalizedId,
|
|
177
|
+
metadata: runMetadata,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
sessionsRoute.get("/api/p/:slug/sessions", async (c) => respond(c, async () => {
|
|
181
|
+
const projectDir = requireProjectDir(c.req.param("slug"));
|
|
182
|
+
const sessions = await listSessions(projectDir, parseFilters(c.req.query("status"), c.req.query("runtimeType")));
|
|
183
|
+
return { sessions };
|
|
184
|
+
}));
|
|
185
|
+
sessionsRoute.post("/api/p/:slug/sessions", async (c) => respond(c, async () => {
|
|
186
|
+
const projectDir = requireProjectDir(c.req.param("slug"));
|
|
187
|
+
const input = await parseBody(c, createSessionSchema);
|
|
188
|
+
return createSession(projectDir, input);
|
|
189
|
+
}, 201));
|
|
190
|
+
/**
|
|
191
|
+
* Creates a live opencode session in the project's primary workspace.
|
|
192
|
+
*
|
|
193
|
+
* The new session is mirrored into the index straight away rather than waiting
|
|
194
|
+
* for the discovery stream to notice it, so the caller can select and message
|
|
195
|
+
* it immediately. The stream's own `session.created` event lands moments later
|
|
196
|
+
* and merges into the same record.
|
|
197
|
+
*
|
|
198
|
+
* There is deliberately no claude-code counterpart: a Claude Code session only
|
|
199
|
+
* exists once a user runs `claude` in a linked directory, so there is nothing
|
|
200
|
+
* for ARCS to create remotely.
|
|
201
|
+
*/
|
|
202
|
+
sessionsRoute.post("/api/p/:slug/sessions/opencode/new", async (c) => respond(c, async () => {
|
|
203
|
+
const slug = c.req.param("slug");
|
|
204
|
+
const projectDir = requireProjectDir(slug);
|
|
205
|
+
const { title } = await parseBody(c, createOpencodeSessionSchema);
|
|
206
|
+
const directory = await primaryWorkspacePath(projectDir, slug);
|
|
207
|
+
const config = requireOpencodeConfig();
|
|
208
|
+
const created = await createOpencodeSession(config, {
|
|
209
|
+
directory,
|
|
210
|
+
...(title && { title }),
|
|
211
|
+
});
|
|
212
|
+
return upsertSession(projectDir, {
|
|
213
|
+
runtimeType: "opencode",
|
|
214
|
+
runtimeSessionId: created.runtimeSessionId,
|
|
215
|
+
status: "active",
|
|
216
|
+
metadata: { directory, ...(created.title && { title: created.title }) },
|
|
217
|
+
});
|
|
218
|
+
}, 201));
|
|
219
|
+
sessionsRoute.get("/api/p/:slug/sessions/:id", async (c) => respond(c, async () => {
|
|
220
|
+
const projectDir = requireProjectDir(c.req.param("slug"));
|
|
221
|
+
return getSession(projectDir, c.req.param("id"));
|
|
222
|
+
}));
|
|
223
|
+
sessionsRoute.patch("/api/p/:slug/sessions/:id", async (c) => respond(c, async () => {
|
|
224
|
+
const projectDir = requireProjectDir(c.req.param("slug"));
|
|
225
|
+
const input = await parseBody(c, updateSessionSchema);
|
|
226
|
+
return updateSession(projectDir, { id: c.req.param("id"), ...input });
|
|
227
|
+
}));
|
|
228
|
+
/**
|
|
229
|
+
* Sends a message to the runtime behind a session.
|
|
230
|
+
*
|
|
231
|
+
* Delivery is asymmetric by runtime, and deliberately so: opencode sessions get
|
|
232
|
+
* live injection (the running agent picks the prompt up mid-turn), while Claude
|
|
233
|
+
* Code sessions get queued delivery (the session itself drains the queue at its
|
|
234
|
+
* next hook checkpoint — Claude Code exposes no live channel to inject into).
|
|
235
|
+
* Both answer with the updated session, so the caller can tell them apart by
|
|
236
|
+
* `messageQueue` rather than by branching on `runtimeType` itself.
|
|
237
|
+
*
|
|
238
|
+
* A `reference` body field appends an ARCS-authored reference turn to the
|
|
239
|
+
* session's transcript sidecar, but only after delivery has succeeded — a
|
|
240
|
+
* failed send must never leave a dangling reference behind. The append is a
|
|
241
|
+
* swallowed no-op on failure (the message itself was already delivered).
|
|
242
|
+
*/
|
|
243
|
+
sessionsRoute.post("/api/p/:slug/sessions/:id/message", async (c) => respond(c, async () => {
|
|
244
|
+
const projectDir = requireProjectDir(c.req.param("slug"));
|
|
245
|
+
const { message, reference } = await parseBody(c, sendMessageSchema);
|
|
246
|
+
const session = await getSession(projectDir, c.req.param("id"));
|
|
247
|
+
let updated;
|
|
248
|
+
if (session.runtimeType === "claude-code") {
|
|
249
|
+
// Queued, not sent: `lastMessageAt` stays untouched until the session
|
|
250
|
+
// actually drains this at a checkpoint.
|
|
251
|
+
updated = await enqueueSessionMessage(projectDir, session.normalizedId, message);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
await sendOpencodeMessage(requireOpencodeConfig(), {
|
|
255
|
+
runtimeSessionId: session.runtimeSessionId,
|
|
256
|
+
...(sessionDirectory(session) && { directory: sessionDirectory(session) }),
|
|
257
|
+
}, message);
|
|
258
|
+
updated = await updateSession(projectDir, {
|
|
259
|
+
id: session.normalizedId,
|
|
260
|
+
lastMessageAt: new Date().toISOString(),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
// Delivery succeeded (or was accepted into the queue) — record the
|
|
264
|
+
// reference turn against the session's transcript sidecar, carrying the
|
|
265
|
+
// section/source payload verbatim so the web UI can render click-through.
|
|
266
|
+
if (reference !== undefined) {
|
|
267
|
+
await appendReferenceTurn(projectDir, session.normalizedId, {
|
|
268
|
+
text: reference.text,
|
|
269
|
+
ts: new Date().toISOString(),
|
|
270
|
+
section: reference.section,
|
|
271
|
+
source: reference.source,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return updated;
|
|
275
|
+
}));
|
|
276
|
+
/**
|
|
277
|
+
* Starts a headless `claude -p` job in one of three targeting modes and answers
|
|
278
|
+
* 202 with the write-target session — the record the run's transcript and reply
|
|
279
|
+
* land on. The 202 is the acceptance: the run proceeds out-of-band in the
|
|
280
|
+
* runner, whose exit-time write-back (T005) then settles the run — finalizing
|
|
281
|
+
* `metadata.run` with the outcome on every path and, for resume mode, mirroring
|
|
282
|
+
* the resumed session's transcript into its sidecar.
|
|
283
|
+
*
|
|
284
|
+
* - resume: the referenced session must be a claude-code session that is not
|
|
285
|
+
* currently active; the run resumes its runtime thread in the session's own
|
|
286
|
+
* directory.
|
|
287
|
+
* - oneshot: the run targets a deterministic ARCS-owned session
|
|
288
|
+
* (`arcs-oneshot-<slug>`) recreated idempotently on every call, in the
|
|
289
|
+
* project's primary workspace.
|
|
290
|
+
* - stable: the run targets a persistent ARCS-owned thread (`arcs-thread-<slug>-
|
|
291
|
+
* <uuid4>` minted once then reused) so a conversation accumulates in one
|
|
292
|
+
* sidecar; the first successful spawn seeds the thread (`--session-id`),
|
|
293
|
+
* later spawns resume it (`--resume` + `--session-id`).
|
|
294
|
+
*
|
|
295
|
+
* Modes 2/3 append the user turn (and optional reference) to the write-target
|
|
296
|
+
* sidecar immediately — the panel shows the prompt before the run ends, with
|
|
297
|
+
* delivery-first ordering (user turn before reference). Mode 1 never appends:
|
|
298
|
+
* its transcript write-back happens at exit (T005).
|
|
299
|
+
*
|
|
300
|
+
* Concurrency: one live run per write-target. The runner's beginRun is the
|
|
301
|
+
* atomic claim; the read-only isRunLive probe here answers the common
|
|
302
|
+
* overlapping case with a proper 409 before anything is appended or spawned
|
|
303
|
+
* (a rare race between probe and claim is still refused by the runner itself).
|
|
304
|
+
*/
|
|
305
|
+
sessionsRoute.post("/api/p/:slug/sessions/:id/run", async (c) => respond(c, async () => {
|
|
306
|
+
const slug = c.req.param("slug");
|
|
307
|
+
const projectDir = requireProjectDir(slug);
|
|
308
|
+
const { mode, message, threadId, reference } = await parseBody(c, runClaudeMessageSchema);
|
|
309
|
+
const session = await getSession(projectDir, c.req.param("id"));
|
|
310
|
+
let writeTarget;
|
|
311
|
+
let dir;
|
|
312
|
+
let runThreadId;
|
|
313
|
+
let firstStableSpawn = false;
|
|
314
|
+
if (mode === "resume") {
|
|
315
|
+
if (session.runtimeType !== "claude-code") {
|
|
316
|
+
throw new DagError("CLAUDE_RUN_TARGET_INVALID", `cannot resume session "${session.normalizedId}": only claude-code sessions can be run headlessly`);
|
|
317
|
+
}
|
|
318
|
+
if (session.status === "active") {
|
|
319
|
+
throw new DagError("CLAUDE_SESSION_ACTIVE", `cannot resume session "${session.normalizedId}": the session is still active`);
|
|
320
|
+
}
|
|
321
|
+
writeTarget = session;
|
|
322
|
+
dir = sessionDirectory(session) ?? (await primaryWorkspacePath(projectDir, slug));
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
dir = await primaryWorkspacePath(projectDir, slug);
|
|
326
|
+
if (mode === "oneshot") {
|
|
327
|
+
writeTarget = await upsertSession(projectDir, {
|
|
328
|
+
runtimeType: "claude-code",
|
|
329
|
+
runtimeSessionId: `arcs-oneshot-${slug}`,
|
|
330
|
+
metadata: { control: "arcs-owned", directory: dir },
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// stable — reuse the referenced session's own thread when ARCS owns
|
|
335
|
+
// it, otherwise take the payload threadId or mint a fresh one.
|
|
336
|
+
const thread = (session.metadata?.control === "arcs-owned" && session.runtimeSessionId) ||
|
|
337
|
+
threadId ||
|
|
338
|
+
`arcs-thread-${slug}-${randomUUID()}`;
|
|
339
|
+
runThreadId = thread;
|
|
340
|
+
writeTarget = await upsertSession(projectDir, {
|
|
341
|
+
runtimeType: "claude-code",
|
|
342
|
+
runtimeSessionId: thread,
|
|
343
|
+
metadata: { control: "arcs-owned", directory: dir },
|
|
344
|
+
});
|
|
345
|
+
firstStableSpawn = writeTarget.metadata?.threadInitialized !== true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// One live run per write-target — refuse before appending anything.
|
|
349
|
+
if (isRunLive(writeTarget.normalizedId)) {
|
|
350
|
+
throw new DagError("CLAUDE_RUN_IN_PROGRESS", `a claude run for "${writeTarget.normalizedId}" is already in progress`);
|
|
351
|
+
}
|
|
352
|
+
if (mode !== "resume") {
|
|
353
|
+
await appendSessionTurn(projectDir, writeTarget.normalizedId, {
|
|
354
|
+
type: "user",
|
|
355
|
+
text: message,
|
|
356
|
+
});
|
|
357
|
+
if (reference !== undefined) {
|
|
358
|
+
await appendReferenceTurn(projectDir, writeTarget.normalizedId, {
|
|
359
|
+
text: reference.text,
|
|
360
|
+
ts: new Date().toISOString(),
|
|
361
|
+
section: reference.section,
|
|
362
|
+
source: reference.source,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// NOTE: no "--cwd" flag — claude >= 2.x rejects it ("error: unknown
|
|
367
|
+
// option '--cwd'"), settling every headless run as outcome:error. The
|
|
368
|
+
// spawn applies the working directory via options.cwd below instead.
|
|
369
|
+
let argv;
|
|
370
|
+
if (mode === "resume") {
|
|
371
|
+
argv = ["-p", message, "--resume", session.runtimeSessionId, "--output-format", "json"];
|
|
372
|
+
}
|
|
373
|
+
else if (mode === "oneshot") {
|
|
374
|
+
argv = ["-p", message, "--output-format", "json"];
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
const thread = runThreadId;
|
|
378
|
+
argv = firstStableSpawn
|
|
379
|
+
? ["-p", message, "--session-id", thread, "--output-format", "json"]
|
|
380
|
+
: ["-p", message, "--resume", thread, "--session-id", thread, "--output-format", "json"];
|
|
381
|
+
}
|
|
382
|
+
// Fire-and-forget: the run proceeds out-of-band. The runner invokes the
|
|
383
|
+
// registered write-back after the child fully exits (it resolves on
|
|
384
|
+
// `close`) on every outcome path; write-back failures are swallowed by
|
|
385
|
+
// the runner, so a failed mirror/finalize never surfaces on the accepted
|
|
386
|
+
// 202. The trailing catch is defensive — the runner never rejects.
|
|
387
|
+
runClaudeJob({
|
|
388
|
+
argv,
|
|
389
|
+
cwd: dir,
|
|
390
|
+
writeTargetKey: writeTarget.normalizedId,
|
|
391
|
+
onSettled: (record) => writeBackRun(projectDir, { mode, writeTarget, firstStableSpawn }, record),
|
|
392
|
+
}).catch(() => {
|
|
393
|
+
// Best-effort — the write-back lives inside the runner's onSettled.
|
|
394
|
+
});
|
|
395
|
+
return {
|
|
396
|
+
session: writeTarget,
|
|
397
|
+
run: {
|
|
398
|
+
accepted: true,
|
|
399
|
+
mode,
|
|
400
|
+
...(mode === "stable" && { threadId: runThreadId }),
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}, 202));
|
|
404
|
+
sessionsRoute.delete("/api/p/:slug/sessions/:id", async (c) => respond(c, async () => {
|
|
405
|
+
const projectDir = requireProjectDir(c.req.param("slug"));
|
|
406
|
+
// Resolve through the index first: the sidecar filename keys on the
|
|
407
|
+
// session's canonical normalizedId, so a non-slugified route id must not
|
|
408
|
+
// re-derive the filename (deleteSession alone would orphan the sidecar).
|
|
409
|
+
const session = await getSession(projectDir, c.req.param("id"));
|
|
410
|
+
await deleteSession(projectDir, session.normalizedId);
|
|
411
|
+
try {
|
|
412
|
+
await unlink(sessionTranscriptPath(projectDir, session.normalizedId));
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
// Sidecar may not exist — a failed unlink is a swallowed no-op.
|
|
416
|
+
}
|
|
417
|
+
return { deleted: true };
|
|
418
|
+
}));
|
|
419
|
+
/**
|
|
420
|
+
* Reads the session's transcript sidecar (mirrored Claude Code lines plus
|
|
421
|
+
* ARCS-authored reference turns) into the read-model the web UI renders.
|
|
422
|
+
* An absent sidecar answers an empty transcript with `mirroredAt: null`; once
|
|
423
|
+
* the sidecar exists, `mirroredAt` is the file mtime so the UI can show how
|
|
424
|
+
* fresh the mirror is.
|
|
425
|
+
*/
|
|
426
|
+
sessionsRoute.get("/api/p/:slug/sessions/:id/transcript", async (c) => respond(c, async () => {
|
|
427
|
+
const projectDir = requireProjectDir(c.req.param("slug"));
|
|
428
|
+
const session = await getSession(projectDir, c.req.param("id"));
|
|
429
|
+
let mirroredAt = null;
|
|
430
|
+
try {
|
|
431
|
+
const info = await stat(sessionTranscriptPath(projectDir, session.normalizedId));
|
|
432
|
+
if (info.isFile())
|
|
433
|
+
mirroredAt = info.mtime.toISOString();
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// No sidecar yet — empty transcript, nothing mirrored.
|
|
437
|
+
}
|
|
438
|
+
if (mirroredAt === null)
|
|
439
|
+
return { turns: [], mirroredAt: null };
|
|
440
|
+
return { turns: await readSessionTurns(projectDir, session.normalizedId), mirroredAt };
|
|
441
|
+
}));
|
|
442
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/web-server/routes/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Local-server hardening against DNS rebinding and browser CSRF.
|
|
5
|
+
*/
|
|
6
|
+
export declare const secureLocalRequest: MiddlewareHandler;
|
|
7
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/web-server/security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAK9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKpD;AAOD;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBA8BhC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { fail } from "./respond.js";
|
|
2
|
+
const MUTATION_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
3
|
+
export function isLoopbackHost(host) {
|
|
4
|
+
const normalized = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
5
|
+
return (normalized === "localhost" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized));
|
|
6
|
+
}
|
|
7
|
+
function hostnameFromHostHeader(value) {
|
|
8
|
+
if (value.startsWith("["))
|
|
9
|
+
return value.slice(1, value.indexOf("]"));
|
|
10
|
+
return value.split(":", 1)[0] ?? "";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Local-server hardening against DNS rebinding and browser CSRF.
|
|
14
|
+
*/
|
|
15
|
+
export const secureLocalRequest = async (c, next) => {
|
|
16
|
+
const host = hostnameFromHostHeader(c.req.header("host") ?? "");
|
|
17
|
+
if (!isLoopbackHost(host)) {
|
|
18
|
+
return c.json(fail("forbidden_host", "ARCS web only accepts loopback requests"), 403);
|
|
19
|
+
}
|
|
20
|
+
const origin = c.req.header("origin");
|
|
21
|
+
if (origin) {
|
|
22
|
+
let originHost = "";
|
|
23
|
+
try {
|
|
24
|
+
originHost = new URL(origin).hostname;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return c.json(fail("forbidden_origin", "Invalid request origin"), 403);
|
|
28
|
+
}
|
|
29
|
+
if (!isLoopbackHost(originHost)) {
|
|
30
|
+
return c.json(fail("forbidden_origin", "Cross-site mutations are not allowed"), 403);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (MUTATION_METHODS.has(c.req.method)) {
|
|
34
|
+
const contentType = c.req.header("content-type") ?? "";
|
|
35
|
+
if (!contentType.toLowerCase().startsWith("application/json")) {
|
|
36
|
+
return c.json(fail("unsupported_media_type", "Mutations require Content-Type: application/json"), 415);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await next();
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/web-server/security.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AAErE,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC9D,OAAO,CACL,UAAU,KAAK,WAAW,IAAI,UAAU,KAAK,KAAK,IAAI,uBAAuB,CAAC,IAAI,CAAC,UAAU,CAAC,CAC/F,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACrE,MAAM,IAAI,GAAG,sBAAsB,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,yCAAyC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,UAAU,GAAG,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,wBAAwB,CAAC,EAAE,GAAG,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,sCAAsC,CAAC,EAAE,GAAG,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAED,IAAI,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QACvD,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC9D,OAAO,CAAC,CAAC,IAAI,CACX,IAAI,CAAC,wBAAwB,EAAE,kDAAkD,CAAC,EAClF,GAAG,CACJ,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,IAAI,EAAE,CAAC;AACf,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static client bundle serving with SPA fallback.
|
|
3
|
+
*
|
|
4
|
+
* The Vite build output lives at dist/web-client (resolved by the caller).
|
|
5
|
+
* If the bundle is missing, the server runs API-only and explains how to
|
|
6
|
+
* build the client.
|
|
7
|
+
*/
|
|
8
|
+
import type { Hono } from "hono";
|
|
9
|
+
export declare function registerStaticServing(app: Hono, root: string): void;
|
|
10
|
+
//# sourceMappingURL=static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/web-server/static.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAMjC,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAuBnE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static client bundle serving with SPA fallback.
|
|
3
|
+
*
|
|
4
|
+
* The Vite build output lives at dist/web-client (resolved by the caller).
|
|
5
|
+
* If the bundle is missing, the server runs API-only and explains how to
|
|
6
|
+
* build the client.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
11
|
+
import { fail } from "./respond.js";
|
|
12
|
+
const NOT_BUILT_HINT = "ARCS web client is not built. Run `npm run build:web` in the arcs repo (or reinstall @rryando/arcs).";
|
|
13
|
+
export function registerStaticServing(app, root) {
|
|
14
|
+
if (!existsSync(join(root, "index.html"))) {
|
|
15
|
+
app.notFound((c) => {
|
|
16
|
+
if (c.req.path.startsWith("/api/")) {
|
|
17
|
+
return c.json(fail("not_found", `No route: ${c.req.method} ${c.req.path}`), 404);
|
|
18
|
+
}
|
|
19
|
+
return c.text(NOT_BUILT_HINT, 404);
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
app.use("*", async (c, next) => {
|
|
24
|
+
if (c.req.path.startsWith("/api/"))
|
|
25
|
+
return next();
|
|
26
|
+
return serveStatic({ root })(c, next);
|
|
27
|
+
});
|
|
28
|
+
app.notFound((c) => {
|
|
29
|
+
if (c.req.path.startsWith("/api/")) {
|
|
30
|
+
return c.json(fail("not_found", `No route: ${c.req.method} ${c.req.path}`), 404);
|
|
31
|
+
}
|
|
32
|
+
// SPA fallback — client-side routing takes over.
|
|
33
|
+
return serveStatic({ path: join(root, "index.html") })(c, async () => { });
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/web-server/static.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAE7D,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,MAAM,cAAc,GAClB,sGAAsG,CAAC;AAEzG,MAAM,UAAU,qBAAqB,CAAC,GAAS,EAAE,IAAY;IAC3D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;QAC1C,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE;YACjB,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACnC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QAC7B,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,EAAE,CAAC;QAClD,OAAO,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE;QACjB,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACnF,CAAC;QACD,iDAAiD;QACjD,OAAO,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC,CAAsB,CAAC;IACjG,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Lock-safe, atomic filesystem mutations used by the web server. */
|
|
2
|
+
import { type RootMeta } from "../utils/dag.js";
|
|
3
|
+
export { writeTextAtomic } from "../utils/storage-utils.js";
|
|
4
|
+
/** Atomically replace a text file while holding its advisory lock. */
|
|
5
|
+
export declare function writeTextLocked(filePath: string, content: string): Promise<void>;
|
|
6
|
+
/** Remove a file while holding the same advisory lock writers use. */
|
|
7
|
+
export declare function removeFileLocked(filePath: string): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Acquire several file locks in deterministic lexical order to avoid deadlock.
|
|
10
|
+
*/
|
|
11
|
+
export declare function withFileLocks<T>(filePaths: string[], fn: () => T | Promise<T>): Promise<T>;
|
|
12
|
+
/**
|
|
13
|
+
* Read-modify-write root meta.json while holding one lock for the whole
|
|
14
|
+
* validation and mutation transaction.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mutateRootMetaLocked<T>(dataDir: string, mutate: (rootMeta: RootMeta) => T | Promise<T>): Promise<T>;
|
|
17
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/web-server/storage.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAIrE,OAAO,EAAE,KAAK,QAAQ,EAAgB,MAAM,iBAAiB,CAAC;AAI9D,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,sEAAsE;AACtE,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtF;AAED,sEAAsE;AACtE,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAUhG;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC,CAQZ"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Lock-safe, atomic filesystem mutations used by the web server. */
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { readRootMeta } from "../utils/dag.js";
|
|
5
|
+
import { withLock } from "../utils/file-lock.js";
|
|
6
|
+
import { writeTextAtomic } from "../utils/storage-utils.js";
|
|
7
|
+
export { writeTextAtomic } from "../utils/storage-utils.js";
|
|
8
|
+
/** Atomically replace a text file while holding its advisory lock. */
|
|
9
|
+
export async function writeTextLocked(filePath, content) {
|
|
10
|
+
await withLock(filePath, () => writeTextAtomic(filePath, content));
|
|
11
|
+
}
|
|
12
|
+
/** Remove a file while holding the same advisory lock writers use. */
|
|
13
|
+
export async function removeFileLocked(filePath) {
|
|
14
|
+
await withLock(filePath, () => unlink(filePath).catch(() => { }));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Acquire several file locks in deterministic lexical order to avoid deadlock.
|
|
18
|
+
*/
|
|
19
|
+
export async function withFileLocks(filePaths, fn) {
|
|
20
|
+
const ordered = [...new Set(filePaths)].sort();
|
|
21
|
+
const acquire = async (index) => {
|
|
22
|
+
const filePath = ordered[index];
|
|
23
|
+
if (filePath === undefined)
|
|
24
|
+
return fn();
|
|
25
|
+
return withLock(filePath, () => acquire(index + 1));
|
|
26
|
+
};
|
|
27
|
+
return acquire(0);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read-modify-write root meta.json while holding one lock for the whole
|
|
31
|
+
* validation and mutation transaction.
|
|
32
|
+
*/
|
|
33
|
+
export async function mutateRootMetaLocked(dataDir, mutate) {
|
|
34
|
+
const rootMetaPath = resolve(dataDir, "meta.json");
|
|
35
|
+
return withLock(rootMetaPath, async () => {
|
|
36
|
+
const rootMeta = await readRootMeta(dataDir);
|
|
37
|
+
const result = await mutate(rootMeta);
|
|
38
|
+
await writeTextAtomic(rootMetaPath, `${JSON.stringify(rootMeta, null, 2)}\n`);
|
|
39
|
+
return result;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=storage.js.map
|