@desplega.ai/agent-swarm 2.0.0 → 2.1.0

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 (238) hide show
  1. package/dist/{acp-adapter-sj2g0m84.js → acp-adapter-kr4abrsk.js} +10 -10
  2. package/dist/{actions-fegk44gr.js → actions-eh4g6w2r.js} +27 -27
  3. package/dist/{amp-adapter-ad8ft89x.js → amp-adapter-7h7wdt8q.js} +5 -5
  4. package/dist/{app-kfvgbvzy.js → app-7k8s9w5p.js} +9 -9
  5. package/dist/{assistant-z7zdy9ja.js → assistant-wswt2y7p.js} +28 -28
  6. package/dist/{boot-reembed-j8xa181e.js → boot-reembed-9apm9qw6.js} +7 -7
  7. package/dist/{boot-reembed-5c2z0mbe.js → boot-reembed-whzbdthj.js} +7 -7
  8. package/dist/{boot-scrub-sweep-hnkn3rgt.js → boot-scrub-sweep-ndzmvmgt.js} +8 -8
  9. package/dist/{boot-seeds-kwjq4d2z.js → boot-seeds-e9sta0yt.js} +13 -11
  10. package/dist/{claude-adapter-9vmvpvgd.js → claude-adapter-4mqd3r6r.js} +11 -11
  11. package/dist/{claude-managed-adapter-k9qae05j.js → claude-managed-adapter-9wswb52m.js} +3 -3
  12. package/dist/{claude-managed-setup-q54zx5q2.js → claude-managed-setup-tpe1effx.js} +4 -4
  13. package/dist/{claude-sdk-session-9jye6cxr.js → claude-sdk-session-q49ghmh9.js} +10340 -10197
  14. package/dist/{cli-ke896fcf.js → cli-01xvj1rq.js} +10 -10
  15. package/dist/{cli-tr4sxp68.js → cli-035vekcw.js} +2 -2
  16. package/dist/{cli-z5jjpaxw.js → cli-0pd8hzhc.js} +3 -3
  17. package/dist/{cli-n5vwg842.js → cli-0veb92a3.js} +6 -1
  18. package/dist/{cli-wayw93nt.js → cli-10jesr25.js} +3 -3
  19. package/dist/{cli-se8w6wny.js → cli-1y7yywmj.js} +1 -1
  20. package/dist/{cli-v1yb0rqb.js → cli-20krdhz5.js} +47 -19
  21. package/dist/{cli-d5f9ed5c.js → cli-29rja109.js} +39 -20
  22. package/dist/{cli-apx87jne.js → cli-2pwa0q4d.js} +15 -4
  23. package/dist/{cli-ee9gtz70.js → cli-4mengw46.js} +348 -25
  24. package/dist/{cli-03gedhng.js → cli-5twdke5x.js} +6 -6
  25. package/dist/{cli-6razj6t4.js → cli-711vwbxn.js} +2 -2
  26. package/dist/{cli-s2sp957d.js → cli-78vv7446.js} +1 -1
  27. package/dist/{cli-3fbmmawm.js → cli-7n6s5man.js} +4 -4
  28. package/dist/{cli-hnhtk6cj.js → cli-8wwxw8a3.js} +1 -1
  29. package/dist/{cli-hnmhtzvh.js → cli-9jzdj90z.js} +2 -0
  30. package/dist/{cli-skxvvpap.js → cli-cvv9k0hh.js} +28 -25
  31. package/dist/{cli-74ahz61x.js → cli-d9286m9h.js} +2 -2
  32. package/dist/{cli-4jxm2qtz.js → cli-gxzkg1de.js} +28 -16
  33. package/dist/{cli-zkpks3r3.js → cli-h1arqn31.js} +1 -1
  34. package/dist/{cli-r62a35ea.js → cli-hqr88ew6.js} +1 -1
  35. package/dist/{cli-ra0xzggj.js → cli-j67mh2vg.js} +2 -2
  36. package/dist/{cli-29jc5q7w.js → cli-ksphgh48.js} +2 -2
  37. package/dist/{cli-eejsgvm4.js → cli-mgz4nbgg.js} +4 -4
  38. package/dist/{cli-vatyhshy.js → cli-mz5as5ha.js} +4 -4
  39. package/dist/{cli-f7kr7svz.js → cli-n8ndw8j7.js} +2 -2
  40. package/dist/{cli-1e4b6bzh.js → cli-naqzr17j.js} +7 -7
  41. package/dist/{cli-q0ke1yww.js → cli-nm1p8jfr.js} +6 -6
  42. package/dist/{cli-m50t4309.js → cli-p3zbc1rw.js} +5 -5
  43. package/dist/{cli-cgtksawx.js → cli-pe05f8yk.js} +23 -3
  44. package/dist/{cli-vdach5a9.js → cli-qmzsbkvy.js} +4 -4
  45. package/dist/{cli-n3hzg89b.js → cli-qnm4e9e4.js} +57 -579
  46. package/dist/{cli-s4setq91.js → cli-qqy4q48b.js} +3 -3
  47. package/dist/{cli-k382tshb.js → cli-r35rw5de.js} +8 -7
  48. package/dist/{cli-k3dp5tpf.js → cli-r610p9bs.js} +2 -2
  49. package/dist/{cli-e6mpjhed.js → cli-rc6pf5kv.js} +1 -1
  50. package/dist/{cli-3dj8k4vy.js → cli-rdxm77k3.js} +1 -1
  51. package/dist/{cli-tqaazemc.js → cli-s69nfyeh.js} +1 -1
  52. package/dist/{cli-6jyj59nb.js → cli-sawv63e8.js} +2 -2
  53. package/dist/{cli-q50942rs.js → cli-sg19gbf0.js} +1 -1
  54. package/dist/{cli-p1x85d2t.js → cli-shk7r5cs.js} +12 -9
  55. package/dist/{cli-przpmdgr.js → cli-sksetb2q.js} +11 -1
  56. package/dist/{cli-d2qr7aj4.js → cli-t02z59f2.js} +1 -1
  57. package/dist/{cli-j0pfptdp.js → cli-v7g551te.js} +39 -43
  58. package/dist/{cli-bzszjf2p.js → cli-vgqzp4em.js} +519 -126
  59. package/dist/cli-wfnemr1t.js +86 -0
  60. package/dist/{cli-ne236afy.js → cli-wms6x9db.js} +8 -8
  61. package/dist/{cli-5hkm7whv.js → cli-x1r545x8.js} +1 -1
  62. package/dist/{cli-kt6zqg0e.js → cli-xczsgshn.js} +2 -2
  63. package/dist/{cli-ef1hg1wv.js → cli-y3167wt1.js} +430 -238
  64. package/dist/{cli-tsde1hdr.js → cli-yjmqpxt3.js} +12 -11
  65. package/dist/{cli-9nfx44eh.js → cli-z780ec7c.js} +1 -1
  66. package/dist/{cli-r2pnvj8g.js → cli-zm9t2vde.js} +2 -2
  67. package/dist/cli.js +24 -23
  68. package/dist/{codex-adapter-y81sssk0.js → codex-adapter-4z16v9n0.js} +8 -8
  69. package/dist/{codex-login-f2rcfpp9.js → codex-login-pkepy2j1.js} +2 -2
  70. package/dist/{codex-session-runner-8vx4j6a9.js → codex-session-runner-rvms9y2x.js} +8 -8
  71. package/dist/{commands-kcpvpcrr.js → commands-2a9rtyat.js} +8 -8
  72. package/dist/{controller-ywrmt6yd.js → controller-3k3madbn.js} +4 -4
  73. package/dist/{controllers-connect-kwpvj3rf.js → controllers-connect-v9yn8zb9.js} +30 -30
  74. package/dist/{cursor-adapter-cehv1qns.js → cursor-adapter-98gbhyzn.js} +4 -4
  75. package/dist/{db-ww5d7jq3.js → db-eywwsnjw.js} +9 -7
  76. package/dist/{db-k8ankj20.js → db-ncypk9np.js} +9 -9
  77. package/dist/{devin-adapter-x1022qb1.js → devin-adapter-zeq8b9gz.js} +2 -2
  78. package/dist/{dispatcher-e8gq5m68.js → dispatcher-w94kc575.js} +26 -26
  79. package/dist/{docker-ff6y1190.js → docker-b1e4cqab.js} +2 -2
  80. package/dist/{dsh-adapter-3epjm3af.js → dsh-adapter-47ztej36.js} +4 -4
  81. package/dist/{e2b-8ygwhg0f.js → e2b-bbt8ey45.js} +3 -3
  82. package/dist/{e2b-swhnpw3n.js → e2b-dp804sen.js} +3 -3
  83. package/dist/{event-bus-6axnngdj.js → event-bus-j2jdg3qb.js} +2 -2
  84. package/dist/{external-channel-h3359cwn.js → external-channel-zrkg8ar9.js} +8 -8
  85. package/dist/{grok-adapter-0d4x7gkx.js → grok-adapter-s9ncp9vy.js} +11 -11
  86. package/dist/{handlers-an1gzv21.js → handlers-kqftvjgw.js} +29 -29
  87. package/dist/{heartbeat-6dakeb61.js → heartbeat-9q96p8v9.js} +31 -31
  88. package/dist/{hook-6v6cv6d5.js → hook-56qe10dp.js} +7 -7
  89. package/dist/{hook-sa5xye4y.js → hook-yy6rw42k.js} +8 -8
  90. package/dist/{http-r7f3eky0.js → http-r2dnb8f8.js} +963 -75
  91. package/dist/{index-7ep4gzdk.js → index-3gtmkhc6.js} +26 -26
  92. package/dist/{index-v1v6n7hg.js → index-3k5hkz7r.js} +31 -31
  93. package/dist/{index-rmpw9a8q.js → index-5e101240.js} +26 -26
  94. package/dist/{index-s2wxgzr0.js → index-5wyfjc2c.js} +392 -15
  95. package/dist/{index-zk96aq1k.js → index-vxvhxxbn.js} +7 -7
  96. package/dist/{keepalive-be9vwy5h.js → keepalive-psj6j54c.js} +9 -9
  97. package/dist/{lead-0861cwrf.js → lead-hajz4688.js} +43 -43
  98. package/dist/{maintenance-mzzgc8bn.js → maintenance-ps02sb5m.js} +7 -7
  99. package/dist/{node-mn1zs7an.js → node-mwb5r697.js} +1 -1
  100. package/dist/{oauth-refresh-sweep-cpdnfwdj.js → oauth-refresh-sweep-0284h93b.js} +8 -8
  101. package/dist/{onboard-fh8qs4r3.js → onboard-rbf6bg01.js} +2 -2
  102. package/dist/{opencode-adapter-nm42wwj8.js → opencode-adapter-13dcatgq.js} +8 -8
  103. package/dist/{otel-impl-x6c1c9g4.js → otel-impl-zvqcbhp7.js} +2 -2
  104. package/dist/{pi-mono-adapter-9n2ya240.js → pi-mono-adapter-xxy47q4v.js} +7 -7
  105. package/dist/{pricing-refresh-qb0rv0ap.js → pricing-refresh-kefx9v3t.js} +9 -9
  106. package/dist/{rbac-roles-1gzdwbh1.js → rbac-roles-3m36rxm2.js} +8 -8
  107. package/dist/{rbac-roles-3mraey52.js → rbac-roles-z76xv0hq.js} +9 -9
  108. package/dist/{render-v2-agpa45zz.js → render-v2-wz73bs25.js} +9 -9
  109. package/dist/{resume-x0ydy9s9.js → resume-k4wzge8w.js} +14 -14
  110. package/dist/{setup-as9ga0d6.js → setup-2fp2pypp.js} +2 -2
  111. package/dist/{work-objects-y5g1y7ev.js → work-objects-6k0ywz3m.js} +29 -29
  112. package/dist/{worker-860c3e38.js → worker-5v3rxsdh.js} +43 -43
  113. package/dist/{x-m477fr0h.js → x-4mtdeym1.js} +2 -2
  114. package/openapi.json +777 -6
  115. package/package.json +8 -7
  116. package/src/agentmail/handlers.ts +21 -11
  117. package/src/apps/definition.ts +16 -0
  118. package/src/azure-devops/api.ts +52 -2
  119. package/src/azure-devops/boards-sync.ts +50 -20
  120. package/src/azure-devops/index.ts +1 -0
  121. package/src/azure-devops/outbound.ts +95 -0
  122. package/src/be/boot-seeds.ts +11 -0
  123. package/src/be/db/model-profiles.ts +315 -0
  124. package/src/be/db/tasks/read.ts +7 -0
  125. package/src/be/db.ts +53 -47
  126. package/src/be/migrations/219_relabel_pi_context_compactions.sql +22 -0
  127. package/src/be/migrations/220_model_profiles.sql +137 -0
  128. package/src/be/model-tier-resolution.ts +145 -11
  129. package/src/be/model-validation.ts +8 -1
  130. package/src/be/scripts/typecheck.ts +12 -0
  131. package/src/be/secret-registry.ts +6 -2
  132. package/src/be/seed-model-profiles.ts +41 -0
  133. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
  134. package/src/be/seed-scripts/index.ts +9 -0
  135. package/src/be/swarm-config-guard.ts +1 -0
  136. package/src/commands/runner.ts +3 -17
  137. package/src/extensions/catalog.generated.json +17 -0
  138. package/src/extensions/contract-types.generated.ts +1 -1
  139. package/src/extensions/loader.ts +10 -5
  140. package/src/heartbeat/heartbeat.ts +2 -1
  141. package/src/http/agents.ts +49 -2
  142. package/src/http/all-routes.ts +1 -0
  143. package/src/http/apps.ts +30 -3
  144. package/src/http/config.ts +26 -2
  145. package/src/http/core.ts +4 -0
  146. package/src/http/index.ts +11 -1
  147. package/src/http/model-profiles.ts +303 -0
  148. package/src/http/pages-public.ts +11 -5
  149. package/src/http/poll.ts +16 -5
  150. package/src/http/script-runs.ts +1 -1
  151. package/src/http/scripts.ts +25 -2
  152. package/src/http/tasks.ts +18 -0
  153. package/src/http/webhooks.ts +1 -2
  154. package/src/pages/feedback-overlay.ts +65 -6
  155. package/src/providers/acp-targets.ts +1 -1
  156. package/src/providers/amp-adapter.ts +1 -1
  157. package/src/providers/dsh-adapter.ts +1 -1
  158. package/src/providers/pi-mono-extension.ts +17 -3
  159. package/src/providers/reasoning-effort.ts +10 -0
  160. package/src/rbac/legacy-policy.ts +8 -0
  161. package/src/rbac/permissions.ts +5 -0
  162. package/src/scheduler/deferred-task-waits.ts +18 -5
  163. package/src/scheduler/scheduler.ts +7 -1
  164. package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
  165. package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
  166. package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
  167. package/src/scripts-runtime/extract-args-schema.ts +9 -8
  168. package/src/scripts-runtime/quickjs-compat.ts +464 -0
  169. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  170. package/src/scripts-runtime/types/stdlib.d.ts +12 -0
  171. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  172. package/src/scripts-runtime/types/swarm-sdk.d.ts +12 -0
  173. package/src/server.ts +10 -1
  174. package/src/slack/blocks.ts +4 -2
  175. package/src/slack/reaction-shortcode.ts +14 -0
  176. package/src/slack/render-v2.ts +12 -7
  177. package/src/slack/responses.ts +5 -4
  178. package/src/slack/thread-buffer.ts +41 -22
  179. package/src/tasks/worker-follow-up.ts +11 -2
  180. package/src/tests/agentmail-filters.test.ts +47 -0
  181. package/src/tests/apps-spike2.test.ts +35 -0
  182. package/src/tests/azure-devops-outbound.test.ts +420 -0
  183. package/src/tests/db-task-read-contract.test.ts +2 -0
  184. package/src/tests/db-task-write-contract.test.ts +1 -0
  185. package/src/tests/deferred-task-wake.test.ts +78 -1
  186. package/src/tests/docs-index-footprint.test.js +2 -2
  187. package/src/tests/dsh-adapter.test.ts +1 -1
  188. package/src/tests/env-flag.test.ts +1 -0
  189. package/src/tests/extensions-catalog.test.ts +1 -0
  190. package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
  191. package/src/tests/extensions-loader.test.ts +15 -0
  192. package/src/tests/heartbeat-jev-gate-credentials.test.ts +168 -0
  193. package/src/tests/heartbeat-jev-gate.test.ts +385 -0
  194. package/src/tests/model-profile-backcompat.test.ts +327 -0
  195. package/src/tests/model-profile-providers.test.ts +302 -0
  196. package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
  197. package/src/tests/model-profiles-api.test.ts +614 -0
  198. package/src/tests/pages-feedback-keys.test.ts +121 -0
  199. package/src/tests/pages-feedback.test.ts +3 -0
  200. package/src/tests/pi-context-compaction.test.ts +193 -0
  201. package/src/tests/profile-claim.test.ts +269 -0
  202. package/src/tests/quickjs-compat.test.ts +213 -0
  203. package/src/tests/rbac-engine.test.ts +21 -0
  204. package/src/tests/script-connections.test.ts +4 -0
  205. package/src/tests/script-executor-conformance.test.ts +252 -1
  206. package/src/tests/script-executor-quickjs.test.ts +96 -0
  207. package/src/tests/script-runs-http.test.ts +89 -0
  208. package/src/tests/scripts-http.test.ts +16 -0
  209. package/src/tests/secret-scrubber.test.ts +10 -0
  210. package/src/tests/seed-model-profiles.test.ts +174 -0
  211. package/src/tests/seed-scripts.test.ts +2 -2
  212. package/src/tests/skill-invocations.test.ts +4 -2
  213. package/src/tests/slack-blocks.test.ts +36 -0
  214. package/src/tests/slack-inline-output.test.ts +14 -0
  215. package/src/tests/slack-reaction-config.test.ts +20 -0
  216. package/src/tests/slack-render-v2.test.ts +37 -0
  217. package/src/tests/slack-thread-buffer.test.ts +98 -1
  218. package/src/tests/workflow-llm.test.ts +61 -0
  219. package/src/tools/model-profiles/list-model-profiles.ts +54 -0
  220. package/src/tools/request-human-input.ts +11 -3
  221. package/src/tools/script-apis.ts +5 -1
  222. package/src/tools/script-upsert.ts +4 -1
  223. package/src/tools/send-task.ts +24 -1
  224. package/src/tools/system-one-decide.ts +104 -0
  225. package/src/tools/task-action.ts +22 -1
  226. package/src/tools/tool-config.ts +6 -0
  227. package/src/types.ts +36 -1
  228. package/src/utils/secret-scrubber.ts +19 -12
  229. package/src/utils/vcs-repo-match.ts +29 -0
  230. package/src/workflows/executors/raw-llm.ts +8 -3
  231. package/src/workflows/executors/system-one-decision.ts +21 -11
  232. package/src/workflows/executors/workflow-llm.ts +22 -0
  233. package/src/workflows/index.ts +21 -12
  234. package/templates/extensions/README.md +1 -0
  235. package/templates/extensions/comms-adherence-gate/README.md +88 -0
  236. package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
  237. package/templates/extensions/comms-adherence-gate/manifest.json +8 -0
  238. package/dist/cli-5t4f29ez.js +0 -33
