@desplega.ai/agent-swarm 1.129.0 → 1.131.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 (226) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-kfv2dawe.js → actions-w53mbns9.js} +11 -11
  3. package/dist/{anthropic-messages-3xgme8my.js → anthropic-messages-rjmd2e7n.js} +5 -5
  4. package/dist/{app-q8vjk6e8.js → app-m9gwqsqv.js} +8 -8
  5. package/dist/{artifact-hg4t1nx7.js → artifact-jysj3wrg.js} +2 -2
  6. package/dist/{assistant-g037r345.js → assistant-bp65m6f3.js} +13 -13
  7. package/dist/{azure-openai-responses-xa4sh9f6.js → azure-openai-responses-c0yjmbfd.js} +8 -5
  8. package/dist/{boot-reembed-18pzsrxa.js → boot-reembed-62p0at2k.js} +6 -6
  9. package/dist/{boot-reembed-wpks2cj8.js → boot-reembed-xmfp6wdh.js} +7 -7
  10. package/dist/{boot-scrub-logs-n7szd57r.js → boot-scrub-logs-rr8q2dms.js} +5 -5
  11. package/dist/{claude-adapter-qz4r0ajt.js → claude-adapter-2y47mrkd.js} +9 -9
  12. package/dist/{cli-mhjnbgb6.js → cli-0rcyq6d9.js} +450 -186
  13. package/dist/{cli-ttkmehy0.js → cli-2q72qz2s.js} +274 -25
  14. package/dist/{cli-gztgc3hz.js → cli-2wadx7p2.js} +2 -2
  15. package/dist/cli-3fj60etf.js +153 -0
  16. package/dist/{cli-7ecgm5v0.js → cli-3kf51j15.js} +1 -1
  17. package/dist/{cli-vgexpfsr.js → cli-57khth04.js} +6 -3
  18. package/dist/{cli-2mbe7vk0.js → cli-5dsbcjjw.js} +1 -1
  19. package/dist/{cli-etr62zy6.js → cli-5kx36evn.js} +2 -2
  20. package/dist/{cli-v2tr6amf.js → cli-5n6e5yfn.js} +30 -8
  21. package/dist/{cli-hx02s9zq.js → cli-5v4rw6yd.js} +2 -2
  22. package/dist/{cli-v37yr6mf.js → cli-5ze9rv97.js} +4 -4
  23. package/dist/{cli-ch7t5v7n.js → cli-6map995v.js} +9 -4
  24. package/dist/{cli-h0457a67.js → cli-70xqj0z3.js} +3 -3
  25. package/dist/{cli-kk4f9r17.js → cli-72q8m8gk.js} +8 -6
  26. package/dist/{cli-7padk69a.js → cli-78xwvkhj.js} +2 -2
  27. package/dist/{cli-dxtxacy7.js → cli-9dr5qzc0.js} +45 -8
  28. package/dist/{cli-17zxdgsq.js → cli-a40rnf71.js} +6 -6
  29. package/dist/{cli-t53vnnxz.js → cli-a4jzdpa3.js} +2514 -1055
  30. package/dist/{cli-w10fkrf1.js → cli-akdvwc9v.js} +354 -115
  31. package/dist/{cli-1qkk5er0.js → cli-bmze986d.js} +1 -1
  32. package/dist/{cli-ga12dc1d.js → cli-bqw0mt1n.js} +3 -3
  33. package/dist/{cli-0sf92efp.js → cli-cdeg08wx.js} +1 -1
  34. package/dist/{cli-a8ynhkyj.js → cli-cvgjwnh1.js} +17 -15
  35. package/dist/{cli-9fxg3v5m.js → cli-d2tfkcga.js} +4 -4
  36. package/dist/{cli-nhcd7x43.js → cli-d3x3x1c9.js} +2 -2
  37. package/dist/{cli-6nh2qxvh.js → cli-darcwax3.js} +1 -1
  38. package/dist/{cli-yjbgmysx.js → cli-e7ckcg4v.js} +12 -12
  39. package/dist/{cli-4n2gcjpd.js → cli-efxfnq6q.js} +4 -3
  40. package/dist/{cli-z9xqqhfk.js → cli-er3a4bb0.js} +1 -1
  41. package/dist/{cli-xg2eezyv.js → cli-f1am9szv.js} +1 -1
  42. package/dist/{cli-xy4cwsm9.js → cli-fywms69x.js} +1 -1
  43. package/dist/{cli-33jj76y8.js → cli-g6b8x1h8.js} +1 -1
  44. package/dist/{cli-epsvdckx.js → cli-gm9cbhbq.js} +20 -10
  45. package/dist/{cli-rkndnn90.js → cli-k9qkf1hw.js} +6 -4
  46. package/dist/{cli-zejvt395.js → cli-kh3ba2zf.js} +1 -1
  47. package/dist/{cli-gb25p1h2.js → cli-nm5gtp1x.js} +2 -2
  48. package/dist/{cli-akxnpx17.js → cli-pm6xbh84.js} +1829 -1629
  49. package/dist/{cli-2k0ztbfp.js → cli-psh3c4va.js} +1 -1
  50. package/dist/{cli-39vgrsep.js → cli-qv61hp37.js} +2 -2
  51. package/dist/{cli-hv2fndhm.js → cli-r6kqqbj0.js} +6 -4
  52. package/dist/{cli-1y1cm570.js → cli-rzbpmxdr.js} +1 -1
  53. package/dist/{cli-mmrk00vp.js → cli-t1xze5qh.js} +1 -1
  54. package/dist/{cli-g252a9ky.js → cli-t55yw3a1.js} +2 -2
  55. package/dist/{cli-7bz8dzqa.js → cli-t76vw5db.js} +1184 -1522
  56. package/dist/{cli-4n4mdv21.js → cli-xpxde7rm.js} +7 -3
  57. package/dist/{cli-fpaa5k2q.js → cli-yw8vghx2.js} +54 -18
  58. package/dist/cli-zsspvvm2.js +490 -0
  59. package/dist/cli.js +27 -25
  60. package/dist/{codex-adapter-7v6t7ff6.js → codex-adapter-d24q75wk.js} +7 -7
  61. package/dist/{codex-hook-amndawyb.js → codex-hook-hq54t9ft.js} +2 -2
  62. package/dist/{codex-session-runner-e7j4khe2.js → codex-session-runner-g50vcsex.js} +7 -7
  63. package/dist/{commands-ex0k5tbf.js → commands-c3nrc5hg.js} +5 -5
  64. package/dist/{db-df7jatfc.js → db-nt7fg3tw.js} +5 -5
  65. package/dist/{google-generative-ai-y42hnw9p.js → google-generative-ai-hysrd8qx.js} +7 -5
  66. package/dist/{google-vertex-4sg66gb6.js → google-vertex-p5arrga0.js} +7 -5
  67. package/dist/{handlers-1j2cfqt2.js → handlers-fkr6swat.js} +13 -13
  68. package/dist/{hook-nx9a0mj3.js → hook-77wpvnjc.js} +7 -7
  69. package/dist/{hook-w6yaf6sy.js → hook-g019yehy.js} +8 -8
  70. package/dist/{http-4tz5h8wg.js → http-jewj0syk.js} +2993 -716
  71. package/dist/{index-0bpkzb09.js → index-070v4eyb.js} +3 -3
  72. package/dist/{index-0s67qv7h.js → index-6gm5he8h.js} +11 -11
  73. package/dist/{index-35gtv6gj.js → index-7n2xpbd4.js} +310 -40
  74. package/dist/{index-ker3gccb.js → index-9znhar3x.js} +4 -4
  75. package/dist/{index-5n2c8yay.js → index-hase6zbe.js} +2 -2
  76. package/dist/{index-kcf3tm9h.js → index-hfvggd0z.js} +12 -12
  77. package/dist/{index-7c0jqna0.js → index-hp1kdbjj.js} +13 -13
  78. package/dist/{keepalive-3j4a9168.js → keepalive-j0fjvqfd.js} +7 -7
  79. package/dist/{lead-6kpevymb.js → lead-1n8x30xe.js} +33 -33
  80. package/dist/{maintenance-m1k82ean.js → maintenance-gvev11sa.js} +7 -7
  81. package/dist/{mistral-conversations-bjkdfb6z.js → mistral-conversations-81fwav0j.js} +6 -6
  82. package/dist/{oauth-refresh-sweep-881k8e4q.js → oauth-refresh-sweep-s9ecsvh0.js} +6 -6
  83. package/dist/{onboard-c0ykffjn.js → onboard-pvm5b38w.js} +2 -2
  84. package/dist/{openai-codex-responses-xetseqvt.js → openai-codex-responses-5g27pd6f.js} +43 -23
  85. package/dist/{openai-completions-5r2g5axa.js → openai-completions-3vkmfz2a.js} +55 -11
  86. package/dist/{openai-responses-ce67hd23.js → openai-responses-dg4t7j7a.js} +8 -5
  87. package/dist/{openrouter-images-pzfp22jb.js → openrouter-images-g3gm8931.js} +1 -1
  88. package/dist/{otel-impl-sqagjerv.js → otel-impl-p6jvgd9c.js} +6 -4
  89. package/dist/{otel-3mp81ppd.js → otel-nqa12wf3.js} +2 -2
  90. package/dist/{pi-mono-adapter-k584k16g.js → pi-mono-adapter-t091s27g.js} +17117 -7517
  91. package/dist/{pricing-refresh-y82whbf9.js → pricing-refresh-k49rcyed.js} +7 -7
  92. package/dist/{rbac-roles-nvkqmzyd.js → rbac-roles-30pspvy7.js} +7 -7
  93. package/dist/{rbac-roles-4ht8gegc.js → rbac-roles-cr80g96t.js} +6 -6
  94. package/dist/{render-v2-wvqdgvvp.js → render-v2-xm457ng1.js} +8 -8
  95. package/dist/{seed-pricing-fy3rxhde.js → seed-pricing-yhpv35fr.js} +6 -6
  96. package/dist/{setup-ak5cnc6f.js → setup-nytnmwwr.js} +2 -2
  97. package/dist/{worker-d8769q8a.js → worker-msytr2cw.js} +33 -33
  98. package/openapi.json +34039 -11036
  99. package/package.json +8 -6
  100. package/src/apps/definition.ts +368 -26
  101. package/src/apps/row-store.ts +66 -10
  102. package/src/apps/schema-migrate.ts +95 -1
  103. package/src/apps/store.ts +13 -4
  104. package/src/apps/sync-run-as.ts +18 -0
  105. package/src/apps/sync.ts +934 -0
  106. package/src/apps/version.ts +16 -2
  107. package/src/assets/key.ts +8 -1
  108. package/src/be/asset-key-audit.ts +3 -1
  109. package/src/be/db-queries/mcp-oauth.ts +297 -24
  110. package/src/be/db.ts +42 -1
  111. package/src/be/migrations/129_asset_keys_apps_scripts.sql +161 -0
  112. package/src/be/scripts/db.ts +5 -2
  113. package/src/be/scripts/typecheck.ts +1 -0
  114. package/src/be/seed-scripts/catalog/app-sync-run.ts +32 -0
  115. package/src/be/seed-scripts/catalog/github-issues-pull.ts +102 -0
  116. package/src/be/seed-scripts/index.ts +18 -0
  117. package/src/be/seed-skills/index.ts +7 -0
  118. package/src/http/active-sessions.ts +26 -18
  119. package/src/http/agents.ts +94 -27
  120. package/src/http/api-keys.ts +91 -17
  121. package/src/http/approval-requests.ts +87 -10
  122. package/src/http/apps.ts +361 -53
  123. package/src/http/assets.ts +165 -31
  124. package/src/http/budgets.ts +7 -5
  125. package/src/http/codex-oauth-keep-warm.ts +21 -3
  126. package/src/http/config.ts +46 -16
  127. package/src/http/context.ts +33 -6
  128. package/src/http/ecosystem.ts +20 -6
  129. package/src/http/events.ts +24 -9
  130. package/src/http/favorites.ts +20 -6
  131. package/src/http/fs.ts +60 -17
  132. package/src/http/heartbeat.ts +18 -4
  133. package/src/http/inbox-state.ts +12 -6
  134. package/src/http/integrations.ts +25 -5
  135. package/src/http/kv.ts +36 -15
  136. package/src/http/mcp-bridge.ts +5 -1
  137. package/src/http/mcp-oauth.ts +332 -17
  138. package/src/http/mcp-servers.ts +56 -18
  139. package/src/http/memory.ts +282 -29
  140. package/src/http/metrics.ts +80 -17
  141. package/src/http/oauth-callback.ts +10 -4
  142. package/src/http/oauth-generic.ts +5 -1
  143. package/src/http/oauth-locks.ts +6 -3
  144. package/src/http/openapi.ts +57 -9
  145. package/src/http/page-proxy.ts +25 -5
  146. package/src/http/pages-public.ts +37 -20
  147. package/src/http/pages.ts +93 -28
  148. package/src/http/poll.ts +89 -5
  149. package/src/http/pricing.ts +9 -6
  150. package/src/http/prompt-templates.ts +85 -21
  151. package/src/http/route-def.ts +67 -4
  152. package/src/http/schedules.ts +63 -14
  153. package/src/http/script-connection-proxy.ts +30 -4
  154. package/src/http/script-connections.ts +393 -43
  155. package/src/http/script-runs.ts +73 -19
  156. package/src/http/scripts.ts +257 -39
  157. package/src/http/session-data.ts +87 -14
  158. package/src/http/sessions.ts +80 -5
  159. package/src/http/skills.ts +115 -35
  160. package/src/http/stats.ts +94 -13
  161. package/src/http/task-templates.ts +6 -4
  162. package/src/http/tasks.ts +230 -61
  163. package/src/http/trackers/jira.ts +90 -30
  164. package/src/http/trackers/linear.ts +46 -12
  165. package/src/http/users.ts +162 -25
  166. package/src/http/utils.ts +12 -2
  167. package/src/http/webhooks.ts +30 -16
  168. package/src/http/workflow-events.ts +18 -5
  169. package/src/http/workflows.ts +128 -39
  170. package/src/http/x.ts +37 -6
  171. package/src/oauth/ensure-mcp-token.ts +16 -1
  172. package/src/oauth/mcp-wrapper.ts +281 -26
  173. package/src/providers/pi-mono-adapter.ts +1 -1
  174. package/src/scripts-runtime/credential-broker/README.md +1 -1
  175. package/src/scripts-runtime/credential-broker/fetch-patch.ts +12 -2
  176. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  177. package/src/scripts-runtime/stdlib/fetch.ts +6 -0
  178. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  179. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  180. package/src/server.ts +2 -0
  181. package/src/tests/apps-rbac.test.ts +110 -0
  182. package/src/tests/apps-spike2.test.ts +4 -1
  183. package/src/tests/apps-sync-engine.test.ts +2540 -0
  184. package/src/tests/apps-sync.test.ts +1627 -0
  185. package/src/tests/asset-key-api.test.ts +48 -2
  186. package/src/tests/asset-key-invariants.test.ts +92 -0
  187. package/src/tests/asset-key-migration.test.ts +174 -8
  188. package/src/tests/asset-key-normalization.test.ts +2 -0
  189. package/src/tests/credential-broker.test.ts +27 -2
  190. package/src/tests/mcp-oauth-dcr-reuse.test.ts +743 -0
  191. package/src/tests/mcp-oauth-ensure-token.test.ts +45 -0
  192. package/src/tests/mcp-oauth-manual-client.test.ts +135 -2
  193. package/src/tests/mcp-oauth-queries.test.ts +153 -0
  194. package/src/tests/mcp-oauth-wrapper.test.ts +432 -0
  195. package/src/tests/memory-http-recall-gating.test.ts +2 -0
  196. package/src/tests/openapi-response-contract.test.ts +138 -0
  197. package/src/tests/rbac-admission-e2e.test.ts +68 -0
  198. package/src/tests/script-connections.test.ts +3 -0
  199. package/src/tests/scripts-runtime-fetch.test.ts +121 -0
  200. package/src/tests/scripts-runtime-secret-egress.test.ts +4 -2
  201. package/src/tests/seed-scripts.test.ts +2 -2
  202. package/src/tests/swarm-tool-result-gate.test.ts +32 -0
  203. package/src/tests/task-working-dir.test.ts +8 -4
  204. package/src/tests/whoami-route.test.ts +127 -0
  205. package/src/tests/workflow-engine-v2.test.ts +80 -7
  206. package/src/tests/workflow-swarm-script.test.ts +104 -0
  207. package/src/tools/app-get.ts +10 -2
  208. package/src/tools/app-rollback.ts +1 -0
  209. package/src/tools/app-sync.ts +112 -0
  210. package/src/tools/script-common.ts +20 -0
  211. package/src/tools/tool-config.ts +2 -1
  212. package/src/tools/utils.ts +11 -0
  213. package/src/tools/workflows/create-workflow.ts +4 -1
  214. package/src/types.ts +1394 -1206
  215. package/src/utils/template.ts +8 -1
  216. package/src/utils/zod-openapi.ts +11 -0
  217. package/src/workflows/engine.ts +27 -4
  218. package/src/workflows/executors/swarm-script.ts +28 -0
  219. package/src/workflows/retry-poller.ts +19 -2
  220. package/templates/skills/apps/content.md +94 -5
  221. package/templates/skills/asset-namespaces/content.md +2 -2
  222. package/templates/skills/scheduled-task-resilience/SKILL.md +102 -0
  223. package/templates/skills/scheduled-task-resilience/config.json +14 -0
  224. package/templates/skills/scheduled-task-resilience/content.md +97 -0
  225. package/dist/cli-7wmybrt5.js +0 -115
  226. package/dist/cli-d91pvmq1.js +0 -14
