@cluesmith/codev 3.2.3 → 3.3.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 (445) hide show
  1. package/dashboard-dist/assets/index-BZRTEMtH.css +32 -0
  2. package/dashboard-dist/assets/{index-BZ1x7x1X.js → index-JCbkpAcV.js} +33 -33
  3. package/dashboard-dist/assets/index-JCbkpAcV.js.map +1 -0
  4. package/dashboard-dist/index.html +2 -2
  5. package/dist/agent-farm/cli.d.ts.map +1 -1
  6. package/dist/agent-farm/cli.js +173 -0
  7. package/dist/agent-farm/cli.js.map +1 -1
  8. package/dist/agent-farm/commands/attach.js +2 -2
  9. package/dist/agent-farm/commands/attach.js.map +1 -1
  10. package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
  11. package/dist/agent-farm/commands/cleanup.js +16 -0
  12. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  13. package/dist/agent-farm/commands/inbox.d.ts +42 -0
  14. package/dist/agent-farm/commands/inbox.d.ts.map +1 -0
  15. package/dist/agent-farm/commands/inbox.js +129 -0
  16. package/dist/agent-farm/commands/inbox.js.map +1 -0
  17. package/dist/agent-farm/commands/interrupt.d.ts +20 -0
  18. package/dist/agent-farm/commands/interrupt.d.ts.map +1 -0
  19. package/dist/agent-farm/commands/interrupt.js +65 -0
  20. package/dist/agent-farm/commands/interrupt.js.map +1 -0
  21. package/dist/agent-farm/commands/reset/constants.d.ts +52 -0
  22. package/dist/agent-farm/commands/reset/constants.d.ts.map +1 -0
  23. package/dist/agent-farm/commands/reset/constants.js +52 -0
  24. package/dist/agent-farm/commands/reset/constants.js.map +1 -0
  25. package/dist/agent-farm/commands/reset/context.d.ts +230 -0
  26. package/dist/agent-farm/commands/reset/context.d.ts.map +1 -0
  27. package/dist/agent-farm/commands/reset/context.js +428 -0
  28. package/dist/agent-farm/commands/reset/context.js.map +1 -0
  29. package/dist/agent-farm/commands/reset/index.d.ts +192 -0
  30. package/dist/agent-farm/commands/reset/index.d.ts.map +1 -0
  31. package/dist/agent-farm/commands/reset/index.js +488 -0
  32. package/dist/agent-farm/commands/reset/index.js.map +1 -0
  33. package/dist/agent-farm/commands/reset/receipt.d.ts +97 -0
  34. package/dist/agent-farm/commands/reset/receipt.d.ts.map +1 -0
  35. package/dist/agent-farm/commands/reset/receipt.js +149 -0
  36. package/dist/agent-farm/commands/reset/receipt.js.map +1 -0
  37. package/dist/agent-farm/commands/reset/reorient.d.ts +117 -0
  38. package/dist/agent-farm/commands/reset/reorient.d.ts.map +1 -0
  39. package/dist/agent-farm/commands/reset/reorient.js +313 -0
  40. package/dist/agent-farm/commands/reset/reorient.js.map +1 -0
  41. package/dist/agent-farm/commands/reset.d.ts +19 -0
  42. package/dist/agent-farm/commands/reset.d.ts.map +1 -0
  43. package/dist/agent-farm/commands/reset.js +292 -0
  44. package/dist/agent-farm/commands/reset.js.map +1 -0
  45. package/dist/agent-farm/commands/send.d.ts +2 -2
  46. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  47. package/dist/agent-farm/commands/send.js +52 -11
  48. package/dist/agent-farm/commands/send.js.map +1 -1
  49. package/dist/agent-farm/commands/setup.d.ts +4 -1
  50. package/dist/agent-farm/commands/setup.d.ts.map +1 -1
  51. package/dist/agent-farm/commands/setup.js +8 -4
  52. package/dist/agent-farm/commands/setup.js.map +1 -1
  53. package/dist/agent-farm/commands/spawn-worktree.d.ts +116 -6
  54. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  55. package/dist/agent-farm/commands/spawn-worktree.js +345 -74
  56. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  57. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  58. package/dist/agent-farm/commands/spawn.js +13 -1
  59. package/dist/agent-farm/commands/spawn.js.map +1 -1
  60. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  61. package/dist/agent-farm/commands/status.js +105 -9
  62. package/dist/agent-farm/commands/status.js.map +1 -1
  63. package/dist/agent-farm/commands/tower-sweep-husks.d.ts +6 -0
  64. package/dist/agent-farm/commands/tower-sweep-husks.d.ts.map +1 -0
  65. package/dist/agent-farm/commands/tower-sweep-husks.js +77 -0
  66. package/dist/agent-farm/commands/tower-sweep-husks.js.map +1 -0
  67. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  68. package/dist/agent-farm/commands/tower.js +34 -0
  69. package/dist/agent-farm/commands/tower.js.map +1 -1
  70. package/dist/agent-farm/commands/whoami.d.ts +65 -0
  71. package/dist/agent-farm/commands/whoami.d.ts.map +1 -0
  72. package/dist/agent-farm/commands/whoami.js +196 -0
  73. package/dist/agent-farm/commands/whoami.js.map +1 -0
  74. package/dist/agent-farm/db/index.d.ts +1 -1
  75. package/dist/agent-farm/db/index.d.ts.map +1 -1
  76. package/dist/agent-farm/db/index.js +80 -1
  77. package/dist/agent-farm/db/index.js.map +1 -1
  78. package/dist/agent-farm/db/mailbox.d.ts +222 -0
  79. package/dist/agent-farm/db/mailbox.d.ts.map +1 -0
  80. package/dist/agent-farm/db/mailbox.js +304 -0
  81. package/dist/agent-farm/db/mailbox.js.map +1 -0
  82. package/dist/agent-farm/db/schema.d.ts +1 -1
  83. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  84. package/dist/agent-farm/db/schema.js +35 -0
  85. package/dist/agent-farm/db/schema.js.map +1 -1
  86. package/dist/agent-farm/db/types.d.ts +45 -0
  87. package/dist/agent-farm/db/types.d.ts.map +1 -1
  88. package/dist/agent-farm/db/types.js.map +1 -1
  89. package/dist/agent-farm/lib/builder-lookup.d.ts +1 -1
  90. package/dist/agent-farm/lib/builder-lookup.js +2 -2
  91. package/dist/agent-farm/lib/builder-lookup.js.map +1 -1
  92. package/dist/agent-farm/lib/reconnect-backoff.d.ts +54 -0
  93. package/dist/agent-farm/lib/reconnect-backoff.d.ts.map +1 -0
  94. package/dist/agent-farm/lib/reconnect-backoff.js +47 -0
  95. package/dist/agent-farm/lib/reconnect-backoff.js.map +1 -0
  96. package/dist/agent-farm/lib/tower-client.d.ts +16 -6
  97. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  98. package/dist/agent-farm/lib/tower-client.js +37 -6
  99. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  100. package/dist/agent-farm/lib/tunnel-client.d.ts +69 -1
  101. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  102. package/dist/agent-farm/lib/tunnel-client.js +218 -27
  103. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  104. package/dist/agent-farm/servers/architect-session-holder.d.ts +133 -0
  105. package/dist/agent-farm/servers/architect-session-holder.d.ts.map +1 -0
  106. package/dist/agent-farm/servers/architect-session-holder.js +268 -0
  107. package/dist/agent-farm/servers/architect-session-holder.js.map +1 -0
  108. package/dist/agent-farm/servers/cron-delivery.d.ts +72 -0
  109. package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -0
  110. package/dist/agent-farm/servers/cron-delivery.js +80 -0
  111. package/dist/agent-farm/servers/cron-delivery.js.map +1 -0
  112. package/dist/agent-farm/servers/delayed-send.d.ts +85 -0
  113. package/dist/agent-farm/servers/delayed-send.d.ts.map +1 -0
  114. package/dist/agent-farm/servers/delayed-send.js +165 -0
  115. package/dist/agent-farm/servers/delayed-send.js.map +1 -0
  116. package/dist/agent-farm/servers/gate-profiles.d.ts +67 -0
  117. package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -0
  118. package/dist/agent-farm/servers/gate-profiles.js +113 -0
  119. package/dist/agent-farm/servers/gate-profiles.js.map +1 -0
  120. package/dist/agent-farm/servers/mailbox-delivery.d.ts +370 -0
  121. package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -0
  122. package/dist/agent-farm/servers/mailbox-delivery.js +617 -0
  123. package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -0
  124. package/dist/agent-farm/servers/mailbox-wiring.d.ts +125 -0
  125. package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -0
  126. package/dist/agent-farm/servers/mailbox-wiring.js +425 -0
  127. package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -0
  128. package/dist/agent-farm/servers/message-write.d.ts +53 -1
  129. package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
  130. package/dist/agent-farm/servers/message-write.js +63 -0
  131. package/dist/agent-farm/servers/message-write.js.map +1 -1
  132. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  133. package/dist/agent-farm/servers/overview.js +23 -4
  134. package/dist/agent-farm/servers/overview.js.map +1 -1
  135. package/dist/agent-farm/servers/process-census.d.ts +41 -0
  136. package/dist/agent-farm/servers/process-census.d.ts.map +1 -0
  137. package/dist/agent-farm/servers/process-census.js +62 -0
  138. package/dist/agent-farm/servers/process-census.js.map +1 -0
  139. package/dist/agent-farm/servers/render-gate.d.ts +141 -0
  140. package/dist/agent-farm/servers/render-gate.d.ts.map +1 -0
  141. package/dist/agent-farm/servers/render-gate.js +251 -0
  142. package/dist/agent-farm/servers/render-gate.js.map +1 -0
  143. package/dist/agent-farm/servers/session-log-sweep.d.ts +93 -0
  144. package/dist/agent-farm/servers/session-log-sweep.d.ts.map +1 -0
  145. package/dist/agent-farm/servers/session-log-sweep.js +165 -0
  146. package/dist/agent-farm/servers/session-log-sweep.js.map +1 -0
  147. package/dist/agent-farm/servers/session-submit.d.ts +90 -0
  148. package/dist/agent-farm/servers/session-submit.d.ts.map +1 -0
  149. package/dist/agent-farm/servers/session-submit.js +127 -0
  150. package/dist/agent-farm/servers/session-submit.js.map +1 -0
  151. package/dist/agent-farm/servers/shellper-husk-sweep.d.ts +87 -0
  152. package/dist/agent-farm/servers/shellper-husk-sweep.d.ts.map +1 -0
  153. package/dist/agent-farm/servers/shellper-husk-sweep.js +129 -0
  154. package/dist/agent-farm/servers/shellper-husk-sweep.js.map +1 -0
  155. package/dist/agent-farm/servers/tower-cron.d.ts +10 -7
  156. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  157. package/dist/agent-farm/servers/tower-cron.js +58 -53
  158. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  159. package/dist/agent-farm/servers/tower-instances.d.ts +14 -2
  160. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  161. package/dist/agent-farm/servers/tower-instances.js +128 -19
  162. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  163. package/dist/agent-farm/servers/tower-messages.d.ts +38 -1
  164. package/dist/agent-farm/servers/tower-messages.d.ts.map +1 -1
  165. package/dist/agent-farm/servers/tower-messages.js +106 -1
  166. package/dist/agent-farm/servers/tower-messages.js.map +1 -1
  167. package/dist/agent-farm/servers/tower-routes.d.ts +16 -4
  168. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  169. package/dist/agent-farm/servers/tower-routes.js +807 -99
  170. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  171. package/dist/agent-farm/servers/tower-server.js +241 -15
  172. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  173. package/dist/agent-farm/servers/tower-terminals.d.ts +17 -2
  174. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  175. package/dist/agent-farm/servers/tower-terminals.js +139 -124
  176. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  177. package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
  178. package/dist/agent-farm/servers/tower-tunnel.js +63 -4
  179. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
  180. package/dist/agent-farm/servers/tower-types.d.ts +2 -1
  181. package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
  182. package/dist/agent-farm/servers/tower-utils.d.ts +99 -1
  183. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  184. package/dist/agent-farm/servers/tower-utils.js +242 -6
  185. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  186. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
  187. package/dist/agent-farm/servers/tower-websocket.js +6 -21
  188. package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
  189. package/dist/agent-farm/servers/write-queue.d.ts +35 -0
  190. package/dist/agent-farm/servers/write-queue.d.ts.map +1 -0
  191. package/dist/agent-farm/servers/write-queue.js +56 -0
  192. package/dist/agent-farm/servers/write-queue.js.map +1 -0
  193. package/dist/agent-farm/state.d.ts +6 -1
  194. package/dist/agent-farm/state.d.ts.map +1 -1
  195. package/dist/agent-farm/state.js +5 -2
  196. package/dist/agent-farm/state.js.map +1 -1
  197. package/dist/agent-farm/types.d.ts +39 -0
  198. package/dist/agent-farm/types.d.ts.map +1 -1
  199. package/dist/agent-farm/utils/agent-names.d.ts +2 -2
  200. package/dist/agent-farm/utils/agent-names.d.ts.map +1 -1
  201. package/dist/agent-farm/utils/agent-names.js +3 -3
  202. package/dist/agent-farm/utils/agent-names.js.map +1 -1
  203. package/dist/agent-farm/utils/architect-name.d.ts +2 -2
  204. package/dist/agent-farm/utils/architect-name.d.ts.map +1 -1
  205. package/dist/agent-farm/utils/architect-name.js +3 -3
  206. package/dist/agent-farm/utils/architect-name.js.map +1 -1
  207. package/dist/agent-farm/utils/config.d.ts +13 -0
  208. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  209. package/dist/agent-farm/utils/config.js +33 -1
  210. package/dist/agent-farm/utils/config.js.map +1 -1
  211. package/dist/agent-farm/utils/harness.d.ts +103 -7
  212. package/dist/agent-farm/utils/harness.d.ts.map +1 -1
  213. package/dist/agent-farm/utils/harness.js +126 -19
  214. package/dist/agent-farm/utils/harness.js.map +1 -1
  215. package/dist/cli.d.ts.map +1 -1
  216. package/dist/cli.js +6 -32
  217. package/dist/cli.js.map +1 -1
  218. package/dist/commands/adopt.d.ts +3 -2
  219. package/dist/commands/adopt.d.ts.map +1 -1
  220. package/dist/commands/adopt.js +7 -6
  221. package/dist/commands/adopt.js.map +1 -1
  222. package/dist/commands/consult/agy-auth-cache.d.ts +97 -0
  223. package/dist/commands/consult/agy-auth-cache.d.ts.map +1 -0
  224. package/dist/commands/consult/agy-auth-cache.js +320 -0
  225. package/dist/commands/consult/agy-auth-cache.js.map +1 -0
  226. package/dist/commands/consult/cli-options.d.ts +34 -0
  227. package/dist/commands/consult/cli-options.d.ts.map +1 -0
  228. package/dist/commands/consult/cli-options.js +67 -0
  229. package/dist/commands/consult/cli-options.js.map +1 -0
  230. package/dist/commands/consult/index.d.ts +56 -2
  231. package/dist/commands/consult/index.d.ts.map +1 -1
  232. package/dist/commands/consult/index.js +330 -27
  233. package/dist/commands/consult/index.js.map +1 -1
  234. package/dist/commands/consult/metrics.d.ts +42 -0
  235. package/dist/commands/consult/metrics.d.ts.map +1 -1
  236. package/dist/commands/consult/metrics.js +129 -9
  237. package/dist/commands/consult/metrics.js.map +1 -1
  238. package/dist/commands/doctor.d.ts +28 -0
  239. package/dist/commands/doctor.d.ts.map +1 -1
  240. package/dist/commands/doctor.js +258 -22
  241. package/dist/commands/doctor.js.map +1 -1
  242. package/dist/commands/init.d.ts +3 -2
  243. package/dist/commands/init.d.ts.map +1 -1
  244. package/dist/commands/init.js +7 -6
  245. package/dist/commands/init.js.map +1 -1
  246. package/dist/commands/porch/checks.d.ts +23 -0
  247. package/dist/commands/porch/checks.d.ts.map +1 -1
  248. package/dist/commands/porch/checks.js +60 -0
  249. package/dist/commands/porch/checks.js.map +1 -1
  250. package/dist/commands/porch/config.d.ts +20 -0
  251. package/dist/commands/porch/config.d.ts.map +1 -1
  252. package/dist/commands/porch/config.js +20 -0
  253. package/dist/commands/porch/config.js.map +1 -1
  254. package/dist/commands/porch/index.d.ts.map +1 -1
  255. package/dist/commands/porch/index.js +32 -36
  256. package/dist/commands/porch/index.js.map +1 -1
  257. package/dist/commands/porch/next.d.ts.map +1 -1
  258. package/dist/commands/porch/next.js +2 -41
  259. package/dist/commands/porch/next.js.map +1 -1
  260. package/dist/commands/update.d.ts +4 -4
  261. package/dist/commands/update.d.ts.map +1 -1
  262. package/dist/commands/update.js +9 -11
  263. package/dist/commands/update.js.map +1 -1
  264. package/dist/lib/config.d.ts +70 -3
  265. package/dist/lib/config.d.ts.map +1 -1
  266. package/dist/lib/config.js +67 -3
  267. package/dist/lib/config.js.map +1 -1
  268. package/dist/lib/consult-lanes.d.ts +149 -0
  269. package/dist/lib/consult-lanes.d.ts.map +1 -0
  270. package/dist/lib/consult-lanes.js +360 -0
  271. package/dist/lib/consult-lanes.js.map +1 -0
  272. package/dist/lib/default-branch.d.ts +1 -1
  273. package/dist/lib/default-branch.js +1 -1
  274. package/dist/lib/github.js +1 -1
  275. package/dist/lib/github.js.map +1 -1
  276. package/dist/lib/gitignore.d.ts +25 -2
  277. package/dist/lib/gitignore.d.ts.map +1 -1
  278. package/dist/lib/gitignore.js +70 -0
  279. package/dist/lib/gitignore.js.map +1 -1
  280. package/dist/lib/migration-backup-audit.d.ts +64 -0
  281. package/dist/lib/migration-backup-audit.d.ts.map +1 -0
  282. package/dist/lib/migration-backup-audit.js +145 -0
  283. package/dist/lib/migration-backup-audit.js.map +1 -0
  284. package/dist/lib/prompt-behavior-metrics.d.ts +67 -0
  285. package/dist/lib/prompt-behavior-metrics.d.ts.map +1 -0
  286. package/dist/lib/prompt-behavior-metrics.js +290 -0
  287. package/dist/lib/prompt-behavior-metrics.js.map +1 -0
  288. package/dist/lib/protocol-drift-audit.d.ts +118 -0
  289. package/dist/lib/protocol-drift-audit.d.ts.map +1 -0
  290. package/dist/lib/protocol-drift-audit.js +217 -0
  291. package/dist/lib/protocol-drift-audit.js.map +1 -0
  292. package/dist/lib/scaffold.d.ts +10 -3
  293. package/dist/lib/scaffold.d.ts.map +1 -1
  294. package/dist/lib/scaffold.js +29 -26
  295. package/dist/lib/scaffold.js.map +1 -1
  296. package/dist/lib/skeleton.d.ts +22 -1
  297. package/dist/lib/skeleton.d.ts.map +1 -1
  298. package/dist/lib/skeleton.js +120 -0
  299. package/dist/lib/skeleton.js.map +1 -1
  300. package/dist/lib/test-env.d.ts +74 -0
  301. package/dist/lib/test-env.d.ts.map +1 -0
  302. package/dist/lib/test-env.js +92 -0
  303. package/dist/lib/test-env.js.map +1 -0
  304. package/dist/terminal/pty-manager.d.ts +9 -0
  305. package/dist/terminal/pty-manager.d.ts.map +1 -1
  306. package/dist/terminal/pty-manager.js +32 -6
  307. package/dist/terminal/pty-manager.js.map +1 -1
  308. package/dist/terminal/pty-session.d.ts +156 -4
  309. package/dist/terminal/pty-session.d.ts.map +1 -1
  310. package/dist/terminal/pty-session.js +307 -42
  311. package/dist/terminal/pty-session.js.map +1 -1
  312. package/dist/terminal/ring-buffer.d.ts +39 -6
  313. package/dist/terminal/ring-buffer.d.ts.map +1 -1
  314. package/dist/terminal/ring-buffer.js +74 -6
  315. package/dist/terminal/ring-buffer.js.map +1 -1
  316. package/dist/terminal/session-manager.d.ts +129 -0
  317. package/dist/terminal/session-manager.d.ts.map +1 -1
  318. package/dist/terminal/session-manager.js +337 -44
  319. package/dist/terminal/session-manager.js.map +1 -1
  320. package/dist/terminal/session-screen.d.ts +91 -0
  321. package/dist/terminal/session-screen.d.ts.map +1 -0
  322. package/dist/terminal/session-screen.js +135 -0
  323. package/dist/terminal/session-screen.js.map +1 -0
  324. package/dist/terminal/shellper-client.d.ts +19 -4
  325. package/dist/terminal/shellper-client.d.ts.map +1 -1
  326. package/dist/terminal/shellper-client.js +70 -8
  327. package/dist/terminal/shellper-client.js.map +1 -1
  328. package/dist/terminal/shellper-main.js +2 -1
  329. package/dist/terminal/shellper-main.js.map +1 -1
  330. package/dist/terminal/shellper-process.d.ts +1 -1
  331. package/dist/terminal/shellper-process.d.ts.map +1 -1
  332. package/dist/terminal/shellper-process.js +33 -9
  333. package/dist/terminal/shellper-process.js.map +1 -1
  334. package/dist/terminal/shellper-protocol.d.ts +40 -2
  335. package/dist/terminal/shellper-protocol.d.ts.map +1 -1
  336. package/dist/terminal/shellper-protocol.js +72 -5
  337. package/dist/terminal/shellper-protocol.js.map +1 -1
  338. package/dist/terminal/shellper-replay-buffer.d.ts +69 -7
  339. package/dist/terminal/shellper-replay-buffer.d.ts.map +1 -1
  340. package/dist/terminal/shellper-replay-buffer.js +167 -31
  341. package/dist/terminal/shellper-replay-buffer.js.map +1 -1
  342. package/package.json +11 -9
  343. package/skeleton/.claude/skills/afx/SKILL.md +27 -1
  344. package/skeleton/.claude/skills/arch-init/SKILL.md +135 -0
  345. package/skeleton/.claude/skills/arch-save/SKILL.md +183 -0
  346. package/skeleton/.claude/skills/codev/SKILL.md +33 -2
  347. package/skeleton/.claude/skills/runnable-worktrees/SKILL.md +119 -0
  348. package/skeleton/.codex/skills/afx/SKILL.md +170 -0
  349. package/skeleton/.codex/skills/arch-init/SKILL.md +135 -0
  350. package/skeleton/.codex/skills/arch-save/SKILL.md +183 -0
  351. package/skeleton/.codex/skills/codev/SKILL.md +90 -0
  352. package/skeleton/.codex/skills/consult/SKILL.md +96 -0
  353. package/skeleton/.codex/skills/generate-image/SKILL.md +45 -0
  354. package/skeleton/.codex/skills/porch/SKILL.md +60 -0
  355. package/skeleton/.codex/skills/runnable-worktrees/SKILL.md +119 -0
  356. package/skeleton/.codex/skills/update-arch-docs/SKILL.md +132 -0
  357. package/skeleton/protocols/air/builder-prompt.md +28 -47
  358. package/skeleton/protocols/air/consult-types/impl-review.md +17 -34
  359. package/skeleton/protocols/air/consult-types/pr-review.md +14 -37
  360. package/skeleton/protocols/air/prompts/implement.md +20 -73
  361. package/skeleton/protocols/air/prompts/pr.md +22 -54
  362. package/skeleton/protocols/air/protocol.md +40 -71
  363. package/skeleton/protocols/aspir/builder-prompt.md +29 -66
  364. package/skeleton/protocols/aspir/consult-types/impl-review.md +17 -44
  365. package/skeleton/protocols/aspir/consult-types/phase-review.md +17 -44
  366. package/skeleton/protocols/aspir/consult-types/plan-review.md +14 -38
  367. package/skeleton/protocols/aspir/consult-types/pr-review.md +13 -39
  368. package/skeleton/protocols/aspir/consult-types/spec-review.md +13 -33
  369. package/skeleton/protocols/aspir/prompts/implement.md +27 -196
  370. package/skeleton/protocols/aspir/prompts/plan.md +21 -85
  371. package/skeleton/protocols/aspir/prompts/review.md +29 -228
  372. package/skeleton/protocols/aspir/prompts/specify.md +25 -111
  373. package/skeleton/protocols/aspir/protocol.json +10 -0
  374. package/skeleton/protocols/aspir/protocol.md +36 -82
  375. package/skeleton/protocols/bugfix/builder-prompt.md +23 -45
  376. package/skeleton/protocols/bugfix/consult-types/impl-review.md +21 -45
  377. package/skeleton/protocols/bugfix/consult-types/pr-review.md +18 -52
  378. package/skeleton/protocols/bugfix/prompts/fix.md +16 -61
  379. package/skeleton/protocols/bugfix/prompts/investigate.md +18 -62
  380. package/skeleton/protocols/bugfix/prompts/pr.md +22 -57
  381. package/skeleton/protocols/bugfix/protocol.md +44 -50
  382. package/skeleton/protocols/experiment/builder-prompt.md +25 -58
  383. package/skeleton/protocols/experiment/protocol.md +22 -188
  384. package/skeleton/protocols/experiment/templates/notes.md +11 -71
  385. package/skeleton/protocols/maintain/builder-prompt.md +24 -45
  386. package/skeleton/protocols/maintain/consult-types/impl-review.md +17 -44
  387. package/skeleton/protocols/maintain/consult-types/pr-review.md +13 -39
  388. package/skeleton/protocols/maintain/prompts/maintain.md +1 -1
  389. package/skeleton/protocols/maintain/protocol.md +32 -266
  390. package/skeleton/protocols/maintain/templates/maintenance-run.md +11 -0
  391. package/skeleton/protocols/pir/builder-prompt.md +30 -64
  392. package/skeleton/protocols/pir/consult-types/impl-review.md +17 -50
  393. package/skeleton/protocols/pir/consult-types/pr-review.md +11 -40
  394. package/skeleton/protocols/pir/prompts/implement.md +1 -2
  395. package/skeleton/protocols/pir/prompts/plan.md +0 -3
  396. package/skeleton/protocols/pir/prompts/review.md +1 -1
  397. package/skeleton/protocols/pir/protocol.md +54 -177
  398. package/skeleton/protocols/research/builder-prompt.md +27 -58
  399. package/skeleton/protocols/research/protocol.md +28 -153
  400. package/skeleton/protocols/spike/builder-prompt.md +30 -48
  401. package/skeleton/protocols/spike/protocol.md +24 -107
  402. package/skeleton/protocols/spike/templates/findings.md +9 -39
  403. package/skeleton/protocols/spir/builder-prompt.md +33 -62
  404. package/skeleton/protocols/spir/consult-types/impl-review.md +17 -44
  405. package/skeleton/protocols/spir/consult-types/phase-review.md +17 -44
  406. package/skeleton/protocols/spir/consult-types/plan-review.md +14 -38
  407. package/skeleton/protocols/spir/consult-types/pr-review.md +13 -39
  408. package/skeleton/protocols/spir/consult-types/spec-review.md +13 -33
  409. package/skeleton/protocols/spir/prompts/implement.md +26 -195
  410. package/skeleton/protocols/spir/prompts/plan.md +20 -84
  411. package/skeleton/protocols/spir/prompts/review.md +28 -227
  412. package/skeleton/protocols/spir/prompts/specify.md +24 -110
  413. package/skeleton/protocols/spir/protocol.json +10 -0
  414. package/skeleton/protocols/spir/protocol.md +84 -633
  415. package/skeleton/protocols/spir/templates/plan.md +28 -147
  416. package/skeleton/protocols/spir/templates/review.md +21 -80
  417. package/skeleton/protocols/spir/templates/spec.md +24 -118
  418. package/skeleton/resources/commands/agent-farm.md +348 -0
  419. package/skeleton/resources/commands/codev.md +5 -0
  420. package/skeleton/resources/commands/consult.md +262 -8
  421. package/skeleton/resources/commands/overview.md +1 -0
  422. package/skeleton/roles/architect.md +67 -306
  423. package/skeleton/roles/builder.md +77 -191
  424. package/skeleton/templates/AGENTS.md +2 -0
  425. package/skeleton/templates/CLAUDE.md +2 -0
  426. package/templates/tower.html +39 -2
  427. package/dashboard-dist/assets/index-BZ1x7x1X.js.map +0 -1
  428. package/dashboard-dist/assets/index-DB1KBSNE.css +0 -32
  429. package/dist/agent-farm/servers/send-buffer.d.ts +0 -59
  430. package/dist/agent-farm/servers/send-buffer.d.ts.map +0 -1
  431. package/dist/agent-farm/servers/send-buffer.js +0 -103
  432. package/dist/agent-farm/servers/send-buffer.js.map +0 -1
  433. package/skeleton/porch/prompts/defend.md +0 -108
  434. package/skeleton/porch/prompts/diagnose.md +0 -70
  435. package/skeleton/porch/prompts/evaluate.md +0 -132
  436. package/skeleton/porch/prompts/fix.md +0 -59
  437. package/skeleton/porch/prompts/implement.md +0 -88
  438. package/skeleton/porch/prompts/plan.md +0 -74
  439. package/skeleton/porch/prompts/pr.md +0 -84
  440. package/skeleton/porch/prompts/review.md +0 -249
  441. package/skeleton/porch/prompts/specify.md +0 -53
  442. package/skeleton/porch/prompts/test.md +0 -63
  443. package/skeleton/protocols/aspir/templates/plan.md +0 -184
  444. package/skeleton/protocols/aspir/templates/review.md +0 -120
  445. package/skeleton/protocols/aspir/templates/spec.md +0 -154
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Render-empty gate (Spec 1313, Phase 2) — the sole authority that answers
3
+ * "is this screen a clean, empty prompt?".
4
+ *
5
+ * A message body is only ever written to a prompt this gate proves empty, so
6
+ * corruption is eliminated by construction: a message can never fuse with a
7
+ * draft because it is never delivered while one exists. The classifier is a direct
8
+ * port of the G-lite classifier validated against the real claude/codex TUIs in
9
+ * spike 1265 (`codev/spikes/1265-poc/exp-g2-glite-prod-path.mjs`).
10
+ *
11
+ * WHAT it reads (Spec 1313 render-gate round 2 — capped-ring reconciliation): the gate
12
+ * classifies a rendered terminal SCREEN via the sync core {@link classifyBuffer}. In
13
+ * PRODUCTION that screen is the session's persistent bounded mirror (`SessionScreen`,
14
+ * terminal layer): one long-lived `@xterm/headless` Terminal fed the session's output
15
+ * incrementally — from birth on the live path — whose current viewport IS the live screen. The gate originally
16
+ * REBUILT the screen every check by replaying the whole output ring
17
+ * (`ringBuffer.getAll().join('\n')`) through a throwaway Terminal — but #1205 capped the ring's
18
+ * newline-free `partial` at 2 MiB (`trimPartial` halves to ~1 MiB), and a claude/codex
19
+ * alt-screen frame (`\x1b[?1049h`) is exactly one giant newline-free partial. So once a busy
20
+ * long-lived agent's frame crossed the cap, the gate was handed a TORN front — dropping the
21
+ * composer marker (→ false `no-composer-marker`) or the composer's lower rule (→ the region
22
+ * spills into status chrome → false `user-text`) — and held its mail PERMANENTLY. That is the
23
+ * over-ceiling delivery outage the whole-ring render was meant to eliminate, resurrected one
24
+ * layer down for exactly the busiest agents. A bounded terminal mirror needs only the live byte
25
+ * stream, not the whole ring, so the cap is irrelevant, the live-ring tear is gone, each classify is
26
+ * O(viewport) rather than O(ring size), and the whole-render era's unbounded-`partial` OOM risk
27
+ * (#1047: one classify allocating a multi-hundred-MB string) is closed. The whole-ring
28
+ * {@link classifyScreen} entry survives for the fixture suite and any transient one-shot
29
+ * classify; it shares the SAME classifier core, so the two paths can never diverge.
30
+ *
31
+ * Caveat — adopt/reconnect seed: after a Tower restart the mirror is seeded from a bounded replay
32
+ * tail (`capRingSeed`, 1 MiB), not the live-from-birth stream, so a long-lived alt-screen frame can
33
+ * be born torn on adopt. That classifies not-clean → the gate HOLDS (fail-safe, never a misdelivery)
34
+ * and self-heals on the next repaint/viewer nudge. Pre-existing (the pre-round-2 whole-ring gate saw
35
+ * the same capped seed), not a round-2 regression; tracked as #1361.
36
+ *
37
+ * Classifier (fail-toward-not-clean): CLEAN requires
38
+ * (a) a recognized composer marker on the screen, AND
39
+ * (b) a positively-bounded composer region (a rule/status line BELOW the
40
+ * marker — never a scan to the screen bottom), AND
41
+ * (c) zero normal-intensity (non-dim), non-whitespace, non-chrome cells in that
42
+ * region — with one measured exemption: claude's suggested-command *ghost* cursor
43
+ * cell (an inverse, non-dim char at the cursor followed by a non-empty dim run), which
44
+ * is composer chrome, not typed text (see `isGhostCursorCell`).
45
+ * The placeholder-vs-user-text distinction is an SGR attribute — both TUIs
46
+ * render rotating placeholder/hint text DIM while typed text is normal-intensity
47
+ * (measured, spike g2) — so no placeholder allowlist is needed. Anything
48
+ * unrecognized (no marker, no region boundary, a menu, a picker, a draft, a
49
+ * wrapper/boot screen, or a mirror that has not yet repainted a coherent frame) → NOT clean →
50
+ * the message stays held. There is no force path.
51
+ *
52
+ * Cost (spike g2, @xterm/headless 6.0.0): classifying a rendered viewport is sub-millisecond
53
+ * (bounded rows × cols, independent of history); the mirror pays a normal terminal-emulator
54
+ * parse per output chunk — the cost any emulator pays for the byte stream — amortised across
55
+ * the session instead of spent in a per-check whole-history burst.
56
+ */
57
+ // `@xterm/headless` resolves to its CommonJS entry (no `exports` map, no
58
+ // `type: module`), and its named exports are not statically analyzable, so a
59
+ // native-node ESM `import { Terminal }` throws "Named export 'Terminal' not
60
+ // found" when the compiled dist runs under node (production; masked under vitest
61
+ // by vite's CJS interop). Default-import the module object — the codebase's
62
+ // convention for CJS deps (cf. `import Database from 'better-sqlite3'`).
63
+ import xtermHeadless from '@xterm/headless';
64
+ const { Terminal } = xtermHeadless;
65
+ /**
66
+ * Box-drawing / prompt chrome that is never "user text". The composer marker
67
+ * glyphs (❯ ›) live here too; the marker cell is additionally skipped by
68
+ * position so a profile whose marker is not listed still never self-trips.
69
+ */
70
+ const IGNORE_CHARS = new Set(['❯', '›', '│', '▌', '─', '━', '╌', '┄', '╭', '╰', '┌', '└', '']);
71
+ /** All-whitespace (incl. NBSP and other Unicode spaces) → ignorable. */
72
+ const WHITESPACE = /^\s+$/u;
73
+ /** Rendered viewport lines, right-trimmed — the same extraction the spike asserts on. */
74
+ function screenLines(term, rows) {
75
+ const buf = term.buffer.active;
76
+ const top = buf.viewportY;
77
+ const lines = [];
78
+ for (let i = 0; i < rows; i++) {
79
+ const line = buf.getLine(top + i);
80
+ lines.push(line ? line.translateToString(true).trimEnd() : '');
81
+ }
82
+ return lines;
83
+ }
84
+ /** Last row index whose text starts with the profile's composer marker, or -1. */
85
+ function findMarkerRow(lines, markerPattern) {
86
+ let markerRow = -1;
87
+ for (let i = 0; i < lines.length; i++) {
88
+ if (markerPattern.test(lines[i]))
89
+ markerRow = i;
90
+ }
91
+ return markerRow;
92
+ }
93
+ /**
94
+ * First region-ending row after the marker (the rule/status line beneath the
95
+ * composer), or -1 when none is found. -1 means the composer has no proven lower
96
+ * bound (a partial/mid-repaint frame, or a torn replay) — the caller MUST hold, not
97
+ * scan to the screen bottom: scanning further counts status chrome below the
98
+ * composer as user text (the old bug) OR, if that chrome renders empty/dim, returns
99
+ * a false CLEAN. A missing boundary is indeterminate, and indeterminate is not-clean.
100
+ */
101
+ function findRegionEnd(lines, markerRow, endPatterns) {
102
+ for (let i = markerRow + 1; i < lines.length; i++) {
103
+ if (endPatterns.some((p) => p.test(lines[i])))
104
+ return i;
105
+ }
106
+ return -1;
107
+ }
108
+ /**
109
+ * Ghost-suggestion cursor cell (Spec 1313 render-gate hardening — false-`busy` on an idle
110
+ * claude composer). When claude's own last reply mentioned a runnable command it paints
111
+ * that command into the otherwise-empty composer as a *suggested-command ghost*, and the
112
+ * ghost's first character doubles as the software block cursor: it is rendered SGR-7
113
+ * INVERSE at normal intensity while the rest of the ghost is SGR-2 dim
114
+ * (`❯ ␛[7m a ␛[27m␛[2mfx cleanup …␛[22m`, measured live — captured as
115
+ * `claude-ghost-suggestion-empty.replay.bin`). The universal dim rule already skips the
116
+ * ghost body, but the lone inverse cursor cell was counted as user text → the composer
117
+ * classified `user-text`/`busy` FOREVER while genuinely empty, so mail to an idle
118
+ * (unattended) agent was never delivered (fail-safe becomes fail-forever for an idle
119
+ * recipient — the exact agent `afx send` exists to wake).
120
+ *
121
+ * This exempts exactly that cell: the cell at the headless buffer's cursor position,
122
+ * rendered inverse at normal intensity, whose following run on the same row is dim or
123
+ * empty (the measured ghost tail). It is deliberately NARROW — NOT the blanket inverse
124
+ * skip the finding warns against — and does not false-clean a real draft:
125
+ * - measured, claude renders the block cursor inverse only on the trailing WHITESPACE
126
+ * past a real draft (already skipped as whitespace) and never inverse-renders typed
127
+ * characters, so a real draft's typed cells are non-inverse and still counted;
128
+ * - an inverse *selection* over real multi-char text fails the dim-tail test (its
129
+ * following cells are non-dim) and, even if it passed, only this one cell is skipped
130
+ * while every other selected cell keeps the verdict `busy`.
131
+ * A lone inverse cursor cell with NO dim tail (a 1-char draft with the cursor sitting on its
132
+ * only char, empty composer otherwise) is NOT exempted — it stays `busy` — because the
133
+ * exemption requires positive ghost evidence (≥1 dim suggestion-body cell). That closes the
134
+ * false-clean an empty-tail exemption would have opened, honoring the no-new-corruption-vector
135
+ * / fail-toward-hold invariant (Codex CMAP, 2026-08-06). Real ghosts always carry a multi-char
136
+ * dim command body (the captured fixture's tail is 23 dim cells), so nothing real is lost.
137
+ */
138
+ function isGhostCursorCell(line, row, col, cols, cursorRow, cursorCol, cell, probe) {
139
+ if (row !== cursorRow || col !== cursorCol)
140
+ return false;
141
+ if (!cell.isInverse())
142
+ return false; // typed text is never inverse-rendered; only the software cursor is
143
+ // Require POSITIVE ghost evidence: at least one dim, non-whitespace, non-chrome cell must
144
+ // follow on this row (the SGR-2 suggestion body), and EVERY following such cell must be dim.
145
+ // An empty / whitespace-only tail is NOT a ghost — it is a 1-char draft with the cursor on
146
+ // its only char, which must stay `busy` (fail-toward-hold; a lone inverse cell is not proof
147
+ // of a ghost). Any non-dim text to the right ⇒ real content, also not a ghost.
148
+ let sawDimTail = false;
149
+ for (let c = col + 1; c < cols; c++) {
150
+ line.getCell(c, probe);
151
+ const ch = probe.getChars();
152
+ if (!ch || WHITESPACE.test(ch) || IGNORE_CHARS.has(ch))
153
+ continue;
154
+ if (!probe.isDim())
155
+ return false;
156
+ sawDimTail = true;
157
+ }
158
+ return sawDimTail;
159
+ }
160
+ /**
161
+ * The classifier CORE (Spec 1313 render-gate round 2): classify an already-rendered
162
+ * headless buffer against a profile. Synchronous — it only READS the live buffer, it never
163
+ * parses — so it is shared, unchanged, by BOTH gate paths: the production persistent-mirror
164
+ * gate (`SessionScreen.read()` → this) and the transient {@link classifyScreen} (write a
165
+ * replay into a throwaway term → this). One classifier core means the two paths can never
166
+ * disagree about what "empty" means.
167
+ *
168
+ * Precondition: the caller has already parsed all input into `term` (the mirror flushes in
169
+ * `read()`; `classifyScreen` awaits its `write`). Having no `await`, a single call is atomic
170
+ * against concurrent feeds — nothing can mutate the buffer mid-scan.
171
+ *
172
+ * Returns `{ clean: true, detail: 'empty' }` only when a composer marker is present and the
173
+ * composer region carries zero normal-intensity user cells; otherwise
174
+ * `{ clean: false, reason: 'busy', … }`.
175
+ */
176
+ export function classifyBuffer(term, cols, rows, profile) {
177
+ const buf = term.buffer.active;
178
+ const lines = screenLines(term, rows);
179
+ const markerRow = findMarkerRow(lines, profile.markerPattern);
180
+ if (markerRow === -1) {
181
+ // No composer marker: a wrapper/boot screen, a full-screen picker with no marker, a
182
+ // mirror that has not yet repainted a coherent frame, or an unrenderable snapshot.
183
+ // Never clean — the safe direction.
184
+ return { clean: false, reason: 'busy', detail: 'no-composer-marker' };
185
+ }
186
+ const endRow = findRegionEnd(lines, markerRow, profile.regionEndPatterns);
187
+ if (endRow === -1) {
188
+ // A marker with no rule/status line beneath it: a partial/mid-repaint frame. The
189
+ // composer has no proven lower bound, so hold rather than scan into the status chrome
190
+ // below it (which would either miscount chrome as user text or, if it renders
191
+ // empty/dim, return a false CLEAN).
192
+ return { clean: false, reason: 'busy', detail: 'no-region-end' };
193
+ }
194
+ const top = buf.viewportY;
195
+ const cell = buf.getNullCell();
196
+ const probe = buf.getNullCell(); // scratch cell for the ghost-tail look-ahead (never clobbers `cell`)
197
+ // Cursor position is viewport-relative (matching `row`, which indexes from `viewportY`).
198
+ const cursorRow = buf.cursorY;
199
+ const cursorCol = buf.cursorX;
200
+ let userCells = 0;
201
+ for (let row = markerRow; row < endRow; row++) {
202
+ const line = buf.getLine(top + row);
203
+ if (!line)
204
+ continue;
205
+ for (let col = 0; col < cols; col++) {
206
+ line.getCell(col, cell);
207
+ const ch = cell.getChars();
208
+ if (!ch || WHITESPACE.test(ch) || IGNORE_CHARS.has(ch))
209
+ continue;
210
+ if (row === markerRow && col === 0)
211
+ continue; // the marker glyph itself
212
+ if (cell.isDim())
213
+ continue; // placeholder / hint chrome renders dim (claude/codex)
214
+ if (profile.placeholderFgPalette !== undefined &&
215
+ cell.isFgPalette() &&
216
+ cell.getFgColor() === profile.placeholderFgPalette) {
217
+ continue; // per-app placeholder color: agy renders its idle hint in palette-8 (gray)
218
+ }
219
+ if (isGhostCursorCell(line, row, col, cols, cursorRow, cursorCol, cell, probe)) {
220
+ continue; // claude's suggested-command ghost cursor cell (see isGhostCursorCell)
221
+ }
222
+ userCells++;
223
+ }
224
+ }
225
+ return userCells === 0
226
+ ? { clean: true, detail: 'empty' }
227
+ : { clean: false, reason: 'busy', detail: 'user-text' };
228
+ }
229
+ /**
230
+ * Classify a one-shot replay snapshot by rendering it into a THROWAWAY headless terminal
231
+ * (Spec 1313). The transient path — the fixture suite and any caller holding a replay string
232
+ * rather than a live mirror. Production instead classifies the session's persistent
233
+ * {@link SessionScreen} directly via {@link classifyBuffer} (see the module header). Async
234
+ * because the headless terminal parses its input on a write callback; the shared
235
+ * {@link classifyBuffer} then does the actual classification.
236
+ */
237
+ export async function classifyScreen(snapshot, profile) {
238
+ const { cols, rows } = snapshot;
239
+ // A throwaway terminal for this single classify. scrollback 2000 is ample for a
240
+ // whole-replay render; the gate reads only the viewport, so the value never changes the
241
+ // verdict (the persistent mirror uses a much smaller one for the same reason).
242
+ const term = new Terminal({ cols, rows, allowProposedApi: true, scrollback: 2000 });
243
+ try {
244
+ await new Promise((resolve) => term.write(snapshot.replay, resolve));
245
+ return classifyBuffer(term, cols, rows, profile);
246
+ }
247
+ finally {
248
+ term.dispose();
249
+ }
250
+ }
251
+ //# sourceMappingURL=render-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-gate.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/render-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,yEAAyE;AACzE,6EAA6E;AAC7E,4EAA4E;AAC5E,iFAAiF;AACjF,4EAA4E;AAC5E,yEAAyE;AACzE,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAK5C,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC;AAqEnC;;;;GAIG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AAE/F,wEAAwE;AACxE,MAAM,UAAU,GAAG,QAAQ,CAAC;AAE5B,yFAAyF;AACzF,SAAS,WAAW,CAAC,IAAsB,EAAE,IAAY;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC;IAC1B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kFAAkF;AAClF,SAAS,aAAa,CAAC,KAAe,EAAE,aAAqB;IAC3D,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,SAAS,GAAG,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,KAAe,EAAE,SAAiB,EAAE,WAAqB;IAC9E,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClD,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,SAAS,iBAAiB,CACxB,IAAgB,EAChB,GAAW,EACX,GAAW,EACX,IAAY,EACZ,SAAiB,EACjB,SAAiB,EACjB,IAAgB,EAChB,KAAiB;IAEjB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACzD,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO,KAAK,CAAC,CAAC,oEAAoE;IACzG,0FAA0F;IAC1F,6FAA6F;IAC7F,2FAA2F;IAC3F,4FAA4F;IAC5F,+EAA+E;IAC/E,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACvB,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,SAAS;QACjE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;YAAE,OAAO,KAAK,CAAC;QACjC,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAsB,EACtB,IAAY,EACZ,IAAY,EACZ,OAAoB;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC/B,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAEtC,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9D,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;QACrB,oFAAoF;QACpF,mFAAmF;QACnF,oCAAoC;QACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1E,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QAClB,iFAAiF;QACjF,sFAAsF;QACtF,8EAA8E;QAC9E,oCAAoC;QACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACnE,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC;IAC1B,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,qEAAqE;IACtG,yFAAyF;IACzF,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC;IAC9B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC;IAC9B,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,IAAI,GAAG,GAAG,SAAS,EAAE,GAAG,GAAG,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;YACpC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACxB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,IAAI,CAAC,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,SAAS;YACjE,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,CAAC;gBAAE,SAAS,CAAC,0BAA0B;YACxE,IAAI,IAAI,CAAC,KAAK,EAAE;gBAAE,SAAS,CAAC,uDAAuD;YACnF,IACE,OAAO,CAAC,oBAAoB,KAAK,SAAS;gBAC1C,IAAI,CAAC,WAAW,EAAE;gBAClB,IAAI,CAAC,UAAU,EAAE,KAAK,OAAO,CAAC,oBAAoB,EAClD,CAAC;gBACD,SAAS,CAAC,2EAA2E;YACvF,CAAC;YACD,IAAI,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC/E,SAAS,CAAC,uEAAuE;YACnF,CAAC;YACD,SAAS,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,SAAS,KAAK,CAAC;QACpB,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAClC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAsB,EAAE,OAAoB;IAC/E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IAChC,gFAAgF;IAChF,wFAAwF;IACxF,+EAA+E;IAC/E,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IACpF,IAAI,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3E,OAAO,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;AACH,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Issue #1238: retention for PTY session logs.
3
+ *
4
+ * `PtySession` already bounds a *single* session's log (50 MB, one `.1`
5
+ * rotation — see `../../terminal/pty-session.ts`), but nothing has ever
6
+ * deleted the log of a session that ended. Every terminal Tower has ever
7
+ * opened leaves a file behind forever: an audited machine reached 29,728 files
8
+ * / 19 GB in `~/.agent-farm/logs`, 97% of it untouched for over a month.
9
+ *
10
+ * This module adds the missing cross-file policy: a log is deleted once its
11
+ * last write is older than the retention window. mtime is the retention key
12
+ * because the last byte written to a session log is, in effect, the moment
13
+ * that session stopped producing output — the "session ended" signal, without
14
+ * needing to join against DB session state (which is itself pruned, so dead
15
+ * sessions leave no row to join against).
16
+ *
17
+ * Live sessions are excluded by id regardless of mtime. A session log's fd is
18
+ * held open for the session's whole life, so unlinking it under a live writer
19
+ * would silently redirect every subsequent write into an unlinked inode —
20
+ * losing the log of the one session someone might actually want to read. A
21
+ * 30-day-idle live session is vanishingly unlikely, but the guard is cheap.
22
+ *
23
+ * Wired in `./tower-server.ts` on two triggers (Tower startup, and a daily
24
+ * in-process timer), mirroring the husk sweep in `./shellper-husk-sweep.ts`.
25
+ */
26
+ export declare const DEFAULT_LOG_RETENTION_DAYS = 30;
27
+ export declare const DEFAULT_LOG_SWEEP_INTERVAL_MS: number;
28
+ export declare const MS_PER_DAY: number;
29
+ /**
30
+ * Retention window in days (env `AGENT_FARM_LOG_RETENTION_DAYS`, default 30).
31
+ * `0` disables the sweep entirely — an explicit "keep everything" opt-out for
32
+ * anyone who treats these logs as an audit trail.
33
+ *
34
+ * NaN-checked rather than `parsed || default` because `0` is a meaningful
35
+ * override and is falsy in JS (same reasoning as `resolveHuskGraceMs`).
36
+ */
37
+ export declare function resolveLogRetentionDays(env?: NodeJS.ProcessEnv): number;
38
+ /**
39
+ * Sweep cadence (env `AGENT_FARM_LOG_SWEEP_INTERVAL_MS`, default 24h). Floored
40
+ * at 1s so a bad value can't spin the event loop; tests shorten it deliberately.
41
+ */
42
+ export declare function resolveLogSweepIntervalMs(env?: NodeJS.ProcessEnv): number;
43
+ export interface SessionLogFootprint {
44
+ /** Number of session log files (including `.log.1` rotations). */
45
+ files: number;
46
+ /** Total bytes on disk. */
47
+ bytes: number;
48
+ /** mtime of the oldest log, or null when there are none. */
49
+ oldestMtimeMs: number | null;
50
+ }
51
+ /**
52
+ * Total footprint of the session log directory. Read-only — used by
53
+ * `codev doctor` to surface the number the issue was filed about, and by the
54
+ * sweep's own logging. A missing directory is a healthy zero, not an error.
55
+ */
56
+ export declare function measureSessionLogs(logDir: string): SessionLogFootprint;
57
+ export interface SweepSessionLogsOptions {
58
+ /** The session log directory, i.e. `~/.agent-farm/logs`. */
59
+ logDir: string;
60
+ /** Age at which a log becomes eligible for deletion. `0` disables the sweep. */
61
+ retentionMs: number;
62
+ /**
63
+ * Ids of sessions that are alive right now. Their logs are never deleted,
64
+ * however old the mtime looks, because their fd is still open.
65
+ */
66
+ activeSessionIds?: Iterable<string>;
67
+ /** Seam, defaults to `Date.now()`. */
68
+ now?: number;
69
+ log?: (msg: string) => void;
70
+ }
71
+ export interface SweepSessionLogsResult {
72
+ /** Session log files examined. */
73
+ scanned: number;
74
+ /** Files unlinked. */
75
+ deleted: number;
76
+ /** Bytes reclaimed (sum of the deleted files' sizes). */
77
+ bytesFreed: number;
78
+ /** Aged-out files kept because their session is still live. */
79
+ skippedActive: number;
80
+ /** Files that were eligible but could not be unlinked. */
81
+ failed: number;
82
+ }
83
+ /**
84
+ * Delete session logs whose last write is older than `retentionMs`.
85
+ *
86
+ * Never throws: a log directory that can't be read, or an individual file that
87
+ * can't be stat'd or unlinked, is counted and skipped. Log retention is
88
+ * janitorial — it must not be able to take Tower's startup path down with it.
89
+ */
90
+ export declare function sweepSessionLogs(opts: SweepSessionLogsOptions): SweepSessionLogsResult;
91
+ /** Human-readable byte count for log lines and `codev doctor` output. */
92
+ export declare function formatBytes(bytes: number): string;
93
+ //# sourceMappingURL=session-log-sweep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-log-sweep.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-log-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAQH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,eAAO,MAAM,6BAA6B,QAAsB,CAAC;AAEjE,eAAO,MAAM,UAAU,QAAsB,CAAC;AAE9C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGpF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGtF;AAED,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAyBtE;AAED,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,sBAAsB,CA2DtF;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUjD"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Issue #1238: retention for PTY session logs.
3
+ *
4
+ * `PtySession` already bounds a *single* session's log (50 MB, one `.1`
5
+ * rotation — see `../../terminal/pty-session.ts`), but nothing has ever
6
+ * deleted the log of a session that ended. Every terminal Tower has ever
7
+ * opened leaves a file behind forever: an audited machine reached 29,728 files
8
+ * / 19 GB in `~/.agent-farm/logs`, 97% of it untouched for over a month.
9
+ *
10
+ * This module adds the missing cross-file policy: a log is deleted once its
11
+ * last write is older than the retention window. mtime is the retention key
12
+ * because the last byte written to a session log is, in effect, the moment
13
+ * that session stopped producing output — the "session ended" signal, without
14
+ * needing to join against DB session state (which is itself pruned, so dead
15
+ * sessions leave no row to join against).
16
+ *
17
+ * Live sessions are excluded by id regardless of mtime. A session log's fd is
18
+ * held open for the session's whole life, so unlinking it under a live writer
19
+ * would silently redirect every subsequent write into an unlinked inode —
20
+ * losing the log of the one session someone might actually want to read. A
21
+ * 30-day-idle live session is vanishingly unlikely, but the guard is cheap.
22
+ *
23
+ * Wired in `./tower-server.ts` on two triggers (Tower startup, and a daily
24
+ * in-process timer), mirroring the husk sweep in `./shellper-husk-sweep.ts`.
25
+ */
26
+ import fs from 'node:fs';
27
+ import path from 'node:path';
28
+ /** `<session-uuid>.log` and its single rotation `<session-uuid>.log.1`. */
29
+ const SESSION_LOG_RE = /^(.+)\.log(\.1)?$/;
30
+ export const DEFAULT_LOG_RETENTION_DAYS = 30;
31
+ export const DEFAULT_LOG_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000;
32
+ export const MS_PER_DAY = 24 * 60 * 60 * 1000;
33
+ /**
34
+ * Retention window in days (env `AGENT_FARM_LOG_RETENTION_DAYS`, default 30).
35
+ * `0` disables the sweep entirely — an explicit "keep everything" opt-out for
36
+ * anyone who treats these logs as an audit trail.
37
+ *
38
+ * NaN-checked rather than `parsed || default` because `0` is a meaningful
39
+ * override and is falsy in JS (same reasoning as `resolveHuskGraceMs`).
40
+ */
41
+ export function resolveLogRetentionDays(env = process.env) {
42
+ const parsed = parseInt(env.AGENT_FARM_LOG_RETENTION_DAYS ?? '', 10);
43
+ return Math.max(Number.isNaN(parsed) ? DEFAULT_LOG_RETENTION_DAYS : parsed, 0);
44
+ }
45
+ /**
46
+ * Sweep cadence (env `AGENT_FARM_LOG_SWEEP_INTERVAL_MS`, default 24h). Floored
47
+ * at 1s so a bad value can't spin the event loop; tests shorten it deliberately.
48
+ */
49
+ export function resolveLogSweepIntervalMs(env = process.env) {
50
+ const parsed = parseInt(env.AGENT_FARM_LOG_SWEEP_INTERVAL_MS ?? '', 10);
51
+ return Math.max(Number.isNaN(parsed) ? DEFAULT_LOG_SWEEP_INTERVAL_MS : parsed, 1000);
52
+ }
53
+ /**
54
+ * Total footprint of the session log directory. Read-only — used by
55
+ * `codev doctor` to surface the number the issue was filed about, and by the
56
+ * sweep's own logging. A missing directory is a healthy zero, not an error.
57
+ */
58
+ export function measureSessionLogs(logDir) {
59
+ const footprint = { files: 0, bytes: 0, oldestMtimeMs: null };
60
+ let entries;
61
+ try {
62
+ entries = fs.readdirSync(logDir);
63
+ }
64
+ catch {
65
+ return footprint;
66
+ }
67
+ for (const name of entries) {
68
+ if (!SESSION_LOG_RE.test(name))
69
+ continue;
70
+ let stat;
71
+ try {
72
+ stat = fs.statSync(path.join(logDir, name));
73
+ }
74
+ catch {
75
+ continue; // vanished mid-scan — nothing to account for
76
+ }
77
+ if (!stat.isFile())
78
+ continue;
79
+ footprint.files += 1;
80
+ footprint.bytes += stat.size;
81
+ if (footprint.oldestMtimeMs === null || stat.mtimeMs < footprint.oldestMtimeMs) {
82
+ footprint.oldestMtimeMs = stat.mtimeMs;
83
+ }
84
+ }
85
+ return footprint;
86
+ }
87
+ /**
88
+ * Delete session logs whose last write is older than `retentionMs`.
89
+ *
90
+ * Never throws: a log directory that can't be read, or an individual file that
91
+ * can't be stat'd or unlinked, is counted and skipped. Log retention is
92
+ * janitorial — it must not be able to take Tower's startup path down with it.
93
+ */
94
+ export function sweepSessionLogs(opts) {
95
+ const result = {
96
+ scanned: 0,
97
+ deleted: 0,
98
+ bytesFreed: 0,
99
+ skippedActive: 0,
100
+ failed: 0,
101
+ };
102
+ if (opts.retentionMs <= 0)
103
+ return result;
104
+ const now = opts.now ?? Date.now();
105
+ const active = new Set(opts.activeSessionIds ?? []);
106
+ let entries;
107
+ try {
108
+ entries = fs.readdirSync(opts.logDir);
109
+ }
110
+ catch {
111
+ return result; // no log dir yet (fresh install) — nothing to sweep
112
+ }
113
+ for (const name of entries) {
114
+ const match = SESSION_LOG_RE.exec(name);
115
+ if (!match)
116
+ continue;
117
+ const fullPath = path.join(opts.logDir, name);
118
+ let stat;
119
+ try {
120
+ stat = fs.statSync(fullPath);
121
+ }
122
+ catch {
123
+ continue;
124
+ }
125
+ if (!stat.isFile())
126
+ continue;
127
+ result.scanned += 1;
128
+ if (now - stat.mtimeMs < opts.retentionMs)
129
+ continue;
130
+ if (active.has(match[1])) {
131
+ result.skippedActive += 1;
132
+ continue;
133
+ }
134
+ try {
135
+ fs.unlinkSync(fullPath);
136
+ result.deleted += 1;
137
+ result.bytesFreed += stat.size;
138
+ }
139
+ catch {
140
+ result.failed += 1;
141
+ }
142
+ }
143
+ if (result.deleted > 0 || result.failed > 0) {
144
+ opts.log?.(`Session log sweep: deleted ${result.deleted} log(s), freed ${formatBytes(result.bytesFreed)}` +
145
+ ` (${result.scanned} scanned` +
146
+ (result.skippedActive > 0 ? `, ${result.skippedActive} kept as live` : '') +
147
+ (result.failed > 0 ? `, ${result.failed} failed` : '') +
148
+ ')');
149
+ }
150
+ return result;
151
+ }
152
+ /** Human-readable byte count for log lines and `codev doctor` output. */
153
+ export function formatBytes(bytes) {
154
+ if (bytes < 1024)
155
+ return `${bytes} B`;
156
+ const units = ['KB', 'MB', 'GB', 'TB'];
157
+ let value = bytes / 1024;
158
+ let unit = 0;
159
+ while (value >= 1024 && unit < units.length - 1) {
160
+ value /= 1024;
161
+ unit += 1;
162
+ }
163
+ return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
164
+ }
165
+ //# sourceMappingURL=session-log-sweep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-log-sweep.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-log-sweep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Per-session submission lock (Spec 1273, verify phase).
3
+ *
4
+ * ## The bug this exists to make impossible
5
+ *
6
+ * `writeMessageToSession` writes text to the PTY and schedules the Enter that
7
+ * submits it **50–80ms later** (`message-write.ts`). The write functions return
8
+ * that completion offset but do not wait for it, and `/api/send` responded as
9
+ * soon as the write was *scheduled*. So an awaited send resolved before its own
10
+ * message had been submitted.
11
+ *
12
+ * In production (`afx reset`, 2026-07-31) that gap swallowed a `/clear`
13
+ * entirely. Reset awaited the raw write of `/clear`, then wrote the
14
+ * re-orientation — landing inside the 50ms window, into the same composer,
15
+ * ahead of the Enter. One Enter then submitted both as a single message
16
+ * beginning `/clear### [ARCHITECT INSTRUCTION...`. The slash command was never
17
+ * executed, the builder's context was never cleared, and every layer reported
18
+ * success.
19
+ *
20
+ * ## Ordering is not atomicity
21
+ *
22
+ * The paced writer already threads a delay offset between consecutive writes, and the
23
+ * mailbox delivery path serialises messages to one agent (`deliverAgentMailSerialized`
24
+ * chains each delivery on the prior one's paced completion). Both fix the *order* in
25
+ * which queued messages reach a session. Neither would have prevented this: the two
26
+ * writes were correctly ordered and still coalesced, because being second is not the
27
+ * same as being separate. What was missing is a guarantee that a submission completes —
28
+ * Enter included — before the next write to that session begins.
29
+ *
30
+ * ## What this provides
31
+ *
32
+ * A promise chain per session key. Each submission waits for the previous one to
33
+ * finish (including its Enter), so concurrent `/api/send` requests to the same
34
+ * terminal cannot interleave in the composer. `await submitToSession(...)` means
35
+ * *submitted*, not *scheduled* — which is what every caller already assumed it
36
+ * meant.
37
+ *
38
+ * Deliberately keyed by session id rather than holding a session object: Tower
39
+ * re-fetches sessions by id, and a lock that outlived its session would pin a
40
+ * dead reference.
41
+ *
42
+ * ## Exactly what it covers — this is NOT blanket per-session atomicity
43
+ *
44
+ * A lock only serialises writers that take it. Currently that is the `escape`
45
+ * and `interrupt` paths of `/api/send`. Every other PTY writer still writes
46
+ * directly, and it is worth being precise about why:
47
+ *
48
+ * - The mailbox delivery path (`deliverAgentMailSerialized`, Spec 1313) — every
49
+ * normal `/api/send` AND every cron notification (Phase 6 rerouted cron here; the
50
+ * old blind `writeMessageToSession` is gone). NOT covered by this lock, and does not
51
+ * need it: it runs its OWN per-agent write serializer that completion-chains each
52
+ * delivery on the prior one's paced write (text + Enter), so two mailbox deliveries
53
+ * to one agent cannot interleave. That is a disjoint lock from this per-session one,
54
+ * so a mailbox delivery is not serialised against a concurrent `escape`/`interrupt`
55
+ * here — but a normal mailbox delivery only ever writes onto a render-gate-verified
56
+ * empty prompt, and `interrupt` is the explicit gate-bypassing human action (see
57
+ * tower-routes.ts), so that residual cross-path race is an accepted, documented
58
+ * boundary, not a regression this lock must close.
59
+ * - `POST /api/terminals/:id/write` — NOT covered. It is a raw passthrough
60
+ * with no Enter semantics of its own.
61
+ * - `tower-websocket.ts` keystrokes and the shellper frame relay — DELIBERATELY
62
+ * not covered. That is a human typing into their own terminal; serialising
63
+ * it behind an agent's message would make the UI feel stuck, and the human
64
+ * is the composer's owner.
65
+ *
66
+ * So the guarantee is: **two lock-taking `/api/send` submissions (escape/interrupt)
67
+ * to one session cannot interleave**, which is the failure that reached production.
68
+ * Anything stronger requires the remaining writers to take the lock too.
69
+ */
70
+ /** Injectable for tests; real timers otherwise. */
71
+ export interface SubmitClock {
72
+ sleep(ms: number): Promise<void>;
73
+ }
74
+ /**
75
+ * Run a write against a session so that it completes before any other
76
+ * submission to the same session begins.
77
+ *
78
+ * @param sessionId terminal/session id — the lock's granularity
79
+ * @param write performs the write; returns ms from now until the final
80
+ * keystroke (the Enter) has been written. This is exactly what
81
+ * `writeMessageToSession` / `writeEscapeToSession` already
82
+ * return, so callers pass them through unchanged.
83
+ * @returns resolves once the submission is complete
84
+ */
85
+ export declare function submitToSession(sessionId: string, write: () => number, clock?: SubmitClock): Promise<void>;
86
+ /** Number of sessions with a submission in flight. Test/observability only. */
87
+ export declare function pendingSubmissionSessions(): number;
88
+ /** Drop all chains. Test-only; a live Tower should let them drain. */
89
+ export declare function resetSubmissionChains(): void;
90
+ //# sourceMappingURL=session-submit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-submit.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-submit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAUH,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,MAAM,EACnB,KAAK,GAAE,WAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,+EAA+E;AAC/E,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}