elanous 0.2.18 → 0.2.19

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 (404) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
  16. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
  28. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
  110. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  111. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
  112. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  113. package/apps/pwa/out/approvals/index.html +1 -1
  114. package/apps/pwa/out/approvals/index.txt +23 -23
  115. package/apps/pwa/out/autopilot/index.html +1 -1
  116. package/apps/pwa/out/autopilot/index.txt +23 -23
  117. package/apps/pwa/out/board/index.html +1 -1
  118. package/apps/pwa/out/board/index.txt +23 -23
  119. package/apps/pwa/out/botlab/index.html +1 -1
  120. package/apps/pwa/out/botlab/index.txt +22 -22
  121. package/apps/pwa/out/bots/index.html +1 -1
  122. package/apps/pwa/out/bots/index.txt +22 -22
  123. package/apps/pwa/out/ceo/index.html +1 -1
  124. package/apps/pwa/out/ceo/index.txt +23 -23
  125. package/apps/pwa/out/chat/index.html +1 -1
  126. package/apps/pwa/out/chat/index.txt +22 -22
  127. package/apps/pwa/out/consult/index.html +1 -1
  128. package/apps/pwa/out/consult/index.txt +23 -23
  129. package/apps/pwa/out/control/index.html +1 -1
  130. package/apps/pwa/out/control/index.txt +23 -23
  131. package/apps/pwa/out/decisions/index.html +1 -1
  132. package/apps/pwa/out/decisions/index.txt +22 -22
  133. package/apps/pwa/out/design-check/index.html +1 -1
  134. package/apps/pwa/out/design-check/index.txt +23 -23
  135. package/apps/pwa/out/editor/index.html +1 -1
  136. package/apps/pwa/out/editor/index.txt +25 -24
  137. package/apps/pwa/out/exec/index.html +1 -1
  138. package/apps/pwa/out/exec/index.txt +23 -23
  139. package/apps/pwa/out/field/index.html +1 -1
  140. package/apps/pwa/out/field/index.txt +23 -23
  141. package/apps/pwa/out/healthz +1 -1
  142. package/apps/pwa/out/index.html +1 -1
  143. package/apps/pwa/out/index.txt +22 -22
  144. package/apps/pwa/out/inside/index.html +1 -1
  145. package/apps/pwa/out/inside/index.txt +24 -23
  146. package/apps/pwa/out/intake/index.html +1 -1
  147. package/apps/pwa/out/intake/index.txt +23 -23
  148. package/apps/pwa/out/live/index.html +1 -1
  149. package/apps/pwa/out/live/index.txt +23 -23
  150. package/apps/pwa/out/loops/index.html +1 -1
  151. package/apps/pwa/out/loops/index.txt +28 -27
  152. package/apps/pwa/out/market/index.html +1 -1
  153. package/apps/pwa/out/market/index.txt +22 -22
  154. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  155. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  156. package/apps/pwa/out/morning/index.html +1 -1
  157. package/apps/pwa/out/morning/index.txt +23 -23
  158. package/apps/pwa/out/observatory/index.html +1 -1
  159. package/apps/pwa/out/observatory/index.txt +23 -23
  160. package/apps/pwa/out/ops/checklist/index.html +1 -1
  161. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  162. package/apps/pwa/out/ops/release/index.html +1 -1
  163. package/apps/pwa/out/ops/release/index.txt +22 -22
  164. package/apps/pwa/out/ops/seats/index.html +1 -1
  165. package/apps/pwa/out/ops/seats/index.txt +22 -22
  166. package/apps/pwa/out/outputs/index.html +1 -1
  167. package/apps/pwa/out/outputs/index.txt +22 -22
  168. package/apps/pwa/out/reflection/index.html +1 -1
  169. package/apps/pwa/out/reflection/index.txt +23 -23
  170. package/apps/pwa/out/scheduler/index.html +1 -1
  171. package/apps/pwa/out/scheduler/index.txt +23 -23
  172. package/apps/pwa/out/sessions/index.html +1 -1
  173. package/apps/pwa/out/sessions/index.txt +22 -22
  174. package/apps/pwa/out/settings/devices/index.html +1 -1
  175. package/apps/pwa/out/settings/devices/index.txt +23 -23
  176. package/apps/pwa/out/settings/index.html +1 -1
  177. package/apps/pwa/out/settings/index.txt +23 -23
  178. package/apps/pwa/out/setup/done/index.html +1 -1
  179. package/apps/pwa/out/setup/done/index.txt +23 -23
  180. package/apps/pwa/out/setup/index.html +1 -1
  181. package/apps/pwa/out/setup/index.txt +23 -23
  182. package/apps/pwa/out/share/index.html +1 -1
  183. package/apps/pwa/out/share/index.txt +23 -23
  184. package/apps/pwa/out/showroom/index.html +1 -1
  185. package/apps/pwa/out/showroom/index.txt +22 -22
  186. package/apps/pwa/out/tasks/index.html +1 -1
  187. package/apps/pwa/out/tasks/index.txt +23 -23
  188. package/apps/pwa/out/term/index.html +1 -1
  189. package/apps/pwa/out/term/index.txt +23 -23
  190. package/apps/pwa/out/today/index.html +1 -1
  191. package/apps/pwa/out/today/index.txt +23 -23
  192. package/apps/pwa/out/trace/index.html +1 -1
  193. package/apps/pwa/out/trace/index.txt +23 -23
  194. package/apps/pwa/out/vault/index.html +1 -1
  195. package/apps/pwa/out/vault/index.txt +23 -23
  196. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  197. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  198. package/apps/pwa/out/workflows/index.html +1 -1
  199. package/apps/pwa/out/workflows/index.txt +23 -23
  200. package/apps/pwa/out/workspace/index.html +1 -1
  201. package/apps/pwa/out/workspace/index.txt +23 -23
  202. package/apps/pwa/out/worktrees/index.html +1 -1
  203. package/apps/pwa/out/worktrees/index.txt +23 -23
  204. package/graphs/ops/guardian.yaml +26 -0
  205. package/graphs/ops/recipes.yaml +3 -0
  206. package/package.json +1 -1
  207. package/scripts/coord-channel-watch.sh +2 -2
  208. package/scripts/discord-seat-probe.ts +3 -1
  209. package/scripts/lib/pch-regress-map.ts +23 -0
  210. package/scripts/morning-report.ts +18 -4
  211. package/scripts/pch-regress.ts +88 -0
  212. package/scripts/release-loop/gate-diff.ts +18 -0
  213. package/scripts/release-loop/gate-node.ts +338 -46
  214. package/scripts/release-loop/gate-partial.ts +106 -0
  215. package/scripts/release-loop/gate-timing.ts +120 -0
  216. package/scripts/release-loop/resume-release.ts +64 -5
  217. package/scripts/release-loop/shard-plan.ts +65 -0
  218. package/scripts/release-loop/unattended-release.ts +2 -0
  219. package/scripts/video/scene-table.ts +159 -0
  220. package/src/acp/core-turn-bridge.ts +39 -2
  221. package/src/away/presence.ts +40 -0
  222. package/src/away/release-watch.ts +255 -0
  223. package/src/chat/index.ts +2 -1
  224. package/src/chat/short-question.ts +3 -40
  225. package/src/cli/away-cli.ts +68 -0
  226. package/src/cli/guardian-cli.ts +49 -0
  227. package/src/cli/intake-cli.ts +13 -7
  228. package/src/cli/log-event-names.ts +1 -0
  229. package/src/cli/pod-cli.ts +5 -1
  230. package/src/cli/pty-drive-cli.ts +14 -3
  231. package/src/cli/release-cli.ts +32 -7
  232. package/src/cli/setup-cli.ts +31 -1
  233. package/src/cli/tasks-cli.ts +104 -11
  234. package/src/context-bus/context-now-public.ts +1 -1
  235. package/src/context-bus/context-now-surfaces.ts +16 -6
  236. package/src/context-bus/context-now.ts +97 -3
  237. package/src/core-turn/short-question.ts +100 -0
  238. package/src/dashboard/dashboard-status-lines.ts +24 -4
  239. package/src/dashboard/index.ts +50 -5
  240. package/src/dashboard/input/slash-executor.ts +6 -2
  241. package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
  242. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  243. package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
  244. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  245. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  246. package/src/domains/guardian-tick.ts +138 -0
  247. package/src/domains/outbound-alert.ts +44 -13
  248. package/src/domains/schedule-registry.ts +23 -0
  249. package/src/domains/telegram-kind-route.ts +39 -0
  250. package/src/domains/watch-default-scheduler.ts +95 -0
  251. package/src/esc-abort-gate.ts +44 -0
  252. package/src/graph-runner/runner.ts +25 -5
  253. package/src/harness/harness-cli-command.ts +2 -0
  254. package/src/harness/harness-salvage-cli.ts +78 -0
  255. package/src/harness/harness-worktree-add.ts +68 -0
  256. package/src/harness/ledger-write-guard.ts +6 -16
  257. package/src/hq/hq.ts +6 -1
  258. package/src/index.ts +52 -3
  259. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  260. package/src/instance/test-write-guard.ts +200 -0
  261. package/src/intake-plane/digest.ts +39 -15
  262. package/src/intake-plane/harness-target.ts +4 -3
  263. package/src/intake-plane/intake-sources.ts +1 -0
  264. package/src/intake-plane/items.ts +1 -0
  265. package/src/loops/orchestrator/degradation.ts +57 -0
  266. package/src/loops/orchestrator/finish-rate.ts +33 -3
  267. package/src/loops/orchestrator/tick.ts +166 -45
  268. package/src/maturity/feature-maturity.ts +6 -4
  269. package/src/maturity/tui-slash-maturity.ts +1 -0
  270. package/src/mss/logging/log-store.ts +3 -0
  271. package/src/nexus/api/http-server.ts +16 -5
  272. package/src/nexus/api/loop-edges.ts +290 -17
  273. package/src/nexus/api/ops-api.ts +96 -28
  274. package/src/nexus/index.ts +7 -0
  275. package/src/release-loop/checklist.ts +2 -0
  276. package/src/release-loop/feature-store.ts +54 -7
  277. package/src/release-loop/light-rc.ts +173 -0
  278. package/src/release-loop/placement.ts +51 -17
  279. package/src/release-loop/rubric.ts +57 -1
  280. package/src/self-dev/dev-cli.ts +36 -0
  281. package/src/self-dev/orchestrate.ts +1 -0
  282. package/src/self-dev/pr-preview.ts +179 -0
  283. package/src/self-dev/shard-run.ts +23 -7
  284. package/src/self-implement/gate-cli.ts +29 -8
  285. package/src/self-implement/goal-author-clarification.ts +88 -13
  286. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  287. package/src/self-implement/orchestrator.ts +2 -1
  288. package/src/self-implement/salvage-branches.ts +254 -0
  289. package/src/task-agent/actions.ts +63 -2
  290. package/src/task-agent/board.ts +132 -0
  291. package/src/task-agent/card-evidence.ts +203 -0
  292. package/src/task-agent/completion-evidence.ts +126 -0
  293. package/src/task-agent/judge.ts +18 -0
  294. package/src/task-agent/live-moves.ts +176 -0
  295. package/src/task-agent/mission.ts +32 -6
  296. package/src/task-agent/shadow.ts +183 -15
  297. package/src/task-agent/task-hand.ts +117 -15
  298. package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
  299. package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
  300. package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
  301. package/src/task-orchestrator/surfaces/self-implement-pod.ts +2 -2
  302. package/src/telegram-away-command.ts +38 -0
  303. package/src/telegram-commands.ts +11 -0
  304. package/src/telegram-report-log-probe.ts +21 -0
  305. package/src/telegram-report.ts +27 -3
  306. package/src/tui-client/dashboard-session.ts +1 -0
  307. package/src/user-config.ts +134 -2
  308. package/src/version/packed-revision.json +1 -1
  309. package/apps/pwa/out/_next/static/chunks/1307-30dff22c9e1f3073.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  311. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  314. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +0 -2
  335. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  344. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-4733c967b56a4277.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/bots/page-02b073a2ac793b83.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/design-check/page-5d97b2901e0d1ea6.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/exec/page-8c3d8e63ae2e1c98.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/field/page-9b72a07d424701c6.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/intake/page-a572f58cd3ca5864.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/morning/page-83a700d4c754d802.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/not-found-7b3170f374f77b9d.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1467047104141297.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/share/page-3cc47dca0922ab9a.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/tasks/page-b17cd46cf0e9b8cf.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/workspace/page-134da468538baf48.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/webpack-c2782b330447ffb6.js +0 -1
  403. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  404. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
