@band-ai/sdk 0.4.2 → 0.4.4

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
@@ -990,7 +990,9 @@ __export(adapters_exports, {
990
990
  CodexAppServerStdioClient: () => CodexAppServerStdioClient,
991
991
  CodexJsonRpcError: () => CodexJsonRpcError,
992
992
  CopilotACPAdapter: () => CopilotACPAdapter,
993
+ CursorACPAdapter: () => CursorACPAdapter,
993
994
  DEFAULT_COPILOT_ACP_COMMAND: () => DEFAULT_COPILOT_ACP_COMMAND,
995
+ DEFAULT_CURSOR_ACP_COMMAND: () => DEFAULT_CURSOR_ACP_COMMAND,
994
996
  DEFAULT_OMP_ACP_COMMAND: () => DEFAULT_OMP_ACP_COMMAND,
995
997
  FAILURE_CODE_SESSION_CONFIG: () => FAILURE_CODE_SESSION_CONFIG,
996
998
  GatewayHistoryConverter: () => GatewayHistoryConverter,
@@ -1696,15 +1698,20 @@ function systemUpdateParts(participantsMessage, contactsMessage) {
1696
1698
  }
1697
1699
  function buildConversationPrompt(options) {
1698
1700
  const parts = [];
1699
- if (options.isSessionBootstrap && options.history.length > 0) {
1700
- const historyText = options.history.raw.slice(-(options.maxHistoryMessages ?? 50)).map(formatHistoryLine).join("\n");
1701
- parts.push(`${options.historyHeader}
1701
+ if (options.isSessionBootstrap) {
1702
+ const historyText = options.history.raw.slice(-(options.maxHistoryMessages ?? 50)).filter(isTextHistoryEntry).map(formatHistoryLine).join("\n");
1703
+ if (historyText) {
1704
+ parts.push(`${options.historyHeader}
1702
1705
  ${historyText}`);
1706
+ }
1703
1707
  }
1704
1708
  parts.push(...systemUpdateParts(options.participantsMessage, options.contactsMessage));
1705
1709
  parts.push(options.currentMessage);
1706
1710
  return parts.join("\n\n");
1707
1711
  }
1712
+ function isTextHistoryEntry(entry) {
1713
+ return entry.message_type === void 0 || entry.message_type === "text";
1714
+ }
1708
1715
  function formatHistoryLine(entry) {
1709
1716
  const sender = String(entry.sender_name ?? entry.sender_type ?? "Unknown");
1710
1717
  const content = String(entry.content ?? "");
@@ -2338,74 +2345,17 @@ function checkPort2(http, port) {
2338
2345
  });
2339
2346
  }
2340
2347
 
2341
- // src/adapters/acp/types.ts
2342
- var DEFAULT_ACP_SERVER_MODES = [
2343
- {
2344
- id: "default",
2345
- name: "Default",
2346
- description: "General-purpose chat mode"
2347
- },
2348
- {
2349
- id: "code",
2350
- name: "Code",
2351
- description: "Route prompts toward coding peers when available"
2352
- }
2353
- ];
2354
- function createPendingPrompt(sessionId) {
2355
- let markDone = () => void 0;
2356
- const done = new Promise((resolve) => {
2357
- markDone = resolve;
2358
- });
2359
- return {
2360
- sessionId,
2361
- done,
2362
- markDone,
2363
- terminalMessageSeen: false,
2364
- completionTimer: null
2365
- };
2366
- }
2367
- function choosePermissionOption(options) {
2368
- if (options.length === 0) {
2369
- return null;
2370
- }
2371
- return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
2372
- }
2373
- function asJsonSafe(value) {
2374
- if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2375
- return value;
2376
- }
2377
- if (Array.isArray(value)) {
2378
- return value.map((item) => asJsonSafe(item));
2379
- }
2380
- if (typeof value === "object") {
2381
- if ("model_dump" in value && typeof value.model_dump === "function") {
2382
- return asJsonSafe(value.model_dump());
2383
- }
2384
- if ("toJSON" in value && typeof value.toJSON === "function") {
2385
- return asJsonSafe(value.toJSON());
2386
- }
2387
- return Object.fromEntries(
2388
- Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
2389
- );
2390
- }
2391
- return String(value);
2392
- }
2393
- function normalizeMcpServers(mcpServers) {
2394
- if (!mcpServers) {
2395
- return [];
2396
- }
2397
- return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
2398
- }
2399
-
2400
2348
  // src/adapters/acp/client.ts
2401
2349
  var BandACPClient = class {
2402
2350
  sessionChunks = /* @__PURE__ */ new Map();
2403
2351
  permissionHandler;
2352
+ extensionHandler;
2404
2353
  // The handler is connection-scoped and required at construction, so it is
2405
2354
  // already in place before the agent process is spawned: there is no window
2406
2355
  // in which a `session/request_permission` has nowhere to go.
2407
- constructor(permissionHandler) {
2356
+ constructor(permissionHandler, extensionHandler) {
2408
2357
  this.permissionHandler = permissionHandler;
2358
+ this.extensionHandler = extensionHandler;
2409
2359
  }
2410
2360
  beginSession(sessionId) {
2411
2361
  this.sessionChunks.set(sessionId, []);
@@ -2450,62 +2400,26 @@ var BandACPClient = class {
2450
2400
  return chunks;
2451
2401
  }
2452
2402
  async extMethod(method, params) {
2453
- if (method === "cursor/ask_question") {
2454
- const options = Array.isArray(params.options) ? params.options : [];
2455
- const selected = choosePermissionOption(
2456
- options.filter((option) => !!option && typeof option === "object")
2457
- );
2458
- if (!selected) {
2459
- return {
2460
- outcome: {
2461
- type: "cancelled"
2462
- }
2463
- };
2464
- }
2465
- return {
2466
- outcome: {
2467
- type: "selected",
2468
- optionId: selected.optionId
2469
- }
2470
- };
2471
- }
2472
- if (method === "cursor/create_plan") {
2473
- return {
2474
- outcome: {
2475
- type: "approved"
2476
- }
2477
- };
2478
- }
2479
- return {};
2403
+ const result = await this.extensionHandler?.extMethod?.(
2404
+ method,
2405
+ params,
2406
+ { sessionId: sessionIdFrom(params) }
2407
+ );
2408
+ return result ?? {};
2480
2409
  }
2481
2410
  async extNotification(method, params) {
2482
- const sessionId = toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
2483
- if (!sessionId) {
2411
+ const sessionId = sessionIdFrom(params);
2412
+ const chunks = await this.extensionHandler?.extNotification?.(
2413
+ method,
2414
+ params,
2415
+ { sessionId }
2416
+ );
2417
+ const targetSessionId = sessionId ?? this.extensionHandler?.extensionSessionId?.() ?? null;
2418
+ if (!targetSessionId || !chunks) {
2484
2419
  return;
2485
2420
  }
2486
- if (method === "cursor/update_todos") {
2487
- const todos = Array.isArray(params.todos) ? params.todos : [];
2488
- const lines = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).filter((line) => line.trim().length > 0);
2489
- if (lines.length > 0) {
2490
- this.appendChunk(sessionId, {
2491
- chunkType: "plan",
2492
- content: lines.join("\n"),
2493
- metadata: {},
2494
- streamed: false
2495
- });
2496
- }
2497
- return;
2498
- }
2499
- if (method === "cursor/task") {
2500
- const result = toOptionalString(params.result);
2501
- if (result) {
2502
- this.appendChunk(sessionId, {
2503
- chunkType: "text",
2504
- content: `[Task completed] ${result}`,
2505
- metadata: {},
2506
- streamed: false
2507
- });
2508
- }
2421
+ for (const chunk of chunks) {
2422
+ this.appendChunk(targetSessionId, chunk);
2509
2423
  }
2510
2424
  }
2511
2425
  appendChunk(sessionId, chunk) {
@@ -2630,6 +2544,68 @@ function extractTextFromContent(content) {
2630
2544
  function toOptionalString(value) {
2631
2545
  return typeof value === "string" && value.length > 0 ? value : null;
2632
2546
  }
2547
+ function sessionIdFrom(params) {
2548
+ return toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
2549
+ }
2550
+
2551
+ // src/adapters/acp/types.ts
2552
+ var DEFAULT_ACP_SERVER_MODES = [
2553
+ {
2554
+ id: "default",
2555
+ name: "Default",
2556
+ description: "General-purpose chat mode"
2557
+ },
2558
+ {
2559
+ id: "code",
2560
+ name: "Code",
2561
+ description: "Route prompts toward coding peers when available"
2562
+ }
2563
+ ];
2564
+ function createPendingPrompt(sessionId) {
2565
+ let markDone = () => void 0;
2566
+ const done = new Promise((resolve) => {
2567
+ markDone = resolve;
2568
+ });
2569
+ return {
2570
+ sessionId,
2571
+ done,
2572
+ markDone,
2573
+ terminalMessageSeen: false,
2574
+ completionTimer: null
2575
+ };
2576
+ }
2577
+ function choosePermissionOption(options) {
2578
+ if (options.length === 0) {
2579
+ return null;
2580
+ }
2581
+ return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
2582
+ }
2583
+ function asJsonSafe(value) {
2584
+ if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2585
+ return value;
2586
+ }
2587
+ if (Array.isArray(value)) {
2588
+ return value.map((item) => asJsonSafe(item));
2589
+ }
2590
+ if (typeof value === "object") {
2591
+ if ("model_dump" in value && typeof value.model_dump === "function") {
2592
+ return asJsonSafe(value.model_dump());
2593
+ }
2594
+ if ("toJSON" in value && typeof value.toJSON === "function") {
2595
+ return asJsonSafe(value.toJSON());
2596
+ }
2597
+ return Object.fromEntries(
2598
+ Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
2599
+ );
2600
+ }
2601
+ return String(value);
2602
+ }
2603
+ function normalizeMcpServers(mcpServers) {
2604
+ if (!mcpServers) {
2605
+ return [];
2606
+ }
2607
+ return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
2608
+ }
2633
2609
 
2634
2610
  // src/adapters/acp/loader.ts
2635
2611
  init_errors();
@@ -2725,6 +2701,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2725
2701
  additionalMcpTools;
2726
2702
  clientCapabilities;
2727
2703
  connectionFactory;
2704
+ extensionHandler;
2728
2705
  tcpEndpoint;
2729
2706
  // The value's `generation` is the connection generation the session was
2730
2707
  // last established/restored against. `client` is the exact BandACPClient
@@ -2789,6 +2766,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2789
2766
  this.additionalMcpTools = [...options.additionalMcpTools ?? []];
2790
2767
  this.clientCapabilities = options.clientCapabilities;
2791
2768
  this.connectionFactory = options.connectionFactory;
2769
+ this.extensionHandler = options.extensionHandler;
2792
2770
  this.tcpEndpoint = tcpEndpoint;
2793
2771
  this.resolvePermission = options.resolvePermission;
2794
2772
  this.resolveSessionMode = options.resolveSessionMode;
@@ -2844,6 +2822,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2844
2822
  let client = null;
2845
2823
  let sessionId;
2846
2824
  let generation = 0;
2825
+ await this.onAcpTurnStarted(message, tools, context);
2847
2826
  try {
2848
2827
  const ensured = await this.ensureConnection();
2849
2828
  connection = ensured.connection;
@@ -2854,6 +2833,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2854
2833
  }
2855
2834
  sessionId = await this.getOrCreateSession(context.roomId, connection, generation, client);
2856
2835
  const sessionKey = this.sessionKey(generation, sessionId);
2836
+ await this.onAcpSessionReady(message, tools, context, sessionId);
2857
2837
  client.beginSession(sessionId);
2858
2838
  const content = replaceUuidMentions(message.content, mentionSubjectsFromMetadata(message.metadata));
2859
2839
  const messageWithContext = [...systemUpdateParts(participantsMessage, contactsMessage), content].join("\n\n");
@@ -2924,8 +2904,16 @@ ${messageWithContext}`;
2924
2904
  ...configError ? { optionId: configError.optionId, selectedValue: configError.selectedValue } : {}
2925
2905
  }
2926
2906
  );
2907
+ } finally {
2908
+ await this.onAcpTurnFinished(message, tools, context);
2927
2909
  }
2928
2910
  }
2911
+ async onAcpTurnStarted(_message, _tools, _context) {
2912
+ }
2913
+ async onAcpTurnFinished(_message, _tools, _context) {
2914
+ }
2915
+ async onAcpSessionReady(_message, _tools, _context, _sessionId) {
2916
+ }
2929
2917
  // Best-effort: tells the agent to stop working on a turn Band has already
2930
2918
  // given up waiting for (the ACP client has no way to force it), evicts the
2931
2919
  // session so the room's next turn re-establishes rather than reuses it
@@ -3095,6 +3083,9 @@ ${messageWithContext}`;
3095
3083
  sessionKey(generation, sessionId) {
3096
3084
  return `${generation}:${sessionId}`;
3097
3085
  }
3086
+ roomIdForSession(sessionId) {
3087
+ return [...this.roomToSession.entries()].find(([, owner]) => owner.sessionId === sessionId)?.[0];
3088
+ }
3098
3089
  async ensureConnection() {
3099
3090
  if (this.connection && !this.connection.signal.aborted) {
3100
3091
  return { connection: this.connection, generation: this.connectionGeneration };
@@ -3131,7 +3122,10 @@ ${messageWithContext}`;
3131
3122
  throw new Error(CONNECTION_ATTEMPT_SUPERSEDED_ERROR);
3132
3123
  }
