@desplega.ai/agent-swarm 1.111.0 → 1.113.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 (156) hide show
  1. package/dist/{actions-a4crgvtd.js → actions-q4n7cz6e.js} +6 -6
  2. package/dist/{app-va5mr9yw.js → app-es4nzc71.js} +3 -3
  3. package/dist/{assistant-0jm4vy8r.js → assistant-0x8ey0vs.js} +9 -9
  4. package/dist/{boot-reembed-50zzk97q.js → boot-reembed-69agkhv6.js} +4 -4
  5. package/dist/{boot-reembed-0t815mmm.js → boot-reembed-p0ny05t8.js} +3 -3
  6. package/dist/{boot-scrub-logs-r99nbmxh.js → boot-scrub-logs-yybrkmvr.js} +2 -2
  7. package/dist/{cli-h3vhr1gj.js → cli-30bbaveh.js} +2 -2
  8. package/dist/{cli-ee3fkebs.js → cli-36ymmpmp.js} +2 -2
  9. package/dist/{cli-dza5039b.js → cli-4xyj9jtq.js} +1495 -254
  10. package/dist/{cli-3cm8ar1c.js → cli-bgef578c.js} +1 -1
  11. package/dist/{cli-xqzvygxp.js → cli-bgvskydy.js} +11 -2
  12. package/dist/{cli-t8m00q3b.js → cli-bnzbn8j8.js} +3 -3
  13. package/dist/{cli-kavm2cv5.js → cli-c3frqf65.js} +4 -3
  14. package/dist/{cli-2b0ew5v8.js → cli-c9mtm09b.js} +1 -1
  15. package/dist/{cli-0d5xddf1.js → cli-f3qa069v.js} +2 -2
  16. package/dist/{cli-r3y5chyg.js → cli-gtkqc144.js} +4 -4
  17. package/dist/{cli-0d404x64.js → cli-h3k622d0.js} +1 -1
  18. package/dist/{cli-y7v6e7yr.js → cli-hjhvekf4.js} +4 -4
  19. package/dist/{cli-fdnpymsv.js → cli-hsetkkd3.js} +4 -2
  20. package/dist/{cli-8j1h9627.js → cli-mq580e06.js} +61 -14
  21. package/dist/{cli-4ke1amr9.js → cli-ncfgsqbd.js} +63 -7
  22. package/dist/{cli-jrmjxsnk.js → cli-pwc5e83c.js} +3 -3
  23. package/dist/{cli-4f6vw5vy.js → cli-qwgtacd6.js} +2 -2
  24. package/dist/{cli-gchg43wt.js → cli-r5g6ngtn.js} +1 -1
  25. package/dist/{cli-6tfm5fr1.js → cli-tbw13sx8.js} +1 -1
  26. package/dist/{cli-f1f82qgv.js → cli-trmapn9k.js} +5 -5
  27. package/dist/{cli-aqr96mny.js → cli-y4ycrnjc.js} +1 -1
  28. package/dist/{cli-z626gjqj.js → cli-y6mrptcn.js} +1 -1
  29. package/dist/{cli-1aqc46he.js → cli-z46pnksz.js} +5 -5
  30. package/dist/cli.js +11 -10
  31. package/dist/{commands-t3rdvb55.js → commands-z5dbwta3.js} +2 -2
  32. package/dist/{db-mjqc8hj6.js → db-bbgahh4z.js} +2 -2
  33. package/dist/{handlers-mcmg5qgc.js → handlers-6b44317z.js} +9 -9
  34. package/dist/{hook-0p4q1tym.js → hook-sbp5fmps.js} +1 -1
  35. package/dist/{http-gs6fk9sg.js → http-2xz43gz3.js} +356 -94
  36. package/dist/{index-20mf9wvc.js → index-0gzmqj4k.js} +8 -8
  37. package/dist/{index-w15fk28m.js → index-5ghxn8s6.js} +7 -7
  38. package/dist/{index-744yvc8x.js → index-nhat35em.js} +9 -9
  39. package/dist/{index-6zpajd9e.js → index-vfedgdw6.js} +26 -15
  40. package/dist/{keepalive-3gvnbxaj.js → keepalive-62gfj91d.js} +4 -4
  41. package/dist/{lead-yjbmhj3g.js → lead-ag0akn0v.js} +17 -17
  42. package/dist/{maintenance-jbvy2bfg.js → maintenance-xxrrzk5x.js} +4 -4
  43. package/dist/{onboard-sexatejh.js → onboard-64zyfcbs.js} +2 -2
  44. package/dist/{otel-impl-sgqvcw0f.js → otel-impl-b86qseaa.js} +1 -1
  45. package/dist/{pricing-refresh-rat240yg.js → pricing-refresh-cgtkffpa.js} +4 -4
  46. package/dist/{seed-pricing-7yfaf123.js → seed-pricing-1r1y1xkq.js} +3 -3
  47. package/dist/{setup-vm5vcc6c.js → setup-mkcgkjf7.js} +2 -2
  48. package/dist/{worker-02y2r0r2.js → worker-351sze63.js} +17 -17
  49. package/openapi.json +314 -3
  50. package/package.json +4 -3
  51. package/src/be/db.ts +43 -3
  52. package/src/be/memory/providers/sqlite-store.ts +5 -1
  53. package/src/be/migrations/108_rbac_permission_audit.sql +26 -0
  54. package/src/be/rbac-audit.ts +218 -0
  55. package/src/be/scripts/typecheck.ts +2 -0
  56. package/src/commands/runner.ts +154 -8
  57. package/src/github/handlers.ts +128 -25
  58. package/src/http/all-routes.ts +58 -0
  59. package/src/http/config.ts +55 -0
  60. package/src/http/favorites.ts +5 -0
  61. package/src/http/fs.ts +27 -7
  62. package/src/http/index.ts +26 -0
  63. package/src/http/kv.ts +21 -4
  64. package/src/http/route-def.ts +9 -0
  65. package/src/http/schedules.ts +56 -22
  66. package/src/http/scripts.ts +33 -0
  67. package/src/http/workflows.ts +13 -2
  68. package/src/prompts/base-prompt.ts +7 -6
  69. package/src/prompts/session-templates.ts +38 -11
  70. package/src/rbac/can.ts +44 -0
  71. package/src/rbac/index.ts +13 -0
  72. package/src/rbac/legacy-policy.ts +185 -0
  73. package/src/rbac/permissions.ts +182 -0
  74. package/src/rbac/types.ts +45 -0
  75. package/src/scripts-runtime/sdk-allowlist.ts +3 -0
  76. package/src/scripts-runtime/swarm-sdk.ts +81 -0
  77. package/src/scripts-runtime/types/stdlib.d.ts +18 -2
  78. package/src/scripts-runtime/types/swarm-sdk.d.ts +20 -2
  79. package/src/server.ts +6 -0
  80. package/src/slack/message-text.ts +25 -0
  81. package/src/stdio.ts +43 -0
  82. package/src/tests/base-prompt.test.ts +7 -4
  83. package/src/tests/github-event-filter.test.ts +3 -2
  84. package/src/tests/github-handlers-inline-comments.test.ts +114 -24
  85. package/src/tests/harness-provider-resolution.test.ts +5 -0
  86. package/src/tests/http-api-integration.test.ts +8 -1
  87. package/src/tests/prompt-template-session.test.ts +21 -8
  88. package/src/tests/rbac-audit.test.ts +345 -0
  89. package/src/tests/rbac-charact-http.test.ts +272 -0
  90. package/src/tests/rbac-charact-misc-tools.test.ts +428 -0
  91. package/src/tests/rbac-charact-skills.test.ts +492 -0
  92. package/src/tests/rbac-charact-slack.test.ts +283 -0
  93. package/src/tests/rbac-e2e-helpers.ts +305 -0
  94. package/src/tests/rbac-engine.test.ts +433 -0
  95. package/src/tests/rbac-lifecycle-e2e.test.ts +262 -0
  96. package/src/tests/rbac-wire-e2e.test.ts +574 -0
  97. package/src/tests/runner-repo-autostash.test.ts +168 -1
  98. package/src/tests/schedule-http-triage-tools.test.ts +121 -0
  99. package/src/tests/scheduled-tasks.test.ts +34 -0
  100. package/src/tests/scripts-http.test.ts +56 -7
  101. package/src/tests/slack-delete.test.ts +157 -0
  102. package/src/tests/slack-message-text.test.ts +37 -1
  103. package/src/tests/slack-update.test.ts +167 -0
  104. package/src/tests/swarm-config-reserved-keys.test.ts +17 -0
  105. package/src/tests/tool-annotations.test.ts +4 -0
  106. package/src/tests/update-schedule-mcp-tool.test.ts +57 -0
  107. package/src/tests/workflow-http-v2.test.ts +80 -0
  108. package/src/tools/cancel-task.ts +13 -3
  109. package/src/tools/context-diff.ts +12 -1
  110. package/src/tools/context-history.ts +12 -1
  111. package/src/tools/credential-bindings/tool.ts +12 -1
  112. package/src/tools/delete-channel.ts +12 -1
  113. package/src/tools/get-task-details.ts +1 -1
  114. package/src/tools/inject-learning.ts +12 -1
  115. package/src/tools/kv/kv-delete.ts +3 -18
  116. package/src/tools/kv/kv-incr.ts +3 -18
  117. package/src/tools/kv/kv-set.ts +3 -20
  118. package/src/tools/kv/kv-write-auth.ts +40 -0
  119. package/src/tools/manage-user.ts +12 -1
  120. package/src/tools/mcp-servers/mcp-server-create.ts +12 -1
  121. package/src/tools/mcp-servers/mcp-server-delete.ts +12 -1
  122. package/src/tools/mcp-servers/mcp-server-install.ts +12 -1
  123. package/src/tools/mcp-servers/mcp-server-uninstall.ts +12 -1
  124. package/src/tools/mcp-servers/mcp-server-update.ts +12 -1
  125. package/src/tools/memory-delete.ts +12 -4
  126. package/src/tools/register-kapso-number.ts +23 -2
  127. package/src/tools/schedules/create-schedule.ts +4 -3
  128. package/src/tools/schedules/index.ts +1 -0
  129. package/src/tools/schedules/list-schedules.ts +36 -2
  130. package/src/tools/schedules/patch-schedule.ts +405 -0
  131. package/src/tools/schedules/update-schedule.ts +2 -2
  132. package/src/tools/script-connections/tool.ts +12 -1
  133. package/src/tools/skills/skill-create.ts +12 -1
  134. package/src/tools/skills/skill-delete.ts +12 -1
  135. package/src/tools/skills/skill-install-remote.ts +12 -1
  136. package/src/tools/skills/skill-install.ts +12 -1
  137. package/src/tools/skills/skill-uninstall.ts +12 -1
  138. package/src/tools/skills/skill-update.ts +25 -2
  139. package/src/tools/slack-delete.ts +109 -0
  140. package/src/tools/slack-post.ts +8 -1
  141. package/src/tools/slack-read.ts +8 -1
  142. package/src/tools/slack-reply.ts +11 -3
  143. package/src/tools/slack-start-thread.ts +10 -1
  144. package/src/tools/slack-update.ts +134 -0
  145. package/src/tools/slack-upload-file.ts +8 -1
  146. package/src/tools/swarm-config/delete-config.ts +28 -1
  147. package/src/tools/swarm-config/get-config.ts +32 -5
  148. package/src/tools/swarm-config/list-config.ts +31 -5
  149. package/src/tools/swarm-config/set-config.ts +38 -1
  150. package/src/tools/task-action.ts +1 -1
  151. package/src/tools/task-tool-ctx.ts +19 -3
  152. package/src/tools/tool-config.ts +5 -2
  153. package/src/tools/update-profile.ts +8 -1
  154. package/src/tools/workflows/list-workflows.ts +14 -2
  155. package/templates/skills/swarm-scripts/SKILL.md +4 -9
  156. package/templates/skills/swarm-scripts/content.md +20 -9
