@yeaft/webchat-agent 0.1.860 → 0.1.863
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/connection/message-router.js +21 -1
- package/conversation.js +57 -2
- package/package.json +1 -1
- package/providers/acp-client.js +135 -0
- package/providers/base.js +17 -0
- package/providers/claude-code.js +12 -1
- package/providers/copilot-models.js +17 -0
- package/providers/copilot.js +484 -185
- package/yeaft/chats/chat-store.js +224 -0
- package/yeaft/conversation/persist.js +98 -0
- package/yeaft/dream-v2/apply.js +8 -0
- package/yeaft/dream-v2/prompts/index.js +3 -0
- package/yeaft/dream-v2/triage.js +16 -1
- package/yeaft/engine.js +24 -8
- package/yeaft/groups/pre-flow.js +13 -4
- package/yeaft/llm/models-dev.js +7 -1
- package/yeaft/memory/segment.js +1 -1
- package/yeaft/memory/store-v2.js +41 -2
- package/yeaft/web-bridge.js +326 -3
package/yeaft/web-bridge.js
CHANGED
|
@@ -45,6 +45,15 @@ import {
|
|
|
45
45
|
groupsRoot,
|
|
46
46
|
} from './groups/group-crud.js';
|
|
47
47
|
import { openGroup, loadGroupMeta } from './groups/group-store.js';
|
|
48
|
+
import {
|
|
49
|
+
createChat as createChatStore,
|
|
50
|
+
openChat,
|
|
51
|
+
listChats,
|
|
52
|
+
renameChat as renameChatStore,
|
|
53
|
+
archiveChat as archiveChatStore,
|
|
54
|
+
deleteChat as deleteChatStore,
|
|
55
|
+
touchChat,
|
|
56
|
+
} from './chats/chat-store.js';
|
|
48
57
|
import { loadGroupConfig, resolveGroupConfig, GroupConfigError } from './groups/group-config.js';
|
|
49
58
|
import { updateGroupConfig } from './groups/group-crud.js';
|
|
50
59
|
import { createCoordinator } from './groups/coordinator.js';
|
|
@@ -1254,12 +1263,13 @@ function resolveGroupDefaultVpId(groupId) {
|
|
|
1254
1263
|
}
|
|
1255
1264
|
}
|
|
1256
1265
|
|
|
1257
|
-
function sendYeaftOutput(data, { groupId, vpId, turnId, threadId } = {}) {
|
|
1258
|
-
const resolvedVpId = vpId || resolveGroupDefaultVpId(groupId);
|
|
1266
|
+
function sendYeaftOutput(data, { groupId, chatId, vpId, turnId, threadId } = {}) {
|
|
1267
|
+
const resolvedVpId = vpId || (groupId ? resolveGroupDefaultVpId(groupId) : null);
|
|
1259
1268
|
sendToServer({
|
|
1260
1269
|
type: 'yeaft_output',
|
|
1261
1270
|
conversationId: yeaftConversationId,
|
|
1262
1271
|
...(groupId ? { groupId } : {}),
|
|
1272
|
+
...(chatId ? { chatId } : {}),
|
|
1263
1273
|
...(resolvedVpId ? { vpId: resolvedVpId } : {}),
|
|
1264
1274
|
...(turnId ? { turnId } : {}),
|
|
1265
1275
|
...(threadId ? { threadId } : {}),
|
|
@@ -1268,11 +1278,12 @@ function sendYeaftOutput(data, { groupId, vpId, turnId, threadId } = {}) {
|
|
|
1268
1278
|
}
|
|
1269
1279
|
|
|
1270
1280
|
/** Send a yeaft_output event (non-claude_output metadata). */
|
|
1271
|
-
function sendYeaftEvent(event, { groupId, vpId, turnId, threadId } = {}) {
|
|
1281
|
+
function sendYeaftEvent(event, { groupId, chatId, vpId, turnId, threadId } = {}) {
|
|
1272
1282
|
sendToServer({
|
|
1273
1283
|
type: 'yeaft_output',
|
|
1274
1284
|
conversationId: yeaftConversationId,
|
|
1275
1285
|
...(groupId ? { groupId } : {}),
|
|
1286
|
+
...(chatId ? { chatId } : {}),
|
|
1276
1287
|
...(vpId ? { vpId } : {}),
|
|
1277
1288
|
...(turnId ? { turnId } : {}),
|
|
1278
1289
|
...(threadId ? { threadId } : {}),
|
|
@@ -1665,6 +1676,318 @@ export function handleYeaftSetDefaultVp(msg) {
|
|
|
1665
1676
|
}
|
|
1666
1677
|
}
|
|
1667
1678
|
|
|
1679
|
+
// ─── Yeaft Chat Mode ──────────────────────────────────────────
|
|
1680
|
+
//
|
|
1681
|
+
// 1:1 conversation between the user and a single VP. Reuses the engine,
|
|
1682
|
+
// memory, tool, and adapter stacks; bypasses the group coordinator and
|
|
1683
|
+
// fan-out machinery entirely. Persisted under `~/.yeaft/chats/<chatId>/`.
|
|
1684
|
+
|
|
1685
|
+
const chatEngines = new Map(); // key: `${chatId}::${vpId}` → Engine
|
|
1686
|
+
const chatAborts = new Map(); // key: chatId → AbortController for the in-flight turn
|
|
1687
|
+
|
|
1688
|
+
function chatsRootFor(yeaftDir) {
|
|
1689
|
+
return join(yeaftDir, 'chats');
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function snapshotChats(yeaftDir) {
|
|
1693
|
+
if (!yeaftDir) return [];
|
|
1694
|
+
try { return listChats(chatsRootFor(yeaftDir)); }
|
|
1695
|
+
catch { return []; }
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
function sendChatCrudResult(payload) {
|
|
1699
|
+
sendYeaftEvent({ type: 'chat_crud_result', ...payload });
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function sendChatSnapshotBroadcast() {
|
|
1703
|
+
try {
|
|
1704
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1705
|
+
if (!yeaftDir) return;
|
|
1706
|
+
sendYeaftEvent({ type: 'chat_list_updated', chats: snapshotChats(yeaftDir) });
|
|
1707
|
+
} catch (err) {
|
|
1708
|
+
console.warn('[Yeaft] sendChatSnapshotBroadcast failed:', err?.message || err);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
function chatErrorPayload(err) {
|
|
1713
|
+
return {
|
|
1714
|
+
code: (err && err.code) || 'unknown',
|
|
1715
|
+
chatId: err && err.chatId,
|
|
1716
|
+
message: err && err.message,
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
export function handleYeaftListChats(msg) {
|
|
1721
|
+
const requestId = msg && msg.requestId;
|
|
1722
|
+
try {
|
|
1723
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1724
|
+
sendChatCrudResult({ op: 'list', requestId, ok: true, chats: snapshotChats(yeaftDir) });
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
sendChatCrudResult({ op: 'list', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
export function handleYeaftCreateChat(msg) {
|
|
1731
|
+
const requestId = msg && msg.requestId;
|
|
1732
|
+
const payload = (msg && msg.payload) || {};
|
|
1733
|
+
try {
|
|
1734
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1735
|
+
if (!yeaftDir) throw new Error('no yeaft directory configured');
|
|
1736
|
+
if (!payload.vpId) throw new Error('vpId required');
|
|
1737
|
+
const root = chatsRootFor(yeaftDir);
|
|
1738
|
+
const chatId = (payload.id && String(payload.id).trim())
|
|
1739
|
+
|| `chat_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1740
|
+
const h = createChatStore(root, {
|
|
1741
|
+
id: chatId,
|
|
1742
|
+
vpId: payload.vpId,
|
|
1743
|
+
displayName: payload.displayName,
|
|
1744
|
+
workDir: payload.workDir,
|
|
1745
|
+
});
|
|
1746
|
+
const meta = h.getMeta();
|
|
1747
|
+
h.close();
|
|
1748
|
+
sendChatCrudResult({ op: 'create', requestId, ok: true, chat: meta });
|
|
1749
|
+
sendChatSnapshotBroadcast();
|
|
1750
|
+
} catch (err) {
|
|
1751
|
+
sendChatCrudResult({ op: 'create', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
export function handleYeaftRenameChat(msg) {
|
|
1756
|
+
const requestId = msg && msg.requestId;
|
|
1757
|
+
const chatId = msg && msg.chatId;
|
|
1758
|
+
const displayName = msg && msg.displayName;
|
|
1759
|
+
try {
|
|
1760
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1761
|
+
const chat = renameChatStore(chatsRootFor(yeaftDir), chatId, displayName);
|
|
1762
|
+
sendChatCrudResult({ op: 'rename', requestId, ok: true, chat });
|
|
1763
|
+
sendChatSnapshotBroadcast();
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
sendChatCrudResult({ op: 'rename', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
export function handleYeaftArchiveChat(msg) {
|
|
1770
|
+
const requestId = msg && msg.requestId;
|
|
1771
|
+
const chatId = msg && msg.chatId;
|
|
1772
|
+
try {
|
|
1773
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1774
|
+
const ok = archiveChatStore(chatsRootFor(yeaftDir), chatId);
|
|
1775
|
+
if (!ok) throw new Error(`chat ${chatId} not found`);
|
|
1776
|
+
invalidateChatRuntime(chatId);
|
|
1777
|
+
sendChatCrudResult({ op: 'archive', requestId, ok: true, chatId });
|
|
1778
|
+
sendChatSnapshotBroadcast();
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
sendChatCrudResult({ op: 'archive', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
export function handleYeaftDeleteChat(msg) {
|
|
1785
|
+
const requestId = msg && msg.requestId;
|
|
1786
|
+
const chatId = msg && msg.chatId;
|
|
1787
|
+
try {
|
|
1788
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1789
|
+
const ok = deleteChatStore(chatsRootFor(yeaftDir), chatId);
|
|
1790
|
+
if (!ok) throw new Error(`chat ${chatId} not found`);
|
|
1791
|
+
invalidateChatRuntime(chatId);
|
|
1792
|
+
sendChatCrudResult({ op: 'delete', requestId, ok: true, chatId });
|
|
1793
|
+
sendChatSnapshotBroadcast();
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
sendChatCrudResult({ op: 'delete', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
function invalidateChatRuntime(chatId) {
|
|
1800
|
+
if (!chatId) return;
|
|
1801
|
+
const prefix = `${chatId}::`;
|
|
1802
|
+
for (const k of Array.from(chatEngines.keys())) {
|
|
1803
|
+
if (k.startsWith(prefix)) chatEngines.delete(k);
|
|
1804
|
+
}
|
|
1805
|
+
const ctrl = chatAborts.get(chatId);
|
|
1806
|
+
if (ctrl) {
|
|
1807
|
+
try { ctrl.abort(); } catch { /* best-effort */ }
|
|
1808
|
+
chatAborts.delete(chatId);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
function getOrCreateChatEngine(chatId, vpId) {
|
|
1813
|
+
const key = `${chatId}::${vpId}`;
|
|
1814
|
+
let eng = chatEngines.get(key);
|
|
1815
|
+
if (eng) return eng;
|
|
1816
|
+
if (!session) throw new Error('getOrCreateChatEngine: session not loaded');
|
|
1817
|
+
eng = new Engine({
|
|
1818
|
+
adapter: session.adapter,
|
|
1819
|
+
trace: session.trace,
|
|
1820
|
+
config: session.config,
|
|
1821
|
+
conversationStore: session.conversationStore,
|
|
1822
|
+
memoryIndex: session.memoryIndex || null,
|
|
1823
|
+
amsRegistry: session.amsRegistry,
|
|
1824
|
+
toolRegistry: session.toolRegistry,
|
|
1825
|
+
skillManager: session.skillManager,
|
|
1826
|
+
mcpManager: session.mcpManager,
|
|
1827
|
+
yeaftDir: session.yeaftDir,
|
|
1828
|
+
toolStats: session.toolStats || null,
|
|
1829
|
+
chatId,
|
|
1830
|
+
vpId,
|
|
1831
|
+
});
|
|
1832
|
+
chatEngines.set(key, eng);
|
|
1833
|
+
return eng;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
/**
|
|
1837
|
+
* Send a single user turn in a chat session. 1:1 — no fan-out, no
|
|
1838
|
+
* coordinator, no @-mention dispatch. Engine events are forwarded as
|
|
1839
|
+
* yeaft_output with `chatId` instead of `groupId`.
|
|
1840
|
+
*/
|
|
1841
|
+
export async function handleYeaftChatSend(msg) {
|
|
1842
|
+
if (!msg || typeof msg !== 'object') return;
|
|
1843
|
+
const chatId = typeof msg.chatId === 'string' ? msg.chatId.trim() : '';
|
|
1844
|
+
const text = typeof msg.text === 'string' ? msg.text : '';
|
|
1845
|
+
if (!chatId) return;
|
|
1846
|
+
if (!text.trim()) return;
|
|
1847
|
+
|
|
1848
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1849
|
+
if (!yeaftDir) {
|
|
1850
|
+
sendYeaftOutput({
|
|
1851
|
+
type: 'assistant',
|
|
1852
|
+
message: { content: [{ type: 'text', text: '⚠️ Yeaft session error: no yeaft directory configured.' }] },
|
|
1853
|
+
}, { chatId });
|
|
1854
|
+
sendYeaftOutput({ type: 'result', result_text: '' }, { chatId });
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
await ensureSessionLoaded();
|
|
1859
|
+
|
|
1860
|
+
const root = chatsRootFor(yeaftDir);
|
|
1861
|
+
let handle;
|
|
1862
|
+
try { handle = openChat(root, chatId); }
|
|
1863
|
+
catch (err) {
|
|
1864
|
+
sendYeaftOutput({
|
|
1865
|
+
type: 'assistant',
|
|
1866
|
+
message: { content: [{ type: 'text', text: `⚠️ ${err.message}` }] },
|
|
1867
|
+
}, { chatId });
|
|
1868
|
+
sendYeaftOutput({ type: 'result', result_text: '' }, { chatId });
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
const meta = handle.getMeta();
|
|
1872
|
+
if (!meta) {
|
|
1873
|
+
handle.close();
|
|
1874
|
+
sendYeaftOutput({
|
|
1875
|
+
type: 'assistant',
|
|
1876
|
+
message: { content: [{ type: 'text', text: `⚠️ Chat ${chatId} not found.` }] },
|
|
1877
|
+
}, { chatId });
|
|
1878
|
+
sendYeaftOutput({ type: 'result', result_text: '' }, { chatId });
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
const vpId = meta.vpId;
|
|
1882
|
+
|
|
1883
|
+
// Persist attachments + build LLM-side multimodal parts. Same flow as
|
|
1884
|
+
// the group path (handleYeaftGroupChat) so chat mode isn't a
|
|
1885
|
+
// second-class citizen for images / files.
|
|
1886
|
+
const inboundFiles = Array.isArray(msg.files) ? msg.files : [];
|
|
1887
|
+
let attachmentBundle = { promptAttachments: [], promptSuffix: '', promptParts: [], failed: [] };
|
|
1888
|
+
if (inboundFiles.length > 0) {
|
|
1889
|
+
try {
|
|
1890
|
+
attachmentBundle = persistYeaftAttachments(inboundFiles, { subdir: `chat-${chatId}` });
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
console.warn('[Yeaft] yeaft_chat_send: attachment persist failed', err?.message || err);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
if (Array.isArray(attachmentBundle.failed) && attachmentBundle.failed.length > 0) {
|
|
1896
|
+
const detail = attachmentBundle.failed.map((f) => ` - ${f.name}: ${f.error}`).join('\n');
|
|
1897
|
+
sendYeaftOutput({
|
|
1898
|
+
type: 'assistant',
|
|
1899
|
+
message: { content: [{ type: 'text', text: `⚠️ ${attachmentBundle.failed.length} file(s) could not be attached:\n${detail}` }] },
|
|
1900
|
+
}, { chatId });
|
|
1901
|
+
}
|
|
1902
|
+
const persistedAttachments = attachmentsForPersistence(attachmentBundle.promptAttachments);
|
|
1903
|
+
|
|
1904
|
+
// Append the user message to the chat log so subsequent reads see it.
|
|
1905
|
+
try {
|
|
1906
|
+
handle.appendMessage({ from: 'user', role: 'user', text, meta: { attachments: persistedAttachments } });
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
console.warn('[Yeaft] chat appendMessage failed:', err?.message || err);
|
|
1909
|
+
} finally {
|
|
1910
|
+
handle.close();
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
const turnId = `t_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1914
|
+
const envelope = { chatId, vpId, turnId };
|
|
1915
|
+
|
|
1916
|
+
// Cancel any in-flight turn on this chat — 1:1 has no parallelism.
|
|
1917
|
+
const prior = chatAborts.get(chatId);
|
|
1918
|
+
if (prior) { try { prior.abort(); } catch { /* ignore */ } }
|
|
1919
|
+
const abort = new AbortController();
|
|
1920
|
+
chatAborts.set(chatId, abort);
|
|
1921
|
+
|
|
1922
|
+
sendYeaftEvent({ type: 'vp_turn_start', vpId, threadId: 'main', turnId, chatId, title: meta.displayName || '' }, envelope);
|
|
1923
|
+
|
|
1924
|
+
let queryTimer = null;
|
|
1925
|
+
const resetQueryTimer = () => {
|
|
1926
|
+
if (queryTimer) clearTimeout(queryTimer);
|
|
1927
|
+
queryTimer = setTimeout(() => {
|
|
1928
|
+
if (!abort.signal.aborted) {
|
|
1929
|
+
try { abort.abort(); } catch { /* ignore */ }
|
|
1930
|
+
}
|
|
1931
|
+
}, QUERY_TIMEOUT_MS);
|
|
1932
|
+
};
|
|
1933
|
+
resetQueryTimer();
|
|
1934
|
+
|
|
1935
|
+
try {
|
|
1936
|
+
const assistantTextParts = [];
|
|
1937
|
+
const toolCallsAccum = [];
|
|
1938
|
+
const toolResultsAccum = [];
|
|
1939
|
+
const thinkingBlocksAccum = [];
|
|
1940
|
+
const appendedUserPrompts = [];
|
|
1941
|
+
|
|
1942
|
+
const eng = getOrCreateChatEngine(chatId, vpId);
|
|
1943
|
+
const handlerCtx = {
|
|
1944
|
+
assistantTextParts,
|
|
1945
|
+
toolCallsAccum,
|
|
1946
|
+
toolResultsAccum,
|
|
1947
|
+
thinkingBlocksAccum,
|
|
1948
|
+
resetQueryTimer,
|
|
1949
|
+
chatId,
|
|
1950
|
+
vpId,
|
|
1951
|
+
turnId,
|
|
1952
|
+
threadId: 'main',
|
|
1953
|
+
thread: null,
|
|
1954
|
+
appendedUserPrompts,
|
|
1955
|
+
};
|
|
1956
|
+
|
|
1957
|
+
for await (const event of eng.query({
|
|
1958
|
+
prompt: text,
|
|
1959
|
+
promptParts: attachmentBundle.promptParts && attachmentBundle.promptParts.length > 0 ? attachmentBundle.promptParts : null,
|
|
1960
|
+
signal: abort.signal,
|
|
1961
|
+
userAlreadyPersisted: false,
|
|
1962
|
+
threadId: 'main',
|
|
1963
|
+
senderVpId: vpId,
|
|
1964
|
+
})) {
|
|
1965
|
+
resetQueryTimer();
|
|
1966
|
+
handleEngineEvent(event, handlerCtx);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
sendYeaftOutput({ type: 'assistant', message: { content: [] } }, envelope);
|
|
1970
|
+
sendYeaftOutput({ type: 'result', result_text: '' }, envelope);
|
|
1971
|
+
|
|
1972
|
+
try { touchChat(root, chatId); } catch { /* best-effort */ }
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
const isAbort = err && (err.name === 'AbortError' || err.name === 'LLMAbortError');
|
|
1975
|
+
if (isAbort) {
|
|
1976
|
+
sendYeaftOutput({ type: 'result', result_text: '', stopped: true }, envelope);
|
|
1977
|
+
} else {
|
|
1978
|
+
console.error('[Yeaft] chat-send error:', err);
|
|
1979
|
+
sendYeaftOutput({
|
|
1980
|
+
type: 'assistant',
|
|
1981
|
+
message: { content: [{ type: 'text', text: `⚠️ Chat error: ${err.message}` }] },
|
|
1982
|
+
}, envelope);
|
|
1983
|
+
sendYeaftOutput({ type: 'result', result_text: '' }, envelope);
|
|
1984
|
+
}
|
|
1985
|
+
} finally {
|
|
1986
|
+
if (queryTimer) clearTimeout(queryTimer);
|
|
1987
|
+
if (chatAborts.get(chatId) === abort) chatAborts.delete(chatId);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1668
1991
|
/**
|
|
1669
1992
|
* Install the dream pipeline progress sink and runtime settings bridge.
|
|
1670
1993
|
* Thread scheduling is owned by the group VP runtime below, not by mutable
|