elanous 0.2.12 → 0.2.14

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 (534) 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/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  9. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/{9906-482c6619b3fdda20.js → 8060-cc008cc6fedfa818.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/{page-a67e11ba962337b5.js → page-15374909d8f1aebd.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-793fa536b3a6ef37.js → page-2ce5471926761fdd.js} +1 -1
  69. package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/term/{page-e297866051595d81.js → page-274703bc41b71333.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  104. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
  105. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -1
  134. package/apps/pwa/out/inside/index.txt +22 -22
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/loops/index.html +1 -0
  140. package/apps/pwa/out/loops/index.txt +30 -0
  141. package/apps/pwa/out/market/index.html +1 -1
  142. package/apps/pwa/out/market/index.txt +22 -22
  143. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  144. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  145. package/apps/pwa/out/morning/index.html +1 -1
  146. package/apps/pwa/out/morning/index.txt +23 -23
  147. package/apps/pwa/out/observatory/index.html +1 -1
  148. package/apps/pwa/out/observatory/index.txt +23 -23
  149. package/apps/pwa/out/ops/checklist/index.html +1 -1
  150. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  151. package/apps/pwa/out/ops/release/index.html +1 -1
  152. package/apps/pwa/out/ops/release/index.txt +22 -22
  153. package/apps/pwa/out/ops/seats/index.html +1 -1
  154. package/apps/pwa/out/ops/seats/index.txt +22 -22
  155. package/apps/pwa/out/outputs/index.html +1 -1
  156. package/apps/pwa/out/outputs/index.txt +22 -22
  157. package/apps/pwa/out/reflection/index.html +1 -1
  158. package/apps/pwa/out/reflection/index.txt +23 -23
  159. package/apps/pwa/out/scheduler/index.html +1 -1
  160. package/apps/pwa/out/scheduler/index.txt +23 -23
  161. package/apps/pwa/out/sessions/index.html +1 -1
  162. package/apps/pwa/out/sessions/index.txt +22 -22
  163. package/apps/pwa/out/settings/devices/index.html +1 -1
  164. package/apps/pwa/out/settings/devices/index.txt +23 -23
  165. package/apps/pwa/out/settings/index.html +1 -1
  166. package/apps/pwa/out/settings/index.txt +23 -23
  167. package/apps/pwa/out/setup/done/index.html +1 -1
  168. package/apps/pwa/out/setup/done/index.txt +23 -23
  169. package/apps/pwa/out/setup/index.html +1 -1
  170. package/apps/pwa/out/setup/index.txt +23 -23
  171. package/apps/pwa/out/share/index.html +1 -1
  172. package/apps/pwa/out/share/index.txt +23 -23
  173. package/apps/pwa/out/showroom/index.html +1 -1
  174. package/apps/pwa/out/showroom/index.txt +22 -22
  175. package/apps/pwa/out/tasks/index.html +1 -1
  176. package/apps/pwa/out/tasks/index.txt +23 -23
  177. package/apps/pwa/out/term/index.html +1 -1
  178. package/apps/pwa/out/term/index.txt +23 -23
  179. package/apps/pwa/out/today/index.html +1 -1
  180. package/apps/pwa/out/today/index.txt +23 -23
  181. package/apps/pwa/out/trace/index.html +1 -1
  182. package/apps/pwa/out/trace/index.txt +23 -23
  183. package/apps/pwa/out/vault/index.html +1 -1
  184. package/apps/pwa/out/vault/index.txt +23 -23
  185. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  186. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  187. package/apps/pwa/out/workflows/index.html +1 -1
  188. package/apps/pwa/out/workflows/index.txt +23 -23
  189. package/apps/pwa/out/workspace/index.html +1 -1
  190. package/apps/pwa/out/workspace/index.txt +23 -23
  191. package/apps/pwa/out/worktrees/index.html +1 -1
  192. package/apps/pwa/out/worktrees/index.txt +23 -23
  193. package/examples/teams/personas/backend.yaml +4 -0
  194. package/examples/teams/personas/pm.yaml +4 -0
  195. package/examples/teams/personas/qa.yaml +4 -0
  196. package/examples/teams/small-product.yaml +20 -0
  197. package/graphs/lecture/lecture-note.yaml +28 -0
  198. package/graphs/lecture/recipes.yaml +17 -0
  199. package/graphs/orchestrator/orchestrator.yaml +59 -0
  200. package/graphs/orchestrator/recipes.yaml +18 -0
  201. package/graphs/release/recipes.yaml +3 -0
  202. package/graphs/release/release-loop.yaml +4 -1
  203. package/graphs/rhythm/daily.yaml +38 -0
  204. package/graphs/rhythm/recipes.yaml +18 -0
  205. package/graphs/rhythm/weekly.yaml +39 -0
  206. package/graphs/video/recipes.yaml +3 -0
  207. package/graphs/video/youtube-summary.yaml +17 -0
  208. package/package.json +2 -1
  209. package/scripts/audit-test-state-writes.ts +72 -17
  210. package/scripts/brand/check.ts +186 -0
  211. package/scripts/context-hooks/emit.ts +36 -14
  212. package/scripts/coord-channel-watch.sh +1 -0
  213. package/scripts/coord-post.sh +11 -1
  214. package/scripts/discord-seat-probe.ts +144 -0
  215. package/scripts/docs-cli-check.ts +23 -6
  216. package/scripts/expose-rubric-check.ts +118 -0
  217. package/scripts/expose-rubric-suggest.ts +163 -0
  218. package/scripts/heal/run-role.ts +14 -0
  219. package/scripts/hq/hq-standby.sh +65 -0
  220. package/scripts/hq/standby-promote.ts +173 -0
  221. package/scripts/hq/standby-role.ts +59 -0
  222. package/scripts/hq/standby-snapshot.ts +226 -0
  223. package/scripts/hq/standby-verify.ts +223 -0
  224. package/scripts/lecture-note/node.ts +256 -0
  225. package/scripts/lessons/handbook-page.ts +85 -0
  226. package/scripts/release-loop/gate-node.ts +62 -15
  227. package/scripts/release-loop/ops-upgrade-node.ts +17 -5
  228. package/scripts/release-loop/publish-node.ts +14 -1
  229. package/scripts/release-loop/unattended-release.ts +23 -1
  230. package/scripts/release-loop/vault-note-node.ts +196 -0
  231. package/scripts/release-story/draft.ts +172 -0
  232. package/scripts/rhythm/daily.ts +383 -0
  233. package/scripts/rhythm/weekly.ts +294 -0
  234. package/scripts/skills/public-check.ts +169 -0
  235. package/scripts/test-deterministic.ts +57 -11
  236. package/scripts/test-diet/effectiveness.ts +195 -0
  237. package/scripts/test-diet/lib.ts +16 -8
  238. package/scripts/test-diet/node.ts +77 -9
  239. package/scripts/ux/demo-rehearsal.ts +2 -2
  240. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  241. package/scripts/video/youtube-summary.ts +133 -0
  242. package/skills/diagram-master/SKILL.md +42 -49
  243. package/skills/diagram-master/references/pyproject.toml +4 -1
  244. package/skills/diagram-master/references/routing-guide.md +4 -2
  245. package/skills/grill-me/SKILL.md +1 -0
  246. package/skills/omni-crawl/SKILL.md +16 -16
  247. package/skills/omni-crawl/package-lock.json +1 -544
  248. package/skills/omni-crawl/package.json +3 -4
  249. package/skills/omni-crawl/scripts/main.ts +13 -13
  250. package/skills/omni-crawl/scripts/monitor.ts +8 -8
  251. package/skills/omni-crawl/src/apify.ts +13 -2
  252. package/skills/omni-crawl/src/capture.ts +6 -0
  253. package/skills/omni-crawl/src/registry.ts +2 -2
  254. package/skills/omni-crawl/src/stdout-json.ts +4 -0
  255. package/skills/omni-digest/SKILL.md +10 -11
  256. package/skills/omni-digest/package-lock.json +0 -545
  257. package/skills/omni-digest/package.json +2 -5
  258. package/skills/omni-digest/scripts/main.ts +3 -3
  259. package/skills/omni-digest/src/delegate.ts +5 -5
  260. package/skills/omni-digest/src/fetch.ts +2 -2
  261. package/skills/omni-digest/src/media.ts +25 -11
  262. package/skills/omni-digest/src/visual.ts +3 -2
  263. package/skills/omni-market/scripts/main.ts +1 -1
  264. package/skills/omni-market/scripts/stdout-json.ts +4 -0
  265. package/skills/youtube-master/SKILL.md +13 -13
  266. package/skills/youtube-master/package-lock.json +0 -545
  267. package/skills/youtube-master/package.json +2 -5
  268. package/skills/youtube-master/scripts/main.ts +2 -2
  269. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  270. package/skills/youtube-master/src/util.ts +30 -7
  271. package/src/agent/identity-env.ts +11 -0
  272. package/src/agent-mission/driver.ts +15 -10
  273. package/src/agent-mission/duo.ts +94 -0
  274. package/src/agent-mission/pty-decision.ts +1 -1
  275. package/src/autopilot/build/llm-conflict-merge.ts +45 -8
  276. package/src/autopilot/build/merge-intent.ts +53 -0
  277. package/src/autopilot/track-agent-shadow.ts +2 -1
  278. package/src/autopilot/track-agent.ts +4 -1
  279. package/src/boot/daemon-prompt-request.ts +3 -0
  280. package/src/boot/daemon-prompt-turn.ts +6 -2
  281. package/src/boot/daemon-runtime.ts +48 -16
  282. package/src/chat/index.ts +2 -0
  283. package/src/claims/claims-ledger.ts +232 -0
  284. package/src/claims/claims-render.ts +157 -0
  285. package/src/cli/claims-cli.ts +94 -0
  286. package/src/cli/decisions-cli.ts +84 -20
  287. package/src/cli/docs-cli.ts +2 -0
  288. package/src/cli/flow-cli.ts +11 -0
  289. package/src/cli/freeze-cli.ts +45 -0
  290. package/src/cli/lesson-cli.ts +108 -0
  291. package/src/cli/logs-cli.ts +104 -2
  292. package/src/cli/logs-model-input.ts +131 -0
  293. package/src/cli/outputs-cli.ts +92 -0
  294. package/src/cli/persona-cli.ts +29 -0
  295. package/src/cli/pod-cli.ts +10 -3
  296. package/src/cli/pr-cli.ts +44 -2
  297. package/src/cli/private-ledger-commands.ts +54 -4
  298. package/src/cli/pty-drive-verdict.ts +11 -2
  299. package/src/cli/release-cli.ts +248 -45
  300. package/src/cli/rfc-status.ts +91 -0
  301. package/src/connectors/linear.ts +5 -3
  302. package/src/context-bus/condense.ts +189 -0
  303. package/src/context-bus/context-day.ts +82 -0
  304. package/src/context-bus/context-now-public.ts +86 -0
  305. package/src/context-bus/context-now-surfaces.ts +70 -4
  306. package/src/context-bus/context-now.ts +4 -3
  307. package/src/context-bus/long-term-memory.ts +201 -24
  308. package/src/context-bus/nightly-condense.ts +1 -1
  309. package/src/dashboard/index.ts +75 -22
  310. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  311. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  312. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  313. package/src/decisions/decision-cards.ts +13 -7
  314. package/src/decisions/decision-ledger.ts +83 -18
  315. package/src/discord/sprint21-runtime.ts +16 -0
  316. package/src/discord-self-message.ts +8 -0
  317. package/src/discord-slash-wire.ts +31 -2
  318. package/src/domains/outbound-alert.ts +1 -1
  319. package/src/elanous-config-dir.ts +29 -4
  320. package/src/execution-loop/launch-gate.ts +29 -14
  321. package/src/flow/cards.ts +239 -0
  322. package/src/graph-kinds/registry.ts +2 -2
  323. package/src/graph-runner/graph-approval-card.ts +2 -2
  324. package/src/graph-runner/graph-grow.ts +64 -11
  325. package/src/graph-runner/graph-tick.ts +13 -2
  326. package/src/graph-runner/runner.ts +111 -22
  327. package/src/harness/harness-cli-command.ts +178 -27
  328. package/src/harness/harness-incidents.ts +43 -12
  329. package/src/harness/harness-pod-dispatch.ts +2 -0
  330. package/src/harness/harness-queue-child.ts +37 -0
  331. package/src/harness/harness-queue.ts +494 -0
  332. package/src/harness/helper-scan.ts +139 -0
  333. package/src/harness/model-input-observation.ts +37 -0
  334. package/src/hooks/github-shadow.ts +72 -2
  335. package/src/hooks/receiver.ts +4 -2
  336. package/src/hq/hq.ts +318 -0
  337. package/src/hq/lease.ts +210 -0
  338. package/src/hq/ledger-write-target.ts +41 -0
  339. package/src/hq/promote.ts +301 -0
  340. package/src/index.ts +281 -27
  341. package/src/intake-plane/discord-seat-work.ts +13 -6
  342. package/src/intake-plane/linear-wish.ts +72 -0
  343. package/src/intake-plane/persona-answer.ts +5 -0
  344. package/src/intake-plane/telegram-seat-work.ts +13 -6
  345. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  346. package/src/intake-plane/wish-card.ts +53 -2
  347. package/src/intake-plane/wish-folder.ts +5 -2
  348. package/src/intake-plane/wish-reply.ts +72 -0
  349. package/src/lessons/lesson-import.ts +106 -0
  350. package/src/lessons/lesson-ledger.ts +206 -0
  351. package/src/loops/activity.ts +203 -0
  352. package/src/loops/budget.ts +32 -0
  353. package/src/loops/checker.ts +180 -0
  354. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  355. package/src/loops/loop-cli.ts +72 -6
  356. package/src/loops/neighbors.ts +40 -0
  357. package/src/loops/observe.ts +59 -0
  358. package/src/loops/orchestrator/author-depth.ts +164 -0
  359. package/src/loops/orchestrator/author-input-check.ts +77 -0
  360. package/src/loops/orchestrator/author-ledger.ts +230 -0
  361. package/src/loops/orchestrator/finish-rate.ts +175 -0
  362. package/src/loops/orchestrator/overlap.ts +180 -0
  363. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  364. package/src/loops/orchestrator/tick.ts +302 -0
  365. package/src/loops/orchestrator/traffic.ts +165 -0
  366. package/src/loops/package/verify.ts +125 -0
  367. package/src/loops/registry.ts +454 -9
  368. package/src/loops/status-rows.ts +118 -0
  369. package/src/loops/verdict.ts +34 -0
  370. package/src/maturity/bot-slash-catalog.ts +66 -0
  371. package/src/maturity/feature-maturity.ts +8 -5
  372. package/src/memory.ts +37 -4
  373. package/src/nexus/api/card-followup-route.ts +118 -0
  374. package/src/nexus/api/dashboard.ts +6 -1
  375. package/src/nexus/api/decisions-route.ts +68 -0
  376. package/src/nexus/api/http-server.ts +65 -2
  377. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  378. package/src/nexus/api/personas.ts +2 -1
  379. package/src/nexus/api/schedules-read.ts +5 -1
  380. package/src/nexus/api/skill-exec-route.ts +97 -0
  381. package/src/nexus/api/task-cards-api.ts +44 -5
  382. package/src/nexus/api/workflows.ts +2 -1
  383. package/src/persona/loader.ts +18 -0
  384. package/src/persona/persona-todo.ts +14 -9
  385. package/src/persona/presets.ts +2 -1
  386. package/src/persona/team.ts +105 -0
  387. package/src/persona/types.ts +4 -0
  388. package/src/pod-lease/host-lease.ts +130 -20
  389. package/src/release-loop/auto-start.ts +48 -0
  390. package/src/release-loop/checklist.ts +13 -4
  391. package/src/release-loop/feature-store.ts +28 -16
  392. package/src/release-loop/landed-but-yellow.ts +12 -0
  393. package/src/release-loop/landing-freeze.ts +121 -0
  394. package/src/release-loop/placement.ts +242 -0
  395. package/src/release-loop/release-schedule.ts +3 -1
  396. package/src/seat-dispatch/ceo-commands.ts +2 -2
  397. package/src/seat-dispatch/seat-ask.ts +73 -15
  398. package/src/seat-dispatch/seat-questions.ts +58 -0
  399. package/src/seat-loop/seat-loop.ts +495 -46
  400. package/src/seat-loop/seat-pack.ts +127 -0
  401. package/src/self-dev/ask-launch-flow.ts +3 -0
  402. package/src/self-dev/dev-pipeline.ts +6 -2
  403. package/src/self-dev/draft-sweep.ts +76 -0
  404. package/src/self-dev/run-store.ts +73 -1
  405. package/src/self-dev/run-supervisor.ts +8 -0
  406. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  407. package/src/self-implement/budget-gate.ts +29 -0
  408. package/src/self-implement/expose-gate.ts +47 -0
  409. package/src/self-implement/frozen-merges.ts +294 -0
  410. package/src/self-implement/gate-cli.ts +10 -4
  411. package/src/self-implement/gate-scope.ts +46 -9
  412. package/src/self-implement/goal-archive.ts +222 -0
  413. package/src/self-implement/goal-lookup.ts +147 -0
  414. package/src/self-implement/heal-intake.ts +79 -0
  415. package/src/self-implement/host-regate.ts +25 -2
  416. package/src/self-implement/l8-merge-queue.ts +157 -1
  417. package/src/self-implement/orchestrator.ts +114 -10
  418. package/src/self-implement/revert-guard.ts +62 -0
  419. package/src/self-implement/run-ledger.ts +1 -1
  420. package/src/self-implement/seams.ts +28 -8
  421. package/src/session/index.ts +2 -0
  422. package/src/steward/launch.ts +5 -1
  423. package/src/steward/readiness.ts +125 -0
  424. package/src/steward/rescue.ts +75 -0
  425. package/src/steward/triage.ts +48 -9
  426. package/src/task-cards/card-cli.ts +86 -0
  427. package/src/task-cards/card-store.ts +10 -6
  428. package/src/task-orchestrator/surfaces/pod-lease.ts +23 -8
  429. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  430. package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
  431. package/src/task-orchestrator/surfaces/self-implement-pod.ts +112 -18
  432. package/src/telegram-commands.ts +27 -3
  433. package/src/telegram-dispatch-forward.ts +1 -1
  434. package/src/telegram-fabric-plan.ts +90 -0
  435. package/src/telegram-loops-command.ts +44 -0
  436. package/src/telegram-project-command.ts +10 -4
  437. package/src/telegram.ts +5 -0
  438. package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
  439. package/src/user-config.ts +204 -16
  440. package/src/version/packed-revision.json +1 -1
  441. package/src/workflow-runtime/executor.ts +4 -0
  442. package/src/workflow-runtime/node-catalog.ts +13 -0
  443. package/src/workflow-runtime/nodes/knowledge.ts +38 -0
  444. package/src/workflow-runtime/schema.ts +17 -1
  445. package/src/workflow-runtime/storage.ts +31 -13
  446. package/src/workflow-runtime/types.ts +13 -1
  447. package/apps/pwa/out/_next/static/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +0 -25
  459. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  463. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/8605-0259994816604c49.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/setup/page-9547b5cec7cf76bf.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  533. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  534. /package/apps/pwa/out/_next/static/{-6XrAoD3eU7NyMHAE4xPS → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
@@ -0,0 +1,90 @@
1
+ import { defaultBaseUrl, defaultToken } from './telegram-dispatch-forward.js';
2
+ import type { FabricPlan } from './self-dev/fabric-plan-core.js';
3
+ import type { TelegramBot, TgIncoming } from './telegram.js';
4
+
5
+ export interface TelegramFabricPlanDeps {
6
+ fetchImpl?: typeof fetch;
7
+ baseUrl?: () => string | null;
8
+ token?: () => string | null;
9
+ }
10
+
11
+ /** Only a successful daemon draft is offered for approval; no execution endpoint is called here. */
12
+ export function attachTelegramFabricPlan(
13
+ bot: Pick<TelegramBot, 'isOwnerAllowed' | 'sendMessage' | 'sendInlineKeyboard' | 'onCallbackQuery' | 'answerCallbackQuery'>,
14
+ deps: TelegramFabricPlanDeps = {},
15
+ ): (ctx: TgIncoming) => Promise<boolean> {
16
+ const fetchImpl = deps.fetchImpl ?? fetch;
17
+ const pending = new Map<string, { chatId: number; userId: number; threadId?: number; messageId: number }>();
18
+ const endpoint = (path: string): { url: string; token: string | null } => {
19
+ const base = (deps.baseUrl ?? defaultBaseUrl)();
20
+ if (!base) throw new Error('데몬에 연결할 수 없습니다');
21
+ return { url: `${base}${path}`, token: (deps.token ?? defaultToken)() };
22
+ };
23
+ const post = async (path: string, body?: unknown): Promise<unknown> => {
24
+ const { url, token } = endpoint(path);
25
+ const response = await fetchImpl(url, {
26
+ method: 'POST',
27
+ headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
28
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
29
+ });
30
+ if (!response.ok) throw new Error(`데몬 응답 ${response.status}`);
31
+ return response.json();
32
+ };
33
+
34
+ bot.onCallbackQuery(async (q) => {
35
+ const match = /^fabric:(approve|edit):([a-f0-9-]{36})$/i.exec(q.data);
36
+ if (!match) return;
37
+ const planId = match[2]!;
38
+ const original = pending.get(planId);
39
+ if (!original || !bot.isOwnerAllowed(q.userId) || q.userId !== original.userId
40
+ || q.chatId !== original.chatId || q.threadId !== original.threadId || q.messageId !== original.messageId) {
41
+ await bot.answerCallbackQuery(q.id, { text: '이 초안의 요청자만 선택할 수 있습니다' });
42
+ return;
43
+ }
44
+ if (match[1] === 'edit') {
45
+ pending.delete(planId);
46
+ await bot.answerCallbackQuery(q.id);
47
+ await bot.sendMessage(original.chatId, '고칠 내용을 반영해 «아크: <복합 문장>»으로 다시 보내세요. 이 초안은 승인되지 않았습니다.', { threadId: original.threadId });
48
+ return;
49
+ }
50
+ try {
51
+ await post(`/v1/fabric/plans/${planId}/approve`);
52
+ pending.delete(planId);
53
+ await bot.answerCallbackQuery(q.id, { text: '승인되었습니다' });
54
+ await bot.sendMessage(original.chatId, `아크 초안 ${planId} 승인됨 — 실행은 별도 단계입니다.`, { threadId: original.threadId });
55
+ } catch (error) {
56
+ await bot.answerCallbackQuery(q.id, { text: `승인 실패: ${error instanceof Error ? error.message : String(error)}` });
57
+ }
58
+ });
59
+
60
+ return async (ctx) => {
61
+ if (ctx.attachments.length || ctx.video || !ctx.text.startsWith('아크:')) return false;
62
+ const request = ctx.text.slice('아크:'.length).trim();
63
+ if (!request || request.includes('\n')) {
64
+ await bot.sendMessage(ctx.chatId, '«아크: <복합 문장>» 한 줄로 보내세요.', { replyTo: ctx.messageId, threadId: ctx.threadId });
65
+ return true;
66
+ }
67
+ try {
68
+ const body = await post('/v1/fabric/decompose', { request }) as { plan?: FabricPlan };
69
+ const plan = body.plan;
70
+ if (!plan || plan.status !== 'draft' || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(plan.id)
71
+ || !Array.isArray(plan.nodes) || plan.nodes.length === 0) throw new Error('초안 형식 오류');
72
+ const steps = plan.nodes.map((node, i) => `${i + 1}. ${node.title}`).join('\n');
73
+ const preview = `아크 초안 · 실행 전 검토\n${steps}`;
74
+ const reply = { replyTo: ctx.messageId, threadId: ctx.threadId };
75
+ for (let offset = 0; offset + 4000 < preview.length; offset += 4000) {
76
+ const sent = await bot.sendMessage(ctx.chatId, preview.slice(offset, offset + 4000), reply);
77
+ if (sent?.messageId === undefined) throw new Error('단계 미리보기를 전부 전달하지 못했습니다 — 승인 불가');
78
+ }
79
+ const finalOffset = Math.floor((preview.length - 1) / 4000) * 4000;
80
+ const sent = await bot.sendInlineKeyboard(ctx.chatId, preview.slice(finalOffset), [[
81
+ { text: '승인', data: `fabric:approve:${plan.id}` },
82
+ { text: '고치기', data: `fabric:edit:${plan.id}` },
83
+ ]], reply);
84
+ if (sent?.messageId !== undefined) pending.set(plan.id, { chatId: ctx.chatId, userId: ctx.userId, threadId: ctx.threadId, messageId: sent.messageId });
85
+ } catch (error) {
86
+ await bot.sendMessage(ctx.chatId, `아크 초안 생성 실패: ${error instanceof Error ? error.message : String(error)}`, { replyTo: ctx.messageId, threadId: ctx.threadId });
87
+ }
88
+ return true;
89
+ };
90
+ }
@@ -0,0 +1,44 @@
1
+ import { dashboardLoops } from './domains/dashboard-data.js';
2
+ import { readSchedulesInventory } from './nexus/api/schedules-read.js';
3
+ import { loadLoopRows, LOOP_SCHEDULES_PATH, LOOPS_PATH, type LoopRow } from './loops/status-rows.js';
4
+
5
+ interface LoopsSources {
6
+ schedules: () => unknown;
7
+ loops: () => unknown;
8
+ now: () => number;
9
+ }
10
+
11
+ const defaultSources: LoopsSources = {
12
+ schedules: () => ({ schedules: readSchedulesInventory() }),
13
+ loops: () => ({ loops: dashboardLoops() }),
14
+ now: Date.now,
15
+ };
16
+
17
+ const clean = (text: string) => text.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
18
+
19
+ /** Read the same two inventories and apply the same row verdicts as PWA /loops. */
20
+ export async function telegramLoopsStatus(args: string[], sources: LoopsSources = defaultSources): Promise<string> {
21
+ if (args.length) return '사용법: /loops';
22
+ try {
23
+ const rows = await loadLoopRows(async (path) => {
24
+ if (path === LOOP_SCHEDULES_PATH) return sources.schedules();
25
+ if (path === LOOPS_PATH) return sources.loops();
26
+ throw new Error('unknown inventory');
27
+ }, sources.now());
28
+ if (!rows.length) return '등록된 루프·크론이 없습니다.';
29
+ const counts = (['늦음', '실패', '살아 있음', '꺼짐', '판정 불가'] as const)
30
+ .map(verdict => `${verdict} ${rows.filter(row => row.verdict === verdict).length}`);
31
+ const priority: Record<LoopRow['verdict'], number> = { '늦음': 0, '실패': 1, '살아 있음': 2, '꺼짐': 3, '판정 불가': 4 };
32
+ const lines = rows.slice().sort((a, b) => priority[a.verdict] - priority[b.verdict]).map(row =>
33
+ `• ${row.verdict === '늦음' ? '🔴' : row.verdict === '실패' ? '❌' : row.verdict === '살아 있음' ? '🟢' : '⚪'} ${row.verdict} · ${clean(row.name)} (${clean(row.layer)}) · ${clean(row.mode)} · 마지막 ${row.lastRun ?? '기록 없음'} · ${clean(row.id)}`);
34
+ let reply = ['루프·크론 현황', counts.join(' · ')].join('\n');
35
+ for (const [index, line] of lines.entries()) {
36
+ // Telegram caps a message at 4096 characters; leave space for an explicit omission count.
37
+ if (reply.length + line.length + 35 > 4000) return `${reply}\n… ${lines.length - index}개 더 (메시지 길이 제한)`;
38
+ reply += `\n${line}`;
39
+ }
40
+ return reply;
41
+ } catch {
42
+ return '루프 현황을 읽지 못했습니다. 일부 레지스트리 조회가 실패했습니다. 데몬 연결을 확인하세요.';
43
+ }
44
+ }
@@ -35,17 +35,23 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
35
35
  debug.log('telegram.project', 'listed', { projects: projects.slice(0, 20).map(p => ({ id: p.id, name: p.name })) });
36
36
  if (ctx.isDm && ctx.chatId === ctx.userId
37
37
  && String(ctx.userId) === telegramDecisionOwner(ctxOptions.userConfig) && ctxOptions.sendButtons) {
38
- const buttons = projects.filter(p => {
38
+ const eligible = projects.filter(p => {
39
39
  const bytes = Buffer.byteLength(`prj:${p.id}`, 'utf8');
40
40
  if (bytes <= 64) return true;
41
41
  debug.log('telegram.project', 'button-rejected', { reason: 'callback-data-too-long', projectId: p.id, bytes });
42
42
  return false;
43
- }).slice(0, 8).map(p => ({ text: p.name, data: `prj:${p.id}` }));
44
- buttons.push({ text: '받은 대화로', data: 'prj:-' });
43
+ });
44
+ const visible = eligible.slice(0, 7);
45
+ if (current && eligible.some(p => p.id === current.id) && !visible.some(p => p.id === current.id)) {
46
+ visible[visible.length - 1] = current;
47
+ }
48
+ const buttons = visible.map(p => ({ text: `${p.id === current?.id ? '✓ ' : ''}${p.name}`, data: `prj:${p.id}` }));
49
+ buttons.push({ text: `${session.projectId ? '' : '✓ '}받은 대화로`, data: 'prj:-' });
45
50
  const rows: Array<Array<{ text: string; data: string }>> = [];
46
51
  for (let i = 0; i < buttons.length; i += 2) rows.push(buttons.slice(i, i + 2));
47
52
  try {
48
53
  await ctxOptions.sendButtons('프로젝트를 선택하세요', rows);
54
+ return;
49
55
  } catch (err) {
50
56
  debug.log('telegram.project', 'button-rejected', {
51
57
  reason: 'send-failed', error: err instanceof Error ? err.message : String(err),
@@ -140,6 +146,6 @@ export function attachTelegramProjectButtons(
140
146
  deps.update(session.id, m => { m.projectId = project.id; });
141
147
  debug.log('telegram.project', 'button-moved', { chatId, sessionId: session.id, projectId: project.id });
142
148
  await bot.answerCallbackQuery(q.id);
143
- await bot.sendMessage(chatId, `이 대화를 ${project.name} 프로젝트로 옮겼습니다`);
149
+ await bot.sendMessage(chatId, `지금 프로젝트: ${project.name}`);
144
150
  });
145
151
  }
package/src/telegram.ts CHANGED
@@ -77,6 +77,7 @@ import { handleTelegramFieldNote } from './field-note/telegram.js';
77
77
  import type { TranscriptionEngine, FieldAudio } from './field-note/transcribe.js';
78
78
  import { maybeHandleCardPhoto, type CardPhotoDeps } from './telegram-card-followup.js';
79
79
  import { attachTelegramProjectButtons } from './telegram-project-command.js';
80
+ import { attachTelegramFabricPlan } from './telegram-fabric-plan.js';
80
81
 
81
82
  export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
82
83
 
@@ -229,6 +230,7 @@ export interface TelegramBotOpts {
229
230
  fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
230
231
  /** Test seam for the business-card graph and its private run root. */
231
232
  cardFollowupDeps?: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
233
+ fabricPlanDeps?: import('./telegram-fabric-plan.js').TelegramFabricPlanDeps;
232
234
  }
233
235
 
234
236
  /** Surface-unification v2 (FU-2) — Telegram trigger event passed to
@@ -350,6 +352,7 @@ export class TelegramBot {
350
352
  private readonly fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
351
353
  private readonly cardFollowupDeps: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
352
354
  private readonly seatWorkDeps: TelegramSeatWorkDeps;
355
+ private readonly handleFabricPlan: (ctx: TgIncoming) => Promise<boolean>;
353
356
  private seatAskTimer: ReturnType<typeof setInterval> | null = null;
354
357
  /** 앨범 id → `#현장` 행사 · 답장 디바운스. 태그된 앨범만 들어온다. */
355
358
  private readonly fieldGroups = new Map<string, {
@@ -388,6 +391,7 @@ export class TelegramBot {
388
391
  this.fieldNoteEngine = opts.fieldNoteEngine;
389
392
  this.fieldNoteDecodeAudio = opts.fieldNoteDecodeAudio;
390
393
  this.cardFollowupDeps = opts.cardFollowupDeps ?? {};
394
+ this.handleFabricPlan = attachTelegramFabricPlan(this, opts.fabricPlanDeps);
391
395
  this.seatWorkDeps = { ...opts.seatWorkDeps, askDeps: { ...opts.seatWorkDeps?.askDeps,
392
396
  channel: 'telegram', botId: this.botId,
393
397
  send: opts.seatWorkDeps?.askDeps?.send ?? (async (origin, text) => {
@@ -1539,6 +1543,7 @@ export class TelegramBot {
1539
1543
  await this.refuseMessage(ctx);
1540
1544
  return;
1541
1545
  }
1546
+ if (await this.handleFabricPlan(ctx)) return;
1542
1547
  // 현장 업로드 — `#현장` 캡션(또는 태그된 앨범의 나머지)의 사진·영상은 LLM 대신 현장 폴더로.
1543
1548
  if (await this.tryHandleFieldUpload(ctx)) return;
1544
1549
  if (await handleTelegramFieldNote(ctx, {
@@ -3,6 +3,7 @@ import { StringDecoder } from 'node:string_decoder';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { dirname, resolve } from 'node:path';
5
5
  import type { LLMToolSpec } from '../llm.js';
6
+ import { stateDirSourceForChild } from '../agent/identity-env.js';
6
7
  import { debug } from '../debug/log.js';
7
8
  import { canonicalizeForBoundary, isWithinBoundary, resolveHarnessBoundary } from '../harness/harness-write-boundary.js';
8
9
  import { getHarnessSpace } from '../harness/harness-space.js';
@@ -133,7 +134,7 @@ async function spawnHeldElanous(command: string[], cwd: string, instanceRoot: st
133
134
  try {
134
135
  child = deps.spawn(command[0]!, command.slice(1), {
135
136
  cwd,
136
- env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot },
137
+ env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(instanceRoot) },
137
138
  detached: true,
138
139
  stdio: ['ignore', 'pipe', 'pipe'],
139
140
  });
@@ -3475,15 +3475,55 @@ export function resolveRoleModel(role: ModelRole, config: UserConfig = getUserCo
3475
3475
  }
3476
3476
 
3477
3477
 
3478
- export type StewardLoopMode = 'off' | 'shadow' | 'live';
3478
+ export type OrchestratorSeat = 'OP' | 'TC' | 'MK' | 'UX';
3479
+ export interface OrchestratorLoopConfig {
3480
+ /** ORCH1 graph mode — shadow writes observations only; live appends delegations (default shadow). */
3481
+ mode: 'off' | 'shadow' | 'live';
3482
+ seatTrees: Partial<Record<OrchestratorSeat, string[]>>;
3483
+ seatCaps: Record<OrchestratorSeat, number>;
3484
+ /** Per-seat release gate; absent seats retain the spawn-budget default. */
3485
+ releaseGate?: Partial<Record<OrchestratorSeat, number>>;
3486
+ trafficMode: 'shadow' | 'live';
3487
+ }
3488
+
3489
+ export const ORCHESTRATOR_DEFAULTS: OrchestratorLoopConfig = {
3490
+ mode: 'shadow', seatTrees: {}, seatCaps: { TC: 8, UX: 6, MK: 6, OP: 4 }, trafficMode: 'shadow',
3491
+ };
3492
+
3493
+ export function parseOrchestratorLoopConfig(raw: unknown): OrchestratorLoopConfig {
3494
+ const value = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
3495
+ const rawTrees = value.seatTrees && typeof value.seatTrees === 'object' && !Array.isArray(value.seatTrees)
3496
+ ? value.seatTrees as Record<string, unknown> : {};
3497
+ const rawCaps = value.seatCaps && typeof value.seatCaps === 'object' && !Array.isArray(value.seatCaps)
3498
+ ? value.seatCaps as Record<string, unknown> : {};
3499
+ const rawGate = value.releaseGate && typeof value.releaseGate === 'object' && !Array.isArray(value.releaseGate)
3500
+ ? value.releaseGate as Record<string, unknown> : {};
3501
+ const seatTrees: OrchestratorLoopConfig['seatTrees'] = {};
3502
+ const seatCaps = { ...ORCHESTRATOR_DEFAULTS.seatCaps };
3503
+ const releaseGate: NonNullable<OrchestratorLoopConfig['releaseGate']> = {};
3504
+ for (const seat of ['OP', 'TC', 'MK', 'UX'] as const) {
3505
+ if (Array.isArray(rawTrees[seat])) seatTrees[seat] = rawTrees[seat].filter((path): path is string => typeof path === 'string' && isAbsolute(path));
3506
+ const cap = rawCaps[seat];
3507
+ if (typeof cap === 'number' && Number.isSafeInteger(cap) && cap >= 0) seatCaps[seat] = cap;
3508
+ const gate = rawGate[seat];
3509
+ if (typeof gate === 'number' && Number.isSafeInteger(gate) && gate >= 0) releaseGate[seat] = gate;
3510
+ }
3511
+ const mode = value.mode === 'off' || value.mode === 'live' ? value.mode : 'shadow';
3512
+ return { mode, seatTrees, seatCaps, ...(Object.keys(releaseGate).length ? { releaseGate } : {}),
3513
+ trafficMode: value.trafficMode === 'live' ? 'live' : 'shadow' };
3514
+ }
3479
3515
 
3480
- export type SeatLoopMode = 'off' | 'shadow' | 'on';
3516
+ export type StewardLoopMode = 'off' | 'shadow' | 'live' | 'rescue';
3517
+
3518
+ export type SeatLoopMode = 'off' | 'shadow' | 'on' | 'live-safe';
3481
3519
 
3482
3520
  export interface PersonaLoopConfig { enabled: boolean }
3483
3521
 
3484
3522
  export interface SeatLoopConfig {
3485
- /** No seat actions by default; shadow observes without execution, on permits the seat loop to act. */
3523
+ /** Shadow observes by default; live-safe acts only through the shared rescue policy, while legacy on retains its behavior. */
3486
3524
  mode: SeatLoopMode;
3525
+ /** Validated loop-agent neighbor entries keyed by the observing seat. */
3526
+ neighbors?: Partial<Record<'OP' | 'TC' | 'MK' | 'UX', import('./loops/neighbors.js').Neighbor[]>>;
3487
3527
  /** Seat-to-seat and human questions default to shadow even when the checklist loop is on. */
3488
3528
  questions?: 'shadow' | 'on';
3489
3529
  seats?: string[];
@@ -3516,25 +3556,54 @@ export function parseEventsConfig(input: unknown): EventsConfig {
3516
3556
  return { mode: events.mode === 'on' ? 'on' : 'shadow', routes, linearAssignees };
3517
3557
  }
3518
3558
 
3559
+ export type HqFenceRole = 'telegram-poller' | 'cron' | 'seat-loop' | 'release-run' | 'conatus' | 'git-push' | 'ledger-cli';
3560
+ export interface HqConfig {
3561
+ /** ssh host that keeps the lease record — homes push to it; it never sshes back (default cloud-vm). */
3562
+ arbiter?: string;
3563
+ /** This host's identity in the lease (default os.hostname()). */
3564
+ hostName?: string;
3565
+ /** Standby host (default node-b) — the quorum's third member; promoted only by the arbiter. */
3566
+ standby?: string;
3567
+ /** Lease TTL in seconds (default 1500 = 25 min). */
3568
+ ttlSeconds?: number;
3569
+ /** Roles that may still run when this host is fenced only for lack of quorum (never when a newer generation exists). Absent = none. */
3570
+ failOpenRoles?: HqFenceRole[];
3571
+ /** Per-host nexus health URL over the tailnet (default https://<host>.<tailnetDomain>:31415/v1/health). */
3572
+ healthUrls?: Record<string, string>;
3573
+ /** MagicDNS suffix (default: read from `tailscale status --json`). */
3574
+ tailnetDomain?: string;
3575
+ /** `tailscale` binary (default tailscale). */
3576
+ tailscaleBin?: string;
3577
+ /** Highest lease generation this host ever saw (default ~/.elanous-hq/seen-generation · 600). */
3578
+ seenGenerationFile?: string;
3579
+ /** Per-probe timeout in seconds (default 5). */
3580
+ probeTimeoutSeconds?: number;
3581
+ }
3582
+
3519
3583
  export interface UserConfig {
3520
3584
  events?: EventsConfig;
3521
3585
  /** Explicit HTTPS destination for doctor diagnostics; absent means no upload. */
3522
3586
  diagnostics?: { uploadUrl?: string };
3587
+ /** HQ lease (본부 임대 · 10-04 HQ-HB/HQ-FENCE): arbiter host over ssh, this host's name, per-role fail-open when the arbiter is unreachable (default: fail closed). */
3588
+ hq?: HqConfig;
3523
3589
  /** Root CLI help audience; absent or invalid means owner. */
3524
3590
  cli?: { helpRole?: 'owner' | 'contributor' | 'general' };
3591
+ /** Daemon public demonstration mode; absent means off. */
3592
+ nexus?: { demoMode?: boolean };
3525
3593
  /** Execution-phase HITL answer deadline; absent means 30 minutes. */
3526
3594
  hitl?: { executionDeadlineMinutes?: number };
3527
3595
  coo?: { linearProject?: string };
3528
- decisions?: { linearProjection: { enabled: boolean } };
3596
+ decisions?: { linearProjection: { enabled: boolean }; requireCrossCheck: boolean;
3597
+ crossCheckNeighbor: Record<EventSeat, EventSeat>; crossCheckWaitMinutes: number };
3529
3598
  /** Per-seat goal and Pod limits; missing or invalid limits use seat-budget defaults. */
3530
3599
  org?: { budget?: Record<string, { dailyGoals?: number; concurrentPods?: number }> };
3531
- /** Steward and seat loops are parsed independently. An absent seat remains off. */
3532
- loops?: { steward?: { mode?: StewardLoopMode; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number; tracks?: Record<string, string>; alertAfterFailures?: number; launch?: 'off' | 'shadow' | 'live'; maxParallel?: number; podPool?: string }; seat?: SeatLoopConfig; persona?: PersonaLoopConfig };
3600
+ /** Steward and seat loops are parsed independently. An absent seat defaults to shadow. */
3601
+ loops?: { owners?: Record<string, EventSeat>; defaultOwner?: EventSeat; orchestrator?: OrchestratorLoopConfig; steward?: { mode?: StewardLoopMode; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number; tracks?: Record<string, string>; alertAfterFailures?: number; launch?: 'off' | 'shadow' | 'live'; maxParallel?: number; podPool?: string }; seat?: SeatLoopConfig; persona?: PersonaLoopConfig };
3533
3602
  /** 🔐 Pod 의 Grok API 키 과금은 명시 opt-in 만. 생략·잘못된 값은 false.
3534
3603
  * budgetGate 와 같은 `harness` 객체다 — 중복 선언이면 뒤 타입이 앞을 지운다. */
3535
- harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; difficultyPlacement?: boolean; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number };
3604
+ harness?: { revertGuard?: { depth: number }; pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; exposeGate?: 'warn' | 'strict'; difficultyPlacement?: boolean; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number; queue?: { seatCap?: Partial<Record<'OP' | 'TC' | 'MK' | 'UX', number>> } };
3536
3605
  /** ☸️ Pod 실행 칸 — `pool` = 기존 기본 풀(`컨텍스트[@ssh호스트][:상한][#k3d-레지스트리:포트]` 쉼표 · 앞이 우선). harness 실행은 인자·ELANOUS_POD_POOL·harness.podPool 다음으로 읽는다. */
3537
- pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string; /** 읽기 전용 Pod lease 권장에 쓰는 계정별 동시 수. */ lease?: { perAccount?: number } };
3606
+ pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string; /** 읽기 전용 Pod lease 권장에 쓰는 계정별 동시 수. */ lease?: { perAccount?: number }; /** 실측 권고를 Pod 발사 기본값으로 쓸지 (기본 off). */ memory?: { adviseDefaults?: boolean } };
3538
3607
  skillRouter: SkillRouterConfig;
3539
3608
  llm: LLMConfig;
3540
3609
  skills: SkillsConfig;
@@ -3860,8 +3929,9 @@ function defaultConfig(): UserConfig {
3860
3929
  return {
3861
3930
  cli: { helpRole: 'owner' },
3862
3931
  coo: { linearProject: '외부 행정·큰 일 (COO)' },
3863
- decisions: { linearProjection: { enabled: false } },
3864
- loops: { steward: { mode: 'shadow' }, seat: { mode: 'off', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' }, persona: { enabled: false } },
3932
+ decisions: { linearProjection: { enabled: false }, requireCrossCheck: false,
3933
+ crossCheckNeighbor: { OP: 'TC', TC: 'OP', MK: 'OP', UX: 'OP' }, crossCheckWaitMinutes: 120 },
3934
+ loops: { owners: {}, defaultOwner: 'OP', orchestrator: parseOrchestratorLoopConfig(undefined), steward: { mode: 'shadow' }, seat: { mode: 'shadow', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' }, persona: { enabled: false } },
3865
3935
  skillRouter: { ...SR_DEFAULTS },
3866
3936
  llm: { ...LLM_DEFAULTS },
3867
3937
  skills: skillsDefaults(),
@@ -3930,7 +4000,7 @@ function defaultConfig(): UserConfig {
3930
4000
  nativeStructure: { ...TOOLS_DEFAULTS.nativeStructure },
3931
4001
  selfImplement: { ...TOOLS_DEFAULTS.selfImplement },
3932
4002
  },
3933
- harness: { budgetGate: { ...DEFAULT_BUDGET_GATE }, difficultyPlacement: false },
4003
+ harness: { revertGuard: { depth: 50 }, budgetGate: { ...DEFAULT_BUDGET_GATE }, exposeGate: 'warn', difficultyPlacement: false },
3934
4004
  grounding: { sources: [] },
3935
4005
  raw: {},
3936
4006
  };
@@ -4341,20 +4411,95 @@ function parseStewardTracks(input: unknown): Record<string, string> | undefined
4341
4411
  return Object.keys(out).length ? out : undefined;
4342
4412
  }
4343
4413
 
4414
+ function parseDecisionsConfig(input: unknown): NonNullable<UserConfig['decisions']> {
4415
+ const defaults: Record<EventSeat, EventSeat> = { OP: 'TC', TC: 'OP', MK: 'OP', UX: 'OP' };
4416
+ const raw = input && typeof input === 'object' && !Array.isArray(input) ? input as Record<string, unknown> : {};
4417
+ const neighbors = { ...defaults };
4418
+ if (raw.crossCheckNeighbor !== undefined) {
4419
+ if (!raw.crossCheckNeighbor || typeof raw.crossCheckNeighbor !== 'object' || Array.isArray(raw.crossCheckNeighbor)) {
4420
+ warnUserConfigDrop('decisions.crossCheckNeighbor', '자리별 이웃 자리 객체가 아닙니다 — 기본값으로 진행합니다.');
4421
+ } else {
4422
+ const values = raw.crossCheckNeighbor as Record<string, unknown>;
4423
+ for (const seat of Object.keys(defaults) as EventSeat[]) {
4424
+ if (values[seat] === undefined) continue;
4425
+ const to = values[seat];
4426
+ if ((to === 'OP' || to === 'TC' || to === 'MK' || to === 'UX') && to !== seat) neighbors[seat] = to;
4427
+ else warnUserConfigDrop(`decisions.crossCheckNeighbor.${seat}`, '다른 자리가 아닙니다 — 기본값으로 진행합니다.');
4428
+ }
4429
+ for (const seat of Object.keys(values)) {
4430
+ if (!(seat in defaults)) warnUserConfigDrop(`decisions.crossCheckNeighbor.${seat}`, '알 수 없는 자리입니다 — 기본값으로 진행합니다.');
4431
+ }
4432
+ }
4433
+ }
4434
+ const wait = raw.crossCheckWaitMinutes;
4435
+ if (wait !== undefined && !(typeof wait === 'number' && Number.isSafeInteger(wait) && wait > 0)) {
4436
+ warnUserConfigDrop('decisions.crossCheckWaitMinutes', '양의 정수가 아닙니다 — 기본값 120으로 진행합니다.');
4437
+ }
4438
+ return { linearProjection: { enabled: (raw.linearProjection as { enabled?: unknown } | undefined)?.enabled === true },
4439
+ requireCrossCheck: raw.requireCrossCheck === true, crossCheckNeighbor: neighbors,
4440
+ crossCheckWaitMinutes: typeof wait === 'number' && Number.isSafeInteger(wait) && wait > 0 ? wait : 120 };
4441
+ }
4442
+
4443
+ function parseLoopOwners(input: unknown): { owners: Record<string, EventSeat>; defaultOwner: EventSeat } {
4444
+ const raw = input && typeof input === 'object' && !Array.isArray(input) ? input as Record<string, unknown> : {};
4445
+ const valid = (value: unknown): value is EventSeat => value === 'OP' || value === 'TC' || value === 'MK' || value === 'UX';
4446
+ const defaultOwner = valid(raw.defaultOwner) ? raw.defaultOwner : 'OP';
4447
+ if (raw.defaultOwner !== undefined && !valid(raw.defaultOwner)) warnUserConfigDrop('loops.defaultOwner', '잘못된 자리 — 기본 OP 로 진행');
4448
+ // Prototype-less: a loop titled «__proto__» (or «constructor») must keep its owner (review round 3).
4449
+ const owners: Record<string, EventSeat> = Object.create(null) as Record<string, EventSeat>;
4450
+ if (raw.owners !== undefined) {
4451
+ if (!raw.owners || typeof raw.owners !== 'object' || Array.isArray(raw.owners)) {
4452
+ warnUserConfigDrop('loops.owners', '객체가 아니다 — 기본 자리로 진행');
4453
+ } else {
4454
+ for (const [key, value] of Object.entries(raw.owners)) {
4455
+ if (key.trim() && valid(value)) owners[key] = value;
4456
+ else warnUserConfigDrop(`loops.owners.${key}`, `잘못된 자리 — 기본 ${defaultOwner} 로 진행`);
4457
+ }
4458
+ }
4459
+ }
4460
+ return { owners, defaultOwner };
4461
+ }
4462
+
4344
4463
  function parseSeatLoopsConfig(input: unknown): SeatLoopConfig {
4345
- const defaults: SeatLoopConfig = { mode: 'off', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' };
4464
+ const defaults: SeatLoopConfig = { mode: 'shadow', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' };
4346
4465
  if (!input || typeof input !== 'object' || Array.isArray(input)) return defaults;
4347
4466
  const seat = (input as Record<string, unknown>).seat;
4348
4467
  if (!seat || typeof seat !== 'object' || Array.isArray(seat)) return defaults;
4349
4468
  const values = seat as Record<string, unknown>;
4350
4469
  const mode = values.mode;
4351
4470
  return {
4352
- mode: mode === 'on' || mode === 'shadow' ? mode : 'off',
4471
+ mode: mode === 'on' || mode === 'shadow' || mode === 'live-safe' || mode === 'off' ? mode : 'shadow',
4353
4472
  questions: values.questions === 'on' ? 'on' : 'shadow',
4354
4473
  seats: Array.isArray(values.seats) && values.seats.length > 0
4355
4474
  && values.seats.every((value) => typeof value === 'string' && /^(?:MK|OP|TC|UX)$/.test(value))
4356
4475
  ? [...new Set(values.seats as string[])] : ['MK'],
4357
4476
  podPool: typeof values.podPool === 'string' && values.podPool.trim() ? values.podPool.trim() : defaults.podPool,
4477
+ ...(() => {
4478
+ const raw = values.neighbors;
4479
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
4480
+ const neighbors: NonNullable<SeatLoopConfig['neighbors']> = {};
4481
+ for (const [owner, entries] of Object.entries(raw)) {
4482
+ if (!/^(?:OP|TC|MK|UX)$/.test(owner) || !Array.isArray(entries)) continue;
4483
+ const valid = entries.filter((entry): entry is import('./loops/neighbors.js').Neighbor => {
4484
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return false;
4485
+ const value = entry as Record<string, unknown>;
4486
+ const heartbeat = value.heartbeat && typeof value.heartbeat === 'object' && !Array.isArray(value.heartbeat)
4487
+ ? value.heartbeat as Record<string, unknown> : undefined;
4488
+ const onAbsent = value.onAbsent && typeof value.onAbsent === 'object' && !Array.isArray(value.onAbsent)
4489
+ ? value.onAbsent as Record<string, unknown> : undefined;
4490
+ return typeof value.id === 'string' && /^(?:op|tc|mk|ux)-seat$/.test(value.id)
4491
+ && value.id !== `${owner.toLowerCase()}-seat`
4492
+ && Array.isArray(value.exchange) && value.exchange.length > 0
4493
+ && value.exchange.every((item) => typeof item === 'string' && ['status', 'request', 'decision', 'blocked'].includes(item))
4494
+ && !!heartbeat && Number.isSafeInteger(heartbeat.everyMinutes) && (heartbeat.everyMinutes as number) > 0
4495
+ && Number.isSafeInteger(heartbeat.missedTicks) && (heartbeat.missedTicks as number) > 0
4496
+ && !!onAbsent && typeof onAbsent.action === 'string' && ['delegate', 'defer', 'escalate'].includes(onAbsent.action)
4497
+ && typeof onAbsent.delegateTo === 'string' && onAbsent.delegateTo.trim().length > 0;
4498
+ });
4499
+ neighbors[owner as keyof typeof neighbors] = valid;
4500
+ }
4501
+ return { neighbors };
4502
+ })(),
4358
4503
  ...(typeof values.reportPr === 'number' && Number.isSafeInteger(values.reportPr) && values.reportPr > 0
4359
4504
  ? { reportPr: values.reportPr } : {}),
4360
4505
  ...(() => {
@@ -4388,7 +4533,7 @@ function parseStewardLoopsConfig(input: unknown): UserConfig['loops'] {
4388
4533
  }
4389
4534
  }
4390
4535
  return { steward: {
4391
- mode: s.mode === 'off' || s.mode === 'shadow' || s.mode === 'live' ? s.mode :
4536
+ mode: s.mode === 'off' || s.mode === 'shadow' || s.mode === 'live' || s.mode === 'rescue' ? s.mode :
4392
4537
  s.launch === 'off' || s.launch === 'shadow' || s.launch === 'live' ? s.launch : 'shadow',
4393
4538
  // A valid legacy launch setting remains effective when mode is absent; explicit mode always wins.
4394
4539
  ...(s.launch === 'off' || s.launch === 'shadow' || s.launch === 'live' ? { launch: s.launch } : {}),
@@ -4407,6 +4552,25 @@ export function parseCliHelpRole(raw: unknown): NonNullable<UserConfig['cli']>['
4407
4552
  return cli.helpRole === 'general' || cli.helpRole === 'contributor' ? cli.helpRole : 'owner';
4408
4553
  }
4409
4554
 
4555
+ const HQ_FENCE_ROLES: readonly HqFenceRole[] = ['telegram-poller', 'cron', 'seat-loop', 'release-run', 'conatus', 'git-push'];
4556
+ /** `hq` block (HQ-HB · HQ-FENCE): keep only well-typed fields — absent/invalid fields fall back to the hq module defaults. */
4557
+ export function parseHqConfig(raw: unknown): HqConfig | undefined {
4558
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;
4559
+ const r = raw as Record<string, unknown>;
4560
+ const str = (v: unknown) => typeof v === 'string' && v.trim() ? v.trim() : undefined;
4561
+ const out: HqConfig = {};
4562
+ for (const key of ['arbiter', 'hostName', 'standby', 'tailnetDomain', 'tailscaleBin', 'seenGenerationFile'] as const) {
4563
+ const v = str(r[key]); if (v) out[key] = v;
4564
+ }
4565
+ if (typeof r.ttlSeconds === 'number' && Number.isFinite(r.ttlSeconds) && r.ttlSeconds > 0) out.ttlSeconds = r.ttlSeconds;
4566
+ if (typeof r.probeTimeoutSeconds === 'number' && Number.isFinite(r.probeTimeoutSeconds) && r.probeTimeoutSeconds > 0) out.probeTimeoutSeconds = r.probeTimeoutSeconds;
4567
+ if (Array.isArray(r.failOpenRoles)) out.failOpenRoles = r.failOpenRoles.filter((v): v is HqFenceRole => HQ_FENCE_ROLES.includes(v as HqFenceRole));
4568
+ if (r.healthUrls && typeof r.healthUrls === 'object' && !Array.isArray(r.healthUrls)) {
4569
+ out.healthUrls = Object.fromEntries(Object.entries(r.healthUrls as Record<string, unknown>).filter((e): e is [string, string] => typeof e[1] === 'string'));
4570
+ }
4571
+ return Object.keys(out).length ? out : undefined;
4572
+ }
4573
+
4410
4574
  export function buildUserConfig(path: string = defaultPath()): UserConfig {
4411
4575
  if (!existsSync(path)) return defaultConfigWithRuntimeProvider();
4412
4576
  let raw: unknown;
@@ -4526,12 +4690,17 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4526
4690
  };
4527
4691
  }
4528
4692
 
4693
+ const nexus = rawObj.nexus && typeof rawObj.nexus === 'object' && !Array.isArray(rawObj.nexus)
4694
+ ? rawObj.nexus as Record<string, unknown> : {};
4529
4695
  return {
4696
+ nexus: { demoMode: nexus.demoMode === true },
4530
4697
  ...(Object.keys(seatBudgets).length ? { org: { budget: seatBudgets } } : {}),
4531
4698
  pod: {
4532
4699
  ...(typeof legacyPod.pool === 'string' && legacyPod.pool.trim() ? { pool: legacyPod.pool.trim() } : {}),
4533
4700
  ...(typeof legacyPod.hostMirror === 'string' && legacyPod.hostMirror.trim() ? { hostMirror: legacyPod.hostMirror.trim() } : {}),
4534
4701
  ...(typeof legacyPod.groundingUrl === 'string' ? { groundingUrl: legacyPod.groundingUrl } : {}),
4702
+ memory: { adviseDefaults: Boolean(legacyPod.memory && typeof legacyPod.memory === 'object' && !Array.isArray(legacyPod.memory)
4703
+ && (legacyPod.memory as Record<string, unknown>).adviseDefaults === true) },
4535
4704
  lease: { perAccount: (() => {
4536
4705
  const lease = legacyPod.lease && typeof legacyPod.lease === 'object' && !Array.isArray(legacyPod.lease)
4537
4706
  ? (legacyPod.lease as Record<string, unknown>) : {};
@@ -4540,8 +4709,20 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4540
4709
  })() },
4541
4710
  },
4542
4711
  harness: {
4712
+ revertGuard: { depth: (() => {
4713
+ const value = harness.revertGuard && typeof harness.revertGuard === 'object' && !Array.isArray(harness.revertGuard)
4714
+ ? (harness.revertGuard as Record<string, unknown>).depth : undefined;
4715
+ return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : 50;
4716
+ })() },
4543
4717
  pod: { grokApiKeyOptIn: harnessPod.grokApiKeyOptIn === true },
4544
4718
  difficultyPlacement: harness.difficultyPlacement === true,
4719
+ exposeGate: harness.exposeGate === 'strict' ? 'strict' : 'warn',
4720
+ queue: { seatCap: Object.fromEntries(Object.entries(
4721
+ harness.queue && typeof harness.queue === 'object' && !Array.isArray(harness.queue)
4722
+ && (harness.queue as Record<string, unknown>).seatCap && typeof (harness.queue as Record<string, unknown>).seatCap === 'object'
4723
+ && !Array.isArray((harness.queue as Record<string, unknown>).seatCap)
4724
+ ? (harness.queue as { seatCap: Record<string, unknown> }).seatCap : {},
4725
+ ).filter(([seat, cap]) => /^(OP|TC|MK|UX)$/.test(seat) && typeof cap === 'number' && Number.isSafeInteger(cap) && cap > 0)) },
4545
4726
  ...(harness.substrate === 'local' || harness.substrate === 'pod' ? { substrate: harness.substrate } : {}),
4546
4727
  ...(typeof harness.podPool === 'string' && harness.podPool.trim() ? { podPool: harness.podPool.trim() } : {}),
4547
4728
  ...(typeof harness.worktreeAddTimeoutSec === 'number' && Number.isSafeInteger(harness.worktreeAddTimeoutSec)
@@ -5186,11 +5367,12 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
5186
5367
  && typeof (rawObj.hitl as Record<string, unknown>).executionDeadlineMinutes === 'number'
5187
5368
  ? { hitl: { executionDeadlineMinutes: (rawObj.hitl as { executionDeadlineMinutes: number }).executionDeadlineMinutes } }
5188
5369
  : {}),
5370
+ ...(parseHqConfig(rawObj.hq) ? { hq: parseHqConfig(rawObj.hq)! } : {}),
5189
5371
  cli: { helpRole: parseCliHelpRole(rawObj.cli) },
5190
5372
  coo: { linearProject: typeof (rawObj.coo as { linearProject?: unknown } | undefined)?.linearProject === 'string' && (rawObj.coo as { linearProject: string }).linearProject.trim()
5191
5373
  ? (rawObj.coo as { linearProject: string }).linearProject.trim() : '외부 행정·큰 일 (COO)' },
5192
- decisions: { linearProjection: { enabled: ((rawObj.decisions as { linearProjection?: { enabled?: unknown } } | undefined)?.linearProjection?.enabled === true) } },
5193
- loops: { ...(stewardLoops ?? {}), seat: parseSeatLoopsConfig(rawObj.loops),
5374
+ decisions: parseDecisionsConfig(rawObj.decisions),
5375
+ loops: { ...(stewardLoops ?? {}), ...parseLoopOwners(rawObj.loops), orchestrator: parseOrchestratorLoopConfig((rawObj.loops as { orchestrator?: unknown } | undefined)?.orchestrator), seat: parseSeatLoopsConfig(rawObj.loops),
5194
5376
  persona: { enabled: (rawObj.loops as { persona?: { enabled?: unknown } } | undefined)?.persona?.enabled === true } },
5195
5377
  events: parseEventsConfig(rawObj.events),
5196
5378
  // M1-1: sparse — undefined when the user hasn't set anything, so
@@ -5786,20 +5968,26 @@ export function saveUserConfig(
5786
5968
  ...rawHarness,
5787
5969
  pod: cfg.harness?.pod ?? rawHarness.pod,
5788
5970
  budgetGate: cfg.harness?.budgetGate ?? rawHarness.budgetGate,
5971
+ exposeGate: cfg.harness?.exposeGate ?? rawHarness.exposeGate,
5972
+ revertGuard: cfg.harness?.revertGuard ?? rawHarness.revertGuard,
5789
5973
  difficultyPlacement: cfg.harness?.difficultyPlacement ?? rawHarness.difficultyPlacement,
5790
5974
  defaultRepo: cfg.harness?.defaultRepo ?? rawHarness.defaultRepo,
5791
5975
  substrate: cfg.harness?.substrate,
5792
5976
  podPool: cfg.harness?.podPool,
5977
+ queue: cfg.harness?.queue ?? rawHarness.queue,
5793
5978
  }),
5794
5979
  ...rawRest,
5795
5980
  ...(cfg.loops ? { loops: {
5796
5981
  ...rawLoops,
5982
+ owners: cfg.loops.owners ?? {}, defaultOwner: cfg.loops.defaultOwner ?? 'OP',
5797
5983
  ...(cfg.loops.steward ? { steward: { ...rawSteward, mode: cfg.loops.steward.mode ?? cfg.loops.steward.launch ?? 'shadow' } } : {}),
5984
+ ...(cfg.loops.orchestrator ? { orchestrator: cfg.loops.orchestrator } : {}),
5798
5985
  ...(cfg.loops.seat ? { seat: {
5799
5986
  mode: cfg.loops.seat.mode,
5800
5987
  questions: cfg.loops.seat.questions ?? 'shadow',
5801
5988
  seats: cfg.loops.seat.seats ?? ['MK'],
5802
5989
  podPool: cfg.loops.seat.podPool ?? 'pool-node-b@node-b:8',
5990
+ ...(cfg.loops.seat.neighbors ? { neighbors: cfg.loops.seat.neighbors } : {}),
5803
5991
  ...(cfg.loops.seat.reportPr === undefined ? {} : { reportPr: cfg.loops.seat.reportPr }),
5804
5992
  } } : {}),
5805
5993
  } } : {}),
@@ -1 +1 @@
1
- {"commit":"27045f4612b148543fb0f91b82d37592d1d6c9f2"}
1
+ {"commit":"855920facfd9b702ac0c47f96011b2fd67dc0f30"}
@@ -18,6 +18,7 @@ import {
18
18
  isClassifyNode,
19
19
  isExtractNode,
20
20
  isFilterNode,
21
+ isKnowledgeNode,
21
22
  isHttpRequestNode,
22
23
  isIfNode,
23
24
  isIterationNode,
@@ -57,6 +58,7 @@ import { executeExtractNode } from './nodes/extract.js';
57
58
  import { executeSetNode } from './nodes/set.js';
58
59
  import { executeTaskNode } from './nodes/task.js';
59
60
  import { executeFilterNode } from './nodes/filter.js';
61
+ import { executeKnowledgeNode } from './nodes/knowledge.js';
60
62
  import { executeTemplateNode } from './nodes/template.js';
61
63
  import { executeHttpRequestNode } from './nodes/http.js';
62
64
  import { executeShowroomNode } from './nodes/showroom.js';
@@ -706,6 +708,7 @@ async function dispatchNode(
706
708
  if (isExtractNode(node)) return executeExtractNode(node, ctx, deps);
707
709
  if (isSetNode(node)) return executeSetNode(node, ctx, deps);
708
710
  if (isFilterNode(node)) return executeFilterNode(node, ctx, deps);
711
+ if (isKnowledgeNode(node)) return executeKnowledgeNode(node, ctx, deps);
709
712
  if (isTemplateNode(node)) return executeTemplateNode(node, ctx, deps);
710
713
  if (isHttpRequestNode(node)) return executeHttpRequestNode(node, ctx, deps);
711
714
  if (isShowroomNode(node)) return executeShowroomNode(node, ctx, deps);
@@ -748,6 +751,7 @@ function isTriggerVariant(node: DagNode): boolean {
748
751
 
749
752
  function variantOf(node: DagNode): string {
750
753
  if (isPluginKindNode(node)) return node.kind;
754
+ if (isKnowledgeNode(node)) return 'knowledge';
751
755
  const guards: ReadonlyArray<(node: DagNode) => boolean> = [
752
756
  isPromptNode, isBashNode, isSkillNode, isCftNode, isApprovalNode, isIfNode,
753
757
  isSwitchNode, isIterationNode, isClassifyNode, isExtractNode, isSetNode,