3133
3124
  const owner = { generation: -1 };
3134
- const client = new BandACPClient((params) => this.routePermissionRequest(params, owner.generation));
3125
+ const client = new BandACPClient(
3126
+ (params) => this.routePermissionRequest(params, owner.generation),
3127
+ this.extensionHandler
3128
+ );
3135
3129
  handle = await (this.connectionFactory ? this.connectionFactory(client, {
3136
3130
  command: this.command,
3137
3131
  cwd: this.cwd,
@@ -4856,6 +4850,358 @@ var CopilotACPAdapter = class extends ACPClientAdapter {
4856
4850
  }
4857
4851
  };
4858
4852
 
4853
+ // src/adapters/cursor-acp/CursorACPAdapter.ts
4854
+ var DEFAULT_CURSOR_ACP_COMMAND = ["agent", "acp"];
4855
+ var DEFAULT_CURSOR_DECISION_TIMEOUT_MS = 3e5;
4856
+ var DEFAULT_CURSOR_MAX_PENDING_DECISIONS = 10;
4857
+ var CursorExtensions = class {
4858
+ adapter = null;
4859
+ todosBySession = /* @__PURE__ */ new Map();
4860
+ bind(adapter) {
4861
+ this.adapter = adapter;
4862
+ }
4863
+ async resolvePermission(request, signal) {
4864
+ return this.adapter?.resolveCursorPermission(request, signal);
4865
+ }
4866
+ extensionSessionId() {
4867
+ return this.adapter?.extensionSessionId() ?? null;
4868
+ }
4869
+ async extMethod(method, params, context) {
4870
+ return this.adapter?.resolveExtension(method, params, context.sessionId) ?? null;
4871
+ }
4872
+ async extNotification(method, params, context) {
4873
+ const sessionId = context.sessionId ?? this.extensionSessionId();
4874
+ if (!sessionId) {
4875
+ return;
4876
+ }
4877
+ if (method === "cursor/update_todos") {
4878
+ const content = this.updateTodos(sessionId, params);
4879
+ if (!content) {
4880
+ return;
4881
+ }
4882
+ return [{ chunkType: "plan", content, metadata: { cursor_todos: true }, streamed: false }];
4883
+ }
4884
+ if (method === "cursor/task") {
4885
+ const description = stringValue(params.description);
4886
+ if (!description) {
4887
+ return [];
4888
+ }
4889
+ const subagentType = stringValue(params.subagentType) ?? "unspecified";
4890
+ const model = stringValue(params.model);
4891
+ const suffix = model ? ` (${model})` : "";
4892
+ return [{ chunkType: "plan", content: `[Cursor ${subagentType} task] ${description}${suffix}`, metadata: {}, streamed: false }];
4893
+ }
4894
+ if (method === "cursor/generate_image") {
4895
+ const description = stringValue(params.description);
4896
+ if (!description) {
4897
+ return [];
4898
+ }
4899
+ const filePath = stringValue(params.filePath);
4900
+ return [{ chunkType: "plan", content: `[Cursor generated image] ${description}${filePath ? ` \u2192 ${filePath}` : ""}`, metadata: {}, streamed: false }];
4901
+ }
4902
+ }
4903
+ forgetSession(sessionId) {
4904
+ this.todosBySession.delete(sessionId);
4905
+ }
4906
+ clearSessions() {
4907
+ this.todosBySession.clear();
4908
+ }
4909
+ updateTodos(sessionId, params) {
4910
+ const todos = parseTodos(params.todos);
4911
+ if (params.merge === true) {
4912
+ const current2 = this.todosBySession.get(sessionId) ?? /* @__PURE__ */ new Map();
4913
+ for (const todo of todos) {
4914
+ current2.set(todo.id, todo);
4915
+ }
4916
+ this.todosBySession.set(sessionId, current2);
4917
+ } else {
4918
+ this.todosBySession.set(sessionId, new Map(todos.map((todo) => [todo.id, todo])));
4919
+ }
4920
+ const current = this.todosBySession.get(sessionId);
4921
+ return current && current.size > 0 ? [...current.values()].map((todo) => `- [${todoMark(todo.status)}] ${todo.content}`).join("\n") : void 0;
4922
+ }
4923
+ };
4924
+ var CursorACPAdapter = class extends ACPClientAdapter {
4925
+ provider = "cursor-acp";
4926
+ approvalMode;
4927
+ questionMode;
4928
+ planMode;
4929
+ decisionTimeoutMs;
4930
+ maxPendingDecisions;
4931
+ authorizedSenders;
4932
+ decisionLogger;
4933
+ extensions;
4934
+ turns = /* @__PURE__ */ new Map();
4935
+ pending = /* @__PURE__ */ new Map();
4936
+ activeTurn = null;
4937
+ turnTail = Promise.resolve();
4938
+ constructor(options = {}) {
4939
+ const extensions = new CursorExtensions();
4940
+ validateOptions(options);
4941
+ const env = cursorEnv(options);
4942
+ super({
4943
+ ...options,
4944
+ env,
4945
+ command: options.command ?? [...DEFAULT_CURSOR_ACP_COMMAND],
4946
+ authMethod: "cursor_login",
4947
+ extensionHandler: extensions,
4948
+ resolvePermission: (request, signal) => extensions.resolvePermission(request, signal)
4949
+ });
4950
+ extensions.bind(this);
4951
+ this.extensions = extensions;
4952
+ this.approvalMode = options.approvalMode ?? "manual";
4953
+ this.questionMode = options.questionMode ?? "manual";
4954
+ this.planMode = options.planMode ?? "manual";
4955
+ this.decisionTimeoutMs = options.decisionTimeoutMs ?? DEFAULT_CURSOR_DECISION_TIMEOUT_MS;
4956
+ this.maxPendingDecisions = options.maxPendingDecisions ?? DEFAULT_CURSOR_MAX_PENDING_DECISIONS;
4957
+ this.authorizedSenders = options.decisionAuthorizedSenders ? new Set(options.decisionAuthorizedSenders) : null;
4958
+ this.decisionLogger = resolveLogger(options.logger);
4959
+ }
4960
+ async onMessage(message, tools, history, participantsMessage, contactsMessage, context) {
4961
+ if (await this.handleControl(message, tools, context.roomId)) {
4962
+ return;
4963
+ }
4964
+ await this.withCursorTurnLock(() => super.onMessage(message, tools, history, participantsMessage, contactsMessage, context));
4965
+ }
4966
+ async onAcpTurnStarted(message, tools, context) {
4967
+ const turn = { messageId: message.id, roomId: context.roomId, tools, requesterId: message.senderId };
4968
+ this.turns.set(context.roomId, turn);
4969
+ this.activeTurn = turn;
4970
+ }
4971
+ async onAcpSessionReady(message, _tools, context, sessionId) {
4972
+ const turn = this.turns.get(context.roomId);
4973
+ if (turn?.messageId === message.id) {
4974
+ turn.sessionId = sessionId;
4975
+ }
4976
+ }
4977
+ async onAcpTurnFinished(message, _tools, context) {
4978
+ const turn = this.turns.get(context.roomId);
4979
+ if (turn?.messageId === message.id) {
4980
+ this.turns.delete(context.roomId);
4981
+ this.cancelRoom(context.roomId);
4982
+ }
4983
+ if (this.activeTurn?.messageId === message.id) {
4984
+ this.activeTurn = null;
4985
+ }
4986
+ }
4987
+ async onCleanup(roomId) {
4988
+ const sessionId = this.turns.get(roomId)?.sessionId;
4989
+ this.cancelRoom(roomId);
4990
+ this.turns.delete(roomId);
4991
+ if (this.activeTurn?.roomId === roomId) {
4992
+ this.activeTurn = null;
4993
+ }
4994
+ await super.onCleanup(roomId);
4995
+ if (sessionId) {
4996
+ this.extensions.forgetSession(sessionId);
4997
+ }
4998
+ }
4999
+ async stop() {
5000
+ for (const decision of this.pending.values()) {
5001
+ decision.resolve(void 0);
5002
+ }
5003
+ this.pending.clear();
5004
+ this.turns.clear();
5005
+ this.activeTurn = null;
5006
+ this.extensions.clearSessions();
5007
+ await super.stop();
5008
+ }
5009
+ async resolveExtension(method, params, sessionId) {
5010
+ const roomId = sessionId ? this.roomIdForSession(sessionId) : this.activeTurn?.roomId;
5011
+ const turn = roomId ? this.turns.get(roomId) : void 0;
5012
+ if (!turn || sessionId && turn.sessionId !== sessionId) {
5013
+ return { outcome: { outcome: "cancelled" } };
5014
+ }
5015
+ if (method === "cursor/ask_question") {
5016
+ return this.resolveQuestion(turn.roomId, turn, params);
5017
+ }
5018
+ if (method === "cursor/create_plan") {
5019
+ return this.resolvePlan(turn.roomId, turn, params);
5020
+ }
5021
+ return {};
5022
+ }
5023
+ async resolveCursorPermission(request, signal) {
5024
+ if (this.approvalMode === "autoAccept") {
5025
+ return allowOption(request.options)?.optionId;
5026
+ }
5027
+ if (this.approvalMode === "autoDecline") {
5028
+ return void 0;
5029
+ }
5030
+ const turn = this.turns.get(request.roomId);
5031
+ if (!turn) {
5032
+ return void 0;
5033
+ }
5034
+ const options = request.options.map((option) => option.optionId);
5035
+ 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);
5036
+ return typeof token === "string" && options.includes(token) ? token : void 0;
5037
+ }
5038
+ extensionSessionId() {
5039
+ return this.activeTurn?.sessionId ?? null;
5040
+ }
5041
+ async resolveQuestion(roomId, turn, params) {
5042
+ const questions = questionChoices(params.questions);
5043
+ if (questions.choices.size === 0) {
5044
+ return { outcome: { outcome: "cancelled" } };
5045
+ }
5046
+ if (this.questionMode === "autoCancel") {
5047
+ return { outcome: { outcome: "cancelled" } };
5048
+ }
5049
+ if (this.questionMode === "autoFirst") {
5050
+ return answered(Object.fromEntries([...questions.choices].map(([id, options]) => [id, [options[0]]])));
5051
+ }
5052
+ 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] ...`.");
5053
+ return isRecord(result) ? result : { outcome: { outcome: "cancelled" } };
5054
+ }
5055
+ async resolvePlan(roomId, turn, params) {
5056
+ if (this.planMode === "autoAccept") {
5057
+ return { outcome: { outcome: "accepted" } };
5058
+ }
5059
+ if (this.planMode === "autoDecline") {
5060
+ return { outcome: { outcome: "rejected" } };
5061
+ }
5062
+ const title = stringValue(params.title) ?? "Cursor plan";
5063
+ 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}\`.`);
5064
+ return isRecord(result) ? result : { outcome: { outcome: "cancelled" } };
5065
+ }
5066
+ async waitForDecision(kind, roomId, turn, choices, multiSelect, prompt, signal) {
5067
+ if (this.pending.size >= this.maxPendingDecisions) {
5068
+ this.pending.values().next().value?.resolve(void 0);
5069
+ }
5070
+ const token = crypto.randomUUID().slice(0, 8);
5071
+ return new Promise((resolve) => {
5072
+ const timer = setTimeout(() => settle(void 0), this.decisionTimeoutMs);
5073
+ const abort = () => settle(void 0);
5074
+ const settle = (value) => {
5075
+ clearTimeout(timer);
5076
+ signal?.removeEventListener("abort", abort);
5077
+ this.pending.delete(token);
5078
+ resolve(value);
5079
+ };
5080
+ this.pending.set(token, { kind, roomId, choices, multiSelect, resolve: settle });
5081
+ signal?.addEventListener("abort", abort, { once: true });
5082
+ void turn.tools.sendMessage(prompt.replaceAll("{token}", token), [turn.requesterId]).catch((error) => {
5083
+ this.decisionLogger.warn("cursor_acp.decision_prompt_delivery_failed", { roomId, kind, error: String(error) });
5084
+ settle(void 0);
5085
+ });
5086
+ });
5087
+ }
5088
+ async handleControl(message, tools, roomId) {
5089
+ const words = message.content.trim().split(/\s+/);
5090
+ if (words[0]?.toLowerCase() !== "/cursor") {
5091
+ return false;
5092
+ }
5093
+ if (words.length === 1 || words[1]?.toLowerCase() === "decisions") {
5094
+ const entries = [...this.pending.entries()].filter(([, decision2]) => decision2.roomId === roomId).map(([token2, decision2]) => `\`${token2}\` (${decision2.kind})`);
5095
+ await tools.sendMessage(`Pending Cursor decisions: ${entries.join(", ") || "none"}`);
5096
+ return true;
5097
+ }
5098
+ const [_, action, token, ...args] = words;
5099
+ const decision = token ? this.pending.get(token) : void 0;
5100
+ if (!decision || decision.roomId !== roomId) {
5101
+ await tools.sendMessage(`Cursor decision \`${token ?? ""}\` is not pending.`);
5102
+ return true;
5103
+ }
5104
+ if (this.authorizedSenders && !this.authorizedSenders.has(message.senderId)) {
5105
+ await tools.sendMessage("You are not authorized to resolve Cursor decisions.");
5106
+ return true;
5107
+ }
5108
+ const result = commandResult(action ?? "", args, decision);
5109
+ if (result === null) {
5110
+ await tools.sendMessage(`That command is not valid for Cursor ${decision.kind} decision \`${token}\`.`);
5111
+ return true;
5112
+ }
5113
+ decision.resolve(result);
5114
+ await tools.sendMessage(`Cursor ${decision.kind} decision \`${token}\` resolved.`);
5115
+ return true;
5116
+ }
5117
+ cancelRoom(roomId) {
5118
+ for (const [token, decision] of this.pending) {
5119
+ if (decision.roomId === roomId) {
5120
+ decision.resolve(void 0);
5121
+ this.pending.delete(token);
5122
+ }
5123
+ }
5124
+ }
5125
+ async withCursorTurnLock(run) {
5126
+ const queued = this.turnTail.then(run, run);
5127
+ this.turnTail = queued.then(() => void 0, () => void 0);
5128
+ return queued;
5129
+ }
5130
+ };
5131
+ function cursorEnv(options) {
5132
+ const env = { ...options.env };
5133
+ if (options.apiKey) env.CURSOR_API_KEY ??= options.apiKey;
5134
+ if (options.authToken) env.CURSOR_AUTH_TOKEN ??= options.authToken;
5135
+ return Object.keys(env).length > 0 ? env : void 0;
5136
+ }
5137
+ function validateOptions(options) {
5138
+ if (options.apiKey && options.authToken) throw new Error("set either apiKey or authToken, not both");
5139
+ if (Array.isArray(options.command) && options.command.length === 0) throw new Error("Cursor ACP command must not be empty");
5140
+ if (options.decisionTimeoutMs !== void 0 && (!Number.isFinite(options.decisionTimeoutMs) || options.decisionTimeoutMs <= 0)) throw new Error("decisionTimeoutMs must be a positive finite number");
5141
+ if (options.maxPendingDecisions !== void 0 && (!Number.isInteger(options.maxPendingDecisions) || options.maxPendingDecisions <= 0)) throw new Error("maxPendingDecisions must be a positive integer");
5142
+ }
5143
+ function allowOption(options) {
5144
+ return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always");
5145
+ }
5146
+ function questionChoices(value) {
5147
+ const choices = /* @__PURE__ */ new Map();
5148
+ const multiSelect = /* @__PURE__ */ new Set();
5149
+ if (!Array.isArray(value)) return { choices, multiSelect };
5150
+ for (const question of value) {
5151
+ if (!isRecord(question) || typeof question.id !== "string" || !Array.isArray(question.options)) continue;
5152
+ const options = question.options.filter(isRecord).map((option) => stringValue(option.id)).filter((id) => !!id);
5153
+ if (options.length === 0) continue;
5154
+ choices.set(question.id, options);
5155
+ if (question.allowMultiple === true) multiSelect.add(question.id);
5156
+ }
5157
+ return { choices, multiSelect };
5158
+ }
5159
+ function commandResult(action, args, decision) {
5160
+ if (decision.kind === "permission") return action === "deny" ? void 0 : action === "select" && args.length === 1 && decision.choices.get("permission")?.includes(args[0] ?? "") ? args[0] : null;
5161
+ if (decision.kind === "plan") return action === "accept" ? { outcome: { outcome: "accepted" } } : action === "reject" ? { outcome: { outcome: "rejected" } } : null;
5162
+ if (action !== "answer") return null;
5163
+ const selected = {};
5164
+ for (const argument of args) {
5165
+ const [id, raw] = argument.split("=", 2);
5166
+ const values = raw?.split(",") ?? [];
5167
+ const offered = id ? decision.choices.get(id) : void 0;
5168
+ if (!id || !offered || selected[id] || values.length === 0 || values.length > 1 && !decision.multiSelect.has(id) || values.some((value) => !offered.includes(value))) return null;
5169
+ selected[id] = values;
5170
+ }
5171
+ return Object.keys(selected).length === decision.choices.size ? answered(selected) : null;
5172
+ }
5173
+ function answered(selected) {
5174
+ return { outcome: { outcome: "answered", answers: Object.entries(selected).map(([questionId, selectedOptionIds]) => ({ questionId, selectedOptionIds })) } };
5175
+ }
5176
+ function parseTodos(value) {
5177
+ if (!Array.isArray(value)) return [];
5178
+ return value.flatMap((todo) => {
5179
+ if (!isRecord(todo)) return [];
5180
+ const id = stringValue(todo.id);
5181
+ const content = stringValue(todo.content);
5182
+ const status = stringValue(todo.status);
5183
+ return id && content && status ? [{ id, content, status }] : [];
5184
+ });
5185
+ }
5186
+ function todoMark(status) {
5187
+ switch (status) {
5188
+ case "completed":
5189
+ return "x";
5190
+ case "in_progress":
5191
+ return "~";
5192
+ case "cancelled":
5193
+ return "-";
5194
+ default:
5195
+ return " ";
5196
+ }
5197
+ }
5198
+ function isRecord(value) {
5199
+ return !!value && typeof value === "object" && !Array.isArray(value);
5200
+ }
5201
+ function stringValue(value) {
5202
+ return typeof value === "string" && value.length > 0 ? value : void 0;
5203
+ }
5204
+
4859
5205
  // src/adapters/tool-calling/ToolCallingAdapter.ts