@@ -0,0 +1,161 @@
1
+ -- Extend canonical asset namespaces to apps and scripts.
2
+
3
+ ALTER TABLE apps ADD COLUMN "key" TEXT NOT NULL DEFAULT 'shared/';
4
+ ALTER TABLE scripts ADD COLUMN "key" TEXT NOT NULL DEFAULT 'shared/';
5
+
6
+ UPDATE apps SET "key" = 'shared/' WHERE "key" IS NULL OR trim("key") = '';
7
+ UPDATE scripts SET "key" = 'shared/' WHERE "key" IS NULL OR trim("key") = '';
8
+
9
+ CREATE INDEX idx_apps_asset_key ON apps("key");
10
+ CREATE INDEX idx_scripts_asset_key ON scripts("key");
11
+
12
+ -- SQLite cannot widen a CHECK constraint in place. Rebuild the append-only
13
+ -- history table with app and script added to the existing entity-type set.
14
+ CREATE TABLE asset_key_history_new (
15
+ id TEXT PRIMARY KEY,
16
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('task', 'workflow', 'schedule', 'page', 'app', 'script', 'file')),
17
+ entity_id TEXT NOT NULL,
18
+ previous_key TEXT,
19
+ new_key TEXT NOT NULL,
20
+ changed_by TEXT REFERENCES users(id),
21
+ changed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
22
+ );
23
+
24
+ INSERT INTO asset_key_history_new (
25
+ id,
26
+ entity_type,
27
+ entity_id,
28
+ previous_key,
29
+ new_key,
30
+ changed_by,
31
+ changed_at
32
+ )
33
+ SELECT
34
+ id,
35
+ entity_type,
36
+ entity_id,
37
+ previous_key,
38
+ new_key,
39
+ changed_by,
40
+ changed_at
41
+ FROM asset_key_history;
42
+
43
+ DROP TABLE asset_key_history;
44
+ ALTER TABLE asset_key_history_new RENAME TO asset_key_history;
45
+
46
+ CREATE INDEX idx_asset_key_history_entity
47
+ ON asset_key_history(entity_type, entity_id, changed_at DESC);
48
+
49
+ -- Keep these bodies byte-for-byte aligned with migration 115's entity-table
50
+ -- triggers. SQLite cannot express the remaining NFKC rule.
51
+ CREATE TRIGGER validate_apps_asset_key_insert
52
+ BEFORE INSERT ON apps
53
+ WHEN NEW."key" IS NULL
54
+ OR length(NEW."key") = 0
55
+ OR NEW."key" != trim(NEW."key")
56
+ OR length(NEW."key") > 255
57
+ OR substr(NEW."key", -1, 1) != '/'
58
+ OR instr(NEW."key", char(0)) > 0
59
+ OR instr(NEW."key", char(92)) > 0
60
+ OR instr(NEW."key", '//') > 0
61
+ OR instr(NEW."key", '/../') > 0
62
+ OR instr(NEW."key", '/./') > 0
63
+ OR NEW."key" != lower(NEW."key")
64
+ OR NOT (
65
+ NEW."key" = 'shared/'
66
+ OR NEW."key" LIKE 'shared/%'
67
+ OR (
68
+ NEW."key" LIKE 'personal/%/%'
69
+ AND EXISTS (
70
+ SELECT 1 FROM users
71
+ WHERE id = substr(NEW."key", 10, instr(substr(NEW."key", 10), '/') - 1)
72
+ )
73
+ )
74
+ )
75
+ BEGIN
76
+ SELECT RAISE(ABORT, 'invalid asset namespace key');
77
+ END;
78
+
79
+ CREATE TRIGGER validate_apps_asset_key_update
80
+ BEFORE UPDATE OF "key" ON apps
81
+ WHEN NEW."key" IS NULL
82
+ OR length(NEW."key") = 0
83
+ OR NEW."key" != trim(NEW."key")
84
+ OR length(NEW."key") > 255
85
+ OR substr(NEW."key", -1, 1) != '/'
86
+ OR instr(NEW."key", char(0)) > 0
87
+ OR instr(NEW."key", char(92)) > 0
88
+ OR instr(NEW."key", '//') > 0
89
+ OR instr(NEW."key", '/../') > 0
90
+ OR instr(NEW."key", '/./') > 0
91
+ OR NEW."key" != lower(NEW."key")
92
+ OR NOT (
93
+ NEW."key" = 'shared/'
94
+ OR NEW."key" LIKE 'shared/%'
95
+ OR (
96
+ NEW."key" LIKE 'personal/%/%'
97
+ AND EXISTS (
98
+ SELECT 1 FROM users
99
+ WHERE id = substr(NEW."key", 10, instr(substr(NEW."key", 10), '/') - 1)
100
+ )
101
+ )
102
+ )
103
+ BEGIN
104
+ SELECT RAISE(ABORT, 'invalid asset namespace key');
105
+ END;
106
+
107
+ CREATE TRIGGER validate_scripts_asset_key_insert
108
+ BEFORE INSERT ON scripts
109
+ WHEN NEW."key" IS NULL
110
+ OR length(NEW."key") = 0
111
+ OR NEW."key" != trim(NEW."key")
112
+ OR length(NEW."key") > 255
113
+ OR substr(NEW."key", -1, 1) != '/'
114
+ OR instr(NEW."key", char(0)) > 0
115
+ OR instr(NEW."key", char(92)) > 0
116
+ OR instr(NEW."key", '//') > 0
117
+ OR instr(NEW."key", '/../') > 0
118
+ OR instr(NEW."key", '/./') > 0
119
+ OR NEW."key" != lower(NEW."key")
120
+ OR NOT (
121
+ NEW."key" = 'shared/'
122
+ OR NEW."key" LIKE 'shared/%'
123
+ OR (
124
+ NEW."key" LIKE 'personal/%/%'
125
+ AND EXISTS (
126
+ SELECT 1 FROM users
127
+ WHERE id = substr(NEW."key", 10, instr(substr(NEW."key", 10), '/') - 1)
128
+ )
129
+ )
130
+ )
131
+ BEGIN
132
+ SELECT RAISE(ABORT, 'invalid asset namespace key');
133
+ END;
134
+
135
+ CREATE TRIGGER validate_scripts_asset_key_update
136
+ BEFORE UPDATE OF "key" ON scripts
137
+ WHEN NEW."key" IS NULL
138
+ OR length(NEW."key") = 0
139
+ OR NEW."key" != trim(NEW."key")
140
+ OR length(NEW."key") > 255
141
+ OR substr(NEW."key", -1, 1) != '/'
142
+ OR instr(NEW."key", char(0)) > 0
143
+ OR instr(NEW."key", char(92)) > 0
144
+ OR instr(NEW."key", '//') > 0
145
+ OR instr(NEW."key", '/../') > 0
146
+ OR instr(NEW."key", '/./') > 0
147
+ OR NEW."key" != lower(NEW."key")
148
+ OR NOT (
149
+ NEW."key" = 'shared/'
150
+ OR NEW."key" LIKE 'shared/%'
151
+ OR (
152
+ NEW."key" LIKE 'personal/%/%'
153
+ AND EXISTS (
154
+ SELECT 1 FROM users
155
+ WHERE id = substr(NEW."key", 10, instr(substr(NEW."key", 10), '/') - 1)
156
+ )
157
+ )
158
+ )
159
+ BEGIN
160
+ SELECT RAISE(ABORT, 'invalid asset namespace key');
161
+ END;
@@ -1,3 +1,4 @@
1
+ import { defaultAssetKey } from "../../assets/key";
1
2
  import type {
2
3
  ScriptApiAuthMode,
3
4
  ScriptApiRecord,
@@ -121,6 +122,7 @@ export function insertScript(args: ScriptWriteArgs): ScriptRecord {
121
122
  .prepare<
122
123
  ScriptRow,
123
124
  [
125
+ string,
124
126
  string,
125
127
  string,
126
128
  ScriptScope,
@@ -142,15 +144,16 @@ export function insertScript(args: ScriptWriteArgs): ScriptRecord {
142
144
  ]
143
145
  >(
144
146
  `INSERT INTO scripts (
145
- id, name, scope, scopeId, source, description, intent, signatureJson,
147
+ id, "key", name, scope, scopeId, source, description, intent, signatureJson,
146
148
  argsJsonSchema, contentHash, isScratch, typeChecked, fsMode, createdByAgentId, createdAt, updatedAt,
147
149
  created_by, updated_by
148
150
  )
149
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
151
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
150
152
  RETURNING *`,
151
153
  )
152
154
  .get(
153
155
  id,
156
+ defaultAssetKey("script", id),
154
157
  args.name,
155
158
  args.scope,
156
159
  scopeId,
@@ -292,6 +292,7 @@ export interface SwarmSdk {
292
292
  migration?: Record<string, unknown>;
293
293
  forceElementBreak?: string[];
294
294
  }): Promise<unknown>;
295
+ app_sync(args: { appId: string; model?: string; source?: string }): Promise<unknown>;
295
296
  app_upsert(args: {
296
297
  name: string;
297
298
  description?: string;
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+
3
+ export const argsSchema = z.object({
4
+ appId: z.string().describe("App id to sync"),
5
+ model: z.string().optional().describe("Only sync this model (default: every model with sources)"),
6
+ source: z.string().optional().describe("Only sync this named source (default: every source)"),
7
+ });
8
+
9
+ /** Run an app's sync passes — the schedulable entry point for refreshing source-backed app rows. */
10
+ export default async function appSyncRun(args: any, ctx: any) {
11
+ const parsed = argsSchema.safeParse(args);
12
+ if (!parsed.success) throw new Error("invalid args: " + parsed.error.message);
13
+ const { appId, model, source } = parsed.data;
14
+ const result: any = await ctx.swarm.app_sync({
15
+ appId,
16
+ ...(model ? { model } : {}),
17
+ ...(source ? { source } : {}),
18
+ });
19
+ // The bridge serializes a FAILED sync as structured content in an HTTP 200:
20
+ // returning it would exit 0 and the scheduler would record a successful run.
21
+ // Throw so schedule error accounting sees the failure.
22
+ const payload = result && result.data !== undefined ? result.data : result;
23
+ const failed =
24
+ !result ||
25
+ result.success === false ||
26
+ (payload && (payload.success === false || payload.ok === false));
27
+ if (failed) {
28
+ const why = payload && typeof payload.error === "string" ? payload.error : "sync pass failed";
29
+ throw new Error("app sync failed: " + why);
30
+ }
31
+ return result;
32
+ }
@@ -0,0 +1,102 @@
1
+ import { z } from "zod";
2
+
3
+ export const argsSchema = z.object({
4
+ repo: z.string().describe("Repository in 'owner/name' form, e.g. 'owner/name'"),
5
+ state: z
6
+ .enum(["open", "closed", "all"])
7
+ .optional()
8
+ .describe("Which issues to pull (default 'open')"),
9
+ limit: z
10
+ .number()
11
+ .int()
12
+ .positive()
13
+ .max(100)
14
+ .optional()
15
+ .describe("Page size, GitHub's per_page cap is 100 (default 100)"),
16
+ connection: z
17
+ .string()
18
+ .optional()
19
+ .describe(
20
+ "Connection slug the app source names; echoed back for provenance — credentials are resolved at the egress layer, not here",
21
+ ),
22
+ });
23
+
24
+ /** Pull a repository's GitHub issues as sync records: {records, complete} with pull requests filtered out. */
25
+ export default async function githubIssuesPull(args: any, ctx: any) {
26
+ const parsed = argsSchema.safeParse(args);
27
+ // Failures THROW: a returned {error} object exits 0 and the sync engine
28
+ // reports it as a generic invalid-payload error, burying the real cause.
29
+ if (!parsed.success) throw new Error("invalid args: " + parsed.error.message);
30
+ const { repo, state = "open", limit = 100, connection } = parsed.data;
31
+
32
+ const segments = repo.split("/");
33
+ if (
34
+ segments.length !== 2 ||
35
+ segments.some((segment) => segment === "" || segment === "." || segment === "..")
36
+ ) {
37
+ throw new Error("repo must be in 'owner/name' form");
38
+ }
39
+ const path = segments.map((segment) => encodeURIComponent(segment)).join("/");
40
+
41
+ const payload: any = await ctx.stdlib.fetchJson(
42
+ "https://api.github.com/repos/" + path + "/issues?state=" + state + "&per_page=" + limit,
43
+ {
44
+ headers: {
45
+ Accept: "application/vnd.github+json",
46
+ "User-Agent": "agent-swarm-apps-sync",
47
+ // The placeholder is the point: the sandbox's egress layer swaps it for
48
+ // the real token on the way out, and only toward hosts allowlisted for
49
+ // the run-as identity. Never resolve the secret yourself — no
50
+ // resolved-config read, no token arg.
51
+ Authorization: "Bearer [REDACTED:GITHUB_TOKEN]",
52
+ },
53
+ signal: AbortSignal.timeout(10_000),
54
+ },
55
+ );
56
+
57
+ // fetchJson hands back the parsed body, not the status — a GitHub error is an
58
+ // object carrying `message`, a successful issues page is an array.
59
+ if (!Array.isArray(payload)) {
60
+ const why =
61
+ payload && typeof payload.message === "string" ? payload.message : "unexpected response";
62
+ throw new Error("GitHub issues " + repo + ": " + why);
63
+ }
64
+
65
+ // Completeness is computed on the RAW page, before the pull-request filter: a
66
+ // full raw page means "there may be more". Filtering first would let a page of
67
+ // mostly PRs look short and wrongly claim a complete window, and the engine
68
+ // would sweep still-live rows stale.
69
+ const complete = payload.length < limit;
70
+
71
+ // The issues endpoint interleaves pull requests; only PR entries carry
72
+ // `pull_request`.
73
+ const records = payload
74
+ .filter((issue: any) => issue && !issue.pull_request)
75
+ .map((issue: any) => ({
76
+ key: String(issue.number),
77
+ fields: {
78
+ number: issue.number,
79
+ id: issue.id,
80
+ title: issue.title,
81
+ state: issue.state,
82
+ // The FULL body goes back: the engine scrubs pulled values whole at
83
+ // its trusted boundary, and truncating here first could split a
84
+ // secret across the cut and defeat exact-value redaction. GitHub
85
+ // bounds issue bodies (~65k), so rows stay sane without a cap here.
86
+ body: typeof issue.body === "string" ? issue.body : "",
87
+ userLogin: issue.user ? issue.user.login : null,
88
+ labelsCsv: Array.isArray(issue.labels)
89
+ ? issue.labels
90
+ .map((label: any) => (typeof label === "string" ? label : label && label.name))
91
+ .filter((name: any) => typeof name === "string" && name.length > 0)
92
+ .join(",")
93
+ : "",
94
+ comments: issue.comments,
95
+ htmlUrl: issue.html_url,
96
+ createdAt: issue.created_at,
97
+ updatedAt: issue.updated_at,
98
+ },
99
+ }));
100
+
101
+ return { records, complete, ...(connection ? { connection } : {}) };
102
+ }
@@ -22,6 +22,7 @@ import { getScript, upsertScriptByName } from "../scripts/db";
22
22
  import { extractArgsJsonSchema } from "../scripts/extract-schema";
23
23
  import { typecheckScript } from "../scripts/typecheck";
24
24
  import type { Seeder, SeederRunOptions, SeedItem } from "../seed/types";
25
+ import appSyncRunSrc from "./catalog/app-sync-run.ts" with { type: "text" };
25
26
  import bootTriageSrc from "./catalog/boot-triage.inline.ts" with { type: "text" };
26
27
  // @ts-expect-error Bun text imports synthesize a default string for this helper.
27
28
  import catalogReportSrc from "./catalog/catalog-report.inline.ts" with { type: "text" };
@@ -32,6 +33,7 @@ import delegateSrc from "./catalog/delegate.ts" with { type: "text" };
32
33
  import fetchReadableSrc from "./catalog/fetch-readable.ts" with { type: "text" };
33
34
  import getChildOutputsSrc from "./catalog/get-child-outputs.ts" with { type: "text" };
34
35
  import ghPrSnapshotSrc from "./catalog/gh-pr-snapshot.ts" with { type: "text" };
36
+ import githubIssuesPullSrc from "./catalog/github-issues-pull.ts" with { type: "text" };
35
37
  import groupCountSrc from "./catalog/group-count.ts" with { type: "text" };
36
38
  import jsonQuerySrc from "./catalog/json-query.ts" with { type: "text" };
37
39
  import linearIssueSrc from "./catalog/linear-issue.ts" with { type: "text" };
@@ -77,6 +79,22 @@ export const SEED_SCRIPTS: SeedScript[] = [
77
79
  "Triage a GitHub PR's status in a single call instead of running several gh pr view / gh pr checks / gh api invocations.",
78
80
  source: asText(ghPrSnapshotSrc),
79
81
  },
82
+ {
83
+ name: "github-issues-pull",
84
+ description:
85
+ "Pull a GitHub repository's issues (pull requests filtered out) as app-sync records: number, title, state, body, author, labels, comments and timestamps, with a completeness flag for the pull window.",
86
+ intent:
87
+ "Back a Swarm App model with GitHub issues — the worked example of a sync source script returning {records, complete} with placeholder-based auth.",
88
+ source: asText(githubIssuesPullSrc),
89
+ },
90
+ {
91
+ name: "app-sync-run",
92
+ description:
93
+ "Run a Swarm App's sync passes — refreshes every source-backed model, or just one model/source, and returns the per-pass created/updated/refreshed tallies.",
94
+ intent:
95
+ "Keep an app's synced rows fresh on a schedule or on demand; the script a targetType:'script' schedule points at to sync an app.",
96
+ source: asText(appSyncRunSrc),
97
+ },
80
98
  {
81
99
  name: "fetch-readable",
82
100
  description:
@@ -130,6 +130,12 @@ import researchingContent from "../../../templates/skills/researching/content.md
130
130
  };
131
131
  import reviewingConfig from "../../../templates/skills/reviewing/config.json" with { type: "text" };
132
132
  import reviewingContent from "../../../templates/skills/reviewing/content.md" with { type: "text" };
133
+ import scheduledTaskResilienceConfig from "../../../templates/skills/scheduled-task-resilience/config.json" with {
134
+ type: "text",
135
+ };
136
+ import scheduledTaskResilienceContent from "../../../templates/skills/scheduled-task-resilience/content.md" with {
137
+ type: "text",
138
+ };
133
139
  import scriptBuilderConfig from "../../../templates/skills/script-builder/config.json" with {
134
140
  type: "text",
135
141
  };
@@ -280,6 +286,7 @@ const BUILT_IN_SKILL_SOURCES = [
280
286
  { config: questioningConfig, body: questioningContent },
281
287
  { config: researchingConfig, body: researchingContent },
282
288
  { config: reviewingConfig, body: reviewingContent },
289
+ { config: scheduledTaskResilienceConfig, body: scheduledTaskResilienceContent },
283
290
  { config: scriptBuilderConfig, body: scriptBuilderContent },
284
291
  { config: scriptWorkflowsConfig, body: scriptWorkflowsContent },
285
292
  { config: stepRunningConfig, body: stepRunningContent },
@@ -11,8 +11,9 @@ import {
11
11
  resetOrphanedInProgressTasksForAgent,
12
12
  updateActiveSessionProviderSessionId,
13
13
  } from "../be/db";
14
+ import { ActiveSessionSchema, AgentTaskSchema } from "../types";
14
15
  import { route } from "./route-def";
15
- import { json } from "./utils";
16
+ import { jsonError } from "./utils";
16
17
 
17
18
  // ─── Route Definitions ───────────────────────────────────────────────────────
18
19
 
@@ -26,7 +27,10 @@ const listActiveSessions = route({
26
27
  agentId: z.string().optional(),
27
28
  }),
28
29
  responses: {
29
- 200: { description: "Active session list" },
30
+ 200: {
31
+ description: "Active session list",
32
+ schema: z.object({ sessions: z.array(ActiveSessionSchema) }),
33
+ },
30
34
  },
31
35
  });
32
36
 
@@ -45,7 +49,7 @@ const createActiveSession = route({
45
49
  runnerSessionId: z.string().optional(),
46
50
  }),
47
51
  responses: {
48
- 201: { description: "Session created" },
52
+ 201: { description: "Session created", schema: z.object({ session: ActiveSessionSchema }) },
49
53
  400: { description: "Validation error" },
50
54
  },
51
55
  });
@@ -58,7 +62,7 @@ const deleteSessionByTask = route({
58
62
  tags: ["Active Sessions"],
59
63
  params: z.object({ taskId: z.string() }),
60
64
  responses: {
61
- 200: { description: "Session deleted" },
65
+ 200: { description: "Session deleted", schema: z.object({ deleted: z.boolean() }) },
62
66
  },
63
67
  });
64
68
 
@@ -70,7 +74,7 @@ const deleteSessionById = route({
70
74
  tags: ["Active Sessions"],
71
75
  params: z.object({ id: z.string() }),
72
76
  responses: {
73
- 200: { description: "Session deleted" },
77
+ 200: { description: "Session deleted", schema: z.object({ deleted: z.boolean() }) },
74
78
  },
75
79
  });
76
80
 
@@ -82,7 +86,7 @@ const heartbeatSession = route({
82
86
  tags: ["Active Sessions"],
83
87
  params: z.object({ taskId: z.string() }),
84
88
  responses: {
85
- 200: { description: "Heartbeat updated" },
89
+ 200: { description: "Heartbeat updated", schema: z.object({ updated: z.boolean() }) },
86
90
  },
87
91
  });
88
92
 
@@ -95,7 +99,7 @@ const updateProviderSession = route({
95
99
  params: z.object({ taskId: z.string() }),
96
100
  body: z.object({ providerSessionId: z.string().min(1) }),
97
101
  responses: {
98
- 200: { description: "Provider session ID updated" },
102
+ 200: { description: "Provider session ID updated", schema: z.object({ updated: z.boolean() }) },
99
103
  },
100
104
  });
101
105
 
@@ -112,7 +116,7 @@ const cleanupSessions = route({
112
116
  })
113
117
  .optional(),
114
118
  responses: {
115
- 200: { description: "Cleanup result" },
119
+ 200: { description: "Cleanup result", schema: z.object({ cleaned: z.number().int() }) },
116
120
  },
117
121
  });
