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
@@ -1,5 +1,5 @@
1
1
  import { execFile } from 'node:child_process';
2
- import { createHash } from 'node:crypto';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
3
  import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
4
4
  import { Database } from 'bun:sqlite';
5
5
  import { dirname, join, resolve } from 'node:path';
@@ -8,16 +8,18 @@ import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot
8
8
  import { debug } from '../debug/log.js';
9
9
  import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
10
10
  import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
11
+ import { loopEvent } from '../loops/observe.js';
11
12
  import { getUserConfig, type PersonaLoopConfig, type SeatLoopConfig, type UserConfig } from '../user-config.js';
12
13
  import { checkSeatBudget, type RunningSeatPod } from '../org/seat-budget.js';
13
14
  import { DEFAULT_STALL_PARENTS, findStalls } from '../org/stall-escalation.js';
14
15
  import { openMsgStore, type MessageEnvelope } from '../msg/msg-store.js';
15
16
  import { loadLayeredPersonaDirs, resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
16
- import { nextPersonaTodo, personaTodoPath, readPersonaTodos, type PersonaTodo } from '../persona/persona-todo.js';
17
+ import { orderedPersonaTodos, personaTodoPath, readPersonaTodos, type PersonaTodo } from '../persona/persona-todo.js';
17
18
  import { PersonaRegistry } from '../persona/registry.js';
19
+ import { recordSeatAskShadow } from '../seat-dispatch/seat-ask.js';
18
20
  import { answerSeat, askSeat, deliveredSeatQuestion, hasSeatAnswer, isSeatId, seatQuestions, type SeatId } from '../seat-dispatch/seat-questions.js';
19
21
  import { queryRunningRuns } from '../self-implement/running-runs.js';
20
- import { listChecklist, type Checklist } from '../release-loop/checklist.js';
22
+ import { checklistHistory, listChecklist, type Checklist } from '../release-loop/checklist.js';
21
23
  import { readSchedules, releasedVersion } from '../release-loop/feature-store.js';
22
24
 
23
25
  // `kind`/`createdAt` are the V3 shadow-compare keys (내부 문서 `METHOD-v3-shadow-compare-2026-10-02` · MK 10-02 18:54).
@@ -31,9 +33,14 @@ export type OpCandidate =
31
33
  | { kind: 'release-readiness'; version: string | null; verdict: 'ready' | 'not-ready' | 'no-open-release'; cutAt?: string; landBy?: string | null; red: string[]; undecided: string[]; blocked: string[] }
32
34
  | { kind: 'unassigned-cell'; version: string | null; id: string | null; title?: string; status?: string; verdict: 'assign' | 'none' }
33
35
  | { kind: 'decision-delegation'; id: string | null; title?: string; category?: DecisionEntry['category']; dueAt?: string; verdict: 'review-delegation' | 'none' };
34
- export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate; action?: SeatAction; reason?: string; runId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean };
36
+ export type TcCandidate =
37
+ | { kind: 'merged-pr-yellow-cell'; version: string; id: string; title: string; pr: number; mergedAt: string }
38
+ | { kind: 'publication-pr-approval-wait'; pr: number; title: string; readyAt: string; approvalWaitAt: string; paths: string[] }
39
+ | { kind: 'other-seat-cell-defect'; version: string; id: string; title: string; to: SeatId };
40
+ export type TcPullRequest = { number: number; title: string; body?: string; state: 'OPEN' | 'MERGED' | 'CLOSED'; isDraft: boolean; createdAt: string; updatedAt?: string; mergedAt?: string | null; readyAt?: string; approvalWaitAt?: string; paths?: string[]; reviewDecision?: string };
41
+ export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate; action?: SeatAction; reason?: string; runId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean };
35
42
  export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off' };
