@band-ai/sdk 0.4.2 → 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
@@ -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,
@@ -2338,74 +2340,17 @@ function checkPort2(http, port) {
2338
2340
  });
2339
2341
  }
2340
2342
 
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
2343
  // src/adapters/acp/client.ts
2401
2344
  var BandACPClient = class {
2402
2345
  sessionChunks = /* @__PURE__ */ new Map();
2403
2346
  permissionHandler;
2347
+ extensionHandler;
2404
2348
  // The handler is connection-scoped and required at construction, so it is
2405
2349
  // already in place before the agent process is spawned: there is no window
2406
2350
  // in which a `session/request_permission` has nowhere to go.
2407
- constructor(permissionHandler) {
2351
+ constructor(permissionHandler, extensionHandler) {
2408
2352
  this.permissionHandler = permissionHandler;
2353
+ this.extensionHandler = extensionHandler;
2409
2354
  }
2410
2355
  beginSession(sessionId) {
2411
2356
  this.sessionChunks.set(sessionId, []);
@@ -2450,62 +2395,26 @@ var BandACPClient = class {
2450
2395
  return chunks;
2451
2396
  }
2452
2397
  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 {};
2398
+ const result = await this.extensionHandler?.extMethod?.(
2399
+ method,
2400
+ params,
2401
+ { sessionId: sessionIdFrom(params) }
2402
+ );
2403
+ return result ?? {};
2480
2404
  }
2481
2405
  async extNotification(method, params) {
2482
- const sessionId = toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
2483
- if (!sessionId) {
2484
- return;
2485
- }
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
- }
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) {
2497
2414
  return;
2498
2415
  }
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
- }
2416
+ for (const chunk of chunks) {
2417
+ this.appendChunk(targetSessionId, chunk);
2509
2418
  }
2510
2419
  }