118
122
 
@@ -127,7 +131,11 @@ const recoverOrphanedTasks = route({
127
131
  minAgeSeconds: z.number().int().positive().optional(),
128
132
  }),
129
133
  responses: {
130
- 200: { description: "Recovery result" },
134
+ 200: {
135
+ description: "Recovery result",
136
+ schema: z.object({ recovered: z.number().int(), tasks: z.array(AgentTaskSchema) }),
137
+ },
138
+ 403: { description: "Can only recover orphaned tasks for the calling agent" },
131
139
  },
132
140
  });
133
141
 
@@ -144,7 +152,7 @@ export async function handleActiveSessions(
144
152
  const parsed = await listActiveSessions.parse(req, res, pathSegments, queryParams);
145
153
  if (!parsed) return true;
146
154
  const sessions = getActiveSessions(parsed.query.agentId || undefined);
147
- json(res, { sessions });
155
+ listActiveSessions.respond(res, 200, { sessions });
148
156
  return true;
149
157
  }
150
158
 
@@ -159,7 +167,7 @@ export async function handleActiveSessions(
159
167
  taskDescription: parsed.body.taskDescription,
160
168
  runnerSessionId: parsed.body.runnerSessionId,
161
169
  });
162
- json(res, { session }, 201);
170
+ createActiveSession.respond(res, 201, { session });
163
171
  return true;
