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
@@ -7,6 +7,13 @@ import { join } from 'node:path';
7
7
  import { dlopen } from 'bun:ffi';
8
8
  import { hostLeaseBaseDir, type PendingJobIdentity } from '../../pod-lease/host-lease.js';
9
9
 
10
+ /** The harness Pod's container requests (self-implement-pod Job manifest). Lives here so admission reuses the real request. */
11
+ export const POD_CHILD_REQUESTS = { cpu: '1', memory: '6Gi' } as const;
12
+ /** POD-ADMIT-BY-USAGE — observed usage is scaled by this before it reserves memory. */
13
+ const POD_USAGE_HEADROOM = 1.5;
14
+ /** POD-ADMIT-BY-USAGE — a node keeps at least this share of allocatable memory free (real usage and bookkeeping). */
15
+ const POD_NODE_FREE_FLOOR_RATIO = 0.1;
16
+
10
17
  /** Persisted on a Job admitted through the host lease; the file lease remains until its Pod is observed Running or terminal. */
11
18
  export const POD_HOST_LEASE_ANNOTATION = 'elanous.dev/host-lease-admitted';
12
19
 
@@ -39,6 +46,27 @@ export interface PodLeaseMember {
39
46
  /** Node-by-node memory left after all assigned Pod requests and harness Running limits. */
40
47
  availableMemoryByNodeBytes: number[] | null;
41
48
  reason: string | null;
49
+ /**
50
+ * POD-ADMIT-BY-USAGE (only when measured with `measureUsage`): observed memory of Running harness Pods
51
+ * (`kubectl top pods`). null = metrics unreadable (see usageReason) — every Pod is then reserved at its limit, as before.
52
+ */
53
+ harnessUsageBytes?: number[] | null;
54
+ usageReason?: string | null;
55
+ /** Pool-wide harness reservation: Σ min(limit, max(request, usage×1.5)), or max(request, limit) when the Pod's usage is unmeasured. */
56
+ memoryReservedBytes?: number | null;
57
+ /** Parallel to availableMemoryByNodeBytes: each schedulable node's allocatable memory. */
58
+ allocatableMemoryByNodeBytes?: number[] | null;
59
+ /** Parallel to availableMemoryByNodeBytes: each node's real memory use (`kubectl top nodes`), null when unmeasured. */
60
+ usedMemoryByNodeBytes?: Array<number | null> | null;
61
+ }
62
+
63
+ /** POD-ADMIT-BY-USAGE — how much memory one new goal is counted at, and why. */
64
+ export interface PodAdmissionByUsage {
65
+ samples: number;
66
+ p95Bytes: number | null;
67
+ admitBytes: number;
68
+ /** Why admission stayed on the conservative per-goal size; null when usage drove it. */
69
+ fallback: string | null;
42
70
  }
43
71
 
44
72
  export interface PoolLeaseMeasure {
@@ -61,6 +89,28 @@ export interface PoolLeaseRecommendation {
61
89
  pendingJobs?: PendingJobIdentity[];
62
90
  /** Absent only for injected legacy status fixtures; real measurements always report a number or null. */
63
91
  unleasedRunning?: number | null;
92
+ /** POD-ADMIT-BY-USAGE — the per-goal size this recommendation used. */
93
+ admission?: PodAdmissionByUsage;
94
+ }
95
+
96
+ /** `kubectl top … --no-headers` → name → memory bytes; null when any line cannot be read (fail conservative). */
97
+ function topMemory(stdout: string, column: number): Map<string, number> | null {
98
+ const out = new Map<string, number>();
99
+ for (const line of stdout.split('\n')) {
100
+ if (!line.trim()) continue;
101
+ const cols = line.trim().split(/\s+/u);
102
+ const bytes = cols.length > column ? quantity(cols[column]!, 'memory') : null;
103
+ if (bytes === null || out.has(cols[0]!)) return null;
104
+ out.set(cols[0]!, bytes);
105
+ }
106
+ return out;
107
+ }
108
+
109
+ /** Nearest-rank p95. */
110
+ function p95(values: readonly number[]): number | null {
111
+ if (!values.length) return null;
112
+ const sorted = [...values].sort((a, b) => a - b);
113
+ return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)]!;
64
114
  }
65
115
 
66
116
  /** Kubernetes quantity to bytes (memory) or millicores (CPU). */
@@ -238,7 +288,7 @@ function runPoolDnsProbe(context: string, kubectl: PoolKubectl): PoolDnsProbe {
238
288
  }
239
289
 
240
290
  /** Each cluster reads resource reservations; DNS requires a bounded disposable Pod. */
