@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,7 @@
1
+ -- Link a one-off schedule back to the task it continues.
2
+ -- `defer-task` completes the current task and creates a wake-up schedule for
3
+ -- the same agent; the task that schedule creates carries this column as its
4
+ -- `parentTaskId`, so the wake-up run receives the deferred task's context
5
+ -- preamble (src/commands/context-preamble.ts).
6
+ -- Nullable: every schedule created by any other path leaves it NULL.
7
+ ALTER TABLE scheduled_tasks ADD COLUMN parentTaskId TEXT;
@@ -0,0 +1,123 @@
1
+ -- Allow SVG images while preserving page IDs, versions, audit fields, and asset keys.
2
+ -- The migration runner disables foreign keys around the transactional rebuild.
3
+ CREATE TABLE pages_new (
4
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
5
+ agentId TEXT NOT NULL,
6
+ slug TEXT NOT NULL,
7
+ title TEXT NOT NULL,
8
+ description TEXT,
9
+ contentType TEXT NOT NULL CHECK (contentType IN ('text/html','application/json','image/svg+xml')),
10
+ authMode TEXT NOT NULL DEFAULT 'authed' CHECK (authMode IN ('public','authed','password')),
11
+ passwordHash TEXT,
12
+ body TEXT NOT NULL,
13
+ needsCredentials TEXT,
14
+ createdAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
15
+ updatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
+ view_count INTEGER NOT NULL DEFAULT 0,
17
+ created_by TEXT REFERENCES users(id),
18
+ updated_by TEXT REFERENCES users(id),
19
+ "key" TEXT NOT NULL DEFAULT 'shared/',
20
+ UNIQUE (agentId, slug)
21
+ );
22
+
23
+ INSERT INTO pages_new (
24
+ id,
25
+ agentId,
26
+ slug,
27
+ title,
28
+ description,
29
+ contentType,
30
+ authMode,
31
+ passwordHash,
32
+ body,
33
+ needsCredentials,
34
+ createdAt,
35
+ updatedAt,
36
+ view_count,
37
+ created_by,
38
+ updated_by,
39
+ "key"
40
+ )
41
+ SELECT
42
+ id,
43
+ agentId,
44
+ slug,
45
+ title,
46
+ description,
47
+ contentType,
48
+ authMode,
49
+ passwordHash,
50
+ body,
51
+ needsCredentials,
52
+ createdAt,
53
+ updatedAt,
54
+ view_count,
55
+ created_by,
56
+ updated_by,
57
+ "key"
58
+ FROM pages;
59
+
60
+ DROP TABLE pages;
61
+ ALTER TABLE pages_new RENAME TO pages;
62
+
63
+ CREATE INDEX IF NOT EXISTS idx_pages_agentId ON pages(agentId);
64
+ CREATE INDEX IF NOT EXISTS idx_pages_updatedAt ON pages(updatedAt DESC);
65
+ CREATE INDEX IF NOT EXISTS idx_pages_created_by ON pages(created_by) WHERE created_by IS NOT NULL;
66
+
67
+ CREATE INDEX idx_pages_asset_key ON pages("key");
68
+ CREATE TRIGGER validate_pages_asset_key_insert
69
+ BEFORE INSERT ON pages
70
+ WHEN NEW."key" IS NULL
71
+ OR length(NEW."key") = 0
72
+ OR NEW."key" != trim(NEW."key")
73
+ OR length(NEW."key") > 255
74
+ OR substr(NEW."key", -1, 1) != '/'
75
+ OR instr(NEW."key", char(0)) > 0
76
+ OR instr(NEW."key", char(92)) > 0
77
+ OR instr(NEW."key", '//') > 0
78
+ OR instr(NEW."key", '/../') > 0
79
+ OR instr(NEW."key", '/./') > 0
80
+ OR NEW."key" != lower(NEW."key")
81
+ OR NOT (
82
+ NEW."key" = 'shared/'
83
+ OR NEW."key" LIKE 'shared/%'
84
+ OR (
85
+ NEW."key" LIKE 'personal/%/%'
86
+ AND EXISTS (
87
+ SELECT 1 FROM users
88
+ WHERE id = substr(NEW."key", 10, instr(substr(NEW."key", 10), '/') - 1)
89
+ )
90
+ )
91
+ )
92
+ BEGIN
93
+ SELECT RAISE(ABORT, 'invalid asset namespace key');
94
+ END;
95
+
96
+ CREATE TRIGGER validate_pages_asset_key_update
97
+ BEFORE UPDATE OF "key" ON pages
98
+ WHEN NEW."key" IS NULL
99
+ OR length(NEW."key") = 0
100
+ OR NEW."key" != trim(NEW."key")
101
+ OR length(NEW."key") > 255
102
+ OR substr(NEW."key", -1, 1) != '/'
103
+ OR instr(NEW."key", char(0)) > 0
104
+ OR instr(NEW."key", char(92)) > 0
105
+ OR instr(NEW."key", '//') > 0
106
+ OR instr(NEW."key", '/../') > 0
107
+ OR instr(NEW."key", '/./') > 0
108
+ OR NEW."key" != lower(NEW."key")
109
+ OR NOT (
110
+ NEW."key" = 'shared/'
111
+ OR NEW."key" LIKE 'shared/%'
112
+ OR (
113
+ NEW."key" LIKE 'personal/%/%'
114
+ AND EXISTS (
115
+ SELECT 1 FROM users
116
+ WHERE id = substr(NEW."key", 10, instr(substr(NEW."key", 10), '/') - 1)
117
+ )
118
+ )
119
+ )
120
+ BEGIN
121
+ SELECT RAISE(ABORT, 'invalid asset namespace key');
122
+ END;
123
+
@@ -0,0 +1,24 @@
1
+ -- Session-scoped ephemeral bearer tokens (prefix: aseph_) minted per ACP
2
+ -- provider session. The ACP adapter hands this token to the target process
3
+ -- in the swarm MCP server's Authorization header instead of the full operator
4
+ -- key. The token is revoked when the session ends (finish / abort) and also
5
+ -- carries an expiresAt ceiling so a leaked bearer cannot be used indefinitely.
6
+ --
7
+ -- Schema mirrors user_tokens but is separate so the two lifecycles cannot
8
+ -- interfere: user tokens are long-lived and manually revoked; session tokens
9
+ -- are programmatically minted and revoked, and always carry expiresAt.
10
+
11
+ CREATE TABLE IF NOT EXISTS session_tokens (
12
+ id TEXT PRIMARY KEY,
13
+ tokenHash TEXT NOT NULL UNIQUE,
14
+ tokenPreview TEXT NOT NULL,
15
+ agentId TEXT NOT NULL,
16
+ taskId TEXT NOT NULL,
17
+ expiresAt TEXT NOT NULL,
18
+ revokedAt TEXT,
19
+ createdAt TEXT NOT NULL DEFAULT (datetime('now')),
20
+ lastUsedAt TEXT
21
+ );
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_session_tokens_agentId
24
+ ON session_tokens(agentId);
@@ -138,6 +138,7 @@ export interface SwarmSdk {
138
138
  task_list(args?: Record<string, unknown>): Promise<unknown>;
139
139
  task_get(args: { taskId: string }): Promise<unknown>;
140
140
  task_storeProgress(args: Record<string, unknown>): Promise<unknown>;
141
+ task_defer(args: { taskId: string; delayMs?: number; runAt?: string; summary: string; note: string; checks?: string[] }): Promise<unknown>;
141
142
  task_poll(args?: Record<string, unknown>): Promise<unknown>;
142
143
  // --- kv ---
143
144
  kv_get<T = unknown>(args: { key: string; namespace?: string }): Promise<KvSdkResponse<KvEntry<T>>>;
@@ -261,6 +261,18 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
261
261
  return rows;
262
262
  }
