elanous 0.2.25 → 0.2.28

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 (337) 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/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
  53. package/apps/pwa/out/approvals/index.html +1 -1
  54. package/apps/pwa/out/approvals/index.txt +13 -13
  55. package/apps/pwa/out/autopilot/index.html +1 -1
  56. package/apps/pwa/out/autopilot/index.txt +13 -13
  57. package/apps/pwa/out/board/index.html +1 -1
  58. package/apps/pwa/out/board/index.txt +13 -13
  59. package/apps/pwa/out/botlab/index.html +1 -1
  60. package/apps/pwa/out/botlab/index.txt +13 -13
  61. package/apps/pwa/out/bots/index.html +1 -1
  62. package/apps/pwa/out/bots/index.txt +13 -13
  63. package/apps/pwa/out/ceo/index.html +1 -1
  64. package/apps/pwa/out/ceo/index.txt +13 -13
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +13 -13
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +13 -13
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +13 -13
  71. package/apps/pwa/out/decisions/index.html +1 -1
  72. package/apps/pwa/out/decisions/index.txt +13 -13
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +13 -13
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +13 -13
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +13 -13
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +13 -13
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +13 -13
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +13 -13
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +13 -13
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +13 -13
  90. package/apps/pwa/out/live-run/index.html +1 -1
  91. package/apps/pwa/out/live-run/index.txt +13 -13
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +13 -13
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +13 -13
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +13 -13
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +13 -13
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +13 -13
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +13 -13
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +13 -13
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +13 -13
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +13 -13
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +14 -14
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +13 -13
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +13 -13
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +13 -13
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +13 -13
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +13 -13
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +13 -13
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +13 -13
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +13 -13
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +13 -13
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +13 -13
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +13 -13
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +13 -13
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +13 -13
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +13 -13
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +13 -13
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +13 -13
  146. package/graphs/implement-loop.yaml +38 -1
  147. package/graphs/steward/recipes.yaml +6 -0
  148. package/graphs/steward/release-day.yaml +23 -0
  149. package/graphs/steward/steward.yaml +9 -1
  150. package/graphs/work/code-pr.yaml +23 -0
  151. package/package.json +1 -1
  152. package/scripts/browser-observe-cycle.ts +76 -2
  153. package/scripts/ci-typecheck-changed.ts +119 -7
  154. package/scripts/coord-msg-bridge.ts +209 -0
  155. package/scripts/coord-post.sh +40 -7
  156. package/scripts/coord-requests-bridge.ts +10 -5
  157. package/scripts/detect-test-interference.ts +31 -1
  158. package/scripts/expose-rubric-suggest.ts +1 -1
  159. package/scripts/measure-tsc-affected-roots.ts +10 -1
  160. package/scripts/release-loop/docs-node.ts +51 -22
  161. package/scripts/release-loop/export-check-node.ts +8 -5
  162. package/scripts/release-loop/gate-node.ts +37 -4
  163. package/scripts/release-loop/node-verdict.ts +10 -3
  164. package/scripts/release-loop/publish-node.ts +3 -0
  165. package/scripts/release-loop/tui-node-checks.ts +5 -2
  166. package/scripts/skills/public-check.ts +5 -2
  167. package/scripts/test-deterministic.ts +520 -37
  168. package/scripts/ux-sim.ts +7 -1
  169. package/scripts/verify-kpack6-live.ts +230 -0
  170. package/src/acp/codex-plugins.ts +17 -0
  171. package/src/agent/task-notification.ts +32 -2
  172. package/src/agent-mission/in-agent-plugin.ts +8 -3
  173. package/src/agent-substrate/review-observation.ts +9 -2
  174. package/src/agent-substrate/self-review-cli.ts +62 -16
  175. package/src/boot/daemon-tools/self-implement.ts +72 -4
  176. package/src/briefing/brief-items.ts +151 -35
  177. package/src/budget/llm-cost.ts +30 -3
  178. package/src/budget/usage-outcomes.ts +32 -0
  179. package/src/claims/claims-ledger.ts +23 -0
  180. package/src/claims/claims-render.ts +22 -2
  181. package/src/cli/brief-cli.ts +131 -10
  182. package/src/cli/claims-cli.ts +24 -4
  183. package/src/cli/control-cli.ts +29 -0
  184. package/src/cli/doctor-cli.ts +90 -8
  185. package/src/cli/msg-cli.ts +177 -11
  186. package/src/cli/plugin-cli.ts +11 -3
  187. package/src/cli/pr-cli.ts +133 -18
  188. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  189. package/src/cli/pr-land-refusal.ts +20 -0
  190. package/src/cli/release-cli.ts +34 -5
  191. package/src/cli/rfc-cli.ts +24 -0
  192. package/src/cli/seat-cli.ts +121 -0
  193. package/src/cli/self-utterances-cli.ts +140 -0
  194. package/src/cli/tailscale-serve.ts +5 -8
  195. package/src/cli/tasks-cli.ts +528 -31
  196. package/src/cli/usage-cli.ts +9 -4
  197. package/src/context-card/cell.ts +40 -0
  198. package/src/dashboard/boot-timing.ts +35 -0
  199. package/src/dashboard/first-screen-band.ts +63 -13
  200. package/src/dashboard/index.ts +94 -27
  201. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  202. package/src/dashboard/skill-detection-runtime.ts +24 -4
  203. package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
  204. package/src/debug/log.ts +1 -1
  205. package/src/domains/morning-report.ts +3 -1
  206. package/src/domains/release-tool.ts +29 -12
  207. package/src/domains/surface-events.ts +16 -0
  208. package/src/elanous-config-dir.ts +2 -2
  209. package/src/fleet/host-doctor.ts +73 -2
  210. package/src/github/pr-labels.ts +10 -4
  211. package/src/graph-runner/runner.ts +104 -15
  212. package/src/harness/harness-clean.ts +21 -3
  213. package/src/harness/harness-cli-command.ts +68 -10
  214. package/src/harness/harness-pod-dispatch.ts +11 -4
  215. package/src/harness/harness-stop.ts +11 -7
  216. package/src/hq/seats.ts +17 -3
  217. package/src/index.ts +69 -9
  218. package/src/instance/current.ts +2 -6
  219. package/src/instance/resolve.ts +5 -6
  220. package/src/intelligence-map/model-bench-scores.ts +102 -0
  221. package/src/llm/openai-sampling-fields.ts +89 -0
  222. package/src/llm/runtime-model-family.ts +61 -0
  223. package/src/llm/zai-coding-plan.ts +27 -1
  224. package/src/llm.ts +13 -89
  225. package/src/loops/orchestrator/inbox.ts +73 -0
  226. package/src/loops/orchestrator/resync-shadow.ts +100 -12
  227. package/src/loops/orchestrator/tick.ts +73 -2
  228. package/src/machines/machine-ledger.ts +31 -3
  229. package/src/maturity/cli-maturity.ts +13 -3
  230. package/src/maturity/feature-maturity.ts +1 -1
  231. package/src/msg/msg-sse-server.ts +141 -0
  232. package/src/msg/msg-store.ts +149 -17
  233. package/src/ops/overview-collect.ts +3 -2
  234. package/src/ops/overview-owner.ts +29 -0
  235. package/src/patents/ledger.ts +128 -0
  236. package/src/plugin-hooks/shell-hook.ts +17 -2
  237. package/src/plugin-missions/evaluator-runner.ts +18 -2
  238. package/src/plugins/install/market-fetch.ts +32 -1
  239. package/src/plugins/install/plugin-install.ts +11 -1
  240. package/src/pod-lease/admission.ts +116 -41
  241. package/src/prompt-enhance/enhance.ts +11 -0
  242. package/src/release-loop/checklist.ts +52 -4
  243. package/src/release-loop/feature-store.ts +10 -9
  244. package/src/release-loop/merged-pr-checklist.ts +24 -5
  245. package/src/release-loop/rubric.ts +70 -0
  246. package/src/rfc-registry/registry.ts +88 -0
  247. package/src/seat-dispatch/seat-request-ledger.ts +4 -0
  248. package/src/seat-loop/knowhow-lint.ts +233 -0
  249. package/src/seat-loop/seat-loop.ts +5 -1
  250. package/src/seat-loop/seat-pack.ts +16 -4
  251. package/src/seat-loop/traffic-signal.ts +7 -0
  252. package/src/self-dev/ask-launch-flow.ts +48 -8
  253. package/src/self-dev/ask-path-candidates.ts +69 -14
  254. package/src/self-dev/decompose-proposal.ts +43 -1
  255. package/src/self-dev/draft-sweep.ts +23 -14
  256. package/src/self-dev/graph-journey-nodes.ts +2 -0
  257. package/src/self-dev/launch-preflight-text.ts +5 -1
  258. package/src/self-dev/launch-preflight.ts +143 -10
  259. package/src/self-dev/orchestrate.ts +6 -2
  260. package/src/self-dev/run-supervisor.ts +35 -1
  261. package/src/self-implement/gate-cli.ts +6 -2
  262. package/src/self-implement/goal-author.ts +5 -28
  263. package/src/self-implement/goal-command-execution.ts +26 -0
  264. package/src/self-implement/goal-new-paths.ts +4 -5
  265. package/src/self-implement/graph-authority.ts +17 -3
  266. package/src/self-implement/graph-yaml.ts +15 -7
  267. package/src/self-implement/headless-elanous-driver.ts +16 -1
  268. package/src/self-implement/host-regate.ts +44 -9
  269. package/src/self-implement/orchestrator.ts +5 -11
  270. package/src/self-implement/pwa-import-graph.ts +194 -7
  271. package/src/self-implement/rework-policy.ts +80 -2
  272. package/src/self-implement/self-implement-runtime.ts +126 -21
  273. package/src/self-implement/speed-claims.ts +145 -6
  274. package/src/session/index.ts +14 -4
  275. package/src/session-runtime/index.ts +55 -11
  276. package/src/skills/tools/persistent-grounding.ts +59 -4
  277. package/src/skills/tools/shell.ts +79 -1
  278. package/src/steward/rejudge-tick.ts +12 -1
  279. package/src/steward/release-day.ts +158 -0
  280. package/src/steward/resync-tick.ts +123 -0
  281. package/src/task-agent/actions.ts +1 -0
  282. package/src/task-agent/adopt-pr.ts +140 -0
  283. package/src/task-agent/board.ts +11 -1
  284. package/src/task-agent/card-evidence.ts +37 -5
  285. package/src/task-agent/cover.ts +105 -19
  286. package/src/task-agent/failure-notify.ts +61 -0
  287. package/src/task-agent/green-sweep.ts +170 -0
  288. package/src/task-agent/land-worktree.ts +52 -5
  289. package/src/task-agent/live-move-history.ts +1 -0
  290. package/src/task-agent/live-moves.ts +56 -17
  291. package/src/task-agent/parent-host.ts +1 -1
  292. package/src/task-agent/postland-card.ts +139 -0
  293. package/src/task-agent/rejudge-on-head.ts +321 -25
  294. package/src/task-agent/remote-rejudge-bind.ts +62 -0
  295. package/src/task-agent/repair-branch-cas.ts +182 -0
  296. package/src/task-agent/repair-cycle.ts +275 -0
  297. package/src/task-agent/repair-next-move.ts +31 -0
  298. package/src/task-agent/repair-review.ts +24 -0
  299. package/src/task-agent/repair-worker.ts +87 -0
  300. package/src/task-agent/review-result-capture.ts +66 -2
  301. package/src/task-agent/shadow.ts +150 -29
  302. package/src/task-agent/task-hand.ts +133 -8
  303. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
  305. package/src/telegram.ts +30 -1
  306. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  307. package/src/tui.ts +20 -1
  308. package/src/user-config.ts +11 -60
  309. package/src/ux-sim/fault-sqlite-busy.ts +50 -0
  310. package/src/version/packed-revision.json +1 -1
  311. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
  336. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
  337. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
