@relayflows/sdk 2.0.42 → 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 +2 -6
- 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 +2 -6
- 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
package/src/cli/reuse.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JournalClient } from '../journal-client.js';
|
|
2
|
+
import type { RunExecution } from './run.js';
|
|
2
3
|
|
|
3
4
|
/** Count durable facts, including failed executions, across paginated reads. */
|
|
4
5
|
export async function reuseSummary(client: JournalClient, runId: string, fromRunId: string): Promise<{
|
|
@@ -21,3 +22,27 @@ export async function reuseSummary(client: JournalClient, runId: string, fromRun
|
|
|
21
22
|
}
|
|
22
23
|
return { fromRunId, reusedSteps: reused.size, executedSteps: executed.size };
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Add the reuse summary to an already classified run. The summary is an extra
|
|
28
|
+
* journal read after the outcome is known: if it cannot be answered, report
|
|
29
|
+
* that as a warning and keep the classified outcome rather than recasting a
|
|
30
|
+
* finished run as resumable or failed.
|
|
31
|
+
*/
|
|
32
|
+
export async function attachReuseSummary(execution: RunExecution, client: JournalClient,
|
|
33
|
+
runId: string, fromRunId: string): Promise<RunExecution> {
|
|
34
|
+
// Classification already ended on an unanswered read: the run is resumable and
|
|
35
|
+
// another read would only spend a fresh budget against the same delayed daemon.
|
|
36
|
+
if (execution.report.diagnostics.some(diagnostic => diagnostic.kind === 'daemon_unresponsive'
|
|
37
|
+
|| diagnostic.kind === 'daemon_unreachable' || diagnostic.kind === 'inspection_interrupted')) return execution;
|
|
38
|
+
try {
|
|
39
|
+
execution.report.reuse = await reuseSummary(client, runId, fromRunId);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
// Optional detail after the outcome is decided: no failure here — an
|
|
42
|
+
// interruption, a cancellation, a bad frame — may replace that outcome.
|
|
43
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
44
|
+
execution.report.diagnostics = [...execution.report.diagnostics, { severity: 'warning', kind: 'reuse_summary_unavailable',
|
|
45
|
+
message: `The run's outcome is final, but the summary of steps reused from ${fromRunId} could not be read: ${why}` }];
|
|
46
|
+
}
|
|
47
|
+
return execution;
|
|
48
|
+
}
|
package/src/cli/run.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { waitForRunningStep, type RunningStep } from './running-step.js';
|
|
2
|
+
export { LEASE_SWEEP_GRACE_MS } from './running-step.js';
|
|
3
|
+
import { completedResultUnreadableReport, daemonUnresponsiveReport, isReadInterruption } from './journal-timeout.js';
|
|
4
|
+
import { FLOW_READ_BUDGET_MS } from '../journal-read-policy.js';
|
|
1
5
|
import { onWorkerFailure } from '../worker-lease.js';
|
|
2
6
|
import { communicationInstruction } from '../communication/spec.js';
|
|
3
7
|
import { checkCommunicationEnvironment, CommunicationEnvironmentError } from '../communication/preflight.js';
|
|
4
8
|
import { parseHumanRecipient } from '../human-to.js';
|
|
5
9
|
import { parseDigestReference } from '../bundle-transport.js';
|
|
6
10
|
import { prepareDigestRun } from './run-digest.js';
|
|
7
|
-
import {
|
|
11
|
+
import { attachReuseSummary } from './reuse.js';
|
|
8
12
|
import { resumeHelperEffect } from '../authored-helper-effect.js';
|
|
9
13
|
import { AuthoredFlowExecutionError, AuthoredHumanParked, type AuthoredHumanWait } from '../authored-flow-error.js';
|
|
10
14
|
import { answerCommand, resumeCommand } from '../authored-human.js';
|
|
@@ -149,7 +153,7 @@ export interface RunLifecycleOptions {
|
|
|
149
153
|
onJournalEntry?: (entry: JournalEvent) => void;
|
|
150
154
|
/** An authored root was admitted: its id is known before its body runs. */
|
|
151
155
|
onRunStarted?: (run: { runId: string; flow: string; resumed?: boolean }) => void;
|
|
152
|
-
/**
|
|
156
|
+
/** The daemon admitted a root (declarative or authored, even an already completed one). */
|
|
153
157
|
onRunReceipt?: (run: { runId: string; flow: string }) => void;
|
|
154
158
|
localAgent?: boolean;
|
|
155
159
|
/** `--agent-capacity`: the local workers' concurrency; the default is `DEFAULT_LOCAL_AGENT_CAPACITY`. */
|
|
@@ -203,7 +207,8 @@ async function executeCheckedFlow(
|
|
|
203
207
|
catch (error) { return { exitCode: 2, report: { ...base, diagnostics: [...base.diagnostics,
|
|
204
208
|
{ severity: 'refusal', kind: 'probe_failed', message: error instanceof Error ? error.message : 'Communication environment could not be checked.' }] } }; }
|
|
205
209
|
}
|
|
206
|
-
const client = new JournalClient(socketPath
|
|
210
|
+
const client = new JournalClient(socketPath, { readBudgetMs: FLOW_READ_BUDGET_MS,
|
|
211
|
+
...(options.signal === undefined ? {} : { readSignal: options.signal }) });
|
|
207
212
|
const connected = await connect(client, 'run', dataDir, base, options);
|
|
208
213
|
if (connected !== undefined) return connected;
|
|
209
214
|
|
|
@@ -225,6 +230,7 @@ async function executeCheckedFlow(
|
|
|
225
230
|
}
|
|
226
231
|
if (options.onJournalEntry !== undefined) client.on('entry', options.onJournalEntry);
|
|
227
232
|
const outcome = await startWatched(client, spec, options);
|
|
233
|
+
base.runId = outcome.run_id;
|
|
228
234
|
// `run.start { watch: true }` is an event stream, not the source of the
|
|
229
235
|
// root identity. A short deterministic run can finish before its first
|
|
230
236
|
// watched entry is delivered (and an older daemon may refuse `watch`), so
|
|
@@ -234,11 +240,10 @@ async function executeCheckedFlow(
|
|
|
234
240
|
// truthful "not projected" diagnostic.
|
|
235
241
|
options.onRunReceipt?.({ runId: outcome.run_id, flow: spec.name ?? 'flow' });
|
|
236
242
|
const execution = await classifyOutcome(client, 'run', outcome, base, socketPath, { ...options, dataDir });
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
return execution;
|
|
243
|
+
return options.reuseFromRunId === undefined ? execution
|
|
244
|
+
: await attachReuseSummary(execution, client, outcome.run_id, options.reuseFromRunId);
|
|
241
245
|
} catch (error) {
|
|
246
|
+
if (isReadInterruption(error)) return daemonUnresponsiveReport('run', base, socketPath, error, base.runId, options, dataDir);
|
|
242
247
|
if (error instanceof CommunicationEnvironmentError) return { exitCode: 2, report: { ...base,
|
|
243
248
|
diagnostics: [...base.diagnostics, { severity: 'refusal', kind: 'probe_failed', message: error.message }] } };
|
|
244
249
|
if (error instanceof JournalProtocolError && (
|
|
@@ -289,7 +294,8 @@ export async function resumeFlow(
|
|
|
289
294
|
const agentEnvironment = localAgentEnvironment();
|
|
290
295
|
const socketPath = socketFor(dataDir);
|
|
291
296
|
const base = emptyReport('resume');
|
|
292
|
-
const client = new JournalClient(socketPath
|
|
297
|
+
const client = new JournalClient(socketPath, { readBudgetMs: FLOW_READ_BUDGET_MS,
|
|
298
|
+
...(options.signal === undefined ? {} : { readSignal: options.signal }) });
|
|
293
299
|
const connected = await connect(client, 'resume', dataDir, base, options);
|
|
294
300
|
if (connected !== undefined) return connected;
|
|
295
301
|
|
|
@@ -379,6 +385,7 @@ export async function resumeFlow(
|
|
|
379
385
|
await client.runWatch(runId).catch(() => client.off('entry', onEntry));
|
|
380
386
|
}
|
|
381
387
|
let outcome = await client.runResume(runId, options.allowHumanInfluenced);
|
|
388
|
+
options.onRunReceipt?.({ runId, flow: 'flow' });
|
|
382
389
|
if (await resumeHelperEffect(client, runId, dataDir)) {
|
|
383
390
|
outcome = await client.runResume(runId, options.allowHumanInfluenced);
|
|
384
391
|
}
|
|
@@ -408,6 +415,14 @@ export async function resumeFlow(
|
|
|
408
415
|
if (error instanceof AuthoredFlowExecutionError && (error.code === 'step_failed' || error.code === 'gate_failed')) {
|
|
409
416
|
return authoredStepFailure('resume', base, socketPath, error, runId);
|
|
410
417
|
}
|
|
418
|
+
if (error instanceof AuthoredFlowExecutionError && error.code === 'result_unreadable') {
|
|
419
|
+
return completedResultUnreadableReport('resume', base, socketPath, error);
|
|
420
|
+
}
|
|
421
|
+
if (error instanceof AuthoredFlowExecutionError) {
|
|
422
|
+
const memoryFailure = memoryWriteFailure('resume', base, socketPath, error, runId);
|
|
423
|
+
if (memoryFailure !== undefined) return memoryFailure;
|
|
424
|
+
}
|
|
425
|
+
if (isReadInterruption(error)) return daemonUnresponsiveReport('resume', base, socketPath, error, runId, options, dataDir);
|
|
411
426
|
if (error instanceof AuthoredFlowExecutionError && error.code === 'root_lease_lost') {
|
|
412
427
|
return rootLeaseLostReport('resume', base, socketPath, error, runId);
|
|
413
428
|
}
|
|
@@ -822,6 +837,19 @@ export async function connect(
|
|
|
822
837
|
/// needs a stubbed client rather than a real run -- the integration test that
|
|
823
838
|
/// found it reproduced the bug roughly 1 time in 12.
|
|
824
839
|
export async function classifyOutcome(
|
|
840
|
+
client: JournalClient, command: RunCommand, outcome: RunOutcome,
|
|
841
|
+
base: CheckReport | RunReport, socketPath: string, options: RunLifecycleOptions,
|
|
842
|
+
): Promise<RunExecution> {
|
|
843
|
+
try { return await classifyOutcomeInner(client, command, outcome, base, socketPath, options); }
|
|
844
|
+
catch (error) {
|
|
845
|
+
// Authored child calls have no CLI socket path: let their durable root
|
|
846
|
+
// handle the interruption, rather than turn it into a child step failure.
|
|
847
|
+
if (socketPath !== '' && isReadInterruption(error)) return daemonUnresponsiveReport(command, base, socketPath, error, outcome.run_id, options);
|
|
848
|
+
throw error;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async function classifyOutcomeInner(
|
|
825
853
|
client: JournalClient,
|
|
826
854
|
command: RunCommand,
|
|
827
855
|
outcome: RunOutcome,
|
|
@@ -929,6 +957,7 @@ export async function classifyOutcome(
|
|
|
929
957
|
kind: current.completion_reason,
|
|
930
958
|
message: `Run "${current.run_id}" failed with completionReason: ${current.completion_reason}.`,
|
|
931
959
|
};
|
|
960
|
+
let interrupted: RunDiagnostic | undefined;
|
|
932
961
|
if (current.completion_reason === 'step_failed') {
|
|
933
962
|
let details: StepFailedDetails | undefined;
|
|
934
963
|
try {
|
|
@@ -936,6 +965,10 @@ export async function classifyOutcome(
|
|
|
936
965
|
} catch (error) {
|
|
937
966
|
// Inspection must not erase the already known run failure.
|
|
938
967
|
diagnostic.message += ` Could not inspect the failed step: ${errorMessage(error)}`;
|
|
968
|
+
// Mark an unanswered read so optional follow-up reads do not spend a
|
|
969
|
+
// fresh budget on the same delayed daemon.
|
|
970
|
+
if (isReadInterruption(error)) interrupted = { severity: 'warning', kind: 'inspection_interrupted',
|
|
971
|
+
message: `The failed step could not be inspected: ${errorMessage(error)}` };
|
|
939
972
|
}
|
|
940
973
|
if (details !== undefined) {
|
|
941
974
|
Object.assign(diagnostic, details);
|
|
@@ -952,7 +985,7 @@ export async function classifyOutcome(
|
|
|
952
985
|
exitCode: 1,
|
|
953
986
|
report: {
|
|
954
987
|
...report,
|
|
955
|
-
diagnostics: [...report.diagnostics, diagnostic],
|
|
988
|
+
diagnostics: [...report.diagnostics, diagnostic, ...(interrupted === undefined ? [] : [interrupted])],
|
|
956
989
|
},
|
|
957
990
|
};
|
|
958
991
|
}
|
|
@@ -1021,10 +1054,6 @@ interface OutOfBandInspection {
|
|
|
1021
1054
|
backoffStep?: ParkedStep;
|
|
1022
1055
|
}
|
|
1023
1056
|
|
|
1024
|
-
interface RunningStep extends ParkedStep {
|
|
1025
|
-
leaseDeadlineMs: number;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
1057
|
// Bound on re-polling a run that reports `running` with no identifiable step.
|
|
1029
1058
|
// 40 x 50ms = 2s, far longer than the sub-second window observed in #179, and
|
|
1030
1059
|
// short enough that a genuinely stuck run still reports rather than hangs.
|
|
@@ -1071,62 +1100,41 @@ async function inspectOutOfBandStep(
|
|
|
1071
1100
|
};
|
|
1072
1101
|
}
|
|
1073
1102
|
|
|
1074
|
-
// Match kernel/relayflowd/src/server/client.rs: allow the lease sweep to dispatch a retry.
|
|
1075
|
-
export const LEASE_SWEEP_GRACE_MS = 5_000;
|
|
1076
|
-
// How often to re-ask the daemon about a step whose lease looks expired here.
|
|
1077
1103
|
const EXPIRED_LEASE_POLL_MS = 250;
|
|
1078
|
-
// A deadline unchanged for a whole lease (relayflowd LEASE_RENEWAL_MS, 30s)
|
|
1079
|
-
// plus the sweep grace was renewed by nobody: the attempt is dead and the
|
|
1080
|
-
// daemon never swept it. Fail rather than hang.
|
|
1081
|
-
const STALE_LEASE_MS = 30_000 + LEASE_SWEEP_GRACE_MS;
|
|
1082
1104
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
const snapshot = await client.runGet(runId);
|
|
1117
|
-
const step = snapshot.steps[runningStep.id];
|
|
1118
|
-
if (step?.state !== 'running') return;
|
|
1119
|
-
if (step.lease_deadline_ms !== undefined && step.lease_deadline_ms !== leaseDeadlineMs) {
|
|
1120
|
-
leaseDeadlineMs = step.lease_deadline_ms;
|
|
1121
|
-
deadlineSeenAt = performance.now();
|
|
1122
|
-
options.onWait?.({
|
|
1123
|
-
runId,
|
|
1124
|
-
stepId: runningStep.id,
|
|
1125
|
-
stepType: runningStep.type,
|
|
1126
|
-
leaseDeadlineMs,
|
|
1127
|
-
});
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1105
|
+
/**
|
|
1106
|
+
* `f.memory` failures keep their own kind instead of collapsing into
|
|
1107
|
+
* `protocol_error`. By then the root has been terminalized as failed, so the
|
|
1108
|
+
* report names it the way a step failure does.
|
|
1109
|
+
*/
|
|
1110
|
+
export function memoryWriteFailure(
|
|
1111
|
+
command: RunCommand,
|
|
1112
|
+
base: CheckReport | RunReport,
|
|
1113
|
+
socketPath: string,
|
|
1114
|
+
error: AuthoredFlowExecutionError,
|
|
1115
|
+
fallbackRunId?: string,
|
|
1116
|
+
): RunExecution | undefined {
|
|
1117
|
+
if (error.code !== 'memory_finding_invalid' && error.code !== 'memory_unwritable'
|
|
1118
|
+
&& error.code !== 'memory_unjournaled' && error.code !== 'memory_unreachable') return undefined;
|
|
1119
|
+
const rootRunId = error.rootRunId ?? fallbackRunId;
|
|
1120
|
+
const runId = error.runId ?? rootRunId;
|
|
1121
|
+
return {
|
|
1122
|
+
exitCode: 1,
|
|
1123
|
+
report: {
|
|
1124
|
+
...fromBase(command, base),
|
|
1125
|
+
ok: false,
|
|
1126
|
+
...(runId === undefined ? {} : { runId }),
|
|
1127
|
+
...(rootRunId === undefined ? {} : { rootRunId }),
|
|
1128
|
+
socketPath,
|
|
1129
|
+
status: 'failed',
|
|
1130
|
+
completionReason: 'step_failed',
|
|
1131
|
+
diagnostics: [...base.diagnostics, {
|
|
1132
|
+
severity: 'failure', kind: error.code,
|
|
1133
|
+
// The `<code>: ` prefix `AuthoredFlowExecutionError` adds is redundant once the diagnostic is labelled.
|
|
1134
|
+
message: error.message.startsWith(`${error.code}: `) ? error.message.slice(error.code.length + 2) : error.message,
|
|
1135
|
+
}],
|
|
1136
|
+
},
|
|
1137
|
+
};
|
|
1130
1138
|
}
|
|
1131
1139
|
|
|
1132
1140
|
export function protocolFailure(
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { JournalFrameError, JournalProtocolError, type JournalClient } from '../journal-client.js';
|
|
2
|
+
import { AuthoredFlowExecutionError } from '../authored-flow-error.js';
|
|
3
|
+
import type { JournalEvent } from '../journal-reader.js';
|
|
4
|
+
import type { RunLifecycleOptions, ParkedStep } from './run.js';
|
|
5
|
+
|
|
6
|
+
export const LEASE_SWEEP_GRACE_MS = 5_000;
|
|
7
|
+
const LEASE_POLL_MS = 2_000;
|
|
8
|
+
const STALE_LEASE_MS = 30_000 + LEASE_SWEEP_GRACE_MS;
|
|
9
|
+
export interface RunningStep extends ParkedStep { leaseDeadlineMs: number }
|
|
10
|
+
|
|
11
|
+
/** Watch completion; snapshots only refresh the non-journaled live lease deadline. */
|
|
12
|
+
export async function waitForRunningStep(client: JournalClient, runId: string,
|
|
13
|
+
runningStep: RunningStep, options: RunLifecycleOptions): Promise<void> {
|
|
14
|
+
let leaseDeadlineMs = runningStep.leaseDeadlineMs;
|
|
15
|
+
if (!Number.isFinite(leaseDeadlineMs)) {
|
|
16
|
+
throw new Error(`running step "${runningStep.id}" omitted lease_deadline_ms`);
|
|
17
|
+
}
|
|
18
|
+
const notify = () => options.onWait?.({ runId, stepId: runningStep.id,
|
|
19
|
+
stepType: runningStep.type, leaseDeadlineMs });
|
|
20
|
+
notify();
|
|
21
|
+
let deadlineSeenAt = performance.now();
|
|
22
|
+
// The protocol has no unwatch. Closing this scoped, non-worker session removes
|
|
23
|
+
// the server watcher, even on cancellation or a read/registration failure.
|
|
24
|
+
const watch = client.createPeer();
|
|
25
|
+
let settled = false;
|
|
26
|
+
let ready = false;
|
|
27
|
+
let finish: () => void = () => {};
|
|
28
|
+
let cancel: () => void = () => {};
|
|
29
|
+
let fail: (error: unknown) => void = () => {};
|
|
30
|
+
const canceled = () => new Error(`worker wait for step "${runningStep.id}" was canceled`);
|
|
31
|
+
const completed = new Promise<void>((resolve, reject) => {
|
|
32
|
+
finish = resolve;
|
|
33
|
+
cancel = () => reject(canceled());
|
|
34
|
+
fail = reject;
|
|
35
|
+
});
|
|
36
|
+
// The subscription handshake itself may be pending when cancellation arrives.
|
|
37
|
+
void completed.catch(() => {});
|
|
38
|
+
// A completion push aborts the lease snapshot in flight: the answer is known.
|
|
39
|
+
let snapshotRead: AbortController | undefined;
|
|
40
|
+
const onEntry = (entry: JournalEvent) => {
|
|
41
|
+
if (entry.run_id !== runId) return;
|
|
42
|
+
if (entry.entry_type === 'run.completed') settled = true;
|
|
43
|
+
else if (entry.step_id === runningStep.id) {
|
|
44
|
+
if (entry.entry_type === 'step.attempt.started') settled = false;
|
|
45
|
+
if (entry.entry_type === 'step.completed' || entry.entry_type === 'wait.human') settled = true;
|
|
46
|
+
}
|
|
47
|
+
// Fold the entire replay before deciding: an older attempt may have failed.
|
|
48
|
+
if (ready && settled) {
|
|
49
|
+
finish();
|
|
50
|
+
snapshotRead?.abort(new Error(`step "${runningStep.id}" completed during the lease snapshot`));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
watch.on('entry', onEntry);
|
|
54
|
+
// A malformed frame after registration has no pending request to reject: fail closed here.
|
|
55
|
+
let protocolFailure: unknown;
|
|
56
|
+
const onProtocolError = (error: unknown) => { protocolFailure ??= error; fail(error); snapshotRead?.abort(error); };
|
|
57
|
+
watch.on('protocol_error', onProtocolError);
|
|
58
|
+
// A dropped watch loses its completion pushes: a resumable read interruption.
|
|
59
|
+
const onDisconnected = (error: Error) => {
|
|
60
|
+
if (settled) return;
|
|
61
|
+
const lost = new AuthoredFlowExecutionError('daemon_unresponsive',
|
|
62
|
+
`the completion watch for step "${runningStep.id}" disconnected: ${error.message}`);
|
|
63
|
+
protocolFailure ??= lost;
|
|
64
|
+
fail(lost);
|
|
65
|
+
snapshotRead?.abort(lost);
|
|
66
|
+
};
|
|
67
|
+
watch.on('disconnected', onDisconnected);
|
|
68
|
+
options.signal?.addEventListener('abort', cancel, { once: true });
|
|
69
|
+
if (options.signal?.aborted) cancel();
|
|
70
|
+
try {
|
|
71
|
+
// Every transport failure up to and including the watch registration is a
|
|
72
|
+
// read interruption: the step may still be running and the run resumable.
|
|
73
|
+
let unconfirmed: unknown;
|
|
74
|
+
await Promise.race([watch.connect().then(() => watch.hello('flows-step-watch'))
|
|
75
|
+
.then(() => watch.runWatch(runId)).catch(error => {
|
|
76
|
+
// The replay precedes the response, but a drop may have truncated it
|
|
77
|
+
// (an older completion, the retry not yet seen): confirm below.
|
|
78
|
+
if (settled) { unconfirmed = error; return; }
|
|
79
|
+
// Refusals and malformed frames are protocol facts, not lost transport: fail closed.
|
|
80
|
+
if (error instanceof JournalProtocolError || error instanceof JournalFrameError) throw error;
|
|
81
|
+
throw new AuthoredFlowExecutionError('daemon_unresponsive',
|
|
82
|
+
`could not establish the completion watch: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
}), completed]);
|
|
84
|
+
if (unconfirmed !== undefined) {
|
|
85
|
+
const snapshot = await client.runGet(runId, options.signal === undefined ? {} : { signal: options.signal });
|
|
86
|
+
if (snapshot.steps[runningStep.id]?.state !== 'running') return;
|
|
87
|
+
throw new AuthoredFlowExecutionError('daemon_unresponsive',
|
|
88
|
+
`could not establish the completion watch: ${unconfirmed instanceof Error ? unconfirmed.message : String(unconfirmed)}`);
|
|
89
|
+
}
|
|
90
|
+
ready = true;
|
|
91
|
+
if (settled) return;
|
|
92
|
+
while (true) {
|
|
93
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
94
|
+
let releaseTimer: () => void = () => {};
|
|
95
|
+
try {
|
|
96
|
+
const elapsed = await Promise.race([
|
|
97
|
+
new Promise<boolean>(resolve => { releaseTimer = () => resolve(false); timer = setTimeout(() => resolve(true), LEASE_POLL_MS); }),
|
|
98
|
+
completed.then(() => false),
|
|
99
|
+
]);
|
|
100
|
+
if (!elapsed) return;
|
|
101
|
+
} finally { clearTimeout(timer); releaseTimer(); }
|
|
102
|
+
// Drain the read before leaving the authored promise scope. A raced but
|
|
103
|
+
// unresolved read would look like unawaited derived work to that scope,
|
|
104
|
+
// so cancellation aborts the read itself and it settles at once.
|
|
105
|
+
const read = snapshotRead = new AbortController();
|
|
106
|
+
const signal = options.signal === undefined ? read.signal
|
|
107
|
+
: AbortSignal.any([options.signal, read.signal]);
|
|
108
|
+
const snapshot = await client.runGet(runId, { signal })
|
|
109
|
+
.catch(error => {
|
|
110
|
+
if (options.signal?.aborted) throw canceled();
|
|
111
|
+
if (protocolFailure !== undefined) throw protocolFailure;
|
|
112
|
+
// A completion push aborted this read: completion was observed, and
|
|
113
|
+
// finish() is one-shot, even if a retry attempt started right after.
|
|
114
|
+
if (settled || read.signal.aborted) return undefined;
|
|
115
|
+
throw error;
|
|
116
|
+
})
|
|
117
|
+
.finally(() => { snapshotRead = undefined; });
|
|
118
|
+
if (options.signal?.aborted) throw canceled();
|
|
119
|
+
if (protocolFailure !== undefined) throw protocolFailure;
|
|
120
|
+
if (settled || snapshot === undefined || snapshot.steps[runningStep.id]?.state !== 'running') return;
|
|
121
|
+
const deadline = snapshot.steps[runningStep.id]?.lease_deadline_ms;
|
|
122
|
+
if (deadline !== undefined && deadline !== leaseDeadlineMs) {
|
|
123
|
+
leaseDeadlineMs = deadline;
|
|
124
|
+
deadlineSeenAt = performance.now();
|
|
125
|
+
notify();
|
|
126
|
+
} else if (performance.now() - deadlineSeenAt > STALE_LEASE_MS) {
|
|
127
|
+
// A stalled sweep/renewal is not evidence that the body itself failed.
|
|
128
|
+
throw new AuthoredFlowExecutionError('daemon_unresponsive',
|
|
129
|
+
`worker lease for step "${runningStep.id}" expired at ${leaseDeadlineMs} without completion; relayflowd's lease sweep may be delayed by CPU load`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
finish();
|
|
134
|
+
options.signal?.removeEventListener('abort', cancel);
|
|
135
|
+
watch.off('entry', onEntry);
|
|
136
|
+
watch.off('protocol_error', onProtocolError);
|
|
137
|
+
watch.off('disconnected', onDisconnected);
|
|
138
|
+
watch.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/cli-adapter.ts
CHANGED
|
@@ -84,8 +84,14 @@ export function agentExecution(
|
|
|
84
84
|
kind: CliAdapterKind,
|
|
85
85
|
instruction: string,
|
|
86
86
|
model?: string,
|
|
87
|
+
resume?: string,
|
|
87
88
|
): CliInvocation {
|
|
88
|
-
return registeredAdapters()[kind].buildAgentInvocation(instruction, model);
|
|
89
|
+
return registeredAdapters()[kind].buildAgentInvocation(instruction, model, resume);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Whether this CLI can continue a recorded session (`f.agent` `resume`). */
|
|
93
|
+
export function agentResumable(kind: CliAdapterKind): boolean {
|
|
94
|
+
return registeredAdapters()[kind].resumable === true;
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
/** Reuse the workspace-free model probe's provider flags for a real LLM call. */
|
package/src/cli-commands.ts
CHANGED
|
@@ -88,7 +88,8 @@ const NO_CONNECT_OPTION: CliOptionSpec = {
|
|
|
88
88
|
const LOCAL_EXECUTION_OPTIONS = [
|
|
89
89
|
JSON_OPTION,
|
|
90
90
|
DATA_DIR_OPTION,
|
|
91
|
-
{ flags: '--local-agent', description: 'Run agent steps in this process
|
|
91
|
+
{ flags: '--local-agent', description: 'Run agent steps in this process (bound to this terminal unless --detach)' },
|
|
92
|
+
{ flags: '--detach', description: 'Start in a separate process, print the run handle and exit; follow with flows status' },
|
|
92
93
|
{
|
|
93
94
|
flags: '--agent-capacity <n>',
|
|
94
95
|
description: `With --local-agent, how many agent steps (and, separately, LLM steps) run at once (1-${MAX_LOCAL_AGENT_CAPACITY})`,
|
|
@@ -117,20 +118,22 @@ export const CLI_VERBS = [
|
|
|
117
118
|
},
|
|
118
119
|
{
|
|
119
120
|
name: 'answer',
|
|
120
|
-
description: 'Answer a
|
|
121
|
+
description: 'Answer a parked f.human; --cloud also resumes, local answers need flows resume',
|
|
121
122
|
args: [
|
|
122
123
|
{ name: 'run-id', description: 'Run parked on the question', required: true },
|
|
123
|
-
{ name: 'wait-id', description: '
|
|
124
|
-
{ name: 'answer', description: '
|
|
124
|
+
{ name: 'wait-id-or-answer', description: 'Local: human-<n> wait id; Cloud: yes or no', required: true },
|
|
125
|
+
{ name: 'answer', description: 'Local decision, as yes or no (also true or false)', required: false },
|
|
125
126
|
],
|
|
126
127
|
options: [
|
|
127
128
|
JSON_OPTION,
|
|
129
|
+
{ flags: '--cloud', description: 'Answer and resume a Cloud run using its open wait' },
|
|
130
|
+
{ flags: '--source <path>', description: 'Original Cloud flow source when the stored copy is unavailable or truncated' },
|
|
128
131
|
DATA_DIR_OPTION,
|
|
129
132
|
{ flags: '--no-spawn', description: 'Require a running relayflowd rather than starting one' },
|
|
130
133
|
{ flags: '--note <text>', description: 'Reason recorded on the journal alongside the answer' },
|
|
131
134
|
{ flags: '--by <identity>', description: 'Who answered, when relaying a person’s decision; defaults to the OS user' },
|
|
132
135
|
],
|
|
133
|
-
variants: ['answer'],
|
|
136
|
+
variants: ['answer', 'cloud-answer'],
|
|
134
137
|
},
|
|
135
138
|
{
|
|
136
139
|
name: 'build',
|
|
@@ -145,9 +148,10 @@ export const CLI_VERBS = [
|
|
|
145
148
|
},
|
|
146
149
|
{
|
|
147
150
|
name: 'check',
|
|
148
|
-
description: 'Compile and
|
|
151
|
+
description: 'Compile, preflight, and validate with the installed runtime when available; never start a run',
|
|
149
152
|
args: [{ name: 'source', description: 'flow.ts, flow.yaml, or spec.json', required: true }],
|
|
150
|
-
options: [JSON_OPTION, { flags: '--watch', description: 'Re-check on every change to the flow and its imports' }
|
|
153
|
+
options: [{ flags: '--against-daemon', description: 'Require acceptance by the installed relayflowd validator (FLOWS_CHECK_AGAINST_DAEMON=1)' }, { flags: '--no-daemon-check', description: 'Local compile and preflight only (FLOWS_NO_DAEMON_CHECK=1)' }, JSON_OPTION, { flags: '--watch', description: 'Re-check on every change to the flow and its imports' },
|
|
154
|
+
{ flags: '--explain-warnings', description: 'List every unprovable-effects warning per step instead of one summary line' }],
|
|
151
155
|
variants: ['check'],
|
|
152
156
|
},
|
|
153
157
|
{
|
|
@@ -192,6 +196,23 @@ export const CLI_VERBS = [
|
|
|
192
196
|
options: [JSON_OPTION],
|
|
193
197
|
variants: ['deployments'],
|
|
194
198
|
},
|
|
199
|
+
{
|
|
200
|
+
name: 'eval',
|
|
201
|
+
description: 'Re-execute a flow against a frozen suite of input cases and gate on the score report',
|
|
202
|
+
args: [{ name: 'flow', description: 'flow.ts, flow.yaml, or spec.json: the exact version to evaluate', required: true }],
|
|
203
|
+
options: [
|
|
204
|
+
{ flags: '--cases <suite.json>', description: 'Required. Frozen suite: { name, cases: [{ id, input?, expect? }], thresholds? }' },
|
|
205
|
+
JSON_OPTION,
|
|
206
|
+
{ flags: '--cloud', description: 'Execute each case as a hosted Cloud run instead of a local run' },
|
|
207
|
+
DATA_DIR_OPTION,
|
|
208
|
+
{ flags: '--local-agent', description: 'Attach a local agent worker to each local run' },
|
|
209
|
+
{ flags: '--concurrency <n>', description: 'Cases executed at once', defaultValue: '1' },
|
|
210
|
+
{ flags: '--report <file>', description: 'Also write the JSON report to this file' },
|
|
211
|
+
{ flags: '--baseline <report.json>', description: 'Fail the gate on any case that passed in this prior report' },
|
|
212
|
+
{ flags: '--expect-version <sha256:hex>', description: 'Refuse unless the flow version (its local source manifest) hashes to exactly this' },
|
|
213
|
+
],
|
|
214
|
+
variants: ['eval'],
|
|
215
|
+
},
|
|
195
216
|
{
|
|
196
217
|
name: 'hn-monitor',
|
|
197
218
|
description: 'Hacker News monitor: poll for matching stories and launch a flow per hit',
|
|
@@ -427,3 +448,18 @@ export type _EveryDeclaredVariantExists = AssertNever<Exclude<DeclaredVariant, P
|
|
|
427
448
|
* from drifting apart.
|
|
428
449
|
*/
|
|
429
450
|
export const CLI_VERB_NAMES: ReadonlySet<string> = new Set(CLI_VERBS.map((verb) => verb.name));
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Flags `runCli` handles before verb parsing, each only as the sole argument.
|
|
454
|
+
* The CLI reference renders this list, so it is the only place they are spelled.
|
|
455
|
+
*/
|
|
456
|
+
export const CLI_GLOBAL_OPTIONS = [
|
|
457
|
+
{ flags: '-h, --help', description: 'Print usage and exit.' },
|
|
458
|
+
{ flags: '-V, --version', description: 'Print the SDK version and exit.' },
|
|
459
|
+
] as const satisfies readonly CliOptionSpec[];
|
|
460
|
+
|
|
461
|
+
/** Whether `args` is exactly one spelling of the global option whose long form is `long`. */
|
|
462
|
+
export function isGlobalOption(args: readonly string[], long: '--help' | '--version'): boolean {
|
|
463
|
+
const option = CLI_GLOBAL_OPTIONS.find((candidate) => candidate.flags.split(', ').includes(long));
|
|
464
|
+
return args.length === 1 && option !== undefined && option.flags.split(', ').includes(args[0]!);
|
|
465
|
+
}
|
package/src/cli-watch.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DaemonValidationMode } from './daemon-spec-validation.js';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
import { accessSync, constants, existsSync, readFileSync, watch, type FSWatcher } from 'node:fs';
|
|
3
4
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
@@ -11,17 +12,18 @@ type ExitCode = 0 | 1 | 2 | 3;
|
|
|
11
12
|
/** A runner around the ordinary CLI, with a fresh module cache for every check. */
|
|
12
13
|
export async function watchCheck(
|
|
13
14
|
path: string,
|
|
14
|
-
json: boolean,
|
|
15
|
+
options: { json: boolean; explainWarnings: boolean },
|
|
15
16
|
io: CliIo,
|
|
16
17
|
/** Cancellation, owned by the caller; this function installs no signal handler. */
|
|
17
18
|
signal: AbortSignal,
|
|
19
|
+
daemonValidation: DaemonValidationMode = 'auto',
|
|
18
20
|
): Promise<ExitCode> {
|
|
19
21
|
try {
|
|
20
22
|
return await watchChecks({
|
|
21
23
|
path,
|
|
22
24
|
signal,
|
|
23
|
-
check: () => checkOnce(path,
|
|
24
|
-
clear: () => { if (!json) io.stdout('\x1b[2J\x1b[H'); },
|
|
25
|
+
check: () => checkOnce(path, { ...options, daemonValidation }, io),
|
|
26
|
+
clear: () => { if (!options.json) io.stdout('\x1b[2J\x1b[H'); },
|
|
25
27
|
});
|
|
26
28
|
} catch (error) {
|
|
27
29
|
io.stderr(`REFUSED [input_unreadable] Could not watch "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -140,13 +142,14 @@ function discoverWatchPaths(path: string, imports: Map<string, string[]>): Set<s
|
|
|
140
142
|
return paths;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
function checkOnce(path: string, json: boolean, io: CliIo): Promise<ExitCode> {
|
|
145
|
+
function checkOnce(path: string, options: { json: boolean; explainWarnings: boolean; daemonValidation: DaemonValidationMode }, io: CliIo): Promise<ExitCode> {
|
|
146
|
+
const mode = options.daemonValidation;
|
|
144
147
|
// Importing an authored TS file in this process again would return its stale
|
|
145
148
|
// cached definition (including transitive imports). A fresh CLI process uses
|
|
146
149
|
// precisely the same check pipeline and reporting as a one-shot invocation.
|
|
147
150
|
const entry = fileURLToPath(new URL(import.meta.url.endsWith('.ts') ? './cli.ts' : './cli.js', import.meta.url));
|
|
148
151
|
return new Promise((resolveCode, reject) => {
|
|
149
|
-
const child = spawn(process.execPath, [...process.execArgv, entry, 'check', ...(json ? ['--json'] : []), path], {
|
|
152
|
+
const child = spawn(process.execPath, [...process.execArgv, entry, 'check', ...(mode === 'required' ? ['--against-daemon'] : mode === 'off' ? ['--no-daemon-check'] : []), ...(options.json ? ['--json'] : []), ...(options.explainWarnings ? ['--explain-warnings'] : []), path], {
|
|
150
153
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
151
154
|
// Ctrl-C belongs to the watcher; let an active check finish so its report
|
|
152
155
|
// and exit status remain a pair, then release all resources.
|