36
- export type PersonaShadowEntry = { personaId: string; ts: string; status: 'shadow' | 'skipped-empty'; todo: PersonaTodo | null };
43
+ export type PersonaShadowEntry = { personaId: string; ts: string; status: 'shadow' | 'skipped-empty'; todo: PersonaTodo | null; action?: 'decision' | 'harness' | 'wait'; what?: string };
37
44
  export type SeatDeps = {
38
45
  personaConfig?: PersonaLoopConfig;
39
46
  personaRegistry?: PersonaRegistry;
@@ -54,12 +61,16 @@ export type SeatDeps = {
54
61
  schedules?: () => Array<{ version: string; cutAt: string; landBy?: string | null }>;
55
62
  /** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
56
63
  checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string }>;
64
+ /** Chronological release-ledger events for a checklist id; injected for isolated release fixtures. */
65
+ checklistHistory?: (id: string) => ReturnType<typeof checklistHistory>;
57
66
  /** Full checklist snapshot including history, for stall detection. */
58
67
  stallChecklist?: (version: string) => Checklist;
59
68
  /** Inject a mailbox write for isolated failure tests; production uses the seat message store. */
60
69
  stallDelivery?: (message: MessageEnvelope, key: string, root: string) => boolean;
61
70
  /** Open cards from the decision ledger; injected only for isolated ledger tests. */
62
71
  pendingDecisions?: () => Array<Pick<DecisionEntry, 'id' | 'title' | 'category' | 'dueAt'>>;
72
+ /** Read-only PR snapshot for TC judgment; defaults to GitHub CLI reads. */
73
+ pullRequests?: () => Promise<TcPullRequest[]>;
63
74
  ledgerFiles?: (directory: string) => string[];
64
75
  lockContended?: () => void;
65
76
  /** Supply a question only when this seat lacks evidence for the selected work item. */
@@ -126,8 +137,17 @@ export function personaShadowLedgerPath(personaId: string, root: string, now: Da
126
137
  return join(root, 'persona-loop', personaId, `${seatDay(now)}.jsonl`);
127
138
  }
128
139
 
140
+ export async function runPersonaLoopOnce(name: string, deps: SeatDeps = {}): Promise<PersonaShadowEntry> {
141
+ const entries = await pickPersonaShadows(deps, name);
142
+ return entries[0]!;
143
+ }
144
+
129
145
  export async function runPersonaShadowOnce(deps: SeatDeps = {}): Promise<PersonaShadowEntry[]> {
130
146
  if ((deps.personaConfig ?? getUserConfig().loops?.persona)?.enabled !== true) return [];
147
+ return pickPersonaShadows(deps);
148
+ }
149
+
150
+ async function pickPersonaShadows(deps: SeatDeps, name?: string): Promise<PersonaShadowEntry[]> {
131
151
  const root = deps.root ?? effectiveInstanceRoot();
132
152
  const dir = deps.personaDir ?? (deps.root ? join(root, 'personas') : resolveStatePersonaDir());
133
153
  const registry = deps.personaRegistry ?? new PersonaRegistry();
@@ -137,19 +157,31 @@ export async function runPersonaShadowOnce(deps: SeatDeps = {}): Promise<Persona
137
157
  }
138
158
  const now = (deps.now ?? (() => new Date()))();
139
159
  const entries: PersonaShadowEntry[] = [];
140
- for (const profile of registry.list().sort((a, b) => a.personaId.localeCompare(b.personaId))) {
160
+ const profiles = registry.list().sort((a, b) => a.personaId.localeCompare(b.personaId));
161
+ const selected = name === undefined ? profiles : profiles.filter((profile) => profile.personaId.toLowerCase() === name.trim().toLowerCase()
162
+ || profile.displayName.toLowerCase() === name.trim().toLowerCase());
163
+ if (name !== undefined && selected.length !== 1) throw new Error(selected.length ? `ambiguous persona name: ${name}` : `persona not found: ${name}`);
164
+ for (const profile of selected) {
141
165
  const personaId = profile.personaId;
142
- const { todos, errors } = readPersonaTodos(dir, personaId);
166
+ const todoFile = profile.todo ?? `${personaId}.todo.jsonl`;
167
+ const { todos, errors } = readPersonaTodos(dir, todoFile.slice(0, -'.todo.jsonl'.length));
143
168
  for (const error of errors) observe('persona-todo-error', { personaId, error });
144
- const todo = nextPersonaTodo(todos);
145
- const entry: PersonaShadowEntry = { personaId, ts: now.toISOString(), status: todo ? 'shadow' : 'skipped-empty', todo };
169
+ const candidates = orderedPersonaTodos(todos);
170
+ const picked = pickNext({ requests: candidates.map((candidate) => ({
171
+ source: 'request', id: candidate.id, title: candidate.title, text: candidate.title, createdAt: candidate.createdAt,
172
+ })), checklist: [], role: '' }, [], { shadow: true });
173
+ const todo = picked ? candidates.find((candidate) => candidate.id === picked.id) ?? null : null;
174
+ const planned = name !== undefined && todo && picked ? planAction(picked, profile.seat) : undefined;
175
+ const entry: PersonaShadowEntry = { personaId, ts: now.toISOString(), status: todo ? 'shadow' : 'skipped-empty', todo,
176
+ ...(planned ? { action: planned.kind, what: planned.text } : {}) };
146
177
  const path = personaShadowLedgerPath(personaId, root, now);
147
178
  if (deps.appendPersona) deps.appendPersona(path, entry);
148
179
  else {
149
180
  mkdirSync(dirname(path), { recursive: true });
150
181
  appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
151
182
  }
152
- observe('persona-shadow', { personaId, status: entry.status, todoId: todo?.id ?? null });
183
+ try { debug.log('persona.loop', 'picked', { personaId, status: entry.status, todoId: todo?.id ?? null, title: todo?.title ?? null, action: entry.action ?? null }); }
184
+ catch { /* observation cannot alter the shadow ledger */ }
153
185
  entries.push(entry);
154
186
  }
155
187
  return entries;
@@ -159,6 +191,25 @@ export async function runSeatLoopTurn(seat: string, deps: SeatDeps = {}): Promis
159
191
  const result = await runSeatLoopOnce(seat, deps);
160
192
  try { await runPersonaShadowOnce(deps); }
161
193
  catch (error) { observe('persona-shadow-error', { error: String(error).slice(0, 200) }); }
194
+ try {
195
+ const config = deps.config ?? getUserConfig().loops?.seat ?? { mode: 'off' };
196
+ const profile = result.status === 'skipped-off' ? 'off' : config.mode;
197
+ const context = { runId: 'runId' in result && result.runId ? result.runId : `seat-turn-${randomUUID()}`,
198
+ profile, sourceRef: `docs/roles/${seat}.md` };
199
+ const loopId = `${seat.toLowerCase()}-seat`;
200
+ loopEvent(loopId, 'tick', { ...context, outcome: result.status, reason: result.status });
201
+ if ('inquiry' in result && result.inquiry && (result.status === 'asked' || result.status === 'hitl')) {
202
+ loopEvent(loopId, 'exchange', { ...context, outcome: result.status, reason: 'delegation',
203
+ from: seat, to: result.inquiry.to, itemId: result.item?.id });
204
+ }
205
+ if ('escalated' in result && result.escalated && result.status === 'skipped-budget') {
206
+ loopEvent(loopId, 'exchange', { ...context, outcome: result.status, reason: 'delegation',
207
+ from: seat, to: (config.stall?.parents ?? {})[seat] ?? DEFAULT_STALL_PARENTS[seat], itemId: result.item?.id });
208
+ } else if (result.status === 'skipped-budget') {
209
+ loopEvent(loopId, 'exchange', { ...context, outcome: result.status, reason: 'requeue',
210
+ itemId: result.item?.id });
211
+ }
212
+ } catch { /* observation cannot change a completed seat turn */ }
162
213
  return result;
163
214
  }
164
215
 
@@ -190,6 +241,8 @@ function decisionReceipt(root: string, seat: string, item: SeatItem): DecisionEn
190
241
  function handledKeys(ledger: readonly SeatEntry[], shadow: boolean, root?: string): Set<string> {
191
242
  const inquiryRow = (entry: SeatEntry) => entry.action === 'seat-question' || entry.action === 'seat-answer' || !!entry.inquiry;
192
243
  return new Set(ledger.filter((entry, index) => entry.status === 'asked' || entry.status === 'answered'
244
+ // RM3a: only TC's other-seat defect ask-shadows are deduplicated here — an ordinary inquiry recorded in shadow must still be delivered once questions turn on.
245
+ || (entry.status === 'shadow' && entry.candidate?.kind === 'other-seat-cell-defect')
193
246
  || (entry.status === 'hitl' && (!entry.inquiry || (!!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))))
194
247
  || (entry.inquiry?.to === 'CEO' && !!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))
195
248
  || (!inquiryRow(entry) && entry.status === 'outcome-unknown')
@@ -211,17 +264,19 @@ function eligibleItem(item: SeatItem, ledger: readonly SeatEntry[], handled: Rea
211
264
  return !lastWait?.item || itemSnapshot(lastWait.item) !== itemSnapshot(item);
212
265
  }
213
266
 
214
- // Compare checklist snapshots without consulting release history or treating a version move as new work.
267
+ const CHECKLIST_HANDLED_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
268
+
269
+ // Same-version snapshots are handled here; carried work requires an explicit release-ledger move.
215
270
  export function alreadyHandled(item: SeatItem, ledger: readonly SeatEntry[]): boolean {
216
271
  if (item.source !== 'checklist' || !item.seat || !item.asOf || item.evidenceHash === undefined) return false;
217
272
  const now = Date.parse(item.asOf);
218
273
  if (!Number.isFinite(now)) return false;
219
274
  return ledger.some((entry) => {
220
275
  const at = Date.parse(entry.at || entry.ts || '');
221
- return entry.seat === item.seat && at >= now - 7 * 24 * 60 * 60 * 1000 && at <= now
276
+ return entry.seat === item.seat && at >= now - CHECKLIST_HANDLED_WINDOW_MS && at <= now
222
277
  // A shadowed seat question was never delivered — it must be picked again once delivery is on (RM3 · TC merge 10-03).
223
278
  && (entry.status === 'launched' || (entry.status === 'shadow' && !entry.inquiry))
224
- && entry.item?.source === 'checklist' && entry.item.id === item.id
279
+ && entry.item?.source === 'checklist' && entry.item.id === item.id && entry.item.version === item.version
225
280
  && entry.item.title === item.title && entry.item.evidenceHash === item.evidenceHash;
226
281
  });
227
282
  }
@@ -293,9 +348,10 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger
293
348
  debug.log('seat.loop', 'schedule-unreadable', { seat, error: String(error).slice(0, 200) });
294
349
  }
295
350
  const checklist: SeatItem[] = [];
351
+ const closed: string[] = [];
296
352
  for (const version of open === null ? [] : versions().sort(versionOrder)) {
297
353
  if (!open!.has(version)) {
298
- debug.log('seat.loop', 'skip-closed-version', { seat, version });
354
+ closed.push(version);
299
355
  continue;
300
356
  }
301
357
  let items: ReturnType<typeof itemsOf>;
@@ -315,6 +371,9 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger
315
371
  }
316
372
  }
317
373
  }
