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
@@ -0,0 +1,88 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { getGlobalWorkflowDir, getProjectWorkflowDir } from '../../workflow-runtime/discovery.js';
5
+
6
+ export interface WorkflowHistoryOpts {
7
+ scope?: 'project' | 'global';
8
+ cwd?: string;
9
+ }
10
+
11
+ export interface WorkflowHistoryVersion {
12
+ id: string;
13
+ createdAt: string;
14
+ size: number;
15
+ }
16
+
17
+ const MAX_VERSIONS = 20;
18
+ const VERSION_ID = /^(\d{13})-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
19
+
20
+ function safeName(name: string): void {
21
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name) || name.includes('..')) {
22
+ throw new Error('invalid workflow name');
23
+ }
24
+ }
25
+
26
+ function safeVersionId(id: string): void {
27
+ if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new Error('invalid workflow history version id');
28
+ }
29
+
30
+ function workflowDir(opts: WorkflowHistoryOpts): string {
31
+ return opts.scope === 'global'
32
+ ? getGlobalWorkflowDir()
33
+ : getProjectWorkflowDir(opts.cwd ?? process.cwd());
34
+ }
35
+
36
+ function historyDir(name: string, opts: WorkflowHistoryOpts): string {
37
+ safeName(name);
38
+ return join(workflowDir(opts), '.history', name);
39
+ }
40
+
41
+ /** Capture the current YAML before its caller overwrites it; a missing file has no history. */
42
+ export function snapshotBeforeSave(name: string, opts: WorkflowHistoryOpts = {}): WorkflowHistoryVersion | null {
43
+ const dir = historyDir(name, opts);
44
+ const source = join(workflowDir(opts), `${name}.yaml`);
45
+ if (!existsSync(source)) return null;
46
+ const content = readFileSync(source);
47
+ const previous = listWorkflowHistory(name, opts)[0];
48
+ if (previous && readFileSync(join(dir, `${previous.id}.yaml`)).equals(content)) return null;
49
+
50
+ mkdirSync(dir, { recursive: true });
51
+ const timestamp = Math.max(Date.now(), previous ? Number(previous.id.slice(0, 13)) + 1 : 0);
52
+ const id = `${String(timestamp).padStart(13, '0')}-${randomUUID()}`;
53
+ const version: WorkflowHistoryVersion = {
54
+ id,
55
+ createdAt: new Date(Number(id.slice(0, 13))).toISOString(),
56
+ size: content.byteLength,
57
+ };
58
+ writeFileSync(join(dir, `${id}.yaml`), content, { flag: 'wx' });
59
+ for (const old of listWorkflowHistory(name, opts).slice(MAX_VERSIONS)) {
60
+ unlinkSync(join(dir, `${old.id}.yaml`));
61
+ }
62
+ return version;
63
+ }
64
+
65
+ /** Enumerate only this workflow's snapshots, newest first. */
66
+ export function listWorkflowHistory(name: string, opts: WorkflowHistoryOpts = {}): WorkflowHistoryVersion[] {
67
+ const dir = historyDir(name, opts);
68
+ if (!existsSync(dir)) return [];
69
+ return readdirSync(dir)
70
+ .filter(file => file.endsWith('.yaml') && VERSION_ID.test(file.slice(0, -5)))
71
+ .map(file => {
72
+ const id = file.slice(0, -5);
73
+ const filePath = join(dir, file);
74
+ const stat = statSync(filePath);
75
+ return stat.isFile() ? { id, createdAt: new Date(Number(id.slice(0, 13))).toISOString(), size: stat.size } : null;
76
+ })
77
+ .filter((version): version is WorkflowHistoryVersion => version !== null)
78
+ .sort((a, b) => b.id.localeCompare(a.id));
79
+ }
80
+
81
+ /** A missing (well-formed) version returns null rather than an empty YAML draft. */
82
+ export function getWorkflowHistoryVersion(name: string, id: string, opts: WorkflowHistoryOpts = {}): string | null {
83
+ const dir = historyDir(name, opts);
84
+ safeVersionId(id);
85
+ const file = join(dir, `${id}.yaml`);
86
+ if (!VERSION_ID.test(id) || !existsSync(file)) return null;
87
+ return readFileSync(file, 'utf-8');
88
+ }
@@ -32,7 +32,9 @@ import { requirePosixShellCommand } from '../../platform/default-shell.js';
32
32
  import { spawn } from 'child_process';