241
- export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; /** Read-only snapshots must not create the disposable DNS Pod or update its cache. */ skipDnsProbe?: boolean } = {}): PoolLeaseMeasure {
291
+ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; /** Read-only snapshots must not create the disposable DNS Pod or update its cache. */ skipDnsProbe?: boolean; /** POD-ADMIT-BY-USAGE — also read `kubectl top` so Running harness Pods reserve by observed usage. */ measureUsage?: boolean } = {}): PoolLeaseMeasure {
242
292
  const kubectl = deps.kubectl ?? leaseKubectl;
243
293
  return { members: members.map((member) => {
244
294
  const base = ['--context', member.context, '--request-timeout=10s'];
@@ -249,6 +299,26 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
249
299
  // All namespaces are necessary: unrelated workloads also reserve memory on these nodes.
250
300
  const pods = kubectl([...base, 'get', 'pods', '--all-namespaces', '-o', 'json']);
251
301
  const reasons: string[] = [];
302
+ // POD-ADMIT-BY-USAGE: metrics are optional — a failed `top` leaves every reservation at today's limit.
303
+ let podUsage: Map<string, number> | null = null;
304
+ let nodeUsage: Map<string, number> | null = null;
305
+ if (deps.measureUsage) {
306
+ const top = kubectl([...base, '-n', 'elanous-test', 'top', 'pods', '--no-headers']);
307
+ podUsage = top.status === 0 ? topMemory(top.stdout, 2) : null;
308
+ result.usageReason = podUsage ? null
309
+ : `top pods unavailable: ${top.status === 0 ? 'unparseable output' : top.stderr.trim().split('\n').pop() || `rc=${top.status}`}`.slice(0, 160);
310
+ if (podUsage) {
311
+ const topNodes = kubectl([...base, 'top', 'nodes', '--no-headers']);
312
+ nodeUsage = topNodes.status === 0 ? topMemory(topNodes.stdout, 3) : null;
313
+ // The node real-usage floor needs node metrics: without them stay on today's limit reservations.
314
+ if (!nodeUsage) {
315
+ podUsage = null;
316
+ result.usageReason = `top nodes unavailable: ${topNodes.status === 0 ? 'unparseable output' : topNodes.stderr.trim().split('\n').pop() || `rc=${topNodes.status}`}`.slice(0, 160);
317
+ }
318
+ }
319
+ }
320
+ const harnessUsage: number[] = [];
321
+ const nodeAllocatable = new Map<string, number>();
252
322
  const parsedItems = (response: ReturnType<PoolKubectl>): Record<string, unknown>[] | null => {
253
323
  if (response.status !== 0) return null;
254
324
  try { return items(JSON.parse(response.stdout)); } catch { return null; }
@@ -280,12 +350,15 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
280
350
  }
281
351
  nodeNames.add(name);
282
352
  const repels = taintList.some((t) => t!.effect === 'NoSchedule' || t!.effect === 'NoExecute');
283
- if (spec?.unschedulable !== true && !repels && object(ready[0])?.status === 'True') nodeFree.set(name, mem);
353
+ if (spec?.unschedulable !== true && !repels && object(ready[0])?.status === 'True') { nodeFree.set(name, mem); nodeAllocatable.set(name, mem); }
284
354
  memory += mem; cpu += cores;
285
355
  }
286
356
  if (valid) { result.allocatableMemoryBytes = memory; result.allocatableCpuMillicores = cpu; }
287
- else { nodeFree.clear(); nodeNames.clear(); reasons.push('cluster nodes: invalid name/allocatable memory/cpu/readiness'); }
357
+ else { nodeFree.clear(); nodeAllocatable.clear(); nodeNames.clear(); reasons.push('cluster nodes: invalid name/allocatable memory/cpu/readiness'); }
288
358
  }
359
+ // POD-ADMIT-BY-USAGE: every schedulable node needs a real-usage reading, or the member stays on limits.
360
+ const unreadNode = podUsage && nodeUsage ? [...nodeFree.keys()].find((n) => !nodeUsage!.has(n)) : undefined;
361
+ if (unreadNode !== undefined) { podUsage = null; result.usageReason = `top nodes: no reading for ${unreadNode}`.slice(0, 160); }
289
362
  const jobItems = parsedItems(jobs);
290
363
  const podItems = parsedItems(pods);
291
364
  if (!jobItems) reasons.push(`cluster jobs: ${jobs.status === 0 ? 'invalid response' : jobs.stderr.trim().split('\n').pop() || `rc=${jobs.status}`}`);
@@ -303,7 +376,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
303
376
  if (object(meta.annotations)?.[POD_HOST_LEASE_ANNOTATION] === 'true') leasedJobs.add(meta.name);
304
377
  }
305
378
  if (!jobsValid) reasons.push('cluster jobs: missing name/labels');
306
- let running = 0, pending = 0, unleasedRunning = 0, limits = 0, limitsValid = true, reservationsValid = nodeItems !== null && nodeNames.size === nodeItems.length;
379
+ let running = 0, pending = 0, unleasedRunning = 0, limits = 0, reserved = 0, limitsValid = true, reservationsValid = nodeItems !== null && nodeNames.size === nodeItems.length;
307
380
  const pendingJobs = new Map<string, PendingJobIdentity>();
308
381
  let unknownPhase = false;
309
382
  const seenPods = new Set<string>();
@@ -324,6 +397,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
324
397
  (labels?.['elanous.substrate'] === 'pod' || jobNames.has(String(labels?.['elanous.job'] ?? '')));
325
398
  const spec = object(pod.spec);
326
399
  let limit: number | null = null;
400
+ let reserve: number | null = null;
327
401
  if (harness) {
328
402
  if (phase === 'Pending') {
329
403
  pending++;
@@ -338,7 +412,15 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
338
412
  if (typeof jobName !== 'string' || !leasedJobs.has(jobName)) unleasedRunning++;
339
413
  limit = spec ? runningLimit(spec) : null;
340
414
  if (limit === null) limitsValid = false;
341
- else limits += limit;
415
+ else {
416
+ limits += limit;
417
+ // Measured usage: max(request, usage×1.5) capped at the limit. Unmeasured: max(request, limit) (today's node reservation).
418
+ const used = typeof metadata.name === 'string' ? podUsage?.get(metadata.name) : undefined;
419
+ const request = spec ? requestedMemory(spec) : null;
420
+ if (used !== undefined) harnessUsage.push(used);
421
+ reserve = used !== undefined && request !== null ? Math.min(limit, Math.max(request, Math.ceil(used * POD_USAGE_HEADROOM))) : Math.max(request ?? 0, limit);
422
+ reserved += reserve;
423
+ }
342
424
  }
343
425
  }
344
426
  // Pending Pods with no assigned node have no node reservation yet. They already occupy a pool slot.
@@ -347,13 +429,22 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
347
429
  requestedMemory(spec) !== null) continue;