2511
2420
  appendChunk(sessionId, chunk) {
@@ -2630,6 +2539,68 @@ function extractTextFromContent(content) {
2630
2539
  function toOptionalString(value) {
2631
2540
  return typeof value === "string" && value.length > 0 ? value : null;
2632
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
+ }
2633
2604
 
2634
2605
  // src/adapters/acp/loader.ts
2635
2606
  init_errors();
@@ -2725,6 +2696,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2725
2696
  additionalMcpTools;
2726
2697
  clientCapabilities;
2727
2698
  connectionFactory;
2699
+ extensionHandler;
2728
2700
  tcpEndpoint;
2729
2701
  // The value's `generation` is the connection generation the session was
2730
2702
  // last established/restored against. `client` is the exact BandACPClient
@@ -2789,6 +2761,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2789
2761
  this.additionalMcpTools = [...options.additionalMcpTools ?? []];
2790
2762
  this.clientCapabilities = options.clientCapabilities;
2791
2763
  this.connectionFactory = options.connectionFactory;
2764
+ this.extensionHandler = options.extensionHandler;
2792
2765
  this.tcpEndpoint = tcpEndpoint;
2793
2766
  this.resolvePermission = options.resolvePermission;
2794
2767
  this.resolveSessionMode = options.resolveSessionMode;
@@ -2844,6 +2817,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2844
2817
  let client = null;
2845
2818
  let sessionId;
2846
2819
  let generation = 0;
2820
+ await this.onAcpTurnStarted(message, tools, context);
2847
2821
  try {
2848
2822
  const ensured = await this.ensureConnection();
2849
2823
  connection = ensured.connection;
@@ -2854,6 +2828,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
2854
2828
  }
2855
2829
  sessionId = await this.getOrCreateSession(context.roomId, connection, generation, client);
2856
2830
  const sessionKey = this.sessionKey(generation, sessionId);
2831
+ await this.onAcpSessionReady(message, tools, context, sessionId);
2857
2832
  client.beginSession(sessionId);
2858
2833
  const content = replaceUuidMentions(message.content, mentionSubjectsFromMetadata(message.metadata));
2859
2834
  const messageWithContext = [...systemUpdateParts(participantsMessage, contactsMessage), content].join("\n\n");
@@ -2924,8 +2899,16 @@ ${messageWithContext}`;
2924
2899
  ...configError ? { optionId: configError.optionId, selectedValue: configError.selectedValue } : {}
2925
2900
  }
2926
2901
  );
2902
+ } finally {
2903
+ await this.onAcpTurnFinished(message, tools, context);
2927
2904
  }
2928
2905
  }
2906
+ async onAcpTurnStarted(_message, _tools, _context) {
2907
+ }
2908
+ async onAcpTurnFinished(_message, _tools, _context) {
2909
+ }
2910
+ async onAcpSessionReady(_message, _tools, _context, _sessionId) {
2911
+ }
2929
2912
  // Best-effort: tells the agent to stop working on a turn Band has already
2930
2913
  // given up waiting for (the ACP client has no way to force it), evicts the
2931
2914
  // session so the room's next turn re-establishes rather than reuses it
@@ -3095,6 +3078,9 @@ ${messageWithContext}`;
3095
3078
  sessionKey(generation, sessionId) {
3096
3079
  return `${generation}:${sessionId}`;
3097
3080
  }
3081
+ roomIdForSession(sessionId) {
3082
+ return [...this.roomToSession.entries()].find(([, owner]) => owner.sessionId === sessionId)?.[0];
3083
+ }
3098
3084
  async ensureConnection() {
3099
3085
  if (this.connection && !this.connection.signal.aborted) {
3100
3086
  return { connection: this.connection, generation: this.connectionGeneration };
@@ -3131,7 +3117,10 @@ ${messageWithContext}`;
3131
3117
  throw new Error(CONNECTION_ATTEMPT_SUPERSEDED_ERROR);
3132
3118
  }
3133
3119
  const owner = { generation: -1 };
3134
- 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
+ );
3135
3124
  handle = await (this.connectionFactory ? this.connectionFactory(client, {
3136
3125
  command: this.command,
3137
3126
  cwd: this.cwd,
@@ -4856,6 +4845,358 @@ var CopilotACPAdapter = class extends ACPClientAdapter {
4856
4845
  }
4857
4846
  };
4858
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
+
4859
5200
  // src/adapters/tool-calling/ToolCallingAdapter.ts
4860
5201
  init_protocols();
4861
5202
 
@@ -7283,20 +7624,20 @@ function unwrapResult(value, depth = 0) {
7283
7624
  }
7284
7625
  return event;
7285
7626
  }
7286
- function isRecord(value) {
7627
+ function isRecord2(value) {
7287
7628
  return typeof value === "object" && value !== null;
7288
7629
  }
7289
7630
  function isOptionalString(value) {
7290
7631
  return value === void 0 || typeof value === "string";
7291
7632
  }
7292
7633
  function isMessagePart(value) {
7293
- if (!isRecord(value)) {
7634
+ if (!isRecord2(value)) {
7294
7635
  return false;
7295
7636
  }
7296
7637
  return isOptionalString(value.kind) && isOptionalString(value.type) && isOptionalString(value.text) && (value.root === void 0 || isMessagePart(value.root));
7297
7638
  }
7298
7639
  function isMessageLike(value) {
7299
- if (!isRecord(value)) {
7640
+ if (!isRecord2(value)) {
7300
7641
  return false;
7301
7642
  }
7302
7643
  if (!isOptionalString(value.kind) || !isOptionalString(value.role)) {
@@ -7311,7 +7652,7 @@ function isMessageLike(value) {
7311
7652
  return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
7312
7653
  }
7313
7654
  function isStatusLike(value) {
7314
- if (!isRecord(value)) {
7655
+ if (!isRecord2(value)) {
7315
7656
  return false;
7316
7657
  }
7317
7658
  if (!isOptionalString(value.state)) {
@@ -7320,7 +7661,7 @@ function isStatusLike(value) {
7320
7661
  return value.message === void 0 || isMessageLike(value.message);
7321
7662
  }
7322
7663
  function isArtifactLike(value) {
7323
- if (!isRecord(value)) {
7664
+ if (!isRecord2(value)) {
7324
7665
  return false;
7325
7666
  }
7326
7667
  if (value.parts === void 0) {
@@ -7329,19 +7670,19 @@ function isArtifactLike(value) {
7329
7670
  return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
7330
7671
  }
7331
7672
  function isMessageEvent(event) {
7332
- 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);
7333
7674
  }
7334
7675
  function isTaskEvent(event) {
7335
- if (!isRecord(event) || event.kind !== "task") {
7676
+ if (!isRecord2(event) || event.kind !== "task") {
7336
7677
  return false;
7337
7678
  }
7338
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)));
7339
7680
  }
7340
7681
  function isStatusUpdateEvent(event) {
7341
- 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);
7342
7683
  }
7343
7684
  function isArtifactUpdateEvent(event) {
7344
- 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);
7345
7686
  }
7346
7687
  async function loadDefaultA2AClientFactory() {
7347
7688
  let module2;
@@ -9981,7 +10322,7 @@ var SdkOpencodeClientBase = class {
9981
10322
  { signal: this.eventsAbortController.signal }
9982
10323
  );
9983
10324
  for await (const event of events.stream) {
9984
- if (isRecord2(event)) {
10325
+ if (isRecord3(event)) {
9985
10326
  yield event;
9986
10327
  }
9987
10328
  }
@@ -10099,7 +10440,7 @@ function optionalString(value) {
10099
10440
  const trimmed = value.trim();
10100
10441
  return trimmed.length > 0 ? trimmed : null;
10101
10442
  }
10102
- function isRecord2(value) {
10443
+ function isRecord3(value) {
10103
10444
  return !!value && typeof value === "object" && !Array.isArray(value);
10104
10445
  }
10105
10446
  function getResultBody(result) {
@@ -10109,7 +10450,7 @@ function expectRecord(result) {
10109
10450
  if (!result.response.ok) {
10110
10451
  throw new HttpStatusError(result.response.status, getResultBody(result));
10111
10452
  }
10112
- return isRecord2(result.data) ? result.data : {};
10453
+ return isRecord3(result.data) ? result.data : {};
10113
10454
  }
10114
10455
  async function expectVoid(resultPromise) {
10115
10456
  const result = await resultPromise;
@@ -13057,7 +13398,9 @@ function parseModelListResponse(value) {
13057
13398
  CodexAppServerStdioClient,
13058
13399
  CodexJsonRpcError,
13059
13400
  CopilotACPAdapter,
13401
+ CursorACPAdapter,
13060
13402
  DEFAULT_COPILOT_ACP_COMMAND,
13403
+ DEFAULT_CURSOR_ACP_COMMAND,
13061
13404
  DEFAULT_OMP_ACP_COMMAND,
13062
13405
  FAILURE_CODE_SESSION_CONFIG,
13063
13406
  GatewayHistoryConverter,