@desplega.ai/agent-swarm 1.165.0 → 1.166.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist/{acp-adapter-w6zws6me.js → acp-adapter-yzfxn8vj.js} +4 -4
  2. package/dist/{actions-jna0b3wm.js → actions-qz3m692w.js} +20 -24
  3. package/dist/{amp-adapter-0eve81pz.js → amp-adapter-j1xbmsqz.js} +4 -4
  4. package/dist/{app-75hm5cfa.js → app-43vrdmv8.js} +6 -7
  5. package/dist/{assistant-yzd51j33.js → assistant-ertffmka.js} +21 -25
  6. package/dist/{boot-reembed-z158xq32.js → boot-reembed-998jtpdg.js} +9 -12
  7. package/dist/{boot-reembed-2ex2mcsh.js → boot-reembed-s7k7c6v2.js} +10 -12
  8. package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
  9. package/dist/{boot-seeds-2bmnh18p.js → boot-seeds-vvzs1zhk.js} +8 -8
  10. package/dist/{claude-adapter-jbj1mycz.js → claude-adapter-2zvmekrp.js} +9 -11
  11. package/dist/{claude-managed-adapter-17ggz09t.js → claude-managed-adapter-s49wbk8t.js} +3 -3
  12. package/dist/{claude-managed-setup-ptm1nzm2.js → claude-managed-setup-cvhqq8qn.js} +4 -4
  13. package/dist/{claude-sdk-session-9re1g7r2.js → claude-sdk-session-kkbxtw5q.js} +2618 -2600
  14. package/dist/{cli-k6xx3pve.js → cli-0nhdwxd5.js} +4 -4
  15. package/dist/{cli-n9ehc28t.js → cli-0qjcsxgz.js} +4 -4
  16. package/dist/{cli-zn2v3t8v.js → cli-3ddybbbx.js} +1 -1
  17. package/dist/{cli-5anb0ev3.js → cli-3qn4r334.js} +71 -14
  18. package/dist/{cli-1kzfvd1z.js → cli-4r1sjfc4.js} +9 -5
  19. package/dist/{cli-ba66wyhy.js → cli-66zh26sp.js} +6 -6
  20. package/dist/{cli-9xb89xca.js → cli-68r1j4sg.js} +6 -6
  21. package/dist/{cli-xnzrzets.js → cli-8kgvmkak.js} +2 -2
  22. package/dist/{cli-28smfvbn.js → cli-8t229cmr.js} +5 -5
  23. package/dist/{cli-9866bd07.js → cli-aqcey741.js} +1 -1
  24. package/dist/{cli-90v942q1.js → cli-cpfmqz3n.js} +8 -8
  25. package/dist/{cli-50eg13rj.js → cli-df56tqdw.js} +459 -282
  26. package/dist/{cli-8gpbwy09.js → cli-dw2q9f7g.js} +5 -5
  27. package/dist/{cli-0n4fasz0.js → cli-ecknp7p1.js} +15 -15
  28. package/dist/{cli-xb3c6aat.js → cli-f2n17zhp.js} +1 -1
  29. package/dist/{cli-ftry7975.js → cli-feh3dkah.js} +1 -1
  30. package/dist/{cli-amwhs8h2.js → cli-fn697bnt.js} +2 -2
  31. package/dist/{cli-prt9emct.js → cli-gt8azyet.js} +3 -3
  32. package/dist/{cli-59jn5twa.js → cli-h3x613ze.js} +2 -2
  33. package/dist/{cli-mmzwwptd.js → cli-hf5s9pgf.js} +1 -1
  34. package/dist/cli-j2jbr8af.js +516 -0
  35. package/dist/{cli-wfajtmf7.js → cli-jhm6hzjn.js} +593 -10
  36. package/dist/{cli-xtgtyafv.js → cli-jvj6a0cv.js} +190 -439
  37. package/dist/{cli-hpryr6em.js → cli-mb75wpxf.js} +1 -1
  38. package/dist/{cli-420bdgw0.js → cli-mv2520nm.js} +1 -1
  39. package/dist/{cli-pjxnr1jn.js → cli-mwegs4af.js} +3 -3
  40. package/dist/{cli-5b2fjjzh.js → cli-p4ztb80h.js} +2 -2
  41. package/dist/{cli-e7t4snpa.js → cli-rggdq8wq.js} +4 -4
  42. package/dist/{cli-bdv39kgj.js → cli-rsy933yz.js} +5 -5
  43. package/dist/{cli-gh5yqzpe.js → cli-v2a8x45d.js} +2 -2
  44. package/dist/{cli-bns5c8k9.js → cli-vzc73wet.js} +43 -47
  45. package/dist/cli-wt7ccy4r.js +2654 -0
  46. package/dist/{cli-j0pdrmwg.js → cli-x7kpr72a.js} +3 -3
  47. package/dist/{cli-x7afk5wm.js → cli-xvc3q9br.js} +1 -1
  48. package/dist/{cli-9s3h836g.js → cli-xvdgasgy.js} +1 -1
  49. package/dist/{cli-zvckv750.js → cli-y59h2pk9.js} +2 -2
  50. package/dist/{cli-qdv8kgz7.js → cli-yshaazs1.js} +13884 -3241
  51. package/dist/{cli-nfygw434.js → cli-ysnyhws9.js} +2 -2
  52. package/dist/{cli-fw6vdcq5.js → cli-zp67qg5x.js} +34 -1
  53. package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
  54. package/dist/cli.js +24 -20
  55. package/dist/{codex-adapter-3qnxshje.js → codex-adapter-dw1wxqt8.js} +7 -7
  56. package/dist/{codex-login-brkxhhdg.js → codex-login-terbdsqq.js} +2 -2
  57. package/dist/{codex-session-runner-612wnapp.js → codex-session-runner-3cgb8y1g.js} +7 -7
  58. package/dist/{commands-2ybfcxkj.js → commands-mhxam6z2.js} +6 -6
  59. package/dist/{cursor-adapter-p3jgdehv.js → cursor-adapter-2qda3st7.js} +3 -3
  60. package/dist/{db-f4ans0jj.js → db-sjks418k.js} +8 -6
  61. package/dist/{devin-adapter-dhzt01ff.js → devin-adapter-bffjj5n4.js} +2 -2
  62. package/dist/{dispatcher-pk182apd.js → dispatcher-1es759rn.js} +18 -22
  63. package/dist/{dsh-adapter-7twaeps1.js → dsh-adapter-bek4cyw9.js} +3 -3
  64. package/dist/{e2b-y8t3jzyz.js → e2b-nd50eccg.js} +1 -1
  65. package/dist/{event-bus-w7ac4n3c.js → event-bus-vnfthafg.js} +2 -2
  66. package/dist/{grok-adapter-yt9pab3k.js → grok-adapter-ar9cw9jg.js} +5 -5
  67. package/dist/{handlers-g2wnr4t9.js → handlers-1gkbhktr.js} +21 -25
  68. package/dist/{hook-f7jt9vnm.js → hook-ds2zxq9v.js} +14 -16
  69. package/dist/{hook-bbdpnkb4.js → hook-vxa3tcsy.js} +8 -10
  70. package/dist/{http-w40q4w94.js → http-3h21z9md.js} +247 -164
  71. package/dist/{index-056j5kzn.js → index-9b65j1m5.js} +8 -8
  72. package/dist/{index-9bfssf54.js → index-bn6cy8ps.js} +18 -22
  73. package/dist/{index-cj1r9q83.js → index-dkfxpkmm.js} +19 -23
  74. package/dist/{index-7n70jwbb.js → index-f8xnqrye.js} +19 -21
  75. package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
  76. package/dist/{index-k8xc083e.js → index-n99fycq7.js} +18 -22
  77. package/dist/{keepalive-1tj10tf4.js → keepalive-3xspa9jw.js} +11 -8
  78. package/dist/{lead-st0bg7v4.js → lead-6qphc124.js} +34 -38
  79. package/dist/{maintenance-nz71zats.js → maintenance-0teem1bb.js} +7 -9
  80. package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
  81. package/dist/{oauth-refresh-sweep-aefzwvvq.js → oauth-refresh-sweep-sh9q25xp.js} +10 -7
  82. package/dist/{onboard-kgpr9mbn.js → onboard-54qmnqxe.js} +2 -2
  83. package/dist/{opencode-adapter-0c8e70dk.js → opencode-adapter-wjfa2wvd.js} +2 -2
  84. package/dist/{otel-impl-x8zp4y5a.js → otel-impl-ycqzp2a1.js} +2 -2
  85. package/dist/{pi-mono-adapter-t280ah5c.js → pi-mono-adapter-e77e9zst.js} +4 -4
  86. package/dist/{pricing-refresh-9e0q7cwk.js → pricing-refresh-atnkngqs.js} +7 -7
  87. package/dist/{rbac-roles-kcp6zge2.js → rbac-roles-9pjphh9q.js} +6 -6
  88. package/dist/{rbac-roles-790j3ga8.js → rbac-roles-j78wdksm.js} +6 -6
  89. package/dist/{render-v2-tch4nefj.js → render-v2-pxjs49fx.js} +6 -7
  90. package/dist/{setup-rcm303ns.js → setup-ymej0xtx.js} +2 -2
  91. package/dist/{work-objects-ctg7t70w.js → work-objects-3nh4ymzb.js} +22 -26
  92. package/dist/{worker-5dp0xm55.js → worker-qa9fkz5s.js} +34 -38
  93. package/dist/{x-v3yw66zs.js → x-ma4gnfgy.js} +2 -2
  94. package/openapi.json +5 -2
  95. package/package.json +9 -5
  96. package/src/be/automation-preflight.ts +9 -4
  97. package/src/be/boot-scrub-sweep.ts +302 -0
  98. package/src/be/crypto/key-bootstrap.ts +1 -1
  99. package/src/be/db/approvals.ts +15 -1
  100. package/src/be/db/codex-oauth-device-flows.ts +40 -0
  101. package/src/be/db/config-secrets.ts +31 -0
  102. package/src/be/db/runtime.ts +4 -0
  103. package/src/be/db/script-runs.ts +15 -0
  104. package/src/be/db/tasks/write.ts +1 -1
  105. package/src/be/db-queries/mcp-oauth.ts +5 -0
  106. package/src/be/db-queries/oauth.ts +77 -0
  107. package/src/be/db.ts +162 -59
  108. package/src/be/events.ts +17 -1
  109. package/src/be/memory/index-content.ts +2 -2
  110. package/src/be/memory/providers/sqlite-store.ts +63 -11
  111. package/src/be/memory/types.ts +14 -0
  112. package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
  113. package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
  114. package/src/be/scripts/db.ts +28 -4
  115. package/src/be/scripts/source-secrets.ts +37 -0
  116. package/src/be/scrub-json.ts +32 -0
  117. package/src/be/sealed-json.ts +59 -0
  118. package/src/be/secret-registry.ts +131 -0
  119. package/src/be/workflow-replay.ts +77 -0
  120. package/src/http/codex-oauth-device.ts +8 -10
  121. package/src/http/db-query-bounded.ts +3 -0
  122. package/src/http/db-query-shared.ts +107 -15
  123. package/src/http/db-query.ts +10 -2
  124. package/src/http/index.ts +23 -6
  125. package/src/http/script-runs.ts +9 -1
  126. package/src/http/scripts.ts +15 -0
  127. package/src/http/workflows.ts +3 -2
  128. package/src/oauth/mcp-wrapper.ts +1 -1
  129. package/src/providers/acp-adapter.ts +25 -11
  130. package/src/providers/acp-target-catalog.ts +35 -1
  131. package/src/providers/acp-targets.ts +69 -3
  132. package/src/providers/amp-adapter.ts +1 -1
  133. package/src/providers/cursor-adapter.ts +1 -1
  134. package/src/script-workflows/supervisor.ts +11 -2
  135. package/src/scripts-runtime/eval-harness.ts +2 -12
  136. package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
  137. package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
  138. package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
  139. package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
  140. package/src/scripts-runtime/executors/quickjs.ts +196 -0
  141. package/src/scripts-runtime/executors/registry.ts +2 -0
  142. package/src/scripts-runtime/executors/sourcemap.ts +79 -0
  143. package/src/scripts-runtime/script-error-hints.ts +14 -0
  144. package/src/server.ts +3 -1
  145. package/src/tests/acp-adapter.test.ts +127 -2
  146. package/src/tests/boot-scrub-sweep.test.ts +390 -0
  147. package/src/tests/check-text-columns.test.ts +145 -0
  148. package/src/tests/claude-sdk-process.fixture.ts +13 -2
  149. package/src/tests/codex-oauth-device.test.ts +23 -37
  150. package/src/tests/db-query-denylist.test.ts +202 -0
  151. package/src/tests/db-task-read-contract.test.ts +1 -0
  152. package/src/tests/db-task-write-contract.test.ts +1 -0
  153. package/src/tests/entrypoint-acp-target-binary.test.ts +31 -1
  154. package/src/tests/events-scrub.test.ts +88 -0
  155. package/src/tests/fixtures/secret-corpus.ts +320 -0
  156. package/src/tests/script-connections.test.ts +5 -0
  157. package/src/tests/script-executor-conformance.test.ts +3 -0
  158. package/src/tests/script-executor-quickjs.test.ts +442 -0
  159. package/src/tests/script-executor-registry.test.ts +6 -1
  160. package/src/tests/script-runs-http.test.ts +88 -1
  161. package/src/tests/script-runs-scrub.test.ts +275 -0
  162. package/src/tests/scripts-http.test.ts +82 -1
  163. package/src/tests/scrubbed-text-12e.test.ts +289 -0
  164. package/src/tests/sealed-key-guard.test.ts +166 -0
  165. package/src/tests/secret-canary-sweep.test.ts +958 -0
  166. package/src/tests/secret-registry.test.ts +355 -0
  167. package/src/tests/secret-rules.test.ts +230 -0
  168. package/src/tests/secret-scrubber.test.ts +3 -2
  169. package/src/tests/workflow-replay.test.ts +518 -0
  170. package/src/tests/workflow-scrub.test.ts +242 -0
  171. package/src/tools/db-query.ts +1 -1
  172. package/src/tools/inject-learning.ts +1 -1
  173. package/src/tools/workflows/get-workflow-run.ts +3 -2
  174. package/src/types.ts +1 -1
  175. package/src/utils/secret-rules.generated.ts +2055 -0
  176. package/src/utils/secret-scrubber.ts +363 -29
  177. package/src/workflows/engine.ts +39 -4
  178. package/src/workflows/recovery.ts +10 -1
  179. package/src/workflows/resume.ts +62 -3
  180. package/src/workflows/retry-poller.ts +3 -0
  181. package/dist/boot-scrub-logs-246yn076.js +0 -99
  182. package/dist/cli-3a4m1aws.js +0 -572
  183. package/dist/cli-a87b04k8.js +0 -951
  184. package/dist/cli-cvk6yzw5.js +0 -40
  185. package/dist/cli-h1spv8v4.js +0 -427
  186. package/dist/cli-jqt9hrnz.js +0 -168
  187. package/dist/cli-yhmwmv0g.js +0 -9281
  188. package/src/be/boot-scrub-logs.ts +0 -132
