@relayflows/sdk 2.0.21 → 2.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/dist/agent-artifacts.d.ts +18 -9
  2. package/dist/agent-artifacts.d.ts.map +1 -1
  3. package/dist/agent-artifacts.js +27 -11
  4. package/dist/agent-artifacts.js.map +1 -1
  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/authored-flow-error.d.ts +33 -1
  10. package/dist/authored-flow-error.d.ts.map +1 -1
  11. package/dist/authored-flow-error.js +20 -1
  12. package/dist/authored-flow-error.js.map +1 -1
  13. package/dist/authored-flow-executor.d.ts.map +1 -1
  14. package/dist/authored-flow-executor.js +4 -1
  15. package/dist/authored-flow-executor.js.map +1 -1
  16. package/dist/authored-node-entry.js +5 -1
  17. package/dist/authored-node-entry.js.map +1 -1
  18. package/dist/authored-node-runner.d.ts +11 -0
  19. package/dist/authored-node-runner.d.ts.map +1 -1
  20. package/dist/authored-node-runner.js +30 -1
  21. package/dist/authored-node-runner.js.map +1 -1
  22. package/dist/authored-root.js +3 -1
  23. package/dist/authored-root.js.map +1 -1
  24. package/dist/authored-source-authority.js +13 -0
  25. package/dist/authored-source-authority.js.map +1 -1
  26. package/dist/authored-step-index.d.ts +82 -0
  27. package/dist/authored-step-index.d.ts.map +1 -0
  28. package/dist/authored-step-index.js +120 -0
  29. package/dist/authored-step-index.js.map +1 -0
  30. package/dist/authored-step-output.d.ts +23 -2
  31. package/dist/authored-step-output.d.ts.map +1 -1
  32. package/dist/authored-step-output.js +47 -5
  33. package/dist/authored-step-output.js.map +1 -1
  34. package/dist/authored-worker-step.d.ts +10 -2
  35. package/dist/authored-worker-step.d.ts.map +1 -1
  36. package/dist/authored-worker-step.js +81 -7
  37. package/dist/authored-worker-step.js.map +1 -1
  38. package/dist/cli/check.d.ts.map +1 -1
  39. package/dist/cli/check.js +67 -12
  40. package/dist/cli/check.js.map +1 -1
  41. package/dist/cli/cloud-read.d.ts +31 -0
  42. package/dist/cli/cloud-read.d.ts.map +1 -0
  43. package/dist/cli/cloud-read.js +482 -0
  44. package/dist/cli/cloud-read.js.map +1 -0
  45. package/dist/cli/run.d.ts +2 -0
  46. package/dist/cli/run.d.ts.map +1 -1
  47. package/dist/cli/run.js +53 -24
  48. package/dist/cli/run.js.map +1 -1
  49. package/dist/cli/status.d.ts +43 -0
  50. package/dist/cli/status.d.ts.map +1 -0
  51. package/dist/cli/status.js +321 -0
  52. package/dist/cli/status.js.map +1 -0
  53. package/dist/cli/step-failure.d.ts +26 -0
  54. package/dist/cli/step-failure.d.ts.map +1 -1
  55. package/dist/cli/step-failure.js +99 -10
  56. package/dist/cli/step-failure.js.map +1 -1
  57. package/dist/cli-commands.d.ts +41 -0
  58. package/dist/cli-commands.d.ts.map +1 -1
  59. package/dist/cli-commands.js +33 -0
  60. package/dist/cli-commands.js.map +1 -1
  61. package/dist/cli.d.ts +4 -2
  62. package/dist/cli.d.ts.map +1 -1
  63. package/dist/cli.js +26 -0
  64. package/dist/cli.js.map +1 -1
  65. package/dist/cloud-http.d.ts +12 -0
  66. package/dist/cloud-http.d.ts.map +1 -1
  67. package/dist/cloud-http.js +14 -6
  68. package/dist/cloud-http.js.map +1 -1
  69. package/dist/cloud-read.d.ts +154 -0
  70. package/dist/cloud-read.d.ts.map +1 -0
  71. package/dist/cloud-read.js +373 -0
  72. package/dist/cloud-read.js.map +1 -0
  73. package/dist/cloud-transcript.d.ts +94 -0
  74. package/dist/cloud-transcript.d.ts.map +1 -0
  75. package/dist/cloud-transcript.js +342 -0
  76. package/dist/cloud-transcript.js.map +1 -0
  77. package/dist/communication/environment.d.ts +3 -0
  78. package/dist/communication/environment.d.ts.map +1 -0
  79. package/dist/communication/environment.js +34 -0
  80. package/dist/communication/environment.js.map +1 -0
  81. package/dist/communication/history.d.ts +6 -0
  82. package/dist/communication/history.d.ts.map +1 -0
  83. package/dist/communication/history.js +38 -0
  84. package/dist/communication/history.js.map +1 -0
  85. package/dist/communication/local.d.ts +6 -0
  86. package/dist/communication/local.d.ts.map +1 -0
  87. package/dist/communication/local.js +56 -0
  88. package/dist/communication/local.js.map +1 -0
  89. package/dist/communication/preflight.d.ts +6 -0
  90. package/dist/communication/preflight.d.ts.map +1 -0
  91. package/dist/communication/preflight.js +37 -0
  92. package/dist/communication/preflight.js.map +1 -0
  93. package/dist/communication/projection.d.ts +8 -0
  94. package/dist/communication/projection.d.ts.map +1 -0
  95. package/dist/communication/projection.js +23 -0
  96. package/dist/communication/projection.js.map +1 -0
  97. package/dist/communication/relay.d.ts +78 -0
  98. package/dist/communication/relay.d.ts.map +1 -0
  99. package/dist/communication/relay.js +89 -0
  100. package/dist/communication/relay.js.map +1 -0
  101. package/dist/communication/session.d.ts +24 -0
  102. package/dist/communication/session.d.ts.map +1 -0
  103. package/dist/communication/session.js +100 -0
  104. package/dist/communication/session.js.map +1 -0
  105. package/dist/communication/spec.d.ts +20 -0
  106. package/dist/communication/spec.d.ts.map +1 -0
  107. package/dist/communication/spec.js +113 -0
  108. package/dist/communication/spec.js.map +1 -0
  109. package/dist/communication/tool.d.ts +3 -0
  110. package/dist/communication/tool.d.ts.map +1 -0
  111. package/dist/communication/tool.js +23 -0
  112. package/dist/communication/tool.js.map +1 -0
  113. package/dist/communication/tools.d.ts +11 -0
  114. package/dist/communication/tools.d.ts.map +1 -0
  115. package/dist/communication/tools.js +65 -0
  116. package/dist/communication/tools.js.map +1 -0
  117. package/dist/communication/worker.d.ts +6 -0
  118. package/dist/communication/worker.d.ts.map +1 -0
  119. package/dist/communication/worker.js +120 -0
  120. package/dist/communication/worker.js.map +1 -0
  121. package/dist/compile.d.ts.map +1 -1
  122. package/dist/compile.js +8 -1
  123. package/dist/compile.js.map +1 -1
  124. package/dist/failure-kinds.d.ts +30 -2
  125. package/dist/failure-kinds.d.ts.map +1 -1
  126. package/dist/failure-kinds.js +9 -0
  127. package/dist/failure-kinds.js.map +1 -1
  128. package/dist/index.d.ts +3 -1
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +2 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/journal-client.d.ts +4 -1
  133. package/dist/journal-client.d.ts.map +1 -1
  134. package/dist/journal-client.js +11 -2
  135. package/dist/journal-client.js.map +1 -1
  136. package/dist/journal-reader.d.ts +1 -1
  137. package/dist/journal-reader.d.ts.map +1 -1
  138. package/dist/journal-reader.js +3 -1
  139. package/dist/journal-reader.js.map +1 -1
  140. package/dist/llm-worker.d.ts.map +1 -1
  141. package/dist/llm-worker.js +12 -1
  142. package/dist/llm-worker.js.map +1 -1
  143. package/dist/permissions-preflight.d.ts +22 -0
  144. package/dist/permissions-preflight.d.ts.map +1 -0
  145. package/dist/permissions-preflight.js +49 -0
  146. package/dist/permissions-preflight.js.map +1 -0
  147. package/dist/preflight.d.ts +13 -2
  148. package/dist/preflight.d.ts.map +1 -1
  149. package/dist/preflight.js +29 -6
  150. package/dist/preflight.js.map +1 -1
  151. package/dist/protocol.d.ts +39 -1
  152. package/dist/protocol.d.ts.map +1 -1
  153. package/dist/pty-sidechannel.d.ts +7 -0
  154. package/dist/pty-sidechannel.d.ts.map +1 -1
  155. package/dist/pty-sidechannel.js.map +1 -1
  156. package/dist/redact.d.ts +16 -0
  157. package/dist/redact.d.ts.map +1 -0
  158. package/dist/redact.js +104 -0
  159. package/dist/redact.js.map +1 -0
  160. package/dist/run-state.d.ts +124 -0
  161. package/dist/run-state.d.ts.map +1 -0
  162. package/dist/run-state.js +368 -0
  163. package/dist/run-state.js.map +1 -0
  164. package/dist/spec.d.ts +24 -1
  165. package/dist/spec.d.ts.map +1 -1
  166. package/dist/spec.js.map +1 -1
  167. package/dist/step-env.d.ts +25 -0
  168. package/dist/step-env.d.ts.map +1 -0
  169. package/dist/step-env.js +34 -0
  170. package/dist/step-env.js.map +1 -0
  171. package/dist/transcript-tail.d.ts +54 -0
  172. package/dist/transcript-tail.d.ts.map +1 -0
  173. package/dist/transcript-tail.js +176 -0
  174. package/dist/transcript-tail.js.map +1 -0
  175. package/dist/worker-cli.d.ts +10 -0
  176. package/dist/worker-cli.d.ts.map +1 -1
  177. package/dist/worker-cli.js +138 -17
  178. package/dist/worker-cli.js.map +1 -1
  179. package/dist/worker-usage.d.ts +7 -2
  180. package/dist/worker-usage.d.ts.map +1 -1
  181. package/dist/worker-usage.js +21 -3
  182. package/dist/worker-usage.js.map +1 -1
  183. package/dist/worker.d.ts +1 -0
  184. package/dist/worker.d.ts.map +1 -1
  185. package/dist/worker.js +41 -7
  186. package/dist/worker.js.map +1 -1
  187. package/dist/wrapper-runtime.d.ts +7 -1
  188. package/dist/wrapper-runtime.d.ts.map +1 -1
  189. package/dist/wrapper-runtime.js +7 -1
  190. package/dist/wrapper-runtime.js.map +1 -1
  191. package/dist/wrapper-session.d.ts +8 -0
  192. package/dist/wrapper-session.d.ts.map +1 -1
  193. package/dist/wrapper-session.js +156 -23
  194. package/dist/wrapper-session.js.map +1 -1
  195. package/package.json +14 -2
  196. package/src/agent-artifacts.ts +27 -11
  197. package/src/agent-transcript.ts +651 -0
  198. package/src/authored-flow-error.ts +17 -0
  199. package/src/authored-flow-executor.ts +4 -1
  200. package/src/authored-node-entry.ts +5 -1
  201. package/src/authored-node-runner.ts +33 -1
  202. package/src/authored-root.ts +2 -1
  203. package/src/authored-source-authority.ts +13 -0
  204. package/src/authored-step-index.ts +157 -0
  205. package/src/authored-step-output.ts +66 -14
  206. package/src/authored-worker-step.ts +88 -15
  207. package/src/cli/check.ts +69 -11
  208. package/src/cli/cloud-read.ts +518 -0
  209. package/src/cli/run.ts +43 -24
  210. package/src/cli/status.ts +349 -0
  211. package/src/cli/step-failure.ts +100 -10
  212. package/src/cli-commands.ts +33 -0
  213. package/src/cli.ts +27 -0
  214. package/src/cloud-http.ts +32 -6
  215. package/src/cloud-read.ts +515 -0
  216. package/src/cloud-transcript.ts +422 -0
  217. package/src/communication/environment.ts +33 -0
  218. package/src/communication/history.ts +38 -0
  219. package/src/communication/local.ts +41 -0
  220. package/src/communication/preflight.ts +34 -0
  221. package/src/communication/projection.ts +23 -0
  222. package/src/communication/relay.ts +102 -0
  223. package/src/communication/session.ts +84 -0
  224. package/src/communication/spec.ts +106 -0
  225. package/src/communication/tool.ts +22 -0
  226. package/src/communication/tools.ts +50 -0
  227. package/src/communication/worker.ts +95 -0
  228. package/src/compile.ts +4 -1
  229. package/src/failure-kinds.ts +31 -1
  230. package/src/index.ts +10 -1
  231. package/src/journal-client.ts +12 -2
  232. package/src/journal-reader.ts +4 -2
  233. package/src/llm-worker.ts +12 -1
  234. package/src/permissions-preflight.ts +53 -0
  235. package/src/preflight.ts +42 -7
  236. package/src/protocol.ts +15 -0
  237. package/src/pty-sidechannel.ts +7 -0
  238. package/src/redact.ts +106 -0
  239. package/src/run-state.ts +468 -0
  240. package/src/spec.ts +18 -1
  241. package/src/step-env.ts +47 -0
  242. package/src/transcript-tail.ts +193 -0
  243. package/src/worker-cli.ts +139 -18
  244. package/src/worker-usage.ts +26 -3
  245. package/src/worker.ts +42 -6
  246. package/src/wrapper-runtime.ts +7 -1
  247. package/src/wrapper-session.ts +159 -24
