elanous 0.2.17 → 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 (491) 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/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
  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 -0
  132. package/apps/pwa/out/decisions/index.txt +30 -0
  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 +2 -1
  207. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  208. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  209. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  210. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  211. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  212. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  213. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  214. package/scripts/ci-public-export-import-gate.ts +7 -2
  215. package/scripts/ci-typecheck-changed.ts +21 -6
  216. package/scripts/coord-channel-watch.sh +2 -2
  217. package/scripts/discord-seat-probe.ts +6 -3
  218. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  219. package/scripts/goals/authoring-metrics.ts +73 -0
  220. package/scripts/landing-heal/must-fix-match.ts +7 -7
  221. package/scripts/landing-heal/run-mission.ts +3 -2
  222. package/scripts/lib/pch-regress-map.ts +23 -0
  223. package/scripts/morning-report.ts +18 -4
  224. package/scripts/pch-regress.ts +88 -0
  225. package/scripts/public-export-test-run.ts +37 -2
  226. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  227. package/scripts/release-loop/cut-branch.ts +2 -1
  228. package/scripts/release-loop/cutoff.ts +4 -2
  229. package/scripts/release-loop/docs-land-node.ts +8 -1
  230. package/scripts/release-loop/docs-node.ts +1 -1
  231. package/scripts/release-loop/gate-diff.ts +18 -0
  232. package/scripts/release-loop/gate-node.ts +436 -59
  233. package/scripts/release-loop/gate-partial.ts +106 -0
  234. package/scripts/release-loop/gate-timing.ts +120 -0
  235. package/scripts/release-loop/node-verdict.ts +2 -1
  236. package/scripts/release-loop/npm-publish-node.ts +11 -7
  237. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  238. package/scripts/release-loop/preflight.ts +246 -0
  239. package/scripts/release-loop/publish-node.ts +8 -1
  240. package/scripts/release-loop/release-readiness.ts +11 -6
  241. package/scripts/release-loop/release-version.ts +48 -0
  242. package/scripts/release-loop/resume-release.ts +174 -0
  243. package/scripts/release-loop/shard-plan.ts +65 -0
  244. package/scripts/release-loop/unattended-release.ts +45 -7
  245. package/scripts/release-loop/version-node.ts +145 -40
  246. package/scripts/rhythm/daily.ts +25 -4
  247. package/scripts/skills/public-check.ts +62 -12
  248. package/scripts/test-deterministic.ts +51 -33
  249. package/scripts/test-diet/effectiveness.ts +62 -1
  250. package/scripts/test-diet/lib.ts +1 -1
  251. package/scripts/test-diet/node.ts +8 -1
  252. package/scripts/video/scene-table.ts +159 -0
  253. package/src/acp/core-turn-bridge.ts +39 -2
  254. package/src/agent-mission/review-loop.ts +4 -3
  255. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  256. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  257. package/src/away/presence.ts +40 -0
  258. package/src/away/release-watch.ts +255 -0
  259. package/src/briefing/brief-items.ts +86 -17
  260. package/src/chat/index.ts +2 -1
  261. package/src/chat/short-question.ts +3 -40
  262. package/src/cli/autopilot-cli.ts +30 -11
  263. package/src/cli/away-cli.ts +68 -0
  264. package/src/cli/brief-cli.ts +36 -2
  265. package/src/cli/freeze-cli.ts +3 -2
  266. package/src/cli/guardian-cli.ts +49 -0
  267. package/src/cli/intake-cli.ts +85 -7
  268. package/src/cli/log-event-names.ts +1 -0
  269. package/src/cli/notify-cli.ts +75 -0
  270. package/src/cli/pod-cli.ts +5 -1
  271. package/src/cli/provider-cli.ts +12 -2
  272. package/src/cli/pty-drive-cli.ts +14 -3
  273. package/src/cli/release-cli.ts +128 -19
  274. package/src/cli/setup-cli.ts +31 -1
  275. package/src/cli/tasks-cli.ts +403 -4
  276. package/src/context-bus/condense.ts +19 -3
  277. package/src/context-bus/context-day.ts +77 -24
  278. package/src/context-bus/context-first.ts +33 -0
  279. package/src/context-bus/context-now-public.ts +1 -1
  280. package/src/context-bus/context-now-surfaces.ts +16 -6
  281. package/src/context-bus/context-now.ts +97 -3
  282. package/src/core-turn/short-question.ts +100 -0
  283. package/src/dashboard/dashboard-status-lines.ts +24 -4
  284. package/src/dashboard/index.ts +50 -5
  285. package/src/dashboard/input/slash-executor.ts +6 -2
  286. package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
  287. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  288. package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
  289. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  290. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  291. package/src/discord-self-message.ts +20 -2
  292. package/src/discord-slash-wire.ts +1 -0
  293. package/src/domains/guardian-tick.ts +138 -0
  294. package/src/domains/outbound-alert.ts +244 -40
  295. package/src/domains/outbound-log-probe.ts +16 -1
  296. package/src/domains/release-tool.ts +2 -2
  297. package/src/domains/schedule-registry.ts +23 -0
  298. package/src/domains/telegram-kind-route.ts +39 -0
  299. package/src/domains/watch-default-scheduler.ts +95 -0
  300. package/src/esc-abort-gate.ts +44 -0
  301. package/src/execution-loop/gates.ts +11 -2
  302. package/src/flow-loop/split.ts +8 -4
  303. package/src/git-fs/worktree.ts +23 -3
  304. package/src/graph-runner/runner.ts +25 -5
  305. package/src/harness/harness-cli-command.ts +12 -2
  306. package/src/harness/harness-queue.ts +388 -33
  307. package/src/harness/harness-salvage-cli.ts +78 -0
  308. package/src/harness/harness-worktree-add.ts +68 -0
  309. package/src/harness/helper-scan.ts +10 -3
  310. package/src/harness/ledger-write-guard.ts +6 -16
  311. package/src/hq/hq.ts +6 -1
  312. package/src/hq/seats.ts +369 -0
  313. package/src/index.ts +185 -18
  314. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  315. package/src/instance/test-write-guard.ts +200 -0
  316. package/src/intake-plane/adopt-gate.ts +49 -0
  317. package/src/intake-plane/check.ts +3 -0
  318. package/src/intake-plane/collect-github.ts +57 -25
  319. package/src/intake-plane/digest.ts +39 -15
  320. package/src/intake-plane/discord-seat-work.ts +8 -2
  321. package/src/intake-plane/harness-target.ts +4 -3
  322. package/src/intake-plane/intake-sources.ts +1 -0
  323. package/src/intake-plane/items.ts +1 -0
  324. package/src/intake-plane/requirement-funnel.ts +316 -0
  325. package/src/knowledge/know-find.ts +10 -1
  326. package/src/loops/budget.ts +36 -0
  327. package/src/loops/orchestrator/degradation.ts +57 -0
  328. package/src/loops/orchestrator/finish-rate.ts +106 -4
  329. package/src/loops/orchestrator/grid-place.ts +11 -0
  330. package/src/loops/orchestrator/tick.ts +450 -54
  331. package/src/maturity/feature-maturity.ts +8 -5
  332. package/src/maturity/tui-slash-maturity.ts +1 -0
  333. package/src/mss/logging/log-store.ts +3 -0
  334. package/src/nexus/api/decisions-route.ts +11 -1
  335. package/src/nexus/api/http-server.ts +16 -5
  336. package/src/nexus/api/loop-edges.ts +352 -23
  337. package/src/nexus/api/ops-api.ts +96 -28
  338. package/src/nexus/api/ops-seats-sources.ts +2 -2
  339. package/src/nexus/api/workflow-templates.ts +3 -0
  340. package/src/nexus/index.ts +7 -0
  341. package/src/oauth/codex.ts +38 -0
  342. package/src/onboarding/first-chat-gate.ts +29 -0
  343. package/src/release-loop/checklist.ts +106 -6
  344. package/src/release-loop/feature-store.ts +64 -16
  345. package/src/release-loop/landing-freeze.ts +6 -4
  346. package/src/release-loop/light-rc.ts +173 -0
  347. package/src/release-loop/placement.ts +64 -20
  348. package/src/release-loop/rubric.ts +136 -0
  349. package/src/seat-dispatch/seat-ask.ts +79 -16
  350. package/src/seat-loop/seat-loop.ts +12 -6
  351. package/src/self-dev/ask-launch-flow.ts +24 -3
  352. package/src/self-dev/ask-path-candidates.ts +68 -0
  353. package/src/self-dev/dev-cli.ts +36 -0
  354. package/src/self-dev/dev-pipeline.ts +4 -0
  355. package/src/self-dev/draft-triage-rules.ts +1 -1
  356. package/src/self-dev/orchestrate-cli.ts +22 -0
  357. package/src/self-dev/orchestrate.ts +1 -0
  358. package/src/self-dev/pr-preview.ts +179 -0
  359. package/src/self-dev/round-pr-supersede.ts +244 -0
  360. package/src/self-dev/run-supervisor.ts +14 -0
  361. package/src/self-dev/shard-run.ts +23 -7
  362. package/src/self-implement/follow-up-goals.ts +2 -2
  363. package/src/self-implement/gate-cli.ts +29 -8
  364. package/src/self-implement/gate-remote.ts +427 -0
  365. package/src/self-implement/goal-author-clarification.ts +88 -13
  366. package/src/self-implement/goal-author.ts +8 -3
  367. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  368. package/src/self-implement/l8-merge-queue.ts +29 -5
  369. package/src/self-implement/orchestrator.ts +61 -13
  370. package/src/self-implement/prior-draft-findings.ts +4 -3
  371. package/src/self-implement/salvage-branches.ts +254 -0
  372. package/src/skills/skill-feedback.ts +76 -0
  373. package/src/task-agent/actions.ts +101 -7
  374. package/src/task-agent/board.ts +132 -0
  375. package/src/task-agent/card-evidence.ts +203 -0
  376. package/src/task-agent/completion-evidence.ts +126 -0
  377. package/src/task-agent/judge.ts +49 -5
  378. package/src/task-agent/live-moves.ts +176 -0
  379. package/src/task-agent/mission.ts +449 -0
  380. package/src/task-agent/shadow.ts +258 -0
  381. package/src/task-agent/task-hand.ts +337 -0
  382. package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
  383. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
  384. package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
  385. package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
  386. package/src/task-orchestrator/surfaces/self-implement-pod.ts +88 -20
  387. package/src/telegram-away-command.ts +38 -0
  388. package/src/telegram-commands.ts +11 -0
  389. package/src/telegram-loops-command.ts +1 -1
  390. package/src/telegram-report-log-probe.ts +21 -0
  391. package/src/telegram-report.ts +27 -3
  392. package/src/telegram.ts +5 -20
  393. package/src/tui-client/dashboard-session.ts +1 -0
  394. package/src/user-config.ts +196 -4
  395. package/src/version/packed-revision.json +1 -1
  396. package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  401. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
  420. package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  429. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +0 -1
  489. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  490. package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
  491. /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
