elanous 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{1394-556de6d71bbdb0fb.js → 1394-931c9827067b2276.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/{3992-2e522a8ac5f2afde.js → 3992-fe5c06d6b31f8b25.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-9da4678f8a310d18.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-e49b9b76c565a0b1.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-4c1dbb21861beab2.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-f6bc66b919b2ea7d.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-60603c1e056c9b25.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-a0ea8f5a2341ee6b.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-31012264da8a341f.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/{layout-d6b1d156fb95e74b.js → layout-50309aeb0bdd554a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-0696ee3fac9fe56c.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6ddd2109b7fc3a80.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-7a2bf830038a2264.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-371423d1a573d64b.js → page-1f327f18694fabca.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4b764b125339ac17.js → page-e5cef2fe6e4028df.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-cdf8e4b01067bcf1.js → page-cf596128d2b420ba.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-f672536f14f0969f.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-29a709616df9b050.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-fee842d7c2151129.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-4dc614f3ce043e63.js → page-78b40a344ed22f24.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-d02c782fc792e2af.js → page-a011dd5b0bff29c5.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-d92e32d51ab5157e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/share/{page-dc6a79fb003f746c.js → page-5f21aed6932ef375.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-0fffb84cafdc9da6.js → page-1e1a53946804debf.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-a1833cd43d832601.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-63d14a4d8fe1e1c1.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-2a511d320f762e06.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-1c3e03f88dac1e21.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/vault/{page-fca096e3e860306f.js → page-c4d5289c2ea97a89.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9ebfa78f0d9885c3.js → page-b91f8952deaeaf27.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-3dc5f21f7e39622d.js → page-71b43a2588489987.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +1 -0
  56. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
  57. package/apps/pwa/out/approvals/index.html +1 -1
  58. package/apps/pwa/out/approvals/index.txt +14 -14
  59. package/apps/pwa/out/autopilot/index.html +1 -1
  60. package/apps/pwa/out/autopilot/index.txt +14 -14
  61. package/apps/pwa/out/board/index.html +1 -1
  62. package/apps/pwa/out/board/index.txt +14 -14
  63. package/apps/pwa/out/botlab/index.html +1 -1
  64. package/apps/pwa/out/botlab/index.txt +14 -14
  65. package/apps/pwa/out/bots/index.html +1 -1
  66. package/apps/pwa/out/bots/index.txt +14 -14
  67. package/apps/pwa/out/chat/index.html +1 -1
  68. package/apps/pwa/out/chat/index.txt +14 -14
  69. package/apps/pwa/out/consult/index.html +1 -1
  70. package/apps/pwa/out/consult/index.txt +14 -14
  71. package/apps/pwa/out/control/index.html +1 -1
  72. package/apps/pwa/out/control/index.txt +14 -14
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +14 -14
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +14 -14
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +14 -14
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +14 -14
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +14 -14
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +14 -14
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +14 -14
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +14 -14
  90. package/apps/pwa/out/market/index.html +1 -1
  91. package/apps/pwa/out/market/index.txt +14 -14
  92. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  93. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  94. package/apps/pwa/out/morning/index.html +1 -1
  95. package/apps/pwa/out/morning/index.txt +14 -14
  96. package/apps/pwa/out/observatory/index.html +1 -1
  97. package/apps/pwa/out/observatory/index.txt +14 -14
  98. package/apps/pwa/out/ops/checklist/index.html +1 -1
  99. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  100. package/apps/pwa/out/ops/release/index.html +1 -1
  101. package/apps/pwa/out/ops/release/index.txt +14 -14
  102. package/apps/pwa/out/ops/seats/index.html +1 -1
  103. package/apps/pwa/out/ops/seats/index.txt +14 -14
  104. package/apps/pwa/out/outputs/index.html +1 -1
  105. package/apps/pwa/out/outputs/index.txt +14 -14
  106. package/apps/pwa/out/reflection/index.html +1 -1
  107. package/apps/pwa/out/reflection/index.txt +15 -15
  108. package/apps/pwa/out/scheduler/index.html +1 -1
  109. package/apps/pwa/out/scheduler/index.txt +14 -14
  110. package/apps/pwa/out/sessions/index.html +1 -1
  111. package/apps/pwa/out/sessions/index.txt +14 -14
  112. package/apps/pwa/out/settings/devices/index.html +1 -1
  113. package/apps/pwa/out/settings/devices/index.txt +14 -14
  114. package/apps/pwa/out/settings/index.html +1 -1
  115. package/apps/pwa/out/settings/index.txt +14 -14
  116. package/apps/pwa/out/setup/done/index.html +1 -1
  117. package/apps/pwa/out/setup/done/index.txt +14 -14
  118. package/apps/pwa/out/setup/index.html +1 -1
  119. package/apps/pwa/out/setup/index.txt +14 -14
  120. package/apps/pwa/out/share/index.html +1 -1
  121. package/apps/pwa/out/share/index.txt +14 -14
  122. package/apps/pwa/out/showroom/index.html +1 -1
  123. package/apps/pwa/out/showroom/index.txt +14 -14
  124. package/apps/pwa/out/tasks/index.html +1 -1
  125. package/apps/pwa/out/tasks/index.txt +14 -14
  126. package/apps/pwa/out/term/index.html +1 -1
  127. package/apps/pwa/out/term/index.txt +14 -14
  128. package/apps/pwa/out/today/index.html +1 -1
  129. package/apps/pwa/out/today/index.txt +14 -14
  130. package/apps/pwa/out/trace/index.html +1 -1
  131. package/apps/pwa/out/trace/index.txt +14 -14
  132. package/apps/pwa/out/vault/index.html +1 -1
  133. package/apps/pwa/out/vault/index.txt +14 -14
  134. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  135. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  136. package/apps/pwa/out/workflows/index.html +1 -1
  137. package/apps/pwa/out/workflows/index.txt +14 -14
  138. package/apps/pwa/out/workspace/index.html +1 -1
  139. package/apps/pwa/out/workspace/index.txt +14 -14
  140. package/apps/pwa/out/worktrees/index.html +1 -1
  141. package/apps/pwa/out/worktrees/index.txt +14 -14
  142. package/examples/teams/personas/backend.yaml +4 -0
  143. package/examples/teams/personas/pm.yaml +4 -0
  144. package/examples/teams/personas/qa.yaml +4 -0
  145. package/examples/teams/small-product.yaml +20 -0
  146. package/graphs/context/nightly-condense.yaml +21 -0
  147. package/graphs/context/recipes.yaml +12 -0
  148. package/graphs/demo/inside-seed.yaml +20 -0
  149. package/graphs/demo/recipes.yaml +12 -0
  150. package/graphs/lecture/lecture-note.yaml +28 -0
  151. package/graphs/lecture/recipes.yaml +17 -0
  152. package/package.json +2 -1
  153. package/plugins/card-followup/examples/sample-card.png +0 -0
  154. package/scripts/context-hooks/emit.ts +36 -14
  155. package/scripts/coord-channel-watch.sh +1 -0
  156. package/scripts/discord-seat-probe.ts +144 -0
  157. package/scripts/hq/hq-standby.sh +65 -0
  158. package/scripts/hq/standby-promote.ts +173 -0
  159. package/scripts/hq/standby-role.ts +59 -0
  160. package/scripts/hq/standby-snapshot.ts +226 -0
  161. package/scripts/hq/standby-verify.ts +219 -0
  162. package/scripts/lecture-note/node.ts +256 -0
  163. package/scripts/lib/telegram-inject-args.ts +31 -0
  164. package/scripts/measure-pod-memory-by-goal.ts +130 -0
  165. package/scripts/release-loop/auto-approve-node.ts +1 -1
  166. package/scripts/release-loop/checklist-gate-node.ts +2 -1
  167. package/scripts/release-loop/gate-node.ts +61 -17
  168. package/scripts/release-loop/ops-upgrade-node.ts +59 -6
  169. package/scripts/release-loop/publish-node.ts +4 -1
  170. package/scripts/release-loop/version-node.ts +13 -3
  171. package/scripts/telegram-inject.ts +14 -6
  172. package/scripts/test-deterministic.ts +53 -3
  173. package/scripts/test-diet/remeasure-list.ts +112 -0
  174. package/scripts/ux/demo-rehearsal.ts +17 -13
  175. package/scripts/ux/demo-seed.ts +108 -0
  176. package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
  177. package/skills/omni-crawl/SKILL.md +16 -16
  178. package/skills/omni-crawl/package-lock.json +1 -544
  179. package/skills/omni-crawl/package.json +3 -4
  180. package/skills/omni-crawl/scripts/main.ts +13 -13
  181. package/skills/omni-crawl/scripts/monitor.ts +8 -8
  182. package/skills/omni-crawl/src/apify.ts +13 -2
  183. package/skills/omni-crawl/src/capture.ts +6 -0
  184. package/skills/omni-crawl/src/registry.ts +2 -2
  185. package/skills/omni-crawl/src/stdout-json.ts +4 -0
  186. package/skills/omni-digest/SKILL.md +10 -11
  187. package/skills/omni-digest/package-lock.json +0 -545
  188. package/skills/omni-digest/package.json +2 -5
  189. package/skills/omni-digest/scripts/main.ts +3 -3
  190. package/skills/omni-digest/src/delegate.ts +5 -5
  191. package/skills/omni-digest/src/fetch.ts +2 -2
  192. package/skills/omni-digest/src/media.ts +25 -11
  193. package/skills/omni-digest/src/visual.ts +3 -2
  194. package/skills/omni-market/scripts/main.ts +1 -1
  195. package/skills/omni-market/scripts/stdout-json.ts +4 -0
  196. package/src/agent/identity-env.ts +11 -0
  197. package/src/agent-mission/driver.ts +150 -23
  198. package/src/agent-mission/duo.ts +94 -0
  199. package/src/agent-mission/pty-decision.ts +2 -2
  200. package/src/autopilot/pty-control-loop.ts +4 -1
  201. package/src/autopilot/track-agent-shadow.ts +2 -1
  202. package/src/autopilot/track-agent.ts +4 -1
  203. package/src/boot/daemon-prompt-request.ts +3 -0
  204. package/src/boot/daemon-prompt-turn.ts +6 -2
  205. package/src/boot/daemon-runtime.ts +48 -16
  206. package/src/chat/index.ts +1 -0
  207. package/src/cli/decisions-cli.ts +51 -19
  208. package/src/cli/outputs-cli.ts +92 -0
  209. package/src/cli/persona-cli.ts +45 -13
  210. package/src/cli/pod-cli.ts +27 -3
  211. package/src/cli/pod-memory-advice.ts +35 -0
  212. package/src/cli/pr-cli.ts +15 -1
  213. package/src/cli/release-cli.ts +135 -15
  214. package/src/context-bus/condense.ts +194 -0
  215. package/src/context-bus/context-day.ts +82 -0
  216. package/src/context-bus/context-now-surfaces.ts +32 -2
  217. package/src/context-bus/context-now.ts +2 -2
  218. package/src/context-bus/coord-events.ts +2 -2
  219. package/src/context-bus/long-term-memory.ts +118 -22
  220. package/src/context-bus/nightly-condense.ts +153 -0
  221. package/src/context-bus/session-events.ts +35 -0
  222. package/src/dashboard/dashboard-status-lines.ts +2 -0
  223. package/src/dashboard/index.ts +8 -0
  224. package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
  225. package/src/decisions/decision-cards.ts +13 -7
  226. package/src/decisions/decision-ledger.ts +43 -17
  227. package/src/domains/outbound-alert.ts +99 -31
  228. package/src/domains/outbound-log-probe.ts +8 -0
  229. package/src/execution-loop/launch-gate.ts +17 -8
  230. package/src/field-note/telegram.ts +142 -0
  231. package/src/graph-kinds/registry.ts +2 -2
  232. package/src/graph-runner/graph-approval-card.ts +2 -2
  233. package/src/graph-runner/graph-grow-llm.ts +23 -13
  234. package/src/graph-runner/graph-grow.ts +69 -13
  235. package/src/graph-runner/graph-tick.ts +9 -2
  236. package/src/graph-runner/runner.ts +92 -24
  237. package/src/harness/harness-cli-command.ts +165 -17
  238. package/src/harness/harness-queue-child.ts +37 -0
  239. package/src/harness/harness-queue.ts +306 -0
  240. package/src/hooks/consult-intake.ts +153 -0
  241. package/src/hooks/github-shadow.ts +72 -2
  242. package/src/hooks/receiver.ts +82 -3
  243. package/src/hq/hq.ts +318 -0
  244. package/src/hq/lease.ts +210 -0
  245. package/src/hq/ledger-write-target.ts +41 -0
  246. package/src/index.ts +232 -16
  247. package/src/intake-plane/discord-seat-work.ts +36 -3
  248. package/src/intake-plane/linear-wish.ts +44 -0
  249. package/src/intake-plane/persona-answer.ts +5 -0
  250. package/src/intake-plane/telegram-seat-work.ts +13 -6
  251. package/src/intake-plane/wish-card.ts +33 -0
  252. package/src/llm/difficulty.ts +1 -1
  253. package/src/loops/checker.ts +178 -0
  254. package/src/loops/loop-cli.ts +37 -3
  255. package/src/loops/observe.ts +49 -0
  256. package/src/loops/orchestrator/overlap.ts +141 -0
  257. package/src/loops/orchestrator/traffic.ts +150 -0
  258. package/src/loops/package/verify.ts +125 -0
  259. package/src/loops/verdict.ts +22 -0
  260. package/src/maturity/feature-maturity.ts +5 -4
  261. package/src/memory.ts +37 -4
  262. package/src/nexus/api/card-followup-route.ts +118 -0
  263. package/src/nexus/api/decisions-route.ts +68 -0
  264. package/src/nexus/api/http-server.ts +78 -8
  265. package/src/nexus/api/outbound-report.ts +40 -7
  266. package/src/nexus/api/personas.ts +97 -54
  267. package/src/nexus/api/skill-exec-route.ts +97 -0
  268. package/src/nexus/api/task-cards-api.ts +19 -0
  269. package/src/nexus/api/workflow-history.ts +88 -0
  270. package/src/nexus/api/workflows.ts +52 -0
  271. package/src/nexus/outbound/delivery-ledger.ts +17 -8
  272. package/src/nexus/outbound/router.ts +8 -3
  273. package/src/persona/loader.ts +18 -0
  274. package/src/persona/persona-todo.ts +14 -9
  275. package/src/persona/presets.ts +10 -2
  276. package/src/persona/team.ts +105 -0
  277. package/src/persona/types.ts +4 -0
  278. package/src/plugins/maker/market-bundle.ts +2 -1
  279. package/src/pod-lease/host-lease.ts +130 -20
  280. package/src/release-loop/checklist.ts +15 -3
  281. package/src/release-loop/feature-store.ts +24 -10
  282. package/src/release-loop/landed-but-yellow.ts +60 -0
  283. package/src/seat-dispatch/seat-ask.ts +57 -12
  284. package/src/seat-loop/seat-loop.ts +253 -18
  285. package/src/seat-loop/seat-pack.ts +127 -0
  286. package/src/self-dev/ask-launch-flow.ts +3 -0
  287. package/src/self-dev/draft-sweep.ts +44 -10
  288. package/src/self-dev/draft-triage-rules.ts +54 -2
  289. package/src/self-implement/budget-gate.ts +29 -12
  290. package/src/self-implement/gate-candidate-ranking.ts +60 -0
  291. package/src/self-implement/gate-scope.ts +9 -8
  292. package/src/self-implement/headless-elanous-driver.ts +7 -2
  293. package/src/self-implement/l8-merge-queue.ts +157 -1
  294. package/src/self-implement/orchestrator.ts +15 -0
  295. package/src/self-implement/test-seams.ts +1 -0
  296. package/src/steward/launch.ts +5 -1
  297. package/src/steward/readiness.ts +125 -0
  298. package/src/steward/rescue.ts +75 -0
  299. package/src/steward/triage.ts +48 -9
  300. package/src/task-cards/card-cli.ts +78 -0
  301. package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
  302. package/src/task-orchestrator/surfaces/pod-lease.ts +18 -6
  303. package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +46 -8
  305. package/src/telegram-commands.ts +21 -0
  306. package/src/telegram-dispatch-forward.ts +1 -1
  307. package/src/telegram-fabric-plan.ts +90 -0
  308. package/src/telegram-project-command.ts +84 -4
  309. package/src/telegram.ts +37 -5
  310. package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
  311. package/src/user-config.ts +101 -13
  312. package/src/version/packed-revision.json +1 -1
  313. package/src/workflow-runtime/executor.ts +4 -0
  314. package/src/workflow-runtime/node-catalog.ts +13 -0
  315. package/src/workflow-runtime/nodes/knowledge.ts +38 -0
  316. package/src/workflow-runtime/schema.ts +17 -1
  317. package/src/workflow-runtime/types.ts +13 -1
  318. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12b6d2f8d2d1aea.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
  340. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
  341. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
  342. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
package/src/telegram.ts CHANGED
@@ -73,7 +73,11 @@ import { effectiveInstanceRoot } from './instance/resolve.js';
73
73
  import { getElanousConfigDir } from './elanous-config-dir.js';
74
74
  import { defaultFieldEvent, FieldUploadError, parseFieldCaption, resolveFieldMime, saveFieldMedia } from './field/field-media.js';
75
75
  import { scheduleFieldReel, type FieldReelOptions } from './field/field-reel.js';
76
+ import { handleTelegramFieldNote } from './field-note/telegram.js';
77
+ import type { TranscriptionEngine, FieldAudio } from './field-note/transcribe.js';
76
78
  import { maybeHandleCardPhoto, type CardPhotoDeps } from './telegram-card-followup.js';
79
+ import { attachTelegramProjectButtons } from './telegram-project-command.js';
80
+ import { attachTelegramFabricPlan } from './telegram-fabric-plan.js';
77
81
 
78
82
  export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
79
83
 
@@ -101,6 +105,8 @@ export interface TgIncoming {
101
105
  text: string;
102
106
  messageId: number;
103
107
  threadId?: number;
108
+ /** Telegram message timestamp, not poll time. */
109
+ receivedAt?: string;
104
110
  isDm: boolean;
105
111
  isGroup: boolean;
106
112
  /** Zero or more attached media — populated by parseUpdate from
@@ -219,8 +225,12 @@ export interface TelegramBotOpts {
219
225
  fieldRootDir?: () => string;
220
226
  /** 시험에서는 실제 렌더 대신 가짜 실행기를 주입한다. */
221
227
  fieldReel?: FieldReelOptions;
228
+ /** NOTE3a engine and audio decoder are injected; no transcription provider is assumed. */
229
+ fieldNoteEngine?: TranscriptionEngine;
230
+ fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
222
231
  /** Test seam for the business-card graph and its private run root. */
223
232
  cardFollowupDeps?: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
233
+ fabricPlanDeps?: import('./telegram-fabric-plan.js').TelegramFabricPlanDeps;
224
234
  }
225
235
 
226
236
  /** Surface-unification v2 (FU-2) — Telegram trigger event passed to
@@ -278,7 +288,7 @@ export function classifyTelegramText(
278
288
 
279
289
  export class TelegramBot {
280
290
  private readonly token: string;
281
- private readonly botId: string;
291
+ readonly botId: string;
282
292
  private readonly allowedUsers: Set<number>;
283
293
  private readonly onMessage: TelegramBotOpts['onMessage'];
284
294
  private readonly fetchImpl: typeof fetch;
@@ -338,8 +348,11 @@ export class TelegramBot {
338
348
  private readonly fieldDefaultEventOpt?: string;
339
349
  private readonly fieldRootDir: () => string;
340
350
  private readonly fieldReel?: FieldReelOptions;
351
+ private readonly fieldNoteEngine?: TranscriptionEngine;
352
+ private readonly fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
341
353
  private readonly cardFollowupDeps: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
342
354
  private readonly seatWorkDeps: TelegramSeatWorkDeps;
355
+ private readonly handleFabricPlan: (ctx: TgIncoming) => Promise<boolean>;
343
356
  private seatAskTimer: ReturnType<typeof setInterval> | null = null;
344
357
  /** 앨범 id → `#현장` 행사 · 답장 디바운스. 태그된 앨범만 들어온다. */
345
358
  private readonly fieldGroups = new Map<string, {
@@ -375,7 +388,10 @@ export class TelegramBot {
375
388
  this.fieldDefaultEventOpt = opts.fieldDefaultEvent;
376
389
  this.fieldRootDir = opts.fieldRootDir ?? getElanousConfigDir;
377
390
  this.fieldReel = opts.fieldReel;
391
+ this.fieldNoteEngine = opts.fieldNoteEngine;
392
+ this.fieldNoteDecodeAudio = opts.fieldNoteDecodeAudio;
378
393
  this.cardFollowupDeps = opts.cardFollowupDeps ?? {};
394
+ this.handleFabricPlan = attachTelegramFabricPlan(this, opts.fabricPlanDeps);
379
395
  this.seatWorkDeps = { ...opts.seatWorkDeps, askDeps: { ...opts.seatWorkDeps?.askDeps,
380
396
  channel: 'telegram', botId: this.botId,
381
397
  send: opts.seatWorkDeps?.askDeps?.send ?? (async (origin, text) => {
@@ -1214,6 +1230,7 @@ export class TelegramBot {
1214
1230
  userId: cq.from.id,
1215
1231
  userName: cq.from.username ?? cq.from.first_name,
1216
1232
  chatId: cq.message?.chat.id,
1233
+ threadId: cq.message?.message_thread_id,
1217
1234
  messageId: cq.message?.message_id,
1218
1235
  data: cq.data ?? '',
1219
1236
  };
@@ -1526,8 +1543,16 @@ export class TelegramBot {
1526
1543
  await this.refuseMessage(ctx);
1527
1544
  return;
1528
1545
  }
1546
+ if (await this.handleFabricPlan(ctx)) return;
1529
1547
  // 현장 업로드 — `#현장` 캡션(또는 태그된 앨범의 나머지)의 사진·영상은 LLM 대신 현장 폴더로.
1530
1548
  if (await this.tryHandleFieldUpload(ctx)) return;
1549
+ if (await handleTelegramFieldNote(ctx, {
1550
+ rootDir: this.fieldRootDir(),
1551
+ download: (fileId) => this.downloadFile(fileId),
1552
+ reply: async (incoming, text) => { await this.sendMessage(incoming.chatId, text, { replyTo: incoming.messageId, threadId: incoming.threadId }); },
1553
+ engine: this.fieldNoteEngine, decodeAudio: this.fieldNoteDecodeAudio,
1554
+ now: () => new Date(this.nowImpl()),
1555
+ })) return;
1531
1556
  if (await maybeHandleCardPhoto(ctx, {
1532
1557
  isOwner: (userId) => this.isOwnerAllowed(userId),
1533
1558
  isFieldAlbum: (id) => this.fieldGroups.has(id),
@@ -1827,6 +1852,7 @@ export class TelegramBot {
1827
1852
  const reply = await slashParse.cmd.handler(slashParse.args, ctx, {
1828
1853
  userConfig: this.slashContext.userConfig,
1829
1854
  allCommands: this.slashCommands,
1855
+ sendButtons: async (text, buttons) => { await this.sendInlineKeyboard(ctx.chatId, text, buttons, { replyTo: ctx.messageId, threadId: ctx.threadId }); },
1830
1856
  downloadAttachments: () => this.downloadCtxAttachments(ctx),
1831
1857
  daemonBridge: this.slashContext.daemonBridge,
1832
1858
  hitlConfirmChannel: this.hitlConfirmChannelForChat(ctx.chatId, ctx.threadId),
@@ -2011,6 +2037,7 @@ export class TelegramBot {
2011
2037
  return await slashParse.cmd.handler(slashParse.args, ctx, {
2012
2038
  userConfig: this.slashContext!.userConfig,
2013
2039
  allCommands: this.slashCommands,
2040
+ sendButtons: async (text, buttons) => { await this.sendInlineKeyboard(ctx.chatId, text, buttons, { replyTo: ctx.messageId, threadId: ctx.threadId }); },
2014
2041
  streamer,
2015
2042
  downloadAttachments: () => this.downloadCtxAttachments(ctx),
2016
2043
  daemonBridge: this.slashContext!.daemonBridge,
@@ -2030,6 +2057,7 @@ export class TelegramBot {
2030
2057
  return await runAcpViaSlash(activeBackend, [ctx.text], ctx, {
2031
2058
  userConfig: this.slashContext!.userConfig,
2032
2059
  allCommands: this.slashCommands,
2060
+ sendButtons: async (text, buttons) => { await this.sendInlineKeyboard(ctx.chatId, text, buttons, { replyTo: ctx.messageId, threadId: ctx.threadId }); },
2033
2061
  streamer,
2034
2062
  downloadAttachments: () => this.downloadCtxAttachments(ctx),
2035
2063
  daemonBridge: this.slashContext!.daemonBridge,
@@ -2270,6 +2298,7 @@ interface RawUpdate {
2270
2298
  update_id: number;
2271
2299
  message?: {
2272
2300
  message_id: number;
2301
+ date?: number;
2273
2302
  from?: { id: number; first_name?: string; username?: string };
2274
2303
  chat: { id: number; type: 'private' | 'group' | 'supergroup' | 'channel' };
2275
2304
  text?: string;
@@ -2286,7 +2315,7 @@ interface RawUpdate {
2286
2315
  callback_query?: {
2287
2316
  id: string;
2288
2317
  from: { id: number; first_name?: string; username?: string };
2289
- message?: { message_id: number; chat: { id: number } };
2318
+ message?: { message_id: number; message_thread_id?: number; chat: { id: number } };
2290
2319
  data?: string;
2291
2320
  chat_instance?: string;
2292
2321
  };
@@ -2305,6 +2334,7 @@ export interface TgCallbackQuery {
2305
2334
  userId: number;
2306
2335
  userName?: string;
2307
2336
  chatId?: number;
2337
+ threadId?: number;
2308
2338
  messageId?: number;
2309
2339
  data: string;
2310
2340
  }
@@ -2397,6 +2427,7 @@ export function parseUpdate(u: RawUpdate): TgIncoming | null {
2397
2427
  text: (typeof m.text === 'string' ? m.text : (m.caption ?? '')),
2398
2428
  messageId: m.message_id,
2399
2429
  threadId: m.message_thread_id,
2430
+ ...(typeof m.date === 'number' ? { receivedAt: new Date(m.date * 1000).toISOString() } : {}),
2400
2431
  isDm,
2401
2432
  isGroup: !isDm,
2402
2433
  attachments,
@@ -2912,9 +2943,10 @@ export function botFromConfig(opts: BotFromConfigOpts): TelegramBot {
2912
2943
  botRef.ensureSurfaceHitl();
2913
2944
  // DEC-TG(대표 10-02) — decision cards on the MAIN bot only (other channel pollers carry other tokens). The ticker
2914
2945
  // itself sends only while this bot polls, so a send-only nexus bot never duplicates the standalone poller's cards.
2915
- if (opts.userConfig.raw?.decisions === undefined || (opts.userConfig.raw.decisions as { cards?: unknown }).cards !== false) {
2916
- const mainToken = resolveChannelBotToken('telegram', opts.userConfig)?.token;
2917
- if (mainToken && botToken === mainToken) {
2946
+ const mainToken = resolveChannelBotToken('telegram', opts.userConfig)?.token;
2947
+ if (mainToken && botToken === mainToken) {
2948
+ attachTelegramProjectButtons(botRef, opts.userConfig);
2949
+ if (opts.userConfig.raw?.decisions === undefined || (opts.userConfig.raw.decisions as { cards?: unknown }).cards !== false) {
2918
2950
  const bot = botRef;
2919
2951
  void import('./decisions/telegram-decision-cards.js')
2920
2952
  .then(({ attachTelegramDecisionCards }) => { attachTelegramDecisionCards(bot, opts.userConfig); })
@@ -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
  });
@@ -3240,7 +3240,7 @@ export interface AutoReviewConfig {
3240
3240
  };
3241
3241
  }
3242
3242
 
3243
- export type ModelRole = 'implement' | 'review' | 'research' | 'planning' | 'audit' | 'classify';
3243
+ export type ModelRole = 'implement' | 'review' | 'research' | 'planning' | 'audit' | 'classify' | 'graph-grow';
3244
3244
 
3245
3245
  export interface RoleModelConfig {
3246
3246
  implement?: string;
@@ -3249,6 +3249,7 @@ export interface RoleModelConfig {
3249
3249
  planning?: string;
3250
3250
  audit?: string;
3251
3251
  classify?: string;
3252
+ 'graph-grow'?: string;
3252
3253
  }
3253
3254
 
3254
3255
  /** Sparse provider-agnostic model tier preferences by execution role. */
@@ -3315,6 +3316,7 @@ export const ROLE_MODEL_DEFAULTS: Record<ModelRole, { environment: string; tier:
3315
3316
  planning: { environment: 'ELANOUS_SKILL_PLAN_MODEL', tier: 'best' },
3316
3317
  audit: { environment: 'ELANOUS_MEMORY_JUDGE_MODEL', tier: 'better' },
3317
3318
  classify: { environment: 'ELANOUS_ACTION_CLASSIFIER_MODEL', tier: 'budget' },
3319
+ 'graph-grow': { environment: 'ELANOUS_GRAPH_GROW_MODEL', tier: 'better' },
3318
3320
  };
3319
3321
 
3320
3322
  /** Resolve `auto` through the same runtime provider selector that dispatches LLM calls.
@@ -3473,7 +3475,34 @@ export function resolveRoleModel(role: ModelRole, config: UserConfig = getUserCo
3473
3475
  }
3474
3476
 
3475
3477
 
3476
- export type StewardLoopMode = 'off' | 'shadow' | 'live';
3478
+ export type OrchestratorSeat = 'OP' | 'TC' | 'MK' | 'UX';
3479
+ export interface OrchestratorLoopConfig {
3480
+ seatTrees: Partial<Record<OrchestratorSeat, string[]>>;
3481
+ seatCaps: Record<OrchestratorSeat, number>;
3482
+ trafficMode: 'shadow' | 'live';
3483
+ }
3484
+
3485
+ export const ORCHESTRATOR_DEFAULTS: OrchestratorLoopConfig = {
3486
+ seatTrees: {}, seatCaps: { TC: 8, UX: 6, MK: 6, OP: 4 }, trafficMode: 'shadow',
3487
+ };
3488
+
3489
+ export function parseOrchestratorLoopConfig(raw: unknown): OrchestratorLoopConfig {
3490
+ const value = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
3491
+ const rawTrees = value.seatTrees && typeof value.seatTrees === 'object' && !Array.isArray(value.seatTrees)
3492
+ ? value.seatTrees as Record<string, unknown> : {};
3493
+ const rawCaps = value.seatCaps && typeof value.seatCaps === 'object' && !Array.isArray(value.seatCaps)
3494
+ ? value.seatCaps as Record<string, unknown> : {};
3495
+ const seatTrees: OrchestratorLoopConfig['seatTrees'] = {};
3496
+ const seatCaps = { ...ORCHESTRATOR_DEFAULTS.seatCaps };
3497
+ for (const seat of ['OP', 'TC', 'MK', 'UX'] as const) {
3498
+ if (Array.isArray(rawTrees[seat])) seatTrees[seat] = rawTrees[seat].filter((path): path is string => typeof path === 'string' && isAbsolute(path));
3499
+ const cap = rawCaps[seat];
3500
+ if (typeof cap === 'number' && Number.isSafeInteger(cap) && cap >= 0) seatCaps[seat] = cap;
3501
+ }
3502
+ return { seatTrees, seatCaps, trafficMode: value.trafficMode === 'live' ? 'live' : 'shadow' };
3503
+ }
3504
+
3505
+ export type StewardLoopMode = 'off' | 'shadow' | 'live' | 'rescue';
3477
3506
 
3478
3507
  export type SeatLoopMode = 'off' | 'shadow' | 'on';
3479
3508
 
@@ -3514,25 +3543,51 @@ export function parseEventsConfig(input: unknown): EventsConfig {
3514
3543
  return { mode: events.mode === 'on' ? 'on' : 'shadow', routes, linearAssignees };
3515
3544
  }
3516
3545
 
3546
+ export type HqFenceRole = 'telegram-poller' | 'cron' | 'seat-loop' | 'release-run' | 'conatus' | 'git-push' | 'ledger-cli';
3547
+ export interface HqConfig {
3548
+ /** ssh host that keeps the lease record — homes push to it; it never sshes back (default cloud-vm). */
3549
+ arbiter?: string;
3550
+ /** This host's identity in the lease (default os.hostname()). */
3551
+ hostName?: string;
3552
+ /** Standby host (default node-b) — the quorum's third member; promoted only by the arbiter. */
3553
+ standby?: string;
3554
+ /** Lease TTL in seconds (default 1500 = 25 min). */
3555
+ ttlSeconds?: number;
3556
+ /** Roles that may still run when this host is fenced only for lack of quorum (never when a newer generation exists). Absent = none. */
3557
+ failOpenRoles?: HqFenceRole[];
3558
+ /** Per-host nexus health URL over the tailnet (default https://<host>.<tailnetDomain>:31415/v1/health). */
3559
+ healthUrls?: Record<string, string>;
3560
+ /** MagicDNS suffix (default: read from `tailscale status --json`). */
3561
+ tailnetDomain?: string;
3562
+ /** `tailscale` binary (default tailscale). */
3563
+ tailscaleBin?: string;
3564
+ /** Highest lease generation this host ever saw (default ~/.elanous-hq/seen-generation · 600). */
3565
+ seenGenerationFile?: string;
3566
+ /** Per-probe timeout in seconds (default 5). */
3567
+ probeTimeoutSeconds?: number;
3568
+ }
3569
+
3517
3570
  export interface UserConfig {
3518
3571
  events?: EventsConfig;
3519
3572
  /** Explicit HTTPS destination for doctor diagnostics; absent means no upload. */
3520
3573
  diagnostics?: { uploadUrl?: string };
3574
+ /** 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). */
3575
+ hq?: HqConfig;
3521
3576
  /** Root CLI help audience; absent or invalid means owner. */
3522
3577
  cli?: { helpRole?: 'owner' | 'contributor' | 'general' };
3523
3578
  /** Execution-phase HITL answer deadline; absent means 30 minutes. */
3524
3579
  hitl?: { executionDeadlineMinutes?: number };
3525
3580
  coo?: { linearProject?: string };
3526
- decisions?: { linearProjection: { enabled: boolean } };
3581
+ decisions?: { linearProjection: { enabled: boolean }; requireCrossCheck: boolean };
3527
3582
  /** Per-seat goal and Pod limits; missing or invalid limits use seat-budget defaults. */
3528
3583
  org?: { budget?: Record<string, { dailyGoals?: number; concurrentPods?: number }> };
3529
3584
  /** Steward and seat loops are parsed independently. An absent seat remains off. */
3530
- 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 };
3585
+ loops?: { 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 };
3531
3586
  /** 🔐 Pod 의 Grok API 키 과금은 명시 opt-in 만. 생략·잘못된 값은 false.
3532
3587
  * budgetGate 와 같은 `harness` 객체다 — 중복 선언이면 뒤 타입이 앞을 지운다. */
3533
- harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number };
3588
+ harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; difficultyPlacement?: boolean; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number; queue?: { seatCap?: Partial<Record<'OP' | 'TC' | 'MK' | 'UX', number>> } };
3534
3589
  /** ☸️ Pod 실행 칸 — `pool` = 기존 기본 풀(`컨텍스트[@ssh호스트][:상한][#k3d-레지스트리:포트]` 쉼표 · 앞이 우선). harness 실행은 인자·ELANOUS_POD_POOL·harness.podPool 다음으로 읽는다. */
3535
- pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string; /** 읽기 전용 Pod lease 권장에 쓰는 계정별 동시 수. */ lease?: { perAccount?: number } };
3590
+ pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string; /** 읽기 전용 Pod lease 권장에 쓰는 계정별 동시 수. */ lease?: { perAccount?: number }; /** 실측 권고를 Pod 발사 기본값으로 쓸지 (기본 off). */ memory?: { adviseDefaults?: boolean } };
3536
3591
  skillRouter: SkillRouterConfig;
3537
3592
  llm: LLMConfig;
3538
3593
  skills: SkillsConfig;
@@ -3700,7 +3755,7 @@ export function parseRoleLlmEntry(raw: unknown): { ok: true; spec: RoleLlmSpec }
3700
3755
  return { ok: true, spec };
3701
3756
  }
3702
3757
 
3703
- export const MODEL_ROLES: readonly ModelRole[] = ['implement', 'review', 'research', 'planning', 'audit', 'classify'];
3758
+ export const MODEL_ROLES: readonly ModelRole[] = ['implement', 'review', 'research', 'planning', 'audit', 'classify', 'graph-grow'];
3704
3759
 
3705
3760
  export function isModelRole(v: unknown): v is ModelRole {
3706
3761
  return typeof v === 'string' && (MODEL_ROLES as readonly string[]).includes(v);
@@ -3858,8 +3913,8 @@ function defaultConfig(): UserConfig {
3858
3913
  return {
3859
3914
  cli: { helpRole: 'owner' },
3860
3915
  coo: { linearProject: '외부 행정·큰 일 (COO)' },
3861
- decisions: { linearProjection: { enabled: false } },
3862
- loops: { steward: { mode: 'shadow' }, seat: { mode: 'off', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' }, persona: { enabled: false } },
3916
+ decisions: { linearProjection: { enabled: false }, requireCrossCheck: false },
3917
+ loops: { orchestrator: parseOrchestratorLoopConfig(undefined), steward: { mode: 'shadow' }, seat: { mode: 'off', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' }, persona: { enabled: false } },
3863
3918
  skillRouter: { ...SR_DEFAULTS },
3864
3919
  llm: { ...LLM_DEFAULTS },
3865
3920
  skills: skillsDefaults(),
@@ -3928,7 +3983,7 @@ function defaultConfig(): UserConfig {
3928
3983
  nativeStructure: { ...TOOLS_DEFAULTS.nativeStructure },
3929
3984
  selfImplement: { ...TOOLS_DEFAULTS.selfImplement },
3930
3985
  },
3931
- harness: { budgetGate: { ...DEFAULT_BUDGET_GATE } },
3986
+ harness: { budgetGate: { ...DEFAULT_BUDGET_GATE }, difficultyPlacement: false },
3932
3987
  grounding: { sources: [] },
3933
3988
  raw: {},
3934
3989
  };
@@ -4386,7 +4441,7 @@ function parseStewardLoopsConfig(input: unknown): UserConfig['loops'] {
4386
4441
  }
4387
4442
  }
4388
4443
  return { steward: {
4389
- mode: s.mode === 'off' || s.mode === 'shadow' || s.mode === 'live' ? s.mode :
4444
+ mode: s.mode === 'off' || s.mode === 'shadow' || s.mode === 'live' || s.mode === 'rescue' ? s.mode :
4390
4445
  s.launch === 'off' || s.launch === 'shadow' || s.launch === 'live' ? s.launch : 'shadow',
4391
4446
  // A valid legacy launch setting remains effective when mode is absent; explicit mode always wins.
4392
4447
  ...(s.launch === 'off' || s.launch === 'shadow' || s.launch === 'live' ? { launch: s.launch } : {}),
@@ -4405,6 +4460,25 @@ export function parseCliHelpRole(raw: unknown): NonNullable<UserConfig['cli']>['
4405
4460
  return cli.helpRole === 'general' || cli.helpRole === 'contributor' ? cli.helpRole : 'owner';
4406
4461
  }
4407
4462
 
4463
+ const HQ_FENCE_ROLES: readonly HqFenceRole[] = ['telegram-poller', 'cron', 'seat-loop', 'release-run', 'conatus', 'git-push'];
4464
+ /** `hq` block (HQ-HB · HQ-FENCE): keep only well-typed fields — absent/invalid fields fall back to the hq module defaults. */
4465
+ export function parseHqConfig(raw: unknown): HqConfig | undefined {
4466
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;
4467
+ const r = raw as Record<string, unknown>;
4468
+ const str = (v: unknown) => typeof v === 'string' && v.trim() ? v.trim() : undefined;
4469
+ const out: HqConfig = {};
4470
+ for (const key of ['arbiter', 'hostName', 'standby', 'tailnetDomain', 'tailscaleBin', 'seenGenerationFile'] as const) {
4471
+ const v = str(r[key]); if (v) out[key] = v;
4472
+ }
4473
+ if (typeof r.ttlSeconds === 'number' && Number.isFinite(r.ttlSeconds) && r.ttlSeconds > 0) out.ttlSeconds = r.ttlSeconds;
4474
+ if (typeof r.probeTimeoutSeconds === 'number' && Number.isFinite(r.probeTimeoutSeconds) && r.probeTimeoutSeconds > 0) out.probeTimeoutSeconds = r.probeTimeoutSeconds;
4475
+ if (Array.isArray(r.failOpenRoles)) out.failOpenRoles = r.failOpenRoles.filter((v): v is HqFenceRole => HQ_FENCE_ROLES.includes(v as HqFenceRole));
4476
+ if (r.healthUrls && typeof r.healthUrls === 'object' && !Array.isArray(r.healthUrls)) {
4477
+ out.healthUrls = Object.fromEntries(Object.entries(r.healthUrls as Record<string, unknown>).filter((e): e is [string, string] => typeof e[1] === 'string'));
4478
+ }
4479
+ return Object.keys(out).length ? out : undefined;
4480
+ }
4481
+
4408
4482
  export function buildUserConfig(path: string = defaultPath()): UserConfig {
4409
4483
  if (!existsSync(path)) return defaultConfigWithRuntimeProvider();
4410
4484
  let raw: unknown;
@@ -4530,6 +4604,8 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4530
4604
  ...(typeof legacyPod.pool === 'string' && legacyPod.pool.trim() ? { pool: legacyPod.pool.trim() } : {}),
4531
4605
  ...(typeof legacyPod.hostMirror === 'string' && legacyPod.hostMirror.trim() ? { hostMirror: legacyPod.hostMirror.trim() } : {}),
4532
4606
  ...(typeof legacyPod.groundingUrl === 'string' ? { groundingUrl: legacyPod.groundingUrl } : {}),
4607
+ memory: { adviseDefaults: Boolean(legacyPod.memory && typeof legacyPod.memory === 'object' && !Array.isArray(legacyPod.memory)
4608
+ && (legacyPod.memory as Record<string, unknown>).adviseDefaults === true) },
4533
4609
  lease: { perAccount: (() => {
4534
4610
  const lease = legacyPod.lease && typeof legacyPod.lease === 'object' && !Array.isArray(legacyPod.lease)
4535
4611
  ? (legacyPod.lease as Record<string, unknown>) : {};
@@ -4539,6 +4615,13 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4539
4615
  },
4540
4616
  harness: {
4541
4617
  pod: { grokApiKeyOptIn: harnessPod.grokApiKeyOptIn === true },
4618
+ difficultyPlacement: harness.difficultyPlacement === true,
4619
+ queue: { seatCap: Object.fromEntries(Object.entries(
4620
+ harness.queue && typeof harness.queue === 'object' && !Array.isArray(harness.queue)
4621
+ && (harness.queue as Record<string, unknown>).seatCap && typeof (harness.queue as Record<string, unknown>).seatCap === 'object'
4622
+ && !Array.isArray((harness.queue as Record<string, unknown>).seatCap)
4623
+ ? (harness.queue as { seatCap: Record<string, unknown> }).seatCap : {},
4624
+ ).filter(([seat, cap]) => /^(OP|TC|MK|UX)$/.test(seat) && typeof cap === 'number' && Number.isSafeInteger(cap) && cap > 0)) },
4542
4625
  ...(harness.substrate === 'local' || harness.substrate === 'pod' ? { substrate: harness.substrate } : {}),
4543
4626
  ...(typeof harness.podPool === 'string' && harness.podPool.trim() ? { podPool: harness.podPool.trim() } : {}),
4544
4627
  ...(typeof harness.worktreeAddTimeoutSec === 'number' && Number.isSafeInteger(harness.worktreeAddTimeoutSec)
@@ -5183,11 +5266,13 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
5183
5266
  && typeof (rawObj.hitl as Record<string, unknown>).executionDeadlineMinutes === 'number'
5184
5267
  ? { hitl: { executionDeadlineMinutes: (rawObj.hitl as { executionDeadlineMinutes: number }).executionDeadlineMinutes } }
5185
5268
  : {}),
5269
+ ...(parseHqConfig(rawObj.hq) ? { hq: parseHqConfig(rawObj.hq)! } : {}),
5186
5270
  cli: { helpRole: parseCliHelpRole(rawObj.cli) },
5187
5271
  coo: { linearProject: typeof (rawObj.coo as { linearProject?: unknown } | undefined)?.linearProject === 'string' && (rawObj.coo as { linearProject: string }).linearProject.trim()
5188
5272
  ? (rawObj.coo as { linearProject: string }).linearProject.trim() : '외부 행정·큰 일 (COO)' },
5189
- decisions: { linearProjection: { enabled: ((rawObj.decisions as { linearProjection?: { enabled?: unknown } } | undefined)?.linearProjection?.enabled === true) } },
5190
- loops: { ...(stewardLoops ?? {}), seat: parseSeatLoopsConfig(rawObj.loops),
5273
+ decisions: { linearProjection: { enabled: ((rawObj.decisions as { linearProjection?: { enabled?: unknown } } | undefined)?.linearProjection?.enabled === true) },
5274
+ requireCrossCheck: (rawObj.decisions as { requireCrossCheck?: unknown } | undefined)?.requireCrossCheck === true },
5275
+ loops: { ...(stewardLoops ?? {}), orchestrator: parseOrchestratorLoopConfig((rawObj.loops as { orchestrator?: unknown } | undefined)?.orchestrator), seat: parseSeatLoopsConfig(rawObj.loops),
5191
5276
  persona: { enabled: (rawObj.loops as { persona?: { enabled?: unknown } } | undefined)?.persona?.enabled === true } },
5192
5277
  events: parseEventsConfig(rawObj.events),
5193
5278
  // M1-1: sparse — undefined when the user hasn't set anything, so
@@ -5783,14 +5868,17 @@ export function saveUserConfig(
5783
5868
  ...rawHarness,
5784
5869
  pod: cfg.harness?.pod ?? rawHarness.pod,
5785
5870
  budgetGate: cfg.harness?.budgetGate ?? rawHarness.budgetGate,
5871
+ difficultyPlacement: cfg.harness?.difficultyPlacement ?? rawHarness.difficultyPlacement,
5786
5872
  defaultRepo: cfg.harness?.defaultRepo ?? rawHarness.defaultRepo,
5787
5873
  substrate: cfg.harness?.substrate,
5788
5874
  podPool: cfg.harness?.podPool,
5875
+ queue: cfg.harness?.queue ?? rawHarness.queue,
5789
5876
  }),
5790
5877
  ...rawRest,
5791
5878
  ...(cfg.loops ? { loops: {
5792
5879
  ...rawLoops,
5793
5880
  ...(cfg.loops.steward ? { steward: { ...rawSteward, mode: cfg.loops.steward.mode ?? cfg.loops.steward.launch ?? 'shadow' } } : {}),
5881
+ ...(cfg.loops.orchestrator ? { orchestrator: cfg.loops.orchestrator } : {}),
5794
5882
  ...(cfg.loops.seat ? { seat: {
5795
5883
  mode: cfg.loops.seat.mode,
5796
5884
  questions: cfg.loops.seat.questions ?? 'shadow',
@@ -1 +1 @@
1
- {"commit":"e61a9846510a7f6ac0a0f223d8e4c8b384685df8"}
1
+ {"commit":"5116219907580581e7bb40b93018d155deafa5c2"}
@@ -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,
@@ -207,6 +207,19 @@ export const NODE_CATALOG: readonly NodeSpec[] = [
207
207
  condition: $item != 'skip'`,
208
208
  related: ['iteration'],
209
209
  },
210
+ {
211
+ kind: 'knowledge',
212
+ category: 'transform',
213
+ summary: '볼트 검색 · 찾은 조각을 다음 노드에',
214
+ yamlKey: 'knowledge',
215
+ required: ['query'],
216
+ optional: ['limit'],
217
+ example: `- id: notes
218
+ knowledge:
219
+ query: $ARGUMENTS
220
+ limit: 3`,
221
+ related: ['prompt'],
222
+ },
210
223
  {
211
224
  kind: 'template',
212
225
  category: 'transform',
@@ -0,0 +1,38 @@
1
+ import { dispatchElanousObsidianSearch } from '../../tool-runtime/elanous-obsidian-search-runtime.js';
2
+ import type { KnowledgeNode, NodeExecContext, NodeOutput, WorkflowDeps } from '../types.js';
3
+ import { interpolate } from '../variables.js';
4
+
5
+ export async function executeKnowledgeNode(
6
+ node: KnowledgeNode,
7
+ ctx: NodeExecContext,
8
+ deps: WorkflowDeps,
9
+ ): Promise<NodeOutput> {
10
+ const startedAt = Date.now();
11
+ try {
12
+ const query = interpolate(node.knowledge.query, {
13
+ arguments: ctx.arguments,
14
+ artifactsDir: ctx.artifactsDir,
15
+ outputs: ctx.outputs,
16
+ }).text;
17
+ const limit = node.knowledge.limit ?? 5;
18
+ const result = await (deps.searchObsidian ?? dispatchElanousObsidianSearch)({ query, limit });
19
+ const matches = result.matches;
20
+ return {
21
+ ok: true,
22
+ output: result.error ? '' : matches.slice(0, limit)
23
+ .map(match => `— ${match.path}:${match.lineNumber}\n${match.snippet.slice(0, 400)}`)
24
+ .join('\n\n'),
25
+ matches,
26
+ ...(result.error ? { error: result.error } : matches.length === 0 ? { error: 'no matches' } : {}),
27
+ durationMs: Date.now() - startedAt,
28
+ };
29
+ } catch (err) {
30
+ return {
31
+ ok: true,
32
+ output: '',
33
+ matches: [],
34
+ error: err instanceof Error ? err.message : String(err),
35
+ durationMs: Date.now() - startedAt,
36
+ };
37
+ }
38
+ }
@@ -16,6 +16,7 @@ import type {
16
16
  DiscordTriggerNode,
17
17
  ExtractNode,
18
18
  FilterNode,
19
+ KnowledgeNode,
19
20
  HttpRequestNode,
20
21
  IfNode,
21
22
  IterationNode,
@@ -47,7 +48,7 @@ let resolvingSubworkflowReferences = false;
47
48
  /** ⛔ 노드 변종의 SSOT — 도움말·문서가 이 배열에서 «파생»한다.
48
49
  * 손으로 목록을 옮겨 적으면 늙는다(2026-09-22 실측: `elanous wf --help` 가 13종만 말했고
49
50
  * 이 배열은 21종을 받고 있었다 — 여덟이 «안내 없이» 살아 있었다). */
50
- export const WORKFLOW_NODE_VARIANT_KEYS = WORKFLOW_CORE_KINDS;
51
+ export const WORKFLOW_NODE_VARIANT_KEYS = [...WORKFLOW_CORE_KINDS] as const;
51
52
 
52
53
  const KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
53
54
  const TRIGGER_RULES: readonly TriggerRule[] = [
@@ -825,6 +826,18 @@ export function validateWorkflow(raw: unknown): ValidationResult {
825
826
  push(`${path}.filter.condition`, "'filter.condition' is required and must be a non-empty string");
826
827
  }
827
828
  }
829
+ } else if (variant === 'knowledge') {
830
+ if (!isObject(variantValue)) {
831
+ push(`${path}.knowledge`, "'knowledge' must be an object with a 'query' field");
832
+ } else {
833
+ if (typeof variantValue['query'] !== 'string') {
834
+ push(`${path}.knowledge.query`, "'knowledge.query' is required and must be a string expression");
835
+ }
836
+ const limit = variantValue['limit'];
837
+ if (limit !== undefined && (!Number.isInteger(limit) || (limit as number) < 1 || (limit as number) > 200)) {
838
+ push(`${path}.knowledge.limit`, "'knowledge.limit' must be an integer from 1 to 200");
839
+ }
840
+ }
828
841
  } else if (variant === 'iteration') {
829
842
  // Node-catalog N1.3 (2026-05-11) — sequential loop.
830
843
  if (!isObject(variantValue)) {
@@ -1093,6 +1106,9 @@ export const isFilterNode = (n: DagNode): n is FilterNode =>
1093
1106
  typeof (n as FilterNode).filter === 'object' && (n as FilterNode).filter !== null
1094
1107
  && typeof ((n as FilterNode).filter as { items?: unknown }).items === 'string'
1095
1108
  && typeof ((n as FilterNode).filter as { condition?: unknown }).condition === 'string';
1109
+ export const isKnowledgeNode = (n: DagNode): n is KnowledgeNode =>
1110
+ typeof (n as KnowledgeNode).knowledge === 'object' && (n as KnowledgeNode).knowledge !== null
1111
+ && typeof ((n as KnowledgeNode).knowledge as { query?: unknown }).query === 'string';
1096
1112
  export const isTemplateNode = (n: DagNode): n is TemplateNode =>
1097
1113
  typeof (n as TemplateNode).template === 'object' && (n as TemplateNode).template !== null
1098
1114
  && typeof ((n as TemplateNode).template as { template?: unknown }).template === 'string';
@@ -6,6 +6,7 @@
6
6
  // `depends_on` + `when` + `trigger_rule` topology.
7
7
 
8
8
  import type { ToolPolicy } from '../tool-runtime/tool-policy.js';
9
+ import type { ElanousObsidianSearchArgs, ElanousObsidianSearchResult } from '../tool-runtime/elanous-obsidian-search-runtime.js';
9
10
  import type { CapabilityRequirements } from '../registry/resolver.js';
10
11
 
11
12
  export type { CapabilityRequirements } from '../registry/resolver.js';
@@ -21,8 +22,10 @@ export interface NodeOutput {
21
22
  * (typically an error message string) so downstream `when` clauses
22
23
  * can branch on `$<id>.error`. */
23
24
  ok: boolean;
24
- /** Error message when ok=false. */
25
+ /** Error message (knowledge search errors can also occur with ok=true). */
25
26
  error?: string;
27
+ /** Raw Obsidian matches for a knowledge node; output remains prompt-ready text. */
28
+ matches?: ElanousObsidianSearchResult['matches'];
26
29
  /** Wall-clock duration in ms. */
27
30
  durationMs: number;
28
31
  /** Run id of the workflow called by a subworkflow node, including failures. */
@@ -185,6 +188,12 @@ export interface FilterNode extends DagNodeBase {
185
188
  condition: string;
186
189
  };
187
190
  }
191
+ export interface KnowledgeNode extends DagNodeBase {
192
+ knowledge: {
193
+ query: string;
194
+ limit?: number;
195
+ };
196
+ }
188
197
  /** Node-catalog N4.1 (2026-05-11) — Schedule trigger. v1 = schema +
189
198
  * executor pass-through. Daemon-side cron / interval registration is
190
199
  * deferred to a follow-up that touches `nexus/daemon/scheduler.ts` +
@@ -513,6 +522,7 @@ export type DagNode =
513
522
  | (ExtractNode & { kind?: undefined })
514
523
  | (SetNode & { kind?: undefined })
515
524
  | (FilterNode & { kind?: undefined })
525
+ | (KnowledgeNode & { kind?: undefined })
516
526
  | (TemplateNode & { kind?: undefined })
517
527
  | (HttpRequestNode & { kind?: undefined })
518
528
  | (ShowroomNode & { kind?: undefined })
@@ -699,6 +709,8 @@ export interface WorkflowDeps {
699
709
  body: string,
700
710
  opts: { timeoutMs?: number; signal?: AbortSignal; cwd?: string; env?: NodeJS.ProcessEnv }
701
711
  ) => Promise<{ stdout: string; stderr: string; exitCode: number }>;
712
+ /** Obsidian search for knowledge nodes. Omitted = in-process vault search. */
713
+ searchObsidian?: (args: ElanousObsidianSearchArgs) => Promise<ElanousObsidianSearchResult>;
702
714
  /** Create a TOX task (`task` node). Omitted = the in-process TOX
703
715
  * `dispatchTaskCreate`. */
704
716
  createTask?: (req: WorkflowTaskRequest) => Promise<WorkflowTaskResult>;