348
430
  const requested = spec ? requestedMemory(spec) : null;
349
431
  if (typeof nodeName !== 'string' || !nodeNames.has(nodeName) || requested === null) { reservationsValid = false; continue; }
350
- if (nodeFree.has(nodeName)) nodeFree.set(nodeName, nodeFree.get(nodeName)! - Math.max(requested, limit ?? 0));
432
+ if (nodeFree.has(nodeName)) nodeFree.set(nodeName, nodeFree.get(nodeName)! - Math.max(requested, reserve ?? limit ?? 0));
351
433
  }
352
434
  if (unknownPhase) reasons.push('cluster pods: missing/Unknown phase');
353
435
  if (jobsValid && !unknownPhase) { result.running = running; result.pending = pending; result.pendingJobs = [...pendingJobs.values()]; result.unleasedRunning = unleasedRunning; }
354
- if (limitsValid && jobsValid && !unknownPhase) result.memoryLimitBytes = limits;
436
+ if (limitsValid && jobsValid && !unknownPhase) {
437
+ result.memoryLimitBytes = limits;
438
+ if (deps.measureUsage) { result.memoryReservedBytes = reserved; result.harnessUsageBytes = podUsage ? harnessUsage : null; }
439
+ }
355
440
  else if (!limitsValid) reasons.push('cluster pods: Running Pod memory limit missing/invalid');
356
- if (reservationsValid && limitsValid && jobsValid && !unknownPhase) result.availableMemoryByNodeBytes = [...nodeFree.values()];
441
+ if (reservationsValid && limitsValid && jobsValid && !unknownPhase) {
442
+ result.availableMemoryByNodeBytes = [...nodeFree.values()];
443
+ if (deps.measureUsage) {
444
+ result.allocatableMemoryByNodeBytes = [...nodeFree.keys()].map((n) => nodeAllocatable.get(n)!);
445
+ result.usedMemoryByNodeBytes = [...nodeFree.keys()].map((n) => nodeUsage?.get(n) ?? null);
446
+ }
447
+ }
357
448
  else if (!reservationsValid && !unknownPhase) reasons.push('cluster reservations: node assignment or memory request/limit missing/invalid');
358
449
  }
359
450
  result.reason = reasons.length ? reasons.join('; ').slice(0, 240) : null;