@@ -936,15 +936,29 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
936
936
  }
937
937
  }
938
938
 
939
+ const decided = await this.decide(config);
940
+ if (!decided.ok) return { status: "failed", error: decided.error };
941
+ if (!config.humanReview) return { status: "success", output: decided.output };
942
+ return this.reviewDecision(config, config.humanReview, decided.output, context, meta);
943
+ }
944
+
945
+ /**
946
+ * One decision from the provider, without the review band: resolve the target,
947
+ * send, validate. The credential stays in this process; the `system-one-decide`
948
+ * tool calls this so scripts get Jev answers without holding a key.
949
+ */
950
+ async decide(
951
+ config: SystemOneDecisionConfig,
952
+ ): Promise<{ ok: true; output: SystemOneDecisionOutput } | { ok: false; error: string }> {
939
953
  const providerId = config.provider;
940
954
  const provider: SystemOneProvider = SYSTEM_ONE_PROVIDERS[providerId];
941
955
 
942
956
  // A provider with no default sends no `model` when the node sets none.
943
957
  const model = config.model ?? provider.defaultModel;
944
958
  const modelProblem = systemOneModelProblem(providerId, model);
945
- if (modelProblem) return { status: "failed", error: modelProblem };
959
+ if (modelProblem) return { ok: false, error: modelProblem };
946
960
  const target = await this.resolveTarget(providerId, model);
947
- if (!target.ok) return { status: "failed", error: target.error };
961
+ if (!target.ok) return { ok: false, error: target.error };
948
962
  const { apiKey, endpoint } = target;
949
963
 
950
964
  const wire = provider.wire ?? SYSTEM_ONE_WIRE;
@@ -959,13 +973,13 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
959
973
  config.timeoutMs,
960
974
  config.maxRetries,
961
975
  );