@@ -34,6 +34,9 @@ import type { KeyEvent } from './display/types.js';
34
34
  import type { ThemeTokens } from './theme/tokens.js';
35
35
  import { debug } from './debug/log.js';
36
36
  import { abortTurnOnly } from './turn-abort-scope.js';
37
+ import { SELF_IMPLEMENT_TOOL_NAMES } from './boot/daemon-tools/self-implement-names.js';
38
+
39
+ export const DASHBOARD_CLARIFICATION_REQUEST = Symbol.for('elanous.dashboard.clarification-request');
37
40
 
38
41
  export interface EscAbortGateDeps {
39
42
  abortCtrl: AbortController;
@@ -87,8 +90,14 @@ export async function routeStreamingEscapeKey(
87
90
  gate: Pick<EscAbortGate, 'handleEscape' | 'handleKey' | 'isGateOpen'>,
88
91
  gateEvent: KeyEvent,
89
92
  consumeDragEscape: () => Promise<boolean>,
93
+ consumeTopModalEscape: () => boolean | 'passthrough' = () => false,
90
94
  ): Promise<boolean> {
91
95
  if (key.kind && key.kind !== 'press') return false;
96
+ if (key.name === 'escape') {
97
+ const modalResult = consumeTopModalEscape();
98
+ if (modalResult === true) return true;
99
+ if (modalResult === 'passthrough') return false;
100
+ }
92
101
  if (gate.isGateOpen()) {
93
102
  gate.handleKey(gateEvent);
94
103
  return true;
@@ -99,6 +108,41 @@ export async function routeStreamingEscapeKey(
99
108
  return true;
100
109
  }
101
110
 
111
+ export function consumeStreamingTopModalEscape(input: {
112
+ modal: Pick<ModalSurface, 'id' | 'onKey'> | null;
113
+ abortGateModalId: string | null;
114
+ event: KeyEvent;
115
+ question?: { id: string; dispose(cancelled: boolean): void; isClarification: boolean };
116
+ onClarificationClosed(): void;
117
+ topModalId(): string | null;
118
+ redraw(): void;
119
+ }): boolean | 'passthrough' {
120
+ const { modal, question } = input;
121
+ if (!modal || modal.id === input.abortGateModalId) return false;
122
+ if (modal.id === question?.id) {
123
+ question.dispose(true);
124
+ if (question.isClarification) input.onClarificationClosed();
125
+ input.redraw();
126
+ return true;
127
+ }
128
+ if (!modal.onKey) return 'passthrough';
129
+ modal.onKey(input.event);
130
+ input.redraw();
131
+ return input.topModalId() === modal.id ? 'passthrough' : true;
132
+ }
133
+
134
+ export function createHarnessEscHint(pushLine: (line: string) => void): (targets: readonly string[]) => boolean {
135
+ let shown = false;
136
+ return (targets) => {
137
+ if (!targets.some(name => (SELF_IMPLEMENT_TOOL_NAMES as readonly string[]).includes(name))) return false;
138
+ if (!shown) {
139
+ pushLine('하니스 런은 따로 계속 돈다 — 멈추려면 /harness stop <space-id> · 목록은 /harness runs');
140
+ shown = true;
141
+ }
142
+ return true;
143
+ };
144
+ }
145
+
102
146
  export function createEscAbortGate(deps: EscAbortGateDeps): EscAbortGate {
103
147
  let current: ApprovalModalHandle | null = null;
104
148
  let currentDispose: (() => void) | null = null;
@@ -35,7 +35,11 @@ export interface GraphRunState {
35
35
  stoppedAt?: string;
36
36
  status: 'running' | 'done' | 'failed' | 'budget-exceeded' | 'awaiting-approval';
37
37
  path: string[];
38
- nodes: Array<{ nodeId: string; ok: boolean; exit: number | null; executed: boolean; output?: unknown; error?: string; decidedBy?: string; decidedAt?: string }>;
38
+ /** startedAt/endedAt (ISO) and seconds are written when the node runs; ledgers before GRAPH-NODE-TIMES (0.2.19) have none. */
39
+ nodes: Array<{ nodeId: string; ok: boolean; exit: number | null; executed: boolean; output?: unknown; error?: string; decidedBy?: string; decidedAt?: string;
40
+ startedAt?: string; endedAt?: string; seconds?: number }>;
41
+ /** The node now running and when it started; cleared when its record is written. */
42
+ currentNode?: { nodeId: string; startedAt: string };
39
43
  input?: unknown;
40
44
  pending?: { nodeId: string; message: string; since: string; notifiedAt?: string; decision?: 'approved' | 'rejected'; decidedBy?: string; decidedAt?: string };
41
45
  approvalSourceHash?: string;
@@ -658,6 +662,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
658
662
  }
659
663
  state.status = 'running';
660
664
  const startedAt = performance.now();
665
+ const nodeStartedAt = new Date().toISOString();
666
+ if (!resumingCompleted) state.currentNode = { nodeId: current, startedAt: nodeStartedAt };
661
667
  console.error(`[graph] ${current} start (0.00s)`);
662
668
  debug.log('graph.run', 'node', { graphId, runId, nodeId: current, phase: 'start', dryRun: state.dryRun });
663
669
  publishInsideEvent({ kind: 'node', graphId, runId, nodeId: current, phase: 'start' });
@@ -723,6 +729,10 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
723
729
  error = result.error ? redactCredentials(result.error) : undefined;
724
730
  state.executed++;
725
731
  }
732
+ const seconds = Number(((performance.now() - startedAt) / 1000).toFixed(2));
733
+ // A wall clock stepped back mid-node must not record an end before the start.
734
+ const nodeEndedAt = new Date(Math.max(Date.now(), Date.parse(nodeStartedAt))).toISOString();
735
+ delete state.currentNode;
726
736
  if (resumingCompleted && completed) {
727
737
  ok = completed.ok;
728
738
  exit = completed.exit;
@@ -734,7 +744,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
734
744
  output = JSON.stringify({ outcome: ok ? 'approved' : 'rejected', ...(state.pending?.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending?.decidedAt ?? null });
735
745
  }
736
746
  state.nodes.push({ nodeId: current, ok, exit, executed: !!command && (!state.dryRun || !!command.dry_run_command), ...((command || approval) && (!state.dryRun || !!command?.dry_run_command) && output !== undefined ? { output } : {}), ...(error ? { error } : {}),
737
- ...(approval && state.pending ? { ...(state.pending.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending.decidedAt } : {}) });
747
+ ...(approval && state.pending ? { ...(state.pending.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending.decidedAt } : {}),
748
+ startedAt: nodeStartedAt, endedAt: nodeEndedAt, seconds });
738
749
  }
739
750
  if (approval) {
740
751
  delete state.pending;
@@ -742,7 +753,6 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
742
753
  }
743
754
  const reported = command && (!state.dryRun || command.dry_run_command) ? lastJsonObject(resumingCompleted ? completed?.output : output)?.outcome : undefined;
744
755
  const namedOutcome = typeof reported === 'string' ? reported : undefined;
745
- const seconds = Number(((performance.now() - startedAt) / 1000).toFixed(2));
746
756
  console.error(`[graph] ${current} ${ok ? 'ok' : 'fail'} (${seconds.toFixed(2)}s)`);
747
757
  debug.log('graph.run', 'node', { graphId, runId, nodeId: current, phase: ok ? 'ok' : 'fail', seconds, exit });
748
758
  publishInsideEvent({ kind: 'node', graphId, runId, nodeId: current, phase: ok ? 'ok' : 'fail', seconds });
@@ -892,11 +902,21 @@ export function manageGraphRun(graphId: string, runId: string, action: 'stop' |
892
902
  catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ESRCH') throw error; }
893
903
  }
894
904
  }
905
+ const current = state.currentNode;
906
+ const running = current && state.path.length === state.nodes.length + 1 && current.nodeId === state.path.at(-1) &&
907
+ Number.isFinite(Date.parse(current.startedAt)) ? current.startedAt : undefined;
908
+ // One stop instant for the node end, finishedAt and stoppedAt — never before the running node's start.
909
+ const stopMs = running === undefined ? Date.now() : Math.max(Date.now(), Date.parse(running));
895
910
  if (state.path.length === state.nodes.length + 1) {
896
- state.nodes.push({ nodeId: state.path.at(-1)!, ok: false, exit: null, executed: false, error: 'stopped before node completed' });
911
+ state.nodes.push({ nodeId: state.path.at(-1)!, ok: false, exit: null, executed: false, error: 'stopped before node completed',
912
+ // The stop instant is always the node end; a start (and so seconds) exists only when the ledger named the running
913
+ // node — a run begun by a runner before GRAPH-NODE-TIMES did not, and a guessed start would be a false time.
914
+ endedAt: new Date(stopMs).toISOString(),
915
+ ...(running === undefined ? {} : { startedAt: running, seconds: Number(((stopMs - Date.parse(running)) / 1000).toFixed(2)) }) });
897
916
  }
917
+ delete state.currentNode;
898
918
  state.status = 'failed';
899
- state.finishedAt = new Date().toISOString();
919
+ state.finishedAt = new Date(stopMs).toISOString();
900
920
  state.stoppedAt = state.finishedAt;
901
921
  delete state.activeNode;
902
922
  persistGraphRun(state);
@@ -23,6 +23,7 @@ import { queryRunningRuns } from '../self-implement/running-runs.js';
23
23
  import { DevPipelineError } from '../self-dev/dev-pipeline.js';
24
24
  import { runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
25
25
  import { debug } from '../debug/log.js';
26
+ import { installHarnessSalvageCommand } from './harness-salvage-cli.js';
26
27
  import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
27
28
  import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
28
29
  import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
@@ -2511,6 +2512,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2511
2512
  installDeliverableVerifyCliCommand(harnessCmd, deps.deliverableVerify);
2512
2513
  installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
2513
2514
  installHarnessBudgetCommand(harnessCmd);
2515
+ installHarnessSalvageCommand(harnessCmd);
2514
2516
  installHarnessGoalCommand(harnessCmd, deps.goalLookup, deps.goalArchive);
2515
2517
  installHarnessDraftSweepCommand(harnessCmd, deps.draftSweep);
2516
2518
  const queue = harnessCmd.command('queue').description('자리별 영속 발사 대기열');
@@ -0,0 +1,78 @@
1
+ /**
2
+ * `elanous harness salvage <runId...> [--json]` — 런마다 «PR #n | 수확 가지 <branch> (+a/-b · N files) | 없음 | 모름(<이유>)».
3
+ * SALVAGE-VISIBLE(0.2.19) — 판정은 `src/self-implement/salvage-branches.ts`, 여기는 배선·출력·관측만.
4
+ */
5
+ import type { Command } from 'commander';
6
+ import { debug } from '../debug/log.js';
7
+ import { normalizeRunId } from './harness-space.js';
8
+ import { loadFederatedRunLedger } from '../self-implement/run-ledger.js';
9
+ import {
10
+ decideRunSalvage,
11
+ findRunSalvageBranches,
12
+ gitSalvageRunner,
13
+ listRemoteSalvageRefs,
14
+ prStateFromLedger,
15
+ type RunPrState,
16
+ type RunSalvageVerdict,
17
+ type SalvageGitRunner,
18
+ } from '../self-implement/salvage-branches.js';
19
+
20
+ export interface HarnessSalvageDeps {
21
+ git?: SalvageGitRunner;
22
+ readPr?: (runId: string) => RunPrState;
23
+ print?: (line: string) => void;
24
+ }
25
+
26
+ function defaultReadPr(runId: string): RunPrState {
27
+ try {
28
+ return prStateFromLedger(loadFederatedRunLedger(runId, { includeTest: true }));
29
+ } catch (error) {
30
+ return { status: 'unknown', reason: `원장 읽기 실패 · ${error instanceof Error ? error.message : String(error)}` };
31
+ }
32
+ }
33
+
34
+ /** 여러 런을 ls-remote «한 번»으로 판정한다. PR 이 있는 런은 git 을 더 부르지 않는다. */
35
+ export function runHarnessSalvage(runIds: readonly string[], deps: HarnessSalvageDeps = {}): RunSalvageVerdict[] {
36
+ const git = deps.git ?? gitSalvageRunner(process.cwd());
37
+ const readPr = deps.readPr ?? defaultReadPr;
38
+ let listing: ReturnType<typeof listRemoteSalvageRefs> | undefined;
39
+ const verdicts: RunSalvageVerdict[] = [];
40
+ for (const raw of runIds) {
41
+ const runId = normalizeRunId(raw);
42
+ const pr = readPr(runId);
43
+ let verdict: RunSalvageVerdict;
44
+ if (pr.status === 'pr') {
45
+ verdict = decideRunSalvage(runId, pr, { status: 'not-checked' });
46
+ } else {
47
+ listing ??= listRemoteSalvageRefs(git);
48
+ verdict = decideRunSalvage(runId, pr, findRunSalvageBranches(runId, git, { listing }));
49
+ }
50
+ const top = verdict.salvage.status === 'found' ? verdict.salvage.branches[0] : undefined;
51
+ debug.log('harness.salvage', 'visible', {
52
+ runId,
53
+ outcome: verdict.outcome,
54
+ branch: top?.branch ?? null,
55
+ files: top?.diffstat.files ?? null,
56
+ ...(verdict.pr.status === 'pr' ? { prNumber: verdict.pr.number } : {}),
57
+ ...(verdict.salvage.status === 'unknown' ? { reason: verdict.salvage.reason } : {}),
58
+ ...(verdict.pr.status === 'unknown' ? { prReason: verdict.pr.reason } : {}),
59
+ });
60
+ verdicts.push(verdict);
61
+ }
62
+ return verdicts;
63
+ }
64
+
65
+ export function installHarnessSalvageCommand(harnessCmd: Command, deps: HarnessSalvageDeps = {}): Command {
66
+ return harnessCmd
67
+ .command('salvage')
68
+ .description('런 → PR | 수확 가지(salvage/*) | 없음 | 모름 — Pod 런이 멈추며 남긴 가지를 보인다')
69
+ .argument('<runIds...>', '런 id (run-xxxxxxxx-…)')
70
+ .option('--json', '런마다 { runId, outcome, pr, salvage } 를 JSON 배열로')
71
+ .action((runIds: string[], opts: { json?: boolean }) => {
72
+ const print = deps.print ?? ((line: string) => console.log(line));
73
+ const verdicts = runHarnessSalvage(runIds, deps);
74
+ if (opts.json) print(JSON.stringify(verdicts.map(({ line: _line, ...rest }) => rest)));
75
+ else for (const verdict of verdicts) print(verdict.line);
76
+ if (verdicts.some((verdict) => verdict.outcome === 'unknown')) process.exitCode = 1;
77
+ });
78
+ }
@@ -2,6 +2,7 @@ import { createWorktree, type CreateWorktreeResult } from '../git-fs/worktree.js
2
2
  import { runGitCommand } from '../git-fs/runner.js';
3
3
  import { configuredWorktreeRoot } from '../user-config.js';
4
4
  import { debug } from '../debug/log.js';
5
+ import { join, resolve } from 'node:path';
5
6
 
6
7
  const OWNER_CONFIG_KEY = 'elanous.harness.owner';
7
8
  const COMMAND_CONFIG_KEY = 'elanous.harness.command';
@@ -87,8 +88,75 @@ function restoreWorktreeProvenance(worktreePath: string, snapshot: WorktreeConfi
87
88
  }
88
89
  }
89
90
 
91
+ /** Keys that must leave the shared config once `extensions.worktreeConfig` is on (`git help worktree` §CONFIGURATION FILE).
92
+ * `core.bare=false` is harmless when shared (linked trees default to non-bare), so only a true value moves. */
93
+ const MAIN_TREE_ONLY_CONFIG_KEYS = ['core.bare', 'core.worktree'] as const;
94
+
95
+ /** Moves `core.bare=true` / `core.worktree` from `$GIT_COMMON_DIR/config` to `$GIT_COMMON_DIR/config.worktree`.
96
+ * Runs every time (also when the extension is already on) and throws before the caller enables the extension.
97
+ * Incident 10-07 (HQ-WORKTREE-CONFIG): a shared `core.bare=true` made every linked worktree of a bare repo unusable. */
98
+ /** Values are moved byte-for-byte (a `core.worktree` path may carry edge whitespace), so read them NUL-terminated. */
99
+ function nulTerminatedValue(stdout: unknown, key: string): string {
100
+ const text = String(stdout ?? '');
101
+ if (!text.endsWith('\0')) throw new Error(`harness worktree ${key} read failed — git config emitted an unterminated value`);
102
+ return text.slice(0, -1);
103
+ }
104
+
105
+ function moveMainTreeOnlyConfig(worktreePath: string): void {
106
+ const common = gitAt(worktreePath, ['rev-parse', '--git-common-dir']);
107
+ const commonOut = String(common.stdout ?? '').trim();
108
+ if (common.status !== 0 || !commonOut) {
109
+ throw new Error(`harness worktree common git dir lookup failed — ${gitConfigFailure(common)}`);
110
+ }
111
+ const commonDir = resolve(worktreePath, commonOut);
112
+ const shared = join(commonDir, 'config');
113
+ const perTree = join(commonDir, 'config.worktree');
114
+ // `null` = absent. Typed reads judge meaning (`yes`/`TRUE` are true); raw reads carry the bytes that move.
115
+ const readValue = (file: string, key: string, typed: boolean): string | null => {
116
+ const read = gitAt(worktreePath, ['config', '--file', file, ...(typed ? ['--type=bool'] : []), '--null', '--get', key]);
117
+ if (read.status === 1) return null;
118
+ if (read.status !== 0) throw new Error(`harness worktree ${key} read failed (${file}) — ${gitConfigFailure(read)}`);
119
+ return nulTerminatedValue(read.stdout, key);
120
+ };
121
+ for (const key of MAIN_TREE_ONLY_CONFIG_KEYS) {
122
+ const typed = key === 'core.bare';
123
+ const meaning = readValue(shared, key, typed);
124
+ if (meaning === null) continue;
125
+ if (typed && meaning !== 'true') continue;
126
+ const raw = typed ? readValue(shared, key, false) : meaning;
127
+ if (raw === null) throw new Error(`harness worktree shared ${key} vanished during the move`);
128
+ // A valueless `bare` means true; writing its empty raw form would mean false.
129
+ const value = typed && raw === '' ? 'true' : raw;
130
+ // A conflicting per-tree value would flip the main tree once the extension is on — refuse instead of guessing.
131
+ // Every per-tree value must agree (not just the last one), so a mixed list also fails closed.
132
+ const existingRead = gitAt(worktreePath, ['config', '--file', perTree, ...(typed ? ['--type=bool'] : []), '--null', '--get-all', key]);
133
+ if (existingRead.status !== 0 && existingRead.status !== 1) {
134
+ throw new Error(`harness worktree ${key} read failed (${perTree}) — ${gitConfigFailure(existingRead)}`);
135
+ }
136
+ const existingValues = existingRead.status === 0 ? nulTerminatedValue(existingRead.stdout, key).split('\0') : [];
137
+ if (existingValues.some((existing) => existing !== meaning)) {
138
+ throw new Error(`harness worktree ${key} conflict — ${shared} has ${JSON.stringify(meaning)} but ${perTree} has ${JSON.stringify(existingValues)}; resolve by hand before enabling extensions.worktreeConfig`);
139
+ }
140
+ const existingMeaning = existingValues.length > 0 ? meaning : null;
141
+ if (existingMeaning === null) {
142
+ const write = gitAt(worktreePath, ['config', '--file', perTree, key, value]);
143
+ if (write.status !== 0) throw new Error(`harness worktree ${key} move failed — ${gitConfigFailure(write)}`);
144
+ }
145
+ const unset = gitAt(worktreePath, ['config', '--file', shared, '--unset-all', key]);
146
+ if (unset.status !== 0) throw new Error(`harness worktree shared ${key} unset failed — ${gitConfigFailure(unset)}`);
147
+ debug.log('harness.worktree', 'core-bare-moved', {
148
+ key,
149
+ value,
150
+ commonDir,
151
+ perTreeHadValue: existingMeaning !== null,
152
+ worktreePath,
153
+ }, { level: 'warn' });
154
+ }
155
+ }
156
+
90
157
  /** Records the shared worktree-scoped provenance used by both manual and self-implement creation paths. */
91
158
  export function recordHarnessWorktreeProvenance(worktreePath: string, provenance: HarnessWorktreeProvenance): void {
159
+ moveMainTreeOnlyConfig(worktreePath);
92
160
  const enable = gitAt(worktreePath, ['config', 'extensions.worktreeConfig', 'true']);
93
161
  if (enable.status !== 0) {
94
162
  throw new Error(`harness worktree owner declaration failed — ${gitConfigFailure(enable)}`);
@@ -1,19 +1,7 @@
1
- import { realpathSync } from 'node:fs';
2
- import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
1
+ import { isAbsolute, relative, sep } from 'node:path';
3
2
  import { debug } from '../debug/log.js';
4
3
  import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
5
-
6
- function physicalPath(path: string): string {
7
- const absolute = resolve(path);
8
- try {
9
- return realpathSync(absolute);
10
- } catch (error) {
11
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
12
- const parent = dirname(absolute);
13
- if (parent === absolute) throw error;
14
- return resolve(physicalPath(parent), relative(parent, absolute));
15
- }
16
- }
4
+ import { isInsideOpsRoot, physicalPath, testProcessSignal } from '../instance/test-write-guard.js';
17
5
 
18
6
  /** Refuse a test's write to the production instance, including an explicitly supplied ledger directory. */
19
7
  export function refuseProductionLedgerWriteInTest(
@@ -23,11 +11,13 @@ export function refuseProductionLedgerWriteInTest(
23
11
  productionRoot = prodInstanceRoot(),
24
12
  instanceRoot = effectiveInstanceRoot(),
25
13
  ): boolean {
26
- if (env.NODE_ENV !== 'test' && !env.ELANOUS_TEST_HOME) return false;
14
+ if (!testProcessSignal(env, env === process.env ? undefined : '')) return false;
27
15
  const target = physicalPath(root);
28
16
  const production = physicalPath(productionRoot);
29
17
  const withinProduction = relative(production, target);
30
- if (withinProduction === '..' || withinProduction.startsWith(`..${sep}`) || isAbsolute(withinProduction)) return false;
18
+ const outsideHomeRoot = withinProduction === '..' || withinProduction.startsWith(`..${sep}`) || isAbsolute(withinProduction);
19
+ // The account's real ~/.elanous is production too when a runner redirected HOME (TEST-PROD-LEAK).
20
+ if (outsideHomeRoot && !isInsideOpsRoot(root)) return false;
31
21
  try {
32
22
  debug.log('harness.incidents', 'write-refused', { store, root: target, effectiveInstanceRoot: instanceRoot });
33
23
  } catch { /* Observation cannot reopen a closed write boundary. */ }
package/src/hq/hq.ts CHANGED
@@ -108,7 +108,7 @@ function resolve(deps: HqDeps = {}): Resolved {
108
108
  ssh,
109
109
  probe: deps.probe ?? tailnetProbe(config),
110
110
  localPath: deps.localPath ?? join(getElanousConfigDir(), 'hq', 'local.json'),
111
- seenPath: deps.seenPath ?? config.seenGenerationFile ?? (deps.localPath ? `${deps.localPath}.seen-generation` : join(homedir(), '.elanous-hq', 'seen-generation')),
111
+ seenPath: deps.seenPath ?? (deps.localPath && !config.seenGenerationFile ? `${deps.localPath}.seen-generation` : hqSeenGenerationPath(config)),
112
112
  now: deps.now ?? (() => Math.floor(Date.now() / 1000)),
113
113
  log,
114
114
  };
@@ -161,6 +161,11 @@ function writeLocal(path: string, state: LocalHqState): void {
161
161
  }
162
162
 
163
163
  /** Highest lease generation this host ever observed (null = never). */
164
+ /** The seen-generation marker this host keeps (hq.seenGenerationFile · default ~/.elanous-hq/seen-generation). */
165
+ export function hqSeenGenerationPath(config: Pick<HqConfig, 'seenGenerationFile'>): string {
166
+ return config.seenGenerationFile ?? join(homedir(), '.elanous-hq', 'seen-generation');
167
+ }
168
+
164
169
  export function readSeenGeneration(path: string): number | null {
165
170
  try {
166
171
  if (!existsSync(path)) return null;
package/src/index.ts CHANGED
@@ -51,6 +51,7 @@ import { registerRoleCommands } from './cli/role-cli.js';
51
51
  import { registerMachineCommands } from './cli/machine-cli.js';
52
52
  import { registerAgentEnvCommands } from './cli/agent-env-cli.js';
53
53
  import { registerScheduleCommands } from './cli/schedule-cli.js';
54
+ import { registerGuardianCommands } from './cli/guardian-cli.js';
54
55
  export { runSchedule, scheduleCreatePlan } from './cli/schedule-cli.js';
55
56
  export type { ScheduleDispatch } from './cli/schedule-cli.js';
56
57
  import { registerReviewLoopOptions, buildReviewLoopOpts } from './agent-mission/review-loop-cli.js';
@@ -75,6 +76,7 @@ import { registerIntakeCommands } from './cli/intake-cli.js';
75
76
  import { registerTasksCommands } from './cli/tasks-cli.js';
76
77
  import { registerBriefCommands } from './cli/brief-cli.js';
77
78
  import { registerNotifyCommand } from './cli/notify-cli.js';
79
+ import { registerAwayCommand } from './cli/away-cli.js';
78
80
  import { registerLabelsCommands } from './cli/labels-cli.js';
79
81
  import { registerMsgCommands } from './cli/msg-cli.js';
80
82
  import { registerDoctorCommand } from './cli/doctor-cli.js';
@@ -215,6 +217,23 @@ export { cliVersion, setInstallMetadataRootForTesting };
215
217
  export const program = new Command();
216
218
  program.enablePositionalOptions();
217
219
 
220
+ program.command('preview <target>')
221
+ .description('Isolated PR/branch-head TUI + PWA preview; publish the TUI-COMFORT friction table to a PR')
222
+ .option('--scenario <id>', 'TUI-COMFORT scenario (default S2)', 'S2')
223
+ .option('--flag <path>', 'Enable an existing config feature flag in main-based preview (repeatable)', (value: string, all: string[]) => [...all, value], [] as string[])
224
+ .option('--comment-pr <number>', 'PR to receive a branch/main preview report', (value: string) => Number(value))
225
+ .action(async (target: string, opts: { scenario: string; flag: string[]; commentPr?: number }) => {
226
+ try {
227
+ const { runPrPreview } = await import('./self-dev/pr-preview.js');
228
+ const result = await runPrPreview({ target, repoRoot: process.cwd(), scenario: opts.scenario, flags: opts.flag, ...(opts.commentPr === undefined ? {} : { commentPr: opts.commentPr }) });
229
+ console.log(`preview ${result.status} · ${result.head} · PR #${result.prNumber} · comment posted`);
230
+ if (result.status !== 'passed') process.exitCode = 1;
231
+ } catch (error) {
232
+ console.error(error instanceof Error ? error.message : String(error));
233
+ process.exitCode = 1;
234
+ }
235
+ });
236
+
218
237
  export { SELF_SEND_RECENT_FRAME_WINDOW_MS, SELF_SEND_STALE_HEARTBEAT_MS, formatSelfSendCandidateDisplay } from './harness/self-send-decide.js';
219
238
  export type { SelfSendCandidate, SelfSendCandidateDisplay } from './harness/self-send-decide.js';
220
239
  import { decideSelfSend, type SelfSendCandidate } from './harness/self-send-decide.js';
@@ -891,6 +910,7 @@ registerResearchCommand(program);
891
910
  registerReleaseCommands(program);
892
911
  registerKnowCommand(program);
893
912
  registerNotifyCommand(program);
913
+ registerAwayCommand(program);
894
914
  registerFreezeCommands(program);
895
915
  registerFlowCommands(program);
896
916
  // HQ-HB · HQ-FENCE (10-04): one writer across mbp · node-b · cloud-vm — lease on the arbiter, 2-of-3 quorum, generation fencing.
@@ -5979,6 +5999,7 @@ program
5979
5999
  });
5980
6000
 
5981
6001
  registerScheduleCommands(program);
6002
+ registerGuardianCommands(program);
5982
6003
 
5983
6004
  /** `elanous decide*` 공통 — 보낼 곳과 자격을 정한다(설정 decide.endpoint > ELANOUS_JEV_ENDPOINT > Typesafe). 없으면 안내하고 rc 2. */
5984
6005
  async function loadJevAccessOrExit(): Promise<import('./decide/jev.js').JevAccess> {
@@ -7173,7 +7194,7 @@ const selfDevCmd = program
7173
7194
  const members = parsePodPool(devAskSubstrate.pool!);
7174
7195
  const host = podPoolHostLease(members);
7175
7196
  while (!authoringLease) {
7176
- const status = recommendConcurrency(measurePoolLease(members), { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
7197
+ const status = recommendConcurrency(measurePoolLease(members, { measureUsage: true }), { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
7177
7198
  if (status.recommended !== null && status.recommended > 0) {
7178
7199
  const { leaseHasPendingPod } = await import('./pod-lease/host-lease.js');
7179
7200
  const pendingJobs = status.pendingJobs ?? [];
@@ -7328,7 +7349,23 @@ const selfDevCmd = program
7328
7349
  if (hasExplicitReviewerContext && loaded.failed.length > 0) throw new DevPipelineError(contextStatus);
7329
7350
  if (loaded.items.length > 0) spec = { ...spec, reviewerContext: loaded.items };
7330
7351
  }
7331
- if (devOpts.worktree === true) {
7352
+ // ⭐ TUI-ONE-WORD-LAUNCH — 명시 격리 우주에서 `--cwd`·`--worktree` 둘 다 없으면 «자동» 갈래로
7353
+ // 같은 `prepareDevWorktree` 를 탄다(새 생성 경로 없음). 명시 `--cwd` 는 그대로 이긴다.
7354
+ const autoWorktreeDecision = await (async () => {
7355
+ if (!devOpts.elanous || devOpts.worktree === true || devOpts.cwd !== undefined) {
7356
+ return devCli.decideAutoWorktree({ explicitlyIsolated: false, ...devOpts });
7357
+ }
7358
+ const [{ resolveCurrentInstance }, { isExplicitlyIsolated }] = await Promise.all([
7359
+ import('./instance/current.js'),
7360
+ import('./cli/pty-drive-cli.js'),
7361
+ ]);
7362
+ return devCli.decideAutoWorktree({
7363
+ explicitlyIsolated: isExplicitlyIsolated(resolveCurrentInstance(), devOpts.isolatedRoot),
7364
+ ...devOpts,
7365
+ });
7366
+ })();
7367
+ debug.log('dev-pipeline', 'auto-worktree-decision', { runId: devRunId, decision: autoWorktreeDecision });
7368
+ if (autoWorktreeDecision === 'requested' || autoWorktreeDecision === 'auto') {
7332
7369
  const { prepareDevWorktree, renderPreparedDevWorktree } = await import('./harness/harness-worktree-auto.js');
7333
7370
  const goal = 'file' in input
7334
7371
  ? await (async () => {
@@ -7338,13 +7375,25 @@ const selfDevCmd = program
7338
7375
  return { id: goalId, file: input.file };
7339
7376
  })()
7340
7377
  : undefined;
7341
- const prepared = prepareDevWorktree(process.cwd(), devRunId, invokedAsDrive ? 'drive' : 'dev', goal);
7378
+ const prepared = (() => {
7379
+ try {
7380
+ return prepareDevWorktree(process.cwd(), devRunId, invokedAsDrive ? 'drive' : 'dev', goal);
7381
+ } catch (error) {
7382
+ if (autoWorktreeDecision !== 'auto') throw error;
7383
+ debug.log('dev-pipeline', 'auto-worktree-failed', { runId: devRunId, cwd: process.cwd(), msg: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
7384
+ throw new DevPipelineError(devCli.formatAutoWorktreeFailure(error));
7385
+ }
7386
+ })();
7342
7387
  preparedWorktree = prepared;
7343
7388
  if (spec.elanous) spec = { ...spec, elanous: { ...spec.elanous, cwd: prepared.worktree.path } };
7344
7389
  else if (spec.drive) spec = { ...spec, drive: { ...spec.drive, cwd: prepared.worktree.path } };
7345
7390
  else throw new DevPipelineError('--worktree 는 elanous 또는 셸 drive child에만 유효');
7346
7391
  if (!opts.json) console.log(renderPreparedDevWorktree(prepared).join('\n'));
7347
7392
  debug.log('dev-pipeline', 'auto-worktree-prepared', { runId: devRunId, ...prepared.environment, worktree: prepared.worktree });
7393
+ if (autoWorktreeDecision === 'auto') {
7394
+ if (!opts.json) console.log(devCli.formatAutoWorktreeNotice(prepared.worktree.path));
7395
+ debug.log('dev-pipeline', 'auto-worktree-implied', { runId: devRunId, worktree: prepared.worktree.path });
7396
+ }
7348
7397
  }
7349
7398
  // ⛔ 종전의 `const plan = planDevPipeline(spec)` 를 지운다(무인 리뷰 must-fix) — 쓰이지 않는
7350
7399
  // 데다 **잔여 조회보다 먼저 던질 수 있어** 관측 자체를 건너뛰게 만든다. 계획은 어차피
@@ -0,0 +1,13 @@
1
+ // Child fixture for test-write-guard.test.ts: a bare spy installed BEFORE outbound-alert is imported must not
2
+ // make the captured «real» curl look like a fake. Prints {refused, curlCalls}.
3
+ import { spyOn } from 'bun:test';
4
+ import * as childProcess from 'node:child_process';
5
+
6
+ const bare = spyOn(childProcess, 'execFileSync');
7
+ const { sendTelegramDirect } = await import('../domains/outbound-alert.js');
8
+ const { getUserConfig } = await import('../user-config.js');
9
+ const base = getUserConfig();
10
+ const config = { ...base, telegram: { ...base.telegram, channels: undefined, botToken: '2:fake', homeChannel: 2 } } as typeof base;
11
+ let refused = '';
12
+ try { sendTelegramDirect('private-alert-body', 'ops-alert', { config }); } catch (e) { refused = e instanceof Error ? e.name : String(e); }
13
+ console.log(JSON.stringify({ refused, curlCalls: bare.mock.calls.length }));