@yeaft/webchat-agent 1.0.295 → 1.0.298
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/index.js +1 -2
- package/local-runtime/server/db/connection.js +13 -0
- package/local-runtime/server/db/session-db.js +3 -0
- package/local-runtime/server/db/yeaft-session-db.js +9 -0
- package/local-runtime/server/handlers/agent-output.js +27 -0
- package/local-runtime/server/handlers/client-conversation.js +2 -2
- package/local-runtime/server/handlers/client-work-center.js +5 -4
- package/local-runtime/server/session-catalog.js +4 -7
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +156 -169
- 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/engine.js +30 -3
- package/yeaft/sessions/session-crud.js +9 -6
- package/yeaft/sessions/session-store.js +1 -0
- package/yeaft/tools/file-edit.js +8 -6
- package/yeaft/tools/file-write.js +2 -2
- package/yeaft/tools/registry.js +28 -10
- package/yeaft/tools/types.js +4 -0
- package/yeaft/web-bridge.js +10 -2
- package/yeaft/work-center/bridge.js +5 -1
- package/yeaft/work-center/controller.js +6 -2
- package/yeaft/work-center/coordinator.js +84 -17
- package/yeaft/work-center/durable-model.js +674 -0
- package/yeaft/work-center/projection.js +10 -1
- package/yeaft/work-center/runner.js +111 -16
- package/yeaft/work-center/service.js +102 -4
- package/yeaft/work-center/store.js +963 -23
package/index.js
CHANGED
|
@@ -136,12 +136,11 @@ async function detectCapabilities() {
|
|
|
136
136
|
// agent build can speak plaintext WS frames. New servers see this and
|
|
137
137
|
// flip `agent.encryptOutbound = false`, stopping outbound encryption
|
|
138
138
|
// to this peer. Old servers ignore the unknown capability token.
|
|
139
|
-
const capabilities = ['background_tasks', 'file_editor', 'ping_session', 'plaintext-ok', 'work_center', 'session_history_search', 'session_history_outline', 'session_history_window_prefetch'];
|
|
139
|
+
const capabilities = ['background_tasks', 'file_editor', 'ping_session', 'plaintext-ok', 'work_center', 'work_center_message_v2', 'session_history_search', 'session_history_outline', 'session_history_window_prefetch'];
|
|
140
140
|
if (process.platform === 'linux') capabilities.push('work_item_attachments');
|
|
141
141
|
const pty = await loadNodePty();
|
|
142
142
|
if (pty) capabilities.push('terminal');
|
|
143
143
|
|
|
144
|
-
|
|
145
144
|
console.log(`[Capabilities] Detected: ${capabilities.join(', ')}`);
|
|
146
145
|
return capabilities;
|
|
147
146
|
}
|
|
@@ -45,6 +45,7 @@ db.exec(`
|
|
|
45
45
|
title TEXT,
|
|
46
46
|
created_at INTEGER NOT NULL,
|
|
47
47
|
updated_at INTEGER NOT NULL,
|
|
48
|
+
metadata_updated_at INTEGER,
|
|
48
49
|
is_active INTEGER DEFAULT 1
|
|
49
50
|
);
|
|
50
51
|
|
|
@@ -153,6 +154,7 @@ const migrations = [
|
|
|
153
154
|
`ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'`,
|
|
154
155
|
`ALTER TABLE messages ADD COLUMN metadata TEXT`,
|
|
155
156
|
`ALTER TABLE sessions ADD COLUMN is_pinned INTEGER DEFAULT 0`,
|
|
157
|
+
`ALTER TABLE sessions ADD COLUMN metadata_updated_at INTEGER`,
|
|
156
158
|
`ALTER TABLE users ADD COLUMN aad_oid TEXT`,
|
|
157
159
|
// fix-chat-title-sticky: persist the "user manually renamed this session"
|
|
158
160
|
// bit so it survives agent reconnect / server restart / DB rehydration.
|
|
@@ -214,6 +216,7 @@ const yeaftSessionsTable = `
|
|
|
214
216
|
announcement TEXT,
|
|
215
217
|
created_at INTEGER,
|
|
216
218
|
updated_at INTEGER NOT NULL,
|
|
219
|
+
metadata_updated_at INTEGER,
|
|
217
220
|
is_archived INTEGER DEFAULT 0,
|
|
218
221
|
is_pinned INTEGER DEFAULT 0,
|
|
219
222
|
sort_order INTEGER,
|
|
@@ -291,6 +294,7 @@ const yeaftMigrations = [
|
|
|
291
294
|
// between chat and yeaft.
|
|
292
295
|
`ALTER TABLE yeaft_sessions ADD COLUMN is_pinned INTEGER DEFAULT 0`,
|
|
293
296
|
`ALTER TABLE yeaft_sessions ADD COLUMN sort_order INTEGER`,
|
|
297
|
+
`ALTER TABLE yeaft_sessions ADD COLUMN metadata_updated_at INTEGER`,
|
|
294
298
|
];
|
|
295
299
|
for (const migration of yeaftMigrations) {
|
|
296
300
|
try { db.exec(migration); } catch (_) { /* column exists */ }
|
|
@@ -531,6 +535,10 @@ export const stmts = {
|
|
|
531
535
|
WHERE id = ?
|
|
532
536
|
`),
|
|
533
537
|
|
|
538
|
+
touchSessionMetadata: db.prepare(`
|
|
539
|
+
UPDATE sessions SET metadata_updated_at = ? WHERE id = ?
|
|
540
|
+
`),
|
|
541
|
+
|
|
534
542
|
updateSessionActive: db.prepare(`
|
|
535
543
|
UPDATE sessions SET is_active = ?, updated_at = ? WHERE id = ?
|
|
536
544
|
`),
|
|
@@ -893,6 +901,11 @@ export const stmts = {
|
|
|
893
901
|
is_archived = excluded.is_archived
|
|
894
902
|
`),
|
|
895
903
|
|
|
904
|
+
touchYeaftSessionMetadata: db.prepare(`
|
|
905
|
+
UPDATE yeaft_sessions SET metadata_updated_at = ?
|
|
906
|
+
WHERE id = ? AND user_id IS ? AND agent_id = ?
|
|
907
|
+
`),
|
|
908
|
+
|
|
896
909
|
getYeaftSession: db.prepare(`
|
|
897
910
|
SELECT * FROM yeaft_sessions WHERE id = ? ORDER BY updated_at DESC LIMIT 1
|
|
898
911
|
`),
|
|
@@ -51,6 +51,7 @@ function mapRow(row) {
|
|
|
51
51
|
announcement: row.announcement || '',
|
|
52
52
|
createdAt: row.created_at || null,
|
|
53
53
|
updatedAt: row.updated_at,
|
|
54
|
+
metadataUpdatedAt: row.metadata_updated_at || null,
|
|
54
55
|
isArchived: row.is_archived === 1,
|
|
55
56
|
// fix-yeaft-session-list-and-menu: persisted pin state. Decorated
|
|
56
57
|
// onto outgoing snapshots in server/handlers/agent-output.js so the
|
|
@@ -90,6 +91,14 @@ export const yeaftSessionDb = {
|
|
|
90
91
|
now,
|
|
91
92
|
0,
|
|
92
93
|
);
|
|
94
|
+
if (session.metadataUpdatedAt) {
|
|
95
|
+
stmts.touchYeaftSessionMetadata.run(
|
|
96
|
+
session.metadataUpdatedAt,
|
|
97
|
+
session.id,
|
|
98
|
+
userId || null,
|
|
99
|
+
agentId,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
93
102
|
},
|
|
94
103
|
|
|
95
104
|
/**
|
|
@@ -528,10 +528,34 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
528
528
|
case 'session_output': {
|
|
529
529
|
const data = hydrateInlinePreviewData(msg.data);
|
|
530
530
|
const event = syncYeaftSessionMetadata(agentId, agent, msg.event);
|
|
531
|
+
let catalogChanged = false;
|
|
531
532
|
if (event?.type === 'yeaft_status') {
|
|
532
533
|
agent.yeaftStatus = event;
|
|
533
534
|
await broadcastAgentList();
|
|
534
535
|
}
|
|
536
|
+
if (event?.type === 'session_roster_changed' && agent.ownerId && event.sessionId) {
|
|
537
|
+
try {
|
|
538
|
+
const existing = yeaftSessionDb.getForAgent(agent.ownerId, agentId, event.sessionId);
|
|
539
|
+
if (existing) {
|
|
540
|
+
yeaftSessionDb.upsertFromSnapshot(agent.ownerId, agentId, {
|
|
541
|
+
id: event.sessionId,
|
|
542
|
+
name: event.name != null ? event.name : (existing.name || event.sessionId),
|
|
543
|
+
roster: Array.isArray(event.roster) ? event.roster : (existing.roster || []),
|
|
544
|
+
defaultVpId: event.defaultVpId != null ? event.defaultVpId : (existing.defaultVpId || null),
|
|
545
|
+
workDir: existing.workDir || '',
|
|
546
|
+
config: existing.config || {},
|
|
547
|
+
announcement: typeof event.announcement === 'string'
|
|
548
|
+
? event.announcement
|
|
549
|
+
: (existing.announcement || ''),
|
|
550
|
+
createdAt: existing.createdAt || Date.now(),
|
|
551
|
+
metadataUpdatedAt: event.metadataUpdatedAt || existing.metadataUpdatedAt || existing.createdAt || null,
|
|
552
|
+
});
|
|
553
|
+
catalogChanged = true;
|
|
554
|
+
}
|
|
555
|
+
} catch (e) {
|
|
556
|
+
console.warn(`[Server] yeaft roster persist failed for agent ${agentId}:`, e?.message || e);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
535
559
|
if (msg.perfTraceId) {
|
|
536
560
|
recordPerfTraceEvent({
|
|
537
561
|
traceId: msg.perfTraceId,
|
|
@@ -614,6 +638,7 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
614
638
|
}
|
|
615
639
|
}
|
|
616
640
|
}
|
|
641
|
+
if (catalogChanged) await broadcastSessionCatalog(agent.ownerId);
|
|
617
642
|
break;
|
|
618
643
|
}
|
|
619
644
|
|
|
@@ -859,8 +884,10 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
859
884
|
? msg.announcement
|
|
860
885
|
: (existing?.announcement || ''),
|
|
861
886
|
createdAt: existing?.createdAt || Date.now(),
|
|
887
|
+
metadataUpdatedAt: msg.metadataUpdatedAt || existing?.metadataUpdatedAt || existing?.createdAt || null,
|
|
862
888
|
};
|
|
863
889
|
yeaftSessionDb.upsertFromSnapshot(agent.ownerId, agentId, merged);
|
|
890
|
+
await broadcastSessionCatalog(agent.ownerId);
|
|
864
891
|
}
|
|
865
892
|
}
|
|
866
893
|
} catch (e) {
|
|
@@ -1017,12 +1017,12 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
1017
1017
|
const titleAgent = agents.get(settingsAgentId);
|
|
1018
1018
|
const titleConvInfo = titleAgent?.conversations.get(settingsConvId);
|
|
1019
1019
|
if (msg.title) {
|
|
1020
|
-
sessionDb.update(settingsConvId, { title: msg.title, isCustomTitle: 1 });
|
|
1020
|
+
sessionDb.update(settingsConvId, { title: msg.title, isCustomTitle: 1, metadataChanged: true });
|
|
1021
1021
|
if (titleConvInfo) { titleConvInfo.title = msg.title; titleConvInfo.customTitle = true; }
|
|
1022
1022
|
} else {
|
|
1023
1023
|
// Clearing the custom title returns the session to auto-naming
|
|
1024
1024
|
// mode — the next user prompt repopulates the title.
|
|
1025
|
-
sessionDb.update(settingsConvId, { isCustomTitle: 0 });
|
|
1025
|
+
sessionDb.update(settingsConvId, { isCustomTitle: 0, metadataChanged: true });
|
|
1026
1026
|
if (titleConvInfo) { titleConvInfo.customTitle = false; }
|
|
1027
1027
|
}
|
|
1028
1028
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
const REQUEST_TIMEOUT_MS = 60_000;
|
|
14
14
|
const pendingRequests = new Map();
|
|
15
|
-
const WORK_ITEM_ATTACHMENT_OPS = new Set(['create', 'work_item_message', 'action_input', 'guide']);
|
|
15
|
+
const WORK_ITEM_ATTACHMENT_OPS = new Set(['create', 'post_work_item_message', 'work_item_message', 'action_input', 'guide']);
|
|
16
16
|
|
|
17
17
|
export function workCenterOpAcceptsAttachments(op) {
|
|
18
18
|
return WORK_ITEM_ATTACHMENT_OPS.has(op);
|
|
@@ -227,9 +227,10 @@ export async function deliverWorkCenterResponse(agentId, msg) {
|
|
|
227
227
|
const pending = typeof msg?.requestId === 'string' ? pendingRequests.get(msg.requestId) : null;
|
|
228
228
|
if (!pending || pending.agentId !== agentId) return false;
|
|
229
229
|
pendingRequests.delete(msg.requestId);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
// Keep staged Work Center bytes until the existing upload cleanup expires them.
|
|
231
|
+
// The Agent may have committed the durable clientMessageId while this response
|
|
232
|
+
// is lost before the browser receives it; a same-envelope retry must still be
|
|
233
|
+
// able to resolve the original fileId and reach the Agent receipt preflight.
|
|
233
234
|
const { agentId: _untrustedAgentId, requestId: _opaqueRequestId, _requestUserId, ...payload } = msg;
|
|
234
235
|
let response = payload;
|
|
235
236
|
if (msg.ok === true && msg.op === 'preview_attachment') {
|
|
@@ -56,7 +56,7 @@ export function projectSessionCatalog({
|
|
|
56
56
|
pinned: meta.pinned ?? session.is_pinned === 1,
|
|
57
57
|
sortRank: meta.sortRank ?? null,
|
|
58
58
|
createdAt: session.created_at || null,
|
|
59
|
-
|
|
59
|
+
metadataUpdatedAt: session.metadata_updated_at ?? session.created_at ?? null,
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -75,17 +75,14 @@ export function projectSessionCatalog({
|
|
|
75
75
|
pinned: meta.pinned ?? !!session.pinned,
|
|
76
76
|
sortRank: meta.sortRank ?? session.sortOrder ?? null,
|
|
77
77
|
createdAt: session.createdAt || null,
|
|
78
|
-
|
|
78
|
+
metadataUpdatedAt: session.metadataUpdatedAt ?? session.createdAt ?? null,
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
return rows.sort((left, right) => {
|
|
83
83
|
if (left.pinned !== right.pinned) return left.pinned ? -1 : 1;
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
if (leftRank !== rightRank) return leftRank - rightRank;
|
|
87
|
-
const creationDelta = timestampValue(right.createdAt) - timestampValue(left.createdAt);
|
|
88
|
-
if (creationDelta !== 0) return creationDelta;
|
|
84
|
+
const metadataDelta = timestampValue(right.metadataUpdatedAt) - timestampValue(left.metadataUpdatedAt);
|
|
85
|
+
if (metadataDelta !== 0) return metadataDelta;
|
|
89
86
|
return left.catalogKey.localeCompare(right.catalogKey);
|
|
90
87
|
});
|
|
91
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.298"}
|