harnery 0.40.0 → 0.42.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 (595) hide show
  1. package/README.md +1 -1
  2. package/dist/commander.js +4 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +34 -20
  5. package/dist/commands/artifacts.d.ts.map +1 -1
  6. package/dist/commands/artifacts.js +113 -12
  7. package/dist/commands/browse-ai.d.ts.map +1 -1
  8. package/dist/commands/browse-ai.js +4 -0
  9. package/dist/commands/browse.d.ts +164 -0
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +388 -45
  12. package/dist/commands/checkpoint.js +8 -12
  13. package/dist/commands/claude-desktop.d.ts.map +1 -1
  14. package/dist/commands/claude-desktop.js +52 -0
  15. package/dist/commands/deinit.d.ts +1 -1
  16. package/dist/commands/deinit.d.ts.map +1 -1
  17. package/dist/commands/deinit.js +8 -4
  18. package/dist/commands/disk.d.ts +6 -0
  19. package/dist/commands/disk.d.ts.map +1 -0
  20. package/dist/commands/disk.js +80 -0
  21. package/dist/commands/docs.d.ts.map +1 -1
  22. package/dist/commands/docs.js +2 -0
  23. package/dist/commands/doctor.d.ts +8 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +58 -3
  26. package/dist/commands/fetch.d.ts.map +1 -1
  27. package/dist/commands/fetch.js +10 -0
  28. package/dist/commands/files.d.ts +16 -0
  29. package/dist/commands/files.d.ts.map +1 -1
  30. package/dist/commands/files.js +54 -2
  31. package/dist/commands/init.d.ts +21 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +271 -109
  34. package/dist/commands/instructions.js +5 -4
  35. package/dist/commands/ledger-v3.d.ts.map +1 -1
  36. package/dist/commands/ledger-v3.js +2 -1
  37. package/dist/commands/rm.d.ts +4 -0
  38. package/dist/commands/rm.d.ts.map +1 -0
  39. package/dist/commands/rm.js +87 -0
  40. package/dist/commands/servers.d.ts +7 -0
  41. package/dist/commands/servers.d.ts.map +1 -0
  42. package/dist/commands/servers.js +394 -0
  43. package/dist/commands/tunnel.d.ts.map +1 -1
  44. package/dist/commands/tunnel.js +82 -2
  45. package/dist/commands/webview.d.ts +4 -0
  46. package/dist/commands/webview.d.ts.map +1 -0
  47. package/dist/commands/webview.js +94 -0
  48. package/dist/core/adapter.d.ts +50 -1
  49. package/dist/core/adapter.d.ts.map +1 -1
  50. package/dist/core/adapter.js +59 -1
  51. package/dist/core/adapters/attestation.d.ts.map +1 -1
  52. package/dist/core/adapters/attestation.js +3 -2
  53. package/dist/core/adapters/profiles.d.ts +29 -3
  54. package/dist/core/adapters/profiles.d.ts.map +1 -1
  55. package/dist/core/adapters/profiles.js +53 -1
  56. package/dist/core/adapters/registry.d.ts.map +1 -1
  57. package/dist/core/adapters/registry.js +33 -0
  58. package/dist/core/adapters/types.d.ts +9 -0
  59. package/dist/core/adapters/types.d.ts.map +1 -1
  60. package/dist/core/agents/canonical-emit.d.ts +1 -1
  61. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  62. package/dist/core/agents/canonical-emit.js +2 -7
  63. package/dist/core/agents/cli.js +25 -16
  64. package/dist/core/agents/coord-client.d.ts.map +1 -1
  65. package/dist/core/agents/coord-client.js +7 -2
  66. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  67. package/dist/core/agents/render/session-context.js +2 -0
  68. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  69. package/dist/core/agents/rules/claim-conflict.js +3 -9
  70. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  71. package/dist/core/agents/rules/commit-conflict.js +2 -8
  72. package/dist/core/agents/rules/stop-hook.d.ts +2 -0
  73. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  74. package/dist/core/agents/rules/stop-hook.js +1 -1
  75. package/dist/core/agents/session-events.d.ts +6 -2
  76. package/dist/core/agents/session-events.d.ts.map +1 -1
  77. package/dist/core/agents/session-events.js +105 -26
  78. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -1
  79. package/dist/core/agents/session-finalization-state-v3.js +2 -1
  80. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
  81. package/dist/core/agents/session-finalizer-v3.js +5 -4
  82. package/dist/core/agents/session-name-display.d.ts +28 -0
  83. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  84. package/dist/core/agents/session-name-display.js +43 -0
  85. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  86. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  87. package/dist/core/agents/state/heartbeat-writer.d.ts +10 -0
  88. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  89. package/dist/core/agents/state/heartbeat-writer.js +21 -2
  90. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  91. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  92. package/dist/core/agents/state/live-coordination-view.js +61 -0
  93. package/dist/core/agents/state/live-coordination-writer.js +3 -0
  94. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  95. package/dist/core/agents/state/stale-sweep.js +2 -10
  96. package/dist/core/agents/turn-changes.d.ts +17 -0
  97. package/dist/core/agents/turn-changes.d.ts.map +1 -0
  98. package/dist/core/agents/turn-changes.js +76 -0
  99. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  100. package/dist/core/artifacts/delivery-card.js +11 -3
  101. package/dist/core/artifacts/index.d.ts +140 -4
  102. package/dist/core/artifacts/index.d.ts.map +1 -1
  103. package/dist/core/artifacts/index.js +526 -52
  104. package/dist/core/config.d.ts +73 -11
  105. package/dist/core/config.d.ts.map +1 -1
  106. package/dist/core/config.js +142 -3
  107. package/dist/core/context/index.d.ts.map +1 -1
  108. package/dist/core/context/index.js +2 -1
  109. package/dist/core/conversations/archive.d.ts.map +1 -1
  110. package/dist/core/conversations/archive.js +4 -3
  111. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  112. package/dist/core/diagnostics/bundle.js +5 -4
  113. package/dist/core/events/legacy-storage/canary.d.ts.map +1 -1
  114. package/dist/core/events/legacy-storage/canary.js +5 -4
  115. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -1
  116. package/dist/core/events/legacy-storage/compression.js +2 -1
  117. package/dist/core/events/v3/adapter-id.d.ts +10 -2
  118. package/dist/core/events/v3/adapter-id.d.ts.map +1 -1
  119. package/dist/core/events/v3/adapter-id.js +16 -0
  120. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  121. package/dist/core/events/v3/authority-outbox.js +2 -1
  122. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  123. package/dist/core/events/v3/bootstrap.js +5 -4
  124. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  125. package/dist/core/events/v3/capabilities.js +17 -0
  126. package/dist/core/events/v3/contract.d.ts +5 -0
  127. package/dist/core/events/v3/contract.d.ts.map +1 -1
  128. package/dist/core/events/v3/contract.js +7 -1
  129. package/dist/core/events/v3/control-witness.d.ts.map +1 -1
  130. package/dist/core/events/v3/control-witness.js +3 -2
  131. package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -1
  132. package/dist/core/events/v3/coordination-transaction-recovery.js +4 -3
  133. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -1
  134. package/dist/core/events/v3/fingerprint-keys.js +7 -6
  135. package/dist/core/events/v3/generated.d.ts +1 -1
  136. package/dist/core/events/v3/generated.js +1 -1
  137. package/dist/core/events/v3/live-feed.d.ts.map +1 -1
  138. package/dist/core/events/v3/live-feed.js +5 -4
  139. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  140. package/dist/core/events/v3/producers/command-recorder.js +8 -7
  141. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  142. package/dist/core/events/v3/producers/coordination-recorder.js +8 -7
  143. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -1
  144. package/dist/core/events/v3/producers/diagnostic-summaries.js +5 -4
  145. package/dist/core/events/v3/producers/hook-base.d.ts +1 -0
  146. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  147. package/dist/core/events/v3/producers/hook-base.js +10 -4
  148. package/dist/core/events/v3/producers/hook.d.ts +2 -0
  149. package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
  150. package/dist/core/events/v3/producers/hook.js +16 -1
  151. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  152. package/dist/core/events/v3/producers/intake.js +16 -6
  153. package/dist/core/events/v3/producers/recorder.d.ts +2 -4
  154. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  155. package/dist/core/events/v3/producers/recorder.js +125 -101
  156. package/dist/core/events/v3/projection.js +9 -1
  157. package/dist/core/events/v3/recovery.d.ts.map +1 -1
  158. package/dist/core/events/v3/recovery.js +4 -3
  159. package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -1
  160. package/dist/core/events/v3/support-storage/inventory.js +2 -1
  161. package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -1
  162. package/dist/core/events/v3/support-storage/maintenance.js +4 -3
  163. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  164. package/dist/core/events/v3/support-storage/pack-reader.js +4 -3
  165. package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -1
  166. package/dist/core/events/v3/support-storage/pack-writer.js +6 -5
  167. package/dist/core/events/v3/tool-line-changes.d.ts +8 -0
  168. package/dist/core/events/v3/tool-line-changes.d.ts.map +1 -0
  169. package/dist/core/events/v3/tool-line-changes.js +135 -0
  170. package/dist/core/events/v3/writer.d.ts.map +1 -1
  171. package/dist/core/events/v3/writer.js +8 -7
  172. package/dist/core/governor/planning.d.ts.map +1 -1
  173. package/dist/core/governor/planning.js +6 -5
  174. package/dist/core/governor/service.d.ts.map +1 -1
  175. package/dist/core/governor/service.js +12 -11
  176. package/dist/core/governor/state.d.ts.map +1 -1
  177. package/dist/core/governor/state.js +7 -6
  178. package/dist/core/guard/evaluator.js +1 -0
  179. package/dist/core/guard/storage.d.ts.map +1 -1
  180. package/dist/core/guard/storage.js +3 -2
  181. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts +4 -0
  182. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts.map +1 -0
  183. package/dist/core/hooks/adapter/behaviors/claude-code.js +31 -0
  184. package/dist/core/hooks/adapter/behaviors/codex.d.ts +15 -0
  185. package/dist/core/hooks/adapter/behaviors/codex.d.ts.map +1 -0
  186. package/dist/core/hooks/adapter/behaviors/codex.js +82 -0
  187. package/dist/core/hooks/adapter/behaviors/cursor.d.ts +7 -0
  188. package/dist/core/hooks/adapter/behaviors/cursor.d.ts.map +1 -0
  189. package/dist/core/hooks/adapter/behaviors/cursor.js +35 -0
  190. package/dist/core/hooks/adapter/behaviors/index.d.ts +11 -0
  191. package/dist/core/hooks/adapter/behaviors/index.d.ts.map +1 -0
  192. package/dist/core/hooks/adapter/behaviors/index.js +20 -0
  193. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts +7 -0
  194. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts.map +1 -0
  195. package/dist/core/hooks/adapter/behaviors/openclaw.js +34 -0
  196. package/dist/core/hooks/adapter/behaviors/opencode.d.ts +7 -0
  197. package/dist/core/hooks/adapter/behaviors/opencode.d.ts.map +1 -0
  198. package/dist/core/hooks/adapter/behaviors/opencode.js +34 -0
  199. package/dist/core/hooks/adapter/behaviors/types.d.ts +89 -0
  200. package/dist/core/hooks/adapter/behaviors/types.d.ts.map +1 -0
  201. package/dist/core/hooks/adapter/behaviors/types.js +1 -0
  202. package/dist/core/hooks/adapter/detect.d.ts +1 -1
  203. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  204. package/dist/core/hooks/adapter/detect.js +2 -3
  205. package/dist/core/hooks/adapter/events.d.ts +46 -1
  206. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  207. package/dist/core/hooks/adapter/events.js +36 -0
  208. package/dist/core/hooks/adapter/opencode-plugin.d.ts +60 -0
  209. package/dist/core/hooks/adapter/opencode-plugin.d.ts.map +1 -0
  210. package/dist/core/hooks/adapter/opencode-plugin.js +129 -0
  211. package/dist/core/hooks/adapter/output.d.ts +7 -0
  212. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  213. package/dist/core/hooks/adapter/output.js +8 -1
  214. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  215. package/dist/core/hooks/adapter/wiring.d.ts +52 -8
  216. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  217. package/dist/core/hooks/adapter/wiring.js +133 -12
  218. package/dist/core/hooks/cli.js +676 -531
  219. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  220. package/dist/core/hooks/effects/image-capture.js +2 -1
  221. package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -1
  222. package/dist/core/hooks/prompt-context/contract.js +1 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts +1 -1
  224. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  225. package/dist/core/hooks/resolve/transcript.js +35 -4
  226. package/dist/core/hooks/run-markers.d.ts +37 -0
  227. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  228. package/dist/core/hooks/run-markers.js +109 -0
  229. package/dist/core/hooks/session-name-presence.d.ts +3 -1
  230. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  231. package/dist/core/hooks/session-name-presence.js +7 -2
  232. package/dist/core/hooks/shell-waiter.d.ts +2 -0
  233. package/dist/core/hooks/shell-waiter.d.ts.map +1 -0
  234. package/dist/core/hooks/shell-waiter.js +173 -0
  235. package/dist/core/resources/service.d.ts.map +1 -1
  236. package/dist/core/resources/service.js +5 -4
  237. package/dist/core/semantic/review.d.ts.map +1 -1
  238. package/dist/core/semantic/review.js +5 -4
  239. package/dist/core/semantic/service.d.ts.map +1 -1
  240. package/dist/core/semantic/service.js +15 -14
  241. package/dist/core/semantic/storage.d.ts.map +1 -1
  242. package/dist/core/semantic/storage.js +5 -4
  243. package/dist/core/servers/index.d.ts +211 -0
  244. package/dist/core/servers/index.d.ts.map +1 -0
  245. package/dist/core/servers/index.js +560 -0
  246. package/dist/core/servers/net.d.ts +58 -0
  247. package/dist/core/servers/net.d.ts.map +1 -0
  248. package/dist/core/servers/net.js +270 -0
  249. package/dist/core/servers/tunnels.d.ts +14 -0
  250. package/dist/core/servers/tunnels.d.ts.map +1 -0
  251. package/dist/core/servers/tunnels.js +33 -0
  252. package/dist/core/storage/atomic-json.d.ts.map +1 -1
  253. package/dist/core/storage/atomic-json.js +7 -3
  254. package/dist/core/storage/builtins.js +1 -0
  255. package/dist/core/storage/durable-history.d.ts.map +1 -1
  256. package/dist/core/storage/durable-history.js +6 -5
  257. package/dist/core/storage/maintenance-providers.d.ts.map +1 -1
  258. package/dist/core/storage/maintenance-providers.js +2 -1
  259. package/dist/core/storage/maintenance.d.ts.map +1 -1
  260. package/dist/core/storage/maintenance.js +7 -6
  261. package/dist/core/storage/metrics.d.ts.map +1 -1
  262. package/dist/core/storage/metrics.js +5 -1
  263. package/dist/core/storage/modes.d.ts +17 -0
  264. package/dist/core/storage/modes.d.ts.map +1 -0
  265. package/dist/core/storage/modes.js +47 -0
  266. package/dist/core/storage/process-log.d.ts.map +1 -1
  267. package/dist/core/storage/process-log.js +3 -2
  268. package/dist/core/storage/receipts.d.ts.map +1 -1
  269. package/dist/core/storage/receipts.js +4 -3
  270. package/dist/core/storage/segments.d.ts.map +1 -1
  271. package/dist/core/storage/segments.js +7 -6
  272. package/dist/core/supervisor/hook-health.d.ts +2 -1
  273. package/dist/core/supervisor/hook-health.d.ts.map +1 -1
  274. package/dist/core/supervisor/hook-health.js +4 -3
  275. package/dist/core/supervisor/service.d.ts.map +1 -1
  276. package/dist/core/supervisor/service.js +7 -6
  277. package/dist/core/work/state.d.ts.map +1 -1
  278. package/dist/core/work/state.js +8 -7
  279. package/dist/core/workflow/adapters.d.ts +7 -0
  280. package/dist/core/workflow/adapters.d.ts.map +1 -1
  281. package/dist/core/workflow/adapters.js +9 -0
  282. package/dist/core/workflow/approvals.d.ts.map +1 -1
  283. package/dist/core/workflow/approvals.js +6 -5
  284. package/dist/core/workflow/billing.d.ts.map +1 -1
  285. package/dist/core/workflow/billing.js +33 -4
  286. package/dist/core/workflow/durable-record.d.ts.map +1 -1
  287. package/dist/core/workflow/durable-record.js +4 -3
  288. package/dist/core/workflow/engine.d.ts.map +1 -1
  289. package/dist/core/workflow/engine.js +30 -1
  290. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  291. package/dist/core/workflow/live-session-v3.js +2 -3
  292. package/dist/core/workflow/run-state.d.ts.map +1 -1
  293. package/dist/core/workflow/run-state.js +3 -2
  294. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  295. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  296. package/dist/core/workflow/sandbox-projection.js +23 -0
  297. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  298. package/dist/core/workflow/spawn-claude.js +6 -1
  299. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  300. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  301. package/dist/core/workflow/spawn-codex.js +20 -1
  302. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  303. package/dist/core/workflow/spawn-cursor.js +6 -1
  304. package/dist/core/workflow/spawn-opencode.d.ts +36 -0
  305. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -0
  306. package/dist/core/workflow/spawn-opencode.js +135 -0
  307. package/dist/core/workflow/transcript.d.ts.map +1 -1
  308. package/dist/core/workflow/transcript.js +3 -2
  309. package/dist/core/workflow/types.d.ts +47 -2
  310. package/dist/core/workflow/types.d.ts.map +1 -1
  311. package/dist/core/workflow/worker-access.d.ts +92 -0
  312. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  313. package/dist/core/workflow/worker-access.js +450 -0
  314. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  315. package/dist/core/workflow/workspaces/leases.js +5 -4
  316. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  317. package/dist/core/workflow/workspaces/local-git.js +3 -2
  318. package/dist/core/workflow/workspaces/paths.d.ts.map +1 -1
  319. package/dist/core/workflow/workspaces/paths.js +2 -1
  320. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  321. package/dist/core/workflow/workspaces/state.js +10 -9
  322. package/dist/lazy-commands.js +8 -1
  323. package/dist/lib/agent-browser/client.d.ts +9 -0
  324. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  325. package/dist/lib/agent-browser/client.js +13 -0
  326. package/dist/lib/browser/batch-steps.d.ts +18 -0
  327. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  328. package/dist/lib/browser/batch-steps.js +82 -0
  329. package/dist/lib/browser/client.d.ts +35 -1
  330. package/dist/lib/browser/client.d.ts.map +1 -1
  331. package/dist/lib/browser/client.js +117 -8
  332. package/dist/lib/browser/collect/collector.d.ts +99 -0
  333. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  334. package/dist/lib/browser/collect/collector.js +202 -0
  335. package/dist/lib/browser/collect/fields.d.ts +31 -0
  336. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  337. package/dist/lib/browser/collect/fields.js +75 -0
  338. package/dist/lib/browser/collect/index.d.ts +6 -0
  339. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  340. package/dist/lib/browser/collect/index.js +5 -0
  341. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  342. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  343. package/dist/lib/browser/collect/page-driver.js +306 -0
  344. package/dist/lib/browser/collect/presets.d.ts +52 -0
  345. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  346. package/dist/lib/browser/collect/presets.js +176 -0
  347. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  348. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  349. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  350. package/dist/lib/browser/identity.d.ts +25 -0
  351. package/dist/lib/browser/identity.d.ts.map +1 -0
  352. package/dist/lib/browser/identity.js +66 -0
  353. package/dist/lib/browser/index.d.ts +3 -2
  354. package/dist/lib/browser/index.d.ts.map +1 -1
  355. package/dist/lib/browser/index.js +2 -1
  356. package/dist/lib/browser/launch-args.d.ts +26 -0
  357. package/dist/lib/browser/launch-args.d.ts.map +1 -1
  358. package/dist/lib/browser/launch-args.js +41 -1
  359. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  360. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  361. package/dist/lib/browser/page-review-judge.js +13 -0
  362. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  363. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  364. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  365. package/dist/lib/browser/qa-run.js +1 -0
  366. package/dist/lib/browser/user-agent.d.ts +53 -0
  367. package/dist/lib/browser/user-agent.d.ts.map +1 -0
  368. package/dist/lib/browser/user-agent.js +79 -0
  369. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  370. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  371. package/dist/lib/browser/windows-chrome.js +179 -0
  372. package/dist/lib/claude-desktop-share.d.ts +136 -0
  373. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  374. package/dist/lib/claude-desktop-share.js +394 -0
  375. package/dist/lib/disk-usage.d.ts +50 -0
  376. package/dist/lib/disk-usage.d.ts.map +1 -0
  377. package/dist/lib/disk-usage.js +303 -0
  378. package/dist/lib/docs-lint.d.ts +6 -0
  379. package/dist/lib/docs-lint.d.ts.map +1 -1
  380. package/dist/lib/docs-lint.js +8 -3
  381. package/dist/lib/docs-metadata-managed.d.ts +1 -0
  382. package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
  383. package/dist/lib/docs-metadata-managed.js +17 -1
  384. package/dist/lib/guarded-remove.d.ts +34 -0
  385. package/dist/lib/guarded-remove.d.ts.map +1 -0
  386. package/dist/lib/guarded-remove.js +310 -0
  387. package/dist/lib/identities/assume.d.ts.map +1 -1
  388. package/dist/lib/identities/assume.js +4 -9
  389. package/dist/lib/instructions/apply.d.ts.map +1 -1
  390. package/dist/lib/instructions/apply.js +2 -0
  391. package/dist/lib/instructions/templates.js +8 -8
  392. package/dist/lib/pace/index.d.ts +2 -0
  393. package/dist/lib/pace/index.d.ts.map +1 -0
  394. package/dist/lib/pace/index.js +1 -0
  395. package/dist/lib/pace/pace.d.ts +95 -0
  396. package/dist/lib/pace/pace.d.ts.map +1 -0
  397. package/dist/lib/pace/pace.js +313 -0
  398. package/dist/lib/tunnel/error-page.d.ts +1 -1
  399. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  400. package/dist/lib/tunnel/error-page.js +19 -8
  401. package/dist/lib/tunnel/forward-headers.d.ts +11 -0
  402. package/dist/lib/tunnel/forward-headers.d.ts.map +1 -0
  403. package/dist/lib/tunnel/forward-headers.js +17 -0
  404. package/dist/lib/tunnel/gate.js +34 -10
  405. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  406. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  407. package/dist/lib/tunnel/path-scope.js +62 -0
  408. package/dist/lib/tunnel/state.d.ts +5 -0
  409. package/dist/lib/tunnel/state.d.ts.map +1 -1
  410. package/dist/lib/tunnel/state.js +17 -3
  411. package/dist/lib/webview/index.d.ts +2 -0
  412. package/dist/lib/webview/index.d.ts.map +1 -0
  413. package/dist/lib/webview/index.js +1 -0
  414. package/dist/lib/webview/webview.d.ts +116 -0
  415. package/dist/lib/webview/webview.d.ts.map +1 -0
  416. package/dist/lib/webview/webview.js +289 -0
  417. package/opencode-plugin/package.json +19 -0
  418. package/opencode-plugin/src/index.ts +791 -0
  419. package/package.json +16 -4
  420. package/schemas/config.schema.json +104 -0
  421. package/schemas/event-v3.schema.json +164 -0
  422. package/src/commander.ts +26 -0
  423. package/src/commands/agents.ts +47 -18
  424. package/src/commands/artifacts.ts +171 -19
  425. package/src/commands/browse-ai.ts +8 -0
  426. package/src/commands/browse.ts +570 -48
  427. package/src/commands/checkpoint.ts +7 -9
  428. package/src/commands/claude-desktop.ts +61 -0
  429. package/src/commands/deinit.ts +9 -4
  430. package/src/commands/disk.ts +114 -0
  431. package/src/commands/docs.ts +2 -0
  432. package/src/commands/doctor.ts +63 -3
  433. package/src/commands/fetch.ts +14 -0
  434. package/src/commands/files.ts +93 -2
  435. package/src/commands/init.ts +334 -122
  436. package/src/commands/instructions.ts +8 -4
  437. package/src/commands/ledger-v3.ts +2 -1
  438. package/src/commands/rm.ts +115 -0
  439. package/src/commands/servers.ts +506 -0
  440. package/src/commands/tunnel.ts +102 -2
  441. package/src/commands/webview.ts +159 -0
  442. package/src/core/adapter.ts +102 -1
  443. package/src/core/adapters/attestation.ts +3 -2
  444. package/src/core/adapters/profiles.ts +63 -1
  445. package/src/core/adapters/registry.ts +37 -0
  446. package/src/core/adapters/types.ts +10 -0
  447. package/src/core/agents/canonical-emit.ts +2 -5
  448. package/src/core/agents/cli.ts +26 -17
  449. package/src/core/agents/coord-client.ts +9 -3
  450. package/src/core/agents/render/session-context.ts +1 -0
  451. package/src/core/agents/rules/claim-conflict.ts +3 -8
  452. package/src/core/agents/rules/commit-conflict.ts +2 -7
  453. package/src/core/agents/rules/stop-hook.ts +3 -1
  454. package/src/core/agents/session-events.ts +120 -26
  455. package/src/core/agents/session-finalization-state-v3.ts +2 -1
  456. package/src/core/agents/session-finalizer-v3.ts +5 -4
  457. package/src/core/agents/session-name-display.ts +48 -0
  458. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  459. package/src/core/agents/state/heartbeat-writer.ts +24 -2
  460. package/src/core/agents/state/live-coordination-view.ts +74 -0
  461. package/src/core/agents/state/live-coordination-writer.ts +3 -0
  462. package/src/core/agents/state/stale-sweep.ts +2 -9
  463. package/src/core/agents/turn-changes.ts +97 -0
  464. package/src/core/artifacts/delivery-card.ts +12 -3
  465. package/src/core/artifacts/index.ts +701 -47
  466. package/src/core/config.ts +206 -4
  467. package/src/core/context/index.ts +2 -1
  468. package/src/core/conversations/archive.ts +4 -3
  469. package/src/core/diagnostics/bundle.ts +5 -3
  470. package/src/core/events/legacy-storage/canary.ts +5 -4
  471. package/src/core/events/legacy-storage/compression.ts +2 -1
  472. package/src/core/events/v3/adapter-id.ts +29 -1
  473. package/src/core/events/v3/authority-outbox.ts +2 -1
  474. package/src/core/events/v3/bootstrap.ts +5 -4
  475. package/src/core/events/v3/capabilities.ts +17 -0
  476. package/src/core/events/v3/contract.ts +9 -1
  477. package/src/core/events/v3/control-witness.ts +3 -2
  478. package/src/core/events/v3/coordination-transaction-recovery.ts +4 -3
  479. package/src/core/events/v3/fingerprint-keys.ts +7 -6
  480. package/src/core/events/v3/generated.ts +1 -1
  481. package/src/core/events/v3/live-feed.ts +5 -4
  482. package/src/core/events/v3/producers/command-recorder.ts +8 -6
  483. package/src/core/events/v3/producers/coordination-recorder.ts +8 -7
  484. package/src/core/events/v3/producers/diagnostic-summaries.ts +5 -4
  485. package/src/core/events/v3/producers/hook-base.ts +11 -4
  486. package/src/core/events/v3/producers/hook.ts +19 -1
  487. package/src/core/events/v3/producers/intake.ts +18 -6
  488. package/src/core/events/v3/producers/recorder.ts +139 -114
  489. package/src/core/events/v3/projection.ts +11 -1
  490. package/src/core/events/v3/recovery.ts +4 -3
  491. package/src/core/events/v3/support-storage/inventory.ts +2 -1
  492. package/src/core/events/v3/support-storage/maintenance.ts +8 -3
  493. package/src/core/events/v3/support-storage/pack-reader.ts +4 -3
  494. package/src/core/events/v3/support-storage/pack-writer.ts +6 -5
  495. package/src/core/events/v3/tool-line-changes.ts +139 -0
  496. package/src/core/events/v3/writer.ts +8 -7
  497. package/src/core/governor/planning.ts +6 -5
  498. package/src/core/governor/service.ts +12 -11
  499. package/src/core/governor/state.ts +7 -6
  500. package/src/core/guard/evaluator.ts +1 -0
  501. package/src/core/guard/storage.ts +3 -2
  502. package/src/core/hooks/adapter/behaviors/claude-code.ts +33 -0
  503. package/src/core/hooks/adapter/behaviors/codex.ts +96 -0
  504. package/src/core/hooks/adapter/behaviors/cursor.ts +37 -0
  505. package/src/core/hooks/adapter/behaviors/index.ts +31 -0
  506. package/src/core/hooks/adapter/behaviors/openclaw.ts +36 -0
  507. package/src/core/hooks/adapter/behaviors/opencode.ts +36 -0
  508. package/src/core/hooks/adapter/behaviors/types.ts +110 -0
  509. package/src/core/hooks/adapter/detect.ts +2 -3
  510. package/src/core/hooks/adapter/events.ts +72 -1
  511. package/src/core/hooks/adapter/opencode-plugin.ts +177 -0
  512. package/src/core/hooks/adapter/output.ts +8 -1
  513. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  514. package/src/core/hooks/adapter/wiring.ts +198 -16
  515. package/src/core/hooks/cli.ts +776 -539
  516. package/src/core/hooks/effects/image-capture.ts +2 -1
  517. package/src/core/hooks/prompt-context/contract.ts +1 -0
  518. package/src/core/hooks/resolve/transcript.ts +42 -5
  519. package/src/core/hooks/run-markers.ts +132 -0
  520. package/src/core/hooks/session-name-presence.ts +10 -3
  521. package/src/core/hooks/shell-waiter.ts +166 -0
  522. package/src/core/resources/service.ts +5 -4
  523. package/src/core/semantic/review.ts +5 -4
  524. package/src/core/semantic/service.ts +15 -14
  525. package/src/core/semantic/storage.ts +5 -4
  526. package/src/core/servers/index.ts +712 -0
  527. package/src/core/servers/net.ts +283 -0
  528. package/src/core/servers/tunnels.ts +39 -0
  529. package/src/core/storage/atomic-json.ts +7 -3
  530. package/src/core/storage/builtins.ts +1 -0
  531. package/src/core/storage/durable-history.ts +6 -5
  532. package/src/core/storage/maintenance-providers.ts +2 -1
  533. package/src/core/storage/maintenance.ts +7 -6
  534. package/src/core/storage/metrics.ts +5 -1
  535. package/src/core/storage/modes.ts +53 -0
  536. package/src/core/storage/process-log.ts +3 -2
  537. package/src/core/storage/receipts.ts +4 -3
  538. package/src/core/storage/segments.ts +7 -6
  539. package/src/core/supervisor/hook-health.ts +4 -4
  540. package/src/core/supervisor/service.ts +7 -6
  541. package/src/core/work/state.ts +8 -7
  542. package/src/core/workflow/adapters.ts +15 -0
  543. package/src/core/workflow/approvals.ts +6 -5
  544. package/src/core/workflow/billing.ts +34 -4
  545. package/src/core/workflow/durable-record.ts +4 -3
  546. package/src/core/workflow/engine.ts +38 -1
  547. package/src/core/workflow/live-session-v3.ts +2 -3
  548. package/src/core/workflow/run-state.ts +3 -2
  549. package/src/core/workflow/sandbox-projection.ts +38 -2
  550. package/src/core/workflow/spawn-claude.ts +5 -1
  551. package/src/core/workflow/spawn-codex.ts +18 -1
  552. package/src/core/workflow/spawn-cursor.ts +5 -1
  553. package/src/core/workflow/spawn-opencode.ts +153 -0
  554. package/src/core/workflow/transcript.ts +3 -2
  555. package/src/core/workflow/types.ts +62 -2
  556. package/src/core/workflow/worker-access.ts +525 -0
  557. package/src/core/workflow/workspaces/leases.ts +5 -4
  558. package/src/core/workflow/workspaces/local-git.ts +3 -2
  559. package/src/core/workflow/workspaces/paths.ts +2 -1
  560. package/src/core/workflow/workspaces/state.ts +10 -9
  561. package/src/lazy-commands.ts +11 -1
  562. package/src/lib/agent-browser/client.ts +18 -0
  563. package/src/lib/browser/batch-steps.ts +79 -0
  564. package/src/lib/browser/client.ts +147 -9
  565. package/src/lib/browser/collect/collector.ts +306 -0
  566. package/src/lib/browser/collect/fields.ts +87 -0
  567. package/src/lib/browser/collect/index.ts +47 -0
  568. package/src/lib/browser/collect/page-driver.ts +327 -0
  569. package/src/lib/browser/collect/presets.ts +237 -0
  570. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  571. package/src/lib/browser/identity.ts +64 -0
  572. package/src/lib/browser/index.ts +16 -1
  573. package/src/lib/browser/launch-args.ts +53 -1
  574. package/src/lib/browser/page-review-judge.ts +16 -0
  575. package/src/lib/browser/qa-run-contracts.ts +11 -0
  576. package/src/lib/browser/qa-run.ts +1 -0
  577. package/src/lib/browser/user-agent.ts +130 -0
  578. package/src/lib/browser/windows-chrome.ts +227 -0
  579. package/src/lib/claude-desktop-share.ts +511 -0
  580. package/src/lib/disk-usage.ts +347 -0
  581. package/src/lib/docs-lint.ts +8 -3
  582. package/src/lib/docs-metadata-managed.ts +21 -0
  583. package/src/lib/guarded-remove.ts +376 -0
  584. package/src/lib/identities/assume.ts +7 -7
  585. package/src/lib/instructions/apply.ts +2 -0
  586. package/src/lib/instructions/templates.ts +8 -8
  587. package/src/lib/pace/index.ts +14 -0
  588. package/src/lib/pace/pace.ts +370 -0
  589. package/src/lib/tunnel/error-page.ts +25 -9
  590. package/src/lib/tunnel/forward-headers.ts +16 -0
  591. package/src/lib/tunnel/gate.ts +39 -10
  592. package/src/lib/tunnel/path-scope.ts +73 -0
  593. package/src/lib/tunnel/state.ts +20 -3
  594. package/src/lib/webview/index.ts +13 -0
  595. package/src/lib/webview/webview.ts +457 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Local socket and process observation for the server registry.
