elanous 0.2.14 → 0.2.16

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 (350) 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/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
  53. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  54. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  55. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
  56. package/apps/pwa/out/approvals/index.html +1 -1
  57. package/apps/pwa/out/approvals/index.txt +15 -15
  58. package/apps/pwa/out/autopilot/index.html +1 -1
  59. package/apps/pwa/out/autopilot/index.txt +15 -15
  60. package/apps/pwa/out/board/index.html +1 -1
  61. package/apps/pwa/out/board/index.txt +15 -15
  62. package/apps/pwa/out/botlab/index.html +1 -1
  63. package/apps/pwa/out/botlab/index.txt +15 -15
  64. package/apps/pwa/out/bots/index.html +1 -1
  65. package/apps/pwa/out/bots/index.txt +15 -15
  66. package/apps/pwa/out/ceo/index.html +1 -0
  67. package/apps/pwa/out/ceo/index.txt +33 -0
  68. package/apps/pwa/out/chat/index.html +1 -1
  69. package/apps/pwa/out/chat/index.txt +15 -15
  70. package/apps/pwa/out/consult/index.html +1 -1
  71. package/apps/pwa/out/consult/index.txt +15 -15
  72. package/apps/pwa/out/control/index.html +1 -1
  73. package/apps/pwa/out/control/index.txt +15 -15
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +15 -15
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +15 -15
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +15 -15
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +15 -15
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +15 -15
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +15 -15
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +15 -15
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +15 -15
  91. package/apps/pwa/out/loops/index.html +1 -1
  92. package/apps/pwa/out/loops/index.txt +15 -15
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +15 -15
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +15 -15
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +15 -15
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +15 -15
  105. package/apps/pwa/out/ops/seats/index.html +1 -1
  106. package/apps/pwa/out/ops/seats/index.txt +15 -15
  107. package/apps/pwa/out/outputs/index.html +1 -1
  108. package/apps/pwa/out/outputs/index.txt +15 -15
  109. package/apps/pwa/out/reflection/index.html +1 -1
  110. package/apps/pwa/out/reflection/index.txt +16 -16
  111. package/apps/pwa/out/scheduler/index.html +1 -1
  112. package/apps/pwa/out/scheduler/index.txt +15 -15
  113. package/apps/pwa/out/sessions/index.html +1 -1
  114. package/apps/pwa/out/sessions/index.txt +15 -15
  115. package/apps/pwa/out/settings/devices/index.html +1 -1
  116. package/apps/pwa/out/settings/devices/index.txt +15 -15
  117. package/apps/pwa/out/settings/index.html +1 -1
  118. package/apps/pwa/out/settings/index.txt +15 -15
  119. package/apps/pwa/out/setup/done/index.html +1 -1
  120. package/apps/pwa/out/setup/done/index.txt +15 -15
  121. package/apps/pwa/out/setup/index.html +1 -1
  122. package/apps/pwa/out/setup/index.txt +15 -15
  123. package/apps/pwa/out/share/index.html +1 -1
  124. package/apps/pwa/out/share/index.txt +15 -15
  125. package/apps/pwa/out/showroom/index.html +1 -1
  126. package/apps/pwa/out/showroom/index.txt +15 -15
  127. package/apps/pwa/out/tasks/index.html +1 -1
  128. package/apps/pwa/out/tasks/index.txt +15 -15
  129. package/apps/pwa/out/term/index.html +1 -1
  130. package/apps/pwa/out/term/index.txt +15 -15
  131. package/apps/pwa/out/today/index.html +1 -1
  132. package/apps/pwa/out/today/index.txt +15 -15
  133. package/apps/pwa/out/trace/index.html +1 -1
  134. package/apps/pwa/out/trace/index.txt +15 -15
  135. package/apps/pwa/out/vault/index.html +1 -1
  136. package/apps/pwa/out/vault/index.txt +15 -15
  137. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  138. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  139. package/apps/pwa/out/workflows/index.html +1 -1
  140. package/apps/pwa/out/workflows/index.txt +15 -15
  141. package/apps/pwa/out/workspace/index.html +1 -1
  142. package/apps/pwa/out/workspace/index.txt +15 -15
  143. package/apps/pwa/out/worktrees/index.html +1 -1
  144. package/apps/pwa/out/worktrees/index.txt +15 -15
  145. package/graphs/doc-gen/doc-gen.yaml +24 -0
  146. package/graphs/doc-gen/recipes.yaml +3 -0
  147. package/graphs/heal/heal-loop.yaml +1 -1
  148. package/graphs/implement-loop.yaml +1 -1
  149. package/graphs/orchestrator/orchestrator.yaml +7 -0
  150. package/graphs/orchestrator/recipes.yaml +3 -0
  151. package/graphs/release/recipes.yaml +3 -0
  152. package/graphs/release/release-loop.yaml +4 -2
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/brand/check.ts +7 -5
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +82 -31
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +437 -0
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/expose-rubric-suggest.ts +18 -12
  163. package/scripts/goals/goal-census.ts +343 -0
  164. package/scripts/lecture-note/node.ts +31 -11
  165. package/scripts/lessons/handbook-page.ts +30 -3
  166. package/scripts/marketing/generate-ax-brief.ts +140 -0
  167. package/scripts/marketing/generate-feature-map.ts +118 -0
  168. package/scripts/measure/author-section-contribution.ts +84 -0
  169. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  170. package/scripts/release-loop/cut-branch.ts +43 -13
  171. package/scripts/release-loop/gate-node.ts +82 -14
  172. package/scripts/release-loop/vault-note-node.ts +257 -8
  173. package/scripts/release-story/draft.ts +57 -9
  174. package/scripts/rhythm/daily.ts +263 -76
  175. package/scripts/rhythm/monthly.ts +97 -0
  176. package/scripts/rhythm/weekly.ts +30 -9
  177. package/scripts/smart-merge/resync-shadow.ts +147 -0
  178. package/src/acp/client.ts +17 -2
  179. package/src/agent-env/profile.ts +161 -0
  180. package/src/agent-mission/driver.ts +51 -1
  181. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  182. package/src/agent-mission/supervisor-metrics.ts +218 -0
  183. package/src/agent-substrate/pr-reviewer.ts +2 -0
  184. package/src/ask-user-question/pending-questions.ts +5 -0
  185. package/src/auth/github-app-token.ts +112 -13
  186. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  187. package/src/autopilot/track-agent-shadow.ts +2 -1
  188. package/src/briefing/brief-items.ts +276 -0
  189. package/src/budget/usage-outcomes.ts +204 -0
  190. package/src/claims/claims-render.ts +14 -5
  191. package/src/cli/agent-env-cli.ts +21 -0
  192. package/src/cli/brief-cli.ts +73 -0
  193. package/src/cli/decisions-cli.ts +9 -0
  194. package/src/cli/doctor-cli.ts +23 -1
  195. package/src/cli/doctor-node-modules.ts +147 -0
  196. package/src/cli/intake-cli.ts +46 -17
  197. package/src/cli/know-cli.ts +20 -0
  198. package/src/cli/logs-cli.ts +43 -1
  199. package/src/cli/pod-cli.ts +2 -0
  200. package/src/cli/pr-cli.ts +80 -12
  201. package/src/cli/release-cli.ts +66 -29
  202. package/src/cli/seat-requests-cli.ts +52 -0
  203. package/src/cli/self-update.ts +200 -1
  204. package/src/cli/usage-cli.ts +26 -0
  205. package/src/context-bus/context-now-public.ts +16 -6
  206. package/src/context-bus/context-now.ts +3 -1
  207. package/src/context-bus/coord-events.ts +6 -4
  208. package/src/decisions/decision-cards.ts +33 -7
  209. package/src/decisions/proact-meter.ts +286 -0
  210. package/src/decisions/proact1-lite.ts +86 -0
  211. package/src/decisions/web-push-decision-cards.ts +1 -1
  212. package/src/discord-self-message.ts +9 -0
  213. package/src/domains/alert-audit.ts +271 -0
  214. package/src/domains/core-tools.ts +4 -3
  215. package/src/domains/ops-facts-tool.ts +31 -1
  216. package/src/flow-loop/split.ts +24 -0
  217. package/src/harness/harness-cli-command.ts +147 -17
  218. package/src/harness/harness-incidents.ts +2 -0
  219. package/src/harness/harness-pod-dispatch.ts +67 -8
  220. package/src/harness/harness-queue.ts +380 -50
  221. package/src/harness/harness-seams.ts +13 -3
  222. package/src/harness/helper-repair.ts +218 -0
  223. package/src/harness/helper-scan.ts +27 -4
  224. package/src/harness/launch-stamp.ts +236 -0
  225. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  226. package/src/harness/ledger-write-guard.ts +35 -0
  227. package/src/harness/nested-elanous-policy.ts +114 -0
  228. package/src/harness/pod-memory-policy.ts +15 -0
  229. package/src/harness/repository-name.ts +55 -0
  230. package/src/hq/fence-audit.ts +90 -0
  231. package/src/hq/hq.ts +70 -6
  232. package/src/hq/lease.ts +9 -7
  233. package/src/hq/move-plan.ts +126 -0
  234. package/src/hq/promote.ts +31 -0
  235. package/src/hq/transfer.ts +185 -0
  236. package/src/index.ts +189 -13
  237. package/src/intake-plane/check.ts +63 -0
  238. package/src/intake-plane/digest.ts +65 -21
  239. package/src/intake-plane/front-classifier.ts +6 -0
  240. package/src/intake-plane/items.ts +3 -2
  241. package/src/intake-plane/lens.ts +177 -0
  242. package/src/intake-plane/route.ts +145 -1
  243. package/src/knowledge/know-find.ts +105 -0
  244. package/src/llm.ts +75 -19
  245. package/src/loops/budget.ts +36 -5
  246. package/src/loops/checker.ts +2 -2
  247. package/src/loops/orchestrator/author-depth.ts +65 -9
  248. package/src/loops/orchestrator/author-input-check.ts +8 -3
  249. package/src/loops/orchestrator/author-ledger.ts +3 -1
  250. package/src/loops/orchestrator/finish-rate.ts +14 -3
  251. package/src/loops/orchestrator/seat-share.ts +56 -0
  252. package/src/loops/orchestrator/tick.ts +107 -29
  253. package/src/loops/orchestrator/traffic.ts +117 -11
  254. package/src/loops/registry.ts +142 -61
  255. package/src/maturity/bot-slash-catalog.ts +11 -2
  256. package/src/maturity/feature-maturity.ts +4 -3
  257. package/src/maturity/tui-slash-availability.ts +40 -0
  258. package/src/mss/logging/log-store.ts +35 -4
  259. package/src/nexus/api/http-server.ts +28 -14
  260. package/src/nexus/api/loop-edges.ts +173 -0
  261. package/src/nexus/api/schedules-read.ts +13 -0
  262. package/src/nexus/api/seat-requests.ts +79 -56
  263. package/src/oauth/overload-failover-launch.ts +75 -0
  264. package/src/oauth/overload-failover.ts +137 -0
  265. package/src/prompt-enhance/enhance.ts +6 -1
  266. package/src/release-loop/auto-start.ts +6 -3
  267. package/src/release-loop/checklist.ts +19 -5
  268. package/src/release-loop/feature-store.ts +59 -31
  269. package/src/release-loop/merged-pr-checklist.ts +125 -0
  270. package/src/release-loop/placement.ts +98 -29
  271. package/src/seat-dispatch/seat-ask.ts +33 -6
  272. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  273. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  274. package/src/seat-loop/seat-loop.ts +215 -12
  275. package/src/self-dev/ask-launch-flow.ts +22 -1
  276. package/src/self-dev/authored-goal-trace.ts +64 -0
  277. package/src/self-dev/dev-cli.ts +19 -1
  278. package/src/self-dev/dev-pipeline.ts +14 -9
  279. package/src/self-dev/draft-sweep.ts +90 -10
  280. package/src/self-dev/draft-triage-rules.ts +25 -8
  281. package/src/self-dev/entrance-registry.ts +10 -6
  282. package/src/self-dev/old-door.ts +73 -0
  283. package/src/self-dev/orchestrate.ts +15 -1
  284. package/src/self-dev/review-provider-fallback.ts +6 -1
  285. package/src/self-dev/run-supervisor.ts +49 -2
  286. package/src/self-implement/app-api-budget.ts +166 -0
  287. package/src/self-implement/budget-gate.ts +7 -5
  288. package/src/self-implement/follow-up-goals.ts +289 -0
  289. package/src/self-implement/frozen-merges.ts +3 -2
  290. package/src/self-implement/goal-author.ts +50 -6
  291. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  292. package/src/self-implement/heal-intake.ts +120 -2
  293. package/src/self-implement/heal-triage.ts +44 -0
  294. package/src/self-implement/host-regate.ts +9 -2
  295. package/src/self-implement/l8-merge-queue.ts +132 -0
  296. package/src/self-implement/orchestrator.ts +325 -47
  297. package/src/self-implement/pipeline-shape.ts +1 -1
  298. package/src/self-implement/revert-guard.ts +42 -7
  299. package/src/self-implement/run-ledger.ts +6 -0
  300. package/src/self-implement/run-status-mapping.ts +2 -0
  301. package/src/self-implement/seams.ts +45 -16
  302. package/src/self-implement/self-implement-cli.ts +1 -0
  303. package/src/self-implement/speed-claims.ts +338 -0
  304. package/src/session-runtime/index.ts +2 -0
  305. package/src/session-runtime/retry-api.ts +87 -0
  306. package/src/session-runtime/retry-policy.ts +107 -6
  307. package/src/steward/triage.ts +74 -3
  308. package/src/steward/working-backwards.ts +20 -2
  309. package/src/task-cards/card-cli.ts +25 -0
  310. package/src/task-cards/card-store.ts +5 -3
  311. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  312. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  313. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  315. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  316. package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
  317. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  318. package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
  319. package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
  320. package/src/telegram-commands.ts +5 -0
  321. package/src/telegram.ts +72 -4
  322. package/src/user-config.ts +64 -7
  323. package/src/version/packed-revision.json +1 -1
  324. package/src/workflow-runtime/nodes/bash.ts +9 -1
  325. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  349. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  350. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