@@ -1,9 +1,97 @@
1
1
  import type { Command } from 'commander';
2
2
  import { BRIEF_DOMAINS, BRIEF_PRIORITIES, BRIEF_REACTIONS, BRIEF_SLOTS, BriefItemsInputError, BriefItemsLedger, type BriefDomain, type BriefPriority, type BriefReaction, type BriefSendSlot } from '../briefing/brief-items.js';
3
3
  import { kindRouteTarget } from '../domains/telegram-kind-route.js';
4
- import { sendTelegramReturningId } from '../autopilot/mission-notify.js';
4
+ import { sendTelegramButtonsTo, type TgButton } from '../autopilot/mission-notify.js';
5
5
  import { getUserConfig } from '../user-config.js';
6
6
 
7
+ export function briefFeedbackButtons(ids: readonly number[]): TgButton[][] {
8
+ return ids.map(id => [
9
+ { text: `👀 ${id} 읽음`, data: `brief-item:${id}:read` },
10
+ { text: `✅ ${id} 움직임`, data: `brief-item:${id}:button` },
11
+ { text: `➖ ${id} 넘김`, data: `brief-item:${id}:ignore` },
12
+ ]);
13
+ }
14
+
15
+ export interface BriefTelegramUpdate {
16
+ botId: string;
17
+ chatId: number;
18
+ messageId: number;
19
+ updateId: string;
20
+ text?: string;
21
+ callbackData?: string;
22
+ }
23
+
24
+ export function recordBriefTelegramUpdate(ledger: BriefItemsLedger, update: BriefTelegramUpdate): boolean {
25
+ if (update.callbackData !== undefined) {
26
+ const match = /^brief-item:([1-9]\d*):(read|button|ignore)$/.exec(update.callbackData);
27
+ if (!match) return false;
28
+ return ledger.recordTelegramFeedback({ botId: update.botId, chatId: update.chatId,
29
+ messageId: update.messageId, updateId: update.updateId, itemId: Number(match[1]),
30
+ reaction: match[2] === 'read' ? '열람' : match[2] === 'ignore' ? '무시' : '버튼' });
31
+ }
32
+ if (!update.text?.trim()) return false;
33
+ const text = update.text.trim();
34
+ const itemId = /^(?:#|항목\s*)([1-9]\d*)(?=\s|[::]|$)/.exec(text)?.[1];
35
+ return ledger.recordTelegramFeedback({ botId: update.botId, chatId: update.chatId,
36
+ messageId: update.messageId, updateId: update.updateId,
37
+ ...(itemId ? { itemId: Number(itemId) } : {}),
38
+ reaction: /그게 아니라|(?:^|\s)정정\s*[::]/.test(text) ? '정정' : '답장' });
39
+ }
40
+
41
+ /** Prefix each item line with `#<id>` so the 대표 can name the reply target ("#12 답장", "#12 그게 아니라 …"). */
42
+ function withItemNumbers(text: string, itemLines: ReadonlyMap<string, readonly number[]>): string {
43
+ const linePositions = new Map<string, number>();
44
+ return text.split('\n').map(line => {
45
+ const ids = itemLines.get(line);
46
+ if (!ids) return line;
47
+ const position = linePositions.get(line) ?? 0;
48
+ linePositions.set(line, position + 1);
49
+ const itemId = ids[position];
50
+ return itemId === undefined ? line : `#${itemId} ${line}`;
51
+ }).join('\n');
52
+ }
53
+
54
+ /** Split only feedback-enabled pages; each Telegram message gets its own real item coordinate.
55
+ * Item lines carry their `#<id>` so a plain text reply can name its target item.
56
+ */
57
+ function briefDeliveryParts(markdown: string, itemLines: ReadonlyMap<string, readonly number[]>): Array<{ text: string; itemId: number }> {
58
+ const parts: Array<{ text: string; itemId: number }> = [];
59
+ const linePositions = new Map<string, number>();
60
+ let pending = '';
61
+ for (const line of markdown.trimEnd().split('\n')) {
62
+ const ids = itemLines.get(line);
63
+ const position = linePositions.get(line) ?? 0;
64
+ const itemId = ids?.[position];
65
+ if (itemId === undefined) {
66
+ pending += `${line}\n`;
67
+ continue;
68
+ }
69
+ linePositions.set(line, position + 1);
70
+ const numbered = `#${itemId} ${line}`;
71
+ // Leave room for the Telegram sender's newline and keep a button on every fragment.
72
+ while (pending.length >= 3800) {
73
+ parts.push({ text: pending.slice(0, 3800), itemId });
74
+ pending = pending.slice(3800);
75
+ }
76
+ let remainder = pending + numbered;
77
+ do {
78
+ parts.push({ text: remainder.slice(0, 3800), itemId });
79
+ remainder = remainder.slice(3800);
80
+ } while (remainder.length);
81
+ pending = '';
82
+ }
83
+ if (pending && parts.length) {
84
+ const last = parts[parts.length - 1]!;
85
+ if (last.text.length + pending.length <= 3800) last.text += pending;
86
+ else {
87
+ const room = 3800 - last.text.length;
88
+ last.text += pending.slice(0, room);
89
+ for (let i = room; i < pending.length; i += 3800) parts.push({ text: pending.slice(i, i + 3800), itemId: last.itemId });
90
+ }
91
+ }
92
+ return parts;
93
+ }
94
+
7
95
  export interface BriefCliDeps {
8
96
  ledger?: BriefItemsLedger;
9
97
  stateDir?: string;
@@ -11,6 +99,10 @@ export interface BriefCliDeps {
11
99
  output?: (line: string) => void;
12
100
  error?: (line: string) => void;
13
101
  send?: (markdown: string) => number | null;
102
+ /** Test seam for the Telegram button sender (one actual message per invocation). */
103
+ sendWithButtons?: (text: string, buttons: TgButton[][]) => number | null;
104
+ /** Allows an injected sender to exercise the same Telegram attribution as the real sender. */
105
+ telegramTarget?: { botToken: string; chatId: number };
14
106
  }
15
107
 
16
108
  export function registerBriefCommands(program: Command, deps: BriefCliDeps = {}): void {
@@ -31,18 +123,47 @@ export function registerBriefCommands(program: Command, deps: BriefCliDeps = {})
31
123
  output(`이번 주 브리핑 중 대표가 움직인 것 ${acted}/${total}`);
32
124
  } catch (cause) { fail('', cause); }
33
125
  });
