@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,76 @@
|
|
|
1
|
+
import { Ajv } from 'ajv';
|
|
2
|
+
import { snapshotJsonValue } from './json-value.js';
|
|
3
|
+
|
|
4
|
+
export const PLUGIN_FAILURE_KINDS = [
|
|
5
|
+
'plugin_unknown', 'plugin_unlisted', 'plugin_install_failed', 'plugin_manifest_missing',
|
|
6
|
+
'plugin_manifest_invalid', 'plugin_preflight_missing', 'plugin_verb_unknown_primitive',
|
|
7
|
+
'plugin_unsupported', 'plugin_credential_missing', 'plugin_server_unreachable',
|
|
8
|
+
] as const;
|
|
9
|
+
export type PluginFailureKind = typeof PLUGIN_FAILURE_KINDS[number];
|
|
10
|
+
export class PluginError extends Error {
|
|
11
|
+
constructor(readonly code: PluginFailureKind, message: string) { super(message); }
|
|
12
|
+
}
|
|
13
|
+
export interface PluginVerb {
|
|
14
|
+
namespace: string;
|
|
15
|
+
method: string;
|
|
16
|
+
lowersTo: 'run' | 'llm' | 'agent' | 'effect' | 'wait';
|
|
17
|
+
args: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface PluginManifest {
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
verbs: readonly PluginVerb[];
|
|
23
|
+
triggers: readonly unknown[];
|
|
24
|
+
gates: readonly unknown[];
|
|
25
|
+
preflight: { credentials: readonly string[]; servers: readonly string[] };
|
|
26
|
+
}
|
|
27
|
+
const identifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
28
|
+
const reserved = new Set(['run', 'llm', 'agent', 'human', 'dispatch', 'done', 'cloud', 'memory', 'mcp', 'slack', '__proto__', 'constructor', 'prototype', 'then']);
|
|
29
|
+
const object = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
30
|
+
const strings = (v: unknown): v is string[] => Array.isArray(v) && v.every(s => typeof s === 'string' && s.trim().length > 0);
|
|
31
|
+
export function pluginPackageName(name: string): string {
|
|
32
|
+
const bare = name.replace(/^@flows\//, '');
|
|
33
|
+
if (!/^helper-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(bare)) {
|
|
34
|
+
throw new PluginError('plugin_manifest_invalid', 'Expected helper-<name> or @flows/helper-<name>.');
|
|
35
|
+
}
|
|
36
|
+
return `@flows/${bare}`;
|
|
37
|
+
}
|
|
38
|
+
export function validatePluginManifest(input: unknown, packageName?: string): PluginManifest {
|
|
39
|
+
let v: unknown;
|
|
40
|
+
try { v = snapshotJsonValue(input, 'plugin manifest'); }
|
|
41
|
+
catch { throw new PluginError('plugin_manifest_invalid', 'Plugin manifest must be JSON data.'); }
|
|
42
|
+
const invalid = (message: string): never => { throw new PluginError('plugin_manifest_invalid', message); };
|
|
43
|
+
if (!object(v)) return invalid('Expected a plugin manifest object.');
|
|
44
|
+
if (!Object.hasOwn(v, 'preflight')) throw new PluginError('plugin_preflight_missing', 'Plugin must declare preflight.');
|
|
45
|
+
if (typeof v.name !== 'string' || typeof v.version !== 'string' || !v.version.trim()) return invalid('Plugin name and version are required.');
|
|
46
|
+
const resolved = pluginPackageName(v.name);
|
|
47
|
+
if (packageName !== undefined && resolved !== packageName) return invalid('Plugin name does not match its package.');
|
|
48
|
+
if (!Array.isArray(v.verbs) || !Array.isArray(v.triggers) || !Array.isArray(v.gates)) return invalid('Expected verbs, triggers, and gates arrays.');
|
|
49
|
+
if (!object(v.preflight) || !strings(v.preflight.credentials) || !strings(v.preflight.servers)) return invalid('Preflight requires credentials and servers arrays.');
|
|
50
|
+
if (!v.preflight.credentials.every(s => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s))) return invalid('Invalid credential environment variable.');
|
|
51
|
+
for (const server of v.preflight.servers) {
|
|
52
|
+
try { if (!['http:', 'https:'].includes(new URL(server).protocol)) return invalid('Servers must be HTTP(S) URLs.'); }
|
|
53
|
+
catch { return invalid('Servers must be HTTP(S) URLs.'); }
|
|
54
|
+
}
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
const ajv = new Ajv({ strict: false });
|
|
57
|
+
for (const verb of v.verbs) {
|
|
58
|
+
if (!object(verb) || typeof verb.namespace !== 'string' || typeof verb.method !== 'string'
|
|
59
|
+
|| !identifier.test(verb.namespace) || !identifier.test(verb.method)
|
|
60
|
+
|| reserved.has(verb.namespace) || ['__proto__', 'constructor', 'prototype', 'then'].includes(verb.method)) return invalid('Invalid or reserved plugin verb.');
|
|
61
|
+
if (!['run', 'llm', 'agent', 'effect', 'wait'].includes(String(verb.lowersTo))) {
|
|
62
|
+
throw new PluginError('plugin_verb_unknown_primitive', `Unknown primitive for ${verb.namespace}.${verb.method}.`);
|
|
63
|
+
}
|
|
64
|
+
if (!object(verb.args)) return invalid('Verb args must be a JSON Schema.');
|
|
65
|
+
try { ajv.compile(verb.args); } catch { return invalid('Invalid verb argument JSON Schema.'); }
|
|
66
|
+
const key = `${verb.namespace}.${verb.method}`;
|
|
67
|
+
if (seen.has(key)) return invalid(`Duplicate verb ${key}.`);
|
|
68
|
+
seen.add(key);
|
|
69
|
+
}
|
|
70
|
+
return v as unknown as PluginManifest;
|
|
71
|
+
}
|
|
72
|
+
export function assertSupportedPlugin(manifest: PluginManifest): void {
|
|
73
|
+
if (manifest.triggers.length || manifest.gates.length || manifest.verbs.some(v => v.lowersTo !== 'effect')) {
|
|
74
|
+
throw new PluginError('plugin_unsupported', 'This slice supports effect verbs; trigger, gate, run, llm, agent and wait dispatch are follow-ups.');
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/preflight.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { loadPlugins, type LoadedPlugin } from './plugin-loader.js';
|
|
2
|
+
import { PluginError } from './plugin-manifest.js';
|
|
3
|
+
import type { FlowSpec, StepSpec, TriggerSpec, McpServerConfig } from './spec.js';
|
|
4
|
+
import { McpError, openMcpSession, type McpDiagnostic } from './mcp-client.js';
|
|
5
|
+
import { BudgetSyntaxError } from './budget.js';
|
|
6
|
+
import { budgetDiagnostics } from './budget-preflight.js';
|
|
7
|
+
import type { TriggerSource } from '@relayflows/surface';
|
|
2
8
|
import { acceptsAnyOutput, inspectStepGate, type StepGateInspection } from './gate-contract.js';
|
|
3
9
|
import { compileSpec, CompileError } from './compile.js';
|
|
10
|
+
import { helperCall } from './yaml-helpers.js';
|
|
11
|
+
import { isNamedGate, NAMED_GATE_FAILURE_KINDS, type NamedGateFailureKind } from './named-gates.js';
|
|
12
|
+
import { compileScopes, type ScopeInput, type MountRegistry } from './scope-compiler.js';
|
|
13
|
+
import { readMountRegistry } from './mount-registry.js';
|
|
4
14
|
import type {
|
|
5
15
|
PreflightFailureKind,
|
|
6
16
|
PreflightWarningKind,
|
|
@@ -48,6 +58,8 @@ type CliProbeOutcome =
|
|
|
48
58
|
* emits `probe_failed` (or `command_unprovable` for a deterministic command).
|
|
49
59
|
*/
|
|
50
60
|
export interface PreflightProbes {
|
|
61
|
+
/** Whether the provider's relayfile mount is available to the helper worker. */
|
|
62
|
+
helper?(provider: string): boolean;
|
|
51
63
|
/**
|
|
52
64
|
* Resolve relative paths against the file implied by `source`, then probe
|
|
53
65
|
* `auth status`. When the step declared a `model`, the probe runs with that
|
|
@@ -60,6 +72,10 @@ export interface PreflightProbes {
|
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
export interface PreflightOptions {
|
|
75
|
+
pluginSearchStart?: string;
|
|
76
|
+
/** Validated tools.mcp header and nearest flows.json connections. */
|
|
77
|
+
mcpServers?: readonly string[];
|
|
78
|
+
mcp?: Readonly<Record<string, McpServerConfig>>;
|
|
63
79
|
projectCli?: string;
|
|
64
80
|
projectConfigPath?: string;
|
|
65
81
|
projectSearchStart?: string;
|
|
@@ -70,6 +86,8 @@ export interface PreflightOptions {
|
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
export interface PreflightRefusal {
|
|
89
|
+
server?: string;
|
|
90
|
+
cause?: McpDiagnostic;
|
|
73
91
|
severity: 'refusal';
|
|
74
92
|
kind: PreflightFailureKind;
|
|
75
93
|
message: string;
|
|
@@ -94,13 +112,65 @@ export interface PreflightWarning {
|
|
|
94
112
|
export type PreflightDiagnostic = PreflightRefusal | PreflightWarning;
|
|
95
113
|
|
|
96
114
|
export interface PreflightResult {
|
|
115
|
+
plugins?: readonly LoadedPlugin[];
|
|
116
|
+
mcpTools?: Readonly<Record<string, readonly string[]>>;
|
|
97
117
|
ok: boolean;
|
|
98
118
|
gates: StepGateInspection[];
|
|
99
119
|
resolutions: CliResolution[];
|
|
100
120
|
diagnostics: PreflightDiagnostic[];
|
|
101
121
|
}
|
|
102
122
|
|
|
103
|
-
|
|
123
|
+
/** Pure declared-surface check, before opening a receiver or journal. */
|
|
124
|
+
export function preflightWebhookTriggers(
|
|
125
|
+
triggers: readonly TriggerSource[],
|
|
126
|
+
executors: readonly string[],
|
|
127
|
+
): PreflightRefusal[] {
|
|
128
|
+
return [...new Set(triggers.map(trigger => trigger.name))]
|
|
129
|
+
.filter(name => !executors.includes(name))
|
|
130
|
+
.map(name => ({
|
|
131
|
+
severity: 'refusal',
|
|
132
|
+
kind: 'no_executor',
|
|
133
|
+
executor: name,
|
|
134
|
+
message: `webhook trigger "${name}" is not registered in flows.json`,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Preserve the synchronous declarative API; an authored MCP declaration opts
|
|
139
|
+
// into asynchronous connection probes after the same pure refusal pass.
|
|
140
|
+
export function preflight(flow: FlowSpec, options: PreflightOptions & { mcpServers: readonly string[] }): Promise<PreflightResult>;
|
|
141
|
+
export function preflight(flow: FlowSpec, options: PreflightOptions & { mcpServers?: undefined }): PreflightResult;
|
|
142
|
+
export function preflight(flow: FlowSpec, options: PreflightOptions): PreflightResult | Promise<PreflightResult>;
|
|
143
|
+
export function preflight(flow: unknown, options: PreflightOptions): PreflightResult | Promise<PreflightResult> {
|
|
144
|
+
const result = preflightSync(flow, options);
|
|
145
|
+
if (options.mcpServers === undefined) return result;
|
|
146
|
+
return probeMcp(result, options).then(async checked => {
|
|
147
|
+
if (!checked.ok || options.pluginSearchStart === undefined) return checked;
|
|
148
|
+
try { return { ...checked, plugins: await loadPlugins(options.pluginSearchStart) }; }
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!(error instanceof PluginError)) throw error;
|
|
151
|
+
return { ...checked, ok: false, diagnostics: [...checked.diagnostics, { severity: 'refusal' as const, kind: error.code, message: error.message }] };
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function probeMcp(result: PreflightResult, options: PreflightOptions): Promise<PreflightResult> {
|
|
157
|
+
if (!result.ok) return result;
|
|
158
|
+
const inventory: Record<string, readonly string[]> = Object.create(null);
|
|
159
|
+
for (const server of new Set(options.mcpServers)) {
|
|
160
|
+
try {
|
|
161
|
+
const session = await openMcpSession(options.mcp![server]!, 10_000);
|
|
162
|
+
try { inventory[server] = Object.freeze(await session.listTools()); }
|
|
163
|
+
finally { await session.close(); }
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const cause = error instanceof McpError ? error.code : 'handshake_rejected';
|
|
166
|
+
result.diagnostics.push({ severity: 'refusal', kind: 'mcp_unreachable', server, cause,
|
|
167
|
+
message: `MCP server "${server}" is unreachable: ${cause}.` });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { ...result, ok: !result.diagnostics.some(d => d.severity === 'refusal'), mcpTools: Object.freeze(inventory) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function preflightSync(flow: unknown, options: PreflightOptions): PreflightResult {
|
|
104
174
|
// Compile before touching any environment fact. `compileSpec` snapshots raw
|
|
105
175
|
// input into inert data, validates it against the closed authoring schema,
|
|
106
176
|
// and lowers `output` sugar into its json_schema gate — so the gate plan
|
|
@@ -108,20 +178,30 @@ export function preflight(flow: FlowSpec, options: PreflightOptions): PreflightR
|
|
|
108
178
|
// inspection ever reads a live accessor. The failure is a named refusal
|
|
109
179
|
// rather than a thrown error (RFC covenant 2), which is the contract main
|
|
110
180
|
// settled for this boundary.
|
|
111
|
-
let compiled:
|
|
181
|
+
let compiled: import('./compile.js').CompiledFlowSpec;
|
|
112
182
|
try {
|
|
113
183
|
compiled = compileSpec(flow);
|
|
114
184
|
} catch (error) {
|
|
115
185
|
const errors = error instanceof CompileError
|
|
116
186
|
? error.errors
|
|
117
187
|
: [error instanceof Error ? error.message : 'spec: expected JSON-compatible data'];
|
|
188
|
+
// BudgetSyntaxError now flows through CompileError with kind on it; the
|
|
189
|
+
// legacy raw-throw instanceof is preserved as a fallback so a caller that
|
|
190
|
+
// constructs preflight input through a different path still classifies.
|
|
191
|
+
const kind: PreflightDiagnostic['kind'] = error instanceof CompileError && error.kind === 'budget_syntax_invalid'
|
|
192
|
+
? 'budget_syntax_invalid'
|
|
193
|
+
: error instanceof CompileError && NAMED_GATE_FAILURE_KINDS.includes(error.kind as NamedGateFailureKind)
|
|
194
|
+
? error.kind as NamedGateFailureKind
|
|
195
|
+
: error instanceof BudgetSyntaxError
|
|
196
|
+
? 'budget_syntax_invalid'
|
|
197
|
+
: 'invalid_spec';
|
|
118
198
|
return {
|
|
119
199
|
ok: false,
|
|
120
200
|
gates: [],
|
|
121
201
|
resolutions: [],
|
|
122
202
|
diagnostics: [{
|
|
123
203
|
severity: 'refusal',
|
|
124
|
-
kind
|
|
204
|
+
kind,
|
|
125
205
|
message: `Relayflow spec is invalid: ${errors.join('; ')}`,
|
|
126
206
|
errors,
|
|
127
207
|
}],
|
|
@@ -133,11 +213,19 @@ export function preflight(flow: FlowSpec, options: PreflightOptions): PreflightR
|
|
|
133
213
|
const cliProbeResults = new Map<string, CliProbeOutcome>();
|
|
134
214
|
|
|
135
215
|
diagnostics.push(...unknownModelDiagnostics(compiled, options));
|
|
216
|
+
diagnostics.push(...scopeDiagnostics(compiled, options));
|
|
217
|
+
for (const server of new Set(options.mcpServers ?? [])) {
|
|
218
|
+
if (options.mcp !== undefined && Object.hasOwn(options.mcp, server)) continue;
|
|
219
|
+
diagnostics.push({ severity: 'refusal', kind: 'mcp_undeclared_server', server,
|
|
220
|
+
message: `MCP server "${server}" is not declared in the nearest flows.json mcp map.` });
|
|
221
|
+
}
|
|
222
|
+
diagnostics.push(...budgetDiagnostics(compiled));
|
|
136
223
|
// Resolve the complete flow before touching any environment fact. A later
|
|
137
224
|
// statically unresolved CLI makes the whole submission impossible, so no
|
|
138
225
|
// earlier command, provider/model, or trigger probe may run first.
|
|
139
226
|
for (const step of compiled.steps) {
|
|
140
227
|
if (step.type === 'deterministic') continue;
|
|
228
|
+
if (step.type === 'agent' && helperCall(step) !== undefined) continue;
|
|
141
229
|
const resolution = resolveCli(step, compiled, options.projectCli);
|
|
142
230
|
if (resolution === undefined) {
|
|
143
231
|
diagnostics.push({
|
|
@@ -156,9 +244,26 @@ export function preflight(flow: FlowSpec, options: PreflightOptions): PreflightR
|
|
|
156
244
|
}
|
|
157
245
|
|
|
158
246
|
for (const step of compiled.steps) {
|
|
247
|
+
probeNamedGate(step, options.probes, diagnostics);
|
|
159
248
|
warnOnVacuousGate(step, diagnostics);
|
|
160
249
|
warnOnUnprovableEffects(step, options.probes, diagnostics);
|
|
161
250
|
if (step.type === 'deterministic') continue;
|
|
251
|
+
const helper = step.type === 'agent' ? helperCall(step) : undefined;
|
|
252
|
+
if (helper !== undefined) {
|
|
253
|
+
try {
|
|
254
|
+
if (options.probes.helper === undefined) {
|
|
255
|
+
diagnostics.push({ severity: 'warning', kind: 'unprovable_effects', stepId: step.id,
|
|
256
|
+
message: `${helper.provider} helper requires a relayfile mount and an SDK agent worker.` });
|
|
257
|
+
} else if (!options.probes.helper(helper.provider)) {
|
|
258
|
+
diagnostics.push({ severity: 'refusal', kind: 'helper_mount_required', stepId: step.id,
|
|
259
|
+
message: `${helper.provider} helper requires a relayfile mount.` });
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
diagnostics.push({ severity: 'refusal', kind: 'probe_failed', stepId: step.id,
|
|
263
|
+
message: `${helper.provider} helper mount could not be checked.` });
|
|
264
|
+
}
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
162
267
|
const resolution = resolutionByStep.get(step.id)!;
|
|
163
268
|
probeResolvedCli(resolution, options.probes, cliProbeResults, diagnostics);
|
|
164
269
|
}
|
|
@@ -175,18 +280,55 @@ export function preflight(flow: FlowSpec, options: PreflightOptions): PreflightR
|
|
|
175
280
|
};
|
|
176
281
|
}
|
|
177
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Compile author-declared `workspace:` / `tools.fs:` grants against the nearest
|
|
285
|
+
* relayfile mount manifest. The compiler is pure; only mount discovery reads a
|
|
286
|
+
* fact about the filesystem. Missing manifest means no known mounts — a grant
|
|
287
|
+
* still parses but refuses as `mount_unknown` in that case.
|
|
288
|
+
*/
|
|
289
|
+
function scopeDiagnostics(flow: FlowSpec, options: PreflightOptions): PreflightRefusal[] {
|
|
290
|
+
const workspace = flow.workspace;
|
|
291
|
+
const toolsFs = flow.tools?.fs;
|
|
292
|
+
if (workspace === undefined && toolsFs === undefined) return [];
|
|
293
|
+
const input: ScopeInput = {
|
|
294
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
295
|
+
...(toolsFs === undefined ? {} : { tools: { fs: toolsFs } }),
|
|
296
|
+
};
|
|
297
|
+
let mounts: MountRegistry | undefined;
|
|
298
|
+
if (options.projectSearchStart !== undefined) {
|
|
299
|
+
try { mounts = readMountRegistry(options.projectSearchStart); }
|
|
300
|
+
catch { mounts = {}; /* fail closed — unreadable manifest treats every mount as unknown */ }
|
|
301
|
+
}
|
|
302
|
+
return compileScopes(input, mounts).diagnostics.map(refusal => ({
|
|
303
|
+
severity: 'refusal',
|
|
304
|
+
kind: refusal.kind,
|
|
305
|
+
message: refusal.message,
|
|
306
|
+
...(refusal.stepId === undefined ? {} : { stepId: refusal.stepId }),
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
|
|
178
310
|
/** Pure authoring validation: no executable, command, trigger, or daemon probe. */
|
|
179
311
|
function unknownModelDiagnostics(
|
|
180
312
|
flow: FlowSpec,
|
|
181
313
|
options: PreflightOptions,
|
|
182
314
|
): PreflightRefusal[] {
|
|
183
315
|
const diagnostics: PreflightRefusal[] = [];
|
|
316
|
+
// model_unknown is a governance check: it exists to enforce a project's
|
|
317
|
+
// registry-declared allowlist. When no flows.json is found, `check.ts` sends
|
|
318
|
+
// `models: []` with `modelRegistryPath: undefined` — an empty list not
|
|
319
|
+
// because the project forbids everything, but because no policy exists.
|
|
320
|
+
// Refusing an inline `agents: { drafter: { cli, model } }` declaration in
|
|
321
|
+
// that state forces every self-contained example flow to ship a second file.
|
|
322
|
+
// A real allowlist (even an empty one from a found flows.json) still
|
|
323
|
+
// enforces; that state is signalled by modelRegistryPath.
|
|
324
|
+
const enforceRegistry = options.modelRegistryPath !== undefined;
|
|
184
325
|
|
|
185
326
|
// Named declarations remain in the normalized authoring object until this
|
|
186
327
|
// boundary so even unused or step-shadowed models are checked. toKernelSpec
|
|
187
328
|
// erases the map and selector only after this pass has had a chance to fail.
|
|
188
329
|
for (const [agent, declaration] of Object.entries(flow.agents ?? {})) {
|
|
189
330
|
if (isKnownModel(declaration.model, options.models)) continue;
|
|
331
|
+
if (!enforceRegistry) continue;
|
|
190
332
|
diagnostics.push({
|
|
191
333
|
severity: 'refusal',
|
|
192
334
|
kind: 'model_unknown',
|
|
@@ -480,3 +622,35 @@ function firstCommandWord(command: string): string | undefined {
|
|
|
480
622
|
const match = rest.match(/^(?:"([^"]+)"|'([^']+)'|([^\s]+))/);
|
|
481
623
|
return match?.[1] ?? match?.[2] ?? match?.[3];
|
|
482
624
|
}
|
|
625
|
+
|
|
626
|
+
function probeNamedGate(step: StepSpec, probes: PreflightProbes, diagnostics: PreflightDiagnostic[]): void {
|
|
627
|
+
const gate = step.verification;
|
|
628
|
+
if (!isNamedGate(gate)) return;
|
|
629
|
+
const commands = ['node', ...(gate.type === 'word_count_bounds' ? ['wc'] : [])];
|
|
630
|
+
if (gate.type === 'subprocess_gate') commands.push(firstCommandWord(gate.command) ?? '');
|
|
631
|
+
for (const command of commands) {
|
|
632
|
+
let exists = false;
|
|
633
|
+
try { exists = command !== '' && probes.command(command); } catch { /* Fail closed on an unprovable gate. */ }
|
|
634
|
+
if (exists) continue;
|
|
635
|
+
diagnostics.push({ severity: 'refusal', kind: 'gate_command_missing', stepId: step.id,
|
|
636
|
+
message: `Step "${step.id}" ${gate.type} command "${command}" does not resolve as an executable.` });
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export { preflightHelpers } from './helper-preflight.js';
|
|
641
|
+
|
|
642
|
+
/** Authored memory probes run before invoking the body or contacting the journal. */
|
|
643
|
+
export async function preflightMemory(
|
|
644
|
+
probe: () => Promise<void>,
|
|
645
|
+
): Promise<PreflightRefusal | undefined> {
|
|
646
|
+
try {
|
|
647
|
+
await probe();
|
|
648
|
+
return undefined;
|
|
649
|
+
} catch {
|
|
650
|
+
return {
|
|
651
|
+
severity: 'refusal',
|
|
652
|
+
kind: 'memory_unreachable',
|
|
653
|
+
message: 'Script memory requires the ai-hist Node SDK and a readable SQLite database at AI_HIST_DB (or defaultDbPath()). Run ai-hist sync first.',
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
package/src/progress.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CompletionReason } from './protocol.js';
|
|
2
|
+
import type { StepType } from './spec.js';
|
|
3
|
+
|
|
4
|
+
export interface ProgressEvent {
|
|
5
|
+
type: 'step.started' | 'step.running' | 'step.completed' | 'step.failed';
|
|
6
|
+
stepId: string;
|
|
7
|
+
stepType: StepType;
|
|
8
|
+
elapsedMs: number;
|
|
9
|
+
completionReason?: CompletionReason;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Pure terminal rendering: caller owns the event source, clock, and output. */
|
|
13
|
+
export function renderProgress(events: Iterable<ProgressEvent>): string[] {
|
|
14
|
+
return Array.from(events, event => {
|
|
15
|
+
const icon = { 'step.started': '○', 'step.running': '↻', 'step.completed': '✓', 'step.failed': '✗' }[event.type];
|
|
16
|
+
const state = event.type.slice('step.'.length);
|
|
17
|
+
const agent = event.stepType === 'agent' ? ` [agent: ${state === 'started' ? 'preparing' : state}]` : '';
|
|
18
|
+
const reason = event.completionReason ? ` completionReason: ${event.completionReason}` : '';
|
|
19
|
+
// Agent-authored names cannot inject terminal control sequences.
|
|
20
|
+
const name = event.stepId.replace(/[\x00-\x1f\x7f-\x9f]/g, '?');
|
|
21
|
+
return `${icon} ${name} (${event.stepType})${agent} ${(Math.max(0, event.elapsedMs) / 1000).toFixed(2)}s${reason}`;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Observe the existing executor; success is emitted only after its journal read. */
|
|
26
|
+
export async function observeStep<T>(
|
|
27
|
+
stepId: string,
|
|
28
|
+
stepType: StepType,
|
|
29
|
+
execute: () => Promise<T>,
|
|
30
|
+
emit?: (event: ProgressEvent) => void,
|
|
31
|
+
): Promise<T> {
|
|
32
|
+
const publish = (event: ProgressEvent): void => {
|
|
33
|
+
try { emit?.(event); } catch {
|
|
34
|
+
// A projection failure must not turn a journaled success into a retry,
|
|
35
|
+
// or replace the executor's original failure. Surface it separately.
|
|
36
|
+
process.emitWarning(`Progress observer failed for ${event.type}.`, {
|
|
37
|
+
code: 'FLOWS_PROGRESS_OBSERVER_ERROR',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const started = performance.now();
|
|
42
|
+
publish({ type: 'step.started', stepId, stepType, elapsedMs: 0 });
|
|
43
|
+
try {
|
|
44
|
+
const result = await execute();
|
|
45
|
+
publish({ type: 'step.completed', stepId, stepType, elapsedMs: performance.now() - started, completionReason: 'success' });
|
|
46
|
+
return result;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
publish({ type: 'step.failed', stepId, stepType, elapsedMs: performance.now() - started });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/protocol.ts
CHANGED
|
@@ -69,7 +69,15 @@ export interface HelloResult {
|
|
|
69
69
|
server: string;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export interface StepSpend {
|
|
73
|
+
tokens_input: number;
|
|
74
|
+
tokens_output: number;
|
|
75
|
+
dollars: number;
|
|
76
|
+
wallclock_ms: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
export interface RunStartParams {
|
|
80
|
+
reuse_from_run_id?: string;
|
|
73
81
|
/**
|
|
74
82
|
* The kernel spec dialect — the ONE boundary shape `RunSpec::parse`
|
|
75
83
|
* accepts (snake_case, flat v0 verification, defaults materialized).
|
|
@@ -90,6 +98,7 @@ export interface RunOutcome {
|
|
|
90
98
|
export type RunStartResult = RunOutcome;
|
|
91
99
|
|
|
92
100
|
export interface RunResumeParams {
|
|
101
|
+
allow_human_influenced?: boolean;
|
|
93
102
|
run_id: string;
|
|
94
103
|
}
|
|
95
104
|
export type RunResumeResult = RunOutcome;
|
|
@@ -179,6 +188,8 @@ export interface RoutingDecision {
|
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
export interface StepDispatchEvent {
|
|
191
|
+
/** Selected successful outputs, resolved by the kernel from the journal. */
|
|
192
|
+
input?: Record<string, unknown>;
|
|
182
193
|
/** Durable choice; optional only for older kernel protocol compatibility. */
|
|
183
194
|
routing?: RoutingDecision;
|
|
184
195
|
/** Already journaled and charged; completion usage excludes this cost. */
|
|
@@ -287,6 +298,7 @@ export interface EffectConfirmResult {
|
|
|
287
298
|
}
|
|
288
299
|
|
|
289
300
|
export interface StepCompleteParams {
|
|
301
|
+
human_intervention?: boolean;
|
|
290
302
|
run_id: string;
|
|
291
303
|
step_id: string;
|
|
292
304
|
attempt: number;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createServer, type Socket } from 'node:net';
|
|
2
|
+
import { chmod, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export interface SidechannelContext {
|
|
6
|
+
dataDir: string;
|
|
7
|
+
runId: string;
|
|
8
|
+
stepId: string;
|
|
9
|
+
onReady?: (path: string) => void;
|
|
10
|
+
onDrive: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ptySocketPath(context: Pick<SidechannelContext, 'dataDir' | 'runId' | 'stepId'>): string {
|
|
14
|
+
for (const id of [context.runId, context.stepId]) {
|
|
15
|
+
if (!id || id === '.' || id === '..' || /[/\\\0]/.test(id)) throw new Error('Invalid sidechannel path component');
|
|
16
|
+
}
|
|
17
|
+
return join(resolve(context.dataDir), 'runs', context.runId, 'steps', context.stepId, 'pty.sock');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Best-effort live bytes. Slow peers are dropped; they never pause execution. */
|
|
21
|
+
export async function openSidechannel(
|
|
22
|
+
context: SidechannelContext,
|
|
23
|
+
input: (bytes: Buffer) => boolean | Promise<boolean>,
|
|
24
|
+
canDrive: () => boolean = () => true,
|
|
25
|
+
) {
|
|
26
|
+
const peers = new Map<Socket, boolean>();
|
|
27
|
+
let closed = false;
|
|
28
|
+
const server = createServer(socket => {
|
|
29
|
+
if (peers.size >= 16) { socket.destroy(); return; }
|
|
30
|
+
peers.set(socket, false);
|
|
31
|
+
let hello = Buffer.alloc(0);
|
|
32
|
+
let mode: string | undefined;
|
|
33
|
+
socket.setTimeout(2_000, () => socket.destroy());
|
|
34
|
+
socket.on('error', () => socket.destroy());
|
|
35
|
+
socket.on('close', () => peers.delete(socket));
|
|
36
|
+
socket.on('data', (bytes: Buffer) => {
|
|
37
|
+
if (mode === undefined) {
|
|
38
|
+
hello = Buffer.concat([hello, bytes]);
|
|
39
|
+
const end = hello.indexOf(10);
|
|
40
|
+
if (end < 0) { if (hello.length > 32) socket.destroy(); return; }
|
|
41
|
+
const line = hello.subarray(0, end).toString('utf8');
|
|
42
|
+
if (!['HELLO view', 'HELLO drive', 'HELLO passthrough'].includes(line)) { socket.destroy(); return; }
|
|
43
|
+
mode = line.slice(6);
|
|
44
|
+
if (mode === 'drive' && !canDrive()) { socket.destroy(); return; }
|
|
45
|
+
socket.setTimeout(0);
|
|
46
|
+
peers.set(socket, true);
|
|
47
|
+
// Passthrough is a passive raw-byte view in this initial slice.
|
|
48
|
+
if (mode === 'drive') context.onDrive();
|
|
49
|
+
bytes = hello.subarray(end + 1);
|
|
50
|
+
hello = Buffer.alloc(0);
|
|
51
|
+
}
|
|
52
|
+
if (mode === 'drive' && bytes.length > 0) {
|
|
53
|
+
socket.pause();
|
|
54
|
+
void Promise.resolve().then(() => input(bytes)).then(accepted => {
|
|
55
|
+
if (!accepted) socket.destroy();
|
|
56
|
+
else if (!socket.destroyed) socket.resume();
|
|
57
|
+
}, () => socket.destroy());
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
server.on('error', () => { for (const peer of peers.keys()) peer.destroy(); });
|
|
62
|
+
try {
|
|
63
|
+
const path = ptySocketPath(context);
|
|
64
|
+
// Restrict traversal as well as socket access to the worker's OS user.
|
|
65
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
66
|
+
await chmod(dirname(path), 0o700);
|
|
67
|
+
await new Promise<void>((resolve, reject) => {
|
|
68
|
+
server.once('error', reject);
|
|
69
|
+
server.listen(path, () => { server.off('error', reject); resolve(); });
|
|
70
|
+
});
|
|
71
|
+
await chmod(path, 0o600);
|
|
72
|
+
context.onReady?.(path);
|
|
73
|
+
} catch {
|
|
74
|
+
// Never unlink a pre-existing socket: it may belong to a live attempt.
|
|
75
|
+
if (server.listening) server.close();
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
publish(bytes: Buffer) {
|
|
80
|
+
if (closed) return;
|
|
81
|
+
for (const [peer, ready] of peers) {
|
|
82
|
+
if (ready && !peer.write(bytes)) peer.destroy();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
close() {
|
|
86
|
+
if (closed) return;
|
|
87
|
+
closed = true;
|
|
88
|
+
for (const peer of peers.keys()) peer.destroy();
|
|
89
|
+
server.close();
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { snapshotJsonValue } from './json-value.js';
|
|
2
|
+
|
|
3
|
+
export const SCOPE_MODES = ['readonly', 'readwrite', 'append'] as const;
|
|
4
|
+
export type ScopeMode = (typeof SCOPE_MODES)[number];
|
|
5
|
+
export type ScopeGrants = string | readonly string[];
|
|
6
|
+
|
|
7
|
+
/** Inert path-prefix permission; paths are relative to the named mount. */
|
|
8
|
+
export interface ScopeDescriptor {
|
|
9
|
+
mount: string;
|
|
10
|
+
path: string;
|
|
11
|
+
mode: ScopeMode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Facts supplied by relayfile's mount manifest, never an author-side ACL. */
|
|
15
|
+
export type MountRegistry = Readonly<Record<string, readonly {
|
|
16
|
+
path: string;
|
|
17
|
+
modes: readonly ScopeMode[];
|
|
18
|
+
}[]>>;
|
|
19
|
+
|
|
20
|
+
export interface ScopeDeclaration {
|
|
21
|
+
source: string;
|
|
22
|
+
stepId?: string;
|
|
23
|
+
grant: string;
|
|
24
|
+
scope: ScopeDescriptor;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ScopeRefusal {
|
|
28
|
+
severity: 'refusal';
|
|
29
|
+
kind: 'scope_syntax_invalid' | 'mount_unknown' | 'scope_ungrantable';
|
|
30
|
+
message: string;
|
|
31
|
+
source: string;
|
|
32
|
+
stepId?: string;
|
|
33
|
+
grant?: string;
|
|
34
|
+
scope?: ScopeDescriptor;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ScopeCompilation {
|
|
38
|
+
scopes: ScopeDeclaration[];
|
|
39
|
+
diagnostics: ScopeRefusal[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ScopeInput {
|
|
43
|
+
workspace?: ScopeGrants;
|
|
44
|
+
tools?: { fs?: ScopeGrants };
|
|
45
|
+
steps?: readonly { id: string; workspace?: ScopeGrants; tools?: { fs?: ScopeGrants } }[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Reject ambiguous or traversing paths instead of normalizing permissions. */
|
|
49
|
+
export function isScopePath(path: unknown): path is string {
|
|
50
|
+
return typeof path === 'string' && path.length > 0
|
|
51
|
+
&& path.split('/').every(part => /^[A-Za-z0-9._-]+$/.test(part)
|
|
52
|
+
&& part !== '.' && part !== '..');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function parseScopeGrant(grant: string): ScopeDescriptor | undefined {
|
|
56
|
+
const match = /^([A-Za-z0-9_-]+)\/(.+):\s*(readonly|readwrite|append)$/.exec(grant);
|
|
57
|
+
if (match === null || match[0] !== grant || !isScopePath(match[2])) return undefined;
|
|
58
|
+
return { mount: match[1]!, path: match[2]!, mode: match[3] as ScopeMode };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** One pure pass collects every declaration error before any live probe. */
|
|
62
|
+
export function compileScopes(input: ScopeInput, mounts?: MountRegistry): ScopeCompilation {
|
|
63
|
+
const result: ScopeCompilation = { scopes: [], diagnostics: [] };
|
|
64
|
+
// Public callers may be JS: never run a getter while collecting permissions.
|
|
65
|
+
const flow = snapshotJsonValue(input, 'scope declarations') as unknown as ScopeInput;
|
|
66
|
+
function collect(value: unknown, source: string, stepId?: string): void {
|
|
67
|
+
if (value === undefined) return;
|
|
68
|
+
const location = { source, ...(stepId === undefined ? {} : { stepId }) };
|
|
69
|
+
const grants = Array.isArray(value) ? value : [value];
|
|
70
|
+
for (const grant of grants) {
|
|
71
|
+
const scope = typeof grant === 'string' ? parseScopeGrant(grant) : undefined;
|
|
72
|
+
if (scope === undefined) {
|
|
73
|
+
result.diagnostics.push({
|
|
74
|
+
...location, severity: 'refusal', kind: 'scope_syntax_invalid',
|
|
75
|
+
...(typeof grant === 'string' ? { grant } : {}),
|
|
76
|
+
message: `${source}: expected "<mount>/<path>: readonly|readwrite|append" with a canonical path prefix.`,
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const declaration = { ...location, grant: grant as string, scope };
|
|
81
|
+
result.scopes.push(declaration);
|
|
82
|
+
if (mounts === undefined) continue; // Compiler does not invent environment facts.
|
|
83
|
+
if (!Object.hasOwn(mounts, scope.mount)) {
|
|
84
|
+
result.diagnostics.push({
|
|
85
|
+
...declaration, severity: 'refusal', kind: 'mount_unknown',
|
|
86
|
+
message: `${source}: mount "${scope.mount}" is not present in the relayfile mount manifest.`,
|
|
87
|
+
});
|
|
88
|
+
} else if (!mounts[scope.mount]!.some(available =>
|
|
89
|
+
available.modes.includes(scope.mode)
|
|
90
|
+
&& (scope.path === available.path || scope.path.startsWith(`${available.path}/`)))) {
|
|
91
|
+
result.diagnostics.push({
|
|
92
|
+
...declaration, severity: 'refusal', kind: 'scope_ungrantable',
|
|
93
|
+
message: `${source}: relayfile cannot grant "${grant}" from the mount's available path permissions.`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
collect(flow.workspace, 'workspace');
|
|
99
|
+
collect(flow.tools?.fs, 'tools.fs');
|
|
100
|
+
for (const step of flow.steps ?? []) {
|
|
101
|
+
collect(step.workspace, `step "${step.id}".workspace`, step.id);
|
|
102
|
+
collect(step.tools?.fs, `step "${step.id}".tools.fs`, step.id);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { helperProviders } from '@relayflows/surface/runtime';
|
|
4
|
+
import { preflightHelpers } from './preflight.js';
|
|
5
|
+
|
|
6
|
+
export function providerMount(provider: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
7
|
+
const root = [env.RELAYFILE_MOUNT_PATH, env.WORKSPACE_ROOT, env.WORKFORCE_SANDBOX_ROOT,
|
|
8
|
+
env.RELAYFILE_MOUNT_ROOT, env.RELAYFILE_ROOT].find(value => value?.trim());
|
|
9
|
+
if (!root?.trim()) return undefined;
|
|
10
|
+
try { return statSync(join(root, provider)).isDirectory() ? root : undefined; }
|
|
11
|
+
catch { return undefined; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const slackMount = (env: NodeJS.ProcessEnv = process.env) => providerMount('slack', env);
|
|
15
|
+
|
|
16
|
+
export function checkProviderHelpers(definition: Parameters<typeof preflightHelpers>[0]) {
|
|
17
|
+
return preflightHelpers(definition, {
|
|
18
|
+
providers: Object.fromEntries(helperProviders.map(p => [p.provider, {
|
|
19
|
+
mount: providerMount(p.provider) !== undefined,
|
|
20
|
+
mock: process.env[p.mockEnv] === '1',
|
|
21
|
+
token: p.provider === 'slack' ? process.env.SLACK_BOT_TOKEN : undefined,
|
|
22
|
+
}])),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export const checkSlackHelpers = checkProviderHelpers;
|