@desplega.ai/agent-swarm 1.113.0 → 1.115.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 (237) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
  3. package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
  4. package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
  5. package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
  6. package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
  7. package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
  8. package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
  9. package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
  10. package/dist/cli-0pygq9r1.js +429 -0
  11. package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
  12. package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
  13. package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
  14. package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
  15. package/dist/cli-52fg7s7x.js +269 -0
  16. package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
  17. package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
  18. package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
  19. package/dist/cli-86fdnerz.js +405 -0
  20. package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
  21. package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
  22. package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
  23. package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
  24. package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
  25. package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
  26. package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
  27. package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
  28. package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
  29. package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
  30. package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
  31. package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
  32. package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
  33. package/dist/cli-jkhwmmgg.js +136 -0
  34. package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
  35. package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
  36. package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
  37. package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
  38. package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
  39. package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
  40. package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
  41. package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
  42. package/dist/cli-t0vqevnm.js +393 -0
  43. package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
  44. package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
  45. package/dist/cli-wh91zby6.js +187222 -0
  46. package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
  47. package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
  48. package/dist/cli.js +32 -15
  49. package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
  50. package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
  51. package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
  52. package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
  53. package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
  54. package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
  55. package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
  56. package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
  57. package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
  58. package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
  59. package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
  60. package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
  61. package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
  62. package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
  63. package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
  64. package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
  65. package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
  66. package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
  67. package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
  68. package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
  69. package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
  70. package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
  71. package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
  72. package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
  73. package/dist/rbac-roles-kfesjy9y.js +28 -0
  74. package/dist/rbac-roles-mwcwhbxc.js +283 -0
  75. package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
  76. package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
  77. package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
  78. package/openapi.json +1327 -20
  79. package/package.json +3 -3
  80. package/src/agentmail/handlers.ts +16 -6
  81. package/src/agentmail/templates.ts +25 -5
  82. package/src/be/audit-user.ts +28 -4
  83. package/src/be/db-queries/oauth.ts +42 -0
  84. package/src/be/db.ts +314 -28
  85. package/src/be/identity.ts +79 -0
  86. package/src/be/mcp-proxy.ts +132 -0
  87. package/src/be/migrations/109_rbac_roles.sql +79 -0
  88. package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
  89. package/src/be/migrations/112_script_connections_graphql.sql +96 -0
  90. package/src/be/migrations/113_task_routing_affinity.sql +9 -0
  91. package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
  92. package/src/be/modelsdev-cache.json +135 -0
  93. package/src/be/oauth-credential-bindings.ts +35 -0
  94. package/src/be/oauth-refresh-sweep.ts +159 -0
  95. package/src/be/rbac-audit.ts +52 -13
  96. package/src/be/rbac-roles.ts +424 -0
  97. package/src/be/script-connections.ts +459 -40
  98. package/src/be/script-credential-broker.ts +29 -3
  99. package/src/be/scripts/typecheck.ts +51 -7
  100. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
  101. package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
  102. package/src/be/users.ts +24 -0
  103. package/src/cli.tsx +17 -0
  104. package/src/github/handlers.ts +10 -9
  105. package/src/github/templates.ts +45 -9
  106. package/src/gitlab/handlers.ts +20 -8
  107. package/src/gitlab/templates.ts +18 -6
  108. package/src/heartbeat/heartbeat.ts +143 -21
  109. package/src/http/all-routes.ts +3 -0
  110. package/src/http/approval-requests.ts +12 -0
  111. package/src/http/config.ts +44 -10
  112. package/src/http/core.ts +30 -3
  113. package/src/http/favorites.ts +1 -5
  114. package/src/http/index.ts +25 -1
  115. package/src/http/mcp-oauth.ts +2 -0
  116. package/src/http/mcp-servers.ts +44 -3
  117. package/src/http/oauth-generic.ts +105 -0
  118. package/src/http/poll.ts +20 -3
  119. package/src/http/script-connection-proxy.ts +116 -0
  120. package/src/http/script-connections.ts +1657 -0
  121. package/src/http/scripts.ts +27 -7
  122. package/src/http/skills.ts +11 -0
  123. package/src/http/tasks.ts +27 -13
  124. package/src/http/x.ts +6 -2
  125. package/src/integrations/kapso/inbound.ts +23 -6
  126. package/src/jira/sync.ts +111 -17
  127. package/src/jira/templates.ts +10 -2
  128. package/src/mcp-client/http-client.ts +194 -0
  129. package/src/oauth/app-validation.ts +16 -0
  130. package/src/oauth/ensure-token.ts +69 -14
  131. package/src/oauth/mcp-wrapper.ts +15 -0
  132. package/src/oauth/wrapper.ts +48 -20
  133. package/src/prompts/session-templates.ts +3 -3
  134. package/src/providers/codex-models.ts +100 -30
  135. package/src/providers/modelsdev-reasoning.json +51 -0
  136. package/src/providers/pi-mono-mcp-client.ts +2 -133
  137. package/src/providers/reasoning-effort.ts +11 -5
  138. package/src/rbac/admission.ts +68 -0
  139. package/src/rbac/index.ts +2 -0
  140. package/src/rbac/legacy-policy.ts +12 -0
  141. package/src/rbac/permissions.ts +48 -0
  142. package/src/scheduler/scheduler.ts +9 -1
  143. package/src/scripts-runtime/api-client.ts +151 -19
  144. package/src/scripts-runtime/api-types.ts +38 -6
  145. package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
  146. package/src/scripts-runtime/credential-broker/types.ts +8 -0
  147. package/src/scripts-runtime/ctx.ts +11 -1
  148. package/src/scripts-runtime/eval-harness.ts +5 -1
  149. package/src/scripts-runtime/executors/types.ts +2 -1
  150. package/src/scripts-runtime/loader.ts +3 -1
  151. package/src/scripts-runtime/mcp-client.ts +87 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/types/stdlib.d.ts +6 -0
  154. package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
  155. package/src/server-user.ts +114 -59
  156. package/src/server.ts +12 -0
  157. package/src/slack/assistant.ts +14 -6
  158. package/src/slack/enrich.ts +17 -0
  159. package/src/slack/handlers.ts +8 -22
  160. package/src/slack/responses.ts +91 -1
  161. package/src/slack/thread-buffer.ts +6 -3
  162. package/src/slack/watcher.ts +9 -6
  163. package/src/tasks/worker-follow-up.ts +100 -3
  164. package/src/tests/agentmail-handlers.test.ts +32 -3
  165. package/src/tests/approval-requests.test.ts +61 -0
  166. package/src/tests/audit-user.test.ts +47 -1
  167. package/src/tests/codex-adapter.test.ts +46 -23
  168. package/src/tests/credential-broker.test.ts +5 -2
  169. package/src/tests/delete-page-tool.test.ts +197 -0
  170. package/src/tests/github-handlers.test.ts +28 -2
  171. package/src/tests/gitlab-handlers.test.ts +125 -0
  172. package/src/tests/heartbeat.test.ts +48 -3
  173. package/src/tests/identity.test.ts +117 -0
  174. package/src/tests/jira-sync.test.ts +99 -1
  175. package/src/tests/kapso-inbound.test.ts +7 -0
  176. package/src/tests/mcp-tools-user.test.ts +60 -6
  177. package/src/tests/model-control.test.ts +8 -2
  178. package/src/tests/oauth-credential-bindings.test.ts +490 -0
  179. package/src/tests/oauth-refresh-sweep.test.ts +160 -0
  180. package/src/tests/pool-affinity.test.ts +401 -0
  181. package/src/tests/prompt-template-remaining.test.ts +2 -2
  182. package/src/tests/rbac-admission-e2e.test.ts +386 -0
  183. package/src/tests/rbac-admission.test.ts +492 -0
  184. package/src/tests/rbac-e2e-helpers.ts +3 -0
  185. package/src/tests/rbac-engine.test.ts +50 -0
  186. package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
  187. package/src/tests/rbac-roles.test.ts +328 -0
  188. package/src/tests/reasoning-effort.test.ts +15 -2
  189. package/src/tests/schedule-target-type.test.ts +50 -0
  190. package/src/tests/scheduled-tasks.test.ts +40 -0
  191. package/src/tests/script-apis-mcp.test.ts +37 -13
  192. package/src/tests/script-connections-http.test.ts +1158 -0
  193. package/src/tests/script-connections-mcp.test.ts +894 -0
  194. package/src/tests/script-connections.test.ts +754 -6
  195. package/src/tests/scripts-external-api.test.ts +49 -1
  196. package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
  197. package/src/tests/scripts-runtime.test.ts +291 -0
  198. package/src/tests/seed-scripts.test.ts +36 -0
  199. package/src/tests/slack-identity-resolution.test.ts +37 -3
  200. package/src/tests/slack-inline-output.test.ts +65 -0
  201. package/src/tests/slack-thread-buffer.test.ts +28 -0
  202. package/src/tests/tool-annotations.test.ts +1 -0
  203. package/src/tests/user-token-rest-auth.test.ts +140 -1
  204. package/src/tests/workflow-swarm-script.test.ts +44 -0
  205. package/src/tests/workflow-triggers-v2.test.ts +300 -1
  206. package/src/tools/credential-bindings/tool.ts +277 -18
  207. package/src/tools/delete-page.ts +145 -0
  208. package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
  209. package/src/tools/request-human-input.ts +2 -0
  210. package/src/tools/resolve-user.ts +77 -27
  211. package/src/tools/script-apis.ts +62 -3
  212. package/src/tools/script-connections/tool.ts +275 -31
  213. package/src/tools/send-task.ts +15 -1
  214. package/src/tools/task-action.ts +34 -2
  215. package/src/tools/templates.ts +53 -1
  216. package/src/tools/tool-config.ts +2 -1
  217. package/src/tools/utils.ts +2 -0
  218. package/src/tools/workflows/create-workflow.ts +8 -1
  219. package/src/tools/workflows/update-workflow.ts +11 -2
  220. package/src/types.ts +90 -11
  221. package/src/utils/scoped-resource.ts +25 -0
  222. package/src/workflows/executors/human-in-the-loop.ts +1 -0
  223. package/src/workflows/executors/swarm-script.ts +7 -1
  224. package/src/workflows/triggers.ts +146 -18
  225. package/templates/skills/swarm-scripts/content.md +33 -8
  226. package/dist/cli-bgef578c.js +0 -771
  227. package/dist/cli-c9mtm09b.js +0 -43
  228. package/dist/cli-ceewqajw.js +0 -1698
  229. package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
  230. package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
  231. package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
  232. package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
  233. package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
  234. package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
  235. package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
  236. package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
  237. package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