@@ -18,6 +18,9 @@ import { dirname, join } from 'node:path';
18
18
  import { elanousStateRoot } from '../autopilot/state-paths.js';
19
19
  import { prevScheduledFire } from './cron-match.js';
20
20
  import { debug } from '../debug/log.js';
21
+ import { listSources } from '../intake-plane/intake-sources.js';
22
+ import { getUserConfig } from '../user-config.js';
23
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
21
24
  import { deriveScheduleName, ensureScheduleRunsSchema, recordScheduleRun } from './schedule-runs.js';
22
25
 
23
26
  /** 스케줄 레지스트리 DB 정본 경로 — state-dir 존중(lazy · Phase B). prod(ELANOUS_STATE_DIR
@@ -490,6 +493,9 @@ export interface InventoryInternalOpts {
490
493
  discoveryIntervalMs?: number;
491
494
  /** 테스트 seam — workflow-runtime schedule 트리거 주입(미지정 시 discoverWorkflows). */
492
495
  workflowSchedules?: Array<{ workflowName: string; nodeId: string; cron?: string | null; intervalMs?: number | null }>;
496
+ /** Test seams for the installed instance; production reads the active config and instance root. */
497
+ watchRoot?: string;
498
+ watchEnabled?: boolean;
493
499
  }
494
500
 
