@desplega.ai/agent-swarm 2.7.0 → 2.7.1

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 (236) hide show
  1. package/dist/{acp-adapter-ma1js5n7.js → acp-adapter-8dn5tq56.js} +6 -6
  2. package/dist/{actions-1vhmtvg9.js → actions-3r6gw7dy.js} +18 -18
  3. package/dist/{amp-adapter-sxqqvmdk.js → amp-adapter-wjtq413p.js} +3 -3
  4. package/dist/{anthropic-messages-txs190dd.js → anthropic-messages-n4cby1rz.js} +4 -4
  5. package/dist/{app-mwx1d5a9.js → app-d53yhykk.js} +3 -3
  6. package/dist/{assistant-yx1p6g82.js → assistant-ksrawj3k.js} +20 -20
  7. package/dist/{boot-reembed-7h78vmvv.js → boot-reembed-fxfw17m2.js} +10 -3
  8. package/dist/{boot-reembed-5h37a2c2.js → boot-reembed-k5y5xbj0.js} +15 -8
  9. package/dist/{boot-scrub-sweep-8mawd4gw.js → boot-scrub-sweep-se41j9fd.js} +3 -3
  10. package/dist/{boot-seeds-ca84dhnn.js → boot-seeds-r3kwwv8b.js} +4 -4
  11. package/dist/{claude-adapter-wqt4hfkq.js → claude-adapter-ahmyf231.js} +7 -7
  12. package/dist/{claude-managed-adapter-6fjqtr2z.js → claude-managed-adapter-5knamegc.js} +3 -3
  13. package/dist/{claude-managed-setup-rqv98sea.js → claude-managed-setup-mdc7dy6b.js} +2 -2
  14. package/dist/{claude-sdk-session-f29f3b2x.js → claude-sdk-session-n5wen9zk.js} +8728 -8599
  15. package/dist/{cli-m5e802r7.js → cli-0dqtv2jg.js} +2 -2
  16. package/dist/{cli-e1595bgx.js → cli-0tww7dnz.js} +2 -2
  17. package/dist/{cli-2y7v133v.js → cli-0w4e4v58.js} +7 -7
  18. package/dist/{cli-d20qvpr2.js → cli-2sjm3t4r.js} +1 -1
  19. package/dist/{cli-gf2850q7.js → cli-3cywb44q.js} +18 -1
  20. package/dist/{cli-8wkb09q5.js → cli-3k9jej0c.js} +2 -2
  21. package/dist/{cli-twctnkng.js → cli-3v2cyqb8.js} +3 -3
  22. package/dist/{cli-he9gbdx0.js → cli-5qt9vzmq.js} +39 -20
  23. package/dist/{cli-a6ds2epe.js → cli-5rp1w4cr.js} +1 -1
  24. package/dist/{cli-6srv653c.js → cli-6vwjsetx.js} +4 -4
  25. package/dist/{cli-1hhbzkbb.js → cli-6w2yrrc0.js} +8 -8
  26. package/dist/{cli-f12mr20z.js → cli-75tdydgv.js} +1 -1
  27. package/dist/{cli-j3y0x2n4.js → cli-8feswf2v.js} +43 -33
  28. package/dist/{cli-07q03xm2.js → cli-8wd3mbyj.js} +1 -1
  29. package/dist/{cli-ymjpkxgn.js → cli-9csfthbw.js} +8 -6
  30. package/dist/{cli-f1e0ptjj.js → cli-b41p764a.js} +21 -2
  31. package/dist/{cli-kv7sbc2c.js → cli-c1t533hh.js} +3 -3
  32. package/dist/{cli-54sj64fc.js → cli-c28jxpcv.js} +1 -1
  33. package/dist/{cli-8mbc9za9.js → cli-cb50g9cd.js} +1 -1
  34. package/dist/{cli-c668vvcf.js → cli-d8gaphtr.js} +1 -1
  35. package/dist/{cli-86ehn4s5.js → cli-dgtfysne.js} +2 -2
  36. package/dist/{cli-sjctjr48.js → cli-dscnhn18.js} +1 -1
  37. package/dist/{cli-j0ppywdr.js → cli-dv11pgpv.js} +12 -12
  38. package/dist/{cli-1m9x8dr5.js → cli-ekptgk2m.js} +5 -5
  39. package/dist/{cli-9gsm2rzp.js → cli-f3mzy662.js} +17 -17
  40. package/dist/{cli-s0mgx214.js → cli-fk7k8s9x.js} +2 -2
  41. package/dist/{cli-vbhgkw9h.js → cli-h3e7bhvn.js} +3 -3
  42. package/dist/{cli-yd2nreqw.js → cli-hkxqdmnb.js} +2 -2
  43. package/dist/{cli-8wmj18cz.js → cli-hpkvgdad.js} +7 -4
  44. package/dist/{cli-wqr7cea4.js → cli-hq2v4rr5.js} +37 -9
  45. package/dist/{cli-sgqm2582.js → cli-k1zb7raw.js} +5 -5
  46. package/dist/{cli-4rvr7zfx.js → cli-k4awgvzm.js} +5 -0
  47. package/dist/{cli-ay40dnj3.js → cli-k7d3hvya.js} +2 -2
  48. package/dist/{cli-j9bpmykh.js → cli-kbtdftbw.js} +5 -5
  49. package/dist/{cli-x37r7qwf.js → cli-kj69b9tw.js} +25 -5
  50. package/dist/{cli-gsfk58nw.js → cli-kk9jy4nm.js} +1 -1
  51. package/dist/{cli-39aet655.js → cli-m52t4cta.js} +1 -1
  52. package/dist/{cli-h5jeg46p.js → cli-mdadewqp.js} +5 -5
  53. package/dist/{cli-a89zdqz9.js → cli-nf6k60sn.js} +1 -1
  54. package/dist/{cli-v6shg7na.js → cli-nzdf09js.js} +1 -1
  55. package/dist/{cli-rr7zx19j.js → cli-prcyzecg.js} +12 -12
  56. package/dist/{cli-6a9qfa8b.js → cli-pzafyqtk.js} +118 -48
  57. package/dist/{cli-48gxjmcz.js → cli-qvaa1gjp.js} +87 -24
  58. package/dist/{cli-qj442fws.js → cli-rg4b2g6j.js} +1 -1
  59. package/dist/{cli-aypd09nf.js → cli-rm66vqdz.js} +1 -1
  60. package/dist/{cli-9pshx6rd.js → cli-s504c3w7.js} +1 -1
  61. package/dist/{cli-ptfq202x.js → cli-swkx9jm2.js} +3 -3
  62. package/dist/{cli-m30k9777.js → cli-thsaq4pd.js} +16 -16
  63. package/dist/{cli-60sty27p.js → cli-twjnyvs8.js} +2 -2
  64. package/dist/{cli-rd5m70gj.js → cli-typdpe8g.js} +4 -4
  65. package/dist/{cli-yxbsmx56.js → cli-vg8pwmsd.js} +6 -6
  66. package/dist/{cli-s09j9vp5.js → cli-vhpckzvp.js} +1 -1
  67. package/dist/{cli-dv97kdzv.js → cli-wdxgsdz5.js} +2 -2
  68. package/dist/{cli-7a6p4e65.js → cli-wtjg6je3.js} +1 -1
  69. package/dist/{cli-b3yzyc57.js → cli-x3cngb1h.js} +44 -12
  70. package/dist/{cli-6mgdba42.js → cli-xbz6mcg4.js} +2 -2
  71. package/dist/{cli-2pvzmas1.js → cli-y4tdxsqb.js} +3 -3
  72. package/dist/{cli-f4dbh7a5.js → cli-yqrsp3ny.js} +7 -7
  73. package/dist/cli.js +27 -19
  74. package/dist/{codex-adapter-zk59x2zh.js → codex-adapter-7wepzq15.js} +5 -5
  75. package/dist/{codex-hook-xyjhvxwd.js → codex-hook-pbdh4xhx.js} +2 -2
  76. package/dist/{codex-session-runner-kh6s4j3n.js → codex-session-runner-02sb49e4.js} +5 -5
  77. package/dist/{commands-68et1qv2.js → commands-3rcspxct.js} +2 -2
  78. package/dist/{controller-fyrbnzg4.js → controller-8ar4avd6.js} +3 -3
  79. package/dist/{controllers-connect-nep9h2xv.js → controllers-connect-ajvh9h0v.js} +19 -19
  80. package/dist/{cursor-adapter-yss3pg7t.js → cursor-adapter-gd0sa7bh.js} +1 -1
  81. package/dist/{db-28gjyvxj.js → db-dv9j7ts5.js} +2 -2
  82. package/dist/{db-8ftwvzba.js → db-z26vgdvj.js} +3 -3
  83. package/dist/{dispatcher-hayexmkr.js → dispatcher-8m0htjq8.js} +17 -17
  84. package/dist/{dsh-adapter-46wawff9.js → dsh-adapter-y8gyk130.js} +1 -1
  85. package/dist/{e2b-fn3zacvt.js → e2b-74vg9b7e.js} +1 -1
  86. package/dist/{e2b-222hsajc.js → e2b-jyay8wzn.js} +1 -1
  87. package/dist/{easter-egg-3d-8d1e0h4m.js → easter-egg-3d-28f880xx.js} +1 -1
  88. package/dist/{execute-cjrfxfnm.js → execute-9seb79vh.js} +1 -1
  89. package/dist/{external-channel-xbmxcg09.js → external-channel-smz7jyth.js} +2 -2
  90. package/dist/{getMachineId-bsd-brn0xym3.js → getMachineId-bsd-whfre6gr.js} +3 -3
  91. package/dist/{getMachineId-darwin-t339sfxm.js → getMachineId-darwin-5zjynb19.js} +3 -3
  92. package/dist/{getMachineId-linux-5eq95d8b.js → getMachineId-linux-8mksrrsq.js} +3 -3
  93. package/dist/{getMachineId-unsupported-qavrtc7j.js → getMachineId-unsupported-fxtqsdt2.js} +3 -3
  94. package/dist/{getMachineId-win-q7fe1wx7.js → getMachineId-win-nzfmz857.js} +3 -3
  95. package/dist/{grok-adapter-ag4wks2a.js → grok-adapter-z0nnvtbn.js} +7 -7
  96. package/dist/handlers-pspryh6g.js +41 -0
  97. package/dist/{heartbeat-psfvcgwp.js → heartbeat-f928aqyp.js} +20 -20
  98. package/dist/{hook-cyq4mryn.js → hook-d7c605r4.js} +7 -7
  99. package/dist/{hook-8pz04697.js → hook-q9hv5xf9.js} +5 -5
  100. package/dist/{http-haj1nd6k.js → http-q6j4k989.js} +934 -180
  101. package/dist/{index-z5h22pes.js → index-5nb266jr.js} +17 -17
  102. package/dist/{index-ysd45kgc.js → index-8fxt5eah.js} +10 -10
  103. package/dist/{index-zgvqzjsw.js → index-cekzwhez.js} +5 -5
  104. package/dist/{index-zyq8ct52.js → index-ch0c1xff.js} +17 -17
  105. package/dist/{index-syvhnvtw.js → index-ebnjhxh1.js} +1 -1
  106. package/dist/{index-y1ajx04a.js → index-ed8dy8rn.js} +20 -20
  107. package/dist/{index-jwt2bj6f.js → index-ejqdkkqx.js} +11 -10
  108. package/dist/{index-dr94zbnh.js → index-farm1zsn.js} +32661 -29443
  109. package/dist/{index-q52f47pe.js → index-yn1yxg85.js} +1 -1
  110. package/dist/{keepalive-xdvsamvj.js → keepalive-zzc3hks1.js} +3 -3
  111. package/dist/{lead-hqg57kdp.js → lead-c0pjcaw4.js} +36 -36
  112. package/dist/{maintenance-4y2xzh3a.js → maintenance-kqsxzpzx.js} +1 -1
  113. package/dist/{oauth-refresh-sweep-jd4r7zbs.js → oauth-refresh-sweep-7qt0g55z.js} +2 -2
  114. package/dist/{onboard-18447q9q.js → onboard-k7x13fqg.js} +118 -5
  115. package/dist/{opencode-adapter-26b2frdf.js → opencode-adapter-tqvrtter.js} +5 -5
  116. package/dist/{otel-impl-f9tgr8yw.js → otel-impl-sqkf6k3b.js} +97 -97
  117. package/dist/{pi-mono-adapter-qn6qbga1.js → pi-mono-adapter-m2qnafxm.js} +63 -9
  118. package/dist/{pricing-refresh-dw2tgb53.js → pricing-refresh-wkpwhgvs.js} +3 -3
  119. package/dist/{rbac-roles-s4nwj2m3.js → rbac-roles-dpn0c0wj.js} +7 -4
  120. package/dist/{rbac-roles-aygm2760.js → rbac-roles-wk7xd6a6.js} +2 -2
  121. package/dist/{render-v2-yfvdfr0x.js → render-v2-ex2m4g2k.js} +3 -3
  122. package/dist/{resume-1bmvjbf3.js → resume-725g9b0b.js} +7 -7
  123. package/dist/{runtime-f7tpzedy.js → runtime-kajas15g.js} +1 -1
  124. package/dist/{session-access-r5garej6.js → session-access-0j2ezjje.js} +8 -4
  125. package/dist/{session-source-backfill-424wzv0r.js → session-source-backfill-62202awe.js} +2 -2
  126. package/dist/{setup-8tygvxkq.js → setup-9sj11ax6.js} +1 -1
  127. package/dist/{virtual-modules-xsw323b3.js → virtual-modules-c6fcq6zr.js} +2 -2
  128. package/dist/{work-objects-ggnghpgf.js → work-objects-0y07495y.js} +20 -20
  129. package/dist/{worker-sqjd1nqf.js → worker-d4q9sqbc.js} +36 -36
  130. package/openapi.json +1070 -9
  131. package/package.json +3 -3
  132. package/src/be/connector-codes.ts +65 -22
  133. package/src/be/memory/boot-reembed.ts +4 -1
  134. package/src/be/memory/constants.ts +6 -0
  135. package/src/be/memory/embedding-recovery.ts +48 -0
  136. package/src/be/memory/index-content.ts +5 -2
  137. package/src/be/memory/providers/openai-embedding.ts +65 -14
  138. package/src/be/memory/task-learning.ts +110 -8
  139. package/src/be/memory/types.ts +13 -2
  140. package/src/be/migrations/246_connector_code_kind.sql +11 -0
  141. package/src/be/onboarding.ts +125 -15
  142. package/src/be/rbac-roles.ts +4 -1
  143. package/src/be/scripts/embeddings.ts +2 -2
  144. package/src/be/seed/agent-fs-provision.ts +75 -14
  145. package/src/be/swarm-config-guard.ts +3 -0
  146. package/src/cli.tsx +9 -0
  147. package/src/comb/config.ts +6 -1
  148. package/src/commands/onboard/agent-fs-version.ts +5 -0
  149. package/src/commands/onboard/compose-generator.ts +112 -1
  150. package/src/commands/onboard/env-generator.ts +9 -0
  151. package/src/commands/onboard/tei-image.ts +12 -0
  152. package/src/commands/onboard/types.ts +7 -0
  153. package/src/commands/onboard.tsx +11 -1
  154. package/src/commands/runner.ts +14 -1
  155. package/src/http/agentfs-proxy.ts +369 -0
  156. package/src/http/all-routes.ts +1 -0
  157. package/src/http/codex-oauth-device.ts +8 -1
  158. package/src/http/fs.ts +64 -10
  159. package/src/http/inbox-state.ts +27 -1
  160. package/src/http/index.ts +4 -1
  161. package/src/http/memory.ts +7 -4
  162. package/src/http/onboarding.ts +139 -11
  163. package/src/http/session-access.ts +24 -0
  164. package/src/http/sessions.ts +10 -6
  165. package/src/http/tasks.ts +85 -11
  166. package/src/http/telemetry-ui.ts +1 -1
  167. package/src/http/user-roles.ts +4 -1
  168. package/src/http/users.ts +272 -3
  169. package/src/http/utils.ts +22 -3
  170. package/src/prompts/base-prompt.ts +24 -9
  171. package/src/prompts/session-templates.ts +26 -2
  172. package/src/providers/amp-adapter.ts +1 -1
  173. package/src/providers/codex-adapter.ts +6 -1
  174. package/src/providers/cursor-adapter.ts +1 -1
  175. package/src/providers/pi-mono-adapter.ts +58 -3
  176. package/src/rbac/legacy-policy.ts +1 -0
  177. package/src/rbac/permissions.ts +4 -0
  178. package/src/tests/agent-fs-provision-seeder.test.ts +261 -92
  179. package/src/tests/agentfs-proxy.test.ts +1106 -0
  180. package/src/tests/base-prompt.test.ts +69 -2
  181. package/src/tests/codex-adapter.test.ts +46 -8
  182. package/src/tests/codex-oauth-device.test.ts +12 -1
  183. package/src/tests/comb-config-guard.test.ts +20 -0
  184. package/src/tests/comb-links.test.ts +79 -1
  185. package/src/tests/comb-status.test.ts +44 -0
  186. package/src/tests/docs-index-footprint.test.js +3 -3
  187. package/src/tests/fixtures/onboard/baseline-states.ts +68 -0
  188. package/src/tests/fixtures/onboard/content.compose.yml.txt +190 -0
  189. package/src/tests/fixtures/onboard/content.env.txt +29 -0
  190. package/src/tests/fixtures/onboard/dev.compose.yml.txt +155 -0
  191. package/src/tests/fixtures/onboard/dev.env.txt +28 -0
  192. package/src/tests/fixtures/onboard/full-all.compose.yml.txt +582 -0
  193. package/src/tests/fixtures/onboard/full-all.env.txt +59 -0
  194. package/src/tests/fixtures/onboard/full.compose.yml.txt +435 -0
  195. package/src/tests/fixtures/onboard/full.env.txt +36 -0
  196. package/src/tests/fixtures/onboard/research.compose.yml.txt +155 -0
  197. package/src/tests/fixtures/onboard/research.env.txt +28 -0
  198. package/src/tests/fixtures/onboard/solo.compose.yml.txt +85 -0
  199. package/src/tests/fixtures/onboard/solo.env.txt +26 -0
  200. package/src/tests/http-log-scrubbing.test.ts +34 -1
  201. package/src/tests/http-task-write-ownership.test.ts +5 -1
  202. package/src/tests/invites-http.test.ts +464 -0
  203. package/src/tests/memory-embedding-input-cap.test.ts +122 -1
  204. package/src/tests/memory-embedding-recovery.test.ts +137 -0
  205. package/src/tests/onboard-compose.test.ts +142 -0
  206. package/src/tests/onboard-env.test.ts +24 -0
  207. package/src/tests/onboard-non-interactive.test.ts +29 -0
  208. package/src/tests/onboard-tei-image.test.ts +36 -0
  209. package/src/tests/onboarding.test.ts +451 -4
  210. package/src/tests/openai-embedding-provider.test.ts +105 -2
  211. package/src/tests/pi-mono-session-cost-on-abort.test.ts +270 -0
  212. package/src/tests/preload-env.test.ts +4 -0
  213. package/src/tests/preload.ts +4 -0
  214. package/src/tests/prompt-template-session.test.ts +19 -3
  215. package/src/tests/rbac-admission-e2e.test.ts +22 -6
  216. package/src/tests/rbac-admission.test.ts +342 -4
  217. package/src/tests/rbac-engine.test.ts +1 -0
  218. package/src/tests/rbac-roles.test.ts +9 -1
  219. package/src/tests/send-task-requester-gate.test.ts +64 -3
  220. package/src/tests/sessions-move.test.ts +39 -1
  221. package/src/tests/store-progress-learning.test.ts +95 -2
  222. package/src/tests/whoami-route.test.ts +41 -0
  223. package/src/tools/inject-learning.ts +1 -1
  224. package/src/tools/memory-edit.ts +1 -1
  225. package/src/tools/memory-search.ts +1 -1
  226. package/src/tools/store-progress.ts +5 -1
  227. package/src/tools/utils.ts +12 -1
  228. package/src/utils/constants.ts +39 -0
  229. package/src/utils/task-attachment-links.ts +15 -3
  230. package/templates/skills/swarm-docs/content.md +1 -1
  231. package/dist/handlers-tzmevqb5.js +0 -41
  232. package/dist/{azure-openai-responses-9kfwhmn7.js → azure-openai-responses-43asa23s.js} +0 -0
  233. package/dist/{node-vxx896fa.js → node-t28cc56b.js} +0 -0
  234. package/dist/{openai-codex-responses-79s8xnbh.js → openai-codex-responses-jhr3sjtm.js} +3 -3
  235. package/dist/{openai-completions-tdbyts0x.js → openai-completions-gs6yn37k.js} +3 -3
  236. /package/dist/{openai-responses-dm3t5vqp.js → openai-responses-jqm9vwwk.js} +0 -0
