@agent-link/agent 0.1.334 → 0.1.335
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/backends/types.d.ts +0 -11
- package/dist/backends/types.js +3 -10
- package/dist/backends/types.js.map +1 -1
- package/dist/connection.js +10 -91
- package/dist/connection.js.map +1 -1
- package/dist/copilot.d.ts +1 -45
- package/dist/copilot.js +14 -733
- package/dist/copilot.js.map +1 -1
- package/dist/provider-command-discovery.d.ts +0 -5
- package/dist/provider-command-discovery.js +1 -41
- package/dist/provider-command-discovery.js.map +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -3
- package/dist/copilot-command-bridge.d.ts +0 -102
- package/dist/copilot-command-bridge.js +0 -654
- package/dist/copilot-command-bridge.js.map +0 -1
|
@@ -1,654 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
3
|
-
import { existsSync, readFileSync } from 'fs';
|
|
4
|
-
import { dirname, join } from 'path';
|
|
5
|
-
import { platform } from 'os';
|
|
6
|
-
import { Readable, Writable } from 'stream';
|
|
7
|
-
import * as acp from '@agentclientprotocol/sdk';
|
|
8
|
-
import { createMessageConnection, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node.js';
|
|
9
|
-
import { getProvider, resolveProviderCommand } from './providers.js';
|
|
10
|
-
import { getCleanEnv, resolveExecutablePath } from './sdk.js';
|
|
11
|
-
const SDK_START_TIMEOUT_MS = 30_000;
|
|
12
|
-
const SDK_REQUEST_TIMEOUT_MS = 30_000;
|
|
13
|
-
const SDK_STOP_TIMEOUT_MS = 5_000;
|
|
14
|
-
const ACP_SESSION_TIMEOUT_MS = 90_000;
|
|
15
|
-
const ACP_PROMPT_TIMEOUT_MS = 15 * 60_000;
|
|
16
|
-
const SUPPORTED_COMMANDS = ['compact', 'plan', 'goal'];
|
|
17
|
-
const sessionLocks = new Map();
|
|
18
|
-
function withTimeout(promise, timeoutMs, label) {
|
|
19
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
20
|
-
const timer = setTimeout(() => rejectPromise(new Error(`${label} timed out`)), timeoutMs);
|
|
21
|
-
promise.then(value => {
|
|
22
|
-
clearTimeout(timer);
|
|
23
|
-
resolvePromise(value);
|
|
24
|
-
}, error => {
|
|
25
|
-
clearTimeout(timer);
|
|
26
|
-
rejectPromise(error);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
function resolveDirectExecution(command) {
|
|
31
|
-
if (platform() !== 'win32' || !command.toLowerCase().endsWith('.cmd'))
|
|
32
|
-
return null;
|
|
33
|
-
const baseDir = dirname(command);
|
|
34
|
-
const loader = join(baseDir, 'node_modules', '@github', 'copilot', 'npm-loader.js');
|
|
35
|
-
if (existsSync(loader))
|
|
36
|
-
return { command: process.execPath, prefixArgs: [loader] };
|
|
37
|
-
try {
|
|
38
|
-
const match = readFileSync(command, 'utf8').match(/"%~dp0\\([^"\r\n]+\.js)"/);
|
|
39
|
-
if (match) {
|
|
40
|
-
const script = join(baseDir, match[1]);
|
|
41
|
-
if (existsSync(script))
|
|
42
|
-
return { command: process.execPath, prefixArgs: [script] };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// Fall through to cmd.exe below.
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
export function resolveCopilotCommandSpawn(providerId, workDir) {
|
|
51
|
-
const provider = getProvider(providerId);
|
|
52
|
-
const isDefaultCopilot = providerId === 'copilot'
|
|
53
|
-
&& provider.command === 'copilot'
|
|
54
|
-
&& !provider.args?.length
|
|
55
|
-
&& !provider.shell;
|
|
56
|
-
const resolved = isDefaultCopilot
|
|
57
|
-
? { command: resolveExecutablePath('copilot') || 'copilot', prefixArgs: [], spawnOpts: {} }
|
|
58
|
-
: resolveProviderCommand(provider);
|
|
59
|
-
const runtimeArgs = providerId === 'agency-copilot'
|
|
60
|
-
? [...resolved.prefixArgs, '--acp']
|
|
61
|
-
: [...resolved.prefixArgs, '--headless', '--no-auto-update', '--stdio'];
|
|
62
|
-
const direct = resolveDirectExecution(resolved.command);
|
|
63
|
-
if (direct) {
|
|
64
|
-
return {
|
|
65
|
-
command: direct.command,
|
|
66
|
-
args: [...direct.prefixArgs, ...runtimeArgs],
|
|
67
|
-
options: {
|
|
68
|
-
cwd: provider.cwd || workDir,
|
|
69
|
-
env: { ...getCleanEnv(), ...(provider.env || {}) },
|
|
70
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
71
|
-
windowsHide: true,
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
if (platform() === 'win32' && resolved.command.toLowerCase().endsWith('.cmd')) {
|
|
76
|
-
return {
|
|
77
|
-
command: process.env.COMSPEC || 'cmd.exe',
|
|
78
|
-
args: ['/d', '/s', '/c', resolved.command, ...runtimeArgs],
|
|
79
|
-
options: {
|
|
80
|
-
cwd: provider.cwd || workDir,
|
|
81
|
-
env: { ...getCleanEnv(), ...(provider.env || {}) },
|
|
82
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
|
-
windowsHide: true,
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
command: resolved.command,
|
|
89
|
-
args: runtimeArgs,
|
|
90
|
-
options: {
|
|
91
|
-
cwd: provider.cwd || workDir,
|
|
92
|
-
env: { ...getCleanEnv(), ...(provider.env || {}) },
|
|
93
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
94
|
-
windowsHide: true,
|
|
95
|
-
...resolved.spawnOpts,
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
export class CopilotCommandRuntime {
|
|
100
|
-
options;
|
|
101
|
-
child = null;
|
|
102
|
-
connection = null;
|
|
103
|
-
acpConnection = null;
|
|
104
|
-
acpSessions = new Map();
|
|
105
|
-
acpCommandWaiters = new Map();
|
|
106
|
-
acpToolNames = new Map();
|
|
107
|
-
stderr = '';
|
|
108
|
-
releaseSessionLock = null;
|
|
109
|
-
constructor(options) {
|
|
110
|
-
this.options = options;
|
|
111
|
-
}
|
|
112
|
-
async start() {
|
|
113
|
-
if (this.connection || this.acpConnection)
|
|
114
|
-
return;
|
|
115
|
-
const spec = resolveCopilotCommandSpawn(this.options.providerId, this.options.workDir);
|
|
116
|
-
const child = spawn(spec.command, spec.args, spec.options);
|
|
117
|
-
this.child = child;
|
|
118
|
-
child.stderr?.on('data', chunk => {
|
|
119
|
-
if (this.stderr.length < 16_384)
|
|
120
|
-
this.stderr += String(chunk);
|
|
121
|
-
});
|
|
122
|
-
if (!child.stdout || !child.stdin) {
|
|
123
|
-
throw new Error('Copilot SDK runtime did not expose stdio pipes');
|
|
124
|
-
}
|
|
125
|
-
if (this.usesAgencyAcp()) {
|
|
126
|
-
const client = acp.client({ name: 'agentlink' })
|
|
127
|
-
.onNotification(acp.methods.client.session.update, ({ params }) => {
|
|
128
|
-
this.handleAcpSessionUpdate(params);
|
|
129
|
-
})
|
|
130
|
-
.onRequest(acp.methods.client.session.requestPermission, ({ params }) => {
|
|
131
|
-
const selected = params.options.find(option => option.kind === 'allow_always')
|
|
132
|
-
|| params.options.find(option => option.kind === 'allow_once');
|
|
133
|
-
return selected
|
|
134
|
-
? { outcome: { outcome: 'selected', optionId: selected.optionId } }
|
|
135
|
-
: { outcome: { outcome: 'cancelled' } };
|
|
136
|
-
})
|
|
137
|
-
.onRequest(acp.methods.client.elicitation.create, ({ params }) => {
|
|
138
|
-
if (!this.options.onAcpElicitationRequest) {
|
|
139
|
-
return { action: 'cancel' };
|
|
140
|
-
}
|
|
141
|
-
return this.options.onAcpElicitationRequest(params);
|
|
142
|
-
});
|
|
143
|
-
const stream = acp.ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
144
|
-
const connection = client.connect(stream);
|
|
145
|
-
this.acpConnection = connection;
|
|
146
|
-
try {
|
|
147
|
-
await withTimeout(connection.agent.request(acp.methods.agent.initialize, {
|
|
148
|
-
protocolVersion: acp.PROTOCOL_VERSION,
|
|
149
|
-
clientCapabilities: {
|
|
150
|
-
session: { compaction: {} },
|
|
151
|
-
...(this.options.onAcpElicitationRequest
|
|
152
|
-
? { elicitation: { form: {} } }
|
|
153
|
-
: {}),
|
|
154
|
-
},
|
|
155
|
-
clientInfo: { name: 'agentlink', version: '1' },
|
|
156
|
-
}), SDK_START_TIMEOUT_MS, 'Agency Copilot ACP startup');
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
const detail = this.stderr.trim().split(/\r?\n/).slice(-4).join('\n');
|
|
160
|
-
await this.stop();
|
|
161
|
-
throw new Error(`${error.message}${detail ? `\n${detail}` : ''}`);
|
|
162
|
-
}
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const connection = createMessageConnection(new StreamMessageReader(child.stdout), new StreamMessageWriter(child.stdin));
|
|
166
|
-
this.connection = connection;
|
|
167
|
-
connection.onNotification('session.event', notification => {
|
|
168
|
-
if (!notification || typeof notification !== 'object')
|
|
169
|
-
return;
|
|
170
|
-
const value = notification;
|
|
171
|
-
if (typeof value.sessionId === 'string'
|
|
172
|
-
&& value.event
|
|
173
|
-
&& typeof value.event === 'object') {
|
|
174
|
-
this.options.onSessionEvent?.(value.sessionId, value.event);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
connection.onRequest('userInput.request', async (params) => {
|
|
178
|
-
if (!this.options.onUserInputRequest || !isRecord(params)) {
|
|
179
|
-
throw new Error('Copilot user input handler is unavailable');
|
|
180
|
-
}
|
|
181
|
-
const sessionId = typeof params.sessionId === 'string' ? params.sessionId : '';
|
|
182
|
-
const question = typeof params.question === 'string' ? params.question : '';
|
|
183
|
-
if (!sessionId || !question)
|
|
184
|
-
throw new Error('Invalid Copilot user input request');
|
|
185
|
-
return this.options.onUserInputRequest(sessionId, {
|
|
186
|
-
question,
|
|
187
|
-
choices: Array.isArray(params.choices)
|
|
188
|
-
? params.choices.filter((value) => typeof value === 'string')
|
|
189
|
-
: undefined,
|
|
190
|
-
allowFreeform: typeof params.allowFreeform === 'boolean'
|
|
191
|
-
? params.allowFreeform
|
|
192
|
-
: undefined,
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
connection.onRequest('exitPlanMode.request', async (params) => {
|
|
196
|
-
if (!this.options.onExitPlanModeRequest || !isRecord(params)) {
|
|
197
|
-
throw new Error('Copilot Plan decision handler is unavailable');
|
|
198
|
-
}
|
|
199
|
-
const sessionId = typeof params.sessionId === 'string' ? params.sessionId : '';
|
|
200
|
-
const summary = typeof params.summary === 'string' ? params.summary : '';
|
|
201
|
-
const actions = Array.isArray(params.actions)
|
|
202
|
-
? params.actions.filter((value) => typeof value === 'string')
|
|
203
|
-
: [];
|
|
204
|
-
const recommendedAction = typeof params.recommendedAction === 'string'
|
|
205
|
-
? params.recommendedAction
|
|
206
|
-
: actions[0] || '';
|
|
207
|
-
if (!sessionId || !summary)
|
|
208
|
-
throw new Error('Invalid Copilot Plan decision request');
|
|
209
|
-
return this.options.onExitPlanModeRequest(sessionId, {
|
|
210
|
-
summary,
|
|
211
|
-
planContent: typeof params.planContent === 'string' ? params.planContent : undefined,
|
|
212
|
-
actions,
|
|
213
|
-
recommendedAction,
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
connection.listen();
|
|
217
|
-
try {
|
|
218
|
-
await withTimeout(connection.sendRequest('connect', {}), SDK_START_TIMEOUT_MS, 'Copilot SDK startup');
|
|
219
|
-
}
|
|
220
|
-
catch (error) {
|
|
221
|
-
const detail = this.stderr.trim().split(/\r?\n/).slice(-4).join('\n');
|
|
222
|
-
await this.stop();
|
|
223
|
-
throw new Error(`${error.message}${detail ? `\n${detail}` : ''}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
async request(method, params = {}, timeoutMs = SDK_REQUEST_TIMEOUT_MS) {
|
|
227
|
-
if (!this.connection)
|
|
228
|
-
throw new Error('Copilot SDK runtime is not started');
|
|
229
|
-
return withTimeout(this.connection.sendRequest(method, params), timeoutMs, method);
|
|
230
|
-
}
|
|
231
|
-
async openSession(sessionId, options = {}) {
|
|
232
|
-
if (this.usesAgencyAcp()) {
|
|
233
|
-
return this.openAcpSession(sessionId);
|
|
234
|
-
}
|
|
235
|
-
const activeSessionId = sessionId || randomUUID();
|
|
236
|
-
await this.acquireSessionLock(activeSessionId);
|
|
237
|
-
const method = sessionId ? 'session.resume' : 'session.create';
|
|
238
|
-
let response;
|
|
239
|
-
try {
|
|
240
|
-
response = await this.request(method, {
|
|
241
|
-
sessionId: activeSessionId,
|
|
242
|
-
clientName: 'agentlink',
|
|
243
|
-
workingDirectory: this.options.workDir,
|
|
244
|
-
model: options.model,
|
|
245
|
-
contextTier: options.contextTier,
|
|
246
|
-
reasoningEffort: options.reasoningEffort,
|
|
247
|
-
streaming: true,
|
|
248
|
-
includeSubAgentStreamingEvents: true,
|
|
249
|
-
requestUserInput: !!this.options.onUserInputRequest,
|
|
250
|
-
requestExitPlanMode: !!this.options.onExitPlanModeRequest,
|
|
251
|
-
toolFilterPrecedence: 'excluded',
|
|
252
|
-
envValueMode: 'direct',
|
|
253
|
-
...(sessionId ? { disableResume: true, continuePendingWork: false } : {}),
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
this.releaseSessionLock?.();
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
const returnedSessionId = response.sessionId || activeSessionId;
|
|
261
|
-
if (returnedSessionId !== activeSessionId) {
|
|
262
|
-
throw new Error(`Copilot SDK returned unexpected session ID ${returnedSessionId}`);
|
|
263
|
-
}
|
|
264
|
-
return returnedSessionId;
|
|
265
|
-
}
|
|
266
|
-
async acquireSessionLock(sessionId) {
|
|
267
|
-
if (this.releaseSessionLock) {
|
|
268
|
-
throw new Error('Copilot SDK runtime already owns a session lock');
|
|
269
|
-
}
|
|
270
|
-
const previous = sessionLocks.get(sessionId) || Promise.resolve();
|
|
271
|
-
let releaseCurrent;
|
|
272
|
-
const current = new Promise(resolvePromise => {
|
|
273
|
-
releaseCurrent = resolvePromise;
|
|
274
|
-
});
|
|
275
|
-
const tail = previous.then(() => current, () => current);
|
|
276
|
-
sessionLocks.set(sessionId, tail);
|
|
277
|
-
await previous.catch(() => { });
|
|
278
|
-
this.releaseSessionLock = () => {
|
|
279
|
-
releaseCurrent();
|
|
280
|
-
if (sessionLocks.get(sessionId) === tail)
|
|
281
|
-
sessionLocks.delete(sessionId);
|
|
282
|
-
this.releaseSessionLock = null;
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
async listSessionCommands(sessionId) {
|
|
286
|
-
if (this.usesAgencyAcp()) {
|
|
287
|
-
const current = this.acpSessions.get(sessionId)?.commands;
|
|
288
|
-
if (current)
|
|
289
|
-
return current;
|
|
290
|
-
return withTimeout(new Promise(resolvePromise => {
|
|
291
|
-
const waiters = this.acpCommandWaiters.get(sessionId) || new Set();
|
|
292
|
-
waiters.add(resolvePromise);
|
|
293
|
-
this.acpCommandWaiters.set(sessionId, waiters);
|
|
294
|
-
}), SDK_REQUEST_TIMEOUT_MS, 'Agency Copilot command catalog');
|
|
295
|
-
}
|
|
296
|
-
return normalizeCopilotCommands(await this.request('session.commands.list', {
|
|
297
|
-
sessionId,
|
|
298
|
-
includeBuiltins: true,
|
|
299
|
-
includeSkills: true,
|
|
300
|
-
includeClientCommands: true,
|
|
301
|
-
}));
|
|
302
|
-
}
|
|
303
|
-
async invokeCommand(sessionId, name, input) {
|
|
304
|
-
if (this.usesAgencyAcp()) {
|
|
305
|
-
await this.requestAcp(acp.methods.agent.session.prompt, {
|
|
306
|
-
sessionId,
|
|
307
|
-
prompt: [{
|
|
308
|
-
type: 'text',
|
|
309
|
-
text: input.trim() ? `/${name} ${input}` : `/${name}`,
|
|
310
|
-
}],
|
|
311
|
-
}, ACP_PROMPT_TIMEOUT_MS);
|
|
312
|
-
return { kind: 'completed' };
|
|
313
|
-
}
|
|
314
|
-
return this.request('session.commands.invoke', { sessionId, name, input });
|
|
315
|
-
}
|
|
316
|
-
async setMode(sessionId, mode) {
|
|
317
|
-
if (this.usesAgencyAcp()) {
|
|
318
|
-
const state = this.acpSessions.get(sessionId);
|
|
319
|
-
const modeId = state?.modes?.availableModes.find(value => normalizeAcpMode(value.id) === mode)?.id
|
|
320
|
-
|| mode;
|
|
321
|
-
await this.requestAcp(acp.methods.agent.session.setMode, { sessionId, modeId });
|
|
322
|
-
if (state?.modes)
|
|
323
|
-
state.modes.currentModeId = modeId;
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
await this.request('session.mode.set', { sessionId, mode });
|
|
327
|
-
}
|
|
328
|
-
async getMode(sessionId) {
|
|
329
|
-
if (this.usesAgencyAcp()) {
|
|
330
|
-
return normalizeAcpMode(this.acpSessions.get(sessionId)?.modes?.currentModeId);
|
|
331
|
-
}
|
|
332
|
-
return this.request('session.mode.get', { sessionId });
|
|
333
|
-
}
|
|
334
|
-
async enableAllowAll(sessionId) {
|
|
335
|
-
if (this.usesAgencyAcp())
|
|
336
|
-
return;
|
|
337
|
-
await this.request('session.permissions.setMode', {
|
|
338
|
-
sessionId,
|
|
339
|
-
mode: 'allow-all',
|
|
340
|
-
source: 'rpc',
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
async send(sessionId, prompt, displayPrompt) {
|
|
344
|
-
if (this.usesAgencyAcp()) {
|
|
345
|
-
await this.requestAcp(acp.methods.agent.session.prompt, { sessionId, prompt: [{ type: 'text', text: displayPrompt || prompt }] }, ACP_PROMPT_TIMEOUT_MS);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
await this.request('session.send', {
|
|
349
|
-
sessionId,
|
|
350
|
-
prompt,
|
|
351
|
-
displayPrompt,
|
|
352
|
-
wait: true,
|
|
353
|
-
}, ACP_PROMPT_TIMEOUT_MS);
|
|
354
|
-
}
|
|
355
|
-
async abort(sessionId) {
|
|
356
|
-
if (this.usesAgencyAcp()) {
|
|
357
|
-
await this.notifyAcp(acp.methods.agent.session.cancel, { sessionId });
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
await this.request('session.abort', { sessionId });
|
|
361
|
-
}
|
|
362
|
-
async abortManualCompaction(sessionId) {
|
|
363
|
-
if (this.usesAgencyAcp()) {
|
|
364
|
-
await this.notifyAcp(acp.methods.agent.session.cancel, { sessionId });
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
await this.request('session.history.abortManualCompaction', { sessionId });
|
|
368
|
-
}
|
|
369
|
-
async destroySession(sessionId) {
|
|
370
|
-
if (this.usesAgencyAcp()) {
|
|
371
|
-
try {
|
|
372
|
-
await this.requestAcp(acp.methods.agent.session.close, { sessionId });
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
// ACP connection shutdown below releases any remaining session resources.
|
|
376
|
-
}
|
|
377
|
-
this.acpSessions.delete(sessionId);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
try {
|
|
381
|
-
await this.request('session.destroy', { sessionId });
|
|
382
|
-
}
|
|
383
|
-
catch {
|
|
384
|
-
// Runtime shutdown below also releases any remaining session handles.
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
async deleteSession(sessionId) {
|
|
388
|
-
if (this.usesAgencyAcp())
|
|
389
|
-
return;
|
|
390
|
-
await this.request('session.delete', { sessionId });
|
|
391
|
-
}
|
|
392
|
-
async stop() {
|
|
393
|
-
const connection = this.connection;
|
|
394
|
-
const acpConnection = this.acpConnection;
|
|
395
|
-
const child = this.child;
|
|
396
|
-
this.connection = null;
|
|
397
|
-
this.acpConnection = null;
|
|
398
|
-
this.child = null;
|
|
399
|
-
if (acpConnection)
|
|
400
|
-
acpConnection.close();
|
|
401
|
-
if (connection) {
|
|
402
|
-
try {
|
|
403
|
-
await withTimeout(connection.sendRequest('runtime.shutdown', {}), SDK_STOP_TIMEOUT_MS, 'Copilot SDK shutdown');
|
|
404
|
-
}
|
|
405
|
-
catch {
|
|
406
|
-
// Force termination below is the bounded fallback.
|
|
407
|
-
}
|
|
408
|
-
connection.dispose();
|
|
409
|
-
}
|
|
410
|
-
if (child && !child.killed)
|
|
411
|
-
child.kill();
|
|
412
|
-
this.acpSessions.clear();
|
|
413
|
-
this.acpCommandWaiters.clear();
|
|
414
|
-
this.acpToolNames.clear();
|
|
415
|
-
this.releaseSessionLock?.();
|
|
416
|
-
}
|
|
417
|
-
usesAgencyAcp() {
|
|
418
|
-
return this.options.providerId === 'agency-copilot';
|
|
419
|
-
}
|
|
420
|
-
async openAcpSession(sessionId) {
|
|
421
|
-
const lockId = sessionId || randomUUID();
|
|
422
|
-
await this.acquireSessionLock(lockId);
|
|
423
|
-
try {
|
|
424
|
-
if (sessionId) {
|
|
425
|
-
const state = this.acpSessions.get(sessionId) || {};
|
|
426
|
-
state.suppressEvents = true;
|
|
427
|
-
this.acpSessions.set(sessionId, state);
|
|
428
|
-
}
|
|
429
|
-
const response = sessionId
|
|
430
|
-
? await this.requestAcp(acp.methods.agent.session.load, { sessionId, cwd: this.options.workDir, mcpServers: [] }, ACP_SESSION_TIMEOUT_MS)
|
|
431
|
-
: await this.requestAcp(acp.methods.agent.session.new, { cwd: this.options.workDir, mcpServers: [] }, ACP_SESSION_TIMEOUT_MS);
|
|
432
|
-
const activeSessionId = sessionId || response.sessionId;
|
|
433
|
-
if (!activeSessionId)
|
|
434
|
-
throw new Error('Agency Copilot ACP did not return a session ID');
|
|
435
|
-
const state = this.acpSessions.get(activeSessionId) || {};
|
|
436
|
-
if (response && 'modes' in response)
|
|
437
|
-
state.modes = response.modes;
|
|
438
|
-
state.suppressEvents = false;
|
|
439
|
-
this.acpSessions.set(activeSessionId, state);
|
|
440
|
-
return activeSessionId;
|
|
441
|
-
}
|
|
442
|
-
catch (error) {
|
|
443
|
-
if (sessionId)
|
|
444
|
-
this.acpSessions.delete(sessionId);
|
|
445
|
-
this.releaseSessionLock?.();
|
|
446
|
-
throw error;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
async requestAcp(method, params, timeoutMs = SDK_REQUEST_TIMEOUT_MS) {
|
|
450
|
-
if (!this.acpConnection)
|
|
451
|
-
throw new Error('Agency Copilot ACP runtime is not started');
|
|
452
|
-
return withTimeout(this.acpConnection.agent.request(method, params), timeoutMs, method);
|
|
453
|
-
}
|
|
454
|
-
async notifyAcp(method, params) {
|
|
455
|
-
if (!this.acpConnection)
|
|
456
|
-
throw new Error('Agency Copilot ACP runtime is not started');
|
|
457
|
-
await this.acpConnection.agent.notify(method, params);
|
|
458
|
-
}
|
|
459
|
-
handleAcpSessionUpdate(notification) {
|
|
460
|
-
const { sessionId, update } = notification;
|
|
461
|
-
const state = this.acpSessions.get(sessionId) || {};
|
|
462
|
-
this.acpSessions.set(sessionId, state);
|
|
463
|
-
switch (update.sessionUpdate) {
|
|
464
|
-
case 'available_commands_update': {
|
|
465
|
-
const commands = normalizeAcpCommands(update.availableCommands);
|
|
466
|
-
state.commands = commands;
|
|
467
|
-
const waiters = this.acpCommandWaiters.get(sessionId);
|
|
468
|
-
if (waiters) {
|
|
469
|
-
this.acpCommandWaiters.delete(sessionId);
|
|
470
|
-
for (const resolvePromise of waiters)
|
|
471
|
-
resolvePromise(commands);
|
|
472
|
-
}
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
|
-
case 'current_mode_update':
|
|
476
|
-
if (state.modes)
|
|
477
|
-
state.modes.currentModeId = update.currentModeId;
|
|
478
|
-
else
|
|
479
|
-
state.modes = { currentModeId: update.currentModeId, availableModes: [] };
|
|
480
|
-
this.emitAcpEvent(sessionId, {
|
|
481
|
-
type: 'session.mode_changed',
|
|
482
|
-
data: { newMode: normalizeAcpMode(update.currentModeId) },
|
|
483
|
-
});
|
|
484
|
-
break;
|
|
485
|
-
case 'compaction_update': {
|
|
486
|
-
const type = update.status === 'in_progress'
|
|
487
|
-
? 'session.compaction_start'
|
|
488
|
-
: update.status === 'completed'
|
|
489
|
-
? 'session.compaction_complete'
|
|
490
|
-
: 'session.compaction_failed';
|
|
491
|
-
this.emitAcpEvent(sessionId, {
|
|
492
|
-
type,
|
|
493
|
-
data: { status: update.status, error: update.error },
|
|
494
|
-
});
|
|
495
|
-
break;
|
|
496
|
-
}
|
|
497
|
-
case 'agent_message_chunk':
|
|
498
|
-
case 'agent_thought_chunk':
|
|
499
|
-
if (update.content.type === 'text') {
|
|
500
|
-
this.emitAcpEvent(sessionId, {
|
|
501
|
-
type: update.sessionUpdate === 'agent_message_chunk'
|
|
502
|
-
? 'assistant.message_delta'
|
|
503
|
-
: 'assistant.reasoning_delta',
|
|
504
|
-
data: { deltaContent: update.content.text },
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
break;
|
|
508
|
-
case 'tool_call': {
|
|
509
|
-
const name = update.name || update.title;
|
|
510
|
-
this.acpToolNames.set(update.toolCallId, name);
|
|
511
|
-
this.emitAcpEvent(sessionId, {
|
|
512
|
-
type: 'assistant.message',
|
|
513
|
-
data: {
|
|
514
|
-
toolRequests: [{
|
|
515
|
-
toolCallId: update.toolCallId,
|
|
516
|
-
name,
|
|
517
|
-
arguments: update.rawInput,
|
|
518
|
-
intentionSummary: update.title,
|
|
519
|
-
}],
|
|
520
|
-
},
|
|
521
|
-
});
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
case 'tool_call_update':
|
|
525
|
-
if (update.status === 'completed' || update.status === 'failed') {
|
|
526
|
-
this.emitAcpEvent(sessionId, {
|
|
527
|
-
type: 'tool.execution_complete',
|
|
528
|
-
data: {
|
|
529
|
-
toolCallId: update.toolCallId,
|
|
530
|
-
toolName: update.name || this.acpToolNames.get(update.toolCallId),
|
|
531
|
-
success: update.status === 'completed',
|
|
532
|
-
result: { content: stringifyAcpToolOutput(update.rawOutput) },
|
|
533
|
-
error: update.status === 'failed'
|
|
534
|
-
? { message: stringifyAcpToolOutput(update.rawOutput) || 'Tool execution failed' }
|
|
535
|
-
: undefined,
|
|
536
|
-
},
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
emitAcpEvent(sessionId, event) {
|
|
543
|
-
if (this.acpSessions.get(sessionId)?.suppressEvents)
|
|
544
|
-
return;
|
|
545
|
-
this.options.onSessionEvent?.(sessionId, event);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
function normalizeAcpMode(modeId) {
|
|
549
|
-
const normalized = modeId?.toLowerCase().split(/[#/]/).filter(Boolean).at(-1);
|
|
550
|
-
if (normalized === 'plan')
|
|
551
|
-
return 'plan';
|
|
552
|
-
if (normalized === 'autopilot')
|
|
553
|
-
return 'autopilot';
|
|
554
|
-
return 'interactive';
|
|
555
|
-
}
|
|
556
|
-
function stringifyAcpToolOutput(value) {
|
|
557
|
-
if (typeof value === 'string')
|
|
558
|
-
return value;
|
|
559
|
-
if (value == null)
|
|
560
|
-
return '';
|
|
561
|
-
try {
|
|
562
|
-
return JSON.stringify(value);
|
|
563
|
-
}
|
|
564
|
-
catch {
|
|
565
|
-
return String(value);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
export function normalizeAcpCommands(values) {
|
|
569
|
-
if (!Array.isArray(values))
|
|
570
|
-
return [];
|
|
571
|
-
const normalized = [];
|
|
572
|
-
for (const supported of SUPPORTED_COMMANDS) {
|
|
573
|
-
const source = values.find(command => {
|
|
574
|
-
const name = command?.name?.toLowerCase();
|
|
575
|
-
return name === supported || (supported === 'goal' && name === 'autopilot');
|
|
576
|
-
});
|
|
577
|
-
if (!source)
|
|
578
|
-
continue;
|
|
579
|
-
normalized.push({
|
|
580
|
-
id: `command:${supported}`,
|
|
581
|
-
name: supported,
|
|
582
|
-
invocation: `/${supported}`,
|
|
583
|
-
description: source.description || '',
|
|
584
|
-
kind: 'command',
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
return normalized;
|
|
588
|
-
}
|
|
589
|
-
export function normalizeCopilotCommands(input) {
|
|
590
|
-
const values = input?.commands;
|
|
591
|
-
if (!Array.isArray(values))
|
|
592
|
-
return [];
|
|
593
|
-
const normalized = [];
|
|
594
|
-
for (const supported of SUPPORTED_COMMANDS) {
|
|
595
|
-
const source = values.find(command => {
|
|
596
|
-
if (!command || typeof command.name !== 'string')
|
|
597
|
-
return false;
|
|
598
|
-
const names = [command.name, ...(Array.isArray(command.aliases) ? command.aliases : [])];
|
|
599
|
-
return names.some(name => name.toLowerCase() === supported);
|
|
600
|
-
});
|
|
601
|
-
if (!source)
|
|
602
|
-
continue;
|
|
603
|
-
normalized.push({
|
|
604
|
-
id: `command:${supported}`,
|
|
605
|
-
name: supported,
|
|
606
|
-
invocation: `/${supported}`,
|
|
607
|
-
description: typeof source.description === 'string' ? source.description : '',
|
|
608
|
-
kind: 'command',
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
return normalized;
|
|
612
|
-
}
|
|
613
|
-
export async function discoverCopilotCommands(scope) {
|
|
614
|
-
const runtime = new CopilotCommandRuntime({
|
|
615
|
-
providerId: scope.providerId,
|
|
616
|
-
workDir: scope.workDir,
|
|
617
|
-
});
|
|
618
|
-
let sessionId;
|
|
619
|
-
let disposableSession = false;
|
|
620
|
-
try {
|
|
621
|
-
await runtime.start();
|
|
622
|
-
const state = await import('./copilot.js');
|
|
623
|
-
const conversation = state.getCopilotConversation(scope.conversationId);
|
|
624
|
-
sessionId = conversation?.copilotSessionId || conversation?.lastCopilotSessionId || undefined;
|
|
625
|
-
if (!sessionId) {
|
|
626
|
-
sessionId = await runtime.openSession();
|
|
627
|
-
disposableSession = true;
|
|
628
|
-
}
|
|
629
|
-
if (!disposableSession)
|
|
630
|
-
await runtime.openSession(sessionId);
|
|
631
|
-
return { commands: await runtime.listSessionCommands(sessionId) };
|
|
632
|
-
}
|
|
633
|
-
catch (error) {
|
|
634
|
-
return {
|
|
635
|
-
commands: [],
|
|
636
|
-
error: error instanceof Error ? error.message : String(error),
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
finally {
|
|
640
|
-
if (sessionId)
|
|
641
|
-
await runtime.destroySession(sessionId);
|
|
642
|
-
if (sessionId && disposableSession) {
|
|
643
|
-
try {
|
|
644
|
-
await runtime.deleteSession(sessionId);
|
|
645
|
-
}
|
|
646
|
-
catch { /* best-effort cleanup */ }
|
|
647
|
-
}
|
|
648
|
-
await runtime.stop();
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
function isRecord(value) {
|
|
652
|
-
return !!value && typeof value === 'object';
|
|
653
|
-
}
|
|
654
|
-
//# sourceMappingURL=copilot-command-bridge.js.map
|