@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.
- package/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3091 -24
- package/bin/aiwg.mjs +86 -21
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/channel/manager.mjs +59 -10
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/decision.js +297 -2
- package/dist/src/cli/handlers/diagnose.js +9 -2
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/init.js +245 -30
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +69 -6
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +16 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/runtime-info.js +24 -9
- package/dist/src/cli/handlers/script-runner.js +16 -6
- package/dist/src/cli/handlers/session.js +396 -62
- package/dist/src/cli/handlers/sessions.js +146 -40
- package/dist/src/cli/handlers/setup-manifest.js +439 -39
- package/dist/src/cli/handlers/setup.js +139 -39
- package/dist/src/cli/handlers/subcommands.js +3 -1
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/use.js +13 -0
- package/dist/src/cli/handlers/utilities.js +106 -23
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/config/aiwg-config.js +268 -4
- package/dist/src/config/cli.js +146 -19
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/user-config.js +36 -10
- package/dist/src/decision/adapters/jev.js +80 -28
- package/dist/src/decision/ask.js +352 -0
- package/dist/src/decision/driver.js +4 -0
- package/dist/src/decision/entry.js +38 -6
- package/dist/src/decision/heldout/collector.js +10 -1
- package/dist/src/decision/index.js +3 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +121 -0
- package/dist/src/decision/ledger.js +839 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +3 -1
- package/dist/src/installation/manager-command.mjs +1 -1
- package/dist/src/installation/manager.mjs +17 -2
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/adapters/codex-runtime.js +360 -56
- package/dist/src/mcp/cli.mjs +587 -77
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.mjs +16 -26
- package/dist/src/mcp/helpers.mjs +106 -2
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +264 -57
- package/dist/src/mcp/registry.mjs +198 -45
- package/dist/src/mcp/server.mjs +403 -20
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +58 -0
- package/dist/src/mcp/tools/orchestration.mjs +38 -0
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +639 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +70 -13
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3091 -24
- package/dist/src/models/model-discovery.js +862 -63
- package/dist/src/models/provider-policy.js +625 -11
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +259 -0
- package/dist/src/models/worker-direct-readiness.js +529 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +131 -0
- package/dist/src/models/worker-installation.mjs +879 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.mjs +387 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/grok-build-headless.js +43 -21
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +9 -0
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +538 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +45 -0
- package/dist/src/sessions/adapters/generic.js +37 -3
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +423 -24
- package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
- package/dist/src/sessions/adapters/muse.js +68 -29
- package/dist/src/sessions/adapters/pi.js +1 -0
- package/dist/src/sessions/batch-contracts.js +13 -1
- package/dist/src/sessions/batch-import.js +23 -42
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +35 -0
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/importer.js +24 -8
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/repository.js +54 -13
- package/dist/src/sessions/workspace-discovery.js +99 -37
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +18 -1
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
- package/tools/_resolve-impl.mjs +39 -0
- package/tools/agents/deploy-agents.mjs +15 -2
- package/tools/agents/providers/base.mjs +63 -43
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +6 -3
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/providers/deepseek-harness-transport.mjs +358 -0
- package/tools/providers/omp-transport.mjs +192 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- 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;
|