elanous 0.2.11 → 0.2.13

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 (342) 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/{1394-556de6d71bbdb0fb.js → 1394-931c9827067b2276.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/{3992-2e522a8ac5f2afde.js → 3992-fe5c06d6b31f8b25.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-9da4678f8a310d18.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-e49b9b76c565a0b1.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-4c1dbb21861beab2.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-f6bc66b919b2ea7d.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-60603c1e056c9b25.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-a0ea8f5a2341ee6b.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-31012264da8a341f.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/{layout-d6b1d156fb95e74b.js → layout-50309aeb0bdd554a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-0696ee3fac9fe56c.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6ddd2109b7fc3a80.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-7a2bf830038a2264.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-371423d1a573d64b.js → page-1f327f18694fabca.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4b764b125339ac17.js → page-e5cef2fe6e4028df.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-cdf8e4b01067bcf1.js → page-cf596128d2b420ba.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-f672536f14f0969f.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-29a709616df9b050.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-fee842d7c2151129.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-4dc614f3ce043e63.js → page-78b40a344ed22f24.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-d02c782fc792e2af.js → page-a011dd5b0bff29c5.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-d92e32d51ab5157e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/share/{page-dc6a79fb003f746c.js → page-5f21aed6932ef375.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-0fffb84cafdc9da6.js → page-1e1a53946804debf.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-a1833cd43d832601.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-63d14a4d8fe1e1c1.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-2a511d320f762e06.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-1c3e03f88dac1e21.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/vault/{page-fca096e3e860306f.js → page-c4d5289c2ea97a89.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9ebfa78f0d9885c3.js → page-b91f8952deaeaf27.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-3dc5f21f7e39622d.js → page-71b43a2588489987.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +1 -0
  56. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
  57. package/apps/pwa/out/approvals/index.html +1 -1
  58. package/apps/pwa/out/approvals/index.txt +14 -14
  59. package/apps/pwa/out/autopilot/index.html +1 -1
  60. package/apps/pwa/out/autopilot/index.txt +14 -14
  61. package/apps/pwa/out/board/index.html +1 -1
  62. package/apps/pwa/out/board/index.txt +14 -14
  63. package/apps/pwa/out/botlab/index.html +1 -1
  64. package/apps/pwa/out/botlab/index.txt +14 -14
  65. package/apps/pwa/out/bots/index.html +1 -1
  66. package/apps/pwa/out/bots/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/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +14 -14
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +14 -14
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +14 -14
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +14 -14
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +14 -14
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +14 -14
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +14 -14
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +14 -14
  90. package/apps/pwa/out/market/index.html +1 -1
  91. package/apps/pwa/out/market/index.txt +14 -14
  92. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  93. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  94. package/apps/pwa/out/morning/index.html +1 -1
  95. package/apps/pwa/out/morning/index.txt +14 -14
  96. package/apps/pwa/out/observatory/index.html +1 -1
  97. package/apps/pwa/out/observatory/index.txt +14 -14
  98. package/apps/pwa/out/ops/checklist/index.html +1 -1
  99. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  100. package/apps/pwa/out/ops/release/index.html +1 -1
  101. package/apps/pwa/out/ops/release/index.txt +14 -14
  102. package/apps/pwa/out/ops/seats/index.html +1 -1
  103. package/apps/pwa/out/ops/seats/index.txt +14 -14
  104. package/apps/pwa/out/outputs/index.html +1 -1
  105. package/apps/pwa/out/outputs/index.txt +14 -14
  106. package/apps/pwa/out/reflection/index.html +1 -1
  107. package/apps/pwa/out/reflection/index.txt +15 -15
  108. package/apps/pwa/out/scheduler/index.html +1 -1
  109. package/apps/pwa/out/scheduler/index.txt +14 -14
  110. package/apps/pwa/out/sessions/index.html +1 -1
  111. package/apps/pwa/out/sessions/index.txt +14 -14
  112. package/apps/pwa/out/settings/devices/index.html +1 -1
  113. package/apps/pwa/out/settings/devices/index.txt +14 -14
  114. package/apps/pwa/out/settings/index.html +1 -1
  115. package/apps/pwa/out/settings/index.txt +14 -14
  116. package/apps/pwa/out/setup/done/index.html +1 -1
  117. package/apps/pwa/out/setup/done/index.txt +14 -14
  118. package/apps/pwa/out/setup/index.html +1 -1
  119. package/apps/pwa/out/setup/index.txt +14 -14
  120. package/apps/pwa/out/share/index.html +1 -1
  121. package/apps/pwa/out/share/index.txt +14 -14
  122. package/apps/pwa/out/showroom/index.html +1 -1
  123. package/apps/pwa/out/showroom/index.txt +14 -14
  124. package/apps/pwa/out/tasks/index.html +1 -1
  125. package/apps/pwa/out/tasks/index.txt +14 -14
  126. package/apps/pwa/out/term/index.html +1 -1
  127. package/apps/pwa/out/term/index.txt +14 -14
  128. package/apps/pwa/out/today/index.html +1 -1
  129. package/apps/pwa/out/today/index.txt +14 -14
  130. package/apps/pwa/out/trace/index.html +1 -1
  131. package/apps/pwa/out/trace/index.txt +14 -14
  132. package/apps/pwa/out/vault/index.html +1 -1
  133. package/apps/pwa/out/vault/index.txt +14 -14
  134. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  135. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  136. package/apps/pwa/out/workflows/index.html +1 -1
  137. package/apps/pwa/out/workflows/index.txt +14 -14
  138. package/apps/pwa/out/workspace/index.html +1 -1
  139. package/apps/pwa/out/workspace/index.txt +14 -14
  140. package/apps/pwa/out/worktrees/index.html +1 -1
  141. package/apps/pwa/out/worktrees/index.txt +14 -14
  142. package/examples/teams/personas/backend.yaml +4 -0
  143. package/examples/teams/personas/pm.yaml +4 -0
  144. package/examples/teams/personas/qa.yaml +4 -0
  145. package/examples/teams/small-product.yaml +20 -0
  146. package/graphs/context/nightly-condense.yaml +21 -0
  147. package/graphs/context/recipes.yaml +12 -0
  148. package/graphs/demo/inside-seed.yaml +20 -0
  149. package/graphs/demo/recipes.yaml +12 -0
  150. package/graphs/lecture/lecture-note.yaml +28 -0
  151. package/graphs/lecture/recipes.yaml +17 -0
  152. package/package.json +2 -1
  153. package/plugins/card-followup/examples/sample-card.png +0 -0
  154. package/scripts/context-hooks/emit.ts +36 -14
  155. package/scripts/coord-channel-watch.sh +1 -0
  156. package/scripts/discord-seat-probe.ts +144 -0
  157. package/scripts/hq/hq-standby.sh +65 -0
  158. package/scripts/hq/standby-promote.ts +173 -0
  159. package/scripts/hq/standby-role.ts +59 -0
  160. package/scripts/hq/standby-snapshot.ts +226 -0
  161. package/scripts/hq/standby-verify.ts +219 -0
  162. package/scripts/lecture-note/node.ts +256 -0
  163. package/scripts/lib/telegram-inject-args.ts +31 -0
  164. package/scripts/measure-pod-memory-by-goal.ts +130 -0
  165. package/scripts/release-loop/auto-approve-node.ts +1 -1
  166. package/scripts/release-loop/checklist-gate-node.ts +2 -1
  167. package/scripts/release-loop/gate-node.ts +61 -17
  168. package/scripts/release-loop/ops-upgrade-node.ts +59 -6
  169. package/scripts/release-loop/publish-node.ts +4 -1
  170. package/scripts/release-loop/version-node.ts +13 -3
  171. package/scripts/telegram-inject.ts +14 -6
  172. package/scripts/test-deterministic.ts +53 -3
  173. package/scripts/test-diet/remeasure-list.ts +112 -0
  174. package/scripts/ux/demo-rehearsal.ts +17 -13
  175. package/scripts/ux/demo-seed.ts +108 -0
  176. package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
  177. package/skills/omni-crawl/SKILL.md +16 -16
  178. package/skills/omni-crawl/package-lock.json +1 -544
  179. package/skills/omni-crawl/package.json +3 -4
  180. package/skills/omni-crawl/scripts/main.ts +13 -13
  181. package/skills/omni-crawl/scripts/monitor.ts +8 -8
  182. package/skills/omni-crawl/src/apify.ts +13 -2
  183. package/skills/omni-crawl/src/capture.ts +6 -0
  184. package/skills/omni-crawl/src/registry.ts +2 -2
  185. package/skills/omni-crawl/src/stdout-json.ts +4 -0
  186. package/skills/omni-digest/SKILL.md +10 -11
  187. package/skills/omni-digest/package-lock.json +0 -545
  188. package/skills/omni-digest/package.json +2 -5
  189. package/skills/omni-digest/scripts/main.ts +3 -3
  190. package/skills/omni-digest/src/delegate.ts +5 -5
  191. package/skills/omni-digest/src/fetch.ts +2 -2
  192. package/skills/omni-digest/src/media.ts +25 -11
  193. package/skills/omni-digest/src/visual.ts +3 -2
  194. package/skills/omni-market/scripts/main.ts +1 -1
  195. package/skills/omni-market/scripts/stdout-json.ts +4 -0
  196. package/src/agent/identity-env.ts +11 -0
  197. package/src/agent-mission/driver.ts +150 -23
  198. package/src/agent-mission/duo.ts +94 -0
  199. package/src/agent-mission/pty-decision.ts +2 -2
  200. package/src/autopilot/pty-control-loop.ts +4 -1
  201. package/src/autopilot/track-agent-shadow.ts +2 -1
  202. package/src/autopilot/track-agent.ts +4 -1
  203. package/src/boot/daemon-prompt-request.ts +3 -0
  204. package/src/boot/daemon-prompt-turn.ts +6 -2
  205. package/src/boot/daemon-runtime.ts +48 -16
  206. package/src/chat/index.ts +1 -0
  207. package/src/cli/decisions-cli.ts +51 -19
  208. package/src/cli/outputs-cli.ts +92 -0
  209. package/src/cli/persona-cli.ts +45 -13
  210. package/src/cli/pod-cli.ts +27 -3
  211. package/src/cli/pod-memory-advice.ts +35 -0
  212. package/src/cli/pr-cli.ts +15 -1
  213. package/src/cli/release-cli.ts +135 -15
  214. package/src/context-bus/condense.ts +194 -0
  215. package/src/context-bus/context-day.ts +82 -0
  216. package/src/context-bus/context-now-surfaces.ts +32 -2
  217. package/src/context-bus/context-now.ts +2 -2
  218. package/src/context-bus/coord-events.ts +2 -2
  219. package/src/context-bus/long-term-memory.ts +118 -22
  220. package/src/context-bus/nightly-condense.ts +153 -0
  221. package/src/context-bus/session-events.ts +35 -0
  222. package/src/dashboard/dashboard-status-lines.ts +2 -0
  223. package/src/dashboard/index.ts +8 -0
  224. package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
  225. package/src/decisions/decision-cards.ts +13 -7
  226. package/src/decisions/decision-ledger.ts +43 -17
  227. package/src/domains/outbound-alert.ts +99 -31
  228. package/src/domains/outbound-log-probe.ts +8 -0
  229. package/src/execution-loop/launch-gate.ts +17 -8
  230. package/src/field-note/telegram.ts +142 -0
  231. package/src/graph-kinds/registry.ts +2 -2
  232. package/src/graph-runner/graph-approval-card.ts +2 -2
  233. package/src/graph-runner/graph-grow-llm.ts +23 -13
  234. package/src/graph-runner/graph-grow.ts +69 -13
  235. package/src/graph-runner/graph-tick.ts +9 -2
  236. package/src/graph-runner/runner.ts +92 -24
  237. package/src/harness/harness-cli-command.ts +165 -17
  238. package/src/harness/harness-queue-child.ts +37 -0
  239. package/src/harness/harness-queue.ts +306 -0
  240. package/src/hooks/consult-intake.ts +153 -0
  241. package/src/hooks/github-shadow.ts +72 -2
  242. package/src/hooks/receiver.ts +82 -3
  243. package/src/hq/hq.ts +318 -0
  244. package/src/hq/lease.ts +210 -0
  245. package/src/hq/ledger-write-target.ts +41 -0
  246. package/src/index.ts +232 -16
  247. package/src/intake-plane/discord-seat-work.ts +36 -3
  248. package/src/intake-plane/linear-wish.ts +44 -0
  249. package/src/intake-plane/persona-answer.ts +5 -0
  250. package/src/intake-plane/telegram-seat-work.ts +13 -6
  251. package/src/intake-plane/wish-card.ts +33 -0
  252. package/src/llm/difficulty.ts +1 -1
  253. package/src/loops/checker.ts +178 -0
  254. package/src/loops/loop-cli.ts +37 -3
  255. package/src/loops/observe.ts +49 -0
  256. package/src/loops/orchestrator/overlap.ts +141 -0
  257. package/src/loops/orchestrator/traffic.ts +150 -0
  258. package/src/loops/package/verify.ts +125 -0
  259. package/src/loops/verdict.ts +22 -0
  260. package/src/maturity/feature-maturity.ts +5 -4
  261. package/src/memory.ts +37 -4
  262. package/src/nexus/api/card-followup-route.ts +118 -0
  263. package/src/nexus/api/decisions-route.ts +68 -0
  264. package/src/nexus/api/http-server.ts +78 -8
  265. package/src/nexus/api/outbound-report.ts +40 -7
  266. package/src/nexus/api/personas.ts +97 -54
  267. package/src/nexus/api/skill-exec-route.ts +97 -0
  268. package/src/nexus/api/task-cards-api.ts +19 -0
  269. package/src/nexus/api/workflow-history.ts +88 -0
  270. package/src/nexus/api/workflows.ts +52 -0
  271. package/src/nexus/outbound/delivery-ledger.ts +17 -8
  272. package/src/nexus/outbound/router.ts +8 -3
  273. package/src/persona/loader.ts +18 -0
  274. package/src/persona/persona-todo.ts +14 -9
  275. package/src/persona/presets.ts +10 -2
  276. package/src/persona/team.ts +105 -0
  277. package/src/persona/types.ts +4 -0
  278. package/src/plugins/maker/market-bundle.ts +2 -1
  279. package/src/pod-lease/host-lease.ts +130 -20
  280. package/src/release-loop/checklist.ts +15 -3
  281. package/src/release-loop/feature-store.ts +24 -10
  282. package/src/release-loop/landed-but-yellow.ts +60 -0
  283. package/src/seat-dispatch/seat-ask.ts +57 -12
  284. package/src/seat-loop/seat-loop.ts +253 -18
  285. package/src/seat-loop/seat-pack.ts +127 -0
  286. package/src/self-dev/ask-launch-flow.ts +3 -0
  287. package/src/self-dev/draft-sweep.ts +44 -10
  288. package/src/self-dev/draft-triage-rules.ts +54 -2
  289. package/src/self-implement/budget-gate.ts +29 -12
  290. package/src/self-implement/gate-candidate-ranking.ts +60 -0
  291. package/src/self-implement/gate-scope.ts +9 -8
  292. package/src/self-implement/headless-elanous-driver.ts +7 -2
  293. package/src/self-implement/l8-merge-queue.ts +157 -1
  294. package/src/self-implement/orchestrator.ts +15 -0
  295. package/src/self-implement/test-seams.ts +1 -0
  296. package/src/steward/launch.ts +5 -1
  297. package/src/steward/readiness.ts +125 -0
  298. package/src/steward/rescue.ts +75 -0
  299. package/src/steward/triage.ts +48 -9
  300. package/src/task-cards/card-cli.ts +78 -0
  301. package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
  302. package/src/task-orchestrator/surfaces/pod-lease.ts +18 -6
  303. package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +46 -8
  305. package/src/telegram-commands.ts +21 -0
  306. package/src/telegram-dispatch-forward.ts +1 -1
  307. package/src/telegram-fabric-plan.ts +90 -0
  308. package/src/telegram-project-command.ts +84 -4
  309. package/src/telegram.ts +37 -5
  310. package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
  311. package/src/user-config.ts +101 -13
  312. package/src/version/packed-revision.json +1 -1
  313. package/src/workflow-runtime/executor.ts +4 -0
  314. package/src/workflow-runtime/node-catalog.ts +13 -0
  315. package/src/workflow-runtime/nodes/knowledge.ts +38 -0
  316. package/src/workflow-runtime/schema.ts +17 -1
  317. package/src/workflow-runtime/types.ts +13 -1
  318. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12b6d2f8d2d1aea.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
  340. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
  341. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
  342. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
@@ -88,7 +88,7 @@ export type ControlDecision =
88
88
  | { readonly action: 'handoff'; readonly to: 'codex' | 'claude' | 'elanous'; readonly mission: string; readonly carry?: 'diff' | 'summary' }
89
89
  | { readonly action: 'ask-human'; readonly reason: string; readonly url?: string; readonly code?: string }
90
90
  /** `no-progress` reports observed lack of progress without claiming completion. */
91
- | { readonly action: 'no-progress'; readonly reason: string };
91
+ | { readonly action: 'no-progress'; readonly reason: string; readonly terminal?: boolean };
92
92
 
93
93
  export interface RunSupervisor {
94
94
  /** run supervisor의 관측 → 다음 행동. 동기/비동기 모두 허용(LLM 은 async). `signal` 이 abort 되면(사람 takeover
@@ -664,6 +664,9 @@ export async function runPtyControlLoop(
664
664
  previousIntervention = { ...intervention, sameScreenCount: 1 };
665
665
  }
666
666
  } else if (decision.action === 'wait' || decision.action === 'no-progress') {
667
+ if (decision.action === 'no-progress' && decision.terminal) {
668
+ return finish({ kind: 'stuck', iteration: step, reason: decision.reason }, step, intervention, true);
669
+ }
667
670
  // wait/no-progress — 종료된 자식은 새 출력을 낼 수 없으므로 대기 없이 즉시 수렴한다.
668
671
  if (deps.isAlive?.() === false) {
669
672
  debug.log('autopilot.control', 'child-exited', { step });
@@ -2,6 +2,7 @@ import { execFile } from 'node:child_process';
2
2
  import { appendFileSync, mkdirSync, readFileSync, rmdirSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { promisify } from 'node:util';
5
+ import { stateDirSourceForChild } from '../agent/identity-env.js';
5
6
  import { effectiveInstanceRoot } from '../instance/resolve.js';
6
7
  import type { TrackAgentDecision, TrackAgentInput, TrackAgentResult } from './track-agent.js';
7
8
 
@@ -127,7 +128,7 @@ export async function launchTrackAgentShadow(input: TrackAgentInput, options: Sh
127
128
  const instanceRoot = effectiveInstanceRoot();
128
129
  const { stdout } = await exec('bun', ['bin/elanous.mjs', '--config-dir', instanceRoot, ...values], {
129
130
  cwd: process.env.ELANOUS_TOOL_CWD?.trim() || resolve(import.meta.dir, '../..'),
130
- env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot },
131
+ env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(instanceRoot) },
131
132
  encoding: 'utf8', maxBuffer: 1024 * 1024,
132
133
  });
133
134
  return { stdout };
@@ -2,6 +2,7 @@ import { execFile, spawn } from 'node:child_process';
2
2
  import { readdirSync, readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { promisify } from 'node:util';
5
+ import { stateDirSourceForChild } from '../agent/identity-env.js';
5
6
  import { debug, redactSecretText } from '../debug/log.js';
6
7
  import { getOrCreatePersonaSession } from '../session/index.js';
7
8
  import { getUserConfig } from '../user-config.js';
@@ -38,7 +39,9 @@ const log = (event: string, data: Record<string, unknown>) => {
38
39
  };
39
40
  /** The daemon's own universe, resolved once and pinned on the child (config ⊕ state) — the harness isolates its own workers. */
40
41
  export function universeLaunch(args: readonly string[], root: string = effectiveInstanceRoot()): { argv: string[]; env: NodeJS.ProcessEnv } {
41
- return { argv: ['bin/elanous.mjs', '--config-dir', root, ...args], env: { ...process.env, ELANOUS_STATE_DIR: root } };
42
+ return { argv: ['bin/elanous.mjs', '--config-dir', root, ...args], env: {
43
+ ...process.env, ELANOUS_STATE_DIR: root, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root),
44
+ } };
42
45
  }
43
46
  const runCli = async (args: string[]): Promise<{ stdout: string }> => {
44
47
  const { argv, env } = universeLaunch(args);
@@ -38,6 +38,7 @@ export interface DaemonPromptBody {
38
38
  * (`none` · `readonly` · `chat` · `webterm`); invalid values reject
39
39
  * the request rather than silently falling back. */
40
40
  tools?: DaemonToolSurfaceKind;
41
+ personaId?: string;
41
42
  }
42
43
 
43
44
  export interface DaemonPromptRequest {
@@ -53,6 +54,7 @@ export interface DaemonPromptRequest {
53
54
  * not opt in; the caller-side (`handlePromptStreamPost`) interprets
54
55
  * null as "use the daemon's boot-time surface". */
55
56
  tools: DaemonToolSurfaceKind | null;
57
+ personaId?: string;
56
58
  }
57
59
 
58
60
  export type DaemonPromptRequestParseResult =
@@ -117,6 +119,7 @@ export function parseDaemonPromptBody(
117
119
  body.source ? writeInputSourceMeta(body.source) : undefined,
118
120
  ),
119
121
  tools: body.tools ?? null,
122
+ ...(typeof body.personaId === 'string' && body.personaId.trim() ? { personaId: body.personaId } : {}),
120
123
  },
121
124
  };
122
125
  }
@@ -10,7 +10,7 @@ import {
10
10
  appendUserPromptBlocksAndBuildMessages,
11
11
  } from './daemon-history-helper.js';
12
12
  import type { ContentBlock as AcpContentBlock } from '@agentclientprotocol/sdk';
13
- import type { DaemonSessionHistory } from './daemon-runtime.js';
13
+ import { personaPromptForChat, type DaemonSessionHistory } from './daemon-runtime.js';
14
14
  import type { DaemonPromptRequest } from './daemon-prompt-request.js';
15
15
  import type { DaemonToolSurface } from './daemon-tools/types.js';
16
16
  import type { ConfirmChannel } from '../hitl/confirm.js';
@@ -199,8 +199,9 @@ export async function runDaemonPromptTurn(opts: {
199
199
  // …" + active terminals) to the system prompt so the LLM has the
200
200
  // session id + terminal ids without an extra discovery call. Helper
201
201
  // returns the original prompt unchanged when there's nothing to add.
202
+ const personaPrompt = await personaPromptForChat(request.personaId);
202
203
  const augmentedSystemPrompt = appendWebtermContext(
203
- request.effectiveSystemPrompt,
204
+ personaPrompt ? [personaPrompt, request.effectiveSystemPrompt].filter(Boolean).join('\n\n') : request.effectiveSystemPrompt,
204
205
  request.sessionId,
205
206
  activeToolSurface,
206
207
  );
@@ -291,6 +292,9 @@ export async function runDaemonPromptTurn(opts: {
291
292
  if (!isFirstTurn && opts.projectInstructions) {
292
293
  messages.unshift({ role: 'system', content: opts.projectInstructions });
293
294
  }
295
+ if (!isFirstTurn && personaPrompt) {
296
+ messages.unshift({ role: 'system', content: personaPrompt });
297
+ }
294
298
  const result = await runCoreTurn({
295
299
  sessionId: request.sessionId,
296
300
  userText: request.userText,
@@ -55,6 +55,33 @@ import { elanousSelfAccessPrompt } from '../agent/self-ambient.js';
55
55
  import { createToolCwdResolver, type ToolCwdResolver } from './tool-cwd.js';
56
56
  import { resolveSessionProjectContext } from '../project/session-context.js';
57
57
  import { debug } from '../debug/log.js';
58
+ import { resolvePersonaAddress } from '../intake-plane/persona-answer.js';
59
+ import { awaitGlobalPersonaLoad, getGlobalPersonaRegistry } from '../persona/global-registry.js';
60
+
61
+ const turnPersonaPrompt = new AsyncLocalStorage<string | undefined>();
62
+
63
+ export function withChatPersonaPrompt<T>(prompt: string | undefined, run: () => T): T {
64
+ return turnPersonaPrompt.run(prompt, run);
65
+ }
66
+
67
+ export function prependChatPersonaOnLaterTurn(
68
+ messages: LLMMessage[], isFirstTurn: boolean,
69
+ ): LLMMessage[] {
70
+ const prompt = turnPersonaPrompt.getStore();
71
+ if (!isFirstTurn && prompt) messages.unshift({ role: 'system', content: prompt });
72
+ return messages;
73
+ }
74
+
75
+ export async function personaPromptForChat(
76
+ id: unknown,
77
+ deps: { load: () => Promise<unknown>; registry: () => Parameters<typeof resolvePersonaAddress>[1] } = {
78
+ load: awaitGlobalPersonaLoad, registry: getGlobalPersonaRegistry,
79
+ },
80
+ ): Promise<string | undefined> {
81
+ if (typeof id !== 'string' || !id.trim()) return undefined;
82
+ await deps.load();
83
+ return resolvePersonaAddress(id, deps.registry())?.systemPrompt;
84
+ }
58
85
 
59
86
  /** C4 (2026-07-12) — arm the non-detached-PTY kill ONCE per turn signal.
60
87
  * Multiple tool calls in one turn share the same AbortSignal instance;
@@ -135,7 +162,7 @@ export function composeDaemonSystemPrompt(
135
162
  promptMeta: Readonly<Record<string, unknown>> | undefined,
136
163
  sessionId?: string,
137
164
  ): string {
138
- return [basePrompt, buildDaemonInputSourceLine(promptMeta), elanousSelfAccessPrompt(sessionId)]
165
+ return [turnPersonaPrompt.getStore(), basePrompt, buildDaemonInputSourceLine(promptMeta), elanousSelfAccessPrompt(sessionId)]
139
166
  .filter((part): part is string => typeof part === 'string' && part.trim().length > 0)
140
167
  .join('\n\n');
141
168
  }
@@ -604,12 +631,11 @@ export function createDaemonRunTurn(
604
631
  const hasNonTextBlock = !!promptBlocks?.some(
605
632
  (b) => (b as { type?: string }).type !== 'text',
606
633
  );
607
- if (hasNonTextBlock) {
608
- return appendUserPromptBlocksAndBuildMessages(
609
- history, sessionId, promptBlocks!, systemPrompt,
610
- );
611
- }
612
- return appendUserAndBuildMessages(history, sessionId, userText, systemPrompt);
634
+ const isFirstTurn = history.get(sessionId).length === 0;
635
+ const messages = hasNonTextBlock
636
+ ? appendUserPromptBlocksAndBuildMessages(history, sessionId, promptBlocks!, systemPrompt)
637
+ : appendUserAndBuildMessages(history, sessionId, userText, systemPrompt);
638
+ return prependChatPersonaOnLaterTurn(messages, isFirstTurn);
613
639
  },
614
640
  getTools: () => surface.specs,
615
641
  dispatchTool: terminalCapableDispatch(surfaceHasPtyShell, async (name, args, ctx) => {
@@ -723,15 +749,21 @@ export function createDaemonRunTurn(
723
749
  });
724
750
  debugBridge.activate();
725
751
  }
726
- const projectCwd = resolveSessionProjectContext(turnCtx.sessionId)?.cwd;
727
- if (projectCwd || opts.acpSessionCwd) {
728
- // An ACP session's explicit cwd wins; the project folder is only the default when none was given (IA3 review).
729
- const explicitCwd = opts.acpSessionCwd && turnCtx.cwd ? turnCtx.cwd : undefined;
730
- const resolver = createToolCwdResolver({ tools: opts.tools ?? 'none', toolCwd: explicitCwd ?? projectCwd ?? turnCtx.cwd });
731
- await turnToolCwd.run(resolver, () => inner(turnCtx));
732
- } else {
733
- await inner(turnCtx);
734
- }
752
+ const elanousMeta = turnCtx.promptMeta?.elanous;
753
+ const personaId = elanousMeta && typeof elanousMeta === 'object' && !Array.isArray(elanousMeta)
754
+ && 'pwaPersonaId' in elanousMeta ? elanousMeta.pwaPersonaId : undefined;
755
+ const personaPrompt = await personaPromptForChat(personaId);
756
+ await withChatPersonaPrompt(personaPrompt, async () => {
757
+ const projectCwd = resolveSessionProjectContext(turnCtx.sessionId)?.cwd;
758
+ if (projectCwd || opts.acpSessionCwd) {
759
+ // An ACP session's explicit cwd wins; the project folder is only the default when none was given (IA3 review).
760
+ const explicitCwd = opts.acpSessionCwd && turnCtx.cwd ? turnCtx.cwd : undefined;
761
+ const resolver = createToolCwdResolver({ tools: opts.tools ?? 'none', toolCwd: explicitCwd ?? projectCwd ?? turnCtx.cwd });
762
+ await turnToolCwd.run(resolver, () => inner(turnCtx));
763
+ } else {
764
+ await inner(turnCtx);
765
+ }
766
+ });
735
767
  } finally {
736
768
  debugBridge?.dispose();
737
769
  }
package/src/chat/index.ts CHANGED
@@ -438,6 +438,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
438
438
  { name: 'clear', aliases: ['cls'], description: '대화 기록 지우기' },
439
439
  { name: 'status', aliases: ['st'], description: '지금 상태 — 모델 · 연결 · 계정' },
440
440
  { name: 'now', aliases: [], description: '현재 맥락과 출처 표 — /now [주제]' },
441
+ { name: 'wish', aliases: [], description: '소원을 카드로 남기기 — /wish <소원 한 줄>' },
441
442
  { name: 'remaining', aliases: [], description: '계정별 남은 사용량' },
442
443
  { name: 'setup', aliases: [], description: '상세 설정 — /setup · /setup reset(다음 실행 때 마법사 다시)', subcommands: ['help', 'reset'] },
443
444
  { name: 'quit', aliases: ['q', 'exit'], description: '나가기' },
@@ -1,4 +1,10 @@
1
- import type { Command } from 'commander';
1
+ import { Option, type Command } from 'commander';
2
+ import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
3
+ import { fileLeaseStore } from '../hq/lease.js';
4
+ import { join } from 'node:path';
5
+ import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
6
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
7
+ import { getUserConfig } from '../user-config.js';
2
8
  import { projectDecisionsToLinear } from '../decisions/decision-linear-projection.js';
3
9
  import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
4
10
 
@@ -46,13 +52,19 @@ export function formatDecisionDetail(e: DecisionEntry): string {
46
52
  ...(e.refs?.length ? [`참조: ${e.refs.join(' · ')}`] : [])].join('\n');
47
53
  }
48
54
 
49
- export function registerDecisionsCommands(program: Command, config: DecisionLedgerOptions = {}, out: Pick<Console, 'log'> = console): void {
50
- const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용');
55
+ export function registerDecisionsCommands(program: Command, config: DecisionLedgerOptions = {}, out: Pick<Console, 'log'> = console, hqDeps: HqDeps = {}): void {
56
+ const noXcheck = new Option('--no-xcheck <이유>', '교차 확인 생략 이유');
57
+ // This flag takes a reason, not Commander's negated boolean default.
58
+ noXcheck.negate = false;
59
+ const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용').option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
60
+ const mayWrite = (command: string, override?: boolean) => hqCliWriteAllowed(`decisions ${command}`, Boolean(override || root.opts().hqOverride),
61
+ (getElanousConfigDirOverride() || config.stateDir) && !hqDeps.store && !getUserConfig().hq?.arbiter ? { ...hqDeps, store: fileLeaseStore(join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'lease.json')), seenPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'seen-generation'), localPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'local.json') } : hqDeps);
51
62
  const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
52
63
  const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
53
64
  root.command('linear-sync').description('열린 결정을 COO Linear 프로젝트에 투영하고 닫힌 결정을 동기화한다')
54
- .option('--dry-run').option('--json')
55
- .action(async (o: { dryRun?: boolean; json?: boolean }) => {
65
+ .option('--dry-run').option('--json').option('--hq-override')
66
+ .action(async (o: { dryRun?: boolean; json?: boolean; hqOverride?: boolean }) => {
67
+ if (!o.dryRun && !mayWrite('linear-sync', o.hqOverride)) return;
56
68
  const result = await projectDecisionsToLinear({ stateDir: config.stateDir, dryRun: o.dryRun });
57
69
  emit(result, o.json, result.reason ?? `생성 ${result.created} · 닫음 ${result.closed} · 건너뜀 ${result.skipped} · 실패 ${result.failed}${result.plan?.length ? `\n${result.plan.map(p => `${p.action} ${p.decisionId}${p.issue ? ` ${p.issue}` : ''}`).join('\n')}` : ''}`);
58
70
  });
@@ -61,21 +73,36 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
61
73
  .requiredOption('--s <text>').requiredOption('--c <text>').option('--q <text>').option('--a <text>')
62
74
  .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
63
75
  .option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
76
+ .option('--xcheck <SEAT:메모>', '교차 확인 (반복)', repeat, [] as string[])
77
+ .addOption(noXcheck)
78
+ .option('--alternative <key>').option('--dissent <text>')
64
79
  .option('--track <track>').option('--agent <agent>').option('--session <id>')
65
80
  .option('--resume-question <qid>').option('--run <runId>')
66
81
  .option('--ref <url>', '참조 (반복)', repeat, [] as string[])
67
82
  .option('--due <when>', '기한 — UTC ISO(2026-10-04T09:00:00Z) 또는 +Nh(지금부터 N시간) · 기한 2시간 전 텔레그램·디스코드로 다시 알린다')
68
- .option('--json')
69
- .action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; resumeQuestion?: string; run?: string; ref: string[]; json?: boolean }) => fail(() => {
83
+ .option('--json').option('--hq-override')
84
+ .action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; xcheck: string[]; noXcheck?: string; alternative?: string; dissent?: string; track?: DecisionTrack; agent?: string; session?: string; resumeQuestion?: string; run?: string; ref: string[]; json?: boolean; hqOverride?: boolean }) => fail(() => {
70
85
  if (o.run !== undefined && o.resumeQuestion === undefined) throw new Error('--run requires --resume-question');
71
86
  if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
72
87
  if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
73
88
  const options: DecisionOption[] = o.option.map(parseOption);
89
+ if (o.xcheck.length && o.noXcheck !== undefined) throw new Error('choose --xcheck or --no-xcheck, not both');
90
+ const crossCheck = o.xcheck.map(raw => {
91
+ const match = /^([^:\s]+):(.+)$/.exec(raw);
92
+ if (!match || !match[2]!.trim()) throw new Error(`invalid --xcheck: ${raw} (expected SEAT:메모)`);
93
+ return { seat: match[1]!, at: new Date().toISOString(), note: match[2]!.trim() };
94
+ });
95
+ if (!crossCheck.length && o.noXcheck === undefined && getUserConfig().decisions?.requireCrossCheck) throw new Error('교차 확인 없음 — --xcheck SEAT:메모 또는 --no-xcheck 이유');
96
+ const who = agent(o.agent);
97
+ if (!mayWrite('raise', o.hqOverride)) return;
74
98
  const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
99
+ ...(crossCheck.length ? { crossCheck } : { crossCheckSkipped: o.noXcheck ?? 'missing' }),
100
+ ...(o.alternative !== undefined ? { alternative: o.alternative } : {}), ...(o.dissent !== undefined ? { dissent: o.dissent } : {}),
75
101
  recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
76
- raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
102
+ raisedBy: { agent: who, ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
77
103
  ...(o.resumeQuestion !== undefined ? { resume: { questionId: o.resumeQuestion, ...(o.run !== undefined ? { runId: o.run } : {}) } } : {}),
78
104
  ...(o.due ? { dueAt: dueAt(o.due) } : {}) });
105
+ if (entry.crossCheckSkipped === 'missing') console.error('교차 확인 없음 — --xcheck SEAT:메모 또는 --no-xcheck 이유');
79
106
  emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
80
107
  }));
81
108
  root.command('list').description('결정 목록 (기본 열린 것)')
@@ -88,11 +115,12 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
88
115
  root.command('show <id>').description('SCQA · 선택지 · 권고 · 이력 · 판').option('--json')
89
116
  .action((id: string, o: { json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).show(id); emit(entry, o.json, formatDecisionDetail(entry)); }));
90
117
  root.command('decide <id> <option>').description('사람 또는 AUTO 위임 결정 기록')
91
- .option('--note <text>').option('--auto').option('--delegation <reason>').option('--track <track>').option('--agent <agent>').option('--json')
92
- .action((id: string, choice: string, o: { note?: string; auto?: boolean; delegation?: string; track?: DecisionTrack; agent?: string; json?: boolean }) => fail(() => {
118
+ .option('--note <text>').option('--auto').option('--delegation <reason>').option('--track <track>').option('--agent <agent>').option('--json').option('--hq-override')
119
+ .action((id: string, choice: string, o: { note?: string; auto?: boolean; delegation?: string; track?: DecisionTrack; agent?: string; json?: boolean; hqOverride?: boolean }) => fail(() => {
93
120
  if (o.auto && !o.delegation?.trim()) throw new Error('--auto requires --delegation');
94
121
  if (!o.auto && (o.delegation || o.track || o.agent)) throw new Error('delegation/track/agent require --auto');
95
122
  const by = o.auto ? { kind: 'auto' as const, agent: agent(o.agent), delegation: o.delegation!, ...(o.track ? { track: o.track } : {}) } : { kind: 'human' as const };
123
+ if (!mayWrite('decide', o.hqOverride)) return;
96
124
  const { entry, delivery } = new DecisionLedger(config).decideWithDelivery(id, choice, by, o.note);
97
125
  if (delivery && !delivery.ok) {
98
126
  // The decision is recorded; only the answer to the waiting run failed — say so and how to retry, never «success».
@@ -103,22 +131,26 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
103
131
  }
104
132
  emit(delivery ? { ...entry, delivery } : entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
105
133
  }));
106
- root.command('retry-answer <id>').description('기록된 결정의 대기 질문 답 전달만 재시도한다').option('--json')
107
- .action((id: string, o: { json?: boolean }) => fail(() => {
134
+ root.command('retry-answer <id>').description('기록된 결정의 대기 질문 답 전달만 재시도한다').option('--json').option('--hq-override')
135
+ .action((id: string, o: { json?: boolean; hqOverride?: boolean }) => fail(() => {
136
+ if (!mayWrite('retry-answer', o.hqOverride)) return;
108
137
  const entry = new DecisionLedger(config).retryAnswer(id);
109
138
  emit(entry, o.json, `답 전달: ${entry.id} → ${entry.resume?.questionId}`);
110
139
  }));
111
140
  root.command('add-options <id>').description('선택지가 미기재된 과거 항목에 확인된 선택지를 추가')
112
141
  .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
113
- .option('--agent <agent>').option('--json')
114
- .action((id: string, o: { option: string[]; agent?: string; json?: boolean }) => fail(() => {
115
- const entry = new DecisionLedger(config).addOptions(id, o.option.map(parseOption), agent(o.agent));
142
+ .option('--agent <agent>').option('--json').option('--hq-override')
143
+ .action((id: string, o: { option: string[]; agent?: string; json?: boolean; hqOverride?: boolean }) => fail(() => {
144
+ const who = agent(o.agent);
145
+ if (!mayWrite('add-options', o.hqOverride)) return;
146
+ const entry = new DecisionLedger(config).addOptions(id, o.option.map(parseOption), who);
116
147
  emit(entry, o.json, formatDecisionDetail(entry));
117
148
  }));
118
- root.command('withdraw <id>').description('결정 철회도 원장에 남긴다').requiredOption('--reason <reason>').option('--json')
119
- .action((id: string, o: { reason: string; json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).withdraw(id, o.reason); emit(entry, o.json, `철회: ${formatDecisionDetail(entry)}`); }));
120
- root.command('import-markdown <file>').description('기존 대표 결정 문서 씨앗을 가져온다 (재실행 안전)').option('--json')
121
- .action((file: string, o: { json?: boolean }) => fail(() => {
149
+ root.command('withdraw <id>').description('결정 철회도 원장에 남긴다').requiredOption('--reason <reason>').option('--json').option('--hq-override')
150
+ .action((id: string, o: { reason: string; json?: boolean; hqOverride?: boolean }) => fail(() => { if (!mayWrite('withdraw', o.hqOverride)) return; const entry = new DecisionLedger(config).withdraw(id, o.reason); emit(entry, o.json, `철회: ${formatDecisionDetail(entry)}`); }));
151
+ root.command('import-markdown <file>').description('기존 대표 결정 문서 씨앗을 가져온다 (재실행 안전)').option('--json').option('--hq-override')
152
+ .action((file: string, o: { json?: boolean; hqOverride?: boolean }) => fail(() => {
153
+ if (!mayWrite('import-markdown', o.hqOverride)) return;
122
154
  const result = importDecisionMarkdown(new DecisionLedger(config), file);
123
155
  emit(result, o.json, `가져옴 ${result.imported.length} · 기존 ${result.existing.length} · 불완전 ${result.incomplete.length} · 못 읽음 ${result.unread.length}${result.incomplete.length ? `\n${result.incomplete.join('\n')}` : ''}${result.unread.length ? `\n${result.unread.join('\n')}` : ''}`);
124
156
  if (result.unread.length) process.exitCode = 1;
@@ -0,0 +1,92 @@
1
+ import { accessSync, constants, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import type { Command } from 'commander';
4
+ import { debug } from '../debug/log.js';
5
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
6
+ import type { OutputEntry } from '../outputs/ledger.js';
7
+ import { writeStdoutJson } from './stdout-json.js';
8
+
9
+ type ListOptions = { since?: string; source?: string; limit?: string; json?: boolean };
10
+
11
+ function sinceIso(value: string): string {
12
+ if (value === '24h' || value === '7d') {
13
+ return new Date(Date.now() - (value === '24h' ? 24 : 7 * 24) * 60 * 60 * 1000).toISOString();
14
+ }
15
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/.test(value)
16
+ || !Number.isFinite(Date.parse(value))
17
+ || new Date(`${value.slice(0, 10)}T00:00:00Z`).toISOString().slice(0, 10) !== value.slice(0, 10)) {
18
+ throw new Error(`invalid --since: ${value} (ISO|24h|7d)`);
19
+ }
20
+ return new Date(value).toISOString();
21
+ }
22
+
23
+ // listOutputs intentionally tolerates unreadable months. Detect inaccessible ledger files here so the CLI
24
+ // does not report an incomplete read as a genuine zero (including when run as root under chmod 000).
25
+ function assertReadableLedger(root: string): void {
26
+ const dir = join(root, 'outputs');
27
+ let names: string[];
28
+ try {
29
+ const info = statSync(dir);
30
+ if (!info.isDirectory() || (info.mode & 0o444) === 0 || (info.mode & 0o111) === 0) {
31
+ throw new Error(`outputs ledger directory unreadable: ${dir}`);
32
+ }
33
+ accessSync(dir, constants.R_OK | constants.X_OK);
34
+ names = readdirSync(dir);
35
+ } catch (error) {
36
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;
37
+ throw error;
38
+ }
39
+ for (const name of names.filter(name => /^outputs-\d{4}-(0[1-9]|1[0-2])\.jsonl$/.test(name) || name === 'outputs.jsonl')) {
40
+ const file = join(dir, name);
41
+ const info = statSync(file);
42
+ if (!info.isFile() || (info.mode & 0o444) === 0) throw new Error(`outputs ledger file unreadable: ${file}`);
43
+ accessSync(file, constants.R_OK);
44
+ }
45
+ }
46
+
47
+ export function registerOutputsCommands(program: Command): void {
48
+ program.command('outputs').description('산출물 원장 조회 (읽기 전용)')
49
+ .command('list').description('산출물 최신순 목록과 종류별 수')
50
+ .option('--since <ISO|24h|7d>', '이 시각 이후')
51
+ .option('--source <source>', 'exec|field-reel|field-feed')
52
+ .option('--limit <N>', '최대 건수 (기본 50)', '50')
53
+ .option('--json', 'JSON 출력')
54
+ .action(async (options: ListOptions) => {
55
+ try {
56
+ if (options.source !== undefined && !['exec', 'field-reel', 'field-feed'].includes(options.source)) {
57
+ throw new Error(`invalid --source: ${options.source} (exec|field-reel|field-feed)`);
58
+ }
59
+ if (!options.limit || !/^[1-9]\d*$/.test(options.limit) || !Number.isSafeInteger(Number(options.limit))) {
60
+ throw new Error(`invalid --limit: ${options.limit}`);
61
+ }
62
+ const scope = {
63
+ since: options.since === undefined ? undefined : sinceIso(options.since),
64
+ source: options.source as OutputEntry['source'] | undefined,
65
+ limit: Number(options.limit),
66
+ };
67
+ const displayedScope = { since: scope.since ?? null, source: scope.source ?? null, limit: scope.limit };
68
+ const root = effectiveInstanceRoot();
69
+ assertReadableLedger(root);
70
+ const { listOutputs } = await import('../outputs/ledger.js');
71
+ const items = listOutputs(scope, root);
72
+ const byKind: Record<string, number> = {};
73
+ for (const item of items) byKind[item.kind] = (byKind[item.kind] ?? 0) + 1;
74
+ debug.log('outputs.cli', 'list', { count: items.length, source: scope.source, since: scope.since });
75
+ if (options.json) {
76
+ await writeStdoutJson(`${JSON.stringify({ items, count: items.length, byKind, scope: displayedScope })}\n`);
77
+ return;
78
+ }
79
+ for (const item of items) {
80
+ const at = Number.isFinite(Date.parse(item.at))
81
+ ? new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', dateStyle: 'short', timeStyle: 'medium', hourCycle: 'h23' }).format(new Date(item.at))
82
+ : item.at;
83
+ console.log(`${at} KST · ${item.kind} · ${item.title} · ${item.source}/${item.sourceId} · ${item.seat ?? '—'} · ${item.path ?? item.url ?? '—'}`);
84
+ }
85
+ if (!items.length) console.log(`scope: ${JSON.stringify(displayedScope)}`);
86
+ console.log(`${items.length}개 · 종류별 수 ${JSON.stringify(byKind)}`);
87
+ } catch (error) {
88
+ console.error(`outputs list: ${error instanceof Error ? error.message : String(error)}`);
89
+ process.exitCode = 1;
90
+ }
91
+ });
92
+ }
@@ -7,8 +7,10 @@ import { parse as parseYaml } from 'yaml';
7
7
  import { debug } from '../debug/log.js';
8
8
  import { parsePersonaYaml } from '../persona/loader.js';
9
9
  import { resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
10
- import { choosePersonaId, clonePreset, describePreset, editPersona, findPreset, loadPresetIndex, loadPresets, presetToProfileYaml } from '../persona/presets.js';
10
+ import { UnknownPersonaSchemaKeyError, choosePersonaId, clonePreset, describePreset, editPersona, findPreset, loadPresetIndex, loadPresets, presetToProfileYaml } from '../persona/presets.js';
11
11
  import { addTeamSet, listTeamSets, removeTeamSet } from '../persona/team-set.js';
12
+ import { validateTeamFile } from '../persona/team.js';
13
+ import { runPersonaLoopOnce } from '../seat-loop/seat-loop.js';
12
14
 
13
15
  interface StoredPersona { personaId: string; displayName: string; description?: string; preset?: string; title?: string; path: string }
14
16
 
@@ -43,11 +45,11 @@ function mapping(value: unknown): value is Record<string, unknown> {
43
45
 
44
46
  function setEdit(edits: Record<string, unknown>, assignment: string): void {
45
47
  const equals = assignment.indexOf('=');
46
- if (equals < 1) throw new Error(`--set requires key=value: ${assignment}`);
48
+ if (equals < 0) throw new Error(`--set requires key=value: ${assignment}`);
47
49
  const key = assignment.slice(0, equals);
48
50
  const parts = key.split('.');
49
51
  if (parts.some((part) => !part || ['__proto__', 'prototype', 'constructor'].includes(part))) {
50
- throw new Error(`invalid --set key: ${key}`);
52
+ throw new UnknownPersonaSchemaKeyError(key);
51
53
  }
52
54
  const value: unknown = parseYaml(assignment.slice(equals + 1));
53
55
  let target = edits;
@@ -68,6 +70,18 @@ function editOptions(fromFile: string | undefined, assignments: readonly string[
68
70
  }
69
71
 
70
72
  export function registerPersonaCommands(program: Command): void {
73
+ program.command('team').description('Validate a YAML team definition')
74
+ .command('validate <file>').description('Check team schema, persona references and handoff order')
75
+ .action((file: string) => {
76
+ try {
77
+ const definition = validateTeamFile(file);
78
+ console.log(`Valid team ${definition.name}`);
79
+ } catch (error) {
80
+ console.error(error instanceof Error ? error.message.replace(/\s+/g, ' ').trim() : String(error));
81
+ process.exitCode = 2;
82
+ }
83
+ });
84
+
71
85
  const team = program.command('team-set').description('Install, remove and list persona team sets');
72
86
  team.command('add <file>').description('Install a YAML team set').action((file: string) => {
73
87
  try { const result = addTeamSet(file); console.log(`Installed team-set ${result['team-set']}`); }
@@ -94,7 +108,7 @@ export function registerPersonaCommands(program: Command): void {
94
108
  if (opts.json) { console.log(JSON.stringify({ presets: presets.map((p) => ({ id: p.personaId, displayName: p.displayName, role: p.role, oneLine: p.oneLine })), mine }, null, 2)); return; }
95
109
  if (!presets.length) console.log('페르소나 프리셋을 찾지 못했습니다(persona-presets/ 가 이 설치본에 없습니다).');
96
110
  else {
97
- console.log('프리셋 — 골라서 내 페르소나로 만들기: elanous persona add <프리셋> [--name <이름>]');
111
+ console.log('프리셋 — 골라서 내 페르소나로 만들기: elanous persona add <이름> [--from <프리셋>]');
98
112
  const width = Math.max(...presets.map((p) => p.personaId.length));
99
113
  for (const p of presets) console.log(` ${p.personaId.padEnd(width)} ${p.displayName} — ${p.role} · ${p.oneLine}`);
100
114
  }
@@ -105,24 +119,27 @@ export function registerPersonaCommands(program: Command): void {
105
119
  }
106
120
  });
107
121
 
108
- persona.command('add <preset>')
109
- .description('Make your own persona from a preset (id, name or role) — the preset file is not changed')
122
+ persona.command('add <name>')
123
+ .description('Make your own persona from a preset — the preset file is not changed')
124
+ .option('--from <preset>', 'Preset to clone (defaults to the named preset)')
110
125
  .option('--name <name>', 'What to call it (default: the preset’s first name)')
111
126
  .option('--as <name>', 'Clone this preset under a new, unique name')
112
127
  .option('--title <title>', 'Optional title, e.g. CMO')
113
128
  .option('--json', 'JSON output')
114
- .action((query: string, opts: { name?: string; as?: string; title?: string; json?: boolean }) => {
129
+ .action((query: string, opts: { from?: string; name?: string; as?: string; title?: string; json?: boolean }) => {
115
130
  const presets = loadPresets();
116
- const preset = findPreset(presets, query);
131
+ const presetQuery = opts.from ?? query;
132
+ const preset = findPreset(presets, presetQuery);
117
133
  if (!preset) {
118
- console.error(`프리셋 «${query}» 을 찾지 못했습니다. 있는 것: ${presets.map((p) => p.personaId).join(', ')}`);
134
+ console.error(`프리셋 «${presetQuery}» 을 찾지 못했습니다. 있는 것: ${presets.map((p) => p.personaId).join(', ')}`);
119
135
  process.exitCode = 1;
120
136
  return;
121
137
  }
122
- if (opts.as !== undefined) {
138
+ if (opts.as !== undefined || opts.from !== undefined) {
123
139
  try {
124
- if (opts.name !== undefined || opts.title !== undefined) throw new Error('--as cannot be combined with --name or --title');
125
- const result = clonePreset(preset, opts.as);
140
+ if (opts.from !== undefined && (opts.as !== undefined || opts.name !== undefined || opts.title !== undefined)) throw new Error('--from cannot be combined with --as, --name or --title');
141
+ if (opts.as !== undefined && (opts.name !== undefined || opts.title !== undefined)) throw new Error('--as cannot be combined with --name or --title');
142
+ const result = clonePreset(preset, opts.from !== undefined ? query : opts.as!);
126
143
  debug.log('persona.cli', 'cloned', { preset: preset.personaId, personaId: result.personaId });
127
144
  if (opts.json) console.log(JSON.stringify({ personaId: result.personaId, displayName: result.displayName, preset: preset.personaId, path: result.path }, null, 2));
128
145
  else {
@@ -170,7 +187,22 @@ export function registerPersonaCommands(program: Command): void {
170
187
  console.log(` 파일: ${result.path}`);
171
188
  }
172
189
  } catch (err) {
173
- console.error(err instanceof Error ? err.message : String(err));
190
+ console.error(err instanceof Error ? err.message.replace(/\s+/g, ' ').trim() : String(err));
191
+ process.exitCode = err instanceof UnknownPersonaSchemaKeyError ? 2 : 1;
192
+ }
193
+ });
194
+
195
+ persona.command('loop <name>')
196
+ .description('Shadow-pick one todo for a saved persona without executing it')
197
+ .requiredOption('--once', 'Pick exactly one todo')
198
+ .option('--json', 'JSON output')
199
+ .action(async (name: string, opts: { json?: boolean }) => {
200
+ try {
201
+ const entry = await runPersonaLoopOnce(name);
202
+ if (opts.json) console.log(JSON.stringify(entry));
203
+ else console.log(entry.todo ? `그림자 선택 — ${entry.todo.title} (${entry.todo.id})` : '할 일 없음');
204
+ } catch (error) {
205
+ console.error(error instanceof Error ? error.message : String(error));
174
206
  process.exitCode = 1;
175
207
  }
176
208
  });
@@ -3,11 +3,13 @@
3
3
 
4
4
  import type { Command } from 'commander';
5
5
  import { debug } from '../debug/log.js';
6
+ import { readPodMemoryAdvice } from './pod-memory-advice.js';
6
7
  import { getUserConfig } from '../user-config.js';
7
8
  import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
8
- import { parsePodPool, resolvePodPoolSpec, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
9
+ import { parsePodPool, resolvePodPoolSpec, podPoolHostLease, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
9
10
  import { leaseKubectl, measurePoolLease, probePoolDns, recommendConcurrency, type PoolLeaseMeasure, type PoolDnsProbe } from '../task-orchestrator/surfaces/pod-lease.js';
10
11
  import { POD_COMMAND_DEADLINE_SECONDS, runPodCommand, type PodCommandResult, type RunPodCommandOptions } from '../task-orchestrator/surfaces/pod-command-job.js';
12
+ import { hostLeaseCounts, leaseHasPendingPod } from '../pod-lease/host-lease.js';
11
13
 
12
14
  export interface PodCliIo {
13
15
  log: (line: string) => void;
@@ -24,6 +26,7 @@ export interface PodCliDeps {
24
26
  perAccount?: () => number;
25
27
  poolSpec?: (explicit?: string) => string | null;
26
28
  harnessPool?: () => string | undefined;
29
+ memoryAdvice?: (logsDb?: string) => ReturnType<typeof readPodMemoryAdvice>;
27
30
  }
28
31
 
29
32
  const HELP = `elanous pod run [--pool <스펙>] [--skill <이름,…>] [--llm grok] [--clone] [--deadline <초>] -- <명령…>
@@ -38,6 +41,21 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
38
41
  };
39
42
  const run = deps.run ?? ((options: RunPodCommandOptions) => runPodCommand(options));
40
43
  const pod = program.command('pod').description('Pod 명령 Job — 하니스 없이 명령 하나를 돌리고 산출을 돌려받는다');
44
+ pod.command('memory').description('실측 기반 Pod 메모리 권고 (읽기 전용)')
45
+ .command('advise').description('골 종류별 standard 16Gi / high 32Gi 권고와 근거')
46
+ .option('--json', '권고와 측정 근거를 JSON 으로')
47
+ .option('--logs-db <path>', '기존 logs.db 경로')
48
+ .action((opts: { json?: boolean; logsDb?: string }) => {
49
+ try {
50
+ const advice = (deps.memoryAdvice ?? readPodMemoryAdvice)(opts.logsDb);
51
+ if (opts.json) io.log(JSON.stringify(advice));
52
+ else for (const row of advice.byGoalType) io.log(`${row.goalType}: ${row.recommended} ${row.limit} · ${row.reason}`);
53
+ io.exit(0);
54
+ } catch (err) {
55
+ io.error(err instanceof Error ? err.message : String(err));
56
+ io.exit(1);
57
+ }
58
+ });
41
59
  pod.command('lease').description('Pod 풀 동시 실행 권장 (DNS 점검용 Pod 를 잠깐 만들었다 지움)')
42
60
  .command('status').description('지금 추가로 쏠 수 있는 Pod 골과 제한 근거')
43
61
  .option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한][#레지스트리:포트] 쉼표')
@@ -61,8 +79,13 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
61
79
  const perAccount = (deps.perAccount ?? (() => getUserConfig().pod?.lease?.perAccount ?? 4))();
62
80
  const decision = recommendConcurrency(measure, { capacity: members.reduce((sum, m) => sum + m.capacity, 0), accounts: accounts ?? -1, perAccount });
63
81
  if (!context) { decision.recommended = null; decision.limitedBy = null; decision.reason = '측정 불가: cluster (current context)'; }
64
- const result = { pool: spec, ...measure, accounts, perAccount, ...decision };
65
- debug.log('pod.lease', 'status', { pool: spec ? members.map((m) => ({ capacity: m.capacity })) : null, running: decision.running, pending: decision.pending, unleasedRunning: decision.unleasedRunning, recommended: decision.recommended, limitedBy: decision.limitedBy });
82
+ const records = members.length ? podPoolHostLease(members).live() : [];
83
+ const host = hostLeaseCounts(records);
84
+ // Pending Pods and Job-only reservations are disjoint; a lease for an already Pending Job is the same Job.
85
+ const waitingJobs = decision.pending === null ? null : decision.pending +
86
+ records.filter((r) => r.stage === 'job' && !leaseHasPendingPod(r, decision.pendingJobs ?? [])).length;
87
+ const result = { pool: spec, ...measure, accounts, perAccount, ...decision, reserved: host.reserved, waitingJobs };
88
+ 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 });
66
89
  if (opts.json) io.log(JSON.stringify(result));
67
90
  else {
68
91
  io.log(`풀: ${(spec ?? context) || '?'}`);
@@ -72,6 +95,7 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
72
95
  io.log(`${m.context} | ${m.capacity} | ${m.running ?? '?'} | ${m.pending ?? '?'} | ${mem(m.memoryLimitBytes)}/${mem(m.allocatableMemoryBytes)} | ${m.allocatableCpuMillicores === null ? '?' : m.allocatableCpuMillicores / 1000}${m.reason ? ` (${m.reason})` : ''}`);
73
96
  }
74
97
  io.log(`권장 지금 ${decision.recommended ?? '?'} 개 더 (limitedBy=${decision.limitedBy ?? 'unknown'})${decision.reason ? ` · ${decision.reason}` : ''}`);
98
+ io.log(`예약(저작 중) ${host.reserved} · 대기 Job ${waitingJobs ?? '?'} · 실행 ${decision.running ?? '?'}`);
75
99
  io.log(`임대 없는 실행 ${decision.unleasedRunning ?? '?'}`);
76
100
  io.log(`capacity: ${decision.capacitySlots ?? '?'} 칸 (상한 ${members.reduce((sum, m) => sum + m.capacity, 0)} − Running ${decision.running ?? '?'} − Pending ${decision.pending ?? '?'}; 권장 수에서 건강한 멤버의 임대 없는 실행 차감)`);
77
101
  io.log(`memory: ${decision.memorySlots ?? '?'} 칸 (할당 가능 − Running 하니스 상한 합; 골당 16Gi · 모든 네임스페이스의 노드별 기존 예약을 반영해 배치 가능 ${decision.placeableSlots ?? '?'} 칸)`);