@agent-link/agent 0.1.326 → 0.1.329

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,10 +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
- import { claudeCapabilities } from './backends/types.js';
36
+ import { discoverProviderCommands, providerCommandDiscoverers, } from './provider-command-discovery.js';
37
+ import { providerCommandsMessage } from './provider-command-types.js';
38
+ import { claudeCapabilities, yeaftCapabilities } from './backends/types.js';
36
39
  import { searchSessions } from './search-sessions.js';
37
40
  import { formatSessionExport, isSessionExportFormat } from './session-export.js';
38
41
  import { listMemoryFiles, updateMemoryFile, deleteMemoryFile } from './memory.js';
@@ -71,6 +74,7 @@ let unsubscribeSend = null;
71
74
  let unsubscribeCopilotSend = null;
72
75
  let unsubscribeCodexSend = null;
73
76
  let unsubscribePiSend = null;
77
+ let unsubscribeYeaftSend = null;
74
78
  let unsubscribeSessionStarted = null;
75
79
  let heartbeatInterval = null;
76
80
  let heartbeatTimeout = null;
@@ -80,18 +84,30 @@ initEntraTimers({
80
84
  setToken: (token) => { state.entraToken = token; },
81
85
  send: (msg) => send(msg),
82
86
  });
83
- function capabilitiesForBackend(backend) {
84
- switch (backend) {
85
- case 'copilot':
86
- return copilotCapabilities();
87
- case 'codex':
88
- return codexCapabilities();
89
- case 'pi':
90
- return piCapabilities();
91
- case 'claude':
92
- default:
93
- return claudeCapabilities();
94
- }
87
+ function capabilitiesForBackend(backend, providerId) {
88
+ const capabilities = (() => {
89
+ switch (backend) {
90
+ case 'copilot':
91
+ return copilotCapabilities();
92
+ case 'codex':
93
+ return codexCapabilities();
94
+ case 'pi':
95
+ return piCapabilities();
96
+ case 'yeaft':
97
+ return yeaftCapabilities();
98
+ case 'claude':
99
+ default:
100
+ return claudeCapabilities();
101
+ }
102
+ })();
103
+ if (!providerId)
104
+ return capabilities;
105
+ return {
106
+ ...capabilities,
107
+ commands: {
108
+ discovery: providerCommandDiscoverers.capability(providerId, backend),
109
+ },
110
+ };
95
111
  }
96
112
  function normalizeDirForCompare(value) {
97
113
  const normalized = value.replace(/[\\/]+$/, '');
@@ -124,6 +140,16 @@ function hasActiveSessionConversation(backend, sessionId, providerId, workDir) {
124
140
  }
125
141
  }
126
142
  }
