@desplega.ai/agent-swarm 1.157.0 → 1.158.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 (256) hide show
  1. package/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
  2. package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
  3. package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
  4. package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
  5. package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
  6. package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
  7. package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
  8. package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
  9. package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
  10. package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
  11. package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
  12. package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
  13. package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
  14. package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
  15. package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
  16. package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
  17. package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
  18. package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
  19. package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
  20. package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
  21. package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
  22. package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
  23. package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
  24. package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
  25. package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
  26. package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
  27. package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
  28. package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
  29. package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
  30. package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
  31. package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
  32. package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
  33. package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
  34. package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
  35. package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
  36. package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
  37. package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
  38. package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
  39. package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
  40. package/dist/cli-t1ze64h3.js +811 -0
  41. package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
  42. package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
  43. package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
  44. package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
  45. package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
  46. package/dist/cli.js +29 -15
  47. package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
  48. package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
  49. package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
  50. package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
  51. package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
  52. package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
  53. package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
  54. package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
  55. package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
  56. package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
  57. package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
  58. package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
  59. package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
  60. package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
  61. package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
  62. package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
  63. package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
  64. package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
  65. package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
  66. package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
  67. package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
  68. package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
  69. package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
  70. package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
  71. package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
  72. package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
  73. package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
  74. package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
  75. package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
  76. package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
  77. package/openapi.json +1626 -629
  78. package/package.json +2 -1
  79. package/src/apps/catalog.generated.json +229 -0
  80. package/src/apps/page-validator.ts +71 -16
  81. package/src/apps/row-store.ts +66 -11
  82. package/src/apps/sync.ts +59 -17
  83. package/src/artifact-sdk/browser-sdk.ts +436 -2
  84. package/src/be/approval-sweeps.ts +109 -0
  85. package/src/be/boot-seeds.ts +40 -0
  86. package/src/be/chunking.ts +1 -1
  87. package/src/be/db/agents.ts +20 -0
  88. package/src/be/db/approvals.ts +78 -0
  89. package/src/be/db/tasks/read.ts +197 -39
  90. package/src/be/db/tasks/write.ts +41 -9
  91. package/src/be/db-client.ts +107 -48
  92. package/src/be/db.ts +219 -76
  93. package/src/be/events.ts +33 -1
  94. package/src/be/memory/graph-expansion.ts +10 -3
  95. package/src/be/memory/providers/sqlite-store.ts +55 -13
  96. package/src/be/memory/raters/retrieval.ts +5 -2
  97. package/src/be/memory/reranker.ts +26 -2
  98. package/src/be/memory/types.ts +2 -0
  99. package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
  100. package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
  101. package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
  102. package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
  103. package/src/be/models-catalog.ts +2 -0
  104. package/src/be/modelsdev-cache.json +1857 -0
  105. package/src/be/modelsdev-cache.ts +14 -1
  106. package/src/be/pricing-normalize.ts +2 -0
  107. package/src/be/scripts/typecheck.ts +1 -0
  108. package/src/be/seed-pricing.ts +3 -1
  109. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  110. package/src/be/steering.ts +53 -3
  111. package/src/be/swarm-config-guard.ts +3 -0
  112. package/src/be/task-citations.ts +29 -0
  113. package/src/cli.tsx +14 -1
  114. package/src/commands/onboard/compose-generator.ts +8 -3
  115. package/src/commands/onboard/container-engine.ts +220 -0
  116. package/src/commands/onboard/manifest.ts +1 -0
  117. package/src/commands/onboard/steps/health-check.tsx +4 -1
  118. package/src/commands/onboard/steps/prereq-check.tsx +50 -54
  119. package/src/commands/onboard/steps/start.tsx +31 -16
  120. package/src/commands/onboard/types.ts +7 -2
  121. package/src/commands/onboard.tsx +39 -4
  122. package/src/commands/profile-sync.ts +79 -26
  123. package/src/commands/runner.ts +8 -1
  124. package/src/extensions/catalog.generated.json +17 -0
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/heartbeat/heartbeat.ts +263 -103
  127. package/src/hooks/hook.ts +21 -12
  128. package/src/http/agents.ts +35 -0
  129. package/src/http/approval-requests.ts +136 -64
  130. package/src/http/apps.ts +14 -3
  131. package/src/http/config.ts +15 -1
  132. package/src/http/events.ts +62 -2
  133. package/src/http/index.ts +6 -4
  134. package/src/http/memory.ts +21 -7
  135. package/src/http/schedules.ts +1 -1
  136. package/src/http/session-data.ts +9 -4
  137. package/src/http/sessions.ts +25 -18
  138. package/src/http/stats.ts +19 -7
  139. package/src/http/status.ts +11 -1
  140. package/src/http/tasks.ts +40 -8
  141. package/src/http/webhooks.ts +67 -46
  142. package/src/http/workflows.ts +15 -20
  143. package/src/linear/outbound.ts +37 -15
  144. package/src/otel-impl.ts +151 -1
  145. package/src/otel.ts +14 -3
  146. package/src/prompts/memories.ts +19 -2
  147. package/src/providers/claude-managed-models.ts +8 -0
  148. package/src/providers/modelsdev-reasoning.json +551 -0
  149. package/src/rbac/legacy-policy.ts +16 -0
  150. package/src/rbac/permissions.ts +4 -0
  151. package/src/scheduler/schedule-task.ts +3 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/swarm-sdk.ts +9 -0
  154. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  155. package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
  156. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  157. package/src/server.ts +12 -7
  158. package/src/slack/app.ts +35 -4
  159. package/src/slack/connection-state.ts +17 -0
  160. package/src/tasks/context-key.ts +1 -0
  161. package/src/tasks/worker-follow-up.ts +6 -2
  162. package/src/tests/agent-task-activity.test.ts +74 -0
  163. package/src/tests/agents-list-model-display.test.ts +6 -1
  164. package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
  165. package/src/tests/approval-requests.test.ts +395 -0
  166. package/src/tests/approval-sweeps-config.test.ts +47 -0
  167. package/src/tests/approval-sweeps.test.ts +508 -0
  168. package/src/tests/apps-kibo-blocks.test.ts +77 -0
  169. package/src/tests/apps-spike.test.ts +56 -1
  170. package/src/tests/apps-sync-engine.test.ts +20 -9
  171. package/src/tests/boot-seeds.test.ts +170 -0
  172. package/src/tests/cancel-approval-request-tool.test.ts +115 -0
  173. package/src/tests/claude-managed-adapter.test.ts +6 -0
  174. package/src/tests/config-key-filter.test.ts +80 -0
  175. package/src/tests/context-window.test.ts +1 -0
  176. package/src/tests/db-client-busy-retry.test.ts +96 -0
  177. package/src/tests/db-client.test.ts +60 -1
  178. package/src/tests/db-task-read-contract.test.ts +10 -0
  179. package/src/tests/db-task-write-contract.test.ts +9 -0
  180. package/src/tests/docs-index-footprint.test.js +3 -3
  181. package/src/tests/events-http.test.ts +103 -0
  182. package/src/tests/extensions-catalog.test.ts +22 -17
  183. package/src/tests/extensions-pre-task.test.ts +107 -0
  184. package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
  185. package/src/tests/follow-up-config-inheritance.test.ts +260 -0
  186. package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
  187. package/src/tests/heartbeat-reboot-live.test.ts +95 -0
  188. package/src/tests/heartbeat-stall-cas.test.ts +80 -0
  189. package/src/tests/heartbeat.test.ts +99 -3
  190. package/src/tests/hook-mcp-config.test.ts +29 -0
  191. package/src/tests/http-api-integration.test.ts +16 -2
  192. package/src/tests/kv-storage.test.ts +62 -0
  193. package/src/tests/linear-outbound-sync.test.ts +120 -5
  194. package/src/tests/list-endpoint-slimming.test.ts +91 -0
  195. package/src/tests/memory-graph-organic-route.test.ts +155 -0
  196. package/src/tests/memory-http-recall-gating.test.ts +14 -4
  197. package/src/tests/memory-hybrid.test.ts +161 -1
  198. package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
  199. package/src/tests/memory.test.ts +22 -0
  200. package/src/tests/onboard-container-engine.test.ts +240 -0
  201. package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
  202. package/src/tests/pricing-routes.test.ts +14 -0
  203. package/src/tests/profile-sync-warning.test.ts +8 -4
  204. package/src/tests/promote-draft-task-route.test.ts +19 -0
  205. package/src/tests/provider-registration.test.ts +296 -42
  206. package/src/tests/rbac-engine.test.ts +30 -0
  207. package/src/tests/reasoning-effort.test.ts +8 -0
  208. package/src/tests/script-connections.test.ts +7 -0
  209. package/src/tests/sdk-allowlist.test.ts +39 -0
  210. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  211. package/src/tests/sessions-detail-batch.test.ts +250 -0
  212. package/src/tests/sessions.test.ts +118 -0
  213. package/src/tests/slack-config.test.ts +22 -0
  214. package/src/tests/sonnet-5-5-registry.test.ts +44 -0
  215. package/src/tests/status.test.ts +36 -1
  216. package/src/tests/swarm-diff.test.ts +316 -10
  217. package/src/tests/task-completion-idempotency.test.ts +2 -1
  218. package/src/tests/tasks-requested-by-filter.test.ts +3 -1
  219. package/src/tests/tool-annotations.test.ts +1 -1
  220. package/src/tests/ui-list-slim-projections.test.ts +158 -0
  221. package/src/tests/webhooks-task-blocked.test.ts +127 -0
  222. package/src/tests/workflow-approval-recovery.test.ts +203 -0
  223. package/src/tests/workflow-hitl-routing.test.ts +22 -1
  224. package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
  225. package/src/tests/workflow-http-v2.test.ts +99 -8
  226. package/src/tests/workflow-retry-validation.test.ts +56 -0
  227. package/src/tests/workflow-tla-races.test.ts +389 -0
  228. package/src/tests/workflow-validation-port-routing.test.ts +113 -1
  229. package/src/tools/cancel-approval-request.ts +57 -0
  230. package/src/tools/memory-search.ts +1 -0
  231. package/src/tools/request-human-input.ts +4 -2
  232. package/src/tools/send-task.ts +4 -0
  233. package/src/tools/templates.ts +19 -0
  234. package/src/tools/tool-config.ts +2 -1
  235. package/src/tools/workflows/list-workflow-runs.ts +1 -0
  236. package/src/types.ts +38 -0
  237. package/src/utils/context-window.ts +1 -0
  238. package/src/workflows/approval-cancel.ts +152 -0
  239. package/src/workflows/approval-notifications.ts +84 -0
  240. package/src/workflows/checkpoint.ts +10 -5
  241. package/src/workflows/definition.ts +21 -0
  242. package/src/workflows/engine.ts +152 -21
  243. package/src/workflows/executors/human-in-the-loop.ts +1 -1
  244. package/src/workflows/recovery.ts +100 -45
  245. package/src/workflows/resume.ts +49 -18
  246. package/src/workflows/retry-poller.ts +71 -21
  247. package/templates/extensions/README.md +1 -0
  248. package/templates/extensions/github-sender-allowlist/README.md +18 -0
  249. package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
  250. package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
  251. package/templates/skills/apps/content.md +20 -1
  252. package/templates/skills/code-quality/content.md +26 -1
  253. package/templates/skills/pages/content.md +16 -4
  254. package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
  255. package/dist/cli-2daksqps.js +0 -378
  256. package/dist/rbac-roles-3gxmnb20.js +0 -37
