wicked-crew 0.7.40 → 0.7.46

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 (361) 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-transcripts.d.ts +20 -1
  14. package/dist/api/chat-transcripts.d.ts.map +1 -1
  15. package/dist/api/chat-transcripts.js +29 -0
  16. package/dist/api/chat-transcripts.js.map +1 -1
  17. package/dist/api/delivery-cache.d.ts +2 -2
  18. package/dist/api/delivery-freeze.d.ts +66 -0
  19. package/dist/api/delivery-freeze.d.ts.map +1 -0
  20. package/dist/api/delivery-freeze.js +104 -0
  21. package/dist/api/delivery-freeze.js.map +1 -0
  22. package/dist/api/delivery-index.d.ts +53 -9
  23. package/dist/api/delivery-index.d.ts.map +1 -1
  24. package/dist/api/delivery-index.js +115 -22
  25. package/dist/api/delivery-index.js.map +1 -1
  26. package/dist/api/demo.d.ts +90 -0
  27. package/dist/api/demo.d.ts.map +1 -0
  28. package/dist/api/demo.js +492 -0
  29. package/dist/api/demo.js.map +1 -0
  30. package/dist/api/diagnostics.d.ts +64 -1
  31. package/dist/api/diagnostics.d.ts.map +1 -1
  32. package/dist/api/diagnostics.js +129 -1
  33. package/dist/api/diagnostics.js.map +1 -1
  34. package/dist/api/eval-sample.d.ts +6 -6
  35. package/dist/api/governance-steering.d.ts +2 -2
  36. package/dist/api/mcp.d.ts +282 -0
  37. package/dist/api/mcp.d.ts.map +1 -0
  38. package/dist/api/mcp.js +521 -0
  39. package/dist/api/mcp.js.map +1 -0
  40. package/dist/api/plan-schema.d.ts +56 -0
  41. package/dist/api/plan-schema.d.ts.map +1 -0
  42. package/dist/api/plan-schema.js +26 -0
  43. package/dist/api/plan-schema.js.map +1 -0
  44. package/dist/api/roster-standing.d.ts +15 -2
  45. package/dist/api/roster-standing.d.ts.map +1 -1
  46. package/dist/api/roster-standing.js +59 -3
  47. package/dist/api/roster-standing.js.map +1 -1
  48. package/dist/api/routes.d.ts +630 -51
  49. package/dist/api/routes.d.ts.map +1 -1
  50. package/dist/api/routes.js +738 -127
  51. package/dist/api/routes.js.map +1 -1
  52. package/dist/api/run-files.d.ts.map +1 -1
  53. package/dist/api/run-files.js +31 -2
  54. package/dist/api/run-files.js.map +1 -1
  55. package/dist/api/run-liveness.d.ts +41 -0
  56. package/dist/api/run-liveness.d.ts.map +1 -0
  57. package/dist/api/run-liveness.js +166 -0
  58. package/dist/api/run-liveness.js.map +1 -0
  59. package/dist/api/seat-health.d.ts +35 -0
  60. package/dist/api/seat-health.d.ts.map +1 -1
  61. package/dist/api/seat-health.js +92 -4
  62. package/dist/api/seat-health.js.map +1 -1
  63. package/dist/api/seat-probe.d.ts +141 -0
  64. package/dist/api/seat-probe.d.ts.map +1 -0
  65. package/dist/api/seat-probe.js +257 -0
  66. package/dist/api/seat-probe.js.map +1 -0
  67. package/dist/api/seat-record.d.ts +40 -0
  68. package/dist/api/seat-record.d.ts.map +1 -0
  69. package/dist/api/seat-record.js +218 -0
  70. package/dist/api/seat-record.js.map +1 -0
  71. package/dist/api/seat-signin.d.ts.map +1 -1
  72. package/dist/api/seat-signin.js +3 -1
  73. package/dist/api/seat-signin.js.map +1 -1
  74. package/dist/api/seat-standing.d.ts +25 -5
  75. package/dist/api/seat-standing.d.ts.map +1 -1
  76. package/dist/api/seat-standing.js +76 -9
  77. package/dist/api/seat-standing.js.map +1 -1
  78. package/dist/api/server.d.ts +34 -48
  79. package/dist/api/server.d.ts.map +1 -1
  80. package/dist/api/server.js +283 -107
  81. package/dist/api/server.js.map +1 -1
  82. package/dist/api/skills.d.ts +3 -1
  83. package/dist/api/skills.d.ts.map +1 -1
  84. package/dist/api/skills.js +7 -2
  85. package/dist/api/skills.js.map +1 -1
  86. package/dist/api/stall-watchdog.d.ts +34 -3
  87. package/dist/api/stall-watchdog.d.ts.map +1 -1
  88. package/dist/api/stall-watchdog.js +107 -8
  89. package/dist/api/stall-watchdog.js.map +1 -1
  90. package/dist/api/steering-landing.d.ts +6 -6
  91. package/dist/api/steering-landing.d.ts.map +1 -1
  92. package/dist/api/steering-landing.js +7 -9
  93. package/dist/api/steering-landing.js.map +1 -1
  94. package/dist/api/testing.d.ts +6 -4
  95. package/dist/api/testing.d.ts.map +1 -1
  96. package/dist/api/testing.js +82 -47
  97. package/dist/api/testing.js.map +1 -1
  98. package/dist/campaign/boot-resume.d.ts +34 -0
  99. package/dist/campaign/boot-resume.d.ts.map +1 -0
  100. package/dist/campaign/boot-resume.js +45 -0
  101. package/dist/campaign/boot-resume.js.map +1 -0
  102. package/dist/campaigns/rollup.d.ts +4 -1
  103. package/dist/campaigns/rollup.d.ts.map +1 -1
  104. package/dist/campaigns/rollup.js +6 -1
  105. package/dist/campaigns/rollup.js.map +1 -1
  106. package/dist/campaigns/routes.d.ts +2 -2
  107. package/dist/cli/bearer.d.ts +27 -1
  108. package/dist/cli/bearer.d.ts.map +1 -1
  109. package/dist/cli/bearer.js +62 -2
  110. package/dist/cli/bearer.js.map +1 -1
  111. package/dist/cli/governance.d.ts +11 -0
  112. package/dist/cli/governance.d.ts.map +1 -1
  113. package/dist/cli/governance.js +8 -2
  114. package/dist/cli/governance.js.map +1 -1
  115. package/dist/cli/index.d.ts.map +1 -1
  116. package/dist/cli/index.js +140 -75
  117. package/dist/cli/index.js.map +1 -1
  118. package/dist/cli/mcp.d.ts +11 -0
  119. package/dist/cli/mcp.d.ts.map +1 -1
  120. package/dist/cli/mcp.js +40 -9
  121. package/dist/cli/mcp.js.map +1 -1
  122. package/dist/cli/service.d.ts +78 -0
  123. package/dist/cli/service.d.ts.map +1 -0
  124. package/dist/cli/service.js +334 -0
  125. package/dist/cli/service.js.map +1 -0
  126. package/dist/core/adapter.d.ts +238 -12
  127. package/dist/core/adapter.d.ts.map +1 -1
  128. package/dist/core/adapter.js +619 -157
  129. package/dist/core/adapter.js.map +1 -1
  130. package/dist/core/bridge-reaper.d.ts +4 -1
  131. package/dist/core/bridge-reaper.d.ts.map +1 -1
  132. package/dist/core/bridge-reaper.js +15 -1
  133. package/dist/core/bridge-reaper.js.map +1 -1
  134. package/dist/core/bus-notice.d.ts +27 -0
  135. package/dist/core/bus-notice.d.ts.map +1 -0
  136. package/dist/core/bus-notice.js +24 -0
  137. package/dist/core/bus-notice.js.map +1 -0
  138. package/dist/core/bus.d.ts +99 -0
  139. package/dist/core/bus.d.ts.map +1 -0
  140. package/dist/core/bus.js +176 -0
  141. package/dist/core/bus.js.map +1 -0
  142. package/dist/core/deliver-exclusions.d.ts +71 -0
  143. package/dist/core/deliver-exclusions.d.ts.map +1 -0
  144. package/dist/core/deliver-exclusions.js +110 -0
  145. package/dist/core/deliver-exclusions.js.map +1 -0
  146. package/dist/core/deliver-text.d.ts +61 -14
  147. package/dist/core/deliver-text.d.ts.map +1 -1
  148. package/dist/core/deliver-text.js +178 -30
  149. package/dist/core/deliver-text.js.map +1 -1
  150. package/dist/core/deliver-triage.d.ts +11 -2
  151. package/dist/core/deliver-triage.d.ts.map +1 -1
  152. package/dist/core/deliver-triage.js +19 -1
  153. package/dist/core/deliver-triage.js.map +1 -1
  154. package/dist/core/deliver.d.ts +161 -3
  155. package/dist/core/deliver.d.ts.map +1 -1
  156. package/dist/core/deliver.js +463 -25
  157. package/dist/core/deliver.js.map +1 -1
  158. package/dist/core/deliverable-floor.d.ts.map +1 -1
  159. package/dist/core/deliverable-floor.js +5 -7
  160. package/dist/core/deliverable-floor.js.map +1 -1
  161. package/dist/core/engine-bus.d.ts +40 -0
  162. package/dist/core/engine-bus.d.ts.map +1 -0
  163. package/dist/core/engine-bus.js +27 -0
  164. package/dist/core/engine-bus.js.map +1 -0
  165. package/dist/core/estate-mcp-client.d.ts +87 -5
  166. package/dist/core/estate-mcp-client.d.ts.map +1 -1
  167. package/dist/core/estate-mcp-client.js +90 -11
  168. package/dist/core/estate-mcp-client.js.map +1 -1
  169. package/dist/core/linked-issues.d.ts +46 -0
  170. package/dist/core/linked-issues.d.ts.map +1 -0
  171. package/dist/core/linked-issues.js +123 -0
  172. package/dist/core/linked-issues.js.map +1 -0
  173. package/dist/core/run-identity.d.ts +100 -0
  174. package/dist/core/run-identity.d.ts.map +1 -0
  175. package/dist/core/run-identity.js +178 -0
  176. package/dist/core/run-identity.js.map +1 -0
  177. package/dist/core/shutdown.d.ts +16 -0
  178. package/dist/core/shutdown.d.ts.map +1 -0
  179. package/dist/core/shutdown.js +39 -0
  180. package/dist/core/shutdown.js.map +1 -0
  181. package/dist/core/types.d.ts +15 -1
  182. package/dist/core/types.d.ts.map +1 -1
  183. package/dist/core/types.js +4 -0
  184. package/dist/core/types.js.map +1 -1
  185. package/dist/interactive/bridge-pool.d.ts +67 -3
  186. package/dist/interactive/bridge-pool.d.ts.map +1 -1
  187. package/dist/interactive/bridge-pool.js +129 -13
  188. package/dist/interactive/bridge-pool.js.map +1 -1
  189. package/dist/interactive/bridge-root.d.ts +9 -0
  190. package/dist/interactive/bridge-root.d.ts.map +1 -1
  191. package/dist/interactive/bridge-root.js +11 -4
  192. package/dist/interactive/bridge-root.js.map +1 -1
  193. package/dist/interactive/chat-events.d.ts +14 -9
  194. package/dist/interactive/chat-events.d.ts.map +1 -1
  195. package/dist/interactive/chat-events.js +55 -57
  196. package/dist/interactive/chat-events.js.map +1 -1
  197. package/dist/interactive/council-outcome.d.ts +1 -0
  198. package/dist/interactive/council-outcome.d.ts.map +1 -1
  199. package/dist/interactive/council-outcome.js +33 -0
  200. package/dist/interactive/council-outcome.js.map +1 -1
  201. package/dist/interactive/draft-events.d.ts +36 -17
  202. package/dist/interactive/draft-events.d.ts.map +1 -1
  203. package/dist/interactive/draft-events.js +121 -92
  204. package/dist/interactive/draft-events.js.map +1 -1
  205. package/dist/interactive/draft-skill.d.ts +59 -0
  206. package/dist/interactive/draft-skill.d.ts.map +1 -1
  207. package/dist/interactive/draft-skill.js +233 -0
  208. package/dist/interactive/draft-skill.js.map +1 -1
  209. package/dist/interactive/edit-events.d.ts +1 -14
  210. package/dist/interactive/edit-events.d.ts.map +1 -1
  211. package/dist/interactive/edit-events.js +37 -70
  212. package/dist/interactive/edit-events.js.map +1 -1
  213. package/dist/interactive/proxy-routes.d.ts +2 -2
  214. package/dist/interactive/proxy-routes.d.ts.map +1 -1
  215. package/dist/interactive/proxy-routes.js +4 -13
  216. package/dist/interactive/proxy-routes.js.map +1 -1
  217. package/dist/interactive/ws-relay.d.ts +9 -10
  218. package/dist/interactive/ws-relay.d.ts.map +1 -1
  219. package/dist/interactive/ws-relay.js +23 -50
  220. package/dist/interactive/ws-relay.js.map +1 -1
  221. package/dist/mcp/broker.d.ts +88 -0
  222. package/dist/mcp/broker.d.ts.map +1 -0
  223. package/dist/mcp/broker.js +307 -0
  224. package/dist/mcp/broker.js.map +1 -0
  225. package/dist/mcp/call-records.d.ts +61 -0
  226. package/dist/mcp/call-records.d.ts.map +1 -0
  227. package/dist/mcp/call-records.js +135 -0
  228. package/dist/mcp/call-records.js.map +1 -0
  229. package/dist/mcp/classify.d.ts +45 -0
  230. package/dist/mcp/classify.d.ts.map +1 -0
  231. package/dist/mcp/classify.js +83 -0
  232. package/dist/mcp/classify.js.map +1 -0
  233. package/dist/mcp/discovery.d.ts +24 -0
  234. package/dist/mcp/discovery.d.ts.map +1 -0
  235. package/dist/mcp/discovery.js +78 -0
  236. package/dist/mcp/discovery.js.map +1 -0
  237. package/dist/mcp/invoke.d.ts +36 -0
  238. package/dist/mcp/invoke.d.ts.map +1 -0
  239. package/dist/mcp/invoke.js +80 -0
  240. package/dist/mcp/invoke.js.map +1 -0
  241. package/dist/mcp/policies.d.ts +110 -0
  242. package/dist/mcp/policies.d.ts.map +1 -0
  243. package/dist/mcp/policies.js +400 -0
  244. package/dist/mcp/policies.js.map +1 -0
  245. package/dist/mcp/probe.d.ts +66 -0
  246. package/dist/mcp/probe.d.ts.map +1 -0
  247. package/dist/mcp/probe.js +116 -0
  248. package/dist/mcp/probe.js.map +1 -0
  249. package/dist/mcp/redact.d.ts +34 -0
  250. package/dist/mcp/redact.d.ts.map +1 -0
  251. package/dist/mcp/redact.js +84 -0
  252. package/dist/mcp/redact.js.map +1 -0
  253. package/dist/mcp/registry-store.d.ts +69 -0
  254. package/dist/mcp/registry-store.d.ts.map +1 -0
  255. package/dist/mcp/registry-store.js +82 -0
  256. package/dist/mcp/registry-store.js.map +1 -0
  257. package/dist/mcp/registry.d.ts +147 -0
  258. package/dist/mcp/registry.d.ts.map +1 -0
  259. package/dist/mcp/registry.js +509 -0
  260. package/dist/mcp/registry.js.map +1 -0
  261. package/dist/mcp/rest.d.ts +111 -0
  262. package/dist/mcp/rest.d.ts.map +1 -0
  263. package/dist/mcp/rest.js +606 -0
  264. package/dist/mcp/rest.js.map +1 -0
  265. package/dist/mcp/secrets.d.ts +66 -0
  266. package/dist/mcp/secrets.d.ts.map +1 -0
  267. package/dist/mcp/secrets.js +180 -0
  268. package/dist/mcp/secrets.js.map +1 -0
  269. package/dist/mcp/tool-list.d.ts +25 -0
  270. package/dist/mcp/tool-list.d.ts.map +1 -0
  271. package/dist/mcp/tool-list.js +62 -0
  272. package/dist/mcp/tool-list.js.map +1 -0
  273. package/dist/mcp/upstream-error.d.ts +12 -0
  274. package/dist/mcp/upstream-error.d.ts.map +1 -0
  275. package/dist/mcp/upstream-error.js +14 -0
  276. package/dist/mcp/upstream-error.js.map +1 -0
  277. package/dist/mcp/usage.d.ts +31 -0
  278. package/dist/mcp/usage.d.ts.map +1 -0
  279. package/dist/mcp/usage.js +159 -0
  280. package/dist/mcp/usage.js.map +1 -0
  281. package/dist/presets/routes.d.ts +53 -0
  282. package/dist/presets/routes.d.ts.map +1 -0
  283. package/dist/presets/routes.js +115 -0
  284. package/dist/presets/routes.js.map +1 -0
  285. package/dist/projects/activity.d.ts +6 -18
  286. package/dist/projects/activity.d.ts.map +1 -1
  287. package/dist/projects/activity.js +17 -60
  288. package/dist/projects/activity.js.map +1 -1
  289. package/dist/projects/events.d.ts +6 -5
  290. package/dist/projects/events.d.ts.map +1 -1
  291. package/dist/projects/events.js +33 -58
  292. package/dist/projects/events.js.map +1 -1
  293. package/dist/projects/routes.d.ts.map +1 -1
  294. package/dist/projects/routes.js +19 -0
  295. package/dist/projects/routes.js.map +1 -1
  296. package/dist/projects/state-home-preflight.d.ts.map +1 -1
  297. package/dist/projects/state-home-preflight.js +7 -0
  298. package/dist/projects/state-home-preflight.js.map +1 -1
  299. package/dist/projects/state-home-registry.d.ts.map +1 -1
  300. package/dist/projects/state-home-registry.js +9 -1
  301. package/dist/projects/state-home-registry.js.map +1 -1
  302. package/dist/qe/acceptance.d.ts +28 -28
  303. package/dist/qe/acceptance.d.ts.map +1 -1
  304. package/dist/qe/acceptance.js +60 -61
  305. package/dist/qe/acceptance.js.map +1 -1
  306. package/dist/qe/test-sets.d.ts +3 -5
  307. package/dist/qe/test-sets.d.ts.map +1 -1
  308. package/dist/qe/test-sets.js +5 -7
  309. package/dist/qe/test-sets.js.map +1 -1
  310. package/dist/skills/installed.d.ts +51 -0
  311. package/dist/skills/installed.d.ts.map +1 -0
  312. package/dist/skills/installed.js +46 -0
  313. package/dist/skills/installed.js.map +1 -0
  314. package/dist/standing-orders/evaluator.d.ts +113 -0
  315. package/dist/standing-orders/evaluator.d.ts.map +1 -0
  316. package/dist/standing-orders/evaluator.js +225 -0
  317. package/dist/standing-orders/evaluator.js.map +1 -0
  318. package/dist/standing-orders/history.d.ts +58 -0
  319. package/dist/standing-orders/history.d.ts.map +1 -0
  320. package/dist/standing-orders/history.js +117 -0
  321. package/dist/standing-orders/history.js.map +1 -0
  322. package/dist/standing-orders/parse.d.ts +19 -0
  323. package/dist/standing-orders/parse.d.ts.map +1 -0
  324. package/dist/standing-orders/parse.js +75 -0
  325. package/dist/standing-orders/parse.js.map +1 -0
  326. package/dist/standing-orders/routes.d.ts +180 -0
  327. package/dist/standing-orders/routes.d.ts.map +1 -0
  328. package/dist/standing-orders/routes.js +95 -0
  329. package/dist/standing-orders/routes.js.map +1 -0
  330. package/dist/standing-orders/store.d.ts +154 -0
  331. package/dist/standing-orders/store.d.ts.map +1 -0
  332. package/dist/standing-orders/store.js +189 -0
  333. package/dist/standing-orders/store.js.map +1 -0
  334. package/dist/studio/assets/index-CvV5qITf.css +32 -0
  335. package/dist/studio/assets/index-jCOy6Ns7.js +568 -0
  336. package/dist/studio/index.html +3 -3
  337. package/dist/studio/testid-inventory.json +3274 -894
  338. package/dist/team/key.d.ts +5 -0
  339. package/dist/team/key.d.ts.map +1 -0
  340. package/dist/team/key.js +21 -0
  341. package/dist/team/key.js.map +1 -0
  342. package/dist/team/routes.d.ts +196 -0
  343. package/dist/team/routes.d.ts.map +1 -0
  344. package/dist/team/routes.js +274 -0
  345. package/dist/team/routes.js.map +1 -0
  346. package/dist/team/ws-relay.d.ts +39 -0
  347. package/dist/team/ws-relay.d.ts.map +1 -0
  348. package/dist/team/ws-relay.js +63 -0
  349. package/dist/team/ws-relay.js.map +1 -0
  350. package/endpoint-manifest.json +502 -6
  351. package/package.json +6 -6
  352. package/dist/interactive/demo-events.d.ts +0 -367
  353. package/dist/interactive/demo-events.d.ts.map +0 -1
  354. package/dist/interactive/demo-events.js +0 -1293
  355. package/dist/interactive/demo-events.js.map +0 -1
  356. package/dist/qe/gate-events.d.ts +0 -113
  357. package/dist/qe/gate-events.d.ts.map +0 -1
  358. package/dist/qe/gate-events.js +0 -172
  359. package/dist/qe/gate-events.js.map +0 -1
  360. package/dist/studio/assets/index-Bg7Z3BR-.css +0 -32
  361. package/dist/studio/assets/index-BvOFMjf9.js +0 -554
