@desplega.ai/agent-swarm 1.114.0 → 1.115.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 (117) hide show
  1. package/dist/{actions-bm8vdxys.js → actions-17ya5wde.js} +6 -6
  2. package/dist/{app-bhwdxmvp.js → app-4f718yk0.js} +4 -4
  3. package/dist/{assistant-15mp2apr.js → assistant-ah7csnjz.js} +9 -9
  4. package/dist/{boot-reembed-awr2179r.js → boot-reembed-4jpxhb25.js} +4 -4
  5. package/dist/{boot-reembed-b9sbk87z.js → boot-reembed-qata34xa.js} +3 -3
  6. package/dist/{boot-scrub-logs-c1zsaw9m.js → boot-scrub-logs-tv48nn3z.js} +2 -2
  7. package/dist/{claude-adapter-2k45y7p7.js → claude-adapter-3csn5bks.js} +2 -2
  8. package/dist/{cli-zmmp2r79.js → cli-12vb0xg6.js} +13 -2
  9. package/dist/{cli-jzwtn8rm.js → cli-15y7qhzp.js} +188 -18
  10. package/dist/{cli-w3nq88yc.js → cli-3samkyap.js} +2 -2
  11. package/dist/{cli-7rgh0w0k.js → cli-52fg7s7x.js} +10 -3
  12. package/dist/{cli-5afkmcfj.js → cli-65ekje01.js} +6 -6
  13. package/dist/{cli-113wmegp.js → cli-6aq8xhvm.js} +1 -1
  14. package/dist/{cli-byjcad11.js → cli-7xje5amz.js} +1 -1
  15. package/dist/{cli-0kgcv878.js → cli-86fdnerz.js} +63 -2
  16. package/dist/{cli-f5pzfvwb.js → cli-b23axgz4.js} +1 -1
  17. package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
  18. package/dist/{cli-q4e2x252.js → cli-beea4a3y.js} +73 -24
  19. package/dist/{cli-w4wk3kx6.js → cli-cchksf5q.js} +2 -2
  20. package/dist/{cli-f9n2jg31.js → cli-cmw9rzy5.js} +2 -2
  21. package/dist/{cli-hvg05qwm.js → cli-cnwssmsr.js} +1 -1
  22. package/dist/{cli-2t4e4ayk.js → cli-dcca6j8m.js} +46 -5
  23. package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
  24. package/dist/{cli-e2xbxmhd.js → cli-hrhye2rm.js} +90 -32
  25. package/dist/{cli-312v3c41.js → cli-k3y4z3h5.js} +1 -1
  26. package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
  27. package/dist/{cli-ggy8c7tk.js → cli-mnbgv3w7.js} +1 -1
  28. package/dist/{cli-hq39e630.js → cli-pet3aev6.js} +95 -10
  29. package/dist/{cli-a6tzw6vq.js → cli-q1rx7g0d.js} +6 -6
  30. package/dist/{cli-pnfyyp6h.js → cli-r9f6ngrf.js} +6 -3
  31. package/dist/{cli-q1z5d5ss.js → cli-shvaxw27.js} +4 -4
  32. package/dist/{cli-49j095n9.js → cli-t0vqevnm.js} +1 -1
  33. package/dist/{cli-k6h2zkw3.js → cli-tece6zgm.js} +1 -1
  34. package/dist/{cli-5ktc9fd4.js → cli-tffvrh6j.js} +1 -1
  35. package/dist/cli-wh91zby6.js +187222 -0
  36. package/dist/{cli-r3mtrrzp.js → cli-zsrdty74.js} +12 -12
  37. package/dist/cli.js +10 -10
  38. package/dist/{codex-adapter-4z8m9mb5.js → codex-adapter-de3w0nch.js} +2 -2
  39. package/dist/{codex-session-runner-rbct46dg.js → codex-session-runner-bnw8cysk.js} +2 -2
  40. package/dist/{commands-zk4d6esf.js → commands-hch4e2k1.js} +2 -2
  41. package/dist/{db-stn16jp2.js → db-p3h2j06x.js} +12 -2
  42. package/dist/{handlers-gshprvpf.js → handlers-9er4zx9q.js} +9 -9
  43. package/dist/{hook-amzx9tap.js → hook-4axs756z.js} +1 -1
  44. package/dist/{http-6g0hfzax.js → http-809fvtz2.js} +182 -65
  45. package/dist/{index-ty42rxw9.js → index-47em3d2n.js} +10 -10
  46. package/dist/{index-jbm7qcqk.js → index-9fqaktjr.js} +8 -8
  47. package/dist/{index-f03rnnrs.js → index-9ht8gr56.js} +9 -9
  48. package/dist/{index-df8fmphk.js → index-rm85cfdx.js} +331 -30
  49. package/dist/{keepalive-pm6g2wpw.js → keepalive-3cps6e3e.js} +4 -4
  50. package/dist/{lead-h79fzpzw.js → lead-fkk95wgq.js} +24 -24
  51. package/dist/{maintenance-433zndy1.js → maintenance-fweqh0yj.js} +4 -4
  52. package/dist/{oauth-refresh-sweep-mjsbmsk2.js → oauth-refresh-sweep-sycqtbph.js} +3 -3
  53. package/dist/{onboard-vdr9hc3f.js → onboard-cy0rkgvx.js} +2 -2
  54. package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
  55. package/dist/{otel-impl-yswd0fty.js → otel-impl-wbns8hf7.js} +1 -1
  56. package/dist/{pi-mono-adapter-vbrvg1p8.js → pi-mono-adapter-05sck1ds.js} +1 -1
  57. package/dist/{pricing-refresh-2kk2ap9b.js → pricing-refresh-0hvazmkf.js} +4 -4
  58. package/dist/{rbac-roles-xdc5208b.js → rbac-roles-kfesjy9y.js} +4 -4
  59. package/dist/{rbac-roles-2wgh9b5t.js → rbac-roles-mwcwhbxc.js} +11 -4
  60. package/dist/{seed-pricing-m9h69d2t.js → seed-pricing-h2kdq4cq.js} +3 -3
  61. package/dist/{setup-eynkvafa.js → setup-8506q97w.js} +2 -2
  62. package/dist/{worker-136q8dc2.js → worker-4r5c6zyb.js} +24 -24
  63. package/openapi.json +8 -4
  64. package/package.json +1 -1
  65. package/src/be/db.ts +253 -20
  66. package/src/be/migrations/113_task_routing_affinity.sql +9 -0
  67. package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
  68. package/src/be/modelsdev-cache.json +135 -0
  69. package/src/be/rbac-audit.ts +19 -8
  70. package/src/be/rbac-roles.ts +8 -1
  71. package/src/be/script-connections.ts +4 -4
  72. package/src/be/scripts/typecheck.ts +11 -0
  73. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
  74. package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
  75. package/src/heartbeat/heartbeat.ts +143 -21
  76. package/src/http/favorites.ts +1 -5
  77. package/src/http/mcp-servers.ts +38 -0
  78. package/src/http/poll.ts +7 -2
  79. package/src/http/scripts.ts +6 -0
  80. package/src/http/skills.ts +11 -0
  81. package/src/http/tasks.ts +21 -9
  82. package/src/providers/codex-models.ts +100 -30
  83. package/src/providers/modelsdev-reasoning.json +51 -0
  84. package/src/providers/reasoning-effort.ts +11 -5
  85. package/src/rbac/admission.ts +26 -2
  86. package/src/rbac/index.ts +2 -2
  87. package/src/rbac/legacy-policy.ts +9 -0
  88. package/src/rbac/permissions.ts +36 -0
  89. package/src/scripts-runtime/api-client.ts +82 -6
  90. package/src/scripts-runtime/types/swarm-sdk.d.ts +11 -0
  91. package/src/server-user.ts +114 -59
  92. package/src/slack/responses.ts +91 -1
  93. package/src/slack/watcher.ts +9 -6
  94. package/src/tasks/worker-follow-up.ts +100 -3
  95. package/src/tests/codex-adapter.test.ts +46 -23
  96. package/src/tests/heartbeat.test.ts +48 -3
  97. package/src/tests/model-control.test.ts +8 -2
  98. package/src/tests/pool-affinity.test.ts +401 -0
  99. package/src/tests/rbac-admission-e2e.test.ts +152 -7
  100. package/src/tests/rbac-admission.test.ts +60 -1
  101. package/src/tests/rbac-engine.test.ts +9 -0
  102. package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
  103. package/src/tests/rbac-roles.test.ts +2 -0
  104. package/src/tests/reasoning-effort.test.ts +15 -2
  105. package/src/tests/script-apis-mcp.test.ts +37 -13
  106. package/src/tests/script-connections.test.ts +27 -1
  107. package/src/tests/scripts-runtime.test.ts +187 -0
  108. package/src/tests/seed-scripts.test.ts +36 -0
  109. package/src/tests/slack-inline-output.test.ts +65 -0
  110. package/src/tests/user-token-rest-auth.test.ts +110 -0
  111. package/src/tools/script-apis.ts +62 -3
  112. package/src/tools/send-task.ts +15 -1
  113. package/src/tools/task-action.ts +34 -2
  114. package/src/tools/templates.ts +48 -0
  115. package/src/tools/utils.ts +2 -0
  116. package/src/types.ts +27 -5
  117. package/dist/cli-46b6kc7f.js +0 -1698
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  buildModelsDevSeedRows
3
- } from "./cli-k6h2zkw3.js";
3
+ } from "./cli-tece6zgm.js";
4
4
  import {
5
5
  createLogEntry,
6
6
  getActivePricingRow,
7
7
  getDb,
8
8
  init_db,
9
9
  insertPricingRow
10
- } from "./cli-jzwtn8rm.js";
10
+ } from "./cli-15y7qhzp.js";
11
11
  import {
12
12
  init_secret_scrubber,
13
13
  scrubSecrets
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  PermissionVerbSchema
3
- } from "./cli-0kgcv878.js";
3
+ } from "./cli-86fdnerz.js";
4
4
  import {
5
5
  getDb,
6
6
  init_db
7
- } from "./cli-jzwtn8rm.js";
7
+ } from "./cli-15y7qhzp.js";
8
8
 
