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
@@ -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;
@@ -1,4 +1,4 @@
1
- import { redactSecretText } from '../debug/log.js';
1
+ import { debug, redactSecretText } from '../debug/log.js';
2
2
  import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
3
3
  import { listCoordEvents, type CoordEvent } from './coord-events.js';
4
4
 
@@ -33,6 +33,14 @@ export interface MemoryDeps {
33
33
  decisions?: () => DecisionEntry[];
34
34
  }
35
35
 
36
+ export type CondenseSkipReason = 'no-seat' | 'no-project' | 'no-topic' | 'no-summary' | 'bad-link' | 'bad-claim' | 'summarize-failed';
37
+ export type CondenseSkipped = Partial<Record<CondenseSkipReason, number>>;
38
+ export interface CondenseWindowReport {
39
+ cards: MemoryItem[];
40
+ skipped: CondenseSkipped;
41
+ folded: number;
42
+ }
43
+
36
44
  const DAY_MS = 86_400_000;
37
45
  const oneLine = (text: string) => redactSecretText(text.split(/\r?\n/, 1)[0] ?? '').trim();
38
46
 
@@ -43,40 +51,125 @@ export async function condenseContextDay(
43
51
  if (!/^\d{4}-\d\d-\d\d$/.test(day) || new Date(`${day}T00:00:00.000Z`).toISOString().slice(0, 10) !== day) {
44
52
  throw new Error('day must be a valid UTC YYYY-MM-DD');
45
53
  }
46
- const since = `${day}T00:00:00.000Z`;
54
+ return condenseContextWindow(`${day}T00:00:00.000Z`, new Date(Date.parse(`${day}T00:00:00.000Z`) + DAY_MS).toISOString(), previous, deps, now);
55
+ }
56
+
57
+ /** Read-only condensation over an exact rolling UTC window; the caller owns any persistence. */
58
+ export async function condenseContextWindow(
59
+ since: string, until: string, previous: readonly MemoryItem[], deps: MemoryDeps, now: Date = new Date(),
60
+ ): Promise<MemoryItem[]> {
61
+ return (await condenseContextWindowWithReport(since, until, previous, deps, now)).cards;
62
+ }
63
+
64
+ export async function condenseContextWindowWithReport(
65
+ since: string, until: string, previous: readonly MemoryItem[], deps: MemoryDeps, now: Date = new Date(),
66
+ ): Promise<CondenseWindowReport> {
47
67
  const start = Date.parse(since);
48
- const until = start + DAY_MS;
49
- // The store compares timestamp text, so fetch the previous local day too before UTC filtering.
68
+ const end = Date.parse(until);
69
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) throw new Error('invalid context window');
70
+ // Fetch the previous local day as well: the store compares timestamp text rather than instants.
50
71
  const events = (deps.events ?? (from => listCoordEvents({ since: from })))(new Date(start - DAY_MS).toISOString());
51
72
  const decisions = (deps.decisions ?? (() => new DecisionLedger().list({ status: 'all' })))();
