wicked-crew 0.7.39 → 0.7.45

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 (380) hide show
  1. package/dist/api/audit.d.ts +3 -0
  2. package/dist/api/audit.d.ts.map +1 -1
  3. package/dist/api/audit.js +2 -0
  4. package/dist/api/audit.js.map +1 -1
  5. package/dist/api/capture.d.ts +149 -0
  6. package/dist/api/capture.d.ts.map +1 -0
  7. package/dist/api/capture.js +330 -0
  8. package/dist/api/capture.js.map +1 -0
  9. package/dist/api/chat-citations.d.ts +174 -0
  10. package/dist/api/chat-citations.d.ts.map +1 -0
  11. package/dist/api/chat-citations.js +528 -0
  12. package/dist/api/chat-citations.js.map +1 -0
  13. package/dist/api/chat-scope.d.ts +37 -1
  14. package/dist/api/chat-scope.d.ts.map +1 -1
  15. package/dist/api/chat-scope.js +131 -7
  16. package/dist/api/chat-scope.js.map +1 -1
  17. package/dist/api/chat-transcripts.d.ts +20 -1
  18. package/dist/api/chat-transcripts.d.ts.map +1 -1
  19. package/dist/api/chat-transcripts.js +29 -0
  20. package/dist/api/chat-transcripts.js.map +1 -1
  21. package/dist/api/delivery-cache.d.ts +2 -2
  22. package/dist/api/delivery-freeze.d.ts +66 -0
  23. package/dist/api/delivery-freeze.d.ts.map +1 -0
  24. package/dist/api/delivery-freeze.js +104 -0
  25. package/dist/api/delivery-freeze.js.map +1 -0
  26. package/dist/api/delivery-index.d.ts +53 -9
  27. package/dist/api/delivery-index.d.ts.map +1 -1
  28. package/dist/api/delivery-index.js +115 -22
  29. package/dist/api/delivery-index.js.map +1 -1
  30. package/dist/api/demo.d.ts +90 -0
  31. package/dist/api/demo.d.ts.map +1 -0
  32. package/dist/api/demo.js +492 -0
  33. package/dist/api/demo.js.map +1 -0
  34. package/dist/api/diagnostics.d.ts +64 -1
  35. package/dist/api/diagnostics.d.ts.map +1 -1
  36. package/dist/api/diagnostics.js +129 -1
  37. package/dist/api/diagnostics.js.map +1 -1
  38. package/dist/api/eval-sample.d.ts +6 -6
  39. package/dist/api/governance-steering.d.ts +2 -2
  40. package/dist/api/mcp.d.ts +282 -0
  41. package/dist/api/mcp.d.ts.map +1 -0
  42. package/dist/api/mcp.js +521 -0
  43. package/dist/api/mcp.js.map +1 -0
  44. package/dist/api/plan-schema.d.ts +56 -0
  45. package/dist/api/plan-schema.d.ts.map +1 -0
  46. package/dist/api/plan-schema.js +26 -0
  47. package/dist/api/plan-schema.js.map +1 -0
  48. package/dist/api/roster-standing.d.ts +15 -2
  49. package/dist/api/roster-standing.d.ts.map +1 -1
  50. package/dist/api/roster-standing.js +55 -3
  51. package/dist/api/roster-standing.js.map +1 -1
  52. package/dist/api/routes.d.ts +647 -59
  53. package/dist/api/routes.d.ts.map +1 -1
  54. package/dist/api/routes.js +906 -146
  55. package/dist/api/routes.js.map +1 -1
  56. package/dist/api/run-files.d.ts.map +1 -1
  57. package/dist/api/run-files.js +31 -2
  58. package/dist/api/run-files.js.map +1 -1
  59. package/dist/api/run-liveness.d.ts +41 -0
  60. package/dist/api/run-liveness.d.ts.map +1 -0
  61. package/dist/api/run-liveness.js +166 -0
  62. package/dist/api/run-liveness.js.map +1 -0
  63. package/dist/api/run-timing-index.d.ts +10 -0
  64. package/dist/api/run-timing-index.d.ts.map +1 -1
  65. package/dist/api/run-timing-index.js +24 -0
  66. package/dist/api/run-timing-index.js.map +1 -1
  67. package/dist/api/seat-health.d.ts.map +1 -1
  68. package/dist/api/seat-health.js +20 -4
  69. package/dist/api/seat-health.js.map +1 -1
  70. package/dist/api/seat-probe.d.ts +141 -0
  71. package/dist/api/seat-probe.d.ts.map +1 -0
  72. package/dist/api/seat-probe.js +257 -0
  73. package/dist/api/seat-probe.js.map +1 -0
  74. package/dist/api/seat-record.d.ts +40 -0
  75. package/dist/api/seat-record.d.ts.map +1 -0
  76. package/dist/api/seat-record.js +218 -0
  77. package/dist/api/seat-record.js.map +1 -0
  78. package/dist/api/seat-signin.d.ts.map +1 -1
  79. package/dist/api/seat-signin.js +3 -1
  80. package/dist/api/seat-signin.js.map +1 -1
  81. package/dist/api/seat-standing.d.ts +24 -4
  82. package/dist/api/seat-standing.d.ts.map +1 -1
  83. package/dist/api/seat-standing.js +50 -3
  84. package/dist/api/seat-standing.js.map +1 -1
  85. package/dist/api/server.d.ts +34 -48
  86. package/dist/api/server.d.ts.map +1 -1
  87. package/dist/api/server.js +316 -116
  88. package/dist/api/server.js.map +1 -1
  89. package/dist/api/skills.d.ts +3 -1
  90. package/dist/api/skills.d.ts.map +1 -1
  91. package/dist/api/skills.js +7 -2
  92. package/dist/api/skills.js.map +1 -1
  93. package/dist/api/stall-watchdog.d.ts +34 -3
  94. package/dist/api/stall-watchdog.d.ts.map +1 -1
  95. package/dist/api/stall-watchdog.js +107 -8
  96. package/dist/api/stall-watchdog.js.map +1 -1
  97. package/dist/api/steering-landing.d.ts +6 -6
  98. package/dist/api/steering-landing.d.ts.map +1 -1
  99. package/dist/api/steering-landing.js +7 -9
  100. package/dist/api/steering-landing.js.map +1 -1
  101. package/dist/api/testing.d.ts +6 -4
  102. package/dist/api/testing.d.ts.map +1 -1
  103. package/dist/api/testing.js +82 -47
  104. package/dist/api/testing.js.map +1 -1
  105. package/dist/campaign/boot-resume.d.ts +34 -0
  106. package/dist/campaign/boot-resume.d.ts.map +1 -0
  107. package/dist/campaign/boot-resume.js +45 -0
  108. package/dist/campaign/boot-resume.js.map +1 -0
  109. package/dist/campaigns/rollup.d.ts +4 -1
  110. package/dist/campaigns/rollup.d.ts.map +1 -1
  111. package/dist/campaigns/rollup.js +6 -1
  112. package/dist/campaigns/rollup.js.map +1 -1
  113. package/dist/campaigns/routes.d.ts +2 -2
  114. package/dist/cli/bearer.d.ts +27 -1
  115. package/dist/cli/bearer.d.ts.map +1 -1
  116. package/dist/cli/bearer.js +62 -2
  117. package/dist/cli/bearer.js.map +1 -1
  118. package/dist/cli/governance.d.ts +11 -0
  119. package/dist/cli/governance.d.ts.map +1 -1
  120. package/dist/cli/governance.js +8 -2
  121. package/dist/cli/governance.js.map +1 -1
  122. package/dist/cli/index.d.ts.map +1 -1
  123. package/dist/cli/index.js +140 -75
  124. package/dist/cli/index.js.map +1 -1
  125. package/dist/cli/mcp.d.ts +11 -0
  126. package/dist/cli/mcp.d.ts.map +1 -1
  127. package/dist/cli/mcp.js +40 -9
  128. package/dist/cli/mcp.js.map +1 -1
  129. package/dist/cli/service.d.ts +78 -0
  130. package/dist/cli/service.d.ts.map +1 -0
  131. package/dist/cli/service.js +334 -0
  132. package/dist/cli/service.js.map +1 -0
  133. package/dist/core/adapter.d.ts +247 -14
  134. package/dist/core/adapter.d.ts.map +1 -1
  135. package/dist/core/adapter.js +638 -161
  136. package/dist/core/adapter.js.map +1 -1
  137. package/dist/core/bridge-reaper.d.ts +4 -1
  138. package/dist/core/bridge-reaper.d.ts.map +1 -1
  139. package/dist/core/bridge-reaper.js +15 -1
  140. package/dist/core/bridge-reaper.js.map +1 -1
  141. package/dist/core/bus-notice.d.ts +27 -0
  142. package/dist/core/bus-notice.d.ts.map +1 -0
  143. package/dist/core/bus-notice.js +24 -0
  144. package/dist/core/bus-notice.js.map +1 -0
  145. package/dist/core/bus.d.ts +99 -0
  146. package/dist/core/bus.d.ts.map +1 -0
  147. package/dist/core/bus.js +176 -0
  148. package/dist/core/bus.js.map +1 -0
  149. package/dist/core/deliver-exclusions.d.ts +71 -0
  150. package/dist/core/deliver-exclusions.d.ts.map +1 -0
  151. package/dist/core/deliver-exclusions.js +110 -0
  152. package/dist/core/deliver-exclusions.js.map +1 -0
  153. package/dist/core/deliver-text.d.ts +61 -14
  154. package/dist/core/deliver-text.d.ts.map +1 -1
  155. package/dist/core/deliver-text.js +178 -30
  156. package/dist/core/deliver-text.js.map +1 -1
  157. package/dist/core/deliver-triage.d.ts +11 -2
  158. package/dist/core/deliver-triage.d.ts.map +1 -1
  159. package/dist/core/deliver-triage.js +19 -1
  160. package/dist/core/deliver-triage.js.map +1 -1
  161. package/dist/core/deliver.d.ts +161 -3
  162. package/dist/core/deliver.d.ts.map +1 -1
  163. package/dist/core/deliver.js +463 -25
  164. package/dist/core/deliver.js.map +1 -1
  165. package/dist/core/deliverable-floor.d.ts.map +1 -1
  166. package/dist/core/deliverable-floor.js +5 -7
  167. package/dist/core/deliverable-floor.js.map +1 -1
  168. package/dist/core/engine-bus.d.ts +40 -0
  169. package/dist/core/engine-bus.d.ts.map +1 -0
  170. package/dist/core/engine-bus.js +27 -0
  171. package/dist/core/engine-bus.js.map +1 -0
  172. package/dist/core/estate-mcp-client.d.ts +87 -5
  173. package/dist/core/estate-mcp-client.d.ts.map +1 -1
  174. package/dist/core/estate-mcp-client.js +90 -11
  175. package/dist/core/estate-mcp-client.js.map +1 -1
  176. package/dist/core/linked-issues.d.ts +46 -0
  177. package/dist/core/linked-issues.d.ts.map +1 -0
  178. package/dist/core/linked-issues.js +123 -0
  179. package/dist/core/linked-issues.js.map +1 -0
  180. package/dist/core/run-identity.d.ts +100 -0
  181. package/dist/core/run-identity.d.ts.map +1 -0
  182. package/dist/core/run-identity.js +178 -0
  183. package/dist/core/run-identity.js.map +1 -0
  184. package/dist/core/shutdown.d.ts +16 -0
  185. package/dist/core/shutdown.d.ts.map +1 -0
  186. package/dist/core/shutdown.js +39 -0
  187. package/dist/core/shutdown.js.map +1 -0
  188. package/dist/core/types.d.ts +15 -1
  189. package/dist/core/types.d.ts.map +1 -1
  190. package/dist/core/types.js +4 -0
  191. package/dist/core/types.js.map +1 -1
  192. package/dist/interactive/bridge-pool.d.ts +67 -3
  193. package/dist/interactive/bridge-pool.d.ts.map +1 -1
  194. package/dist/interactive/bridge-pool.js +129 -13
  195. package/dist/interactive/bridge-pool.js.map +1 -1
  196. package/dist/interactive/bridge-root.d.ts +9 -0
  197. package/dist/interactive/bridge-root.d.ts.map +1 -1
  198. package/dist/interactive/bridge-root.js +11 -4
  199. package/dist/interactive/bridge-root.js.map +1 -1
  200. package/dist/interactive/chat-events.d.ts +14 -9
  201. package/dist/interactive/chat-events.d.ts.map +1 -1
  202. package/dist/interactive/chat-events.js +55 -57
  203. package/dist/interactive/chat-events.js.map +1 -1
  204. package/dist/interactive/council-outcome.d.ts +1 -0
  205. package/dist/interactive/council-outcome.d.ts.map +1 -1
  206. package/dist/interactive/council-outcome.js +33 -0
  207. package/dist/interactive/council-outcome.js.map +1 -1
  208. package/dist/interactive/draft-events.d.ts +36 -17
  209. package/dist/interactive/draft-events.d.ts.map +1 -1
  210. package/dist/interactive/draft-events.js +121 -92
  211. package/dist/interactive/draft-events.js.map +1 -1
  212. package/dist/interactive/draft-skill.d.ts +59 -0
  213. package/dist/interactive/draft-skill.d.ts.map +1 -1
  214. package/dist/interactive/draft-skill.js +233 -0
  215. package/dist/interactive/draft-skill.js.map +1 -1
  216. package/dist/interactive/edit-events.d.ts +1 -14
  217. package/dist/interactive/edit-events.d.ts.map +1 -1
  218. package/dist/interactive/edit-events.js +37 -70
  219. package/dist/interactive/edit-events.js.map +1 -1
  220. package/dist/interactive/proxy-routes.d.ts +2 -2
  221. package/dist/interactive/proxy-routes.d.ts.map +1 -1
  222. package/dist/interactive/proxy-routes.js +4 -13
  223. package/dist/interactive/proxy-routes.js.map +1 -1
  224. package/dist/interactive/ws-relay.d.ts +9 -10
  225. package/dist/interactive/ws-relay.d.ts.map +1 -1
  226. package/dist/interactive/ws-relay.js +23 -50
  227. package/dist/interactive/ws-relay.js.map +1 -1
  228. package/dist/mcp/broker.d.ts +88 -0
  229. package/dist/mcp/broker.d.ts.map +1 -0
  230. package/dist/mcp/broker.js +307 -0
  231. package/dist/mcp/broker.js.map +1 -0
  232. package/dist/mcp/call-records.d.ts +61 -0
  233. package/dist/mcp/call-records.d.ts.map +1 -0
  234. package/dist/mcp/call-records.js +135 -0
  235. package/dist/mcp/call-records.js.map +1 -0
  236. package/dist/mcp/classify.d.ts +45 -0
  237. package/dist/mcp/classify.d.ts.map +1 -0
  238. package/dist/mcp/classify.js +83 -0
  239. package/dist/mcp/classify.js.map +1 -0
  240. package/dist/mcp/discovery.d.ts +24 -0
  241. package/dist/mcp/discovery.d.ts.map +1 -0
  242. package/dist/mcp/discovery.js +78 -0
  243. package/dist/mcp/discovery.js.map +1 -0
  244. package/dist/mcp/invoke.d.ts +36 -0
  245. package/dist/mcp/invoke.d.ts.map +1 -0
  246. package/dist/mcp/invoke.js +80 -0
  247. package/dist/mcp/invoke.js.map +1 -0
  248. package/dist/mcp/policies.d.ts +110 -0
  249. package/dist/mcp/policies.d.ts.map +1 -0
  250. package/dist/mcp/policies.js +400 -0
  251. package/dist/mcp/policies.js.map +1 -0
  252. package/dist/mcp/probe.d.ts +66 -0
  253. package/dist/mcp/probe.d.ts.map +1 -0
  254. package/dist/mcp/probe.js +116 -0
  255. package/dist/mcp/probe.js.map +1 -0
  256. package/dist/mcp/redact.d.ts +34 -0
  257. package/dist/mcp/redact.d.ts.map +1 -0
  258. package/dist/mcp/redact.js +84 -0
  259. package/dist/mcp/redact.js.map +1 -0
  260. package/dist/mcp/registry-store.d.ts +69 -0
  261. package/dist/mcp/registry-store.d.ts.map +1 -0
  262. package/dist/mcp/registry-store.js +82 -0
  263. package/dist/mcp/registry-store.js.map +1 -0
  264. package/dist/mcp/registry.d.ts +147 -0
  265. package/dist/mcp/registry.d.ts.map +1 -0
  266. package/dist/mcp/registry.js +509 -0
  267. package/dist/mcp/registry.js.map +1 -0
  268. package/dist/mcp/rest.d.ts +111 -0
  269. package/dist/mcp/rest.d.ts.map +1 -0
  270. package/dist/mcp/rest.js +606 -0
  271. package/dist/mcp/rest.js.map +1 -0
  272. package/dist/mcp/secrets.d.ts +66 -0
  273. package/dist/mcp/secrets.d.ts.map +1 -0
  274. package/dist/mcp/secrets.js +180 -0
  275. package/dist/mcp/secrets.js.map +1 -0
  276. package/dist/mcp/tool-list.d.ts +25 -0
  277. package/dist/mcp/tool-list.d.ts.map +1 -0
  278. package/dist/mcp/tool-list.js +62 -0
  279. package/dist/mcp/tool-list.js.map +1 -0
  280. package/dist/mcp/upstream-error.d.ts +12 -0
  281. package/dist/mcp/upstream-error.d.ts.map +1 -0
  282. package/dist/mcp/upstream-error.js +14 -0
  283. package/dist/mcp/upstream-error.js.map +1 -0
  284. package/dist/mcp/usage.d.ts +31 -0
  285. package/dist/mcp/usage.d.ts.map +1 -0
  286. package/dist/mcp/usage.js +159 -0
  287. package/dist/mcp/usage.js.map +1 -0
  288. package/dist/presets/routes.d.ts +53 -0
  289. package/dist/presets/routes.d.ts.map +1 -0
  290. package/dist/presets/routes.js +115 -0
  291. package/dist/presets/routes.js.map +1 -0
  292. package/dist/projects/activity.d.ts +6 -18
  293. package/dist/projects/activity.d.ts.map +1 -1
  294. package/dist/projects/activity.js +17 -60
  295. package/dist/projects/activity.js.map +1 -1
  296. package/dist/projects/events.d.ts +6 -5
  297. package/dist/projects/events.d.ts.map +1 -1
  298. package/dist/projects/events.js +33 -58
  299. package/dist/projects/events.js.map +1 -1
  300. package/dist/projects/graph.d.ts +11 -0
  301. package/dist/projects/graph.d.ts.map +1 -1
  302. package/dist/projects/graph.js +12 -0
  303. package/dist/projects/graph.js.map +1 -1
  304. package/dist/projects/routes.d.ts.map +1 -1
  305. package/dist/projects/routes.js +19 -0
  306. package/dist/projects/routes.js.map +1 -1
  307. package/dist/projects/state-home-preflight.d.ts.map +1 -1
  308. package/dist/projects/state-home-preflight.js +7 -0
  309. package/dist/projects/state-home-preflight.js.map +1 -1
  310. package/dist/projects/state-home-registry.d.ts.map +1 -1
  311. package/dist/projects/state-home-registry.js +9 -1
  312. package/dist/projects/state-home-registry.js.map +1 -1
  313. package/dist/qe/acceptance.d.ts +28 -28
  314. package/dist/qe/acceptance.d.ts.map +1 -1
  315. package/dist/qe/acceptance.js +60 -61
  316. package/dist/qe/acceptance.js.map +1 -1
  317. package/dist/qe/test-sets.d.ts +3 -5
  318. package/dist/qe/test-sets.d.ts.map +1 -1
  319. package/dist/qe/test-sets.js +5 -7
  320. package/dist/qe/test-sets.js.map +1 -1
  321. package/dist/skills/installed.d.ts +51 -0
  322. package/dist/skills/installed.d.ts.map +1 -0
  323. package/dist/skills/installed.js +46 -0
  324. package/dist/skills/installed.js.map +1 -0
  325. package/dist/skills/phase-skill-gaps.d.ts +108 -0
  326. package/dist/skills/phase-skill-gaps.d.ts.map +1 -0
  327. package/dist/skills/phase-skill-gaps.js +199 -0
  328. package/dist/skills/phase-skill-gaps.js.map +1 -0
  329. package/dist/skills/runtime.d.ts +1 -1
  330. package/dist/skills/runtime.d.ts.map +1 -1
  331. package/dist/skills/runtime.js +2 -1
  332. package/dist/skills/runtime.js.map +1 -1
  333. package/dist/standing-orders/evaluator.d.ts +113 -0
  334. package/dist/standing-orders/evaluator.d.ts.map +1 -0
  335. package/dist/standing-orders/evaluator.js +225 -0
  336. package/dist/standing-orders/evaluator.js.map +1 -0
  337. package/dist/standing-orders/history.d.ts +58 -0
  338. package/dist/standing-orders/history.d.ts.map +1 -0
  339. package/dist/standing-orders/history.js +117 -0
  340. package/dist/standing-orders/history.js.map +1 -0
  341. package/dist/standing-orders/parse.d.ts +19 -0
  342. package/dist/standing-orders/parse.d.ts.map +1 -0
  343. package/dist/standing-orders/parse.js +75 -0
  344. package/dist/standing-orders/parse.js.map +1 -0
  345. package/dist/standing-orders/routes.d.ts +180 -0
  346. package/dist/standing-orders/routes.d.ts.map +1 -0
  347. package/dist/standing-orders/routes.js +95 -0
  348. package/dist/standing-orders/routes.js.map +1 -0
  349. package/dist/standing-orders/store.d.ts +154 -0
  350. package/dist/standing-orders/store.d.ts.map +1 -0
  351. package/dist/standing-orders/store.js +189 -0
  352. package/dist/standing-orders/store.js.map +1 -0
  353. package/dist/studio/assets/index-CvV5qITf.css +32 -0
  354. package/dist/studio/assets/index-jCOy6Ns7.js +568 -0
  355. package/dist/studio/index.html +3 -3
  356. package/dist/studio/testid-inventory.json +3307 -897
  357. package/dist/team/key.d.ts +5 -0
  358. package/dist/team/key.d.ts.map +1 -0
  359. package/dist/team/key.js +21 -0
  360. package/dist/team/key.js.map +1 -0
  361. package/dist/team/routes.d.ts +196 -0
  362. package/dist/team/routes.d.ts.map +1 -0
  363. package/dist/team/routes.js +274 -0
  364. package/dist/team/routes.js.map +1 -0
  365. package/dist/team/ws-relay.d.ts +39 -0
  366. package/dist/team/ws-relay.d.ts.map +1 -0
  367. package/dist/team/ws-relay.js +63 -0
  368. package/dist/team/ws-relay.js.map +1 -0
  369. package/endpoint-manifest.json +502 -6
  370. package/package.json +6 -6
  371. package/dist/interactive/demo-events.d.ts +0 -367
  372. package/dist/interactive/demo-events.d.ts.map +0 -1
  373. package/dist/interactive/demo-events.js +0 -1293
  374. package/dist/interactive/demo-events.js.map +0 -1
  375. package/dist/qe/gate-events.d.ts +0 -113
  376. package/dist/qe/gate-events.d.ts.map +0 -1
  377. package/dist/qe/gate-events.js +0 -172
  378. package/dist/qe/gate-events.js.map +0 -1
  379. package/dist/studio/assets/index-BRDAN35i.js +0 -548
  380. package/dist/studio/assets/index-Drfcsy8b.css +0 -32
