harnery 0.42.0 → 0.44.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 (343) hide show
  1. package/README.md +3 -3
  2. package/dist/commander.d.ts +16 -0
  3. package/dist/commander.d.ts.map +1 -1
  4. package/dist/commander.js +18 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +95 -52
  7. package/dist/commands/browse.d.ts.map +1 -1
  8. package/dist/commands/browse.js +38 -11
  9. package/dist/commands/diagnostics.d.ts.map +1 -1
  10. package/dist/commands/diagnostics.js +10 -0
  11. package/dist/commands/doctor.d.ts +12 -0
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +22 -7
  14. package/dist/commands/governor.js +22 -10
  15. package/dist/commands/inbox.d.ts +1 -1
  16. package/dist/commands/inbox.d.ts.map +1 -1
  17. package/dist/commands/inbox.js +55 -2
  18. package/dist/commands/rm.d.ts.map +1 -1
  19. package/dist/commands/rm.js +28 -4
  20. package/dist/commands/servers.d.ts.map +1 -1
  21. package/dist/commands/servers.js +16 -1
  22. package/dist/commands/supervisor.d.ts.map +1 -1
  23. package/dist/commands/supervisor.js +7 -2
  24. package/dist/commands/toc.d.ts +14 -0
  25. package/dist/commands/toc.d.ts.map +1 -1
  26. package/dist/commands/toc.js +7 -4
  27. package/dist/commands/tunnel.d.ts +65 -0
  28. package/dist/commands/tunnel.d.ts.map +1 -1
  29. package/dist/commands/tunnel.js +290 -101
  30. package/dist/commands/web-fetch.d.ts +3 -2
  31. package/dist/commands/web-fetch.d.ts.map +1 -1
  32. package/dist/commands/web-fetch.js +25 -19
  33. package/dist/commands/web.d.ts +7 -1
  34. package/dist/commands/web.d.ts.map +1 -1
  35. package/dist/commands/web.js +9 -3
  36. package/dist/commands/workflow.js +24 -30
  37. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  38. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  39. package/dist/core/agents/live-authority-v3.js +11 -0
  40. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  41. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  42. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  43. package/dist/core/agents/mailbox.d.ts +9 -8
  44. package/dist/core/agents/mailbox.d.ts.map +1 -1
  45. package/dist/core/agents/mailbox.js +24 -17
  46. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  47. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  48. package/dist/core/agents/render/prompt-context.js +19 -17
  49. package/dist/core/agents/render/session-context.d.ts +2 -8
  50. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  51. package/dist/core/agents/render/session-context.js +64 -43
  52. package/dist/core/agents/rules/stop-hook.js +3 -3
  53. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  54. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  55. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  56. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  57. package/dist/core/agents/state/names.d.ts +43 -3
  58. package/dist/core/agents/state/names.d.ts.map +1 -1
  59. package/dist/core/agents/state/names.js +87 -3
  60. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  61. package/dist/core/agents/state/pidmap.js +5 -0
  62. package/dist/core/agents/state/proc-start.d.ts +19 -0
  63. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  64. package/dist/core/agents/state/proc-start.js +41 -0
  65. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  66. package/dist/core/agents/state/stale-sweep.js +180 -70
  67. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  68. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  69. package/dist/core/agents/state/unchanged-file.js +390 -0
  70. package/dist/core/context/index.d.ts +22 -10
  71. package/dist/core/context/index.d.ts.map +1 -1
  72. package/dist/core/context/index.js +381 -119
  73. package/dist/core/context/observation.d.ts +26 -0
  74. package/dist/core/context/observation.d.ts.map +1 -0
  75. package/dist/core/context/observation.js +11 -0
  76. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  77. package/dist/core/diagnostics/bundle.js +25 -14
  78. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  79. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  80. package/dist/core/diagnostics/path-identity.js +12 -0
  81. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  82. package/dist/core/diagnostics/sanitize.js +4 -3
  83. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  84. package/dist/core/events/v3/authority-outbox.js +29 -0
  85. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  86. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  87. package/dist/core/events/v3/generation-custody.js +50 -0
  88. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  89. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  90. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  91. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  92. package/dist/core/events/v3/producers/recorder.js +77 -64
  93. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  94. package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
  95. package/dist/core/governor/planning.d.ts.map +1 -1
  96. package/dist/core/governor/planning.js +14 -1
  97. package/dist/core/governor/service-read.d.ts +10 -2
  98. package/dist/core/governor/service-read.d.ts.map +1 -1
  99. package/dist/core/governor/service-read.js +105 -22
  100. package/dist/core/governor/service.d.ts +20 -2
  101. package/dist/core/governor/service.d.ts.map +1 -1
  102. package/dist/core/governor/service.js +72 -47
  103. package/dist/core/hooks/cli.js +120 -23
  104. package/dist/core/hooks/fork-lineage.d.ts +40 -0
  105. package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
  106. package/dist/core/hooks/fork-lineage.js +62 -0
  107. package/dist/core/hooks/resolve/transcript.d.ts +16 -0
  108. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  109. package/dist/core/hooks/resolve/transcript.js +84 -0
  110. package/dist/core/hooks/shell-rm.d.ts +2 -0
  111. package/dist/core/hooks/shell-rm.d.ts.map +1 -0
  112. package/dist/core/hooks/shell-rm.js +105 -0
  113. package/dist/core/hooks/shell-tokens.d.ts +12 -0
  114. package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
  115. package/dist/core/hooks/shell-tokens.js +97 -0
  116. package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
  117. package/dist/core/hooks/shell-waiter.js +3 -93
  118. package/dist/core/inbox/contract.d.ts +2 -1
  119. package/dist/core/inbox/contract.d.ts.map +1 -1
  120. package/dist/core/inbox/index.d.ts +2 -0
  121. package/dist/core/inbox/index.d.ts.map +1 -1
  122. package/dist/core/inbox/index.js +2 -0
  123. package/dist/core/inbox/peer-messages.d.ts +37 -0
  124. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  125. package/dist/core/inbox/peer-messages.js +72 -0
  126. package/dist/core/inbox/service.d.ts +8 -1
  127. package/dist/core/inbox/service.d.ts.map +1 -1
  128. package/dist/core/inbox/service.js +67 -21
  129. package/dist/core/inbox/targets.d.ts +22 -0
  130. package/dist/core/inbox/targets.d.ts.map +1 -0
  131. package/dist/core/inbox/targets.js +108 -0
  132. package/dist/core/inbox/watcher.js +7 -3
  133. package/dist/core/journal/index.d.ts.map +1 -1
  134. package/dist/core/journal/index.js +17 -2
  135. package/dist/core/resources/contract.d.ts +3 -0
  136. package/dist/core/resources/contract.d.ts.map +1 -1
  137. package/dist/core/resources/service-status.d.ts +14 -1
  138. package/dist/core/resources/service-status.d.ts.map +1 -1
  139. package/dist/core/resources/service-status.js +23 -13
  140. package/dist/core/resources/service.d.ts +9 -1
  141. package/dist/core/resources/service.d.ts.map +1 -1
  142. package/dist/core/resources/service.js +47 -27
  143. package/dist/core/resources/status.d.ts +2 -5
  144. package/dist/core/resources/status.d.ts.map +1 -1
  145. package/dist/core/resources/status.js +36 -7
  146. package/dist/core/semantic/contract.d.ts +5 -5
  147. package/dist/core/servers/index.d.ts +24 -13
  148. package/dist/core/servers/index.d.ts.map +1 -1
  149. package/dist/core/servers/index.js +132 -92
  150. package/dist/core/servers/tunnels.d.ts +3 -5
  151. package/dist/core/servers/tunnels.d.ts.map +1 -1
  152. package/dist/core/servers/tunnels.js +3 -9
  153. package/dist/core/storage/process-log.d.ts +12 -0
  154. package/dist/core/storage/process-log.d.ts.map +1 -1
  155. package/dist/core/storage/process-log.js +10 -2
  156. package/dist/core/supervisor/contract.d.ts +12 -0
  157. package/dist/core/supervisor/contract.d.ts.map +1 -1
  158. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  159. package/dist/core/supervisor/explanations.js +2 -1
  160. package/dist/core/supervisor/findings.d.ts.map +1 -1
  161. package/dist/core/supervisor/findings.js +54 -9
  162. package/dist/core/supervisor/service.d.ts +9 -1
  163. package/dist/core/supervisor/service.d.ts.map +1 -1
  164. package/dist/core/supervisor/service.js +59 -29
  165. package/dist/core/supervisor/services.d.ts.map +1 -1
  166. package/dist/core/supervisor/services.js +22 -1
  167. package/dist/core/supervisor/status.d.ts +17 -2
  168. package/dist/core/supervisor/status.d.ts.map +1 -1
  169. package/dist/core/supervisor/status.js +70 -12
  170. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  171. package/dist/core/supervisor/timeline.js +2 -1
  172. package/dist/core/work/state.js +7 -7
  173. package/dist/core/workflow/engine.d.ts.map +1 -1
  174. package/dist/core/workflow/engine.js +105 -32
  175. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  176. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  177. package/dist/core/workflow/failure-evidence.js +114 -0
  178. package/dist/core/workflow/index.d.ts +1 -1
  179. package/dist/core/workflow/index.d.ts.map +1 -1
  180. package/dist/core/workflow/index.js +1 -1
  181. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  182. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  183. package/dist/core/workflow/live-session-v3.js +126 -5
  184. package/dist/core/workflow/proof.d.ts +5 -5
  185. package/dist/core/workflow/proof.d.ts.map +1 -1
  186. package/dist/core/workflow/proof.js +115 -20
  187. package/dist/core/workflow/run-state.d.ts +3 -1
  188. package/dist/core/workflow/run-state.d.ts.map +1 -1
  189. package/dist/core/workflow/run-state.js +31 -27
  190. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  191. package/dist/core/workflow/spawn-claude.js +19 -0
  192. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  193. package/dist/core/workflow/spawn-codex.js +13 -0
  194. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  195. package/dist/core/workflow/spawn-cursor.js +19 -0
  196. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  197. package/dist/core/workflow/spawn-opencode.js +14 -0
  198. package/dist/core/workflow/types.d.ts +64 -17
  199. package/dist/core/workflow/types.d.ts.map +1 -1
  200. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  201. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  202. package/dist/core/workflow/workspaces/index.js +0 -1
  203. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  204. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  205. package/dist/core/workflow/workspaces/leases.js +30 -16
  206. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  207. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  208. package/dist/core/workflow/workspaces/local-git.js +270 -6
  209. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  210. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  211. package/dist/core/workflow/workspaces/ownership.js +194 -0
  212. package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
  213. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  214. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  215. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  216. package/dist/core/workflow/workspaces/recovery.js +21 -13
  217. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  218. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  219. package/dist/core/workflow/workspaces/state.js +21 -1
  220. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  221. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  222. package/dist/lib/browser/windows-chrome.js +262 -73
  223. package/dist/lib/docs-links.d.ts.map +1 -1
  224. package/dist/lib/docs-links.js +45 -2
  225. package/dist/lib/guarded-remove.d.ts +7 -0
  226. package/dist/lib/guarded-remove.d.ts.map +1 -1
  227. package/dist/lib/guarded-remove.js +106 -12
  228. package/dist/lib/identities/assume.d.ts.map +1 -1
  229. package/dist/lib/identities/assume.js +4 -0
  230. package/dist/lib/tunnel/allowlist.d.ts +44 -0
  231. package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
  232. package/dist/lib/tunnel/allowlist.js +204 -0
  233. package/dist/lib/tunnel/current-address.d.ts +41 -0
  234. package/dist/lib/tunnel/current-address.d.ts.map +1 -0
  235. package/dist/lib/tunnel/current-address.js +123 -0
  236. package/dist/lib/tunnel/gate.js +13 -6
  237. package/dist/lib/tunnel/processes.d.ts +39 -0
  238. package/dist/lib/tunnel/processes.d.ts.map +1 -0
  239. package/dist/lib/tunnel/processes.js +121 -0
  240. package/dist/lib/tunnel/state.d.ts +28 -3
  241. package/dist/lib/tunnel/state.d.ts.map +1 -1
  242. package/dist/lib/tunnel/state.js +98 -27
  243. package/dist/lib/zip/index.d.ts +41 -0
  244. package/dist/lib/zip/index.d.ts.map +1 -0
  245. package/dist/lib/zip/index.js +398 -0
  246. package/package.json +8 -1
  247. package/src/commander.ts +32 -0
  248. package/src/commands/agents.ts +151 -74
  249. package/src/commands/browse.ts +50 -14
  250. package/src/commands/diagnostics.ts +10 -0
  251. package/src/commands/doctor.ts +26 -7
  252. package/src/commands/governor.ts +23 -10
  253. package/src/commands/inbox.ts +52 -2
  254. package/src/commands/rm.ts +43 -4
  255. package/src/commands/servers.ts +16 -4
  256. package/src/commands/supervisor.ts +7 -2
  257. package/src/commands/toc.ts +14 -6
  258. package/src/commands/tunnel.ts +394 -116
  259. package/src/commands/web-fetch.ts +30 -19
  260. package/src/commands/web.ts +9 -3
  261. package/src/commands/workflow.ts +30 -30
  262. package/src/core/agents/live-authority-v3.ts +17 -0
  263. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  264. package/src/core/agents/mailbox.ts +26 -17
  265. package/src/core/agents/render/prompt-context.ts +35 -20
  266. package/src/core/agents/render/session-context.ts +92 -48
  267. package/src/core/agents/rules/stop-hook.ts +3 -3
  268. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  269. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  270. package/src/core/agents/state/names.ts +117 -6
  271. package/src/core/agents/state/pidmap.ts +13 -0
  272. package/src/core/agents/state/proc-start.ts +57 -0
  273. package/src/core/agents/state/stale-sweep.ts +247 -82
  274. package/src/core/agents/state/unchanged-file.ts +456 -0
  275. package/src/core/context/index.ts +446 -136
  276. package/src/core/context/observation.ts +29 -0
  277. package/src/core/diagnostics/bundle.ts +23 -18
  278. package/src/core/diagnostics/path-identity.ts +15 -0
  279. package/src/core/diagnostics/sanitize.ts +4 -3
  280. package/src/core/events/v3/authority-outbox.ts +56 -0
  281. package/src/core/events/v3/generation-custody.ts +59 -0
  282. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  283. package/src/core/events/v3/producers/recorder.ts +94 -68
  284. package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
  285. package/src/core/governor/planning.ts +20 -8
  286. package/src/core/governor/service-read.ts +128 -20
  287. package/src/core/governor/service.ts +119 -42
  288. package/src/core/hooks/cli.ts +139 -22
  289. package/src/core/hooks/fork-lineage.ts +82 -0
  290. package/src/core/hooks/resolve/transcript.ts +88 -0
  291. package/src/core/hooks/shell-rm.ts +103 -0
  292. package/src/core/hooks/shell-tokens.ts +96 -0
  293. package/src/core/hooks/shell-waiter.ts +4 -91
  294. package/src/core/inbox/contract.ts +2 -1
  295. package/src/core/inbox/index.ts +2 -0
  296. package/src/core/inbox/peer-messages.ts +94 -0
  297. package/src/core/inbox/service.ts +88 -22
  298. package/src/core/inbox/targets.ts +148 -0
  299. package/src/core/inbox/watcher.ts +8 -9
  300. package/src/core/journal/index.ts +19 -2
  301. package/src/core/resources/contract.ts +4 -0
  302. package/src/core/resources/service-status.ts +35 -12
  303. package/src/core/resources/service.ts +71 -24
  304. package/src/core/resources/status.ts +57 -8
  305. package/src/core/servers/index.ts +177 -91
  306. package/src/core/servers/tunnels.ts +3 -11
  307. package/src/core/storage/process-log.ts +17 -2
  308. package/src/core/supervisor/contract.ts +13 -0
  309. package/src/core/supervisor/explanations.ts +4 -1
  310. package/src/core/supervisor/findings.ts +68 -10
  311. package/src/core/supervisor/service.ts +88 -26
  312. package/src/core/supervisor/services.ts +26 -8
  313. package/src/core/supervisor/status.ts +89 -12
  314. package/src/core/supervisor/timeline.ts +2 -1
  315. package/src/core/work/state.ts +7 -7
  316. package/src/core/workflow/engine.ts +131 -49
  317. package/src/core/workflow/failure-evidence.ts +141 -0
  318. package/src/core/workflow/index.ts +0 -1
  319. package/src/core/workflow/live-session-v3.ts +146 -5
  320. package/src/core/workflow/proof.ts +134 -26
  321. package/src/core/workflow/run-state.ts +31 -29
  322. package/src/core/workflow/spawn-claude.ts +18 -0
  323. package/src/core/workflow/spawn-codex.ts +13 -0
  324. package/src/core/workflow/spawn-cursor.ts +18 -0
  325. package/src/core/workflow/spawn-opencode.ts +14 -0
  326. package/src/core/workflow/types.ts +51 -17
  327. package/src/core/workflow/workspaces/index.ts +0 -1
  328. package/src/core/workflow/workspaces/leases.ts +48 -17
  329. package/src/core/workflow/workspaces/local-git.ts +315 -5
  330. package/src/core/workflow/workspaces/ownership.ts +232 -0
  331. package/src/core/workflow/workspaces/reclaim.ts +58 -0
  332. package/src/core/workflow/workspaces/recovery.ts +25 -13
  333. package/src/core/workflow/workspaces/state.ts +20 -0
  334. package/src/lib/browser/windows-chrome.ts +346 -86
  335. package/src/lib/docs-links.ts +49 -2
  336. package/src/lib/guarded-remove.ts +132 -14
  337. package/src/lib/identities/assume.ts +4 -0
  338. package/src/lib/tunnel/allowlist.ts +208 -0
  339. package/src/lib/tunnel/current-address.ts +158 -0
  340. package/src/lib/tunnel/gate.ts +16 -8
  341. package/src/lib/tunnel/processes.ts +149 -0
  342. package/src/lib/tunnel/state.ts +123 -27
  343. package/src/lib/zip/index.ts +483 -0