9
9
  // src/be/rbac-roles.ts
10
10
  init_db();
@@ -24,7 +24,14 @@ var BUILTIN_ROLES = [
24
24
  description: "Own-task lifecycle: what legacy policy grants user principals.",
25
25
  isBuiltin: true,
26
26
  grantsAll: false,
27
- verbs: ["task.read.own", "task.cancel.own", "task.action.own", "task.fs.mutate"]
27
+ verbs: [
28
+ "task.create.own",
29
+ "task.read.own",
30
+ "task.cancel.own",
31
+ "task.action.own",
32
+ "task.fs.mutate",
33
+ "favorite.write.own"
34
+ ]
28
35
  }
29
36
  ];
30
37
  var CREATE_USER_DEFAULT_ROLE_TRIGGER_SQL = `
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  createAdditiveBuffer,
3
3
  extractSlackMessageText
4
- } from "./cli-5ktc9fd4.js";
4
+ } from "./cli-tffvrh6j.js";
5
5
  import {
6
6
  getSlackApp,
7
7
  registerTreeMessage
8
- } from "./cli-hq39e630.js";
8
+ } from "./cli-pet3aev6.js";
9
9
  import {
10
10
  rewriteSlackMentions
11
- } from "./cli-w4wk3kx6.js";
11
+ } from "./cli-cchksf5q.js";
12
12
  import {
13
13
  buildBufferFlushBlocks
14
- } from "./cli-6xd0bvf8.js";
14
+ } from "./cli-ma04faw5.js";
15
15
  import {
16
16
  createTaskWithSiblingAwareness,
17
17
  slackContextKey
18
- } from "./cli-312v3c41.js";
18
+ } from "./cli-k3y4z3h5.js";
19
19
  import {
20
20
  getLatestActiveTaskInThread,
21
21
  getLeadAgent,
@@ -23,7 +23,7 @@ import {
23
23
  init_db,
24
24
  init_registry,
25
25
  registerTemplate
26
- } from "./cli-jzwtn8rm.js";
26
+ } from "./cli-15y7qhzp.js";
27
27
 
28
28
  // src/slack/event-dedup.ts
29
29
  var DEFAULT_TTL_MS = 300000;
@@ -4,7 +4,7 @@ import {
4
4
  incrKv,
5
5
  init_db,
6
6
  upsertKv
7
- } from "./cli-jzwtn8rm.js";
7
+ } from "./cli-15y7qhzp.js";
8
8
 
9
9
  // src/be/users.ts
10
10
  init_db();
@@ -6,7 +6,7 @@ import {
6
6
  getDb,
7
7
  init_db,
8
8
  isSqliteVecAvailable
9
- } from "./cli-jzwtn8rm.js";
9
+ } from "./cli-15y7qhzp.js";
10
10
  import {
11
11
  __esm,
12
12
  __export,
@@ -24,6 +24,10 @@ var PERMISSIONS = {
24
24
  description: "Cancel any task (beyond tasks the caller created).",
25
25
  namespace: "task"
26
26
  },
27
+ "task.create.own": {
28
+ description: "Create a task the caller owns.",
29
+ namespace: "task"
30
+ },
27
31
  "task.read.own": {
28
32
  description: "Read details of a task the principal requested.",
29
33
  namespace: "task"
@@ -40,6 +44,10 @@ var PERMISSIONS = {
40
44
  description: "Mutate a task's filesystem artifacts and attachments.",
41
45
  namespace: "task"
42
46
  },
47
+ "favorite.write.own": {
48
+ description: "Set a favorite the caller owns.",
49
+ namespace: "favorite"
50
+ },
43
51
  "memory.learning.inject": {
44
52
  description: "Inject a learning into another agent's memory.",
45
53
  namespace: "memory"
@@ -156,6 +164,10 @@ var PERMISSIONS = {
156
164
  description: "Update an MCP server the caller does not own.",
157
165
  namespace: "mcp-server"
158
166
  },
167
+ "mcp-server.read.secrets": {
168
+ description: "Read resolved MCP server secret env/header values.",
169
+ namespace: "mcp-server"
170
+ },
159
171
  "mcp-oauth.authorize.any": {
160
172
  description: "Start an MCP-server OAuth authorize flow for a caller-supplied user scope.",
161
173
  namespace: "mcp-oauth"
@@ -175,6 +187,30 @@ var PERMISSIONS = {
175
187
  "script.global.delete": {
176
188
  description: "Delete a global-scope script.",
177
189
  namespace: "script"
190
+ },
191
+ "script.search": {
192
+ description: "Search reusable scripts visible to the caller.",
193
+ namespace: "script"
194
+ },
195
+ "script.api.read.secrets": {
196
+ description: "Reveal an external script API endpoint bearer token.",
197
+ namespace: "script"
198
+ },
199
+ "script.api.create": {
200
+ description: "Expose a script as an external HTTP API endpoint.",
201
+ namespace: "script"
202
+ },
203
+ "script.api.update": {
204
+ description: "Update an external script API endpoint.",
205
+ namespace: "script"
206
+ },
207
+ "script.api.rotate": {
208
+ description: "Rotate an external script API endpoint bearer token.",
209
+ namespace: "script"
210
+ },
211
+ "script.api.delete": {
212
+ description: "Delete an external script API endpoint.",
213
+ namespace: "script"
178
214
  }
179
215
  };
180
216
  var PERMISSION_VERBS = Object.keys(PERMISSIONS);
@@ -201,6 +237,22 @@ function decideAdmission(input) {
201
237
  }
202
238
  return { allow: false, reason: "admission: route has no permission verb (operator-only)" };
203
239
  }
240
+ function decideToolAdmission(input) {
241
+ if (input.grant.grantsAll) {
242
+ return { allow: true };
243
+ }
244
+ if (input.rbac && "permission" in input.rbac) {
245
+ const verb = input.rbac.permission;
246
+ if (input.grant.verbs.has(verb)) {
247
+ return { allow: true, verb };
248
+ }
249
+ return { allow: false, reason: `admission: missing permission '${verb}'`, verb };
250
+ }
251
+ if (input.readOnly) {
252
+ return { allow: true };
253
+ }
254
+ return { allow: false, reason: "admission: tool has no permission verb (operator-only)" };
255
+ }
204
256
  // src/rbac/legacy-policy.ts
205
257
  var leadOnly = {
206
258
  name: "lead-only",
@@ -283,10 +335,12 @@ var LEGACY_POLICY = {
283
335
  "agent.profile.update.any": leadOnly,
284
336
  "agent.context.read.any": leadOnly,
285
337
  "task.cancel.any": leadOrTaskCreator,
338
+ "task.create.own": anyAuthenticated,
286
339
  "task.read.own": requesterOwnsTask,
287
340
  "task.cancel.own": requesterOwnsTask,
288
341
  "task.action.own": requesterOwnsTask,
289
342
  "task.fs.mutate": taskFsMutate,
343
+ "favorite.write.own": anyAuthenticated,
290
344
  "memory.learning.inject": leadOnly,
291
345
  "memory.delete.any": memoryOwnerOrLeadSwarm,
292
346
  "channel.delete": leadOnly,
@@ -316,11 +370,18 @@ var LEGACY_POLICY = {
316
370
  "mcp-server.uninstall.any": leadOnly,
317
371
  "mcp-server.delete.any": leadOrResourceOwner,
318
372
  "mcp-server.update.any": leadOrResourceOwner,
373
+ "mcp-server.read.secrets": leadOnly,
319
374
  "mcp-oauth.authorize.any": anyAuthenticated,
320
375
  "kv.write.any": leadOrOwnNamespace,
321
376
  "page.delete.any": leadOrResourceOwner,
322
377
  "script.global.write": leadOnly,
323
- "script.global.delete": leadOnly
378
+ "script.global.delete": leadOnly,
379
+ "script.search": anyAuthenticated,
380
+ "script.api.read.secrets": leadOnly,
381
+ "script.api.create": leadOnly,
382
+ "script.api.update": leadOnly,
383
+ "script.api.rotate": leadOnly,
384
+ "script.api.delete": leadOnly
324
385
  };
325
386
 
326
387
  // src/rbac/can.ts
@@ -341,4 +402,4 @@ function can(check) {
341
402
  }
342
403
  return decision;
343
404
  }
344
- export { isRbacEnabled, decideAdmission, setAuditSink, clearAuditSink, can, PermissionVerbSchema };
405
+ export { isRbacEnabled, decideAdmission, decideToolAdmission, setAuditSink, clearAuditSink, can, PermissionVerbSchema };
@@ -7,7 +7,7 @@ var package_default;
7
7
  var init_package = __esm(() => {
8
8
  package_default = {
9
9
  name: "@desplega.ai/agent-swarm",
10
- version: "1.114.0",
10
+ version: "1.115.0",
11
11
  description: "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
12
12
  license: "MIT",
13
13
  author: "desplega.sh <contact@desplega.sh>",
@@ -20,7 +20,7 @@ import {
20
20
  } from "./cli-tghre7g6.js";
21
21
  import {
22
22
  applyReasoningEffort
23
- } from "./cli-tkmm5nf9.js";
23
+ } from "./cli-e61fs2hp.js";
24
24
  import {
25
25
  init_credentials,
26
26
  validateOpencodeCredentials
@@ -1,18 +1,22 @@
1
1
  import {
2
+ REBOOT_RETRY_PIN_TAG,
3
+ createPoolStarvationDecisionTask,
2
4
  createRerouteDecisionTask,
3
5
  createResumeFollowUp,
4
6
  getNextResumeGeneration,
7
+ getPinCandidateAgent,
5
8
  getResumeGeneration,
6
9
  repointTrackerSyncBySwarmId
7
- } from "./cli-2t4e4ayk.js";
10
+ } from "./cli-dcca6j8m.js";
8
11
  import {
9
12
  getExecutorRegistry,
10
13
  recoverIncompleteRuns
11
- } from "./cli-a6tzw6vq.js";
14
+ } from "./cli-q1rx7g0d.js";
12
15
  import {
13
16
  MAX_EMPTY_POLLS,
14
17
  assignUnassignedTaskPending,
15
18
  backfillSupersedeTaskResumeTaskId,
19
+ buildRoutingAffinityFromAgent,
16
20
  cleanupStaleSessions,
17
21
  createTaskExtended,
18
22
  deleteActiveSession,
@@ -28,6 +32,7 @@ import {
28
32
  getRecentFailedCount,
29
33
  getRecentFailedTasks,
30
34
  getStalePinnedResumes,
35
+ getStaleUnassignedAffinityTasks,
31
36
  getStalledInProgressTasks,
32
37
  getTaskById,
33
38
  getTaskStats,
@@ -37,6 +42,8 @@ import {
37
42
  init_db,
38
43
  init_registry,
39
44
  init_resolver,
45
+ isAgentEligibleForTask,
46
+ isPoolAffinityEnforcementEnabled,
40
47
  registerTemplate,
41
48
  releaseStaleMentionProcessing,
42
49
  releaseStaleProcessingInbox,
@@ -44,7 +51,7 @@ import {
44
51
  resolveTemplate,
45
52
  supersedeTask,
46
53
  updateAgentStatus
47
- } from "./cli-jzwtn8rm.js";
54
+ } from "./cli-15y7qhzp.js";
48
55
 
49
56
  // src/heartbeat/heartbeat.ts
50
57
  init_db();
@@ -155,6 +162,8 @@ var STALL_THRESHOLD_NO_SESSION_MIN = Number(process.env.HEARTBEAT_STALL_NO_SESSI
155
162
  var STALL_THRESHOLD_STALE_HEARTBEAT_MIN = Number(process.env.HEARTBEAT_STALL_STALE_HB_MIN) || 15;
156
163
  var STALE_CLEANUP_THRESHOLD_MINUTES = Number(process.env.HEARTBEAT_STALE_CLEANUP_MIN) || 30;
157
164
  var MAX_AUTO_ASSIGN_PER_SWEEP = Number(process.env.HEARTBEAT_MAX_AUTO_ASSIGN) || 5;
165
+ var POOL_SCAN_BATCH_SIZE = Number(process.env.HEARTBEAT_POOL_SCAN_BATCH_SIZE) || 50;
166
+ var POOL_SCAN_CAP = Number(process.env.HEARTBEAT_POOL_SCAN_CAP) || 500;
158
167
  var MAX_RESUME_GENERATIONS = Number(process.env.HEARTBEAT_MAX_RESUME_GENERATIONS) || 3;
159
168
  var RESUME_BUDGET_EXHAUSTED_REASON = "resume_budget_exhausted";
160
169
  var HEARTBEAT_RESUME_PIN_GRACE_MIN = (() => {
@@ -164,6 +173,7 @@ var HEARTBEAT_RESUME_PIN_GRACE_MIN = (() => {
164
173
  const parsed = Number(raw);
165
174
  return Number.isFinite(parsed) ? parsed : 10;
166
175
  })();
176
+ var POOL_AFFINITY_ESCALATION_MIN = Number(process.env.POOL_AFFINITY_ESCALATION_MIN) || 15;
167
177
  var HEARTBEAT_CHECKLIST_INTERVAL_MS = Number(process.env.HEARTBEAT_CHECKLIST_INTERVAL_MS) || 30 * 60 * 1000;
168
178
  var HEARTBEAT_CHECKLIST_DISABLE = Boolean(process.env.HEARTBEAT_CHECKLIST_DISABLE);
169
179
  var heartbeatInterval = null;
@@ -412,12 +422,28 @@ async function runRebootSweep() {
412
422
  LIMIT 1`).get(task.id);
