@desplega.ai/agent-swarm 2.4.2 → 2.4.4

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 (183) hide show
  1. package/dist/{acp-adapter-dv7hmx4r.js → acp-adapter-thq04adh.js} +4 -5
  2. package/dist/{actions-51nc1gny.js → actions-j6ywh6x5.js} +18 -18
  3. package/dist/{amp-adapter-abp5tdx8.js → amp-adapter-k8b5xmj8.js} +3 -3
  4. package/dist/{app-9h14qvr6.js → app-rb7vc02s.js} +3 -3
  5. package/dist/{assistant-wv1gz3mt.js → assistant-ftta1zjf.js} +20 -20
  6. package/dist/{boot-reembed-6yn53ms9.js → boot-reembed-srp6v6af.js} +1 -1
  7. package/dist/{boot-reembed-7fcka4z5.js → boot-reembed-xns7h5h9.js} +1 -1
  8. package/dist/{boot-scrub-sweep-pm1580ax.js → boot-scrub-sweep-3qqrh2mz.js} +3 -3
  9. package/dist/{boot-seeds-g5hfc1x8.js → boot-seeds-9ks8hce7.js} +4 -4
  10. package/dist/{claude-adapter-gf3kxhf0.js → claude-adapter-k5ksyres.js} +4 -4
  11. package/dist/{claude-managed-setup-h00hsgtz.js → claude-managed-setup-n5a5eqks.js} +1 -1
  12. package/dist/{claude-sdk-session-s2k1qd8y.js → claude-sdk-session-2j3j4kwn.js} +9822 -9749
  13. package/dist/{cli-pkht5hap.js → cli-1d70crsy.js} +2 -2
  14. package/dist/{cli-r99kc9yq.js → cli-1ephgwnq.js} +3 -3
  15. package/dist/{cli-7225gwj7.js → cli-1m17dc9d.js} +1 -1
  16. package/dist/{cli-bcj753sq.js → cli-2v045xyf.js} +3 -3
  17. package/dist/{cli-h38rjv87.js → cli-2y91wnbh.js} +12 -7
  18. package/dist/{cli-sv5gb0yb.js → cli-2zqpkvtt.js} +1 -1
  19. package/dist/{cli-xbzvet96.js → cli-36ckw0wx.js} +5 -4
  20. package/dist/{cli-8sxqsbbq.js → cli-37yb7307.js} +1 -1
  21. package/dist/{cli-hyfw2j1e.js → cli-3cgngyd8.js} +11 -11
  22. package/dist/{cli-a4gvwttf.js → cli-44159k28.js} +2 -2
  23. package/dist/{cli-7awf1fp8.js → cli-4dg55wsk.js} +3 -3
  24. package/dist/{cli-mhywgrrz.js → cli-4sydckyj.js} +1 -1
  25. package/dist/{cli-y2x52gs2.js → cli-51y09c6p.js} +20 -17
  26. package/dist/{cli-ahtpy9kf.js → cli-55qmbp6z.js} +10 -5
  27. package/dist/{cli-pw7v69vp.js → cli-625tb7aq.js} +1 -1
  28. package/dist/{cli-x5mq1fyq.js → cli-74c1wpm0.js} +3 -3
  29. package/dist/{cli-b1dfr1m4.js → cli-7a6p4e65.js} +1 -1
  30. package/dist/{cli-hqhzg1qq.js → cli-82d3nkc3.js} +1 -1
  31. package/dist/{cli-nmqdc2t3.js → cli-9fhkkpj5.js} +78 -12
  32. package/dist/{cli-h93c9vb3.js → cli-amyhm3gj.js} +1 -1
  33. package/dist/{cli-0qvtffe9.js → cli-b8k7epj1.js} +6 -6
  34. package/dist/{cli-1pa3rz69.js → cli-ba8ne1es.js} +6 -1
  35. package/dist/{cli-g0hgtz4p.js → cli-bnz0r3kc.js} +3 -3
  36. package/dist/{cli-0hyhdkdk.js → cli-cebvbc9t.js} +4 -1
  37. package/dist/{cli-efaqv1c7.js → cli-cynvc9fj.js} +35 -7
  38. package/dist/{cli-8zwe9mpk.js → cli-dqb4n629.js} +10 -10
  39. package/dist/{cli-7a91w22y.js → cli-e8xyjvfc.js} +1 -1
  40. package/dist/{cli-s6vr7r2d.js → cli-esxerzg7.js} +1 -1
  41. package/dist/{cli-w9g4sz81.js → cli-gf2850q7.js} +1 -1
  42. package/dist/{cli-10hhg0km.js → cli-gqvh8d8a.js} +7 -7
  43. package/dist/{cli-t30yj95q.js → cli-hj3debgw.js} +1 -1
  44. package/dist/{cli-fpmsqq59.js → cli-nc35pxb7.js} +3 -3
  45. package/dist/{cli-d3qqdy4c.js → cli-p20r8w42.js} +16 -14
  46. package/dist/{cli-shvfedvm.js → cli-pccfmtb5.js} +5 -5
  47. package/dist/{cli-02qm7src.js → cli-q6r7t299.js} +1037 -299
  48. package/dist/{cli-2wag8973.js → cli-qeaq3j6t.js} +1 -1
  49. package/dist/{cli-nn47d7e8.js → cli-qkhc5aqy.js} +15 -15
  50. package/dist/{cli-faypxz7q.js → cli-t7cyef5g.js} +4 -4
  51. package/dist/{cli-z4wd2ftv.js → cli-te6b5wvg.js} +149 -184
  52. package/dist/{cli-5rk36sjf.js → cli-v3yr2gbg.js} +119 -44
  53. package/dist/{cli-239yjk27.js → cli-vdm20wej.js} +5 -5
  54. package/dist/{cli-zfg9enh9.js → cli-vfha21c9.js} +6 -1
  55. package/dist/{cli-rdyw3mct.js → cli-vq97g1jq.js} +6 -5
  56. package/dist/{cli-n85dqg6d.js → cli-w6nadkh4.js} +4 -4
  57. package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
  58. package/dist/{cli-saw9a6f6.js → cli-xc27fwzg.js} +2 -2
  59. package/dist/{cli-ftyypbg5.js → cli-xj0re5tm.js} +1 -1
  60. package/dist/{cli-fyzjmseb.js → cli-zrk6jxv8.js} +1 -1
  61. package/dist/{cli-y8n5k515.js → cli-zrwpctp4.js} +28 -28
  62. package/dist/cli.js +44 -19
  63. package/dist/{codex-adapter-ttygw382.js → codex-adapter-968xwfy7.js} +2 -2
  64. package/dist/{codex-hook-x7nejyrr.js → codex-hook-e2he7aca.js} +2 -2
  65. package/dist/{codex-session-runner-60b58vv3.js → codex-session-runner-ht4z99fa.js} +2 -2
  66. package/dist/{commands-edmdwa4x.js → commands-pcymaeg9.js} +3 -3
  67. package/dist/{controller-m263fg43.js → controller-fpjyk46r.js} +3 -3
  68. package/dist/{controllers-connect-5fym8sw1.js → controllers-connect-mwzx1fh6.js} +19 -19
  69. package/dist/{cursor-adapter-cz6nm6mj.js → cursor-adapter-cp4s2e2f.js} +2 -2
  70. package/dist/{db-2y0mgstc.js → db-sgd12bt9.js} +3 -3
  71. package/dist/{db-nn0hcmx5.js → db-xsjg9jqq.js} +2 -2
  72. package/dist/{dispatcher-0a9wwjcx.js → dispatcher-x0jfw2c9.js} +17 -17
  73. package/dist/{dsh-adapter-3n3net33.js → dsh-adapter-jrq7ma7h.js} +2 -2
  74. package/dist/{e2b-thdcxtjd.js → e2b-16d6ch39.js} +1 -1
  75. package/dist/{e2b-7am6tmq6.js → e2b-zn0b4w5w.js} +1 -1
  76. package/dist/{external-channel-zhvt95n3.js → external-channel-8njj4h3b.js} +2 -2
  77. package/dist/{grok-adapter-qnr1hp2y.js → grok-adapter-kkr97v2f.js} +5 -6
  78. package/dist/handlers-0pvee9nw.js +39 -0
  79. package/dist/{heartbeat-gz97r7ac.js → heartbeat-wd03zhn0.js} +20 -20
  80. package/dist/{hook-1mmdgjqs.js → hook-f0vghj42.js} +10 -5
  81. package/dist/{hook-x8rthca4.js → hook-xzj2ts2p.js} +2 -2
  82. package/dist/{http-r8f85xr9.js → http-4hwxv62p.js} +261 -123
  83. package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
  84. package/dist/{index-y7rbcrc1.js → index-97q4xn03.js} +20 -11
  85. package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
  86. package/dist/{index-vzvfqzfj.js → index-gdh8jrdm.js} +17 -17
  87. package/dist/{index-aex0eba9.js → index-hg0x2cdv.js} +17 -17
  88. package/dist/index-thm0y6a8.js +41 -0
  89. package/dist/{index-smxyk04s.js → index-ww2vznkp.js} +1 -1
  90. package/dist/{keepalive-64c0f34c.js → keepalive-hrh7xnr9.js} +3 -3
  91. package/dist/{lead-b1da0f97.js → lead-ee8vazz2.js} +30 -31
  92. package/dist/{maintenance-mnxpar12.js → maintenance-e8wnwnqx.js} +1 -1
  93. package/dist/{oauth-refresh-sweep-36hs2v4h.js → oauth-refresh-sweep-zm0yj4sj.js} +2 -2
  94. package/dist/{onboard-xes8de61.js → onboard-dtwcq603.js} +3 -3
  95. package/dist/{opencode-adapter-3s7ax02e.js → opencode-adapter-x4tqzhkh.js} +3 -3
  96. package/dist/{otel-impl-fzc57kcq.js → otel-impl-w3fbhpn0.js} +1 -1
  97. package/dist/{pi-mono-adapter-ytp7gz8b.js → pi-mono-adapter-mcfkn9vf.js} +3 -3
  98. package/dist/{pricing-refresh-1nek2w7b.js → pricing-refresh-j38dehfs.js} +3 -3
  99. package/dist/{rbac-roles-ttyfaep2.js → rbac-roles-ae8yttq4.js} +3 -3
  100. package/dist/{rbac-roles-sqefb5hw.js → rbac-roles-ejxx9b35.js} +2 -2
  101. package/dist/{render-v2-vb527jwn.js → render-v2-n16n0phe.js} +3 -3
  102. package/dist/{resume-jcdzg1bn.js → resume-z0fgjc38.js} +5 -5
  103. package/dist/{session-access-zsp4wsp0.js → session-access-cv3mq6mc.js} +6 -4
  104. package/dist/{setup-1nb2zqd3.js → setup-mafhj3yt.js} +1 -1
  105. package/dist/{work-objects-61w08fwr.js → work-objects-8b86mgjp.js} +21 -21
  106. package/dist/{worker-c1e68hpa.js → worker-16s75hm5.js} +30 -31
  107. package/dist/worker-token-xf11yyds.js +136 -0
  108. package/openapi.json +295 -12
  109. package/package.json +5 -5
  110. package/src/apps/action-runner.ts +4 -0
  111. package/src/apps/catalog.generated.json +3 -0
  112. package/src/apps/page-validator.ts +8 -2
  113. package/src/be/db.ts +7 -4
  114. package/src/be/migrations/231_slack_tasks_last_updated_index.sql +5 -0
  115. package/src/be/migrations/232_agent_session_tokens.sql +41 -0
  116. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +10 -1
  117. package/src/be/swarm-config-guard.ts +16 -0
  118. package/src/be/users.ts +169 -12
  119. package/src/cli.tsx +24 -0
  120. package/src/commands/worker-token.ts +164 -0
  121. package/src/github/handlers.ts +6 -5
  122. package/src/hooks/hook.ts +2 -1
  123. package/src/http/apps.ts +43 -5
  124. package/src/http/codex-oauth-keep-warm.ts +27 -9
  125. package/src/http/config.ts +5 -3
  126. package/src/http/core.ts +2 -0
  127. package/src/http/session-access.ts +51 -2
  128. package/src/http/sessions.ts +249 -10
  129. package/src/http/tasks.ts +4 -32
  130. package/src/providers/acp-adapter.ts +33 -13
  131. package/src/providers/acp-swarm-events.ts +1 -1
  132. package/src/providers/amp-adapter.ts +1 -1
  133. package/src/providers/codex-adapter.ts +4 -4
  134. package/src/providers/codex-oauth/storage.ts +1 -1
  135. package/src/providers/opencode-adapter.ts +2 -2
  136. package/src/providers/pi-mono-extension.ts +2 -1
  137. package/src/rbac/legacy-policy.ts +3 -1
  138. package/src/rbac/permissions.ts +4 -0
  139. package/src/tests/acp-adapter.test.ts +10 -23
  140. package/src/tests/acp-fake-agent.ts +36 -0
  141. package/src/tests/acp-swarm-events.test.ts +1 -1
  142. package/src/tests/agent-session-tokens.test.ts +452 -0
  143. package/src/tests/apps-action-parent-task.test.ts +295 -0
  144. package/src/tests/claude-stop-hook.test.ts +47 -0
  145. package/src/tests/codex-adapter.test.ts +42 -0
  146. package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
  147. package/src/tests/config-session-auth.test.ts +14 -0
  148. package/src/tests/docs-index-footprint.test.js +2 -2
  149. package/src/tests/env-flag.test.ts +15 -1
  150. package/src/tests/github-handlers-cancel-config.test.ts +29 -8
  151. package/src/tests/github-handlers-inline-comments.test.ts +9 -2
  152. package/src/tests/grok-adapter.test.ts +3 -3
  153. package/src/tests/heartbeat-jev-gate.test.ts +23 -0
  154. package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
  155. package/src/tests/rbac-engine.test.ts +1 -0
  156. package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
  157. package/src/tests/script-connections.test.ts +4 -0
  158. package/src/tests/session-route-posture.test.ts +2 -0
  159. package/src/tests/skill-script-refs.test.ts +63 -0
  160. package/src/tests/slack-render-v2-query-plans.test.ts +147 -0
  161. package/src/tests/worker-token-cli.test.ts +76 -0
  162. package/src/tests/workflow-executors.test.ts +4 -2
  163. package/src/tests/workflow-system-one-extraction.test.ts +805 -0
  164. package/src/types.ts +2 -1
  165. package/src/utils/env-flag.ts +9 -0
  166. package/src/utils/request-auth-context.ts +2 -1
  167. package/src/workflows/definition.ts +7 -1
  168. package/src/workflows/engine.ts +18 -12
  169. package/src/workflows/executors/index.ts +1 -0
  170. package/src/workflows/executors/registry.ts +2 -0
  171. package/src/workflows/executors/system-one-decision.ts +56 -255
  172. package/src/workflows/executors/system-one-extract-wire.ts +57 -0
  173. package/src/workflows/executors/system-one-extraction.ts +830 -0
  174. package/src/workflows/executors/system-one-providers.ts +277 -110
  175. package/src/workflows/executors/system-one-transport.ts +264 -0
  176. package/templates/official/lead/HEARTBEAT.md +1 -1
  177. package/templates/skills/apps/SKILL.md +4 -1
  178. package/templates/skills/apps/content.md +4 -1
  179. package/templates/skills/code-quality/content.md +9 -14
  180. package/templates/skills/heartbeat-runbook/content.md +3 -3
  181. package/dist/cli-v1sjhn52.js +0 -7
  182. package/dist/handlers-dkcarkd0.js +0 -39
  183. package/dist/index-5jecgp3n.js +0 -41