@@ -2,7 +2,7 @@ import Fastify from 'fastify';
2
2
  import fastifyWebsocket from '@fastify/websocket';
3
3
  import fastifyStatic from '@fastify/static';
4
4
  import { existsSync } from 'node:fs';
5
- import { join } from 'node:path';
5
+ import { join, resolve as resolvePath } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { registerRoutes } from './routes.js';
8
8
  import { ErrorRing, teeStreamWithErrorRing } from './diagnostics.js';
@@ -16,16 +16,17 @@ import { GroupIndex } from './group-index.js';
16
16
  import { RunTimingIndex, recordRunLaunched } from './run-timing-index.js';
17
17
  import { GuidanceIndex } from './guidance-index.js';
18
18
  import { ChatScopeIndex, reapStaleChatNamespaces } from './chat-scope.js';
19
- import { DeliveryIndex, deliverUnitOf, gitRunBranchIsEmpty, gitWorktreeIsClean, prUrlFrom, canDeliverResolver, } from './delivery-index.js';
19
+ import { chatCitationDeps, citationsFrame, verifyCitations, } from './chat-citations.js';
20
+ import { DeliveryIndex, deliverUnitOf, gitRunBranchIsEmpty, gitWorktreeIsClean, deliveryRecordFrom, canDeliverResolver, } from './delivery-index.js';
21
+ import { DeliveryFreeze } from './delivery-freeze.js';
20
22
  import { DeliveryDerivationCache } from './delivery-cache.js';
