@agent-link/agent 0.1.328 → 0.1.330

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.
@@ -29,12 +29,13 @@ const require = createRequire(import.meta.url);
29
29
  const pkg = require('../package.json');
30
30
  const TEAM_SESSIONS_DIR = path.join(CONFIG_DIR, 'team-sessions');
31
31
  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, 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';
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';
33
33
  import { addPiSendFn, cancelPiExecution, cleanupAllPiConversations, createPiPlaceholder, deletePiSession, evictByPiSessionId, getPiConversation, getPiConversations, getPiSessionWorkDir, handlePiChat, listAllPiSessionsPage, listPiSessionsPage, movePiSession, piCapabilities, readPiSessionMessages, rebindPiConversation, renamePiSession, restartPiConversation, } from './pi.js';
34
+ import { addYeaftSendFn, cancelYeaftExecution, cleanupAllYeaftConversations, createYeaftPlaceholder, deleteYeaftSession, evictByYeaftSessionId, getYeaftConversation, getYeaftConversations, handleYeaftChat, handleYeaftUserAnswer, listAllYeaftSessionsPage, listYeaftSessionsPage, moveYeaftSession, readYeaftSessionMessages, rebindYeaftConversation, renameYeaftSession, restartYeaftConversation, } from './yeaft.js';
34
35
  import { DEFAULT_SESSION_LIST_LIMIT, LEGACY_SESSION_LIST_CAP, listSessions, listSessionsPageAsync, readSessionMessages, deleteSession, renameSession, moveSession, listAllRecentSessionsPage, getSessionCacheStats } from './history.js';
35
36
  import { discoverProviderCommands, providerCommandDiscoverers, } from './provider-command-discovery.js';
36
37
  import { providerCommandsMessage } from './provider-command-types.js';
37
- import { claudeCapabilities } from './backends/types.js';
38
+ import { claudeCapabilities, yeaftCapabilities } from './backends/types.js';
38
39
  import { searchSessions } from './search-sessions.js';
39
40
  import { formatSessionExport, isSessionExportFormat } from './session-export.js';
40
41
  import { listMemoryFiles, updateMemoryFile, deleteMemoryFile } from './memory.js';
@@ -45,6 +46,7 @@ import { initEntraTimers, startEntraRetryTimer, stopEntraRetryTimer, scheduleEnt
45
46
  import { initScheduler, getRunningExecutions, } from './scheduler.js';
46
47
  import { handleWebUpgradeMessage } from './web-upgrade.js';
47
48
  import { refreshRuntimeLaunchSpecDeviceName } from './upgrade-launch-spec.js';
49
+ import { getAgentRuntimeInfo } from './runtime-info.js';
48
50
  let tunnelHandler = null;
49
51
  let terminalManager = null;
50
52
  const RECONNECT_BASE_DELAY = 1000;
@@ -73,6 +75,7 @@ let unsubscribeSend = null;
73
75
  let unsubscribeCopilotSend = null;
74
76
  let unsubscribeCodexSend = null;
75
77
  let unsubscribePiSend = null;
78
+ let unsubscribeYeaftSend = null;
76
79
  let unsubscribeSessionStarted = null;
77
80
  let heartbeatInterval = null;
78
81
  let heartbeatTimeout = null;
@@ -91,6 +94,8 @@ function capabilitiesForBackend(backend, providerId) {
91
94
  return codexCapabilities();
92
95
  case 'pi':
93
96
  return piCapabilities();
97
+ case 'yeaft':
98
+ return yeaftCapabilities();
94
99
  case 'claude':
95
100
  default:
96
101
  return claudeCapabilities();
@@ -136,6 +141,16 @@ function hasActiveSessionConversation(backend, sessionId, providerId, workDir) {
136
141
  }
137
142
  }
138
143
  }
