@band-ai/sdk 0.4.1 → 0.4.3

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.
package/dist/adapters.cjs CHANGED
@@ -978,6 +978,7 @@ __export(adapters_exports, {
978
978
  A2AHistoryConverter: () => A2AHistoryConverter,
979
979
  ACPClientAdapter: () => ACPClientAdapter,
980
980
  ACPServer: () => ACPServer,
981
+ AcpSessionConfigError: () => AcpSessionConfigError,
981
982
  AnthropicAdapter: () => AnthropicAdapter,
982
983
  AnthropicToolCallingModel: () => AnthropicToolCallingModel,
983
984
  BandACPServerAdapter: () => BandACPServerAdapter,
@@ -989,8 +990,11 @@ __export(adapters_exports, {
989
990
  CodexAppServerStdioClient: () => CodexAppServerStdioClient,
990
991
  CodexJsonRpcError: () => CodexJsonRpcError,
991
992
  CopilotACPAdapter: () => CopilotACPAdapter,
993
+ CursorACPAdapter: () => CursorACPAdapter,
992
994
  DEFAULT_COPILOT_ACP_COMMAND: () => DEFAULT_COPILOT_ACP_COMMAND,
995
+ DEFAULT_CURSOR_ACP_COMMAND: () => DEFAULT_CURSOR_ACP_COMMAND,
993
996
  DEFAULT_OMP_ACP_COMMAND: () => DEFAULT_OMP_ACP_COMMAND,
997
+ FAILURE_CODE_SESSION_CONFIG: () => FAILURE_CODE_SESSION_CONFIG,
994
998
  GatewayHistoryConverter: () => GatewayHistoryConverter,
995
999
  GatewayServer: () => GatewayServer,
996
1000
  GeminiAdapter: () => GeminiAdapter,
@@ -1002,6 +1006,7 @@ __export(adapters_exports, {
1002
1006
  LangGraphAdapter: () => LangGraphAdapter,
1003
1007
  LettaAdapter: () => LettaAdapter,
1004
1008
  LettaHistoryConverter: () => LettaHistoryConverter,
1009
+ MISSING_CONFIG_OPTIONS_REASON: () => MISSING_CONFIG_OPTIONS_REASON,
1005
1010
  OmpACPAdapter: () => OmpACPAdapter,
1006
1011
  OpenAIAdapter: () => OpenAIAdapter,
1007
1012
  OpenAIToolCallingModel: () => OpenAIToolCallingModel,
@@ -1011,6 +1016,7 @@ __export(adapters_exports, {
1011
1016
  ToolCallingAdapter: () => ToolCallingAdapter,
1012
1017
  VercelAISDKAdapter: () => VercelAISDKAdapter,
1013
1018
  VercelAISDKToolCallingModel: () => VercelAISDKToolCallingModel,
1019
+ applySessionConfigSelections: () => applySessionConfigSelections,
1014
1020
  buildA2AAuthHeaders: () => buildA2AAuthHeaders,
1015
1021
  createGatewayServer: () => createGatewayServer,
1016
1022
  runSingleToolRound: () => runSingleToolRound
@@ -1730,6 +1736,149 @@ function abandon(operation, onError = () => void 0) {
1730
1736
  void Promise.resolve().then(operation).catch(onError);
1731
1737
  }
1732
1738
 
1739
+ // src/adapters/acp/sessionConfigReconciliation.ts
1740
+ var FAILURE_CODE_SESSION_CONFIG = "session_config";
1741
+ var MISSING_CONFIG_OPTIONS_REASON = "missing_config_options";
1742
+ var AcpSessionConfigTimeoutError = class extends Error {
1743
+ };
1744
+ var AcpSessionConfigError = class extends Error {
1745
+ provider;
1746
+ sessionId;
1747
+ optionId;
1748
+ selectedValue;
1749
+ acpCode;
1750
+ detail;
1751
+ timedOut;
1752
+ constructor(input) {
1753
+ super(input.message, input.cause !== void 0 ? { cause: input.cause } : void 0);
1754
+ this.name = "AcpSessionConfigError";
1755
+ this.provider = input.provider;
1756
+ this.sessionId = input.sessionId;
1757
+ this.optionId = input.optionId;
1758
+ this.selectedValue = input.selectedValue;
1759
+ this.acpCode = input.acpCode;
1760
+ this.detail = input.detail;
1761
+ this.timedOut = input.timedOut ?? false;
1762
+ }
1763
+ toAgentFailure() {
1764
+ return agentFailure(
1765
+ this.provider,
1766
+ this.message,
1767
+ this.acpCode !== void 0 ? String(this.acpCode) : FAILURE_CODE_SESSION_CONFIG,
1768
+ {
1769
+ sessionId: this.sessionId,
1770
+ optionId: this.optionId,
1771
+ selectedValue: this.selectedValue,
1772
+ detail: this.detail
1773
+ }
1774
+ );
1775
+ }
1776
+ };
1777
+ async function applySessionConfigSelections(input) {
1778
+ let catalog = input.catalog;
1779
+ for (const { configId, value: selectedValue } of sessionConfigSelectionEntries(input.selections)) {
1780
+ if (selectedValue === void 0) {
1781
+ continue;
1782
+ }
1783
+ const option = catalog.find((entry) => entry?.id === configId);
1784
+ if (!option || !isSessionConfigSelect(option)) {
1785
+ throw new AcpSessionConfigError({
1786
+ provider: input.provider,
1787
+ sessionId: input.sessionId,
1788
+ optionId: configId,
1789
+ selectedValue,
1790
+ message: `Session config option "${configId}" is not available after prior selections.`
1791
+ });
1792
+ }
1793
+ if (selectedValue === option.currentValue) {
1794
+ continue;
1795
+ }
1796
+ const availableValues = flattenConfigSelectOptions(option.options).map((entry) => entry.value);
1797
+ if (!availableValues.includes(selectedValue)) {
1798
+ throw new AcpSessionConfigError({
1799
+ provider: input.provider,
1800
+ sessionId: input.sessionId,
1801
+ optionId: configId,
1802
+ selectedValue,
1803
+ message: `Session config value "${selectedValue}" is not advertised for option "${configId}".`,
1804
+ detail: { availableValues }
1805
+ });
1806
+ }
1807
+ const timeoutMessage = `setSessionConfigOption did not respond within ${input.timeoutMs}ms`;
1808
+ try {
1809
+ const response = await withTimeout(
1810
+ input.setOption({ sessionId: input.sessionId, configId, value: selectedValue }),
1811
+ input.timeoutMs,
1812
+ () => new AcpSessionConfigTimeoutError(timeoutMessage)
1813
+ );
1814
+ if (!Array.isArray(response?.configOptions)) {
1815
+ throw new AcpSessionConfigError({
1816
+ provider: input.provider,
1817
+ sessionId: input.sessionId,
1818
+ optionId: configId,
1819
+ selectedValue,
1820
+ message: `Session config option "${configId}" response did not include a refreshed catalog.`,
1821
+ detail: { reason: MISSING_CONFIG_OPTIONS_REASON }
1822
+ });
1823
+ }
1824
+ catalog = response.configOptions;
1825
+ } catch (error) {
1826
+ if (error instanceof AcpSessionConfigError) {
1827
+ throw error;
1828
+ }
1829
+ const acpError = asAcpJsonRpcError(error);
1830
+ throw new AcpSessionConfigError({
1831
+ provider: input.provider,
1832
+ sessionId: input.sessionId,
1833
+ optionId: configId,
1834
+ selectedValue,
1835
+ acpCode: acpError?.code,
1836
+ detail: acpError?.data,
1837
+ message: acpError?.message ?? asErrorMessage(error),
1838
+ cause: error,
1839
+ timedOut: error instanceof AcpSessionConfigTimeoutError
1840
+ });
1841
+ }
1842
+ }
1843
+ return { catalog };
1844
+ }
1845
+ function sessionConfigSelectionEntries(selections) {
1846
+ if (isOrderedSessionConfigSelections(selections)) {
1847
+ return selections;
1848
+ }
1849
+ return Object.keys(selections).map((configId) => ({ configId, value: selections[configId] }));
1850
+ }
1851
+ function isOrderedSessionConfigSelections(selections) {
1852
+ return Array.isArray(selections);
1853
+ }
1854
+ function isSessionConfigSelect(option) {
1855
+ return option?.type === "select";
1856
+ }
1857
+ function flattenConfigSelectOptions(options) {
1858
+ if (!Array.isArray(options)) {
1859
+ return [];
1860
+ }
1861
+ return options.flatMap((entry) => {
1862
+ if (!asOptionalRecord2(entry)) {
1863
+ return [];
1864
+ }
1865
+ if ("group" in entry) {
1866
+ return Array.isArray(entry.options) ? entry.options : [];
1867
+ }
1868
+ return [entry];
1869
+ });
1870
+ }
1871
+ function isAcpErrorResponse(error) {
1872
+ return typeof error === "object" && error !== null && typeof error.code === "number" && typeof error.message === "string";
1873
+ }
1874
+ function asAcpJsonRpcError(error) {
1875
+ if (isAcpErrorResponse(error)) {
1876
+ return error;
1877
+ }
1878
+ const nested = asOptionalRecord2(error)?.error;
1879
+ return isAcpErrorResponse(nested) ? nested : void 0;
1880
+ }
1881
+
1733
1882
  // src/adapters/acp/ACPClientAdapter.ts
1734
1883
  init_chatEvents();
1735
1884
  init_schemas();
@@ -2191,74 +2340,17 @@ function checkPort2(http, port) {
2191
2340
  });
2192
2341
  }
2193
2342
 
2194
- // src/adapters/acp/types.ts
2195
- var DEFAULT_ACP_SERVER_MODES = [
2196
- {
2197
- id: "default",
2198
- name: "Default",
2199
- description: "General-purpose chat mode"
2200
- },
2201
- {
2202
- id: "code",
2203
- name: "Code",
2204
- description: "Route prompts toward coding peers when available"
2205
- }
2206
- ];
2207
- function createPendingPrompt(sessionId) {
2208
- let markDone = () => void 0;
2209
- const done = new Promise((resolve) => {
2210
- markDone = resolve;
2211
- });
2212
- return {
2213
- sessionId,
2214
- done,
2215
- markDone,
2216
- terminalMessageSeen: false,
2217
- completionTimer: null
2218
- };
2219
- }
2220
- function choosePermissionOption(options) {
2221
- if (options.length === 0) {
2222
- return null;
2223
- }
2224
- return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
2225
- }
2226
- function asJsonSafe(value) {
2227
- if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2228
- return value;
2229
- }
2230
- if (Array.isArray(value)) {
2231
- return value.map((item) => asJsonSafe(item));
2232
- }
2233
- if (typeof value === "object") {
2234
- if ("model_dump" in value && typeof value.model_dump === "function") {
2235
- return asJsonSafe(value.model_dump());
2236
- }
2237
- if ("toJSON" in value && typeof value.toJSON === "function") {
2238
- return asJsonSafe(value.toJSON());
2239
- }
2240
- return Object.fromEntries(
2241
- Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
2242
- );
2243
- }
2244
- return String(value);
2245
- }
2246
- function normalizeMcpServers(mcpServers) {
2247
- if (!mcpServers) {
2248
- return [];
2249
- }
2250
- return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
2251
- }
2252
-
2253
2343
  // src/adapters/acp/client.ts
2254
2344
  var BandACPClient = class {
2255
2345
  sessionChunks = /* @__PURE__ */ new Map();
2256
2346
  permissionHandler;
2347
+ extensionHandler;
2257
2348
  // The handler is connection-scoped and required at construction, so it is
2258
2349
  // already in place before the agent process is spawned: there is no window
2259
2350
  // in which a `session/request_permission` has nowhere to go.
2260
- constructor(permissionHandler) {
2351
+ constructor(permissionHandler, extensionHandler) {
2261
2352
  this.permissionHandler = permissionHandler;
2353
+ this.extensionHandler = extensionHandler;
2262
2354
  }
2263
2355
  beginSession(sessionId) {
2264
2356
  this.sessionChunks.set(sessionId, []);
@@ -2303,62 +2395,26 @@ var BandACPClient = class {
2303
2395
  return chunks;
2304
2396
  }
2305
2397
  async extMethod(method, params) {
2306
- if (method === "cursor/ask_question") {
2307
- const options = Array.isArray(params.options) ? params.options : [];
2308
- const selected = choosePermissionOption(
2309
- options.filter((option) => !!option && typeof option === "object")
2310
- );
2311
- if (!selected) {
2312
- return {
2313
- outcome: {
2314
- type: "cancelled"
2315
- }
2316
- };
2317
- }
2318
- return {
2319
- outcome: {
2320
- type: "selected",
2321
- optionId: selected.optionId
2322
- }
2323
- };
2324
- }
2325
- if (method === "cursor/create_plan") {
2326
- return {
2327
- outcome: {
2328
- type: "approved"
2329
- }
2330
- };
2331
- }
2332
- return {};
2398
+ const result = await this.extensionHandler?.extMethod?.(
2399
+ method,
2400
+ params,
2401
+ { sessionId: sessionIdFrom(params) }
2402
+ );
2403
+ return result ?? {};
2333
2404
  }
2334
2405
  async extNotification(method, params) {
2335
- const sessionId = toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
2336
- if (!sessionId) {
2337
- return;
2338
- }
2339
- if (method === "cursor/update_todos") {
2340
- const todos = Array.isArray(params.todos) ? params.todos : [];
2341
- const lines = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).filter((line) => line.trim().length > 0);
2342
- if (lines.length > 0) {
2343
- this.appendChunk(sessionId, {
2344
- chunkType: "plan",
2345
- content: lines.join("\n"),
2346
- metadata: {},
2347
- streamed: false
2348
- });
2349
- }
2406
+ const sessionId = sessionIdFrom(params);
2407
+ const chunks = await this.extensionHandler?.extNotification?.(
2408
+ method,
2409
+ params,
2410
+ { sessionId }
2411
+ );
2412
+ const targetSessionId = sessionId ?? this.extensionHandler?.extensionSessionId?.() ?? null;
2413
+ if (!targetSessionId || !chunks) {
2350
2414
  return;
2351
2415
  }
2352
- if (method === "cursor/task") {
2353
- const result = toOptionalString(params.result);
2354
- if (result) {
2355
- this.appendChunk(sessionId, {
2356
- chunkType: "text",
2357
- content: `[Task completed] ${result}`,
2358
- metadata: {},
2359
- streamed: false
2360
- });
2361
- }
2416
+ for (const chunk of chunks) {
2417
+ this.appendChunk(targetSessionId, chunk);
2362
2418
  }
2363
2419
  }
2364
2420
  appendChunk(sessionId, chunk) {
@@ -2483,6 +2539,68 @@ function extractTextFromContent(content) {
2483
2539
  function toOptionalString(value) {
2484
2540
  return typeof value === "string" && value.length > 0 ? value : null;
2485
2541
  }
2542
+ function sessionIdFrom(params) {
2543
+ return toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
2544
+ }
2545
+
2546
+ // src/adapters/acp/types.ts
2547
+ var DEFAULT_ACP_SERVER_MODES = [
2548
+ {
2549
+ id: "default",
2550
+ name: "Default",
2551
+ description: "General-purpose chat mode"
2552
+ },
2553
+ {
2554
+ id: "code",
2555
+ name: "Code",
2556
+ description: "Route prompts toward coding peers when available"
2557
+ }
2558
+ ];
2559
+ function createPendingPrompt(sessionId) {
2560
+ let markDone = () => void 0;
2561
+ const done = new Promise((resolve) => {
2562
+ markDone = resolve;
2563
+ });
2564
+ return {
2565
+ sessionId,
2566
+ done,
2567
+ markDone,
2568
+ terminalMessageSeen: false,
2569
+ completionTimer: null
2570
+ };
2571
+ }
2572
+ function choosePermissionOption(options) {
2573
+ if (options.length === 0) {
2574
+ return null;
2575
+ }
2576
+ return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
2577
+ }
2578
+ function asJsonSafe(value) {
2579
+ if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2580
+ return value;
2581
+ }
2582
+ if (Array.isArray(value)) {
2583
+ return value.map((item) => asJsonSafe(item));
2584
+ }
2585
+ if (typeof value === "object") {
2586
+ if ("model_dump" in value && typeof value.model_dump === "function") {
2587
+ return asJsonSafe(value.model_dump());
2588
+ }
2589
+ if ("toJSON" in value && typeof value.toJSON === "function") {
2590
+ return asJsonSafe(value.toJSON());
2591
+ }
2592
+ return Object.fromEntries(
2593
+ Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
2594
+ );
2595
+ }
2596
+ return String(value);
2597
+ }
2598
+ function normalizeMcpServers(mcpServers) {
2599
+ if (!mcpServers) {
2600
+ return [];
2601
+ }
2602
+ return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
2603
+ }
2486
2604
 
2487
2605
  // src/adapters/acp/loader.ts
2488
2606
  init_errors();
@@ -2546,6 +2664,13 @@ var acpModule = new LazyAsyncValue({
2546
2664
  });
2547
2665
 
2548
2666
  // src/adapters/acp/ACPClientAdapter.ts
2667
+ function createConnectionRetirement() {
2668
+ let reject = () => void 0;
2669
+ const promise = new Promise((_resolve, rejectPromise) => {
2670
+ reject = rejectPromise;
2671
+ });
2672
+ return { promise, reject };
2673
+ }
2549
2674
  var DEFAULT_PERMISSION_TIMEOUT_MS = 5 * 6e4;
2550
2675
  var DEFAULT_TURN_TIMEOUT_MS = 60 * 6e4;
2551
2676
  var SET_SESSION_CONFIG_TIMEOUT_MS = 1e4;
@@ -2571,6 +2696,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2571
2696
  additionalMcpTools;
2572
2697
  clientCapabilities;
2573
2698
  connectionFactory;
2699
+ extensionHandler;
2574
2700
  tcpEndpoint;
2575
2701
  // The value's `generation` is the connection generation the session was
2576
2702
  // last established/restored against. `client` is the exact BandACPClient
@@ -2603,6 +2729,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2603
2729
  permissionTimeoutMs;
2604
2730
  turnTimeoutMs;
2605
2731
  logger;
2732
+ customSection;
2606
2733
  backend = null;
2607
2734
  backendPromise = null;
2608
2735
  client = null;
@@ -2613,6 +2740,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2613
2740
  started = false;
2614
2741
  systemPrompt = "";
2615
2742
  spawnPromise = null;
2743
+ connectionRetirements = /* @__PURE__ */ new WeakMap();
2616
2744
  // Bumped by `stop()` and on every successful spawn install. Cleanup/timeout
2617
2745
  // and permission maps key by this plus session id so a stale generation
2618
2746
  // cannot alias a same-id session on a newer connection.
@@ -2633,12 +2761,14 @@ var ACPClientAdapter = class extends SimpleAdapter {
2633
2761
  this.additionalMcpTools = [...options.additionalMcpTools ?? []];
2634
2762
  this.clientCapabilities = options.clientCapabilities;
2635
2763
  this.connectionFactory = options.connectionFactory;
2764
+ this.extensionHandler = options.extensionHandler;
2636
2765
  this.tcpEndpoint = tcpEndpoint;
2637
2766
  this.resolvePermission = options.resolvePermission;
2638
2767
  this.resolveSessionMode = options.resolveSessionMode;
2639
2768
  this.resolveSessionModel = options.resolveSessionModel;
2640
2769
  this.resolveSessionConfig = options.resolveSessionConfig;
2641
2770
  this.logger = resolveLogger(options.logger);
2771
+ this.customSection = options.customSection;
2642
2772
  this.permissionTimeoutMs = options.permissionTimeoutMs ?? DEFAULT_PERMISSION_TIMEOUT_MS;
2643
2773
  if ((this.resolvePermission || this.resolveSessionMode || this.resolveSessionModel || this.resolveSessionConfig) && (!Number.isFinite(this.permissionTimeoutMs) || this.permissionTimeoutMs <= 0)) {
2644
2774
  throw new ValidationError(`permissionTimeoutMs must be a positive finite number, got ${options.permissionTimeoutMs}`);
@@ -2670,7 +2800,8 @@ var ACPClientAdapter = class extends SimpleAdapter {
2670
2800
  this.systemPrompt = renderSystemPrompt({
2671
2801
  agentName,
2672
2802
  agentDescription,
2673
- includeBaseInstructions: false
2803
+ includeBaseInstructions: false,
2804
+ customSection: this.customSection
2674
2805
  });
2675
2806
  await this.ensureConnection();
2676
2807
  }
@@ -2686,6 +2817,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2686
2817
  let client = null;
2687
2818
  let sessionId;
2688
2819
  let generation = 0;
2820
+ await this.onAcpTurnStarted(message, tools, context);
2689
2821
  try {
2690
2822
  const ensured = await this.ensureConnection();
2691
2823
  connection = ensured.connection;
@@ -2696,6 +2828,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2696
2828
  }
2697
2829
  sessionId = await this.getOrCreateSession(context.roomId, connection, generation, client);
2698
2830
  const sessionKey = this.sessionKey(generation, sessionId);
2831
+ await this.onAcpSessionReady(message, tools, context, sessionId);
2699
2832
  client.beginSession(sessionId);
2700
2833
  const content = replaceUuidMentions(message.content, mentionSubjectsFromMetadata(message.metadata));
2701
2834
  const messageWithContext = [...systemUpdateParts(participantsMessage, contactsMessage), content].join("\n\n");
@@ -2703,13 +2836,17 @@ var ACPClientAdapter = class extends SimpleAdapter {
2703
2836
 
2704
2837
  ${messageWithContext}`;
2705
2838
  this.bootstrappedSessions.add(sessionKey);
2706
- const response = await withTimeout(connection.prompt({
2707
- sessionId,
2708
- prompt: [{
2709
- type: "text",
2710
- text: promptText
2711
- }]
2712
- }), this.turnTimeoutMs, () => new AcpTurnTimeoutError());
2839
+ const response = await withTimeout(
2840
+ this.raceAgainstConnectionRetirement(connection, connection.prompt({
2841
+ sessionId,
2842
+ prompt: [{
2843
+ type: "text",
2844
+ text: promptText
2845
+ }]
2846
+ })),
2847
+ this.turnTimeoutMs,
2848
+ () => new AcpTurnTimeoutError()
2849
+ );
2713
2850
  await this.flushChunks({
2714
2851
  client,
2715
2852
  tools,
@@ -2750,15 +2887,28 @@ ${messageWithContext}`;
2750
2887
  }
2751
2888
  }
2752
2889
  }
2753
- const acpError = asAcpJsonRpcError(error);
2890
+ const configError = error instanceof AcpSessionConfigError ? error : void 0;
2891
+ const acpError = configError ? void 0 : asAcpJsonRpcError(error);
2754
2892
  await reportTurnFailure(
2755
2893
  tools,
2756
- isTimeout ? agentFailure(this.provider, "ACP turn timed out.", FAILURE_CODE_TIMEOUT) : acpError ? agentFailure(this.provider, acpError.message, String(acpError.code), acpError.data) : agentFailure(this.provider, asErrorMessage(error)),
2894
+ isTimeout ? agentFailure(this.provider, "ACP turn timed out.", FAILURE_CODE_TIMEOUT) : configError ? configError.toAgentFailure() : acpError ? agentFailure(this.provider, acpError.message, String(acpError.code), acpError.data) : agentFailure(this.provider, asErrorMessage(error)),
2757
2895
  this.logger,
2758
- { roomId: context.roomId, sessionId }
2896
+ {
2897
+ roomId: context.roomId,
2898
+ sessionId: configError?.sessionId ?? sessionId,
2899
+ ...configError ? { optionId: configError.optionId, selectedValue: configError.selectedValue } : {}
2900
+ }
2759
2901
  );
2902
+ } finally {
2903
+ await this.onAcpTurnFinished(message, tools, context);
2760
2904
  }
2761
2905
  }
2906
+ async onAcpTurnStarted(_message, _tools, _context) {
2907
+ }
2908
+ async onAcpTurnFinished(_message, _tools, _context) {
2909
+ }
2910
+ async onAcpSessionReady(_message, _tools, _context, _sessionId) {
2911
+ }
2762
2912
  // Best-effort: tells the agent to stop working on a turn Band has already
2763
2913
  // given up waiting for (the ACP client has no way to force it), evicts the
2764
2914
  // session so the room's next turn re-establishes rather than reuses it
@@ -2774,17 +2924,12 @@ ${messageWithContext}`;
2774
2924
  // would risk blocking this room's turn lock forever on the very process
2775
2925
  // that just proved it can hang.
2776
2926
  async abandonTimedOutTurn(connection, sessionId, generation) {
2777
- const key = this.sessionKey(generation, sessionId);
2778
- this.activeSessions.delete(key);
2779
- this.abandonedSessions.add(key);
2780
- const owner = [...this.roomToSession.entries()].find(([, value]) => value.sessionId === sessionId && value.generation === generation);
2781
- if (owner) {
2782
- this.unlinkOwner(owner[0], owner[1]);
2783
- }
2784
- abandon(
2785
- () => connection.cancel({ sessionId }),
2786
- (error) => this.safeWarn("acp_client.cancel_failed", { sessionId, error: asErrorMessage(error) })
2787
- );
2927
+ this.evictAbandonedSession(sessionId, generation, connection, () => {
2928
+ const owner = [...this.roomToSession.entries()].find(([, value]) => value.sessionId === sessionId && value.generation === generation);
2929
+ if (owner) {
2930
+ this.unlinkOwner(owner[0], owner[1]);
2931
+ }
2932
+ });
2788
2933
  }
2789
2934
  // A per-room async mutex: `fn` for a given `roomId` never overlaps another
2790
2935
  // call for that same room, while different rooms stay fully concurrent.
@@ -2910,6 +3055,11 @@ ${messageWithContext}`;
2910
3055
  void connection.closed.then(() => reject(new Error("ACP connection closed while a session operation was still in flight")));
2911
3056
  return Promise.race([operation, closedRejection]);
2912
3057
  }
3058
+ raceAgainstConnectionRetirement(connection, operation) {
3059
+ const retirement = this.connectionRetirements.get(connection) ?? createConnectionRetirement();
3060
+ this.connectionRetirements.set(connection, retirement);
3061
+ return Promise.race([operation, retirement.promise]);
3062
+ }
2913
3063
  unlinkRoom(roomId) {
2914
3064
  const owner = this.roomToSession.get(roomId);
2915
3065
  if (owner) {
@@ -2928,6 +3078,9 @@ ${messageWithContext}`;
2928
3078
  sessionKey(generation, sessionId) {
2929
3079
  return `${generation}:${sessionId}`;
2930
3080
  }
3081
+ roomIdForSession(sessionId) {
3082
+ return [...this.roomToSession.entries()].find(([, owner]) => owner.sessionId === sessionId)?.[0];
3083
+ }
2931
3084
  async ensureConnection() {
2932
3085
  if (this.connection && !this.connection.signal.aborted) {
2933
3086
  return { connection: this.connection, generation: this.connectionGeneration };
@@ -2964,7 +3117,10 @@ ${messageWithContext}`;
2964
3117
  throw new Error(CONNECTION_ATTEMPT_SUPERSEDED_ERROR);
2965
3118
  }
2966
3119
  const owner = { generation: -1 };
2967
- const client = new BandACPClient((params) => this.routePermissionRequest(params, owner.generation));
3120
+ const client = new BandACPClient(
3121
+ (params) => this.routePermissionRequest(params, owner.generation),
3122
+ this.extensionHandler
3123
+ );
2968
3124
  handle = await (this.connectionFactory ? this.connectionFactory(client, {
2969
3125
  command: this.command,
2970
3126
  cwd: this.cwd,
@@ -3054,7 +3210,7 @@ ${messageWithContext}`;
3054
3210
  this.activeSessions.add(restoredKey);
3055
3211
  this.bootstrappedSessions.add(restoredKey);
3056
3212
  await this.configureSessionMode(roomId, existingSessionId, restored.modes, connection);
3057
- await this.configureSessionConfig(roomId, existingSessionId, restored.configOptions, connection);
3213
+ await this.configureSessionConfig(roomId, existingSessionId, restored.configOptions, connection, connectionGeneration, client);
3058
3214
  if (!this.resolveSessionConfig) {
3059
3215
  await this.configureSessionModel(roomId, existingSessionId, restored.configOptions, connection);
3060
3216
  }
@@ -3068,13 +3224,13 @@ ${messageWithContext}`;
3068
3224
  this.linkOrAbandon(roomId, created.sessionId, generation, connectionGeneration, client);
3069
3225
  this.activeSessions.add(this.sessionKey(connectionGeneration, created.sessionId));
3070
3226
  await this.configureSessionMode(roomId, created.sessionId, created.modes, connection);
3071
- await this.configureSessionConfig(roomId, created.sessionId, created.configOptions, connection);
3227
+ await this.configureSessionConfig(roomId, created.sessionId, created.configOptions, connection, connectionGeneration, client);
3072
3228
  if (!this.resolveSessionConfig) {
3073
3229
  await this.configureSessionModel(roomId, created.sessionId, created.configOptions, connection);
3074
3230
  }
3075
3231
  return created.sessionId;
3076
3232
  }
3077
- async configureSessionConfig(roomId, sessionId, configOptions, connection) {
3233
+ async configureSessionConfig(roomId, sessionId, configOptions, connection, connectionGeneration, client) {
3078
3234
  if (!this.resolveSessionConfig || !Array.isArray(configOptions) || configOptions.length === 0) {
3079
3235
  return;
3080
3236
  }
@@ -3087,35 +3243,76 @@ ${messageWithContext}`;
3087
3243
  if (!selections) {
3088
3244
  return;
3089
3245
  }
3090
- for (const option of advertisedOptions) {
3091
- const selectedValue = selections[option.id];
3092
- if (selectedValue === void 0 || selectedValue === option.currentValue || !isSessionConfigSelect(option)) {
3093
- continue;
3094
- }
3095
- const availableValues = flattenConfigSelectOptions(option.options).map((entry) => entry.value);
3096
- if (!availableValues.includes(selectedValue)) {
3097
- this.safeWarn("resolveSessionConfig selected a value this session does not advertise", {
3098
- sessionId,
3099
- configId: option.id,
3100
- selectedValue,
3101
- availableValues
3102
- });
3103
- continue;
3104
- }
3105
- try {
3106
- await withTimeout(
3107
- connection.setSessionConfigOption({ sessionId, configId: option.id, value: selectedValue }),
3108
- SET_SESSION_CONFIG_TIMEOUT_MS,
3109
- `setSessionConfigOption did not respond within ${SET_SESSION_CONFIG_TIMEOUT_MS}ms`
3110
- );
3111
- } catch (error) {
3112
- this.safeWarn("failed to switch session config option", {
3113
- sessionId,
3114
- configId: option.id,
3115
- selectedValue,
3116
- error: String(error)
3117
- });
3246
+ try {
3247
+ await applySessionConfigSelections({
3248
+ provider: this.provider,
3249
+ sessionId,
3250
+ catalog: advertisedOptions,
3251
+ selections,
3252
+ setOption: (params) => connection.setSessionConfigOption(params),
3253
+ timeoutMs: SET_SESSION_CONFIG_TIMEOUT_MS
3254
+ });
3255
+ } catch (error) {
3256
+ this.abandonFailedConfigSession(
3257
+ roomId,
3258
+ sessionId,
3259
+ connectionGeneration,
3260
+ client,
3261
+ connection,
3262
+ error instanceof AcpSessionConfigError && error.timedOut
3263
+ );
3264
+ throw error;
3265
+ }
3266
+ }
3267
+ // A config failure mid-establish must not leave a half-applied session
3268
+ // active for the room: the next turn needs a fresh `newSession` catalog.
3269
+ abandonFailedConfigSession(roomId, sessionId, connectionGeneration, client, connection, retireConnection) {
3270
+ const key = this.sessionKey(connectionGeneration, sessionId);
3271
+ this.bootstrappedSessions.delete(key);
3272
+ client.resetChunks(sessionId);
3273
+ this.evictAbandonedSession(sessionId, connectionGeneration, connection, () => {
3274
+ const owner = this.roomToSession.get(roomId);
3275
+ if (owner && owner.sessionId === sessionId && owner.generation === connectionGeneration) {
3276
+ this.unlinkOwner(roomId, owner);
3118
3277
  }
3278
+ });
3279
+ if (retireConnection) {
3280
+ this.retireConnection(connection, connectionGeneration);
3281
+ }
3282
+ }
3283
+ // Common half of timeout and config-failure abandon: mark the session
3284
+ // unusable for restore, unlink ownership, and best-effort cancel.
3285
+ evictAbandonedSession(sessionId, connectionGeneration, connection, unlink) {
3286
+ const key = this.sessionKey(connectionGeneration, sessionId);
3287
+ const wasActive = this.activeSessions.delete(key);
3288
+ if (wasActive) {
3289
+ this.abandonedSessions.add(key);
3290
+ }
3291
+ unlink();
3292
+ abandon(
3293
+ () => connection.cancel({ sessionId }),
3294
+ (error) => this.safeWarn("acp_client.cancel_failed", { sessionId, error: asErrorMessage(error) })
3295
+ );
3296
+ }
3297
+ // A timed-out config RPC means this transport has already failed to answer
3298
+ // one request. Retire it so the next turn cannot wait forever on another.
3299
+ retireConnection(connection, generation) {
3300
+ if (this.connection !== connection || this.connectionGeneration !== generation) {
3301
+ return;
3302
+ }
3303
+ const handle = this.connectionHandle;
3304
+ this.connectionGeneration++;
3305
+ this.connection = null;
3306
+ this.connectionHandle = null;
3307
+ this.connectionState = null;
3308
+ this.client = null;
3309
+ this.pruneConnectionGeneration(generation);
3310
+ this.connectionRetirements.get(connection)?.reject(new Error("ACP connection retired after a config timeout"));
3311
+ if (handle) {
3312
+ abandon(
3313
+ () => handle.stop(),
3314
+ (error) => this.safeWarn("acp_client.handle_stop_after_config_timeout", { error: asErrorMessage(error) })
3315
+ );
3119
3316
  }
3120
3317
  }
3121
3318
  // The single gate an establishment must pass before it's allowed to claim
@@ -3729,39 +3926,12 @@ async function createTcpConnection(client, endpoint, signal) {
3729
3926
  };
3730
3927
  }
3731
3928
  var MODEL_CONFIG_OPTION_KEY = "model";
3732
- function isSessionConfigSelect(option) {
3733
- return option?.type === "select";
3734
- }
3735
3929
  function isModelConfigOption(option) {
3736
3930
  return isSessionConfigSelect(option) && option.category === MODEL_CONFIG_OPTION_KEY;
3737
3931
  }
3738
3932
  function isModelConfigOptionById(option) {
3739
3933
  return isSessionConfigSelect(option) && option.id === MODEL_CONFIG_OPTION_KEY;
3740
3934
  }
3741
- function flattenConfigSelectOptions(options) {
3742
- if (!Array.isArray(options)) {
3743
- return [];
3744
- }
3745
- return options.flatMap((entry) => {
3746
- if (!asOptionalRecord2(entry)) {
3747
- return [];
3748
- }
3749
- if ("group" in entry) {
3750
- return Array.isArray(entry.options) ? entry.options : [];
3751
- }
3752
- return [entry];
3753
- });
3754
- }
3755
- function isAcpErrorResponse(error) {
3756
- return typeof error === "object" && error !== null && typeof error.code === "number" && typeof error.message === "string";
3757
- }
3758
- function asAcpJsonRpcError(error) {
3759
- if (isAcpErrorResponse(error)) {
3760
- return error;
3761
- }
3762
- const nested = asOptionalRecord2(error)?.error;
3763
- return isAcpErrorResponse(nested) ? nested : void 0;
3764
- }
3765
3935
 
3766
3936
  // src/adapters/acp/ACPServer.ts
3767
3937
  var import_node_stream2 = require("stream");
@@ -4675,6 +4845,358 @@ var CopilotACPAdapter = class extends ACPClientAdapter {
4675
4845
  }
4676
4846
  };
4677
4847
 
4848
+ // src/adapters/cursor-acp/CursorACPAdapter.ts
4849
+ var DEFAULT_CURSOR_ACP_COMMAND = ["agent", "acp"];
4850
+ var DEFAULT_CURSOR_DECISION_TIMEOUT_MS = 3e5;
4851
+ var DEFAULT_CURSOR_MAX_PENDING_DECISIONS = 10;
4852
+ var CursorExtensions = class {
4853
+ adapter = null;
4854
+ todosBySession = /* @__PURE__ */ new Map();
4855
+ bind(adapter) {
4856
+ this.adapter = adapter;
4857
+ }
4858
+ async resolvePermission(request, signal) {
4859
+ return this.adapter?.resolveCursorPermission(request, signal);
4860
+ }
4861
+ extensionSessionId() {
4862
+ return this.adapter?.extensionSessionId() ?? null;
4863
+ }
4864
+ async extMethod(method, params, context) {
4865
+ return this.adapter?.resolveExtension(method, params, context.sessionId) ?? null;
4866
+ }
4867
+ async extNotification(method, params, context) {
4868
+ const sessionId = context.sessionId ?? this.extensionSessionId();
4869
+ if (!sessionId) {
4870
+ return;
4871
+ }
4872
+ if (method === "cursor/update_todos") {
4873
+ const content = this.updateTodos(sessionId, params);
4874
+ if (!content) {
4875
+ return;
4876
+ }
4877
+ return [{ chunkType: "plan", content, metadata: { cursor_todos: true }, streamed: false }];
4878
+ }
4879
+ if (method === "cursor/task") {
4880
+ const description = stringValue(params.description);
4881
+ if (!description) {
4882
+ return [];
4883
+ }
4884
+ const subagentType = stringValue(params.subagentType) ?? "unspecified";
4885
+ const model = stringValue(params.model);
4886
+ const suffix = model ? ` (${model})` : "";
4887
+ return [{ chunkType: "plan", content: `[Cursor ${subagentType} task] ${description}${suffix}`, metadata: {}, streamed: false }];
4888
+ }
4889
+ if (method === "cursor/generate_image") {
4890
+ const description = stringValue(params.description);
4891
+ if (!description) {
4892
+ return [];
4893
+ }
4894
+ const filePath = stringValue(params.filePath);
4895
+ return [{ chunkType: "plan", content: `[Cursor generated image] ${description}${filePath ? ` \u2192 ${filePath}` : ""}`, metadata: {}, streamed: false }];
4896
+ }
4897
+ }
4898
+ forgetSession(sessionId) {
4899
+ this.todosBySession.delete(sessionId);
4900
+ }
4901
+ clearSessions() {
4902
+ this.todosBySession.clear();
4903
+ }
4904
+ updateTodos(sessionId, params) {
4905
+ const todos = parseTodos(params.todos);
4906
+ if (params.merge === true) {
4907
+ const current2 = this.todosBySession.get(sessionId) ?? /* @__PURE__ */ new Map();
4908
+ for (const todo of todos) {
4909
+ current2.set(todo.id, todo);
4910
+ }
4911
+ this.todosBySession.set(sessionId, current2);
4912
+ } else {
4913
+ this.todosBySession.set(sessionId, new Map(todos.map((todo) => [todo.id, todo])));
4914
+ }
4915
+ const current = this.todosBySession.get(sessionId);
4916
+ return current && current.size > 0 ? [...current.values()].map((todo) => `- [${todoMark(todo.status)}] ${todo.content}`).join("\n") : void 0;
4917
+ }
4918
+ };
4919
+ var CursorACPAdapter = class extends ACPClientAdapter {
4920
+ provider = "cursor-acp";
4921
+ approvalMode;
4922
+ questionMode;
4923
+ planMode;
4924
+ decisionTimeoutMs;
4925
+ maxPendingDecisions;
4926
+ authorizedSenders;
4927
+ decisionLogger;
4928
+ extensions;
4929
+ turns = /* @__PURE__ */ new Map();
4930
+ pending = /* @__PURE__ */ new Map();
4931
+ activeTurn = null;
4932
+ turnTail = Promise.resolve();
4933
+ constructor(options = {}) {
4934
+ const extensions = new CursorExtensions();
4935
+ validateOptions(options);
4936
+ const env = cursorEnv(options);
4937
+ super({
4938
+ ...options,
4939
+ env,
4940
+ command: options.command ?? [...DEFAULT_CURSOR_ACP_COMMAND],
4941
+ authMethod: "cursor_login",
4942
+ extensionHandler: extensions,
4943
+ resolvePermission: (request, signal) => extensions.resolvePermission(request, signal)
4944
+ });
4945
+ extensions.bind(this);
4946
+ this.extensions = extensions;
4947
+ this.approvalMode = options.approvalMode ?? "manual";
4948
+ this.questionMode = options.questionMode ?? "manual";
4949
+ this.planMode = options.planMode ?? "manual";
4950
+ this.decisionTimeoutMs = options.decisionTimeoutMs ?? DEFAULT_CURSOR_DECISION_TIMEOUT_MS;
4951
+ this.maxPendingDecisions = options.maxPendingDecisions ?? DEFAULT_CURSOR_MAX_PENDING_DECISIONS;
4952
+ this.authorizedSenders = options.decisionAuthorizedSenders ? new Set(options.decisionAuthorizedSenders) : null;
4953
+ this.decisionLogger = resolveLogger(options.logger);
4954
+ }
4955
+ async onMessage(message, tools, history, participantsMessage, contactsMessage, context) {
4956
+ if (await this.handleControl(message, tools, context.roomId)) {
4957
+ return;
4958
+ }
4959
+ await this.withCursorTurnLock(() => super.onMessage(message, tools, history, participantsMessage, contactsMessage, context));
4960
+ }
4961
+ async onAcpTurnStarted(message, tools, context) {
4962
+ const turn = { messageId: message.id, roomId: context.roomId, tools, requesterId: message.senderId };
4963
+ this.turns.set(context.roomId, turn);
4964
+ this.activeTurn = turn;
4965
+ }
4966
+ async onAcpSessionReady(message, _tools, context, sessionId) {
4967
+ const turn = this.turns.get(context.roomId);
4968
+ if (turn?.messageId === message.id) {
4969
+ turn.sessionId = sessionId;
4970
+ }
4971
+ }
4972
+ async onAcpTurnFinished(message, _tools, context) {
4973
+ const turn = this.turns.get(context.roomId);
4974
+ if (turn?.messageId === message.id) {
4975
+ this.turns.delete(context.roomId);
4976
+ this.cancelRoom(context.roomId);
4977
+ }
4978
+ if (this.activeTurn?.messageId === message.id) {
4979
+ this.activeTurn = null;
4980
+ }
4981
+ }
4982
+ async onCleanup(roomId) {
4983
+ const sessionId = this.turns.get(roomId)?.sessionId;
4984
+ this.cancelRoom(roomId);
4985
+ this.turns.delete(roomId);
4986
+ if (this.activeTurn?.roomId === roomId) {
4987
+ this.activeTurn = null;
4988
+ }
4989
+ await super.onCleanup(roomId);
4990
+ if (sessionId) {
4991
+ this.extensions.forgetSession(sessionId);
4992
+ }
4993
+ }
4994
+ async stop() {
4995
+ for (const decision of this.pending.values()) {
4996
+ decision.resolve(void 0);
4997
+ }
4998
+ this.pending.clear();
4999
+ this.turns.clear();
5000
+ this.activeTurn = null;
5001
+ this.extensions.clearSessions();
5002
+ await super.stop();
5003
+ }
5004
+ async resolveExtension(method, params, sessionId) {
5005
+ const roomId = sessionId ? this.roomIdForSession(sessionId) : this.activeTurn?.roomId;
5006
+ const turn = roomId ? this.turns.get(roomId) : void 0;
5007
+ if (!turn || sessionId && turn.sessionId !== sessionId) {
5008
+ return { outcome: { outcome: "cancelled" } };
5009
+ }
5010
+ if (method === "cursor/ask_question") {
5011
+ return this.resolveQuestion(turn.roomId, turn, params);
5012
+ }
5013
+ if (method === "cursor/create_plan") {
5014
+ return this.resolvePlan(turn.roomId, turn, params);
5015
+ }
5016
+ return {};
5017
+ }
5018
+ async resolveCursorPermission(request, signal) {
5019
+ if (this.approvalMode === "autoAccept") {
5020
+ return allowOption(request.options)?.optionId;
5021
+ }
5022
+ if (this.approvalMode === "autoDecline") {
5023
+ return void 0;
5024
+ }
5025
+ const turn = this.turns.get(request.roomId);
5026
+ if (!turn) {
5027
+ return void 0;
5028
+ }
5029
+ const options = request.options.map((option) => option.optionId);
5030
+ const token = await this.waitForDecision("permission", request.roomId, turn, /* @__PURE__ */ new Map([["permission", options]]), /* @__PURE__ */ new Set(), `Cursor needs permission. Reply \`/cursor select {token} option-id\` or \`/cursor deny {token}\`.`, signal);
5031
+ return typeof token === "string" && options.includes(token) ? token : void 0;
5032
+ }
5033
+ extensionSessionId() {
5034
+ return this.activeTurn?.sessionId ?? null;
5035
+ }
5036
+ async resolveQuestion(roomId, turn, params) {
5037
+ const questions = questionChoices(params.questions);
5038
+ if (questions.choices.size === 0) {
5039
+ return { outcome: { outcome: "cancelled" } };
5040
+ }
5041
+ if (this.questionMode === "autoCancel") {
5042
+ return { outcome: { outcome: "cancelled" } };
5043
+ }
5044
+ if (this.questionMode === "autoFirst") {
5045
+ return answered(Object.fromEntries([...questions.choices].map(([id, options]) => [id, [options[0]]])));
5046
+ }
5047
+ const result = await this.waitForDecision("question", roomId, turn, questions.choices, questions.multiSelect, "Cursor needs input. Reply `/cursor answer {token} question-id=option-id[,option-id] ...`.");
5048
+ return isRecord(result) ? result : { outcome: { outcome: "cancelled" } };
5049
+ }
5050
+ async resolvePlan(roomId, turn, params) {
5051
+ if (this.planMode === "autoAccept") {
5052
+ return { outcome: { outcome: "accepted" } };
5053
+ }
5054
+ if (this.planMode === "autoDecline") {
5055
+ return { outcome: { outcome: "rejected" } };
5056
+ }
5057
+ const title = stringValue(params.title) ?? "Cursor plan";
5058
+ const result = await this.waitForDecision("plan", roomId, turn, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set(), `${title} needs approval. Reply \`/cursor accept {token}\` or \`/cursor reject {token}\`.`);
5059
+ return isRecord(result) ? result : { outcome: { outcome: "cancelled" } };
5060
+ }
5061
+ async waitForDecision(kind, roomId, turn, choices, multiSelect, prompt, signal) {
5062
+ if (this.pending.size >= this.maxPendingDecisions) {
5063
+ this.pending.values().next().value?.resolve(void 0);
5064
+ }
5065
+ const token = crypto.randomUUID().slice(0, 8);
5066
+ return new Promise((resolve) => {
5067
+ const timer = setTimeout(() => settle(void 0), this.decisionTimeoutMs);
5068
+ const abort = () => settle(void 0);
5069
+ const settle = (value) => {
5070
+ clearTimeout(timer);
5071
+ signal?.removeEventListener("abort", abort);
5072
+ this.pending.delete(token);
5073
+ resolve(value);
5074
+ };
5075
+ this.pending.set(token, { kind, roomId, choices, multiSelect, resolve: settle });
5076
+ signal?.addEventListener("abort", abort, { once: true });
5077
+ void turn.tools.sendMessage(prompt.replaceAll("{token}", token), [turn.requesterId]).catch((error) => {
5078
+ this.decisionLogger.warn("cursor_acp.decision_prompt_delivery_failed", { roomId, kind, error: String(error) });
5079
+ settle(void 0);
5080
+ });
5081
+ });
5082
+ }
5083
+ async handleControl(message, tools, roomId) {
5084
+ const words = message.content.trim().split(/\s+/);
5085
+ if (words[0]?.toLowerCase() !== "/cursor") {
5086
+ return false;
5087
+ }
5088
+ if (words.length === 1 || words[1]?.toLowerCase() === "decisions") {
5089
+ const entries = [...this.pending.entries()].filter(([, decision2]) => decision2.roomId === roomId).map(([token2, decision2]) => `\`${token2}\` (${decision2.kind})`);
5090
+ await tools.sendMessage(`Pending Cursor decisions: ${entries.join(", ") || "none"}`);
5091
+ return true;
5092
+ }
5093
+ const [_, action, token, ...args] = words;
5094
+ const decision = token ? this.pending.get(token) : void 0;
5095
+ if (!decision || decision.roomId !== roomId) {
5096
+ await tools.sendMessage(`Cursor decision \`${token ?? ""}\` is not pending.`);
5097
+ return true;
5098
+ }
5099
+ if (this.authorizedSenders && !this.authorizedSenders.has(message.senderId)) {
5100
+ await tools.sendMessage("You are not authorized to resolve Cursor decisions.");
5101
+ return true;
5102
+ }
5103
+ const result = commandResult(action ?? "", args, decision);
5104
+ if (result === null) {
5105
+ await tools.sendMessage(`That command is not valid for Cursor ${decision.kind} decision \`${token}\`.`);
5106
+ return true;
5107
+ }
5108
+ decision.resolve(result);
5109
+ await tools.sendMessage(`Cursor ${decision.kind} decision \`${token}\` resolved.`);
5110
+ return true;
5111
+ }
5112
+ cancelRoom(roomId) {
5113
+ for (const [token, decision] of this.pending) {
5114
+ if (decision.roomId === roomId) {
5115
+ decision.resolve(void 0);
5116
+ this.pending.delete(token);
5117
+ }
5118
+ }
5119
+ }
5120
+ async withCursorTurnLock(run) {
5121
+ const queued = this.turnTail.then(run, run);
5122
+ this.turnTail = queued.then(() => void 0, () => void 0);
5123
+ return queued;
5124
+ }
5125
+ };
5126
+ function cursorEnv(options) {
5127
+ const env = { ...options.env };
5128
+ if (options.apiKey) env.CURSOR_API_KEY ??= options.apiKey;
5129
+ if (options.authToken) env.CURSOR_AUTH_TOKEN ??= options.authToken;
5130
+ return Object.keys(env).length > 0 ? env : void 0;
5131
+ }
5132
+ function validateOptions(options) {
5133
+ if (options.apiKey && options.authToken) throw new Error("set either apiKey or authToken, not both");
5134
+ if (Array.isArray(options.command) && options.command.length === 0) throw new Error("Cursor ACP command must not be empty");
5135
+ if (options.decisionTimeoutMs !== void 0 && (!Number.isFinite(options.decisionTimeoutMs) || options.decisionTimeoutMs <= 0)) throw new Error("decisionTimeoutMs must be a positive finite number");
5136
+ if (options.maxPendingDecisions !== void 0 && (!Number.isInteger(options.maxPendingDecisions) || options.maxPendingDecisions <= 0)) throw new Error("maxPendingDecisions must be a positive integer");
5137
+ }
5138
+ function allowOption(options) {
5139
+ return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always");
5140
+ }
5141
+ function questionChoices(value) {
5142
+ const choices = /* @__PURE__ */ new Map();
5143
+ const multiSelect = /* @__PURE__ */ new Set();
5144
+ if (!Array.isArray(value)) return { choices, multiSelect };
5145
+ for (const question of value) {
5146
+ if (!isRecord(question) || typeof question.id !== "string" || !Array.isArray(question.options)) continue;
5147
+ const options = question.options.filter(isRecord).map((option) => stringValue(option.id)).filter((id) => !!id);
5148
+ if (options.length === 0) continue;
5149
+ choices.set(question.id, options);
5150
+ if (question.allowMultiple === true) multiSelect.add(question.id);
5151
+ }
5152
+ return { choices, multiSelect };
5153
+ }
5154
+ function commandResult(action, args, decision) {
5155
+ if (decision.kind === "permission") return action === "deny" ? void 0 : action === "select" && args.length === 1 && decision.choices.get("permission")?.includes(args[0] ?? "") ? args[0] : null;
5156
+ if (decision.kind === "plan") return action === "accept" ? { outcome: { outcome: "accepted" } } : action === "reject" ? { outcome: { outcome: "rejected" } } : null;
5157
+ if (action !== "answer") return null;
5158
+ const selected = {};
5159
+ for (const argument of args) {
5160
+ const [id, raw] = argument.split("=", 2);
5161
+ const values = raw?.split(",") ?? [];
5162
+ const offered = id ? decision.choices.get(id) : void 0;
5163
+ if (!id || !offered || selected[id] || values.length === 0 || values.length > 1 && !decision.multiSelect.has(id) || values.some((value) => !offered.includes(value))) return null;
5164
+ selected[id] = values;
5165
+ }
5166
+ return Object.keys(selected).length === decision.choices.size ? answered(selected) : null;
5167
+ }
5168
+ function answered(selected) {
5169
+ return { outcome: { outcome: "answered", answers: Object.entries(selected).map(([questionId, selectedOptionIds]) => ({ questionId, selectedOptionIds })) } };
5170
+ }
5171
+ function parseTodos(value) {
5172
+ if (!Array.isArray(value)) return [];
5173
+ return value.flatMap((todo) => {
5174
+ if (!isRecord(todo)) return [];
5175
+ const id = stringValue(todo.id);
5176
+ const content = stringValue(todo.content);
5177
+ const status = stringValue(todo.status);
5178
+ return id && content && status ? [{ id, content, status }] : [];
5179
+ });
5180
+ }
5181
+ function todoMark(status) {
5182
+ switch (status) {
5183
+ case "completed":
5184
+ return "x";
5185
+ case "in_progress":
5186
+ return "~";
5187
+ case "cancelled":
5188
+ return "-";
5189
+ default:
5190
+ return " ";
5191
+ }
5192
+ }
5193
+ function isRecord(value) {
5194
+ return !!value && typeof value === "object" && !Array.isArray(value);
5195
+ }
5196
+ function stringValue(value) {
5197
+ return typeof value === "string" && value.length > 0 ? value : void 0;
5198
+ }
5199
+
4678
5200
  // src/adapters/tool-calling/ToolCallingAdapter.ts
4679
5201
  init_protocols();
4680
5202
 
@@ -7102,20 +7624,20 @@ function unwrapResult(value, depth = 0) {
7102
7624
  }
7103
7625
  return event;
7104
7626
  }
7105
- function isRecord(value) {
7627
+ function isRecord2(value) {
7106
7628
  return typeof value === "object" && value !== null;
7107
7629
  }
7108
7630
  function isOptionalString(value) {
7109
7631
  return value === void 0 || typeof value === "string";
7110
7632
  }
7111
7633
  function isMessagePart(value) {
7112
- if (!isRecord(value)) {
7634
+ if (!isRecord2(value)) {
7113
7635
  return false;
7114
7636
  }
7115
7637
  return isOptionalString(value.kind) && isOptionalString(value.type) && isOptionalString(value.text) && (value.root === void 0 || isMessagePart(value.root));
7116
7638
  }
7117
7639
  function isMessageLike(value) {
7118
- if (!isRecord(value)) {
7640
+ if (!isRecord2(value)) {
7119
7641
  return false;
7120
7642
  }
7121
7643
  if (!isOptionalString(value.kind) || !isOptionalString(value.role)) {
@@ -7130,7 +7652,7 @@ function isMessageLike(value) {
7130
7652
  return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
7131
7653
  }
7132
7654
  function isStatusLike(value) {
7133
- if (!isRecord(value)) {
7655
+ if (!isRecord2(value)) {
7134
7656
  return false;
7135
7657
  }
7136
7658
  if (!isOptionalString(value.state)) {
@@ -7139,7 +7661,7 @@ function isStatusLike(value) {
7139
7661
  return value.message === void 0 || isMessageLike(value.message);
7140
7662
  }
7141
7663
  function isArtifactLike(value) {
7142
- if (!isRecord(value)) {
7664
+ if (!isRecord2(value)) {
7143
7665
  return false;
7144
7666
  }
7145
7667
  if (value.parts === void 0) {
@@ -7148,19 +7670,19 @@ function isArtifactLike(value) {
7148
7670
  return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
7149
7671
  }
7150
7672
  function isMessageEvent(event) {
7151
- return isRecord(event) && event.kind === "message" && isMessageLike(event) && Array.isArray(event.parts);
7673
+ return isRecord2(event) && event.kind === "message" && isMessageLike(event) && Array.isArray(event.parts);
7152
7674
  }
7153
7675
  function isTaskEvent(event) {
7154
- if (!isRecord(event) || event.kind !== "task") {
7676
+ if (!isRecord2(event) || event.kind !== "task") {
7155
7677
  return false;
7156
7678
  }
7157
7679
  return typeof event.id === "string" && isOptionalString(event.contextId) && isStatusLike(event.status) && (event.artifacts === void 0 || Array.isArray(event.artifacts) && event.artifacts.every((item) => isArtifactLike(item))) && (event.history === void 0 || Array.isArray(event.history) && event.history.every((item) => isMessageLike(item)));
7158
7680
  }
7159
7681
  function isStatusUpdateEvent(event) {
7160
- return isRecord(event) && event.kind === "status-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isStatusLike(event.status);
7682
+ return isRecord2(event) && event.kind === "status-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isStatusLike(event.status);
7161
7683
  }
7162
7684
  function isArtifactUpdateEvent(event) {
7163
- return isRecord(event) && event.kind === "artifact-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isArtifactLike(event.artifact);
7685
+ return isRecord2(event) && event.kind === "artifact-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isArtifactLike(event.artifact);
7164
7686
  }
7165
7687
  async function loadDefaultA2AClientFactory() {
7166
7688
  let module2;
@@ -9800,7 +10322,7 @@ var SdkOpencodeClientBase = class {
9800
10322
  { signal: this.eventsAbortController.signal }
9801
10323
  );
9802
10324
  for await (const event of events.stream) {
9803
- if (isRecord2(event)) {
10325
+ if (isRecord3(event)) {
9804
10326
  yield event;
9805
10327
  }
9806
10328
  }
@@ -9918,7 +10440,7 @@ function optionalString(value) {
9918
10440
  const trimmed = value.trim();
9919
10441
  return trimmed.length > 0 ? trimmed : null;
9920
10442
  }
9921
- function isRecord2(value) {
10443
+ function isRecord3(value) {
9922
10444
  return !!value && typeof value === "object" && !Array.isArray(value);
9923
10445
  }
9924
10446
  function getResultBody(result) {
@@ -9928,7 +10450,7 @@ function expectRecord(result) {
9928
10450
  if (!result.response.ok) {
9929
10451
  throw new HttpStatusError(result.response.status, getResultBody(result));
9930
10452
  }
9931
- return isRecord2(result.data) ? result.data : {};
10453
+ return isRecord3(result.data) ? result.data : {};
9932
10454
  }
9933
10455
  async function expectVoid(resultPromise) {
9934
10456
  const result = await resultPromise;
@@ -12864,6 +13386,7 @@ function parseModelListResponse(value) {
12864
13386
  A2AHistoryConverter,
12865
13387
  ACPClientAdapter,
12866
13388
  ACPServer,
13389
+ AcpSessionConfigError,
12867
13390
  AnthropicAdapter,
12868
13391
  AnthropicToolCallingModel,
12869
13392
  BandACPServerAdapter,
@@ -12875,8 +13398,11 @@ function parseModelListResponse(value) {
12875
13398
  CodexAppServerStdioClient,
12876
13399
  CodexJsonRpcError,
12877
13400
  CopilotACPAdapter,
13401
+ CursorACPAdapter,
12878
13402
  DEFAULT_COPILOT_ACP_COMMAND,
13403
+ DEFAULT_CURSOR_ACP_COMMAND,
12879
13404
  DEFAULT_OMP_ACP_COMMAND,
13405
+ FAILURE_CODE_SESSION_CONFIG,
12880
13406
  GatewayHistoryConverter,
12881
13407
  GatewayServer,
12882
13408
  GeminiAdapter,
@@ -12888,6 +13414,7 @@ function parseModelListResponse(value) {
12888
13414
  LangGraphAdapter,
12889
13415
  LettaAdapter,
12890
13416
  LettaHistoryConverter,
13417
+ MISSING_CONFIG_OPTIONS_REASON,
12891
13418
  OmpACPAdapter,
12892
13419
  OpenAIAdapter,
12893
13420
  OpenAIToolCallingModel,
@@ -12897,6 +13424,7 @@ function parseModelListResponse(value) {
12897
13424
  ToolCallingAdapter,
12898
13425
  VercelAISDKAdapter,
12899
13426
  VercelAISDKToolCallingModel,
13427
+ applySessionConfigSelections,
12900
13428
  buildA2AAuthHeaders,
12901
13429
  createGatewayServer,
12902
13430
  runSingleToolRound