@@ -366,6 +457,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
366
457
  } catch (error) {
367
458
  result.running = null; result.pending = null; result.unleasedRunning = null; result.memoryLimitBytes = null;
368
459
  result.allocatableMemoryBytes = null; result.allocatableCpuMillicores = null; result.availableMemoryByNodeBytes = null;
460
+ if (deps.measureUsage) { result.memoryReservedBytes = null; result.harnessUsageBytes = null; result.allocatableMemoryByNodeBytes = null; result.usedMemoryByNodeBytes = null; }
369
461
  result.reason = `cluster: ${error instanceof Error ? error.message : String(error)}`.split('\n')[0]!.slice(0, 240);
370
462
  }
371
463
  return result;
@@ -377,7 +469,9 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
377
469
  const empty: PoolLeaseRecommendation = { recommended: null, limitedBy: null, reason: null, capacitySlots: null, memorySlots: null, liteMemorySlots: null, placeableSlots: null, accountSlots: null, running: null, pending: null, unleasedRunning: null };
378
470
  const goalBytes = quantity(options.perGoalMemory ?? '16Gi', 'memory');
379
471
  if (goalBytes === null || goalBytes <= 0) return { ...empty, reason: '측정 불가: perGoalMemory' };
472
+ const admission = admissionByUsage(measure, goalBytes, options.perGoalMemory !== undefined);
380
473
  if (!measure.members.length || measure.members.some((m) => m.running === null || m.pending === null || m.unleasedRunning === null || m.allocatableMemoryBytes === null || m.memoryLimitBytes === null || m.allocatableCpuMillicores === null || m.availableMemoryByNodeBytes === null)) {
474
+ debug.log('pod-lease', 'admit-by-usage', { samples: admission.samples, p95Bytes: admission.p95Bytes, admitBytes: admission.admitBytes, fallback: admission.fallback, recommended: null, limitedBy: null });
381
475
  return { ...empty, reason: `측정 불가: cluster${measure.members.map((m) => m.reason ? ` ${m.context}: ${m.reason}` : '').join('')}` };
382
476
  }
383
477
  if (!Number.isSafeInteger(options.capacity) || options.capacity < 0) return { ...empty, reason: '측정 불가: capacity' };
@@ -392,12 +486,25 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
392
486
  const eligibleOccupied = measure.members.reduce((sum, m) => sum + (m.reason === 'dns' ? 0 : Math.min(m.capacity, m.running! + m.pending!)), 0);
393
487
  const capacitySlots = Math.max(0, Math.min(options.capacity - eligibleOccupied,
394
488
  memberFree.reduce((sum, free) => sum + free, 0)));
395
- const memberMemorySlots = measure.members.map((m) => m.availableMemoryByNodeBytes!.reduce(
396
- (sum, bytes) => sum + Math.max(0, Math.floor(bytes / goalBytes)), 0));
489
+ // The goal size may fall back to the conservative size pool-wide; the node floor is separate and follows each
490
+ // member: once its Running Pods reserve by usage (usage read), its nodes keep 10% free in bookkeeping and real use.
491
+ const admitBytes = admission.admitBytes;
492
+ const usageRead = (m: PodLeaseMember) => Array.isArray(m.harnessUsageBytes);
493
+ const memberMemorySlots = measure.members.map((m) => m.availableMemoryByNodeBytes!.reduce((sum, bytes, i) => {
494
+ if (!usageRead(m)) return sum + Math.max(0, Math.floor(bytes / admitBytes));
495
+ const allocatable = m.allocatableMemoryByNodeBytes?.[i];
496
+ const used = m.usedMemoryByNodeBytes?.[i];
497
+ // A usage-read member always carries both per-node readings; anything missing admits nothing on that node.
498
+ if (typeof allocatable !== 'number' || typeof used !== 'number') return sum;
499
+ const floor = allocatable * POD_NODE_FREE_FLOOR_RATIO;
500
+ const slots = Math.min(Math.floor((bytes - floor) / admitBytes), Math.floor((allocatable - used - floor) / admitBytes));
501
+ return sum + Math.max(0, slots);
502
+ }, 0));
397
503
  // Preserve the requested allocatable-minus-harness-limit metric. The node-local
398
504
  // placement bound additionally accounts for every namespace's reservations.
399
- const memorySlots = measure.members.reduce((sum, m) => sum + (m.reason === 'dns' ? 0 : Math.max(0,
400
- Math.floor((m.allocatableMemoryBytes! - m.memoryLimitBytes!) / goalBytes))), 0);
505
+ const memorySlots = measure.members.reduce((sum, m) => sum + (m.reason === 'dns' ? 0 : Math.max(0, usageRead(m)
506
+ ? Math.floor((m.allocatableMemoryBytes! * (1 - POD_NODE_FREE_FLOOR_RATIO) - (m.memoryReservedBytes ?? m.memoryLimitBytes!)) / admitBytes)
507
+ : Math.floor((m.allocatableMemoryBytes! - m.memoryLimitBytes!) / admitBytes))), 0);
401
508
  const placeableSlots = measure.members.reduce((sum, m, i) => sum + Math.min(
402
509
  Math.max(0, m.capacity - m.running! - m.pending!), memberMemorySlots[i]!,
403
510
  ), 0);
