harnery 0.40.0 → 0.41.0

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 (441) hide show
  1. package/README.md +1 -1
  2. package/dist/commander.js +1 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +25 -19
  5. package/dist/commands/browse-ai.d.ts.map +1 -1
  6. package/dist/commands/browse-ai.js +4 -0
  7. package/dist/commands/browse.d.ts.map +1 -1
  8. package/dist/commands/browse.js +141 -10
  9. package/dist/commands/checkpoint.js +8 -12
  10. package/dist/commands/deinit.d.ts +1 -1
  11. package/dist/commands/deinit.d.ts.map +1 -1
  12. package/dist/commands/deinit.js +8 -4
  13. package/dist/commands/docs.d.ts.map +1 -1
  14. package/dist/commands/docs.js +2 -0
  15. package/dist/commands/doctor.js +10 -2
  16. package/dist/commands/fetch.d.ts.map +1 -1
  17. package/dist/commands/fetch.js +10 -0
  18. package/dist/commands/init.d.ts +19 -3
  19. package/dist/commands/init.d.ts.map +1 -1
  20. package/dist/commands/init.js +232 -104
  21. package/dist/commands/instructions.js +5 -4
  22. package/dist/commands/ledger-v3.d.ts.map +1 -1
  23. package/dist/commands/ledger-v3.js +2 -1
  24. package/dist/commands/webview.d.ts +4 -0
  25. package/dist/commands/webview.d.ts.map +1 -0
  26. package/dist/commands/webview.js +94 -0
  27. package/dist/core/adapter.d.ts +50 -1
  28. package/dist/core/adapter.d.ts.map +1 -1
  29. package/dist/core/adapter.js +59 -1
  30. package/dist/core/adapters/attestation.d.ts.map +1 -1
  31. package/dist/core/adapters/attestation.js +3 -2
  32. package/dist/core/adapters/profiles.d.ts +20 -3
  33. package/dist/core/adapters/profiles.d.ts.map +1 -1
  34. package/dist/core/adapters/profiles.js +32 -1
  35. package/dist/core/adapters/registry.d.ts.map +1 -1
  36. package/dist/core/adapters/registry.js +33 -0
  37. package/dist/core/agents/canonical-emit.d.ts +1 -1
  38. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  39. package/dist/core/agents/canonical-emit.js +2 -7
  40. package/dist/core/agents/cli.js +18 -16
  41. package/dist/core/agents/coord-client.d.ts.map +1 -1
  42. package/dist/core/agents/coord-client.js +7 -2
  43. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  44. package/dist/core/agents/rules/claim-conflict.js +3 -9
  45. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  46. package/dist/core/agents/rules/commit-conflict.js +2 -8
  47. package/dist/core/agents/rules/stop-hook.d.ts +2 -0
  48. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  49. package/dist/core/agents/rules/stop-hook.js +1 -1
  50. package/dist/core/agents/session-events.d.ts +6 -2
  51. package/dist/core/agents/session-events.d.ts.map +1 -1
  52. package/dist/core/agents/session-events.js +105 -26
  53. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -1
  54. package/dist/core/agents/session-finalization-state-v3.js +2 -1
  55. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
  56. package/dist/core/agents/session-finalizer-v3.js +5 -4
  57. package/dist/core/agents/session-name-display.d.ts +28 -0
  58. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  59. package/dist/core/agents/session-name-display.js +43 -0
  60. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  61. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  62. package/dist/core/agents/state/heartbeat-writer.d.ts +10 -0
  63. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  64. package/dist/core/agents/state/heartbeat-writer.js +21 -2
  65. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  66. package/dist/core/agents/state/live-coordination-view.js +1 -0
  67. package/dist/core/agents/state/live-coordination-writer.js +3 -0
  68. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  69. package/dist/core/agents/state/stale-sweep.js +2 -10
  70. package/dist/core/agents/turn-changes.d.ts +17 -0
  71. package/dist/core/agents/turn-changes.d.ts.map +1 -0
  72. package/dist/core/agents/turn-changes.js +76 -0
  73. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  74. package/dist/core/artifacts/delivery-card.js +2 -1
  75. package/dist/core/artifacts/index.d.ts.map +1 -1
  76. package/dist/core/artifacts/index.js +4 -3
  77. package/dist/core/config.d.ts +5 -0
  78. package/dist/core/config.d.ts.map +1 -1
  79. package/dist/core/config.js +14 -0
  80. package/dist/core/context/index.d.ts.map +1 -1
  81. package/dist/core/context/index.js +2 -1
  82. package/dist/core/conversations/archive.d.ts.map +1 -1
  83. package/dist/core/conversations/archive.js +4 -3
  84. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  85. package/dist/core/diagnostics/bundle.js +3 -2
  86. package/dist/core/events/legacy-storage/canary.d.ts.map +1 -1
  87. package/dist/core/events/legacy-storage/canary.js +5 -4
  88. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -1
  89. package/dist/core/events/legacy-storage/compression.js +2 -1
  90. package/dist/core/events/v3/adapter-id.d.ts +10 -2
  91. package/dist/core/events/v3/adapter-id.d.ts.map +1 -1
  92. package/dist/core/events/v3/adapter-id.js +16 -0
  93. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  94. package/dist/core/events/v3/authority-outbox.js +2 -1
  95. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  96. package/dist/core/events/v3/bootstrap.js +5 -4
  97. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  98. package/dist/core/events/v3/capabilities.js +17 -0
  99. package/dist/core/events/v3/contract.d.ts +5 -0
  100. package/dist/core/events/v3/contract.d.ts.map +1 -1
  101. package/dist/core/events/v3/contract.js +7 -1
  102. package/dist/core/events/v3/control-witness.d.ts.map +1 -1
  103. package/dist/core/events/v3/control-witness.js +3 -2
  104. package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -1
  105. package/dist/core/events/v3/coordination-transaction-recovery.js +4 -3
  106. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -1
  107. package/dist/core/events/v3/fingerprint-keys.js +7 -6
  108. package/dist/core/events/v3/generated.d.ts +1 -1
  109. package/dist/core/events/v3/generated.js +1 -1
  110. package/dist/core/events/v3/live-feed.d.ts.map +1 -1
  111. package/dist/core/events/v3/live-feed.js +5 -4
  112. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  113. package/dist/core/events/v3/producers/command-recorder.js +8 -7
  114. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  115. package/dist/core/events/v3/producers/coordination-recorder.js +8 -7
  116. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -1
  117. package/dist/core/events/v3/producers/diagnostic-summaries.js +5 -4
  118. package/dist/core/events/v3/producers/hook-base.d.ts +1 -0
  119. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  120. package/dist/core/events/v3/producers/hook-base.js +10 -4
  121. package/dist/core/events/v3/producers/hook.d.ts +2 -0
  122. package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
  123. package/dist/core/events/v3/producers/hook.js +16 -1
  124. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  125. package/dist/core/events/v3/producers/intake.js +16 -6
  126. package/dist/core/events/v3/producers/recorder.d.ts +2 -4
  127. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  128. package/dist/core/events/v3/producers/recorder.js +109 -101
  129. package/dist/core/events/v3/recovery.d.ts.map +1 -1
  130. package/dist/core/events/v3/recovery.js +4 -3
  131. package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -1
  132. package/dist/core/events/v3/support-storage/inventory.js +2 -1
  133. package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -1
  134. package/dist/core/events/v3/support-storage/maintenance.js +4 -3
  135. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  136. package/dist/core/events/v3/support-storage/pack-reader.js +4 -3
  137. package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -1
  138. package/dist/core/events/v3/support-storage/pack-writer.js +6 -5
  139. package/dist/core/events/v3/tool-line-changes.d.ts +8 -0
  140. package/dist/core/events/v3/tool-line-changes.d.ts.map +1 -0
  141. package/dist/core/events/v3/tool-line-changes.js +135 -0
  142. package/dist/core/events/v3/writer.d.ts.map +1 -1
  143. package/dist/core/events/v3/writer.js +8 -7
  144. package/dist/core/governor/planning.d.ts.map +1 -1
  145. package/dist/core/governor/planning.js +6 -5
  146. package/dist/core/governor/service.d.ts.map +1 -1
  147. package/dist/core/governor/service.js +12 -11
  148. package/dist/core/governor/state.d.ts.map +1 -1
  149. package/dist/core/governor/state.js +7 -6
  150. package/dist/core/guard/evaluator.js +1 -0
  151. package/dist/core/guard/storage.d.ts.map +1 -1
  152. package/dist/core/guard/storage.js +3 -2
  153. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts +4 -0
  154. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts.map +1 -0
  155. package/dist/core/hooks/adapter/behaviors/claude-code.js +31 -0
  156. package/dist/core/hooks/adapter/behaviors/codex.d.ts +15 -0
  157. package/dist/core/hooks/adapter/behaviors/codex.d.ts.map +1 -0
  158. package/dist/core/hooks/adapter/behaviors/codex.js +82 -0
  159. package/dist/core/hooks/adapter/behaviors/cursor.d.ts +7 -0
  160. package/dist/core/hooks/adapter/behaviors/cursor.d.ts.map +1 -0
  161. package/dist/core/hooks/adapter/behaviors/cursor.js +35 -0
  162. package/dist/core/hooks/adapter/behaviors/index.d.ts +11 -0
  163. package/dist/core/hooks/adapter/behaviors/index.d.ts.map +1 -0
  164. package/dist/core/hooks/adapter/behaviors/index.js +20 -0
  165. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts +7 -0
  166. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts.map +1 -0
  167. package/dist/core/hooks/adapter/behaviors/openclaw.js +34 -0
  168. package/dist/core/hooks/adapter/behaviors/opencode.d.ts +7 -0
  169. package/dist/core/hooks/adapter/behaviors/opencode.d.ts.map +1 -0
  170. package/dist/core/hooks/adapter/behaviors/opencode.js +34 -0
  171. package/dist/core/hooks/adapter/behaviors/types.d.ts +89 -0
  172. package/dist/core/hooks/adapter/behaviors/types.d.ts.map +1 -0
  173. package/dist/core/hooks/adapter/behaviors/types.js +1 -0
  174. package/dist/core/hooks/adapter/detect.d.ts +1 -1
  175. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  176. package/dist/core/hooks/adapter/detect.js +2 -3
  177. package/dist/core/hooks/adapter/events.d.ts +29 -1
  178. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  179. package/dist/core/hooks/adapter/events.js +31 -0
  180. package/dist/core/hooks/adapter/opencode-plugin.d.ts +60 -0
  181. package/dist/core/hooks/adapter/opencode-plugin.d.ts.map +1 -0
  182. package/dist/core/hooks/adapter/opencode-plugin.js +129 -0
  183. package/dist/core/hooks/adapter/output.d.ts +7 -0
  184. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  185. package/dist/core/hooks/adapter/output.js +8 -1
  186. package/dist/core/hooks/adapter/wiring.d.ts +15 -1
  187. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  188. package/dist/core/hooks/adapter/wiring.js +74 -11
  189. package/dist/core/hooks/cli.js +635 -529
  190. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  191. package/dist/core/hooks/effects/image-capture.js +2 -1
  192. package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -1
  193. package/dist/core/hooks/prompt-context/contract.js +1 -0
  194. package/dist/core/hooks/resolve/transcript.d.ts +1 -1
  195. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  196. package/dist/core/hooks/resolve/transcript.js +35 -4
  197. package/dist/core/hooks/session-name-presence.d.ts +3 -1
  198. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  199. package/dist/core/hooks/session-name-presence.js +7 -2
  200. package/dist/core/hooks/shell-waiter.d.ts +2 -0
  201. package/dist/core/hooks/shell-waiter.d.ts.map +1 -0
  202. package/dist/core/hooks/shell-waiter.js +173 -0
  203. package/dist/core/resources/service.d.ts.map +1 -1
  204. package/dist/core/resources/service.js +5 -4
  205. package/dist/core/semantic/review.d.ts.map +1 -1
  206. package/dist/core/semantic/review.js +5 -4
  207. package/dist/core/semantic/service.d.ts.map +1 -1
  208. package/dist/core/semantic/service.js +15 -14
  209. package/dist/core/semantic/storage.d.ts.map +1 -1
  210. package/dist/core/semantic/storage.js +5 -4
  211. package/dist/core/storage/atomic-json.d.ts.map +1 -1
  212. package/dist/core/storage/atomic-json.js +7 -3
  213. package/dist/core/storage/durable-history.d.ts.map +1 -1
  214. package/dist/core/storage/durable-history.js +6 -5
  215. package/dist/core/storage/maintenance-providers.d.ts.map +1 -1
  216. package/dist/core/storage/maintenance-providers.js +2 -1
  217. package/dist/core/storage/maintenance.d.ts.map +1 -1
  218. package/dist/core/storage/maintenance.js +7 -6
  219. package/dist/core/storage/metrics.d.ts.map +1 -1
  220. package/dist/core/storage/metrics.js +5 -1
  221. package/dist/core/storage/modes.d.ts +17 -0
  222. package/dist/core/storage/modes.d.ts.map +1 -0
  223. package/dist/core/storage/modes.js +47 -0
  224. package/dist/core/storage/process-log.d.ts.map +1 -1
  225. package/dist/core/storage/process-log.js +3 -2
  226. package/dist/core/storage/receipts.d.ts.map +1 -1
  227. package/dist/core/storage/receipts.js +4 -3
  228. package/dist/core/storage/segments.d.ts.map +1 -1
  229. package/dist/core/storage/segments.js +7 -6
  230. package/dist/core/supervisor/hook-health.d.ts +2 -1
  231. package/dist/core/supervisor/hook-health.d.ts.map +1 -1
  232. package/dist/core/supervisor/hook-health.js +4 -3
  233. package/dist/core/supervisor/service.d.ts.map +1 -1
  234. package/dist/core/supervisor/service.js +7 -6
  235. package/dist/core/work/state.d.ts.map +1 -1
  236. package/dist/core/work/state.js +8 -7
  237. package/dist/core/workflow/adapters.d.ts +7 -0
  238. package/dist/core/workflow/adapters.d.ts.map +1 -1
  239. package/dist/core/workflow/adapters.js +9 -0
  240. package/dist/core/workflow/approvals.d.ts.map +1 -1
  241. package/dist/core/workflow/approvals.js +6 -5
  242. package/dist/core/workflow/billing.d.ts.map +1 -1
  243. package/dist/core/workflow/billing.js +33 -4
  244. package/dist/core/workflow/durable-record.d.ts.map +1 -1
  245. package/dist/core/workflow/durable-record.js +4 -3
  246. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  247. package/dist/core/workflow/live-session-v3.js +2 -3
  248. package/dist/core/workflow/run-state.d.ts.map +1 -1
  249. package/dist/core/workflow/run-state.js +3 -2
  250. package/dist/core/workflow/spawn-opencode.d.ts +36 -0
  251. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -0
  252. package/dist/core/workflow/spawn-opencode.js +132 -0
  253. package/dist/core/workflow/transcript.d.ts.map +1 -1
  254. package/dist/core/workflow/transcript.js +3 -2
  255. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  256. package/dist/core/workflow/workspaces/leases.js +5 -4
  257. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  258. package/dist/core/workflow/workspaces/local-git.js +3 -2
  259. package/dist/core/workflow/workspaces/paths.d.ts.map +1 -1
  260. package/dist/core/workflow/workspaces/paths.js +2 -1
  261. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  262. package/dist/core/workflow/workspaces/state.js +10 -9
  263. package/dist/lazy-commands.js +8 -1
  264. package/dist/lib/agent-browser/client.d.ts +9 -0
  265. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  266. package/dist/lib/agent-browser/client.js +13 -0
  267. package/dist/lib/browser/client.d.ts +35 -1
  268. package/dist/lib/browser/client.d.ts.map +1 -1
  269. package/dist/lib/browser/client.js +117 -8
  270. package/dist/lib/browser/identity.d.ts +25 -0
  271. package/dist/lib/browser/identity.d.ts.map +1 -0
  272. package/dist/lib/browser/identity.js +66 -0
  273. package/dist/lib/browser/index.d.ts +2 -1
  274. package/dist/lib/browser/index.d.ts.map +1 -1
  275. package/dist/lib/browser/index.js +2 -1
  276. package/dist/lib/browser/launch-args.d.ts +26 -0
  277. package/dist/lib/browser/launch-args.d.ts.map +1 -1
  278. package/dist/lib/browser/launch-args.js +41 -1
  279. package/dist/lib/browser/user-agent.d.ts +53 -0
  280. package/dist/lib/browser/user-agent.d.ts.map +1 -0
  281. package/dist/lib/browser/user-agent.js +79 -0
  282. package/dist/lib/docs-metadata-managed.d.ts +1 -0
  283. package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
  284. package/dist/lib/docs-metadata-managed.js +17 -1
  285. package/dist/lib/identities/assume.d.ts.map +1 -1
  286. package/dist/lib/identities/assume.js +4 -9
  287. package/dist/lib/instructions/apply.d.ts.map +1 -1
  288. package/dist/lib/instructions/apply.js +2 -0
  289. package/dist/lib/pace/index.d.ts +2 -0
  290. package/dist/lib/pace/index.d.ts.map +1 -0
  291. package/dist/lib/pace/index.js +1 -0
  292. package/dist/lib/pace/pace.d.ts +95 -0
  293. package/dist/lib/pace/pace.d.ts.map +1 -0
  294. package/dist/lib/pace/pace.js +313 -0
  295. package/dist/lib/tunnel/forward-headers.d.ts +11 -0
  296. package/dist/lib/tunnel/forward-headers.d.ts.map +1 -0
  297. package/dist/lib/tunnel/forward-headers.js +17 -0
  298. package/dist/lib/tunnel/gate.js +15 -10
  299. package/dist/lib/webview/index.d.ts +2 -0
  300. package/dist/lib/webview/index.d.ts.map +1 -0
  301. package/dist/lib/webview/index.js +1 -0
  302. package/dist/lib/webview/webview.d.ts +116 -0
  303. package/dist/lib/webview/webview.d.ts.map +1 -0
  304. package/dist/lib/webview/webview.js +289 -0
  305. package/opencode-plugin/package.json +19 -0
  306. package/opencode-plugin/src/index.ts +791 -0
  307. package/package.json +11 -4
  308. package/schemas/config.schema.json +13 -0
  309. package/schemas/event-v3.schema.json +164 -0
  310. package/src/commander.ts +7 -0
  311. package/src/commands/agents.ts +33 -18
  312. package/src/commands/browse-ai.ts +8 -0
  313. package/src/commands/browse.ts +189 -10
  314. package/src/commands/checkpoint.ts +7 -9
  315. package/src/commands/deinit.ts +9 -4
  316. package/src/commands/docs.ts +2 -0
  317. package/src/commands/doctor.ts +11 -2
  318. package/src/commands/fetch.ts +14 -0
  319. package/src/commands/init.ts +281 -119
  320. package/src/commands/instructions.ts +8 -4
  321. package/src/commands/ledger-v3.ts +2 -1
  322. package/src/commands/webview.ts +159 -0
  323. package/src/core/adapter.ts +102 -1
  324. package/src/core/adapters/attestation.ts +3 -2
  325. package/src/core/adapters/profiles.ts +42 -1
  326. package/src/core/adapters/registry.ts +37 -0
  327. package/src/core/agents/canonical-emit.ts +2 -5
  328. package/src/core/agents/cli.ts +18 -17
  329. package/src/core/agents/coord-client.ts +9 -3
  330. package/src/core/agents/rules/claim-conflict.ts +3 -8
  331. package/src/core/agents/rules/commit-conflict.ts +2 -7
  332. package/src/core/agents/rules/stop-hook.ts +3 -1
  333. package/src/core/agents/session-events.ts +120 -26
  334. package/src/core/agents/session-finalization-state-v3.ts +2 -1
  335. package/src/core/agents/session-finalizer-v3.ts +5 -4
  336. package/src/core/agents/session-name-display.ts +48 -0
  337. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  338. package/src/core/agents/state/heartbeat-writer.ts +24 -2
  339. package/src/core/agents/state/live-coordination-view.ts +1 -0
  340. package/src/core/agents/state/live-coordination-writer.ts +3 -0
  341. package/src/core/agents/state/stale-sweep.ts +2 -9
  342. package/src/core/agents/turn-changes.ts +97 -0
  343. package/src/core/artifacts/delivery-card.ts +2 -1
  344. package/src/core/artifacts/index.ts +4 -3
  345. package/src/core/config.ts +23 -0
  346. package/src/core/context/index.ts +2 -1
  347. package/src/core/conversations/archive.ts +4 -3
  348. package/src/core/diagnostics/bundle.ts +3 -2
  349. package/src/core/events/legacy-storage/canary.ts +5 -4
  350. package/src/core/events/legacy-storage/compression.ts +2 -1
  351. package/src/core/events/v3/adapter-id.ts +29 -1
  352. package/src/core/events/v3/authority-outbox.ts +2 -1
  353. package/src/core/events/v3/bootstrap.ts +5 -4
  354. package/src/core/events/v3/capabilities.ts +17 -0
  355. package/src/core/events/v3/contract.ts +9 -1
  356. package/src/core/events/v3/control-witness.ts +3 -2
  357. package/src/core/events/v3/coordination-transaction-recovery.ts +4 -3
  358. package/src/core/events/v3/fingerprint-keys.ts +7 -6
  359. package/src/core/events/v3/generated.ts +1 -1
  360. package/src/core/events/v3/live-feed.ts +5 -4
  361. package/src/core/events/v3/producers/command-recorder.ts +8 -6
  362. package/src/core/events/v3/producers/coordination-recorder.ts +8 -7
  363. package/src/core/events/v3/producers/diagnostic-summaries.ts +5 -4
  364. package/src/core/events/v3/producers/hook-base.ts +11 -4
  365. package/src/core/events/v3/producers/hook.ts +19 -1
  366. package/src/core/events/v3/producers/intake.ts +18 -6
  367. package/src/core/events/v3/producers/recorder.ts +123 -114
  368. package/src/core/events/v3/recovery.ts +4 -3
  369. package/src/core/events/v3/support-storage/inventory.ts +2 -1
  370. package/src/core/events/v3/support-storage/maintenance.ts +8 -3
  371. package/src/core/events/v3/support-storage/pack-reader.ts +4 -3
  372. package/src/core/events/v3/support-storage/pack-writer.ts +6 -5
  373. package/src/core/events/v3/tool-line-changes.ts +139 -0
  374. package/src/core/events/v3/writer.ts +8 -7
  375. package/src/core/governor/planning.ts +6 -5
  376. package/src/core/governor/service.ts +12 -11
  377. package/src/core/governor/state.ts +7 -6
  378. package/src/core/guard/evaluator.ts +1 -0
  379. package/src/core/guard/storage.ts +3 -2
  380. package/src/core/hooks/adapter/behaviors/claude-code.ts +33 -0
  381. package/src/core/hooks/adapter/behaviors/codex.ts +96 -0
  382. package/src/core/hooks/adapter/behaviors/cursor.ts +37 -0
  383. package/src/core/hooks/adapter/behaviors/index.ts +31 -0
  384. package/src/core/hooks/adapter/behaviors/openclaw.ts +36 -0
  385. package/src/core/hooks/adapter/behaviors/opencode.ts +36 -0
  386. package/src/core/hooks/adapter/behaviors/types.ts +110 -0
  387. package/src/core/hooks/adapter/detect.ts +2 -3
  388. package/src/core/hooks/adapter/events.ts +53 -1
  389. package/src/core/hooks/adapter/opencode-plugin.ts +177 -0
  390. package/src/core/hooks/adapter/output.ts +8 -1
  391. package/src/core/hooks/adapter/wiring.ts +85 -11
  392. package/src/core/hooks/cli.ts +733 -537
  393. package/src/core/hooks/effects/image-capture.ts +2 -1
  394. package/src/core/hooks/prompt-context/contract.ts +1 -0
  395. package/src/core/hooks/resolve/transcript.ts +42 -5
  396. package/src/core/hooks/session-name-presence.ts +10 -3
  397. package/src/core/hooks/shell-waiter.ts +166 -0
  398. package/src/core/resources/service.ts +5 -4
  399. package/src/core/semantic/review.ts +5 -4
  400. package/src/core/semantic/service.ts +15 -14
  401. package/src/core/semantic/storage.ts +5 -4
  402. package/src/core/storage/atomic-json.ts +7 -3
  403. package/src/core/storage/durable-history.ts +6 -5
  404. package/src/core/storage/maintenance-providers.ts +2 -1
  405. package/src/core/storage/maintenance.ts +7 -6
  406. package/src/core/storage/metrics.ts +5 -1
  407. package/src/core/storage/modes.ts +53 -0
  408. package/src/core/storage/process-log.ts +3 -2
  409. package/src/core/storage/receipts.ts +4 -3
  410. package/src/core/storage/segments.ts +7 -6
  411. package/src/core/supervisor/hook-health.ts +4 -4
  412. package/src/core/supervisor/service.ts +7 -6
  413. package/src/core/work/state.ts +8 -7
  414. package/src/core/workflow/adapters.ts +15 -0
  415. package/src/core/workflow/approvals.ts +6 -5
  416. package/src/core/workflow/billing.ts +34 -4
  417. package/src/core/workflow/durable-record.ts +4 -3
  418. package/src/core/workflow/live-session-v3.ts +2 -3
  419. package/src/core/workflow/run-state.ts +3 -2
  420. package/src/core/workflow/spawn-opencode.ts +150 -0
  421. package/src/core/workflow/transcript.ts +3 -2
  422. package/src/core/workflow/workspaces/leases.ts +5 -4
  423. package/src/core/workflow/workspaces/local-git.ts +3 -2
  424. package/src/core/workflow/workspaces/paths.ts +2 -1
  425. package/src/core/workflow/workspaces/state.ts +10 -9
  426. package/src/lazy-commands.ts +11 -1
  427. package/src/lib/agent-browser/client.ts +18 -0
  428. package/src/lib/browser/client.ts +147 -9
  429. package/src/lib/browser/identity.ts +64 -0
  430. package/src/lib/browser/index.ts +15 -1
  431. package/src/lib/browser/launch-args.ts +53 -1
  432. package/src/lib/browser/user-agent.ts +130 -0
  433. package/src/lib/docs-metadata-managed.ts +21 -0
  434. package/src/lib/identities/assume.ts +7 -7
  435. package/src/lib/instructions/apply.ts +2 -0
  436. package/src/lib/pace/index.ts +14 -0
  437. package/src/lib/pace/pace.ts +370 -0
  438. package/src/lib/tunnel/forward-headers.ts +16 -0
  439. package/src/lib/tunnel/gate.ts +18 -10
  440. package/src/lib/webview/index.ts +13 -0
  441. package/src/lib/webview/webview.ts +457 -0