374
+ if (closed.length > 0) {
375
+ debug.log('seat.loop', 'skip-closed-versions', { seat, count: closed.length, newest: closed.at(-1)!, oldest: closed[0]! });
376
+ }
318
377
  checklist.sort((a, b) => versionOrder(a.version!, b.version!) || a.id.localeCompare(b.id));
319
378
  return { requests: pending, checklist, role: read(join(deps.repo ?? repoRoot, 'docs', 'roles', `${seat}.md`)).slice(0, 4_000) };
320
379
  }
@@ -357,6 +416,137 @@ function opCandidates(deps: SeatDeps, now: Date): OpCandidate[] {
357
416
  ];
358
417
  }
359
418
 
419
+ const TC_APPROVAL_WAIT_MS = 2 * 60 * 60 * 1000;
420
+
421
+ function readTcChecklist(root: string): Array<{ version: string; id: string; title: string; status: string; owner: string | null }> {
422
+ const path = join(root, 'release', 'features.sqlite');
423
+ if (!existsSync(path)) return [];
424
+ const db = new Database(path, { readonly: true, strict: true });
425
+ try {
426
+ return db.query(`SELECT a.version, f.id, COALESCE(a.title_override, f.title) AS title, a.status, a.owner
427
+ FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.status IN ('yellow', 'red') AND a.owner IN ('OP', 'TC', 'MK', 'UX')
428
+ ORDER BY a.version, f.id`).all() as Array<{ version: string; id: string; title: string; status: string; owner: string | null }>;
429
+ } finally { db.close(); }
430
+ }
431
+
432
+ type TcTimelineEvent = { event: string; created_at: string };
433
+
434
+ export function tcApprovalWaitAt(pr: TcPullRequest, timeline: readonly TcTimelineEvent[]): { readyAt: string; approvalWaitAt: string } | undefined {
435
+ if (pr.state !== 'OPEN' || pr.isDraft || pr.reviewDecision !== 'REVIEW_REQUIRED') return undefined;
436
+ const events = timeline.filter((event) => typeof event.created_at === 'string' && Number.isFinite(Date.parse(event.created_at)))
437
+ .sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
438
+ let readyAt: string | undefined = pr.createdAt;
439
+ let waitAt: string | undefined = pr.createdAt;
440
+ for (const event of events) {
441
+ if (event.event === 'converted_to_draft') { readyAt = undefined; waitAt = undefined; }
442
+ if (event.event === 'ready_for_review') { readyAt = event.created_at; waitAt = event.created_at; }
443
+ if (event.event === 'review_requested' && readyAt && Date.parse(event.created_at) >= Date.parse(readyAt)) waitAt = event.created_at;
444
+ }
445
+ return readyAt && waitAt && Number.isFinite(Date.parse(readyAt)) && Date.parse(waitAt) >= Date.parse(readyAt)
446
+ ? { readyAt, approvalWaitAt: waitAt } : undefined;
447
+ }
448
+
449
+ export async function readTcPullRequests(query: (args: string[]) => Promise<string> = async (args) => {
450
+ const { stdout } = await exec('gh', args, { cwd: repoRoot, encoding: 'utf8', timeout: 120_000, maxBuffer: 64 * 1024 * 1024 });
451
+ return stdout;
452
+ }): Promise<TcPullRequest[]> {
453
+ const pages: unknown = JSON.parse(await query(['api', '--paginate', '--slurp', 'repos/{owner}/{repo}/pulls?state=all&per_page=100']));
454
+ if (!Array.isArray(pages) || !pages.every((page) => Array.isArray(page))) throw new Error('PR list is not an array of pages');
455
+ const prs = pages.flatMap((page: unknown[]) => page).filter((pr) => pr && typeof pr === 'object'
456
+ && ((pr as Record<string, unknown>).state === 'open' || typeof (pr as Record<string, unknown>).merged_at === 'string'));
457
+ const results: TcPullRequest[] = [];
458
+ for (let offset = 0; offset < prs.length; offset += 10) {
459
+ results.push(...await Promise.all(prs.slice(offset, offset + 10).map(async (pr): Promise<TcPullRequest> => {
460
+ const listing = pr as Record<string, unknown>;
461
+ if (typeof listing.number !== 'number' || typeof listing.title !== 'string' || typeof listing.created_at !== 'string'
462
+ || typeof listing.draft !== 'boolean' || (listing.state !== 'open' && listing.state !== 'closed')) {
463
+ throw new Error('incomplete PR record');
464
+ }
465
+ const details: unknown = listing.state === 'open'
466
+ ? JSON.parse(await query(['pr', 'view', String(listing.number), '--json', 'number,title,body,state,isDraft,createdAt,mergedAt,files,reviewDecision']))
467
+ : { number: listing.number, title: listing.title, body: listing.body, state: 'MERGED', isDraft: listing.draft,
468
+ createdAt: listing.created_at, mergedAt: listing.merged_at, files: [] };
469
+ if (!details || typeof details !== 'object' || Array.isArray(details)) throw new Error('invalid PR record');
470
+ const row = details as Record<string, unknown>;
471
+ if (typeof row.number !== 'number' || typeof row.title !== 'string' || typeof row.createdAt !== 'string'
472
+ || typeof row.isDraft !== 'boolean' || (row.state !== 'OPEN' && row.state !== 'MERGED' && row.state !== 'CLOSED')) {
473
+ throw new Error('incomplete PR record');
474
+ }
475
+ const paths = Array.isArray(row.files) ? row.files.flatMap((file: unknown) =>
476
+ file && typeof file === 'object' && typeof (file as { path?: unknown }).path === 'string' ? [(file as { path: string }).path] : []) : [];
477
+ const result: TcPullRequest = { number: row.number, title: row.title, body: typeof row.body === 'string' ? row.body : '',
478
+ state: row.state, isDraft: row.isDraft, createdAt: row.createdAt,
479
+ mergedAt: typeof row.mergedAt === 'string' ? row.mergedAt : null, paths,
480
+ reviewDecision: typeof row.reviewDecision === 'string' ? row.reviewDecision : undefined };
481
+ if (result.state === 'OPEN' && !result.isDraft && result.reviewDecision === 'REVIEW_REQUIRED'
482
+ && paths.some((path) => path.startsWith('release/public/') || path.startsWith('scripts/public-export') || path.startsWith('src/market/'))) {
483
+ try {
484
+ const pages: unknown = JSON.parse(await query(['api', '--paginate', '--slurp', `repos/{owner}/{repo}/issues/${result.number}/timeline`]));
485
+ if (!Array.isArray(pages) || !pages.every((page) => Array.isArray(page))) throw new Error('PR timeline is not an array of pages');
486
+ const timeline: unknown[] = pages.flatMap((page: unknown[]) => page);
487
+ const transitions = timeline.filter((event): event is Record<string, unknown> => event !== null && typeof event === 'object'
488
+ && ['converted_to_draft', 'ready_for_review', 'review_requested'].includes((event as Record<string, unknown>).event as string));
489
+ if (!transitions.every((event) => typeof event.created_at === 'string' && Number.isFinite(Date.parse(event.created_at)))) {
490
+ throw new Error('invalid PR timeline transition date');
491
+ }
492
+ Object.assign(result, tcApprovalWaitAt(result, transitions as TcTimelineEvent[]));
493
+ } catch (error) { observe('tc-pr-timeline-unreadable', { pr: result.number, error: String(error).slice(0, 200) }); }
494
+ }
495
+ return result;
496
+ })));
497
+ }
498
+ return results;
499
+ }
500
+
501
+ async function tcCandidates(deps: SeatDeps, now: Date): Promise<TcCandidate[]> {
502
+ let prs: TcPullRequest[];
503
+ // An injected instance root must not fetch unrelated live GitHub state.
504
+ try { prs = await (deps.pullRequests ?? (deps.root ? async () => [] : readTcPullRequests))(); }
505
+ catch (error) {
506
+ observe('tc-pr-unreadable', { seat: 'TC', error: String(error).slice(0, 200) });
507
+ return [];
508
+ }
509
+ const merged = prs.filter((pr) => pr.state === 'MERGED' && pr.mergedAt && Number.isFinite(Date.parse(pr.mergedAt)) && Date.parse(pr.mergedAt) <= now.getTime());
510
+ const candidates: TcCandidate[] = [];
511
+ const releaseRoot = deps.root ?? releaseLedgerRoot();
512
+ try {
513
+ const cells = deps.checklistItems && deps.schedules
514
+ ? deps.schedules().flatMap(({ version }) => deps.checklistItems!(version).map((cell) => ({ ...cell, version })))
515
+ : readTcChecklist(releaseRoot);
516
+ let shipped: string | undefined;
517
+ try { shipped = releasedVersion(releaseRoot); }
518
+ catch (error) { observe('tc-release-unreadable', { seat: 'TC', error: String(error).slice(0, 200) }); }
519
+ const openVersions = deps.schedules ? new Set(deps.schedules().filter(({ cutAt }) => Number.isFinite(Date.parse(cutAt))
520
+ && Date.parse(cutAt) > now.getTime()).map(({ version }) => version)) : null;
521
+ for (const cell of cells) {
522
+ if (cell.owner !== 'TC' && ((shipped && versionOrder(cell.version, shipped) <= 0) || (openVersions && !openVersions.has(cell.version)))) continue;
523
+ if (cell.status !== 'yellow' && cell.status !== 'red') continue;
524
+ if (!cell.owner || !isSeatId(cell.owner)) continue;
525
+ if (cell.owner !== 'TC') {
526
+ if (cell.status === 'red') candidates.push({ kind: 'other-seat-cell-defect', version: cell.version, id: cell.id, title: cell.title, to: cell.owner });
527
+ continue;
528
+ }
529
+ for (const pr of merged) {
530
+ const id = cell.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
531
+ const reference = new RegExp(`(?<![A-Za-z0-9])${id}(?![A-Za-z0-9])`);
532
+ if (reference.test(pr.title) || reference.test(pr.body ?? '')) {
533
+ if (cell.owner === 'TC' && cell.status === 'yellow') candidates.push({ kind: 'merged-pr-yellow-cell', version: cell.version, id: cell.id, title: cell.title, pr: pr.number, mergedAt: pr.mergedAt! });
534
+ }
535
+ }
536
+ }
537
+ } catch (error) { observe('tc-checklist-unreadable', { seat: 'TC', error: String(error).slice(0, 200) }); }
538
+ for (const pr of prs) {
539
+ if (pr.state !== 'OPEN' || pr.isDraft || pr.reviewDecision !== 'REVIEW_REQUIRED'
540
+ || !pr.paths?.some((path) => path.startsWith('release/public/') || path.startsWith('scripts/public-export') || path.startsWith('src/market/'))) continue;
541
+ const readyAt = pr.readyAt;
542
+ const age = now.getTime() - Date.parse(pr.approvalWaitAt ?? '');
543
+ if (!readyAt || !Number.isFinite(Date.parse(readyAt)) || Date.parse(readyAt) > now.getTime()
544
+ || !Number.isFinite(age) || age < TC_APPROVAL_WAIT_MS) continue;
545
+ candidates.push({ kind: 'publication-pr-approval-wait', pr: pr.number, title: pr.title, readyAt, approvalWaitAt: pr.approvalWaitAt!, paths: pr.paths });
546
+ }
547
+ return candidates;
548
+ }
549
+
360
550
  function deliverStall(message: MessageEnvelope, key: string, root: string): boolean {
361
551
  const store = openMsgStore(join(root, 'msg', 'messages.db'));
362
552
  try {
@@ -411,13 +601,34 @@ function escalateStalls(seat: string, deps: SeatDeps, config: SeatLoopConfig, no
411
601
  }
412
602
 
413
603
  // In shadow mode a shadowed item counts as handled, so a rehearsal day walks the queue like the seat would.
414
- export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean; root?: string } = {}): SeatItem | null {
604
+ export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean; root?: string; history?: SeatDeps['checklistHistory'] } = {}): SeatItem | null {
415
605
  const handled = handledKeys(ledger, opts.shadow === true, opts.root);
416
606
  return [...inputs.requests, ...inputs.checklist].find((item) => {
417
607
  if (alreadyHandled(item, ledger)) {
418
608
  debug.log('seat.loop', 'skip-handled', { id: item.id });
419
609
  return false;
420
610
  }
611
+ if (item.source === 'checklist' && item.version && item.seat && item.asOf && item.evidenceHash !== undefined && opts.history
612
+ && ledger.some((entry) => entry.seat === item.seat && entry.status === 'shadow'
613
+ && entry.item?.source === 'checklist' && entry.item.id === item.id && entry.item.version !== item.version)) {
614
+ // Only an actual move into this version identifies a carried cell; unrelated releases may reuse an id.
615
+ const moves = opts.history(item.id).filter((event) => event.field === 'move'
616
+ && typeof event.from === 'string' && typeof event.to === 'string' && Date.parse(event.at) <= Date.parse(item.asOf!));
617
+ const latest = moves.at(-1);
618
+ if (latest?.to === item.version) {
619
+ const processed = [...ledger].reverse().find((entry) => entry.seat === item.seat
620
+ && entry.status === 'shadow' && !entry.inquiry && entry.action !== 'wait'
621
+ && entry.item?.source === 'checklist' && entry.item.id === item.id);
622
+ if (processed && processed.item && processed.item.version === latest.from && processed.item.title === item.title
623
+ && processed.item.evidenceHash === item.evidenceHash
624
+ && Date.parse(processed.at || processed.ts || '') >= Date.parse(item.asOf) - CHECKLIST_HANDLED_WINDOW_MS
625
+ && Date.parse(processed.at || processed.ts || '') <= Date.parse(item.asOf)
626
+ && Date.parse(processed.at || processed.ts || '') <= Date.parse(latest.at)) {
627
+ debug.log('seat.loop', 'skip-carried', { id: item.id, from: latest.from, version: item.version });
628
+ return false;
629
+ }
630
+ }
631
+ }
421
632
  return eligibleItem(item, ledger, handled, opts.shadow === true);
422
633
  }) ?? null;
423
634
  }
