@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
@@ -51,7 +51,7 @@ import {
51
51
  splitSlackSectionText,
52
52
  } from "./blocks";
53
53
  import { buildAskClosure, type ClosureState, closureState } from "./closure";
54
- import { reactionName, type SlackReactionEvent } from "./reaction-shortcode";
54
+ import { completedStatusIcon, reactionName, type SlackReactionEvent } from "./reaction-shortcode";
55
55
  import { getAgentDisplayName, getAgentEmoji } from "./responses";
56
56
  import {
57
57
  _resetSlackSessionStatusForTests,
@@ -346,7 +346,7 @@ async function taskStateGlyph(
346
346
  case "paused":
347
347
  return "⏸️";
348
348
  case "completed":
349
- return "✅";
349
+ return completedStatusIcon();
350
350
  case "failed":
351
351
  return "❌";
352
352
  case "cancelled":
@@ -496,7 +496,9 @@ function threadHeaderText(tasks: AgentTask[], now: Date, duration: string): stri
496
496
  // A parked deferral is stored `completed`, but the ask is not answered yet.
497
497
  if (tasks.some((task) => isAwaitingWake(task, tasks))) return `🧵 ⏳ waiting — ${duration}`;
498
498
  const hasFailure = tasks.some((task) => task.status === "failed");
499
- return hasFailure ? `🧵 ❌ done with failures — ${duration}` : `🧵 ✅ done — ${duration}`;
499
+ return hasFailure
500
+ ? `🧵 ❌ done with failures — ${duration}`
501
+ : `🧵 ${completedStatusIcon()} done — ${duration}`;
500
502
  }
501
503
 
502
504
  export async function renderThreadTree(
@@ -909,7 +911,7 @@ async function outcomeContent(task: AgentTask, slackReplySent: boolean): Promise
909
911
  const agentName = task.agentId
910
912
  ? ((await getAgentById(task.agentId))?.name ?? "Agent")
911
913
  : "Agent";
912
- return `✅ ${agentName} completed`;
914
+ return `${completedStatusIcon()} ${agentName} completed`;
913
915
  }
914
916
  // A deferral is `completed` in the database but unfinished to a human: the
915
917
  // work resumes in a wake-up task. Rendering it ✅ told the thread the ask
@@ -917,7 +919,10 @@ async function outcomeContent(task: AgentTask, slackReplySent: boolean): Promise
917
919
  if (isDeferred) {
918
920
  return withStatusLead("⏳", outcomeText(slackTaskOutput(task), "Deferred."));
919
921
  }
920
- return withStatusLead("✅", outcomeText(slackTaskOutput(task), "Task completed."));
922
+ return withStatusLead(
923
+ completedStatusIcon(),
924
+ outcomeText(slackTaskOutput(task), "Task completed."),
925
+ );
921
926
  }
922
927
 
923
928
  async function agentDisplayNameFor(task: AgentTask): Promise<string> {
@@ -947,7 +952,7 @@ export async function childOutcomeContent(
947
952
  );
948
953
  }
949
954
  if (slackReplySent && !isDeferredTask(task)) {
950
- return `↳ ✅ ${agentName} completed`;
955
+ return `↳ ${completedStatusIcon()} ${agentName} completed`;
951
956
  }
952
957
  if (isDeferredTask(task)) {
953
958
  return withStatusLead(
@@ -956,7 +961,7 @@ export async function childOutcomeContent(
956
961
  );
957
962
  }
958
963
  return withStatusLead(
959
- `↳ ✅ ${agentName} — result:`,
964
+ `↳ ${completedStatusIcon()} ${agentName} — result:`,
960
965
  outcomeText(slackTaskOutput(task), "Task completed."),
961
966
  );
962
967
  }
@@ -15,6 +15,7 @@ import {
15
15
  markdownToSlack,
16
16
  splitSlackSectionText,
17
17
  } from "./blocks";
18
+ import { completedStatusIcon } from "./reaction-shortcode";
18
19
  import { getSlackOutputAttachments } from "./task-attachments";
19
20
  import { slackTaskOutput } from "./task-output";
20
21
 