@@ -0,0 +1,196 @@
1
+ import type { QuickJSWorkerMessage, QuickJSWorkerRequest } from "./quickjs-worker";
2
+ import type { ExecutorInput, ExecutorOutput, ScriptExecutor, ScriptExecutorError } from "./types";
3
+
4
+ /**
5
+ * Opt-in executor (`SCRIPT_EXECUTOR=quickjs`) that evaluates scripts in
6
+ * QuickJS compiled to WASM, inside a small pool of worker threads. There is
7
+ * no process spawn per run, so a short script costs about a millisecond
8
+ * instead of the native floor (~10 ms on Linux, ~35 ms on macOS).
9
+ *
10
+ * The sandbox has no filesystem, no process APIs, and no network except the
11
+ * host calls that the quickjs runner exposes (ctx.swarm, ctx.api, ctx.mcp,
12
+ * fetch). Every value crosses as JSON. See quickjs-runner.ts for the limits.
13
+ */
14
+
15
+ // Each worker keeps one QuickJS WASM module (~25 ms to load) and runs one job
16
+ // at a time. Jobs beyond the pool size wait in a queue.
17
+ const POOL_SIZE = 4;
18
+ // The worker enforces the wall clock itself. This margin only catches a
19
+ // worker that stopped answering, for example a host call that never settles.
20
+ const UNRESPONSIVE_GRACE_MS = 5_000;
21
+ // A worker's WASM memory keeps the peak of its largest job (up to memoryMb).
22
+ // Recycle a worker that grew past this, so idle workers stay small.
23
+ const RECYCLE_WASM_HEAP_BYTES = 64 * 1024 * 1024;
24
+
25
+ type Slot = { worker: Worker; ready: Promise<void>; busy: boolean };
26
+
27
+ /**
28
+ * In the compiled API binary every module's `import.meta.url` is the binary
29
+ * itself, and the extra worker entrypoint lives at its path relative to the
30
+ * entrypoints' common root (`src/`). See the Dockerfile compile step.
31
+ */
32
+ function workerSpecifier(): string {
33
+ if (import.meta.url.includes("/$bunfs/")) return "./scripts-runtime/executors/quickjs-worker.ts";
34
+ return new URL("./quickjs-worker.ts", import.meta.url).href;
35
+ }
36
+
37
+ function emptyOutput(error: ScriptExecutorError, stderr = ""): ExecutorOutput {
38
+ return {
39
+ result: undefined,
40
+ stdout: "",
41
+ stderr,
42
+ truncated: { stdout: false, stderr: false },
43
+ durationMs: 0,
44
+ exitCode: 1,
45
+ error,
46
+ };
47
+ }
48
+
49
+ class QuickJSWorkerPool {
50
+ private readonly slots: Slot[] = [];
51
+ private readonly waiters: Array<(slot: Slot) => void> = [];
52
+
53
+ constructor(private readonly size: number) {}
54
+
55
+ private spawn(): Slot {
56
+ const worker = new Worker(workerSpecifier());
57
+ // Idle workers must not keep the API process (or a test run) alive.
58
+ (worker as Worker & { unref?: () => void }).unref?.();
59
+ const ready = new Promise<void>((resolve, reject) => {
60
+ const onMessage = (event: MessageEvent<QuickJSWorkerMessage>) => {
61
+ if (event.data.type === "ready") resolve();
62
+ else if (event.data.type === "fatal" && event.data.id === undefined) {
63
+ reject(new Error(event.data.message));
64
+ } else return;
65
+ worker.removeEventListener("message", onMessage);
66
+ };
67
+ worker.addEventListener("message", onMessage);
68
+ worker.addEventListener("error", (event) => reject(new Error(event.message)), { once: true });
69
+ });
70
+ // A failed boot is reported to the job that waits on `ready`.
71
+ ready.catch(() => {});
72
+ const slot: Slot = { worker, ready, busy: true };
73
+ this.slots.push(slot);
74
+ return slot;
75
+ }
76
+
77
+ acquire(): Promise<Slot> {
78
+ const idle = this.slots.find((slot) => !slot.busy);
79
+ if (idle) {
80
+ idle.busy = true;
81
+ return Promise.resolve(idle);
82
+ }
83
+ if (this.slots.length < this.size) return Promise.resolve(this.spawn());
84
+ return new Promise((resolve) => this.waiters.push(resolve));
85
+ }
86
+
87
+ release(slot: Slot): void {
88
+ const waiter = this.waiters.shift();
89
+ if (waiter) waiter(slot);
90
+ else slot.busy = false;
91
+ }
92
+
93
+ /** Terminate a worker that is in an unknown state and hand its place to a waiter. */
94
+ get workerCount(): number {
95
+ return this.slots.length;
96
+ }
97
+
98
+ discard(slot: Slot): void {
99
+ slot.worker.terminate();
100
+ const index = this.slots.indexOf(slot);
101
+ if (index !== -1) this.slots.splice(index, 1);
102
+ const waiter = this.waiters.shift();
103
+ if (waiter) waiter(this.spawn());
104
+ }
105
+ }
106
+
107
+ let sharedPool: QuickJSWorkerPool | undefined;
108
+
109
+ /** Number of live workers in the shared pool. For tests and diagnostics. */
110
+ export function quickJSWorkerCount(): number {
111
+ return sharedPool?.workerCount ?? 0;
112
+ }
113
+ let requestSeq = 0;
114
+
115
+ export class QuickJSScriptExecutor implements ScriptExecutor {
116
+ readonly name = "quickjs";
117
+
118
+ async run(input: ExecutorInput): Promise<ExecutorOutput> {
119
+ if (input.fsMode === "workspace-rw") {
120
+ return emptyOutput("executor_error", "workspace-rw not supported by quickjs executor");
121
+ }
122
+ if (input.signal?.aborted) return emptyOutput("killed");
123
+
124
+ sharedPool ??= new QuickJSWorkerPool(POOL_SIZE);
125
+ const pool = sharedPool;
126
+ const slot = await pool.acquire();
127
+ if (input.signal?.aborted) {
128
+ pool.release(slot);
129
+ return emptyOutput("killed");
130
+ }
131
+ try {
132
+ await slot.ready;
133
+ } catch (error) {
134
+ pool.discard(slot);
135
+ return emptyOutput(
136
+ "executor_error",
137
+ `quickjs worker failed to start: ${error instanceof Error ? error.message : String(error)}`,
138
+ );
139
+ }
140
+
141
+ const id = ++requestSeq;
142
+ const start = performance.now();
143
+ return await new Promise<ExecutorOutput>((resolve) => {
144
+ let settled = false;
145
+ const settle = (output: ExecutorOutput, healthy: boolean) => {
146
+ if (settled) return;
147
+ settled = true;
148
+ clearTimeout(watchdog);
149
+ slot.worker.removeEventListener("message", onMessage);
150
+ slot.worker.removeEventListener("error", onError);
151
+ input.signal?.removeEventListener("abort", onAbort);
152
+ if (healthy) pool.release(slot);
153
+ else pool.discard(slot);
154
+ resolve(output);
155
+ };
156
+ const elapsed = () => Math.round(performance.now() - start);
157
+ const onMessage = (event: MessageEvent<QuickJSWorkerMessage>) => {
158
+ const message = event.data;
159
+ if (message.type === "result" && message.id === id) {
160
+ // Reuse the worker only if every host call settled and its memory stayed small.
161
+ const reusable =
162
+ message.leakedHostCalls === 0 &&
163
+ (message.wasmHeapBytes ?? 0) <= RECYCLE_WASM_HEAP_BYTES;
164
+ settle(message.output, reusable);
165
+ } else if (message.type === "fatal" && message.id === id) {
166
+ settle(
167
+ { ...emptyOutput("executor_error", message.message), durationMs: elapsed() },
168
+ false,
169
+ );
170
+ }
171
+ };
172
+ const onError = (event: ErrorEvent) => {
173
+ settle({ ...emptyOutput("executor_error", event.message), durationMs: elapsed() }, false);
174
+ };
175
+ // Terminating the worker is the hard kill: it also stops a busy loop.
176
+ const onAbort = () => settle({ ...emptyOutput("killed"), durationMs: elapsed() }, false);
177
+ const watchdog = setTimeout(
178
+ () => settle({ ...emptyOutput("timeout"), exitCode: 124, durationMs: elapsed() }, false),
179
+ input.resources.wallClockMs + UNRESPONSIVE_GRACE_MS,
180
+ );
181
+
182
+ slot.worker.addEventListener("message", onMessage);
183
+ slot.worker.addEventListener("error", onError);
184
+ input.signal?.addEventListener("abort", onAbort, { once: true });
185
+ slot.worker.postMessage({
186
+ id,
187
+ job: {
188
+ source: input.source,
189
+ args: input.args,
190
+ configPayload: input.configPayload,
191
+ resources: input.resources,
192
+ },
193
+ } satisfies QuickJSWorkerRequest);
194
+ });
195
+ }
196
+ }
@@ -1,8 +1,10 @@
1
1
  import { NativeScriptExecutor } from "./native";
