@yeaft/webchat-agent 1.0.91 → 1.0.95
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/package.json
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
|
|
12
12
|
import { readMemory, readSummary } from '../memory/store.js';
|
|
13
|
-
import { readSessionState } from './state.js';
|
|
13
|
+
import { readDreamError, readSessionState } from './state.js';
|
|
14
|
+
import { buildRunDreamOpts } from './session-wiring.js';
|
|
14
15
|
|
|
15
16
|
export const DREAM_SNAPSHOT_TEXT_LIMIT = 6000;
|
|
16
17
|
|
|
@@ -32,7 +33,7 @@ export async function buildDreamOutputSnapshot(sessionLike, sessionId) {
|
|
|
32
33
|
const scope = `sessions/${sessionId}`;
|
|
33
34
|
const memoryScope = { kind: 'session', id: sessionId };
|
|
34
35
|
const root = join(sessionLike.yeaftDir, 'memory');
|
|
35
|
-
const [memoryRaw, summaryRaw, state] = await Promise.all([
|
|
36
|
+
const [memoryRaw, summaryRaw, state, lastError] = await Promise.all([
|
|
36
37
|
readMemory(memoryScope, { root }).catch(() => ''),
|
|
37
38
|
readSummary(memoryScope, { root }).catch(() => ''),
|
|
38
39
|
readSessionState(root, sessionId).catch(() => ({
|
|
@@ -40,7 +41,9 @@ export async function buildDreamOutputSnapshot(sessionLike, sessionId) {
|
|
|
40
41
|
lastDreamAt: null,
|
|
41
42
|
messageCount: 0,
|
|
42
43
|
})),
|
|
44
|
+
readDreamError(root, scope).catch(() => null),
|
|
43
45
|
]);
|
|
46
|
+
const totalMessageCount = await countSessionMessages(sessionLike, sessionId);
|
|
44
47
|
const memory = truncateDreamText(memoryRaw);
|
|
45
48
|
const summary = truncateDreamText(summaryRaw);
|
|
46
49
|
return {
|
|
@@ -50,10 +53,26 @@ export async function buildDreamOutputSnapshot(sessionLike, sessionId) {
|
|
|
50
53
|
lastDreamAt: state?.lastDreamAt || null,
|
|
51
54
|
lastDreamMessageId: state?.lastDreamMessageId || null,
|
|
52
55
|
messageCount: Number.isFinite(state?.messageCount) ? state.messageCount : 0,
|
|
56
|
+
totalMessageCount,
|
|
53
57
|
hasOutput: !!(memoryRaw || summaryRaw),
|
|
58
|
+
lastError,
|
|
54
59
|
memoryText: memory.text,
|
|
55
60
|
memoryTruncated: memory.truncated,
|
|
56
61
|
summaryText: summary.text,
|
|
57
62
|
summaryTruncated: summary.truncated,
|
|
58
63
|
};
|
|
59
64
|
}
|
|
65
|
+
|
|
66
|
+
async function countSessionMessages(sessionLike, sessionId) {
|
|
67
|
+
try {
|
|
68
|
+
const opts = buildRunDreamOpts({
|
|
69
|
+
yeaftDir: sessionLike.yeaftDir,
|
|
70
|
+
config: sessionLike.config || {},
|
|
71
|
+
adapter: { call: async () => ({ text: '', usage: {} }) },
|
|
72
|
+
});
|
|
73
|
+
const n = await opts.countMessages(sessionId);
|
|
74
|
+
return Number.isFinite(n) ? n : 0;
|
|
75
|
+
} catch {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/yeaft/dream/runner.js
CHANGED
|
@@ -45,7 +45,7 @@ import { listScopes, readSummary } from '../memory/store.js';
|
|
|
45
45
|
import {
|
|
46
46
|
DEFAULT_LIMITS,
|
|
47
47
|
} from './limits.js';
|
|
48
|
-
import { readSessionState, writeSessionState, writeDreamError } from './state.js';
|
|
48
|
+
import { clearDreamError, readSessionState, writeSessionState, writeDreamError } from './state.js';
|
|
49
49
|
import { segmentDiff, truncateMessage, estimateMessagesTokens } from './segment.js';
|
|
50
50
|
import { triageGroupSegments } from './triage.js';
|
|
51
51
|
import { mergeByTarget } from './merge.js';
|
|
@@ -166,7 +166,7 @@ export async function runDream(opts) {
|
|
|
166
166
|
sessionId,
|
|
167
167
|
segments,
|
|
168
168
|
topicSummaries,
|
|
169
|
-
llm: opts.llm,
|
|
169
|
+
llm: dreamLlmForSession(opts.llm, sessionId),
|
|
170
170
|
onProgress,
|
|
171
171
|
language: opts.language,
|
|
172
172
|
});
|
|
@@ -208,13 +208,14 @@ export async function runDream(opts) {
|
|
|
208
208
|
const r = await applyMergedTarget(merged, {
|
|
209
209
|
root: opts.root,
|
|
210
210
|
ts,
|
|
211
|
-
llm: opts.llm,
|
|
211
|
+
llm: dreamLlmForSession(opts.llm, sourceSessionId(merged)),
|
|
212
212
|
limits,
|
|
213
213
|
nowIso: opts.nowIso || (() => nowIso),
|
|
214
214
|
onProgress,
|
|
215
215
|
siblingTopicsFor: opts.siblingTopicsFor,
|
|
216
216
|
language: opts.language,
|
|
217
217
|
});
|
|
218
|
+
await clearDreamError(opts.root, merged.target);
|
|
218
219
|
targetsReport.push({ ...r, sources: merged.sources.length, status: 'done' });
|
|
219
220
|
} catch (err) {
|
|
220
221
|
targetsReport.push({
|
|
@@ -252,11 +253,12 @@ export async function runDream(opts) {
|
|
|
252
253
|
sessionId: triage.sessionId,
|
|
253
254
|
messages: triage.diff,
|
|
254
255
|
targets,
|
|
255
|
-
llm: opts.llm,
|
|
256
|
+
llm: dreamLlmForSession(opts.llm, triage.sessionId),
|
|
256
257
|
language: opts.language,
|
|
257
258
|
nowIso: opts.nowIso || (() => nowIso),
|
|
258
259
|
segmentIndex: opts.segmentIndex || null,
|
|
259
260
|
});
|
|
261
|
+
await clearDreamError(opts.root, `sessions/${triage.sessionId}`);
|
|
260
262
|
segmentReports.push({ sessionId: triage.sessionId, status: 'done', ...r });
|
|
261
263
|
onProgress({ phase: 'extract-segments', sessionId: triage.sessionId, status: 'done', ...r });
|
|
262
264
|
} catch (err) {
|
|
@@ -335,6 +337,10 @@ function deriveSessionFilter(filter) {
|
|
|
335
337
|
return sessions.length > 0 ? new Set(sessions) : null;
|
|
336
338
|
}
|
|
337
339
|
|
|
340
|
+
function dreamLlmForSession(llm, sessionId) {
|
|
341
|
+
return (req = {}) => llm({ ...req, sessionId: req.sessionId || sessionId || null });
|
|
342
|
+
}
|
|
343
|
+
|
|
338
344
|
function sourceSessionId(mergedTarget) {
|
|
339
345
|
const src = Array.isArray(mergedTarget?.sources) ? mergedTarget.sources[0] : null;
|
|
340
346
|
return src && typeof src.sessionId === 'string' ? src.sessionId : '';
|
|
@@ -168,9 +168,7 @@ function normalizeExtractedSegment({ item, scope, now }) {
|
|
|
168
168
|
if (!body) return null;
|
|
169
169
|
const kind = VALID_KINDS.has(String(item.kind || '')) ? String(item.kind) : 'context';
|
|
170
170
|
const tags = Array.isArray(item.tags) ? item.tags.map(t => String(t).trim()).filter(Boolean) : [];
|
|
171
|
-
const sourceMessages =
|
|
172
|
-
? item.sourceMessages.map(id => String(id).trim()).filter(Boolean)
|
|
173
|
-
: [];
|
|
171
|
+
const sourceMessages = normalizeSourceMessageIds(item.sourceMessages);
|
|
174
172
|
return makeSegment({
|
|
175
173
|
scope,
|
|
176
174
|
kind,
|
|
@@ -182,6 +180,23 @@ function normalizeExtractedSegment({ item, scope, now }) {
|
|
|
182
180
|
});
|
|
183
181
|
}
|
|
184
182
|
|
|
183
|
+
function normalizeSourceMessageIds(value) {
|
|
184
|
+
if (!Array.isArray(value)) return [];
|
|
185
|
+
const ids = [];
|
|
186
|
+
for (const item of value) {
|
|
187
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
188
|
+
const id = String(item).trim();
|
|
189
|
+
if (id) ids.push(id);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (item && typeof item === 'object') {
|
|
193
|
+
const id = String(item.id || item.messageId || item.uuid || '').trim();
|
|
194
|
+
if (id) ids.push(id);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return ids;
|
|
198
|
+
}
|
|
199
|
+
|
|
185
200
|
function buildRecentSegment({ scope, messages, now }) {
|
|
186
201
|
const recentMessages = messages.slice(-RECENT_MESSAGE_COUNT);
|
|
187
202
|
const body = [
|
|
@@ -43,6 +43,8 @@ import { join } from 'path';
|
|
|
43
43
|
import { runDream } from './runner.js';
|
|
44
44
|
import { createDreamScheduler } from './schedule.js';
|
|
45
45
|
import { parseMessage, parseSeqFromId } from '../conversation/persist.js';
|
|
46
|
+
import { loadSessionConfig, resolveSessionConfig } from '../sessions/session-config.js';
|
|
47
|
+
import { listSessions as listSessionMetas } from '../sessions/session-store.js';
|
|
46
48
|
import { readSessionState } from './state.js';
|
|
47
49
|
import { DREAM_NUDGE_AFTER_MESSAGES, DREAM_INTERVAL_HOURS } from './limits.js';
|
|
48
50
|
|
|
@@ -83,7 +85,7 @@ export function buildRunDreamOpts(session, onProgress) {
|
|
|
83
85
|
segmentIndex: session.memoryIndex || null,
|
|
84
86
|
llm: makeLlm(session),
|
|
85
87
|
listSessions: async () => {
|
|
86
|
-
try { return
|
|
88
|
+
try { return listRegisteredSessions([sessionConversationsRoot, legacySessionConversationsRoot]); }
|
|
87
89
|
catch { return []; }
|
|
88
90
|
},
|
|
89
91
|
countMessages: async (sessionId) => {
|
|
@@ -132,15 +134,25 @@ function translateSessionConversationMessage(m) {
|
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
/**
|
|
135
|
-
* Enumerate
|
|
136
|
-
*
|
|
137
|
+
* Enumerate sessions from authoritative session metadata first, then fall back
|
|
138
|
+
* to legacy transcript-only directories. Message presence is not used as the
|
|
139
|
+
* source of truth: a stale/corrupt JSONL index must not make Dream forget that
|
|
140
|
+
* a Session exists. The runner's .dream-state cursor decides whether there is
|
|
141
|
+
* anything new to process.
|
|
137
142
|
*
|
|
138
143
|
* @param {string[]} roots session transcript roots in priority order
|
|
139
144
|
* @returns {string[]}
|
|
140
145
|
*/
|
|
141
|
-
function
|
|
146
|
+
function listRegisteredSessions(roots) {
|
|
142
147
|
const out = new Set();
|
|
143
|
-
|
|
148
|
+
const [sessionsRoot, ...legacyRoots] = roots;
|
|
149
|
+
try {
|
|
150
|
+
for (const meta of listSessionMetas(sessionsRoot)) {
|
|
151
|
+
if (meta?.id) out.add(meta.id);
|
|
152
|
+
}
|
|
153
|
+
} catch { /* fall through to legacy fallback */ }
|
|
154
|
+
|
|
155
|
+
for (const root of legacyRoots) {
|
|
144
156
|
if (!existsSync(root)) continue;
|
|
145
157
|
for (const name of readdirSync(root)) {
|
|
146
158
|
if (name.startsWith('.')) continue;
|
|
@@ -149,8 +161,8 @@ function listConversationSessions(roots) {
|
|
|
149
161
|
if (!hasReadableMessages(dir)) continue;
|
|
150
162
|
out.add(name);
|
|
151
163
|
} catch {
|
|
152
|
-
// Ignore partial
|
|
153
|
-
//
|
|
164
|
+
// Ignore partial old directories. Canonical sessions come from
|
|
165
|
+
// sessions/<id>/session.json above; this branch is read-only legacy.
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
}
|
|
@@ -210,14 +222,13 @@ function hasJsonlMessages(dir) {
|
|
|
210
222
|
|
|
211
223
|
function loadSessionDiskMessages(dir, sessionId) {
|
|
212
224
|
const conversationDir = join(dir, 'conversation');
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
];
|
|
225
|
+
// Canonical conversation history is the source Dream needs: it contains the
|
|
226
|
+
// full user/assistant timeline. The session audit log under messages/ is a
|
|
227
|
+
// fallback for older or partially migrated sessions, not a second source to
|
|
228
|
+
// union in — otherwise the same user turn can appear twice with different ids.
|
|
229
|
+
const canonical = loadConversationDirMessages(conversationDir);
|
|
230
|
+
if (canonical.length > 0) return canonical;
|
|
231
|
+
return loadSessionJsonlMessages(join(dir, 'messages'), sessionId);
|
|
221
232
|
}
|
|
222
233
|
|
|
223
234
|
function loadConversationDirMessages(conversationDir) {
|
|
@@ -302,6 +313,24 @@ function normalizeSessionJsonlMessage(row, sessionId) {
|
|
|
302
313
|
function stringifyMessageBody(value) {
|
|
303
314
|
if (typeof value === 'string') return value;
|
|
304
315
|
if (value == null) return '';
|
|
316
|
+
if (Array.isArray(value)) return value.map(stringifyMessageBody).filter(Boolean).join('\n');
|
|
317
|
+
if (typeof value === 'object') {
|
|
318
|
+
if (typeof value.text === 'string') return value.text;
|
|
319
|
+
if (typeof value.content === 'string') return value.content;
|
|
320
|
+
if (typeof value.body === 'string') return value.body;
|
|
321
|
+
if (typeof value.value === 'string') return value.value;
|
|
322
|
+
if (Array.isArray(value.content)) return stringifyMessageBody(value.content);
|
|
323
|
+
if (Array.isArray(value.parts)) return stringifyMessageBody(value.parts);
|
|
324
|
+
if (Array.isArray(value.blocks)) return stringifyMessageBody(value.blocks);
|
|
325
|
+
if (value.type && (value.type === 'image' || value.type === 'image_url')) return '[image]';
|
|
326
|
+
if (value.type && (value.type === 'tool_use' || value.type === 'tool_call')) {
|
|
327
|
+
const name = value.name || value.toolName || value.function?.name || 'tool';
|
|
328
|
+
return `[tool call: ${name}]`;
|
|
329
|
+
}
|
|
330
|
+
if (value.type && (value.type === 'tool_result' || value.type === 'function_result')) {
|
|
331
|
+
return stringifyMessageBody(value.result ?? value.output ?? value.content ?? '[tool result]');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
305
334
|
try { return JSON.stringify(value); } catch { return String(value); }
|
|
306
335
|
}
|
|
307
336
|
|
|
@@ -323,9 +352,13 @@ function safeDirComponent(s) {
|
|
|
323
352
|
* @param {Object} session
|
|
324
353
|
*/
|
|
325
354
|
function makeLlm(session) {
|
|
326
|
-
return async ({ pass, prompt, system }) => {
|
|
355
|
+
return async ({ pass, prompt, system, sessionId }) => {
|
|
327
356
|
const adapter = session.adapter;
|
|
328
|
-
const
|
|
357
|
+
const effectiveConfig = resolveDreamSessionConfig(session, sessionId);
|
|
358
|
+
const model = effectiveConfig?.model || effectiveConfig?.primaryModel;
|
|
359
|
+
if (!model) {
|
|
360
|
+
throw new Error(`dream: no session model configured (pass=${pass}, sessionId=${sessionId || 'unknown'})`);
|
|
361
|
+
}
|
|
329
362
|
if (!adapter || typeof adapter.call !== 'function') {
|
|
330
363
|
throw new Error(`dream: no adapter.call available (pass=${pass})`);
|
|
331
364
|
}
|
|
@@ -335,7 +368,7 @@ function makeLlm(session) {
|
|
|
335
368
|
session._dreamLoopCounter = (session._dreamLoopCounter || 0) + 1;
|
|
336
369
|
const loopNumber = session._dreamLoopCounter;
|
|
337
370
|
const turnId = session._dreamTurnId || 'dream';
|
|
338
|
-
const effectiveSystem = system || (String(
|
|
371
|
+
const effectiveSystem = system || (String(effectiveConfig?.language || '').toLowerCase().startsWith('zh')
|
|
339
372
|
? `你是梦境流水线 — pass: ${pass}。请用中文生成自然语言内容;JSON key 保持英文。`
|
|
340
373
|
: `You are the dream pipeline — pass: ${pass}.`);
|
|
341
374
|
|
|
@@ -344,6 +377,7 @@ function makeLlm(session) {
|
|
|
344
377
|
system: effectiveSystem,
|
|
345
378
|
messages: [{ role: 'user', content: prompt }],
|
|
346
379
|
maxTokens: 2048,
|
|
380
|
+
modelEffort: effectiveConfig?.modelEffort || undefined,
|
|
347
381
|
});
|
|
348
382
|
|
|
349
383
|
// Emit complete loop event (request + response) to the debug panel.
|
|
@@ -366,6 +400,7 @@ function makeLlm(session) {
|
|
|
366
400
|
loopNumber,
|
|
367
401
|
pass,
|
|
368
402
|
model: model || 'unknown',
|
|
403
|
+
sessionId: sessionId || null,
|
|
369
404
|
systemPrompt: effectiveSystem,
|
|
370
405
|
messages: [{ role: 'user', content: prompt }],
|
|
371
406
|
response: typeof r?.text === 'string' ? r.text : '',
|
|
@@ -387,6 +422,15 @@ function makeLlm(session) {
|
|
|
387
422
|
}
|
|
388
423
|
|
|
389
424
|
|
|
425
|
+
function resolveDreamSessionConfig(session, sessionId) {
|
|
426
|
+
const base = session?.config || {};
|
|
427
|
+
if (!sessionId || !session?.yeaftDir) return { ...base };
|
|
428
|
+
const sessionConfig = loadSessionConfig(session.yeaftDir, sessionId);
|
|
429
|
+
if (!sessionConfig || Object.keys(sessionConfig).length === 0) return { ...base };
|
|
430
|
+
return resolveSessionConfig(base, sessionConfig);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
390
434
|
function stampDreamScope(session, evt) {
|
|
391
435
|
if (!evt || typeof evt !== 'object') return evt;
|
|
392
436
|
if (evt.sessionId || !session?._dreamActiveGroupId) return evt;
|
|
@@ -646,7 +690,7 @@ export async function bootInitEmptyGroups(args) {
|
|
|
646
690
|
if (!args || !args.memoryIndex || !args.dreamScheduler) return out;
|
|
647
691
|
const sessionsRoot = join(args.yeaftDir, 'sessions');
|
|
648
692
|
let ids;
|
|
649
|
-
try { ids =
|
|
693
|
+
try { ids = listSessionMetas(sessionsRoot).map(g => g.id); }
|
|
650
694
|
catch { return out; }
|
|
651
695
|
const empty = [];
|
|
652
696
|
for (const gid of ids) {
|
|
@@ -656,11 +700,7 @@ export async function bootInitEmptyGroups(args) {
|
|
|
656
700
|
if (segCount > 0) continue;
|
|
657
701
|
let hasMessages = false;
|
|
658
702
|
try {
|
|
659
|
-
|
|
660
|
-
// Any message at all is enough — pull the first record off the
|
|
661
|
-
// iterator and stop.
|
|
662
|
-
const first = h.streamMessages().next();
|
|
663
|
-
hasMessages = !first.done;
|
|
703
|
+
hasMessages = loadSessionConversationMessages([sessionsRoot], gid).length > 0;
|
|
664
704
|
} catch { continue; }
|
|
665
705
|
if (!hasMessages) continue;
|
|
666
706
|
empty.push(`sessions/${gid}`);
|
|
@@ -716,7 +756,7 @@ export async function bootCatchUpStaleDream(args) {
|
|
|
716
756
|
const now = args.now ?? Date.now();
|
|
717
757
|
|
|
718
758
|
let sessionIds;
|
|
719
|
-
try { sessionIds =
|
|
759
|
+
try { sessionIds = listSessionMetas(sessionsRoot).map(g => g.id); }
|
|
720
760
|
catch { return out; }
|
|
721
761
|
|
|
722
762
|
// Find the newest lastDreamAt across all groups.
|
|
@@ -732,9 +772,7 @@ export async function bootCatchUpStaleDream(args) {
|
|
|
732
772
|
}
|
|
733
773
|
if (!anyTraffic) {
|
|
734
774
|
try {
|
|
735
|
-
|
|
736
|
-
const first = h.streamMessages().next();
|
|
737
|
-
if (!first.done) anyTraffic = true;
|
|
775
|
+
if (loadSessionConversationMessages([sessionsRoot], gid).length > 0) anyTraffic = true;
|
|
738
776
|
} catch { /* keep going */ }
|
|
739
777
|
}
|
|
740
778
|
}
|
package/yeaft/dream/state.js
CHANGED
|
@@ -187,6 +187,19 @@ export async function writeDreamError(root, scope, info) {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Clear a scope's last Dream error after a successful pass. Best-effort: a
|
|
192
|
+
* missing file is fine and I/O failure must not turn success into failure.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} root
|
|
195
|
+
* @param {string} scope
|
|
196
|
+
* @returns {Promise<void>}
|
|
197
|
+
*/
|
|
198
|
+
export async function clearDreamError(root, scope) {
|
|
199
|
+
try { await fsp.unlink(join(scopeDirFor(root, scope), ERROR_FILE)); }
|
|
200
|
+
catch { /* best-effort */ }
|
|
201
|
+
}
|
|
202
|
+
|
|
190
203
|
/**
|
|
191
204
|
* Read the last dream error JSON for a scope, or null if absent. Used
|
|
192
205
|
* by the debug panel and by tests. Tolerates a malformed file by
|
package/yeaft/memory/segment.js
CHANGED
|
@@ -93,9 +93,7 @@ export function makeSegment(raw) {
|
|
|
93
93
|
const tags = Array.isArray(raw.tags)
|
|
94
94
|
? raw.tags.map(t => String(t).trim()).filter(Boolean)
|
|
95
95
|
: [];
|
|
96
|
-
const sourceMessages =
|
|
97
|
-
? raw.sourceMessages.map(t => String(t).trim()).filter(Boolean)
|
|
98
|
-
: [];
|
|
96
|
+
const sourceMessages = normalizeSourceMessages(raw.sourceMessages);
|
|
99
97
|
|
|
100
98
|
const now = new Date().toISOString();
|
|
101
99
|
const createdAt = raw.createdAt || now;
|
|
@@ -105,6 +103,23 @@ export function makeSegment(raw) {
|
|
|
105
103
|
return { id, scope, kind, tags, sourceMessages, createdAt, updatedAt, body };
|
|
106
104
|
}
|
|
107
105
|
|
|
106
|
+
function normalizeSourceMessages(value) {
|
|
107
|
+
if (!Array.isArray(value)) return [];
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const item of value) {
|
|
110
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
111
|
+
const id = String(item).trim();
|
|
112
|
+
if (id && id !== '[object Object]') out.push(id);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (item && typeof item === 'object') {
|
|
116
|
+
const id = String(item.id || item.messageId || item.uuid || '').trim();
|
|
117
|
+
if (id) out.push(id);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
/**
|
|
109
124
|
* Parse a memory.md text into segments. Tolerant by design:
|
|
110
125
|
* - Empty / whitespace input → [].
|