@@ -67,7 +68,7 @@ export function formatInlineCompletionOutputChunks(opts: {
67
68
  taskId: string;
68
69
  output: string;
69
70
  }): string[] {
70
- const header = `✅ *${opts.agentName}* completed with output (${getTaskLink(opts.taskId)}):\n\n`;
71
+ const header = `${completedStatusIcon()} *${opts.agentName}* completed with output (${getTaskLink(opts.taskId)}):\n\n`;
71
72
  const slackOutput = markdownToSlack(opts.output.trim());
72
73
  return splitSlackSectionText(header + slackOutput);
73
74
  }
@@ -169,7 +170,7 @@ export async function sendTaskResponse(task: AgentTask): Promise<boolean> {
169
170
  thread_ts: task.slackThreadTs,
170
171
  text:
171
172
  task.slackReplySent || i > 0
172
- ? `✅ ${agentName} completed${i > 0 ? ` (continued ${i + 1}/${blockBatches.length})` : ""}`
173
+ ? `${completedStatusIcon()} ${agentName} completed${i > 0 ? ` (continued ${i + 1}/${blockBatches.length})` : ""}`
173
174
  : body,
174
175
  username: getAgentDisplayName(agent),
175
176
  icon_emoji: getAgentEmoji(agent),
@@ -324,7 +325,7 @@ export async function updateToFinal(
324
325
  };
325
326
  completionBlockBatches = buildCompletedBlockBatches(completionOpts);
326
327
  blocks = completionBlockBatches[0] ?? buildCompletedBlocks(completionOpts);
327
- text = task.slackReplySent ? `✅ ${agentName} completed` : body;
328
+ text = task.slackReplySent ? `${completedStatusIcon()} ${agentName} completed` : body;
328
329
  } else if (task.status === "cancelled") {
329
330
  blocks = buildCancelledBlocks({ agentName, taskId: task.id });
330
331
  text = "Task cancelled";
@@ -351,7 +352,7 @@ export async function updateToFinal(
351
352
  await sendWithPersona(app.client, {
352
353
  channel: task.slackChannelId,
353
354
  thread_ts: task.slackThreadTs ?? messageTs,
354
- text: `✅ ${agentName} completed (continued ${i + 1}/${completionBlockBatches.length})`,
355
+ text: `${completedStatusIcon()} ${agentName} completed (continued ${i + 1}/${completionBlockBatches.length})`,
355
356
  username: getAgentDisplayName(agent),
356
357
  icon_emoji: getAgentEmoji(agent),
357
358
  blocks: completionBlockBatches[i],
@@ -7,6 +7,9 @@ import {
7
7
  import { findUserByExternalId } from "../be/users";
8
8
  import { createAdditiveBuffer } from "../tasks/additive-buffer";
9
9
  import { slackContextKey } from "../tasks/context-key";
10
+ import { TaskCreationBlockedError } from "../tasks/errors";
11
+ import { scrubSecrets } from "../utils/secret-scrubber";
12
+ import { ackSlackMessage } from "./ack";
10
13
  import { getSlackApp } from "./app";
11
14
  import { buildBufferFlushBlocks } from "./blocks";
12
15
  import { resolveSlackUserId, rewriteSlackMentions } from "./enrich";
@@ -286,28 +289,44 @@ async function slackFlush(
286
289
  const dependsOn = !immediate && latestActiveTask ? [latestActiveTask.id] : undefined;
287
290
 
288
291
  const mostRecentTask = await getMostRecentTaskInThread(channelId, threadTs);
289
- const { task, unattached } = await createSlackTaskWithFiles(
290
- fullDescription,
291
- {
292
- agentId: lead?.id,
293
- routingReason:
294
- lead && mostRecentTask?.agentId === lead.id ? "continuity" : lead ? "skill" : undefined,
295
- routingSource: lead ? "engine_default" : undefined,
296
- source: "slack",
297
- slackChannelId: channelId,
298
- slackThreadTs: threadTs,
299
- slackTriggerMessageTs: items.at(-1)!.ts,
300
- slackUserId: originalRequesterId,
301
- requestedByUserId,
302
- dependsOn,
303
- parentTaskId: mostRecentTask?.id,
304
- // A human message in the thread: never answer it in the latest thread
305
- // task's structured-output contract.
306
- inheritParentOutputSchema: false,
307
- contextKey: slackContextKey({ channelId, threadTs }),
308
- },
309
- inbound,
310
- );
292
+ let created: Awaited<ReturnType<typeof createSlackTaskWithFiles>>;
293
+ try {
294
+ created = await createSlackTaskWithFiles(
295
+ fullDescription,
296
+ {
297
+ agentId: lead?.id,
298
+ routingReason:
299
+ lead && mostRecentTask?.agentId === lead.id ? "continuity" : lead ? "skill" : undefined,
300
+ routingSource: lead ? "engine_default" : undefined,
301
+ source: "slack",
302
+ slackChannelId: channelId,
303
+ slackThreadTs: threadTs,
304
+ slackTriggerMessageTs: items.at(-1)!.ts,
305
+ slackUserId: originalRequesterId,
306
+ requestedByUserId,
307
+ dependsOn,
308
+ parentTaskId: mostRecentTask?.id,
309
+ // A human message in the thread: never answer it in the latest thread
310
+ // task's structured-output contract.
311
+ inheritParentOutputSchema: false,
312
+ contextKey: slackContextKey({ channelId, threadTs }),
313
+ },
314
+ inbound,
315
+ );
316
+ } catch (error) {
317
+ if (!(error instanceof TaskCreationBlockedError)) throw error;
318
+
319
+ console.info(
320
+ "[Slack] pre.task.create blocked:",
321
+ error.extension.name,
322
+ scrubSecrets(error.reason),
323
+ );
324
+ if (app) {
325
+ await ackSlackMessage(app.client, channelId, items.at(-1)!.ts, "no_entry");
326
+ }
327
+ return;
328
+ }
329
+ const { task, unattached } = created;
311
330
 
312
331
  if (app) await notifySlackFileFailures(app.client, channelId, threadTs, unattached);
313
332
 
@@ -179,13 +179,22 @@ export function getNextResumeGeneration(parent: Pick<AgentTask, "tags">): number
179
179
  export function inheritedModelSelection(
180
180
  parent: Pick<
181
181
  AgentTask,
182
- "model" | "modelTier" | "modelSource" | "modelAlias" | "resolvedModel" | "claudeSessionId"
182
+ | "model"
183
+ | "modelTier"
184
+ | "modelSource"
185
+ | "modelAlias"
186
+ | "resolvedModel"
187
+ | "claudeSessionId"
188
+ | "profile"
183
189
  >,
184
- ): Pick<CreateTaskOptions, "model" | "modelTier"> {
190
+ ): Pick<CreateTaskOptions, "model" | "modelTier" | "profile"> {
185
191
  const model = taskModelPin(parent);
192
+ // The task's own profile travels like its tier; an agent-default profile
193
+ // (`resolvedProfile` only) is re-resolved by the child's claim.
186
194
  return {
187
195
  ...(model ? { model } : {}),
188
196
  ...(parent.modelTier ? { modelTier: parent.modelTier } : {}),
197
+ ...(parent.profile ? { profile: parent.profile } : {}),
189
198
  };
190
199
  }
191
200
 
@@ -38,6 +38,27 @@ describe("isInboxAllowed", () => {
38
38
  test("rejects empty inbox id", () => {
39
39
  expect(isInboxAllowed("", "x.dev")).toBe(false);
40
40
  });
41
+
42
+ test("allows exact full-address match", () => {
43
+ expect(isInboxAllowed("bot@x.dev", "bot@x.dev")).toBe(true);
44
+ });
45
+
46
+ test("rejects another address on the same domain as a full-address entry", () => {
47
+ expect(isInboxAllowed("other@x.dev", "bot@x.dev")).toBe(false);
48
+ });
49
+
50
+ test("full-address match is case-insensitive", () => {
51
+ expect(isInboxAllowed("Bot@X.Dev", "bot@x.dev")).toBe(true);
52
+ expect(isInboxAllowed("bot@x.dev", "BOT@X.DEV")).toBe(true);
53
+ });
54
+
55
+ test("supports mixed address and domain entries", () => {
56
+ const filter = "bot@x.dev, y.xyz";
57
+ expect(isInboxAllowed("bot@x.dev", filter)).toBe(true);
58
+ expect(isInboxAllowed("anyone@y.xyz", filter)).toBe(true);
59
+ expect(isInboxAllowed("other@x.dev", filter)).toBe(false);
60
+ expect(isInboxAllowed("bot@evil.com", filter)).toBe(false);
61
+ });
41
62
  });
42
63
 
43
64
  describe("isSenderAllowed", () => {
@@ -94,4 +115,30 @@ describe("isSenderAllowed", () => {
94
115
  test("rejects non-matching domain in 'Name <email>' format", () => {
95
116
  expect(isSenderAllowed("Taras <t@evil.com>", "desplega.ai")).toBe(false);
96
117
  });
118
+
119
+ test("allows exact full-address match", () => {
120
+ expect(isSenderAllowed("alice@a.com", "alice@a.com")).toBe(true);
121
+ });
122
+
123
+ test("rejects another address on the same domain as a full-address entry", () => {
124
+ expect(isSenderAllowed("bob@a.com", "alice@a.com")).toBe(false);
125
+ });
126
+
127
+ test("full-address match is case-insensitive", () => {
128
+ expect(isSenderAllowed("Alice@A.com", "alice@a.com")).toBe(true);
129
+ expect(isSenderAllowed("alice@a.com", "ALICE@A.COM")).toBe(true);
130
+ });
131
+
132
+ test("matches a full address in 'Name <email>' format", () => {
133
+ expect(isSenderAllowed("Taras <t@gmail.com>", "t@gmail.com")).toBe(true);
134
+ expect(isSenderAllowed(["Someone <s@gmail.com>"], "t@gmail.com")).toBe(false);
135
+ });
136
+
137
+ test("supports mixed address and domain entries", () => {
138
+ const filter = "t@gmail.com, desplega.ai";
139
+ expect(isSenderAllowed("t@gmail.com", filter)).toBe(true);
140
+ expect(isSenderAllowed("anyone@desplega.ai", filter)).toBe(true);
141
+ expect(isSenderAllowed("other@gmail.com", filter)).toBe(false);
142
+ expect(isSenderAllowed(["other@gmail.com", "Taras <t@gmail.com>"], filter)).toBe(true);
143
+ });
97
144
  });
@@ -987,6 +987,41 @@ describe("custom app actions", () => {
987
987
  );
988
988
  });
989
989
 
990
+ test("app writes return quickjs compat warnings for wired scripts without rejecting", async () => {
991
+ const saved = await upsertScriptByName({
992
+ name: `app_compat_${crypto.randomUUID().replaceAll("-", "")}`,
993
+ scope: "agent",
994
+ scopeId: AGENT_ID,
995
+ source: "export default async () => Buffer.from('aGk=', 'base64').toString();",
996
+ description: "Apps compat warning fixture",
997
+ intent: "Exercise quickjs compat warnings on app writes",
998
+ signatureJson: JSON.stringify({ args: { type: "object" }, result: { type: "string" } }),
999
+ agentId: AGENT_ID,
1000
+ typeChecked: true,
1001
+ });
1002
+ const created = await request<{ app: { id: string }; warnings?: string[] }>("/api/apps", {
1003
+ method: "POST",
1004
+ body: JSON.stringify({
1005
+ name: "Compat",
1006
+ definition: {
1007
+ ...baseDefinition,
1008
+ actions: { decode: { kind: "script", scriptId: saved.script.id } },
1009
+ },
1010
+ }),
1011
+ });
1012
+ expect(created.status).toBe(201);
1013
+ expect(created.body.warnings).toEqual([
1014
+ "actions.decode: quickjs compat (unsupportedGlobal, line 1): Buffer is not available under quickjs",
1015
+ ]);
1016
+
1017
+ const cleared = await request<{ warnings?: string[] }>(`/api/apps/${created.body.app.id}`, {
1018
+ method: "PUT",
1019
+ body: JSON.stringify({ definition: baseDefinition }),
1020
+ });
1021
+ expect(cleared.status).toBe(200);
1022
+ expect(cleared.body.warnings).toBeUndefined();
1023
+ });
1024
+
990
1025
  test("agent writers cannot wire another agent's script; operator wiring is grandfathered", async () => {
991
1026
  const OTHER_AGENT_ID = crypto.randomUUID();
992
1027
  const foreign = await upsertScriptByName({