@aiwg/cli 2026.10.0 → 2026.10.3

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 (168) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +270 -0
  2. package/agentic/code/providers/model-capabilities.v1.json +3091 -24
  3. package/bin/aiwg.mjs +86 -21
  4. package/dist/src/a2a/codecs.js +9 -0
  5. package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
  6. package/dist/src/artifacts/repair.js +9 -4
  7. package/dist/src/channel/manager.mjs +59 -10
  8. package/dist/src/cli/child-process-result.js +40 -0
  9. package/dist/src/cli/env.js +38 -16
  10. package/dist/src/cli/flag-parsing.js +236 -0
  11. package/dist/src/cli/handlers/decision.js +297 -2
  12. package/dist/src/cli/handlers/diagnose.js +9 -2
  13. package/dist/src/cli/handlers/feedback.js +14 -5
  14. package/dist/src/cli/handlers/init.js +245 -30
  15. package/dist/src/cli/handlers/mc.js +23 -0
  16. package/dist/src/cli/handlers/models.js +69 -6
  17. package/dist/src/cli/handlers/ralph.js +3 -0
  18. package/dist/src/cli/handlers/refresh.js +16 -0
  19. package/dist/src/cli/handlers/regenerate.js +14 -0
  20. package/dist/src/cli/handlers/runtime-info.js +24 -9
  21. package/dist/src/cli/handlers/script-runner.js +16 -6
  22. package/dist/src/cli/handlers/session.js +396 -62
  23. package/dist/src/cli/handlers/sessions.js +146 -40
  24. package/dist/src/cli/handlers/setup-manifest.js +439 -39
  25. package/dist/src/cli/handlers/setup.js +139 -39
  26. package/dist/src/cli/handlers/subcommands.js +3 -1
  27. package/dist/src/cli/handlers/team.js +18 -8
  28. package/dist/src/cli/handlers/use.js +13 -0
  29. package/dist/src/cli/handlers/utilities.js +106 -23
  30. package/dist/src/cli/prompt-utils.js +72 -0
  31. package/dist/src/cli/provider-resolution.js +11 -0
  32. package/dist/src/config/aiwg-config.js +268 -4
  33. package/dist/src/config/cli.js +146 -19
  34. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  35. package/dist/src/config/project-artifacts-health.mjs +14 -3
  36. package/dist/src/config/user-config.js +36 -10
  37. package/dist/src/decision/adapters/jev.js +80 -28
  38. package/dist/src/decision/ask.js +352 -0
  39. package/dist/src/decision/driver.js +4 -0
  40. package/dist/src/decision/entry.js +38 -6
  41. package/dist/src/decision/heldout/collector.js +10 -1
  42. package/dist/src/decision/index.js +3 -0
  43. package/dist/src/decision/interop.js +60 -8
  44. package/dist/src/decision/jev-credentials.js +121 -0
  45. package/dist/src/decision/ledger.js +839 -0
  46. package/dist/src/decision/validate.js +6 -3
  47. package/dist/src/effects/dispatch-verifier.js +252 -0
  48. package/dist/src/effects/index.js +1 -0
  49. package/dist/src/features/installer.js +2 -2
  50. package/dist/src/flow/graph-metadata.js +5 -0
  51. package/dist/src/gates/providers/loader.js +3 -1
  52. package/dist/src/installation/manager-command.mjs +1 -1
  53. package/dist/src/installation/manager.mjs +17 -2
  54. package/dist/src/lint/runner.js +2 -2
  55. package/dist/src/mcp/adapters/codex-runtime.js +360 -56
  56. package/dist/src/mcp/cli.mjs +587 -77
  57. package/dist/src/mcp/config-file.mjs +279 -0
  58. package/dist/src/mcp/config-layers.mjs +348 -0
  59. package/dist/src/mcp/credentials.mjs +484 -0
  60. package/dist/src/mcp/grok-build-config.mjs +16 -26
  61. package/dist/src/mcp/helpers.mjs +106 -2
  62. package/dist/src/mcp/omp-config.mjs +50 -29
  63. package/dist/src/mcp/profiles.js +97 -23
  64. package/dist/src/mcp/profiles.mjs +99 -27
  65. package/dist/src/mcp/registry.js +264 -57
  66. package/dist/src/mcp/registry.mjs +198 -45
  67. package/dist/src/mcp/server.mjs +403 -20
  68. package/dist/src/mcp/toml-string.mjs +9 -0
  69. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  70. package/dist/src/mcp/tool-filters.mjs +357 -0
  71. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  72. package/dist/src/mcp/tools/decision.mjs +58 -0
  73. package/dist/src/mcp/tools/orchestration.mjs +38 -0
  74. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  75. package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
  76. package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
  77. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  78. package/dist/src/memory/project-registry.js +2 -2
  79. package/dist/src/mission-protocol/codecs.js +639 -1
  80. package/dist/src/mission-protocol/types.js +110 -0
  81. package/dist/src/mission-protocol/worker-status.js +2 -0
  82. package/dist/src/models/config-loader.js +70 -13
  83. package/dist/src/models/grok-build-models.js +93 -41
  84. package/dist/src/models/model-capabilities.v1.json +3091 -24
  85. package/dist/src/models/model-discovery.js +862 -63
  86. package/dist/src/models/provider-policy.js +625 -11
  87. package/dist/src/models/qualification-digest.mjs +17 -0
  88. package/dist/src/models/resolver.js +259 -0
  89. package/dist/src/models/worker-direct-readiness.js +529 -0
  90. package/dist/src/models/worker-dispatch-offer.js +169 -0
  91. package/dist/src/models/worker-health-readiness.js +131 -0
  92. package/dist/src/models/worker-installation.mjs +879 -0
  93. package/dist/src/models/wrapper-route.js +18 -0
  94. package/dist/src/ops/registry.js +9 -14
  95. package/dist/src/plugins/standalone-packager.js +4 -3
  96. package/dist/src/process/bounded-child-process.mjs +387 -0
  97. package/dist/src/process/cli-command.mjs +75 -0
  98. package/dist/src/providers/capability-matrix.js +8 -0
  99. package/dist/src/providers/capability-matrix.yaml +270 -0
  100. package/dist/src/providers/grok-build-headless.js +43 -21
  101. package/dist/src/providers/provider-definitions.js +9 -1
  102. package/dist/src/providers/provider-definitions.mjs +9 -2
  103. package/dist/src/serve/executor-registry.js +16 -24
  104. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  105. package/dist/src/serve/mission-conductor.js +9 -0
  106. package/dist/src/serve/shared-host-scheduler.js +2 -0
  107. package/dist/src/serve/worker-invocation.js +538 -0
  108. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  109. package/dist/src/sessions/adapter-registry.js +45 -0
  110. package/dist/src/sessions/adapters/generic.js +37 -3
  111. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  112. package/dist/src/sessions/adapters/grokbot.js +423 -24
  113. package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
  114. package/dist/src/sessions/adapters/muse.js +68 -29
  115. package/dist/src/sessions/adapters/pi.js +1 -0
  116. package/dist/src/sessions/batch-contracts.js +13 -1
  117. package/dist/src/sessions/batch-import.js +23 -42
  118. package/dist/src/sessions/candidates.js +47 -11
  119. package/dist/src/sessions/contracts.js +35 -0
  120. package/dist/src/sessions/coverage-preflight.js +134 -0
  121. package/dist/src/sessions/discovery.js +3 -0
  122. package/dist/src/sessions/grok-build-cli.js +67 -5
  123. package/dist/src/sessions/importer.js +24 -8
  124. package/dist/src/sessions/index.js +2 -0
  125. package/dist/src/sessions/repository.js +54 -13
  126. package/dist/src/sessions/workspace-discovery.js +99 -37
  127. package/dist/src/skills/adapters/clawhub.js +10 -8
  128. package/dist/src/skills/adapters/openclaw.js +10 -7
  129. package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
  130. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  131. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  132. package/dist/src/smiths/mcpsmith/example.js +1 -1
  133. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
  134. package/dist/src/storage/backends/logseq.js +68 -7
  135. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  136. package/dist/src/testing/generators/test-code-generator.js +57 -30
  137. package/dist/src/uhp/client.js +11 -1
  138. package/dist/src/uhp/mission.js +5 -0
  139. package/dist/src/update/checker.mjs +5 -5
  140. package/dist/src/update/notifier.mjs +54 -2
  141. package/dist/src/writing/fidelity.js +18 -1
  142. package/package.json +18 -1
  143. package/schemas/mission-v1.schema.json +2429 -0
  144. package/schemas/models/model-catalog.v1.schema.json +301 -0
  145. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  146. package/schemas/models/model-policy.v1.schema.json +14 -0
  147. package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
  148. package/tools/_resolve-impl.mjs +39 -0
  149. package/tools/agents/deploy-agents.mjs +15 -2
  150. package/tools/agents/providers/base.mjs +63 -43
  151. package/tools/agents/providers/grokbot.mjs +11 -12
  152. package/tools/agents/providers/hermes.mjs +6 -3
  153. package/tools/agents/providers/omp.mjs +5 -2
  154. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  155. package/tools/providers/deepseek-harness-transport.mjs +358 -0
  156. package/tools/providers/omp-transport.mjs +192 -0
  157. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  158. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  159. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  160. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  161. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  162. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  163. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  164. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  165. package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
  166. package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
  167. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  168. package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
