@agent-link/agent 0.1.338 → 0.1.339

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.
Files changed (78) hide show
  1. package/dist/backends/types.d.ts +58 -0
  2. package/dist/backends/types.js +15 -4
  3. package/dist/backends/types.js.map +1 -1
  4. package/dist/connection.js +231 -28
  5. package/dist/connection.js.map +1 -1
  6. package/dist/copilot-capabilities-offline.d.ts +254 -0
  7. package/dist/copilot-capabilities-offline.js +392 -0
  8. package/dist/copilot-capabilities-offline.js.map +1 -0
  9. package/dist/copilot-capabilities.d.ts +43 -0
  10. package/dist/copilot-capabilities.js +128 -0
  11. package/dist/copilot-capabilities.js.map +1 -0
  12. package/dist/copilot-capability-service.d.ts +24 -0
  13. package/dist/copilot-capability-service.js +116 -0
  14. package/dist/copilot-capability-service.js.map +1 -0
  15. package/dist/copilot-command-bridge.d.ts +184 -0
  16. package/dist/copilot-command-bridge.js +1800 -0
  17. package/dist/copilot-command-bridge.js.map +1 -0
  18. package/dist/copilot-compaction-barrier.d.ts +36 -0
  19. package/dist/copilot-compaction-barrier.js +152 -0
  20. package/dist/copilot-compaction-barrier.js.map +1 -0
  21. package/dist/copilot-compaction-lifecycle.d.ts +34 -0
  22. package/dist/copilot-compaction-lifecycle.js +88 -0
  23. package/dist/copilot-compaction-lifecycle.js.map +1 -0
  24. package/dist/copilot-compaction-policy.d.ts +5 -0
  25. package/dist/copilot-compaction-policy.js +7 -0
  26. package/dist/copilot-compaction-policy.js.map +1 -0
  27. package/dist/copilot-configuration.d.ts +63 -0
  28. package/dist/copilot-configuration.js +283 -0
  29. package/dist/copilot-configuration.js.map +1 -0
  30. package/dist/copilot-event-scope.d.ts +13 -0
  31. package/dist/copilot-event-scope.js +26 -0
  32. package/dist/copilot-event-scope.js.map +1 -0
  33. package/dist/copilot-fork-metadata.d.ts +9 -0
  34. package/dist/copilot-fork-metadata.js +86 -0
  35. package/dist/copilot-fork-metadata.js.map +1 -0
  36. package/dist/copilot-message-kind.d.ts +21 -0
  37. package/dist/copilot-message-kind.js +45 -0
  38. package/dist/copilot-message-kind.js.map +1 -0
  39. package/dist/copilot-plan-state.d.ts +19 -0
  40. package/dist/copilot-plan-state.js +180 -0
  41. package/dist/copilot-plan-state.js.map +1 -0
  42. package/dist/copilot-request-dispatch.d.ts +10 -0
  43. package/dist/copilot-request-dispatch.js +31 -0
  44. package/dist/copilot-request-dispatch.js.map +1 -0
  45. package/dist/copilot-review-documents.d.ts +17 -0
  46. package/dist/copilot-review-documents.js +162 -0
  47. package/dist/copilot-review-documents.js.map +1 -0
  48. package/dist/copilot-session-activity.d.ts +3 -0
  49. package/dist/copilot-session-activity.js +94 -0
  50. package/dist/copilot-session-activity.js.map +1 -0
  51. package/dist/copilot-sidecar.d.ts +2 -0
  52. package/dist/copilot-sidecar.js +17 -0
  53. package/dist/copilot-sidecar.js.map +1 -0
  54. package/dist/copilot-turn-journal.d.ts +36 -0
  55. package/dist/copilot-turn-journal.js +246 -0
  56. package/dist/copilot-turn-journal.js.map +1 -0
  57. package/dist/copilot-user-requests.d.ts +11 -0
  58. package/dist/copilot-user-requests.js +84 -0
  59. package/dist/copilot-user-requests.js.map +1 -0
  60. package/dist/copilot.d.ts +194 -9
  61. package/dist/copilot.js +5012 -240
  62. package/dist/copilot.js.map +1 -1
  63. package/dist/history-message-transform.d.ts +47 -1
  64. package/dist/history-message-transform.js +67 -0
  65. package/dist/history-message-transform.js.map +1 -1
  66. package/dist/history.d.ts +8 -0
  67. package/dist/history.js.map +1 -1
  68. package/dist/provider-command-discovery.d.ts +5 -0
  69. package/dist/provider-command-discovery.js +49 -1
  70. package/dist/provider-command-discovery.js.map +1 -1
  71. package/dist/provider-command-types.d.ts +5 -0
  72. package/dist/provider-command-types.js.map +1 -1
  73. package/dist/runtime.d.ts +1 -1
  74. package/dist/runtime.js +1 -1
  75. package/dist/runtime.js.map +1 -1
  76. package/dist/search-sessions.js +2 -1
  77. package/dist/search-sessions.js.map +1 -1
  78. package/package.json +3 -1