73
+ const inWindow = (at: string) => {
74
+ const time = Date.parse(at);
75
+ return Number.isFinite(time) && time >= start && time < end;
76
+ };
77
+ const harnessRuns: Array<{ events: CoordEvent[]; locators: Set<string> }> = [];
78
+ const earlierStarts: CoordEvent[] = [];
79
+ const otherEvents: CoordEvent[] = [];
80
+ let harnessEvents = 0;
81
+ for (const event of events) {
82
+ if (!inWindow(event.at)) {
83
+ const time = Date.parse(event.at);
84
+ if (event.kind === 'started' && Number.isFinite(time) && time >= start - DAY_MS && time < start) {
85
+ earlierStarts.push(event);
86
+ }
87
+ continue;
88
+ }
89
+ // Harness lifecycle = started/finished, or any harness-child event that is not a report (review round 3).
90
+ const isLifecycle = event.kind === 'started' || event.kind === 'finished'
91
+ || (event.refs.seat === 'harness-child' && event.kind !== '보고');
92
+ if (!isLifecycle) {
93
+ otherEvents.push(event);
94
+ continue;
95
+ }
96
+ harnessEvents++;
97
+ const locators = new Set([event.refs.source, event.refs.url].filter((ref): ref is string => !!ref));
98
+ const group = { events: [event], locators };
99
+ // Either locator can identify the run, including a bridge through a third event.
100
+ for (let i = 0; i < harnessRuns.length;) {
101
+ const prior = harnessRuns[i]!;
102
+ if (locators.size && [...prior.locators].some(ref => locators.has(ref))) {
103
+ group.events.push(...prior.events);
104
+ for (const ref of prior.locators) locators.add(ref);
105
+ harnessRuns.splice(i, 1);
106
+ i = 0;
107
+ } else i++;
108
+ }
109
+ harnessRuns.push(group);
110
+ }
111
+ const eventSource = (event: CoordEvent): MemorySource => ({ kind: 'event', at: event.at, seat: event.refs.seat,
112
+ text: event.summary, source: event.refs.source ?? event.refs.url ?? `elanous://context/event/${encodeURIComponent(event.id)}` });
113
+ const foldedSources: MemorySource[] = [];
114
+ for (const group of harnessRuns) {
115
+ group.events.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.id.localeCompare(b.id));
116
+ const finish = group.events.filter(event => event.kind === 'finished').at(-1);
117
+ const inWindowStart = group.events.find(event => event.kind === 'started');
118
+ const begin = inWindowStart ?? (finish ? earlierStarts.filter(event =>
119
+ (group.locators.has(event.refs.source ?? '') || group.locators.has(event.refs.url ?? '')))
120
+ .sort((a, b) => Date.parse(b.at) - Date.parse(a.at)).at(0) : undefined);
121
+ const winner = finish ?? inWindowStart ?? group.events.at(-1)!;
122
+ const frequency = (ref: string) => group.events.filter(event => event.refs.source === ref || event.refs.url === ref).length;
123
+ const run = [...group.locators].sort((a, b) => frequency(b) - frequency(a))[0] ?? eventSource(winner).source;
124
+ const duration = finish && begin && Date.parse(finish.at) >= Date.parse(begin.at)
125
+ ? `${Math.floor((Date.parse(finish.at) - Date.parse(begin.at)) / 60_000)}m ${Math.floor(((Date.parse(finish.at) - Date.parse(begin.at)) % 60_000) / 1_000)}s`
126
+ : '알 수 없음';
127
+ const result = finish ? oneLine(finish.summary).replace(/^Harness child finished:\s*/i, '') || '종료'
128
+ : '진행 중';
129
+ foldedSources.push({ ...eventSource(winner), seat: winner.refs.seat || begin?.refs.seat || '',
130
+ source: run, text: `${run} ${result} · ${duration}` });
131
+ }
132
+ const folded = harnessEvents - foldedSources.length;
133
+ try { debug.log('context.condense', 'folded', { harnessEvents, kept: foldedSources.length }); }
134
+ catch { /* Observation must not stop condensation. */ }
52
135
  const sources: MemorySource[] = [
53
- ...events.map(event => ({ kind: 'event' as const, at: event.at, seat: event.refs.seat,
54
- text: event.summary, source: event.refs.source ?? event.refs.url ?? `elanous://context/event/${encodeURIComponent(event.id)}` })),
136
+ ...otherEvents.map(eventSource), ...foldedSources,
55
137
  ...decisions.map(decision => ({ kind: 'decision' as const,
56
138
  at: decision.decidedAt ?? decision.raisedAt ?? decision.importedAt ?? '',
57
139
  seat: decision.raisedBy.track ?? 'OP',
58
140
  text: decision.status === 'decided' ? `${decision.title}: ${decision.options.find(o => o.key === decision.choice)?.label ?? decision.note ?? decision.scqa.a ?? '결정됨'}` : `${decision.title}: ${decision.status}`,
59
141
  source: `elanous://decisions/${encodeURIComponent(decision.id)}` })),
60
- ].filter(source => {
61
- const at = Date.parse(source.at);
62
- return Number.isFinite(at) && at >= start && at < until;
63
- }).sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.source.localeCompare(b.source));
142
+ ].filter(source => inWindow(source.at))
143
+ .sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.source.localeCompare(b.source));
64
144
 
