elanous 0.2.31 → 0.2.32

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 (282) 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/chunks/{1834-50625904996a8fa8.js → 1834-98b64172bc1efe77.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3535a4a69f1fafaa.js → page-a9717a67c2eecbaf.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5a503541353e60a8.js → page-c0f1ca371f710b06.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/chat/{page-6025101384e361a5.js → page-46430d45a9e04eac.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/control/{page-a645f00a06483e9a.js → page-a27b1e5a0d1b308f.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-e9bab7413b028b28.js → page-8ea49a4c6dd2c6e7.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-e547e6ce55f1fb33.js → page-0e5a41fac1c75396.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-d27e1b9850fd54bf.js → page-31aaf1ed37c6df9a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-0fec29c7ba01894b.js → page-c69247e7134d329d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-339a07410cd6f36b.js → layout-66dc5cd3a0999abe.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/loops/{page-2fd93ed8c9f16580.js → page-d256f5d5d9552823.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/market/{page-9b564dbff0fefc35.js → page-ce8f1907b8a6101c.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/morning/{page-814ba51385225776.js → page-32866485913d6a04.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/{not-found-397d479626e5a459.js → not-found-7607cca9b84950c6.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-af1bba4e3251b728.js → page-eeab86176b03dc3b.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-3ad3be5e7d3fdce9.js → page-5b93dbae3432b996.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4da93ad35d4a8d28.js → page-f47e768b5b242b5a.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-3a658d5fa6d16a25.js → page-95306f892aa2d0ee.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-0157de6e85f8bb6b.js → page-940fb6c217f6b470.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0a07363b130182a1.js → page-8c66a3b24a745630.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/settings/{page-841d73d4ac44ed49.js → page-7b675dfd35c36fc9.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-6f43a3d1146e6377.js → page-8bd0d3cf7665bcfc.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/setup/{page-521ee23ac8c0abfa.js → page-c219f2ed171131be.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/share/{page-a9992ee285efaa83.js → page-40f8fa21bdae955b.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-3d48e85fa89ec04f.js → page-468f8313e4f1fbd9.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/term/page-04d00ae9b0890349.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/today/{page-233c71cd5f68f0d7.js → page-4214b855d9233ea8.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/trace/{page-70151d78de2f375e.js → page-936752c7aa58762e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/vault/{page-dcac33a3e2cb72c0.js → page-ec052ad359df1797.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0448145ccd5c45c1.js → page-fad650e4793adb65.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +1 -0
  54. package/apps/pwa/out/_next/static/css/b5d88eba78a3d7bf.css +1 -0
  55. package/apps/pwa/out/approvals/index.html +1 -1
  56. package/apps/pwa/out/approvals/index.txt +14 -14
  57. package/apps/pwa/out/autopilot/index.html +1 -1
  58. package/apps/pwa/out/autopilot/index.txt +14 -14
  59. package/apps/pwa/out/board/index.html +1 -1
  60. package/apps/pwa/out/board/index.txt +14 -14
  61. package/apps/pwa/out/botlab/index.html +1 -1
  62. package/apps/pwa/out/botlab/index.txt +14 -14
  63. package/apps/pwa/out/bots/index.html +1 -1
  64. package/apps/pwa/out/bots/index.txt +14 -14
  65. package/apps/pwa/out/ceo/index.html +1 -1
  66. package/apps/pwa/out/ceo/index.txt +14 -14
  67. package/apps/pwa/out/chat/index.html +1 -1
  68. package/apps/pwa/out/chat/index.txt +14 -14
  69. package/apps/pwa/out/consult/index.html +1 -1
  70. package/apps/pwa/out/consult/index.txt +14 -14
  71. package/apps/pwa/out/control/index.html +1 -1
  72. package/apps/pwa/out/control/index.txt +14 -14
  73. package/apps/pwa/out/decisions/index.html +1 -1
  74. package/apps/pwa/out/decisions/index.txt +14 -14
  75. package/apps/pwa/out/design-check/index.html +1 -1
  76. package/apps/pwa/out/design-check/index.txt +14 -14
  77. package/apps/pwa/out/editor/index.html +1 -1
  78. package/apps/pwa/out/editor/index.txt +14 -14
  79. package/apps/pwa/out/exec/index.html +1 -1
  80. package/apps/pwa/out/exec/index.txt +14 -14
  81. package/apps/pwa/out/field/index.html +1 -1
  82. package/apps/pwa/out/field/index.txt +14 -14
  83. package/apps/pwa/out/healthz +1 -1
  84. package/apps/pwa/out/index.html +1 -1
  85. package/apps/pwa/out/index.txt +14 -14
  86. package/apps/pwa/out/inside/index.html +1 -1
  87. package/apps/pwa/out/inside/index.txt +14 -14
  88. package/apps/pwa/out/intake/index.html +1 -1
  89. package/apps/pwa/out/intake/index.txt +14 -14
  90. package/apps/pwa/out/live/index.html +1 -1
  91. package/apps/pwa/out/live/index.txt +17 -14
  92. package/apps/pwa/out/live-run/index.html +1 -1
  93. package/apps/pwa/out/live-run/index.txt +14 -14
  94. package/apps/pwa/out/loops/index.html +1 -1
  95. package/apps/pwa/out/loops/index.txt +14 -14
  96. package/apps/pwa/out/market/index.html +1 -1
  97. package/apps/pwa/out/market/index.txt +14 -14
  98. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  99. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  100. package/apps/pwa/out/morning/index.html +1 -1
  101. package/apps/pwa/out/morning/index.txt +14 -14
  102. package/apps/pwa/out/observatory/index.html +1 -1
  103. package/apps/pwa/out/observatory/index.txt +14 -14
  104. package/apps/pwa/out/ops/checklist/index.html +1 -1
  105. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  106. package/apps/pwa/out/ops/release/index.html +1 -1
  107. package/apps/pwa/out/ops/release/index.txt +14 -14
  108. package/apps/pwa/out/ops/seats/index.html +1 -1
  109. package/apps/pwa/out/ops/seats/index.txt +14 -14
  110. package/apps/pwa/out/outputs/index.html +1 -1
  111. package/apps/pwa/out/outputs/index.txt +14 -14
  112. package/apps/pwa/out/reflection/index.html +1 -1
  113. package/apps/pwa/out/reflection/index.txt +15 -15
  114. package/apps/pwa/out/scheduler/index.html +1 -1
  115. package/apps/pwa/out/scheduler/index.txt +14 -14
  116. package/apps/pwa/out/sessions/index.html +1 -1
  117. package/apps/pwa/out/sessions/index.txt +14 -14
  118. package/apps/pwa/out/settings/devices/index.html +1 -1
  119. package/apps/pwa/out/settings/devices/index.txt +14 -14
  120. package/apps/pwa/out/settings/index.html +1 -1
  121. package/apps/pwa/out/settings/index.txt +14 -14
  122. package/apps/pwa/out/setup/done/index.html +1 -1
  123. package/apps/pwa/out/setup/done/index.txt +14 -14
  124. package/apps/pwa/out/setup/index.html +1 -1
  125. package/apps/pwa/out/setup/index.txt +14 -14
  126. package/apps/pwa/out/share/index.html +1 -1
  127. package/apps/pwa/out/share/index.txt +14 -14
  128. package/apps/pwa/out/showroom/index.html +1 -1
  129. package/apps/pwa/out/showroom/index.txt +14 -14
  130. package/apps/pwa/out/tasks/index.html +1 -1
  131. package/apps/pwa/out/tasks/index.txt +14 -14
  132. package/apps/pwa/out/term/index.html +1 -1
  133. package/apps/pwa/out/term/index.txt +14 -14
  134. package/apps/pwa/out/today/index.html +1 -1
  135. package/apps/pwa/out/today/index.txt +14 -14
  136. package/apps/pwa/out/trace/index.html +1 -1
  137. package/apps/pwa/out/trace/index.txt +14 -14
  138. package/apps/pwa/out/vault/index.html +1 -1
  139. package/apps/pwa/out/vault/index.txt +14 -14
  140. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  141. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  142. package/apps/pwa/out/workflows/index.html +1 -1
  143. package/apps/pwa/out/workflows/index.txt +14 -14
  144. package/apps/pwa/out/workspace/index.html +1 -1
  145. package/apps/pwa/out/workspace/index.txt +14 -14
  146. package/apps/pwa/out/worktrees/index.html +1 -1
  147. package/apps/pwa/out/worktrees/index.txt +14 -14
  148. package/graphs/implement-loop.yaml +5 -0
  149. package/package.json +1 -1
  150. package/scripts/ci-typecheck-changed.ts +26 -1
  151. package/scripts/measure-si-task-cpu.ts +143 -0
  152. package/scripts/ratchets/test-interference-patterns.allowlist.txt +0 -2
  153. package/scripts/release-loop/prepare-node.ts +102 -4
  154. package/src/acp/core-turn-bridge.ts +16 -4
  155. package/src/acp/elanous-extensions.ts +3 -0
  156. package/src/acp/server.ts +3 -0
  157. package/src/acp/tool-trace.ts +186 -11
  158. package/src/agent/self-ambient-cache.ts +156 -0
  159. package/src/agent/self-ambient.ts +8 -1
  160. package/src/agent/task-notification.ts +5 -0
  161. package/src/agent-substrate/pr-reviewer.ts +3 -1
  162. package/src/ask-user-question/tool.ts +22 -0
  163. package/src/autopilot/build/llm-conflict-merge.ts +56 -10
  164. package/src/autopilot/pr-manager.ts +23 -5
  165. package/src/capture/frame-state-detect.ts +39 -0
  166. package/src/capture/tui-self-report.ts +134 -6
  167. package/src/chat/index.ts +61 -15
  168. package/src/chat/pickers/modal-runtime.ts +6 -0
  169. package/src/chat/pickers/modals.ts +8 -2
  170. package/src/cli/ask-bare.ts +1 -0
  171. package/src/cli/import-cli.ts +12 -0
  172. package/src/cli/log-event-names.ts +10 -0
  173. package/src/cli/msg-cli.ts +1 -0
  174. package/src/cli/pod-cli.ts +14 -3
  175. package/src/cli/pr-cli.ts +67 -32
  176. package/src/cli/pr-land-base-read.ts +34 -0
  177. package/src/cli/pr-land-heavy-remote.ts +81 -25
  178. package/src/cli/pr-land-overlap-evidence.ts +46 -2
  179. package/src/cli/pr-land-pod-shadow-job.ts +26 -3
  180. package/src/cli/pr-land-refusal.ts +20 -5
  181. package/src/cli/pty-takeover-cli.ts +88 -14
  182. package/src/cli/sim-cli.ts +18 -0
  183. package/src/context-bus/ops-events.ts +6 -2
  184. package/src/core-turn/run-core-turn.ts +1 -0
  185. package/src/core-turn/run-goal-loop.ts +21 -0
  186. package/src/core-turn/types.ts +3 -0
  187. package/src/dashboard/acp-active-model.ts +42 -1
  188. package/src/dashboard/background-jobs-status.ts +66 -0
  189. package/src/dashboard/index.ts +117 -44
  190. package/src/dashboard/input/agent-wake.ts +242 -0
  191. package/src/dashboard/input/chat-main-entry-runtime.ts +5 -0
  192. package/src/dashboard/input/external-input-host.ts +23 -9
  193. package/src/dashboard/input/mouse-wiring.ts +108 -11
  194. package/src/dashboard/log-click-runtime.ts +118 -2
  195. package/src/dashboard/turn-preamble.ts +116 -40
  196. package/src/dashboard/turn-session-layers.ts +4 -2
  197. package/src/dashboard/turn-start-segments.ts +222 -0
  198. package/src/display/prompt-row-owner.ts +146 -0
  199. package/src/display/resize-listener.ts +33 -2
  200. package/src/fold-stack.ts +4 -2
  201. package/src/git-fs/index.ts +263 -36
  202. package/src/harness/gate-reserve.ts +87 -0
  203. package/src/harness/harness-incidents.ts +13 -0
  204. package/src/harness/harness-queue.ts +15 -0
  205. package/src/index.ts +8 -6
  206. package/src/input/external-input-queue.ts +34 -9
  207. package/src/llm/route-decision.ts +36 -0
  208. package/src/llm/tool-loop-steered-rejection.ts +8 -0
  209. package/src/llm-request-shape.ts +209 -0
  210. package/src/llm.ts +166 -267
  211. package/src/loops/orchestrator/tick.ts +24 -2
  212. package/src/maturity/feature-maturity.ts +1 -1
  213. package/src/mss/logging/render-categories.ts +5 -0
  214. package/src/ops/overview.ts +59 -2
  215. package/src/pr-tool/census.ts +19 -4
  216. package/src/pr-tool/inflight-registry.ts +143 -0
  217. package/src/pty-shell/pty-manifest.ts +12 -3
  218. package/src/pty-shell/tui-run-state.ts +34 -3
  219. package/src/seat-loop/traffic-signal.ts +2 -1
  220. package/src/self-dev/ask-launch-flow.ts +13 -0
  221. package/src/self-dev/draft-residue.ts +243 -24
  222. package/src/self-dev/shard-plan.ts +11 -3
  223. package/src/self-dev/shard-run.ts +103 -10
  224. package/src/self-implement/elanous-tui-spawn.ts +14 -0
  225. package/src/self-implement/follow-up-placement.ts +79 -0
  226. package/src/self-implement/gate-artifacts.ts +15 -4
  227. package/src/self-implement/gate-baseline.ts +96 -5
  228. package/src/self-implement/gate-cli.ts +7 -2
  229. package/src/self-implement/gate-remote.ts +26 -1
  230. package/src/self-implement/host-regate.ts +36 -4
  231. package/src/self-implement/orchestrator.ts +101 -11
  232. package/src/self-implement/run-ledger.ts +16 -14
  233. package/src/session-runtime/index.ts +22 -267
  234. package/src/shell-runner/background-surface.ts +15 -5
  235. package/src/skills/tools/agent.ts +39 -6
  236. package/src/skills/tools/list-dir.ts +11 -0
  237. package/src/skills/tools/schedule-wakeup.ts +408 -0
  238. package/src/skills/tools/shell.ts +245 -3
  239. package/src/status/bar.ts +26 -0
  240. package/src/steward/digest-delivery.ts +118 -0
  241. package/src/steward/hold-hygiene.ts +120 -0
  242. package/src/steward/triage.ts +10 -0
  243. package/src/surface/shell-runner-boot.ts +5 -2
  244. package/src/task-agent/task-hand.ts +24 -0
  245. package/src/task-orchestrator/surfaces/pod-command-job.ts +65 -11
  246. package/src/task-orchestrator/surfaces/pod-lease.ts +98 -0
  247. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +1 -1
  248. package/src/task-orchestrator/surfaces/pod-pool.ts +3 -1
  249. package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +10 -6
  250. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +525 -0
  251. package/src/task-orchestrator/surfaces/pod-warm-worker-runner.ts +90 -0
  252. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +156 -2
  253. package/src/task-orchestrator/surfaces/self-implement-pod.ts +7 -5
  254. package/src/tui/chat/execution-badge.ts +5 -1
  255. package/src/tui/input-lag-probe.ts +230 -0
  256. package/src/tui.ts +4 -0
  257. package/src/ux-sim/scenario.ts +255 -0
  258. package/src/version/packed-revision.json +1 -1
  259. package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +0 -1
  260. package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +0 -1
  261. package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +0 -1
  262. package/apps/pwa/out/_next/static/chunks/app/botlab/page-2ebb8fd59b9461f9.js +0 -1
  263. package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +0 -1
  264. package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +0 -1
  265. package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +0 -1
  266. package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +0 -1
  267. package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +0 -1
  268. package/apps/pwa/out/_next/static/chunks/app/live/page-349f2752d8cd37ad.js +0 -1
  269. package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +0 -1
  270. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +0 -1
  271. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +0 -1
  272. package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +0 -1
  273. package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +0 -1
  274. package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +0 -1
  275. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +0 -1
  276. package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +0 -1
  277. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-149dc1caba411bb9.js +0 -1
  278. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +0 -1
  280. package/apps/pwa/out/_next/static/css/3f5fc6f96e2cf7b2.css +0 -1
  281. /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_buildManifest.js +0 -0
  282. /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_ssgManifest.js +0 -0
@@ -39,10 +39,11 @@ import { createTuiScreenMirror } from './tui-screen-mirror.js';
39
39
  import { getChannelBus } from '../terminal-matrix/index.js';
40
40
  import { publishSelfReportFrame } from './self-report-frame.js';
41
41
  import {
42
- upsertPtyManifest, updatePtyManifestFrame, touchLivePtyManifest, markPtyManifestClosed, removePtyManifest,
42
+ upsertPtyManifest, updatePtyManifestFrame, touchLivePtyManifest, markPtyManifestClosed, removePtyManifest, getPtyManifest,
43
+ updatePtyManifestBuild,
43
44
  } from '../pty-shell/pty-manifest.js';
44
45
  import { writeHarnessScreen } from '../harness/harness-screen.js';
45
- import { startTuiRunStateRecorder } from '../pty-shell/tui-run-state.js';
46
+ import { runningBuild, startTuiRunStateRecorder } from '../pty-shell/tui-run-state.js';
46
47
  import { debug } from '../debug/log.js';
47
48
  import { routeExternalInput, type ExternalInputRoute } from '../input/external-input-queue.js';
48
49
 
@@ -187,6 +188,46 @@ export interface TuiSelfReportDeps {
187
188
  startRunState?: (surfaceId: string) => (() => void);
188
189
  /** 미룬 우편 깨움 폴러(TUI-H4)를 연다 — 반환 = 정리. 기본은 실 행 등록과 짝일 때만 늦게 불러온 `startPendingWakePoller`. */
189
190
  startWakePoller?: (surfaceId: string) => (() => void);
191
+ /** 박동마다 자기 행을 본다 — 없으면 null · ⛔ 조회 실패는 «던진다»(없다로 읽지 않는다). 기본 = `getPtyManifest(id, { strict })`. */
192
+ lookupRow?: (surfaceId: string) => TuiOwnRowView | null;
193
+ /** 재등록 뒤 실행 판(build) 칸을 다시 적는다 — 기본 = 기동 때 판(`runningBuild`). */
194
+ restoreBuild?: (surfaceId: string) => void;
195
+ /** 박동 스케줄러 — 기본 setInterval(unref). 시험이 박동을 손으로 돌린다. */
196
+ driveHeartbeat?: (tick: () => void, ms: number) => (() => void);
197
+ /** SIGCONT 수신 훅(행 상실 단서) — 반환 = 해제. 기본 = process.on('SIGCONT'). */
198
+ onSigcont?: (fn: () => void) => (() => void);
199
+ /** 행 상실 관측 — 기본 = debug.log('tui.self-report', 'row-missing', data). */
200
+ logRowMissing?: (data: TuiRowMissingEvent) => void;
201
+ }
202
+
203
+ /** 박동이 보는 자기 행 — `PtyManifestRow` 의 부분(재등록 뒤 frame·build 복원 확인에도 쓴다). */
204
+ export interface TuiOwnRowView {
205
+ readonly alive: boolean;
206
+ readonly frameAt?: number;
207
+ readonly build?: unknown;
208
+ }
209
+
210
+ /** `tui.self-report` · `row-missing` 한 줄 — 살아 있는 TUI 의 manifest 행이 사라졌을 때(재등록 시도 결과 ⊕ 원인 단서). */
211
+ export interface TuiRowMissingEvent {
212
+ readonly surfaceId: string;
213
+ readonly pid: number;
214
+ /** 'absent' = 행이 없다 · 'closed' = 행은 있는데 alive=0 · 'lookup-failed' = 조회 자체가 실패(재등록 안 함). */
215
+ readonly state: 'absent' | 'closed' | 'lookup-failed';
216
+ /** 마지막으로 자기 행을 «본» 시각(등록·재등록 포함). */
217
+ readonly lastSeenAt: number;
218
+ readonly sinceMs: number;
219
+ /** 직전 박동과의 간격 — 박동 주기보다 훨씬 크면 그 사이 프로세스가 멈췄다(SIGSTOP·SIGTTIN·이벤트 루프 정체). */
220
+ readonly gapMs: number;
221
+ readonly heartbeatMs: number;
222
+ /** 마지막 SIGCONT 시각(없으면 null) — `lastSeenAt` 뒤면 «정지 → 재개» 사이에 걷혔다는 단서. */
223
+ readonly sigcontAt: number | null;
224
+ readonly sigcontSinceLastSeen: boolean;
225
+ readonly reRegistered: boolean;
226
+ /** 재등록이 비운 frame 칸에 마지막 화면을 다시 적었나(`pty snapshot` 이 바로 닿는다). */
227
+ readonly frameRestored: boolean;
228
+ /** 재등록이 비운 실행 판(build) 칸을 다시 적었나(다시 읽어 확인). */
229
+ readonly buildRestored: boolean;
230
+ readonly error?: string;
190
231
  }
191
232
 
192
233
  export type TuiControlInitStage = 'create-target' | 'register-target' | 'start-poller' | 'publish-manifest';
@@ -292,8 +333,11 @@ export function startTuiSelfReport(deps: TuiSelfReportDeps = {}): () => void {
292
333
  // The manifest row is published only after both input capabilities are
293
334
  // live. A listed TUI therefore always has a registered target and poller.
294
335
 
336
+ // 마지막으로 내보낸 화면 — 재등록이 frame 칸을 비우면 다시 적는다(거울은 바뀐 화면만 내보내 idle 이면 영영 빈 칸).
337
+ let lastEmittedText: string | null = null;
295
338
  const emitText = (text: string, cols: number, rows: number): void => {
296
339
  const at = now();
340
+ lastEmittedText = text;
297
341
  const frame: SelfReportFrame = { surfaceId, instance, kind: 'tui', mode: 'self-report', text, cols, rows, at, ...(runId ? { runId } : {}) };
298
342
  try { publishFrame(frame); } catch { /* fail-soft */ }
299
343
  // `at` is shared with the manifest write → its internal throttle keys
@@ -340,16 +384,100 @@ export function startTuiSelfReport(deps: TuiSelfReportDeps = {}): () => void {
340
384
  }
341
385
 
342
386
  // Heartbeat so a live-but-idle TUI isn't culled by reapStale.
343
- const hb = setInterval(() => { try { heartbeat(surfaceId, now()); } catch { /* fail-soft */ } }, heartbeatMs);
344
- (hb as { unref?: () => void }).unref?.();
345
-
387
+ // ⭐ 박동마다 자기 행을 본다 — 남이 걷어 간 행(정지 중 stale 수거 등)을 같은 박동에서 다시 세운다.
388
+ // 행이 없으면 pty text/key/snapshot·우편 깨움이 이 TUI 에 영영 안 닿는다(#75 · #84).
389
+ // 행 등록을 갈아 끼운 호출자(시험)는 실 manifest 를 안 읽는다 — 기본 조회는 기본 행 등록과 짝일 때만.
390
+ const lookupRow: (id: string) => TuiOwnRowView | null = deps.lookupRow
391
+ ?? (deps.registerRow ? () => ({ alive: true }) : (id: string) => getPtyManifest(id, { strict: true }));
392
+ const restoreBuild = deps.restoreBuild ?? (deps.registerRow ? () => {} : (id: string) => { updatePtyManifestBuild(id, runningBuild()); });
393
+ const logRowMissing = deps.logRowMissing ?? ((data: TuiRowMissingEvent) => debug.log('tui.self-report', 'row-missing', data));
394
+ const rowNickname = deps.nickname?.trim() || undefined;
395
+ let lastSeenAt = now();
396
+ let lastBeatAt = lastSeenAt;
397
+ let sigcontAt: number | null = null;
398
+ let stopSigcont: () => void = () => {};
399
+ try {
400
+ stopSigcont = (deps.onSigcont ?? ((fn) => {
401
+ process.on('SIGCONT', fn);
402
+ return () => { process.off('SIGCONT', fn); };
403
+ }))(() => { sigcontAt = now(); });
404
+ } catch { /* fail-soft — 단서일 뿐 */ }
346
405
  let stopped = false;
406
+ const beat = (): void => {
407
+ if (stopped) return;
408
+ const at = now();
409
+ const prevSeenAt = lastSeenAt;
410
+ const gapMs = at - lastBeatAt;
411
+ lastBeatAt = at;
412
+ let state: TuiRowMissingEvent['state'] | null = null;
413
+ let error: string | undefined;
414
+ try {
415
+ const row = lookupRow(surfaceId);
416
+ if (!row) state = 'absent';
417
+ else if (!row.alive) state = 'closed';
418
+ } catch (e) {
419
+ state = 'lookup-failed';
420
+ error = e instanceof Error ? e.message : String(e);
421
+ }
422
+ if (state === null) {
423
+ lastSeenAt = at;
424
+ try { heartbeat(surfaceId, at); } catch { /* fail-soft */ }
425
+ return;
426
+ }
427
+ let reRegistered = false;
428
+ let frameRestored = false;
429
+ let buildRestored = false;
430
+ if (state === 'lookup-failed') {
431
+ // ⛔ 실패를 «없다»로 읽지 않는다 — 재등록 없이 평소 박동만.
432
+ try { heartbeat(surfaceId, at); } catch { /* fail-soft */ }
433
+ } else {
434
+ try {
435
+ registerRow(surfaceId, at, rowNickname);
436
+ // 기본 행 등록은 fail-soft(조용히 삼킨다) — «다시 세웠다» 는 다시 읽어서만 말한다.
437
+ reRegistered = lookupRow(surfaceId)?.alive === true;
438
+ if (!reRegistered) throw new Error('row-still-missing-after-register');
439
+ lastSeenAt = at;
440
+ // 재등록은 실행 판·턴 칸을 비운다 — 판은 기동 때 값으로 다시 적는다. 턴은 비운 채(unknown) 둔다:
441
+ // 녹음기를 다시 열면 «idle» 을 적는데, 턴 도중이면 거짓 idle 이 깨움을 끼어들게 한다. 다음 턴 변화가 채운다.
442
+ const restoreErrors: string[] = [];
443
+ try { restoreBuild(surfaceId); } catch (e) { restoreErrors.push(`build: ${e instanceof Error ? e.message : String(e)}`); }
444
+ if (lastEmittedText !== null) {
445
+ try { writeFrame(surfaceId, lastEmittedText, at); } catch (e) { restoreErrors.push(`frame: ${e instanceof Error ? e.message : String(e)}`); }
446
+ }
447
+ // 기본 쓰기도 fail-soft — «복원했다» 는 다시 읽어서만 말한다.
448
+ try {
449
+ const after = lookupRow(surfaceId);
450
+ frameRestored = lastEmittedText !== null && (after?.frameAt ?? 0) >= at;
451
+ buildRestored = after?.build != null;
452
+ } catch (e) { restoreErrors.push(`verify: ${e instanceof Error ? e.message : String(e)}`); }
453
+ if (restoreErrors.length) error = restoreErrors.join('; ');
454
+ } catch (e) {
455
+ error = e instanceof Error ? e.message : String(e);
456
+ }
457
+ }
458
+ try {
459
+ logRowMissing({
460
+ surfaceId, pid: process.pid, state, lastSeenAt: prevSeenAt, sinceMs: at - prevSeenAt, gapMs, heartbeatMs,
461
+ sigcontAt, sigcontSinceLastSeen: sigcontAt !== null && sigcontAt >= prevSeenAt,
462
+ reRegistered, frameRestored, buildRestored, ...(error ? { error } : {}),
463
+ });
464
+ } catch { /* fail-soft diagnostic */ }
465
+ };
466
+ const stopHeartbeat = (deps.driveHeartbeat ?? ((tick, ms) => {
467
+ // setImmediate 로 한 칸 미룬다 — 정지(SIGSTOP·SIGTTIN)에서 풀리면 밀린 타이머가 SIGCONT 처리기보다 «먼저» 돈다
468
+ // (WSL bun 실측). 미뤄야 같은 박동의 row-missing 이 sigcontAt 을 본다.
469
+ const t = setInterval(() => { setImmediate(tick); }, ms);
470
+ (t as { unref?: () => void }).unref?.();
471
+ return () => clearInterval(t);
472
+ }))(beat, heartbeatMs);
473
+
347
474
  const stop = (): void => {
348
475
  if (stopped) return;
349
476
  stopped = true;
350
477
  try { stopSource(); } catch { /* fail-soft — unregisters observer / clears tick timer */ }
351
478
  try { mirror?.stop(); } catch { /* fail-soft — restores stdout.write */ }
352
- clearInterval(hb);
479
+ try { stopHeartbeat(); } catch { /* fail-soft */ }
480
+ try { stopSigcont(); } catch { /* fail-soft */ }
353
481
  try { stopRunState(); } catch { /* fail-soft */ }
354
482
  wakePollerStopped = true;
355
483
  try { stopWakePoller(); } catch { /* fail-soft */ }
package/src/chat/index.ts CHANGED
@@ -10,6 +10,7 @@ import type { ChatPickerKind } from './pickers/kinds.js';
10
10
  import { createPickerState, type BufferAction, type PickerBufferView } from './pickers/state.js';
11
11
  import { createChatPickerModalRuntime } from './pickers/modal-runtime.js';
12
12
  import type { ModalSurface } from '../display/modal-stack.js';
13
+ import type { PromptRowOwner } from '../display/prompt-row-owner.js';
13
14
  // Type-only import: avoids the runtime circular dep that streamGrok
14
15
  // below sidesteps with a dynamic `await import('../llm.js')`. Keeping
15
16
  // ChatMessage structurally compatible with LLMMessage lets the
@@ -35,6 +36,7 @@ import {
35
36
  } from '../input-core/index.js';
36
37
  import { applyInlineEditorKey } from '../input-core/inline-editor-key.js';
37
38
  import type { InputSourceRef } from '../input/input-source-kind.js';
39
+ import { routeRawPtyStop } from '../input/external-input-queue.js';
38
40
  import {
39
41
  multilineEditorBackspace,
40
42
  multilineEditorInsertLineBreak,
@@ -70,6 +72,7 @@ export interface ModalSink {
70
72
  * DisplayCoordinator that satisfies both. */
71
73
  export interface DisplaySink extends CursorSink, ModalSink {}
72
74
  import type { Key } from '../tui.js';
75
+ import { noteInputLagKey, noteInputLagPaint } from '../tui/input-lag-probe.js';
73
76
  import {
74
77
  createSpinner,
75
78
  createToolLoader,
@@ -173,6 +176,8 @@ function writeTerminal(chunk: string): void {
173
176
  process.stdout.write(chunk);
174
177
  // viaCoordinator=false — this is the bypass path /perf highlights.
175
178
  perf.recordStdoutWrite(chunk.length, false);
179
+ // 입력칸 키 echo 는 render() 가 아니라 이 직접 쓰기로 칠해진다 — 키→칠 관측의 칠 지점.
180
+ noteInputLagPaint();
176
181
  }
177
182
 
178
183
  /** textInput reports the current prompt's top row so writeTerminal
@@ -324,11 +329,16 @@ export function createTextInputRenderActions(opts: {
324
329
  * coordinator-owned cursor sinks re-show it at the final position. */
325
330
  hideCursorBeforePaint?: boolean;
326
331
  beforePaint?: () => void;
332
+ /** The frame renderer owns the prompt band (TUI-PROMPT-SINGLE-OWNER):
333
+ * `paint` only publishes, so there are no direct writes to wrap in
334
+ * synchronized output. */
335
+ frameOwned?: boolean;
327
336
  modalSink?: ModalSink;
328
337
  shouldRequestModalRender?: () => boolean;
329
338
  }): TextInputRenderActions {
330
- const paintAll = (synchronizedOutput = false) => {
339
+ const paintAll = (requestSynchronizedOutput = false) => {
331
340
  if (opts.shouldPaint?.() === false) return false;
341
+ const synchronizedOutput = requestSynchronizedOutput && !opts.frameOwned;
332
342
  if (synchronizedOutput) writeTerminal(SYNCHRONIZED_OUTPUT_BEGIN);
333
343
  try {
334
344
  if (opts.beforePaint) {
@@ -837,6 +847,10 @@ export async function textInput(opts: {
837
847
  * input is idle. */
838
848
  insertAtCursor?: (text: string) => void;
839
849
  };
850
+ /** TUI-PROMPT-SINGLE-OWNER — when supplied, the frame renderer owns
851
+ * the prompt band: drawInput publishes its rows/hint/caret here and
852
+ * writes nothing to the prompt rows itself (no direct `\x1b[2K`). */
853
+ promptRowOwner?: PromptRowOwner;
840
854
  /** P2.2.c — sink for cursor placement. When supplied, drawInput
841
855
  * routes its caret-position decision through this instead of
842
856
  * emitting `\\x1b[r;cH` directly. The coordinator owns the
@@ -1194,6 +1208,8 @@ export async function textInput(opts: {
1194
1208
  // from there as lines accumulate. `startOffset` is how many rows
1195
1209
  // up from `row` we start touching — zero when both current and
1196
1210
  // previous drew only one line.
1211
+ const promptRowOwner = opts.promptRowOwner;
1212
+ const ownedRows: string[] = [];
1197
1213
  for (let i = 0; i < rowsToTouch; i++) {
1198
1214
  const r = row - i; // i=0 → bottom row; i=1 → one row up; etc.
1199
1215
  if (r < 1) continue;
@@ -1212,8 +1228,9 @@ export async function textInput(opts: {
1212
1228
  ).display;
1213
1229
 
1214
1230
  const padded = display + ' '.repeat(Math.max(0, rowTextW - visibleWidth(display)));
1215
- writeTerminal(ansi.moveTo(r, col) + '\x1b[2K' + pfx + padded);
1216
- } else {
1231
+ if (promptRowOwner) ownedRows.unshift(pfx + padded);
1232
+ else writeTerminal(ansi.moveTo(r, col) + '\x1b[2K' + pfx + padded);
1233
+ } else if (!promptRowOwner) {
1217
1234
  // This row had content on the previous paint but doesn't anymore
1218
1235
  // (user deleted a line / shrank the input). Clear it so stale
1219
1236
  // text doesn't linger on what is now part of the log / divider
@@ -1223,7 +1240,9 @@ export async function textInput(opts: {
1223
1240
  }
1224
1241
  prevDrawnLines = visibleLines;
1225
1242
 
1226
- if (ctrlCHint) {
1243
+ if (promptRowOwner) {
1244
+ // The frame renders the hint row from the published paint.
1245
+ } else if (ctrlCHint) {
1227
1246
  writeTerminal(ansi.moveTo(row + 2, col) + '\x1b[2K' + C.muted(truncate(ctrlCHint, getWidth())));
1228
1247
  ctrlCHintPainted = true;
1229
1248
  } else if (ctrlCHintPainted) {
@@ -1244,6 +1263,12 @@ export async function textInput(opts: {
1244
1263
  const pfxW = lineIdx === 0 ? visibleWidth(prompt) : contPfxW;
1245
1264
  const { cursorCol } = inputLineWindow(lines[lineIdx]!, colIdx, textWFor(lineIdx));
1246
1265
  const curX = col + pfxW + cursorCol;
1266
+ promptRowOwner?.publish({
1267
+ rows: ownedRows,
1268
+ hint: ctrlCHint ? C.muted(truncate(ctrlCHint, getWidth())) : null,
1269
+ caret: curR >= 1 ? { row: curR, col: curX, visible: true } : null,
1270
+ source: 'text-input',
1271
+ });
1247
1272
  // P2.2.c — coordinator owns the cursor emit when wired; falls
1248
1273
  // back to direct stdout for non-coordinator callers (tests, non-dashboard
1249
1274
  // entry points) so the change is non-breaking.
@@ -1342,19 +1367,28 @@ export async function textInput(opts: {
1342
1367
  const display = prefix + result;
1343
1368
  const padded = display + ' '.repeat(Math.max(0, textW - visibleWidth(display)));
1344
1369
 
1345
- for (let i = 0; i < rowsToTouch; i++) {
1346
- const r = row - i;
1347
- if (r < 1) continue;
1348
- if (i === 0) {
1349
- writeTerminal(ansi.moveTo(searchRow, col) + '\x1b[2K' + promptStr + padded);
1350
- } else {
1351
- writeTerminal(ansi.moveTo(r, col) + '\x1b[2K');
1370
+ const curR = searchRow;
1371
+ const curX = col + promptW + visibleWidth(prefix);
1372
+ if (opts.promptRowOwner) {
1373
+ opts.promptRowOwner.publish({
1374
+ rows: [promptStr + padded],
1375
+ hint: null,
1376
+ caret: { row: curR, col: curX, visible: true },
1377
+ source: 'reverse-search',
1378
+ });
1379
+ } else {
1380
+ for (let i = 0; i < rowsToTouch; i++) {
1381
+ const r = row - i;
1382
+ if (r < 1) continue;
1383
+ if (i === 0) {
1384
+ writeTerminal(ansi.moveTo(searchRow, col) + '\x1b[2K' + promptStr + padded);
1385
+ } else {
1386
+ writeTerminal(ansi.moveTo(r, col) + '\x1b[2K');
1387
+ }
1352
1388
  }
1353
1389
  }
1354
1390
  prevDrawnLines = visibleLines;
1355
1391
 
1356
- const curR = searchRow;
1357
- const curX = col + promptW + visibleWidth(prefix);
1358
1392
  if (opts.cursorSink && (opts.canClaimCursor?.() ?? true)) {
1359
1393
  opts.cursorSink.setCursor({ row: curR, col: curX, visible: true });
1360
1394
  } else if (!opts.cursorSink) {
@@ -1389,11 +1423,16 @@ export async function textInput(opts: {
1389
1423
  syncActivePickerModal();
1390
1424
  drawInput();
1391
1425
  };
1426
+ // A frame-owned prompt band has no direct paint to hide the cursor
1427
+ // for, wrap in synchronized output, or re-assert divider chrome around:
1428
+ // the frame render does all three in its own single write.
1429
+ const frameOwnsPromptBand = opts.promptRowOwner !== undefined;
1392
1430
  const renderActions = createTextInputRenderActions({
1393
1431
  shouldPaint: opts.shouldPaint,
1394
- paintChrome: opts.onPaintChrome,
1432
+ paintChrome: frameOwnsPromptBand ? undefined : opts.onPaintChrome,
1395
1433
  paint: repaintPure,
1396
- beforePaint: () => {
1434
+ frameOwned: frameOwnsPromptBand,
1435
+ beforePaint: frameOwnsPromptBand ? () => {} : () => {
1397
1436
  const chunk = hideTextInputCursor(opts.cursorSink);
1398
1437
  if (chunk) writeTerminal(chunk);
1399
1438
  else if (opts.cursorSink) writeTerminal(paintCursorVisibility(false));
@@ -1407,6 +1446,7 @@ export async function textInput(opts: {
1407
1446
  const finalizeInput = (result: InputResult): InputResult => {
1408
1447
  // 외부 주입 라우터가 «composer 가 안 떠 있다»를 알 수 있게 내린다.
1409
1448
  if (hostControl) hostControl.getBufferText = undefined;
1449
+ opts.promptRowOwner?.clear();
1410
1450
  detachInternalTurnYield(result.yieldedToInternalTurn === true);
1411
1451
  clearPicker();
1412
1452
  writeTerminal(endTextInputCursor(opts.cursorSink));
@@ -1723,6 +1763,7 @@ export async function textInput(opts: {
1723
1763
  // input loop exits cleanly and the dashboard's pane dispatcher
1724
1764
  // picks up the keys.
1725
1765
  if (hostControl) hostControl.getBufferText = undefined;
1766
+ opts.promptRowOwner?.clear();
1726
1767
  detachInternalTurnYield();
1727
1768
  return { text: '', submitted: false, gotoPane: true };
1728
1769
  }
@@ -2066,7 +2107,12 @@ export function attachStreamingKeys(
2066
2107
  const listener = (data: string | Buffer) => {
2067
2108
  clearEscapeTimer();
2068
2109
  deliverInjected();
2110
+ // ⏹ TUI-STOP-RAW-PTY — 원시 pty 길로 한 청크에 온 `/stop⏎`·`/esc⏎` 는 typeahead 대기열 대신 지금 턴의 escGate 로.
2111
+ // 파서가 이스케이프·붙여넣기·UTF-8 조각을 쥐고 있으면 그 청크는 앞 입력의 이어짐이라 건드리지 않는다.
2112
+ if (!parser.hasPendingEscape() && !parser.hasPendingPaste() && !parser.hasPendingDecoderBytes()
2113
+ && routeRawPtyStop(typeof data === 'string' ? data : data.toString('utf8'))) return;
2069
2114
  const keys = parser.push(data);
2115
+ if (keys.length > 0) noteInputLagKey(keys.length);
2070
2116
  deliver(keys);
2071
2117
  // ⚠️ `unref()` — 이 타이머가 프로세스를 붙잡아 두면 안 된다(입력 대기는 stdin 의 몫).
2072
2118
  if (parser.hasPendingEscape()) {
@@ -146,6 +146,12 @@ export function createChatPickerModalRuntime(deps: ChatPickerModalRuntimeDeps):
146
146
  getInputZoneHeight: deps.getInputZoneHeight,
147
147
  width: deps.getBounds().width,
148
148
  getWidth: () => deps.getBounds().width,
149
+ // TUI-PICKER-RESIZE-SHRINK — follow the live prompt row, not the
150
+ // row captured when the family was created (stale after resize).
151
+ getAnchor: () => {
152
+ const b = deps.getBounds();
153
+ return { row: b.row, col: b.col };
154
+ },
149
155
  onKey: makePickerOnKey(kind),
150
156
  onRowClick: makePickerOnRowClick(kind),
151
157
  });
@@ -108,6 +108,10 @@ export interface ChatPickerModalBindings {
108
108
  getInputZoneHeight: () => number;
109
109
  width: number;
110
110
  getWidth?: () => number;
111
+ /** Live prompt anchor (input bottom row + col). When present it wins
112
+ * over the mount-time `bounds` snapshot, so a terminal resize moves
113
+ * an already-open picker with the prompt (TUI-PICKER-RESIZE-SHRINK). */
114
+ getAnchor?: () => { row: number; col: number };
111
115
  onKey?: PickerOnKey;
112
116
  onRowClick?: PickerOnRowClick;
113
117
  }
@@ -124,6 +128,7 @@ type FramedPickerSpec<T> = {
124
128
  getInputZoneHeight: () => number;
125
129
  width: number;
126
130
  getWidth?: () => number;
131
+ getAnchor?: () => { row: number; col: number };
127
132
  onKey?: PickerOnKey;
128
133
  onRowClick?: PickerOnRowClick;
129
134
  };
@@ -132,7 +137,8 @@ function createFramedPickerModal<T>(spec: FramedPickerSpec<T>): ModalSurface {
132
137
  const computeBounds = (): (ModalBounds & { startIdx: number; visibleCount: number }) | null => {
133
138
  const rows = spec.getRows();
134
139
  const inputZoneHeight = spec.getInputZoneHeight();
135
- const bottomRow = spec.bounds.row - inputZoneHeight;
140
+ const anchor = spec.getAnchor?.() ?? spec.bounds;
141
+ const bottomRow = anchor.row - inputZoneHeight;
136
142
  if (rows.length === 0 || bottomRow < 4) return null;
137
143
  const { visibleCount, startIdx } = pickerWindow(
138
144
  rows.length,
@@ -142,7 +148,7 @@ function createFramedPickerModal<T>(spec: FramedPickerSpec<T>): ModalSurface {
142
148
  if (visibleCount <= 0) return null;
143
149
  const height = visibleCount + 3;
144
150
  const row = bottomRow - height + 1;
145
- return { row, col: spec.bounds.col, width: spec.getWidth?.() ?? spec.width, height, startIdx, visibleCount };
151
+ return { row, col: anchor.col, width: spec.getWidth?.() ?? spec.width, height, startIdx, visibleCount };
146
152
  };
147
153
 
148
154
  /** Phase 4.5a (substrate Occam · §4-pre.7 · 2026-05-03) — update
@@ -37,6 +37,7 @@ export async function askBare({ text, provider, model, deps }: {
37
37
  ...(provider === undefined ? { initialProvider: chosenProvider } : { provider: chosenProvider }),
38
38
  ...(selected.model === '(none)' ? {} : { model: selected.model }),
39
39
  onResolvedProvider: (name, activeModel) => { resolvedProvider = name; resolvedModel = activeModel; },
40
+ usageSite: 'cli.ask-bare',
40
41
  });
41
42
  return { provider: resolvedProvider, model: resolvedModel, reply };
42
43
  }
@@ -21,8 +21,20 @@ export async function buildImportPlan(home = homedir()): Promise<ImportPlan> {
21
21
  }
22
22
 
23
23
  export function registerImportCommand(program: Command): void {
24
+ const publicContributionHelp = `
25
+ Public contribution (EC1 · 10-28 update; moved from 0.2.8 in the 10-01 release split):
26
+ Show HN / community: share a working import example and feedback; posting and replies remain with the human author.
27
+ CB1 contribution entry: open an issue or pull request at https://github.com/ElanvitalAI/elanous
28
+ Marketplace plugins: propose a plugin in plugins/ via pull request; inclusion in the signed marketplace is reviewed, not automatic.
29
+ Remaining drafts (not published): Show HN · 긱뉴스 · 3-minute install-to-first-result video
30
+ docs/marketing/DRAFT-ec1-show-hn-geeknews-and-3min-video-2026-10-01.md
31
+ Measure adoption separately: GitHub stars · installs · npm downloads (downloads are not unique users).
32
+ Historical positioning (§4, 2026-10-01; not current counts): elanous ★0 · OpenClaw ~390k · Hermes ~250k · Codex ~127k.
33
+ 10-03 baseline (not current results): stars 0 · npm 7-day downloads 790 (includes our own installs).
34
+ `;
24
35
  program.command('import')
25
36
  .description('Show what elanous can bring over from Claude Code, Codex, ~/.agents, OpenClaw and Hermes — a plan first; `--apply` registers skills in place and adds MCP switched off, `--undo` puts everything back')
37
+ .addHelpText('after', publicContributionHelp)
26
38
  .option('--json', 'Print the plan (or the apply result) as JSON')
27
39
  .option('--apply', 'Apply the plan: skills in place (ref) or as link/copy · MCP switched off · secrets and hooks only listed')
28
40
  .option('--mode <mode>', `How skills come over: ${IMPORT_MODES.join('|')} (default ref — nothing copied)`)
@@ -316,6 +316,10 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
316
316
  "daily-reflection.error",
317
317
  "dashboard-acp",
318
318
  "dashboard-boot",
319
+ "dashboard-executed",
320
+ "dashboard-executed-fallback",
321
+ "dashboard-executed-remote-skipped",
322
+ "dashboard-executed-session-unknown",
319
323
  "dashboard-optional-specs.return",
320
324
  "dashboard-optional-specs.short-circuited",
321
325
  "debug-logs.ingest",
@@ -430,6 +434,7 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
430
434
  "doom-loop-ask-timeout",
431
435
  "doom-loop-auto-undo-applied",
432
436
  "doom-loop-detected",
437
+ "doom-loop-self-heal",
433
438
  "downgrade",
434
439
  "downstream-dep-deescalate",
435
440
  "downstream.emit",
@@ -547,6 +552,8 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
547
552
  "focused-widget-key",
548
553
  "fold-applied",
549
554
  "fold-broken",
555
+ "fold-click-anchor",
556
+ "fold-click-follow-tail",
550
557
  "force-flush",
551
558
  "forced",
552
559
  "forked",
@@ -1519,7 +1526,10 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
1519
1526
  "tool-unavailable",
1520
1527
  "tool.call",
1521
1528
  "tools-tier",
1529
+ "trace-persist-deferred",
1530
+ "trace-persist-dropped",
1522
1531
  "trace-persist-failed",
1532
+ "trace-persist-flushed",
1523
1533
  "transcribe.begin",
1524
1534
  "transcribe.error",
1525
1535
  "transcribe.ok",
@@ -359,6 +359,7 @@ export function registerMsgCommands(program: Command, deps: MsgCliDeps = {}): vo
359
359
  let store: MsgStore | undefined;
360
360
  try {
361
361
  const asked = withStore(ledger => ledger.post({ from, to, body: question }));
362
+ emit(asked, opts.json, () => out.log(`#${asked.id}`));
362
363
  // msg ask uses the same saved-message handler before waiting for its linked reply.
363
364
  await handleTaMessage(asked);
364
365
  const deadline = Date.now() + waitSeconds * 1000;
@@ -7,7 +7,7 @@ import { readPodMemoryAdvice } from './pod-memory-advice.js';
7
7
  import { getUserConfig } from '../user-config.js';
8
8
  import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
9
9
  import { parsePodPool, resolvePodPoolSpec, podPoolHostLease, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
10
- import { leaseKubectl, measurePoolLease, probePoolDns, recommendConcurrency, type PoolLeaseMeasure, type PoolDnsProbe } from '../task-orchestrator/surfaces/pod-lease.js';
10
+ import { leaseKubectl, measurePoolLease, probePoolDns, probePoolGitHub, recommendConcurrency, type PoolLeaseMeasure, type PoolDnsProbe, type ExternalGitHubProbe } from '../task-orchestrator/surfaces/pod-lease.js';
11
11
  import { POD_COMMAND_DEADLINE_SECONDS, runPodCommand, type PodCommandResult, type RunPodCommandOptions } from '../task-orchestrator/surfaces/pod-command-job.js';
12
12
  import { hostLeaseCounts, leaseHasPendingPod } from '../pod-lease/host-lease.js';
13
13
 
@@ -22,6 +22,7 @@ export interface PodCliDeps {
22
22
  run?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
23
23
  kubectl?: PoolKubectl;
24
24
  dns?: (context: string, kubectl: PoolKubectl) => PoolDnsProbe;
25
+ externalGitHub?: (context: string, kubectl: PoolKubectl) => ExternalGitHubProbe;
25
26
  accounts?: () => number;
26
27
  perAccount?: () => number;
27
28
  poolSpec?: (explicit?: string) => string | null;
@@ -81,7 +82,8 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
81
82
  .command('status').description('지금 추가로 쏠 수 있는 Pod 골과 제한 근거')
82
83
  .option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한][#레지스트리:포트] 쉼표')
83
84
  .option('--json', '표 대신 동일 측정값을 JSON 으로')
84
- .action((opts: { pool?: string; json?: boolean }) => {
85
+ .option('--github-egress', '같은 namespace 임시 Pod에서 GitHub DNS/HTTPS를 별도 관측 (admission 불변)')
86
+ .action((opts: { pool?: string; json?: boolean; githubEgress?: boolean }) => {
85
87
  try {
86
88
  const kubectl = deps.kubectl ?? leaseKubectl;
87
89
  const spec = (deps.poolSpec ?? ((explicit?: string) => resolvePodPoolSpec(explicit, process.env, () => {
@@ -105,7 +107,14 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
105
107
  // Pending Pods and Job-only reservations are disjoint; a lease for an already Pending Job is the same Job.
106
108
  const waitingJobs = decision.pending === null ? null : decision.pending +
107
109
  records.filter((r) => r.stage === 'job' && !leaseHasPendingPod(r, decision.pendingJobs ?? [])).length;
108
- const result = { pool: spec, ...measure, accounts, perAccount, ...decision, reserved: host.reserved, waitingJobs };
110
+ // This explicit observation runs after the admission calculation. A slow or failed external
111
+ // probe can never change recommended/limitedBy or slow the ordinary lease status path.
112
+ const externalGitHub = opts.githubEgress ? Object.fromEntries(members.map((member) => {
113
+ try { return [member.context, (deps.externalGitHub ?? probePoolGitHub)(member.context, kubectl)]; }
114
+ catch { return [member.context, { externalDns: null, githubHttps: null, measuredAt: null, reason: 'probe failed' }]; }
115
+ })) : undefined;
116
+ const result = { pool: spec, ...measure, accounts, perAccount, ...decision, reserved: host.reserved, waitingJobs,
117
+ ...(externalGitHub ? { externalGitHub } : {}) };
109
118
  debug.log('pod.lease', 'status', { pool: spec ? members.map((m) => ({ capacity: m.capacity })) : null, running: decision.running, pending: decision.pending, reserved: host.reserved, waitingJobs, unleasedRunning: decision.unleasedRunning, recommended: decision.recommended, limitedBy: decision.limitedBy });
110
119
  if (opts.json) io.log(JSON.stringify(result));
111
120
  else {
@@ -123,6 +132,8 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
123
132
  }
124
133
  io.log(`예약(저작 중) ${host.reserved} · 대기 Job ${waitingJobs ?? '?'} · 실행 ${decision.running ?? '?'}`);
125
134
  io.log(`임대 없는 실행 ${decision.unleasedRunning ?? '?'}`);
135
+ if (externalGitHub) for (const [context, probe] of Object.entries(externalGitHub))
136
+ io.log(`GitHub 외부망 ${context}: DNS=${probe.externalDns ?? '?'} HTTPS=${probe.githubHttps ?? '?'} 측정=${probe.measuredAt ?? '?'}${probe.reason ? ` · ${probe.reason}` : ''}`);
126
137
  io.log(`capacity: ${decision.capacitySlots ?? '?'} 칸 (상한 ${members.reduce((sum, m) => sum + m.capacity, 0)} − Running ${decision.running ?? '?'} − Pending ${decision.pending ?? '?'}; 권장 수에서 건강한 멤버의 임대 없는 실행 차감)`);
127
138
  io.log(`memory: ${decision.memorySlots ?? '?'} 칸 (할당 가능 − Running 하니스 상한 합; 골당 16Gi · 모든 네임스페이스의 노드별 기존 예약을 반영해 배치 가능 ${decision.placeableSlots ?? '?'} 칸)`);
128
139
  io.log(`lite: ${decision.liteMemorySlots ?? '?'} 칸 (같은 여유를 lite 골 2Gi 로 센 값 · 관측만 — 권장 수는 위 standard 기준)`);