@relayflows/sdk 2.0.29 → 2.0.31
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/agent-artifacts.d.ts.map +1 -1
- package/dist/agent-artifacts.js +2 -9
- package/dist/agent-artifacts.js.map +1 -1
- package/dist/agent-cwd.d.ts +69 -0
- package/dist/agent-cwd.d.ts.map +1 -0
- package/dist/agent-cwd.js +140 -0
- package/dist/agent-cwd.js.map +1 -0
- package/dist/artifact-scan-policy.d.ts +35 -0
- package/dist/artifact-scan-policy.d.ts.map +1 -0
- package/dist/artifact-scan-policy.js +49 -0
- package/dist/artifact-scan-policy.js.map +1 -0
- package/dist/authored-completion-record.d.ts +35 -0
- package/dist/authored-completion-record.d.ts.map +1 -0
- package/dist/authored-completion-record.js +92 -0
- package/dist/authored-completion-record.js.map +1 -0
- package/dist/authored-completion.d.ts +99 -0
- package/dist/authored-completion.d.ts.map +1 -0
- package/dist/authored-completion.js +184 -0
- package/dist/authored-completion.js.map +1 -0
- package/dist/authored-flow-error.d.ts +13 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +12 -0
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts +7 -30
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +64 -48
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-human.d.ts.map +1 -1
- package/dist/authored-human.js +1 -3
- package/dist/authored-human.js.map +1 -1
- package/dist/authored-node-entry.js +4 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts +9 -1
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +92 -17
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root.d.ts +5 -0
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +20 -2
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-step-output.d.ts +35 -10
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +108 -40
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-verdict.d.ts +28 -0
- package/dist/authored-verdict.d.ts.map +1 -0
- package/dist/authored-verdict.js +79 -0
- package/dist/authored-verdict.js.map +1 -0
- package/dist/authored-worker-step.d.ts +5 -2
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +48 -11
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/bundle-extensions.d.ts.map +1 -1
- package/dist/bundle-extensions.js +4 -7
- package/dist/bundle-extensions.js.map +1 -1
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +41 -7
- package/dist/bundle.js.map +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +18 -6
- package/dist/canonical.js.map +1 -1
- package/dist/cli/check-worker-surface.d.ts +27 -0
- package/dist/cli/check-worker-surface.d.ts.map +1 -0
- package/dist/cli/check-worker-surface.js +63 -0
- package/dist/cli/check-worker-surface.js.map +1 -0
- package/dist/cli/check.d.ts +19 -2
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +12 -4
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-format.d.ts +9 -0
- package/dist/cli/cloud-format.d.ts.map +1 -0
- package/dist/cli/cloud-format.js +77 -0
- package/dist/cli/cloud-format.js.map +1 -0
- package/dist/cli/cloud-live.d.ts +39 -0
- package/dist/cli/cloud-live.d.ts.map +1 -0
- package/dist/cli/cloud-live.js +325 -0
- package/dist/cli/cloud-live.js.map +1 -0
- package/dist/cli/cloud-read.d.ts +3 -7
- package/dist/cli/cloud-read.d.ts.map +1 -1
- package/dist/cli/cloud-read.js +14 -266
- package/dist/cli/cloud-read.js.map +1 -1
- package/dist/cli/cloud-refusal.d.ts +39 -0
- package/dist/cli/cloud-refusal.d.ts.map +1 -0
- package/dist/cli/cloud-refusal.js +95 -0
- package/dist/cli/cloud-refusal.js.map +1 -0
- package/dist/cli/cloud-status-view.d.ts +7 -0
- package/dist/cli/cloud-status-view.d.ts.map +1 -0
- package/dist/cli/cloud-status-view.js +214 -0
- package/dist/cli/cloud-status-view.js.map +1 -0
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +16 -2
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/hn-monitor.d.ts +0 -13
- 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/local-agent-remedy.d.ts +80 -0
- package/dist/cli/local-agent-remedy.d.ts.map +1 -0
- package/dist/cli/local-agent-remedy.js +116 -0
- package/dist/cli/local-agent-remedy.js.map +1 -0
- package/dist/cli/observer-session.d.ts +41 -0
- package/dist/cli/observer-session.d.ts.map +1 -0
- package/dist/cli/observer-session.js +78 -0
- package/dist/cli/observer-session.js.map +1 -0
- package/dist/cli/run.d.ts +30 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +186 -24
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +9 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +31 -2
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/step-evidence.d.ts +90 -0
- package/dist/cli/step-evidence.d.ts.map +1 -0
- package/dist/cli/step-evidence.js +259 -0
- package/dist/cli/step-evidence.js.map +1 -0
- package/dist/cli/step-failure.d.ts +14 -4
- package/dist/cli/step-failure.d.ts.map +1 -1
- package/dist/cli/step-failure.js +41 -76
- package/dist/cli/step-failure.js.map +1 -1
- package/dist/cli-commands.d.ts +6 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +2 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -42
- package/dist/cli.js.map +1 -1
- package/dist/cloud-read.d.ts +15 -0
- package/dist/cloud-read.d.ts.map +1 -1
- package/dist/cloud-read.js +20 -1
- package/dist/cloud-read.js.map +1 -1
- package/dist/cloud-run-record.d.ts +27 -0
- package/dist/cloud-run-record.d.ts.map +1 -0
- package/dist/cloud-run-record.js +56 -0
- package/dist/cloud-run-record.js.map +1 -0
- package/dist/cloud-run.d.ts +2 -9
- package/dist/cloud-run.d.ts.map +1 -1
- package/dist/cloud-run.js +3 -20
- package/dist/cloud-run.js.map +1 -1
- package/dist/communication/local.d.ts.map +1 -1
- package/dist/communication/local.js +2 -1
- package/dist/communication/local.js.map +1 -1
- package/dist/communication/worker.d.ts +1 -1
- package/dist/communication/worker.d.ts.map +1 -1
- package/dist/communication/worker.js +13 -5
- package/dist/communication/worker.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +28 -6
- package/dist/compile.js.map +1 -1
- package/dist/direct-input.d.ts.map +1 -1
- package/dist/direct-input.js +21 -1
- package/dist/direct-input.js.map +1 -1
- package/dist/failure-kinds.d.ts +96 -6
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +29 -2
- package/dist/failure-kinds.js.map +1 -1
- package/dist/flow-extension-compat.d.ts.map +1 -1
- package/dist/flow-extension-compat.js +26 -8
- package/dist/flow-extension-compat.js.map +1 -1
- package/dist/flow-extension-loader.d.ts +7 -0
- package/dist/flow-extension-loader.d.ts.map +1 -1
- package/dist/flow-extension-loader.js +48 -22
- package/dist/flow-extension-loader.js.map +1 -1
- package/dist/flow-requirements.d.ts.map +1 -1
- package/dist/flow-requirements.js +24 -9
- package/dist/flow-requirements.js.map +1 -1
- package/dist/fs-descriptor.d.ts +17 -0
- package/dist/fs-descriptor.d.ts.map +1 -0
- package/dist/fs-descriptor.js +96 -0
- package/dist/fs-descriptor.js.map +1 -0
- package/dist/gate-contract.d.ts +7 -0
- package/dist/gate-contract.d.ts.map +1 -1
- package/dist/gate-contract.js +6 -1
- package/dist/gate-contract.js.map +1 -1
- package/dist/helper-preflight.d.ts.map +1 -1
- package/dist/helper-preflight.js +10 -3
- package/dist/helper-preflight.js.map +1 -1
- package/dist/helper-reference.d.ts +25 -0
- package/dist/helper-reference.d.ts.map +1 -0
- package/dist/helper-reference.js +129 -0
- package/dist/helper-reference.js.map +1 -0
- package/dist/hosted-base-snapshot.d.ts +25 -0
- package/dist/hosted-base-snapshot.d.ts.map +1 -0
- package/dist/hosted-base-snapshot.js +278 -0
- package/dist/hosted-base-snapshot.js.map +1 -0
- package/dist/hosted-extension-declarations.d.ts +10 -0
- package/dist/hosted-extension-declarations.d.ts.map +1 -0
- package/dist/hosted-extension-declarations.js +272 -0
- package/dist/hosted-extension-declarations.js.map +1 -0
- package/dist/hosted-extension-isolation.d.ts +82 -0
- package/dist/hosted-extension-isolation.d.ts.map +1 -0
- package/dist/hosted-extension-isolation.js +322 -0
- package/dist/hosted-extension-isolation.js.map +1 -0
- package/dist/hosted-extension-manifest.d.ts +9 -0
- package/dist/hosted-extension-manifest.d.ts.map +1 -0
- package/dist/hosted-extension-manifest.js +235 -0
- package/dist/hosted-extension-manifest.js.map +1 -0
- package/dist/hosted-extension-protocol.d.ts +16 -0
- package/dist/hosted-extension-protocol.d.ts.map +1 -0
- package/dist/hosted-extension-protocol.js +224 -0
- package/dist/hosted-extension-protocol.js.map +1 -0
- package/dist/hosted-extension-runtime.d.ts +30 -0
- package/dist/hosted-extension-runtime.d.ts.map +1 -0
- package/dist/hosted-extension-runtime.js +167 -0
- package/dist/hosted-extension-runtime.js.map +1 -0
- package/dist/hosted-extension-sandbox-source.d.ts +9 -0
- package/dist/hosted-extension-sandbox-source.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox-source.js +95 -0
- package/dist/hosted-extension-sandbox-source.js.map +1 -0
- package/dist/hosted-extension-sandbox.d.ts +39 -0
- package/dist/hosted-extension-sandbox.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox.js +418 -0
- package/dist/hosted-extension-sandbox.js.map +1 -0
- package/dist/hosted-project.d.ts +3 -0
- package/dist/hosted-project.d.ts.map +1 -0
- package/dist/hosted-project.js +19 -0
- package/dist/hosted-project.js.map +1 -0
- package/dist/hosted-promise-safety.d.ts +12 -0
- package/dist/hosted-promise-safety.d.ts.map +1 -0
- package/dist/hosted-promise-safety.js +63 -0
- package/dist/hosted-promise-safety.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/intrinsic-array.d.ts +3 -0
- package/dist/intrinsic-array.d.ts.map +1 -0
- package/dist/intrinsic-array.js +14 -0
- package/dist/intrinsic-array.js.map +1 -0
- package/dist/journal-client.d.ts +3 -1
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +17 -2
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-projection.d.ts +16 -0
- package/dist/journal-projection.d.ts.map +1 -0
- package/dist/journal-projection.js +108 -0
- package/dist/journal-projection.js.map +1 -0
- package/dist/json-value.d.ts +6 -1
- package/dist/json-value.d.ts.map +1 -1
- package/dist/json-value.js +155 -36
- package/dist/json-value.js.map +1 -1
- package/dist/llm-worker.d.ts +9 -0
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +16 -2
- package/dist/llm-worker.js.map +1 -1
- package/dist/local-agent.d.ts +1 -1
- package/dist/local-agent.d.ts.map +1 -1
- package/dist/local-agent.js +4 -3
- package/dist/local-agent.js.map +1 -1
- package/dist/named-gate-lowering.js +35 -5
- package/dist/named-gate-lowering.js.map +1 -1
- package/dist/named-gate-preflight.d.ts +27 -0
- package/dist/named-gate-preflight.d.ts.map +1 -0
- package/dist/named-gate-preflight.js +47 -0
- package/dist/named-gate-preflight.js.map +1 -0
- package/dist/named-gates.d.ts +1 -1
- package/dist/named-gates.d.ts.map +1 -1
- package/dist/named-gates.js +3 -0
- package/dist/named-gates.js.map +1 -1
- package/dist/observer-link.d.ts +7 -0
- package/dist/observer-link.d.ts.map +1 -1
- package/dist/observer-link.js +3 -1
- package/dist/observer-link.js.map +1 -1
- package/dist/plugin-store.d.ts +8 -2
- package/dist/plugin-store.d.ts.map +1 -1
- package/dist/plugin-store.js +233 -56
- package/dist/plugin-store.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +7 -0
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +2 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/redact.d.ts +42 -0
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +128 -0
- package/dist/redact.js.map +1 -1
- package/dist/run-projection.d.ts +83 -0
- package/dist/run-projection.d.ts.map +1 -0
- package/dist/run-projection.js +199 -0
- package/dist/run-projection.js.map +1 -0
- package/dist/semver-range.d.ts.map +1 -1
- package/dist/semver-range.js +27 -15
- package/dist/semver-range.js.map +1 -1
- package/dist/shell-word.d.ts +19 -0
- package/dist/shell-word.d.ts.map +1 -0
- package/dist/shell-word.js +23 -0
- package/dist/shell-word.js.map +1 -0
- package/dist/spec.d.ts +52 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/step-fields.d.ts +1 -1
- package/dist/step-fields.js +1 -1
- package/dist/step-fields.js.map +1 -1
- package/dist/steps-green.d.ts +27 -0
- package/dist/steps-green.d.ts.map +1 -0
- package/dist/steps-green.js +164 -0
- package/dist/steps-green.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +39 -8
- package/dist/validate.js.map +1 -1
- package/dist/worker-lease.d.ts +8 -1
- package/dist/worker-lease.d.ts.map +1 -1
- package/dist/worker-lease.js +34 -4
- package/dist/worker-lease.js.map +1 -1
- package/dist/worker.d.ts +8 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +17 -9
- package/dist/worker.js.map +1 -1
- package/package.json +3 -7
- package/src/agent-artifacts.ts +2 -10
- package/src/agent-cwd.ts +139 -0
- package/src/artifact-scan-policy.ts +51 -0
- package/src/authored-completion-record.ts +119 -0
- package/src/authored-completion.ts +203 -0
- package/src/authored-flow-error.ts +13 -1
- package/src/authored-flow-executor.ts +101 -59
- package/src/authored-human.ts +1 -4
- package/src/authored-node-entry.ts +4 -1
- package/src/authored-node-runner.ts +96 -22
- package/src/authored-root.ts +20 -2
- package/src/authored-step-output.ts +139 -42
- package/src/authored-verdict.ts +87 -0
- package/src/authored-worker-step.ts +60 -12
- package/src/bundle-extensions.ts +6 -5
- package/src/bundle.ts +52 -7
- package/src/canonical.ts +20 -6
- package/src/cli/check-worker-surface.ts +66 -0
- package/src/cli/check.ts +35 -4
- package/src/cli/cloud-format.ts +82 -0
- package/src/cli/cloud-live.ts +366 -0
- package/src/cli/cloud-read.ts +13 -282
- package/src/cli/cloud-refusal.ts +113 -0
- package/src/cli/cloud-status-view.ts +213 -0
- package/src/cli/direct-run.ts +19 -2
- package/src/cli/hn-monitor.ts +2 -1
- package/src/cli/local-agent-remedy.ts +160 -0
- package/src/cli/observer-session.ts +107 -0
- package/src/cli/run.ts +233 -28
- package/src/cli/status.ts +59 -3
- package/src/cli/step-evidence.ts +310 -0
- package/src/cli/step-failure.ts +57 -81
- package/src/cli-commands.ts +2 -0
- package/src/cli.ts +64 -44
- package/src/cloud-read.ts +25 -1
- package/src/cloud-run-record.ts +64 -0
- package/src/cloud-run.ts +5 -24
- package/src/communication/local.ts +2 -1
- package/src/communication/worker.ts +13 -5
- package/src/compile.ts +28 -6
- package/src/direct-input.ts +22 -1
- package/src/failure-kinds.ts +104 -2
- package/src/flow-extension-compat.ts +32 -7
- package/src/flow-extension-loader.ts +56 -21
- package/src/flow-requirements.ts +24 -9
- package/src/fs-descriptor.ts +127 -0
- package/src/gate-contract.ts +13 -1
- package/src/helper-preflight.ts +10 -3
- package/src/helper-reference.ts +130 -0
- package/src/hosted-base-snapshot.ts +351 -0
- package/src/hosted-extension-declarations.ts +387 -0
- package/src/hosted-extension-isolation.ts +457 -0
- package/src/hosted-extension-manifest.ts +249 -0
- package/src/hosted-extension-protocol.ts +262 -0
- package/src/hosted-extension-runtime.ts +266 -0
- package/src/hosted-extension-sandbox-source.ts +94 -0
- package/src/hosted-extension-sandbox.ts +489 -0
- package/src/hosted-project.ts +18 -0
- package/src/hosted-promise-safety.ts +68 -0
- package/src/index.ts +20 -0
- package/src/intrinsic-array.ts +14 -0
- package/src/journal-client.ts +19 -2
- package/src/journal-projection.ts +106 -0
- package/src/json-value.ts +184 -32
- package/src/llm-worker.ts +16 -2
- package/src/local-agent.ts +4 -2
- package/src/named-gate-lowering.ts +35 -5
- package/src/named-gate-preflight.ts +47 -0
- package/src/named-gates.ts +3 -0
- package/src/observer-link.ts +10 -1
- package/src/plugin-store.ts +323 -56
- package/src/preflight.ts +7 -0
- package/src/protocol.ts +2 -0
- package/src/redact.ts +111 -0
- package/src/run-projection.ts +247 -0
- package/src/semver-range.ts +39 -14
- package/src/shell-word.ts +24 -0
- package/src/spec.ts +54 -3
- package/src/step-fields.ts +1 -1
- package/src/steps-green.ts +168 -0
- package/src/validate.ts +35 -8
- package/src/worker-lease.ts +35 -5
- package/src/worker.ts +27 -11
package/src/failure-kinds.ts
CHANGED
|
@@ -74,6 +74,15 @@ export const CHECK_FAILURE_KINDS = [
|
|
|
74
74
|
* (#442). Warning-only by design: the declaration stays legal and the flow
|
|
75
75
|
* still runs, but an author who wrote one must not be left believing it
|
|
76
76
|
* sandboxes the step.
|
|
77
|
+
*
|
|
78
|
+
* `gate_path_unscanned` names an `artifact_exists` path inside a prefix the
|
|
79
|
+
* bundled agent worker's artifact scan excludes, so the scan can never put it
|
|
80
|
+
* in the journaled `output.artifacts` the gate reads. It warns rather than
|
|
81
|
+
* refuses because the scan is not the only writer of that list — the same
|
|
82
|
+
* worker promotes JSON stdout and completed Relay task output verbatim, and a
|
|
83
|
+
* custom worker may journal anything — so such a gate is unproven, not
|
|
84
|
+
* unsatisfiable. Temporary: it describes #513, and retires with
|
|
85
|
+
* `named-gate-preflight.ts` when the scan stops excluding those paths.
|
|
77
86
|
*/
|
|
78
87
|
export const PREFLIGHT_WARNING_KINDS = [
|
|
79
88
|
'unprovable_effects',
|
|
@@ -83,6 +92,7 @@ export const PREFLIGHT_WARNING_KINDS = [
|
|
|
83
92
|
'vacuous_gate',
|
|
84
93
|
'budget_unmetered',
|
|
85
94
|
'permissions_unenforced',
|
|
95
|
+
'gate_path_unscanned',
|
|
86
96
|
] as const;
|
|
87
97
|
|
|
88
98
|
/**
|
|
@@ -124,6 +134,14 @@ export const RUN_FAILURE_KINDS = [
|
|
|
124
134
|
'gate_failed',
|
|
125
135
|
/** `flows answer` named a wait the run is not asking: unknown, or already answered. */
|
|
126
136
|
'human_wait_unknown',
|
|
137
|
+
/**
|
|
138
|
+
* `--local-agent` cannot be honoured for this invocation, so it is refused
|
|
139
|
+
* rather than accepted and ignored. A local agent worker is admitted at run
|
|
140
|
+
* start and pinned into the authored root's metadata; a resume can only
|
|
141
|
+
* reproduce the surface the root was started with. Exit 2, before any worker
|
|
142
|
+
* attaches and before the resume touches the journal.
|
|
143
|
+
*/
|
|
144
|
+
'local_agent_unavailable',
|
|
127
145
|
] as const;
|
|
128
146
|
|
|
129
147
|
/**
|
|
@@ -136,8 +154,17 @@ export const RUN_WARNING_KINDS = [
|
|
|
136
154
|
'connection_file_stale',
|
|
137
155
|
] as const;
|
|
138
156
|
|
|
139
|
-
/**
|
|
140
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Warnings emitted by flows check, outside pure preflight.
|
|
159
|
+
*
|
|
160
|
+
* `editor_schema_missing` is a file-level editor hint. `agent_worker_unresolved`
|
|
161
|
+
* is a property of the *invocation*, not of the spec: a spec with `agent`
|
|
162
|
+
* steps needs a worker attached for step type `agent`, and only the caller
|
|
163
|
+
* knows whether it attaches one. `flows check` attaches none and, being
|
|
164
|
+
* daemon-free, can see none either — so it opts in, while `flows run`, `flows
|
|
165
|
+
* build` and SDK submissions do not (cli/check-worker-surface.ts).
|
|
166
|
+
*/
|
|
167
|
+
export const CHECK_WARNING_KINDS = ['editor_schema_missing', 'agent_worker_unresolved'] as const;
|
|
141
168
|
export type CheckWarningKind = (typeof CHECK_WARNING_KINDS)[number];
|
|
142
169
|
|
|
143
170
|
export type PreflightFailureKind = (typeof PREFLIGHT_FAILURE_KINDS)[number];
|
|
@@ -146,6 +173,69 @@ export type PreflightWarningKind = (typeof PREFLIGHT_WARNING_KINDS)[number];
|
|
|
146
173
|
export type RunFailureKind = (typeof RUN_FAILURE_KINDS)[number];
|
|
147
174
|
export type RunWarningKind = (typeof RUN_WARNING_KINDS)[number];
|
|
148
175
|
|
|
176
|
+
/**
|
|
177
|
+
* One failed attempt of a step, as the journal recorded it.
|
|
178
|
+
*
|
|
179
|
+
* The kernel appends a `step.completed` PER ATTEMPT (relayflowd-core/src/machine.rs
|
|
180
|
+
* `completion_actions`), so a step that was retried leaves an ordered account
|
|
181
|
+
* of every failure — not just the one that ran out of budget. Reporting only
|
|
182
|
+
* the last is how a push rejected by a pre-receive hook was reported as
|
|
183
|
+
* "nothing staged inside the declared scope": the retry failed for a different
|
|
184
|
+
* reason than the original attempt, and the original reason was the diagnosis.
|
|
185
|
+
*
|
|
186
|
+
* Excerpts here are bounded harder than the terminal attempt's (256 bytes
|
|
187
|
+
* rather than 1,024): this is history beside the primary account, not a
|
|
188
|
+
* replacement for it.
|
|
189
|
+
*/
|
|
190
|
+
export interface StepAttemptFailure {
|
|
191
|
+
/**
|
|
192
|
+
* The journal entry envelope's attempt number. Absent when the journal did
|
|
193
|
+
* not carry one; the position in the list is not a substitute, because a
|
|
194
|
+
* crashed attempt that consumed no iteration is still its own record.
|
|
195
|
+
*/
|
|
196
|
+
attempt?: number;
|
|
197
|
+
/** The kernel's label for THIS attempt, e.g. `verification_failed`. */
|
|
198
|
+
completionReason?: string;
|
|
199
|
+
/** `retry`, `step_done` or `park`: what the kernel did next, not why it failed. */
|
|
200
|
+
disposition?: string;
|
|
201
|
+
exitCode?: number;
|
|
202
|
+
/** Redacted, terminal-safe UTF-8 excerpt, at most 256 bytes. */
|
|
203
|
+
stdoutTail?: string;
|
|
204
|
+
/** Redacted, terminal-safe UTF-8 excerpt, at most 256 bytes. */
|
|
205
|
+
stderrTail?: string;
|
|
206
|
+
/** This attempt's own account, from the same fields the terminal one reads. */
|
|
207
|
+
detail?: string;
|
|
208
|
+
/** Set when an excerpt above was cut to fit the per-attempt bound. */
|
|
209
|
+
truncated?: boolean;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* What comparing the attempts' recorded evidence established — and no more.
|
|
214
|
+
*
|
|
215
|
+
* `differs` means the journal's own failure evidence is not the same across
|
|
216
|
+
* attempts, which usually means an earlier attempt had a side effect the retry
|
|
217
|
+
* then tripped over. `unchanged` is a statement about the RECORD, not a proof
|
|
218
|
+
* that the underlying causes were identical. `unknown` is the honest answer
|
|
219
|
+
* when an attempt journaled no account of itself, or when its account was
|
|
220
|
+
* already truncated by its producer: equal evidence that was never complete is
|
|
221
|
+
* not evidence of equality.
|
|
222
|
+
*/
|
|
223
|
+
export type AttemptEvidenceComparison = 'differs' | 'unchanged' | 'unknown';
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Why a run parked, for the reporting side that has to name a remedy.
|
|
227
|
+
*
|
|
228
|
+
* `worker_unavailable` is a step nothing is attached to run — the one case
|
|
229
|
+
* `--local-agent` fixes. `needs_human` is the kernel's manual-recovery wait
|
|
230
|
+
* after a worker attempt already failed, and attaching a worker does not clear
|
|
231
|
+
* it. Both arrive as exit 3 under `run_parked`, so the distinction has to
|
|
232
|
+
* travel as a value: it lives here, beside the run vocabulary, because the
|
|
233
|
+
* classifier (cli/run.ts), the authored error boundary (authored-flow-error.ts)
|
|
234
|
+
* and the remedy formatter (cli/local-agent-remedy.ts) all need the same one
|
|
235
|
+
* and none of them may depend on the others.
|
|
236
|
+
*/
|
|
237
|
+
export type ParkCause = 'worker_unavailable' | 'needs_human';
|
|
238
|
+
|
|
149
239
|
/**
|
|
150
240
|
* Optional evidence on the existing step_failed diagnostic, not a new kind.
|
|
151
241
|
*
|
|
@@ -154,6 +244,9 @@ export type RunWarningKind = (typeof RUN_WARNING_KINDS)[number];
|
|
|
154
244
|
* agent or llm step leaves `completionReason` and whatever the daemon captured
|
|
155
245
|
* into `detail` (see cli/step-failure.ts). Absent means "not journaled", never
|
|
156
246
|
* "zero" — an exit code is only ever reported when one was actually recorded.
|
|
247
|
+
*
|
|
248
|
+
* The scalar evidence fields always describe the TERMINAL attempt. `attempts`
|
|
249
|
+
* is additive history and is present only when the step failed more than once.
|
|
157
250
|
*/
|
|
158
251
|
export interface StepFailedDetails {
|
|
159
252
|
stepId?: string;
|
|
@@ -197,6 +290,15 @@ export interface StepFailedDetails {
|
|
|
197
290
|
detail?: string;
|
|
198
291
|
/** The attempt's redacted `stream-json` transcript on disk, when the worker wrote one. */
|
|
199
292
|
transcriptPath?: string;
|
|
293
|
+
/**
|
|
294
|
+
* Every failed attempt of the step this failure names, oldest first, present
|
|
295
|
+
* only when there was more than one. The last element is the same attempt the
|
|
296
|
+
* scalar fields above describe; the first is the one `retries_exhausted`
|
|
297
|
+
* alone used to hide.
|
|
298
|
+
*/
|
|
299
|
+
attempts?: StepAttemptFailure[];
|
|
300
|
+
/** What comparing those attempts' recorded evidence established. */
|
|
301
|
+
attemptEvidence?: AttemptEvidenceComparison;
|
|
200
302
|
/** A runnable `flows replay` invocation for this run. */
|
|
201
303
|
hint?: string;
|
|
202
304
|
/** The on-disk journal for this run, when the data dir is known. */
|
|
@@ -1,20 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import packageManifest from '../package.json' with { type: 'json' };
|
|
2
2
|
import type { FlowExtensionManifest } from './flow-extension-manifest.js';
|
|
3
3
|
import { PluginError } from './plugin-manifest.js';
|
|
4
4
|
import { satisfiesRange } from './semver-range.js';
|
|
5
5
|
|
|
6
|
+
const OBJECT_FREEZE = Object.freeze;
|
|
7
|
+
// This static import is resolved before module evaluation and embedded by the
|
|
8
|
+
// standalone Bun build. It neither consults mutable fs exports after authored
|
|
9
|
+
// code nor depends on import.meta.url naming a real package directory.
|
|
10
|
+
const RUNTIME_VERSIONS = OBJECT_FREEZE({
|
|
11
|
+
sdk: packageManifest.version,
|
|
12
|
+
surface: packageManifest.dependencies['@relayflows/surface']!,
|
|
13
|
+
});
|
|
14
|
+
|
|
6
15
|
export interface RuntimeVersions { readonly sdk: string; readonly surface: string }
|
|
7
16
|
|
|
8
17
|
/** The versions a plugin's `compat` is checked against: this SDK and the surface it pins. */
|
|
9
18
|
export function runtimeVersions(): RuntimeVersions {
|
|
10
|
-
|
|
11
|
-
return { sdk: pkg.version, surface: pkg.dependencies['@relayflows/surface']! };
|
|
19
|
+
return RUNTIME_VERSIONS;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
/** `compat.surface` / `compat.sdk` against the runtime: a miss is a refusal, never a warning. */
|
|
15
23
|
export function assertCompatible(manifest: FlowExtensionManifest, versions: RuntimeVersions): void {
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
assertRuntimeRange(manifest, 'surface', manifest.compat.surface, versions.surface);
|
|
25
|
+
assertRuntimeRange(manifest, 'sdk', manifest.compat.sdk, versions.sdk);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertRuntimeRange(
|
|
29
|
+
manifest: FlowExtensionManifest,
|
|
30
|
+
what: 'surface' | 'sdk',
|
|
31
|
+
range: string,
|
|
32
|
+
actual: string,
|
|
33
|
+
): void {
|
|
34
|
+
if (!satisfiesRange(actual, range)) {
|
|
35
|
+
throw new PluginError('plugin_incompatible', `${manifest.name} requires ${what} ${range}; this runtime has ${actual}.`);
|
|
18
36
|
}
|
|
19
37
|
}
|
|
20
38
|
|
|
@@ -23,9 +41,16 @@ export function assertCompatible(manifest: FlowExtensionManifest, versions: Runt
|
|
|
23
41
|
* optional: a base without it matches only `"*"`.
|
|
24
42
|
*/
|
|
25
43
|
export function assertBaseCompatible(manifest: FlowExtensionManifest, base: { readonly name: string; readonly version?: string }): void {
|
|
26
|
-
|
|
44
|
+
let entry: FlowExtensionManifest['compat']['base'][number] | undefined;
|
|
45
|
+
for (let index = 0; index < manifest.compat.base.length; index += 1) {
|
|
46
|
+
if (manifest.compat.base[index]!.name === base.name) entry = manifest.compat.base[index];
|
|
47
|
+
}
|
|
27
48
|
if (entry === undefined) {
|
|
28
|
-
|
|
49
|
+
let names = '';
|
|
50
|
+
for (let index = 0; index < manifest.compat.base.length; index += 1) {
|
|
51
|
+
names += `${index === 0 ? '' : ', '}${manifest.compat.base[index]!.name}`;
|
|
52
|
+
}
|
|
53
|
+
throw new PluginError('plugin_incompatible', `${manifest.name} extends ${names}, not "${base.name}".`);
|
|
29
54
|
}
|
|
30
55
|
if (base.version === undefined) {
|
|
31
56
|
if (entry.version !== '*') throw new PluginError('plugin_incompatible', `${manifest.name} requires ${base.name} ${entry.version}, but the base flow declares no version; only "*" can be satisfied.`);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
1
|
import { dirname, join } from 'node:path';
|
|
3
2
|
import type { Ctx } from '@relayflows/surface';
|
|
4
3
|
import type { AuthoredFlowDefinition, FlowHandle } from './authored-flow.js';
|
|
@@ -6,9 +5,22 @@ import { sha256 } from './bundle.js';
|
|
|
6
5
|
import { assertBaseCompatible, assertCompatible, runtimeVersions, type RuntimeVersions } from './flow-extension-compat.js';
|
|
7
6
|
import { validateFlowExtensionManifest, type FlowExtensionManifest } from './flow-extension-manifest.js';
|
|
8
7
|
import { findPluginProject } from './plugin-loader.js';
|
|
8
|
+
import { appendIntrinsicArray } from './intrinsic-array.js';
|
|
9
9
|
import { reconcileDeclaredExtensions, type PluginLockEntry } from './plugin-lock.js';
|
|
10
10
|
import { PluginError } from './plugin-manifest.js';
|
|
11
|
-
import { pluginStoreDirectory,
|
|
11
|
+
import { pluginStoreDirectory, readStoredPluginFiles } from './plugin-store.js';
|
|
12
|
+
|
|
13
|
+
const JSON_PARSE = JSON.parse;
|
|
14
|
+
const ARRAY_IS_ARRAY = Array.isArray;
|
|
15
|
+
const JSON_STRINGIFY = JSON.stringify;
|
|
16
|
+
const OBJECT_FREEZE = Object.freeze;
|
|
17
|
+
const REGEXP_TEST = Function.prototype.call.bind(RegExp.prototype.test) as (
|
|
18
|
+
regexp: RegExp, value: string,
|
|
19
|
+
) => boolean;
|
|
20
|
+
const STRING_SPLIT = Function.prototype.call.bind(String.prototype.split) as (
|
|
21
|
+
value: string,
|
|
22
|
+
separator: string | RegExp,
|
|
23
|
+
) => string[];
|
|
12
24
|
|
|
13
25
|
/**
|
|
14
26
|
* Compose schema-2 flow extensions onto a base authored flow.
|
|
@@ -94,7 +106,7 @@ function subscriptionOf(handler: TriggerHandler): { provider: string; event: str
|
|
|
94
106
|
if (trigger.kind !== 'webhook' || trigger.filter === undefined) return undefined;
|
|
95
107
|
const { provider, type, payload } = trigger.filter as { provider?: unknown; type?: unknown; payload?: unknown };
|
|
96
108
|
if (typeof provider !== 'string' || provider !== trigger.name || typeof type !== 'string') return undefined;
|
|
97
|
-
const action = typeof payload === 'object' && payload !== null && !
|
|
109
|
+
const action = typeof payload === 'object' && payload !== null && !ARRAY_IS_ARRAY(payload) ? (payload as { action?: unknown }).action : undefined;
|
|
98
110
|
if (action !== undefined && typeof action !== 'string') return undefined;
|
|
99
111
|
return action === undefined ? { provider, event: type } : { provider, event: type, action };
|
|
100
112
|
}
|
|
@@ -115,32 +127,41 @@ export type HostedExtensionDispatch = {
|
|
|
115
127
|
readonly deliveryId: string;
|
|
116
128
|
};
|
|
117
129
|
|
|
118
|
-
type HostedEventIdentity = { readonly provider: string; readonly event: string; readonly action?: string };
|
|
130
|
+
export type HostedEventIdentity = { readonly provider: string; readonly event: string; readonly action?: string };
|
|
119
131
|
const DISPATCH_PROVIDER = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
120
132
|
const DISPATCH_EVENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
121
133
|
const DISPATCH_DELIVERY = /^[A-Za-z0-9_.:-]{1,200}$/;
|
|
122
134
|
|
|
123
135
|
function hostedEventIdentity(dispatch: unknown): HostedEventIdentity {
|
|
124
|
-
if (typeof dispatch !== 'object' || dispatch === null ||
|
|
136
|
+
if (typeof dispatch !== 'object' || dispatch === null || ARRAY_IS_ARRAY(dispatch)) {
|
|
125
137
|
throw new PluginError('plugin_event_unroutable', 'Hosted extension dispatch authority is malformed.');
|
|
126
138
|
}
|
|
127
139
|
const { provenance, provider, eventType, deliveryId } = dispatch as Partial<HostedExtensionDispatch>;
|
|
128
140
|
if ((dispatch as Partial<HostedExtensionDispatch>)[HOSTED_EXTENSION_DISPATCH_AUTHORITY] !== true
|
|
129
141
|
|| provenance !== 'integration-watch'
|
|
130
|
-
|| typeof provider !== 'string' || !DISPATCH_PROVIDER
|
|
131
|
-
|| typeof deliveryId !== 'string' || !DISPATCH_DELIVERY
|
|
142
|
+
|| typeof provider !== 'string' || !REGEXP_TEST(DISPATCH_PROVIDER, provider)
|
|
143
|
+
|| typeof deliveryId !== 'string' || !REGEXP_TEST(DISPATCH_DELIVERY, deliveryId)
|
|
132
144
|
|| typeof eventType !== 'string') {
|
|
133
145
|
throw new PluginError('plugin_event_unroutable', 'Hosted extension dispatch authority is malformed.');
|
|
134
146
|
}
|
|
135
|
-
const parts = eventType
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
const parts = STRING_SPLIT(eventType, '.');
|
|
148
|
+
let valid = parts.length === 1 || parts.length === 2;
|
|
149
|
+
for (let index = 0; valid && index < parts.length; index += 1) {
|
|
150
|
+
valid = REGEXP_TEST(DISPATCH_EVENT, parts[index]!);
|
|
151
|
+
}
|
|
152
|
+
if (!valid) {
|
|
153
|
+
throw new PluginError('plugin_event_unroutable', `Hosted extension event ${JSON_STRINGIFY(eventType)} is malformed.`);
|
|
138
154
|
}
|
|
139
155
|
return parts.length === 1
|
|
140
156
|
? { provider, event: parts[0]! }
|
|
141
157
|
: { provider, event: parts[0]!, action: parts[1]! };
|
|
142
158
|
}
|
|
143
159
|
|
|
160
|
+
/** Validate and project branded host authority without making it serializable. */
|
|
161
|
+
export function hostedExtensionDispatchIdentity(dispatch: unknown): HostedEventIdentity {
|
|
162
|
+
return OBJECT_FREEZE(hostedEventIdentity(dispatch));
|
|
163
|
+
}
|
|
164
|
+
|
|
144
165
|
/**
|
|
145
166
|
* Brand metadata only after the host has authenticated the integration
|
|
146
167
|
* delivery. The symbol is deliberately not serializable, so copying a direct
|
|
@@ -149,7 +170,7 @@ function hostedEventIdentity(dispatch: unknown): HostedEventIdentity {
|
|
|
149
170
|
export function hostedExtensionDispatchFromVerifiedDelivery(
|
|
150
171
|
delivery: Omit<HostedExtensionDispatch, typeof HOSTED_EXTENSION_DISPATCH_AUTHORITY | 'provenance'>,
|
|
151
172
|
): HostedExtensionDispatch {
|
|
152
|
-
const dispatch =
|
|
173
|
+
const dispatch = OBJECT_FREEZE({
|
|
153
174
|
[HOSTED_EXTENSION_DISPATCH_AUTHORITY]: true as const,
|
|
154
175
|
provenance: 'integration-watch' as const,
|
|
155
176
|
...delivery,
|
|
@@ -171,16 +192,29 @@ export function extensionHandlerForHostedDispatch(
|
|
|
171
192
|
): { readonly extension: Pick<LoadedFlowExtension, 'name' | 'handlers'>; readonly handler: TriggerHandler } | undefined {
|
|
172
193
|
if (dispatch === undefined) return undefined;
|
|
173
194
|
const identity = hostedEventIdentity(dispatch);
|
|
174
|
-
const matches
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
195
|
+
const matches: Array<{
|
|
196
|
+
extension: Pick<LoadedFlowExtension, 'name' | 'handlers'>;
|
|
197
|
+
handler: TriggerHandler;
|
|
198
|
+
}> = [];
|
|
199
|
+
for (let extensionIndex = 0; extensionIndex < extensions.length; extensionIndex += 1) {
|
|
200
|
+
const extension = extensions[extensionIndex]!;
|
|
201
|
+
for (let handlerIndex = 0; handlerIndex < extension.handlers.length; handlerIndex += 1) {
|
|
202
|
+
const handler = extension.handlers[handlerIndex]!;
|
|
203
|
+
const subscription = subscriptionOf(handler);
|
|
204
|
+
if (subscription === undefined || subscription.provider !== identity.provider
|
|
205
|
+
|| subscription.event !== identity.event) continue;
|
|
206
|
+
if (subscription.action !== undefined && subscription.action !== identity.action) continue;
|
|
207
|
+
appendIntrinsicArray(matches, { extension, handler });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
180
210
|
if (matches.length > 1) {
|
|
211
|
+
let matchingNames = '';
|
|
212
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
213
|
+
matchingNames += `${index === 0 ? '' : ', '}${matches[index]!.extension.name}`;
|
|
214
|
+
}
|
|
181
215
|
throw new PluginError(
|
|
182
216
|
'plugin_event_ambiguous',
|
|
183
|
-
`Hosted event ${identity.provider}.${identity.event}${identity.action === undefined ? '' : `.${identity.action}`} matches multiple extension handlers (${
|
|
217
|
+
`Hosted event ${identity.provider}.${identity.event}${identity.action === undefined ? '' : `.${identity.action}`} matches multiple extension handlers (${matchingNames}).`,
|
|
184
218
|
);
|
|
185
219
|
}
|
|
186
220
|
return matches[0];
|
|
@@ -206,11 +240,12 @@ async function loadOne<Authority>(
|
|
|
206
240
|
options: LoadFlowExtensionsOptions<Authority>,
|
|
207
241
|
): Promise<LoadedFlowExtension> {
|
|
208
242
|
const directory = pluginStoreDirectory(root, lock.name, lock.digest);
|
|
209
|
-
await
|
|
210
|
-
const manifestBytes =
|
|
243
|
+
const stored = await readStoredPluginFiles(directory, lock.digest);
|
|
244
|
+
const manifestBytes = stored.find(file => file.path === 'flows-plugin.json')?.data;
|
|
245
|
+
if (manifestBytes === undefined) throw new PluginError('plugin_source_drift', `${ref}: flows-plugin.json is missing.`);
|
|
211
246
|
if (sha256(manifestBytes) !== lock.manifestSha256) throw new PluginError('plugin_source_drift', `${ref}: flows-plugin.json differs from the lockfile's manifest hash.`);
|
|
212
247
|
let input: unknown;
|
|
213
|
-
try { input =
|
|
248
|
+
try { input = JSON_PARSE(manifestBytes.toString('utf8')); }
|
|
214
249
|
catch { throw new PluginError('plugin_manifest_invalid', `${ref}: flows-plugin.json is not valid JSON.`); }
|
|
215
250
|
const manifest = validateFlowExtensionManifest(input);
|
|
216
251
|
if (manifest.name !== lock.name || manifest.version !== lock.version) throw new PluginError('plugin_source_drift', `${ref}: manifest names ${manifest.name}@${manifest.version}, lockfile has ${lock.name}@${lock.version}.`);
|
package/src/flow-requirements.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { TriggerSource } from '@relayflows/surface';
|
|
|
4
4
|
import { providerDeclaration } from './provider-trigger-contract.js';
|
|
5
5
|
import type { FlowSpec } from './spec.js';
|
|
6
6
|
import { helperCall } from './yaml-helpers.js';
|
|
7
|
+
import { helperNamespacesUsed } from './helper-reference.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* What a flow needs from the workspace it deploys into, read from inert
|
|
@@ -155,10 +156,14 @@ export function flowRequirements(
|
|
|
155
156
|
// payload as input, and handler bodies are not dispatched yet (flows #301).
|
|
156
157
|
// A handler's *trigger* is still a requirement — it is what wakes the flow.
|
|
157
158
|
const text = typeof flow.body === 'function' ? Function.prototype.toString.call(flow.body) : '';
|
|
158
|
-
const
|
|
159
|
-
|
|
159
|
+
const rootName = contextParameter(text);
|
|
160
|
+
const root = rootName === undefined ? undefined : escapeRegExp(rootName);
|
|
161
|
+
if (root !== undefined && rootName !== undefined) {
|
|
162
|
+
// The AST walk compares against an Identifier name, so it needs the raw
|
|
163
|
+
// parameter; the regex scanners below need the escaped one.
|
|
164
|
+
const referenced = helperNamespacesUsed(text, rootName);
|
|
160
165
|
for (const { provider, namespace } of helperProviders) {
|
|
161
|
-
if (
|
|
166
|
+
if (referenced.has(namespace)) declare({ provider, from: 'helper', detail: `f.${namespace}` });
|
|
162
167
|
}
|
|
163
168
|
for (const use of workerCalls(root, text)) need(use.cli === undefined ? fallback : harnessFromCli(use.cli), use.detail);
|
|
164
169
|
// `f.human(q, { to: "slack:#eng" })` is delivered by Cloud through that
|
|
@@ -219,15 +224,25 @@ function stringList(value: unknown): string[] {
|
|
|
219
224
|
return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : [];
|
|
220
225
|
}
|
|
221
226
|
|
|
222
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* The body's first parameter (`f` in `async (f, input) => …`), RAW.
|
|
229
|
+
*
|
|
230
|
+
* Returned unescaped because the AST walk compares it to an `Identifier`
|
|
231
|
+
* name: escaping turned a legal parameter like `f$` into `f\$`, which matches
|
|
232
|
+
* no identifier and hid every helper call in that flow. Callers that build a
|
|
233
|
+
* pattern escape it themselves with `escapeRegExp`.
|
|
234
|
+
*/
|
|
223
235
|
function contextParameter(body: string): string | undefined {
|
|
224
|
-
|
|
225
|
-
|
|
236
|
+
// The third alternative is an object-literal method (`async post(f) { … }`),
|
|
237
|
+
// a shape `Function.prototype.toString()` can return and which the first two
|
|
238
|
+
// do not match — leaving `root` undefined and the body unscanned entirely.
|
|
239
|
+
const parameter = body.match(/^(?:async\s+)?(?:function\s*\*?\s*(?:[\w$]+)?\s*)?(?:\(\s*([\w$]+)|([\w$]+)\s*=>|\*?\s*[\w$]+\s*\(\s*([\w$]+))/u);
|
|
240
|
+
return parameter?.[1] ?? parameter?.[2] ?? parameter?.[3];
|
|
226
241
|
}
|
|
227
242
|
|
|
228
|
-
/**
|
|
229
|
-
function
|
|
230
|
-
return
|
|
243
|
+
/** Escape a literal for embedding in a RegExp source. */
|
|
244
|
+
function escapeRegExp(value: string): string {
|
|
245
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
231
246
|
}
|
|
232
247
|
|
|
233
248
|
/**
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dir,
|
|
3
|
+
Dirent,
|
|
4
|
+
Stats,
|
|
5
|
+
close,
|
|
6
|
+
fstat,
|
|
7
|
+
lstat,
|
|
8
|
+
open,
|
|
9
|
+
opendir,
|
|
10
|
+
read,
|
|
11
|
+
readdir,
|
|
12
|
+
type BigIntStats,
|
|
13
|
+
type PathLike,
|
|
14
|
+
} from 'node:fs';
|
|
15
|
+
import { hostedPromiseValue } from './hosted-promise-safety.js';
|
|
16
|
+
|
|
17
|
+
const CLOSE = close;
|
|
18
|
+
const FSTAT = fstat;
|
|
19
|
+
const LSTAT = lstat;
|
|
20
|
+
const OPEN = open;
|
|
21
|
+
const OPENDIR = opendir;
|
|
22
|
+
const READ = read;
|
|
23
|
+
const READDIR = readdir;
|
|
24
|
+
const PROMISE = Promise;
|
|
25
|
+
const DIRENT_IS_DIRECTORY = Function.prototype.call.bind(Dirent.prototype.isDirectory) as (
|
|
26
|
+
entry: Dirent,
|
|
27
|
+
) => boolean;
|
|
28
|
+
const DIRENT_IS_FILE = Function.prototype.call.bind(Dirent.prototype.isFile) as (
|
|
29
|
+
entry: Dirent,
|
|
30
|
+
) => boolean;
|
|
31
|
+
const DIR_CLOSE = Function.prototype.call.bind(Dir.prototype.close) as (
|
|
32
|
+
directory: Dir,
|
|
33
|
+
callback: (error?: NodeJS.ErrnoException | null) => void,
|
|
34
|
+
) => void;
|
|
35
|
+
const DIR_READ = Function.prototype.call.bind(Dir.prototype.read) as (
|
|
36
|
+
directory: Dir,
|
|
37
|
+
callback: (error: NodeJS.ErrnoException | null, entry: Dirent | null) => void,
|
|
38
|
+
) => void;
|
|
39
|
+
const STATS_IS_DIRECTORY = Function.prototype.call.bind(Stats.prototype.isDirectory) as (
|
|
40
|
+
stats: Stats | BigIntStats,
|
|
41
|
+
) => boolean;
|
|
42
|
+
const STATS_IS_FILE = Function.prototype.call.bind(Stats.prototype.isFile) as (
|
|
43
|
+
stats: Stats | BigIntStats,
|
|
44
|
+
) => boolean;
|
|
45
|
+
|
|
46
|
+
export const descriptorIsDirectory = STATS_IS_DIRECTORY;
|
|
47
|
+
export const descriptorIsFile = STATS_IS_FILE;
|
|
48
|
+
export const directoryEntryIsDirectory = DIRENT_IS_DIRECTORY;
|
|
49
|
+
export const directoryEntryIsFile = DIRENT_IS_FILE;
|
|
50
|
+
|
|
51
|
+
export function closeDescriptor(descriptor: number): Promise<void> {
|
|
52
|
+
return new PROMISE<void>((resolvePromise, rejectPromise) => {
|
|
53
|
+
CLOSE(descriptor, error => error === null ? resolvePromise() : rejectPromise(error));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function openDescriptor(path: PathLike, flags: number): Promise<number> {
|
|
58
|
+
return new PROMISE<number>((resolvePromise, rejectPromise) => {
|
|
59
|
+
OPEN(path, flags, (error, descriptor) => error === null ? resolvePromise(descriptor) : rejectPromise(error));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function statDescriptor(descriptor: number, options: { bigint: true }): Promise<BigIntStats> {
|
|
64
|
+
return new PROMISE<BigIntStats>((resolvePromise, rejectPromise) => {
|
|
65
|
+
FSTAT(descriptor, options, (error, stats) => {
|
|
66
|
+
if (error !== null) rejectPromise(error);
|
|
67
|
+
else resolvePromise(hostedPromiseValue(stats));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function lstatPath(path: PathLike): Promise<Stats> {
|
|
73
|
+
return new PROMISE<Stats>((resolvePromise, rejectPromise) => {
|
|
74
|
+
LSTAT(path, (error, stats) => {
|
|
75
|
+
if (error !== null) rejectPromise(error);
|
|
76
|
+
else resolvePromise(hostedPromiseValue(stats));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function openDirectory(path: PathLike): Promise<Dir> {
|
|
82
|
+
return new PROMISE<Dir>((resolvePromise, rejectPromise) => {
|
|
83
|
+
OPENDIR(path, (error, directory) => {
|
|
84
|
+
if (error !== null) rejectPromise(error);
|
|
85
|
+
else resolvePromise(hostedPromiseValue(directory));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function readDirectory(path: PathLike): Promise<Dirent[]> {
|
|
91
|
+
return new PROMISE<Dirent[]>((resolvePromise, rejectPromise) => {
|
|
92
|
+
READDIR(path, { withFileTypes: true }, (error, entries) => {
|
|
93
|
+
if (error !== null) rejectPromise(error);
|
|
94
|
+
else resolvePromise(hostedPromiseValue(entries));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function readDirectoryEntry(directory: Dir): Promise<Dirent | null> {
|
|
100
|
+
return new PROMISE<Dirent | null>((resolvePromise, rejectPromise) => {
|
|
101
|
+
DIR_READ(directory, (error, entry) => {
|
|
102
|
+
if (error !== null) rejectPromise(error);
|
|
103
|
+
else resolvePromise(entry === null ? null : hostedPromiseValue(entry));
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function closeDirectory(directory: Dir): Promise<void> {
|
|
109
|
+
return new PROMISE<void>((resolvePromise, rejectPromise) => {
|
|
110
|
+
DIR_CLOSE(directory, error => error == null ? resolvePromise() : rejectPromise(error));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function readDescriptor(
|
|
115
|
+
descriptor: number,
|
|
116
|
+
buffer: Buffer,
|
|
117
|
+
offset: number,
|
|
118
|
+
length: number,
|
|
119
|
+
position: number,
|
|
120
|
+
): Promise<number> {
|
|
121
|
+
return await new PROMISE<number>((resolvePromise, rejectPromise) => {
|
|
122
|
+
READ(descriptor, buffer, offset, length, position, (error, bytesRead) => {
|
|
123
|
+
if (error !== null) rejectPromise(error);
|
|
124
|
+
else resolvePromise(bytesRead);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
package/src/gate-contract.ts
CHANGED
|
@@ -24,6 +24,13 @@ export interface StepGateInspection extends DataGateClassification {
|
|
|
24
24
|
* identically to a gate that judges something.
|
|
25
25
|
*/
|
|
26
26
|
acceptsAnyOutput?: true;
|
|
27
|
+
/**
|
|
28
|
+
* Present when the step declares `onNonZero: 'record'`. The `exit_code`
|
|
29
|
+
* check is still applied and still journaled, but a positive code satisfies
|
|
30
|
+
* it instead of failing the step. Without this a recording step reads
|
|
31
|
+
* exactly like a gated one — the `|| true` ambiguity, moved into the report.
|
|
32
|
+
*/
|
|
33
|
+
recordsNonZeroExit?: true;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/** Annotation-only keywords: present, they still constrain no instance. */
|
|
@@ -55,11 +62,15 @@ export function acceptsAnyOutput(schema: unknown): boolean {
|
|
|
55
62
|
/** Describe the exact named checks the existing kernel applies to a step. */
|
|
56
63
|
export function inspectStepGate(step: StepSpec): StepGateInspection {
|
|
57
64
|
const checks: JournalGateCheck[] = [];
|
|
65
|
+
// A policy for the step's own exit code, not a gate of its own: it never
|
|
66
|
+
// adds or removes a check, it changes what a positive code means to one.
|
|
67
|
+
const recording = step.type === 'deterministic' && step.onNonZero === 'record'
|
|
68
|
+
? { recordsNonZeroExit: true as const } : {};
|
|
58
69
|
if (isNamedGate(step.verification)) {
|
|
59
70
|
checks.push('exit_code');
|
|
60
71
|
if (step.verification.type === 'references_input') checks.push('output_contains');
|
|
61
72
|
if (step.verification.type === 'word_count_bounds') checks.push('json_schema');
|
|
62
|
-
return { stepId: step.id, kind: 'data', checks, evaluator: 'kernel', preflightable: true, replayable: true };
|
|
73
|
+
return { stepId: step.id, kind: 'data', checks, evaluator: 'kernel', preflightable: true, replayable: true, ...recording };
|
|
63
74
|
}
|
|
64
75
|
if (step.type === 'deterministic') checks.push('exit_code');
|
|
65
76
|
if (step.verification?.type === 'output_contains') checks.push('output_contains');
|
|
@@ -75,5 +86,6 @@ export function inspectStepGate(step: StepSpec): StepGateInspection {
|
|
|
75
86
|
preflightable: true,
|
|
76
87
|
replayable: true,
|
|
77
88
|
...(vacuous ? { acceptsAnyOutput: true as const } : {}),
|
|
89
|
+
...recording,
|
|
78
90
|
};
|
|
79
91
|
}
|
package/src/helper-preflight.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { helperProviders } from '@relayflows/surface/runtime';
|
|
2
2
|
import type { PreflightResult, PreflightDiagnostic } from './preflight.js';
|
|
3
|
+
import { helperNamespacesUsed } from './helper-reference.js';
|
|
3
4
|
|
|
4
5
|
/** Static discovery never executes the body; dynamic aliases are checked at call time. */
|
|
5
6
|
export function preflightHelpers(
|
|
@@ -8,12 +9,18 @@ export function preflightHelpers(
|
|
|
8
9
|
providers?: Readonly<Record<string, { mount: boolean; mock: boolean; token?: string }>> },
|
|
9
10
|
): PreflightResult {
|
|
10
11
|
const body = typeof definition.body === 'function' ? Function.prototype.toString.call(definition.body) : '';
|
|
11
|
-
const parameter = body.match(/^(?:async\s+)?(?:function(
|
|
12
|
-
|
|
12
|
+
const parameter = body.match(/^(?:async\s+)?(?:function\s*\*?\s*(?:[\w$]+)?\s*)?(?:\(\s*([\w$]+)|([\w$]+)\s*=>|\*?\s*[\w$]+\s*\(\s*([\w$]+))/u);
|
|
13
|
+
// NOT regex-escaped: this is compared to an AST Identifier name, so a legal
|
|
14
|
+
// parameter like `f$` must stay `f$`. Escaping it hid every helper call.
|
|
15
|
+
const root = parameter?.[1] ?? parameter?.[2] ?? parameter?.[3];
|
|
13
16
|
const diagnostics: PreflightDiagnostic[] = [];
|
|
17
|
+
// Read from a parse, not from the text: a helper named inside a string,
|
|
18
|
+
// comment, template quasi or regex is not used, and refusing on one demands
|
|
19
|
+
// a mount the flow never touches.
|
|
20
|
+
const referenced = root === undefined ? new Set<string>() : helperNamespacesUsed(body, root);
|
|
14
21
|
for (const { provider, namespace, supported } of helperProviders) {
|
|
15
22
|
const used = definition.header?.tools?.[namespace] === true
|
|
16
|
-
|| (
|
|
23
|
+
|| referenced.has(namespace);
|
|
17
24
|
if (!used) continue;
|
|
18
25
|
const fact = facts.providers?.[provider] ?? (provider === 'slack'
|
|
19
26
|
? { mount: facts.slackMount, mock: facts.slackMock, token: facts.slackToken }
|