@relayflows/sdk 2.0.35 → 2.0.37
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-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 +11 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +32 -1
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-helper-effect.d.ts.map +1 -1
- package/dist/authored-helper-effect.js +3 -2
- package/dist/authored-helper-effect.js.map +1 -1
- package/dist/authored-mcp.d.ts.map +1 -1
- package/dist/authored-mcp.js +4 -2
- package/dist/authored-mcp.js.map +1 -1
- package/dist/authored-node-entry.js +9 -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 +42 -9
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-plugin-effect.d.ts.map +1 -1
- package/dist/authored-plugin-effect.js +4 -2
- package/dist/authored-plugin-effect.js.map +1 -1
- package/dist/authored-preflight.d.ts +1 -1
- package/dist/authored-preflight.d.ts.map +1 -1
- package/dist/authored-preflight.js +2 -2
- package/dist/authored-preflight.js.map +1 -1
- package/dist/authored-root.d.ts +12 -7
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +24 -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-verdict.d.ts +2 -0
- package/dist/authored-verdict.d.ts.map +1 -1
- package/dist/authored-verdict.js +2 -1
- package/dist/authored-verdict.js.map +1 -1
- package/dist/authored-worker-step.d.ts +1 -1
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +9 -4
- 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/check.d.ts +2 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +3 -3
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cli-probe.d.ts +2 -2
- package/dist/cli/cli-probe.d.ts.map +1 -1
- package/dist/cli/cli-probe.js +22 -9
- 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 +17 -4
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/run.d.ts +18 -5
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +37 -12
- 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 +13 -5
- package/dist/cli.js.map +1 -1
- package/dist/communication/local.d.ts +1 -1
- package/dist/communication/local.d.ts.map +1 -1
- package/dist/communication/local.js +2 -2
- package/dist/communication/local.js.map +1 -1
- package/dist/communication/worker.d.ts +1 -1
- package/dist/communication/worker.d.ts.map +1 -1
- package/dist/communication/worker.js +4 -4
- package/dist/communication/worker.js.map +1 -1
- package/dist/decimal-dollars.d.ts +12 -0
- package/dist/decimal-dollars.d.ts.map +1 -0
- package/dist/decimal-dollars.js +39 -0
- package/dist/decimal-dollars.js.map +1 -0
- package/dist/effect-channel.d.ts.map +1 -1
- package/dist/effect-channel.js +2 -0
- package/dist/effect-channel.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/helper-instruction.d.ts +3 -0
- package/dist/helper-instruction.d.ts.map +1 -0
- package/dist/helper-instruction.js +3 -0
- package/dist/helper-instruction.js.map +1 -0
- 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 +21 -3
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +45 -6
- 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/llm-worker.d.ts +2 -1
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +7 -2
- package/dist/llm-worker.js.map +1 -1
- package/dist/local-agent-environment.d.ts +13 -0
- package/dist/local-agent-environment.d.ts.map +1 -0
- package/dist/local-agent-environment.js +104 -0
- package/dist/local-agent-environment.js.map +1 -0
- package/dist/local-agent.d.ts +4 -1
- package/dist/local-agent.d.ts.map +1 -1
- package/dist/local-agent.js +15 -5
- 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 +187 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/reported-cost-total.d.ts +41 -0
- package/dist/reported-cost-total.d.ts.map +1 -0
- package/dist/reported-cost-total.js +63 -0
- package/dist/reported-cost-total.js.map +1 -0
- package/dist/reported-cost.d.ts +24 -0
- package/dist/reported-cost.d.ts.map +1 -0
- package/dist/reported-cost.js +49 -0
- package/dist/reported-cost.js.map +1 -0
- package/dist/run-state.d.ts +9 -11
- package/dist/run-state.d.ts.map +1 -1
- package/dist/run-state.js +15 -38
- package/dist/run-state.js.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-cli.d.ts +1 -1
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +3 -3
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker.d.ts +2 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +5 -2
- package/dist/worker.js.map +1 -1
- package/dist/yaml-helper-effect.d.ts.map +1 -1
- package/dist/yaml-helper-effect.js +2 -1
- package/dist/yaml-helper-effect.js.map +1 -1
- package/dist/yaml-helpers.d.ts.map +1 -1
- package/dist/yaml-helpers.js +2 -1
- package/dist/yaml-helpers.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-flow-error.ts +33 -0
- package/src/authored-flow-executor.ts +44 -1
- package/src/authored-helper-effect.ts +3 -2
- package/src/authored-mcp.ts +4 -2
- package/src/authored-node-entry.ts +11 -2
- package/src/authored-node-runner.ts +43 -9
- package/src/authored-plugin-effect.ts +4 -2
- package/src/authored-preflight.ts +2 -2
- package/src/authored-root.ts +34 -4
- package/src/authored-step-output.ts +88 -15
- package/src/authored-subscription-park.ts +46 -0
- package/src/authored-verdict.ts +2 -1
- package/src/authored-worker-step.ts +9 -3
- package/src/cli/check-activities.ts +144 -0
- package/src/cli/check.ts +6 -3
- package/src/cli/cli-probe.ts +23 -9
- package/src/cli/direct-run.ts +16 -1
- package/src/cli/run.ts +59 -14
- package/src/cli/subscription-report.ts +51 -0
- package/src/cli-adapter.ts +8 -2
- package/src/cli.ts +13 -6
- package/src/communication/local.ts +3 -2
- package/src/communication/worker.ts +7 -4
- package/src/decimal-dollars.ts +38 -0
- package/src/effect-channel.ts +2 -0
- package/src/failure-kinds.ts +2 -0
- package/src/helper-instruction.ts +2 -0
- package/src/index.ts +6 -0
- package/src/journal-client.ts +60 -7
- package/src/journal-reader.ts +2 -0
- package/src/llm-worker.ts +6 -1
- package/src/local-agent-environment.ts +92 -0
- package/src/local-agent.ts +19 -4
- package/src/preflight.ts +46 -10
- package/src/protocol.ts +151 -0
- package/src/reported-cost-total.ts +82 -0
- package/src/reported-cost.ts +51 -0
- package/src/run-state.ts +25 -37
- package/src/step-settling.ts +19 -0
- package/src/worker-cli.ts +3 -2
- package/src/worker.ts +9 -2
- package/src/yaml-helper-effect.ts +2 -1
- package/src/yaml-helpers.ts +2 -1
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 {
|
|
@@ -37,12 +39,13 @@ import {
|
|
|
37
39
|
type LoweredCompletionReason,
|
|
38
40
|
} from '../authored-completion.js';
|
|
39
41
|
import { DEFAULT_LOCAL_AGENT_CAPACITY } from '../worker-slots.js';
|
|
42
|
+
import { localAgentEnvironment } from '../local-agent-environment.js';
|
|
40
43
|
import {
|
|
41
44
|
checkFlow,
|
|
42
45
|
type CheckReport,
|
|
43
46
|
} from './check.js';
|
|
44
47
|
|
|
45
|
-
export type RunExitCode = 0 | 1 | 2 | 3;
|
|
48
|
+
export type RunExitCode = 0 | 1 | 2 | 3 | 4;
|
|
46
49
|
export type RunCommand = 'run' | 'resume' | 'answer';
|
|
47
50
|
|
|
48
51
|
export interface ParkedStep {
|
|
@@ -52,7 +55,7 @@ export interface ParkedStep {
|
|
|
52
55
|
|
|
53
56
|
export interface RunDiagnostic extends StepFailedDetails {
|
|
54
57
|
severity: 'refusal' | 'failure' | 'parked' | 'warning' | 'declined';
|
|
55
|
-
kind: RunFailureKind | RunWarningKind | RunCompletionReason;
|
|
58
|
+
kind: RunFailureKind | RunWarningKind | RunCompletionReason | 'subscription_suspended';
|
|
56
59
|
message: string;
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -61,10 +64,14 @@ export interface RunReport {
|
|
|
61
64
|
command: RunCommand;
|
|
62
65
|
path?: string;
|
|
63
66
|
runId?: string;
|
|
64
|
-
/** Authored root
|
|
67
|
+
/** Authored root owning subscriptions when runId names a failed child. */
|
|
65
68
|
rootRunId?: string;
|
|
66
69
|
socketPath?: string;
|
|
67
|
-
status?: RunStatus;
|
|
70
|
+
status?: RunStatus | 'suspended';
|
|
71
|
+
/** Cloud consumes this exact durable boundary before launching a resume. */
|
|
72
|
+
suspension?: AuthoredFlowSuspendedResult['suspension'] & { settleMs?: number; idleAtMs?: number };
|
|
73
|
+
/** Durable subscription projection for Cloud routing and cleanup. */
|
|
74
|
+
subscriptions?: SubscriptionSnapshot[];
|
|
68
75
|
completionReason?: RunCompletionReason;
|
|
69
76
|
/**
|
|
70
77
|
* What the body passed to `done(reason, { detail })`, normalized: redacted,
|
|
@@ -103,6 +110,26 @@ export interface RunExecution {
|
|
|
103
110
|
report: RunReport;
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
export function suspendedExecution(
|
|
114
|
+
command: RunCommand,
|
|
115
|
+
base: CheckReport | RunReport,
|
|
116
|
+
socketPath: string,
|
|
117
|
+
runId: string,
|
|
118
|
+
result: AuthoredFlowSuspendedResult & { readonly rootRunId: string },
|
|
119
|
+
): RunExecution {
|
|
120
|
+
return {
|
|
121
|
+
exitCode: 4,
|
|
122
|
+
report: {
|
|
123
|
+
...fromBase(command, base), ok: false, runId, socketPath, status: 'suspended',
|
|
124
|
+
suspension: result.suspension, completedSteps: result.journalSteps.length,
|
|
125
|
+
diagnostics: [...base.diagnostics, {
|
|
126
|
+
severity: 'warning', kind: 'subscription_suspended',
|
|
127
|
+
message: `Flow "${result.name}" suspended for ${result.suspension.kind}.`,
|
|
128
|
+
}],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
106
133
|
export interface RunProgress {
|
|
107
134
|
runId: string;
|
|
108
135
|
stepId: string;
|
|
@@ -150,20 +177,22 @@ export async function runFlow(
|
|
|
150
177
|
dataDir: string,
|
|
151
178
|
options: RunLifecycleOptions = {},
|
|
152
179
|
): Promise<RunExecution> {
|
|
180
|
+
const agentEnvironment = localAgentEnvironment();
|
|
153
181
|
const prepared = parseDigestReference(path) ? await prepareDigestRun(path, options.bucket) : undefined;
|
|
154
182
|
if (prepared && 'exitCode' in prepared) return prepared;
|
|
155
|
-
const checked = prepared ?? checkFlow(path);
|
|
183
|
+
const checked = prepared ?? checkFlow(path, { environment: agentEnvironment });
|
|
156
184
|
if (!checked.report.ok || checked.flow === undefined) {
|
|
157
185
|
return { exitCode: 2, report: fromCheckReport('run', checked.report) };
|
|
158
186
|
}
|
|
159
187
|
|
|
160
|
-
return executeCheckedFlow(checked, dataDir, options);
|
|
188
|
+
return executeCheckedFlow(checked, dataDir, options, agentEnvironment);
|
|
161
189
|
}
|
|
162
190
|
|
|
163
191
|
async function executeCheckedFlow(
|
|
164
192
|
checked: ReturnType<typeof checkFlow>,
|
|
165
193
|
dataDir: string,
|
|
166
194
|
options: RunLifecycleOptions,
|
|
195
|
+
agentEnvironment?: NodeJS.ProcessEnv,
|
|
167
196
|
): Promise<RunExecution> {
|
|
168
197
|
const socketPath = socketFor(dataDir);
|
|
169
198
|
// Carry the preflight's diagnostics as a RunReport from here on, so the
|
|
@@ -185,11 +214,14 @@ async function executeCheckedFlow(
|
|
|
185
214
|
// Use the checked CLI/model and declared surfaces unchanged. The worker
|
|
186
215
|
// advertises its existing pins; the daemon still owns surface matching.
|
|
187
216
|
if (options.localAgent) {
|
|
188
|
-
localAgent = await attachLocalAgent(
|
|
217
|
+
localAgent = await attachLocalAgent(
|
|
218
|
+
client, dataDir, options.onPtyReady, undefined, options.agentCapacity,
|
|
219
|
+
undefined, declaredLocalAgentStreams(spec), agentEnvironment,
|
|
220
|
+
);
|
|
189
221
|
}
|
|
190
222
|
if (options.localAgent && spec.steps.some(step => step.type === 'agent' && communicationInstruction(step.instruction))) {
|
|
191
223
|
const { attachCommunicationWorkers } = await import('../communication/local.js');
|
|
192
|
-
communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir);
|
|
224
|
+
communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir, agentEnvironment);
|
|
193
225
|
}
|
|
194
226
|
if (options.onJournalEntry !== undefined) client.on('entry', options.onJournalEntry);
|
|
195
227
|
const outcome = await startWatched(client, spec, options);
|
|
@@ -252,6 +284,9 @@ export async function resumeFlow(
|
|
|
252
284
|
dataDir: string,
|
|
253
285
|
options: RunLifecycleOptions = {},
|
|
254
286
|
): Promise<RunExecution> {
|
|
287
|
+
// Resume can load authored source; consume the descriptor before reading the
|
|
288
|
+
// journal so no source evaluation can race credential pickup.
|
|
289
|
+
const agentEnvironment = localAgentEnvironment();
|
|
255
290
|
const socketPath = socketFor(dataDir);
|
|
256
291
|
const base = emptyReport('resume');
|
|
257
292
|
const client = new JournalClient(socketPath);
|
|
@@ -271,6 +306,7 @@ export async function resumeFlow(
|
|
|
271
306
|
try {
|
|
272
307
|
authoredRoot = await readAuthoredRootMetadata(client, runId);
|
|
273
308
|
if (authoredRoot !== undefined) {
|
|
309
|
+
base.rootRunId = runId;
|
|
274
310
|
// Both worker-surface mismatches are refusals, not protocol failures.
|
|
275
311
|
// They used to throw bare `Error`s, which landed on `protocol_error`
|
|
276
312
|
// ("RUN <id> unknown") and told nobody what to do instead; and the
|
|
@@ -293,11 +329,12 @@ export async function resumeFlow(
|
|
|
293
329
|
if (options.localAgent) {
|
|
294
330
|
authoredAgent = await attachLocalAgent(
|
|
295
331
|
client, dataDir, options.onPtyReady, authoredRoot.localAgentStream, workerCapacity,
|
|
332
|
+
undefined, [], agentEnvironment,
|
|
296
333
|
);
|
|
297
334
|
authoredLlmClient = new JournalClient(socketPath);
|
|
298
335
|
await authoredLlmClient.connect();
|
|
299
336
|
await authoredLlmClient.hello('flows-authored-resume-llm');
|
|
300
|
-
authoredLlm = new LlmWorker(authoredLlmClient, `${authoredAgent.stream}-llm`, workerCapacity);
|
|
337
|
+
authoredLlm = new LlmWorker(authoredLlmClient, `${authoredAgent.stream}-llm`, workerCapacity, agentEnvironment);
|
|
301
338
|
authoredLlm.on('error', onWorkerFailure('resume-llm', error => {
|
|
302
339
|
llmFailure = error;
|
|
303
340
|
client.close();
|
|
@@ -308,9 +345,13 @@ export async function resumeFlow(
|
|
|
308
345
|
dataDir,
|
|
309
346
|
localAgentStream: authoredAgent?.stream,
|
|
310
347
|
...(authoredAgent === undefined ? {} : { workerCapacity }),
|
|
348
|
+
...(agentEnvironment === undefined ? {} : { agentEnvironment }),
|
|
311
349
|
lifecycle: options,
|
|
312
350
|
});
|
|
313
351
|
if (result === undefined) throw new Error('authored root disappeared during resume');
|
|
352
|
+
if (result.state === 'suspended') {
|
|
353
|
+
return suspendedExecution('resume', base, socketPath, runId, result);
|
|
354
|
+
}
|
|
314
355
|
return authoredCompletion('resume', base, socketPath, result, runId);
|
|
315
356
|
}
|
|
316
357
|
// resumeHelperEffect subsumes the old resumeSlackEffect: it handles the
|
|
@@ -318,12 +359,16 @@ export async function resumeFlow(
|
|
|
318
359
|
// second call the earlier rebase left is a stale reference from before
|
|
319
360
|
// the helper fanout renamed the API.
|
|
320
361
|
if (options.localAgent) {
|
|
321
|
-
authoredAgent = await attachLocalAgent(client, dataDir, options.onPtyReady, undefined, options.agentCapacity);
|
|
322
362
|
const entries = (await client.journalRead(runId, 1)).entries as Array<{ entry_type: string; payload?: { spec?: import('../spec.js').KernelRunSpec } }>;
|
|
323
363
|
const spec = entries.find(entry => entry.entry_type === 'run.spawned')?.payload?.spec;
|
|
364
|
+
if (!spec) throw new Error('Cannot attach a local worker: the journaled run spec is missing');
|
|
365
|
+
authoredAgent = await attachLocalAgent(
|
|
366
|
+
client, dataDir, options.onPtyReady, undefined, options.agentCapacity,
|
|
367
|
+
undefined, declaredLocalAgentStreams(spec), agentEnvironment,
|
|
368
|
+
);
|
|
324
369
|
if (spec?.steps.some(step => step.type === 'agent' && communicationInstruction(step.instruction))) {
|
|
325
370
|
const { attachCommunicationWorkers } = await import('../communication/local.js');
|
|
326
|
-
communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir);
|
|
371
|
+
communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir, agentEnvironment);
|
|
327
372
|
}
|
|
328
373
|
}
|
|
329
374
|
const onEntry = options.onJournalEntry;
|
|
@@ -955,7 +1000,7 @@ async function inspectOutOfBandStep(
|
|
|
955
1000
|
}
|
|
956
1001
|
|
|
957
1002
|
// Match kernel/relayflowd/src/server/client.rs: allow the lease sweep to dispatch a retry.
|
|
958
|
-
const LEASE_SWEEP_GRACE_MS = 5_000;
|
|
1003
|
+
export const LEASE_SWEEP_GRACE_MS = 5_000;
|
|
959
1004
|
|
|
960
1005
|
async function waitForRunningStep(
|
|
961
1006
|
client: JournalClient,
|
|
@@ -1056,7 +1101,7 @@ function throwIfCanceled(signal: AbortSignal | undefined, stepId: string): void
|
|
|
1056
1101
|
if (signal?.aborted === true) throw new Error(`waiting for running step "${stepId}" was canceled`);
|
|
1057
1102
|
}
|
|
1058
1103
|
|
|
1059
|
-
function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
1104
|
+
export function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
1060
1105
|
return new Promise((resolveDelay, rejectDelay) => {
|
|
1061
1106
|
const finish = (): void => {
|
|
1062
1107
|
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
|
|
@@ -413,13 +414,14 @@ export async function runCli(
|
|
|
413
414
|
// preflight) is worse than printing `Observer:` on a later line, so we
|
|
414
415
|
// emit the run report immediately and finalize the observer link after.
|
|
415
416
|
if (parsed.json) {
|
|
417
|
+
const reported = await withSubscriptionMetadata(execution);
|
|
416
418
|
const observerUrl = await observerUrlFrom(observerMint, logged);
|
|
417
|
-
emitRunReport(
|
|
419
|
+
emitRunReport(reported, parsed.json, logged, observerUrl, await mirror?.receipt());
|
|
418
420
|
// Last, and after the report: the run's own output is what the mirror
|
|
419
421
|
// uploads, and a run's exit code has never waited on Cloud. The mirror
|
|
420
422
|
// bounds itself and never rejects.
|
|
421
|
-
await mirror?.finish(
|
|
422
|
-
return
|
|
423
|
+
await mirror?.finish(reported.report);
|
|
424
|
+
return reported.exitCode;
|
|
423
425
|
}
|
|
424
426
|
emitRunReport(execution, parsed.json, logged);
|
|
425
427
|
await finalizeObserverLine(observerMint, logged);
|
|
@@ -437,6 +439,11 @@ export async function runCli(
|
|
|
437
439
|
async function checkAuthoredFlowComposed(path: string): Promise<{ report: CheckReport }> {
|
|
438
440
|
const helper = await checkHelperBody(path);
|
|
439
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;
|
|
440
447
|
const mcp = await checkTypeScriptFlow(path);
|
|
441
448
|
const triggers = isAuthoredFlowPath(path)
|
|
442
449
|
? await checkAuthoredTriggers(path)
|
|
@@ -452,8 +459,8 @@ async function checkAuthoredFlowComposed(path: string): Promise<{ report: CheckR
|
|
|
452
459
|
// The authored definition sees helper flags, body use and `cli:`
|
|
453
460
|
// declarations; the compiled view underneath knows only its steps.
|
|
454
461
|
...(triggers?.report.requirements === undefined ? {} : { requirements: triggers.report.requirements }),
|
|
455
|
-
diagnostics: [...helper.report.diagnostics, ...mcp.report.diagnostics, ...triggerDiagnostics],
|
|
456
|
-
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,
|
|
457
464
|
},
|
|
458
465
|
};
|
|
459
466
|
}
|
|
@@ -8,7 +8,8 @@ import { loadRelayModules } from './relay.js';
|
|
|
8
8
|
import { checkCommunicationEnvironment, CommunicationEnvironmentError } from './preflight.js';
|
|
9
9
|
import { requireCommunicationCli } from './worker.js';
|
|
10
10
|
|
|
11
|
-
export async function attachCommunicationWorkers(spec: KernelRunSpec, socketPath: string, dataDir: string
|
|
11
|
+
export async function attachCommunicationWorkers(spec: KernelRunSpec, socketPath: string, dataDir: string,
|
|
12
|
+
environment?: NodeJS.ProcessEnv) {
|
|
12
13
|
const steps = spec.steps.filter(step => step.type === 'agent' && communicationInstruction(step.instruction));
|
|
13
14
|
checkCommunicationEnvironment(spec);
|
|
14
15
|
try { await loadRelayModules(); }
|
|
@@ -28,7 +29,7 @@ export async function attachCommunicationWorkers(spec: KernelRunSpec, socketPath
|
|
|
28
29
|
requireCommunicationCli(step.cli ?? spec.cli);
|
|
29
30
|
if (step.surfaces?.workspace?.length) throw new Error('Local communication workers support stream surfaces only');
|
|
30
31
|
const client = new JournalClient(socketPath);
|
|
31
|
-
const worker = new AgentWorker(client, { workerId: `communication-${randomUUID()}`, dataDir,
|
|
32
|
+
const worker = new AgentWorker(client, { workerId: `communication-${randomUUID()}`, dataDir, environment,
|
|
32
33
|
requiredStreams: [channelName(step.id, '$receipts')],
|
|
33
34
|
pins: { workspace: [], streams: step.surfaces?.streams?.map(({ stream }) => ({ stream, read_offset: 0 })) ?? [] } });
|
|
34
35
|
workers.push({ client, worker });
|
|
@@ -18,13 +18,15 @@ export function requireCommunicationCli(cli: string | undefined): void {
|
|
|
18
18
|
if (!cli?.trim()) throw new Error('Agent communication requires a declared CLI executable');
|
|
19
19
|
}
|
|
20
20
|
export async function completeCommunicationDispatch(client: JournalClient, dispatch: StepDispatchEvent,
|
|
21
|
-
instruction: CommunicationInstruction, dataDir: string, runRoot?: string
|
|
21
|
+
instruction: CommunicationInstruction, dataDir: string, runRoot?: string,
|
|
22
|
+
environment?: NodeJS.ProcessEnv): Promise<void> {
|
|
22
23
|
const spec = dispatch.spec as KernelAgentStep;
|
|
23
24
|
requireCommunicationCli(spec.cli);
|
|
24
25
|
let output: unknown;
|
|
25
26
|
let completionReason: 'success' | 'worker_error' = 'success';
|
|
26
27
|
try {
|
|
27
|
-
output = await withWorkerLease(client, dispatch, signal =>
|
|
28
|
+
output = await withWorkerLease(client, dispatch, signal =>
|
|
29
|
+
run(client, dispatch, instruction, spec, dataDir, signal, runRoot, environment));
|
|
28
30
|
} catch (error) {
|
|
29
31
|
completionReason = 'worker_error';
|
|
30
32
|
output = { error: error instanceof Error ? error.message : String(error) };
|
|
@@ -34,7 +36,8 @@ export async function completeCommunicationDispatch(client: JournalClient, dispa
|
|
|
34
36
|
started_pins: dispatch.pins, end_pins: dispatch.pins });
|
|
35
37
|
}
|
|
36
38
|
async function run(client: JournalClient, dispatch: StepDispatchEvent, instruction: CommunicationInstruction,
|
|
37
|
-
spec: KernelAgentStep, dataDir: string, lease: AbortSignal, runRoot?: string
|
|
39
|
+
spec: KernelAgentStep, dataDir: string, lease: AbortSignal, runRoot?: string,
|
|
40
|
+
environment?: NodeJS.ProcessEnv): Promise<unknown> {
|
|
38
41
|
// Same contract as the CLI worker: a declared directory is resolved and held
|
|
39
42
|
// inside the same run root the CLI worker measures against, before anything
|
|
40
43
|
// is spawned. Thrown, not reported, because `completeCommunicationDispatch`
|
|
@@ -80,7 +83,7 @@ async function run(client: JournalClient, dispatch: StepDispatchEvent, instructi
|
|
|
80
83
|
handle = await relay.broker.spawnPty({ name, cli: basename(spec.cli!).replace(/\.exe$/i, ''), task: prompt, channels: [], skipRelayPrompt: true,
|
|
81
84
|
model: resolveCliModel(spec.cli!, spec.model), cwd: directory,
|
|
82
85
|
harnessConfig: { runtime: 'pty', command: quote(spec.cli!), args: [],
|
|
83
|
-
cwd: directory, env: { ...agentEnvironment(spec.cli
|
|
86
|
+
cwd: directory, env: { ...agentEnvironment(spec.cli!, environment ?? process.env),
|
|
84
87
|
RELAYFLOW_COMMUNICATION_SOCKET: tools.path, RELAYFLOW_COMMUNICATION_TOKEN: tools.token },
|
|
85
88
|
delivery: { mode: 'pty-injection', format: 'relay-block' } } });
|
|
86
89
|
const ready = await handle.waitForReady(Math.min(instruction.timeoutMs, 90_000));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Exact decimal-dollar arithmetic shared by the journal folds.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sum two decimal dollar strings exactly, at whatever scale they carry — the
|
|
5
|
+
* kernel's budget fold (`relayflowd-core/src/state/budget.rs`) aligns every
|
|
6
|
+
* fractional digit before adding, and it accepts arbitrary precision. Scaling
|
|
7
|
+
* to a fixed six fractional digits dropped a charge of `0.0000001` to zero, so
|
|
8
|
+
* `flows status` underreported spend the kernel had totalled exactly.
|
|
9
|
+
*
|
|
10
|
+
* A value that is not a decimal number is left out of the sum rather than
|
|
11
|
+
* counted as zero, and the caller is told by `malformed`.
|
|
12
|
+
*/
|
|
13
|
+
export function addDollars(left: string, right: string): string {
|
|
14
|
+
const parsed = [left, right].map((value) => /^\d+(?:\.\d+)?$/.test(value) ? value : null);
|
|
15
|
+
const usable = parsed.filter((value): value is string => value !== null);
|
|
16
|
+
if (usable.length === 0) return '0';
|
|
17
|
+
if (usable.length === 1) return normalizeDollars(usable[0]!);
|
|
18
|
+
const scale = Math.max(...usable.map((value) => (value.split('.')[1] ?? '').length));
|
|
19
|
+
const scaled = usable.map((value) => {
|
|
20
|
+
const [whole, fraction = ''] = value.split('.');
|
|
21
|
+
return BigInt(`${whole}${fraction.padEnd(scale, '0')}`);
|
|
22
|
+
});
|
|
23
|
+
return fromScaled(scaled[0]! + scaled[1]!, scale);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `12.3400` -> `12.34`, `0.000` -> `0`; the journal's own spelling otherwise. */
|
|
27
|
+
function normalizeDollars(value: string): string {
|
|
28
|
+
const [whole, fraction = ''] = value.split('.');
|
|
29
|
+
return fromScaled(BigInt(`${whole}${fraction}`), fraction.length);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fromScaled(total: bigint, scale: number): string {
|
|
33
|
+
if (scale === 0) return String(total);
|
|
34
|
+
const digits = String(total).padStart(scale + 1, '0');
|
|
35
|
+
const whole = digits.slice(0, digits.length - scale);
|
|
36
|
+
const fraction = digits.slice(digits.length - scale).replace(/0+$/, '');
|
|
37
|
+
return fraction.length === 0 ? whole : `${whole}.${fraction}`;
|
|
38
|
+
}
|
package/src/effect-channel.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { JournalClient } from './journal-client.js';
|
|
|
4
4
|
import type { StepDispatchEvent } from './protocol.js';
|
|
5
5
|
import { SPEC_SCHEMA_VERSION } from './spec.js';
|
|
6
6
|
import { withWorkerLease } from './worker-lease.js';
|
|
7
|
+
import { NO_MODEL_COST } from './reported-cost.js';
|
|
7
8
|
import { snapshotJsonValue } from './json-value.js';
|
|
8
9
|
|
|
9
10
|
/** Structural subset of Agent Relay's messages.dm; credentials stay in the client. */
|
|
@@ -93,5 +94,6 @@ export async function completeChannelPost(
|
|
|
93
94
|
messageId, channel: call.channel, to: call.to, text: call.text },
|
|
94
95
|
started_pins: dispatch.pins, end_pins: dispatch.pins,
|
|
95
96
|
effects: [{ surface_path: surfacePath, idempotency_key: dispatch.idempotency_key }],
|
|
97
|
+
reported_cost: NO_MODEL_COST,
|
|
96
98
|
});
|
|
97
99
|
}
|
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, ReportedCost } from './protocol.js';
|
|
17
17
|
import {
|
|
18
18
|
PROTOCOL_VERSION,
|
|
19
19
|
type CompletionReason,
|
|
@@ -61,6 +61,8 @@ export class JournalProtocolError extends Error {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export class JournalClient extends EventEmitter {
|
|
64
|
+
/** Additive request capabilities the daemon advertised at `hello`; none until then. */
|
|
65
|
+
private features: ReadonlySet<string> = new Set();
|
|
64
66
|
private socket: Socket | null = null;
|
|
65
67
|
/** Why the connection ended, so a later "not connected" names its cause rather than hiding it. */
|
|
66
68
|
private disconnectCause: Error | undefined;
|
|
@@ -215,9 +217,11 @@ export class JournalClient extends EventEmitter {
|
|
|
215
217
|
|
|
216
218
|
// --- Typed verb methods (gate 1 minimal set, kernel DESIGN.md §5) --------
|
|
217
219
|
|
|
218
|
-
/** Handshake; version mismatch is a hard error. */
|
|
219
|
-
hello(client: string): Promise<VerbContract['hello']['result']> {
|
|
220
|
-
|
|
220
|
+
/** Handshake; version mismatch is a hard error. Records the daemon's additive features. */
|
|
221
|
+
async hello(client: string): Promise<VerbContract['hello']['result']> {
|
|
222
|
+
const result = await this.request('hello', { protocol: PROTOCOL_VERSION, client });
|
|
223
|
+
this.features = new Set(Array.isArray(result.features) ? result.features.filter(f => typeof f === 'string') : []);
|
|
224
|
+
return result;
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
/**
|
|
@@ -403,15 +407,20 @@ export class JournalClient extends EventEmitter {
|
|
|
403
407
|
effects?: EffectRef[];
|
|
404
408
|
trajectory_tail?: unknown;
|
|
405
409
|
human_intervention?: boolean;
|
|
410
|
+
reported_cost?: ReportedCost;
|
|
406
411
|
} = {},
|
|
407
412
|
): Promise<VerbContract['step.complete']['result']> {
|
|
413
|
+
// `reported_cost` is display-only, and a daemon that predates it refuses
|
|
414
|
+
// unknown completion fields: drop it rather than fail the completion.
|
|
415
|
+
const { reported_cost: reportedCost, ...rest } = extra;
|
|
408
416
|
return this.request('step.complete', {
|
|
409
417
|
run_id: runId,
|
|
410
418
|
step_id: stepId,
|
|
411
419
|
attempt,
|
|
412
420
|
idempotency_key: idempotencyKey,
|
|
413
421
|
completionReason,
|
|
414
|
-
...
|
|
422
|
+
...rest,
|
|
423
|
+
...(reportedCost !== undefined && this.features.has('reported_cost') ? { reported_cost: reportedCost } : {}),
|
|
415
424
|
}, null).catch(error => {
|
|
416
425
|
if (error instanceof JournalProtocolError) error.verb = 'step.complete';
|
|
417
426
|
throw error;
|
|
@@ -442,8 +451,17 @@ export class JournalClient extends EventEmitter {
|
|
|
442
451
|
});
|
|
443
452
|
}
|
|
444
453
|
|
|
445
|
-
eventEmit(
|
|
446
|
-
|
|
454
|
+
eventEmit(
|
|
455
|
+
runId: string,
|
|
456
|
+
eventKey: string,
|
|
457
|
+
payload: unknown,
|
|
458
|
+
options: Pick<EventEmitParams, 'delivery_id' | 'actor'> = {},
|
|
459
|
+
): Promise<VerbContract['event.emit']['result']> {
|
|
460
|
+
return this.request('event.emit', { run_id: runId, event_key: eventKey, payload, ...options });
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
subscriptionPark(params: VerbContract['subscription.park']['params']): Promise<VerbContract['subscription.park']['result']> {
|
|
464
|
+
return this.request('subscription.park', params);
|
|
447
465
|
}
|
|
448
466
|
|
|
449
467
|
/**
|
|
@@ -457,6 +475,41 @@ export class JournalClient extends EventEmitter {
|
|
|
457
475
|
return this.request('event.submit', { spec, event });
|
|
458
476
|
}
|
|
459
477
|
|
|
478
|
+
/** Prepare a body subscription. Cloud must activate the returned request before a body can observe it. */
|
|
479
|
+
subscriptionOpen(params: VerbContract['subscription.open']['params']): Promise<VerbContract['subscription.open']['result']> {
|
|
480
|
+
return this.request('subscription.open', params, null);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Commit Cloud's durable binding receipt and ingress fence. */
|
|
484
|
+
subscriptionActivate(params: VerbContract['subscription.activate']['params']): Promise<VerbContract['subscription.activate']['result']> {
|
|
485
|
+
return this.request('subscription.activate', params, null);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Append to one activated subscription under its immutable router receipt. */
|
|
489
|
+
subscriptionDeliver(params: VerbContract['subscription.deliver']['params']): Promise<VerbContract['subscription.deliver']['result']> {
|
|
490
|
+
return this.request('subscription.deliver', params, null);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Read durable subscription state without changing timers or ingress. */
|
|
494
|
+
subscriptionInspect(params: VerbContract['subscription.inspect']['params']): Promise<VerbContract['subscription.inspect']['result']> {
|
|
495
|
+
return this.request('subscription.inspect', params);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** Fence the exact activated receipt after router-side overflow. */
|
|
499
|
+
subscriptionFenceOverflow(params: VerbContract['subscription.fence_overflow']['params']): Promise<VerbContract['subscription.fence_overflow']['result']> {
|
|
500
|
+
return this.request('subscription.fence_overflow', params, null);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Return a durable wake, or an explicit suspension with no daemon-side sleep. */
|
|
504
|
+
subscriptionNext(params: VerbContract['subscription.next']['params']): Promise<VerbContract['subscription.next']['result']> {
|
|
505
|
+
return this.request('subscription.next', params, null);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Close a body subscription; the server refuses later external appends. */
|
|
509
|
+
subscriptionClose(params: VerbContract['subscription.close']['params']): Promise<VerbContract['subscription.close']['result']> {
|
|
510
|
+
return this.request('subscription.close', params, null);
|
|
511
|
+
}
|
|
512
|
+
|
|
460
513
|
channelAppend(params: VerbContract['channel.append']['params']): Promise<VerbContract['channel.append']['result']> {
|
|
461
514
|
return this.request('channel.append', params);
|
|
462
515
|
}
|
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/llm-worker.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { workerSpend } from './worker-spend.js';
|
|
2
|
+
import { reportedCost } from './reported-cost.js';
|
|
2
3
|
import { DEFAULT_LOCAL_AGENT_CAPACITY } from './worker-slots.js';
|
|
3
4
|
import type { WorkerCliResult } from './worker-cli.js';
|
|
4
5
|
import { EventEmitter } from 'node:events';
|
|
@@ -22,6 +23,7 @@ export class LlmWorker extends EventEmitter {
|
|
|
22
23
|
private readonly client: JournalClient,
|
|
23
24
|
private readonly workerId: string,
|
|
24
25
|
private readonly capacity: number = DEFAULT_LOCAL_AGENT_CAPACITY,
|
|
26
|
+
private readonly environment?: NodeJS.ProcessEnv,
|
|
25
27
|
) {
|
|
26
28
|
super();
|
|
27
29
|
}
|
|
@@ -59,9 +61,11 @@ export class LlmWorker extends EventEmitter {
|
|
|
59
61
|
const effectiveModel = typeof spec.cli === 'string' ? resolveCliModel(spec.cli, spec.model) : spec.model;
|
|
60
62
|
const completed: WorkerCliResult = await withWorkerLease(this.client, dispatch, signal =>
|
|
61
63
|
typeof spec.cli === 'string' && typeof spec.prompt === 'string'
|
|
62
|
-
? runAgentCli(spec.cli, workerInstruction(prompt, dispatch), dispatch.wake_context, effectiveModel, undefined, signal, 'llm'
|
|
64
|
+
? runAgentCli(spec.cli, workerInstruction(prompt, dispatch), dispatch.wake_context, effectiveModel, undefined, signal, 'llm',
|
|
65
|
+
undefined, undefined, 'direct', undefined, this.environment)
|
|
63
66
|
: Promise.resolve({ exit_code: null, stdout_tail: '', stderr_tail: 'llm step has no declared CLI' }));
|
|
64
67
|
const { result, usage } = workerSpend(completed, effectiveModel);
|
|
68
|
+
const cost = reportedCost(completed, effectiveModel);
|
|
65
69
|
let reason: CompletionReason = result.exit_code === 0 ? 'success' : 'worker_error';
|
|
66
70
|
let output: unknown = result.stdout_tail;
|
|
67
71
|
let detail = result.stderr_tail;
|
|
@@ -94,6 +98,7 @@ export class LlmWorker extends EventEmitter {
|
|
|
94
98
|
dispatch.idempotency_key, reason, {
|
|
95
99
|
output,
|
|
96
100
|
...(usage !== undefined ? { usage } : {}),
|
|
101
|
+
...(cost === undefined ? {} : { reported_cost: cost }),
|
|
97
102
|
...(Object.keys(trajectoryTail).length === 0 ? {} : { trajectory_tail: trajectoryTail }),
|
|
98
103
|
});
|
|
99
104
|
}
|