@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
package/dist/copilot.js
CHANGED
|
@@ -14,14 +14,12 @@ import { readdirSync, readFileSync, writeFileSync, existsSync, rmSync, mkdirSync
|
|
|
14
14
|
import { basename, dirname, extname, join, resolve } from 'path';
|
|
15
15
|
import { homedir, platform } from 'os';
|
|
16
16
|
import { randomUUID } from 'crypto';
|
|
17
|
-
import * as acp from '@agentclientprotocol/sdk';
|
|
18
17
|
import { processUserMessage } from './history-message-transform.js';
|
|
19
18
|
import { copilotCapabilities } from './backends/types.js';
|
|
20
19
|
import { CONFIG_DIR } from './config.js';
|
|
21
20
|
import { getCleanEnv, resolveExecutablePath } from './sdk.js';
|
|
22
21
|
import { getProvider, resolveProviderCommand } from './providers.js';
|
|
23
22
|
import { FileCache } from './file-cache.js';
|
|
24
|
-
import { CopilotCommandRuntime, } from './copilot-command-bridge.js';
|
|
25
23
|
// ── Module state ───────────────────────────────────────────────────────────
|
|
26
24
|
const conversations = new Map();
|
|
27
25
|
const sendFns = new Set();
|
|
@@ -29,24 +27,6 @@ let globalModel = null;
|
|
|
29
27
|
const MODEL_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
30
28
|
const CONTEXT_TIERS = new Set(['default', 'long_context']);
|
|
31
29
|
const REASONING_EFFORTS = new Set(['none', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
32
|
-
const COPILOT_PLAN_INPUT_TIMEOUT_MS = 10 * 60_000;
|
|
33
|
-
const COPILOT_CONTINUE_PLAN_ANSWER = 'Continue developing the plan. In your final response, ask me to confirm the plan or describe any changes.';
|
|
34
|
-
const pendingCopilotDecisions = new Map();
|
|
35
|
-
class CopilotDecisionCancelledError extends Error {
|
|
36
|
-
constructor() {
|
|
37
|
-
super('Copilot decision request was cancelled');
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function setCopilotModeState(state, mode) {
|
|
41
|
-
state.copilotMode = mode;
|
|
42
|
-
state.planMode = mode === 'plan';
|
|
43
|
-
sendFn({
|
|
44
|
-
type: 'plan_mode_changed',
|
|
45
|
-
enabled: state.planMode,
|
|
46
|
-
mode,
|
|
47
|
-
conversationId: state.conversationId,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
30
|
const COPILOT_NATIVE_ATTACHMENT_MIME_TYPES = new Set([
|
|
51
31
|
'application/pdf',
|
|
52
32
|
'image/gif',
|
|
@@ -80,107 +60,6 @@ export function addCopilotSendFn(fn) {
|
|
|
80
60
|
sendFns.add(fn);
|
|
81
61
|
return () => { sendFns.delete(fn); };
|
|
82
62
|
}
|
|
83
|
-
function emitCopilotDecisionRequest(pending) {
|
|
84
|
-
sendFn({
|
|
85
|
-
type: 'backend_event',
|
|
86
|
-
conversationId: pending.conversationId,
|
|
87
|
-
providerId: pending.providerId,
|
|
88
|
-
claudeSessionId: pending.sessionId,
|
|
89
|
-
event: {
|
|
90
|
-
type: 'user_input_requested',
|
|
91
|
-
session: sessionRef(pending.sessionId),
|
|
92
|
-
requestId: pending.requestId,
|
|
93
|
-
request: pending.request,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
function settleCopilotDecision(pending, answer) {
|
|
98
|
-
if (pendingCopilotDecisions.get(pending.requestId) !== pending)
|
|
99
|
-
return false;
|
|
100
|
-
pendingCopilotDecisions.delete(pending.requestId);
|
|
101
|
-
if (pending.timer)
|
|
102
|
-
clearTimeout(pending.timer);
|
|
103
|
-
pending.timer = null;
|
|
104
|
-
sendFn({
|
|
105
|
-
type: 'backend_event',
|
|
106
|
-
conversationId: pending.conversationId,
|
|
107
|
-
providerId: pending.providerId,
|
|
108
|
-
claudeSessionId: pending.sessionId,
|
|
109
|
-
event: {
|
|
110
|
-
type: 'user_input_resolved',
|
|
111
|
-
session: sessionRef(pending.sessionId),
|
|
112
|
-
requestId: pending.requestId,
|
|
113
|
-
resolution: answer.action === 'cancel' ? 'cancelled' : 'answered',
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
pending.resolve(answer);
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
function waitForCopilotDecision(state, sessionId, request, choices = []) {
|
|
120
|
-
const requestId = randomUUID();
|
|
121
|
-
return new Promise(resolvePromise => {
|
|
122
|
-
const pending = {
|
|
123
|
-
requestId,
|
|
124
|
-
conversationId: state.conversationId,
|
|
125
|
-
providerId: state.providerId,
|
|
126
|
-
sessionId,
|
|
127
|
-
request,
|
|
128
|
-
choices,
|
|
129
|
-
resolve: resolvePromise,
|
|
130
|
-
timer: null,
|
|
131
|
-
};
|
|
132
|
-
pendingCopilotDecisions.set(requestId, pending);
|
|
133
|
-
if (request.expiresAt) {
|
|
134
|
-
pending.timer = setTimeout(() => {
|
|
135
|
-
if (choices.length > 0) {
|
|
136
|
-
settleCopilotDecision(pending, { action: 'answer', answer: choices[0] });
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
settleCopilotDecision(pending, {
|
|
140
|
-
action: 'answer',
|
|
141
|
-
answer: COPILOT_CONTINUE_PLAN_ANSWER,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}, Math.max(0, request.expiresAt - Date.now()));
|
|
145
|
-
}
|
|
146
|
-
emitCopilotDecisionRequest(pending);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
function cancelPendingCopilotDecisions(conversationId) {
|
|
150
|
-
for (const pending of [...pendingCopilotDecisions.values()]) {
|
|
151
|
-
if (pending.conversationId === conversationId) {
|
|
152
|
-
settleCopilotDecision(pending, { action: 'cancel' });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
export function getPendingCopilotDecisions(conversationId) {
|
|
157
|
-
return [...pendingCopilotDecisions.values()]
|
|
158
|
-
.filter(pending => pending.conversationId === conversationId)
|
|
159
|
-
.map(pending => ({
|
|
160
|
-
requestId: pending.requestId,
|
|
161
|
-
conversationId: pending.conversationId,
|
|
162
|
-
providerId: pending.providerId,
|
|
163
|
-
sessionId: pending.sessionId,
|
|
164
|
-
request: pending.request,
|
|
165
|
-
}));
|
|
166
|
-
}
|
|
167
|
-
export function handleCopilotUserAnswer(requestId, answers, scope = {}) {
|
|
168
|
-
const pending = pendingCopilotDecisions.get(requestId);
|
|
169
|
-
if (!pending)
|
|
170
|
-
return false;
|
|
171
|
-
if (scope.conversationId && pending.conversationId !== scope.conversationId)
|
|
172
|
-
return false;
|
|
173
|
-
if (scope.providerId && pending.providerId !== scope.providerId)
|
|
174
|
-
return false;
|
|
175
|
-
const action = typeof answers.__action === 'string' ? answers.__action : undefined;
|
|
176
|
-
const feedback = typeof answers.__feedback === 'string' ? answers.__feedback.trim() : undefined;
|
|
177
|
-
const firstAnswer = Object.entries(answers)
|
|
178
|
-
.find(([key, value]) => !key.startsWith('__') && typeof value === 'string')?.[1];
|
|
179
|
-
const answer = action || (typeof firstAnswer === 'string' ? firstAnswer : '');
|
|
180
|
-
if (!answer)
|
|
181
|
-
return false;
|
|
182
|
-
return settleCopilotDecision(pending, { action: 'answer', answer, feedback });
|
|
183
|
-
}
|
|
184
63
|
function resolveDirectExecution(cmdPath) {
|
|
185
64
|
if (platform() !== 'win32')
|
|
186
65
|
return null;
|
|
@@ -254,18 +133,10 @@ function finishCopilotTurn(state) {
|
|
|
254
133
|
state.heartbeatTimer = null;
|
|
255
134
|
}
|
|
256
135
|
}
|
|
257
|
-
function ensureCopilotCommandTurnActive(state, turnId) {
|
|
258
|
-
if (!state.turnActive || state.turnId !== turnId) {
|
|
259
|
-
throw new CopilotDecisionCancelledError();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
136
|
function emitCopilotCancelled(conversationId, state) {
|
|
263
137
|
sendFn({
|
|
264
138
|
type: 'backend_event',
|
|
265
139
|
conversationId,
|
|
266
|
-
claudeSessionId: state?.copilotSessionId || null,
|
|
267
|
-
turnId: state?.turnId || null,
|
|
268
|
-
stateVersion: state?.stateVersion || 0,
|
|
269
140
|
event: { type: 'turn_cancelled', session: sessionRef(state?.copilotSessionId || '') },
|
|
270
141
|
});
|
|
271
142
|
sendFn({
|
|
@@ -334,10 +205,6 @@ export function rebindCopilotConversation(copilotSessionId, newConvId) {
|
|
|
334
205
|
conversations.delete(oldConvId);
|
|
335
206
|
state.conversationId = newConvId;
|
|
336
207
|
conversations.set(newConvId, state);
|
|
337
|
-
for (const pending of pendingCopilotDecisions.values()) {
|
|
338
|
-
if (pending.conversationId === oldConvId)
|
|
339
|
-
pending.conversationId = newConvId;
|
|
340
|
-
}
|
|
341
208
|
return true;
|
|
342
209
|
}
|
|
343
210
|
}
|
|
@@ -362,13 +229,6 @@ export function createCopilotPlaceholder(conversationId, opts) {
|
|
|
362
229
|
stateVersion: 0,
|
|
363
230
|
heartbeatTimer: null,
|
|
364
231
|
sessionNotified: false,
|
|
365
|
-
copilotMode: 'interactive',
|
|
366
|
-
planMode: false,
|
|
367
|
-
planModePending: false,
|
|
368
|
-
isCompacting: false,
|
|
369
|
-
commandRuntime: null,
|
|
370
|
-
commandSessionId: null,
|
|
371
|
-
deferredCommandUsage: null,
|
|
372
232
|
});
|
|
373
233
|
}
|
|
374
234
|
export function restartCopilotConversation(conversationId, opts) {
|
|
@@ -383,7 +243,7 @@ export function restartCopilotConversation(conversationId, opts) {
|
|
|
383
243
|
child: null,
|
|
384
244
|
abortController: null,
|
|
385
245
|
copilotSessionId: null,
|
|
386
|
-
lastCopilotSessionId:
|
|
246
|
+
lastCopilotSessionId: copilotSessionId,
|
|
387
247
|
workDir: state?.workDir || process.cwd(),
|
|
388
248
|
turnActive: false,
|
|
389
249
|
providerId: opts?.providerId || state?.providerId || 'copilot',
|
|
@@ -394,13 +254,6 @@ export function restartCopilotConversation(conversationId, opts) {
|
|
|
394
254
|
stateVersion: (state?.stateVersion || 0) + 1,
|
|
395
255
|
heartbeatTimer: null,
|
|
396
256
|
sessionNotified: false,
|
|
397
|
-
copilotMode: state?.copilotMode || (state?.planMode ? 'plan' : 'interactive'),
|
|
398
|
-
planMode: state?.planMode || false,
|
|
399
|
-
planModePending: state?.planModePending || false,
|
|
400
|
-
isCompacting: false,
|
|
401
|
-
commandRuntime: null,
|
|
402
|
-
commandSessionId: null,
|
|
403
|
-
deferredCommandUsage: null,
|
|
404
257
|
});
|
|
405
258
|
return { wasTurnActive, copilotSessionId };
|
|
406
259
|
}
|
|
@@ -414,459 +267,6 @@ export function setModel(conversationId, model) {
|
|
|
414
267
|
globalModel = model;
|
|
415
268
|
}
|
|
416
269
|
}
|
|
417
|
-
function detachCopilotProviderSession(state, providerId) {
|
|
418
|
-
if (state.providerId === providerId)
|
|
419
|
-
return;
|
|
420
|
-
state.providerId = providerId;
|
|
421
|
-
state.copilotSessionId = null;
|
|
422
|
-
state.lastCopilotSessionId = null;
|
|
423
|
-
state.sessionNotified = false;
|
|
424
|
-
}
|
|
425
|
-
function isMissingCopilotSessionError(error) {
|
|
426
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
427
|
-
return /(?:session|resource).*not found|not found.*(?:session|resource)|no (?:session|task|name).*matched/i.test(message);
|
|
428
|
-
}
|
|
429
|
-
function emitCopilotCompactionStarted(state) {
|
|
430
|
-
if (state.isCompacting)
|
|
431
|
-
return false;
|
|
432
|
-
state.isCompacting = true;
|
|
433
|
-
const session = sessionRef(state.copilotSessionId || state.commandSessionId || 'pending');
|
|
434
|
-
sendFn({
|
|
435
|
-
type: 'context_compaction',
|
|
436
|
-
conversationId: state.conversationId,
|
|
437
|
-
turnId: state.turnId,
|
|
438
|
-
status: 'started',
|
|
439
|
-
});
|
|
440
|
-
sendFn({
|
|
441
|
-
type: 'backend_event',
|
|
442
|
-
conversationId: state.conversationId,
|
|
443
|
-
turnId: state.turnId,
|
|
444
|
-
event: { type: 'compact_started', session },
|
|
445
|
-
});
|
|
446
|
-
return true;
|
|
447
|
-
}
|
|
448
|
-
function emitCopilotCompactionCompleted(state) {
|
|
449
|
-
if (!state.isCompacting)
|
|
450
|
-
return false;
|
|
451
|
-
state.isCompacting = false;
|
|
452
|
-
const session = sessionRef(state.copilotSessionId || state.commandSessionId || '');
|
|
453
|
-
sendFn({
|
|
454
|
-
type: 'context_compaction',
|
|
455
|
-
conversationId: state.conversationId,
|
|
456
|
-
turnId: state.turnId,
|
|
457
|
-
status: 'completed',
|
|
458
|
-
});
|
|
459
|
-
sendFn({
|
|
460
|
-
type: 'backend_event',
|
|
461
|
-
conversationId: state.conversationId,
|
|
462
|
-
turnId: state.turnId,
|
|
463
|
-
event: { type: 'compact_completed', session },
|
|
464
|
-
});
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
function emitCopilotCommandOutput(state, content) {
|
|
468
|
-
if (!content.trim())
|
|
469
|
-
return;
|
|
470
|
-
sendFn({
|
|
471
|
-
type: 'command_output',
|
|
472
|
-
conversationId: state.conversationId,
|
|
473
|
-
content,
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
function emitCopilotCommandCompleted(state) {
|
|
477
|
-
if (!state.turnActive)
|
|
478
|
-
return;
|
|
479
|
-
finishCopilotTurn(state);
|
|
480
|
-
const session = sessionRef(state.copilotSessionId || '');
|
|
481
|
-
const usage = state.deferredCommandUsage || undefined;
|
|
482
|
-
state.deferredCommandUsage = null;
|
|
483
|
-
sendFn({
|
|
484
|
-
type: 'backend_event',
|
|
485
|
-
conversationId: state.conversationId,
|
|
486
|
-
event: { type: 'turn_completed', session, ...(usage ? { usage } : {}) },
|
|
487
|
-
});
|
|
488
|
-
sendFn({
|
|
489
|
-
type: 'turn_completed',
|
|
490
|
-
conversationId: state.conversationId,
|
|
491
|
-
workDir: state.workDir,
|
|
492
|
-
claudeSessionId: state.copilotSessionId,
|
|
493
|
-
turnId: state.turnId,
|
|
494
|
-
stateVersion: state.stateVersion,
|
|
495
|
-
...(usage ? { usage } : {}),
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
function commandInputForDisplay(command, input) {
|
|
499
|
-
return input.trim() ? `/${command} ${input}` : `/${command}`;
|
|
500
|
-
}
|
|
501
|
-
async function applyCopilotCommandResult(state, runtime, sessionId, command, input, result) {
|
|
502
|
-
switch (result.kind) {
|
|
503
|
-
case 'text':
|
|
504
|
-
emitCopilotCommandOutput(state, result.text);
|
|
505
|
-
return;
|
|
506
|
-
case 'completed':
|
|
507
|
-
if (result.message)
|
|
508
|
-
emitCopilotCommandOutput(state, result.message);
|
|
509
|
-
return;
|
|
510
|
-
case 'select-subcommand':
|
|
511
|
-
throw new Error(`${result.title}: ${result.options.map(option => option.name).join(', ')}`);
|
|
512
|
-
case 'agent-prompt':
|
|
513
|
-
if (result.notice)
|
|
514
|
-
emitCopilotCommandOutput(state, result.notice);
|
|
515
|
-
if (result.mode) {
|
|
516
|
-
await runtime.setMode(sessionId, result.mode);
|
|
517
|
-
setCopilotModeState(state, result.mode);
|
|
518
|
-
}
|
|
519
|
-
await runtime.send(sessionId, result.prompt, result.displayPrompt || commandInputForDisplay(command, input));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async function requestCopilotPlanInput(state, sessionId, request) {
|
|
523
|
-
const choices = request.choices || [];
|
|
524
|
-
const expiresAt = Date.now() + COPILOT_PLAN_INPUT_TIMEOUT_MS;
|
|
525
|
-
const decision = await waitForCopilotDecision(state, sessionId, {
|
|
526
|
-
questions: [{
|
|
527
|
-
question: request.question,
|
|
528
|
-
header: 'Plan question',
|
|
529
|
-
options: choices.map(choice => ({ label: choice, value: choice })),
|
|
530
|
-
}],
|
|
531
|
-
source: 'tool_request_user_input',
|
|
532
|
-
presentation: 'inline',
|
|
533
|
-
kind: 'plan_input',
|
|
534
|
-
allowFreeform: request.allowFreeform !== false,
|
|
535
|
-
expiresAt,
|
|
536
|
-
timeoutAction: choices.length > 0 ? 'first_option' : 'continue_plan',
|
|
537
|
-
}, choices);
|
|
538
|
-
if (decision.action === 'cancel')
|
|
539
|
-
throw new CopilotDecisionCancelledError();
|
|
540
|
-
const answer = decision.answer || '';
|
|
541
|
-
return {
|
|
542
|
-
answer,
|
|
543
|
-
wasFreeform: !choices.includes(answer),
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
async function requestCopilotAcpElicitation(state, request) {
|
|
547
|
-
if (!acp.CreateElicitationRequest.isForm(request) || !('sessionId' in request)) {
|
|
548
|
-
return { action: 'cancel' };
|
|
549
|
-
}
|
|
550
|
-
const properties = Object.entries(request.requestedSchema.properties || {});
|
|
551
|
-
const [fieldName, field] = properties[0] || [];
|
|
552
|
-
if (!fieldName || !field)
|
|
553
|
-
return { action: 'decline' };
|
|
554
|
-
let choices = [];
|
|
555
|
-
let multiSelect = false;
|
|
556
|
-
if (acp.ElicitationPropertySchema.isString(field)) {
|
|
557
|
-
choices = field.oneOf?.map(option => option.const) || field.enum || [];
|
|
558
|
-
}
|
|
559
|
-
else if (acp.ElicitationPropertySchema.isArray(field)) {
|
|
560
|
-
multiSelect = true;
|
|
561
|
-
choices = acp.MultiSelectItems.isTitled(field.items)
|
|
562
|
-
? field.items.anyOf.map(option => option.const)
|
|
563
|
-
: (acp.MultiSelectItems.isString(field.items) ? field.items.enum : []);
|
|
564
|
-
}
|
|
565
|
-
else if (acp.ElicitationPropertySchema.isBoolean(field)) {
|
|
566
|
-
choices = ['true', 'false'];
|
|
567
|
-
}
|
|
568
|
-
const fieldRecord = field;
|
|
569
|
-
const fieldDescription = typeof fieldRecord.description === 'string'
|
|
570
|
-
? fieldRecord.description
|
|
571
|
-
: request.message;
|
|
572
|
-
const fieldTitle = typeof fieldRecord.title === 'string'
|
|
573
|
-
? fieldRecord.title
|
|
574
|
-
: 'Plan question';
|
|
575
|
-
const expiresAt = Date.now() + COPILOT_PLAN_INPUT_TIMEOUT_MS;
|
|
576
|
-
const decision = await waitForCopilotDecision(state, request.sessionId, {
|
|
577
|
-
questions: [{
|
|
578
|
-
question: fieldDescription,
|
|
579
|
-
header: fieldTitle,
|
|
580
|
-
options: choices.map(choice => ({ label: choice, value: choice })),
|
|
581
|
-
multiSelect,
|
|
582
|
-
}],
|
|
583
|
-
source: 'tool_request_user_input',
|
|
584
|
-
presentation: 'inline',
|
|
585
|
-
kind: 'plan_input',
|
|
586
|
-
allowFreeform: choices.length === 0,
|
|
587
|
-
expiresAt,
|
|
588
|
-
timeoutAction: choices.length > 0 ? 'first_option' : 'continue_plan',
|
|
589
|
-
}, choices);
|
|
590
|
-
if (decision.action === 'cancel')
|
|
591
|
-
return { action: 'cancel' };
|
|
592
|
-
const raw = decision.answer || '';
|
|
593
|
-
let value = raw;
|
|
594
|
-
if (acp.ElicitationPropertySchema.isArray(field))
|
|
595
|
-
value = raw ? raw.split(/,\s*/) : [];
|
|
596
|
-
else if (acp.ElicitationPropertySchema.isBoolean(field))
|
|
597
|
-
value = raw === 'true';
|
|
598
|
-
else if (acp.ElicitationPropertySchema.isNumber(field)
|
|
599
|
-
|| acp.ElicitationPropertySchema.isInteger(field))
|
|
600
|
-
value = Number(raw);
|
|
601
|
-
return { action: 'accept', content: { [fieldName]: value } };
|
|
602
|
-
}
|
|
603
|
-
function copilotPlanConfirmationPrompt(content) {
|
|
604
|
-
return /[\u3400-\u9fff]/.test(content)
|
|
605
|
-
? '请确认以上计划,或直接回复需要修改的内容。'
|
|
606
|
-
: 'Please confirm the Plan above, or reply with any changes you want.';
|
|
607
|
-
}
|
|
608
|
-
function emitCopilotPlanReview(state, sessionId, content, languageHint = content || '') {
|
|
609
|
-
const plan = content?.trim() || '';
|
|
610
|
-
const prompt = copilotPlanConfirmationPrompt(languageHint);
|
|
611
|
-
sendFn({
|
|
612
|
-
type: 'backend_event',
|
|
613
|
-
conversationId: state.conversationId,
|
|
614
|
-
providerId: state.providerId,
|
|
615
|
-
claudeSessionId: sessionId,
|
|
616
|
-
event: {
|
|
617
|
-
type: 'text_delta',
|
|
618
|
-
session: sessionRef(sessionId),
|
|
619
|
-
text: `${plan ? `\n\n${plan}\n\n` : '\n\n'}> ${prompt}`,
|
|
620
|
-
},
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
async function acceptCopilotPlanExitWithoutBlocking(state, request) {
|
|
624
|
-
const selectedAction = request.actions.includes('exit_only')
|
|
625
|
-
? 'exit_only'
|
|
626
|
-
: request.actions.includes('interactive')
|
|
627
|
-
? 'interactive'
|
|
628
|
-
: null;
|
|
629
|
-
if (!selectedAction) {
|
|
630
|
-
return {
|
|
631
|
-
approved: false,
|
|
632
|
-
feedback: COPILOT_CONTINUE_PLAN_ANSWER,
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
state.planModePending = false;
|
|
636
|
-
return { approved: true, selectedAction };
|
|
637
|
-
}
|
|
638
|
-
export async function executeCopilotCommand(conversationId, command, input, workDir, options) {
|
|
639
|
-
let state = conversations.get(conversationId);
|
|
640
|
-
if (!state) {
|
|
641
|
-
createCopilotPlaceholder(conversationId, { providerId: options?.providerId });
|
|
642
|
-
state = conversations.get(conversationId);
|
|
643
|
-
}
|
|
644
|
-
if (state.turnActive || state.commandRuntime) {
|
|
645
|
-
sendFn({
|
|
646
|
-
type: 'backend_event',
|
|
647
|
-
conversationId,
|
|
648
|
-
event: { type: 'error', message: 'Copilot is already processing this conversation' },
|
|
649
|
-
});
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
state.workDir = workDir;
|
|
653
|
-
const providerChanged = !!options?.providerId && state.providerId !== options.providerId;
|
|
654
|
-
if (options?.providerId)
|
|
655
|
-
detachCopilotProviderSession(state, options.providerId);
|
|
656
|
-
const resumeSessionId = providerChanged
|
|
657
|
-
? undefined
|
|
658
|
-
: options?.resumeSessionId
|
|
659
|
-
|| state.copilotSessionId
|
|
660
|
-
|| state.lastCopilotSessionId
|
|
661
|
-
|| undefined;
|
|
662
|
-
const normalized = normalizeCopilotRunOptions(options?.copilotOptions);
|
|
663
|
-
if (normalized.error) {
|
|
664
|
-
sendCopilotSetupError(state, normalized.error);
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
if (normalized.options.model)
|
|
668
|
-
state.model = normalized.options.model;
|
|
669
|
-
if (normalized.options.context)
|
|
670
|
-
state.contextTier = normalized.options.context;
|
|
671
|
-
if (normalized.options.effort)
|
|
672
|
-
state.reasoningEffort = normalized.options.effort;
|
|
673
|
-
state.deferredCommandUsage = null;
|
|
674
|
-
let pendingPlanReview = null;
|
|
675
|
-
const runtime = new CopilotCommandRuntime({
|
|
676
|
-
providerId: state.providerId,
|
|
677
|
-
workDir,
|
|
678
|
-
onSessionEvent: (eventSessionId, event) => {
|
|
679
|
-
if (eventSessionId === state.commandSessionId)
|
|
680
|
-
handleCopilotEvent(state, event);
|
|
681
|
-
},
|
|
682
|
-
onUserInputRequest: command === 'plan'
|
|
683
|
-
? (eventSessionId, request) => requestCopilotPlanInput(state, eventSessionId, request)
|
|
684
|
-
: undefined,
|
|
685
|
-
onExitPlanModeRequest: command === 'plan'
|
|
686
|
-
? (eventSessionId, request) => {
|
|
687
|
-
pendingPlanReview = {
|
|
688
|
-
sessionId: eventSessionId,
|
|
689
|
-
content: request.planContent || request.summary,
|
|
690
|
-
languageHint: `${request.summary}\n${request.planContent || ''}`,
|
|
691
|
-
};
|
|
692
|
-
return acceptCopilotPlanExitWithoutBlocking(state, request);
|
|
693
|
-
}
|
|
694
|
-
: undefined,
|
|
695
|
-
onAcpElicitationRequest: command === 'plan'
|
|
696
|
-
? request => requestCopilotAcpElicitation(state, request)
|
|
697
|
-
: undefined,
|
|
698
|
-
});
|
|
699
|
-
state.commandRuntime = runtime;
|
|
700
|
-
const previousMode = state.copilotMode;
|
|
701
|
-
startCopilotTurn(state);
|
|
702
|
-
const commandTurnId = state.turnId;
|
|
703
|
-
sendFn({
|
|
704
|
-
type: 'backend_event',
|
|
705
|
-
conversationId,
|
|
706
|
-
event: {
|
|
707
|
-
type: 'turn_started',
|
|
708
|
-
session: sessionRef(resumeSessionId || 'pending'),
|
|
709
|
-
},
|
|
710
|
-
});
|
|
711
|
-
if (command === 'compact')
|
|
712
|
-
emitCopilotCompactionStarted(state);
|
|
713
|
-
let sessionId = null;
|
|
714
|
-
try {
|
|
715
|
-
await runtime.start();
|
|
716
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
717
|
-
const sessionOptions = {
|
|
718
|
-
model: state.model || globalModel || undefined,
|
|
719
|
-
contextTier: state.contextTier || undefined,
|
|
720
|
-
reasoningEffort: state.reasoningEffort || undefined,
|
|
721
|
-
};
|
|
722
|
-
try {
|
|
723
|
-
sessionId = await runtime.openSession(resumeSessionId, sessionOptions);
|
|
724
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
725
|
-
}
|
|
726
|
-
catch (error) {
|
|
727
|
-
if (!resumeSessionId || !isMissingCopilotSessionError(error))
|
|
728
|
-
throw error;
|
|
729
|
-
state.copilotSessionId = null;
|
|
730
|
-
state.lastCopilotSessionId = null;
|
|
731
|
-
state.sessionNotified = false;
|
|
732
|
-
sessionId = await runtime.openSession(undefined, sessionOptions);
|
|
733
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
734
|
-
}
|
|
735
|
-
state.commandSessionId = sessionId;
|
|
736
|
-
state.copilotSessionId = sessionId;
|
|
737
|
-
state.lastCopilotSessionId = sessionId;
|
|
738
|
-
await runtime.enableAllowAll(sessionId);
|
|
739
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
740
|
-
if (!state.sessionNotified) {
|
|
741
|
-
state.sessionNotified = true;
|
|
742
|
-
sendFn({
|
|
743
|
-
type: 'backend_event',
|
|
744
|
-
conversationId,
|
|
745
|
-
event: { type: 'session_started', session: sessionRef(sessionId) },
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
const commands = await runtime.listSessionCommands(sessionId);
|
|
749
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
750
|
-
if (!commands.some(value => value.name === command)) {
|
|
751
|
-
throw new Error(`Copilot provider ${state.providerId} does not expose /${command}`);
|
|
752
|
-
}
|
|
753
|
-
if (command === 'plan')
|
|
754
|
-
state.planModePending = true;
|
|
755
|
-
if (command === 'goal')
|
|
756
|
-
state.planModePending = false;
|
|
757
|
-
const result = await runtime.invokeCommand(sessionId, command, input);
|
|
758
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
759
|
-
await applyCopilotCommandResult(state, runtime, sessionId, command, input, result);
|
|
760
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
761
|
-
if (command === 'compact')
|
|
762
|
-
emitCopilotCompactionCompleted(state);
|
|
763
|
-
const reportedMode = await runtime.getMode(sessionId);
|
|
764
|
-
ensureCopilotCommandTurnActive(state, commandTurnId);
|
|
765
|
-
const mode = command === 'plan'
|
|
766
|
-
? (state.planModePending ? 'plan' : reportedMode)
|
|
767
|
-
: command === 'goal'
|
|
768
|
-
? 'autopilot'
|
|
769
|
-
: reportedMode;
|
|
770
|
-
state.planModePending = mode === 'plan';
|
|
771
|
-
setCopilotModeState(state, mode);
|
|
772
|
-
if (command === 'plan') {
|
|
773
|
-
const review = pendingPlanReview || {
|
|
774
|
-
sessionId,
|
|
775
|
-
languageHint: input,
|
|
776
|
-
};
|
|
777
|
-
emitCopilotPlanReview(state, review.sessionId, review.content, review.languageHint);
|
|
778
|
-
pendingPlanReview = null;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
catch (error) {
|
|
782
|
-
pendingPlanReview = null;
|
|
783
|
-
if (command === 'compact')
|
|
784
|
-
state.isCompacting = false;
|
|
785
|
-
if (!resumeSessionId && sessionId && state.copilotSessionId === sessionId) {
|
|
786
|
-
state.copilotSessionId = null;
|
|
787
|
-
state.lastCopilotSessionId = null;
|
|
788
|
-
state.sessionNotified = false;
|
|
789
|
-
}
|
|
790
|
-
if (command === 'plan') {
|
|
791
|
-
state.planModePending = true;
|
|
792
|
-
setCopilotModeState(state, 'plan');
|
|
793
|
-
}
|
|
794
|
-
else if (command === 'goal') {
|
|
795
|
-
setCopilotModeState(state, previousMode);
|
|
796
|
-
}
|
|
797
|
-
if (!(error instanceof CopilotDecisionCancelledError) && state.turnActive) {
|
|
798
|
-
sendFn({
|
|
799
|
-
type: 'backend_event',
|
|
800
|
-
conversationId,
|
|
801
|
-
event: {
|
|
802
|
-
type: 'error',
|
|
803
|
-
message: error instanceof Error ? error.message : String(error),
|
|
804
|
-
},
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
finally {
|
|
809
|
-
if (sessionId)
|
|
810
|
-
await runtime.destroySession(sessionId);
|
|
811
|
-
await runtime.stop();
|
|
812
|
-
state.commandRuntime = null;
|
|
813
|
-
state.commandSessionId = null;
|
|
814
|
-
state.isCompacting = false;
|
|
815
|
-
emitCopilotCommandCompleted(state);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
export async function setCopilotPlanMode(conversationId, enabled, workDir, providerId = 'copilot') {
|
|
819
|
-
let state = conversations.get(conversationId);
|
|
820
|
-
if (!state) {
|
|
821
|
-
createCopilotPlaceholder(conversationId, { providerId });
|
|
822
|
-
state = conversations.get(conversationId);
|
|
823
|
-
}
|
|
824
|
-
if (state.turnActive || state.commandRuntime) {
|
|
825
|
-
throw new Error('Copilot is already processing this conversation');
|
|
826
|
-
}
|
|
827
|
-
state.workDir = workDir;
|
|
828
|
-
detachCopilotProviderSession(state, providerId);
|
|
829
|
-
if (enabled) {
|
|
830
|
-
state.planModePending = true;
|
|
831
|
-
setCopilotModeState(state, 'plan');
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
state.planModePending = false;
|
|
835
|
-
const sessionId = state.copilotSessionId || state.lastCopilotSessionId;
|
|
836
|
-
if (!sessionId) {
|
|
837
|
-
setCopilotModeState(state, 'interactive');
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
const runtime = new CopilotCommandRuntime({ providerId, workDir });
|
|
841
|
-
state.commandRuntime = runtime;
|
|
842
|
-
state.commandSessionId = sessionId;
|
|
843
|
-
let activeSessionId = sessionId;
|
|
844
|
-
try {
|
|
845
|
-
await runtime.start();
|
|
846
|
-
try {
|
|
847
|
-
await runtime.openSession(sessionId);
|
|
848
|
-
}
|
|
849
|
-
catch (error) {
|
|
850
|
-
if (!isMissingCopilotSessionError(error))
|
|
851
|
-
throw error;
|
|
852
|
-
state.copilotSessionId = null;
|
|
853
|
-
state.lastCopilotSessionId = null;
|
|
854
|
-
state.sessionNotified = false;
|
|
855
|
-
activeSessionId = await runtime.openSession();
|
|
856
|
-
state.copilotSessionId = activeSessionId;
|
|
857
|
-
state.lastCopilotSessionId = activeSessionId;
|
|
858
|
-
state.commandSessionId = activeSessionId;
|
|
859
|
-
}
|
|
860
|
-
await runtime.setMode(activeSessionId, 'interactive');
|
|
861
|
-
setCopilotModeState(state, await runtime.getMode(activeSessionId));
|
|
862
|
-
}
|
|
863
|
-
finally {
|
|
864
|
-
await runtime.destroySession(activeSessionId);
|
|
865
|
-
await runtime.stop();
|
|
866
|
-
state.commandRuntime = null;
|
|
867
|
-
state.commandSessionId = null;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
270
|
export function normalizeCopilotRunOptions(input) {
|
|
871
271
|
const options = {};
|
|
872
272
|
if (!input)
|
|
@@ -943,30 +343,26 @@ export function handleCopilotChat(conversationId, text, workDir, options, files)
|
|
|
943
343
|
stateVersion: 0,
|
|
944
344
|
heartbeatTimer: null,
|
|
945
345
|
sessionNotified: false,
|
|
946
|
-
copilotMode: 'interactive',
|
|
947
|
-
planMode: false,
|
|
948
|
-
planModePending: false,
|
|
949
|
-
isCompacting: false,
|
|
950
|
-
commandRuntime: null,
|
|
951
|
-
commandSessionId: null,
|
|
952
|
-
deferredCommandUsage: null,
|
|
953
346
|
};
|
|
954
347
|
conversations.set(conversationId, state);
|
|
955
348
|
}
|
|
956
349
|
state.workDir = workDir;
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
350
|
+
if (options?.providerId) {
|
|
351
|
+
state.providerId = options.providerId;
|
|
352
|
+
}
|
|
960
353
|
// Determine session to resume
|
|
961
|
-
const resumeId =
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|| state.copilotSessionId
|
|
965
|
-
|| state.lastCopilotSessionId;
|
|
354
|
+
const resumeId = options?.resumeSessionId
|
|
355
|
+
|| state.copilotSessionId
|
|
356
|
+
|| state.lastCopilotSessionId;
|
|
966
357
|
if (resumeId) {
|
|
967
358
|
state.copilotSessionId = resumeId;
|
|
968
359
|
state.lastCopilotSessionId = resumeId;
|
|
969
360
|
}
|
|
361
|
+
// For new sessions, pre-assign a sessionId via --session-id so we know it
|
|
362
|
+
// immediately (don't have to wait for the `result` event).
|
|
363
|
+
if (!resumeId) {
|
|
364
|
+
state.copilotSessionId = randomUUID();
|
|
365
|
+
}
|
|
970
366
|
const normalized = normalizeCopilotRunOptions(options?.copilotOptions);
|
|
971
367
|
if (normalized.error) {
|
|
972
368
|
sendCopilotSetupError(state, normalized.error);
|
|
@@ -978,19 +374,6 @@ export function handleCopilotChat(conversationId, text, workDir, options, files)
|
|
|
978
374
|
state.contextTier = normalized.options.context;
|
|
979
375
|
if (normalized.options.effort)
|
|
980
376
|
state.reasoningEffort = normalized.options.effort;
|
|
981
|
-
if (state.planModePending) {
|
|
982
|
-
void executeCopilotCommand(conversationId, 'plan', text, workDir, {
|
|
983
|
-
resumeSessionId: resumeId || undefined,
|
|
984
|
-
providerId: state.providerId,
|
|
985
|
-
copilotOptions: options?.copilotOptions,
|
|
986
|
-
});
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
// For new one-shot sessions, pre-assign a sessionId via --session-id so we
|
|
990
|
-
// know it immediately (the SDK command path creates its own persisted ID).
|
|
991
|
-
if (!resumeId) {
|
|
992
|
-
state.copilotSessionId = randomUUID();
|
|
993
|
-
}
|
|
994
377
|
let processedFiles;
|
|
995
378
|
try {
|
|
996
379
|
processedFiles = processFilesForCopilot(files);
|
|
@@ -1111,20 +494,7 @@ export function handleCopilotChat(conversationId, text, workDir, options, files)
|
|
|
1111
494
|
state.child = null;
|
|
1112
495
|
state.abortController = null;
|
|
1113
496
|
if (state.turnActive) {
|
|
1114
|
-
const missingResumeSession = !!resumeId
|
|
1115
|
-
&& code !== 0
|
|
1116
|
-
&& isMissingCopilotSessionError(stderrBuf);
|
|
1117
497
|
finishCopilotTurn(state);
|
|
1118
|
-
if (missingResumeSession) {
|
|
1119
|
-
state.copilotSessionId = null;
|
|
1120
|
-
state.lastCopilotSessionId = null;
|
|
1121
|
-
state.sessionNotified = false;
|
|
1122
|
-
handleCopilotChat(conversationId, text, workDir, {
|
|
1123
|
-
providerId: state.providerId,
|
|
1124
|
-
copilotOptions: options?.copilotOptions,
|
|
1125
|
-
}, files);
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1128
498
|
// If turn wasn't completed by a result event, emit error or cancellation
|
|
1129
499
|
if (code !== 0 && code !== null) {
|
|
1130
500
|
const errMsg = stderrBuf.trim() || `Copilot process exited with code ${code}`;
|
|
@@ -1202,44 +572,6 @@ function handleCopilotEvent(state, event) {
|
|
|
1202
572
|
}
|
|
1203
573
|
const session = sessionRef(state.copilotSessionId || 'pending');
|
|
1204
574
|
switch (event.type) {
|
|
1205
|
-
case 'session.mode_changed': {
|
|
1206
|
-
const data = event.data;
|
|
1207
|
-
if (data?.newMode) {
|
|
1208
|
-
const mode = data.newMode;
|
|
1209
|
-
if (state.planModePending && mode === 'interactive')
|
|
1210
|
-
break;
|
|
1211
|
-
setCopilotModeState(state, mode);
|
|
1212
|
-
}
|
|
1213
|
-
break;
|
|
1214
|
-
}
|
|
1215
|
-
case 'session.compaction_start': {
|
|
1216
|
-
emitCopilotCompactionStarted(state);
|
|
1217
|
-
break;
|
|
1218
|
-
}
|
|
1219
|
-
case 'session.compaction_complete': {
|
|
1220
|
-
// Native providers can report a compaction boundary before the command
|
|
1221
|
-
// request itself settles. Keep the synthesized row pending until
|
|
1222
|
-
// invokeCommand succeeds so a later rejection cannot leave a false
|
|
1223
|
-
// completed checkmark behind.
|
|
1224
|
-
if (state.commandRuntime && state.isCompacting)
|
|
1225
|
-
break;
|
|
1226
|
-
emitCopilotCompactionCompleted(state);
|
|
1227
|
-
break;
|
|
1228
|
-
}
|
|
1229
|
-
case 'session.compaction_failed': {
|
|
1230
|
-
state.isCompacting = false;
|
|
1231
|
-
const data = event.data;
|
|
1232
|
-
sendFn({
|
|
1233
|
-
type: 'backend_event',
|
|
1234
|
-
conversationId: convId,
|
|
1235
|
-
turnId: state.turnId,
|
|
1236
|
-
event: {
|
|
1237
|
-
type: 'error',
|
|
1238
|
-
message: data?.error || 'Context compaction failed',
|
|
1239
|
-
},
|
|
1240
|
-
});
|
|
1241
|
-
break;
|
|
1242
|
-
}
|
|
1243
575
|
case 'assistant.message_delta': {
|
|
1244
576
|
const data = event.data;
|
|
1245
577
|
if (data?.deltaContent) {
|
|
@@ -1330,15 +662,8 @@ function handleCopilotEvent(state, event) {
|
|
|
1330
662
|
state.copilotSessionId = sessionId;
|
|
1331
663
|
state.lastCopilotSessionId = sessionId;
|
|
1332
664
|
}
|
|
1333
|
-
const usage = buildCopilotUsage(state, event.usage);
|
|
1334
|
-
// Native command turns can remain open after Copilot finishes generating
|
|
1335
|
-
// while AgentLink waits for a required final Plan decision. Their outer
|
|
1336
|
-
// command lifecycle emits the authoritative terminal event.
|
|
1337
|
-
if (state.commandRuntime) {
|
|
1338
|
-
state.deferredCommandUsage = usage;
|
|
1339
|
-
break;
|
|
1340
|
-
}
|
|
1341
665
|
finishCopilotTurn(state);
|
|
666
|
+
const usage = buildCopilotUsage(state, event.usage);
|
|
1342
667
|
// Emit backend_event turn_completed
|
|
1343
668
|
sendFn({
|
|
1344
669
|
type: 'backend_event',
|
|
@@ -1366,23 +691,10 @@ function handleCopilotEvent(state, event) {
|
|
|
1366
691
|
// ── Cancel ──────────────────────────────────────────────────────────────────
|
|
1367
692
|
export function cancelCopilotExecution(conversationId) {
|
|
1368
693
|
const state = conversations.get(conversationId);
|
|
1369
|
-
cancelPendingCopilotDecisions(conversationId);
|
|
1370
694
|
if (!state) {
|
|
1371
695
|
emitCopilotCancelled(conversationId);
|
|
1372
696
|
return;
|
|
1373
697
|
}
|
|
1374
|
-
state.deferredCommandUsage = null;
|
|
1375
|
-
if (state.commandRuntime && state.commandSessionId) {
|
|
1376
|
-
const cancellation = state.isCompacting
|
|
1377
|
-
? state.commandRuntime.abortManualCompaction(state.commandSessionId)
|
|
1378
|
-
: state.commandRuntime.abort(state.commandSessionId);
|
|
1379
|
-
state.isCompacting = false;
|
|
1380
|
-
void cancellation.catch(() => { });
|
|
1381
|
-
if (state.turnActive)
|
|
1382
|
-
finishCopilotTurn(state);
|
|
1383
|
-
emitCopilotCancelled(conversationId, state);
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
698
|
if (!state.child) {
|
|
1387
699
|
if (state.turnActive) {
|
|
1388
700
|
finishCopilotTurn(state);
|
|
@@ -1411,7 +723,7 @@ function cleanupCopilotConversation(conversationId) {
|
|
|
1411
723
|
const state = conversations.get(conversationId);
|
|
1412
724
|
if (!state)
|
|
1413
725
|
return;
|
|
1414
|
-
if (state.child
|
|
726
|
+
if (state.child) {
|
|
1415
727
|
cancelCopilotExecution(conversationId);
|
|
1416
728
|
}
|
|
1417
729
|
if (state.heartbeatTimer) {
|
|
@@ -1722,37 +1034,6 @@ export function readCopilotSessionMessages(_workDir, sessionId) {
|
|
|
1722
1034
|
}
|
|
1723
1035
|
return messages;
|
|
1724
1036
|
}
|
|
1725
|
-
export function readCopilotSessionMode(sessionId) {
|
|
1726
|
-
const sessionRoot = resolve(COPILOT_SESSION_DIR);
|
|
1727
|
-
const sessionDir = resolve(COPILOT_SESSION_DIR, sessionId);
|
|
1728
|
-
if (sessionDir === sessionRoot || !sessionDir.startsWith(`${sessionRoot}${process.platform === 'win32' ? '\\' : '/'}`)) {
|
|
1729
|
-
return 'interactive';
|
|
1730
|
-
}
|
|
1731
|
-
const eventsPath = join(sessionDir, 'events.jsonl');
|
|
1732
|
-
if (!existsSync(eventsPath))
|
|
1733
|
-
return 'interactive';
|
|
1734
|
-
let mode = 'interactive';
|
|
1735
|
-
try {
|
|
1736
|
-
for (const line of readFileSync(eventsPath, 'utf-8').split('\n')) {
|
|
1737
|
-
if (!line.trim())
|
|
1738
|
-
continue;
|
|
1739
|
-
try {
|
|
1740
|
-
const event = JSON.parse(line);
|
|
1741
|
-
const nextMode = event.type === 'session.mode_changed' ? event.data?.newMode : undefined;
|
|
1742
|
-
if (nextMode === 'interactive' || nextMode === 'plan' || nextMode === 'autopilot') {
|
|
1743
|
-
mode = nextMode;
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
catch {
|
|
1747
|
-
// Ignore partial or malformed history lines.
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
catch {
|
|
1752
|
-
return 'interactive';
|
|
1753
|
-
}
|
|
1754
|
-
return mode;
|
|
1755
|
-
}
|
|
1756
1037
|
export function getCopilotSessionWorkDir(sessionId) {
|
|
1757
1038
|
const sessionRoot = resolve(COPILOT_SESSION_DIR);
|
|
1758
1039
|
const sessionDir = resolve(COPILOT_SESSION_DIR, sessionId);
|