2
+ import { QuickJSScriptExecutor } from "./quickjs";
2
3
  import type { ScriptExecutor } from "./types";
3
4
 
4
5
  const EXECUTORS: Record<string, () => ScriptExecutor> = {
5
6
  native: () => new NativeScriptExecutor(),
7
+ quickjs: () => new QuickJSScriptExecutor(),
6
8
  };
7
9
 
8
10
  export function getScriptExecutor(name = process.env.SCRIPT_EXECUTOR ?? "native"): ScriptExecutor {
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Minimal source map v3 reader for the quickjs executor. It maps a
3
+ * generated (line, column) back to the original source position so runtime
4
+ * errors point at the user's TypeScript, not at the bundled JavaScript.
5
+ *
6
+ * Lines and columns are 1-based on input and output, to match stack frames.
7
+ */
8
+
9
+ type Segment = { genColumn: number; source: number; line: number; column: number };
10
+
11
+ export type OriginalPosition = { source: string; line: number; column: number };
12
+
13
+ const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
14
+ const BASE64_INDEX = new Map([...BASE64].map((char, index) => [char, index]));
15
+
16
+ function decodeVlq(segment: string): number[] {
17
+ const values: number[] = [];
18
+ let value = 0;
19
+ let shift = 0;
20
+ for (const char of segment) {
21
+ const digit = BASE64_INDEX.get(char);
22
+ if (digit === undefined) throw new Error(`invalid source map VLQ character: ${char}`);
23
+ value += (digit & 31) << shift;
24
+ if (digit & 32) {
25
+ shift += 5;
26
+ continue;
27
+ }
28
+ const negative = value & 1;
29
+ value >>>= 1;
30
+ values.push(negative ? -value : value);
31
+ value = 0;
32
+ shift = 0;
33
+ }
34
+ return values;
35
+ }
36
+
37
+ export class SourceMapReader {
38
+ private readonly sources: string[];
39
+ private readonly lines: Segment[][];
40
+
41
+ constructor(map: { sources: string[]; mappings: string }) {
42
+ this.sources = map.sources;
43
+ this.lines = [];
44
+ let source = 0;
45
+ let line = 0;
46
+ let column = 0;
47
+ for (const generatedLine of map.mappings.split(";")) {
48
+ const segments: Segment[] = [];
49
+ let genColumn = 0;
50
+ for (const raw of generatedLine.split(",")) {
51
+ if (!raw) continue;
52
+ const fields = decodeVlq(raw);
53
+ genColumn += fields[0] ?? 0;
54
+ if (fields.length < 4) continue;
55
+ source += fields[1] ?? 0;
56
+ line += fields[2] ?? 0;
57
+ column += fields[3] ?? 0;
58
+ segments.push({ genColumn, source, line, column });
59
+ }
60
+ this.lines.push(segments);
61
+ }
62
+ }
63
+
64
+ /** Look up the closest mapping at or before (line, column) on that line. */
65
+ originalPositionFor(line: number, column: number): OriginalPosition | undefined {
66
+ const segments = this.lines[line - 1];
67
+ if (!segments || segments.length === 0) return undefined;
68
+ let match: Segment | undefined;
69
+ for (const segment of segments) {
70
+ if (segment.genColumn > column - 1) break;
71
+ match = segment;
72
+ }
73
+ match ??= segments[0];
74
+ if (!match) return undefined;
75
+ const source = this.sources[match.source];
76
+ if (source === undefined) return undefined;
77
+ return { source, line: match.line + 1, column: match.column + 1 };
78
+ }
79
+ }
@@ -0,0 +1,14 @@
1
+ // Matches runtime errors that look like a swapped/misused (args, ctx) signature,
2
+ // e.g. destructuring `ctx.api`/`ctx.kv`/`ctx.fetchJson`/`ctx.log` off `undefined`/`null`
3
+ // because the script read them off `args` instead.
4
+ const CTX_UNDEFINED_ACCESS_RE = /cannot read propert(?:y|ies)(?: .*)? of (?:undefined|null)/i;
5
+ const CTX_MEMBER_RE = /\b(api|kv|fetchJson|log)\b/i;
6
+ const CTX_SIGNATURE_HINT =
7
+ "Hint: swarm scripts export default async function (args, ctx) — args comes first, ctx second.";
8
+
9
+ /** Shared by every script executor so structured errors carry the same hint. */
10
+ export function ctxSignatureHintFor(message: string): string | undefined {
11
+ return CTX_UNDEFINED_ACCESS_RE.test(message) && CTX_MEMBER_RE.test(message)
12
+ ? CTX_SIGNATURE_HINT
13
+ : undefined;
14
+ }
package/src/server.ts CHANGED
@@ -392,7 +392,9 @@ export async function createServer(
392
392
  registerResolveUserTool(server);
393
393
  registerManageUserTool(server); // self-guards with lead check
394
394
 
395
- // Debug tools (self-guard with lead check)
395
+ // Debug tools. Neither has a lead check: both are open to every
396
+ // authenticated agent. db-query refuses credential tables and its results
397
+ // are scrubbed; get-oauth-access-token returns tokens by design.
396
398
  registerDbQueryTool(server);
397
399
  registerGetOauthAccessTokenTool(server);
398
400
 
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, spyOn, test } from "bun:test";
2
- import { mkdtempSync, rmSync } from "node:fs";
2
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
3
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
@@ -22,7 +22,12 @@ import {
22
22
  sanitizeAcpConfigOptions,
23
23
  toAcpMcpServers,
24
24
  } from "../providers/acp-adapter";
25
- import { AcpTargetResolutionError, resolveAcpTarget } from "../providers/acp-targets";
25
+ import {
26
+ AcpTargetResolutionError,
27
+ COPILOT_INSTRUCTIONS_FILE,
28
+ copilotInstructionsDir,
29
+ resolveAcpTarget,
30
+ } from "../providers/acp-targets";
26
31
  import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
27
32
  import { listenOnFreePort } from "./test-net";
28
33
 
@@ -600,6 +605,126 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
600
605
  expect(target.env(config).GEMINI_SYSTEM_MD).toBeUndefined();
601
606
  });
