@aiwg/cli 2026.10.1 → 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 (361) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/capability-matrix.yaml +270 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +3183 -24
  4. package/bin/aiwg.mjs +135 -47
  5. package/dist/src/a2a/codecs.js +9 -0
  6. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  7. package/dist/src/artifacts/browser-export.js +6 -0
  8. package/dist/src/artifacts/checksum-manifest.js +1 -0
  9. package/dist/src/artifacts/citation-parser.js +173 -92
  10. package/dist/src/artifacts/citation-table-schema.js +59 -0
  11. package/dist/src/artifacts/cli.js +57 -18
  12. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  13. package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
  14. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  15. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  16. package/dist/src/artifacts/discovery-eval.js +6 -4
  17. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  18. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  19. package/dist/src/artifacts/framework-capabilities.js +171 -0
  20. package/dist/src/artifacts/framework-index-paths.js +73 -0
  21. package/dist/src/artifacts/framework-source-digest.js +75 -0
  22. package/dist/src/artifacts/index-builder.js +285 -131
  23. package/dist/src/artifacts/index-files.js +132 -18
  24. package/dist/src/artifacts/index-preparation.js +65 -0
  25. package/dist/src/artifacts/index-refresh.js +576 -0
  26. package/dist/src/artifacts/index-worker.mjs +9 -0
  27. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  28. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  29. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  30. package/dist/src/artifacts/query-engine.js +410 -88
  31. package/dist/src/artifacts/relations-builder.js +168 -0
  32. package/dist/src/artifacts/relations.js +148 -0
  33. package/dist/src/artifacts/repair.js +9 -4
  34. package/dist/src/artifacts/stats.js +5 -2
  35. package/dist/src/artifacts/types.js +30 -10
  36. package/dist/src/artifacts/watcher.js +1 -1
  37. package/dist/src/auth/credential-store.js +2 -1
  38. package/dist/src/catalog/cli.d.mts +5 -0
  39. package/dist/src/channel/manager.d.mts +20 -0
  40. package/dist/src/channel/manager.mjs +78 -12
  41. package/dist/src/cli/child-process-result.js +40 -0
  42. package/dist/src/cli/command-log.js +2 -3
  43. package/dist/src/cli/env.js +38 -16
  44. package/dist/src/cli/flag-parsing.js +236 -0
  45. package/dist/src/cli/handlers/artifacts.js +3 -0
  46. package/dist/src/cli/handlers/auth.js +3 -0
  47. package/dist/src/cli/handlers/decision.js +218 -5
  48. package/dist/src/cli/handlers/diagnose.js +13 -5
  49. package/dist/src/cli/handlers/effect.js +4 -2
  50. package/dist/src/cli/handlers/evidence.js +3 -0
  51. package/dist/src/cli/handlers/feedback.js +14 -5
  52. package/dist/src/cli/handlers/help.js +1 -0
  53. package/dist/src/cli/handlers/index.js +3 -1
  54. package/dist/src/cli/handlers/init.js +248 -30
  55. package/dist/src/cli/handlers/lint.js +5 -0
  56. package/dist/src/cli/handlers/mc.js +23 -0
  57. package/dist/src/cli/handlers/models.js +169 -10
  58. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  59. package/dist/src/cli/handlers/ralph.js +3 -0
  60. package/dist/src/cli/handlers/refresh.js +28 -0
  61. package/dist/src/cli/handlers/regenerate.js +14 -0
  62. package/dist/src/cli/handlers/resource-versions.js +3 -0
  63. package/dist/src/cli/handlers/rules.js +195 -0
  64. package/dist/src/cli/handlers/run.js +4 -0
  65. package/dist/src/cli/handlers/runtime-info.js +25 -11
  66. package/dist/src/cli/handlers/script-runner.js +19 -8
  67. package/dist/src/cli/handlers/session.js +406 -62
  68. package/dist/src/cli/handlers/sessions.js +243 -76
  69. package/dist/src/cli/handlers/setup-manifest.js +474 -79
  70. package/dist/src/cli/handlers/setup.js +180 -39
  71. package/dist/src/cli/handlers/steward.js +11 -0
  72. package/dist/src/cli/handlers/subcommands.js +60 -3
  73. package/dist/src/cli/handlers/team.js +18 -8
  74. package/dist/src/cli/handlers/uhp.js +3 -0
  75. package/dist/src/cli/handlers/use.js +307 -55
  76. package/dist/src/cli/handlers/utilities.js +125 -26
  77. package/dist/src/cli/handlers/version.js +8 -0
  78. package/dist/src/cli/prompt-utils.js +72 -0
  79. package/dist/src/cli/provider-resolution.js +11 -0
  80. package/dist/src/cli/router.js +15 -1
  81. package/dist/src/cli/services/deployment-verification.js +19 -9
  82. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  83. package/dist/src/cli/skill-usage.js +2 -3
  84. package/dist/src/config/aiwg-config.js +349 -35
  85. package/dist/src/config/cli.js +256 -23
  86. package/dist/src/config/decision-config.js +106 -0
  87. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  88. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  89. package/dist/src/config/gitignore.js +50 -0
  90. package/dist/src/config/index-graph-overrides.js +36 -0
  91. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  92. package/dist/src/config/project-artifacts-health.mjs +14 -3
  93. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  94. package/dist/src/config/user-config-dir.d.mts +9 -0
  95. package/dist/src/config/user-config.js +38 -11
  96. package/dist/src/config/user-registry.js +23 -15
  97. package/dist/src/config/user-state.js +7 -0
  98. package/dist/src/decision/adapters/jev.js +75 -27
  99. package/dist/src/decision/ask.js +203 -43
  100. package/dist/src/decision/context-live-qualification.js +2 -2
  101. package/dist/src/decision/driver.js +14 -7
  102. package/dist/src/decision/ensemble/aggregate.js +23 -10
  103. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  104. package/dist/src/decision/entry.js +25 -6
  105. package/dist/src/decision/graph-live-qualification.js +1 -1
  106. package/dist/src/decision/graph-run.js +9 -0
  107. package/dist/src/decision/heldout/collector.js +15 -14
  108. package/dist/src/decision/heldout/contract.js +30 -7
  109. package/dist/src/decision/index.js +1 -0
  110. package/dist/src/decision/interop.js +60 -8
  111. package/dist/src/decision/jev-credentials.js +15 -1
  112. package/dist/src/decision/ledger.js +942 -0
  113. package/dist/src/decision/offload-activation.js +137 -0
  114. package/dist/src/decision/offload.js +550 -0
  115. package/dist/src/decision/qualification/load.js +4 -6
  116. package/dist/src/decision/savings.js +28 -0
  117. package/dist/src/decision/validate.js +6 -3
  118. package/dist/src/effects/dispatch-verifier.js +252 -0
  119. package/dist/src/effects/index.js +1 -0
  120. package/dist/src/effects/types.js +5 -1
  121. package/dist/src/effects/verifiers/git.js +2 -1
  122. package/dist/src/effects/verifiers/tracker.js +26 -14
  123. package/dist/src/extensions/commands/definitions.js +19 -0
  124. package/dist/src/extensions/project-local-remove.js +36 -1
  125. package/dist/src/features/installer.js +2 -2
  126. package/dist/src/flow/graph-metadata.js +5 -0
  127. package/dist/src/gates/providers/loader.js +64 -18
  128. package/dist/src/installation/manager-command.d.mts +18 -0
  129. package/dist/src/installation/manager-command.mjs +15 -3
  130. package/dist/src/installation/manager.d.mts +49 -0
  131. package/dist/src/installation/manager.mjs +95 -5
  132. package/dist/src/issues/cli.js +4 -4
  133. package/dist/src/issues/local.js +24 -5
  134. package/dist/src/jobs/runner.js +1 -1
  135. package/dist/src/lint/cli.js +27 -1
  136. package/dist/src/lint/loader.js +2 -1
  137. package/dist/src/lint/reporters.js +25 -1
  138. package/dist/src/lint/rule-activation.js +43 -0
  139. package/dist/src/lint/runner.js +137 -62
  140. package/dist/src/mcp/adapters/codex-runtime.js +366 -56
  141. package/dist/src/mcp/claude-install.d.mts +4 -0
  142. package/dist/src/mcp/claude-install.mjs +41 -0
  143. package/dist/src/mcp/cli.d.mts +1 -0
  144. package/dist/src/mcp/cli.mjs +620 -85
  145. package/dist/src/mcp/config-file.d.mts +28 -0
  146. package/dist/src/mcp/config-file.mjs +279 -0
  147. package/dist/src/mcp/config-layers.d.mts +36 -0
  148. package/dist/src/mcp/config-layers.mjs +348 -0
  149. package/dist/src/mcp/credentials.d.mts +51 -0
  150. package/dist/src/mcp/credentials.mjs +484 -0
  151. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  152. package/dist/src/mcp/grok-build-config.mjs +17 -27
  153. package/dist/src/mcp/helpers.mjs +109 -4
  154. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  155. package/dist/src/mcp/muse-native-config.mjs +1 -0
  156. package/dist/src/mcp/omp-config.d.mts +4 -0
  157. package/dist/src/mcp/omp-config.mjs +50 -29
  158. package/dist/src/mcp/profiles.js +97 -23
  159. package/dist/src/mcp/profiles.mjs +99 -27
  160. package/dist/src/mcp/registry.js +248 -131
  161. package/dist/src/mcp/registry.mjs +169 -100
  162. package/dist/src/mcp/server-config.d.mts +3 -0
  163. package/dist/src/mcp/server-config.mjs +89 -0
  164. package/dist/src/mcp/server.mjs +127 -82
  165. package/dist/src/mcp/test-simple.mjs +2 -1
  166. package/dist/src/mcp/toml-editor.d.mts +4 -0
  167. package/dist/src/mcp/toml-string.d.mts +1 -0
  168. package/dist/src/mcp/toml-string.mjs +9 -0
  169. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  170. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  171. package/dist/src/mcp/tool-filters.d.mts +46 -0
  172. package/dist/src/mcp/tool-filters.mjs +357 -0
  173. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  174. package/dist/src/mcp/tools/decision.mjs +22 -3
  175. package/dist/src/mcp/tools/discovery.mjs +62 -55
  176. package/dist/src/mcp/tools/orchestration.mjs +50 -2
  177. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  178. package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
  179. package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
  180. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  181. package/dist/src/memory/project-registry.js +2 -2
  182. package/dist/src/mission-protocol/codecs.js +647 -1
  183. package/dist/src/mission-protocol/types.js +110 -0
  184. package/dist/src/mission-protocol/worker-status.js +2 -0
  185. package/dist/src/models/config-loader.js +76 -14
  186. package/dist/src/models/grok-build-models.js +93 -41
  187. package/dist/src/models/model-capabilities.v1.json +3183 -24
  188. package/dist/src/models/model-discovery.js +931 -67
  189. package/dist/src/models/provider-policy.js +719 -11
  190. package/dist/src/models/qualification-digest.d.mts +2 -0
  191. package/dist/src/models/qualification-digest.mjs +17 -0
  192. package/dist/src/models/resolver.js +261 -0
  193. package/dist/src/models/worker-direct-readiness.js +550 -0
  194. package/dist/src/models/worker-dispatch-offer.js +169 -0
  195. package/dist/src/models/worker-health-readiness.js +144 -0
  196. package/dist/src/models/worker-installation.d.mts +39 -0
  197. package/dist/src/models/worker-installation.mjs +986 -0
  198. package/dist/src/models/wrapper-route.js +18 -0
  199. package/dist/src/ops/registry.js +9 -14
  200. package/dist/src/plugins/standalone-packager.js +4 -3
  201. package/dist/src/process/bounded-child-process.d.mts +84 -0
  202. package/dist/src/process/bounded-child-process.mjs +430 -0
  203. package/dist/src/process/cli-command.d.mts +16 -0
  204. package/dist/src/process/cli-command.mjs +75 -0
  205. package/dist/src/process/signal-child.d.mts +6 -0
  206. package/dist/src/process/signal-child.mjs +7 -0
  207. package/dist/src/providers/capability-matrix.js +8 -0
  208. package/dist/src/providers/capability-matrix.yaml +270 -0
  209. package/dist/src/providers/deployed-skill-roots.js +20 -0
  210. package/dist/src/providers/grok-build-acp.js +3 -2
  211. package/dist/src/providers/grok-build-headless.js +53 -23
  212. package/dist/src/providers/grok-build-inspect.js +1 -0
  213. package/dist/src/providers/omp-agent.d.mts +8 -0
  214. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  215. package/dist/src/providers/omp-paths.d.mts +13 -0
  216. package/dist/src/providers/provider-definitions.js +9 -1
  217. package/dist/src/providers/provider-definitions.mjs +9 -2
  218. package/dist/src/providers/skill-ownership.d.mts +9 -0
  219. package/dist/src/providers/skill-ownership.mjs +88 -0
  220. package/dist/src/rules/activation.d.mts +21 -0
  221. package/dist/src/rules/activation.mjs +62 -0
  222. package/dist/src/rules/inline-budget.d.mts +26 -0
  223. package/dist/src/rules/inline-budget.mjs +66 -0
  224. package/dist/src/rules/inventory.js +129 -0
  225. package/dist/src/rules/pin-state.js +73 -0
  226. package/dist/src/rules/pins.d.mts +14 -0
  227. package/dist/src/rules/pins.mjs +112 -0
  228. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  229. package/dist/src/serve/executor-registry.js +16 -24
  230. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  231. package/dist/src/serve/mission-conductor.js +47 -12
  232. package/dist/src/serve/shared-host-scheduler.js +2 -0
  233. package/dist/src/serve/worker-invocation.js +558 -0
  234. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  235. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  236. package/dist/src/sessions/adapter-registry.js +49 -0
  237. package/dist/src/sessions/adapters/claude.js +39 -54
  238. package/dist/src/sessions/adapters/codex.js +82 -67
  239. package/dist/src/sessions/adapters/copilot.js +4 -4
  240. package/dist/src/sessions/adapters/cursor.js +40 -57
  241. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  242. package/dist/src/sessions/adapters/factory.js +31 -27
  243. package/dist/src/sessions/adapters/generic.js +43 -9
  244. package/dist/src/sessions/adapters/grok-build.js +11 -11
  245. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  246. package/dist/src/sessions/adapters/grokbot.js +434 -24
  247. package/dist/src/sessions/adapters/hermes.js +27 -29
  248. package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
  249. package/dist/src/sessions/adapters/muse.js +200 -104
  250. package/dist/src/sessions/adapters/omp.js +28 -14
  251. package/dist/src/sessions/adapters/openclaw.js +89 -79
  252. package/dist/src/sessions/adapters/opencode.js +27 -24
  253. package/dist/src/sessions/adapters/openhuman.js +18 -60
  254. package/dist/src/sessions/adapters/pi.js +30 -14
  255. package/dist/src/sessions/adapters/warp.js +3 -3
  256. package/dist/src/sessions/adapters/windsurf.js +19 -48
  257. package/dist/src/sessions/batch-contracts.js +25 -5
  258. package/dist/src/sessions/batch-import.js +72 -51
  259. package/dist/src/sessions/candidates.js +47 -11
  260. package/dist/src/sessions/contracts.js +75 -2
  261. package/dist/src/sessions/coverage-preflight.js +134 -0
  262. package/dist/src/sessions/discovery.js +3 -0
  263. package/dist/src/sessions/export-plan.js +4 -3
  264. package/dist/src/sessions/grok-build-cli.js +67 -5
  265. package/dist/src/sessions/import-exclusions.js +72 -0
  266. package/dist/src/sessions/import-lease.js +34 -18
  267. package/dist/src/sessions/import-limits.js +37 -0
  268. package/dist/src/sessions/importer.js +84 -46
  269. package/dist/src/sessions/index.js +2 -0
  270. package/dist/src/sessions/lifecycle.js +45 -0
  271. package/dist/src/sessions/policy.js +133 -25
  272. package/dist/src/sessions/readers.js +106 -53
  273. package/dist/src/sessions/refresh.js +106 -0
  274. package/dist/src/sessions/repository.js +199 -63
  275. package/dist/src/sessions/workspace-discovery.js +315 -88
  276. package/dist/src/sessions/workspace-matching.js +107 -0
  277. package/dist/src/skills/adapters/clawhub.js +10 -8
  278. package/dist/src/skills/adapters/openclaw.js +10 -7
  279. package/dist/src/skills/importer.js +1 -0
  280. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  281. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  282. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  283. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  284. package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
  285. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  286. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  287. package/dist/src/smiths/mcpsmith/example.js +1 -1
  288. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  289. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  290. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
  291. package/dist/src/storage/backends/fs.js +18 -76
  292. package/dist/src/storage/backends/logseq.js +68 -7
  293. package/dist/src/storage/backends/obsidian.js +1 -0
  294. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  295. package/dist/src/testing/generators/test-code-generator.js +57 -30
  296. package/dist/src/tracker/forge-http.js +2 -1
  297. package/dist/src/uhp/client.js +11 -1
  298. package/dist/src/uhp/mission.js +5 -0
  299. package/dist/src/update/checker.d.mts +2 -0
  300. package/dist/src/update/checker.mjs +5 -5
  301. package/dist/src/update/notifier.mjs +54 -2
  302. package/dist/src/update/service.d.mts +22 -0
  303. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  304. package/dist/src/utils/contained-filesystem.mjs +683 -0
  305. package/dist/src/utils/temporary-directory.js +36 -0
  306. package/dist/src/writing/example-generator.js +1 -1
  307. package/dist/src/writing/fidelity.js +18 -1
  308. package/package.json +32 -2
  309. package/schemas/mission-v1.schema.json +2429 -0
  310. package/schemas/models/model-catalog.v1.schema.json +301 -0
  311. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  312. package/schemas/models/model-policy.v1.schema.json +14 -0
  313. package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
  314. package/tools/_resolve-impl.mjs +39 -2
  315. package/tools/agents/deploy-agents.mjs +64 -23
  316. package/tools/agents/providers/base.mjs +153 -166
  317. package/tools/agents/providers/claude.mjs +8 -24
  318. package/tools/agents/providers/codex.mjs +18 -11
  319. package/tools/agents/providers/cursor.mjs +19 -18
  320. package/tools/agents/providers/grok-build.mjs +3 -1
  321. package/tools/agents/providers/grokbot.mjs +11 -12
  322. package/tools/agents/providers/hermes.mjs +62 -49
  323. package/tools/agents/providers/omp.mjs +5 -2
  324. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  325. package/tools/agents/providers/openclaw.mjs +9 -4
  326. package/tools/agents/providers/pi.mjs +5 -0
  327. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  328. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  329. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  330. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  331. package/tools/agents/providers/warp.mjs +20 -26
  332. package/tools/cli/decision-offload-report.mjs +40 -0
  333. package/tools/cli/workspace-status.mjs +862 -0
  334. package/tools/plugin/package-plugins.mjs +186 -8
  335. package/tools/plugin/plugin-links.mjs +114 -0
  336. package/tools/providers/antigravity-transport.mjs +3 -1
  337. package/tools/providers/deepseek-harness-transport.mjs +360 -0
  338. package/tools/providers/omp-transport.mjs +194 -0
  339. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  340. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  341. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  342. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  343. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  344. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  345. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  346. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  347. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  348. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  349. package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
  350. package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
  351. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  352. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  353. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  354. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  355. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  356. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  357. package/tools/ralph-external/process-monitor.mjs +1017 -0
  358. package/tools/ralph-external/session-launcher.mjs +5057 -0
  359. package/tools/scripts/resource-preflight.mjs +541 -0
  360. package/tools/skills/deploy-skills-codex.mjs +2 -1
  361. package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
