@agent-link/agent 0.1.338 → 0.1.340
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 +58 -0
- package/dist/backends/types.js +15 -4
- package/dist/backends/types.js.map +1 -1
- package/dist/connection.d.ts +2 -1
- package/dist/connection.js +279 -48
- package/dist/connection.js.map +1 -1
- package/dist/copilot-capabilities-offline.d.ts +254 -0
- package/dist/copilot-capabilities-offline.js +392 -0
- package/dist/copilot-capabilities-offline.js.map +1 -0
- package/dist/copilot-capabilities.d.ts +43 -0
- package/dist/copilot-capabilities.js +128 -0
- package/dist/copilot-capabilities.js.map +1 -0
- package/dist/copilot-capability-service.d.ts +24 -0
- package/dist/copilot-capability-service.js +116 -0
- package/dist/copilot-capability-service.js.map +1 -0
- package/dist/copilot-command-bridge.d.ts +184 -0
- package/dist/copilot-command-bridge.js +1800 -0
- package/dist/copilot-command-bridge.js.map +1 -0
- package/dist/copilot-compaction-barrier.d.ts +36 -0
- package/dist/copilot-compaction-barrier.js +152 -0
- package/dist/copilot-compaction-barrier.js.map +1 -0
- package/dist/copilot-compaction-lifecycle.d.ts +34 -0
- package/dist/copilot-compaction-lifecycle.js +88 -0
- package/dist/copilot-compaction-lifecycle.js.map +1 -0
- package/dist/copilot-compaction-policy.d.ts +5 -0
- package/dist/copilot-compaction-policy.js +7 -0
- package/dist/copilot-compaction-policy.js.map +1 -0
- package/dist/copilot-configuration.d.ts +63 -0
- package/dist/copilot-configuration.js +283 -0
- package/dist/copilot-configuration.js.map +1 -0
- package/dist/copilot-event-scope.d.ts +13 -0
- package/dist/copilot-event-scope.js +26 -0
- package/dist/copilot-event-scope.js.map +1 -0
- package/dist/copilot-fork-metadata.d.ts +9 -0
- package/dist/copilot-fork-metadata.js +86 -0
- package/dist/copilot-fork-metadata.js.map +1 -0
- package/dist/copilot-message-kind.d.ts +21 -0
- package/dist/copilot-message-kind.js +45 -0
- package/dist/copilot-message-kind.js.map +1 -0
- package/dist/copilot-plan-state.d.ts +19 -0
- package/dist/copilot-plan-state.js +180 -0
- package/dist/copilot-plan-state.js.map +1 -0
- package/dist/copilot-request-dispatch.d.ts +10 -0
- package/dist/copilot-request-dispatch.js +31 -0
- package/dist/copilot-request-dispatch.js.map +1 -0
- package/dist/copilot-review-documents.d.ts +17 -0
- package/dist/copilot-review-documents.js +162 -0
- package/dist/copilot-review-documents.js.map +1 -0
- package/dist/copilot-session-activity.d.ts +4 -0
- package/dist/copilot-session-activity.js +185 -0
- package/dist/copilot-session-activity.js.map +1 -0
- package/dist/copilot-sidecar.d.ts +2 -0
- package/dist/copilot-sidecar.js +17 -0
- package/dist/copilot-sidecar.js.map +1 -0
- package/dist/copilot-turn-journal.d.ts +36 -0
- package/dist/copilot-turn-journal.js +246 -0
- package/dist/copilot-turn-journal.js.map +1 -0
- package/dist/copilot-user-requests.d.ts +11 -0
- package/dist/copilot-user-requests.js +84 -0
- package/dist/copilot-user-requests.js.map +1 -0
- package/dist/copilot.d.ts +202 -9
- package/dist/copilot.js +5120 -243
- package/dist/copilot.js.map +1 -1
- package/dist/file-cache.d.ts +6 -1
- package/dist/file-cache.js +62 -1
- package/dist/file-cache.js.map +1 -1
- package/dist/history-message-transform.d.ts +47 -1
- package/dist/history-message-transform.js +67 -0
- package/dist/history-message-transform.js.map +1 -1
- package/dist/history.d.ts +8 -0
- package/dist/history.js.map +1 -1
- package/dist/provider-command-discovery.d.ts +5 -0
- package/dist/provider-command-discovery.js +49 -1
- package/dist/provider-command-discovery.js.map +1 -1
- package/dist/provider-command-types.d.ts +5 -0
- package/dist/provider-command-types.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/dist/search-sessions.js +2 -1
- package/dist/search-sessions.js.map +1 -1
- package/package.json +3 -1
package/dist/connection.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
|
+
import { setCopilotProvider } from './runtime.js';
|
|
3
|
+
import { dispatchCopilotRequest, resolveCopilotCommandResumeSessionId } from './copilot-request-dispatch.js';
|
|
4
|
+
import { COPILOT_COMPACTION_POLICY } from './copilot-compaction-policy.js';
|
|
2
5
|
import os from 'os';
|
|
3
6
|
import path from 'path';
|
|
4
7
|
import { performance } from 'perf_hooks';
|
|
@@ -29,12 +32,15 @@ const require = createRequire(import.meta.url);
|
|
|
29
32
|
const pkg = require('../package.json');
|
|
30
33
|
const TEAM_SESSIONS_DIR = path.join(CONFIG_DIR, 'team-sessions');
|
|
31
34
|
const LOOP_SESSIONS_DIR = path.join(CONFIG_DIR, 'loop-sessions');
|
|
32
|
-
import { handleChat as claudeHandleChat, addSendFn, abort as abortClaude, abortAll as abortAllClaude, cancelExecution as claudeCancelExecution, handleUserAnswer, handleToolPermissionResponse, handleBtwQuestion, getConversation, getConversations, getIsCompacting, clearSessionId, evictByClaudeSessionId, rebindConversation, addOutputObserver, removeOutputObserver, addCloseObserver, removeCloseObserver, setOutputObserver, clearOutputObserver, setCloseObserver, clearCloseObserver, restartConversation, createPlaceholderConversation, getPendingQuestions, getPendingToolPermissions, setModel, addOnSessionStarted, getBackendType, setBackendType, isCopilotBackend, isCodexBackend, isPiBackend, isYeaftBackend, addCopilotSendFn, handleCopilotChat,
|
|
35
|
+
import { handleChat as claudeHandleChat, addSendFn, abort as abortClaude, abortAll as abortAllClaude, cancelExecution as claudeCancelExecution, handleUserAnswer, handleToolPermissionResponse, handleBtwQuestion, getConversation, getConversations, getIsCompacting, clearSessionId, evictByClaudeSessionId, rebindConversation, addOutputObserver, removeOutputObserver, addCloseObserver, removeCloseObserver, setOutputObserver, clearOutputObserver, setCloseObserver, clearCloseObserver, restartConversation, createPlaceholderConversation, getPendingQuestions, getPendingToolPermissions, setModel, addOnSessionStarted, getBackendType, setBackendType, isCopilotBackend, isCodexBackend, isPiBackend, isYeaftBackend, addCopilotSendFn, handleCopilotChat, executeCopilotCommand, setCopilotPlanMode, startCopilotPlanImplementation, cancelCopilotExecution, readCopilotSessionMessages, readCopilotSessionMode, readCopilotSessionProvider, readCopilotSessionUsage, getCopilotSessionWorkDir, deleteCopilotSession, renameCopilotSession, moveCopilotSession, getCopilotConversation, getCopilotConversations, createCopilotPlaceholder, rebindCopilotConversation, getPendingCopilotDecisions, handleCopilotUserAnswer, cleanupAllCopilotConversations, copilotCapabilities, addCodexSendFn, handleCodexChat, cancelCodexExecution, listCodexSessionsPage, listAllCodexSessionsPage, readCodexSessionMessages, deleteCodexSession, renameCodexSession, moveCodexSession, getCodexConversation, getCodexConversations, createCodexPlaceholder, restartCodexConversation, rebindCodexConversation, cleanupAllCodexConversations, evictByCodexSessionId, codexCapabilities } from './runtime.js';
|
|
33
36
|
import { addPiSendFn, cancelPiExecution, cleanupAllPiConversations, createPiPlaceholder, deletePiSession, evictByPiSessionId, getPiConversation, getPiConversations, getPiSessionWorkDir, handlePiChat, listAllPiSessionsPage, listPiSessionsPage, movePiSession, piCapabilities, readPiSessionMessages, rebindPiConversation, renamePiSession, restartPiConversation, } from './pi.js';
|
|
34
37
|
import { addYeaftSendFn, cancelYeaftExecution, cleanupAllYeaftConversations, createYeaftPlaceholder, deleteYeaftSession, evictByYeaftSessionId, getYeaftConversation, getYeaftConversations, handleYeaftChat, handleYeaftUserAnswer, listAllYeaftSessionsPage, listYeaftSessionsPage, moveYeaftSession, readYeaftSessionMessages, rebindYeaftConversation, renameYeaftSession, restartYeaftConversation, } from './yeaft.js';
|
|
35
38
|
import { DEFAULT_SESSION_LIST_LIMIT, LEGACY_SESSION_LIST_CAP, listSessions, listSessionsPageAsync, readSessionMessages, deleteSession, renameSession, moveSession, listAllRecentSessionsPage, getSessionCacheStats } from './history.js';
|
|
36
39
|
import { discoverProviderCommands, providerCommandDiscoverers, } from './provider-command-discovery.js';
|
|
37
40
|
import { providerCommandsMessage } from './provider-command-types.js';
|
|
41
|
+
import { copilotCapabilityService } from './copilot-capability-service.js';
|
|
42
|
+
import { mergeCopilotCapabilities } from './copilot-capabilities.js';
|
|
43
|
+
const copilotCapabilityRequests = new Map();
|
|
38
44
|
import { claudeCapabilities, yeaftCapabilities } from './backends/types.js';
|
|
39
45
|
import { createSessionSearchHandler } from './session-search-handler.js';
|
|
40
46
|
import { formatSessionExport, isSessionExportFormat } from './session-export.js';
|
|
@@ -47,6 +53,7 @@ import { initScheduler, getRunningExecutions, } from './scheduler.js';
|
|
|
47
53
|
import { handleWebUpgradeMessage } from './web-upgrade.js';
|
|
48
54
|
import { refreshRuntimeLaunchSpecDeviceName } from './upgrade-launch-spec.js';
|
|
49
55
|
import { getAgentRuntimeInfo } from './runtime-info.js';
|
|
56
|
+
import { listCopilotSessionsPageAsync, listAllCopilotSessionsPageAsync } from './runtime.js';
|
|
50
57
|
let tunnelHandler = null;
|
|
51
58
|
let terminalManager = null;
|
|
52
59
|
const RECONNECT_BASE_DELAY = 1000;
|
|
@@ -103,6 +110,9 @@ function capabilitiesForBackend(backend, providerId) {
|
|
|
103
110
|
})();
|
|
104
111
|
if (!providerId)
|
|
105
112
|
return capabilities;
|
|
113
|
+
if (backend === 'copilot' && ['copilot', 'agency-copilot'].includes(providerId)) {
|
|
114
|
+
capabilities.copilotOptions.catalog = mergeCopilotCapabilities(providerId);
|
|
115
|
+
}
|
|
106
116
|
return {
|
|
107
117
|
...capabilities,
|
|
108
118
|
commands: {
|
|
@@ -638,10 +648,13 @@ function deduped(key, fn) {
|
|
|
638
648
|
});
|
|
639
649
|
inflightHandlers.set(key, run());
|
|
640
650
|
}
|
|
641
|
-
export function send(msg) {
|
|
651
|
+
export function send(msg, expectedSocket) {
|
|
652
|
+
if (expectedSocket !== undefined && state.ws !== expectedSocket)
|
|
653
|
+
return;
|
|
642
654
|
if (state.ws && state.ws.readyState === WebSocket.OPEN) {
|
|
643
655
|
sendQueue = sendQueue.then(async () => {
|
|
644
|
-
if (!state.ws || state.ws.readyState !== WebSocket.OPEN
|
|
656
|
+
if (!state.ws || state.ws.readyState !== WebSocket.OPEN
|
|
657
|
+
|| (expectedSocket !== undefined && state.ws !== expectedSocket))
|
|
645
658
|
return;
|
|
646
659
|
let payload = msg;
|
|
647
660
|
// Don't E2E-encrypt messages the server must inspect (it only decrypts
|
|
@@ -662,6 +675,8 @@ export function send(msg) {
|
|
|
662
675
|
if (state.e2eKey && state.e2eNegotiated && !skipE2E.includes(msg.type)) {
|
|
663
676
|
payload = await e2eEncrypt(msg, state.e2eKey);
|
|
664
677
|
}
|
|
678
|
+
if (expectedSocket !== undefined && state.ws !== expectedSocket)
|
|
679
|
+
return;
|
|
665
680
|
return encryptAndSend(state.ws, payload, state.sessionKey);
|
|
666
681
|
}).catch((err) => {
|
|
667
682
|
console.error(`[AgentLink] send() failed for ${msg.type}:`, err.message);
|
|
@@ -981,17 +996,48 @@ function handleServerMessage(msg) {
|
|
|
981
996
|
}
|
|
982
997
|
else if (isCopilotBackend()) {
|
|
983
998
|
const copilotMsg = msg;
|
|
984
|
-
handleCopilotChat(chatConvId || '', copilotMsg.prompt, chatDir, {
|
|
999
|
+
void dispatchCopilotRequest({ conversationId: chatConvId || '', providerId: effectiveProviderId }, () => handleCopilotChat(chatConvId || '', copilotMsg.prompt, chatDir, {
|
|
985
1000
|
resumeSessionId: chatOptions.resumeSessionId,
|
|
986
1001
|
providerId: effectiveProviderId,
|
|
987
1002
|
copilotOptions: copilotMsg.copilotOptions,
|
|
988
|
-
|
|
1003
|
+
forkSourceSessionId: copilotMsg.forkSourceSessionId,
|
|
1004
|
+
}, copilotMsg.files), send, () => getCopilotConversation(chatConvId || '')?.turnActive === true);
|
|
989
1005
|
}
|
|
990
1006
|
else {
|
|
991
1007
|
claudeHandleChat(chatConvId, msg.prompt, chatDir, chatOptions, msg.files);
|
|
992
1008
|
}
|
|
993
1009
|
break;
|
|
994
1010
|
}
|
|
1011
|
+
case 'get_copilot_capabilities': {
|
|
1012
|
+
const m = msg;
|
|
1013
|
+
if (typeof m.requestId !== 'string' || !m.requestId || m.requestId.length > 200)
|
|
1014
|
+
break;
|
|
1015
|
+
if (m.cancel) {
|
|
1016
|
+
copilotCapabilityRequests.get(m.requestId)?.abort();
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
if (!['copilot', 'agency-copilot'].includes(m.providerId) || copilotCapabilityRequests.has(m.requestId))
|
|
1020
|
+
break;
|
|
1021
|
+
const scope = { providerId: m.providerId, workDir: path.resolve(m.workDir || state.workDir) };
|
|
1022
|
+
const controller = new AbortController();
|
|
1023
|
+
copilotCapabilityRequests.set(m.requestId, controller);
|
|
1024
|
+
send({ type: 'copilot_capabilities', requestId: m.requestId, providerId: m.providerId,
|
|
1025
|
+
workDir: scope.workDir, loading: true, catalog: copilotCapabilityService.snapshot(scope) });
|
|
1026
|
+
void copilotCapabilityService.query(scope, controller.signal).then(catalog => {
|
|
1027
|
+
if (!controller.signal.aborted)
|
|
1028
|
+
send({ type: 'copilot_capabilities', requestId: m.requestId,
|
|
1029
|
+
providerId: m.providerId, workDir: scope.workDir, loading: false, catalog });
|
|
1030
|
+
}).catch(error => {
|
|
1031
|
+
if (!controller.signal.aborted)
|
|
1032
|
+
send({ type: 'copilot_capabilities', requestId: m.requestId,
|
|
1033
|
+
providerId: m.providerId, workDir: scope.workDir, loading: false,
|
|
1034
|
+
catalog: { ...copilotCapabilityService.snapshot(scope), error: String(error) } });
|
|
1035
|
+
}).finally(() => {
|
|
1036
|
+
if (copilotCapabilityRequests.get(m.requestId) === controller)
|
|
1037
|
+
copilotCapabilityRequests.delete(m.requestId);
|
|
1038
|
+
});
|
|
1039
|
+
break;
|
|
1040
|
+
}
|
|
995
1041
|
case 'get_provider_commands': {
|
|
996
1042
|
const m = msg;
|
|
997
1043
|
if (typeof m.requestId !== 'string' || !m.requestId.trim()
|
|
@@ -1014,6 +1060,7 @@ function handleServerMessage(msg) {
|
|
|
1014
1060
|
providerId: m.providerId,
|
|
1015
1061
|
conversationId: m.conversationId,
|
|
1016
1062
|
workDir: path.resolve(m.workDir),
|
|
1063
|
+
...(m.copilotOptions ? { copilotOptions: m.copilotOptions } : {}),
|
|
1017
1064
|
};
|
|
1018
1065
|
void discoverProviderCommands(scope)
|
|
1019
1066
|
.then(result => send(providerCommandsMessage(scope, result)))
|
|
@@ -1023,6 +1070,32 @@ function handleServerMessage(msg) {
|
|
|
1023
1070
|
})));
|
|
1024
1071
|
break;
|
|
1025
1072
|
}
|
|
1073
|
+
case 'execute_provider_command': {
|
|
1074
|
+
const m = msg;
|
|
1075
|
+
const conversation = m.conversationId
|
|
1076
|
+
? getCopilotConversation(m.conversationId)
|
|
1077
|
+
: undefined;
|
|
1078
|
+
const providerId = m.providerId || conversation?.providerId || 'copilot';
|
|
1079
|
+
const command = m.command?.trim().toLowerCase();
|
|
1080
|
+
if (!m.conversationId
|
|
1081
|
+
|| getBackendTypeForProvider(providerId) !== 'copilot'
|
|
1082
|
+
|| !['compact', 'plan', 'goal'].includes(command || '')) {
|
|
1083
|
+
send({
|
|
1084
|
+
type: 'error',
|
|
1085
|
+
conversationId: m.conversationId,
|
|
1086
|
+
message: 'Invalid Copilot provider command request',
|
|
1087
|
+
});
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
const workDir = path.resolve(m.workDir || conversation?.workDir || state.workDir);
|
|
1091
|
+
const conversationId = m.conversationId;
|
|
1092
|
+
void dispatchCopilotRequest({ conversationId, providerId }, () => executeCopilotCommand(conversationId, command, typeof m.input === 'string' ? m.input : '', workDir, {
|
|
1093
|
+
providerId,
|
|
1094
|
+
resumeSessionId: resolveCopilotCommandResumeSessionId(m.resumeSessionId, conversation),
|
|
1095
|
+
copilotOptions: m.copilotOptions,
|
|
1096
|
+
}), send, () => getCopilotConversation(conversationId)?.turnActive === true);
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1026
1099
|
case 'cancel_execution':
|
|
1027
1100
|
{
|
|
1028
1101
|
const conversationId = msg.conversationId || '';
|
|
@@ -1174,18 +1247,7 @@ function handleServerMessage(msg) {
|
|
|
1174
1247
|
});
|
|
1175
1248
|
}
|
|
1176
1249
|
else if (recentBackend === 'copilot') {
|
|
1177
|
-
|
|
1178
|
-
console.log(`[AgentLink] → recent_sessions_list (${page.sessions.length} copilot sessions) hasMore=${page.hasMore}`);
|
|
1179
|
-
send({
|
|
1180
|
-
type: 'recent_sessions_list',
|
|
1181
|
-
sessions: page.sessions,
|
|
1182
|
-
hasMore: page.hasMore,
|
|
1183
|
-
nextCursor: page.nextCursor,
|
|
1184
|
-
indexState: page.indexState,
|
|
1185
|
-
...(m.mode ? { mode: m.mode } : {}),
|
|
1186
|
-
...(m.requestId ? { requestId: m.requestId } : {}),
|
|
1187
|
-
...(m.providerId ? { providerId: m.providerId } : {}),
|
|
1188
|
-
});
|
|
1250
|
+
void handleListRecentCopilotSessions(m);
|
|
1189
1251
|
}
|
|
1190
1252
|
else {
|
|
1191
1253
|
handleListRecentSessions(m);
|
|
@@ -1418,6 +1480,10 @@ function handleServerMessage(msg) {
|
|
|
1418
1480
|
}
|
|
1419
1481
|
// Route to copilot backend
|
|
1420
1482
|
if (resumeBackend === 'copilot') {
|
|
1483
|
+
const liveOwner = [...getCopilotConversations().values()].find(conv => conv.copilotSessionId === m.claudeSessionId || conv.lastCopilotSessionId === m.claudeSessionId);
|
|
1484
|
+
const resumeProviderId = liveOwner?.providerId || readCopilotSessionProvider(m.claudeSessionId)
|
|
1485
|
+
|| m.providerId
|
|
1486
|
+
|| 'copilot';
|
|
1421
1487
|
// Copilot rewrites workspace.yaml using the cwd of each resume process.
|
|
1422
1488
|
// Recover the persisted cwd before creating a placeholder so a reconnect
|
|
1423
1489
|
// that omits workDir cannot silently move the session to the agent cwd.
|
|
@@ -1426,35 +1492,101 @@ function handleServerMessage(msg) {
|
|
|
1426
1492
|
|| state.workDir;
|
|
1427
1493
|
// Rebind running copilot process to new conversationId (handles page refresh)
|
|
1428
1494
|
if (convId) {
|
|
1495
|
+
const existing = getCopilotConversation(convId);
|
|
1496
|
+
const boundSession = existing?.copilotSessionId || existing?.lastCopilotSessionId;
|
|
1497
|
+
if (existing && (boundSession || existing.turnActive) && boundSession !== m.claudeSessionId) {
|
|
1498
|
+
send({ type: 'error', operation: 'resume_conversation', conversationId: convId,
|
|
1499
|
+
providerId: resumeProviderId, claudeSessionId: m.claudeSessionId,
|
|
1500
|
+
message: 'Copilot conversation/session mismatch. Reopen the intended conversation.' });
|
|
1501
|
+
break;
|
|
1502
|
+
}
|
|
1429
1503
|
rebindCopilotConversation(m.claudeSessionId, convId);
|
|
1430
1504
|
}
|
|
1431
1505
|
// Create a copilot placeholder so follow-up chats auto-resume
|
|
1432
1506
|
if (convId) {
|
|
1433
|
-
createCopilotPlaceholder(convId, { providerId:
|
|
1507
|
+
createCopilotPlaceholder(convId, { providerId: resumeProviderId });
|
|
1434
1508
|
const placeholder = getCopilotConversation(convId);
|
|
1435
1509
|
if (placeholder) {
|
|
1510
|
+
placeholder.providerId = resumeProviderId;
|
|
1436
1511
|
placeholder.copilotSessionId = m.claudeSessionId;
|
|
1437
1512
|
placeholder.workDir = resolvedWorkDir;
|
|
1438
1513
|
}
|
|
1439
1514
|
}
|
|
1440
1515
|
const history = readCopilotSessionMessages(resolvedWorkDir, m.claudeSessionId);
|
|
1516
|
+
const persistedCopilotMode = readCopilotSessionMode(m.claudeSessionId);
|
|
1517
|
+
const persistedCopilotUsage = readCopilotSessionUsage(m.claudeSessionId);
|
|
1441
1518
|
// Check if this session has an active copilot process running
|
|
1442
1519
|
const copilotState = getCopilotConversation(convId || '');
|
|
1443
|
-
|
|
1520
|
+
// A live persistent Runtime can be newer than events.jsonl while the
|
|
1521
|
+
// Provider is flushing its final usage checkpoint. Do not replace its
|
|
1522
|
+
// authoritative in-memory value merely because the page was reopened.
|
|
1523
|
+
const restoredCopilotUsage = copilotState?.lastUsage || persistedCopilotUsage;
|
|
1524
|
+
const isCopilotProcessing = !!copilotState?.turnActive;
|
|
1525
|
+
if (copilotState && !isCopilotProcessing) {
|
|
1526
|
+
copilotState.copilotMode = persistedCopilotMode;
|
|
1527
|
+
copilotState.planMode = persistedCopilotMode === 'plan';
|
|
1528
|
+
copilotState.planModePending = persistedCopilotMode === 'plan';
|
|
1529
|
+
copilotState.lastUsage = restoredCopilotUsage;
|
|
1530
|
+
copilotState.configuration = restoredCopilotUsage?.configuration;
|
|
1531
|
+
copilotState.model = restoredCopilotUsage?.configuration?.requested.model || restoredCopilotUsage?.model || null;
|
|
1532
|
+
const requestedContext = restoredCopilotUsage?.configuration?.requested.context || restoredCopilotUsage?.contextTier;
|
|
1533
|
+
const requestedEffort = restoredCopilotUsage?.configuration?.requested.effort || restoredCopilotUsage?.reasoningEffort;
|
|
1534
|
+
copilotState.contextTier = requestedContext === 'default' || requestedContext === 'long_context'
|
|
1535
|
+
? requestedContext
|
|
1536
|
+
: null;
|
|
1537
|
+
copilotState.reasoningEffort = [
|
|
1538
|
+
'none', 'low', 'medium', 'high', 'xhigh', 'max',
|
|
1539
|
+
].includes(requestedEffort || '')
|
|
1540
|
+
? requestedEffort
|
|
1541
|
+
: null;
|
|
1542
|
+
copilotState.autoCompactionPending = !!restoredCopilotUsage
|
|
1543
|
+
&& Number.isFinite(restoredCopilotUsage.contextTokens)
|
|
1544
|
+
&& Number.isFinite(restoredCopilotUsage.contextWindow)
|
|
1545
|
+
&& Number(restoredCopilotUsage.contextWindow) > 0
|
|
1546
|
+
&& Number(restoredCopilotUsage.contextTokens) / Number(restoredCopilotUsage.contextWindow) >= COPILOT_COMPACTION_POLICY.preRequest;
|
|
1547
|
+
}
|
|
1444
1548
|
console.log(`[AgentLink] → conversation_resumed (${history.length} copilot messages, session ${m.claudeSessionId.slice(0, 8)})`);
|
|
1445
1549
|
send({
|
|
1446
1550
|
type: 'conversation_resumed',
|
|
1447
1551
|
conversationId: convId,
|
|
1448
1552
|
claudeSessionId: m.claudeSessionId,
|
|
1449
1553
|
history,
|
|
1450
|
-
isCompacting: false,
|
|
1554
|
+
isCompacting: copilotState?.isCompacting || false,
|
|
1555
|
+
compactions: [...(copilotState?.compactionRecords?.values() || [])].slice(-8),
|
|
1451
1556
|
isProcessing: isCopilotProcessing,
|
|
1452
|
-
|
|
1557
|
+
turnId: copilotState?.turnId,
|
|
1558
|
+
stateVersion: copilotState?.stateVersion,
|
|
1559
|
+
planMode: copilotState?.planMode ?? (persistedCopilotMode === 'plan'),
|
|
1560
|
+
copilotMode: copilotState?.copilotMode || persistedCopilotMode,
|
|
1453
1561
|
permissionMode: 'normal',
|
|
1454
1562
|
workDir: resolvedWorkDir,
|
|
1563
|
+
providerId: resumeProviderId,
|
|
1564
|
+
...(restoredCopilotUsage ? { usage: restoredCopilotUsage } : {}),
|
|
1455
1565
|
...(typeof m.scrollToMessageIdx === 'number' ? { scrollToMessageIdx: m.scrollToMessageIdx } : {}),
|
|
1456
1566
|
...(m.searchQuery ? { searchQuery: m.searchQuery } : {}),
|
|
1457
1567
|
});
|
|
1568
|
+
// Seed the resolved history Provider's offline capabilities without
|
|
1569
|
+
// creating a metadata Runtime just because history was opened.
|
|
1570
|
+
send({ type: 'backend_info', conversationId: convId, backend: 'copilot', providerId: resumeProviderId,
|
|
1571
|
+
capabilities: capabilitiesForBackend('copilot', resumeProviderId) });
|
|
1572
|
+
if (convId && isCopilotProcessing) {
|
|
1573
|
+
for (const pending of getPendingCopilotDecisions(convId)) {
|
|
1574
|
+
send({
|
|
1575
|
+
type: 'backend_event',
|
|
1576
|
+
conversationId: convId,
|
|
1577
|
+
providerId: pending.providerId,
|
|
1578
|
+
claudeSessionId: pending.sessionId,
|
|
1579
|
+
turnId: copilotState?.turnId,
|
|
1580
|
+
stateVersion: copilotState?.stateVersion,
|
|
1581
|
+
event: {
|
|
1582
|
+
type: 'user_input_requested',
|
|
1583
|
+
session: { backendType: 'copilot', backendSessionId: pending.sessionId },
|
|
1584
|
+
requestId: pending.requestId,
|
|
1585
|
+
request: pending.request,
|
|
1586
|
+
},
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1458
1590
|
break;
|
|
1459
1591
|
}
|
|
1460
1592
|
if (!convId) {
|
|
@@ -1553,10 +1685,22 @@ function handleServerMessage(msg) {
|
|
|
1553
1685
|
}
|
|
1554
1686
|
case 'ask_user_answer': {
|
|
1555
1687
|
const m = msg;
|
|
1556
|
-
|
|
1688
|
+
const copilotConversation = m.conversationId
|
|
1689
|
+
? getCopilotConversation(m.conversationId)
|
|
1690
|
+
: undefined;
|
|
1691
|
+
if (copilotConversation
|
|
1692
|
+
|| (m.providerId && getBackendTypeForProvider(m.providerId) === 'copilot')) {
|
|
1693
|
+
handleCopilotUserAnswer(m.requestId, m.answers, {
|
|
1694
|
+
conversationId: m.conversationId,
|
|
1695
|
+
providerId: m.providerId,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
else if (isYeaftBackend()) {
|
|
1557
1699
|
handleYeaftUserAnswer(m.requestId, m.answers, { conversationId: m.conversationId });
|
|
1558
|
-
|
|
1700
|
+
}
|
|
1701
|
+
else {
|
|
1559
1702
|
handleUserAnswer(m.requestId, m.answers);
|
|
1703
|
+
}
|
|
1560
1704
|
break;
|
|
1561
1705
|
}
|
|
1562
1706
|
case 'delete_session': {
|
|
@@ -1621,9 +1765,19 @@ function handleServerMessage(msg) {
|
|
|
1621
1765
|
handleListCodexSessionsFn(dir);
|
|
1622
1766
|
}
|
|
1623
1767
|
else if (sessionBackend === 'copilot') {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1768
|
+
void deleteCopilotSession(dir, m.sessionId).then(ok => {
|
|
1769
|
+
console.log(`[AgentLink] delete copilot session ${m.sessionId}: ${ok}`);
|
|
1770
|
+
if (ok)
|
|
1771
|
+
send({ type: 'session_deleted', sessionId: m.sessionId, providerId: m.providerId, workDir: dir });
|
|
1772
|
+
else
|
|
1773
|
+
send({ type: 'error', operation: 'delete_session', sessionId: m.sessionId,
|
|
1774
|
+
message: 'Session not found or could not be deleted.' });
|
|
1775
|
+
handleListCopilotSessionsFn(dir);
|
|
1776
|
+
}).catch(error => {
|
|
1777
|
+
send({ type: 'error', operation: 'delete_session', sessionId: m.sessionId,
|
|
1778
|
+
message: String(error.message || error) });
|
|
1779
|
+
handleListCopilotSessionsFn(dir);
|
|
1780
|
+
});
|
|
1627
1781
|
}
|
|
1628
1782
|
else {
|
|
1629
1783
|
handleDeleteSession(m.sessionId, dir);
|
|
@@ -1856,10 +2010,12 @@ function handleServerMessage(msg) {
|
|
|
1856
2010
|
const sessionId = conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null;
|
|
1857
2011
|
if (conv.turnActive) {
|
|
1858
2012
|
active.push({
|
|
2013
|
+
providerId: conv.providerId, workDir: conv.workDir,
|
|
1859
2014
|
conversationId: convId,
|
|
1860
2015
|
claudeSessionId: sessionId,
|
|
1861
2016
|
isProcessing: true,
|
|
1862
2017
|
isCompacting: !!conv.isCompacting,
|
|
2018
|
+
...(conv.compactionRecords ? { compactions: [...conv.compactionRecords.values()].slice(-8) } : {}),
|
|
1863
2019
|
turnId: conv.turnId,
|
|
1864
2020
|
stateVersion: conv.stateVersion,
|
|
1865
2021
|
});
|
|
@@ -1868,12 +2024,14 @@ function handleServerMessage(msg) {
|
|
|
1868
2024
|
// Include idle but resumable conversations so other tabs can
|
|
1869
2025
|
// learn the backend session ID for multi-device continuity.
|
|
1870
2026
|
active.push({
|
|
2027
|
+
providerId: conv.providerId, workDir: conv.workDir,
|
|
1871
2028
|
conversationId: convId,
|
|
1872
2029
|
claudeSessionId: sessionId,
|
|
1873
2030
|
isProcessing: false,
|
|
1874
2031
|
isCompacting: false,
|
|
1875
2032
|
turnId: conv.turnId,
|
|
1876
2033
|
stateVersion: conv.stateVersion,
|
|
2034
|
+
...(conv.compactionRecords ? { compactions: [...conv.compactionRecords.values()].slice(-8) } : {}),
|
|
1877
2035
|
});
|
|
1878
2036
|
}
|
|
1879
2037
|
};
|
|
@@ -1917,12 +2075,14 @@ function handleServerMessage(msg) {
|
|
|
1917
2075
|
const status = isProc ? (isComp ? 'compacting' : 'processing') : 'idle';
|
|
1918
2076
|
return {
|
|
1919
2077
|
type: 'turn_status',
|
|
2078
|
+
providerId: conv.providerId, workDir: conv.workDir,
|
|
1920
2079
|
conversationId: convId,
|
|
1921
2080
|
claudeSessionId: conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null,
|
|
1922
2081
|
turnId: conv.turnId,
|
|
1923
2082
|
status,
|
|
1924
2083
|
isProcessing: isProc,
|
|
1925
2084
|
isCompacting: isComp,
|
|
2085
|
+
...(conv.compactionRecords ? { compactions: [...conv.compactionRecords.values()].slice(-8) } : {}),
|
|
1926
2086
|
stateVersion: conv.stateVersion,
|
|
1927
2087
|
};
|
|
1928
2088
|
};
|
|
@@ -2114,8 +2274,26 @@ function handleServerMessage(msg) {
|
|
|
2114
2274
|
break;
|
|
2115
2275
|
}
|
|
2116
2276
|
case 'set_plan_mode': {
|
|
2117
|
-
const { enabled, conversationId } = msg;
|
|
2277
|
+
const { enabled, conversationId, providerId, workDir, copilotOptions, resumeSessionId } = msg;
|
|
2118
2278
|
console.log(`[AgentLink] set_plan_mode: enabled=${enabled}, conversationId=${conversationId}`);
|
|
2279
|
+
const copilotConversation = conversationId
|
|
2280
|
+
? getCopilotConversation(conversationId)
|
|
2281
|
+
: undefined;
|
|
2282
|
+
const effectiveProviderId = providerId || copilotConversation?.providerId;
|
|
2283
|
+
if (conversationId
|
|
2284
|
+
&& (copilotConversation
|
|
2285
|
+
|| (effectiveProviderId && getBackendTypeForProvider(effectiveProviderId) === 'copilot')
|
|
2286
|
+
|| isCopilotBackend())) {
|
|
2287
|
+
void setCopilotPlanMode(conversationId, enabled, path.resolve(workDir || copilotConversation?.workDir || state.workDir), effectiveProviderId || 'copilot', copilotOptions, resumeSessionId).catch(error => {
|
|
2288
|
+
send({
|
|
2289
|
+
type: 'error',
|
|
2290
|
+
conversationId,
|
|
2291
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2292
|
+
});
|
|
2293
|
+
send({ type: 'plan_mode_changed', enabled: !enabled, conversationId });
|
|
2294
|
+
});
|
|
2295
|
+
break;
|
|
2296
|
+
}
|
|
2119
2297
|
const conv = getConversation(conversationId);
|
|
2120
2298
|
if (conv) {
|
|
2121
2299
|
// Restart with new mode
|
|
@@ -2131,6 +2309,34 @@ function handleServerMessage(msg) {
|
|
|
2131
2309
|
send({ type: 'plan_mode_changed', enabled, conversationId });
|
|
2132
2310
|
break;
|
|
2133
2311
|
}
|
|
2312
|
+
case 'start_plan_implementation': {
|
|
2313
|
+
const { conversationId, planId, providerId, workDir, claudeSessionId, prompt, copilotOptions } = msg;
|
|
2314
|
+
if (!conversationId || !planId) {
|
|
2315
|
+
send({ type: 'error', conversationId, message: 'Invalid Plan implementation request' });
|
|
2316
|
+
break;
|
|
2317
|
+
}
|
|
2318
|
+
void startCopilotPlanImplementation(conversationId, planId, path.resolve(workDir || state.workDir), { providerId, resumeSessionId: claudeSessionId, prompt, copilotOptions }).catch(error => {
|
|
2319
|
+
send({
|
|
2320
|
+
type: 'backend_event',
|
|
2321
|
+
conversationId,
|
|
2322
|
+
providerId,
|
|
2323
|
+
claudeSessionId,
|
|
2324
|
+
event: {
|
|
2325
|
+
type: 'plan_implementation_status',
|
|
2326
|
+
session: { backendType: 'copilot', backendSessionId: claudeSessionId || '' },
|
|
2327
|
+
planId,
|
|
2328
|
+
status: 'failed',
|
|
2329
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2330
|
+
},
|
|
2331
|
+
});
|
|
2332
|
+
send({
|
|
2333
|
+
type: 'error',
|
|
2334
|
+
conversationId,
|
|
2335
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2336
|
+
});
|
|
2337
|
+
});
|
|
2338
|
+
break;
|
|
2339
|
+
}
|
|
2134
2340
|
case 'set_permission_mode': {
|
|
2135
2341
|
const { mode, conversationId } = msg;
|
|
2136
2342
|
console.log(`[AgentLink] set_permission_mode: mode=${mode}, conversationId=${conversationId}`);
|
|
@@ -2189,7 +2395,7 @@ function handleServerMessage(msg) {
|
|
|
2189
2395
|
break;
|
|
2190
2396
|
}
|
|
2191
2397
|
case 'set_provider': {
|
|
2192
|
-
|
|
2398
|
+
let { providerId, conversationId, resumeSessionId, workDir, restoreOnly } = msg;
|
|
2193
2399
|
console.log(`[AgentLink] set_provider: providerId=${providerId}, conversationId=${conversationId}`);
|
|
2194
2400
|
// Detect backend type change
|
|
2195
2401
|
const newBackendType = getBackendTypeForProvider(providerId);
|
|
@@ -2202,6 +2408,9 @@ function handleServerMessage(msg) {
|
|
|
2202
2408
|
// are routed to background via the web's backgroundRouting handler.
|
|
2203
2409
|
setBackendType(newBackendType);
|
|
2204
2410
|
console.log(`[AgentLink] Backend switched: ${oldBackendType} → ${newBackendType}`);
|
|
2411
|
+
if (newBackendType === 'copilot' && conversationId) {
|
|
2412
|
+
providerId = setCopilotProvider(conversationId, providerId, { resumeSessionId, workDir, restoreOnly });
|
|
2413
|
+
}
|
|
2205
2414
|
}
|
|
2206
2415
|
else if (oldBackendType === 'yeaft') {
|
|
2207
2416
|
const existingYeaftConv = conversationId ? getYeaftConversation(conversationId) : undefined;
|
|
@@ -2250,19 +2459,8 @@ function handleServerMessage(msg) {
|
|
|
2250
2459
|
else if (oldBackendType === 'copilot') {
|
|
2251
2460
|
// Same backend (copilot): provider sync during reconnect must not
|
|
2252
2461
|
// restart an active subprocess.
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
existingCopilotConv.providerId = providerId;
|
|
2256
|
-
}
|
|
2257
|
-
else if (conversationId && existingCopilotConv) {
|
|
2258
|
-
const result = restartCopilotConversation(conversationId, { providerId });
|
|
2259
|
-
if (result.wasTurnActive) {
|
|
2260
|
-
send({ type: 'execution_cancelled', conversationId });
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
else if (conversationId) {
|
|
2264
|
-
createCopilotPlaceholder(conversationId, { providerId });
|
|
2265
|
-
}
|
|
2462
|
+
if (conversationId)
|
|
2463
|
+
providerId = setCopilotProvider(conversationId, providerId, { resumeSessionId, workDir, restoreOnly });
|
|
2266
2464
|
}
|
|
2267
2465
|
else {
|
|
2268
2466
|
// Same backend (claude): reconnect provider sync must not interrupt
|
|
@@ -2281,7 +2479,7 @@ function handleServerMessage(msg) {
|
|
|
2281
2479
|
createPlaceholderConversation(conversationId, { providerId });
|
|
2282
2480
|
}
|
|
2283
2481
|
}
|
|
2284
|
-
send({ type: 'provider_changed', providerId, conversationId });
|
|
2482
|
+
send({ type: 'provider_changed', providerId, conversationId, ...(restoreOnly ? { restoreOnly: true } : {}) });
|
|
2285
2483
|
// Capabilities are provider-scoped: wrappers and custom providers can
|
|
2286
2484
|
// share a backend while exposing different command discovery behavior.
|
|
2287
2485
|
send({
|
|
@@ -2847,18 +3045,24 @@ async function handleListSessions(dir, options = {}) {
|
|
|
2847
3045
|
});
|
|
2848
3046
|
}
|
|
2849
3047
|
}
|
|
2850
|
-
function handleListCopilotSessionsFn(dir, options = {}) {
|
|
3048
|
+
async function handleListCopilotSessionsFn(dir, options = {}) {
|
|
3049
|
+
const requestSocket = state.ws;
|
|
3050
|
+
const backend = getBackendType();
|
|
3051
|
+
const stillCurrent = () => state.ws === requestSocket && (!!options.providerId || getBackendType() === backend);
|
|
3052
|
+
const startedAt = performance.now();
|
|
2851
3053
|
try {
|
|
2852
3054
|
const page = options.legacy
|
|
2853
|
-
? { ...
|
|
2854
|
-
:
|
|
3055
|
+
? { ...await listCopilotSessionsPageAsync(dir, { limit: Number.POSITIVE_INFINITY }), hasMore: false }
|
|
3056
|
+
: await listCopilotSessionsPageAsync(dir, {
|
|
2855
3057
|
limit: options.limit ?? DEFAULT_SESSION_LIST_LIMIT,
|
|
2856
3058
|
cursor: options.cursor,
|
|
2857
3059
|
});
|
|
2858
3060
|
if (options.legacy && page.sessions.length > LEGACY_SESSION_LIST_CAP) {
|
|
2859
3061
|
page.sessions = page.sessions.slice(0, LEGACY_SESSION_LIST_CAP);
|
|
2860
3062
|
}
|
|
2861
|
-
|
|
3063
|
+
if (!stillCurrent())
|
|
3064
|
+
return;
|
|
3065
|
+
console.log(`[AgentLink] → sessions_list (${page.sessions.length} copilot sessions for ${dir}) hasMore=${page.hasMore} durationMs=${Math.round(performance.now() - startedAt)}`);
|
|
2862
3066
|
send({
|
|
2863
3067
|
type: 'sessions_list',
|
|
2864
3068
|
sessions: page.sessions,
|
|
@@ -2869,10 +3073,12 @@ function handleListCopilotSessionsFn(dir, options = {}) {
|
|
|
2869
3073
|
...(options.mode ? { mode: options.mode } : {}),
|
|
2870
3074
|
...(options.requestId ? { requestId: options.requestId } : {}),
|
|
2871
3075
|
...(options.providerId ? { providerId: options.providerId } : {}),
|
|
2872
|
-
});
|
|
3076
|
+
}, requestSocket);
|
|
2873
3077
|
}
|
|
2874
3078
|
catch (err) {
|
|
2875
3079
|
console.error(`[AgentLink] listCopilotSessions failed:`, err);
|
|
3080
|
+
if (!stillCurrent())
|
|
3081
|
+
return;
|
|
2876
3082
|
send({
|
|
2877
3083
|
type: 'sessions_list',
|
|
2878
3084
|
sessions: [],
|
|
@@ -2883,7 +3089,32 @@ function handleListCopilotSessionsFn(dir, options = {}) {
|
|
|
2883
3089
|
...(options.mode ? { mode: options.mode } : {}),
|
|
2884
3090
|
...(options.requestId ? { requestId: options.requestId } : {}),
|
|
2885
3091
|
...(options.providerId ? { providerId: options.providerId } : {}),
|
|
2886
|
-
});
|
|
3092
|
+
}, requestSocket);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
async function handleListRecentCopilotSessions(options) {
|
|
3096
|
+
const requestSocket = state.ws;
|
|
3097
|
+
const backend = getBackendType();
|
|
3098
|
+
const stillCurrent = () => state.ws === requestSocket && (!!options.providerId || getBackendType() === backend);
|
|
3099
|
+
const envelope = {
|
|
3100
|
+
type: 'recent_sessions_list',
|
|
3101
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
3102
|
+
...(options.requestId ? { requestId: options.requestId } : {}),
|
|
3103
|
+
...(options.providerId ? { providerId: options.providerId } : {}),
|
|
3104
|
+
};
|
|
3105
|
+
const startedAt = performance.now();
|
|
3106
|
+
try {
|
|
3107
|
+
const page = await listAllCopilotSessionsPageAsync({ limit: options.limit, cursor: options.cursor });
|
|
3108
|
+
if (!stillCurrent())
|
|
3109
|
+
return;
|
|
3110
|
+
console.log(`[AgentLink] → recent_sessions_list (${page.sessions.length} copilot sessions) hasMore=${page.hasMore} durationMs=${Math.round(performance.now() - startedAt)}`);
|
|
3111
|
+
send({ ...envelope, ...page }, requestSocket);
|
|
3112
|
+
}
|
|
3113
|
+
catch (error) {
|
|
3114
|
+
console.error('[AgentLink] listAllCopilotSessions failed:', error);
|
|
3115
|
+
if (stillCurrent())
|
|
3116
|
+
send({ ...envelope, sessions: [], hasMore: false, indexState: 'ready',
|
|
3117
|
+
error: error instanceof Error ? error.message : String(error) }, requestSocket);
|
|
2887
3118
|
}
|
|
2888
3119
|
}
|
|
2889
3120
|
function handleListCodexSessionsFn(dir, options = {}) {
|