@@ -30,7 +30,7 @@ import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationComman
30
30
  import { readHeartbeat, setAssignedNameCache, stampSessionNameReminded, stampSessionNameRequested, stampSessionNameSeen, } from "../agents/state/heartbeat-writer.js";
31
31
  import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
32
32
  import { ensureLiveCoordinationHeartbeat } from "../agents/state/live-coordination-writer.js";
33
- import { assignName } from "../agents/state/names.js";
33
+ import { assignName, readForkParent, readForkState, recordForkDetection, resolveName, } from "../agents/state/names.js";
34
34
  import { writePidmapRow } from "../agents/state/pidmap.js";
35
35
  import { agentsRequireGitFinalization, coordFreshnessSeconds, hostPromptContextConfig, resolveBinName, } from "../config.js";
36
36
  import { checkpointContext, completeContextRecovery, extractContextSample, markContextCompactionCompleted, prepareContextRecovery, readContextState, recordContextSample, } from "../context/index.js";
@@ -48,6 +48,7 @@ import { hookTimeoutFor } from "./adapter/wiring.js";
48
48
  import { scheduleBackupSnapshot } from "./backup-schedule.js";
49
49
  import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, recordImageArtifactsV3, resetSoundCounters, runSessionSyncExtension, soundForEvent, } from "./effects/index.js";