164
172
  }
165
173
 
@@ -167,7 +175,7 @@ export async function handleActiveSessions(
167
175
  const parsed = await deleteSessionByTask.parse(req, res, pathSegments, queryParams);
168
176
  if (!parsed) return true;
169
177
  const deleted = deleteActiveSession(parsed.params.taskId);
170
- json(res, { deleted });
178
+ deleteSessionByTask.respond(res, 200, { deleted });
171
179
  return true;
172
180
  }
173
181
 
@@ -175,7 +183,7 @@ export async function handleActiveSessions(
175
183
  const parsed = await deleteSessionById.parse(req, res, pathSegments, queryParams);
176
184
  if (!parsed) return true;
177
185
  const deleted = deleteActiveSessionById(parsed.params.id);
178
- json(res, { deleted });
186
+ deleteSessionById.respond(res, 200, { deleted });
179
187
  return true;
180
188
  }
181
189
 
@@ -183,7 +191,7 @@ export async function handleActiveSessions(
183
191
  const parsed = await heartbeatSession.parse(req, res, pathSegments, queryParams);
184
192
  if (!parsed) return true;
185
193
  const updated = heartbeatActiveSession(parsed.params.taskId);
186
- json(res, { updated });
194
+ heartbeatSession.respond(res, 200, { updated });
187
195
  return true;
188
196
  }
