@yeaft/webchat-agent 1.0.476 → 1.0.478

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.
@@ -290,49 +290,49 @@ export function migrateRegisteredWorkDirSessions(yeaftDir) {
290
290
  }
291
291
 
292
292
  /**
293
- * Scan the `.yeaft/sessions/` directory under `workDir` and return every
294
- * session meta we can read. Read-only: never touches the registry.
293
+ * List every Session owned by `yeaftDir` whose normalized workDir matches.
294
+ * Workdir-local `.yeaft/sessions` is also read as a legacy import fallback;
295
+ * those rows are marked `legacyImport` and never replace a current row with
296
+ * the same Session id. The normal Session snapshot may repair its own manifest;
297
+ * this query never writes the legacy workdir registry.
295
298
  *
296
- * Each returned record carries `workDir` (the normalized path we scanned).
297
- * This utility deliberately does NOT decorate `alreadyRegistered` — that
298
- * cross-references the central workdir registry, which is a separate
299
- * concern owned by the handler / caller (see
300
- * `handleYeaftScanWorkdirSessions`). Keeping the utility layer-pure makes
301
- * it usable from contexts that don't have / don't care about the registry
302
- * (e.g. CLI tools, future per-workdir snapshots).
303
- *
304
- * Returns `[]` for missing dir / empty dir / unreadable dir — never throws.
305
- * Sort is `createdAt` descending (most-recent first) because the restore
306
- * UI typically cares about "the session I made yesterday".
307
- *
308
- * @param {string} workDir — the working directory to scan.
299
+ * @param {string} workDir working directory to match
300
+ * @param {string|null} yeaftDir Agent-owned data root; omit for legacy scans
309
301
  * @returns {Array<object>}
310
302
  */