@@ -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,17 +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
26
  import { INTERACTIVE_DRAFT_WORKFLOW_DEF, startInteractiveDraftSubscriber } from '../interactive/draft-events.js';
26
27
  import { INTERACTIVE_EDIT_WORKFLOW_DEF, startInteractiveEditSubscriber } from '../interactive/edit-events.js';
27
28
  import { INTERACTIVE_CHAT_WORKFLOW_DEF, startInteractiveChatSubscriber } from '../interactive/chat-events.js';
28
29
  import { PhaseSkillArming, RunSkillGapIndex } from '../skills/phase-skill-gaps.js';
29
- import { startInteractiveDemoSubscriber } from '../interactive/demo-events.js';
30
30
  import { resolveProjectInteractiveRoot } from '../interactive/bridge-root.js';
31
31
  import { sweepDocLedgers } from '../interactive/doc-ledger-sweep.js';
32
32
  import { DocRunIndex } from '../interactive/doc-run-index.js';
@@ -34,8 +34,18 @@ import { TestSetIndex, registerTestSetForRun } from '../qe/test-sets.js';
34
34
  import { DocGroundingStore } from '../interactive/doc-grounding.js';
35
35
  import { ProjectSettingsStore } from '../projects/settings.js';
36
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';
37
46
  import { startProjectBus, MEMBERSHIP_ATTACHED, membershipAttachedKey } from '../projects/events.js';