@@ -470,6 +681,29 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
470
681
  const stateRoot = deps.root ?? effectiveInstanceRoot();
471
682
  let turnError: unknown;
472
683
  try {
684
+ if (seat === 'TC') {
685
+ const candidates = await tcCandidates(deps, now);
686
+ for (const candidate of candidates) {
687
+ if (candidate.kind === 'other-seat-cell-defect') {
688
+ const question = `${candidate.version} 체크리스트 ${candidate.id} · ${candidate.title}: ${candidate.to} 칸 결함을 확인해 주세요.`;
689
+ const recorded = recordSeatAskShadow(stateRoot, { from: 'TC', to: candidate.to, itemId: candidate.id, version: candidate.version, question }, now.getTime());
690
+ const inLedger = ledger.some((row) => row.status === 'shadow' && row.candidate?.kind === 'other-seat-cell-defect'
691
+ && row.candidate.version === candidate.version && row.candidate.id === candidate.id && row.candidate.to === candidate.to);
692
+ if (!inLedger) {
693
+ const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: 'shadow', candidate,
694
+ item: { source: 'checklist', kind: 'cell', id: candidate.id, title: candidate.title, text: candidate.title, version: candidate.version, seat: candidate.to },
695
+ action: 'seat-question', inquiry: { to: candidate.to, question } };
696
+ (deps.append ?? defaultAppend)(path, entry);
697
+ }
698
+ if (!inLedger || recorded) observe('ask-shadow', { from: seat, to: candidate.to, itemId: candidate.id });
699
+ continue;
700
+ }
701
+ if (ledger.some((row) => row.status === 'shadow' && JSON.stringify(row.candidate) === JSON.stringify(candidate))) continue;
702
+ const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: 'shadow', candidate };
703
+ (deps.append ?? defaultAppend)(path, entry);
704
+ observe('tc-shadow-candidate', { seat, candidate });
705
+ }
706
+ }
473
707
  if (seat === 'OP' && !seatQuestions(stateRoot, 'OP').some((question) =>
474
708
  !hasSeatAnswer(stateRoot, question)
475
709
  && !handledKeys(ledger, config.mode !== 'on' || (config.questions ?? 'shadow') !== 'on', stateRoot).has(`seat-question::${question.id}`))) {
@@ -526,7 +760,8 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
526
760
  const questionsMode = config.mode === 'on' ? config.questions ?? 'shadow' : 'shadow';
527
761
  const questionHandled = handledKeys(ledger, questionsMode !== 'on', stateRoot);
528
762
  const pendingQuestion = inputs.requests.find((candidate) => candidate.source === 'seat-question' && !questionHandled.has(itemKey(candidate)));
529
- const ordinary = pickNext({ ...inputs, requests: inputs.requests.filter((candidate) => candidate.source !== 'seat-question') }, ledger, { shadow: config.mode === 'shadow', root: stateRoot });
763
+ const ordinary = pickNext({ ...inputs, requests: inputs.requests.filter((candidate) => candidate.source !== 'seat-question') }, ledger,
764
+ { shadow: config.mode === 'shadow', root: stateRoot, history: deps.checklistHistory ?? checklistHistory });
530
765
  const item = pendingQuestion ?? ordinary;
531
766
  const append = deps.append ?? defaultAppend;
532
767
  // RM3 review must-fix: `questions` now has a default, so it can't be the trigger — a shadow seat loop never calls the model on its own.
@@ -600,7 +835,7 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
600
835
  await run(['decisions', 'raise', '--title', `${seat}: ${ask.question}`, '--category', 'other',
601
836
  '--s', `${seat} 질문 · ${item.title}`, '--c', '판단 근거 부족 — 사람의 결정이 필요하다',
602
837
  '--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
603
- '--skip-recommend', '근거가 부족하다', '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
838
+ '--skip-recommend', '근거가 부족하다', '--no-xcheck', '자리 루프 · 이웃 교환은 DEC-XCHECK ②', '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
604
839
  }
605
840
  if (!decisionReceipt(root, seat, item)) throw new Error('decisions raise did not deliver a matching card');
606
841
  entry.status = 'hitl';
@@ -705,7 +940,7 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
705
940
  const raised = await run(['decisions', 'raise', '--title', title, '--category', 'other',
706
941
  '--s', `${seat} 배정 항목: ${action.text.replace(/[\r\n]+/g, ' ')}\n역할: ${inputs.role}`, '--c', '자동 실행 금지 문면에 해당하여 사람 결정이 필요하다',
707
942
  '--option', 'a=사람 승인:승인 후 별도로 집행', '--option', 'b=보류:집행하지 않음',
708
- '--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--agent', 'seat-loop', '--json']);
943
+ '--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--no-xcheck', '자리 루프 · 이웃 교환은 DEC-XCHECK ②', '--agent', 'seat-loop', '--json']);
709
944
  const decision: unknown = JSON.parse(raised.trim());
