@relayflows/sdk 2.0.20 → 2.0.22

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.
Files changed (107) hide show
  1. package/dist/agent-reaper.d.ts +4 -0
  2. package/dist/agent-reaper.d.ts.map +1 -0
  3. package/dist/agent-reaper.js +53 -0
  4. package/dist/agent-reaper.js.map +1 -0
  5. package/dist/agent-transcript.d.ts +186 -0
  6. package/dist/agent-transcript.d.ts.map +1 -0
  7. package/dist/agent-transcript.js +585 -0
  8. package/dist/agent-transcript.js.map +1 -0
  9. package/dist/child-stop.d.ts +1 -1
  10. package/dist/child-stop.d.ts.map +1 -1
  11. package/dist/child-stop.js +68 -12
  12. package/dist/child-stop.js.map +1 -1
  13. package/dist/cli/run.js +2 -1
  14. package/dist/cli/run.js.map +1 -1
  15. package/dist/cli/status.d.ts +34 -0
  16. package/dist/cli/status.d.ts.map +1 -0
  17. package/dist/cli/status.js +307 -0
  18. package/dist/cli/status.js.map +1 -0
  19. package/dist/cli/step-failure.js +31 -7
  20. package/dist/cli/step-failure.js.map +1 -1
  21. package/dist/cli-commands.d.ts +13 -0
  22. package/dist/cli-commands.d.ts.map +1 -1
  23. package/dist/cli-commands.js +11 -0
  24. package/dist/cli-commands.js.map +1 -1
  25. package/dist/cli.d.ts +2 -1
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +9 -0
  28. package/dist/cli.js.map +1 -1
  29. package/dist/failure-kinds.d.ts +15 -2
  30. package/dist/failure-kinds.d.ts.map +1 -1
  31. package/dist/failure-kinds.js +8 -0
  32. package/dist/failure-kinds.js.map +1 -1
  33. package/dist/journal-reader.d.ts +1 -1
  34. package/dist/journal-reader.d.ts.map +1 -1
  35. package/dist/journal-reader.js +3 -1
  36. package/dist/journal-reader.js.map +1 -1
  37. package/dist/llm-worker.d.ts.map +1 -1
  38. package/dist/llm-worker.js +12 -1
  39. package/dist/llm-worker.js.map +1 -1
  40. package/dist/permissions-preflight.d.ts +22 -0
  41. package/dist/permissions-preflight.d.ts.map +1 -0
  42. package/dist/permissions-preflight.js +49 -0
  43. package/dist/permissions-preflight.js.map +1 -0
  44. package/dist/preflight.d.ts.map +1 -1
  45. package/dist/preflight.js +6 -0
  46. package/dist/preflight.js.map +1 -1
  47. package/dist/pty-sidechannel.d.ts +7 -0
  48. package/dist/pty-sidechannel.d.ts.map +1 -1
  49. package/dist/pty-sidechannel.js.map +1 -1
  50. package/dist/redact.d.ts +16 -0
  51. package/dist/redact.d.ts.map +1 -0
  52. package/dist/redact.js +104 -0
  53. package/dist/redact.js.map +1 -0
  54. package/dist/run-state.d.ts +124 -0
  55. package/dist/run-state.d.ts.map +1 -0
  56. package/dist/run-state.js +368 -0
  57. package/dist/run-state.js.map +1 -0
  58. package/dist/spec.d.ts +16 -1
  59. package/dist/spec.d.ts.map +1 -1
  60. package/dist/spec.js.map +1 -1
  61. package/dist/step-env.d.ts +25 -0
  62. package/dist/step-env.d.ts.map +1 -0
  63. package/dist/step-env.js +34 -0
  64. package/dist/step-env.js.map +1 -0
  65. package/dist/transcript-tail.d.ts +54 -0
  66. package/dist/transcript-tail.d.ts.map +1 -0
  67. package/dist/transcript-tail.js +176 -0
  68. package/dist/transcript-tail.js.map +1 -0
  69. package/dist/worker-cli.d.ts +18 -0
  70. package/dist/worker-cli.d.ts.map +1 -1
  71. package/dist/worker-cli.js +219 -22
  72. package/dist/worker-cli.js.map +1 -1
  73. package/dist/worker-usage.d.ts +15 -2
  74. package/dist/worker-usage.d.ts.map +1 -1
  75. package/dist/worker-usage.js +40 -3
  76. package/dist/worker-usage.js.map +1 -1
  77. package/dist/worker.d.ts.map +1 -1
  78. package/dist/worker.js +31 -6
  79. package/dist/worker.js.map +1 -1
  80. package/dist/wrapper-runtime.d.ts +7 -1
  81. package/dist/wrapper-runtime.d.ts.map +1 -1
  82. package/dist/wrapper-runtime.js +7 -1
  83. package/dist/wrapper-runtime.js.map +1 -1
  84. package/package.json +2 -2
  85. package/src/agent-reaper.ts +52 -0
  86. package/src/agent-transcript.ts +651 -0
  87. package/src/child-stop.ts +62 -12
  88. package/src/cli/run.ts +2 -1
  89. package/src/cli/status.ts +329 -0
  90. package/src/cli/step-failure.ts +32 -7
  91. package/src/cli-commands.ts +11 -0
  92. package/src/cli.ts +8 -0
  93. package/src/failure-kinds.ts +15 -1
  94. package/src/journal-reader.ts +4 -2
  95. package/src/llm-worker.ts +12 -1
  96. package/src/permissions-preflight.ts +53 -0
  97. package/src/preflight.ts +6 -0
  98. package/src/pty-sidechannel.ts +7 -0
  99. package/src/redact.ts +106 -0
  100. package/src/run-state.ts +468 -0
  101. package/src/spec.ts +16 -1
  102. package/src/step-env.ts +47 -0
  103. package/src/transcript-tail.ts +193 -0
  104. package/src/worker-cli.ts +213 -21
  105. package/src/worker-usage.ts +39 -3
  106. package/src/worker.ts +32 -6
  107. package/src/wrapper-runtime.ts +7 -1
