@desplega.ai/agent-swarm 1.119.6 → 1.121.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 (207) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-qe5zjcbr.js → actions-mwshtkf7.js} +6 -6
  3. package/dist/{anthropic-messages-qjxzsabf.js → anthropic-messages-4gn944cn.js} +29 -13
  4. package/dist/{app-31x7k3vj.js → app-skbty91x.js} +4 -4
  5. package/dist/{assistant-4crt9dve.js → assistant-tg7dhfj8.js} +13 -13
  6. package/dist/{azure-openai-responses-ntfd05zd.js → azure-openai-responses-79zc0wyy.js} +28 -13
  7. package/dist/{boot-reembed-8g69ttz4.js → boot-reembed-ntjmeyh2.js} +4 -4
  8. package/dist/{boot-reembed-q648j733.js → boot-reembed-q5x0xpcp.js} +5 -5
  9. package/dist/{boot-scrub-logs-sfmzd2hj.js → boot-scrub-logs-5gznsfbe.js} +4 -4
  10. package/dist/{claude-adapter-9n3nzt65.js → claude-adapter-vxvcym3r.js} +3 -3
  11. package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-g807894s.js} +2 -2
  12. package/dist/{cli-bpvgs2y6.js → cli-072t49hf.js} +2123 -1533
  13. package/dist/{cli-g6qz2kts.js → cli-0df2g86x.js} +336 -18
  14. package/dist/cli-1728qh70.js +791 -0
  15. package/dist/{cli-vg1jxfqz.js → cli-3b1ayz83.js} +5 -5
  16. package/dist/{cli-r93nz80j.js → cli-3fxqwfh3.js} +1 -1
  17. package/dist/{cli-w6rhc30s.js → cli-3jrdwnn9.js} +20 -5
  18. package/dist/{cli-pasyk3vc.js → cli-46etyprn.js} +10 -5
  19. package/dist/{cli-433c0f4b.js → cli-4pfweq4x.js} +8 -8
  20. package/dist/{cli-2r7s8jhp.js → cli-5c4327dx.js} +7 -1
  21. package/dist/{cli-4s84mw72.js → cli-5hpvd15x.js} +2 -2
  22. package/dist/{cli-branks1w.js → cli-6s3ytc5x.js} +1 -1
  23. package/dist/{cli-da3x3btq.js → cli-7pt9sc83.js} +2 -2
  24. package/dist/{cli-2gv8ygwr.js → cli-7pzae66m.js} +2 -2
  25. package/dist/{cli-qs7nb3zn.js → cli-8wrympd7.js} +27 -1
  26. package/dist/{cli-qw1ccvxm.js → cli-95rbe11h.js} +70 -51
  27. package/dist/{cli-6rvm3jkk.js → cli-agdrxyex.js} +1 -1
  28. package/dist/{cli-wr706gez.js → cli-av2s7my4.js} +167 -66
  29. package/dist/{cli-59jy217p.js → cli-avw9ev3x.js} +55 -21
  30. package/dist/{cli-fkxxh5d4.js → cli-b2mqkg1h.js} +2 -2
  31. package/dist/{cli-1arw00dp.js → cli-ccjwgarc.js} +12 -3
  32. package/dist/{cli-pscrc6tw.js → cli-ch7t5v7n.js} +5 -0
  33. package/dist/{cli-tghre7g6.js → cli-cw70hymf.js} +1 -0
  34. package/dist/{cli-n61x2aph.js → cli-dmzv0986.js} +816 -1335
  35. package/dist/{cli-d1reabed.js → cli-g9vva2qh.js} +2 -2
  36. package/dist/{cli-ner6af7m.js → cli-gdpx70nc.js} +24048 -23715
  37. package/dist/{cli-96feg24n.js → cli-ghq3v4c9.js} +2 -2
  38. package/dist/{cli-wxbnjgce.js → cli-ky8esb8t.js} +3 -3
  39. package/dist/{cli-gdqdkr3j.js → cli-nf7sadev.js} +2 -2
  40. package/dist/{cli-h6cf69x5.js → cli-p9w7kb3h.js} +1 -1
  41. package/dist/{cli-xeatwfbe.js → cli-ry656xem.js} +24 -17
  42. package/dist/{cli-q8dway4w.js → cli-s8bdcke2.js} +1 -1
  43. package/dist/cli-s8s499xy.js +81 -0
  44. package/dist/{cli-sbkwvn5e.js → cli-srjqn7fd.js} +1 -1
  45. package/dist/cli-tv7vekye.js +367 -0
  46. package/dist/{cli-gg5bd32n.js → cli-vg3zrw6h.js} +4 -4
  47. package/dist/{cli-vx0y68jv.js → cli-vp9r4ap4.js} +5 -4
  48. package/dist/{cli-mg280wsv.js → cli-we156bmz.js} +1 -1
  49. package/dist/{cli-86fdnerz.js → cli-wwsaj97q.js} +9 -4
  50. package/dist/{cli-e0y603nn.js → cli-x0e0yvnq.js} +101 -1
  51. package/dist/{cli-mw3xpgwb.js → cli-x4apc9t3.js} +4 -4
  52. package/dist/cli.js +26 -21
  53. package/dist/{codex-adapter-6j2b38tg.js → codex-adapter-mmy741zd.js} +8 -9
  54. package/dist/{codex-session-runner-vz215wgw.js → codex-session-runner-sazmx9zt.js} +8 -9
  55. package/dist/{commands-6xr20jbe.js → commands-cxxye5e4.js} +3 -3
  56. package/dist/{db-1mmvk1z4.js → db-ab1954b7.js} +5 -3
  57. package/dist/{google-generative-ai-n1d3xt4n.js → google-generative-ai-wwa2ked5.js} +14 -18
  58. package/dist/{google-vertex-mxst23gy.js → google-vertex-71xmdd7n.js} +14 -18
  59. package/dist/{handlers-xvrknk8w.js → handlers-fmfe2343.js} +13 -13
  60. package/dist/{hook-qm7he8wn.js → hook-ygnw3cvt.js} +7 -8
  61. package/dist/{http-hj08zq67.js → http-kactcc0z.js} +1128 -536
  62. package/dist/{index-688jph6a.js → index-6erq0wvk.js} +11 -11
  63. package/dist/{index-gze4fg2w.js → index-g86j2yt5.js} +9 -9
  64. package/dist/{index-etz7d8md.js → index-gmzs6a2q.js} +10 -10
  65. package/dist/{index-wj8dfpwh.js → index-hmp82wn0.js} +12 -12
  66. package/dist/{keepalive-94wn9yxw.js → keepalive-3cvt26dw.js} +18 -14
  67. package/dist/{lead-rvcqkp11.js → lead-ysf26cjr.js} +31 -32
  68. package/dist/{maintenance-qhfc4dnp.js → maintenance-9npwptbb.js} +5 -5
  69. package/dist/{mistral-conversations-ykcg66za.js → mistral-conversations-26ya4yah.js} +17 -13
  70. package/dist/{oauth-refresh-sweep-rj8wgvsw.js → oauth-refresh-sweep-0r2v76vw.js} +23 -17
  71. package/dist/{onboard-8bv8q1wc.js → onboard-ddb1674a.js} +2 -2
  72. package/dist/{openai-codex-responses-zy0907j9.js → openai-codex-responses-6rr2ye0s.js} +78 -38
  73. package/dist/{openai-completions-xk4p56n9.js → openai-completions-zfa9nkvt.js} +130 -33
  74. package/dist/{openai-responses-4gv3ca5d.js → openai-responses-9wmdks0g.js} +38 -14
  75. package/dist/{opencode-adapter-5bwzvbwj.js → opencode-adapter-h6rkxwrk.js} +2 -2
  76. package/dist/{openrouter-images-4fsrrzes.js → openrouter-images-dgq98729.js} +10 -3
  77. package/dist/{otel-impl-1kf6fr2m.js → otel-impl-b43bkq8e.js} +4 -4
  78. package/dist/{pi-messages-zb8v76wx.js → pi-messages-nzjbbz47.js} +4 -6
  79. package/dist/{pi-mono-adapter-9sg69kg3.js → pi-mono-adapter-ke789738.js} +2328 -1281
  80. package/dist/{pricing-refresh-e84tar2s.js → pricing-refresh-2rqf114z.js} +5 -5
  81. package/dist/{rbac-roles-atytq5ch.js → rbac-roles-5wkhc8xv.js} +5 -5
  82. package/dist/{rbac-roles-pg0zvnwn.js → rbac-roles-6vtafvzb.js} +4 -4
  83. package/dist/{seed-pricing-xd6nxq7k.js → seed-pricing-23wrb873.js} +4 -4
  84. package/dist/{setup-4z389yk0.js → setup-6rny4fk6.js} +2 -2
  85. package/dist/{worker-m61r0t7m.js → worker-yapewe14.js} +31 -32
  86. package/openapi.json +2671 -1811
  87. package/package.json +10 -5
  88. package/plugin/commands/swarm-chat.md +2 -0
  89. package/plugin/pi-skills/swarm-chat/SKILL.md +2 -0
  90. package/src/be/connection-bindings-blob-migration.ts +123 -0
  91. package/src/be/db-queries/mcp-oauth.ts +409 -247
  92. package/src/be/db-queries/oauth.ts +813 -131
  93. package/src/be/db.ts +97 -6
  94. package/src/be/migrations/117_unified_oauth.sql +624 -0
  95. package/src/be/migrations/119_agent_avatar.sql +7 -0
  96. package/src/be/migrations/120_task_title.sql +3 -0
  97. package/src/be/oauth-credential-bindings.ts +48 -14
  98. package/src/be/oauth-encryption-backfill.ts +84 -0
  99. package/src/be/oauth-refresh-sweep.ts +38 -23
  100. package/src/be/script-connections.ts +1130 -163
  101. package/src/be/script-credential-broker.ts +37 -27
  102. package/src/be/scripts/typecheck.ts +3 -2
  103. package/src/be/seed-capabilities.ts +62 -0
  104. package/src/be/seed-pricing.ts +1 -1
  105. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  106. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  107. package/src/be/vendored-openapi.ts +98 -0
  108. package/src/commands/runner.ts +69 -6
  109. package/src/http/agents.ts +23 -5
  110. package/src/http/all-routes.ts +1 -0
  111. package/src/http/core.ts +8 -5
  112. package/src/http/index.ts +18 -5
  113. package/src/http/mcp-bridge.ts +5 -2
  114. package/src/http/mcp-oauth.ts +103 -101
  115. package/src/http/oauth-callback.ts +332 -0
  116. package/src/http/oauth-generic.ts +14 -54
  117. package/src/http/poll.ts +5 -1
  118. package/src/http/script-connections.ts +651 -170
  119. package/src/http/scripts.ts +4 -2
  120. package/src/http/sessions.ts +1 -1
  121. package/src/http/tasks.ts +35 -0
  122. package/src/http/trackers/jira.ts +22 -34
  123. package/src/http/trackers/linear.ts +21 -36
  124. package/src/http/x.ts +6 -2
  125. package/src/jira/app.ts +12 -6
  126. package/src/jira/oauth.ts +48 -48
  127. package/src/linear/app.ts +44 -2
  128. package/src/linear/oauth.ts +12 -37
  129. package/src/oauth/app-validation.ts +27 -9
  130. package/src/oauth/ensure-mcp-token.ts +1 -0
  131. package/src/oauth/ensure-token.ts +331 -108
  132. package/src/oauth/identity-capture.ts +103 -0
  133. package/src/oauth/keepalive.ts +14 -9
  134. package/src/oauth/presets.ts +242 -0
  135. package/src/oauth/wrapper.ts +197 -76
  136. package/src/prompts/base-prompt.ts +48 -4
  137. package/src/prompts/session-templates.ts +13 -1
  138. package/src/providers/claude-managed-models.ts +8 -0
  139. package/src/rbac/legacy-policy.ts +2 -1
  140. package/src/rbac/permissions.ts +7 -3
  141. package/src/scripts-runtime/credential-broker/broker.ts +51 -8
  142. package/src/scripts-runtime/credential-broker/fetch-patch.ts +30 -3
  143. package/src/scripts-runtime/credential-broker/index.ts +1 -1
  144. package/src/scripts-runtime/credential-broker/types.ts +55 -8
  145. package/src/scripts-runtime/egress-secrets.ts +6 -2
  146. package/src/scripts-runtime/eval-harness.ts +2 -2
  147. package/src/scripts-runtime/executors/types.ts +2 -1
  148. package/src/scripts-runtime/loader.ts +3 -0
  149. package/src/scripts-runtime/types/stdlib.d.ts +11 -0
  150. package/src/server.ts +230 -126
  151. package/src/tests/agent-avatar-resolution.test.ts +123 -0
  152. package/src/tests/agents-list-model-display.test.ts +10 -0
  153. package/src/tests/base-prompt.test.ts +72 -0
  154. package/src/tests/claude-managed-adapter.test.ts +6 -0
  155. package/src/tests/connection-embedded-auth.test.ts +709 -0
  156. package/src/tests/context-window.test.ts +1 -0
  157. package/src/tests/credential-broker.test.ts +123 -125
  158. package/src/tests/db-queries-oauth.test.ts +126 -1
  159. package/src/tests/ensure-token.test.ts +44 -2
  160. package/src/tests/list-endpoint-slimming.test.ts +45 -0
  161. package/src/tests/mcp-oauth-manual-client.test.ts +20 -1
  162. package/src/tests/mcp-oauth-queries.test.ts +154 -8
  163. package/src/tests/oauth-access-token-tool.test.ts +37 -1
  164. package/src/tests/oauth-callback-flow.test.ts +534 -0
  165. package/src/tests/oauth-credential-bindings.test.ts +50 -22
  166. package/src/tests/oauth-presets.test.ts +275 -0
  167. package/src/tests/oauth-refresh-failure.test.ts +263 -0
  168. package/src/tests/oauth-refresh-sweep.test.ts +83 -3
  169. package/src/tests/oauth-wrapper.test.ts +77 -55
  170. package/src/tests/prompt-template-session.test.ts +6 -5
  171. package/src/tests/rbac-charact-misc-tools.test.ts +60 -0
  172. package/src/tests/rbac-engine.test.ts +2 -1
  173. package/src/tests/script-connections-http.test.ts +356 -11
  174. package/src/tests/script-connections.test.ts +102 -0
  175. package/src/tests/sdk-allowlist.test.ts +4 -1
  176. package/src/tests/seed-capabilities.test.ts +92 -0
  177. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  178. package/src/tests/sessions.test.ts +46 -0
  179. package/src/tests/spec-base-url.test.ts +266 -0
  180. package/src/tests/status.test.ts +3 -4
  181. package/src/tests/swarm-config-reserved-keys.test.ts +4 -2
  182. package/src/tests/task-title-route.test.ts +106 -0
  183. package/src/tests/tool-annotations.test.ts +6 -3
  184. package/src/tests/tracker-fold.test.ts +412 -0
  185. package/src/tests/unified-oauth-migration.test.ts +455 -0
  186. package/src/tests/vendored-openapi.test.ts +263 -0
  187. package/src/tools/credential-bindings/tool.ts +232 -77
  188. package/src/tools/oauth-access-token.ts +55 -6
  189. package/src/tools/script-connections/tool.ts +73 -53
  190. package/src/tools/swarm-config/set-config.ts +24 -50
  191. package/src/tools/update-profile.ts +15 -5
  192. package/src/tracker/types.ts +12 -0
  193. package/src/types.ts +29 -0
  194. package/src/utils/context-window.ts +1 -0
  195. package/src/workflows/executors/swarm-script.ts +6 -2
  196. package/vendored-openapi/README.md +22 -0
  197. package/vendored-openapi/github.json +7759 -0
  198. package/vendored-openapi/gmail.json +393 -0
  199. package/vendored-openapi/jira.json +3396 -0
  200. package/vendored-openapi/linear.json +252 -0
  201. package/vendored-openapi/manifest.json +128 -0
  202. package/vendored-openapi/slack.json +2895 -0
  203. package/dist/cli-0pygq9r1.js +0 -429
  204. package/dist/cli-8hhms4hp.js +0 -393
  205. package/dist/cli-z9gzq07x.js +0 -27
  206. package/src/scripts-runtime/credential-broker/store.ts +0 -72
  207. package/dist/{otel-g299k41b.js → otel-63jr5v38.js} +1 -1
