@aiwg/cli 2026.10.8 → 2026.10.10

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 (104) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +197 -3
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/artifacts/cli.js +9 -5
  4. package/dist/src/auth/config.js +3 -8
  5. package/dist/src/auth/resource-credentials.js +35 -2
  6. package/dist/src/cli/handlers/diagnose.js +13 -0
  7. package/dist/src/cli/handlers/mc-admission.js +39 -0
  8. package/dist/src/cli/handlers/mc.js +2 -31
  9. package/dist/src/cli/handlers/models-help.js +52 -0
  10. package/dist/src/cli/handlers/models.js +11 -81
  11. package/dist/src/cli/handlers/resource-versions.js +8 -5
  12. package/dist/src/cli/handlers/script-runner.js +13 -1
  13. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  14. package/dist/src/cli/handlers/use.js +209 -233
  15. package/dist/src/cli/ui.js +1 -0
  16. package/dist/src/config/aiwg-config.js +38 -184
  17. package/dist/src/config/cli-help.js +54 -0
  18. package/dist/src/config/cli.js +5 -69
  19. package/dist/src/config/config-normalization.js +10 -0
  20. package/dist/src/config/worker-activation-gate.js +26 -0
  21. package/dist/src/config/worker-adoption-cli.js +102 -0
  22. package/dist/src/config/worker-adoption.js +368 -0
  23. package/dist/src/config/worker-config-digest.js +14 -0
  24. package/dist/src/config/worker-group-templates.js +150 -0
  25. package/dist/src/config/worker-guidance-warning.js +28 -0
  26. package/dist/src/config/worker-owner-lock.js +152 -0
  27. package/dist/src/config/worker-parallelism.js +154 -0
  28. package/dist/src/config/worker-write.js +75 -0
  29. package/dist/src/decision/telemetry/exporter.js +2 -1
  30. package/dist/src/decision/telemetry/index.js +1 -0
  31. package/dist/src/decision/telemetry/mapping.js +43 -0
  32. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  33. package/dist/src/decision/telemetry/redaction.js +6 -1
  34. package/dist/src/decision/telemetry/types.js +3 -0
  35. package/dist/src/decision/telemetry/worker.js +69 -0
  36. package/dist/src/lint/rule-activation.js +15 -7
  37. package/dist/src/lint/runner.js +2 -2
  38. package/dist/src/mcp/server.mjs +2 -11
  39. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  40. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  41. package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
  42. package/dist/src/mission-protocol/codecs.js +8 -38
  43. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  44. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  45. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  46. package/dist/src/models/conductor-data-policy.mjs +190 -0
  47. package/dist/src/models/config-loader.js +19 -3
  48. package/dist/src/models/config-writer.js +27 -0
  49. package/dist/src/models/model-capabilities.v1.json +197 -3
  50. package/dist/src/models/provider-policy.js +7 -155
  51. package/dist/src/models/resolver.js +2 -293
  52. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  53. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  54. package/dist/src/models/worker-direct-readiness.js +9 -218
  55. package/dist/src/models/worker-health-readiness.js +105 -26
  56. package/dist/src/models/worker-installation.mjs +12 -14
  57. package/dist/src/models/worker-member-references.js +64 -0
  58. package/dist/src/models/worker-plan.js +317 -0
  59. package/dist/src/models/worker-selection-policy.js +217 -0
  60. package/dist/src/models/wrapper-deployment.js +1 -1
  61. package/dist/src/resources/web-release.js +76 -65
  62. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  63. package/dist/src/serve/shared-host-scheduler.js +25 -107
  64. package/dist/src/serve/shared-host-starvation.js +73 -0
  65. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  66. package/dist/src/serve/worker-artifacts.mjs +56 -0
  67. package/dist/src/serve/worker-command-request.js +68 -0
  68. package/dist/src/serve/worker-invocation-paths.js +144 -0
  69. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  70. package/dist/src/serve/worker-invocation.js +24 -291
  71. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  72. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  73. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  74. package/dist/src/storage/backends/fortemi.js +33 -29
  75. package/dist/src/storage/backends/logseq.js +16 -3
  76. package/dist/src/storage/backends/postgres.js +74 -33
  77. package/dist/src/storage/backends/postgrest.js +37 -13
  78. package/dist/src/storage/config.js +6 -1
  79. package/dist/src/uhp/config.js +4 -0
  80. package/package.json +9 -1
  81. package/schemas/mission-v1.schema.json +20 -19
  82. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  83. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  84. package/tools/agents/providers/claude.mjs +3 -30
  85. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  86. package/tools/agents/providers/codex.mjs +2 -8
  87. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  88. package/tools/agents/providers/rule-delivery.mjs +3 -16
  89. package/tools/agents/providers/rule-presentation.mjs +38 -0
  90. package/tools/plugin/package-plugins.mjs +1 -1
  91. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  92. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  93. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  94. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  95. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  96. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  97. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  98. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  99. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  100. package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
  101. package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
  102. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  103. package/tools/ralph-external/session-launcher.mjs +73 -217
  104. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -1,14 +1,17 @@
1
+ import { WorkerRequestArgumentError } from './worker-command-request.js';
2
+ export { parseWorkerCommandArgs } from './worker-command-request.js';
1
3
  /** One nonlaunching typed renderer shared by models resolve and Ralph. */
2
4
  import path from 'node:path';
3
- import { createHash } from 'node:crypto';
4
- import { constants } from 'node:fs';
5
- import { lstat, realpath, open } from 'node:fs/promises';
6
- import { fileURLToPath } from 'node:url';
5
+ import { inspectWorkerPath as inspectPath, workerOutputPaths, readBrief, assertConductorWorkerPlan } from './worker-invocation-paths.js';
6
+ export { inspectWorkerPath } from './worker-invocation-paths.js';
7
+ import { createHash, randomUUID } from 'node:crypto';
7
8
  import { resolveWorkerPlan } from '../models/resolver.js';
8
9
  import { workerConfigDigest, validateWorkerHealthConfig } from '../config/aiwg-config.js';
9
10
  import { assertValidWorkerPayload } from '../mission-protocol/codecs.js';
10
11
  import { qualificationDigest } from '../models/qualification-digest.mjs';
