@relayflows/sdk 2.0.29 → 2.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-artifacts.d.ts.map +1 -1
- package/dist/agent-artifacts.js +2 -9
- package/dist/agent-artifacts.js.map +1 -1
- package/dist/agent-cwd.d.ts +69 -0
- package/dist/agent-cwd.d.ts.map +1 -0
- package/dist/agent-cwd.js +140 -0
- package/dist/agent-cwd.js.map +1 -0
- package/dist/artifact-scan-policy.d.ts +35 -0
- package/dist/artifact-scan-policy.d.ts.map +1 -0
- package/dist/artifact-scan-policy.js +49 -0
- package/dist/artifact-scan-policy.js.map +1 -0
- package/dist/authored-completion-record.d.ts +35 -0
- package/dist/authored-completion-record.d.ts.map +1 -0
- package/dist/authored-completion-record.js +92 -0
- package/dist/authored-completion-record.js.map +1 -0
- package/dist/authored-completion.d.ts +99 -0
- package/dist/authored-completion.d.ts.map +1 -0
- package/dist/authored-completion.js +184 -0
- package/dist/authored-completion.js.map +1 -0
- package/dist/authored-flow-error.d.ts +13 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +12 -0
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts +7 -30
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +64 -48
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-human.d.ts.map +1 -1
- package/dist/authored-human.js +1 -3
- package/dist/authored-human.js.map +1 -1
- package/dist/authored-node-entry.js +4 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts +9 -1
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +92 -17
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root.d.ts +5 -0
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +20 -2
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-step-output.d.ts +35 -10
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +108 -40
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-verdict.d.ts +28 -0
- package/dist/authored-verdict.d.ts.map +1 -0
- package/dist/authored-verdict.js +79 -0
- package/dist/authored-verdict.js.map +1 -0
- package/dist/authored-worker-step.d.ts +5 -2
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +48 -11
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/bundle-extensions.d.ts.map +1 -1
- package/dist/bundle-extensions.js +4 -7
- package/dist/bundle-extensions.js.map +1 -1
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +41 -7
- package/dist/bundle.js.map +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +18 -6
- package/dist/canonical.js.map +1 -1
- package/dist/cli/check-worker-surface.d.ts +27 -0
- package/dist/cli/check-worker-surface.d.ts.map +1 -0
- package/dist/cli/check-worker-surface.js +63 -0
- package/dist/cli/check-worker-surface.js.map +1 -0
- package/dist/cli/check.d.ts +19 -2
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +12 -4
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-format.d.ts +9 -0
- package/dist/cli/cloud-format.d.ts.map +1 -0
- package/dist/cli/cloud-format.js +77 -0
- package/dist/cli/cloud-format.js.map +1 -0
- package/dist/cli/cloud-live.d.ts +39 -0
- package/dist/cli/cloud-live.d.ts.map +1 -0
- package/dist/cli/cloud-live.js +325 -0
- package/dist/cli/cloud-live.js.map +1 -0
- package/dist/cli/cloud-read.d.ts +3 -7
- package/dist/cli/cloud-read.d.ts.map +1 -1
- package/dist/cli/cloud-read.js +14 -266
- package/dist/cli/cloud-read.js.map +1 -1
- package/dist/cli/cloud-refusal.d.ts +39 -0
- package/dist/cli/cloud-refusal.d.ts.map +1 -0
- package/dist/cli/cloud-refusal.js +95 -0
- package/dist/cli/cloud-refusal.js.map +1 -0
- package/dist/cli/cloud-status-view.d.ts +7 -0
- package/dist/cli/cloud-status-view.d.ts.map +1 -0
- package/dist/cli/cloud-status-view.js +214 -0
- package/dist/cli/cloud-status-view.js.map +1 -0
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +16 -2
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/hn-monitor.d.ts +0 -13
- package/dist/cli/hn-monitor.d.ts.map +1 -1
- package/dist/cli/hn-monitor.js +2 -1
- package/dist/cli/hn-monitor.js.map +1 -1
- package/dist/cli/local-agent-remedy.d.ts +80 -0
- package/dist/cli/local-agent-remedy.d.ts.map +1 -0
- package/dist/cli/local-agent-remedy.js +116 -0
- package/dist/cli/local-agent-remedy.js.map +1 -0
- package/dist/cli/observer-session.d.ts +41 -0
- package/dist/cli/observer-session.d.ts.map +1 -0
- package/dist/cli/observer-session.js +78 -0
- package/dist/cli/observer-session.js.map +1 -0
- package/dist/cli/run.d.ts +30 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +186 -24
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +9 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +31 -2
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/step-evidence.d.ts +90 -0
- package/dist/cli/step-evidence.d.ts.map +1 -0
- package/dist/cli/step-evidence.js +259 -0
- package/dist/cli/step-evidence.js.map +1 -0
- package/dist/cli/step-failure.d.ts +14 -4
- package/dist/cli/step-failure.d.ts.map +1 -1
- package/dist/cli/step-failure.js +41 -76
- package/dist/cli/step-failure.js.map +1 -1
- package/dist/cli-commands.d.ts +6 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +2 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -42
- package/dist/cli.js.map +1 -1
- package/dist/cloud-read.d.ts +15 -0
- package/dist/cloud-read.d.ts.map +1 -1
- package/dist/cloud-read.js +20 -1
- package/dist/cloud-read.js.map +1 -1
- package/dist/cloud-run-record.d.ts +27 -0
- package/dist/cloud-run-record.d.ts.map +1 -0
- package/dist/cloud-run-record.js +56 -0
- package/dist/cloud-run-record.js.map +1 -0
- package/dist/cloud-run.d.ts +2 -9
- package/dist/cloud-run.d.ts.map +1 -1
- package/dist/cloud-run.js +3 -20
- package/dist/cloud-run.js.map +1 -1
- package/dist/communication/local.d.ts.map +1 -1
- package/dist/communication/local.js +2 -1
- package/dist/communication/local.js.map +1 -1
- package/dist/communication/worker.d.ts +1 -1
- package/dist/communication/worker.d.ts.map +1 -1
- package/dist/communication/worker.js +13 -5
- package/dist/communication/worker.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +28 -6
- package/dist/compile.js.map +1 -1
- package/dist/direct-input.d.ts.map +1 -1
- package/dist/direct-input.js +21 -1
- package/dist/direct-input.js.map +1 -1
- package/dist/failure-kinds.d.ts +96 -6
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +29 -2
- package/dist/failure-kinds.js.map +1 -1
- package/dist/flow-extension-compat.d.ts.map +1 -1
- package/dist/flow-extension-compat.js +26 -8
- package/dist/flow-extension-compat.js.map +1 -1
- package/dist/flow-extension-loader.d.ts +7 -0
- package/dist/flow-extension-loader.d.ts.map +1 -1
- package/dist/flow-extension-loader.js +48 -22
- package/dist/flow-extension-loader.js.map +1 -1
- package/dist/flow-requirements.d.ts.map +1 -1
- package/dist/flow-requirements.js +24 -9
- package/dist/flow-requirements.js.map +1 -1
- package/dist/fs-descriptor.d.ts +17 -0
- package/dist/fs-descriptor.d.ts.map +1 -0
- package/dist/fs-descriptor.js +96 -0
- package/dist/fs-descriptor.js.map +1 -0
- package/dist/gate-contract.d.ts +7 -0
- package/dist/gate-contract.d.ts.map +1 -1
- package/dist/gate-contract.js +6 -1
- package/dist/gate-contract.js.map +1 -1
- package/dist/helper-preflight.d.ts.map +1 -1
- package/dist/helper-preflight.js +10 -3
- package/dist/helper-preflight.js.map +1 -1
- package/dist/helper-reference.d.ts +25 -0
- package/dist/helper-reference.d.ts.map +1 -0
- package/dist/helper-reference.js +129 -0
- package/dist/helper-reference.js.map +1 -0
- package/dist/hosted-base-snapshot.d.ts +25 -0
- package/dist/hosted-base-snapshot.d.ts.map +1 -0
- package/dist/hosted-base-snapshot.js +278 -0
- package/dist/hosted-base-snapshot.js.map +1 -0
- package/dist/hosted-extension-declarations.d.ts +10 -0
- package/dist/hosted-extension-declarations.d.ts.map +1 -0
- package/dist/hosted-extension-declarations.js +272 -0
- package/dist/hosted-extension-declarations.js.map +1 -0
- package/dist/hosted-extension-isolation.d.ts +82 -0
- package/dist/hosted-extension-isolation.d.ts.map +1 -0
- package/dist/hosted-extension-isolation.js +322 -0
- package/dist/hosted-extension-isolation.js.map +1 -0
- package/dist/hosted-extension-manifest.d.ts +9 -0
- package/dist/hosted-extension-manifest.d.ts.map +1 -0
- package/dist/hosted-extension-manifest.js +235 -0
- package/dist/hosted-extension-manifest.js.map +1 -0
- package/dist/hosted-extension-protocol.d.ts +16 -0
- package/dist/hosted-extension-protocol.d.ts.map +1 -0
- package/dist/hosted-extension-protocol.js +224 -0
- package/dist/hosted-extension-protocol.js.map +1 -0
- package/dist/hosted-extension-runtime.d.ts +30 -0
- package/dist/hosted-extension-runtime.d.ts.map +1 -0
- package/dist/hosted-extension-runtime.js +167 -0
- package/dist/hosted-extension-runtime.js.map +1 -0
- package/dist/hosted-extension-sandbox-source.d.ts +9 -0
- package/dist/hosted-extension-sandbox-source.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox-source.js +95 -0
- package/dist/hosted-extension-sandbox-source.js.map +1 -0
- package/dist/hosted-extension-sandbox.d.ts +39 -0
- package/dist/hosted-extension-sandbox.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox.js +418 -0
- package/dist/hosted-extension-sandbox.js.map +1 -0
- package/dist/hosted-project.d.ts +3 -0
- package/dist/hosted-project.d.ts.map +1 -0
- package/dist/hosted-project.js +19 -0
- package/dist/hosted-project.js.map +1 -0
- package/dist/hosted-promise-safety.d.ts +12 -0
- package/dist/hosted-promise-safety.d.ts.map +1 -0
- package/dist/hosted-promise-safety.js +63 -0
- package/dist/hosted-promise-safety.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/intrinsic-array.d.ts +3 -0
- package/dist/intrinsic-array.d.ts.map +1 -0
- package/dist/intrinsic-array.js +14 -0
- package/dist/intrinsic-array.js.map +1 -0
- package/dist/journal-client.d.ts +3 -1
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +17 -2
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-projection.d.ts +16 -0
- package/dist/journal-projection.d.ts.map +1 -0
- package/dist/journal-projection.js +108 -0
- package/dist/journal-projection.js.map +1 -0
- package/dist/json-value.d.ts +6 -1
- package/dist/json-value.d.ts.map +1 -1
- package/dist/json-value.js +155 -36
- package/dist/json-value.js.map +1 -1
- package/dist/llm-worker.d.ts +9 -0
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +16 -2
- package/dist/llm-worker.js.map +1 -1
- package/dist/local-agent.d.ts +1 -1
- package/dist/local-agent.d.ts.map +1 -1
- package/dist/local-agent.js +4 -3
- package/dist/local-agent.js.map +1 -1
- package/dist/named-gate-lowering.js +35 -5
- package/dist/named-gate-lowering.js.map +1 -1
- package/dist/named-gate-preflight.d.ts +27 -0
- package/dist/named-gate-preflight.d.ts.map +1 -0
- package/dist/named-gate-preflight.js +47 -0
- package/dist/named-gate-preflight.js.map +1 -0
- package/dist/named-gates.d.ts +1 -1
- package/dist/named-gates.d.ts.map +1 -1
- package/dist/named-gates.js +3 -0
- package/dist/named-gates.js.map +1 -1
- package/dist/observer-link.d.ts +7 -0
- package/dist/observer-link.d.ts.map +1 -1
- package/dist/observer-link.js +3 -1
- package/dist/observer-link.js.map +1 -1
- package/dist/plugin-store.d.ts +8 -2
- package/dist/plugin-store.d.ts.map +1 -1
- package/dist/plugin-store.js +233 -56
- package/dist/plugin-store.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +7 -0
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +2 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/redact.d.ts +42 -0
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +128 -0
- package/dist/redact.js.map +1 -1
- package/dist/run-projection.d.ts +83 -0
- package/dist/run-projection.d.ts.map +1 -0
- package/dist/run-projection.js +199 -0
- package/dist/run-projection.js.map +1 -0
- package/dist/semver-range.d.ts.map +1 -1
- package/dist/semver-range.js +27 -15
- package/dist/semver-range.js.map +1 -1
- package/dist/shell-word.d.ts +19 -0
- package/dist/shell-word.d.ts.map +1 -0
- package/dist/shell-word.js +23 -0
- package/dist/shell-word.js.map +1 -0
- package/dist/spec.d.ts +52 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/step-fields.d.ts +1 -1
- package/dist/step-fields.js +1 -1
- package/dist/step-fields.js.map +1 -1
- package/dist/steps-green.d.ts +27 -0
- package/dist/steps-green.d.ts.map +1 -0
- package/dist/steps-green.js +164 -0
- package/dist/steps-green.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +39 -8
- package/dist/validate.js.map +1 -1
- package/dist/worker-lease.d.ts +8 -1
- package/dist/worker-lease.d.ts.map +1 -1
- package/dist/worker-lease.js +34 -4
- package/dist/worker-lease.js.map +1 -1
- package/dist/worker.d.ts +8 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +17 -9
- package/dist/worker.js.map +1 -1
- package/package.json +3 -7
- package/src/agent-artifacts.ts +2 -10
- package/src/agent-cwd.ts +139 -0
- package/src/artifact-scan-policy.ts +51 -0
- package/src/authored-completion-record.ts +119 -0
- package/src/authored-completion.ts +203 -0
- package/src/authored-flow-error.ts +13 -1
- package/src/authored-flow-executor.ts +101 -59
- package/src/authored-human.ts +1 -4
- package/src/authored-node-entry.ts +4 -1
- package/src/authored-node-runner.ts +96 -22
- package/src/authored-root.ts +20 -2
- package/src/authored-step-output.ts +139 -42
- package/src/authored-verdict.ts +87 -0
- package/src/authored-worker-step.ts +60 -12
- package/src/bundle-extensions.ts +6 -5
- package/src/bundle.ts +52 -7
- package/src/canonical.ts +20 -6
- package/src/cli/check-worker-surface.ts +66 -0
- package/src/cli/check.ts +35 -4
- package/src/cli/cloud-format.ts +82 -0
- package/src/cli/cloud-live.ts +366 -0
- package/src/cli/cloud-read.ts +13 -282
- package/src/cli/cloud-refusal.ts +113 -0
- package/src/cli/cloud-status-view.ts +213 -0
- package/src/cli/direct-run.ts +19 -2
- package/src/cli/hn-monitor.ts +2 -1
- package/src/cli/local-agent-remedy.ts +160 -0
- package/src/cli/observer-session.ts +107 -0
- package/src/cli/run.ts +233 -28
- package/src/cli/status.ts +59 -3
- package/src/cli/step-evidence.ts +310 -0
- package/src/cli/step-failure.ts +57 -81
- package/src/cli-commands.ts +2 -0
- package/src/cli.ts +64 -44
- package/src/cloud-read.ts +25 -1
- package/src/cloud-run-record.ts +64 -0
- package/src/cloud-run.ts +5 -24
- package/src/communication/local.ts +2 -1
- package/src/communication/worker.ts +13 -5
- package/src/compile.ts +28 -6
- package/src/direct-input.ts +22 -1
- package/src/failure-kinds.ts +104 -2
- package/src/flow-extension-compat.ts +32 -7
- package/src/flow-extension-loader.ts +56 -21
- package/src/flow-requirements.ts +24 -9
- package/src/fs-descriptor.ts +127 -0
- package/src/gate-contract.ts +13 -1
- package/src/helper-preflight.ts +10 -3
- package/src/helper-reference.ts +130 -0
- package/src/hosted-base-snapshot.ts +351 -0
- package/src/hosted-extension-declarations.ts +387 -0
- package/src/hosted-extension-isolation.ts +457 -0
- package/src/hosted-extension-manifest.ts +249 -0
- package/src/hosted-extension-protocol.ts +262 -0
- package/src/hosted-extension-runtime.ts +266 -0
- package/src/hosted-extension-sandbox-source.ts +94 -0
- package/src/hosted-extension-sandbox.ts +489 -0
- package/src/hosted-project.ts +18 -0
- package/src/hosted-promise-safety.ts +68 -0
- package/src/index.ts +20 -0
- package/src/intrinsic-array.ts +14 -0
- package/src/journal-client.ts +19 -2
- package/src/journal-projection.ts +106 -0
- package/src/json-value.ts +184 -32
- package/src/llm-worker.ts +16 -2
- package/src/local-agent.ts +4 -2
- package/src/named-gate-lowering.ts +35 -5
- package/src/named-gate-preflight.ts +47 -0
- package/src/named-gates.ts +3 -0
- package/src/observer-link.ts +10 -1
- package/src/plugin-store.ts +323 -56
- package/src/preflight.ts +7 -0
- package/src/protocol.ts +2 -0
- package/src/redact.ts +111 -0
- package/src/run-projection.ts +247 -0
- package/src/semver-range.ts +39 -14
- package/src/shell-word.ts +24 -0
- package/src/spec.ts +54 -3
- package/src/step-fields.ts +1 -1
- package/src/steps-green.ts +168 -0
- package/src/validate.ts +35 -8
- package/src/worker-lease.ts +35 -5
- package/src/worker.ts +27 -11
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// The `flows status --cloud` page: one run record and its step rows, rendered
|
|
2
|
+
// in the grammar `cli/status.ts` already uses for a local run.
|
|
3
|
+
//
|
|
4
|
+
// Moved out of `cli/cloud-read.ts` so the one-shot read and the watched read
|
|
5
|
+
// (cli/cloud-live.ts) draw the identical page through the identical scrubber.
|
|
6
|
+
// A watched frame that redacted differently to the page it replaces would be
|
|
7
|
+
// a leak nobody could see from either file alone.
|
|
8
|
+
//
|
|
9
|
+
// Live rows are the part this view has that the local one does not have to
|
|
10
|
+
// think about: Cloud's step snapshot is the only evidence, and where it is
|
|
11
|
+
// silent the row stays silent. An elapsed time is derived only for a step that
|
|
12
|
+
// is demonstrably running, an attempt number is printed only where the
|
|
13
|
+
// snapshot establishes one, and neither a maximum-attempt denominator nor a
|
|
14
|
+
// wait id is invented — Cloud's step rows carry neither.
|
|
15
|
+
|
|
16
|
+
import { isCloudRunActive } from '../cloud-run-record.js';
|
|
17
|
+
import { redact } from '../redact.js';
|
|
18
|
+
import type { CloudRunDetail, CloudStep } from '../cloud-read.js';
|
|
19
|
+
import { ago, dollars, errorLines, safe, thousands } from './cloud-format.js';
|
|
20
|
+
import { formatDuration } from './status.js';
|
|
21
|
+
|
|
22
|
+
const GLYPH: Record<string, string> = {
|
|
23
|
+
completed: '✓', succeeded: '✓', failed: '✗', cancelled: '✗',
|
|
24
|
+
running: '↻', backoff: '↻', waiting: '⏸', needs_human: '⏸',
|
|
25
|
+
pending: '○', queued: '○',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Step statuses that mean the step is in flight, in the local view's
|
|
30
|
+
* vocabulary (`cli/status.ts`). These are the rows with no `durationMs` and no
|
|
31
|
+
* `endTime`: their timing has to be derived, and their attempt number is the
|
|
32
|
+
* one being attempted rather than a count of the ones that finished.
|
|
33
|
+
*/
|
|
34
|
+
const ACTIVE_STEP_STATUSES: readonly string[] = ['running', 'backoff', 'waiting', 'needs_human'];
|
|
35
|
+
|
|
36
|
+
function glyph(step: CloudStep): string {
|
|
37
|
+
if (step.status === 'completed' && step.completion_reason !== null && step.completion_reason !== 'success') return '✗';
|
|
38
|
+
return GLYPH[step.status] ?? '○';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The `steps N: 3 done · 1 running` line, counted off whatever Cloud called each status. */
|
|
42
|
+
function stepCounts(steps: readonly CloudStep[]): string {
|
|
43
|
+
const counts = new Map<string, number>();
|
|
44
|
+
for (const step of steps) counts.set(step.status, (counts.get(step.status) ?? 0) + 1);
|
|
45
|
+
const parts = [...counts].map(([status, count]) => `${count} ${status}`);
|
|
46
|
+
return `steps ${steps.length}${parts.length === 0 ? '' : `: ${parts.join(' · ')}`}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* How long an in-flight step has been in flight.
|
|
51
|
+
*
|
|
52
|
+
* Only from a start timestamp the row actually carries, and only while the row
|
|
53
|
+
* says the step has not ended: a finished step whose `endTime` is missing keeps
|
|
54
|
+
* whatever duration Cloud reported rather than being advanced to now, which
|
|
55
|
+
* would make a step that ended an hour ago look like it is still burning. A
|
|
56
|
+
* start in the future is clock skew between Cloud and here, not negative time,
|
|
57
|
+
* so it clamps to zero.
|
|
58
|
+
*/
|
|
59
|
+
function liveElapsed(step: CloudStep, now: number): number | null {
|
|
60
|
+
if (!ACTIVE_STEP_STATUSES.includes(step.status) || step.ended_at !== null || step.started_at === null) return null;
|
|
61
|
+
const started = Date.parse(step.started_at);
|
|
62
|
+
return Number.isFinite(started) ? Math.max(0, now - started) : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The attempt cell.
|
|
67
|
+
*
|
|
68
|
+
* A finished step counts the attempt records Cloud kept. An in-flight step
|
|
69
|
+
* wants the number of the attempt *now running*, and `attempts` cannot supply
|
|
70
|
+
* it: those entries are completion records, so during a second attempt the
|
|
71
|
+
* array still holds one entry and would print `attempt 1`. `retryCount` plus
|
|
72
|
+
* one is that number — but only where the row also shows the step was
|
|
73
|
+
* dispatched, because a row with no start timestamp has not attempted
|
|
74
|
+
* anything and `attempt 1` would be a claim about work that has not begun.
|
|
75
|
+
* Where neither establishes it, the cell is omitted: the local view prints no
|
|
76
|
+
* attempt at zero either. No `/max` denominator — Cloud's step rows carry no
|
|
77
|
+
* maximum, and printing one would invent a budget.
|
|
78
|
+
*/
|
|
79
|
+
function attemptCell(step: CloudStep): string | null {
|
|
80
|
+
if (!ACTIVE_STEP_STATUSES.includes(step.status)) {
|
|
81
|
+
const attempts = step.attempts.length;
|
|
82
|
+
return attempts > 0 ? `${attempts} attempt${attempts === 1 ? '' : 's'}` : null;
|
|
83
|
+
}
|
|
84
|
+
if (step.retry_count === null || step.started_at === null) return null;
|
|
85
|
+
return `attempt ${Math.max(0, Math.trunc(step.retry_count)) + 1}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderStep(step: CloudStep, runId: string, idWidth: number, now: number): string[] {
|
|
89
|
+
const cells = [
|
|
90
|
+
safe(step.step_name).slice(0, 24).padEnd(idWidth),
|
|
91
|
+
step.step_type.padEnd(13),
|
|
92
|
+
step.status.padEnd(11),
|
|
93
|
+
];
|
|
94
|
+
const attempt = attemptCell(step);
|
|
95
|
+
if (attempt !== null) cells.push(attempt);
|
|
96
|
+
// The derived time first, not second: a row that is still in flight can
|
|
97
|
+
// carry a `wallclockMs` from the attempt that already ended, and preferring
|
|
98
|
+
// it would freeze the cell at that attempt's duration while the step runs
|
|
99
|
+
// on. `liveElapsed` is null for every row that is not demonstrably in
|
|
100
|
+
// flight, so a finished row still prints the duration Cloud reported.
|
|
101
|
+
const elapsed = liveElapsed(step, now) ?? step.duration_ms;
|
|
102
|
+
if (elapsed !== null) cells.push(formatDuration(elapsed));
|
|
103
|
+
if (step.completion_reason !== null) cells.push(safe(step.completion_reason));
|
|
104
|
+
if (step.gate !== null) cells.push(`gate: ${safe(step.gate.gate)} ${safe(step.gate.verdict)}`);
|
|
105
|
+
const lines = [` ${glyph(step)} ${cells.join(' ')}`.trimEnd()];
|
|
106
|
+
|
|
107
|
+
if (step.gate !== null && step.gate.verdict !== 'pass' && step.gate.detail.length > 0) {
|
|
108
|
+
lines.push(` gate: ${safe(step.gate.gate)} ${step.gate.verdict.toUpperCase()} — ${JSON.stringify(safe(step.gate.detail))}`);
|
|
109
|
+
}
|
|
110
|
+
const transcript = step.transcript;
|
|
111
|
+
if (transcript !== null) {
|
|
112
|
+
const facts = [
|
|
113
|
+
transcript.model === null ? null : safe(transcript.model),
|
|
114
|
+
transcript.num_turns === null ? null : `${transcript.num_turns} turn${transcript.num_turns === 1 ? '' : 's'}`,
|
|
115
|
+
transcript.total_calls === null ? null : `${transcript.total_calls} tool call${transcript.total_calls === 1 ? '' : 's'}`,
|
|
116
|
+
transcript.total_cost_usd === null ? null : dollars(transcript.total_cost_usd),
|
|
117
|
+
].filter((fact): fact is string => fact !== null);
|
|
118
|
+
lines.push(` transcript (attempt ${transcript.attempt ?? step.attempts.length})${facts.length === 0 ? '' : `: ${facts.join(' · ')}`}`);
|
|
119
|
+
if (transcript.frames_total !== null || transcript.bytes_total !== null) {
|
|
120
|
+
const frames = transcript.frames_total === null ? ''
|
|
121
|
+
: `${thousands(transcript.frames_kept ?? transcript.frames_total)} of ${thousands(transcript.frames_total)} frames`;
|
|
122
|
+
const bytes = transcript.bytes_total === null ? '' : `${thousands(transcript.bytes_total)} bytes`;
|
|
123
|
+
const cut = transcript.truncated ? ', truncated' : '';
|
|
124
|
+
lines.push(` ${[frames, bytes].filter((part) => part.length > 0).join(', ')}${cut}`);
|
|
125
|
+
}
|
|
126
|
+
if (transcript.tokens_in !== null || transcript.tokens_out !== null) {
|
|
127
|
+
const cache = [
|
|
128
|
+
transcript.cache_read === null ? null : `${thousands(transcript.cache_read)} cache read`,
|
|
129
|
+
transcript.cache_creation === null ? null : `${thousands(transcript.cache_creation)} cache write`,
|
|
130
|
+
].filter((part): part is string => part !== null);
|
|
131
|
+
lines.push(` ${thousands(transcript.tokens_in ?? 0)} in / ${thousands(transcript.tokens_out ?? 0)} out`
|
|
132
|
+
+ `${cache.length === 0 ? '' : ` · ${cache.join(' · ')}`}`);
|
|
133
|
+
}
|
|
134
|
+
if (transcript.tools.length > 0) {
|
|
135
|
+
lines.push(` tools: ${transcript.tools.map((tool) =>
|
|
136
|
+
`${safe(tool.name)} ×${tool.calls ?? 0}${tool.errors ? ` (${tool.errors} error${tool.errors === 1 ? '' : 's'})` : ''}`).join(', ')}`);
|
|
137
|
+
}
|
|
138
|
+
for (const call of transcript.last_calls) {
|
|
139
|
+
const excerpt = call.input_excerpt === null ? '' : ` ${safe(call.input_excerpt).slice(0, 120)}`;
|
|
140
|
+
lines.push(` call ${call.seq ?? '?'} ${safe(call.name)}${excerpt} → ${thousands(call.result_bytes ?? 0)} bytes`);
|
|
141
|
+
}
|
|
142
|
+
lines.push(` artifacts: ${transcript.artifacts.length === 0
|
|
143
|
+
? 'none' : transcript.artifacts.slice(0, 10).map(safe).join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
if (step.sandbox_id.length > 0) lines.push(` logs: flows logs ${runId} --step ${safe(step.sandbox_id)}`);
|
|
146
|
+
return lines;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function renderCloudStatus(run: CloudRunDetail, steps: readonly CloudStep[], now: number): string[] {
|
|
150
|
+
let tokensIn = 0;
|
|
151
|
+
let tokensOut = 0;
|
|
152
|
+
let cost = 0;
|
|
153
|
+
for (const step of steps) {
|
|
154
|
+
tokensIn += step.tokens_in ?? 0;
|
|
155
|
+
tokensOut += step.tokens_out ?? 0;
|
|
156
|
+
cost += step.cost_usd ?? 0;
|
|
157
|
+
}
|
|
158
|
+
// Summed from the step rows, because the run record carries no total. Cloud
|
|
159
|
+
// stores `costUsd` as a float, so this is a float sum — not the exact decimal
|
|
160
|
+
// addition the local journal fold does (run-state.ts `addDollars`). Shown to
|
|
161
|
+
// six places and labelled `spend`, the same word the local view uses.
|
|
162
|
+
const spend = `${thousands(tokensIn)} in / ${thousands(tokensOut)} out / ${dollars(cost)}`;
|
|
163
|
+
const finished = run.completion_reason === null ? '' : ` finished ${safe(run.completion_reason)}`;
|
|
164
|
+
const lines = [
|
|
165
|
+
`RUN ${run.run_id} ${safe(run.name)} ${run.status} started ${ago(run.created_at, now)}${finished} spend ${spend}`,
|
|
166
|
+
stepCounts(steps),
|
|
167
|
+
];
|
|
168
|
+
// `steps 0` alone reads as "this run has no steps", which is a claim about
|
|
169
|
+
// the run. For a run that is still going it is a fact about the snapshot:
|
|
170
|
+
// Cloud served no rows, this time.
|
|
171
|
+
if (steps.length === 0 && isCloudRunActive(run.status)) lines.push('No step snapshot available yet.');
|
|
172
|
+
if (run.authority !== null) {
|
|
173
|
+
const facts = [
|
|
174
|
+
run.authority.surface_version === null ? null : `surface ${run.authority.surface_version}`,
|
|
175
|
+
run.authority.artifact_sha256 === null ? null : `artifact ${run.authority.artifact_sha256.slice(0, 12)}`,
|
|
176
|
+
run.authority.source_commit === null ? null : `commit ${run.authority.source_commit.slice(0, 12)}`,
|
|
177
|
+
].filter((fact): fact is string => fact !== null);
|
|
178
|
+
if (facts.length > 0) lines.push(`authority ${facts.join(' · ')}`);
|
|
179
|
+
}
|
|
180
|
+
if (run.pull_request_url !== null) lines.push(`pr ${run.pull_request_url}`);
|
|
181
|
+
if (run.error !== null) lines.push('error', ...errorLines(run.error, ' '));
|
|
182
|
+
lines.push('');
|
|
183
|
+
const idWidth = Math.max(4, ...steps.map((step) => Math.min(24, step.step_name.length)));
|
|
184
|
+
for (const step of steps) lines.push(...renderStep(step, run.run_id, idWidth, now));
|
|
185
|
+
return lines;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Every free-text field of the run record, through the status page's redactor. */
|
|
189
|
+
export function scrubRun(run: CloudRunDetail, env: NodeJS.ProcessEnv): CloudRunDetail {
|
|
190
|
+
return {
|
|
191
|
+
...run,
|
|
192
|
+
name: redact(run.name, env),
|
|
193
|
+
error: run.error === null ? null : redact(run.error, env),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The same, for the step rows — including every string inside the digest. */
|
|
198
|
+
export function scrubSteps(steps: readonly CloudStep[], env: NodeJS.ProcessEnv): CloudStep[] {
|
|
199
|
+
return steps.map((step) => ({
|
|
200
|
+
...step,
|
|
201
|
+
output_summary: step.output_summary === null ? null : redact(step.output_summary, env),
|
|
202
|
+
gate: step.gate === null ? null : { ...step.gate, detail: redact(step.gate.detail, env) },
|
|
203
|
+
transcript: step.transcript === null ? null : {
|
|
204
|
+
...step.transcript,
|
|
205
|
+
model: step.transcript.model === null ? null : redact(step.transcript.model, env),
|
|
206
|
+
last_calls: step.transcript.last_calls.map((call) => ({
|
|
207
|
+
...call,
|
|
208
|
+
input_excerpt: call.input_excerpt === null ? null : redact(call.input_excerpt, env),
|
|
209
|
+
})),
|
|
210
|
+
artifacts: step.transcript.artifacts.map((path) => redact(path, env)),
|
|
211
|
+
},
|
|
212
|
+
}));
|
|
213
|
+
}
|
package/src/cli/direct-run.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { onWorkerFailure } from '../worker-lease.js';
|
|
1
2
|
import { McpStepError } from '../authored-mcp.js';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { authoredLocalAgentStream } from '../authored-admission.js';
|
|
@@ -15,6 +16,7 @@ import { DirectInputError, parseDirectInput } from '../direct-input.js';
|
|
|
15
16
|
import { JournalClient } from '../journal-client.js';
|
|
16
17
|
import { inputFailureReport } from './check.js';
|
|
17
18
|
import { checkAuthoredTriggers } from './check-triggers.js';
|
|
19
|
+
import { authoredInput, authoredWorkerRemedy, localAgentRemedy } from './local-agent-remedy.js';
|
|
18
20
|
import {
|
|
19
21
|
authoredCompletion,
|
|
20
22
|
authoredHumanParked,
|
|
@@ -82,7 +84,7 @@ export async function runDirectFlow(
|
|
|
82
84
|
await llmClient.connect();
|
|
83
85
|
await llmClient.hello('flows-local-llm');
|
|
84
86
|
localLlm = new LlmWorker(llmClient, `${localAgent.stream}-llm`, workerCapacity);
|
|
85
|
-
localLlm.on('error', error => { llmFailure = error; client.close(); });
|
|
87
|
+
localLlm.on('error', onWorkerFailure('local-llm', error => { llmFailure = error; client.close(); }));
|
|
86
88
|
await localLlm.attach();
|
|
87
89
|
}
|
|
88
90
|
const result = await executeDurableAuthoredFlow(
|
|
@@ -94,6 +96,7 @@ export async function runDirectFlow(
|
|
|
94
96
|
...(localAgent === undefined ? {} : { workerCapacity }),
|
|
95
97
|
lifecycle: {
|
|
96
98
|
onProgress: options.onProgress,
|
|
99
|
+
...(options.onRunStarted !== undefined ? { onRunStarted: options.onRunStarted } : {}),
|
|
97
100
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
98
101
|
...(options.onWait !== undefined ? { onWait: options.onWait } : {}),
|
|
99
102
|
},
|
|
@@ -163,12 +166,26 @@ export async function runDirectFlow(
|
|
|
163
166
|
...base,
|
|
164
167
|
ok: false,
|
|
165
168
|
runId: error.runId,
|
|
169
|
+
rootRunId: error.rootRunId,
|
|
166
170
|
socketPath,
|
|
167
171
|
status: 'parked',
|
|
172
|
+
parkCause: error.parkCause,
|
|
168
173
|
diagnostics: [...base.diagnostics, {
|
|
169
174
|
severity: 'parked',
|
|
170
175
|
kind: 'run_parked',
|
|
171
|
-
|
|
176
|
+
// The remedy is rendered here rather than by the child's own
|
|
177
|
+
// `classifyOutcome`, which is deliberately silent for authored
|
|
178
|
+
// paths: this is the only frame that knows both the flow path and
|
|
179
|
+
// the `--input` argument a new run has to repeat, and it knows
|
|
180
|
+
// whether a worker was already attached.
|
|
181
|
+
//
|
|
182
|
+
// `inputArgument` is the word this process was invoked with, so a
|
|
183
|
+
// run started from a file names that file rather than the megabyte
|
|
184
|
+
// inside it — the thing the journal can no longer tell a resume.
|
|
185
|
+
message: error.message + localAgentRemedy(authoredWorkerRemedy(
|
|
186
|
+
error.parkCause, options.localAgent === true,
|
|
187
|
+
{ path, input: authoredInput(inputArgument), dataDir },
|
|
188
|
+
)),
|
|
172
189
|
}],
|
|
173
190
|
},
|
|
174
191
|
};
|
package/src/cli/hn-monitor.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { onWorkerFailure } from '../worker-lease.js';
|
|
1
2
|
/**
|
|
2
3
|
* `flows hn-monitor start` — CLI-inlined proactive workload for gate 2.
|
|
3
4
|
*
|
|
@@ -169,7 +170,7 @@ async function defaultAttachWorker(
|
|
|
169
170
|
// synchronously, bypasses the drain-aware close(), and crashes the
|
|
170
171
|
// process. Subscribe BEFORE attach so an error during attach is not
|
|
171
172
|
// lost.
|
|
172
|
-
worker.on('error', onWorkerError);
|
|
173
|
+
worker.on('error', onWorkerFailure('hn-monitor', onWorkerError));
|
|
173
174
|
await worker.attach();
|
|
174
175
|
return worker;
|
|
175
176
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { resumeCommand } from '../authored-human.js';
|
|
2
|
+
import type { ParkCause } from '../failure-kinds.js';
|
|
3
|
+
import { shellQuote, shellWord } from '../shell-word.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What to tell someone whose run stopped for want of an agent worker.
|
|
7
|
+
*
|
|
8
|
+
* A closed union rather than a bag of optional fields: the remedy for a spec
|
|
9
|
+
* run is a *new* run, for a spec resume it is *this* run, and for an authored
|
|
10
|
+
* `.flow.ts` it is a new run that must carry the same `--input` — three
|
|
11
|
+
* different sentences, and no combination of them is meaningful. `attached`
|
|
12
|
+
* is the case the message used to get wrong by omission: a worker WAS offered
|
|
13
|
+
* and none of them was eligible, so naming the flag again is not the fix.
|
|
14
|
+
*/
|
|
15
|
+
export type LocalAgentRemedy =
|
|
16
|
+
/** Nothing to say: not a worker park, or the caller renders it elsewhere. */
|
|
17
|
+
| { kind: 'none' }
|
|
18
|
+
| { kind: 'attached' }
|
|
19
|
+
| { kind: 'spec-run'; path: string; dataDir?: string }
|
|
20
|
+
| { kind: 'spec-resume'; runId: string; dataDir?: string }
|
|
21
|
+
| { kind: 'authored-run'; path: string; input: AuthoredInput; dataDir?: string };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The `--input` argument a new run of an authored flow would have to repeat.
|
|
25
|
+
*
|
|
26
|
+
* Three outcomes, not an optional string, because each is a different sentence
|
|
27
|
+
* and only one of them ends in a command. None of them is `{}`: fabricating an
|
|
28
|
+
* input would hand over a command that runs a different flow invocation than
|
|
29
|
+
* the one that parked.
|
|
30
|
+
*/
|
|
31
|
+
export type AuthoredInput =
|
|
32
|
+
/** The argument this run was started with, rendered back verbatim. */
|
|
33
|
+
| { kind: 'inline'; argument: string }
|
|
34
|
+
/** The journal recorded no input argument to repeat. */
|
|
35
|
+
| { kind: 'absent' }
|
|
36
|
+
/** Recorded, but too many bytes to hand a shell as one word. */
|
|
37
|
+
| { kind: 'oversized'; bytes: number };
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The largest `--input` value worth printing inline, in bytes.
|
|
41
|
+
*
|
|
42
|
+
* Linux caps a single `execve` argument at `MAX_ARG_STRLEN` — 32 pages,
|
|
43
|
+
* 131072 bytes including the terminating NUL — independently of the much
|
|
44
|
+
* larger `ARG_MAX` total. `MAX_DIRECT_INPUT_BYTES` admits a full mebibyte, so
|
|
45
|
+
* a run started from a perfectly ordinary input file can have recorded more
|
|
46
|
+
* input than any shell can pass back. Printing it anyway yields a command that
|
|
47
|
+
* dies with `Argument list too long` before the CLI is even reached, which is
|
|
48
|
+
* no better than the park it was meant to answer.
|
|
49
|
+
*/
|
|
50
|
+
const MAX_INLINE_INPUT_BYTES = 131_071;
|
|
51
|
+
|
|
52
|
+
/** How a recorded input argument should be rendered, given its size. */
|
|
53
|
+
export function authoredInput(argument: string | undefined): AuthoredInput {
|
|
54
|
+
if (argument === undefined) return { kind: 'absent' };
|
|
55
|
+
const bytes = Buffer.byteLength(argument, 'utf8');
|
|
56
|
+
return bytes > MAX_INLINE_INPUT_BYTES ? { kind: 'oversized', bytes } : { kind: 'inline', argument };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The declared-surface caveat, stated as the condition it is.
|
|
61
|
+
*
|
|
62
|
+
* A step that declares a workspace or a stream needs a worker holding those
|
|
63
|
+
* pins, and the local agent worker holds only its own stream. That is worth
|
|
64
|
+
* saying next to the command — but it is a caveat on the remedy, not a
|
|
65
|
+
* diagnosis of this park, and it is phrased so it cannot be read as one.
|
|
66
|
+
*/
|
|
67
|
+
const PINS = 'Declared workspace or stream surfaces require a worker that holds their pins.';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The remedy clause appended to a worker-park diagnostic, with a leading
|
|
71
|
+
* space, or `''` when there is nothing to add.
|
|
72
|
+
*/
|
|
73
|
+
export function localAgentRemedy(remedy: LocalAgentRemedy): string {
|
|
74
|
+
switch (remedy.kind) {
|
|
75
|
+
case 'none':
|
|
76
|
+
return '';
|
|
77
|
+
case 'attached':
|
|
78
|
+
// Never "pass --local-agent": it was passed. The honest report is that
|
|
79
|
+
// a worker was offered and the daemon matched none of them to this step.
|
|
80
|
+
return ' A local agent worker is already attached to this run, so passing --local-agent again'
|
|
81
|
+
+ ' changes nothing: no attached worker was eligible for this step.'
|
|
82
|
+
+ ` ${PINS} The local agent worker holds only its own stream.`;
|
|
83
|
+
case 'spec-run':
|
|
84
|
+
return ` To start a new run with a local agent worker: ${
|
|
85
|
+
newRunCommand(remedy.path, undefined, remedy.dataDir)}. ${PINS}`;
|
|
86
|
+
case 'spec-resume':
|
|
87
|
+
// This run is resumable, so the remedy is this run — not a new one.
|
|
88
|
+
return ` To continue this run with a local agent worker: ${
|
|
89
|
+
resumeCommand(remedy.runId, remedy.dataDir, true)}. ${PINS}`;
|
|
90
|
+
case 'authored-run':
|
|
91
|
+
return authoredRunRemedy(remedy);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Either a runnable new run, or the requirement stated in prose.
|
|
97
|
+
*
|
|
98
|
+
* Prose, not a command, whenever the `--input` cannot be printed as something
|
|
99
|
+
* a shell would deliver intact: a `.flow.ts` invocation without its `--input`
|
|
100
|
+
* is refused (`input_missing`), and a placeholder or a truncation in its place
|
|
101
|
+
* is worse than a sentence, because it looks runnable.
|
|
102
|
+
*/
|
|
103
|
+
function authoredRunRemedy(remedy: Extract<LocalAgentRemedy, { kind: 'authored-run' }>): string {
|
|
104
|
+
if (remedy.input.kind === 'inline') {
|
|
105
|
+
return ` To start a new run with a local agent worker: ${
|
|
106
|
+
newRunCommand(remedy.path, remedy.input.argument, remedy.dataDir)}. ${PINS}`;
|
|
107
|
+
}
|
|
108
|
+
const requirement = ' A local agent worker is admitted at run start, so this run needs a new one.'
|
|
109
|
+
+ ` Starting one needs --local-agent, the flow path ${shellQuote(remedy.path)}, and the same`
|
|
110
|
+
+ ' --input this flow takes; ';
|
|
111
|
+
return requirement + (remedy.input.kind === 'absent'
|
|
112
|
+
? 'the journal recorded no input argument to repeat here.'
|
|
113
|
+
// Naming the size says which input, and says why this clause is prose:
|
|
114
|
+
// the recorded value is larger than one `execve` argument may be, so a
|
|
115
|
+
// printed `--input '<json>'` would fail before the CLI ran at all.
|
|
116
|
+
: `the journal recorded ${remedy.input.bytes} bytes of input, more than a shell`
|
|
117
|
+
+ ' can carry in one argument, so pass the input file this run was started from.')
|
|
118
|
+
+ ` ${PINS}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Which remedy an authored `.flow.ts` park gets, decided once for both authored
|
|
123
|
+
* boundaries (`cli/direct-run.ts` on a fresh run, `resumeFlow` on a resume).
|
|
124
|
+
*
|
|
125
|
+
* The cause is trusted, not guessed: anything other than `worker_unavailable` —
|
|
126
|
+
* including an absent cause — yields `none`, so a `needs_human` recovery wait is
|
|
127
|
+
* never answered with "attach a worker", and an unclassified park says nothing
|
|
128
|
+
* at all rather than something plausible.
|
|
129
|
+
*
|
|
130
|
+
* `path` is optional because the caller may only have the journal to go on. A
|
|
131
|
+
* park with no known flow path yields `none` for the same reason an unknown
|
|
132
|
+
* input yields prose: the point of this clause is a command someone can run.
|
|
133
|
+
*/
|
|
134
|
+
export function authoredWorkerRemedy(
|
|
135
|
+
parkCause: ParkCause | undefined,
|
|
136
|
+
attached: boolean,
|
|
137
|
+
run: { path?: string; input: AuthoredInput; dataDir?: string },
|
|
138
|
+
): LocalAgentRemedy {
|
|
139
|
+
if (parkCause !== 'worker_unavailable') return { kind: 'none' };
|
|
140
|
+
if (attached) return { kind: 'attached' };
|
|
141
|
+
if (run.path === undefined) return { kind: 'none' };
|
|
142
|
+
return { kind: 'authored-run', path: run.path, input: run.input, dataDir: run.dataDir };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* `flows run --local-agent <path> [--input <input>] [--data-dir <dir>]`.
|
|
147
|
+
*
|
|
148
|
+
* The flag/path prefix is byte-for-byte what the spec-run message has always
|
|
149
|
+
* emitted, so the one thing people already grep for still matches. `--input`
|
|
150
|
+
* and `--data-dir` follow the path because the parser is order-insensitive
|
|
151
|
+
* (cli.ts `parseRunArgs`), and keeping the prefix intact matters more than
|
|
152
|
+
* mirroring the usage line's flag order.
|
|
153
|
+
*/
|
|
154
|
+
function newRunCommand(path: string, input: string | undefined, dataDir: string | undefined): string {
|
|
155
|
+
return `flows run --local-agent ${shellQuote(path)}`
|
|
156
|
+
+ (input === undefined ? '' : ` --input ${shellQuote(input)}`)
|
|
157
|
+
// `shellWord` here, matching `resumeCommand`/`answerCommand`: a data dir is
|
|
158
|
+
// this machine's path, and the two renderings must not disagree.
|
|
159
|
+
+ (dataDir === undefined ? '' : ` --data-dir ${shellWord(dataDir)}`);
|
|
160
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The observer half of `flows run` / `flows resume`: once the run id is known,
|
|
3
|
+
* project the run into its `wf-<runId>` channel and mint a read-only link
|
|
4
|
+
* scoped to that channel, so the link opens on this run's step graph instead
|
|
5
|
+
* of an arbitrary workspace channel. The link is printed on stderr the moment
|
|
6
|
+
* it exists, so a human can follow the run live, and again after `RUN`.
|
|
7
|
+
*
|
|
8
|
+
* Best-effort throughout: no workspace key means no session, and a projection
|
|
9
|
+
* or mint failure is a labeled stderr line. The run never depends on it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { CliIo } from '../cli.js';
|
|
13
|
+
import { createJournalProjector } from '../journal-projection.js';
|
|
14
|
+
import type { JournalEvent } from '../journal-reader.js';
|
|
15
|
+
import { mintObserverUrl, resolveObserverLinkEnv, type MintObserverOptions } from '../observer-link.js';
|
|
16
|
+
import type { ProgressEvent } from '../progress.js';
|
|
17
|
+
import {
|
|
18
|
+
createRunProjection, runChannelName, type DeclaredStep, type ProjectionFetch, type RunProjection,
|
|
19
|
+
} from '../run-projection.js';
|
|
20
|
+
import type { RunReport } from './run.js';
|
|
21
|
+
|
|
22
|
+
type MintOutcome = { observerUrl?: string; warning?: string };
|
|
23
|
+
|
|
24
|
+
/** Bounded so an unreachable Relaycast cannot hold the CLI open. */
|
|
25
|
+
const DRAIN_GRACE_MS = 5_000;
|
|
26
|
+
|
|
27
|
+
export interface ObserverSession {
|
|
28
|
+
/** YAML runs: every entry `run.start {watch}` / `run.watch` pushes. */
|
|
29
|
+
onJournalEntry(entry: JournalEvent): void;
|
|
30
|
+
/** Authored runs: the root's id and name, known once it is admitted. */
|
|
31
|
+
onRunStarted(run: { runId: string; flow: string; resumed?: boolean }): void;
|
|
32
|
+
/** Authored runs: step transitions from the body's executor. */
|
|
33
|
+
onProgress(event: ProgressEvent): void;
|
|
34
|
+
/** Close the projection from the final report and settle the link. */
|
|
35
|
+
finish(report: RunReport): Promise<MintOutcome> | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ObserverSessionDeps {
|
|
39
|
+
fetch?: ProjectionFetch;
|
|
40
|
+
mint?: (options: MintObserverOptions) => Promise<MintOutcome>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createObserverSession(
|
|
44
|
+
command: 'run' | 'resume',
|
|
45
|
+
io: CliIo,
|
|
46
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
47
|
+
deps: ObserverSessionDeps = {},
|
|
48
|
+
): ObserverSession | undefined {
|
|
49
|
+
const link = resolveObserverLinkEnv(env);
|
|
50
|
+
if (link.suppressed || link.workspaceKey === undefined) return undefined;
|
|
51
|
+
const workspaceKey = link.workspaceKey;
|
|
52
|
+
const mint = deps.mint ?? mintObserverUrl;
|
|
53
|
+
const liveSinceMs = command === 'resume' ? Date.now() : 0;
|
|
54
|
+
let projection: RunProjection | undefined;
|
|
55
|
+
let minted: Promise<MintOutcome> | undefined;
|
|
56
|
+
let rootRunId: string | undefined;
|
|
57
|
+
|
|
58
|
+
const mintFor = (runId: string): Promise<MintOutcome> => minted ??= mint({
|
|
59
|
+
workspaceKey,
|
|
60
|
+
channel: runChannelName(runId),
|
|
61
|
+
...(link.baseUrl === undefined ? {} : { baseUrl: link.baseUrl }),
|
|
62
|
+
...(link.dashboardUrl === undefined ? {} : { dashboardUrl: link.dashboardUrl }),
|
|
63
|
+
}).catch((error: unknown) => ({ warning: error instanceof Error ? error.message : 'unknown mint error' }));
|
|
64
|
+
|
|
65
|
+
const open = (run: { runId: string; flow: string; steps?: DeclaredStep[]; resumed?: boolean }): RunProjection => {
|
|
66
|
+
if (projection !== undefined) return projection;
|
|
67
|
+
rootRunId = run.runId;
|
|
68
|
+
projection = createRunProjection({
|
|
69
|
+
workspaceKey,
|
|
70
|
+
...(link.baseUrl === undefined ? {} : { baseUrl: link.baseUrl }),
|
|
71
|
+
...(deps.fetch === undefined ? {} : { fetch: deps.fetch }),
|
|
72
|
+
diagnostic: message => io.stderr(`[observer] ${message}`),
|
|
73
|
+
}, run);
|
|
74
|
+
void mintFor(run.runId).then(outcome => {
|
|
75
|
+
if (outcome.observerUrl !== undefined) io.stderr(`Observer: ${outcome.observerUrl}`);
|
|
76
|
+
});
|
|
77
|
+
return projection;
|
|
78
|
+
};
|
|
79
|
+
const project = createJournalProjector(
|
|
80
|
+
run => open({ ...run, ...(liveSinceMs > 0 ? { resumed: true } : {}) }), liveSinceMs,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
onJournalEntry(entry) {
|
|
85
|
+
try { project(entry); } catch (error) {
|
|
86
|
+
io.stderr(`[observer] could not project journal entry ${entry.seq}: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
onRunStarted: run => { open(run); },
|
|
90
|
+
onProgress(event) { projection?.step(event); },
|
|
91
|
+
finish(report) {
|
|
92
|
+
const runId = rootRunId ?? report.runId;
|
|
93
|
+
if (runId === undefined) return undefined;
|
|
94
|
+
if (projection === undefined) {
|
|
95
|
+
io.stderr('[observer] this run was not projected (the daemon did not stream its journal); '
|
|
96
|
+
+ 'the observer link opens an empty channel');
|
|
97
|
+
}
|
|
98
|
+
projection?.finish({
|
|
99
|
+
status: report.status === 'parked' ? 'parked' : report.completionReason === 'canceled' ? 'canceled'
|
|
100
|
+
: report.ok ? 'completed' : 'failed',
|
|
101
|
+
...(report.completionReason === undefined ? {} : { completionReason: report.completionReason }),
|
|
102
|
+
});
|
|
103
|
+
const drained = projection?.close(DRAIN_GRACE_MS) ?? Promise.resolve();
|
|
104
|
+
return drained.then(() => mintFor(runId));
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|