@relayflows/sdk 2.0.34 → 2.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/base.d.ts +16 -0
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +15 -0
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +4 -0
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/usage-limit.d.ts +2 -0
- package/dist/adapters/usage-limit.d.ts.map +1 -0
- package/dist/adapters/usage-limit.js +17 -0
- package/dist/adapters/usage-limit.js.map +1 -0
- package/dist/authored-activity.d.ts +17 -0
- package/dist/authored-activity.d.ts.map +1 -0
- package/dist/authored-activity.js +218 -0
- package/dist/authored-activity.js.map +1 -0
- package/dist/authored-child-run.d.ts +34 -0
- package/dist/authored-child-run.d.ts.map +1 -0
- package/dist/authored-child-run.js +128 -0
- package/dist/authored-child-run.js.map +1 -0
- package/dist/authored-dispatch.d.ts +24 -0
- package/dist/authored-dispatch.d.ts.map +1 -0
- package/dist/authored-dispatch.js +81 -0
- package/dist/authored-dispatch.js.map +1 -0
- package/dist/authored-flow-error.d.ts +32 -3
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +3 -1
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts +26 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +122 -16
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-flow-loader.d.ts.map +1 -1
- package/dist/authored-flow-loader.js +9 -0
- package/dist/authored-flow-loader.js.map +1 -1
- package/dist/authored-mcp.js +2 -2
- package/dist/authored-mcp.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.map +1 -1
- package/dist/authored-node-runner.js +40 -8
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-plugin-effect.js +2 -2
- package/dist/authored-plugin-effect.js.map +1 -1
- package/dist/authored-root.d.ts +10 -7
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +20 -0
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-step-output.d.ts +24 -8
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +81 -15
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-subscription-park.d.ts +5 -0
- package/dist/authored-subscription-park.d.ts.map +1 -0
- package/dist/authored-subscription-park.js +47 -0
- package/dist/authored-subscription-park.js.map +1 -0
- package/dist/authored-worker-step.d.ts +3 -1
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +25 -9
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check-activities.d.ts +8 -0
- package/dist/cli/check-activities.d.ts.map +1 -0
- package/dist/cli/check-activities.js +155 -0
- package/dist/cli/check-activities.js.map +1 -0
- package/dist/cli/cli-probe.d.ts.map +1 -1
- package/dist/cli/cli-probe.js +14 -2
- package/dist/cli/cli-probe.js.map +1 -1
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +10 -2
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/run.d.ts +23 -5
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +32 -5
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/subscription-report.d.ts +5 -0
- package/dist/cli/subscription-report.d.ts.map +1 -0
- package/dist/cli/subscription-report.js +48 -0
- package/dist/cli/subscription-report.js.map +1 -0
- package/dist/cli-adapter.d.ts +4 -2
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +4 -0
- package/dist/cli-adapter.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -5
- package/dist/cli.js.map +1 -1
- package/dist/cloud-mirror-step.d.ts +2 -0
- package/dist/cloud-mirror-step.d.ts.map +1 -1
- package/dist/cloud-mirror-step.js +9 -1
- package/dist/cloud-mirror-step.js.map +1 -1
- package/dist/cloud-mirror.d.ts.map +1 -1
- package/dist/cloud-mirror.js +17 -2
- package/dist/cloud-mirror.js.map +1 -1
- package/dist/failure-kinds.d.ts +2 -2
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +2 -0
- package/dist/failure-kinds.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/journal-client.d.ts +17 -2
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +33 -2
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-reader.d.ts.map +1 -1
- package/dist/journal-reader.js +2 -0
- package/dist/journal-reader.js.map +1 -1
- package/dist/local-agent.d.ts +4 -1
- package/dist/local-agent.d.ts.map +1 -1
- package/dist/local-agent.js +14 -4
- package/dist/local-agent.js.map +1 -1
- package/dist/preflight.d.ts +6 -0
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +36 -10
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +166 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/step-settling.d.ts +11 -0
- package/dist/step-settling.d.ts.map +1 -0
- package/dist/step-settling.js +22 -0
- package/dist/step-settling.js.map +1 -0
- package/dist/worker-slots.d.ts +15 -0
- package/dist/worker-slots.d.ts.map +1 -1
- package/dist/worker-slots.js +40 -3
- package/dist/worker-slots.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/base.ts +19 -0
- package/src/adapters/claude.ts +15 -0
- package/src/adapters/codex.ts +6 -0
- package/src/adapters/usage-limit.ts +17 -0
- package/src/authored-activity.ts +251 -0
- package/src/authored-child-run.ts +138 -0
- package/src/authored-dispatch.ts +113 -0
- package/src/authored-flow-error.ts +36 -0
- package/src/authored-flow-executor.ts +166 -15
- package/src/authored-flow-loader.ts +12 -0
- package/src/authored-mcp.ts +2 -2
- package/src/authored-node-entry.ts +4 -1
- package/src/authored-node-runner.ts +41 -8
- package/src/authored-plugin-effect.ts +2 -2
- package/src/authored-root.ts +28 -4
- package/src/authored-step-output.ts +88 -15
- package/src/authored-subscription-park.ts +46 -0
- package/src/authored-worker-step.ts +24 -8
- package/src/cli/check-activities.ts +144 -0
- package/src/cli/cli-probe.ts +15 -3
- package/src/cli/direct-run.ts +9 -0
- package/src/cli/run.ts +56 -9
- package/src/cli/subscription-report.ts +51 -0
- package/src/cli-adapter.ts +8 -2
- package/src/cli.ts +16 -6
- package/src/cloud-mirror-step.ts +16 -2
- package/src/cloud-mirror.ts +15 -2
- package/src/failure-kinds.ts +2 -0
- package/src/index.ts +6 -0
- package/src/journal-client.ts +47 -3
- package/src/journal-reader.ts +2 -0
- package/src/local-agent.ts +17 -3
- package/src/preflight.ts +46 -10
- package/src/protocol.ts +129 -0
- package/src/step-settling.ts +19 -0
- package/src/worker-slots.ts +63 -4
package/src/cli/run.ts
CHANGED
|
@@ -23,13 +23,15 @@ import {
|
|
|
23
23
|
import type { ParkCause, RunFailureKind, RunWarningKind, StepFailedDetails } from '../failure-kinds.js';
|
|
24
24
|
import { inspectionHint, renderInspection, renderStepEvidence, stepFailureDetails } from './step-failure.js';
|
|
25
25
|
import { JournalClient, JournalProtocolError } from '../journal-client.js';
|
|
26
|
-
import { attachLocalAgent } from '../local-agent.js';
|
|
26
|
+
import { attachLocalAgent, declaredLocalAgentStreams } from '../local-agent.js';
|
|
27
27
|
import { LlmWorker } from '../llm-worker.js';
|
|
28
28
|
import { readAuthoredRootMetadata, resumeDurableAuthoredFlow, type AuthoredRootMetadata } from '../authored-root.js';
|
|
29
|
+
import type { AuthoredFlowSuspendedResult } from '../authored-flow-executor.js';
|
|
29
30
|
import type {
|
|
30
31
|
RunCompletionReason,
|
|
31
32
|
RunOutcome,
|
|
32
33
|
RunStatus,
|
|
34
|
+
SubscriptionSnapshot,
|
|
33
35
|
} from '../protocol.js';
|
|
34
36
|
import type { StepType } from '../spec.js';
|
|
35
37
|
import {
|
|
@@ -42,7 +44,7 @@ import {
|
|
|
42
44
|
type CheckReport,
|
|
43
45
|
} from './check.js';
|
|
44
46
|
|
|
45
|
-
export type RunExitCode = 0 | 1 | 2 | 3;
|
|
47
|
+
export type RunExitCode = 0 | 1 | 2 | 3 | 4;
|
|
46
48
|
export type RunCommand = 'run' | 'resume' | 'answer';
|
|
47
49
|
|
|
48
50
|
export interface ParkedStep {
|
|
@@ -52,7 +54,7 @@ export interface ParkedStep {
|
|
|
52
54
|
|
|
53
55
|
export interface RunDiagnostic extends StepFailedDetails {
|
|
54
56
|
severity: 'refusal' | 'failure' | 'parked' | 'warning' | 'declined';
|
|
55
|
-
kind: RunFailureKind | RunWarningKind | RunCompletionReason;
|
|
57
|
+
kind: RunFailureKind | RunWarningKind | RunCompletionReason | 'subscription_suspended';
|
|
56
58
|
message: string;
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -61,10 +63,14 @@ export interface RunReport {
|
|
|
61
63
|
command: RunCommand;
|
|
62
64
|
path?: string;
|
|
63
65
|
runId?: string;
|
|
64
|
-
/** Authored root
|
|
66
|
+
/** Authored root owning subscriptions when runId names a failed child. */
|
|
65
67
|
rootRunId?: string;
|
|
66
68
|
socketPath?: string;
|
|
67
|
-
status?: RunStatus;
|
|
69
|
+
status?: RunStatus | 'suspended';
|
|
70
|
+
/** Cloud consumes this exact durable boundary before launching a resume. */
|
|
71
|
+
suspension?: AuthoredFlowSuspendedResult['suspension'] & { settleMs?: number; idleAtMs?: number };
|
|
72
|
+
/** Durable subscription projection for Cloud routing and cleanup. */
|
|
73
|
+
subscriptions?: SubscriptionSnapshot[];
|
|
68
74
|
completionReason?: RunCompletionReason;
|
|
69
75
|
/**
|
|
70
76
|
* What the body passed to `done(reason, { detail })`, normalized: redacted,
|
|
@@ -103,6 +109,26 @@ export interface RunExecution {
|
|
|
103
109
|
report: RunReport;
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
export function suspendedExecution(
|
|
113
|
+
command: RunCommand,
|
|
114
|
+
base: CheckReport | RunReport,
|
|
115
|
+
socketPath: string,
|
|
116
|
+
runId: string,
|
|
117
|
+
result: AuthoredFlowSuspendedResult & { readonly rootRunId: string },
|
|
118
|
+
): RunExecution {
|
|
119
|
+
return {
|
|
120
|
+
exitCode: 4,
|
|
121
|
+
report: {
|
|
122
|
+
...fromBase(command, base), ok: false, runId, socketPath, status: 'suspended',
|
|
123
|
+
suspension: result.suspension, completedSteps: result.journalSteps.length,
|
|
124
|
+
diagnostics: [...base.diagnostics, {
|
|
125
|
+
severity: 'warning', kind: 'subscription_suspended',
|
|
126
|
+
message: `Flow "${result.name}" suspended for ${result.suspension.kind}.`,
|
|
127
|
+
}],
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
106
132
|
export interface RunProgress {
|
|
107
133
|
runId: string;
|
|
108
134
|
stepId: string;
|
|
@@ -122,6 +148,8 @@ export interface RunLifecycleOptions {
|
|
|
122
148
|
onJournalEntry?: (entry: JournalEvent) => void;
|
|
123
149
|
/** An authored root was admitted: its id is known before its body runs. */
|
|
124
150
|
onRunStarted?: (run: { runId: string; flow: string; resumed?: boolean }) => void;
|
|
151
|
+
/** A declarative root returned its admission receipt, even if no watched entry arrived. */
|
|
152
|
+
onRunReceipt?: (run: { runId: string; flow: string }) => void;
|
|
125
153
|
localAgent?: boolean;
|
|
126
154
|
/** `--agent-capacity`: the local workers' concurrency; the default is `DEFAULT_LOCAL_AGENT_CAPACITY`. */
|
|
127
155
|
agentCapacity?: number;
|
|
@@ -183,7 +211,10 @@ async function executeCheckedFlow(
|
|
|
183
211
|
// Use the checked CLI/model and declared surfaces unchanged. The worker
|
|
184
212
|
// advertises its existing pins; the daemon still owns surface matching.
|
|
185
213
|
if (options.localAgent) {
|
|
186
|
-
localAgent = await attachLocalAgent(
|
|
214
|
+
localAgent = await attachLocalAgent(
|
|
215
|
+
client, dataDir, options.onPtyReady, undefined, options.agentCapacity,
|
|
216
|
+
undefined, declaredLocalAgentStreams(spec),
|
|
217
|
+
);
|
|
187
218
|
}
|
|
188
219
|
if (options.localAgent && spec.steps.some(step => step.type === 'agent' && communicationInstruction(step.instruction))) {
|
|
189
220
|
const { attachCommunicationWorkers } = await import('../communication/local.js');
|
|
@@ -191,6 +222,14 @@ async function executeCheckedFlow(
|
|
|
191
222
|
}
|
|
192
223
|
if (options.onJournalEntry !== undefined) client.on('entry', options.onJournalEntry);
|
|
193
224
|
const outcome = await startWatched(client, spec, options);
|
|
225
|
+
// `run.start { watch: true }` is an event stream, not the source of the
|
|
226
|
+
// root identity. A short deterministic run can finish before its first
|
|
227
|
+
// watched entry is delivered (and an older daemon may refuse `watch`), so
|
|
228
|
+
// receipt consumers such as the Cloud mirror need the admitted id too.
|
|
229
|
+
// This stays separate from `onRunStarted`: opening the observer without a
|
|
230
|
+
// streamed journal would manufacture an empty projection and suppress its
|
|
231
|
+
// truthful "not projected" diagnostic.
|
|
232
|
+
options.onRunReceipt?.({ runId: outcome.run_id, flow: spec.name ?? 'flow' });
|
|
194
233
|
const execution = await classifyOutcome(client, 'run', outcome, base, socketPath, { ...options, dataDir });
|
|
195
234
|
if (options.reuseFromRunId !== undefined) {
|
|
196
235
|
execution.report.reuse = await reuseSummary(client, outcome.run_id, options.reuseFromRunId);
|
|
@@ -261,6 +300,7 @@ export async function resumeFlow(
|
|
|
261
300
|
try {
|
|
262
301
|
authoredRoot = await readAuthoredRootMetadata(client, runId);
|
|
263
302
|
if (authoredRoot !== undefined) {
|
|
303
|
+
base.rootRunId = runId;
|
|
264
304
|
// Both worker-surface mismatches are refusals, not protocol failures.
|
|
265
305
|
// They used to throw bare `Error`s, which landed on `protocol_error`
|
|
266
306
|
// ("RUN <id> unknown") and told nobody what to do instead; and the
|
|
@@ -301,6 +341,9 @@ export async function resumeFlow(
|
|
|
301
341
|
lifecycle: options,
|
|
302
342
|
});
|
|
303
343
|
if (result === undefined) throw new Error('authored root disappeared during resume');
|
|
344
|
+
if (result.state === 'suspended') {
|
|
345
|
+
return suspendedExecution('resume', base, socketPath, runId, result);
|
|
346
|
+
}
|
|
304
347
|
return authoredCompletion('resume', base, socketPath, result, runId);
|
|
305
348
|
}
|
|
306
349
|
// resumeHelperEffect subsumes the old resumeSlackEffect: it handles the
|
|
@@ -308,9 +351,13 @@ export async function resumeFlow(
|
|
|
308
351
|
// second call the earlier rebase left is a stale reference from before
|
|
309
352
|
// the helper fanout renamed the API.
|
|
310
353
|
if (options.localAgent) {
|
|
311
|
-
authoredAgent = await attachLocalAgent(client, dataDir, options.onPtyReady, undefined, options.agentCapacity);
|
|
312
354
|
const entries = (await client.journalRead(runId, 1)).entries as Array<{ entry_type: string; payload?: { spec?: import('../spec.js').KernelRunSpec } }>;
|
|
313
355
|
const spec = entries.find(entry => entry.entry_type === 'run.spawned')?.payload?.spec;
|
|
356
|
+
if (!spec) throw new Error('Cannot attach a local worker: the journaled run spec is missing');
|
|
357
|
+
authoredAgent = await attachLocalAgent(
|
|
358
|
+
client, dataDir, options.onPtyReady, undefined, options.agentCapacity,
|
|
359
|
+
undefined, declaredLocalAgentStreams(spec),
|
|
360
|
+
);
|
|
314
361
|
if (spec?.steps.some(step => step.type === 'agent' && communicationInstruction(step.instruction))) {
|
|
315
362
|
const { attachCommunicationWorkers } = await import('../communication/local.js');
|
|
316
363
|
communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir);
|
|
@@ -945,7 +992,7 @@ async function inspectOutOfBandStep(
|
|
|
945
992
|
}
|
|
946
993
|
|
|
947
994
|
// Match kernel/relayflowd/src/server/client.rs: allow the lease sweep to dispatch a retry.
|
|
948
|
-
const LEASE_SWEEP_GRACE_MS = 5_000;
|
|
995
|
+
export const LEASE_SWEEP_GRACE_MS = 5_000;
|
|
949
996
|
|
|
950
997
|
async function waitForRunningStep(
|
|
951
998
|
client: JournalClient,
|
|
@@ -1046,7 +1093,7 @@ function throwIfCanceled(signal: AbortSignal | undefined, stepId: string): void
|
|
|
1046
1093
|
if (signal?.aborted === true) throw new Error(`waiting for running step "${stepId}" was canceled`);
|
|
1047
1094
|
}
|
|
1048
1095
|
|
|
1049
|
-
function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
1096
|
+
export function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
1050
1097
|
return new Promise((resolveDelay, rejectDelay) => {
|
|
1051
1098
|
const finish = (): void => {
|
|
1052
1099
|
signal?.removeEventListener('abort', cancel);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { JournalClient } from '../journal-client.js';
|
|
2
|
+
import type { SubscriptionInspectResult } from '../protocol.js';
|
|
3
|
+
import type { RunExecution } from './run.js';
|
|
4
|
+
|
|
5
|
+
/** One JSON emission boundary covers authored/declarative run and resume outcomes. */
|
|
6
|
+
export async function withSubscriptionMetadata(
|
|
7
|
+
execution: RunExecution,
|
|
8
|
+
inspect: (socket: string, runId: string) => Promise<SubscriptionInspectResult> = inspectSubscriptions,
|
|
9
|
+
): Promise<RunExecution> {
|
|
10
|
+
const { report } = execution;
|
|
11
|
+
const rootRunId = report.rootRunId ?? report.runId;
|
|
12
|
+
if (rootRunId === undefined || execution.exitCode === 2) {
|
|
13
|
+
return { ...execution, report: { ...report, subscriptions: [] } };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
if (report.socketPath === undefined) throw new Error('run report has no daemon socket');
|
|
17
|
+
const { subscriptions } = await inspect(report.socketPath, rootRunId);
|
|
18
|
+
let suspension = report.suspension;
|
|
19
|
+
if (suspension?.kind === 'event_wait') {
|
|
20
|
+
const state = subscriptions.find(item => item.subscriptionId === suspension!.subscriptionId);
|
|
21
|
+
if (state?.state !== 'active' || !Number.isSafeInteger(state.idleAtMs)
|
|
22
|
+
|| !Number.isSafeInteger(state.settleMs) || !Number.isSafeInteger(state.deadlineAtMs)) {
|
|
23
|
+
throw new Error('event wait has no active durable timing snapshot');
|
|
24
|
+
}
|
|
25
|
+
suspension = { ...suspension, settleMs: state.settleMs,
|
|
26
|
+
idleAtMs: state.idleAtMs, deadlineAtMs: state.deadlineAtMs };
|
|
27
|
+
}
|
|
28
|
+
return { ...execution, report: { ...report, subscriptions,
|
|
29
|
+
...(suspension === undefined ? {} : { suspension }) } };
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// Missing authority is not an empty snapshot: Cloud must not close bindings
|
|
32
|
+
// or schedule timers from invented state. Preserve the run's diagnostic,
|
|
33
|
+
// but fail this report instead of presenting a usable suspension/success.
|
|
34
|
+
const { suspension: _suspension, subscriptions: _subscriptions, ...failed } = report;
|
|
35
|
+
return { exitCode: 1, report: { ...failed, ok: false, diagnostics: [
|
|
36
|
+
...report.diagnostics, { severity: 'failure', kind: 'protocol_error',
|
|
37
|
+
message: `Cannot inspect durable subscriptions: ${error instanceof Error ? error.message : 'unknown error'}` },
|
|
38
|
+
] } };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function inspectSubscriptions(socket: string, runId: string): Promise<SubscriptionInspectResult> {
|
|
43
|
+
const client = new JournalClient(socket);
|
|
44
|
+
try {
|
|
45
|
+
await client.connect();
|
|
46
|
+
await client.hello('flows-subscription-report');
|
|
47
|
+
return await client.subscriptionInspect({ run_id: runId });
|
|
48
|
+
} finally {
|
|
49
|
+
client.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/cli-adapter.ts
CHANGED
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
registeredAdapters,
|
|
11
11
|
} from './adapters/index.js';
|
|
12
12
|
import type { CliAdapterKind } from './adapters/index.js';
|
|
13
|
-
import type { CliInvocation, CliAdapterIdentification } from './adapters/base.js';
|
|
13
|
+
import type { CliInvocation, CliAdapterIdentification, ModelProbeFailure } from './adapters/base.js';
|
|
14
14
|
|
|
15
15
|
export type { CliAdapterKind } from './adapters/index.js';
|
|
16
|
-
export type { CliInvocation, CliAdapterIdentification, HeadlessAdapter
|
|
16
|
+
export type { CliInvocation, CliAdapterIdentification, HeadlessAdapter, ModelProbeFailure,
|
|
17
|
+
ModelProbeFailureCause } from './adapters/base.js';
|
|
17
18
|
|
|
18
19
|
export {
|
|
19
20
|
WRAPPER_IDENTIFY_ARG,
|
|
@@ -73,6 +74,11 @@ export function modelReadinessProbe(kind: CliAdapterKind, model: string): CliInv
|
|
|
73
74
|
return registeredAdapters()[kind].buildModelReadinessProbe(model);
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
/** Why a failed readiness probe failed, when the adapter recognises its output. */
|
|
78
|
+
export function classifyModelProbeFailure(kind: CliAdapterKind, output: string): ModelProbeFailure | undefined {
|
|
79
|
+
return registeredAdapters()[kind].classifyModelProbeFailure?.(output);
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
/** Build the actual worker argv; this is shared contract, not probe-only lore. */
|
|
77
83
|
export function agentExecution(
|
|
78
84
|
kind: CliAdapterKind,
|
package/src/cli.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { withSubscriptionMetadata } from './cli/subscription-report.js';
|
|
2
3
|
import { addPlugin } from './cli/add.js';
|
|
3
4
|
import { parsePluginArgs, runPluginCommand, type PluginArgs } from './cli/plugin.js';
|
|
4
5
|
import { watchCheck } from './cli-watch.js';
|
|
@@ -77,7 +78,7 @@ export interface CliIo {
|
|
|
77
78
|
tty?: boolean;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
type CliExitCode = 0 | 1 | 2 | 3;
|
|
81
|
+
type CliExitCode = 0 | 1 | 2 | 3 | 4;
|
|
81
82
|
|
|
82
83
|
/**
|
|
83
84
|
* Every shape `parseArgs` can produce. Exported for `cli-commands.ts`, whose
|
|
@@ -380,6 +381,9 @@ export async function runCli(
|
|
|
380
381
|
observer?.onRunStarted(run);
|
|
381
382
|
mirror?.onRunStarted(run);
|
|
382
383
|
},
|
|
384
|
+
...(mirror === undefined ? {} : {
|
|
385
|
+
onRunReceipt: (run: { runId: string }) => { mirror.onRunStarted(run); },
|
|
386
|
+
}),
|
|
383
387
|
}),
|
|
384
388
|
onWait: (progress: RunProgress) => {
|
|
385
389
|
emitWait(progress, logged);
|
|
@@ -410,13 +414,14 @@ export async function runCli(
|
|
|
410
414
|
// preflight) is worse than printing `Observer:` on a later line, so we
|
|
411
415
|
// emit the run report immediately and finalize the observer link after.
|
|
412
416
|
if (parsed.json) {
|
|
417
|
+
const reported = await withSubscriptionMetadata(execution);
|
|
413
418
|
const observerUrl = await observerUrlFrom(observerMint, logged);
|
|
414
|
-
emitRunReport(
|
|
419
|
+
emitRunReport(reported, parsed.json, logged, observerUrl, await mirror?.receipt());
|
|
415
420
|
// Last, and after the report: the run's own output is what the mirror
|
|
416
421
|
// uploads, and a run's exit code has never waited on Cloud. The mirror
|
|
417
422
|
// bounds itself and never rejects.
|
|
418
|
-
await mirror?.finish(
|
|
419
|
-
return
|
|
423
|
+
await mirror?.finish(reported.report);
|
|
424
|
+
return reported.exitCode;
|
|
420
425
|
}
|
|
421
426
|
emitRunReport(execution, parsed.json, logged);
|
|
422
427
|
await finalizeObserverLine(observerMint, logged);
|
|
@@ -434,6 +439,11 @@ export async function runCli(
|
|
|
434
439
|
async function checkAuthoredFlowComposed(path: string): Promise<{ report: CheckReport }> {
|
|
435
440
|
const helper = await checkHelperBody(path);
|
|
436
441
|
if (!helper.report.ok) return helper;
|
|
442
|
+
// The activity checker loads the TypeScript compiler. YAML checks and
|
|
443
|
+
// unrelated CLI commands should not pay that startup cost on every run.
|
|
444
|
+
const { checkAuthoredActivities } = await import('./cli/check-activities.js');
|
|
445
|
+
const activities = await checkAuthoredActivities(path);
|
|
446
|
+
if (!activities.report.ok) return activities;
|
|
437
447
|
const mcp = await checkTypeScriptFlow(path);
|
|
438
448
|
const triggers = isAuthoredFlowPath(path)
|
|
439
449
|
? await checkAuthoredTriggers(path)
|
|
@@ -449,8 +459,8 @@ async function checkAuthoredFlowComposed(path: string): Promise<{ report: CheckR
|
|
|
449
459
|
// The authored definition sees helper flags, body use and `cli:`
|
|
450
460
|
// declarations; the compiled view underneath knows only its steps.
|
|
451
461
|
...(triggers?.report.requirements === undefined ? {} : { requirements: triggers.report.requirements }),
|
|
452
|
-
diagnostics: [...helper.report.diagnostics, ...mcp.report.diagnostics, ...triggerDiagnostics],
|
|
453
|
-
ok: helper.report.ok && mcp.report.ok && triggerOk,
|
|
462
|
+
diagnostics: [...helper.report.diagnostics, ...activities.report.diagnostics, ...mcp.report.diagnostics, ...triggerDiagnostics],
|
|
463
|
+
ok: helper.report.ok && activities.report.ok && mcp.report.ok && triggerOk,
|
|
454
464
|
},
|
|
455
465
|
};
|
|
456
466
|
}
|
package/src/cloud-mirror-step.ts
CHANGED
|
@@ -152,6 +152,8 @@ export interface MirroredJournal {
|
|
|
152
152
|
children: string[];
|
|
153
153
|
/** Authored graph hints by step id, from an authored root's step index. */
|
|
154
154
|
hints: Map<string, { label?: string; after?: string[] }>;
|
|
155
|
+
/** Authored completion receipts are durable evidence, not graph nodes. */
|
|
156
|
+
hidden: string[];
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
type Payload = Record<string, unknown>;
|
|
@@ -507,6 +509,7 @@ export function mirrorJournal(
|
|
|
507
509
|
transcripts,
|
|
508
510
|
children: index.children,
|
|
509
511
|
hints: index.hints,
|
|
512
|
+
hidden: index.hidden,
|
|
510
513
|
};
|
|
511
514
|
}
|
|
512
515
|
|
|
@@ -522,7 +525,11 @@ export function mirrorJournal(
|
|
|
522
525
|
function authoredIndex(
|
|
523
526
|
events: readonly JournalEvent[],
|
|
524
527
|
env: NodeJS.ProcessEnv,
|
|
525
|
-
): {
|
|
528
|
+
): {
|
|
529
|
+
children: string[];
|
|
530
|
+
hints: Map<string, { label?: string; after?: string[] }>;
|
|
531
|
+
hidden: string[];
|
|
532
|
+
} {
|
|
526
533
|
const messages: unknown[] = [];
|
|
527
534
|
for (const event of events) {
|
|
528
535
|
if (event.entry_type !== 'stream.appended') continue;
|
|
@@ -531,8 +538,15 @@ function authoredIndex(
|
|
|
531
538
|
}
|
|
532
539
|
const children: string[] = [];
|
|
533
540
|
const hints = new Map<string, { label?: string; after?: string[] }>();
|
|
541
|
+
const hidden: string[] = [];
|
|
534
542
|
for (const entry of foldAuthoredStepRecords(messages).values()) {
|
|
535
543
|
if (/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(entry.runId)) children.push(entry.runId);
|
|
544
|
+
// This is the executor's durable verdict receipt, not an authored unit of
|
|
545
|
+
// work. Only an entry in the authored root index can be hidden here, so a
|
|
546
|
+
// declarative step coincidentally named `complete-N` remains visible.
|
|
547
|
+
if (/(?:^|--)complete-[1-9][0-9]*$/.test(entry.step)) {
|
|
548
|
+
hidden.push(`${entry.runId}/${entry.step}`);
|
|
549
|
+
}
|
|
536
550
|
const name = label(entry.label, env);
|
|
537
551
|
const after = entry.after === undefined
|
|
538
552
|
? undefined
|
|
@@ -546,7 +560,7 @@ function authoredIndex(
|
|
|
546
560
|
...(after === undefined ? {} : { after }),
|
|
547
561
|
});
|
|
548
562
|
}
|
|
549
|
-
return { children, hints };
|
|
563
|
+
return { children, hints, hidden };
|
|
550
564
|
}
|
|
551
565
|
|
|
552
566
|
/**
|
package/src/cloud-mirror.ts
CHANGED
|
@@ -287,6 +287,7 @@ export function createRunMirror(options: RunMirrorOptions): RunMirror {
|
|
|
287
287
|
const finals = new Map<string, { journalRunId: string; row: FinalStep }>();
|
|
288
288
|
const transcripts = new Map<string, { stepName: string; attempts: Array<{ attempt: number; path: string }> }>();
|
|
289
289
|
const hints = new Map<string, { label?: string; after?: string[] }>();
|
|
290
|
+
const hidden = new Set<string>();
|
|
290
291
|
/** Live steps the cache cap dropped, by identity, so a capped view owns up to it. */
|
|
291
292
|
const evicted = new Set<string>();
|
|
292
293
|
/** Finished steps past the report cap, by identity. Counted, never sent. */
|
|
@@ -318,6 +319,7 @@ export function createRunMirror(options: RunMirrorOptions): RunMirror {
|
|
|
318
319
|
|
|
319
320
|
const remember = (journalRunId: string, step: SnapshotStep, order: number): void => {
|
|
320
321
|
const key = `${journalRunId}/${step.stepName}`;
|
|
322
|
+
if (hidden.has(key)) return;
|
|
321
323
|
const existing = steps.get(key);
|
|
322
324
|
const changed = existing === undefined || fingerprint(existing.step) !== fingerprint(step);
|
|
323
325
|
steps.set(key, { step, order, changedAt: changed ? now() : existing!.changedAt });
|
|
@@ -365,19 +367,30 @@ export function createRunMirror(options: RunMirrorOptions): RunMirror {
|
|
|
365
367
|
continue;
|
|
366
368
|
}
|
|
367
369
|
for (const child of folded.children) admit(child);
|
|
370
|
+
for (const key of folded.hidden) {
|
|
371
|
+
hidden.add(key);
|
|
372
|
+
steps.delete(key);
|
|
373
|
+
finals.delete(key);
|
|
374
|
+
transcripts.delete(key);
|
|
375
|
+
}
|
|
368
376
|
for (const [key, hint] of folded.hints) {
|
|
369
377
|
// Merge, never replace: a completion record can omit a label or an
|
|
370
378
|
// edge the admission carried, and the view must not lose a step's
|
|
371
379
|
// name between them.
|
|
372
380
|
const existing = hints.get(key);
|
|
373
381
|
hints.set(key, {
|
|
374
|
-
|
|
375
|
-
|
|
382
|
+
// The root's authored index is scanned before the child journal and
|
|
383
|
+
// carries causal edges across child runs. A child spec only knows
|
|
384
|
+
// its own single-step graph (`depends_on: []`), so it may fill a
|
|
385
|
+
// missing field but must never erase the root's richer answer.
|
|
386
|
+
...(existing?.label ?? hint.label ? { label: existing?.label ?? hint.label } : {}),
|
|
387
|
+
...(existing?.after ?? hint.after ? { after: existing?.after ?? hint.after } : {}),
|
|
376
388
|
});
|
|
377
389
|
}
|
|
378
390
|
folded.steps.forEach((step, order) => remember(runId, step, order));
|
|
379
391
|
for (const row of folded.finals) {
|
|
380
392
|
const key = `${runId}/${row.stepName}`;
|
|
393
|
+
if (hidden.has(key)) continue;
|
|
381
394
|
// The report cap bounds what this process holds, not just what it
|
|
382
395
|
// sends: a run with ten thousand steps must not grow its own mirror.
|
|
383
396
|
// Steps past it are counted by identity, so a step read twice is one
|
package/src/failure-kinds.ts
CHANGED
|
@@ -15,6 +15,7 @@ const PREFLIGHT_ENVIRONMENT_FAILURE_KINDS = [
|
|
|
15
15
|
'mcp_unreachable',
|
|
16
16
|
'budget_syntax_invalid',
|
|
17
17
|
'cli_missing',
|
|
18
|
+
'cli_outdated',
|
|
18
19
|
'cli_unauthenticated',
|
|
19
20
|
'cli_unresolved',
|
|
20
21
|
'cli_unsupported',
|
|
@@ -24,6 +25,7 @@ const PREFLIGHT_ENVIRONMENT_FAILURE_KINDS = [
|
|
|
24
25
|
'memory_unreachable',
|
|
25
26
|
'no_executor',
|
|
26
27
|
'probe_failed',
|
|
28
|
+
'provider_usage_limited',
|
|
27
29
|
'scope_syntax_invalid',
|
|
28
30
|
'mount_unknown',
|
|
29
31
|
'scope_ungrantable',
|
package/src/index.ts
CHANGED
|
@@ -173,6 +173,12 @@ export type {
|
|
|
173
173
|
EffectRef,
|
|
174
174
|
EventEmitParams,
|
|
175
175
|
EventEmitResult,
|
|
176
|
+
SubscriptionCloseParams,
|
|
177
|
+
SubscriptionCloseResult,
|
|
178
|
+
SubscriptionNextParams,
|
|
179
|
+
SubscriptionNextResult,
|
|
180
|
+
SubscriptionOpenParams,
|
|
181
|
+
SubscriptionOpenResult,
|
|
176
182
|
HelloParams,
|
|
177
183
|
HelloResult,
|
|
178
184
|
JournalReadParams,
|
package/src/journal-client.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { EventEmitter } from 'node:events';
|
|
|
13
13
|
export { walkJournal, JournalReadError, type JournalEvent, type JournalReadFailure } from './journal-reader.js';
|
|
14
14
|
import { randomUUID } from 'node:crypto';
|
|
15
15
|
import { createConnection, type Socket } from 'node:net';
|
|
16
|
-
import type { VerbContract, EventSubmitParams } from './protocol.js';
|
|
16
|
+
import type { VerbContract, EventEmitParams, EventSubmitParams } from './protocol.js';
|
|
17
17
|
import {
|
|
18
18
|
PROTOCOL_VERSION,
|
|
19
19
|
type CompletionReason,
|
|
@@ -442,8 +442,17 @@ export class JournalClient extends EventEmitter {
|
|
|
442
442
|
});
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
eventEmit(
|
|
446
|
-
|
|
445
|
+
eventEmit(
|
|
446
|
+
runId: string,
|
|
447
|
+
eventKey: string,
|
|
448
|
+
payload: unknown,
|
|
449
|
+
options: Pick<EventEmitParams, 'delivery_id' | 'actor'> = {},
|
|
450
|
+
): Promise<VerbContract['event.emit']['result']> {
|
|
451
|
+
return this.request('event.emit', { run_id: runId, event_key: eventKey, payload, ...options });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
subscriptionPark(params: VerbContract['subscription.park']['params']): Promise<VerbContract['subscription.park']['result']> {
|
|
455
|
+
return this.request('subscription.park', params);
|
|
447
456
|
}
|
|
448
457
|
|
|
449
458
|
/**
|
|
@@ -457,6 +466,41 @@ export class JournalClient extends EventEmitter {
|
|
|
457
466
|
return this.request('event.submit', { spec, event });
|
|
458
467
|
}
|
|
459
468
|
|
|
469
|
+
/** Prepare a body subscription. Cloud must activate the returned request before a body can observe it. */
|
|
470
|
+
subscriptionOpen(params: VerbContract['subscription.open']['params']): Promise<VerbContract['subscription.open']['result']> {
|
|
471
|
+
return this.request('subscription.open', params, null);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Commit Cloud's durable binding receipt and ingress fence. */
|
|
475
|
+
subscriptionActivate(params: VerbContract['subscription.activate']['params']): Promise<VerbContract['subscription.activate']['result']> {
|
|
476
|
+
return this.request('subscription.activate', params, null);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Append to one activated subscription under its immutable router receipt. */
|
|
480
|
+
subscriptionDeliver(params: VerbContract['subscription.deliver']['params']): Promise<VerbContract['subscription.deliver']['result']> {
|
|
481
|
+
return this.request('subscription.deliver', params, null);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** Read durable subscription state without changing timers or ingress. */
|
|
485
|
+
subscriptionInspect(params: VerbContract['subscription.inspect']['params']): Promise<VerbContract['subscription.inspect']['result']> {
|
|
486
|
+
return this.request('subscription.inspect', params);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Fence the exact activated receipt after router-side overflow. */
|
|
490
|
+
subscriptionFenceOverflow(params: VerbContract['subscription.fence_overflow']['params']): Promise<VerbContract['subscription.fence_overflow']['result']> {
|
|
491
|
+
return this.request('subscription.fence_overflow', params, null);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Return a durable wake, or an explicit suspension with no daemon-side sleep. */
|
|
495
|
+
subscriptionNext(params: VerbContract['subscription.next']['params']): Promise<VerbContract['subscription.next']['result']> {
|
|
496
|
+
return this.request('subscription.next', params, null);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** Close a body subscription; the server refuses later external appends. */
|
|
500
|
+
subscriptionClose(params: VerbContract['subscription.close']['params']): Promise<VerbContract['subscription.close']['result']> {
|
|
501
|
+
return this.request('subscription.close', params, null);
|
|
502
|
+
}
|
|
503
|
+
|
|
460
504
|
channelAppend(params: VerbContract['channel.append']['params']): Promise<VerbContract['channel.append']['result']> {
|
|
461
505
|
return this.request('channel.append', params);
|
|
462
506
|
}
|
package/src/journal-reader.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { journalRecordOffset } from './journal-offset.js';
|
|
|
8
8
|
// Journal version 1's closed vocabulary (relayflowd-core/src/entry.rs).
|
|
9
9
|
const ENTRY_TYPES = new Set([
|
|
10
10
|
'run.spawned', 'run.cancel.requested', 'event.received', 'subscription.registered',
|
|
11
|
+
'subscription.prepared', 'subscription.opened', 'subscription.closed',
|
|
12
|
+
'subscription.overflow.fenced', 'subscription.acknowledged',
|
|
11
13
|
'subscription.matched', 'subscription.stale', 'step.routed', 'step.attempt.started',
|
|
12
14
|
'step.completed', 'wait.event', 'wait.human', 'sleep.until', 'wait.completed',
|
|
13
15
|
'stream.appended', 'memory.injected', 'channel.appended', 'channel.delivered',
|
package/src/local-agent.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { onWorkerFailure } from './worker-lease.js';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import type { JournalClient } from './journal-client.js';
|
|
4
4
|
import { AgentWorker } from './worker.js';
|
|
5
|
+
import type { KernelRunSpec } from './spec.js';
|
|
6
|
+
import { communicationInstruction } from './communication/spec.js';
|
|
5
7
|
import { DEFAULT_LOCAL_AGENT_CAPACITY } from './worker-slots.js';
|
|
6
8
|
|
|
7
9
|
/** A local worker for stream-only steps; no workspace recovery is claimed. */
|
|
@@ -12,13 +14,16 @@ export async function attachLocalAgent(
|
|
|
12
14
|
requestedStream?: string,
|
|
13
15
|
capacity: number = DEFAULT_LOCAL_AGENT_CAPACITY,
|
|
14
16
|
runRoot?: string,
|
|
17
|
+
declaredStreams: readonly string[] = [],
|
|
15
18
|
): Promise<{
|
|
16
19
|
stream: string;
|
|
17
20
|
readonly failure: unknown;
|
|
18
21
|
close(): Promise<void>;
|
|
19
22
|
}> {
|
|
20
|
-
//
|
|
21
|
-
//
|
|
23
|
+
// This worker has consumed no messages: its read offsets are zero, including
|
|
24
|
+
// named declarative streams. The kernel retains ownership of dispatch pins
|
|
25
|
+
// and rejects a resume whose required offsets this worker does not hold.
|
|
26
|
+
// No worktree revision is invented.
|
|
22
27
|
const stream = requestedStream ?? `local-agent-${randomUUID()}`;
|
|
23
28
|
const worker = new AgentWorker(client, {
|
|
24
29
|
workerId: stream,
|
|
@@ -27,7 +32,8 @@ export async function attachLocalAgent(
|
|
|
27
32
|
// this same number (worker-slots.ts), so they never ask for more.
|
|
28
33
|
capacity,
|
|
29
34
|
dataDir, onPtyReady, runRoot,
|
|
30
|
-
pins: { workspace: [], streams: [
|
|
35
|
+
pins: { workspace: [], streams: [...new Set([stream, ...declaredStreams])]
|
|
36
|
+
.map(stream => ({ stream, read_offset: 0 })) },
|
|
31
37
|
});
|
|
32
38
|
let failure: unknown;
|
|
33
39
|
// The cause travels with the close, so the flow's next request names it.
|
|
@@ -41,3 +47,11 @@ export async function attachLocalAgent(
|
|
|
41
47
|
},
|
|
42
48
|
};
|
|
43
49
|
}
|
|
50
|
+
|
|
51
|
+
/** Conversation workers own their streams and registration; do not steal their dispatches. */
|
|
52
|
+
export function declaredLocalAgentStreams(spec: KernelRunSpec): string[] {
|
|
53
|
+
return [...new Set(spec.steps.flatMap(step =>
|
|
54
|
+
step.type === 'agent' && !communicationInstruction(step.instruction)
|
|
55
|
+
? step.surfaces?.streams?.map(({ stream }) => stream) ?? []
|
|
56
|
+
: []))];
|
|
57
|
+
}
|
package/src/preflight.ts
CHANGED
|
@@ -52,6 +52,12 @@ export interface CliProbeResult {
|
|
|
52
52
|
* unauthenticated CLI.
|
|
53
53
|
*/
|
|
54
54
|
authFailureDetail?: string;
|
|
55
|
+
/** Exit code of a FAILED model-scoped readiness probe. */
|
|
56
|
+
modelExitCode?: number | null;
|
|
57
|
+
/** Cause the adapter read from a failed readiness probe's output, if any. */
|
|
58
|
+
modelFailure?: import('./adapters/base.js').ModelProbeFailure;
|
|
59
|
+
/** Redacted output of a FAILED model-scoped readiness probe. */
|
|
60
|
+
modelFailureDetail?: string;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export type CliProbeFailureDetail =
|
|
@@ -603,17 +609,47 @@ function probeResolvedCli(
|
|
|
603
609
|
message: `Step "${resolution.stepId}" declares CLI "${resolution.cli}", but "${command}" exited non-zero${exitCode}; authenticate it or repair that adapter's authentication probe.${detail}`,
|
|
604
610
|
});
|
|
605
611
|
} else if (resolution.model !== undefined && result.modelAvailable !== true) {
|
|
606
|
-
diagnostics.push({
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
612
|
+
diagnostics.push(modelProbeRefusal(resolution as CliResolution & { model: string }, result));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Only a probe whose output says the model is unknown (or the adapter gave no
|
|
617
|
+
// output to read) is told to check the model name. An outdated CLI and a
|
|
618
|
+
// usage-limited credential name a different remedy, and "verify the model
|
|
619
|
+
// name" sends the author after a typo that is not there.
|
|
620
|
+
function modelProbeRefusal(
|
|
621
|
+
resolution: CliResolution & { model: string },
|
|
622
|
+
result: CliProbeResult,
|
|
623
|
+
): PreflightDiagnostic {
|
|
624
|
+
const { stepId, cli, model } = resolution;
|
|
625
|
+
const effective = describeEffectiveModel(stepId, model, cli, resolution.modelSource);
|
|
626
|
+
const command = `"${result.modelCommand ?? `${cli} auth status`}"`;
|
|
627
|
+
const exit = result.modelExitCode === undefined || result.modelExitCode === null ? '' : ` (exit ${result.modelExitCode})`;
|
|
628
|
+
const reported = result.modelFailureDetail === undefined ? '' : ` It reported: ${result.modelFailureDetail}`;
|
|
629
|
+
const base = { severity: 'refusal' as const, stepId, cli, model };
|
|
630
|
+
const cause = result.modelFailure?.cause;
|
|
631
|
+
if (cause === 'cli_outdated') {
|
|
632
|
+
const required = result.modelFailure?.requiredVersion;
|
|
633
|
+
return { ...base, kind: 'cli_outdated',
|
|
634
|
+
message: `${effective}, but this "${cli}" is too old to use that model`
|
|
635
|
+
+ (required === undefined ? '' : `; version ${required} or newer is required`)
|
|
636
|
+
+ `. Upgrade the CLI in the environment that runs this flow; the model name is not the problem.${reported}` };
|
|
637
|
+
}
|
|
638
|
+
if (cause === 'provider_usage_limited') {
|
|
639
|
+
return { ...base, kind: 'provider_usage_limited',
|
|
640
|
+
message: `${effective}, but the provider refused the model-scoped ${command} probe${exit} because this credential`
|
|
641
|
+
+ ` has reached a usage or rate limit. Retry after the limit resets or use a credential with capacity;`
|
|
642
|
+
+ ` the model name is not the problem.${reported}` };
|
|
616
643
|
}
|
|
644
|
+
// A probe that printed nothing is judged on its exit code alone, which is
|
|
645
|
+
// how a wrapper refuses a model it cannot serve.
|
|
646
|
+
const unknownModel = cause === 'model_unknown' || result.modelFailureDetail === undefined;
|
|
647
|
+
return { ...base, kind: 'model_unavailable',
|
|
648
|
+
message: `${effective}, but its model-scoped ${command} probe exited non-zero${exit}`
|
|
649
|
+
+ (unknownModel
|
|
650
|
+
? `; verify the model name and this credential's access.`
|
|
651
|
+
: ` for a reason Relayflows does not recognise.`)
|
|
652
|
+
+ reported };
|
|
617
653
|
}
|
|
618
654
|
|
|
619
655
|
function probeFailedMessage(
|