@@ -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
+ }
@@ -0,0 +1,439 @@
1
+ /** Bounded execution observations, separate from worker claims and final parsing. */
2
+ import { createHash } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import { open } from 'node:fs/promises';
5
+ import { execFileSync } from 'node:child_process';
6
+ import path from 'node:path';
7
+
8
+ const digest = value => `sha256:${createHash('sha256').update(value).digest('hex')}`;
9
+ const MAX_EVENTS = 64;
10
+ const MAX_KEYS = 512;
11
+ // Bounded replay horizon beyond the short dedup FIFO. Only exact replay of
12
+ // evicted identities counts as churn; fresh identities reset the streak.
13
+ export const PROGRESS_CHURN_LIMITS = Object.freeze({ historyKeys: 4096, repeatedEvictedKeys: 32 });
14
+ const DEFAULTS = { startupSeconds: 60, checkSeconds: 15, quietSeconds: 90, stallSeconds: 300,
15
+ diagnosticGraceSeconds: 60, terminateGraceSeconds: 5, hardTimeoutSeconds: 1800, maxSteps: 100, stepEnforcement: 'unavailable' };
16
+ const reasons = new Set(['OK', 'AWAITING_HANDSHAKE', 'PROCESS_ONLY', 'ACCEPTED', 'PROGRESS', 'QUIET', 'NO_PROGRESS',
17
+ 'STARTUP_DEADLINE', 'HARD_DEADLINE', 'STEP_LIMIT', 'AUTH', 'RATE_LIMIT', 'NETWORK', 'MODEL', 'TRUST', 'STDIN_WAIT',
18
+ 'SANDBOX', 'NOT_FOUND', 'RESOURCE', 'PROVIDER_ERROR', 'CRASH', 'EXIT_ERROR', 'FINAL_INVALID', 'CANCELLED',
19
+ 'DISCONNECTED', 'OBSERVER_GAP', 'CAPTURE_WRITE', 'OUTPUT_LIMIT', 'MALFORMED_FRAME', 'SOURCE_GAP', 'IDENTITY_CHANGED']);
20
+ const reason = value => reasons.has(value) ? value : 'PROVIDER_ERROR';
21
+ const field = (event, key) => key.split('.').reduce((node, part) => node?.[part], event);
22
+
23
+ /** Missing vendor identities stay unavailable; an observed mismatch never crosses runs. */
24
+ export function healthEventMatchesBinding(event, binding, fields = ['invocationId', 'requestId', 'sessionId', 'runId']) {
25
+ if (!event || typeof event !== 'object' || Array.isArray(event)) return false;
26
+ const keys = { session_id: 'sessionId', thread_id: 'sessionId', sessionID: 'sessionId', id: 'sessionId',
27
+ 'stream.id': 'sessionId', 'payload.run_id': 'runId', run_id: 'runId', request_id: 'requestId' };
28
+ return fields.every(key => {
29
+ const actual = field(event, key);
30
+ const expected = binding[keys[key] ?? key];
31
+ if (actual !== undefined && expected != null && actual !== expected) {
32
+ binding.onIdentityConflict?.();
33
+ return false;
34
+ }
35
+ return true;
36
+ });
37
+ }
38
+
39
+ /** Heuristic diagnoses are never terminal proof; arbitrary stdout prose is ignored. */
40
+ export function classifyWorkerHealthFailure(value, { source = {}, provider = '' } = {}) {
41
+ const typed = value && typeof value === 'object' && !Array.isArray(value);
42
+ if (source.kind !== 'stderr' && !typed) return null;
43
+ const text = (typed ? JSON.stringify(value) : String(value)).slice(0, 8192);
44
+ let code = null;
45
+ if (/\b(?:ENOENT|EACCES)\b|command not found|no such executable/i.test(text)) code = 'NOT_FOUND';
46
+ else if (/\b(?:EPERM|EROFS)\b|read-only file system|operation not permitted/i.test(text)) code = 'SANDBOX';
47
+ else if (/\b429\b|rate.limit|quota.exceeded/i.test(text)) code = 'RATE_LIMIT';
48
+ else if (/\b(?:401|403)\b|authentication (?:failed|expired)|unauthorized|invalid api key|login required/i.test(text)) code = 'AUTH';
49
+ else if (/model[_ -](?:not[_ -]found|unavailable)|unknown model|invalid (?:model|effort)/i.test(text)) code = 'MODEL';
50
+ else if (/Reading additional input from stdin|waiting for stdin|stdin.wait/i.test(text)) code = 'STDIN_WAIT';
51
+ else if (/trust (?:this )?(?:workspace|directory)|approval required|workspace (?:is )?untrusted/i.test(text)) code = 'TRUST';
52
+ else if (/\b(?:ENOSPC|ENOMEM|OOM)\b|out of memory|disk full/i.test(text)) code = 'RESOURCE';
53
+ else if (/\b(?:ECONNRESET|ENOTFOUND|ETIMEDOUT|EAI_AGAIN)\b|TLS (?:error|failed)|\b50[234]\b/i.test(text)) code = 'NETWORK';
54
+ else if (typed) code = 'PROVIDER_ERROR';
55
+ if (!code) return null;
56
+ const retry = /retry.after["\s:=]+(\d+(?:\.\d+)?)/i.exec(text);
57
+ return { kind: 'error', reasonCode: code, provider,
58
+ ...(retry ? { retryAfterSeconds: Math.min(86400, Number(retry[1])) } : {}) };
59
+ }
60
+
61
+ export function observeProviderHealthEvent(provider, event, source, binding) {
62
+ if (!healthEventMatchesBinding(event, binding)) return null;
63
+ if (provider === 'hermes') {
64
+ // Only an explicitly located, session-filtered log is qualified. Plain -z
65
+ // stdout/usage and agent.log growth cannot establish semantic progress.
66
+ if (source.kind !== 'session-log' || !binding.sessionId || event.session_id !== binding.sessionId) return null;
67
+ if (event.marker === 'turn_started') return { kind: 'accepted' };
68
+ if (event.marker === 'tool_completed' && typeof event.tool_call_id === 'string') return { kind: 'progress', key: event.tool_call_id };
69
+ if (event.marker === 'turn_completed') return { kind: 'terminal', success: true };
70
+ } else if (provider === 'antigravity') {
71
+ if (event.event === 'init') return { kind: 'setup', sessionId: event.session_id };
72
+ if (event.event === 'step_update' && event.step_update?.state === 'DONE' && Number.isSafeInteger(event.step_update.step_index)) return { kind: 'progress', key: `step:${event.step_update.step_index}` };
73
+ if (event.event === 'result') return { kind: 'terminal', success: event.result?.status === 'SUCCESS' };
74
+ if (event.event === 'error') return classifyWorkerHealthFailure(event, { source, provider });
75
+ } else if (provider === 'grok-build') {
76
+ // streaming-json is ACP NDJSON, not the Messages API stream. Missing
77
+ // identities stay unknown; a generic tool id is not a session binding.
78
+ if (source?.kind !== 'stdout') return null;
79
+ if (event.type === 'error') return { ...classifyWorkerHealthFailure({ type: 'error', message: event.message }, { source, provider }), fatal: true };
80
+ if (event.type === 'tool_call' || event.type === 'tool_call_update') {
81
+ if (event.status === 'failed') return { ...classifyWorkerHealthFailure({ type: 'error', message: `tool call ${event.toolCallId ?? 'unknown'} failed` }, { source, provider }), fatal: true };
82
+ return typeof event.toolCallId === 'string' && event.toolCallId
83
+ ? { kind: 'progress', key: event.toolCallId } : { kind: 'activity' };
84
+ }
85
+ if (['text', 'thought', 'usage', 'plan', 'available_commands'].includes(event.type)) return { kind: 'activity' };
86
+ if (event.type === 'end') return { kind: 'terminal', success: event.stopReason === 'end_turn', key: event.stopReason ?? 'end' };
87
+ } else if (provider === 'local-http' || provider === 'ollama') {
88
+ if (typeof event.response === 'string' && event.done !== true) return { kind: 'activity' };
89
+ if (event.done === true) return { kind: 'terminal', success: !event.error };
90
+ if (event.error) return { ...classifyWorkerHealthFailure(event, { source, provider }), fatal: true };
91
+ } else if (provider === 'copilot') {
92
+ // CONSERVATIVE GUARD ONLY (roctinam/aiwg#2901 round 1): Copilot's own
93
+ // JSONL event vocabulary is not documented in --help (see
94
+ // copilot-adapter.mjs module doc) and is never invented here. This
95
+ // branch defends only against the synthetic "error-shaped JSON at exit
96
+ // 0" shape — `type: "error"` or a top-level non-null `error` field — so
97
+ // it can never be mistaken for a completed/OK final. Mirrors
98
+ // copilot-adapter.mjs's parseOutput guard.
99
+ if (event.type === 'error' || (event.error !== undefined && event.error !== null)) {
100
+ return { ...classifyWorkerHealthFailure(event, { source, provider }), fatal: true };
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+
106
+ /** Parser/locator strings are identifiers only. No dynamic import/eval from profiles. */
107
+ export function createWorkerHealthObserver(options = {}) {
108
+ const profile = options.profile?.health ?? options.health ?? {};
109
+ const windows = { ...DEFAULTS, ...options.effectiveHealth };
110
+ const now = options.now ?? (() => Number(process.hrtime.bigint()) / 1e6);
111
+ const wallNow = options.wallNow ?? Date.now;
112
+ const started = options.startedAt ?? now();
113
+ const epoch = options.launchTime === undefined ? wallNow() : Date.parse(options.launchTime);
114
+ const utc = value => value === null ? null : new Date(epoch + value - started).toISOString();
115
+ const hardDeadline = started + windows.hardTimeoutSeconds * 1000;
116
+ const startupDeadline = started + windows.startupSeconds * 1000;
117
+ const binding = { invocationId: options.invocationId ?? 'unbound', requestId: options.requestId ?? options.invocationId,
118
+ sessionId: options.sessionId ?? null, runId: options.runId ?? null };
119
+ const normalize = options.normalizeEvent ?? ((event, source) => observeProviderHealthEvent(options.provider, event, source, binding));
120
+ const buffers = new Map();
121
+ const keys = new Set();
122
+ const progressHistory = new Set();
123
+ const replayedKeys = new Set();
124
+ const events = [];
125
+ const gaps = [];
126
+ let sequence = 0, generation = 0, steps = null, total = 0, lastCheck = started, maxGap = 0;
127
+ let observed = null, activity = null, progress = null, heartbeat = null, handshakeAt = null, stalledAt = null;
128
+ let state = 'launching', handshake = 'unobserved', confidence = 'unobserved', reasonCode = 'AWAITING_HANDSHAKE';
129
+ let termination = 'running', transitionCount = 0, live = null, disconnected = false, finalized = false;
130
+ let terminal = null, fatalFailure = false, cpuTimeMs = null, contentDigest = options.contentDigest ?? null;
131
+ const unavailable = new Set(profile.handshake === 'semantic' ? [] : ['semantic-start']);
132
+ if (!profile.progressEvidence?.length) unavailable.add('semantic-progress');
133
+ unavailable.add('steps');
134
+ const transition = (next, code, certainty = 'observed') => {
135
+ if (state !== next) { transitionCount = Math.min(1000000, transitionCount + 1); state = next; }
136
+ reasonCode = reason(code); confidence = certainty;
137
+ };
138
+ const sourceOf = source => ({ kind: source?.kind ?? 'stdout', id: source?.id ?? source?.kind ?? 'stdout',
139
+ generation: source?.generation ?? 0, offset: source?.offset ?? 0 });
140
+ const gap = (code = 'OBSERVER_GAP', source = {}) => {
141
+ generation++;
142
+ gaps.push({ sequence: ++sequence, reasonCode: reason(code), source: digest(sourceOf(source).id) });
143
+ if (gaps.length > 32) gaps.shift();
144
+ unavailable.add('observer-continuity');
145
+ };
146
+ const observe = (event, suppliedSource = {}) => {
147
+ if (finalized || !event || typeof event !== 'object') return;
148
+ const source = sourceOf(suppliedSource);
149
+ if (suppliedSource.invocationId != null && suppliedSource.invocationId !== binding.invocationId) return;
150
+ if (!['setup', 'accepted', 'progress', 'activity', 'terminal', 'error', 'heartbeat'].includes(event.kind)) return;
151
+ if (['setup', 'accepted'].includes(event.kind)) {
152
+ // Stdout identities provide intra-run correlation only, never authority
153
+ // over a process or filesystem. The first binding wins, atomically;
154
+ // conflicting later setup is an anomaly and cannot rebind either field.
155
+ if (['sessionId', 'runId'].some(key => event[key] != null && binding[key] != null && binding[key] !== event[key])) {
156
+ gap('IDENTITY_CHANGED', source);
157
+ return;
158
+ }
159
+ for (const key of ['sessionId', 'runId']) {
160
+ if (event[key] != null && source.kind === 'stdout' && binding[key] == null) binding[key] = event[key];
161
+ }
162
+ }
163
+ const time = now();
164
+ observed = time;
165
+ if (time >= hardDeadline) { reasonCode = 'HARD_DEADLINE'; return; }
166
+ if (fatalFailure && event.kind !== 'error' && event.kind !== 'heartbeat') return;
167
+ const rawKey = event.key ?? event.identity;
168
+ const keyDigest = typeof rawKey === 'string' && rawKey.length <= 4096 ? digest(rawKey) : null;
169
+ if (event.kind === 'progress') {
170
+ if (!keyDigest) return;
171
+ const qualified = handshakeAt !== null && (profile.progressEvidence?.length || source.kind === 'worktree');
172
+ if (qualified) activity = time;
173
+ if (source.kind !== 'worktree' && qualified) {
174
+ if (unavailable.has('progress-key-churn')) return;
175
+ const replay = progressHistory.has(keyDigest);
176
+ if (!replay) replayedKeys.clear();
177
+ else if (!keys.has(keyDigest)) replayedKeys.add(keyDigest);
178
+ // Refresh history on replay so a cycling set within this explicit
179
+ // horizon cannot become novel by evicting its oldest identities.
180
+ progressHistory.delete(keyDigest);
181
+ if (progressHistory.size >= PROGRESS_CHURN_LIMITS.historyKeys) progressHistory.delete(progressHistory.values().next().value);
182
+ progressHistory.add(keyDigest);
183
+ if (replayedKeys.size >= PROGRESS_CHURN_LIMITS.repeatedEvictedKeys) {
184
+ unavailable.add('semantic-progress');
185
+ unavailable.add('progress-key-churn');
186
+ }
187
+ if (replay) {
188
+ if (!keys.has(keyDigest)) {
189
+ if (keys.size >= MAX_KEYS) keys.delete(keys.values().next().value);
190
+ keys.add(keyDigest);
191
+ }
192
+ return;
193
+ }
194
+ }
195
+ if (keys.has(keyDigest)) return;
196
+ // Bound dedup memory without dropping new progress after saturation.
197
+ // Set insertion order makes this a FIFO window of recent identities.
198
+ if (keys.size >= MAX_KEYS) keys.delete(keys.values().next().value);
199
+ keys.add(keyDigest);
200
+ }
201
+ events.push({ sequence: ++sequence, kind: event.kind, source: digest(source.id), generation: source.generation,
202
+ offset: source.offset, keyDigest });
203
+ if (events.length > MAX_EVENTS) events.shift();
204
+ if (event.kind === 'heartbeat') heartbeat = time;
205
+ else if (event.kind === 'accepted') {
206
+ if (handshakeAt === null && profile.handshake === 'semantic') {
207
+ if (time >= startupDeadline) transition('failed-to-start', 'STARTUP_DEADLINE', 'suspected');
208
+ else { handshakeAt = time; handshake = 'semantic'; live = true; transition('started', 'ACCEPTED'); }
209
+ }
210
+ } else if (event.kind === 'progress') {
211
+ // Completed steps do not retroactively invent accepted-start evidence.
212
+ if (handshakeAt !== null && (profile.progressEvidence?.length || source.kind === 'worktree')) {
213
+ progress = time; activity = time;
214
+ // Tool/item completion establishes progress, not a model-turn count.
215
+ if (event.step === true && windows.stepEnforcement !== 'unavailable') { steps = (steps ?? 0) + 1; unavailable.delete('steps'); }
216
+ stalledAt = null; transition('active', 'PROGRESS');
217
+ }
218
+ } else if (event.kind === 'activity') activity = time;
219
+ else if (event.kind === 'error') {
220
+ if (event.fatal === true) fatalFailure = true;
221
+ terminal = event.fatal === true ? false : terminal;
222
+ reasonCode = reason(event.reasonCode);
223
+ if (handshakeAt === null) transition('failed-to-start', reasonCode, 'suspected');
224
+ } else if (event.kind === 'terminal') {
225
+ if (event.success !== true) fatalFailure = true;
226
+ terminal = event.success === true && !fatalFailure;
227
+ if (event.success !== true) reasonCode = reason(event.reasonCode);
228
+ }
229
+ };
230
+ // Adapter binding filters report conflicts even when they reject a raw
231
+ // setup frame before normalization reaches observe(). No raw ID is exported.
232
+ binding.onIdentityConflict = () => gap('IDENTITY_CHANGED');
233
+ // Read-only status resumes qualified clocks, never replays output as fresh
234
+ // progress. The caller validates the invocation, boot and timestamp bounds.
235
+ if (options.initialSnapshot) {
236
+ const initial = options.initialSnapshot;
237
+ const clock = value => value == null ? null : started + Date.parse(value) - epoch;
238
+ observed = clock(initial.lastObservedAt); activity = clock(initial.lastActivityAt);
239
+ progress = clock(initial.lastProgressAt); handshakeAt = clock(initial.handshakeAt);
240
+ stalledAt = clock(initial.stalledAt);
241
+ handshake = initial.handshake ?? 'unobserved';
242
+ state = initial.state ?? 'launching'; reasonCode = reason(initial.reasonCode);
243
+ generation = initial.observerGeneration ?? 0; transitionCount = initial.transitionCount ?? 0;
244
+ maxGap = initial.maxCheckGapSeconds ?? 0;
245
+ for (const signal of initial.unavailableSignals ?? []) unavailable.add(signal);
246
+ if (observed !== null) lastCheck = observed;
247
+ }
248
+ const feed = (bytes, suppliedSource = {}) => {
249
+ if (finalized) return;
250
+ const source = sourceOf(suppliedSource);
251
+ if (suppliedSource.offset === undefined) source.offset = buffers.get(source.id)?.nextOffset ?? 0;
252
+ const input = Buffer.from(bytes);
253
+ total += input.length;
254
+ const maxBytes = Math.min(options.maxOutputBytes ?? 10485760, 10485760);
255
+ if (total > maxBytes) { gap('OUTPUT_LIMIT', source); return; }
256
+ if (source.kind !== 'session-log') { activity = now(); observed = activity; }
257
+ const previous = buffers.get(source.id);
258
+ if (previous && (previous.generation !== source.generation || source.offset < previous.nextOffset)) {
259
+ gap('SOURCE_GAP', source); buffers.delete(source.id);
260
+ }
261
+ if (!buffers.has(source.id) && buffers.size >= 8) { gap('SOURCE_GAP', source); return; }
262
+ let cursor = buffers.get(source.id);
263
+ if (!cursor) { cursor = { bytes: Buffer.alloc(0), generation: source.generation, nextOffset: source.offset, discarding: false }; buffers.set(source.id, cursor); }
264
+ if (source.offset > cursor.nextOffset) { gap('SOURCE_GAP', source); cursor.bytes = Buffer.alloc(0); }
265
+ cursor.nextOffset = source.offset + input.length;
266
+ // stderr diagnostics are line framed regardless of stdout framing.
267
+ const framing = profile.outputFraming ?? 'opaque';
268
+ if (source.kind !== 'stderr' && source.kind !== 'session-log' && !['jsonl', 'http-stream'].includes(framing)) return;
269
+ const maxFrame = Math.min(options.maxFrameBytes ?? 65536, 1048576);
270
+ for (let start = 0; start < input.length;) {
271
+ const newline = input.indexOf(10, start);
272
+ const end = newline < 0 ? input.length : newline;
273
+ const piece = input.subarray(start, end);
274
+ if (!cursor.discarding && cursor.bytes.length + piece.length > maxFrame) {
275
+ cursor.bytes = Buffer.alloc(0); cursor.discarding = true; gap('OUTPUT_LIMIT', source);
276
+ }
277
+ if (!cursor.discarding) cursor.bytes = Buffer.concat([cursor.bytes, piece]);
278
+ if (newline >= 0) {
279
+ if (!cursor.discarding && cursor.bytes.length) {
280
+ try {
281
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(cursor.bytes);
282
+ if (source.kind === 'stderr') observe(classifyWorkerHealthFailure(text, { source, provider: options.provider }), source);
283
+ else {
284
+ const event = JSON.parse(text);
285
+ const normalized = normalize(event, source, binding);
286
+ for (const item of Array.isArray(normalized) ? normalized : [normalized]) observe(item, { ...source, offset: source.offset + newline });
287
+ }
288
+ } catch { gap('MALFORMED_FRAME', source); }
289
+ }
290
+ cursor.bytes = Buffer.alloc(0); cursor.discarding = false;
291
+ }
292
+ start = end + 1;
293
+ }
294
+ };
295
+ const check = (time = now()) => {
296
+ if (time < lastCheck) { gap('OBSERVER_GAP'); time = lastCheck; }
297
+ const elapsed = time - lastCheck;
298
+ maxGap = Math.max(maxGap, elapsed / 1000);
299
+ if (elapsed > windows.checkSeconds * 1000) gap('OBSERVER_GAP');
300
+ lastCheck = time; observed = time;
301
+ if (finalized) return snapshot();
302
+ if (time >= hardDeadline) { reasonCode = 'HARD_DEADLINE'; return snapshot(); }
303
+ if (fatalFailure) return snapshot();
304
+ if (handshakeAt === null) {
305
+ if (time >= startupDeadline) transition('failed-to-start', reasonCode === 'AWAITING_HANDSHAKE' ? 'STARTUP_DEADLINE' : reasonCode, 'suspected');
306
+ } else {
307
+ const noProgress = time - (progress ?? handshakeAt);
308
+ if (noProgress >= windows.stallSeconds * 1000) {
309
+ stalledAt ??= (progress ?? handshakeAt) + windows.stallSeconds * 1000;
310
+ if (time - stalledAt >= windows.diagnosticGraceSeconds * 1000 && live === true && !disconnected) transition('hung', 'NO_PROGRESS');
311
+ else transition('stalled', disconnected ? 'DISCONNECTED' : 'NO_PROGRESS', unavailable.has('semantic-progress') || disconnected ? 'suspected' : 'observed');
312
+ } else if (time - (activity ?? handshakeAt) >= windows.quietSeconds * 1000) transition('quiet', 'QUIET', 'suspected');
313
+ else if (progress !== null) transition('active', 'PROGRESS');
314
+ }
315
+ if (windows.stepEnforcement !== 'unavailable' && steps !== null && steps >= windows.maxSteps) reasonCode = 'STEP_LIMIT';
316
+ return snapshot();
317
+ };
318
+ const snapshot = () => ({ version: 1, state,
319
+ coverage: profile.coverage === 'unobserved' ? 'unobserved' : profile.coverage === 'full' && !gaps.length && !unavailable.has('progress-key-churn') ? 'full' : 'degraded',
320
+ handshake, lastObservedAt: utc(observed), lastActivityAt: utc(activity), lastProgressAt: utc(progress), reasonCode,
321
+ termination, transitionCount, restartCount: 0, unavailableSignals: [...unavailable].slice(0, 32), transitionRefs: [],
322
+ observerGeneration: generation, startupDeadlineAt: utc(startupDeadline), hardDeadlineAt: utc(hardDeadline),
323
+ maxCheckGapSeconds: maxGap, confidence, handshakeAt: utc(handshakeAt), stalledAt: utc(stalledAt) });
324
+ return {
325
+ feed, observe, gap, check, snapshot,
326
+ shouldStop() { return fatalFailure; },
327
+ processStarted(identity) {
328
+ if (options.transport === 'mcp') return; // shared server process is not this request
329
+ if (!identity || !Number.isSafeInteger(identity.pid) || !identity.startTicks || !identity.executable) return;
330
+ live = true; observed = now();
331
+ if (profile.handshake === 'process-only' && handshakeAt === null) {
332
+ if (now() >= startupDeadline) transition('failed-to-start', 'STARTUP_DEADLINE', 'suspected');
333
+ else { handshakeAt = now(); handshake = 'process-only'; transition('started', 'PROCESS_ONLY'); }
334
+ }
335
+ },
336
+ sampleActivity({ running = null, cpu = null, content = null } = {}) {
337
+ if (finalized) return;
338
+ observed = now(); live = running;
339
+ if (options.transport !== 'mcp' && cpu !== null && Number.isFinite(cpu) && cpu >= 0) {
340
+ if (cpuTimeMs !== null && cpu > cpuTimeMs) activity = now();
341
+ cpuTimeMs = cpu;
342
+ } else unavailable.add('cpu');
343
+ if (content !== null) {
344
+ if (contentDigest !== null && contentDigest !== content) observe({ kind: 'progress', key: `content:${sequence}:${content}` }, { kind: 'worktree', id: 'owned-worktree' });
345
+ contentDigest = content;
346
+ } else unavailable.add('worktree-content');
347
+ },
348
+ disconnect() { disconnected = true; live = null; termination = 'uncertain'; gap('DISCONNECTED'); reasonCode = 'DISCONNECTED'; },
349
+ finish(result = {}) {
350
+ if (finalized) return snapshot();
351
+ for (const [id, cursor] of buffers) if (cursor.bytes.length || cursor.discarding) gap('MALFORMED_FRAME', { id });
352
+ finalized = true; observed = now(); termination = result.termination ?? 'uncertain';
353
+ if (termination !== 'confirmed' || disconnected) {
354
+ termination = 'uncertain';
355
+ if (result.signal || result.exitCode != null && result.exitCode !== 0) transition(handshakeAt === null ? 'failed-to-start' : 'crashed', 'CRASH');
356
+ } else if (result.cancelled || result.timedOut || now() >= hardDeadline) transition(result.cancelled ? 'cancelled' : 'failed', result.cancelled ? 'CANCELLED' : reason(result.stopReason ?? 'HARD_DEADLINE'));
357
+ else if (state === 'failed-to-start') transition('failed', reasonCode);
358
+ else if (fatalFailure) transition('failed', reasonCode);
359
+ else if (!result.errorCode && result.finalValid && result.exitCode === 0 && terminal !== false) transition('completed', 'OK');
360
+ else if (!result.errorCode && result.finalValid && options.transport === 'mcp' && result.exitCode === null && terminal === true) transition('completed', 'OK');
361
+ else transition(handshakeAt === null ? 'failed-to-start' : 'failed', result.errorCode ? reason(result.errorCode) : result.exitCode !== 0 ? 'EXIT_ERROR' : 'FINAL_INVALID');
362
+ return snapshot();
363
+ },
364
+ nextDelayMs() {
365
+ const time = now();
366
+ const pending = [time + windows.checkSeconds * 1000, hardDeadline];
367
+ if (handshakeAt === null && state !== 'failed-to-start') pending.push(startupDeadline);
368
+ if (handshakeAt !== null) {
369
+ const quiet = (activity ?? handshakeAt) + windows.quietSeconds * 1000;
370
+ const stall = (progress ?? handshakeAt) + windows.stallSeconds * 1000;
371
+ if (quiet > time) pending.push(quiet);
372
+ if (stall > time) pending.push(stall);
373
+ if (stalledAt !== null && stalledAt + windows.diagnosticGraceSeconds * 1000 > time) pending.push(stalledAt + windows.diagnosticGraceSeconds * 1000);
374
+ }
375
+ return Math.max(1, Math.min(...pending) - time);
376
+ },
377
+ evidence() { return { invocationId: binding.invocationId, sequence, observerGeneration: generation,
378
+ lastWorkerHeartbeatAt: utc(heartbeat), cpuTimeMs, steps: windows.stepEnforcement === 'unavailable' ? null : steps,
379
+ gaps: gaps.map(item => ({ ...item })), events: events.map(item => ({ ...item })) }; },
380
+ };
381
+ }
382
+
383
+ /** Same-file edits matter. Export only a digest of bounded, owned content. */
384
+ export async function sampleOwnedWorktreeContent(root, { files, maxFiles = 128, maxBytes = 1048576 } = {}) {
385
+ let candidates = files;
386
+ try {
387
+ if (!candidates) {
388
+ const status = execFileSync('git', ['--no-optional-locks', '-c', 'core.fsmonitor=false', '-c', 'core.untrackedCache=false',
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' }, killSignal: 'SIGKILL' });
391
+ candidates = [];
392
+ const records = status.split('\0');
393
+ for (let i = 0; i < records.length; i++) {
394
+ if (!records[i]) continue;
395
+ if (records[i][0] === 'R' || records[i][1] === 'R' || records[i][0] === 'C') return null;
396
+ candidates.push(records[i].slice(3));
397
+ }
398
+ }
399
+ if (!Array.isArray(candidates) || candidates.length > maxFiles) return null;
400
+ const hash = createHash('sha256');
401
+ let total = 0;
402
+ for (const name of [...new Set(candidates)].sort()) {
403
+ if (typeof name !== 'string' || path.isAbsolute(name) || name.split('/').some(part => part === '..' || !part)
404
+ || name.includes('\0')) return null;
405
+ if (name.split('/').some(part => ['.git', 'node_modules', '.cache', '__pycache__', '.aiwg'].includes(part)) || /\.(?:log|jsonl)$/.test(name)) continue;
406
+ if (!path.isAbsolute(root) || root.includes('\0') || root.split('/').includes('..')) return null;
407
+ let directory = await open('/', constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
408
+ let handle;
409
+ try {
410
+ for (const component of root.split('/').filter(Boolean)) {
411
+ const next = await open(`/proc/self/fd/${directory.fd}/${component}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
412
+ await directory.close(); directory = next;
413
+ }
414
+ const parts = name.split('/');
415
+ const leaf = parts.pop();
416
+ for (const component of parts) {
417
+ const next = await open(`/proc/self/fd/${directory.fd}/${component}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
418
+ await directory.close(); directory = next;
419
+ }
420
+ handle = await open(`/proc/self/fd/${directory.fd}/${leaf}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
421
+ const stat = await handle.stat();
422
+ if (!stat.isFile() || stat.nlink !== 1 || stat.size + total > maxBytes) return null;
423
+ const buffer = Buffer.alloc(maxBytes - total + 1);
424
+ let length = 0;
425
+ while (length < buffer.length) {
426
+ const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
427
+ if (!bytesRead) break;
428
+ length += bytesRead;
429
+ }
430
+ const bytes = buffer.subarray(0, length);
431
+ total += length;
432
+ if (total > maxBytes) return null;
433
+ hash.update(name); hash.update('\0'); hash.update(bytes); hash.update('\0');
434
+ } catch (error) { if (error.code !== 'ENOENT') return null; hash.update(name); hash.update(':deleted\0'); }
435
+ finally { await handle?.close(); await directory.close(); }
436
+ }
437
+ return `sha256:${hash.digest('hex')}`;
438
+ } catch { return null; }
439
+ }