@yeaft/webchat-agent 1.0.296 → 1.0.299

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.
Files changed (31) hide show
  1. package/local-runtime/server/auth/login.js +2 -0
  2. package/local-runtime/server/db/connection.js +13 -0
  3. package/local-runtime/server/db/session-db.js +3 -0
  4. package/local-runtime/server/db/yeaft-session-db.js +9 -0
  5. package/local-runtime/server/handlers/agent-output.js +27 -0
  6. package/local-runtime/server/handlers/client-conversation.js +2 -2
  7. package/local-runtime/server/handlers/client-work-center.js +4 -3
  8. package/local-runtime/server/routes/auth-routes.js +8 -1
  9. package/local-runtime/server/routes/user-routes.js +1 -0
  10. package/local-runtime/server/session-catalog.js +4 -7
  11. package/local-runtime/version.json +1 -1
  12. package/local-runtime/web/app.bundle.js +110 -95
  13. package/local-runtime/web/app.bundle.js.gz +0 -0
  14. package/local-runtime/web/index.html +2 -2
  15. package/local-runtime/web/style.bundle.css +1 -1
  16. package/local-runtime/web/style.bundle.css.gz +0 -0
  17. package/package.json +1 -1
  18. package/yeaft/engine.js +3 -0
  19. package/yeaft/sessions/session-crud.js +9 -6
  20. package/yeaft/sessions/session-store.js +1 -0
  21. package/yeaft/tools/file-edit.js +8 -6
  22. package/yeaft/tools/file-write.js +2 -2
  23. package/yeaft/tools/registry.js +28 -10
  24. package/yeaft/tools/types.js +4 -0
  25. package/yeaft/web-bridge.js +10 -2
  26. package/yeaft/work-center/coordinator.js +37 -10
  27. package/yeaft/work-center/durable-model.js +59 -3
  28. package/yeaft/work-center/projection.js +2 -0
  29. package/yeaft/work-center/runner.js +14 -5
  30. package/yeaft/work-center/service.js +59 -16
  31. package/yeaft/work-center/store.js +242 -53
@@ -12,12 +12,14 @@ import { generateVerificationCode, maskEmail } from './utils.js';
12
12
  * Helper: complete login and return token + sessionKey + role
13
13
  */
14
14
  export function completeLogin(username, sessionKey, role) {
15
+ const user = getUserByUsername(username);
15
16
  const token = issueSessionToken(username);
16
17
  activeSessions.set(token, { username, sessionKey });
17
18
  return {
18
19
  success: true,
19
20
  token,
20
21
  sessionKey: encodeKey(sessionKey),
22
+ userId: user?.id || null,
21
23
  role: role === 'admin' ? 'admin' : 'pro',
22
24
  needTotpCode: false,
23
25
  needTotpSetup: false,
@@ -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
  `),
@@ -44,6 +44,9 @@ export const sessionDb = {
44
44
  now,
45
45
  id
46
46
  );
47
+ if (updates.metadataChanged === true) {
48
+ stmts.touchSessionMetadata.run(now, id);
49
+ }
47
50
  },
48
51
 
49
52
  setActive(id, active) {
@@ -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
  }
@@ -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
- if (msg.ok === true) {
231
- for (const fileId of pending.attachmentFileIds || []) pendingFiles.delete(fileId);
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') {
@@ -239,7 +239,13 @@ export function registerAuthRoutes(app, { requireAuth, checkRateLimit }) {
239
239
  if (!r) return res.json({ status: 'pending' });
240
240
  if (r.kind === 'login') {
241
241
  setSessionCookie(req, res, r.token);
242
- return res.json({ status: 'login', token: r.token, sessionKey: r.sessionKey, role: r.role });
242
+ return res.json({
243
+ status: 'login',
244
+ token: r.token,
245
+ sessionKey: r.sessionKey,
246
+ userId: r.userId,
247
+ role: r.role,
248
+ });
243
249
  }
244
250
  if (r.kind === 'bind') {
245
251
  return res.json({ status: 'bind', provider: r.provider });
@@ -278,6 +284,7 @@ export function registerAuthRoutes(app, { requireAuth, checkRateLimit }) {
278
284
  const params = new URLSearchParams({
279
285
  token: result.token,
280
286
  sessionKey: result.sessionKey,
287
+ userId: result.userId || '',
281
288
  role: result.role
282
289
  });
283
290
  return res.redirect(`/#/sso-complete?${params.toString()}`);
@@ -57,6 +57,7 @@ export function registerUserRoutes(app, { requireAuth, requireAdmin }) {
57
57
  return res.status(404).json({ error: 'User not found' });
58
58
  }
59
59
  res.json({
60
+ userId: user.id,
60
61
  username: user.username,
61
62
  displayName: user.display_name,
62
63
  email: user.email,
@@ -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
- updatedAt: session.updated_at || null,
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
- updatedAt: session.updatedAt || null,
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 leftRank = Number.isFinite(left.sortRank) ? left.sortRank : Number.MAX_SAFE_INTEGER;
85
- const rightRank = Number.isFinite(right.sortRank) ? right.sortRank : Number.MAX_SAFE_INTEGER;
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.296"}
1
+ {"version":"1.0.299"}