@relayflows/sdk 2.0.9 → 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 +2 -21
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +1 -12
- 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 +116 -235
- 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 -18
- 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 -10
- package/src/authored-flow-executor.ts +171 -347
- 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 -18
- 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
package/src/worker-cli.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
import { decodeProviderResult, decodeWrapperResult, requirePricedUsage } from './worker-usage.js';
|
|
2
|
+
import { openSidechannel, type SidechannelContext } from './pty-sidechannel.js';
|
|
1
3
|
import { spawn } from 'node:child_process';
|
|
4
|
+
import { childStop, ownsProcessGroup } from './child-stop.js';
|
|
2
5
|
import {
|
|
3
6
|
agentExecution,
|
|
7
|
+
llmExecution,
|
|
4
8
|
cliAdapterKind,
|
|
5
9
|
type CliInvocation,
|
|
10
|
+
type CliAdapterKind,
|
|
6
11
|
} from './cli-adapter.js';
|
|
7
12
|
import {
|
|
8
13
|
runWrapperSession,
|
|
9
14
|
type WrapperSessionLimits,
|
|
10
15
|
} from './wrapper-session.js';
|
|
11
16
|
import { wrapperEnvironment } from './wrapper-runtime.js';
|
|
17
|
+
import {
|
|
18
|
+
agentRelaySpawn,
|
|
19
|
+
deriveAgentName,
|
|
20
|
+
AgentRelayTransportError,
|
|
21
|
+
type AgentTransport,
|
|
22
|
+
} from './agent-relay-transport.js';
|
|
12
23
|
|
|
13
24
|
/** Present only when a dispatched agent step carries a journaled wake context. */
|
|
14
25
|
export const WAKE_CONTEXT_ENV = 'RELAYFLOW_WAKE_CONTEXT';
|
|
@@ -21,35 +32,61 @@ export const WAKE_CONTEXT_ENV = 'RELAYFLOW_WAKE_CONTEXT';
|
|
|
21
32
|
export const MODEL_ENV = 'RELAYFLOW_MODEL';
|
|
22
33
|
|
|
23
34
|
export interface WorkerCliResult {
|
|
35
|
+
tokens_input?: number;
|
|
36
|
+
tokens_output?: number;
|
|
24
37
|
exit_code: number | null;
|
|
25
38
|
stdout_tail: string;
|
|
26
39
|
stderr_tail: string;
|
|
27
40
|
}
|
|
28
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Optional identity threaded through so the relay transport can derive a
|
|
44
|
+
* stable, DM-addressable agent name (flows#385).
|
|
45
|
+
*/
|
|
46
|
+
export interface AgentRelayContext {
|
|
47
|
+
runId: string;
|
|
48
|
+
stepId: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
export async function runAgentCli(
|
|
30
52
|
cli: string,
|
|
31
53
|
instruction: string,
|
|
32
54
|
wakeContext: unknown,
|
|
33
55
|
model?: string,
|
|
34
56
|
wrapperLimits?: Partial<WrapperSessionLimits>,
|
|
57
|
+
signal?: AbortSignal,
|
|
58
|
+
mode: 'agent' | 'llm' = 'agent',
|
|
59
|
+
sidechannel?: SidechannelContext,
|
|
60
|
+
cwd?: string,
|
|
61
|
+
transport: AgentTransport = 'direct',
|
|
62
|
+
relayContext?: AgentRelayContext,
|
|
35
63
|
): Promise<WorkerCliResult> {
|
|
64
|
+
signal?.throwIfAborted();
|
|
65
|
+
if (signal !== undefined && process.platform === 'win32') {
|
|
66
|
+
throw new Error('Lease-bound agent execution requires macOS or Linux process-group cancellation; Windows is unsupported.');
|
|
67
|
+
}
|
|
36
68
|
const kind = cliAdapterKind(cli);
|
|
37
69
|
|
|
38
70
|
if (kind === 'relayflows-wrapper-v1') {
|
|
39
|
-
return runWrapperSession(
|
|
71
|
+
return requirePricedUsage(decodeWrapperResult(await runWrapperSession(
|
|
40
72
|
cli,
|
|
41
73
|
instruction,
|
|
42
74
|
wakeContext,
|
|
43
75
|
model,
|
|
44
76
|
wrapperEnvironment(process.env),
|
|
45
77
|
wrapperLimits,
|
|
46
|
-
|
|
78
|
+
signal,
|
|
79
|
+
)), model);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (mode === 'agent' && transport === 'relay') {
|
|
83
|
+
return runViaAgentRelay(kind, instruction, model, relayContext, cwd);
|
|
47
84
|
}
|
|
48
85
|
|
|
49
86
|
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
50
87
|
delete env[WAKE_CONTEXT_ENV];
|
|
51
88
|
delete env[MODEL_ENV];
|
|
52
|
-
const invocation = agentExecution(kind, instruction, model);
|
|
89
|
+
const invocation = mode === 'llm' ? llmExecution(kind, instruction, model) : agentExecution(kind, instruction, model);
|
|
53
90
|
|
|
54
91
|
if (wakeContext !== undefined) {
|
|
55
92
|
try {
|
|
@@ -64,16 +101,100 @@ export async function runAgentCli(
|
|
|
64
101
|
}
|
|
65
102
|
|
|
66
103
|
if (invocation.modelEnv !== undefined) env[MODEL_ENV] = invocation.modelEnv;
|
|
67
|
-
|
|
104
|
+
// Structured provider output carries the authoritative token counts.
|
|
105
|
+
const args = [...invocation.args];
|
|
106
|
+
args.splice(args.length - 1, 0, ...(kind === 'claude' ? ['--output-format', 'json'] : ['--json']));
|
|
107
|
+
return requirePricedUsage(decodeProviderResult(await spawnInvocation(cli, { ...invocation, args }, env, signal, sidechannel, cwd), kind), model);
|
|
68
108
|
}
|
|
69
109
|
|
|
70
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Relay-transport path (flows#385). Fires an agent-relay spawn HTTP request
|
|
112
|
+
* that registers the CLI as a first-class workspace participant DMs can
|
|
113
|
+
* steer, then reports the spawn outcome as a WorkerCliResult. Streaming
|
|
114
|
+
* completion tracking is a follow-up — this initial slice proves the
|
|
115
|
+
* transport wire and returns quickly with the registered name in stdout.
|
|
116
|
+
*/
|
|
117
|
+
async function runViaAgentRelay(
|
|
118
|
+
kind: CliAdapterKind,
|
|
119
|
+
instruction: string,
|
|
120
|
+
model: string | undefined,
|
|
121
|
+
relayContext: AgentRelayContext | undefined,
|
|
122
|
+
worker_cwd: string | undefined,
|
|
123
|
+
): Promise<WorkerCliResult> {
|
|
124
|
+
if (kind === 'relayflows-wrapper-v1') {
|
|
125
|
+
return {
|
|
126
|
+
exit_code: null,
|
|
127
|
+
stdout_tail: '',
|
|
128
|
+
stderr_tail: 'agent-relay transport does not support the relayflows-wrapper-v1 same-process session.',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (relayContext === undefined) {
|
|
132
|
+
return {
|
|
133
|
+
exit_code: null,
|
|
134
|
+
stdout_tail: '',
|
|
135
|
+
stderr_tail: 'agent-relay transport requires a relayContext (runId, stepId); the caller did not thread it through.',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const name = deriveAgentName(relayContext.runId, relayContext.stepId);
|
|
139
|
+
try {
|
|
140
|
+
const handle = await agentRelaySpawn({
|
|
141
|
+
name,
|
|
142
|
+
cli: kind,
|
|
143
|
+
task: instruction,
|
|
144
|
+
...(model === undefined ? {} : { model }),
|
|
145
|
+
...(worker_cwd === undefined ? {} : { worker_cwd }),
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
exit_code: 0,
|
|
149
|
+
stdout_tail: JSON.stringify({ registeredName: handle.registeredName, invocationId: handle.invocationId }),
|
|
150
|
+
stderr_tail: '',
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const detail = error instanceof AgentRelayTransportError
|
|
154
|
+
? error.message
|
|
155
|
+
: `agent-relay spawn failed: ${(error as Error).message ?? String(error)}`;
|
|
156
|
+
return { exit_code: null, stdout_tail: '', stderr_tail: detail };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function spawnInvocation(
|
|
71
161
|
cli: string,
|
|
72
162
|
invocation: CliInvocation,
|
|
73
163
|
env: NodeJS.ProcessEnv,
|
|
164
|
+
signal?: AbortSignal,
|
|
165
|
+
sidechannel?: SidechannelContext,
|
|
166
|
+
cwd?: string,
|
|
74
167
|
): Promise<WorkerCliResult> {
|
|
168
|
+
let writeInput: (bytes: Buffer) => Promise<boolean> = async () => false;
|
|
169
|
+
let canDrive = () => false;
|
|
170
|
+
let driven = false;
|
|
171
|
+
const channel = sidechannel === undefined ? undefined : await openSidechannel({
|
|
172
|
+
...sidechannel,
|
|
173
|
+
onDrive() { driven = true; sidechannel.onDrive(); },
|
|
174
|
+
}, bytes => writeInput(bytes), () => canDrive());
|
|
175
|
+
if (signal?.aborted) { channel?.close(); signal.throwIfAborted(); }
|
|
75
176
|
return new Promise((resolve) => {
|
|
76
|
-
const
|
|
177
|
+
const ownsGroup = ownsProcessGroup(signal);
|
|
178
|
+
const child = spawn(cli, invocation.args, {
|
|
179
|
+
stdio: ['pipe', 'pipe', 'pipe'], env,
|
|
180
|
+
detached: ownsGroup,
|
|
181
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
182
|
+
});
|
|
183
|
+
child.stdin.on('error', () => {});
|
|
184
|
+
if (channel === undefined) child.stdin.end();
|
|
185
|
+
canDrive = () => !child.stdin.destroyed && !child.stdin.writableEnded;
|
|
186
|
+
// A pipe cannot be reopened after EOF. Give startup subscribers a bounded
|
|
187
|
+
// chance to opt into drive, then let unattended/view-only CLIs read EOF.
|
|
188
|
+
const inputTimer = channel === undefined ? undefined : setTimeout(() => {
|
|
189
|
+
if (!driven) child.stdin.end();
|
|
190
|
+
}, 100);
|
|
191
|
+
writeInput = bytes => new Promise(resolve => {
|
|
192
|
+
if (!canDrive()) { resolve(false); return; }
|
|
193
|
+
// write(false) still accepts the bytes. The completion callback waits
|
|
194
|
+
// until they flush; the sidechannel pauses its reader in the meantime.
|
|
195
|
+
child.stdin.write(bytes, error => resolve(!error));
|
|
196
|
+
});
|
|
197
|
+
const stop = childStop(child, ownsGroup);
|
|
77
198
|
const stdout: Buffer[] = [];
|
|
78
199
|
const stderr: Buffer[] = [];
|
|
79
200
|
let settled = false;
|
|
@@ -81,24 +202,48 @@ function spawnInvocation(
|
|
|
81
202
|
const finish = (result: WorkerCliResult): void => {
|
|
82
203
|
if (settled) return;
|
|
83
204
|
settled = true;
|
|
205
|
+
if (inputTimer !== undefined) clearTimeout(inputTimer);
|
|
206
|
+
channel?.close();
|
|
84
207
|
if (timer !== undefined) clearTimeout(timer);
|
|
208
|
+
signal?.removeEventListener('abort', onAbort);
|
|
85
209
|
resolve(result);
|
|
86
210
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
211
|
+
const onAbort = (): void => {
|
|
212
|
+
stop.kill();
|
|
213
|
+
finish({ exit_code: null, stdout_tail: '', stderr_tail: 'Agent execution aborted: lease ownership lost.' });
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Same invariant as `wrapper-session.ts`: `'close'` and `'error'` are
|
|
217
|
+
* evidence about the DIRECT CHILD, so they may not settle over a pending
|
|
218
|
+
* escalation, and only `maySettleOnChildExit` may drop one. This settle
|
|
219
|
+
* carries no deadline of its own because it needs none — the timeout below
|
|
220
|
+
* settles on the spot and lets its escalation outlive that, so refusing
|
|
221
|
+
* here can only defer to a `'close'` we are still going to get.
|
|
222
|
+
*/
|
|
223
|
+
const finishOnChildExit = (result: WorkerCliResult): void => {
|
|
224
|
+
if (!stop.maySettleOnChildExit()) return;
|
|
225
|
+
finish(result);
|
|
226
|
+
};
|
|
227
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
228
|
+
if (signal?.aborted) onAbort();
|
|
229
|
+
child.stdout.on('data', (chunk: Buffer) => { stdout.push(chunk); channel?.publish(chunk); });
|
|
230
|
+
child.stderr.on('data', (chunk: Buffer) => { stderr.push(chunk); channel?.publish(chunk); });
|
|
231
|
+
child.once('error', (error) => finishOnChildExit({
|
|
90
232
|
exit_code: null,
|
|
91
233
|
stdout_tail: Buffer.concat(stdout).toString('utf8'),
|
|
92
234
|
stderr_tail: error.message,
|
|
93
235
|
}));
|
|
94
|
-
child.once('close', (code) =>
|
|
236
|
+
child.once('close', (code) => finishOnChildExit({
|
|
95
237
|
exit_code: code,
|
|
96
238
|
stdout_tail: Buffer.concat(stdout).toString('utf8'),
|
|
97
239
|
stderr_tail: Buffer.concat(stderr).toString('utf8'),
|
|
98
240
|
}));
|
|
99
241
|
if (invocation.timeoutMs > 0) {
|
|
100
242
|
timer = setTimeout(() => {
|
|
101
|
-
|
|
243
|
+
// The stop outlives this settle on purpose: `finish` resolves the step,
|
|
244
|
+
// but only the forced group kill releases the pipes a leaked descendant
|
|
245
|
+
// is holding, and until they are released `flows run` cannot exit.
|
|
246
|
+
stop.terminate();
|
|
102
247
|
finish({
|
|
103
248
|
exit_code: null,
|
|
104
249
|
stdout_tail: Buffer.concat(stdout).toString('utf8'),
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StepDispatchEvent } from './protocol.js';
|
|
2
|
+
|
|
3
|
+
/** Memory precedes the final, visibly delimited JSON input segment. */
|
|
4
|
+
export function workerInstruction(instruction: string, dispatch: StepDispatchEvent): string {
|
|
5
|
+
const withMemory = dispatch.memory === undefined ? instruction
|
|
6
|
+
: `${instruction}\n\nMemory context (journaled):\n${JSON.stringify(dispatch.memory.pack)}`;
|
|
7
|
+
if (dispatch.input === undefined) {
|
|
8
|
+
if (typeof dispatch.spec === 'object' && dispatch.spec !== null
|
|
9
|
+
&& 'input' in dispatch.spec) {
|
|
10
|
+
throw new Error('Step declares input bindings but the kernel did not resolve them; upgrade relayflowd.');
|
|
11
|
+
}
|
|
12
|
+
return withMemory;
|
|
13
|
+
}
|
|
14
|
+
return `${withMemory}\n\ninput:\n${JSON.stringify(dispatch.input)}`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { JournalClient } from './journal-client.js';
|
|
2
|
+
import type { StepDispatchEvent } from './protocol.js';
|
|
3
|
+
|
|
4
|
+
/** Hold the dispatched lease only while its subprocess is still ours to run. */
|
|
5
|
+
export async function withWorkerLease<T>(
|
|
6
|
+
client: JournalClient,
|
|
7
|
+
dispatch: StepDispatchEvent,
|
|
8
|
+
execute: (signal: AbortSignal) => Promise<T>,
|
|
9
|
+
): Promise<T> {
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
let stopped = false;
|
|
12
|
+
let latestDeadline = dispatch.lease_deadline_ms;
|
|
13
|
+
let renewalTimer: NodeJS.Timeout | undefined;
|
|
14
|
+
let expiryTimer: NodeJS.Timeout | undefined;
|
|
15
|
+
let pending: Promise<void> = Promise.resolve();
|
|
16
|
+
const fail = (error: unknown): void => { controller.abort(error); };
|
|
17
|
+
const armExpiry = (deadline: number): number => {
|
|
18
|
+
const remaining = deadline - Date.now();
|
|
19
|
+
if (!Number.isFinite(remaining) || remaining <= 0) {
|
|
20
|
+
throw new Error(`Agent lease is already expired for ${dispatch.run_id}/${dispatch.step_id}.`);
|
|
21
|
+
}
|
|
22
|
+
latestDeadline = deadline;
|
|
23
|
+
if (expiryTimer !== undefined) clearTimeout(expiryTimer);
|
|
24
|
+
expiryTimer = setTimeout(() => fail(new Error(
|
|
25
|
+
`Agent lease expired before renewal for ${dispatch.run_id}/${dispatch.step_id}.`,
|
|
26
|
+
)), remaining);
|
|
27
|
+
return remaining;
|
|
28
|
+
};
|
|
29
|
+
const renew = async (): Promise<void> => {
|
|
30
|
+
const result = await untilAborted(client.stepHeartbeat(
|
|
31
|
+
dispatch.run_id, dispatch.step_id, dispatch.attempt, dispatch.lease_id,
|
|
32
|
+
), controller.signal);
|
|
33
|
+
controller.signal.throwIfAborted();
|
|
34
|
+
// A response handled after local expiry cannot revive ownership, even
|
|
35
|
+
// if its future deadline was issued before this event loop stalled.
|
|
36
|
+
if (Date.now() >= latestDeadline) {
|
|
37
|
+
throw new Error(`Agent lease expired before renewal for ${dispatch.run_id}/${dispatch.step_id}.`);
|
|
38
|
+
}
|
|
39
|
+
const remaining = armExpiry(result.lease_deadline_ms);
|
|
40
|
+
if (!stopped) {
|
|
41
|
+
renewalTimer = setTimeout(() => {
|
|
42
|
+
pending = renew().catch(fail);
|
|
43
|
+
}, Math.max(1, Math.floor(remaining / 3)));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
armExpiry(dispatch.lease_deadline_ms);
|
|
48
|
+
// Establish ownership before starting an effectful process.
|
|
49
|
+
await renew();
|
|
50
|
+
const result = await execute(controller.signal);
|
|
51
|
+
stopped = true;
|
|
52
|
+
if (renewalTimer !== undefined) clearTimeout(renewalTimer);
|
|
53
|
+
// Drain any renewal before the caller sends step.complete. A renewal
|
|
54
|
+
// racing after completion would otherwise report a spurious lease error.
|
|
55
|
+
await pending;
|
|
56
|
+
controller.signal.throwIfAborted();
|
|
57
|
+
// Timer callbacks can be delayed behind a resolved subprocess promise.
|
|
58
|
+
// Check the clock itself before permitting step.complete.
|
|
59
|
+
if (Date.now() >= latestDeadline) {
|
|
60
|
+
throw new Error(`Agent lease expired before completion for ${dispatch.run_id}/${dispatch.step_id}.`);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
} finally {
|
|
64
|
+
stopped = true;
|
|
65
|
+
controller.abort(new Error('Worker lease scope ended.'));
|
|
66
|
+
if (renewalTimer !== undefined) clearTimeout(renewalTimer);
|
|
67
|
+
if (expiryTimer !== undefined) clearTimeout(expiryTimer);
|
|
68
|
+
await pending;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function untilAborted<T>(request: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const abort = (): void => { reject(signal.reason); };
|
|
75
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
76
|
+
request.then(value => {
|
|
77
|
+
signal.removeEventListener('abort', abort);
|
|
78
|
+
resolve(value);
|
|
79
|
+
}, error => {
|
|
80
|
+
signal.removeEventListener('abort', abort);
|
|
81
|
+
reject(error);
|
|
82
|
+
});
|
|
83
|
+
if (signal.aborted) abort();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { pricedUsage } from './model-pricing.js';
|
|
2
|
+
import type { WorkerCliResult } from './worker-cli.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Attach token/dollar usage to a worker's CLI result. Invalid token counts
|
|
6
|
+
* (non-integer or negative) are the one remaining failure mode — those are
|
|
7
|
+
* journaled as `worker_error` with the usage projected from clamped counts.
|
|
8
|
+
*
|
|
9
|
+
* Unpriced models are NOT a failure here: `pricedUsage` returns
|
|
10
|
+
* `dollars: null` and preflight (see `budgetDiagnostics`) has already refused
|
|
11
|
+
* declared dollar budgets against unpriced models before the CLI dispatched.
|
|
12
|
+
*/
|
|
13
|
+
export function workerSpend(result: WorkerCliResult, model?: string) {
|
|
14
|
+
try { return { result, usage: pricedUsage(model, result.tokens_input, result.tokens_output) }; }
|
|
15
|
+
catch (error) {
|
|
16
|
+
return {
|
|
17
|
+
result: { ...result, exit_code: null, stderr_tail: error instanceof Error ? error.message : 'Invalid model usage' },
|
|
18
|
+
usage: pricedUsage(undefined,
|
|
19
|
+
Number.isSafeInteger(result.tokens_input) && result.tokens_input! >= 0 ? result.tokens_input : 0,
|
|
20
|
+
Number.isSafeInteger(result.tokens_output) && result.tokens_output! >= 0 ? result.tokens_output : 0),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { WorkerCliResult } from './worker-cli.js';
|
|
2
|
+
import { MODEL_PRICING } from './model-pricing.js';
|
|
3
|
+
|
|
4
|
+
type RecordValue = Record<string, unknown>;
|
|
5
|
+
const record = (value: unknown): value is RecordValue => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
const count = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
7
|
+
|
|
8
|
+
function invalid(result: WorkerCliResult, detail: string): WorkerCliResult {
|
|
9
|
+
return { ...result, exit_code: null, stderr_tail: `${result.stderr_tail}\n${detail}`.trim() };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function usageResult(result: WorkerCliResult, usage: unknown, output: unknown): WorkerCliResult {
|
|
13
|
+
if (output === undefined) return invalid(result, 'Provider completion is missing its output.');
|
|
14
|
+
if (!record(usage) || !count(usage.input_tokens) || !count(usage.output_tokens)) {
|
|
15
|
+
return invalid(result, 'Provider completion has invalid token usage.');
|
|
16
|
+
}
|
|
17
|
+
return { ...result, stdout_tail: typeof output === 'string' ? output : JSON.stringify(output),
|
|
18
|
+
tokens_input: usage.input_tokens, tokens_output: usage.output_tokens };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Provider envelopes are execution metadata, never the authored output. */
|
|
22
|
+
export function decodeProviderResult(result: WorkerCliResult, kind: 'claude' | 'codex'): WorkerCliResult {
|
|
23
|
+
const frames: RecordValue[] = [];
|
|
24
|
+
for (const line of result.stdout_tail.split('\n')) {
|
|
25
|
+
try { const frame: unknown = JSON.parse(line); if (record(frame)) frames.push(frame); } catch { /* Plain text remains output. */ }
|
|
26
|
+
}
|
|
27
|
+
const terminal = [...frames].reverse().find(f => kind === 'claude' ? f.type === 'result' : f.type === 'turn.completed');
|
|
28
|
+
if (terminal === undefined) return result;
|
|
29
|
+
const text = kind === 'claude' ? terminal.result : frames
|
|
30
|
+
.flatMap(f => f.type === 'item.completed' && record(f.item) && f.item.type === 'agent_message' && typeof f.item.text === 'string' ? [f.item.text] : []).join('\n');
|
|
31
|
+
return usageResult(result, terminal.usage, text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Optional wrapper result envelope; existing opaque text output stays valid. */
|
|
35
|
+
export function decodeWrapperResult(result: WorkerCliResult): WorkerCliResult {
|
|
36
|
+
let value: unknown;
|
|
37
|
+
try { value = JSON.parse(result.stdout_tail); } catch { return result; }
|
|
38
|
+
if (!record(value) || value.protocol !== 'relayflows-agent-cli-v1-result') return result;
|
|
39
|
+
return usageResult(result, value.usage, value.output);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function requirePricedUsage(result: WorkerCliResult, model?: string): WorkerCliResult {
|
|
43
|
+
if (model !== undefined && Object.hasOwn(MODEL_PRICING, model)
|
|
44
|
+
&& (result.tokens_input === undefined || result.tokens_output === undefined)) {
|
|
45
|
+
return invalid(result, 'Priced model completion is missing token usage.');
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
package/src/worker.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { workerSpend } from './worker-spend.js';
|
|
2
|
+
import type { WorkerCliResult } from './worker-cli.js';
|
|
1
3
|
import { EventEmitter } from 'node:events';
|
|
2
4
|
import type { JournalClient } from './journal-client.js';
|
|
3
5
|
import type { Pins, StepDispatchEvent } from './protocol.js';
|
|
4
6
|
import type { KernelAgentStep } from './spec.js';
|
|
5
7
|
import { runAgentCli } from './worker-cli.js';
|
|
8
|
+
import { withWorkerLease } from './worker-lease.js';
|
|
9
|
+
import { workerInstruction } from './worker-input.js';
|
|
10
|
+
import { helperCall } from './yaml-helpers.js';
|
|
11
|
+
import { completeHelperDispatch } from './yaml-helper-effect.js';
|
|
6
12
|
|
|
7
13
|
export { MODEL_ENV, WAKE_CONTEXT_ENV } from './worker-cli.js';
|
|
8
14
|
|
|
@@ -10,6 +16,8 @@ export interface AgentWorkerOptions {
|
|
|
10
16
|
workerId: string;
|
|
11
17
|
pins: Pins;
|
|
12
18
|
capacity?: number;
|
|
19
|
+
dataDir?: string;
|
|
20
|
+
onPtyReady?: (path: string) => void;
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
/**
|
|
@@ -90,9 +98,23 @@ export class AgentWorker extends EventEmitter {
|
|
|
90
98
|
|
|
91
99
|
private async execute(dispatch: StepDispatchEvent): Promise<void> {
|
|
92
100
|
const spec = dispatch.spec as Partial<KernelAgentStep>;
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
const helper = helperCall(spec);
|
|
102
|
+
if (helper !== undefined) {
|
|
103
|
+
if (this.options.dataDir === undefined) throw new Error('Helper worker requires a data directory for durable receipts');
|
|
104
|
+
await completeHelperDispatch(this.client, dispatch, helper, this.options.dataDir);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
let humanIntervention = false;
|
|
108
|
+
const completed: WorkerCliResult = await withWorkerLease(this.client, dispatch, signal =>
|
|
109
|
+
typeof spec.cli === 'string' && typeof spec.instruction === 'string'
|
|
110
|
+
? runAgentCli(spec.cli, workerInstruction(spec.instruction, dispatch), dispatch.wake_context, spec.model, undefined, signal, 'agent', this.options.dataDir === undefined ? undefined : {
|
|
111
|
+
dataDir: this.options.dataDir, runId: dispatch.run_id, stepId: dispatch.step_id,
|
|
112
|
+
onReady: this.options.onPtyReady, onDrive: () => { humanIntervention = true; },
|
|
113
|
+
}, typeof spec.cwd === 'string' ? spec.cwd : undefined,
|
|
114
|
+
spec.transport === 'relay' ? 'relay' : 'direct',
|
|
115
|
+
{ runId: dispatch.run_id, stepId: dispatch.step_id })
|
|
116
|
+
: Promise.resolve({ exit_code: null, stdout_tail: '', stderr_tail: 'agent step has no declared CLI' }));
|
|
117
|
+
const { result, usage } = workerSpend(completed, spec.model);
|
|
96
118
|
const completionReason = result.exit_code === 0 ? 'success' : 'worker_error';
|
|
97
119
|
|
|
98
120
|
// Output shape: if the CLI's stdout parses as JSON, promote THAT
|
|
@@ -118,6 +140,8 @@ export class AgentWorker extends EventEmitter {
|
|
|
118
140
|
completionReason,
|
|
119
141
|
{
|
|
120
142
|
output,
|
|
143
|
+
...(humanIntervention ? { human_intervention: true } : {}),
|
|
144
|
+
...(usage !== undefined ? { usage } : {}),
|
|
121
145
|
started_pins: dispatch.pins,
|
|
122
146
|
end_pins: dispatch.pins,
|
|
123
147
|
},
|
|
@@ -148,9 +172,3 @@ export function parseJsonOutput(stdout: string): Record<string, unknown> | null
|
|
|
148
172
|
}
|
|
149
173
|
return parsed as Record<string, unknown>;
|
|
150
174
|
}
|
|
151
|
-
|
|
152
|
-
/** Pass the journaled pack to both raw CLI and wrapper execution paths. */
|
|
153
|
-
function memoryInstruction(instruction: string, memory: StepDispatchEvent['memory']): string {
|
|
154
|
-
return memory === undefined ? instruction
|
|
155
|
-
: `${instruction}\n\nMemory context (journaled):\n${JSON.stringify(memory.pack)}`;
|
|
156
|
-
}
|
package/src/wrapper-session.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { FORCE_KILL_DELAY_MS, childStop, ownsProcessGroup } from './child-stop.js';
|
|
2
3
|
import {
|
|
3
4
|
WRAPPER_EXECUTE_TOKEN,
|
|
4
5
|
WRAPPER_IDENTIFY_ARG,
|
|
@@ -28,7 +29,6 @@ const DEFAULT_LIMITS: WrapperSessionLimits = {
|
|
|
28
29
|
maxOutputBytes: 1_048_576,
|
|
29
30
|
};
|
|
30
31
|
const HANDSHAKE_OUTPUT_LIMIT = 8_192;
|
|
31
|
-
const FORCE_KILL_DELAY_MS = 1_000;
|
|
32
32
|
/**
|
|
33
33
|
* Grace after `SIGKILL` before the reader settles on its own. Node emits
|
|
34
34
|
* `'close'` only once every inherited stdio pipe is closed, which any
|
|
@@ -48,7 +48,12 @@ export function runWrapperSession(
|
|
|
48
48
|
model: string | undefined,
|
|
49
49
|
env: NodeJS.ProcessEnv,
|
|
50
50
|
overrides: Partial<WrapperSessionLimits> = {},
|
|
51
|
+
signal?: AbortSignal,
|
|
51
52
|
): Promise<WrapperSessionResult> {
|
|
53
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
54
|
+
if (signal !== undefined && process.platform === 'win32') {
|
|
55
|
+
return Promise.reject(new Error('Lease-bound wrapper execution requires macOS or Linux process-group cancellation; Windows is unsupported.'));
|
|
56
|
+
}
|
|
52
57
|
const limits = sessionLimits(overrides);
|
|
53
58
|
let request: string;
|
|
54
59
|
try {
|
|
@@ -72,7 +77,7 @@ export function runWrapperSession(
|
|
|
72
77
|
));
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
return executePinnedWrapper(cli, identity, request, env, limits);
|
|
80
|
+
return executePinnedWrapper(cli, identity, request, env, limits, signal);
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
function executePinnedWrapper(
|
|
@@ -81,12 +86,16 @@ function executePinnedWrapper(
|
|
|
81
86
|
request: string,
|
|
82
87
|
env: NodeJS.ProcessEnv,
|
|
83
88
|
limits: WrapperSessionLimits,
|
|
89
|
+
signal?: AbortSignal,
|
|
84
90
|
): Promise<WrapperSessionResult> {
|
|
85
91
|
return new Promise((resolve) => {
|
|
92
|
+
const ownsGroup = ownsProcessGroup(signal);
|
|
86
93
|
const child = spawn(identity.executable, [WRAPPER_IDENTIFY_ARG], {
|
|
87
94
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
95
|
env,
|
|
96
|
+
detached: ownsGroup,
|
|
89
97
|
});
|
|
98
|
+
const stop = childStop(child, ownsGroup);
|
|
90
99
|
const stdout: string[] = [];
|
|
91
100
|
const stderr: Buffer[] = [];
|
|
92
101
|
let handshakePending = '';
|
|
@@ -96,27 +105,53 @@ function executePinnedWrapper(
|
|
|
96
105
|
let protocolError: string | undefined;
|
|
97
106
|
let settled = false;
|
|
98
107
|
let lifecycleTimer: NodeJS.Timeout | undefined;
|
|
99
|
-
let killTimer: NodeJS.Timeout | undefined;
|
|
100
108
|
let settleTimer: NodeJS.Timeout | undefined;
|
|
101
109
|
|
|
102
|
-
const clearTimers = (): void => {
|
|
103
|
-
if (lifecycleTimer !== undefined) clearTimeout(lifecycleTimer);
|
|
104
|
-
if (killTimer !== undefined) clearTimeout(killTimer);
|
|
105
|
-
if (settleTimer !== undefined) clearTimeout(settleTimer);
|
|
106
|
-
};
|
|
107
110
|
const finish = (result: WrapperSessionResult): void => {
|
|
108
111
|
if (settled) return;
|
|
109
112
|
settled = true;
|
|
110
|
-
|
|
113
|
+
if (lifecycleTimer !== undefined) clearTimeout(lifecycleTimer);
|
|
114
|
+
if (settleTimer !== undefined) clearTimeout(settleTimer);
|
|
115
|
+
signal?.removeEventListener('abort', onAbort);
|
|
111
116
|
resolve(result);
|
|
112
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* INVARIANT: a session may not settle until either the process group is
|
|
120
|
+
* confirmed dead or the escalation has actually run.
|
|
121
|
+
*
|
|
122
|
+
* `'close'` and `'error'` are evidence about the DIRECT CHILD and nothing
|
|
123
|
+
* more. A descendant that ignores `SIGTERM` and inherited none of the
|
|
124
|
+
* wrapper's stdio emits exactly those events while it is still running, so
|
|
125
|
+
* neither may drop a pending escalation and neither may settle ahead of
|
|
126
|
+
* one. Every child-level settle therefore goes through
|
|
127
|
+
* `maySettleOnChildExit`, which is the one place that asks the GROUP.
|
|
128
|
+
*
|
|
129
|
+
* When it says no, `terminate`'s own deadline settles instead, with a
|
|
130
|
+
* byte-identical `failure(protocolError)` result. That deadline is armed
|
|
131
|
+
* whenever an escalation is — both come from the single `terminate` below —
|
|
132
|
+
* so refusing here can defer a settle but can never strand one.
|
|
133
|
+
*/
|
|
134
|
+
const finishOnChildExit = (result: WrapperSessionResult): void => {
|
|
135
|
+
// Asked before `finish`, and asked even once we have already settled:
|
|
136
|
+
// this is also the only place a pointless escalation is refunded, and a
|
|
137
|
+
// session that settled on `terminate`'s deadline still owes that refund.
|
|
138
|
+
if (!stop.maySettleOnChildExit()) return;
|
|
139
|
+
finish(result);
|
|
140
|
+
};
|
|
141
|
+
const onAbort = (): void => {
|
|
142
|
+
stop.kill();
|
|
143
|
+
finish(failure('Agent execution aborted: lease ownership lost.'));
|
|
144
|
+
};
|
|
145
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
146
|
+
if (signal?.aborted) { onAbort(); return; }
|
|
113
147
|
const terminate = (message: string): void => {
|
|
114
148
|
if (protocolError !== undefined) return;
|
|
115
149
|
protocolError = message;
|
|
116
150
|
if (lifecycleTimer !== undefined) clearTimeout(lifecycleTimer);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
// Same reach as an abort, only gentler first: this stop must find the
|
|
152
|
+
// whole group, or a descendant outlives the session still holding the
|
|
153
|
+
// stdio it inherited.
|
|
154
|
+
stop.terminate();
|
|
120
155
|
// The reader owns the bound. `'close'` is emitted only after every
|
|
121
156
|
// inherited stdio pipe closes, so a wrapper that leaves a descendant
|
|
122
157
|
// holding one withholds it forever and strands the step with no
|
|
@@ -241,7 +276,7 @@ function executePinnedWrapper(
|
|
|
241
276
|
child.stdin.on('error', () => {
|
|
242
277
|
// A child that closes stdin before acknowledgement is classified on close.
|
|
243
278
|
});
|
|
244
|
-
child.once('error', (error) =>
|
|
279
|
+
child.once('error', (error) => finishOnChildExit(failure(error.message)));
|
|
245
280
|
child.once('close', (code) => {
|
|
246
281
|
if (protocolError === undefined && phase === 'execute' && executionPending.length > 0) {
|
|
247
282
|
if (normalizeLine(executionPending) === WRAPPER_EXECUTE_TOKEN) {
|
|
@@ -251,13 +286,13 @@ function executePinnedWrapper(
|
|
|
251
286
|
}
|
|
252
287
|
}
|
|
253
288
|
if (protocolError !== undefined || phase !== 'execute') {
|
|
254
|
-
|
|
289
|
+
finishOnChildExit(failure(
|
|
255
290
|
protocolError
|
|
256
291
|
?? `CLI ${JSON.stringify(cli)} exited before completing the ${WRAPPER_IDENTIFY_TOKEN} same-process handshake.`,
|
|
257
292
|
));
|
|
258
293
|
return;
|
|
259
294
|
}
|
|
260
|
-
|
|
295
|
+
finishOnChildExit({
|
|
261
296
|
exit_code: code,
|
|
262
297
|
stdout_tail: stdout.join(''),
|
|
263
298
|
stderr_tail: Buffer.concat(stderr).toString('utf8'),
|