@@ -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, cancelCopilotExecution, listCopilotSessionsPage, listAllCopilotSessionsPage, readCopilotSessionMessages, getCopilotSessionWorkDir, deleteCopilotSession, renameCopilotSession, moveCopilotSession, getCopilotConversation, getCopilotConversations, createCopilotPlaceholder, restartCopilotConversation, rebindCopilotConversation, cleanupAllCopilotConversations, copilotCapabilities, addCodexSendFn, handleCodexChat, cancelCodexExecution, listCodexSessionsPage, listAllCodexSessionsPage, readCodexSessionMessages, deleteCodexSession, renameCodexSession, moveCodexSession, getCodexConversation, getCodexConversations, createCodexPlaceholder, restartCodexConversation, rebindCodexConversation, cleanupAllCodexConversations, evictByCodexSessionId, codexCapabilities } from './runtime.js';
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, listCopilotSessionsPage, listAllCopilotSessionsPage, 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';
@@ -103,6 +109,9 @@ function capabilitiesForBackend(backend, providerId) {
103
109
  })();
104
110
  if (!providerId)
105
111
  return capabilities;
112
+ if (backend === 'copilot' && ['copilot', 'agency-copilot'].includes(providerId)) {
113
+ capabilities.copilotOptions.catalog = mergeCopilotCapabilities(providerId);
114
+ }
106
115
  return {
107
116
  ...capabilities,
108
117
  commands: {
@@ -981,17 +990,48 @@ function handleServerMessage(msg) {
981
990
  }
982
991
  else if (isCopilotBackend()) {
983
992
  const copilotMsg = msg;
984
- handleCopilotChat(chatConvId || '', copilotMsg.prompt, chatDir, {
993
+ void dispatchCopilotRequest({ conversationId: chatConvId || '', providerId: effectiveProviderId }, () => handleCopilotChat(chatConvId || '', copilotMsg.prompt, chatDir, {
985
994
  resumeSessionId: chatOptions.resumeSessionId,
986
995
  providerId: effectiveProviderId,
987
996
  copilotOptions: copilotMsg.copilotOptions,
988
- }, copilotMsg.files);
997
+ forkSourceSessionId: copilotMsg.forkSourceSessionId,
998
+ }, copilotMsg.files), send, () => getCopilotConversation(chatConvId || '')?.turnActive === true);
989
999
  }
990
1000
  else {
991
1001
  claudeHandleChat(chatConvId, msg.prompt, chatDir, chatOptions, msg.files);
992
1002
  }
993
1003
  break;
994
1004
  }
1005
+ case 'get_copilot_capabilities': {
1006
+ const m = msg;
1007
+ if (typeof m.requestId !== 'string' || !m.requestId || m.requestId.length > 200)
1008
+ break;
1009
+ if (m.cancel) {
1010
+ copilotCapabilityRequests.get(m.requestId)?.abort();
1011
+ break;
1012
+ }
1013
+ if (!['copilot', 'agency-copilot'].includes(m.providerId) || copilotCapabilityRequests.has(m.requestId))
1014
+ break;
1015
+ const scope = { providerId: m.providerId, workDir: path.resolve(m.workDir || state.workDir) };
1016
+ const controller = new AbortController();
1017
+ copilotCapabilityRequests.set(m.requestId, controller);
1018
+ send({ type: 'copilot_capabilities', requestId: m.requestId, providerId: m.providerId,
1019
+ workDir: scope.workDir, loading: true, catalog: copilotCapabilityService.snapshot(scope) });
1020
+ void copilotCapabilityService.query(scope, controller.signal).then(catalog => {
1021
+ if (!controller.signal.aborted)
1022
+ send({ type: 'copilot_capabilities', requestId: m.requestId,
1023
+ providerId: m.providerId, workDir: scope.workDir, loading: false, catalog });
1024
+ }).catch(error => {
1025
+ if (!controller.signal.aborted)
1026
+ send({ type: 'copilot_capabilities', requestId: m.requestId,
1027
+ providerId: m.providerId, workDir: scope.workDir, loading: false,
1028
+ catalog: { ...copilotCapabilityService.snapshot(scope), error: String(error) } });
1029
+ }).finally(() => {
1030
+ if (copilotCapabilityRequests.get(m.requestId) === controller)
1031
+ copilotCapabilityRequests.delete(m.requestId);
1032
+ });
1033
+ break;
1034
+ }
995
1035
  case 'get_provider_commands': {
996
1036
  const m = msg;
997
1037
  if (typeof m.requestId !== 'string' || !m.requestId.trim()
@@ -1014,6 +1054,7 @@ function handleServerMessage(msg) {
1014
1054
  providerId: m.providerId,
1015
1055
  conversationId: m.conversationId,
1016
1056
  workDir: path.resolve(m.workDir),
1057
+ ...(m.copilotOptions ? { copilotOptions: m.copilotOptions } : {}),
1017
1058
  };
1018
1059
  void discoverProviderCommands(scope)
1019
1060
  .then(result => send(providerCommandsMessage(scope, result)))
@@ -1023,6 +1064,32 @@ function handleServerMessage(msg) {
1023
1064
  })));