143
+ else if (backend === 'yeaft') {
144
+ for (const conv of getYeaftConversations().values()) {
145
+ if ((conv.yeaftSessionId === sessionId || conv.lastYeaftSessionId === sessionId)
146
+ && (!providerId || conv.providerId === providerId)
147
+ && (!workDir || normalizeDirForCompare(conv.workDir) === normalizeDirForCompare(workDir))
148
+ && conv.turnActive) {
149
+ return true;
150
+ }
151
+ }
152
+ }
127
153
  else {
128
154
  for (const conv of getConversations().values()) {
129
155
  if (conv.claudeSessionId === sessionId || conv.lastClaudeSessionId === sessionId) {
@@ -155,6 +181,14 @@ function syncMovedConversationWorkDir(backend, sessionId, toWorkDir, providerId,
155
181
  conv.workDir = toWorkDir;
156
182
  }
157
183
  }
184
+ else if (backend === 'yeaft') {
185
+ for (const conv of getYeaftConversations().values()) {
186
+ if ((conv.yeaftSessionId === sessionId || conv.lastYeaftSessionId === sessionId)
187
+ && (!providerId || conv.providerId === providerId)
188
+ && (!fromWorkDir || normalizeDirForCompare(conv.workDir) === normalizeDirForCompare(fromWorkDir)))
189
+ conv.workDir = toWorkDir;
190
+ }
191
+ }
158
192
  else {
159
193
  for (const conv of getConversations().values()) {
160
194
  if (conv.claudeSessionId === sessionId || conv.lastClaudeSessionId === sessionId)
@@ -224,6 +258,13 @@ export function initializeLocalConnection(config) {
224
258
  catch { /* ignore */ }
225
259
  }
226
260
  unsubscribePiSend = addPiSendFn(send);
261
+ if (unsubscribeYeaftSend) {
262
+ try {
263
+ unsubscribeYeaftSend();
264
+ }
265
+ catch { /* ignore */ }
266
+ }
267
+ unsubscribeYeaftSend = addYeaftSendFn(send);
227
268
  // Wire up action item session linkage
228
269
  if (unsubscribeSessionStarted) {
229
270
  try {
@@ -522,6 +563,7 @@ export function disconnect() {
522
563
  cleanupAllCopilotConversations();
523
564
  cleanupAllCodexConversations();
524
565
  cleanupAllPiConversations();
566
+ cleanupAllYeaftConversations();
525
567
  if (unsubscribeSend) {
526
568
  try {
527
569
  unsubscribeSend();
@@ -550,6 +592,13 @@ export function disconnect() {
550
592
  catch { /* ignore */ }
551
593
  unsubscribePiSend = null;
552
594
  }
595
+ if (unsubscribeYeaftSend) {
596
+ try {
597
+ unsubscribeYeaftSend();
598
+ }
599
+ catch { /* ignore */ }
600
+ unsubscribeYeaftSend = null;
601
+ }
553
602
  if (unsubscribeSessionStarted) {
554
603
  try {
555
604
  unsubscribeSessionStarted();
@@ -663,6 +712,7 @@ export function getConnectionStatus() {
663
712
  conversations: getConversations().size
664
713
  + getCopilotConversations().size
665
714
  + getCodexConversations().size
715
+ + getYeaftConversations().size
666
716
  + getPiConversations().size,
667
717
  };
668
718
  }
@@ -883,7 +933,7 @@ function handleServerMessage(msg) {
883
933
  if (requiredBt !== getBackendType()) {
884
934
  setBackendType(requiredBt);
885
935
  console.log(`[AgentLink] Backend auto-switched to ${requiredBt} (chat providerId=${effectiveProviderId})`);
886
- const caps = capabilitiesForBackend(requiredBt);
936
+ const caps = capabilitiesForBackend(requiredBt, effectiveProviderId);
887
937
  send({
888
938
  type: 'backend_info',
889
939
  backend: requiredBt,
@@ -892,7 +942,15 @@ function handleServerMessage(msg) {
892
942
  }
893
943
  }
894
944
  // Route to the selected backend.
895
- if (isPiBackend()) {
945
+ if (isYeaftBackend()) {
946
+ const yeaftMsg = msg;
947
+ handleYeaftChat(chatConvId || '', yeaftMsg.prompt, chatDir, {
948
+ resumeSessionId: chatOptions.resumeSessionId,
949
+ providerId: effectiveProviderId,
950
+ yeaftOptions: yeaftMsg.yeaftOptions || yeaftMsg.copilotOptions,
951
+ }, yeaftMsg.files);
952
+ }
953
+ else if (isPiBackend()) {
896
954
  const piMsg = msg;
897
955
  const piState = chatConvId ? getPiConversation(chatConvId) : undefined;
898
956
  const piResumeDir = chatOptions.resumeSessionId
@@ -926,12 +984,46 @@ function handleServerMessage(msg) {
926
984
  }
927
985
  break;
928
986
  }
987
+ case 'get_provider_commands': {
988
+ const m = msg;
989
+ if (typeof m.requestId !== 'string' || !m.requestId.trim()
990
+ || typeof m.providerId !== 'string' || !m.providerId.trim()
991
+ || typeof m.conversationId !== 'string' || !m.conversationId.trim()
992
+ || typeof m.workDir !== 'string' || !m.workDir.trim()) {
993
+ send({
994
+ type: 'provider_commands_list',
995
+ requestId: m.requestId,
996
+ providerId: m.providerId,
997
+ conversationId: m.conversationId,
998
+ workDir: m.workDir,
999
+ commands: [],
1000
+ error: 'Invalid provider command discovery scope',
1001
+ });
1002
+ break;
1003
+ }
1004
+ const scope = {
1005
+ requestId: m.requestId,
1006
+ providerId: m.providerId,
1007
+ conversationId: m.conversationId,
1008
+ workDir: path.resolve(m.workDir),
1009
+ };
1010
+ void discoverProviderCommands(scope)
1011
+ .then(result => send(providerCommandsMessage(scope, result)))
1012
+ .catch(error => send(providerCommandsMessage(scope, {
1013
+ commands: [],
1014
+ error: error.message,
1015
+ })));
1016
+ break;
1017
+ }
929
1018
  case 'cancel_execution':
930
1019
  {
931
1020
  const conversationId = msg.conversationId || '';
932
1021
  // Cancellation follows conversation ownership, not the currently selected
933
1022
  // provider. This keeps background turns cancellable after a tab switches.
934
- if (getPiConversation(conversationId)) {
1023
+ if (getYeaftConversation(conversationId)) {
1024
+ cancelYeaftExecution(conversationId);
1025
+ }
1026
+ else if (getPiConversation(conversationId)) {
935
1027
  cancelPiExecution(conversationId);
936
1028
  }
937
1029
  else if (getCodexConversation(conversationId)) {
@@ -943,6 +1035,9 @@ function handleServerMessage(msg) {
943
1035
  else if (getConversation(conversationId)) {
944
1036
  claudeCancelExecution(conversationId);
945
1037
  }
1038
+ else if (isYeaftBackend()) {
1039
+ cancelYeaftExecution(conversationId);
1040
+ }
946
1041
  else if (isPiBackend()) {
947
1042
  cancelPiExecution(conversationId);
948
1043
  }
@@ -964,7 +1059,17 @@ function handleServerMessage(msg) {
964
1059
  const sessionBackend = m.providerId
965
1060
  ? getBackendTypeForProvider(m.providerId)
966
1061
  : getBackendType();
967
- if (sessionBackend === 'pi') {
1062
+ if (sessionBackend === 'yeaft') {
1063
+ handleListYeaftSessionsFn(dir, {
1064
+ limit: m.limit,
1065
+ cursor: m.cursor,
1066
+ mode: m.mode,
1067
+ requestId: m.requestId,
1068
+ providerId: m.providerId,
1069
+ legacy: m.limit == null && !m.cursor && !m.mode && !m.requestId,
1070
+ });
1071
+ }
1072
+ else if (sessionBackend === 'pi') {
968
1073
  handleListPiSessionsFn(dir, {
969
1074
  limit: m.limit,
970
1075
  cursor: m.cursor,
@@ -1014,7 +1119,21 @@ function handleServerMessage(msg) {
1014
1119
  const recentBackend = m.providerId
1015
1120
  ? getBackendTypeForProvider(m.providerId)
1016
1121
  : getBackendType();
1017
- if (recentBackend === 'pi') {
1122
+ if (recentBackend === 'yeaft') {
1123
+ const page = listAllYeaftSessionsPage({ limit: m.limit, cursor: m.cursor });
1124
+ console.log(`[AgentLink] → recent_sessions_list (${page.sessions.length} Yeaft sessions) hasMore=${page.hasMore}`);
1125
+ send({
1126
+ type: 'recent_sessions_list',
1127
+ sessions: page.sessions,
1128
+ hasMore: page.hasMore,
1129
+ nextCursor: page.nextCursor,
1130
+ indexState: page.indexState,
1131
+ ...(m.mode ? { mode: m.mode } : {}),
1132
+ ...(m.requestId ? { requestId: m.requestId } : {}),
1133
+ providerId: m.providerId || 'yeaft',
1134
+ });
1135
+ }
1136
+ else if (recentBackend === 'pi') {
1018
1137
  const page = listAllPiSessionsPage({
1019
1138
  limit: m.limit,
1020
1139
  cursor: m.cursor,
@@ -1179,6 +1298,39 @@ function handleServerMessage(msg) {
1179
1298
  const resumeBackend = m.providerId
1180
1299
  ? getBackendTypeForProvider(m.providerId)
1181
1300
  : getBackendType();
1301
+ if (resumeBackend === 'yeaft') {
1302
+ if (convId) {
1303
+ rebindYeaftConversation(m.claudeSessionId, convId);
1304
+ if (!getYeaftConversation(convId)) {
1305
+ createYeaftPlaceholder(convId, { providerId: m.providerId || 'yeaft' });
1306
+ }
1307
+ }
1308
+ const yeaftState = getYeaftConversation(convId || '');
1309
+ const resolvedWorkDir = explicitWorkDir || yeaftState?.workDir || state.workDir;
1310
+ if (yeaftState) {
1311
+ yeaftState.yeaftSessionId = m.claudeSessionId;
1312
+ yeaftState.lastYeaftSessionId = m.claudeSessionId;
1313
+ yeaftState.workDir = resolvedWorkDir;
1314
+ }
1315
+ const history = readYeaftSessionMessages(resolvedWorkDir, m.claudeSessionId);
1316
+ const isYeaftProcessing = !!(yeaftState?.turnActive && yeaftState.child);
1317
+ console.log(`[AgentLink] → conversation_resumed (${history.length} Yeaft messages, session ${m.claudeSessionId.slice(0, 8)})`);
1318
+ send({
1319
+ type: 'conversation_resumed',
1320
+ conversationId: convId,
1321
+ claudeSessionId: m.claudeSessionId,
1322
+ history,
1323
+ isCompacting: false,
1324
+ isProcessing: isYeaftProcessing,
1325
+ planMode: false,
1326
+ permissionMode: 'normal',
1327
+ workDir: resolvedWorkDir,
1328
+ providerId: m.providerId || 'yeaft',
1329
+ ...(typeof m.scrollToMessageIdx === 'number' ? { scrollToMessageIdx: m.scrollToMessageIdx } : {}),
1330
+ ...(m.searchQuery ? { searchQuery: m.searchQuery } : {}),
1331
+ });
1332
+ break;
1333
+ }
1182
1334
  if (resumeBackend === 'pi') {
1183
1335
  const persistedWorkDir = getPiSessionWorkDir(m.claudeSessionId, m.providerId || 'pi', explicitWorkDir);
1184
1336
  if (convId) {
@@ -1392,7 +1544,10 @@ function handleServerMessage(msg) {
1392
1544
  }
1393
1545
  case 'ask_user_answer': {
1394
1546
  const m = msg;
1395
- handleUserAnswer(m.requestId, m.answers);
1547
+ if (isYeaftBackend())
1548
+ handleYeaftUserAnswer(m.requestId, m.answers, { conversationId: m.conversationId });
1549
+ else
1550
+ handleUserAnswer(m.requestId, m.answers);
1396
1551
  break;
1397
1552
  }
1398
1553
  case 'delete_session': {
@@ -1403,7 +1558,27 @@ function handleServerMessage(msg) {
1403
1558
  const dir = m.workDir?.trim()
1404
1559
  ? path.resolve(m.workDir)
1405
1560
  : (m.providerId === 'brain' ? BRAIN_DATA_DIR : state.workDir);
1406
- if (sessionBackend === 'pi') {
1561
+ if (sessionBackend === 'yeaft') {
1562
+ if (evictByYeaftSessionId(m.sessionId)) {
1563
+ send({ type: 'error', message: 'Cannot delete a session while it is processing.' });
1564
+ break;
1565
+ }
1566
+ const ok = deleteYeaftSession(dir, m.sessionId);
1567
+ console.log(`[AgentLink] delete Yeaft session ${m.sessionId}: ${ok}`);
1568
+ if (ok) {
1569
+ send({
1570
+ type: 'session_deleted',
1571
+ sessionId: m.sessionId,
1572
+ providerId: m.providerId || 'yeaft',
1573
+ workDir: dir,
1574
+ });
1575
+ }
1576
+ else {
1577
+ send({ type: 'error', message: 'Session not found or could not be deleted.' });
1578
+ }
1579
+ handleListYeaftSessionsFn(dir, { providerId: m.providerId || 'yeaft' });
1580
+ }
1581
+ else if (sessionBackend === 'pi') {
1407
1582
  if (evictByPiSessionId(m.sessionId, m.providerId || 'pi', dir)) {
1408
1583
  send({ type: 'error', message: 'Cannot delete a session while it is processing.' });
1409
1584
  break;
@@ -1454,7 +1629,28 @@ function handleServerMessage(msg) {
1454
1629
  const dir = m.workDir?.trim()
1455
1630
  ? path.resolve(m.workDir)
1456
1631
  : (m.providerId === 'brain' ? BRAIN_DATA_DIR : state.workDir);
1457
- if (sessionBackend === 'pi') {
1632
+ if (sessionBackend === 'yeaft') {
1633
+ if (hasActiveSessionConversation('yeaft', m.sessionId, m.providerId || 'yeaft', dir)) {
1634
+ send({ type: 'error', message: 'Cannot rename a session while it is processing.' });
1635
+ break;
1636
+ }
1637
+ const ok = renameYeaftSession(dir, m.sessionId, m.newTitle);
1638
+ console.log(`[AgentLink] rename Yeaft session ${m.sessionId}: ${ok}`);
1639
+ if (ok) {
1640
+ send({
1641
+ type: 'session_renamed',
1642
+ sessionId: m.sessionId,
1643
+ newTitle: m.newTitle,
1644
+ providerId: m.providerId || 'yeaft',
1645
+ workDir: dir,
1646
+ });
1647
+ }
1648
+ else {
1649
+ send({ type: 'error', message: 'Session not found or could not be renamed.' });
1650
+ }
1651
+ handleListYeaftSessionsFn(dir, { providerId: m.providerId || 'yeaft' });
1652
+ }
1653
+ else if (sessionBackend === 'pi') {
1458
1654
  if (hasActiveSessionConversation('pi', m.sessionId, m.providerId || 'pi', dir)) {
1459
1655
  send({ type: 'error', message: 'Cannot rename a session while it is processing.' });
1460
1656
  break;
@@ -1518,7 +1714,10 @@ function handleServerMessage(msg) {
1518
1714
  break;
1519
1715
  }
1520
1716
  let ok = false;
1521
- if (sessionBackend === 'pi') {
1717
+ if (sessionBackend === 'yeaft') {
1718
+ ok = moveYeaftSession(fromDir, toDir, m.sessionId);
1719
+ }
1720
+ else if (sessionBackend === 'pi') {
1522
1721
  ok = movePiSession(fromDir, toDir, m.sessionId, m.providerId || 'pi');
1523
1722
  }
1524
1723
  else if (sessionBackend === 'codex') {
@@ -1541,11 +1740,15 @@ function handleServerMessage(msg) {
1541
1740
  sessionId: m.sessionId,
1542
1741
  fromWorkDir: fromDir,
1543
1742
  toWorkDir: toDir,
1544
- ...(sessionBackend === 'pi'
1545
- ? { providerId: m.providerId || 'pi', workDir: toDir }
1743
+ ...(sessionBackend === 'pi' || sessionBackend === 'yeaft'
1744
+ ? { providerId: m.providerId || sessionBackend, workDir: toDir }
1546
1745
  : (m.providerId ? { providerId: m.providerId } : {})),
1547
1746
  });
1548
- if (sessionBackend === 'pi') {
1747
+ if (sessionBackend === 'yeaft') {
1748
+ handleListYeaftSessionsFn(fromDir, { providerId: m.providerId || 'yeaft' });
1749
+ handleListYeaftSessionsFn(toDir, { providerId: m.providerId || 'yeaft' });
1750
+ }
1751
+ else if (sessionBackend === 'pi') {
1549
1752
  handleListPiSessionsFn(fromDir, { providerId: m.providerId || 'pi' });
1550
1753
  handleListPiSessionsFn(toDir, { providerId: m.providerId || 'pi' });
1551
1754
  }
@@ -1579,7 +1782,13 @@ function handleServerMessage(msg) {
1579
1782
  break;
1580
1783
  }
1581
1784
  let readResult;
1582
- if (exportBackend === 'pi') {
1785
+ if (exportBackend === 'yeaft') {
1786
+ readResult = {
1787
+ history: readYeaftSessionMessages(dir, m.sessionId),
1788
+ workDir: dir,
1789
+ };
1790
+ }
1791
+ else if (exportBackend === 'pi') {
1583
1792
  readResult = {
1584
1793
  history: readPiSessionMessages(dir, m.sessionId, m.providerId || 'pi'),
1585
1794
  workDir: dir,
@@ -1626,7 +1835,7 @@ function handleServerMessage(msg) {
1626
1835
  break;
1627
1836
  }
1628
1837
  sendSessionExportResult(requestId, m.sessionId, result, {
1629
- providerId: m.providerId || (exportBackend === 'pi' ? 'pi' : undefined),
1838
+ providerId: m.providerId || ((exportBackend === 'pi' || exportBackend === 'yeaft') ? exportBackend : undefined),
1630
1839
  workDir: readResult.workDir,
1631
1840
  });
1632
1841
  break;
@@ -1635,7 +1844,7 @@ function handleServerMessage(msg) {
1635
1844
  const request = msg;
1636
1845
  const active = [];
1637
1846
  const pushActive = (convId, conv) => {
1638
- const sessionId = conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.piSessionId ?? null;
1847
+ const sessionId = conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null;
1639
1848
  if (conv.turnActive) {
1640
1849
  active.push({
1641
1850
  conversationId: convId,
@@ -1668,6 +1877,9 @@ function handleServerMessage(msg) {
1668
1877
  for (const [convId, conv] of getCodexConversations()) {
1669
1878
  pushActive(convId, conv);
1670
1879
  }
1880
+ for (const [convId, conv] of getYeaftConversations()) {
1881
+ pushActive(convId, conv);
1882
+ }
1671
1883
  for (const [convId, conv] of getPiConversations()) {
1672
1884
  pushActive(convId, conv);
1673
1885
  }
@@ -1697,7 +1909,7 @@ function handleServerMessage(msg) {
1697
1909
  return {
1698
1910
  type: 'turn_status',
1699
1911
  conversationId: convId,
1700
- claudeSessionId: conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.piSessionId ?? null,
1912
+ claudeSessionId: conv.claudeSessionId ?? conv.copilotSessionId ?? conv.codexSessionId ?? conv.yeaftSessionId ?? conv.piSessionId ?? null,
1701
1913
  turnId: conv.turnId,
1702
1914
  status,
1703
1915
  isProcessing: isProc,
@@ -1732,6 +1944,15 @@ function handleServerMessage(msg) {
1732
1944
  }
1733
1945
  }
1734
1946
  }
1947
+ if (!found) {
1948
+ for (const [convId, conv] of getYeaftConversations()) {
1949
+ if (conv.turnId === m.turnId) {
1950
+ send(buildTurnStatus(convId, conv));
1951
+ found = true;
1952
+ break;
1953
+ }
1954
+ }
1955
+ }
1735
1956
  if (!found) {
1736
1957
  for (const [convId, conv] of getPiConversations()) {
1737
1958
  if (conv.turnId === m.turnId) {
@@ -1761,12 +1982,18 @@ function handleServerMessage(msg) {
1761
1982
  send(buildTurnStatus(m.conversationId, codexConv));
1762
1983
  }
1763
1984
  else {
1764
- const piConv = getPiConversation(m.conversationId);
1765
- if (piConv) {
1766
- send(buildTurnStatus(m.conversationId, piConv));
1985
+ const yeaftConv = getYeaftConversation(m.conversationId);
1986
+ if (yeaftConv) {
1987
+ send(buildTurnStatus(m.conversationId, yeaftConv));
1767
1988
  }
1768
1989
  else {
1769
- send({ type: 'turn_status', conversationId: m.conversationId, status: 'unknown', isProcessing: false, isCompacting: false, stateVersion: 0 });
1990
+ const piConv = getPiConversation(m.conversationId);
1991
+ if (piConv) {
1992
+ send(buildTurnStatus(m.conversationId, piConv));
1993
+ }
1994
+ else {
1995
+ send({ type: 'turn_status', conversationId: m.conversationId, status: 'unknown', isProcessing: false, isCompacting: false, stateVersion: 0 });
1996
+ }
1770
1997
  }
1771
1998
  }
1772
1999
  }
@@ -1967,6 +2194,20 @@ function handleServerMessage(msg) {
1967
2194
  setBackendType(newBackendType);
1968
2195
  console.log(`[AgentLink] Backend switched: ${oldBackendType} → ${newBackendType}`);
1969
2196
  }
2197
+ else if (oldBackendType === 'yeaft') {
2198
+ const existingYeaftConv = conversationId ? getYeaftConversation(conversationId) : undefined;
2199
+ if (existingYeaftConv?.turnActive) {
2200
+ existingYeaftConv.providerId = providerId;
2201
+ }
2202
+ else if (conversationId && existingYeaftConv) {
2203
+ const result = restartYeaftConversation(conversationId, { providerId });
2204
+ if (result.wasTurnActive)
2205
+ send({ type: 'execution_cancelled', conversationId });
2206
+ }
2207
+ else if (conversationId) {
2208
+ createYeaftPlaceholder(conversationId, { providerId });
2209
+ }
2210
+ }
1970
2211
  else if (oldBackendType === 'pi') {
1971
2212
  const existingPiConversation = conversationId ? getPiConversation(conversationId) : undefined;
1972
2213
  if (existingPiConversation?.turnActive) {
@@ -2032,14 +2273,14 @@ function handleServerMessage(msg) {
2032
2273
  }
2033
2274
  }
2034
2275
  send({ type: 'provider_changed', providerId, conversationId });
2035
- // Send backend_info when backend type changed
2036
- if (backendChanged) {
2037
- send({
2038
- type: 'backend_info',
2039
- backend: newBackendType,
2040
- capabilities: capabilitiesForBackend(newBackendType),
2041
- });
2042
- }
2276
+ // Capabilities are provider-scoped: wrappers and custom providers can
2277
+ // share a backend while exposing different command discovery behavior.
2278
+ send({
2279
+ type: 'backend_info',
2280
+ backend: newBackendType,
2281
+ providerId,
2282
+ capabilities: capabilitiesForBackend(newBackendType, providerId),
2283
+ });
2043
2284
  break;
2044
2285
  }
2045
2286
  case 'list_providers': {
@@ -2050,7 +2291,8 @@ function handleServerMessage(msg) {
2050
2291
  send({
2051
2292
  type: 'backend_info',
2052
2293
  backend: getBackendType(),
2053
- capabilities: capabilitiesForBackend(getBackendType()),
2294
+ providerId: defaultId,
2295
+ capabilities: capabilitiesForBackend(getBackendType(), defaultId),
2054
2296
  });
2055
2297
  break;
2056
2298
  }
@@ -2673,6 +2915,45 @@ function handleListCodexSessionsFn(dir, options = {}) {
2673
2915
  });
2674
2916
  }
2675
2917
  }
2918
+ function handleListYeaftSessionsFn(dir, options = {}) {
2919
+ try {
2920
+ const page = options.legacy
2921
+ ? { ...listYeaftSessionsPage(dir, { limit: Number.POSITIVE_INFINITY }), hasMore: false }
2922
+ : listYeaftSessionsPage(dir, {
2923
+ limit: options.limit ?? DEFAULT_SESSION_LIST_LIMIT,
2924
+ cursor: options.cursor,
2925
+ });
2926
+ if (options.legacy && page.sessions.length > LEGACY_SESSION_LIST_CAP) {
2927
+ page.sessions = page.sessions.slice(0, LEGACY_SESSION_LIST_CAP);
2928
+ }
2929
+ console.log(`[AgentLink] → sessions_list (${page.sessions.length} Yeaft sessions for ${dir}) hasMore=${page.hasMore}`);
2930
+ send({
2931
+ type: 'sessions_list',
2932
+ sessions: page.sessions,
2933
+ workDir: dir,
2934
+ hasMore: page.hasMore,
2935
+ nextCursor: page.nextCursor,
2936
+ indexState: 'ready',
2937
+ ...(options.mode ? { mode: options.mode } : {}),
2938
+ ...(options.requestId ? { requestId: options.requestId } : {}),
2939
+ providerId: options.providerId || 'yeaft',
2940
+ });
2941
+ }
2942
+ catch (err) {
2943
+ console.error('[AgentLink] listYeaftSessions failed:', err);
2944
+ send({
2945
+ type: 'sessions_list',
2946
+ sessions: [],
2947
+ workDir: dir,
2948
+ hasMore: false,
2949
+ indexState: 'ready',
2950
+ error: err.message,
2951
+ ...(options.mode ? { mode: options.mode } : {}),
2952
+ ...(options.requestId ? { requestId: options.requestId } : {}),
2953
+ providerId: options.providerId || 'yeaft',
2954
+ });
2955
+ }
2956
+ }
2676
2957
  function handleListPiSessionsFn(dir, options = {}) {
2677
2958
  try {
2678
2959
  const page = options.legacy