@aiwg/cli 2026.10.9 → 2026.10.11
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/model-capabilities.v1.json +275 -17
- package/bin/aiwg.mjs +8 -0
- package/dist/src/cli/handlers/mc-admission.js +39 -0
- package/dist/src/cli/handlers/mc.js +2 -31
- package/dist/src/cli/handlers/models-help.js +52 -0
- package/dist/src/cli/handlers/models.js +11 -81
- package/dist/src/cli/handlers/sessions.js +198 -15
- package/dist/src/cli/handlers/use-addon-profile.js +72 -0
- package/dist/src/cli/handlers/use.js +203 -234
- package/dist/src/cli/ui.js +1 -0
- package/dist/src/config/aiwg-config.js +38 -184
- package/dist/src/config/cli-help.js +54 -0
- package/dist/src/config/cli.js +5 -69
- package/dist/src/config/config-normalization.js +10 -0
- package/dist/src/config/worker-activation-gate.js +26 -0
- package/dist/src/config/worker-adoption-cli.js +102 -0
- package/dist/src/config/worker-adoption.js +368 -0
- package/dist/src/config/worker-config-digest.js +14 -0
- package/dist/src/config/worker-context.js +337 -0
- package/dist/src/config/worker-group-templates.js +150 -0
- package/dist/src/config/worker-guidance-warning.js +28 -0
- package/dist/src/config/worker-owner-lock.js +152 -0
- package/dist/src/config/worker-parallelism.js +154 -0
- package/dist/src/config/worker-write.js +75 -0
- package/dist/src/decision/telemetry/exporter.js +2 -1
- package/dist/src/decision/telemetry/index.js +1 -0
- package/dist/src/decision/telemetry/mapping.js +43 -0
- package/dist/src/decision/telemetry/otlp-http.js +5 -2
- package/dist/src/decision/telemetry/redaction.js +6 -1
- package/dist/src/decision/telemetry/types.js +3 -0
- package/dist/src/decision/telemetry/worker.js +69 -0
- package/dist/src/lint/rule-activation.js +15 -7
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/server.mjs +2 -11
- package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
- package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
- package/dist/src/mcp/tools/worker-invoke.mjs +114 -16
- package/dist/src/mission-protocol/codecs.js +8 -38
- package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
- package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
- package/dist/src/models/conductor-data-policy.d.mts +8 -0
- package/dist/src/models/conductor-data-policy.mjs +190 -0
- package/dist/src/models/config-loader.js +19 -3
- package/dist/src/models/config-writer.js +27 -0
- package/dist/src/models/model-capabilities.v1.json +275 -17
- package/dist/src/models/provider-policy.js +14 -155
- package/dist/src/models/resolver.js +2 -293
- package/dist/src/models/worker-artifact-binding.mjs +12 -0
- package/dist/src/models/worker-direct-readiness-policy.js +155 -0
- package/dist/src/models/worker-direct-readiness.js +9 -218
- package/dist/src/models/worker-health-readiness.js +105 -26
- package/dist/src/models/worker-installation.mjs +12 -14
- package/dist/src/models/worker-member-references.js +64 -0
- package/dist/src/models/worker-plan.js +317 -0
- package/dist/src/models/worker-selection-policy.js +217 -0
- package/dist/src/models/wrapper-deployment.js +1 -1
- package/dist/src/policy/authorization.js +29 -1
- package/dist/src/process/aiwg-probe-command.d.mts +15 -0
- package/dist/src/process/aiwg-probe-command.mjs +37 -0
- package/dist/src/serve/shared-host-admission-checks.js +116 -0
- package/dist/src/serve/shared-host-scheduler.js +25 -107
- package/dist/src/serve/shared-host-starvation.js +73 -0
- package/dist/src/serve/worker-artifacts.d.mts +4 -0
- package/dist/src/serve/worker-artifacts.mjs +56 -0
- package/dist/src/serve/worker-command-request.js +68 -0
- package/dist/src/serve/worker-invocation-paths.js +144 -0
- package/dist/src/serve/worker-invocation-preparation.js +169 -0
- package/dist/src/serve/worker-invocation.js +24 -291
- package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
- package/dist/src/serve/worker-stdin-frame.mjs +6 -6
- package/dist/src/sessions/adapters/grokbot.js +7 -0
- package/dist/src/sessions/batch-import.js +34 -6
- package/dist/src/sessions/contracts.js +19 -0
- package/dist/src/sessions/coverage-preflight.js +7 -1
- package/dist/src/sessions/fortemi-export-mapping.js +33 -3
- package/dist/src/sessions/host-catalog.js +157 -0
- package/dist/src/sessions/image-blobs.js +141 -0
- package/dist/src/sessions/importer.js +23 -3
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/refresh.js +25 -2
- package/dist/src/sessions/repository.js +164 -22
- package/dist/src/sessions/workspace-discovery.js +5 -2
- package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
- package/package.json +15 -1
- package/schemas/mission-v1.schema.json +20 -19
- package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
- package/tools/agents/providers/claude-post-deploy.mjs +35 -0
- package/tools/agents/providers/claude.mjs +3 -30
- package/tools/agents/providers/codex-post-deploy.mjs +13 -0
- package/tools/agents/providers/codex.mjs +2 -8
- package/tools/agents/providers/conductor-data-guard.mjs +61 -0
- package/tools/agents/providers/rule-delivery.mjs +3 -16
- package/tools/agents/providers/rule-presentation.mjs +38 -0
- package/tools/plugin/package-plugins.mjs +1 -1
- package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
- package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
- package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
- package/tools/ralph-external/lib/session-attempt.mjs +85 -0
- package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
- package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
- package/tools/ralph-external/lib/worker-context.mjs +237 -0
- package/tools/ralph-external/lib/worker-final.mjs +143 -0
- package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
- package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
- package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
- package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +19 -73
- package/tools/ralph-external/lib/worker-launch-cli.mjs +49 -0
- package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
- package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
- package/tools/ralph-external/lib/worker-termination.mjs +82 -0
- package/tools/ralph-external/session-launcher.mjs +124 -313
- package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Compile the existing project data policy into project-local provider refusals.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { importImpl, resolveImpl } from '../../_resolve-impl.mjs';
|
|
6
|
+
|
|
7
|
+
const quote = value => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
8
|
+
|
|
9
|
+
// Local-only paths are declared only in project/user models.json or project
|
|
10
|
+
// parallelism (the built-in defaults declare none). Skip the full model-config
|
|
11
|
+
// load when none of them mention the field, so ordinary deploys never depend
|
|
12
|
+
// on the framework default config (absent from the slim CLI package).
|
|
13
|
+
function declaresLocalOnlyPaths(targetDir) {
|
|
14
|
+
const userDir = process.env.AIWG_CONFIG || path.join(os.homedir(), '.config/aiwg');
|
|
15
|
+
return [path.join(targetDir, 'models.json'), path.join(userDir, 'models.json'), path.join(targetDir, '.aiwg/aiwg.config')]
|
|
16
|
+
.some(file => { try { return fs.readFileSync(file, 'utf8').includes('localOnlyPaths'); } catch { return false; } });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function deployConductorDataGuard(targetDir, provider, opts = {}) {
|
|
20
|
+
if (!declaresLocalOnlyPaths(targetDir)) return [];
|
|
21
|
+
// Always resolve policy through the existing loader, including inherited restrictions.
|
|
22
|
+
const { ConfigLoader } = await importImpl(import.meta.url, 'models/config-loader.js');
|
|
23
|
+
const { readAiwgConfig } = await importImpl(import.meta.url, 'config/aiwg-config.js');
|
|
24
|
+
const guard = await importImpl(import.meta.url, 'models/conductor-data-policy.mjs');
|
|
25
|
+
const config = await readAiwgConfig(targetDir);
|
|
26
|
+
const models = await new ConfigLoader(targetDir).load(config?.parallelism);
|
|
27
|
+
const paths = guard.collectLocalOnlyPaths(models.defaults.workerSelection, config?.parallelism);
|
|
28
|
+
if (!paths.length || opts.dryRun) return paths;
|
|
29
|
+
const root = path.resolve(targetDir);
|
|
30
|
+
if (provider === 'claude') {
|
|
31
|
+
const settingsPath = path.join(root, '.claude/settings.json');
|
|
32
|
+
const settings = fs.existsSync(settingsPath) ? JSON.parse(fs.readFileSync(settingsPath, 'utf8')) : {};
|
|
33
|
+
const hookPath = path.join(root, '.claude/hooks/aiwg-local-only.mjs');
|
|
34
|
+
const hookSource = fs.readFileSync(resolveImpl(import.meta.url, 'models/conductor-data-policy.mjs'), 'utf8');
|
|
35
|
+
fs.mkdirSync(path.dirname(hookPath), { recursive: true });
|
|
36
|
+
fs.writeFileSync(hookPath, `${hookSource}\nrunConductorDataHook(${JSON.stringify(root)}, ${JSON.stringify(paths)});\n`);
|
|
37
|
+
settings.permissions ??= {};
|
|
38
|
+
settings.permissions.deny = [...new Set([...(settings.permissions.deny ?? []), ...paths.flatMap(file => [
|
|
39
|
+
`Read(//${path.join(root, file).slice(1)})`, `Read(//${path.join(root, file).slice(1)}/**)`,
|
|
40
|
+
])])];
|
|
41
|
+
settings.hooks ??= {};
|
|
42
|
+
const command = `node ${quote(hookPath)}`;
|
|
43
|
+
const entries = settings.hooks.PreToolUse ?? [];
|
|
44
|
+
settings.hooks.PreToolUse = [...entries.filter(entry => !entry.hooks?.some(hook => hook.command === command)),
|
|
45
|
+
{ matcher: '.*', hooks: [{ type: 'command', command }] }];
|
|
46
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
47
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
48
|
+
} else if (provider === 'codex') {
|
|
49
|
+
const rulesPath = path.join(root, '.codex/rules/aiwg-local-only.rules');
|
|
50
|
+
const lines = ['# AIWG local-only inputs: explicit command prefixes only; not filesystem isolation.'];
|
|
51
|
+
for (const file of paths) for (const spelling of [file, `./${file}`, path.join(root, file)]) {
|
|
52
|
+
for (const command of ['cat', 'head', 'tail', 'less', 'more', 'sed', 'awk', 'grep', 'rg']) {
|
|
53
|
+
lines.push(`prefix_rule(pattern = ${JSON.stringify([command, spelling])}, decision = "forbidden")`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
fs.mkdirSync(path.dirname(rulesPath), { recursive: true });
|
|
57
|
+
fs.writeFileSync(rulesPath, lines.join('\n') + '\n');
|
|
58
|
+
}
|
|
59
|
+
console.log(`Local-only conductor guard (${provider}): ${paths.join(', ')}; shell coverage is limited.`);
|
|
60
|
+
return paths;
|
|
61
|
+
}
|
|
@@ -20,6 +20,8 @@ const { guardRulesInlineBudget } = await importImpl(import.meta.url, 'rules/inli
|
|
|
20
20
|
const { userConfigFile } = await importImpl(import.meta.url, 'config/user-config-dir.mjs');
|
|
21
21
|
const { resolveProjectAiwgDir } = await importImpl(import.meta.url, 'config/project-artifacts-runtime.mjs');
|
|
22
22
|
import { writeContextFileSync, CONTEXT_STAGE_ENV } from './context-write.mjs';
|
|
23
|
+
import { projectOnDemandReferences, renderRuleKernelIndex } from './rule-presentation.mjs';
|
|
24
|
+
export { renderRuleKernelIndex } from './rule-presentation.mjs';
|
|
23
25
|
const PREPARATION_HOOK = Symbol.for('aiwg.provider-preparation-hook.v1');
|
|
24
26
|
|
|
25
27
|
export function installRuleDeploymentPreparationHook(hook) {
|
|
@@ -298,21 +300,6 @@ export async function prepareRuleDelivery(opts) {
|
|
|
298
300
|
return { rules, bindings, targets, pins: pinned.rules, pinWarnings: pinned.warnings };
|
|
299
301
|
}
|
|
300
302
|
|
|
301
|
-
export function renderRuleKernelIndex(rules, nativePaths = false) {
|
|
302
|
-
const summary = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'summary');
|
|
303
|
-
const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
|
|
304
|
-
const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
|
|
305
|
-
if (!summary.length && !paths.length && !kernels.length) return '';
|
|
306
|
-
const lines = ['# Rule kernel index', ''];
|
|
307
|
-
for (const rule of kernels) lines.push(`- ${rule.name} — ${rule.summary || 'Fetch and follow the full rule before governed work.'} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
|
|
308
|
-
for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
|
|
309
|
-
if (paths.length) {
|
|
310
|
-
lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
|
|
311
|
-
nativePaths ? 'The provider loads these rules for matching paths; full contracts remain available through the commands below.' : 'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
|
|
312
|
-
for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
|
|
313
|
-
}
|
|
314
|
-
return lines.join('\n');
|
|
315
|
-
}
|
|
316
303
|
|
|
317
304
|
function managed(content, opts) {
|
|
318
305
|
return addManagedMarker(content, opts.deployVersion, opts.deploySource || 'bundled');
|
|
@@ -334,7 +321,7 @@ function putStartup(file, body) {
|
|
|
334
321
|
|
|
335
322
|
/** Tagged rules bypass legacy aggregate writers. Untagged projections retain their tier policy. */
|
|
336
323
|
function projectRules(opts, provider) {
|
|
337
|
-
const
|
|
324
|
+
const rules = projectOnDemandReferences(opts.ruleDelivery.rules, ['claude', 'cursor'].includes(provider.name));
|
|
338
325
|
if (opts.contextFiles !== false && provider.name === 'hermes') {
|
|
339
326
|
// The old context generator carries hardcoded corpus directives. Only the
|
|
340
327
|
// selected contracts below may activate policy; preserve operator prose.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Render deployed rule references without links to catalog-only siblings. */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function projectOnDemandReferences(rules, nativePaths = false) {
|
|
5
|
+
const byFile = new Map(rules.map(rule => [path.resolve(rule.file), rule]));
|
|
6
|
+
return rules.map(rule => {
|
|
7
|
+
const rewrite = content => content?.replace(
|
|
8
|
+
/\[[^\]\n]+\]\((?:\.\/)?([\w-]+)\.md\)[ \t]*\(`aiwg show rule \1(?: --body)?`, on-demand\)/g,
|
|
9
|
+
(reference, name) => {
|
|
10
|
+
const sibling = byFile.get(path.resolve(path.dirname(rule.file), `${name}.md`));
|
|
11
|
+
// Only known catalog rules can replace a relative file reference. Keep
|
|
12
|
+
// unknown/broken links and links to files selected for native delivery.
|
|
13
|
+
if (!sibling?.tagged || sibling.name !== name || sibling.activation === 'kernel' ||
|
|
14
|
+
(nativePaths && sibling.activation === 'paths')) return reference;
|
|
15
|
+
return `\`aiwg show rule ${name} --body\` (on-demand)`;
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
return { ...rule, content: rewrite(rule.content), body: rewrite(rule.body),
|
|
19
|
+
...(rule.coreBody === undefined ? {} : { coreBody: rewrite(rule.coreBody) }) };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function renderRuleKernelIndex(rules, nativePaths = false) {
|
|
24
|
+
const summary = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'summary');
|
|
25
|
+
const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
|
|
26
|
+
const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
|
|
27
|
+
if (!summary.length && !paths.length && !kernels.length) return '';
|
|
28
|
+
const lines = ['# Rule kernel index', ''];
|
|
29
|
+
for (const rule of kernels) lines.push(`- ${rule.name} — ${rule.summary || 'Fetch and follow the full rule before governed work.'} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
|
|
30
|
+
for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
|
|
31
|
+
if (paths.length) {
|
|
32
|
+
lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
|
|
33
|
+
nativePaths ? 'The provider loads these rules for matching paths; full contracts remain available through the commands below.' : 'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
|
|
34
|
+
for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
|
|
35
|
+
}
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -41,7 +41,7 @@ const PLUGIN_CONFIGS = {
|
|
|
41
41
|
skills: 'agentic/code/frameworks/sdlc-complete/skills'
|
|
42
42
|
},
|
|
43
43
|
extraCopy: [
|
|
44
|
-
|
|
44
|
+
...['threat-assessment.mjs', 'threat-assessment'].map(name => ({ from: `tools/security/${name}`, to: `tools/security/${name}` })),
|
|
45
45
|
{ from: 'agentic/code/frameworks/sdlc-complete/flows/flow-architecture-evolution.playbook.yaml', to: 'flows/flow-architecture-evolution.playbook.yaml' },
|
|
46
46
|
{ from: 'agentic/code/frameworks/sdlc-complete/flows/flow-release.playbook.yaml', to: 'flows/flow-release.playbook.yaml' }
|
|
47
47
|
],
|
|
@@ -212,8 +212,8 @@ export class ClaudeAdapter extends ProviderAdapter {
|
|
|
212
212
|
];
|
|
213
213
|
|
|
214
214
|
// Session tracking
|
|
215
|
-
if (options.sessionId) {
|
|
216
|
-
args.push('--session-id', options.sessionId);
|
|
215
|
+
if (options.resumeSession || options.sessionId) {
|
|
216
|
+
args.push(options.resumeSession ? '--resume' : '--session-id', options.resumeSession || options.sessionId);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
// Model selection (Claude passes through directly)
|
|
@@ -44,7 +44,7 @@ const MODEL_MAP = {
|
|
|
44
44
|
*
|
|
45
45
|
* codex --no-daemon exec --ignore-user-config -m M \
|
|
46
46
|
* -c model_reasoning_effort="E" -s S -C W -c approval_policy="never" \
|
|
47
|
-
* --skip-git-repo-check -o F --json - < briefPath
|
|
47
|
+
* --skip-git-repo-check -o F.last-message --json - < briefPath
|
|
48
48
|
*
|
|
49
49
|
* `--no-daemon` is a root-level flag and `--ignore-user-config` is an
|
|
50
50
|
* `exec`-level flag (root `--help` lists no `--ignore-user-config`;
|
|
@@ -206,7 +206,7 @@ export class CodexAdapter extends ProviderAdapter {
|
|
|
206
206
|
* model, explicit effort as one `-c model_reasoning_effort="E"` token
|
|
207
207
|
* (TOML `"E"` quoting via `JSON.stringify`; there is no shell in argv),
|
|
208
208
|
* explicit sandbox/approval (`never` only), the conductor workdir as
|
|
209
|
-
* `-C`,
|
|
209
|
+
* `-C`, a separate last-message capture as `-o`, the JSONL stream as `--json` and
|
|
210
210
|
* `-` as the prompt argument so the brief arrives on stdin
|
|
211
211
|
* (`stdin: 'brief-text'`). Permission flags from the legacy Ralph loop
|
|
212
212
|
* (`--dangerously-bypass-approvals-and-sandbox`), guessed step caps and
|
|
@@ -260,7 +260,7 @@ export class CodexAdapter extends ProviderAdapter {
|
|
|
260
260
|
'-C', cwd,
|
|
261
261
|
'-c', `approval_policy=${JSON.stringify(approval)}`,
|
|
262
262
|
'--skip-git-repo-check',
|
|
263
|
-
'-o', finalPath
|
|
263
|
+
'-o', `${finalPath}.last-message`,
|
|
264
264
|
'--json',
|
|
265
265
|
'-',
|
|
266
266
|
];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Validate the parent reservation and disclose one-shot attempt bindings.
|
|
2
|
+
import { importImpl } from '../../_resolve-impl.mjs';
|
|
3
|
+
import { sampleWorkerIdentity } from '../process-monitor.mjs';
|
|
4
|
+
import { changedBindings } from './session-attempt.mjs';
|
|
5
|
+
const { qualificationDigest } = await importImpl(import.meta.url, 'models/qualification-digest.mjs');
|
|
6
|
+
|
|
7
|
+
export function managedAttemptBindings(plan) {
|
|
8
|
+
const invocation = plan.invocation;
|
|
9
|
+
const digests = plan.digests ?? {};
|
|
10
|
+
const selected = invocation?.selected ?? plan.selected;
|
|
11
|
+
return { policyDigest: invocation?.policyDigest ?? (digests.configDigest && digests.resolvedModelsDigest
|
|
12
|
+
? qualificationDigest([digests.configDigest, digests.resolvedModelsDigest]) : null),
|
|
13
|
+
catalogDigest: invocation?.catalogDigest ?? digests.catalogDigest ?? null,
|
|
14
|
+
profileDigest: invocation?.profileDigest ?? digests.profileDigest ?? null,
|
|
15
|
+
selected: selected ? { group: selected.group, member: selected.member, cli: selected.cli,
|
|
16
|
+
inferenceProvider: selected.inferenceProvider, model: selected.model,
|
|
17
|
+
effort: typeof selected.effort === 'object' ? selected.effort?.value ?? null : selected.effort ?? null,
|
|
18
|
+
profileRef: selected.profileRef ?? selected.profile } : null, sessionId: null };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function validateManagedAttempt(options, saved, inputs, now, validate, publish) {
|
|
22
|
+
const attempt = await managedWorkerAttempt(options, saved, inputs, now);
|
|
23
|
+
await publish(attempt);
|
|
24
|
+
try {
|
|
25
|
+
const invocation = await validate(saved, inputs.snapshot, inputs.drivers, now);
|
|
26
|
+
return { invocation, attempt };
|
|
27
|
+
} catch (error) {
|
|
28
|
+
attempt.status = 'blocked'; attempt.errorCode = error.code ?? 'PLAN_REVALIDATION_FAILED';
|
|
29
|
+
await publish(attempt);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function managedWorkerAttempt(options, saved, inputs, now) {
|
|
35
|
+
const { resolveWorkerInvocation } = await importImpl(import.meta.url, 'serve/worker-invocation.js');
|
|
36
|
+
const current = await resolveWorkerInvocation(inputs.snapshot, saved.request, inputs.drivers, now);
|
|
37
|
+
const original = options.attempt?.originalBindings ?? managedAttemptBindings(saved);
|
|
38
|
+
const bindings = managedAttemptBindings(current);
|
|
39
|
+
return { ...options.attempt, kind: 'fresh', reason: options.attempt?.reason
|
|
40
|
+
?? (options.resumeSession || options.sessionId ? 'one-shot-continuation-unsupported' : 'one-shot-profile'),
|
|
41
|
+
requestedSessionId: options.attempt?.requestedSessionId ?? options.resumeSession ?? options.sessionId ?? null,
|
|
42
|
+
requestId: options.managedAdmission.requestId, originalBindings: original,
|
|
43
|
+
bindings, changes: changedBindings(original, bindings),
|
|
44
|
+
status: current.status === 'eligible' ? 'prepared' : 'blocked',
|
|
45
|
+
diagnostics: current.diagnostics };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Reuse the MC authority and its registered one-shot identity at every spawn boundary. */
|
|
49
|
+
export async function assertManagedWorkerLaunch(options, invocation, inputs, projectRoot = process.cwd()) {
|
|
50
|
+
if (!options.managedAdmission || invocation.mode !== 'managed'
|
|
51
|
+
|| inputs.snapshot.config.parallelism?.worker_mode !== 'managed') throw new Error('Managed worker admission is required');
|
|
52
|
+
const { createMcAdmissionScheduler } = await importImpl(import.meta.url, 'cli/handlers/mc.js');
|
|
53
|
+
const { loadLauncherRegistry } = await importImpl(import.meta.url, 'cli/handlers/ralph-launcher.js');
|
|
54
|
+
const { workerModelKey } = await importImpl(import.meta.url, 'models/provider-policy.js');
|
|
55
|
+
const scheduler = await createMcAdmissionScheduler(projectRoot, inputs.snapshot.config);
|
|
56
|
+
const { requestId, generation } = options.managedAdmission;
|
|
57
|
+
const snapshot = scheduler.snapshot();
|
|
58
|
+
const record = snapshot.records[requestId];
|
|
59
|
+
const deadlineAtMs = Math.min(options.cycleDeadlineAtMs ?? Infinity, ...[record?.accounting?.runKey, record?.accounting?.taskKey]
|
|
60
|
+
.map(key => snapshot.allowances?.[key]?.deadlineAt).filter(Boolean).map(Date.parse));
|
|
61
|
+
const registered = loadLauncherRegistry(projectRoot).loops[options.loopId ?? process.env.RALPH_LOOP_ID];
|
|
62
|
+
const worker = record?.managed;
|
|
63
|
+
const reservation = record?.accounting?.reservation;
|
|
64
|
+
if (!record || record.state !== 'admitted' || record.leaseGeneration !== generation
|
|
65
|
+
|| !record.launchClaimedAt || !record.metadata?.invocationId || Date.parse(record.leaseExpiresAt ?? '') <= Date.now()
|
|
66
|
+
|| deadlineAtMs <= Date.now()
|
|
67
|
+
|| worker?.kind !== 'external' || worker.group !== invocation.selected.group
|
|
68
|
+
|| worker.member !== invocation.selected.member || worker.cli !== invocation.selected.cli
|
|
69
|
+
|| worker.model !== workerModelKey(invocation.selected.inferenceProvider, invocation.selected.model)
|
|
70
|
+
|| reservation?.tokens != null && !(invocation.limits.tokens <= reservation.tokens)
|
|
71
|
+
|| reservation?.costUsd != null && !(invocation.limits.costUsd !== null && invocation.limits.costUsd <= reservation.costUsd)
|
|
72
|
+
|| registered?.managedAdmission?.requestId !== requestId || registered.managedAdmission.generation !== generation
|
|
73
|
+
|| registered.managedWorker?.invocationPath !== options.invocationPath
|
|
74
|
+
|| registered.managedWorker?.runDir !== options.runDir) {
|
|
75
|
+
throw new Error('Managed worker launch differs from the current claimed admission');
|
|
76
|
+
}
|
|
77
|
+
if (record.execution) {
|
|
78
|
+
const identity = sampleWorkerIdentity(process.pid);
|
|
79
|
+
if (record.execution.pid !== process.pid || !identity?.bootId || !identity.startTicks
|
|
80
|
+
|| record.execution.startedAt !== `${identity.bootId}:${identity.startTicks}:${identity.uid}`) {
|
|
81
|
+
throw new Error('Managed worker supervisor identity differs');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { ...record, deadlineAtMs };
|
|
85
|
+
}
|
|
86
|
+
|
|
@@ -31,6 +31,13 @@
|
|
|
31
31
|
* - `--approval-mode <mode>` — common to both; "`muse exec` accepts both
|
|
32
32
|
* approval flags" (configuration). Opt-in only: the adapter sets NO
|
|
33
33
|
* approval posture by default (#230 out of scope: `--yolo` defaults).
|
|
34
|
+
* - `--trust-workspace` — loads project rules, skills and hooks for the
|
|
35
|
+
* workspace (#3685: untrusted Muse reports `rules.context_load`
|
|
36
|
+
* `project_sources=0` with user skills only and empty hook admission;
|
|
37
|
+
* trusted reports `project_sources=1` with project skills and hooks).
|
|
38
|
+
* Tier 0 workspaces are conductor-prepared worker trees the conductor
|
|
39
|
+
* owns, so the direct profile binds this flag by default through the
|
|
40
|
+
* host-controlled trust mapping; the brief never supplies it.
|
|
34
41
|
* - `muse export --session <uuid> --out <path>` — offline,
|
|
35
42
|
* byte-deterministic transcript projection; `--last` and `--redacted`
|
|
36
43
|
* skip the picker / redact payload strings (cookbook:
|
|
@@ -173,6 +180,9 @@ export class MuseAdapter extends ProviderAdapter {
|
|
|
173
180
|
if ((permissions.approval ?? 'never') !== 'never') throw new Error('Muse direct worker approval requires never');
|
|
174
181
|
// Trust is a host-owned, qualification-bound control, like the schema.
|
|
175
182
|
// Brief contents and CLI defaults cannot opt into workspace instructions.
|
|
183
|
+
// Tier 0 binds ["--trust-workspace"] because the workspace is always a
|
|
184
|
+
// conductor-prepared worker tree (#3685); untrusted trees stay refused
|
|
185
|
+
// at dispatch by the operator native-argv gate.
|
|
176
186
|
let trust;
|
|
177
187
|
try { trust = JSON.parse(profile.controls.trust.mapping); } catch { /* fail closed below */ }
|
|
178
188
|
if (!Array.isArray(trust) || !(trust.length === 0 || trust.length === 1 && trust[0] === '--trust-workspace')) {
|
|
@@ -1,220 +1,14 @@
|
|
|
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
1
|
import { spawn } from 'child_process';
|
|
13
2
|
import { importImpl } from '../../_resolve-impl.mjs';
|
|
14
3
|
import { createWorkerHealthObserver, observeProviderHealthEvent } from './worker-health.mjs';
|
|
15
4
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
}
|
|
5
|
+
import { parseStrictWorkerJson, StrictJsonScanError } from './strict-worker-json.mjs';
|
|
6
|
+
import { classifySessionAttempt } from './session-attempt.mjs';
|
|
204
7
|
|
|
205
8
|
const installedOwner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
|
|
206
9
|
export const { workerNegativeFixtureSuite, workerNegativeFixtureDigest, registerProvider, createProvider, listProviders, hasProvider } = installedOwner;
|
|
207
10
|
|
|
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
|
-
*/
|
|
11
|
+
/** The optional Muse adapter is independent of provider deployment. */
|
|
218
12
|
export function isMuseRalphEnabled() {
|
|
219
13
|
const raw = String(process.env.AIWG_MUSE_RALPH_ENABLED ?? '').trim().toLowerCase();
|
|
220
14
|
return !['0', 'false', 'no', 'off'].includes(raw);
|
|
@@ -661,24 +455,15 @@ export class ProviderAdapter {
|
|
|
661
455
|
return null;
|
|
662
456
|
}
|
|
663
457
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
* @param {string} [context] - Context for the warning message
|
|
669
|
-
* @returns {boolean}
|
|
670
|
-
*/
|
|
458
|
+
classifySessionAttempt(request) {
|
|
459
|
+
return classifySessionAttempt(this, request);
|
|
460
|
+
}
|
|
461
|
+
|
|
671
462
|
hasCapability(capability) {
|
|
672
463
|
const caps = this.getCapabilities();
|
|
673
464
|
return !!caps[capability];
|
|
674
465
|
}
|
|
675
466
|
|
|
676
|
-
/**
|
|
677
|
-
* Log a capability warning when a feature is not supported.
|
|
678
|
-
*
|
|
679
|
-
* @param {string} capability - Capability name
|
|
680
|
-
* @param {string} feature - Human-readable feature description
|
|
681
|
-
*/
|
|
682
467
|
warnUnsupported(capability, feature) {
|
|
683
468
|
if (!this.hasCapability(capability)) {
|
|
684
469
|
console.warn(
|
|
@@ -687,52 +472,18 @@ export class ProviderAdapter {
|
|
|
687
472
|
}
|
|
688
473
|
}
|
|
689
474
|
}
|
|
690
|
-
|
|
691
|
-
// ============================================================================
|
|
692
|
-
// Provider Registry & Factory
|
|
693
|
-
// ============================================================================
|
|
694
|
-
|
|
695
475
|
export async function loadInstalledWorkerAdapters(request) {
|
|
696
476
|
await ensureProvidersRegistered();
|
|
697
477
|
return installedOwner.loadInstalledWorkerAdapters(request);
|
|
698
478
|
}
|
|
699
479
|
|
|
700
|
-
//
|
|
701
|
-
// Auto-register built-in providers on import
|
|
702
|
-
// ============================================================================
|
|
703
|
-
|
|
704
|
-
// Lazy imports to avoid circular dependencies - adapters register themselves
|
|
705
|
-
// when their modules are loaded. We trigger that here.
|
|
480
|
+
// Adapters register themselves; keep imports lazy to avoid registry cycles.
|
|
706
481
|
async function registerBuiltinProviders() {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
try {
|
|
711
|
-
await import('./codex-adapter.mjs');
|
|
712
|
-
} catch { /* ignore if not found */ }
|
|
713
|
-
try {
|
|
714
|
-
await import('./opencode-adapter.mjs');
|
|
715
|
-
} catch { /* ignore if not found */ }
|
|
716
|
-
try {
|
|
717
|
-
await import('./factory-adapter.mjs');
|
|
718
|
-
} catch { /* ignore if not found */ }
|
|
719
|
-
try {
|
|
720
|
-
await import('./pi-adapter.mjs');
|
|
721
|
-
await import('./omp-adapter.mjs');
|
|
722
|
-
} catch { /* ignore if not found */ }
|
|
723
|
-
try {
|
|
724
|
-
await import('./deepseek-harness-adapter.mjs');
|
|
725
|
-
} catch { /* ignore if not found */ }
|
|
726
|
-
// #230: optional / post-experimental. Disabled via AIWG_MUSE_RALPH_ENABLED=0
|
|
727
|
-
// without affecting `aiwg use --provider muse`.
|
|
728
|
-
if (isMuseRalphEnabled()) {
|
|
729
|
-
try {
|
|
730
|
-
await import('./muse-adapter.mjs');
|
|
731
|
-
} catch { /* ignore if not found */ }
|
|
482
|
+
for (const name of ['claude', 'codex', 'opencode', 'factory', 'pi', 'omp', 'deepseek-harness',
|
|
483
|
+
...(isMuseRalphEnabled() ? ['muse'] : [])]) {
|
|
484
|
+
try { await import(`./${name}-adapter.mjs`); } catch { /* Optional adapter unavailable. */ }
|
|
732
485
|
}
|
|
733
486
|
}
|
|
734
|
-
|
|
735
|
-
// Run registration — store promise so callers can await it
|
|
736
487
|
const _registrationPromise = registerBuiltinProviders();
|
|
737
488
|
|
|
738
489
|
/**
|