413
423
  if (!existingRetry) {
414
424
  try {
425
+ let preferredAgentId;
426
+ const candidate = getPinCandidateAgent(task.agentId);
427
+ if (candidate) {
428
+ const activeCount = getActiveTaskCount(candidate.id);
429
+ const hasCap = activeCount < (candidate.maxTasks ?? 1);
430
+ if (hasCap) {
431
+ preferredAgentId = candidate.id;
432
+ } else {
433
+ console.warn(`[Heartbeat] Reboot retry for task ${task.id.slice(0, 8)} NOT pinned: agent ${candidate.id.slice(0, 8)} at capacity (${activeCount}/${candidate.maxTasks ?? 1}); falling back to affinity-gated pool`);
434
+ }
435
+ }
436
+ const tags = ["reboot-retry", "auto-generated"];
437
+ if (preferredAgentId !== undefined)
438
+ tags.push(REBOOT_RETRY_PIN_TAG);
415
439
  const retryTask = createTaskExtended(task.task, {
416
440
  parentTaskId: task.id,
417
- tags: ["reboot-retry", "auto-generated"],
441
+ agentId: preferredAgentId,
442
+ tags,
418
443
  priority: task.priority,
419
444
  source: task.source,
420
- taskType: task.taskType ?? undefined
445
+ taskType: task.taskType ?? undefined,
446
+ routingAffinity: buildRoutingAffinityFromAgent(task.agentId) ?? undefined
421
447
  });
422
448
  retryTaskId = retryTask.id;
423
449
  console.log(`[Heartbeat] Reboot retry created: ${retryTaskId} (parent: ${task.id})`);
@@ -461,10 +487,6 @@ function autoAssignPoolTasks(findings) {
461
487
  const idleWorkers = getIdleWorkersWithCapacity().filter((w) => (w.emptyPollCount ?? 0) < MAX_EMPTY_POLLS);
462
488
  if (idleWorkers.length === 0)
463
489
  return;
464
- const poolTasks = getUnassignedPoolTasks(MAX_AUTO_ASSIGN_PER_SWEEP);
465
- if (poolTasks.length === 0)
466
- return;
467
- let workerIndex = 0;
468
490
  const reservedByWorker = new Map;
469
491
  const reservedForWorker = (agentId) => {
470
492
  const cached = reservedByWorker.get(agentId);
@@ -475,24 +497,28 @@ function autoAssignPoolTasks(findings) {
475
497
  reservedByWorker.set(agentId, reserved);
476
498
  return reserved;
477
499
  };
478
- for (const task of poolTasks) {
479
- if (workerIndex >= idleWorkers.length)
500
+ let assignedCount = 0;
501
+ let offset = 0;
502
+ while (assignedCount < MAX_AUTO_ASSIGN_PER_SWEEP && offset < POOL_SCAN_CAP) {
503
+ const batch = getUnassignedPoolTasks(POOL_SCAN_BATCH_SIZE, offset);
504
+ if (batch.length === 0)
480
505
  break;
481
- const worker = idleWorkers[workerIndex];
482
- const maxTasks = worker.maxTasks ?? 1;
483
- if (reservedForWorker(worker.id) >= maxTasks) {
484
- workerIndex++;
485
- continue;
486
- }
487
- const assigned = assignUnassignedTaskPending(task.id, worker.id);
488
- if (assigned) {
489
- findings.autoAssigned.push({ taskId: task.id, agentId: worker.id });
490
- reservedByWorker.set(worker.id, reservedForWorker(worker.id) + 1);
491
- const remaining = maxTasks - reservedForWorker(worker.id);
492
- if (remaining <= 0) {
493
- workerIndex++;
506
+ for (const task of batch) {
507
+ if (assignedCount >= MAX_AUTO_ASSIGN_PER_SWEEP)
508
+ break;
509
+ const worker = idleWorkers.find((w) => reservedForWorker(w.id) < (w.maxTasks ?? 1) && isAgentEligibleForTask(w, task));
510
+ if (!worker)
511
+ continue;
512
+ const assigned = assignUnassignedTaskPending(task.id, worker.id);
513
+ if (assigned) {
514
+ findings.autoAssigned.push({ taskId: task.id, agentId: worker.id });
515
+ reservedByWorker.set(worker.id, reservedForWorker(worker.id) + 1);
516
+ assignedCount++;
494
517
  }
495
518
  }
519
+ offset += batch.length;
520
+ if (batch.length < POOL_SCAN_BATCH_SIZE)
521
+ break;
496
522
  }
497
523
  })();
498
524
  }
@@ -550,12 +576,35 @@ function escalateUnreclaimedResumes(findings) {
550
576
  console.log(`[Heartbeat] Escalated unreclaimed pinned resume ${resume.id.slice(0, 8)} → Lead reroute-decision ${escalation.decisionTaskId.slice(0, 8)} (original ${original.id.slice(0, 8)})`);
551
577
  }
552
578
  }
579
+ function escalateStarvedPoolTasks(findings) {
580
+ if (!isPoolAffinityEnforcementEnabled())
581
+ return;
582
+ const cutoff = new Date(Date.now() - POOL_AFFINITY_ESCALATION_MIN * 60 * 1000).toISOString();
583
+ const candidates = getStaleUnassignedAffinityTasks(cutoff);
584
+ if (candidates.length === 0)
585
+ return;
586
+ const registeredAgents = getAllAgents().filter((a) => !a.isLead);
587
+ for (const task of candidates) {
588
+ const hasEligibleAgent = registeredAgents.some((agent) => isAgentEligibleForTask(agent, task));
589
+ if (hasEligibleAgent)
590
+ continue;
591
+ const decision = createPoolStarvationDecisionTask({ original: task });
592
+ if (decision.kind === "created") {
593
+ findings.escalatedReroutes.push({
594
+ originalTaskId: task.id,
595
+ decisionTaskId: decision.task.id
596
+ });
597
+ console.log(`[Heartbeat] Escalated starved pool task ${task.id.slice(0, 8)} → Lead reroute-decision ${decision.task.id.slice(0, 8)} (zero eligible agents)`);
598
+ }
599
+ }
600
+ }
553
601
  async function cleanupStaleResources(findings) {
554
602
  findings.staleCleanup.sessions = cleanupStaleSessions(STALE_CLEANUP_THRESHOLD_MINUTES);
555
603
  findings.staleCleanup.reviewingTasks = releaseStaleReviewingTasks(STALE_CLEANUP_THRESHOLD_MINUTES);
556
604
  findings.staleCleanup.mentionProcessing = releaseStaleMentionProcessing(STALE_CLEANUP_THRESHOLD_MINUTES);
557
605
  findings.staleCleanup.inboxProcessing = releaseStaleProcessingInbox(STALE_CLEANUP_THRESHOLD_MINUTES);
558
606
  escalateUnreclaimedResumes(findings);
607
+ escalateStarvedPoolTasks(findings);
559
608
  try {
560
609
  findings.staleCleanup.workflowRuns = await recoverIncompleteRuns(getExecutorRegistry());
561
610
  } catch {
@@ -4,12 +4,12 @@ import {
4
4
  linkIdentity,
5
5
  recordUnmappedIdentity,
6
6
  resolveIdentity
7
- } from "./cli-113wmegp.js";
7
+ } from "./cli-6aq8xhvm.js";
8
8
  import {
9
9
  getKv,
10
10
  init_db,
11
11
  upsertKv
12
- } from "./cli-jzwtn8rm.js";
12
+ } from "./cli-15y7qhzp.js";
13
13
 
14
14
  // src/slack/enrich.ts
15
15
  init_db();
@@ -4,11 +4,11 @@ import {
4
4
  getEmbeddingProvider,
5
5
  init_embedding,
6
6
  serializeEmbedding
7
- } from "./cli-byjcad11.js";
7
+ } from "./cli-7xje5amz.js";
8
8
  import {
9
9
  getDb,
10
10
  init_db
11
- } from "./cli-jzwtn8rm.js";
11
+ } from "./cli-15y7qhzp.js";
12
12
  import {
13
13
  init_secret_scrubber,
14
14
  scrubSecrets
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  init_package,
3
3
  package_default
4
- } from "./cli-f5pzfvwb.js";
4
+ } from "./cli-b23axgz4.js";
5
5
 