@@ -1,6 +1,14 @@
1
1
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
2
  import { unlink } from "node:fs/promises";
3
- import { closeDb, completeTask, createAgent, getDb, getTaskById, initDb } from "../be/db";
3
+ import {
4
+ closeDb,
5
+ completeTask,
6
+ createAgent,
7
+ createUser,
8
+ getDb,
9
+ getTaskById,
10
+ initDb,
11
+ } from "../be/db";
4
12
  import { upsertOAuthApp } from "../be/db-queries/oauth";
5
13
  import {
6
14
  createTrackerSync,
@@ -8,6 +16,9 @@ import {
8
16
  getTrackerSyncByExternalId,
9
17
  updateTrackerSync,
10
18
  } from "../be/db-queries/tracker";
19
+ import { findUserByExternalId, type IdentityActor, linkIdentity } from "../be/users";
20
+
21
+ const SYSTEM_ACTOR: IdentityActor = { kind: "system", id: "test" };
11
22
 
12
23
  const TEST_DB_PATH = "./test-jira-sync.sqlite";
13
24
  const BOT_ACCOUNT_ID = "bot-account-12345";
@@ -303,6 +314,93 @@ describe("handleCommentEvent — short-circuits", () => {
303
314
  });
304
315
  });
305
316
 
317
+ describe("identity resolution — Jira routes through resolveIdentity, never raw displayName", () => {
318
+ test("issue-assigned: reporter linked by accountId -> requestedByUserId set + rendered pair in task text", async () => {
319
+ const reporter = createUser({ name: "Zbigniew Reporter", email: "zbigniew-jira@example.com" });
320
+ linkIdentity(reporter.id, "jira", "reporter-linked-1", SYSTEM_ACTOR);
321
+
322
+ const event = makeIssueAssignedEvent("10030", "KAN-30", "Linked reporter issue");
323
+ event.issue.fields.reporter = {
324
+ displayName: "Reporter Name",
325
+ accountId: "reporter-linked-1",
326
+ };
327
+ await handleIssueEvent(event);
328
+
329
+ const sync = getTrackerSyncByExternalId("jira", "task", "10030");
330
+ const task = getTaskById(sync?.swarmId ?? "");
331
+ expect(task).not.toBeNull();
332
+ expect(task?.requestedByUserId).toBe(reporter.id);
333
+ expect(task?.task).toContain("Zbigniew Reporter (jira:reporter-linked-1)");
334
+ expect(task?.task).not.toContain("Reporter Name");
335
+ });
336
+
337
+ test("issue-assigned: reporter with an email but no prior link auto-links via findOrCreateUserByEmail", async () => {
338
+ const event = makeIssueAssignedEvent("10031", "KAN-31", "Email-cascade issue");
339
+ event.issue.fields.reporter = {
340
+ displayName: "Auto Linked",
341
+ accountId: "reporter-autolink-1",
342
+ emailAddress: "auto-linked-jira@example.com",
343
+ };
344
+ await handleIssueEvent(event);
345
+
346
+ const linked = findUserByExternalId("jira", "reporter-autolink-1");
347
+ expect(linked).not.toBeNull();
348
+ expect(linked?.name).toBe("Auto Linked");
349
+
350
+ const sync = getTrackerSyncByExternalId("jira", "task", "10031");
351
+ const task = getTaskById(sync?.swarmId ?? "");
352
+ expect(task?.requestedByUserId).toBe(linked?.id);
353
+ expect(task?.task).toContain("Auto Linked (jira:reporter-autolink-1)");
354
+ });
355
+
356
+ test("issue-assigned: unlinked reporter with no email renders the UNKNOWN sentinel, never the displayName", async () => {
357
+ const event = makeIssueAssignedEvent("10032", "KAN-32", "Unknown reporter issue");
358
+ event.issue.fields.reporter = {
359
+ displayName: "Never Registered",
360
+ accountId: "reporter-unknown-1",
361
+ };
362
+ await handleIssueEvent(event);
363
+
364
+ const sync = getTrackerSyncByExternalId("jira", "task", "10032");
365
+ const task = getTaskById(sync?.swarmId ?? "");
366
+ expect(task).not.toBeNull();
367
+ expect(task?.requestedByUserId ?? null).toBeNull();
368
+ expect(task?.task).toContain("jira:reporter-unknown-1 (unknown user)");
369
+ expect(task?.task).not.toContain("Never Registered");
370
+ });
371
+
372
+ test("comment-mention: linked author -> requestedByUserId set + rendered pair as comment_author", async () => {
373
+ const author = createUser({ name: "Manuel Commenter", email: "manuel-jira@example.com" });
374
+ linkIdentity(author.id, "jira", "commenter-linked-1", SYSTEM_ACTOR);
375
+
376
+ await handleCommentEvent(
377
+ makeCommentEvent("10033", "KAN-33", "commenter-linked-1", "please take a look", [
378
+ BOT_ACCOUNT_ID,
379
+ ]),
380
+ );
381
+
382
+ const sync = getTrackerSyncByExternalId("jira", "task", "10033");
383
+ const task = getTaskById(sync?.swarmId ?? "");
384
+ expect(task).not.toBeNull();
385
+ expect(task?.requestedByUserId).toBe(author.id);
386
+ expect(task?.task).toContain("Manuel Commenter (jira:commenter-linked-1)");
387
+ expect(task?.task).not.toContain("Some User");
388
+ });
389
+
390
+ test("comment-mention: unlinked author renders the UNKNOWN sentinel, never the raw displayName", async () => {
391
+ await handleCommentEvent(
392
+ makeCommentEvent("10034", "KAN-34", "commenter-unknown-1", "any updates?", [BOT_ACCOUNT_ID]),
393
+ );
394
+
395
+ const sync = getTrackerSyncByExternalId("jira", "task", "10034");
396
+ const task = getTaskById(sync?.swarmId ?? "");
397
+ expect(task).not.toBeNull();
398
+ expect(task?.requestedByUserId ?? null).toBeNull();
399
+ expect(task?.task).toContain("jira:commenter-unknown-1 (unknown user)");
400
+ expect(task?.task).not.toContain("Some User");
401
+ });
402
+ });
403
+
306
404
  describe("createTrackerSyncIfAbsent — UNIQUE-gated insert", () => {
307
405
  test("first call inserts; second call returns existing", () => {
308
406
  const first = createTrackerSyncIfAbsent({
@@ -147,6 +147,10 @@ describe("routeKapsoInbound", () => {
147
147
  const task = getTaskById(routing.taskId);
148
148
  expect(task!.requestedByUserId).toBe(user.id);
149
149
  expect(getKv("integration:unmapped:kapso", "34679077778:meta")).toBeNull();
150
+ // The resolved canonical name renders in the task text — never the raw
151
+ // WhatsApp profile `contact_name` ("Taras" from the fixture payload).
152
+ expect(task!.task).toContain("Known WhatsApp Sender (kapso:34679077778)");
153
+ expect(task!.task).not.toContain("Taras");
150
154
  });
151
155
 
152
156
  test("unknown Kapso sender → records unmapped identity and leaves task unowned", () => {
@@ -170,6 +174,9 @@ describe("routeKapsoInbound", () => {
170
174
  if (routing.kind !== "task") throw new Error("expected task");
171
175
  const task = getTaskById(routing.taskId);
172
176
  expect(task!.requestedByUserId).toBeUndefined();
177
+ // Unresolved -> explicit UNKNOWN sentinel, never the raw contact_name.
178
+ expect(task!.task).toContain("kapso:34679077779 (unknown user)");
179
+ expect(task!.task).not.toContain("Taras");
173
180
 
174
181
  const meta = getKv("integration:unmapped:kapso", "34679077779:meta");
175
182
  expect(meta?.valueType).toBe("json");
@@ -106,12 +106,17 @@ describe("resolve-user MCP tool (new {kind, externalId, email} shape)", () => {
106
106
  expect(textOf(byAlias)).toContain(user.id);
107
107
  });
108
108
 
109
- test("returns 'No user found' when nothing matches", async () => {
109
+ test("returns a structured {status: 'unknown', ...} payload when nothing matches — never prose", async () => {
110
110
  const result = await callTool(server, "resolve-user", {
111
111
  kind: "slack",
112
112
  externalId: "U_DOES_NOT_EXIST",
113
113
  });
114
- expect(textOf(result)).toContain("No user found");
114
+ const parsed = JSON.parse(textOf(result));
115
+ expect(parsed).toEqual({
116
+ status: "unknown",
117
+ kind: "slack",
118
+ externalId: "U_DOES_NOT_EXIST",
119
+ });
115
120
  });
116
121
 
117
122
  // Schema-level validation tests. The MCP SDK runs Zod at the transport
@@ -132,12 +137,17 @@ describe("resolve-user MCP tool (new {kind, externalId, email} shape)", () => {
132
137
  expect(parsed.success).toBe(false);
133
138
  if (!parsed.success) {
134
139
  const msg = JSON.stringify(parsed.error.issues);
135
- expect(msg).toMatch(/Provide either \(kind \+ externalId\), email, or userId/);
140
+ expect(msg).toMatch(/Provide either \(kind \+ externalId\), email, userId, or name/);
136
141
  }
137
142
  });
138
143
 
139
- test("name parameter (old shape) is rejected", () => {
144
+ test("valid {name} input passes the schema", () => {
140
145
  const parsed = resolveUserInputSchema.safeParse({ name: "Whoever" });
146
+ expect(parsed.success).toBe(true);
147
+ });
148
+
149
+ test("name shorter than 2 chars fails the min-length constraint", () => {
150
+ const parsed = resolveUserInputSchema.safeParse({ name: "A" });
141
151
  expect(parsed.success).toBe(false);
142
152
  });
143
153
 
@@ -201,9 +211,53 @@ describe("resolve-user MCP tool (new {kind, externalId, email} shape)", () => {
201
211
  expect(parsed.externalIds[0]).toMatchObject({ kind: "linear", externalId: "L_UIDLOOKUP" });
202
212
  });
203
213
 
204
- test("userId lookup returns 'No user found' for unknown ID", async () => {
214
+ test("userId lookup returns a structured {status: 'unknown', ...} payload for an unknown ID", async () => {
205
215
  const result = await callTool(server, "resolve-user", { userId: "nonexistent-user-id-xyz" });
206
- expect(textOf(result)).toContain("No user found");
216
+ const parsed = JSON.parse(textOf(result));
217
+ expect(parsed).toEqual({
218
+ status: "unknown",
219
+ kind: "userId",
220
+ externalId: "nonexistent-user-id-xyz",
221
+ });
222
+ });
223
+
224
+ test("name lookup: single exact match resolves to the user profile", async () => {
225
+ const user = createUser({ name: "Zbigniew Solo", email: "zbigniew@example.com" });
226
+
227
+ const result = await callTool(server, "resolve-user", { name: "Zbigniew Solo" });
228
+ const parsed = JSON.parse(textOf(result));
229
+ expect(parsed.id).toBe(user.id);
230
+ expect(parsed.name).toBe("Zbigniew Solo");
231
+ });
232
+
233
+ test("name lookup: single first-token prefix match resolves to the user profile", async () => {
234
+ const user = createUser({ name: "Priyanka Unique Prefix", email: "priyanka@example.com" });
235
+
236
+ const result = await callTool(server, "resolve-user", { name: "Priyanka" });
237
+ const parsed = JSON.parse(textOf(result));
238
+ expect(parsed.id).toBe(user.id);
239
+ });
240
+
241
+ test("name lookup: multiple matches return {status: 'ambiguous', candidates: [...]} — never a guess", async () => {
242
+ const a = createUser({ name: "Alberto Maurel", email: "alberto.maurel@example.com" });
243
+ const b = createUser({ name: "Alberto Dubois", email: "alberto.dubois@example.com" });
244
+
245
+ const result = await callTool(server, "resolve-user", { name: "Alberto" });
246
+ const parsed = JSON.parse(textOf(result));
247
+ expect(parsed.status).toBe("ambiguous");
248
+ expect(parsed.message).toMatch(/AMBIGUOUS/);
249
+ const candidateIds = parsed.candidates.map((c: { userId: string }) => c.userId).sort();
250
+ expect(candidateIds).toEqual([a.id, b.id].sort());
251
+ });
252
+
253
+ test("name lookup: zero matches return a structured {status: 'unknown', kind: 'name', ...} payload", async () => {
254
+ const result = await callTool(server, "resolve-user", { name: "Nobody Registered Xyz" });
255
+ const parsed = JSON.parse(textOf(result));
256
+ expect(parsed).toEqual({
257
+ status: "unknown",
258
+ kind: "name",
259
+ externalId: "Nobody Registered Xyz",
260
+ });
207
261
  });
208
262
  });
209
263
 
@@ -485,7 +485,10 @@ describe("Model Control - Priority Resolution Logic", () => {
485
485
 
486
486
  test("task.modelTier resolves using the claiming worker harness", () => {
487
487
  expect(resolveModelTier({ tier: "smol", harnessProvider: "claude" })).toBe("haiku");
488
- expect(resolveModelTier({ tier: "smol", harnessProvider: "codex" })).toBe("gpt-5.4-mini");
488
+ expect(resolveModelTier({ tier: "smol", harnessProvider: "codex" })).toBe("gpt-5.6-luna");
489
+ expect(resolveModelTier({ tier: "regular", harnessProvider: "codex" })).toBe("gpt-5.6-terra");
490
+ expect(resolveModelTier({ tier: "smart", harnessProvider: "codex" })).toBe("gpt-5.6-sol");
491
+ expect(resolveModelTier({ tier: "ultra", harnessProvider: "codex" })).toBe("gpt-5.6-sol");
489
492
  expect(resolveModelTier({ tier: "smart", harnessProvider: "opencode" })).toBe(
490
493
  "openrouter/deepseek/deepseek-v4-pro",
491
494
  );
@@ -561,11 +564,14 @@ describe("Model Control - Zod Validation Schema", () => {
561
564
  sendTaskInputSchema.parse({ agentId: crypto.randomUUID(), task: "x", effort: "xhigh" })
562
565
  .effort,
563
566
  ).toBe("xhigh");
567
+ expect(
568
+ sendTaskInputSchema.parse({ agentId: crypto.randomUUID(), task: "x", effort: "max" }).effort,
569
+ ).toBe("max");
564
570
  expect(taskActionInputSchema.parse({ action: "create", task: "x", effort: "off" }).effort).toBe(
565
571
  "off",
566
572
  );
567
573
  expect(() =>
568
- sendTaskInputSchema.parse({ agentId: crypto.randomUUID(), task: "x", effort: "max" }),
574
+ sendTaskInputSchema.parse({ agentId: crypto.randomUUID(), task: "x", effort: "ultra" }),
569
575
  ).toThrow();
570
576
  });
571
577