@yeaft/webchat-agent 1.0.284 → 1.0.286
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/local-runtime/server/handlers/agent-output.js +16 -2
- package/local-runtime/server/handlers/client-conversation.js +124 -53
- package/local-runtime/server/ws-client.js +2 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +116 -95
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/web-bridge.js +32 -10
|
@@ -548,6 +548,13 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
548
548
|
bytes: Buffer.byteLength(JSON.stringify(msg)),
|
|
549
549
|
});
|
|
550
550
|
}
|
|
551
|
+
// History request completions are client-scoped; live Session output stays
|
|
552
|
+
// broadcast to every authenticated tab owned by this user.
|
|
553
|
+
const hasRequestedClient = typeof msg._requestClientId === 'string' && !!msg._requestClientId;
|
|
554
|
+
const requestedClient = hasRequestedClient ? webClients.get(msg._requestClientId) : null;
|
|
555
|
+
const outputClients = hasRequestedClient
|
|
556
|
+
? (requestedClient ? [[msg._requestClientId, requestedClient]] : [])
|
|
557
|
+
: webClients;
|
|
551
558
|
// Forward Yeaft Session output to all authenticated clients of this agent's owner.
|
|
552
559
|
// Payload carries { conversationId, data } (assistant output frame) or { event } (metadata).
|
|
553
560
|
//
|
|
@@ -572,12 +579,13 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
572
579
|
// pass through whatever was stamped, including empty strings. IDs
|
|
573
580
|
// are non-empty in practice, but we don't want the relay silently
|
|
574
581
|
// eating a legitimate "" or 0 if one ever shows up.
|
|
575
|
-
for (const [cId, c] of
|
|
582
|
+
for (const [cId, c] of outputClients) {
|
|
576
583
|
if (c.authenticated && (CONFIG.skipAuth || c.userId === agent.ownerId)) {
|
|
577
584
|
await sendToWebClient(c, {
|
|
578
585
|
type: 'yeaft_output',
|
|
579
586
|
conversationId: msg.conversationId,
|
|
580
587
|
...(msg.perfTraceId != null ? { perfTraceId: msg.perfTraceId } : {}),
|
|
588
|
+
...(msg.requestId != null ? { requestId: msg.requestId } : {}),
|
|
581
589
|
// Stamp the source agent so the web sessions store can keep
|
|
582
590
|
// per-agent rosters (cross-agent listing in the unified
|
|
583
591
|
// sidebar). Older web bundles ignore the extra field.
|
|
@@ -695,13 +703,19 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
695
703
|
detail: { mode: msg.mode || 'older', count: messages.length },
|
|
696
704
|
});
|
|
697
705
|
}
|
|
698
|
-
|
|
706
|
+
const hasRequestedClient = typeof msg._requestClientId === 'string' && !!msg._requestClientId;
|
|
707
|
+
const requestedClient = hasRequestedClient ? webClients.get(msg._requestClientId) : null;
|
|
708
|
+
const historyClients = hasRequestedClient
|
|
709
|
+
? (requestedClient ? [[msg._requestClientId, requestedClient]] : [])
|
|
710
|
+
: webClients;
|
|
711
|
+
for (const [cId, c] of historyClients) {
|
|
699
712
|
if (c.authenticated && (CONFIG.skipAuth || c.userId === agent.ownerId)) {
|
|
700
713
|
await sendToWebClient(c, {
|
|
701
714
|
type: 'yeaft_history_chunk',
|
|
702
715
|
agentId,
|
|
703
716
|
conversationId: msg.conversationId,
|
|
704
717
|
...(msg.perfTraceId != null ? { perfTraceId: msg.perfTraceId } : {}),
|
|
718
|
+
...(msg.requestId != null ? { requestId: msg.requestId } : {}),
|
|
705
719
|
...(msg.sessionId != null ? { sessionId: msg.sessionId } : {}),
|
|
706
720
|
messages,
|
|
707
721
|
mode: msg.mode || 'older',
|
|
@@ -184,71 +184,138 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
-
await broadcastAgentList();
|
|
188
187
|
// Yeaft session rows are only actionable while their owning Agent is
|
|
189
188
|
// connected. Keep offline rows in the DB for reconnect recovery, but do
|
|
190
189
|
// not hydrate them into the sidebar where remove/settings cannot work.
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
console.warn('[Server] yeaft session hydrate failed:', e?.message || e);
|
|
190
|
+
// Send every authoritative slice before a single completion frame; the UI
|
|
191
|
+
// must not promote a partial multi-Agent inventory to its visible state.
|
|
192
|
+
let hydrateError = null;
|
|
193
|
+
try {
|
|
194
|
+
if (!client.userId) throw new Error('session owner unavailable');
|
|
195
|
+
const allRows = yeaftSessionDb.getByUser(client.userId);
|
|
196
|
+
const byAgent = groupOnlineYeaftSessions(allRows);
|
|
197
|
+
for (const [agentId, agent] of agents) {
|
|
198
|
+
const visible = agent?.ws?.readyState === 1 && (
|
|
199
|
+
CONFIG.skipAuth
|
|
200
|
+
|| agent.ownerId === client.userId
|
|
201
|
+
|| (!agent.ownerId && client.role === 'admin')
|
|
202
|
+
);
|
|
203
|
+
if (visible && !byAgent[agentId]) byAgent[agentId] = [];
|
|
206
204
|
}
|
|
205
|
+
for (const [agentId, sessions] of Object.entries(byAgent)) {
|
|
206
|
+
await sendToWebClient(client, {
|
|
207
|
+
type: 'yeaft_session_hydrate',
|
|
208
|
+
...(typeof msg.requestId === 'string' && msg.requestId ? { requestId: msg.requestId } : {}),
|
|
209
|
+
agentId,
|
|
210
|
+
sessions,
|
|
211
|
+
fromDb: true,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (Object.keys(byAgent).length === 0) {
|
|
215
|
+
await sendToWebClient(client, {
|
|
216
|
+
type: 'yeaft_session_hydrate',
|
|
217
|
+
...(typeof msg.requestId === 'string' && msg.requestId ? { requestId: msg.requestId } : {}),
|
|
218
|
+
agentId: null,
|
|
219
|
+
sessions: [],
|
|
220
|
+
fromDb: true,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
hydrateError = e?.message || String(e);
|
|
225
|
+
console.warn('[Server] yeaft session hydrate failed:', hydrateError);
|
|
207
226
|
}
|
|
227
|
+
await broadcastAgentList();
|
|
228
|
+
await sendToWebClient(client, {
|
|
229
|
+
type: 'yeaft_session_hydrate_complete',
|
|
230
|
+
...(typeof msg.requestId === 'string' && msg.requestId ? { requestId: msg.requestId } : {}),
|
|
231
|
+
ok: hydrateError === null,
|
|
232
|
+
...(hydrateError ? { error: hydrateError } : {}),
|
|
233
|
+
});
|
|
208
234
|
break;
|
|
209
235
|
|
|
210
236
|
case 'select_agent': {
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
237
|
+
const silentSelection = msg.silent === true;
|
|
238
|
+
const explicitGenerationAtStart = Number(client.agentSelectionGeneration || 0);
|
|
239
|
+
let selectionGeneration;
|
|
240
|
+
if (silentSelection) {
|
|
241
|
+
// Background reconnect/restore is advisory. It cannot supersede a user
|
|
242
|
+
// selection, but concurrent silent restores still obey newest-wins.
|
|
243
|
+
if (client.pendingAgentSelectionGeneration != null) break;
|
|
244
|
+
selectionGeneration = Number(client.silentAgentSelectionGeneration || 0) + 1;
|
|
245
|
+
client.silentAgentSelectionGeneration = selectionGeneration;
|
|
246
|
+
} else {
|
|
247
|
+
selectionGeneration = explicitGenerationAtStart + 1;
|
|
248
|
+
client.agentSelectionGeneration = selectionGeneration;
|
|
249
|
+
client.pendingAgentSelectionGeneration = selectionGeneration;
|
|
250
|
+
}
|
|
251
|
+
const selectionIsCurrent = () => silentSelection
|
|
252
|
+
? client.pendingAgentSelectionGeneration == null
|
|
253
|
+
&& Number(client.agentSelectionGeneration || 0) === explicitGenerationAtStart
|
|
254
|
+
&& client.silentAgentSelectionGeneration === selectionGeneration
|
|
255
|
+
: client.agentSelectionGeneration === selectionGeneration
|
|
256
|
+
&& client.pendingAgentSelectionGeneration === selectionGeneration;
|
|
257
|
+
try {
|
|
258
|
+
const allowed = await checkAgentAccess(msg.agentId);
|
|
259
|
+
if (!selectionIsCurrent()) break;
|
|
260
|
+
if (!allowed) {
|
|
261
|
+
if (typeof msg.requestId === 'string' && msg.requestId) {
|
|
262
|
+
await sendToWebClient(client, {
|
|
263
|
+
type: 'agent_selected', requestId: msg.requestId, agentId: msg.agentId, ok: false,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
217
267
|
}
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
).map(c => {
|
|
224
|
-
// fix-chat-title-sticky: lazy-hydrate the title AND the
|
|
225
|
-
// sticky bit on send. This catches conversations rebuilt
|
|
226
|
-
// before the bit was wired through (older code paths,
|
|
227
|
-
if (!c.title || c.customTitle === undefined) {
|
|
228
|
-
const dbSession = sessionDb.get(c.id);
|
|
229
|
-
if (dbSession?.title && !c.title) c.title = dbSession.title;
|
|
230
|
-
if (c.customTitle === undefined) c.customTitle = !!dbSession?.customTitle;
|
|
268
|
+
const agent = agents.get(msg.agentId);
|
|
269
|
+
if (agent && agent.ws.readyState === 1 /* WebSocket.OPEN */) {
|
|
270
|
+
client.currentAgent = msg.agentId;
|
|
271
|
+
if (!silentSelection) {
|
|
272
|
+
client.currentConversation = null;
|
|
231
273
|
}
|
|
232
|
-
return c;
|
|
233
|
-
});
|
|
234
|
-
await sendToWebClient(client, {
|
|
235
|
-
type: 'agent_selected',
|
|
236
|
-
agentId: msg.agentId,
|
|
237
|
-
agentName: agent.name,
|
|
238
|
-
workDir: agent.workDir,
|
|
239
|
-
capabilities: agent.capabilities || ['terminal', 'file_editor', 'background_tasks'],
|
|
240
|
-
version: agent.version || null,
|
|
241
|
-
conversations: filteredConvs,
|
|
242
|
-
slashCommands: agent.slashCommands || [],
|
|
243
|
-
slashCommandDescriptions: agent.slashCommandDescriptions || {}
|
|
244
|
-
});
|
|
245
274
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
275
|
+
if (silentSelection) break;
|
|
276
|
+
|
|
277
|
+
const filteredConvs = Array.from(agent.conversations.values()).filter(c =>
|
|
278
|
+
CONFIG.skipAuth || !c.userId || c.userId === client.userId
|
|
279
|
+
).map(c => {
|
|
280
|
+
// fix-chat-title-sticky: lazy-hydrate the title AND the
|
|
281
|
+
// sticky bit on send. This catches conversations rebuilt
|
|
282
|
+
// before the bit was wired through (older code paths,
|
|
283
|
+
if (!c.title || c.customTitle === undefined) {
|
|
284
|
+
const dbSession = sessionDb.get(c.id);
|
|
285
|
+
if (dbSession?.title && !c.title) c.title = dbSession.title;
|
|
286
|
+
if (c.customTitle === undefined) c.customTitle = !!dbSession?.customTitle;
|
|
287
|
+
}
|
|
288
|
+
return c;
|
|
289
|
+
});
|
|
290
|
+
await sendToWebClient(client, {
|
|
291
|
+
type: 'agent_selected',
|
|
292
|
+
...(typeof msg.requestId === 'string' && msg.requestId ? { requestId: msg.requestId } : {}),
|
|
293
|
+
agentId: msg.agentId,
|
|
294
|
+
agentName: agent.name,
|
|
295
|
+
workDir: agent.workDir,
|
|
296
|
+
capabilities: agent.capabilities || ['terminal', 'file_editor', 'background_tasks'],
|
|
297
|
+
version: agent.version || null,
|
|
298
|
+
conversations: filteredConvs,
|
|
299
|
+
slashCommands: agent.slashCommands || [],
|
|
300
|
+
slashCommandDescriptions: agent.slashCommandDescriptions || {}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// If slash commands cache is empty, ask Agent to reload from filesystem
|
|
304
|
+
if (!agent.slashCommands?.length) {
|
|
305
|
+
await forwardToAgent(msg.agentId, { type: 'request_slash_commands' });
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
await sendToWebClient(client, { type: 'error', message: 'Agent not found or offline' });
|
|
309
|
+
if (typeof msg.requestId === 'string' && msg.requestId) {
|
|
310
|
+
await sendToWebClient(client, {
|
|
311
|
+
type: 'agent_selected', requestId: msg.requestId, agentId: msg.agentId, ok: false,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} finally {
|
|
316
|
+
if (!silentSelection && client.pendingAgentSelectionGeneration === selectionGeneration) {
|
|
317
|
+
client.pendingAgentSelectionGeneration = null;
|
|
249
318
|
}
|
|
250
|
-
} else {
|
|
251
|
-
await sendToWebClient(client, { type: 'error', message: 'Agent not found or offline' });
|
|
252
319
|
}
|
|
253
320
|
break;
|
|
254
321
|
}
|
|
@@ -1067,9 +1134,11 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
1067
1134
|
type: 'yeaft_load_history',
|
|
1068
1135
|
limit: msg.limit,
|
|
1069
1136
|
sessionId: msg.sessionId || null,
|
|
1137
|
+
...(typeof msg.requestId === 'string' ? { requestId: msg.requestId } : {}),
|
|
1070
1138
|
...(typeof msg.perfTraceId === 'string' ? { perfTraceId: msg.perfTraceId } : {}),
|
|
1071
1139
|
...(Number.isFinite(msg.afterSeq) ? { afterSeq: msg.afterSeq } : {}),
|
|
1072
1140
|
...(typeof msg.afterMessageId === 'string' ? { afterMessageId: msg.afterMessageId } : {}),
|
|
1141
|
+
_requestClientId: clientId,
|
|
1073
1142
|
};
|
|
1074
1143
|
if (forwarded.perfTraceId) {
|
|
1075
1144
|
recordPerfTraceEvent({
|
|
@@ -1152,9 +1221,11 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
1152
1221
|
await forwardToAgent(moreAgentId, {
|
|
1153
1222
|
type: 'yeaft_load_more_history',
|
|
1154
1223
|
sessionId: msg.sessionId || null,
|
|
1224
|
+
requestId: typeof msg.requestId === 'string' ? msg.requestId : null,
|
|
1155
1225
|
beforeSeq: typeof msg.beforeSeq === 'number' ? msg.beforeSeq : null,
|
|
1156
1226
|
turns: typeof msg.turns === 'number' ? msg.turns : 20,
|
|
1157
1227
|
...(typeof msg.perfTraceId === 'string' ? { perfTraceId: msg.perfTraceId } : {}),
|
|
1228
|
+
_requestClientId: clientId,
|
|
1158
1229
|
});
|
|
1159
1230
|
break;
|
|
1160
1231
|
}
|
|
@@ -93,7 +93,8 @@ export function handleWebConnection(ws, url, req = {}) {
|
|
|
93
93
|
success: true,
|
|
94
94
|
sessionKey: sessionKey ? encodeKey(sessionKey) : null,
|
|
95
95
|
role,
|
|
96
|
-
acceptPlaintext: true
|
|
96
|
+
acceptPlaintext: true,
|
|
97
|
+
yeaftSessionInventoryComplete: true,
|
|
97
98
|
}));
|
|
98
99
|
setTimeout(() => broadcastAgentList(), 100);
|
|
99
100
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.286"}
|