962
- if (!sent.ok) return { status: "failed", error: scrub(sent.error) };
976
+ if (!sent.ok) return { ok: false, error: scrub(sent.error) };
963
977
 
964
978
  let parsed: unknown;
965
979
  try {
966
980
  parsed = JSON.parse(sent.text);
967
981
  } catch {
968
- return { status: "failed", error: "SystemOne response was not valid JSON" };
982
+ return { ok: false, error: "SystemOne response was not valid JSON" };
969
983
  }
970
984
  let decision: SystemOneDecisionOutput;
971
985
  try {
@@ -978,18 +992,14 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
978
992
  } catch (err) {
979
993
  if (err instanceof SystemOneWireError) {
980
994
  const withId = sent.requestId ? `${err.message} [request ${sent.requestId}]` : err.message;
981
- return { status: "failed", error: scrub(withId) };
995
+ return { ok: false, error: scrub(withId) };
982
996
  }
983
997
  if (err instanceof SystemOneContractError) {
984
- return {
985
- status: "failed",
986
- error: scrub(`SystemOne response failed validation: ${err.message}`),
987
- };
998
+ return { ok: false, error: scrub(`SystemOne response failed validation: ${err.message}`) };
988
999
  }
989
1000
  throw err;
990
1001
  }
991
- if (!config.humanReview) return { status: "success", output: decision };
992
- return this.reviewDecision(config, config.humanReview, decision, context, meta);
1002
+ return { ok: true, output: decision };
993
1003
  }