65
145
  const items = new Map<string, MemoryItem>();
146
+ const skipped: CondenseSkipped = {};
147
+ let kept = 0;
148
+ const skip = (reason: CondenseSkipReason, source: MemorySource) => {
149
+ skipped[reason] = (skipped[reason] ?? 0) + 1;
150
+ try { debug.log('context.condense', 'source-skipped', { reason, kind: source.kind, at: source.at }); }
151
+ catch { /* Observation must not stop condensation. */ }
152
+ };
153
+ const safeLine = (value: unknown) => typeof value === 'string' ? oneLine(value) : '';
66
154
  const keyFor = (item: Pick<MemoryItem, 'project' | 'seat' | 'topic'>) => JSON.stringify([item.project, item.seat, item.topic]);
67
155
  for (const item of previous) items.set(keyFor(item), { ...item });
68
156
  for (const source of sources) {
69
- const summary = await deps.summarize(source);
70
- const project = oneLine(summary.project);
71
- const seat = oneLine(source.seat);
72
- const topic = oneLine(summary.topic);
73
- const text = oneLine(summary.summary);
74
- const link = oneLine(source.source);
75
- if (!project || !seat || !topic || !text || !/^(?:https?:\/\/|elanous:\/\/)[^\s]+$/.test(link)) {
76
- throw new Error('memory summary needs a project, seat, topic, one-line summary and source link');
77
- }
78
- const claim = summary.claim && { key: oneLine(summary.claim.key), value: oneLine(summary.claim.value) };
79
- if (claim && (!claim.key || !claim.value)) throw new Error('memory claim needs key and value');
157
+ const seat = safeLine(source.seat);
158
+ if (!seat) { skip('no-seat', source); continue; }
159
+ let summary: MemorySummary;
160
+ try { summary = await deps.summarize(source); }
161
+ catch { skip('summarize-failed', source); continue; }
162
+ const project = safeLine(summary?.project);
163
+ const topic = safeLine(summary?.topic);
164
+ const text = safeLine(summary?.summary);
165
+ const link = safeLine(source.source);
166
+ if (!project) { skip('no-project', source); continue; }
167
+ if (!topic) { skip('no-topic', source); continue; }
168
+ if (!text) { skip('no-summary', source); continue; }
169
+ if (!/^(?:https?:\/\/|elanous:\/\/)[^\s]+$/.test(link)) { skip('bad-link', source); continue; }
170
+ const claim = summary.claim && { key: safeLine(summary.claim.key), value: safeLine(summary.claim.value) };
171
+ if (claim && (!claim.key || !claim.value)) { skip('bad-claim', source); continue; }
172
+ kept++;
80
173
  const key = keyFor({ project, seat, topic });
81
174
  const old = items.get(key);
82
175
  // Equal timestamps are resolved by the source link, so shuffled input does not change the winner.
@@ -103,6 +196,9 @@ export async function condenseContextDay(
103
196
  finalItems[index] = { ...item, conflict: { owner: 'OP', sources: [conflicting.source, item.source] } };
104
197
  }
105
198
  }
106
- return finalItems.sort((a, b) => a.project.localeCompare(b.project) || a.seat.localeCompare(b.seat)
199
+ const cards = finalItems.sort((a, b) => a.project.localeCompare(b.project) || a.seat.localeCompare(b.seat)
107
200
  || a.topic.localeCompare(b.topic));
201
+ try { debug.log('context.condense', 'window', { sources: sources.length, kept, skipped }); }
202
+ catch { /* Observation must not stop condensation. */ }
203
+ return { cards, skipped, folded };
108
204
  }