@@ -2,21 +2,30 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { checkBrand } from '../../scripts/brand/check.js';
5
+ import { debug } from '../debug/log.js';
5
6
  import type { ContextFact, ContextNowAnswer } from './context-now.js';
6
7
 
7
8
  const internalItem = (count: number) => `내부 항목 ${count}개`;
8
9
  const seatNames: Record<string, string> = { OP: '운영', TC: '기술', MK: '마케팅', UX: '사용자 경험' };
9
10
 
10
11
  /** Public copy only: no raw ledger identifiers or private sources in the rendered answer. */
11
- export function filterPublicDemoContext(answer: ContextNowAnswer): ContextNowAnswer {
12
+ export function filterPublicDemoContext(answer: ContextNowAnswer, brandCheck: typeof checkBrand = checkBrand): ContextNowAnswer {
12
13
  const dir = mkdtempSync(join(tmpdir(), 'context-now-public-'));
13
14
  const file = join(dir, 'text.txt');
14
15
  try {
16
+ let rulesMissing = brandCheck('public-docs', []).missing;
17
+ const logMissing = () => debug.log('context.now', 'brand-rules-missing', { audience: 'public-demo' });
18
+ if (rulesMissing) logMissing();
15
19
  const flagged = (texts: string[]): Set<number> => {
20
+ if (rulesMissing) return new Set(texts.map((_, index) => index));
16
21
  if (!texts.length) return new Set();
17
22
  writeFileSync(file, texts.map(text => text.replace(/[\r\n]+/g, ' ')).join('\n') + '\n');
18
- const result = checkBrand('public-docs', [file]);
19
- if (result.missing) throw new Error('brand rules missing');
23
+ const result = brandCheck('public-docs', [file]);
24
+ if (result.missing) {
25
+ rulesMissing = true;
26
+ logMissing();
27
+ return new Set(texts.map((_, index) => index));
28
+ }
20
29
  return new Set(result.findings.map(finding => finding.line - 1));
21
30
  };
22
31
  const publicText = (value: string): string => value
@@ -60,8 +69,9 @@ export function filterPublicDemoContext(answer: ContextNowAnswer): ContextNowAns
60
69
  status: safe(fact.status, '진행 중'), source: source(fact.source) });
61
70
  } else if (fact.kind === 'seat') {
62
71
  facts.push({ ...fact, seat: safe(fact.seat, '공개 담당'), status: safe(fact.status, '진행 중'),
63
- id: fact.id ? (privateIds.has(fact.id) ? '내부 항목' : safe(fact.id, '공개 항목')) : null,
64
- title: fact.title ? (privateIds.has(fact.id ?? '') ? '내부 항목' : safe(fact.title, '공개 항목')) : null, source: source(fact.source) });
72
+ id: fact.id ? (rulesMissing || privateIds.has(fact.id) ? '내부 항목' : safe(fact.id, '공개 항목')) : null,
73
+ title: fact.title === null ? null : rulesMissing ? internalItem(1) : privateIds.has(fact.id ?? '') ? '내부 항목' : safe(fact.title, '공개 항목'),
74
+ source: source(fact.source) });
65
75
  } else facts.push({ ...fact, version: safe(fact.version, '공개 판'), source: source(fact.source) });
