@relayflows/sdk 2.0.50 → 2.0.52

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 (167) hide show
  1. package/dist/authored-budget.d.ts +1 -1
  2. package/dist/authored-budget.d.ts.map +1 -1
  3. package/dist/authored-budget.js +3 -3
  4. package/dist/authored-budget.js.map +1 -1
  5. package/dist/authored-flow-error.d.ts +3 -1
  6. package/dist/authored-flow-error.d.ts.map +1 -1
  7. package/dist/authored-flow-error.js.map +1 -1
  8. package/dist/authored-flow-executor.d.ts +2 -0
  9. package/dist/authored-flow-executor.d.ts.map +1 -1
  10. package/dist/authored-flow-executor.js +97 -14
  11. package/dist/authored-flow-executor.js.map +1 -1
  12. package/dist/authored-flow-lifecycle.d.ts +28 -3
  13. package/dist/authored-flow-lifecycle.d.ts.map +1 -1
  14. package/dist/authored-flow-lifecycle.js +206 -10
  15. package/dist/authored-flow-lifecycle.js.map +1 -1
  16. package/dist/authored-node-entry.js +2 -0
  17. package/dist/authored-node-entry.js.map +1 -1
  18. package/dist/authored-parallel-body.d.ts +10 -0
  19. package/dist/authored-parallel-body.d.ts.map +1 -0
  20. package/dist/authored-parallel-body.js +68 -0
  21. package/dist/authored-parallel-body.js.map +1 -0
  22. package/dist/authored-parallel-eligibility.d.ts +8 -0
  23. package/dist/authored-parallel-eligibility.d.ts.map +1 -0
  24. package/dist/authored-parallel-eligibility.js +287 -0
  25. package/dist/authored-parallel-eligibility.js.map +1 -0
  26. package/dist/authored-preflight.js +4 -4
  27. package/dist/authored-preflight.js.map +1 -1
  28. package/dist/authored-promise-graph.d.ts +16 -0
  29. package/dist/authored-promise-graph.d.ts.map +1 -1
  30. package/dist/authored-promise-graph.js +36 -0
  31. package/dist/authored-promise-graph.js.map +1 -1
  32. package/dist/authored-root-readback.d.ts +1 -2
  33. package/dist/authored-root-readback.d.ts.map +1 -1
  34. package/dist/authored-root-readback.js +1 -1
  35. package/dist/authored-root-readback.js.map +1 -1
  36. package/dist/authored-root.d.ts +6 -1
  37. package/dist/authored-root.d.ts.map +1 -1
  38. package/dist/authored-root.js +55 -8
  39. package/dist/authored-root.js.map +1 -1
  40. package/dist/authored-step-graph.d.ts +19 -4
  41. package/dist/authored-step-graph.d.ts.map +1 -1
  42. package/dist/authored-step-graph.js +49 -6
  43. package/dist/authored-step-graph.js.map +1 -1
  44. package/dist/authored-step-index.d.ts +2 -1
  45. package/dist/authored-step-index.d.ts.map +1 -1
  46. package/dist/authored-step-index.js +2 -1
  47. package/dist/authored-step-index.js.map +1 -1
  48. package/dist/authored-verdict.d.ts +2 -0
  49. package/dist/authored-verdict.d.ts.map +1 -1
  50. package/dist/authored-verdict.js +4 -1
  51. package/dist/authored-verdict.js.map +1 -1
  52. package/dist/authored-worker-step.d.ts +4 -3
  53. package/dist/authored-worker-step.d.ts.map +1 -1
  54. package/dist/authored-worker-step.js +23 -10
  55. package/dist/authored-worker-step.js.map +1 -1
  56. package/dist/cli/check.js +2 -2
  57. package/dist/cli/check.js.map +1 -1
  58. package/dist/cli/cli-probe.d.ts +1 -1
  59. package/dist/cli/cli-probe.d.ts.map +1 -1
  60. package/dist/cli/cli-probe.js +8 -7
  61. package/dist/cli/cli-probe.js.map +1 -1
  62. package/dist/cli/run.d.ts.map +1 -1
  63. package/dist/cli/run.js +4 -2
  64. package/dist/cli/run.js.map +1 -1
  65. package/dist/cloud-http.d.ts +1 -0
  66. package/dist/cloud-http.d.ts.map +1 -1
  67. package/dist/cloud-http.js +1 -1
  68. package/dist/cloud-http.js.map +1 -1
  69. package/dist/cloud-run.d.ts +1 -1
  70. package/dist/cloud-run.d.ts.map +1 -1
  71. package/dist/cloud-run.js +45 -6
  72. package/dist/cloud-run.js.map +1 -1
  73. package/dist/cloud-signal.d.ts +12 -0
  74. package/dist/cloud-signal.d.ts.map +1 -0
  75. package/dist/cloud-signal.js +117 -0
  76. package/dist/cloud-signal.js.map +1 -0
  77. package/dist/cloud-sync.d.ts +8 -1
  78. package/dist/cloud-sync.d.ts.map +1 -1
  79. package/dist/cloud-sync.js +36 -4
  80. package/dist/cloud-sync.js.map +1 -1
  81. package/dist/communication/environment.d.ts +1 -1
  82. package/dist/communication/environment.d.ts.map +1 -1
  83. package/dist/communication/environment.js +26 -3
  84. package/dist/communication/environment.js.map +1 -1
  85. package/dist/communication/worker.js +1 -1
  86. package/dist/communication/worker.js.map +1 -1
  87. package/dist/compile.d.ts.map +1 -1
  88. package/dist/compile.js +5 -2
  89. package/dist/compile.js.map +1 -1
  90. package/dist/failure-kinds.d.ts +1 -1
  91. package/dist/failure-kinds.d.ts.map +1 -1
  92. package/dist/failure-kinds.js +2 -0
  93. package/dist/failure-kinds.js.map +1 -1
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +1 -0
  97. package/dist/index.js.map +1 -1
  98. package/dist/journal-client.d.ts +5 -1
  99. package/dist/journal-client.d.ts.map +1 -1
  100. package/dist/journal-client.js +44 -4
  101. package/dist/journal-client.js.map +1 -1
  102. package/dist/llm-worker.d.ts.map +1 -1
  103. package/dist/llm-worker.js +1 -1
  104. package/dist/llm-worker.js.map +1 -1
  105. package/dist/preflight.d.ts +2 -2
  106. package/dist/preflight.d.ts.map +1 -1
  107. package/dist/preflight.js +12 -7
  108. package/dist/preflight.js.map +1 -1
  109. package/dist/spec.d.ts +4 -0
  110. package/dist/spec.d.ts.map +1 -1
  111. package/dist/spec.js.map +1 -1
  112. package/dist/step-fields.d.ts +1 -1
  113. package/dist/step-fields.d.ts.map +1 -1
  114. package/dist/step-fields.js +1 -0
  115. package/dist/step-fields.js.map +1 -1
  116. package/dist/step-secrets.d.ts +20 -0
  117. package/dist/step-secrets.d.ts.map +1 -0
  118. package/dist/step-secrets.js +146 -0
  119. package/dist/step-secrets.js.map +1 -0
  120. package/dist/validate.d.ts.map +1 -1
  121. package/dist/validate.js +10 -0
  122. package/dist/validate.js.map +1 -1
  123. package/dist/worker-cli.d.ts +5 -1
  124. package/dist/worker-cli.d.ts.map +1 -1
  125. package/dist/worker-cli.js +43 -14
  126. package/dist/worker-cli.js.map +1 -1
  127. package/dist/worker.d.ts +1 -1
  128. package/dist/worker.d.ts.map +1 -1
  129. package/dist/worker.js +1 -1
  130. package/dist/worker.js.map +1 -1
  131. package/package.json +3 -2
  132. package/src/authored-budget.ts +4 -3
  133. package/src/authored-flow-error.ts +2 -0
  134. package/src/authored-flow-executor.ts +89 -13
  135. package/src/authored-flow-lifecycle.ts +215 -9
  136. package/src/authored-node-entry.ts +2 -0
  137. package/src/authored-parallel-body.ts +67 -0
  138. package/src/authored-parallel-eligibility.ts +245 -0
  139. package/src/authored-preflight.ts +4 -4
  140. package/src/authored-promise-graph.ts +35 -0
  141. package/src/authored-root-readback.ts +1 -2
  142. package/src/authored-root.ts +62 -9
  143. package/src/authored-step-graph.ts +50 -5
  144. package/src/authored-step-index.ts +2 -1
  145. package/src/authored-verdict.ts +4 -1
  146. package/src/authored-worker-step.ts +29 -9
  147. package/src/cli/check.ts +3 -3
  148. package/src/cli/cli-probe.ts +9 -6
  149. package/src/cli/run.ts +4 -2
  150. package/src/cloud-http.ts +1 -1
  151. package/src/cloud-run.ts +45 -8
  152. package/src/cloud-signal.ts +110 -0
  153. package/src/cloud-sync.ts +39 -4
  154. package/src/communication/environment.ts +28 -3
  155. package/src/communication/worker.ts +2 -1
  156. package/src/compile.ts +5 -2
  157. package/src/failure-kinds.ts +2 -0
  158. package/src/index.ts +1 -0
  159. package/src/journal-client.ts +48 -2
  160. package/src/llm-worker.ts +2 -1
  161. package/src/preflight.ts +13 -7
  162. package/src/spec.ts +4 -0
  163. package/src/step-fields.ts +1 -0
  164. package/src/step-secrets.ts +145 -0
  165. package/src/validate.ts +11 -0
  166. package/src/worker-cli.ts +41 -13
  167. package/src/worker.ts +3 -2