@@ -8,11 +8,13 @@ import {
8
8
  agentWithCapacity,
9
9
  applyRating,
10
10
  assertSelectOnlyQuery,
11
+ can,
11
12
  canClaim,
12
13
  canReadMemory,
13
14
  cancelTaskHandler,
14
15
  cancelTaskInputSchema,
15
16
  cancelTaskOutputSchema,
17
+ clearAuditSink,
16
18
  clearJiraMetadata,
17
19
  createEvent,
18
20
  createEventsBatch,
@@ -93,6 +95,7 @@ import {
93
95
  sendKapsoReaction,
94
96
  sendTaskHandler,
95
97
  sendTaskOutputSchema,
98
+ setAuditSink,
96
99
  setCorsHeaders,
97
100
  shouldPersistAutomaticTaskMemory,
98
101
  snapshotMetric,
@@ -111,7 +114,7 @@ import {
111
114
  withRemoteContext,
112
115
  withSpanContext,
113
116
  writeSkillsToFilesystem
114
- } from "./cli-dza5039b.js";
117
+ } from "./cli-4xyj9jtq.js";
115
118
  import {
116
119
  BROWSER_SDK_JS,
117
120
  SWARM_UI_JS
@@ -144,11 +147,11 @@ import {
144
147
  getOAuthApp,
145
148
  getOAuthTokens,
146
149
  releaseOAuthRefreshLock
147
- } from "./cli-3cm8ar1c.js";
148
- import"./cli-ee3fkebs.js";
150
+ } from "./cli-bgef578c.js";
151
+ import"./cli-36ymmpmp.js";
149
152
  import {
150
153
  normalizeModelKey
151
- } from "./cli-6tfm5fr1.js";
154
+ } from "./cli-tbw13sx8.js";
152
155
  import {
153
156
  SCRIPT_STDLIB_TYPES,
154
157
  ensureAgentFsCredentialsForAgent,
@@ -156,16 +159,16 @@ import {
156
159
  extractScriptSignature,
157
160
  scriptSdkTypesWithGeneratedApis,
158
161
  typecheckScript
159
- } from "./cli-fdnpymsv.js";
162
+ } from "./cli-hsetkkd3.js";
160
163
  import {
161
164
  checkHeartbeatChecklist,
162
165
  runHeartbeatSweep,
163
166
  stopHeartbeat
164
- } from "./cli-t8m00q3b.js";
167
+ } from "./cli-bnzbn8j8.js";
165
168
  import {
166
169
  createResumeFollowUp,
167
170
  createWorkerTaskFollowUp
168
- } from "./cli-gchg43wt.js";
171
+ } from "./cli-r5g6ngtn.js";
169
172
  import"./cli-fad8m16k.js";