@@ -0,0 +1,53 @@
1
+ import type { CompiledFlowSpec } from './compile.js';
2
+ import type { PermissionsSpec } from './spec.js';
3
+ import type { PreflightWarning } from './preflight.js';
4
+
5
+ /** Declared fields, reported in this order so two runs read identically. */
6
+ const PERMISSION_FIELDS = ['fileGlobs', 'networkAllowlist', 'accessPreset'] as const;
7
+
8
+ /**
9
+ * An agent step's `permissions` block is accepted by gate 1, lowered to
10
+ * `file_globs` / `network_allowlist` / `access_preset`, carried in the kernel
11
+ * step spec and journalled with it. Nothing reads it to gate a file or network
12
+ * access: enforcement is gate 8 (#442). A spec that declares it therefore
13
+ * looks sandboxed and is not, and until this warning existed nothing said so
14
+ * at authoring time.
15
+ *
16
+ * Every defined declaration warns, including `{}` and empty arrays. SURFACE.md
17
+ * accepts those without inferring defaults, and an empty block is exactly as
18
+ * likely to read to an author as "a default sandbox applies". Omitting
19
+ * `permissions` stays silent — there is no belief to correct.
20
+ *
21
+ * Warning-only: `ok` is unaffected, the declaration stays legal, and no value
22
+ * is echoed back (a glob or host is often the least interesting part of the
23
+ * declaration and can be long). The message names which fields were declared
24
+ * so the author can find them, and nothing more.
25
+ */
26
+ export function permissionsDiagnostics(flow: CompiledFlowSpec): PreflightWarning[] {
27
+ const warnings: PreflightWarning[] = [];
28
+ for (const step of flow.steps) {
29
+ if (step.type !== 'agent' || step.permissions === undefined) continue;
30
+ warnings.push({
31
+ severity: 'warning',
32
+ kind: 'permissions_unenforced',
33
+ stepId: step.id,
34
+ message: permissionsMessage(step.id, step.permissions),
35
+ });
36
+ }
37
+ return warnings;
38
+ }
39
+
40
+ function permissionsMessage(stepId: string, permissions: PermissionsSpec): string {
41
+ const declared = PERMISSION_FIELDS.filter((field) => permissions[field] !== undefined);
42
+ const fields = declared.length === 0 ? '' : ` (${declared.join(', ')})`;
43
+ // `readonly` is the one value whose entire meaning is a guarantee, so it is
44
+ // the one value worth naming: an author reading "cannot write" needs to be
45
+ // told, in the same breath, that today nothing stops a write.
46
+ const preset = permissions.accessPreset === 'readonly'
47
+ ? " accessPreset: 'readonly' does not prevent writes."
48
+ : '';
49
+ return `Step "${stepId}" declares permissions${fields}. `
50
+ + 'This declaration is validated and recorded with the step spec; it is not '
51
+ + 'currently enforced (gate 8 / #442). These permissions do not restrict file '
52
+ + `or network access.${preset}`;
53
+ }
package/src/preflight.ts CHANGED
@@ -1,9 +1,11 @@
1
+ import { communicationInstruction } from './communication/spec.js';
1
2
  import { loadPlugins, type LoadedPlugin } from './plugin-loader.js';