@@ -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
+ export 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)();
@@ -35,21 +35,27 @@ export function isIrreversible(entry: Pick<DecisionEntry, 'category'>): boolean
35
35
 
36
36
  const kst = (at: string) => new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }).format(new Date(at));
37
37
 
38
- /** The card body: title, SCQA in four short lines, options with consequences, recommendation, deadline. */
38
+ /** The card opens with a short pitch; the full question and choice buttons remain below it. */
39
39
  export function renderCardText(e: DecisionEntry, extra: { note?: string; confirm?: string } = {}): string {
40
+ const recommendedKey = 'option' in e.recommendation ? e.recommendation.option : undefined;
41
+ const recommended = e.options.find(o => o.key === recommendedKey);
42
+ const alternative = e.options.find(o => o.key === e.alternative);
40
43
  const lines = [
41
44
  `🗳 결정 요청 ${e.id}${isIrreversible(e) ? ' · ⚠️ 되돌릴 수 없음' : ''}`,
42
45
  `«${e.title}» · 올린 자리: ${e.raisedBy.agent}${e.raisedBy.track ? `(${e.raisedBy.track})` : ''}`,
43
46
  '',
44
- `상황: ${e.scqa.s}`,
45
- `문제: ${e.scqa.c}`,
47
+ `S: ${e.scqa.s.replace(/\s+/g, ' ')}`,
48
+ `C: ${e.scqa.c.replace(/\s+/g, ' ')}`,
49
+ `Q: ${(e.scqa.q ?? '(비움)').replace(/\s+/g, ' ')}`,
50
+ `A: ${(e.scqa.a ?? '(비움)').replace(/\s+/g, ' ')}`,
51
+ 'skipped' in e.recommendation ? `권고 없음: ${e.recommendation.reason}` : `권고: ${recommended?.label ?? e.recommendation.option} — ${e.recommendation.why}`,
52
+ ...(alternative ? [`대안: ${alternative.label} — ${alternative.consequence}`] : []),
53
+ e.crossCheck?.length ? `교차 확인: ${e.crossCheck.map(check => `${check.seat} ✓ ${check.note.replace(/\s+/g, ' ')}`).join(' · ')}` : `교차 확인 없음(${e.crossCheckSkipped ?? '미기재'})`,
54
+ ...(e.dissent ? [`이견: ${e.dissent}`] : []),
55
+ ...(e.dueAt ? [`기한: ${kst(e.dueAt)} KST`] : []),
46
56
  ...(e.pendingQuestion ? ['', `대기 질문 전문:\n${e.pendingQuestion}`] : []),
47
- ...(e.scqa.q ? [`질문: ${e.scqa.q}`] : []),
48
- ...(e.scqa.a ? [`제안: ${e.scqa.a}`] : []),
49
57
  '',
50
58
  ...e.options.map((o) => `${o.key.toUpperCase()}) ${o.label} — ${o.consequence}`),
51
- 'skipped' in e.recommendation ? `권고 없음: ${e.recommendation.reason}` : `권고: ${e.recommendation.option.toUpperCase()} — ${e.recommendation.why}`,
52
- ...(e.dueAt ? [`기한: ${kst(e.dueAt)}`] : []),
53
59
  ...(extra.note ? ['', `📝 메모: ${extra.note}`] : []),
54
60
  ];
55
61
  if (extra.confirm) {
@@ -22,11 +22,15 @@ export interface DecisionEntry {
22
22
  pendingQuestion?: string;
23
23
  /** DEC-TG — optional deadline (UTC). Cards remind the owner two hours before it. */
24
24
  dueAt?: string;
25
+ crossCheck?: Array<{ seat: string; at: string; note: string }>;
26
+ crossCheckSkipped?: string;
27
+ alternative?: string;
28
+ dissent?: string;
25
29
  decidedAt?: string; decidedBy?: DecisionActor; choice?: string; note?: string; versionAtDecision?: Versions;
26
30
  withdrawnAt?: string; withdrawReason?: string;
27
31
  history: Array<{ type: 'raised' | 'options-added' | 'decided' | 'withdrawn'; at?: string; by: string; version?: Versions; choice?: string; reason?: string }>;
28
32
  }
29
- export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion'> & { raisedAt?: string };
33
+ export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion' | 'crossCheck' | 'crossCheckSkipped' | 'alternative' | 'dissent'> & { raisedAt?: string };
30
34
  type Event = { type: 'raised'; entry: DecisionEntry } | { type: 'options-added'; id: string; at: string; options: DecisionOption[]; by: string } | { type: 'decided'; id: string; at?: string; by: DecisionActor; choice?: string; version?: Versions; note?: string } | { type: 'withdrawn'; id: string; at: string; reason: string; version: Versions };
31
35
  export interface DecisionLedgerOptions extends VersionOptions { stateDir?: string; now?: () => Date; resolveVersion?: (at: string) => Versions; writeAnswer?: typeof writePendingQuestionAnswer }
32
36
 
@@ -89,13 +93,27 @@ function validate(input: RaiseInput, historical = false): RaiseInput {
89
93
  ? { skipped: true as const, reason: safe(single(input.recommendation.reason, 'skip reason')) }
90
94
  : { option: input.recommendation.option, why: safe(single(input.recommendation.why, 'recommendation why')) };
91
95
  if ('option' in recommendation && !options.some(o => o.key === recommendation.option)) throw new Error('recommended option not found');
96
+ const alternative = input.alternative === undefined ? undefined : single(input.alternative, 'alternative');
97
+ if (alternative && !options.some(o => o.key === alternative)) throw new Error('alternative option not found');
98
+ if (alternative && 'option' in recommendation && alternative === recommendation.option) throw new Error('alternative must differ from recommendation');
99
+ if (input.crossCheck?.length && input.crossCheckSkipped !== undefined) throw new Error('cross-check and skipped reason cannot coexist');
100
+ if (input.crossCheck !== undefined && !Array.isArray(input.crossCheck)) throw new Error('cross-check must be a list');
101
+ const crossCheck = input.crossCheck?.map(check => {
102
+ const seat = single(check.seat, 'cross-check seat');
103
+ if (!['OP', 'MK', 'TC', 'UX'].includes(seat)) throw new Error('invalid cross-check seat');
104
+ return { seat, at: utc(check.at), note: safe(single(check.note, 'cross-check note')) };
105
+ });
106
+ const crossCheckSkipped = input.crossCheckSkipped === undefined ? undefined : safe(single(input.crossCheckSkipped, 'cross-check skipped reason'));
107
+ const dissent = input.dissent === undefined ? undefined : safe(single(input.dissent, 'dissent'));
92
108
  if (input.resume && !/^(?:auq:[0-9a-z]+:[0-9a-z]{5}|execution:[A-Za-z0-9_-]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?![\s\S])/.test(input.resume.questionId)) throw new Error('invalid resume questionId');
93
109
  const resume = input.resume ? { questionId: input.resume.questionId,
94
110
  ...(input.resume.runId === undefined ? {} : { runId: single(input.resume.runId, 'resume runId') }) } : undefined;
95
111
  if (input.pendingQuestion !== undefined) required(input.pendingQuestion, 'pending question');
96
112
  return { ...input, title, scqa: { s, c, ...(q ? { q } : {}), ...(a ? { a } : {}) }, options, recommendation,
97
113
  ...(input.pendingQuestion === undefined ? {} : { pendingQuestion: safe(input.pendingQuestion) }),
98
- ...(resume ? { resume } : {}),
114
+ ...(resume ? { resume } : {}), ...(crossCheck ? { crossCheck } : {}),
115
+ ...(crossCheckSkipped !== undefined ? { crossCheckSkipped } : {}),
116
+ ...(alternative !== undefined ? { alternative } : {}), ...(dissent !== undefined ? { dissent } : {}),
99
117
  raisedBy: { agent: safe(single(input.raisedBy.agent, 'agent')), ...(input.raisedBy.track ? { track: input.raisedBy.track } : {}), ...(input.raisedBy.session ? { session: safe(single(input.raisedBy.session, 'session')) } : {}) },
100
118
  ...(input.refs ? { refs: input.refs.map(r => safe(single(r, 'ref'))) } : {}),
101
119
  ...(input.dueAt ? { dueAt: utc(input.dueAt) } : {}) };
@@ -206,21 +224,29 @@ export class DecisionLedger {
206
224
  show(id: string): DecisionEntry { const entry = this.list({ status: 'all' }).find(e => e.id === id); if (!entry) throw new Error(`decision not found: ${id}`); return entry; }
207
225
  raise(input: RaiseInput): DecisionEntry {
208
226
  const clean = validate(input);
209
- return this.locked(() => {
210
- const at = utc(clean.raisedAt ?? this.now().toISOString());
211
- const day = at.slice(0, 10).replaceAll('-', '');
212
- const prefix = `D-${day}-`;
213
- let next = 1;
214
- for (const entry of this.list({ status: 'all' })) {
215
- if (entry.id.startsWith(prefix)) next = Math.max(next, Number(entry.id.slice(prefix.length)) + 1);
216
- }
217
- const id = `${prefix}${String(next).padStart(2, '0')}`;
218
- const version = this.version(at);
219
- const entry: DecisionEntry = { ...clean, id, raisedAt: at, version, status: 'open', history: [{ type: 'raised', at, by: clean.raisedBy.agent, version }] };
220
- this.append({ type: 'raised', entry });
221
- debug.log('decisions', 'raised', { id, category: entry.category, by: entry.raisedBy.agent });
222
- return entry;
223
- });
227
+ return this.locked(() => this.raiseLocked(clean));
228
+ }
229
+ /** Reuse a card with the same source reference, including across competing raisers. */
230
+ raiseOnce(input: RaiseInput, ref: string): DecisionEntry {
231
+ const clean = validate(input);
232
+ if (!clean.refs?.includes(ref)) throw new Error('decision source reference required');
233
+ return this.locked(() => this.list({ status: 'all' }).find(entry => entry.refs?.includes(ref)) ?? this.raiseLocked(clean));
234
+ }
235
+ private raiseLocked(clean: RaiseInput): DecisionEntry {
236
+ const at = utc(clean.raisedAt ?? this.now().toISOString());
237
+ const day = at.slice(0, 10).replaceAll('-', '');
238
+ const prefix = `D-${day}-`;
239
+ let next = 1;
240
+ for (const entry of this.list({ status: 'all' })) {
241
+ if (entry.id.startsWith(prefix)) next = Math.max(next, Number(entry.id.slice(prefix.length)) + 1);
242
+ }
243
+ const id = `${prefix}${String(next).padStart(2, '0')}`;
244
+ const version = this.version(at);
245
+ const entry: DecisionEntry = { ...clean, id, raisedAt: at, version, status: 'open', history: [{ type: 'raised', at, by: clean.raisedBy.agent, version }] };
246
+ this.append({ type: 'raised', entry });
247
+ debug.log('decisions', 'raised', { id, category: entry.category, by: entry.raisedBy.agent });
248
+ debug.log('decisions', 'raised-crosscheck', { id, seats: entry.crossCheck?.length ?? 0, skipped: entry.crossCheckSkipped ?? null });
249
+ return entry;
224
250
  }
225
251
  /** Record the decision and say whether its answer reached the waiting run (null = the decision resumes nothing). */
226
252
  decideWithDelivery(id: string, choice: string, by: DecisionActor, note?: string, decidedAt?: string): { entry: DecisionEntry; delivery: AnswerDelivery } {