@@ -0,0 +1,879 @@
1
+ /** Shared installed-worker qualification owner, also used by the Ralph compatibility facade. */
2
+ import { spawn } from 'node:child_process';
3
+ import { open, realpath, lstat, mkdir, rename, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { homedir } from 'node:os';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { createHash } from 'node:crypto';
8
+ import { parse } from 'acorn';
9
+ import { satisfies, valid, validRange } from 'semver';
10
+
11
+ // Shipped negative-suite definition. Evidence producers hash this definition,
12
+ // including case semantics and permission rows; arbitrary fixture labels cannot
13
+ // qualify an installation. Passed observations still come from the trusted host.
14
+ export const workerNegativeFixtureSuite = Object.freeze({
15
+ revision: 2,
16
+ cases: Object.freeze({
17
+ 'forbidden-write': 'Deny sibling/home writes; observe an allowed workspace write as a positive control.',
18
+ 'denied-egress': 'Observe an attempted forbidden connection and its denial at the enforcing boundary.',
19
+ 'authority-request': 'Reject requests to widen permissions without host authority.',
20
+ children: 'Deny or bound child workers under the same authority.',
21
+ fallback: 'Deny unapproved provider or model fallback.',
22
+ 'tool-allowlist': 'Reject a tool outside the authorized allowlist.',
23
+ 'model-pin': 'Observe the selected model and reject substitution.',
24
+ 'workspace-trust': 'Reject an untrusted workspace without a trust bypass.',
25
+ 'run-config': 'Reject changed or unscoped per-run configuration.',
26
+ }),
27
+ baseCases: Object.freeze(['forbidden-write', 'denied-egress', 'authority-request']),
28
+ // Canonical provider IDs only. Alias authority remains provider-definitions.
29
+ providers: Object.freeze({
30
+ codex: Object.freeze({ externalSandbox: false, cases: Object.freeze([]) }),
31
+ claude: Object.freeze({ externalSandbox: true, cases: Object.freeze(['tool-allowlist', 'model-pin']) }),
32
+ muse: Object.freeze({ externalSandbox: true, cases: Object.freeze(['workspace-trust', 'model-pin']) }),
33
+ hermes: Object.freeze({ externalSandbox: true, cases: Object.freeze(['run-config', 'model-pin']) }),
34
+ }),
35
+ defaultPolicy: 'External boundary and every negative case, regardless of requested controls.',
36
+ });
37
+
38
+ export async function workerNegativeFixtureDigest() {
39
+ const { qualificationDigest } = await import('./qualification-digest.mjs');
40
+ return qualificationDigest(workerNegativeFixtureSuite);
41
+ }
42
+
43
+ async function workerPermissionRequirements(provider, requiredControls) {
44
+ const { normalizeProviderDefinitionId } = await import('../providers/provider-definitions.js');
45
+ const id = normalizeProviderDefinitionId(provider);
46
+ const rows = workerNegativeFixtureSuite.providers;
47
+ const row = id && Object.hasOwn(rows, id) ? rows[id] : null;
48
+ return {
49
+ provider: id,
50
+ externalBoundary: !row || (row.externalSandbox && requiredControls.includes('sandbox')),
51
+ cases: row ? [...workerNegativeFixtureSuite.baseCases,
52
+ ...['children', 'fallback'].filter(name => requiredControls.includes(name)), ...row.cases]
53
+ : Object.keys(workerNegativeFixtureSuite.cases),
54
+ };
55
+ }
56
+
57
+ // Scoped native process mappings are exact argv tokens: either one flag or a
58
+ // JSON array for a contiguous flag/value sequence. Prose is not executable proof.
59
+ // External controls also require their declared tokens when they use argv;
60
+ // boundary-only prose mappings are checked against the qualified host boundary.
61
+ function hasControlArgvMapping(control) {
62
+ return control.support === 'native' || (control.support === 'external'
63
+ && (control.mapping.startsWith('[') || control.mapping.startsWith('-')));
64
+ }
65
+
66
+ function assertWorkerControlArgv(profile, argv) {
67
+ if (!Array.isArray(argv) || argv.some(token => typeof token !== 'string' || token.includes('\0'))) {
68
+ throw new Error('Worker control requires typed argv');
69
+ }
70
+ for (const [name, control] of Object.entries(profile.controls)) {
71
+ if (!hasControlArgvMapping(control)) continue;
72
+ let tokens;
73
+ try { tokens = control.mapping.startsWith('[') ? JSON.parse(control.mapping) : [control.mapping]; }
74
+ catch { throw new Error(`Worker control ${name} has invalid argv mapping`); }
75
+ const endOfOptions = argv.indexOf('--');
76
+ const options = endOfOptions < 0 ? argv : argv.slice(0, endOfOptions);
77
+ if (!Array.isArray(tokens) || !tokens.length || typeof tokens[0] !== 'string' || !/^--?[^-\s]/.test(tokens[0])
78
+ || tokens.some(token => typeof token !== 'string' || !token || token.includes('\0'))
79
+ || !options.some((_, index) => tokens.every((token, offset) => options[index + offset] === token))) {
80
+ throw new Error(`Worker control ${name} is absent from authorized argv`);
81
+ }
82
+ }
83
+ }
84
+
85
+
86
+ async function assertWorkerInstallationEvidence(request, scopeDigest) {
87
+ const { DEFAULT_WORKER_SELECTION } = await import('./provider-policy.js');
88
+ const { evidence, now } = request;
89
+ if (!Number.isFinite(now) || !evidence || !/^sha256:[a-f0-9]{64}$/.test(scopeDigest) || evidence.scopeDigest !== scopeDigest
90
+ || evidence.fixtureDigest !== await workerNegativeFixtureDigest()
91
+ || !Number.isFinite(Date.parse(evidence.verifiedAt)) || Date.parse(evidence.verifiedAt) > now
92
+ || typeof evidence.evidenceRef !== 'string' || !evidence.evidenceRef.trim()) {
93
+ throw new Error('Missing or stale installation evidence binding');
94
+ }
95
+ if (now - Date.parse(evidence.verifiedAt) >= (request.policy?.freshness?.policyDays ?? DEFAULT_WORKER_SELECTION.freshness.policyDays) * 86400000) {
96
+ throw new Error('Installation evidence expired');
97
+ }
98
+ const { cases: requiredCases } = await workerPermissionRequirements(request.adapter.provider, request.requiredControls);
99
+ if (requiredCases.some(id => evidence.cases?.[id] !== 'passed')) {
100
+ throw new Error('Installation negative evidence is incomplete');
101
+ }
102
+ }
103
+
104
+ /** Read-only recheck: the version probe is bound to the unchanged binary digest. */
105
+ async function recheckWorkerInstallation(instance, options) {
106
+ const request = structuredClone(options);
107
+ const { requiredWorkerControls } = await import('./provider-policy.js');
108
+ request.requiredControls = requiredWorkerControls(request);
109
+ const scope = await inspectWorkerInstallation(instance, request);
110
+ await assertWorkerInstallationEvidence(request, scope.scopeDigest);
111
+ return { scope, request };
112
+ }
113
+
114
+ export async function qualifyWorkerInstallation(instance, options) {
115
+ try {
116
+ const { scope, request } = await recheckWorkerInstallation(instance, options);
117
+ const { evidence } = request;
118
+ const { rawBanner, version } = await boundedVersionProbe(scope.binary.realpath,
119
+ request.adapter.invocation.versionArgv, request.environment ?? {}, request.cwd,
120
+ request.timeoutMs ?? 10_000, Math.min(request.maxBytes ?? 4096, 4096), request.adapter.invocation.versionPattern);
121
+ if (version !== request.cliVersion) throw new Error('Installed CLI version changed');
122
+ if (request.versionProbe) {
123
+ const pattern = request.adapter.invocation.versionPattern;
124
+ const captured = banner => pattern === undefined ? banner.trim()
125
+ : compileWorkerVersionPattern(pattern).exec(banner.trim())?.[1];
126
+ if (request.versionProbe.version !== version
127
+ || captured(request.versionProbe.rawBanner) !== captured(rawBanner)) {
128
+ throw new Error('Installed CLI captured version text changed');
129
+ }
130
+ }
131
+ // Detect changes during the probe as well as on subsequent dispatch.
132
+ if ((await inspectWorkerInstallation(instance, request)).scopeDigest !== scope.scopeDigest) {
133
+ throw new Error('Installation changed during qualification');
134
+ }
135
+ return { status: 'ENABLED', scopeDigest: scope.scopeDigest, fixtureDigest: evidence.fixtureDigest,
136
+ verifiedAt: evidence.verifiedAt, evidenceRef: evidence.evidenceRef,
137
+ cliVersion: version, rawVersionBanner: rawBanner, platform: request.platform ?? process.platform,
138
+ ...(request.mcpServerDigest ? { serverDigest: request.mcpServerDigest } : {}) };
139
+ } catch {
140
+ // Probe output, env values, argv and filesystem errors may contain secrets.
141
+ return { status: 'DISABLED', reason: 'Installation missing, unsupported, changed or not qualified' };
142
+ }
143
+ }
144
+
145
+ export async function prepareInstalledWorkerInvocation(instance, invocation, qualification) {
146
+ const { prepareWorkerInvocation } = await import('../serve/worker-invocation.js');
147
+ const { requiredWorkerControls, WorkerPolicyError } = await import('./provider-policy.js');
148
+ qualification = { ...qualification, requiredControls: requiredWorkerControls({
149
+ ...qualification, effort: invocation.selected.effort.value }) };
150
+ const result = await prepareWorkerInvocation(invocation, { ...qualification, adapterId: instance.getName() });
151
+ const profile = qualification.adapter.invocationProfiles[result.selected.profileRef];
152
+ if (profile.installationQualification) {
153
+ const installation = qualification.installation;
154
+ if (!installation) throw new Error('Worker installation recheck is required');
155
+ if (['scopeDigest', 'fixtureDigest', 'verifiedAt', 'evidenceRef'].some(key =>
156
+ installation.evidence?.[key] !== profile.installationQualification[key])
157
+ || installation.evidence?.fixtureDigest !== await workerNegativeFixtureDigest()) {
158
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker installation evidence changed');
159
+ }
160
+ const scope = await inspectWorkerInstallation(instance, { ...installation,
161
+ adapter: qualification.adapter, profile: result.selected.profileRef,
162
+ cliVersion: qualification.cliVersion, requiredControls: qualification.requiredControls,
163
+ policy: qualification.policy, permissions: qualification.permissions, effort: result.selected.effort.value,
164
+ boundary: qualification.boundary, mcp: qualification.mcp });
165
+ if (scope.scopeDigest !== profile.installationQualification.scopeDigest
166
+ || result.selected.qualificationDigest !== scope.scopeDigest) {
167
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker installation qualification changed');
168
+ }
169
+ if (await realpath(result.cwd) !== scope.cwd) {
170
+ throw new WorkerPolicyError('OG_CONFIG_REFERENCE', 'Worker cwd differs from the qualified workspace');
171
+ }
172
+ for (const key of ['briefPath', 'stdoutPath', 'stderrPath', 'finalPath']) {
173
+ const target = await canonicalFuturePath(result[key]);
174
+ const roots = key === 'briefPath' ? scope.inputRoots : scope.outputRoots;
175
+ if (!roots.some(root => inside(root, target))) throw new WorkerPolicyError('OG_CONFIG_REFERENCE', 'Worker input/output path is outside the qualified scope');
176
+ }
177
+ if (result.transport === 'shell') {
178
+ const expectedArtifacts = typeof instance.buildWorkerArtifacts === 'function'
179
+ ? instance.buildWorkerArtifacts({ ...result, permissions: qualification.permissions }) : undefined;
180
+ if (JSON.stringify(expectedArtifacts) !== JSON.stringify(result.artifacts)) throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker artifacts differ from authorized driver mapping');
181
+ const workerArgs = instance.buildWorkerArgs(result, profile, qualification.permissions);
182
+ assertWorkerControlArgv(profile, workerArgs);
183
+ const expected = [scope.binary.realpath, ...workerArgs];
184
+ if (JSON.stringify(expected) !== JSON.stringify(result.argv)
185
+ || result.stdin !== qualification.adapter.invocation.stdin) {
186
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker argv/stdin differs from the authorized driver mapping');
187
+ }
188
+ // Shell plans contain names, never credential values. Recheck the named
189
+ // launch environment now; the conductor must repeat preparation if it
190
+ // changes before copying the command into its shell.
191
+ if (JSON.stringify([...result.envNames].sort()) !== JSON.stringify(Object.keys(installation.environment ?? {}).sort())
192
+ || result.envNames.some(name => process.env[name] !== installation.environment[name])) {
193
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker launch environment differs from qualification');
194
+ }
195
+ }
196
+ }
197
+ return result;
198
+ }
199
+
200
+ /** @type {Map<string, () => ProviderAdapter>} */
201
+ const providerRegistry = new Map();
202
+ const installedModules = new Map();
203
+ let installedLoad = Promise.resolve();
204
+ // Same default byte ceiling as workerAdapter.catalogAdapter.maxBytes.
205
+ const installedModuleMaxBytes = 5 * 1024 * 1024;
206
+
207
+ async function binaryIdentity(filename, workerWritableRoots) {
208
+ if (typeof filename !== 'string' || !path.isAbsolute(filename) || filename.includes('\0')) throw new Error('Absolute installed binary required');
209
+ const canonical = await realpath(filename);
210
+ for (const root of workerWritableRoots) {
211
+ if (inside(await realpath(root), canonical)) throw new Error('Worker-writable binary');
212
+ }
213
+ const handle = await open(canonical, 'r');
214
+ try {
215
+ const stat = await handle.stat();
216
+ if (!stat.isFile() || !(stat.mode & 0o111) || (stat.mode & 0o022)
217
+ || (stat.nlink > 1 && (stat.mode & 0o222))) throw new Error('Untrusted binary permissions');
218
+ const hash = createHash('sha256');
219
+ // Incremental, bounded reads: never allocate the size reported by the binary.
220
+ const buffer = Buffer.alloc(64 * 1024);
221
+ let total = 0;
222
+ while (true) {
223
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
224
+ if (!bytesRead) break;
225
+ total += bytesRead;
226
+ if (total > 256 * 1024 * 1024) throw new Error('Binary exceeds qualification byte bound');
227
+ hash.update(buffer.subarray(0, bytesRead));
228
+ }
229
+ return { realpath: canonical, sha256: `sha256:${hash.digest('hex')}` };
230
+ } finally { await handle.close(); }
231
+ }
232
+
233
+ // Resolve existing ancestors too: a not-yet-created output can traverse a symlink.
234
+ async function canonicalFuturePath(filename) {
235
+ try { return await realpath(filename); }
236
+ catch (error) {
237
+ if (error.code !== 'ENOENT') throw error;
238
+ // A dangling link is not a new output: following it may leave the scope.
239
+ try { if ((await lstat(filename)).isSymbolicLink()) throw new Error('Dangling worker path'); }
240
+ catch (statError) { if (statError.code !== 'ENOENT') throw statError; }
241
+ const parent = path.dirname(filename);
242
+ if (parent === filename) throw error;
243
+ return path.join(await canonicalFuturePath(parent), path.basename(filename));
244
+ }
245
+ }
246
+
247
+ export async function inspectWorkerInstallation(instance, request) {
248
+ const { inspectWorkerInvocationProfile, requiredWorkerControls } = await import('./provider-policy.js');
249
+ const { qualificationDigest } = await import('./qualification-digest.mjs');
250
+ const { adapter, profile: profileId, cliVersion, workerWritableRoots,
251
+ effectiveConfigDigest, accountScope, boundary, cwd } = request;
252
+ const requiredControls = requiredWorkerControls(request);
253
+ const platform = request.platform ?? process.platform;
254
+ if (platform !== 'linux' || adapter.id !== instance.getName()) throw new Error('Unsupported installation identity/platform');
255
+ if (!Array.isArray(requiredControls) || !Array.isArray(workerWritableRoots)
256
+ || !path.isAbsolute(cwd ?? '') || !/^sha256:[a-f0-9]{64}$/.test(effectiveConfigDigest)
257
+ || typeof accountScope !== 'string' || !accountScope) throw new Error('Missing installation scope');
258
+ const profile = inspectWorkerInvocationProfile(adapter, { profile: profileId,
259
+ transport: adapter.invocationProfiles?.[profileId]?.transport, cliVersion, requiredControls, policy: request.policy, permissions: request.permissions,
260
+ effort: request.effort, boundary, mcp: request.mcp });
261
+ if (profile.transport === 'mcp' && !/^sha256:[a-f0-9]{64}$/.test(request.mcpServerDigest)) {
262
+ throw new Error('MCP registration binding required');
263
+ }
264
+ if (adapter.invocation.kind !== 'process') throw new Error('HTTP installation requires its qualified local transport driver');
265
+ if (!boundary || !['native', 'os', 'executor'].includes(boundary.kind)
266
+ || typeof boundary.identity !== 'string' || !boundary.identity
267
+ || !/^sha256:[a-f0-9]{64}$/.test(boundary.digest) || !Array.isArray(boundary.controls)) throw new Error('Missing enforcing boundary');
268
+ if (requiredControls.some(name => profile.controls[name].support === 'external' && !boundary.controls.includes(name))) {
269
+ throw new Error('External control lacks an enforcing boundary');
270
+ }
271
+ const permissionRequirements = await workerPermissionRequirements(adapter.provider, requiredControls);
272
+ if (permissionRequirements.externalBoundary && boundary.kind === 'native') {
273
+ throw new Error('Profile requires an external enforcing boundary');
274
+ }
275
+ let controlArgv = null;
276
+ if (Object.values(profile.controls).some(hasControlArgvMapping)) {
277
+ if (!request.invocation) throw new Error('Worker control qualification requires representative argv invocation');
278
+ controlArgv = instance.buildWorkerArgs(structuredClone(request.invocation), profile, request.permissions);
279
+ assertWorkerControlArgv(profile, controlArgv);
280
+ }
281
+ const environment = request.environment ?? {};
282
+ if (Object.entries(environment).some(([name, value]) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
283
+ || !profile.envAllowlist.includes(name) || !adapter.invocation.envAllowlist.includes(name)
284
+ || typeof value !== 'string' || value.includes('\0'))) throw new Error('Unscoped worker environment');
285
+ const endpoints = request.endpoints ?? [];
286
+ for (const endpoint of endpoints) {
287
+ const url = new URL(endpoint);
288
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash
289
+ || !request.allowedEndpoints?.includes(endpoint)) throw new Error('Endpoint is not explicitly authorized');
290
+ }
291
+ // The allowlist is supplied by the installation authority, never a downloaded manifest.
292
+ const references = [adapter.runtimeDriver, ...(adapter.catalogAdapter.kind === 'module' ? [adapter.catalogAdapter.locator] : [])];
293
+ const modules = [];
294
+ for (const reference of new Set(references)) {
295
+ const matches = (request.modules ?? []).filter(entry => entry.reference === reference);
296
+ if (matches.length !== 1) throw new Error('Missing trusted module');
297
+ const inspected = await inspectInstalledModule(matches[0], workerWritableRoots, platform);
298
+ if (reference === adapter.runtimeDriver && (inspected.entry.packageVersion !== adapter.adapterVersion
299
+ || !inspected.entry.adapterIds.includes(adapter.id))) throw new Error('Installed driver identity mismatch');
300
+ modules.push({ reference, filename: inspected.filename, key: inspected.key });
301
+ }
302
+ const binary = await binaryIdentity(request.executable, workerWritableRoots);
303
+ const canonicalCwd = await realpath(cwd);
304
+ const canonicalWritableRoots = await Promise.all(workerWritableRoots.map(root => realpath(root)));
305
+ const inputRoots = await Promise.all((request.inputRoots ?? [cwd]).map(root => realpath(root)));
306
+ const outputRoots = await Promise.all((request.outputRoots ?? workerWritableRoots).map(root => realpath(root)));
307
+ if (!Array.isArray(request.runtimeFiles)) throw new Error('Declared runtime dependency closure is required');
308
+ const runtimeFiles = [];
309
+ for (const filename of request.runtimeFiles) {
310
+ if (!path.isAbsolute(filename)) throw new Error('Runtime dependency must be installed');
311
+ const canonical = await realpath(filename);
312
+ for (const root of workerWritableRoots) {
313
+ if (inside(await realpath(root), canonical)) throw new Error('Worker-writable runtime dependency');
314
+ }
315
+ const stat = await lstat(canonical);
316
+ if ((stat.mode & 0o022) || (stat.nlink > 1 && (stat.mode & 0o222))) throw new Error('Untrusted runtime dependency');
317
+ runtimeFiles.push({ filename: canonical, sha256: `sha256:${createHash('sha256').update(await readInstalledFile(canonical)).digest('hex')}` });
318
+ }
319
+ const metadata = structuredClone(adapter);
320
+ for (const entry of Object.values(metadata.invocationProfiles ?? {})) delete entry.installationQualification;
321
+ return { binary, cwd: canonicalCwd, inputRoots, outputRoots, scopeDigest: qualificationDigest({ metadata, profileId, cliVersion, platform, binary, modules, runtimeFiles,
322
+ permissionRequirements, controlArgv, fixtureDigest: await workerNegativeFixtureDigest(),
323
+ requiredControls: [...requiredControls].sort(), effectiveConfigDigest, accountScope, boundary, cwd: canonicalCwd,
324
+ workerWritableRoots: canonicalWritableRoots, inputRoots, outputRoots, environmentDigest: qualificationDigest(environment), endpoints, mcp: request.mcp ?? null,
325
+ mcpServerDigest: request.mcpServerDigest ?? null }) };
326
+ }
327
+
328
+ /** Restricted whole-banner regex: one capture, no branching, assertions,
329
+ * backreferences, wildcards, unbounded repetition or repeated groups. Bounded
330
+ * at most two atom repetitions, each capped at 128; banners are capped at 4096 bytes. */
331
+ export function compileWorkerVersionPattern(pattern) {
332
+ if (typeof pattern !== 'string' || pattern.length > 256 || !pattern.startsWith('^') || !pattern.endsWith('$')) throw new Error('Invalid worker version pattern');
333
+ let captures = 0, depth = 0, repetitions = 0, atom = false;
334
+ for (let i = 1; i < pattern.length - 1; i++) {
335
+ const c = pattern[i];
336
+ if (c === '\\') {
337
+ const next = pattern[++i];
338
+ if (!next || !'dws.-+()[]\\'.includes(next)) throw new Error('Unsafe worker version escape');
339
+ atom = true;
340
+ } else if (c === '[') {
341
+ const start = i;
342
+ while (++i < pattern.length && pattern[i] !== ']') {
343
+ if (pattern[i] === '\\') i++;
344
+ }
345
+ if (i >= pattern.length - 1 || i === start + 1) throw new Error('Invalid worker version class');
346
+ atom = true;
347
+ } else if (c === '(') {
348
+ if (depth || ++captures > 1) throw new Error('Worker version pattern requires one capture');
349
+ depth++; atom = false;
350
+ } else if (c === ')') {
351
+ if (!depth) throw new Error('Invalid worker version capture');
352
+ depth--; atom = false; // groups cannot be repeated
353
+ } else if (c === '{') {
354
+ const match = /^\{([0-9]+),([0-9]+)\}/.exec(pattern.slice(i));
355
+ if (++repetitions > 2 || !atom || !match || +match[1] < 1 || +match[2] < +match[1] || +match[2] > 128) throw new Error('Unsafe worker version repetition');
356
+ i += match[0].length - 1; atom = false;
357
+ } else {
358
+ if ('.*+?|^$}'.includes(c)) throw new Error('Unsafe worker version pattern');
359
+ atom = true;
360
+ }
361
+ }
362
+ if (depth || captures !== 1) throw new Error('Worker version pattern requires one capture');
363
+ return new RegExp(pattern, 'u');
364
+ }
365
+
366
+ export function normalizeWorkerVersionBanner(banner, pattern) {
367
+ if (typeof banner !== 'string' || Buffer.byteLength(banner) > 4096) throw new Error('Worker version banner exceeds bound');
368
+ if (pattern === undefined) return banner;
369
+ const match = compileWorkerVersionPattern(pattern).exec(banner);
370
+ // JS $ also matches before a final newline; require the entire banner.
371
+ if (!match || match[0] !== banner) throw new Error('Worker version banner does not match');
372
+ // Date-style decimal cores (Cursor 2026.10.01) become 2026.10.1;
373
+ // prerelease and build identifiers remain byte-for-byte unchanged.
374
+ const version = match[1].replace(/^([0-9]+)\.([0-9]+)\.([0-9]+)/,
375
+ (_, major, minor, patch) => [major, minor, patch].map(n => String(Number(n))).join('.'));
376
+ if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-|\+|$)/.test(version) || !valid(version)) throw new Error('Worker version capture is not semver');
377
+ return version;
378
+ }
379
+
380
+ // Credential-free environment for read-only `<executable> ...versionArgv`
381
+ // probes. A version call needs process plumbing only (PATH for `env`-shebang
382
+ // interpreters, Windows system roots for process startup) — never provider API
383
+ // keys, account tokens, HOME, or caller session state. This allowlist is
384
+ // intentionally narrower than any adapter `envAllowlist`, which governs worker
385
+ // launches, not version probes.
386
+ export const workerVersionProbeEnvAllowlist = Object.freeze([
387
+ 'PATH', 'PATHEXT', 'SYSTEMROOT', 'SYSTEMDRIVE', 'WINDIR',
388
+ ]);
389
+
390
+ // Build the cleared probe environment from a source mapping (defaults to
391
+ // process.env). Only allowlisted string values survive; everything else,
392
+ // including provider credentials, is dropped. Always returns a fresh object.
393
+ export function workerVersionProbeEnvironment(source = process.env) {
394
+ const environment = {};
395
+ for (const name of workerVersionProbeEnvAllowlist) {
396
+ const value = source?.[name];
397
+ if (typeof value === 'string' && !value.includes('\0')) environment[name] = value;
398
+ }
399
+ return environment;
400
+ }
401
+
402
+ // Default bounds for read-only version probes: 10s deadline, 64 KiB output.
403
+ export const workerVersionProbeDefaults = Object.freeze({ timeoutMs: 10_000, maxBytes: 64 * 1024 });
404
+
405
+ // Shared semver range check for an installed CLI version. Returns the first
406
+ // unsupported range (or the invalid range declaration), else null. Read-only
407
+ // surfaces reuse this instead of re-deriving the inspector's range logic.
408
+ export function workerVersionRangeMismatch(version, ranges) {
409
+ for (const range of ranges) {
410
+ if (typeof range !== 'string' || !validRange(range)) return `Unsupported version range '${range}'`;
411
+ if (!satisfies(version, range)) return range;
412
+ }
413
+ return null;
414
+ }
415
+
416
+ export function boundedVersionProbe(executable, argv, environment, cwd, timeoutMs, maxBytes, versionPattern) {
417
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 10_000
418
+ || !Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > 5 * 1024 * 1024
419
+ || !Array.isArray(argv) || argv.some(arg => typeof arg !== 'string' || arg.includes('\0'))) {
420
+ throw new Error('Invalid probe bounds');
421
+ }
422
+ return new Promise((resolve, reject) => {
423
+ const child = spawn(executable, argv, { cwd, env: environment, stdio: 'pipe', detached: true, shell: false });
424
+ let output = Buffer.alloc(0);
425
+ let size = 0;
426
+ let failed = false;
427
+ const stop = () => {
428
+ if (child.pid) { try { process.kill(-child.pid, 'SIGKILL'); } catch { /* already stopped */ } }
429
+ };
430
+ // Never rely on close: an escaped descendant may retain the pipe descriptors.
431
+ const abort = () => {
432
+ failed = true; stop();
433
+ child.stdout.destroy(); child.stderr.destroy(); child.stdin.destroy(); child.unref();
434
+ reject(new Error('Bounded version probe failed'));
435
+ };
436
+ const timer = setTimeout(abort, timeoutMs);
437
+ const consume = (chunk, stdout) => {
438
+ size += chunk.length;
439
+ if (size > maxBytes) { clearTimeout(timer); abort(); }
440
+ else if (stdout) output = Buffer.concat([output, chunk]);
441
+ };
442
+ child.stdout.on('data', chunk => consume(chunk, true));
443
+ child.stderr.on('data', chunk => consume(chunk, false));
444
+ child.stdin.on('error', () => {});
445
+ child.stdin.end();
446
+ child.once('error', cause => {
447
+ clearTimeout(timer); stop();
448
+ const error = new Error('Version probe unavailable');
449
+ // Preserve the spawn cause (notably ENOENT for a missing CLI) so
450
+ // callers can report "not installed" without re-deriving it.
451
+ if (cause?.code !== undefined) error.code = cause.code;
452
+ reject(error);
453
+ });
454
+ child.once('exit', stop);
455
+ child.once('close', code => {
456
+ clearTimeout(timer); stop();
457
+ if (failed || code !== 0) reject(new Error('Bounded version probe failed'));
458
+ else {
459
+ try {
460
+ const rawBanner = new TextDecoder('utf-8', { fatal: true }).decode(output);
461
+ resolve({ rawBanner, version: normalizeWorkerVersionBanner(rawBanner.trim(), versionPattern) });
462
+ }
463
+ catch (error) { reject(error); }
464
+ }
465
+ });
466
+ });
467
+ }
468
+
469
+ function inside(root, target) {
470
+ const relative = path.relative(root, target);
471
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
472
+ }
473
+
474
+ // Inspect syntax without executing module bodies (including top-level effects).
475
+ // Only statically declared interfaceVersion and callable register exports qualify.
476
+ function checkInstalledExports(source) {
477
+ const ast = parse(source, { ecmaVersion: 'latest', sourceType: 'module' });
478
+ const declarations = new Map();
479
+ const exports = new Map();
480
+ for (const node of ast.body) {
481
+ const declaration = node.type === 'ExportNamedDeclaration' ? node.declaration : node;
482
+ if (declaration?.type === 'FunctionDeclaration' && declaration.id) declarations.set(declaration.id.name, declaration);
483
+ if (declaration?.type === 'VariableDeclaration' && declaration.kind === 'const') {
484
+ for (const item of declaration.declarations) if (item.id.type === 'Identifier') declarations.set(item.id.name, item.init);
485
+ }
486
+ if (node.type === 'ExportNamedDeclaration' && !node.source) {
487
+ if (declaration?.type === 'FunctionDeclaration') exports.set(declaration.id.name, declaration.id.name);
488
+ if (declaration?.type === 'VariableDeclaration') {
489
+ for (const item of declaration.declarations) if (item.id.type === 'Identifier') exports.set(item.id.name, item.id.name);
490
+ }
491
+ for (const item of node.specifiers) exports.set(item.exported.name, item.local.name);
492
+ }
493
+ }
494
+ const version = declarations.get(exports.get('interfaceVersion'));
495
+ const register = declarations.get(exports.get('register'));
496
+ if (version?.type !== 'Literal' || version.value !== 1
497
+ || !['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(register?.type)
498
+ || register.generator) throw new Error('Installed worker module requires interfaceVersion=1 and register(factory) exports');
499
+ const interfaceBindings = new Set([exports.get('interfaceVersion'), exports.get('register')]);
500
+ function writesInterface(node) {
501
+ if (!node || typeof node !== 'object') return false;
502
+ if (node.type === 'Identifier' && interfaceBindings.has(node.name)) return true;
503
+ // Member mutation does not replace the exported binding.
504
+ if (node.type === 'MemberExpression') return false;
505
+ return Object.values(node).some(value => Array.isArray(value) ? value.some(writesInterface) : writesInterface(value));
506
+ }
507
+ function inspect(node) {
508
+ if (!node || typeof node !== 'object') return;
509
+ // Reject visible binding writes and direct eval calls only. This is not a
510
+ // general dynamic-code detector; execution still trusts installation authority.
511
+ if ((node.type === 'AssignmentExpression' && writesInterface(node.left))
512
+ || (node.type === 'UpdateExpression' && writesInterface(node.argument))
513
+ || (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'eval')) {
514
+ throw new Error('Installed worker module exports must remain statically bound');
515
+ }
516
+ for (const value of Object.values(node)) {
517
+ if (Array.isArray(value)) value.forEach(inspect);
518
+ else if (value && typeof value === 'object') inspect(value);
519
+ }
520
+ }
521
+ inspect(ast);
522
+ }
523
+
524
+ async function readInstalledFile(filename) {
525
+ const file = await open(filename, 'r');
526
+ try {
527
+ const stat = await file.stat();
528
+ if (!stat.isFile()) throw new Error('Installed module inputs must be regular files');
529
+ if (stat.size > installedModuleMaxBytes) throw new Error('Installed module input exceeds maxBytes (5 MiB)');
530
+ // Bound the actual read too: an fstat size check alone allows concurrent growth.
531
+ const bytes = Buffer.alloc(installedModuleMaxBytes + 1);
532
+ let length = 0;
533
+ while (length < bytes.length) {
534
+ const { bytesRead } = await file.read(bytes, length, bytes.length - length, null);
535
+ if (!bytesRead) break;
536
+ length += bytesRead;
537
+ }
538
+ if (length > installedModuleMaxBytes) throw new Error('Installed module input exceeds maxBytes (5 MiB)');
539
+ return bytes.subarray(0, length);
540
+ } finally {
541
+ await file.close();
542
+ }
543
+ }
544
+
545
+ async function inspectInstalledModule(entry, workerWritableRoots, platform) {
546
+ if (!entry || !/^installed:[a-z][a-z0-9-]{0,62}$/.test(entry.reference)
547
+ || typeof entry.packageName !== 'string' || !entry.packageName
548
+ || typeof entry.packageVersion !== 'string' || !entry.packageVersion
549
+ || entry.interfaceVersion !== 1 || !/^sha256:[a-f0-9]{64}$/.test(entry.sha256)
550
+ || !Array.isArray(entry.adapterIds) || new Set(entry.adapterIds).size !== entry.adapterIds.length
551
+ || !entry.adapterIds.every(id => /^[a-z][a-z0-9-]{0,62}$/.test(id))) {
552
+ throw new Error('Invalid trusted installed module binding');
553
+ }
554
+ if (!Array.isArray(entry.platforms) || !entry.platforms.includes(platform)) throw new Error(`Unsupported worker module platform: ${platform}`);
555
+ if (!path.isAbsolute(entry.packageRoot) || !entry.modulePath || path.isAbsolute(entry.modulePath)
556
+ || entry.modulePath.includes('\\') || entry.modulePath.split('/').some(part => !part || part === '..' || part === '.')
557
+ || !/\.(mjs|js)$/.test(entry.modulePath)) throw new Error('Invalid installed module path');
558
+ const root = await realpath(entry.packageRoot);
559
+ const filename = await realpath(path.join(root, entry.modulePath));
560
+ if (!inside(root, filename)) throw new Error('Installed module path escapes package');
561
+ for (const writable of workerWritableRoots) {
562
+ const canonical = await realpath(writable);
563
+ if (inside(canonical, filename) || inside(canonical, root)) throw new Error('Installed module is in a worker-writable path');
564
+ }
565
+ for (const target of [root, path.join(root, 'package.json'), ...entry.modulePath.split('/').map((_, i, parts) => path.join(root, ...parts.slice(0, i + 1)))]) {
566
+ const stat = await lstat(target);
567
+ if (stat.isSymbolicLink() || (stat.mode & 0o022) !== 0) throw new Error('Symlink or untrusted writable installed module path');
568
+ // Directory link counts reflect children, not file aliases. Hardlinked store
569
+ // files qualify only when no permission class can write the shared inode.
570
+ if (stat.isFile() && stat.nlink > 1 && (stat.mode & 0o222) !== 0) {
571
+ throw new Error('Writable hardlink in installed module path');
572
+ }
573
+ }
574
+ const pkg = JSON.parse((await readInstalledFile(path.join(root, 'package.json'))).toString('utf8'));
575
+ if (pkg.name !== entry.packageName || pkg.version !== entry.packageVersion) throw new Error('Installed module package identity/version mismatch');
576
+ if (entry.modulePath.endsWith('.js') && pkg.type !== 'module') throw new Error('Installed worker module must be ESM');
577
+ const source = await readInstalledFile(filename);
578
+ if (`sha256:${createHash('sha256').update(source).digest('hex')}` !== entry.sha256) throw new Error('Installed module digest mismatch');
579
+ checkInstalledExports(source.toString('utf8'));
580
+ return { entry, filename, key: `${filename}:${entry.packageVersion}:${entry.sha256}` };
581
+ }
582
+
583
+ /**
584
+ * Trusted host/executor registration boundary, never project configuration.
585
+ * `modules` is the installation authority's allowlist, with realpath-resolved packageRoot,
586
+ * packageName/packageVersion, relative modulePath, sha256, interfaceVersion:1,
587
+ * adapterIds and qualified platforms. workerWritableRoots describes the worker's
588
+ * writable mounts. The host must keep install roots and their dependency closure
589
+ * read-only throughout loading/execution, including against concurrent installers
590
+ * and writable aliases. Path checks and rechecks do not close the import race;
591
+ * this loader is not a sandbox.
592
+ * No module locations are resolved from metadata, URLs, or runtime downloads.
593
+ */
594
+ export function loadInstalledWorkerAdapters({ registry, modules, workerWritableRoots, platform = process.platform }) {
595
+ // Snapshot caller-owned bindings before asynchronous work; serialize registration.
596
+ const request = structuredClone({ registry, modules, workerWritableRoots, platform });
597
+ const pending = installedLoad.then(() => loadQualifiedModules(request));
598
+ installedLoad = pending.catch(() => {});
599
+ return pending;
600
+ }
601
+
602
+ async function loadQualifiedModules({ registry, modules, workerWritableRoots, platform }) {
603
+ const { parseProviderModelCapabilities } = await import('./provider-policy.js');
604
+ const parsed = parseProviderModelCapabilities(registry);
605
+ if (!Array.isArray(modules) || !Array.isArray(workerWritableRoots)) throw new Error('Trusted module bindings and worker writable roots are required');
606
+ const allowlist = new Map();
607
+ for (const entry of modules) {
608
+ if (allowlist.has(entry.reference)) throw new Error(`Duplicate installed module reference: ${entry.reference}`);
609
+ allowlist.set(entry.reference, entry);
610
+ }
611
+ const required = new Map();
612
+ const adapters = [...Object.values(parsed.providers).flatMap(p => p.workerAdapter ? [p.workerAdapter] : []), ...Object.values(parsed.workerAdapters ?? {})];
613
+ // An id is "canonical" when the trusted registry itself pins a provider's
614
+ // own `workerAdapter` to that same id (e.g. `providers.codex.workerAdapter.id
615
+ // === 'codex'`). Such an id may already carry a legacy builtin
616
+ // self-registration (same file, loaded a second way); the registry-pinned,
617
+ // digest-authenticated install below is then the upgrade path, not a
618
+ // hijack, so a prior builtin registration alone must not block it. A
619
+ // non-canonical adapter (e.g. an ad-hoc `workerAdapters.*` entry) claiming
620
+ // an id that is already builtin-registered stays a hard conflict.
621
+ const canonicalIds = new Set(Object.entries(parsed.providers)
622
+ .filter(([name, p]) => p.workerAdapter && p.workerAdapter.id === name).map(([name]) => name));
623
+ for (const adapter of adapters) {
624
+ for (const reference of [adapter.runtimeDriver, ...(adapter.catalogAdapter.kind === 'module' ? [adapter.catalogAdapter.locator] : [])]) {
625
+ const entry = allowlist.get(reference);
626
+ if (!entry) throw new Error(`Worker module is not installed/approved: ${reference}`);
627
+ if (reference === adapter.runtimeDriver && (entry.packageVersion !== adapter.adapterVersion || !entry.adapterIds?.includes(adapter.id))) {
628
+ throw new Error('Worker adapter identity/version does not match trusted module');
629
+ }
630
+ required.set(reference, entry);
631
+ }
632
+ }
633
+ // Validate every module, digest, package, export and duplicate before ANY import.
634
+ const inspected = [];
635
+ const ids = new Set();
636
+ for (const entry of required.values()) {
637
+ const inspectedModule = await inspectInstalledModule(entry, workerWritableRoots, platform);
638
+ for (const id of entry.adapterIds) {
639
+ const builtinConflict = canonicalIds.has(id) ? false : providerRegistry.has(id);
640
+ if (ids.has(id) || ((builtinConflict || installedModules.has(inspectedModule.key))
641
+ && installedModules.get(id) !== inspectedModule.key)) throw new Error(`Duplicate worker adapter ID: ${id}`);
642
+ ids.add(id);
643
+ }
644
+ inspected.push(inspectedModule);
645
+ }
646
+ const staged = new Map();
647
+ for (const { entry, filename, key } of inspected) {
648
+ if (installedModules.has(key)) continue;
649
+ // Recheck immediately before import. ESM opens by path afterward, so a
650
+ // residual race remains; the host must enforce read-only install roots.
651
+ const rechecked = await inspectInstalledModule(entry, workerWritableRoots, platform);
652
+ if (rechecked.key !== key) throw new Error('Installed module canonical identity changed');
653
+ const module = await import(`${pathToFileURL(filename).href}?sha256=${entry.sha256.slice(7)}`);
654
+ if (module.interfaceVersion !== 1 || typeof module.register !== 'function') throw new Error('Installed worker module export mismatch');
655
+ const registered = new Set();
656
+ await module.register((id, factory) => {
657
+ if (!entry.adapterIds.includes(id) || registered.has(id) || staged.has(id) || typeof factory !== 'function') throw new Error(`Invalid or duplicate installed factory: ${id}`);
658
+ registered.add(id);
659
+ staged.set(id, { factory, key });
660
+ });
661
+ if (entry.adapterIds.some(id => !registered.has(id))) throw new Error('Installed module omitted an approved factory');
662
+ }
663
+ for (const [id, { factory, key }] of staged) {
664
+ registerProvider(id, factory);
665
+ installedModules.set(id, key);
666
+ }
667
+ for (const { key } of inspected) installedModules.set(key, true);
668
+ return adapters.map(adapter => adapter.id);
669
+ }
670
+
671
+ /**
672
+ * Register a provider adapter factory.
673
+ *
674
+ * @param {string} name - Provider name (e.g., 'claude', 'codex')
675
+ * @param {() => ProviderAdapter} factory - Factory function
676
+ */
677
+ export function registerProvider(name, factory) {
678
+ providerRegistry.set(name.toLowerCase(), factory);
679
+ }
680
+
681
+ /**
682
+ * Create a provider adapter by name.
683
+ *
684
+ * @param {string} name - Provider name (e.g., 'claude', 'codex')
685
+ * @returns {ProviderAdapter}
686
+ * @throws {Error} If provider is not registered
687
+ */
688
+ export function createProvider(name) {
689
+ const factory = providerRegistry.get(name.toLowerCase());
690
+ if (!factory) {
691
+ const available = Array.from(providerRegistry.keys()).join(', ');
692
+ throw new Error(
693
+ `Unknown provider: "${name}". Available providers: ${available}`
694
+ );
695
+ }
696
+ return factory();
697
+ }
698
+
699
+ /**
700
+ * List all registered provider names.
701
+ * @returns {string[]}
702
+ */
703
+ export function listProviders() {
704
+ return Array.from(providerRegistry.keys());
705
+ }
706
+
707
+ /**
708
+ * Check if a provider is registered.
709
+ * @param {string} name
710
+ * @returns {boolean}
711
+ */
712
+ export function hasProvider(name) {
713
+ return providerRegistry.has(name.toLowerCase());
714
+ }
715
+
716
+
717
+ // This host-owned record extends the installation owner. It is not project
718
+ // configuration and never accepts module locations from models.json/aiwg.config.
719
+ function installationStorePath() {
720
+ const root = process.env.AIWG_CONFIG || path.join(homedir(), '.config/aiwg');
721
+ if (!path.isAbsolute(root)) throw new Error('Installation store must be absolute');
722
+ return path.join(root, 'worker-installations.json');
723
+ }
724
+
725
+ async function assertTrustedStore(filename, writableRoots) {
726
+ const canonical = await canonicalFuturePath(filename);
727
+ if (canonical !== filename) throw new Error('Symlinked installation store');
728
+ for (const root of writableRoots) {
729
+ if (inside(await realpath(root), canonical)) throw new Error('Worker-writable installation store');
730
+ }
731
+ for (const target of [path.dirname(filename), filename]) {
732
+ try {
733
+ const stat = await lstat(target);
734
+ if (stat.isSymbolicLink() || (stat.mode & 0o022) || stat.isFile() && stat.nlink !== 1
735
+ || process.getuid && stat.uid !== process.getuid()) throw new Error('Untrusted installation store permissions');
736
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
737
+ }
738
+ }
739
+
740
+ async function readInstallationStore(filename, expectedDigest) {
741
+ try {
742
+ const bytes = await readInstalledFile(filename);
743
+ const digest = `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
744
+ if (expectedDigest !== undefined && digest !== expectedDigest) throw new Error('Installation store binding changed');
745
+ const stored = JSON.parse(bytes.toString('utf8'));
746
+ if (stored.version !== 1 || !Array.isArray(stored.registrations) || stored.registrations.length > 128) {
747
+ throw new Error('Unsupported installation record');
748
+ }
749
+ return { stored, digest };
750
+ } catch (error) {
751
+ if (error.code === 'ENOENT' && expectedDigest === undefined) return { stored: { version: 1, registrations: [] }, digest: null };
752
+ throw error;
753
+ }
754
+ }
755
+
756
+ /** Explicit host operation: successful bounded qualification is required before
757
+ * recording; credentials are reduced to names and are never written to disk. */
758
+ export async function recordWorkerInstallation(instance, options) {
759
+ const { cli, binding } = options;
760
+ if (!/^[a-z][a-z0-9-]{0,62}$/.test(cli)) throw new Error('Invalid installation ID');
761
+ const installation = structuredClone(options.installation);
762
+ const result = await qualifyWorkerInstallation(instance, { ...installation, now: Date.now() });
763
+ if (result.status !== 'ENABLED') return result;
764
+ const adapter = structuredClone(installation.adapter);
765
+ adapter.invocationProfiles[installation.profile].installationQualification = {
766
+ scopeDigest: result.scopeDigest, fixtureDigest: result.fixtureDigest,
767
+ verifiedAt: result.verifiedAt, evidenceRef: result.evidenceRef,
768
+ };
769
+ if (binding.profile !== installation.profile || binding.cliVersion !== installation.cliVersion
770
+ || binding.accountScope !== installation.accountScope) throw new Error('Installation binding differs from qualification');
771
+ const filename = installationStorePath();
772
+ await assertTrustedStore(filename, installation.workerWritableRoots);
773
+ const { stored } = await readInstallationStore(filename);
774
+ const environmentNames = Object.keys(installation.environment ?? {});
775
+ delete installation.environment;
776
+ delete installation.adapter;
777
+ delete installation.now;
778
+ const record = { cli, binding: { ...structuredClone(binding), boundary: installation.boundary }, adapter,
779
+ installation: { ...installation, environmentNames, versionProbe: { rawBanner: result.rawVersionBanner, version: result.cliVersion } },
780
+ ...(options.mcpSchemas ? { mcpSchemas: structuredClone(options.mcpSchemas) } : {}),
781
+ ...(options.mcpProfile ? { mcpProfile: structuredClone(options.mcpProfile) } : {}) };
782
+ stored.registrations = stored.registrations.filter(entry => entry.cli !== cli);
783
+ stored.registrations.push(record);
784
+ await mkdir(path.dirname(filename), { recursive: true, mode: 0o700 });
785
+ await assertTrustedStore(filename, installation.workerWritableRoots);
786
+ const temporary = `${filename}.${process.pid}.${createHash('sha256').update(String(Math.random())).digest('hex')}.tmp`;
787
+ await writeFile(temporary, `${JSON.stringify(stored, null, 2)}\n`, { mode: 0o600, flag: 'wx' });
788
+ await rename(temporary, filename);
789
+ return result;
790
+ }
791
+
792
+ /** Read recorded host authority, validate installation bytes, then load the same
793
+ * approved factory used by Ralph. Resolution never launches a version probe. */
794
+ export async function loadRecordedWorkerInstallations({ registry, cwd, policy, storePath, storeDigest }) {
795
+ const { WorkerPolicyError, parseProviderModelCapabilities } = await import('./provider-policy.js');
796
+ const { qualificationDigest } = await import('./qualification-digest.mjs');
797
+ const fail = () => new WorkerPolicyError('OG_ADAPTER_UNQUALIFIED', 'Trusted installation qualification is required');
798
+ const filename = storePath ?? installationStorePath();
799
+ let stored, digest;
800
+ try {
801
+ if (!path.isAbsolute(filename) || (storePath !== undefined && !/^sha256:[a-f0-9]{64}$/.test(storeDigest ?? ''))) throw fail();
802
+ await assertTrustedStore(filename, [cwd]);
803
+ ({ stored, digest } = await readInstallationStore(filename, storeDigest));
804
+ }
805
+ catch { throw fail(); }
806
+ const installations = {};
807
+ const drivers = {};
808
+ const resolvedRegistry = structuredClone(registry);
809
+ const counts = new Map();
810
+ for (const record of stored.registrations) counts.set(record?.cli, (counts.get(record?.cli) ?? 0) + 1);
811
+ for (const record of stored.registrations) {
812
+ try {
813
+ const { cli, binding, adapter } = record;
814
+ if (!/^[a-z][a-z0-9-]{0,62}$/.test(cli) || counts.get(cli) !== 1) throw fail();
815
+ const installation = { ...record.installation, adapter, now: Date.now(), environment: {} };
816
+ if (record.installation.environment && Object.keys(record.installation.environment).length) throw fail();
817
+ for (const name of installation.environmentNames ?? []) {
818
+ if (typeof name !== 'string' || process.env[name] === undefined) throw fail();
819
+ installation.environment[name] = process.env[name];
820
+ }
821
+ if (binding.profile !== installation.profile || binding.cliVersion !== installation.cliVersion
822
+ || binding.accountScope !== installation.accountScope
823
+ || qualificationDigest(binding.boundary) !== qualificationDigest(installation.boundary)
824
+ || qualificationDigest(policy) !== installation.effectiveConfigDigest) throw fail();
825
+ const proof = adapter.invocationProfiles?.[binding.profile]?.installationQualification;
826
+ if (!proof || ['scopeDigest', 'fixtureDigest', 'verifiedAt', 'evidenceRef'].some(key => proof[key] !== installation.evidence?.[key])) throw fail();
827
+ // Reject expired/unbound negative observations before any factory import.
828
+ const { requiredWorkerControls } = await import('./provider-policy.js');
829
+ installation.requiredControls = requiredWorkerControls(installation);
830
+ await assertWorkerInstallationEvidence(installation, proof.scopeDigest);
831
+ await assertTrustedStore(filename, installation.workerWritableRoots);
832
+ const candidateRegistry = structuredClone(resolvedRegistry);
833
+ if (candidateRegistry.providers[adapter.id]) candidateRegistry.providers[adapter.id].workerAdapter = adapter;
834
+ else { candidateRegistry.workerAdapters ??= {}; candidateRegistry.workerAdapters[adapter.id] = adapter; }
835
+ parseProviderModelCapabilities(candidateRegistry);
836
+ // The loader validates package identity, bytes and static exports BEFORE import.
837
+ // Only this installation's metadata is needed; unrelated absent modules do not block it.
838
+ const loadingRegistry = structuredClone(registry);
839
+ for (const provider of Object.values(loadingRegistry.providers)) delete provider.workerAdapter;
840
+ loadingRegistry.workerAdapters = { [adapter.id]: adapter };
841
+ await loadInstalledWorkerAdapters({ registry: loadingRegistry, modules: installation.modules,
842
+ workerWritableRoots: installation.workerWritableRoots, platform: process.platform });
843
+ const instance = createProvider(adapter.id);
844
+ const { scope } = await recheckWorkerInstallation(instance, installation);
845
+ if (scope.scopeDigest !== proof.scopeDigest) throw fail();
846
+ const profile = adapter.invocationProfiles[binding.profile];
847
+ let mcpRegistry;
848
+ if (profile.transport === 'mcp') {
849
+ const { McpServerRegistry } = await import('../mcp/registry.js');
850
+ mcpRegistry = new McpServerRegistry();
851
+ if (typeof instance.buildInvocation !== 'function' || !record.mcpProfile || !record.mcpSchemas) throw fail();
852
+ }
853
+ drivers[cli] = {
854
+ extractFinal: output => instance.parseOutput(output),
855
+ ...(typeof instance.parseWorkerFinal === 'function'
856
+ ? { parseWorkerFinal: (output, options) => instance.parseWorkerFinal(output, options) } : {}),
857
+ ...(typeof instance.createHealthObserver === 'function'
858
+ ? { createHealthObserver: options => instance.createHealthObserver(options) } : {}),
859
+ qualification: { adapter, adapterId: adapter.id, cliVersion: binding.cliVersion, boundary: binding.boundary,
860
+ platform: process.platform, installation, stdinFramerBinding: { storePath: filename, storeDigest: digest },
861
+ ...(profile.transport === 'mcp' ? { mcp: binding.mcp, mcpSchemas: record.mcpSchemas } : {}) },
862
+ ...(typeof instance.buildWorkerStdinFrame === 'function'
863
+ ? { buildWorkerStdinFrame: brief => instance.buildWorkerStdinFrame(brief) } : {}),
864
+ buildInvocation: async input => profile.transport === 'shell' ? {
865
+ argv: [scope.binary.realpath, ...instance.buildWorkerArgs(input, profile, input.permissions)],
866
+ stdin: adapter.invocation.stdin, envNames: Object.keys(installation.environment),
867
+ ...(typeof instance.buildWorkerArtifacts === 'function'
868
+ ? { artifacts: instance.buildWorkerArtifacts(input) } : {}),
869
+ } : instance.buildInvocation(input),
870
+ prepareWorkerInvocation: (invocation, qualification) => prepareInstalledWorkerInvocation(instance, invocation, qualification),
871
+ ...(mcpRegistry ? { mcpRegistry, mcpProfile: record.mcpProfile } : {}),
872
+ };
873
+ installations[cli] = structuredClone(binding);
874
+ Object.assign(resolvedRegistry, candidateRegistry);
875
+ } catch { /* One missing/changed installation cannot grant authority or hide qualified peers. */ }
876
+ }
877
+ if (!Object.keys(installations).length) throw fail();
878
+ return { registry: resolvedRegistry, installations, drivers };
879
+ }