4860
5206
  init_protocols();
4861
5207
 
@@ -7283,20 +7629,20 @@ function unwrapResult(value, depth = 0) {
7283
7629
  }
7284
7630
  return event;
7285
7631
  }
7286
- function isRecord(value) {
7632
+ function isRecord2(value) {
7287
7633
  return typeof value === "object" && value !== null;
7288
7634
  }
7289
7635
  function isOptionalString(value) {
7290
7636
  return value === void 0 || typeof value === "string";
7291
7637
  }
7292
7638
  function isMessagePart(value) {
7293
- if (!isRecord(value)) {
7639
+ if (!isRecord2(value)) {
7294
7640
  return false;
7295
7641
  }
7296
7642
  return isOptionalString(value.kind) && isOptionalString(value.type) && isOptionalString(value.text) && (value.root === void 0 || isMessagePart(value.root));
7297
7643
  }
7298
7644
  function isMessageLike(value) {
7299
- if (!isRecord(value)) {
7645
+ if (!isRecord2(value)) {
7300
7646
  return false;
7301
7647
  }
7302
7648
  if (!isOptionalString(value.kind) || !isOptionalString(value.role)) {
@@ -7311,7 +7657,7 @@ function isMessageLike(value) {
7311
7657
  return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
7312
7658
  }
7313
7659
  function isStatusLike(value) {
7314
- if (!isRecord(value)) {
7660
+ if (!isRecord2(value)) {
7315
7661
  return false;
7316
7662
  }
7317
7663
  if (!isOptionalString(value.state)) {
@@ -7320,7 +7666,7 @@ function isStatusLike(value) {
7320
7666
  return value.message === void 0 || isMessageLike(value.message);
7321
7667
  }
7322
7668
  function isArtifactLike(value) {
7323
- if (!isRecord(value)) {
7669
+ if (!isRecord2(value)) {
7324
7670
  return false;
7325
7671
  }
7326
7672
  if (value.parts === void 0) {
@@ -7329,19 +7675,19 @@ function isArtifactLike(value) {
7329
7675
  return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
7330
7676
  }
7331
7677
  function isMessageEvent(event) {
7332
- return isRecord(event) && event.kind === "message" && isMessageLike(event) && Array.isArray(event.parts);
7678
+ return isRecord2(event) && event.kind === "message" && isMessageLike(event) && Array.isArray(event.parts);
7333
7679
  }
7334
7680
  function isTaskEvent(event) {
7335
- if (!isRecord(event) || event.kind !== "task") {
7681
+ if (!isRecord2(event) || event.kind !== "task") {
7336
7682
  return false;
7337
7683
  }
7338
7684
  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)));
7339
7685
  }
7340
7686
  function isStatusUpdateEvent(event) {
7341
- return isRecord(event) && event.kind === "status-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isStatusLike(event.status);
7687
+ return isRecord2(event) && event.kind === "status-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isStatusLike(event.status);
7342
7688
  }
7343
7689
  function isArtifactUpdateEvent(event) {
7344
- return isRecord(event) && event.kind === "artifact-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isArtifactLike(event.artifact);
7690
+ return isRecord2(event) && event.kind === "artifact-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isArtifactLike(event.artifact);
7345
7691
  }
7346
7692
  async function loadDefaultA2AClientFactory() {
7347
7693
  let module2;
@@ -9981,7 +10327,7 @@ var SdkOpencodeClientBase = class {
9981
10327
  { signal: this.eventsAbortController.signal }
9982
10328
  );
9983
10329
  for await (const event of events.stream) {
9984
- if (isRecord2(event)) {
10330
+ if (isRecord3(event)) {
9985
10331
  yield event;
9986
10332
  }
9987
10333
  }
@@ -10099,7 +10445,7 @@ function optionalString(value) {
10099
10445
  const trimmed = value.trim();
10100
10446
  return trimmed.length > 0 ? trimmed : null;
10101
10447
  }
10102
- function isRecord2(value) {
10448
+ function isRecord3(value) {
10103
10449
  return !!value && typeof value === "object" && !Array.isArray(value);
10104
10450
  }
10105
10451
  function getResultBody(result) {
@@ -10109,7 +10455,7 @@ function expectRecord(result) {
10109
10455
  if (!result.response.ok) {
10110
10456
  throw new HttpStatusError(result.response.status, getResultBody(result));
10111
10457
  }
10112
- return isRecord2(result.data) ? result.data : {};
10458
+ return isRecord3(result.data) ? result.data : {};
10113
10459
  }
10114
10460
  async function expectVoid(resultPromise) {
10115
10461
  const result = await resultPromise;
@@ -13057,7 +13403,9 @@ function parseModelListResponse(value) {
13057
13403
  CodexAppServerStdioClient,
13058
13404
  CodexJsonRpcError,
13059
13405
  CopilotACPAdapter,
13406
+ CursorACPAdapter,
13060
13407
  DEFAULT_COPILOT_ACP_COMMAND,
13408
+ DEFAULT_CURSOR_ACP_COMMAND,
13061
13409
  DEFAULT_OMP_ACP_COMMAND,
13062
13410
  FAILURE_CODE_SESSION_CONFIG,
13063
13411
  GatewayHistoryConverter,