@rryando/arcs 3.10.0 → 4.0.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 +76 -0
- package/dist/cli/commands/hooks.d.ts.map +1 -0
- package/dist/cli/commands/hooks.js +436 -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 +66 -0
- package/dist/cli/commands/web.js.map +1 -0
- package/dist/cli/config.d.ts +46 -3
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +95 -38
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/instructions.d.ts +32 -7
- package/dist/cli/instructions.d.ts.map +1 -1
- package/dist/cli/instructions.js +99 -34
- 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 +230 -91
- 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/shared/session-vocabulary.d.ts +88 -0
- package/dist/shared/session-vocabulary.d.ts.map +1 -0
- package/dist/shared/session-vocabulary.js +114 -0
- package/dist/shared/session-vocabulary.js.map +1 -0
- 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 +211 -0
- package/dist/utils/claude-code-hook-install.js.map +1 -0
- package/dist/utils/claude-transcript.d.ts +206 -0
- package/dist/utils/claude-transcript.d.ts.map +1 -0
- package/dist/utils/claude-transcript.js +384 -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/git.d.ts +21 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +65 -1
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/hook-contract.d.ts +36 -0
- package/dist/utils/hook-contract.d.ts.map +1 -0
- package/dist/utils/hook-contract.js +35 -0
- package/dist/utils/hook-contract.js.map +1 -0
- package/dist/utils/hook-token-store.d.ts +61 -0
- package/dist/utils/hook-token-store.d.ts.map +1 -0
- package/dist/utils/hook-token-store.js +86 -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 +339 -0
- package/dist/utils/session-store.d.ts.map +1 -0
- package/dist/utils/session-store.js +577 -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-CTyf_XXQ.js +1 -0
- package/dist/web-client/assets/MarkdownEditor-af2vwQOX.js +77 -0
- package/dist/web-client/assets/abnfDiagram-VRR7QNED-CdxcKX9t.js +1 -0
- package/dist/web-client/assets/arc-Bfaogrm1.js +1 -0
- package/dist/web-client/assets/architecture-TIHT7OUA-Bt2-DmfV.js +1 -0
- package/dist/web-client/assets/architectureDiagram-ZJ3FMSHR-DOISDv6o.js +36 -0
- package/dist/web-client/assets/array-BifhSqXX.js +1 -0
- package/dist/web-client/assets/blockDiagram-677ZJIJ3-DKwtbttM.js +132 -0
- package/dist/web-client/assets/c4Diagram-LMCZKHZV-CilqK-Mm.js +10 -0
- package/dist/web-client/assets/channel-C2mb9np_.js +1 -0
- package/dist/web-client/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/web-client/assets/chunk-32BRIVSS-C79m1mkG.js +1 -0
- package/dist/web-client/assets/chunk-52WLFC77-C6WelGWJ.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-DLg7ryWI.js +1 -0
- package/dist/web-client/assets/chunk-EX3LRPZG-DjWgo4gL.js +231 -0
- package/dist/web-client/assets/chunk-FWX5IMBZ-BgS9p_zy.js +2 -0
- package/dist/web-client/assets/chunk-HOUHSVGY-DuVR7dZO.js +1 -0
- package/dist/web-client/assets/chunk-ICXQ74PX-Y8DlnIJM.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-BEM3QgeD.js +88 -0
- package/dist/web-client/assets/chunk-OGEWGWER-DlM8LxGr.js +1 -0
- package/dist/web-client/assets/chunk-PUDLZKDR-c6cqNVTx.js +156 -0
- package/dist/web-client/assets/chunk-Q4XR5HBZ-C5lNmcka.js +70 -0
- package/dist/web-client/assets/chunk-RYQCIY6F-np2RyIY1.js +1 -0
- package/dist/web-client/assets/chunk-V7JOEXUC-nSswxvSG.js +206 -0
- package/dist/web-client/assets/chunk-VAUOI2AC-CqJkCkT0.js +1 -0
- package/dist/web-client/assets/chunk-VR4S4FIN-D3pFchin.js +1 -0
- package/dist/web-client/assets/chunk-WYO6CB5R-BI9c-NzI.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-CKTF2kLR.js +62 -0
- package/dist/web-client/assets/chunk-ZIRB5QZD-BpeS1aZ7.js +32 -0
- package/dist/web-client/assets/classDiagram-OUVF2IWQ-CPd_w3Zr.js +1 -0
- package/dist/web-client/assets/classDiagram-v2-EOCWNBFH-CPd_w3Zr.js +1 -0
- package/dist/web-client/assets/cose-bilkent-JH36ORCC-BebHqzgp.js +1 -0
- package/dist/web-client/assets/cynefin-VYW2F7L2-BrJrnfh1.js +1 -0
- package/dist/web-client/assets/cynefinDiagram-TSTJHNR4-9jYzPWJZ.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-B705Djpu.js +4 -0
- package/dist/web-client/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/web-client/assets/diagram-FQU43EPY-BfQAlUlJ.js +3 -0
- package/dist/web-client/assets/diagram-G47NLZAW-s1YDe06A.js +24 -0
- package/dist/web-client/assets/diagram-NH7WQ7WH-Dy3z11Hc.js +24 -0
- package/dist/web-client/assets/diagram-OA4YK3LP-Bius2xUN.js +30 -0
- package/dist/web-client/assets/diagram-WEI45ONY-D7x4VcHM.js +41 -0
- package/dist/web-client/assets/dist-B_J_HbC0.js +1 -0
- package/dist/web-client/assets/ebnfDiagram-CCIWWBDH-CBg1xrmD.js +1 -0
- package/dist/web-client/assets/erDiagram-Q63AITRT-CJRvTFvd.js +85 -0
- package/dist/web-client/assets/eventmodeling-45OFAUF4-CTKRC2Yj.js +1 -0
- package/dist/web-client/assets/flowDiagram-23GEKE2U-CaZjFyU1.js +1 -0
- package/dist/web-client/assets/ganttDiagram-NO4QXBWP-C_LsypZ4.js +292 -0
- package/dist/web-client/assets/gitGraph-TEB2WS4Q-Dx2XxdGk.js +1 -0
- package/dist/web-client/assets/gitGraphDiagram-IHSO6WYX-DiKWkGWQ.js +106 -0
- package/dist/web-client/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/web-client/assets/index-CYwhkPtc.js +60 -0
- package/dist/web-client/assets/index-wSzUPvml.css +2 -0
- package/dist/web-client/assets/info-DKCQHKI2-DORwHenK.js +1 -0
- package/dist/web-client/assets/infoDiagram-FWYZ7A6U-CQecXS1E.js +2 -0
- package/dist/web-client/assets/init-C-OQMol4.js +1 -0
- package/dist/web-client/assets/ishikawaDiagram-FXEZZL3T-CWhj60Zp.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-Cj3z2U8u.js +139 -0
- package/dist/web-client/assets/kanban-definition-HUTT4EX6-DFmBRenP.js +89 -0
- package/dist/web-client/assets/katex-DolUETbr.js +257 -0
- package/dist/web-client/assets/line-C_Hxz9xb.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-E3xRdzPn.js +7 -0
- package/dist/web-client/assets/mermaid.core-C2_CcotH.js +11 -0
- package/dist/web-client/assets/mindmap-definition-LN4V7U3C-Bs7VxPid.js +96 -0
- package/dist/web-client/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/web-client/assets/packet-7NZHBO7P-D2nqso7i.js +1 -0
- package/dist/web-client/assets/path-COt_16Va.js +1 -0
- package/dist/web-client/assets/pegDiagram-2B236MQR-Cepo1sIu.js +1 -0
- package/dist/web-client/assets/pie-RZYD4A2V-Sx0IGbXt.js +1 -0
- package/dist/web-client/assets/pieDiagram-ENE6RG2P-D-PMDNhk.js +39 -0
- package/dist/web-client/assets/quadrantDiagram-ABIIQ3AL-BNGC7Fms.js +7 -0
- package/dist/web-client/assets/radar-I7S5WNFK-CFf_-2OC.js +1 -0
- package/dist/web-client/assets/railroad-3IZDKUUU-BvZeVRGh.js +1 -0
- package/dist/web-client/assets/railroad-abnf-AHOZXSZD-DnGh6x2O.js +1 -0
- package/dist/web-client/assets/railroad-ebnf-EBAXGLYW-CGfgCoaE.js +1 -0
- package/dist/web-client/assets/railroad-peg-LSFZ7HO6-BzQxYdqp.js +1 -0
- package/dist/web-client/assets/railroadDiagram-RFXS5EU6-D0iK_k84.js +1 -0
- package/dist/web-client/assets/requirementDiagram-TGXJPOKE-BPdsw9tz.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-maA6ukOJ.js +40 -0
- package/dist/web-client/assets/sequenceDiagram-DBY2YBRQ-DKmAjmgr.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-esxmzk3w.js +1 -0
- package/dist/web-client/assets/stateDiagram-v2-6OUMAXLB-hcBqlOLc.js +1 -0
- package/dist/web-client/assets/swimlanes-5IMT3BWC-DFd-wGBv.js +2 -0
- package/dist/web-client/assets/swimlanesDiagram-G3AALYLV-DbSrPBYr.js +8 -0
- package/dist/web-client/assets/timeline-definition-FHXFAJF6-C7SG8MQH.js +120 -0
- package/dist/web-client/assets/treeView-QDETBFTQ-M0rhxbs2.js +1 -0
- package/dist/web-client/assets/treemap-6X3UGDF4-BQfkQxSh.js +1 -0
- package/dist/web-client/assets/vennDiagram-L72KCM5P-CyIlb8ju.js +34 -0
- package/dist/web-client/assets/wardley-OPB4EBWU-EE-7q8M1.js +1 -0
- package/dist/web-client/assets/wardleyDiagram-EHGQE667-bY8XdYYS.js +78 -0
- package/dist/web-client/assets/xychartDiagram-FW5EYKEG-AhuXJ3de.js +7 -0
- package/dist/web-client/index.html +15 -0
- package/dist/web-server/app.d.ts +16 -0
- package/dist/web-server/app.d.ts.map +1 -0
- package/dist/web-server/app.js +77 -0
- package/dist/web-server/app.js.map +1 -0
- package/dist/web-server/claude-runner.d.ts +188 -0
- package/dist/web-server/claude-runner.d.ts.map +1 -0
- package/dist/web-server/claude-runner.js +599 -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 +50 -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/permission-policy.d.ts +46 -0
- package/dist/web-server/permission-policy.d.ts.map +1 -0
- package/dist/web-server/permission-policy.js +96 -0
- package/dist/web-server/permission-policy.js.map +1 -0
- package/dist/web-server/prompt-assembly.d.ts +454 -0
- package/dist/web-server/prompt-assembly.d.ts.map +1 -0
- package/dist/web-server/prompt-assembly.js +1122 -0
- package/dist/web-server/prompt-assembly.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 +44 -0
- package/dist/web-server/routes/hook-events.d.ts.map +1 -0
- package/dist/web-server/routes/hook-events.js +320 -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 +18 -0
- package/dist/web-server/routes/sessions.d.ts.map +1 -0
- package/dist/web-server/routes/sessions.js +1298 -0
- package/dist/web-server/routes/sessions.js.map +1 -0
- package/dist/web-server/routes/workspace.d.ts +27 -0
- package/dist/web-server/routes/workspace.d.ts.map +1 -0
- package/dist/web-server/routes/workspace.js +280 -0
- package/dist/web-server/routes/workspace.js.map +1 -0
- package/dist/web-server/run-event-log.d.ts +167 -0
- package/dist/web-server/run-event-log.d.ts.map +1 -0
- package/dist/web-server/run-event-log.js +468 -0
- package/dist/web-server/run-event-log.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/session-reconciler.d.ts +162 -0
- package/dist/web-server/session-reconciler.d.ts.map +1 -0
- package/dist/web-server/session-reconciler.js +363 -0
- package/dist/web-server/session-reconciler.js.map +1 -0
- package/dist/web-server/static.d.ts +17 -0
- package/dist/web-server/static.d.ts.map +1 -0
- package/dist/web-server/static.js +79 -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/dist/web-server/web-auth.d.ts +17 -0
- package/dist/web-server/web-auth.d.ts.map +1 -0
- package/dist/web-server/web-auth.js +33 -0
- package/dist/web-server/web-auth.js.map +1 -0
- package/dist/web-server/web-token.d.ts +39 -0
- package/dist/web-server/web-token.d.ts.map +1 -0
- package/dist/web-server/web-token.js +71 -0
- package/dist/web-server/web-token.js.map +1 -0
- package/opencode/arcs/bundle-runtime.json +5 -13
- package/opencode/arcs/manifest.json +215 -151
- 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 +171 -0
- package/scripts/deploy-claudecode-bundle.mjs +206 -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,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-run stdout event log — the durable spine of a headless `claude -p` run.
|
|
3
|
+
*
|
|
4
|
+
* Owns `sessions/{normalizedId}.run-{runId}.events.jsonl`: every byte the child
|
|
5
|
+
* writes to stdout lands here VERBATIM, one file line per stdout line, BEFORE
|
|
6
|
+
* anything parses it. That ordering is the whole point — the log is the source
|
|
7
|
+
* of truth and the runner's NDJSON reader is merely its first consumer, so a
|
|
8
|
+
* line the reader cannot parse (drifted wire format, truncated JSON, an event
|
|
9
|
+
* type that did not exist when this shipped) is still on disk, and a process
|
|
10
|
+
* that dies mid-run still leaves behind everything that arrived.
|
|
11
|
+
*
|
|
12
|
+
* Writes are synchronous (`writeSync` against an fd opened with "a") for
|
|
13
|
+
* exactly that reason: an async queue would only ever have *issued* the write
|
|
14
|
+
* before the parse, and a hard kill would lose whatever was still in flight.
|
|
15
|
+
* The chunks are small and few (one per stdout chunk of a single child), so the
|
|
16
|
+
* event-loop cost is a rounding error against a run measured in minutes.
|
|
17
|
+
*
|
|
18
|
+
* The `runId` in the filename is the SAME id the route persists as
|
|
19
|
+
* `currentRunId`, so the log's name and the session record can never disagree
|
|
20
|
+
* about which run produced it.
|
|
21
|
+
*
|
|
22
|
+
* Total by construction, like the reader it feeds: nothing here throws. A log
|
|
23
|
+
* that cannot be opened or written reports itself through `RunEventLogStats`
|
|
24
|
+
* and the run proceeds untouched — a logging failure must never fail a run.
|
|
25
|
+
* Reporting is the whole point: a log that hit its size cap or lost bytes to a
|
|
26
|
+
* short write says so on `truncated`, which rides all the way out to
|
|
27
|
+
* `metadata.run.eventLogTruncated`. A silent fallback here would make an empty
|
|
28
|
+
* log and a capped one look identical to every consumer downstream.
|
|
29
|
+
*
|
|
30
|
+
* At settle the log folds down into the transcript sidecar (assistant text plus
|
|
31
|
+
* one turn per `tool_use`) through `appendSessionTurn`, and older logs for the
|
|
32
|
+
* same session are pruned so the sessions dir cannot grow without limit.
|
|
33
|
+
*/
|
|
34
|
+
import { closeSync, mkdirSync, openSync, writeSync } from "node:fs";
|
|
35
|
+
import { readdir, readFile, stat, unlink } from "node:fs/promises";
|
|
36
|
+
import { dirname, join } from "node:path";
|
|
37
|
+
import { appendSessionTurn, readSessionTurns } from "../utils/claude-transcript.js";
|
|
38
|
+
import { normalizeIdentifier } from "../utils/slug.js";
|
|
39
|
+
/**
|
|
40
|
+
* Ceiling on one run's log — a runaway child cannot fill the disk.
|
|
41
|
+
*
|
|
42
|
+
* The finished file is never LARGER than this, terminator included: the writer
|
|
43
|
+
* refuses a chunk that would take it TO the cap (`>=`, not `>`), leaving the one
|
|
44
|
+
* byte `flush()` may still need. The off-by-one that leaves matters because
|
|
45
|
+
* `foldRunEventLog` refuses to read anything past this size — a writer allowed
|
|
46
|
+
* to land on exactly the cap and then add a terminator produces a file its own
|
|
47
|
+
* fold silently discards.
|
|
48
|
+
*
|
|
49
|
+
* Reaching it does not close the log: only the crossing chunk is refused, later
|
|
50
|
+
* chunks that still fit are still written, and `RunEventLogStats.truncated`
|
|
51
|
+
* reports that bytes are missing. The file always ends on a line boundary, so an
|
|
52
|
+
* offset-based tail never reads half a record.
|
|
53
|
+
*/
|
|
54
|
+
export const RUN_EVENT_LOG_MAX_BYTES = 32 * 1024 * 1024;
|
|
55
|
+
/**
|
|
56
|
+
* How many event logs a single session keeps. Count-based rather than
|
|
57
|
+
* age-based: a count is a hard bound (N logs per session, at most
|
|
58
|
+
* RUN_EVENT_LOG_MAX_BYTES each), while an age window still admits unbounded
|
|
59
|
+
* growth from a session that runs in a tight loop.
|
|
60
|
+
*/
|
|
61
|
+
export const RUN_EVENT_LOG_RETENTION = 5;
|
|
62
|
+
const LOG_SUFFIX = ".events.jsonl";
|
|
63
|
+
const NEWLINE = 0x0a;
|
|
64
|
+
/** The only byte the log ever adds of its own: a line terminator. */
|
|
65
|
+
const TERMINATOR = Buffer.from("\n", "utf-8");
|
|
66
|
+
/**
|
|
67
|
+
* Filename-safe form of a run id. The id reaches a path, so it is sanitized
|
|
68
|
+
* rather than trusted: anything outside `[A-Za-z0-9_-]` (`/`, `.`, `..`) becomes
|
|
69
|
+
* `-` before it can traverse out of the sessions dir.
|
|
70
|
+
*/
|
|
71
|
+
export function runEventLogSegment(runId) {
|
|
72
|
+
const safe = runId.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 64);
|
|
73
|
+
return safe === "" ? "unknown" : safe;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Log path for one run. Keys on the session's canonical `normalizedId` exactly
|
|
77
|
+
* as the transcript sidecar does, so both files for a session sort together and
|
|
78
|
+
* neither can be derived from a raw route id.
|
|
79
|
+
*/
|
|
80
|
+
export function runEventLogPath(projectDir, sessionId, runId) {
|
|
81
|
+
const name = `${normalizeIdentifier(sessionId)}.run-${runEventLogSegment(runId)}${LOG_SUFFIX}`;
|
|
82
|
+
return join(projectDir, "sessions", name);
|
|
83
|
+
}
|
|
84
|
+
/** The handle a run with no log target gets — every method is a no-op. */
|
|
85
|
+
const NOOP_HANDLE = {
|
|
86
|
+
path: "",
|
|
87
|
+
push() { },
|
|
88
|
+
flush() { },
|
|
89
|
+
close() {
|
|
90
|
+
return { lines: 0, bytes: 0, truncated: false };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Opens (creating) the run's log and returns a write-through handle.
|
|
95
|
+
*
|
|
96
|
+
* The file is created up front, so a run that produces no stdout at all — a
|
|
97
|
+
* child killed at its deadline before it spoke, an immediate non-zero exit —
|
|
98
|
+
* still leaves the log behind. `undefined` target yields the no-op handle, which
|
|
99
|
+
* is how the runner stays usable without a project dir (tests, and any caller
|
|
100
|
+
* that does not want a log).
|
|
101
|
+
*/
|
|
102
|
+
export function openRunEventLog(target) {
|
|
103
|
+
if (target === undefined)
|
|
104
|
+
return NOOP_HANDLE;
|
|
105
|
+
const path = runEventLogPath(target.projectDir, target.sessionId, target.runId);
|
|
106
|
+
let fd = null;
|
|
107
|
+
let lines = 0;
|
|
108
|
+
let bytes = 0;
|
|
109
|
+
let truncated = false;
|
|
110
|
+
let closed = false;
|
|
111
|
+
let endsWithNewline = true;
|
|
112
|
+
/**
|
|
113
|
+
* Bytes were dropped part-way through a record, so the stream now continues
|
|
114
|
+
* INSIDE a line this file does not have. Appending those bytes would splice
|
|
115
|
+
* the tail of a record the log never took onto whatever precedes it and emit
|
|
116
|
+
* a line the child never wrote, so they are dropped until the next newline.
|
|
117
|
+
*/
|
|
118
|
+
let resyncing = false;
|
|
119
|
+
let error;
|
|
120
|
+
const note = (err) => {
|
|
121
|
+
if (error === undefined)
|
|
122
|
+
error = String(err);
|
|
123
|
+
};
|
|
124
|
+
try {
|
|
125
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
126
|
+
// "a" — the kernel appends at every write, so nothing here has to track a
|
|
127
|
+
// file position and a concurrent reader (the tail) can never see a hole.
|
|
128
|
+
fd = openSync(path, "a");
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
note(err);
|
|
132
|
+
}
|
|
133
|
+
const stats = () => ({
|
|
134
|
+
lines,
|
|
135
|
+
bytes,
|
|
136
|
+
truncated,
|
|
137
|
+
...(error !== undefined && { error }),
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* Appends `buf` whole, returning how many of its bytes actually landed.
|
|
141
|
+
*
|
|
142
|
+
* `writeSync` may return SHORT — a signal interrupting the syscall (EINTR), a
|
|
143
|
+
* filesystem that ran out of room mid-buffer (ENOSPC) — and its return value
|
|
144
|
+
* is the only place that ever shows. Discarding it emits half a record while
|
|
145
|
+
* counting a whole one, which breaks "always ends on a line boundary" exactly
|
|
146
|
+
* when the disk is full. So the loop keeps writing while the fd makes
|
|
147
|
+
* progress, and reports precisely how far it got. `n <= 0` ends the loop
|
|
148
|
+
* rather than spinning on an fd that has stopped accepting bytes.
|
|
149
|
+
*/
|
|
150
|
+
const writeAll = (handle, buf) => {
|
|
151
|
+
let written = 0;
|
|
152
|
+
while (written < buf.length) {
|
|
153
|
+
let n;
|
|
154
|
+
try {
|
|
155
|
+
n = writeSync(handle, buf, written, buf.length - written);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
note(err);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (n <= 0)
|
|
162
|
+
break;
|
|
163
|
+
written += n;
|
|
164
|
+
}
|
|
165
|
+
return written;
|
|
166
|
+
};
|
|
167
|
+
/** Counts what landed: bytes, whole lines, and where the file now ends. */
|
|
168
|
+
const account = (buf) => {
|
|
169
|
+
if (buf.length === 0)
|
|
170
|
+
return;
|
|
171
|
+
bytes += buf.length;
|
|
172
|
+
// Native indexOf rather than a per-byte loop: a megabyte chunk must not
|
|
173
|
+
// cost a million iterations on the event loop.
|
|
174
|
+
for (let at = buf.indexOf(NEWLINE); at !== -1; at = buf.indexOf(NEWLINE, at + 1)) {
|
|
175
|
+
lines += 1;
|
|
176
|
+
}
|
|
177
|
+
endsWithNewline = buf[buf.length - 1] === NEWLINE;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Closes a line the child left open. The only byte the log adds of its own,
|
|
181
|
+
* and only when the file would otherwise not end on a line boundary — an
|
|
182
|
+
* offset-based tail would mis-frame the last record without it.
|
|
183
|
+
*
|
|
184
|
+
* Runs at flush() AND the instant a chunk is refused or lands short, not just
|
|
185
|
+
* at close: after a gap in the stream the next accepted chunk must start its
|
|
186
|
+
* own line rather than extend the fragment before the gap.
|
|
187
|
+
*
|
|
188
|
+
* The terminator write can itself fail, and failing SILENTLY is how a
|
|
189
|
+
* fabricated line survives every other guard here: the file is left
|
|
190
|
+
* un-line-closed with `resyncing` unset, so an fd that recovers splices the
|
|
191
|
+
* next chunk onto the open fragment. A terminator that does not land therefore
|
|
192
|
+
* sets BOTH flags — `truncated` because the file is no longer line-framed, and
|
|
193
|
+
* `resyncing` because nothing may extend that fragment ever again.
|
|
194
|
+
*/
|
|
195
|
+
const terminate = (handle) => {
|
|
196
|
+
if (endsWithNewline || bytes === 0)
|
|
197
|
+
return;
|
|
198
|
+
if (writeAll(handle, TERMINATOR) !== TERMINATOR.length) {
|
|
199
|
+
truncated = true;
|
|
200
|
+
resyncing = true;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
account(TERMINATOR);
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
path,
|
|
207
|
+
push(chunk) {
|
|
208
|
+
if (fd === null || closed)
|
|
209
|
+
return;
|
|
210
|
+
let buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8");
|
|
211
|
+
if (buf.length === 0)
|
|
212
|
+
return;
|
|
213
|
+
if (resyncing) {
|
|
214
|
+
const at = buf.indexOf(NEWLINE);
|
|
215
|
+
// Still inside the record whose bytes were dropped — all of it belongs
|
|
216
|
+
// to that record, none of it is a line this file may claim to hold.
|
|
217
|
+
if (at === -1)
|
|
218
|
+
return;
|
|
219
|
+
buf = buf.subarray(at + 1);
|
|
220
|
+
resyncing = false;
|
|
221
|
+
// The stream is back at a record start, but the FILE may not be: a
|
|
222
|
+
// terminator that failed AT the gap left an open fragment behind, and
|
|
223
|
+
// knowing where the stream resumed does nothing about it. Close it here,
|
|
224
|
+
// before any of these bytes can land on it — the two questions are
|
|
225
|
+
// different and only this one keeps the next whole record off the
|
|
226
|
+
// fragment. A no-op whenever the file already ends on a boundary.
|
|
227
|
+
terminate(fd);
|
|
228
|
+
// Still open: the fd will not take even one byte, so stay resynced (a
|
|
229
|
+
// failed terminate re-arms it) and drop this record too. A fabricated
|
|
230
|
+
// line is not detectable downstream; a missing one is.
|
|
231
|
+
if (resyncing)
|
|
232
|
+
return;
|
|
233
|
+
if (buf.length === 0)
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (bytes + buf.length >= RUN_EVENT_LOG_MAX_BYTES) {
|
|
237
|
+
// The CROSSING chunk is refused whole — never half a record — but the
|
|
238
|
+
// log stays open. Returning early here for the rest of the run turned
|
|
239
|
+
// one oversized chunk into a 0-byte file; now every later chunk that
|
|
240
|
+
// still fits is still kept, and `truncated` is what says the file is no
|
|
241
|
+
// longer the whole stream. `>=` reserves the byte terminate() may add,
|
|
242
|
+
// so the finished file never exceeds the size the fold agrees to read.
|
|
243
|
+
truncated = true;
|
|
244
|
+
// The dropped bytes end mid-record unless the chunk's own last byte was
|
|
245
|
+
// a terminator; only then does the next chunk start a fresh line.
|
|
246
|
+
resyncing = buf[buf.length - 1] !== NEWLINE;
|
|
247
|
+
terminate(fd);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const written = writeAll(fd, buf);
|
|
251
|
+
account(buf.subarray(0, written));
|
|
252
|
+
if (written === buf.length)
|
|
253
|
+
return;
|
|
254
|
+
// A short write: this record is on disk in part only and the stream
|
|
255
|
+
// continues from bytes that are gone. Same treatment as the cap — say the
|
|
256
|
+
// file is incomplete, close the half-written line, and skip to the next
|
|
257
|
+
// boundary instead of splicing the next chunk onto a fragment.
|
|
258
|
+
truncated = true;
|
|
259
|
+
// The lost bytes are `buf[written..]`, so the stream stands exactly where
|
|
260
|
+
// it does after a refusal: at `buf`'s last byte. Same rule as the cap,
|
|
261
|
+
// deliberately — hardcoding a resync here is safe in direction but throws
|
|
262
|
+
// away a whole record that begins at a record boundary, for nothing.
|
|
263
|
+
resyncing = buf[buf.length - 1] !== NEWLINE;
|
|
264
|
+
terminate(fd);
|
|
265
|
+
},
|
|
266
|
+
flush() {
|
|
267
|
+
if (fd === null || closed)
|
|
268
|
+
return;
|
|
269
|
+
terminate(fd);
|
|
270
|
+
},
|
|
271
|
+
close() {
|
|
272
|
+
if (fd !== null && !closed) {
|
|
273
|
+
try {
|
|
274
|
+
closeSync(fd);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
note(err);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
closed = true;
|
|
281
|
+
return stats();
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const EMPTY_FOLD = {
|
|
286
|
+
appended: 0,
|
|
287
|
+
alreadyFolded: false,
|
|
288
|
+
assistantTextFolded: false,
|
|
289
|
+
};
|
|
290
|
+
function isBlock(value) {
|
|
291
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Turns one stream-json `assistant` event yields, in content-block order.
|
|
295
|
+
*
|
|
296
|
+
* Consecutive text blocks coalesce into one turn; every `tool_use` block gets
|
|
297
|
+
* its own turn (unlike the transcript mirror, which keeps only the first) so a
|
|
298
|
+
* message that fans out to three tools reads as three rows. Only completed
|
|
299
|
+
* `assistant` messages are folded: `stream_event` deltas repeat their text and
|
|
300
|
+
* the terminal `result` repeats the final message, so both would duplicate.
|
|
301
|
+
*/
|
|
302
|
+
function foldAssistantEvent(event) {
|
|
303
|
+
const message = event.message;
|
|
304
|
+
if (!isBlock(message))
|
|
305
|
+
return [];
|
|
306
|
+
const content = message.content;
|
|
307
|
+
if (typeof content === "string")
|
|
308
|
+
return content === "" ? [] : [{ text: content }];
|
|
309
|
+
if (!Array.isArray(content))
|
|
310
|
+
return [];
|
|
311
|
+
const turns = [];
|
|
312
|
+
let text = "";
|
|
313
|
+
const flushText = () => {
|
|
314
|
+
if (text === "")
|
|
315
|
+
return;
|
|
316
|
+
turns.push({ text });
|
|
317
|
+
text = "";
|
|
318
|
+
};
|
|
319
|
+
for (const block of content) {
|
|
320
|
+
if (!isBlock(block))
|
|
321
|
+
continue;
|
|
322
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
323
|
+
text += block.text;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (block.type === "tool_use" && typeof block.name === "string") {
|
|
327
|
+
flushText();
|
|
328
|
+
turns.push({ text: "", tool: { name: block.name } });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
flushText();
|
|
332
|
+
return turns;
|
|
333
|
+
}
|
|
334
|
+
/** Every turn the log implies, in the order the child emitted them. */
|
|
335
|
+
function foldTurns(raw) {
|
|
336
|
+
const turns = [];
|
|
337
|
+
for (const line of raw.split("\n")) {
|
|
338
|
+
const text = (line.endsWith("\r") ? line.slice(0, -1) : line).trim();
|
|
339
|
+
if (text === "")
|
|
340
|
+
continue;
|
|
341
|
+
let value;
|
|
342
|
+
try {
|
|
343
|
+
value = JSON.parse(text);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
continue; // unparsable lines stay in the log; they just fold to nothing
|
|
347
|
+
}
|
|
348
|
+
if (!isBlock(value) || value.type !== "assistant")
|
|
349
|
+
continue;
|
|
350
|
+
turns.push(...foldAssistantEvent(value));
|
|
351
|
+
}
|
|
352
|
+
return turns;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Folds a run's event log down into the session's transcript sidecar: assistant
|
|
356
|
+
* text plus one turn per `tool_use`, appended through `appendSessionTurn` in
|
|
357
|
+
* stream order, each tagged with the run id.
|
|
358
|
+
*
|
|
359
|
+
* Idempotence mechanism: the fold's OWN OUTPUT is the marker. Every appended
|
|
360
|
+
* turn carries `run: <runId>`, and a fold refuses as soon as the sidecar holds
|
|
361
|
+
* any turn tagged with that id. This was chosen over a separate marker (a
|
|
362
|
+
* `foldedAt` on the run record, a `.folded` sentinel file) precisely because a
|
|
363
|
+
* separate marker has a window: a crash between "turns appended" and "marker
|
|
364
|
+
* written" would re-fold and duplicate the whole run on the next settle. Here
|
|
365
|
+
* there is no window — the evidence and the mark are the same bytes. The cost
|
|
366
|
+
* is at-most-once rather than exactly-once under a crash MID-fold (the turns
|
|
367
|
+
* already appended are tagged, so the remainder is never completed), which is
|
|
368
|
+
* the safe direction: a short transcript, never a doubled one.
|
|
369
|
+
*
|
|
370
|
+
* Never throws — a fold that cannot read the log or write the sidecar reports
|
|
371
|
+
* an empty result and the settle continues.
|
|
372
|
+
*/
|
|
373
|
+
export async function foldRunEventLog(projectDir, sessionId, runId) {
|
|
374
|
+
try {
|
|
375
|
+
const path = runEventLogPath(projectDir, sessionId, runId);
|
|
376
|
+
const info = await stat(path);
|
|
377
|
+
if (!info.isFile() || info.size > RUN_EVENT_LOG_MAX_BYTES)
|
|
378
|
+
return EMPTY_FOLD;
|
|
379
|
+
// The mark IS the data: any turn already tagged with this run id means a
|
|
380
|
+
// previous fold got there first.
|
|
381
|
+
const existing = await readSessionTurns(projectDir, sessionId);
|
|
382
|
+
const tagged = existing.filter((turn) => turn.run === runId);
|
|
383
|
+
if (tagged.length > 0) {
|
|
384
|
+
return {
|
|
385
|
+
appended: 0,
|
|
386
|
+
alreadyFolded: true,
|
|
387
|
+
assistantTextFolded: tagged.some((turn) => turn.type === "assistant" && turn.text !== ""),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const turns = foldTurns(await readFile(path, "utf-8"));
|
|
391
|
+
if (turns.length === 0)
|
|
392
|
+
return EMPTY_FOLD;
|
|
393
|
+
let appended = 0;
|
|
394
|
+
let assistantTextFolded = false;
|
|
395
|
+
for (const turn of turns) {
|
|
396
|
+
// Sequential on purpose: `appendSessionTurn` mints its negative id from
|
|
397
|
+
// what is already on disk, so concurrent appends would collide.
|
|
398
|
+
await appendSessionTurn(projectDir, sessionId, {
|
|
399
|
+
type: "assistant",
|
|
400
|
+
text: turn.text,
|
|
401
|
+
run: runId,
|
|
402
|
+
...(turn.tool !== undefined && { tool: turn.tool }),
|
|
403
|
+
});
|
|
404
|
+
appended += 1;
|
|
405
|
+
if (turn.tool === undefined && turn.text !== "")
|
|
406
|
+
assistantTextFolded = true;
|
|
407
|
+
}
|
|
408
|
+
return { appended, alreadyFolded: false, assistantTextFolded };
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Missing log, unreadable sidecar, a full disk — all fold to nothing.
|
|
412
|
+
return EMPTY_FOLD;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
// Retention
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
/**
|
|
419
|
+
* Keeps the newest `keep` event logs for one session and deletes the rest.
|
|
420
|
+
*
|
|
421
|
+
* Called at EVERY settle (after the fold), so the log that just settled is
|
|
422
|
+
* always among the survivors and the sessions dir is bounded at `keep` logs per
|
|
423
|
+
* session no matter how many runs a session accumulates. "Every settle" means
|
|
424
|
+
* the startup orphan sweep too (`settleOrphanedRuns`): a server that dies
|
|
425
|
+
* mid-run never reaches the route's write-back, so without that call site a
|
|
426
|
+
* session whose runs are always interrupted grows one capped log per run
|
|
427
|
+
* forever. `keep: 0` drops them all — what session deletion uses.
|
|
428
|
+
*
|
|
429
|
+
* Never throws; returns how many files it removed.
|
|
430
|
+
*/
|
|
431
|
+
export async function pruneRunEventLogs(projectDir, sessionId, keep = RUN_EVENT_LOG_RETENTION) {
|
|
432
|
+
try {
|
|
433
|
+
const sessionsDir = join(projectDir, "sessions");
|
|
434
|
+
const prefix = `${normalizeIdentifier(sessionId)}.run-`;
|
|
435
|
+
const names = (await readdir(sessionsDir)).filter((name) => name.startsWith(prefix) &&
|
|
436
|
+
name.endsWith(LOG_SUFFIX) &&
|
|
437
|
+
name.length > prefix.length + LOG_SUFFIX.length);
|
|
438
|
+
if (names.length <= keep)
|
|
439
|
+
return 0;
|
|
440
|
+
const dated = await Promise.all(names.map(async (name) => {
|
|
441
|
+
try {
|
|
442
|
+
return { name, mtimeMs: (await stat(join(sessionsDir, name))).mtimeMs };
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// Vanished under us — sort it to the front so it is dropped first.
|
|
446
|
+
return { name, mtimeMs: 0 };
|
|
447
|
+
}
|
|
448
|
+
}));
|
|
449
|
+
// Newest first; the name breaks mtime ties (same-millisecond runs) so the
|
|
450
|
+
// ordering is total and the prune is deterministic.
|
|
451
|
+
dated.sort((a, b) => b.mtimeMs - a.mtimeMs || (a.name < b.name ? 1 : -1));
|
|
452
|
+
let pruned = 0;
|
|
453
|
+
for (const entry of dated.slice(Math.max(keep, 0))) {
|
|
454
|
+
try {
|
|
455
|
+
await unlink(join(sessionsDir, entry.name));
|
|
456
|
+
pruned += 1;
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
// Already gone or locked — retention is best-effort.
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return pruned;
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return 0;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
//# sourceMappingURL=run-event-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-event-log.js","sourceRoot":"","sources":["../../src/web-server/run-event-log.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"}
|