1024
1065
  break;
1025
1066
  }
1067
+ case 'execute_provider_command': {
1068
+ const m = msg;
1069
+ const conversation = m.conversationId
1070
+ ? getCopilotConversation(m.conversationId)
1071
+ : undefined;
1072
+ const providerId = m.providerId || conversation?.providerId || 'copilot';
1073
+ const command = m.command?.trim().toLowerCase();
1074
+ if (!m.conversationId
1075
+ || getBackendTypeForProvider(providerId) !== 'copilot'
1076
+ || !['compact', 'plan', 'goal'].includes(command || '')) {
1077
+ send({
1078
+ type: 'error',
1079
+ conversationId: m.conversationId,
1080
+ message: 'Invalid Copilot provider command request',
1081
+ });
1082
+ break;
1083
+ }
1084
+ const workDir = path.resolve(m.workDir || conversation?.workDir || state.workDir);
1085
+ const conversationId = m.conversationId;
1086
+ void dispatchCopilotRequest({ conversationId, providerId }, () => executeCopilotCommand(conversationId, command, typeof m.input === 'string' ? m.input : '', workDir, {
1087
+ providerId,
1088
+ resumeSessionId: resolveCopilotCommandResumeSessionId(m.resumeSessionId, conversation),
1089
+ copilotOptions: m.copilotOptions,
1090
+ }), send, () => getCopilotConversation(conversationId)?.turnActive === true);
1091
+ break;
1092
+ }
1026
1093
  case 'cancel_execution':
