@relayflows/sdk 2.0.22 → 2.0.24
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/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 -25
- 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 +14 -0
- package/dist/cli/status.js.map +1 -1
- 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 +68 -3
- package/dist/cli/step-failure.js.map +1 -1
- package/dist/cli-commands.d.ts +29 -1
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +23 -1
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -2
- 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 +16 -1
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +1 -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/preflight.d.ts +13 -2
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +23 -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/spec.d.ts +8 -0
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +29 -50
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker.d.ts +1 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +10 -1
- package/dist/worker.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/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 -25
- package/src/cli/status.ts +20 -0
- package/src/cli/step-failure.ts +68 -3
- package/src/cli-commands.ts +23 -1
- package/src/cli.ts +20 -1
- 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 +16 -0
- package/src/index.ts +10 -1
- package/src/journal-client.ts +12 -2
- package/src/preflight.ts +36 -7
- package/src/protocol.ts +15 -0
- package/src/spec.ts +2 -0
- package/src/worker-cli.ts +28 -47
- package/src/worker.ts +10 -0
- package/src/wrapper-session.ts +159 -24
package/src/cli/run.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { communicationInstruction } from '../communication/spec.js';
|
|
2
|
+
import { checkCommunicationEnvironment, CommunicationEnvironmentError } from '../communication/preflight.js';
|
|
1
3
|
import { parseHumanRecipient } from '../human-to.js';
|
|
2
4
|
import { parseDigestReference } from '../bundle-transport.js';
|
|
3
5
|
import { prepareDigestRun } from './run-digest.js';
|
|
@@ -13,7 +15,7 @@ import { ensureDaemon, type EnsureDaemonOptions } from '../daemon-lifecycle.js';
|
|
|
13
15
|
import { isAuthoredFlowPath } from '../direct-input.js';
|
|
14
16
|
import { daemonRefusal } from './daemon-refusal.js';
|
|
15
17
|
import type { RunFailureKind, RunWarningKind, StepFailedDetails } from '../failure-kinds.js';
|
|
16
|
-
import { inspectionHint, stepFailureDetails } from './step-failure.js';
|
|
18
|
+
import { inspectionHint, renderInspection, renderStepEvidence, stepFailureDetails } from './step-failure.js';
|
|
17
19
|
import { JournalClient, JournalProtocolError } from '../journal-client.js';
|
|
18
20
|
import { attachLocalAgent } from '../local-agent.js';
|
|
19
21
|
import { LlmWorker } from '../llm-worker.js';
|
|
@@ -49,6 +51,8 @@ export interface RunReport {
|
|
|
49
51
|
command: RunCommand;
|
|
50
52
|
path?: string;
|
|
51
53
|
runId?: string;
|
|
54
|
+
/** Authored root to resume/collect when runId identifies a failed child. */
|
|
55
|
+
rootRunId?: string;
|
|
52
56
|
socketPath?: string;
|
|
53
57
|
status?: RunStatus;
|
|
54
58
|
completionReason?: RunCompletionReason;
|
|
@@ -129,16 +133,26 @@ async function executeCheckedFlow(
|
|
|
129
133
|
// Carry the preflight's diagnostics as a RunReport from here on, so the
|
|
130
134
|
// attach step has one accumulator to append to (see `connect`).
|
|
131
135
|
const base = fromCheckReport('run', checked.report);
|
|
136
|
+
if (options.localAgent) {
|
|
137
|
+
try { checkCommunicationEnvironment(checked.flow!); }
|
|
138
|
+
catch (error) { return { exitCode: 2, report: { ...base, diagnostics: [...base.diagnostics,
|
|
139
|
+
{ severity: 'refusal', kind: 'probe_failed', message: error instanceof Error ? error.message : 'Communication environment could not be checked.' }] } }; }
|
|
140
|
+
}
|
|
132
141
|
const client = new JournalClient(socketPath);
|
|
133
142
|
const connected = await connect(client, 'run', dataDir, base, options);
|
|
134
143
|
if (connected !== undefined) return connected;
|
|
135
144
|
|
|
145
|
+
let communicationWorkers: Awaited<ReturnType<typeof import('../communication/local.js').attachCommunicationWorkers>> | undefined;
|
|
136
146
|
let localAgent: Awaited<ReturnType<typeof attachLocalAgent>> | undefined;
|
|
137
147
|
try {
|
|
138
148
|
const spec = toKernelSpec(checked.flow!);
|
|
139
149
|
// Use the checked CLI/model and declared surfaces unchanged. The worker
|
|
140
150
|
// advertises its existing pins; the daemon still owns surface matching.
|
|
141
151
|
if (options.localAgent) localAgent = await attachLocalAgent(client, dataDir, options.onPtyReady);
|
|
152
|
+
if (options.localAgent && spec.steps.some(step => step.type === 'agent' && communicationInstruction(step.instruction))) {
|
|
153
|
+
const { attachCommunicationWorkers } = await import('../communication/local.js');
|
|
154
|
+
communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir);
|
|
155
|
+
}
|
|
142
156
|
const outcome = await client.runStart(spec, options.reuseFromRunId);
|
|
143
157
|
const execution = await classifyOutcome(client, 'run', outcome, base, socketPath, { ...options, dataDir });
|
|
144
158
|
if (options.reuseFromRunId !== undefined) {
|
|
@@ -146,6 +160,8 @@ async function executeCheckedFlow(
|
|
|
146
160
|
}
|
|
147
161
|
return execution;
|
|
148
162
|
} catch (error) {
|
|
163
|
+
if (error instanceof CommunicationEnvironmentError) return { exitCode: 2, report: { ...base,
|
|
164
|
+
diagnostics: [...base.diagnostics, { severity: 'refusal', kind: 'probe_failed', message: error.message }] } };
|
|
149
165
|
if (error instanceof JournalProtocolError && (
|
|
150
166
|
error.code === 'reuse_spec_mismatch' || error.code === 'reuse_run_not_found'
|
|
151
167
|
|| error.code === 'reuse_journal_read_failed'
|
|
@@ -155,9 +171,9 @@ async function executeCheckedFlow(
|
|
|
155
171
|
diagnostics: [...base.diagnostics, { severity: failed ? 'failure' : 'refusal',
|
|
156
172
|
kind: error.code, message: error.message }] } };
|
|
157
173
|
}
|
|
158
|
-
return protocolFailure('run', base, socketPath, localAgent?.failure ?? error);
|
|
174
|
+
return protocolFailure('run', base, socketPath, communicationWorkers?.failure ?? localAgent?.failure ?? error);
|
|
159
175
|
} finally {
|
|
160
|
-
try { await localAgent?.close(); } finally { client.close(); }
|
|
176
|
+
try { await communicationWorkers?.close(); } finally { try { await localAgent?.close(); } finally { client.close(); } }
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
179
|
|
|
@@ -172,6 +188,7 @@ export async function resumeFlow(
|
|
|
172
188
|
const connected = await connect(client, 'resume', dataDir, base, options);
|
|
173
189
|
if (connected !== undefined) return connected;
|
|
174
190
|
|
|
191
|
+
let communicationWorkers: Awaited<ReturnType<typeof import('../communication/local.js').attachCommunicationWorkers>> | undefined;
|
|
175
192
|
let authoredAgent: Awaited<ReturnType<typeof attachLocalAgent>> | undefined;
|
|
176
193
|
let authoredLlm: LlmWorker | undefined;
|
|
177
194
|
let authoredLlmClient: JournalClient | undefined;
|
|
@@ -206,12 +223,23 @@ export async function resumeFlow(
|
|
|
206
223
|
// slack effect resume plus every other provider from N's codegen. The
|
|
207
224
|
// second call the earlier rebase left is a stale reference from before
|
|
208
225
|
// the helper fanout renamed the API.
|
|
226
|
+
if (options.localAgent) {
|
|
227
|
+
authoredAgent = await attachLocalAgent(client, dataDir, options.onPtyReady);
|
|
228
|
+
const entries = (await client.journalRead(runId, 1)).entries as Array<{ entry_type: string; payload?: { spec?: import('../spec.js').KernelRunSpec } }>;
|
|
229
|
+
const spec = entries.find(entry => entry.entry_type === 'run.spawned')?.payload?.spec;
|
|
230
|
+
if (spec?.steps.some(step => step.type === 'agent' && communicationInstruction(step.instruction))) {
|
|
231
|
+
const { attachCommunicationWorkers } = await import('../communication/local.js');
|
|
232
|
+
communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
209
235
|
let outcome = await client.runResume(runId, options.allowHumanInfluenced);
|
|
210
236
|
if (await resumeHelperEffect(client, runId, dataDir)) {
|
|
211
237
|
outcome = await client.runResume(runId, options.allowHumanInfluenced);
|
|
212
238
|
}
|
|
213
239
|
return await classifyOutcome(client, 'resume', outcome, base, socketPath, { ...options, dataDir });
|
|
214
240
|
} catch (error) {
|
|
241
|
+
if (error instanceof CommunicationEnvironmentError) return { exitCode: 2, report: { ...base, runId, socketPath,
|
|
242
|
+
diagnostics: [...base.diagnostics, { severity: 'refusal', kind: 'probe_failed', message: error.message }] } };
|
|
215
243
|
if (error instanceof JournalProtocolError && error.code === 'human_influenced_run') {
|
|
216
244
|
return { exitCode: 2, report: { ...base, runId, socketPath,
|
|
217
245
|
diagnostics: [{ severity: 'refusal', kind: 'human_influenced_run', message: error.message.replace(/^human_influenced_run: /, '') }] } };
|
|
@@ -255,6 +283,7 @@ export async function resumeFlow(
|
|
|
255
283
|
};
|
|
256
284
|
} finally {
|
|
257
285
|
try {
|
|
286
|
+
await communicationWorkers?.close();
|
|
258
287
|
await authoredLlm?.close();
|
|
259
288
|
authoredLlmClient?.close();
|
|
260
289
|
await authoredAgent?.close();
|
|
@@ -287,16 +316,23 @@ export function authoredStepFailure(
|
|
|
287
316
|
// The failing step runs as its own kernel run, so the error's run id is the
|
|
288
317
|
// one whose journal holds the evidence. The resume target is the fallback.
|
|
289
318
|
const runId = error.runId ?? fallbackRunId;
|
|
319
|
+
const rootRunId = error.rootRunId ?? fallbackRunId;
|
|
290
320
|
return {
|
|
291
321
|
exitCode: 1,
|
|
292
322
|
report: {
|
|
293
323
|
...fromBase(command, base),
|
|
294
324
|
ok: false,
|
|
295
325
|
...(runId === undefined ? {} : { runId }),
|
|
326
|
+
...(rootRunId === undefined ? {} : { rootRunId }),
|
|
296
327
|
socketPath,
|
|
297
328
|
status: 'failed',
|
|
298
329
|
completionReason: 'step_failed',
|
|
299
330
|
diagnostics: [...base.diagnostics, {
|
|
331
|
+
// The structured evidence the failing step left in its own journal.
|
|
332
|
+
// `RunDiagnostic extends StepFailedDetails`, so `--json` gains the
|
|
333
|
+
// fields it already declares instead of leaving them to be re-parsed
|
|
334
|
+
// out of the rendered message.
|
|
335
|
+
...error.details,
|
|
300
336
|
severity: 'failure',
|
|
301
337
|
// A predicate gate that judged false is a run failure with its own
|
|
302
338
|
// name, so the report says which kind of check the body did not pass.
|
|
@@ -522,7 +558,9 @@ export async function classifyOutcome(
|
|
|
522
558
|
let unclassifiedPolls = 0;
|
|
523
559
|
while (current.status === 'parked') {
|
|
524
560
|
const inspection = await inspectOutOfBandStep(client, current.run_id);
|
|
525
|
-
|
|
561
|
+
// A runnable sibling may only be waiting for capacity held by a live
|
|
562
|
+
// attempt. Keep driving until that lease releases before declaring a park.
|
|
563
|
+
if (inspection?.parkedStep !== undefined && (inspection.needsHuman || inspection.runningStep === undefined)) {
|
|
526
564
|
parkedStep = inspection.parkedStep;
|
|
527
565
|
needsHuman = inspection.needsHuman;
|
|
528
566
|
break;
|
|
@@ -603,8 +641,7 @@ export async function classifyOutcome(
|
|
|
603
641
|
// must still end with somewhere to go rather than with a dead end.
|
|
604
642
|
const where = inspectionHint(current.run_id, details?.stepId, options.dataDir);
|
|
605
643
|
Object.assign(diagnostic, where);
|
|
606
|
-
diagnostic.message +=
|
|
607
|
-
+ (where.journalPath === undefined ? '' : `\nJournal: ${where.journalPath}`);
|
|
644
|
+
diagnostic.message += renderInspection(where);
|
|
608
645
|
}
|
|
609
646
|
return {
|
|
610
647
|
exitCode: 1,
|
|
@@ -793,25 +830,6 @@ function errorMessage(error: unknown): string {
|
|
|
793
830
|
return error instanceof Error ? error.message : 'unknown protocol error';
|
|
794
831
|
}
|
|
795
832
|
|
|
796
|
-
/**
|
|
797
|
-
* The evidence half of a `step_failed` diagnostic; `inspectionHint` adds the
|
|
798
|
-
* rest. Each field is printed only when the journal actually carried it — an
|
|
799
|
-
* agent step has no exit code to report, and inventing `exit=undefined` (which
|
|
800
|
-
* is what the deterministic-only version printed for one) is worse than
|
|
801
|
-
* silence on that field.
|
|
802
|
-
*/
|
|
803
|
-
function renderStepEvidence(details: StepFailedDetails): string {
|
|
804
|
-
return ` Step ${JSON.stringify(details.stepId)}`
|
|
805
|
-
+ (details.stepType === undefined ? '' : ` (${details.stepType})`)
|
|
806
|
-
+ ` completionReason: ${details.completionReason}`
|
|
807
|
-
+ (details.exitCode === undefined ? '' : ` exit=${details.exitCode}`)
|
|
808
|
-
+ '.'
|
|
809
|
-
+ (details.detail === undefined ? '' : `\nDetail: ${details.detail}`)
|
|
810
|
-
+ (details.stdoutTail ? `\nStdout (last 1,024 bytes):\n${details.stdoutTail}` : '')
|
|
811
|
-
+ (details.stderrTail ? `\nStderr (last 1,024 bytes):\n${details.stderrTail}` : '')
|
|
812
|
-
+ (details.transcriptPath === undefined ? '' : `\nTranscript: ${details.transcriptPath}`);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
833
|
function throwIfCanceled(signal: AbortSignal | undefined, stepId: string): void {
|
|
816
834
|
if (signal?.aborted === true) throw new Error(`waiting for running step "${stepId}" was canceled`);
|
|
817
835
|
}
|
package/src/cli/status.ts
CHANGED
|
@@ -22,6 +22,15 @@ export interface StatusArgs {
|
|
|
22
22
|
dataDir?: string;
|
|
23
23
|
tail?: number;
|
|
24
24
|
runId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* `--cloud`: read the run from the Cloud API instead of a local journal.
|
|
27
|
+
*
|
|
28
|
+
* Parsed here because the verb is one verb — a reader should not have to
|
|
29
|
+
* know that a hosted run is a different command — but never *handled* here:
|
|
30
|
+
* `runCli` routes a `--cloud` invocation to `cli/cloud-read.ts`, so this
|
|
31
|
+
* module keeps its property of opening one file and no socket.
|
|
32
|
+
*/
|
|
33
|
+
cloud?: true;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export const DEFAULT_TAIL_LINES = 20;
|
|
@@ -53,6 +62,7 @@ export interface StatusOptions {
|
|
|
53
62
|
|
|
54
63
|
export function parseStatusArgs(args: readonly string[]): StatusArgs | undefined {
|
|
55
64
|
let json = false;
|
|
65
|
+
let cloud = false;
|
|
56
66
|
let dataDir: string | undefined;
|
|
57
67
|
let tail: number | undefined;
|
|
58
68
|
const positionals: string[] = [];
|
|
@@ -61,6 +71,9 @@ export function parseStatusArgs(args: readonly string[]): StatusArgs | undefined
|
|
|
61
71
|
if (argument === '--json') {
|
|
62
72
|
if (json) return undefined;
|
|
63
73
|
json = true;
|
|
74
|
+
} else if (argument === '--cloud') {
|
|
75
|
+
if (cloud) return undefined;
|
|
76
|
+
cloud = true;
|
|
64
77
|
} else if (argument === '--data-dir' || argument === '--tail') {
|
|
65
78
|
const value = args[++index];
|
|
66
79
|
if (value === undefined || value.length === 0 || value.startsWith('-')) return undefined;
|
|
@@ -78,8 +91,15 @@ export function parseStatusArgs(args: readonly string[]): StatusArgs | undefined
|
|
|
78
91
|
}
|
|
79
92
|
}
|
|
80
93
|
if (positionals.length > 1) return undefined;
|
|
94
|
+
// `--data-dir` and `--tail` name things on this filesystem: a data directory
|
|
95
|
+
// to read a journal out of, and per-attempt tail files beside it. A hosted
|
|
96
|
+
// run has neither, so pairing them with `--cloud` is not a narrower request
|
|
97
|
+
// but a contradiction, and is refused as an invocation rather than silently
|
|
98
|
+
// ignored. A hosted run id is mandatory: there is no ambient one.
|
|
99
|
+
if (cloud && (dataDir !== undefined || tail !== undefined || positionals.length === 0)) return undefined;
|
|
81
100
|
return {
|
|
82
101
|
command: 'status', json,
|
|
102
|
+
...(cloud ? { cloud: true as const } : {}),
|
|
83
103
|
...(dataDir === undefined ? {} : { dataDir }),
|
|
84
104
|
...(tail === undefined ? {} : { tail }),
|
|
85
105
|
...(positionals[0] === undefined ? {} : { runId: positionals[0] }),
|
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
|
*
|
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',
|
|
@@ -267,12 +288,13 @@ export const CLI_VERBS = [
|
|
|
267
288
|
},
|
|
268
289
|
{
|
|
269
290
|
name: 'status',
|
|
270
|
-
description: 'Show a run’s steps, attempts, leases and last verdicts from its journal
|
|
291
|
+
description: 'Show a run’s steps, attempts, leases and last verdicts from its journal, or from Cloud with --cloud',
|
|
271
292
|
args: [{ name: 'run-id', description: 'Run to inspect; defaults to RELAYFLOW_RUN_ID inside a step', required: false }],
|
|
272
293
|
options: [
|
|
273
294
|
JSON_OPTION,
|
|
274
295
|
DATA_DIR_OPTION,
|
|
275
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' },
|
|
276
298
|
],
|
|
277
299
|
variants: ['status'],
|
|
278
300
|
},
|
package/src/cli.ts
CHANGED
|
@@ -25,6 +25,10 @@ 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
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';
|
|
28
32
|
import { transcriptTailSource } from './transcript-tail.js';
|
|
29
33
|
import { checkTypeScriptFlow } from './cli/check-typescript.js';
|
|
30
34
|
import { runCloudCli } from './cli/cloud-run.js';
|
|
@@ -77,6 +81,8 @@ export type ParsedArgs =
|
|
|
77
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 }
|
|
78
82
|
| { command: 'resume'; localAgent: boolean; dataDir: string; json: boolean; spawn: boolean; noObserverLink: boolean; allowHumanInfluenced: boolean; value: string }
|
|
79
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
|
|
80
86
|
| { command: 'observer'; dataDir: string }
|
|
81
87
|
| { command: 'hn-monitor'; sub: 'start'; dataDir: string; specPath: string; pollIntervalMs: number | undefined }
|
|
82
88
|
| { command: 'tick'; sub: 'start'; dataDir: string; specPath: string; scheduleId: string;
|
|
@@ -108,6 +114,9 @@ const USAGE = [
|
|
|
108
114
|
'flows answer [--json] [--no-spawn] [--data-dir <dir>] [--note <text>] [--by <identity>] <run-id> <wait-id> <yes|no>',
|
|
109
115
|
'flows replay [--allow-human-influenced] [--json] [--data-dir <dir>] <run-id> [--at <step-id>]',
|
|
110
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>',
|
|
111
120
|
'flows observer [--data-dir <dir>]',
|
|
112
121
|
'flows hn-monitor start [--data-dir <dir>] [--poll-interval-ms <n>] <spec.json>',
|
|
113
122
|
].join('\n');
|
|
@@ -201,7 +210,15 @@ export async function runCli(
|
|
|
201
210
|
if (parsed.command === 'replay') return replayJournal(parsed, io);
|
|
202
211
|
// Daemon-free like `check`: reads one journal file and nothing else, so it
|
|
203
212
|
// works inside a step of a run whose daemon is gone (kernel/DAEMON-LIFECYCLE.md §4).
|
|
204
|
-
if (parsed.command === 'status')
|
|
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);
|
|
205
222
|
if (parsed.command === 'answer') {
|
|
206
223
|
const execution = await answerFlow(parsed.runId, parsed.waitId, parsed.answer, parsed.dataDir, {
|
|
207
224
|
...(parsed.note === undefined ? {} : { note: parsed.note }),
|
|
@@ -506,6 +523,8 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
506
523
|
if (command === 'add') return args.length === 2 ? { command: 'add', value: args[1]! } : undefined;
|
|
507
524
|
if (command === 'replay') return parseReplayArgs(args.slice(1));
|
|
508
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));
|
|
509
528
|
if (command === 'build') return parseBuildArgs(args.slice(1));
|
|
510
529
|
if (command === 'deploy') {
|
|
511
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()) {
|