@desplega.ai/agent-swarm 1.145.0 → 1.147.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 (221) hide show
  1. package/README.md +2 -1
  2. package/dist/{acp-adapter-580t41c0.js → acp-adapter-9n319wqc.js} +61 -6
  3. package/dist/{actions-m662tjng.js → actions-2vxqvpr9.js} +9 -8
  4. package/dist/{app-zb3rndm8.js → app-w066xfy0.js} +6 -5
  5. package/dist/assistant-kt31dj2h.js +236 -0
  6. package/dist/{boot-reembed-072x00vn.js → boot-reembed-az5rassp.js} +7 -6
  7. package/dist/{boot-reembed-65zz2smt.js → boot-reembed-j3mm3rfz.js} +8 -7
  8. package/dist/{boot-scrub-logs-yz0ams04.js → boot-scrub-logs-b6h54817.js} +4 -4
  9. package/dist/{claude-adapter-paj270y2.js → claude-adapter-w1z04ann.js} +8 -6
  10. package/dist/{claude-managed-setup-w619jncn.js → claude-managed-setup-f5pmsyrp.js} +24 -3
  11. package/dist/{claude-sdk-session-swt80j9z.js → claude-sdk-session-f0cc1kec.js} +8 -6
  12. package/dist/{cli-dt553ktb.js → cli-0b6y7kxm.js} +1 -1
  13. package/dist/{cli-bxdhw615.js → cli-12fz972k.js} +4 -4
  14. package/dist/{cli-s2f20r5w.js → cli-1th1728g.js} +2 -2
  15. package/dist/{cli-rs6ec3b9.js → cli-3tgwnf43.js} +3 -3
  16. package/dist/{cli-gxnzc6wh.js → cli-4nr988h6.js} +1 -1
  17. package/dist/{cli-axs8e6d4.js → cli-53s590z8.js} +1 -1
  18. package/dist/{cli-ygbjp3z8.js → cli-54wwve05.js} +5 -4
  19. package/dist/{cli-v0asr5yv.js → cli-719k9j2c.js} +2 -2
  20. package/dist/{cli-anktweqr.js → cli-89xfd7f9.js} +21 -47
  21. package/dist/{cli-q34a0e10.js → cli-8v0g1yc8.js} +1 -1
  22. package/dist/cli-9d8ntetj.js +42 -0
  23. package/dist/{cli-z9fprcrf.js → cli-atcve9y8.js} +881 -738
  24. package/dist/{cli-jmb7g47h.js → cli-b5bq9crk.js} +14 -31
  25. package/dist/cli-d8brzxjb.js +190 -0
  26. package/dist/{cli-gzky0gkr.js → cli-de14znh8.js} +2 -2
  27. package/dist/{cli-xytwff7f.js → cli-dttph1bw.js} +2 -2
  28. package/dist/cli-ep19k3mb.js +19 -0
  29. package/dist/{cli-m1xa8be7.js → cli-f146mn5s.js} +7 -50
  30. package/dist/{cli-1fj0chdk.js → cli-f7dsy61y.js} +5 -3
  31. package/dist/{cli-f567d5c1.js → cli-ftht3zzj.js} +2782 -2589
  32. package/dist/{cli-a0g1gbt5.js → cli-hbv0kq6w.js} +2 -2
  33. package/dist/{cli-fhsz3twr.js → cli-jsp14b6c.js} +171 -1
  34. package/dist/{cli-7fnkj1hj.js → cli-kpv03zhj.js} +21 -17
  35. package/dist/{cli-m4j6trn4.js → cli-m8yrsg97.js} +2 -2
  36. package/dist/cli-m9sxbkhm.js +779 -0
  37. package/dist/cli-mmemxxdc.js +823 -0
  38. package/dist/{cli-tqtvj5pt.js → cli-n8508nre.js} +1 -1
  39. package/dist/{cli-z96t0thj.js → cli-p1d5b073.js} +37 -2
  40. package/dist/{cli-gh8y8f36.js → cli-rw51bq3j.js} +13 -10
  41. package/dist/{cli-59ej1c07.js → cli-smby5m17.js} +7 -6
  42. package/dist/{cli-65vqzz3v.js → cli-tzvk9haz.js} +94 -372
  43. package/dist/{cli-dc9k2ba9.js → cli-w9kb3bk1.js} +1 -1
  44. package/dist/{cli-kpev657q.js → cli-y6b6fb76.js} +25 -3
  45. package/dist/{cli-123q2get.js → cli-yz3djrm0.js} +9 -9
  46. package/dist/{cli-wvp6bvy4.js → cli-zhjmqxzh.js} +1 -1
  47. package/dist/cli.js +13 -13
  48. package/dist/{codex-adapter-k98vaw2r.js → codex-adapter-zw4dwsx7.js} +2 -2
  49. package/dist/{codex-hook-9pf19cks.js → codex-hook-bw8p5nh4.js} +2 -2
  50. package/dist/{codex-session-runner-tngtphx7.js → codex-session-runner-k981s65v.js} +2 -2
  51. package/dist/{commands-y8nbr3fv.js → commands-sykrxx7e.js} +4 -4
  52. package/dist/{db-30p4h2t1.js → db-sfbsc8wt.js} +6 -4
  53. package/dist/handlers-0aspcejm.js +603 -0
  54. package/dist/{hook-dcw4jfbj.js → hook-4nvqj2sb.js} +7 -5
  55. package/dist/{hook-21jexvfx.js → hook-hxccs7m5.js} +24 -49
  56. package/dist/{http-r4kq5g8d.js → http-y06z517d.js} +230 -94
  57. package/dist/{index-q3qja6ta.js → index-hssww7kj.js} +15 -14
  58. package/dist/{index-gay66b9w.js → index-k8znfx0z.js} +13 -12
  59. package/dist/{index-53zyht4z.js → index-tce1rss4.js} +371 -110
  60. package/dist/{index-nqvf26za.js → index-x5j894zf.js} +14 -13
  61. package/dist/{keepalive-xs8fa55y.js → keepalive-w733ax66.js} +6 -6
  62. package/dist/{lead-d9m2t4s0.js → lead-75bjqa9d.js} +29 -27
  63. package/dist/{maintenance-3zh82g6z.js → maintenance-w4f1zjck.js} +8 -7
  64. package/dist/{oauth-refresh-sweep-03snsye5.js → oauth-refresh-sweep-wdetky26.js} +5 -5
  65. package/dist/{onboard-0cg1925x.js → onboard-bb5net8s.js} +2 -2
  66. package/dist/{otel-impl-0ab95z27.js → otel-impl-0w7c14ap.js} +1 -1
  67. package/dist/{pi-mono-adapter-dtkz4e7j.js → pi-mono-adapter-2has4gnp.js} +12 -2
  68. package/dist/{pricing-refresh-e5jhzz4j.js → pricing-refresh-9nzsrgcq.js} +6 -6
  69. package/dist/{rbac-roles-p1mf40z3.js → rbac-roles-r3hqjp09.js} +5 -5
  70. package/dist/{rbac-roles-2w3cmrkc.js → rbac-roles-z1jrffbt.js} +4 -4
  71. package/dist/{registry-9kdck5ye.js → registry-v5wytg0s.js} +1 -2
  72. package/dist/{render-v2-xhawry3k.js → render-v2-vbr89fnb.js} +6 -5
  73. package/dist/{seed-pricing-t223sw91.js → seed-pricing-g62hy1pk.js} +7 -5
  74. package/dist/{setup-zcs0rce7.js → setup-gnfqnptp.js} +2 -2
  75. package/dist/{worker-qnp2ka62.js → worker-kznxp4qm.js} +29 -27
  76. package/openapi.json +263 -12
  77. package/package.json +1 -1
  78. package/plugin/commands/start-worker.md +4 -9
  79. package/plugin/pi-skills/start-worker/SKILL.md +4 -9
  80. package/src/be/db/agents.ts +556 -0
  81. package/src/be/db/context-versions.ts +117 -0
  82. package/src/be/db/runtime.ts +406 -0
  83. package/src/be/db/tasks/read.ts +1167 -0
  84. package/src/be/db/tasks/write.ts +953 -0
  85. package/src/be/db.ts +584 -3429
  86. package/src/be/migrations/147_scheduled_tasks_parent_task.sql +7 -0
  87. package/src/be/migrations/148_pages_svg.sql +123 -0
  88. package/src/be/migrations/149_session_tokens.sql +24 -0
  89. package/src/be/scripts/typecheck.ts +1 -0
  90. package/src/be/seed-pricing.ts +13 -8
  91. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  92. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  93. package/src/be/seed-scripts/catalog/star-history-refresh.ts +101 -0
  94. package/src/be/seed-scripts/catalog/star-history-renderer.ts +119 -0
  95. package/src/be/seed-scripts/index.ts +17 -0
  96. package/src/be/seed-skills/index.ts +14 -0
  97. package/src/be/swarm-config-guard.ts +5 -2
  98. package/src/be/task-attachment-store.ts +70 -0
  99. package/src/be/users.ts +77 -0
  100. package/src/commands/attachments-section.ts +37 -0
  101. package/src/commands/claude-managed-setup.ts +45 -3
  102. package/src/commands/claude-md-session.ts +408 -0
  103. package/src/commands/context-preamble.ts +15 -3
  104. package/src/commands/profile-sync.ts +205 -5
  105. package/src/commands/provider-credentials.ts +60 -27
  106. package/src/commands/runner.ts +63 -58
  107. package/src/commands/templates.ts +25 -2
  108. package/src/hooks/hook.ts +26 -67
  109. package/src/http/agents.ts +40 -1
  110. package/src/http/auth.ts +13 -1
  111. package/src/http/fs.ts +21 -51
  112. package/src/http/index.ts +2 -0
  113. package/src/http/mcp.ts +32 -0
  114. package/src/http/pages-public.ts +20 -4
  115. package/src/http/pages.ts +11 -3
  116. package/src/http/poll.ts +4 -0
  117. package/src/http/repos.ts +3 -3
  118. package/src/http/sessions.ts +82 -0
  119. package/src/http/tasks.ts +10 -2
  120. package/src/http/utils.ts +79 -1
  121. package/src/prompts/session-templates.ts +12 -8
  122. package/src/providers/acp-adapter.ts +62 -8
  123. package/src/providers/pi-mono-adapter.ts +23 -6
  124. package/src/providers/pricing-sources.md +11 -0
  125. package/src/scheduler/scheduler.test.ts +49 -2
  126. package/src/scheduler/scheduler.ts +4 -0
  127. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  128. package/src/scripts-runtime/types/stdlib.d.ts +8 -0
  129. package/src/scripts-runtime/types/swarm-sdk.d.ts +8 -0
  130. package/src/server.ts +2 -0
  131. package/src/slack/assistant.ts +87 -46
  132. package/src/slack/handlers.test.ts +1 -3
  133. package/src/slack/handlers.ts +102 -87
  134. package/src/slack/inbound-files.ts +466 -0
  135. package/src/slack/templates.ts +14 -0
  136. package/src/tasks/task-terminal-effects.ts +158 -0
  137. package/src/tests/acp-adapter.test.ts +369 -117
  138. package/src/tests/budget-claim-gate.test.ts +1 -0
  139. package/src/tests/build-pi-skills.test.ts +5 -7
  140. package/src/tests/claude-managed-setup.test.ts +52 -4
  141. package/src/tests/claude-md-session.test.ts +603 -0
  142. package/src/tests/codex-skill-resolver.test.ts +19 -0
  143. package/src/tests/context-versioning.test.ts +107 -0
  144. package/src/tests/cors-allowlist.test.ts +309 -0
  145. package/src/tests/credential-check.test.ts +157 -14
  146. package/src/tests/db-agents.test.ts +91 -0
  147. package/src/tests/db-context-versions.test.ts +37 -0
  148. package/src/tests/db-extraction-contract.test.ts +45 -0
  149. package/src/tests/db-runtime.test.ts +70 -0
  150. package/src/tests/db-task-read-contract.test.ts +624 -0
  151. package/src/tests/db-task-write-contract.test.ts +653 -0
  152. package/src/tests/db-tasks-read.test.ts +183 -0
  153. package/src/tests/db-tasks-write.test.ts +230 -0
  154. package/src/tests/defer-task.test.ts +389 -0
  155. package/src/tests/docs-index-footprint.test.js +2 -2
  156. package/src/tests/e2e-visuals-stage.test.ts +151 -0
  157. package/src/tests/file-lock.test.ts +144 -0
  158. package/src/tests/fixtures/file-lock-holder.ts +13 -0
  159. package/src/tests/fixtures/hold-file-lock.ts +25 -0
  160. package/src/tests/heartbeat-expiry.test.ts +188 -0
  161. package/src/tests/http-api-integration.test.ts +78 -1
  162. package/src/tests/pages-public-html.test.ts +43 -0
  163. package/src/tests/pages-svg-migration.test.ts +42 -0
  164. package/src/tests/preload.ts +4 -1
  165. package/src/tests/profile-sync-warning.test.ts +16 -0
  166. package/src/tests/profile-sync.test.ts +126 -0
  167. package/src/tests/prompt-template-session.test.ts +20 -4
  168. package/src/tests/provider-registration.test.ts +93 -0
  169. package/src/tests/rbac-e2e-helpers.test.ts +53 -0
  170. package/src/tests/rbac-e2e-helpers.ts +19 -2
  171. package/src/tests/reload-config.test.ts +26 -0
  172. package/src/tests/routing-affinity-inventory.test.ts +1 -1
  173. package/src/tests/runner-attachments-section.test.ts +22 -2
  174. package/src/tests/runner-context-preamble.test.ts +30 -0
  175. package/src/tests/runner-deployment-resume.test.ts +107 -0
  176. package/src/tests/runner-repo-autostash.test.ts +12 -0
  177. package/src/tests/script-connections.test.ts +4 -0
  178. package/src/tests/seed-scripts.test.ts +2 -2
  179. package/src/tests/session-tokens.test.ts +279 -0
  180. package/src/tests/slack-file-tools-attachments.test.ts +408 -0
  181. package/src/tests/slack-inbound-files.test.ts +847 -0
  182. package/src/tests/star-history-refresh.test.ts +102 -0
  183. package/src/tests/swarm-config-reserved-keys.test.ts +21 -0
  184. package/src/tests/swarm-repos.test.ts +20 -0
  185. package/src/tests/tool-annotations.test.ts +4 -3
  186. package/src/tests/ui-logs-parser.test.ts +176 -0
  187. package/src/tools/create-page.ts +4 -2
  188. package/src/tools/defer-task.ts +239 -0
  189. package/src/tools/repos/update-repo.ts +2 -2
  190. package/src/tools/schedules/create-schedule.ts +1 -0
  191. package/src/tools/schedules/list-schedules.ts +1 -0
  192. package/src/tools/schedules/patch-schedule.ts +1 -0
  193. package/src/tools/schedules/update-schedule.ts +1 -0
  194. package/src/tools/slack-download-file.ts +60 -29
  195. package/src/tools/slack-read.ts +73 -32
  196. package/src/tools/store-progress.ts +16 -146
  197. package/src/tools/tool-config.ts +1 -0
  198. package/src/tools/utils.ts +21 -0
  199. package/src/types.ts +37 -4
  200. package/src/utils/acp-session-token.ts +56 -0
  201. package/src/utils/file-lock.ts +111 -0
  202. package/src/utils/heartbeat-expiry.ts +78 -0
  203. package/src/utils/request-auth-context.ts +2 -0
  204. package/src/utils/task-attachment-links.ts +22 -0
  205. package/templates/skills/code-quality/content.md +8 -5
  206. package/templates/skills/download-task-attachment/content.md +2 -1
  207. package/{plugin/pi-skills → templates/skills}/review-offered-task/SKILL.md +5 -5
  208. package/templates/skills/review-offered-task/config.json +14 -0
  209. package/{plugin/commands/review-offered-task.md → templates/skills/review-offered-task/content.md} +3 -8
  210. package/templates/skills/scheduling/content.md +4 -0
  211. package/templates/skills/slack-interaction/content.md +2 -2
  212. package/templates/skills/work-on-task/SKILL.md +16 -0
  213. package/templates/skills/work-on-task/config.json +14 -0
  214. package/templates/skills/work-on-task/content.md +11 -0
  215. package/dist/assistant-0qt42nzg.js +0 -212
  216. package/dist/cli-fs1kkqgr.js +0 -317
  217. package/dist/cli-jnf5zghe.js +0 -269
  218. package/dist/cli-n1q8q14d.js +0 -177
  219. package/dist/handlers-yfp1qn81.js +0 -592
  220. package/plugin/commands/work-on-task.md +0 -39
  221. package/plugin/pi-skills/work-on-task/SKILL.md +0 -39