602
607
 
608
+ test("Copilot preset forwards its own credentials, pins the model, and never forwards GITHUB_TOKEN", () => {
609
+ const config = baseConfig({
610
+ model: "openai/gpt-5.2",
611
+ env: {
612
+ PATH: "/bin",
613
+ HOME: "/home/test",
614
+ ACP_TARGET: "copilot",
615
+ COPILOT_GITHUB_TOKEN: "example-copilot-token",
616
+ COPILOT_PROVIDER_BASE_URL: "https://llm.example/v1",
617
+ COPILOT_PROVIDER_API_KEY: "example-provider-key",
618
+ COPILOT_CUSTOM_INSTRUCTIONS_DIRS: "/opt/team-instructions",
619
+ GITHUB_TOKEN: "example-git-token",
620
+ GH_TOKEN: "example-gh-token",
621
+ },
622
+ });
623
+ const target = resolveAcpTarget(config);
624
+
625
+ expect(target.target).toBe("copilot");
626
+ expect(target.command(config)).toEqual(["copilot", "--acp"]);
627
+ const env = target.env(config);
628
+ expect(env).toMatchObject({
629
+ COPILOT_GITHUB_TOKEN: "example-copilot-token",
630
+ COPILOT_PROVIDER_BASE_URL: "https://llm.example/v1",
631
+ COPILOT_PROVIDER_API_KEY: "example-provider-key",
632
+ COPILOT_MODEL: "openai/gpt-5.2",
633
+ COPILOT_AUTO_UPDATE: "false",
634
+ COPILOT_CUSTOM_INSTRUCTIONS_DIRS: `${copilotInstructionsDir(config)},/opt/team-instructions`,
635
+ });
636
+ // The worker's git token lacks the Copilot Requests permission and would
637
+ // shadow a BYOK or COPILOT_GITHUB_TOKEN setup.
638
+ expect(env.GITHUB_TOKEN).toBeUndefined();
639
+ expect(env.GH_TOKEN).toBeUndefined();
640
+ });
641
+
642
+ test("Copilot preset delivers the system prompt outside the repo and removes it after the session", async () => {
643
+ const cwd = makeTempDir();
644
+ const binDir = makeTempDir();
645
+ const captureFile = join(binDir, "capture.json");
646
+ const agentPath = join(binDir, "fake-copilot-acp.ts");
647
+ const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
648
+ const repoAgentsMd = "# Repo rules\nKeep this file as is.\n";
649
+ await Bun.write(join(cwd, "AGENTS.md"), repoAgentsMd);
650
+ await Bun.write(
651
+ agentPath,
652
+ `
653
+ import { readFileSync } from "node:fs";
654
+ import { join } from "node:path";
655
+ import { Readable, Writable } from "node:stream";
656
+ import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from "${sdkPath}";
657
+ class FakeCopilot {
658
+ constructor(connection) { this.connection = connection; }
659
+ async initialize() {
660
+ return { protocolVersion: PROTOCOL_VERSION, agentCapabilities: { loadSession: false } };
661
+ }
662
+ async newSession() {
663
+ const dir = process.env.COPILOT_CUSTOM_INSTRUCTIONS_DIRS.split(",")[0];
664
+ await Bun.write(${JSON.stringify(captureFile)}, JSON.stringify({
665
+ argv: process.argv.slice(2),
666
+ dir,
667
+ instructions: readFileSync(join(dir, ${JSON.stringify(COPILOT_INSTRUCTIONS_FILE)}), "utf8"),
668
+ model: process.env.COPILOT_MODEL,
669
+ }));
670
+ return { sessionId: "copilot-session-1" };
671
+ }
672
+ async prompt(params) {
673
+ await this.connection.sessionUpdate({
674
+ sessionId: params.sessionId,
675
+ update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: "done" }, messageId: "m1" },
676
+ });
677
+ return { stopReason: "end_turn" };
678
+ }
679
+ async cancel() {}
680
+ }
681
+ const stream = ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
682
+ new AgentSideConnection((connection) => new FakeCopilot(connection), stream);
683
+ `,
684
+ );
685
+ const copilotStub = join(binDir, "copilot");
686
+ await Bun.write(copilotStub, `#!/bin/sh\nexec "${process.execPath}" "${agentPath}" "$@"\n`);
687
+ chmodSync(copilotStub, 0o755);
688
+ mkdirSync(join(binDir, "home"));
689
+
690
+ const { server, apiUrl } = await startTokenStubServer("copilot-token-id", "example-aseph_x");
691
+ try {
692
+ const config = baseConfig({
693
+ cwd,
694
+ apiUrl,
695
+ model: "openai/gpt-5.2",
696
+ systemPrompt: "You are a swarm worker.",
697
+ env: {
698
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
699
+ HOME: join(binDir, "home"),
700
+ ACP_TARGET: "copilot",
701
+ },
702
+ });
703
+ const session = await new ACPAdapter().createSession(config);
704
+ const result = await session.waitForCompletion();
705
+ expect(result.isError).toBe(false);
706
+
707
+ const captured = JSON.parse(await Bun.file(captureFile).text()) as {
708
+ argv: string[];
709
+ dir: string;
710
+ instructions: string;
711
+ model: string;
712
+ };
713
+ expect(captured.argv).toEqual(["--acp"]);
714
+ expect(captured.dir).toBe(copilotInstructionsDir(config));
715
+ expect(captured.dir.startsWith(cwd)).toBe(false);
716
+ expect(captured.instructions).toBe('---\napplyTo: "**"\n---\n\nYou are a swarm worker.\n');
717
+ expect(captured.model).toBe("openai/gpt-5.2");
718
+ expect(await Bun.file(join(cwd, "AGENTS.md")).text()).toBe(repoAgentsMd);
719
+
720
+ const deadline = Date.now() + 2_000;
721
+ while (existsSync(captured.dir) && Date.now() < deadline) await Bun.sleep(10);
722
+ expect(existsSync(captured.dir)).toBe(false);
723
+ } finally {
724
+ await new Promise<void>((resolve) => server.close(() => resolve()));
725
+ }
726
+ });
727
+
603
728
  test("custom target passes through only explicitly named env and supports a model env fallback", () => {
604
729
  const config = baseConfig({
605
730
  model: "custom-model",