@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,315 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { ProviderAdapter, registerProvider } from './provider-adapter.mjs';
|
|
3
|
+
import { OmpFrameDecoder, summarizeOmpEvents, OmpRpcClient, MAX_FRAME, MAX_MESSAGE } from '../../providers/omp-transport.mjs';
|
|
4
|
+
import { createWorkerHealthObserver, healthEventMatchesBinding } from './worker-health.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Version-bound direct-worker surface (roctinam/aiwg#2910, B-OG-45).
|
|
8
|
+
*
|
|
9
|
+
* Qualified against `omp/18.8.7` `--help` (help/version syntax only,
|
|
10
|
+
* 2026-10-09, captured on noodle; never live qualification):
|
|
11
|
+
*
|
|
12
|
+
* omp --mode json --print --no-extensions --no-skills --no-rules
|
|
13
|
+
* --model M --thinking E --cwd W --approval-mode write < briefPath
|
|
14
|
+
*
|
|
15
|
+
* `--print`/`-p` ("Non-interactive mode: process prompt and exit") is kept
|
|
16
|
+
* explicit alongside `--mode json` because OMP's own `--help` lists it as a
|
|
17
|
+
* separate flag (unlike the evidenced Pi fork behavior where `--mode json`
|
|
18
|
+
* alone forces non-interactive): the direct profile never depends on an
|
|
19
|
+
* unverified inheritance assumption for this one invariant.
|
|
20
|
+
*
|
|
21
|
+
* `--approval-mode` is ALWAYS rendered explicitly and is NEVER omitted: a
|
|
22
|
+
* static read of the installed omp binary (`~/.local/bin/omp` on noodle,
|
|
23
|
+
* 18.8.7, noodle) shows the compiled-in default is `"yolo"`
|
|
24
|
+
* (`approvalMode: Uxa(s) ? s : "yolo"`) — a full approval/sandbox bypass —
|
|
25
|
+
* so an omitted flag would silently inherit the most dangerous mode, not a
|
|
26
|
+
* safe one. The same binary defines the mode->tier gate explicitly:
|
|
27
|
+
* `Kxa = { "always-ask": "read", write: "write", yolo: "exec" }`
|
|
28
|
+
* `PS = { read: 0, write: 1, exec: 2 }`
|
|
29
|
+
* `Xxa(mode, toolTier) { return PS[toolTier] <= PS[Kxa[mode]]; }`
|
|
30
|
+
* i.e. a tool call is auto-allowed only when its own tier rank is at or
|
|
31
|
+
* below the mode's threshold tier rank; anything above resolves to a
|
|
32
|
+
* `"prompt"`-required decision. `write`'s threshold is `write` (rank 1), so
|
|
33
|
+
* it auto-approves `read`/`write`-tier tools but an `exec`-tier tool
|
|
34
|
+
* (`bash`, the dominant tool for a coding worker) still resolves to
|
|
35
|
+
* `"prompt"` required. `write` is therefore NOT a complete non-prompting
|
|
36
|
+
* guarantee for this profile — it is only the least-prompting of the two
|
|
37
|
+
* non-bypass values (`always-ask`'s threshold is `read`, rank 0, so it
|
|
38
|
+
* prompts for `write` AND `exec`). `yolo` is never used (its threshold is
|
|
39
|
+
* `exec`, rank 2: every tier auto-allowed, i.e. a full bypass). No headless
|
|
40
|
+
* auto-resolution for a `"prompt"`-required decision was found in the
|
|
41
|
+
* binary (the one "Cannot prompt for confirmation (no TTY)" string in the
|
|
42
|
+
* binary belongs to Bun's own bundled package-install security-advisory
|
|
43
|
+
* prompt, not OMP's tool-approval gate); the changelog text compiled into
|
|
44
|
+
* the same binary separately describes ACP approval requests getting stuck
|
|
45
|
+
* `"pending"` when the channel "cannot answer" them. Exec-tier tool calls
|
|
46
|
+
* under `write` in print mode are therefore an OPEN GAP, not a qualified
|
|
47
|
+
* non-prompting path: the `approval` control stays `unqualified` and this
|
|
48
|
+
* gap is the reason, not a documentation placeholder. OMP has no
|
|
49
|
+
* `--approve/--no-approve` trust flag like Pi; the closest documented
|
|
50
|
+
* surface for unvetted project-local resources is discovery of
|
|
51
|
+
* extensions/skills/rules, so the direct profile always disables all three
|
|
52
|
+
* (`--no-extensions --no-skills --no-rules`) rather than trusting arbitrary
|
|
53
|
+
* workspace files. `--cwd` is explicit and documented (`--help`), unlike
|
|
54
|
+
* Pi, which has no such flag. There is no documented sandbox,
|
|
55
|
+
* output-file, output-schema, step-cap or child-denial flag (`omp --help`,
|
|
56
|
+
* 2026-10-09): those controls stay unavailable rather than invented, and
|
|
57
|
+
* the final answer is extracted from the stdout event stream
|
|
58
|
+
* (`parseOutput()`/`summarizeOmpEvents`), never a `-o`-style file.
|
|
59
|
+
*
|
|
60
|
+
* The brief travels on stdin, never argv — CONFIRMED by a static read of
|
|
61
|
+
* the installed omp binary (18.8.7, noodle), not merely inherited from
|
|
62
|
+
* Pi's lineage: `async function wUt()` (OMP's own `readPipedInput`) returns
|
|
63
|
+
* early when `process.stdin.isTTY === true`, otherwise awaits
|
|
64
|
+
* `Bun.stdin.text()` to EOF and returns the text unless it is
|
|
65
|
+
* all-whitespace; it runs whenever `mode` is not `rpc`/`rpc-ui`/`acp`
|
|
66
|
+
* (`--mode json` included: `const p = r === "rpc" || r === "rpc-ui" || r
|
|
67
|
+
* === "acp"; const c = p ? undefined : await readPipedInput();`), and its
|
|
68
|
+
* result is passed as `stdinContent` into OMP's own `buildInitialMessage`
|
|
69
|
+
* (`function aso({parsed, fileText, stdinContent}) { ... }`), which
|
|
70
|
+
* prefixes it onto the initial message exactly like Pi's equivalent
|
|
71
|
+
* function. Unlike Pi's `readPipedStdin`, OMP's `readPipedInput` does not
|
|
72
|
+
* trim the returned text (only the emptiness check is trimmed), so a brief
|
|
73
|
+
* with leading/trailing whitespace is NOT altered by OMP (a difference
|
|
74
|
+
* from Pi, noted since the shared fixture briefs intentionally carry
|
|
75
|
+
* leading/trailing whitespace).
|
|
76
|
+
*/
|
|
77
|
+
export const OMP_DIRECT_VERSION = '18.8.7';
|
|
78
|
+
/** Mirrors providers.omp.effortValues in model-capabilities.v1.json (hardcoded,
|
|
79
|
+
* not read via a repo-relative path: the installed-module loader copies this
|
|
80
|
+
* file to an arbitrary package root, so import.meta.url-relative reads to the
|
|
81
|
+
* repository are not safe here). */
|
|
82
|
+
export const OMP_DIRECT_EFFORT_VALUES = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
|
83
|
+
/** Explicit, non-bypassing approval only (`yolo` refused; OMP's unset default is yolo). `write`
|
|
84
|
+
* auto-approves read/write tools; exec-tier (bash) approvals have no confirmed headless
|
|
85
|
+
* resolution, so the approval control stays unqualified. */
|
|
86
|
+
export const OMP_DIRECT_APPROVAL_MODES = Object.freeze(['write']);
|
|
87
|
+
/** Brief transport for the direct profile: piped stdin, never argv. */
|
|
88
|
+
export const OMP_DIRECT_STDIN = 'brief-text';
|
|
89
|
+
|
|
90
|
+
/** Oh My Pi's native flags/events intentionally differ from Pi. */
|
|
91
|
+
export class OmpAdapter extends ProviderAdapter {
|
|
92
|
+
createHealthObserver(options = {}) {
|
|
93
|
+
// Reuse OMP's owned v2 chunk grammar. Neither another CLI's framing nor
|
|
94
|
+
// the strict worker-final parser is a substitute for native reassembly.
|
|
95
|
+
const messageLimit = Math.min(options.maxMessageBytes ?? 4 * MAX_FRAME, MAX_MESSAGE);
|
|
96
|
+
let decoder = new OmpFrameDecoder({ messageLimit });
|
|
97
|
+
let lastSource = null;
|
|
98
|
+
const observer = createWorkerHealthObserver({ ...options, provider: this.getName(),
|
|
99
|
+
maxFrameBytes: options.maxFrameBytes ?? MAX_FRAME,
|
|
100
|
+
normalizeEvent: (frame, source, binding) => {
|
|
101
|
+
if (source.kind !== 'stdout') return null;
|
|
102
|
+
const identity = `${source.id}:${source.generation}`;
|
|
103
|
+
if (lastSource !== null && lastSource !== identity) decoder = new OmpFrameDecoder({ messageLimit });
|
|
104
|
+
lastSource = identity;
|
|
105
|
+
try {
|
|
106
|
+
const event = decoder.decode(frame);
|
|
107
|
+
return event ? this.observeHealthEvent(event, source, binding) : null;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
decoder = new OmpFrameDecoder({ messageLimit });
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const finish = observer.finish;
|
|
115
|
+
observer.finish = observation => {
|
|
116
|
+
try { decoder.end(); } catch { observer.gap('MALFORMED_FRAME'); }
|
|
117
|
+
return finish(observation);
|
|
118
|
+
};
|
|
119
|
+
return observer;
|
|
120
|
+
}
|
|
121
|
+
observeHealthEvent(event, source, binding) {
|
|
122
|
+
if (source.kind !== 'stdout' || !event || typeof event !== 'object') return null;
|
|
123
|
+
if (!healthEventMatchesBinding({ sessionId: event.sessionId }, binding)) return null;
|
|
124
|
+
if (event.type === 'agent_start') return { kind: 'accepted' };
|
|
125
|
+
if (event.type === 'tool_execution_end') {
|
|
126
|
+
if (event.isError === true) return { kind: 'error', reasonCode: 'tool-failed' };
|
|
127
|
+
if (typeof event.toolCallId === 'string') return { kind: 'progress', key: event.toolCallId };
|
|
128
|
+
return { kind: 'activity' };
|
|
129
|
+
}
|
|
130
|
+
if (event.type === 'agent_end') {
|
|
131
|
+
if (Array.isArray(event.messages) && event.messages.some(message => message.stopReason === 'error' || message.stopReason === 'aborted')) return { kind: 'error', reasonCode: 'provider-failed' };
|
|
132
|
+
return event.willContinue === true ? { kind: 'activity' } : { kind: 'terminal', success: true, key: 'agent_end' };
|
|
133
|
+
}
|
|
134
|
+
if (event.type === 'rpc_frame_error' || event.type === 'error') return { kind: 'error', reasonCode: 'provider-failed' };
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
async getVersion() { const result = spawnSync(this.getBinary(), ['--version'], { encoding: 'utf8', timeout: 5000, maxBuffer: 65536 }); return result.status === 0 ? result.stdout.trim() : null; }
|
|
138
|
+
async isAvailable() { return Boolean(await this.getVersion()); }
|
|
139
|
+
getName() { return 'omp'; }
|
|
140
|
+
getBinary() { return process.env.AIWG_OMP_BIN || 'omp'; }
|
|
141
|
+
getCapabilities() { return { streamJson: true, sessionResume: true, budgetControl: false, systemPrompt: true, agentMode: false, mcpConfig: false, maxTurns: false, rpcAbort: true }; }
|
|
142
|
+
buildSessionArgs(options) {
|
|
143
|
+
const args = ['--mode', 'json'];
|
|
144
|
+
if (options.profile) args.push('--profile', options.profile);
|
|
145
|
+
for (const config of options.config ?? []) args.push('--config', config);
|
|
146
|
+
if (options.model) args.push('--model', this.mapModel(options.model));
|
|
147
|
+
if (options.thinking) args.push('--thinking', options.thinking);
|
|
148
|
+
if (options.sessionId) args.push('--session', options.sessionId);
|
|
149
|
+
if (options.sessionDir) args.push('--session-dir', options.sessionDir);
|
|
150
|
+
if (options.noSession) args.push('--no-session');
|
|
151
|
+
if (options.noExtensions) args.push('--no-extensions');
|
|
152
|
+
if (options.tools) { if (options.tools.length) args.push('--tools', options.tools.join(',')); else args.push('--no-tools'); }
|
|
153
|
+
if (options.systemPrompt) args.push('--append-system-prompt', options.systemPrompt);
|
|
154
|
+
for (const [option, capability] of [['budget', 'budgetControl'], ['maxTurns', 'maxTurns'], ['mcpConfig', 'mcpConfig']]) if (options[option]) this.warnUnsupported(capability, option);
|
|
155
|
+
args.push('--', options.prompt);
|
|
156
|
+
return args;
|
|
157
|
+
}
|
|
158
|
+
buildAnalysisArgs(options) { return this.buildSessionArgs(options); }
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Driver-owned argv after the binary for the version-bound direct-shell
|
|
162
|
+
* profile. Model, thinking level and workdir are always explicit (never
|
|
163
|
+
* a CLI default or alias); approval is pinned explicitly to `write` (never
|
|
164
|
+
* left unset, which would be yolo; exec-tier approvals stay unresolved); extension/skill/rule discovery is
|
|
165
|
+
* always disabled (no `--approve`-style trust flag exists on OMP); the
|
|
166
|
+
* brief is validated here but never placed in argv — it travels on
|
|
167
|
+
* stdin (`stdin: 'brief-text'`).
|
|
168
|
+
*
|
|
169
|
+
* @param {{ selected?: { model?: unknown, effort?: { value?: unknown } }, cwd?: unknown, brief?: unknown }} invocation
|
|
170
|
+
* @param {unknown} _profile
|
|
171
|
+
* @param {{ approval?: unknown }} [permissions]
|
|
172
|
+
* @returns {string[]}
|
|
173
|
+
*/
|
|
174
|
+
buildWorkerArgs(invocation, _profile, permissions) {
|
|
175
|
+
const model = invocation?.selected?.model;
|
|
176
|
+
if (typeof model !== 'string' || !model || model.includes('\0')) {
|
|
177
|
+
throw new Error('OMP worker requires an explicit model');
|
|
178
|
+
}
|
|
179
|
+
const effort = invocation?.selected?.effort?.value ?? null;
|
|
180
|
+
if (effort === null || !OMP_DIRECT_EFFORT_VALUES.includes(effort)) {
|
|
181
|
+
throw new Error('OMP worker requires an explicit qualified thinking level (never a CLI default)');
|
|
182
|
+
}
|
|
183
|
+
const cwd = invocation?.cwd;
|
|
184
|
+
if (typeof cwd !== 'string' || !cwd.startsWith('/') || cwd.includes('\0')) {
|
|
185
|
+
throw new Error('OMP worker requires an absolute workspace');
|
|
186
|
+
}
|
|
187
|
+
// The shared resolved-permission vocabulary's 'never' ("never require
|
|
188
|
+
// approval") is the only input that maps to OMP's explicit, non-bypassing
|
|
189
|
+
// `write` mode (exec-tier approvals unresolved headless; see module doc); 'always'/'on-request' have no equivalent
|
|
190
|
+
// without prompting or a full sandbox/approval bypass and are refused.
|
|
191
|
+
const requestedApproval = permissions?.approval ?? 'never';
|
|
192
|
+
if (requestedApproval !== 'never') {
|
|
193
|
+
throw new Error(`OMP worker approval is not a qualified direct mode: ${String(requestedApproval)}`);
|
|
194
|
+
}
|
|
195
|
+
const approval = OMP_DIRECT_APPROVAL_MODES[0];
|
|
196
|
+
const brief = invocation?.brief;
|
|
197
|
+
if (typeof brief !== 'string' || !brief.trim() || brief.includes('\0')) {
|
|
198
|
+
throw new Error('OMP worker requires a non-empty brief');
|
|
199
|
+
}
|
|
200
|
+
return ['--mode', 'json', '--print', '--no-extensions', '--no-skills', '--no-rules',
|
|
201
|
+
'--model', model, '--thinking', effort, '--cwd', cwd, '--approval-mode', approval];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
mapModel(model) { return model; }
|
|
205
|
+
getEnvOverrides() { return { CI: 'true', NO_COLOR: '1' }; }
|
|
206
|
+
// JSON print mode has no stdin abort protocol; runner terminates its process.
|
|
207
|
+
getAbortInput() { return null; }
|
|
208
|
+
createRpcClient(options = {}) { return new OmpRpcClient({ binary: this.getBinary(), ...options }); }
|
|
209
|
+
getTranscriptPath(sessionId) { return sessionId?.endsWith('.jsonl') ? sessionId : null; }
|
|
210
|
+
parseOutput(stdout, options = {}) {
|
|
211
|
+
if (Buffer.byteLength(stdout) > MAX_MESSAGE) return null;
|
|
212
|
+
try { const decoder = new OmpFrameDecoder({ frameLimit: MAX_MESSAGE }); const events = decoder.push(Buffer.from(stdout)); decoder.end(); return events.length ? summarizeOmpEvents(events, options) : null; } catch { return null; }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* #2995 round 3: strict direct-worker-profile final, separate from the
|
|
217
|
+
* legacy `parseOutput`/`summarizeOmpEvents` candidate above (that SHARED
|
|
218
|
+
* helper is never changed here — module doc). `summarizeOmpEvents`
|
|
219
|
+
* tracks the LAST assistant message across every event with a
|
|
220
|
+
* `messages`/`message` field, including `tool_execution_end.message`,
|
|
221
|
+
* so it can retain a stale or tool-carried answer when the real terminal
|
|
222
|
+
* `agent_end` is answerless. This rule instead: (1) only reads the
|
|
223
|
+
* assistant answer from the LAST terminal `agent_end` (`willContinue !==
|
|
224
|
+
* true`) itself, never from any other event type; (2) a non-terminal
|
|
225
|
+
* `agent_end` (`willContinue: true`) clears any earlier candidate text,
|
|
226
|
+
* since it is not the run's actual final answer; (3) ANY event observed
|
|
227
|
+
* after the terminal `agent_end` fails the parse, mirroring Factory/
|
|
228
|
+
* Cursor's structural "nothing follows the terminal" rule.
|
|
229
|
+
* @param {string|Uint8Array} stdout
|
|
230
|
+
* @returns {{ text: string, success: true } | null}
|
|
231
|
+
*/
|
|
232
|
+
parseDirectFinal(stdout) {
|
|
233
|
+
if (Buffer.byteLength(stdout) > MAX_MESSAGE) return null;
|
|
234
|
+
let events;
|
|
235
|
+
try {
|
|
236
|
+
const decoder = new OmpFrameDecoder({ frameLimit: MAX_MESSAGE });
|
|
237
|
+
events = decoder.push(Buffer.from(stdout));
|
|
238
|
+
decoder.end();
|
|
239
|
+
} catch { return null; }
|
|
240
|
+
if (!events.length) return null;
|
|
241
|
+
let terminal = null, text = null, failed = false, aborted = false, elided = false, afterTerminal = false;
|
|
242
|
+
for (const event of events) {
|
|
243
|
+
if (/elided for RPC frame|rpcFrameElidedKeys/.test(JSON.stringify(event))) elided = true;
|
|
244
|
+
if (terminal) { afterTerminal = true; continue; }
|
|
245
|
+
if (event.type === 'rpc_frame_error' || event.type === 'error') { failed = true; continue; }
|
|
246
|
+
if (event.type === 'tool_execution_end' && event.isError) { failed = true; continue; }
|
|
247
|
+
if (event.type === 'agent_end' && event.willContinue === true) { text = null; continue; }
|
|
248
|
+
if (event.type === 'agent_end' && event.willContinue !== true) {
|
|
249
|
+
terminal = event;
|
|
250
|
+
const assistant = Array.isArray(event.messages) ? event.messages.filter(message => message.role === 'assistant').at(-1) : undefined;
|
|
251
|
+
if (!assistant) { text = null; continue; }
|
|
252
|
+
if (assistant.stopReason === 'error') failed = true;
|
|
253
|
+
if (assistant.stopReason === 'aborted') aborted = true;
|
|
254
|
+
text = Array.isArray(assistant.content) ? assistant.content.filter(part => part.type === 'text').map(part => part.text).join('\n') : null;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (!terminal || afterTerminal || failed || aborted || elided) return null;
|
|
259
|
+
if (!text || !text.trim()) return null;
|
|
260
|
+
return { text, success: true };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Self-register on import for the legacy Ralph loop only. The direct worker
|
|
264
|
+
// profile loads through the #2885 installed loader instead (see below).
|
|
265
|
+
registerProvider('omp', () => new OmpAdapter());
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* #2995 round 3: `OmpDirectAdapter` is the Claude-precedent `-direct`
|
|
269
|
+
* subclass, registered under its OWN id (`omp-direct`, never the bare
|
|
270
|
+
* `omp` legacy id above) so loading this installed module cannot overwrite
|
|
271
|
+
* the legacy registration in-process (src/models/worker-installation.mjs
|
|
272
|
+
* ~l.550). `parseOutput` here is the strict `parseDirectFinal` rule (never
|
|
273
|
+
* the loose, generic-accepting legacy candidate), and `parseWorkerFinal`
|
|
274
|
+
* is strict with no generic-envelope fallback: this class is never
|
|
275
|
+
* reachable from the legacy bare id, so the legacy group-mode contract
|
|
276
|
+
* (test/unit/ralph-external/worker-finals.test.ts) never sees it.
|
|
277
|
+
*/
|
|
278
|
+
export class OmpDirectAdapter extends OmpAdapter {
|
|
279
|
+
// #2995 round 4 (item 3): production resolves the installed adapter by
|
|
280
|
+
// `adapter.id` (now `omp-direct`, item B) and src/models/
|
|
281
|
+
// worker-installation.mjs's `inspectWorkerInstallation` requires
|
|
282
|
+
// `adapter.id === instance.getName()`. `getBinary()` is untouched; the
|
|
283
|
+
// inherited `createHealthObserver` override's `provider: this.getName()`
|
|
284
|
+
// label changes too, but it only feeds the shared observer's stderr
|
|
285
|
+
// classifier/report label -- `observeHealthEvent` below (the actual
|
|
286
|
+
// event dispatch) is unaffected.
|
|
287
|
+
getName() { return 'omp-direct'; }
|
|
288
|
+
|
|
289
|
+
parseOutput(stdout) {
|
|
290
|
+
return this.parseDirectFinal(stdout);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* @param {string|Uint8Array} output
|
|
295
|
+
* @param {Object} [options]
|
|
296
|
+
* @returns {Promise<import('../../../src/mission-protocol/types.js').WorkerClaims>}
|
|
297
|
+
*/
|
|
298
|
+
async parseWorkerFinal(output, options = {}) {
|
|
299
|
+
if (options.format === 'mcp') return super.parseWorkerFinal(output, options);
|
|
300
|
+
const text = typeof output === 'string' ? output : Buffer.from(output).toString('utf8');
|
|
301
|
+
const final = this.parseDirectFinal(text);
|
|
302
|
+
if (!final) throw new Error('OMP worker stream did not produce a successful final answer');
|
|
303
|
+
return super.parseWorkerFinal(final.text, { ...options, format: 'json' });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Installed-module interface version (mirrors #2897 codex-adapter.mjs). */
|
|
308
|
+
export const interfaceVersion = 1;
|
|
309
|
+
|
|
310
|
+
/** Installed-module registration: exactly the one approved adapter ID. */
|
|
311
|
+
export function register(factory) {
|
|
312
|
+
factory('omp-direct', () => new OmpDirectAdapter());
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default OmpAdapter;
|