@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/index.cjs CHANGED
@@ -1070,7 +1070,9 @@ __export(src_exports, {
1070
1070
  ClaudeSDKAdapter: () => ClaudeSDKAdapter,
1071
1071
  CodexAdapter: () => CodexAdapter,
1072
1072
  CopilotACPAdapter: () => CopilotACPAdapter,
1073
+ CursorACPAdapter: () => CursorACPAdapter,
1073
1074
  DEFAULT_COPILOT_ACP_COMMAND: () => DEFAULT_COPILOT_ACP_COMMAND,
1075
+ DEFAULT_CURSOR_ACP_COMMAND: () => DEFAULT_CURSOR_ACP_COMMAND,
1074
1076
  DEFAULT_OMP_ACP_COMMAND: () => DEFAULT_OMP_ACP_COMMAND,
1075
1077
  DefaultPreprocessor: () => DefaultPreprocessor,
1076
1078
  DeliveryFailedError: () => DeliveryFailedError,
@@ -17255,23 +17257,17 @@ function asAcpJsonRpcError(error) {
17255
17257
  init_chatEvents();
17256
17258
  init_schemas();
17257
17259
 
17258
- // src/adapters/acp/types.ts
17259
- function choosePermissionOption(options) {
17260
- if (options.length === 0) {
17261
- return null;
17262
- }
17263
- return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
17264
- }
17265
-
17266
17260
  // src/adapters/acp/client.ts
17267
17261
  var BandACPClient = class {
17268
17262
  sessionChunks = /* @__PURE__ */ new Map();
17269
17263
  permissionHandler;
17264
+ extensionHandler;
17270
17265
  // The handler is connection-scoped and required at construction, so it is
17271
17266
  // already in place before the agent process is spawned: there is no window
17272
17267
  // in which a `session/request_permission` has nowhere to go.
17273
- constructor(permissionHandler) {
17268
+ constructor(permissionHandler, extensionHandler) {
17274
17269
  this.permissionHandler = permissionHandler;
17270
+ this.extensionHandler = extensionHandler;
17275
17271
  }
17276
17272
  beginSession(sessionId) {
17277
17273
  this.sessionChunks.set(sessionId, []);
@@ -17316,62 +17312,26 @@ var BandACPClient = class {
17316
17312
  return chunks;
17317
17313
  }
17318
17314
  async extMethod(method, params) {
17319
- if (method === "cursor/ask_question") {
17320
- const options = Array.isArray(params.options) ? params.options : [];
17321
- const selected = choosePermissionOption(
17322
- options.filter((option) => !!option && typeof option === "object")
17323
- );
17324
- if (!selected) {
17325
- return {
17326
- outcome: {
17327
- type: "cancelled"
17328
- }
17329
- };
17330
- }
17331
- return {
17332
- outcome: {
17333
- type: "selected",
17334
- optionId: selected.optionId
17335
- }
17336
- };
17337
- }
17338
- if (method === "cursor/create_plan") {
17339
- return {
17340
- outcome: {
17341
- type: "approved"
17342
- }
17343
- };
17344
- }
17345
- return {};
17315
+ const result = await this.extensionHandler?.extMethod?.(
17316
+ method,
17317
+ params,
17318
+ { sessionId: sessionIdFrom(params) }
17319
+ );
17320
+ return result ?? {};
17346
17321
  }
17347
17322
  async extNotification(method, params) {
17348
- const sessionId = toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
17349
- if (!sessionId) {
17350
- return;
17351
- }
17352
- if (method === "cursor/update_todos") {
17353
- const todos = Array.isArray(params.todos) ? params.todos : [];
17354
- const lines = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).filter((line) => line.trim().length > 0);
17355
- if (lines.length > 0) {
17356
- this.appendChunk(sessionId, {
17357
- chunkType: "plan",
17358
- content: lines.join("\n"),
17359
- metadata: {},
17360
- streamed: false
17361
- });
17362
- }
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) {
17363
17331
  return;
17364
17332
  }
17365
- if (method === "cursor/task") {
17366
- const result = toOptionalString(params.result);
17367
- if (result) {
17368
- this.appendChunk(sessionId, {
17369
- chunkType: "text",
17370
- content: `[Task completed] ${result}`,
17371
- metadata: {},
17372
- streamed: false
17373
- });
17374
- }
17333
+ for (const chunk of chunks) {
17334
+ this.appendChunk(targetSessionId, chunk);
17375
17335
  }
17376
17336
  }
17377
17337
  appendChunk(sessionId, chunk) {
@@ -17496,6 +17456,17 @@ function extractTextFromContent(content) {
17496
17456
  function toOptionalString(value) {
17497
17457
  return typeof value === "string" && value.length > 0 ? value : null;
17498
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
+ }
17499
17470
 
17500
17471
  // src/adapters/acp/loader.ts
17501
17472
  init_errors();
@@ -17542,6 +17513,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17542
17513
  additionalMcpTools;
17543
17514
  clientCapabilities;
17544
17515
  connectionFactory;
17516
+ extensionHandler;
17545
17517
  tcpEndpoint;
17546
17518
  // The value's `generation` is the connection generation the session was
17547
17519
  // last established/restored against. `client` is the exact BandACPClient
@@ -17606,6 +17578,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17606
17578
  this.additionalMcpTools = [...options.additionalMcpTools ?? []];
17607
17579
  this.clientCapabilities = options.clientCapabilities;
17608
17580
  this.connectionFactory = options.connectionFactory;
17581
+ this.extensionHandler = options.extensionHandler;
17609
17582
  this.tcpEndpoint = tcpEndpoint;
17610
17583
  this.resolvePermission = options.resolvePermission;
17611
17584
  this.resolveSessionMode = options.resolveSessionMode;
@@ -17661,6 +17634,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17661
17634
  let client = null;
17662
17635
  let sessionId;
17663
17636
  let generation = 0;
17637
+ await this.onAcpTurnStarted(message, tools, context);
17664
17638
  try {
17665
17639
  const ensured = await this.ensureConnection();
17666
17640
  connection = ensured.connection;
@@ -17671,6 +17645,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
17671
17645
  }
17672
17646
  sessionId = await this.getOrCreateSession(context.roomId, connection, generation, client);
17673
17647
  const sessionKey = this.sessionKey(generation, sessionId);
17648
+ await this.onAcpSessionReady(message, tools, context, sessionId);
17674
17649
  client.beginSession(sessionId);
17675
17650
  const content = replaceUuidMentions(message.content, mentionSubjectsFromMetadata(message.metadata));
17676
17651
  const messageWithContext = [...systemUpdateParts(participantsMessage, contactsMessage), content].join("\n\n");
@@ -17741,8 +17716,16 @@ ${messageWithContext}`;
17741
17716
  ...configError ? { optionId: configError.optionId, selectedValue: configError.selectedValue } : {}
17742
17717
  }
17743
17718
  );
17719
+ } finally {
17720
+ await this.onAcpTurnFinished(message, tools, context);
17744
17721
  }
17745
17722
  }
17723
+ async onAcpTurnStarted(_message, _tools, _context) {
17724
+ }
17725
+ async onAcpTurnFinished(_message, _tools, _context) {
17726
+ }
17727
+ async onAcpSessionReady(_message, _tools, _context, _sessionId) {
17728
+ }
17746
17729
  // Best-effort: tells the agent to stop working on a turn Band has already
17747
17730
  // given up waiting for (the ACP client has no way to force it), evicts the
17748
17731
  // session so the room's next turn re-establishes rather than reuses it
@@ -17912,6 +17895,9 @@ ${messageWithContext}`;
17912
17895
  sessionKey(generation, sessionId) {
17913
17896
  return `${generation}:${sessionId}`;
17914
17897
  }
17898
+ roomIdForSession(sessionId) {
17899
+ return [...this.roomToSession.entries()].find(([, owner]) => owner.sessionId === sessionId)?.[0];
17900
+ }
17915
17901
  async ensureConnection() {
17916
17902
  if (this.connection && !this.connection.signal.aborted) {
17917
17903
  return { connection: this.connection, generation: this.connectionGeneration };
@@ -17948,7 +17934,10 @@ ${messageWithContext}`;
17948
17934
  throw new Error(CONNECTION_ATTEMPT_SUPERSEDED_ERROR);
17949
17935
  }
17950
17936
  const owner = { generation: -1 };
17951
- 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
+ );
17952
17941
  handle = await (this.connectionFactory ? this.connectionFactory(client, {
17953
17942
  command: this.command,
17954
17943
  cwd: this.cwd,
@@ -18804,6 +18793,358 @@ var CopilotACPAdapter = class extends ACPClientAdapter {
18804
18793
  }
18805
18794
  };
18806
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
+
18807
19148
  // src/core/index.ts
18808
19149
  var import_band_sdk_core13 = require("@band-ai/band-sdk-core");
18809
19150
  init_protocols();
@@ -18828,7 +19169,9 @@ init_schemas();
18828
19169
  ClaudeSDKAdapter,
18829
19170
  CodexAdapter,
18830
19171
  CopilotACPAdapter,
19172
+ CursorACPAdapter,
18831
19173
  DEFAULT_COPILOT_ACP_COMMAND,
19174
+ DEFAULT_CURSOR_ACP_COMMAND,
18832
19175
  DEFAULT_OMP_ACP_COMMAND,
18833
19176
  DefaultPreprocessor,
18834
19177
  DeliveryFailedError,
package/dist/index.d.cts CHANGED
@@ -9,7 +9,7 @@ import { P as PlatformMessage } from './types-DtcOLALn.cjs';
9
9
  export { A as AgentConfig, a as AgentInput, C as ContactEvent, b as ContactEventCallback, c as ContactEventConfig, d as ContactEventStrategy, e as ConversationContext, H as HistoryProvider, M as MessageHandler, f as PlatformEvent, R as ReconnectedEvent, S as SessionConfig } from './types-DtcOLALn.cjs';
10
10
  export { W as WebSocketConflictPolicy, a as WebSocketDisconnectError, b as WebSocketDisconnectReason } from './disconnectReason-Cctmg1SN.cjs';
11
11
  export { C as CustomToolDef } from './customTools-Bfecd0mJ.cjs';
12
- export { A as A2AAdapter, a as A2AAdapterOptions, b as A2AGatewayAdapter, c as ACPClientAdapter, d as ACPClientAdapterBaseOptions, e as ACPClientAdapterOptions, f as ACPClientStdioOptions, g as ACPClientTcpOptions, h as ACPConfigRequest, i as ACPConfigSelections, j as AcpSessionConfigError, k as AnthropicAdapter, l as AnthropicAdapterOptions, C as CODEX_REASONING_EFFORTS, m as CODEX_REASONING_SUMMARIES, n as CODEX_WEB_SEARCH_MODES, o as ClaudePermissionMode, p as ClaudeSDKAdapter, q as ClaudeSDKAdapterOptions, r as CodexAdapter, s as CodexAdapterConfig, t as CodexApprovalPolicy, u as CodexReasoningEffort, v as CodexReasoningSummary, w as CodexSandboxMode, x as CodexWebSearchMode, y as CopilotACPAdapter, z as CopilotACPAdapterOptions, B as CopilotACPStdioOptions, D as CopilotACPTcpOptions, E as DEFAULT_COPILOT_ACP_COMMAND, F as DEFAULT_OMP_ACP_COMMAND, G as FAILURE_CODE_SESSION_CONFIG, H as GeminiAdapter, I as GeminiAdapterOptions, J as GenericAdapter, K as GenericAdapterHandler, L as GoogleADKAdapter, M as GoogleADKAdapterOptions, N as LangGraphAdapter, O as LangGraphAdapterOptions, P as LangGraphGraph, Q as LettaAdapter, R as LettaAdapterOptions, S as MISSING_CONFIG_OPTIONS_REASON, T as OmpACPAdapter, U as OmpACPAdapterOptions, V as OpenAIAdapter, W as OpenAIAdapterOptions, X as OpencodeAdapter, Y as OpencodeAdapterConfig, Z as OpencodeApprovalMode, _ as OpencodeApprovalReply, $ as OpencodeQuestionMode, a0 as ParlantAdapter, a1 as ParlantAdapterOptions, a2 as ToolCallingModel, a3 as VercelAISDKAdapter, a4 as VercelAISDKAdapterOptions, a5 as applySessionConfigSelections } from './CopilotACPAdapter-HRvat7CP.cjs';
12
+ export { A as A2AAdapter, a as A2AAdapterOptions, b as A2AGatewayAdapter, c as ACPClientAdapter, d as ACPClientAdapterBaseOptions, e as ACPClientAdapterOptions, f as ACPClientStdioOptions, g as ACPClientTcpOptions, h as ACPConfigRequest, i as ACPConfigSelections, j as AcpSessionConfigError, k as AnthropicAdapter, l as AnthropicAdapterOptions, C as CODEX_REASONING_EFFORTS, m as CODEX_REASONING_SUMMARIES, n as CODEX_WEB_SEARCH_MODES, o as ClaudePermissionMode, p as ClaudeSDKAdapter, q as ClaudeSDKAdapterOptions, r as CodexAdapter, s as CodexAdapterConfig, t as CodexApprovalPolicy, u as CodexReasoningEffort, v as CodexReasoningSummary, w as CodexSandboxMode, x as CodexWebSearchMode, y as CopilotACPAdapter, z as CopilotACPAdapterOptions, B as CopilotACPStdioOptions, D as CopilotACPTcpOptions, E as CursorACPAdapter, F as CursorACPAdapterOptions, G as CursorApprovalMode, H as CursorPlanMode, I as CursorQuestionMode, J as DEFAULT_COPILOT_ACP_COMMAND, K as DEFAULT_CURSOR_ACP_COMMAND, L as DEFAULT_OMP_ACP_COMMAND, M as FAILURE_CODE_SESSION_CONFIG, N as GeminiAdapter, O as GeminiAdapterOptions, P as GenericAdapter, Q as GenericAdapterHandler, R as GoogleADKAdapter, S as GoogleADKAdapterOptions, T as LangGraphAdapter, U as LangGraphAdapterOptions, V as LangGraphGraph, W as LettaAdapter, X as LettaAdapterOptions, Y as MISSING_CONFIG_OPTIONS_REASON, Z as OmpACPAdapter, _ as OmpACPAdapterOptions, $ as OpenAIAdapter, a0 as OpenAIAdapterOptions, a1 as OpencodeAdapter, a2 as OpencodeAdapterConfig, a3 as OpencodeApprovalMode, a4 as OpencodeApprovalReply, a5 as OpencodeQuestionMode, a6 as ParlantAdapter, a7 as ParlantAdapterOptions, a8 as ToolCallingModel, a9 as VercelAISDKAdapter, aa as VercelAISDKAdapterOptions, ab as applySessionConfigSelections } from './CursorACPAdapter-_YhrXRWq.cjs';
13
13
  export { S as SimpleAdapter } from './simpleAdapter-D75rcz9n.cjs';
14
14
  export { A as A2AAuth, a as A2AGatewayAdapterOptions } from './acp-client-D-I_5lK-.cjs';
15
15
  export { AgentFailure } from '@band-ai/band-sdk-core';
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ import { P as PlatformMessage } from './types-CKU1N0SK.js';
9
9
  export { A as AgentConfig, a as AgentInput, C as ContactEvent, b as ContactEventCallback, c as ContactEventConfig, d as ContactEventStrategy, e as ConversationContext, H as HistoryProvider, M as MessageHandler, f as PlatformEvent, R as ReconnectedEvent, S as SessionConfig } from './types-CKU1N0SK.js';
10
10
  export { W as WebSocketConflictPolicy, a as WebSocketDisconnectError, b as WebSocketDisconnectReason } from './disconnectReason-Cctmg1SN.js';
11
11
  export { C as CustomToolDef } from './customTools-Bfecd0mJ.js';
12
- export { A as A2AAdapter, a as A2AAdapterOptions, b as A2AGatewayAdapter, c as ACPClientAdapter, d as ACPClientAdapterBaseOptions, e as ACPClientAdapterOptions, f as ACPClientStdioOptions, g as ACPClientTcpOptions, h as ACPConfigRequest, i as ACPConfigSelections, j as AcpSessionConfigError, k as AnthropicAdapter, l as AnthropicAdapterOptions, C as CODEX_REASONING_EFFORTS, m as CODEX_REASONING_SUMMARIES, n as CODEX_WEB_SEARCH_MODES, o as ClaudePermissionMode, p as ClaudeSDKAdapter, q as ClaudeSDKAdapterOptions, r as CodexAdapter, s as CodexAdapterConfig, t as CodexApprovalPolicy, u as CodexReasoningEffort, v as CodexReasoningSummary, w as CodexSandboxMode, x as CodexWebSearchMode, y as CopilotACPAdapter, z as CopilotACPAdapterOptions, B as CopilotACPStdioOptions, D as CopilotACPTcpOptions, E as DEFAULT_COPILOT_ACP_COMMAND, F as DEFAULT_OMP_ACP_COMMAND, G as FAILURE_CODE_SESSION_CONFIG, H as GeminiAdapter, I as GeminiAdapterOptions, J as GenericAdapter, K as GenericAdapterHandler, L as GoogleADKAdapter, M as GoogleADKAdapterOptions, N as LangGraphAdapter, O as LangGraphAdapterOptions, P as LangGraphGraph, Q as LettaAdapter, R as LettaAdapterOptions, S as MISSING_CONFIG_OPTIONS_REASON, T as OmpACPAdapter, U as OmpACPAdapterOptions, V as OpenAIAdapter, W as OpenAIAdapterOptions, X as OpencodeAdapter, Y as OpencodeAdapterConfig, Z as OpencodeApprovalMode, _ as OpencodeApprovalReply, $ as OpencodeQuestionMode, a0 as ParlantAdapter, a1 as ParlantAdapterOptions, a2 as ToolCallingModel, a3 as VercelAISDKAdapter, a4 as VercelAISDKAdapterOptions, a5 as applySessionConfigSelections } from './CopilotACPAdapter-Bj6NRdqd.js';
12
+ export { A as A2AAdapter, a as A2AAdapterOptions, b as A2AGatewayAdapter, c as ACPClientAdapter, d as ACPClientAdapterBaseOptions, e as ACPClientAdapterOptions, f as ACPClientStdioOptions, g as ACPClientTcpOptions, h as ACPConfigRequest, i as ACPConfigSelections, j as AcpSessionConfigError, k as AnthropicAdapter, l as AnthropicAdapterOptions, C as CODEX_REASONING_EFFORTS, m as CODEX_REASONING_SUMMARIES, n as CODEX_WEB_SEARCH_MODES, o as ClaudePermissionMode, p as ClaudeSDKAdapter, q as ClaudeSDKAdapterOptions, r as CodexAdapter, s as CodexAdapterConfig, t as CodexApprovalPolicy, u as CodexReasoningEffort, v as CodexReasoningSummary, w as CodexSandboxMode, x as CodexWebSearchMode, y as CopilotACPAdapter, z as CopilotACPAdapterOptions, B as CopilotACPStdioOptions, D as CopilotACPTcpOptions, E as CursorACPAdapter, F as CursorACPAdapterOptions, G as CursorApprovalMode, H as CursorPlanMode, I as CursorQuestionMode, J as DEFAULT_COPILOT_ACP_COMMAND, K as DEFAULT_CURSOR_ACP_COMMAND, L as DEFAULT_OMP_ACP_COMMAND, M as FAILURE_CODE_SESSION_CONFIG, N as GeminiAdapter, O as GeminiAdapterOptions, P as GenericAdapter, Q as GenericAdapterHandler, R as GoogleADKAdapter, S as GoogleADKAdapterOptions, T as LangGraphAdapter, U as LangGraphAdapterOptions, V as LangGraphGraph, W as LettaAdapter, X as LettaAdapterOptions, Y as MISSING_CONFIG_OPTIONS_REASON, Z as OmpACPAdapter, _ as OmpACPAdapterOptions, $ as OpenAIAdapter, a0 as OpenAIAdapterOptions, a1 as OpencodeAdapter, a2 as OpencodeAdapterConfig, a3 as OpencodeApprovalMode, a4 as OpencodeApprovalReply, a5 as OpencodeQuestionMode, a6 as ParlantAdapter, a7 as ParlantAdapterOptions, a8 as ToolCallingModel, a9 as VercelAISDKAdapter, aa as VercelAISDKAdapterOptions, ab as applySessionConfigSelections } from './CursorACPAdapter-7-Jtl1LV.js';
13
13
  export { S as SimpleAdapter } from './simpleAdapter-eLvsAQNo.js';
14
14
  export { A as A2AAuth, a as A2AGatewayAdapterOptions } from './acp-client-DUXyczlF.js';
15
15
  export { AgentFailure } from '@band-ai/band-sdk-core';
package/dist/index.js CHANGED
@@ -8,7 +8,9 @@ import {
8
8
  ClaudeSDKAdapter,
9
9
  CodexAdapter,
10
10
  CopilotACPAdapter,
11
+ CursorACPAdapter,
11
12
  DEFAULT_COPILOT_ACP_COMMAND,
13
+ DEFAULT_CURSOR_ACP_COMMAND,
12
14
  DEFAULT_OMP_ACP_COMMAND,
13
15
  FAILURE_CODE_SESSION_CONFIG,
14
16
  GeminiAdapter,
@@ -22,7 +24,7 @@ import {
22
24
  OpencodeAdapter,
23
25
  VercelAISDKAdapter,
24
26
  applySessionConfigSelections
25
- } from "./chunk-KXLV645N.js";
27
+ } from "./chunk-VHYPQAQ5.js";
26
28
  import "./chunk-UL3Y5C4J.js";
27
29
  import "./chunk-JDW5WSGF.js";
28
30
  import {
@@ -283,7 +285,9 @@ export {
283
285
  ClaudeSDKAdapter,
284
286
  CodexAdapter,
285
287
  CopilotACPAdapter,
288
+ CursorACPAdapter,
286
289
  DEFAULT_COPILOT_ACP_COMMAND,
290
+ DEFAULT_CURSOR_ACP_COMMAND,
287
291
  DEFAULT_OMP_ACP_COMMAND,
288
292
  DefaultPreprocessor,
289
293
  DeliveryFailedError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@band-ai/sdk",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Band TypeScript SDK core runtime",
5
5
  "license": "MIT",
6
6
  "repository": {