21
23
  import { coreUnitId } from './evidence.js';
22
24
  import { registerClient, broadcast } from '../events/bus.js';
23
25
  import { TerminalHub, registerTerminalWs } from '../events/terminals.js';
24
- import { QeGateCache, startQeGateSubscriber } from '../qe/gate-events.js';
25
- import { startInteractiveDraftSubscriber } from '../interactive/draft-events.js';
26
- import { startInteractiveEditSubscriber } from '../interactive/edit-events.js';
27
- import { startInteractiveChatSubscriber } from '../interactive/chat-events.js';
28
- import { startInteractiveDemoSubscriber } from '../interactive/demo-events.js';
26
+ import { INTERACTIVE_DRAFT_WORKFLOW_DEF, startInteractiveDraftSubscriber } from '../interactive/draft-events.js';
27
+ import { INTERACTIVE_EDIT_WORKFLOW_DEF, startInteractiveEditSubscriber } from '../interactive/edit-events.js';
28
+ import { INTERACTIVE_CHAT_WORKFLOW_DEF, startInteractiveChatSubscriber } from '../interactive/chat-events.js';
29
+ import { PhaseSkillArming, RunSkillGapIndex } from '../skills/phase-skill-gaps.js';
29
30
  import { resolveProjectInteractiveRoot } from '../interactive/bridge-root.js';
30
31
  import { sweepDocLedgers } from '../interactive/doc-ledger-sweep.js';
31
32
  import { DocRunIndex } from '../interactive/doc-run-index.js';
@@ -33,8 +34,18 @@ import { TestSetIndex, registerTestSetForRun } from '../qe/test-sets.js';
33
34
  import { DocGroundingStore } from '../interactive/doc-grounding.js';
34
35
  import { ProjectSettingsStore } from '../projects/settings.js';
35
36
  import { crewStateHome } from '../projects/state-home.js';
37
+ import { homedir } from 'node:os';
38
+ import { discoverMcpServers } from '../mcp/discovery.js';
39
+ import { probeMcpServer } from '../mcp/probe.js';
40
+ import { budgetFromEnv, MCP_CALL_COMPLETED, McpBroker } from '../mcp/broker.js';
41
+ import { McpCallRecordFile } from '../mcp/call-records.js';
42
+ import { emitOnBus } from '../core/bus.js';
43
+ import { McpRegistry } from '../mcp/registry.js';
44
+ import { McpRegistryStore } from '../mcp/registry-store.js';
45
+ import { platformSecretStore } from '../mcp/secrets.js';
36
46
  import { startProjectBus, MEMBERSHIP_ATTACHED, membershipAttachedKey } from '../projects/events.js';
37
47
  import { startInteractiveWsRelay, registerInteractiveEventRoutes } from '../interactive/ws-relay.js';
48
+ import { startTeamWsRelay } from '../team/ws-relay.js';
38
49
  import { MembershipIndex } from '../projects/membership-index.js';
39
50
  import { writeRunEvidencePointer } from '../projects/charter.js';