710
945
  if (!decision || typeof decision !== 'object' || typeof (decision as { id?: unknown }).id !== 'string') {
711
946
  throw new Error('decisions raise returned no decision id');
@@ -0,0 +1,127 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, relative, resolve, sep } from 'node:path';
4
+ import { userInfo } from 'node:os';
5
+ import { DEFAULT_CONCURRENT_PODS, DEFAULT_DAILY_GOALS } from '../org/seat-budget.js';
6
+
7
+ export interface SeatPackManifest {
8
+ name: string;
9
+ version: string;
10
+ files: Array<{ path: string; sha256: string }>;
11
+ }
12
+
13
+ export class SeatPackLeakError extends Error {
14
+ constructor(file: string, line: number, marker: string) {
15
+ super(`${file}:${line}: public leak (${marker})`);
16
+ }
17
+ }
18
+
19
+ function regularFile(path: string): boolean {
20
+ return lstatSync(path).isFile();
21
+ }
22
+
23
+ function walk(directory: string, root: string): string[] {
24
+ if (!existsSync(directory)) return [];
25
+ if (!lstatSync(directory).isDirectory()) throw new Error(`not a directory: ${relative(root, directory)}`);
26
+ return readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))
27
+ .flatMap((entry) => {
28
+ const path = join(directory, entry.name);
29
+ if (entry.isSymbolicLink()) throw new Error(`symlink in seat pack: ${relative(root, path)}`);
30
+ return entry.isDirectory() ? walk(path, root) : entry.isFile() ? [relative(root, path)] : [];
31
+ });
32
+ }
33
+
34
+ function scan(file: string, body: string): void {
35
+ const usernames = new Set([userInfo().username, process.env.ELANOUS_LEAK_HOME_USER].filter((name): name is string => Boolean(name)));
36
+ const accountPatterns = [...usernames].map((name) => new RegExp(`(?<![A-Za-z0-9._-])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9._-])`, 'iu'));
37
+ const patterns: Array<[string, RegExp]> = [
38
+ ['royal marker', /\u{1F451}/u],
39
+ ['track marker', /(?:🅞|🅣|🅢|🅕|\*\*\[(?:OP|TC|MK|UX|S|T|O|F)\]\*\*|\[(?:OP|TC|MK|UX|S|T|O|F)\])/u],
40
+ ['absolute user path', /\/Users\/[A-Za-z0-9._-]+/u],
41
+ ...accountPatterns.map((pattern): [string, RegExp] => ['account name', pattern]),
42
+ ];
43
+ for (const [index, line] of body.split(/\r?\n/u).entries()) {
44
+ for (const [marker, pattern] of patterns) {
45
+ if (pattern.test(line)) throw new SeatPackLeakError(file, index + 1, marker);
46
+ }
47
+ }
48
+ }
49
+
50
+ /** Build from charters, explicitly referenced seat graphs and the seat's own `graphs/<seat>/` folder; never copy an entire shared graph directory by default. */
51
+ export function exportSeatPack(seat: string, out: string, root = resolve(import.meta.dir, '../..')): SeatPackManifest {
52
+ if (!/^[A-Za-z][A-Za-z0-9_-]*$/u.test(seat)) throw new Error(`invalid seat: ${seat}`);
53
+ const repo = resolve(root);
54
+ const output = resolve(out);
55
+ const roles = join(repo, 'docs', 'roles');
56
+ const charter = join(roles, `${seat}.md`);
57
+ if (!existsSync(charter) || !regularFile(charter)) throw new Error(`seat charter not found: docs/roles/${seat}.md`);
58
+ const roleFiles = [`docs/roles/${seat}.md`, ...walk(join(roles, seat), repo).filter((file) => file.endsWith('.md'))];
59
+ const graphFiles = new Set<string>();
60
+ const contents = new Map<string, Buffer>();
61
+ for (const file of roleFiles) {
62
+ const body = readFileSync(join(repo, file));
63
+ contents.set(file, body);
64
+ for (const match of body.toString('utf8').matchAll(/graphs\/([A-Za-z0-9_./-]+(?:\.ya?ml|\/\*\*))/gu)) {
65
+ const ref = `graphs/${match[1]!}`;
66
+ if (ref.split('/').includes('..')) throw new Error(`invalid seat graph: ${ref}`);
67
+ if (ref.endsWith('/**')) {
68
+ if (!existsSync(join(repo, ref.slice(0, -3)))) throw new Error(`seat graph not found: ${ref}`);
69
+ for (const graph of walk(join(repo, ref.slice(0, -3)), repo)) {
70
+ if (/\.ya?ml$/u.test(graph)) graphFiles.add(graph);
71
+ }
72
+ } else graphFiles.add(ref);
73
+ }
74
+ }
75
+ // A seat's own graph folder (`graphs/<seat in lower case>/`) belongs to the seat even when the
76
+ // charter no longer mentions it — inclusion must not hinge on a text reference that can be edited away.
77
+ const ownGraphs = join(repo, 'graphs', seat.toLowerCase());
78
+ if (existsSync(ownGraphs)) {
79
+ for (const graph of walk(ownGraphs, repo)) if (/\.ya?ml$/u.test(graph)) graphFiles.add(graph);
80
+ }
81
+ for (const file of graphFiles) {
82
+ const path = join(repo, file);
83
+ if (!existsSync(path) || !regularFile(path)) throw new Error(`seat graph not found: ${file}`);
84
+ contents.set(file, readFileSync(path));
85
+ }
86
+ const verdicts = roleFiles.flatMap((file) => contents.get(file)!.toString('utf8').split(/\r?\n/u)
87
+ .filter((line) => /\|\s*판정선\s*\|/u.test(line)).map((line) => ({ source: file, text: line.split('|').slice(2, -1).join('|').trim() })));
88
+ for (const file of roleFiles) {
89
+ const charter = contents.get(file)!.toString('utf8');
90
+ for (const match of charter.matchAll(/(?:^|\n)\s*[-*]\s*\*\*(?:관문|판정선)\*\*\s*[—:-]\s*([^\n]+)/gu)) {
91
+ verdicts.push({ source: file, text: match[1]!.trim() });
92
+ }
93
+ let inGateSection = false;
94
+ for (const line of charter.split(/\r?\n/u)) {
95
+ if (/^##\s+\d+\.\s+관문(?:\s|\(|$)/u.test(line)) { inGateSection = true; continue; }
96
+ if (/^##\s/u.test(line)) inGateSection = false;
97
+ if (!inGateSection) continue;
98
+ const bullet = line.match(/^\s*[-*]\s+(.+)$/u);
99
+ if (bullet) verdicts.push({ source: file, text: bullet[1]!.trim() });
100
+ }
101
+ }
102
+ if (verdicts.length === 0) throw new Error(`seat verdict not found for ${seat}`);
103
+ contents.set('verdicts.json', Buffer.from(`${JSON.stringify(verdicts, null, 2)}\n`));
104
+ contents.set('budget.json', Buffer.from(`${JSON.stringify({ dailyGoals: DEFAULT_DAILY_GOALS, concurrentPods: DEFAULT_CONCURRENT_PODS }, null, 2)}\n`));
105
+ const version = (JSON.parse(readFileSync(join(repo, 'package.json'), 'utf8')) as { version: string }).version;
106
+ const files = [...contents].sort(([a], [b]) => a.localeCompare(b)).map(([path, body]) => ({
107
+ path, sha256: createHash('sha256').update(body).digest('hex'),
108
+ }));
109
+ const manifest: SeatPackManifest = { name: seat, version, files };
110
+ const manifestBody = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
111
+ for (const [file, body] of [...contents, ['manifest.json', manifestBody] as [string, Buffer]]) scan(file, body.toString('utf8'));
112
+ if (existsSync(output)) {
113
+ if (!lstatSync(output).isDirectory() || readdirSync(output).length > 0) throw new Error(`output must be an empty directory: ${output}`);
114
+ }
115
+ const sources = [roles, join(repo, 'graphs')];
116
+ if (output === repo || sources.some((source) => output === source || source.startsWith(`${output}${sep}`) || output.startsWith(`${source}${sep}`))) {
117
+ throw new Error('output overlaps seat pack sources');
118
+ }
119
+ mkdirSync(output, { recursive: true });
120
+ for (const [file, body] of contents) {
121
+ const dest = join(output, file);
122
+ mkdirSync(dirname(dest), { recursive: true });
123
+ writeFileSync(dest, body);
124
+ }
125
+ writeFileSync(join(output, 'manifest.json'), manifestBody);
126
+ return manifest;
127
+ }
@@ -158,6 +158,8 @@ export interface AskLaunchFlowDeps {
158
158
  /** ⛔ 못 얻으면 `null` — 같은 이유. */
159
159
  recentAuthoringSamples(): RecentAuthoringSample[] | null;
160
160
  authorGoal(args: readonly string[], options: Record<string, unknown>): Promise<AskAuthoredGoal>;
161
+ /** Pod launches reserve a host slot before goal authoring begins. */
162
+ beforeAuthoring?(): Promise<void>;
161
163
  /** 발사 전 권고용 분해 seam. 생략하면 전용 관측명을 준 기존 분해기를 쓴다. */
162
164
  decomposeGoal?(goal: string): Promise<SelfDevDecomposition>;
163
165
  /**
@@ -825,6 +827,7 @@ export async function runAskLaunchFlow(
825
827
  const launchPreflight = await runPrePreflight(input, deps);
826
828
  if (launchPreflight === false) return { kind: 'stopped-before-authoring' };
827
829
 
830
+ await deps.beforeAuthoring?.();
828
831
  const authorAndPreflight = async (): Promise<{
829
832
  readonly goalFile: string;
830
833
  readonly decision: ReturnType<typeof decideAskPreflight>;