@@ -0,0 +1,270 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
5
+ import {
6
+ type ReconcileActiveTasksOptions,
7
+ type RunnerState,
8
+ type RunningTask,
9
+ reconcileActiveTasks,
10
+ } from "../commands/runner";
11
+ import { PiMonoSession } from "../providers/pi-mono-adapter";
12
+ import type { CostData, ProviderEvent, ProviderSessionConfig } from "../providers/types";
13
+
14
+ type AgentSessionEvent = Parameters<Parameters<AgentSession["subscribe"]>[0]>[0];
15
+
16
+ const tmpLogDir = `/tmp/pi-mono-cost-abort-test-${Date.now()}`;
17
+
18
+ beforeAll(() => {
19
+ mkdirSync(tmpLogDir, { recursive: true });
20
+ });
21
+
22
+ afterAll(() => {
23
+ rmSync(tmpLogDir, { recursive: true, force: true });
24
+ });
25
+
26
+ function makeSessionConfig(logFile: string): ProviderSessionConfig {
27
+ return {
28
+ prompt: "Coordinate sub-agents and complete the lead task",
29
+ systemPrompt: "",
30
+ model: "anthropic/claude-opus-4-6",
31
+ role: "lead",
32
+ agentId: "lead-agent-id",
33
+ taskId: "lead-task-id",
34
+ apiUrl: "http://localhost:3013",
35
+ apiKey: "example-test-key",
36
+ cwd: "/tmp",
37
+ logFile,
38
+ iteration: 1,
39
+ };
40
+ }
41
+
42
+ describe("PiMonoSession & runner — long lead session cost preservation (#2073)", () => {
43
+ test("abort() unblocks waitForIdle() and preserves cost after terminal store-progress", async () => {
44
+ const listeners: Array<(event: AgentSessionEvent) => void> = [];
45
+ const mockSession = {
46
+ sessionId: "lead-pi-session-1",
47
+ isStreaming: true,
48
+ model: { provider: "anthropic", id: "claude-opus-4-6" },
49
+ subscribe(listener: (event: AgentSessionEvent) => void) {
50
+ listeners.push(listener);
51
+ return () => {
52
+ const idx = listeners.indexOf(listener);
53
+ if (idx >= 0) listeners.splice(idx, 1);
54
+ };
55
+ },
56
+ async prompt() {
57
+ for (const l of listeners) {
58
+ l({
59
+ type: "tool_execution_start",
60
+ toolCallId: "sp-1",
61
+ toolName: "agent-swarm_store-progress",
62
+ args: { status: "completed", output: "All sub-tasks merged" },
63
+ } as unknown as AgentSessionEvent);
64
+ l({
65
+ type: "tool_execution_end",
66
+ toolCallId: "sp-1",
67
+ toolName: "agent-swarm_store-progress",
68
+ result: { success: true },
69
+ isError: false,
70
+ } as unknown as AgentSessionEvent);
71
+ }
72
+ // Simulate a streaming turn that never emits agent_end before runner aborts it
73
+ },
74
+ getContextUsage: () => null,
75
+ getSessionStats: () => ({
76
+ tokens: { input: 42000, output: 3800, cacheRead: 120000, cacheWrite: 15000, total: 180800 },
77
+ cost: 1.85,
78
+ userMessages: 1,
79
+ assistantMessages: 14,
80
+ }),
81
+ abort: async () => {},
82
+ dispose: () => {},
83
+ } as unknown as AgentSession;
84
+
85
+ const logFile = join(tmpLogDir, `lead-abort-idle-${Date.now()}.log`);
86
+ const session = new PiMonoSession(mockSession, makeSessionConfig(logFile), false);
87
+ const events: ProviderEvent[] = [];
88
+ session.onEvent((e) => events.push(e));
89
+
90
+ // Allow prompt() to emit store-progress and enter waitForIdle()
91
+ await Bun.sleep(20);
92
+
93
+ // Mid-session result snapshot should already be emitted on terminal store-progress
94
+ const resultEventsBeforeAbort = events.filter((e) => e.type === "result");
95
+ expect(resultEventsBeforeAbort.length).toBeGreaterThanOrEqual(1);
96
+
97
+ // Runner reconciliation aborts the session because the task is now completed server-side
98
+ await session.abort();
99
+
100
+ const result = await session.waitForCompletion();
101
+ expect(result.exitCode).toBe(0);
102
+ expect(result.isError).toBe(false);
103
+ expect(result.cost).toBeDefined();
104
+ expect(result.cost?.totalCostUsd).toBe(1.85);
105
+ expect(result.cost?.inputTokens).toBe(42000);
106
+ expect(result.cost?.outputTokens).toBe(3800);
107
+ expect(result.cost?.cacheReadTokens).toBe(120000);
108
+ expect(result.cost?.cacheWriteTokens).toBe(15000);
109
+ expect(result.cost?.numTurns).toBe(15);
110
+ expect(result.cost?.provider).toBe("pi");
111
+ });
112
+
113
+ test("prompt() rejection on abort after terminal store-progress still returns cost and exitCode 0", async () => {
114
+ const listeners: Array<(event: AgentSessionEvent) => void> = [];
115
+ let rejectPrompt: (err: Error) => void = () => {};
116
+ const promptBlocked = new Promise<void>((_, reject) => {
117
+ rejectPrompt = reject;
118
+ });
119
+
120
+ const mockSession = {
121
+ sessionId: "lead-pi-session-2",
122
+ isStreaming: true,
123
+ model: { provider: "anthropic", id: "claude-opus-4-6" },
124
+ subscribe(listener: (event: AgentSessionEvent) => void) {
125
+ listeners.push(listener);
126
+ return () => {
127
+ const idx = listeners.indexOf(listener);
128
+ if (idx >= 0) listeners.splice(idx, 1);
129
+ };
130
+ },
131
+ async prompt() {
132
+ for (const l of listeners) {
133
+ l({
134
+ type: "tool_execution_start",
135
+ toolCallId: "sp-2",
136
+ toolName: "store-progress",
137
+ args: { status: "completed", output: "Done" },
138
+ } as unknown as AgentSessionEvent);
139
+ l({
140
+ type: "tool_execution_end",
141
+ toolCallId: "sp-2",
142
+ toolName: "store-progress",
143
+ result: { ok: true },
144
+ isError: false,
145
+ } as unknown as AgentSessionEvent);
146
+ }
147
+ await promptBlocked;
148
+ },
149
+ getContextUsage: () => null,
150
+ getSessionStats: () => ({
151
+ tokens: { input: 18500, output: 2100, cacheRead: 50000, cacheWrite: 6000, total: 76600 },
152
+ cost: 0.92,
153
+ userMessages: 1,
154
+ assistantMessages: 8,
155
+ }),
156
+ abort: async () => {
157
+ rejectPrompt(new Error("Request aborted"));
158
+ },
159
+ dispose: () => {},
160
+ } as unknown as AgentSession;
161
+
162
+ const logFile = join(tmpLogDir, `lead-abort-throw-${Date.now()}.log`);
163
+ const session = new PiMonoSession(mockSession, makeSessionConfig(logFile), false);
164
+
165
+ await Bun.sleep(20);
166
+ await session.abort();
167
+
168
+ const result = await session.waitForCompletion();
169
+ expect(result.exitCode).toBe(0);
170
+ expect(result.isError).toBe(false);
171
+ expect(result.cost).toBeDefined();
172
+ expect(result.cost?.totalCostUsd).toBe(0.92);
173
+ expect(result.cost?.inputTokens).toBe(18500);
174
+ expect(result.cost?.outputTokens).toBe(2100);
175
+ });
176
+
177
+ test("unexpected throw in runSession attaches partial session cost with isError: true", async () => {
178
+ const mockSession = {
179
+ sessionId: "lead-pi-session-3",
180
+ isStreaming: false,
181
+ model: { provider: "anthropic", id: "claude-opus-4-6" },
182
+ subscribe: () => () => {},
183
+ async prompt() {
184
+ throw new Error("MCP transport disconnected after 10 turns");
185
+ },
186
+ getContextUsage: () => null,
187
+ getSessionStats: () => ({
188
+ tokens: { input: 9000, output: 1100, cacheRead: 4000, cacheWrite: 1000, total: 15100 },
189
+ cost: 0.41,
190
+ userMessages: 1,
191
+ assistantMessages: 5,
192
+ }),
193
+ abort: async () => {},
194
+ dispose: () => {},
195
+ } as unknown as AgentSession;
196
+
197
+ const logFile = join(tmpLogDir, `lead-unexpected-throw-${Date.now()}.log`);
198
+ const session = new PiMonoSession(mockSession, makeSessionConfig(logFile), false);
199
+
200
+ const result = await session.waitForCompletion();
201
+ expect(result.exitCode).toBe(1);
202
+ expect(result.isError).toBe(true);
203
+ expect(result.cost).toBeDefined();
204
+ expect(result.cost?.isError).toBe(true);
205
+ expect(result.cost?.totalCostUsd).toBe(0.41);
206
+ expect(result.cost?.inputTokens).toBe(9000);
207
+ });
208
+
209
+ test("reconcileActiveTasks preserves task.lastCost when settling an unresponsive aborted session", async () => {
210
+ const snapshotCost: CostData = {
211
+ sessionId: "runner-session-1",
212
+ taskId: "task-2073",
213
+ agentId: "lead-agent-id",
214
+ totalCostUsd: 2.34,
215
+ inputTokens: 64000,
216
+ outputTokens: 5200,
217
+ cacheReadTokens: 150000,
218
+ cacheWriteTokens: 20000,
219
+ durationMs: 1_200_000,
220
+ numTurns: 22,
221
+ model: "anthropic/claude-opus-4-6",
222
+ isError: false,
223
+ provider: "pi",
224
+ };
225
+
226
+ const task: RunningTask = {
227
+ taskId: "task-2073",
228
+ session: {
229
+ sessionId: "pi-session-2073",
230
+ onEvent: () => {},
231
+ waitForCompletion: () => new Promise(() => {}),
232
+ abort: async () => {},
233
+ },
234
+ logFile: "/tmp/task-2073.log",
235
+ startTime: new Date(1_000),
236
+ promise: new Promise(() => {}),
237
+ result: null,
238
+ harnessProvider: "pi",
239
+ hasLocalEnvironment: true,
240
+ abortRequestedAt: 10_000,
241
+ serverTerminalStatus: "completed",
242
+ lastCost: { value: snapshotCost },
243
+ };
244
+
245
+ const state: RunnerState = {
246
+ activeTasks: new Map([["task-2073", task]]),
247
+ deliveryMode: "poll",
248
+ maxConcurrent: 2,
249
+ startedAt: 0,
250
+ tasksProcessed: 0,
251
+ harnessProvider: "pi",
252
+ codexCreditsExhaustedCooldownMs: 60_000,
253
+ modelWindowBlocks: new Map(),
254
+ };
255
+
256
+ const opts: ReconcileActiveTasksOptions = {
257
+ cancelledSignaled: new Set(),
258
+ isCancelled: async () => false,
259
+ fetchStatus: async () => "completed",
260
+ intervalMs: 30_000,
261
+ abortGraceMs: 10_000,
262
+ now: () => 25_000,
263
+ };
264
+
265
+ await reconcileActiveTasks(state, "lead", opts);
266
+
267
+ expect(task.result).not.toBeNull();
268
+ expect(task.result?.cost).toEqual(snapshotCost);
269
+ });
270
+ });
@@ -16,11 +16,15 @@ test(
16
16
  "COMB_ENABLED",
17
17
  "APP_URL",
18
18
  "AGENT_FS_API_URL",
19
+ "AGENT_FS_PROXY",
19
20
  "AGENT_FS_LIVE_URL",
21
+ "AGENT_FS_LINK_TARGET",
20
22
  "AGENT_FS_DEFAULT_ORG_ID",
21
23
  "AGENT_FS_DEFAULT_DRIVE_ID",
22
24
  "SWARM_ORG_NAME",
23
25
  "SWARM_ORG_WEBSITE",
26
+ "EMBEDDING_QUERY_PREFIX",
27
+ "EMBEDDING_DOCUMENT_PREFIX",
24
28
  ];