6
6
  // src/commands/shared/client-config.ts
7
7
  init_package();
@@ -2,6 +2,7 @@ import {
2
2
  taskAttachmentDisplayUrl
3
3
  } from "./cli-wspgs9bt.js";
4
4
  import {
5
+ buildRoutingAffinityFromAgent,
5
6
  createTaskExtended,
6
7
  getActiveTaskCount,
7
8
  getAgentById,
@@ -17,7 +18,7 @@ import {
17
18
  init_templates1 as init_templates,
18
19
  normalizeDateRequired,
19
20
  resolveTemplate
20
- } from "./cli-jzwtn8rm.js";
21
+ } from "./cli-15y7qhzp.js";
21
22
 
22
23
  // src/be/db-queries/tracker.ts
23
24
  init_date_utils();
@@ -147,6 +148,13 @@ function isGracefulResumePinEnabled() {
147
148
  var RESUME_GENERATION_TAG_PREFIX = "resume-generation:";
148
149
  var CRASH_RECOVERY_PIN_TAG = "crash-recovery-pin";
149
150
  var GRACEFUL_SHUTDOWN_PIN_TAG = "graceful-shutdown-pin";
151
+ var REBOOT_RETRY_PIN_TAG = "reboot-retry-pin";
152
+ function getPinCandidateAgent(agentId) {
153
+ const candidate = getAgentById(agentId);
154
+ if (!candidate || candidate.status === "offline")
155
+ return null;
156
+ return candidate;
157
+ }
150
158
  function getResumeGeneration(task) {
151
159
  const tag = task.tags.find((value) => value.startsWith(RESUME_GENERATION_TAG_PREFIX));
152
160
  if (!tag)
@@ -247,8 +255,8 @@ function createResumeFollowUp(args) {
247
255
  }
248
256
  let preferredAgentId;
249
257
  if (parent.agentId) {
250
- const candidate = getAgentById(parent.agentId);
251
- if (candidate && candidate.status !== "offline") {
258
+ const candidate = getPinCandidateAgent(parent.agentId);
259
+ if (candidate) {
252
260
  const lastActivity = candidate.lastActivityAt ? Date.parse(candidate.lastActivityAt) : 0;
253
261
  const fresh = Number.isFinite(lastActivity) && Date.now() - lastActivity < WORKER_LIVENESS_WINDOW_SECONDS * 1000;
254
262
  const activeCount = getActiveTaskCount(candidate.id);
@@ -289,6 +297,7 @@ function createResumeFollowUp(args) {
289
297
  if (args.reason === "graceful_shutdown" && preferredAgentId !== undefined) {
290
298
  tags.push(GRACEFUL_SHUTDOWN_PIN_TAG);
291
299
  }
300
+ const routingAffinity = parent.agentId ? buildRoutingAffinityFromAgent(parent.agentId) ?? undefined : undefined;
292
301
  const created = createTaskExtended(followUpDescription, {
293
302
  agentId: preferredAgentId,
294
303
  creatorAgentId: parent.creatorAgentId,
@@ -296,7 +305,8 @@ function createResumeFollowUp(args) {
296
305
  taskType: "resume",
297
306
  tags,
298
307
  priority,
299
- parentTaskId: parent.id
308
+ parentTaskId: parent.id,
309
+ routingAffinity
300
310
  });
301
311
  const repointed = repointTrackerSyncBySwarmId(parent.id, created.id);
302
312
  if (repointed > 0) {
@@ -338,5 +348,36 @@ function createRerouteDecisionTask(args) {
338
348
  });
339
349
  return { kind: "created", task: created };
340
350
  }
351
+ function createPoolStarvationDecisionTask(args) {
352
+ const { original } = args;
353
+ const leadAgent = getLeadAgent();
354
+ if (!leadAgent)
355
+ return { kind: "skipped", reason: "lead_not_found" };
356
+ if (hasNonTerminalRerouteDecisionChild(original.id)) {
357
+ return { kind: "skipped", reason: "duplicate_exists" };
358
+ }
359
+ const affinity = original.routingAffinity;
360
+ const requiredRole = affinity?.role ?? "(none declared)";
361
+ const requiredCapabilities = affinity?.capabilities && affinity.capabilities.length > 0 ? affinity.capabilities.join(", ") : "(none declared)";
362
+ const attachmentsBlock = formatAttachmentsBlock(getTaskAttachments(original.id));
363
+ const decision = resolveTemplate("task.pool.starved.decision", {
364
+ original_task_id: original.id,
365
+ task_desc: original.task.slice(0, 200),
366
+ required_role: requiredRole,
367
+ required_capabilities: requiredCapabilities,
368
+ artifacts_block: attachmentsBlock
369
+ });
370
+ const created = createTaskExtended(decision.text, {
371
+ agentId: leadAgent.id,
372
+ creatorAgentId: original.creatorAgentId,
373
+ source: "system",
374
+ taskType: "reroute-decision",
375
+ tags: ["reroute-decision", "pool-starvation"],
376
+ priority: Math.min(100, (original.priority ?? 50) + 10),
377
+ parentTaskId: original.id,
378
+ inheritParentOutputSchema: false
379
+ });
380
+ return { kind: "created", task: created };
381
+ }
341
382
 
342
- export { getTrackerSync, getTrackerSyncByExternalId, createTrackerSyncIfAbsent, updateTrackerSyncSwarmId, repointTrackerSyncBySwarmId, createTrackerSync, updateTrackerSync, deleteTrackerSync, hasTrackerDelivery, markTrackerDelivery, getAllTrackerSyncs, createTrackerAgentMapping, getResumeGeneration, getNextResumeGeneration, createWorkerTaskFollowUp, createResumeFollowUp, createRerouteDecisionTask };
383
+ export { getTrackerSync, getTrackerSyncByExternalId, createTrackerSyncIfAbsent, updateTrackerSyncSwarmId, repointTrackerSyncBySwarmId, createTrackerSync, updateTrackerSync, deleteTrackerSync, hasTrackerDelivery, markTrackerDelivery, getAllTrackerSyncs, createTrackerAgentMapping, REBOOT_RETRY_PIN_TAG, getPinCandidateAgent, getResumeGeneration, getNextResumeGeneration, createWorkerTaskFollowUp, createResumeFollowUp, createRerouteDecisionTask, createPoolStarvationDecisionTask };
@@ -781,6 +781,57 @@ var modelsdev_reasoning_default = {
781
781
  ]
782
782
  }
783
783
  ]
784
+ },
785
+ "gpt-5.6-sol": {
786
+ id: "gpt-5.6-sol",
787
+ reasoning: true,
788
+ reasoningOptions: [
789
+ {
790
+ type: "effort",
791
+ values: [
792
+ "none",
793
+ "low",
794
+ "medium",
795
+ "high",
796
+ "xhigh",
797
+ "max"
798
+ ]
799
+ }
800
+ ]
801
+ },
802
+ "gpt-5.6-terra": {
803
+ id: "gpt-5.6-terra",
804
+ reasoning: true,
805
+ reasoningOptions: [
806
+ {
807
+ type: "effort",
808
+ values: [
809
+ "none",
810
+ "low",
811
+ "medium",
812
+ "high",
813
+ "xhigh",
814
+ "max"
815
+ ]
816
+ }
817
+ ]
818
+ },
819
+ "gpt-5.6-luna": {
820
+ id: "gpt-5.6-luna",
821
+ reasoning: true,
822
+ reasoningOptions: [
823
+ {
824
+ type: "effort",
825
+ values: [
826
+ "none",
827
+ "low",
828
+ "medium",
829
+ "high",
830
+ "xhigh",
831
+ "max"
832
+ ]
833
+ }
834
+ ]
784
835
  }
785
836
  },
786
837
  openrouter: {
@@ -4035,7 +4086,7 @@ var modelsdev_reasoning_default = {
4035
4086
  };
4036
4087
 
4037
4088
  // src/providers/reasoning-effort.ts
4038
- var REASONING_EFFORT_LEVELS = ["off", "low", "medium", "high", "xhigh"];
4089
+ var REASONING_EFFORT_LEVELS = ["off", "low", "medium", "high", "xhigh", "max"];
4039
4090
  var SNAPSHOT = modelsdev_reasoning_default;
4040
4091
  var FALLBACK_LEVELS = ["low", "medium", "high"];
4041
4092
  var EFFORT_VALUE_MAP = {
@@ -4043,7 +4094,8 @@ var EFFORT_VALUE_MAP = {
4043
4094
  low: "low",
4044
4095
  medium: "medium",
4045
4096
  high: "high",
4046
- xhigh: "xhigh"
4097
+ xhigh: "xhigh",
4098
+ max: "max"
4047
4099
  };
4048
4100
  function splitProviderModel(model) {
4049
4101
  const slash = model.indexOf("/");
@@ -4080,6 +4132,9 @@ function applyHarnessOverrides(harness, model, entry, levels) {
4080
4132
  result = ["off", ...result];
4081
4133
  }
4082
4134
  }
4135
+ if (harness !== "codex") {
4136
+ result = result.filter((l) => l !== "max");
4137
+ }
4083
4138
  if (harness === "codex") {
4084
4139
  const isCodexMax = /-codex-max$/.test(model);
4085
4140
  const isCodexNonMax = /-codex$/.test(model) && !isCodexMax;
@@ -4133,6 +4188,8 @@ var ANTHROPIC_BUDGET_TOKENS_BY_LEVEL = {
4133
4188
  };
4134
4189
  function buildOpencodeReasoningOptions(providerId, level) {
4135
4190
  if (providerId === "anthropic") {
4191
+ if (level === "max")
4192
+ return { reasoningEffort: level };
4136
4193
  return { thinking: { type: "enabled", budgetTokens: ANTHROPIC_BUDGET_TOKENS_BY_LEVEL[level] } };
4137
4194
  }
4138
4195
  if (providerId === "openrouter") {