189
197
 
@@ -194,7 +202,7 @@ export async function handleActiveSessions(
194
202
  parsed.params.taskId,
195
203
  parsed.body.providerSessionId,
196
204
  );
197
- json(res, { updated });
205
+ updateProviderSession.respond(res, 200, { updated });
198
206
  return true;
199
207
  }
200
208
 
@@ -207,7 +215,7 @@ export async function handleActiveSessions(
207
215
  } else {
208
216
  cleaned = cleanupStaleSessions(parsed.body?.maxAgeMinutes ?? 30);
209
217
  }
210
- json(res, { cleaned });
218
+ cleanupSessions.respond(res, 200, { cleaned });
211
219
  return true;
212
220
  }
213
221
 
@@ -215,14 +223,14 @@ export async function handleActiveSessions(
215
223
  const parsed = await recoverOrphanedTasks.parse(req, res, pathSegments, queryParams);
216
224
  if (!parsed) return true;
217
225
  if (!myAgentId || parsed.body.agentId !== myAgentId) {
218
- json(res, { error: "Can only recover orphaned tasks for the calling agent" }, 403);
226
+ jsonError(res, "Can only recover orphaned tasks for the calling agent", 403);
219
227
  return true;
220
228
  }
221
229
  const tasks = resetOrphanedInProgressTasksForAgent(
222
230
  parsed.body.agentId,
223
231
  parsed.body.minAgeSeconds ?? 60,
224
232
  );
225
- json(res, { recovered: tasks.length, tasks });
233
+ recoverOrphanedTasks.respond(res, 200, { recovered: tasks.length, tasks });
226
234
  return true;
227
235
  }
228
236