@yeaft/webchat-agent 1.0.299 → 1.0.300
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/database.js +1 -0
- package/local-runtime/server/db/connection.js +86 -0
- package/local-runtime/server/db/yeaft-project-db.js +225 -0
- package/local-runtime/server/handlers/agent-output.js +41 -5
- package/local-runtime/server/handlers/client-conversation.js +73 -0
- package/local-runtime/server/ws-utils.js +5 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +73 -78
- 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/conversation/search.js +13 -7
- package/yeaft/engine.js +21 -4
- package/yeaft/sub-agent/runner.js +5 -0
- package/yeaft/tools/history-search.js +12 -3
- package/yeaft/tools/types.js +1 -0
- package/yeaft/web-bridge.js +90 -6
|
@@ -4,6 +4,7 @@ export { userDb } from './db/user-db.js';
|
|
|
4
4
|
export { invitationDb } from './db/invitation-db.js';
|
|
5
5
|
export { sessionDb } from './db/session-db.js';
|
|
6
6
|
export { yeaftSessionDb } from './db/yeaft-session-db.js';
|
|
7
|
+
export { yeaftProjectDb, YeaftProjectDbError } from './db/yeaft-project-db.js';
|
|
7
8
|
export { sessionUiMetadataDb } from './db/session-ui-metadata-db.js';
|
|
8
9
|
export { messageDb } from './db/message-db.js';
|
|
9
10
|
export { userStatsDb } from './db/user-stats-db.js';
|
|
@@ -241,6 +241,37 @@ db.exec(`
|
|
|
241
241
|
);
|
|
242
242
|
CREATE INDEX IF NOT EXISTS idx_session_ui_metadata_user_sort
|
|
243
243
|
ON session_ui_metadata(user_id, pinned DESC, sort_rank ASC);
|
|
244
|
+
|
|
245
|
+
-- Projects are user-owned organization metadata. Membership keeps the full
|
|
246
|
+
-- Agent + Session identity because Session ids are only unique per Agent.
|
|
247
|
+
CREATE TABLE IF NOT EXISTS yeaft_projects (
|
|
248
|
+
id TEXT NOT NULL,
|
|
249
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
250
|
+
name TEXT NOT NULL,
|
|
251
|
+
sort_order INTEGER NOT NULL,
|
|
252
|
+
created_at INTEGER NOT NULL,
|
|
253
|
+
updated_at INTEGER NOT NULL,
|
|
254
|
+
PRIMARY KEY (user_id, id)
|
|
255
|
+
);
|
|
256
|
+
CREATE TABLE IF NOT EXISTS yeaft_project_sessions (
|
|
257
|
+
user_id TEXT NOT NULL,
|
|
258
|
+
project_id TEXT NOT NULL,
|
|
259
|
+
agent_id TEXT NOT NULL,
|
|
260
|
+
session_id TEXT NOT NULL,
|
|
261
|
+
created_at INTEGER NOT NULL,
|
|
262
|
+
PRIMARY KEY (user_id, agent_id, session_id),
|
|
263
|
+
FOREIGN KEY (user_id, project_id) REFERENCES yeaft_projects(user_id, id) ON DELETE CASCADE
|
|
264
|
+
);
|
|
265
|
+
CREATE INDEX IF NOT EXISTS idx_yeaft_projects_user_sort
|
|
266
|
+
ON yeaft_projects(user_id, sort_order ASC, created_at ASC);
|
|
267
|
+
CREATE INDEX IF NOT EXISTS idx_yeaft_project_sessions_project
|
|
268
|
+
ON yeaft_project_sessions(user_id, project_id, agent_id);
|
|
269
|
+
CREATE TABLE IF NOT EXISTS yeaft_project_imports (
|
|
270
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
271
|
+
agent_id TEXT NOT NULL,
|
|
272
|
+
imported_at INTEGER NOT NULL,
|
|
273
|
+
PRIMARY KEY (user_id, agent_id)
|
|
274
|
+
);
|
|
244
275
|
`);
|
|
245
276
|
|
|
246
277
|
try {
|
|
@@ -645,6 +676,61 @@ export const stmts = {
|
|
|
645
676
|
DELETE FROM session_ui_metadata WHERE user_id = ? AND catalog_key = ?
|
|
646
677
|
`),
|
|
647
678
|
|
|
679
|
+
// Project organization metadata. Projects are server-owned and may contain
|
|
680
|
+
// Sessions from multiple Agents; sharing still filters by agent_id.
|
|
681
|
+
insertYeaftProject: db.prepare(`
|
|
682
|
+
INSERT INTO yeaft_projects (id, user_id, name, sort_order, created_at, updated_at)
|
|
683
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
684
|
+
`),
|
|
685
|
+
getYeaftProjectsByUser: db.prepare(`
|
|
686
|
+
SELECT * FROM yeaft_projects WHERE user_id = ? ORDER BY sort_order ASC, created_at ASC
|
|
687
|
+
`),
|
|
688
|
+
getYeaftProjectForUser: db.prepare(`
|
|
689
|
+
SELECT * FROM yeaft_projects WHERE user_id = ? AND id = ?
|
|
690
|
+
`),
|
|
691
|
+
updateYeaftProjectName: db.prepare(`
|
|
692
|
+
UPDATE yeaft_projects SET name = ?, updated_at = ? WHERE user_id = ? AND id = ?
|
|
693
|
+
`),
|
|
694
|
+
deleteYeaftProject: db.prepare(`
|
|
695
|
+
DELETE FROM yeaft_projects WHERE user_id = ? AND id = ?
|
|
696
|
+
`),
|
|
697
|
+
getYeaftProjectMembersByUser: db.prepare(`
|
|
698
|
+
SELECT * FROM yeaft_project_sessions
|
|
699
|
+
WHERE user_id = ?
|
|
700
|
+
ORDER BY created_at ASC, agent_id ASC, session_id ASC
|
|
701
|
+
`),
|
|
702
|
+
getYeaftProjectForSession: db.prepare(`
|
|
703
|
+
SELECT p.* FROM yeaft_projects p
|
|
704
|
+
JOIN yeaft_project_sessions m
|
|
705
|
+
ON m.user_id = p.user_id AND m.project_id = p.id
|
|
706
|
+
WHERE m.user_id = ? AND m.agent_id = ? AND m.session_id = ?
|
|
707
|
+
`),
|
|
708
|
+
getYeaftProjectMembersForAgent: db.prepare(`
|
|
709
|
+
SELECT agent_id, session_id FROM yeaft_project_sessions
|
|
710
|
+
WHERE user_id = ? AND project_id = ? AND agent_id = ?
|
|
711
|
+
ORDER BY created_at ASC, session_id ASC
|
|
712
|
+
`),
|
|
713
|
+
deleteYeaftProjectSessionMembership: db.prepare(`
|
|
714
|
+
DELETE FROM yeaft_project_sessions
|
|
715
|
+
WHERE user_id = ? AND agent_id = ? AND session_id = ?
|
|
716
|
+
`),
|
|
717
|
+
insertYeaftProjectSessionMembership: db.prepare(`
|
|
718
|
+
INSERT INTO yeaft_project_sessions
|
|
719
|
+
(user_id, project_id, agent_id, session_id, created_at)
|
|
720
|
+
VALUES (?, ?, ?, ?, ?)
|
|
721
|
+
`),
|
|
722
|
+
deleteYeaftProjectMembershipsForSession: db.prepare(`
|
|
723
|
+
DELETE FROM yeaft_project_sessions
|
|
724
|
+
WHERE user_id = ? AND agent_id = ? AND session_id = ?
|
|
725
|
+
`),
|
|
726
|
+
getYeaftProjectImport: db.prepare(`
|
|
727
|
+
SELECT imported_at FROM yeaft_project_imports WHERE user_id = ? AND agent_id = ?
|
|
728
|
+
`),
|
|
729
|
+
insertYeaftProjectImport: db.prepare(`
|
|
730
|
+
INSERT OR IGNORE INTO yeaft_project_imports (user_id, agent_id, imported_at)
|
|
731
|
+
VALUES (?, ?, ?)
|
|
732
|
+
`),
|
|
733
|
+
|
|
648
734
|
// Message 操作
|
|
649
735
|
insertMessage: db.prepare(`
|
|
650
736
|
INSERT INTO messages (session_id, role, content, message_type, tool_name, tool_input, created_at, metadata)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { stmts, transaction } from './connection.js';
|
|
3
|
+
|
|
4
|
+
export class YeaftProjectDbError extends Error {
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message || code);
|
|
7
|
+
this.name = 'YeaftProjectDbError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function requireUserId(value) {
|
|
13
|
+
const userId = typeof value === 'string' ? value.trim() : '';
|
|
14
|
+
if (!userId) throw new YeaftProjectDbError('missing_user', 'User identity is required');
|
|
15
|
+
return userId;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function requireName(value) {
|
|
19
|
+
const name = typeof value === 'string' ? value.trim() : '';
|
|
20
|
+
if (!name) throw new YeaftProjectDbError('invalid_name', 'Project name is required');
|
|
21
|
+
return name.slice(0, 120);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function requireId(value, code, label) {
|
|
25
|
+
const id = typeof value === 'string' ? value.trim() : '';
|
|
26
|
+
if (!id) throw new YeaftProjectDbError(code, `${label} is required`);
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mapProject(row, members = []) {
|
|
31
|
+
if (!row) return null;
|
|
32
|
+
return {
|
|
33
|
+
id: row.id,
|
|
34
|
+
name: row.name,
|
|
35
|
+
sortOrder: row.sort_order,
|
|
36
|
+
createdAt: row.created_at,
|
|
37
|
+
updatedAt: row.updated_at,
|
|
38
|
+
members: members.map(member => ({
|
|
39
|
+
agentId: member.agent_id,
|
|
40
|
+
sessionId: member.session_id,
|
|
41
|
+
})),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function requireProject(userId, projectId) {
|
|
46
|
+
const row = stmts.getYeaftProjectForUser.get(userId, projectId);
|
|
47
|
+
if (!row) throw new YeaftProjectDbError('not_found', 'Project not found');
|
|
48
|
+
return row;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const yeaftProjectDb = {
|
|
52
|
+
list(userId) {
|
|
53
|
+
const ownerId = requireUserId(userId);
|
|
54
|
+
const membersByProject = new Map();
|
|
55
|
+
for (const member of stmts.getYeaftProjectMembersByUser.all(ownerId)) {
|
|
56
|
+
if (!membersByProject.has(member.project_id)) membersByProject.set(member.project_id, []);
|
|
57
|
+
membersByProject.get(member.project_id).push(member);
|
|
58
|
+
}
|
|
59
|
+
return stmts.getYeaftProjectsByUser.all(ownerId)
|
|
60
|
+
.map(row => mapProject(row, membersByProject.get(row.id) || []));
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
listForAgent(userId, agentId) {
|
|
64
|
+
const targetAgentId = typeof agentId === 'string' ? agentId.trim() : '';
|
|
65
|
+
return this.list(userId).map(project => ({
|
|
66
|
+
...project,
|
|
67
|
+
sessionIds: targetAgentId
|
|
68
|
+
? project.members
|
|
69
|
+
.filter(member => member.agentId === targetAgentId)
|
|
70
|
+
.map(member => member.sessionId)
|
|
71
|
+
: [],
|
|
72
|
+
}));
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
create(userId, name) {
|
|
76
|
+
const ownerId = requireUserId(userId);
|
|
77
|
+
const projects = this.list(ownerId);
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const project = {
|
|
80
|
+
id: `project-${randomUUID().slice(0, 8)}`,
|
|
81
|
+
name: requireName(name),
|
|
82
|
+
sortOrder: projects.reduce((max, row) => Math.max(max, Number(row.sortOrder) || 0), -1) + 1,
|
|
83
|
+
createdAt: now,
|
|
84
|
+
updatedAt: now,
|
|
85
|
+
members: [],
|
|
86
|
+
};
|
|
87
|
+
stmts.insertYeaftProject.run(
|
|
88
|
+
project.id,
|
|
89
|
+
ownerId,
|
|
90
|
+
project.name,
|
|
91
|
+
project.sortOrder,
|
|
92
|
+
now,
|
|
93
|
+
now,
|
|
94
|
+
);
|
|
95
|
+
return project;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
rename(userId, projectId, name) {
|
|
99
|
+
const ownerId = requireUserId(userId);
|
|
100
|
+
const id = requireId(projectId, 'invalid_project_id', 'Project id');
|
|
101
|
+
requireProject(ownerId, id);
|
|
102
|
+
const nextName = requireName(name);
|
|
103
|
+
stmts.updateYeaftProjectName.run(nextName, Date.now(), ownerId, id);
|
|
104
|
+
return mapProject(stmts.getYeaftProjectForUser.get(ownerId, id),
|
|
105
|
+
stmts.getYeaftProjectMembersByUser.all(ownerId).filter(member => member.project_id === id));
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
delete(userId, projectId) {
|
|
109
|
+
const ownerId = requireUserId(userId);
|
|
110
|
+
const id = requireId(projectId, 'invalid_project_id', 'Project id');
|
|
111
|
+
requireProject(ownerId, id);
|
|
112
|
+
stmts.deleteYeaftProject.run(ownerId, id);
|
|
113
|
+
return { projectId: id };
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
moveSession(userId, { agentId, sessionId, projectId = null } = {}) {
|
|
117
|
+
const ownerId = requireUserId(userId);
|
|
118
|
+
const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
|
|
119
|
+
const targetSessionId = requireId(sessionId, 'invalid_session_id', 'Session id');
|
|
120
|
+
const targetProjectId = projectId == null || projectId === ''
|
|
121
|
+
? null
|
|
122
|
+
: requireId(projectId, 'invalid_project_id', 'Project id');
|
|
123
|
+
if (targetProjectId) requireProject(ownerId, targetProjectId);
|
|
124
|
+
|
|
125
|
+
transaction(() => {
|
|
126
|
+
stmts.deleteYeaftProjectSessionMembership.run(ownerId, targetAgentId, targetSessionId);
|
|
127
|
+
if (targetProjectId) {
|
|
128
|
+
stmts.insertYeaftProjectSessionMembership.run(
|
|
129
|
+
ownerId,
|
|
130
|
+
targetProjectId,
|
|
131
|
+
targetAgentId,
|
|
132
|
+
targetSessionId,
|
|
133
|
+
Date.now(),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
return { agentId: targetAgentId, sessionId: targetSessionId, projectId: targetProjectId };
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
reconcileAgentSessions(userId, agentId, sessionIds) {
|
|
141
|
+
const ownerId = requireUserId(userId);
|
|
142
|
+
const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
|
|
143
|
+
const currentIds = new Set((Array.isArray(sessionIds) ? sessionIds : [])
|
|
144
|
+
.filter(id => typeof id === 'string' && id.trim())
|
|
145
|
+
.map(id => id.trim()));
|
|
146
|
+
let removed = 0;
|
|
147
|
+
for (const project of this.list(ownerId)) {
|
|
148
|
+
for (const member of project.members) {
|
|
149
|
+
if (member.agentId !== targetAgentId || currentIds.has(member.sessionId)) continue;
|
|
150
|
+
removed += stmts.deleteYeaftProjectMembershipsForSession.run(
|
|
151
|
+
ownerId,
|
|
152
|
+
targetAgentId,
|
|
153
|
+
member.sessionId,
|
|
154
|
+
).changes;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return removed;
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
removeSession(userId, agentId, sessionId) {
|
|
161
|
+
const ownerId = requireUserId(userId);
|
|
162
|
+
const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
|
|
163
|
+
const targetSessionId = requireId(sessionId, 'invalid_session_id', 'Session id');
|
|
164
|
+
return stmts.deleteYeaftProjectMembershipsForSession.run(
|
|
165
|
+
ownerId,
|
|
166
|
+
targetAgentId,
|
|
167
|
+
targetSessionId,
|
|
168
|
+
).changes > 0;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
importLegacyProjects(userId, agentId, projects, sessionExists = () => true) {
|
|
172
|
+
const ownerId = requireUserId(userId);
|
|
173
|
+
const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
|
|
174
|
+
if (stmts.getYeaftProjectImport.get(ownerId, targetAgentId)) return false;
|
|
175
|
+
if (!Array.isArray(projects)) return false;
|
|
176
|
+
const rows = projects;
|
|
177
|
+
transaction(() => {
|
|
178
|
+
const existingProjects = this.list(ownerId);
|
|
179
|
+
const usedNames = new Set(existingProjects.map(project => project.name));
|
|
180
|
+
let sortOrder = existingProjects.length;
|
|
181
|
+
for (const row of rows) {
|
|
182
|
+
const baseName = typeof row?.name === 'string' ? row.name.trim() : '';
|
|
183
|
+
if (!baseName) continue;
|
|
184
|
+
let name = baseName;
|
|
185
|
+
for (let suffix = 2; usedNames.has(name); suffix += 1) name = `${baseName} ${suffix}`;
|
|
186
|
+
const projectId = `project-${randomUUID().slice(0, 8)}`;
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
stmts.insertYeaftProject.run(projectId, ownerId, name.slice(0, 120), sortOrder, now, now);
|
|
189
|
+
sortOrder += 1;
|
|
190
|
+
usedNames.add(name);
|
|
191
|
+
for (const rawSessionId of Array.isArray(row.sessionIds) ? row.sessionIds : []) {
|
|
192
|
+
const sessionId = typeof rawSessionId === 'string' ? rawSessionId.trim() : '';
|
|
193
|
+
if (!sessionId || !sessionExists(sessionId)) continue;
|
|
194
|
+
stmts.deleteYeaftProjectSessionMembership.run(ownerId, targetAgentId, sessionId);
|
|
195
|
+
stmts.insertYeaftProjectSessionMembership.run(
|
|
196
|
+
ownerId,
|
|
197
|
+
projectId,
|
|
198
|
+
targetAgentId,
|
|
199
|
+
sessionId,
|
|
200
|
+
now,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
stmts.insertYeaftProjectImport.run(ownerId, targetAgentId, Date.now());
|
|
205
|
+
})();
|
|
206
|
+
return true;
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
contextForSession(userId, agentId, sessionId) {
|
|
210
|
+
const ownerId = requireUserId(userId);
|
|
211
|
+
const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
|
|
212
|
+
const targetSessionId = requireId(sessionId, 'invalid_session_id', 'Session id');
|
|
213
|
+
const project = stmts.getYeaftProjectForSession.get(ownerId, targetAgentId, targetSessionId);
|
|
214
|
+
if (!project) return null;
|
|
215
|
+
const sameAgentMembers = stmts.getYeaftProjectMembersForAgent
|
|
216
|
+
.all(ownerId, project.id, targetAgentId)
|
|
217
|
+
.map(row => row.session_id)
|
|
218
|
+
.filter(id => id && id !== targetSessionId);
|
|
219
|
+
return {
|
|
220
|
+
projectId: project.id,
|
|
221
|
+
projectName: project.name,
|
|
222
|
+
sessionIds: sameAgentMembers,
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
-
import { messageDb, yeaftSessionDb } from '../database.js';
|
|
2
|
+
import { messageDb, yeaftProjectDb, yeaftSessionDb } from '../database.js';
|
|
3
|
+
import { transaction } from '../db/connection.js';
|
|
3
4
|
import { broadcastAgentList, broadcastSessionCatalog, forwardToClients, sendToAgent, sendToWebClient } from '../ws-utils.js';
|
|
4
5
|
import { webClients, previewFiles } from '../context.js';
|
|
5
6
|
import { CONFIG } from '../config.js';
|
|
@@ -33,6 +34,19 @@ export function decorateYeaftSessionsWithPinned(agentId, sessions) {
|
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
function reconcileAuthoritativeSessionSnapshot(ownerId, agentId, sessions) {
|
|
38
|
+
const rows = Array.isArray(sessions) ? sessions : [];
|
|
39
|
+
transaction(() => {
|
|
40
|
+
yeaftSessionDb.reconcileFromSnapshot(ownerId, agentId, rows);
|
|
41
|
+
yeaftProjectDb.reconcileAgentSessions(
|
|
42
|
+
ownerId,
|
|
43
|
+
agentId,
|
|
44
|
+
rows.map(row => row?.id).filter(Boolean),
|
|
45
|
+
);
|
|
46
|
+
})();
|
|
47
|
+
return rows;
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
function syncYeaftSessionMetadata(agentId, agent, event) {
|
|
37
51
|
if (!event || typeof event !== 'object') return event;
|
|
38
52
|
const ownerId = agent?.ownerId || null;
|
|
@@ -40,7 +54,15 @@ function syncYeaftSessionMetadata(agentId, agent, event) {
|
|
|
40
54
|
if (event.type === 'session_list_updated') {
|
|
41
55
|
const rows = Array.isArray(event.sessions) ? event.sessions : [];
|
|
42
56
|
try {
|
|
43
|
-
if (ownerId)
|
|
57
|
+
if (ownerId) {
|
|
58
|
+
reconcileAuthoritativeSessionSnapshot(ownerId, agentId, rows);
|
|
59
|
+
yeaftProjectDb.importLegacyProjects(
|
|
60
|
+
ownerId,
|
|
61
|
+
agentId,
|
|
62
|
+
event.projects,
|
|
63
|
+
sessionId => rows.some(row => row?.id === sessionId),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
44
66
|
} catch (e) {
|
|
45
67
|
console.warn(`[Server] yeaft session persist failed for agent ${agentId}:`, e?.message || e);
|
|
46
68
|
}
|
|
@@ -52,7 +74,7 @@ function syncYeaftSessionMetadata(agentId, agent, event) {
|
|
|
52
74
|
const sessionId = event.sessionId;
|
|
53
75
|
if (event.ok && op === 'list' && Array.isArray(event.sessions)) {
|
|
54
76
|
try {
|
|
55
|
-
if (ownerId)
|
|
77
|
+
if (ownerId) reconcileAuthoritativeSessionSnapshot(ownerId, agentId, event.sessions);
|
|
56
78
|
} catch (e) {
|
|
57
79
|
console.warn(`[Server] yeaft session list persist failed for agent ${agentId}:`, e?.message || e);
|
|
58
80
|
}
|
|
@@ -63,6 +85,7 @@ function syncYeaftSessionMetadata(agentId, agent, event) {
|
|
|
63
85
|
if (op === 'archive') {
|
|
64
86
|
try {
|
|
65
87
|
yeaftSessionDb.setArchivedForAgent(ownerId, agentId, sessionId, true);
|
|
88
|
+
yeaftProjectDb.removeSession(ownerId, agentId, sessionId);
|
|
66
89
|
} catch (e) {
|
|
67
90
|
console.warn('[Server] Yeaft Session metadata archive failed:', e?.message || e);
|
|
68
91
|
}
|
|
@@ -71,6 +94,7 @@ function syncYeaftSessionMetadata(agentId, agent, event) {
|
|
|
71
94
|
|
|
72
95
|
try {
|
|
73
96
|
yeaftSessionDb.deleteForAgent(ownerId, agentId, sessionId);
|
|
97
|
+
yeaftProjectDb.removeSession(ownerId, agentId, sessionId);
|
|
74
98
|
} catch (e) {
|
|
75
99
|
console.warn('[Server] Yeaft Session metadata cleanup failed:', e?.message || e);
|
|
76
100
|
}
|
|
@@ -527,7 +551,14 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
527
551
|
case 'yeaft_session_output':
|
|
528
552
|
case 'session_output': {
|
|
529
553
|
const data = hydrateInlinePreviewData(msg.data);
|
|
530
|
-
|
|
554
|
+
let event = syncYeaftSessionMetadata(agentId, agent, msg.event);
|
|
555
|
+
if ((event?.type === 'session_list_updated' || event?.type === 'session_crud_result') && agent.ownerId) {
|
|
556
|
+
event = {
|
|
557
|
+
...event,
|
|
558
|
+
projects: yeaftProjectDb.listForAgent(agent.ownerId, agentId),
|
|
559
|
+
projectsAuthoritative: true,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
531
562
|
let catalogChanged = false;
|
|
532
563
|
if (event?.type === 'yeaft_status') {
|
|
533
564
|
agent.yeaftStatus = event;
|
|
@@ -850,6 +881,8 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
850
881
|
type: msg.type,
|
|
851
882
|
agentId: agentId,
|
|
852
883
|
sessions: decoratedSessions,
|
|
884
|
+
projects: agent.ownerId ? yeaftProjectDb.listForAgent(agent.ownerId, agentId) : [],
|
|
885
|
+
projectsAuthoritative: true,
|
|
853
886
|
});
|
|
854
887
|
}
|
|
855
888
|
}
|
|
@@ -907,7 +940,10 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
907
940
|
// probes too: entering Yeaft asks each connected agent for its opened
|
|
908
941
|
// sessions, and that response must carry persisted server-side pin
|
|
909
942
|
// state before it hits the web store.
|
|
910
|
-
const
|
|
943
|
+
const syncedMsg = syncYeaftSessionMetadata(agentId, agent, msg);
|
|
944
|
+
const outboundMsg = agent.ownerId
|
|
945
|
+
? { ...syncedMsg, projects: yeaftProjectDb.listForAgent(agent.ownerId, agentId), projectsAuthoritative: true }
|
|
946
|
+
: { ...syncedMsg, projects: [], projectsAuthoritative: true };
|
|
911
947
|
// CRUD acknowledgements are not authoritative catalog snapshots. The
|
|
912
948
|
// agent emits session_list_updated after mutations; broadcast only after
|
|
913
949
|
// that reconciliation so create/rename cannot briefly project stale data.
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
sessionDb,
|
|
5
5
|
messageDb,
|
|
6
6
|
userDb,
|
|
7
|
+
yeaftProjectDb,
|
|
7
8
|
yeaftSessionDb,
|
|
8
9
|
sessionUiMetadataDb,
|
|
9
10
|
} from '../database.js';
|
|
@@ -1277,6 +1278,65 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
1277
1278
|
break;
|
|
1278
1279
|
}
|
|
1279
1280
|
|
|
1281
|
+
case 'yeaft_project_mutation': {
|
|
1282
|
+
const requestId = typeof msg.requestId === 'string' ? msg.requestId : null;
|
|
1283
|
+
const op = msg.op;
|
|
1284
|
+
const respond = async (payload) => {
|
|
1285
|
+
await sendToWebClient(client, {
|
|
1286
|
+
type: 'yeaft_output',
|
|
1287
|
+
agentId: msg.targetAgentId || msg.agentId || client.currentAgent || null,
|
|
1288
|
+
event: {
|
|
1289
|
+
type: 'project_mutation_result',
|
|
1290
|
+
requestId,
|
|
1291
|
+
op,
|
|
1292
|
+
projectsAuthoritative: true,
|
|
1293
|
+
...payload,
|
|
1294
|
+
},
|
|
1295
|
+
});
|
|
1296
|
+
};
|
|
1297
|
+
try {
|
|
1298
|
+
if (!client.userId) throw new Error('User identity is required');
|
|
1299
|
+
let result = null;
|
|
1300
|
+
if (op === 'create') result = yeaftProjectDb.create(client.userId, msg.name);
|
|
1301
|
+
else if (op === 'rename') result = yeaftProjectDb.rename(client.userId, msg.projectId, msg.name);
|
|
1302
|
+
else if (op === 'delete') result = yeaftProjectDb.delete(client.userId, msg.projectId);
|
|
1303
|
+
else if (op === 'move_session') {
|
|
1304
|
+
const agentId = msg.targetAgentId || msg.agentId;
|
|
1305
|
+
const sessionId = typeof msg.sessionId === 'string' ? msg.sessionId.trim() : '';
|
|
1306
|
+
if (!agentId || !sessionId) throw new Error('Agent and Session identities are required');
|
|
1307
|
+
const targetSession = yeaftSessionDb.getForAgent(client.userId, agentId, sessionId);
|
|
1308
|
+
if (!targetSession) throw new Error('Session not found');
|
|
1309
|
+
if (targetSession.isArchived) {
|
|
1310
|
+
const error = new Error('Archived Sessions cannot be moved to Projects');
|
|
1311
|
+
error.code = 'session_archived';
|
|
1312
|
+
throw error;
|
|
1313
|
+
}
|
|
1314
|
+
result = yeaftProjectDb.moveSession(client.userId, {
|
|
1315
|
+
agentId,
|
|
1316
|
+
sessionId,
|
|
1317
|
+
projectId: msg.projectId || null,
|
|
1318
|
+
});
|
|
1319
|
+
} else {
|
|
1320
|
+
throw new Error('Unknown Project operation');
|
|
1321
|
+
}
|
|
1322
|
+
const responseAgentId = msg.targetAgentId || msg.agentId || client.currentAgent || null;
|
|
1323
|
+
const projects = responseAgentId
|
|
1324
|
+
? yeaftProjectDb.listForAgent(client.userId, responseAgentId)
|
|
1325
|
+
: yeaftProjectDb.list(client.userId);
|
|
1326
|
+
await respond({ ok: true, result, projects });
|
|
1327
|
+
await broadcastSessionCatalog(client.userId);
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
await respond({
|
|
1330
|
+
ok: false,
|
|
1331
|
+
error: {
|
|
1332
|
+
code: err?.code || 'project_mutation_failed',
|
|
1333
|
+
message: err?.message || String(err),
|
|
1334
|
+
},
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1280
1340
|
case 'yeaft_merge_thread':
|
|
1281
1341
|
case 'unify_merge_thread': {
|
|
1282
1342
|
const mergeAgentId = msg.agentId || client.currentAgent;
|
|
@@ -1404,6 +1464,19 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
1404
1464
|
rest.type = relayType;
|
|
1405
1465
|
if (rest.type === 'yeaft_session_send' || rest.type === 'yeaft_session_chat') {
|
|
1406
1466
|
trackUserTurn(client.userId, Buffer.byteLength(JSON.stringify(msg)));
|
|
1467
|
+
const sessionId = typeof rest.sessionId === 'string' ? rest.sessionId.trim() : '';
|
|
1468
|
+
if (sessionId && client.userId) {
|
|
1469
|
+
const projectContext = yeaftProjectDb.contextForSession(
|
|
1470
|
+
client.userId,
|
|
1471
|
+
relayAgentId,
|
|
1472
|
+
sessionId,
|
|
1473
|
+
);
|
|
1474
|
+
rest.projectContext = projectContext || {
|
|
1475
|
+
projectId: null,
|
|
1476
|
+
projectName: null,
|
|
1477
|
+
sessionIds: [],
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1407
1480
|
}
|
|
1408
1481
|
|
|
1409
1482
|
if (rest.type === 'yeaft_session_chat' && !rest.id) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WebSocket } from 'ws';
|
|
2
2
|
import { CONFIG } from './config.js';
|
|
3
3
|
import { encrypt, decrypt, isEncrypted, encodeKey } from './encryption.js';
|
|
4
|
-
import { sessionDb, yeaftSessionDb, sessionUiMetadataDb } from './database.js';
|
|
4
|
+
import { sessionDb, yeaftProjectDb, yeaftSessionDb, sessionUiMetadataDb } from './database.js';
|
|
5
5
|
import { projectSessionCatalog } from './session-catalog.js';
|
|
6
6
|
import { agents, webClients, directoryCache, DIR_CACHE_TTL, DIR_CACHE_MAX_SIZE, trackMessageBytesSent } from './context.js';
|
|
7
7
|
|
|
@@ -126,6 +126,8 @@ export async function broadcastSessionCatalog(userId) {
|
|
|
126
126
|
await sendToWebClient(client, {
|
|
127
127
|
type: 'session_catalog_snapshot',
|
|
128
128
|
catalog: buildSessionCatalog(userId, client.role),
|
|
129
|
+
projects: yeaftProjectDb.list(userId),
|
|
130
|
+
projectsAuthoritative: true,
|
|
129
131
|
});
|
|
130
132
|
} catch (e) {
|
|
131
133
|
console.warn('[Server] session catalog projection failed:', e?.message || e);
|
|
@@ -187,6 +189,8 @@ export async function broadcastAgentList() {
|
|
|
187
189
|
await sendToWebClient(client, {
|
|
188
190
|
type: 'session_catalog_snapshot',
|
|
189
191
|
catalog: buildSessionCatalog(client.userId, client.role),
|
|
192
|
+
projects: yeaftProjectDb.list(client.userId),
|
|
193
|
+
projectsAuthoritative: true,
|
|
190
194
|
});
|
|
191
195
|
} catch (e) {
|
|
192
196
|
console.warn('[Server] session catalog projection failed:', e?.message || e);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.300"}
|