@yeaft/webchat-agent 0.1.868 → 0.1.870
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 +1 -21
- package/package.json +1 -1
- package/yeaft/web-bridge.js +1 -342
- package/yeaft/chats/chat-store.js +0 -224
|
@@ -37,7 +37,7 @@ import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
|
37
37
|
import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
38
38
|
import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
|
|
39
39
|
import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
|
|
40
|
-
import { handleYeaftGroupChat, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListGroups, handleYeaftCreateGroup, handleYeaftRenameGroup, handleYeaftUpdateGroup, handleYeaftUpdateGroupConfig, handleYeaftArchiveGroup, handleYeaftDeleteGroup, handleYeaftAddMember, handleYeaftRemoveMember, handleYeaftSetDefaultVp,
|
|
40
|
+
import { handleYeaftGroupChat, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListGroups, handleYeaftCreateGroup, handleYeaftRenameGroup, handleYeaftUpdateGroup, handleYeaftUpdateGroupConfig, handleYeaftArchiveGroup, handleYeaftDeleteGroup, handleYeaftAddMember, handleYeaftRemoveMember, handleYeaftSetDefaultVp, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, broadcastLanguageChange } from '../yeaft/web-bridge.js';
|
|
41
41
|
|
|
42
42
|
export async function handleMessage(msg) {
|
|
43
43
|
switch (msg.type) {
|
|
@@ -565,26 +565,6 @@ export async function handleMessage(msg) {
|
|
|
565
565
|
handleYeaftGroupChat(msg);
|
|
566
566
|
break;
|
|
567
567
|
|
|
568
|
-
// Yeaft Chat Mode (1:1 single-VP) — separate from group fan-out.
|
|
569
|
-
case 'yeaft_chat_send':
|
|
570
|
-
await handleYeaftChatSend(msg);
|
|
571
|
-
break;
|
|
572
|
-
case 'yeaft_list_chats':
|
|
573
|
-
await handleYeaftListChats(msg);
|
|
574
|
-
break;
|
|
575
|
-
case 'yeaft_create_chat':
|
|
576
|
-
await handleYeaftCreateChat(msg);
|
|
577
|
-
break;
|
|
578
|
-
case 'yeaft_rename_chat':
|
|
579
|
-
await handleYeaftRenameChat(msg);
|
|
580
|
-
break;
|
|
581
|
-
case 'yeaft_archive_chat':
|
|
582
|
-
await handleYeaftArchiveChat(msg);
|
|
583
|
-
break;
|
|
584
|
-
case 'yeaft_delete_chat':
|
|
585
|
-
await handleYeaftDeleteChat(msg);
|
|
586
|
-
break;
|
|
587
|
-
|
|
588
568
|
// wave-6b: manual dream trigger from VP detail page
|
|
589
569
|
case 'yeaft_dream_trigger':
|
|
590
570
|
case 'unify_dream_trigger':
|
package/package.json
CHANGED
package/yeaft/web-bridge.js
CHANGED
|
@@ -45,15 +45,6 @@ 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';
|
|
57
48
|
import { loadGroupConfig, resolveGroupConfig, GroupConfigError } from './groups/group-config.js';
|
|
58
49
|
import { updateGroupConfig } from './groups/group-crud.js';
|
|
59
50
|
import { createCoordinator } from './groups/coordinator.js';
|
|
@@ -1676,53 +1667,12 @@ export function handleYeaftSetDefaultVp(msg) {
|
|
|
1676
1667
|
}
|
|
1677
1668
|
}
|
|
1678
1669
|
|
|
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
1670
|
|
|
1720
1671
|
/**
|
|
1721
1672
|
* Build the vpPersona payload threaded into engine.query so the worker
|
|
1722
1673
|
* system prompt carries the VP's identity/role/persona/planInstruction.
|
|
1723
1674
|
* Returns null on miss — callers treat that as "use generic prompt".
|
|
1724
|
-
*
|
|
1725
|
-
* set stays in lockstep.
|
|
1675
|
+
* Used by the group fan-out path (buildVpQueryOpts).
|
|
1726
1676
|
*/
|
|
1727
1677
|
function buildVpPersona(vpId) {
|
|
1728
1678
|
if (!vpId) return null;
|
|
@@ -1743,297 +1693,6 @@ function buildVpPersona(vpId) {
|
|
|
1743
1693
|
}
|
|
1744
1694
|
}
|
|
1745
1695
|
|
|
1746
|
-
export function handleYeaftListChats(msg) {
|
|
1747
|
-
const requestId = msg && msg.requestId;
|
|
1748
|
-
try {
|
|
1749
|
-
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1750
|
-
sendChatCrudResult({ op: 'list', requestId, ok: true, chats: snapshotChats(yeaftDir) });
|
|
1751
|
-
} catch (err) {
|
|
1752
|
-
sendChatCrudResult({ op: 'list', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
export function handleYeaftCreateChat(msg) {
|
|
1757
|
-
const requestId = msg && msg.requestId;
|
|
1758
|
-
// Accept fields at top-level (current wire shape) AND inside `payload`
|
|
1759
|
-
// (legacy / future) — keeps backwards compatibility cheap.
|
|
1760
|
-
const top = msg || {};
|
|
1761
|
-
const payload = (msg && msg.payload) || {};
|
|
1762
|
-
const displayName = payload.displayName || top.displayName;
|
|
1763
|
-
const workDir = payload.workDir || top.workDir;
|
|
1764
|
-
const explicitId = payload.id || top.id;
|
|
1765
|
-
// Chat mode is 1:1 with the built-in Omni assistant by default. The
|
|
1766
|
-
// VP picker is gone from the UI; callers may still override vpId to
|
|
1767
|
-
// bind a chat to a specialist, but the default is omni.
|
|
1768
|
-
const vpId = payload.vpId || top.vpId || 'omni';
|
|
1769
|
-
try {
|
|
1770
|
-
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1771
|
-
if (!yeaftDir) throw new Error('no yeaft directory configured');
|
|
1772
|
-
// Fail loud at create time if the requested VP (default: omni) is
|
|
1773
|
-
// not installed — otherwise the chat would silently degrade to a
|
|
1774
|
-
// generic prompt at first send.
|
|
1775
|
-
if (!buildVpPersona(vpId)) {
|
|
1776
|
-
throw new Error(`VP '${vpId}' is not installed`);
|
|
1777
|
-
}
|
|
1778
|
-
const root = chatsRootFor(yeaftDir);
|
|
1779
|
-
const chatId = (explicitId && String(explicitId).trim())
|
|
1780
|
-
|| `chat_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1781
|
-
const h = createChatStore(root, {
|
|
1782
|
-
id: chatId,
|
|
1783
|
-
vpId,
|
|
1784
|
-
displayName,
|
|
1785
|
-
workDir,
|
|
1786
|
-
});
|
|
1787
|
-
const meta = h.getMeta();
|
|
1788
|
-
h.close();
|
|
1789
|
-
sendChatCrudResult({ op: 'create', requestId, ok: true, chat: meta });
|
|
1790
|
-
sendChatSnapshotBroadcast();
|
|
1791
|
-
} catch (err) {
|
|
1792
|
-
sendChatCrudResult({ op: 'create', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
export function handleYeaftRenameChat(msg) {
|
|
1797
|
-
const requestId = msg && msg.requestId;
|
|
1798
|
-
const chatId = msg && msg.chatId;
|
|
1799
|
-
const displayName = msg && msg.displayName;
|
|
1800
|
-
try {
|
|
1801
|
-
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1802
|
-
const chat = renameChatStore(chatsRootFor(yeaftDir), chatId, displayName);
|
|
1803
|
-
sendChatCrudResult({ op: 'rename', requestId, ok: true, chat });
|
|
1804
|
-
sendChatSnapshotBroadcast();
|
|
1805
|
-
} catch (err) {
|
|
1806
|
-
sendChatCrudResult({ op: 'rename', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
export function handleYeaftArchiveChat(msg) {
|
|
1811
|
-
const requestId = msg && msg.requestId;
|
|
1812
|
-
const chatId = msg && msg.chatId;
|
|
1813
|
-
try {
|
|
1814
|
-
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1815
|
-
const ok = archiveChatStore(chatsRootFor(yeaftDir), chatId);
|
|
1816
|
-
if (!ok) throw new Error(`chat ${chatId} not found`);
|
|
1817
|
-
invalidateChatRuntime(chatId);
|
|
1818
|
-
sendChatCrudResult({ op: 'archive', requestId, ok: true, chatId });
|
|
1819
|
-
sendChatSnapshotBroadcast();
|
|
1820
|
-
} catch (err) {
|
|
1821
|
-
sendChatCrudResult({ op: 'archive', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
export function handleYeaftDeleteChat(msg) {
|
|
1826
|
-
const requestId = msg && msg.requestId;
|
|
1827
|
-
const chatId = msg && msg.chatId;
|
|
1828
|
-
try {
|
|
1829
|
-
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1830
|
-
const ok = deleteChatStore(chatsRootFor(yeaftDir), chatId);
|
|
1831
|
-
if (!ok) throw new Error(`chat ${chatId} not found`);
|
|
1832
|
-
invalidateChatRuntime(chatId);
|
|
1833
|
-
sendChatCrudResult({ op: 'delete', requestId, ok: true, chatId });
|
|
1834
|
-
sendChatSnapshotBroadcast();
|
|
1835
|
-
} catch (err) {
|
|
1836
|
-
sendChatCrudResult({ op: 'delete', requestId, ok: false, error: chatErrorPayload(err) });
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
function invalidateChatRuntime(chatId) {
|
|
1841
|
-
if (!chatId) return;
|
|
1842
|
-
const prefix = `${chatId}::`;
|
|
1843
|
-
for (const k of Array.from(chatEngines.keys())) {
|
|
1844
|
-
if (k.startsWith(prefix)) chatEngines.delete(k);
|
|
1845
|
-
}
|
|
1846
|
-
const ctrl = chatAborts.get(chatId);
|
|
1847
|
-
if (ctrl) {
|
|
1848
|
-
try { ctrl.abort(); } catch { /* best-effort */ }
|
|
1849
|
-
chatAborts.delete(chatId);
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
function getOrCreateChatEngine(chatId, vpId) {
|
|
1854
|
-
const key = `${chatId}::${vpId}`;
|
|
1855
|
-
let eng = chatEngines.get(key);
|
|
1856
|
-
if (eng) return eng;
|
|
1857
|
-
if (!session) throw new Error('getOrCreateChatEngine: session not loaded');
|
|
1858
|
-
eng = new Engine({
|
|
1859
|
-
adapter: session.adapter,
|
|
1860
|
-
trace: session.trace,
|
|
1861
|
-
config: session.config,
|
|
1862
|
-
conversationStore: session.conversationStore,
|
|
1863
|
-
memoryIndex: session.memoryIndex || null,
|
|
1864
|
-
amsRegistry: session.amsRegistry,
|
|
1865
|
-
toolRegistry: session.toolRegistry,
|
|
1866
|
-
skillManager: session.skillManager,
|
|
1867
|
-
mcpManager: session.mcpManager,
|
|
1868
|
-
yeaftDir: session.yeaftDir,
|
|
1869
|
-
toolStats: session.toolStats || null,
|
|
1870
|
-
chatId,
|
|
1871
|
-
vpId,
|
|
1872
|
-
});
|
|
1873
|
-
chatEngines.set(key, eng);
|
|
1874
|
-
return eng;
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
/**
|
|
1878
|
-
* Send a single user turn in a chat session. 1:1 — no fan-out, no
|
|
1879
|
-
* coordinator, no @-mention dispatch. Engine events are forwarded as
|
|
1880
|
-
* yeaft_output with `chatId` instead of `groupId`.
|
|
1881
|
-
*/
|
|
1882
|
-
export async function handleYeaftChatSend(msg) {
|
|
1883
|
-
if (!msg || typeof msg !== 'object') return;
|
|
1884
|
-
const chatId = typeof msg.chatId === 'string' ? msg.chatId.trim() : '';
|
|
1885
|
-
const text = typeof msg.text === 'string' ? msg.text : '';
|
|
1886
|
-
if (!chatId) return;
|
|
1887
|
-
if (!text.trim()) return;
|
|
1888
|
-
|
|
1889
|
-
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1890
|
-
if (!yeaftDir) {
|
|
1891
|
-
sendYeaftOutput({
|
|
1892
|
-
type: 'assistant',
|
|
1893
|
-
message: { content: [{ type: 'text', text: '⚠️ Yeaft session error: no yeaft directory configured.' }] },
|
|
1894
|
-
}, { chatId });
|
|
1895
|
-
sendYeaftOutput({ type: 'result', result_text: '' }, { chatId });
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
await ensureSessionLoaded();
|
|
1900
|
-
|
|
1901
|
-
const root = chatsRootFor(yeaftDir);
|
|
1902
|
-
let handle;
|
|
1903
|
-
try { handle = openChat(root, chatId); }
|
|
1904
|
-
catch (err) {
|
|
1905
|
-
sendYeaftOutput({
|
|
1906
|
-
type: 'assistant',
|
|
1907
|
-
message: { content: [{ type: 'text', text: `⚠️ ${err.message}` }] },
|
|
1908
|
-
}, { chatId });
|
|
1909
|
-
sendYeaftOutput({ type: 'result', result_text: '' }, { chatId });
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
|
-
const meta = handle.getMeta();
|
|
1913
|
-
if (!meta) {
|
|
1914
|
-
handle.close();
|
|
1915
|
-
sendYeaftOutput({
|
|
1916
|
-
type: 'assistant',
|
|
1917
|
-
message: { content: [{ type: 'text', text: `⚠️ Chat ${chatId} not found.` }] },
|
|
1918
|
-
}, { chatId });
|
|
1919
|
-
sendYeaftOutput({ type: 'result', result_text: '' }, { chatId });
|
|
1920
|
-
return;
|
|
1921
|
-
}
|
|
1922
|
-
const vpId = meta.vpId;
|
|
1923
|
-
|
|
1924
|
-
// Persist attachments + build LLM-side multimodal parts. Same flow as
|
|
1925
|
-
// the group path (handleYeaftGroupChat) so chat mode isn't a
|
|
1926
|
-
// second-class citizen for images / files.
|
|
1927
|
-
const inboundFiles = Array.isArray(msg.files) ? msg.files : [];
|
|
1928
|
-
let attachmentBundle = { promptAttachments: [], promptSuffix: '', promptParts: [], failed: [] };
|
|
1929
|
-
if (inboundFiles.length > 0) {
|
|
1930
|
-
try {
|
|
1931
|
-
attachmentBundle = persistYeaftAttachments(inboundFiles, { subdir: `chat-${chatId}` });
|
|
1932
|
-
} catch (err) {
|
|
1933
|
-
console.warn('[Yeaft] yeaft_chat_send: attachment persist failed', err?.message || err);
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
if (Array.isArray(attachmentBundle.failed) && attachmentBundle.failed.length > 0) {
|
|
1937
|
-
const detail = attachmentBundle.failed.map((f) => ` - ${f.name}: ${f.error}`).join('\n');
|
|
1938
|
-
sendYeaftOutput({
|
|
1939
|
-
type: 'assistant',
|
|
1940
|
-
message: { content: [{ type: 'text', text: `⚠️ ${attachmentBundle.failed.length} file(s) could not be attached:\n${detail}` }] },
|
|
1941
|
-
}, { chatId });
|
|
1942
|
-
}
|
|
1943
|
-
const persistedAttachments = attachmentsForPersistence(attachmentBundle.promptAttachments);
|
|
1944
|
-
|
|
1945
|
-
// Append the user message to the chat log so subsequent reads see it.
|
|
1946
|
-
try {
|
|
1947
|
-
handle.appendMessage({ from: 'user', role: 'user', text, meta: { attachments: persistedAttachments } });
|
|
1948
|
-
} catch (err) {
|
|
1949
|
-
console.warn('[Yeaft] chat appendMessage failed:', err?.message || err);
|
|
1950
|
-
} finally {
|
|
1951
|
-
handle.close();
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
const turnId = `t_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1955
|
-
const envelope = { chatId, vpId, turnId };
|
|
1956
|
-
|
|
1957
|
-
// Cancel any in-flight turn on this chat — 1:1 has no parallelism.
|
|
1958
|
-
const prior = chatAborts.get(chatId);
|
|
1959
|
-
if (prior) { try { prior.abort(); } catch { /* ignore */ } }
|
|
1960
|
-
const abort = new AbortController();
|
|
1961
|
-
chatAborts.set(chatId, abort);
|
|
1962
|
-
|
|
1963
|
-
sendYeaftEvent({ type: 'vp_turn_start', vpId, threadId: 'main', turnId, chatId, title: meta.displayName || '' }, envelope);
|
|
1964
|
-
|
|
1965
|
-
let queryTimer = null;
|
|
1966
|
-
const resetQueryTimer = () => {
|
|
1967
|
-
if (queryTimer) clearTimeout(queryTimer);
|
|
1968
|
-
queryTimer = setTimeout(() => {
|
|
1969
|
-
if (!abort.signal.aborted) {
|
|
1970
|
-
try { abort.abort(); } catch { /* ignore */ }
|
|
1971
|
-
}
|
|
1972
|
-
}, QUERY_TIMEOUT_MS);
|
|
1973
|
-
};
|
|
1974
|
-
resetQueryTimer();
|
|
1975
|
-
|
|
1976
|
-
try {
|
|
1977
|
-
const assistantTextParts = [];
|
|
1978
|
-
const toolCallsAccum = [];
|
|
1979
|
-
const toolResultsAccum = [];
|
|
1980
|
-
const thinkingBlocksAccum = [];
|
|
1981
|
-
const appendedUserPrompts = [];
|
|
1982
|
-
|
|
1983
|
-
const eng = getOrCreateChatEngine(chatId, vpId);
|
|
1984
|
-
const handlerCtx = {
|
|
1985
|
-
assistantTextParts,
|
|
1986
|
-
toolCallsAccum,
|
|
1987
|
-
toolResultsAccum,
|
|
1988
|
-
thinkingBlocksAccum,
|
|
1989
|
-
resetQueryTimer,
|
|
1990
|
-
chatId,
|
|
1991
|
-
vpId,
|
|
1992
|
-
turnId,
|
|
1993
|
-
threadId: 'main',
|
|
1994
|
-
thread: null,
|
|
1995
|
-
appendedUserPrompts,
|
|
1996
|
-
};
|
|
1997
|
-
|
|
1998
|
-
// Load the VP persona (defaults to omni) so the engine's worker
|
|
1999
|
-
// prompt has the right identity/role/persona blocks. Without this,
|
|
2000
|
-
// chat mode runs with a generic system prompt instead of Omni.
|
|
2001
|
-
const vpPersona = buildVpPersona(vpId);
|
|
2002
|
-
|
|
2003
|
-
for await (const event of eng.query({
|
|
2004
|
-
prompt: text,
|
|
2005
|
-
promptParts: attachmentBundle.promptParts && attachmentBundle.promptParts.length > 0 ? attachmentBundle.promptParts : null,
|
|
2006
|
-
signal: abort.signal,
|
|
2007
|
-
userAlreadyPersisted: false,
|
|
2008
|
-
threadId: 'main',
|
|
2009
|
-
senderVpId: vpId,
|
|
2010
|
-
vpPersona,
|
|
2011
|
-
})) {
|
|
2012
|
-
resetQueryTimer();
|
|
2013
|
-
handleEngineEvent(event, handlerCtx);
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
sendYeaftOutput({ type: 'assistant', message: { content: [] } }, envelope);
|
|
2017
|
-
sendYeaftOutput({ type: 'result', result_text: '' }, envelope);
|
|
2018
|
-
|
|
2019
|
-
try { touchChat(root, chatId); } catch { /* best-effort */ }
|
|
2020
|
-
} catch (err) {
|
|
2021
|
-
const isAbort = err && (err.name === 'AbortError' || err.name === 'LLMAbortError');
|
|
2022
|
-
if (isAbort) {
|
|
2023
|
-
sendYeaftOutput({ type: 'result', result_text: '', stopped: true }, envelope);
|
|
2024
|
-
} else {
|
|
2025
|
-
console.error('[Yeaft] chat-send error:', err);
|
|
2026
|
-
sendYeaftOutput({
|
|
2027
|
-
type: 'assistant',
|
|
2028
|
-
message: { content: [{ type: 'text', text: `⚠️ Chat error: ${err.message}` }] },
|
|
2029
|
-
}, envelope);
|
|
2030
|
-
sendYeaftOutput({ type: 'result', result_text: '' }, envelope);
|
|
2031
|
-
}
|
|
2032
|
-
} finally {
|
|
2033
|
-
if (queryTimer) clearTimeout(queryTimer);
|
|
2034
|
-
if (chatAborts.get(chatId) === abort) chatAborts.delete(chatId);
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
1696
|
|
|
2038
1697
|
/**
|
|
2039
1698
|
* Install the dream pipeline progress sink and runtime settings bridge.
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* chat-store.js — Per-chat persistent store for Yeaft Chat Mode.
|
|
3
|
-
*
|
|
4
|
-
* Layout (architecture parity with group-store.js):
|
|
5
|
-
* ~/.yeaft/chats/<chat-id>/
|
|
6
|
-
* chat.json # { id, displayName, vpId, workDir, createdAt, lastTurnAt }
|
|
7
|
-
* messages/ # JSONL size-rotation log
|
|
8
|
-
* 000001.jsonl
|
|
9
|
-
* index.json
|
|
10
|
-
*
|
|
11
|
-
* A chat is 1:1 with a single VP and persists messages the same way groups
|
|
12
|
-
* do — same storage primitives, same jsonl shape — but without a roster.
|
|
13
|
-
*
|
|
14
|
-
* Hard constraint: no @-mention parsing, no dispatch, no engine awareness.
|
|
15
|
-
* This module only owns chat.json + the messages log.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
existsSync,
|
|
20
|
-
mkdirSync,
|
|
21
|
-
readFileSync,
|
|
22
|
-
readdirSync,
|
|
23
|
-
renameSync,
|
|
24
|
-
rmSync,
|
|
25
|
-
statSync,
|
|
26
|
-
} from 'fs';
|
|
27
|
-
import { join } from 'path';
|
|
28
|
-
import { writeAtomic, openLog } from '../storage/index.js';
|
|
29
|
-
import { nextMsgId, isReservedVpId, ReservedVpIdError, validateVpId, InvalidVpIdError } from '../groups/ids.js';
|
|
30
|
-
|
|
31
|
-
const CHAT_FILE = 'chat.json';
|
|
32
|
-
const MESSAGES_DIR = 'messages';
|
|
33
|
-
|
|
34
|
-
const CHAT_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Open (or partially create) the directory for a chat. Returns a handle even
|
|
38
|
-
* when chat.json is absent — call createChat() to materialise it.
|
|
39
|
-
*
|
|
40
|
-
* @param {string} chatsRoot
|
|
41
|
-
* @param {string} chatId
|
|
42
|
-
* @returns {ChatHandle}
|
|
43
|
-
*/
|
|
44
|
-
export function openChat(chatsRoot, chatId) {
|
|
45
|
-
if (!chatId || typeof chatId !== 'string') {
|
|
46
|
-
throw new Error('openChat: chatId required (string)');
|
|
47
|
-
}
|
|
48
|
-
if (!CHAT_ID_RE.test(chatId)) {
|
|
49
|
-
throw new Error(`openChat: invalid chatId "${chatId}"`);
|
|
50
|
-
}
|
|
51
|
-
const dir = join(chatsRoot, chatId);
|
|
52
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
53
|
-
|
|
54
|
-
let meta = loadChatMeta(dir);
|
|
55
|
-
|
|
56
|
-
const messagesDir = join(dir, MESSAGES_DIR);
|
|
57
|
-
if (!existsSync(messagesDir)) mkdirSync(messagesDir, { recursive: true });
|
|
58
|
-
const log = openLog(messagesDir);
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
dir,
|
|
62
|
-
id: chatId,
|
|
63
|
-
getMeta() { return meta ? structuredClone(meta) : null; },
|
|
64
|
-
saveMeta(next) {
|
|
65
|
-
validateMeta(next);
|
|
66
|
-
meta = next;
|
|
67
|
-
writeAtomic(join(dir, CHAT_FILE), JSON.stringify(meta, null, 2));
|
|
68
|
-
},
|
|
69
|
-
appendMessage(record) {
|
|
70
|
-
if (!record || typeof record !== 'object') {
|
|
71
|
-
throw new Error('appendMessage: record required');
|
|
72
|
-
}
|
|
73
|
-
const leaked = Object.keys(record).filter((k) => typeof k === 'string' && k.startsWith('_'));
|
|
74
|
-
if (leaked.length > 0) {
|
|
75
|
-
throw new Error(`appendMessage: ephemeral fields leaked into log: ${leaked.join(', ')}`);
|
|
76
|
-
}
|
|
77
|
-
const stored = {
|
|
78
|
-
id: record.id || nextMsgId(),
|
|
79
|
-
ts: record.ts || new Date().toISOString(),
|
|
80
|
-
from: record.from,
|
|
81
|
-
role: record.role || (record.from === 'user' ? 'user' : 'assistant'),
|
|
82
|
-
text: record.text ?? '',
|
|
83
|
-
taskId: record.taskId || null,
|
|
84
|
-
mentions: Array.isArray(record.mentions) ? record.mentions.slice() : [],
|
|
85
|
-
meta: record.meta || {},
|
|
86
|
-
};
|
|
87
|
-
log.append(stored);
|
|
88
|
-
return stored;
|
|
89
|
-
},
|
|
90
|
-
*streamMessages() { yield* log.streamAll(); },
|
|
91
|
-
*readMessageRange(firstId, lastId) { yield* log.readRange(firstId, lastId); },
|
|
92
|
-
close() { log.close(); },
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Create a new chat on disk. Fails if chat.json already exists.
|
|
98
|
-
* @param {string} chatsRoot
|
|
99
|
-
* @param {{id:string, vpId:string, displayName?:string, workDir?:string, createdAt?:string}} spec
|
|
100
|
-
* @returns {ChatHandle}
|
|
101
|
-
*/
|
|
102
|
-
export function createChat(chatsRoot, spec) {
|
|
103
|
-
if (!spec || !spec.id) throw new Error('createChat: spec.id required');
|
|
104
|
-
if (!spec.vpId) throw new Error('createChat: spec.vpId required');
|
|
105
|
-
if (isReservedVpId(spec.vpId)) throw new ReservedVpIdError(spec.vpId);
|
|
106
|
-
const verdict = validateVpId(spec.vpId);
|
|
107
|
-
if (!verdict.ok) throw new InvalidVpIdError(spec.vpId, verdict.reason);
|
|
108
|
-
|
|
109
|
-
const h = openChat(chatsRoot, spec.id);
|
|
110
|
-
if (h.getMeta()) {
|
|
111
|
-
throw new Error(`chat ${spec.id} already exists`);
|
|
112
|
-
}
|
|
113
|
-
const meta = {
|
|
114
|
-
id: spec.id,
|
|
115
|
-
displayName: typeof spec.displayName === 'string' && spec.displayName.trim()
|
|
116
|
-
? spec.displayName.trim() : spec.id,
|
|
117
|
-
vpId: spec.vpId,
|
|
118
|
-
workDir: typeof spec.workDir === 'string' ? spec.workDir.trim() : '',
|
|
119
|
-
createdAt: spec.createdAt || new Date().toISOString(),
|
|
120
|
-
lastTurnAt: null,
|
|
121
|
-
};
|
|
122
|
-
h.saveMeta(meta);
|
|
123
|
-
return h;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Non-destructive load — returns null if chat.json is missing/corrupt. */
|
|
127
|
-
export function loadChatMeta(dir) {
|
|
128
|
-
const path = join(dir, CHAT_FILE);
|
|
129
|
-
if (!existsSync(path)) return null;
|
|
130
|
-
try {
|
|
131
|
-
const raw = readFileSync(path, 'utf8');
|
|
132
|
-
const parsed = JSON.parse(raw);
|
|
133
|
-
validateMeta(parsed);
|
|
134
|
-
if (typeof parsed.displayName !== 'string') parsed.displayName = parsed.id;
|
|
135
|
-
if (typeof parsed.workDir !== 'string') parsed.workDir = '';
|
|
136
|
-
if (parsed.lastTurnAt === undefined) parsed.lastTurnAt = null;
|
|
137
|
-
return parsed;
|
|
138
|
-
} catch {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** List every chat directory under `chatsRoot`. */
|
|
144
|
-
export function listChats(chatsRoot) {
|
|
145
|
-
if (!existsSync(chatsRoot)) return [];
|
|
146
|
-
const out = [];
|
|
147
|
-
for (const name of readdirSync(chatsRoot)) {
|
|
148
|
-
if (name.startsWith('.')) continue;
|
|
149
|
-
const p = join(chatsRoot, name);
|
|
150
|
-
try {
|
|
151
|
-
if (!statSync(p).isDirectory()) continue;
|
|
152
|
-
} catch { continue; }
|
|
153
|
-
const meta = loadChatMeta(p);
|
|
154
|
-
if (meta) out.push(meta);
|
|
155
|
-
}
|
|
156
|
-
return out;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Update a chat's displayName. */
|
|
160
|
-
export function renameChat(chatsRoot, chatId, displayName) {
|
|
161
|
-
const h = openChat(chatsRoot, chatId);
|
|
162
|
-
const meta = h.getMeta();
|
|
163
|
-
if (!meta) throw new Error(`renameChat: chat ${chatId} not found`);
|
|
164
|
-
const next = { ...meta, displayName: String(displayName || '').trim() || meta.id };
|
|
165
|
-
h.saveMeta(next);
|
|
166
|
-
h.close();
|
|
167
|
-
return next;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/** Stamp lastTurnAt — invoked by web-bridge after a successful turn. */
|
|
171
|
-
export function touchChat(chatsRoot, chatId, when = new Date().toISOString()) {
|
|
172
|
-
const h = openChat(chatsRoot, chatId);
|
|
173
|
-
const meta = h.getMeta();
|
|
174
|
-
if (!meta) { h.close(); return null; }
|
|
175
|
-
const next = { ...meta, lastTurnAt: when };
|
|
176
|
-
h.saveMeta(next);
|
|
177
|
-
h.close();
|
|
178
|
-
return next;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Soft-archive: rename dir to `.archived-<chatId>-<ts>`. */
|
|
182
|
-
export function archiveChat(chatsRoot, chatId) {
|
|
183
|
-
const src = join(chatsRoot, chatId);
|
|
184
|
-
if (!existsSync(src)) return false;
|
|
185
|
-
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
186
|
-
const dst = join(chatsRoot, `.archived-${chatId}-${ts}`);
|
|
187
|
-
renameSync(src, dst);
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** Permanently delete a chat directory. Use after archive when sure. */
|
|
192
|
-
export function deleteChat(chatsRoot, chatId) {
|
|
193
|
-
const src = join(chatsRoot, chatId);
|
|
194
|
-
if (!existsSync(src)) return false;
|
|
195
|
-
rmSync(src, { recursive: true, force: true });
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function validateMeta(meta) {
|
|
200
|
-
if (!meta || typeof meta !== 'object') throw new Error('chat.json must be object');
|
|
201
|
-
if (!meta.id || typeof meta.id !== 'string') throw new Error('chat.id required');
|
|
202
|
-
if (!meta.vpId || typeof meta.vpId !== 'string') throw new Error('chat.vpId required');
|
|
203
|
-
if (meta.displayName != null && typeof meta.displayName !== 'string') {
|
|
204
|
-
throw new Error('chat.displayName must be string');
|
|
205
|
-
}
|
|
206
|
-
if (meta.workDir != null && typeof meta.workDir !== 'string') {
|
|
207
|
-
throw new Error('chat.workDir must be string');
|
|
208
|
-
}
|
|
209
|
-
if (meta.lastTurnAt != null && typeof meta.lastTurnAt !== 'string') {
|
|
210
|
-
throw new Error('chat.lastTurnAt must be string|null');
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* @typedef {Object} ChatHandle
|
|
216
|
-
* @property {string} dir
|
|
217
|
-
* @property {string} id
|
|
218
|
-
* @property {() => any} getMeta
|
|
219
|
-
* @property {(next:any) => void} saveMeta
|
|
220
|
-
* @property {(record:any) => any} appendMessage
|
|
221
|
-
* @property {() => Generator<any>} streamMessages
|
|
222
|
-
* @property {(first:string,last:string) => Generator<any>} readMessageRange
|
|
223
|
-
* @property {() => void} close
|
|
224
|
-
*/
|