2
3
  import { PluginError } from './plugin-manifest.js';
3
4
  import type { FlowSpec, StepSpec, TriggerSpec, McpServerConfig } from './spec.js';
4
5
  import { McpError, openMcpSession, type McpDiagnostic } from './mcp-client.js';
5
6
  import { BudgetSyntaxError } from './budget.js';
6
7
  import { budgetDiagnostics } from './budget-preflight.js';
8
+ import { permissionsDiagnostics } from './permissions-preflight.js';
7
9
  import type { TriggerSource } from '@relayflows/surface';
8
10
  import { acceptsAnyOutput, inspectStepGate, type StepGateInspection } from './gate-contract.js';
9
11
  import { compileSpec, CompileError } from './compile.js';
@@ -31,13 +33,24 @@ export interface CliResolution {
31
33
 
32
34
  export interface CliProbeResult {
33
35
  exists: boolean;
34
- authenticated: boolean;
36
+ authenticated: boolean | 'unverified';
35
37
  /** False when a custom executable did not identify as a wrapper adapter. */
36
38
  supported?: boolean;
37
39
  /** Exact declared model passed the CLI's model-scoped readiness probe. */
38
40
  modelAvailable?: boolean;
39
41
  authCommand?: string;
40
42
  modelCommand?: string;
43
+ /** Exit code of the authentication probe, when it failed. */
44
+ authExitCode?: number | null;
45
+ /**
46
+ * Redacted output of a FAILED authentication probe.
47
+ *
48
+ * Present only on failure. Without it a `cli_unauthenticated` refusal can
49
+ * state that a probe exited non-zero and never what it said, which makes an
50
+ * intermittent probe failure impossible to tell apart from a genuinely
51
+ * unauthenticated CLI.
52
+ */
53
+ authFailureDetail?: string;
41
54
  }
42
55
 
43
56
  export type CliProbeFailureDetail =
@@ -69,7 +82,7 @@ export interface PreflightProbes {
69
82
  * model in scope, so readiness answers "can this CLI use THIS model" rather
70
83
  * than the weaker "is this CLI authenticated at all".
71
84
  */
72
- cli(cli: string, source: CliResolutionSource, model?: string): CliProbeResult;
85
+ cli(cli: string, source: CliResolutionSource, model?: string, execution?: 'managed'): CliProbeResult;
73
86
  executor(trigger: TriggerSpec): boolean;
74
87
  command(binary: string): boolean;
75
88
  }
@@ -223,6 +236,11 @@ function preflightSync(flow: unknown, options: PreflightOptions): PreflightResul
223
236
  const resolutionByStep = new Map<string, CliResolution>();
224
237
  const cliProbeResults = new Map<string, CliProbeOutcome>();
225
238
 
239
+ // A pure fact about the compiled snapshot, collected before anything that
240
+ // can return early. An unresolved CLI, an unknown model or a bad scope all
241
+ // refuse below without probing, and the author should still be told that the
242
+ // permissions they declared on the same file are not enforced.
243
+ diagnostics.push(...permissionsDiagnostics(compiled));
226
244
  diagnostics.push(...scopeDiagnostics(compiled, options));
227
245
  for (const server of new Set(options.mcpServers ?? [])) {
228
246
  if (options.mcp !== undefined && Object.hasOwn(options.mcp, server)) continue;
@@ -286,7 +304,8 @@ function preflightSync(flow: unknown, options: PreflightOptions): PreflightResul
286
304
  continue;
287
305
  }
288
306
  const resolution = resolutionByStep.get(step.id)!;
289
- probeResolvedCli(resolution, options.probes, cliProbeResults, diagnostics);
307
+ probeResolvedCli(resolution, options.probes, cliProbeResults, diagnostics,
308
+ step.type === 'agent' && communicationInstruction(step.instruction) !== undefined);
290
309
  }