40
51
  import { engineBenchesUnclassifiedSeats, CoreAdapter } from '../core/adapter.js';
@@ -47,6 +58,15 @@ import { ChatTranscriptStore } from './chat-transcripts.js';
47
58
  import { sweepDeliveredWorktree } from './worktree-sweep.js';
48
59
  import { installEndpointManifestHook } from './endpoint-manifest.js';
49
60
  import { WorkerStallWatchdog } from './stall-watchdog.js';
61
+ import { RunLivenessSampler } from './run-liveness.js';
62
+ import { resumeRunningCampaigns } from '../campaign/boot-resume.js';
63
+ import { StandingOrderStore } from '../standing-orders/store.js';
64
+ import { StandingOrderEvaluator } from '../standing-orders/evaluator.js';
65
+ import { registerStandingOrderRoutes } from '../standing-orders/routes.js';
66
+ import { seatParser } from '../standing-orders/parse.js';
67
+ import { registerGateHistoryRoute, runBand, runPreset, runProject } from '../standing-orders/history.js';
68
+ import { busRows, openPlanGateRisk } from '../team/routes.js';
69
+ import { isSteeringAuthorRun } from './steering-landing.js';
50
70
  import { applyWorkerConfigRoot } from './seat-signin.js';
51
71
  import { registeredSkillRefs } from '../skills/core-closure.js';
52
72
  import { assertSkillsRootFenced } from '../skills/root-fence.js';
