@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/index.cjs CHANGED
@@ -1058,6 +1058,7 @@ __export(src_exports, {
1058
1058
  A2AAdapter: () => A2AAdapter,
1059
1059
  A2AGatewayAdapter: () => A2AGatewayAdapter,
1060
1060
  ACPClientAdapter: () => ACPClientAdapter,
1061
+ AcpSessionConfigError: () => AcpSessionConfigError,
1061
1062
  Agent: () => Agent,
1062
1063
  AgentFailure: () => import_band_sdk_core13.AgentFailure,
1063
1064
  AgentRuntime: () => AgentRuntime,
@@ -1069,10 +1070,13 @@ __export(src_exports, {
1069
1070
  ClaudeSDKAdapter: () => ClaudeSDKAdapter,
1070
1071
  CodexAdapter: () => CodexAdapter,
1071
1072
  CopilotACPAdapter: () => CopilotACPAdapter,
1073
+ CursorACPAdapter: () => CursorACPAdapter,
1072
1074
  DEFAULT_COPILOT_ACP_COMMAND: () => DEFAULT_COPILOT_ACP_COMMAND,
1075
+ DEFAULT_CURSOR_ACP_COMMAND: () => DEFAULT_CURSOR_ACP_COMMAND,
1073
1076
  DEFAULT_OMP_ACP_COMMAND: () => DEFAULT_OMP_ACP_COMMAND,
1074
1077
  DefaultPreprocessor: () => DefaultPreprocessor,
1075
1078
  DeliveryFailedError: () => DeliveryFailedError,
1079
+ FAILURE_CODE_SESSION_CONFIG: () => FAILURE_CODE_SESSION_CONFIG,
1076
1080
  FAILURE_EVENT_TYPE: () => FAILURE_EVENT_TYPE,
1077
1081
  FAILURE_METADATA_KEY: () => FAILURE_METADATA_KEY,
1078
1082
  GeminiAdapter: () => GeminiAdapter,
@@ -1082,6 +1086,7 @@ __export(src_exports, {
1082
1086
  LettaAdapter: () => LettaAdapter,
1083
1087
  MCP_SERVER_NAME: () => MCP_SERVER_NAME,
1084
1088
  MCP_TOOL_PREFIX: () => MCP_TOOL_PREFIX,
1089
+ MISSING_CONFIG_OPTIONS_REASON: () => MISSING_CONFIG_OPTIONS_REASON,
1085
1090
  OmpACPAdapter: () => OmpACPAdapter,
1086
1091
  OpenAIAdapter: () => OpenAIAdapter,
1087
1092
  OpencodeAdapter: () => OpencodeAdapter,
@@ -1094,6 +1099,7 @@ __export(src_exports, {
1094
1099
  VercelAISDKAdapter: () => VercelAISDKAdapter,
1095
1100
  WebSocketDisconnectError: () => WebSocketDisconnectError,
1096
1101
  agentFailure: () => agentFailure,
1102
+ applySessionConfigSelections: () => applySessionConfigSelections,
1097
1103
  deliverReply: () => deliverReply,
1098
1104
  deriveDefaultRestUrl: () => deriveDefaultRestUrl,
1099
1105
  isDirectExecution: () => isDirectExecution,
@@ -17103,26 +17109,165 @@ var ACPClientHistoryConverter = class {
17103
17109
 
17104
17110
  // src/adapters/acp/ACPClientAdapter.ts
17105
17111
  init_errors();
17106
- init_chatEvents();
17107
- init_schemas();
17108
17112
 
17109
- // src/adapters/acp/types.ts
17110
- function choosePermissionOption(options) {
17111
- if (options.length === 0) {
17112
- return null;
17113
+ // src/adapters/acp/sessionConfigReconciliation.ts
17114
+ var FAILURE_CODE_SESSION_CONFIG = "session_config";
17115
+ var MISSING_CONFIG_OPTIONS_REASON = "missing_config_options";
17116
+ var AcpSessionConfigTimeoutError = class extends Error {
17117
+ };
17118
+ var AcpSessionConfigError = class extends Error {
17119
+ provider;
17120
+ sessionId;
17121
+ optionId;
17122
+ selectedValue;
17123
+ acpCode;
17124
+ detail;
17125
+ timedOut;
17126
+ constructor(input) {
17127
+ super(input.message, input.cause !== void 0 ? { cause: input.cause } : void 0);
17128
+ this.name = "AcpSessionConfigError";
17129
+ this.provider = input.provider;
17130
+ this.sessionId = input.sessionId;
17131
+ this.optionId = input.optionId;
17132
+ this.selectedValue = input.selectedValue;
17133
+ this.acpCode = input.acpCode;
17134
+ this.detail = input.detail;
17135
+ this.timedOut = input.timedOut ?? false;
17136
+ }
17137
+ toAgentFailure() {
17138
+ return agentFailure(
17139
+ this.provider,
17140
+ this.message,
17141
+ this.acpCode !== void 0 ? String(this.acpCode) : FAILURE_CODE_SESSION_CONFIG,
17142
+ {
17143
+ sessionId: this.sessionId,
17144
+ optionId: this.optionId,
17145
+ selectedValue: this.selectedValue,
17146
+ detail: this.detail
17147
+ }
17148
+ );
17113
17149
  }
17114
- return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
17150
+ };
17151
+ async function applySessionConfigSelections(input) {
17152
+ let catalog = input.catalog;
17153
+ for (const { configId, value: selectedValue } of sessionConfigSelectionEntries(input.selections)) {
17154
+ if (selectedValue === void 0) {
17155
+ continue;
17156
+ }
17157
+ const option = catalog.find((entry) => entry?.id === configId);
17158
+ if (!option || !isSessionConfigSelect(option)) {
17159
+ throw new AcpSessionConfigError({
17160
+ provider: input.provider,
17161
+ sessionId: input.sessionId,
17162
+ optionId: configId,
17163
+ selectedValue,
17164
+ message: `Session config option "${configId}" is not available after prior selections.`
17165
+ });
17166
+ }
17167
+ if (selectedValue === option.currentValue) {
17168
+ continue;
17169
+ }
17170
+ const availableValues = flattenConfigSelectOptions(option.options).map((entry) => entry.value);
17171
+ if (!availableValues.includes(selectedValue)) {
17172
+ throw new AcpSessionConfigError({
17173
+ provider: input.provider,
17174
+ sessionId: input.sessionId,
17175
+ optionId: configId,
17176
+ selectedValue,
17177
+ message: `Session config value "${selectedValue}" is not advertised for option "${configId}".`,
17178
+ detail: { availableValues }
17179
+ });
17180
+ }
17181
+ const timeoutMessage = `setSessionConfigOption did not respond within ${input.timeoutMs}ms`;
17182
+ try {
17183
+ const response = await withTimeout(
17184
+ input.setOption({ sessionId: input.sessionId, configId, value: selectedValue }),
17185
+ input.timeoutMs,
17186
+ () => new AcpSessionConfigTimeoutError(timeoutMessage)
17187
+ );
17188
+ if (!Array.isArray(response?.configOptions)) {
17189
+ throw new AcpSessionConfigError({
17190
+ provider: input.provider,
17191
+ sessionId: input.sessionId,
17192
+ optionId: configId,
17193
+ selectedValue,
17194
+ message: `Session config option "${configId}" response did not include a refreshed catalog.`,
17195
+ detail: { reason: MISSING_CONFIG_OPTIONS_REASON }
17196
+ });
17197
+ }
17198
+ catalog = response.configOptions;
17199
+ } catch (error) {
17200
+ if (error instanceof AcpSessionConfigError) {
17201
+ throw error;
17202
+ }
17203
+ const acpError = asAcpJsonRpcError(error);
17204
+ throw new AcpSessionConfigError({
17205
+ provider: input.provider,
17206
+ sessionId: input.sessionId,
17207
+ optionId: configId,
17208
+ selectedValue,
17209
+ acpCode: acpError?.code,
17210
+ detail: acpError?.data,
17211
+ message: acpError?.message ?? asErrorMessage(error),
17212
+ cause: error,
17213
+ timedOut: error instanceof AcpSessionConfigTimeoutError
17214
+ });
17215
+ }
17216
+ }
17217
+ return { catalog };
17218
+ }
17219
+ function sessionConfigSelectionEntries(selections) {
17220
+ if (isOrderedSessionConfigSelections(selections)) {
17221
+ return selections;
17222
+ }
17223
+ return Object.keys(selections).map((configId) => ({ configId, value: selections[configId] }));
17224
+ }
17225
+ function isOrderedSessionConfigSelections(selections) {
17226
+ return Array.isArray(selections);
17227
+ }
17228
+ function isSessionConfigSelect(option) {
17229
+ return option?.type === "select";
17230
+ }
17231
+ function flattenConfigSelectOptions(options) {
17232
+ if (!Array.isArray(options)) {
17233
+ return [];
17234
+ }
17235
+ return options.flatMap((entry) => {
17236
+ if (!asOptionalRecord2(entry)) {
17237
+ return [];
17238
+ }
17239
+ if ("group" in entry) {
17240
+ return Array.isArray(entry.options) ? entry.options : [];
17241
+ }
17242
+ return [entry];
17243
+ });
17244
+ }
17245
+ function isAcpErrorResponse(error) {
17246
+ return typeof error === "object" && error !== null && typeof error.code === "number" && typeof error.message === "string";
17247
+ }
17248
+ function asAcpJsonRpcError(error) {
17249
+ if (isAcpErrorResponse(error)) {
17250
+ return error;
17251
+ }
17252
+ const nested = asOptionalRecord2(error)?.error;
17253
+ return isAcpErrorResponse(nested) ? nested : void 0;
17115
17254
  }
17116
17255
 
17256
+ // src/adapters/acp/ACPClientAdapter.ts
17257
+ init_chatEvents();
17258
+ init_schemas();
17259
+
17117
17260
  // src/adapters/acp/client.ts
17118
17261
  var BandACPClient = class {
17119
17262
  sessionChunks = /* @__PURE__ */ new Map();
17120
17263
  permissionHandler;
17264
+ extensionHandler;
17121
17265
  // The handler is connection-scoped and required at construction, so it is
17122
17266
  // already in place before the agent process is spawned: there is no window
17123
17267
  // in which a `session/request_permission` has nowhere to go.
17124
- constructor(permissionHandler) {
17268
+ constructor(permissionHandler, extensionHandler) {
17125
17269
  this.permissionHandler = permissionHandler;
17270
+ this.extensionHandler = extensionHandler;
17126
17271
  }
17127
17272
  beginSession(sessionId) {
17128
17273
  this.sessionChunks.set(sessionId, []);
@@ -17167,62 +17312,26 @@ var BandACPClient = class {
17167
17312
  return chunks;
17168
17313
  }
17169
17314
  async extMethod(method, params) {
17170
- if (method === "cursor/ask_question") {
17171
- const options = Array.isArray(params.options) ? params.options : [];
17172
- const selected = choosePermissionOption(
17173
- options.filter((option) => !!option && typeof option === "object")
17174
- );
17175
- if (!selected) {
17176
- return {
17177
- outcome: {
17178
- type: "cancelled"
17179
- }
17180
- };
17181
- }
17182
- return {
17183
- outcome: {
17184
- type: "selected",
17185
- optionId: selected.optionId
17186
- }
17187
- };
17188
- }
17189
- if (method === "cursor/create_plan") {
17190
- return {
17191
- outcome: {
17192
- type: "approved"
17193
- }
17194
- };
17195
- }
17196
- return {};
17315
+ const result = await this.extensionHandler?.extMethod?.(
17316
+ method,
17317
+ params,
17318
+ { sessionId: sessionIdFrom(params) }
17319
+ );
17320
+ return result ?? {};
17197
17321
  }
17198
17322
  async extNotification(method, params) {
17199
- const sessionId = toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
17200
- if (!sessionId) {
17201
- return;
17202
- }
17203
- if (method === "cursor/update_todos") {
17204
- const todos = Array.isArray(params.todos) ? params.todos : [];
17205
- const lines = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).filter((line) => line.trim().length > 0);
17206
- if (lines.length > 0) {
17207
- this.appendChunk(sessionId, {
17208
- chunkType: "plan",
17209
- content: lines.join("\n"),
17210
- metadata: {},
17211
- streamed: false
17212
- });
17213
- }
17323
+ const sessionId = sessionIdFrom(params);
17324
+ const chunks = await this.extensionHandler?.extNotification?.(
17325
+ method,
17326
+ params,
17327
+ { sessionId }
17328
+ );
17329
+ const targetSessionId = sessionId ?? this.extensionHandler?.extensionSessionId?.() ?? null;
17330
+ if (!targetSessionId || !chunks) {
17214
17331
  return;
17215
17332
  }
17216
- if (method === "cursor/task") {
17217
- const result = toOptionalString(params.result);
17218
- if (result) {
17219
- this.appendChunk(sessionId, {
17220
- chunkType: "text",
17221
- content: `[Task completed] ${result}`,
17222
- metadata: {},
17223
- streamed: false
17224
- });
17225
- }
17333
+ for (const chunk of chunks) {
17334
+ this.appendChunk(targetSessionId, chunk);
17226
17335
  }
17227
17336
  }
17228
17337
  appendChunk(sessionId, chunk) {
@@ -17347,6 +17456,17 @@ function extractTextFromContent(content) {
17347
17456
  function toOptionalString(value) {
17348
17457
  return typeof value === "string" && value.length > 0 ? value : null;
17349
17458
  }
17459
+ function sessionIdFrom(params) {
17460
+ return toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
17461
+ }
17462
+
17463
+ // src/adapters/acp/types.ts
17464
+ function choosePermissionOption(options) {
17465
+ if (options.length === 0) {
17466
+ return null;
17467
+ }
17468
+ return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
17469
+ }
17350
17470
 
17351
17471
  // src/adapters/acp/loader.ts
17352
17472
  init_errors();
@@ -17361,6 +17481,13 @@ var acpModule = new LazyAsyncValue({
17361
17481
  });
17362
17482
 
17363
17483
  // src/adapters/acp/ACPClientAdapter.ts
17484
+ function createConnectionRetirement() {
17485
+ let reject = () => void 0;
17486
+ const promise = new Promise((_resolve, rejectPromise) => {
17487
+ reject = rejectPromise;
17488
+ });
17489
+ return { promise, reject };
17490
+ }
17364
17491
  var DEFAULT_PERMISSION_TIMEOUT_MS = 5 * 6e4;
17365
17492
  var DEFAULT_TURN_TIMEOUT_MS = 60 * 6e4;
17366
17493
  var SET_SESSION_CONFIG_TIMEOUT_MS = 1e4;
@@ -17386,6 +17513,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17386
17513
  additionalMcpTools;
17387
17514
  clientCapabilities;
17388
17515
  connectionFactory;
17516
+ extensionHandler;
17389
17517
  tcpEndpoint;
17390
17518
  // The value's `generation` is the connection generation the session was
17391
17519
  // last established/restored against. `client` is the exact BandACPClient
@@ -17418,6 +17546,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17418
17546
  permissionTimeoutMs;
17419
17547
  turnTimeoutMs;
17420
17548
  logger;
17549
+ customSection;
17421
17550
  backend = null;
17422
17551
  backendPromise = null;
17423
17552
  client = null;
@@ -17428,6 +17557,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17428
17557
  started = false;
17429
17558
  systemPrompt = "";
17430
17559
  spawnPromise = null;
17560
+ connectionRetirements = /* @__PURE__ */ new WeakMap();
17431
17561
  // Bumped by `stop()` and on every successful spawn install. Cleanup/timeout
17432
17562
  // and permission maps key by this plus session id so a stale generation
17433
17563
  // cannot alias a same-id session on a newer connection.
@@ -17448,12 +17578,14 @@ var ACPClientAdapter = class extends SimpleAdapter {
17448
17578
  this.additionalMcpTools = [...options.additionalMcpTools ?? []];
17449
17579
  this.clientCapabilities = options.clientCapabilities;
17450
17580
  this.connectionFactory = options.connectionFactory;
17581
+ this.extensionHandler = options.extensionHandler;
17451
17582
  this.tcpEndpoint = tcpEndpoint;
17452
17583
  this.resolvePermission = options.resolvePermission;
17453
17584
  this.resolveSessionMode = options.resolveSessionMode;
17454
17585
  this.resolveSessionModel = options.resolveSessionModel;
17455
17586
  this.resolveSessionConfig = options.resolveSessionConfig;
17456
17587
  this.logger = resolveLogger(options.logger);
17588
+ this.customSection = options.customSection;
17457
17589
  this.permissionTimeoutMs = options.permissionTimeoutMs ?? DEFAULT_PERMISSION_TIMEOUT_MS;
17458
17590
  if ((this.resolvePermission || this.resolveSessionMode || this.resolveSessionModel || this.resolveSessionConfig) && (!Number.isFinite(this.permissionTimeoutMs) || this.permissionTimeoutMs <= 0)) {
17459
17591
  throw new ValidationError(`permissionTimeoutMs must be a positive finite number, got ${options.permissionTimeoutMs}`);
@@ -17485,7 +17617,8 @@ var ACPClientAdapter = class extends SimpleAdapter {
17485
17617
  this.systemPrompt = renderSystemPrompt({
17486
17618
  agentName,
17487
17619
  agentDescription,
17488
- includeBaseInstructions: false
17620
+ includeBaseInstructions: false,
17621
+ customSection: this.customSection
17489
17622
  });
17490
17623
  await this.ensureConnection();
17491
17624
  }
@@ -17501,6 +17634,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17501
17634
  let client = null;
17502
17635
  let sessionId;
17503
17636
  let generation = 0;
17637
+ await this.onAcpTurnStarted(message, tools, context);
17504
17638
  try {
17505
17639
  const ensured = await this.ensureConnection();
17506
17640
  connection = ensured.connection;
@@ -17511,6 +17645,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17511
17645
  }
17512
17646
  sessionId = await this.getOrCreateSession(context.roomId, connection, generation, client);
17513
17647
  const sessionKey = this.sessionKey(generation, sessionId);
17648
+ await this.onAcpSessionReady(message, tools, context, sessionId);
17514
17649
  client.beginSession(sessionId);
17515
17650
  const content = replaceUuidMentions(message.content, mentionSubjectsFromMetadata(message.metadata));
17516
17651
  const messageWithContext = [...systemUpdateParts(participantsMessage, contactsMessage), content].join("\n\n");
@@ -17518,13 +17653,17 @@ var ACPClientAdapter = class extends SimpleAdapter {
17518
17653
 
17519
17654
  ${messageWithContext}`;
17520
17655
  this.bootstrappedSessions.add(sessionKey);
17521
- const response = await withTimeout(connection.prompt({
17522
- sessionId,
17523
- prompt: [{
17524
- type: "text",
17525
- text: promptText
17526
- }]
17527
- }), this.turnTimeoutMs, () => new AcpTurnTimeoutError());
17656
+ const response = await withTimeout(
17657
+ this.raceAgainstConnectionRetirement(connection, connection.prompt({
17658
+ sessionId,
17659
+ prompt: [{
17660
+ type: "text",
17661
+ text: promptText
17662
+ }]
17663
+ })),
17664
+ this.turnTimeoutMs,
17665
+ () => new AcpTurnTimeoutError()
17666
+ );
17528
17667
  await this.flushChunks({
17529
17668
  client,
17530
17669
  tools,
@@ -17565,15 +17704,28 @@ ${messageWithContext}`;
17565
17704
  }
17566
17705
  }
17567
17706
  }
17568
- const acpError = asAcpJsonRpcError(error);
17707
+ const configError = error instanceof AcpSessionConfigError ? error : void 0;
17708
+ const acpError = configError ? void 0 : asAcpJsonRpcError(error);
17569
17709
  await reportTurnFailure(
17570
17710
  tools,
17571
- 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)),
17711
+ 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)),
17572
17712
  this.logger,
17573
- { roomId: context.roomId, sessionId }
17713
+ {
17714
+ roomId: context.roomId,
17715
+ sessionId: configError?.sessionId ?? sessionId,
17716
+ ...configError ? { optionId: configError.optionId, selectedValue: configError.selectedValue } : {}
17717
+ }
17574
17718
  );
17719
+ } finally {
17720
+ await this.onAcpTurnFinished(message, tools, context);
17575
17721
  }
17576
17722
  }
17723
+ async onAcpTurnStarted(_message, _tools, _context) {
17724
+ }
17725
+ async onAcpTurnFinished(_message, _tools, _context) {
17726
+ }
17727
+ async onAcpSessionReady(_message, _tools, _context, _sessionId) {
17728
+ }
17577
17729
  // Best-effort: tells the agent to stop working on a turn Band has already
17578
17730
  // given up waiting for (the ACP client has no way to force it), evicts the
17579
17731
  // session so the room's next turn re-establishes rather than reuses it
@@ -17589,17 +17741,12 @@ ${messageWithContext}`;
17589
17741
  // would risk blocking this room's turn lock forever on the very process
17590
17742
  // that just proved it can hang.
17591
17743
  async abandonTimedOutTurn(connection, sessionId, generation) {
17592
- const key = this.sessionKey(generation, sessionId);
17593
- this.activeSessions.delete(key);
17594
- this.abandonedSessions.add(key);
17595
- const owner = [...this.roomToSession.entries()].find(([, value]) => value.sessionId === sessionId && value.generation === generation);
17596
- if (owner) {
17597
- this.unlinkOwner(owner[0], owner[1]);
17598
- }
17599
- abandon(
17600
- () => connection.cancel({ sessionId }),
17601
- (error) => this.safeWarn("acp_client.cancel_failed", { sessionId, error: asErrorMessage(error) })
17602
- );
17744
+ this.evictAbandonedSession(sessionId, generation, connection, () => {
17745
+ const owner = [...this.roomToSession.entries()].find(([, value]) => value.sessionId === sessionId && value.generation === generation);
17746
+ if (owner) {
17747
+ this.unlinkOwner(owner[0], owner[1]);
17748
+ }
17749
+ });
17603
17750
  }
17604
17751
  // A per-room async mutex: `fn` for a given `roomId` never overlaps another
17605
17752
  // call for that same room, while different rooms stay fully concurrent.
@@ -17725,6 +17872,11 @@ ${messageWithContext}`;
17725
17872
  void connection.closed.then(() => reject(new Error("ACP connection closed while a session operation was still in flight")));
17726
17873
  return Promise.race([operation, closedRejection]);
17727
17874
  }
17875
+ raceAgainstConnectionRetirement(connection, operation) {
17876
+ const retirement = this.connectionRetirements.get(connection) ?? createConnectionRetirement();
17877
+ this.connectionRetirements.set(connection, retirement);
17878
+ return Promise.race([operation, retirement.promise]);
17879
+ }
17728
17880
  unlinkRoom(roomId) {
17729
17881
  const owner = this.roomToSession.get(roomId);
17730
17882
  if (owner) {
@@ -17743,6 +17895,9 @@ ${messageWithContext}`;
17743
17895
  sessionKey(generation, sessionId) {
17744
17896
  return `${generation}:${sessionId}`;
17745
17897
  }
17898
+ roomIdForSession(sessionId) {
17899
+ return [...this.roomToSession.entries()].find(([, owner]) => owner.sessionId === sessionId)?.[0];
17900
+ }
17746
17901
  async ensureConnection() {
17747
17902
  if (this.connection && !this.connection.signal.aborted) {
17748
17903
  return { connection: this.connection, generation: this.connectionGeneration };
@@ -17779,7 +17934,10 @@ ${messageWithContext}`;
17779
17934
  throw new Error(CONNECTION_ATTEMPT_SUPERSEDED_ERROR);
17780
17935
  }
17781
17936
  const owner = { generation: -1 };
17782
- const client = new BandACPClient((params) => this.routePermissionRequest(params, owner.generation));
17937
+ const client = new BandACPClient(
17938
+ (params) => this.routePermissionRequest(params, owner.generation),
17939
+ this.extensionHandler
17940
+ );
17783
17941
  handle = await (this.connectionFactory ? this.connectionFactory(client, {
17784
17942
  command: this.command,
17785
17943
  cwd: this.cwd,
@@ -17869,7 +18027,7 @@ ${messageWithContext}`;
17869
18027
  this.activeSessions.add(restoredKey);
17870
18028
  this.bootstrappedSessions.add(restoredKey);
17871
18029
  await this.configureSessionMode(roomId, existingSessionId, restored.modes, connection);
17872
- await this.configureSessionConfig(roomId, existingSessionId, restored.configOptions, connection);
18030
+ await this.configureSessionConfig(roomId, existingSessionId, restored.configOptions, connection, connectionGeneration, client);
17873
18031
  if (!this.resolveSessionConfig) {
17874
18032
  await this.configureSessionModel(roomId, existingSessionId, restored.configOptions, connection);
17875
18033
  }
@@ -17883,13 +18041,13 @@ ${messageWithContext}`;
17883
18041
  this.linkOrAbandon(roomId, created.sessionId, generation, connectionGeneration, client);
17884
18042
  this.activeSessions.add(this.sessionKey(connectionGeneration, created.sessionId));
17885
18043
  await this.configureSessionMode(roomId, created.sessionId, created.modes, connection);
17886
- await this.configureSessionConfig(roomId, created.sessionId, created.configOptions, connection);
18044
+ await this.configureSessionConfig(roomId, created.sessionId, created.configOptions, connection, connectionGeneration, client);
17887
18045
  if (!this.resolveSessionConfig) {
17888
18046
  await this.configureSessionModel(roomId, created.sessionId, created.configOptions, connection);
17889
18047
  }
17890
18048
  return created.sessionId;
17891
18049
  }
17892
- async configureSessionConfig(roomId, sessionId, configOptions, connection) {
18050
+ async configureSessionConfig(roomId, sessionId, configOptions, connection, connectionGeneration, client) {
17893
18051
  if (!this.resolveSessionConfig || !Array.isArray(configOptions) || configOptions.length === 0) {
17894
18052
  return;
17895
18053
  }
@@ -17902,35 +18060,76 @@ ${messageWithContext}`;
17902
18060
  if (!selections) {
17903
18061
  return;
17904
18062
  }
17905
- for (const option of advertisedOptions) {
17906
- const selectedValue = selections[option.id];
17907
- if (selectedValue === void 0 || selectedValue === option.currentValue || !isSessionConfigSelect(option)) {
17908
- continue;
17909
- }
17910
- const availableValues = flattenConfigSelectOptions(option.options).map((entry) => entry.value);
17911
- if (!availableValues.includes(selectedValue)) {
17912
- this.safeWarn("resolveSessionConfig selected a value this session does not advertise", {
17913
- sessionId,
17914
- configId: option.id,
17915
- selectedValue,
17916
- availableValues
17917
- });
17918
- continue;
17919
- }
17920
- try {
17921
- await withTimeout(
17922
- connection.setSessionConfigOption({ sessionId, configId: option.id, value: selectedValue }),
17923
- SET_SESSION_CONFIG_TIMEOUT_MS,
17924
- `setSessionConfigOption did not respond within ${SET_SESSION_CONFIG_TIMEOUT_MS}ms`
17925
- );
17926
- } catch (error) {
17927
- this.safeWarn("failed to switch session config option", {
17928
- sessionId,
17929
- configId: option.id,
17930
- selectedValue,
17931
- error: String(error)
17932
- });
18063
+ try {
18064
+ await applySessionConfigSelections({
18065
+ provider: this.provider,
18066
+ sessionId,
18067
+ catalog: advertisedOptions,
18068
+ selections,
18069
+ setOption: (params) => connection.setSessionConfigOption(params),
18070
+ timeoutMs: SET_SESSION_CONFIG_TIMEOUT_MS
18071
+ });
18072
+ } catch (error) {
18073
+ this.abandonFailedConfigSession(
18074
+ roomId,
18075
+ sessionId,
18076
+ connectionGeneration,
18077
+ client,
18078
+ connection,
18079
+ error instanceof AcpSessionConfigError && error.timedOut
18080
+ );
18081
+ throw error;
18082
+ }
18083
+ }
18084
+ // A config failure mid-establish must not leave a half-applied session
18085
+ // active for the room: the next turn needs a fresh `newSession` catalog.
18086
+ abandonFailedConfigSession(roomId, sessionId, connectionGeneration, client, connection, retireConnection) {
18087
+ const key = this.sessionKey(connectionGeneration, sessionId);
18088
+ this.bootstrappedSessions.delete(key);
18089
+ client.resetChunks(sessionId);
18090
+ this.evictAbandonedSession(sessionId, connectionGeneration, connection, () => {
18091
+ const owner = this.roomToSession.get(roomId);
18092
+ if (owner && owner.sessionId === sessionId && owner.generation === connectionGeneration) {
18093
+ this.unlinkOwner(roomId, owner);
17933
18094
  }
18095
+ });
18096
+ if (retireConnection) {
18097
+ this.retireConnection(connection, connectionGeneration);
18098
+ }
18099
+ }
18100
+ // Common half of timeout and config-failure abandon: mark the session
18101
+ // unusable for restore, unlink ownership, and best-effort cancel.
18102
+ evictAbandonedSession(sessionId, connectionGeneration, connection, unlink) {
18103
+ const key = this.sessionKey(connectionGeneration, sessionId);
18104
+ const wasActive = this.activeSessions.delete(key);
18105
+ if (wasActive) {
18106
+ this.abandonedSessions.add(key);
18107
+ }
18108
+ unlink();
18109
+ abandon(
18110
+ () => connection.cancel({ sessionId }),
18111
+ (error) => this.safeWarn("acp_client.cancel_failed", { sessionId, error: asErrorMessage(error) })
18112
+ );
18113
+ }
18114
+ // A timed-out config RPC means this transport has already failed to answer
18115
+ // one request. Retire it so the next turn cannot wait forever on another.
18116
+ retireConnection(connection, generation) {
18117
+ if (this.connection !== connection || this.connectionGeneration !== generation) {
18118
+ return;
18119
+ }
18120
+ const handle = this.connectionHandle;
18121
+ this.connectionGeneration++;
18122
+ this.connection = null;
18123
+ this.connectionHandle = null;
18124
+ this.connectionState = null;
18125
+ this.client = null;
18126
+ this.pruneConnectionGeneration(generation);
18127
+ this.connectionRetirements.get(connection)?.reject(new Error("ACP connection retired after a config timeout"));
18128
+ if (handle) {
18129
+ abandon(
18130
+ () => handle.stop(),
18131
+ (error) => this.safeWarn("acp_client.handle_stop_after_config_timeout", { error: asErrorMessage(error) })
18132
+ );
17934
18133
  }
17935
18134
  }
17936
18135
  // The single gate an establishment must pass before it's allowed to claim
@@ -18544,39 +18743,12 @@ async function createTcpConnection(client, endpoint, signal) {
18544
18743
  };
18545
18744
  }
18546
18745
  var MODEL_CONFIG_OPTION_KEY = "model";
18547
- function isSessionConfigSelect(option) {
18548
- return option?.type === "select";
18549
- }
18550
18746
  function isModelConfigOption(option) {
18551
18747
  return isSessionConfigSelect(option) && option.category === MODEL_CONFIG_OPTION_KEY;
18552
18748
  }
18553
18749
  function isModelConfigOptionById(option) {
18554
18750
  return isSessionConfigSelect(option) && option.id === MODEL_CONFIG_OPTION_KEY;
18555
18751
  }
18556
- function flattenConfigSelectOptions(options) {
18557
- if (!Array.isArray(options)) {
18558
- return [];
18559
- }
18560
- return options.flatMap((entry) => {
18561
- if (!asOptionalRecord2(entry)) {
18562
- return [];
18563
- }
18564
- if ("group" in entry) {
18565
- return Array.isArray(entry.options) ? entry.options : [];
18566
- }
18567
- return [entry];
18568
- });
18569
- }
18570
- function isAcpErrorResponse(error) {
18571
- return typeof error === "object" && error !== null && typeof error.code === "number" && typeof error.message === "string";
18572
- }
18573
- function asAcpJsonRpcError(error) {
18574
- if (isAcpErrorResponse(error)) {
18575
- return error;
18576
- }
18577
- const nested = asOptionalRecord2(error)?.error;
18578
- return isAcpErrorResponse(nested) ? nested : void 0;
18579
- }
18580
18752
 
18581
18753
  // src/adapters/acp/ACPServer.ts
18582
18754
  var import_node_stream2 = require("stream");
@@ -18621,6 +18793,358 @@ var CopilotACPAdapter = class extends ACPClientAdapter {
18621
18793
  }
18622
18794
  };
18623
18795
 
18796
+ // src/adapters/cursor-acp/CursorACPAdapter.ts
18797
+ var DEFAULT_CURSOR_ACP_COMMAND = ["agent", "acp"];
18798
+ var DEFAULT_CURSOR_DECISION_TIMEOUT_MS = 3e5;
18799
+ var DEFAULT_CURSOR_MAX_PENDING_DECISIONS = 10;
18800
+ var CursorExtensions = class {
18801
+ adapter = null;
18802
+ todosBySession = /* @__PURE__ */ new Map();
18803
+ bind(adapter) {
18804
+ this.adapter = adapter;
18805
+ }
18806
+ async resolvePermission(request, signal) {
18807
+ return this.adapter?.resolveCursorPermission(request, signal);
18808
+ }
18809
+ extensionSessionId() {
18810
+ return this.adapter?.extensionSessionId() ?? null;
18811
+ }
18812
+ async extMethod(method, params, context) {
18813
+ return this.adapter?.resolveExtension(method, params, context.sessionId) ?? null;
18814
+ }
18815
+ async extNotification(method, params, context) {
18816
+ const sessionId = context.sessionId ?? this.extensionSessionId();
18817
+ if (!sessionId) {
18818
+ return;
18819
+ }
18820
+ if (method === "cursor/update_todos") {
18821
+ const content = this.updateTodos(sessionId, params);
18822
+ if (!content) {
18823
+ return;
18824
+ }
18825
+ return [{ chunkType: "plan", content, metadata: { cursor_todos: true }, streamed: false }];
18826
+ }
18827
+ if (method === "cursor/task") {
18828
+ const description = stringValue(params.description);
18829
+ if (!description) {
18830
+ return [];
18831
+ }
18832
+ const subagentType = stringValue(params.subagentType) ?? "unspecified";
18833
+ const model = stringValue(params.model);
18834
+ const suffix = model ? ` (${model})` : "";
18835
+ return [{ chunkType: "plan", content: `[Cursor ${subagentType} task] ${description}${suffix}`, metadata: {}, streamed: false }];
18836
+ }
18837
+ if (method === "cursor/generate_image") {
18838
+ const description = stringValue(params.description);
18839
+ if (!description) {
18840
+ return [];
18841
+ }
18842
+ const filePath = stringValue(params.filePath);
18843
+ return [{ chunkType: "plan", content: `[Cursor generated image] ${description}${filePath ? ` \u2192 ${filePath}` : ""}`, metadata: {}, streamed: false }];
18844
+ }
18845
+ }
18846
+ forgetSession(sessionId) {
18847
+ this.todosBySession.delete(sessionId);
18848
+ }
18849
+ clearSessions() {
18850
+ this.todosBySession.clear();
18851
+ }
18852
+ updateTodos(sessionId, params) {
18853
+ const todos = parseTodos(params.todos);
18854
+ if (params.merge === true) {
18855
+ const current2 = this.todosBySession.get(sessionId) ?? /* @__PURE__ */ new Map();
18856
+ for (const todo of todos) {
18857
+ current2.set(todo.id, todo);
18858
+ }
18859
+ this.todosBySession.set(sessionId, current2);
18860
+ } else {
18861
+ this.todosBySession.set(sessionId, new Map(todos.map((todo) => [todo.id, todo])));
18862
+ }
18863
+ const current = this.todosBySession.get(sessionId);
18864
+ return current && current.size > 0 ? [...current.values()].map((todo) => `- [${todoMark(todo.status)}] ${todo.content}`).join("\n") : void 0;
18865
+ }
18866
+ };
18867
+ var CursorACPAdapter = class extends ACPClientAdapter {
18868
+ provider = "cursor-acp";
18869
+ approvalMode;
18870
+ questionMode;
18871
+ planMode;
18872
+ decisionTimeoutMs;
18873
+ maxPendingDecisions;
18874
+ authorizedSenders;
18875
+ decisionLogger;
18876
+ extensions;
18877
+ turns = /* @__PURE__ */ new Map();
18878
+ pending = /* @__PURE__ */ new Map();
18879
+ activeTurn = null;
18880
+ turnTail = Promise.resolve();
18881
+ constructor(options = {}) {
18882
+ const extensions = new CursorExtensions();
18883
+ validateOptions(options);
18884
+ const env = cursorEnv(options);
18885
+ super({
18886
+ ...options,
18887
+ env,
18888
+ command: options.command ?? [...DEFAULT_CURSOR_ACP_COMMAND],
18889
+ authMethod: "cursor_login",
18890
+ extensionHandler: extensions,
18891
+ resolvePermission: (request, signal) => extensions.resolvePermission(request, signal)
18892
+ });
18893
+ extensions.bind(this);
18894
+ this.extensions = extensions;
18895
+ this.approvalMode = options.approvalMode ?? "manual";
18896
+ this.questionMode = options.questionMode ?? "manual";
18897
+ this.planMode = options.planMode ?? "manual";
18898
+ this.decisionTimeoutMs = options.decisionTimeoutMs ?? DEFAULT_CURSOR_DECISION_TIMEOUT_MS;
18899
+ this.maxPendingDecisions = options.maxPendingDecisions ?? DEFAULT_CURSOR_MAX_PENDING_DECISIONS;
18900
+ this.authorizedSenders = options.decisionAuthorizedSenders ? new Set(options.decisionAuthorizedSenders) : null;
18901
+ this.decisionLogger = resolveLogger(options.logger);
18902
+ }
18903
+ async onMessage(message, tools, history, participantsMessage, contactsMessage, context) {
18904
+ if (await this.handleControl(message, tools, context.roomId)) {
18905
+ return;
18906
+ }
18907
+ await this.withCursorTurnLock(() => super.onMessage(message, tools, history, participantsMessage, contactsMessage, context));
18908
+ }
18909
+ async onAcpTurnStarted(message, tools, context) {
18910
+ const turn = { messageId: message.id, roomId: context.roomId, tools, requesterId: message.senderId };
18911
+ this.turns.set(context.roomId, turn);
18912
+ this.activeTurn = turn;
18913
+ }
18914
+ async onAcpSessionReady(message, _tools, context, sessionId) {
18915
+ const turn = this.turns.get(context.roomId);
18916
+ if (turn?.messageId === message.id) {
18917
+ turn.sessionId = sessionId;
18918
+ }
18919
+ }
18920
+ async onAcpTurnFinished(message, _tools, context) {
18921
+ const turn = this.turns.get(context.roomId);
18922
+ if (turn?.messageId === message.id) {
18923
+ this.turns.delete(context.roomId);
18924
+ this.cancelRoom(context.roomId);
18925
+ }
18926
+ if (this.activeTurn?.messageId === message.id) {
18927
+ this.activeTurn = null;
18928
+ }
18929
+ }
18930
+ async onCleanup(roomId) {
18931
+ const sessionId = this.turns.get(roomId)?.sessionId;
18932
+ this.cancelRoom(roomId);
18933
+ this.turns.delete(roomId);
18934
+ if (this.activeTurn?.roomId === roomId) {
18935
+ this.activeTurn = null;
18936
+ }
18937
+ await super.onCleanup(roomId);
18938
+ if (sessionId) {
18939
+ this.extensions.forgetSession(sessionId);
18940
+ }
18941
+ }
18942
+ async stop() {
18943
+ for (const decision of this.pending.values()) {
18944
+ decision.resolve(void 0);
18945
+ }
18946
+ this.pending.clear();
18947
+ this.turns.clear();
18948
+ this.activeTurn = null;
18949
+ this.extensions.clearSessions();
18950
+ await super.stop();
18951
+ }
18952
+ async resolveExtension(method, params, sessionId) {
18953
+ const roomId = sessionId ? this.roomIdForSession(sessionId) : this.activeTurn?.roomId;
18954
+ const turn = roomId ? this.turns.get(roomId) : void 0;
18955
+ if (!turn || sessionId && turn.sessionId !== sessionId) {
18956
+ return { outcome: { outcome: "cancelled" } };
18957
+ }
18958
+ if (method === "cursor/ask_question") {
18959
+ return this.resolveQuestion(turn.roomId, turn, params);
18960
+ }
18961
+ if (method === "cursor/create_plan") {
18962
+ return this.resolvePlan(turn.roomId, turn, params);
18963
+ }
18964
+ return {};
18965
+ }
18966
+ async resolveCursorPermission(request, signal) {
18967
+ if (this.approvalMode === "autoAccept") {
18968
+ return allowOption(request.options)?.optionId;
18969
+ }
18970
+ if (this.approvalMode === "autoDecline") {
18971
+ return void 0;
18972
+ }
18973
+ const turn = this.turns.get(request.roomId);
18974
+ if (!turn) {
18975
+ return void 0;
18976
+ }
18977
+ const options = request.options.map((option) => option.optionId);
18978
+ 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);
18979
+ return typeof token === "string" && options.includes(token) ? token : void 0;
18980
+ }
18981
+ extensionSessionId() {
18982
+ return this.activeTurn?.sessionId ?? null;
18983
+ }
18984
+ async resolveQuestion(roomId, turn, params) {
18985
+ const questions = questionChoices(params.questions);
18986
+ if (questions.choices.size === 0) {
18987
+ return { outcome: { outcome: "cancelled" } };
18988
+ }
18989
+ if (this.questionMode === "autoCancel") {
18990
+ return { outcome: { outcome: "cancelled" } };
18991
+ }
18992
+ if (this.questionMode === "autoFirst") {
18993
+ return answered(Object.fromEntries([...questions.choices].map(([id, options]) => [id, [options[0]]])));
18994
+ }
18995
+ 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] ...`.");
18996
+ return isRecord3(result) ? result : { outcome: { outcome: "cancelled" } };
18997
+ }
18998
+ async resolvePlan(roomId, turn, params) {
18999
+ if (this.planMode === "autoAccept") {
19000
+ return { outcome: { outcome: "accepted" } };
19001
+ }
19002
+ if (this.planMode === "autoDecline") {
19003
+ return { outcome: { outcome: "rejected" } };
19004
+ }
19005
+ const title = stringValue(params.title) ?? "Cursor plan";
19006
+ 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}\`.`);
19007
+ return isRecord3(result) ? result : { outcome: { outcome: "cancelled" } };
19008
+ }
19009
+ async waitForDecision(kind, roomId, turn, choices, multiSelect, prompt, signal) {
19010
+ if (this.pending.size >= this.maxPendingDecisions) {
19011
+ this.pending.values().next().value?.resolve(void 0);
19012
+ }
19013
+ const token = crypto.randomUUID().slice(0, 8);
19014
+ return new Promise((resolve) => {
19015
+ const timer = setTimeout(() => settle(void 0), this.decisionTimeoutMs);
19016
+ const abort = () => settle(void 0);
19017
+ const settle = (value) => {
19018
+ clearTimeout(timer);
19019
+ signal?.removeEventListener("abort", abort);
19020
+ this.pending.delete(token);
19021
+ resolve(value);
19022
+ };
19023
+ this.pending.set(token, { kind, roomId, choices, multiSelect, resolve: settle });
19024
+ signal?.addEventListener("abort", abort, { once: true });
19025
+ void turn.tools.sendMessage(prompt.replaceAll("{token}", token), [turn.requesterId]).catch((error) => {
19026
+ this.decisionLogger.warn("cursor_acp.decision_prompt_delivery_failed", { roomId, kind, error: String(error) });
19027
+ settle(void 0);
19028
+ });
19029
+ });
19030
+ }
19031
+ async handleControl(message, tools, roomId) {
19032
+ const words = message.content.trim().split(/\s+/);
19033
+ if (words[0]?.toLowerCase() !== "/cursor") {
19034
+ return false;
19035
+ }
19036
+ if (words.length === 1 || words[1]?.toLowerCase() === "decisions") {
19037
+ const entries = [...this.pending.entries()].filter(([, decision2]) => decision2.roomId === roomId).map(([token2, decision2]) => `\`${token2}\` (${decision2.kind})`);
19038
+ await tools.sendMessage(`Pending Cursor decisions: ${entries.join(", ") || "none"}`);
19039
+ return true;
19040
+ }
19041
+ const [_, action, token, ...args] = words;
19042
+ const decision = token ? this.pending.get(token) : void 0;
19043
+ if (!decision || decision.roomId !== roomId) {
19044
+ await tools.sendMessage(`Cursor decision \`${token ?? ""}\` is not pending.`);
19045
+ return true;
19046
+ }
19047
+ if (this.authorizedSenders && !this.authorizedSenders.has(message.senderId)) {
19048
+ await tools.sendMessage("You are not authorized to resolve Cursor decisions.");
19049
+ return true;
19050
+ }
19051
+ const result = commandResult(action ?? "", args, decision);
19052
+ if (result === null) {
19053
+ await tools.sendMessage(`That command is not valid for Cursor ${decision.kind} decision \`${token}\`.`);
19054
+ return true;
19055
+ }
19056
+ decision.resolve(result);
19057
+ await tools.sendMessage(`Cursor ${decision.kind} decision \`${token}\` resolved.`);
19058
+ return true;
19059
+ }
19060
+ cancelRoom(roomId) {
19061
+ for (const [token, decision] of this.pending) {
19062
+ if (decision.roomId === roomId) {
19063
+ decision.resolve(void 0);
19064
+ this.pending.delete(token);
19065
+ }
19066
+ }
19067
+ }
19068
+ async withCursorTurnLock(run) {
19069
+ const queued = this.turnTail.then(run, run);
19070
+ this.turnTail = queued.then(() => void 0, () => void 0);
19071
+ return queued;
19072
+ }
19073
+ };
19074
+ function cursorEnv(options) {
19075
+ const env = { ...options.env };
19076
+ if (options.apiKey) env.CURSOR_API_KEY ??= options.apiKey;
19077
+ if (options.authToken) env.CURSOR_AUTH_TOKEN ??= options.authToken;
19078
+ return Object.keys(env).length > 0 ? env : void 0;
19079
+ }
19080
+ function validateOptions(options) {
19081
+ if (options.apiKey && options.authToken) throw new Error("set either apiKey or authToken, not both");
19082
+ if (Array.isArray(options.command) && options.command.length === 0) throw new Error("Cursor ACP command must not be empty");
19083
+ if (options.decisionTimeoutMs !== void 0 && (!Number.isFinite(options.decisionTimeoutMs) || options.decisionTimeoutMs <= 0)) throw new Error("decisionTimeoutMs must be a positive finite number");
19084
+ if (options.maxPendingDecisions !== void 0 && (!Number.isInteger(options.maxPendingDecisions) || options.maxPendingDecisions <= 0)) throw new Error("maxPendingDecisions must be a positive integer");
19085
+ }
19086
+ function allowOption(options) {
19087
+ return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always");
19088
+ }
19089
+ function questionChoices(value) {
19090
+ const choices = /* @__PURE__ */ new Map();
19091
+ const multiSelect = /* @__PURE__ */ new Set();
19092
+ if (!Array.isArray(value)) return { choices, multiSelect };
19093
+ for (const question of value) {
19094
+ if (!isRecord3(question) || typeof question.id !== "string" || !Array.isArray(question.options)) continue;
19095
+ const options = question.options.filter(isRecord3).map((option) => stringValue(option.id)).filter((id) => !!id);
19096
+ if (options.length === 0) continue;
19097
+ choices.set(question.id, options);
19098
+ if (question.allowMultiple === true) multiSelect.add(question.id);
19099
+ }
19100
+ return { choices, multiSelect };
19101
+ }
19102
+ function commandResult(action, args, decision) {
19103
+ if (decision.kind === "permission") return action === "deny" ? void 0 : action === "select" && args.length === 1 && decision.choices.get("permission")?.includes(args[0] ?? "") ? args[0] : null;
19104
+ if (decision.kind === "plan") return action === "accept" ? { outcome: { outcome: "accepted" } } : action === "reject" ? { outcome: { outcome: "rejected" } } : null;
19105
+ if (action !== "answer") return null;
19106
+ const selected = {};
19107
+ for (const argument of args) {
19108
+ const [id, raw] = argument.split("=", 2);
19109
+ const values = raw?.split(",") ?? [];
19110
+ const offered = id ? decision.choices.get(id) : void 0;
19111
+ if (!id || !offered || selected[id] || values.length === 0 || values.length > 1 && !decision.multiSelect.has(id) || values.some((value) => !offered.includes(value))) return null;
19112
+ selected[id] = values;
19113
+ }
19114
+ return Object.keys(selected).length === decision.choices.size ? answered(selected) : null;
19115
+ }
19116
+ function answered(selected) {
19117
+ return { outcome: { outcome: "answered", answers: Object.entries(selected).map(([questionId, selectedOptionIds]) => ({ questionId, selectedOptionIds })) } };
19118
+ }
19119
+ function parseTodos(value) {
19120
+ if (!Array.isArray(value)) return [];
19121
+ return value.flatMap((todo) => {
19122
+ if (!isRecord3(todo)) return [];
19123
+ const id = stringValue(todo.id);
19124
+ const content = stringValue(todo.content);
19125
+ const status = stringValue(todo.status);
19126
+ return id && content && status ? [{ id, content, status }] : [];
19127
+ });
19128
+ }
19129
+ function todoMark(status) {
19130
+ switch (status) {
19131
+ case "completed":
19132
+ return "x";
19133
+ case "in_progress":
19134
+ return "~";
19135
+ case "cancelled":
19136
+ return "-";
19137
+ default:
19138
+ return " ";
19139
+ }
19140
+ }
19141
+ function isRecord3(value) {
19142
+ return !!value && typeof value === "object" && !Array.isArray(value);
19143
+ }
19144
+ function stringValue(value) {
19145
+ return typeof value === "string" && value.length > 0 ? value : void 0;
19146
+ }
19147
+
18624
19148
  // src/core/index.ts
18625
19149
  var import_band_sdk_core13 = require("@band-ai/band-sdk-core");
18626
19150
  init_protocols();
@@ -18633,6 +19157,7 @@ init_schemas();
18633
19157
  A2AAdapter,
18634
19158
  A2AGatewayAdapter,
18635
19159
  ACPClientAdapter,
19160
+ AcpSessionConfigError,
18636
19161
  Agent,
18637
19162
  AgentFailure,
18638
19163
  AgentRuntime,
@@ -18644,10 +19169,13 @@ init_schemas();
18644
19169
  ClaudeSDKAdapter,
18645
19170
  CodexAdapter,
18646
19171
  CopilotACPAdapter,
19172
+ CursorACPAdapter,
18647
19173
  DEFAULT_COPILOT_ACP_COMMAND,
19174
+ DEFAULT_CURSOR_ACP_COMMAND,
18648
19175
  DEFAULT_OMP_ACP_COMMAND,
18649
19176
  DefaultPreprocessor,
18650
19177
  DeliveryFailedError,
19178
+ FAILURE_CODE_SESSION_CONFIG,
18651
19179
  FAILURE_EVENT_TYPE,
18652
19180
  FAILURE_METADATA_KEY,
18653
19181
  GeminiAdapter,
@@ -18657,6 +19185,7 @@ init_schemas();
18657
19185
  LettaAdapter,
18658
19186
  MCP_SERVER_NAME,
18659
19187
  MCP_TOOL_PREFIX,
19188
+ MISSING_CONFIG_OPTIONS_REASON,
18660
19189
  OmpACPAdapter,
18661
19190
  OpenAIAdapter,
18662
19191
  OpencodeAdapter,
@@ -18669,6 +19198,7 @@ init_schemas();
18669
19198
  VercelAISDKAdapter,
18670
19199
  WebSocketDisconnectError,
18671
19200
  agentFailure,
19201
+ applySessionConfigSelections,
18672
19202
  deliverReply,
18673
19203
  deriveDefaultRestUrl,
18674
19204
  isDirectExecution,