25
29
  try {
26
30
  await Bun.write(
@@ -32,11 +32,15 @@ for (const key of Object.keys(process.env)) {
32
32
  "COMB_ENABLED",
33
33
  "APP_URL",
34
34
  "AGENT_FS_API_URL",
35
+ "AGENT_FS_PROXY",
35
36
  "AGENT_FS_LIVE_URL",
37
+ "AGENT_FS_LINK_TARGET",
36
38
  "AGENT_FS_DEFAULT_ORG_ID",
37
39
  "AGENT_FS_DEFAULT_DRIVE_ID",
38
40
  "SWARM_ORG_NAME",
39
41
  "SWARM_ORG_WEBSITE",
42
+ "EMBEDDING_QUERY_PREFIX",
43
+ "EMBEDDING_DOCUMENT_PREFIX",
40
44
  ].includes(key)
41
45
  ) {
42
46
  delete process.env[key];
@@ -41,6 +41,8 @@ const SYSTEM_TEMPLATES = [
41
41
  "system.agent.tool_preload",
42
42
  "system.agent.tool_discovery.search",
43
43
  "system.agent.tool_discovery.amp",
44
+ "system.agent.tool_discovery.codex",
45
+ "system.agent.tool_discovery.opencode",
44
46
  "system.agent.tool_discovery.direct",
45
47
  "system.agent.tools_skills",
46
48
  "system.agent.worker",
@@ -119,11 +121,11 @@ describe("Session templates: registration", () => {
119
121
  expect(sessionSystemEventTypes("session")).toEqual([...SESSION_TEMPLATES].sort());
120
122
  });
121
123
 
122
- test("registers 33 system and session templates in total", () => {
124
+ test("registers 35 system and session templates in total", () => {
123
125
  const all = getAllTemplateDefinitions();
124
126
  const sessionSystem = all.filter((d) => d.category === "system" || d.category === "session");
125
- // 28 system blocks + 5 session composites.
126
- expect(sessionSystem.length).toBe(33);
127
+ // 30 system blocks + 5 session composites.
128
+ expect(sessionSystem.length).toBe(35);
127
129
  });
128
130
 
129
131
  test("drops the v1 blocks that prompt v2 deleted", () => {
@@ -246,6 +248,20 @@ describe("Session templates: MUST pointers", () => {
246
248
  expect(result.text).toContain("`implementing` skill");
247
249
  expect(result.text).not.toContain("/researching");
248
250
  });
251
+
252
+ test("the outputs block takes its file-link base from fileLinkBase", () => {
253
+ const definition = getAllTemplateDefinitions().find(
254
+ (d) => d.eventType === "system.agent.outputs",
255
+ );
256
+ expect(definition?.variables.map((v) => v.name)).toEqual(["fileLinkBase"]);
257
+ const result = resolveTemplate("system.agent.outputs", {
258
+ fileLinkBase: "http://localhost:5274",
259
+ });
260
+ expect(result.text).toContain(
261
+ "Give people the live app link `http://localhost:5274/file/~/<org>/<drive>/<path>`.",
262
+ );
263
+ expect(result.unresolved).toEqual([]);
264
+ });
249
265
  });
250
266
 
251
267
  // ============================================================================
@@ -247,12 +247,20 @@ describe("RBAC admission over real HTTP", () => {
247
247
 
248
248
  rewriteUserToRequester(dbPath, userId);
249
249
 
250
- const deniedTaskCreate = await api(server.base, "POST", "/api/tasks", {
250
+ // A requester creates its own task (`task.create.own`); a verb-less write
251
+ // stays operator-only.
252
+ const requesterTaskCreate = await api(server.base, "POST", "/api/tasks", {
251
253
  bearer: userToken,
252
- body: { task: "rbac admission denied task" },
254
+ body: { task: "rbac admission requester task" },
253
255
  });
254
- expect(deniedTaskCreate.status).toBe(403);
255
- expect(deniedTaskCreate.body.error).toContain("admission: route has no permission verb");
256
+ expect(requesterTaskCreate.status).toBe(201);
257
+
258
+ const deniedDbQuery = await api(server.base, "POST", "/api/db-query", {
259
+ bearer: userToken,
260
+ body: { sql: "select 1" },
261
+ });
262
+ expect(deniedDbQuery.status).toBe(403);
263
+ expect(deniedDbQuery.body.error).toContain("admission: route has no permission verb");
256
264
 
257
265
  // The ranking state carries the private claim: a requester cannot read it.
258
266
  const deniedRanking = await api(server.base, "GET", "/api/ranking", { bearer: userToken });
@@ -378,8 +386,8 @@ describe("RBAC admission over real HTTP", () => {
378
386
  "GET /api/ranking",
379
387
  "GET /api/scripts/{id}/apis/{endpointId}/secret",
380
388
  "PATCH /api/assets/app/{id}/key",
389
+ "POST /api/db-query",
381
390
  "POST /api/scripts/{id}/apis",
382
- "POST /api/tasks",
383
391
  ].every((resourceId) =>
384
392
  userRows.some((row) => row.resourceId === resourceId && row.decision === "deny"),
385
393
  ) &&
@@ -410,9 +418,17 @@ describe("RBAC admission over real HTTP", () => {
410
418
  "GET /api/ranking",
411
419
  "GET /api/scripts/{id}/apis/{endpointId}/secret",
412
420
  "PATCH /api/assets/app/{id}/key",
421
+ "POST /api/db-query",
413
422
  "POST /api/scripts/{id}/apis",
414
- "POST /api/tasks",
415
423
  ]);
424
+ expect(
425
+ userHttpRows.some(
426
+ (row) =>
427
+ row.resourceId === "POST /api/tasks" &&
428
+ row.verb === "task.create.own" &&
429
+ row.decision === "allow",
430
+ ),
431
+ ).toBe(true);
416
432
  expect(
417
433
  denyRows.some(
418
434
  (row) =>