50
50
  import { toolInputHash, toolTargetHash } from "./events/input-hash.js";
51
+ import { detectClaudeCodeFork } from "./fork-lineage.js";
51
52
  import { canonicalize } from "./guard-path.js";
52
53
  import { beginHookHealth, observeHookDebug, observeHookError, writeHookHealthCompletion, } from "./health.js";
53
54
  import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/anchor.js";
@@ -56,6 +57,7 @@ import { resolveOwner } from "./resolve/owner.js";
56
57
  import { inspectSessionNameDisplayImmediately, scanAssistantStatusBoxPresent, scanStatusBoxPresent, scanTranscriptRuntime, } from "./resolve/transcript.js";
57
58
  import { clearHookRunMarker, writeHookRunMarker } from "./run-markers.js";
58
59
  import { sessionNamePresence } from "./session-name-presence.js";
60
+ import { shellRmReason } from "./shell-rm.js";
59
61
  import { shellWaiterReason } from "./shell-waiter.js";
60
62
  let hookHealthState;
61
63
  let hookRunMarkerPath = null;
@@ -155,11 +157,13 @@ function logError(coordRoot, err, context) {
155
157
  * Mint or recover the hurricane-style name for this owner in-process. Returns
156
158
  * null on any failure so session.started emission never breaks adapter flow.
157
159
  */
158
- function assignNameInProcess(coordRoot, instanceId, kind, forkedFrom) {
160
+ function assignNameInProcess(coordRoot, instanceId, kind, fork) {
159
161
  try {
160
162
  const name = assignName(coordRoot, instanceId, kind, {
161
163
  freshnessSecs: coordFreshnessSeconds(coordRoot),
162
- ...(forkedFrom ? { forkedFrom } : {}),
164
+ ...(fork?.forkedFrom ? { forkedFrom: fork.forkedFrom } : {}),
165
+ ...(fork?.fork ? { fork: true } : {}),
166
+ ...(fork?.forkChecked ? { forkChecked: true } : {}),
163
167
  });
164
168
  return { name, kind };
165
169
  }
@@ -167,9 +171,60 @@ function assignNameInProcess(coordRoot, instanceId, kind, forkedFrom) {
167
171
  return null;
168
172
  }
169
173
  }
174
+ /**
175
+ * Name a main session, recording fork lineage when the instance is new. Every
176
+ * main-session naming path goes through here: whichever hook first sees a new
177
+ * instance writes its history row, and lineage can only ride that first row
178
+ * (later detection appends a `fork.detected` row instead).
179
+ */
180
+ function assignSessionNameInProcess(coordRoot, instanceId, payload, adapter) {
181
+ let fork;
182
+ try {
183
+ if (adapter === "claude-code" &&
184
+ payload?.session_id === instanceId &&
185
+ !resolveName(coordRoot, instanceId)) {
186
+ const detected = detectClaudeCodeFork({
187
+ coordRoot,
188
+ transcriptPath: payload.transcript_path,
189
+ sessionId: instanceId,
190
+ source: payload.source,
191
+ });
192
+ fork = {
193
+ ...(detected.forkedFrom ? { forkedFrom: detected.forkedFrom } : {}),
194
+ fork: detected.fork,
195
+ forkChecked: detected.checked,
196
+ };
197
+ }
198
+ }
199
+ catch {
200
+ fork = undefined;
201
+ }
202
+ return assignNameInProcess(coordRoot, instanceId, "session", fork);
203
+ }
204
+ /**
205
+ * Finish fork detection that session start could not: a CLI fork starts before
206
+ * its transcript exists, and a mid-flight instance may never see SessionStart.
207
+ * Runs once per instance; the appended `fork_checked` row stops later calls.
208
+ */
209
+ function healForkLineage(coordRoot, instanceId, payload) {
210
+ if (!payload?.transcript_path || payload.session_id !== instanceId)
211
+ return;
212
+ const state = readForkState(coordRoot, instanceId);
213
+ if (!state || state.checked)
214
+ return;
215
+ const detected = detectClaudeCodeFork({
216
+ coordRoot,
217
+ transcriptPath: payload.transcript_path,
218
+ sessionId: instanceId,
219
+ knownFork: state.fork,
220
+ });
221
+ if (!detected.checked)
222
+ return;
223
+ recordForkDetection(coordRoot, instanceId, detected);
224
+ }
170
225
  /** Mid-flight sessions can emit tools before SessionStart assigns a pool name.
171
226
  * Stamp the disposable cache so Codec does not render the 8-character id. */
172
- function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter) {
227
+ function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter, payload) {
173
228
  if (coordEnv("WORKFLOW_CHILD") === "1")
174
229
  return;
175
230
  const row = readLiveCoordinationRow(coordRoot, instanceId) ?? readHeartbeat(coordRoot, instanceId);
@@ -177,7 +232,7 @@ function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter) {
177
232
  return;
178
233
  if (row?.name?.trim())
179
234
  return;
180
- const assigned = assignNameInProcess(coordRoot, instanceId, "session");
235
+ const assigned = assignSessionNameInProcess(coordRoot, instanceId, payload, adapter);
181
236
  if (!assigned?.name)
182
237
  return;
183
238
  if (!readHeartbeat(coordRoot, instanceId)) {
@@ -230,21 +285,21 @@ function buildEventData(eventType, ctx) {
230
285
  // The platform label is the adapter id itself; enumerating and defaulting
231
286
  // to codex silently mislabels any adapter not in the list.
232
287
  const adapterPlatform = ctx.adapter;
233
- // Recorded fork lineage is NOT detected here. On claude-code a fork
234
- // never fires its own session.started — SessionStart fires under the
235
- // PARENT's session id (source=resume) before the fork id is minted
236
- // (verified 2026-08-05) — so detection at this point can only mislabel
237
- // the resumed parent. The fork's new instance is caught by the
238
- // tool.requested heal path instead. The forkedFrom plumbing below stays
239
- // for adapters that DO report a parent at session start.
240
- const forkedFrom = undefined;
241
288
  // Assign (or recover) name + kind in-process. Idempotent: resume
242
- // returns the original name; new owner consumes a counter slot.
289
+ // returns the original name; new owner consumes a counter slot and,
290
+ // on claude-code, runs fork detection (fork-lineage.ts). Since Claude
291
+ // Code 2.1.283 both fork flows fire SessionStart under the FORK's own
292
+ // id (verified 2026-10-07), superseding the 2026-08-05 finding that a
293
+ // fork never started on its own id. A resumed parent keeps its existing
294
+ // history row, so it is never relabeled here.
243
295
  // Workflow children stay unnamed, as on the tool path: a pool name
244
296
  // would make them the addressee of mail held for that name.
245
297
  const assigned = coordEnv("WORKFLOW_CHILD") === "1"
246
298
  ? undefined
247
- : assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session", forkedFrom);
299
+ : assignSessionNameInProcess(ctx.coordRoot, ctx.instanceId, p, ctx.adapter);
300
+ const forkedFrom = assigned
301
+ ? readForkParent(ctx.coordRoot, ctx.instanceId)?.instance_id
302
+ : undefined;
248
303
  // Write the adapter pid-map row so `harn agents whoami` ppid-walks find
249
304
  // this owner. Prefer the payload pid (the actual claude binary), then the
250
305
  // anchor walk (the `node` ancestor for Cursor, which has no payload pid),
@@ -309,7 +364,7 @@ function buildEventData(eventType, ctx) {
309
364
  // 8-character native id.
310
365
  if (coordEnv("WORKFLOW_CHILD") !== "1" &&
311
366
  !readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name) {
312
- assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session");
367
+ assignSessionNameInProcess(ctx.coordRoot, ctx.instanceId, p, ctx.adapter);
313
368
  }
314
369
  const prompt = p?.prompt ?? "";
315
370
  const { value, truncated } = clampString(prompt, 4000);
@@ -645,7 +700,7 @@ async function main() {
645
700
  priorCoordination?.kind !== "transient" &&
646
701
  !priorCoordination?.workflow_run_id &&
647
702
  !priorCoordination?.name?.trim()) {
648
- assignNameInProcess(coordRoot, owner.instance_id, "session");
703
+ assignSessionNameInProcess(coordRoot, owner.instance_id, payload, adapter);
649
704
  }
650
705
  const data = buildEventData(norm.event_type, {
651
706
  coordRoot,
@@ -820,12 +875,20 @@ function recordSignal(run) {
820
875
  }
821
876
  if (norm.event_type !== "session.ended") {
822
877
  try {
823
- ensureSessionDisplayName(coordRoot, owner.instance_id, sessionId, adapter);
878
+ ensureSessionDisplayName(coordRoot, owner.instance_id, sessionId, adapter, payload);
824
879
  }
825
880
  catch (error) {
826
881
  logError(coordRoot, error, { phase: "session-display-name" });
827
882
  }
828
883
  }
884
+ if (norm.event_type === "tool.requested" && adapter === "claude-code" && run.topLevelSession) {
885
+ try {
886
+ healForkLineage(coordRoot, owner.instance_id, payload);
887
+ }
888
+ catch (error) {
889
+ logError(coordRoot, error, { phase: "fork-lineage-heal" });
890
+ }
891
+ }
829
892
  const v3EventId = v3Result && "event" in v3Result
830
893
  ? v3Result.event.event_id
831
894
  : v3Result && "event_id" in v3Result
@@ -1281,6 +1344,12 @@ async function handleToolRequested(run) {
1281
1344
  emitDeny(adapter, waiterReason);
1282
1345
  return 0;
1283
1346
  }
1347
+ const rmReason = shellRmReason(payload?.tool_name, payload?.tool_input, resolveBinName(coordRoot));
1348
+ if (rmReason) {
1349
+ const { emitDeny } = await import("./adapter/output.js");
1350
+ emitDeny(adapter, rmReason);
1351
+ return 0;
1352
+ }
1284
1353
  // G-guard for ALL adapters. Claude Code previously ran this via a
1285
1354
  // pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
1286
1355
  // that adapter is now deleted, so agent-hook owns the deny for every adapter.
@@ -1869,12 +1938,16 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
1869
1938
  return false;
1870
1939
  const behavior = adapterBehavior(adapter);
1871
1940
  const { renderPromptContext } = await import("../agents/render/prompt-context.js");
1941
+ let peerMessageIds = [];
1872
1942
  let additionalContext = renderPromptContext({
1873
1943
  coordRoot,
1874
1944
  instanceId,
1875
1945
  sessionId,
1876
1946
  sessionNameNudge: true,
1877
1947
  ...behavior.promptContextNudges,
1948
+ onInboxPrepared: (ids) => {
1949
+ peerMessageIds = ids;
1950
+ },
1878
1951
  }).trim();
1879
1952
  const fileLinkContext = behavior.fileLinkContext?.(coordRoot, workspaceCwd) ?? "";
1880
1953
  if (fileLinkContext) {
@@ -1889,7 +1962,12 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
1889
1962
  if (!additionalContext)
1890
1963
  return false;
1891
1964
  const { emitContext } = await import("./adapter/output.js");
1892
- return emitContext(adapter, "UserPromptSubmit", additionalContext);
1965
+ const emitted = emitContext(adapter, "UserPromptSubmit", additionalContext);
1966
+ if (emitted) {
1967
+ const { acknowledgeEmittedPeerInbox } = await import("../inbox/peer-messages.js");
1968
+ await acknowledgeEmittedPeerInbox(coordRoot, instanceId, peerMessageIds);
1969
+ }
1970
+ return emitted;
1893
1971
  }
1894
1972
  async function emitSubagentStartContext(coordRoot, instanceId, sessionId, data, adapter) {
1895
1973
  // Look up the subagent's assigned name (just-written by the in-process name
@@ -1905,19 +1983,26 @@ async function emitSubagentStartContext(coordRoot, instanceId, sessionId, data,
1905
1983
  // is around. Reuse prompt-context (which dedups against the per-owner hash);
1906
1984
  // first call will always emit.
1907
1985
  let combined = message;
1986
+ let peerMessageIds = [];
1908
1987
  const { renderPromptContext } = await import("../agents/render/prompt-context.js");
1909
1988
  const context = renderPromptContext({
1910
1989
  coordRoot,
1911
1990
  instanceId,
1912
1991
  sessionId,
1913
1992
  agentName: subagentName,
1993
+ onInboxPrepared: (ids) => {
1994
+ peerMessageIds = ids;
1995
+ },
1914
1996
  }).trim();
1915
1997
  if (context)
1916
1998
  combined = `${message}\n\n${context}`;
1917
1999
  // Use SubagentStart event-name in CC's hookSpecificOutput shape; cursor's
1918
2000
  // flat `additional_context` works the same way.
1919
2001
  const { emitContext } = await import("./adapter/output.js");
1920
- emitContext(adapter, "SubagentStart", combined);
2002
+ if (emitContext(adapter, "SubagentStart", combined)) {
2003
+ const { acknowledgeEmittedPeerInbox } = await import("../inbox/peer-messages.js");
2004
+ await acknowledgeEmittedPeerInbox(coordRoot, instanceId, peerMessageIds);
2005
+ }
1921
2006
  }
1922
2007
  function adapterPlatform(adapter) {
1923
2008
  return adapter;
@@ -1926,6 +2011,7 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
1926
2011
  const behavior = adapterBehavior(adapter);
1927
2012
  const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
1928
2013
  let additionalContext = "";
2014
+ let peerMessageIds = [];
1929
2015
  // Opportunistic reconciliation makes normal session starts a failsafe for
1930
2016
  // archive, idle, cascade, and host lifecycle observations. It also runs the
1931
2017
  // stale cache sweep through the shared ADR 0077 composition.
@@ -1944,6 +2030,9 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
1944
2030
  sessionId,
1945
2031
  agentName: agentName || undefined,
1946
2032
  platformLabel: behavior.sessionStartPlatformLabel,
2033
+ onInboxPrepared: (ids) => {
2034
+ peerMessageIds = ids;
2035
+ },
1947
2036
  }).trim();
1948
2037
  }
1949
2038
  // Effect (journal-maintaining adapters): merge the journal recovery cue into
@@ -1972,11 +2061,19 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
1972
2061
  }
1973
2062
  }
1974
2063
  const { emitContext } = await import("./adapter/output.js");
1975
- return emitContext(adapter, "SessionStart", additionalContext);
2064
+ const emitted = emitContext(adapter, "SessionStart", additionalContext);
2065
+ if (emitted) {
2066
+ const { acknowledgeEmittedPeerInbox } = await import("../inbox/peer-messages.js");
2067
+ await acknowledgeEmittedPeerInbox(coordRoot, instanceId, peerMessageIds);
2068
+ }
2069
+ return emitted;
1976
2070
  }
1977
2071
  function completeRecoveryInjection(coordRoot, instanceId, sessionId, recovery) {
1978
- completeContextRecovery(coordRoot, { sessionId, instanceId });
1979
- void recovery;
2072
+ completeContextRecovery(coordRoot, {
2073
+ sessionId,
2074
+ instanceId,
2075
+ checkpoint: recovery.checkpoint,
2076
+ });
1980
2077
  }
1981
2078
  main()
1982
2079
  .then(async (code) => {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Claude Code fork detection: decide whether a new main-session instance is a
3
+ * branch of another session, and which one.
4
+ *
5
+ * Two shapes exist as of Claude Code 2.1.283 (verified 2026-10-07):
6
+ *
7
+ * - CLI `--resume <id> --fork-session` fires SessionStart under the FORK's id
8
+ * with `source: "fork"`, before its transcript exists, and names no parent.
9
+ * The transcript later holds the parent's message uuids with every row's
10
+ * `sessionId` rewritten, so the parent is found by uuid containment
11
+ * (`detectForkParent`) once the transcript is on disk.
12
+ * - The desktop app's "Fork from here" copies the parent's rows into the
13
+ * fork's transcript with their original `sessionId`, so inherited rows
14
+ * name the parent directly (`detectInheritedSessionParent`).
15
+ *
16
+ * Inherited rows alone do not prove a fork: a continuation of a finished
17
+ * session can carry them too. The parent's liveness separates the two. A
18
+ * parent with an unended generation is still running (or resumable) and owns
19
+ * its name, task, and claims, so the new instance is a fork. A finished
20
+ * parent makes it a continuation, which keeps the generic notice.
21
+ *
22
+ * Everything here fails open: an unreadable transcript or an unknown layout
23
+ * yields "not a fork", never an error.
24
+ */
25
+ export interface ForkDetection {
26
+ fork: boolean;
27
+ forkedFrom?: string;
28
+ /** Detection had the evidence it needs; the tool-path heal can skip it. */
29
+ checked: boolean;
30
+ }
31
+ export declare function detectClaudeCodeFork(input: {
32
+ coordRoot: string;
33
+ transcriptPath: string | undefined;
34
+ sessionId: string;
35
+ source?: string;
36
+ /** The instance was already recorded as a fork without a parent. */
37
+ knownFork?: boolean;
38
+ isLive?: (instanceId: string) => boolean;
39
+ }): ForkDetection;
40
+ //# sourceMappingURL=fork-lineage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fork-lineage.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/fork-lineage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;CAClB;AAKD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;CAC1C,GAAG,aAAa,CAkChB"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Claude Code fork detection: decide whether a new main-session instance is a
3
+ * branch of another session, and which one.
4
+ *
5
+ * Two shapes exist as of Claude Code 2.1.283 (verified 2026-10-07):
6
+ *
7
+ * - CLI `--resume <id> --fork-session` fires SessionStart under the FORK's id
8
+ * with `source: "fork"`, before its transcript exists, and names no parent.
9
+ * The transcript later holds the parent's message uuids with every row's
10
+ * `sessionId` rewritten, so the parent is found by uuid containment
11
+ * (`detectForkParent`) once the transcript is on disk.
12
+ * - The desktop app's "Fork from here" copies the parent's rows into the
13
+ * fork's transcript with their original `sessionId`, so inherited rows
14
+ * name the parent directly (`detectInheritedSessionParent`).
15
+ *
16
+ * Inherited rows alone do not prove a fork: a continuation of a finished
17
+ * session can carry them too. The parent's liveness separates the two. A
18
+ * parent with an unended generation is still running (or resumable) and owns
19
+ * its name, task, and claims, so the new instance is a fork. A finished
20
+ * parent makes it a continuation, which keeps the generic notice.
21
+ *
22
+ * Everything here fails open: an unreadable transcript or an unknown layout
23
+ * yields "not a fork", never an error.
24
+ */
25
+ import { existsSync } from "node:fs";
26
+ import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
27
+ import { detectForkParent, detectInheritedSessionParent } from "./resolve/transcript.js";
28
+ /** Sources after which a brand-new instance cannot hold inherited history. */
29
+ const FRESH_SOURCES = new Set(["startup", "clear"]);
30
+ export function detectClaudeCodeFork(input) {
31
+ try {
32
+ const declared = input.source === "fork" || input.knownFork === true;
33
+ const transcriptReady = !!input.transcriptPath && existsSync(input.transcriptPath);
34
+ const isLive = input.isLive ?? ((id) => readLiveCoordinationRow(input.coordRoot, id) !== null);
35
+ const inherited = transcriptReady
36
+ ? detectInheritedSessionParent(input.transcriptPath, input.sessionId)
37
+ : undefined;
38
+ if (inherited && inherited !== input.sessionId) {
39
+ if (declared || isLive(inherited)) {
40
+ return { fork: true, forkedFrom: inherited, checked: true };
41
+ }
42
+ return { fork: false, checked: true };
43
+ }
44
+ if (declared) {
45
+ if (!transcriptReady)
46
+ return { fork: true, checked: false };
47
+ const parent = detectForkParent(input.transcriptPath, input.sessionId);
48
+ return {
49
+ fork: true,
50
+ ...(parent && parent !== input.sessionId ? { forkedFrom: parent } : {}),
51
+ checked: true,
52
+ };
53
+ }
54
+ return {
55
+ fork: false,
56
+ checked: transcriptReady || FRESH_SOURCES.has(input.source ?? ""),
57
+ };
58
+ }
59
+ catch {
60
+ return { fork: false, checked: true };
61
+ }
62
+ }
@@ -92,6 +92,22 @@ export interface TranscriptRuntimeScan {
92
92
  * SessionStart).
93
93
  */
94
94
  export declare function scanTranscriptRuntime(transcriptPath: string | undefined): TranscriptRuntimeScan | undefined;
95
+ /**
96
+ * Detect inherited history from row session ids: the session whose rows were
97
+ * copied into this transcript, or undefined when every row is this session's.
98
+ *
99
+ * Empirical basis (verified 2026-10-07, Claude Code 2.1.283): the desktop
100
+ * app's "Fork from here" copies the parent's rows into the fork's transcript
101
+ * WITHOUT rewriting their `sessionId`, so the fork's file opens with thousands
102
+ * of rows stamped with the parent's id. The CLI `--fork-session` path rewrites
103
+ * the ids instead and is covered by {@link detectForkParent}.
104
+ *
105
+ * When the history is itself several generations deep, the LAST foreign row is
106
+ * the nearest ancestor, so the tail is scanned first. Whether the foreign
107
+ * session is a fork parent or a dead predecessor (a continuation) is the
108
+ * caller's call. Fail-soft: never throws, undefined on any read problem.
109
+ */
110
+ export declare function detectInheritedSessionParent(transcriptPath: string | undefined, sessionId: string): string | undefined;
95
111
  /**
96
112
  * Detect the parent session of a forked CC conversation from its transcript.
97
113
  *
@@ -1 +1 @@
1
- {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,wBAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,OAAO,CAIT;AAED,MAAM,MAAM,4BAA4B,GACpC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IACE,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;CAClF,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,4BAA4B,CAqD9B;AAgBD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;4CAC4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,qBAAqB,GAAG,SAAS,CA8BnC;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAkDpB"}
1
+ {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,wBAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,OAAO,CAIT;AAED,MAAM,MAAM,4BAA4B,GACpC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IACE,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;CAClF,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,4BAA4B,CAqD9B;AAgBD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;4CAC4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,qBAAqB,GAAG,SAAS,CA8BnC;AAgBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAiBpB;AA6CD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAkDpB"}
@@ -318,6 +318,90 @@ const SESSION_FILE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-
318
318
  const FORK_SCAN_MAX_BYTES = 128 * 1024 * 1024;
319
319
  /** How many message uuids to sample across the fork's copied prefix. */
320
320
  const FORK_SAMPLE_SIZE = 24;
321
+ /** Tail window scanned for inherited rows; a fork's own rows are few when the
322
+ * scan runs (session start or the first tool call), so the copied history ends
323
+ * well inside it. */
324
+ const INHERITED_TAIL_BYTES = 4 * 1024 * 1024;
325
+ const INHERITED_HEAD_BYTES = 64 * 1024;
326
+ const SESSION_ID_FIELD_RE = /"sessionId":"([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})"/gi;
327
+ /**
328
+ * Detect inherited history from row session ids: the session whose rows were
329
+ * copied into this transcript, or undefined when every row is this session's.
330
+ *
331
+ * Empirical basis (verified 2026-10-07, Claude Code 2.1.283): the desktop
332
+ * app's "Fork from here" copies the parent's rows into the fork's transcript
333
+ * WITHOUT rewriting their `sessionId`, so the fork's file opens with thousands
334
+ * of rows stamped with the parent's id. The CLI `--fork-session` path rewrites
335
+ * the ids instead and is covered by {@link detectForkParent}.
336
+ *
337
+ * When the history is itself several generations deep, the LAST foreign row is
338
+ * the nearest ancestor, so the tail is scanned first. Whether the foreign
339
+ * session is a fork parent or a dead predecessor (a continuation) is the
340
+ * caller's call. Fail-soft: never throws, undefined on any read problem.
341
+ */
342
+ export function detectInheritedSessionParent(transcriptPath, sessionId) {
343
+ try {
344
+ const readablePath = resolveTranscriptPath(transcriptPath);
345
+ if (!readablePath || !sessionId)
346
+ return undefined;
347
+ const size = statSync(readablePath).size;
348
+ const fromTail = lastForeignSessionId(readRange(readablePath, Math.max(0, size - INHERITED_TAIL_BYTES), size), sessionId);
349
+ if (fromTail || size <= INHERITED_TAIL_BYTES)
350
+ return fromTail;
351
+ return lastForeignSessionId(readRange(readablePath, 0, Math.min(size, INHERITED_HEAD_BYTES)), sessionId);
352
+ }
353
+ catch {
354
+ return undefined;
355
+ }
356
+ }
357
+ function lastForeignSessionId(text, sessionId) {
358
+ if (!text)
359
+ return undefined;
360
+ const own = sessionId.toLowerCase();
361
+ const lines = text.split("\n");
362
+ for (let i = lines.length - 1; i >= 0; i--) {
363
+ const line = lines[i];
364
+ if (!line.includes('"sessionId":"'))
365
+ continue;
366
+ let foreign = false;
367
+ for (const m of line.matchAll(SESSION_ID_FIELD_RE)) {
368
+ if (m[1].toLowerCase() !== own)
369
+ foreign = true;
370
+ }
371
+ if (!foreign)
372
+ continue;
373
+ // Confirm on the parsed row: a nested object can carry another session's
374
+ // id, but only the row's own top-level field says whose row it is. A
375
+ // partial line at a window edge fails to parse and is skipped.
376
+ try {
377
+ const row = JSON.parse(line);
378
+ if (typeof row.sessionId === "string" && row.sessionId.toLowerCase() !== own) {
379
+ return row.sessionId;
380
+ }
381
+ }
382
+ catch {
383
+ /* torn line */
384
+ }
385
+ }
386
+ return undefined;
387
+ }
388
+ function readRange(path, start, end) {
389
+ try {
390
+ const fs = require("node:fs");
391
+ const fd = fs.openSync(path, "r");
392
+ try {
393
+ const buf = Buffer.alloc(Math.max(0, end - start));
394
+ fs.readSync(fd, buf, 0, buf.length, start);
395
+ return buf.toString("utf8");
396
+ }
397
+ finally {
398
+ fs.closeSync(fd);
399
+ }
400
+ }
401
+ catch {
402
+ return undefined;
403
+ }
404
+ }
321
405
  /**
322
406
  * Detect the parent session of a forked CC conversation from its transcript.
323
407
  *
@@ -0,0 +1,2 @@
1
+ export declare function shellRmReason(toolName: unknown, toolInput: unknown, binName: string): string | null;
2
+ //# sourceMappingURL=shell-rm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-rm.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-rm.ts"],"names":[],"mappings":"AAsFA,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAYf"}
@@ -0,0 +1,105 @@
1
+ import { basename, shellCommandFromTool, tokenize } from "./shell-tokens.js";
2
+ /**
3
+ * Deny a shell `rm` and name the guarded replacement. The instructions have
4
+ * said "use <bin> rm instead of shell rm" since the command shipped, and
5
+ * agents still reached for bare rm several times a day, often on unquoted
6
+ * variable paths that Claude Code then stops on for a human prompt. A
7
+ * repeated, machine-detectable failure belongs in a gate, not more prose.
8
+ *
9
+ * This is a guardrail against the reflex, not a security boundary: it reads
10
+ * command positions only (so `git rm`, `docker rm`, and quoted examples pass),
11
+ * plus the `xargs rm`, `find -exec rm`, and `bash -c '...rm...'` shapes.
12
+ * HARNERY_ALLOW_RM=1 on the command is the deliberate override.
13
+ */
14
+ const OVERRIDE = "HARNERY_ALLOW_RM=1";
15
+ const SEPARATORS = new Set([";", "|", "||", "&", "&&", "(", ")", "{", "}"]);
16
+ const KEYWORDS = new Set(["do", "if", "then", "elif", "else", "!", "while", "until", "time"]);
17
+ const WRAPPERS = new Set(["command", "exec", "builtin", "nohup", "sudo", "doas", "env", "nice"]);
18
+ // xargs options that consume the following token as their value.
19
+ const XARGS_VALUE_FLAGS = new Set(["-n", "-I", "-L", "-P", "-d", "-E", "-s", "-a"]);
20
+ function isRm(value, quoted) {
21
+ return !quoted && basename(value) === "rm";
22
+ }
23
+ function invokesRm(source, depth = 0) {
24
+ if (depth > 8)
25
+ return false;
26
+ const tokens = tokenize(source);
27
+ if (tokens.some((t) => !t.quoted && t.value === OVERRIDE))
28
+ return false;
29
+ let start = true;
30
+ let afterWrapper = false;
31
+ for (let i = 0; i < tokens.length; i++) {
32
+ const token = tokens[i];
33
+ if (token.operator) {
34
+ if (SEPARATORS.has(token.value)) {
35
+ start = true;
36
+ afterWrapper = false;
37
+ }
38
+ continue;
39
+ }
40
+ if (!start)
41
+ continue;
42
+ if (!token.quoted) {
43
+ if (KEYWORDS.has(token.value))
44
+ continue;
45
+ if (/^[A-Za-z_][A-Za-z_0-9]*=/.test(token.value))
46
+ continue;
47
+ if (afterWrapper && token.value.startsWith("-"))
48
+ continue;
49
+ }
50
+ const name = basename(token.value);
51
+ if (WRAPPERS.has(name)) {
52
+ afterWrapper = true;
53
+ continue;
54
+ }
55
+ start = false;
56
+ afterWrapper = false;
57
+ if (isRm(token.value, token.quoted))
58
+ return true;
59
+ if (name === "xargs") {
60
+ for (let j = i + 1; j < tokens.length && !tokens[j].operator; j++) {
61
+ const arg = tokens[j];
62
+ if (arg.value.startsWith("-")) {
63
+ if (XARGS_VALUE_FLAGS.has(arg.value))
64
+ j++;
65
+ continue;
66
+ }
67
+ if (isRm(arg.value, arg.quoted))
68
+ return true;
69
+ break;
70
+ }
71
+ }
72
+ if (name === "find") {
73
+ for (let j = i + 1; j < tokens.length && !tokens[j].operator; j++) {
74
+ if (!/^-(?:exec|execdir|ok|okdir)$/.test(tokens[j].value))
75
+ continue;
76
+ const next = tokens[j + 1];
77
+ if (next && !next.operator && isRm(next.value, next.quoted))
78
+ return true;
79
+ }
80
+ }
81
+ if (["bash", "sh", "dash", "zsh", "ksh"].includes(name)) {
82
+ for (let j = i + 1; j < tokens.length && !tokens[j].operator; j++) {
83
+ if (/^-[a-z]*c[a-z]*$/.test(tokens[j].value)) {
84
+ const script = tokens[j + 1];
85
+ if (script && !script.operator && invokesRm(script.value, depth + 1))
86
+ return true;
87
+ break;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ return false;
93
+ }
94
+ export function shellRmReason(toolName, toolInput, binName) {
95
+ const command = shellCommandFromTool(toolName, toolInput);
96
+ if (command === null || !invokesRm(command))
97
+ return null;
98
+ return (`Shell rm blocked: it skips the resolved-path, Git-ownership, and peer-claim checks, and an ` +
99
+ `unquoted variable path can expand to the wrong target. Use the guarded paths instead: ` +
100
+ `\`${binName} rm --root <directory> <paths...>\` previews removal of untracked files (add ` +
101
+ `--yes to delete, -r for a directory); \`git rm\` removes tracked source; ` +
102
+ `\`${binName} artifacts discard <id>\` retires managed evidence. To replace a file, overwrite ` +
103
+ `it in place or \`mv\` the new file over the old one, so nothing needs deleting. If none of ` +
104
+ `these fit, prefix the command with ${OVERRIDE}.`);
105
+ }
@@ -0,0 +1,12 @@
1
+ /** Shared, deliberately small shell lexer for PreToolUse command guards. */
2
+ export interface Token {
3
+ value: string;
4
+ quoted: boolean;
5
+ operator?: boolean;
6
+ }
7
+ /** Keep quoted arguments opaque, including examples and heredoc bodies. */
8
+ export declare function tokenize(source: string): Token[];
9
+ export declare function basename(value: string): string;
10
+ /** The command string of a Bash-style tool call, or null for any other tool. */
11
+ export declare function shellCommandFromTool(toolName: unknown, toolInput: unknown): string | null;
12
+ //# sourceMappingURL=shell-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-tokens.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-tokens.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,CA+DhD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgBzF"}