@@ -1,12 +1,214 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
2
3
  import { mkdtemp, rm } from "node:fs/promises";
3
4
  import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
+ import { delimiter, join } from "node:path";
5
6
  import { buildPricingSeedRows } from "../be/seed-pricing";
6
- import { CREDENTIAL_PROVIDER_CHECKERS } from "../commands/provider-credentials";
7
- import { ProviderNameSchema } from "../types";
7
+ import {
8
+ CREDENTIAL_PROVIDER_CHECKERS,
9
+ validateProviderCredentials,
10
+ } from "../commands/provider-credentials";
11
+ import { createProviderAdapter } from "../providers";
12
+ import { PricingProviderSchema, type ProviderName, ProviderNameSchema } from "../types";
13
+ import { CHILD_PROCESS_TEST_BUDGET_MS, runChild } from "./test-proc";
8
14
 
9
- const entrypoint = await Bun.file(new URL("../../docker-entrypoint.sh", import.meta.url)).text();
15
+ /**
16
+ * Every `ProviderNameSchema` member must reach every mandatory touch point, or
17
+ * be exempted there with a one-line reason (#1559). A failure names the
18
+ * provider and the file to edit.
19
+ *
20
+ * Some lists are subsets or supersets by design (PricingProviderSchema also
21
+ * accepts `gemini`; the local-harness lists skip cloud providers), so each
22
+ * touch point only asserts that every provider is present or consciously
23
+ * exempted. An exemption for a provider that is actually present fails as
24
+ * stale, so the map cannot rot.
25
+ *
26
+ * Touch points that are already compile-bound to one of these lists are not
27
+ * repeated here: HARNESS_LABEL and ICON_BY_HARNESS (`Record<ProviderName, …>`
28
+ * over the UI's PROVIDER_NAMES), ROUTING_PREFIXES_BY_PROVIDER
29
+ * (`Record<PricingProvider, …>`), the session-costs `provider` field (reuses
30
+ * PricingProviderSchema), DEFAULT_MODEL_TIER_MAP and PROVIDER_STEER_CAPABILITIES
31
+ * (`Record<ProviderName, …>`; the factory switch is covered by
32
+ * provider-steering-capabilities.test.ts).
33
+ */
34
+
35
+ const repoRoot = join(import.meta.dir, "..", "..");
36
+
37
+ async function readRepoFile(relPath: string): Promise<string> {
38
+ return Bun.file(join(repoRoot, relPath)).text();
39
+ }
40
+
41
+ /** The quoted members of the first array/enum literal matched by `pattern`. */
42
+ async function literalMembers(relPath: string, pattern: RegExp): Promise<string[]> {
43
+ const match = (await readRepoFile(relPath)).match(pattern);
44
+ if (!match?.[1]) {
45
+ throw new Error(`Could not find ${pattern} in ${relPath}. Did the declaration move?`);
46
+ }
47
+ return [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1] as string);
48
+ }
49
+
50
+ /** The provider names listed after `Supported:` in an unknown-provider error. */
51
+ function supportedList(message: string): string[] {
52
+ const list = message.split("Supported:")[1];
53
+ if (list === undefined) throw new Error(`No "Supported:" list in: ${message}`);
54
+ return list
55
+ .replace(/\.\s*$/, "")
56
+ .split(",")
57
+ .map((name) => name.trim());
58
+ }
59
+
60
+ const modelsDevFixture = {
61
+ anthropic: { models: { "claude-test": { cost: { input: 1, output: 2 } } } },
62
+ openai: { models: { "gpt-test": { cost: { input: 1, output: 2 } } } },
63
+ openrouter: { models: { "test/model": { cost: { input: 1, output: 2 } } } },
64
+ };
65
+
66
+ interface TouchPoint {
67
+ /** The file a new provider must be added to. */
68
+ file: string;
69
+ /** What inside that file. */
70
+ what: string;
71
+ members: () => Promise<readonly string[]>;
72
+ /** Providers deliberately absent here, each with a one-line reason. */
73
+ exempt: Partial<Record<ProviderName, string>>;
74
+ }
75
+
76
+ const TOUCH_POINTS: TouchPoint[] = [
77
+ {
78
+ file: "src/commands/provider-credentials.ts",
79
+ what: "CREDENTIAL_PROVIDER_CHECKERS (and the SupportedProvider union it is keyed by)",
80
+ members: async () => Object.keys(CREDENTIAL_PROVIDER_CHECKERS),
81
+ exempt: {},
82
+ },
83
+ {
84
+ file: "src/commands/provider-credentials.ts",
85
+ what: "the validateProviderCredentials switch (dashboard Test connection)",
86
+ members: async () => {
87
+ const source = await readRepoFile("src/commands/provider-credentials.ts");
88
+ const start = source.indexOf("export async function validateProviderCredentials(");
89
+ const end = source.indexOf("\n}\n", start);
90
+ if (start === -1 || end === -1) {
91
+ throw new Error(
92
+ "Could not find validateProviderCredentials in src/commands/provider-credentials.ts.",
93
+ );
94
+ }
95
+ return [...source.slice(start, end).matchAll(/case "([^"]+)":/g)].map((m) => m[1] as string);
96
+ },
97
+ exempt: {},
98
+ },
99
+ {
100
+ file: "src/commands/provider-credentials.ts",
101
+ what: "the validateProviderCredentials unknown-provider error message",
102
+ members: async () => {
103
+ const result = await validateProviderCredentials("__unregistered_provider__");
104
+ return supportedList(result.ok ? "" : result.error);
105
+ },
106
+ exempt: {},
107
+ },
108
+ {
109
+ file: "src/providers/index.ts",
110
+ what: "the createProviderAdapter unknown-provider error message",
111
+ members: async () => {
112
+ try {
113
+ await createProviderAdapter("__unregistered_provider__");
114
+ } catch (error) {
115
+ return supportedList(String(error));
116
+ }
117
+ throw new Error("createProviderAdapter accepted an unregistered provider.");
118
+ },
119
+ exempt: {},
120
+ },
121
+ {
122
+ file: "src/types.ts",
123
+ what: "PricingProviderSchema (#1636; the session-costs provider field reuses it)",
124
+ members: async () => PricingProviderSchema.options,
125
+ exempt: {},
126
+ },
127
+ {
128
+ file: "src/be/seed-pricing.ts",
129
+ what: "the provider rows buildPricingSeedRows emits",
130
+ members: async () => [
131
+ ...new Set(buildPricingSeedRows(modelsDevFixture).map((row) => row.provider)),
132
+ ],
133
+ exempt: {
134
+ acp: "A generic ACP target owns its own billing and the adapter reports totalCostUsd: 0.",
135
+ dsh: "The dsh adapter reports no cost data today, so there is nothing to price.",
136
+ },
137
+ },
138
+ {
139
+ file: "apps/ui/src/api/types.ts",
140
+ what: "PROVIDER_NAMES (#1637; HARNESS_LABEL and ICON_BY_HARNESS are typed against it)",
141
+ members: () =>
142
+ literalMembers("apps/ui/src/api/types.ts", /export const PROVIDER_NAMES = \[([^\]]*)\]/),
143
+ exempt: {},
144
+ },
145
+ {
146
+ file: "src/http/agents.ts",
147
+ what: "LocalHarnessProviderSchema (PATCH /api/agents/{id}/runtime)",
148
+ members: () =>
149
+ literalMembers(
150
+ "src/http/agents.ts",
151
+ /const LocalHarnessProviderSchema = z\.enum\(\[([^\]]*)\]/,
152
+ ),
153
+ exempt: {
154
+ "claude-managed":
155
+ "Cloud harness: runs in Anthropic's sandbox, no local runtime to configure.",
156
+ devin: "Cloud harness: runs on Devin's API, no local runtime to configure.",
157
+ },
158
+ },
159
+ {
160
+ file: "apps/ui/src/lib/agent-runtime-models.ts",
161
+ what: "LOCAL_HARNESSES (dashboard runtime harness picker)",
162
+ members: () =>
163
+ literalMembers(
164
+ "apps/ui/src/lib/agent-runtime-models.ts",
165
+ /export const LOCAL_HARNESSES[^=]*=\s*\[([^\]]*)\]/,
166
+ ),
167
+ exempt: {
168
+ "claude-managed":
169
+ "Cloud harness: runs in Anthropic's sandbox, no local runtime to configure.",
170
+ devin: "Cloud harness: runs on Devin's API, no local runtime to configure.",
171
+ dsh: "KNOWN GAP (#1559): the server's LocalHarnessProviderSchema accepts dsh but the picker does not offer it yet.",
172
+ },
173
+ },
174
+ ];
175
+
176
+ describe("provider registration synchronization", () => {
177
+ for (const touchPoint of TOUCH_POINTS) {
178
+ describe(`${touchPoint.file}: ${touchPoint.what}`, () => {
179
+ test("the extractor finds the list and every exemption is still needed", async () => {
180
+ const members = await touchPoint.members();
181
+ expect(members.length).toBeGreaterThan(0);
182
+ const stale = Object.keys(touchPoint.exempt).filter((provider) =>
183
+ members.includes(provider),
184
+ );
185
+ if (stale.length > 0) {
186
+ throw new Error(
187
+ `Stale exemption in ${touchPoint.file} (${touchPoint.what}): ${stale.join(", ")} is now present. Remove it from the exempt map in src/tests/provider-registration.test.ts.`,
188
+ );
189
+ }
190
+ });
191
+
192
+ for (const provider of ProviderNameSchema.options) {
193
+ if (touchPoint.exempt[provider]) continue;
194
+ test(`registers ${provider}`, async () => {
195
+ const members = await touchPoint.members();
196
+ if (!members.includes(provider)) {
197
+ throw new Error(
198
+ `Provider "${provider}" is missing from ${touchPoint.what} in ${touchPoint.file}. Add it there, or exempt it for this touch point in src/tests/provider-registration.test.ts with a one-line reason.`,
199
+ );
200
+ }
201
+ });
202
+ }
203
+ });
204
+ }
205
+ });
206
+
207
+ // ─── docker-entrypoint.sh ────────────────────────────────────────────────────
208
+ // Bash chains a TS type cannot reach, so these run the real blocks in a bash
209
+ // subprocess and check which branch each provider lands in.
210
+
211
+ const entrypoint = await readRepoFile("docker-entrypoint.sh");
10
212
 