263
263
 
264
+ /** Build the exact provider rows inserted by the boot-time pricing seeder. */
265
+ export function buildPricingSeedRows(cache: ModelsDevCache | null): PricingSeedRow[] {
266
+ const modelsdevRows = cache ? buildModelsDevSeedRows(cache) : [];
267
+ const manualRows = MANUAL_PRICING_OVERRIDES.map((override) => ({
268
+ provider: override.provider,
269
+ model: override.model,
270
+ tokenClass: override.tokenClass,
271
+ pricePerMillionUsd: override.pricePerMillionUsd,
272
+ }));
273
+ return [...modelsdevRows, ...manualRows];
274
+ }
275
+
264
276
  /**
265
277
  * Phase 2 entrypoint. Idempotent — safe to call on every boot. Logs a one-line
266
278
  * summary so operators can tell whether the boot picked up new rates.
@@ -271,14 +283,7 @@ export function seedPricingFromModelsDev(opts?: { quiet?: boolean }): {
271
283
  } {
272
284
  const db = getDb();
273
285
  const cache = loadModelsDevCache();
274
- const modelsdevRows = cache ? buildModelsDevSeedRows(cache) : [];
275
- const manualRows = MANUAL_PRICING_OVERRIDES.map((o) => ({
276
- provider: o.provider,
277
- model: o.model,
278
- tokenClass: o.tokenClass,
279
- pricePerMillionUsd: o.pricePerMillionUsd,
280
- }));
281
- const allRows = [...modelsdevRows, ...manualRows];
286
+ const allRows = buildPricingSeedRows(cache);
282
287
 
283
288
  const insert = db.prepare<null, [string, string, string, number]>(
284
289
  `INSERT OR IGNORE INTO pricing
@@ -38,7 +38,7 @@ const CONTRADICTORY_RE =
38
38
  /\b(do not|don't|never)\b[\s\S]{0,240}\b(always|must|required)\b|\b(always|must|required)\b[\s\S]{0,240}\b(do not|don't|never)\b/i;
39
39
 
40
40
  const CODE_REGISTRY_EVENTS =
41
- "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
41
+ "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.attachment_failed slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
42
42
  " ",
43
43
  );
44
44
 
@@ -38,7 +38,7 @@ const CONTRADICTORY_RE =
38
38
  /\b(do not|don't|never)\b[\s\S]{0,240}\b(always|must|required)\b|\b(always|must|required)\b[\s\S]{0,240}\b(do not|don't|never)\b/i;
39
39
 
40
40
  const CODE_REGISTRY_EVENTS =
41
- "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
41
+ "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.attachment_failed slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
42
42
  " ",
43
43
  );
44
44
 
@@ -0,0 +1,101 @@
1
+ import type { ScriptContext } from "swarm-sdk";
2
+ import * as z from "zod";
3
+ import { renderChart } from "./star-history-renderer";
4
+
5
+ export const argsSchema = z.object({
6
+ dryRun: z.boolean().default(false),
7
+ authenticated: z.boolean().default(true),
8
+ });
9
+
10
+ const repository = "desplega-ai/agent-swarm";
11
+ const namespace = "star-history:desplega-ai/agent-swarm";
12
+ const cacheSchema = z.object({
13
+ fetchedAt: z.string(),
14
+ pages: z.array(z.object({ etag: z.string().nullable(), timestamps: z.array(z.number().finite()) })),
15
+ });
16
+ type Cache = z.infer<typeof cacheSchema>;
17
+
18
+ /** Refresh the light and dark star-history images, retaining the last-known series on GitHub failure. */
19
+ export default async function main(args: z.input<typeof argsSchema>, ctx: ScriptContext) {
20
+ const options = argsSchema.parse(args ?? {});
21
+ const entry = await ctx.swarm.kv_getOrNull({ namespace, key: "series" });
22
+ const parsed = cacheSchema.safeParse(entry?.value);
23
+ const cached = parsed.success ? parsed.data : undefined;
24
+ let series: Cache;
25
+ let stale = false;
26
+ let fetchError: string | undefined;
27
+ let requests = 0;
28
+ let notModified = 0;
29
+ try {
30
+ const pages: Cache["pages"] = [];
31
+ const deadline = Date.now() + 12000;
32
+ // Bound the job so a pagination error cannot consume the entire API budget.
33
+ for (let page = 1; page <= 50; page++) {
34
+ if (Date.now() > deadline) throw new Error("GitHub refresh budget exhausted");
35
+ const previous = cached?.pages[page - 1];
36
+ const headers: Record<string, string> = {
37
+ Accept: "application/vnd.github.star+json",
38
+ "User-Agent": "agent-swarm-star-history-generator",
39
+ "X-GitHub-Api-Version": "2022-11-28",
40
+ };
41
+ if (options.authenticated) headers.Authorization = "Bearer [REDACTED:GITHUB_TOKEN]";
42
+ if (previous?.etag) headers["If-None-Match"] = previous.etag;
43
+ requests++;
44
+ const response = await ctx.stdlib.fetch(
45
+ `https://api.github.com/repos/${repository}/stargazers?per_page=100&page=${page}`,
46
+ { headers, signal: AbortSignal.timeout(1500) },
47
+ );
48
+ let current: Cache["pages"][number];
49
+ if (response.status === 304 && previous) {
50
+ current = previous;
51
+ notModified++;
52
+ } else {
53
+ if (!response.ok) throw new Error(`GitHub stargazers HTTP ${response.status}`);
54
+ const rows = z.array(z.object({ starred_at: z.string() })).parse(await response.json());
55
+ const timestamps = rows.map((row) => Date.parse(row.starred_at));
56
+ if (timestamps.some((timestamp) => !Number.isFinite(timestamp))) {
57
+ throw new Error("GitHub returned invalid star timestamps");
58
+ }
59
+ current = { etag: response.headers.get("etag"), timestamps };
60
+ }
61
+ pages.push(current);
62
+ if (current.timestamps.length < 100) break;
63
+ if (page === 50) throw new Error("Stargazer pagination exceeded 50 pages");
64
+ }
65
+ if (!pages.some((page) => page.timestamps.length)) throw new Error("No stargazers returned");
66
+ series = { fetchedAt: new Date().toISOString(), pages };
67
+ await ctx.swarm.kv_set({ namespace, key: "series", value: series });
68
+ } catch (error) {
69
+ if (!cached?.pages.some((page) => page.timestamps.length)) {
70
+ throw new Error("GitHub fetch failed and no cached series exists; no pages published");
71
+ }
72
+ series = cached;
73
+ stale = true;
74
+ // Avoid transporting fetch exception text, which may include request details.
75
+ fetchError = error instanceof Error && error.message.startsWith("GitHub stargazers HTTP")
76
+ ? error.message : "GitHub refresh failed; using last-known series";
77
+ }
78
+
79
+ const timestamps = series.pages.flatMap((page) => page.timestamps).sort((a, b) => a - b);
80
+ const pages = [];
81
+ for (const theme of ["light", "dark"] as const) {
82
+ const body = renderChart(repository, timestamps, theme);
83
+ const slug = `star-history-${theme}`;
84
+ if (options.dryRun) {
85
+ pages.push({ slug, bytes: new TextEncoder().encode(body).length });
86
+ continue;
87
+ }
88
+ const response = await ctx.swarm.page_create({
89
+ slug, title: `Star history (${theme})`, body,
90
+ description: `GitHub stars for ${repository}; fetched ${series.fetchedAt}`,
91
+ contentType: "image/svg+xml", authMode: "public",
92
+ }) as { data?: { id?: string; api_url?: string }; id?: string; api_url?: string };
93
+ const page = response.data ?? response;
94
+ if (!page.id || !page.api_url) throw new Error(`Failed to publish ${slug}`);
95
+ pages.push({ slug, id: page.id, apiUrl: page.api_url });
96
+ }
97
+ return {
98
+ repository, stars: timestamps.length, fetchedAt: series.fetchedAt, stale, fetchError,
99
+ authenticated: options.authenticated, requests, notModified, dryRun: options.dryRun, pages,
100
+ };
101
+ }
@@ -0,0 +1,119 @@
1
+ const themes = {
2
+ light: {
3
+ background: "#ffffff",
4
+ border: "#e5e7eb",
5
+ grid: "#e5e7eb",
6
+ text: "#111827",
7
+ muted: "#6b7280",
8
+ accent: "#f59e0b",
9
+ },
10
+ dark: {
11
+ background: "#0d1117",
12
+ border: "#30363d",
13
+ grid: "#21262d",
14
+ text: "#f0f6fc",
15
+ muted: "#8b949e",
16
+ accent: "#f59e0b",
17
+ },
18
+ };
19
+
20
+ function escapeXml(value: string) {
21
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
22
+ }
23
+
24
+ function niceStep(maximum: number, targetTickCount = 6) {
25
+ const roughStep = maximum / targetTickCount;
26
+ const power = 10 ** Math.floor(Math.log10(roughStep));
27
+ const normalized = roughStep / power;
28
+ const multiplier = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
29
+ return multiplier * power;
30
+ }
31
+
32
+ export function renderChart(repository: string, timestamps: number[], themeName: keyof typeof themes) {
33
+ const theme = themes[themeName];
34
+ const width = 960;
35
+ const height = 520;
36
+ const margin = { top: 96, right: 34, bottom: 62, left: 76 };
37
+ const plotWidth = width - margin.left - margin.right;
38
+ const plotHeight = height - margin.top - margin.bottom;
39
+ const firstStar = timestamps[0]!;
40
+ const lastStar = timestamps.at(-1)!;
41
+ const timeRange = Math.max(lastStar - firstStar, 1);
42
+ const yStep = niceStep(timestamps.length);
43
+ const yMaximum = Math.ceil(timestamps.length / yStep) * yStep;
44
+ const plotBottom = margin.top + plotHeight;
45
+
46
+ const x = (timestamp: number) => margin.left + ((timestamp - firstStar) / timeRange) * plotWidth;
47
+ const y = (stars: number) => margin.top + plotHeight - (stars / yMaximum) * plotHeight;
48
+
49
+ const points = timestamps.map((timestamp, index) => ({
50
+ x: x(timestamp),
51
+ y: y(index + 1),
52
+ }));
53
+ const linePath = points
54
+ .map((point, index) => `${index === 0 ? "M" : "L"} ${point.x.toFixed(1)} ${point.y.toFixed(1)}`)
55
+ .join(" ");
56
+ const areaPath = `M ${points[0]!.x.toFixed(1)} ${plotBottom.toFixed(1)} ${linePath.replace(/^M/, "L")} L ${points.at(-1)!.x.toFixed(1)} ${plotBottom.toFixed(1)} Z`;
57
+
58
+ const yTicks = [];
59
+ for (let stars = 0; stars <= yMaximum; stars += yStep) {
60
+ const tickY = y(stars);
61
+ yTicks.push(`
62
+ <line x1="${margin.left}" y1="${tickY.toFixed(1)}" x2="${width - margin.right}" y2="${tickY.toFixed(1)}" stroke="${theme.grid}" />
63
+ <text x="${margin.left - 14}" y="${(tickY + 5).toFixed(1)}" text-anchor="end" fill="${theme.muted}" font-size="13">${stars}</text>`);
64
+ }
65
+
66
+ const dateFormatter = new Intl.DateTimeFormat("en-US", {
67
+ month: "short",
68
+ year: "numeric",
69
+ timeZone: "UTC",
70
+ });
71
+ const updatedFormatter = new Intl.DateTimeFormat("en-US", {
72
+ day: "numeric",
73
+ month: "short",
74
+ year: "numeric",
75
+ timeZone: "UTC",
76
+ });
77
+ const xTicks = Array.from({ length: 7 }, (_, index) => {
78
+ const fraction = index / 6;
79
+ const timestamp = firstStar + timeRange * fraction;
80
+ const tickX = margin.left + plotWidth * fraction;
81
+ return `
82
+ <line x1="${tickX.toFixed(1)}" y1="${margin.top}" x2="${tickX.toFixed(1)}" y2="${plotBottom}" stroke="${theme.grid}" />
83
+ <text x="${tickX.toFixed(1)}" y="${plotBottom + 30}" text-anchor="middle" fill="${theme.muted}" font-size="13">${dateFormatter.format(timestamp)}</text>`;
84
+ });
85
+
86
+ const escapedRepository = escapeXml(repository);
87
+ const lastPoint = points.at(-1)!;
88
+
89
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="520" viewBox="0 0 960 520" role="img" aria-labelledby="title description">
90
+ <title id="title">Star history for ${escapedRepository}</title>
91
+ <desc id="description">${timestamps.length} cumulative GitHub stars from ${updatedFormatter.format(firstStar)} through ${updatedFormatter.format(lastStar)}.</desc>
92
+ <defs>
93
+ <linearGradient id="star-area" x1="0" y1="0" x2="0" y2="1">
94
+ <stop offset="0%" stop-color="${theme.accent}" stop-opacity="0.24" />
95
+ <stop offset="100%" stop-color="${theme.accent}" stop-opacity="0.02" />
96
+ </linearGradient>
97
+ <filter id="point-shadow" x="-100%" y="-100%" width="300%" height="300%">
98
+ <feDropShadow dx="0" dy="1" stdDeviation="2" flood-color="${theme.accent}" flood-opacity="0.35" />
99
+ </filter>
100
+ </defs>
101
+ <rect x="0.5" y="0.5" width="959" height="519" rx="12" fill="${theme.background}" stroke="${theme.border}" />
102
+ <g font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif">
103
+ <text x="${margin.left}" y="43" fill="${theme.text}" font-size="21" font-weight="600">${escapedRepository}</text>
104
+ <text x="${margin.left}" y="69" fill="${theme.muted}" font-size="13">Cumulative GitHub stars · updated ${updatedFormatter.format(lastStar)}</text>
105
+ <g transform="translate(${width - margin.right - 118}, 34)">
106
+ <text x="0" y="17" fill="${theme.accent}" font-size="19">★</text>
107
+ <text x="27" y="17" fill="${theme.text}" font-size="16" font-weight="600">${timestamps.length} stars</text>
108
+ </g>
109
+ ${yTicks.join("").trim()}
110
+ ${xTicks.join("").trim()}
111
+ <path d="${areaPath}" fill="url(#star-area)" />
112
+ <path d="${linePath}" fill="none" stroke="${theme.accent}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
113
+ <circle cx="${lastPoint.x.toFixed(1)}" cy="${lastPoint.y.toFixed(1)}" r="5" fill="${theme.background}" stroke="${theme.accent}" stroke-width="3" filter="url(#point-shadow)" />
114
+ <text x="20" y="${margin.top + plotHeight / 2}" transform="rotate(-90 20 ${margin.top + plotHeight / 2})" text-anchor="middle" fill="${theme.muted}" font-size="13">GitHub stars</text>
115
+ </g>
116
+ </svg>
117
+ `;
118
+ }
119
+
@@ -44,6 +44,9 @@ import reportProgressSrc from "./catalog/report-progress.ts" with { type: "text"
44
44
  import scheduleHealthSrc from "./catalog/schedule-health.ts" with { type: "text" };
45
45
  import slackThreadFlattenSrc from "./catalog/slack-thread-flatten.ts" with { type: "text" };
46
46
  import smartRecallSrc from "./catalog/smart-recall.ts" with { type: "text" };
47
+ import starHistoryRefreshSrc from "./catalog/star-history-refresh.ts" with { type: "text" };
48
+ // @ts-expect-error Bun text imports synthesize a default string for this helper.
49
+ import starHistoryRendererSrc from "./catalog/star-history-renderer.ts" with { type: "text" };
47
50
  import swarmOverviewSrc from "./catalog/swarm-overview.ts" with { type: "text" };
48
51
  import taskContextGatheringSrc from "./catalog/task-context-gathering.ts" with { type: "text" };
49
52
  import taskFailureAuditSrc from "./catalog/task-failure-audit.ts" with { type: "text" };
@@ -71,6 +74,20 @@ function bundleCatalogReport(source: string): string {
71
74
  }
72
75
 
73
76
  export const SEED_SCRIPTS: SeedScript[] = [
77
+ {
78
+ name: "star-history-refresh",
79
+ description:
80
+ "Refresh stable public light/dark SVG star-history pages from GitHub with ETags and a last-known KV series.",
81
+ intent:
82
+ "Run hourly under the same page-owning agent identity; dryRun validates rendering before SVG page support deploys.",
83
+ source:
84
+ asText(starHistoryRendererSrc) +
85
+ "\n" +
86
+ asText(starHistoryRefreshSrc).replace(
87
+ 'import { renderChart } from "./star-history-renderer";',
88
+ "",
89
+ ),
90
+ },
74
91
  {
75
92
  name: "gh-pr-snapshot",
76
93
  description:
@@ -154,6 +154,12 @@ import researchingConfig from "../../../templates/skills/researching/config.json
154
154
  import researchingContent from "../../../templates/skills/researching/content.md" with {
155
155
  type: "text",
156
156
  };
157
+ import reviewOfferedTaskConfig from "../../../templates/skills/review-offered-task/config.json" with {
158
+ type: "text",
159
+ };
160
+ import reviewOfferedTaskContent from "../../../templates/skills/review-offered-task/content.md" with {
161
+ type: "text",
162
+ };
157
163
  import reviewingConfig from "../../../templates/skills/reviewing/config.json" with { type: "text" };
158
164
  import reviewingContent from "../../../templates/skills/reviewing/content.md" with { type: "text" };
159
165
  import scheduledTaskResilienceConfig from "../../../templates/skills/scheduled-task-resilience/config.json" with {
@@ -230,6 +236,12 @@ import vPlanningContent from "../../../templates/skills/v-planning/content.md" w
230
236
  };
231
237
  import verifyingConfig from "../../../templates/skills/verifying/config.json" with { type: "text" };
232
238
  import verifyingContent from "../../../templates/skills/verifying/content.md" with { type: "text" };
239
+ import workOnTaskConfig from "../../../templates/skills/work-on-task/config.json" with {
240
+ type: "text",
241
+ };
242
+ import workOnTaskContent from "../../../templates/skills/work-on-task/content.md" with {
243
+ type: "text",
244
+ };
233
245
  import workflowIterateConfig from "../../../templates/skills/workflow-iterate/config.json" with {
234
246
  type: "text",
235
247
  };
@@ -328,6 +340,7 @@ const BUILT_IN_SKILL_SOURCES = [
328
340
  { config: qaConfig, body: qaContent },
329
341
  { config: questioningConfig, body: questioningContent },
330
342
  { config: researchingConfig, body: researchingContent },
343
+ { config: reviewOfferedTaskConfig, body: reviewOfferedTaskContent },
331
344
  { config: reviewingConfig, body: reviewingContent },
332
345
  { config: scheduledTaskResilienceConfig, body: scheduledTaskResilienceContent },
333
346
  { config: schedulingConfig, body: schedulingContent },
@@ -342,6 +355,7 @@ const BUILT_IN_SKILL_SOURCES = [
342
355
  { config: vImplementingConfig, body: vImplementingContent },
343
356
  { config: vPlanningConfig, body: vPlanningContent },
344
357
  { config: verifyingConfig, body: verifyingContent },
358
+ { config: workOnTaskConfig, body: workOnTaskContent },
345
359
  { config: workflowIterateConfig, body: workflowIterateContent },
346
360
  { config: workflowStructuredOutputConfig, body: workflowStructuredOutputContent },
347
361
  { config: wtsExpertConfig, body: wtsExpertContent },
@@ -11,10 +11,13 @@ import { ProviderNameSchema } from "../types";
11
11
  * - `SECRETS_ENCRYPTION_KEY` is required to decrypt secrets stored in
12
12
  * swarm_config, so it cannot itself be stored encrypted there.
13
13
  *
14
+ * `CORS_ALLOW_ANY_ORIGIN` is deployment-only: runtime config must not
15
+ * re-enable unrestricted credentialed CORS.
16
+ *
14
17
  * Matching is case-insensitive so `api_key`, `Api_Key`, etc. are all
15
18
  * rejected at every write path (DB helpers, HTTP routes, MCP tools).
16
19
  */
17
- const RESERVED_KEYS = new Set(["API_KEY", "SECRETS_ENCRYPTION_KEY"]);
20
+ const RESERVED_KEYS = new Set(["API_KEY", "SECRETS_ENCRYPTION_KEY", "CORS_ALLOW_ANY_ORIGIN"]);
18
21
 
19
22
  export function isReservedConfigKey(key: string): boolean {
20
23
  return RESERVED_KEYS.has(key.toUpperCase());
@@ -223,7 +226,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
223
226
  },
224
227
  // AWS credential mode for the Bedrock path on the pi harness.
225
228
  // sdk — AWS SDK default credential chain (env, ~/.aws/*, SSO, IMDS, …)
226
- // bearer — explicit bearer token via AWS_BEARER_TOKEN_BEDROCK (future/Mantle)
229
+ // bearer — explicit Bedrock API key via AWS_BEARER_TOKEN_BEDROCK (required in this mode)
227
230
  // When absent the worker infers the mode from MODEL_OVERRIDE (sdk semantics).
228
231
  BEDROCK_AUTH_MODE: (value) => {
229
232
  if (value === "sdk" || value === "bearer") return null;
@@ -0,0 +1,70 @@
1
+ import type { FileObject, FileScope, FileStorageProvider } from "../fs/provider";
2
+ import type { TaskAttachment } from "../types";
3
+ import { scrubSecrets } from "../utils/secret-scrubber";
4
+ import { insertTaskAttachment } from "./db";
5
+
6
+ /** Per-file cap for every path that stores a blob as a task attachment. */
7
+ export const MAX_TASK_ATTACHMENT_BYTES = 50 * 1024 * 1024;
8
+
9
+ export type RecordTaskAttachmentUploadInput = {
10
+ provider: FileStorageProvider;
11
+ scope: FileScope;
12
+ /** What `provider.upload(scope, body, …)` returned. */
13
+ uploaded: FileObject;
14
+ /** Size and sha256 of the body that was uploaded. */
15
+ sizeBytes: number;
16
+ sha256: string;
17
+ contentType: string;
18
+ agentId: string | null;
19
+ intent?: string;
20
+ description?: string;
21
+ isPrimary?: boolean;
22
+ createdBy?: string;
23
+ };
24
+
25
+ /**
26
+ * Record a blob the active provider just stored as a `task_attachments` row.
27
+ * If the row can't be written the blob is deleted, so a failed insert never
28
+ * leaves an orphan in storage, and the insert error is rethrown.
29
+ */
30
+ export async function recordTaskAttachmentUpload(
31
+ input: RecordTaskAttachmentUploadInput,
32
+ ): Promise<TaskAttachment> {
33
+ const { provider, scope, uploaded, contentType } = input;
34
+ try {
35
+ return await insertTaskAttachment({
36
+ taskId: scope.taskId,
37
+ agentId: input.agentId,
38
+ name: scope.name,
39
+ kind: provider.id === "agent-fs" ? "agent-fs" : "shared-fs",
40
+ path: uploaded.key,
41
+ providerId: provider.id,
42
+ providerKey: uploaded.key,
43
+ capabilities: {
44
+ ...provider.capabilities,
45
+ version: uploaded.version,
46
+ etag: uploaded.etag,
47
+ },
48
+ mimeType: uploaded.contentType ?? contentType,
49
+ sizeBytes: uploaded.sizeBytes ?? input.sizeBytes,
50
+ sha256: uploaded.sha256 ?? input.sha256,
51
+ intent: input.intent,
52
+ description: input.description,
53
+ isPrimary: input.isPrimary ?? false,
54
+ createdBy: input.createdBy,
55
+ });
56
+ } catch (error) {
57
+ try {
58
+ await provider.delete(scope);
59
+ } catch (cleanupError) {
60
+ console.warn(
61
+ scrubSecrets(
62
+ `[fs] upload metadata insert failed and blob cleanup failed: ${
63
+ cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
64
+ }`,
65
+ ),
66
+ );
67
+ }
68
+ throw error;
69
+ }
70
+ }