@yeaft/webchat-agent 1.0.575 → 1.0.576
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/connection/message-router.js +8 -22
- package/local-runtime/server/handlers/agent-output.js +12 -34
- package/local-runtime/server/handlers/agent-sync.js +4 -15
- package/local-runtime/server/handlers/client-conversation.js +7 -2
- package/local-runtime/server/handlers/client-misc.js +4 -6
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +101 -298
- 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/cli-session-runner.js +2 -2
- package/yeaft/cli.js +1 -1
- package/yeaft/config.js +3 -1
- package/yeaft/debug-trace.js +12 -135
- package/yeaft/init.js +3 -22
- package/yeaft/migrate/sessions.js +52 -47
- package/yeaft/session.js +15 -108
- package/yeaft/sessions/seed-default.js +0 -26
- package/yeaft/sessions/session-crud.js +6 -29
- package/yeaft/sub-agent/runner.js +7 -8
- package/yeaft/web-bridge.js +16 -308
- package/yeaft/work-center/runner.js +2 -98
|
Binary file
|
package/package.json
CHANGED
|
@@ -115,8 +115,8 @@ export function createCliVpEngine(loaded, sessionId, vpId) {
|
|
|
115
115
|
trace: loaded.trace,
|
|
116
116
|
config: effectiveConfig,
|
|
117
117
|
conversationStore: loaded.conversationStore,
|
|
118
|
-
memoryIndex:
|
|
119
|
-
amsRegistry:
|
|
118
|
+
memoryIndex: null,
|
|
119
|
+
amsRegistry: null,
|
|
120
120
|
toolRegistry: loaded.toolRegistry,
|
|
121
121
|
skillManager: loaded.skillManager,
|
|
122
122
|
mcpManager: loaded.mcpManager,
|
package/yeaft/cli.js
CHANGED
|
@@ -480,7 +480,7 @@ async function runREPL(config, args) {
|
|
|
480
480
|
}
|
|
481
481
|
|
|
482
482
|
case 'memory': {
|
|
483
|
-
console.log('
|
|
483
|
+
console.log('Dream memory is disabled. Historical memory files are retained but are not loaded automatically.');
|
|
484
484
|
break;
|
|
485
485
|
}
|
|
486
486
|
|
package/yeaft/config.js
CHANGED
|
@@ -545,7 +545,9 @@ export function loadConfig(overrides = {}) {
|
|
|
545
545
|
yeaft: normaliseYeaftSection(jsonConfig.yeaft),
|
|
546
546
|
telemetry: normaliseTelemetrySection(jsonConfig.telemetry),
|
|
547
547
|
workCenter: { enabled: jsonConfig.workCenter?.enabled === true },
|
|
548
|
-
|
|
548
|
+
// Dream runtime is disabled. Keep the stable shape but never honor a
|
|
549
|
+
// persisted toggle from an older client.
|
|
550
|
+
dream: { enabled: false },
|
|
549
551
|
browserRuntime: normaliseBrowserRuntimeSection(jsonConfig.browserRuntime),
|
|
550
552
|
|
|
551
553
|
// Agent-level tools / skills / MCP server allowlists. Missing fields mean
|
package/yeaft/debug-trace.js
CHANGED
|
@@ -18,12 +18,10 @@ import { createInterface } from 'readline';
|
|
|
18
18
|
const TRACE_VERSION = 3;
|
|
19
19
|
const REQUEST_RETENTION = 10;
|
|
20
20
|
const MAX_HISTORY_LIMIT = 5;
|
|
21
|
-
const MAX_DREAM_EVENTS = 100;
|
|
22
21
|
// Debug records are diagnostic source data. They deliberately do not inherit
|
|
23
22
|
// model-context or ordinary history budgets, and explicit detail transport is
|
|
24
23
|
// chunked instead of truncating persisted values.
|
|
25
24
|
const TRACE_APPEND_BATCH_MS = 100;
|
|
26
|
-
const EVENT_FLUSH_INTERVAL_MS = 30_000;
|
|
27
25
|
const MAX_SEARCH_PATTERN_CHARS = 300;
|
|
28
26
|
|
|
29
27
|
function isPlainObject(value) {
|
|
@@ -924,23 +922,6 @@ export class DebugTrace {
|
|
|
924
922
|
#appendTimer = null;
|
|
925
923
|
/** @type {number} */
|
|
926
924
|
#sequence = 0;
|
|
927
|
-
/**
|
|
928
|
-
* In-memory dream/event ring (authoritative copy; persisted async).
|
|
929
|
-
* @type {Array<object>}
|
|
930
|
-
*/
|
|
931
|
-
#events = [];
|
|
932
|
-
/** @type {boolean} */
|
|
933
|
-
#eventsDirty = false;
|
|
934
|
-
/**
|
|
935
|
-
* Whether the on-disk events.json has been folded into #events yet. The
|
|
936
|
-
* events ring must merge prior-run history exactly once before any flush
|
|
937
|
-
* overwrites the file, or a live append landing before first hydrate would
|
|
938
|
-
* silently destroy cross-restart dream history.
|
|
939
|
-
* @type {boolean}
|
|
940
|
-
*/
|
|
941
|
-
#eventsHydrated = false;
|
|
942
|
-
/** @type {NodeJS.Timeout|null} */
|
|
943
|
-
#eventFlushTimer = null;
|
|
944
925
|
/**
|
|
945
926
|
* One-time metadata hydrate guard. Reads/maintenance keep only bounded
|
|
946
927
|
* request headers resident. Full loop/tool payloads are loaded for one
|
|
@@ -1102,32 +1083,10 @@ export class DebugTrace {
|
|
|
1102
1083
|
return id;
|
|
1103
1084
|
}
|
|
1104
1085
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
// dream/event sink runs on the engine hot path, so it must not block on a
|
|
1110
|
-
// synchronous read-modify-write of events.json.
|
|
1111
|
-
this.#events.push({
|
|
1112
|
-
id,
|
|
1113
|
-
traceId: traceId || String(eventType || 'event'),
|
|
1114
|
-
eventType: eventType || 'event',
|
|
1115
|
-
eventData: cloneJsonValue(eventData),
|
|
1116
|
-
createdAt: Date.now(),
|
|
1117
|
-
});
|
|
1118
|
-
if (this.#events.length > MAX_DREAM_EVENTS) {
|
|
1119
|
-
this.#events = this.#events.slice(-MAX_DREAM_EVENTS);
|
|
1120
|
-
}
|
|
1121
|
-
this.#scheduleEventFlush();
|
|
1122
|
-
return id;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
event(eventType, eventData = null) {
|
|
1126
|
-
const traceId = (eventData && typeof eventData === 'object' && (eventData.turnId || eventData.runId))
|
|
1127
|
-
? String(eventData.turnId || eventData.runId)
|
|
1128
|
-
: String(eventType || 'event');
|
|
1129
|
-
return this.logEvent({ traceId, eventType, eventData });
|
|
1130
|
-
}
|
|
1086
|
+
// The global events.json ring belonged to Dream. Keep these call shapes for
|
|
1087
|
+
// compatibility, but never read, append, or rewrite its archived contents.
|
|
1088
|
+
logEvent() { return 'null'; }
|
|
1089
|
+
event() { return 'null'; }
|
|
1131
1090
|
|
|
1132
1091
|
async queryByMessage(messageId) {
|
|
1133
1092
|
await this.#drainWrites();
|
|
@@ -1163,7 +1122,7 @@ export class DebugTrace {
|
|
|
1163
1122
|
}
|
|
1164
1123
|
}
|
|
1165
1124
|
|
|
1166
|
-
async fetchTurnDebug({ sessionId, turnId
|
|
1125
|
+
async fetchTurnDebug({ sessionId, turnId } = {}) {
|
|
1167
1126
|
const requestedSessionId = typeof sessionId === 'string' && sessionId ? sessionId : null;
|
|
1168
1127
|
const requestedTurnId = typeof turnId === 'string' && turnId ? turnId : null;
|
|
1169
1128
|
if (!requestedSessionId || !requestedTurnId) {
|
|
@@ -1193,16 +1152,16 @@ export class DebugTrace {
|
|
|
1193
1152
|
}
|
|
1194
1153
|
}
|
|
1195
1154
|
}
|
|
1196
|
-
const dreamEvents =
|
|
1155
|
+
const dreamEvents = []; // Compatibility shape; retired Dream events are not replayed.
|
|
1197
1156
|
if (!trace) return { loops: [], turns: [], dreamEvents, detailTurnId: requestedTurnId };
|
|
1198
1157
|
const expanded = expandTrace(trace);
|
|
1199
1158
|
return { ...expanded, dreamEvents, detailTurnId: requestedTurnId };
|
|
1200
1159
|
}
|
|
1201
1160
|
|
|
1202
|
-
async fetchRecentDebugHistory({ limit = MAX_HISTORY_LIMIT,
|
|
1161
|
+
async fetchRecentDebugHistory({ limit = MAX_HISTORY_LIMIT, sessionId = null, threadId = null, indexOnly = false, detailTurnId = null, search = '' } = {}) {
|
|
1203
1162
|
const requestedDetailTurnId = typeof detailTurnId === 'string' && detailTurnId ? detailTurnId : null;
|
|
1204
1163
|
if (requestedDetailTurnId && sessionId) {
|
|
1205
|
-
const detail = await this.fetchTurnDebug({ sessionId, turnId: requestedDetailTurnId
|
|
1164
|
+
const detail = await this.fetchTurnDebug({ sessionId, turnId: requestedDetailTurnId });
|
|
1206
1165
|
return { ...detail, hasMore: false, limit: detail.loops.length, indexOnly: false };
|
|
1207
1166
|
}
|
|
1208
1167
|
await this.#drainWrites();
|
|
@@ -1212,7 +1171,7 @@ export class DebugTrace {
|
|
|
1212
1171
|
.map(({ trace }) => trace)
|
|
1213
1172
|
.filter(trace => !threadId || trace.threadId === threadId)
|
|
1214
1173
|
.filter(trace => requestedDetailTurnId || traceMatchesRegex(trace, searchRegex));
|
|
1215
|
-
const dreamEvents =
|
|
1174
|
+
const dreamEvents = [];
|
|
1216
1175
|
if (requestedDetailTurnId) {
|
|
1217
1176
|
const trace = traces.find(t => t.requestId === requestedDetailTurnId || t.traceId === requestedDetailTurnId);
|
|
1218
1177
|
if (!trace) return { loops: [], turns: [], dreamEvents, hasMore: false, limit: 0, indexOnly: false, detailTurnId: requestedDetailTurnId };
|
|
@@ -1308,7 +1267,7 @@ export class DebugTrace {
|
|
|
1308
1267
|
turnCount += counts.loopCount;
|
|
1309
1268
|
toolCount += counts.toolCount;
|
|
1310
1269
|
}
|
|
1311
|
-
const eventCount =
|
|
1270
|
+
const eventCount = 0;
|
|
1312
1271
|
const { bytes } = await countDirFiles(this.#rootDir);
|
|
1313
1272
|
return { turnCount, toolCount, eventCount, dbSizeBytes: bytes, fileSizeBytes: bytes, requestCount: traces.length };
|
|
1314
1273
|
}
|
|
@@ -1343,10 +1302,8 @@ export class DebugTrace {
|
|
|
1343
1302
|
this.#initializedRequestKeys.clear();
|
|
1344
1303
|
this.#reconciledRetentionSessions.clear();
|
|
1345
1304
|
this.#retentionIndex.clear();
|
|
1346
|
-
this.#events = [];
|
|
1347
1305
|
this.#hydrated = false;
|
|
1348
1306
|
this.#hydratePromise = null;
|
|
1349
|
-
this.#eventsHydrated = false;
|
|
1350
1307
|
this.#acceptingWrites = true;
|
|
1351
1308
|
}
|
|
1352
1309
|
|
|
@@ -1497,39 +1454,19 @@ export class DebugTrace {
|
|
|
1497
1454
|
if (this.#hydrated) return;
|
|
1498
1455
|
if (this.#hydratePromise) return this.#hydratePromise;
|
|
1499
1456
|
this.#hydratePromise = (async () => {
|
|
1500
|
-
const
|
|
1501
|
-
readTraceHeaders(this.#rootDir, null),
|
|
1502
|
-
readJson(join(this.#rootDir, 'events.json')),
|
|
1503
|
-
]);
|
|
1457
|
+
const headers = await readTraceHeaders(this.#rootDir, null);
|
|
1504
1458
|
for (const { trace } of headers) {
|
|
1505
1459
|
if (!trace?.requestKey || this.#requestCache.has(trace.requestKey)) continue;
|
|
1506
1460
|
this.#diskHeaders.set(trace.requestKey, trace.active
|
|
1507
1461
|
? { ...trace, active: false, interrupted: true }
|
|
1508
1462
|
: trace);
|
|
1509
1463
|
}
|
|
1510
|
-
if (Array.isArray(storedEvents)) this.#mergeStoredEvents(storedEvents);
|
|
1511
1464
|
this.#hydrated = true;
|
|
1512
1465
|
this.#hydratePromise = null;
|
|
1513
1466
|
})();
|
|
1514
1467
|
return this.#hydratePromise;
|
|
1515
1468
|
}
|
|
1516
1469
|
|
|
1517
|
-
/**
|
|
1518
|
-
* Fold on-disk events into the in-memory ring by id, preserving live order
|
|
1519
|
-
* and bounding to MAX_DREAM_EVENTS. Unseen disk records are prepended (they
|
|
1520
|
-
* are older); a live append therefore never erases prior-run history. Sets
|
|
1521
|
-
* #eventsHydrated so this runs at most once.
|
|
1522
|
-
*/
|
|
1523
|
-
#mergeStoredEvents(stored) {
|
|
1524
|
-
if (this.#eventsHydrated) return;
|
|
1525
|
-
this.#eventsHydrated = true;
|
|
1526
|
-
if (!Array.isArray(stored) || stored.length === 0) return;
|
|
1527
|
-
const seen = new Set(this.#events.map(e => e?.id).filter(Boolean));
|
|
1528
|
-
const older = stored.filter(e => e && (!e.id || !seen.has(e.id)));
|
|
1529
|
-
if (older.length === 0) return;
|
|
1530
|
-
this.#events = [...older, ...this.#events].slice(-MAX_DREAM_EVENTS);
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
1470
|
#traceFile(trace) {
|
|
1534
1471
|
return tracePathFor(this.#rootDir, trace.sessionId || null, trace.requestKey);
|
|
1535
1472
|
}
|
|
@@ -1580,16 +1517,6 @@ export class DebugTrace {
|
|
|
1580
1517
|
if (typeof this.#appendTimer.unref === 'function') this.#appendTimer.unref();
|
|
1581
1518
|
}
|
|
1582
1519
|
|
|
1583
|
-
#scheduleEventFlush() {
|
|
1584
|
-
this.#eventsDirty = true;
|
|
1585
|
-
if (this.#eventFlushTimer) return;
|
|
1586
|
-
this.#eventFlushTimer = setTimeout(() => {
|
|
1587
|
-
this.#eventFlushTimer = null;
|
|
1588
|
-
this.#flushEvents();
|
|
1589
|
-
}, EVENT_FLUSH_INTERVAL_MS);
|
|
1590
|
-
if (typeof this.#eventFlushTimer.unref === 'function') this.#eventFlushTimer.unref();
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
1520
|
/** Queue append-only loop/tool records onto the single-writer chain. */
|
|
1594
1521
|
#flushPending() {
|
|
1595
1522
|
if (this.#appendTimer) {
|
|
@@ -1597,10 +1524,7 @@ export class DebugTrace {
|
|
|
1597
1524
|
this.#appendTimer = null;
|
|
1598
1525
|
}
|
|
1599
1526
|
const entries = this.#pendingWrites.splice(0);
|
|
1600
|
-
if (entries.length === 0)
|
|
1601
|
-
this.#flushEvents();
|
|
1602
|
-
return;
|
|
1603
|
-
}
|
|
1527
|
+
if (entries.length === 0) return;
|
|
1604
1528
|
this.#chain(async () => {
|
|
1605
1529
|
const batches = new Map();
|
|
1606
1530
|
for (const entry of entries) {
|
|
@@ -1697,7 +1621,6 @@ export class DebugTrace {
|
|
|
1697
1621
|
try { await this.#pruneAll(REQUEST_RETENTION); }
|
|
1698
1622
|
catch (err) { console.warn('[Yeaft] debug trace prune failed:', err?.message || err); }
|
|
1699
1623
|
});
|
|
1700
|
-
this.#flushEvents();
|
|
1701
1624
|
}
|
|
1702
1625
|
|
|
1703
1626
|
/**
|
|
@@ -1717,26 +1640,6 @@ export class DebugTrace {
|
|
|
1717
1640
|
return this.#flushChain;
|
|
1718
1641
|
}
|
|
1719
1642
|
|
|
1720
|
-
#flushEvents() {
|
|
1721
|
-
if (!this.#eventsDirty) return;
|
|
1722
|
-
this.#eventsDirty = false;
|
|
1723
|
-
if (this.#eventFlushTimer) {
|
|
1724
|
-
clearTimeout(this.#eventFlushTimer);
|
|
1725
|
-
this.#eventFlushTimer = null;
|
|
1726
|
-
}
|
|
1727
|
-
const file = join(this.#rootDir, 'events.json');
|
|
1728
|
-
this.#chain(async () => {
|
|
1729
|
-
// Fold in any prior-run events we haven't loaded yet BEFORE overwriting,
|
|
1730
|
-
// so a write that beats #ensureHydrated can't drop cross-restart history.
|
|
1731
|
-
if (!this.#eventsHydrated) {
|
|
1732
|
-
this.#mergeStoredEvents(await readJson(file));
|
|
1733
|
-
}
|
|
1734
|
-
const text = JSON.stringify(this.#events.slice(-MAX_DREAM_EVENTS));
|
|
1735
|
-
try { await atomicWriteText(file, text); }
|
|
1736
|
-
catch (err) { console.warn('[Yeaft] debug trace event write failed:', err?.message || err); }
|
|
1737
|
-
});
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
1643
|
/** Await every scheduled write (and prune) to settle. Used by close()/queries. */
|
|
1741
1644
|
async #drainWrites() {
|
|
1742
1645
|
this.#flushPending();
|
|
@@ -1757,32 +1660,6 @@ export class DebugTrace {
|
|
|
1757
1660
|
return snapshot;
|
|
1758
1661
|
}
|
|
1759
1662
|
|
|
1760
|
-
#readDreamEvents({ sessionId = null, dreamLimit = 5 } = {}) {
|
|
1761
|
-
const limit = Number.isFinite(Number(dreamLimit)) ? Math.max(0, Math.min(50, Number(dreamLimit))) : 5;
|
|
1762
|
-
if (limit <= 0) return [];
|
|
1763
|
-
// In-memory ring (hydrated once); no disk read on the query path.
|
|
1764
|
-
const events = this.#events;
|
|
1765
|
-
const out = [];
|
|
1766
|
-
for (const event of events.slice().reverse()) {
|
|
1767
|
-
const data = isPlainObject(event.eventData) ? event.eventData : {};
|
|
1768
|
-
if (sessionId) {
|
|
1769
|
-
const evtSessionId = typeof data.sessionId === 'string' && data.sessionId ? data.sessionId : null;
|
|
1770
|
-
const target = typeof data.target === 'string' ? data.target : '';
|
|
1771
|
-
const isBroadcast = !evtSessionId && !target;
|
|
1772
|
-
const isThisSession = evtSessionId === sessionId || target === `sessions/${sessionId}` || target === `group/${sessionId}`;
|
|
1773
|
-
if (!isBroadcast && !isThisSession) continue;
|
|
1774
|
-
}
|
|
1775
|
-
out.push({
|
|
1776
|
-
type: data.type || event.eventType || 'event',
|
|
1777
|
-
...data,
|
|
1778
|
-
at: event.createdAt,
|
|
1779
|
-
ts: data.ts || data.at || event.createdAt,
|
|
1780
|
-
});
|
|
1781
|
-
if (out.length >= limit) break;
|
|
1782
|
-
}
|
|
1783
|
-
return out.reverse();
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
1663
|
async #pruneAll(keep) {
|
|
1787
1664
|
const sessions = new Set();
|
|
1788
1665
|
for (const trace of this.#diskHeaders.values()) sessions.add(trace.sessionId || null);
|
package/yeaft/init.js
CHANGED
|
@@ -86,7 +86,6 @@ const SUBDIRS = [
|
|
|
86
86
|
'chat/cold',
|
|
87
87
|
'chat/blobs',
|
|
88
88
|
'sessions',
|
|
89
|
-
'memory/entries',
|
|
90
89
|
'tasks',
|
|
91
90
|
'skills',
|
|
92
91
|
];
|
|
@@ -115,19 +114,6 @@ const DEFAULT_CONFIG_JSON = `{
|
|
|
115
114
|
}
|
|
116
115
|
`;
|
|
117
116
|
|
|
118
|
-
/** Default MEMORY.md content. */
|
|
119
|
-
const DEFAULT_MEMORY = `# Yeaft Memory
|
|
120
|
-
|
|
121
|
-
This file stores persistent memory entries. The agent will read and update this file.
|
|
122
|
-
|
|
123
|
-
## Facts
|
|
124
|
-
|
|
125
|
-
## Preferences
|
|
126
|
-
|
|
127
|
-
## Project Context
|
|
128
|
-
|
|
129
|
-
`;
|
|
130
|
-
|
|
131
117
|
/** Default mcp.json example — generated as reference for MCP server configuration. */
|
|
132
118
|
const DEFAULT_MCP_EXAMPLE = `{
|
|
133
119
|
"_comment": "MCP server configuration. Rename to mcp.json to enable.",
|
|
@@ -160,9 +146,10 @@ This file tracks one Yeaft message-history mode.
|
|
|
160
146
|
* Initialize the Yeaft data directory structure.
|
|
161
147
|
*
|
|
162
148
|
* @param {string} [dir] — Root directory path. Defaults to ~/.yeaft/
|
|
149
|
+
* @param {{ migrateMemory?: boolean }} [options] — Archived Dream migration is explicit opt-in, never part of Agent/service/Session startup.
|
|
163
150
|
* @returns {{ dir: string, created: string[], writable: boolean, warnings: string[], seededSkills?: number }} — The root dir, list of created paths, writability status, any warnings, and how many bundled skills were seeded
|
|
164
151
|
*/
|
|
165
|
-
export function initYeaftDir(dir) {
|
|
152
|
+
export function initYeaftDir(dir, { migrateMemory = false } = {}) {
|
|
166
153
|
const root = dir || DEFAULT_YEAFT_DIR;
|
|
167
154
|
const created = [];
|
|
168
155
|
const warnings = [];
|
|
@@ -211,12 +198,6 @@ export function initYeaftDir(dir) {
|
|
|
211
198
|
}
|
|
212
199
|
}
|
|
213
200
|
|
|
214
|
-
const memoryPath = join(root, 'memory', 'MEMORY.md');
|
|
215
|
-
if (!existsSync(memoryPath)) {
|
|
216
|
-
safeWriteFile(memoryPath, DEFAULT_MEMORY, warnings);
|
|
217
|
-
created.push(memoryPath);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
201
|
const chatIndexPath = join(root, 'chat', 'index.md');
|
|
221
202
|
if (!existsSync(chatIndexPath)) {
|
|
222
203
|
safeWriteFile(chatIndexPath, DEFAULT_CONVERSATION_INDEX, warnings);
|
|
@@ -252,7 +233,7 @@ export function initYeaftDir(dir) {
|
|
|
252
233
|
// Idempotent (sentinel file) so re-running on a fully-migrated dir is a
|
|
253
234
|
// no-op; on a partial-crash dir, each step is independently resumable.
|
|
254
235
|
try {
|
|
255
|
-
const res = migrateSessions(root);
|
|
236
|
+
const res = migrateSessions(root, { migrateMemory });
|
|
256
237
|
if (res && res.migrated) {
|
|
257
238
|
console.log(`[yeaft] session migration complete (${res.moved} dirs moved, ${res.frontmatterRewrites} messages rewritten${res.warnings?.length ? `, ${res.warnings.length} warnings` : ''})`);
|
|
258
239
|
if (res.warnings?.length) for (const w of res.warnings) console.warn(`[yeaft] migration: ${w}`);
|
|
@@ -64,19 +64,21 @@ const SENTINEL_VERSION = 3;
|
|
|
64
64
|
* @param {string} yeaftDir
|
|
65
65
|
* @returns {{ migrated: boolean, moved: number, frontmatterRewrites: number, warnings: string[] }}
|
|
66
66
|
*/
|
|
67
|
-
export function migrateSessions(yeaftDir) {
|
|
67
|
+
export function migrateSessions(yeaftDir, { migrateMemory = true } = {}) {
|
|
68
68
|
const warnings = [];
|
|
69
69
|
if (!yeaftDir || !existsSync(yeaftDir)) {
|
|
70
70
|
return { migrated: false, moved: 0, frontmatterRewrites: 0, warnings: ['yeaftDir missing'] };
|
|
71
71
|
}
|
|
72
72
|
const sentinel = join(yeaftDir, SENTINEL);
|
|
73
73
|
if (existsSync(sentinel)) {
|
|
74
|
-
// Version-aware: ignore a sentinel from an older schema.
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
let
|
|
78
|
-
try {
|
|
79
|
-
|
|
74
|
+
// Version-aware: ignore a sentinel from an older schema. A runtime-only
|
|
75
|
+
// migration deliberately leaves memoryMigrated=false so an explicitly
|
|
76
|
+
// invoked full migration can still process the archived Dream data later.
|
|
77
|
+
let state = null;
|
|
78
|
+
try { state = JSON.parse(readFileSync(sentinel, 'utf8')); } catch { /* corrupt sentinel — treat as incomplete */ }
|
|
79
|
+
const versionComplete = (state?.version ?? 0) >= SENTINEL_VERSION;
|
|
80
|
+
const memoryComplete = state?.memoryMigrated !== false;
|
|
81
|
+
if (versionComplete && (!migrateMemory || memoryComplete)) {
|
|
80
82
|
return { migrated: false, moved: 0, frontmatterRewrites: 0, warnings: [] };
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -96,8 +98,8 @@ export function migrateSessions(yeaftDir) {
|
|
|
96
98
|
const memSessionsAmsRoot = join(memoryRoot, 'sessions');
|
|
97
99
|
|
|
98
100
|
if (!existsSync(sessionsRoot)) mkdirSync(sessionsRoot, { recursive: true });
|
|
99
|
-
if (!existsSync(memSessionRoot)) mkdirSync(memSessionRoot, { recursive: true });
|
|
100
|
-
if (!existsSync(memSessionsAmsRoot)) mkdirSync(memSessionsAmsRoot, { recursive: true });
|
|
101
|
+
if (migrateMemory && !existsSync(memSessionRoot)) mkdirSync(memSessionRoot, { recursive: true });
|
|
102
|
+
if (migrateMemory && !existsSync(memSessionsAmsRoot)) mkdirSync(memSessionsAmsRoot, { recursive: true });
|
|
101
103
|
|
|
102
104
|
// ID collision check
|
|
103
105
|
const groupIds = listDirs(groupsRoot);
|
|
@@ -165,52 +167,52 @@ export function migrateSessions(yeaftDir) {
|
|
|
165
167
|
moved++;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
170
|
+
if (migrateMemory) {
|
|
171
|
+
// 3+4. memory/{group,chat}/<id>/ → memory/session/<id>/
|
|
172
|
+
for (const family of ['group', 'chat']) {
|
|
173
|
+
const root = join(memoryRoot, family);
|
|
174
|
+
if (!existsSync(root)) continue;
|
|
175
|
+
for (const id of listDirs(root)) {
|
|
176
|
+
const src = join(root, id);
|
|
177
|
+
const dst = join(memSessionRoot, id);
|
|
178
|
+
if (existsSync(dst)) {
|
|
179
|
+
warnings.push(`memory/session/${id} already exists; skipping memory/${family}/${id}`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
renameSync(src, dst);
|
|
183
|
+
rewriteSegmentScopes(dst, family, id, warnings);
|
|
178
184
|
}
|
|
179
|
-
renameSync(src, dst);
|
|
180
|
-
rewriteSegmentScopes(dst, family, id, warnings);
|
|
181
185
|
}
|
|
182
|
-
}
|
|
183
186
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
// 5. memory/groups/<g>/ams.json → memory/sessions/<g>/ams.json
|
|
188
|
+
const amsGroupRoot = join(memoryRoot, 'groups');
|
|
189
|
+
if (existsSync(amsGroupRoot)) {
|
|
190
|
+
for (const id of listDirs(amsGroupRoot)) {
|
|
191
|
+
const srcDir = join(amsGroupRoot, id);
|
|
192
|
+
const dstDir = join(memSessionsAmsRoot, id);
|
|
193
|
+
if (existsSync(dstDir)) {
|
|
194
|
+
warnings.push(`memory/sessions/${id} already exists; skipping memory/groups/${id}`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
renameSync(srcDir, dstDir);
|
|
193
198
|
}
|
|
194
|
-
renameSync(srcDir, dstDir);
|
|
195
199
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
200
|
+
const amsChatRoot = join(memoryRoot, 'chats');
|
|
201
|
+
if (existsSync(amsChatRoot)) {
|
|
202
|
+
for (const id of listDirs(amsChatRoot)) {
|
|
203
|
+
const srcDir = join(amsChatRoot, id);
|
|
204
|
+
const dstDir = join(memSessionsAmsRoot, id);
|
|
205
|
+
if (existsSync(dstDir)) {
|
|
206
|
+
warnings.push(`memory/sessions/${id} already exists; skipping memory/chats/${id}`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
renameSync(srcDir, dstDir);
|
|
205
210
|
}
|
|
206
|
-
renameSync(srcDir, dstDir);
|
|
207
211
|
}
|
|
208
|
-
}
|
|
209
212
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
rewriteFtsScopes(memoryRoot, warnings);
|
|
213
|
+
// 6. Rewrite SQLite FTS index scope strings via the shared index-db module.
|
|
214
|
+
rewriteFtsScopes(memoryRoot, warnings);
|
|
215
|
+
}
|
|
214
216
|
|
|
215
217
|
// 7. Per-message frontmatter rewrite. Walks both the legacy flat
|
|
216
218
|
// conversation directory and every per-session conversation directory
|
|
@@ -224,8 +226,11 @@ export function migrateSessions(yeaftDir) {
|
|
|
224
226
|
moved += cleanup.moved;
|
|
225
227
|
|
|
226
228
|
// 9. Sentinel — version 3 = consolidated migration plus legacy cleanup.
|
|
229
|
+
// Record whether the optional archived-memory steps ran; runtime startup
|
|
230
|
+
// skips them, while explicit migration callers retain the ability to run them.
|
|
227
231
|
writeFileSync(sentinel, JSON.stringify({
|
|
228
232
|
version: SENTINEL_VERSION,
|
|
233
|
+
memoryMigrated: migrateMemory,
|
|
229
234
|
migratedAt: new Date().toISOString(),
|
|
230
235
|
moved,
|
|
231
236
|
frontmatterRewrites,
|