@aiwg/cli 2026.10.1 → 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 (166) 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 +210 -4
  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 +75 -27
  38. package/dist/src/decision/driver.js +3 -1
  39. package/dist/src/decision/entry.js +38 -6
  40. package/dist/src/decision/heldout/collector.js +10 -1
  41. package/dist/src/decision/index.js +1 -0
  42. package/dist/src/decision/interop.js +60 -8
  43. package/dist/src/decision/ledger.js +839 -0
  44. package/dist/src/decision/validate.js +6 -3
  45. package/dist/src/effects/dispatch-verifier.js +252 -0
  46. package/dist/src/effects/index.js +1 -0
  47. package/dist/src/features/installer.js +2 -2
  48. package/dist/src/flow/graph-metadata.js +5 -0
  49. package/dist/src/gates/providers/loader.js +3 -1
  50. package/dist/src/installation/manager-command.mjs +1 -1
  51. package/dist/src/installation/manager.mjs +17 -2
  52. package/dist/src/lint/runner.js +2 -2
  53. package/dist/src/mcp/adapters/codex-runtime.js +360 -56
  54. package/dist/src/mcp/cli.mjs +587 -77
  55. package/dist/src/mcp/config-file.mjs +279 -0
  56. package/dist/src/mcp/config-layers.mjs +348 -0
  57. package/dist/src/mcp/credentials.mjs +484 -0
  58. package/dist/src/mcp/grok-build-config.mjs +16 -26
  59. package/dist/src/mcp/helpers.mjs +106 -2
  60. package/dist/src/mcp/omp-config.mjs +50 -29
  61. package/dist/src/mcp/profiles.js +97 -23
  62. package/dist/src/mcp/profiles.mjs +99 -27
  63. package/dist/src/mcp/registry.js +264 -57
  64. package/dist/src/mcp/registry.mjs +198 -45
  65. package/dist/src/mcp/server.mjs +403 -20
  66. package/dist/src/mcp/toml-string.mjs +9 -0
  67. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  68. package/dist/src/mcp/tool-filters.mjs +357 -0
  69. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  70. package/dist/src/mcp/tools/decision.mjs +21 -3
  71. package/dist/src/mcp/tools/orchestration.mjs +38 -0
  72. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  73. package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
  74. package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
  75. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  76. package/dist/src/memory/project-registry.js +2 -2
  77. package/dist/src/mission-protocol/codecs.js +639 -1
  78. package/dist/src/mission-protocol/types.js +110 -0
  79. package/dist/src/mission-protocol/worker-status.js +2 -0
  80. package/dist/src/models/config-loader.js +70 -13
  81. package/dist/src/models/grok-build-models.js +93 -41
  82. package/dist/src/models/model-capabilities.v1.json +3091 -24
  83. package/dist/src/models/model-discovery.js +862 -63
  84. package/dist/src/models/provider-policy.js +625 -11
  85. package/dist/src/models/qualification-digest.mjs +17 -0
  86. package/dist/src/models/resolver.js +259 -0
  87. package/dist/src/models/worker-direct-readiness.js +529 -0
  88. package/dist/src/models/worker-dispatch-offer.js +169 -0
  89. package/dist/src/models/worker-health-readiness.js +131 -0
  90. package/dist/src/models/worker-installation.mjs +879 -0
  91. package/dist/src/models/wrapper-route.js +18 -0
  92. package/dist/src/ops/registry.js +9 -14
  93. package/dist/src/plugins/standalone-packager.js +4 -3
  94. package/dist/src/process/bounded-child-process.mjs +387 -0
  95. package/dist/src/process/cli-command.mjs +75 -0
  96. package/dist/src/providers/capability-matrix.js +8 -0
  97. package/dist/src/providers/capability-matrix.yaml +270 -0
  98. package/dist/src/providers/grok-build-headless.js +43 -21
  99. package/dist/src/providers/provider-definitions.js +9 -1
  100. package/dist/src/providers/provider-definitions.mjs +9 -2
  101. package/dist/src/serve/executor-registry.js +16 -24
  102. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  103. package/dist/src/serve/mission-conductor.js +9 -0
  104. package/dist/src/serve/shared-host-scheduler.js +2 -0
  105. package/dist/src/serve/worker-invocation.js +538 -0
  106. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  107. package/dist/src/sessions/adapter-registry.js +45 -0
  108. package/dist/src/sessions/adapters/generic.js +37 -3
  109. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  110. package/dist/src/sessions/adapters/grokbot.js +423 -24
  111. package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
  112. package/dist/src/sessions/adapters/muse.js +68 -29
  113. package/dist/src/sessions/adapters/pi.js +1 -0
  114. package/dist/src/sessions/batch-contracts.js +13 -1
  115. package/dist/src/sessions/batch-import.js +23 -42
  116. package/dist/src/sessions/candidates.js +47 -11
  117. package/dist/src/sessions/contracts.js +35 -0
  118. package/dist/src/sessions/coverage-preflight.js +134 -0
  119. package/dist/src/sessions/discovery.js +3 -0
  120. package/dist/src/sessions/grok-build-cli.js +67 -5
  121. package/dist/src/sessions/importer.js +24 -8
  122. package/dist/src/sessions/index.js +2 -0
  123. package/dist/src/sessions/repository.js +54 -13
  124. package/dist/src/sessions/workspace-discovery.js +99 -37
  125. package/dist/src/skills/adapters/clawhub.js +10 -8
  126. package/dist/src/skills/adapters/openclaw.js +10 -7
  127. package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
  128. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  129. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  130. package/dist/src/smiths/mcpsmith/example.js +1 -1
  131. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
  132. package/dist/src/storage/backends/logseq.js +68 -7
  133. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  134. package/dist/src/testing/generators/test-code-generator.js +57 -30
  135. package/dist/src/uhp/client.js +11 -1
  136. package/dist/src/uhp/mission.js +5 -0
  137. package/dist/src/update/checker.mjs +5 -5
  138. package/dist/src/update/notifier.mjs +54 -2
  139. package/dist/src/writing/fidelity.js +18 -1
  140. package/package.json +18 -1
  141. package/schemas/mission-v1.schema.json +2429 -0
  142. package/schemas/models/model-catalog.v1.schema.json +301 -0
  143. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  144. package/schemas/models/model-policy.v1.schema.json +14 -0
  145. package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
  146. package/tools/_resolve-impl.mjs +39 -0
  147. package/tools/agents/deploy-agents.mjs +15 -2
  148. package/tools/agents/providers/base.mjs +49 -32
  149. package/tools/agents/providers/grokbot.mjs +11 -12
  150. package/tools/agents/providers/hermes.mjs +6 -3
  151. package/tools/agents/providers/omp.mjs +5 -2
  152. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  153. package/tools/providers/deepseek-harness-transport.mjs +358 -0
  154. package/tools/providers/omp-transport.mjs +192 -0
  155. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  156. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  157. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  158. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  159. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  160. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  161. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  162. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  163. package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
  164. package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
  165. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  166. package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