@@ -30,6 +30,7 @@ import {
30
30
  } from "../providers/grok-adapter";
31
31
  import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
32
32
  import { getModelAwareCredentialVars } from "../utils/credentials";
33
+ import { ACP_SDK_PATH, serveFakeAcpAgent } from "./acp-fake-agent";
33
34
 
34
35
  /**
35
36
  * Recorded from a live `grok agent stdio` session (Grok CLI 1.0.46,
@@ -39,7 +40,6 @@ import { getModelAwareCredentialVars } from "../utils/credentials";
39
40
  */
40
41
  const FIXTURE = join(import.meta.dir, "fixtures", "grok", "acp-updates-recorded.jsonl");
41
42
  const PROMPT_META = join(import.meta.dir, "fixtures", "grok", "prompt-response-meta.json");
42
- const SDK_PATH = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
43
43
 
44
44
  const tmpDirs: string[] = [];
45
45
 
@@ -129,7 +129,7 @@ function writeFakeGrok(
129
129
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
130
130
  import { join } from "node:path";
131
131
  import { Readable, Writable } from "node:stream";
132
- import { AgentSideConnection, PROTOCOL_VERSION, RequestError, ndJsonStream } from "${SDK_PATH}";
132
+ import { PROTOCOL_VERSION, RequestError, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
133
133
  const home = process.env.GROK_HOME ?? "";
134
134
  const read = (name) => (existsSync(join(home, name)) ? readFileSync(join(home, name), "utf8") : null);
135
135
  const capture = {
@@ -160,7 +160,7 @@ class FakeGrok {
160
160
  }
161
161
  async cancel() { this.cancelled?.(); }
162
162
  }
163
- new AgentSideConnection((c) => new FakeGrok(c), ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin)));
163
+ ${serveFakeAcpAgent("FakeGrok")}
164
164
  `,
165
165
  );
166
166
  const binPath = join(dir, "grok");
@@ -261,6 +261,29 @@ describe("heartbeat-jev-gate with a provider", () => {
261
261
  });
262
262
  });
263
263
 
264
+ test("only a bare MM-DD counts as a due tracked item", async () => {
265
+ const mocked = Date.now;
266
+ Date.now = () => Date.parse("2026-10-20T09:30:00.000Z");
267
+ try {
268
+ const { ctx } = fakeCtx({
269
+ configs: { TYPESAFE_API_KEY: "x" },
270
+ heartbeatMd: [
271
+ "- **2026-10-20 ~10:45Z (heartbeat 2026-10-20):** re-verified, nothing pending",
272
+ "- Watch: 10-20s retry window closed, nothing pending",
273
+ "- Watch: session 4a12-10-20-9e2f finished cleanly, nothing pending",
274
+ "- Watch: release on 10-20 — lift after ship",
275
+ ].join("\n"),
276
+ jev: jevSays(0.9),
277
+ });
278
+ const verdict = await run(ctx, { dryRun: true });
279
+ expect(verdict.state.tracked_items_mentioning_today_or_past_delete_date).toEqual([
280
+ "- Watch: release on 10-20 — lift after ship",
281
+ ]);
282
+ } finally {
283
+ Date.now = mocked;
284
+ }
285
+ });
286
+
264
287
  for (const ordersFirst of [true, false]) {
265
288
  test(`previous failures are not new with standing orders ${ordersFirst ? "before" : "after"} status`, async () => {
266
289
  const failures =
@@ -0,0 +1,67 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { expect, test } from "bun:test";
3
+
4
+ test("migration 232 preserves every token kind and adds agent-only tokens", async () => {
5
+ const db = new Database(":memory:");
6
+ try {
7
+ db.exec(await Bun.file("src/be/migrations/149_session_tokens.sql").text());
8
+ db.exec(await Bun.file("src/be/migrations/207_runtime_session_tokens.sql").text());
9
+ const seed = db.query(
10
+ `INSERT INTO session_tokens
11
+ (id, tokenHash, tokenPreview, agentId, taskId, runtimeId, parentRuntimeId, controllerId,
12
+ expiresAt, revokedAt, createdAt, lastUsedAt)
13
+ VALUES (?, ?, 'view', 'agent', ?, ?, ?, ?, '2030-01-01', NULL, '2025-01-01', '2025-12-01')`,
14
+ );
15
+ seed.run("acp", "acp", "task", null, null, null);
16
+ seed.run("runtime", "runtime", null, "runtime", null, null);
17
+ seed.run("child", "child", "task", null, "runtime", null);
18
+ seed.run("controller", "controller", null, null, null, "ctrl");
19
+ const before = db.query("SELECT * FROM session_tokens ORDER BY id").all();
20
+
21
+ db.exec(await Bun.file("src/be/migrations/232_agent_session_tokens.sql").text());
22
+
23
+ // prepare, not query: query() caches the statement and its pre-migration column list.
24
+ expect(db.prepare("SELECT * FROM session_tokens ORDER BY id").all()).toEqual(
25
+ before.map((row) => ({ ...(row as object), parentTokenId: null })),
26
+ );
27
+ const insert = db.query(
28
+ `INSERT INTO session_tokens
29
+ (id, tokenHash, tokenPreview, agentId, taskId, runtimeId, parentRuntimeId, controllerId,
30
+ parentTokenId, expiresAt)
31
+ VALUES (?, ?, 'view', 'agent', ?, ?, ?, ?, ?, '2030-01-01')`,
32
+ );
33
+ insert.run("worker", "worker", null, null, null, null, null);
34
+ insert.run("worker-child", "worker-child", "task", null, null, null, "worker");
35
+ expect(() => insert.run("mixed", "mixed", "task", "runtime", null, null, null)).toThrow();
36
+ expect(() =>
37
+ insert.run("two-parents", "two-parents", "task", null, "rt", null, "worker"),
38
+ ).toThrow();
39
+ expect(() =>
40
+ insert.run("runtime-parent-token", "runtime-parent-token", null, "rt", null, null, "worker"),
41
+ ).toThrow();
42
+ expect(() =>
43
+ insert.run("controller-parent", "controller-parent", null, null, null, "ctrl", "worker"),
44
+ ).toThrow();
45
+ expect(() =>
46
+ insert.run("agent-parent-runtime", "agent-parent-runtime", null, null, "rt", null, null),
47
+ ).toThrow();
48
+ expect(() =>
49
+ insert.run("agent-with-parent", "agent-with-parent", null, null, null, null, "worker"),
50
+ ).toThrow();
51
+ const indexes = db
52
+ .query<{ name: string }, []>("PRAGMA index_list(session_tokens)")
53
+ .all()
54
+ .map((row) => row.name);
55
+ for (const name of [
56
+ "idx_session_tokens_agentId",
57
+ "idx_session_tokens_runtimeId",
58
+ "idx_session_tokens_parentRuntimeId",
59
+ "idx_session_tokens_parentTokenId",
60
+ ]) {
61
+ expect(indexes).toContain(name);
62
+ }
63
+ expect(db.query("PRAGMA integrity_check").get()).toEqual({ integrity_check: "ok" });
64
+ } finally {
65
+ db.close();
66
+ }
67
+ });
@@ -105,6 +105,7 @@ const OPERATOR_OR_USER_VERBS: PermissionVerb[] = [
105
105
  "approval.respond",
106
106
  "runtime_controller.write",
107
107
  "runtime_pool.write",
108
+ "agent.token.manage",
108
109
  ];
109
110
 
110
111
  const LEAD_OR_OPERATOR_VERBS: PermissionVerb[] = ["models.catalog.write"];
@@ -9,6 +9,7 @@ import { handleSessions } from "../http/sessions";
9
9
  import { getPathSegments, parseQueryParams } from "../http/utils";
10
10
  import { ACPAdapter } from "../providers/acp-adapter";
11
11
  import { setRequestAuth } from "../utils/request-auth-context";
12
+ import { ACP_SDK_PATH, serveFakeAcpAgent } from "./acp-fake-agent";
12
13
  import { listenOnFreePort } from "./test-net";
13
14
  import { CHILD_PROCESS_TEST_BUDGET_MS } from "./test-proc";
14
15
 
@@ -69,12 +70,11 @@ for (const setupFails of [false, true]) {
69
70
  const api = await startApi(resolveRevoke);
70
71
  const script = join(tmpdir(), `runtime-child-${crypto.randomUUID()}.ts`);
71
72
  const capture = `${script}.auth`;
72
- const sdk = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
73
73
  await Bun.write(
74
74
  script,
75
75
  `