@@ -0,0 +1,145 @@
1
+ import type { CliAdapterKind } from './cli-adapter.js';
2
+
3
+ /** Cloud supplies this name-only manifest alongside delivered envSecrets. */
4
+ export const SECRET_ENV_NAMES = 'RELAY_SECRET_ENV_NAMES';
5
+ /** Cloud's name-only exception for model login it selected from delivered values. */
6
+ export const PLATFORM_AGENT_ENV_NAMES = 'RELAY_PLATFORM_AGENT_ENV_NAMES';
7
+ /** CLI-only hosted signal read capability. Never a run or step secret. */
8
+ export const SIGNAL_READ_TOKEN_ENV = 'RELAYFLOW_V2_SIGNAL_READ_TOKEN';
9
+ const VALID_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
10
+ const BEDROCK_LOGIN = new Set([
11
+ 'CLAUDE_CODE_USE_BEDROCK', 'AWS_BEARER_TOKEN_BEDROCK',
12
+ 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN',
13
+ 'AWS_REGION', 'AWS_DEFAULT_REGION',
14
+ ]);
15
+
16
+ const REPOSITORY_ENV = new Set([
17
+ 'GH_TOKEN', 'GH_CONFIG_DIR', 'GH_PROMPT_DISABLED', 'GITLAB_TOKEN',
18
+ 'RELAYFLOW_V2_GITLAB_API_URL',
19
+ ]);
20
+ export function repositoryEnvironmentNames(source: NodeJS.ProcessEnv): string[] {
21
+ return Object.keys(source).filter(name => REPOSITORY_ENV.has(name) || name.startsWith('GIT_')
22
+ || name.startsWith('RELAYFLOW_V2_REPOSITORY_'));
23
+ }
24
+
25
+ const BASE_ENV = [
26
+ 'PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'TERM', 'LANG', 'LANGUAGE',
27
+ 'LC_ALL', 'LC_CTYPE', 'TZ', 'TMPDIR', 'TMP', 'TEMP', 'XDG_RUNTIME_DIR',
28
+ 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'HOSTNAME',
29
+ 'NODE_PATH', 'NODE_ENV', 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE',
30
+ 'SSL_CERT_DIR', 'NVM_DIR', 'NVM_BIN', 'NVM_INC',
31
+ 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
32
+ 'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy',
33
+ 'NVM_SYMLINK_CURRENT', 'PIPX_HOME', 'PIPX_BIN_DIR', 'PYTHON_PATH',
34
+ 'PYTHON_VERSION', 'PYTHON_SHA256', 'RELAYFILE_MOUNT_PATH',
35
+ 'RELAYFILE_MOUNT_PATHS', 'RELAYFILE_MOUNT_ROOT', 'WORKSPACE_ROOT',
36
+ 'RELAY_BASE_URL',
37
+ 'SystemRoot', 'ComSpec', 'PATHEXT', 'WINDIR',
38
+ ] as const;
39
+
40
+ // Model login is an intrinsic capability of the selected CLI. It is not a
41
+ // run's delivered envSecret; only that CLI gets its own login variables.
42
+ const CLI_LOGIN: Record<CliAdapterKind, readonly string[]> = {
43
+ claude: [
44
+ 'CLAUDE_CODE_OAUTH_TOKEN', 'CLAUDE_CONFIG_DIR', 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN',
45
+ 'ANTHROPIC_BASE_URL', 'CLAUDE_CODE_USE_BEDROCK',
46
+ 'AWS_BEARER_TOKEN_BEDROCK', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY',
47
+ 'AWS_SESSION_TOKEN', 'AWS_REGION', 'AWS_DEFAULT_REGION',
48
+ ],
49
+ codex: ['CODEX_HOME', 'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_API_BASE', 'OPENROUTER_API_KEY'],
50
+ 'relayflows-wrapper-v1': [],
51
+ };
52
+
53
+ /** A selected CLI can use its own delivered login; generic cloud credentials need a grant. */
54
+ export function isCliLoginName(kind: string, name: string, providerNames?: readonly string[]): boolean {
55
+ if (BEDROCK_LOGIN.has(name) || name.startsWith('AWS_')
56
+ || name === 'GOOGLE_APPLICATION_CREDENTIALS' || name === 'GOOGLE_API_KEY') return false;
57
+ const names = providerNames ?? (Object.hasOwn(CLI_LOGIN, kind) ? CLI_LOGIN[kind as CliAdapterKind] : []);
58
+ return names.includes(name);
59
+ }
60
+
61
+ export function deliveredNames(source: NodeJS.ProcessEnv): ReadonlySet<string> | undefined {
62
+ const raw = source[SECRET_ENV_NAMES];
63
+ if (raw === undefined) return undefined; // self-hosted runs may grant present env names
64
+ let parsed: unknown;
65
+ try { parsed = JSON.parse(raw); } catch { throw new Error(`${SECRET_ENV_NAMES} must be a JSON array of names`); }
66
+ if (!Array.isArray(parsed) || !parsed.every(name => typeof name === 'string' && VALID_NAME.test(name))) {
67
+ throw new Error(`${SECRET_ENV_NAMES} must be a JSON array of valid names`);
68
+ }
69
+ if (new Set(parsed).size !== parsed.length || parsed.includes(SECRET_ENV_NAMES)
70
+ || parsed.includes(PLATFORM_AGENT_ENV_NAMES)
71
+ || parsed.includes(SIGNAL_READ_TOKEN_ENV)) {
72
+ throw new Error(`${SECRET_ENV_NAMES} contains a duplicate or reserved name`);
73
+ }
74
+ return new Set(parsed);
75
+ }
76
+
77
+ /** Only Cloud-selected Claude Bedrock login may override the delivered filter. */
78
+ export function platformAgentNames(source: NodeJS.ProcessEnv, kind: string): ReadonlySet<string> {
79
+ const raw = source[PLATFORM_AGENT_ENV_NAMES];
80
+ if (raw === undefined) return new Set();
81
+ let parsed: unknown;
82
+ try { parsed = JSON.parse(raw); } catch { throw new Error(`${PLATFORM_AGENT_ENV_NAMES} must be a JSON array of names`); }
83
+ if (!Array.isArray(parsed) || !parsed.every(name => typeof name === 'string' && BEDROCK_LOGIN.has(name)
84
+ && typeof source[name] === 'string') || new Set(parsed).size !== parsed.length) {
85
+ throw new Error(`${PLATFORM_AGENT_ENV_NAMES} contains an invalid or unavailable model login name`);
86
+ }
87
+ return kind === 'claude' ? new Set(parsed) : new Set();
88
+ }
89
+
90
+ /** Refuse a misspelled, unavailable or platform-owned name before a child runs. */
91
+ export function validateStepSecrets(declaration: unknown, source: NodeJS.ProcessEnv): string[] {
92
+ const known = deliveredNames(source);
93
+ if (declaration === undefined) return [];
94
+ if (!Array.isArray(declaration) || !declaration.every(name => typeof name === 'string' && VALID_NAME.test(name))) {
95
+ throw new Error('step secrets must be an array of environment variable names');
96
+ }
97
+ const names = declaration as string[];
98
+ if (new Set(names).size !== names.length) throw new Error('step secrets contain duplicate names');
99
+ for (const name of names) {
100
+ if (name === SECRET_ENV_NAMES || name === PLATFORM_AGENT_ENV_NAMES || name === SIGNAL_READ_TOKEN_ENV
101
+ || !Object.hasOwn(source, name) || typeof source[name] !== 'string'
102
+ || (known !== undefined && !known.has(name))) {
103
+ throw new Error(`step secret ${JSON.stringify(name)} is not available to this run`);
104
+ }
105
+ }
106
+ return [...names];
107
+ }
108
+
109
+ /** One provider CLI starts from an allowlist, then receives only its grants. */
110
+ export function agentStepEnvironment(
111
+ source: NodeJS.ProcessEnv,
112
+ kind: CliAdapterKind,
113
+ declaration?: unknown,
114
+ ): NodeJS.ProcessEnv {
115
+ const known = deliveredNames(source);
116
+ const platform = platformAgentNames(source, kind);
117
+ const grants = validateStepSecrets(declaration, source);
118
+ const env: NodeJS.ProcessEnv = {};
119
+ for (const name of [...BASE_ENV, ...CLI_LOGIN[kind], ...repositoryEnvironmentNames(source)]) {
120
+ if (known?.has(name) && !grants.includes(name) && !platform.has(name)
121
+ && !isCliLoginName(kind, name)) continue;
122
+ if (source[name] !== undefined) env[name] = source[name];
123
+ }
124
+ for (const name of grants) env[name] = source[name];
125
+ return env;
126
+ }
127
+
128
+ /** Provider readiness probes run before worker attach, but obey the step's grants. */
129
+ export function agentProbeEnvironment(
130
+ source: NodeJS.ProcessEnv,
131
+ kind: CliAdapterKind,
132
+ declaration?: unknown,
133
+ ): NodeJS.ProcessEnv {
134
+ const env = { ...source };
135
+ const allowed = new Set(validateStepSecrets(declaration, source));
136
+ const platform = platformAgentNames(source, kind);
137
+ for (const name of deliveredNames(source) ?? []) {
138
+ if (!allowed.has(name) && !platform.has(name) && !isCliLoginName(kind, name)) delete env[name];
139
+ }
140
+ delete env[SECRET_ENV_NAMES];
141
+ delete env[PLATFORM_AGENT_ENV_NAMES];
142
+ delete env[SIGNAL_READ_TOKEN_ENV];
143
+ delete env.RELAY_AGENT_TOKEN;
144
+ return env;
145
+ }
package/src/validate.ts CHANGED
@@ -388,6 +388,14 @@ class Validator {
388
388
  const type = st['type'] as StepType;
389
389
  this.checkKeys(st, [...STEP_COMMON_FIELDS, ...STEP_FIELDS_BY_TYPE[type]], at);
390
390
 
391
+ if (st['secrets'] !== undefined) {
392
+ const names = st['secrets'];
393
+ if (!Array.isArray(names) || !names.every(name => typeof name === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
394
+ || new Set(names).size !== names.length || names.includes('RELAY_SECRET_ENV_NAMES')) {
395
+ this.fail(`${at}.secrets: expected distinct environment variable names`);
396
+ }
397
+ }
398
+
391
399
  if (st['requirements'] !== undefined) this.validateRequirements(st['requirements'], `${at}.requirements`);
392
400
  if (st['memory'] !== undefined) this.validateMemory(st['memory'], `${at}.memory`);
393
401
 
@@ -491,6 +499,9 @@ class Validator {
491
499
  }
492
500
 
493
501
  private validateAgent(st: AgentStepSpec, at: string): void {
502
+ if (st.transport === 'relay' && st.secrets?.length) {
503
+ this.fail(`${at}.secrets: relay transport cannot deliver per-step secrets`);
504
+ }
494
505
  if (st.timeoutMs !== undefined) {
495
506
  if (!isPosInt(st.timeoutMs) || st.timeoutMs > 60 * 60_000) {
496
507
  this.fail(`${at}.timeoutMs: expected a positive integer at most 3600000 (60 minutes)`);
package/src/worker-cli.ts CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  } from './wrapper-session.js';
32
32
  import { wrapperEnvironment } from './wrapper-runtime.js';
33
33
  import { applyStepEnvironment } from './step-env.js';
34
+ import { PLATFORM_AGENT_ENV_NAMES, SIGNAL_READ_TOKEN_ENV, agentStepEnvironment } from './step-secrets.js';
34
35
  import { TAIL_CLOSE_TIMEOUT_MS, openTranscriptTail, type TranscriptTailWriter } from './transcript-tail.js';
35
36
  import {
36
37
  isRetryableSpawnError,
@@ -139,12 +140,33 @@ export async function runAgentCli(
139
140
  stepTimeoutMs?: number,
140
141
  /** A recorded CLI session to continue (`f.agent` `resume`); agent mode only. */
141
142
  resume?: string,
143
+ /** Names of run secrets this step was granted by its journaled spec. */
144
+ secrets?: string[],
145
+ /** A featureless daemon retains its legacy child environment for plain runs. */
146
+ scopeSecrets = true,
142
147
  ): Promise<WorkerCliResult> {
143
148
  signal?.throwIfAborted();
144
149
  if (signal !== undefined && process.platform === 'win32') {
145
150
  throw new Error('Lease-bound agent execution requires macOS or Linux process-group cancellation; Windows is unsupported.');
146
151
  }
147
152
  const kind = cliAdapterKind(cliIdentity ?? cli);
153
+ if (!scopeSecrets && secrets?.length) {
154
+ return { exit_code: null, stdout_tail: '',
155
+ stderr_tail: 'per-step secret grants require a daemon with step_secret_scoping support' };
156
+ }
157
+ let childEnvironment: NodeJS.ProcessEnv;
158
+ try {
159
+ childEnvironment = scopeSecrets
160
+ ? agentStepEnvironment(processEnvironment, kind, secrets)
161
+ : { ...processEnvironment };
162
+ // The old-daemon compatibility path retains other legacy variables, but
163
+ // this CLI-only signal capability must never enter an agent subprocess.
164
+ delete childEnvironment[SIGNAL_READ_TOKEN_ENV];
165
+ delete childEnvironment[PLATFORM_AGENT_ENV_NAMES];
166
+ } catch (error) {
167
+ return { exit_code: null, stdout_tail: '',
168
+ stderr_tail: error instanceof Error ? error.message : 'step secret grant is invalid' };
169
+ }
148
170
  const effectiveModel = resolveCliModel(cliIdentity ?? cli, model);
149
171
  const argv0 = cliInvocationArgv0(cli, cliIdentity);
150
172
 
@@ -206,7 +228,8 @@ export async function runAgentCli(
206
228
  if (kind === 'relayflows-wrapper-v1') {
207
229
  // The closed allowlist admits no ambient RELAYFLOW_* value; the four
208
230
  // discovery names are set from this dispatch, exactly as for a direct spawn.
209
- const wrapperEnv = wrapperEnvironment(processEnvironment);
231
+ const wrapperEnv = wrapperEnvironment(childEnvironment);
232
+ for (const name of secrets ?? []) wrapperEnv[name] = childEnvironment[name];
210
233
  applyStepEnvironment(wrapperEnv, sidechannel);
211
234
  const session = await runWrapperSession(
212
235
  cli,
@@ -225,7 +248,7 @@ export async function runAgentCli(
225
248
  return requirePricedUsage(session.transport?.cause === 'timeout' ? { ...decoded, stdout_tail: '' } : decoded, effectiveModel);
226
249
  }
227
250
 
228
- const env: NodeJS.ProcessEnv = { ...processEnvironment };
251
+ const env: NodeJS.ProcessEnv = { ...childEnvironment };
229
252
  delete env[WAKE_CONTEXT_ENV];
230
253
  delete env[MODEL_ENV];
231
254
  // Where this attempt's journal is, so the agent can run `flows status` on
@@ -256,9 +279,13 @@ export async function runAgentCli(
256
279
  const args = [...invocation.args];
257
280
  args.splice(args.length - promptOperand('').length, 0, ...(kind === 'claude' ? ['--output-format', 'stream-json', '--verbose'] : ['--json']));
258
281
  const completion = kind === 'claude' ? claudeResultOutcome : undefined;
282
+ // Retain the source values for output redaction even though the child
283
+ // receives only its filtered environment. A provider can echo a value from
284
+ // its prompt or fixture that was not granted as an environment variable.
285
+ const redactionEnvironment = { ...processEnvironment, ...env };
259
286
  return requirePricedUsage(decodeProviderResult(await spawnInvocation(
260
- cli, { ...invocation, args }, env, signal, sidechannel, cwd, completion, argv0, kind,
261
- ), kind, env), effectiveModel);
287
+ cli, { ...invocation, args }, env, signal, sidechannel, cwd, completion, argv0, kind, redactionEnvironment,
288
+ ), kind, redactionEnvironment), effectiveModel);
262
289
  }
263
290
  }
264
291
 
@@ -399,6 +426,7 @@ async function spawnInvocation(
399
426
  completion?: (line: string) => { failed: boolean } | undefined,
400
427
  argv0?: string,
401
428
  kind?: CliAdapterKind,
429
+ redactionEnvironment: NodeJS.ProcessEnv = env,
402
430
  ): Promise<WorkerCliResult> {
403
431
  const pendingInput: Buffer[] = [];
404
432
  let acceptingDrive = true;
@@ -436,7 +464,7 @@ async function spawnInvocation(
436
464
  };
437
465
  try {
438
466
  writer = sidechannel?.attempt === undefined ? undefined
439
- : await openTranscriptWriter(transcriptPath(sidechannel, sidechannel.attempt), env);
467
+ : await openTranscriptWriter(transcriptPath(sidechannel, sidechannel.attempt), redactionEnvironment);
440
468
  channel = sidechannel === undefined ? undefined : await openSidechannel({
441
469
  ...sidechannel,
442
470
  onDrive() { driven = true; sidechannel.onDrive(); },
@@ -597,7 +625,7 @@ async function spawnInvocation(
597
625
  const transport = transportEvidence({
598
626
  phase: 'abort', cause: 'lease_lost', exitCode: null, signal: null,
599
627
  stderr: 'Agent execution aborted: lease ownership lost.', retryable: false,
600
- }, env);
628
+ }, redactionEnvironment);
601
629
  finishAfterStop(
602
630
  { exit_code: null, stdout_tail: '', stderr_tail: 'Agent execution aborted: lease ownership lost.', transport },
603
631
  'kill',
@@ -635,11 +663,11 @@ async function spawnInvocation(
635
663
  phase: 'result_exit_grace', cause: 'result_exit_timeout', exitCode, signal: null,
636
664
  stderr: stderrText,
637
665
  retryable: false,
638
- }, env);
666
+ }, redactionEnvironment);
639
667
  finishAfterStop({
640
668
  exit_code: exitCode,
641
669
  stdout_tail: Buffer.concat(stdout).toString('utf8'),
642
- stderr_tail: redactText(stderrText, env),
670
+ stderr_tail: redactText(stderrText, redactionEnvironment),
643
671
  transport,
644
672
  }, 'terminate');
645
673
  }, RESULT_EXIT_GRACE_MS);
@@ -671,11 +699,11 @@ async function spawnInvocation(
671
699
  const transport = transportEvidence({
672
700
  phase: 'spawn', cause: child.pid === undefined ? 'spawn_error' : 'process_error',
673
701
  exitCode: null, signal: null, stderr: error.message, errorCode: code, retryable,
674
- }, env);
702
+ }, redactionEnvironment);
675
703
  finishOnChildExit({
676
704
  exit_code: null,
677
705
  stdout_tail: Buffer.concat(stdout).toString('utf8'),
678
- stderr_tail: redactText(error.message, env),
706
+ stderr_tail: redactText(error.message, redactionEnvironment),
679
707
  transport,
680
708
  });
681
709
  });
@@ -692,11 +720,11 @@ async function spawnInvocation(
692
720
  const retryable = codexStdinLifecycle || signalName !== null || code === null;
693
721
  const transport = transportEvidence({
694
722
  phase: 'close', cause, exitCode: code, signal: signalName, stderr: stderrText, retryable,
695
- }, env);
723
+ }, redactionEnvironment);
696
724
  finishOnChildExit({
697
725
  exit_code: code,
698
726
  stdout_tail: Buffer.concat(stdout).toString('utf8'),
699
- stderr_tail: redactText(stderrText, env),
727
+ stderr_tail: redactText(stderrText, redactionEnvironment),
700
728
  transport,
701
729
  });