@@ -0,0 +1,406 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { configureDbResolver } from "../../prompts/resolver";
3
+ import { VersionableFieldSchema } from "../../types";
4
+ import { enforceAssetKeyStartupAudit } from "../asset-key-audit";
5
+ import { migrateLegacyCredentialBindingBlob } from "../connection-bindings-blob-migration";
6
+ import { resolveEncryptionKey } from "../crypto";
7
+ import { createBunSqliteClient, type DbClient } from "../db-client";
8
+ import { runMigrations } from "../migrations/runner";
9
+ import { autoEncryptLegacyOAuthSecrets } from "../oauth-encryption-backfill";
10
+
11
+ let db: Database | null = null;
12
+ let sqliteVecAvailable = false;
13
+
14
+ export function isSqliteVecAvailable(): boolean {
15
+ return sqliteVecAvailable;
16
+ }
17
+
18
+ /**
19
+ * Resolve the sqlite-vec loadable extension path without opening a Database.
20
+ * Mirrors loadSqliteVec's env-var-first, npm-fallback resolution, but returns
21
+ * the path instead of loading it: the bounded db-query child process (see
22
+ * src/http/db-query-bounded.ts) opens its own read-only connection and must
23
+ * load the extension itself, so the parent resolves the path once and passes
24
+ * it along. The result is memoized — `undefined` is a valid resolved answer
25
+ * (extension genuinely unavailable) and is cached too, so a failing
26
+ * `require` doesn't retry on every query.
27
+ */
28
+ let cachedSqliteVecExtensionPath: string | undefined | null = null;
29
+
30
+ export function resolveSqliteVecExtensionPath(): string | undefined {
31
+ if (cachedSqliteVecExtensionPath !== null) return cachedSqliteVecExtensionPath;
32
+ const extensionPath = process.env.SQLITE_VEC_EXTENSION_PATH;
33
+ if (extensionPath) {
34
+ cachedSqliteVecExtensionPath = extensionPath;
35
+ return cachedSqliteVecExtensionPath;
36
+ }
37
+ try {
38
+ cachedSqliteVecExtensionPath = (
39
+ require("sqlite-vec") as { getLoadablePath(): string }
40
+ ).getLoadablePath();
41
+ } catch {
42
+ cachedSqliteVecExtensionPath = undefined;
43
+ }
44
+ return cachedSqliteVecExtensionPath;
45
+ }
46
+
47
+ /** Test-only: clear the memoized sqlite-vec extension path cache. */
48
+ export function __resetSqliteVecExtensionPathCacheForTests(): void {
49
+ cachedSqliteVecExtensionPath = null;
50
+ }
51
+
52
+ function loadSqliteVec(database: Database): void {
53
+ sqliteVecAvailable = false;
54
+ try {
55
+ const extensionPath = process.env.SQLITE_VEC_EXTENSION_PATH;
56
+ if (extensionPath) {
57
+ database.loadExtension(extensionPath);
58
+ } else {
59
+ const sqliteVec = require("sqlite-vec");
60
+ sqliteVec.load(database);
61
+ }
62
+ sqliteVecAvailable = true;
63
+ console.log(`[db] sqlite-vec loaded${extensionPath ? ` from ${extensionPath}` : ""}`);
64
+ } catch (err) {
65
+ console.warn(
66
+ "[db] sqlite-vec not available, falling back to in-memory cosine:",
67
+ (err as Error).message,
68
+ );
69
+ }
70
+ }
71
+
72
+ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
73
+ if (db) {
74
+ return db;
75
+ }
76
+
77
+ // Load facade callbacks only after module evaluation. Boot stays synchronous,
78
+ // while the runtime has no static dependency back through the facade.
79
+ const { resolvePromptTemplate, autoEncryptLegacyPlaintextSecrets } =
80
+ require("../db") as typeof import("../db");
81
+
82
+ // Fast path for tests: restore from pre-built template that already has
83
+ // migrations, seeds, and all post-init work baked in. Only the per-connection
84
+ // PRAGMA and the in-memory resolver function need to be set.
85
+ const templateGlobals = globalThis as typeof globalThis & {
86
+ __testMigrationTemplate?: Uint8Array;
87
+ };
88
+ const templateBytes = templateGlobals.__testMigrationTemplate;
89
+ if (templateBytes) {
90
+ db = Database.deserialize(templateBytes);
91
+ db.run("PRAGMA busy_timeout = 5000;");
92
+ db.run("PRAGMA foreign_keys = ON;");
93
+ loadSqliteVec(db);
94
+ configureDbResolver(resolvePromptTemplate);
95
+ enforceAssetKeyStartupAudit(db);
96
+ // Ensure the encryption key is resolved even when restoring from the test
97
+ // template. The cache may have been cleared via __resetEncryptionKeyForTests
98
+ // between test suites; this call is a no-op if the cache is already warm.
99
+ resolveEncryptionKey(dbPath);
100
+ return db;
101
+ }
102
+
103
+ db = new Database(dbPath, { create: true });
104
+ console.log(`Database initialized at ${dbPath}`);
105
+
106
+ const database = db;
107
+ database.run("PRAGMA journal_mode = WAL;");
108
+ database.run("PRAGMA busy_timeout = 5000;");
109
+ database.run("PRAGMA foreign_keys = ON;");
110
+ database.run("PRAGMA synchronous = NORMAL;");
111
+ database.run("PRAGMA cache_size = -64000;");
112
+ database.run("PRAGMA mmap_size = 268435456;");
113
+ database.run("PRAGMA temp_store = MEMORY;");
114
+
115
+ // Load sqlite-vec extension for vector search.
116
+ // In compiled binaries (`bun build --compile`) the JS lives in /$bunfs/ and
117
+ // `require.resolve("sqlite-vec-<platform>/vec0.so")` can't find the native
118
+ // asset — so we prefer an explicit filesystem path when set, and only fall
119
+ // back to the npm resolver for normal dev runs.
120
+ loadSqliteVec(database);
121
+
122
+ // Run database migrations (schema creation + incremental changes)
123
+ try {
124
+ runMigrations(database);
125
+ } catch (error) {
126
+ db = null;
127
+ try {
128
+ database.close();
129
+ } catch (closeError) {
130
+ console.error("[migrations] Failed to close database after migration failure:", closeError);
131
+ }
132
+ throw error;
133
+ }
134
+
135
+ // Compatibility migration for legacy databases that predate profile fields
136
+ ensureAgentProfileColumns(database);
137
+
138
+ // Migration: Remove restrictive CHECK constraint on agent_tasks.status
139
+ // Old databases have CHECK(status IN ('pending','in_progress','completed','failed'))
140
+ // which blocks 'cancelled', 'paused', 'offered', 'unassigned' statuses
141
+ try {
142
+ const taskSchemaInfo = db
143
+ .prepare<{ sql: string | null }, []>(
144
+ "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'agent_tasks'",
145
+ )
146
+ .get();
147
+
148
+ const schemaSql = taskSchemaInfo?.sql ?? "";
149
+ const hasStatusCheck = /status\s+TEXT\b[^,]*\bCHECK\s*\(\s*status\s+IN\s*\(/i.test(schemaSql);
150
+ const statusAllowsCancelled = /status\s+IN\s*\([^)]*'cancelled'/i.test(schemaSql);
151
+ const needsStatusMigration = hasStatusCheck && !statusAllowsCancelled;
152
+
153
+ if (needsStatusMigration) {
154
+ console.log("[Migration] Removing restrictive CHECK constraint on agent_tasks.status");
155
+ db.run("PRAGMA foreign_keys=off");
156
+
157
+ // Migrations run before this compatibility guard, so a legacy table now
158
+ // carries every current column. Rebuilding from an old hard-coded column
159
+ // list would silently discard later fields. Derive the replacement
160
+ // schema, copy list, indexes, and triggers from the live table instead;
161
+ // remove only the obsolete status CHECK.
162
+ const rebuiltSchemaSql = schemaSql
163
+ .replace(
164
+ /^(CREATE TABLE\s+(?:IF NOT EXISTS\s+)?)(?:"agent_tasks"|agent_tasks)/i,
165
+ "$1agent_tasks_new",
166
+ )
167
+ .replace(/\s+CHECK\s*\(\s*status\s+IN\s*\([^)]*\)\s*\)/i, "");
168
+ if (rebuiltSchemaSql === schemaSql || !/agent_tasks_new/i.test(rebuiltSchemaSql)) {
169
+ throw new Error("Could not derive agent_tasks schema without legacy status CHECK");
170
+ }
171
+ const columns = db
172
+ .prepare<{ name: string }, []>('PRAGMA table_info("agent_tasks")')
173
+ .all()
174
+ .map((column) => `"${column.name.replaceAll('"', '""')}"`)
175
+ .join(", ");
176
+ const schemaObjects = db
177
+ .prepare<{ sql: string }, []>(
178
+ `SELECT sql FROM sqlite_master
179
+ WHERE tbl_name = 'agent_tasks'
180
+ AND type IN ('index', 'trigger')
181
+ AND sql IS NOT NULL
182
+ ORDER BY type, name`,
183
+ )
184
+ .all()
185
+ .map((row) => row.sql);
186
+
187
+ database.transaction(() => {
188
+ database.run("DROP TABLE IF EXISTS agent_tasks_new");
189
+ database.run(rebuiltSchemaSql);
190
+ database.run(`INSERT INTO agent_tasks_new (${columns}) SELECT ${columns} FROM agent_tasks`);
191
+ database.run("DROP TABLE agent_tasks");
192
+ database.run("ALTER TABLE agent_tasks_new RENAME TO agent_tasks");
193
+ for (const sql of schemaObjects) database.run(sql);
194
+ })();
195
+
196
+ db.run("PRAGMA foreign_keys=on");
197
+ console.log("[Migration] Successfully removed CHECK constraint on agent_tasks.status");
198
+ }
199
+ } catch (e) {
200
+ console.error("[Migration] Failed to update agent_tasks CHECK constraint:", e);
201
+ try {
202
+ db.run("PRAGMA foreign_keys=on");
203
+ } catch (cleanupError) {
204
+ console.error("[Migration] Failed to re-enable SQLite foreign_keys pragma:", cleanupError);
205
+ }
206
+ throw e;
207
+ }
208
+
209
+ // Mandatory namespace invariant: structural corruption is fatal before the
210
+ // API starts listening. Unknown personal users/provider drift remain
211
+ // readable warnings so operators can repair them through the audit surface.
212
+ enforceAssetKeyStartupAudit(database);
213
+
214
+ // Backfill: Seed v1 for existing agents that don't have any context versions yet
215
+ seedContextVersions();
216
+
217
+ // Inject DB resolver into the prompt template resolver (DI to avoid worker/API boundary violation)
218
+ configureDbResolver(resolvePromptTemplate);
219
+
220
+ // Seed default prompt templates from the in-memory code registry
221
+ // The seeder imports the facade; loading it here avoids an initialization cycle.
222
+ const { seedDefaultTemplates } =
223
+ require("../seed-prompt-templates") as typeof import("../seed-prompt-templates");
224
+ seedDefaultTemplates();
225
+
226
+ const hasExistingEncryptedSecrets =
227
+ (database
228
+ .prepare<{ present: number }, []>(
229
+ `SELECT EXISTS(
230
+ SELECT 1 FROM swarm_config WHERE isSecret = 1 AND encrypted = 1
231
+ UNION ALL
232
+ SELECT 1 FROM oauth_apps
233
+ WHERE clientSecretEncrypted = 1 AND clientSecret IS NOT NULL
234
+ UNION ALL
235
+ SELECT 1 FROM oauth_authorizations WHERE tokensEncrypted = 1
236
+ ) AS present`,
237
+ )
238
+ .get()?.present ?? 0) === 1;
239
+
240
+ // Track whether user provided the key (for backup decision)
241
+ const userProvidedKey = !!(
242
+ process.env.SECRETS_ENCRYPTION_KEY?.length || process.env.SECRETS_ENCRYPTION_KEY_FILE?.length
243
+ );
244
+
245
+ // Resolve the secrets encryption key after migrations so we can tell whether
246
+ // this DB already contains encrypted secret rows (must reuse an explicit or
247
+ // on-disk key) or is still plaintext-only (safe to generate a new key before
248
+ // auto-migrating legacy plaintext rows).
249
+ resolveEncryptionKey(dbPath, { allowGenerate: !hasExistingEncryptedSecrets });
250
+
251
+ // Migration 117 carries plaintext tracker OAuth rows with explicit flags;
252
+ // encrypt them only after the shared key has been resolved. This pass is
253
+ // idempotent and intentionally fatal on failure so boot never continues
254
+ // with OAuth credentials left in plaintext.
255
+ try {
256
+ autoEncryptLegacyOAuthSecrets(database);
257
+ } catch (err) {
258
+ console.error(
259
+ `[oauth-encryption] FATAL: failed to auto-encrypt legacy OAuth secrets: ${(err as Error).message}`,
260
+ );
261
+ throw err;
262
+ }
263
+
264
+ // Auto-encrypt any legacy plaintext secrets that predate the encryption
265
+ // feature. Runs after all compatibility guards; failures are fatal because
266
+ // continuing would leave secrets at rest in plaintext — the opposite of the
267
+ // guarantee this feature provides.
268
+ try {
269
+ autoEncryptLegacyPlaintextSecrets(database, dbPath, { createBackup: !userProvidedKey });
270
+ } catch (err) {
271
+ console.error(
272
+ `[secrets] FATAL: failed to auto-encrypt legacy secrets: ${(err as Error).message}`,
273
+ );
274
+ throw err;
275
+ }
276
+
277
+ // Retire the legacy SCRIPT_CREDENTIAL_BINDINGS swarm-config blob: promote any
278
+ // remaining entries to relational rows so the credential broker is
279
+ // relational-only. Idempotent; failures are fatal because a silently-dropped
280
+ // binding would leave scripts unable to authenticate.
281
+ try {
282
+ migrateLegacyCredentialBindingBlob(database);
283
+ } catch (err) {
284
+ console.error(
285
+ `[credential-bindings] FATAL: failed to migrate legacy credential binding blob: ${(err as Error).message}`,
286
+ );
287
+ throw err;
288
+ }
289
+
290
+ return db;
291
+ }
292
+
293
+ export function getDb(path?: string): Database {
294
+ if (!db) {
295
+ return initDb(path ?? process.env.DATABASE_PATH);
296
+ }
297
+ return db;
298
+ }
299
+
300
+ export function closeDb(): void {
301
+ if (db) {
302
+ db.close();
303
+ db = null;
304
+ }
305
+ sqliteVecAvailable = false;
306
+ }
307
+
308
+ // Async seam over the shared connection. The client resolves the underlying
309
+ // handle per operation via getDb(), so close/reopen cycles need no reset.
310
+ let dbClientInstance: DbClient | null = null;
311
+
312
+ export function getDbClient(): DbClient {
313
+ if (!dbClientInstance) {
314
+ dbClientInstance = createBunSqliteClient(() => getDb());
315
+ }
316
+ return dbClientInstance;
317
+ }
318
+
319
+ const VERSIONABLE_FIELDS = VersionableFieldSchema.options;
320
+
321
+ function ensureAgentProfileColumns(database: Database): void {
322
+ // `PRAGMA table_info` on a nonexistent table returns an empty result set
323
+ // rather than erroring, which used to make every column below look
324
+ // "missing" on a table that was never created and throw `no such table:
325
+ // agents` from the ALTER below. This is a legacy-compat shim for
326
+ // pre-migration-system databases; runMigrations() (and its own
327
+ // assertNotEmptyDatabase guard) is what's responsible for the `agents`
328
+ // table existing at all, and already fails loudly if it doesn't. This
329
+ // function must never be the thing that crashes startup instead.
330
+ const agentsTableExists = database
331
+ .prepare<{ name: string }, []>(
332
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='agents'",
333
+ )
334
+ .get();
335
+ if (!agentsTableExists) {
336
+ console.warn("[Migration] agents table does not exist yet — skipping profile column backfill");
337
+ return;
338
+ }
339
+
340
+ const existingColumns = new Set(
341
+ database
342
+ .prepare<{ name: string }, []>("PRAGMA table_info(agents)")
343
+ .all()
344
+ .map((row) => row.name),
345
+ );
346
+
347
+ for (const column of VERSIONABLE_FIELDS) {
348
+ if (!existingColumns.has(column)) {
349
+ try {
350
+ database.run(`ALTER TABLE agents ADD COLUMN ${column} TEXT`);
351
+ } catch (error) {
352
+ console.error(`[Migration] Failed to add missing agents.${column} column`, error);
353
+ throw error;
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Seed v1 context versions for existing agents that don't have any versions yet.
361
+ * Called during migration.
362
+ */
363
+ function seedContextVersions(): void {
364
+ const { computeContentHash } = require("../db") as typeof import("../db");
365
+ const database = getDb();
366
+ const agents = database
367
+ .prepare<
368
+ {
369
+ id: string;
370
+ soulMd: string | null;
371
+ identityMd: string | null;
372
+ toolsMd: string | null;
373
+ claudeMd: string | null;
374
+ setupScript: string | null;
375
+ heartbeatMd: string | null;
376
+ },
377
+ []
378
+ >(`SELECT id, soulMd, identityMd, toolsMd, claudeMd, setupScript, heartbeatMd FROM agents`)
379
+ .all();
380
+
381
+ for (const agent of agents) {
382
+ for (const field of VERSIONABLE_FIELDS) {
383
+ const content = agent[field];
384
+ if (!content) continue;
385
+
386
+ // Check if a version already exists for this agent+field
387
+ const existing = database
388
+ .prepare<{ id: string }, [string, string]>(
389
+ `SELECT id FROM context_versions WHERE agentId = ? AND field = ? LIMIT 1`,
390
+ )
391
+ .get(agent.id, field);
392
+ if (existing) continue;
393
+
394
+ const id = crypto.randomUUID();
395
+ const hash = computeContentHash(content);
396
+ const now = new Date().toISOString();
397
+
398
+ database
399
+ .prepare(
400
+ `INSERT INTO context_versions (id, agentId, field, content, version, changeSource, contentHash, createdAt)
401
+ VALUES (?, ?, ?, ?, 1, 'system', ?, ?)`,
402
+ )
403
+ .run(id, agent.id, field, content, hash, now);
404
+ }
405
+ }
406
+ }