170
173
  import {
171
174
  calculateNextRun,
@@ -173,7 +176,7 @@ import {
173
176
  ensure,
174
177
  initialize,
175
178
  require_dist
176
- } from "./cli-r3y5chyg.js";
179
+ } from "./cli-gtkqc144.js";
177
180
  import {
178
181
  RawLlmConfigSchema,
179
182
  TriggerSchemaError,
@@ -193,7 +196,7 @@ import {
193
196
  validateDefinition,
194
197
  validateJsonSchema,
195
198
  verifyHmacSignature
196
- } from "./cli-1aqc46he.js";
199
+ } from "./cli-z46pnksz.js";
197
200
  import {
198
201
  getApiKey
199
202
  } from "./cli-f14fvzag.js";
@@ -214,10 +217,10 @@ import {
214
217
  rotateScriptApiSecret,
215
218
  updateScriptApi,
216
219
  upsertScriptByName
217
- } from "./cli-4f6vw5vy.js";
220
+ } from "./cli-qwgtacd6.js";
218
221
  import {
219
222
  searchScripts
220
- } from "./cli-h3vhr1gj.js";
223
+ } from "./cli-30bbaveh.js";
221
224
  import {
222
225
  CANDIDATE_SET_MULTIPLIER,
223
226
  getEmbeddingProvider,
@@ -225,19 +228,19 @@ import {
225
228
  init_constants,
226
229
  init_reranker,
227
230
  rerank
228
- } from "./cli-0d5xddf1.js";
231
+ } from "./cli-f3qa069v.js";
229
232
  import"./cli-q25ejbyn.js";
230
- import"./cli-xqzvygxp.js";
233
+ import"./cli-bgvskydy.js";
231
234
  import {
232
235
  getSlackApp,
233
236
  startSlackApp,
234
237
  stopSlackApp
235
- } from "./cli-f1f82qgv.js";
238
+ } from "./cli-trmapn9k.js";
236
239
  import"./cli-rkndnn90.js";
237
240
  import"./cli-b0p7rfnd.js";
238
241
  import {
239
242
  recordUnmappedIdentity
240
- } from "./cli-2b0ew5v8.js";
243
+ } from "./cli-c9mtm09b.js";
241
244
  import {
242
245
  findOrCreateUserByEmail,
243
246
  findUserByExternalId,
@@ -251,7 +254,7 @@ import {
251
254
  resolveUserByToken,
252
255
  revokeToken,
253
256
  unlinkIdentity
254
- } from "./cli-0d404x64.js";
257
+ } from "./cli-h3k622d0.js";
255
258
  import"./cli-6xd0bvf8.js";
256
259
  import"./cli-wspgs9bt.js";
257
260
  import {
@@ -259,7 +262,7 @@ import {
259
262
  createTaskWithSiblingAwareness,
260
263
  gitlabContextKey,
261
264
  pageContextKey
262
- } from "./cli-aqr96mny.js";
265
+ } from "./cli-y4ycrnjc.js";
263
266
  import {
264
267
  getAppUrl,
265
268
  getConfiguredAppUrls,
@@ -571,7 +574,7 @@ import {
571
574
  upsertSwarmConfig,
572
575
  validateConfigValue,
573
576
  withFavoriteFlags
574
- } from "./cli-4ke1amr9.js";
577
+ } from "./cli-ncfgsqbd.js";
575
578
  import"./cli-z2zcxes1.js";
