@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
package/src/http/tasks.ts CHANGED
@@ -87,6 +87,7 @@ import { buildTaskRequestedBy, TaskRequestedBySchema } from "./requested-by";
87
87
  import { bodyTask, route } from "./route-def";
88
88
  import {
89
89
  canReadSession,
90
+ denyParentAttach,
90
91
  readableByFor,
91
92
  resolveSessionActor,
92
93
  SESSION_READ_DENIED,
@@ -779,16 +780,6 @@ const updateTaskTitleRoute = route({
779
780
  },
780
781
  });
781
782
 
782
- /** True unless the request acts for a user who lacks write access to the task's session. */
783
- async function hasSessionWrite(
784
- req: IncomingMessage,
785
- myAgentId: string | undefined,
786
- taskId: string,
787
- ): Promise<boolean> {
788
- const access = await sessionAccessFor(await resolveSessionActor(req, myAgentId), taskId);
789
- return hasSessionAccess(access, "write");
790
- }
791
-
792
783
  async function canSteerTask(
793
784
  req: IncomingMessage,
794
785
  myAgentId: string | undefined,
@@ -1069,29 +1060,10 @@ export async function handleTasks(
1069
1060
  ? await getTaskById(parsed.body.parentTaskId)
1070
1061
  : null;
1071
1062
  // The child inherits the parent's thread context, so an agent caller must own or
1072
- // have created the parent. Operator and user callers keep their access.
1063
+ // have created the parent, and a follow-up in someone else's session needs a
1064
+ // write share. The same gate guards app actions that chain under a task.
1073
1065
  const principal = await resolveTaskWritePrincipal(req, myAgentId);
1074
- if (
1075
- parentTask &&
1076
- !can({
1077
- principal,
1078
- verb: "task.parent.attach",
1079
- resource: {
1080
- kind: "task",
1081
- taskId: parentTask.id,
1082
- requestedByUserId: parentTask.requestedByUserId,
1083
- creatorAgentId: parentTask.creatorAgentId,
1084
- agentId: parentTask.agentId,
1085
- },
1086
- source: "http",
1087
- }).allow
1088
- ) {
1089
- jsonError(res, `You don't have context for parent task ${parentTask.id}.`, 403);
1090
- return true;
1091
- }
1092
- // A follow-up in someone else's session needs a write share.
1093
- if (parentTask && !(await hasSessionWrite(req, myAgentId, parentTask.id))) {
1094
- jsonError(res, SESSION_WRITE_DENIED, 403);
1066
+ if (parentTask && (await denyParentAttach(req, res, myAgentId, parentTask, principal))) {
1095
1067
  return true;
1096
1068
  }
1097
1069
  // A chat-sdk contextKey binds the task to a chat thread that its assignee and creator
@@ -1,15 +1,21 @@
1
+ // `/unstable` keeps the full ACP v1 surface the 1.x default import carried:
2
+ // turn `usage` on the prompt response is unstable, and the stable entrypoint
3
+ // does not type it. Both entrypoints speak ACP v1 on the wire.
1
4
  import {
2
- type Client,
3
- ClientSideConnection,
5
+ client as acpClient,
6
+ type ClientContext,
4
7
  type McpServer,
8
+ methods,
5
9
  ndJsonStream,
6
10
  PROTOCOL_VERSION,
7
11
  type RequestPermissionRequest,
8
12
  type RequestPermissionResponse,
9
13
  type SessionConfigOption,
10
14
  type SessionNotification,
15
+ type SetSessionConfigOptionRequest,
16
+ type SetSessionConfigOptionResponse,
11
17
  type Usage,
12
- } from "@agentclientprotocol/sdk";
18
+ } from "@agentclientprotocol/sdk/unstable";
13
19
  import pkg from "../../package.json";
14
20
  import type { AcpSessionConfigOption } from "../types";
15
21
  import { mintAcpSessionToken, revokeAcpSessionToken } from "../utils/acp-session-token";
@@ -154,7 +160,7 @@ function boundedAcpLogScalar(value: unknown): unknown {
154
160
  return typeof value === "string" ? value.slice(0, 500) : value;
155
161
  }
156
162
 
157
- class SwarmAcpClient implements Client {
163
+ class SwarmAcpClient {
158
164
  /** Latest cumulative USD cost the target reported for the session. */
159
165
  reportedCostUsd: number | null = null;
160
166
 
@@ -199,7 +205,7 @@ class ACPSession implements ProviderSession {
199
205
  private ephemeralTokenId: string | null = null;
200
206
 
201
207
  constructor(
202
- private readonly connection: ClientSideConnection,
208
+ private readonly agent: ClientContext,
203
209
  private readonly client: SwarmAcpClient,
204
210
  private readonly process: Bun.Subprocess<"pipe", "pipe", "pipe">,
205
211
  private readonly config: ProviderSessionConfig,
@@ -234,7 +240,7 @@ class ACPSession implements ProviderSession {
234
240
  if (this.aborted) return;
235
241
  this.aborted = true;
236
242
  try {
237
- await this.connection.cancel({ sessionId: this.sessionId });
243
+ await this.agent.notify(methods.agent.session.cancel, { sessionId: this.sessionId });
238
244
  await Promise.race([this.completionPromise, Bun.sleep(ACP_CANCEL_SETTLE_MS)]);
239
245
  } catch (err) {
240
246
  this.emit({
@@ -282,7 +288,7 @@ class ACPSession implements ProviderSession {
282
288
  private async runPrompt(): Promise<void> {
283
289
  let result: ProviderResult;
284
290
  try {
285
- const response = await this.connection.prompt({
291
+ const response = await this.agent.request(methods.agent.session.prompt, {
286
292
  sessionId: this.sessionId,
287
293
  prompt: [{ type: "text", text: this.config.prompt }],
288
294
  });
@@ -441,11 +447,16 @@ export class ACPAdapter implements ProviderAdapter {
441
447
  else preSessionEvents.push(event);
442
448
  }, target);
443
449
  const stream = ndJsonStream(fileSinkWritableStream(proc.stdin), proc.stdout);
444
- const connection = new ClientSideConnection(() => client, stream);
450
+ const agent = acpClient()
451
+ .onRequest(methods.client.session.requestPermission, ({ params }) =>
452
+ client.requestPermission(params),
453
+ )
454
+ .onNotification(methods.client.session.update, ({ params }) => client.sessionUpdate(params))
455
+ .connect(stream).agent;
445
456
 
446
457
  let ephemeralToken: { tokenId: string; plaintext: string } | null = null;
447
458
  try {
448
- await connection.initialize({
459
+ await agent.request(methods.agent.initialize, {
449
460
  protocolVersion: PROTOCOL_VERSION,
450
461
  clientInfo: { name: "agent-swarm", version: pkg.version },
451
462
  clientCapabilities: { session: { configOptions: { boolean: {} } } },
@@ -473,7 +484,7 @@ export class ACPAdapter implements ProviderAdapter {
473
484
  // shape for an ACP agent with no network route to the swarm API. Gated on
474
485
  // `mcpCapabilities.acp` and UNSTABLE; not adopted here.
475
486
  const sessionMeta = target.sessionMeta?.(config);
476
- const newSession = await connection.newSession({
487
+ const newSession = await agent.request(methods.agent.session.new, {
477
488
  ...(sessionMeta ? { _meta: sessionMeta } : {}),
478
489
  cwd: config.cwd,
479
490
  mcpServers: [
@@ -491,13 +502,16 @@ export class ACPAdapter implements ProviderAdapter {
491
502
  ],
492
503
  });
493
504
  const configOptions = await applyConfiguredOptions(
494
- connection,
505
+ {
506
+ setSessionConfigOption: (params) =>
507
+ agent.request(methods.agent.session.setConfigOption, params),
508
+ },
495
509
  newSession.sessionId,
496
510
  newSession.configOptions ?? [],
497
511
  target.configuredOptions(config),
498
512
  );
499
513
  session = new ACPSession(
500
- connection,
514
+ agent,
501
515
  client,
502
516
  proc,
503
517
  config,
@@ -537,8 +551,14 @@ export class ACPAdapter implements ProviderAdapter {
537
551
  }
538
552
  }
539
553
 
554
+ export interface AcpConfigOptionSetter {
555
+ setSessionConfigOption(
556
+ params: SetSessionConfigOptionRequest,
557
+ ): Promise<SetSessionConfigOptionResponse>;
558
+ }
559
+
540
560
  export async function applyConfiguredOptions(
541
- connection: Pick<ClientSideConnection, "setSessionConfigOption">,
561
+ connection: AcpConfigOptionSetter,
542
562
  sessionId: string,
543
563
  advertised: SessionConfigOption[],
544
564
  configured: Record<string, string | boolean>,
@@ -3,7 +3,7 @@ import type {
3
3
  SessionNotification,
4
4
  SessionUpdate,
5
5
  ToolCallUpdate,
6
- } from "@agentclientprotocol/sdk";
6
+ } from "@agentclientprotocol/sdk/unstable";
7
7
  import type { ProviderEvent } from "./types";
8
8
 
9
9
  function contentToText(content: ContentBlock): string {
@@ -44,7 +44,7 @@ import type {
44
44
  * default; the image installs exactly this version and the per-task settings
45
45
  * set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
46
46
  */
47
- export const AMP_PACKAGE = "@ampcode/cli@0.0.1792411243-gb8cffc";
47
+ export const AMP_PACKAGE = "@ampcode/cli@0.0.1792506219-g025e7e";
48
48
 
49
49
  /** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
50
50
  const AMP_MCP_SERVER_NAME = "agent-swarm";
@@ -37,7 +37,7 @@ import {
37
37
  clampContextPercent,
38
38
  computeContextUsedUnified,
39
39
  } from "../utils/context-window";
40
- import { isEnvFlagEnabled } from "../utils/env-flag";
40
+ import { isEnvFlagEnabled, isSessionSummarySkipped } from "../utils/env-flag";
41
41
  import { SessionErrorTracker } from "../utils/error-tracker";
42
42
  import { summarizeSession as runSummarize } from "../utils/internal-ai";
43
43
  import { getMemoryRaterNames } from "../utils/memory-raters";
@@ -1739,8 +1739,8 @@ export class CodexSession implements ProviderSession {
1739
1739
  } finally {
1740
1740
  // Session-end summarization. Pure addition for codex — no behavior to
1741
1741
  // preserve. Wrapped in its own try/catch so summary failure must NOT
1742
- // block the existing log/AGENTS.md cleanup below. Gate `SKIP_SESSION_SUMMARY=1`
1743
- // matches the parity convention used by the claude Stop hook + pi/opencode.
1742
+ // block the existing log/AGENTS.md cleanup below. `SKIP_SESSION_SUMMARY` is
1743
+ // read with `isSessionSummarySkipped`, the same as the claude Stop hook + pi/opencode.
1744
1744
  //
1745
1745
  // Skip the summary entirely when the session was aborted. The transcript
1746
1746
  // is incomplete, the LLM call would retry 3× through openrouter and
@@ -1751,7 +1751,7 @@ export class CodexSession implements ProviderSession {
1751
1751
  this.aborted ||
1752
1752
  this.abortController?.signal.aborted === true ||
1753
1753
  this.pendingResult?.exitCode === 130;
1754
- if (process.env.SKIP_SESSION_SUMMARY !== "1" && !sessionWasAborted) {
1754
+ if (!isSessionSummarySkipped() && !sessionWasAborted) {
1755
1755
  try {
1756
1756
  await this.summarizeAtEnd();
1757
1757
  } catch (err) {
@@ -364,7 +364,7 @@ export async function persistCodexOAuth(
364
364
  * once it was issued more than that long ago (see {@link isStaleByAge}) — on
365
365
  * top of the near-expiry skew above, not instead of it. This is how the
366
366
  * locked keep-warm sweep (`POST /api/oauth/keep-warm/codex`) reuses this same
367
- * function to refresh slots proactively (~weekly, `maxAgeMs` ≈ 7 days) rather
367
+ * function to refresh slots proactively (`maxAgeMs` default 5 days) rather
368
368
  * than waiting for a task to draw a slot within `REFRESH_SKEW_MS` of expiry.
369
369
  * Default behavior (opts omitted) is unchanged.
370
370
  *
@@ -31,7 +31,7 @@ import {
31
31
  splitCredentialPool,
32
32
  validateOpencodeCredentials,
33
33
  } from "../utils/credentials";
34
- import { isEnvFlagEnabled } from "../utils/env-flag";
34
+ import { isEnvFlagEnabled, isSessionSummarySkipped } from "../utils/env-flag";
35
35
  import { fetchInstalledMcpServers } from "../utils/mcp-server-fetcher";
36
36
  import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
37
37
  import {
@@ -797,7 +797,7 @@ export class OpencodeSession implements ProviderSession {
797
797
  /** Identity sync, session summary, and session close (Stop-hook parity). */
798
798
  private async runSessionEnd(signal: AbortSignal): Promise<void> {
799
799
  await sessionHooks.syncIdentity(this.swarm, "session_sync", signal);
800
- if (process.env.SKIP_SESSION_SUMMARY) {
800
+ if (isSessionSummarySkipped()) {
801
801
  console.debug("session_summary skipped (opencode): SKIP_SESSION_SUMMARY is set");
802
802
  } else {
803
803
  const transcript = this.transcript.join("\n").slice(-TRANSCRIPT_TAIL_CHARS);
@@ -10,6 +10,7 @@ import type { ExtensionFactory, McpServerConfig } from "@earendil-works/pi-codin
10
10
  import { buildRatingsFromLlm, fetchRetrievalsForTask, postRatings } from "../be/memory/raters/llm";
11
11
  import { checkToolLoop, clearToolHistory } from "../hooks/tool-loop-detection";
12
12
  import { renderGoalReminder } from "../prompts/goal-reminder";
13
+ import { isSessionSummarySkipped } from "../utils/env-flag";
13
14
  import { summarizeSession as runSummarize } from "../utils/internal-ai";
14
15
  import { getMemoryRaterNames } from "../utils/memory-raters";
15
16
  import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
@@ -743,7 +744,7 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
743
744
 
744
745
  // Session summarization — get session file from context's session manager
745
746
  const sessionFile = ctx.sessionManager.getSessionFile?.();
746
- if (!process.env.SKIP_SESSION_SUMMARY) {
747
+ if (!isSessionSummarySkipped()) {
747
748
  await summarizeSessionForPi(config, sessionFile);
748
749
  } else {
749
750
  console.debug("session_summary skipped (pi): SKIP_SESSION_SUMMARY is set");
@@ -318,7 +318,7 @@ export const LEGACY_POLICY = {
318
318
  "config.write.any": leadOnly,
319
319
  "config.delete.any": leadOnly,
320
320
  "config.read.secrets": leadOnly,
321
- // HTTP runtime tokens can read their own resolved config in src/http/config.ts.
321
+ // HTTP runtime and agent tokens can read their own resolved config in src/http/config.ts.
322
322
  // Lead tokens retain config.read.secrets. This exception does not grant config administration.
323
323
  "models.catalog.write": leadOrOperator,
324
324
  "models.harness-support.write": agentOrOperator,
@@ -356,6 +356,8 @@ export const LEGACY_POLICY = {
356
356
  "runtime_pool.write": operatorOrUser,
357
357
  "runtime_pool.bind": leadOrOperatorOrUser,
358
358
  "runtime_controller.write": operatorOrUser,
359
+ // A worker token stands in for the swarm API key on a worker, so only humans mint it.
360
+ "agent.token.manage": operatorOrUser,
359
361
  "extension.write": extensionWrite,
360
362
  "extension.activate": extensionActivation,
361
363
  "extension.install.inline": leadOrOperatorOrUser,
@@ -33,6 +33,10 @@ export const PERMISSIONS = {
33
33
  description: "View or diff another agent's context history.",
34
34
  namespace: "agent",
35
35
  },
36
+ "agent.token.manage": {
37
+ description: "Mint, rotate, or revoke an agent-bound worker token.",
38
+ namespace: "agent",
39
+ },
36
40
  "task.cancel.any": {
37
41
  description: "Cancel any task (beyond tasks the caller created).",
38
42
  namespace: "task",
@@ -3,7 +3,7 @@ import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
3
  import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
- import type { Usage } from "@agentclientprotocol/sdk";
6
+ import type { Usage } from "@agentclientprotocol/sdk/unstable";
7
7
  import { normalizeSessionLogs } from "../../apps/ui/src/logs-parser";
8
8
  import {
9
9
  closeDb,
@@ -29,6 +29,7 @@ import {
29
29
  resolveAcpTarget,
30
30
  } from "../providers/acp-targets";
31
31
  import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
32
+ import { ACP_SDK_PATH, serveFakeAcpAgent } from "./acp-fake-agent";
32
33
  import { listenOnFreePort } from "./test-net";
33
34
 
34
35
  const tmpDirs: string[] = [];
@@ -148,16 +149,11 @@ describe("ACPAdapter", () => {
148
149
  }) => {
149
150
  const cwd = makeTempDir();
150
151
  const agentPath = join(cwd, "fake-acp-agent.ts");
151
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
152
152
  await Bun.write(
153
153
  agentPath,
154
154
  `
155
155
  import { Readable, Writable } from "node:stream";
156
- import {
157
- AgentSideConnection,
158
- PROTOCOL_VERSION,
159
- ndJsonStream,
160
- } from "${sdkPath}";
156
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
161
157
 
162
158
  class FakeAgent {
163
159
  constructor(connection) {
@@ -313,10 +309,7 @@ class FakeAgent {
313
309
  async cancel() {}
314
310
  }
315
311
 
316
- const input = Writable.toWeb(process.stdout);
317
- const output = Readable.toWeb(process.stdin);
318
- const stream = ndJsonStream(input, output);
319
- new AgentSideConnection((connection) => new FakeAgent(connection), stream);
312
+ ${serveFakeAcpAgent("FakeAgent")}
320
313
  `,
321
314
  );
322
315
 
@@ -952,7 +945,6 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
952
945
  const binDir = makeTempDir();
953
946
  const captureFile = join(binDir, "capture.json");
954
947
  const agentPath = join(binDir, "fake-copilot-acp.ts");
955
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
956
948
  const repoAgentsMd = "# Repo rules\nKeep this file as is.\n";
957
949
  await Bun.write(join(cwd, "AGENTS.md"), repoAgentsMd);
958
950
  await Bun.write(
@@ -961,7 +953,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
961
953
  import { readFileSync } from "node:fs";
962
954
  import { join } from "node:path";
963
955
  import { Readable, Writable } from "node:stream";
964
- import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from "${sdkPath}";
956
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
965
957
  class FakeCopilot {
966
958
  constructor(connection) { this.connection = connection; }
967
959
  async initialize() {
@@ -986,8 +978,7 @@ class FakeCopilot {
986
978
  }
987
979
  async cancel() {}
988
980
  }
989
- const stream = ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
990
- new AgentSideConnection((connection) => new FakeCopilot(connection), stream);
981
+ ${serveFakeAcpAgent("FakeCopilot")}
991
982
  `,
992
983
  );
993
984
  const copilotStub = join(binDir, "copilot");
@@ -1191,7 +1182,6 @@ new AgentSideConnection((connection) => new FakeCopilot(connection), stream);
1191
1182
  test("completes before the revoke response and logs a failed revocation", async () => {
1192
1183
  const cwd = makeTempDir();
1193
1184
  const agentPath = join(cwd, "fake-acp-ephem-agent.ts");
1194
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
1195
1185
 
1196
1186
  const captureFile = join(cwd, "captured-auth.txt");
1197
1187
 
@@ -1199,7 +1189,7 @@ new AgentSideConnection((connection) => new FakeCopilot(connection), stream);
1199
1189
  agentPath,
1200
1190
  `
1201
1191
  import { Readable, Writable } from "node:stream";
1202
- import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from "${sdkPath}";
1192
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
1203
1193
  class FakeAgent {
1204
1194
  constructor(connection) { this.connection = connection; }
1205
1195
  async initialize() {
@@ -1223,8 +1213,7 @@ class FakeAgent {
1223
1213
  }
1224
1214
  async cancel() {}
1225
1215
  }
1226
- const stream = ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
1227
- new AgentSideConnection((connection) => new FakeAgent(connection), stream);
1216
+ ${serveFakeAcpAgent("FakeAgent")}
1228
1217
  `,
1229
1218
  );
1230
1219
 
@@ -1361,12 +1350,11 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
1361
1350
  ])("$name", async ({ costs, totalCostUsd, costSource }) => {
1362
1351
  const cwd = makeTempDir();
1363
1352
  const agentPath = join(cwd, "fake-acp-cost-agent.ts");
1364
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
1365
1353
  await Bun.write(
1366
1354
  agentPath,
1367
1355
  `
1368
1356
  import { Readable, Writable } from "node:stream";
1369
- import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from "${sdkPath}";
1357
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
1370
1358
  class FakeAgent {
1371
1359
  constructor(connection) { this.connection = connection; }
1372
1360
  async initialize() {
@@ -1384,8 +1372,7 @@ class FakeAgent {
1384
1372
  }
1385
1373
  async cancel() {}
1386
1374
  }
1387
- const stream = ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
1388
- new AgentSideConnection((connection) => new FakeAgent(connection), stream);
1375
+ ${serveFakeAcpAgent("FakeAgent")}
1389
1376
  `,
1390
1377
  );
1391
1378
  const { close: closeTokenStub, apiUrl } = await startTokenStubServer(
@@ -0,0 +1,36 @@
1
+ import { join } from "node:path";
2
+
3
+ // Fake ACP targets run from temp dirs, so they import the SDK by path. The
4
+ // unstable v1 entry point lets them return turn `usage` like OpenCode does.
5
+ export const ACP_SDK_PATH = join(
6
+ process.cwd(),
7
+ "node_modules/@agentclientprotocol/sdk/dist/v1/unstable/acp.js",
8
+ );
9
+
10
+ /**
11
+ * Script tail that serves a fake agent class over stdio with the SDK's agent
12
+ * app API. The class implements the ACP agent methods and gets
13
+ * `{ sessionUpdate }` to notify the client. The script must import `agent`,
14
+ * `methods`, and `ndJsonStream` from ACP_SDK_PATH, and `Readable`/`Writable`
15
+ * from node:stream.
16
+ */
17
+ export function serveFakeAcpAgent(className: string): string {
18
+ return `
19
+ const fake = new ${className}({
20
+ sessionUpdate: (params) => connection.client.notify(methods.client.session.update, params),
21
+ });
22
+ const app = agent()
23
+ .onRequest(methods.agent.initialize, ({ params }) => fake.initialize(params))
24
+ .onRequest(methods.agent.session.new, ({ params }) => fake.newSession(params))
25
+ .onRequest(methods.agent.session.prompt, ({ params }) => fake.prompt(params))
26
+ .onNotification(methods.agent.session.cancel, ({ params }) => fake.cancel(params));
27
+ if (fake.setSessionConfigOption) {
28
+ app.onRequest(methods.agent.session.setConfigOption, ({ params }) =>
29
+ fake.setSessionConfigOption(params),
30
+ );
31
+ }
32
+ const connection = app.connect(
33
+ ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin)),
34
+ );
35
+ `;
36
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import type { SessionNotification } from "@agentclientprotocol/sdk";
2
+ import type { SessionNotification } from "@agentclientprotocol/sdk/unstable";
3
3
  import { acpReportedCostUsd, translateAcpSessionNotification } from "../providers/acp-swarm-events";
4
4
 
5
5
  function notification(update: SessionNotification["update"]): SessionNotification {