1027
1094
  {
1028
1095
  const conversationId = msg.conversationId || '';
@@ -1418,6 +1485,10 @@ function handleServerMessage(msg) {
1418
1485
  }
1419
1486
  // Route to copilot backend
1420
1487
  if (resumeBackend === 'copilot') {
1488
+ const liveOwner = [...getCopilotConversations().values()].find(conv => conv.copilotSessionId === m.claudeSessionId || conv.lastCopilotSessionId === m.claudeSessionId);
1489
+ const resumeProviderId = liveOwner?.providerId || readCopilotSessionProvider(m.claudeSessionId)
1490
+ || m.providerId
1491
+ || 'copilot';
1421
1492
  // Copilot rewrites workspace.yaml using the cwd of each resume process.
1422
1493
  // Recover the persisted cwd before creating a placeholder so a reconnect
1423
1494
  // that omits workDir cannot silently move the session to the agent cwd.
@@ -1426,35 +1497,101 @@ function handleServerMessage(msg) {
1426
1497
  || state.workDir;
1427
1498
  // Rebind running copilot process to new conversationId (handles page refresh)
1428
1499
  if (convId) {
1500
+ const existing = getCopilotConversation(convId);
1501
+ const boundSession = existing?.copilotSessionId || existing?.lastCopilotSessionId;
1502
+ if (existing && (boundSession || existing.turnActive) && boundSession !== m.claudeSessionId) {
1503
+ send({ type: 'error', operation: 'resume_conversation', conversationId: convId,
1504
+ providerId: resumeProviderId, claudeSessionId: m.claudeSessionId,
1505
+ message: 'Copilot conversation/session mismatch. Reopen the intended conversation.' });
1506
+ break;
1507
+ }
1429
1508
  rebindCopilotConversation(m.claudeSessionId, convId);
1430
1509
  }
1431
1510
  // Create a copilot placeholder so follow-up chats auto-resume
1432
1511
  if (convId) {
1433
- createCopilotPlaceholder(convId, { providerId: m.providerId || 'copilot' });
1512
+ createCopilotPlaceholder(convId, { providerId: resumeProviderId });
1434
1513
  const placeholder = getCopilotConversation(convId);
1435
1514
  if (placeholder) {
1515
+ placeholder.providerId = resumeProviderId;
1436
1516
  placeholder.copilotSessionId = m.claudeSessionId;
1437
1517
  placeholder.workDir = resolvedWorkDir;
1438
1518
  }
1439
1519
  }
1440
1520
  const history = readCopilotSessionMessages(resolvedWorkDir, m.claudeSessionId);
1521
+ const persistedCopilotMode = readCopilotSessionMode(m.claudeSessionId);
1522
+ const persistedCopilotUsage = readCopilotSessionUsage(m.claudeSessionId);
1441
1523
  // Check if this session has an active copilot process running
1442
1524
  const copilotState = getCopilotConversation(convId || '');
1443
- const isCopilotProcessing = !!(copilotState?.turnActive && copilotState?.child);
1525
+ // A live persistent Runtime can be newer than events.jsonl while the
1526
+ // Provider is flushing its final usage checkpoint. Do not replace its
1527
+ // authoritative in-memory value merely because the page was reopened.
1528
+ const restoredCopilotUsage = copilotState?.lastUsage || persistedCopilotUsage;
1529
+ const isCopilotProcessing = !!copilotState?.turnActive;
1530
+ if (copilotState && !isCopilotProcessing) {
1531
+ copilotState.copilotMode = persistedCopilotMode;
1532
+ copilotState.planMode = persistedCopilotMode === 'plan';
1533
+ copilotState.planModePending = persistedCopilotMode === 'plan';
1534
+ copilotState.lastUsage = restoredCopilotUsage;
1535
+ copilotState.configuration = restoredCopilotUsage?.configuration;
1536
+ copilotState.model = restoredCopilotUsage?.configuration?.requested.model || restoredCopilotUsage?.model || null;
1537
+ const requestedContext = restoredCopilotUsage?.configuration?.requested.context || restoredCopilotUsage?.contextTier;
1538
+ const requestedEffort = restoredCopilotUsage?.configuration?.requested.effort || restoredCopilotUsage?.reasoningEffort;
1539
+ copilotState.contextTier = requestedContext === 'default' || requestedContext === 'long_context'
1540
+ ? requestedContext
1541
+ : null;
1542
+ copilotState.reasoningEffort = [
1543
+ 'none', 'low', 'medium', 'high', 'xhigh', 'max',
1544
+ ].includes(requestedEffort || '')
1545
+ ? requestedEffort
1546
+ : null;
1547
+ copilotState.autoCompactionPending = !!restoredCopilotUsage
1548
+ && Number.isFinite(restoredCopilotUsage.contextTokens)
1549
+ && Number.isFinite(restoredCopilotUsage.contextWindow)
1550
+ && Number(restoredCopilotUsage.contextWindow) > 0
1551
+ && Number(restoredCopilotUsage.contextTokens) / Number(restoredCopilotUsage.contextWindow) >= COPILOT_COMPACTION_POLICY.preRequest;
1552
+ }
1444
1553
  console.log(`[AgentLink] → conversation_resumed (${history.length} copilot messages, session ${m.claudeSessionId.slice(0, 8)})`);
1445
1554
  send({
1446
1555
  type: 'conversation_resumed',
1447
1556
  conversationId: convId,
1448
1557
  claudeSessionId: m.claudeSessionId,
1449
1558
  history,
1450
- isCompacting: false,
1559
+ isCompacting: copilotState?.isCompacting || false,
1560
+ compactions: [...(copilotState?.compactionRecords?.values() || [])].slice(-8),
1451
1561
  isProcessing: isCopilotProcessing,
1452
- planMode: false,
1562
+ turnId: copilotState?.turnId,
1563
+ stateVersion: copilotState?.stateVersion,
1564
+ planMode: copilotState?.planMode ?? (persistedCopilotMode === 'plan'),
1565
+ copilotMode: copilotState?.copilotMode || persistedCopilotMode,
1453
1566
  permissionMode: 'normal',
1454
1567
  workDir: resolvedWorkDir,
1568
+ providerId: resumeProviderId,
1569
+ ...(restoredCopilotUsage ? { usage: restoredCopilotUsage } : {}),
1455
1570
  ...(typeof m.scrollToMessageIdx === 'number' ? { scrollToMessageIdx: m.scrollToMessageIdx } : {}),
1456
1571
  ...(m.searchQuery ? { searchQuery: m.searchQuery } : {}),
1457
1572
  });
1573
+ // Seed the resolved history Provider's offline capabilities without
1574
+ // creating a metadata Runtime just because history was opened.
1575
+ send({ type: 'backend_info', conversationId: convId, backend: 'copilot', providerId: resumeProviderId,
1576
+ capabilities: capabilitiesForBackend('copilot', resumeProviderId) });
1577
+ if (convId && isCopilotProcessing) {
1578
+ for (const pending of getPendingCopilotDecisions(convId)) {
1579
+ send({
1580
+ type: 'backend_event',
1581
+ conversationId: convId,
1582
+ providerId: pending.providerId,
1583
+ claudeSessionId: pending.sessionId,
1584
+ turnId: copilotState?.turnId,
1585
+ stateVersion: copilotState?.stateVersion,
1586
+ event: {
1587
+ type: 'user_input_requested',
1588
+ session: { backendType: 'copilot', backendSessionId: pending.sessionId },
1589
+ requestId: pending.requestId,
1590
+ request: pending.request,
1591
+ },
1592
+ });
1593
+ }
1594
+ }
1458
1595
  break;
1459
1596
  }
1460
1597
  if (!convId) {
@@ -1553,10 +1690,22 @@ function handleServerMessage(msg) {
1553
1690
  }
1554
1691
  case 'ask_user_answer': {
1555
1692
  const m = msg;
1556
- if (isYeaftBackend())
1693
+ const copilotConversation = m.conversationId
1694
+ ? getCopilotConversation(m.conversationId)
1695
+ : undefined;
1696
+ if (copilotConversation
1697
+ || (m.providerId && getBackendTypeForProvider(m.providerId) === 'copilot')) {
1698
+ handleCopilotUserAnswer(m.requestId, m.answers, {
1699
+ conversationId: m.conversationId,
1700
+ providerId: m.providerId,
1701
+ });
1702
+ }
1703
+ else if (isYeaftBackend()) {
1557
1704
  handleYeaftUserAnswer(m.requestId, m.answers, { conversationId: m.conversationId });
1558
- else
1705
+ }
1706
+ else {
1559
1707
  handleUserAnswer(m.requestId, m.answers);
1708
+ }
1560
1709
  break;
1561
1710
  }
1562
1711
  case 'delete_session': {
@@ -1621,9 +1770,19 @@ function handleServerMessage(msg) {
1621
1770
  handleListCodexSessionsFn(dir);
1622
1771
  }
1623
1772
  else if (sessionBackend === 'copilot') {
1624
- const ok = deleteCopilotSession(dir, m.sessionId);
1625
- console.log(`[AgentLink] delete copilot session ${m.sessionId}: ${ok}`);
1626
- handleListCopilotSessionsFn(dir);
1773
+ void deleteCopilotSession(dir, m.sessionId).then(ok => {
1774
+ console.log(`[AgentLink] delete copilot session ${m.sessionId}: ${ok}`);
1775
+ if (ok)
1776
+ send({ type: 'session_deleted', sessionId: m.sessionId, providerId: m.providerId, workDir: dir });
1777
+ else
1778
+ send({ type: 'error', operation: 'delete_session', sessionId: m.sessionId,
1779
+ message: 'Session not found or could not be deleted.' });
1780
+ handleListCopilotSessionsFn(dir);
1781
+ }).catch(error => {
1782
+ send({ type: 'error', operation: 'delete_session', sessionId: m.sessionId,
1783
+ message: String(error.message || error) });
1784
+ handleListCopilotSessionsFn(dir);
1785
+ });
1627
1786
  }
1628
1787
  else {
1629
1788
  handleDeleteSession(m.sessionId, dir);
@@ -1856,10 +2015,12 @@ function handleServerMessage(msg) {
1856
2015
  const sessionId = conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null;
1857
2016
  if (conv.turnActive) {
1858
2017
  active.push({
2018
+ providerId: conv.providerId, workDir: conv.workDir,
1859
2019
  conversationId: convId,
1860
2020
  claudeSessionId: sessionId,
1861
2021
  isProcessing: true,
1862
2022
  isCompacting: !!conv.isCompacting,
2023
+ ...(conv.compactionRecords ? { compactions: [...conv.compactionRecords.values()].slice(-8) } : {}),
1863
2024
  turnId: conv.turnId,
1864
2025
  stateVersion: conv.stateVersion,
1865
2026
  });
@@ -1868,12 +2029,14 @@ function handleServerMessage(msg) {
1868
2029
  // Include idle but resumable conversations so other tabs can
1869
2030
  // learn the backend session ID for multi-device continuity.
1870
2031
  active.push({
2032
+ providerId: conv.providerId, workDir: conv.workDir,
1871
2033
  conversationId: convId,
1872
2034
  claudeSessionId: sessionId,
1873
2035
  isProcessing: false,
1874
2036
  isCompacting: false,
1875
2037
  turnId: conv.turnId,
1876
2038
  stateVersion: conv.stateVersion,
2039
+ ...(conv.compactionRecords ? { compactions: [...conv.compactionRecords.values()].slice(-8) } : {}),
1877
2040
  });
1878
2041
  }
1879
2042
  };
@@ -1917,12 +2080,14 @@ function handleServerMessage(msg) {
1917
2080
  const status = isProc ? (isComp ? 'compacting' : 'processing') : 'idle';
1918
2081
  return {
1919
2082
  type: 'turn_status',
2083
+ providerId: conv.providerId, workDir: conv.workDir,
1920
2084
  conversationId: convId,
1921
2085
  claudeSessionId: conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null,
1922
2086
  turnId: conv.turnId,
1923
2087
  status,
1924
2088
  isProcessing: isProc,
1925
2089
  isCompacting: isComp,
2090
+ ...(conv.compactionRecords ? { compactions: [...conv.compactionRecords.values()].slice(-8) } : {}),
1926
2091
  stateVersion: conv.stateVersion,
1927
2092
  };
1928
2093
  };
@@ -2114,8 +2279,26 @@ function handleServerMessage(msg) {
2114
2279
  break;
2115
2280
  }
2116
2281
  case 'set_plan_mode': {
2117
- const { enabled, conversationId } = msg;
2282
+ const { enabled, conversationId, providerId, workDir, copilotOptions, resumeSessionId } = msg;
2118
2283
  console.log(`[AgentLink] set_plan_mode: enabled=${enabled}, conversationId=${conversationId}`);
2284
+ const copilotConversation = conversationId
2285
+ ? getCopilotConversation(conversationId)
2286
+ : undefined;
2287
+ const effectiveProviderId = providerId || copilotConversation?.providerId;
2288
+ if (conversationId
2289
+ && (copilotConversation
2290
+ || (effectiveProviderId && getBackendTypeForProvider(effectiveProviderId) === 'copilot')
2291
+ || isCopilotBackend())) {
2292
+ void setCopilotPlanMode(conversationId, enabled, path.resolve(workDir || copilotConversation?.workDir || state.workDir), effectiveProviderId || 'copilot', copilotOptions, resumeSessionId).catch(error => {
2293
+ send({
2294
+ type: 'error',
2295
+ conversationId,
2296
+ message: error instanceof Error ? error.message : String(error),
2297
+ });
2298
+ send({ type: 'plan_mode_changed', enabled: !enabled, conversationId });
2299
+ });
2300
+ break;
2301
+ }
2119
2302
  const conv = getConversation(conversationId);
2120
2303
  if (conv) {
2121
2304
  // Restart with new mode
@@ -2131,6 +2314,34 @@ function handleServerMessage(msg) {
2131
2314
  send({ type: 'plan_mode_changed', enabled, conversationId });
2132
2315
  break;
2133
2316
  }
2317
+ case 'start_plan_implementation': {
2318
+ const { conversationId, planId, providerId, workDir, claudeSessionId, prompt, copilotOptions } = msg;
2319
+ if (!conversationId || !planId) {
2320
+ send({ type: 'error', conversationId, message: 'Invalid Plan implementation request' });
2321
+ break;
2322
+ }
2323
+ void startCopilotPlanImplementation(conversationId, planId, path.resolve(workDir || state.workDir), { providerId, resumeSessionId: claudeSessionId, prompt, copilotOptions }).catch(error => {
2324
+ send({
2325
+ type: 'backend_event',
2326
+ conversationId,
2327
+ providerId,
2328
+ claudeSessionId,
2329
+ event: {
2330
+ type: 'plan_implementation_status',
2331
+ session: { backendType: 'copilot', backendSessionId: claudeSessionId || '' },
2332
+ planId,
2333
+ status: 'failed',
2334
+ message: error instanceof Error ? error.message : String(error),
2335
+ },
2336
+ });
2337
+ send({
2338
+ type: 'error',
2339
+ conversationId,
2340
+ message: error instanceof Error ? error.message : String(error),
2341
+ });
2342
+ });
2343
+ break;
2344
+ }
2134
2345
  case 'set_permission_mode': {
2135
2346
  const { mode, conversationId } = msg;
2136
2347
  console.log(`[AgentLink] set_permission_mode: mode=${mode}, conversationId=${conversationId}`);
@@ -2189,7 +2400,7 @@ function handleServerMessage(msg) {
2189
2400
  break;
2190
2401
  }
2191
2402
  case 'set_provider': {
2192
- const { providerId, conversationId } = msg;
2403
+ let { providerId, conversationId, resumeSessionId, workDir, restoreOnly } = msg;
2193
2404
  console.log(`[AgentLink] set_provider: providerId=${providerId}, conversationId=${conversationId}`);
2194
2405
  // Detect backend type change
2195
2406
  const newBackendType = getBackendTypeForProvider(providerId);
@@ -2202,6 +2413,9 @@ function handleServerMessage(msg) {
2202
2413
  // are routed to background via the web's backgroundRouting handler.
2203
2414
  setBackendType(newBackendType);
2204
2415
  console.log(`[AgentLink] Backend switched: ${oldBackendType} → ${newBackendType}`);
2416
+ if (newBackendType === 'copilot' && conversationId) {
2417
+ providerId = setCopilotProvider(conversationId, providerId, { resumeSessionId, workDir, restoreOnly });
2418
+ }
2205
2419
  }
2206
2420
  else if (oldBackendType === 'yeaft') {
2207
2421
  const existingYeaftConv = conversationId ? getYeaftConversation(conversationId) : undefined;
@@ -2250,19 +2464,8 @@ function handleServerMessage(msg) {
2250
2464
  else if (oldBackendType === 'copilot') {
2251
2465
  // Same backend (copilot): provider sync during reconnect must not
2252
2466
  // restart an active subprocess.
2253
- const existingCopilotConv = conversationId ? getCopilotConversation(conversationId) : undefined;
2254
- if (existingCopilotConv?.turnActive) {
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
- }
2467
+ if (conversationId)
2468
+ providerId = setCopilotProvider(conversationId, providerId, { resumeSessionId, workDir, restoreOnly });
2266
2469
  }
2267
2470
  else {
2268
2471
  // Same backend (claude): reconnect provider sync must not interrupt
@@ -2281,7 +2484,7 @@ function handleServerMessage(msg) {
2281
2484
  createPlaceholderConversation(conversationId, { providerId });
2282
2485
  }
2283
2486
  }
2284
- send({ type: 'provider_changed', providerId, conversationId });
2487
+ send({ type: 'provider_changed', providerId, conversationId, ...(restoreOnly ? { restoreOnly: true } : {}) });
2285
2488
  // Capabilities are provider-scoped: wrappers and custom providers can
2286
2489
  // share a backend while exposing different command discovery behavior.
2287
2490
  send({