@@ -14,6 +14,7 @@ import {
14
14
  } from "node:fs";
15
15
  import { dirname, join, resolve } from "node:path";
16
16
  import type { ParsedPayload } from "../../../hooks/adapter/parse.ts";
17
+ import { stateDirMode, stateFileMode, stateModeTooOpen } from "../../../storage/modes.ts";
17
18
  import { fsyncParentDirectory } from "../../../workflow/durable-record.ts";
18
19
  import { EVENT_ADAPTER_IDS_V3, type EventAdapterIdV3 } from "../adapter-id.ts";
19
20
  import type { EventV3WriteMode } from "../control.ts";
@@ -152,14 +153,14 @@ function intakeOrderKey(): string {
152
153
  }
153
154
 
154
155
  function ensureOwnerOnlyDirectory(path: string): void {
155
- mkdirSync(path, { recursive: true, mode: 0o700 });
156
- chmodSync(path, 0o700);
156
+ mkdirSync(path, { recursive: true, mode: stateDirMode() });
157
+ chmodSync(path, stateDirMode());
157
158
  }
158
159
 
159
160
  function writeOwnerOnlyFileDurably(path: string, contents: string): void {
160
161
  let fd: number | undefined;
161
162
  try {
162
- fd = openSync(path, "wx", 0o600);
163
+ fd = openSync(path, "wx", stateFileMode());
163
164
  writeFileSync(fd, contents, "utf8");
164
165
  fsyncSync(fd);
165
166
  closeSync(fd);
@@ -240,7 +241,7 @@ function readIntakeRecord(path: string): HookIntakeRecordV3 | undefined {
240
241
  try {
241
242
  const metadata = lstatSync(path);
242
243
  if (!metadata.isFile() || metadata.isSymbolicLink()) return undefined;
243
- if ((metadata.mode & 0o077) !== 0) return undefined;
244
+ if (stateModeTooOpen(metadata.mode)) return undefined;
244
245
  parsed = JSON.parse(readFileSync(path, "utf8"));
245
246
  } catch {
246
247
  return undefined;
@@ -289,14 +290,25 @@ function validDelegatedChild(value: HookIntakeRecordV3["delegated_child"]): bool
289
290
  function validTurnRitual(value: TurnRitualEvidenceV3 | undefined): boolean {
290
291
  if (value === undefined) return true;
291
292
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
292
- const keys = Object.keys(value).sort();
293
+ const keys = Object.keys(value)
294
+ .filter((key) => key !== "session_name_unavailable_reason")
295
+ .sort();
293
296
  if (
294
297
  keys.join(",") !==
295
298
  "session_name_present,session_name_required,status_box_present,status_box_present_strict"
296
299
  ) {
297
300
  return false;
298
301
  }
299
- return keys.every((key) => typeof value[key as keyof TurnRitualEvidenceV3] === "boolean");
302
+ return (
303
+ keys.every((key) => typeof value[key as keyof TurnRitualEvidenceV3] === "boolean") &&
304
+ (value.session_name_unavailable_reason === undefined ||
305
+ [
306
+ "missing_transcript",
307
+ "transcript_not_ready",
308
+ "transcript_parse_error",
309
+ "observation_error",
310
+ ].includes(value.session_name_unavailable_reason))
311
+ );
300
312
  }
301
313
 
302
314
  /**
@@ -15,14 +15,16 @@ import {
15
15
  } from "node:fs";
16
16
  import { hostname } from "node:os";
17
17
  import { basename, join, resolve } from "node:path";
18
+ import { normalizeAdapter } from "../../../adapter.ts";
19
+ import { adapterBehavior } from "../../../hooks/adapter/behaviors/index.ts";
18
20
  import { extractBashCommand, type ParsedPayload } from "../../../hooks/adapter/parse.ts";
19
21
  import {
20
- discoverCodexSessionTranscript,
21
22
  type RuntimeContextTelemetry,
22
23
  type RuntimeTelemetryOptions,
23
24
  readRuntimeContextTelemetry,
24
25
  readRuntimeTuning,
25
26
  } from "../../../hooks/adapter/runtime-telemetry.ts";
27
+ import { stateDirMode, stateFileMode, stateModeTooOpen } from "../../../storage/modes.ts";
26
28
  import { fsyncParentDirectory } from "../../../workflow/durable-record.ts";
27
29
  import { acquireNoClobberLease } from "../../../workflow/workspaces/leases.ts";
28
30
  import { EVENT_ADAPTER_IDS_V3, type EventAdapterIdV3 } from "../adapter-id.ts";
@@ -61,6 +63,7 @@ import {
61
63
  recoverSpanUpperBoundV3,
62
64
  type SpanClockV3,
63
65
  } from "../span-state.ts";
66
+ import { type LineChanges, prepareToolLineChanges } from "../tool-line-changes.ts";
64
67
  import {
65
68
  type ContextMeasurementV3,
66
69
  type ContextTelemetryProvenanceV3,
@@ -103,19 +106,9 @@ const ACTIVE_RUNTIME_CONTEXT_PROBE_INTERVAL_MS = 15_000;
103
106
  const STOP_RUNTIME_CONTEXT_RETRY_DELAYS_MS = [75, 175] as const;
104
107
  /** Finalization is off the interactive hook path; briefly cover Codex's delayed transcript flush. */
105
108
  const APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS = [0, 250, 250] as const;
106
- /**
107
- * Adapters whose turn-boundary recovery and mid-flight onboarding are enabled.
108
- * Kept in code, outside the digested capability profiles, so tuning recovery
109
- * never changes an adapter capability digest (ADR 0078).
110
- */
111
- const RECOVERY_ENABLED_ADAPTERS: ReadonlySet<string> = new Set([
112
- "claude-code",
113
- "codex",
114
- "cursor",
115
- "openclaw",
116
- ]);
117
109
 
118
110
  interface SpanStateV3 extends OpenSpanStateV3 {
111
+ line_changes?: LineChanges;
119
112
  source_id: `hid_${string}`;
120
113
  semantic_key?: `hid_${string}`;
121
114
  recovery_reason?: string;
@@ -467,11 +460,11 @@ function sessionHashForSignal(
467
460
  return normalizeNativeIdV3(context, `${input.adapter}.session`, nativeSession);
468
461
  }
469
462
 
470
- // Cursor carries native session identity only on its start signal. Later
471
- // hooks may still have an exact coordination instance, so reuse its single
472
- // live producer authority instead of hashing the instance into a second
473
- // state path. Ambiguous or absent matches keep the fail-closed fallback.
474
- if (input.adapter === "cursor") {
463
+ // Some adapters carry native session identity only on their start signal.
464
+ // Later hooks may still have an exact coordination instance, so reuse its
465
+ // single live producer authority instead of hashing the instance into a
466
+ // second state path. Ambiguous or absent matches keep the fail-closed fallback.
467
+ if (adapterBehavior(input.adapter).sessionIdentityOnStartOnly) {
475
468
  const matches = listHookProducerStateRecordsV3(input.coordRoot).filter(
476
469
  ({ state }) => state.adapter === input.adapter && state.instance_id === input.instance_id,
477
470
  );
@@ -666,6 +659,7 @@ function processHookSignalLocked(
666
659
  sessionHash: `hid_${string}`,
667
660
  path: string,
668
661
  ): RecordHookSignalV3Result {
662
+ const behavior = adapterBehavior(input.adapter);
669
663
  const rootId = control.genesis.event.scope.root_id as `root_${string}`;
670
664
  const epochId = control.genesis.profile.privacy_key_epoch;
671
665
  const boundaryEventId =
@@ -701,14 +695,15 @@ function processHookSignalLocked(
701
695
  throw new Error("V3 producer state authority does not match the active boundary");
702
696
  }
703
697
  if (
704
- state?.adapter === "cursor" &&
698
+ state &&
699
+ behavior.tracksExecutionMode &&
705
700
  input.payload.cursor_mode &&
706
701
  input.payload.cursor_mode !== "unknown"
707
702
  ) {
708
703
  if (!state.cursor_mode || state.cursor_mode === "unknown") {
709
704
  state.cursor_mode = input.payload.cursor_mode;
710
705
  } else if (state.cursor_mode !== input.payload.cursor_mode) {
711
- throw new Error("Cursor execution mode changed within one V3 generation");
706
+ throw new Error("Execution mode changed within one V3 generation");
712
707
  }
713
708
  }
714
709
  let recovered: RecordHookSignalV3Result | undefined;
@@ -756,7 +751,7 @@ function processHookSignalLocked(
756
751
  input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id;
757
752
  const onboardable =
758
753
  !state &&
759
- RECOVERY_ENABLED_ADAPTERS.has(input.adapter) &&
754
+ behavior.turnRecovery &&
760
755
  input.signal !== "session-end" &&
761
756
  nativeSessionIdentity !== undefined;
762
757
  if (!onboardable) {
@@ -794,7 +789,7 @@ function processHookSignalLocked(
794
789
  instance_id: input.instance_id,
795
790
  signal: input.signal,
796
791
  session_hash: sessionHash,
797
- ...(input.adapter === "codex" ? codexMidFlightDiagnosticContext(input.payload) : {}),
792
+ ...(behavior.midFlightDiagnosticContext?.(input.payload) ?? {}),
798
793
  });
799
794
  }
800
795
  }
@@ -805,7 +800,7 @@ function processHookSignalLocked(
805
800
  state.generation_id,
806
801
  state.privacy_epoch_id,
807
802
  );
808
- const recoveryEnabled = RECOVERY_ENABLED_ADAPTERS.has(input.adapter);
803
+ const recoveryEnabled = behavior.turnRecovery;
809
804
  const nativeTid = input.payload.turn_id
810
805
  ? (normalizeNativeIdV3(
811
806
  fingerprintContext,
@@ -814,7 +809,7 @@ function processHookSignalLocked(
814
809
  ).replace(/^hid_/, "tid_") as `tid_${string}`)
815
810
  : undefined;
816
811
  const cursorPromptWithinOpenTurn =
817
- input.adapter === "cursor" &&
812
+ behavior.promptContinuesOpenTurn &&
818
813
  input.signal === "user-prompt-submit" &&
819
814
  state.current_turn_id !== undefined &&
820
815
  state.current_turn_span !== undefined;
@@ -923,8 +918,32 @@ function processHookSignalLocked(
923
918
  // their self-contained timing cannot drift by a millisecond.
924
919
  const eventClock = signalClock(input);
925
920
  if (input.signal === "after-agent-response") {
921
+ // Cursor Stop followups auto-submit without beforeSubmitPrompt, so they
922
+ // never open a turn. The first stop already closed the human turn.
923
+ // Later completed replies still fire afterAgentResponse; bind them to a
924
+ // derived remediation turn so Stop can consume the new ritual instead of
925
+ // looping on the frozen first terminal.
926
+ const ritualFromResponseEvent = behavior.completedResponseEvent === input.signal;
927
+ if (
928
+ ritualFromResponseEvent &&
929
+ input.turn_ritual &&
930
+ (!state.current_turn_id || !state.current_turn_span)
931
+ ) {
932
+ commitRecoveredToolTurnStart(input, state, path, rootId, fingerprintContext, {
933
+ stop_remediation: true,
934
+ });
935
+ writeProducerDiagnosticV3(input.coordRoot, "cursor_response_ritual_recovered_turn", {
936
+ adapter: input.adapter,
937
+ instance_id: input.instance_id,
938
+ generation_id: state.generation_id,
939
+ signal: input.signal,
940
+ reason: "no_open_turn",
941
+ turn_id: state.current_turn_id,
942
+ status_box_present_strict: input.turn_ritual.status_box_present_strict,
943
+ });
944
+ }
926
945
  if (
927
- input.adapter !== "cursor" ||
946
+ !ritualFromResponseEvent ||
928
947
  !state.current_turn_id ||
929
948
  !state.current_turn_span ||
930
949
  !input.turn_ritual
@@ -1023,6 +1042,10 @@ function processHookSignalLocked(
1023
1042
  };
1024
1043
  state.spans.push(span);
1025
1044
  }
1045
+ span.line_changes = prepareToolLineChanges(
1046
+ input.payload,
1047
+ input.payload.cwd ?? input.coordRoot,
1048
+ );
1026
1049
  } else if (input.signal === "post-tool-use" || input.signal === "post-tool-use-failure") {
1027
1050
  span = sourceId
1028
1051
  ? state.spans.find((candidate) => candidate.source_id === sourceId)
@@ -1174,13 +1197,12 @@ function processHookSignalLocked(
1174
1197
  input.signal === "stop" || input.signal === "stop-failure"
1175
1198
  ? turnTelemetryForTerminal(input, state, eventClock.observed_at)
1176
1199
  : undefined;
1177
- const cursorToolChannelSupport =
1178
- input.adapter === "cursor"
1179
- ? cursorToolChannelSupportV3(state.cursor_mode ?? "unknown")
1180
- : undefined;
1200
+ const cursorToolChannelSupport = behavior.tracksExecutionMode
1201
+ ? cursorToolChannelSupportV3(state.cursor_mode ?? "unknown")
1202
+ : undefined;
1181
1203
  const cursorToolChannelUnattested =
1182
1204
  (input.signal === "stop" || input.signal === "stop-failure") &&
1183
- input.adapter === "cursor" &&
1205
+ behavior.tracksExecutionMode &&
1184
1206
  cursorToolChannelSupport !== "unsupported" &&
1185
1207
  state.tool_call_count === 0;
1186
1208
  if (input.signal === "sub-agent-start" && delegation && !delegation.open_event_id) {
@@ -1192,15 +1214,16 @@ function processHookSignalLocked(
1192
1214
  // Before the signal's own event: the tool call or turn that carries the
1193
1215
  // new effort already ran under it, so it must ride the new attestation.
1194
1216
  maybeAttestTuningChange(input, state, path, rootId);
1217
+ const ritualArrivesSeparately = behavior.completedResponseEvent !== undefined;
1195
1218
  const cursorResponseRitual =
1196
- input.adapter === "cursor" &&
1219
+ ritualArrivesSeparately &&
1197
1220
  (input.signal === "stop" || input.signal === "stop-failure") &&
1198
1221
  state.current_turn_id &&
1199
1222
  state.cursor_response_ritual?.turn_id === state.current_turn_id
1200
1223
  ? state.cursor_response_ritual
1201
1224
  : undefined;
1202
1225
  const terminalTurnRitual =
1203
- input.adapter === "cursor" && (input.signal === "stop" || input.signal === "stop-failure")
1226
+ ritualArrivesSeparately && (input.signal === "stop" || input.signal === "stop-failure")
1204
1227
  ? cursorResponseRitual
1205
1228
  ? {
1206
1229
  status_box_present: cursorResponseRitual.status_box_present,
@@ -1253,6 +1276,7 @@ function processHookSignalLocked(
1253
1276
  span_id: openingSpan?.span_id ?? closingSpan?.span_id,
1254
1277
  parent_span_id: openingSpan?.parent_span_id ?? closingSpan?.parent_span_id,
1255
1278
  terminal_span: terminalSpan,
1279
+ tool_line_changes: span?.line_changes,
1256
1280
  harness_timing: state.turn_harness,
1257
1281
  turn_telemetry: turnTelemetry,
1258
1282
  caused_by: [
@@ -1777,13 +1801,14 @@ function commitRecoveredTurnCompleted(
1777
1801
  commitEventLocked(input, state, path, event);
1778
1802
  }
1779
1803
 
1780
- /** Open a derived turn for tools that arrive after their native turn closed. */
1804
+ /** Open a derived turn for signals that arrive after their native turn closed. */
1781
1805
  function commitRecoveredToolTurnStart(
1782
1806
  input: RecordHookSignalV3Input,
1783
1807
  state: HookProducerStateV3,
1784
1808
  path: string,
1785
1809
  rootId: `root_${string}`,
1786
1810
  fingerprintContext: ReturnType<typeof fingerprintContextV3>,
1811
+ options?: { stop_remediation?: boolean },
1787
1812
  ): void {
1788
1813
  const clock = signalClock(input);
1789
1814
  const span = openSpanStateV3({
@@ -1819,6 +1844,7 @@ function commitRecoveredToolTurnStart(
1819
1844
  observed_at: clock.observed_at,
1820
1845
  monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1821
1846
  clock_id: state.clock_id,
1847
+ ...(options?.stop_remediation ? { stop_remediation: true } : {}),
1822
1848
  });
1823
1849
  if (event?.event_type !== "turn.started") {
1824
1850
  throw new Error("recovered tool turn start could not be normalized");
@@ -2105,7 +2131,8 @@ function maybeCommitActiveRuntimeContextObservation(
2105
2131
  }
2106
2132
  const nativeSessionId = input.payload.session_id ?? input.payload.conversation_id;
2107
2133
  const nativeTurnId = input.payload.turn_id ?? state.current_native_turn_id;
2108
- if (!nativeSessionId || (!nativeTurnId && input.adapter !== "cursor")) return;
2134
+ const behavior = adapterBehavior(input.adapter);
2135
+ if (!nativeSessionId || (!nativeTurnId && !behavior.nativeTurnIdOptional)) return;
2109
2136
 
2110
2137
  const now = Date.now();
2111
2138
  const configuredInterval = input.runtimeTelemetryOptions?.activeContextProbeIntervalMs;
@@ -2148,23 +2175,22 @@ function maybeCommitActiveRuntimeContextObservation(
2148
2175
  );
2149
2176
  return;
2150
2177
  }
2151
- if (input.adapter === "cursor" && state.cursor_mode === "cloud") {
2152
- publishProducerState(input.coordRoot, path, state);
2153
- return;
2154
- }
2155
- if (input.adapter === "openclaw") {
2178
+ // Runtime readers exist only for workflow adapters; an event-only adapter
2179
+ // has no transcript to read.
2180
+ const runtimeAdapter = normalizeAdapter(input.adapter);
2181
+ if (!runtimeAdapter || !behavior.runtimeContextAvailable(state.cursor_mode)) {
2156
2182
  publishProducerState(input.coordRoot, path, state);
2157
2183
  return;
2158
2184
  }
2159
2185
 
2160
2186
  const transcriptPath = runtimeTranscriptPath(input, state);
2161
- if (!transcriptPath && input.adapter !== "cursor") {
2187
+ if (!transcriptPath && !behavior.runtimeContextTranscriptOptional) {
2162
2188
  publishProducerState(input.coordRoot, path, state);
2163
2189
  return;
2164
2190
  }
2165
2191
  const runtime = readRuntimeContextTelemetry(
2166
2192
  {
2167
- adapter: input.adapter,
2193
+ adapter: runtimeAdapter,
2168
2194
  session_id: nativeSessionId,
2169
2195
  ...(nativeTurnId ? { turn_id: nativeTurnId } : {}),
2170
2196
  ...(state.current_turn_span?.opened_at
@@ -2354,7 +2380,7 @@ function queuePendingRuntimeContext(
2354
2380
  nativeTurnId: string | undefined,
2355
2381
  ): void {
2356
2382
  if (
2357
- input.adapter !== "codex" ||
2383
+ !adapterBehavior(input.adapter).runtimeContextRetry ||
2358
2384
  !target ||
2359
2385
  measurement.state !== "expected_but_missing" ||
2360
2386
  !retryableRuntimeContextReason(measurement.reason)
@@ -2389,7 +2415,12 @@ function reconcilePendingRuntimeContexts(
2389
2415
  fingerprintContext: ReturnType<typeof fingerprintContextV3>,
2390
2416
  options: { deferRetryLimit?: boolean; finalAttempt?: boolean } = {},
2391
2417
  ): void {
2392
- if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length) return;
2418
+ if (
2419
+ !adapterBehavior(state.adapter).runtimeContextRetry ||
2420
+ !state.pending_runtime_contexts?.length
2421
+ ) {
2422
+ return;
2423
+ }
2393
2424
  for (const pending of [...state.pending_runtime_contexts]) {
2394
2425
  const runtime = readRuntimeContextTelemetry(
2395
2426
  {
@@ -2455,7 +2486,20 @@ function reconcilePendingRuntimeContextsBeforeApprovedEnd(
2455
2486
  rootId: `root_${string}`,
2456
2487
  fingerprintContext: ReturnType<typeof fingerprintContextV3>,
2457
2488
  ): void {
2458
- if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length) return;
2489
+ if (
2490
+ !adapterBehavior(state.adapter).runtimeContextRetry ||
2491
+ !state.pending_runtime_contexts?.length
2492
+ ) {
2493
+ return;
2494
+ }
2495
+ // Context reconciliation continues the hook boot's sequence, even when the
2496
+ // finalizer drives it. Recover that producer identity from its validated
2497
+ // session start instead of attaching the sequence to the finalizer's name.
2498
+ const ledger = readLedgerV3(input.coordRoot, { authority: input.mode });
2499
+ const start = ledger.events.find(({ event }) => event.event_id === state.started_event_id)?.event;
2500
+ if (!ledger.complete || !start || start.producer.boot_id !== state.boot_id) {
2501
+ throw new Error("event_v3_finalizer_context_producer_unavailable");
2502
+ }
2459
2503
  for (const [index, delayMs] of APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS.entries()) {
2460
2504
  if (delayMs > 0) sleepSync(delayMs);
2461
2505
  reconcilePendingRuntimeContexts(
@@ -2466,7 +2510,7 @@ function reconcilePendingRuntimeContextsBeforeApprovedEnd(
2466
2510
  payload: { raw: {} },
2467
2511
  adapter: state.adapter,
2468
2512
  instance_id: state.instance_id,
2469
- producer_id: "prd_agent-finalizer",
2513
+ producer_id: start.producer.producer_id as `prd_${string}`,
2470
2514
  build_id: input.build_id,
2471
2515
  platform: input.platform,
2472
2516
  observed_at: new Date().toISOString(),
@@ -2499,9 +2543,11 @@ function turnTelemetryForTerminal(
2499
2543
  observedAt: string,
2500
2544
  ): TurnTelemetryV3 {
2501
2545
  const native = extractTurnTelemetryV3(input.adapter, input.payload.raw, observedAt);
2546
+ const behavior = adapterBehavior(input.adapter);
2502
2547
  if (native.context.state === "observed") return native;
2503
- if (input.adapter === "openclaw") return native;
2504
- if (input.adapter === "cursor" && state.cursor_mode === "cloud") {
2548
+ const runtimeAdapter = normalizeAdapter(input.adapter);
2549
+ if (!runtimeAdapter || behavior.nativeTelemetryOnly) return native;
2550
+ if (!behavior.runtimeContextAvailable(state.cursor_mode)) {
2505
2551
  return {
2506
2552
  ...native,
2507
2553
  context: { state: "unsupported", capability: "context_usage" },
@@ -2509,7 +2555,7 @@ function turnTelemetryForTerminal(
2509
2555
  }
2510
2556
 
2511
2557
  const request = {
2512
- adapter: input.adapter,
2558
+ adapter: runtimeAdapter,
2513
2559
  session_id: input.payload.session_id ?? input.payload.conversation_id,
2514
2560
  turn_id: input.payload.turn_id ?? state.current_native_turn_id,
2515
2561
  transcript_path: runtimeTranscriptPath(input, state),
@@ -2519,7 +2565,7 @@ function turnTelemetryForTerminal(
2519
2565
  let runtime = readRuntimeContextTelemetry(request, input.runtimeTelemetryOptions);
2520
2566
  if (runtime.bytes_read > 0) recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
2521
2567
  if (
2522
- input.adapter === "codex" &&
2568
+ behavior.runtimeContextRetry &&
2523
2569
  runtime.state === "partial" &&
2524
2570
  retryableRuntimeContextReason(runtime.reason)
2525
2571
  ) {
@@ -2554,24 +2600,21 @@ function runtimeTranscriptPath(
2554
2600
  input: RecordHookSignalV3Input,
2555
2601
  state: HookProducerStateV3,
2556
2602
  ): string | undefined {
2557
- if (input.adapter !== "codex") return input.payload.transcript_path;
2603
+ const discover = adapterBehavior(input.adapter).discoverTranscript;
2604
+ if (!discover) return input.payload.transcript_path;
2558
2605
  const nativeSessionId = input.payload.session_id ?? input.payload.conversation_id;
2559
2606
  if (!nativeSessionId) return input.payload.transcript_path;
2560
2607
 
2561
2608
  const supplied = input.payload.transcript_path;
2562
2609
  if (supplied) {
2563
- const verified = discoverCodexSessionTranscript(
2564
- nativeSessionId,
2565
- supplied,
2566
- input.runtimeTelemetryOptions,
2567
- );
2610
+ const verified = discover(nativeSessionId, supplied, input.runtimeTelemetryOptions);
2568
2611
  if (verified) state.runtime_transcript_path = verified;
2569
2612
  // Preserve a supplied mismatch so the typed reader reports it honestly.
2570
2613
  return verified ?? supplied;
2571
2614
  }
2572
2615
 
2573
2616
  if (state.runtime_transcript_path) {
2574
- const verified = discoverCodexSessionTranscript(
2617
+ const verified = discover(
2575
2618
  nativeSessionId,
2576
2619
  state.runtime_transcript_path,
2577
2620
  input.runtimeTelemetryOptions,
@@ -2580,11 +2623,7 @@ function runtimeTranscriptPath(
2580
2623
  state.runtime_transcript_path = undefined;
2581
2624
  }
2582
2625
 
2583
- const discovered = discoverCodexSessionTranscript(
2584
- nativeSessionId,
2585
- undefined,
2586
- input.runtimeTelemetryOptions,
2587
- );
2626
+ const discovered = discover(nativeSessionId, undefined, input.runtimeTelemetryOptions);
2588
2627
  if (discovered) state.runtime_transcript_path = discovered;
2589
2628
  return discovered;
2590
2629
  }
@@ -2676,11 +2715,12 @@ function maybeAttestTuningChange(
2676
2715
  let candidate: { effort?: string; speed?: string; model?: string } | undefined;
2677
2716
  let attestationSource: "native" | "derived" = "native";
2678
2717
  let sourceEvent = `${input.adapter}.effort-payload`;
2679
- if (input.adapter === "claude-code" || input.adapter === "cursor") {
2718
+ const behavior = adapterBehavior(input.adapter);
2719
+ if (behavior.effortAttestation === "payload") {
2680
2720
  if (!input.payload.effort) return;
2681
2721
  candidate = { effort: input.payload.effort };
2682
2722
  } else if (
2683
- input.adapter === "codex" &&
2723
+ behavior.effortAttestation === "rollout-probe" &&
2684
2724
  (input.signal === "stop" ||
2685
2725
  // Early probe: without it, a Codex card shows no effort until the first
2686
2726
  // turn terminal. One rollout read per turn while effort is unknown.
@@ -2723,9 +2763,9 @@ function maybeAttestTuningChange(
2723
2763
  return;
2724
2764
  }
2725
2765
 
2726
- // CC change detection rides the payload; the paired transcript row also
2727
- // carries speed and the (possibly swapped) model, so refresh from it.
2728
- if (input.adapter === "claude-code") {
2766
+ // Payload change detection rides the payload; the paired transcript row
2767
+ // also carries speed and the (possibly swapped) model, so refresh from it.
2768
+ if (behavior.transcriptTuningRefresh) {
2729
2769
  const runtime = readRuntimeTuning({
2730
2770
  adapter: "claude-code",
2731
2771
  transcript_path: input.payload.transcript_path,
@@ -2878,9 +2918,7 @@ function commitRuntimeAttestationChange(
2878
2918
  }
2879
2919
 
2880
2920
  function modelProviderFor(adapter: EventAdapterIdV3): string {
2881
- if (adapter === "claude-code") return "anthropic";
2882
- if (adapter === "codex") return "openai";
2883
- return adapter;
2921
+ return adapterBehavior(adapter).modelProvider;
2884
2922
  }
2885
2923
 
2886
2924
  function recordRuntimeTelemetryTiming(state: HookProducerStateV3, durationMs: number): void {
@@ -3125,47 +3163,13 @@ function buildMidFlightSessionStart(
3125
3163
  */
3126
3164
  function isCursorPromptBootstrap(input: RecordHookSignalV3Input): boolean {
3127
3165
  return (
3128
- input.adapter === "cursor" &&
3166
+ adapterBehavior(input.adapter).promptMayPrecedeSessionStart &&
3129
3167
  input.signal === "user-prompt-submit" &&
3130
3168
  (input.payload.conversation_id !== undefined || input.payload.session_id !== undefined) &&
3131
3169
  input.payload.turn_id !== undefined
3132
3170
  );
3133
3171
  }
3134
3172
 
3135
- /** Privacy-safe environment and recovery provenance for Codex mid-flight
3136
- * onboarding. WSLENV values are never recorded, only normalized variable
3137
- * names, and the native session identifier itself remains fingerprinted. */
3138
- export function codexMidFlightDiagnosticContext(
3139
- payload: ParsedPayload,
3140
- env: NodeJS.ProcessEnv = process.env,
3141
- ): Record<string, string | boolean> {
3142
- const wslenv = env.WSLENV?.trim() ?? "";
3143
- const wslenvNames = [
3144
- ...new Set(
3145
- wslenv
3146
- .split(":")
3147
- .map((entry) => entry.split("/", 1)[0]?.trim() ?? "")
3148
- .filter((name) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(name)),
3149
- ),
3150
- ]
3151
- .sort()
3152
- .join(":")
3153
- .slice(0, 512);
3154
- const identityRecoverySource = payload.session_id
3155
- ? "native_session_id"
3156
- : payload.conversation_id
3157
- ? "native_conversation_id"
3158
- : payload.agent_id
3159
- ? "native_agent_id"
3160
- : "unavailable";
3161
- return {
3162
- thread_id_present: Boolean(env.CODEX_THREAD_ID?.trim()),
3163
- wslenv_present: wslenv.length > 0,
3164
- wslenv_names: wslenvNames,
3165
- identity_recovery_source: identityRecoverySource,
3166
- };
3167
- }
3168
-
3169
3173
  function suppressClosedSpanSignal(
3170
3174
  input: RecordHookSignalV3Input,
3171
3175
  sessionHash: `hid_${string}`,
@@ -3554,7 +3558,7 @@ export function reanchorArchivedHookProducersV3(
3554
3558
  payload: {
3555
3559
  raw: {},
3556
3560
  ...(previous.current_native_turn_id ? { turn_id: previous.current_native_turn_id } : {}),
3557
- ...(previous.adapter === "cursor" && previous.cursor_mode
3561
+ ...(adapterBehavior(previous.adapter).tracksExecutionMode && previous.cursor_mode
3558
3562
  ? { cursor_mode: previous.cursor_mode }
3559
3563
  : {}),
3560
3564
  },
@@ -3745,7 +3749,10 @@ export function reconcilePendingRuntimeContextV3(
3745
3749
  // holds everything this producer durably recorded.
3746
3750
  return { state: "missing" };
3747
3751
  }
3748
- if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length) {
3752
+ if (
3753
+ !adapterBehavior(state.adapter).runtimeContextRetry ||
3754
+ !state.pending_runtime_contexts?.length
3755
+ ) {
3749
3756
  return { state: "not_pending" };
3750
3757
  }
3751
3758
  const context = fingerprintContextV3(
@@ -3863,7 +3870,9 @@ function newProducerState(
3863
3870
  generation_id: input.delegated_child?.generation_id ?? generationIdV3(),
3864
3871
  attestation_id: attestationIdV3(),
3865
3872
  capability_profile: adapterCapabilityProfileDigestV3(input.adapter),
3866
- cursor_mode: input.adapter === "cursor" ? (input.payload.cursor_mode ?? "unknown") : undefined,
3873
+ cursor_mode: adapterBehavior(input.adapter).tracksExecutionMode
3874
+ ? (input.payload.cursor_mode ?? "unknown")
3875
+ : undefined,
3867
3876
  privacy_epoch_id: epochId,
3868
3877
  epoch_genesis_id: genesisId,
3869
3878
  boot_id: bootId,
@@ -4048,7 +4057,7 @@ function cursorShellSemanticKey(
4048
4057
  input: RecordHookSignalV3Input,
4049
4058
  context: ReturnType<typeof fingerprintContextV3>,
4050
4059
  ): `hid_${string}` | undefined {
4051
- if (input.adapter !== "cursor") return undefined;
4060
+ if (!adapterBehavior(input.adapter).shellOperationDedup) return undefined;
4052
4061
  const command = extractBashCommand(input.payload.tool_name, input.payload.tool_input);
4053
4062
  return command
4054
4063
  ? normalizeNativeIdV3(context, "cursor.shell-operation", command.normalize("NFC"))
@@ -4215,9 +4224,9 @@ function producerStatePath(
4215
4224
  function acquireStateLease(coordRoot: string, statePath: string) {
4216
4225
  const directory = join(statePath, "..");
4217
4226
  const producerRoot = join(resolve(coordRoot), EVENT_V3_LEDGER_RELATIVE_ROOT, "private-producers");
4218
- mkdirSync(directory, { recursive: true, mode: 0o700 });
4219
- chmodSync(producerRoot, 0o700);
4220
- chmodSync(directory, 0o700);
4227
+ mkdirSync(directory, { recursive: true, mode: stateDirMode() });
4228
+ chmodSync(producerRoot, stateDirMode());
4229
+ chmodSync(directory, stateDirMode());
4221
4230
  const acquiredControl = readEventV3ControlState(coordRoot);
4222
4231
  const acquiredGenesis =
4223
4232
  acquiredControl.state === "candidate" || acquiredControl.state === "active"
@@ -4263,13 +4272,13 @@ function publishProducerState(coordRoot: string, path: string, state: HookProduc
4263
4272
  const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
4264
4273
  let fd: number | undefined;
4265
4274
  try {
4266
- fd = openSync(temporary, "wx", 0o600);
4275
+ fd = openSync(temporary, "wx", stateFileMode());
4267
4276
  writeFileSync(fd, `${JSON.stringify(state)}\n`, "utf8");
4268
4277
  fsyncSync(fd);
4269
4278
  closeSync(fd);
4270
4279
  fd = undefined;
4271
4280
  renameSync(temporary, path);
4272
- chmodSync(path, 0o600);
4281
+ chmodSync(path, stateFileMode());
4273
4282
  fsyncParentDirectory(path);
4274
4283
  } finally {
4275
4284
  if (fd !== undefined) closeSync(fd);
@@ -4282,7 +4291,7 @@ function readProducerState(path: string): HookProducerStateV3 {
4282
4291
  if (!metadata.isFile() || metadata.isSymbolicLink()) {
4283
4292
  throw new Error("V3 producer state path is unsafe");
4284
4293
  }
4285
- if ((metadata.mode & 0o077) !== 0) throw new Error("V3 producer state is not owner-only");
4294
+ if (stateModeTooOpen(metadata.mode)) throw new Error("V3 producer state is open to other users");
4286
4295
  let parsed: unknown;
4287
4296
  try {
4288
4297
  parsed = JSON.parse(readFileSync(path, "utf8"));
@@ -4312,7 +4321,7 @@ function readProducerState(path: string): HookProducerStateV3 {
4312
4321
  ) {
4313
4322
  state.pending_runtime_contexts = undefined;
4314
4323
  }
4315
- if (state.adapter === "cursor") state.cursor_mode ??= "unknown";
4324
+ if (adapterBehavior(state.adapter).tracksExecutionMode) state.cursor_mode ??= "unknown";
4316
4325
  const allowedKeys = new Set([
4317
4326
  "active_runtime_context_probe",
4318
4327
  "adapter",
@@ -11,6 +11,7 @@ import {
11
11
  writeFileSync,
12
12
  } from "node:fs";
13
13
  import { basename, join, resolve } from "node:path";
14
+ import { stateDirMode, stateFileMode } from "../../storage/modes.ts";
14
15
  import { fsyncParentDirectory } from "../../workflow/durable-record.ts";
15
16
  import { initializeEventLedgerV3 } from "./bootstrap.ts";
16
17
  import { canonicalJsonV3, sha256V3 } from "./canonical.ts";
@@ -133,8 +134,8 @@ function recoveryIdFor(failure: EventV3RecoveryFailure): `rcv_${string}` {
133
134
  function publishRecoveryIntent(root: string, value: EventV3RecoveryIntent): EventV3RecoveryIntent {
134
135
  const intent = validateEventV3RecoveryIntent(value);
135
136
  const records = eventV3RecoveryRecordsRoot(root);
136
- mkdirSync(records, { recursive: true, mode: 0o700 });
137
- chmodSync(records, 0o700);
137
+ mkdirSync(records, { recursive: true, mode: stateDirMode() });
138
+ chmodSync(records, stateDirMode());
138
139
  const path = recoveryIntentPath(records, intent.recovery_id);
139
140
  const serialized = `${canonicalJsonV3(intent)}\n`;
140
141
  if (existsSync(path)) {
@@ -256,7 +257,7 @@ function publishExclusive(path: string, serialized: string): void {
256
257
  const temporary = `${path}.tmp.${process.pid}`;
257
258
  let fd: number | undefined;
258
259
  try {
259
- fd = openSync(temporary, "wx", 0o600);
260
+ fd = openSync(temporary, "wx", stateFileMode());
260
261
  writeFileSync(fd, serialized, "utf8");
261
262
  fsyncSync(fd);
262
263
  closeSync(fd);