@yeaft/webchat-agent 0.1.947 → 0.1.949
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/buffer.js +3 -2
- package/package.json +1 -1
- package/yeaft/history-compact.js +1 -1
- package/yeaft/vp/vp-bridge.js +7 -7
- package/yeaft/web-bridge.js +89 -87
package/connection/buffer.js
CHANGED
|
@@ -2,9 +2,10 @@ import WebSocket from 'ws';
|
|
|
2
2
|
import ctx from '../context.js';
|
|
3
3
|
import { encrypt, decrypt, isEncrypted } from '../encryption.js';
|
|
4
4
|
|
|
5
|
-
// 需要在断连期间缓冲的消息类型(
|
|
5
|
+
// 需要在断连期间缓冲的消息类型(CLI / Session 输出相关的关键消息)
|
|
6
6
|
export const BUFFERABLE_TYPES = new Set([
|
|
7
|
-
'claude_output', '
|
|
7
|
+
'claude_output', 'yeaft_output', 'yeaft_session_output', 'session_output',
|
|
8
|
+
'turn_completed', 'conversation_closed',
|
|
8
9
|
'session_id_update', 'compact_status', 'slash_commands_update',
|
|
9
10
|
'background_task_started', 'background_task_output',
|
|
10
11
|
'subagent_started', 'subagent_message', 'subagent_completed',
|
package/package.json
CHANGED
package/yeaft/history-compact.js
CHANGED
|
@@ -442,7 +442,7 @@ export function findCutIndex(messages, keepRecent) {
|
|
|
442
442
|
/**
|
|
443
443
|
* Wrap a summary string into the canonical "session continued" recovery
|
|
444
444
|
* message. The wording is deliberately close to Claude Code's compact
|
|
445
|
-
* marker so frontend filters (already in `web/stores/helpers/
|
|
445
|
+
* marker so frontend filters (already in `web/stores/helpers/assistantOutput.js`,
|
|
446
446
|
* `server/db/message-db.js`) recognise it.
|
|
447
447
|
*
|
|
448
448
|
* @param {string} summary
|
package/yeaft/vp/vp-bridge.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* • color / avatar → web-derived, NOT emitted here
|
|
13
13
|
*
|
|
14
14
|
* task-334h — live diff:
|
|
15
|
-
* • subscribers (Set of
|
|
15
|
+
* • subscribers (Set of sendSessionEvent fns) receive per-vp `vp_updated`
|
|
16
16
|
* and `vp_removed` events as VpLoader's debounced rescan commits
|
|
17
17
|
* added / updated / removed vpIds.
|
|
18
18
|
* • Event shape: `{ type, vpId, vp?, reason? }` where `reason` ∈
|
|
@@ -232,19 +232,19 @@ export function buildVpSnapshot(registry = defaultRegistry) {
|
|
|
232
232
|
/**
|
|
233
233
|
* Handle an `yeaft_vp_subscribe` request from the web client.
|
|
234
234
|
*
|
|
235
|
-
* Registers `
|
|
235
|
+
* Registers `sendSessionEvent` as a live-diff subscriber, emits an initial
|
|
236
236
|
* `vp_snapshot`, and (on first call process-wide) starts the VpLoader
|
|
237
237
|
* whose debounced rescan fans out `vp_updated` / `vp_removed` events.
|
|
238
238
|
*
|
|
239
239
|
* Returns an unsubscribe fn the caller MAY invoke on WS close to prevent
|
|
240
240
|
* sending to a dead socket. Web-bridge is expected to manage this.
|
|
241
241
|
*
|
|
242
|
-
* @param {(event: object) => void}
|
|
242
|
+
* @param {(event: object) => void} sendSessionEvent
|
|
243
243
|
* @param {import('./registry.js').Registry} [registry]
|
|
244
244
|
* @param {{dir?: string}} [options]
|
|
245
245
|
* @returns {() => void} unsubscribe fn
|
|
246
246
|
*/
|
|
247
|
-
export function handleVpSubscribe(
|
|
247
|
+
export function handleVpSubscribe(sendSessionEvent, registry = defaultRegistry, options = {}) {
|
|
248
248
|
const { loader, fresh } = ensureLoader(registry, options);
|
|
249
249
|
// task-338-F2 + task-339-followup: replay semantics.
|
|
250
250
|
//
|
|
@@ -273,13 +273,13 @@ export function handleVpSubscribe(sendYeaftEvent, registry = defaultRegistry, op
|
|
|
273
273
|
if (shouldRescan && loader && typeof loader.rescanNow === 'function') {
|
|
274
274
|
try { loader.rescanNow(); } catch { /* never crash subscribe on rescan */ }
|
|
275
275
|
}
|
|
276
|
-
_subscribers.add(
|
|
276
|
+
_subscribers.add(sendSessionEvent);
|
|
277
277
|
try {
|
|
278
|
-
|
|
278
|
+
sendSessionEvent(buildVpSnapshot(registry));
|
|
279
279
|
} catch {
|
|
280
280
|
// Never crash the WS pipeline from snapshot serialisation.
|
|
281
281
|
}
|
|
282
|
-
return () => { _subscribers.delete(
|
|
282
|
+
return () => { _subscribers.delete(sendSessionEvent); };
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
/**
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* inbox, abort, todo, persistence, and frontend timeline boundaries. Legacy
|
|
7
7
|
* 1:1 chat paths still use the default `main` thread.
|
|
8
8
|
*
|
|
9
|
-
* Translates Engine events into
|
|
10
|
-
* frontend can fully reuse the standard Chat rendering pipeline
|
|
9
|
+
* Translates Engine events into provider-neutral assistant output frames so
|
|
10
|
+
* the frontend can fully reuse the standard Chat rendering pipeline
|
|
11
11
|
* (MessageList, AssistantTurn, ToolLine, AskCard, waiting cat, etc.).
|
|
12
12
|
*
|
|
13
13
|
* task-330c lint guard:
|
|
@@ -119,7 +119,7 @@ const inflightScopedDreamGroups = new Set();
|
|
|
119
119
|
async function sendDreamSnapshotForSession(sessionId, extra = {}) {
|
|
120
120
|
const snapshot = await buildDreamOutputSnapshot(session, sessionId);
|
|
121
121
|
if (!snapshot) return null;
|
|
122
|
-
|
|
122
|
+
sendSessionEvent({ type: 'yeaft_dream_snapshot', ...extra, snapshot }, { sessionId });
|
|
123
123
|
return snapshot;
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -157,7 +157,7 @@ const turnAbortMeta = new Map();
|
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
159
|
* Per-VP status broker — the agent-side authority for VP timeline
|
|
160
|
-
* status. Lazy-initialized on first use because `
|
|
160
|
+
* status. Lazy-initialized on first use because `sendSessionEvent` is
|
|
161
161
|
* declared below; trying to call it at module top-level would crash
|
|
162
162
|
* with a TDZ error during agent boot.
|
|
163
163
|
*
|
|
@@ -173,7 +173,7 @@ function getVpStatusBroker() {
|
|
|
173
173
|
vpStatusBroker = createVpStatusBroker({
|
|
174
174
|
send: (event) => {
|
|
175
175
|
// The broker emits both `vp_status_changed` and
|
|
176
|
-
// `vp_status_snapshot`. Both ride the standard
|
|
176
|
+
// `vp_status_snapshot`. Both ride the standard sendSessionEvent
|
|
177
177
|
// envelope so the frontend's existing yeaft_output dispatcher
|
|
178
178
|
// sees them. We stamp sessionId/vpId on the envelope for
|
|
179
179
|
// events that target a specific VP so the server's per-client
|
|
@@ -185,7 +185,7 @@ function getVpStatusBroker() {
|
|
|
185
185
|
if (event.vpId) env.vpId = event.vpId;
|
|
186
186
|
if (event.turnId) env.turnId = event.turnId;
|
|
187
187
|
}
|
|
188
|
-
|
|
188
|
+
sendSessionEvent(event, env);
|
|
189
189
|
},
|
|
190
190
|
});
|
|
191
191
|
}
|
|
@@ -1023,7 +1023,7 @@ async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
|
|
|
1023
1023
|
});
|
|
1024
1024
|
thread.updatedAt = Date.now();
|
|
1025
1025
|
try {
|
|
1026
|
-
|
|
1026
|
+
sendSessionEvent({
|
|
1027
1027
|
type: 'vp_thread_user_appended',
|
|
1028
1028
|
sessionId,
|
|
1029
1029
|
vpId,
|
|
@@ -1045,7 +1045,7 @@ async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
|
|
|
1045
1045
|
inbox.push({ envelope, turnId, thread });
|
|
1046
1046
|
|
|
1047
1047
|
try {
|
|
1048
|
-
|
|
1048
|
+
sendSessionEvent({
|
|
1049
1049
|
type: 'vp_typing_start',
|
|
1050
1050
|
sessionId,
|
|
1051
1051
|
vpId,
|
|
@@ -1137,7 +1137,7 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
|
|
|
1137
1137
|
turnAbortMeta.delete(turnId);
|
|
1138
1138
|
if (vpAborts.get(key) === vpAbort) vpAborts.delete(key);
|
|
1139
1139
|
try {
|
|
1140
|
-
|
|
1140
|
+
sendSessionEvent({
|
|
1141
1141
|
type: 'vp_typing_end',
|
|
1142
1142
|
sessionId,
|
|
1143
1143
|
vpId,
|
|
@@ -1149,7 +1149,7 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
|
|
|
1149
1149
|
}
|
|
1150
1150
|
try {
|
|
1151
1151
|
if (text && envelope?.msg) {
|
|
1152
|
-
|
|
1152
|
+
sendSessionEvent({
|
|
1153
1153
|
type: 'session_message',
|
|
1154
1154
|
sessionId,
|
|
1155
1155
|
vpId,
|
|
@@ -1217,7 +1217,7 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
|
|
|
1217
1217
|
console.warn('[Yeaft] vp-status typing transition (rescue) failed:', err?.message || err);
|
|
1218
1218
|
}
|
|
1219
1219
|
try {
|
|
1220
|
-
|
|
1220
|
+
sendSessionEvent({
|
|
1221
1221
|
type: 'vp_typing_start',
|
|
1222
1222
|
sessionId,
|
|
1223
1223
|
vpId,
|
|
@@ -1285,9 +1285,11 @@ export async function __testResetVpState() {
|
|
|
1285
1285
|
|
|
1286
1286
|
|
|
1287
1287
|
/**
|
|
1288
|
-
* Send a
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1288
|
+
* Send a provider-neutral assistant output frame for a Yeaft Session.
|
|
1289
|
+
*
|
|
1290
|
+
* Wire compatibility: this still emits the legacy `yeaft_output` envelope so
|
|
1291
|
+
* upgraded agents continue to work with older servers. The frame in `data` is
|
|
1292
|
+
* intentionally generic and is consumed by the web's assistant-output handler.
|
|
1291
1293
|
*/
|
|
1292
1294
|
function resolveGroupDefaultVpId(sessionId) {
|
|
1293
1295
|
if (!sessionId) return null;
|
|
@@ -1300,7 +1302,7 @@ function resolveGroupDefaultVpId(sessionId) {
|
|
|
1300
1302
|
}
|
|
1301
1303
|
}
|
|
1302
1304
|
|
|
1303
|
-
function
|
|
1305
|
+
function sendSessionOutputFrame(data, { sessionId, chatId, vpId, turnId, threadId } = {}) {
|
|
1304
1306
|
const resolvedVpId = vpId || (sessionId ? resolveGroupDefaultVpId(sessionId) : null);
|
|
1305
1307
|
sendToServer({
|
|
1306
1308
|
type: 'yeaft_output',
|
|
@@ -1314,8 +1316,8 @@ function sendYeaftOutput(data, { sessionId, chatId, vpId, turnId, threadId } = {
|
|
|
1314
1316
|
});
|
|
1315
1317
|
}
|
|
1316
1318
|
|
|
1317
|
-
/** Send a
|
|
1318
|
-
function
|
|
1319
|
+
/** Send a Yeaft Session metadata event over the legacy-compatible envelope. */
|
|
1320
|
+
function sendSessionEvent(event, { sessionId, chatId, vpId, turnId, threadId } = {}) {
|
|
1319
1321
|
sendToServer({
|
|
1320
1322
|
type: 'yeaft_output',
|
|
1321
1323
|
conversationId: yeaftConversationId,
|
|
@@ -1345,7 +1347,7 @@ export function handleYeaftVpSubscribe(_msg) {
|
|
|
1345
1347
|
}
|
|
1346
1348
|
const { libDir } = configuredVpPaths();
|
|
1347
1349
|
_vpUnsubscribe = handleVpSubscribe(
|
|
1348
|
-
|
|
1350
|
+
sendSessionEvent,
|
|
1349
1351
|
undefined,
|
|
1350
1352
|
libDir ? { dir: libDir } : {},
|
|
1351
1353
|
);
|
|
@@ -1355,7 +1357,7 @@ export function handleYeaftVpSubscribe(_msg) {
|
|
|
1355
1357
|
* VP CRUD from the web client. See historic doc for full message shapes.
|
|
1356
1358
|
*/
|
|
1357
1359
|
function sendVpCrudResult(payload) {
|
|
1358
|
-
|
|
1360
|
+
sendSessionEvent({ type: 'vp_crud_result', ...payload });
|
|
1359
1361
|
}
|
|
1360
1362
|
|
|
1361
1363
|
export function handleYeaftVpCreate(msg) {
|
|
@@ -1462,7 +1464,7 @@ export function handleYeaftVpRead(msg) {
|
|
|
1462
1464
|
* Session CRUD wired to WS events.
|
|
1463
1465
|
*/
|
|
1464
1466
|
function sendSessionCrudResult(payload) {
|
|
1465
|
-
|
|
1467
|
+
sendSessionEvent({ type: 'session_crud_result', ...payload });
|
|
1466
1468
|
}
|
|
1467
1469
|
|
|
1468
1470
|
function sendSessionSnapshotBroadcast() {
|
|
@@ -1470,7 +1472,7 @@ function sendSessionSnapshotBroadcast() {
|
|
|
1470
1472
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1471
1473
|
if (!yeaftDir) return;
|
|
1472
1474
|
const sessions = snapshotSessions(yeaftDir);
|
|
1473
|
-
|
|
1475
|
+
sendSessionEvent({ type: 'session_list_updated', sessions });
|
|
1474
1476
|
} catch (err) {
|
|
1475
1477
|
console.warn('[Yeaft] sendSessionSnapshotBroadcast failed:', err?.message || err);
|
|
1476
1478
|
}
|
|
@@ -1502,7 +1504,7 @@ function sendSessionRosterChanged(session) {
|
|
|
1502
1504
|
defaultVpId: session.defaultVpId,
|
|
1503
1505
|
workDir: session.workDir || '',
|
|
1504
1506
|
};
|
|
1505
|
-
|
|
1507
|
+
sendSessionEvent({ type: 'session_roster_changed', ...payload });
|
|
1506
1508
|
}
|
|
1507
1509
|
|
|
1508
1510
|
function sessionErrorPayload(err) {
|
|
@@ -1859,11 +1861,11 @@ export function installYeaftRuntimeBridge(s) {
|
|
|
1859
1861
|
try {
|
|
1860
1862
|
if (evt.type === 'turn_open' || evt.type === 'turn_close' || evt.type === 'loop') {
|
|
1861
1863
|
const tag = evt && evt.sessionId ? { sessionId: evt.sessionId } : {};
|
|
1862
|
-
|
|
1864
|
+
sendSessionEvent(evt, tag);
|
|
1863
1865
|
} else {
|
|
1864
1866
|
const out = { type: 'dream_progress', ...evt };
|
|
1865
1867
|
const tag = evt && evt.sessionId ? { sessionId: evt.sessionId } : {};
|
|
1866
|
-
|
|
1868
|
+
sendSessionEvent(out, tag);
|
|
1867
1869
|
}
|
|
1868
1870
|
} catch { /* never let event delivery throw */ }
|
|
1869
1871
|
};
|
|
@@ -1893,14 +1895,14 @@ export function installYeaftRuntimeBridge(s) {
|
|
|
1893
1895
|
};
|
|
1894
1896
|
|
|
1895
1897
|
// Wire the post-compact WS sink. Compactor is constructed in
|
|
1896
|
-
// session.js with a no-op sink; bridge owns `
|
|
1898
|
+
// session.js with a no-op sink; bridge owns `sendSessionEvent` /
|
|
1897
1899
|
// `yeaftConversationId`, so the sink is wired here once a session is
|
|
1898
1900
|
// present. Per-group `yeaft_history_compacted` events fire after a
|
|
1899
1901
|
// successful summarize+swap.
|
|
1900
1902
|
if (s.compactor && typeof s.compactor.setOnCompacted === 'function') {
|
|
1901
1903
|
s.compactor.setOnCompacted((sessionId, result) => {
|
|
1902
1904
|
try {
|
|
1903
|
-
|
|
1905
|
+
sendSessionEvent({
|
|
1904
1906
|
type: 'yeaft_history_compacted',
|
|
1905
1907
|
reason: result?.reason ?? null,
|
|
1906
1908
|
beforeTurns: result?.beforeTurns,
|
|
@@ -1979,7 +1981,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
1979
1981
|
switch (event.type) {
|
|
1980
1982
|
case 'text_delta':
|
|
1981
1983
|
hctx.assistantTextParts.push(event.text);
|
|
1982
|
-
|
|
1984
|
+
sendSessionOutputFrame({
|
|
1983
1985
|
type: 'assistant',
|
|
1984
1986
|
message: { content: [{ type: 'text', text: event.text }] },
|
|
1985
1987
|
}, envelope);
|
|
@@ -1990,7 +1992,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
1990
1992
|
break;
|
|
1991
1993
|
|
|
1992
1994
|
case 'thinking_delta':
|
|
1993
|
-
|
|
1995
|
+
sendSessionEvent({ type: 'thinking_delta', text: event.text }, envelope);
|
|
1994
1996
|
break;
|
|
1995
1997
|
|
|
1996
1998
|
case 'thinking_block_end':
|
|
@@ -1999,7 +2001,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
1999
2001
|
// turns / VPs includes it. Without this echo Anthropic 400s the
|
|
2000
2002
|
// next request with "content[].thinking in the thinking mode must
|
|
2001
2003
|
// be passed back to the API". The signature stays server-side
|
|
2002
|
-
// only — wire serializers (stripMetaForWire /
|
|
2004
|
+
// only — wire serializers (stripMetaForWire / sendSessionOutputFrame)
|
|
2003
2005
|
// never reference thinkingBlocks, so it cannot leak to the UI.
|
|
2004
2006
|
if (hctx.thinkingBlocksAccum && event.signature) {
|
|
2005
2007
|
if (event.redacted) {
|
|
@@ -2029,11 +2031,11 @@ function handleEngineEvent(event, hctx) {
|
|
|
2029
2031
|
});
|
|
2030
2032
|
}
|
|
2031
2033
|
// Finish any in-progress text streaming so UI shows typing dots
|
|
2032
|
-
|
|
2034
|
+
sendSessionOutputFrame({
|
|
2033
2035
|
type: 'assistant',
|
|
2034
2036
|
message: { content: [] },
|
|
2035
2037
|
}, envelope);
|
|
2036
|
-
|
|
2038
|
+
sendSessionOutputFrame({
|
|
2037
2039
|
type: 'assistant',
|
|
2038
2040
|
message: {
|
|
2039
2041
|
content: [{
|
|
@@ -2048,7 +2050,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2048
2050
|
break;
|
|
2049
2051
|
|
|
2050
2052
|
case 'tool_start':
|
|
2051
|
-
|
|
2053
|
+
sendSessionEvent({
|
|
2052
2054
|
type: 'tool_start',
|
|
2053
2055
|
id: event.id,
|
|
2054
2056
|
name: event.name,
|
|
@@ -2064,7 +2066,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2064
2066
|
isError: !!event.isError,
|
|
2065
2067
|
});
|
|
2066
2068
|
}
|
|
2067
|
-
|
|
2069
|
+
sendSessionOutputFrame({
|
|
2068
2070
|
type: 'user',
|
|
2069
2071
|
tool_use_result: [{
|
|
2070
2072
|
type: 'tool_result',
|
|
@@ -2109,7 +2111,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2109
2111
|
} catch (err) {
|
|
2110
2112
|
console.warn('[Yeaft] vp-status settleIdle (route_forward) failed:', err?.message || err);
|
|
2111
2113
|
}
|
|
2112
|
-
|
|
2114
|
+
sendSessionEvent({
|
|
2113
2115
|
type: 'vp_turn_end',
|
|
2114
2116
|
sessionId: hctx.sessionId,
|
|
2115
2117
|
vpId: hctx.vpId,
|
|
@@ -2125,7 +2127,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2125
2127
|
break;
|
|
2126
2128
|
|
|
2127
2129
|
case 'usage':
|
|
2128
|
-
|
|
2130
|
+
sendSessionEvent({
|
|
2129
2131
|
type: 'context_usage',
|
|
2130
2132
|
inputTokens: event.inputTokens,
|
|
2131
2133
|
outputTokens: event.outputTokens,
|
|
@@ -2133,7 +2135,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2133
2135
|
break;
|
|
2134
2136
|
|
|
2135
2137
|
case 'recall':
|
|
2136
|
-
|
|
2138
|
+
sendSessionEvent({
|
|
2137
2139
|
type: 'recall',
|
|
2138
2140
|
entryCount: event.entryCount,
|
|
2139
2141
|
cached: event.cached,
|
|
@@ -2144,7 +2146,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2144
2146
|
// Engine compressed the context — clear THIS group's accumulated
|
|
2145
2147
|
// history. Other groups' histories stay intact.
|
|
2146
2148
|
if (hctx.sessionId) setGroupHistory(hctx.sessionId, []);
|
|
2147
|
-
|
|
2149
|
+
sendSessionEvent({
|
|
2148
2150
|
type: 'consolidate',
|
|
2149
2151
|
archivedCount: event.archivedCount,
|
|
2150
2152
|
extractedCount: event.extractedCount,
|
|
@@ -2152,7 +2154,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2152
2154
|
break;
|
|
2153
2155
|
|
|
2154
2156
|
case 'fallback':
|
|
2155
|
-
|
|
2157
|
+
sendSessionEvent({
|
|
2156
2158
|
type: 'fallback',
|
|
2157
2159
|
from: event.from,
|
|
2158
2160
|
to: event.to,
|
|
@@ -2161,7 +2163,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2161
2163
|
break;
|
|
2162
2164
|
|
|
2163
2165
|
case 'reflection':
|
|
2164
|
-
|
|
2166
|
+
sendSessionEvent({
|
|
2165
2167
|
type: 'reflection',
|
|
2166
2168
|
// feat-6af5f9f1 PR B: stamp turnId/loopNumber so the debug panel
|
|
2167
2169
|
// can attach reflection cards to the matching loop.
|
|
@@ -2178,7 +2180,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2178
2180
|
break;
|
|
2179
2181
|
|
|
2180
2182
|
case 'turn_open':
|
|
2181
|
-
|
|
2183
|
+
sendSessionEvent({
|
|
2182
2184
|
type: 'turn_open',
|
|
2183
2185
|
turnId: event.turnId,
|
|
2184
2186
|
userPrompt: event.userPrompt,
|
|
@@ -2189,7 +2191,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2189
2191
|
break;
|
|
2190
2192
|
|
|
2191
2193
|
case 'turn_close':
|
|
2192
|
-
|
|
2194
|
+
sendSessionEvent({
|
|
2193
2195
|
type: 'turn_close',
|
|
2194
2196
|
turnId: event.turnId,
|
|
2195
2197
|
totalMs: event.totalMs,
|
|
@@ -2199,7 +2201,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2199
2201
|
break;
|
|
2200
2202
|
|
|
2201
2203
|
case 'memory_used':
|
|
2202
|
-
|
|
2204
|
+
sendSessionEvent({
|
|
2203
2205
|
type: 'memory_used',
|
|
2204
2206
|
turnId: event.turnId,
|
|
2205
2207
|
loaded: event.loaded || [],
|
|
@@ -2207,7 +2209,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2207
2209
|
break;
|
|
2208
2210
|
|
|
2209
2211
|
case 'dream_memory_loaded':
|
|
2210
|
-
|
|
2212
|
+
sendSessionEvent({
|
|
2211
2213
|
type: 'dream_memory_loaded',
|
|
2212
2214
|
turnId: event.turnId,
|
|
2213
2215
|
vpId: event.vpId || null,
|
|
@@ -2218,7 +2220,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2218
2220
|
break;
|
|
2219
2221
|
|
|
2220
2222
|
case 'memory_adjust':
|
|
2221
|
-
|
|
2223
|
+
sendSessionEvent({
|
|
2222
2224
|
type: 'memory_adjust',
|
|
2223
2225
|
turnId: event.turnId,
|
|
2224
2226
|
sessionKey: event.sessionKey,
|
|
@@ -2233,7 +2235,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2233
2235
|
if (hctx && Array.isArray(hctx.appendedUserPrompts) && event.preview) {
|
|
2234
2236
|
hctx.appendedUserPrompts.push(String(event.preview));
|
|
2235
2237
|
}
|
|
2236
|
-
|
|
2238
|
+
sendSessionEvent({
|
|
2237
2239
|
type: 'vp_thread_user_append_consumed',
|
|
2238
2240
|
turnId: event.turnId,
|
|
2239
2241
|
threadId: event.threadId,
|
|
@@ -2244,7 +2246,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2244
2246
|
break;
|
|
2245
2247
|
|
|
2246
2248
|
case 'tool_exec':
|
|
2247
|
-
|
|
2249
|
+
sendSessionEvent({
|
|
2248
2250
|
type: 'tool_exec',
|
|
2249
2251
|
turnId: event.turnId,
|
|
2250
2252
|
loopNumber: event.loopNumber,
|
|
@@ -2258,7 +2260,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2258
2260
|
case 'loop':
|
|
2259
2261
|
// feat-6af5f9f1 PR B: replaces the old `debug_turn` event. Same
|
|
2260
2262
|
// payload shape plus turnId + loopNumber + usage.totalTokens.
|
|
2261
|
-
|
|
2263
|
+
sendSessionEvent({
|
|
2262
2264
|
type: 'loop',
|
|
2263
2265
|
turnId: event.turnId,
|
|
2264
2266
|
loopNumber: event.loopNumber,
|
|
@@ -2281,7 +2283,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2281
2283
|
if (isPermissionErrorMsg(errMsg)) {
|
|
2282
2284
|
if (!_permissionDiagnosticSent) {
|
|
2283
2285
|
_permissionDiagnosticSent = true;
|
|
2284
|
-
|
|
2286
|
+
sendSessionOutputFrame({
|
|
2285
2287
|
type: 'assistant',
|
|
2286
2288
|
message: {
|
|
2287
2289
|
content: [{
|
|
@@ -2292,7 +2294,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2292
2294
|
}, envelope);
|
|
2293
2295
|
}
|
|
2294
2296
|
} else {
|
|
2295
|
-
|
|
2297
|
+
sendSessionOutputFrame({
|
|
2296
2298
|
type: 'assistant',
|
|
2297
2299
|
message: {
|
|
2298
2300
|
content: [{ type: 'text', text: `⚠️ Error: ${errMsg}` }],
|
|
@@ -2358,11 +2360,11 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2358
2360
|
// session lying around.
|
|
2359
2361
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
2360
2362
|
if (!yeaftDir) {
|
|
2361
|
-
|
|
2363
|
+
sendSessionOutputFrame({
|
|
2362
2364
|
type: 'assistant',
|
|
2363
2365
|
message: { content: [{ type: 'text', text: '⚠️ Yeaft session error: no yeaft directory configured.' }] },
|
|
2364
2366
|
}, { sessionId });
|
|
2365
|
-
|
|
2367
|
+
sendSessionOutputFrame({ type: 'result', result_text: '' }, { sessionId });
|
|
2366
2368
|
return;
|
|
2367
2369
|
}
|
|
2368
2370
|
|
|
@@ -2393,11 +2395,11 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2393
2395
|
}
|
|
2394
2396
|
|
|
2395
2397
|
if (!sessionHandle) {
|
|
2396
|
-
|
|
2398
|
+
sendSessionOutputFrame({
|
|
2397
2399
|
type: 'assistant',
|
|
2398
2400
|
message: { content: [{ type: 'text', text: `⚠️ Session ${sessionId} not found.` }] },
|
|
2399
2401
|
}, { sessionId });
|
|
2400
|
-
|
|
2402
|
+
sendSessionOutputFrame({ type: 'result', result_text: '' }, { sessionId });
|
|
2401
2403
|
return;
|
|
2402
2404
|
}
|
|
2403
2405
|
|
|
@@ -2477,7 +2479,7 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2477
2479
|
const detail = attachmentBundle.failed
|
|
2478
2480
|
.map((f) => ` - ${f.name}: ${f.error}`)
|
|
2479
2481
|
.join('\n');
|
|
2480
|
-
|
|
2482
|
+
sendSessionOutputFrame({
|
|
2481
2483
|
type: 'assistant',
|
|
2482
2484
|
message: { content: [{ type: 'text', text: `⚠️ ${attachmentBundle.failed.length} file(s) could not be attached:\n${detail}` }] },
|
|
2483
2485
|
}, { sessionId });
|
|
@@ -2507,11 +2509,11 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2507
2509
|
});
|
|
2508
2510
|
} catch (err) {
|
|
2509
2511
|
console.warn('[Yeaft] yeaft_session_chat: coord.ingest failed', err?.message || err);
|
|
2510
|
-
|
|
2512
|
+
sendSessionOutputFrame({
|
|
2511
2513
|
type: 'assistant',
|
|
2512
2514
|
message: { content: [{ type: 'text', text: `⚠️ Session dispatch error: ${err?.message || err}` }] },
|
|
2513
2515
|
}, { sessionId });
|
|
2514
|
-
|
|
2516
|
+
sendSessionOutputFrame({ type: 'result', result_text: '' }, { sessionId });
|
|
2515
2517
|
return;
|
|
2516
2518
|
}
|
|
2517
2519
|
|
|
@@ -2528,11 +2530,11 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2528
2530
|
// with a healthy roster. Surface the failure explicitly rather than
|
|
2529
2531
|
// silently retrying as a single-VP turn (the legacy fallback masked
|
|
2530
2532
|
// group-roster bugs).
|
|
2531
|
-
|
|
2533
|
+
sendSessionOutputFrame({
|
|
2532
2534
|
type: 'assistant',
|
|
2533
2535
|
message: { content: [{ type: 'text', text: '⚠️ No VP available to respond — check the group roster.' }] },
|
|
2534
2536
|
}, { sessionId });
|
|
2535
|
-
|
|
2537
|
+
sendSessionOutputFrame({ type: 'result', result_text: '' }, { sessionId });
|
|
2536
2538
|
return;
|
|
2537
2539
|
}
|
|
2538
2540
|
|
|
@@ -2727,7 +2729,7 @@ async function ensureSessionLoaded() {
|
|
|
2727
2729
|
if (session.engine && typeof session.engine.setSubAgentEventSink === 'function') {
|
|
2728
2730
|
session.engine.setSubAgentEventSink((agentId, evt) => {
|
|
2729
2731
|
try {
|
|
2730
|
-
|
|
2732
|
+
sendSessionEvent({ type: 'sub_agent_event', agentId, payload: evt });
|
|
2731
2733
|
} catch { /* ignore */ }
|
|
2732
2734
|
});
|
|
2733
2735
|
}
|
|
@@ -2753,7 +2755,7 @@ async function ensureSessionLoaded() {
|
|
|
2753
2755
|
// Per-group history is hydrated lazily on first `getOrCreateSessionHistory`
|
|
2754
2756
|
// — there's no global "all conversations" tape any more.
|
|
2755
2757
|
|
|
2756
|
-
|
|
2758
|
+
sendSessionEvent({
|
|
2757
2759
|
type: 'session_ready',
|
|
2758
2760
|
conversationId: yeaftConversationId,
|
|
2759
2761
|
model: session.config.model,
|
|
@@ -2812,7 +2814,7 @@ async function runVpTurnWithEscalation(args) {
|
|
|
2812
2814
|
`[Yeaft] runVpTurn watchdog escalation: VP ${vpId} did not return ${deadlineMs}ms after enqueue — emitting synthetic stop and unblocking driver`,
|
|
2813
2815
|
);
|
|
2814
2816
|
try {
|
|
2815
|
-
|
|
2817
|
+
sendSessionOutputFrame(
|
|
2816
2818
|
{ type: 'result', result_text: '', stopped: true },
|
|
2817
2819
|
{ sessionId, vpId, turnId, threadId },
|
|
2818
2820
|
);
|
|
@@ -2918,7 +2920,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
2918
2920
|
if (turnEndEmitted) return;
|
|
2919
2921
|
turnEndEmitted = true;
|
|
2920
2922
|
try {
|
|
2921
|
-
|
|
2923
|
+
sendSessionEvent({
|
|
2922
2924
|
type: 'vp_turn_end',
|
|
2923
2925
|
sessionId,
|
|
2924
2926
|
vpId,
|
|
@@ -2952,7 +2954,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
2952
2954
|
resetQueryTimer();
|
|
2953
2955
|
|
|
2954
2956
|
// Emit turn_start so frontend can create the message block.
|
|
2955
|
-
|
|
2957
|
+
sendSessionEvent({ type: 'vp_turn_start', vpId, threadId, turnId, sessionId, title: thread?.title || '' }, envelope);
|
|
2956
2958
|
// vp-status: LLM call about to start, no text/tool yet → 'thinking'.
|
|
2957
2959
|
try {
|
|
2958
2960
|
getVpStatusBroker().transition({ sessionId, vpId, threadId, title: thread?.title || '', state: 'thinking', turnId, messageCount: thread?.messageIds?.length || 0 });
|
|
@@ -3034,11 +3036,11 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3034
3036
|
// Turn completed — atomically append this VP's output to shared history.
|
|
3035
3037
|
appendTurnToSessionHistory(sessionId, threadId, vpId, [prompt, ...appendedUserPrompts], assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum);
|
|
3036
3038
|
|
|
3037
|
-
|
|
3039
|
+
sendSessionOutputFrame({
|
|
3038
3040
|
type: 'assistant',
|
|
3039
3041
|
message: { content: [] },
|
|
3040
3042
|
}, envelope);
|
|
3041
|
-
|
|
3043
|
+
sendSessionOutputFrame({
|
|
3042
3044
|
type: 'result',
|
|
3043
3045
|
result_text: '',
|
|
3044
3046
|
}, envelope);
|
|
@@ -3052,7 +3054,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3052
3054
|
} catch (err) {
|
|
3053
3055
|
const isAbort = err && (err.name === 'AbortError' || err.name === 'LLMAbortError');
|
|
3054
3056
|
if (isAbort) {
|
|
3055
|
-
|
|
3057
|
+
sendSessionOutputFrame({
|
|
3056
3058
|
type: 'result',
|
|
3057
3059
|
result_text: '',
|
|
3058
3060
|
stopped: true,
|
|
@@ -3079,7 +3081,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3079
3081
|
if (isPermissionErrorMsg(err.message)) {
|
|
3080
3082
|
if (!_permissionDiagnosticSent) {
|
|
3081
3083
|
_permissionDiagnosticSent = true;
|
|
3082
|
-
|
|
3084
|
+
sendSessionOutputFrame({
|
|
3083
3085
|
type: 'assistant',
|
|
3084
3086
|
message: {
|
|
3085
3087
|
content: [{
|
|
@@ -3090,7 +3092,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3090
3092
|
}, envelope);
|
|
3091
3093
|
}
|
|
3092
3094
|
} else {
|
|
3093
|
-
|
|
3095
|
+
sendSessionOutputFrame({
|
|
3094
3096
|
type: 'assistant',
|
|
3095
3097
|
message: {
|
|
3096
3098
|
content: [{
|
|
@@ -3100,7 +3102,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3100
3102
|
},
|
|
3101
3103
|
}, envelope);
|
|
3102
3104
|
}
|
|
3103
|
-
|
|
3105
|
+
sendSessionOutputFrame({
|
|
3104
3106
|
type: 'result',
|
|
3105
3107
|
result_text: '',
|
|
3106
3108
|
}, envelope);
|
|
@@ -3374,7 +3376,7 @@ export function handleYeaftAbortThread(_msg = {}) {
|
|
|
3374
3376
|
const thread = vpMap.get(targetThreadId);
|
|
3375
3377
|
if (thread) thread.status = 'aborted';
|
|
3376
3378
|
}
|
|
3377
|
-
|
|
3379
|
+
sendSessionEvent({ type: 'yeaft_aborted', aborted, all: false, threadId: targetThreadId });
|
|
3378
3380
|
return { aborted, all: false };
|
|
3379
3381
|
}
|
|
3380
3382
|
|
|
@@ -3388,7 +3390,7 @@ export function handleYeaftAbortThread(_msg = {}) {
|
|
|
3388
3390
|
turnAbortCtrls.clear();
|
|
3389
3391
|
turnAbortMeta.clear();
|
|
3390
3392
|
abortAllVpRuntime(aborted);
|
|
3391
|
-
|
|
3393
|
+
sendSessionEvent({ type: 'yeaft_aborted', aborted, all: false });
|
|
3392
3394
|
return { aborted, all: false };
|
|
3393
3395
|
}
|
|
3394
3396
|
|
|
@@ -3409,7 +3411,7 @@ export function handleYeaftAbortAll() {
|
|
|
3409
3411
|
turnAbortCtrls.clear();
|
|
3410
3412
|
turnAbortMeta.clear();
|
|
3411
3413
|
abortAllVpRuntime(aborted);
|
|
3412
|
-
|
|
3414
|
+
sendSessionEvent({ type: 'yeaft_aborted', aborted, all: true });
|
|
3413
3415
|
return { aborted, all: true };
|
|
3414
3416
|
}
|
|
3415
3417
|
|
|
@@ -3421,7 +3423,7 @@ export function handleYeaftAbortAll() {
|
|
|
3421
3423
|
export function handleYeaftAbortTurn(msg = {}) {
|
|
3422
3424
|
const { turnId } = msg;
|
|
3423
3425
|
if (!turnId) {
|
|
3424
|
-
|
|
3426
|
+
sendSessionEvent({ type: 'yeaft_turn_aborted', turnId: null, success: false });
|
|
3425
3427
|
return;
|
|
3426
3428
|
}
|
|
3427
3429
|
const ctrl = turnAbortCtrls.get(turnId);
|
|
@@ -3429,11 +3431,11 @@ export function handleYeaftAbortTurn(msg = {}) {
|
|
|
3429
3431
|
try { ctrl.abort(); } catch { /* best-effort */ }
|
|
3430
3432
|
turnAbortCtrls.delete(turnId);
|
|
3431
3433
|
turnAbortMeta.delete(turnId);
|
|
3432
|
-
|
|
3434
|
+
sendSessionEvent({ type: 'yeaft_turn_aborted', turnId, success: true });
|
|
3433
3435
|
} else {
|
|
3434
3436
|
turnAbortCtrls.delete(turnId);
|
|
3435
3437
|
turnAbortMeta.delete(turnId);
|
|
3436
|
-
|
|
3438
|
+
sendSessionEvent({ type: 'yeaft_turn_aborted', turnId, success: false });
|
|
3437
3439
|
}
|
|
3438
3440
|
}
|
|
3439
3441
|
|
|
@@ -3445,7 +3447,7 @@ export function abortYeaftSession(opts = {}) {
|
|
|
3445
3447
|
if (opts && opts.all) return handleYeaftAbortAll();
|
|
3446
3448
|
if (opts && opts.threadId) return handleYeaftAbortThread({ threadId: opts.threadId });
|
|
3447
3449
|
// No payload — conservative no-op ack.
|
|
3448
|
-
|
|
3450
|
+
sendSessionEvent({ type: 'yeaft_aborted', aborted: [], all: false });
|
|
3449
3451
|
return { aborted: [], all: false };
|
|
3450
3452
|
}
|
|
3451
3453
|
|
|
@@ -3812,7 +3814,7 @@ export function handleYeaftModelSwitch(msg) {
|
|
|
3812
3814
|
|
|
3813
3815
|
session.config.model = msg.model;
|
|
3814
3816
|
|
|
3815
|
-
|
|
3817
|
+
sendSessionEvent({
|
|
3816
3818
|
type: 'model_switched',
|
|
3817
3819
|
model: msg.model,
|
|
3818
3820
|
});
|
|
@@ -3870,7 +3872,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3870
3872
|
}
|
|
3871
3873
|
|
|
3872
3874
|
// Always replay session_ready so refresh / reconnect rebuilds UI state.
|
|
3873
|
-
|
|
3875
|
+
sendSessionEvent({
|
|
3874
3876
|
type: 'session_ready',
|
|
3875
3877
|
conversationId: yeaftConversationId,
|
|
3876
3878
|
model: session.config.model,
|
|
@@ -3907,7 +3909,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3907
3909
|
const delta = session.conversationStore.loadAfterSeqByGroup(sessionId, afterSeq);
|
|
3908
3910
|
for (const entry of delta.messages) {
|
|
3909
3911
|
if (entry.role === 'user') {
|
|
3910
|
-
|
|
3912
|
+
sendSessionOutputFrame({
|
|
3911
3913
|
type: 'user',
|
|
3912
3914
|
message: {
|
|
3913
3915
|
content: entry.content,
|
|
@@ -3923,15 +3925,15 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3923
3925
|
turnId: entry.turnId || entry.threadId || 'main',
|
|
3924
3926
|
};
|
|
3925
3927
|
if (entry.speakerVpId) envelopeOpts.vpId = entry.speakerVpId;
|
|
3926
|
-
|
|
3928
|
+
sendSessionOutputFrame({
|
|
3927
3929
|
type: 'assistant',
|
|
3928
3930
|
message: { id: entry.id || null, content: [{ type: 'text', text: entry.content }] },
|
|
3929
3931
|
ts: entry.ts || null,
|
|
3930
3932
|
}, envelopeOpts);
|
|
3931
|
-
|
|
3933
|
+
sendSessionOutputFrame({ type: 'result', result_text: '' }, envelopeOpts);
|
|
3932
3934
|
}
|
|
3933
3935
|
}
|
|
3934
|
-
|
|
3936
|
+
sendSessionEvent({
|
|
3935
3937
|
type: 'history_loaded',
|
|
3936
3938
|
mode: 'delta',
|
|
3937
3939
|
count: delta.messages.length,
|
|
@@ -3962,7 +3964,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3962
3964
|
|
|
3963
3965
|
for (const entry of replayEntries) {
|
|
3964
3966
|
if (entry.role === 'user') {
|
|
3965
|
-
|
|
3967
|
+
sendSessionOutputFrame({
|
|
3966
3968
|
type: 'user',
|
|
3967
3969
|
message: {
|
|
3968
3970
|
content: entry.content,
|
|
@@ -3982,12 +3984,12 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3982
3984
|
turnId: entry.turnId || entry.threadId || 'main',
|
|
3983
3985
|
};
|
|
3984
3986
|
if (entry.speakerVpId) envelopeOpts.vpId = entry.speakerVpId;
|
|
3985
|
-
|
|
3987
|
+
sendSessionOutputFrame({
|
|
3986
3988
|
type: 'assistant',
|
|
3987
3989
|
message: { id: entry.id || null, content: [{ type: 'text', text: entry.content }] },
|
|
3988
3990
|
ts: entry.ts || null,
|
|
3989
3991
|
}, envelopeOpts);
|
|
3990
|
-
|
|
3992
|
+
sendSessionOutputFrame({ type: 'result', result_text: '' }, envelopeOpts);
|
|
3991
3993
|
}
|
|
3992
3994
|
}
|
|
3993
3995
|
|
|
@@ -4021,7 +4023,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
4021
4023
|
if (last && last.id) latestSeq = session.conversationStore.getMessageSeqById(last.id);
|
|
4022
4024
|
}
|
|
4023
4025
|
|
|
4024
|
-
|
|
4026
|
+
sendSessionEvent({
|
|
4025
4027
|
type: 'history_loaded',
|
|
4026
4028
|
mode: 'recent',
|
|
4027
4029
|
count: replayEntries.length,
|
|
@@ -4168,7 +4170,7 @@ export async function resetYeaftSession() {
|
|
|
4168
4170
|
// Per-group history hydrates lazily via getOrCreateSessionHistory on
|
|
4169
4171
|
// first read. Nothing to seed here.
|
|
4170
4172
|
|
|
4171
|
-
|
|
4173
|
+
sendSessionEvent({
|
|
4172
4174
|
type: 'session_ready',
|
|
4173
4175
|
conversationId: yeaftConversationId,
|
|
4174
4176
|
model: session.config.model,
|