311
- export function scanWorkdirSessions(workDir) {
303
+ export function scanWorkdirSessions(workDir, yeaftDir = null) {
312
304
  const normalized = normalizeWorkDir(workDir);
313
305
  if (!normalized) return [];
306
+
307
+ // Session storage is Agent-owned. The workDir-local `.yeaft/sessions`
308
+ // location below is only a legacy import source; querying a folder must
309
+ // start from the Agent manifest so sidebar UI metadata cannot hide an
310
+ // otherwise valid Session from the create/resume picker.
311
+ const currentSessions = yeaftDir
312
+ ? snapshotSessions(yeaftDir).filter(meta => normalizeWorkDir(meta.workDir) === normalized)
313
+ : [];
314
+ const seen = new Set(currentSessions.map(meta => meta.id));
314
315
  const groupYeaftDir = yeaftDirForWorkDir(normalized);
315
316
  const root = sessionsRoot(groupYeaftDir);
316
- if (!existsSync(root)) return [];
317
- const out = [];
318
- let entries;
317
+ const legacySessions = [];
318
+ let entries = [];
319
319
  try {
320
- entries = readdirSync(root);
321
- } catch {
322
- return [];
323
- }
320
+ if (existsSync(root)) entries = readdirSync(root);
321
+ } catch { /* treat an unreadable legacy root as empty */ }
324
322
  for (const name of entries) {
325
- if (name.startsWith('.')) continue; // skip .archived-* and dotfiles
323
+ if (name.startsWith('.') || seen.has(name)) continue;
326
324
  const dir = join(root, name);
327
325
  try { if (!statSync(dir).isDirectory()) continue; } catch { continue; }
328
326
  const meta = loadSessionMeta(dir);
329
- if (!meta) continue;
330
- out.push({
327
+ if (!meta || seen.has(meta.id)) continue;
328
+ legacySessions.push({
331
329
  ...meta,
332
330
  workDir: normalized,
331
+ legacyImport: true,
333
332
  });
334
333
  }
335
- return out.sort((a, b) => String(b.createdAt || '').localeCompare(String(a.createdAt || '')));
334
+ return [...currentSessions, ...legacySessions]
335
+ .sort((a, b) => String(b.createdAt || '').localeCompare(String(a.createdAt || '')));
336
336
  }
337
337
 
338
338
  /**
@@ -35,6 +35,43 @@ import { buildPluginCatalog, createPluginSkillManager, resolveMcpPluginPolicy }
35
35
  import { MCPManager } from './mcp.js';
36
36
  import { sendToServer } from '../connection/buffer.js';
37
37
  import ctx from '../context.js';
38
+
39
+ const DEBUG_HISTORY_CHUNK_BYTES = 512 * 1024;
40
+
41
+ function splitUtf8Json(text, maxBytes = DEBUG_HISTORY_CHUNK_BYTES) {
42
+ const source = Buffer.from(text, 'utf8');
43
+ const chunks = [];
44
+ for (let start = 0; start < source.length;) {
45
+ let end = Math.min(source.length, start + maxBytes);
46
+ if (end < source.length) {
47
+ while (end > start && (source[end] & 0xc0) === 0x80) end -= 1;
48
+ if (end === start) throw new Error('Unable to split debug history UTF-8 payload');
49
+ }
50
+ chunks.push(source.subarray(start, end).toString('utf8'));
51
+ start = end;
52
+ }
53
+ return chunks;
54
+ }
55
+
56
+ async function sendDebugHistory(payload) {
57
+ const encoded = JSON.stringify(payload);
58
+ if (Buffer.byteLength(encoded, 'utf8') <= DEBUG_HISTORY_CHUNK_BYTES) {
59
+ await sendToServer(payload); // Legacy single-frame wire remains valid.
60
+ return;
61
+ }
62
+ const chunks = splitUtf8Json(encoded);
63
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
64
+ await sendToServer({
65
+ type: 'yeaft_debug_history_chunk',
66
+ requestId: payload.requestId,
67
+ sessionId: payload.sessionId,
68
+ chunkIndex,
69
+ chunkCount: chunks.length,
70
+ data: chunks[chunkIndex],
71
+ });
72
+ }
73
+ }
74
+
38
75
  import { hydrateYeaftStatusFromSession } from './status-cache.js';
39
76
  import { handleVpSubscribe } from './vp/vp-bridge.js';
40
77
  import { createVp, updateVp, deleteVp, readVp, VpCrudError } from './vp/vp-crud.js';
@@ -3595,13 +3632,13 @@ export function handleYeaftScanWorkdirSessions(msg) {
3595
3632
  const workDir = String(msg && msg.workDir || '').trim();
3596
3633
  if (!workDir) throw new SessionCrudError('invalid_workdir', null, 'workDir required');
3597
3634
  const yeaftDir = ctx.CONFIG?.yeaftDir;
3598
- // scanWorkdirSessions is now a compatibility no-op because Session data is
3599
- // user-level only. Keep the decoration shape stable for older clients.
3600
- const sessions = scanWorkdirSessions(workDir);
3635
+ // Current rows come from the Agent manifest; workDir-local rows remain a
3636
+ // legacy import fallback. Keep the decoration shape stable for old clients.
3637
+ const sessions = scanWorkdirSessions(workDir, yeaftDir);
3601
3638
  const registry = readWorkDirRegistry(yeaftDir);
3602
3639
  const decorated = sessions.map(s => ({
3603
3640
  ...s,
3604
- alreadyRegistered: Object.prototype.hasOwnProperty.call(registry, s.id),
3641
+ alreadyRegistered: !s.legacyImport || Object.prototype.hasOwnProperty.call(registry, s.id),
3605
3642
  }));
3606
3643
  sendSessionCrudResult({ op: 'scan_workdir', requestId, ok: true, sessions: decorated });
3607
3644
  } catch (err) {
@@ -6694,7 +6731,7 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
6694
6731
  hasMore = !!out?.hasMore;
6695
6732
  }
6696
6733
  } catch (err) {
6697
- sendToServer({
6734
+ await sendDebugHistory({
6698
6735
  type: 'yeaft_debug_history',
6699
6736
  loops: [],
6700
6737
  turns: [],
@@ -6712,7 +6749,7 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
6712
6749
  });
6713
6750
  return;
6714
6751
  }
6715
- sendToServer({
6752
+ await sendDebugHistory({
6716
6753
  type: 'yeaft_debug_history',
6717
6754
  loops,
6718
6755
  turns,