@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,333 @@
|
|
|
1
|
+
/** Pi Coding Agent adapter for headless External Ralph sessions. */
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { ProviderAdapter, registerProvider } from './provider-adapter.mjs';
|
|
6
|
+
import { healthEventMatchesBinding } from './worker-health.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Version-bound direct-worker surface (roctinam/aiwg#2910, B-OG-45).
|
|
10
|
+
*
|
|
11
|
+
* Qualified against pi 1.1.0 `--help` (help/version syntax only,
|
|
12
|
+
* 2026-10-09; never live qualification; upgraded from the originally
|
|
13
|
+
* qualified 0.87.1 -- the `--help` diff at
|
|
14
|
+
* `.aiwg/working/s1-scratch/cli-help/pi-diff.txt` shows every flag this
|
|
15
|
+
* profile emits is unchanged, plus a new `--no-mcp`):
|
|
16
|
+
*
|
|
17
|
+
* pi --mode json --no-approve --no-context-files --no-extensions \
|
|
18
|
+
* --no-skills --no-mcp --model M --thinking E < briefPath
|
|
19
|
+
*
|
|
20
|
+
* `--mode json` forces non-interactive JSONL output regardless of TTY or
|
|
21
|
+
* `--print` (installed pi 1.1.0 bundle, `resolveAppMode()`: `parsed.mode
|
|
22
|
+
* === "json"` short-circuits to `"json"` before any print/interactive
|
|
23
|
+
* check), so no separate `-p/--print` flag is needed. `--no-approve`
|
|
24
|
+
* ("Ignore project-local files for this run") alone is NOT a complete
|
|
25
|
+
* isolation boundary: it sets project trust to false but does not stop
|
|
26
|
+
* context loading. The installed pi 1.1.0 bundle (`chunk-OIM2DMFI.js`,
|
|
27
|
+
* static read 2026-10-09: `agentsFiles = this.noContextFiles ? [] :
|
|
28
|
+
* loadProjectContextFiles(...)`) loads ancestor `AGENTS.md`/`CLAUDE.md`
|
|
29
|
+
* files independently of trust unless `noContextFiles` is set. The direct
|
|
30
|
+
* profile therefore also emits `--no-context-files`
|
|
31
|
+
* (`-nc`, "Disable AGENTS.md and CLAUDE.md discovery and loading"),
|
|
32
|
+
* `--no-extensions` (`-ne`) and `--no-skills` (`-ns`) -- the same discovery
|
|
33
|
+
* surfaces OMP's profile disables -- so no untrusted workspace file
|
|
34
|
+
* (context, extension or skill) is ever loaded into the worker. 1.1.0
|
|
35
|
+
* adds built-in MCP support, on by default; the direct profile also emits
|
|
36
|
+
* `--no-mcp` ("Disable built-in MCP support: no servers connect and no
|
|
37
|
+
* MCP tools", `pi --help`) for the same reason -- an unvetted MCP server
|
|
38
|
+
* configuration is an untrusted-workspace surface like
|
|
39
|
+
* extensions/skills, and the AIWG `worker-invoke` bridge remains the one
|
|
40
|
+
* MCP route. The direct profile never emits `--approve` ("Trust
|
|
41
|
+
* project-local files for this run"). There is no documented sandbox,
|
|
42
|
+
* approval-prompt, output-file, output-schema, step-cap or child-denial
|
|
43
|
+
* flag (`pi --help`, 2026-10-09): those controls stay unavailable rather
|
|
44
|
+
* than invented, and the final answer is extracted from the stdout JSONL
|
|
45
|
+
* stream (`parseOutput()`), never a `-o`-style file. There is no
|
|
46
|
+
* `--cwd`/`-C` flag either; the launcher's shell `cd` to the workspace is
|
|
47
|
+
* the only workdir binding. The brief travels on stdin, never argv:
|
|
48
|
+
* installed pi 1.1.0 bundle (`chunk-OIM2DMFI.js`, `readPipedStdin()` /
|
|
49
|
+
* `buildInitialMessage()`, static read 2026-10-09, byte-identical to the
|
|
50
|
+
* 0.87.1 evidence) shows piped, non-TTY stdin is trimmed and becomes the
|
|
51
|
+
* sole initial-message part whenever no positional message/file argument
|
|
52
|
+
* is given, so omitting the positional message and piping the brief is
|
|
53
|
+
* sufficient -- and an all-whitespace brief would trim to nothing and
|
|
54
|
+
* must never be sent.
|
|
55
|
+
*/
|
|
56
|
+
export const PI_DIRECT_VERSION = '1.1.0';
|
|
57
|
+
/** Mirrors providers.pi.effortValues in model-capabilities.v1.json (hardcoded,
|
|
58
|
+
* not read via a repo-relative path: the installed-module loader copies this
|
|
59
|
+
* file to an arbitrary package root, so import.meta.url-relative reads to the
|
|
60
|
+
* repository are not safe here). */
|
|
61
|
+
export const PI_DIRECT_EFFORT_VALUES = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
|
62
|
+
/** Brief transport for the direct profile: piped stdin, never argv. */
|
|
63
|
+
export const PI_DIRECT_STDIN = 'brief-text';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pi CLI versions this adapter is qualified against. `isAvailable()` fails
|
|
67
|
+
* closed outside this list (#2550): Pi's `--mode json` contract (stdin
|
|
68
|
+
* handling, JSONL framing, `agent_settled`) is verified per version by
|
|
69
|
+
* `npm run smoke:pi:live`, and an unqualified release must not be driven by
|
|
70
|
+
* an unattended loop on the strength of `pi --version` exiting 0.
|
|
71
|
+
*/
|
|
72
|
+
export const PI_SUPPORTED_VERSIONS = Object.freeze(['0.85.0']);
|
|
73
|
+
|
|
74
|
+
export function normalizePiVersion(version) {
|
|
75
|
+
return String(version ?? '').trim().replace(/^pi\s+/i, '').replace(/^v/, '');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isSupportedPiVersion(version) {
|
|
79
|
+
return PI_SUPPORTED_VERSIONS.includes(normalizePiVersion(version));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class PiAdapter extends ProviderAdapter {
|
|
83
|
+
observeHealthEvent(event, source, binding) {
|
|
84
|
+
if (source.kind !== 'stdout' || !event || typeof event !== 'object') return null;
|
|
85
|
+
if (!healthEventMatchesBinding({ sessionId: event.sessionId ?? (event.type === 'session' ? event.id : undefined) }, binding)) return null;
|
|
86
|
+
if (event.type === 'session') return { kind: 'setup', sessionId: event.id };
|
|
87
|
+
if (event.type === 'agent_start') return { kind: 'accepted' };
|
|
88
|
+
if (event.type === 'agent_end') {
|
|
89
|
+
if (event.willRetry === true || (Array.isArray(event.messages) && event.messages.some(message => message.stopReason === 'error' || message.stopReason === 'aborted'))) {
|
|
90
|
+
return { kind: 'error', reasonCode: 'provider-failed' };
|
|
91
|
+
}
|
|
92
|
+
// agent_end can precede retry/continuation: settlement requires Pi's
|
|
93
|
+
// own agent_settled event. Completed output can still be progress.
|
|
94
|
+
if (Array.isArray(event.messages) && event.messages.some(message => message.role === 'assistant')) {
|
|
95
|
+
return { kind: 'progress', key: JSON.stringify(event.messages) };
|
|
96
|
+
}
|
|
97
|
+
return { kind: 'activity' };
|
|
98
|
+
}
|
|
99
|
+
if (event.type === 'agent_settled') return { kind: 'terminal', success: true, key: 'agent_settled' };
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
getBinary() { return process.env.AIWG_PI_BIN || 'pi'; }
|
|
103
|
+
getName() { return 'pi'; }
|
|
104
|
+
getCapabilities() {
|
|
105
|
+
// rpcAbort is false on purpose. Pi 0.85.0 reads stdin commands only in
|
|
106
|
+
// `--mode rpc`; in `--mode json` it reads piped stdin as prompt text and
|
|
107
|
+
// blocks until EOF (readPipedStdin in dist/main.js). Opening a stdin pipe
|
|
108
|
+
// to send an abort frame therefore keeps the session from ever starting,
|
|
109
|
+
// so the launcher must leave stdin closed and cancel with the bounded
|
|
110
|
+
// TERM/KILL path (#2550).
|
|
111
|
+
return { streamJson: true, sessionResume: true, budgetControl: false,
|
|
112
|
+
systemPrompt: true, agentMode: false, mcpConfig: false, maxTurns: false,
|
|
113
|
+
rpcAbort: false };
|
|
114
|
+
}
|
|
115
|
+
async isAvailable() {
|
|
116
|
+
const node = spawnSync(process.execPath, ['-p', 'process.versions.node'], { encoding: 'utf8', timeout: 5000 });
|
|
117
|
+
const [major, minor] = String(node.stdout).trim().split('.').map(Number);
|
|
118
|
+
if (node.status !== 0 || major < 22 || (major === 22 && minor < 19)) return false;
|
|
119
|
+
if (!await super.isAvailable()) return false;
|
|
120
|
+
return isSupportedPiVersion(await this.getVersion());
|
|
121
|
+
}
|
|
122
|
+
buildSessionArgs(options) {
|
|
123
|
+
const args = ['--mode', 'json', '--no-approve'];
|
|
124
|
+
if (options.model) args.push('--model', this.mapModel(options.model));
|
|
125
|
+
if (options.thinking) args.push('--thinking', options.thinking);
|
|
126
|
+
if (options.sessionId) args.push('--session', options.sessionId);
|
|
127
|
+
if (options.tools?.length) args.push('--tools', options.tools.join(','));
|
|
128
|
+
if (options.systemPrompt) args.push('--append-system-prompt', options.systemPrompt);
|
|
129
|
+
if (options.budget) this.warnUnsupported('budgetControl', 'Budget control');
|
|
130
|
+
if (options.maxTurns) this.warnUnsupported('maxTurns', 'Max turns');
|
|
131
|
+
if (options.mcpConfig) this.warnUnsupported('mcpConfig', 'MCP configuration');
|
|
132
|
+
args.push(options.prompt);
|
|
133
|
+
return args;
|
|
134
|
+
}
|
|
135
|
+
buildAnalysisArgs(options) { return this.buildSessionArgs(options); }
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Driver-owned argv after the binary for the version-bound direct-shell
|
|
139
|
+
* profile. `--no-approve`, `--no-context-files`, `--no-extensions`,
|
|
140
|
+
* `--no-skills` and `--no-mcp` are all fixed (never `--approve`, and
|
|
141
|
+
* never omitted — `--no-approve` alone does not stop ancestor
|
|
142
|
+
* AGENTS.md/CLAUDE.md loading); model and thinking level are always
|
|
143
|
+
* explicit (never a CLI default or alias); the brief is validated here
|
|
144
|
+
* but never placed in
|
|
145
|
+
* argv — it travels on stdin (`stdin: 'brief-text'`). There is no
|
|
146
|
+
* `-C/--cwd` flag to render: the launcher's own `cd` to the workspace is
|
|
147
|
+
* the only binding.
|
|
148
|
+
*
|
|
149
|
+
* @param {{ selected?: { model?: unknown, effort?: { value?: unknown } }, cwd?: unknown, brief?: unknown }} invocation
|
|
150
|
+
* @param {unknown} _profile
|
|
151
|
+
* @param {unknown} _permissions
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
buildWorkerArgs(invocation, _profile, _permissions) {
|
|
155
|
+
const model = invocation?.selected?.model;
|
|
156
|
+
if (typeof model !== 'string' || !model || model.includes('\0')) {
|
|
157
|
+
throw new Error('Pi worker requires an explicit model');
|
|
158
|
+
}
|
|
159
|
+
const effort = invocation?.selected?.effort?.value ?? null;
|
|
160
|
+
if (effort === null || !PI_DIRECT_EFFORT_VALUES.includes(effort)) {
|
|
161
|
+
throw new Error('Pi worker requires an explicit qualified thinking level (never a CLI default)');
|
|
162
|
+
}
|
|
163
|
+
const cwd = invocation?.cwd;
|
|
164
|
+
if (typeof cwd !== 'string' || !cwd.startsWith('/') || cwd.includes('\0')) {
|
|
165
|
+
throw new Error('Pi worker requires an absolute workspace');
|
|
166
|
+
}
|
|
167
|
+
const brief = invocation?.brief;
|
|
168
|
+
// Pi's own readPipedStdin() trims the stream; an all-whitespace brief
|
|
169
|
+
// would resolve to no initial message at all, so it is refused here.
|
|
170
|
+
if (typeof brief !== 'string' || !brief.trim() || brief.includes('\0')) {
|
|
171
|
+
throw new Error('Pi worker requires a non-empty brief');
|
|
172
|
+
}
|
|
173
|
+
return ['--mode', 'json', '--no-approve', '--no-context-files', '--no-extensions', '--no-skills', '--no-mcp',
|
|
174
|
+
'--model', model, '--thinking', effort];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
mapModel(model) { return model; }
|
|
178
|
+
getEnvOverrides() { return { CI: 'true', NO_COLOR: '1' }; }
|
|
179
|
+
/** No stdin abort in `--mode json`; see getCapabilities() (#2550). */
|
|
180
|
+
getAbortInput() { return null; }
|
|
181
|
+
getTranscriptPath(sessionId) {
|
|
182
|
+
if (!sessionId) return null;
|
|
183
|
+
if (sessionId.endsWith('.jsonl') || sessionId.includes('/')) return sessionId;
|
|
184
|
+
const root = process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
185
|
+
return root ? join(root, `${sessionId}.jsonl`) : join(homedir(), '.pi', 'agent', 'sessions', `${sessionId}.jsonl`);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Legacy Ralph-loop contract (#2550), unchanged: parse `--mode json`
|
|
189
|
+
* output into Pi's own JSONL events and report settlement only.
|
|
190
|
+
* `settled` is an `agent_settled` event with no `agent_end` signalling
|
|
191
|
+
* `willRetry: true`. This is the exact shape
|
|
192
|
+
* `test/unit/ralph/pi-adapter.test.mjs` and the launcher depend on — it
|
|
193
|
+
* does NOT extract or require a final answer; see `parseDirectFinal()`
|
|
194
|
+
* for the strict direct-worker-profile contract that does.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} stdout
|
|
197
|
+
* @returns {{ events: object[], settled: boolean } | null}
|
|
198
|
+
*/
|
|
199
|
+
parseOutput(stdout) {
|
|
200
|
+
const events = [];
|
|
201
|
+
for (const raw of stdout.split('\n')) {
|
|
202
|
+
const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
|
|
203
|
+
if (!line) continue;
|
|
204
|
+
try { events.push(JSON.parse(line)); } catch { return null; }
|
|
205
|
+
}
|
|
206
|
+
return events.length ? { events, settled: events.some(event => event.type === 'agent_settled')
|
|
207
|
+
&& !events.some(event => event.type === 'agent_end' && event.willRetry === true) } : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Strict direct-worker-profile final: reuses `parseOutput()`'s event
|
|
212
|
+
* parsing, then additionally requires a non-empty extracted assistant
|
|
213
|
+
* answer and fails on any error signal (a standalone `error` event, an
|
|
214
|
+
* `agent_end` with `willRetry: true`, or an `agent_end` assistant
|
|
215
|
+
* message with `stopReason` `error`/`aborted`) even at exit zero. `text`
|
|
216
|
+
* is the last assistant message's concatenated text content across all
|
|
217
|
+
* `agent_end` events (a later `agent_end` can supersede an earlier one
|
|
218
|
+
* before `agent_settled` arrives); `text` is non-null only when
|
|
219
|
+
* `settled` is true. Used only by the direct worker profile
|
|
220
|
+
* (roctinam/aiwg#2910) and the installed-module path — never the legacy
|
|
221
|
+
* Ralph loop, which calls `parseOutput()` directly.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} stdout
|
|
224
|
+
* @returns {{ events: object[], settled: boolean, text: string|null } | null}
|
|
225
|
+
*/
|
|
226
|
+
parseDirectFinal(stdout) {
|
|
227
|
+
const events = [];
|
|
228
|
+
for (const raw of stdout.split('\n')) {
|
|
229
|
+
const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
|
|
230
|
+
if (!line) continue;
|
|
231
|
+
try { events.push(JSON.parse(line)); } catch { return null; }
|
|
232
|
+
}
|
|
233
|
+
if (!events.length) return null;
|
|
234
|
+
let failed = false;
|
|
235
|
+
let settledEvent = false;
|
|
236
|
+
let text = null;
|
|
237
|
+
for (const event of events) {
|
|
238
|
+
// #2995 round 3: `agent_settled` is documented as terminal; ANY event
|
|
239
|
+
// observed after it (including a second agent_start) breaks that
|
|
240
|
+
// contract and fails the parse, exactly like Factory/Cursor's
|
|
241
|
+
// post-terminal check.
|
|
242
|
+
if (settledEvent) { failed = true; continue; }
|
|
243
|
+
if (event.type === 'error') failed = true;
|
|
244
|
+
if (event.type === 'agent_settled') { settledEvent = true; continue; }
|
|
245
|
+
if (event.type !== 'agent_end') continue;
|
|
246
|
+
// #2995 round 3: an `agent_end` with NO `messages` array at all (not
|
|
247
|
+
// just an explicit empty array) must CLEAR any earlier candidate
|
|
248
|
+
// text -- never let an earlier claim survive a later answerless
|
|
249
|
+
// terminal.
|
|
250
|
+
if (!Array.isArray(event.messages)) { text = null; continue; }
|
|
251
|
+
if (event.willRetry === true || event.messages.some(message => message.stopReason === 'error' || message.stopReason === 'aborted')) {
|
|
252
|
+
failed = true;
|
|
253
|
+
}
|
|
254
|
+
// Pi accumulates messages chronologically: the final answer is the LAST
|
|
255
|
+
// assistant message of the LAST agent_end. An earlier non-empty message
|
|
256
|
+
// must never stand in for an empty final one.
|
|
257
|
+
const assistant = event.messages.filter(message => message.role === 'assistant').at(-1);
|
|
258
|
+
if (!assistant) { text = null; continue; }
|
|
259
|
+
const extracted = Array.isArray(assistant.content)
|
|
260
|
+
? assistant.content.filter(part => part.type === 'text').map(part => part.text).join('\n')
|
|
261
|
+
: (typeof assistant.text === 'string' ? assistant.text : null);
|
|
262
|
+
text = extracted && extracted.trim() ? extracted : null;
|
|
263
|
+
}
|
|
264
|
+
const settled = settledEvent && !failed && Boolean(text);
|
|
265
|
+
return { events, settled, text: settled ? text : null };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Self-register on import for the legacy Ralph loop only. The direct worker
|
|
271
|
+
// profile loads through the #2885 installed loader instead (see below).
|
|
272
|
+
registerProvider('pi', () => new PiAdapter());
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* #2995: `PiAdapter.parseOutput` above is the legacy-pinned, settlement-only
|
|
276
|
+
* candidate parser (`test/unit/ralph/pi-adapter.test.mjs` and the legacy
|
|
277
|
+
* Ralph loop depend on its exact `{events,settled}` shape, with no answer
|
|
278
|
+
* requirement at all) — it is non-null whenever the stream parses as JSONL,
|
|
279
|
+
* regardless of whether the run actually settled successfully with a
|
|
280
|
+
* non-empty answer. The installed-module loader's `extractFinal` hook IS
|
|
281
|
+
* `instance.parseOutput` (src/models/worker-installation.mjs ~l.730) and
|
|
282
|
+
* session-launcher.mjs's gate (~l.462) throws unless that hook returns
|
|
283
|
+
* null for anything that is not an affirmative success — so wiring the
|
|
284
|
+
* legacy parser there would let an unsettled or empty-answer run pass the
|
|
285
|
+
* gate as a false candidate.
|
|
286
|
+
*
|
|
287
|
+
* `PiDirectAdapter` is the Claude/Hermes `-direct`-subclass pattern: only
|
|
288
|
+
* `parseOutput` changes, to a thin wrapper over the ALREADY-correct
|
|
289
|
+
* `parseDirectFinal` (above) that returns null unless `settled` is true
|
|
290
|
+
* AND `text` is a non-empty string — never a second, looser rule.
|
|
291
|
+
* `parseWorkerFinal` only needs the strict version here (no
|
|
292
|
+
* generic-envelope fallback): this class is never reachable through the
|
|
293
|
+
* legacy bare `registerProvider('pi', ...)` id above, only through the
|
|
294
|
+
* installed-module `register(factory)` export below, so the legacy
|
|
295
|
+
* group-mode contract (test/unit/ralph-external/worker-finals.test.ts)
|
|
296
|
+
* never sees it.
|
|
297
|
+
*/
|
|
298
|
+
export class PiDirectAdapter extends PiAdapter {
|
|
299
|
+
// #2995 round 4 (item 3): production resolves the installed adapter by
|
|
300
|
+
// `adapter.id` (now `pi-direct`, item B) and src/models/
|
|
301
|
+
// worker-installation.mjs's `inspectWorkerInstallation` requires
|
|
302
|
+
// `adapter.id === instance.getName()`. `getBinary()` is untouched.
|
|
303
|
+
getName() { return 'pi-direct'; }
|
|
304
|
+
|
|
305
|
+
/** @param {string} stdout @returns {{events:object[],settled:true,text:string}|null} */
|
|
306
|
+
parseOutput(stdout) {
|
|
307
|
+
const final = this.parseDirectFinal(stdout);
|
|
308
|
+
return final && final.settled && typeof final.text === 'string' && final.text.trim() ? final : null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {string|Uint8Array} output
|
|
313
|
+
* @param {Object} [options]
|
|
314
|
+
* @returns {Promise<import('../../../src/mission-protocol/types.js').WorkerClaims>}
|
|
315
|
+
*/
|
|
316
|
+
async parseWorkerFinal(output, options = {}) {
|
|
317
|
+
if (options.format === 'mcp') return super.parseWorkerFinal(output, options);
|
|
318
|
+
const text = typeof output === 'string' ? output : Buffer.from(output).toString('utf8');
|
|
319
|
+
const final = this.parseOutput(text);
|
|
320
|
+
if (!final) throw new Error('Pi worker stream did not produce a settled, non-empty final answer');
|
|
321
|
+
return super.parseWorkerFinal(final.text, { ...options, format: 'json' });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Installed-module interface version (mirrors #2897 codex-adapter.mjs). */
|
|
326
|
+
export const interfaceVersion = 1;
|
|
327
|
+
|
|
328
|
+
/** Installed-module registration: exactly the one approved adapter ID. */
|
|
329
|
+
export function register(factory) {
|
|
330
|
+
factory('pi-direct', () => new PiDirectAdapter());
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export default PiAdapter;
|