@relayflows/sdk 2.0.21 → 2.0.23
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 +18 -9
- package/dist/agent-artifacts.d.ts.map +1 -1
- package/dist/agent-artifacts.js +27 -11
- package/dist/agent-artifacts.js.map +1 -1
- package/dist/agent-transcript.d.ts +186 -0
- package/dist/agent-transcript.d.ts.map +1 -0
- package/dist/agent-transcript.js +585 -0
- package/dist/agent-transcript.js.map +1 -0
- package/dist/authored-flow-error.d.ts +33 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +20 -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 +4 -1
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-node-entry.js +5 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts +11 -0
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +30 -1
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root.js +3 -1
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-source-authority.js +13 -0
- package/dist/authored-source-authority.js.map +1 -1
- package/dist/authored-step-index.d.ts +82 -0
- package/dist/authored-step-index.d.ts.map +1 -0
- package/dist/authored-step-index.js +120 -0
- package/dist/authored-step-index.js.map +1 -0
- package/dist/authored-step-output.d.ts +23 -2
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +47 -5
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-worker-step.d.ts +10 -2
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +81 -7
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +67 -12
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-read.d.ts +31 -0
- package/dist/cli/cloud-read.d.ts.map +1 -0
- package/dist/cli/cloud-read.js +482 -0
- package/dist/cli/cloud-read.js.map +1 -0
- package/dist/cli/run.d.ts +2 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +53 -24
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +43 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +321 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/step-failure.d.ts +26 -0
- package/dist/cli/step-failure.d.ts.map +1 -1
- package/dist/cli/step-failure.js +99 -10
- package/dist/cli/step-failure.js.map +1 -1
- package/dist/cli-commands.d.ts +41 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +33 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -1
- package/dist/cloud-http.d.ts +12 -0
- package/dist/cloud-http.d.ts.map +1 -1
- package/dist/cloud-http.js +14 -6
- package/dist/cloud-http.js.map +1 -1
- package/dist/cloud-read.d.ts +154 -0
- package/dist/cloud-read.d.ts.map +1 -0
- package/dist/cloud-read.js +373 -0
- package/dist/cloud-read.js.map +1 -0
- package/dist/cloud-transcript.d.ts +94 -0
- package/dist/cloud-transcript.d.ts.map +1 -0
- package/dist/cloud-transcript.js +342 -0
- package/dist/cloud-transcript.js.map +1 -0
- package/dist/communication/environment.d.ts +3 -0
- package/dist/communication/environment.d.ts.map +1 -0
- package/dist/communication/environment.js +34 -0
- package/dist/communication/environment.js.map +1 -0
- package/dist/communication/history.d.ts +6 -0
- package/dist/communication/history.d.ts.map +1 -0
- package/dist/communication/history.js +38 -0
- package/dist/communication/history.js.map +1 -0
- package/dist/communication/local.d.ts +6 -0
- package/dist/communication/local.d.ts.map +1 -0
- package/dist/communication/local.js +56 -0
- package/dist/communication/local.js.map +1 -0
- package/dist/communication/preflight.d.ts +6 -0
- package/dist/communication/preflight.d.ts.map +1 -0
- package/dist/communication/preflight.js +37 -0
- package/dist/communication/preflight.js.map +1 -0
- package/dist/communication/projection.d.ts +8 -0
- package/dist/communication/projection.d.ts.map +1 -0
- package/dist/communication/projection.js +23 -0
- package/dist/communication/projection.js.map +1 -0
- package/dist/communication/relay.d.ts +78 -0
- package/dist/communication/relay.d.ts.map +1 -0
- package/dist/communication/relay.js +89 -0
- package/dist/communication/relay.js.map +1 -0
- package/dist/communication/session.d.ts +24 -0
- package/dist/communication/session.d.ts.map +1 -0
- package/dist/communication/session.js +100 -0
- package/dist/communication/session.js.map +1 -0
- package/dist/communication/spec.d.ts +20 -0
- package/dist/communication/spec.d.ts.map +1 -0
- package/dist/communication/spec.js +113 -0
- package/dist/communication/spec.js.map +1 -0
- package/dist/communication/tool.d.ts +3 -0
- package/dist/communication/tool.d.ts.map +1 -0
- package/dist/communication/tool.js +23 -0
- package/dist/communication/tool.js.map +1 -0
- package/dist/communication/tools.d.ts +11 -0
- package/dist/communication/tools.d.ts.map +1 -0
- package/dist/communication/tools.js +65 -0
- package/dist/communication/tools.js.map +1 -0
- package/dist/communication/worker.d.ts +6 -0
- package/dist/communication/worker.d.ts.map +1 -0
- package/dist/communication/worker.js +120 -0
- package/dist/communication/worker.js.map +1 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +8 -1
- package/dist/compile.js.map +1 -1
- package/dist/failure-kinds.d.ts +30 -2
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +9 -0
- package/dist/failure-kinds.js.map +1 -1
- 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/journal-client.d.ts +4 -1
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +11 -2
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-reader.d.ts +1 -1
- package/dist/journal-reader.d.ts.map +1 -1
- package/dist/journal-reader.js +3 -1
- package/dist/journal-reader.js.map +1 -1
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +12 -1
- package/dist/llm-worker.js.map +1 -1
- package/dist/permissions-preflight.d.ts +22 -0
- package/dist/permissions-preflight.d.ts.map +1 -0
- package/dist/permissions-preflight.js +49 -0
- package/dist/permissions-preflight.js.map +1 -0
- package/dist/preflight.d.ts +13 -2
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +29 -6
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +39 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/pty-sidechannel.d.ts +7 -0
- package/dist/pty-sidechannel.d.ts.map +1 -1
- package/dist/pty-sidechannel.js.map +1 -1
- package/dist/redact.d.ts +16 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +104 -0
- package/dist/redact.js.map +1 -0
- package/dist/run-state.d.ts +124 -0
- package/dist/run-state.d.ts.map +1 -0
- package/dist/run-state.js +368 -0
- package/dist/run-state.js.map +1 -0
- package/dist/spec.d.ts +24 -1
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/step-env.d.ts +25 -0
- package/dist/step-env.d.ts.map +1 -0
- package/dist/step-env.js +34 -0
- package/dist/step-env.js.map +1 -0
- package/dist/transcript-tail.d.ts +54 -0
- package/dist/transcript-tail.d.ts.map +1 -0
- package/dist/transcript-tail.js +176 -0
- package/dist/transcript-tail.js.map +1 -0
- package/dist/worker-cli.d.ts +10 -0
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +138 -17
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker-usage.d.ts +7 -2
- package/dist/worker-usage.d.ts.map +1 -1
- package/dist/worker-usage.js +21 -3
- package/dist/worker-usage.js.map +1 -1
- package/dist/worker.d.ts +1 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +41 -7
- package/dist/worker.js.map +1 -1
- package/dist/wrapper-runtime.d.ts +7 -1
- package/dist/wrapper-runtime.d.ts.map +1 -1
- package/dist/wrapper-runtime.js +7 -1
- package/dist/wrapper-runtime.js.map +1 -1
- package/dist/wrapper-session.d.ts +8 -0
- package/dist/wrapper-session.d.ts.map +1 -1
- package/dist/wrapper-session.js +156 -23
- package/dist/wrapper-session.js.map +1 -1
- package/package.json +14 -2
- package/src/agent-artifacts.ts +27 -11
- package/src/agent-transcript.ts +651 -0
- package/src/authored-flow-error.ts +17 -0
- package/src/authored-flow-executor.ts +4 -1
- package/src/authored-node-entry.ts +5 -1
- package/src/authored-node-runner.ts +33 -1
- package/src/authored-root.ts +2 -1
- package/src/authored-source-authority.ts +13 -0
- package/src/authored-step-index.ts +157 -0
- package/src/authored-step-output.ts +66 -14
- package/src/authored-worker-step.ts +88 -15
- package/src/cli/check.ts +69 -11
- package/src/cli/cloud-read.ts +518 -0
- package/src/cli/run.ts +43 -24
- package/src/cli/status.ts +349 -0
- package/src/cli/step-failure.ts +100 -10
- package/src/cli-commands.ts +33 -0
- package/src/cli.ts +27 -0
- package/src/cloud-http.ts +32 -6
- package/src/cloud-read.ts +515 -0
- package/src/cloud-transcript.ts +422 -0
- package/src/communication/environment.ts +33 -0
- package/src/communication/history.ts +38 -0
- package/src/communication/local.ts +41 -0
- package/src/communication/preflight.ts +34 -0
- package/src/communication/projection.ts +23 -0
- package/src/communication/relay.ts +102 -0
- package/src/communication/session.ts +84 -0
- package/src/communication/spec.ts +106 -0
- package/src/communication/tool.ts +22 -0
- package/src/communication/tools.ts +50 -0
- package/src/communication/worker.ts +95 -0
- package/src/compile.ts +4 -1
- package/src/failure-kinds.ts +31 -1
- package/src/index.ts +10 -1
- package/src/journal-client.ts +12 -2
- package/src/journal-reader.ts +4 -2
- package/src/llm-worker.ts +12 -1
- package/src/permissions-preflight.ts +53 -0
- package/src/preflight.ts +42 -7
- package/src/protocol.ts +15 -0
- package/src/pty-sidechannel.ts +7 -0
- package/src/redact.ts +106 -0
- package/src/run-state.ts +468 -0
- package/src/spec.ts +18 -1
- package/src/step-env.ts +47 -0
- package/src/transcript-tail.ts +193 -0
- package/src/worker-cli.ts +139 -18
- package/src/worker-usage.ts +26 -3
- package/src/worker.ts +42 -6
- package/src/wrapper-runtime.ts +7 -1
- package/src/wrapper-session.ts +159 -24
package/src/cli/step-failure.ts
CHANGED
|
@@ -34,18 +34,33 @@ export async function stepFailureDetails(
|
|
|
34
34
|
const snapshot = await client.runGet(runId);
|
|
35
35
|
let fromSeq = 1;
|
|
36
36
|
const failures = new Map<string, StepFailedDetails>();
|
|
37
|
+
// `runGet`'s `StepSnapshot` carries only `type`, `state` and an optional
|
|
38
|
+
// lease deadline — neither the attempt number nor the attempt budget. Both
|
|
39
|
+
// are journal facts: the envelope's `attempt` counts the attempt, and
|
|
40
|
+
// `step.attempt.started` records the `max_iterations` the kernel is
|
|
41
|
+
// enforcing (relayflowd-core/src/entry.rs `AttemptStartedPayload`). They are
|
|
42
|
+
// collected on the same walk and reported only when the journal held them.
|
|
43
|
+
const budgets = new Map<string, number>();
|
|
37
44
|
while (true) {
|
|
38
45
|
const { entries } = await client.journalRead(runId, fromSeq, 100);
|
|
39
46
|
if (entries.length === 0) break;
|
|
40
47
|
for (const raw of entries) {
|
|
41
48
|
const entry = record(raw);
|
|
42
49
|
const seq = entry?.['seq'];
|
|
43
|
-
if (typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < fromSeq) {
|
|
50
|
+
if (entry === undefined || typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < fromSeq) {
|
|
44
51
|
throw new Error('invalid journal sequence in step failure inspection');
|
|
45
52
|
}
|
|
46
53
|
fromSeq = seq + 1;
|
|
47
|
-
const stepId = entry
|
|
48
|
-
if (
|
|
54
|
+
const stepId = entry['step_id'];
|
|
55
|
+
if (typeof stepId !== 'string') continue;
|
|
56
|
+
if (entry['entry_type'] === 'step.attempt.started') {
|
|
57
|
+
const maxIterations = record(entry['payload'])?.['max_iterations'];
|
|
58
|
+
if (typeof maxIterations === 'number' && Number.isSafeInteger(maxIterations) && maxIterations > 0) {
|
|
59
|
+
budgets.set(stepId, maxIterations);
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (entry['entry_type'] !== 'step.completed') continue;
|
|
49
64
|
// A later completion supersedes an earlier failed attempt.
|
|
50
65
|
failures.delete(stepId);
|
|
51
66
|
const payload = record(entry['payload']);
|
|
@@ -59,10 +74,14 @@ export async function stepFailureDetails(
|
|
|
59
74
|
if (payload['disposition'] !== 'step_done'
|
|
60
75
|
|| typeof completionReason !== 'string' || completionReason === 'success') continue;
|
|
61
76
|
const stepType = snapshot.steps[stepId]?.type;
|
|
77
|
+
const attempt = entry['attempt'];
|
|
78
|
+
const maxIterations = budgets.get(stepId);
|
|
62
79
|
failures.set(stepId, {
|
|
63
80
|
stepId,
|
|
64
81
|
completionReason,
|
|
65
82
|
...(stepType === undefined ? {} : { stepType }),
|
|
83
|
+
...(typeof attempt === 'number' && Number.isSafeInteger(attempt) && attempt > 0 ? { attempt } : {}),
|
|
84
|
+
...(maxIterations === undefined ? {} : { maxIterations }),
|
|
66
85
|
...evidence(payload),
|
|
67
86
|
});
|
|
68
87
|
}
|
|
@@ -70,6 +89,52 @@ export async function stepFailureDetails(
|
|
|
70
89
|
return [...failures.values()].at(-1);
|
|
71
90
|
}
|
|
72
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The evidence half of a `step_failed` diagnostic; `renderInspection` adds the
|
|
94
|
+
* rest. Each field is printed only when the journal actually carried it — an
|
|
95
|
+
* agent step has no exit code to report, and inventing `exit=undefined` (which
|
|
96
|
+
* is what the deterministic-only version printed for one) is worse than
|
|
97
|
+
* silence on that field.
|
|
98
|
+
*
|
|
99
|
+
* `attempt=N/M` is here for `retries_exhausted`, which is the kernel's terminal
|
|
100
|
+
* reason for "the attempt budget was consumed" (kernel/DESIGN.md) and is
|
|
101
|
+
* accurate kernel vocabulary that reads in English as though retries happened.
|
|
102
|
+
* A deterministic step gets the default budget of one, so a single failed
|
|
103
|
+
* attempt with no retry at all terminates as `retries_exhausted`. The
|
|
104
|
+
* vocabulary does not change (AGENTS.md rule 7); printing the facts beside it
|
|
105
|
+
* stops it being misread. Unknown values are omitted rather than defaulted —
|
|
106
|
+
* an absent budget must not be reported as one attempt.
|
|
107
|
+
*
|
|
108
|
+
* Lives next to `StepFailedDetails`'s extractor rather than in `cli/run.ts` so
|
|
109
|
+
* the authored `f.run` path can render the same grammar without importing the
|
|
110
|
+
* run lifecycle (which would be a cycle).
|
|
111
|
+
*/
|
|
112
|
+
export function renderStepEvidence(details: StepFailedDetails): string {
|
|
113
|
+
return ` Step ${JSON.stringify(details.stepId)}`
|
|
114
|
+
+ (details.stepType === undefined ? '' : ` (${details.stepType})`)
|
|
115
|
+
+ ` completionReason: ${details.completionReason}`
|
|
116
|
+
+ renderAttempt(details)
|
|
117
|
+
+ (details.exitCode === undefined ? '' : ` exit=${details.exitCode}`)
|
|
118
|
+
+ '.'
|
|
119
|
+
+ (details.detail === undefined ? '' : `\nDetail: ${details.detail}`)
|
|
120
|
+
+ (details.stdoutTail ? `\nStdout (last 1,024 bytes):\n${details.stdoutTail}` : '')
|
|
121
|
+
+ (details.stderrTail ? `\nStderr (last 1,024 bytes):\n${details.stderrTail}` : '')
|
|
122
|
+
+ (details.transcriptPath === undefined ? '' : `\nTranscript: ${details.transcriptPath}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function renderAttempt(details: StepFailedDetails): string {
|
|
126
|
+
if (details.attempt === undefined) return '';
|
|
127
|
+
return ` attempt=${details.attempt}`
|
|
128
|
+
+ (details.maxIterations === undefined ? '' : `/${details.maxIterations}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The `Inspect:`/`Journal:` tail every `step_failed` message ends with. */
|
|
132
|
+
export function renderInspection(where: { hint?: string; journalPath?: string }): string {
|
|
133
|
+
if (where.hint === undefined) return '';
|
|
134
|
+
return `\nInspect: ${where.hint}`
|
|
135
|
+
+ (where.journalPath === undefined ? '' : `\nJournal: ${where.journalPath}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
73
138
|
/**
|
|
74
139
|
* Where to look, derived from the run id and data dir ALONE.
|
|
75
140
|
*
|
|
@@ -108,28 +173,53 @@ export function inspectionHint(
|
|
|
108
173
|
* it does not. A truncated render (the daemon caps at 2,000 chars and appends
|
|
109
174
|
* a truncation note) will not parse; that falls through to the raw string,
|
|
110
175
|
* which is still the account of what went wrong.
|
|
176
|
+
*
|
|
177
|
+
* An agent or llm completion now also carries `trajectory_tail.transcript`
|
|
178
|
+
* (agent-transcript.ts) on every attempt. That object is not process-shaped,
|
|
179
|
+
* so it must not shadow the render that is: the first candidate that carries
|
|
180
|
+
* an exit code or a tail wins. The digest contributes what only it has — the
|
|
181
|
+
* failure excerpt the worker picked out of the provider's frames, and the
|
|
182
|
+
* path of the transcript file.
|
|
111
183
|
*/
|
|
112
184
|
function evidence(payload: Record<string, unknown>): Partial<StepFailedDetails> {
|
|
113
185
|
const detail = record(payload['verification'])?.['detail'];
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
186
|
+
const candidates = [
|
|
187
|
+
record(payload['output']),
|
|
188
|
+
record(payload['trajectory_tail']),
|
|
189
|
+
typeof detail === 'string' ? parsed(detail) : undefined,
|
|
190
|
+
].filter((candidate): candidate is Record<string, unknown> => candidate !== undefined);
|
|
191
|
+
const structured = candidates.find(processShaped) ?? candidates[0];
|
|
117
192
|
const exitCode = structured?.['exit_code'];
|
|
118
193
|
const stdout = structured?.['stdout_tail'];
|
|
119
194
|
const stderr = structured?.['stderr_tail'];
|
|
120
|
-
const structuredShape =
|
|
121
|
-
|
|
195
|
+
const structuredShape = structured !== undefined && processShaped(structured);
|
|
196
|
+
const transcript = record(record(payload['trajectory_tail'])?.['transcript']);
|
|
197
|
+
const failure = record(transcript?.['failure']);
|
|
198
|
+
const excerpt = failure?.['excerpt'];
|
|
199
|
+
const transcriptPath = record(transcript?.['file'])?.['path'];
|
|
200
|
+
// The excerpt is the worker's own pick of the failure; a `stderr` excerpt
|
|
201
|
+
// is the same bytes as `stderrTail`, so it is not printed twice.
|
|
202
|
+
const excerptDetail = typeof excerpt === 'string' && excerpt.length > 0
|
|
203
|
+
&& !(failure?.['kind'] === 'stderr' && typeof stderr === 'string')
|
|
204
|
+
? tail(excerpt) : undefined;
|
|
122
205
|
return {
|
|
123
206
|
...(typeof exitCode === 'number' && Number.isSafeInteger(exitCode) ? { exitCode } : {}),
|
|
124
207
|
...(typeof stdout === 'string' && stdout.length > 0 ? { stdoutTail: tail(stdout) } : {}),
|
|
125
208
|
...(typeof stderr === 'string' ? { stderrTail: tail(stderr) } : {}),
|
|
126
209
|
// Keep the daemon's account only when it was NOT just a render of the
|
|
127
210
|
// fields above — otherwise the same bytes print twice.
|
|
128
|
-
...(
|
|
129
|
-
|
|
211
|
+
...(excerptDetail !== undefined ? { detail: excerptDetail }
|
|
212
|
+
: typeof detail === 'string' && detail.length > 0 && !structuredShape
|
|
213
|
+
? { detail: tail(detail) } : {}),
|
|
214
|
+
...(typeof transcriptPath === 'string' && transcriptPath.length > 0 ? { transcriptPath } : {}),
|
|
130
215
|
};
|
|
131
216
|
}
|
|
132
217
|
|
|
218
|
+
function processShaped(value: Record<string, unknown>): boolean {
|
|
219
|
+
return typeof value['exit_code'] === 'number'
|
|
220
|
+
|| typeof value['stdout_tail'] === 'string' || typeof value['stderr_tail'] === 'string';
|
|
221
|
+
}
|
|
222
|
+
|
|
133
223
|
function parsed(value: string): Record<string, unknown> | undefined {
|
|
134
224
|
try {
|
|
135
225
|
return record(JSON.parse(value));
|
package/src/cli-commands.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_DATA_DIR } from './daemon-connection.js';
|
|
2
|
+
import { DEFAULT_RUN_LIMIT } from './cli/cloud-read.js';
|
|
2
3
|
import type { ParsedArgs } from './cli.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -181,6 +182,17 @@ export const CLI_VERBS = [
|
|
|
181
182
|
],
|
|
182
183
|
variants: ['hn-monitor'],
|
|
183
184
|
},
|
|
185
|
+
{
|
|
186
|
+
name: 'logs',
|
|
187
|
+
description: 'Read a hosted run’s runner log, or one agent step’s transcript, from Cloud',
|
|
188
|
+
args: [{ name: 'run-id', description: 'Hosted run id, as `flows runs` lists it', required: true }],
|
|
189
|
+
options: [
|
|
190
|
+
{ flags: '--step <name>', description: 'Show that agent step’s transcript instead of the runner log' },
|
|
191
|
+
{ flags: '--raw', description: 'Print the transcript JSONL unrendered (still redacted)' },
|
|
192
|
+
JSON_OPTION,
|
|
193
|
+
],
|
|
194
|
+
variants: ['logs'],
|
|
195
|
+
},
|
|
184
196
|
{
|
|
185
197
|
name: 'observer',
|
|
186
198
|
description: 'Mint a read-only observer link without running a flow',
|
|
@@ -231,6 +243,15 @@ export const CLI_VERBS = [
|
|
|
231
243
|
],
|
|
232
244
|
variants: ['run', 'cloud-run'],
|
|
233
245
|
},
|
|
246
|
+
{
|
|
247
|
+
name: 'runs',
|
|
248
|
+
description: 'List the hosted runs this Cloud credential can read, newest first',
|
|
249
|
+
options: [
|
|
250
|
+
{ flags: '--limit <n>', description: 'How many runs to show', defaultValue: DEFAULT_RUN_LIMIT },
|
|
251
|
+
JSON_OPTION,
|
|
252
|
+
],
|
|
253
|
+
variants: ['runs'],
|
|
254
|
+
},
|
|
234
255
|
{
|
|
235
256
|
name: 'schedule',
|
|
236
257
|
description: 'Register a flow to run in Cloud on a cron or interval, or on the one it declares',
|
|
@@ -265,6 +286,18 @@ export const CLI_VERBS = [
|
|
|
265
286
|
],
|
|
266
287
|
variants: ['serve-webhook'],
|
|
267
288
|
},
|
|
289
|
+
{
|
|
290
|
+
name: 'status',
|
|
291
|
+
description: 'Show a run’s steps, attempts, leases and last verdicts from its journal, or from Cloud with --cloud',
|
|
292
|
+
args: [{ name: 'run-id', description: 'Run to inspect; defaults to RELAYFLOW_RUN_ID inside a step', required: false }],
|
|
293
|
+
options: [
|
|
294
|
+
JSON_OPTION,
|
|
295
|
+
DATA_DIR_OPTION,
|
|
296
|
+
{ flags: '--tail <n>', description: 'Lines of each agent attempt’s transcript tail to show' },
|
|
297
|
+
{ flags: '--cloud', description: 'Read the run from Cloud instead of a local journal; needs the run id' },
|
|
298
|
+
],
|
|
299
|
+
variants: ['status'],
|
|
300
|
+
},
|
|
268
301
|
{
|
|
269
302
|
name: 'sync',
|
|
270
303
|
description: 'Apply a hosted run’s code changes to a local tree (replaces `agent-relay cloud sync`)',
|
package/src/cli.ts
CHANGED
|
@@ -24,6 +24,12 @@ import { checkAuthoredTriggers } from './cli/check-triggers.js';
|
|
|
24
24
|
import { parseWebhookArgs, runServeWebhook } from './cli/serve-webhook.js';
|
|
25
25
|
import { runDirectFlow } from './cli/direct-run.js';
|
|
26
26
|
import { parseReplayArgs, replayJournal, type ReplayArgs } from './cli/replay.js';
|
|
27
|
+
import { parseStatusArgs, runStatus, type StatusArgs } from './cli/status.js';
|
|
28
|
+
import {
|
|
29
|
+
parseLogsArgs, parseRunsArgs, runCloudLogsCli, runCloudRunsCli, runCloudStatusCli,
|
|
30
|
+
type LogsArgs, type RunsArgs,
|
|
31
|
+
} from './cli/cloud-read.js';
|
|
32
|
+
import { transcriptTailSource } from './transcript-tail.js';
|
|
27
33
|
import { checkTypeScriptFlow } from './cli/check-typescript.js';
|
|
28
34
|
import { runCloudCli } from './cli/cloud-run.js';
|
|
29
35
|
import { runCloudSyncCli } from './cli/cloud-sync.js';
|
|
@@ -59,6 +65,7 @@ type CliExitCode = 0 | 1 | 2 | 3;
|
|
|
59
65
|
export type ParsedArgs =
|
|
60
66
|
| { command: 'add'; value: string }
|
|
61
67
|
| ReplayArgs
|
|
68
|
+
| StatusArgs
|
|
62
69
|
| BuildArgs
|
|
63
70
|
| DeployArgs
|
|
64
71
|
| { command: 'serve-webhook'; dataDir: string; port: number; admitted?: readonly string[] }
|
|
@@ -74,6 +81,8 @@ export type ParsedArgs =
|
|
|
74
81
|
| { command: 'run'; bucket: string | undefined; reuseFromRunId: string | undefined; localAgent: boolean; dataDir: string; input: string | undefined; json: boolean; spawn: boolean; noObserverLink: boolean; allowHumanInfluenced: boolean; value: string }
|
|
75
82
|
| { command: 'resume'; localAgent: boolean; dataDir: string; json: boolean; spawn: boolean; noObserverLink: boolean; allowHumanInfluenced: boolean; value: string }
|
|
76
83
|
| { command: 'answer'; dataDir: string; json: boolean; spawn: boolean; note: string | undefined; by: string | undefined; runId: string; waitId: string; answer: boolean }
|
|
84
|
+
| RunsArgs
|
|
85
|
+
| LogsArgs
|
|
77
86
|
| { command: 'observer'; dataDir: string }
|
|
78
87
|
| { command: 'hn-monitor'; sub: 'start'; dataDir: string; specPath: string; pollIntervalMs: number | undefined }
|
|
79
88
|
| { command: 'tick'; sub: 'start'; dataDir: string; specPath: string; scheduleId: string;
|
|
@@ -104,6 +113,10 @@ const USAGE = [
|
|
|
104
113
|
'flows resume [--allow-human-influenced] [--json] [--no-spawn] [--no-observer-link] [--data-dir <dir>] [--local-agent] <run-id>',
|
|
105
114
|
'flows answer [--json] [--no-spawn] [--data-dir <dir>] [--note <text>] [--by <identity>] <run-id> <wait-id> <yes|no>',
|
|
106
115
|
'flows replay [--allow-human-influenced] [--json] [--data-dir <dir>] <run-id> [--at <step-id>]',
|
|
116
|
+
'flows status [--json] [--data-dir <dir>] [--tail <n>] [<run-id>]',
|
|
117
|
+
'flows status --cloud [--json] <run-id>',
|
|
118
|
+
'flows runs [--limit <n>] [--json]',
|
|
119
|
+
'flows logs [--step <name>] [--raw] [--json] <run-id>',
|
|
107
120
|
'flows observer [--data-dir <dir>]',
|
|
108
121
|
'flows hn-monitor start [--data-dir <dir>] [--poll-interval-ms <n>] <spec.json>',
|
|
109
122
|
].join('\n');
|
|
@@ -195,6 +208,17 @@ export async function runCli(
|
|
|
195
208
|
if (parsed.command === 'schedules') return runCloudSchedulesCli(parsed, io);
|
|
196
209
|
if (parsed.command === 'unschedule') return runCloudUnscheduleCli(parsed, io);
|
|
197
210
|
if (parsed.command === 'replay') return replayJournal(parsed, io);
|
|
211
|
+
// Daemon-free like `check`: reads one journal file and nothing else, so it
|
|
212
|
+
// works inside a step of a run whose daemon is gone (kernel/DAEMON-LIFECYCLE.md §4).
|
|
213
|
+
if (parsed.command === 'status') {
|
|
214
|
+
// One verb, two sources. `--cloud` never reaches `runStatus`, so the
|
|
215
|
+
// offline reader stays offline (cli/status.ts).
|
|
216
|
+
return parsed.cloud === true
|
|
217
|
+
? runCloudStatusCli(parsed, io)
|
|
218
|
+
: runStatus(parsed, io, { tails: transcriptTailSource() });
|
|
219
|
+
}
|
|
220
|
+
if (parsed.command === 'runs') return runCloudRunsCli(parsed, io);
|
|
221
|
+
if (parsed.command === 'logs') return runCloudLogsCli(parsed, io);
|
|
198
222
|
if (parsed.command === 'answer') {
|
|
199
223
|
const execution = await answerFlow(parsed.runId, parsed.waitId, parsed.answer, parsed.dataDir, {
|
|
200
224
|
...(parsed.note === undefined ? {} : { note: parsed.note }),
|
|
@@ -498,6 +522,9 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
498
522
|
if (command === undefined || !CLI_VERB_NAMES.has(command)) return undefined;
|
|
499
523
|
if (command === 'add') return args.length === 2 ? { command: 'add', value: args[1]! } : undefined;
|
|
500
524
|
if (command === 'replay') return parseReplayArgs(args.slice(1));
|
|
525
|
+
if (command === 'status') return parseStatusArgs(args.slice(1));
|
|
526
|
+
if (command === 'runs') return parseRunsArgs(args.slice(1));
|
|
527
|
+
if (command === 'logs') return parseLogsArgs(args.slice(1));
|
|
501
528
|
if (command === 'build') return parseBuildArgs(args.slice(1));
|
|
502
529
|
if (command === 'deploy') {
|
|
503
530
|
// The positional decides the form: an authored source deploys a hosted
|
package/src/cloud-http.ts
CHANGED
|
@@ -21,7 +21,26 @@ export interface CloudRefusal {
|
|
|
21
21
|
received?: { packageName?: string; version?: string };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Narrows a `configuration` failure to the thing that was wrong.
|
|
26
|
+
*
|
|
27
|
+
* `configuration` has always covered every local misconfiguration, and its
|
|
28
|
+
* message says which. A caller that must answer with a *code* — the read
|
|
29
|
+
* verbs print `REFUSED [cloud_auth_missing]` and name `agent-relay cloud
|
|
30
|
+
* login` — cannot get that from prose without matching on it. Optional and
|
|
31
|
+
* additive: every existing `catch` on `code === 'configuration'` is unchanged.
|
|
32
|
+
*/
|
|
33
|
+
export type CloudConfigurationReason =
|
|
34
|
+
| 'auth_missing'
|
|
35
|
+
| 'auth_expired'
|
|
36
|
+
| 'url_invalid'
|
|
37
|
+
| 'url_mismatch'
|
|
38
|
+
| 'timeout_invalid';
|
|
39
|
+
|
|
24
40
|
export class CloudFlowError extends Error {
|
|
41
|
+
/** Set only for `configuration`; see {@link CloudConfigurationReason}. */
|
|
42
|
+
reason?: CloudConfigurationReason;
|
|
43
|
+
|
|
25
44
|
constructor(
|
|
26
45
|
readonly code: 'configuration' | 'unsupported_source' | 'invalid_input' | 'invalid_response' | 'http_error'
|
|
27
46
|
| 'transport_error' | 'transient_error' | 'unsupported_storage_backend' | 'sync_too_large' | 'sync_unsupported'
|
|
@@ -35,6 +54,13 @@ export class CloudFlowError extends Error {
|
|
|
35
54
|
}
|
|
36
55
|
}
|
|
37
56
|
|
|
57
|
+
/** A `configuration` refusal that also carries its {@link CloudConfigurationReason}. */
|
|
58
|
+
function configurationError(reason: CloudConfigurationReason, message: string): CloudFlowError {
|
|
59
|
+
const error = new CloudFlowError('configuration', message);
|
|
60
|
+
error.reason = reason;
|
|
61
|
+
return error;
|
|
62
|
+
}
|
|
63
|
+
|
|
38
64
|
/**
|
|
39
65
|
* The `agent-relay cloud login` credential store. Read only when neither the
|
|
40
66
|
* `token` option nor `FLOWS_CLOUD_TOKEN` is set, so an explicit credential
|
|
@@ -79,7 +105,7 @@ export function cloudConnection(options: CloudConnectionOptions): { baseUrl: str
|
|
|
79
105
|
if (login !== undefined) {
|
|
80
106
|
const expiresAt = login.accessTokenExpiresAt === undefined ? Number.NaN : Date.parse(login.accessTokenExpiresAt);
|
|
81
107
|
if (Number.isFinite(expiresAt) && expiresAt <= Date.now()) {
|
|
82
|
-
throw
|
|
108
|
+
throw configurationError('auth_expired',
|
|
83
109
|
'The agent-relay cloud login has expired. Run `agent-relay cloud login` again, or set FLOWS_CLOUD_TOKEN.');
|
|
84
110
|
}
|
|
85
111
|
rawToken = login.accessToken;
|
|
@@ -88,7 +114,7 @@ export function cloudConnection(options: CloudConnectionOptions): { baseUrl: str
|
|
|
88
114
|
}
|
|
89
115
|
const token = rawToken?.trim();
|
|
90
116
|
if (!token || /[\r\n]/u.test(rawToken!) || /^(?:rk|ot)_live_/u.test(token)) {
|
|
91
|
-
throw
|
|
117
|
+
throw configurationError('auth_missing',
|
|
92
118
|
'Set FLOWS_CLOUD_TOKEN to a scoped Cloud API token (workflow:invoke:write and workflow:runs:read), '
|
|
93
119
|
+ 'or sign in with `agent-relay cloud login`.');
|
|
94
120
|
}
|
|
@@ -96,11 +122,11 @@ export function cloudConnection(options: CloudConnectionOptions): { baseUrl: str
|
|
|
96
122
|
try {
|
|
97
123
|
url = new URL(options.apiUrl ?? process.env['FLOWS_CLOUD_URL'] ?? loginApiUrl ?? 'https://agentrelay.com/cloud');
|
|
98
124
|
} catch {
|
|
99
|
-
throw
|
|
125
|
+
throw configurationError('url_invalid', 'FLOWS_CLOUD_URL must be an absolute Cloud application base URL.');
|
|
100
126
|
}
|
|
101
127
|
if (url.protocol !== 'https:'
|
|
102
128
|
|| url.username || url.password || url.search || url.hash || !/^\/[A-Za-z0-9/_-]*$/u.test(url.pathname)) {
|
|
103
|
-
throw
|
|
129
|
+
throw configurationError('url_invalid', 'Cloud URL must use HTTPS and a plain base path.');
|
|
104
130
|
}
|
|
105
131
|
const baseUrl = `${url.origin}${url.pathname.replace(/\/+$/u, '')}`;
|
|
106
132
|
// A login-store token is bound to the deployment that issued it. An explicit
|
|
@@ -113,7 +139,7 @@ export function cloudConnection(options: CloudConnectionOptions): { baseUrl: str
|
|
|
113
139
|
issued = `${login.origin}${login.pathname.replace(/\/+$/u, '')}`;
|
|
114
140
|
} catch { issued = undefined; }
|
|
115
141
|
if (issued !== baseUrl) {
|
|
116
|
-
throw
|
|
142
|
+
throw configurationError('url_mismatch',
|
|
117
143
|
`The agent-relay cloud login was issued for ${loginApiUrl}, not ${baseUrl}. `
|
|
118
144
|
+ 'Set FLOWS_CLOUD_TOKEN for that deployment, or unset FLOWS_CLOUD_URL to use the login.');
|
|
119
145
|
}
|
|
@@ -159,7 +185,7 @@ export async function cloudFetch(
|
|
|
159
185
|
const { baseUrl, token } = cloudConnection(options);
|
|
160
186
|
const timeout = options.requestTimeoutMs ?? 30_000;
|
|
161
187
|
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 2_147_483_647) {
|
|
162
|
-
throw
|
|
188
|
+
throw configurationError('timeout_invalid', 'requestTimeoutMs must be a positive 32-bit integer.');
|
|
163
189
|
}
|
|
164
190
|
const bearer = init.bearerToken ?? token;
|
|
165
191
|
if (/[\r\n]/u.test(bearer) || !bearer.trim()) {
|