76
76
  import { Readable, Writable } from "node:stream";
77
- import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from ${JSON.stringify(sdk)};
77
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from ${JSON.stringify(ACP_SDK_PATH)};
78
78
  class Agent {
79
79
  async initialize() {
80
80
  return { protocolVersion: PROTOCOL_VERSION, agentCapabilities: { loadSession: false } };
@@ -89,7 +89,7 @@ class Agent {
89
89
  async prompt() { return { stopReason: "end_turn" }; }
90
90
  async cancel() {}
91
91
  }
92
- new AgentSideConnection(() => new Agent(), ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin)));
92
+ ${serveFakeAcpAgent("Agent")}
93
93
  `,
94
94
  );
95
95
  try {
@@ -634,6 +634,10 @@ describe("script connections", () => {
634
634
  markMigrationApplied(database, "220_model_profiles.sql");
635
635
  // 227 alters inbox_item_state, which this migration-112-only fixture does not create.
636
636
  markMigrationApplied(database, "227_inbox_item_state_last_read_at.sql");
637
+ // 231 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
638
+ markMigrationApplied(database, "231_slack_tasks_last_updated_index.sql");
639
+ // 232 rebuilds session_tokens with the 207 columns, which this fixture skipped.
640
+ markMigrationApplied(database, "232_agent_session_tokens.sql");
637
641
 
638
642
  const id = crypto.randomUUID();
639
643
  const now = new Date().toISOString();
@@ -25,6 +25,7 @@ import "../http/all-routes";
25
25
  import { handleActiveSessions } from "../http/active-sessions";
26
26
  import { handleApiKeys } from "../http/api-keys";
27
27
  import { handleApprovalRequests } from "../http/approval-requests";
28
+ import { handleApps } from "../http/apps";
28
29
  import { handleContext } from "../http/context";
29
30
  import { handleCore } from "../http/core";
30
31
  import { handleEvents } from "../http/events";
@@ -76,6 +77,7 @@ const HANDLERS: Handler[] = [
76
77
  handleFs,
77
78
  handleActiveSessions,
78
79
  handleSessions,
80
+ handleApps,
79
81
  (req, res, segments, query) => handleApprovalRequests(req, res, segments, query),
80
82
  (req, res, segments, query) => handleApiKeys(req, res, segments, query),
81
83
  (req, res, segments, query) => handleScriptRuns(req, res, segments, query),
@@ -0,0 +1,63 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { join, relative } from "node:path";
3
+ import { SEED_SCRIPTS } from "../be/seed-scripts";
4
+
5
+ // A shipped skill that tells agents to `script-run` a named script must name a
6
+ // script this repo ships in SEED_SCRIPTS. Otherwise the skill works only on a
7
+ // swarm that happens to hold that script in its catalog, and every other
8
+ // deployment hits "Script not found" at that step.
9
+
10
+ const REPO_ROOT = join(import.meta.dir, "../..");
11
+ // Seeded skills, baked plugin commands/agents/skills, and public operator skills.
12
+ const SKILL_DIRS = ["templates/skills", "plugin", "skills"].map((d) => join(REPO_ROOT, d));
13
+
14
+ // `script-run name="x"` and the prose forms "`script-run` with name `x`" / "`script-run` on `x`".
15
+ const REF_PATTERNS = [
16
+ /script-run\s+name=["']([a-z0-9][a-z0-9-]*)["']/g,
17
+ /`script-run`\s+(?:with\s+name|on)\s+`([a-z0-9][a-z0-9-]*)`/g,
18
+ ];
19
+
20
+ function findScriptRefs(text: string): string[] {
21
+ const names = new Set<string>();
22
+ for (const re of REF_PATTERNS) {
23
+ for (const m of text.matchAll(re)) if (m[1]) names.add(m[1]);
24
+ }
25
+ return [...names];
26
+ }
27
+
28
+ async function collectRefs(): Promise<{ file: string; name: string }[]> {
29
+ const refs: { file: string; name: string }[] = [];
30
+ for (const dir of SKILL_DIRS) {
31
+ for await (const path of new Bun.Glob("**/*.md").scan({ cwd: dir, absolute: true })) {
32
+ const text = await Bun.file(path).text();
33
+ for (const name of findScriptRefs(text)) refs.push({ file: relative(REPO_ROOT, path), name });
34
+ }
35
+ }
36
+ return refs;
37
+ }
38
+
39
+ describe("shipped skills only script-run scripts the repo ships", () => {
40
+ test("the reference scanner matches each call form and ignores prose", () => {
41
+ expect(
42
+ findScriptRefs(
43
+ [
44
+ 'Run `script-run name="internal-only-gate" scope="global" args=<draft>`.',
45
+ "Run `script-run` with name `boot-triage`.",
46
+ "Preview with `script-run` on `heartbeat-jev-gate`.",
47
+ "Seed scripts (`script-run` with `name` and `args`):",
48
+ "Inline `script-run` for a one-off.",
49
+ ].join("\n"),
50
+ ).sort(),
51
+ ).toEqual(["boot-triage", "heartbeat-jev-gate", "internal-only-gate"]);
52
+ });
53
+
54
+ test("every script-run name in a shipped skill is in SEED_SCRIPTS", async () => {
55
+ const refs = await collectRefs();
56
+ // Positive control: the scan must see heartbeat-runbook's boot-triage reference, or it is blind.
57
+ expect(refs.some((r) => r.name === "boot-triage")).toBe(true);
58
+
59
+ const shipped = new Set(SEED_SCRIPTS.map((s) => s.name));
60
+ const missing = refs.filter((r) => !shipped.has(r.name)).map((r) => `${r.file}: ${r.name}`);
61
+ expect(missing).toEqual([]);
62
+ });
63
+ });
@@ -0,0 +1,147 @@
1
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+ import {
3
+ closeDb,
4
+ createAgent,
5
+ createTaskExtended,
6
+ getDb,
7
+ getDbClient,
8
+ getResolvableDeferralOutcomes,
9
+ initDb,
10
+ recordSlackMessage,
11
+ } from "../be/db";
12
+ import { getSlackTasksMissingTree } from "../be/db/tasks/read";
13
+ import { completeTask, startTask } from "../be/db/tasks/write";
14
+
15
+ // Both queries run on every Slack watcher tick. The DB has no sqlite_stat1, so
16
+ // the planner works from heuristics; these pin the plans that keep each tick
17
+ // proportional to Slack rows instead of every task.
18
+
19
+ async function capturedSql(run: () => Promise<unknown>): Promise<string> {
20
+ const client = getDbClient();
21
+ const seen: string[] = [];
22
+ const original = client.query.bind(client);
23
+ const spy = spyOn(client, "query").mockImplementation(((sql: string, params?: unknown[]) => {
24
+ seen.push(sql);
25
+ return original(sql, params);
26
+ }) as typeof client.query);
27
+ try {
28
+ await run();
29
+ } finally {
30
+ spy.mockRestore();
31
+ }
32
+ expect(seen).toHaveLength(1);
33
+ return seen[0]!;
34
+ }
35
+
36
+ function plan(sql: string, params: unknown[] = []): string {
37
+ return getDb()
38
+ .query<{ detail: string }, any[]>(`EXPLAIN QUERY PLAN ${sql}`)
39
+ .all(...params)
40
+ .map((row) => row.detail)
41
+ .join("\n");
42
+ }
43
+
44
+ async function deferredAsk(label: string, leadId: string) {
45
+ const ask = await createTaskExtended(`ask ${label}`, {
46
+ agentId: leadId,
47
+ source: "slack",
48
+ slackChannelId: `C_${label}`,
49
+ slackThreadTs: `${label}.0`,
50
+ });
51
+ await startTask(ask.id);
52
+ await completeTask(ask.id, "Checking back later", {
53
+ addTags: ["deferred"],
54
+ deferredAt: new Date().toISOString(),
55
+ });
56
+ const card = await recordSlackMessage({
57
+ contextKey: `slack:C_${label}:${label}.0`,
58
+ channelId: `C_${label}`,
59
+ threadTs: `${label}.0`,
60
+ ts: `${label}.1`,
61
+ kind: "outcome",
62
+ taskId: ask.id,
63
+ finalized: true,
64
+ });
65
+ return { ask, card };
66
+ }
67
+
68
+ async function wake(parentTaskId: string, leadId: string, opts: { deferAgain?: boolean } = {}) {
69
+ await Bun.sleep(2);
70
+ const task = await createTaskExtended("wake", {
71
+ agentId: leadId,
72
+ parentTaskId,
73
+ taskType: "deferred",
74
+ });
75
+ await startTask(task.id);
76
+ await completeTask(
77
+ task.id,
78
+ "done",
79
+ opts.deferAgain ? { deferredAt: new Date().toISOString() } : {},
80
+ );
81
+ return task;
82
+ }
83
+
84
+ describe("slack render-v2 watcher queries", () => {
85
+ beforeEach(() => {
86
+ initDb(":memory:");
87
+ getDb().run(
88
+ "INSERT OR IGNORE INTO slack_render_v2_state (id, activated_at) VALUES (1, '2000-01-01T00:00:00.000Z')",
89
+ );
90
+ });
91
+
92
+ afterEach(() => {
93
+ closeDb();
94
+ });
95
+
96
+ test("deferral resolution drives from the chain, not from every task", async () => {
97
+ const sql = await capturedSql(() => getResolvableDeferralOutcomes());
98
+ const detail = plan(sql, [50]);
99
+ expect(detail).toContain(
100
+ "SEARCH wake USING INDEX idx_agent_tasks_parentTaskId (parentTaskId=?)",
101
+ );
102
+ expect(detail).not.toContain("idx_agent_tasks_taskType");
103
+ expect(detail).not.toContain("idx_agent_tasks_status_last_updated");
104
+ });
105
+
106
+ test("missing-tree scan reads only Slack tasks", async () => {
107
+ const sql = await capturedSql(() => getSlackTasksMissingTree());
108
+ const detail = plan(sql);
109
+ expect(detail).toContain("SCAN task USING INDEX idx_agent_tasks_slack_last_updated");
110
+ expect(detail).not.toContain("idx_agent_tasks_last_updated");
111
+ });
112
+
113
+ test("deferral resolution returns the latest terminal wake of each open chain", async () => {
114
+ const lead = await createAgent({ name: "Plan Lead", isLead: true, status: "idle" });
115
+
116
+ const single = await deferredAsk("SINGLE", lead.id);
117
+ const singleWake = await wake(single.ask.id, lead.id);
118
+
119
+ const chained = await deferredAsk("CHAIN", lead.id);
120
+ const firstWake = await wake(chained.ask.id, lead.id, { deferAgain: true });
121
+ const lastWake = await wake(firstWake.id, lead.id);
122
+
123
+ const pending = await deferredAsk("PENDING", lead.id);
124
+ const running = await createTaskExtended("still running", {
125
+ agentId: lead.id,
126
+ parentTaskId: pending.ask.id,
127
+ taskType: "deferred",
128
+ });
129
+ await startTask(running.id);
130
+
131
+ const plainChild = await deferredAsk("PLAIN", lead.id);
132
+ const notDeferred = await createTaskExtended("ordinary child", {
133
+ agentId: lead.id,
134
+ parentTaskId: plainChild.ask.id,
135
+ });
136
+ await startTask(notDeferred.id);
137
+ await completeTask(notDeferred.id, "done");
138
+
139
+ const rows = await getResolvableDeferralOutcomes();
140
+ expect(rows.map((row) => [row.card.id, row.deferredTaskId, row.wakeTaskId]).sort()).toEqual(
141
+ [
142
+ [single.card.id, single.ask.id, singleWake.id],
143
+ [chained.card.id, chained.ask.id, lastWake.id],
144
+ ].sort(),
145
+ );
146
+ });
147
+ });
@@ -0,0 +1,76 @@
1
+ import { expect, test } from "bun:test";
2
+ import { parseWorkerTokenArgs, runWorkerToken } from "../commands/worker-token";
3
+
4
+ const env = { MCP_BASE_URL: "http://swarm.test", AGENT_SWARM_API_KEY: "operator-key-for-test" };
5
+
6
+ function fakeFetch(agents: { id: string; name: string }[]) {
7
+ const calls: { url: string; method: string; body?: unknown }[] = [];
8
+ const impl = (async (url: string, init?: RequestInit) => {
9
+ calls.push({
10
+ url,
11
+ method: init?.method ?? "GET",
12
+ body: init?.body ? JSON.parse(String(init.body)) : undefined,
13
+ });
14
+ if (url.endsWith("/api/agents")) return Response.json({ agents });
15
+ if (init?.method === "DELETE") return Response.json({ revoked: 1 });
16
+ return Response.json({ plaintext: "minted-value", expiresAt: null, agentCreated: true });
17
+ }) as unknown as typeof fetch;
18
+ return { calls, impl };
19
+ }
20
+
21
+ function capture() {
22
+ const out: string[] = [];
23
+ const err: string[] = [];
24
+ return { out, err, stdout: (l: string) => out.push(l), stderr: (l: string) => err.push(l) };
25
+ }
26
+
27
+ test("argument parsing requires one action and exactly one agent selector", () => {
28
+ expect(parseWorkerTokenArgs(["create", "--name", "w"]).error).toBeUndefined();
29
+ expect(parseWorkerTokenArgs(["create"]).error).toContain("exactly one");
30
+ expect(parseWorkerTokenArgs(["create", "--name", "w", "--agent-id", "a"]).error).toContain(
31
+ "exactly one",
32
+ );
33
+ expect(parseWorkerTokenArgs(["--name", "w"]).error).toContain("Missing action");
34
+ expect(parseWorkerTokenArgs(["create", "--name", "w", "--ttl-days", "0"]).error).toContain(
35
+ "--ttl-days",
36
+ );
37
+ expect(parseWorkerTokenArgs(["create", "--bogus"]).error).toContain("Unknown argument");
38
+ });
39
+
40
+ test("create by an unused name makes the agent and prints only the token to stdout", async () => {
41
+ const { calls, impl } = fakeFetch([]);
42
+ const io = capture();
43
+ const code = await runWorkerToken(["create", "--name", "w1", "--ttl-days", "2"], {
44
+ env,
45
+ fetch: impl,
46
+ ...io,
47
+ });
48
+ expect(code).toBe(0);
49
+ expect(io.out).toEqual(["minted-value"]);
50
+ expect(io.err.join("\n")).not.toContain("minted-value");
51
+ const mint = calls[1]!;
52
+ expect(mint.method).toBe("POST");
53
+ expect(mint.url).toMatch(/^http:\/\/swarm\.test\/api\/agents\/[0-9a-f-]{36}\/token$/);
54
+ expect(mint.body).toEqual({ name: "w1", ttlMs: 2 * 86_400_000 });
55
+ });
56
+
57
+ test("rotate and revoke resolve an existing name; an ambiguous name fails", async () => {
58
+ const agents = [{ id: "agent-1", name: "w1" }];
59
+ for (const [action, method, suffix] of [
60
+ ["rotate", "POST", "/rotate"],
61
+ ["revoke", "DELETE", ""],
62
+ ] as const) {
63
+ const { calls, impl } = fakeFetch(agents);
64
+ expect(await runWorkerToken([action, "--name", "w1"], { env, fetch: impl, ...capture() })).toBe(
65
+ 0,
66
+ );
67
+ expect(calls[1]).toMatchObject({
68
+ method,
69
+ url: `http://swarm.test/api/agents/agent-1/token${suffix}`,
70
+ });
71
+ }
72
+ const { impl } = fakeFetch([...agents, { id: "agent-2", name: "w1" }]);
73
+ const io = capture();
74
+ expect(await runWorkerToken(["rotate", "--name", "w1"], { env, fetch: impl, ...io })).toBe(1);
75
+ expect(io.err.join("\n")).toContain("Several agents");
76
+ });
@@ -984,7 +984,7 @@ describe("ValidateExecutor", () => {
984
984
  // ─── Registry Wiring ─────────────────────────────────────────
985
985
 
986
986
  describe("createExecutorRegistry", () => {
987
- test("registers all 14 executors (9 instant + 5 async)", () => {
987
+ test("registers all 15 executors (10 instant + 5 async)", () => {
988
988
  const registry = createExecutorRegistry(mockDeps);
989
989
  const types = registry.types();
990
990
 
@@ -993,6 +993,7 @@ describe("createExecutorRegistry", () => {
993
993
  expect(types).toContain("notify");
994
994
  expect(types).toContain("raw-llm");
995
995
  expect(types).toContain("system-one-decision");
996
+ expect(types).toContain("system-one-extraction");
996
997
  expect(types).toContain("script");
997
998
  expect(types).toContain("swarm-script");
998
999
  expect(types).toContain("vcs");
@@ -1002,7 +1003,7 @@ describe("createExecutorRegistry", () => {
1002
1003
  expect(types).toContain("human-in-the-loop");
1003
1004
  expect(types).toContain("wait");
1004
1005
  expect(types).toContain("sub-workflow");
1005
- expect(types).toHaveLength(14);
1006
+ expect(types).toHaveLength(15);
1006
1007
  });
1007
1008
 
1008
1009
  test("instant executors have mode instant, async executors have mode async", () => {
@@ -1012,6 +1013,7 @@ describe("createExecutorRegistry", () => {
1012
1013
  "code-match",
1013
1014
  "notify",
1014
1015
  "raw-llm",
1016
+ "system-one-extraction",
1015
1017
  "script",
1016
1018
  "swarm-script",
1017
1019
  "vcs",