@relayflows/sdk 2.0.41 → 2.0.43
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 +12 -1
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/base.js +11 -1
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +8 -4
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +15 -6
- package/dist/adapters/codex.js.map +1 -1
- package/dist/agent-resume.d.ts +8 -0
- package/dist/agent-resume.d.ts.map +1 -0
- package/dist/agent-resume.js +37 -0
- package/dist/agent-resume.js.map +1 -0
- package/dist/agent-transcript.d.ts.map +1 -1
- package/dist/agent-transcript.js +5 -3
- package/dist/agent-transcript.js.map +1 -1
- package/dist/authored-flow-error.d.ts +10 -2
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +16 -2
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-flow-loader.d.ts +6 -0
- package/dist/authored-flow-loader.d.ts.map +1 -1
- package/dist/authored-flow-loader.js +18 -6
- package/dist/authored-flow-loader.js.map +1 -1
- package/dist/authored-helper-effect.js +3 -2
- package/dist/authored-helper-effect.js.map +1 -1
- package/dist/authored-human.d.ts +2 -0
- package/dist/authored-human.d.ts.map +1 -1
- package/dist/authored-human.js +4 -0
- package/dist/authored-human.js.map +1 -1
- package/dist/authored-memory.d.ts +47 -2
- package/dist/authored-memory.d.ts.map +1 -1
- package/dist/authored-memory.js +394 -15
- package/dist/authored-memory.js.map +1 -1
- package/dist/authored-node-entry.js +6 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts +1 -1
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +6 -3
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root-readback.d.ts +21 -0
- package/dist/authored-root-readback.d.ts.map +1 -0
- package/dist/authored-root-readback.js +90 -0
- package/dist/authored-root-readback.js.map +1 -0
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +53 -74
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-step-output.d.ts +2 -0
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +1 -0
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +26 -4
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check-helper-body.d.ts +2 -2
- package/dist/cli/check-helper-body.d.ts.map +1 -1
- package/dist/cli/check-helper-body.js +7 -2
- package/dist/cli/check-helper-body.js.map +1 -1
- package/dist/cli/check-helper-surface.d.ts +8 -0
- package/dist/cli/check-helper-surface.d.ts.map +1 -0
- package/dist/cli/check-helper-surface.js +37 -0
- package/dist/cli/check-helper-surface.js.map +1 -0
- package/dist/cli/check-triggers.d.ts +4 -3
- package/dist/cli/check-triggers.d.ts.map +1 -1
- package/dist/cli/check-triggers.js +7 -3
- package/dist/cli/check-triggers.js.map +1 -1
- package/dist/cli/check.d.ts +7 -1
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +35 -8
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-answer.d.ts +9 -0
- package/dist/cli/cloud-answer.d.ts.map +1 -0
- package/dist/cli/cloud-answer.js +39 -0
- package/dist/cli/cloud-answer.js.map +1 -0
- package/dist/cli/cloud-connect-cli.d.ts +2 -1
- package/dist/cli/cloud-connect-cli.d.ts.map +1 -1
- package/dist/cli/cloud-connect-cli.js +10 -2
- package/dist/cli/cloud-connect-cli.js.map +1 -1
- package/dist/cli/cloud-human.d.ts +7 -0
- package/dist/cli/cloud-human.d.ts.map +1 -0
- package/dist/cli/cloud-human.js +22 -0
- package/dist/cli/cloud-human.js.map +1 -0
- package/dist/cli/cloud-live.d.ts +2 -2
- package/dist/cli/cloud-live.d.ts.map +1 -1
- package/dist/cli/cloud-live.js +9 -2
- package/dist/cli/cloud-live.js.map +1 -1
- package/dist/cli/cloud-mirror-session.d.ts.map +1 -1
- package/dist/cli/cloud-mirror-session.js +3 -2
- package/dist/cli/cloud-mirror-session.js.map +1 -1
- package/dist/cli/cloud-run.d.ts +1 -1
- package/dist/cli/cloud-run.d.ts.map +1 -1
- package/dist/cli/cloud-run.js +12 -0
- package/dist/cli/cloud-run.js.map +1 -1
- package/dist/cli/detached-record.d.ts +22 -0
- package/dist/cli/detached-record.d.ts.map +1 -0
- package/dist/cli/detached-record.js +57 -0
- package/dist/cli/detached-record.js.map +1 -0
- package/dist/cli/detached-run.d.ts +28 -0
- package/dist/cli/detached-run.d.ts.map +1 -0
- package/dist/cli/detached-run.js +122 -0
- package/dist/cli/detached-run.js.map +1 -0
- package/dist/cli/diagnostic-fold.d.ts +19 -0
- package/dist/cli/diagnostic-fold.d.ts.map +1 -0
- package/dist/cli/diagnostic-fold.js +19 -0
- package/dist/cli/diagnostic-fold.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/eval.d.ts +22 -0
- package/dist/cli/eval.d.ts.map +1 -0
- package/dist/cli/eval.js +131 -0
- package/dist/cli/eval.js.map +1 -0
- package/dist/cli/journal-timeout.d.ts +12 -0
- package/dist/cli/journal-timeout.d.ts.map +1 -0
- package/dist/cli/journal-timeout.js +42 -0
- package/dist/cli/journal-timeout.js.map +1 -0
- package/dist/cli/local-agent-remedy.d.ts.map +1 -1
- package/dist/cli/local-agent-remedy.js +5 -4
- package/dist/cli/local-agent-remedy.js.map +1 -1
- package/dist/cli/observer-session.d.ts +1 -0
- package/dist/cli/observer-session.d.ts.map +1 -1
- package/dist/cli/observer-session.js +8 -1
- package/dist/cli/observer-session.js.map +1 -1
- package/dist/cli/reuse.d.ts +8 -0
- package/dist/cli/reuse.d.ts.map +1 -1
- package/dist/cli/reuse.js +24 -0
- package/dist/cli/reuse.js.map +1 -1
- package/dist/cli/run.d.ts +8 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +72 -56
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/running-step.d.ts +9 -0
- package/dist/cli/running-step.d.ts.map +1 -0
- package/dist/cli/running-step.js +156 -0
- package/dist/cli/running-step.js.map +1 -0
- package/dist/cli-adapter.d.ts +3 -1
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +6 -2
- package/dist/cli-adapter.js.map +1 -1
- package/dist/cli-commands.d.ts +77 -11
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +41 -7
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli-watch.d.ts +6 -2
- package/dist/cli-watch.d.ts.map +1 -1
- package/dist/cli-watch.js +7 -6
- package/dist/cli-watch.js.map +1 -1
- package/dist/cli.d.ts +22 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +154 -50
- package/dist/cli.js.map +1 -1
- package/dist/cloud-answer.d.ts +35 -0
- package/dist/cloud-answer.d.ts.map +1 -0
- package/dist/cloud-answer.js +194 -0
- package/dist/cloud-answer.js.map +1 -0
- package/dist/cloud-auth-store.d.ts +46 -0
- package/dist/cloud-auth-store.d.ts.map +1 -0
- package/dist/cloud-auth-store.js +171 -0
- package/dist/cloud-auth-store.js.map +1 -0
- package/dist/cloud-http.d.ts +7 -16
- package/dist/cloud-http.d.ts.map +1 -1
- package/dist/cloud-http.js +59 -40
- package/dist/cloud-http.js.map +1 -1
- package/dist/cloud-human-wait.d.ts +10 -0
- package/dist/cloud-human-wait.d.ts.map +1 -0
- package/dist/cloud-human-wait.js +17 -0
- package/dist/cloud-human-wait.js.map +1 -0
- package/dist/cloud-mirror-transport.d.ts.map +1 -1
- package/dist/cloud-mirror-transport.js +1 -0
- package/dist/cloud-mirror-transport.js.map +1 -1
- package/dist/cloud-run-record.d.ts +7 -2
- package/dist/cloud-run-record.d.ts.map +1 -1
- package/dist/cloud-run-record.js +13 -5
- package/dist/cloud-run-record.js.map +1 -1
- package/dist/cloud-run.d.ts +9 -0
- package/dist/cloud-run.d.ts.map +1 -1
- package/dist/cloud-run.js +36 -2
- package/dist/cloud-run.js.map +1 -1
- package/dist/communication/local.d.ts.map +1 -1
- package/dist/communication/local.js +6 -1
- package/dist/communication/local.js.map +1 -1
- package/dist/communication/worker.d.ts.map +1 -1
- package/dist/communication/worker.js +12 -2
- package/dist/communication/worker.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +5 -3
- package/dist/compile.js.map +1 -1
- package/dist/daemon-spec-validation.d.ts +31 -0
- package/dist/daemon-spec-validation.d.ts.map +1 -0
- package/dist/daemon-spec-validation.js +115 -0
- package/dist/daemon-spec-validation.js.map +1 -0
- package/dist/failure-kinds.d.ts +6 -4
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +17 -1
- package/dist/failure-kinds.js.map +1 -1
- package/dist/flow-eval-cloud.d.ts +25 -0
- package/dist/flow-eval-cloud.d.ts.map +1 -0
- package/dist/flow-eval-cloud.js +108 -0
- package/dist/flow-eval-cloud.js.map +1 -0
- package/dist/flow-eval-error.d.ts +7 -0
- package/dist/flow-eval-error.d.ts.map +1 -0
- package/dist/flow-eval-error.js +10 -0
- package/dist/flow-eval-error.js.map +1 -0
- package/dist/flow-eval-gate.d.ts +15 -0
- package/dist/flow-eval-gate.d.ts.map +1 -0
- package/dist/flow-eval-gate.js +102 -0
- package/dist/flow-eval-gate.js.map +1 -0
- package/dist/flow-eval-local.d.ts +54 -0
- package/dist/flow-eval-local.d.ts.map +1 -0
- package/dist/flow-eval-local.js +220 -0
- package/dist/flow-eval-local.js.map +1 -0
- package/dist/flow-eval-outcome.d.ts +22 -0
- package/dist/flow-eval-outcome.d.ts.map +1 -0
- package/dist/flow-eval-outcome.js +37 -0
- package/dist/flow-eval-outcome.js.map +1 -0
- package/dist/flow-eval-report.d.ts +106 -0
- package/dist/flow-eval-report.d.ts.map +1 -0
- package/dist/flow-eval-report.js +4 -0
- package/dist/flow-eval-report.js.map +1 -0
- package/dist/flow-eval-suite.d.ts +46 -0
- package/dist/flow-eval-suite.d.ts.map +1 -0
- package/dist/flow-eval-suite.js +144 -0
- package/dist/flow-eval-suite.js.map +1 -0
- package/dist/flow-eval-version.d.ts +59 -0
- package/dist/flow-eval-version.d.ts.map +1 -0
- package/dist/flow-eval-version.js +303 -0
- package/dist/flow-eval-version.js.map +1 -0
- package/dist/flow-eval.d.ts +41 -0
- package/dist/flow-eval.d.ts.map +1 -0
- package/dist/flow-eval.js +201 -0
- package/dist/flow-eval.js.map +1 -0
- package/dist/helper-operation-use.d.ts +18 -0
- package/dist/helper-operation-use.d.ts.map +1 -0
- package/dist/helper-operation-use.js +336 -0
- package/dist/helper-operation-use.js.map +1 -0
- package/dist/helper-preflight.d.ts.map +1 -1
- package/dist/helper-preflight.js +16 -2
- package/dist/helper-preflight.js.map +1 -1
- package/dist/helper-reference.d.ts +33 -0
- package/dist/helper-reference.d.ts.map +1 -1
- package/dist/helper-reference.js +5 -5
- package/dist/helper-reference.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/journal-budgeted-reads.d.ts +41 -0
- package/dist/journal-budgeted-reads.d.ts.map +1 -0
- package/dist/journal-budgeted-reads.js +142 -0
- package/dist/journal-budgeted-reads.js.map +1 -0
- package/dist/journal-client.d.ts +34 -29
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +45 -151
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-connection.d.ts +42 -0
- package/dist/journal-connection.d.ts.map +1 -0
- package/dist/journal-connection.js +209 -0
- package/dist/journal-connection.js.map +1 -0
- package/dist/journal-read-policy.d.ts +38 -0
- package/dist/journal-read-policy.d.ts.map +1 -0
- package/dist/journal-read-policy.js +126 -0
- package/dist/journal-read-policy.js.map +1 -0
- package/dist/named-gate-lowering.d.ts.map +1 -1
- package/dist/named-gate-lowering.js +10 -7
- package/dist/named-gate-lowering.js.map +1 -1
- package/dist/re2js-embedded.d.ts +3 -0
- package/dist/re2js-embedded.d.ts.map +1 -0
- package/dist/re2js-embedded.js +4 -0
- package/dist/re2js-embedded.js.map +1 -0
- package/dist/spec.d.ts +10 -0
- 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/validate.d.ts.map +1 -1
- package/dist/validate.js +15 -0
- package/dist/validate.js.map +1 -1
- package/dist/worker-cli.d.ts +3 -1
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +15 -5
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker-lease.d.ts.map +1 -1
- package/dist/worker-lease.js +11 -3
- package/dist/worker-lease.js.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +5 -3
- package/src/adapters/base.ts +16 -1
- package/src/adapters/claude.ts +8 -4
- package/src/adapters/codex.ts +15 -6
- package/src/agent-resume.ts +36 -0
- package/src/agent-transcript.ts +5 -3
- package/src/authored-flow-error.ts +9 -0
- package/src/authored-flow-executor.ts +18 -1
- package/src/authored-flow-loader.ts +27 -10
- package/src/authored-helper-effect.ts +3 -2
- package/src/authored-human.ts +5 -0
- package/src/authored-memory.ts +445 -15
- package/src/authored-node-entry.ts +6 -1
- package/src/authored-node-runner.ts +6 -3
- package/src/authored-root-readback.ts +100 -0
- package/src/authored-root.ts +58 -85
- package/src/authored-step-output.ts +3 -0
- package/src/authored-worker-step.ts +29 -4
- package/src/cli/check-helper-body.ts +9 -3
- package/src/cli/check-helper-surface.ts +43 -0
- package/src/cli/check-triggers.ts +9 -4
- package/src/cli/check.ts +37 -7
- package/src/cli/cloud-answer.ts +40 -0
- package/src/cli/cloud-connect-cli.ts +9 -2
- package/src/cli/cloud-human.ts +24 -0
- package/src/cli/cloud-live.ts +10 -5
- package/src/cli/cloud-mirror-session.ts +3 -2
- package/src/cli/cloud-run.ts +10 -1
- package/src/cli/detached-record.ts +71 -0
- package/src/cli/detached-run.ts +131 -0
- package/src/cli/diagnostic-fold.ts +28 -0
- package/src/cli/direct-run.ts +14 -1
- package/src/cli/eval.ts +141 -0
- package/src/cli/journal-timeout.ts +49 -0
- package/src/cli/local-agent-remedy.ts +6 -4
- package/src/cli/observer-session.ts +7 -1
- package/src/cli/reuse.ts +25 -0
- package/src/cli/run.ts +75 -67
- package/src/cli/running-step.ts +140 -0
- package/src/cli-adapter.ts +7 -1
- package/src/cli-commands.ts +43 -7
- package/src/cli-watch.ts +8 -5
- package/src/cli.ts +158 -51
- package/src/cloud-answer.ts +202 -0
- package/src/cloud-auth-store.ts +185 -0
- package/src/cloud-http.ts +61 -50
- package/src/cloud-human-wait.ts +23 -0
- package/src/cloud-mirror-transport.ts +1 -0
- package/src/cloud-run-record.ts +14 -5
- package/src/cloud-run.ts +46 -2
- package/src/communication/local.ts +6 -1
- package/src/communication/worker.ts +11 -3
- package/src/compile.ts +5 -3
- package/src/daemon-spec-validation.ts +126 -0
- package/src/failure-kinds.ts +17 -1
- package/src/flow-eval-cloud.ts +114 -0
- package/src/flow-eval-error.ts +10 -0
- package/src/flow-eval-gate.ts +114 -0
- package/src/flow-eval-local.ts +236 -0
- package/src/flow-eval-outcome.ts +43 -0
- package/src/flow-eval-report.ts +102 -0
- package/src/flow-eval-suite.ts +169 -0
- package/src/flow-eval-version.ts +316 -0
- package/src/flow-eval.ts +234 -0
- package/src/helper-operation-use.ts +285 -0
- package/src/helper-preflight.ts +17 -2
- package/src/helper-reference.ts +6 -6
- package/src/index.ts +15 -1
- package/src/journal-budgeted-reads.ts +134 -0
- package/src/journal-client.ts +59 -166
- package/src/journal-connection.ts +222 -0
- package/src/journal-read-policy.ts +123 -0
- package/src/named-gate-lowering.ts +10 -7
- package/src/re2js-embedded.ts +3 -0
- package/src/spec.ts +10 -0
- package/src/step-fields.ts +1 -1
- package/src/validate.ts +14 -0
- package/src/worker-cli.ts +14 -3
- package/src/worker-lease.ts +11 -3
- package/src/worker.ts +1 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { isFunction, isNode, memberName, parseFlowBody, walkReferences, type AstNode } from './helper-reference.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a flow body calls `f.<namespace>.<method>`.
|
|
5
|
+
*
|
|
6
|
+
* - `called`: a direct, named call such as `f.notion.appendBlock(...)`.
|
|
7
|
+
* - `absent`: the namespace is only ever used directly — `f.notion.x(...)` with
|
|
8
|
+
* named methods — or read (`if (f.notion)`, `typeof`, comparisons, a discarded
|
|
9
|
+
* `f.notion && ...`), so the call provably does not occur.
|
|
10
|
+
* - `unprovable`: the namespace or the whole context leaves direct use — an
|
|
11
|
+
* alias, destructuring, an argument, a return, a property, a computed member
|
|
12
|
+
* or namespace. Its later calls cannot be attributed statically, so callers
|
|
13
|
+
* that must fail closed treat this as the call.
|
|
14
|
+
*
|
|
15
|
+
* Same scoped parse as `helperNamespacesUsed`: strings and comments naming the
|
|
16
|
+
* call are not uses, and a nested binding of the context name is not the context.
|
|
17
|
+
* An unparseable body falls back to the text, permissively.
|
|
18
|
+
*/
|
|
19
|
+
export function helperOperationUse(body: string, root: string, namespace: string, method: string): 'called' | 'absent' | 'unprovable' {
|
|
20
|
+
const program = parseFlowBody(body);
|
|
21
|
+
if (program === null) return new RegExp(`\\.\\s*${method}\\b`).test(body) ? 'unprovable' : 'absent';
|
|
22
|
+
const parents = new WeakMap<AstNode, AstNode>();
|
|
23
|
+
const all: AstNode[] = [];
|
|
24
|
+
const link = (node: AstNode): void => {
|
|
25
|
+
all.push(node);
|
|
26
|
+
for (const key of Object.keys(node)) {
|
|
27
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
|
|
28
|
+
const child = node[key];
|
|
29
|
+
for (const entry of Array.isArray(child) ? child : [child]) {
|
|
30
|
+
if (isNode(entry)) { parents.set(entry, node); link(entry); }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
link(program);
|
|
35
|
+
let result: 'called' | 'absent' | 'unprovable' = 'absent';
|
|
36
|
+
const mark = (found: 'called' | 'unprovable') => { if (result !== 'unprovable') result = found; };
|
|
37
|
+
// Body-wide, whatever scope binds the context name: code that rewrites the
|
|
38
|
+
// prototypes helpers inherit from (or runs source text) can intercept the
|
|
39
|
+
// helper anywhere, so nothing is attributable once it is present.
|
|
40
|
+
if (all.some(node => reachesMachinery(node, parents))) return 'unprovable';
|
|
41
|
+
walkReferences(program, root, false, { rootFunctionFound: false }, (node) => {
|
|
42
|
+
// `arguments[0]` is the context in a non-arrow root body without naming it.
|
|
43
|
+
if (node.type === 'Identifier' && node.name === 'arguments' && rootArguments(node, parents)) return mark('unprovable');
|
|
44
|
+
if (node.type !== 'Identifier' || node.name !== root) return;
|
|
45
|
+
const parent = parents.get(node);
|
|
46
|
+
if (parent === undefined || declares(parent, node)) return;
|
|
47
|
+
if (parent.type === 'MemberExpression' && parent.object === node) {
|
|
48
|
+
const name = memberName(parent);
|
|
49
|
+
if (name === undefined) return mark('unprovable'); // f[expr]
|
|
50
|
+
if (name !== namespace) return;
|
|
51
|
+
const grand = parents.get(parent);
|
|
52
|
+
if (grand?.type === 'MemberExpression' && grand.object === parent) {
|
|
53
|
+
const operation = memberName(grand);
|
|
54
|
+
if (operation === undefined) mark('unprovable'); // f.notion[expr]
|
|
55
|
+
else if (operation === method) {
|
|
56
|
+
// Calling it is the operation; a read (typeof, comparison) is not;
|
|
57
|
+
// anything else hands the function on.
|
|
58
|
+
// Any use but a pure read reaches the operation: a call, however
|
|
59
|
+
// wrapped (parentheses, comma, ||, ?:, new), or the function handed on.
|
|
60
|
+
if (!readOrCall(grand, parents, false)) mark('called');
|
|
61
|
+
}
|
|
62
|
+
// Inherited object members (valueOf, constructor, toString, ...) can hand
|
|
63
|
+
// the helper back — directly, or through a prototype the body replaced —
|
|
64
|
+
// so none is attributable; helper methods return steps.
|
|
65
|
+
else if (operation === '__proto__' || Object.prototype.hasOwnProperty.call(Object.prototype, operation)) mark('unprovable');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!readOrCall(parent, parents)) mark('unprovable');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// The whole context used as a value (`const { notion } = f`, `use(f)`).
|
|
72
|
+
if (!readOrCall(node, parents)) mark('unprovable');
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Every name a binding pattern introduces. */
|
|
78
|
+
function patternNames(pattern: AstNode | undefined | null, into: Set<string>): void {
|
|
79
|
+
if (!pattern) return;
|
|
80
|
+
if (pattern.type === 'Identifier') into.add(pattern.name!);
|
|
81
|
+
else if (pattern.type === 'AssignmentPattern') patternNames(pattern.left as AstNode, into);
|
|
82
|
+
else if (pattern.type === 'RestElement') patternNames(pattern.argument as AstNode, into);
|
|
83
|
+
else if (pattern.type === 'ArrayPattern') for (const element of pattern.elements as Array<AstNode | null>) patternNames(element, into);
|
|
84
|
+
else if (pattern.type === 'ObjectPattern') {
|
|
85
|
+
for (const property of pattern.properties as AstNode[]) patternNames((property.type === 'Property' ? property.value : property.argument) as AstNode, into);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The names one scope node binds directly: its parameters, function name, catch parameter, or declarations. */
|
|
90
|
+
function scopeBindings(scope: AstNode): Set<string> {
|
|
91
|
+
const names = new Set<string>();
|
|
92
|
+
if (isFunction(scope)) {
|
|
93
|
+
if (scope.type === 'FunctionExpression') patternNames(scope.id as AstNode | undefined, names);
|
|
94
|
+
for (const param of (scope.params as AstNode[] | undefined) ?? []) patternNames(param, names);
|
|
95
|
+
}
|
|
96
|
+
if (scope.type === 'CatchClause') patternNames(scope.param as AstNode | undefined, names);
|
|
97
|
+
const statements = scope.type === 'BlockStatement' || scope.type === 'Program' ? scope.body as AstNode[]
|
|
98
|
+
: ['ForStatement', 'ForInStatement', 'ForOfStatement'].includes(scope.type) ? [(scope.init ?? scope.left) as AstNode] : [];
|
|
99
|
+
for (const statement of statements ?? []) {
|
|
100
|
+
if (statement?.type === 'VariableDeclaration') {
|
|
101
|
+
for (const declarator of statement.declarations as AstNode[]) patternNames(declarator.id as AstNode, names);
|
|
102
|
+
} else if (statement?.type === 'FunctionDeclaration' || statement?.type === 'ClassDeclaration') {
|
|
103
|
+
patternNames(statement.id as AstNode | undefined, names);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return names;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Whether `node`, a use of `name`, resolves to a binding in an enclosing scope rather than the global. */
|
|
110
|
+
function boundLocally(node: AstNode, name: string, parents: WeakMap<AstNode, AstNode>): boolean {
|
|
111
|
+
for (let at = parents.get(node); at !== undefined; at = parents.get(at)) {
|
|
112
|
+
if (scopeBindings(at).has(name)) return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Prototype machinery, eval or Function: anything that lets the body intercept or hide a helper use. */
|
|
118
|
+
function reachesMachinery(node: AstNode, parents: WeakMap<AstNode, AstNode>): boolean {
|
|
119
|
+
// memberName also reads string-literal computed keys (Object['prototype']).
|
|
120
|
+
if (node.type === 'MemberExpression' && PROTOTYPE_MACHINERY.has(memberName(node) ?? '')) {
|
|
121
|
+
// A class or function the body defines has its own prototype, which cannot
|
|
122
|
+
// reach the helper: Local.prototype.flag = true is not machinery.
|
|
123
|
+
const object = node.object as AstNode | undefined;
|
|
124
|
+
return !(memberName(node) === 'prototype' && object?.type === 'Identifier' && ownConstructor(object, parents));
|
|
125
|
+
}
|
|
126
|
+
// A prototype-bearing global is safe only as the object of a static,
|
|
127
|
+
// non-machinery member (Object.keys, Symbol.iterator). Aliasing it, passing
|
|
128
|
+
// it, destructuring it or indexing it with a runtime key can reach the
|
|
129
|
+
// machinery under another name.
|
|
130
|
+
if (node.type === 'Identifier' && (MACHINERY_GLOBALS.has(node.name!) || GLOBAL_ROOTS.has(node.name!))) {
|
|
131
|
+
const parent = parents.get(node);
|
|
132
|
+
if (parent !== undefined && declares(parent, node)) return false;
|
|
133
|
+
if (parent?.type === 'VariableDeclarator' && parent.id === node) return false;
|
|
134
|
+
if (boundLocally(node, node.name!, parents)) return false;
|
|
135
|
+
if (!GLOBAL_ROOTS.has(node.name!)) return onlyStaticSafeMember(node, parents);
|
|
136
|
+
// A global root (globalThis, global, window, self) reaches every global:
|
|
137
|
+
// follow root-to-root chains, then judge a named prototype-bearing global
|
|
138
|
+
// like the bare one. Any other use of a root can reach them by any name.
|
|
139
|
+
let value: AstNode = node;
|
|
140
|
+
for (let up = parents.get(value); up?.type === 'MemberExpression' && up.object === value
|
|
141
|
+
&& GLOBAL_ROOTS.has(memberName(up) ?? ''); up = parents.get(value)) value = up;
|
|
142
|
+
const up = parents.get(value);
|
|
143
|
+
const named = up?.type === 'MemberExpression' && up.object === value ? memberName(up) : undefined;
|
|
144
|
+
if (named !== undefined && MACHINERY_GLOBALS.has(named)) return onlyStaticSafeMember(up!, parents);
|
|
145
|
+
return named === undefined || PROTOTYPE_MACHINERY.has(named);
|
|
146
|
+
}
|
|
147
|
+
// Destructuring a prototype-bearing global at all (computed keys, rest)
|
|
148
|
+
// can pick out any of its machinery.
|
|
149
|
+
if ((node.type === 'VariableDeclarator' && (node.id as AstNode).type === 'ObjectPattern')
|
|
150
|
+
|| (node.type === 'AssignmentExpression' && (node.left as AstNode).type === 'ObjectPattern')) {
|
|
151
|
+
const source = (node.type === 'VariableDeclarator' ? node.init : node.right) as AstNode | undefined;
|
|
152
|
+
if (source?.type === 'Identifier' && MACHINERY_GLOBALS.has(source.name!) && !boundLocally(source, source.name!, parents)) return true;
|
|
153
|
+
}
|
|
154
|
+
// Destructuring reads the same members: const { prototype: p } = Object.
|
|
155
|
+
if (node.type === 'Property' && parents.get(node)?.type === 'ObjectPattern') {
|
|
156
|
+
const key = node.key as AstNode | undefined;
|
|
157
|
+
const name = key?.type === 'Identifier' && node.computed !== true ? key.name
|
|
158
|
+
: key?.type === 'Literal' && typeof key.value === 'string' ? key.value : undefined;
|
|
159
|
+
if (name !== undefined && PROTOTYPE_MACHINERY.has(name)) return true;
|
|
160
|
+
}
|
|
161
|
+
if (node.type === 'Identifier' && ['Reflect', 'Proxy', 'eval', 'Function'].includes(node.name!)) {
|
|
162
|
+
const parent = parents.get(node);
|
|
163
|
+
if (parent !== undefined && declares(parent, node)) return false;
|
|
164
|
+
if (parent?.type === 'VariableDeclarator' && parent.id === node) return false;
|
|
165
|
+
// eval and Function matter only when called; a use resolving to a local
|
|
166
|
+
// binding (scope by scope, not body-wide) is not the global.
|
|
167
|
+
// Any use of eval (indirect call, alias) runs source text the scan never
|
|
168
|
+
// sees; Function is judged with the other prototype-bearing globals.
|
|
169
|
+
return !boundLocally(node, node.name!, parents);
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Whether `node` names a class or function the body itself defines, in an enclosing scope. */
|
|
175
|
+
function ownConstructor(node: AstNode, parents: WeakMap<AstNode, AstNode>): boolean {
|
|
176
|
+
const name = node.name!;
|
|
177
|
+
// Class and function bindings are reassignable; one that is reassigned
|
|
178
|
+
// anywhere could hold any constructor.
|
|
179
|
+
let program: AstNode = node;
|
|
180
|
+
for (let at = parents.get(node); at !== undefined; at = parents.get(at)) program = at;
|
|
181
|
+
if (reassigns(program, name)) return false;
|
|
182
|
+
const constructs = (value: AstNode | undefined) => value?.type === 'ClassExpression' || value?.type === 'FunctionExpression';
|
|
183
|
+
for (let scope = parents.get(node); scope !== undefined; scope = parents.get(scope)) {
|
|
184
|
+
const statements = scope.type === 'BlockStatement' || scope.type === 'Program' ? scope.body as AstNode[] : [];
|
|
185
|
+
for (const statement of statements) {
|
|
186
|
+
if ((statement.type === 'ClassDeclaration' || statement.type === 'FunctionDeclaration')
|
|
187
|
+
&& (statement.id as AstNode | undefined)?.name === name) return true;
|
|
188
|
+
if (statement.type === 'VariableDeclaration' && statement.kind === 'const') {
|
|
189
|
+
for (const declarator of statement.declarations as AstNode[]) {
|
|
190
|
+
if ((declarator.id as AstNode).type === 'Identifier' && (declarator.id as AstNode).name === name) return constructs(declarator.init as AstNode | undefined);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (scopeBindings(scope).has(name)) return false; // bound here to something else
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Whether any assignment or update anywhere under `node` targets the identifier `name`. */
|
|
200
|
+
function reassigns(node: AstNode, name: string): boolean {
|
|
201
|
+
if ((node.type === 'AssignmentExpression' && (node.left as AstNode).type === 'Identifier' && (node.left as AstNode).name === name)
|
|
202
|
+
|| (node.type === 'UpdateExpression' && (node.argument as AstNode).type === 'Identifier' && (node.argument as AstNode).name === name)) return true;
|
|
203
|
+
for (const key of Object.keys(node)) {
|
|
204
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
|
|
205
|
+
const child = node[key];
|
|
206
|
+
for (const entry of Array.isArray(child) ? child : [child]) if (isNode(entry) && reassigns(entry, name)) return true;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* For a value that is a prototype-bearing global: true (machinery reached)
|
|
213
|
+
* unless it is only the object of a static, non-machinery member.
|
|
214
|
+
*/
|
|
215
|
+
function onlyStaticSafeMember(value: AstNode, parents: WeakMap<AstNode, AstNode>): boolean {
|
|
216
|
+
const parent = parents.get(value);
|
|
217
|
+
const member = parent?.type === 'MemberExpression' && parent.object === value ? memberName(parent) : undefined;
|
|
218
|
+
return member === undefined || PROTOTYPE_MACHINERY.has(member);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Globals whose members include the prototype machinery. */
|
|
222
|
+
const MACHINERY_GLOBALS = new Set(['Object', 'Symbol', 'Reflect', 'Function', 'Proxy']);
|
|
223
|
+
/** Names for the global object itself, through which every global is reachable. */
|
|
224
|
+
const GLOBAL_ROOTS = new Set(['globalThis', 'global', 'window', 'self']);
|
|
225
|
+
|
|
226
|
+
/** Members through which a body can reach or rewrite the prototypes every helper inherits from. */
|
|
227
|
+
const PROTOTYPE_MACHINERY = new Set(['prototype', '__proto__', 'defineProperty', 'defineProperties',
|
|
228
|
+
'setPrototypeOf', 'getPrototypeOf', 'toPrimitive', '__defineGetter__', '__defineSetter__',
|
|
229
|
+
'__lookupGetter__', '__lookupSetter__',
|
|
230
|
+
// Any function's (or global's) constructor chain reaches Function, which compiles source text,
|
|
231
|
+
// and eval (globalThis.eval, ...) runs it directly.
|
|
232
|
+
'constructor', 'eval']);
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A use that cannot hand the value on: a discarded value or a read, and —
|
|
236
|
+
* when `allowCall` — a direct call of it.
|
|
237
|
+
*/
|
|
238
|
+
function readOrCall(value: AstNode, parents: WeakMap<AstNode, AstNode>, allowCall = true): boolean {
|
|
239
|
+
let current = value;
|
|
240
|
+
let parent = parents.get(current);
|
|
241
|
+
// Wrappers that pass their operand's value through unchanged.
|
|
242
|
+
while (parent !== undefined && ['ChainExpression', 'ParenthesizedExpression', 'AwaitExpression',
|
|
243
|
+
'LogicalExpression', 'ConditionalExpression', 'SequenceExpression'].includes(parent.type)
|
|
244
|
+
&& !(parent.type === 'ConditionalExpression' && parent.test === current)) {
|
|
245
|
+
// Only a sequence's last operand is its value; earlier ones are discarded reads.
|
|
246
|
+
if (parent.type === 'SequenceExpression' && (parent.expressions as AstNode[]).at(-1) !== current) return true;
|
|
247
|
+
current = parent;
|
|
248
|
+
parent = parents.get(current);
|
|
249
|
+
}
|
|
250
|
+
if (parent === undefined) return false;
|
|
251
|
+
if ((parent.type === 'CallExpression' || parent.type === 'NewExpression') && parent.callee === current) return allowCall;
|
|
252
|
+
// `instanceof` hands its left operand to the right side's Symbol.hasInstance.
|
|
253
|
+
if (parent.type === 'BinaryExpression') return parent.operator !== 'instanceof';
|
|
254
|
+
if (['ExpressionStatement', 'UnaryExpression'].includes(parent.type)) return true;
|
|
255
|
+
// `${f.notion}` coerces to a string; a tagged template hands the value to its tag.
|
|
256
|
+
if (parent.type === 'TemplateLiteral') return parents.get(parent)?.type !== 'TaggedTemplateExpression';
|
|
257
|
+
return ['IfStatement', 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ConditionalExpression'].includes(parent.type)
|
|
258
|
+
&& parent.test === current;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* `arguments` used (not named) inside the root flow function itself — the
|
|
263
|
+
* outermost non-arrow function, whatever shape its context parameter has — not
|
|
264
|
+
* inside a nested non-arrow function with its own `arguments`.
|
|
265
|
+
*/
|
|
266
|
+
function rootArguments(node: AstNode, parents: WeakMap<AstNode, AstNode>): boolean {
|
|
267
|
+
const parent = parents.get(node);
|
|
268
|
+
if (parent !== undefined && declares(parent, node)) return false;
|
|
269
|
+
let owner: AstNode | undefined;
|
|
270
|
+
for (let at = parents.get(node); at !== undefined; at = parents.get(at)) {
|
|
271
|
+
if (at.type === 'FunctionDeclaration' || at.type === 'FunctionExpression') { owner = at; break; }
|
|
272
|
+
}
|
|
273
|
+
if (owner === undefined) return false;
|
|
274
|
+
for (let at = parents.get(owner); at !== undefined; at = parents.get(at)) {
|
|
275
|
+
if (at.type === 'FunctionDeclaration' || at.type === 'FunctionExpression' || at.type === 'ArrowFunctionExpression') return false;
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** The identifier is a name being declared or a property name, not a use of the context. */
|
|
281
|
+
function declares(parent: AstNode, node: AstNode): boolean {
|
|
282
|
+
if (isFunction(parent)) return ((parent.params as AstNode[] | undefined) ?? []).includes(node);
|
|
283
|
+
if (parent.type === 'Property') return parent.key === node && parent.computed !== true && parent.shorthand !== true;
|
|
284
|
+
return parent.type === 'MemberExpression' && parent.property === node && parent.computed !== true;
|
|
285
|
+
}
|
package/src/helper-preflight.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { helperProviders } from '@relayflows/surface/runtime';
|
|
2
2
|
import type { PreflightResult, PreflightDiagnostic } from './preflight.js';
|
|
3
3
|
import { helperNamespacesUsed } from './helper-reference.js';
|
|
4
|
+
import { helperOperationUse } from './helper-operation-use.js';
|
|
4
5
|
|
|
5
6
|
/** Static discovery never executes the body; dynamic aliases are checked at call time. */
|
|
6
7
|
export function preflightHelpers(
|
|
@@ -18,6 +19,16 @@ export function preflightHelpers(
|
|
|
18
19
|
// comment, template quasi or regex is not used, and refusing on one demands
|
|
19
20
|
// a mount the flow never touches.
|
|
20
21
|
const referenced = root === undefined ? new Set<string>() : helperNamespacesUsed(body, root);
|
|
22
|
+
// f.notion.appendBlock has no writeback route. It is attributed precisely only
|
|
23
|
+
// while f.notion is used directly; once it is aliased, passed on or reached by a
|
|
24
|
+
// computed name, the call cannot be ruled out and check refuses, saying why.
|
|
25
|
+
// Without a named context parameter (destructured, say) nothing can be
|
|
26
|
+
// attributed, so a declared Notion flow cannot rule the call out.
|
|
27
|
+
const notionAppend = body.trim() === '' ? 'absent'
|
|
28
|
+
: root === undefined ? 'unprovable'
|
|
29
|
+
: helperOperationUse(body, root, 'notion', 'appendBlock');
|
|
30
|
+
const appendsNotionBlock = notionAppend !== 'absent';
|
|
31
|
+
const notionAppendInferred = notionAppend === 'unprovable';
|
|
21
32
|
for (const { provider, namespace, supported } of helperProviders) {
|
|
22
33
|
const used = definition.header?.tools?.[namespace] === true
|
|
23
34
|
|| referenced.has(namespace);
|
|
@@ -32,9 +43,13 @@ export function preflightHelpers(
|
|
|
32
43
|
diagnostics.push({ severity: 'refusal',
|
|
33
44
|
kind: provider === 'slack' ? (fact.token?.trim() ? 'helper_slack.mount_required' : 'helper_slack.credential_missing') : 'helper_provider.mount_required',
|
|
34
45
|
message: `f.${namespace} requires a relayfile ${provider} mount; direct-token transport is not implemented.` });
|
|
35
|
-
}
|
|
46
|
+
}
|
|
47
|
+
// Missing local mounts must not hide an operation unsupported on Cloud too.
|
|
48
|
+
if (supported && provider === 'notion' && !fact.mock && appendsNotionBlock) {
|
|
36
49
|
diagnostics.push({ severity: 'refusal', kind: 'helper_provider.unsupported',
|
|
37
|
-
message: 'f.notion.appendBlock is mock-only: the Notion adapter has no append-block writeback route.'
|
|
50
|
+
message: 'f.notion.appendBlock is mock-only: the Notion adapter has no append-block writeback route.'
|
|
51
|
+
+ (notionAppendInferred ? ' f.notion is aliased, passed on or called through a computed name in this body, so the call cannot be ruled out;'
|
|
52
|
+
+ ' call f.notion methods directly by name on a named context parameter (f.notion.createPage(...)) so flows check can see which ones run.' : '') });
|
|
38
53
|
}
|
|
39
54
|
}
|
|
40
55
|
return { ok: diagnostics.length === 0, gates: [], resolutions: [], diagnostics };
|
package/src/helper-reference.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function helperNamespacesUsed(body: string, root: string): ReadonlySet<st
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** `f.slack`, `f["slack"]`, `f?.slack` — but not `f[variable]`, which is unknowable. */
|
|
42
|
-
function memberName(node: AstNode): string | undefined {
|
|
42
|
+
export function memberName(node: AstNode): string | undefined {
|
|
43
43
|
const property = node.property as AstNode | undefined;
|
|
44
44
|
if (property === undefined) return undefined;
|
|
45
45
|
if (node.computed !== true) return property.type === 'Identifier' ? property.name : undefined;
|
|
@@ -55,7 +55,7 @@ function memberName(node: AstNode): string | undefined {
|
|
|
55
55
|
* here (Node strips before evaluating, and bundlers transpile), so this is
|
|
56
56
|
* plain JavaScript.
|
|
57
57
|
*/
|
|
58
|
-
function parseFlowBody(body: string): AstNode | null {
|
|
58
|
+
export function parseFlowBody(body: string): AstNode | null {
|
|
59
59
|
const options = { ecmaVersion: 'latest' as const, allowAwaitOutsideFunction: true, allowReturnOutsideFunction: true };
|
|
60
60
|
// `parseExpressionAt` stops at the end of the first expression and does not
|
|
61
61
|
// object to what follows, so `async post(f) { … }` parses as the identifier
|
|
@@ -103,7 +103,7 @@ function textFallback(body: string, root: string): ReadonlySet<string> {
|
|
|
103
103
|
return used;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
type AstNode = {
|
|
106
|
+
export type AstNode = {
|
|
107
107
|
type: string;
|
|
108
108
|
end?: number;
|
|
109
109
|
name?: string;
|
|
@@ -120,7 +120,7 @@ type AstNode = {
|
|
|
120
120
|
* would refuse the flow for a mount it never touches. Acorn gives us binding
|
|
121
121
|
* shapes, so scopes are tracked as syntax instead of guessed from text.
|
|
122
122
|
*/
|
|
123
|
-
function walkReferences(
|
|
123
|
+
export function walkReferences(
|
|
124
124
|
node: AstNode,
|
|
125
125
|
root: string,
|
|
126
126
|
shadowed: boolean,
|
|
@@ -165,7 +165,7 @@ function walkReferences(
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
function isFunction(node: AstNode): boolean {
|
|
168
|
+
export function isFunction(node: AstNode): boolean {
|
|
169
169
|
return node.type === 'ArrowFunctionExpression'
|
|
170
170
|
|| node.type === 'FunctionExpression'
|
|
171
171
|
|| node.type === 'FunctionDeclaration';
|
|
@@ -250,6 +250,6 @@ function functionVarBinds(node: AstNode, root: string): boolean {
|
|
|
250
250
|
return found;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
function isNode(value: unknown): value is AstNode {
|
|
253
|
+
export function isNode(value: unknown): value is AstNode {
|
|
254
254
|
return typeof value === 'object' && value !== null && typeof (value as { type?: unknown }).type === 'string';
|
|
255
255
|
}
|
package/src/index.ts
CHANGED
|
@@ -119,6 +119,17 @@ export {
|
|
|
119
119
|
} from './flow-requirements.js';
|
|
120
120
|
export { parseHumanTo, parseHumanRecipient, humanRecipientProvider, type HumanRecipient, type HumanRecipientParse } from './human-to.js';
|
|
121
121
|
|
|
122
|
+
export {
|
|
123
|
+
evaluateFlow, parseFlowEvalSuite, loadFlowEvalSuite, flowEvalSuiteSha256, flowEvalVersion, flowEvalSources,
|
|
124
|
+
parseFlowEvalBaseline, FlowEvalError, FLOW_EVAL_REPORT_SCHEMA_VERSION, MAX_FLOW_EVAL_CASES, MAX_FLOW_EVAL_SOURCES,
|
|
125
|
+
type EvaluateFlowOptions, type FlowEvalBaseline, type FlowEvalCase, type FlowEvalCaseResult, type FlowEvalErrorCode,
|
|
126
|
+
type FlowEvalExecutionRequest, type FlowEvalExecutor, type FlowEvalExpectation, type FlowEvalOutcome,
|
|
127
|
+
type FlowEvalReport, type FlowEvalRun, type FlowEvalScore, type FlowEvalScorer, type FlowEvalStep,
|
|
128
|
+
type FlowEvalSuite, type FlowEvalTarget, type FlowEvalThresholds, type FlowEvalVersion,
|
|
129
|
+
} from './flow-eval.js';
|
|
130
|
+
export { localFlowEvalExecutor, type LocalFlowEvalExecutorOptions } from './flow-eval-local.js';
|
|
131
|
+
export { cloudFlowEvalExecutor, type CloudFlowEvalExecutorOptions } from './flow-eval-cloud.js';
|
|
132
|
+
|
|
122
133
|
export { canonicalize, specHash } from './canonical.js';
|
|
123
134
|
export {
|
|
124
135
|
compileAndHash,
|
|
@@ -217,7 +228,7 @@ export type {
|
|
|
217
228
|
} from './protocol.js';
|
|
218
229
|
export { JOURNAL_WRITE_FAILED, PROTOCOL_VERSION } from './protocol.js';
|
|
219
230
|
|
|
220
|
-
export { JournalClient, type JournalClientOptions } from './journal-client.js';
|
|
231
|
+
export { JournalClient, JournalRequestTimeoutError, type JournalClientOptions } from './journal-client.js';
|
|
221
232
|
export { AgentWorker, type AgentWorkerOptions } from './worker.js';
|
|
222
233
|
|
|
223
234
|
export {
|
|
@@ -287,3 +298,6 @@ export { createFlow, type CreateFlowOptions, type CreatedFlow } from './create-f
|
|
|
287
298
|
export { renderProgress, type ProgressEvent } from './progress.js';
|
|
288
299
|
export { webhookTriggerSpec } from './trigger-executor.js';
|
|
289
300
|
export { scheduleTriggerSpec, scheduleLowering, SCHEDULE_EXECUTOR, type ScheduleLowering } from './schedule-trigger.js';
|
|
301
|
+
|
|
302
|
+
export { answerCloudFlow, readCloudHumanWait, CloudAnswerError, type CloudAnswerOptions, type CloudResumeReceipt } from './cloud-answer.js';
|
|
303
|
+
export type { CloudHumanWait } from './cloud-human-wait.js';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { JournalFrameError, JournalProtocolError } from './journal-connection.js';
|
|
2
|
+
import type { JournalClient } from './journal-client.js';
|
|
3
|
+
import { JournalReadInterruptedError, JournalReadPolicy, JournalRequestTimeoutError } from './journal-read-policy.js';
|
|
4
|
+
import type { VerbContract } from './protocol.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Budgeted, retried reads for one flow client, on a dedicated reader session.
|
|
8
|
+
*
|
|
9
|
+
* Reads go to a second connection so they never queue behind the primary's
|
|
10
|
+
* unbounded commands; the policy serializes them and retries timeouts and lost
|
|
11
|
+
* reader transport within the budget. Protocol refusals and malformed frames
|
|
12
|
+
* fail closed. A refused reader handshake falls back to the primary session,
|
|
13
|
+
* whose own failures keep their meaning because it also carries writes.
|
|
14
|
+
*/
|
|
15
|
+
export class BudgetedReads {
|
|
16
|
+
private readonly policy = new JournalReadPolicy();
|
|
17
|
+
private reader: JournalClient | undefined;
|
|
18
|
+
private readerReady: Promise<JournalClient | undefined> | undefined;
|
|
19
|
+
private readonly scoped = new Set<AbortSignal>();
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly primary: JournalClient,
|
|
23
|
+
private readonly budgetMs: number,
|
|
24
|
+
private readonly lifecycle?: AbortSignal,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
/** Add an abort source (a root attempt's lease) for as long as the returned release is not called. */
|
|
28
|
+
scope(signal: AbortSignal): () => void {
|
|
29
|
+
this.scoped.add(signal);
|
|
30
|
+
return () => { this.scoped.delete(signal); };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stop all reads. An unexpected primary drop (`interrupted`) ends in-flight
|
|
35
|
+
* reads as a read interruption, so the run stays resumable; a caller close
|
|
36
|
+
* keeps its plain error.
|
|
37
|
+
*/
|
|
38
|
+
close(cause?: unknown, interrupted = false): void {
|
|
39
|
+
this.policy.close(interrupted ? new JournalReadInterruptedError('journal', 1, 0, undefined, { cause }) : undefined);
|
|
40
|
+
this.reader?.close(cause);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
read<V extends keyof VerbContract>(verb: V, params: VerbContract[V]['params'], timeoutMs: number,
|
|
44
|
+
signal?: AbortSignal): Promise<VerbContract[V]['result']> {
|
|
45
|
+
const sources = [signal, this.lifecycle, ...this.scoped].filter((source): source is AbortSignal => source !== undefined);
|
|
46
|
+
const combined = sources.length <= 1 ? sources[0] : AbortSignal.any(sources);
|
|
47
|
+
return this.policy.read(verb, timeoutMs, this.budgetMs, async (bound, attemptSignal) => {
|
|
48
|
+
const started = performance.now();
|
|
49
|
+
const reader = await this.untilAborted(this.session(), attemptSignal).catch(error => {
|
|
50
|
+
attemptSignal.throwIfAborted();
|
|
51
|
+
// A malformed handshake frame is a protocol violation: fail closed.
|
|
52
|
+
if (error instanceof JournalFrameError) throw error;
|
|
53
|
+
throw new JournalReadInterruptedError(verb, 1, performance.now() - started, undefined, { cause: error });
|
|
54
|
+
});
|
|
55
|
+
attemptSignal.throwIfAborted();
|
|
56
|
+
const remaining = bound - (performance.now() - started);
|
|
57
|
+
if (remaining <= 0) throw new JournalRequestTimeoutError(verb, bound);
|
|
58
|
+
if (reader === undefined) return this.primary.requestOnce(verb, params, remaining, attemptSignal);
|
|
59
|
+
try {
|
|
60
|
+
return await reader.requestOnce(verb, params, remaining, attemptSignal);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// relayflowd answers one frame at a time per connection, so a canceled
|
|
63
|
+
// request still occupies this reader: drop it for a fresh session.
|
|
64
|
+
if (attemptSignal.aborted) { this.drop(reader); throw error; }
|
|
65
|
+
// A timed-out request still occupies this sequential connection: retry
|
|
66
|
+
// on a fresh reader rather than queueing behind it.
|
|
67
|
+
if (error instanceof JournalRequestTimeoutError) { this.drop(reader); throw error; }
|
|
68
|
+
if (error instanceof JournalProtocolError || error instanceof JournalFrameError) throw error;
|
|
69
|
+
// Only the dedicated reader's transport failed; reconnect within the budget.
|
|
70
|
+
this.drop(reader);
|
|
71
|
+
throw new JournalReadInterruptedError(verb, 1, performance.now() - started, undefined, { cause: error });
|
|
72
|
+
}
|
|
73
|
+
}, combined);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The primary session's own `hello`, retried within the same budget as reads. */
|
|
77
|
+
handshake(params: VerbContract['hello']['params'], timeoutMs: number): Promise<VerbContract['hello']['result']> {
|
|
78
|
+
return this.policy.read('hello', timeoutMs, this.budgetMs,
|
|
79
|
+
(bound, attemptSignal) => this.primary.requestOnce('hello', params, bound, attemptSignal).catch(error => {
|
|
80
|
+
if (attemptSignal.aborted || error instanceof JournalRequestTimeoutError
|
|
81
|
+
|| error instanceof JournalProtocolError || error instanceof JournalFrameError) throw error;
|
|
82
|
+
// The primary's own transport failed (ECONNRESET, ...): a read
|
|
83
|
+
// interruption, not a body failure, and not retryable on this session.
|
|
84
|
+
throw new JournalReadInterruptedError('hello', 1, 0, undefined, { cause: error, retryable: false });
|
|
85
|
+
}), this.lifecycle);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private session(): Promise<JournalClient | undefined> {
|
|
89
|
+
if (this.readerReady === undefined) {
|
|
90
|
+
const reader = this.primary.createPeer();
|
|
91
|
+
this.reader = reader;
|
|
92
|
+
this.readerReady = reader.connect().then(() => reader.hello('flows-reader')).then(() => reader)
|
|
93
|
+
.catch(error => {
|
|
94
|
+
reader.close();
|
|
95
|
+
// A daemon that refuses the session will keep refusing it: read on the primary.
|
|
96
|
+
if (error instanceof JournalProtocolError) return undefined;
|
|
97
|
+
// A connect or hello that failed under load is retried with a fresh session,
|
|
98
|
+
// never by queueing reads behind the primary's unbounded commands for good.
|
|
99
|
+
if (this.reader === reader) {
|
|
100
|
+
this.reader = undefined;
|
|
101
|
+
this.readerReady = undefined;
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return this.readerReady;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Settle with the session, or at once with the attempt's abort — abandoning a
|
|
111
|
+
* setup still in progress, so the serialized queue never waits on it.
|
|
112
|
+
*/
|
|
113
|
+
private untilAborted<T>(setup: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
114
|
+
return new Promise<T>((resolve, reject) => {
|
|
115
|
+
const onAbort = () => {
|
|
116
|
+
const pending = this.reader;
|
|
117
|
+
if (pending !== undefined) this.drop(pending);
|
|
118
|
+
reject(signal.reason);
|
|
119
|
+
};
|
|
120
|
+
if (signal.aborted) { onAbort(); return; }
|
|
121
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
122
|
+
setup.then(value => { signal.removeEventListener('abort', onAbort); resolve(value); },
|
|
123
|
+
error => { signal.removeEventListener('abort', onAbort); reject(error); });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Forget a reader whose transport failed so the next attempt reconnects one. */
|
|
128
|
+
private drop(reader: JournalClient): void {
|
|
129
|
+
if (this.reader !== reader) return;
|
|
130
|
+
this.reader = undefined;
|
|
131
|
+
this.readerReady = undefined;
|
|
132
|
+
reader.close();
|
|
133
|
+
}
|
|
134
|
+
}
|