@@ -411,5 +518,27 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
411
518
  const recommended = Math.max(0, Math.min(capacitySlots, memoryBound) - eligibleUnleasedRunning);
412
519
  const limitedBy = memoryBound < capacitySlots ? 'memory' : 'capacity';
413
520
  if (unleasedRunning > 0) debug.log('pod-lease', 'unleased', { unleasedRunning, running, recommended });
414
- return { recommended, limitedBy, reason: measure.members.some((m) => m.reason === 'dns') ? 'dns' : null, capacitySlots, memorySlots, liteMemorySlots, placeableSlots, accountSlots, running, pending, pendingJobs, unleasedRunning };
521
+ debug.log('pod-lease', 'admit-by-usage', { samples: admission.samples, p95Bytes: admission.p95Bytes, admitBytes: admission.admitBytes, fallback: admission.fallback, recommended, limitedBy });
522
+ return { recommended, limitedBy, reason: measure.members.some((m) => m.reason === 'dns') ? 'dns' : null, capacitySlots, memorySlots, liteMemorySlots, placeableSlots, accountSlots, running, pending, pendingJobs, unleasedRunning, admission };
523
+ }
524
+
525
+ /**
526
+ * POD-ADMIT-BY-USAGE — one new goal counts at max(the Pod's memory request, p95 of Running harness Pod usage × 1.5),
527
+ * capped at the per-goal size. No samples (metrics unreadable, nothing running, or an explicit per-goal size such as an
528
+ * OOM retry tier) keeps the conservative per-goal size.
529
+ */
530
+ function admissionByUsage(measure: PoolLeaseMeasure, goalBytes: number, explicit: boolean): PodAdmissionByUsage {
531
+ const live = measure.members.filter((m) => m.reason !== 'dns');
532
+ const samples = live.flatMap((m) => m.harnessUsageBytes ?? []);
533
+ const p95Bytes = p95(samples);
534
+ const requestBytes = quantity(POD_CHILD_REQUESTS.memory, 'memory')!;
535
+ // Any live member whose usage was not read keeps the whole pool on today's per-goal size (fail conservative).
536
+ const fallback = explicit ? 'explicit perGoalMemory'
537
+ : live.every((m) => m.harnessUsageBytes === undefined) ? 'usage not measured'
538
+ : live.some((m) => m.harnessUsageBytes == null) ? (live.map((m) => m.harnessUsageBytes == null ? `${m.context}: ${m.usageReason ?? 'usage not measured'}` : '').filter(Boolean).join('; '))
539
+ : p95Bytes === null ? 'no Running harness Pod usage samples'
540
+ : null;
541
+ const admitBytes = fallback !== null || p95Bytes === null ? goalBytes
542
+ : Math.min(goalBytes, Math.max(requestBytes, Math.ceil(p95Bytes * POD_USAGE_HEADROOM)));
543
+ return { samples: samples.length, p95Bytes, admitBytes, fallback };
415
544
  }