@@ -94,9 +114,11 @@ export async function createServer(adapter, options) {
94
114
  // test); costs one array push per route at boot, nothing per request.
95
115
  app.decorate('endpointManifest', installEndpointManifestHook(app));
96
116
  const gateCache = new GateCache();
117
+ // Standing orders (behaviour 10): armed once the routes (and so THE gate decision path) exist;
118
+ // the event relays above it read it late-bound.
119
+ let standingOrderEvaluator = null;
97
120
  const elicitationCache = new ElicitationCache();
98
121
  const terminals = new TerminalHub();
99
- const qeGateCache = new QeGateCache();
100
122
  // Per-seat runtime health (crew#274): folded from the single CoreEvent subscription below,
101
123
  // surfaced on GET /roster, recovered by the low-frequency probe armed further down.
102
124
  const seatHealth = new SeatHealthTracker({
@@ -300,10 +322,20 @@ export async function createServer(adapter, options) {
300
322
  // trail's `guidance.set` entries so notes survive a daemon restart.
301
323
  const guidanceIndex = new GuidanceIndex();
302
324
  await guidanceIndex.hydrate(audit, (m) => app.log.warn(m));
325
+ // crew#661: each drafting seam's ARM-TIME skill outcome (diagnostics / health), and the runs
326
+ // launched while a seam was unarmed (`session.skill_gaps`) — same durable trail pattern.
327
+ const phaseSkills = new PhaseSkillArming(() => skillsRuntime?.health().current?.gen ?? null);
328
+ const runSkillGaps = new RunSkillGapIndex();
329
+ await runSkillGaps.hydrate(audit, (m) => app.log.warn(m));
330
+ const skillHeld = (name) => skillsRuntime?.holdsSkill(name) ?? false;
303
331
  // Delivered-PR record (CREW-UX-8, crew#321): same durable pattern — hydrated from the
304
332
  // trail's `run.delivered` entries so `session.delivery` survives a daemon restart.
305
333
  const deliveryIndex = new DeliveryIndex();
306
334
  await deliveryIndex.hydrate(audit, (m) => app.log.warn(m));
335
+ // Freeze deliveries (idea 15): same durable pattern — the trail's `deliveries.frozen` /
336
+ // `deliveries.unfrozen` entries, so a restart keeps the freeze on.
337
+ const deliveryFreeze = new DeliveryFreeze();
338
+ await deliveryFreeze.hydrate(audit, (m) => app.log.warn(m));
307
339
  // Wave 6 (F-7R2-014): the test sets `qe-author-tests` runs registered — same durable pattern,
308
340
  // hydrated from the trail's `testing.testset.registered` entries, fed at each terminal frame.
309
341
  const testSets = new TestSetIndex();
@@ -330,7 +362,7 @@ export async function createServer(adapter, options) {
330
362
  const deliveryCache = new DeliveryDerivationCache({
331
363
  listViews: () => adapter.sessionsDetail(),
332
364
  probes: vacuityProbes,
333
- isDelivered: (runId) => deliveryIndex.urlFor(runId) !== undefined,
365
+ isDelivered: (runId) => deliveryIndex.isDelivered(runId),
334
366
  canDeliver,
335
367
  log: (m) => app.log.warn(m),
336
368
  // Non-probe derivation throws are defects — error level, so the diagnostics ring sees them.
@@ -358,7 +390,7 @@ export async function createServer(adapter, options) {
358
390
  try {
359
391
  // Resume/retry re-terminals: already resolved once, and a terminal run's PR URL never
360
392
  // changes — never re-read, never double-write the trail.
361
- if (deliveryIndex.urlFor(runId) !== undefined)
393
+ if (deliveryIndex.isDelivered(runId))
362
394
  return;
363
395
  const views = await adapter.sessionsDetail();
364
396
  const view = views.find((v) => v.session.id === runId);
@@ -373,28 +405,35 @@ export async function createServer(adapter, options) {
373
405
  const output = await adapter.workOutput(coreUnitId(runId, unit));
374
406
  if (output === null)
375
407
  return;
376
- const url = prUrlFrom(output);
377
- if (url === null)
408
+ // A PR URL, or (N1) a push-only delivery to an origin gh cannot resolve to GitHub — a
409
+ // delivery too, so the run reads `delivery: 'pushed'` and never `'stranded'`.
410
+ const record = deliveryRecordFrom(output);
411
+ if (record === null)
378
412
  return;
379
413
  // The durable record first, then the read-side index — the same write order as
380
414
  // `guidance.set`, so the index can only LAG a crash (rehydrated at next boot), never
381
415
  // hold a record the trail does not.
382
- audit.record('run.delivered', DAEMON_ACTOR, {
383
- runId,
384
- detail: { url },
385
- });
386
- deliveryIndex.set(runId, url);
416
+ audit.record('run.delivered', DAEMON_ACTOR, { runId, detail: record });
417
+ if ('url' in record)
418
+ deliveryIndex.set(runId, record.url);
419
+ else
420
+ deliveryIndex.setPushed(runId, record.pushed);
387
421
  }
388
422
  catch (err) {
389
423
  app.log.warn(`[runs] delivery resolution for ${runId} failed (field absent until restart replays the trail): ${err instanceof Error ? err.message : String(err)}`);
390
424
  }
391
425
  };
426
+ // Crew reaches a bus only through the engine that holds it (wicked-core#631, core/bus.ts): a seam
427
+ // with no bus db of its own reads and writes the one this adapter handed its engine.
428
+ const engineBusDb = typeof adapter.busDbPath === 'string' ? adapter.busDbPath : undefined;
429
+ const busOf = (dbPath) => {
430
+ const bus = dbPath ?? engineBusDb;
431
+ return bus !== undefined ? { dbPath: bus } : {};
432
+ };
392
433
  const projectBus = options?.projectEvents?.disabled === true
393
434
  ? null
394
435
  : await startProjectBus({
395
- ...(options?.projectEvents?.dbPath !== undefined
396
- ? { dbPath: options.projectEvents.dbPath }
397
- : {}),
436
+ ...busOf(options?.projectEvents?.dbPath),
398
437
  ...(options?.projectEvents?.pollIntervalMs !== undefined
399
438
  ? { pollIntervalMs: options.projectEvents.pollIntervalMs }
400
439
  : {}),
@@ -415,7 +454,6 @@ export async function createServer(adapter, options) {
415
454
  // is still preferred over its file once armed; the seams themselves arm further down.
416
455
  let draftSub = null;
417
456
  let editSub = null;
418
- let demoSub = null;
419
457
  let chatSub = null;
420
458
  /** The crew-side half of deleting an interactive doc (crew#338): drop the doc's replay-dedup
421
459
  * rows from all four handoff ledgers — the draft leg keys by DOCUMENT ID ("one first draft
@@ -426,11 +464,11 @@ export async function createServer(adapter, options) {
426
464
  * override, crew#353/#398). Never throws — the report says what happened. */
427
465
  const crewStateDir = crewStateHome();
428
466
  // F-046: the create-time doc → subject-repo bindings, shared by the proxy (records) and the
429
- // draft/demo seams (read). The store keeps NO file of its own — each binding is a
467
+ // draft seam (read). The store keeps NO file of its own — each binding is a
430
468
  // `crew-grounding.json` sidecar beside the doc's `versions.json` under the project's docs root
431
469
  // (doc-grounding.ts says why not the state home: core's fence refuses unregistered entries).
432
470
  const docGrounding = new DocGroundingStore();
433
- /** The four seams' ledgers, read AT USE TIME (a seam that armed after this closure was built is
471
+ /** The three seams' ledgers, read AT USE TIME (a seam that armed after this closure was built is
434
472
  * still preferred over its file) — shared by the doc-delete sweep and the doc↔run index. */
435
473
  const docLedgerSources = () => [
436
474
  {
@@ -451,12 +489,6 @@ export async function createServer(adapter, options) {
451
489
  path: options?.interactiveChatEvents?.ledgerPath ??
452
490
  join(crewStateDir, 'interactive-chat-ledger.json'),
453
491
  },
454
- {
455
- name: 'demo',
456
- ledger: demoSub?.ledger,
457
- path: options?.interactiveDemoEvents?.ledgerPath ??
458
- join(crewStateDir, 'interactive-demo-ledger.json'),
459
- },
460
492
  ];
461
493
  const dropDocLedgerRows = (documentId) => sweepDocLedgers(documentId, docLedgerSources());
462
494
  // Wave 6 (F-4R2-006 root fix): the document ↔ run binding as a direct read off the SAME four
@@ -470,9 +502,7 @@ export async function createServer(adapter, options) {
470
502
  const interactiveRelay = options?.interactiveWsRelay?.disabled === true
471
503
  ? null
472
504
  : await startInteractiveWsRelay({
473
- ...(options?.interactiveWsRelay?.dbPath !== undefined
474
- ? { dbPath: options.interactiveWsRelay.dbPath }
475
- : {}),
505
+ ...busOf(options?.interactiveWsRelay?.dbPath),
476
506
  ...(options?.interactiveWsRelay?.pollIntervalMs !== undefined
477
507
  ? { pollIntervalMs: options.interactiveWsRelay.pollIntervalMs }
478
508
  : {}),
@@ -499,6 +529,31 @@ export async function createServer(adapter, options) {
499
529
  await interactiveRelay.stop();
500
530
  });
501
531
  }
532
+ // The team relay (DES-TEAMING-002 §4.5): every team row the engine publishes becomes a
533
+ // `teamEvent` frame on the same /ws socket, tagged with the run's project. Only where the engine
534
+ // has a bus: no bus, no team rows.
535
+ const teamBusDb = engineBusDb;
536
+ const teamRelay = options?.teamWsRelay?.disabled === true || teamBusDb === undefined
537
+ ? null
538
+ : await startTeamWsRelay({
539
+ dbPath: teamBusDb,
540
+ projectOf: (runId) => membershipIndex.projectOf(runId),
541
+ // Standing orders (behaviour 10) read the same team rows: a finding can wake the operator.
542
+ broadcast: (frame) => {
543
+ broadcast(frame);
544
+ void standingOrderEvaluator?.onTeamFrame(frame);
545
+ },
546
+ ...(options?.teamWsRelay?.pollIntervalMs !== undefined
547
+ ? { pollIntervalMs: options.teamWsRelay.pollIntervalMs }
548
+ : {}),
549
+ log: (m) => app.log.warn(m),
550
+ });
551
+ if (teamRelay !== null) {
552
+ app.log.info('team /ws relay armed (team rows → teamEvent)');
553
+ app.addHook('onClose', async () => {
554
+ await teamRelay.stop();
555
+ });
556
+ }
502
557
  /** The post-commit half of a project-FILED launch, shared with the launch route (§2.2/§4):
503
558
  * the engine already attached the crew.run membership atomically with the launch — here we
504
559
  * tag future /ws frames and announce the attach on the project bus. */
@@ -513,34 +568,16 @@ export async function createServer(adapter, options) {
513
568
  /** The docs root a project's interactive docs live under — the SAME per-project resolution
514
569
  * the project routes and the interactive proxy use (DES-MERGE-001 §7.1/§7.2; partitioned
515
570
  * per project since crew#472, with an event that carries no `project_id` belonging to
516
- * Unfiled). Shared by the edit seam (demo-kind gate, CREW-UX-9), the demo seam (spec
517
- * install + manifest reads), and the chat seam. The partition is containment-checked on
571
+ * Unfiled). Shared by the edit seam (demo-kind gate) and the chat seam. The partition is containment-checked on
518
572
  * REAL paths here exactly as the routes check it (crew#474 — one walk, `bridge-root.ts`):
519
573
  * a symlinked `projects/<id>` throws `InteractivePartitionRefusedError` into the seam's
520
574
  * handler (logged by its `onError`, the event unanswered — fail closed) instead of being
521
575
  * followed into another project's docs; the seams only READ under a root the routes
522
576
  * materialized, so a missing partition is returned as spelled and nothing is created. */
523
577
  const interactiveDocsRoot = (projectId) => resolveProjectInteractiveRoot(projectId, projectId !== undefined ? projectSettings.get(projectId) : null);
524
- // Arm the opt-in QE gate-event subscription (crew's bus seam). Failure to
525
- // arm is LOUD but non-fatal: the acceptance route never depends on the bus.
526
- if (options?.qeGateEvents?.enabled === true) {
527
- const { dbPath, pollIntervalMs } = options.qeGateEvents;
528
- const sub = await startQeGateSubscriber(qeGateCache, {
529
- ...(dbPath !== undefined ? { dbPath } : {}),
530
- ...(pollIntervalMs !== undefined ? { pollIntervalMs } : {}),
531
- log: (m) => app.log.warn(m),
532
- logError: (m) => app.log.error(m),
533
- });
534
- if (sub !== null) {
535
- app.log.info(`qe gate-event subscription armed (filter wicked.qe.**)`);
536
- app.addHook('onClose', async () => {
537
- await sub.stop();
538
- });
539
- }
540
- }
541
578
  // ── A STUB ENGINE NEVER ANSWERS ANOTHER PRODUCT'S TRAFFIC (crew#309) ────────────────────────
542
579
  //
543
- // The four seams below are ANSWERERS: each opens a durable wicked-bus subscription and replies
580
+ // The four seams below are ANSWERERS: each taps the bus (core/bus.ts) and replies
544
581
  // to wicked-interactive's events by LAUNCHING A GOVERNED RUN. Under `serve --stub` the engine is
545
582
  // `Core.spawnStub` — a `StubDispatcher` (every seat votes for the first roster option, no
546
583
  // subprocess) plus a `StubStepRunner` (fixed text, no CLI) — so such a run resolves every phase
@@ -549,7 +586,8 @@ export async function createServer(adapter, options) {
549
586
  // approved outline — moving on…", "Gate approved the draft…". Nothing ran. Nothing was written.
550
587
  // Two gate approvals were announced anyway.
551
588
  //
552
- // Worse than fabricating locally: the durable cursor is keyed by PLUGIN NAME
589
+ // Worse than fabricating locally (until crew#679 moved the seams to in-memory taps): the durable
590
+ // cursor was keyed by PLUGIN NAME
553
591
  // (`wicked-crew-interactive-draft`), so on the shared bus a stub daemon and the production
554
592
  // daemon are ONE consumer group — a frame claimed by the stub is a frame the real daemon never
555
593
  // sees. That is what wicked-crew#309 recorded: bus rows 242632–242643 carry a complete draft
@@ -560,8 +598,8 @@ export async function createServer(adapter, options) {
560
598
  // convening, which is the StubDispatcher's signature; the real runs 5 minutes later took ~95s to
561
599
  // vote and split their seats (`pi` for outline, `claude` for draft).
562
600
  //
563
- // So: refuse to arm, loudly. The deny is scoped to the ANSWERERS on purpose — the QE seam above
564
- // only fills a freshness cache and the project seam only relays, neither launches work nor
601
+ // So: refuse to arm, loudly. The deny is scoped to the ANSWERERS on purpose — the project seam
602
+ // only relays, neither launches work nor
565
603
  // narrates governance, so neither can fabricate a verdict. Offline/deterministic runs are NOT
566
604
  // lost by this: every harness in `e2e/` already does the correct thing, keeping the REAL engine
567
605
  // (`stub: false`) and registering a scripted stub SEAT in the roster — which exercises planning,
@@ -586,7 +624,7 @@ export async function createServer(adapter, options) {
586
624
  if (options?.interactiveDraftEvents?.enabled === true && !refuseStubSeam('interactive-draft')) {
587
625
  const o = options.interactiveDraftEvents;
588
626
  draftSub = await startInteractiveDraftSubscriber(adapter, {
589
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
627
+ ...busOf(o.dbPath),
590
628
  ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
591
629
  ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
592
630
  ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
@@ -594,8 +632,9 @@ export async function createServer(adapter, options) {
594
632
  ...(o.clisJson !== undefined ? { clisJson: o.clisJson } : {}),
595
633
  // The roster WITH standing when no override is set (F-RECON-002/003).
596
634
  roster: rosterWithStanding,
597
- // The quality-floor skill gate (draft-skill.ts): stamped only when the published snapshot holds it.
598
- skillHeld: (name) => skillsRuntime?.holdsSkill(name) ?? false,
635
+ // The quality-floor skill gate (draft-skill.ts): stamped only when the published snapshot holds it;
636
+ // the arm-time answer is recorded for /diagnostics (crew#661).
637
+ skillHeld: phaseSkills.probe('interactive-draft', INTERACTIVE_DRAFT_WORKFLOW_DEF, skillHeld),
599
638
  onRunFiled: fileRun,
600
639
  onRunLaunched: (runId, detail) => { recordRunLaunched(audit, runTimingIndex, DAEMON_ACTOR, runId, detail); },
601
640
  // F-046: the create-time grounding sidecar is read under the SAME per-project docs root the
@@ -605,6 +644,9 @@ export async function createServer(adapter, options) {
605
644
  log: (m) => app.log.warn(m),
606
645
  logError: (m) => app.log.error(m),
607
646
  });
647
+ // crew#661: a seam that failed to arm runs nothing — its arm-time skill answer is not a gap.
648
+ if (draftSub === null)
649
+ phaseSkills.forget('interactive-draft');
608
650
  if (draftSub !== null) {
609
651
  const sub = draftSub;
610
652
  app.log.info('interactive-draft subscription armed (filter wicked.interactive.doc.created)');
@@ -616,14 +658,10 @@ export async function createServer(adapter, options) {
616
658
  // Arm the opt-in interactive STRUCTURAL-edit answering seam (task #86 final leg). Same
617
659
  // posture as the draft seam: failure to arm is LOUD but non-fatal — interactive's assist
618
660
  // loop is the fallback answerer, and this daemon must boot on a machine whose bus is broken.
619
- // `demoSub` (declared above) is deliberately still null here: the demo seam arms after (it
620
- // must, so the per-doc serialization contract can see the edit seam's in-flight set), but the
621
- // edit seam's demo-kind gate needs to know at EVENT time whether the demo seam actually came
622
- // up — a closure over the binding reads the settled value; a boolean captured here would not.
623
661
  if (options?.interactiveEditEvents?.enabled === true && !refuseStubSeam('interactive-edit')) {
624
662
  const o = options.interactiveEditEvents;
625
663
  editSub = await startInteractiveEditSubscriber(adapter, {
626
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
664
+ ...busOf(o.dbPath),
627
665
  ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
628
666
  ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
629
667
  ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
@@ -631,17 +669,19 @@ export async function createServer(adapter, options) {
631
669
  ...(o.clisJson !== undefined ? { clisJson: o.clisJson } : {}),
632
670
  // The roster WITH standing when no override is set (F-RECON-002/003).
633
671
  roster: rosterWithStanding,
634
- // The quality-floor skill gate (draft-skill.ts): stamped only when the published snapshot holds it.
635
- skillHeld: (name) => skillsRuntime?.holdsSkill(name) ?? false,
636
- // The demo-kind gate (CREW-UX-9): a demo doc's step feedback is the demo seam's — but
637
- // only when that seam is actually up. Probed per event (the demo seam arms below), so an
638
- // un-armed demo seam gets an honest error status instead of a silent, unanswerable drop.
672
+ // The quality-floor skill gate (draft-skill.ts): stamped only when the published snapshot holds it;
673
+ // the arm-time answer is recorded for /diagnostics (crew#661).
674
+ skillHeld: phaseSkills.probe('interactive-edit', INTERACTIVE_EDIT_WORKFLOW_DEF, skillHeld),
675
+ // The demo-kind gate: a demo doc's step feedback is declined with an honest error status
676
+ // (demos are made by the Demo experience's `demo` preset now, studio#373).
639
677
  resolveDocsRoot: o.resolveDocsRoot ?? interactiveDocsRoot,
640
- demoSeamArmed: () => demoSub !== null,
641
678
  onRunFiled: fileRun,
642
679
  log: (m) => app.log.warn(m),
643
680
  logError: (m) => app.log.error(m),
644
681
  });
682
+ // crew#661: a seam that failed to arm runs nothing — its arm-time skill answer is not a gap.
683
+ if (editSub === null)
684
+ phaseSkills.forget('interactive-edit');
645
685
  if (editSub !== null) {
646
686
  const sub = editSub;
647
687
  app.log.info('interactive-edit subscription armed (filter wicked.interactive.feedback.processed)');
@@ -650,37 +690,6 @@ export async function createServer(adapter, options) {
650
690
  });
651
691
  }
652
692
  }
653
- // Arm the opt-in interactive DEMO answering seam (CREW-UX-9 — the retired assist agent's
654
- // Step 8). Same posture as the sibling seams: failure to arm is LOUD but non-fatal — a demo
655
- // doc then keeps its placeholder, and this daemon must boot on a machine whose bus is
656
- // broken. Armed beside the edit seam because the two split `feedback.processed` by the doc
657
- // manifest's kind (demo → re-author the spec here; everything else → structural edit there).
658
- if (options?.interactiveDemoEvents?.enabled === true && !refuseStubSeam('interactive-demo')) {
659
- const o = options.interactiveDemoEvents;
660
- demoSub = await startInteractiveDemoSubscriber(adapter, {
661
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
662
- ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
663
- ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
664
- ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
665
- ...(o.demoDir !== undefined ? { demoDir: o.demoDir } : {}),
666
- ...(o.clisJson !== undefined ? { clisJson: o.clisJson } : {}),
667
- // The roster WITH standing when no override is set (F-RECON-002/003).
668
- roster: rosterWithStanding,
669
- resolveDocsRoot: o.resolveDocsRoot ?? interactiveDocsRoot,
670
- onRunFiled: fileRun,
671
- onRunLaunched: (runId, detail) => { recordRunLaunched(audit, runTimingIndex, DAEMON_ACTOR, runId, detail); },
672
- groundingStore: docGrounding,
673
- log: (m) => app.log.warn(m),
674
- logError: (m) => app.log.error(m),
675
- });
676
- if (demoSub !== null) {
677
- const sub = demoSub;
678
- app.log.info('interactive-demo subscription armed (filters wicked.interactive.doc.created + feedback.processed, kind:demo)');
679
- app.addHook('onClose', async () => {
680
- await sub.stop();
681
- });
682
- }
683
- }
684
693
  // Arm the opt-in interactive CHAT answering seam (CREW-UX-5 — the iteration ask). Same
685
694
  // posture again: failure to arm is LOUD but non-fatal. Armed AFTER the sibling seams so the
686
695
  // per-doc serialization contract (no draft/edit/chat run races another on one doc) can
@@ -689,7 +698,7 @@ export async function createServer(adapter, options) {
689
698
  if (options?.interactiveChatEvents?.enabled === true && !refuseStubSeam('interactive-chat')) {
690
699
  const o = options.interactiveChatEvents;
691
700
  chatSub = await startInteractiveChatSubscriber(adapter, {
692
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
701
+ ...busOf(o.dbPath),
693
702
  ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
694
703
  ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
695
704
  ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
@@ -697,8 +706,9 @@ export async function createServer(adapter, options) {
697
706
  ...(o.clisJson !== undefined ? { clisJson: o.clisJson } : {}),
698
707
  // The roster WITH standing when no override is set (F-RECON-002/003).
699
708
  roster: rosterWithStanding,
700
- // The quality-floor skill gate (draft-skill.ts): stamped only when the published snapshot holds it.
701
- skillHeld: (name) => skillsRuntime?.holdsSkill(name) ?? false,
709
+ // The quality-floor skill gate (draft-skill.ts): stamped only when the published snapshot holds it;
710
+ // the arm-time answer is recorded for /diagnostics (crew#661).
711
+ skillHeld: phaseSkills.probe('interactive-chat', INTERACTIVE_CHAT_WORKFLOW_DEF, skillHeld),
702
712
  ...(o.queueSweepMs !== undefined ? { queueSweepMs: o.queueSweepMs } : {}),
703
713
  ...(o.landingGateMs !== undefined ? { landingGateMs: o.landingGateMs } : {}),
704
714
  resolveDocsRoot: o.resolveDocsRoot ?? interactiveDocsRoot,
@@ -708,6 +718,9 @@ export async function createServer(adapter, options) {
708
718
  log: (m) => app.log.warn(m),
709
719
  logError: (m) => app.log.error(m),
710
720
  });
721
+ // crew#661: a seam that failed to arm runs nothing — its arm-time skill answer is not a gap.
722
+ if (chatSub === null)
723
+ phaseSkills.forget('interactive-chat');
711
724
  if (chatSub !== null) {
712
725
  const sub = chatSub;
713
726
  app.log.info('interactive-chat subscription armed (filter wicked.interactive.chat.posted)');
@@ -734,14 +747,23 @@ export async function createServer(adapter, options) {
734
747
  // candidates. Views with no units (older engines, stub adapters) keep the historical
735
748
  // `unit_ix` fallback.
736
749
  const cursor = resolveCursorUnit(v);
737
- // DES-L3 PR-3E (F-RC1-012): an EVALUATOR cursor must never fail over onto a seat
738
- // that built the work it reviews — the creators' seats ride as `avoid`. A free-text
739
- // unit carries no role → no constraint (today's pick).
740
- const avoid = cursor?.role === 'evaluator'
741
- ? v.units
742
- .filter((u) => u.role === 'creator' && u.assigned_cli != null)
743
- .map((u) => u.assigned_cli)
744
- : [];
750
+ // Evaluator ≠ creator across a failover: an EVALUATOR cursor avoids the seats that
751
+ // built the work it reviews (DES-L3 PR-3E), a CREATOR cursor avoids the run's
752
+ // evaluator seats (crew#638), and a unit the engine routed `evaluator_distinct`
753
+ // avoids the seat it was routed away from (crew#583). A free-text unit carries no role
754
+ // and no such routing → no constraint.
755
+ const seatsOfRole = (role) => v.units
756
+ .filter((u) => u.role === role && u.assigned_cli != null)
757
+ .map((u) => u.assigned_cli);
758
+ const cursorUnit = cursor === undefined ? undefined : v.units.find((u) => u.ord === cursor.ord);
759
+ const routing = cursorUnit?.routing;
760
+ const avoid = [
761
+ ...new Set([
762
+ ...(cursor?.role === 'evaluator' ? seatsOfRole('creator') : []),
763
+ ...(cursor?.role === 'creator' ? seatsOfRole('evaluator') : []),
764
+ ...(routing?.method === 'evaluator_distinct' ? [routing.was] : []),
765
+ ]),
766
+ ].filter((s) => s !== cursor?.cli);
745
767
  return {
746
768
  id: v.session.id,
747
769
  ord: cursor?.ord ?? v.session.unit_ix,
@@ -801,6 +823,13 @@ export async function createServer(adapter, options) {
801
823
  onTurnTimeout: ({ session, ...detail }) => {
802
824
  audit.record('run.turn.timedout', { id: 'stall-watchdog', kind: 'system', trust: 'admin' }, { runId: session, detail });
803
825
  },
826
+ // crew#629: a unit whose own processes are working (a test runner, a build) is not silent.
827
+ ...(options?.stallWatchdog?.busy === null
828
+ ? {}
829
+ : {
830
+ busy: options?.stallWatchdog?.busy ??
831
+ ((sampler) => (runIds) => sampler.busy(runIds))(new RunLivenessSampler()),
832
+ }),
804
833
  log: (m) => app.log.warn(m),
805
834
  });
806
835
  // The daemon's single CoreEvent subscription fans out here: cache gate prompts
@@ -827,6 +856,51 @@ export async function createServer(adapter, options) {
827
856
  // written from the stamped frames below, dropped with the chat on `chatClosed`, served on
828
857
  // `GET /chats/:id.messages`.
829
858
  const chatTranscripts = new ChatTranscriptStore();
859
+ // Chat citations (crew#561, F-RC1-117): what the seat CITED, checked against the very read roots
860
+ // the daemon handed it. Runs off the reply's own path — after the broadcast below — and publishes
861
+ // one `chatCitations` frame per reply; the seat's text is never edited.
862
+ const citationsDisabled = options?.chatCitations?.disabled === true;
863
+ const citationDeps = options?.chatCitations?.deps ?? chatCitationDeps();
864
+ const citationLimits = options?.chatCitations?.limits;
865
+ async function verifyReplyCitations(frame, projectId) {
866
+ const f = frame;
867
+ const chatId = typeof f['chat'] === 'string' ? f['chat'] : undefined;
868
+ const cliKey = typeof f['cliKey'] === 'string' ? f['cliKey'] : undefined;
869
+ const text = typeof f['text'] === 'string' ? f['text'] : '';
870
+ // A NOT-ok reply is the daemon's/engine's own reason line (an eviction, a refusal): it cites
871
+ // nothing the reader would paste, so it is not verified — and never marked.
872
+ if (chatId === undefined || cliKey === undefined || text === '' || f['ok'] === false)
873
+ return;
874
+ const scope = chatScopes.get(chatId);
875
+ // No scope (a chat this daemon did not open, or `kind: 'none'`/`system`) ⇒ nothing to verify
876
+ // AGAINST. Saying "unverified" then would blame the seat for the daemon's missing roots.
877
+ if (scope === undefined || scope.repos.length === 0)
878
+ return;
879
+ const roots = scope.repos.map((r) => ({ absRoot: resolvePath(r.rootPath), name: r.name }));
880
+ const turnId = typeof f['turn_id'] === 'string' ? f['turn_id'] : undefined;
881
+ // A chat is filed by its CHAT id (`crew.chat` membership), so the project of a chat frame is
882
+ // looked up by that — the run-keyed lookup on the relay above answers `undefined` for chats.
883
+ const project = projectId ?? membershipIndex.projectOf(chatId);
884
+ try {
885
+ const result = await verifyCitations(text, roots, citationDeps, citationLimits);
886
+ const out = citationsFrame({
887
+ chat: chatId,
888
+ cliKey,
889
+ ...(turnId !== undefined ? { turn_id: turnId } : {}),
890
+ ...(project !== undefined ? { project_id: project } : {}),
891
+ }, result);
892
+ if (out !== null) {
893
+ broadcast(out);
894
+ // ...and into the transcript, so a reload still shows the marks (api-types 0.68.0): the
895
+ // verdicts are their own append-only record, folded onto their reply by the reader.
896
+ chatTranscripts.recordCitations(out);
897
+ }
898
+ }
899
+ catch (err) {
900
+ // Loud-non-fatal: a reply is never held hostage to its own verification.
901
+ app.log.warn(`[chat] citation verification failed for ${chatId}/${cliKey}: ${err instanceof Error ? err.message : String(err)}`);
902
+ }
903
+ }
830
904
  // crew#619: see declaration of chatRetained/runToChat above (before the hydration block).
831
905
  // linkChatRun populates both maps and is called from the hydration block (function-hoisted)
832
906
  // and from the launch route (via RoutesRuntime.linkChatRun).
@@ -889,6 +963,13 @@ export async function createServer(adapter, options) {
889
963
  const session = typeof event.session === 'string' ? event.session : undefined;
890
964
  const projectId = session !== undefined ? membershipIndex.projectOf(session) : undefined;
891
965
  broadcast(projectId !== undefined ? { ...rewritten, project_id: projectId } : rewritten);
966
+ // crew#561: the citations in that reply, checked against the chat's read roots — AFTER the
967
+ // broadcast (the answer is never delayed by verification) and never awaited on this path.
968
+ if (!citationsDisabled && rewritten.type === 'chatReply')
969
+ void verifyReplyCitations(rewritten, projectId);
970
+ // Standing orders (behaviour 10): a gate that opened may be one an order answers, holds or
971
+ // reports — after the gate cache folded it, so the order's decision carries the right ord.
972
+ void standingOrderEvaluator?.onEvent(event);
892
973
  // The delivered-PR record (CREW-UX-8, crew#321): resolved once per run at its terminal
893
974
  // frame, best-effort, off the hot path — see `resolveRunDelivery` above for why BOTH
894
975
  // terminal frames trigger it and why a failed deliver is a no-op. THEN the delivery-
@@ -958,7 +1039,6 @@ export async function createServer(adapter, options) {
958
1039
  adapter,
959
1040
  audit,
960
1041
  index: testSets,
961
- workflows: () => adapter.listWorkflows(),
962
1042
  deliveryUrlFor: (runId) => deliveryIndex.urlFor(runId),
963
1043
  labelFor: (runId) => {
964
1044
  const attach = groupIndex.attachOf(runId);
@@ -1011,6 +1091,9 @@ export async function createServer(adapter, options) {
1011
1091
  // close like the event listener.
1012
1092
  const offLaunch = adapter.onLaunch((notice) => {
1013
1093
  skillsRuntime?.launched(notice);
1094
+ // crew#661: a run the engine ACCEPTED on a workflow that armed WITHOUT its declared skill proceeds
1095
+ // degraded and says so on the run (`session.skill_gaps`, a `run.skill.unarmed` trail entry). Never throws.
1096
+ runSkillGaps.onLaunch(notice, phaseSkills, audit, DAEMON_ACTOR, (m) => app.log.warn(m));
1014
1097
  });
1015
1098
  app.addHook('onClose', async () => {
1016
1099
  offEvent();
@@ -1058,14 +1141,17 @@ export async function createServer(adapter, options) {
1058
1141
  try {
1059
1142
  done(null, JSON.parse(body));
1060
1143
  }
1061
- catch (err) {
1144
+ catch {
1062
1145
  // A syntactically invalid body is the CLIENT's error: without an explicit statusCode
1063
1146
  // Fastify reports a parser error as a 500, which misfiles "you sent `{not json`" as a
1064
1147
  // server fault — on the elicitation answer path that told an operator the daemon broke
1065
1148
  // when their request did. 400 matches Fastify's own default JSON parser
1066
1149
  // (FST_ERR_CTP_INVALID_JSON) and every schema-level 400 these routes already return.
1067
- err.statusCode = 400;
1068
- done(err);
1150
+ // The message is a fixed one, never JSON.parse's: V8 quotes the body around the bad token
1151
+ // (`Unexpected token 's', ..."{"value": sk-…"... is not valid JSON`), and Fastify both
1152
+ // answers and logs it, so an unquoted `PUT /mcp/servers/:name/secret` value would leave the
1153
+ // broker in the response and the log (DES-MCP-TOOLS-001 D-2).
1154
+ done(Object.assign(new Error('the request body is not valid JSON'), { statusCode: 400, code: 'FST_ERR_CTP_INVALID_JSON' }));
1069
1155
  }
1070
1156
  });
1071
1157
  await app.register(fastifyWebsocket);
@@ -1087,13 +1173,47 @@ export async function createServer(adapter, options) {
1087
1173
  // diagnostics reads the bundle's shipped version manifest from the same root the static
1088
1174
  // handler serves. One resolution, two consumers.
1089
1175
  const studioRoot = options?.studioRoot ?? defaultStudioRoot();
1090
- registerRoutes(app, adapter, gateCache, elicitationCache, qeGateCache, {
1176
+ // DES-MCP-TOOLS-001 S2: the registry over `<state home>/mcp` (created on the first save).
1177
+ const mcpRegistry = new McpRegistry({
1178
+ store: new McpRegistryStore(),
1179
+ secrets: platformSecretStore(),
1180
+ probe: probeMcpServer,
1181
+ // Read the worker home at request time: `PUT /settings` can move it (applyWorkerConfigRoot).
1182
+ discover: (managed) => discoverMcpServers({ home: homedir(), workerHome: process.env['WICKED_WORKER_HOME'] ?? null }, managed),
1183
+ });
1184
+ // S3: every brokered call is judged by the in-process engine (the token registry is the
1185
+ // engine's, process-global), recorded to `<state home>/mcp/calls.ndjson`, and published on /ws
1186
+ // and the engine's bus. A bus that is not attached only loses the event; the record is the record.
1187
+ // S7: the same file (one serialized chain) is what `GET /mcp/usage` reads back and folds.
1188
+ const mcpCallRecords = new McpCallRecordFile();
1189
+ const mcpBroker = new McpBroker({
1190
+ registry: mcpRegistry,
1191
+ engine: () => CoreAdapter.mcpEngineGate(),
1192
+ records: mcpCallRecords,
1193
+ budgetPerUnit: budgetFromEnv(),
1194
+ log: (m) => app.log.warn(m),
1195
+ publish: async (record) => {
1196
+ broadcast({ type: 'mcpCallCompleted', record });
1197
+ if (engineBusDb === undefined)
1198
+ return;
1199
+ await emitOnBus(engineBusDb, {
1200
+ event_type: MCP_CALL_COMPLETED,
1201
+ domain: 'wicked-crew',
1202
+ subdomain: 'mcp',
1203
+ payload: record,
1204
+ producer_id: 'wicked-crew',
1205
+ idempotency_key: `${MCP_CALL_COMPLETED}:${record.spanId}`,
1206
+ });
1207
+ },
1208
+ });
1209
+ const registered = registerRoutes(app, adapter, gateCache, elicitationCache, {
1091
1210
  bus: projectBus,
1092
1211
  index: membershipIndex,
1093
1212
  log: (m) => app.log.warn(m),
1094
1213
  settings: projectSettings,
1095
1214
  }, { audit, authMode: auth.mode }, {
1096
1215
  seatHealth,
1216
+ deliveryFreeze,
1097
1217
  // wicked-studio#284: the watchdog's remembered frames ride `GET /runs/:id/events`.
1098
1218
  stallFrames: (runId) => stallFrameIndex.framesFor(runId),
1099
1219
  // The SAME standing accessor the seams and the adapter launch with (F-RECON-002/003).
@@ -1102,6 +1222,8 @@ export async function createServer(adapter, options) {
1102
1222
  groupIndex,
1103
1223
  runTimingIndex,
1104
1224
  guidanceIndex,
1225
+ phaseSkills,
1226
+ runSkillGaps,
1105
1227
  chatScopes,
1106
1228
  chatTurns,
1107
1229
  chatTranscripts,
@@ -1125,6 +1247,13 @@ export async function createServer(adapter, options) {
1125
1247
  interactiveBridgeBusDataDir: options?.interactiveBridge?.busDataDir ?? null,
1126
1248
  docGrounding,
1127
1249
  ...(skillsRuntime !== undefined ? { skills: skillsRuntime } : {}),
1250
+ // DES-MCP-TOOLS-001 S2: the registry over `<state home>/mcp` (created on the first save).
1251
+ // Route tests drive `registerMcpRoutes` over their own registry (tests/mcp-registry.test.ts).
1252
+ mcp: mcpRegistry,
1253
+ // DES-MCP-TOOLS-001 S3: the broker's call path over that registry.
1254
+ mcpBroker,
1255
+ // DES-MCP-TOOLS-001 S7: the call records the usage fold reads.
1256
+ mcpCalls: mcpCallRecords,
1128
1257
  // wicked-core#411 / crew#497: the live state-home classification the routes report and gate on.
1129
1258
  stateHome: stateHomeWatch,
1130
1259
  // Routes that say something to the thread (a refused chat seat, F-2R2-007) emit through the
@@ -1133,6 +1262,68 @@ export async function createServer(adapter, options) {
1133
1262
  // crew#619: retain a chat's transcript for the lifetime of its promoted run.
1134
1263
  linkChatRun,
1135
1264
  });
1265
+ // Standing orders (Studio OS behaviour 10): the store, the evaluator over THE gate decision path
1266
+ // (`registered.decideGate`), and the routes. See src/standing-orders/.
1267
+ // The durable record is the audit trail (store.ts): fold it once, here.
1268
+ const standingOrders = new StandingOrderStore(audit);
1269
+ await standingOrders.hydrate(audit, (m) => app.log.warn(m));
1270
+ standingOrderEvaluator = new StandingOrderEvaluator({
1271
+ store: standingOrders,
1272
+ decideGate: registered.decideGate,
1273
+ audit,
1274
+ runFacts: async (runId) => {
1275
+ const v = (await adapter.sessionsDetail()).find((x) => x.session.id === runId);
1276
+ if (v === undefined)
1277
+ return undefined;
1278
+ return {
1279
+ projectId: runProject(v, runId, (id) => membershipIndex.projectOf(id)),
1280
+ band: runBand(v),
1281
+ preset: runPreset(v),
1282
+ problem: v.session.problem,
1283
+ phaseOf: (ord) => v.units.find((u) => u.ord === ord)?.phase_ref ?? undefined,
1284
+ firstOrd: v.units.length === 0 ? undefined : Math.min(...v.units.map((u) => u.ord)),
1285
+ landsDoctrine: isSteeringAuthorRun(v),
1286
+ };
1287
+ },
1288
+ openGates: async () => {
1289
+ if (typeof adapter.interactionRequests !== 'function')
1290
+ return [];
1291
+ const rows = (await adapter.interactionRequests(undefined, 'open')) ?? [];
1292
+ return rows
1293
+ .filter((r) => r.kind === 'gate' && typeof r.ord === 'number')
1294
+ .map((r) => ({
1295
+ runId: r.session_id,
1296
+ ord: r.ord,
1297
+ reviewingOrd: r.reviewing_ord,
1298
+ gateKind: typeof r['gate_kind'] === 'string' ? r['gate_kind'] : '',
1299
+ prompt: r.prompt,
1300
+ }));
1301
+ },
1302
+ // Brainstorm idea 13: a plan-approval trust order re-reads the open plan gate's own band.
1303
+ planGate: async (runId) => openPlanGateRisk(await busRows(adapter.busDbPath, runId)),
1304
+ log: (m) => app.log.warn(m),
1305
+ });
1306
+ // The decided-gate history (brainstorm ideas 7 and 8): a seat's track record, and the preview
1307
+ // of what a proposed order would have done — read with the evaluator's own phase and invariant.
1308
+ registerGateHistoryRoute(app, {
1309
+ audit,
1310
+ views: () => adapter.sessionsDetail(),
1311
+ gateRows: async () => typeof adapter.interactionRequests === 'function' ? ((await adapter.interactionRequests(undefined, undefined)) ?? []) : [],
1312
+ projectOf: (id) => membershipIndex.projectOf(id),
1313
+ landsDoctrine: isSteeringAuthorRun,
1314
+ });
1315
+ registerStandingOrderRoutes(app, {
1316
+ store: standingOrders,
1317
+ evaluator: standingOrderEvaluator,
1318
+ parse: seatParser({ adapter, roster: rosterWithStanding }),
1319
+ });
1320
+ // Orders hydrated from the trail act on gates ALREADY open: one sweep of the engine's open gate
1321
+ // rows at boot. It also covers any gate that opened before the evaluator existed (the live
1322
+ // subscription above reads it late-bound); every later gate arrives as a live frame.
1323
+ void standingOrderEvaluator.sweep().catch((err) => app.log.warn(`[standing-orders] boot sweep failed: ${String(err)}`));
1324
+ // crew#471: a campaign the previous daemon left `running` is crash-resumed by the engine (its
1325
+ // node runs froze with that process) — asked once, at boot, never for a paused one.
1326
+ void resumeRunningCampaigns(adapter, (m) => app.log.warn(m));
1136
1327
  // The UI-emittable direction of the interactive seam. Registered unconditionally (a null relay
1137
1328
  // answers 503, not 404) and BEFORE the static/SPA fallback below, like every other API route.
1138
1329
  registerInteractiveEventRoutes(app, interactiveRelay);
@@ -1189,6 +1380,11 @@ export async function createServer(adapter, options) {
1189
1380
  }
1190
1381
  return app;
1191
1382
  }
1383
+ /** The URL a worker on this host reaches the daemon at: a wildcard bind is reached on loopback. */
1384
+ export function brokerUrlFor(host, port) {
1385
+ const h = host === '0.0.0.0' || host === '' ? '127.0.0.1' : host === '::' ? '::1' : host;
1386
+ return `http://${h.includes(':') ? `[${h}]` : h}:${port}`;
1387
+ }
1192
1388
  export async function startServer(adapter, port = 7701, host = '127.0.0.1', options) {
1193
1389
  const app = await createServer(adapter, options);
1194
1390
  await app.listen({ port, host });
@@ -1196,6 +1392,10 @@ export async function startServer(adapter, port = 7701, host = '127.0.0.1', opti
1196
1392
  const boundPort = typeof addr === 'object' && addr ? addr.port : port;
1197
1393
  const boundHost = typeof addr === 'object' && addr ? addr.address : host;
1198
1394
  app.log.info(`wicked-crew daemon listening on ${boundHost}:${boundPort}`);
1395
+ // DES-MCP-TOOLS-001 S1/S3: the in-process engine hands each governed worker this URL (with its
1396
+ // `WICKED_MCP_TOKEN`) as `WICKED_CREW_URL`, so the garden shim reaches THIS daemon's broker. The
1397
+ // engine reads the daemon's own env at every spawn; a daemon that never listened hands none.
1398
+ process.env['WICKED_CREW_URL'] = brokerUrlFor(boundHost, boundPort);
1199
1399
  return { app, port: boundPort, host: boundHost };
1200
1400
  }
1201
1401
  //# sourceMappingURL=server.js.map