33
33
  import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
34
34
  import { join } from 'path';
35
+ import { debug } from '../../debug/log.js';
35
36
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
37
+ import { getWorkflowHistoryVersion, listWorkflowHistory, snapshotBeforeSave } from './workflow-history.js';
36
38
  import {
37
39
  deleteWorkflow,
38
40
  discoverWorkflows,
@@ -232,6 +234,45 @@ export function handleWorkflowGet(
232
234
  );
233
235
  }
234
236
 
237
+ export function handleWorkflowHistoryList(
238
+ req: Request,
239
+ name: string,
240
+ opts: MetaApiOpts,
241
+ ): Response {
242
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
243
+ if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method: req.method }, 405);
244
+ const scope = new URL(req.url).searchParams.get('scope') === 'global' ? 'global' : 'project';
245
+ try {
246
+ return jsonResponse({ versions: listWorkflowHistory(name, { scope }) }, 200);
247
+ } catch (err) {
248
+ if (err instanceof Error && err.message === 'invalid workflow name') {
249
+ return jsonResponse({ error: 'bad_request', reason: err.message }, 400);
250
+ }
251
+ throw err;
252
+ }
253
+ }
254
+
255
+ export function handleWorkflowHistoryVersionGet(
256
+ req: Request,
257
+ name: string,
258
+ id: string,
259
+ opts: MetaApiOpts,
260
+ ): Response {
261
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
262
+ if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method: req.method }, 405);
263
+ const scope = new URL(req.url).searchParams.get('scope') === 'global' ? 'global' : 'project';
264
+ try {
265
+ const yaml = getWorkflowHistoryVersion(name, id, { scope });
266
+ if (yaml === null) return jsonResponse({ error: 'not_found', name, id }, 404);
267
+ return jsonResponse({ yaml }, 200);
268
+ } catch (err) {
269
+ if (err instanceof Error && (err.message === 'invalid workflow name' || err.message === 'invalid workflow history version id')) {
270
+ return jsonResponse({ error: 'bad_request', reason: err.message }, 400);
271
+ }
272
+ throw err;
273
+ }
274
+ }
275
+
235
276
  export async function handleWorkflowPut(
236
277
  req: Request,
237
278
  name: string,
@@ -248,6 +289,17 @@ export async function handleWorkflowPut(
248
289
  return jsonResponse({ error: 'bad_request', reason: '`yaml` must be a string' }, 400);
249
290
  }
250
291
  const scope = scopeRaw === 'global' ? 'global' : 'project';
292
+ const parsed = parseWorkflowYaml(yamlText);
293
+ if (parsed.ok && parsed.workflow?.name === name) {
294
+ // Capture the existing file before saveWorkflow overwrites it; a failed snapshot is non-fatal.
295
+ try {
296
+ snapshotBeforeSave(name, { scope });
297
+ } catch (err) {
298
+ debug.log('nexus.workflow-history', 'snapshot-failed', {
299
+ name, scope, error: err instanceof Error ? err.message : String(err),
300
+ }, { level: 'warn' });
301
+ }
302
+ }
251
303
  const r = saveWorkflow(name, yamlText, { scope });
252
304
  if (!r.ok) {
253
305
  return jsonResponse(
@@ -52,20 +52,29 @@ export function deliveryDedupKey(kind: string, text: string): string {
52
52
  return createHash('sha1').update(`${kind}\n${norm}`).digest('hex').slice(0, 16);
53
53
  }
54
54
 
55
- /** ① 중복 발사 제어 — 같은 dedupKey가 windowSec 내에 이미 발송됐나(재팬아웃 억제).
56
- * 보수적 짧은 창(기본 120s) — 크론 재시도·이중호출 같은 즉시 중복만 잡고,
57
- * 시간 지난 정당한 재발송은 통과. (의미 중복은 상류 signal-dedup 6h 담당.) */
58
- export function recentlyDelivered(
55
+ /** ① 중복 발사 제어 — windowSec 내 동일 메시지가 한 채널 이상 성공했으면 그 messageId.
56
+ * 실패 기록은 재시도를 막지 않는다. 짧은 창(기본 120s) 밖의 재발송은 통과. */
57
+ export function successfulDeliveryId(
59
58
  db: Database, dedupKey: string, windowSec = 120, nowMs: number = Date.now(),
60
- ): boolean {
59
+ ): string | null {
61
60
  // ⚠️ 축 ②(JS 임계 · db-window 참조) — 같은 파일의 `recentDeliveryCount` 와 동형이다.
62
61
  // SQL 안의 `datetime('now')` 를 쓰면 **시계를 주입할 수 없어** 테스트가 실제 시각에
63
62
  // 매달린다. 실제로 이 함수의 테스트가 고정 날짜로 seed 한 채 시간이 흘러 어느 날 빨개진
64
63
  // 시한폭탄이었다. `deliveries.ts` 는 ISO 로만 쓰므로 raw 비교가 옳고 인덱스도 산다.
65
64
  const row = db.prepare(
66
- `SELECT 1 FROM deliveries WHERE dedup_key = ? AND ts >= ? LIMIT 1`,
67
- ).get(dedupKey, sinceTs(SECONDS(windowSec), nowMs));
68
- return !!row;
65
+ `SELECT message_id FROM deliveries
66
+ WHERE dedup_key = ? AND ts >= ?
67
+ AND EXISTS (SELECT 1 FROM json_each(deliveries.channels) WHERE json_extract(value, '$.ok') = 1)
68
+ ORDER BY ts DESC LIMIT 1`,
69
+ ).get(dedupKey, sinceTs(SECONDS(windowSec), nowMs)) as { message_id: string } | null;
70
+ return row?.message_id ?? null;
71
+ }
72
+
73
+ /** 기존 boolean 호출부 호환 — 성공 배송 기록만 중복으로 센다. */
74
+ export function recentlyDelivered(
75
+ db: Database, dedupKey: string, windowSec = 120, nowMs: number = Date.now(),
76
+ ): boolean {
77
+ return successfulDeliveryId(db, dedupKey, windowSec, nowMs) !== null;
69
78
  }
70
79
 
71
80
  /** 최근 창(windowSec) 내 총 발송 수 — 버스트(밀림) 판정용. 전 kind 합산.
@@ -27,13 +27,14 @@
27
27
  // that boolean before falling back to direct Telegram).
28
28
 
29
29
  import { createHash } from 'node:crypto';
30
+ import { debug } from '../../debug/log.js';
30
31
  import { sendTelegramReport } from '../../telegram-report.js';
31
32
  import { getPushcutClient } from '../../pushcut/client.js';
32
33
  import type { UserConfig } from '../../user-config.js';
33
34
  import { formatForChannel, type OutboundChannelType, type OutboundMsg } from './format.js';
34
35
  import { spillLongContent } from '../../storage/content-spill.js';
35
36
  import {
36
- openDeliveryDb, deliveryDedupKey, recentlyDelivered, recordDelivery, type ChannelDelivery,
37
+ openDeliveryDb, deliveryDedupKey, successfulDeliveryId, recordDelivery, type ChannelDelivery,
37
38
  } from './delivery-ledger.js';
38
39
 
39
40
  // 채널 타입·메시지·분할기는 format.ts가 단일 출처 — 기존 소비처(test 등) 호환 재export.
@@ -71,6 +72,8 @@ export interface RouteResult {
71
72
  channels: ChannelResult[];
72
73
  /** 중복 발사 제어(dedup)로 재팬아웃이 억제됨 — 최근 동일 발송 존재. */
73
74
  suppressed?: boolean;
75
+ /** 억제 근거가 된 앞선 성공 발송의 식별자. */
76
+ suppressedBy?: string;
74
77
  /** 이 발송의 식별자(리드 동기화·회상 교차참조용). */
75
78
  messageId?: string;
76
79
  }
@@ -207,9 +210,11 @@ export async function routeOutbound(cfg: UserConfig, msg: OutboundMsg, deps: Rou
207
210
  // ① 중복 발사 제어 — 최근(120s) 동일 발송이면 재팬아웃 억제.
208
211
  if (ledger && deps.dedup !== false) {
209
212
  try {
210
- if (recentlyDelivered(ledger, dedupKey)) {
213
+ const suppressedBy = successfulDeliveryId(ledger, dedupKey);
214
+ if (suppressedBy) {
211
215
  if (ownLedger) ledger.close();
212
- return { delivered: true, channels: [], suppressed: true, messageId };
216
+ try { debug.log('outbound.send', 'suppressed', { kind: msg.kind, suppressedBy }); } catch { /* 관측 실패는 발송 결과에 영향 없음 */ }
217
+ return { delivered: true, channels: [], suppressed: true, suppressedBy, messageId };
213
218
  }
214
219
  } catch { /* 원장 조회 실패 — 억제 없이 진행 */ }
215
220
  }
@@ -138,6 +138,22 @@ export function validatePersonaShape(
138
138
  models = mr.models;
139
139
  }
140
140
 
141
+ let seat: string | undefined;
142
+ if (Object.hasOwn(obj, 'seat')) {
143
+ if (typeof obj['seat'] !== 'string' || !obj['seat'].trim()) {
144
+ return err(path, 'seat must be a non-empty string');
145
+ }
146
+ seat = obj['seat'];
147
+ }
148
+
149
+ let todo: string | undefined;
150
+ if (Object.hasOwn(obj, 'todo')) {
151
+ if (typeof obj['todo'] !== 'string' || !/^[a-z0-9_][a-z0-9_-]*\.todo\.jsonl$/i.test(obj['todo'])) {
152
+ return err(path, 'todo must be a per-persona .todo.jsonl filename');
153
+ }
154
+ todo = obj['todo'];
155
+ }
156
+
141
157
  let mentionPatterns: readonly string[] | undefined;
142
158
  if (obj['mentionPatterns'] !== undefined) {
143
159
  if (!Array.isArray(obj['mentionPatterns'])
@@ -149,6 +165,8 @@ export function validatePersonaShape(
149
165
 
150
166
  const profile: PersonaProfile = {
151
167
  personaId, displayName,
168
+ ...(seat !== undefined ? { seat } : {}),
169
+ ...(todo !== undefined ? { todo } : {}),
152
170
  ...(description.value !== undefined ? { description: description.value } : {}),
153
171
  ...(systemPrompt.value !== undefined ? { systemPrompt: systemPrompt.value } : {}),
154
172
  ...(brand !== undefined ? { brand } : {}),
@@ -6,6 +6,8 @@ export interface PersonaTodo {
6
6
  title: string;
7
7
  status: 'open' | 'done';
8
8
  createdAt: string;
9
+ priority?: number;
10
+ dueAt?: string;
9
11
  }
10
12
 
11
13
  export interface PersonaTodoReadResult {
@@ -28,7 +30,9 @@ function isPersonaTodo(value: unknown): value is PersonaTodo {
28
30
  && typeof item.title === 'string' && item.title.length > 0
29
31
  && (item.status === 'open' || item.status === 'done')
30
32
  && typeof item.createdAt === 'string'
31
- && Number.isFinite(Date.parse(item.createdAt));
33
+ && Number.isFinite(Date.parse(item.createdAt))
34
+ && (item.priority === undefined || (typeof item.priority === 'number' && Number.isInteger(item.priority) && item.priority >= 0))
35
+ && (item.dueAt === undefined || (typeof item.dueAt === 'string' && Number.isFinite(Date.parse(item.dueAt))));
32
36
  }
33
37
 
34
38
  /** Missing files are empty; malformed lines are skipped and returned for observation. */
@@ -67,13 +71,14 @@ export function writePersonaTodos(dir: string, personaId: string, todos: readonl
67
71
  writeFileSync(path, todos.map(todo => JSON.stringify(todo)).join('\n') + (todos.length ? '\n' : ''), 'utf8');
68
72
  }
69
73
 
70
- /** Earliest created open item; original order breaks equal-timestamp ties. */
74
+ /** Higher priority first, then earlier deadline, then earlier creation; file order breaks ties. */
75
+ export function orderedPersonaTodos(todos: readonly PersonaTodo[]): PersonaTodo[] {
76
+ return [...todos].filter((todo) => todo.status === 'open').sort((a, b) =>
77
+ (b.priority ?? 0) - (a.priority ?? 0)
78
+ || (a.dueAt && b.dueAt ? Date.parse(a.dueAt) - Date.parse(b.dueAt) : a.dueAt ? -1 : b.dueAt ? 1 : 0)
79
+ || Date.parse(a.createdAt) - Date.parse(b.createdAt));
80
+ }
81
+
71
82
  export function nextPersonaTodo(todos: readonly PersonaTodo[]): PersonaTodo | null {
72
- let oldest: PersonaTodo | null = null;
73
- for (const todo of todos) {
74
- if (todo.status === 'open' && (!oldest || Date.parse(todo.createdAt) < Date.parse(oldest.createdAt))) {
75
- oldest = todo;
76
- }
77
- }
78
- return oldest;
83
+ return orderedPersonaTodos(todos)[0] ?? null;
79
84
  }
@@ -88,6 +88,7 @@ export function presetToProfileYaml(p: PersonaPreset, common: PresetCommon, opts
88
88
  displayName: opts.name?.trim() ? `${opts.name.trim()} · ${p.role}` : p.displayName,
89
89
  description: p.oneLine,
90
90
  systemPrompt: presetSystemPrompt(p, common, name),
91
+ todo: `${opts.personaId}.todo.jsonl`,
91
92
  preset: { id: p.personaId, role: p.role, ...(title ? { title } : {}), tools: p.tools.map((t) => t.name), firstQuestions: p.firstQuestions },
92
93
  };
93
94
  return `# 프리셋 «${p.personaId}» 에서 만든 페르소나 — elanous persona add (원본: persona-presets/${p.personaId}.yaml)\n${stringify(profile, { lineWidth: 0 })}`;
@@ -99,7 +100,7 @@ export interface PersonaWriteResult { personaId: string; displayName: string; pa
99
100
  // Only fields in the runtime persona schema may be edited; provenance (`preset`) is retained, not editable.
100
101
  const PROFILE_KEYS = new Set([
101
102
  'personaId', 'displayName', 'description', 'systemPrompt', 'actionHosts', 'offsiteNavigation',
102
- 'brand', 'models', 'mentionPatterns', 'avatarUrl', 'brandColor', 'browserPort', 'residence', 'capabilities',
103
+ 'brand', 'models', 'mentionPatterns', 'avatarUrl', 'brandColor', 'browserPort', 'residence', 'capabilities', 'todo',
103
104
  ]);
104
105
  const NESTED_KEYS: Record<string, readonly string[]> = {
105
106
  models: ['primary', 'fallback', 'providers'], 'models.providers': ['ollama', 'openai'],
@@ -111,11 +112,18 @@ function mapping(value: unknown): value is Record<string, unknown> {
111
112
  return value !== null && typeof value === 'object' && !Array.isArray(value);
112
113
  }
113
114
 
115
+ export class UnknownPersonaSchemaKeyError extends Error {
116
+ constructor(key: string) {
117
+ super(`unknown persona schema key: ${key}`);
118
+ this.name = 'UnknownPersonaSchemaKeyError';
119
+ }
120
+ }
121
+
114
122
  function assertSchemaKeys(value: Record<string, unknown>, parent = ''): void {
115
123
  const allowed: ReadonlySet<string> = parent ? new Set(NESTED_KEYS[parent] ?? []) : PROFILE_KEYS;
116
124
  for (const [key, child] of Object.entries(value)) {
117
125
  if (!allowed.has(key)) {
118
- throw new Error(`unknown persona schema key: ${parent ? `${parent}.` : ''}${key}`);
126
+ throw new UnknownPersonaSchemaKeyError(`${parent ? `${parent}.` : ''}${key}`);
119
127
  }
120
128
  const path = parent ? `${parent}.${key}` : key;
121
129
  if (NESTED_KEYS[path] && mapping(child)) assertSchemaKeys(child, path);
@@ -0,0 +1,105 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { parseDocument } from 'yaml';
4
+ import { parsePersonaYaml } from './loader.js';
5
+
6
+ export interface TeamMember {
7
+ persona: string;
8
+ title: string;
9
+ checklistTemplate: string[];
10
+ }
11
+
12
+ export interface TeamHandoff {
13
+ from: string;
14
+ to: string;
15
+ }
16
+
17
+ export interface TeamDefinition {
18
+ name: string;
19
+ members: TeamMember[];
20
+ handoffs: TeamHandoff[];
21
+ }
22
+
23
+ function mapping(value: unknown): value is Record<string, unknown> {
24
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
25
+ }
26
+
27
+ function fields(value: Record<string, unknown>, required: readonly string[], label: string): void {
28
+ for (const key of required) {
29
+ if (!Object.hasOwn(value, key)) throw new Error(`${label}.${key} is required`);
30
+ }
31
+ for (const key of Object.keys(value)) {
32
+ if (!required.includes(key)) throw new Error(`${label}.${key} is not a team schema field`);
33
+ }
34
+ }
35
+
36
+ function text(value: unknown, label: string): string {
37
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} must be a non-empty string`);
38
+ return value;
39
+ }
40
+
41
+ /** Read-only validation: resolve persona paths relative to the team file, not the caller's cwd. */
42
+ export function validateTeamFile(file: string): TeamDefinition {
43
+ const document = parseDocument(readFileSync(file, 'utf8'), { uniqueKeys: true });
44
+ if (document.errors.length) throw new Error(`invalid team YAML: ${document.errors[0]!.message}`);
45
+ const raw: unknown = document.toJS();
46
+ if (!mapping(raw)) throw new Error('team must be a YAML mapping');
47
+ fields(raw, ['name', 'members', 'handoffs'], 'team');
48
+ const name = text(raw.name, 'team.name');
49
+ if (!Array.isArray(raw.members) || raw.members.length === 0) {
50
+ throw new Error('team.members must be a non-empty list');
51
+ }
52
+ const titles = new Set<string>();
53
+ const personaIds = new Set<string>();
54
+ const members: TeamMember[] = raw.members.map((item: unknown, index: number) => {
55
+ const label = `team.members[${index}]`;
56
+ if (!mapping(item)) throw new Error(`${label} must be a mapping`);
57
+ fields(item, ['persona', 'title', 'checklistTemplate'], label);
58
+ const persona = text(item.persona, `${label}.persona`);
59
+ const title = text(item.title, `${label}.title`);
60
+ if (titles.has(title)) throw new Error(`duplicate team title: ${title}`);
61
+ titles.add(title);
62
+ if (!Array.isArray(item.checklistTemplate) || item.checklistTemplate.length === 0) {
63
+ throw new Error(`${label}.checklistTemplate must be a non-empty list of strings`);
64
+ }
65
+ const checklistTemplate = item.checklistTemplate.map((entry: unknown) => text(entry, `${label}.checklistTemplate item`));
66
+ const personaPath = resolve(dirname(file), persona);
67
+ let yaml: string;
68
+ try {
69
+ yaml = readFileSync(personaPath, 'utf8');
70
+ } catch (error) {
71
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') throw new Error(`persona not found: ${persona} (${title})`);
72
+ throw error;
73
+ }
74
+ const parsed = parsePersonaYaml(yaml, personaPath);
75
+ if (!parsed.ok) throw new Error(`invalid persona ${persona}: ${parsed.error.message}`);
76
+ if (parsed.profile.seat !== title) throw new Error(`persona seat mismatch: ${persona} requires seat ${title}`);
77
+ if (personaIds.has(parsed.profile.personaId)) throw new Error(`duplicate personaId: ${parsed.profile.personaId}`);
78
+ personaIds.add(parsed.profile.personaId);
79
+ return { persona, title, checklistTemplate };
80
+ });
81
+ if (!Array.isArray(raw.handoffs)) throw new Error('team.handoffs must be a list');
82
+ const handoffs: TeamHandoff[] = raw.handoffs.map((item: unknown, index: number) => {
83
+ const label = `team.handoffs[${index}]`;
84
+ if (!mapping(item)) throw new Error(`${label} must be a mapping`);
85
+ fields(item, ['from', 'to'], label);
86
+ const from = text(item.from, `${label}.from`);
87
+ const to = text(item.to, `${label}.to`);
88
+ if (!titles.has(from) || !titles.has(to)) throw new Error(`handoff references missing title: ${from} -> ${to}`);
89
+ return { from, to };
90
+ });
91
+ const edges = new Map([...titles].map((title) => [title, [] as string[]]));
92
+ for (const { from, to } of handoffs) edges.get(from)!.push(to);
93
+ const visiting = new Set<string>();
94
+ const visited = new Set<string>();
95
+ const visit = (title: string): void => {
96
+ if (visiting.has(title)) throw new Error(`handoff cycle at ${title}`);
97
+ if (visited.has(title)) return;
98
+ visiting.add(title);
99
+ for (const next of edges.get(title)!) visit(next);
100
+ visiting.delete(title);
101
+ visited.add(title);
102
+ };
103
+ for (const title of titles) visit(title);
104
+ return { name, members, handoffs };
105
+ }
@@ -19,6 +19,10 @@ export type PersonaBrand =
19
19
  export interface PersonaProfile {
20
20
  readonly personaId: string;
21
21
  readonly displayName: string;
22
+ /** Seat address (title, id or alias) represented by this persona. */
23
+ readonly seat?: string;
24
+ /** Per-persona JSONL todo filename in the state persona directory. */
25
+ readonly todo?: string;
22
26
  readonly description?: string;
23
27
 
24
28
  /** System prompt to prepend in LLM calls. Markdown text. May
@@ -17,7 +17,8 @@ function packageFiles(dir: string, prefix = ''): string[] {
17
17
  const files: string[] = [];
18
18
  for (const entry of readdirSync(join(dir, prefix), { withFileTypes: true })) {
19
19
  const path = prefix ? `${prefix}/${entry.name}` : entry.name;
20
- if (entry.name === '.git' || entry.name === '.elanous' || entry.name === 'node_modules') continue;
20
+ if (entry.name === '.git' || entry.name === '.elanous' || entry.name === '.elanous-local' ||
21
+ entry.name === '.elanous-test' || entry.name === 'graph-runs' || entry.name === 'node_modules') continue;
21
22
  if (entry.isSymbolicLink() || (!entry.isFile() && !entry.isDirectory())) throw new Error(`unsafe market bundle entry: ${path}`);
22
23
  if (entry.isDirectory()) files.push(...packageFiles(dir, path));
23
24
  else files.push(path);