@@ -193,7 +193,8 @@ export class PodPoolScheduler {
193
193
  // One lease directory per pool (contexts, not caps) — every CLI process launching into the same clusters shares it.
194
194
  this.hostLease = options.hostLease ?? podPoolHostLease(members);
195
195
  const measureStatus = options.status ?? (() => {
196
- const measure = measurePoolLease(members, { ...(options.kubectl ? { kubectl: options.kubectl } : {}), ...(options.dns ? { dns: options.dns } : {}) });
196
+ // POD-ADMIT-BY-USAGE: admission reads observed Pod usage (`kubectl top`); tryAcquire's occupancy read does not.
197
+ const measure = measurePoolLease(members, { ...(options.kubectl ? { kubectl: options.kubectl } : {}), ...(options.dns ? { dns: options.dns } : {}), measureUsage: true });
197
198
  return recommendConcurrency(measure, { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
198
199
  });
199
200
  this.measure = async () => measureStatus();
@@ -297,11 +298,18 @@ export class PodPoolScheduler {
297
298
  * POOL-SPREAD (10-06): comparing absolute free slots always picked the big member (node-b 25 vs node-c 4), so the small
298
299
  * member idled until the big one was full. Comparing the free share fills members in proportion to their capacity.
299
300
  * A member whose occupancy could not be read falls back to inflight-only (never treated as zero occupied).
301
+ * `selfCapped` (POOLGLOBAL-REG 10-07): a caller with its own concurrency cap (the release gate) counts only its own Jobs
302
+ * — free = capacity − this scheduler's inflight, summed over members as the pool total — and reads no cluster occupancy.
303
+ * #24253 made every acquire subtract all Running+Pending harness goal Pods, so with ~24 goal Pods on the gate's 25-slot
304
+ * member the 0.2.15 gate launched its shards one at a time.
300
305
  * Ties keep the configured member order. Returns null when every readable member is full.
301
306
  */
302
- async tryAcquire(skip?: ReadonlySet<string>, onEmpty?: (reason: string) => void): Promise<PodPoolMember | null> {
307
+ async tryAcquire(skip?: ReadonlySet<string>, onEmpty?: (reason: string) => void, mode: { selfCapped?: boolean } = {}): Promise<PodPoolMember | null> {
308
+ const selfCapped = mode.selfCapped === true;
303
309
  let reading: Record<string, MemberOccupancy>;
304
- try { reading = await this.occupancy(); }
310
+ // A self-capped caller's own Jobs are its inflight; other workloads' Pods are not its slots (no cluster read).
311
+ if (selfCapped) reading = Object.fromEntries(this.members.map((m) => [m.context, { occupied: 0 }]));
312
+ else try { reading = await this.occupancy(); }
305
313
  catch (error) {
306
314
  const reason = error instanceof Error ? error.message : String(error);
307
315
  reading = Object.fromEntries(this.members.map((m) => [m.context, { occupied: null, reason }]));
@@ -329,12 +337,12 @@ export class PodPoolScheduler {
329
337
  if (better(cand)) best = cand;
330
338
  }
331
339
  if (!best) {
332
- onEmpty?.(`no free slot · free=${JSON.stringify(free)} · measured=${JSON.stringify(measured)}${skip?.size ? ` · skipped=${[...skip].join(',')}` : ''}`);
340
+ onEmpty?.(`no free slot · free=${JSON.stringify(free)} · measured=${JSON.stringify(measured)}${selfCapped ? ' · selfCapped' : ''}${skip?.size ? ` · skipped=${[...skip].join(',')}` : ''}`);
333
341
  return null;
334
342
  }
335
343
  this.inflight.set(best.member.context, (this.inflight.get(best.member.context) ?? 0) + 1);
336
344
  const share = Object.fromEntries(this.members.map((m) => [m.context, Math.round(Math.max(0, free[m.context] ?? 0) / m.capacity * 100) / 100]));
337
- debug.log('pod.pool', 'member-selected', { member: best.member.context, free, measured, share });
345
+ debug.log('pod.pool', 'member-selected', { member: best.member.context, free, measured, share, ...(selfCapped ? { selfCapped: true } : {}) });
338
346
  return best.member;
339
347
  }
340
348
  release(member: PodPoolMember): void {
@@ -48,9 +48,9 @@ export const POD_JOB_DEADLINE_SECONDS = 10_800;
48
48
  // POD-DIET (10-06 03:47~05:18 · node-b goal Pods · 89 one-minute samples · 42 Pods): per-Pod observed max median 4.6Gi ·
49
49
  // p75 6.2Gi · p90 11.7Gi — 23/42 went above the old 4Gi request, so the scheduler over-packed. 6Gi ≈ p75; limits stay.
50
50
  // Node allocatable ≈ 343Gi ≫ 25 × 6Gi. One-minute samples can miss the true peak (lower bound).
51
- export const POD_CHILD_REQUESTS = { cpu: '1', memory: '6Gi' } as const;
52
51
  import type { PodPoolMember, PodPoolScheduler } from './pod-pool.js';
53
- import { measurePoolLease, recommendConcurrency, POD_HOST_LEASE_ANNOTATION, LEASE_KUBECTL_MAX_BUFFER, memoryQuantityBytes } from './pod-lease.js';
52
+ export { POD_CHILD_REQUESTS } from './pod-lease.js';
53
+ import { measurePoolLease, recommendConcurrency, POD_CHILD_REQUESTS, POD_HOST_LEASE_ANNOTATION, LEASE_KUBECTL_MAX_BUFFER, memoryQuantityBytes } from './pod-lease.js';
54
54
  import { ACTUAL_SUBSTRATE_ENV, RUN_CONTRACT_ENV, carryRunContract, completionFloorFor } from '../../self-implement/graph-run-contract.js';
55
55
  import { spawnSync } from 'node:child_process';
56
56
  import { createHash } from 'node:crypto';
@@ -0,0 +1,38 @@
1
+ import { readPresence, writePresence, type Presence } from './away/presence.js';
2
+ import { releaseNowText, releaseWatchTick } from './away/release-watch.js';
3
+ import { readReleaseRuns, type ReleaseRunView } from './nexus/api/ops-api.js';
4
+
5
+ /** AWAY-MODE-1 ③ — 텔레그램 /release: PWA /ops/release 와 같은 원장을 읽어 지금 런·노드를 한 통으로. */
6
+ export function telegramReleaseStatus(args: string[], runs: () => ReleaseRunView[] | 'unavailable' = () => readReleaseRuns(null, undefined, { facts: true }), now: () => number = Date.now): string {
7
+ if (args.length) return '사용법: /release';
8
+ return releaseNowText(runs(), now());
9
+ }
10
+
11
+ export interface AwaySlashDeps {
12
+ read?: () => Presence;
13
+ write?: (away: boolean, by: string) => Presence;
14
+ tick?: () => unknown;
15
+ release?: () => string;
16
+ }
17
+
18
+ /** AWAY-MODE-1 ① — 텔레그램 /away on|off|status. 답은 이 대화로 돌아오므로 따로 발송하지 않는다. */
19
+ export function telegramAwaySlash(args: string[], deps: AwaySlashDeps = {}): string {
20
+ const read = deps.read ?? (() => readPresence());
21
+ const write = deps.write ?? ((away: boolean, by: string) => writePresence(away, by));
22
+ const tick = deps.tick ?? (() => releaseWatchTick());
23
+ const release = deps.release ?? (() => telegramReleaseStatus([]));
24
+ const sub = (args[0] ?? 'status').toLowerCase();
25
+ if (args.length > 1 || !['on', 'off', 'status'].includes(sub)) return '사용법: /away on | off | status';
26
+ if (sub === 'on') {
27
+ write(true, 'telegram');
28
+ tick();
29
+ return `🚶 외출 모드 켬 — 발행 노드 전이·막힘·60분 생존 줄을 여기로 보낸다(끄기: /away off)\n${release()}`;
30
+ }
31
+ if (sub === 'off') {
32
+ write(false, 'telegram');
33
+ tick();
34
+ return '🏠 외출 모드 끔 — 발행 알림은 평소대로';
35
+ }
36
+ const presence = read();
37
+ return `${presence.away ? '🚶 외출 중' : '🏠 자리에 있음'}${presence.since ? ` · ${presence.since}` : ''}\n${release()}`;
38
+ }
@@ -50,6 +50,7 @@ import { createAdPipelineDeps, runAdPipeline } from './ad-pipeline/run.js';
50
50
  import { projectCommand } from './telegram-project-command.js';
51
51
  import { telegramNowSlash } from './context-bus/context-now-surfaces.js';
52
52
  import { telegramLoopsStatus } from './telegram-loops-command.js';
53
+ import { telegramAwaySlash, telegramReleaseStatus } from './telegram-away-command.js';
53
54
  import { createWishCard } from './intake-plane/wish-card.js';
54
55
  import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
55
56
  import { CardStore } from './task-cards/card-store.js';
@@ -334,6 +335,16 @@ export function defaultTelegramCommands(
334
335
  description: '루프·크론 현황 — 늦음·실패를 먼저 보여줍니다',
335
336
  handler: (args) => telegramLoopsStatus(args),
336
337
  },
338
+ {
339
+ name: 'release',
340
+ description: '발행 현황 — 지금 런·노드·막힘을 한 통으로',
341
+ handler: async (args) => telegramReleaseStatus(args),
342
+ },
343
+ {
344
+ name: 'away',
345
+ description: '외출 모드 — /away on|off|status (켜면 발행 전이를 여기로)',
346
+ handler: async (args) => telegramAwaySlash(args),
347
+ },
337
348
  {
338
349
  name: 'status',
339
350
  description: 'Show bot identity, current session, provider + model',
@@ -0,0 +1,21 @@
1
+ import { sendTelegramReport } from './telegram-report.js';
2
+ import { getUserConfig, setUserConfigOverlay } from './user-config.js';
3
+ import { registerStandaloneLogSink } from './domains/standalone-log-sink.js';
4
+
5
+ // Focused subprocess fixture (BRIEF-DELIVERY-1007): the production shape — a channels table whose
6
+ // trading channel lacks the `report` role while the legacy reportChannel points at that bot.
7
+ setUserConfigOverlay(cfg => ({
8
+ ...cfg,
9
+ telegram: {
10
+ ...cfg.telegram,
11
+ channels: [
12
+ { name: 'main', botToken: '111111:private-secret', chatId: 98765, interactive: true, roles: ['qa', 'default', 'system'] },
13
+ { name: 'conatus', botToken: '222222:private-secret', chatId: 98766, interactive: false, roles: ['investment', 'finance'] },
14
+ ],
15
+ reportChannel: { botToken: '222222:private-secret', chatId: 98766 },
16
+ },
17
+ }));
18
+ // The entry point attaches logs.db, exactly as cron morning-report does.
19
+ if (process.env.NODE_ENV !== 'test') await registerStandaloneLogSink('telegram-report-probe');
20
+ console.log(`probe-start ${new Date().toISOString()}`);
21
+ if (await sendTelegramReport(getUserConfig(), 'SECRET-BODY-DO-NOT-LOG', { kind: 'report' })) process.exitCode = 1;
@@ -11,11 +11,25 @@
11
11
  // the report channel while interactive Q&A stays on the main bot.
12
12
 
13
13
  import { TelegramBot } from './telegram.js';
14
- import { isTradingKind, kindRouteTarget, logKindRouteFallback, mainHomeTarget } from './domains/telegram-kind-route.js';
14
+ import { explainReportRoute, isTradingKind, kindRouteTarget, logKindRouteFallback, mainHomeTarget } from './domains/telegram-kind-route.js';
15
+ export { explainReportRoute } from './domains/telegram-kind-route.js';
15
16
  export { DEFAULT_KIND_ROLES, roleForKind } from './domains/telegram-kind-route.js';
16
17
  import { resolveChannelBotToken } from './channel-bot-token.js';
17
18
  import type { UserConfig } from './user-config.js';
18
19
  import { findSessionByTelegramChat, createSession, appendMessage } from './session/index.js';
20
+ import { debug } from './debug/log.js';
21
+ // Observations go through debug.log only: a standalone caller (cron morning-report) registers its own
22
+ // logs.db sink at its entry point (registerStandaloneLogSink) — registering here would duplicate every
23
+ // row inside the daemon, which already has one (review r4).
24
+ function logReport(event: 'sent' | 'unconfirmed' | 'unrouted', data: Record<string, unknown>): void {
25
+ try { debug.log('telegram.report', event, data); } catch { /* fail-soft */ }
26
+ }
27
+
28
+ /** Public numeric bot id only — never the token's secret suffix. */
29
+ function botId(token: string): string {
30
+ const id = token.split(':', 1)[0] ?? '';
31
+ return /^\d+$/.test(id) ? id : 'configured';
32
+ }
19
33
 
20
34
  /** Cross-surface memory — mirror an outbound report/alert into the
21
35
  * RECEIVING channel's session transcript so a follow-up question in that
@@ -95,14 +109,24 @@ export async function sendTelegramReport(
95
109
  opts: SendReportOpts = {},
96
110
  ): Promise<boolean> {
97
111
  const target = resolveReportTarget(cfg, opts.kind);
98
- if (!target || !text) return false;
112
+ if (!text) return false;
113
+ if (!target) {
114
+ const why = explainReportRoute(cfg, opts.kind);
115
+ logReport('unrouted', { kind: opts.kind ?? null, role: why.role, reason: why.reason });
116
+ return false;
117
+ }
99
118
  const bot = new TelegramBot({
100
119
  token: target.botToken,
101
120
  allowedUsers: [],
102
121
  onMessage: async () => undefined,
103
122
  ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
104
123
  });
105
- await bot.sendMessage(target.chatId, text, { markdown: opts.markdown ?? true });
124
+ const sent = await bot.sendMessage(target.chatId, text, { markdown: opts.markdown ?? true });
125
+ // Delivered only with Telegram's message id back — an empty answer is unconfirmed and returns false (review r4).
126
+ const confirmed = Number.isSafeInteger(sent?.messageId) && (sent?.messageId ?? 0) > 0;
127
+ logReport(confirmed ? 'sent' : 'unconfirmed',
128
+ { kind: opts.kind ?? null, role: explainReportRoute(cfg, opts.kind).role, bot: botId(target.botToken), chars: text.length });
129
+ if (!confirmed) return false;
106
130
  // Mirror into the report channel's session so a follow-up question in
107
131
  // that chat can recall this alert (Fix — was surface_events-only).
108
132
  mirrorOutboundToSession(target, text);
@@ -195,6 +195,7 @@ export class DashboardSession {
195
195
  const pair = createInProcessTransportPair();
196
196
  const shutdownCtrl = new AbortController();
197
197
  const runTurn = bridgeCoreTurnToAcp({
198
+ tuiRouting: true,
198
199
  getMessages: opts.getMessages,
199
200
  getTools: opts.getTools,
200
201
  dispatchTool: opts.dispatchTool,