@aiwg/cli 2026.10.3 → 2026.10.4
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/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +98 -37
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +51 -11
- package/dist/src/sessions/contracts.js +40 -2
- package/dist/src/sessions/coverage-preflight.js +1 -1
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +147 -52
- package/dist/src/sessions/workspace-discovery.js +221 -56
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { runRecoveryProbe } from './recovery-probe.mjs';
|
|
2
|
+
|
|
3
|
+
/** Recovery evidence observed outside worker output by the launch owner. */
|
|
4
|
+
const observations = new WeakMap();
|
|
5
|
+
const failureHints = new WeakMap();
|
|
6
|
+
|
|
7
|
+
export function recordLauncherRecoveryHint(result, reasonCode) {
|
|
8
|
+
failureHints.set(result, reasonCode);
|
|
9
|
+
}
|
|
10
|
+
const HOST_ERRORS = new Set(['ENOMEM', 'ENOSPC', 'EAGAIN', 'EMFILE', 'ENFILE']);
|
|
11
|
+
const HOST_RESULTS = new Set(['oom-kill', 'resources']);
|
|
12
|
+
|
|
13
|
+
export function launcherRecoveryEvidence(result) {
|
|
14
|
+
return observations.get(result) ?? null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A failed task's transcript cannot attest an upstream outage. Independently
|
|
19
|
+
* check only an endpoint already authorized by installation qualification.
|
|
20
|
+
* HEAD sends no task, credentials or response body and never follows redirects.
|
|
21
|
+
* Only upstream failure hints warrant this check; hints never attest an outage.
|
|
22
|
+
* beforeRequest checks the remaining task deadline; onDispatch charges allowance.
|
|
23
|
+
*/
|
|
24
|
+
export async function observeLauncherRecoveryOutcome({ invocation, result, installation, breaker,
|
|
25
|
+
failureHint = '', beforeRequest, onDispatch, fetchImpl = globalThis.fetch, wallNowMs = Date.now, timeoutMs = 10_000 } = {}) {
|
|
26
|
+
if (!result || result.attemptOutcome !== 'failed' || result.cancelled) return;
|
|
27
|
+
const backend = invocation?.selected?.inferenceProvider;
|
|
28
|
+
if (HOST_ERRORS.has(result.errorCode) || HOST_RESULTS.has(result.nativeResult)) {
|
|
29
|
+
const corroboratedBackends = breaker?.observeHostFailure({ invocationId: result.invocationId,
|
|
30
|
+
inferenceProvider: backend, reasonCode: 'RESOURCE' }) ?? [];
|
|
31
|
+
if (corroboratedBackends.length >= 2) observations.set(result, {
|
|
32
|
+
reasonCode: 'RESOURCE', evidenceSource: 'launcher', hostId: 'local', corroboratedBackends,
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!['RATE_LIMIT', 'NETWORK'].includes(failureHints.get(result) ?? result.health?.reasonCode)
|
|
37
|
+
&& !/\b429\b|rate.limit|quota.exceeded|\b(?:ECONNRESET|ENOTFOUND|ETIMEDOUT|EAI_AGAIN|50[234])\b/i.test(String(failureHint).slice(0, 8192))) return;
|
|
38
|
+
const endpoint = installation?.endpoints?.[0];
|
|
39
|
+
if (typeof endpoint !== 'string' || typeof beforeRequest !== 'function' || typeof fetchImpl !== 'function') return;
|
|
40
|
+
const remaining = () => typeof timeoutMs === 'function' ? timeoutMs() : timeoutMs;
|
|
41
|
+
if (!Number.isFinite(remaining()) || remaining() <= 0) return;
|
|
42
|
+
try {
|
|
43
|
+
if (!await beforeRequest()) return;
|
|
44
|
+
const requestTimeoutMs = remaining();
|
|
45
|
+
if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) return;
|
|
46
|
+
const observed = await runRecoveryProbe({ driver: { qualification: { installation } }, invocation: invocation ?? { transport: 'shell' },
|
|
47
|
+
timeoutMs: Math.min(10_000, requestTimeoutMs), maxOutputBytes: 65_536, onDispatch, fetchImpl, wallNowMs });
|
|
48
|
+
if (observed.reason === 'probe-rate-limited') {
|
|
49
|
+
observations.set(result, { reasonCode: 'RATE_LIMIT', evidenceSource: 'launcher',
|
|
50
|
+
retryAfterSeconds: observed.retryAfterSeconds });
|
|
51
|
+
} else if (observed.status >= 500 && observed.status <= 599) {
|
|
52
|
+
observations.set(result, { reasonCode: 'NETWORK', evidenceSource: 'launcher' });
|
|
53
|
+
}
|
|
54
|
+
} catch { /* A failed diagnostic is not evidence of a provider-wide outage. */ }
|
|
55
|
+
}
|
|
@@ -134,7 +134,7 @@ export class OmpAdapter extends ProviderAdapter {
|
|
|
134
134
|
if (event.type === 'rpc_frame_error' || event.type === 'error') return { kind: 'error', reasonCode: 'provider-failed' };
|
|
135
135
|
return null;
|
|
136
136
|
}
|
|
137
|
-
async getVersion() { const result = spawnSync(this.getBinary(), ['--version'], { encoding: 'utf8', timeout: 5000, maxBuffer: 65536 }); return result.status === 0 ? result.stdout.trim() : null; }
|
|
137
|
+
async getVersion() { const result = spawnSync(this.getBinary(), ['--version'], { encoding: 'utf8', timeout: 5000, maxBuffer: 65536, killSignal: 'SIGKILL' }); return result.status === 0 ? result.stdout.trim() : null; }
|
|
138
138
|
async isAvailable() { return Boolean(await this.getVersion()); }
|
|
139
139
|
getName() { return 'omp'; }
|
|
140
140
|
getBinary() { return process.env.AIWG_OMP_BIN || 'omp'; }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/** Pi Coding Agent adapter for headless External Ralph sessions. */
|
|
2
|
-
import { spawnSync } from 'child_process';
|
|
3
2
|
import { homedir } from 'os';
|
|
4
3
|
import { join } from 'path';
|
|
5
4
|
import { ProviderAdapter, registerProvider } from './provider-adapter.mjs';
|
|
@@ -113,9 +112,6 @@ export class PiAdapter extends ProviderAdapter {
|
|
|
113
112
|
rpcAbort: false };
|
|
114
113
|
}
|
|
115
114
|
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
115
|
if (!await super.isAvailable()) return false;
|
|
120
116
|
return isSupportedPiVersion(await this.getVersion());
|
|
121
117
|
}
|
|
@@ -448,18 +448,76 @@ export class ProviderAdapter {
|
|
|
448
448
|
return this.parseWorkerOutput(output, options);
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
+
/**
|
|
452
|
+
* #2915 (evidence contract): the attempt-terminal sibling of
|
|
453
|
+
* `parseWorkerFinal`/`parseWorkerOutput` below. Shares the exact same
|
|
454
|
+
* strict decoding AND the exact same per-CLI extraction every subclass's
|
|
455
|
+
* own `parseWorkerFinal` override performs — the only difference is the
|
|
456
|
+
* terminal assertion: `assertWorkerAttemptTerminal` requires the
|
|
457
|
+
* launcher/adapter-owned `attemptOutcome` (never confirmed descendant
|
|
458
|
+
* termination) instead of `assertWorkerTerminal`'s
|
|
459
|
+
* `termination: 'confirmed'`. This lets a degraded-isolation shell
|
|
460
|
+
* attempt or an MCP response — neither of which can ever produce a
|
|
461
|
+
* confirmed descendant-stop — still validate a genuinely successful
|
|
462
|
+
* attempt's final claims (session-launcher.mjs `captureOneWorker`). It
|
|
463
|
+
* never manufactures confirmed termination: the caller's own
|
|
464
|
+
* `termination`/`descendants` fields are untouched.
|
|
465
|
+
*
|
|
466
|
+
* Fixed once here, for every adapter, with NO per-adapter edit required
|
|
467
|
+
* (2915 follow-up: the previous version called `parseWorkerOutput`
|
|
468
|
+
* directly, skipping every subclass's own extraction override entirely).
|
|
469
|
+
* The trick is calling `this.parseWorkerFinal` — polymorphic dispatch
|
|
470
|
+
* picks up the ACTUAL (possibly overridden) method — with
|
|
471
|
+
* `assertTerminal` folded into `options`. Every subclass override in this
|
|
472
|
+
* directory follows the same shape: extract a CLI-specific final, then
|
|
473
|
+
* `return super.parseWorkerFinal(extracted, { ...options, format: '...' })`
|
|
474
|
+
* — the `...options` spread carries `assertTerminal` straight through to
|
|
475
|
+
* the base `parseWorkerOutput` below, so the base class never needs
|
|
476
|
+
* subclass-specific knowledge of each extraction. An adapter whose
|
|
477
|
+
* override always throws regardless of input (Warp, Copilot:
|
|
478
|
+
* `supportsAttemptVerification() === false`) throws here too, by the same
|
|
479
|
+
* unconditional path — exactly the intended "never verifiable" behavior
|
|
480
|
+
* for those two; `admitDirectWorker` treats such a CLI as `not-gated`
|
|
481
|
+
* (recovery never consulted) rather than permanently blocking it.
|
|
482
|
+
*/
|
|
483
|
+
parseWorkerAttemptFinal(output, options = {}) {
|
|
484
|
+
return this.parseWorkerAttemptOutput(output, options);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async parseWorkerAttemptOutput(output, options = {}) {
|
|
488
|
+
const { assertWorkerAttemptTerminal } = await importImpl(import.meta.url, 'mission-protocol/index.js');
|
|
489
|
+
return this.parseWorkerFinal(output, { ...options, assertTerminal: assertWorkerAttemptTerminal });
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Capability signal (#2915 follow-up), default `true`: can this adapter's
|
|
494
|
+
* `parseWorkerFinal`/`parseWorkerAttemptFinal` ever validate a genuine
|
|
495
|
+
* final for ANY input, given a real completed attempt? Override `false`
|
|
496
|
+
* only for an adapter whose `parseWorkerFinal` always throws regardless
|
|
497
|
+
* of input (no observed output envelope exists to extract from yet —
|
|
498
|
+
* Warp, Copilot). `admitDirectWorker` uses this to treat such a CLI as
|
|
499
|
+
* never eligible for recovery-breaker gating, rather than admitting a
|
|
500
|
+
* canary that can structurally never report success.
|
|
501
|
+
*/
|
|
502
|
+
supportsAttemptVerification() { return true; }
|
|
503
|
+
|
|
451
504
|
/** Strict group-mode path, shared by CLI and MCP. Never falls back to regex.
|
|
452
505
|
* Drivers normalize qualified vendor event framing to result/message/terminal
|
|
453
506
|
* JSONL, or supply one claims JSON document. Observation comes from the trusted
|
|
454
507
|
* process/request owner, not from the final. This returns claims, not acceptance.
|
|
455
508
|
* Called directly or through the `parseWorkerFinal()` alias; never through
|
|
456
509
|
* `parseOutput()`, which keeps its own synchronous, non-group contract.
|
|
510
|
+
* `options.assertTerminal`, when supplied (by `parseWorkerAttemptOutput`
|
|
511
|
+
* above), replaces the default `assertWorkerTerminal` confirmed-termination
|
|
512
|
+
* gate with the attempt-terminal one — every other step of decoding is
|
|
513
|
+
* identical and unmodified.
|
|
457
514
|
*/
|
|
458
515
|
async parseWorkerOutput(output, options = {}) {
|
|
459
516
|
const {
|
|
460
517
|
WORKER_MAX_JSON_BYTES, WORKER_MAX_NESTING_DEPTH, WorkerContractError,
|
|
461
518
|
decodeWorkerProducedPayload, assertWorkerTerminal,
|
|
462
519
|
} = await importImpl(import.meta.url, 'mission-protocol/index.js');
|
|
520
|
+
const assertTerminal = options.assertTerminal ?? assertWorkerTerminal;
|
|
463
521
|
const fail = message => { throw new WorkerContractError('OG_CONTRACT_INVALID', message); };
|
|
464
522
|
const format = options.format ?? 'jsonl';
|
|
465
523
|
const streamLimit = 10 * 1024 * 1024;
|
|
@@ -561,7 +619,7 @@ export class ProviderAdapter {
|
|
|
561
619
|
boundedJson(final, WORKER_MAX_JSON_BYTES);
|
|
562
620
|
const claims = decodeWorkerProducedPayload('workerClaims', 'worker', final);
|
|
563
621
|
if (options.observation?.transport !== (format === 'mcp' ? 'mcp' : 'shell')) fail('Worker transport observation mismatch.');
|
|
564
|
-
|
|
622
|
+
assertTerminal(options.observation);
|
|
565
623
|
return claims;
|
|
566
624
|
}
|
|
567
625
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** A recovery probe is a conductor-owned request, never an uncontained worker. */
|
|
2
|
+
export async function runRecoveryProbe({ driver, invocation, timeoutMs = 10_000, maxOutputBytes = 65_536,
|
|
3
|
+
onDispatch, fetchImpl = globalThis.fetch, wallNowMs = Date.now }) {
|
|
4
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 10_000
|
|
5
|
+
|| !Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1 || maxOutputBytes > 65_536) {
|
|
6
|
+
throw new RangeError('Recovery probe exceeds the 10s/64KiB bounds');
|
|
7
|
+
}
|
|
8
|
+
const failed = (reason, extra = {}) => ({ success: false, relevant: false, termination: 'confirmed', reason, ...extra });
|
|
9
|
+
// These endpoints already passed the installation authority's allowlist and
|
|
10
|
+
// are bound into its qualification digest. Never derive a URL from worker
|
|
11
|
+
// output, task argv, a prompt, or ambient provider configuration.
|
|
12
|
+
const endpoint = driver?.qualification?.installation?.endpoints?.[0];
|
|
13
|
+
if (!['shell', 'mcp'].includes(invocation?.transport) || typeof endpoint !== 'string' || typeof fetchImpl !== 'function') {
|
|
14
|
+
return failed('probe-profile-unavailable', { operatorIntervention: true });
|
|
15
|
+
}
|
|
16
|
+
let url;
|
|
17
|
+
try { url = new URL(endpoint); } catch { return failed('probe-endpoint-unavailable', { operatorIntervention: true }); }
|
|
18
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash) {
|
|
19
|
+
return failed('probe-endpoint-unavailable', { operatorIntervention: true });
|
|
20
|
+
}
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const timer = setTimeout(() => controller.abort(), Math.max(1, Math.floor(timeoutMs)));
|
|
23
|
+
let aborted;
|
|
24
|
+
const timeout = new Promise((_, reject) => {
|
|
25
|
+
aborted = () => reject(new Error('probe-timeout'));
|
|
26
|
+
controller.signal.addEventListener('abort', aborted, { once: true });
|
|
27
|
+
});
|
|
28
|
+
// The rejection can precede a slow allowance debit; attach a handler now
|
|
29
|
+
// while still awaiting that debit's definitive outcome before returning.
|
|
30
|
+
timeout.catch(() => {});
|
|
31
|
+
try {
|
|
32
|
+
await onDispatch?.();
|
|
33
|
+
if (controller.signal.aborted) return failed('probe-timeout');
|
|
34
|
+
try {
|
|
35
|
+
// HEAD has no task, credentials, inference body or worker filesystem.
|
|
36
|
+
// A successful endpoint check permits only one subsequent task canary.
|
|
37
|
+
const response = await Promise.race([fetchImpl(endpoint, { method: 'HEAD', redirect: 'error', signal: controller.signal }), timeout]);
|
|
38
|
+
let headerBytes = 16 + Buffer.byteLength(response.statusText ?? '');
|
|
39
|
+
for (const [name, value] of response.headers) headerBytes += Buffer.byteLength(name) + Buffer.byteLength(value) + 4;
|
|
40
|
+
try {
|
|
41
|
+
if (headerBytes > maxOutputBytes) return failed('probe-output-limit');
|
|
42
|
+
if (response.body) return failed('probe-body-unexpected');
|
|
43
|
+
const success = response.status >= 200 && response.status <= 299;
|
|
44
|
+
if (success) return { success: true, relevant: true, termination: 'confirmed', reason: 'probe-succeeded' };
|
|
45
|
+
if (response.status === 429) {
|
|
46
|
+
const after = response.headers.get('retry-after');
|
|
47
|
+
const seconds = after != null && /^\d+(?:\.\d+)?$/.test(after.trim()) ? Number(after) : null;
|
|
48
|
+
const date = seconds === null && after ? Date.parse(after) : NaN;
|
|
49
|
+
const reset = response.headers.get('x-ratelimit-reset');
|
|
50
|
+
const resetEpoch = reset != null && /^\d+(?:\.\d+)?$/.test(reset.trim()) ? Number(reset) * 1000 : NaN;
|
|
51
|
+
const resetDelay = response.headers.get('ratelimit-reset');
|
|
52
|
+
const delays = [seconds, Number.isFinite(date) ? (date - wallNowMs()) / 1000 : null,
|
|
53
|
+
Number.isFinite(resetEpoch) ? (resetEpoch - wallNowMs()) / 1000 : null,
|
|
54
|
+
resetDelay != null && /^\d+(?:\.\d+)?$/.test(resetDelay.trim()) ? Number(resetDelay) : null]
|
|
55
|
+
.filter(value => Number.isFinite(value) && value >= 0);
|
|
56
|
+
return failed('probe-rate-limited', { retryAfterSeconds: delays.length ? Math.max(...delays) : null });
|
|
57
|
+
}
|
|
58
|
+
return failed('probe-failed', { status: response.status });
|
|
59
|
+
} finally {
|
|
60
|
+
// HEAD exposes no response body on the native transport. Cancel an
|
|
61
|
+
// unexpected body without reading it, under the same absolute bound.
|
|
62
|
+
if (response.body) await Promise.race([response.body.cancel(), timeout]);
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
return failed(controller.signal.aborted ? 'probe-timeout' : 'probe-unavailable');
|
|
66
|
+
}
|
|
67
|
+
} finally {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
controller.signal.removeEventListener('abort', aborted);
|
|
70
|
+
controller.abort();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
// Provider-owned session policy; single-shot worker allowlists stay independent.
|
|
4
|
+
const { providers } = JSON.parse(readFileSync(
|
|
5
|
+
new URL('../../../agentic/code/providers/model-capabilities.v1.json', import.meta.url), 'utf8'));
|
|
6
|
+
|
|
7
|
+
const baseNames = new Set([
|
|
8
|
+
'PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'LANG', 'LANGUAGE',
|
|
9
|
+
'TERM', 'COLORTERM', 'TERM_PROGRAM', 'TMPDIR', 'TMP', 'TEMP',
|
|
10
|
+
'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'CI', 'NO_COLOR', 'FORCE_COLOR',
|
|
11
|
+
'NODE_OPTIONS', 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'SSL_CERT_DIR',
|
|
12
|
+
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY', 'FTP_PROXY',
|
|
13
|
+
'SYSTEMROOT', 'WINDIR', 'COMSPEC', 'PATHEXT', 'USERPROFILE',
|
|
14
|
+
'HOMEDRIVE', 'HOMEPATH', 'APPDATA', 'LOCALAPPDATA', 'PROGRAMDATA',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/** Build the legacy full-session environment. Explicit adapter overrides win. */
|
|
18
|
+
export function buildSessionEnvironment(provider = 'claude', envOverrides = {}, env = process.env) {
|
|
19
|
+
const providerNames = new Set(providers[provider]?.sessionEnvAllowlist ?? []);
|
|
20
|
+
const inherited = Object.fromEntries(Object.entries(env).filter(([name, value]) => {
|
|
21
|
+
if (value === undefined) return false;
|
|
22
|
+
// Windows names are case insensitive; proxy tools also use lowercase names.
|
|
23
|
+
const baseName = name.toUpperCase();
|
|
24
|
+
return baseNames.has(baseName) || /^(LC_|XDG_|GIT_)/.test(baseName)
|
|
25
|
+
|| providerNames.has(name);
|
|
26
|
+
}));
|
|
27
|
+
return { ...inherited, ...envOverrides };
|
|
28
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { accessSync, constants, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const CONNECTION_NAMES = ['XDG_RUNTIME_DIR', 'DBUS_SESSION_BUS_ADDRESS', 'SYSTEMD_BUS_ADDRESS'];
|
|
5
|
+
|
|
6
|
+
export function isClientControlVariable(name) {
|
|
7
|
+
return /^(LD_|DYLD_|GLIBC_)/.test(name) || ['NODE_OPTIONS', 'NODE_PATH'].includes(name);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Conductor PATH directories and symlink targets must be protected from
|
|
11
|
+
* untrusted writers; canonicalization does not check ownership or prevent races. */
|
|
12
|
+
export function resolveSystemdClient(name) {
|
|
13
|
+
if (!['systemd-run', 'systemctl'].includes(name)) throw new Error('Invalid systemd client');
|
|
14
|
+
for (const directory of (process.env.PATH ?? '/usr/bin:/bin').split(path.delimiter)) {
|
|
15
|
+
// Relative/empty entries would let the working directory select the client.
|
|
16
|
+
if (!path.isAbsolute(directory)) continue;
|
|
17
|
+
const candidate = path.join(directory, name);
|
|
18
|
+
try {
|
|
19
|
+
const canonical = realpathSync(candidate);
|
|
20
|
+
accessSync(canonical, constants.X_OK);
|
|
21
|
+
if (statSync(canonical).isFile()) return canonical;
|
|
22
|
+
} catch { /* try the next conductor PATH entry */ }
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`${name} is unavailable on the conductor's absolute PATH`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveSystemdRun() { return resolveSystemdClient('systemd-run'); }
|
|
28
|
+
|
|
29
|
+
/** Reject bootstrap controls before the client loader can act on them. */
|
|
30
|
+
export function unitEnvironmentNames(envNames) {
|
|
31
|
+
for (const name of envNames) {
|
|
32
|
+
if (isClientControlVariable(name)) throw new Error('Worker environment cannot alter trusted transport bootstrap');
|
|
33
|
+
}
|
|
34
|
+
return envNames.filter(name => !CONNECTION_NAMES.includes(name));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Import caller values for bare --setenv; connections belong only to the conductor. */
|
|
38
|
+
export function buildSystemdClientEnv({ env = process.env, envNames = ['PATH', 'HOME'] } = {}) {
|
|
39
|
+
const clientEnv = {};
|
|
40
|
+
for (const name of ['PATH', 'HOME', ...CONNECTION_NAMES]) {
|
|
41
|
+
if (process.env[name] !== undefined) clientEnv[name] = process.env[name];
|
|
42
|
+
}
|
|
43
|
+
for (const name of envNames) {
|
|
44
|
+
if (CONNECTION_NAMES.includes(name) || isClientControlVariable(name)) continue;
|
|
45
|
+
if (env?.[name] === undefined) delete clientEnv[name];
|
|
46
|
+
else clientEnv[name] = env[name];
|
|
47
|
+
}
|
|
48
|
+
return clientEnv;
|
|
49
|
+
}
|
|
@@ -387,7 +387,7 @@ export async function sampleOwnedWorktreeContent(root, { files, maxFiles = 128,
|
|
|
387
387
|
if (!candidates) {
|
|
388
388
|
const status = execFileSync('git', ['--no-optional-locks', '-c', 'core.fsmonitor=false', '-c', 'core.untrackedCache=false',
|
|
389
389
|
'-C', root, 'status', '--porcelain=v1', '-z', '--untracked-files=all'],
|
|
390
|
-
{ timeout: 1000, maxBuffer: 65536, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_CONFIG_COUNT: '0' } });
|
|
390
|
+
{ timeout: 1000, maxBuffer: 65536, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_CONFIG_COUNT: '0' }, killSignal: 'SIGKILL' });
|
|
391
391
|
candidates = [];
|
|
392
392
|
const records = status.split('\0');
|
|
393
393
|
for (let i = 0; i < records.length; i++) {
|