34
- const deliver = (slot: BriefSendSlot): string => ledger().withSendLock(slot, ({ markdown, ids }, markSent) => {
126
+ const deliver = (slot: BriefSendSlot): string => ledger().withSendLock(slot, ({ markdown, ids, itemLines }, markSent) => {
35
127
  if (ids.length === 0) return '(보낼 항목 없음)';
36
- const send = deps.send ?? ((text: string) => {
37
- const target = kindRouteTarget(getUserConfig(), 'ops-report');
38
- if (!target) return null;
39
- return sendTelegramReturningId(target.botToken, target.chatId, text);
40
- });
41
- const messageId = send(markdown);
42
- if (typeof messageId !== 'number' || !Number.isFinite(messageId) || messageId <= 0) {
128
+ const target = deps.telegramTarget ?? (deps.send ? null : kindRouteTarget(getUserConfig(), 'ops-report'));
129
+ const numberedMarkdown = withItemNumbers(markdown, itemLines);
130
+ const oneMessage = numberedMarkdown.length + numberedMarkdown.split('\n').length <= 3900 && ids.length <= 30;
131
+ const send = (text: string, itemIds: readonly number[]) => deps.sendWithButtons
132
+ ? deps.sendWithButtons(text, briefFeedbackButtons(itemIds))
133
+ : deps.send ? deps.send(text)
134
+ : target ? sendTelegramButtonsTo(target.botToken, target.chatId, text, briefFeedbackButtons(itemIds)) : null;
135
+ const botId = target ? target.botToken.split(':')[0]! : '';
136
+ if (target && !oneMessage) {
137
+ // A sent fragment is not a completed item. Persist only items whose every fragment
138
+ // succeeded; an incomplete item must remain available to the next send.
139
+ const parts = briefDeliveryParts(markdown, itemLines);
140
+ const remainingParts = new Map<number, number>();
141
+ for (const part of parts) remainingParts.set(part.itemId, (remainingParts.get(part.itemId) ?? 0) + 1);
142
+ const coordinates: Array<{ botId: string; chatId: number; messageId: number; itemId: number }> = [];
143
+ let sendThrew = false;
144
+ let sendCause: unknown;
145
+ for (const part of parts) {
146
+ let partMessageId: number | null;
147
+ try { partMessageId = send(part.text, [part.itemId]); }
148
+ catch (cause) { sendThrew = true; sendCause = cause; break; }
149
+ if (typeof partMessageId !== 'number' || !Number.isSafeInteger(partMessageId) || partMessageId <= 0) break;
150
+ coordinates.push({ botId, chatId: target.chatId, messageId: partMessageId, itemId: part.itemId });
151
+ remainingParts.set(part.itemId, remainingParts.get(part.itemId)! - 1);
152
+ }
153
+ const deliveredIds = [...new Set(coordinates.map(row => row.itemId))].filter(id => remainingParts.get(id) === 0);
154
+ if (deliveredIds.length > 0) markSent(deliveredIds, slot, coordinates.filter(row => deliveredIds.includes(row.itemId)));
155
+ if (coordinates.length < parts.length) {
156
+ if (sendThrew) throw sendCause;
157
+ throw new Error(`발송 실패: ${deliveredIds.length}/${ids.length} 항목 전송 후 중단 — 나머지는 다음 발송에서 이어진다`);
158
+ }
159
+ const lastMessageId = coordinates[coordinates.length - 1]!.messageId;
160
+ return `sent ${lastMessageId} (${ids.length} items)`;
161
+ }
162
+ const messageId = target ? send(numberedMarkdown, ids) : send(markdown, ids);
163
+ if (typeof messageId !== 'number' || !Number.isSafeInteger(messageId) || messageId <= 0) {
43
164
  throw new Error('발송 실패: 양수 메시지 id 없음');
44
165
  }
45
- markSent(ids, slot);
166
+ markSent(ids, slot, target ? { botId, chatId: target.chatId, messageId } : undefined);
46
167
  return `sent ${messageId} (${ids.length} items)`;
47
168
  });
48
169
 
@@ -1,6 +1,7 @@
1
1
  import type { Command } from 'commander';
2
2
  import { ClaimsInputError, ClaimsLedger, type ClaimsLedgerOptions, type ClaimRow, type ClaimDetail } from '../claims/claims-ledger.js';
3
3
  import { renderClaims, queueStaleRechecks, type ClaimSurface } from '../claims/claims-render.js';
4
+ import { PatentLedger, PatentsInputError, type PatentStatus, type PatentTrack } from '../patents/ledger.js';
4
5
 
5
6
  function actor(explicit?: string): string {
6
7
  const by = explicit?.trim() || process.env.AI_AGENT?.trim();
@@ -19,7 +20,7 @@ function handle<Args extends unknown[]>(command: string, action: (...args: Args)
19
20
  return (...args) => {
20
21
  try { action(...args); }
21
22
  catch (error) {
22
- if (!(error instanceof ClaimsInputError)) throw error;
23
+ if (!(error instanceof ClaimsInputError) && !(error instanceof PatentsInputError)) throw error;
23
24
  const message = error.message.replace(/\r/g, '\\r').replace(/\n/g, '\\n')
24
25
  .replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
25
26
  process.stderr.write(`claims ${command}: ${message}\n`);
@@ -41,7 +42,19 @@ function detail(row: ClaimDetail): string {
41
42
  export function registerClaimsCommands(program: Command, options: ClaimsLedgerOptions & { instanceRoot?: string } = {}, out: Pick<Console, 'log'> = console): void {
42
43
  const root = program.command('claims').description('소구점 · 실측 근거 원장');
43
44
  const ledger = () => new ClaimsLedger(options);
45
+ const patentLedger = () => new PatentLedger(options);
44
46
  const emit = (value: unknown, json: boolean | undefined, text: string) => out.log(json ? JSON.stringify(value) : text);
47
+ const patents = root.command('patents').description('비공개 특허 후보 원장');
48
+ patents.command('add <id>').requiredOption('--title <title>').requiredOption('--track <track>')
49
+ .option('--deadline <YYYY-MM-DD>').action(handle('patents add', (id: string, o: { title: string; track: PatentTrack; deadline?: string }) => {
50
+ const row = patentLedger().add({ id, title: o.title, track: o.track, ...(o.deadline ? { deadline: o.deadline } : {}) });
51
+ emit(row, false, `${row.id} · ${row.status}`);
52
+ }));
53
+ patents.command('status <id>').requiredOption('--status <status>')
54
+ .action(handle('patents status', (id: string, o: { status: PatentStatus }) => {
55
+ const row = patentLedger().setStatus(id, o.status);
56
+ emit(row, false, `${row.id} · ${row.status}`);
57
+ }));
45
58
  root.command('add <id>').requiredOption('--claim <text>').requiredOption('--audience <list>').requiredOption('--owner <seat>')
46
59
  .option('--contrast <text>').action(handle('add', (id: string, o: { claim: string; audience: string; owner: string; contrast?: string }) => {
47
60
  const row = ledger().add({ id, claim: o.claim, audience: o.audience, owner: o.owner, ...(o.contrast ? { contrast: o.contrast } : {}) });
@@ -79,9 +92,16 @@ export function registerClaimsCommands(program: Command, options: ClaimsLedgerOp
79
92
  emit(row, o.json, detail(row));
80
93
  }));
81
94
  root.command('render').requiredOption('--surface <surface>', 'deck | site | notice')
82
- .option('--audience <audience>').option('--json')
83
- .action(handle('render', (o: { surface: ClaimSurface; audience?: string; json?: boolean }) => {
84
- const result = renderClaims(ledger(), { surface: o.surface, ...(o.audience ? { audience: o.audience } : {}) });
95
+ .option('--audience <audience>').option('--include-patents', '명시적으로 공개 허용 특허 후보를 포함')
96
+ .option('--patent-owner <owner>', '특허 후보의 확인된 담당').option('--json')
97
+ .action(handle('render', (o: { surface: ClaimSurface; audience?: string; includePatents?: boolean; patentOwner?: string; json?: boolean }) => {
98
+ if (o.includePatents && (!o.patentOwner?.trim() || !o.audience?.trim())) {
99
+ throw new ClaimsInputError('--include-patents requires --patent-owner and --audience');
100
+ }
101
+ if (!o.includePatents && o.patentOwner) throw new ClaimsInputError('--patent-owner requires --include-patents');
102
+ const patents = o.includePatents
103
+ ? { ledger: patentLedger(), owner: o.patentOwner!, audience: o.audience! } : undefined;
104
+ const result = renderClaims(ledger(), { surface: o.surface, ...(o.audience ? { audience: o.audience } : {}), ...(patents ? { patents } : {}) });
85
105
  // Review follow-up: rendering is where stale claims surface — queue their re-measurement in the same act.
86
106
  const rechecks = result.excluded.some(item => item.reason === 'stale')
87
107
  ? queueStaleRechecks(ledger(), { root: options.instanceRoot, now: options.now?.() }) : 0;
@@ -31,6 +31,35 @@ function reasonFor(error: unknown, token?: string): string {
31
31
  return token ? message.replaceAll(token, '[redacted]') : message;
32
32
  }
33
33
 
34
+ /** Doctor's read-only live source. One malformed Nexus machine row invalidates the whole
35
+ * reading: doctor reports unmeasured rather than treating a partial registry as complete. */
36
+ export async function listMachineResources(options: { primaryUrl?: string; queryToken?: string; port?: string; root?: string; fetch?: (input: URL | RequestInfo, init?: RequestInit) => Promise<Response> } = {}): Promise<ResourceView[]> {
37
+ const portOverride = options.port ?? (process.env.ELANOUS_CONTROL_PORT?.trim() || undefined);
38
+ const primary = await resolvePrimary({
39
+ role: 'query',
40
+ ...(options.root ? { root: options.root } : {}),
41
+ config: { url: options.primaryUrl, tokens: options.queryToken ? { query: options.queryToken } : undefined },
42
+ ...(portOverride !== undefined ? { port: Number(portOverride) } : {}),
43
+ });
44
+ const token = primary.token;
45
+ if (!token) throw new Error('control query token unavailable');
46
+ const url = new URL('/v1/resources', primary.url);
47
+ url.searchParams.set('kind', 'machine');
48
+ const response = await (options.fetch ?? globalThis.fetch)(url, {
49
+ headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(5000),
50
+ });
51
+ if (!response.ok) throw new Error(`control HTTP ${response.status}`);
52
+ const body: unknown = await response.json();
53
+ if (!body || typeof body !== 'object' || !('resources' in body) || !Array.isArray(body.resources) ||
54
+ body.resources.some((row: unknown) => !row || typeof row !== 'object' ||
55
+ (row as ResourceView).kind !== 'machine' || typeof (row as ResourceView).machine !== 'string' ||
56
+ typeof (row as ResourceView).name !== 'string' || typeof (row as ResourceView).expired !== 'boolean' ||
57
+ !((row as ResourceView).attrs && typeof (row as ResourceView).attrs === 'object'))) {
58
+ throw new Error('invalid machine resource response');
59
+ }
60
+ return body.resources as ResourceView[];
61
+ }
62
+
34
63
  export async function runResourcesQuery(options: { where?: string; kind?: string; port?: string; primaryUrl?: string; queryToken?: string; json?: boolean }): Promise<number> {
35
64
  let base = '';
36
65
  let token: string | undefined;
@@ -27,7 +27,7 @@ import {
27
27
  type ReadinessReport,
28
28
  type SubstrateSignals,
29
29
  } from './doctor-readiness.js';
30
- import { debug } from '../debug/log.js';
30
+ import { debug, redactSecretText } from '../debug/log.js';
31
31
  import { resolveDaemonEndpoint, type ResolveDaemonEndpointOpts } from '../nexus/daemon-endpoint.js';
32
32
  import { applyDoctorFixes, applyGitInstall, applyServiceRestart, applySudoFixes, planDoctorFixes, type DoctorFixDeps, type DoctorFixItem } from './doctor-fix.js';
33
33
  import { ADVISABLE_DOCTOR_FIX_IDS, adviseDoctorFixes, type DoctorAdvice } from './doctor-llm-assist.js';
@@ -40,7 +40,10 @@ import type { LlmInventory } from '../llm/local-manager/types.js';
40
40
  import { buildDoctorBundle } from './doctor-bundle.js';
41
41
  import { uploadDoctorBundle } from './doctor-bundle-upload.js';
42
42
  import { checkNodeModules, formatNodeModulesCheck, nodeModulesTrees } from './doctor-node-modules.js';
43
- import { formatHostDoctorLines, runHostDoctor, type HostDoctorExpected, type HostDoctorRemote } from '../fleet/host-doctor.js';
43
+ import { formatHostDoctorLines, runHostDoctor, type HostDoctorExpected, type HostDoctorOptions, type HostDoctorRemote } from '../fleet/host-doctor.js';
44
+ import { listMachineResources } from './control-cli.js';
45
+ import type { ResourceView } from '../control-plane/ledger.js';
46
+ import { loadMachineLedger } from '../machines/machine-ledger.js';
44
47
 
45
48
  function defaultCheckPythonEnv(): { status: 'ok' | 'fixable' | 'manual'; evidence: string; remedy?: string } {
46
49
  const c = checkPythonEnv(false);
@@ -203,9 +206,14 @@ export interface DoctorCliDeps extends DoctorOptions, Pick<DoctorFixDeps, 'home'
203
206
  hostDoctorRemote?: HostDoctorRemote;
204
207
  /** `doctor host` 가 대조할 HQ 실측값(설치 판 · 이미지 핀) — 없으면 해당 점검은 unmeasured. */
205
208
  hostDoctorExpected?: HostDoctorExpected;
209
+ /** HQ membership readings; injected tests need no live Nexus. */
210
+ hostDoctorMembership?: () => Promise<NonNullable<HostDoctorOptions['membership']>>;
211
+ /** Nexus machine query seam; tests can exercise the default assembly without contacting a live service. */
212
+ hostDoctorMachineResources?: () => Promise<ResourceView[]>;
206
213
  }
207
214
 
208
215
  const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
216
+ const observationError = (error: unknown): string => redactSecretText(error instanceof Error ? error.message : String(error)).slice(0, 200);
209
217
  const tavilyNames = new Set(['TAVILY_API_KEY', 'TAVILY_KEY']);
210
218
 
211
219
  function defaultCodeRevision(): string | undefined {
@@ -1211,6 +1219,52 @@ function formatDoctorAdvice(advice: DoctorAdvice): string {
1211
1219
  ].join('\n');
1212
1220
  }
1213
1221
 
1222
+ function formatDoctorAdviceProposal(advice: DoctorAdvice, report: DoctorReport): { text: string; itemId: string; observedValue: string } | null {
1223
+ const proposal = advice.ok ? advice.order[0] : undefined;
1224
+ if (!proposal) return null;
1225
+ const observation = report.readiness?.items.find((item) => item.id === proposal.readinessId);
1226
+ if (!observation) return null;
1227
+ const observedValue = `${observation.status}: ${observation.evidence}`;
1228
+ return {
1229
+ itemId: proposal.readinessId,
1230
+ observedValue,
1231
+ text: [
1232
+ `LLM 제안: ${proposal.why}`,
1233
+ `운영 팩 항목 id: ${proposal.readinessId}`,
1234
+ `현재 관측값: ${observedValue}`,
1235
+ ].join('\n'),
1236
+ };
1237
+ }
1238
+
1239
+ async function recordDoctorAdviceResponse(
1240
+ proposal: { itemId: string; observedValue: string },
1241
+ accepted: boolean,
1242
+ ): Promise<void> {
1243
+ debug.log('doctor.advise', accepted ? 'accepted' : 'rejected', {
1244
+ itemId: proposal.itemId,
1245
+ observedValue: proposal.observedValue,
1246
+ applied: 0,
1247
+ });
1248
+ }
1249
+
1250
+ async function presentDoctorAdviceProposal(
1251
+ advice: DoctorAdvice,
1252
+ report: DoctorReport,
1253
+ deps: DoctorCliDeps,
1254
+ json: boolean,
1255
+ announce: (text: string) => void,
1256
+ ): Promise<{ proposal: { itemId: string; observedValue: string }; response: boolean | null; text: string } | null> {
1257
+ const formatted = formatDoctorAdviceProposal(advice, report);
1258
+ if (!formatted) return null;
1259
+ const proposal = { itemId: formatted.itemId, observedValue: formatted.observedValue };
1260
+ const interactive = deps.adviceInteractive ?? (process.stdin.isTTY === true && (json ? process.stderr.isTTY === true : process.stdout.isTTY === true));
1261
+ if (!interactive) return { proposal, response: null, text: formatted.text };
1262
+ announce(formatted.text);
1263
+ const accepted = await (deps.confirmAdvice ?? ((message) => confirmDoctorAdvice(message, json)))('이 제안을 수락할까요? [y/N] ');
1264
+ await recordDoctorAdviceResponse(proposal, accepted);
1265
+ return { proposal, response: accepted, text: `${formatted.text}\n응답: ${accepted ? '수락' : '거절'}` };
1266
+ }
1267
+
1214
1268
  async function confirmDoctorAdvice(message: string, json: boolean): Promise<boolean> {
1215
1269
  const prompt = createInterface({ input: process.stdin, output: json ? process.stderr : process.stdout });
1216
1270
  try { return /^(y|yes)$/i.test((await prompt.question(message)).trim()); }
@@ -1246,18 +1300,39 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1246
1300
  if (checks.some((check) => check.status !== 'ok')) setExitCode(1);
1247
1301
  });
1248
1302
  doctor.command('host')
1249
- .description('Check whether a fleet host stands ready for its role (read-only · setup-status shape)')
1250
- .argument('<host>', "ssh name, or 'local' for this machine")
1303
+ .description('Check whether a fleet host stands ready (read-only; membership needs a repository checkout and a named remote worker)')
1304
+ .argument('<host>', "SSH name in fleet intent; an unknown name fails membership; 'local' checks readiness without membership")
1251
1305
  .option('--role <role>', 'host role: worker or hq', 'worker')
1252
1306
  .option('--json', 'structured output')
1253
- .action((host: string, opts: { role: string; json?: boolean }, command: Command) => {
1307
+ .action(async (host: string, opts: { role: string; json?: boolean }, command: Command) => {
1254
1308
  if (opts.role !== 'worker' && opts.role !== 'hq') {
1255
1309
  err.error('--role must be worker or hq');
1256
1310
  setExitCode(1);
1257
1311
  return;
1258
1312
  }
1259
1313
  const json = opts.json === true || command.parent?.opts().json === true;
1260
- const report = runHostDoctor({ host, role: opts.role, remote: deps.hostDoctorRemote, expected: deps.hostDoctorExpected });
1314
+ let membership: HostDoctorOptions['membership'];
1315
+ if (opts.role === 'worker' && host !== 'local') {
1316
+ if (deps.hostDoctorMembership) membership = await deps.hostDoctorMembership();
1317
+ else {
1318
+ let intent: NonNullable<HostDoctorOptions['membership']>['intent'] = null;
1319
+ let machines: NonNullable<HostDoctorOptions['membership']>['machines'] = null;
1320
+ let parentHosts: NonNullable<HostDoctorOptions['membership']>['parentHosts'] = null;
1321
+ let intentError: string | undefined;
1322
+ let machinesError: string | undefined;
1323
+ let parentHostsError: string | undefined;
1324
+ try { intent = loadMachineLedger(deps.repositoryRoot ?? repositoryRoot); } catch (error) { intentError = observationError(error); }
1325
+ try { machines = await (deps.hostDoctorMachineResources ?? listMachineResources)(); } catch (error) { machinesError = observationError(error); }
1326
+ try {
1327
+ // UserConfig permits one parent object or an array of parent objects, never a bare host string.
1328
+ const configured = (deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)()).taskAgent?.parentHost;
1329
+ if (configured === null) parentHostsError = 'taskAgent.parentHost is null';
1330
+ else parentHosts = configured === undefined ? [] : Array.isArray(configured) ? configured : [configured];
1331
+ } catch (error) { parentHostsError = observationError(error); }
1332
+ membership = { intent, machines, parentHosts, intentError, machinesError, parentHostsError };
1333
+ }
1334
+ }
1335
+ const report = runHostDoctor({ host, role: opts.role, remote: deps.hostDoctorRemote, expected: deps.hostDoctorExpected, membership });
1261
1336
  out.log(json ? JSON.stringify(report, null, 2) : formatHostDoctorLines(report).join('\n'));
1262
1337
  if (!report.ok) setExitCode(1);
1263
1338
  });
@@ -1404,10 +1479,17 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1404
1479
  // Planning never writes; existing repairs require --yes, while git alone can use affirmative TTY consent.
1405
1480
  const plan = planDoctorFixes(fixes);
1406
1481
  const advice = opts.advise ? await requestDoctorAdvice(report, plan, deps) : undefined;
1482
+ const proposal = advice && !opts.fix ? await presentDoctorAdviceProposal(advice, report, deps, opts.json === true, (text) => {
1483
+ if (opts.json) err.error(text);
1484
+ else out.log(text);
1485
+ }) : null;
1407
1486
  if (!opts.fix) {
1408
1487
  out.log(opts.json
1409
- ? JSON.stringify({ ...report, advise: advice }, null, 2)
1410
- : [formatDoctorReport(report, { credentials: opts.credentials }), formatDoctorAdvice(advice!)].join('\n'));
1488
+ ? JSON.stringify({ ...report, advise: advice, ...(proposal ? { adviceProposal: { itemId: proposal.proposal.itemId, observedValue: proposal.proposal.observedValue, response: proposal.response } } : {}) }, null, 2)
1489
+ : [
1490
+ formatDoctorReport(report, { credentials: opts.credentials }),
1491
+ proposal?.response === null ? proposal.text : proposal ? `응답: ${proposal.response ? '수락' : '거절'}` : formatDoctorAdvice(advice!),
1492
+ ].join('\n'));
1411
1493
  if (report.requiredMissing?.length) setExitCode(1);
1412
1494
  return;
1413
1495
  }