66
76
  }
67
77
  for (const kind of ['cell', 'decision'] as const) {
@@ -78,7 +88,7 @@ export function filterPublicDemoContext(answer: ContextNowAnswer): ContextNowAns
78
88
  events: answer.events.map(event => ({ ...event, kind: safe(event.kind, '소식'),
79
89
  summary: publicField(event.summary, '공개 소식'), source: source(event.source) })),
80
90
  guide: answer.guide.map(text => publicField(text, '공개 안내')),
81
- hiddenCount: collapsed.cell + collapsed.decision,
91
+ hiddenCount: collapsed.cell + collapsed.decision + (rulesMissing ? answer.facts.filter(fact => fact.kind === 'seat' && fact.title !== null).length : 0),
82
92
  };
83
93
  } finally {
84
94
  rmSync(dir, { recursive: true, force: true });
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
+ import { checkBrand } from '../../scripts/brand/check.js';
2
3
  import { debug, redactSecretText } from '../debug/log.js';
3
4
  import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
4
5
  import { listChecklist, devVersion, type Checklist } from '../release-loop/checklist.js';
@@ -22,6 +23,7 @@ export interface ContextNowDeps {
22
23
  decisions?: () => DecisionEntry[];
23
24
  seatEntries?: (now: Date) => Array<{ entry: SeatEntry; source: string }>;
24
25
  events?: (since: string) => CoordEvent[];
26
+ brandCheck?: typeof checkBrand;
25
27
  }
26
28
 
27
29
  function todaySeatEntries(now: Date): Array<{ entry: SeatEntry; source: string }> {
@@ -101,5 +103,5 @@ export function contextNow(options: { topic?: string; limit?: number; audience?:
101
103
  .map(event => `📌 ${event.summary} — ${event.source}`);
102
104
  const answer = { at, topic, facts: boundedFacts, events: selectedEvents, guide };
103
105
  debug.log('context.now', 'answer', { topic, facts: answer.facts, events: answer.events });
104
- return options.audience === 'public-demo' ? filterPublicDemoContext(answer) : answer;
106
+ return options.audience === 'public-demo' ? filterPublicDemoContext(answer, deps.brandCheck) : answer;
105
107
  }
@@ -1,4 +1,4 @@
1
- import type { Database } from 'bun:sqlite';
1
+ import { Database } from 'bun:sqlite';
2
2
  import { readFileSync, existsSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { debug, redactSecretText } from '../debug/log.js';
@@ -99,12 +99,14 @@ export function recordCoordEvent(input: CoordEventInput, deps: { db?: Database }
99
99
  }
100
100
 
101
101
  /** Read-only chronological list; a missing store is an empty ledger, not a newly created one. */
102
- export function listCoordEvents(opts: { since: string; seat?: string }, deps: { db?: Database } = {}): CoordEvent[] {
102
+ export function listCoordEvents(opts: { since: string; seat?: string; channelOnly?: boolean }, deps: { db?: Database } = {}): CoordEvent[] {
103
103
  if (!deps.db && !existsSync(surfaceEventsDbPath())) return [];
104
- const db = deps.db ?? openSurfaceEventsDb();
104
+ const db = deps.db ?? (opts.channelOnly
105
+ ? new Database(surfaceEventsDbPath(), { readonly: true, strict: true })
106
+ : openSurfaceEventsDb());
105
107
  try {
106
108
  const rows = db.prepare(`SELECT id, ts, text, summary, kind, refs FROM events
107
- WHERE surface IN ('coord:channel', 'context:external', 'context:session') AND direction='outbound' AND ts>=?
109
+ WHERE surface ${opts.channelOnly ? "= 'coord:channel'" : "IN ('coord:channel', 'context:external', 'context:session')"} AND direction='outbound' AND ts>=?
108
110
  ${opts.seat ? "AND json_extract(refs,'$.seat')=?" : ''} ORDER BY ts ASC, rowid ASC`)
109
111
  .all(...(opts.seat ? [opts.since, opts.seat] : [opts.since])) as Array<{
110
112
  id: string; ts: string; text: string; summary: string; kind: string; refs: string;
@@ -28,6 +28,7 @@ export interface CardTransport {
28
28
  const IRREVERSIBLE = new Set(['money', 'publish', 'security', 'irreversible', 'secret']);
29
29
  export const DECISION_DATA_PREFIX = 'dec:';
30
30
  const REMIND_BEFORE_MS = 2 * 3600_000;
31
+ const WEBPUSH_MAX_SEND_ATTEMPTS = 3;
31
32
 
32
33
  export function isIrreversible(entry: Pick<DecisionEntry, 'category'>): boolean {
33
34
  return IRREVERSIBLE.has(entry.category);
@@ -99,7 +100,8 @@ interface CardState {
99
100
  /** First start of this platform's service — decisions raised before it are not pushed (no burst of old items at
100
101
  * deploy); they stay reachable through `/decisions`. */
101
102
  since?: string;
102
- cards: Record<string, { refs: CardRef[]; closed?: boolean; remindedAt?: string; note?: string }>;
103
+ cards: Record<string, { refs: CardRef[]; closed?: boolean; remindedAt?: string; note?: string; attempts?: number; noSubscribers?: boolean }>;
104
+ testOriginQuestions?: Record<string, true>;
103
105
  }
104
106
 
105
107
  export interface DecisionCardServiceOptions {
@@ -165,16 +167,27 @@ export class DecisionCardService {
165
167
  for (const record of pending.questions) {
166
168
  for (const question of record.questions) {
167
169
  const data = { questionId: record.id, runId: record.runId };
170
+ const logSkip = (reason: string): void => debug.log('hitl.card-bridge', 'skipped', { ...data, reason });
171
+ // Path is not provenance: operational goals can live under the OS temporary directory too.
172
+ if (record.testOrigin === true) {
173
+ state.testOriginQuestions ??= {};
174
+ if (!state.testOriginQuestions[record.id]) {
175
+ state.testOriginQuestions[record.id] = true;
176
+ dirty = true;
177
+ debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'test-origin' });
178
+ }
179
+ continue;
180
+ }
168
181
  if (record.expiresAt && record.expiresAt <= this.now().toISOString()) {
169
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'expired' });
182
+ logSkip('expired');
170
183
  continue;
171
184
  }
172
185
  if (question.impact !== 'high' && question.impact !== 'critical') {
173
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'impact' });
186
+ logSkip('impact');
174
187
  continue;
175
188
  }
176
189
  if (record.questions.length !== 1 || question.options.length < 2 || question.options.length > 4) {
177
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'options' });
190
+ logSkip('options');
178
191
  continue;
179
192
  }
180
193
  if (seen.has(record.id)) {
@@ -200,7 +213,7 @@ export class DecisionCardService {
200
213
  all.unshift(entry);
201
214
  debug.log('hitl.card-bridge', 'raised', { ...data, decisionId: entry.id });
202
215
  } catch (error) {
203
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: error instanceof Error ? error.name : 'unknown' });
216
+ logSkip(error instanceof Error ? error.name : 'unknown');
204
217
  }
205
218
  }
206
219
  }
@@ -208,13 +221,26 @@ export class DecisionCardService {
208
221
  const chats = await this.opts.transport.ownerChats().catch(() => [] as string[]);
209
222
  for (const e of all) {
210
223
  const card = state.cards[e.id];
211
- if (e.status === 'open' && !card && e.options.length >= 2 && e.raisedAt !== undefined && e.raisedAt >= state.since) {
224
+ if (e.status === 'open' && (!card || (platform === 'webpush' && !card.refs.length && (card.attempts ?? 0) < WEBPUSH_MAX_SEND_ATTEMPTS))
225
+ && e.options.length >= 2 && e.raisedAt !== undefined && e.raisedAt >= state.since) {
226
+ if (platform === 'webpush' && !chats.length) {
227
+ if (!card?.noSubscribers) {
228
+ state.cards[e.id] = { ...(card ?? { refs: [] }), noSubscribers: true };
229
+ dirty = true;
230
+ debug.log('decisions.webpush', 'no-subscribers', { id: e.id });
231
+ }
232
+ continue;
233
+ }
212
234
  const refs: CardRef[] = [];
213
235
  for (const chat of chats) {
214
236
  const ref = await this.opts.transport.send(chat, renderCard(e)).catch(() => null);
215
237
  if (ref) refs.push(ref);
216
238
  }
217
- if (refs.length) { state.cards[e.id] = { refs }; sent++; }
239
+ if (platform === 'webpush') {
240
+ state.cards[e.id] = { ...(card ?? { refs: [] }), refs, attempts: (card?.attempts ?? 0) + 1 };
241
+ dirty = true;
242
+ } else if (refs.length) state.cards[e.id] = { refs };
243
+ if (refs.length) sent++;
218
244
  debug.log('decisions.telegram', 'card-sent', { platform, id: e.id, category: e.category, chats: chats.length, delivered: refs.length });
219
245
  continue;
220
246
  }
@@ -0,0 +1,286 @@
1
+ import { readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { DecisionLedger, type DecisionEntry, type SeatDecisionRecord } from './decision-ledger.js';
4
+ import { createRequire } from 'node:module';
5
+ import { listCoordEvents, type CoordEvent } from '../context-bus/coord-events.js';
6
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
7
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
8
+
9
+ /** Directive row fields this meter reads — kept local because `src/directives/directive-index` is excluded from the public export. */
10
+ export interface DirectiveRow { ts: string; text: string; source_file: string; line_no: number }
11
+
12
+ /** Loads the private directive index at run time only, so public builds (no index) bundle cleanly and report `directivesError`. */
13
+ function openDirectiveIndex(stateDir?: string): { search(q: string, f: { since?: string; until?: string; limit?: number }): DirectiveRow[]; close(): void } {
14
+ const spec = ['..', 'directives', 'directive-index.js'].join('/');
15
+ const mod = createRequire(import.meta.url)(spec) as { DirectiveIndex: new (o: { stateDir?: string }) => ReturnType<typeof openDirectiveIndex> };
16
+ return new mod.DirectiveIndex(stateDir ? { stateDir } : {});
17
+ }
18
+
19
+ /** KST calendar day, matching the seat-loop ledger file name. */
20
+ export function proactKstDay(at: Date | string): string {
21
+ const date = typeof at === 'string' ? new Date(at) : at;
22
+ if (!Number.isFinite(date.getTime())) throw new Error('valid timestamp required');
23
+ return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit' }).format(date);
24
+ }
25
+
26
+ export function proactWindow(now: Date, days = 7): { since: string; until: string; days: string[] } {
27
+ if (!Number.isFinite(now.getTime()) || !Number.isInteger(days) || days < 1) throw new Error('valid window required');
28
+ const today = proactKstDay(now);
29
+ const todayStart = Date.parse(`${today}T00:00:00+09:00`);
30
+ const list: string[] = [];
31
+ for (let i = days - 1; i >= 0; i--) list.push(proactKstDay(new Date(todayStart - i * 86_400_000)));
32
+ return { since: new Date(todayStart - (days - 1) * 86_400_000).toISOString(), until: now.toISOString(), days: list };
33
+ }
34
+
35
+ export type ProactKind = 'proposal' | 'alert' | 'action';
36
+ export type ProactSource = 'decision' | 'directive' | 'coord' | 'loop';
37
+ export interface ProactSample {
38
+ readonly day: string;
39
+ readonly bucket: 'offered' | 'adopted' | 'asked';
40
+ readonly source: ProactSource;
41
+ readonly id: string;
42
+ readonly at: string;
43
+ readonly title: string;
44
+ /** Where the original row lives, so a count can be opened again. */
45
+ readonly link: string;
46
+ }
47
+ export interface ProactDay {
48
+ readonly day: string;
49
+ /** System offered a proposal, alert, or action before the owner asked. */
50
+ readonly offered: number;
51
+ /** Of those, the ones the owner (or a delegated seat) adopted. */
52
+ readonly adopted: number;
53
+ /** Problems that stayed invisible until the owner asked first. */
54
+ readonly asked: number;
55
+ }
56
+ export interface ProactSourceStatus { readonly read: boolean; readonly reason?: string }
57
+ export interface ProactMeter {
58
+ readonly since: string;
59
+ readonly until: string;
60
+ readonly days: readonly ProactDay[];
61
+ readonly samples: readonly ProactSample[];
62
+ readonly sources: Readonly<Record<ProactSource, ProactSourceStatus>>;
63
+ }
64
+
65
+ export interface ProactMeterInput {
66
+ decisions?: readonly DecisionEntry[];
67
+ seatDecisions?: readonly SeatDecisionRecord[];
68
+ directives?: readonly DirectiveRow[];
69
+ coord?: readonly CoordEvent[];
70
+ loops?: readonly ProactLoopRow[];
71
+ decisionsError?: string;
72
+ directivesError?: string;
73
+ coordError?: string;
74
+ loopsError?: string;
75
+ }
76
+ export interface ProactLoopRow {
77
+ readonly seat: string;
78
+ readonly at: string;
79
+ readonly status: string;
80
+ readonly action?: string;
81
+ readonly id?: string;
82
+ readonly title?: string;
83
+ readonly file: string;
84
+ }
85
+
86
+ const ALERT = /경보|경고|장애|실패|막힘|alert|fail|blocked|stall/i;
87
+ const ACTION = /조치|실행|발사|launch|queued|launched/i;
88
+ const ASKED = /왜|문제|안 되|안돼|고장|버그|실패|막혀|뭐가|무슨 일|확인해/;
89
+
90
+ export function proactKind(text: string, action?: string): ProactKind {
91
+ if (action === 'decision' || action === 'harness' || action === 'seat-question' || ACTION.test(text)) return 'action';
92
+ if (ALERT.test(text)) return 'alert';
93
+ return 'proposal';
94
+ }
95
+
96
+ function sample(day: string, bucket: ProactSample['bucket'], source: ProactSource, id: string, at: string, title: string, link: string): ProactSample {
97
+ return { day, bucket, source, id, at, title: title.replace(/\s+/g, ' ').trim().slice(0, 160), link };
98
+ }
99
+
100
+ /** Count one row once. Adoption is a property of an offered row, never a second offered row. */
101
+ export function measureProact(input: ProactMeterInput, now: Date, days = 7): ProactMeter {
102
+ const window = proactWindow(now, days);
103
+ const inWindow = (at: string | undefined): at is string => !!at && at >= window.since && at <= window.until;
104
+ const offered: ProactSample[] = [];
105
+ const adopted: ProactSample[] = [];
106
+ const asked: ProactSample[] = [];
107
+
108
+ if (!input.decisionsError) {
109
+ for (const card of input.decisions ?? []) {
110
+ const at = card.raisedAt ?? card.importedAt;
111
+ if (!inWindow(at)) continue;
112
+ const day = proactKstDay(at);
113
+ const link = `decisions://${card.id}`;
114
+ const system = card.raisedBy.agent !== 'human' && card.raisedBy.agent !== 'owner';
115
+ if (system) {
116
+ offered.push(sample(day, 'offered', 'decision', card.id, at, card.title, link));
117
+ const decidedAt = card.decidedAt;
118
+ const adoptedDay = decidedAt ? proactKstDay(decidedAt) : '';
119
+ if (card.status === 'decided' && decidedAt && decidedAt <= window.until) {
120
+ adopted.push(sample(adoptedDay, 'adopted', 'decision', card.id, decidedAt, card.title, link));
121
+ }
122
+ } else if (ASKED.test(`${card.title} ${card.scqa.c}`)) {
123
+ asked.push(sample(day, 'asked', 'decision', card.id, at, card.title, link));
124
+ }
125
+ }
126
+ for (const seat of input.seatDecisions ?? []) {
127
+ if (!inWindow(seat.recordedAt)) continue;
128
+ const at = seat.decidedAt && seat.decidedAt <= window.until ? seat.decidedAt : seat.recordedAt;
129
+ const adoptedDay = proactKstDay(at);
130
+ const link = `decisions://${seat.id}`;
131
+ offered.push(sample(proactKstDay(seat.recordedAt), 'offered', 'decision', seat.id, seat.recordedAt, seat.title, link));
132
+ if (window.days.includes(adoptedDay)) adopted.push(sample(adoptedDay, 'adopted', 'decision', seat.id, at, seat.title, link));
133
+ }
134
+ }
135
+
136
+ if (!input.directivesError) {
137
+ for (const row of input.directives ?? []) {
138
+ if (!inWindow(row.ts) || !ASKED.test(row.text)) continue;
139
+ asked.push(sample(proactKstDay(row.ts), 'asked', 'directive', `${row.source_file}:${row.line_no}`, row.ts, row.text, `directives://${row.source_file}#L${row.line_no}`));
140
+ }
141
+ }
142
+
143
+ if (!input.coordError) {
144
+ for (const event of input.coord ?? []) {
145
+ if (!inWindow(event.at)) continue;
146
+ const day = proactKstDay(event.at);
147
+ const link = event.refs.url ?? `coord://${event.id}`;
148
+ const text = `${event.summary} ${event.kind}`;
149
+ const systemOffered = event.refs.seat && event.refs.seat !== 'CEO' && (event.kind === '제안' || event.kind === '보고' || event.kind === '사고' || ALERT.test(text) || ACTION.test(text));
150
+ if (systemOffered) {
151
+ offered.push(sample(day, 'offered', 'coord', event.id, event.at, event.summary, link));
152
+ } else if (event.refs.seat === 'CEO' && (event.kind === '요청' || ASKED.test(text))) {
153
+ asked.push(sample(day, 'asked', 'coord', event.id, event.at, event.summary, link));
154
+ }
155
+ }
156
+ }
157
+
158
+ if (!input.loopsError) {
159
+ const proactive = new Set(['queued', 'launched', 'asked', 'hitl', 'shadow', 'attempting']);
160
+ for (const row of input.loops ?? []) {
161
+ if (!inWindow(row.at)) continue;
162
+ const day = proactKstDay(row.at);
163
+ const id = row.id ?? `${row.seat}:${row.at}`;
164
+ const link = `loop://${row.file}#${id}`;
165
+ const title = row.title ?? `${row.seat} ${row.status}`;
166
+ if (proactive.has(row.status) || row.action === 'decision' || row.action === 'harness') {
167
+ offered.push(sample(day, 'offered', 'loop', id, row.at, `${proactKind(title, row.action)}:${title}`, link));
168
+ if (row.status === 'launched' || row.status === 'queued' || row.action === 'decision') {
169
+ adopted.push(sample(day, 'adopted', 'loop', id, row.at, title, link));
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ const tally = new Map<string, { day: string; offered: number; adopted: number; asked: number }>(window.days.map(day => [day, { day, offered: 0, adopted: 0, asked: 0 }]));
176
+ for (const row of offered) tally.get(row.day)!.offered += 1;
177
+ for (const row of adopted) {
178
+ const cell = tally.get(row.day);
179
+ if (cell) cell.adopted += 1;
180
+ }
181
+ for (const row of asked) tally.get(row.day)!.asked += 1;
182
+ const pick = (rows: ProactSample[]) => {
183
+ const seen = new Set<string>();
184
+ const out: ProactSample[] = [];
185
+ for (const source of ['decision', 'directive', 'coord', 'loop'] as const) {
186
+ for (const row of rows.filter(item => item.source === source).sort((a, b) => b.at.localeCompare(a.at) || a.id.localeCompare(b.id))) {
187
+ if (seen.has(row.link)) continue;
188
+ seen.add(row.link);
189
+ out.push(row);
190
+ if (out.length === 2) break;
191
+ }
192
+ }
193
+ return out;
194
+ };
195
+ const status = (error: string | undefined, read: boolean): ProactSourceStatus => error ? { read: false, reason: error } : { read };
196
+ return {
197
+ since: window.since, until: window.until,
198
+ days: window.days.map(day => tally.get(day)!),
199
+ samples: [...pick(offered), ...pick(adopted), ...pick(asked)],
200
+ sources: {
201
+ decision: status(input.decisionsError, !input.decisionsError),
202
+ directive: status(input.directivesError, !input.directivesError),
203
+ coord: status(input.coordError, !input.coordError),
204
+ loop: status(input.loopsError, !input.loopsError),
205
+ },
206
+ };
207
+ }
208
+
209
+ function readLoopRows(root: string, days: readonly string[]): ProactLoopRow[] {
210
+ const base = join(root, 'seat-loop');
211
+ const seats = readdirSync(base, { withFileTypes: true }).filter(entry => entry.isDirectory()).map(entry => entry.name);
212
+ const rows: ProactLoopRow[] = [];
213
+ for (const seat of seats) {
214
+ for (const day of days) {
215
+ const file = join(base, seat, `${day}.jsonl`);
216
+ let text = '';
217
+ try { text = readFileSync(file, 'utf8'); } catch (error) {
218
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
219
+ throw error;
220
+ }
221
+ for (const line of text.split('\n')) {
222
+ if (!line.trim()) continue;
223
+ const parsed = JSON.parse(line) as { at?: string; ts?: string; status?: string; action?: string; item?: { id?: string; title?: string } };
224
+ const at = parsed.at ?? parsed.ts;
225
+ if (!at || !parsed.status) continue;
226
+ rows.push({ seat, at, status: parsed.status, action: parsed.action, id: parsed.item?.id, title: parsed.item?.title, file });
227
+ }
228
+ }
229
+ }
230
+ return rows;
231
+ }
232
+
233
+ export interface ProactMeterDeps {
234
+ readonly now?: Date;
235
+ readonly days?: number;
236
+ readonly stateDir?: string;
237
+ readonly instanceRoot?: string;
238
+ readonly readDecisions?: () => { decisions: DecisionEntry[]; seatDecisions: SeatDecisionRecord[] };
239
+ readonly readDirectives?: (since: string, until: string) => DirectiveRow[];
240
+ readonly readCoord?: (since: string) => CoordEvent[];
241
+ readonly readLoops?: (days: readonly string[]) => ProactLoopRow[];
242
+ }
243
+
244
+ /** Read the four ledgers. A source that cannot be opened is reported, never counted as zero evidence. */
245
+ export function readProactMeter(deps: ProactMeterDeps = {}): ProactMeter {
246
+ const now = deps.now ?? new Date();
247
+ const window = proactWindow(now, deps.days ?? 7);
248
+ const input: ProactMeterInput = {};
249
+ const stateDir = deps.stateDir;
250
+ try {
251
+ const read = deps.readDecisions ?? (() => {
252
+ const ledger = new DecisionLedger(stateDir ? { stateDir } : {});
253
+ return { decisions: ledger.list({ status: 'all', since: window.since }), seatDecisions: ledger.seatReport({ since: window.since }) };
254
+ });
255
+ const got = read();
256
+ input.decisions = got.decisions;
257
+ input.seatDecisions = got.seatDecisions;
258
+ } catch (error) { input.decisionsError = error instanceof Error ? error.message : String(error); }
259
+ try {
260
+ input.directives = (deps.readDirectives ?? ((since, until) => {
261
+ const index = openDirectiveIndex(stateDir);
262
+ try { return index.search('', { since, until, limit: 1000 }); }
263
+ finally { index.close(); }
264
+ }))(window.since, window.until);
265
+ } catch (error) { input.directivesError = error instanceof Error ? error.message : String(error); }
266
+ try {
267
+ input.coord = (deps.readCoord ?? ((since) => listCoordEvents({ since, channelOnly: true })))(window.since);
268
+ } catch (error) { input.coordError = error instanceof Error ? error.message : String(error); }
269
+ try {
270
+ const loopRoot = deps.instanceRoot ?? stateDir ?? effectiveInstanceRoot() ?? elanousStateRoot();
271
+ input.loops = (deps.readLoops ?? ((days) => readLoopRows(loopRoot, days)))(window.days);
272
+ } catch (error) { input.loopsError = error instanceof Error ? error.message : String(error); }
273
+ return measureProact(input, now, deps.days ?? 7);
274
+ }
275
+
276
+ export function formatProactMeter(meter: ProactMeter): string {
277
+ const lines = [`선제성 ${meter.since} ~ ${meter.until}`, '날짜 · 먼저 냄 · 채택 · 물어서야'];
278
+ for (const day of meter.days) lines.push(`${day.day} · ${day.offered} · ${day.adopted} · ${day.asked}`);
279
+ const unread = (Object.entries(meter.sources) as Array<[ProactSource, ProactSourceStatus]>).filter(([, status]) => !status.read);
280
+ if (unread.length) lines.push(`못 읽음: ${unread.map(([name, status]) => `${name}(${status.reason})`).join(' · ')}`);
281
+ if (meter.samples.length) {
282
+ lines.push('표본:');
283
+ for (const row of meter.samples) lines.push(` ${row.bucket} · ${row.source} · ${row.id} · ${row.link}`);
284
+ }
285
+ return lines.join('\n');
286
+ }
@@ -0,0 +1,86 @@
1
+ import type { DecisionEntry } from './decision-ledger.js';
2
+ import type { ChecklistItem } from '../release-loop/checklist.js';
3
+ import type { ReleaseSchedule } from '../release-loop/release-schedule.js';
4
+ import type { DraftSweepResult, SweepDraft } from '../self-dev/draft-sweep.js';
5
+
6
+ type ProactLoop = { readonly name: string; readonly state: 'alive' | 'late' | 'failing' | 'off' | 'unknown'; readonly scope?: string };
7
+ type ProactYellow = { readonly version: string; readonly item: Pick<ChecklistItem, 'id' | 'title' | 'status'> };
8
+ type ProactDrafts = { readonly drafts: readonly Pick<SweepDraft, 'number' | 'title'>[]; readonly sweep: Pick<DraftSweepResult, 'complete' | 'entries'> };
9
+ export interface ProactSignals {
10
+ /** A suspended run (resume) or an explicitly observed blocker, not merely an old open card. */
11
+ readonly decisions?: readonly (Pick<DecisionEntry, 'id' | 'title' | 'status' | 'raisedAt' | 'importedAt' | 'resume'> & { readonly blocked?: boolean })[];
12
+ readonly yellow?: readonly ProactYellow[];
13
+ readonly schedules?: readonly Pick<ReleaseSchedule, 'version' | 'landBy'>[];
14
+ readonly loops?: readonly ProactLoop[];
15
+ /** Only a complete, read-only runDraftSweep({ apply: false }) assessment proves draft staleness. */
16
+ readonly draftAssessment?: ProactDrafts;
17
+ }
18
+ type ProactSuggestion = {
19
+ readonly key: string;
20
+ readonly kind: 'decision' | 'yellow' | 'loop' | 'draft';
21
+ readonly title: string;
22
+ readonly reason: string;
23
+ readonly source: string;
24
+ readonly score: number;
25
+ };
26
+
27
+ const HOUR = 3_600_000;
28
+ const DECISION_AGE_HOURS = 24;
29
+ const LANDING_WINDOW_HOURS = 24;
30
+ const MAX_SUGGESTIONS = 3;
31
+
32
+ /** Rules-only, read-only selection. Missing/invalid observations are unknown, never evidence of urgency. */
33
+ export function selectProact1Lite(signals: ProactSignals, options: { now: Date; previouslySuggested?: ReadonlySet<string> }): ProactSuggestion[] {
34
+ const now = options.now.getTime();
35
+ if (!Number.isFinite(now)) throw new Error('valid now required');
36
+ const candidates: ProactSuggestion[] = [];
37
+ const elapsedHours = (iso: string | undefined): number => iso ? (now - Date.parse(iso)) / HOUR : NaN;
38
+ const valid = (text: string): boolean => text.trim().length > 0;
39
+
40
+ for (const card of signals.decisions ?? []) {
41
+ if (card.status !== 'open' || (card.blocked !== true && !card.resume) || !valid(card.id) || !valid(card.title)) continue;
42
+ // Imported historical cards have no known raise time; their import time is a lower bound on age.
43
+ const age = elapsedHours(card.raisedAt ?? card.importedAt);
44
+ if (!Number.isFinite(age) || age < DECISION_AGE_HOURS) continue;
45
+ candidates.push({ key: `decision:${card.id}`, kind: 'decision', title: card.title,
46
+ reason: `열린 결정 ${Math.floor(age)}시간 대기${card.raisedAt ? '' : ' (가져온 시각 기준)'}`,
47
+ source: card.id, score: 400 + Math.min(100, Math.floor(age / 24)) });
48
+ }
49
+
50
+ const deadlines = new Map((signals.schedules ?? []).map(schedule => [schedule.version, schedule.landBy]));
51
+ for (const { version, item } of signals.yellow ?? []) {
52
+ if (item.status !== 'yellow' || !valid(item.id) || !valid(item.title) || !valid(version)) continue;
53
+ const landBy = deadlines.get(version);
54
+ const remaining = landBy ? (Date.parse(landBy) - now) / HOUR : NaN;
55
+ if (!Number.isFinite(remaining) || remaining > LANDING_WINDOW_HOURS) continue;
56
+ candidates.push({ key: `yellow:${version}:${item.id}`, kind: 'yellow', title: item.title,
57
+ reason: remaining < 0 ? `착지 마감 ${Math.ceil(-remaining)}시간 경과` : `착지 마감 ${Math.ceil(remaining)}시간 전`,
58
+ source: `${version}/${item.id} · ${landBy}`, score: 500 + Math.min(100, Math.max(0, Math.ceil(-remaining))) });
59
+ }
60
+
61
+ for (const loop of signals.loops ?? []) {
62
+ if (loop.state !== 'late' || !valid(loop.name)) continue;
63
+ candidates.push({ key: `loop:${loop.scope ?? ''}:${loop.name}`, kind: 'loop', title: loop.name,
64
+ reason: '루프 실행 지연 관측', source: `${loop.scope ?? 'loop'}/${loop.name}`, score: 300 });
65
+ }
66
+
67
+ const assessment = signals.draftAssessment;
68
+ if (assessment?.sweep.complete) {
69
+ const drafts = new Map(assessment.drafts.map(draft => [draft.number, draft]));
70
+ for (const entry of assessment.sweep.entries) {
71
+ // A stale assessment is not a request to close: only surface the already-verified signal.
72
+ if (entry.action !== 'close' || entry.applied || !['stale-unobserved', 'claim-expired', 'claim-expired (self-implement.result final; worktree is not live)'].includes(entry.reason)) continue;
73
+ const draft = drafts.get(entry.number);
74
+ if (!draft || !valid(draft.title)) continue;
75
+ candidates.push({ key: `draft:${entry.number}`, kind: 'draft', title: draft.title,
76
+ reason: `draft PR 정체 (${entry.reason})`, source: `#${entry.number}`, score: 200 });
77
+ }
78
+ }
79
+
80
+ const selected = new Map<string, ProactSuggestion>();
81
+ for (const candidate of candidates) {
82
+ if (options.previouslySuggested?.has(candidate.key)) continue;
83
+ if (!selected.has(candidate.key) || selected.get(candidate.key)!.score < candidate.score) selected.set(candidate.key, candidate);
84
+ }
85
+ return [...selected.values()].sort((a, b) => b.score - a.score || a.key.localeCompare(b.key)).slice(0, MAX_SUGGESTIONS);
86
+ }
@@ -28,7 +28,7 @@ export function webPushDecisionTransport(
28
28
  const recommendation = 'skipped' in entry.recommendation ? '추천안 없음' : `추천안: ${entry.recommendation.option.toUpperCase()}`;
29
29
  const body = `${recommendation}${entry.dueAt ? ` · 기한: ${new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }).format(new Date(entry.dueAt))}` : ''}`;
30
30
  const result = await push({ title: title('대표 결정 · ', entry.title), body, url: url(id), tag: tag(id) });
31
- debug.log('decisions.webpush', 'card-sent', { id, delivered: result.delivered });
31
+ if (result.attempted) debug.log('decisions.webpush', 'card-sent', { id, delivered: result.delivered });
32
32
  return result.delivered ? { chat: 'webpush', message: id } : null;
33
33
  },
34
34
  edit: async (ref, _view) => {
@@ -66,6 +66,7 @@ import { localOcrText } from './telegram-card-followup.js';
66
66
  import type { runGraph } from './graph-runner/runner.js';
67
67
  import { readSlashContextNow, renderTelegramNow } from './context-bus/context-now-surfaces.js';
68
68
  import type { ContextNowDeps } from './context-bus/context-now.js';
69
+ import { botUnavailableSlashReply, deriveTuiSlashAvailability, type TuiSlashAvailability } from './maturity/tui-slash-availability.js';
69
70
 
70
71
  const SLASH_FOCUS_TURNS_DEFAULT = 8;
71
72
 
@@ -142,6 +143,8 @@ export interface DiscordSelfMessageDeps {
142
143
  log?: (msg: string) => void;
143
144
  cardFollowupDeps?: { ocrText?: (path: string) => Promise<string | null>; runGraph?: typeof runGraph; rootDir?: () => string };
144
145
  nowDeps?: ContextNowDeps;
146
+ /** Override the TUI slash projection for isolated message-path tests. */
147
+ tuiSlashAvailability?: readonly TuiSlashAvailability[];
145
148
  }
146
149
 
147
150
  /** Compose the full discord self+interweave onMessage handler. */
@@ -399,6 +402,12 @@ export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessa
399
402
  log(`session cmd: ${ctx.text.trim().slice(0, 40)}`);
400
403
  return sessionReply;
401
404
  }
405
+ const slashName = /^\/([a-z0-9_-]{1,32})(?:\s|$)/i.exec(ctx.text.trim())?.[1]?.toLowerCase();
406
+ if (slashName) {
407
+ const entry = (deps.tuiSlashAvailability ?? deriveTuiSlashAvailability()).find(({ name }) => name === slashName);
408
+ const unavailable = botUnavailableSlashReply(entry, 'discord');
409
+ if (unavailable) return unavailable;
410
+ }
402
411
  log(`◀ ${ctx.userName ?? ctx.userId}: ${ctx.text.slice(0, 80)}${ctx.attachments.length ? ` (+첨부 ${ctx.attachments.length})` : ''}`);
403
412
  debug.log('discord.self.turn', 'inbound', { channelId: ctx.channelId, chars: ctx.text.length, attachments: ctx.attachments.length });
404
413
  // C2+ — 첨부 기억(사진만 먼저 올리는 패턴 지원). 명령 여부와 무관.