@@ -0,0 +1,263 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
3
+ import { mkdtempSync, readFileSync, rmSync, unlinkSync } from "node:fs";
4
+ import type { IncomingMessage, ServerResponse } from "node:http";
5
+ import { tmpdir } from "node:os";
6
+ import path from "node:path";
7
+ import { Readable } from "node:stream";
8
+ import {
9
+ canonicalJson,
10
+ type Manifest,
11
+ sha256,
12
+ trimOpenapiSpec,
13
+ } from "../../scripts/vendored-openapi-utils";
14
+ import { closeDb, createAgent, getDb, initDb } from "../be/db";
15
+ import { runMigrations } from "../be/migrations/runner";
16
+ import { refreshScriptConnection, upsertScriptConnection } from "../be/script-connections";
17
+ import {
18
+ listVendoredOpenapiEntries,
19
+ readVendoredOpenapiSpec,
20
+ resolveVendoredOpenapiDirectory,
21
+ } from "../be/vendored-openapi";
22
+ import {
23
+ handleScriptConnections,
24
+ resetIntegrationsCatalogCacheForTesting,
25
+ } from "../http/script-connections";
26
+ import { getPathSegments, parseQueryParams } from "../http/utils";
27
+
28
+ const TEST_DB_PATH = "./test-vendored-openapi.sqlite";
29
+ const MIGRATION_DB_PATH = "./test-vendored-openapi-migration.sqlite";
30
+ const originalFetch = globalThis.fetch;
31
+ let leadAgentId: string;
32
+
33
+ function removeDbFiles(file: string): void {
34
+ for (const suffix of ["", "-wal", "-shm"]) {
35
+ try {
36
+ unlinkSync(file + suffix);
37
+ } catch (error) {
38
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
39
+ }
40
+ }
41
+ }
42
+
43
+ async function dispatch(
44
+ pathname: string,
45
+ init: { method?: string; body?: unknown; agentId?: string } = {},
46
+ ): Promise<{ status: number; body: unknown }> {
47
+ const req = Readable.from(
48
+ init.body === undefined ? [] : [Buffer.from(JSON.stringify(init.body))],
49
+ ) as IncomingMessage;
50
+ req.method = init.method ?? "GET";
51
+ req.url = pathname;
52
+ req.headers = init.agentId
53
+ ? { "content-type": "application/json", "x-agent-id": init.agentId }
54
+ : { "content-type": "application/json" };
55
+ let status = 200;
56
+ let text = "";
57
+ const res = {
58
+ headersSent: false,
59
+ writableEnded: false,
60
+ setHeader() {},
61
+ writeHead(code: number) {
62
+ status = code;
63
+ this.headersSent = true;
64
+ return this;
65
+ },
66
+ end(chunk?: unknown) {
67
+ if (chunk !== undefined) text += String(chunk);
68
+ this.writableEnded = true;
69
+ return this;
70
+ },
71
+ } as unknown as ServerResponse;
72
+ await handleScriptConnections(
73
+ req,
74
+ res,
75
+ getPathSegments(pathname),
76
+ parseQueryParams(pathname),
77
+ init.agentId,
78
+ );
79
+ return { status, body: JSON.parse(text) as unknown };
80
+ }
81
+
82
+ beforeAll(() => {
83
+ removeDbFiles(TEST_DB_PATH);
84
+ initDb(TEST_DB_PATH);
85
+ leadAgentId = createAgent({ name: "vendored-openapi-lead", isLead: true, status: "idle" }).id;
86
+ });
87
+
88
+ afterEach(() => {
89
+ globalThis.fetch = originalFetch;
90
+ resetIntegrationsCatalogCacheForTesting();
91
+ getDb().run("DELETE FROM script_connections");
92
+ });
93
+
94
+ afterAll(() => {
95
+ closeDb();
96
+ removeDbFiles(TEST_DB_PATH);
97
+ removeDbFiles(MIGRATION_DB_PATH);
98
+ });
99
+
100
+ describe("vendored OpenAPI", () => {
101
+ test("manifest entries are canonical blessed trims with matching checksums", () => {
102
+ const directory = path.join(process.cwd(), "vendored-openapi");
103
+ const manifest = JSON.parse(
104
+ readFileSync(path.join(directory, "manifest.json"), "utf-8"),
105
+ ) as Manifest;
106
+ expect(manifest.version).toBe(1);
107
+ expect(manifest.integrations.map((entry) => entry.slug)).toEqual([
108
+ "github",
109
+ "slack",
110
+ "linear",
111
+ "jira",
112
+ "gmail",
113
+ ]);
114
+ for (const entry of manifest.integrations) {
115
+ const specText = readFileSync(path.join(directory, entry.specFile), "utf-8");
116
+ expect(specText).toBe(canonicalJson(trimOpenapiSpec(JSON.parse(specText), entry)));
117
+ expect(sha256(specText)).toBe(entry.specSha256);
118
+ expect(entry.blessedOperations.length).toBeGreaterThan(0);
119
+ }
120
+ });
121
+
122
+ test("vendored connections read from disk and refresh without network", async () => {
123
+ globalThis.fetch = (() => {
124
+ throw new Error("vendored refresh must not use the network");
125
+ }) as typeof fetch;
126
+ const connection = await upsertScriptConnection({
127
+ slug: "blessedGithub",
128
+ kind: "openapi",
129
+ openapiSpecSourceKind: "vendored",
130
+ openapiSpecSource: "github",
131
+ });
132
+ expect(connection.openapiSpecSourceKind).toBe("vendored");
133
+ expect(connection.openapiSpecSource).toBe("github");
134
+ expect(connection.baseUrl).toBe("https://api.github.com");
135
+ expect(connection.generatedTypes).toContain("issuesCreate");
136
+ expect(connection.generatedTypes).toMatch(/"full_name"|"html_url"/);
137
+
138
+ const refreshed = await refreshScriptConnection(connection.id);
139
+ expect(refreshed?.generationError).toBeNull();
140
+ expect(refreshed?.version).toBe(connection.version + 1);
141
+ });
142
+
143
+ test("resolves vendored specs from the module location when cwd is unrelated (npm-package install)", () => {
144
+ // Simulate the published-package case: the operator runs from an unrelated
145
+ // cwd and there is no /app. Only the module-relative candidate
146
+ // (../../vendored-openapi from src/be) can resolve — proving the specs are
147
+ // found without depending on process.cwd() or the Docker /app fallback.
148
+ const originalCwd = process.cwd();
149
+ const savedEnv = process.env.VENDORED_OPENAPI_DIR;
150
+ delete process.env.VENDORED_OPENAPI_DIR;
151
+ const scratch = mkdtempSync(path.join(tmpdir(), "vendored-cwd-"));
152
+ try {
153
+ process.chdir(scratch);
154
+ const directory = resolveVendoredOpenapiDirectory();
155
+ expect(directory).not.toBeNull();
156
+ expect(listVendoredOpenapiEntries().length).toBeGreaterThan(0);
157
+ expect(readVendoredOpenapiSpec("github").entry.slug).toBe("github");
158
+ } finally {
159
+ process.chdir(originalCwd);
160
+ if (savedEnv === undefined) delete process.env.VENDORED_OPENAPI_DIR;
161
+ else process.env.VENDORED_OPENAPI_DIR = savedEnv;
162
+ rmSync(scratch, { recursive: true, force: true });
163
+ }
164
+ });
165
+
166
+ test("HTTP upsert accepts a vendored spec source without a caller-provided base URL", async () => {
167
+ const response = await dispatch("/api/script-connections", {
168
+ method: "POST",
169
+ agentId: leadAgentId,
170
+ body: {
171
+ kind: "openapi",
172
+ slug: "blessedSlack",
173
+ specSource: { kind: "vendored", slug: "slack" },
174
+ },
175
+ });
176
+ expect(response.status).toBe(200);
177
+ expect(response.body).toEqual(
178
+ expect.objectContaining({ connection: expect.objectContaining({ slug: "blessedSlack" }) }),
179
+ );
180
+ });
181
+
182
+ test("catalog puts blessed entries first and degrades to blessed-only when upstream is down", async () => {
183
+ globalThis.fetch = (async () => new Response("down", { status: 503 })) as typeof fetch;
184
+ const down = (await dispatch("/api/integrations-catalog")).body as {
185
+ entries: Array<{ slug: string; feeds: string[] }>;
186
+ partial: boolean;
187
+ };
188
+ expect(down.partial).toBe(true);
189
+ expect(down.entries).toHaveLength(5);
190
+ expect(down.entries.every((entry) => entry.feeds.includes("blessed"))).toBe(true);
191
+
192
+ globalThis.fetch = (async () =>
193
+ new Response(
194
+ JSON.stringify([
195
+ {
196
+ id: "github-upstream",
197
+ kind: "openapi",
198
+ slug: "github",
199
+ name: "GitHub upstream",
200
+ domain: "github.com",
201
+ },
202
+ { id: "stripe", kind: "openapi", slug: "stripe", name: "Stripe", domain: "stripe.com" },
203
+ ]),
204
+ { status: 200, headers: { "content-type": "application/json" } },
205
+ )) as typeof fetch;
206
+ const merged = (await dispatch("/api/integrations-catalog")).body as {
207
+ entries: Array<{ slug: string; feeds: string[] }>;
208
+ partial: boolean;
209
+ };
210
+ expect(merged.partial).toBe(false);
211
+ expect(merged.entries.slice(0, 5).every((entry) => entry.feeds.includes("blessed"))).toBe(true);
212
+ expect(merged.entries.map((entry) => entry.slug)).toEqual([
213
+ "github",
214
+ "slack",
215
+ "linear",
216
+ "jira",
217
+ "gmail",
218
+ "stripe",
219
+ ]);
220
+ });
221
+
222
+ test("migration 117 preserves rows while widening the source-kind check", () => {
223
+ removeDbFiles(MIGRATION_DB_PATH);
224
+ const database = new Database(MIGRATION_DB_PATH);
225
+ try {
226
+ database.run(`
227
+ CREATE TABLE _migrations (
228
+ version INTEGER PRIMARY KEY,
229
+ name TEXT NOT NULL,
230
+ applied_at TEXT NOT NULL,
231
+ checksum TEXT NOT NULL
232
+ )
233
+ `);
234
+ const migration117 = readFileSync("src/be/migrations/117_unified_oauth.sql", "utf-8");
235
+ database
236
+ .prepare(
237
+ "INSERT INTO _migrations (version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
238
+ )
239
+ .run(
240
+ 117,
241
+ "117_unified_oauth",
242
+ new Date().toISOString(),
243
+ new Bun.CryptoHasher("sha256").update(migration117).digest("hex"),
244
+ );
245
+ runMigrations(database);
246
+ database
247
+ .query(
248
+ "INSERT INTO script_connections (id, slug, kind, openapi_spec_json) VALUES (?, ?, ?, ?)",
249
+ )
250
+ .run("legacy-row", "legacy", "openapi", "{}");
251
+ database.run("DELETE FROM _migrations WHERE version = 117");
252
+ runMigrations(database);
253
+ expect(
254
+ database.query<{ id: string }, []>("SELECT id FROM script_connections").get()?.id,
255
+ ).toBe("legacy-row");
256
+ database
257
+ .query("UPDATE script_connections SET openapi_spec_source_kind = ? WHERE id = ?")
258
+ .run("vendored", "legacy-row");
259
+ } finally {
260
+ database.close();
261
+ }
262
+ });
263
+ });