@@ -1,12 +1,21 @@
1
1
  import type { WorkerCliResult } from './worker-cli.js';
2
2
  import { MODEL_PRICING } from './model-pricing.js';
3
+ import { FAILURE_EXCERPT_MAX_BYTES, buildTranscriptDigest, redactText, utf8Tail } from './agent-transcript.js';
3
4
 
4
5
  type RecordValue = Record<string, unknown>;
5
6
  const record = (value: unknown): value is RecordValue => typeof value === 'object' && value !== null && !Array.isArray(value);
6
7
  const count = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
7
8
 
9
+ /**
10
+ * A completion this decoder refuses. The refusal is appended to stderr, and a
11
+ * digest already built from the frames gets it as its failure — otherwise the
12
+ * digest would still describe the attempt as the provider reported it.
13
+ */
8
14
  function invalid(result: WorkerCliResult, detail: string): WorkerCliResult {
9
- return { ...result, exit_code: null, stderr_tail: `${result.stderr_tail}\n${detail}`.trim() };
15
+ const stderr_tail = `${result.stderr_tail}\n${detail}`.trim();
16
+ const transcript = result.transcript === undefined ? undefined : { ...result.transcript,
17
+ failure: { kind: 'stderr' as const, excerpt: utf8Tail(redactText(stderr_tail), FAILURE_EXCERPT_MAX_BYTES) } };
18
+ return { ...result, exit_code: null, stderr_tail, ...(transcript === undefined ? {} : { transcript }) };
10
19
  }
11
20
 
12
21
  function usageResult(result: WorkerCliResult, usage: unknown, output: unknown): WorkerCliResult {
@@ -18,12 +27,26 @@ function usageResult(result: WorkerCliResult, usage: unknown, output: unknown):
18
27
  tokens_input: usage.input_tokens, tokens_output: usage.output_tokens };
19
28
  }
20
29
 
