@band-ai/sdk 0.3.4 → 0.4.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 (97) hide show
  1. package/README.md +54 -1
  2. package/dist/{ClaudeSDKAdapter-CXud2DBE.d.ts → CopilotACPAdapter-B_AVyAl6.d.ts} +249 -19
  3. package/dist/{ClaudeSDKAdapter-Cx6zhSaG.d.cts → CopilotACPAdapter-Clid9xGR.d.cts} +249 -19
  4. package/dist/{opencode-2YdscIc1.d.cts → acp-client-D-I_5lK-.d.cts} +23 -4
  5. package/dist/{opencode-Bld62x5t.d.ts → acp-client-DUXyczlF.d.ts} +23 -4
  6. package/dist/acp-server-BlcYMxKt.d.ts +17 -0
  7. package/dist/acp-server-CJhclo6P.d.cts +17 -0
  8. package/dist/adapters.cjs +2257 -969
  9. package/dist/adapters.d.cts +21 -132
  10. package/dist/adapters.d.ts +21 -132
  11. package/dist/adapters.js +26 -1922
  12. package/dist/{backends-D-dBdDm-.d.ts → backends-CgsPQa9B.d.ts} +2 -2
  13. package/dist/{backends-ZLxmQrTZ.d.cts → backends-Dh2WXcHQ.d.cts} +2 -2
  14. package/dist/{chunk-JY75VSQK.js → chunk-4OVNFDD7.js} +14 -1
  15. package/dist/chunk-6BE2EG5P.js +128 -0
  16. package/dist/{chunk-U6AFCLYZ.js → chunk-6TGXV5TE.js} +1824 -514
  17. package/dist/{chunk-OH6VVVKT.js → chunk-CBCHTTUY.js} +10 -0
  18. package/dist/{chunk-K5KAEYPF.js → chunk-HNWZZXAE.js} +288 -120
  19. package/dist/chunk-I34SXMTJ.js +6 -0
  20. package/dist/{chunk-XWHWJP4I.js → chunk-JDW5WSGF.js} +2 -2
  21. package/dist/{chunk-KXJ74J73.js → chunk-JIZE4EC6.js} +4 -10
  22. package/dist/chunk-OGOXY43L.js +119 -0
  23. package/dist/{chunk-W5IAI6Z4.js → chunk-OVXVZUPO.js} +5 -5
  24. package/dist/{chunk-YJ42AZO4.js → chunk-RJNJUISR.js} +5 -2
  25. package/dist/{chunk-MQY74R7D.js → chunk-SHZBLLDQ.js} +21 -9
  26. package/dist/{chunk-JADCAMZY.js → chunk-TGDDFBYS.js} +45 -0
  27. package/dist/{chunk-US226AS4.js → chunk-UENIC5FJ.js} +14 -11
  28. package/dist/{chunk-MQBBWYRP.js → chunk-UFO6XMCV.js} +3404 -572
  29. package/dist/{chunk-WA7CHXUS.js → chunk-UL3Y5C4J.js} +2 -2
  30. package/dist/{chunk-WL2HNJZ6.js → chunk-VOMJRQOM.js} +3 -3
  31. package/dist/{chunk-OD2G5LFQ.js → chunk-ZYOXI4JL.js} +41 -2
  32. package/dist/config.cjs +2 -12
  33. package/dist/config.js +2 -2
  34. package/dist/converters.cjs +36 -11
  35. package/dist/converters.d.cts +7 -5
  36. package/dist/converters.d.ts +7 -5
  37. package/dist/converters.js +11 -13
  38. package/dist/core.cjs +239 -5
  39. package/dist/core.d.cts +71 -6
  40. package/dist/core.d.ts +71 -6
  41. package/dist/core.js +31 -6
  42. package/dist/{customTools-BzF0IISO.d.cts → customTools-Bfecd0mJ.d.cts} +1 -1
  43. package/dist/{customTools-BzF0IISO.d.ts → customTools-Bfecd0mJ.d.ts} +1 -1
  44. package/dist/{disconnectReason-IuADl6TH.d.ts → disconnectReason-Cctmg1SN.d.cts} +1 -1
  45. package/dist/{disconnectReason-IuADl6TH.d.cts → disconnectReason-Cctmg1SN.d.ts} +1 -1
  46. package/dist/{dtos-B8KU_q4d.d.ts → dtos-CjQsN1_h.d.cts} +5 -12
  47. package/dist/{dtos-B8KU_q4d.d.cts → dtos-CjQsN1_h.d.ts} +5 -12
  48. package/dist/index.cjs +5289 -1474
  49. package/dist/index.d.cts +61 -22
  50. package/dist/index.d.ts +61 -22
  51. package/dist/index.js +151 -43
  52. package/dist/linear.cjs +45 -7
  53. package/dist/linear.d.cts +4 -3
  54. package/dist/linear.d.ts +4 -3
  55. package/dist/linear.js +10 -10
  56. package/dist/mcp-claude.cjs +11 -6
  57. package/dist/mcp-claude.d.cts +4 -3
  58. package/dist/mcp-claude.d.ts +4 -3
  59. package/dist/mcp-claude.js +4 -5
  60. package/dist/mcp.cjs +13 -7
  61. package/dist/mcp.d.cts +6 -5
  62. package/dist/mcp.d.ts +6 -5
  63. package/dist/mcp.js +5 -6
  64. package/dist/{pagination-DqdkQArT.d.cts → pagination-BZDhB_nO.d.cts} +2 -2
  65. package/dist/{pagination-BgEx-HeL.d.ts → pagination-CPz0J_vQ.d.ts} +2 -2
  66. package/dist/{protocols-Bz1rxcUg.d.cts → protocols-CabgO52c.d.cts} +38 -3
  67. package/dist/{protocols-Bwwcql0h.d.ts → protocols-Dpci6uae.d.ts} +38 -3
  68. package/dist/rest.cjs +43 -1
  69. package/dist/rest.d.cts +6 -5
  70. package/dist/rest.d.ts +6 -5
  71. package/dist/rest.js +6 -5
  72. package/dist/runtime.cjs +2113 -597
  73. package/dist/runtime.d.cts +44 -25
  74. package/dist/runtime.d.ts +44 -25
  75. package/dist/runtime.js +11 -11
  76. package/dist/{schemas-BgJhlqqO.d.cts → schemas-BKnh4fKR.d.cts} +209 -24
  77. package/dist/{schemas-C4mK1WSs.d.ts → schemas-CDa3V_I5.d.ts} +209 -24
  78. package/dist/{sdk-BTciSLRQ.d.cts → sdk-CXNqzoY1.d.cts} +1 -1
  79. package/dist/{sdk-qKJtMma_.d.ts → sdk-Cin80BTC.d.ts} +1 -1
  80. package/dist/{simpleAdapter-BpT4XbZC.d.cts → simpleAdapter-D75rcz9n.d.cts} +3 -1
  81. package/dist/{simpleAdapter--wznuoOw.d.ts → simpleAdapter-eLvsAQNo.d.ts} +3 -1
  82. package/dist/testing.cjs +25 -7
  83. package/dist/testing.d.cts +5 -3
  84. package/dist/testing.d.ts +5 -3
  85. package/dist/testing.js +9 -6
  86. package/dist/{types-BivGO7I9.d.ts → types-42-HWeX8.d.cts} +10 -4
  87. package/dist/types-CKU1N0SK.d.ts +137 -0
  88. package/dist/{types-BgYFd_Yw.d.cts → types-CjxZekMH.d.ts} +10 -4
  89. package/dist/types-DtcOLALn.d.cts +137 -0
  90. package/package.json +25 -26
  91. package/dist/acp-server-CiUqN3G3.d.ts +0 -24
  92. package/dist/acp-server-Dlj7D563.d.cts +0 -24
  93. package/dist/chunk-AVVDPXP4.js +0 -39
  94. package/dist/chunk-F63TPOI3.js +0 -29
  95. package/dist/chunk-V5TSWS7P.js +0 -62
  96. package/dist/types-BNNtOXu3.d.cts +0 -446
  97. package/dist/types-DFUtpxah.d.ts +0 -446