576
579
  import {
577
580
  init_zod
@@ -586,7 +589,7 @@ import {
586
589
  import {
587
590
  init_package,
588
591
  package_default
589
- } from "./cli-kavm2cv5.js";
592
+ } from "./cli-c3frqf65.js";
590
593
  import {
591
594
  init_secret_scrubber,
592
595
  registerVolatileSecret,
@@ -636,6 +639,146 @@ import {
636
639
  } from "node:http";
637
640
  init_db();
638
641
 
642
+ // src/be/rbac-audit.ts
643
+ init_db();
644
+ var FLUSH_INTERVAL_MS = 2000;
645
+ var FLUSH_MAX_ROWS = 200;
646
+ var AUDIT_GC_INTERVAL_MS = 24 * 60 * 60 * 1000;
647
+ var DEFAULT_RETENTION_DAYS = 30;
648
+ var buffer = [];
649
+ var flushTimer = null;
650
+ var auditGcTimer = null;
651
+ var thresholdFlushScheduled = false;
652
+ function isAuditDisabled() {
653
+ return process.env.RBAC_AUDIT_DISABLED === "true";
654
+ }
655
+ function principalIdOf(principal) {
656
+ switch (principal.kind) {
657
+ case "agent":
658
+ return principal.agentId;
659
+ case "user":
660
+ return principal.userId;
661
+ case "operator":
662
+ return null;
663
+ }
664
+ }
665
+ function resourceIdOf(resource) {
666
+ if (!resource)
667
+ return null;
668
+ switch (resource.kind) {
669
+ case "task":
670
+ return resource.taskId;
671
+ case "agent":
672
+ return resource.agentId;
673
+ case "kv-namespace":
674
+ return resource.namespace;
675
+ case "owned":
676
+ return resource.ownerAgentId ?? null;
677
+ case "none":
678
+ return null;
679
+ }
680
+ }
681
+ function toRow(check, decision) {
682
+ return {
683
+ principalType: check.principal.kind,
684
+ principalId: principalIdOf(check.principal),
685
+ originatorUserId: null,
686
+ verb: check.verb,
687
+ resourceType: check.resource?.kind ?? null,
688
+ resourceId: resourceIdOf(check.resource),
689
+ decision: decision.allow ? "allow" : "deny",
690
+ reason: decision.allow ? null : decision.reason,
691
+ source: check.source
692
+ };
693
+ }
694
+ function enqueueAuditRow(check, decision) {
695
+ if (isAuditDisabled())
696
+ return;
697
+ try {
698
+ buffer.push(toRow(check, decision));
699
+ if (buffer.length >= FLUSH_MAX_ROWS && !thresholdFlushScheduled) {
700
+ thresholdFlushScheduled = true;
701
+ const t = setTimeout(() => {
702
+ thresholdFlushScheduled = false;
703
+ flushAuditBuffer();
704
+ }, 0);
705
+ if (typeof t.unref === "function")
706
+ t.unref();
707
+ }
708
+ } catch (err) {
709
+ console.warn("[rbac-audit] enqueue failed, dropping row:", err.message);
710
+ }
711
+ }
712
+ function flushAuditBuffer() {
713
+ if (buffer.length === 0)
714
+ return;
715
+ const rows = buffer;
716
+ buffer = [];
717
+ try {
718
+ const db = getDb();
719
+ const stmt = db.prepare(`INSERT INTO permission_audit
720
+ (principalType, principalId, originatorUserId, verb, resourceType, resourceId, decision, reason, source)
721
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
722
+ const insertAll = db.transaction((batch) => {
723
+ for (const r of batch) {
724
+ stmt.run(r.principalType, r.principalId, r.originatorUserId, r.verb, r.resourceType, r.resourceId, r.decision, r.reason, r.source);
725
+ }
726
+ });
727
+ insertAll(rows);
728
+ } catch (err) {
729
+ console.warn(`[rbac-audit] flush failed, dropping ${rows.length} row(s):`, err.message);
730
+ }
731
+ }
732
+ function flushIntervalMs() {
733
+ const v = Number(process.env.RBAC_AUDIT_FLUSH_MS);
734
+ return Number.isFinite(v) && v > 0 ? v : FLUSH_INTERVAL_MS;
735
+ }
736
+ function startAuditWriter(intervalMs = flushIntervalMs()) {
737
+ if (flushTimer)
738
+ return;
739
+ flushTimer = setInterval(() => flushAuditBuffer(), intervalMs);
740
+ if (typeof flushTimer?.unref === "function")
741
+ flushTimer.unref();
742
+ }
743
+ function stopAuditWriter() {
744
+ if (flushTimer) {
745
+ clearInterval(flushTimer);
746
+ flushTimer = null;
747
+ }
748
+ }
749
+ function purgeExpiredAuditRows() {
750
+ try {
751
+ const days = Number(process.env.RBAC_AUDIT_RETENTION_DAYS) || DEFAULT_RETENTION_DAYS;
752
+ const result = getDb().prepare("DELETE FROM permission_audit WHERE ts < datetime('now', ?)").run(`-${days} days`);
753
+ return result.changes;
754
+ } catch (err) {
755
+ console.warn("[rbac-audit] retention purge failed:", err.message);
756
+ return 0;
757
+ }
758
+ }
759
+ function startAuditGc(intervalMs = AUDIT_GC_INTERVAL_MS) {
760
+ if (auditGcTimer)
761
+ return;
762
+ const purged = purgeExpiredAuditRows();
763
+ if (purged > 0) {
764
+ console.log(`[rbac-audit] Initial retention purge removed ${purged} audit row(s)`);
765
+ }
766
+ auditGcTimer = setInterval(() => {
767
+ const n = purgeExpiredAuditRows();
768
+ if (n > 0) {
769
+ console.log(`[rbac-audit] Retention purge removed ${n} audit row(s)`);
770
+ }
771
+ }, intervalMs);
772
+ if (typeof auditGcTimer?.unref === "function")
773
+ auditGcTimer.unref();
774
+ }
775
+ function stopAuditGc() {
776
+ if (auditGcTimer) {
777
+ clearInterval(auditGcTimer);
778
+ auditGcTimer = null;
779
+ }
780
+ }
781
+
639
782
  // src/gitlab/auth.ts
640
783
  var initialized = false;
641
784
  var webhookSecret = null;
@@ -2845,12 +2988,35 @@ async function handleCodexOAuthKeepWarm(req, res, pathSegments) {
2845
2988
  init_zod();
2846
2989
  init_db();
2847
2990
  init_swarm_config_guard();
2991
+ init_request_auth_context();
2848
2992
  init_secret_scrubber();
2849
2993
  var MAX_ENV_PRESENCE_KEYS = 200;
2850
2994
  var API_ONLY_CONFIG_KEYS = new Set(["API_AGENT_FS_API_KEY"]);
2851
2995
  function stripApiOnlyKeys(configs) {
2852
2996
  return configs.filter((config) => !API_ONLY_CONFIG_KEYS.has(config.key));
2853
2997
  }
2998
+ function singleHeader(req, name) {
2999
+ const raw = req.headers[name];
3000
+ return Array.isArray(raw) ? raw[0] : raw;
3001
+ }
3002
+ function ensureConfigAdmin(req, res, verb) {
3003
+ const auth = getRequestAuth(req);
3004
+ if (auth?.kind === "operator" || auth?.kind === "user")
3005
+ return true;
3006
+ const agentId = singleHeader(req, "x-agent-id");
3007
+ const agent = agentId ? getAgentById(agentId) : undefined;
3008
+ const decision = can({
3009
+ principal: { kind: "agent", agentId: agentId ?? "", isLead: agent?.isLead ?? false },
3010
+ verb,
3011
+ resource: { kind: "none" },
3012
+ source: "http"
3013
+ });
3014
+ if (!decision.allow) {
3015
+ jsonError(res, verb === "config.write.any" ? "Writing swarm config requires the lead agent" : "Deleting swarm config requires the lead agent", 403);
3016
+ return false;
3017
+ }
3018
+ return true;
3019
+ }
2854
3020
  var getResolvedConfigRoute = route({
2855
3021
  method: "get",
2856
3022
  path: "/api/config/resolved",
@@ -2928,6 +3094,7 @@ var upsertConfig = route({
2928
3094
  pattern: ["api", "config"],
2929
3095
  summary: "Create or update a config entry (reserved env-only keys are rejected). Global-scope writes auto-trigger an integrations reload (debounced ~250ms) so Slack/GitHub/Linear/Jira/AgentMail pick up new credentials without an explicit /api/config/reload call.",
2930
3096
  tags: ["Config"],
3097
+ rbac: { permission: "config.write.any" },
2931
3098
  body: exports_external.object({
2932
3099
  scope: exports_external.enum(["global", "agent", "repo"]),
2933
3100
  scopeId: exports_external.string().nullish(),
@@ -2948,6 +3115,7 @@ var deleteConfig = route({
2948
3115
  pattern: ["api", "config", null],
2949
3116
  summary: "Delete a config entry by ID (including legacy reserved rows for cleanup). Global-scope deletes auto-trigger an integrations reload.",
2950
3117
  tags: ["Config"],
3118
+ rbac: { permission: "config.delete.any" },
2951
3119
  params: exports_external.object({ id: exports_external.string() }),
2952
3120
  responses: {
2953
3121
  200: { description: "Config deleted" },
@@ -3041,6 +3209,8 @@ async function handleConfig(req, res, pathSegments, queryParams) {
3041
3209
  const parsed = await upsertConfig.parse(req, res, pathSegments, queryParams);
3042
3210
  if (!parsed)
3043
3211
  return true;
3212
+ if (!ensureConfigAdmin(req, res, "config.write.any"))
3213
+ return true;
3044
3214
  const { scope, scopeId, key, value, isSecret, envPath, description } = parsed.body;
3045
3215
  if (scope === "global" && scopeId) {
3046
3216
  jsonError(res, "Global scope must not have scopeId", 400);
@@ -3084,6 +3254,8 @@ async function handleConfig(req, res, pathSegments, queryParams) {
3084
3254
  const parsed = await deleteConfig.parse(req, res, pathSegments, queryParams);
3085
3255
  if (!parsed)
3086
3256
  return true;
3257
+ if (!ensureConfigAdmin(req, res, "config.delete.any"))
3258
+ return true;
3087
3259
  const existing = getSwarmConfigLookupById(parsed.params.id);
3088
3260
  if (!existing) {
3089
3261
  jsonError(res, "Config not found", 404);
@@ -3420,6 +3592,7 @@ var putFavorite = route({
3420
3592
  pattern: ["api", "favorites"],
3421
3593
  summary: "Set favorite state for an item",
3422
3594
  tags: ["Favorites"],
3595
+ rbac: { ungated: "self-scoped to the authenticated user; no cross-principal access" },
3423
3596
  body: exports_external.object({
3424
3597
  itemType: FavoriteItemTypeSchema,
3425
3598
  itemId: exports_external.string().min(1),
@@ -3985,7 +4158,8 @@ var uploadTaskFileRoute = route({
3985
4158
  404: { description: "Task not found" },
3986
4159
  413: { description: "Upload exceeds 50 MiB" }
3987
4160
  },
3988
- auth: { apiKey: true, agentId: true }
4161
+ auth: { apiKey: true, agentId: true },
4162
+ rbac: { permission: "task.fs.mutate" }
3989
4163
  });
3990
4164
  var getTaskFileRoute = route({
3991
4165
  method: "get",
@@ -4038,7 +4212,8 @@ var deleteTaskFileRoute = route({
4038
4212
  403: { description: "Caller cannot mutate this task" },
4039
4213
  404: { description: "Task or attachment not found" }
4040
4214
  },
4041
- auth: { apiKey: true, agentId: true }
4215
+ auth: { apiKey: true, agentId: true },
4216
+ rbac: { permission: "task.fs.mutate" }
4042
4217
  });
4043
4218
  async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
4044
4219
  if (ensureAgentCredentialsRoute.match(req.method, pathSegments)) {
@@ -4138,7 +4313,7 @@ async function sendUpload(req, res, taskId, query, agentId) {
4138
4313
  return true;
4139
4314
  }
4140
4315
  const provider = getFileStorageProvider();
4141
- const contentType = singleHeader(req, "content-type") ?? "application/octet-stream";
4316
+ const contentType = singleHeader2(req, "content-type") ?? "application/octet-stream";
4142
4317
  const scope = { taskId, name: query.name };
4143
4318
  let uploaded;
4144
4319
  try {
@@ -4273,31 +4448,39 @@ function findAttachment(taskId, attachmentId, res) {
4273
4448
  return attachment;
4274
4449
  }
4275
4450
  function canMutateTask(task, myAgentId, req) {
4451
+ const resource = {
4452
+ kind: "task",
4453
+ taskId: task.id,
4454
+ agentId: task.agentId,
4455
+ creatorAgentId: task.creatorAgentId
4456
+ };
4276
4457
  const auth = getRequestAuth(req);
4277
- if (auth?.kind === "operator")
4278
- return true;
4279
- if (auth?.kind === "user")
4280
- return true;
4281
- if (!myAgentId)
4282
- return false;
4283
- const agent = getAgentById(myAgentId);
4284
- if (agent?.isLead)
4285
- return true;
4286
- return task.agentId === myAgentId || task.creatorAgentId === myAgentId;
4458
+ let principal;
4459
+ if (auth?.kind === "operator") {
4460
+ principal = { kind: "operator" };
4461
+ } else if (auth?.kind === "user") {
4462
+ principal = { kind: "user", userId: auth.userId };
4463
+ } else {
4464
+ if (!myAgentId)
4465
+ return false;
4466
+ const agent = getAgentById(myAgentId);
4467
+ principal = { kind: "agent", agentId: myAgentId, isLead: agent?.isLead ?? false };
4468
+ }
4469
+ return can({ principal, verb: "task.fs.mutate", resource, source: "http" }).allow;
4287
4470
  }
4288
4471
  async function readRawBody(req, maxBytes) {
4289
4472
  const chunks = [];
4290
4473
  let total = 0;
4291
4474
  for await (const chunk of req) {
4292
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4293
- total += buffer.byteLength;
4475
+ const buffer2 = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4476
+ total += buffer2.byteLength;
4294
4477
  if (total > maxBytes)
4295
4478
  return BODY_TOO_LARGE;
4296
- chunks.push(buffer);
4479
+ chunks.push(buffer2);
4297
4480
  }
4298
4481
  return Buffer.concat(chunks);
4299
4482
  }
4300
- function singleHeader(req, name) {
4483
+ function singleHeader2(req, name) {
4301
4484
  const raw = req.headers[name];
4302
4485
  return Array.isArray(raw) ? raw[0] : raw;
4303
4486
  }
@@ -4589,7 +4772,8 @@ var putKvHeader = route({
4589
4772
  tags: ["KV"],
4590
4773
  params: exports_external.object({ key: KvKeySchema }),
4591
4774
  body: kvSetBodySchema,
4592
- responses: RESPONSES_PUT
4775
+ responses: RESPONSES_PUT,
4776
+ rbac: { permission: "kv.write.any" }
4593
4777
  });
4594
4778
  var deleteKvHeader = route({
4595
4779
  method: "delete",
@@ -4603,7 +4787,8 @@ var deleteKvHeader = route({
4603
4787
  404: { description: "KV entry not found" },
4604
4788
  403: { description: "Caller may not write this namespace" },
4605
4789
  400: { description: "Validation error or unresolvable namespace" }
4606
- }
4790
+ },
4791
+ rbac: { permission: "kv.write.any" }
4607
4792
  });
4608
4793
  var incrKvHeader = route({
4609
4794
  method: "post",
@@ -4613,7 +4798,8 @@ var incrKvHeader = route({
4613
4798
  tags: ["KV"],
4614
4799
  params: exports_external.object({ key: KvKeySchema }),
4615
4800
  body: kvIncrBodySchema,
4616
- responses: RESPONSES_PUT
4801
+ responses: RESPONSES_PUT,
4802
+ rbac: { permission: "kv.write.any" }
4617
4803
  });
4618
4804
  var listKvHeader = route({
4619
4805
  method: "get",
@@ -4641,7 +4827,8 @@ var putKvExplicit = route({
4641
4827
  tags: ["KV"],
4642
4828
  params: exports_external.object({ namespace: KvNamespaceSchema, key: KvKeySchema }),
4643
4829
  body: kvSetBodySchema,
4644
- responses: RESPONSES_PUT
4830
+ responses: RESPONSES_PUT,
4831
+ rbac: { permission: "kv.write.any" }
4645
4832
  });
4646
4833
  var deleteKvExplicit = route({
4647
4834
  method: "delete",
@@ -4654,7 +4841,8 @@ var deleteKvExplicit = route({
4654
4841
  204: { description: "KV entry deleted" },
4655
4842
  404: { description: "KV entry not found" },
4656
4843
  403: { description: "Caller may not write this namespace" }
4657
- }
4844
+ },
4845
+ rbac: { permission: "kv.write.any" }
4658
4846
  });
4659
4847
  var incrKvExplicit = route({
4660
4848
  method: "post",
@@ -4664,7 +4852,8 @@ var incrKvExplicit = route({
4664
4852
  tags: ["KV"],
4665
4853
  params: exports_external.object({ namespace: KvNamespaceSchema, key: KvKeySchema }),
4666
4854
  body: kvIncrBodySchema,
4667
- responses: RESPONSES_PUT
4855
+ responses: RESPONSES_PUT,
4856
+ rbac: { permission: "kv.write.any" }
4668
4857
  });
4669
4858
  var listKvExplicit = route({
4670
4859
  method: "get",
@@ -4690,14 +4879,14 @@ function decodeKvSegment(res, raw, label) {
4690
4879
  }
4691
4880
  return decoded;
4692
4881
  }
4693
- function singleHeader2(req, name) {
4882
+ function singleHeader3(req, name) {
4694
4883
  const raw = req.headers[name];
4695
4884
  if (raw === undefined)
4696
4885
  return;
4697
4886
  return Array.isArray(raw) ? raw[0] : raw;
4698
4887
  }
4699
4888
  function resolveNamespaceFromHeaders(req) {
4700
- const pageId = singleHeader2(req, "x-page-id");
4889
+ const pageId = singleHeader3(req, "x-page-id");
4701
4890
  if (pageId) {
4702
4891
  try {
4703
4892
  return pageContextKey({ pageId });
@@ -4705,7 +4894,7 @@ function resolveNamespaceFromHeaders(req) {
4705
4894
  return null;
4706
4895
  }
4707
4896
  }
4708
- const sourceTaskId = singleHeader2(req, "x-source-task-id");
4897
+ const sourceTaskId = singleHeader3(req, "x-source-task-id");
4709
4898
  if (sourceTaskId) {
4710
4899
  const task = getTaskById(sourceTaskId);
4711
4900
  if (task?.contextKey)
@@ -4716,7 +4905,7 @@ function resolveNamespaceFromHeaders(req) {
4716
4905
  } catch {}
4717
4906
  }
4718
4907
  }
4719
- const agentId = singleHeader2(req, "x-agent-id");
4908
+ const agentId = singleHeader3(req, "x-agent-id");
4720
4909
  if (agentId) {
4721
4910
  try {
4722
4911
  return agentContextKey({ agentId });
@@ -4727,8 +4916,8 @@ function resolveNamespaceFromHeaders(req) {
4727
4916
  return null;
4728
4917
  }
4729
4918
  function buildAuthCtx(req) {
4730
- const callerAgentId = singleHeader2(req, "x-agent-id");
4731
- const pageId = singleHeader2(req, "x-page-id");
4919
+ const callerAgentId = singleHeader3(req, "x-agent-id");
4920
+ const pageId = singleHeader3(req, "x-page-id");
4732
4921
  let isLead = false;
4733
4922
  if (callerAgentId) {
4734
4923
  const agent = getAgentById(callerAgentId);
@@ -4744,12 +4933,16 @@ function authorizeWrite(namespace, ctx) {
4744
4933
  return null;
4745
4934
  }
4746
4935
  if (namespace.startsWith("task:agent:")) {
4747
- const target = namespace.slice("task:agent:".length);
4748
- if (ctx.callerAgentId && target === ctx.callerAgentId)
4749
- return null;
4750
- if (ctx.isLead)
4751
- return null;
4752
- return { status: 403, message: "writes to another agent's namespace require lead" };
4936
+ const allowed = ctx.callerAgentId != null && can({
4937
+ principal: { kind: "agent", agentId: ctx.callerAgentId, isLead: ctx.isLead },
4938
+ verb: "kv.write.any",
4939
+ resource: { kind: "kv-namespace", namespace },
4940
+ source: "http"
4941
+ }).allow;
4942
+ if (!allowed) {
4943
+ return { status: 403, message: "writes to another agent's namespace require lead" };
4944
+ }
4945
+ return null;
4753
4946
  }
4754
4947
  return null;
4755
4948
  }
@@ -4789,7 +4982,7 @@ function encodeValueOrError(res, value, valueType) {
4789
4982
  }
4790
4983
  }
4791
4984
  async function handleKv(req, res, pathSegments, queryParams) {
4792
- const hasPageHeader = singleHeader2(req, "x-page-id") !== undefined;
4985
+ const hasPageHeader = singleHeader3(req, "x-page-id") !== undefined;
4793
4986
  if (hasPageHeader && pathSegments[0] === "api" && pathSegments[1] === "kv" && pathSegments[2] === "_") {
4794
4987
  pathSegments = [pathSegments[0], pathSegments[1], ...pathSegments.slice(4)];
4795
4988
  }
@@ -6286,6 +6479,7 @@ var SDK_TOOL_NAME_MAP = {
6286
6479
  schedule_list: "list-schedules",
6287
6480
  schedule_create: "create-schedule",
6288
6481
  schedule_update: "update-schedule",
6482
+ schedule_patch: "patch-schedule",
6289
6483
  schedule_delete: "delete-schedule",
6290
6484
  schedule_runNow: "run-schedule-now",
6291
6485
  script_search: "script-search",
@@ -6314,6 +6508,8 @@ var SDK_TOOL_NAME_MAP = {
6314
6508
  slack_startThread: "slack-start-thread",
6315
6509
  slack_uploadFile: "slack-upload-file",
6316
6510
  slack_downloadFile: "slack-download-file",
6511
+ slack_delete: "slack-delete",
6512
+ slack_update: "slack-update",
6317
6513
  message_read: "read-messages",
6318
6514
  message_post: "post-message",
6319
6515
  profile_update: "update-profile",
@@ -11865,6 +12061,26 @@ init_zod();
11865
12061
  var import_cron_parser = __toESM(require_dist(), 1);
11866
12062
  init_db();
11867
12063
  init_types();
12064
+ var scheduleUpdateBodySchema = exports_external.object({
12065
+ name: exports_external.string().optional(),
12066
+ description: exports_external.string().optional(),
12067
+ cronExpression: exports_external.string().nullable().optional(),
12068
+ intervalMs: exports_external.number().int().positive().nullable().optional(),
12069
+ taskTemplate: exports_external.string().optional(),
12070
+ taskType: exports_external.string().optional(),
12071
+ tags: exports_external.array(exports_external.string()).optional(),
12072
+ priority: exports_external.number().int().optional(),
12073
+ targetAgentId: exports_external.string().uuid().nullable().optional(),
12074
+ enabled: exports_external.boolean().optional(),
12075
+ timezone: exports_external.string().optional(),
12076
+ model: exports_external.string().nullable().optional(),
12077
+ modelTier: ModelTierSchema.nullable().optional(),
12078
+ nextRunAt: exports_external.string().nullable().optional(),
12079
+ targetType: ScheduledTaskTargetTypeSchema.optional(),
12080
+ workflowId: exports_external.string().uuid().nullable().optional(),
12081
+ scriptName: exports_external.string().nullable().optional(),
12082
+ scriptArgs: exports_external.record(exports_external.string(), exports_external.unknown()).nullable().optional()
12083
+ });
11868
12084
  var createSchedule = route({
11869
12085
  method: "post",
11870
12086
  path: "/api/schedules",
@@ -11927,6 +12143,8 @@ var listSchedules = route({
11927
12143
  workflowId: exports_external.string().uuid().optional(),
11928
12144
  scriptName: exports_external.string().optional(),
11929
12145
  hideCompleted: exports_external.enum(["true", "false"]).optional().transform((v) => v === undefined ? undefined : v === "true"),
12146
+ consecutiveErrorsMin: exports_external.coerce.number().int().min(0).optional(),
12147
+ lastRunStatus: exports_external.enum(["failed", "succeeded"]).optional(),
11930
12148
  fields: exports_external.enum(["full", "slim"]).optional()
11931
12149
  }),
11932
12150
  responses: {
@@ -11952,26 +12170,7 @@ var updateSchedule = route({
11952
12170
  summary: "Update a schedule",
11953
12171
  tags: ["Schedules"],
11954
12172
  params: exports_external.object({ id: exports_external.string() }),
11955
- body: exports_external.object({
11956
- name: exports_external.string().optional(),
11957
- description: exports_external.string().optional(),
11958
- cronExpression: exports_external.string().nullable().optional(),
11959
- intervalMs: exports_external.number().int().positive().nullable().optional(),
11960
- taskTemplate: exports_external.string().optional(),
11961
- taskType: exports_external.string().optional(),
11962
- tags: exports_external.array(exports_external.string()).optional(),
11963
- priority: exports_external.number().int().optional(),
11964
- targetAgentId: exports_external.string().uuid().optional(),
11965
- enabled: exports_external.boolean().optional(),
11966
- timezone: exports_external.string().optional(),
11967
- model: exports_external.string().optional(),
11968
- modelTier: ModelTierSchema.nullable().optional(),
11969
- nextRunAt: exports_external.string().nullable().optional(),
11970
- targetType: ScheduledTaskTargetTypeSchema.optional(),
11971
- workflowId: exports_external.string().uuid().nullable().optional(),
11972
- scriptName: exports_external.string().nullable().optional(),
11973
- scriptArgs: exports_external.record(exports_external.string(), exports_external.unknown()).nullable().optional()
11974
- }),
12173
+ body: scheduleUpdateBodySchema,
11975
12174
  responses: {
11976
12175
  200: { description: "Schedule updated" },
11977
12176
  400: { description: "Validation error" },
@@ -11979,6 +12178,25 @@ var updateSchedule = route({
11979
12178
  409: { description: "Duplicate name" }
11980
12179
  }
11981
12180
  });
12181
+ var patchSchedule = route({
12182
+ method: "patch",
12183
+ path: "/api/schedules/{id}",
12184
+ pattern: ["api", "schedules", null],
12185
+ summary: "Patch a schedule",
12186
+ description: "Partially updates a schedule by shallow-merging provided fields over the existing row.",
12187
+ tags: ["Schedules"],
12188
+ params: exports_external.object({ id: exports_external.string() }),
12189
+ body: scheduleUpdateBodySchema,
12190
+ responses: {
12191
+ 200: { description: "Schedule patched" },
12192
+ 400: { description: "Validation error" },
12193
+ 404: { description: "Schedule not found" },
12194
+ 409: { description: "Duplicate name" }
12195
+ },
12196
+ rbac: {
12197
+ ungated: "matches existing schedule update/delete posture: bearer-authenticated agents may manage schedules"
12198
+ }
12199
+ });
11982
12200
  var deleteSchedule = route({
11983
12201
  method: "delete",
11984
12202
  path: "/api/schedules/{id}",
@@ -12003,7 +12221,9 @@ async function handleSchedules(req, res, pathSegments, queryParams, myAgentId) {
12003
12221
  targetType: parsed.query.targetType,
12004
12222
  workflowId: parsed.query.workflowId,
12005
12223
  scriptName: parsed.query.scriptName,
12006
- hideCompleted: parsed.query.hideCompleted
12224
+ hideCompleted: parsed.query.hideCompleted,
12225
+ consecutiveErrorsMin: parsed.query.consecutiveErrorsMin,
12226
+ lastRunStatus: parsed.query.lastRunStatus
12007
12227
  };
12008
12228
  const schedules = parsed.query.fields === "full" ? getScheduledTasks(filters) : getScheduledTasks(filters, { slim: true });
12009
12229
  const userId = resolveHttpAuditUserId(req, myAgentId);
@@ -12178,8 +12398,9 @@ async function handleSchedules(req, res, pathSegments, queryParams, myAgentId) {
12178
12398
  json(res, decorated ?? schedule);
12179
12399
  return true;
12180
12400
  }
12181
- if (updateSchedule.match(req.method, pathSegments)) {
12182
- const parsed = await updateSchedule.parse(req, res, pathSegments, queryParams);
12401
+ if (updateSchedule.match(req.method, pathSegments) || patchSchedule.match(req.method, pathSegments)) {
12402
+ const routeHandle = updateSchedule.match(req.method, pathSegments) ? updateSchedule : patchSchedule;
12403
+ const parsed = await routeHandle.parse(req, res, pathSegments, queryParams);
12183
12404
  if (!parsed)
12184
12405
  return true;
12185
12406
  const body = parsed.body;
@@ -12886,7 +13107,8 @@ var upsertRoute2 = route({
12886
13107
  200: { description: "Script upserted" },
12887
13108
  400: { description: "Validation or typecheck failure" },
12888
13109
  403: { description: "Global write requires lead agent" }
12889
- }
13110
+ },
13111
+ rbac: { permission: "script.global.write" }
12890
13112
  });
12891
13113
  var runRoute = route({
12892
13114
  method: "post",
@@ -12931,7 +13153,8 @@ var deleteRoute = route({
12931
13153
  200: { description: "Delete result" },
12932
13154
  400: { description: "Validation error" },
12933
13155
  403: { description: "Global delete requires lead agent" }
12934
- }
13156
+ },
13157
+ rbac: { permission: "script.global.delete" }
12935
13158
  });
12936
13159
  var typesRoute = route({
12937
13160
  method: "get",
@@ -13149,6 +13372,18 @@ async function handleScripts(req, res, pathSegments, queryParams, agentId) {
13149
13372
  const agent = requireAgent2(res, agentId);
13150
13373
  if (!agent)
13151
13374
  return true;
13375
+ if (parsed.body.scope === "global") {
13376
+ const decision = can({
13377
+ principal: { kind: "agent", agentId: agent.id, isLead: agent.isLead },
13378
+ verb: "script.global.write",
13379
+ resource: { kind: "owned", scope: "global" },
13380
+ source: "http"
13381
+ });
13382
+ if (!decision.allow) {
13383
+ jsonError(res, "Global write requires lead agent", 403);
13384
+ return true;
13385
+ }
13386
+ }
13152
13387
  const typecheck = typecheckScript(parsed.body.source, { agentId: agent.id });
13153
13388
  if (!typecheck.ok) {
13154
13389
  json(res, {
@@ -13404,6 +13639,18 @@ async function handleScripts(req, res, pathSegments, queryParams, agentId) {
13404
13639
  const agent = requireAgent2(res, agentId);
13405
13640
  if (!agent)
13406
13641
  return true;
13642
+ if (parsed.query.scope === "global") {
13643
+ const decision = can({
13644
+ principal: { kind: "agent", agentId: agent.id, isLead: agent.isLead },
13645
+ verb: "script.global.delete",
13646
+ resource: { kind: "owned", scope: "global" },
13647
+ source: "http"
13648
+ });
13649
+ if (!decision.allow) {
13650
+ jsonError(res, "Global delete requires lead agent", 403);
13651
+ return true;
13652
+ }
13653
+ }
13407
13654
  const deleted = deleteScript({
13408
13655
  name: parsed.params.name,
13409
13656
  scope: parsed.query.scope,
@@ -18024,6 +18271,9 @@ var listWorkflowsRoute = route({
18024
18271
  description: "Returns workflows WITHOUT the heavy `definition` (the full DAG) by default — the list view only needs a `nodeCount`, which is included. Pass `fields=full` to restore `definition` + trigger config. Fetch the full workflow via `GET /api/workflows/{id}`.",
18025
18272
  tags: ["Workflows"],
18026
18273
  query: exports_external.object({
18274
+ enabled: exports_external.enum(["true", "false"]).optional().transform((v) => v === undefined ? undefined : v === "true"),
18275
+ consecutiveErrorsMin: exports_external.coerce.number().int().min(0).optional(),
18276
+ lastRunStatus: WorkflowRunStatusSchema.optional(),
18027
18277
  fields: exports_external.enum(["full", "slim"]).optional()
18028
18278
  }),
18029
18279
  responses: {
@@ -18340,10 +18590,15 @@ async function handleWorkflows(req, res, pathSegments, queryParams, myAgentId) {
18340
18590
  if (!parsed)
18341
18591
  return true;
18342
18592
  const userId = resolveHttpAuditUserId(req, myAgentId);
18593
+ const filters = {
18594
+ enabled: parsed.query.enabled,
18595
+ consecutiveErrorsMin: parsed.query.consecutiveErrorsMin,
18596
+ lastRunStatus: parsed.query.lastRunStatus
18597
+ };
18343
18598
  if (parsed.query.fields === "full") {
18344
- json(res, withFavoriteFlags(listWorkflows(), { userId, itemType: "workflow" }));
18599
+ json(res, withFavoriteFlags(listWorkflows(filters), { userId, itemType: "workflow" }));
18345
18600
  } else {
18346
- json(res, withFavoriteFlags(listWorkflows(undefined, { slim: true }), {
18601
+ json(res, withFavoriteFlags(listWorkflows(filters, { slim: true }), {
18347
18602
  userId,
18348
18603
  itemType: "workflow"
18349
18604
  }));
@@ -19025,18 +19280,22 @@ async function shutdown() {
19025
19280
  sessionsProcessed: getServerSessionsProcessed()
19026
19281
  });
19027
19282
  if (hasCapability("scheduling")) {
19028
- const { stopScheduler } = await import("./index-20mf9wvc.js");
19283
+ const { stopScheduler } = await import("./index-0gzmqj4k.js");
19029
19284
  stopScheduler();
19030
19285
  }
19031
19286
  stopHeartbeat();
19032
19287
  stopScriptRunSupervisor();
19033
19288
  await stopSlackApp();
19034
19289
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
19035
- const { stopOAuthKeepalive } = await import("./keepalive-3gvnbxaj.js");
19290
+ const { stopOAuthKeepalive } = await import("./keepalive-62gfj91d.js");
19036
19291
  await stopOAuthKeepalive();
19037
19292
  }
19038
19293
  stopMcpOAuthPendingGc();
19039
19294
  stopMemoryGc();
19295
+ stopAuditGc();
19296
+ stopAuditWriter();
19297
+ flushAuditBuffer();
19298
+ clearAuditSink();
19040
19299
  if (globalState.__apiGcInterval) {
19041
19300
  clearInterval(globalState.__apiGcInterval);
19042
19301
  delete globalState.__apiGcInterval;
@@ -19085,19 +19344,22 @@ try {
19085
19344
  throw err;
19086
19345
  }
19087
19346
  try {
19088
- const { seedPricingFromModelsDev } = await import("./seed-pricing-7yfaf123.js");
19347
+ const { seedPricingFromModelsDev } = await import("./seed-pricing-1r1y1xkq.js");
19089
19348
  seedPricingFromModelsDev();
19090
- const { startPricingRefreshLoop } = await import("./pricing-refresh-rat240yg.js");
19349
+ const { startPricingRefreshLoop } = await import("./pricing-refresh-cgtkffpa.js");
19091
19350
  startPricingRefreshLoop();
19092
19351
  } catch (err) {
19093
19352
  console.error("[startup] Failed to seed pricing rows:", err);
19094
19353
  }
19095
19354
  try {
19096
- const { runAllSeeders } = await import("./index-6zpajd9e.js");
19355
+ const { runAllSeeders } = await import("./index-vfedgdw6.js");
19097
19356
  await runAllSeeders({ scriptEmbeddingMode: "skip" });
19098
19357
  } catch (err) {
19099
19358
  console.error("[startup] Failed to seed built-in entities:", err);
19100
19359
  }
19360
+ setAuditSink(enqueueAuditRow);
19361
+ startAuditWriter();
19362
+ startAuditGc();
19101
19363
  initialize();
19102
19364
  await initOtel("api");
19103
19365
  httpServer.listen(port, async () => {
@@ -19126,31 +19388,31 @@ httpServer.listen(port, async () => {
19126
19388
  initWorkflows();
19127
19389
  startScriptRunSupervisor(getMcpBaseUrl());
19128
19390
  if (hasCapability("scheduling")) {
19129
- const { startScheduler } = await import("./index-20mf9wvc.js");
19130
- const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-w15fk28m.js");
19391
+ const { startScheduler } = await import("./index-0gzmqj4k.js");
19392
+ const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-5ghxn8s6.js");
19131
19393
  const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
19132
19394
  startScheduler(getExecutorRegistry2(), intervalMs, {
19133
19395
  runId: globalState.__runId
19134
19396
  });
19135
19397
  }
19136
19398
  if (process.env.HEARTBEAT_DISABLE !== "true") {
19137
- const { startHeartbeat } = await import("./index-744yvc8x.js");
19399
+ const { startHeartbeat } = await import("./index-nhat35em.js");
19138
19400
  const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
19139
19401
  startHeartbeat(heartbeatMs);
19140
19402
  }
19141
19403
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
19142
- const { startOAuthKeepalive } = await import("./keepalive-3gvnbxaj.js");
19404
+ const { startOAuthKeepalive } = await import("./keepalive-62gfj91d.js");
19143
19405
  startOAuthKeepalive();
19144
19406
  }
19145
19407
  startMcpOAuthPendingGc();
19146
19408
  startMemoryGc();
19147
- import("./boot-reembed-0t815mmm.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
19409
+ import("./boot-reembed-p0ny05t8.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
19148
19410
  console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
19149
19411
  });
19150
- import("./boot-reembed-50zzk97q.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
19412
+ import("./boot-reembed-69agkhv6.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
19151
19413
  console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
19152
19414
  });
19153
- import("./boot-scrub-logs-r99nbmxh.js").then(({ runBootScrubLogs }) => runBootScrubLogs()).catch((err) => {
19415
+ import("./boot-scrub-logs-yybrkmvr.js").then(({ runBootScrubLogs }) => runBootScrubLogs()).catch((err) => {
19154
19416
  console.error("[boot-scrub-logs] startup scrub failed (non-fatal):", err);
19155
19417
  });
19156
19418
  }).on("error", (err) => {