@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.
Files changed (279) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/model-capabilities.v1.json +96 -4
  3. package/bin/aiwg.mjs +50 -27
  4. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  5. package/dist/src/artifacts/browser-export.js +6 -0
  6. package/dist/src/artifacts/checksum-manifest.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +173 -92
  8. package/dist/src/artifacts/citation-table-schema.js +59 -0
  9. package/dist/src/artifacts/cli.js +57 -18
  10. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  11. package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
  12. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  13. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  14. package/dist/src/artifacts/discovery-eval.js +6 -4
  15. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  16. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  17. package/dist/src/artifacts/framework-capabilities.js +171 -0
  18. package/dist/src/artifacts/framework-index-paths.js +73 -0
  19. package/dist/src/artifacts/framework-source-digest.js +75 -0
  20. package/dist/src/artifacts/index-builder.js +285 -131
  21. package/dist/src/artifacts/index-files.js +132 -18
  22. package/dist/src/artifacts/index-preparation.js +65 -0
  23. package/dist/src/artifacts/index-refresh.js +576 -0
  24. package/dist/src/artifacts/index-worker.mjs +9 -0
  25. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  26. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  27. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  28. package/dist/src/artifacts/query-engine.js +410 -88
  29. package/dist/src/artifacts/relations-builder.js +168 -0
  30. package/dist/src/artifacts/relations.js +148 -0
  31. package/dist/src/artifacts/stats.js +5 -2
  32. package/dist/src/artifacts/types.js +30 -10
  33. package/dist/src/artifacts/watcher.js +1 -1
  34. package/dist/src/auth/credential-store.js +2 -1
  35. package/dist/src/catalog/cli.d.mts +5 -0
  36. package/dist/src/channel/manager.d.mts +20 -0
  37. package/dist/src/channel/manager.mjs +19 -2
  38. package/dist/src/cli/command-log.js +2 -3
  39. package/dist/src/cli/handlers/artifacts.js +3 -0
  40. package/dist/src/cli/handlers/auth.js +3 -0
  41. package/dist/src/cli/handlers/decision.js +19 -12
  42. package/dist/src/cli/handlers/diagnose.js +4 -3
  43. package/dist/src/cli/handlers/effect.js +4 -2
  44. package/dist/src/cli/handlers/evidence.js +3 -0
  45. package/dist/src/cli/handlers/help.js +1 -0
  46. package/dist/src/cli/handlers/index.js +3 -1
  47. package/dist/src/cli/handlers/init.js +4 -1
  48. package/dist/src/cli/handlers/lint.js +5 -0
  49. package/dist/src/cli/handlers/models.js +104 -8
  50. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  51. package/dist/src/cli/handlers/refresh.js +12 -0
  52. package/dist/src/cli/handlers/resource-versions.js +3 -0
  53. package/dist/src/cli/handlers/rules.js +195 -0
  54. package/dist/src/cli/handlers/run.js +4 -0
  55. package/dist/src/cli/handlers/runtime-info.js +1 -2
  56. package/dist/src/cli/handlers/script-runner.js +3 -2
  57. package/dist/src/cli/handlers/session.js +19 -9
  58. package/dist/src/cli/handlers/sessions.js +98 -37
  59. package/dist/src/cli/handlers/setup-manifest.js +39 -44
  60. package/dist/src/cli/handlers/setup.js +41 -0
  61. package/dist/src/cli/handlers/steward.js +11 -0
  62. package/dist/src/cli/handlers/subcommands.js +57 -2
  63. package/dist/src/cli/handlers/uhp.js +3 -0
  64. package/dist/src/cli/handlers/use.js +294 -55
  65. package/dist/src/cli/handlers/utilities.js +26 -10
  66. package/dist/src/cli/handlers/version.js +8 -0
  67. package/dist/src/cli/router.js +15 -1
  68. package/dist/src/cli/services/deployment-verification.js +19 -9
  69. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  70. package/dist/src/cli/skill-usage.js +2 -3
  71. package/dist/src/config/aiwg-config.js +87 -37
  72. package/dist/src/config/cli.js +111 -5
  73. package/dist/src/config/decision-config.js +106 -0
  74. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  75. package/dist/src/config/gitignore.js +50 -0
  76. package/dist/src/config/index-graph-overrides.js +36 -0
  77. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  78. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  79. package/dist/src/config/user-config-dir.d.mts +9 -0
  80. package/dist/src/config/user-config.js +2 -1
  81. package/dist/src/config/user-registry.js +23 -15
  82. package/dist/src/config/user-state.js +7 -0
  83. package/dist/src/decision/ask.js +203 -43
  84. package/dist/src/decision/context-live-qualification.js +2 -2
  85. package/dist/src/decision/driver.js +13 -8
  86. package/dist/src/decision/ensemble/aggregate.js +23 -10
  87. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  88. package/dist/src/decision/entry.js +2 -15
  89. package/dist/src/decision/graph-live-qualification.js +1 -1
  90. package/dist/src/decision/graph-run.js +9 -0
  91. package/dist/src/decision/heldout/collector.js +5 -13
  92. package/dist/src/decision/heldout/contract.js +30 -7
  93. package/dist/src/decision/jev-credentials.js +15 -1
  94. package/dist/src/decision/ledger.js +131 -28
  95. package/dist/src/decision/offload-activation.js +137 -0
  96. package/dist/src/decision/offload.js +550 -0
  97. package/dist/src/decision/qualification/load.js +4 -6
  98. package/dist/src/decision/savings.js +28 -0
  99. package/dist/src/effects/types.js +5 -1
  100. package/dist/src/effects/verifiers/git.js +2 -1
  101. package/dist/src/effects/verifiers/tracker.js +26 -14
  102. package/dist/src/extensions/commands/definitions.js +19 -0
  103. package/dist/src/extensions/project-local-remove.js +36 -1
  104. package/dist/src/gates/providers/loader.js +61 -17
  105. package/dist/src/installation/manager-command.d.mts +18 -0
  106. package/dist/src/installation/manager-command.mjs +14 -2
  107. package/dist/src/installation/manager.d.mts +49 -0
  108. package/dist/src/installation/manager.mjs +78 -3
  109. package/dist/src/issues/cli.js +4 -4
  110. package/dist/src/issues/local.js +24 -5
  111. package/dist/src/jobs/runner.js +1 -1
  112. package/dist/src/lint/cli.js +27 -1
  113. package/dist/src/lint/loader.js +2 -1
  114. package/dist/src/lint/reporters.js +25 -1
  115. package/dist/src/lint/rule-activation.js +43 -0
  116. package/dist/src/lint/runner.js +137 -62
  117. package/dist/src/mcp/adapters/codex-runtime.js +7 -1
  118. package/dist/src/mcp/claude-install.d.mts +4 -0
  119. package/dist/src/mcp/claude-install.mjs +41 -0
  120. package/dist/src/mcp/cli.d.mts +1 -0
  121. package/dist/src/mcp/cli.mjs +36 -11
  122. package/dist/src/mcp/config-file.d.mts +28 -0
  123. package/dist/src/mcp/config-layers.d.mts +36 -0
  124. package/dist/src/mcp/credentials.d.mts +51 -0
  125. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  126. package/dist/src/mcp/grok-build-config.mjs +1 -1
  127. package/dist/src/mcp/helpers.mjs +3 -2
  128. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  129. package/dist/src/mcp/muse-native-config.mjs +1 -0
  130. package/dist/src/mcp/omp-config.d.mts +4 -0
  131. package/dist/src/mcp/registry.js +3 -93
  132. package/dist/src/mcp/registry.mjs +3 -87
  133. package/dist/src/mcp/server-config.d.mts +3 -0
  134. package/dist/src/mcp/server-config.mjs +89 -0
  135. package/dist/src/mcp/server.mjs +37 -375
  136. package/dist/src/mcp/test-simple.mjs +2 -1
  137. package/dist/src/mcp/toml-editor.d.mts +4 -0
  138. package/dist/src/mcp/toml-string.d.mts +1 -0
  139. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  140. package/dist/src/mcp/tool-filters.d.mts +46 -0
  141. package/dist/src/mcp/tools/decision.mjs +12 -11
  142. package/dist/src/mcp/tools/discovery.mjs +62 -55
  143. package/dist/src/mcp/tools/orchestration.mjs +12 -2
  144. package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
  145. package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
  146. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
  147. package/dist/src/mission-protocol/codecs.js +8 -0
  148. package/dist/src/models/config-loader.js +7 -2
  149. package/dist/src/models/model-capabilities.v1.json +96 -4
  150. package/dist/src/models/model-discovery.js +76 -11
  151. package/dist/src/models/provider-policy.js +101 -7
  152. package/dist/src/models/qualification-digest.d.mts +2 -0
  153. package/dist/src/models/resolver.js +12 -10
  154. package/dist/src/models/worker-direct-readiness.js +65 -44
  155. package/dist/src/models/worker-dispatch-offer.js +3 -3
  156. package/dist/src/models/worker-health-readiness.js +16 -3
  157. package/dist/src/models/worker-installation.d.mts +39 -0
  158. package/dist/src/models/worker-installation.mjs +135 -28
  159. package/dist/src/process/bounded-child-process.d.mts +84 -0
  160. package/dist/src/process/bounded-child-process.mjs +43 -0
  161. package/dist/src/process/cli-command.d.mts +16 -0
  162. package/dist/src/process/signal-child.d.mts +6 -0
  163. package/dist/src/process/signal-child.mjs +7 -0
  164. package/dist/src/providers/deployed-skill-roots.js +20 -0
  165. package/dist/src/providers/grok-build-acp.js +3 -2
  166. package/dist/src/providers/grok-build-headless.js +12 -4
  167. package/dist/src/providers/grok-build-inspect.js +1 -0
  168. package/dist/src/providers/omp-agent.d.mts +8 -0
  169. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  170. package/dist/src/providers/omp-paths.d.mts +13 -0
  171. package/dist/src/providers/skill-ownership.d.mts +9 -0
  172. package/dist/src/providers/skill-ownership.mjs +88 -0
  173. package/dist/src/rules/activation.d.mts +21 -0
  174. package/dist/src/rules/activation.mjs +62 -0
  175. package/dist/src/rules/inline-budget.d.mts +26 -0
  176. package/dist/src/rules/inline-budget.mjs +66 -0
  177. package/dist/src/rules/inventory.js +129 -0
  178. package/dist/src/rules/pin-state.js +73 -0
  179. package/dist/src/rules/pins.d.mts +14 -0
  180. package/dist/src/rules/pins.mjs +112 -0
  181. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  182. package/dist/src/serve/mission-conductor.js +38 -12
  183. package/dist/src/serve/worker-invocation.js +30 -10
  184. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  185. package/dist/src/sessions/adapter-registry.js +23 -19
  186. package/dist/src/sessions/adapters/claude.js +39 -54
  187. package/dist/src/sessions/adapters/codex.js +82 -67
  188. package/dist/src/sessions/adapters/copilot.js +4 -4
  189. package/dist/src/sessions/adapters/cursor.js +40 -57
  190. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  191. package/dist/src/sessions/adapters/factory.js +31 -27
  192. package/dist/src/sessions/adapters/generic.js +10 -10
  193. package/dist/src/sessions/adapters/grok-build.js +11 -11
  194. package/dist/src/sessions/adapters/grokbot.js +24 -13
  195. package/dist/src/sessions/adapters/hermes.js +27 -29
  196. package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
  197. package/dist/src/sessions/adapters/muse.js +150 -93
  198. package/dist/src/sessions/adapters/omp.js +28 -14
  199. package/dist/src/sessions/adapters/openclaw.js +89 -79
  200. package/dist/src/sessions/adapters/opencode.js +27 -24
  201. package/dist/src/sessions/adapters/openhuman.js +18 -60
  202. package/dist/src/sessions/adapters/pi.js +29 -14
  203. package/dist/src/sessions/adapters/warp.js +3 -3
  204. package/dist/src/sessions/adapters/windsurf.js +19 -48
  205. package/dist/src/sessions/batch-contracts.js +16 -8
  206. package/dist/src/sessions/batch-import.js +51 -11
  207. package/dist/src/sessions/contracts.js +40 -2
  208. package/dist/src/sessions/coverage-preflight.js +1 -1
  209. package/dist/src/sessions/export-plan.js +4 -3
  210. package/dist/src/sessions/import-exclusions.js +72 -0
  211. package/dist/src/sessions/import-lease.js +34 -18
  212. package/dist/src/sessions/import-limits.js +37 -0
  213. package/dist/src/sessions/importer.js +60 -38
  214. package/dist/src/sessions/lifecycle.js +45 -0
  215. package/dist/src/sessions/policy.js +133 -25
  216. package/dist/src/sessions/readers.js +106 -53
  217. package/dist/src/sessions/refresh.js +106 -0
  218. package/dist/src/sessions/repository.js +147 -52
  219. package/dist/src/sessions/workspace-discovery.js +221 -56
  220. package/dist/src/sessions/workspace-matching.js +107 -0
  221. package/dist/src/skills/importer.js +1 -0
  222. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  223. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  224. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  225. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  226. package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
  227. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  228. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  229. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
  230. package/dist/src/storage/backends/fs.js +18 -76
  231. package/dist/src/storage/backends/obsidian.js +1 -0
  232. package/dist/src/tracker/forge-http.js +2 -1
  233. package/dist/src/update/checker.d.mts +2 -0
  234. package/dist/src/update/service.d.mts +22 -0
  235. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  236. package/dist/src/utils/contained-filesystem.mjs +683 -0
  237. package/dist/src/utils/temporary-directory.js +36 -0
  238. package/dist/src/writing/example-generator.js +1 -1
  239. package/package.json +15 -2
  240. package/schemas/models/model-catalog.v1.schema.json +1 -1
  241. package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
  242. package/tools/_resolve-impl.mjs +13 -15
  243. package/tools/agents/deploy-agents.mjs +49 -21
  244. package/tools/agents/providers/base.mjs +104 -134
  245. package/tools/agents/providers/claude.mjs +8 -24
  246. package/tools/agents/providers/codex.mjs +18 -11
  247. package/tools/agents/providers/cursor.mjs +19 -18
  248. package/tools/agents/providers/grok-build.mjs +3 -1
  249. package/tools/agents/providers/hermes.mjs +57 -47
  250. package/tools/agents/providers/openclaw.mjs +9 -4
  251. package/tools/agents/providers/pi.mjs +5 -0
  252. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  253. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  254. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  255. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  256. package/tools/agents/providers/warp.mjs +20 -26
  257. package/tools/cli/decision-offload-report.mjs +40 -0
  258. package/tools/cli/workspace-status.mjs +862 -0
  259. package/tools/plugin/package-plugins.mjs +186 -8
  260. package/tools/plugin/plugin-links.mjs +114 -0
  261. package/tools/providers/antigravity-transport.mjs +3 -1
  262. package/tools/providers/deepseek-harness-transport.mjs +6 -4
  263. package/tools/providers/omp-transport.mjs +4 -2
  264. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  265. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  266. package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
  267. package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
  268. package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
  269. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  270. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  271. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  272. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  273. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  274. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  275. package/tools/ralph-external/process-monitor.mjs +1017 -0
  276. package/tools/ralph-external/session-launcher.mjs +5057 -0
  277. package/tools/scripts/resource-preflight.mjs +541 -0
  278. package/tools/skills/deploy-skills-codex.mjs +2 -1
  279. 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
- assertWorkerTerminal(options.observation);
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++) {