495
501
  /** 데몬 내부 스케줄을 레지스트리에 upsert(멱등·고정 id). 등록 수 반환. */
@@ -513,6 +519,23 @@ export function inventoryInternalSchedules(db: Database, opts: InventoryInternal
513
519
  // 회고(retro loop = retro-aggregate/report/rebalance)와는 별개.
514
520
  const hour = opts.reflectionHour ?? (Number.isFinite(Number.parseInt(process.env.ELANOUS_REFLECTION_HOUR ?? '', 10)) ? Number.parseInt(process.env.ELANOUS_REFLECTION_HOUR!, 10) : 21);
515
521
  upsert('internal:daily-reflection', 'daily-reflection', 'daily-reflection', `0 ${hour} * * *`, null, 'daemon: elanous 활동 회고(노트/OCR/세션) 스냅샷 푸시', 'report', 'elanous');
522
+ // WATCH-DEFAULT — fail-soft: a broken sources file or config must not stop the other internal rows.
523
+ let watchSources: ReturnType<typeof listSources> = [];
524
+ try {
525
+ const watchEnabled = opts.watchEnabled ?? (getUserConfig().watch?.enabled !== false);
526
+ watchSources = watchEnabled ? listSources({ seat: 'user' }, opts.watchRoot ?? effectiveInstanceRoot()) : [];
527
+ } catch (error) {
528
+ debug.log('watch.default', 'failed', { stage: 'inventory', reason: error instanceof Error ? error.message : String(error) });
529
+ }
530
+ if (watchSources.length > 0) {
531
+ upsert('internal:watch-collect', 'watch-collect', 'watch-default', '0 7 * * *', null,
532
+ 'intake source run-due --seat user', 'ingest', 'elanous');
533
+ upsert('internal:watch-brief', 'watch-brief', 'watch-default', '30 8 * * *', null,
534
+ 'intake digest --seat user --telegram', 'digest', 'elanous');
535
+ debug.log('watch.default', 'scheduled', { topics: watchSources.length, items: 0, sent: false });
536
+ } else {
537
+ db.run("DELETE FROM schedule_registry WHERE id IN ('internal:watch-collect', 'internal:watch-brief') AND source = 'watch-default'");
538
+ }
516
539
  // discovery (env 설정 시에만 가동) — 코어 인프라 → elanous.
517
540
  const dm = opts.discoveryIntervalMs ?? Number.parseInt(process.env.ELANOUS_DISCOVERY_CRON_INTERVAL_MS ?? '', 10);
518
541
  if (Number.isFinite(dm) && dm > 0) {
@@ -75,3 +75,42 @@ export function kindRouteTarget(cfg: UserConfig, kind?: string): { botToken: str
75
75
  if (!ch || !Number.isFinite(ch.chatId)) return null;
76
76
  return { botToken: ch.botToken, chatId: ch.chatId };
77
77
  }
78
+
79
+ export type ReportRouteReason =
80
+ | 'routed' | 'report-role-missing' | 'no-role-channel'
81
+ | 'no-report-channel' | 'report-bot-not-distinct' | 'report-chat-invalid' | 'no-main-home';
82
+
83
+ /** Why a kind has (or lacks) a delivery target — no logging, no sending. Mirrors
84
+ * `resolveReportTarget` so a skipped send can say *which* rule closed it instead of
85
+ * «no report channel configured» (BRIEF-DELIVERY-1007: a channels table without a
86
+ * `report` role closed the morning brief for four days while reportChannel was set). */
87
+ export function explainReportRoute(cfg: UserConfig, kind?: string): { reason: ReportRouteReason; role: string; hint: string } {
88
+ const role = roleForKind(cfg, kind);
89
+ const trading = isTradingKind(kind);
90
+ if (cfg.telegram.channels?.length) {
91
+ if (kindRouteTarget(cfg, kind)) return { reason: 'routed', role, hint: '' };
92
+ const hasRole = resolveTelegramChannels(cfg.telegram).some(c => c.roles.includes(role));
93
+ if (trading && !hasRole && cfg.telegram.reportChannel) {
94
+ return {
95
+ reason: 'report-role-missing', role,
96
+ hint: `채널 표에 «${role}» 역할 칸이 없다 — telegram.reportChannel 은 채널 표가 있으면 쓰지 않는다. `
97
+ + `telegram.channels[].roles 에 "${role}" 를 더하거나 telegram.kindRoles.${kind ?? '<kind>'} 로 있는 역할을 고른다`,
98
+ };
99
+ }
100
+ return { reason: 'no-role-channel', role, hint: `채널 표에 «${role}» 역할을 가진 허용된 봇 칸이 없다 — telegram.channels 를 확인` };
101
+ }
102
+ if (!trading) {
103
+ return mainHomeTarget(cfg)
104
+ ? { reason: 'routed', role, hint: '' }
105
+ : { reason: 'no-main-home', role, hint: '운영 봇 또는 telegram.homeChannel·allowedUsers 가 없다' };
106
+ }
107
+ const rc = cfg.telegram.reportChannel;
108
+ if (!rc) return { reason: 'no-report-channel', role, hint: 'telegram.reportChannel 이 없다' };
109
+ const mainToken = resolveChannelBotToken('telegram', cfg).token;
110
+ if (!rc.botToken || rc.botToken === mainToken) {
111
+ return { reason: 'report-bot-not-distinct', role, hint: 'telegram.reportChannel 의 봇이 없거나 운영 봇과 같다 — 매매 보고는 별도 봇만 쓴다' };
112
+ }
113
+ return Number.isFinite(rc.chatId)
114
+ ? { reason: 'routed', role, hint: '' }
115
+ : { reason: 'report-chat-invalid', role, hint: 'telegram.reportChannel.chatId 가 숫자가 아니다' };
116
+ }
@@ -0,0 +1,95 @@
1
+ import { debug } from '../debug/log.js';
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { getUserConfig } from '../user-config.js';
5
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
6
+ import { listSources, runDue } from '../intake-plane/intake-sources.js';
7
+ import { runIntakeDigestCli } from '../cli/intake-cli.js';
8
+ import { resolveTimeZone } from '../time/format.js';
9
+ import { inventoryInternalSchedules, openSchedulesDb } from './schedule-registry.js';
10
+ import type { Database } from 'bun:sqlite';
11
+
12
+ export interface WatchDefaultSchedulerOpts {
13
+ root?: string;
14
+ enabled?: () => boolean;
15
+ now?: () => Date;
16
+ timeZone?: string;
17
+ collect?: () => Promise<unknown>;
18
+ brief?: () => Promise<unknown>;
19
+ setInterval?: (tick: () => void, ms: number) => ReturnType<typeof setInterval>;
20
+ openDb?: () => Database;
21
+ }
22
+
23
+ /** Daemon-owned watch jobs: the inventory rows describe these two actual timers. */
24
+ export function startWatchDefaultScheduler(opts: WatchDefaultSchedulerOpts = {}): { stop(): void; tickNow(): Promise<void> } {
25
+ const root = opts.root ?? effectiveInstanceRoot();
26
+ const enabled = opts.enabled ?? (() => getUserConfig().watch?.enabled !== false);
27
+ const timeZone = opts.timeZone ?? resolveTimeZone().timeZone;
28
+ const now = opts.now ?? (() => new Date());
29
+ const collect = opts.collect ?? (() => runDue({ seat: 'user', deps: { stateDir: root } }));
30
+ // The CLI sets process.exitCode on an unsent report; inside the daemon that would leak into the daemon's own exit status.
31
+ const brief = opts.brief ?? (async () => {
32
+ const before = process.exitCode;
33
+ try { return await runIntakeDigestCli({ seat: 'user', telegram: true }, { root }); }
34
+ finally { process.exitCode = before; }
35
+ });
36
+ const lastRunFile = join(root, 'intake', 'watch-default-last-run.json');
37
+ const lastRun = new Map<string, string>();
38
+ let lastInventory: string | undefined;
39
+ let lastFailure: string | undefined;
40
+ try {
41
+ if (existsSync(lastRunFile)) {
42
+ const saved = JSON.parse(readFileSync(lastRunFile, 'utf8')) as Record<string, string>;
43
+ for (const [job, day] of Object.entries(saved)) if (typeof day === 'string') lastRun.set(job, day);
44
+ }
45
+ } catch (error) { debug.log('watch.default', 'failed', { reason: `last-run: ${String(error)}` }); }
46
+ let running = false;
47
+ let stopped = false;
48
+ const tickNow = async (): Promise<void> => {
49
+ if (stopped || running) return;
50
+ running = true;
51
+ try {
52
+ const active = enabled() && listSources({ seat: 'user' }, root).length > 0;
53
+ const registryDay = now().toISOString().slice(0, 10);
54
+ const inventoryKey = `${registryDay}:${active}`;
55
+ if (lastInventory !== inventoryKey) {
56
+ // The registry row is a view; a registry failure must not skip the real collect/brief of this minute.
57
+ try {
58
+ const db = (opts.openDb ?? openSchedulesDb)();
59
+ try { inventoryInternalSchedules(db, { watchRoot: root, watchEnabled: active }); lastInventory = inventoryKey; }
60
+ finally { if (!opts.openDb) db.close(); }
61
+ } catch (error) { debug.log('watch.default', 'failed', { stage: 'registry', reason: String(error) }); }
62
+ }
63
+ lastFailure = undefined;
64
+ if (!active) return;
65
+ const parts = new Intl.DateTimeFormat('en-CA', {
66
+ timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23',
67
+ }).formatToParts(now());
68
+ const part = (type: string) => parts.find((entry) => entry.type === type)?.value ?? '';
69
+ const day = `${part('year')}-${part('month')}-${part('day')}`;
70
+ const clock = `${part('hour')}:${part('minute')}`;
71
+ for (const [job, at, fire] of [['watch-collect', '07:00', collect], ['watch-brief', '08:30', brief]] as const) {
72
+ if (clock !== at || lastRun.get(job) === day) continue;
73
+ try {
74
+ const outcome = await fire();
75
+ if (job === 'watch-brief' && (!outcome || (outcome as { sent?: boolean }).sent !== true)) continue;
76
+ if (job === 'watch-collect' && typeof outcome === 'object' && outcome && 'ran' in outcome
77
+ && (outcome as { ran: Array<{ error?: string }> }).ran.some((row) => row.error)) continue;
78
+ lastRun.set(job, day);
79
+ mkdirSync(join(root, 'intake'), { recursive: true });
80
+ const temp = `${lastRunFile}.${process.pid}.tmp`;
81
+ writeFileSync(temp, JSON.stringify(Object.fromEntries(lastRun)) + '\n');
82
+ renameSync(temp, lastRunFile);
83
+ } catch (error) { debug.log('watch.default', 'failed', { job, reason: String(error) }); }
84
+ }
85
+ } catch (error) {
86
+ const reason = String(error);
87
+ if (reason !== lastFailure) debug.log('watch.default', 'failed', { reason });
88
+ lastFailure = reason;
89
+ } finally { running = false; }
90
+ };
91
+ const timer = (opts.setInterval ?? setInterval)(() => { void tickNow(); }, 60_000);
92
+ if (typeof timer === 'object') timer.unref?.();
93
+ void tickNow();
94
+ return { stop() { stopped = true; clearInterval(timer); }, tickNow };
95
+ }
@@ -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;
@@ -3,6 +3,7 @@ import { decideBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInp
3
3
  import { resolveHarnessSubstrate, type ResolvedHarnessSubstrate } from '../harness/harness-substrate-default.js';
4
4
  import { podMemoryLimitFor } from '../task-orchestrator/surfaces/self-implement-pod.js';
5
5
  import { recallMemoryContext } from '../agent-substrate/execution/memory-context.js';
6
+ import { CONTEXT_CARD_SCREEN_CHARS } from '../context-card/index.js';
6
7
 
7
8
  export interface GateResult<T> {
8
9
  decision: T;
@@ -169,6 +170,7 @@ export async function relationGate(
169
170
  export interface MemoryGateDeps {
170
171
  recall: (query: string) => Promise<string>;
171
172
  priorAbandonment: (goalId: string) => Promise<string | undefined> | string | undefined;
173
+ priorMustFix?: (goalId: string) => readonly string[];
172
174
  }
173
175
 
174
176
  export interface MemoryDecision {
@@ -183,15 +185,22 @@ export async function memoryGate(
183
185
  card: Pick<TaskCard, 'goalId' | 'title'>,
184
186
  deps: MemoryGateDeps = { recall: recallMemoryContext, priorAbandonment: () => undefined },
185
187
  ): Promise<GateResult<MemoryDecision>> {
186
- const [context, priorAbandonment] = await Promise.all([
188
+ const [recall, priorAbandonment] = await Promise.all([
187
189
  deps.recall(card.title), deps.priorAbandonment(card.goalId),
188
190
  ]);
191
+ // must-fix 와 recall 은 서로 독립이다 — recall 이 비어도 앞선 must-fix 는 맥락에 남는다.
192
+ const mustFix = deps.priorMustFix?.(card.goalId) ?? [];
193
+ const mustFixBlock = mustFix.length ? `앞선 must-fix:\n${mustFix.map((item) => `- ${item}`).join('\n')}` : '';
194
+ const combined = [mustFixBlock, recall].filter(Boolean).join('\n');
195
+ const codePoints = [...combined];
196
+ const truncated = codePoints.length > CONTEXT_CARD_SCREEN_CHARS;
197
+ const context = truncated ? codePoints.slice(0, CONTEXT_CARD_SCREEN_CHARS).join('') : combined;
189
198
  const decision: MemoryDecision = {
190
199
  action: 'record', context, fragmentIds: [],
191
200
  ...(priorAbandonment === undefined ? {} : { priorAbandonment }),
192
201
  };
193
202
  return {
194
203
  decision,
195
- explanation: `memory=${context ? 'recalled' : 'none or unavailable'}; prior abandonment=${priorAbandonment ?? 'unknown'}; fragment IDs unavailable from recall API`,
204
+ explanation: `memory=${recall ? 'recalled' : 'none or unavailable'}; prior abandonment=${priorAbandonment ?? 'unknown'}; fragment IDs unavailable from recall API${truncated ? `; truncated; chars=${[...context].length}/${CONTEXT_CARD_SCREEN_CHARS}` : ''}`,
196
205
  };
197
206
  }
@@ -1,23 +1,27 @@
1
1
  import type { TaskCard } from '../task-cards/card-store.js';
2
2
 
3
- export interface SplitCell { id: string; title: string; seat?: string; host?: string }
3
+ export interface SplitCell { id: string; title: string; kind?: string; seat?: string; host?: string }
4
4
 
5
5
  /** FLOW1: only explicitly assigned work may enter a seat's queue. The same proposal is used in shadow and live. */
6
6
  export function splitCard(card: TaskCard, _opts: { shadow?: boolean } = {}): SplitCell[] {
7
7
  const intake = [...card.sections].reverse().find(section => section.key.startsWith('intake:wish:'));
8
8
  let text = card.title;
9
+ let kind: string | undefined;
9
10
  if (intake) {
10
11
  let body: unknown;
11
12
  try { body = JSON.parse(intake.content); } catch { body = intake.content; }
12
13
  if (typeof body === 'string') text = body;
13
- else if (body && typeof body === 'object' && typeof (body as { text?: unknown }).text === 'string') text = (body as { text: string }).text;
14
+ else if (body && typeof body === 'object') {
15
+ if (typeof (body as { text?: unknown }).text === 'string') text = (body as { text: string }).text;
16
+ if (typeof (body as { kind?: unknown }).kind === 'string') kind = (body as { kind: string }).kind;
17
+ }
14
18
  }
15
19
  const lines = text.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
16
20
  const cells: SplitCell[] = lines.map((line, index) => {
17
21
  const match = /^(?:[-*]|\d+[.)])?\s*\[(OP|MK|TC|UX)\]\s*(.+)$/u.exec(line);
18
- if (match) return { id: `${card.id}-${index + 1}`, title: match[2]!.trim(), seat: match[1]! };
22
+ if (match) return { id: `${card.id}-${index + 1}`, title: match[2]!.trim(), seat: match[1]!, ...(kind !== undefined ? { kind } : {}) };
19
23
  // 담당 없는 행도 칸으로 남긴다 — seat 가 없으니 대기열엔 못 들어가고 tick 이 unplaced-no-owner 로 센다.
20
- return { id: `${card.id}-${index + 1}`, title: line.replace(/^(?:[-*]|\d+[.)])\s*/u, '') };
24
+ return { id: `${card.id}-${index + 1}`, title: line.replace(/^(?:[-*]|\d+[.)])\s*/u, ''), ...(kind !== undefined ? { kind } : {}) };
21
25
  });
22
26
  // 의뢰 본문이 있으면 담당이 하나도 없어도 행마다 칸으로 남긴다. 본문 없이 제목뿐일 때만 카드 한 칸.
23
27
  return intake || cells.some(cell => cell.seat) ? cells : [{ id: card.id, title: card.title }];
@@ -20,6 +20,9 @@ import { debug } from '../debug/log.js';
20
20
  import { getUserConfig } from '../user-config.js';
21
21
 
22
22
  const GIT_TIMEOUT_MS = 30_000;
23
+ /** 얕은 클론(Pod `git clone --depth 50` · docker/harness/job.yaml)에서 base 를 가져올 깊이 — 클론과 같은 값.
24
+ * ⛔ 깊이 없이 얕은 저장소로 다른 브랜치를 fetch 하면 그 브랜치의 «전 이력»을 받는다(실측 193s vs 4s · 30s 에 SIGTERM). */
25
+ const SHALLOW_BASE_FETCH_DEPTH = 50;
23
26
  const WORKTREE_ADD_TIMEOUT_MS = 5 * 60_000;
24
27
 
25
28
  export interface WorktreeAddDeps {
@@ -439,14 +442,31 @@ export function syncBaseWithRemote(
439
442
  // 원격이 "그런 브랜치 없다" 고 답했다 ⇒ 축약 hex 여도 모호하지 않다.
440
443
  if (!hasRemote) return asSha(false) ?? { checkout: base, freshness: 'local-only' };
441
444
 
445
+ // ⭐ 얕은 저장소면 깊이를 묶는다 — 아니면 그 브랜치의 전 이력을 받느라 GIT_TIMEOUT_MS 를 넘긴다
446
+ // (Pod 클론은 `--depth 50` · 비-main base 런이 전부 여기서 SIGTERM 으로 죽었다). 받은 tip 에서 갈리므로
447
+ // 이후 merge-base(HEAD ↔ 그 tip)는 tip 자체라 깊이 50 으로 족하다. 얕지 않으면 종전 인자 그대로다.
448
+ // probe 를 못 읽으면 종전 경로로 간다(값은 'unknown' 으로 관측에 남긴다).
449
+ const shallowProbe = git('rev-parse', '--is-shallow-repository');
450
+ const shallowAnswer = shallowProbe.status === 0 ? (shallowProbe.stdout || '').trim() : '';
451
+ const shallow: boolean | 'unknown' = shallowAnswer === 'true' ? true : shallowAnswer === 'false' ? false : 'unknown';
452
+ const depth = shallow === true ? SHALLOW_BASE_FETCH_DEPTH : null;
453
+ const fetchArgs = depth === null ? ['fetch', 'origin', wanted] : ['fetch', `--depth=${depth}`, 'origin', wanted];
454
+ const fetchStartedAt = Date.now();
442
455
  // Only the no-runner fallback joins the shared retry seam; injected runners remain
443
456
  // an exact test/caller seam with their prior command count.
444
457
  const fetched = runner
445
- ? git('fetch', 'origin', wanted)
446
- : runGitCommand(repoRoot, ['fetch', 'origin', wanted], { encoding: 'utf8', timeout: GIT_TIMEOUT_MS });
458
+ ? git(...fetchArgs)
459
+ : runGitCommand(repoRoot, fetchArgs, { encoding: 'utf8', timeout: GIT_TIMEOUT_MS });
460
+ const fetchMs = Date.now() - fetchStartedAt;
461
+ debug.log('git-fs.worktree', 'base.fetch', { base, shallow, depth, ms: fetchMs, ok: fetched.status === 0, signal: fetched.signal ?? null }, fetched.status === 0 ? undefined : { level: 'warn' });
447
462
  if (fetched.status !== 0) {
448
463
  // origin 에 있는 것을 확인했는데 못 가져왔다 ⇒ 낡은 로컬로 진행하면 조용히 틀린다.
449
- throw new Error(`git worktree base sync failed — origin/${remoteBranch} exists but fetch failed: status=${fetched.status}, signal=${fetched.signal ?? 'none'}, stdout=${JSON.stringify((fetched.stdout || '').trim().slice(0, 200))}, stderr=${JSON.stringify((fetched.stderr || '').trim().slice(0, 200))}`);
464
+ // spawnSync 의 timeout 은 SIGTERM 으로 끝난다 — 「실패」와 「시간 초과」를 문면에서 가른다.
465
+ const timedOut = fetched.status === null && fetched.signal === 'SIGTERM';
466
+ const what = timedOut
467
+ ? `fetch timed out after ${GIT_TIMEOUT_MS / 1000}s (shallow=${shallow}${depth === null ? '' : `, depth=${depth}`})`
468
+ : 'fetch failed';
469
+ throw new Error(`git worktree base sync failed — origin/${remoteBranch} exists but ${what}: status=${fetched.status}, signal=${fetched.signal ?? 'none'}, stdout=${JSON.stringify((fetched.stdout || '').trim().slice(0, 200))}, stderr=${JSON.stringify((fetched.stderr || '').trim().slice(0, 200))}`);
450
470
  }
451
471
  // ⭐ **가져온 것**에서 갈린다(리뷰 must-fix ②) — ls-remote 로 먼저 읽은 SHA 를 쓰면 그 사이 원격이
452
472
  // 전진했을 때 "원격 tip" 계약이 깨진다. FETCH_HEAD 가 방금 받은 tip 이다.
@@ -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';
@@ -33,7 +34,7 @@ import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDep
33
34
  import { getUserConfig } from '../user-config.js';
34
35
  import { resolveRepositoryName } from './repository-name.js';
35
36
  import { installHarnessCliSinkHook } from './harness-cli-sink.js';
36
- import { addHarnessQueue, HarnessQueueDuplicateError, harnessQueueReceiptPath, listHarnessQueue, queueSeatForCwd, reconcileHarnessQueue, removeHarnessQueue, tickHarnessQueue, type HarnessQueueDeps, type QueueItem, type QueueSeat } from './harness-queue.js';
37
+ import { addHarnessQueue, HarnessQueueDuplicateError, harnessQueueReceiptPath, listHarnessQueue, queueSeatForCwd, reconcileHarnessQueue, removeHarnessQueue, setHarnessQueuePriority, tickHarnessQueue, type HarnessQueueDeps, type QueueItem, type QueueSeat } from './harness-queue.js';
37
38
  import { writeHarnessQueueReceipt } from './harness-queue-child.js';
38
39
  import { parseDoorSince, queryLaunchDoors, renderLaunchDoors, type DoorTable } from './launch-stamp.js';
39
40
  import { resolveHarnessSubstrate, type ResolvedHarnessSubstrate } from './harness-substrate-default.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('자리별 영속 발사 대기열');
@@ -2548,12 +2550,20 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2548
2550
  console.log(`${row.id} ${row.seat} ${row.kind} queued`);
2549
2551
  }));
2550
2552
  queue.command('list').description('대기·발사 원장 조회').action(() => queueAction(async () => {
2551
- for (const row of listHarnessQueue(queueDeps)) console.log(`${row.id} ${row.seat} ${row.status} ${row.kind} ${row.input}${row.waitingReason ? ` · ${row.waitingReason}` : ''}`);
2553
+ for (const row of listHarnessQueue(queueDeps)) console.log(`${row.id} ${row.seat} ${row.status} ${row.kind} ${row.input}${row.priority === undefined ? '' : ` · prio ${row.priority}`}${row.waitingReason ? ` · ${row.waitingReason}` : ''}`);
2552
2554
  }));
2553
2555
  queue.command('remove <id>').description('대기 중 또는 종료 확인된 항목 제거').action((id: string) => queueAction(async () => {
2554
2556
  if (!await removeHarnessQueue(id, queueDeps)) throw new Error(`queue item active or not found: ${id}`);
2555
2557
  console.log(`${id} removed`);
2556
2558
  }));
2559
+ queue.command('prio <id> <n>').description('대기 중 항목의 우선순위 재조정(클수록 먼저 · 다음 tick 부터)').action((id: string, n: string) => queueAction(async () => {
2560
+ const value = Number(n);
2561
+ if (!n.trim() || !Number.isFinite(value)) throw new Error(`harness queue prio: 수가 아니다 — ${n}`);
2562
+ // A CLI call is the actor here; a seat or track that drives it names itself through the environment.
2563
+ const by = process.env.ELANOUS_TRACK || process.env.ELANOUS_HARNESS_SEAT || 'cli';
2564
+ const row = await setHarnessQueuePriority(id, value, by, queueDeps);
2565
+ console.log(`${row.id} prio ${row.priority}`);
2566
+ }));
2557
2567
  queue.command('reconcile <id>').description('불확정 발사를 확인하고 종료 또는 미발사 증거가 있으면 예약 해소').action((id: string) => queueAction(async () => {
2558
2568
  console.log(`${id} ${await reconcileHarnessQueue(id, queueDeps)}`);
2559
2569
  }));