291
310
 
292
311
  for (const trigger of compiled.triggers ?? []) {
@@ -447,6 +466,7 @@ function probeResolvedCli(
447
466
  probes: PreflightProbes,
448
467
  cache: Map<string, CliProbeOutcome>,
449
468
  diagnostics: PreflightDiagnostic[],
469
+ managed = false,
450
470
  ): void {
451
471
  // Source is load-bearing: the same relative CLI string resolves from the
452
472
  // flow directory for step/named/flow declarations and the config directory for
@@ -454,11 +474,13 @@ function probeResolvedCli(
454
474
  // Model is part of the key: the same CLI probed with two different models
455
475
  // is two different questions, and caching on the CLI alone would let a
456
476
  // model that the CLI cannot resolve inherit an earlier model's pass.
457
- const cacheKey = JSON.stringify([resolution.cli, resolution.source, resolution.model ?? null]);
477
+ const cacheKey = JSON.stringify([resolution.cli, resolution.source, resolution.model ?? null, managed]);
458
478
  let outcome = cache.get(cacheKey);
459
479
  if (outcome === undefined) {
460
480
  try {
461
- outcome = { result: probes.cli(resolution.cli, resolution.source, resolution.model) };
481
+ outcome = { result: managed
482
+ ? probes.cli(resolution.cli, resolution.source, resolution.model, 'managed')
483
+ : probes.cli(resolution.cli, resolution.source, resolution.model) };
462
484
  } catch (error) {
463
485
  const detail = error instanceof CliProbeError ? error.detail : undefined;
464
486
  outcome = { failure: detail ?? null };
@@ -494,14 +516,27 @@ function probeResolvedCli(
494
516
  cli: resolution.cli,
495
517
  message: `Step "${resolution.stepId}" declares CLI "${resolution.cli}", but it is neither a supported raw Claude/Codex executable nor a conforming Relayflows wrapper; custom wrappers must identify with the relayflows-agent-cli-v1 contract.`,
496
518
  });
497
- } else if (!result.authenticated) {
519
+ } else if (managed && result.authenticated === 'unverified') {
520
+ diagnostics.push({ severity: 'warning', kind: 'managed_cli_unverified', stepId: resolution.stepId,
521
+ message: `Step "${resolution.stepId}" uses Relay's interactive CLI transport. The executable exists, but authentication${resolution.model ? ' and access to model "' + resolution.model + '"' : ''} could not be verified before startup. The managed session must execute its task and report completion; startup or task failure fails the step.` });
522
+ } else if (result.authenticated !== true) {
498
523
  const command = result.authCommand ?? `${resolution.cli} auth status`;
524
+ // Say what the probe reported. A refusal that names only the command turns
525
+ // a transient provider rejection and a genuinely unauthenticated CLI into
526
+ // the same message, and the difference decides whether retrying is
527
+ // correct.
528
+ const exitCode = result.authExitCode === undefined || result.authExitCode === null
529
+ ? ''
530
+ : ` (exit ${result.authExitCode})`;
531
+ const detail = result.authFailureDetail !== undefined && result.authFailureDetail.length > 0
532
+ ? ` It reported: ${result.authFailureDetail}`
533
+ : ' It produced no output, so the reason is unavailable.';
499
534
  diagnostics.push({
500
535
  severity: 'refusal',
501
536
  kind: 'cli_unauthenticated',
502
537
  stepId: resolution.stepId,
503
538
  cli: resolution.cli,
504
- message: `Step "${resolution.stepId}" declares CLI "${resolution.cli}", but "${command}" exited non-zero; authenticate it or repair that adapter's authentication probe.`,
539
+ message: `Step "${resolution.stepId}" declares CLI "${resolution.cli}", but "${command}" exited non-zero${exitCode}; authenticate it or repair that adapter's authentication probe.${detail}`,
505
540
  });
506
541
  } else if (resolution.model !== undefined && result.modelAvailable !== true) {
507
542
  diagnostics.push({
package/src/protocol.ts CHANGED
@@ -55,6 +55,9 @@ export type Verb =
55
55
  | 'step.wait'
56
56
  | 'event.emit'
57
57
  | 'event.submit'
58
+ | 'channel.append'
59
+ | 'channel.receive'
60
+ | 'channel.ack'
58
61
  | 'stream.append'
59
62
  | 'stream.read'
60
63
  | 'journal.read';
@@ -160,6 +163,8 @@ export interface WorkerAttachParams {
160
163
  step_types: StepType[];
161
164
  /** Maximum concurrent assignments. Omitted means the conservative default 1. */
162
165
  capacity?: number;
166
+ /** Accept only agent steps declaring every named stream (must also be pinned). */
167
+ required_streams?: string[];
163
168
  /**
164
169
  * The surfaces this worker holds, as opaque revisions/offsets. Required when
165
170
  * `step_types` includes `agent` — an agent attempt's start pins come from
@@ -411,7 +416,17 @@ export interface JournalReadResult {
411
416
  }
412
417
 
413
418
  /** Typed map of verb -> { params, result }. Used by the client for type-safety. */
419
+ export interface ChannelIdentity {
420
+ run_id: string; step_id: string; attempt: number; idempotency_key: string; channel: string;
421
+ }
422
+ export interface ChannelEntry {
423
+ seq: number;
424
+ payload: { channel: string; offset: number; message?: unknown; producer?: string; consumer?: string; message_id?: string; delivery_seq?: number };
425
+ }
414
426
  export interface VerbContract {
427
+ 'channel.append': { params: ChannelIdentity & { message_id: string; message: unknown }; result: ChannelEntry };
428
+ 'channel.receive': { params: ChannelIdentity; result: ChannelEntry | null };
429
+ 'channel.ack': { params: ChannelIdentity & { delivery_seq: number }; result: ChannelEntry };
415
430
  hello: { params: HelloParams; result: HelloResult };
416
431
  'run.start': { params: RunStartParams; result: RunStartResult };
417
432
  'run.resume': { params: RunResumeParams; result: RunResumeResult };
@@ -6,6 +6,13 @@ export interface SidechannelContext {
6
6
  dataDir: string;
7
7
  runId: string;
8
8
  stepId: string;
9
+ /**
10
+ * Which attempt this is. Names the per-attempt transcript file
11
+ * (`agent-transcript.ts`) and the transcript tails (`transcript-tail.ts`),
12
+ * and is exported into the agent's environment. Optional: without it there
13
+ * is no attempt to name, so neither file is written.
14
+ */
15
+ attempt?: number;
9
16
  onReady?: (path: string) => void;
10
17
  onDrive: () => void;
11
18
  }
package/src/redact.ts ADDED
@@ -0,0 +1,106 @@
1
+ // Redaction for free text an agent-facing surface may print.
2
+ //
3
+ // `flows status` renders gate `detail`, transcript tails and error messages
4
+ // to a process that may be an agent, in a sandbox whose stdout is captured.
5
+ // A direct agent inherits the worker's whole environment (worker-cli.ts), so
6
+ // anything the operator exported can be echoed back into that text. This
7
+ // module is the one place that decides what never reaches the page.
8
+ //
9
+ // Applied to free text only. Identifiers — run ids, step ids, hashes and env
10
+ // NAMES — are never rewritten, so the view stays greppable.
11
+
12
+ /** Env names whose values are treated as secret when they are long enough. */
13
+ const SECRET_NAME = /TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL|AUTH/i;
14
+
15
+ /** Shorter values are flags and modes (`AUTH_MODE=off`), not material. */
16
+ const MIN_SECRET_LENGTH = 8;
17
+
18
+ /**
19
+ * Token shapes replaced wholesale. The first is the relay token family
20
+ * `redactRelayError` in worker-cli.ts already scrubbed; the rest are the
21
+ * bearer, header and vendor shapes a gate render can carry.
22
+ */
23
+ const TOKEN_PATTERNS: readonly RegExp[] = [
24
+ /\b(?:at|rk|nt|ot|br|arr)_(?:live_)?[A-Za-z0-9_-]+/g,
25
+ /\bsk-[A-Za-z0-9_-]{16,}/g,
26
+ /\bghp_[A-Za-z0-9]{20,}/g,
27
+ /\bxox[abp]-[A-Za-z0-9-]+/g,
28
+ ];
29
+
30
+ /**
31
+ * Header and assignment shapes: keep the name (and an auth scheme), replace
32
+ * the value. A value already replaced by an earlier pass is left as it is,
33
+ * so the reader still sees which env name it came from.
34
+ */
35
+ const NAMED_VALUE_PATTERNS: readonly RegExp[] = [
36
+ /\b(authorization:\s*(?:\w+\s+)?)(?!\[redacted)\S+/gi,
37
+ /\b(Bearer\s+)(?!\[redacted)\S+/g,
38
+ /\b(x-callback-token:\s*)(?!\[redacted)\S+/gi,
39
+ /\b(x-nightcto-evidence-token:\s*)(?!\[redacted)\S+/gi,
40
+ /\b([A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD)=)(?!\[redacted)\S+/gi,
41
+ ];
42
+
43
+ /**
44
+ * Credential fields in serialized JSON. A gate detail or a transcript line
45
+ * often carries a request or config body, where the value is an opaque string
46
+ * that matches no vendor token shape and — for anything this process did not
47
+ * export — no environment value either: `{"authToken":"opaque-secret"}` went
48
+ * through untouched. The field name is kept, so the reader knows what was cut.
49
+ *
50
+ * Quoted string values only — including escaped ones: a serialized private
51
+ * key or nested JSON reaches these fields as `\n` and `\"`, and a value
52
+ * class that stopped at the first backslash left every one of them intact.
53
+ * Only where the field NAME ends in a credential noun. Containing one is not enough: `monkey` and `keyboard` are
54
+ * ordinary fields, so the name is split on separators and camelCase humps and
55
+ * the last word decides.
56
+ */
57
+ const JSON_STRING_FIELD = /("([A-Za-z0-9_.\-]{1,64})"\s*:\s*")(?!\[redacted)((?:[^"\\]|\\.){4,})(")/g;
58
+ const CREDENTIAL_NOUN = /^(?:token|secret|key|password|passwd|credential|cookie)s?$/i;
59
+
60
+ function isCredentialName(name: string): boolean {
61
+ if (name.toLowerCase() === 'authorization') return true;
62
+ const words = name.split(/[_\-.]+/).flatMap((word) => word.split(/(?<=[a-z0-9])(?=[A-Z])/u));
63
+ const last = words.at(-1);
64
+ return last !== undefined && CREDENTIAL_NOUN.test(last);
65
+ }
66
+
67
+ /** Secret env values, longest first so a value that contains another is replaced whole. */
68
+ function secretEnvValues(env: NodeJS.ProcessEnv): Array<[name: string, value: string]> {
69
+ const secrets: Array<[string, string]> = [];
70
+ for (const [name, value] of Object.entries(env)) {
71
+ if (typeof value !== 'string' || value.length < MIN_SECRET_LENGTH) continue;
72
+ if (SECRET_NAME.test(name)) secrets.push([name, value]);
73
+ }
74
+ return secrets.sort((a, b) => b[1].length - a[1].length);
75
+ }
76
+
77
+ /**
78
+ * Scrub secret env values and known token shapes out of `text`.
79
+ *
80
+ * An env value is replaced by `[redacted:<NAME>]` so the reader learns which
81
+ * variable leaked without learning its value. Token shapes are replaced by
82
+ * `[redacted]`; header and `NAME=value` shapes keep their name.
83
+ */
84
+ export function redact(text: string, env: NodeJS.ProcessEnv = process.env): string {
85
+ let out = text;
86
+ for (const [name, value] of secretEnvValues(env)) out = out.replaceAll(value, `[redacted:${name}]`);
87
+ for (const pattern of TOKEN_PATTERNS) out = out.replace(pattern, '[redacted]');
88
+ for (const pattern of NAMED_VALUE_PATTERNS) out = out.replace(pattern, '$1[redacted]');
89
+ out = out.replace(JSON_STRING_FIELD, (match, open: string, name: string, _value: string, close: string) =>
90
+ isCredentialName(name) ? `${open}[redacted]${close}` : match);
91
+ return out;
92
+ }
93
+
94
+ /**
95
+ * The relay-transport error scrub `worker-cli.ts` has always applied: the two
96
+ * relay credential names by exact match, at any length, spelled `[redacted]`.
97
+ * Everything `redact` adds is applied after, so the historical output for
98
+ * those two names and the token pattern is unchanged.
99
+ */
100
+ export function redactRelayError(message: string, env: NodeJS.ProcessEnv = process.env): string {
101
+ for (const key of ['RELAY_AGENT_TOKEN', 'RELAY_API_KEY']) {
102
+ const secret = env[key];
103
+ if (secret) message = message.replaceAll(secret, '[redacted]');
104
+ }
105
+ return redact(message, env);
106
+ }