package/dist/adapters.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import {
2
+ ACPClientAdapter,
3
+ ACPServer,
2
4
  AnthropicAdapter,
3
5
  AnthropicToolCallingModel,
6
+ BandACPServerAdapter,
4
7
  CODEX_REASONING_EFFORTS,
5
8
  CODEX_REASONING_SUMMARIES,
6
9
  CODEX_WEB_SEARCH_MODES,
@@ -8,6 +11,9 @@ import {
8
11
  CodexAdapter,
9
12
  CodexAppServerStdioClient,
10
13
  CodexJsonRpcError,
14
+ CopilotACPAdapter,
15
+ DEFAULT_COPILOT_ACP_COMMAND,
16
+ DEFAULT_OMP_ACP_COMMAND,
11
17
  GeminiAdapter,
12
18
  GeminiToolCallingModel,
13
19
  GenericAdapter,
@@ -17,1943 +23,37 @@ import {
17
23
  LangGraphAdapter,
18
24
  LettaAdapter,
19
25
  LettaHistoryConverter,
26
+ OmpACPAdapter,
20
27
  OpenAIAdapter,
21
28
  OpenAIToolCallingModel,
22
29
  OpencodeAdapter,
23
30
  ToolCallingAdapter,
24
31
  VercelAISDKAdapter,
25
32
  VercelAISDKToolCallingModel,
26
- runSingleToolRound,
27
- systemUpdateParts
28
- } from "./chunk-MQBBWYRP.js";
29
- import {
30
- BandMcpServer,
31
- BandMcpSseServer,
32
- generateAuthToken
33
- } from "./chunk-WA7CHXUS.js";
34
- import "./chunk-XWHWJP4I.js";
35
- import {
36
- ACPClientHistoryConverter,
37
- ACPServerHistoryConverter
38
- } from "./chunk-V5TSWS7P.js";
33
+ runSingleToolRound
34
+ } from "./chunk-UFO6XMCV.js";
35
+ import "./chunk-UL3Y5C4J.js";
36
+ import "./chunk-JDW5WSGF.js";
39
37
  import {
40
38
  A2AAdapter,
41
39
  A2AGatewayAdapter,
42
40
  A2AHistoryConverter,
43
41
  GatewayHistoryConverter,
44
42
  GatewayServer,
45
- LazyAsyncValue,
46
43
  ParlantAdapter,
47
44
  ParlantHistoryConverter,
48
45
  buildA2AAuthHeaders,
49
- createGatewayServer,
50
- parseToolCall,
51
- parseToolResult
52
- } from "./chunk-K5KAEYPF.js";
53
- import {
54
- SimpleAdapter
55
- } from "./chunk-AVVDPXP4.js";
56
- import {
57
- ensureHandlePrefix,
58
- mentionSubjectsFromMetadata,
59
- replaceUuidMentions
60
- } from "./chunk-OH6VVVKT.js";
61
- import {
62
- renderSystemPrompt
63
- } from "./chunk-YJ42AZO4.js";
64
- import {
65
- MCP_SERVER_NAME
66
- } from "./chunk-MQY74R7D.js";
67
- import {
68
- isBlankEventContent
69
- } from "./chunk-F63TPOI3.js";
70
- import "./chunk-KXJ74J73.js";
71
- import "./chunk-JADCAMZY.js";
72
- import {
73
- NoopLogger
74
- } from "./chunk-OD2G5LFQ.js";
75
- import {
76
- UnsupportedFeatureError,
77
- ValidationError
78
- } from "./chunk-JY75VSQK.js";
79
-
80
- // src/adapters/acp/ACPClientAdapter.ts
81
- import { spawn } from "child_process";
82
- import { Readable, Writable } from "stream";
83
-
84
- // src/adapters/shared/withTimeout.ts
85
- async function withTimeout(promise, timeoutMs, message) {
86
- let timer;
87
- try {
88
- return await Promise.race([
89
- promise,
90
- new Promise((_, reject) => {
91
- timer = setTimeout(() => reject(new Error(message)), timeoutMs);
92
- })
93
- ]);
94
- } finally {
95
- clearTimeout(timer);
96
- }
97
- }
98
-
99
- // src/adapters/acp/types.ts
100
- var DEFAULT_ACP_SERVER_MODES = [
101
- {
102
- id: "default",
103
- name: "Default",
104
- description: "General-purpose chat mode"
105
- },
106
- {
107
- id: "code",
108
- name: "Code",
109
- description: "Route prompts toward coding peers when available"
110
- }
111
- ];
112
- function createPendingPrompt(sessionId) {
113
- let markDone = () => void 0;
114
- const done = new Promise((resolve) => {
115
- markDone = resolve;
116
- });
117
- return {
118
- sessionId,
119
- done,
120
- markDone,
121
- terminalMessageSeen: false,
122
- completionTimer: null
123
- };
124
- }
125
- function choosePermissionOption(options) {
126
- if (options.length === 0) {
127
- return null;
128
- }
129
- return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
130
- }
131
- function asJsonSafe(value) {
132
- if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
133
- return value;
134
- }
135
- if (Array.isArray(value)) {
136
- return value.map((item) => asJsonSafe(item));
137
- }
138
- if (typeof value === "object") {
139
- if ("model_dump" in value && typeof value.model_dump === "function") {
140
- return asJsonSafe(value.model_dump());
141
- }
142
- if ("toJSON" in value && typeof value.toJSON === "function") {
143
- return asJsonSafe(value.toJSON());
144
- }
145
- return Object.fromEntries(
146
- Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
147
- );
148
- }
149
- return String(value);
150
- }
151
- function normalizeMcpServers(mcpServers) {
152
- if (!mcpServers) {
153
- return [];
154
- }
155
- return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
156
- }
157
-
158
- // src/adapters/acp/client.ts
159
- var BandACPClient = class {
160
- sessionChunks = /* @__PURE__ */ new Map();
161
- permissionHandler;
162
- // The handler is connection-scoped and required at construction, so it is
163
- // already in place before the agent process is spawned: there is no window
164
- // in which a `session/request_permission` has nowhere to go.
165
- constructor(permissionHandler) {
166
- this.permissionHandler = permissionHandler;
167
- }
168
- async sessionUpdate(params) {
169
- const chunk = toCollectedChunk(params.update);
170
- if (!chunk) {
171
- return;
172
- }
173
- const existing = this.sessionChunks.get(params.sessionId) ?? [];
174
- existing.push(chunk);
175
- this.sessionChunks.set(params.sessionId, existing);
176
- }
177
- async requestPermission(params) {
178
- return this.permissionHandler(params);
179
- }
180
- // Named for the one thing it clears: collected chunks are per-turn, and a
181
- // per-turn caller must not be able to reach anything with a longer life.
182
- resetChunks(sessionId) {
183
- this.sessionChunks.delete(sessionId);
184
- }
185
- getCollectedText(sessionId) {
186
- return this.getCollectedChunks(sessionId).filter((chunk) => chunk.chunkType === "text").map((chunk) => chunk.content).join("");
187
- }
188
- getCollectedChunks(sessionId) {
189
- if (sessionId) {
190
- return coalesceChunks(this.sessionChunks.get(sessionId) ?? []);
191
- }
192
- return [...this.sessionChunks.values()].flatMap((chunks) => coalesceChunks(chunks));
193
- }
194
- async extMethod(method, params) {
195
- if (method === "cursor/ask_question") {
196
- const options = Array.isArray(params.options) ? params.options : [];
197
- const selected = choosePermissionOption(
198
- options.filter((option) => !!option && typeof option === "object")
199
- );
200
- if (!selected) {
201
- return {
202
- outcome: {
203
- type: "cancelled"
204
- }
205
- };
206
- }
207
- return {
208
- outcome: {
209
- type: "selected",
210
- optionId: selected.optionId
211
- }
212
- };
213
- }
214
- if (method === "cursor/create_plan") {
215
- return {
216
- outcome: {
217
- type: "approved"
218
- }
219
- };
220
- }
221
- return {};
222
- }
223
- async extNotification(method, params) {
224
- const sessionId = toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
225
- if (!sessionId) {
226
- return;
227
- }
228
- if (method === "cursor/update_todos") {
229
- const todos = Array.isArray(params.todos) ? params.todos : [];
230
- const lines = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).filter((line) => line.trim().length > 0);
231
- if (lines.length > 0) {
232
- this.appendChunk(sessionId, {
233
- chunkType: "plan",
234
- content: lines.join("\n"),
235
- metadata: {},
236
- streamed: false
237
- });
238
- }
239
- return;
240
- }
241
- if (method === "cursor/task") {
242
- const result = toOptionalString(params.result);
243
- if (result) {
244
- this.appendChunk(sessionId, {
245
- chunkType: "text",
246
- content: `[Task completed] ${result}`,
247
- metadata: {},
248
- streamed: false
249
- });
250
- }
251
- }
252
- }
253
- appendChunk(sessionId, chunk) {
254
- const existing = this.sessionChunks.get(sessionId) ?? [];
255
- existing.push(chunk);
256
- this.sessionChunks.set(sessionId, existing);
257
- }
258
- };
259
- function coalesceChunks(chunks) {
260
- const result = [];
261
- const openRuns = /* @__PURE__ */ new Map();
262
- for (const chunk of chunks) {
263
- if (!chunk.streamed) {
264
- openRuns.clear();
265
- result.push({ ...chunk });
266
- continue;
267
- }
268
- const openRun = openRuns.get(chunk.chunkType);
269
- if (openRun) {
270
- openRun.content += chunk.content;
271
- continue;
272
- }
273
- const clone = { ...chunk };
274
- openRuns.set(chunk.chunkType, clone);
275
- result.push(clone);
276
- }
277
- return result;
278
- }
279
- function toCollectedChunk(update) {
280
- switch (update.sessionUpdate) {
281
- case "agent_message_chunk":
282
- return {
283
- chunkType: "text",
284
- content: extractTextFromContent(update.content),
285
- metadata: {},
286
- streamed: true
287
- };
288
- case "agent_thought_chunk":
289
- return {
290
- chunkType: "thought",
291
- content: extractTextFromContent(update.content),
292
- metadata: {},
293
- streamed: true
294
- };
295
- case "tool_call":
296
- return {
297
- chunkType: "tool_call",
298
- content: update.title,
299
- metadata: {
300
- tool_call_id: update.toolCallId,
301
- raw_input: update.rawInput,
302
- status: update.status ?? "pending"
303
- },
304
- streamed: false
305
- };
306
- case "tool_call_update":
307
- return {
308
- chunkType: "tool_result",
309
- content: extractToolOutput(update),
310
- metadata: {
311
- tool_call_id: update.toolCallId,
312
- status: update.status ?? "completed"
313
- },
314
- streamed: false
315
- };
316
- case "plan":
317
- return {
318
- chunkType: "plan",
319
- content: update.entries.map((entry) => entry.content).join("\n"),
320
- metadata: {},
321
- streamed: false
322
- };
323
- default:
324
- return null;
325
- }
326
- }
327
- function extractToolOutput(update) {
328
- if (typeof update.rawOutput === "string") {
329
- return update.rawOutput;
330
- }
331
- if (update.rawOutput !== void 0 && update.rawOutput !== null) {
332
- try {
333
- return JSON.stringify(update.rawOutput);
334
- } catch {
335
- return String(update.rawOutput);
336
- }
337
- }
338
- const parts = (update.content ?? []).map(extractTextFromToolContent).filter((text) => text.length > 0);
339
- return parts.join("\n");
340
- }
341
- function extractTextFromToolContent(content) {
342
- if (content.type === "content") {
343
- return extractTextFromContent(content.content);
344
- }
345
- if (content.type === "diff") {
346
- return content.newText;
347
- }
348
- if (content.type === "terminal") {
349
- return `[Terminal: ${content.terminalId}]`;
350
- }
351
- return "";
352
- }
353
- function extractTextFromContent(content) {
354
- switch (content.type) {
355
- case "text":
356
- return content.text;
357
- case "resource_link":
358
- return `[Resource: ${content.title ?? content.name ?? content.uri}]`;
359
- case "resource": {
360
- const resource = content.resource;
361
- if ("text" in resource && typeof resource.text === "string") {
362
- return resource.text;
363
- }
364
- return `[Resource: ${resource.uri ?? "embedded"}]`;
365
- }
366
- case "image":
367
- return `[Image: ${content.mimeType ?? "image"}]`;
368
- case "audio":
369
- return `[Audio: ${content.mimeType ?? "audio"}]`;
370
- default:
371
- return "";
372
- }
373
- }
374
- function toOptionalString(value) {
375
- return typeof value === "string" && value.length > 0 ? value : null;
376
- }
377
-
378
- // src/adapters/acp/loader.ts
379
- var acpModule = new LazyAsyncValue({
380
- load: async () => {
381
- return await import("@agentclientprotocol/sdk").catch((error) => {
382
- throw new UnsupportedFeatureError(
383
- `ACP adapters require optional dependency "@agentclientprotocol/sdk". Install it with "pnpm add @agentclientprotocol/sdk". (${error instanceof Error ? error.message : String(error)})`
384
- );
385
- });
386
- }
387
- });
388
-
389
- // src/adapters/acp/ACPClientAdapter.ts
390
- var DEFAULT_PERMISSION_TIMEOUT_MS = 5 * 6e4;
391
- var SET_SESSION_MODE_TIMEOUT_MS = 1e4;
392
- var ACPClientAdapter = class extends SimpleAdapter {
393
- command;
394
- cwd;
395
- env;
396
- mcpServers;
397
- authMethod;
398
- enableMemoryTools;
399
- enableMcpTools;
400
- additionalMcpTools;
401
- clientCapabilities;
402
- connectionFactory;
403
- roomToSession = /* @__PURE__ */ new Map();
404
- sessionToRoom = /* @__PURE__ */ new Map();
405
- roomTools = /* @__PURE__ */ new Map();
406
- activeSessions = /* @__PURE__ */ new Set();
407
- bootstrappedSessions = /* @__PURE__ */ new Set();
408
- pendingPermissions = /* @__PURE__ */ new Map();
409
- sessionsInFlight = /* @__PURE__ */ new Map();
410
- roomTurnLocks = /* @__PURE__ */ new Map();
411
- // Bumped each time a room starts a *new* establishment (never on a
412
- // coalesced reuse) and whenever a room is torn down. An establishment
413
- // captures its own value at the start; if the room has moved on by the
414
- // time it would link/activate a session, it was superseded and must not.
415
- roomGeneration = /* @__PURE__ */ new Map();
416
- resolvePermission;
417
- resolveSessionMode;
418
- permissionTimeoutMs;
419
- logger;
420
- backend = null;
421
- backendPromise = null;
422
- client = null;
423
- connectionHandle = null;
424
- connection = null;
425
- connectionState = null;
426
- started = false;
427
- systemPrompt = "";
428
- spawnPromise = null;
429
- constructor(options) {
430
- super({
431
- historyConverter: new ACPClientHistoryConverter()
432
- });
433
- this.command = Array.isArray(options.command) ? [...options.command] : [options.command];
434
- if (this.command.length === 0 || this.command[0].length === 0) {
435
- throw new Error("ACPClientAdapter requires a command");
436
- }
437
- this.cwd = options.cwd ?? process.cwd();
438
- this.env = options.env;
439
- this.mcpServers = [...options.mcpServers ?? []];
440
- this.authMethod = options.authMethod;
441
- this.enableMemoryTools = options.enableMemoryTools ?? false;
442
- this.enableMcpTools = options.enableMcpTools ?? true;
443
- this.additionalMcpTools = [...options.additionalMcpTools ?? []];
444
- this.clientCapabilities = options.clientCapabilities;
445
- this.connectionFactory = options.connectionFactory ?? createSubprocessConnection;
446
- this.resolvePermission = options.resolvePermission;
447
- this.resolveSessionMode = options.resolveSessionMode;
448
- this.logger = options.logger ?? new NoopLogger();
449
- this.permissionTimeoutMs = options.permissionTimeoutMs ?? DEFAULT_PERMISSION_TIMEOUT_MS;
450
- if ((this.resolvePermission || this.resolveSessionMode) && (!Number.isFinite(this.permissionTimeoutMs) || this.permissionTimeoutMs <= 0)) {
451
- throw new ValidationError(`permissionTimeoutMs must be a positive finite number, got ${options.permissionTimeoutMs}`);
452
- }
453
- }
454
- async onStarted(agentName, agentDescription) {
455
- await super.onStarted(agentName, agentDescription);
456
- this.started = true;
457
- this.systemPrompt = renderSystemPrompt({
458
- agentName,
459
- agentDescription,
460
- includeBaseInstructions: false
461
- });
462
- await this.ensureConnection();
463
- }
464
- async onMessage(message, tools, history, participantsMessage, contactsMessage, context) {
465
- if (context.isSessionBootstrap) {
466
- this.rehydrate(history);
467
- }
468
- this.roomTools.set(context.roomId, tools);
469
- await this.withRoomTurnLock(context.roomId, () => this.runTurn(message, tools, participantsMessage, contactsMessage, context));
470
- }
471
- async runTurn(message, tools, participantsMessage, contactsMessage, context) {
472
- const connection = await this.ensureConnection();
473
- const client = this.client;
474
- if (!client) {
475
- throw new Error("ACP client was not initialized");
476
- }
477
- const sessionId = await this.getOrCreateSession(context.roomId, connection);
478
- client.resetChunks(sessionId);
479
- const content = replaceUuidMentions(message.content, mentionSubjectsFromMetadata(message.metadata));
480
- const messageWithContext = [...systemUpdateParts(participantsMessage, contactsMessage), content].join("\n\n");
481
- const promptText = this.bootstrappedSessions.has(sessionId) ? messageWithContext : `${this.buildSystemContext(context.roomId, message)}
482
-
483
- ${messageWithContext}`;
484
- this.bootstrappedSessions.add(sessionId);
485
- try {
486
- await connection.prompt({
487
- sessionId,
488
- prompt: [{
489
- type: "text",
490
- text: promptText
491
- }]
492
- });
493
- } catch (error) {
494
- await this.stop();
495
- await tools.sendEvent(`ACP agent error: ${toErrorMessage(error)}`, "error", {
496
- acp_error: toErrorMessage(error)
497
- });
498
- return;
499
- }
500
- await this.flushChunks({
501
- tools,
502
- sessionId,
503
- senderId: message.senderId,
504
- senderHandle: message.senderName ?? message.senderType
505
- });
506
- await tools.sendEvent("ACP client session", "task", {
507
- acp_client_session_id: sessionId,
508
- acp_client_room_id: context.roomId
509
- });
510
- }
511
- // A per-room async mutex: `fn` for a given `roomId` never overlaps another
512
- // call for that same room, while different rooms stay fully concurrent.
513
- // The tracked tail (`this.roomTurnLocks`) always settles — via the
514
- // trailing `.catch` — so one turn's failure can't wedge every later turn
515
- // for the room; the real result/rejection is still `run`, returned to this
516
- // call's own caller.
517
- async withRoomTurnLock(roomId, fn) {
518
- const previous = this.roomTurnLocks.get(roomId) ?? Promise.resolve();
519
- const run = previous.then(fn, fn);
520
- this.roomTurnLocks.set(roomId, run.catch(() => void 0));
521
- return run;
522
- }
523
- async onCleanup(roomId) {
524
- const sessionId = this.unlinkRoom(roomId);
525
- this.roomTools.delete(roomId);
526
- this.sessionsInFlight.delete(roomId);
527
- this.roomTurnLocks.delete(roomId);
528
- this.nextRoomGeneration(roomId);
529
- if (sessionId) {
530
- this.activeSessions.delete(sessionId);
531
- this.bootstrappedSessions.delete(sessionId);
532
- this.cancelPendingPermissions(sessionId, "room-closed");
533
- }
534
- }
535
- async onRuntimeStop() {
536
- await this.stop();
537
- }
538
- async stop() {
539
- this.spawnPromise = null;
540
- this.connectionState = null;
541
- this.activeSessions.clear();
542
- this.bootstrappedSessions.clear();
543
- this.roomToSession.clear();
544
- this.sessionToRoom.clear();
545
- this.roomTools.clear();
546
- this.sessionsInFlight.clear();
547
- this.roomTurnLocks.clear();
548
- for (const roomId of this.roomGeneration.keys()) {
549
- this.nextRoomGeneration(roomId);
550
- }
551
- this.cancelAllPendingPermissions("adapter-stopped");
552
- this.client = null;
553
- this.connection = null;
554
- if (this.backend) {
555
- const backend = this.backend;
556
- this.backend = null;
557
- await backend.stop();
558
- }
559
- if (this.connectionHandle) {
560
- const handle = this.connectionHandle;
561
- this.connectionHandle = null;
562
- await handle.stop();
563
- }
564
- }
565
- rehydrate(history) {
566
- for (const [roomId, sessionId] of Object.entries(history.roomToSession)) {
567
- if (!this.roomToSession.has(roomId)) {
568
- this.linkSession(roomId, sessionId);
569
- }
570
- }
571
- }
572
- // The only writer of both session maps, so they cannot drift: replacing a
573
- // room's session drops the old session's route, and a session id already
574
- // routed to another room is refused rather than silently re-pointed — two
575
- // rooms sharing one session id would make its permission requests
576
- // unattributable. Returns whether the link was made — a caller that goes
577
- // on to activate/configure/prompt a session regardless of a `false` here
578
- // would use a session this room was refused, not just fail to route its
579
- // permissions.
580
- linkSession(roomId, sessionId) {
581
- const routedRoomId = this.sessionToRoom.get(sessionId);
582
- if (routedRoomId !== void 0 && routedRoomId !== roomId) {
583
- this.safeWarn("refusing to route one ACP session to a second room", {
584
- sessionId,
585
- roomId,
586
- routedRoomId
587
- });
588
- return false;
589
- }
590
- const replacedSessionId = this.roomToSession.get(roomId);
591
- if (replacedSessionId !== void 0 && replacedSessionId !== sessionId) {
592
- this.sessionToRoom.delete(replacedSessionId);
593
- }
594
- this.roomToSession.set(roomId, sessionId);
595
- this.sessionToRoom.set(sessionId, roomId);
596
- return true;
597
- }
598
- nextRoomGeneration(roomId) {
599
- const next = (this.roomGeneration.get(roomId) ?? 0) + 1;
600
- this.roomGeneration.set(roomId, next);
601
- return next;
602
- }
603
- isCurrentGeneration(roomId, generation) {
604
- return this.roomGeneration.get(roomId) === generation;
605
- }
606
- // The installed ACP SDK's `sendRequest` never rejects a pending call when
607
- // its connection closes (no server response ever arrives to reject it
608
- // with) — so a session-establishment RPC in flight when the subprocess
609
- // dies would otherwise hang forever, wedging the room's `sessionsInFlight`
610
- // entry along with it. Racing every such RPC against the connection's own
611
- // `closed` promise gives it a real, prompt failure instead.
612
- raceAgainstConnectionClose(connection, operation) {
613
- let reject = () => void 0;
614
- const closedRejection = new Promise((_resolve, rejectFn) => {
615
- reject = rejectFn;
616
- });
617
- void connection.closed.then(() => reject(new Error("ACP connection closed while a session operation was still in flight")));
618
- return Promise.race([operation, closedRejection]);
619
- }
620
- unlinkRoom(roomId) {
621
- const sessionId = this.roomToSession.get(roomId);
622
- this.roomToSession.delete(roomId);
623
- if (sessionId !== void 0) {
624
- this.sessionToRoom.delete(sessionId);
625
- }
626
- return sessionId;
627
- }
628
- async ensureConnection() {
629
- if (this.connection && !this.connection.signal.aborted) {
630
- return this.connection;
631
- }
632
- if (!this.started) {
633
- throw new Error("ACPClientAdapter was not started");
634
- }
635
- const isCreator = !this.spawnPromise;
636
- if (isCreator) {
637
- this.spawnPromise = this.spawnConnection();
638
- }
639
- try {
640
- return await this.spawnPromise;
641
- } finally {
642
- if (isCreator) this.spawnPromise = null;
643
- }
644
- }
645
- async spawnConnection() {
646
- const acp = await acpModule.get();
647
- const client = new BandACPClient((params) => this.routePermissionRequest(params));
648
- const handle = await this.connectionFactory(client, {
649
- command: this.command,
650
- cwd: this.cwd,
651
- env: this.env
652
- });
653
- const connection = handle.connection;
654
- const initializeResult = await connection.initialize({
655
- protocolVersion: acp.PROTOCOL_VERSION,
656
- clientCapabilities: this.clientCapabilities ?? {}
657
- });
658
- if (this.authMethod) {
659
- await connection.authenticate({
660
- methodId: this.authMethod
661
- });
662
- }
663
- this.client = client;
664
- this.connection = connection;
665
- this.connectionHandle = handle;
666
- this.connectionState = initializeResult;
667
- void connection.closed.finally(() => {
668
- if (this.connection === connection) {
669
- this.connection = null;
670
- this.connectionHandle = null;
671
- this.connectionState = null;
672
- this.activeSessions.clear();
673
- this.cancelAllPendingPermissions("connection-lost");
674
- }
675
- });
676
- return connection;
677
- }
678
- async getOrCreateSession(roomId, connection) {
679
- const existingSessionId = this.roomToSession.get(roomId);
680
- if (existingSessionId && this.activeSessions.has(existingSessionId)) {
681
- return existingSessionId;
682
- }
683
- const inFlight = this.sessionsInFlight.get(roomId);
684
- if (inFlight) {
685
- return inFlight;
686
- }
687
- const generation = this.nextRoomGeneration(roomId);
688
- const establishing = this.establishSession(roomId, existingSessionId, connection, generation);
689
- establishing.finally(() => {
690
- if (this.sessionsInFlight.get(roomId) === establishing) {
691
- this.sessionsInFlight.delete(roomId);
692
- }
693
- }).catch(() => void 0);
694
- this.sessionsInFlight.set(roomId, establishing);
695
- return establishing;
696
- }
697
- async establishSession(roomId, existingSessionId, connection, generation) {
698
- const mcpServers = await this.buildSessionMcpServers();
699
- if (existingSessionId) {
700
- const restored = await this.tryRestoreSession(connection, existingSessionId, mcpServers);
701
- if (restored.ok) {
702
- this.linkOrAbandon(roomId, existingSessionId, generation);
703
- this.activeSessions.add(existingSessionId);
704
- this.bootstrappedSessions.add(existingSessionId);
705
- await this.configureSessionMode(roomId, existingSessionId, restored.modes, connection);
706
- return existingSessionId;
707
- }
708
- }
709
- const created = await this.raceAgainstConnectionClose(connection, connection.newSession({
710
- cwd: this.cwd,
711
- mcpServers
712
- }));
713
- this.linkOrAbandon(roomId, created.sessionId, generation);
714
- this.activeSessions.add(created.sessionId);
715
- await this.configureSessionMode(roomId, created.sessionId, created.modes, connection);
716
- return created.sessionId;
717
- }
718
- // The single gate an establishment must pass before it's allowed to claim
719
- // the room: it must still be the room's current generation (not
720
- // superseded by a teardown or a fresher establishment while this one was
721
- // awaiting an RPC), and its session id must not already belong to another
722
- // room. Either failure throws — this establishment cannot silently
723
- // continue to activate, configure, and prompt a session it has no right
724
- // to use for this room.
725
- linkOrAbandon(roomId, sessionId, generation) {
726
- if (!this.isCurrentGeneration(roomId, generation)) {
727
- throw new Error(`ACP session establishment for room "${roomId}" was superseded before it could be linked`);
728
- }
729
- if (!this.linkSession(roomId, sessionId)) {
730
- throw new Error(`ACP session "${sessionId}" could not be linked to room "${roomId}": already routed elsewhere`);
731
- }
732
- }
733
- // Best-effort: never throws, so a mode switch going wrong can't take a
734
- // session establishment down with it.
735
- async configureSessionMode(roomId, sessionId, modes, connection) {
736
- if (!this.resolveSessionMode || !modes) {
737
- return;
738
- }
739
- const availableModes = Array.isArray(modes.availableModes) ? modes.availableModes : [];
740
- if (availableModes.length === 0) {
741
- return;
742
- }
743
- const selectedModeId = await this.resolveSessionModeManually(
744
- (signal) => this.resolveSessionMode({
745
- roomId,
746
- sessionId,
747
- currentModeId: modes.currentModeId,
748
- modes: availableModes
749
- }, signal),
750
- connection.signal
751
- );
752
- if (!selectedModeId || selectedModeId === modes.currentModeId) {
753
- return;
754
- }
755
- if (!availableModes.some((mode) => mode?.id === selectedModeId)) {
756
- this.safeWarn("resolveSessionMode selected a mode id this session does not advertise", {
757
- sessionId,
758
- selectedModeId,
759
- availableModeIds: availableModes.map((mode) => mode?.id)
760
- });
761
- return;
762
- }
763
- try {
764
- await withTimeout(
765
- connection.setSessionMode({ sessionId, modeId: selectedModeId }),
766
- SET_SESSION_MODE_TIMEOUT_MS,
767
- `setSessionMode did not respond within ${SET_SESSION_MODE_TIMEOUT_MS}ms`
768
- );
769
- } catch (error) {
770
- this.safeWarn("failed to switch session into the selected mode", {
771
- sessionId,
772
- selectedModeId,
773
- error: String(error)
774
- });
775
- }
776
- }
777
- async resolveSessionModeManually(resolver, signal) {
778
- const controller = new AbortController();
779
- const abort = () => controller.abort();
780
- signal.addEventListener("abort", abort, { once: true });
781
- let timer;
782
- try {
783
- const cancelled = new Promise((resolve) => {
784
- controller.signal.addEventListener("abort", () => resolve(void 0), { once: true });
785
- });
786
- const timeout = new Promise((resolve) => {
787
- timer = setTimeout(() => resolve(void 0), this.permissionTimeoutMs);
788
- });
789
- return await Promise.race([
790
- Promise.resolve().then(() => resolver(controller.signal)).catch((error) => {
791
- this.safeWarn("resolveSessionMode threw; preserving the harness default", { error: String(error) });
792
- return void 0;
793
- }),
794
- timeout,
795
- cancelled
796
- ]);
797
- } finally {
798
- clearTimeout(timer);
799
- signal.removeEventListener("abort", abort);
800
- controller.abort();
801
- }
802
- }
803
- async tryRestoreSession(connection, sessionId, mcpServers) {
804
- const capabilities = this.connectionState?.agentCapabilities;
805
- const params = { cwd: this.cwd, mcpServers, sessionId };
806
- const restore = capabilities?.loadSession ? () => connection.loadSession(params) : capabilities?.sessionCapabilities?.resume ? () => connection.unstable_resumeSession(params) : null;
807
- if (!restore) {
808
- return { ok: false };
809
- }
810
- try {
811
- const restored = await this.raceAgainstConnectionClose(connection, restore());
812
- return { ok: true, modes: restored?.modes };
813
- } catch {
814
- return { ok: false };
815
- }
816
- }
817
- async buildSessionMcpServers() {
818
- const mcpServers = [...this.mcpServers];
819
- if (!this.enableMcpTools) {
820
- return mcpServers;
821
- }
822
- const backend = await this.getOrCreateBackend();
823
- if (backend.kind === "http") {
824
- const url = backend.server.url;
825
- if (!url) {
826
- throw new Error("Band MCP HTTP backend did not expose a URL");
827
- }
828
- mcpServers.push({
829
- type: "http",
830
- name: MCP_SERVER_NAME,
831
- url,
832
- headers: [{ name: "Authorization", value: `Bearer ${backend.authToken}` }]
833
- });
834
- return mcpServers;
835
- }
836
- if (backend.kind === "sse") {
837
- const url = backend.server.sseUrl;
838
- if (!url) {
839
- throw new Error("Band MCP SSE backend did not expose a URL");
840
- }
841
- mcpServers.push({
842
- type: "sse",
843
- name: MCP_SERVER_NAME,
844
- url,
845
- headers: [{ name: "Authorization", value: `Bearer ${backend.authToken}` }]
846
- });
847
- return mcpServers;
848
- }
849
- return mcpServers;
850
- }
851
- async getOrCreateBackend() {
852
- if (this.backend) {
853
- return this.backend;
854
- }
855
- this.backendPromise ??= this.createBackend().finally(() => {
856
- this.backendPromise = null;
857
- });
858
- return this.backendPromise;
859
- }
860
- async createBackend() {
861
- const mcpCapabilities = this.connectionState?.agentCapabilities?.mcpCapabilities;
862
- const transport = mcpCapabilities?.http ? "http" : mcpCapabilities?.sse ? "sse" : null;
863
- if (transport === null) {
864
- throw new Error(
865
- "ACP agent does not advertise MCP transport support: its initialize response has mcpCapabilities.http and .sse both false or missing, so Band tools cannot be exposed to it over MCP."
866
- );
867
- }
868
- const authToken = generateAuthToken();
869
- if (transport === "sse") {
870
- const server2 = new BandMcpSseServer({
871
- tools: (roomId) => this.roomTools.get(roomId),
872
- enableMemoryTools: this.enableMemoryTools,
873
- enableContactTools: true,
874
- additionalTools: this.additionalMcpTools,
875
- authToken
876
- });
877
- await server2.start();
878
- this.backend = {
879
- kind: "sse",
880
- server: server2,
881
- authToken,
882
- stop: async () => {
883
- await server2.stop();
884
- }
885
- };
886
- return this.backend;
887
- }
888
- const server = new BandMcpServer({
889
- tools: (roomId) => this.roomTools.get(roomId),
890
- enableMemoryTools: this.enableMemoryTools,
891
- enableContactTools: true,
892
- additionalTools: this.additionalMcpTools,
893
- authToken
894
- });
895
- await server.start();
896
- this.backend = {
897
- kind: "http",
898
- server,
899
- authToken,
900
- stop: async () => {
901
- await server.stop();
902
- }
903
- };
904
- return this.backend;
905
- }
906
- buildSystemContext(roomId, message) {
907
- const requesterName = message.senderName ?? message.senderId;
908
- const requesterId = message.senderId;
909
- return [
910
- "[System Context]",
911
- this.systemPrompt,
912
- "",
913
- "## Room Context",
914
- "You are connected to Band using Band MCP tools.",
915
- "Use the Band tools for any visible room action. Plain text output is not posted back to the room.",
916
- "",
917
- `Current room_id: ${roomId}`,
918
- `Current requester name: ${requesterName}`,
919
- `Current requester id: ${requesterId}`,
920
- "",
921
- "All Band MCP tool calls must include room_id."
922
- ].join("\n");
923
- }
924
- // The connection's single permission entry point, and total by
925
- // construction: every path resolves, nothing throws, and a request that
926
- // can't be attributed to a live room is cancelled *and* warned rather than
927
- // silently declined. `activeSessions` is the gate that keeps a dead
928
- // session from raising a live prompt — `roomToSession` deliberately
929
- // outlives a dropped connection so the session can be restored later.
930
- async routePermissionRequest(params) {
931
- const isActive = this.activeSessions.has(params.sessionId);
932
- const roomId = isActive ? this.sessionToRoom.get(params.sessionId) : void 0;
933
- const tools = roomId === void 0 ? void 0 : this.roomTools.get(roomId);
934
- if (roomId === void 0 || !tools) {
935
- this.safeWarn("cancelling a permission request that maps to no live room", {
936
- sessionId: params.sessionId,
937
- toolName: params.toolCall?.title,
938
- sessionActive: isActive,
939
- roomId
940
- });
941
- return { outcome: { outcome: "cancelled" } };
942
- }
943
- try {
944
- return await this.handlePermissionRequest(tools, roomId, params);
945
- } catch (error) {
946
- this.safeWarn("permission handling failed; cancelling the request", {
947
- sessionId: params.sessionId,
948
- roomId,
949
- error: String(error)
950
- });
951
- return { outcome: { outcome: "cancelled" } };
952
- }
953
- }
954
- async handlePermissionRequest(tools, roomId, params) {
955
- const toolName = params.toolCall.title ?? "unknown";
956
- const autoSelection = this.resolvePermission ? void 0 : choosePermissionOption(params.options);
957
- const controller = this.resolvePermission ? new AbortController() : void 0;
958
- if (controller) {
959
- this.trackPending(params.sessionId, controller);
960
- }
961
- let requestEventFailed = false;
962
- const [, resolvedChosenId] = await Promise.all([
963
- // Started immediately rather than serialized in front of a manual
964
- // wait that can take up to `permissionTimeoutMs`.
965
- tools.sendEvent(`Permission requested: ${toolName}`, "tool_call", {
966
- permission_request: true,
967
- tool_name: toolName,
968
- tool_call_id: params.toolCall.toolCallId,
969
- acp_session_id: params.sessionId,
970
- auto_allowed: autoSelection !== void 0 && autoSelection !== null
971
- }).catch((error) => {
972
- requestEventFailed = true;
973
- this.safeWarn("failed to post the permission-requested event; cancelling the request", {
974
- roomId,
975
- sessionId: params.sessionId,
976
- error: String(error)
977
- });
978
- if (controller) {
979
- this.abandon(controller, "no-answer");
980
- }
981
- }),
982
- controller ? this.resolveManually(roomId, params, controller) : Promise.resolve(autoSelection?.optionId)
983
- ]);
984
- const chosenId = requestEventFailed ? void 0 : resolvedChosenId;
985
- const response = this.toResponse(chosenId, params.options, { roomId, sessionId: params.sessionId });
986
- if (controller && !controller.signal.aborted) {
987
- this.abandon(controller, response.outcome.outcome === "selected" ? "settled" : "no-answer");
988
- }
989
- return response;
990
- }
991
- // `undefined`, or an id absent from this request's own `options` (a buggy
992
- // or stale caller), both map to `cancelled` — never silently treated as a
993
- // deny. A real match, reject-kind options included, maps to `selected`.
994
- toResponse(chosenId, options, context) {
995
- if (chosenId === void 0) {
996
- return { outcome: { outcome: "cancelled" } };
997
- }
998
- if (!options.some((option) => option.optionId === chosenId)) {
999
- this.safeWarn("resolvePermission chose an option this request does not offer", {
1000
- ...context,
1001
- chosenId,
1002
- optionIds: options.map((option) => option.optionId)
1003
- });
1004
- return { outcome: { outcome: "cancelled" } };
1005
- }
1006
- return { outcome: { outcome: "selected", optionId: chosenId } };
1007
- }
1008
- // A caller-supplied `Logger` isn't guaranteed to be synchronous or
1009
- // non-throwing. Every best-effort warning in this file routes through here
1010
- // so one failing sink — a synchronous throw, or an `async` implementation
1011
- // rejecting (the `Logger` interface's `void` return type permits either;
1012
- // a bare try/catch only ever catches the former) — can't turn a warning
1013
- // into an unhandled rejection in its place.
1014
- safeWarn(message, context) {
1015
- try {
1016
- Promise.resolve(this.logger.warn(message, context)).catch(() => void 0);
1017
- } catch {
1018
- }
1019
- }
1020
- // Races the caller-supplied resolver against `controller`'s abort signal,
1021
- // which is the request's single termination channel: the timeout below
1022
- // fires it with `"timeout"`, and `cancelPendingPermissions` /
1023
- // `cancelAllPendingPermissions` fire it with the reason their caller
1024
- // supplies. `controller` is the same object tracked in `pendingPermissions`,
1025
- // so there is exactly one cancellation channel here, not a second
1026
- // hand-rolled one alongside it.
1027
- async resolveManually(roomId, params, controller) {
1028
- const abandoned = new Promise((resolve) => {
1029
- controller.signal.addEventListener("abort", () => resolve(void 0));
1030
- });
1031
- const timer = setTimeout(() => this.abandon(controller, "timeout"), this.permissionTimeoutMs);
1032
- try {
1033
- return await Promise.race([
1034
- // `resolvePermission` is caller-supplied; nothing guarantees it's
1035
- // `async` or otherwise well-behaved. `Promise.resolve().then(...)`
1036
- // normalizes a synchronous throw the same way it normalizes a
1037
- // rejected promise, so both land in the `.catch` below rather than
1038
- // escaping this race uncaught.
1039
- Promise.resolve().then(() => this.resolvePermission({ ...params, roomId }, controller.signal)).then((chosenId) => this.discardLateAnswer(chosenId, controller, roomId, params.sessionId)).catch((error) => {
1040
- this.safeWarn("resolvePermission threw; treating as no answer", { error: String(error) });
1041
- return void 0;
1042
- }),
1043
- abandoned
1044
- ]);
1045
- } finally {
1046
- clearTimeout(timer);
1047
- this.untrackPending(params.sessionId, controller);
1048
- }
1049
- }
1050
- // An answer that lands after the request was given up on can no longer be
1051
- // honoured — the response has already gone back to the agent. It is dropped
1052
- // either way; warning is what makes "my click did nothing" explicable.
1053
- discardLateAnswer(chosenId, controller, roomId, sessionId) {
1054
- if (!controller.signal.aborted || chosenId === void 0) {
1055
- return chosenId;
1056
- }
1057
- this.safeWarn("resolvePermission answered after the request was abandoned; discarding", {
1058
- roomId,
1059
- sessionId,
1060
- chosenId,
1061
- reason: String(controller.signal.reason)
1062
- });
1063
- return void 0;
1064
- }
1065
- // The only place a permission's controller is ever aborted, so every
1066
- // `signal.reason` a consumer can observe comes from the documented union.
1067
- abandon(controller, reason) {
1068
- controller.abort(reason);
1069
- }
1070
- trackPending(sessionId, controller) {
1071
- const pending = this.pendingPermissions.get(sessionId) ?? /* @__PURE__ */ new Set();
1072
- pending.add(controller);
1073
- this.pendingPermissions.set(sessionId, pending);
1074
- }
1075
- untrackPending(sessionId, controller) {
1076
- const pending = this.pendingPermissions.get(sessionId);
1077
- pending?.delete(controller);
1078
- if (pending?.size === 0) {
1079
- this.pendingPermissions.delete(sessionId);
1080
- }
1081
- }
1082
- cancelPendingPermissions(sessionId, reason) {
1083
- for (const controller of this.pendingPermissions.get(sessionId) ?? []) {
1084
- this.abandon(controller, reason);
1085
- }
1086
- }
1087
- cancelAllPendingPermissions(reason) {
1088
- for (const sessionId of this.pendingPermissions.keys()) {
1089
- this.cancelPendingPermissions(sessionId, reason);
1090
- }
1091
- }
1092
- async flushChunks(input) {
1093
- const client = this.client;
1094
- if (!client) {
1095
- return;
1096
- }
1097
- for (const chunk of client.getCollectedChunks(input.sessionId)) {
1098
- if (isBlankEventContent(chunk.content)) {
1099
- continue;
1100
- }
1101
- if (chunk.chunkType === "text") {
1102
- await input.tools.sendMessage(chunk.content, [{
1103
- id: input.senderId,
1104
- handle: input.senderHandle
1105
- }]);
1106
- continue;
1107
- }
1108
- const messageType = chunk.chunkType === "plan" ? "task" : chunk.chunkType;
1109
- await input.tools.sendEvent(
1110
- chunk.content,
1111
- messageType,
1112
- chunk.metadata
1113
- );
1114
- }
1115
- }
1116
- };
1117
- async function createSubprocessConnection(client, options) {
1118
- const acp = await acpModule.get();
1119
- const child = spawn(options.command[0], options.command.slice(1), {
1120
- cwd: options.cwd,
1121
- env: {
1122
- ...process.env,
1123
- ...options.env
1124
- },
1125
- stdio: ["pipe", "pipe", "pipe"]
1126
- });
1127
- if (!child.stdin || !child.stdout) {
1128
- throw new Error("ACP subprocess did not expose stdio pipes");
1129
- }
1130
- const stream = acp.ndJsonStream(
1131
- Writable.toWeb(child.stdin),
1132
- Readable.toWeb(child.stdout)
1133
- );
1134
- const connection = new acp.ClientSideConnection(() => client, stream);
1135
- return {
1136
- connection,
1137
- stop: async () => {
1138
- await new Promise((resolve) => {
1139
- if (child.exitCode !== null || child.signalCode !== null) {
1140
- resolve();
1141
- return;
1142
- }
1143
- let settled = false;
1144
- const finish = () => {
1145
- if (settled) {
1146
- return;
1147
- }
1148
- settled = true;
1149
- child.off("exit", finish);
1150
- child.off("close", finish);
1151
- resolve();
1152
- };
1153
- child.once("exit", finish);
1154
- child.once("close", finish);
1155
- if (!child.killed) {
1156
- child.kill();
1157
- }
1158
- if (child.exitCode !== null || child.signalCode !== null) {
1159
- finish();
1160
- }
1161
- });
1162
- }
1163
- };
1164
- }
1165
- function toErrorMessage(error) {
1166
- if (error instanceof Error) {
1167
- return error.message;
1168
- }
1169
- return String(error);
1170
- }
1171
-
1172
- // src/adapters/acp/ACPServer.ts
1173
- import { Readable as Readable2, Writable as Writable2 } from "stream";
1174
-
1175
- // src/adapters/acp/BandACPServerAdapter.ts
1176
- import { randomUUID } from "crypto";
1177
-
1178
- // src/adapters/acp/eventConverter.ts
1179
- var EventConverter = class {
1180
- static convert(message) {
1181
- switch (message.messageType) {
1182
- case "text":
1183
- return {
1184
- sessionUpdate: "agent_message_chunk",
1185
- content: {
1186
- type: "text",
1187
- text: message.content
1188
- }
1189
- };
1190
- case "thought":
1191
- return {
1192
- sessionUpdate: "agent_thought_chunk",
1193
- content: {
1194
- type: "text",
1195
- text: message.content
1196
- }
1197
- };
1198
- case "tool_call":
1199
- return this.convertToolCall(message.content);
1200
- case "tool_result":
1201
- return this.convertToolResult(message.content);
1202
- case "error":
1203
- return {
1204
- sessionUpdate: "agent_message_chunk",
1205
- content: {
1206
- type: "text",
1207
- text: `[Error] ${message.content}`
1208
- }
1209
- };
1210
- case "task":
1211
- return {
1212
- sessionUpdate: "plan",
1213
- entries: [{
1214
- content: message.content,
1215
- priority: "medium",
1216
- status: "in_progress"
1217
- }]
1218
- };
1219
- default:
1220
- return null;
1221
- }
1222
- }
1223
- static convertToolCall(value) {
1224
- const parsed = parseToolCall(value);
1225
- if (!parsed) {
1226
- return {
1227
- sessionUpdate: "agent_message_chunk",
1228
- content: {
1229
- type: "text",
1230
- text: value
1231
- }
1232
- };
1233
- }
1234
- return {
1235
- sessionUpdate: "tool_call",
1236
- toolCallId: parsed.toolCallId,
1237
- title: parsed.name,
1238
- kind: "other",
1239
- status: "in_progress",
1240
- rawInput: parsed.args
1241
- };
1242
- }
1243
- static convertToolResult(value) {
1244
- const parsed = parseToolResult(value);
1245
- if (!parsed) {
1246
- return {
1247
- sessionUpdate: "agent_message_chunk",
1248
- content: {
1249
- type: "text",
1250
- text: value
1251
- }
1252
- };
1253
- }
1254
- const textBlock = {
1255
- type: "text",
1256
- text: parsed.output
1257
- };
1258
- const content = [{
1259
- type: "content",
1260
- content: textBlock
1261
- }];
1262
- return {
1263
- sessionUpdate: "tool_call_update",
1264
- toolCallId: parsed.toolCallId,
1265
- status: parsed.isError ? "failed" : "completed",
1266
- rawOutput: parsed.output,
1267
- content
1268
- };
1269
- }
1270
- };
1271
-
1272
- // src/adapters/acp/pushHandler.ts
1273
- var ACPPushHandler = class {
1274
- adapter;
1275
- constructor(adapter) {
1276
- this.adapter = adapter;
1277
- }
1278
- async handlePushEvent(message, roomId) {
1279
- const sessionId = this.adapter.getSessionForRoom(roomId);
1280
- const connection = this.adapter.getConnection();
1281
- if (!sessionId || !connection) {
1282
- return;
1283
- }
1284
- const update = EventConverter.convert(message);
1285
- if (!update) {
1286
- return;
1287
- }
1288
- await connection.sessionUpdate({
1289
- sessionId,
1290
- update
1291
- });
1292
- }
1293
- };
1294
-
1295
- // src/adapters/acp/router.ts
1296
- var AgentRouter = class {
1297
- modeToPeer;
1298
- slashCommands;
1299
- constructor(options) {
1300
- this.modeToPeer = { ...options?.modeToPeer ?? {} };
1301
- this.slashCommands = Object.fromEntries(
1302
- Object.entries(options?.slashCommands ?? {}).map(([command, peer]) => [command.toLowerCase(), peer])
1303
- );
1304
- }
1305
- resolve(text, currentMode) {
1306
- if (text.startsWith("/")) {
1307
- const [command, ...rest] = text.slice(1).split(/\s+/);
1308
- const peer = this.slashCommands[command?.toLowerCase() ?? ""];
1309
- if (peer) {
1310
- return {
1311
- text: rest.join(" ").trim(),
1312
- targetPeer: peer
1313
- };
1314
- }
1315
- }
1316
- if (currentMode && this.modeToPeer[currentMode]) {
1317
- return {
1318
- text,
1319
- targetPeer: this.modeToPeer[currentMode]
1320
- };
1321
- }
1322
- return {
1323
- text,
1324
- targetPeer: null
1325
- };
1326
- }
1327
- };
1328
-
1329
- // src/adapters/acp/BandACPServerAdapter.ts
1330
- var DEFAULT_MAX_SESSIONS = 100;
1331
- var DEFAULT_PROMPT_TIMEOUT_MS = 3e5;
1332
- var DEFAULT_PROMPT_COMPLETION_GRACE_MS = 250;
1333
- var BandACPServerAdapter = class extends SimpleAdapter {
1334
- bandRest;
1335
- maxSessions;
1336
- responseTimeoutMs;
1337
- promptCompletionGraceMs;
1338
- sessionModes;
1339
- sessionToRoom = /* @__PURE__ */ new Map();
1340
- roomToSession = /* @__PURE__ */ new Map();
1341
- pendingPrompts = /* @__PURE__ */ new Map();
1342
- sessionModeIds = /* @__PURE__ */ new Map();
1343
- sessionModelIds = /* @__PURE__ */ new Map();
1344
- sessionCwds = /* @__PURE__ */ new Map();
1345
- sessionMcpServers = /* @__PURE__ */ new Map();
1346
- connection = null;
1347
- agentId = null;
1348
- sessionsInFlight = 0;
1349
- router;
1350
- pushHandler;
1351
- constructor(options) {
1352
- super({
1353
- historyConverter: new ACPServerHistoryConverter()
1354
- });
1355
- this.bandRest = options.bandRest;
1356
- this.maxSessions = options.maxSessions ?? DEFAULT_MAX_SESSIONS;
1357
- this.responseTimeoutMs = options.responseTimeoutMs ?? DEFAULT_PROMPT_TIMEOUT_MS;
1358
- this.promptCompletionGraceMs = options.promptCompletionGraceMs ?? DEFAULT_PROMPT_COMPLETION_GRACE_MS;
1359
- this.sessionModes = options.sessionModes?.length ? [...options.sessionModes] : [...DEFAULT_ACP_SERVER_MODES];
1360
- this.router = new AgentRouter({
1361
- modeToPeer: options.modeToPeer,
1362
- slashCommands: options.slashCommands
1363
- });
1364
- this.pushHandler = new ACPPushHandler(this);
1365
- }
1366
- bindConnection(connection) {
1367
- this.connection = connection;
1368
- }
1369
- getConnection() {
1370
- return this.connection;
1371
- }
1372
- hasSession(sessionId) {
1373
- return this.sessionToRoom.has(sessionId);
1374
- }
1375
- getSessionIds() {
1376
- return [...this.sessionToRoom.keys()];
1377
- }
1378
- getSessionForRoom(roomId) {
1379
- return this.roomToSession.get(roomId) ?? null;
1380
- }
1381
- get displayName() {
1382
- return this.agentName;
1383
- }
1384
- getSessionCwd(sessionId) {
1385
- return this.sessionCwds.get(sessionId) ?? ".";
1386
- }
1387
- getModeState(sessionId) {
1388
- return {
1389
- availableModes: this.sessionModes,
1390
- currentModeId: this.sessionModeIds.get(sessionId) ?? this.sessionModes[0]?.id ?? "default"
1391
- };
1392
- }
1393
- setSessionModes(modes) {
1394
- this.sessionModes.splice(0, this.sessionModes.length, ...modes);
1395
- }
1396
- setSessionMode(sessionId, modeId) {
1397
- this.sessionModeIds.set(sessionId, modeId);
1398
- }
1399
- setSessionModel(sessionId, modelId) {
1400
- this.sessionModelIds.set(sessionId, modelId);
1401
- }
1402
- updateSessionContext(sessionId, input) {
1403
- if (input.cwd) {
1404
- this.sessionCwds.set(sessionId, input.cwd);
1405
- }
1406
- if (input.mcpServers) {
1407
- const normalized = normalizeMcpServers(input.mcpServers);
1408
- if (normalized.length > 0) {
1409
- this.sessionMcpServers.set(sessionId, normalized);
1410
- } else {
1411
- this.sessionMcpServers.delete(sessionId);
1412
- }
1413
- }
1414
- }
1415
- async verifyCredentials() {
1416
- try {
1417
- await this.bandRest.getAgentMe();
1418
- return true;
1419
- } catch {
1420
- return false;
1421
- }
1422
- }
1423
- async onStarted(agentName, agentDescription) {
1424
- await super.onStarted(agentName, agentDescription);
1425
- try {
1426
- const identity = await this.bandRest.getAgentMe();
1427
- this.agentId = identity.id;
1428
- } catch {
1429
- this.agentId = null;
1430
- }
1431
- }
1432
- async createSession(input = {}) {
1433
- this.sessionsInFlight += 1;
1434
- const activeCount = this.sessionToRoom.size + this.sessionsInFlight;
1435
- if (activeCount > this.maxSessions) {
1436
- this.sessionsInFlight = Math.max(0, this.sessionsInFlight - 1);
1437
- throw new Error(`Maximum ACP sessions (${this.maxSessions}) reached`);
1438
- }
1439
- try {
1440
- const room = await this.bandRest.createChat();
1441
- const sessionId = randomUUID();
1442
- const normalizedMcpServers = normalizeMcpServers(input.mcpServers);
1443
- this.sessionToRoom.set(sessionId, room.id);
1444
- this.roomToSession.set(room.id, sessionId);
1445
- this.sessionCwds.set(sessionId, input.cwd ?? ".");
1446
- if (normalizedMcpServers.length > 0) {
1447
- this.sessionMcpServers.set(sessionId, normalizedMcpServers);
1448
- }
1449
- try {
1450
- await this.bandRest.createChatEvent(room.id, {
1451
- content: "ACP session context",
1452
- messageType: "task",
1453
- metadata: {
1454
- acp_session_id: sessionId,
1455
- acp_room_id: room.id,
1456
- acp_cwd: input.cwd ?? ".",
1457
- acp_mcp_servers: normalizedMcpServers
1458
- }
1459
- });
1460
- } catch (error) {
1461
- this.sessionToRoom.delete(sessionId);
1462
- this.roomToSession.delete(room.id);
1463
- this.sessionCwds.delete(sessionId);
1464
- this.sessionMcpServers.delete(sessionId);
1465
- this.sessionModeIds.delete(sessionId);
1466
- this.sessionModelIds.delete(sessionId);
1467
- throw error;
1468
- }
1469
- return sessionId;
1470
- } finally {
1471
- this.sessionsInFlight = Math.max(0, this.sessionsInFlight - 1);
1472
- }
1473
- }
1474
- async handlePrompt(sessionId, text) {
1475
- const roomId = this.sessionToRoom.get(sessionId);
1476
- if (!roomId) {
1477
- throw new Error(`Unknown ACP session: ${sessionId}`);
1478
- }
1479
- const pending = createPendingPrompt(sessionId);
1480
- this.pendingPrompts.set(roomId, pending);
1481
- const resolved = this.router.resolve(text, this.sessionModeIds.get(sessionId));
1482
- const promptText = this.prependSessionContext(sessionId, resolved.text);
1483
- const participants = await this.bandRest.listChatParticipants(roomId);
1484
- const mentions = this.resolveMentions(participants, resolved.targetPeer);
1485
- try {
1486
- await this.bandRest.createChatMessage(roomId, {
1487
- content: promptText,
1488
- mentions
1489
- });
1490
- await Promise.race([
1491
- pending.done,
1492
- new Promise((_, reject) => {
1493
- const timer = setTimeout(() => {
1494
- reject(new Error(`ACP prompt timed out after ${this.responseTimeoutMs}ms`));
1495
- }, this.responseTimeoutMs);
1496
- pending.done.finally(() => clearTimeout(timer)).catch(() => void 0);
1497
- })
1498
- ]);
1499
- } finally {
1500
- this.finishPendingPrompt(roomId, pending, false);
1501
- }
1502
- }
1503
- async onMessage(message, _tools, history, _participantsMessage, _contactsMessage, context) {
1504
- if (context.isSessionBootstrap) {
1505
- this.rehydrate(history);
1506
- }
1507
- if (this.agentId && message.senderId === this.agentId) {
1508
- return;
1509
- }
1510
- const pending = this.pendingPrompts.get(context.roomId);
1511
- if (pending && this.connection) {
1512
- const update = EventConverter.convert(message);
1513
- if (update) {
1514
- await this.connection.sessionUpdate({
1515
- sessionId: pending.sessionId,
1516
- update
1517
- });
1518
- }
1519
- if (message.messageType === "text" || message.messageType === "error") {
1520
- pending.terminalMessageSeen = true;
1521
- }
1522
- if (update || pending.terminalMessageSeen) {
1523
- this.schedulePromptCompletion(context.roomId, pending);
1524
- }
1525
- return;
1526
- }
1527
- if (this.connection) {
1528
- await this.pushHandler.handlePushEvent(message, context.roomId);
1529
- }
1530
- }
1531
- async onCleanup(roomId) {
1532
- const sessionId = this.roomToSession.get(roomId);
1533
- if (sessionId) {
1534
- this.roomToSession.delete(roomId);
1535
- this.sessionToRoom.delete(sessionId);
1536
- this.sessionModeIds.delete(sessionId);
1537
- this.sessionModelIds.delete(sessionId);
1538
- this.sessionCwds.delete(sessionId);
1539
- this.sessionMcpServers.delete(sessionId);
1540
- }
1541
- this.finishPendingPrompt(roomId, this.pendingPrompts.get(roomId) ?? null, true);
1542
- }
1543
- async onRuntimeStop() {
1544
- for (const roomId of [...this.roomToSession.keys()]) {
1545
- await this.onCleanup(roomId);
1546
- }
1547
- this.connection = null;
1548
- }
1549
- async cancelPrompt(sessionId) {
1550
- const roomId = this.sessionToRoom.get(sessionId);
1551
- if (!roomId) {
1552
- return;
1553
- }
1554
- this.finishPendingPrompt(roomId, this.pendingPrompts.get(roomId) ?? null, true);
1555
- }
1556
- async closeSession(sessionId) {
1557
- const roomId = this.sessionToRoom.get(sessionId);
1558
- if (!roomId) {
1559
- return;
1560
- }
1561
- await this.onCleanup(roomId);
1562
- }
1563
- rehydrate(history) {
1564
- for (const [sessionId, roomId] of Object.entries(history.sessionToRoom)) {
1565
- if (!this.sessionToRoom.has(sessionId)) {
1566
- this.sessionToRoom.set(sessionId, roomId);
1567
- }
1568
- if (!this.roomToSession.has(roomId)) {
1569
- this.roomToSession.set(roomId, sessionId);
1570
- }
1571
- }
1572
- for (const [sessionId, cwd] of Object.entries(history.sessionCwd)) {
1573
- if (!this.sessionCwds.has(sessionId)) {
1574
- this.sessionCwds.set(sessionId, cwd);
1575
- }
1576
- }
1577
- for (const [sessionId, mcpServers] of Object.entries(history.sessionMcpServers)) {
1578
- if (!this.sessionMcpServers.has(sessionId)) {
1579
- this.sessionMcpServers.set(sessionId, [...mcpServers]);
1580
- }
1581
- }
1582
- }
1583
- prependSessionContext(sessionId, text) {
1584
- const cwd = this.sessionCwds.get(sessionId);
1585
- const mcpServers = this.sessionMcpServers.get(sessionId) ?? [];
1586
- if (!cwd && mcpServers.length === 0) {
1587
- return text;
1588
- }
1589
- const lines = ["[ACP Session Context]"];
1590
- if (cwd) {
1591
- lines.push(`Editor cwd: ${cwd}`);
1592
- }
1593
- if (mcpServers.length > 0) {
1594
- lines.push("Editor MCP servers:");
1595
- lines.push(...mcpServers.map((server) => formatMcpServerLine(server)));
1596
- lines.push("These MCP servers belong to the connected editor session.");
1597
- }
1598
- lines.push("");
1599
- lines.push(text);
1600
- return lines.join("\n");
1601
- }
1602
- resolveMentions(participants, targetPeer) {
1603
- const filtered = participants.filter((participant) => {
1604
- if (this.agentId && participant.id === this.agentId) {
1605
- return false;
1606
- }
1607
- if (!targetPeer) {
1608
- return true;
1609
- }
1610
- const normalizedTarget = targetPeer.startsWith("@") ? targetPeer.slice(1) : targetPeer;
1611
- const participantHandles = [
1612
- participant.handle,
1613
- ensureHandlePrefix(participant.handle)?.slice(1)
1614
- ].filter((value) => typeof value === "string" && value.length > 0);
1615
- return participant.name === targetPeer || participant.name === normalizedTarget || participantHandles.includes(targetPeer) || participantHandles.includes(normalizedTarget);
1616
- });
1617
- return filtered.map((participant) => ({
1618
- id: participant.id,
1619
- handle: participant.handle ?? void 0,
1620
- name: participant.name
1621
- }));
1622
- }
1623
- schedulePromptCompletion(roomId, pending) {
1624
- if (this.pendingPrompts.get(roomId) !== pending) {
1625
- return;
1626
- }
1627
- if (pending.completionTimer) {
1628
- clearTimeout(pending.completionTimer);
1629
- }
1630
- pending.completionTimer = setTimeout(() => {
1631
- this.finishPendingPrompt(roomId, pending, true);
1632
- }, this.promptCompletionGraceMs);
1633
- }
1634
- finishPendingPrompt(roomId, pending, setDone) {
1635
- const current = this.pendingPrompts.get(roomId);
1636
- if (!current || pending && current !== pending) {
1637
- return;
1638
- }
1639
- this.pendingPrompts.delete(roomId);
1640
- if (current.completionTimer) {
1641
- clearTimeout(current.completionTimer);
1642
- current.completionTimer = null;
1643
- }
1644
- if (setDone) {
1645
- current.markDone();
1646
- }
1647
- }
1648
- };
1649
- function formatMcpServerLine(server) {
1650
- const type = typeof server.type === "string" ? server.type : "unknown";
1651
- const name = typeof server.name === "string" ? server.name : typeof server.command === "string" ? server.command : typeof server.url === "string" ? server.url : "unnamed";
1652
- const details = [type];
1653
- if (type === "stdio" && typeof server.command === "string") {
1654
- details.push(`command=${server.command}`);
1655
- }
1656
- if (type !== "stdio" && typeof server.url === "string") {
1657
- details.push(`url=${server.url}`);
1658
- }
1659
- return `- ${name} (${details.join(", ")})`;
1660
- }
1661
-
1662
- // src/adapters/acp/cursorExtensions.ts
1663
- var CursorExtensionHandler = class {
1664
- async extMethod(method, params) {
1665
- if (method === "cursor/ask_question") {
1666
- const options = Array.isArray(params.options) ? params.options : [];
1667
- const first = options.find((option) => !!option && typeof option === "object");
1668
- if (!first) {
1669
- return {
1670
- outcome: {
1671
- type: "cancelled"
1672
- }
1673
- };
1674
- }
1675
- return {
1676
- outcome: {
1677
- type: "selected",
1678
- optionId: String(first.optionId ?? first.id ?? "0")
1679
- }
1680
- };
1681
- }
1682
- if (method === "cursor/create_plan") {
1683
- return {
1684
- outcome: {
1685
- type: "approved"
1686
- }
1687
- };
1688
- }
1689
- return null;
1690
- }
1691
- async extNotification(method, params, context) {
1692
- if (method === "cursor/update_todos") {
1693
- const todos = Array.isArray(params.todos) ? params.todos : [];
1694
- const text = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).join("\n");
1695
- if (text.length > 0) {
1696
- await context.connection.sessionUpdate({
1697
- sessionId: context.sessionId,
1698
- update: {
1699
- sessionUpdate: "agent_message_chunk",
1700
- content: {
1701
- type: "text",
1702
- text
1703
- }
1704
- }
1705
- });
1706
- }
1707
- return;
1708
- }
1709
- if (method === "cursor/task" && typeof params.result === "string" && params.result.length > 0) {
1710
- await context.connection.sessionUpdate({
1711
- sessionId: context.sessionId,
1712
- update: {
1713
- sessionUpdate: "agent_message_chunk",
1714
- content: {
1715
- type: "text",
1716
- text: `[Task completed] ${params.result}`
1717
- }
1718
- }
1719
- });
1720
- }
1721
- }
1722
- };
1723
-
1724
- // src/adapters/acp/ACPServer.ts
1725
- var ACPServer = class {
1726
- adapter;
1727
- authMethods;
1728
- agentInfo;
1729
- extensionHandler;
1730
- connection = null;
1731
- constructor(adapter, options) {
1732
- this.adapter = adapter;
1733
- if (options?.modes?.length) {
1734
- this.adapter.setSessionModes([...options.modes]);
1735
- }
1736
- this.authMethods = options?.authMethods ?? [{
1737
- id: "api_key",
1738
- name: "API Key",
1739
- description: "Authenticate with BAND_API_KEY (legacy THENVOI_API_KEY)."
1740
- }];
1741
- this.agentInfo = options?.agentInfo ?? {};
1742
- this.extensionHandler = options?.extensionHandler ?? new CursorExtensionHandler();
1743
- }
1744
- async connectStream(stream) {
1745
- if (this.connection && !this.connection.signal.aborted) {
1746
- return this.connection;
1747
- }
1748
- const acp = await acpModule.get();
1749
- this.connection = new acp.AgentSideConnection((connection) => {
1750
- this.adapter.bindConnection(connection);
1751
- return this;
1752
- }, stream);
1753
- void this.connection.closed.finally(() => {
1754
- if (this.connection?.signal.aborted) {
1755
- this.adapter.bindConnection(null);
1756
- }
1757
- });
1758
- return this.connection;
1759
- }
1760
- connectStdio(options) {
1761
- const stdin = options?.stdin ?? process.stdin;
1762
- const stdout = options?.stdout ?? process.stdout;
1763
- return acpModule.get().then((acp) => this.connectStream(
1764
- acp.ndJsonStream(
1765
- Writable2.toWeb(stdout),
1766
- Readable2.toWeb(stdin)
1767
- )
1768
- ));
1769
- }
1770
- get closed() {
1771
- return this.connection?.closed ?? Promise.resolve();
1772
- }
1773
- async initialize(params) {
1774
- const acp = await acpModule.get();
1775
- return {
1776
- protocolVersion: params.protocolVersion === acp.PROTOCOL_VERSION ? params.protocolVersion : acp.PROTOCOL_VERSION,
1777
- agentCapabilities: {
1778
- loadSession: true,
1779
- promptCapabilities: {
1780
- image: false,
1781
- audio: false,
1782
- embeddedContext: true
1783
- },
1784
- sessionCapabilities: {
1785
- list: {},
1786
- resume: {},
1787
- fork: {},
1788
- close: {}
1789
- }
1790
- },
1791
- agentInfo: {
1792
- name: this.agentInfo.name ?? "band-agent",
1793
- title: this.agentInfo.title ?? this.adapter.displayName ?? "Band Agent",
1794
- version: this.agentInfo.version ?? "0.1.0"
1795
- },
1796
- authMethods: this.authMethods
1797
- };
1798
- }
1799
- async newSession(params) {
1800
- const sessionId = await this.adapter.createSession({
1801
- cwd: params.cwd,
1802
- mcpServers: params.mcpServers
1803
- });
1804
- return {
1805
- sessionId,
1806
- modes: this.adapter.getModeState(sessionId)
1807
- };
1808
- }
1809
- async loadSession(params) {
1810
- if (!this.adapter.hasSession(params.sessionId)) {
1811
- const acp = await acpModule.get();
1812
- throw acp.RequestError.resourceNotFound(params.sessionId);
1813
- }
1814
- this.adapter.updateSessionContext(params.sessionId, {
1815
- cwd: params.cwd,
1816
- mcpServers: params.mcpServers
1817
- });
1818
- return {
1819
- modes: this.adapter.getModeState(params.sessionId)
1820
- };
1821
- }
1822
- async listSessions(params) {
1823
- const sessions = this.adapter.getSessionIds().filter((sessionId) => !params.cwd || this.adapter.getSessionCwd(sessionId) === params.cwd).map((sessionId) => ({
1824
- sessionId,
1825
- cwd: this.adapter.getSessionCwd(sessionId)
1826
- }));
1827
- return { sessions };
1828
- }
1829
- async unstable_forkSession(params) {
1830
- if (!this.adapter.hasSession(params.sessionId)) {
1831
- const acp = await acpModule.get();
1832
- throw acp.RequestError.resourceNotFound(params.sessionId);
1833
- }
1834
- const sessionId = await this.adapter.createSession({
1835
- cwd: params.cwd,
1836
- mcpServers: params.mcpServers
1837
- });
1838
- return {
1839
- sessionId
1840
- };
1841
- }
1842
- async unstable_resumeSession(params) {
1843
- if (!this.adapter.hasSession(params.sessionId)) {
1844
- const acp = await acpModule.get();
1845
- throw acp.RequestError.resourceNotFound(params.sessionId);
1846
- }
1847
- this.adapter.updateSessionContext(params.sessionId, {
1848
- cwd: params.cwd,
1849
- mcpServers: params.mcpServers
1850
- });
1851
- return {
1852
- modes: this.adapter.getModeState(params.sessionId)
1853
- };
1854
- }
1855
- async unstable_closeSession(params) {
1856
- await this.adapter.closeSession(params.sessionId);
1857
- return {};
1858
- }
1859
- async setSessionMode(params) {
1860
- if (!this.adapter.hasSession(params.sessionId)) {
1861
- const acp = await acpModule.get();
1862
- throw acp.RequestError.resourceNotFound(params.sessionId);
1863
- }
1864
- this.adapter.setSessionMode(params.sessionId, params.modeId);
1865
- return {};
1866
- }
1867
- async unstable_setSessionModel(params) {
1868
- if (!this.adapter.hasSession(params.sessionId)) {
1869
- const acp = await acpModule.get();
1870
- throw acp.RequestError.resourceNotFound(params.sessionId);
1871
- }
1872
- this.adapter.setSessionModel(params.sessionId, params.modelId);
1873
- return {};
1874
- }
1875
- async setSessionConfigOption(_params) {
1876
- return {
1877
- configOptions: []
1878
- };
1879
- }
1880
- async authenticate(params) {
1881
- if (params.methodId !== "api_key" && params.methodId !== "cursor_login") {
1882
- const acp = await acpModule.get();
1883
- throw acp.RequestError.authRequired(void 0, `Unsupported auth method: ${params.methodId}`);
1884
- }
1885
- const ok = await this.adapter.verifyCredentials();
1886
- if (!ok) {
1887
- const acp = await acpModule.get();
1888
- throw acp.RequestError.authRequired(void 0, "Authentication failed");
1889
- }
1890
- return {};
1891
- }
1892
- async prompt(params) {
1893
- const text = extractPromptText(params.prompt);
1894
- await this.adapter.handlePrompt(params.sessionId, text);
1895
- return {
1896
- stopReason: "end_turn"
1897
- };
1898
- }
1899
- async cancel(params) {
1900
- await this.adapter.cancelPrompt(params.sessionId);
1901
- }
1902
- async extMethod(method, params) {
1903
- if (this.extensionHandler.extMethod) {
1904
- const result = await this.extensionHandler.extMethod(method, params);
1905
- if (result) {
1906
- return result;
1907
- }
1908
- }
1909
- return {
1910
- error: `Unknown extension method: ${method}`
1911
- };
1912
- }
1913
- async extNotification(method, params) {
1914
- const sessionId = typeof params.sessionId === "string" ? params.sessionId : typeof params.session_id === "string" ? params.session_id : null;
1915
- if (!sessionId || !this.adapter.hasSession(sessionId)) {
1916
- return;
1917
- }
1918
- const connection = this.adapter.getConnection();
1919
- if (!connection) {
1920
- return;
1921
- }
1922
- if (this.extensionHandler.extNotification) {
1923
- await this.extensionHandler.extNotification(method, params, { sessionId, connection });
1924
- }
1925
- }
1926
- };
1927
- function extractPromptText(prompt) {
1928
- const parts = [];
1929
- for (const block of prompt) {
1930
- if (block.type === "text") {
1931
- parts.push(block.text);
1932
- continue;
1933
- }
1934
- if (block.type === "image") {
1935
- parts.push(block.uri ? `[Image: ${block.uri}]` : "[Image]");
1936
- continue;
1937
- }
1938
- if (block.type === "audio") {
1939
- parts.push("[Audio]");
1940
- continue;
1941
- }
1942
- if (block.type === "resource_link") {
1943
- parts.push(`[Resource: ${block.title ?? block.name ?? block.uri}]`);
1944
- continue;
1945
- }
1946
- if (block.type === "resource") {
1947
- const resource = block.resource;
1948
- if ("text" in resource && typeof resource.text === "string") {
1949
- parts.push(resource.text);
1950
- } else {
1951
- parts.push(`[Resource: ${resource.uri ?? "embedded"}]`);
1952
- }
1953
- }
1954
- }
1955
- return parts.join("\n").trim();
1956
- }
46
+ createGatewayServer
47
+ } from "./chunk-HNWZZXAE.js";
48
+ import "./chunk-OGOXY43L.js";
49
+ import "./chunk-CBCHTTUY.js";
50
+ import "./chunk-RJNJUISR.js";
51
+ import "./chunk-SHZBLLDQ.js";
52
+ import "./chunk-JIZE4EC6.js";
53
+ import "./chunk-6BE2EG5P.js";
54
+ import "./chunk-TGDDFBYS.js";
55
+ import "./chunk-ZYOXI4JL.js";
56
+ import "./chunk-4OVNFDD7.js";
1957
57
  export {
1958
58
  A2AAdapter,
1959
59
  A2AGatewayAdapter,
@@ -1970,6 +70,9 @@ export {
1970
70
  CodexAdapter,
1971
71
  CodexAppServerStdioClient,
1972
72
  CodexJsonRpcError,
73
+ CopilotACPAdapter,
74
+ DEFAULT_COPILOT_ACP_COMMAND,
75
+ DEFAULT_OMP_ACP_COMMAND,
1973
76
  GatewayHistoryConverter,
1974
77
  GatewayServer,
1975
78
  GeminiAdapter,
@@ -1981,6 +84,7 @@ export {
1981
84
  LangGraphAdapter,
1982
85
  LettaAdapter,
1983
86
  LettaHistoryConverter,
87
+ OmpACPAdapter,
1984
88
  OpenAIAdapter,
1985
89
  OpenAIToolCallingModel,
1986
90
  OpencodeAdapter,