11
213
  const bootstrapStart = 'if [ "$HARNESS_PROVIDER" = "pi" ]; then';
12
214
  const bootstrapEnd = "# ---- Verify provider binary is reachable ----";
@@ -23,20 +225,23 @@ if (isolatedBootstrapBlock === bootstrapBlock) {
23
225
  throw new Error(`Could not isolate entrypoint assignment: ${codexHomeAssignment}`);
24
226
  }
25
227
 
26
- const modelsDevFixture = {
27
- anthropic: { models: { "claude-test": { cost: { input: 1, output: 2 } } } },
28
- openai: { models: { "gpt-test": { cost: { input: 1, output: 2 } } } },
29
- openrouter: { models: { "test/model": { cost: { input: 1, output: 2 } } } },
30
- };
31
-
32
- const ENTRYPOINT_EXEMPTIONS = new Set(["claude", "acp"]);
33
- const ENTRYPOINT_OUTPUT: Record<string, string> = {
34
- dsh: "Warning: dsh provider has no credentials yet",
35
- pi: "Warning: pi provider has no credentials yet",
36
- opencode: "Warning: opencode provider has no credentials yet",
37
- "claude-managed": "Warning: claude-managed provider missing:",
38
- devin: "Warning: devin provider missing DEVIN_API_KEY / DEVIN_ORG_ID",
39
- codex: "Warning: codex provider has no auth.json yet",
228
+ /**
229
+ * The line each provider's credential-bootstrap branch prints with no
230
+ * credentials set, or why it has no branch. Typed over every provider, so a
231
+ * new one does not compile until it is listed here.
232
+ */
233
+ const BOOTSTRAP: Record<ProviderName, { expect: string } | { exempt: string }> = {
234
+ claude: { expect: "Warning: claude provider has no credentials yet" },
235
+ dsh: { expect: "Warning: dsh provider has no credentials yet" },
236
+ pi: { expect: "Warning: pi provider has no credentials yet" },
237
+ opencode: { expect: "Warning: opencode provider has no credentials yet" },
238
+ "claude-managed": { expect: "Warning: claude-managed provider missing:" },
239
+ devin: { expect: "Warning: devin provider missing DEVIN_API_KEY / DEVIN_ORG_ID" },
240
+ codex: { expect: "Warning: codex provider has no auth.json yet" },
241
+ acp: {
242
+ exempt:
243
+ "KNOWN GAP (#1559): no acp branch, so it falls into the claude else branch and prints the claude warning.",
244
+ },
40
245
  };
41
246
 
42
247
  async function runCredentialBootstrap(provider: string): Promise<{
@@ -65,32 +270,81 @@ async function runCredentialBootstrap(provider: string): Promise<{
65
270
  }
66
271
  }
67
272
 
68
- describe("provider registration synchronization", () => {
69
- test("credential dispatcher has a concrete handler for every provider", () => {
70
- expect(Object.keys(CREDENTIAL_PROVIDER_CHECKERS).sort()).toEqual(
71
- [...ProviderNameSchema.options].sort(),
72
- );
73
- });
273
+ function extractMarkedBlock(name: string): string {
274
+ const begin = entrypoint.indexOf(`# BEGIN ${name}`);
275
+ const end = entrypoint.indexOf(`# END ${name}`, begin);
276
+ if (begin === -1 || end === -1) {
277
+ throw new Error(`Could not find the # BEGIN/END ${name} markers in docker-entrypoint.sh.`);
278
+ }
279
+ return entrypoint.slice(begin, end);
280
+ }
74
281
 
75
- test("pricing seeder emits rows for providers with cost reporting", () => {
76
- const registered = new Set(buildPricingSeedRows(modelsDevFixture).map((row) => row.provider));
77
- const expected = ProviderNameSchema.options.filter(
78
- (provider) => provider !== "acp" && provider !== "dsh",
79
- );
80
- expect([...registered].filter((provider) => expected.includes(provider)).sort()).toEqual(
81
- [...expected].sort(),
82
- );
83
- });
282
+ /** Resolvable no-op stubs for every binary the verify chain may look up. */
283
+ function makeBinaryStubDir(): string {
284
+ const dir = mkdtempSync(join(tmpdir(), "provider-registration-bin-"));
285
+ for (const name of ["claude", "codex", "opencode", "dsh"]) {
286
+ const file = join(dir, name);
287
+ writeFileSync(file, "#!/bin/sh\nexit 0\n");
288
+ chmodSync(file, 0o755);
289
+ }
290
+ return dir;
291
+ }
84
292
 
85
- test.skipIf(process.platform === "win32")(
86
- "entrypoint executes a credential bootstrap for every non-exempt provider",
87
- async () => {
88
- for (const provider of ProviderNameSchema.options) {
89
- if (ENTRYPOINT_EXEMPTIONS.has(provider)) continue;
293
+ describe.skipIf(process.platform === "win32")("docker-entrypoint.sh provider chains", () => {
294
+ for (const provider of ProviderNameSchema.options) {
295
+ const entry = BOOTSTRAP[provider] as (typeof BOOTSTRAP)[ProviderName] | undefined;
296
+ if (entry && !("expect" in entry)) {
297
+ test(`credential bootstrap exemption for ${provider} is still needed`, async () => {
90
298
  const result = await runCredentialBootstrap(provider);
91
- expect(result.exitCode).toBe(0);
92
- expect(result.stdout).toContain(ENTRYPOINT_OUTPUT[provider]);
299
+ if (result.stdout.includes(`Warning: ${provider} provider`)) {
300
+ throw new Error(
301
+ `Stale exemption: docker-entrypoint.sh now has a ${provider} credential-bootstrap branch. Replace its BOOTSTRAP exemption in src/tests/provider-registration.test.ts with the warning it prints.`,
302
+ );
303
+ }
304
+ });
305
+ continue;
306
+ }
307
+ test(`credential bootstrap has a ${provider} branch`, async () => {
308
+ if (!entry) {
309
+ throw new Error(
310
+ `Provider "${provider}" is missing from BOOTSTRAP in src/tests/provider-registration.test.ts. Add its docker-entrypoint.sh credential warning, or an exemption with a one-line reason.`,
311
+ );
93
312
  }
94
- },
95
- );
313
+ const result = await runCredentialBootstrap(provider);
314
+ expect(result.exitCode).toBe(0);
315
+ if (!result.stdout.includes(entry.expect)) {
316
+ throw new Error(
317
+ `Provider "${provider}" has no credential-bootstrap branch in docker-entrypoint.sh (expected "${entry.expect}", got "${result.stdout}"). Add an elif, or exempt it in BOOTSTRAP with a one-line reason.`,
318
+ );
319
+ }
320
+ });
321
+ }
322
+
323
+ // #1463 class: a provider with no branch of its own falls through to the
324
+ // catch-all and gets the claude CLI checked instead of its own binary.
325
+ const verifyChain = extractMarkedBlock("verify_provider_binary");
326
+ for (const provider of ProviderNameSchema.options) {
327
+ test(
328
+ `verify_provider_binary does not route ${provider} through another provider's check`,
329
+ async () => {
330
+ const stubDir = makeBinaryStubDir();
331
+ const result = await runChild(["bash", "-c", verifyChain], {
332
+ env: {
333
+ PATH: [stubDir, "/usr/bin", "/bin"].join(delimiter),
334
+ HARNESS_PROVIDER: provider,
335
+ ACP_TARGET: "opencode",
336
+ },
337
+ });
338
+ await rm(stubDir, { recursive: true, force: true });
339
+ expect(result.exitCode).toBe(0);
340
+ const checkedClaude = result.stdout.includes("Claude CLI:");
341
+ if (checkedClaude !== (provider === "claude")) {
342
+ throw new Error(
343
+ `Provider "${provider}" ${checkedClaude ? "falls through to the claude CLI check" : "skips the claude CLI check"} in docker-entrypoint.sh verify_provider_binary. Give it its own elif, or add it to the final \`!= "pi"\` guard if it has no binary.`,
344
+ );
345
+ }
346
+ },
347
+ CHILD_PROCESS_TEST_BUDGET_MS,
348
+ );
349
+ }
96
350
  });
@@ -97,6 +97,8 @@ const LEAD_OR_TASK_CREATOR_VERBS: PermissionVerb[] = ["task.cancel.any", "task.s
97
97
 
98
98
  const LEAD_OR_OWN_NAMESPACE_VERBS: PermissionVerb[] = ["kv.write.any"];
99
99
 
100
+ const HUMAN_OR_LEAD_OR_RESOURCE_OWNER_VERBS: PermissionVerb[] = ["approval.cancel.any"];
101
+
100
102
  const ANY_AUTHENTICATED_VERBS: PermissionVerb[] = [
101
103
  "app.manage",
102
104
  "app.use",
@@ -171,6 +173,7 @@ describe("verb-group partition", () => {
171
173
  ...LEAD_OR_RESOURCE_OWNER_VERBS,
172
174
  ...LEAD_OR_TASK_CREATOR_VERBS,
173
175
  ...LEAD_OR_OWN_NAMESPACE_VERBS,
176
+ ...HUMAN_OR_LEAD_OR_RESOURCE_OWNER_VERBS,
174
177
  ...ANY_AUTHENTICATED_VERBS,
175
178
  ...REQUESTER_OWNS_TASK_VERBS,
176
179
  ...COMPOSITE_VERBS,
@@ -314,6 +317,33 @@ describe("lead-or-resource-owner verbs", () => {
314
317
  });
315
318
  });
316
319
 
320
+ describe("human-or-lead-or-resource-owner verbs (approval.cancel.any)", () => {
321
+ const expected: Expected = {
322
+ lead: true,
323
+ worker: false,
324
+ ownerWorker: true,
325
+ creatorWorker: false,
326
+ userRequester: true,
327
+ foreignUser: true,
328
+ operator: true,
329
+ };
330
+ for (const verb of HUMAN_OR_LEAD_OR_RESOURCE_OWNER_VERBS) {
331
+ test(`${verb}: humans, lead, or owner allowed`, () => {
332
+ expectDecisions(verb, OWNED_RESOURCE, expected);
333
+ });
334
+ }
335
+
336
+ test("ownerless resource denies non-lead agents", () => {
337
+ const decision = can({
338
+ principal: PRINCIPALS.ownerWorker,
339
+ verb: "approval.cancel.any",
340
+ resource: { kind: "owned", ownerAgentId: null },
341
+ source: "mcp",
342
+ });
343
+ expect(decision.allow).toBe(false);
344
+ });
345
+ });
346
+
317
347
  describe("lead-or-task-creator verbs (task.cancel.any)", () => {
318
348
  const expected: Expected = {
319
349
  lead: true,
@@ -30,6 +30,14 @@ describe("reasoningCapability — cache-sourced levels", () => {
30
30
  "xhigh",
31
31
  ]);
32
32
  });
33
+ test("Claude Sonnet 5.5 exposes supported CLI effort levels", () => {
34
+ expect(reasoningCapability("claude", "claude-sonnet-5-5").levels).toEqual([
35
+ "low",
36
+ "medium",
37
+ "high",
38
+ "xhigh",
39
+ ]);
40
+ });
33
41
  test("claude claude-opus-4-8: levels come from reasoning_options.effort, not the fallback", () => {
34
42
  const cap = reasoningCapability("claude", "claude-opus-4-8");
35
43
  expect(cap.supported).toBe(true);
@@ -578,6 +578,13 @@ describe("script connections", () => {
578
578
  // 164 indexes agent_tasks columns and alters api_key_status, neither of
579
579
  // which this migration-112-only fixture creates.
580
580
  markMigrationApplied(database, "164_usage_cover_index_and_key_plans.sql");
581
+ markMigrationApplied(database, "166_approval_request_auto_cancellation.sql");
582
+ // 171 alters memory_retrieval, which this migration-112-only fixture does not create.
583
+ markMigrationApplied(database, "171_memory_retrieval_relevance.sql");
584
+ // 175 indexes workflow_runs, which this migration-112-only fixture does not create.
585
+ markMigrationApplied(database, "175_workflow_runs_list_index.sql");
586
+ // 177 indexes events, which this migration-112-only fixture does not create.
587
+ markMigrationApplied(database, "177_events_event_agent_created_index.sql");
581
588
 
582
589
  const id = crypto.randomUUID();
583
590
  const now = new Date().toISOString();
@@ -98,6 +98,45 @@ describe("script SDK allowlist", () => {
98
98
  }
99
99
  });
100
100
 
101
+ test("approval_cancel posts to the cancel route with the reason", async () => {
102
+ let request: { method: string; path: string; body: unknown } | undefined;
103
+ const httpServer = Bun.serve({
104
+ port: 0,
105
+ async fetch(req) {
106
+ request = {
107
+ method: req.method,
108
+ path: new URL(req.url).pathname,
109
+ body: await req.json(),
110
+ };
111
+ return Response.json({ alreadyCancelled: false, runCancelled: false });
112
+ },
113
+ });
114
+ const config = new SwarmConfig({
115
+ system: {
116
+ apiKey: { value: "sdk-test-key", isSecret: true },
117
+ agentId: { value: "sdk-test-agent", isSecret: false },
118
+ mcpBaseUrl: { value: `http://127.0.0.1:${httpServer.port}`, isSecret: false },
119
+ },
120
+ user: {},
121
+ });
122
+ const sdk = createSwarmSdk(config);
123
+
124
+ try {
125
+ const requestId = crypto.randomUUID();
126
+ await sdk.approval_cancel({ requestId, reason: "done" });
127
+ expect(request).toEqual({
128
+ method: "POST",
129
+ path: `/api/approval-requests/${requestId}/cancel`,
130
+ body: { reason: "done" },
131
+ });
132
+ await expect(sdk.approval_cancel({} as { requestId: string })).rejects.toThrow(
133
+ "approval_cancel requires string `requestId`",
134
+ );
135
+ } finally {
136
+ httpServer.stop(true);
137
+ }
138
+ });
139
+
101
140
  test("schedule_list maps includeFull to the full HTTP field and keeps the default slim", async () => {
102
141
  const requestUrls: URL[] = [];
103
142
  const httpServer = Bun.serve({
@@ -137,6 +137,7 @@ describe("normalizeModelKey()", () => {
137
137
  expect(normalizeModelKey("claude", "claude-opus-5")).toBe("claude-opus-5");
138
138
  expect(normalizeModelKey("claude", "claude-fable-5")).toBe("claude-fable-5");
139
139
  expect(normalizeModelKey("claude", "claude-mythos-5")).toBe("claude-mythos-5");
140
+ expect(normalizeModelKey("claude", "claude-sonnet-5-5")).toBe("claude-sonnet-5-5");
140
141
  expect(normalizeModelKey("claude", "claude-sonnet-5")).toBe("claude-sonnet-5");
141
142
  });
142
143