11
- import { qualifyWorkerInvocationProfile, isOperatorQualifiedWorkerAdapter, assertOperatorWorkerNativeArgv, assertWorkerSelectionAllowed, assertWorkerLaunchMode, WorkerRoleEffortError, requiredWorkerControls, requiresWorkerExternalBoundary, isCanonicalWorkerModelKey, assertWorkerHealthQualification, WorkerPolicyError, DEFAULT_WORKER_SELECTION } from '../models/provider-policy.js';
12
+ import { prepareWorkerInvocation } from './worker-invocation-preparation.js';
13
+ export { prepareWorkerInvocation, mcpToolSchemaDigest } from './worker-invocation-preparation.js';
14
+ import { isOperatorQualifiedWorkerAdapter, assertOperatorWorkerNativeArgv, assertWorkerLaunchMode, WorkerRoleEffortError, requiredWorkerControls, isCanonicalWorkerModelKey, WorkerPolicyError, DEFAULT_WORKER_SELECTION } from '../models/provider-policy.js';
12
15
  /** Frozen per-attempt windows; parent/transport ceilings include time already spent. */
13
16
  export function resolveWorkerEffectiveHealth(config, input) {
14
17
  const roleDefaults = { efficiency: 1, coding: 1, reasoning: 2 };
@@ -70,165 +73,6 @@ export function resolveWorkerEffectiveHealth(config, input) {
70
73
  assertValidWorkerPayload('workerEffectiveHealth', health);
71
74
  return health;
72
75
  }
73
- /**
74
- * The exact digest formula an MCP worker binding's `inputSchemaDigest`/
75
- * `outputSchemaDigest` are pinned against (used below and reused verbatim by
76
- * the #2892 MCP worker-invoke bridge's own live `tools/list` schema-drift
77
- * check — see `src/mcp/tools/worker-invoke.mjs` M3). Deliberately the raw
78
- * `JSON.stringify` of the schema object, NOT `workerConfigDigest`'s
79
- * canonicalized (sorted-key) form — a schema's own declared key order is
80
- * part of what gets pinned at qualification time.
81
- */
82
- export function mcpToolSchemaDigest(schema) {
83
- return `sha256:${createHash('sha256').update(JSON.stringify(schema)).digest('hex')}`;
84
- }
85
- export async function prepareWorkerInvocation(invocation, qualification) {
86
- const result = structuredClone(invocation);
87
- // shellCommand is derived syntax, never authority supplied by a brief.
88
- if (result.transport === 'shell')
89
- result.shellCommand = '';
90
- // Validate the established invocation shape first. Health is optional and
91
- // is checked after policy, installation and transport qualification below.
92
- const contract = { ...result };
93
- delete contract.health;
94
- assertValidWorkerPayload('workerResolvedInvocation', contract);
95
- if ((qualification.platform ?? process.platform) !== 'linux') {
96
- throw new Error('Unsupported worker invocation platform; Linux is required');
97
- }
98
- if (!Number.isFinite(qualification.now) || Date.parse(result.expiresAt) <= qualification.now) {
99
- throw new Error('Worker invocation is expired or has no validation clock');
100
- }
101
- if (qualification.adapter.id !== qualification.adapterId) {
102
- throw new Error('Worker invocation adapter identity mismatch');
103
- }
104
- assertWorkerLaunchMode(qualification.adapter, qualification.permissions);
105
- if (result.isolation !== undefined && result.isolation !== (qualification.permissions?.launchMode ?? 'systemd-unit')) {
106
- throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker launch mode differs from the execution policy');
107
- }
108
- for (const [key, value] of Object.entries(qualification.expectedDigests ?? {})) {
109
- if (result[key] !== value)
110
- throw new WorkerPolicyError('OG_PIN_MISMATCH', `Worker invocation ${key} changed`);
111
- }
112
- const verified = Date.parse(qualification.adapter.qualification.verifiedAt ?? '');
113
- const policyDays = qualification.policy?.freshness?.policyDays ?? DEFAULT_WORKER_SELECTION.freshness.policyDays;
114
- if (!Number.isFinite(verified) || verified > qualification.now || qualification.now - verified >= policyDays * 86400000) {
115
- throw new Error('Worker installation qualification is stale or invalid');
116
- }
117
- assertWorkerSelectionAllowed(result.selected, qualification);
118
- const profile = qualifyWorkerInvocationProfile(qualification.adapter, {
119
- profile: result.selected.profileRef, transport: result.transport,
120
- cliVersion: qualification.cliVersion,
121
- requiredControls: qualification.requiredControls, policy: qualification.policy,
122
- permissions: qualification.permissions, effort: result.selected.effort.value, boundary: qualification.boundary,
123
- mcp: qualification.mcp,
124
- });
125
- if (requiresWorkerExternalBoundary(qualification.adapter, { ...qualification,
126
- profile: result.selected.profileRef, transport: result.transport, effort: result.selected.effort.value })) {
127
- const installed = qualification.installation;
128
- const evidence = profile.installationQualification;
129
- if (!installed || !evidence || ['scopeDigest', 'fixtureDigest', 'verifiedAt', 'evidenceRef']
130
- .some(key => installed.evidence?.[key] !== evidence[key])
131
- || !qualification.boundary || qualificationDigest(qualification.boundary) !== qualificationDigest(installed.boundary)) {
132
- throw new WorkerPolicyError('OG_ADAPTER_UNQUALIFIED', 'Worker installation evidence and qualified boundary must match');
133
- }
134
- }
135
- if (profile.installationQualification) {
136
- const installedAt = Date.parse(profile.installationQualification.verifiedAt);
137
- if (!Number.isFinite(installedAt) || installedAt > qualification.now
138
- || qualification.now - installedAt >= policyDays * 86400000) {
139
- throw new Error('Worker installation qualification is stale or invalid');
140
- }
141
- }
142
- const scopeDigest = profile.installationQualification?.scopeDigest ?? qualification.adapter.qualification.fixtureDigest;
143
- if (result.selected.qualificationDigest !== scopeDigest)
144
- throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker qualification digest changed');
145
- const absolute = (value) => typeof value === 'string' && path.posix.isAbsolute(value) && !value.includes('\0');
146
- for (const key of ['cwd', 'briefPath', 'stdoutPath', 'stderrPath', 'finalPath']) {
147
- if (!absolute(result[key]))
148
- throw new Error(`Worker invocation requires absolute ${key}`);
149
- }
150
- const briefPath = path.posix.normalize(result.briefPath);
151
- if ([result.stdoutPath, result.stderrPath, result.finalPath].some(value => path.posix.normalize(value) === briefPath)
152
- || path.posix.normalize(result.stdoutPath) === path.posix.normalize(result.stderrPath)) {
153
- throw new Error('Worker invocation input/output paths must be distinct');
154
- }
155
- if (result.transport === 'shell') {
156
- assertOperatorWorkerNativeArgv(qualification.adapter, result.argv, result.selected, qualification.permissions);
157
- if (!absolute(result.argv[0]) || result.argv[0].includes('=') || result.argv.some(value => value.includes('\0'))) {
158
- throw new WorkerPolicyError('OG_CONFIG_REFERENCE', 'Worker invocation requires an absolute binary without equals and NUL-free argv');
159
- }
160
- if (result.envNames.some(name => !profile.envAllowlist.includes(name))) {
161
- throw new Error('Worker invocation environment is outside the qualified allowlist');
162
- }
163
- const quote = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
164
- // Only validated environment names appear as shell syntax; their values
165
- // remain in the conductor environment and are never printed in the plan.
166
- const environment = result.envNames.map(name => `${name}="\${${name}-}"`).join(' ');
167
- // The frozen 'closed' value means empty stdin (immediate EOF). 'brief-json'
168
- // pipes a conductor-built params document framed from the brief file
169
- // (worker-stdin-frame.mjs), so brief text never enters any argv.
170
- const worker = `env -i${environment ? ` ${environment}` : ''} ${result.argv.map(quote).join(' ')}`;
171
- const outputs = `> ${quote(result.stdoutPath)} 2> ${quote(result.stderrPath)}`;
172
- if (result.stdin === 'brief-json') {
173
- const framer = fileURLToPath(new URL('./worker-stdin-frame.mjs', import.meta.url));
174
- const frame = [process.execPath, framer, result.briefPath, result.selected.model, result.cwd].map(quote).join(' ');
175
- result.shellCommand = `cd ${quote(result.cwd)} && env -i ${frame} | ${worker} ${outputs}`;
176
- }
177
- else if (result.stdin === 'adapter-frame') {
178
- const binding = qualification.stdinFramerBinding;
179
- if (!binding || !absolute(binding.storePath) || !/^sha256:[a-f0-9]{64}$/.test(binding.storeDigest)) {
180
- throw new Error('Adapter stdin framer requires a trusted installation binding');
181
- }
182
- const framer = fileURLToPath(new URL('./worker-stdin-frame.mjs', import.meta.url));
183
- const frame = [process.execPath, framer, '--adapter-frame', result.briefPath, result.cwd,
184
- result.selected.cli, qualification.adapterId, result.selected.qualificationDigest, result.briefDigest,
185
- binding.storePath, binding.storeDigest, JSON.stringify(qualification.policy ?? null)].map(quote).join(' ');
186
- result.shellCommand = `cd ${quote(result.cwd)} && env -i${environment ? ` ${environment}` : ''} ${frame} | ${worker} ${outputs}`;
187
- }
188
- else if (result.stdin === 'closed' || result.stdin === 'brief-text') {
189
- const stdin = result.stdin === 'closed' ? '< /dev/null' : `< ${quote(result.briefPath)}`;
190
- result.shellCommand = `cd ${quote(result.cwd)} && ${worker} ${stdin} ${outputs}`;
191
- }
192
- else {
193
- throw new Error(`Unsupported worker stdin mode: ${result.stdin}`);
194
- }
195
- }
196
- else {
197
- const binding = result.mcp;
198
- for (const key of ['serverRef', 'tool', 'inputSchemaDigest', 'outputSchemaDigest', 'sessionIsolation']) {
199
- if (binding[key] !== profile.mcp[key])
200
- throw new WorkerPolicyError('OG_PIN_MISMATCH', `MCP worker binding changed: ${key}`);
201
- }
202
- if (binding.timeoutSeconds > result.limits.wallSeconds) {
203
- throw new Error('MCP worker timeout exceeds the invocation deadline');
204
- }
205
- for (const key of ['input', 'output']) {
206
- const schema = qualification.mcpSchemas?.[key];
207
- if (!schema || mcpToolSchemaDigest(schema) !== binding[`${key}SchemaDigest`]) {
208
- throw new WorkerPolicyError('OG_PIN_MISMATCH', `MCP worker ${key} schema digest changed`);
209
- }
210
- }
211
- const { default: Ajv2020 } = await import('ajv/dist/2020.js');
212
- const { default: addFormats } = await import('ajv-formats');
213
- const validate = addFormats(new Ajv2020({ strict: true, allErrors: true })).compile(qualification.mcpSchemas.input);
214
- if (!validate(binding.arguments))
215
- throw new Error('MCP worker arguments violate the pinned input schema');
216
- }
217
- assertValidWorkerPayload('workerResolvedInvocation', result);
218
- if (result.health !== undefined) {
219
- if (result.health.profileRef !== result.selected.profileRef
220
- || result.health.hardTimeoutSeconds > Math.min(result.limits.wallSeconds, result.mcp?.timeoutSeconds ?? Infinity)) {
221
- throw new Error('Worker health profile or wall ceiling mismatch');
222
- }
223
- if (['native', 'external'].includes(result.health.stepEnforcement)
224
- && profile.controls.stepCap?.support !== result.health.stepEnforcement)
225
- throw new Error('Unqualified worker step enforcement');
226
- }
227
- assertWorkerHealthQualification(profile, { profileRef: result.selected.profileRef, cliVersion: qualification.cliVersion,
228
- accountScope: qualification.installation?.accountScope ?? '', inferenceProvider: result.selected.inferenceProvider,
229
- model: result.selected.model, effort: result.selected.effort.value });
230
- return result;
231
- }
232
76
  const invocationChecks = {
233
77
  invocation_build_failed: { pointer: '/invocation/buildInvocation',
234
78
  message: 'Worker adapter could not build the invocation; check the selected model, effort, permissions and request paths.' },
@@ -269,127 +113,7 @@ export async function buildWorkerInvocationTransport(driver, input) {
269
113
  }
270
114
  return transport;
271
115
  }
272
- /** Only fixed argument guidance is safe to expose in public request diagnostics. */
273
- class WorkerRequestArgumentError extends WorkerPolicyError {
274
- pointer;
275
- constructor(key) {
276
- super('OG_CONFIG_REFERENCE', key === 'task'
277
- ? `Missing required --task. Valid task classes: ${Object.keys(DEFAULT_WORKER_SELECTION.taskClasses).join(', ')}, plus classes configured in defaults.workerSelection.taskClasses.`
278
- : `--${key.replace('_', '-')} must be an absolute path; relative paths are not resolved against cwd or --workdir.`);
279
- this.pointer = `/request/${key}`;
280
- }
281
- }
282
- /** Reject unsupported syntax before discovery or any path reads. */
283
- export function parseWorkerCommandArgs(args) {
284
- const names = { role: 'role', task: 'task', group: 'group', brief: 'brief',
285
- workdir: 'workdir', 'final-output': 'final_output', transport: 'transport', format: 'format' };
286
- const result = {};
287
- let json = false;
288
- let premiumAuthorized = false;
289
- for (let i = 0; i < args.length; i++) {
290
- const token = args[i];
291
- if (token === '--authorize-premium') {
292
- if (premiumAuthorized)
293
- throw new Error('Duplicate --authorize-premium');
294
- premiumAuthorized = true;
295
- continue;
296
- }
297
- if (token === '--json') {
298
- if (json)
299
- throw new Error('Duplicate --json');
300
- json = true;
301
- continue;
302
- }
303
- const match = /^--([^=]+)(?:=([\s\S]*))?$/.exec(token);
304
- if (!match || !Object.hasOwn(names, match[1]))
305
- throw new Error(`Unsupported models resolve argument: ${token}`);
306
- const key = names[match[1]];
307
- if (key in result)
308
- throw new Error(`Duplicate --${match[1]}`);
309
- const value = match[2] ?? args[++i];
310
- if (!value || value.startsWith('--') && match[2] === undefined || value.includes('\0')) {
311
- if (key === 'task' && !value?.includes('\0'))
312
- throw new WorkerRequestArgumentError('task');
313
- throw new Error(`--${match[1]} requires a value`);
314
- }
315
- result[key] = value;
316
- }
317
- if (json && result.format && result.format !== 'json')
318
- throw new Error('--json conflicts with --format');
319
- if (json)
320
- result.format = 'json';
321
- for (const key of ['role', 'task', 'group', 'brief', 'workdir', 'final_output']) {
322
- if (key === 'task' && !result[key])
323
- throw new WorkerRequestArgumentError('task');
324
- if (!result[key])
325
- throw new Error(`Missing ${key}`);
326
- }
327
- if (!['efficiency', 'coding', 'reasoning'].includes(result.role))
328
- throw new Error('Unsupported role');
329
- if (!/^[a-z][a-z0-9-]{0,62}$/.test(result.group))
330
- throw new Error('Invalid group');
331
- if (result.transport && !['shell', 'mcp'].includes(result.transport))
332
- throw new Error('Unsupported transport');
333
- if (result.format && !['shell', 'json'].includes(result.format))
334
- throw new Error('Unsupported renderer; use POSIX shell or JSON');
335
- return { ...result, ...(premiumAuthorized ? { authority: { premiumAuthorized: true, maxAutoTier: 3 } } : {}) };
336
- }
337
116
  const bytesDigest = (bytes) => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
338
- export async function inspectWorkerPath(filename, kind) {
339
- if (!path.isAbsolute(filename) || filename.includes('\0'))
340
- throw new Error('Absolute NUL-free paths are required');
341
- if (filename.split('/').includes('..'))
342
- throw new Error('Worker path traversal is not allowed');
343
- const normalized = path.resolve(filename);
344
- let current = normalized;
345
- while (true) {
346
- try {
347
- const canonical = await realpath(current);
348
- if (canonical !== current)
349
- throw new Error(`Symlink path is not allowed: ${filename}`);
350
- if (current !== normalized)
351
- return false;
352
- const stat = await lstat(normalized);
353
- if (kind === 'directory' ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1) {
354
- throw new Error(`Expected an unaliased ${kind}: ${filename}`);
355
- }
356
- return true;
357
- }
358
- catch (error) {
359
- if (error.code !== 'ENOENT')
360
- throw error;
361
- const parent = path.dirname(current);
362
- if (parent === current)
363
- throw error;
364
- current = parent;
365
- }
366
- }
367
- }
368
- const inspectPath = inspectWorkerPath;
369
- async function readBrief(filename) {
370
- if (!await inspectPath(filename, 'file'))
371
- return null;
372
- const handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
373
- try {
374
- const stat = await handle.stat();
375
- if (!stat.isFile() || stat.nlink !== 1 || stat.size > 262144)
376
- throw new Error('Brief must be a bounded regular file (256 KiB)');
377
- const buffer = Buffer.alloc(262145);
378
- let length = 0;
379
- while (length < buffer.length) {
380
- const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
381
- if (!bytesRead)
382
- break;
383
- length += bytesRead;
384
- }
385
- if (length > 262144)
386
- throw new Error('Brief exceeds 256 KiB');
387
- return buffer.subarray(0, length);
388
- }
389
- finally {
390
- await handle.close();
391
- }
392
- }
393
117
  /** Structured root-check failure; only these explicit path details are public. */
394
118
  export class WorkerPathScopeError extends WorkerPolicyError {
395
119
  pointer;
@@ -399,7 +123,7 @@ export class WorkerPathScopeError extends WorkerPolicyError {
399
123
  }
400
124
  }
401
125
  /** Re-resolve saved JSON through the same trusted owners; saved argv is never authority. */
402
- export async function revalidateWorkerInvocationPlan(saved, snapshot, drivers, now = new Date().toISOString()) {
126
+ export async function revalidateWorkerInvocationPlan(saved, snapshot, drivers, now = new Date().toISOString(), invocationPath) {
403
127
  const managed = saved.mode === 'managed' && snapshot.config.parallelism?.worker_mode === 'managed';
404
128
  if (!saved.request || (saved.mode !== 'direct' && !managed) || saved.mode !== (snapshot.config.parallelism?.worker_mode ?? 'direct') || saved.status !== 'eligible'
405
129
  || (managed && saved.invocation?.mode !== 'managed')
@@ -407,7 +131,9 @@ export async function revalidateWorkerInvocationPlan(saved, snapshot, drivers, n
407
131
  throw new Error('A fresh launchable models resolve JSON plan is required');
408
132
  }
409
133
  assertValidWorkerPayload('workerResolvedInvocation', saved.invocation);
410
- const current = await resolveWorkerInvocation(snapshot, saved.request, drivers, now, { digests: saved.digests, briefDigest: saved.invocation.briefDigest ?? null });
134
+ if (saved.invocation.invocationId)
135
+ await assertConductorWorkerPlan(saved, invocationPath, drivers);
136
+ const current = await resolveWorkerInvocation(snapshot, saved.request, drivers, now, { digests: saved.digests, briefDigest: saved.invocation.briefDigest ?? null, invocationId: saved.invocation.invocationId });
411
137
  // Expiry is calculated from the resolution clock. All execution bindings,
412
138
  // controls, thresholds and paths must remain byte-for-byte digest equivalent.
413
139
  if (!current.invocation || current.status !== 'eligible'
@@ -449,7 +175,7 @@ export async function resolveWorkerInvocation(snapshot, request, drivers, now =
449
175
  if (managedIntent && workerConfigDigest(brief.intent) !== workerConfigDigest(managedIntent)) {
450
176
  throw new Error('Managed brief differs from parent task authority');
451
177
  }
452
- if (brief.intent.role !== request.role || brief.intent.taskClass !== request.task || brief.intent.group !== request.group
178
+ if (brief.intent.role !== (snapshot.models.defaults.workerSelection?.roleGuidance?.[request.role]?.baseRole ?? request.role) || brief.intent.taskClass !== request.task || brief.intent.group !== request.group
453
179
  || brief.workspace.path !== request.workdir || brief.output.resultPath !== request.final_output) {
454
180
  throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Brief role/task/group/workspace/output differs from request');
455
181
  }
@@ -545,14 +271,20 @@ export async function resolveWorkerInvocation(snapshot, request, drivers, now =
545
271
  requiredControls: healthConfig?.requireStepCap ? ['stepCap'] : [] });
546
272
  pointer = '/parallelism/health';
547
273
  failureCode = 'OG_ADAPTER_UNQUALIFIED';
548
- const healthInput = { role: request.role, effort: tuple.effort,
274
+ const healthInput = { role: plan.policy?.roleGuidance?.[request.role]?.baseRole ?? request.role, effort: tuple.effort,
549
275
  modelKey: tuple.modelKey, profileRef: tuple.profile, wallSeconds: limits.wallSeconds,
550
276
  transportSeconds: adapter.invocation.kind === 'http' ? adapter.invocation.timeoutSeconds : undefined,
551
277
  stepEnforcement: controls.includes('stepCap') ? profile.controls.stepCap.support : 'unavailable' };
552
278
  let effectiveHealth = resolveWorkerEffectiveHealth(healthConfig, healthInput);
553
- const transport = await buildWorkerInvocationTransport(driver, { selected: structuredClone(selected), permissions: structuredClone(tuple.permissions),
279
+ const invocationId = expected?.invocationId ?? randomUUID();
280
+ const transport = await buildWorkerInvocationTransport(driver, { invocationId, selected: structuredClone(selected), permissions: structuredClone(tuple.permissions),
554
281
  cwd: request.workdir, briefPath: request.brief, brief: bytes?.toString('utf8') ?? '', finalPath, limits: structuredClone(limits),
555
- health: structuredClone(effectiveHealth) });
282
+ health: structuredClone(effectiveHealth), role: request.role });
283
+ for (const output of Object.values(workerOutputPaths({ ...transport, selected, transport: tuple.transport, finalPath, stdoutPath, stderrPath }))) {
284
+ await inspectPath(output, 'output');
285
+ if (path.resolve(request.brief) === output)
286
+ throw new Error('Brief and output paths must be distinct');
287
+ }
556
288
  pointer = '/invocation/buildInvocation';
557
289
  if (transport.stdin === 'adapter-frame' && typeof driver.buildWorkerStdinFrame !== 'function')
558
290
  throw new Error('Installed adapter stdin framer is required');
@@ -564,7 +296,8 @@ export async function resolveWorkerInvocation(snapshot, request, drivers, now =
564
296
  isolation: tuple.permissions.launchMode ?? 'systemd-unit',
565
297
  policyDigest, catalogDigest: plan.digests.catalogDigest, profileDigest: plan.digests.profileDigest,
566
298
  expiresAt, cwd: request.workdir, briefPath: request.brief, stdoutPath, stderrPath, finalPath,
567
- ...(transport.artifacts ? { artifacts: transport.artifacts } : {}),
299
+ ...(transport.artifacts ? { artifacts: transport.artifacts,
300
+ ...(transport.artifacts.some(artifact => artifact.location === 'workspace-relative') ? { invocationId } : {}) } : {}),
568
301
  argv: transport.argv, stdin: transport.stdin, envNames: transport.envNames, mcp: transport.mcp,
569
302
  ...(tuple.transport === 'shell' ? { shellCommand: '' } : {}),
570
303
  readiness: bytes !== null && workspaceExists && outputParentsExist ? 'launchable' : 'preparatory', briefDigest,
@@ -3,8 +3,8 @@ export const WORKER_STDIN_FRAME_MAX_BRIEF_BYTES: number;
3
3
  export const WORKER_STDIN_FRAME_MAX_BYTES: number;
4
4
  export function recordedWorkerStdinBytes(input: {
5
5
  filename: string; workspace: string; cli: string; adapterId: string; scopeDigest: string; briefDigest: string;
6
- storePath: string; storeDigest: string; policy?: import('../models/types.js').WorkerSelectionPolicy;
6
+ storePath: string; storeDigest: string; model?: string; policy?: import('../models/types.js').WorkerSelectionPolicy;
7
7
  }): Promise<Buffer | null>;
8
8
  export function workerStdinDocument(input: { brief: string | Buffer; model: string; cwd: string }): string;
9
9
  export function workerStdinBytes(invocation: { stdin: 'closed' | 'brief-text' | 'brief-json' | 'adapter-frame'; cwd?: string;
10
- selected?: { model?: string } }, brief: string | Buffer, adapter?: { buildWorkerStdinFrame?(brief: string | Buffer): string | Uint8Array }): Buffer | null;
10
+ selected?: { model?: string } }, brief: string | Buffer, adapter?: { buildWorkerStdinFrame?(brief: string | Buffer, invocation?: { selected?: { model?: string } }): string | Uint8Array }): Buffer | null;
@@ -39,7 +39,7 @@ export function workerStdinBytes(invocation, brief, adapter) {
39
39
  if (invocation.stdin === 'adapter-frame') {
40
40
  if (typeof adapter?.buildWorkerStdinFrame !== 'function') throw new Error('Installed adapter stdin framer is required');
41
41
  if (Buffer.byteLength(brief) > WORKER_STDIN_FRAME_MAX_BRIEF_BYTES) throw new Error('Brief exceeds the framing bound');
42
- const frame = adapter.buildWorkerStdinFrame(brief);
42
+ const frame = adapter.buildWorkerStdinFrame(brief, invocation);
43
43
  if (typeof frame !== 'string' && !(frame instanceof Uint8Array)) throw new Error('Adapter stdin frame must be bytes or text');
44
44
  if (Buffer.byteLength(frame) === 0) throw new Error('Adapter stdin frame must not be empty');
45
45
  if (Buffer.byteLength(frame) > WORKER_STDIN_FRAME_MAX_BYTES) throw new Error('Adapter stdin frame exceeds byte bound');
@@ -67,7 +67,7 @@ async function readBounded(filename) {
67
67
 
68
68
  /** Bound host authority only; workspace is an execution scope, never a config source. */
69
69
  export async function recordedWorkerStdinBytes({ filename, workspace, cli, adapterId, scopeDigest, briefDigest,
70
- storePath, storeDigest, policy }) {
70
+ storePath, storeDigest, policy, model }) {
71
71
  if (!filename?.startsWith('/') || !storePath?.startsWith('/')
72
72
  || !/^sha256:[a-f0-9]{64}$/.test(storeDigest ?? '')) throw new Error('Invalid adapter stdin framing request');
73
73
  const { loadRecordedWorkerInstallations } = await import('../models/worker-installation.mjs');
@@ -80,7 +80,7 @@ export async function recordedWorkerStdinBytes({ filename, workspace, cli, adapt
80
80
  || driver.qualification.installation?.evidence?.scopeDigest !== scopeDigest) throw new Error('Adapter stdin binding changed');
81
81
  const brief = await readBounded(filename);
82
82
  if (`sha256:${createHash('sha256').update(brief).digest('hex')}` !== briefDigest) throw new Error('Adapter stdin brief changed');
83
- return workerStdinBytes({ stdin: 'adapter-frame' }, brief, driver);
83
+ return workerStdinBytes({ stdin: 'adapter-frame', selected: { model } }, brief, driver);
84
84
  }
85
85
 
86
86
  // Pipeline stage: `worker-stdin-frame.mjs BRIEF_PATH MODEL CWD`. Only the
@@ -89,13 +89,13 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
89
89
  const [briefPath, model, cwd] = process.argv.slice(2);
90
90
  try {
91
91
  if (briefPath === '--adapter-frame') {
92
- const [filename, workspace, cli, adapterId, scopeDigest, briefDigest, storePath, storeDigest, policyJson] = process.argv.slice(3);
93
- if (process.argv.length !== 12) throw new Error('Invalid adapter stdin framing request');
92
+ const [filename, workspace, cli, adapterId, scopeDigest, briefDigest, storePath, storeDigest, policyJson, selectedModel] = process.argv.slice(3);
93
+ if (process.argv.length !== 13) throw new Error('Invalid adapter stdin framing request');
94
94
  // Reuse the recorded-installation loader; no module path or code supplied
95
95
  // by a brief is ever imported. The conductor binds store bytes and policy;
96
96
  // never resolve configuration from the worker-writable workspace.
97
97
  process.stdout.write(await recordedWorkerStdinBytes({ filename, workspace, cli, adapterId, scopeDigest, briefDigest,
98
- storePath, storeDigest, policy: JSON.parse(policyJson) ?? undefined }));
98
+ storePath, storeDigest, policy: JSON.parse(policyJson) ?? undefined, model: selectedModel }));
99
99
  } else {
100
100
  if (process.argv.length !== 5 || !briefPath?.startsWith('/')) throw new Error('usage: worker-stdin-frame.mjs BRIEF_PATH MODEL CWD');
101
101
  process.stdout.write(workerStdinDocument({ brief: await readBounded(briefPath), model, cwd }));
@@ -87,7 +87,7 @@ export const MAX_SHOWN_GROUPS = 8;
87
87
  */
88
88
  function excludedMembers(parallelism, group) {
89
89
  const excluded = [];
90
- for (const member of group.members) {
90
+ for (const member of (group.members ?? [])) {
91
91
  if (member.enabled === false)
92
92
  excluded.push(`${member.id} (member disabled)`);
93
93
  else if (parallelism?.clis?.[member.cli]?.enabled === false)
@@ -97,15 +97,19 @@ function excludedMembers(parallelism, group) {
97
97
  }
98
98
  function groupSummaries(entries, parallelism) {
99
99
  return entries.map(([id, group]) => {
100
- const roles = [...new Set(group.members.flatMap(m => m.roles ?? []))].join('/');
101
- const clis = [...new Set(group.members.map(m => m.cli))].join('/');
102
- const parts = [`${oneLine(id, 64)} (members: ${group.members.length}`];
100
+ const roles = [...new Set((group.members ?? []).flatMap(m => m.roles ?? []))].join('/');
101
+ const clis = [...new Set((group.members ?? []).map(m => m.cli))].join('/');
102
+ const parts = [`${oneLine(id, 64)} (members: ${(group.members ?? []).length}`];
103
103
  if (clis)
104
104
  parts.push(`clis: ${oneLine(clis, 96)}`);
105
105
  if (roles)
106
106
  parts.push(`roles: ${oneLine(roles, 96)}`);
107
107
  if (group.maxParallel !== undefined)
108
108
  parts.push(`maxParallel: ${group.maxParallel}`);
109
+ if (group.efforts)
110
+ parts.push(`efforts: ${oneLine(group.efforts.join('/'), 96)}`);
111
+ if (group.dataPolicy)
112
+ parts.push(`dataPolicy: ${oneLine(JSON.stringify(group.dataPolicy), 240)}`);
109
113
  if (group.requiredRoles?.length)
110
114
  parts.push(`requiredRoles: ${group.requiredRoles.join('/')}`);
111
115
  parts.push('configured');
@@ -147,13 +151,13 @@ async function observationSummaries(entries, deps) {
147
151
  `Cached model observations read at ${readAt.toISOString()}; catalog fetchedAt: ${stamp(catalog?.discovery?.fetchedAt)}. Ages are measured at this read and grow afterward. This cache may differ from the resolver's current installation observations; no eligibility read is performed by context generation. Missing or ambiguous observations have unknown age.`,
148
152
  '',
149
153
  ...entries.map(([id, group]) => {
150
- const observations = group.members.flatMap(member => {
154
+ const observations = (group.members ?? []).flatMap(member => {
151
155
  // Mirror the resolver's exact, unambiguous id/API-id lookup per installation.
152
156
  const rows = catalog?.discovery?.instances?.[member.cli]?.models
153
157
  .filter(row => row.id === member.model || row.apiModelId === member.model) ?? [];
154
158
  return rows.length === 1 && rows[0].worker ? [rows[0].worker] : [];
155
159
  });
156
- return `- ${oneLine(id, 64)} cached observations: ${observations.length}/${group.members.length} members; observedAt: ${range(observations.map(row => row.observedAt))}; verifiedAt: ${range(observations.map(row => row.verifiedAt))}.`;
160
+ return `- ${oneLine(id, 64)} cached observations: ${observations.length}/${(group.members ?? []).length} members; observedAt: ${range(observations.map(row => row.observedAt))}; verifiedAt: ${range(observations.map(row => row.verifiedAt))}.`;
157
161
  }),
158
162
  '',
159
163
  ];
@@ -313,7 +317,7 @@ export async function buildParallelismSection(projectPath, deps = {}) {
313
317
  const hard = healthValue(health, 'hardTimeoutSeconds', 1800);
314
318
  lines.push('### Orchestration groups (group dispatch enabled)');
315
319
  lines.push('');
316
- const entries = Object.entries(cfg.parallelism?.groups ?? {});
320
+ const entries = Object.entries(resolved.groups ?? {});
317
321
  if (entries.length > 0) {
318
322
  // Slice before summarizing so at most MAX_SHOWN_GROUPS summaries are built.
319
323
  const shown = groupSummaries(entries.slice(0, MAX_SHOWN_GROUPS), cfg.parallelism).join(' | ');
@@ -328,6 +332,14 @@ export async function buildParallelismSection(projectPath, deps = {}) {
328
332
  lines.push('');
329
333
  lines.push(...await observationSummaries(entries.slice(0, MAX_SHOWN_GROUPS), deps));
330
334
  const models = await new ConfigLoader(projectPath, { aiwgHome: resolveAiwgHome() }).load(cfg.parallelism);
335
+ for (const [pointer, origins] of Object.entries(models.workerPolicyOrigins ?? {})) {
336
+ if (!pointer.startsWith('/parallelism/groups/') || !pointer.endsWith('/template'))
337
+ continue;
338
+ lines.push(`Template expansion ${oneLine(pointer, 160)}: ${origins.map(origin => `${oneLine(origin.source, 240)}#${oneLine(origin.pointer, 160)} (${origin.digest})`).join(' → ')}. Contributions are retained in workerPolicyOrigins; inherited ceilings remain binding.`);
339
+ }
340
+ const aliases = Object.entries(models.defaults.workerSelection?.roleGuidance ?? {}).filter(([, guidance]) => guidance.baseRole);
341
+ for (const [role, guidance] of aliases)
342
+ lines.push(`Project role ${role} aliases ${guidance.baseRole}; ordered groups: ${guidance.groups.join(', ')}. Resolver eligibility and base-role qualification remain required.`);
331
343
  const freshness = models.defaults.workerSelection.freshness;
332
344
  lines.push(`Resolver freshness limits from merged model policy: observation warning after ${freshness.warnHours} hours; observation blocked after ${freshness.blockHours} hours; verification blocked after ${freshness.policyDays} days. Future-dated evidence is blocked. Re-resolve before launch; cached ages do not establish eligibility.`);
333
345
  lines.push('');
@@ -38,6 +38,7 @@
38
38
  */
39
39
  import { createHash } from 'node:crypto';
40
40
  import { debuglog } from 'node:util';
41
+ import { hasSecretLookingLiterals } from '../../mcp/credentials.mjs';
41
42
  const FORTEMI_QUERY_LIMIT = 50;
42
43
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
43
44
  function schemaProperties(tool) {
@@ -82,7 +83,7 @@ export function resolveMcpRequestHeaders(server, environment = process.env) {
82
83
  const headers = { ...(server.headers ?? {}) };
83
84
  for (const [header, envName] of Object.entries(server.headerEnv ?? {})) {
84
85
  if (!ENV_NAME.test(envName)) {
85
- throw new Error(`storage(fortemi): invalid environment variable reference "${envName}"`);
86
+ throw new Error('storage(fortemi): invalid environment variable reference; use ENV_NAME, file:ABSOLUTE_PATH or store:NAME.');
86
87
  }
87
88
  const value = environment[envName];
88
89
  if (!value) {
@@ -382,20 +383,31 @@ export const createDefaultMcpClient = async (serverName, registryOverride, envir
382
383
  }
383
384
  // Lazy imports keep unit tests that inject a stub isolated from transports.
384
385
  const { Client } = await import('@modelcontextprotocol/sdk/client/index.js');
386
+ const hasCredentials = [server.env, server.headers, server.envFrom, server.headerEnv]
387
+ .some(map => Object.keys(map ?? {}).length > 0);
388
+ if (hasCredentials && server.type === 'stdio' && debuglog('child_process').enabled) {
389
+ throw new Error('MCP stdio credentials require NODE_DEBUG child_process logging to be disabled.');
390
+ }
391
+ // Reuse the registry resolver for file/store references. Preserve legacy env
392
+ // bytes and Authorization's existing Bearer prefix at the transport boundary.
385
393
  const hasNewReferences = [...Object.values(server.headerEnv ?? {}), ...Object.values(server.envFrom ?? {})]
386
394
  .some(reference => /^(?:file|store):/.test(reference));
387
- // Keep legacy header/environment behavior unchanged. New locators are resolved
388
- // only at connection time and never fed back to the registry or config writers.
389
- if (hasNewReferences && server.type === 'stdio' && debuglog('child_process').enabled) {
390
- throw new Error('MCP file/store stdio credentials require NODE_DEBUG child_process logging to be disabled.');
391
- }
392
395
  const resolved = hasNewReferences
393
396
  ? await resolveMcpCredentialMaps(server, { ...sensitiveInput, environment })
394
- : undefined;
395
- const secrets = resolved ? [
396
- ...Object.keys(server.envFrom ?? {}).map(key => resolved.env?.[key]),
397
- ...Object.keys(server.headerEnv ?? {}).map(key => resolved.headers?.[key]),
398
- ].filter((value) => Boolean(value)).sort((a, b) => b.length - a.length) : [];
397
+ : { env: server.env ? { ...server.env } : undefined, headers: resolveMcpRequestHeaders(server, environment) };
398
+ if (!hasNewReferences) {
399
+ for (const [key, name] of Object.entries(server.envFrom ?? {})) {
400
+ if (!ENV_NAME.test(name) || !environment[name]) {
401
+ throw new Error('storage(fortemi): envFrom requires a set ENV_NAME, file:ABSOLUTE_PATH or store:NAME.');
402
+ }
403
+ resolved.env = { ...resolved.env, [key]: environment[name] };
404
+ }
405
+ }
406
+ const secrets = [
407
+ ...Object.entries(resolved.env ?? {}).filter(([key, value]) => key in (server.envFrom ?? {}) || hasSecretLookingLiterals({ name: server.name, env: { [key]: value } })).map(([, value]) => value),
408
+ ...Object.values(resolved.headers ?? {}),
409
+ ...Object.values(resolved.headers ?? {}).map(value => value.replace(/^Bearer /i, '')),
410
+ ].filter(Boolean).sort((a, b) => b.length - a.length);
399
411
  const redact = (text) => secrets.reduce((text, secret) => text.split(secret).join('[REDACTED]'), text);
400
412
  const protect = async (operation) => {
401
413
  try {
@@ -421,8 +433,8 @@ export const createDefaultMcpClient = async (serverName, registryOverride, envir
421
433
  transport = new StdioClientTransport({
422
434
  command: server.command ?? '',
423
435
  args: server.args ?? [],
424
- env: resolved?.env ?? server.env,
425
- ...(hasNewReferences ? { stderr: 'pipe' } : {}),
436
+ env: resolved.env,
437
+ ...(hasCredentials ? { stderr: 'pipe' } : {}),
426
438
  });
427
439
  }
428
440
  else {
@@ -430,8 +442,8 @@ export const createDefaultMcpClient = async (serverName, registryOverride, envir
430
442
  throw new Error(`storage(fortemi): MCP server "${serverName}" has no URL`);
431
443
  }
432
444
  const url = validateRemoteMcpUrl(server.url);
433
- const headers = resolved?.headers ?? resolveMcpRequestHeaders(server, environment);
434
- if (resolved) {
445
+ const headers = resolved.headers ?? {};
446
+ if (hasNewReferences) {
435
447
  for (const header of Object.keys(server.headerEnv ?? {})) {
436
448
  if (header.toLowerCase() === 'authorization')
437
449
  headers[header] = `Bearer ${headers[header]}`;
@@ -462,36 +474,28 @@ export const createDefaultMcpClient = async (serverName, registryOverride, envir
462
474
  }
463
475
  }
464
476
  const client = new Client({ name: 'aiwg-storage-fortemi-adapter', version: '1.0.0' }, { capabilities: {} });
465
- if (hasNewReferences && server.type === 'stdio') {
477
+ if (hasCredentials && server.type === 'stdio') {
466
478
  // The recipient's stderr can contain credentials. Drain without forwarding.
467
479
  transport.stderr?.on('data', () => { });
468
480
  }
469
- if (hasNewReferences)
470
- await protect(() => client.connect(transport));
471
- else
472
- await client.connect(transport);
481
+ await protect(() => client.connect(transport));
473
482
  return {
474
483
  async callTool(name, args) {
475
484
  const call = async () => unwrapMcpToolResult(await client.callTool({ name, arguments: args }));
476
- return hasNewReferences ? protectResult(await protect(call)) : call();
485
+ return protectResult(await protect(call));
477
486
  },
478
487
  async listTools() {
479
- return hasNewReferences ? protectResult(await protect(() => client.listTools())) : client.listTools();
488
+ return protectResult(await protect(() => client.listTools()));
480
489
  },
481
490
  serverVersion() {
482
- return hasNewReferences ? protectResult(client.getServerVersion()) : client.getServerVersion();
491
+ return protectResult(client.getServerVersion());
483
492
  },
484
493
  async close() {
485
- if (hasNewReferences)
486
- await protect(() => client.close());
487
- else
488
- await client.close();
494
+ await protect(() => client.close());
489
495
  },
490
496
  };
491
497
  }
492
498
  catch (error) {
493
- if (!hasNewReferences)
494
- throw error;
495
499
  throw new Error(redact(error instanceof Error ? error.message : 'MCP transport failed'));
496
500
  }
497
501
  };