994
1004
 
995
1005
  /**
@@ -1,3 +1,5 @@
1
+ import { providerBaseUrl, providerForModel, providerKeyVars } from "@desplega/model-catalog";
2
+ import { selectCredential } from "../../utils/credentials";
1
3
  import { DEFAULT_MODEL, resolveCredential } from "../../utils/internal-ai/credentials";
2
4
  import {
3
5
  getOpenRouterAttributionHeaders,
@@ -25,6 +27,26 @@ export async function resolveWorkflowLlmConfig(
25
27
  env: NodeJS.ProcessEnv = process.env,
26
28
  options: { requireKind?: "openrouter" | "openai" } = {},
27
29
  ): Promise<WorkflowLlmConfig> {
30
+ // `nan/<model>`, `helmcode/<model>`, ...: an OpenAI-compatible registry
31
+ // provider is reached with its own key and base URL, never through OpenRouter.
32
+ const registryProvider = options.requireKind ? undefined : providerForModel(requestedModel);
33
+ if (registryProvider && requestedModel) {
34
+ const keyVar = providerKeyVars(registryProvider).find((name) => env[name]?.trim());
35
+ if (!keyVar) {
36
+ throw new Error(
37
+ `No ${registryProvider.displayName} credential found for ${requestedModel}. Set ${registryProvider.envVar}.`,
38
+ );
39
+ }
40
+ return {
41
+ // The API hydrates global config verbatim, so a key may still be a
42
+ // comma pool; workflow calls skip the runner's per-task pick.
43
+ apiKey: selectCredential(env[keyVar] as string, undefined, keyVar).selected,
44
+ baseURL: providerBaseUrl(registryProvider, env),
45
+ headers: {},
46
+ model: requestedModel.slice(registryProvider.id.length + 1),
47
+ };
48
+ }
49
+
28
50
  const credential = await resolveCredential({ env });
29
51
  if (!credential) {
30
52
  throw new Error("No workflow LLM credential found. Set OPENROUTER_API_KEY or OPENAI_API_KEY.");
@@ -37,7 +37,14 @@ import { startWaitPoller } from "./wait-poller";
37
37
  // ─── Module-level singleton ────────────────────────────────
38
38
 
39
39
  let _registry: ExecutorRegistry | null = null;
40
- let slackMessageTriggerListenerInitialized = false;
40
+ let eventTriggerListenersInitialized = false;
41
+
42
+ /** Bus events that start workflows with a matching `event` trigger. Keep in sync with `TriggerConfigSchema`. */
43
+ const EVENT_TRIGGER_NAMES = [
44
+ "slack.message",
45
+ "azure-devops.work_item.assigned",
46
+ "azure-devops.work_item.commented",
47
+ ] as const;
41
48
 