38
47
  import { startInteractiveWsRelay, registerInteractiveEventRoutes } from '../interactive/ws-relay.js';
48
+ import { startTeamWsRelay } from '../team/ws-relay.js';
39
49
  import { MembershipIndex } from '../projects/membership-index.js';
40
50
  import { writeRunEvidencePointer } from '../projects/charter.js';
41
51
  import { engineBenchesUnclassifiedSeats, CoreAdapter } from '../core/adapter.js';
@@ -48,6 +58,15 @@ import { ChatTranscriptStore } from './chat-transcripts.js';
48
58
  import { sweepDeliveredWorktree } from './worktree-sweep.js';
49
59
  import { installEndpointManifestHook } from './endpoint-manifest.js';
50
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';
51
70
  import { applyWorkerConfigRoot } from './seat-signin.js';
52
71
  import { registeredSkillRefs } from '../skills/core-closure.js';
53
72
  import { assertSkillsRootFenced } from '../skills/root-fence.js';
@@ -95,9 +114,11 @@ export async function createServer(adapter, options) {
95
114
  // test); costs one array push per route at boot, nothing per request.
96
115
  app.decorate('endpointManifest', installEndpointManifestHook(app));
97
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;
98
120
  const elicitationCache = new ElicitationCache();
99
121
  const terminals = new TerminalHub();
100
- const qeGateCache = new QeGateCache();
101
122
  // Per-seat runtime health (crew#274): folded from the single CoreEvent subscription below,
102
123
  // surfaced on GET /roster, recovered by the low-frequency probe armed further down.
103
124
  const seatHealth = new SeatHealthTracker({
@@ -311,6 +332,10 @@ export async function createServer(adapter, options) {
311
332
  // trail's `run.delivered` entries so `session.delivery` survives a daemon restart.
312
333
  const deliveryIndex = new DeliveryIndex();
313
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));
314
339
  // Wave 6 (F-7R2-014): the test sets `qe-author-tests` runs registered — same durable pattern,
315
340
  // hydrated from the trail's `testing.testset.registered` entries, fed at each terminal frame.
316
341
  const testSets = new TestSetIndex();
@@ -337,7 +362,7 @@ export async function createServer(adapter, options) {
337
362
  const deliveryCache = new DeliveryDerivationCache({
338
363
  listViews: () => adapter.sessionsDetail(),
339
364
  probes: vacuityProbes,
340
- isDelivered: (runId) => deliveryIndex.urlFor(runId) !== undefined,
365
+ isDelivered: (runId) => deliveryIndex.isDelivered(runId),
341
366
  canDeliver,
342
367
  log: (m) => app.log.warn(m),
343
368
  // Non-probe derivation throws are defects — error level, so the diagnostics ring sees them.
@@ -365,7 +390,7 @@ export async function createServer(adapter, options) {
365
390
  try {
366
391
  // Resume/retry re-terminals: already resolved once, and a terminal run's PR URL never
367
392
  // changes — never re-read, never double-write the trail.
368
- if (deliveryIndex.urlFor(runId) !== undefined)
393
+ if (deliveryIndex.isDelivered(runId))
369
394
  return;
370
395
  const views = await adapter.sessionsDetail();
371
396
  const view = views.find((v) => v.session.id === runId);
@@ -380,28 +405,35 @@ export async function createServer(adapter, options) {
380
405
  const output = await adapter.workOutput(coreUnitId(runId, unit));
381
406
  if (output === null)
382
407
  return;
383
- const url = prUrlFrom(output);
384
- 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)
385
412
  return;
386
413
  // The durable record first, then the read-side index — the same write order as
387
414
  // `guidance.set`, so the index can only LAG a crash (rehydrated at next boot), never
388
415
  // hold a record the trail does not.
389
- audit.record('run.delivered', DAEMON_ACTOR, {
390
- runId,
391
- detail: { url },
392
- });
393
- 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);
394
421
  }
395
422
  catch (err) {
396
423
  app.log.warn(`[runs] delivery resolution for ${runId} failed (field absent until restart replays the trail): ${err instanceof Error ? err.message : String(err)}`);
397
424
  }
398
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
+ };
399
433
  const projectBus = options?.projectEvents?.disabled === true
400
434
  ? null
401
435
  : await startProjectBus({
402
- ...(options?.projectEvents?.dbPath !== undefined
403
- ? { dbPath: options.projectEvents.dbPath }
404
- : {}),
436
+ ...busOf(options?.projectEvents?.dbPath),
405
437
  ...(options?.projectEvents?.pollIntervalMs !== undefined
406
438
  ? { pollIntervalMs: options.projectEvents.pollIntervalMs }
407
439
  : {}),
@@ -422,7 +454,6 @@ export async function createServer(adapter, options) {
422
454
  // is still preferred over its file once armed; the seams themselves arm further down.
423
455
  let draftSub = null;
424
456
  let editSub = null;
425
- let demoSub = null;
426
457
  let chatSub = null;
427
458
  /** The crew-side half of deleting an interactive doc (crew#338): drop the doc's replay-dedup
428
459
  * rows from all four handoff ledgers — the draft leg keys by DOCUMENT ID ("one first draft
@@ -433,11 +464,11 @@ export async function createServer(adapter, options) {
433
464
  * override, crew#353/#398). Never throws — the report says what happened. */
434
465
  const crewStateDir = crewStateHome();
435
466
  // F-046: the create-time doc → subject-repo bindings, shared by the proxy (records) and the
436
- // 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
437
468
  // `crew-grounding.json` sidecar beside the doc's `versions.json` under the project's docs root
438
469
  // (doc-grounding.ts says why not the state home: core's fence refuses unregistered entries).
439
470
  const docGrounding = new DocGroundingStore();
440
- /** 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
441
472
  * still preferred over its file) — shared by the doc-delete sweep and the doc↔run index. */
442
473
  const docLedgerSources = () => [
443
474
  {
@@ -458,12 +489,6 @@ export async function createServer(adapter, options) {
458
489
  path: options?.interactiveChatEvents?.ledgerPath ??
459
490
  join(crewStateDir, 'interactive-chat-ledger.json'),
460
491
  },
461
- {
462
- name: 'demo',
463
- ledger: demoSub?.ledger,
464
- path: options?.interactiveDemoEvents?.ledgerPath ??
465
- join(crewStateDir, 'interactive-demo-ledger.json'),
466
- },
467
492
  ];
468
493
  const dropDocLedgerRows = (documentId) => sweepDocLedgers(documentId, docLedgerSources());
469
494
  // Wave 6 (F-4R2-006 root fix): the document ↔ run binding as a direct read off the SAME four
@@ -477,9 +502,7 @@ export async function createServer(adapter, options) {
477
502
  const interactiveRelay = options?.interactiveWsRelay?.disabled === true
478
503
  ? null
479
504
  : await startInteractiveWsRelay({
480
- ...(options?.interactiveWsRelay?.dbPath !== undefined
481
- ? { dbPath: options.interactiveWsRelay.dbPath }
482
- : {}),
505
+ ...busOf(options?.interactiveWsRelay?.dbPath),
483
506
  ...(options?.interactiveWsRelay?.pollIntervalMs !== undefined
484
507
  ? { pollIntervalMs: options.interactiveWsRelay.pollIntervalMs }
485
508
  : {}),
@@ -506,6 +529,31 @@ export async function createServer(adapter, options) {
506
529
  await interactiveRelay.stop();
507
530
  });
508
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
+ }
509
557
  /** The post-commit half of a project-FILED launch, shared with the launch route (§2.2/§4):
510
558
  * the engine already attached the crew.run membership atomically with the launch — here we
511
559
  * tag future /ws frames and announce the attach on the project bus. */
@@ -520,34 +568,16 @@ export async function createServer(adapter, options) {
520
568
  /** The docs root a project's interactive docs live under — the SAME per-project resolution
521
569
  * the project routes and the interactive proxy use (DES-MERGE-001 §7.1/§7.2; partitioned
522
570
  * per project since crew#472, with an event that carries no `project_id` belonging to
523
- * Unfiled). Shared by the edit seam (demo-kind gate, CREW-UX-9), the demo seam (spec
524
- * 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
525
572
  * REAL paths here exactly as the routes check it (crew#474 — one walk, `bridge-root.ts`):
526
573
  * a symlinked `projects/<id>` throws `InteractivePartitionRefusedError` into the seam's
527
574
  * handler (logged by its `onError`, the event unanswered — fail closed) instead of being
528
575
  * followed into another project's docs; the seams only READ under a root the routes
529
576
  * materialized, so a missing partition is returned as spelled and nothing is created. */
530
577
  const interactiveDocsRoot = (projectId) => resolveProjectInteractiveRoot(projectId, projectId !== undefined ? projectSettings.get(projectId) : null);
531
- // Arm the opt-in QE gate-event subscription (crew's bus seam). Failure to
532
- // arm is LOUD but non-fatal: the acceptance route never depends on the bus.
533
- if (options?.qeGateEvents?.enabled === true) {
534
- const { dbPath, pollIntervalMs } = options.qeGateEvents;
535
- const sub = await startQeGateSubscriber(qeGateCache, {
536
- ...(dbPath !== undefined ? { dbPath } : {}),
537
- ...(pollIntervalMs !== undefined ? { pollIntervalMs } : {}),
538
- log: (m) => app.log.warn(m),
539
- logError: (m) => app.log.error(m),
540
- });
541
- if (sub !== null) {
542
- app.log.info(`qe gate-event subscription armed (filter wicked.qe.**)`);
543
- app.addHook('onClose', async () => {
544
- await sub.stop();
545
- });
546
- }
547
- }
548
578
  // ── A STUB ENGINE NEVER ANSWERS ANOTHER PRODUCT'S TRAFFIC (crew#309) ────────────────────────
549
579
  //
550
- // 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
551
581
  // to wicked-interactive's events by LAUNCHING A GOVERNED RUN. Under `serve --stub` the engine is
552
582
  // `Core.spawnStub` — a `StubDispatcher` (every seat votes for the first roster option, no
553
583
  // subprocess) plus a `StubStepRunner` (fixed text, no CLI) — so such a run resolves every phase
@@ -556,7 +586,8 @@ export async function createServer(adapter, options) {
556
586
  // approved outline — moving on…", "Gate approved the draft…". Nothing ran. Nothing was written.
557
587
  // Two gate approvals were announced anyway.
558
588
  //
559
- // 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
560
591
  // (`wicked-crew-interactive-draft`), so on the shared bus a stub daemon and the production
561
592
  // daemon are ONE consumer group — a frame claimed by the stub is a frame the real daemon never
562
593
  // sees. That is what wicked-crew#309 recorded: bus rows 242632–242643 carry a complete draft
@@ -567,8 +598,8 @@ export async function createServer(adapter, options) {
567
598
  // convening, which is the StubDispatcher's signature; the real runs 5 minutes later took ~95s to
568
599
  // vote and split their seats (`pi` for outline, `claude` for draft).
569
600
  //
570
- // So: refuse to arm, loudly. The deny is scoped to the ANSWERERS on purpose — the QE seam above
571
- // 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
572
603
  // narrates governance, so neither can fabricate a verdict. Offline/deterministic runs are NOT
573
604
  // lost by this: every harness in `e2e/` already does the correct thing, keeping the REAL engine
574
605
  // (`stub: false`) and registering a scripted stub SEAT in the roster — which exercises planning,
@@ -593,7 +624,7 @@ export async function createServer(adapter, options) {
593
624
  if (options?.interactiveDraftEvents?.enabled === true && !refuseStubSeam('interactive-draft')) {
594
625
  const o = options.interactiveDraftEvents;
595
626
  draftSub = await startInteractiveDraftSubscriber(adapter, {
596
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
627
+ ...busOf(o.dbPath),
597
628
  ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
598
629
  ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
599
630
  ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
@@ -627,14 +658,10 @@ export async function createServer(adapter, options) {
627
658
  // Arm the opt-in interactive STRUCTURAL-edit answering seam (task #86 final leg). Same
628
659
  // posture as the draft seam: failure to arm is LOUD but non-fatal — interactive's assist
629
660
  // loop is the fallback answerer, and this daemon must boot on a machine whose bus is broken.
630
- // `demoSub` (declared above) is deliberately still null here: the demo seam arms after (it
631
- // must, so the per-doc serialization contract can see the edit seam's in-flight set), but the
632
- // edit seam's demo-kind gate needs to know at EVENT time whether the demo seam actually came
633
- // up — a closure over the binding reads the settled value; a boolean captured here would not.
634
661
  if (options?.interactiveEditEvents?.enabled === true && !refuseStubSeam('interactive-edit')) {
635
662
  const o = options.interactiveEditEvents;
636
663
  editSub = await startInteractiveEditSubscriber(adapter, {
637
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
664
+ ...busOf(o.dbPath),
638
665
  ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
639
666
  ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
640
667
  ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
@@ -645,11 +672,9 @@ export async function createServer(adapter, options) {
645
672
  // The quality-floor skill gate (draft-skill.ts): stamped only when the published snapshot holds it;
646
673
  // the arm-time answer is recorded for /diagnostics (crew#661).
647
674
  skillHeld: phaseSkills.probe('interactive-edit', INTERACTIVE_EDIT_WORKFLOW_DEF, skillHeld),
648
- // The demo-kind gate (CREW-UX-9): a demo doc's step feedback is the demo seam's — but
649
- // only when that seam is actually up. Probed per event (the demo seam arms below), so an
650
- // un-armed demo seam gets an honest error status instead of a silent, unanswerable drop.
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).
651
677
  resolveDocsRoot: o.resolveDocsRoot ?? interactiveDocsRoot,
652
- demoSeamArmed: () => demoSub !== null,
653
678
  onRunFiled: fileRun,
654
679
  log: (m) => app.log.warn(m),
655
680
  logError: (m) => app.log.error(m),
@@ -665,37 +690,6 @@ export async function createServer(adapter, options) {
665
690
  });
666
691
  }
667
692
  }
668
- // Arm the opt-in interactive DEMO answering seam (CREW-UX-9 — the retired assist agent's
669
- // Step 8). Same posture as the sibling seams: failure to arm is LOUD but non-fatal — a demo
670
- // doc then keeps its placeholder, and this daemon must boot on a machine whose bus is
671
- // broken. Armed beside the edit seam because the two split `feedback.processed` by the doc
672
- // manifest's kind (demo → re-author the spec here; everything else → structural edit there).
673
- if (options?.interactiveDemoEvents?.enabled === true && !refuseStubSeam('interactive-demo')) {
674
- const o = options.interactiveDemoEvents;
675
- demoSub = await startInteractiveDemoSubscriber(adapter, {
676
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
677
- ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
678
- ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
679
- ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
680
- ...(o.demoDir !== undefined ? { demoDir: o.demoDir } : {}),
681
- ...(o.clisJson !== undefined ? { clisJson: o.clisJson } : {}),
682
- // The roster WITH standing when no override is set (F-RECON-002/003).
683
- roster: rosterWithStanding,
684
- resolveDocsRoot: o.resolveDocsRoot ?? interactiveDocsRoot,
685
- onRunFiled: fileRun,
686
- onRunLaunched: (runId, detail) => { recordRunLaunched(audit, runTimingIndex, DAEMON_ACTOR, runId, detail); },
687
- groundingStore: docGrounding,
688
- log: (m) => app.log.warn(m),
689
- logError: (m) => app.log.error(m),
690
- });
691
- if (demoSub !== null) {
692
- const sub = demoSub;
693
- app.log.info('interactive-demo subscription armed (filters wicked.interactive.doc.created + feedback.processed, kind:demo)');
694
- app.addHook('onClose', async () => {
695
- await sub.stop();
696
- });
697
- }
698
- }
699
693
  // Arm the opt-in interactive CHAT answering seam (CREW-UX-5 — the iteration ask). Same
700
694
  // posture again: failure to arm is LOUD but non-fatal. Armed AFTER the sibling seams so the
701
695
  // per-doc serialization contract (no draft/edit/chat run races another on one doc) can
@@ -704,7 +698,7 @@ export async function createServer(adapter, options) {
704
698
  if (options?.interactiveChatEvents?.enabled === true && !refuseStubSeam('interactive-chat')) {
705
699
  const o = options.interactiveChatEvents;
706
700
  chatSub = await startInteractiveChatSubscriber(adapter, {
707
- ...(o.dbPath !== undefined ? { dbPath: o.dbPath } : {}),
701
+ ...busOf(o.dbPath),
708
702
  ...(o.pollIntervalMs !== undefined ? { pollIntervalMs: o.pollIntervalMs } : {}),
709
703
  ...(o.heartbeatMs !== undefined ? { heartbeatMs: o.heartbeatMs } : {}),
710
704
  ...(o.ledgerPath !== undefined ? { ledgerPath: o.ledgerPath } : {}),
@@ -753,14 +747,23 @@ export async function createServer(adapter, options) {
753
747
  // candidates. Views with no units (older engines, stub adapters) keep the historical
754
748
  // `unit_ix` fallback.
755
749
  const cursor = resolveCursorUnit(v);
756
- // DES-L3 PR-3E (F-RC1-012): an EVALUATOR cursor must never fail over onto a seat
757
- // that built the work it reviews — the creators' seats ride as `avoid`. A free-text
758
- // unit carries no role → no constraint (today's pick).
759
- const avoid = cursor?.role === 'evaluator'
760
- ? v.units
761
- .filter((u) => u.role === 'creator' && u.assigned_cli != null)
762
- .map((u) => u.assigned_cli)
763
- : [];
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);
764
767
  return {
765
768
  id: v.session.id,
766
769
  ord: cursor?.ord ?? v.session.unit_ix,
@@ -820,6 +823,13 @@ export async function createServer(adapter, options) {
820
823
  onTurnTimeout: ({ session, ...detail }) => {
821
824
  audit.record('run.turn.timedout', { id: 'stall-watchdog', kind: 'system', trust: 'admin' }, { runId: session, detail });
822
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
+ }),
823
833
  log: (m) => app.log.warn(m),
824
834
  });
825
835
  // The daemon's single CoreEvent subscription fans out here: cache gate prompts
@@ -846,6 +856,51 @@ export async function createServer(adapter, options) {
846
856
  // written from the stamped frames below, dropped with the chat on `chatClosed`, served on
847
857
  // `GET /chats/:id.messages`.
848
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
+ }
849
904
  // crew#619: see declaration of chatRetained/runToChat above (before the hydration block).
850
905
  // linkChatRun populates both maps and is called from the hydration block (function-hoisted)
851
906
  // and from the launch route (via RoutesRuntime.linkChatRun).
@@ -908,6 +963,13 @@ export async function createServer(adapter, options) {
908
963
  const session = typeof event.session === 'string' ? event.session : undefined;
909
964
  const projectId = session !== undefined ? membershipIndex.projectOf(session) : undefined;
910
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);
911
973
  // The delivered-PR record (CREW-UX-8, crew#321): resolved once per run at its terminal
912
974
  // frame, best-effort, off the hot path — see `resolveRunDelivery` above for why BOTH
913
975
  // terminal frames trigger it and why a failed deliver is a no-op. THEN the delivery-
@@ -977,7 +1039,6 @@ export async function createServer(adapter, options) {
977
1039
  adapter,
978
1040
  audit,
979
1041
  index: testSets,
980
- workflows: () => adapter.listWorkflows(),
981
1042
  deliveryUrlFor: (runId) => deliveryIndex.urlFor(runId),
982
1043
  labelFor: (runId) => {
983
1044
  const attach = groupIndex.attachOf(runId);
@@ -1080,14 +1141,17 @@ export async function createServer(adapter, options) {
1080
1141
  try {
1081
1142
  done(null, JSON.parse(body));
1082
1143
  }
1083
- catch (err) {
1144
+ catch {
1084
1145
  // A syntactically invalid body is the CLIENT's error: without an explicit statusCode
1085
1146
  // Fastify reports a parser error as a 500, which misfiles "you sent `{not json`" as a
1086
1147
  // server fault — on the elicitation answer path that told an operator the daemon broke
1087
1148
  // when their request did. 400 matches Fastify's own default JSON parser
1088
1149
  // (FST_ERR_CTP_INVALID_JSON) and every schema-level 400 these routes already return.
1089
- err.statusCode = 400;
1090
- 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' }));
1091
1155
  }
1092
1156
  });
1093
1157
  await app.register(fastifyWebsocket);
@@ -1109,13 +1173,47 @@ export async function createServer(adapter, options) {
1109
1173
  // diagnostics reads the bundle's shipped version manifest from the same root the static
1110
1174
  // handler serves. One resolution, two consumers.
1111
1175
  const studioRoot = options?.studioRoot ?? defaultStudioRoot();
1112
- 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, {
1113
1210
  bus: projectBus,
1114
1211
  index: membershipIndex,
1115
1212
  log: (m) => app.log.warn(m),
1116
1213
  settings: projectSettings,
1117
1214
  }, { audit, authMode: auth.mode }, {
1118
1215
  seatHealth,
1216
+ deliveryFreeze,
1119
1217
  // wicked-studio#284: the watchdog's remembered frames ride `GET /runs/:id/events`.
1120
1218
  stallFrames: (runId) => stallFrameIndex.framesFor(runId),
1121
1219
  // The SAME standing accessor the seams and the adapter launch with (F-RECON-002/003).
@@ -1149,6 +1247,13 @@ export async function createServer(adapter, options) {
1149
1247
  interactiveBridgeBusDataDir: options?.interactiveBridge?.busDataDir ?? null,
1150
1248
  docGrounding,
1151
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,
1152
1257
  // wicked-core#411 / crew#497: the live state-home classification the routes report and gate on.
1153
1258
  stateHome: stateHomeWatch,
1154
1259
  // Routes that say something to the thread (a refused chat seat, F-2R2-007) emit through the
@@ -1157,6 +1262,68 @@ export async function createServer(adapter, options) {
1157
1262
  // crew#619: retain a chat's transcript for the lifetime of its promoted run.
1158
1263
  linkChatRun,
1159
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));
1160
1327
  // The UI-emittable direction of the interactive seam. Registered unconditionally (a null relay
1161
1328
  // answers 503, not 404) and BEFORE the static/SPA fallback below, like every other API route.
1162
1329
  registerInteractiveEventRoutes(app, interactiveRelay);
@@ -1213,6 +1380,11 @@ export async function createServer(adapter, options) {
1213
1380
  }
1214
1381
  return app;
1215
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
+ }
1216
1388
  export async function startServer(adapter, port = 7701, host = '127.0.0.1', options) {
1217
1389
  const app = await createServer(adapter, options);
1218
1390
  await app.listen({ port, host });
@@ -1220,6 +1392,10 @@ export async function startServer(adapter, port = 7701, host = '127.0.0.1', opti
1220
1392
  const boundPort = typeof addr === 'object' && addr ? addr.port : port;
1221
1393
  const boundHost = typeof addr === 'object' && addr ? addr.address : host;
1222
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);
1223
1399
  return { app, port: boundPort, host: boundHost };
1224
1400
  }
1225
1401
  //# sourceMappingURL=server.js.map