elanous 0.2.11 → 0.2.12

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 (246) 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/1147-466ec15afacc1a65.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1394-556de6d71bbdb0fb.js → 1394-ee3795b112907122.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-0259994816604c49.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-cefe8169e36cf62a.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/board/{page-cb95a7432c9ee744.js → page-d2e35228bc497119.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12b6d2f8d2d1aea.js → page-5c05b0da4bc1392c.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/bots/{page-b9efe868bfd36255.js → page-e0ccdb576812b523.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-813c4a52c9d05ca8.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-a022b257950f39e6.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-a2ffc717e04b8b0a.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-b59ee4e70fc18fcf.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-d5dc7ff69a37b4cf.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/live/{page-2a9932ddb62d76bd.js → page-1d6a533ce5a6efcb.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-dfca9b7d2de0b135.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-793fa536b3a6ef37.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6dd3bfbbde2a5c2c.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/{not-found-e7a529e0507ac86c.js → not-found-490653e8d4cc8852.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-77447356baad75d2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-6d604227e1664b71.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-0be8d4b2ced75ba0.js → page-4048543b4b7ad5e0.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-4c3a3126abed551a.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-64b0aecb1ca75e75.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/settings/{page-53f80edc6b8dc07c.js → page-db14d0be7f1c460c.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-9547b5cec7cf76bf.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-2cddeb9e2aeedc55.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-e297866051595d81.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-baba2e047872fb0f.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-e895da4ec9cac345.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +1 -0
  54. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
  55. package/apps/pwa/out/approvals/index.html +1 -1
  56. package/apps/pwa/out/approvals/index.txt +14 -14
  57. package/apps/pwa/out/autopilot/index.html +1 -1
  58. package/apps/pwa/out/autopilot/index.txt +14 -14
  59. package/apps/pwa/out/board/index.html +1 -1
  60. package/apps/pwa/out/board/index.txt +14 -14
  61. package/apps/pwa/out/botlab/index.html +1 -1
  62. package/apps/pwa/out/botlab/index.txt +14 -14
  63. package/apps/pwa/out/bots/index.html +1 -1
  64. package/apps/pwa/out/bots/index.txt +14 -14
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +14 -14
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +14 -14
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +14 -14
  71. package/apps/pwa/out/design-check/index.html +1 -1
  72. package/apps/pwa/out/design-check/index.txt +14 -14
  73. package/apps/pwa/out/editor/index.html +1 -1
  74. package/apps/pwa/out/editor/index.txt +14 -14
  75. package/apps/pwa/out/exec/index.html +1 -1
  76. package/apps/pwa/out/exec/index.txt +14 -14
  77. package/apps/pwa/out/field/index.html +1 -1
  78. package/apps/pwa/out/field/index.txt +14 -14
  79. package/apps/pwa/out/healthz +1 -1
  80. package/apps/pwa/out/index.html +1 -1
  81. package/apps/pwa/out/index.txt +14 -14
  82. package/apps/pwa/out/inside/index.html +1 -1
  83. package/apps/pwa/out/inside/index.txt +14 -14
  84. package/apps/pwa/out/intake/index.html +1 -1
  85. package/apps/pwa/out/intake/index.txt +14 -14
  86. package/apps/pwa/out/live/index.html +1 -1
  87. package/apps/pwa/out/live/index.txt +14 -14
  88. package/apps/pwa/out/market/index.html +1 -1
  89. package/apps/pwa/out/market/index.txt +14 -14
  90. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  91. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  92. package/apps/pwa/out/morning/index.html +1 -1
  93. package/apps/pwa/out/morning/index.txt +14 -14
  94. package/apps/pwa/out/observatory/index.html +1 -1
  95. package/apps/pwa/out/observatory/index.txt +14 -14
  96. package/apps/pwa/out/ops/checklist/index.html +1 -1
  97. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  98. package/apps/pwa/out/ops/release/index.html +1 -1
  99. package/apps/pwa/out/ops/release/index.txt +14 -14
  100. package/apps/pwa/out/ops/seats/index.html +1 -1
  101. package/apps/pwa/out/ops/seats/index.txt +14 -14
  102. package/apps/pwa/out/outputs/index.html +1 -1
  103. package/apps/pwa/out/outputs/index.txt +14 -14
  104. package/apps/pwa/out/reflection/index.html +1 -1
  105. package/apps/pwa/out/reflection/index.txt +15 -15
  106. package/apps/pwa/out/scheduler/index.html +1 -1
  107. package/apps/pwa/out/scheduler/index.txt +14 -14
  108. package/apps/pwa/out/sessions/index.html +1 -1
  109. package/apps/pwa/out/sessions/index.txt +14 -14
  110. package/apps/pwa/out/settings/devices/index.html +1 -1
  111. package/apps/pwa/out/settings/devices/index.txt +14 -14
  112. package/apps/pwa/out/settings/index.html +1 -1
  113. package/apps/pwa/out/settings/index.txt +14 -14
  114. package/apps/pwa/out/setup/done/index.html +1 -1
  115. package/apps/pwa/out/setup/done/index.txt +14 -14
  116. package/apps/pwa/out/setup/index.html +1 -1
  117. package/apps/pwa/out/setup/index.txt +14 -14
  118. package/apps/pwa/out/share/index.html +1 -1
  119. package/apps/pwa/out/share/index.txt +14 -14
  120. package/apps/pwa/out/showroom/index.html +1 -1
  121. package/apps/pwa/out/showroom/index.txt +14 -14
  122. package/apps/pwa/out/tasks/index.html +1 -1
  123. package/apps/pwa/out/tasks/index.txt +14 -14
  124. package/apps/pwa/out/term/index.html +1 -1
  125. package/apps/pwa/out/term/index.txt +14 -14
  126. package/apps/pwa/out/today/index.html +1 -1
  127. package/apps/pwa/out/today/index.txt +14 -14
  128. package/apps/pwa/out/trace/index.html +1 -1
  129. package/apps/pwa/out/trace/index.txt +14 -14
  130. package/apps/pwa/out/vault/index.html +1 -1
  131. package/apps/pwa/out/vault/index.txt +14 -14
  132. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  133. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  134. package/apps/pwa/out/workflows/index.html +1 -1
  135. package/apps/pwa/out/workflows/index.txt +14 -14
  136. package/apps/pwa/out/workspace/index.html +1 -1
  137. package/apps/pwa/out/workspace/index.txt +14 -14
  138. package/apps/pwa/out/worktrees/index.html +1 -1
  139. package/apps/pwa/out/worktrees/index.txt +14 -14
  140. package/graphs/context/nightly-condense.yaml +21 -0
  141. package/graphs/context/recipes.yaml +12 -0
  142. package/graphs/demo/inside-seed.yaml +20 -0
  143. package/graphs/demo/recipes.yaml +12 -0
  144. package/package.json +1 -1
  145. package/plugins/card-followup/examples/sample-card.png +0 -0
  146. package/scripts/lib/telegram-inject-args.ts +31 -0
  147. package/scripts/measure-pod-memory-by-goal.ts +130 -0
  148. package/scripts/release-loop/auto-approve-node.ts +1 -1
  149. package/scripts/release-loop/checklist-gate-node.ts +2 -1
  150. package/scripts/release-loop/gate-node.ts +8 -2
  151. package/scripts/release-loop/ops-upgrade-node.ts +47 -6
  152. package/scripts/release-loop/publish-node.ts +4 -1
  153. package/scripts/release-loop/version-node.ts +13 -3
  154. package/scripts/telegram-inject.ts +14 -6
  155. package/scripts/test-diet/remeasure-list.ts +112 -0
  156. package/scripts/ux/demo-rehearsal.ts +17 -13
  157. package/scripts/ux/demo-seed.ts +108 -0
  158. package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
  159. package/src/agent-mission/driver.ts +138 -16
  160. package/src/agent-mission/pty-decision.ts +1 -1
  161. package/src/autopilot/pty-control-loop.ts +4 -1
  162. package/src/chat/index.ts +1 -0
  163. package/src/cli/persona-cli.ts +17 -14
  164. package/src/cli/pod-cli.ts +17 -0
  165. package/src/cli/pod-memory-advice.ts +35 -0
  166. package/src/cli/release-cli.ts +46 -6
  167. package/src/context-bus/context-now-surfaces.ts +17 -0
  168. package/src/context-bus/context-now.ts +2 -2
  169. package/src/context-bus/coord-events.ts +2 -2
  170. package/src/context-bus/nightly-condense.ts +153 -0
  171. package/src/context-bus/session-events.ts +35 -0
  172. package/src/dashboard/dashboard-status-lines.ts +2 -0
  173. package/src/dashboard/index.ts +8 -0
  174. package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
  175. package/src/domains/outbound-alert.ts +99 -31
  176. package/src/domains/outbound-log-probe.ts +8 -0
  177. package/src/execution-loop/launch-gate.ts +17 -8
  178. package/src/field-note/telegram.ts +142 -0
  179. package/src/graph-runner/graph-grow-llm.ts +23 -13
  180. package/src/graph-runner/graph-grow.ts +5 -2
  181. package/src/graph-runner/runner.ts +12 -8
  182. package/src/harness/harness-cli-command.ts +138 -16
  183. package/src/hooks/consult-intake.ts +153 -0
  184. package/src/hooks/receiver.ts +78 -1
  185. package/src/index.ts +33 -9
  186. package/src/intake-plane/discord-seat-work.ts +28 -2
  187. package/src/intake-plane/wish-card.ts +33 -0
  188. package/src/llm/difficulty.ts +1 -1
  189. package/src/maturity/feature-maturity.ts +2 -1
  190. package/src/nexus/api/http-server.ts +31 -8
  191. package/src/nexus/api/outbound-report.ts +40 -7
  192. package/src/nexus/api/personas.ts +95 -53
  193. package/src/nexus/api/task-cards-api.ts +19 -0
  194. package/src/nexus/api/workflow-history.ts +88 -0
  195. package/src/nexus/api/workflows.ts +52 -0
  196. package/src/nexus/outbound/delivery-ledger.ts +17 -8
  197. package/src/nexus/outbound/router.ts +8 -3
  198. package/src/persona/presets.ts +8 -1
  199. package/src/plugins/maker/market-bundle.ts +2 -1
  200. package/src/release-loop/checklist.ts +15 -3
  201. package/src/release-loop/feature-store.ts +24 -10
  202. package/src/release-loop/landed-but-yellow.ts +48 -0
  203. package/src/seat-loop/seat-loop.ts +168 -7
  204. package/src/self-dev/draft-sweep.ts +44 -10
  205. package/src/self-dev/draft-triage-rules.ts +54 -2
  206. package/src/self-implement/budget-gate.ts +29 -12
  207. package/src/self-implement/gate-candidate-ranking.ts +60 -0
  208. package/src/self-implement/gate-scope.ts +9 -8
  209. package/src/self-implement/headless-elanous-driver.ts +7 -2
  210. package/src/self-implement/orchestrator.ts +15 -0
  211. package/src/self-implement/test-seams.ts +1 -0
  212. package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
  213. package/src/task-orchestrator/surfaces/self-implement-pod.ts +8 -0
  214. package/src/telegram-commands.ts +21 -0
  215. package/src/telegram-project-command.ts +78 -4
  216. package/src/telegram.ts +32 -5
  217. package/src/user-config.ts +8 -4
  218. package/src/version/packed-revision.json +1 -1
  219. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
  220. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
  221. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
  222. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
  223. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
  224. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
  225. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
  226. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
  227. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
  228. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
  229. package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +0 -1
  230. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
  231. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +0 -1
  232. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +0 -1
  233. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +0 -1
  234. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
  235. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +0 -1
  236. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +0 -1
  237. package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +0 -1
  238. package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +0 -1
  239. package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +0 -1
  240. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +0 -1
  241. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +0 -1
  242. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
  243. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
  244. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
  245. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_buildManifest.js +0 -0
  246. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_ssgManifest.js +0 -0
@@ -0,0 +1,35 @@
1
+ import { readPodMemoryMeasurement, type MemoryMeasurement } from '../../scripts/measure-pod-memory-by-goal.js';
2
+
3
+ export type PodMemoryAdvice = {
4
+ window: MemoryMeasurement['window'];
5
+ sources: MemoryMeasurement['sources'];
6
+ byGoalType: Array<{
7
+ goalType: MemoryMeasurement['byGoalType'][number]['goalType'];
8
+ recommended: 'standard' | 'high';
9
+ limit: '16Gi' | '32Gi';
10
+ reason: string;
11
+ evidence: MemoryMeasurement['byGoalType'][number];
12
+ }>;
13
+ };
14
+
15
+ /** Advice only: the launch selector and its configuration remain independent. */
16
+ export function advisePodMemory(measurement: MemoryMeasurement): PodMemoryAdvice {
17
+ return {
18
+ window: measurement.window,
19
+ sources: measurement.sources,
20
+ byGoalType: measurement.byGoalType.map((evidence) => {
21
+ const high = evidence.oomKilled > 0 || (evidence.peakMiB.p95 !== null && evidence.peakMiB.p95 >= 16 * 1024);
22
+ const reason = evidence.oomKilled > 0
23
+ ? `${evidence.oomKilled}/${evidence.runs} OOMKilled; measured peak p50=${evidence.peakMiB.p50 ?? 'n/a'} MiB, p95=${evidence.peakMiB.p95 ?? 'n/a'} MiB (${evidence.measured}/${evidence.runs} measured)`
24
+ : evidence.measured === 0
25
+ ? `No measured peaks (${evidence.missingPeak}/${evidence.runs} missing); standard is provisional, not evidence of safety`
26
+ : `Measured peak p50=${evidence.peakMiB.p50} MiB, p95=${evidence.peakMiB.p95} MiB (${evidence.measured}/${evidence.runs} measured, ${evidence.missingPeak} missing); ${high ? 'at or above' : 'below'} 16Gi`;
27
+ return { goalType: evidence.goalType, recommended: high ? 'high' as const : 'standard' as const,
28
+ limit: high ? '32Gi' as const : '16Gi' as const, reason, evidence };
29
+ }),
30
+ };
31
+ }
32
+
33
+ export function readPodMemoryAdvice(path?: string): PodMemoryAdvice {
34
+ return advisePodMemory(readPodMemoryMeasurement(path));
35
+ }
@@ -26,6 +26,7 @@ import { envLiteral } from '../platform/env-literal.js';
26
26
  import { userConfigPath } from '../user-config.js';
27
27
  import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
28
28
  import * as features from '../release-loop/feature-store.js';
29
+ import { landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
29
30
  import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
30
31
  import { writeStdoutJson } from './stdout-json.js';
31
32
  import { CliUserError } from './cli-user-error.js';
@@ -499,7 +500,13 @@ async function checklistOutput(v: string, codenames: Record<string, string>, mod
499
500
  }
500
501
  }
501
502
 
502
- export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}): void {
503
+ export interface LandedButYellowDeps {
504
+ run?: Runner;
505
+ checklist?: typeof listChecklist;
506
+ now?: () => Date;
507
+ }
508
+
509
+ export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}, landedDeps: LandedButYellowDeps = {}): void {
503
510
  const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
504
511
  const schedule = release.command('schedule').description('판별 컷·착지 마감 조회·갱신');
505
512
  const scheduleLedgerRoot = () => releaseRunDeps.ledgerRoot ?? releaseLedgerRoot();
@@ -538,10 +545,43 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
538
545
  const { version, codenames, json } = context(cmd);
539
546
  await checklistOutput(version, codenames, 'status', json, opts.owner);
540
547
  });
548
+ withContext(checklist.command('landed-but-yellow').description('병합 PR 이 있지만 노랑·빨강인 칸(읽기 전용)'))
549
+ .option('--owner <owner>', '자리 또는 하위 자리로 거르기')
550
+ .action(async (opts: { owner?: string }, cmd: Command) => {
551
+ const { version, json } = context(cmd);
552
+ try {
553
+ if (opts.owner !== undefined) parseOwner(opts.owner);
554
+ const since = new Date((landedDeps.now ?? (() => new Date()))().getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
555
+ const raw = must((landedDeps.run ?? defaultRunner)('gh', ['pr', 'list', '--state', 'merged', '--search', `merged:>=${since}`, '--limit', '400', '--json', 'number,title,body,mergedAt'], process.cwd()), '병합 PR 조회(gh)');
556
+ const prs = JSON.parse(raw) as MergedChecklistPr[];
557
+ if (!Array.isArray(prs)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
558
+ const items = (landedDeps.checklist ?? listChecklist)(version).items;
559
+ const rows = landedButYellow(items, prs, { owner: opts.owner });
560
+ if (json) await writeStdoutJson(`${JSON.stringify(rows)}\n`);
561
+ else {
562
+ const updatedAtById = new Map(items.map((item) => [item.id, item.updatedAt]));
563
+ const date = (iso: string) => {
564
+ const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Seoul', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(new Date(iso));
565
+ const part = (name: string) => parts.find((entry) => entry.type === name)?.value;
566
+ return `${part('month')}-${part('day')} ${part('hour')}:${part('minute')}`;
567
+ };
568
+ for (const row of rows) {
569
+ const icon = row.status === 'yellow' ? '🟡' : '🔴';
570
+ console.log(`${row.id} ${row.owner ?? '-'} ${icon} ← ${row.prs.map((pr, i) => `#${pr.number} «${pr.title}» (${date(pr.mergedAt)} · 근거에 ${row.alreadyInEvidence[i] ? '있음' : '없음'}${pr.mergedAt > (updatedAtById.get(row.id) ?? '') ? ' · 칸보다 새것' : ''})`).join(' · ')}`);
571
+ }
572
+ console.log(`${rows.length}칸 · 그중 근거에 없는 PR 이 있는 칸 ${rows.filter((row) => row.alreadyInEvidence.includes(false)).length}`);
573
+ }
574
+ if (prs.length >= 400) console.error('⚠ 병합 PR 400개 상한: 목록이 잘렸을 수 있다');
575
+ } catch (error) {
576
+ console.error(`⛔ ${error instanceof Error ? error.message : String(error)}`);
577
+ process.exitCode = 1;
578
+ }
579
+ });
541
580
  withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
542
- .action((id: string, title: string, opts: { owner?: string; kind?: string }, cmd: Command) => {
581
+ .option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
582
+ .action((id: string, title: string, opts: { owner?: string; kind?: string; allowDuplicateId?: boolean }, cmd: Command) => {
543
583
  const { version, json } = context(cmd);
544
- const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) });
584
+ const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
545
585
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
546
586
  });
547
587
  withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
@@ -581,11 +621,11 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
581
621
  });
582
622
  const actor = () => process.env.ELANOUS_TRACK || 'cli';
583
623
  withContext(checklist.command('move <id>').description('칸을 한 트랜잭션으로 다른 판에 옮긴다'))
584
- .requiredOption('--from <v>', '현재 판').requiredOption('--to <v>', '새 판')
585
- .action((id: string, opts: { from: string; to: string }, cmd: Command) => {
624
+ .requiredOption('--from <v>', '현재 판').requiredOption('--to <v>', '새 판').option('--reason <reason>', '이동 사유')
625
+ .action((id: string, opts: { from: string; to: string; reason?: string }, cmd: Command) => {
586
626
  const { codenames, json } = context(cmd);
587
627
  const from = checklistVersion(opts.from, codenames), to = checklistVersion(opts.to, codenames);
588
- features.move(id, from, to, actor());
628
+ features.move(id, from, to, actor(), undefined, undefined, opts.reason);
589
629
  if (json) console.log(JSON.stringify(listChecklist(to))); else console.log(`✅ ${id} ${from} → ${to}`);
590
630
  });
591
631
  withContext(checklist.command('retitle <id> <title>').description('칸 제목 수정')).action((id: string, title: string, _opts: unknown, cmd: Command) => {
@@ -4,6 +4,23 @@ export function readSlashContextNow(args: string[], deps?: ContextNowDeps): Cont
4
4
  return contextNow({ topic: args.join(' ').trim() }, deps);
5
5
  }
6
6
 
7
+ export function seatsNowLine(answer: ContextNowAnswer, now: number): string | null {
8
+ const seats = [ ['OP', 'COO'], ['MK', 'CMO'], ['TC', 'CTO'], ['UX', 'CXO'] ] as const;
9
+ const facts = answer.facts.filter(fact => fact.kind === 'seat');
10
+ const lines = seats.flatMap(([seat, label]) => {
11
+ const latest = facts.filter(fact => fact.seat === seat)
12
+ .sort((a, b) => b.at.localeCompare(a.at))[0];
13
+ if (!latest) return [];
14
+ const minutes = Math.max(0, Math.floor((now - Date.parse(latest.at)) / 60_000));
15
+ const age = minutes >= 24 * 60 ? '하루 넘음'
16
+ : minutes >= 60 ? `${Math.floor(minutes / 60)}시간 전`
17
+ : minutes > 0 ? `${minutes}분 전` : '방금';
18
+ const title = latest.title?.split(/\r?\n/, 1)[0]?.trim();
19
+ return [`${label} ${title ? title.slice(0, 20) : latest.status} · ${age}`];
20
+ });
21
+ return lines.length ? `지금 자리들: ${lines.join(' | ')}` : null;
22
+ }
23
+
7
24
  function factText(fact: ContextFact): string {
8
25
  switch (fact.kind) {
9
26
  case 'version': return fact.version;
@@ -66,7 +66,7 @@ export function contextNow(options: { topic?: string; limit?: number } = {}, dep
66
66
  ? redactSecretText(entry.item.title.split(/\r?\n/, 1)[0] ?? '').slice(0, 120) : null, source });
67
67
  }
68
68
  const rawEvents = (deps.events ?? (since => listCoordEvents({ since })))(new Date(now.getTime() - 7 * 86_400_000).toISOString());
69
- const chronologicalEvents = rawEvents.slice().sort((a, b) => a.at.localeCompare(b.at) || a.id.localeCompare(b.id));
69
+ const chronologicalEvents = rawEvents.slice().sort((a, b) => a.at.localeCompare(b.at));
70
70
  const matches = (text: string) => !topic || text.toLocaleLowerCase().includes(topic.toLocaleLowerCase());
71
71
  const selectedFacts = facts.filter(f => !topic || (f.kind === 'cell' && matches(`${f.id} ${f.title}`))
72
72
  || (f.kind === 'seat' && matches(`${f.id ?? ''} ${f.title ?? ''}`)));
@@ -95,7 +95,7 @@ export function contextNow(options: { topic?: string; limit?: number } = {}, dep
95
95
  source: event.refs.source ?? event.refs.url ?? `elanous://context/event/${encodeURIComponent(event.id)}`,
96
96
  }));
97
97
  const selectedEvents = summaries.filter(event => matches(event.summary)).reverse().slice(0, limit);
98
- const guide = summaries.filter(event => event.kind === 'guide-changed' || event.summary.startsWith('📌안내'))
98
+ const guide = summaries.filter(event => event.kind === 'guide-changed' || event.kind === 'guidance-changed' || event.summary.startsWith('📌안내'))
99
99
  .filter(event => matches(event.summary)).reverse().slice(0, limit)
100
100
  .map(event => `📌 ${event.summary} — ${event.source}`);
101
101
  const answer = { at, topic, facts: boundedFacts, events: selectedEvents, guide };
@@ -22,7 +22,7 @@ export interface CoordEvent {
22
22
  text: string;
23
23
  summary: string;
24
24
  kind: string;
25
- refs: Pick<CoordEventInput, 'seat' | 'recipients' | 'all' | 'kind' | 'slot' | 'deadline' | 'url'> & { origin?: string; source?: string };
25
+ refs: Pick<CoordEventInput, 'seat' | 'recipients' | 'all' | 'kind' | 'slot' | 'deadline' | 'url'> & { origin?: string; source?: string; ref?: string | null };
26
26
  }
27
27
 
28
28
  function seatIds(): Set<string> {
@@ -104,7 +104,7 @@ export function listCoordEvents(opts: { since: string; seat?: string }, deps: {
104
104
  const db = deps.db ?? openSurfaceEventsDb();
105
105
  try {
106
106
  const rows = db.prepare(`SELECT id, ts, text, summary, kind, refs FROM events
107
- WHERE surface IN ('coord:channel', 'context:external') AND direction='outbound' AND ts>=?
107
+ WHERE surface IN ('coord:channel', 'context:external', 'context:session') AND direction='outbound' AND ts>=?
108
108
  ${opts.seat ? "AND json_extract(refs,'$.seat')=?" : ''} ORDER BY ts ASC, rowid ASC`)
109
109
  .all(...(opts.seat ? [opts.since, opts.seat] : [opts.since])) as Array<{
110
110
  id: string; ts: string; text: string; summary: string; kind: string; refs: string;
@@ -0,0 +1,153 @@
1
+ import { readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { streamLLM } from '../llm.js';
4
+ import { condenseContextDay, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
5
+ import { listCoordEvents, type CoordEvent } from './coord-events.js';
6
+ import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
7
+ import { acquireLockAsync } from '../storage/file-lock.js';
8
+
9
+ type Stage = 'collect' | 'condense' | 'record' | 'conflict-candidates';
10
+ type Input = { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; summaries?: Record<string, MemorySummary> };
11
+ type Context = { graphId?: string; nodeId?: string; input: Input | null; outputs: Record<string, { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; items?: MemoryItem[] } | null> };
12
+
13
+ const dayBefore = (date: Date) => new Date(date.getTime() - 86_400_000).toISOString().slice(0, 10);
14
+ const fileName = (value: string) => encodeURIComponent(value).replace(/\./g, '%2E');
15
+
16
+ function snapshotPath(root: string, project: string, seat: string): string {
17
+ if (project === '.' || project === '..' || seat === '.' || seat === '..') throw new Error('invalid memory path');
18
+ return join(root, 'context-memory', fileName(project), `${fileName(seat)}.json`);
19
+ }
20
+
21
+ function save(file: string, value: unknown): void {
22
+ mkdirSync(dirname(file), { recursive: true });
23
+ const temp = `${file}.${process.pid}.tmp`;
24
+ writeFileSync(temp, JSON.stringify(value, null, 2) + '\n');
25
+ renameSync(temp, file);
26
+ }
27
+
28
+ async function withCurrentDay<T>(root: string, day: string, write: () => T): Promise<T> {
29
+ const dir = join(root, 'context-memory');
30
+ mkdirSync(dir, { recursive: true });
31
+ const lock = await acquireLockAsync(join(dir, '.nightly-write.lock'));
32
+ try {
33
+ const marker = join(dir, 'latest-day');
34
+ const latest = latestDay(root);
35
+ if ((latest && latest > day) || storedItems(root).some(item =>
36
+ Date.parse(item.updatedAt) >= Date.parse(`${day}T00:00:00Z`) + 86_400_000)) {
37
+ throw new Error(`cannot replay ${day}: later project/seat memory exists`);
38
+ }
39
+ if (!lock.stillHeld()) throw new Error('nightly memory write lock lost');
40
+ // Reserve the day before touching either output, so a failed writer cannot let an older run overwrite it.
41
+ if (!latest || latest < day) save(marker, day);
42
+ return write();
43
+ } finally { lock.release(); }
44
+ }
45
+
46
+ function latestDay(root: string): string | undefined {
47
+ const marker = join(root, 'context-memory', 'latest-day');
48
+ return existsSync(marker) ? JSON.parse(readFileSync(marker, 'utf8')) as string : undefined;
49
+ }
50
+
51
+ function storedItems(root: string): MemoryItem[] {
52
+ const dir = join(root, 'context-memory');
53
+ if (!existsSync(dir)) return [];
54
+ const items: MemoryItem[] = [];
55
+ for (const project of readdirSync(dir, { withFileTypes: true })) {
56
+ if (!project.isDirectory()) continue;
57
+ for (const seat of readdirSync(join(dir, project.name))) {
58
+ if (!seat.endsWith('.json')) continue;
59
+ const content: unknown = JSON.parse(readFileSync(join(dir, project.name, seat), 'utf8'));
60
+ if (!Array.isArray(content)) throw new Error('invalid context memory file');
61
+ items.push(...content as MemoryItem[]);
62
+ }
63
+ }
64
+ return items;
65
+ }
66
+
67
+ async function summarize(source: MemorySource, injected?: Record<string, MemorySummary>): Promise<MemorySummary> {
68
+ if (injected) {
69
+ const result = injected[source.source];
70
+ if (!result) throw new Error(`missing test summary: ${source.source}`);
71
+ return result;
72
+ }
73
+ const response = await streamLLM([
74
+ { role: 'system', content: 'Summarize this one-line context event as a compact project/seat memory item. Return ONLY JSON: {"project":"...","topic":"...","summary":"...","claim":{"key":"...","value":"..."}}. Omit claim when the event makes no comparable factual assertion. Never invent a project, topic or claim.' },
75
+ { role: 'user', content: JSON.stringify(source) },
76
+ ], () => {}, { maxTokens: 350, usageRole: 'research' });
77
+ const parsed: unknown = JSON.parse(response.replace(/^```(?:json)?\s*|\s*```$/g, '').trim());
78
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid memory summary');
79
+ const value = parsed as Record<string, unknown>;
80
+ if (typeof value.project !== 'string' || typeof value.topic !== 'string' || typeof value.summary !== 'string' ||
81
+ (value.claim !== undefined && (!value.claim || typeof value.claim !== 'object' || Array.isArray(value.claim) ||
82
+ typeof (value.claim as Record<string, unknown>).key !== 'string' || typeof (value.claim as Record<string, unknown>).value !== 'string'))) {
83
+ throw new Error('invalid memory summary');
84
+ }
85
+ return value as unknown as MemorySummary;
86
+ }
87
+
88
+ /** Stage outputs travel only through the graph runner's per-run contexts. No decision ledger write occurs here. */
89
+ export async function runNightlyStage(stage: Stage, context: Context, root: string, dryRun: boolean, now = new Date()): Promise<Record<string, unknown>> {
90
+ const input = context.input ?? {};
91
+ if (dryRun && stage !== 'collect' && (!input.events || !input.decisions || !input.summaries)) {
92
+ throw new Error('dry-run requires fake events, decisions and summaries');
93
+ }
94
+ const day = context.outputs.collect?.day ?? input.day ?? dayBefore(now);
95
+ if (!/^\d{4}-\d\d-\d\d$/.test(day) || new Date(`${day}T00:00:00Z`).toISOString().slice(0, 10) !== day) {
96
+ throw new Error('invalid UTC day');
97
+ }
98
+ if (stage === 'collect') {
99
+ if (!dryRun && ((latestDay(root) ?? '') > day || storedItems(root).some(item =>
100
+ Date.parse(item.updatedAt) >= Date.parse(`${day}T00:00:00Z`) + 86_400_000))) {
101
+ throw new Error(`cannot replay ${day}: later project/seat memory exists`);
102
+ }
103
+ if (dryRun && (!input.events || !input.decisions || !input.summaries)) {
104
+ throw new Error('dry-run requires fake events, decisions and summaries');
105
+ }
106
+ const events = input.events ?? listCoordEvents({ since: new Date(Date.parse(`${day}T00:00:00Z`) - 86_400_000).toISOString() });
107
+ const decisions = input.decisions ?? (dryRun ? [] : new DecisionLedger().list({ status: 'all' }));
108
+ return { outcome: 'ok', day, events, decisions };
109
+ }
110
+ const collected = context.outputs.collect;
111
+ if (!collected?.day || !collected.events || !collected.decisions) throw new Error('collect output missing');
112
+ if (stage === 'condense') {
113
+ const items = await condenseContextDay(collected.day, dryRun ? [] : storedItems(root).filter(item => Date.parse(item.updatedAt) < Date.parse(`${collected.day}T00:00:00Z`)), {
114
+ events: () => collected.events!, decisions: () => collected.decisions!,
115
+ summarize: source => summarize(source, input.summaries),
116
+ }, now);
117
+ return { outcome: 'ok', items };
118
+ }
119
+ const items = context.outputs.condense?.items;
120
+ if (!items) throw new Error('condense output missing');
121
+ if (stage === 'record') {
122
+ const grouped = new Map<string, MemoryItem[]>();
123
+ for (const item of items) {
124
+ const file = snapshotPath(root, item.project, item.seat);
125
+ grouped.set(file, [...grouped.get(file) ?? [], item]);
126
+ }
127
+ if (!dryRun) await withCurrentDay(root, day, () => {
128
+ for (const [file, group] of grouped) save(file, group);
129
+ });
130
+ return { outcome: 'ok', files: [...grouped.keys()], items: items.length, memoryItems: dryRun ? items : undefined, dryRun };
131
+ }
132
+ if (stage === 'conflict-candidates') {
133
+ if (!context.outputs.record) throw new Error('record output missing');
134
+ const candidates = items.filter(item => item.conflict && item.status === 'active').map(item => ({
135
+ project: item.project, topic: item.topic, owner: item.conflict!.owner,
136
+ sources: item.conflict!.sources, status: 'candidate' as const,
137
+ }));
138
+ if (!dryRun) await withCurrentDay(root, day, () => save(join(root, 'context-memory', 'conflict-candidates.json'), candidates));
139
+ return { outcome: 'ok', candidates, published: 0 };
140
+ }
141
+ throw new Error(`unknown stage: ${stage}`);
142
+ }
143
+
144
+ if (import.meta.main) {
145
+ const stage = process.argv[2] as Stage;
146
+ const path = process.env.ELANOUS_GRAPH_CONTEXT;
147
+ if (!path || !['collect', 'condense', 'record', 'conflict-candidates'].includes(stage)) throw new Error('invalid nightly graph stage');
148
+ const context = JSON.parse(readFileSync(path, 'utf8')) as Context;
149
+ if (context.graphId !== 'nightly-condense' || context.nodeId !== stage) throw new Error('nightly graph context mismatch');
150
+ const statePath = resolve(path, '..', '..', '..', '..');
151
+ const result = await runNightlyStage(stage, context, statePath, process.env.ELANOUS_GRAPH_DRY_RUN === '1');
152
+ console.log(JSON.stringify(result));
153
+ }
@@ -0,0 +1,35 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import { redactSecretText } from '../debug/log.js';
3
+ import { openSurfaceEventsDb, recordEvent } from '../domains/surface-events.js';
4
+
5
+ export const SESSION_EVENT_KINDS = ['task-claimed', 'task-done', 'asked', 'guidance-changed'] as const;
6
+ export type SessionEventKind = typeof SESSION_EVENT_KINDS[number];
7
+
8
+ export interface SessionEventInput {
9
+ kind: SessionEventKind;
10
+ seat: string;
11
+ text: string;
12
+ ref?: string;
13
+ at?: string;
14
+ }
15
+
16
+ /** Publish a one-line external-agent session signal to the existing context journal. */
17
+ export function emitSessionEvent(input: SessionEventInput, deps: { db?: Database } = {}): string {
18
+ if (!SESSION_EVENT_KINDS.includes(input.kind)) throw new Error('unknown context session kind');
19
+ if (!['OP', 'TC', 'MK', 'UX'].includes(input.seat)) throw new Error('context session seat must be OP|TC|MK|UX');
20
+ if (/\r|\n/.test(input.text) || !input.text.trim()) throw new Error('context session text must be one nonempty line');
21
+ if (input.ref !== undefined && (/\r|\n/.test(input.ref) || !input.ref.trim())) throw new Error('context session ref must be one nonempty line');
22
+ const summary = redactSecretText(input.text).trim().slice(0, 120);
23
+ const ref = input.ref ?? null;
24
+ const db = deps.db ?? openSurfaceEventsDb();
25
+ try {
26
+ return recordEvent(db, {
27
+ surface: 'context:session', direction: 'outbound', domain: 'elanous', category: 'coordination',
28
+ kind: input.kind, text: input.text, summary,
29
+ refs: JSON.stringify({ seat: input.seat, recipients: [], all: false, kind: input.kind,
30
+ slot: null, deadline: null, url: null, ref,
31
+ ...(ref ? { source: `elanous://context/ref/${encodeURIComponent(ref)}` } : {}) }),
32
+ ...(input.at ? { ts: input.at } : {}),
33
+ });
34
+ } finally { if (!deps.db) db.close(); }
35
+ }
@@ -5,6 +5,7 @@ export interface DashboardStatusLinesInput {
5
5
  readonly daemonAddress?: string | null;
6
6
  readonly daemonConnected: boolean;
7
7
  readonly accountName?: string | null;
8
+ readonly seatsNow?: string | null;
8
9
  readonly sessionSurfaceLines: readonly string[];
9
10
  readonly view: string;
10
11
  readonly focus: string;
@@ -31,6 +32,7 @@ export function buildDashboardStatusLines(input: DashboardStatusLinesInput): str
31
32
  `모델: ${known(input.provider)}/${known(input.model)} (${known(input.reasoning)})`,
32
33
  `연결: 데몬 ${address} ${input.daemonConnected ? '붙음' : '안 붙음'}`,
33
34
  `계정: ${input.accountName?.trim() === 'default' ? '기본' : known(input.accountName)}`,
35
+ ...(input.seatsNow ? [input.seatsNow] : []),
34
36
  '',
35
37
  '자세히',
36
38
  ...input.sessionSurfaceLines.map(line => ` ${line}`),
@@ -884,6 +884,7 @@ import { getInputHistoryStore, inputHistoryDbPath, inputHistoryJsonPath } from '
884
884
  import { inspectActiveProvider } from '../provider-summary.js';
885
885
  import { activeCodexAccountView } from '../oauth/codex-account-store.js';
886
886
  import { buildDashboardStatusLines } from './dashboard-status-lines.js';
887
+ import { readSlashContextNow, seatsNowLine } from '../context-bus/context-now-surfaces.js';
887
888
  import { buildFirstScreenBand } from './first-screen-band.js';
888
889
  import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
889
890
  import {
@@ -6405,7 +6406,14 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6405
6406
  const active = inspectActiveProvider(cfg);
6406
6407
  const reasoningProvider = (['openai-codex', 'openai', 'anthropic', 'gemini', 'grok', 'local', 'openrouter'] as const)
6407
6408
  .find(provider => active.provider === provider || active.provider === `auto:${provider}`);
6409
+ let seatsNow: string | null = null;
6410
+ try {
6411
+ seatsNow = seatsNowLine(readSlashContextNow([]), Date.now());
6412
+ } catch (error) {
6413
+ debug.log('dashboard.status', 'seats-unreadable', { error: String(error) });
6414
+ }
6408
6415
  return buildDashboardStatusLines({
6416
+ seatsNow,
6409
6417
  provider: active.provider,
6410
6418
  model: active.model,
6411
6419
  reasoning: reasoningProvider
@@ -273,6 +273,8 @@
273
273
  // of circular dependency on dashboard/index.ts.
274
274
 
275
275
  import { tuiNowSlash } from '../../context-bus/context-now-surfaces.js';
276
+ import { createWishCard } from '../../intake-plane/wish-card.js';
277
+ import { CardStore } from '../../task-cards/card-store.js';
276
278
  import type { ContextNowDeps } from '../../context-bus/context-now.js';
277
279
  import { runRebindCommand } from '../../input-core/index.js';
278
280
  // B4 (TUI half) — /design reuses the CLI's own resolution so the three
@@ -422,6 +424,8 @@ export interface DashboardSlashContext {
422
424
  /** Existing TUI structured-question capability. Absent means /ask keeps
423
425
  * the self-dev noninteractive deferred-clarification contract. */
424
426
  surfaceUx?: Pick<SurfaceUx, 'question'>;
427
+ /** Optional wish-card store for isolated slash dispatch tests. */
428
+ wishCardStore?: CardStore;
425
429
  attachmentRowMap: { clear(): void };
426
430
  pushDebugLine(line: string): void;
427
431
  pushChatLine(line: string): void;
@@ -1367,6 +1371,26 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
1367
1371
  for (const line of tuiNowSlash(args, nowDeps)) ctx.pushChatLine(line);
1368
1372
  });
1369
1373
 
1374
+ registry.register('wish', (args, ctx) => {
1375
+ const text = args.join(' ').trim();
1376
+ if (!text) {
1377
+ ctx.pushChatLine(ctx.warning(' usage: /wish <소원 한 줄>'));
1378
+ ctx.setChatScrollOffset(-1);
1379
+ return;
1380
+ }
1381
+ let store: CardStore | undefined;
1382
+ try {
1383
+ store = ctx.wishCardStore ?? new CardStore();
1384
+ const card = createWishCard({ text, source: 'tui', ref: crypto.randomUUID() }, store);
1385
+ ctx.pushChatLine(ctx.success(` 소원 카드로 남겼습니다 — ${card.title}`));
1386
+ } catch (err) {
1387
+ ctx.pushChatLine(ctx.error(` 소원 카드 등록 실패 — ${err instanceof Error ? err.message : String(err)}`));
1388
+ } finally {
1389
+ if (!ctx.wishCardStore) store?.close();
1390
+ }
1391
+ ctx.setChatScrollOffset(-1);
1392
+ });
1393
+
1370
1394
  // ── B-1.a pilot ────────────────────────────────────────────────────
1371
1395
  registry.register(['quit', 'q', 'exit'], (_args, ctx) => {
1372
1396
  (ctx.exitTui ?? ctx.closeTui)();