144
+ else if (backend === 'yeaft') {
145
+ for (const conv of getYeaftConversations().values()) {
146
+ if ((conv.yeaftSessionId === sessionId || conv.lastYeaftSessionId === sessionId)
147
+ && (!providerId || conv.providerId === providerId)
148
+ && (!workDir || normalizeDirForCompare(conv.workDir) === normalizeDirForCompare(workDir))
149
+ && conv.turnActive) {
150
+ return true;
151
+ }
152
+ }
153
+ }
139
154
  else {
140
155
  for (const conv of getConversations().values()) {
141
156
  if (conv.claudeSessionId === sessionId || conv.lastClaudeSessionId === sessionId) {
@@ -167,6 +182,14 @@ function syncMovedConversationWorkDir(backend, sessionId, toWorkDir, providerId,
167
182
  conv.workDir = toWorkDir;
168
183
  }
169
184
  }
185
+ else if (backend === 'yeaft') {
186
+ for (const conv of getYeaftConversations().values()) {
187
+ if ((conv.yeaftSessionId === sessionId || conv.lastYeaftSessionId === sessionId)
188
+ && (!providerId || conv.providerId === providerId)
189
+ && (!fromWorkDir || normalizeDirForCompare(conv.workDir) === normalizeDirForCompare(fromWorkDir)))
190
+ conv.workDir = toWorkDir;
191
+ }
192
+ }
170
193
  else {
171
194
  for (const conv of getConversations().values()) {
172
195
  if (conv.claudeSessionId === sessionId || conv.lastClaudeSessionId === sessionId)
@@ -236,6 +259,13 @@ export function initializeLocalConnection(config) {
236
259
  catch { /* ignore */ }
237
260
  }
238
261
  unsubscribePiSend = addPiSendFn(send);
262
+ if (unsubscribeYeaftSend) {
263
+ try {
264
+ unsubscribeYeaftSend();
265
+ }
266
+ catch { /* ignore */ }
267
+ }
268
+ unsubscribeYeaftSend = addYeaftSendFn(send);
239
269
  // Wire up action item session linkage
240
270
  if (unsubscribeSessionStarted) {
241
271
  try {
@@ -534,6 +564,7 @@ export function disconnect() {
534
564
  cleanupAllCopilotConversations();
535
565
  cleanupAllCodexConversations();
536
566
  cleanupAllPiConversations();
567
+ cleanupAllYeaftConversations();
537
568
  if (unsubscribeSend) {
538
569
  try {
539
570
  unsubscribeSend();
@@ -562,6 +593,13 @@ export function disconnect() {
562
593
  catch { /* ignore */ }
563
594
  unsubscribePiSend = null;
564
595
  }
596
+ if (unsubscribeYeaftSend) {
597
+ try {
598
+ unsubscribeYeaftSend();
599
+ }
600
+ catch { /* ignore */ }
601
+ unsubscribeYeaftSend = null;
602
+ }
565
603
  if (unsubscribeSessionStarted) {
566
604
  try {
567
605
  unsubscribeSessionStarted();
@@ -675,11 +713,13 @@ export function getConnectionStatus() {
675
713
  conversations: getConversations().size
676
714
  + getCopilotConversations().size
677
715
  + getCodexConversations().size
716
+ + getYeaftConversations().size
678
717
  + getPiConversations().size,
679
718
  };
680
719
  }
681
720
  function buildWsUrl(config) {
682
721
  const base = config.server.replace(/\/$/, '');
722
+ const runtimeInfo = getAgentRuntimeInfo();
683
723
  const params = new URLSearchParams({
684
724
  type: 'agent',
685
725
  id: config.name,
@@ -690,6 +730,12 @@ function buildWsUrl(config) {
690
730
  deviceNameEditing: '1',
691
731
  version: pkg.version,
692
732
  qacRequestIds: '1',
733
+ nodeVersion: runtimeInfo.nodeVersion,
734
+ nodeMajor: String(runtimeInfo.nodeMajor),
735
+ nodeAbi: runtimeInfo.nodeAbi,
736
+ platform: runtimeInfo.platform,
737
+ arch: runtimeInfo.arch,
738
+ versionStoreAdopted: runtimeInfo.versionStoreAdopted ? '1' : '0',
693
739
  });
694
740
  // On reconnect, send previous sessionId so the URL stays valid
695
741
  if (state.sessionId) {
@@ -904,7 +950,15 @@ function handleServerMessage(msg) {
904
950
  }
905
951
  }
906
952
  // Route to the selected backend.
907
- if (isPiBackend()) {
953
+ if (isYeaftBackend()) {
954
+ const yeaftMsg = msg;
955
+ handleYeaftChat(chatConvId || '', yeaftMsg.prompt, chatDir, {
956
+ resumeSessionId: chatOptions.resumeSessionId,
957
+ providerId: effectiveProviderId,
958
+ yeaftOptions: yeaftMsg.yeaftOptions || yeaftMsg.copilotOptions,
959
+ }, yeaftMsg.files);
960
+ }
961
+ else if (isPiBackend()) {
908
962
  const piMsg = msg;
909
963
  const piState = chatConvId ? getPiConversation(chatConvId) : undefined;
910
964
  const piResumeDir = chatOptions.resumeSessionId
@@ -974,7 +1028,10 @@ function handleServerMessage(msg) {
974
1028
  const conversationId = msg.conversationId || '';
975
1029
  // Cancellation follows conversation ownership, not the currently selected
976
1030
  // provider. This keeps background turns cancellable after a tab switches.
977
- if (getPiConversation(conversationId)) {
1031
+ if (getYeaftConversation(conversationId)) {
1032
+ cancelYeaftExecution(conversationId);
1033
+ }
1034
+ else if (getPiConversation(conversationId)) {
978
1035
  cancelPiExecution(conversationId);
979
1036
  }
980
1037
  else if (getCodexConversation(conversationId)) {
@@ -986,6 +1043,9 @@ function handleServerMessage(msg) {
986
1043
  else if (getConversation(conversationId)) {
987
1044
  claudeCancelExecution(conversationId);
988
1045
  }
1046
+ else if (isYeaftBackend()) {
1047
+ cancelYeaftExecution(conversationId);
1048
+ }
989
1049
  else if (isPiBackend()) {
990
1050
  cancelPiExecution(conversationId);
991
1051
  }
@@ -1007,7 +1067,17 @@ function handleServerMessage(msg) {
1007
1067
  const sessionBackend = m.providerId
1008
1068
  ? getBackendTypeForProvider(m.providerId)
1009
1069
  : getBackendType();
1010
- if (sessionBackend === 'pi') {
1070
+ if (sessionBackend === 'yeaft') {
1071
+ handleListYeaftSessionsFn(dir, {
1072
+ limit: m.limit,
1073
+ cursor: m.cursor,
1074
+ mode: m.mode,
1075
+ requestId: m.requestId,
1076
+ providerId: m.providerId,
1077
+ legacy: m.limit == null && !m.cursor && !m.mode && !m.requestId,
1078
+ });
1079
+ }
1080
+ else if (sessionBackend === 'pi') {
1011
1081
  handleListPiSessionsFn(dir, {
1012
1082
  limit: m.limit,
1013
1083
  cursor: m.cursor,
@@ -1057,7 +1127,21 @@ function handleServerMessage(msg) {
1057
1127
  const recentBackend = m.providerId
1058
1128
  ? getBackendTypeForProvider(m.providerId)
1059
1129
  : getBackendType();
1060
- if (recentBackend === 'pi') {
1130
+ if (recentBackend === 'yeaft') {
1131
+ const page = listAllYeaftSessionsPage({ limit: m.limit, cursor: m.cursor });
1132
+ console.log(`[AgentLink] → recent_sessions_list (${page.sessions.length} Yeaft sessions) hasMore=${page.hasMore}`);
1133
+ send({
1134
+ type: 'recent_sessions_list',
1135
+ sessions: page.sessions,
1136
+ hasMore: page.hasMore,
1137
+ nextCursor: page.nextCursor,
1138
+ indexState: page.indexState,
1139
+ ...(m.mode ? { mode: m.mode } : {}),
1140
+ ...(m.requestId ? { requestId: m.requestId } : {}),
1141
+ providerId: m.providerId || 'yeaft',
1142
+ });
1143
+ }
1144
+ else if (recentBackend === 'pi') {
1061
1145
  const page = listAllPiSessionsPage({
1062
1146
  limit: m.limit,
1063
1147
  cursor: m.cursor,
@@ -1222,6 +1306,39 @@ function handleServerMessage(msg) {
1222
1306
  const resumeBackend = m.providerId
1223
1307
  ? getBackendTypeForProvider(m.providerId)
1224
1308
  : getBackendType();
1309
+ if (resumeBackend === 'yeaft') {
1310
+ if (convId) {
1311
+ rebindYeaftConversation(m.claudeSessionId, convId);
1312
+ if (!getYeaftConversation(convId)) {
1313
+ createYeaftPlaceholder(convId, { providerId: m.providerId || 'yeaft' });
1314
+ }
1315
+ }
1316
+ const yeaftState = getYeaftConversation(convId || '');
1317
+ const resolvedWorkDir = explicitWorkDir || yeaftState?.workDir || state.workDir;
1318
+ if (yeaftState) {
1319
+ yeaftState.yeaftSessionId = m.claudeSessionId;
1320
+ yeaftState.lastYeaftSessionId = m.claudeSessionId;
1321
+ yeaftState.workDir = resolvedWorkDir;
1322
+ }
1323
+ const history = readYeaftSessionMessages(resolvedWorkDir, m.claudeSessionId);
1324
+ const isYeaftProcessing = !!(yeaftState?.turnActive && yeaftState.child);
1325
+ console.log(`[AgentLink] → conversation_resumed (${history.length} Yeaft messages, session ${m.claudeSessionId.slice(0, 8)})`);
1326
+ send({
1327
+ type: 'conversation_resumed',
1328
+ conversationId: convId,
1329
+ claudeSessionId: m.claudeSessionId,
1330
+ history,
1331
+ isCompacting: false,
1332
+ isProcessing: isYeaftProcessing,
1333
+ planMode: false,
1334
+ permissionMode: 'normal',
1335
+ workDir: resolvedWorkDir,
1336
+ providerId: m.providerId || 'yeaft',
1337
+ ...(typeof m.scrollToMessageIdx === 'number' ? { scrollToMessageIdx: m.scrollToMessageIdx } : {}),
1338
+ ...(m.searchQuery ? { searchQuery: m.searchQuery } : {}),
1339
+ });
1340
+ break;
1341
+ }
1225
1342
  if (resumeBackend === 'pi') {
1226
1343
  const persistedWorkDir = getPiSessionWorkDir(m.claudeSessionId, m.providerId || 'pi', explicitWorkDir);
1227
1344
  if (convId) {
@@ -1435,7 +1552,10 @@ function handleServerMessage(msg) {
1435
1552
  }
1436
1553
  case 'ask_user_answer': {
1437
1554
  const m = msg;
1438
- handleUserAnswer(m.requestId, m.answers);
1555
+ if (isYeaftBackend())
1556
+ handleYeaftUserAnswer(m.requestId, m.answers, { conversationId: m.conversationId });
1557
+ else
1558
+ handleUserAnswer(m.requestId, m.answers);
1439
1559
  break;
1440
1560
  }
1441
1561
  case 'delete_session': {
@@ -1446,7 +1566,27 @@ function handleServerMessage(msg) {
1446
1566
  const dir = m.workDir?.trim()
1447
1567
  ? path.resolve(m.workDir)
1448
1568
  : (m.providerId === 'brain' ? BRAIN_DATA_DIR : state.workDir);
1449
- if (sessionBackend === 'pi') {
1569
+ if (sessionBackend === 'yeaft') {
1570
+ if (evictByYeaftSessionId(m.sessionId)) {
1571
+ send({ type: 'error', message: 'Cannot delete a session while it is processing.' });
1572
+ break;
1573
+ }
1574
+ const ok = deleteYeaftSession(dir, m.sessionId);
1575
+ console.log(`[AgentLink] delete Yeaft session ${m.sessionId}: ${ok}`);
1576
+ if (ok) {
1577
+ send({
1578
+ type: 'session_deleted',
1579
+ sessionId: m.sessionId,
1580
+ providerId: m.providerId || 'yeaft',
1581
+ workDir: dir,
1582
+ });
1583
+ }
1584
+ else {
1585
+ send({ type: 'error', message: 'Session not found or could not be deleted.' });
1586
+ }
1587
+ handleListYeaftSessionsFn(dir, { providerId: m.providerId || 'yeaft' });
1588
+ }
1589
+ else if (sessionBackend === 'pi') {
1450
1590
  if (evictByPiSessionId(m.sessionId, m.providerId || 'pi', dir)) {
1451
1591
  send({ type: 'error', message: 'Cannot delete a session while it is processing.' });
1452
1592
  break;
@@ -1497,7 +1637,28 @@ function handleServerMessage(msg) {
1497
1637
  const dir = m.workDir?.trim()
1498
1638
  ? path.resolve(m.workDir)
1499
1639
  : (m.providerId === 'brain' ? BRAIN_DATA_DIR : state.workDir);
1500
- if (sessionBackend === 'pi') {
1640
+ if (sessionBackend === 'yeaft') {
1641
+ if (hasActiveSessionConversation('yeaft', m.sessionId, m.providerId || 'yeaft', dir)) {
1642
+ send({ type: 'error', message: 'Cannot rename a session while it is processing.' });
1643
+ break;
1644
+ }
1645
+ const ok = renameYeaftSession(dir, m.sessionId, m.newTitle);
1646
+ console.log(`[AgentLink] rename Yeaft session ${m.sessionId}: ${ok}`);
1647
+ if (ok) {
1648
+ send({
1649
+ type: 'session_renamed',
1650
+ sessionId: m.sessionId,
1651
+ newTitle: m.newTitle,
1652
+ providerId: m.providerId || 'yeaft',
1653
+ workDir: dir,
1654
+ });
1655
+ }
1656
+ else {
1657
+ send({ type: 'error', message: 'Session not found or could not be renamed.' });
1658
+ }
1659
+ handleListYeaftSessionsFn(dir, { providerId: m.providerId || 'yeaft' });
1660
+ }
1661
+ else if (sessionBackend === 'pi') {
1501
1662
  if (hasActiveSessionConversation('pi', m.sessionId, m.providerId || 'pi', dir)) {
1502
1663
  send({ type: 'error', message: 'Cannot rename a session while it is processing.' });
1503
1664
  break;
@@ -1561,7 +1722,10 @@ function handleServerMessage(msg) {
1561
1722
  break;
1562
1723
  }
1563
1724
  let ok = false;
1564
- if (sessionBackend === 'pi') {
1725
+ if (sessionBackend === 'yeaft') {
1726
+ ok = moveYeaftSession(fromDir, toDir, m.sessionId);
1727
+ }
1728
+ else if (sessionBackend === 'pi') {
1565
1729
  ok = movePiSession(fromDir, toDir, m.sessionId, m.providerId || 'pi');
1566
1730
  }
1567
1731
  else if (sessionBackend === 'codex') {
@@ -1584,11 +1748,15 @@ function handleServerMessage(msg) {
1584
1748
  sessionId: m.sessionId,
1585
1749
  fromWorkDir: fromDir,
1586
1750
  toWorkDir: toDir,
1587
- ...(sessionBackend === 'pi'
1588
- ? { providerId: m.providerId || 'pi', workDir: toDir }
1751
+ ...(sessionBackend === 'pi' || sessionBackend === 'yeaft'
1752
+ ? { providerId: m.providerId || sessionBackend, workDir: toDir }
1589
1753
  : (m.providerId ? { providerId: m.providerId } : {})),
1590
1754
  });
1591
- if (sessionBackend === 'pi') {
1755
+ if (sessionBackend === 'yeaft') {
1756
+ handleListYeaftSessionsFn(fromDir, { providerId: m.providerId || 'yeaft' });
1757
+ handleListYeaftSessionsFn(toDir, { providerId: m.providerId || 'yeaft' });
1758
+ }
1759
+ else if (sessionBackend === 'pi') {
1592
1760
  handleListPiSessionsFn(fromDir, { providerId: m.providerId || 'pi' });
1593
1761
  handleListPiSessionsFn(toDir, { providerId: m.providerId || 'pi' });
1594
1762
  }
@@ -1622,7 +1790,13 @@ function handleServerMessage(msg) {
1622
1790
  break;
1623
1791
  }
1624
1792
  let readResult;
1625
- if (exportBackend === 'pi') {
1793
+ if (exportBackend === 'yeaft') {
1794
+ readResult = {
1795
+ history: readYeaftSessionMessages(dir, m.sessionId),
1796
+ workDir: dir,
1797
+ };
1798
+ }
1799
+ else if (exportBackend === 'pi') {
1626
1800
  readResult = {
1627
1801
  history: readPiSessionMessages(dir, m.sessionId, m.providerId || 'pi'),
1628
1802
  workDir: dir,
@@ -1669,7 +1843,7 @@ function handleServerMessage(msg) {
1669
1843
  break;
1670
1844
  }
1671
1845
  sendSessionExportResult(requestId, m.sessionId, result, {
1672
- providerId: m.providerId || (exportBackend === 'pi' ? 'pi' : undefined),
1846
+ providerId: m.providerId || ((exportBackend === 'pi' || exportBackend === 'yeaft') ? exportBackend : undefined),
1673
1847
  workDir: readResult.workDir,
1674
1848
  });
1675
1849
  break;
@@ -1678,7 +1852,7 @@ function handleServerMessage(msg) {
1678
1852
  const request = msg;
1679
1853
  const active = [];
1680
1854
  const pushActive = (convId, conv) => {
1681
- const sessionId = conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.piSessionId ?? null;
1855
+ const sessionId = conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null;
1682
1856
  if (conv.turnActive) {
1683
1857
  active.push({
1684
1858
  conversationId: convId,
@@ -1711,6 +1885,9 @@ function handleServerMessage(msg) {
1711
1885
  for (const [convId, conv] of getCodexConversations()) {
1712
1886
  pushActive(convId, conv);
1713
1887
  }
1888
+ for (const [convId, conv] of getYeaftConversations()) {
1889
+ pushActive(convId, conv);
1890
+ }
1714
1891
  for (const [convId, conv] of getPiConversations()) {
1715
1892
  pushActive(convId, conv);
1716
1893
  }
@@ -1740,7 +1917,7 @@ function handleServerMessage(msg) {
1740
1917
  return {
1741
1918
  type: 'turn_status',
1742
1919
  conversationId: convId,
1743
- claudeSessionId: conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.piSessionId ?? null,
1920
+ claudeSessionId: conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null,
1744
1921
  turnId: conv.turnId,
1745
1922
  status,
1746
1923
  isProcessing: isProc,
@@ -1775,6 +1952,15 @@ function handleServerMessage(msg) {
1775
1952
  }
1776
1953
  }
1777
1954
  }
1955
+ if (!found) {
1956
+ for (const [convId, conv] of getYeaftConversations()) {
1957
+ if (conv.turnId === m.turnId) {
1958
+ send(buildTurnStatus(convId, conv));
1959
+ found = true;
1960
+ break;
1961
+ }
1962
+ }
1963
+ }
1778
1964
  if (!found) {
1779
1965
  for (const [convId, conv] of getPiConversations()) {
1780
1966
  if (conv.turnId === m.turnId) {
@@ -1804,12 +1990,18 @@ function handleServerMessage(msg) {
1804
1990
  send(buildTurnStatus(m.conversationId, codexConv));
1805
1991
  }
1806
1992
  else {
1807
- const piConv = getPiConversation(m.conversationId);
1808
- if (piConv) {
1809
- send(buildTurnStatus(m.conversationId, piConv));
1993
+ const yeaftConv = getYeaftConversation(m.conversationId);
1994
+ if (yeaftConv) {
1995
+ send(buildTurnStatus(m.conversationId, yeaftConv));
1810
1996
  }
1811
1997
  else {
1812
- send({ type: 'turn_status', conversationId: m.conversationId, status: 'unknown', isProcessing: false, isCompacting: false, stateVersion: 0 });
1998
+ const piConv = getPiConversation(m.conversationId);
1999
+ if (piConv) {
2000
+ send(buildTurnStatus(m.conversationId, piConv));
2001
+ }
2002
+ else {
2003
+ send({ type: 'turn_status', conversationId: m.conversationId, status: 'unknown', isProcessing: false, isCompacting: false, stateVersion: 0 });
2004
+ }
1813
2005
  }
1814
2006
  }
1815
2007
  }
@@ -2010,6 +2202,20 @@ function handleServerMessage(msg) {
2010
2202
  setBackendType(newBackendType);
2011
2203
  console.log(`[AgentLink] Backend switched: ${oldBackendType} → ${newBackendType}`);
2012
2204
  }
2205
+ else if (oldBackendType === 'yeaft') {
2206
+ const existingYeaftConv = conversationId ? getYeaftConversation(conversationId) : undefined;
2207
+ if (existingYeaftConv?.turnActive) {
2208
+ existingYeaftConv.providerId = providerId;
2209
+ }
2210
+ else if (conversationId && existingYeaftConv) {
2211
+ const result = restartYeaftConversation(conversationId, { providerId });
2212
+ if (result.wasTurnActive)
2213
+ send({ type: 'execution_cancelled', conversationId });
2214
+ }
2215
+ else if (conversationId) {
2216
+ createYeaftPlaceholder(conversationId, { providerId });
2217
+ }
2218
+ }
2013
2219
  else if (oldBackendType === 'pi') {
2014
2220
  const existingPiConversation = conversationId ? getPiConversation(conversationId) : undefined;
2015
2221
  if (existingPiConversation?.turnActive) {
@@ -2717,6 +2923,45 @@ function handleListCodexSessionsFn(dir, options = {}) {
2717
2923
  });
2718
2924
  }
2719
2925
  }
2926
+ function handleListYeaftSessionsFn(dir, options = {}) {
2927
+ try {
2928
+ const page = options.legacy
2929
+ ? { ...listYeaftSessionsPage(dir, { limit: Number.POSITIVE_INFINITY }), hasMore: false }
2930
+ : listYeaftSessionsPage(dir, {
2931
+ limit: options.limit ?? DEFAULT_SESSION_LIST_LIMIT,
2932
+ cursor: options.cursor,
2933
+ });
2934
+ if (options.legacy && page.sessions.length > LEGACY_SESSION_LIST_CAP) {
2935
+ page.sessions = page.sessions.slice(0, LEGACY_SESSION_LIST_CAP);
2936
+ }
2937
+ console.log(`[AgentLink] → sessions_list (${page.sessions.length} Yeaft sessions for ${dir}) hasMore=${page.hasMore}`);
2938
+ send({
2939
+ type: 'sessions_list',
2940
+ sessions: page.sessions,
2941
+ workDir: dir,
2942
+ hasMore: page.hasMore,
2943
+ nextCursor: page.nextCursor,
2944
+ indexState: 'ready',
2945
+ ...(options.mode ? { mode: options.mode } : {}),
2946
+ ...(options.requestId ? { requestId: options.requestId } : {}),
2947
+ providerId: options.providerId || 'yeaft',
2948
+ });
2949
+ }
2950
+ catch (err) {
2951
+ console.error('[AgentLink] listYeaftSessions failed:', err);
2952
+ send({
2953
+ type: 'sessions_list',
2954
+ sessions: [],
2955
+ workDir: dir,
2956
+ hasMore: false,
2957
+ indexState: 'ready',
2958
+ error: err.message,
2959
+ ...(options.mode ? { mode: options.mode } : {}),
2960
+ ...(options.requestId ? { requestId: options.requestId } : {}),
2961
+ providerId: options.providerId || 'yeaft',
2962
+ });
2963
+ }
2964
+ }
2720
2965
  function handleListPiSessionsFn(dir, options = {}) {
2721
2966
  try {
2722
2967
  const page = options.legacy