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