@@ -0,0 +1,690 @@
1
+ /**
2
+ * Provider Adapter Base Class for External Ralph Loop
3
+ *
4
+ * Defines the interface that all CLI provider adapters must implement.
5
+ * Each adapter wraps a specific agentic coding CLI (claude, codex, etc.)
6
+ * and handles binary resolution, argument construction, model mapping,
7
+ * and output parsing.
8
+ *
9
+ * @implements Plan: Multi-Provider Support for External Ralph Loop
10
+ */
11
+
12
+ import { spawn } from 'child_process';
13
+ import { importImpl } from '../../_resolve-impl.mjs';
14
+ import { createWorkerHealthObserver, observeProviderHealthEvent } from './worker-health.mjs';
15
+
16
+ /**
17
+ * Thrown by {@link parseStrictWorkerJson} with a stable `code` so callers can
18
+ * map a scan failure to a specific contract-violation message without
19
+ * string-matching free-text.
20
+ */
21
+ class StrictJsonScanError extends Error {
22
+ constructor(code, message) {
23
+ super(message);
24
+ this.code = code;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * RFC 8259 JSON parser used on every strict worker-final path (JSONL lines,
30
+ * `format: 'json'`, and the MCP `content[0].text`/top-level envelope) instead
31
+ * of `JSON.parse`. `JSON.parse` silently keeps the LAST value when an object
32
+ * repeats a member name — RFC 8259 §4 explicitly leaves this to the
33
+ * implementation, and V8 picks "last wins". That lets a forged worker final
34
+ * smuggle a second, hidden value past a first one: a trailing duplicate
35
+ * `"status":"completed"` can overwrite an earlier `"status":"failed"`, or a
36
+ * trailing duplicate `"type":"result"` can collapse a `"type":"message"`
37
+ * framing envelope into a bogus result in one line (#2890 BLOCKER 1+2).
38
+ *
39
+ * This scanner tracks member names per object independently at every
40
+ * nesting level (a sibling object may reuse a name the scanner already saw
41
+ * in its parent or an earlier sibling) and rejects any object that repeats
42
+ * one. Names are compared only after the scanner has fully decoded them —
43
+ * resolving `\"`, other single-character escapes, and `\uXXXX` escapes
44
+ * (including surrogate pairs, since JS string concatenation of two decoded
45
+ * UTF-16 code units forms the pair automatically) — so an escape-obfuscated
46
+ * collision (`"status"` vs `"status"`) is caught exactly like a literal
47
+ * one. A non-colliding unknown key, escaped or not, is left for the existing
48
+ * `additionalProperties: false` schema check to reject, unchanged.
49
+ *
50
+ * Nesting depth is bounded during the scan (not only afterward, by
51
+ * `boundedJson()`'s post-parse walk) so pathological depth fails fast with a
52
+ * clear `DEPTH` code instead of relying on a recursive-descent stack
53
+ * overflow to be caught as a generic syntax error.
54
+ *
55
+ * @param {string} text - Decoded JSON text (already UTF-8 validated by the caller)
56
+ * @param {number} maxDepth - Same bound as `WORKER_MAX_NESTING_DEPTH`; root value is depth 1
57
+ * @returns {unknown}
58
+ */
59
+ function parseStrictWorkerJson(text, maxDepth) {
60
+ const length = text.length;
61
+ let index = 0;
62
+
63
+ function fail(code, message) {
64
+ throw new StrictJsonScanError(code, `${message} (offset ${index})`);
65
+ }
66
+
67
+ function skipWhitespace() {
68
+ while (index < length) {
69
+ const code = text.charCodeAt(index);
70
+ if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) index += 1;
71
+ else break;
72
+ }
73
+ }
74
+
75
+ function parseValue(depth) {
76
+ skipWhitespace();
77
+ if (index >= length) fail('SYNTAX', 'Unexpected end of JSON input');
78
+ const code = text.charCodeAt(index);
79
+ if (code === 0x7b) return parseObject(depth);
80
+ if (code === 0x5b) return parseArray(depth);
81
+ if (code === 0x22) return parseString();
82
+ if (code === 0x2d || (code >= 0x30 && code <= 0x39)) return parseNumber();
83
+ if (text.startsWith('true', index)) { index += 4; return true; }
84
+ if (text.startsWith('false', index)) { index += 5; return false; }
85
+ if (text.startsWith('null', index)) { index += 4; return null; }
86
+ fail('SYNTAX', 'Unexpected token');
87
+ }
88
+
89
+ function parseObject(depth) {
90
+ if (depth > maxDepth) fail('DEPTH', 'Worker nesting depth exceeds bound');
91
+ index += 1; // {
92
+ const result = {};
93
+ const seen = new Set();
94
+ skipWhitespace();
95
+ if (text.charCodeAt(index) === 0x7d) { index += 1; return result; }
96
+ for (;;) {
97
+ skipWhitespace();
98
+ if (text.charCodeAt(index) !== 0x22) fail('SYNTAX', 'Expected object member name');
99
+ const key = parseString();
100
+ if (seen.has(key)) fail('DUPLICATE', `Duplicate object member name ${JSON.stringify(key)}`);
101
+ seen.add(key);
102
+ skipWhitespace();
103
+ if (text.charCodeAt(index) !== 0x3a) fail('SYNTAX', 'Expected colon after object member name');
104
+ index += 1;
105
+ // Define, never assign: assignment would invoke the inherited __proto__
106
+ // setter, hiding the member from own-key validation and diverging from
107
+ // JSON.parse, which creates an ordinary own property.
108
+ Object.defineProperty(result, key, { value: parseValue(depth + 1), enumerable: true, writable: true, configurable: true });
109
+ skipWhitespace();
110
+ const delimiter = text.charCodeAt(index);
111
+ if (delimiter === 0x2c) { index += 1; continue; }
112
+ if (delimiter === 0x7d) { index += 1; break; }
113
+ fail('SYNTAX', 'Expected , or } in object');
114
+ }
115
+ return result;
116
+ }
117
+
118
+ function parseArray(depth) {
119
+ if (depth > maxDepth) fail('DEPTH', 'Worker nesting depth exceeds bound');
120
+ index += 1; // [
121
+ const result = [];
122
+ skipWhitespace();
123
+ if (text.charCodeAt(index) === 0x5d) { index += 1; return result; }
124
+ for (;;) {
125
+ result.push(parseValue(depth + 1));
126
+ skipWhitespace();
127
+ const delimiter = text.charCodeAt(index);
128
+ if (delimiter === 0x2c) { index += 1; continue; }
129
+ if (delimiter === 0x5d) { index += 1; break; }
130
+ fail('SYNTAX', 'Expected , or ] in array');
131
+ }
132
+ return result;
133
+ }
134
+
135
+ function parseString() {
136
+ index += 1; // opening quote
137
+ let value = '';
138
+ for (;;) {
139
+ if (index >= length) fail('SYNTAX', 'Unterminated string');
140
+ const code = text.charCodeAt(index);
141
+ if (code === 0x22) { index += 1; return value; }
142
+ if (code === 0x5c) {
143
+ index += 1;
144
+ const escape = text[index];
145
+ if (escape === undefined) fail('SYNTAX', 'Unterminated escape sequence');
146
+ switch (escape) {
147
+ case '"': value += '"'; index += 1; break;
148
+ case '\\': value += '\\'; index += 1; break;
149
+ case '/': value += '/'; index += 1; break;
150
+ case 'b': value += '\b'; index += 1; break;
151
+ case 'f': value += '\f'; index += 1; break;
152
+ case 'n': value += '\n'; index += 1; break;
153
+ case 'r': value += '\r'; index += 1; break;
154
+ case 't': value += '\t'; index += 1; break;
155
+ case 'u': {
156
+ const hex = text.slice(index + 1, index + 5);
157
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) fail('SYNTAX', 'Invalid unicode escape');
158
+ // Decoded code unit is appended as-is; two consecutive decoded
159
+ // surrogate halves recombine into one character automatically
160
+ // through normal JS UTF-16 string concatenation.
161
+ value += String.fromCharCode(parseInt(hex, 16));
162
+ index += 5;
163
+ break;
164
+ }
165
+ default: fail('SYNTAX', 'Invalid escape sequence');
166
+ }
167
+ } else if (code < 0x20) {
168
+ fail('SYNTAX', 'Unescaped control character in string');
169
+ } else {
170
+ value += text[index];
171
+ index += 1;
172
+ }
173
+ }
174
+ }
175
+
176
+ function parseNumber() {
177
+ const start = index;
178
+ if (text.charCodeAt(index) === 0x2d) index += 1;
179
+ if (text.charCodeAt(index) === 0x30) {
180
+ index += 1;
181
+ } else if (text.charCodeAt(index) >= 0x31 && text.charCodeAt(index) <= 0x39) {
182
+ index += 1;
183
+ while (text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39) index += 1;
184
+ } else fail('SYNTAX', 'Invalid number');
185
+ if (text.charCodeAt(index) === 0x2e) {
186
+ index += 1;
187
+ if (!(text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39)) fail('SYNTAX', 'Invalid number fraction');
188
+ while (text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39) index += 1;
189
+ }
190
+ if (text.charCodeAt(index) === 0x65 || text.charCodeAt(index) === 0x45) {
191
+ index += 1;
192
+ if (text.charCodeAt(index) === 0x2b || text.charCodeAt(index) === 0x2d) index += 1;
193
+ if (!(text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39)) fail('SYNTAX', 'Invalid number exponent');
194
+ while (text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39) index += 1;
195
+ }
196
+ return Number(text.slice(start, index));
197
+ }
198
+
199
+ const result = parseValue(1);
200
+ skipWhitespace();
201
+ if (index !== length) fail('SYNTAX', 'Unexpected trailing characters after JSON value');
202
+ return result;
203
+ }
204
+
205
+ const installedOwner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
206
+ export const { workerNegativeFixtureSuite, workerNegativeFixtureDigest, registerProvider, createProvider, listProviders, hasProvider } = installedOwner;
207
+
208
+ /**
209
+ * Whether the optional muse exec Ralph adapter is registered.
210
+ * Enabled by default; set `AIWG_MUSE_RALPH_ENABLED` to `0`, `false`, `no`,
211
+ * or `off` to disable the adapter without affecting
212
+ * `aiwg use --provider muse` (the deploy/writer path lives in `src/` and
213
+ * never consults this registry). See
214
+ * `tools/ralph-external/lib/muse-adapter.mjs` (#230).
215
+ *
216
+ * @returns {boolean}
217
+ */
218
+ export function isMuseRalphEnabled() {
219
+ const raw = String(process.env.AIWG_MUSE_RALPH_ENABLED ?? '').trim().toLowerCase();
220
+ return !['0', 'false', 'no', 'off'].includes(raw);
221
+ }
222
+
223
+ /**
224
+ * @typedef {Object} ProviderCapabilities
225
+ * @property {boolean} streamJson - Supports --output-format stream-json
226
+ * @property {boolean} sessionResume - Supports --session-id for resumption
227
+ * @property {boolean} budgetControl - Supports --max-budget-usd
228
+ * @property {boolean} systemPrompt - Supports --append-system-prompt
229
+ * @property {boolean} agentMode - Supports --agent flag
230
+ * @property {boolean} mcpConfig - Supports --mcp-config
231
+ * @property {boolean} maxTurns - Supports --max-turns
232
+ */
233
+
234
+ /**
235
+ * @typedef {Object} SessionArgs
236
+ * @property {string} prompt - The prompt to send
237
+ * @property {string} [sessionId] - Session UUID for tracking
238
+ * @property {string} [model] - Model name (generic: opus/sonnet/haiku)
239
+ * @property {number} [budget] - Budget per iteration in USD
240
+ * @property {number} [maxTurns] - Maximum turns
241
+ * @property {boolean} [verbose] - Enable verbose output
242
+ * @property {string} [systemPrompt] - System prompt to append
243
+ * @property {Object} [mcpConfig] - MCP server configuration
244
+ */
245
+
246
+ /**
247
+ * @typedef {Object} AnalysisArgs
248
+ * @property {string} prompt - The analysis prompt
249
+ * @property {string} [model] - Model name (generic: opus/sonnet/haiku)
250
+ * @property {string} [agent] - Agent name to use
251
+ * @property {number} [timeout] - Timeout in ms
252
+ */
253
+
254
+ /**
255
+ * Base class for provider adapters.
256
+ * Subclasses must override all methods that throw NotImplementedError.
257
+ */
258
+ export class ProviderAdapter {
259
+ constructor() {
260
+ if (new.target === ProviderAdapter) {
261
+ throw new Error('ProviderAdapter is abstract and cannot be instantiated directly');
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Get the CLI binary name (e.g., 'claude', 'codex')
267
+ * @returns {string}
268
+ */
269
+ getBinary() {
270
+ throw new Error('Not implemented: getBinary()');
271
+ }
272
+
273
+ /**
274
+ * Get the provider name for display and config
275
+ * @returns {string}
276
+ */
277
+ getName() {
278
+ throw new Error('Not implemented: getName()');
279
+ }
280
+
281
+ /**
282
+ * Get provider capabilities
283
+ * @returns {ProviderCapabilities}
284
+ */
285
+ getCapabilities() {
286
+ throw new Error('Not implemented: getCapabilities()');
287
+ }
288
+
289
+ /**
290
+ * Build CLI arguments for a main headless session (async spawn).
291
+ * This is the primary long-running session that performs the task.
292
+ *
293
+ * @param {SessionArgs} options
294
+ * @returns {string[]}
295
+ */
296
+ buildSessionArgs(options) {
297
+ throw new Error('Not implemented: buildSessionArgs()');
298
+ }
299
+
300
+ /** Qualified drivers own exact argv mapping; permissions come from resolved policy,
301
+ * never legacy session defaults or worker-supplied invocation data. */
302
+ buildWorkerArgs(invocation, profile, permissions) {
303
+ throw new Error('Not implemented: buildWorkerArgs()');
304
+ }
305
+
306
+ /**
307
+ * Inspect a trusted host installation, without importing code or running inference.
308
+ * The returned digest binds negative qualification evidence to this exact scope.
309
+ * Install roots and the enforcing boundary must remain protected by the host.
310
+ */
311
+ async inspectWorkerInstallation(options) {
312
+ const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
313
+ return owner.inspectWorkerInstallation(this, structuredClone(options));
314
+ }
315
+
316
+ /**
317
+ * Bounded version probe plus host-owned negative evidence, never worker claims.
318
+ * Missing, unsupported or changed installations stay DISABLED. No downloads,
319
+ * login, credentials discovery, server startup or managed state is involved.
320
+ */
321
+ async qualifyWorkerInstallation(options) {
322
+ const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
323
+ return owner.qualifyWorkerInstallation(this, options);
324
+ }
325
+
326
+ async recordWorkerInstallation(options) {
327
+ const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
328
+ return owner.recordWorkerInstallation(this, options);
329
+ }
330
+
331
+ /**
332
+ * Prepare one policy-resolved invocation through the supported installed path.
333
+ * This only validates/renders data: the conductor still owns path/digest
334
+ * rechecks, launch, observation and verification. No Ralph loop is started.
335
+ * Qualified drivers supply explicit argv; legacy buildSessionArgs defaults
336
+ * must never be used as a fallback for a worker profile.
337
+ *
338
+ * Callers gate-then-launch through `checkWorkerDispatch()` (from
339
+ * `tools/scripts/resource-preflight.mjs`) and `launchIsolatedWorker()` (from
340
+ * `./worker-isolation.mjs`): over resource threshold means queue, pick
341
+ * another qualified host, or reject — never launch. Preparation itself stays
342
+ * launch-free and performs no resource gating (#2954).
343
+ *
344
+ * @param {Object} invocation - Existing workerResolvedInvocation payload
345
+ * @param {Object} qualification - Trusted adapter, cliVersion, requiredControls,
346
+ * now (epoch milliseconds), platform, current mcp/schema bindings, and optional
347
+ * merged policy and dataClass from trusted resolution. Observation evidence
348
+ * (modelObservation or defaultObservation) is required; null effort also
349
+ * requires the selected role and policy role guidance allowing its absence.
350
+ * @returns {Promise<Object>} A detached typed descriptor; never executes it
351
+ */
352
+ async prepareWorkerInvocation(invocation, qualification) {
353
+ const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
354
+ return owner.prepareInstalledWorkerInvocation(this, invocation, qualification);
355
+ }
356
+
357
+ /**
358
+ * Build CLI arguments for a short analysis call (spawnSync).
359
+ * Used by OutputAnalyzer, StateAssessor, and ClaudePromptGenerator
360
+ * for quick Claude-powered analysis between iterations.
361
+ *
362
+ * @param {AnalysisArgs} options
363
+ * @returns {string[]}
364
+ */
365
+ buildAnalysisArgs(options) {
366
+ throw new Error('Not implemented: buildAnalysisArgs()');
367
+ }
368
+
369
+ /**
370
+ * Map generic model names (opus/sonnet/haiku) to provider-specific models.
371
+ *
372
+ * @param {string} genericModel - Generic model name
373
+ * @returns {string} Provider-specific model name
374
+ */
375
+ mapModel(genericModel) {
376
+ throw new Error('Not implemented: mapModel()');
377
+ }
378
+
379
+ /**
380
+ * Check if the provider CLI is available on the system.
381
+ * @returns {Promise<boolean>}
382
+ */
383
+ async isAvailable() {
384
+ return new Promise((resolve) => {
385
+ const child = spawn(this.getBinary(), ['--version'], {
386
+ stdio: 'pipe',
387
+ });
388
+ child.on('close', (code) => resolve(code === 0));
389
+ child.on('error', () => resolve(false));
390
+ });
391
+ }
392
+
393
+ /**
394
+ * Get the provider CLI version string.
395
+ * @returns {Promise<string|null>}
396
+ */
397
+ async getVersion() {
398
+ return new Promise((resolve) => {
399
+ let output = '';
400
+ const child = spawn(this.getBinary(), ['--version'], {
401
+ stdio: 'pipe',
402
+ });
403
+ child.stdout.on('data', (chunk) => {
404
+ output += chunk.toString();
405
+ });
406
+ child.on('close', (code) => {
407
+ resolve(code === 0 ? output.trim() : null);
408
+ });
409
+ child.on('error', () => resolve(null));
410
+ });
411
+ }
412
+
413
+ /**
414
+ * Parse provider-specific output to extract JSON.
415
+ * Default implementation extracts the first JSON object from stdout.
416
+ * Legacy, synchronous contract only: always returns `Object|null`, never a
417
+ * Promise. Group-mode/worker-final parsing is a distinct async contract —
418
+ * call `parseWorkerFinal()` (or `parseWorkerOutput()` directly), never this
419
+ * method with a `groupMode` flag. An un-awaited Promise is a truthy object,
420
+ * so a caller that mixed the two contracts through one return type could
421
+ * mistake a pending parse for a successful legacy result (#2890 NOTE 6).
422
+ *
423
+ * @param {string} stdout - Raw stdout from the CLI
424
+ * @returns {Object|null} Parsed JSON, or null if none found/malformed
425
+ */
426
+ parseOutput(stdout) {
427
+ try {
428
+ const jsonMatch = stdout.match(/\{[\s\S]*\}/);
429
+ if (!jsonMatch) return null;
430
+ return JSON.parse(jsonMatch[0]);
431
+ } catch {
432
+ return null;
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Strict group-mode entry point. Always returns a Promise; callers MUST
438
+ * await it. Thin, intentionally-named alias over `parseWorkerOutput()` so
439
+ * group-mode call sites never have to pass a `groupMode` flag into the
440
+ * legacy, synchronous `parseOutput()` contract above.
441
+ *
442
+ * @param {string|Uint8Array|Object} output
443
+ * @param {Object} [options] - format is json, jsonl or mcp; observation is
444
+ * supplied by the adapter.
445
+ * @returns {Promise<Object>} Validated workerClaims
446
+ */
447
+ parseWorkerFinal(output, options = {}) {
448
+ return this.parseWorkerOutput(output, options);
449
+ }
450
+
451
+ /** Strict group-mode path, shared by CLI and MCP. Never falls back to regex.
452
+ * Drivers normalize qualified vendor event framing to result/message/terminal
453
+ * JSONL, or supply one claims JSON document. Observation comes from the trusted
454
+ * process/request owner, not from the final. This returns claims, not acceptance.
455
+ * Called directly or through the `parseWorkerFinal()` alias; never through
456
+ * `parseOutput()`, which keeps its own synchronous, non-group contract.
457
+ */
458
+ async parseWorkerOutput(output, options = {}) {
459
+ const {
460
+ WORKER_MAX_JSON_BYTES, WORKER_MAX_NESTING_DEPTH, WorkerContractError,
461
+ decodeWorkerProducedPayload, assertWorkerTerminal,
462
+ } = await importImpl(import.meta.url, 'mission-protocol/index.js');
463
+ const fail = message => { throw new WorkerContractError('OG_CONTRACT_INVALID', message); };
464
+ const format = options.format ?? 'jsonl';
465
+ const streamLimit = 10 * 1024 * 1024;
466
+ const limit = options.maxOutputBytes ?? streamLimit;
467
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > streamLimit) fail('Invalid worker stream bound.');
468
+ function boundedJson(value, bound) {
469
+ // Check depth before serializing MCP objects supplied by a transport SDK.
470
+ function visit(node, depth) {
471
+ if (!node || typeof node !== 'object') return;
472
+ if (depth > WORKER_MAX_NESTING_DEPTH) fail('Worker nesting depth exceeds 16.');
473
+ for (const child of Object.values(node)) visit(child, depth + 1);
474
+ }
475
+ visit(value, 1);
476
+ const bytes = JSON.stringify(value);
477
+ if (bytes === undefined || Buffer.byteLength(bytes) > bound) fail('Worker final exceeds JSON byte bound.');
478
+ return bytes;
479
+ }
480
+ function parse(bytes, bound) {
481
+ if (typeof bytes !== 'string' && !(bytes instanceof Uint8Array)) fail('Worker output must be UTF-8 bytes or text.');
482
+ // Encoding would silently replace lone surrogates with U+FFFD; reject.
483
+ if (typeof bytes === 'string' && !bytes.isWellFormed()) fail('Malformed worker final JSON or UTF-8.');
484
+ const buffer = typeof bytes === 'string' ? Buffer.from(bytes, 'utf8') : bytes;
485
+ if (buffer.byteLength > bound) fail('Worker output exceeds byte bound.');
486
+ let value;
487
+ try {
488
+ // ignoreBOM keeps a leading BOM in the text so the scanner rejects it
489
+ // (RFC 8259 §8.1: a BOM must not be added).
490
+ const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(buffer);
491
+ // Strict scanner, not JSON.parse: rejects duplicate object member
492
+ // names (RFC 8259 §4) on every worker-final path instead of silently
493
+ // keeping JSON.parse's "last value wins". See parseStrictWorkerJson.
494
+ value = parseStrictWorkerJson(text, WORKER_MAX_NESTING_DEPTH);
495
+ } catch (error) {
496
+ if (error instanceof StrictJsonScanError && error.code === 'DEPTH') fail('Worker nesting depth exceeds 16.');
497
+ if (error instanceof StrictJsonScanError && error.code === 'DUPLICATE') fail(`Duplicate worker JSON member name: ${error.message}`);
498
+ fail('Malformed worker final JSON or UTF-8.');
499
+ }
500
+ return value;
501
+ }
502
+ let final;
503
+ if (format === 'mcp') {
504
+ const response = typeof output === 'string' || output instanceof Uint8Array ? parse(output, limit) : output;
505
+ boundedJson(response, limit);
506
+ if (!response || typeof response !== 'object' || Array.isArray(response)) fail('Malformed MCP worker result.');
507
+ if (Object.keys(response).some(key => !['content', 'structuredContent', 'isError', '_meta'].includes(key))) fail('Unknown MCP result field.');
508
+ const isError = response.isError;
509
+ if (isError !== undefined && isError !== false) fail('MCP worker isError result.');
510
+ const content = response.content;
511
+ if (content !== undefined && (!Array.isArray(content) || content.length > 1)) fail('Missing or double MCP final.');
512
+ let textFinal;
513
+ if (content?.length) {
514
+ const item = content[0];
515
+ if (item?.type !== 'text' || typeof item.text !== 'string') fail('MCP final must be typed JSON text.');
516
+ textFinal = parse(item.text, WORKER_MAX_JSON_BYTES);
517
+ }
518
+ final = response.structuredContent ?? textFinal;
519
+ if (final === undefined) fail('Missing MCP worker final.');
520
+ if (response.structuredContent !== undefined && textFinal !== undefined) {
521
+ // MCP's required text mirror is not a second final, but must agree.
522
+ const { isDeepStrictEqual } = await import('node:util');
523
+ if (!isDeepStrictEqual(final, textFinal)) fail('Conflicting MCP final representations.');
524
+ }
525
+ } else if (format === 'json') {
526
+ final = parse(output, Math.min(limit, WORKER_MAX_JSON_BYTES));
527
+ } else if (format === 'jsonl') {
528
+ if (typeof output !== 'string' && !(output instanceof Uint8Array)) fail('Worker stream must be UTF-8 bytes or text.');
529
+ const bytes = typeof output === 'string' ? Buffer.from(output, 'utf8') : output;
530
+ if (bytes.byteLength > limit) fail('Worker stream exceeds byte bound.');
531
+ let stream;
532
+ try { stream = new TextDecoder('utf-8', { fatal: true }).decode(bytes); }
533
+ catch { fail('Malformed worker stream UTF-8.'); }
534
+ // The `{"type":"terminal",...}` line is optional by design (05 §5,
535
+ // 11 §B-OG-14: "qualified successful terminal event/exit" — either is
536
+ // acceptable evidence, not both). It is in-band confirmation only.
537
+ // The authoritative truncation backstop is `options.observation`
538
+ // (adapter-owned process/transport exit), enforced unconditionally
539
+ // below via `assertWorkerTerminal()` regardless of whether this line
540
+ // was present. A stream cut immediately after the `result` line, with
541
+ // no observed clean exit, still fails there — never on the missing
542
+ // terminal line alone (#2890 MINOR 4).
543
+ let terminal = false;
544
+ for (const line of stream.split('\n')) {
545
+ if (!line.trim()) continue;
546
+ const event = parse(line, WORKER_MAX_JSON_BYTES);
547
+ if (terminal || !event || typeof event !== 'object' || Array.isArray(event)) fail('Inconsistent worker terminal stream.');
548
+ if (event.type === 'result') {
549
+ if (final !== undefined) fail('Double worker final.');
550
+ if (Object.keys(event).some(key => !['type', 'result'].includes(key)) || event.result === undefined) fail('Malformed worker result envelope.');
551
+ final = event.result;
552
+ } else if (event.type === 'message') {
553
+ if (final !== undefined || typeof event.text !== 'string' || Object.keys(event).some(key => !['type', 'text'].includes(key))) fail('Inconsistent worker message stream.');
554
+ } else if (event.type === 'terminal') {
555
+ if (final === undefined || event.status !== 'completed' || Object.keys(event).some(key => !['type', 'status'].includes(key))) fail('Inconsistent worker terminal stream.');
556
+ terminal = true;
557
+ } else fail('Unknown or error worker stream event.');
558
+ }
559
+ if (final === undefined) fail('Missing worker final.');
560
+ } else fail('Unknown worker final format.');
561
+ boundedJson(final, WORKER_MAX_JSON_BYTES);
562
+ const claims = decodeWorkerProducedPayload('workerClaims', 'worker', final);
563
+ if (options.observation?.transport !== (format === 'mcp' ? 'mcp' : 'shell')) fail('Worker transport observation mismatch.');
564
+ assertWorkerTerminal(options.observation);
565
+ return claims;
566
+ }
567
+
568
+ /**
569
+ * Get environment variable overrides for headless mode.
570
+ * @returns {Object<string, string>}
571
+ */
572
+ getEnvOverrides() {
573
+ return {};
574
+ }
575
+
576
+ /** JSONL written to stdin before bounded process termination, when supported. */
577
+ getAbortInput() { return null; }
578
+
579
+ /**
580
+ * Observe execution through the selected profile's framing and grammar.
581
+ * This hook never parses or verifies worker claims: parseWorkerFinal remains
582
+ * the separate strict final contract, and the conductor owns verification.
583
+ */
584
+ createHealthObserver(options = {}) {
585
+ return createWorkerHealthObserver({
586
+ ...options,
587
+ provider: this.getName(),
588
+ normalizeEvent: (event, source, binding) => this.observeHealthEvent(event, source, binding),
589
+ });
590
+ }
591
+
592
+ /** Unknown provider grammar supplies no semantic execution evidence. */
593
+ observeHealthEvent(event, source, binding) { return observeProviderHealthEvent(this.getName(), event, source, binding); }
594
+
595
+ /**
596
+ * Get the path where the provider stores session transcripts.
597
+ *
598
+ * @param {string} sessionId - Session UUID
599
+ * @param {string} workingDir - Working directory path
600
+ * @returns {string|null} Path to transcript file, or null if not supported
601
+ */
602
+ getTranscriptPath(sessionId, workingDir) {
603
+ return null;
604
+ }
605
+
606
+ /**
607
+ * Check if a capability is supported, and optionally log a warning.
608
+ *
609
+ * @param {string} capability - Capability name from ProviderCapabilities
610
+ * @param {string} [context] - Context for the warning message
611
+ * @returns {boolean}
612
+ */
613
+ hasCapability(capability) {
614
+ const caps = this.getCapabilities();
615
+ return !!caps[capability];
616
+ }
617
+
618
+ /**
619
+ * Log a capability warning when a feature is not supported.
620
+ *
621
+ * @param {string} capability - Capability name
622
+ * @param {string} feature - Human-readable feature description
623
+ */
624
+ warnUnsupported(capability, feature) {
625
+ if (!this.hasCapability(capability)) {
626
+ console.warn(
627
+ `[${this.getName()}] Warning: ${feature} not supported by this provider, skipping`
628
+ );
629
+ }
630
+ }
631
+ }
632
+
633
+ // ============================================================================
634
+ // Provider Registry & Factory
635
+ // ============================================================================
636
+
637
+ export async function loadInstalledWorkerAdapters(request) {
638
+ await ensureProvidersRegistered();
639
+ return installedOwner.loadInstalledWorkerAdapters(request);
640
+ }
641
+
642
+ // ============================================================================
643
+ // Auto-register built-in providers on import
644
+ // ============================================================================
645
+
646
+ // Lazy imports to avoid circular dependencies - adapters register themselves
647
+ // when their modules are loaded. We trigger that here.
648
+ async function registerBuiltinProviders() {
649
+ try {
650
+ await import('./claude-adapter.mjs');
651
+ } catch { /* ignore if not found */ }
652
+ try {
653
+ await import('./codex-adapter.mjs');
654
+ } catch { /* ignore if not found */ }
655
+ try {
656
+ await import('./opencode-adapter.mjs');
657
+ } catch { /* ignore if not found */ }
658
+ try {
659
+ await import('./factory-adapter.mjs');
660
+ } catch { /* ignore if not found */ }
661
+ try {
662
+ await import('./pi-adapter.mjs');
663
+ await import('./omp-adapter.mjs');
664
+ } catch { /* ignore if not found */ }
665
+ try {
666
+ await import('./deepseek-harness-adapter.mjs');
667
+ } catch { /* ignore if not found */ }
668
+ // #230: optional / post-experimental. Disabled via AIWG_MUSE_RALPH_ENABLED=0
669
+ // without affecting `aiwg use --provider muse`.
670
+ if (isMuseRalphEnabled()) {
671
+ try {
672
+ await import('./muse-adapter.mjs');
673
+ } catch { /* ignore if not found */ }
674
+ }
675
+ }
676
+
677
+ // Run registration — store promise so callers can await it
678
+ const _registrationPromise = registerBuiltinProviders();
679
+
680
+ /**
681
+ * Ensure all built-in providers are registered before use.
682
+ * Must be awaited before calling createProvider().
683
+ * Optional installed bindings come only from the trusted host registration path.
684
+ */
685
+ export async function ensureProvidersRegistered(installed) {
686
+ await _registrationPromise;
687
+ if (installed !== undefined) return loadInstalledWorkerAdapters(installed);
688
+ }
689
+
690
+ export default ProviderAdapter;