3
+ *
4
+ * Linux reads procfs directly (no `ss`/`lsof` dependency, works under WSL).
5
+ * macOS shells out to `lsof` and `ps`. Other platforms report `unsupported`
6
+ * so callers can say the scan did not run rather than implying it found
7
+ * nothing.
8
+ */
9
+
10
+ import { spawnSync } from "node:child_process";
11
+ import { readdirSync, readFileSync, readlinkSync } from "node:fs";
12
+
13
+ export type ScanSupport = "procfs" | "lsof" | "unsupported";
14
+
15
+ export interface ListeningSocket {
16
+ pid: number;
17
+ port: number;
18
+ address: string;
19
+ }
20
+
21
+ export interface ProcessInfo {
22
+ pid: number;
23
+ ppid: number | null;
24
+ command: string;
25
+ cwd: string | null;
26
+ }
27
+
28
+ export function scanSupport(): ScanSupport {
29
+ if (process.platform === "linux") return "procfs";
30
+ if (process.platform === "darwin") return "lsof";
31
+ return "unsupported";
32
+ }
33
+
34
+ interface ProcSocketRow {
35
+ localAddress: string;
36
+ localPort: number;
37
+ state: string;
38
+ inode: string;
39
+ }
40
+
41
+ /** Parse `/proc/net/tcp` or `/proc/net/tcp6` content. */
42
+ export function parseProcNetTcp(content: string): ProcSocketRow[] {
43
+ const rows: ProcSocketRow[] = [];
44
+ for (const line of content.split("\n").slice(1)) {
45
+ const fields = line.trim().split(/\s+/);
46
+ if (fields.length < 10) continue;
47
+ const [address, portHex] = (fields[1] ?? "").split(":");
48
+ if (!address || !portHex) continue;
49
+ rows.push({
50
+ localAddress: decodeProcAddress(address),
51
+ localPort: Number.parseInt(portHex, 16),
52
+ state: fields[3] ?? "",
53
+ inode: fields[9] ?? "",
54
+ });
55
+ }
56
+ return rows;
57
+ }
58
+
59
+ function decodeProcAddress(hex: string): string {
60
+ if (hex.length === 8) {
61
+ const bytes = hex.match(/../g)?.map((pair) => Number.parseInt(pair, 16)) ?? [];
62
+ return bytes.reverse().join(".");
63
+ }
64
+ if (/^0+$/.test(hex)) return "::";
65
+ if (hex === "00000000000000000000000001000000") return "::1";
66
+ if (hex.startsWith("0000000000000000FFFF0000")) return decodeProcAddress(hex.slice(24));
67
+ return `ipv6:${hex.toLowerCase()}`;
68
+ }
69
+
70
+ function readProcSockets(): ProcSocketRow[] {
71
+ const rows: ProcSocketRow[] = [];
72
+ for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
73
+ try {
74
+ rows.push(...parseProcNetTcp(readFileSync(file, "utf8")));
75
+ } catch {
76
+ // tcp6 is absent on IPv6-disabled kernels.
77
+ }
78
+ }
79
+ return rows;
80
+ }
81
+
82
+ /** Map socket inodes to the pids holding them (only processes we may read). */
83
+ function socketOwners(inodes: Set<string>): Map<string, number> {
84
+ const owners = new Map<string, number>();
85
+ if (inodes.size === 0) return owners;
86
+ let entries: string[];
87
+ try {
88
+ entries = readdirSync("/proc");
89
+ } catch {
90
+ return owners;
91
+ }
92
+ for (const entry of entries) {
93
+ if (!/^\d+$/.test(entry)) continue;
94
+ let fds: string[];
95
+ try {
96
+ fds = readdirSync(`/proc/${entry}/fd`);
97
+ } catch {
98
+ continue;
99
+ }
100
+ for (const fd of fds) {
101
+ let target: string;
102
+ try {
103
+ target = readlinkSync(`/proc/${entry}/fd/${fd}`);
104
+ } catch {
105
+ continue;
106
+ }
107
+ const match = /^socket:\[(\d+)\]$/.exec(target);
108
+ if (match?.[1] && inodes.has(match[1]) && !owners.has(match[1])) {
109
+ owners.set(match[1], Number(entry));
110
+ }
111
+ }
112
+ if (owners.size === inodes.size) break;
113
+ }
114
+ return owners;
115
+ }
116
+
117
+ /** Listening TCP sockets whose owning process this user can see. */
118
+ export function listListeningSockets(): ListeningSocket[] {
119
+ const support = scanSupport();
120
+ if (support === "procfs") {
121
+ const listening = readProcSockets().filter((row) => row.state === "0A");
122
+ const owners = socketOwners(new Set(listening.map((row) => row.inode)));
123
+ const seen = new Set<string>();
124
+ const sockets: ListeningSocket[] = [];
125
+ for (const row of listening) {
126
+ const pid = owners.get(row.inode);
127
+ if (!pid) continue;
128
+ const key = `${pid}:${row.localPort}`;
129
+ if (seen.has(key)) continue;
130
+ seen.add(key);
131
+ sockets.push({ pid, port: row.localPort, address: row.localAddress });
132
+ }
133
+ return sockets.sort((a, b) => a.port - b.port);
134
+ }
135
+ if (support === "lsof") return lsofListeningSockets();
136
+ return [];
137
+ }
138
+
139
+ /**
140
+ * Every listening TCP port on this machine, including ports whose owner this
141
+ * user cannot see (containers, other users). Used to avoid port collisions.
142
+ */
143
+ export function listListeningPorts(): Set<number> {
144
+ const support = scanSupport();
145
+ if (support === "procfs")
146
+ return new Set(
147
+ readProcSockets()
148
+ .filter((row) => row.state === "0A")
149
+ .map((row) => row.localPort),
150
+ );
151
+ if (support === "lsof") return new Set(listListeningSockets().map((socket) => socket.port));
152
+ return new Set();
153
+ }
154
+
155
+ /** Parse `lsof -Fpn` output for listening sockets. */
156
+ export function parseLsofListen(output: string): ListeningSocket[] {
157
+ const sockets: ListeningSocket[] = [];
158
+ const seen = new Set<string>();
159
+ let pid = 0;
160
+ for (const line of output.split("\n")) {
161
+ if (line.startsWith("p")) pid = Number(line.slice(1));
162
+ else if (line.startsWith("n") && pid > 0) {
163
+ const match = /^(.*):(\d+)$/.exec(line.slice(1));
164
+ if (!match?.[2]) continue;
165
+ const port = Number(match[2]);
166
+ const key = `${pid}:${port}`;
167
+ if (seen.has(key)) continue;
168
+ seen.add(key);
169
+ sockets.push({ pid, port, address: (match[1] ?? "").replace(/^\[|\]$/g, "") });
170
+ }
171
+ }
172
+ return sockets.sort((a, b) => a.port - b.port);
173
+ }
174
+
175
+ /**
176
+ * Count established server-side connections per local port. A browser tab
177
+ * holding a live-reload socket or keep-alive connection counts as activity.
178
+ */
179
+ export function establishedConnectionCounts(ports: number[]): Map<number, number> {
180
+ const counts = new Map<number, number>();
181
+ const wanted = new Set(ports.filter((port) => Number.isInteger(port) && port > 0));
182
+ if (wanted.size === 0) return counts;
183
+ const support = scanSupport();
184
+ if (support === "procfs") {
185
+ for (const row of readProcSockets()) {
186
+ // 01 ESTABLISHED; 03 SYN_RECV, where a deferred-accept server parks a
187
+ // connected client until its first bytes arrive.
188
+ if ((row.state !== "01" && row.state !== "03") || !wanted.has(row.localPort)) continue;
189
+ counts.set(row.localPort, (counts.get(row.localPort) ?? 0) + 1);
190
+ }
191
+ return counts;
192
+ }
193
+ if (support === "lsof") return lsofConnectionCounts(wanted);
194
+ return counts;
195
+ }
196
+
197
+ /** Listening sockets via `lsof` (the macOS path; also runs wherever lsof exists). */
198
+ export function lsofListeningSockets(): ListeningSocket[] {
199
+ return parseLsofListen(run("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-Fpn"]));
200
+ }
201
+
202
+ /** Server-side established connections per wanted local port, via `lsof`. */
203
+ export function lsofConnectionCounts(wanted: Set<number>): Map<number, number> {
204
+ const counts = new Map<number, number>();
205
+ const output = run("lsof", ["-nP", "-iTCP", "-sTCP:ESTABLISHED", "-Fn"]);
206
+ for (const line of output.split("\n")) {
207
+ const match = /^n.*?:(\d+)->/.exec(line);
208
+ const port = match?.[1] ? Number(match[1]) : 0;
209
+ if (wanted.has(port)) counts.set(port, (counts.get(port) ?? 0) + 1);
210
+ }
211
+ return counts;
212
+ }
213
+
214
+ /** Parent, command line, and working directory via `ps` and `lsof` (the macOS path). */
215
+ export function psProcessInfo(pid: number): ProcessInfo | null {
216
+ const ps = run("ps", ["-o", "ppid=,command=", "-p", String(pid)]).trim();
217
+ if (!ps) return null;
218
+ const match = /^(\d+)\s+(.*)$/.exec(ps);
219
+ const cwdLine = run("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"])
220
+ .split("\n")
221
+ .find((line) => line.startsWith("n"));
222
+ return {
223
+ pid,
224
+ ppid: match?.[1] ? Number(match[1]) : null,
225
+ command: match?.[2] ?? ps,
226
+ cwd: cwdLine ? cwdLine.slice(1) : null,
227
+ };
228
+ }
229
+
230
+ export function readProcessInfo(pid: number): ProcessInfo | null {
231
+ if (process.platform === "linux") {
232
+ try {
233
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
234
+ const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
235
+ const ppid = Number(afterComm[1]);
236
+ const command = readFileSync(`/proc/${pid}/cmdline`, "utf8").split("\0").join(" ").trim();
237
+ let cwd: string | null = null;
238
+ try {
239
+ cwd = readlinkSync(`/proc/${pid}/cwd`);
240
+ } catch {
241
+ cwd = null;
242
+ }
243
+ return { pid, ppid: Number.isInteger(ppid) ? ppid : null, command, cwd };
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ if (process.platform === "darwin") return psProcessInfo(pid);
249
+ return null;
250
+ }
251
+
252
+ /** Parse `ps -o etime=` output (`[[dd-]hh:]mm:ss`) into seconds. */
253
+ export function parseElapsed(value: string): number | null {
254
+ const match = /^\s*(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)\s*$/.exec(value);
255
+ if (!match) return null;
256
+ const [, days, hours, minutes, seconds] = match;
257
+ return (
258
+ Number(days ?? 0) * 86_400 + Number(hours ?? 0) * 3_600 + Number(minutes) * 60 + Number(seconds)
259
+ );
260
+ }
261
+
262
+ /** When a process started, from its elapsed run time; null when unavailable. */
263
+ export function processStartedAt(pid: number, now = Date.now()): string | null {
264
+ if (process.platform !== "linux" && process.platform !== "darwin") return null;
265
+ const elapsed = parseElapsed(run("ps", ["-o", "etime=", "-p", String(pid)]));
266
+ return elapsed === null ? null : new Date(now - elapsed * 1_000).toISOString();
267
+ }
268
+
269
+ /** The pid followed by its ancestors, nearest first (bounded). */
270
+ export function ancestry(pid: number, maxHops = 32): number[] {
271
+ const chain: number[] = [];
272
+ let current: number | null = pid;
273
+ while (current && current > 1 && chain.length < maxHops && !chain.includes(current)) {
274
+ chain.push(current);
275
+ current = readProcessInfo(current)?.ppid ?? null;
276
+ }
277
+ return chain;
278
+ }
279
+
280
+ function run(command: string, args: string[]): string {
281
+ const result = spawnSync(command, args, { encoding: "utf8", timeout: 10_000 });
282
+ return result.status === 0 || result.stdout ? (result.stdout ?? "") : "";
283
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tunnels as registry services.
3
+ *
4
+ * `tunnel up` and `tunnel reload` register the instance; `tunnel down`
5
+ * removes it. Tunnels started before the registry existed are adopted the next
6
+ * time the registry is read, so a long-lived tunnel never shows as an
7
+ * unregistered listener.
8
+ */
9
+
10
+ import { resolve } from "node:path";
11
+ import { gateLogFile, type TunnelState } from "../../lib/tunnel/state.ts";
12
+ import { resolveBinName } from "../config.ts";
13
+ import type { RegisterServerInput } from "./index.ts";
14
+
15
+ export function tunnelServerId(name: string): string {
16
+ return `tunnel-${name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-")}`;
17
+ }
18
+
19
+ /** The registry input for one tunnel instance. */
20
+ export function tunnelServerInput(state: TunnelState, coordRoot: string): RegisterServerInput {
21
+ const helpers = [state.cloudflared_pid, state.provider_pid].filter(
22
+ (pid): pid is number => typeof pid === "number" && pid > 0,
23
+ );
24
+ return {
25
+ id: tunnelServerId(state.name),
26
+ kind: "service",
27
+ type: "tunnel",
28
+ label: `Tunnel ${state.name} → ${state.target}${state.vhost ? ` (${state.vhost})` : ""}`,
29
+ url: state.url,
30
+ port: state.gate_port,
31
+ pid: state.gate_pid,
32
+ ...(helpers.length ? { pids: helpers } : {}),
33
+ log: resolve(coordRoot, ".cache", "tunnel", gateLogFile(state.name)),
34
+ cwd: coordRoot,
35
+ stop_argv: [resolveBinName(coordRoot), "tunnel", "down", "--name", state.name],
36
+ started_at: state.started_at,
37
+ owner: null,
38
+ };
39
+ }
@@ -1,13 +1,17 @@
1
1
  import { chmodSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
+ import { stateDirMode, stateFileMode } from "./modes.ts";
3
4
 
4
5
  /** Write one private JSON projection without exposing a partial frame to readers. */
5
6
  export function writePrivateJsonAtomic(path: string, value: unknown): void {
6
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
7
+ mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
7
8
  const temporary = `${path}.tmp.${process.pid}.${Date.now()}`;
8
- writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600 });
9
+ writeFileSync(temporary, `${JSON.stringify(value)}\n`, {
10
+ encoding: "utf8",
11
+ mode: stateFileMode(),
12
+ });
9
13
  try {
10
- chmodSync(temporary, 0o600);
14
+ chmodSync(temporary, stateFileMode());
11
15
  } catch {
12
16
  // Windows does not expose POSIX modes. The atomic write still applies.
13
17
  }
@@ -476,6 +476,7 @@ function cacheFamilies(): HarneryStorageFamily[] {
476
476
  ["pid-map-cache", ".harnery/pid-map", "active native process observations"],
477
477
  ["remote-presence-cache", ".harnery/presence/remote", "presence relay observations"],
478
478
  ["event-v3-live-display", ".harnery/live/v3", "Event Ledger V3 canonical events"],
479
+ ["local-server-registry", ".harnery/servers", "running local server processes"],
479
480
  ] as const;
480
481
  return [
481
482
  ...definitions.map(([id, root, source]) =>
@@ -20,6 +20,7 @@ import {
20
20
  import type { FileHandle } from "node:fs/promises";
21
21
  import { open, rename, rm } from "node:fs/promises";
22
22
  import { basename, dirname, isAbsolute, join, relative } from "node:path";
23
+ import { stateDirMode, stateFileMode } from "./modes.ts";
23
24
 
24
25
  export const HARNERY_DURABLE_HISTORY_SCHEMA = "harnery.durable-history/v1" as const;
25
26
 
@@ -271,7 +272,7 @@ export async function rewriteCrashSafeJsonlFile(
271
272
  const handle = await open(
272
273
  temp,
273
274
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag(),
274
- 0o600,
275
+ stateFileMode(),
275
276
  );
276
277
  try {
277
278
  await writeAll(handle, Buffer.from(body, "utf8"));
@@ -325,7 +326,7 @@ function appendAndSync(
325
326
  const fd = openSync(
326
327
  path,
327
328
  fsConstants.O_RDWR | fsConstants.O_APPEND | fsConstants.O_CREAT | noFollowFlag(),
328
- 0o600,
329
+ stateFileMode(),
329
330
  );
330
331
  try {
331
332
  assertOpenFileIdentity(path, fd, parent);
@@ -348,7 +349,7 @@ interface DirectoryIdentity {
348
349
  }
349
350
 
350
351
  function ensurePrivateDirectory(path: string): DirectoryIdentity {
351
- mkdirSync(path, { recursive: true, mode: 0o700 });
352
+ mkdirSync(path, { recursive: true, mode: stateDirMode() });
352
353
  return captureDirectoryIdentity(path);
353
354
  }
354
355
 
@@ -522,7 +523,7 @@ function acquireLease(lease: string): LeaseOwner {
522
523
  };
523
524
  let claimed: DirectoryIdentity | undefined;
524
525
  try {
525
- mkdirSync(lease, { mode: 0o700 });
526
+ mkdirSync(lease, { mode: stateDirMode() });
526
527
  assertDirectoryWithinParent(lease, parent);
527
528
  claimed = captureDirectoryIdentity(lease);
528
529
  writeOwner(join(lease, "owner.json"), owner);
@@ -549,7 +550,7 @@ function writeOwner(path: string, owner: LeaseOwner): void {
549
550
  const fd = openSync(
550
551
  path,
551
552
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag(),
552
- 0o600,
553
+ stateFileMode(),
553
554
  );
554
555
  try {
555
556
  assertOpenFileIdentity(path, fd, parent);
@@ -12,6 +12,7 @@ import {
12
12
  readMaintenanceTransaction,
13
13
  runAutomaticMaintenanceSlice,
14
14
  } from "./maintenance.ts";
15
+ import { stateFileMode } from "./modes.ts";
15
16
 
16
17
  const STALE_RUNNING_CLAIM_MS = 10 * 60 * 1_000;
17
18
 
@@ -191,7 +192,7 @@ function recoverInterruptedDailyClaim(coordRoot: string, now: Date): void {
191
192
 
192
193
  function atomicCursor(path: string, cursor: DailyCursor): void {
193
194
  const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
194
- writeFileSync(temporary, `${JSON.stringify(cursor, null, 2)}\n`, { mode: 0o600 });
195
+ writeFileSync(temporary, `${JSON.stringify(cursor, null, 2)}\n`, { mode: stateFileMode() });
195
196
  renameSync(temporary, path);
196
197
  }
197
198
 
@@ -20,6 +20,7 @@ import {
20
20
  HARNERY_STRUCTURED_LOG_PROVIDER_ID,
21
21
  type HarneryRegisteredStorageFamily,
22
22
  } from "./contract.ts";
23
+ import { stateDirMode, stateFileMode } from "./modes.ts";
23
24
 
24
25
  export const HARNERY_MAINTENANCE_TRANSACTION_SCHEMA =
25
26
  "harnery.storage-maintenance-transaction/v1" as const;
@@ -641,7 +642,7 @@ function writeReceipt(
641
642
  committed_at: now.toISOString(),
642
643
  };
643
644
  const path = receiptPath(coordRoot, transactionId, action.action_id);
644
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
645
+ mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
645
646
  writeExclusiveDurable(path, `${JSON.stringify(receipt, null, 2)}\n`);
646
647
  }
647
648
 
@@ -678,7 +679,7 @@ function writeRunOutcome(
678
679
  now: Date,
679
680
  ): void {
680
681
  const root = join(resolve(coordRoot), ".harnery", "logs", "storage-maintenance");
681
- mkdirSync(root, { recursive: true, mode: 0o700 });
682
+ mkdirSync(root, { recursive: true, mode: stateDirMode() });
682
683
  const active = join(root, "active.jsonl");
683
684
  if (existsSync(active) && statSync(active).size > 1_048_576) {
684
685
  renameSync(active, join(root, `sealed-${now.toISOString().replaceAll(/[:.]/g, "-")}.jsonl`));
@@ -686,7 +687,7 @@ function writeRunOutcome(
686
687
  appendFileSync(
687
688
  active,
688
689
  `${JSON.stringify({ ts: now.toISOString(), transaction_id: transaction.transaction_id, state: transaction.state, actions: transaction.actions.length, files: transaction.actions.reduce((n, action) => n + action.files, 0), bytes: transaction.actions.reduce((n, action) => n + action.bytes, 0), reason_codes: transaction.reason_codes })}\n`,
689
- { encoding: "utf8", mode: 0o600 },
690
+ { encoding: "utf8", mode: stateFileMode() },
690
691
  );
691
692
  }
692
693
 
@@ -871,9 +872,9 @@ function readJsonFile(path: string): unknown {
871
872
  }
872
873
 
873
874
  function atomicJson(path: string, value: unknown, fsync: boolean): void {
874
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
875
+ mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
875
876
  const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
876
- const fd = openSync(temporary, "wx", 0o600);
877
+ const fd = openSync(temporary, "wx", stateFileMode());
877
878
  try {
878
879
  writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, "utf8");
879
880
  if (fsync) fsyncSync(fd);
@@ -884,7 +885,7 @@ function atomicJson(path: string, value: unknown, fsync: boolean): void {
884
885
  }
885
886
 
886
887
  function writeExclusiveDurable(path: string, value: string): void {
887
- const fd = openSync(path, "wx", 0o600);
888
+ const fd = openSync(path, "wx", stateFileMode());
888
889
  try {
889
890
  writeFileSync(fd, value, "utf8");
890
891
  fsyncSync(fd);
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { stateFileMode } from "./modes.ts";
3
4
 
4
5
  export interface HarneryLogMetricsDelta {
5
6
  accepted?: number;
@@ -109,7 +110,10 @@ export function mergeMetricsSidecar(
109
110
  const merged = mergeMetrics(current, delta, now);
110
111
  const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
111
112
  try {
112
- writeFileSync(temporary, `${JSON.stringify(merged)}\n`, { encoding: "utf8", mode: 0o600 });
113
+ writeFileSync(temporary, `${JSON.stringify(merged)}\n`, {
114
+ encoding: "utf8",
115
+ mode: stateFileMode(),
116
+ });
113
117
  renameSync(temporary, path);
114
118
  return { merged: true, generation_reset: generationReset };
115
119
  } catch {
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The permission modes for everything Harnery writes as project state under `.harnery/`.
3
+ *
4
+ * `private` (the default) is owner-only: 0700 directories and 0600 files, exactly as before this
5
+ * module existed. `group` is for a project several Unix users share through one group, such as a
6
+ * workspace that several people's agents work in at once: 2770 (setgid) and 0660, with nothing ever granted
7
+ * to other users. Every state write takes its mode from here, and every integrity check that
8
+ * rejects a loosely permissioned file asks `stateModeTooOpen`, so the two cannot disagree.
9
+ *
10
+ * Personal state outside the project (browser cookies, sessions, pacing ledgers in the user's home)
11
+ * is not project state and stays owner-only regardless.
12
+ */
13
+ import { storageSharing } from "../config.ts";
14
+
15
+ let resolved: "private" | "group" | undefined;
16
+
17
+ /** The sharing mode for this process, resolved once. Tests reset it with `resetStorageSharing`. */
18
+ export function stateSharing(): "private" | "group" {
19
+ if (resolved === undefined) {
20
+ resolved = storageSharing();
21
+ // A umask that strips group bits would quietly turn every group file back into an owner-only
22
+ // one, and the next user's write would fail. Keep the process's other-bits mask as it was.
23
+ if (resolved === "group" && typeof process.umask === "function")
24
+ process.umask(process.umask() & ~0o070);
25
+ }
26
+ return resolved;
27
+ }
28
+
29
+ export function resetStorageSharing(): void {
30
+ resolved = undefined;
31
+ }
32
+
33
+ /**
34
+ * Mode for a directory of project state. Group mode includes setgid: Harnery chmods directories
35
+ * after creating them, and a plain 0770 would clear the bit, so files made inside would take the
36
+ * writer's own group instead of the project's, and other users could not read them.
37
+ */
38
+ export function stateDirMode(): number {
39
+ return stateSharing() === "group" ? 0o2770 : 0o700;
40
+ }
41
+
42
+ /** Mode for a file of project state. */
43
+ export function stateFileMode(): number {
44
+ return stateSharing() === "group" ? 0o660 : 0o600;
45
+ }
46
+
47
+ /**
48
+ * True when a state file or directory is readable or writable by someone it should not be: anyone
49
+ * but the owner in private mode, anyone outside the owner and group in group mode.
50
+ */
51
+ export function stateModeTooOpen(mode: number): boolean {
52
+ return (mode & (stateSharing() === "group" ? 0o007 : 0o077)) !== 0;
53
+ }
@@ -12,6 +12,7 @@ import {
12
12
  import { basename, dirname, join, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import { createStorageCatalog } from "./catalog.ts";
15
+ import { stateDirMode, stateFileMode } from "./modes.ts";
15
16
  import { familyLogDirectory } from "./segments.ts";
16
17
 
17
18
  export interface RotatingTextSinkOptions {
@@ -79,7 +80,7 @@ export class RotatingTextSink {
79
80
  append(text: string): void {
80
81
  if (this.#closed) throw new Error("text sink is closed");
81
82
  const bytes = Buffer.from(text, "utf8");
82
- mkdirSync(dirname(this.#options.path), { recursive: true, mode: 0o700 });
83
+ mkdirSync(dirname(this.#options.path), { recursive: true, mode: stateDirMode() });
83
84
  let offset = 0;
84
85
  while (offset < bytes.byteLength) {
85
86
  let current = existsSync(this.#options.path) ? statSync(this.#options.path).size : 0;
@@ -88,7 +89,7 @@ export class RotatingTextSink {
88
89
  current = 0;
89
90
  }
90
91
  const length = Math.min(bytes.byteLength - offset, this.#options.max_bytes - current);
91
- const fd = openSync(this.#options.path, "a", 0o600);
92
+ const fd = openSync(this.#options.path, "a", stateFileMode());
92
93
  try {
93
94
  writeSync(fd, bytes.subarray(offset, offset + length));
94
95
  if (this.#options.durable) fdatasyncSync(fd);
@@ -18,6 +18,7 @@ import {
18
18
  HarneryMaintenanceError,
19
19
  type HarneryMaintenanceReceipt,
20
20
  } from "./maintenance.ts";
21
+ import { stateDirMode, stateFileMode } from "./modes.ts";
21
22
 
22
23
  export const HARNERY_RECEIPT_SEGMENT_MANIFEST_SCHEMA =
23
24
  "harnery.storage-maintenance-receipt-segment/v1" as const;
@@ -85,7 +86,7 @@ export function consolidateMutationReceipts(
85
86
  );
86
87
  }
87
88
  const segments = join(root, "segments");
88
- mkdirSync(segments, { recursive: true, mode: 0o700 });
89
+ mkdirSync(segments, { recursive: true, mode: stateDirMode() });
89
90
  const payloadPath = join(segments, `${segmentId}.jsonl`);
90
91
  const manifestPath = join(segments, `${segmentId}.manifest.json`);
91
92
  writeExclusiveDurable(payloadPath, payload);
@@ -144,9 +145,9 @@ function readReceipt(path: string): {
144
145
  }
145
146
 
146
147
  function writeExclusiveDurable(path: string, bytes: string): void {
147
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
148
+ mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
148
149
  const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
149
- const fd = openSync(temporary, "wx", 0o600);
150
+ const fd = openSync(temporary, "wx", stateFileMode());
150
151
  try {
151
152
  writeFileSync(fd, bytes, "utf8");
152
153
  fsyncSync(fd);
@@ -23,6 +23,7 @@ import type { HarneryRegisteredStorageFamily } from "./contract.ts";
23
23
  import { parseLogRecord } from "./jsonl.ts";
24
24
  import type { HarneryLogMetricsDelta } from "./metrics.ts";
25
25
  import { mergeMetricsSidecar } from "./metrics.ts";
26
+ import { stateDirMode, stateFileMode } from "./modes.ts";
26
27
 
27
28
  export interface HarneryLogSegmentV1 {
28
29
  sequence: number;
@@ -165,7 +166,7 @@ export class FileSegmentSink {
165
166
  const fd = openSync(
166
167
  active,
167
168
  fsConstants.O_RDWR | fsConstants.O_APPEND | fsConstants.O_CREAT | noFollowFlag(),
168
- 0o600,
169
+ stateFileMode(),
169
170
  );
170
171
  let bytes = 0;
171
172
  try {
@@ -337,7 +338,7 @@ export async function withFamilyLease<T>(
337
338
  let owner: LeaseOwner | undefined;
338
339
  while (true) {
339
340
  try {
340
- mkdirSync(lease, { mode: 0o700 });
341
+ mkdirSync(lease, { mode: stateDirMode() });
341
342
  const leaseIdentity = captureDirectoryIdentity(lease);
342
343
  owner = {
343
344
  owner_id: randomUUID(),
@@ -397,7 +398,7 @@ function sealActive(active: string, options: ResolvedSegmentOptions, now: Date):
397
398
  const sequence = manifest.next_sequence;
398
399
  const name = `${now.toISOString().slice(0, 10).replaceAll("-", "")}-${String(sequence).padStart(8, "0")}.jsonl.gz`;
399
400
  const target = join(directory, "segments", name);
400
- mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
401
+ mkdirSync(dirname(target), { recursive: true, mode: stateDirMode() });
401
402
  const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
402
403
  const compressed = gzipSync(content);
403
404
  writeNewFile(temporary, compressed);
@@ -425,7 +426,7 @@ function sealActive(active: string, options: ResolvedSegmentOptions, now: Date):
425
426
  ],
426
427
  };
427
428
  const sealedManifest = join(directory, "manifests", `${String(sequence).padStart(8, "0")}.json`);
428
- mkdirSync(dirname(sealedManifest), { recursive: true, mode: 0o700 });
429
+ mkdirSync(dirname(sealedManifest), { recursive: true, mode: stateDirMode() });
429
430
  writeNewFile(sealedManifest, Buffer.from(`${JSON.stringify(next)}\n`));
430
431
  atomicJson(join(directory, "manifest.json"), next);
431
432
  truncateRegularFile(active);
@@ -539,7 +540,7 @@ interface DirectoryIdentity {
539
540
  }
540
541
 
541
542
  function ensurePrivateDirectory(path: string): DirectoryIdentity {
542
- mkdirSync(path, { recursive: true, mode: 0o700 });
543
+ mkdirSync(path, { recursive: true, mode: stateDirMode() });
543
544
  return captureDirectoryIdentity(path);
544
545
  }
545
546
 
@@ -632,7 +633,7 @@ function writeNewFile(path: string, content: Buffer): void {
632
633
  const fd = openSync(
633
634
  path,
634
635
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag(),
635
- 0o600,
636
+ stateFileMode(),
636
637
  );
637
638
  try {
638
639
  assertOpenFileIdentity(path, fd, parent);