42
49
  function createBuiltInExecutorRegistry(): ExecutorRegistry {
43
50
  return createExecutorRegistry({
@@ -57,18 +64,20 @@ export function getExecutorRegistry(): ExecutorRegistry {
57
64
  return _registry;
58
65
  }
59
66
 
60
- function setupSlackMessageTriggerListener(): void {
61
- if (slackMessageTriggerListenerInitialized) return;
67
+ function setupEventTriggerListeners(): void {
68
+ if (eventTriggerListenersInitialized) return;
62
69
 
63
- workflowEventBus.on("slack.message", (payload) => {
64
- void handleEventTrigger("slack.message", payload, getExecutorRegistry()).catch((err) => {
65
- console.error(
66
- "[workflows] Failed to dispatch slack.message event trigger:",
67
- scrubSecrets(err instanceof Error ? err.message : String(err)),
68
- );
70
+ for (const eventName of EVENT_TRIGGER_NAMES) {
71
+ workflowEventBus.on(eventName, (payload) => {
72
+ void handleEventTrigger(eventName, payload, getExecutorRegistry()).catch((err) => {
73
+ console.error(
74
+ `[workflows] Failed to dispatch ${eventName} event trigger:`,
75
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
76
+ );
77
+ });
69
78
  });
70
- });
71
- slackMessageTriggerListenerInitialized = true;
79
+ }
80
+ eventTriggerListenersInitialized = true;
72
81
  }
73
82
 
74
83
  /**
@@ -84,7 +93,7 @@ export async function initWorkflows(): Promise<void> {
84
93
 
85
94
  // 2. Wire up resume listener (task.completed / task.failed / task.cancelled)
86
95
  setupWorkflowResumeListener(workflowEventBus, _registry);
87
- setupSlackMessageTriggerListener();
96
+ setupEventTriggerListeners();
88
97
 
89
98
  // 3. Recover incomplete runs (running + waiting) from previous lifecycle
90
99
  recoverIncompleteRuns(_registry).catch((err) => {
@@ -14,6 +14,7 @@ Each folder holds exactly one `manifest.yaml`, `manifest.yml`, or `manifest.json
14
14
  | `notify-on-complete` | `post.task.completed`, `post.task.failed` | Posts a summary to a Slack channel |
15
15
  | `deploy-awareness` | `pre.task.create` | Appends a one-line note to new tasks while a Dokploy deploy is in progress |
16
16
  | `slack-reply-gate` | `pre.task.create` | Asks Jev (TypeSafe or OpenRouter) whether Lead should answer a Slack thread follow-up; in `enforce` drops the wake-up and marks the message :mute: |
17
+ | `comms-adherence-gate` | `pre.tool.call` | Asks Jev (TypeSafe or OpenRouter) whether a Slack task's final output matches the requester's comms preferences and latest request; in `enforce` blocks a non-adherent completion once per task |
17
18
  | `require-verification-note` | `pre.tool.call` | Refuses completion without a `Verified:` line in the output |
18
19
  | `task-digest` | `post.task.completed`, `post.task.failed` | Ships the `task-digest-collect` script, the `task-digest-daily` schedule (09:00 UTC), the `task-digest-report` workflow, and the `task-digest-guide` skill |
19
20
 
@@ -0,0 +1,88 @@
1
+ # comms-adherence-gate
2
+
3
+ Agents finish Slack tasks with a final message a human reads. This extension judges that message against the requester's stated comms preferences and their latest request with Jev, [TypeSafe](https://typesafe.ai)'s decision model. In `enforce` it blocks a non-adherent completion once and tells the agent what to fix.
4
+
5
+ Install config:
6
+
7
+ ```json
8
+ { "mode": "shadow" }
9
+ ```
10
+
11
+ Start in `shadow`, read the verdicts in KV for a few days, then switch to `enforce`.
12
+
13
+ ## Modes
14
+
15
+ | `mode` | Effect |
16
+ |---|---|
17
+ | `off` | Never evaluates. |
18
+ | `shadow` (default) | Asks Jev and records the verdict. Never blocks. |
19
+ | `enforce` | Asks Jev, records the verdict, and blocks the `store-progress` call when a dimension fails, at most `maxBlocksPerTask` times per task. The agent gets the failed dimensions, their scores and the stated preference, and calls again. |
20
+
21
+ ## What it gates
22
+
23
+ Hook: `pre.tool.call` on `store-progress` with `status: "completed"` and a non-empty `output`. Calls made by extensions are skipped. The task must:
24
+
25
+ 1. Come from Slack (`source = "slack"` or a `slackThreadTs`). Other tasks pass without a Jev call.
26
+ 2. Have a requester with stated preferences in `users.metadata.comms`. A requester with none passes. Set them with the `manage-user` tool.
27
+
28
+ ## Decision
29
+
30
+ The gate reads the task row and asks one `noul` question per stated preference it knows: `verbosity`, `tone`, `language`. Any other key in `comms` is context only.
31
+
32
+ Jev sees the preferences, the requester's latest message (the text after the last `</thread_context>`, from the last 1,400 characters of the task), the first 400 characters of the task, and the output. Its judging rules say an explicit request outranks the stored default. A requested long answer, story or document is not a verbosity failure.
33
+
34
+ A dimension fails when its score is below `threshold`. Any failed dimension fails the output.
35
+
36
+ ## Jev transport
37
+
38
+ Jev is reachable two ways. Both take the same request and return the same answer.
39
+
40
+ | `provider` | Endpoint | Key | Model |
41
+ |---|---|---|---|
42
+ | `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` (or `apiKey`) | `model`, default `jev-latest` |
43
+ | `openrouter` | `POST https://openrouter.ai/api/alpha/decisions` | `OPENROUTER_API_KEY` | `openrouterModel`, default `typesafe/jev-1.13` |
44
+ | `auto` (default) | TypeSafe when its key is set, else OpenRouter | | |
45
+
46
+ Each verdict row records the `provider` that answered.
47
+
48
+ ## Fail open
49
+
50
+ Every error lets the completion through: a missing key, an HTTP error, a network error, a timeout, a malformed answer, or a failed DB read. The handler never throws, so an outage cannot auto-disable the extension. Each failure bumps the `error` counter and overwrites `error:last`.
51
+
52
+ ## Self-test
53
+
54
+ Call `store-progress` with a non-completed status and `progress` starting with `comms-gate-selftest`. The gate judges a known-bad and a known-good message against a terse preference and writes `selftest:last`: both scores, the threshold, the provider, and `controlsOk` (bad below threshold and good at or above it). Real traffic is untouched.
55
+
56
+ ## Config
57
+
58
+ | Key | Default | Notes |
59
+ |---|---|---|
60
+ | `mode` | `shadow` | `off`, `shadow`, or `enforce` |
61
+ | `threshold` | `0.5` | A dimension scoring below this fails |
62
+ | `timeoutMs` | `4000` | Jev call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |
63
+ | `maxBlocksPerTask` | `1` | Blocks per task in `enforce`; after that the gate records and lets it through |
64
+ | `provider` | `auto` | `auto`, `typesafe`, or `openrouter`. See [Jev transport](#jev-transport) |
65
+ | `model` | `jev-latest` | Jev model on TypeSafe |
66
+ | `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |
67
+ | `apiKey` | unset | Explicit TypeSafe key. Prefer the `TYPESAFE_API_KEY` secret below |
68
+
69
+ ## Required secrets
70
+
71
+ Read from the API process environment at call time. Set them as global `swarm_config` secrets: the API process loads global rows into `process.env` at boot and on config reload.
72
+
73
+ - `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`: one is required for any evaluation. Under `auto`, TypeSafe wins when both are set. Without either, every completion fails open.
74
+
75
+ No key is written to config, KV, logs, or a block reason.
76
+
77
+ ## KV namespace `ext:comms-gate`
78
+
79
+ - `last`: the latest verdict, with `scores`, `failed`, `provider`, `latencyMs`, the preferences, a 400-character `askPreview` and a 700-character `outputPreview`.
80
+ - `fail:<epochMs>:<taskId>`: one row per failed verdict, with `action` (`blocked` or `recorded`).
81
+ - `blocks:<taskId>`: blocks issued for that task.
82
+ - `error:last`, `selftest:last`.
83
+ - Counters: `total`, `pass`, `fail`, `blocked`, `escaped` (enforce, block budget spent), `shadowed`, `error`, and `dim:<dimension>`.
84
+
85
+ ## Known limits
86
+
87
+ - Only the last 1,400 characters of the task are searched for the latest request. A very long final message is cut to its last 900 characters.
88
+ - The block budget is per task, so a retried completion after a block goes through once the budget is spent, even if it still fails.