702
730
  });
@@ -710,7 +738,7 @@ async function spawnInvocation(
710
738
  phase: 'timeout', cause: 'timeout', exitCode: null, signal: null,
711
739
  stderr: `${Buffer.concat(stderr).toString('utf8')}\n${timeoutMessage}`,
712
740
  retryable: false,
713
- }, env);
741
+ }, redactionEnvironment);
714
742
  finishAfterStop({
715
743
  exit_code: null,
716
744
  stdout_tail: Buffer.concat(stdout).toString('utf8'),
package/src/worker.ts CHANGED
@@ -39,7 +39,7 @@ export interface AgentWorkerOptions {
39
39
  * the root a dispatch is measured against is a value, not an assumption.
40
40
  */
41
41
  runRoot?: string;
42
- /** Environment inherited by provider CLIs, including agent-only secrets. */
42
+ /** Source environment; each provider CLI receives only its allowlist and step grants. */
43
43
  environment?: NodeJS.ProcessEnv;
44
44
  }
45
45
 
@@ -156,7 +156,8 @@ export class AgentWorker extends EventEmitter {
156
156
  spec.transport === 'relay' ? 'relay' : 'direct',
157
157
  { runId: dispatch.run_id, stepId: dispatch.step_id, idempotencyKey: dispatch.idempotency_key,
158
158
  dataDir: this.options.dataDir, resultSchema: spec.verification?.json_schema },
159
- this.options.environment, spec.cli_identity, spec.timeout_ms, spec.resume)
159
+ this.options.environment, spec.cli_identity, spec.timeout_ms, spec.resume, spec.secrets,
160
+ this.client.supportsFeature?.('step_secret_scoping') ?? true)
160
161
  : Promise.resolve({ exit_code: null, stdout_tail: '', stderr_tail: 'agent step has no declared CLI' }));
161
162
  const { result, usage } = workerSpend(completed, effectiveModel);
162
163
  const cost = reportedCost(completed, effectiveModel);