@relayflows/sdk 2.0.8 → 2.0.10
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/dist/adapters/base.d.ts +46 -0
- package/dist/adapters/base.d.ts.map +1 -0
- package/dist/adapters/base.js +15 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude.d.ts +5 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +44 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.d.ts +5 -0
- package/dist/adapters/codex.d.ts.map +1 -0
- package/dist/adapters/codex.js +45 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/index.d.ts +14 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +29 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/wrapper.d.ts +12 -0
- package/dist/adapters/wrapper.d.ts.map +1 -0
- package/dist/adapters/wrapper.js +31 -0
- package/dist/adapters/wrapper.js.map +1 -0
- package/dist/agent-relay-transport.d.ts +84 -0
- package/dist/agent-relay-transport.d.ts.map +1 -0
- package/dist/agent-relay-transport.js +134 -0
- package/dist/agent-relay-transport.js.map +1 -0
- package/dist/authored-budget.d.ts +13 -0
- package/dist/authored-budget.d.ts.map +1 -0
- package/dist/authored-budget.js +78 -0
- package/dist/authored-budget.js.map +1 -0
- package/dist/authored-flow-error.d.ts +1 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts +13 -7
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +115 -194
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-flow-loader.d.ts +10 -1
- package/dist/authored-flow-loader.d.ts.map +1 -1
- package/dist/authored-flow-loader.js +56 -3
- package/dist/authored-flow-loader.js.map +1 -1
- package/dist/authored-flow-operation.d.ts +9 -1
- package/dist/authored-flow-operation.d.ts.map +1 -1
- package/dist/authored-flow-operation.js +35 -4
- package/dist/authored-flow-operation.js.map +1 -1
- package/dist/authored-helper-effect.d.ts +12 -0
- package/dist/authored-helper-effect.d.ts.map +1 -0
- package/dist/authored-helper-effect.js +144 -0
- package/dist/authored-helper-effect.js.map +1 -0
- package/dist/authored-mcp.d.ts +17 -0
- package/dist/authored-mcp.d.ts.map +1 -0
- package/dist/authored-mcp.js +171 -0
- package/dist/authored-mcp.js.map +1 -0
- package/dist/authored-memory.d.ts +7 -0
- package/dist/authored-memory.d.ts.map +1 -0
- package/dist/authored-memory.js +60 -0
- package/dist/authored-memory.js.map +1 -0
- package/dist/authored-plugin-effect.d.ts +16 -0
- package/dist/authored-plugin-effect.d.ts.map +1 -0
- package/dist/authored-plugin-effect.js +163 -0
- package/dist/authored-plugin-effect.js.map +1 -0
- package/dist/authored-step-output.d.ts +23 -0
- package/dist/authored-step-output.d.ts.map +1 -0
- package/dist/authored-step-output.js +67 -0
- package/dist/authored-step-output.js.map +1 -0
- package/dist/authored-worker-step.d.ts +15 -0
- package/dist/authored-worker-step.d.ts.map +1 -0
- package/dist/authored-worker-step.js +136 -0
- package/dist/authored-worker-step.js.map +1 -0
- package/dist/budget-preflight.d.ts +5 -0
- package/dist/budget-preflight.d.ts.map +1 -0
- package/dist/budget-preflight.js +33 -0
- package/dist/budget-preflight.js.map +1 -0
- package/dist/budget.d.ts +14 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +52 -0
- package/dist/budget.js.map +1 -0
- package/dist/bundle-transport.d.ts +18 -0
- package/dist/bundle-transport.d.ts.map +1 -0
- package/dist/bundle-transport.js +111 -0
- package/dist/bundle-transport.js.map +1 -0
- package/dist/bundle-typescript.d.ts +8 -0
- package/dist/bundle-typescript.d.ts.map +1 -0
- package/dist/bundle-typescript.js +108 -0
- package/dist/bundle-typescript.js.map +1 -0
- package/dist/bundle.d.ts +25 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +199 -0
- package/dist/bundle.js.map +1 -0
- package/dist/child-stop.d.ts +59 -0
- package/dist/child-stop.d.ts.map +1 -0
- package/dist/child-stop.js +94 -0
- package/dist/child-stop.js.map +1 -0
- package/dist/cli/add.d.ts +6 -0
- package/dist/cli/add.d.ts.map +1 -0
- package/dist/cli/add.js +47 -0
- package/dist/cli/add.js.map +1 -0
- package/dist/cli/build.d.ts +13 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +224 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/bundle-preflight.d.ts +5 -0
- package/dist/cli/bundle-preflight.d.ts.map +1 -0
- package/dist/cli/bundle-preflight.js +28 -0
- package/dist/cli/bundle-preflight.js.map +1 -0
- package/dist/cli/check-helper-body.d.ts +4 -0
- package/dist/cli/check-helper-body.d.ts.map +1 -0
- package/dist/cli/check-helper-body.js +15 -0
- package/dist/cli/check-helper-body.js.map +1 -0
- package/dist/cli/check-triggers.d.ts +14 -0
- package/dist/cli/check-triggers.d.ts.map +1 -0
- package/dist/cli/check-triggers.js +37 -0
- package/dist/cli/check-triggers.js.map +1 -0
- package/dist/cli/check-typescript.d.ts +20 -0
- package/dist/cli/check-typescript.d.ts.map +1 -0
- package/dist/cli/check-typescript.js +54 -0
- package/dist/cli/check-typescript.js.map +1 -0
- package/dist/cli/check.d.ts +36 -3
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +122 -11
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-run.d.ts +8 -0
- package/dist/cli/cloud-run.d.ts.map +1 -0
- package/dist/cli/cloud-run.js +50 -0
- package/dist/cli/cloud-run.js.map +1 -0
- package/dist/cli/deploy.d.ts +9 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/deploy.js +58 -0
- package/dist/cli/deploy.js.map +1 -0
- package/dist/cli/deterministic-failure.d.ts +5 -0
- package/dist/cli/deterministic-failure.d.ts.map +1 -0
- package/dist/cli/deterministic-failure.js +66 -0
- package/dist/cli/deterministic-failure.js.map +1 -0
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +83 -7
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/hn-monitor.d.ts.map +1 -1
- package/dist/cli/hn-monitor.js +2 -1
- package/dist/cli/hn-monitor.js.map +1 -1
- package/dist/cli/replay.d.ts +12 -0
- package/dist/cli/replay.d.ts.map +1 -0
- package/dist/cli/replay.js +82 -0
- package/dist/cli/replay.js.map +1 -0
- package/dist/cli/reuse.d.ts +8 -0
- package/dist/cli/reuse.d.ts.map +1 -0
- package/dist/cli/reuse.js +25 -0
- package/dist/cli/reuse.js.map +1 -0
- package/dist/cli/run-digest.d.ts +4 -0
- package/dist/cli/run-digest.d.ts.map +1 -0
- package/dist/cli/run-digest.js +24 -0
- package/dist/cli/run-digest.js.map +1 -0
- package/dist/cli/run.d.ts +16 -5
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +99 -17
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/serve-webhook.d.ts +40 -0
- package/dist/cli/serve-webhook.d.ts.map +1 -0
- package/dist/cli/serve-webhook.js +233 -0
- package/dist/cli/serve-webhook.js.map +1 -0
- package/dist/cli/tick-runner.d.ts.map +1 -1
- package/dist/cli/tick-runner.js +2 -1
- package/dist/cli/tick-runner.js.map +1 -1
- package/dist/cli-adapter.d.ts +8 -14
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +19 -64
- package/dist/cli-adapter.js.map +1 -1
- package/dist/cli-watch.d.ts +13 -0
- package/dist/cli-watch.d.ts.map +1 -0
- package/dist/cli-watch.js +185 -0
- package/dist/cli-watch.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +387 -14
- package/dist/cli.js.map +1 -1
- package/dist/cloud-http.d.ts +22 -0
- package/dist/cloud-http.d.ts.map +1 -0
- package/dist/cloud-http.js +84 -0
- package/dist/cloud-http.js.map +1 -0
- package/dist/cloud-run.d.ts +38 -0
- package/dist/cloud-run.d.ts.map +1 -0
- package/dist/cloud-run.js +112 -0
- package/dist/cloud-run.js.map +1 -0
- package/dist/compile.d.ts +21 -3
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +97 -23
- package/dist/compile.js.map +1 -1
- package/dist/create-flow.d.ts +17 -0
- package/dist/create-flow.d.ts.map +1 -0
- package/dist/create-flow.js +72 -0
- package/dist/create-flow.js.map +1 -0
- package/dist/daemon-connection.d.ts +14 -0
- package/dist/daemon-connection.d.ts.map +1 -1
- package/dist/daemon-connection.js +33 -1
- package/dist/daemon-connection.js.map +1 -1
- package/dist/demo-hn-monitor.js +2 -1
- package/dist/demo-hn-monitor.js.map +1 -1
- package/dist/effect-channel.d.ts +30 -0
- package/dist/effect-channel.d.ts.map +1 -0
- package/dist/effect-channel.js +71 -0
- package/dist/effect-channel.js.map +1 -0
- package/dist/failure-kinds.d.ts +14 -3
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +27 -0
- package/dist/failure-kinds.js.map +1 -1
- package/dist/gate-contract.d.ts.map +1 -1
- package/dist/gate-contract.js +9 -0
- package/dist/gate-contract.js.map +1 -1
- package/dist/helper-preflight.d.ts +18 -0
- package/dist/helper-preflight.d.ts.map +1 -0
- package/dist/helper-preflight.js +32 -0
- package/dist/helper-preflight.js.map +1 -0
- package/dist/helper-storage.d.ts +5 -0
- package/dist/helper-storage.d.ts.map +1 -0
- package/dist/helper-storage.js +31 -0
- package/dist/helper-storage.js.map +1 -0
- package/dist/helper-writeback.d.ts +12 -0
- package/dist/helper-writeback.d.ts.map +1 -0
- package/dist/helper-writeback.js +95 -0
- package/dist/helper-writeback.js.map +1 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/input-binding.d.ts +4 -0
- package/dist/input-binding.d.ts.map +1 -0
- package/dist/input-binding.js +78 -0
- package/dist/input-binding.js.map +1 -0
- package/dist/journal-client.d.ts +12 -4
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +17 -6
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-offset.d.ts +3 -0
- package/dist/journal-offset.d.ts.map +1 -0
- package/dist/journal-offset.js +69 -0
- package/dist/journal-offset.js.map +1 -0
- package/dist/journal-reader.d.ts +30 -0
- package/dist/journal-reader.d.ts.map +1 -0
- package/dist/journal-reader.js +156 -0
- package/dist/journal-reader.js.map +1 -0
- package/dist/json-schema.d.ts +2 -0
- package/dist/json-schema.d.ts.map +1 -1
- package/dist/json-schema.js +38 -22
- package/dist/json-schema.js.map +1 -1
- package/dist/llm-worker.d.ts +16 -0
- package/dist/llm-worker.d.ts.map +1 -0
- package/dist/llm-worker.js +79 -0
- package/dist/llm-worker.js.map +1 -0
- package/dist/local-agent.d.ts +8 -0
- package/dist/local-agent.d.ts.map +1 -0
- package/dist/local-agent.js +25 -0
- package/dist/local-agent.js.map +1 -0
- package/dist/mcp-client.d.ts +15 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +104 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp-config.d.ts +4 -0
- package/dist/mcp-config.d.ts.map +1 -0
- package/dist/mcp-config.js +61 -0
- package/dist/mcp-config.js.map +1 -0
- package/dist/mcp-stdio.d.ts +24 -0
- package/dist/mcp-stdio.d.ts.map +1 -0
- package/dist/mcp-stdio.js +89 -0
- package/dist/mcp-stdio.js.map +1 -0
- package/dist/model-pricing.d.ts +22 -0
- package/dist/model-pricing.d.ts.map +1 -0
- package/dist/model-pricing.js +30 -0
- package/dist/model-pricing.js.map +1 -0
- package/dist/mount-registry.d.ts +9 -0
- package/dist/mount-registry.d.ts.map +1 -0
- package/dist/mount-registry.js +47 -0
- package/dist/mount-registry.js.map +1 -0
- package/dist/named-gate-lowering.d.ts +9 -0
- package/dist/named-gate-lowering.d.ts.map +1 -0
- package/dist/named-gate-lowering.js +152 -0
- package/dist/named-gate-lowering.js.map +1 -0
- package/dist/named-gates.d.ts +10 -0
- package/dist/named-gates.d.ts.map +1 -0
- package/dist/named-gates.js +69 -0
- package/dist/named-gates.js.map +1 -0
- package/dist/observer-link.d.ts +129 -0
- package/dist/observer-link.d.ts.map +1 -0
- package/dist/observer-link.js +274 -0
- package/dist/observer-link.js.map +1 -0
- package/dist/plugin-loader.d.ts +13 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/plugin-loader.js +110 -0
- package/dist/plugin-loader.js.map +1 -0
- package/dist/plugin-manifest.d.ts +27 -0
- package/dist/plugin-manifest.d.ts.map +1 -0
- package/dist/plugin-manifest.js +89 -0
- package/dist/plugin-manifest.js.map +1 -0
- package/dist/preflight.d.ts +26 -2
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +175 -1
- package/dist/preflight.js.map +1 -1
- package/dist/progress.d.ts +14 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +39 -0
- package/dist/progress.js.map +1 -0
- package/dist/protocol.d.ts +11 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/pty-sidechannel.d.ts +14 -0
- package/dist/pty-sidechannel.d.ts.map +1 -0
- package/dist/pty-sidechannel.js +103 -0
- package/dist/pty-sidechannel.js.map +1 -0
- package/dist/scope-compiler.d.ts +52 -0
- package/dist/scope-compiler.d.ts.map +1 -0
- package/dist/scope-compiler.js +62 -0
- package/dist/scope-compiler.js.map +1 -0
- package/dist/slack-preflight.d.ts +6 -0
- package/dist/slack-preflight.d.ts.map +1 -0
- package/dist/slack-preflight.js +28 -0
- package/dist/slack-preflight.js.map +1 -0
- package/dist/slack-writeback.d.ts +40 -0
- package/dist/slack-writeback.d.ts.map +1 -0
- package/dist/slack-writeback.js +60 -0
- package/dist/slack-writeback.js.map +1 -0
- package/dist/spec.d.ts +134 -4
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/step-dependencies.d.ts.map +1 -1
- package/dist/step-dependencies.js +2 -1
- package/dist/step-dependencies.js.map +1 -1
- package/dist/step-fields.d.ts +4 -4
- package/dist/step-fields.d.ts.map +1 -1
- package/dist/step-fields.js +5 -2
- package/dist/step-fields.js.map +1 -1
- package/dist/trigger-executor.d.ts +7 -0
- package/dist/trigger-executor.d.ts.map +1 -0
- package/dist/trigger-executor.js +35 -0
- package/dist/trigger-executor.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +53 -6
- package/dist/validate.js.map +1 -1
- package/dist/webhook-rate-limit.d.ts +29 -0
- package/dist/webhook-rate-limit.d.ts.map +1 -0
- package/dist/webhook-rate-limit.js +38 -0
- package/dist/webhook-rate-limit.js.map +1 -0
- package/dist/webhook-signature.d.ts +22 -0
- package/dist/webhook-signature.d.ts.map +1 -0
- package/dist/webhook-signature.js +53 -0
- package/dist/webhook-signature.js.map +1 -0
- package/dist/worker-cli.d.ts +13 -1
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +133 -12
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker-input.d.ts +4 -0
- package/dist/worker-input.d.ts.map +1 -0
- package/dist/worker-input.js +14 -0
- package/dist/worker-input.js.map +1 -0
- package/dist/worker-lease.d.ts +5 -0
- package/dist/worker-lease.d.ts.map +1 -0
- package/dist/worker-lease.js +80 -0
- package/dist/worker-lease.js.map +1 -0
- package/dist/worker-spend.d.ts +19 -0
- package/dist/worker-spend.d.ts.map +1 -0
- package/dist/worker-spend.js +22 -0
- package/dist/worker-spend.js.map +1 -0
- package/dist/worker-usage.d.ts +7 -0
- package/dist/worker-usage.d.ts.map +1 -0
- package/dist/worker-usage.js +54 -0
- package/dist/worker-usage.js.map +1 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +22 -8
- package/dist/worker.js.map +1 -1
- package/dist/wrapper-session.d.ts +1 -1
- package/dist/wrapper-session.d.ts.map +1 -1
- package/dist/wrapper-session.js +56 -19
- package/dist/wrapper-session.js.map +1 -1
- package/dist/yaml-helper-effect.d.ts +8 -0
- package/dist/yaml-helper-effect.d.ts.map +1 -0
- package/dist/yaml-helper-effect.js +79 -0
- package/dist/yaml-helper-effect.js.map +1 -0
- package/dist/yaml-helpers.d.ts +18 -0
- package/dist/yaml-helpers.d.ts.map +1 -0
- package/dist/yaml-helpers.js +116 -0
- package/dist/yaml-helpers.js.map +1 -0
- package/package.json +19 -2
- package/src/adapters/base.ts +53 -0
- package/src/adapters/claude.ts +55 -0
- package/src/adapters/codex.ts +56 -0
- package/src/adapters/index.ts +36 -0
- package/src/adapters/wrapper.ts +42 -0
- package/src/agent-relay-transport.ts +187 -0
- package/src/authored-budget.ts +69 -0
- package/src/authored-flow-error.ts +10 -0
- package/src/authored-flow-executor.ts +170 -293
- package/src/authored-flow-loader.ts +60 -3
- package/src/authored-flow-operation.ts +38 -5
- package/src/authored-helper-effect.ts +143 -0
- package/src/authored-mcp.ts +166 -0
- package/src/authored-memory.ts +55 -0
- package/src/authored-plugin-effect.ts +152 -0
- package/src/authored-step-output.ts +107 -0
- package/src/authored-worker-step.ts +181 -0
- package/src/budget-preflight.ts +31 -0
- package/src/budget.ts +49 -0
- package/src/bundle-transport.ts +79 -0
- package/src/bundle-typescript.ts +98 -0
- package/src/bundle.ts +182 -0
- package/src/child-stop.ts +138 -0
- package/src/cli/add.ts +45 -0
- package/src/cli/build.ts +214 -0
- package/src/cli/bundle-preflight.ts +28 -0
- package/src/cli/check-helper-body.ts +14 -0
- package/src/cli/check-triggers.ts +41 -0
- package/src/cli/check-typescript.ts +63 -0
- package/src/cli/check.ts +132 -14
- package/src/cli/cloud-run.ts +47 -0
- package/src/cli/deploy.ts +52 -0
- package/src/cli/deterministic-failure.ts +69 -0
- package/src/cli/direct-run.ts +79 -7
- package/src/cli/hn-monitor.ts +2 -1
- package/src/cli/replay.ts +81 -0
- package/src/cli/reuse.ts +23 -0
- package/src/cli/run-digest.ts +24 -0
- package/src/cli/run.ts +104 -21
- package/src/cli/serve-webhook.ts +246 -0
- package/src/cli/tick-runner.ts +2 -1
- package/src/cli-adapter.ts +31 -74
- package/src/cli-watch.ts +174 -0
- package/src/cli.ts +396 -17
- package/src/cloud-http.ts +100 -0
- package/src/cloud-run.ts +137 -0
- package/src/compile.ts +102 -23
- package/src/create-flow.ts +82 -0
- package/src/daemon-connection.ts +32 -1
- package/src/demo-hn-monitor.ts +2 -1
- package/src/effect-channel.ts +97 -0
- package/src/failure-kinds.ts +38 -0
- package/src/gate-contract.ts +7 -0
- package/src/helper-preflight.ts +34 -0
- package/src/helper-storage.ts +23 -0
- package/src/helper-writeback.ts +83 -0
- package/src/index.ts +26 -0
- package/src/input-binding.ts +76 -0
- package/src/journal-client.ts +20 -7
- package/src/journal-offset.ts +60 -0
- package/src/journal-reader.ts +165 -0
- package/src/json-schema.ts +34 -18
- package/src/llm-worker.ts +82 -0
- package/src/local-agent.ts +30 -0
- package/src/mcp-client.ts +97 -0
- package/src/mcp-config.ts +42 -0
- package/src/mcp-stdio.ts +84 -0
- package/src/model-pricing.ts +32 -0
- package/src/mount-registry.ts +44 -0
- package/src/named-gate-lowering.ts +147 -0
- package/src/named-gates.ts +76 -0
- package/src/observer-link.ts +368 -0
- package/src/plugin-loader.ts +88 -0
- package/src/plugin-manifest.ts +76 -0
- package/src/preflight.ts +178 -4
- package/src/progress.ts +51 -0
- package/src/protocol.ts +12 -0
- package/src/pty-sidechannel.ts +92 -0
- package/src/scope-compiler.ts +105 -0
- package/src/slack-preflight.ts +25 -0
- package/src/slack-writeback.ts +63 -0
- package/src/spec.ts +112 -4
- package/src/step-dependencies.ts +3 -1
- package/src/step-fields.ts +5 -2
- package/src/trigger-executor.ts +34 -0
- package/src/validate.ts +47 -6
- package/src/webhook-rate-limit.ts +69 -0
- package/src/webhook-signature.ts +74 -0
- package/src/worker-cli.ts +156 -11
- package/src/worker-input.ts +15 -0
- package/src/worker-lease.ts +85 -0
- package/src/worker-spend.ts +23 -0
- package/src/worker-usage.ts +48 -0
- package/src/worker.ts +27 -9
- package/src/wrapper-session.ts +50 -15
- package/src/yaml-helper-effect.ts +78 -0
- package/src/yaml-helpers.ts +131 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, rename, unlink, lstat } from 'node:fs/promises';
|
|
3
|
+
import { createServer, type Server, type ServerResponse } from 'node:http';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { TextDecoder } from 'node:util';
|
|
6
|
+
import type { CliIo } from '../cli.js';
|
|
7
|
+
import { providerInboxEvent } from '../trigger-executor.js';
|
|
8
|
+
import { verifySignature, schemeFor } from '../webhook-signature.js';
|
|
9
|
+
import { TokenBucketLimiter, keyFor, type RateLimitConfig } from '../webhook-rate-limit.js';
|
|
10
|
+
|
|
11
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
12
|
+
const NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
13
|
+
|
|
14
|
+
const DEFAULT_RATE_LIMIT: RateLimitConfig = { ratePerSecond: 20, burst: 60 };
|
|
15
|
+
|
|
16
|
+
export function parseWebhookArgs(args: readonly string[]): {
|
|
17
|
+
command: 'serve-webhook'; dataDir: string; port: number; admitted?: readonly string[];
|
|
18
|
+
} | undefined {
|
|
19
|
+
const values = new Map<string, string>();
|
|
20
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
21
|
+
const flag = args[i]!;
|
|
22
|
+
const value = args[i + 1];
|
|
23
|
+
if (!['--data-dir', '--port', '--allow'].includes(flag) || values.has(flag)
|
|
24
|
+
|| !value || value.startsWith('-')) return undefined;
|
|
25
|
+
values.set(flag, value);
|
|
26
|
+
}
|
|
27
|
+
const dataDir = values.get('--data-dir');
|
|
28
|
+
const portText = values.get('--port');
|
|
29
|
+
if (!dataDir || !portText || !/^\d+$/.test(portText)) return undefined;
|
|
30
|
+
const port = Number(portText);
|
|
31
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) return undefined;
|
|
32
|
+
const allow = values.get('--allow');
|
|
33
|
+
// --allow is comma-separated. Empty list is not admitted (would refuse everything);
|
|
34
|
+
// treat as parse failure so the author sees the typo rather than a mute receiver.
|
|
35
|
+
const admitted = allow === undefined ? undefined
|
|
36
|
+
: allow.split(',').map(entry => entry.trim());
|
|
37
|
+
if (admitted !== undefined && (admitted.length === 0 || admitted.some(entry => !NAME.test(entry)))) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return admitted === undefined
|
|
41
|
+
? { command: 'serve-webhook', dataDir, port }
|
|
42
|
+
: { command: 'serve-webhook', dataDir, port, admitted };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function reply(response: ServerResponse, status: number, body: object): void {
|
|
46
|
+
response.writeHead(status, { 'content-type': 'application/json' });
|
|
47
|
+
response.end(JSON.stringify(body));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Look up the shared secret for a provider from the process environment.
|
|
52
|
+
*
|
|
53
|
+
* `WEBHOOK_SECRET_<PROVIDER_UPPER>` (e.g. `WEBHOOK_SECRET_GITHUB`) is the
|
|
54
|
+
* canonical env var. When unset, the receiver runs unsigned — this is
|
|
55
|
+
* documented in #301 as the local-development posture.
|
|
56
|
+
*/
|
|
57
|
+
export function providerSecret(provider: string | undefined, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
58
|
+
if (!provider) return env.WEBHOOK_SECRET_DEFAULT;
|
|
59
|
+
const upper = provider.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
60
|
+
return env[`WEBHOOK_SECRET_${upper}`];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface WebhookServerOptions {
|
|
64
|
+
/**
|
|
65
|
+
* When set, unknown names refuse with `webhook_flow_unknown`. Closes the
|
|
66
|
+
* "any-name" ingress opened by slice E (#333) so the receiver only accepts
|
|
67
|
+
* inbox writes for triggers whose flows have been explicitly loaded — the
|
|
68
|
+
* loaded-flow admission half of #303. Durable handler execution against
|
|
69
|
+
* the journal is deferred; per #303 that half depends on kernel authored-
|
|
70
|
+
* handler registration + resume protocol.
|
|
71
|
+
*/
|
|
72
|
+
admittedNames?: ReadonlySet<string>;
|
|
73
|
+
/** Optional rate limit config; defaults to 20/s with 60 burst per key. */
|
|
74
|
+
rateLimit?: RateLimitConfig;
|
|
75
|
+
/** Injectable env lookup for tests. */
|
|
76
|
+
env?: NodeJS.ProcessEnv;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** POST /<name> accepts JSON; /providers/<provider> accepts typed event envelopes. */
|
|
80
|
+
export async function startWebhookServer(
|
|
81
|
+
dataDir: string,
|
|
82
|
+
port: number,
|
|
83
|
+
options: WebhookServerOptions = {},
|
|
84
|
+
): Promise<Server> {
|
|
85
|
+
const inbox = join(resolve(dataDir), 'inbox');
|
|
86
|
+
await directory(inbox);
|
|
87
|
+
const admitted = options.admittedNames;
|
|
88
|
+
const env = options.env ?? process.env;
|
|
89
|
+
const limiter = new TokenBucketLimiter(options.rateLimit ?? DEFAULT_RATE_LIMIT);
|
|
90
|
+
// TODO https://github.com/AgentWorkforce/flows/issues/301: provider signatures,
|
|
91
|
+
// public ingress and Cloud mount provisioning belong to the deployment slice.
|
|
92
|
+
const server = createServer(async (request, response) => {
|
|
93
|
+
if (request.method !== 'POST') {
|
|
94
|
+
request.resume();
|
|
95
|
+
response.setHeader('allow', 'POST');
|
|
96
|
+
reply(response, 405, { error: 'method_not_allowed' });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const providerRoute = request.url?.startsWith('/providers/') ?? false;
|
|
100
|
+
const name = request.url?.slice(providerRoute ? '/providers/'.length : 1);
|
|
101
|
+
if (!name || !NAME.test(name)) {
|
|
102
|
+
request.resume();
|
|
103
|
+
reply(response, 404, { error: 'invalid_webhook_name' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Loaded-flow admission (#303) runs FIRST: refuse unknown names before
|
|
107
|
+
// spending any body-read or rate-limit budget. A rogue POST can't
|
|
108
|
+
// accumulate events in an inbox the daemon will never drain, and can't
|
|
109
|
+
// steal from the honest ratelimit token bucket.
|
|
110
|
+
if (admitted !== undefined && !admitted.has(name)) {
|
|
111
|
+
request.resume();
|
|
112
|
+
reply(response, 404, { error: 'webhook_flow_unknown', name });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// #304 rate limiting — key by provider+name+source so an abusive caller
|
|
116
|
+
// cannot starve every route. Loopback callers share 127.0.0.1 as the
|
|
117
|
+
// source key by design (single-process, single-tenant).
|
|
118
|
+
const sourceAddress = request.socket.remoteAddress ?? '127.0.0.1';
|
|
119
|
+
const provider = providerRoute ? name : undefined;
|
|
120
|
+
const rateKey = keyFor(provider, name, sourceAddress);
|
|
121
|
+
const decision = limiter.consume(rateKey);
|
|
122
|
+
if (!decision.allowed) {
|
|
123
|
+
request.resume();
|
|
124
|
+
const retryAfterSeconds = Math.max(1, Math.ceil(decision.retryAfterMs / 1000));
|
|
125
|
+
response.setHeader('retry-after', String(retryAfterSeconds));
|
|
126
|
+
reply(response, 429, { error: 'webhook_rate_limited', retryAfterMs: decision.retryAfterMs });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let temporary: string | undefined;
|
|
130
|
+
try {
|
|
131
|
+
let bytes = 0;
|
|
132
|
+
const chunks: Buffer[] = [];
|
|
133
|
+
for await (const chunk of request) {
|
|
134
|
+
const buffer = Buffer.from(chunk as Uint8Array);
|
|
135
|
+
bytes += buffer.length;
|
|
136
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
137
|
+
reply(response, 413, { error: 'payload_too_large' });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
chunks.push(buffer);
|
|
141
|
+
}
|
|
142
|
+
const rawBody = Buffer.concat(chunks);
|
|
143
|
+
// #304 signature verification — only enforced when a secret is
|
|
144
|
+
// configured for this provider/name. Absent secret = development mode.
|
|
145
|
+
const secret = providerSecret(provider, env);
|
|
146
|
+
if (secret) {
|
|
147
|
+
const scheme = schemeFor(provider);
|
|
148
|
+
const headerValue = request.headers[scheme.header];
|
|
149
|
+
const suppliedHeader = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
150
|
+
const verdict = verifySignature(provider, rawBody, suppliedHeader, secret);
|
|
151
|
+
if (!verdict.ok) {
|
|
152
|
+
reply(response, 401, { error: 'webhook_signature_invalid', reason: verdict.reason });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
let payload: unknown;
|
|
157
|
+
try {
|
|
158
|
+
payload = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(rawBody), (_key, value: unknown) => {
|
|
159
|
+
if (typeof value === 'number' && !Number.isFinite(value)) throw new Error('non-finite JSON number');
|
|
160
|
+
return value;
|
|
161
|
+
});
|
|
162
|
+
} catch {
|
|
163
|
+
reply(response, 400, { error: 'invalid_json' });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (providerRoute) {
|
|
167
|
+
try {
|
|
168
|
+
payload = providerInboxEvent(name, payload);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// #304 payload-shape rejection: providers declare a closed event
|
|
171
|
+
// vocabulary; anything else is `webhook_payload_invalid`.
|
|
172
|
+
reply(response, 400, { error: 'webhook_payload_invalid',
|
|
173
|
+
message: error instanceof Error ? error.message : 'invalid provider event' });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const target = join(inbox, name);
|
|
178
|
+
await directory(target);
|
|
179
|
+
const id = randomUUID();
|
|
180
|
+
temporary = join(target, `.${id}.tmp`);
|
|
181
|
+
const file = await open(temporary, 'wx', 0o600);
|
|
182
|
+
try {
|
|
183
|
+
await file.writeFile(JSON.stringify(payload));
|
|
184
|
+
await file.sync();
|
|
185
|
+
} finally {
|
|
186
|
+
await file.close();
|
|
187
|
+
}
|
|
188
|
+
await rename(temporary, join(target, `${id}.json`));
|
|
189
|
+
temporary = undefined;
|
|
190
|
+
const dir = await open(target, 'r');
|
|
191
|
+
try { await dir.sync(); } finally { await dir.close(); }
|
|
192
|
+
reply(response, 202, { accepted: true, id });
|
|
193
|
+
} catch {
|
|
194
|
+
if (temporary) await unlink(temporary).catch(() => undefined);
|
|
195
|
+
if (!response.headersSent) reply(response, 500, { error: 'inbox_write_failed' });
|
|
196
|
+
else response.destroy();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
server.requestTimeout = 30_000;
|
|
200
|
+
server.headersTimeout = 10_000;
|
|
201
|
+
await new Promise<void>((accept, reject) => {
|
|
202
|
+
server.once('error', reject);
|
|
203
|
+
server.listen(port, '127.0.0.1', () => {
|
|
204
|
+
server.off('error', reject);
|
|
205
|
+
accept();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
return server;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function directory(path: string): Promise<void> {
|
|
212
|
+
await mkdir(path, { recursive: true });
|
|
213
|
+
if (!(await lstat(path)).isDirectory()) throw new Error('inbox directory must not be a symlink');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function runServeWebhook(
|
|
217
|
+
options: { dataDir: string; port: number; admitted?: readonly string[] }, io: CliIo,
|
|
218
|
+
): Promise<0 | 1> {
|
|
219
|
+
try {
|
|
220
|
+
const admittedNames = options.admitted === undefined ? undefined : new Set(options.admitted);
|
|
221
|
+
const server = await startWebhookServer(options.dataDir, options.port, { admittedNames });
|
|
222
|
+
const address = server.address();
|
|
223
|
+
const port = typeof address === 'object' && address ? address.port : options.port;
|
|
224
|
+
io.stdout(`WEBHOOK http://127.0.0.1:${port}`);
|
|
225
|
+
if (admittedNames !== undefined) {
|
|
226
|
+
io.stdout(`ADMITTED ${[...admittedNames].sort().join(',')}`);
|
|
227
|
+
}
|
|
228
|
+
await new Promise<void>((accept, reject) => {
|
|
229
|
+
const stop = (): void => {
|
|
230
|
+
server.close(error => error ? reject(error) : accept());
|
|
231
|
+
server.closeAllConnections();
|
|
232
|
+
};
|
|
233
|
+
server.once('error', reject);
|
|
234
|
+
process.once('SIGINT', stop);
|
|
235
|
+
process.once('SIGTERM', stop);
|
|
236
|
+
server.once('close', () => {
|
|
237
|
+
process.off('SIGINT', stop);
|
|
238
|
+
process.off('SIGTERM', stop);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
return 0;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
io.stderr(`FAILED [webhook_server] ${error instanceof Error ? error.message : 'receiver failed'}`);
|
|
244
|
+
return 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
package/src/cli/tick-runner.ts
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
39
39
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
40
|
+
import { socketPathFor } from '../daemon-connection.js';
|
|
40
41
|
import { JournalClient } from '../journal-client.js';
|
|
41
42
|
import {
|
|
42
43
|
DEFAULT_MAX_CATCH_UP,
|
|
@@ -269,7 +270,7 @@ export async function runTickRunner(args: TickRunnerArgs, io: CliIo): Promise<nu
|
|
|
269
270
|
const resuming = state.lastEmittedSlot !== undefined;
|
|
270
271
|
const cursor: TickCursor = resuming ? { lastEmittedSlot: state.lastEmittedSlot } : {};
|
|
271
272
|
|
|
272
|
-
const socketPath =
|
|
273
|
+
const socketPath = socketPathFor(args.dataDir);
|
|
273
274
|
let client: TickRunnerClient;
|
|
274
275
|
try {
|
|
275
276
|
client = args.connectClient
|
package/src/cli-adapter.ts
CHANGED
|
@@ -1,50 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Legacy dispatch surface — retained so existing callers (`worker-cli.ts`,
|
|
3
|
+
* `cli/check.ts`, real-cli-adapters tests) compile unchanged. The per-CLI
|
|
4
|
+
* knowledge now lives in `./adapters/{claude,codex,wrapper}.ts` behind the
|
|
5
|
+
* `HeadlessAdapter` interface (flows#141). New CLIs implement the interface
|
|
6
|
+
* and register in `./adapters/index.ts`; this file no longer needs edits.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
resolveAdapterKind,
|
|
10
|
+
registeredAdapters,
|
|
11
|
+
} from './adapters/index.js';
|
|
12
|
+
import type { CliAdapterKind } from './adapters/index.js';
|
|
13
|
+
import type { CliInvocation, CliAdapterIdentification } from './adapters/base.js';
|
|
11
14
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
expectedStdout?: string;
|
|
15
|
-
}
|
|
15
|
+
export type { CliAdapterKind } from './adapters/index.js';
|
|
16
|
+
export type { CliInvocation, CliAdapterIdentification, HeadlessAdapter } from './adapters/base.js';
|
|
16
17
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
export {
|
|
19
|
+
WRAPPER_IDENTIFY_ARG,
|
|
20
|
+
WRAPPER_IDENTIFY_TOKEN,
|
|
21
|
+
WRAPPER_EXECUTE_TOKEN,
|
|
22
|
+
} from './adapters/wrapper.js';
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
export { resolveAdapter, resolveAdapterKind, registeredAdapters } from './adapters/index.js';
|
|
22
25
|
|
|
23
26
|
/** Select a closed adapter from the resolved executable's basename. */
|
|
24
27
|
export function cliAdapterKind(executable: string): CliAdapterKind {
|
|
25
|
-
|
|
26
|
-
if (name === 'claude') return 'claude';
|
|
27
|
-
if (name === 'codex') return 'codex';
|
|
28
|
-
return 'relayflows-wrapper-v1';
|
|
28
|
+
return resolveAdapterKind(executable);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Prove the adapter command shape before classifying an auth failure. */
|
|
32
32
|
export function adapterIdentification(kind: CliAdapterKind): CliAdapterIdentification {
|
|
33
|
-
|
|
34
|
-
return { invocation: { args: ['auth', 'status', '--help'], timeoutMs: 10_000 } };
|
|
35
|
-
}
|
|
36
|
-
if (kind === 'codex') {
|
|
37
|
-
return { invocation: { args: ['login', 'status', '--help'], timeoutMs: 10_000 } };
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
invocation: { args: [WRAPPER_IDENTIFY_ARG], timeoutMs: 10_000 },
|
|
41
|
-
expectedStdout: WRAPPER_IDENTIFY_TOKEN,
|
|
42
|
-
};
|
|
33
|
+
return registeredAdapters()[kind].buildIdentification();
|
|
43
34
|
}
|
|
44
35
|
|
|
45
36
|
export function authenticationProbe(kind: CliAdapterKind): CliInvocation {
|
|
46
|
-
|
|
47
|
-
return { args: ['auth', 'status'], timeoutMs: 10_000 };
|
|
37
|
+
return registeredAdapters()[kind].buildAuthProbe();
|
|
48
38
|
}
|
|
49
39
|
|
|
50
40
|
/**
|
|
@@ -53,29 +43,7 @@ export function authenticationProbe(kind: CliAdapterKind): CliInvocation {
|
|
|
53
43
|
* exact model through its explicitly identified environment contract.
|
|
54
44
|
*/
|
|
55
45
|
export function modelReadinessProbe(kind: CliAdapterKind, model: string): CliInvocation {
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
args: [
|
|
59
|
-
'-p', '--model', model, '--tools', '', '--no-session-persistence',
|
|
60
|
-
MODEL_PROBE_PROMPT,
|
|
61
|
-
],
|
|
62
|
-
timeoutMs: 60_000,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
if (kind === 'codex') {
|
|
66
|
-
return {
|
|
67
|
-
args: [
|
|
68
|
-
'exec', '--ephemeral', '--sandbox', 'read-only', '--skip-git-repo-check',
|
|
69
|
-
'--model', model, MODEL_PROBE_PROMPT,
|
|
70
|
-
],
|
|
71
|
-
timeoutMs: 60_000,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return {
|
|
75
|
-
args: ['auth', 'status'],
|
|
76
|
-
timeoutMs: 60_000,
|
|
77
|
-
modelEnv: model,
|
|
78
|
-
};
|
|
46
|
+
return registeredAdapters()[kind].buildModelReadinessProbe(model);
|
|
79
47
|
}
|
|
80
48
|
|
|
81
49
|
/** Build the actual worker argv; this is shared contract, not probe-only lore. */
|
|
@@ -84,23 +52,12 @@ export function agentExecution(
|
|
|
84
52
|
instruction: string,
|
|
85
53
|
model?: string,
|
|
86
54
|
): CliInvocation {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (kind === 'codex') {
|
|
94
|
-
return {
|
|
95
|
-
args: [
|
|
96
|
-
'exec', '--ephemeral', '--skip-git-repo-check',
|
|
97
|
-
...(model === undefined ? [] : ['--model', model]),
|
|
98
|
-
instruction,
|
|
99
|
-
],
|
|
100
|
-
timeoutMs: 0,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
throw new Error('custom wrapper execution requires the runAgentCli same-process session');
|
|
55
|
+
return registeredAdapters()[kind].buildAgentInvocation(instruction, model);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Reuse the workspace-free model probe's provider flags for a real LLM call. */
|
|
59
|
+
export function llmExecution(kind: CliAdapterKind, prompt: string, model?: string): CliInvocation {
|
|
60
|
+
return registeredAdapters()[kind].buildLlmInvocation(prompt, model);
|
|
104
61
|
}
|
|
105
62
|
|
|
106
63
|
export function displayInvocation(cli: string, invocation: CliInvocation): string {
|
package/src/cli-watch.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants, existsSync, readFileSync, watch, type FSWatcher } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { parse as parseYaml } from 'yaml';
|
|
6
|
+
import type { CliIo } from './cli.js';
|
|
7
|
+
|
|
8
|
+
const DEBOUNCE_MS = 150;
|
|
9
|
+
type ExitCode = 0 | 1 | 2 | 3;
|
|
10
|
+
|
|
11
|
+
/** A runner around the ordinary CLI, with a fresh module cache for every check. */
|
|
12
|
+
export async function watchCheck(path: string, json: boolean, io: CliIo): Promise<ExitCode> {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const stop = (): void => controller.abort();
|
|
15
|
+
process.once('SIGINT', stop);
|
|
16
|
+
process.once('SIGTERM', stop);
|
|
17
|
+
try {
|
|
18
|
+
return await watchChecks({
|
|
19
|
+
path,
|
|
20
|
+
signal: controller.signal,
|
|
21
|
+
check: () => checkOnce(path, json, io),
|
|
22
|
+
clear: () => { if (!json) io.stdout('\x1b[2J\x1b[H'); },
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
io.stderr(`REFUSED [input_unreadable] Could not watch "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
return 2;
|
|
27
|
+
} finally {
|
|
28
|
+
process.off('SIGINT', stop);
|
|
29
|
+
process.off('SIGTERM', stop);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Inject the check and cancellation, just as the SDK pollers inject their I/O. */
|
|
34
|
+
export async function watchChecks(options: {
|
|
35
|
+
path: string;
|
|
36
|
+
signal: AbortSignal;
|
|
37
|
+
check: () => Promise<ExitCode>;
|
|
38
|
+
clear: () => void;
|
|
39
|
+
}): Promise<ExitCode> {
|
|
40
|
+
const watchers = new Map<string, FSWatcher>();
|
|
41
|
+
const imports = new Map<string, string[]>();
|
|
42
|
+
let paths = new Set<string>();
|
|
43
|
+
let lastCode: ExitCode = 0;
|
|
44
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
45
|
+
let pending = false;
|
|
46
|
+
let stopped = options.signal.aborted;
|
|
47
|
+
let wake: (() => void) | undefined;
|
|
48
|
+
let watchError: Error | undefined;
|
|
49
|
+
|
|
50
|
+
const changed = (): void => {
|
|
51
|
+
if (stopped) return;
|
|
52
|
+
pending = true;
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
timer = setTimeout(() => { timer = undefined; wake?.(); }, DEBOUNCE_MS);
|
|
55
|
+
};
|
|
56
|
+
const stop = (): void => {
|
|
57
|
+
stopped = true;
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
timer = undefined;
|
|
60
|
+
wake?.();
|
|
61
|
+
};
|
|
62
|
+
const refresh = (): void => {
|
|
63
|
+
paths = discoverWatchPaths(options.path, imports);
|
|
64
|
+
const directories = new Set<string>();
|
|
65
|
+
for (const path of paths) {
|
|
66
|
+
let directory = dirname(path);
|
|
67
|
+
while (!existsSync(directory) && dirname(directory) !== directory) directory = dirname(directory);
|
|
68
|
+
directories.add(directory);
|
|
69
|
+
if (watchers.has(directory)) continue;
|
|
70
|
+
// Watch directories so atomic editor saves and delete/recreate keep working.
|
|
71
|
+
const watcher = watch(directory, (_event, filename) => {
|
|
72
|
+
const changedPath = filename === null ? undefined : resolve(directory, filename.toString());
|
|
73
|
+
if (changedPath === undefined || [...paths].some((target) =>
|
|
74
|
+
target === changedPath || target.startsWith(`${changedPath}${sep}`))) changed();
|
|
75
|
+
});
|
|
76
|
+
watcher.on('error', (error) => { watchError = error; stop(); });
|
|
77
|
+
watchers.set(directory, watcher);
|
|
78
|
+
}
|
|
79
|
+
for (const [directory, watcher] of watchers) {
|
|
80
|
+
if (!directories.has(directory)) { watcher.close(); watchers.delete(directory); }
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
options.signal.addEventListener('abort', stop, { once: true });
|
|
85
|
+
try {
|
|
86
|
+
refresh();
|
|
87
|
+
let first = true;
|
|
88
|
+
while (!stopped) {
|
|
89
|
+
if (!first && (!pending || timer !== undefined)) {
|
|
90
|
+
await new Promise<void>((resolveWake) => { wake = resolveWake; });
|
|
91
|
+
wake = undefined;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
pending = false;
|
|
95
|
+
refresh();
|
|
96
|
+
if (!first) options.clear();
|
|
97
|
+
first = false;
|
|
98
|
+
lastCode = await options.check();
|
|
99
|
+
// A change while checking remains pending. Never overlap checks or lose
|
|
100
|
+
// a save merely because its debounce deadline elapsed during a check.
|
|
101
|
+
if (!stopped) refresh();
|
|
102
|
+
}
|
|
103
|
+
if (watchError !== undefined) throw watchError;
|
|
104
|
+
return lastCode;
|
|
105
|
+
} finally {
|
|
106
|
+
options.signal.removeEventListener('abort', stop);
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
for (const watcher of watchers.values()) watcher.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function discoverWatchPaths(path: string, imports: Map<string, string[]>): Set<string> {
|
|
113
|
+
const paths = new Set<string>();
|
|
114
|
+
const visit = (file: string): void => {
|
|
115
|
+
if (paths.has(file)) return;
|
|
116
|
+
paths.add(file);
|
|
117
|
+
try {
|
|
118
|
+
const value: unknown = parseYaml(readFileSync(file, 'utf8'));
|
|
119
|
+
const use = typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
120
|
+
? (value as Record<string, unknown>)['use'] : undefined;
|
|
121
|
+
imports.set(file, Array.isArray(use) ? use.filter((entry): entry is string =>
|
|
122
|
+
typeof entry === 'string' && (entry.startsWith('./') || entry.startsWith('../'))) : []);
|
|
123
|
+
} catch {
|
|
124
|
+
// Keep the previous graph while an editor has left this file incomplete.
|
|
125
|
+
// The ordinary check owns all parse and validation diagnostics.
|
|
126
|
+
}
|
|
127
|
+
for (const dependency of imports.get(file) ?? []) visit(resolve(dirname(file), dependency));
|
|
128
|
+
};
|
|
129
|
+
visit(resolve(path));
|
|
130
|
+
for (const file of imports.keys()) { if (!paths.has(file)) imports.delete(file); }
|
|
131
|
+
let directory = dirname(resolve(path));
|
|
132
|
+
while (true) {
|
|
133
|
+
const candidate = join(directory, 'flows.json');
|
|
134
|
+
paths.add(candidate);
|
|
135
|
+
try { accessSync(candidate, constants.R_OK); break; }
|
|
136
|
+
catch { /* Match check's nearest-readable-config discovery. */ }
|
|
137
|
+
const parent = dirname(directory);
|
|
138
|
+
if (parent === directory) break;
|
|
139
|
+
directory = parent;
|
|
140
|
+
}
|
|
141
|
+
return paths;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkOnce(path: string, json: boolean, io: CliIo): Promise<ExitCode> {
|
|
145
|
+
// Importing an authored TS file in this process again would return its stale
|
|
146
|
+
// cached definition (including transitive imports). A fresh CLI process uses
|
|
147
|
+
// precisely the same check pipeline and reporting as a one-shot invocation.
|
|
148
|
+
const entry = fileURLToPath(new URL(import.meta.url.endsWith('.ts') ? './cli.ts' : './cli.js', import.meta.url));
|
|
149
|
+
return new Promise((resolveCode, reject) => {
|
|
150
|
+
const child = spawn(process.execPath, [...process.execArgv, entry, 'check', ...(json ? ['--json'] : []), path], {
|
|
151
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
152
|
+
// Ctrl-C belongs to the watcher; let an active check finish so its report
|
|
153
|
+
// and exit status remain a pair, then release all resources.
|
|
154
|
+
detached: true,
|
|
155
|
+
});
|
|
156
|
+
const forward = (stream: NodeJS.ReadableStream, write: (line: string) => void): void => {
|
|
157
|
+
let pending = '';
|
|
158
|
+
stream.setEncoding('utf8');
|
|
159
|
+
stream.on('data', (chunk: string) => {
|
|
160
|
+
pending += chunk;
|
|
161
|
+
let end: number;
|
|
162
|
+
while ((end = pending.indexOf('\n')) >= 0) {
|
|
163
|
+
write(pending.slice(0, end));
|
|
164
|
+
pending = pending.slice(end + 1);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
stream.on('end', () => { if (pending) write(pending); });
|
|
168
|
+
};
|
|
169
|
+
forward(child.stdout, io.stdout);
|
|
170
|
+
forward(child.stderr, io.stderr);
|
|
171
|
+
child.once('error', reject);
|
|
172
|
+
child.once('close', (code) => resolveCode(code === 0 || code === 2 || code === 3 ? code : 1));
|
|
173
|
+
});
|
|
174
|
+
}
|