21
- /** Provider envelopes are execution metadata, never the authored output. */
22
- export function decodeProviderResult(result: WorkerCliResult, kind: 'claude' | 'codex'): WorkerCliResult {
30
+ /**
31
+ * Provider envelopes are execution metadata, never the authored output. The
32
+ * same frames also yield the transcript digest (`agent-transcript.ts`): the
33
+ * metered `tokens_input/output` stay the budget source, and the provider's own
34
+ * cost, model, tool calls and failure ride beside them in `transcript`.
35
+ */
36
+ export function decodeProviderResult(
37
+ result: WorkerCliResult,
38
+ kind: 'claude' | 'codex',
39
+ env: NodeJS.ProcessEnv = process.env,
40
+ ): WorkerCliResult {
23
41
  const frames: RecordValue[] = [];
24
42
  for (const line of result.stdout_tail.split('\n')) {
25
43
  try { const frame: unknown = JSON.parse(line); if (record(frame)) frames.push(frame); } catch { /* Plain text remains output. */ }
26
44
  }
45
+ const decoded = decodeUsage(result, frames, kind);
46
+ return { ...decoded, transcript: { ...result.transcript, ...buildTranscriptDigest(frames, kind, decoded, env) } };
47
+ }
48
+
49
+ function decodeUsage(result: WorkerCliResult, frames: RecordValue[], kind: 'claude' | 'codex'): WorkerCliResult {
27
50
  const terminal = [...frames].reverse().find(f => kind === 'claude' ? f.type === 'result' : f.type === 'turn.completed');
28
51
  if (terminal === undefined) return result;
29
52
  const text = kind === 'claude' ? terminal.result : frames
@@ -31,6 +54,19 @@ export function decodeProviderResult(result: WorkerCliResult, kind: 'claude' | '
31
54
  return usageResult(result, terminal.usage, text);
32
55
  }
33
56
 
57
+ /**
58
+ * Claude's terminal `stream-json` frame, recognised line by line as it
59
+ * streams. `failed` mirrors the exit status Claude gives that result: an
60
+ * error flag or a non-success subtype is a failed run.
61
+ */
62
+ export function claudeResultOutcome(line: string): { failed: boolean } | undefined {
63
+ if (!line.includes('"result"')) return undefined;
64
+ let frame: unknown;
65
+ try { frame = JSON.parse(line); } catch { return undefined; }
66
+ if (!record(frame) || frame.type !== 'result') return undefined;
67
+ return { failed: frame.is_error === true || (typeof frame.subtype === 'string' && frame.subtype !== 'success') };
68
+ }
69
+
34
70
  /** Optional wrapper result envelope; existing opaque text output stays valid. */
35
71
  export function decodeWrapperResult(result: WorkerCliResult): WorkerCliResult {
36
72
  let value: unknown;
package/src/worker.ts CHANGED
@@ -10,6 +10,7 @@ import { withWorkerLease } from './worker-lease.js';
10
10
  import { workerInstruction } from './worker-input.js';
11
11
  import { helperCall } from './yaml-helpers.js';
12
12
  import { completeHelperDispatch } from './yaml-helper-effect.js';
13
+ import { ARTIFACT_PATHS_MAX, boundTranscriptDigest, type TranscriptDigest } from './agent-transcript.js';
13
14
 
14
15
  export { MODEL_ENV, WAKE_CONTEXT_ENV } from './worker-cli.js';
15
16
 
@@ -110,7 +111,7 @@ export class AgentWorker extends EventEmitter {
110
111
  const completed: WorkerCliResult = await withWorkerLease(this.client, dispatch, signal =>
111
112
  typeof spec.cli === 'string' && typeof spec.instruction === 'string'
112
113
  ? runAgentCli(spec.cli, workerInstruction(spec.instruction, dispatch), dispatch.wake_context, effectiveModel, undefined, signal, 'agent', this.options.dataDir === undefined ? undefined : {
113
- dataDir: this.options.dataDir, runId: dispatch.run_id, stepId: dispatch.step_id,
114
+ dataDir: this.options.dataDir, runId: dispatch.run_id, stepId: dispatch.step_id, attempt: dispatch.attempt,
114
115
  onReady: this.options.onPtyReady, onDrive: () => { humanIntervention = true; },
115
116
  }, typeof spec.cwd === 'string' ? spec.cwd : undefined,
116
117
  spec.transport === 'relay' ? 'relay' : 'direct',
@@ -142,8 +143,20 @@ export class AgentWorker extends EventEmitter {
142
143
  // or `summary` never asked for. An agent that answers with a JSON object
143
144
  // therefore journals no artifacts; gate such a step on a deterministic
144
145
  // check instead. The relay transport reports none (the agent ran elsewhere).
146
+ //
147
+ // `transcript` is not part of the wrapper either: it is evidence about the
148
+ // attempt, journaled in `trajectory_tail` below on success and failure
149
+ // alike, where the kernel already accepts and bounds it (16 KiB).
150
+ const { transcript, ...wrapper } = result;
145
151
  const output = result.relay_task?.status === 'completed' && result.exit_code === 0
146
- ? result.relay_task.output : parseJsonOutput(result.stdout_tail) ?? result;
152
+ ? result.relay_task.output : parseJsonOutput(result.stdout_tail) ?? wrapper;
153
+ const trajectoryTail = {
154
+ ...(result.relay_task === undefined ? {} : { relay_task: {
155
+ invocation_id: result.relay_task.invocation_id, status: result.relay_task.status,
156
+ task_execution: result.relay_task.task_execution, error: result.relay_task.error,
157
+ } }),
158
+ ...(transcript === undefined ? {} : { transcript: transcriptDigest(transcript, dispatch.attempt, result) }),
159
+ };
147
160
 
148
161
  await this.client.stepComplete(
149
162
  dispatch.run_id,
@@ -153,10 +166,7 @@ export class AgentWorker extends EventEmitter {
153
166
  completionReason,
154
167
  {
155
168
  output,
156
- ...(result.relay_task === undefined ? {} : { trajectory_tail: { relay_task: {
157
- invocation_id: result.relay_task.invocation_id, status: result.relay_task.status,
158
- task_execution: result.relay_task.task_execution, error: result.relay_task.error,
159
- } } }),
169
+ ...(Object.keys(trajectoryTail).length === 0 ? {} : { trajectory_tail: trajectoryTail }),
160
170
  ...(humanIntervention ? { human_intervention: true } : {}),
161
171
  ...(usage !== undefined ? { usage } : {}),
162
172
  started_pins: dispatch.pins,
@@ -166,6 +176,22 @@ export class AgentWorker extends EventEmitter {
166
176
  }
167
177
  }
168
178
 
179
+ /**
180
+ * The digest as journaled: the attempt and exit code (dropped from `output` on
181
+ * the JSON path by design) and the artifact paths folded in, then bounded to
182
+ * `TRANSCRIPT_DIGEST_MAX_BYTES` with every cut stated on the digest itself.
183
+ */
184
+ function transcriptDigest(transcript: TranscriptDigest, attempt: number, result: WorkerCliResult): TranscriptDigest {
185
+ return boundTranscriptDigest({
186
+ attempt,
187
+ exit_code: result.exit_code,
188
+ ...transcript,
189
+ ...(result.artifacts === undefined ? {} : { artifacts: {
190
+ count: result.artifacts.length, paths: result.artifacts.slice(0, ARTIFACT_PATHS_MAX),
191
+ } }),
192
+ });
193
+ }
194
+
169
195
  /**
170
196
  * Return an object-shaped JSON payload parsed from `stdout`, or
171
197
  * `null` when stdout is empty, non-JSON, or JSON-of-a-scalar/array.
@@ -25,7 +25,13 @@ export interface WrapperIdentity {
25
25
  fingerprint: string;
26
26
  }
27
27
 
28
- /** Build a wrapper environment from a closed list; ambient credentials never cross. */
28
+ /**
29
+ * Build a wrapper environment from a closed list; ambient credentials never
30
+ * cross. The step discovery names (`RELAYFLOW_DATA_DIR`, `RELAYFLOW_RUN_ID`,
31
+ * `RELAYFLOW_STEP_ID`, `RELAYFLOW_ATTEMPT`) are deliberately not on the list:
32
+ * an ambient value is a parent step's identity, not this one's, so the worker
33
+ * sets them from the dispatch after this filter (step-env.ts).
34
+ */
29
35
  export function wrapperEnvironment(source: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
30
36
  const env: NodeJS.ProcessEnv = {};
31
37
  for (const name of WRAPPER_ENV_ALLOWLIST) {