@yeaft/webchat-agent 1.0.450 → 1.0.454
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/version.json +1 -1
- package/local-runtime/web/app.bundle.js +103 -91
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +5 -5
- package/yeaft/debug-trace.js +23 -5
- package/yeaft/dream/limits.js +8 -0
- package/yeaft/dream/runner.js +72 -11
- package/yeaft/dream/segment-extract.js +12 -2
- package/yeaft/dream/session-wiring.js +43 -7
- package/yeaft/dream/state.js +30 -9
- package/yeaft/engine.js +23 -6
- package/yeaft/llm/adapter.js +1 -1
- package/yeaft/llm/anthropic.js +1 -0
- package/yeaft/llm/openai-responses.js +1 -0
- package/yeaft/llm/router.js +1 -1
- package/yeaft/web-bridge.js +2 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.454",
|
|
4
4
|
"description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"yeaft-agent": "
|
|
9
|
-
"yeaft-agent-container": "
|
|
10
|
-
"yeaft": "
|
|
11
|
-
"yeaft-stats": "
|
|
8
|
+
"yeaft-agent": "cli.js",
|
|
9
|
+
"yeaft-agent-container": "container-cli.js",
|
|
10
|
+
"yeaft": "yeaft/cli.js",
|
|
11
|
+
"yeaft-stats": "bin/yeaft-stats.js"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"start": "node index.js",
|
package/yeaft/debug-trace.js
CHANGED
|
@@ -754,8 +754,13 @@ function summarizeTrace(trace, detailsLoaded = false) {
|
|
|
754
754
|
summaryInputTokens: loops.length > 0 ? usage.summaryInputTokens : Number(trace?.summaryInputTokens || 0),
|
|
755
755
|
summaryOutputTokens: loops.length > 0 ? usage.summaryOutputTokens : Number(trace?.summaryOutputTokens || 0),
|
|
756
756
|
loopCount: loops.length > 0 ? loops.length : Number(trace?.loopCount || 0),
|
|
757
|
-
memoryLoaded: null,
|
|
758
|
-
|
|
757
|
+
memoryLoaded: Array.isArray(trace?.memoryLoaded) ? cloneJsonValue(trace.memoryLoaded) : null,
|
|
758
|
+
memoryLoadedMeta: trace?.memoryLoadedMeta && typeof trace.memoryLoadedMeta === 'object'
|
|
759
|
+
? cloneJsonValue(trace.memoryLoadedMeta)
|
|
760
|
+
: null,
|
|
761
|
+
memoryAdjust: trace?.memoryAdjust && typeof trace.memoryAdjust === 'object'
|
|
762
|
+
? cloneJsonValue(trace.memoryAdjust)
|
|
763
|
+
: null,
|
|
759
764
|
tools: Array.isArray(trace?.tools) ? trace.tools.map(t => ({
|
|
760
765
|
loopNumber: t.loopNumber || 0,
|
|
761
766
|
callId: t.toolCallId || t.id || null,
|
|
@@ -910,7 +915,7 @@ export function projectDebugDetailForWire(detail, maxBytes = DEBUG_DETAIL_WIRE_M
|
|
|
910
915
|
}
|
|
911
916
|
for (const [turnIndex, turn] of (Array.isArray(wire.turns) ? wire.turns : []).entries()) {
|
|
912
917
|
if (!turn || typeof turn !== 'object') continue;
|
|
913
|
-
for (const field of ['userPrompt', 'memoryLoaded', 'memoryAdjust']) {
|
|
918
|
+
for (const field of ['userPrompt', 'memoryLoaded', 'memoryLoadedMeta', 'memoryAdjust']) {
|
|
914
919
|
addCandidate(turn, field, `turns[${turnIndex}].${field}`);
|
|
915
920
|
}
|
|
916
921
|
for (const [toolIndex, tool] of (Array.isArray(turn.tools) ? turn.tools : []).entries()) {
|
|
@@ -1212,7 +1217,7 @@ export class DebugTrace {
|
|
|
1212
1217
|
this.#textMaxBytes = normalizeTextMaxBytes(nextValue, this.#textMaxBytes);
|
|
1213
1218
|
}
|
|
1214
1219
|
|
|
1215
|
-
startTurn({ traceId, messageId = null, mode = null, turnNumber = null, sessionId = null, vpId = null, threadId = null, userPrompt = null } = {}) {
|
|
1220
|
+
startTurn({ traceId, messageId = null, mode = null, turnNumber = null, sessionId = null, vpId = null, threadId = null, userPrompt = null, memoryLoaded = null, memoryLoadedMeta = null } = {}) {
|
|
1216
1221
|
if (!this.#acceptingWrites) return 'null';
|
|
1217
1222
|
const turnRowId = randomUUID();
|
|
1218
1223
|
const now = Date.now();
|
|
@@ -1225,6 +1230,8 @@ export class DebugTrace {
|
|
|
1225
1230
|
vpId,
|
|
1226
1231
|
threadId,
|
|
1227
1232
|
userPrompt,
|
|
1233
|
+
memoryLoaded,
|
|
1234
|
+
memoryLoadedMeta,
|
|
1228
1235
|
now,
|
|
1229
1236
|
turnRowId,
|
|
1230
1237
|
});
|
|
@@ -1584,7 +1591,7 @@ export class DebugTrace {
|
|
|
1584
1591
|
await this.#drainWrites();
|
|
1585
1592
|
}
|
|
1586
1593
|
|
|
1587
|
-
#getOrCreateRequest({ traceId, turnNumber, messageId, mode, sessionId, vpId, threadId, userPrompt, now, turnRowId }) {
|
|
1594
|
+
#getOrCreateRequest({ traceId, turnNumber, messageId, mode, sessionId, vpId, threadId, userPrompt, memoryLoaded, memoryLoadedMeta, now, turnRowId }) {
|
|
1588
1595
|
const normalizedSessionId = sessionId || null;
|
|
1589
1596
|
const isUsableExisting = (t) => (
|
|
1590
1597
|
t?.sessionId === normalizedSessionId
|
|
@@ -1604,6 +1611,12 @@ export class DebugTrace {
|
|
|
1604
1611
|
.at(-1) || null;
|
|
1605
1612
|
if (existing) {
|
|
1606
1613
|
existing.updatedAt = now;
|
|
1614
|
+
if (!Array.isArray(existing.memoryLoaded) && Array.isArray(memoryLoaded)) {
|
|
1615
|
+
existing.memoryLoaded = cloneJsonValue(memoryLoaded);
|
|
1616
|
+
}
|
|
1617
|
+
if (!existing.memoryLoadedMeta && memoryLoadedMeta && typeof memoryLoadedMeta === 'object') {
|
|
1618
|
+
existing.memoryLoadedMeta = cloneJsonValue(memoryLoadedMeta);
|
|
1619
|
+
}
|
|
1607
1620
|
this.#requestCache.set(existing.requestKey, existing);
|
|
1608
1621
|
return existing;
|
|
1609
1622
|
}
|
|
@@ -1621,6 +1634,11 @@ export class DebugTrace {
|
|
|
1621
1634
|
vpId: vpId || null,
|
|
1622
1635
|
threadId: threadId || null,
|
|
1623
1636
|
userPrompt: truncateText(userPrompt || '', this.#textMaxBytes),
|
|
1637
|
+
memoryLoaded: Array.isArray(memoryLoaded) ? cloneJsonValue(memoryLoaded) : null,
|
|
1638
|
+
memoryLoadedMeta: memoryLoadedMeta && typeof memoryLoadedMeta === 'object'
|
|
1639
|
+
? cloneJsonValue(memoryLoadedMeta)
|
|
1640
|
+
: null,
|
|
1641
|
+
memoryAdjust: null,
|
|
1624
1642
|
openedAt: now,
|
|
1625
1643
|
closedAt: null,
|
|
1626
1644
|
updatedAt: now,
|
package/yeaft/dream/limits.js
CHANGED
|
@@ -20,6 +20,11 @@ export const MAX_SINGLE_MESSAGE_CHARS = 8000;
|
|
|
20
20
|
export const MAX_DREAM_PROMPT_CHARS = 96000;
|
|
21
21
|
export const MAX_DIFF_TOKENS_PER_TRIAGE = 60000;
|
|
22
22
|
export const MAX_APPLY_TOKENS = 80000;
|
|
23
|
+
// Dream writes compact canonical memory, not long-form user output. Keep the
|
|
24
|
+
// response large enough for structured updates while bounding runaway cost.
|
|
25
|
+
export const MAX_DREAM_OUTPUT_TOKENS = 8192;
|
|
26
|
+
export const DREAM_FAILURE_BACKOFF_HOURS = 2;
|
|
27
|
+
export const DREAM_FAILURE_BACKOFF_MAX_HOURS = 24;
|
|
23
28
|
export const DREAM_BACKUP_KEEP = 7;
|
|
24
29
|
|
|
25
30
|
// task-710: nudge dream off the 1h timer when a group has accumulated
|
|
@@ -35,6 +40,9 @@ export const DEFAULT_LIMITS = Object.freeze({
|
|
|
35
40
|
MAX_DREAM_PROMPT_CHARS,
|
|
36
41
|
MAX_DIFF_TOKENS_PER_TRIAGE,
|
|
37
42
|
MAX_APPLY_TOKENS,
|
|
43
|
+
MAX_DREAM_OUTPUT_TOKENS,
|
|
44
|
+
DREAM_FAILURE_BACKOFF_HOURS,
|
|
45
|
+
DREAM_FAILURE_BACKOFF_MAX_HOURS,
|
|
38
46
|
DREAM_BACKUP_KEEP,
|
|
39
47
|
DREAM_NUDGE_AFTER_MESSAGES,
|
|
40
48
|
});
|
package/yeaft/dream/runner.js
CHANGED
|
@@ -150,6 +150,17 @@ export async function runDream(opts) {
|
|
|
150
150
|
sessionsReport.push({ sessionId, new: newCount, status: 'skipped', reason: 'below-threshold' });
|
|
151
151
|
continue;
|
|
152
152
|
}
|
|
153
|
+
const retryAt = dreamRetryAt(state, limits);
|
|
154
|
+
if (!opts.manual && retryAt && Date.parse(nowIso) < retryAt) {
|
|
155
|
+
sessionsReport.push({
|
|
156
|
+
sessionId,
|
|
157
|
+
new: newCount,
|
|
158
|
+
status: 'skipped',
|
|
159
|
+
reason: 'failure-backoff',
|
|
160
|
+
retryAt: new Date(retryAt).toISOString(),
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
153
164
|
|
|
154
165
|
onProgress({ phase: 'load-diff', sessionId });
|
|
155
166
|
const diffCursor = state.lastDreamMessageId;
|
|
@@ -182,6 +193,8 @@ export async function runDream(opts) {
|
|
|
182
193
|
lastDreamMessageId: tailId,
|
|
183
194
|
lastDreamAt: nowIso,
|
|
184
195
|
messageCount: beforeCount,
|
|
196
|
+
failureCount: 0,
|
|
197
|
+
lastFailureAt: null,
|
|
185
198
|
});
|
|
186
199
|
}
|
|
187
200
|
sessionsReport.push({ sessionId, new: newCount, status: 'skipped', reason: 'no-durable-new-messages' });
|
|
@@ -221,6 +234,7 @@ export async function runDream(opts) {
|
|
|
221
234
|
stack: err.stack,
|
|
222
235
|
rawSnippet: err.rawSnippet,
|
|
223
236
|
});
|
|
237
|
+
await recordDreamFailure(opts.root, sessionId, state, nowIso);
|
|
224
238
|
continue;
|
|
225
239
|
}
|
|
226
240
|
|
|
@@ -232,7 +246,7 @@ export async function runDream(opts) {
|
|
|
232
246
|
});
|
|
233
247
|
|
|
234
248
|
const tailId = lastMessageId(diffNew);
|
|
235
|
-
processedSessions.push({ sessionId, tailId, beforeCount, newCount, segments: segments.length, actions: actions.length });
|
|
249
|
+
processedSessions.push({ sessionId, tailId, beforeCount, newCount, state, segments: segments.length, actions: actions.length });
|
|
236
250
|
sessionsReport.push({ sessionId, new: newCount, segments: segments.length, actions: actions.length, status: 'triaged' });
|
|
237
251
|
}
|
|
238
252
|
|
|
@@ -304,9 +318,21 @@ export async function runDream(opts) {
|
|
|
304
318
|
nowIso: opts.nowIso || (() => nowIso),
|
|
305
319
|
segmentIndex: opts.segmentIndex || null,
|
|
306
320
|
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
321
|
+
if (r.errors.length > 0) {
|
|
322
|
+
const status = r.successfulScopes > 0 ? 'partial' : 'error';
|
|
323
|
+
const error = summarizeExtractionErrors(r.errors);
|
|
324
|
+
segmentReports.push({ sessionId: triage.sessionId, status, ...r, error });
|
|
325
|
+
onProgress({ phase: 'extract-segments', sessionId: triage.sessionId, status, ...r, error });
|
|
326
|
+
await writeDreamError(opts.root, `sessions/${triage.sessionId}`, {
|
|
327
|
+
phase: 'extract-segments',
|
|
328
|
+
message: error,
|
|
329
|
+
rawSnippet: r.errors.find(item => item.rawSnippet)?.rawSnippet,
|
|
330
|
+
});
|
|
331
|
+
} else {
|
|
332
|
+
await clearDreamError(opts.root, `sessions/${triage.sessionId}`);
|
|
333
|
+
segmentReports.push({ sessionId: triage.sessionId, status: 'done', ...r });
|
|
334
|
+
onProgress({ phase: 'extract-segments', sessionId: triage.sessionId, status: 'done', ...r });
|
|
335
|
+
}
|
|
310
336
|
} catch (err) {
|
|
311
337
|
segmentReports.push({ sessionId: triage.sessionId, status: 'error', error: err.message });
|
|
312
338
|
onProgress({ phase: 'extract-segments', sessionId: triage.sessionId, status: 'error', error: err.message });
|
|
@@ -349,18 +375,22 @@ export async function runDream(opts) {
|
|
|
349
375
|
}
|
|
350
376
|
}
|
|
351
377
|
|
|
352
|
-
// 7. bookkeep — advance a Session cursor only after every Apply target
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
378
|
+
// 7. bookkeep — advance a Session cursor only after every Apply target and
|
|
379
|
+
// its provenance extraction completed successfully. Apply keeps all batch
|
|
380
|
+
// output in memory until the target's final canonical write, while extraction
|
|
381
|
+
// reports per-scope failures after any successful writes. Either failure must
|
|
382
|
+
// retain the source diff so the next run can retry it rather than permanently
|
|
383
|
+
// losing canonical content or evidence.
|
|
358
384
|
const targetStatus = new Map(targetsReport.map(report => [report.target, report.status]));
|
|
385
|
+
const extractionStatus = new Map(segmentReports.map(report => [report.sessionId, report.status]));
|
|
359
386
|
for (const pg of processedSessions) {
|
|
360
387
|
const sessionTargets = targetsToApply
|
|
361
388
|
.filter(target => target.sources.some(source => source.sessionId === pg.sessionId))
|
|
362
389
|
.map(target => target.target);
|
|
363
|
-
|
|
390
|
+
const applySucceeded = sessionTargets.length > 0
|
|
391
|
+
&& sessionTargets.every(target => targetStatus.get(target) === 'done');
|
|
392
|
+
if (!applySucceeded || extractionStatus.get(pg.sessionId) !== 'done') {
|
|
393
|
+
await recordDreamFailure(opts.root, pg.sessionId, pg.state, nowIso);
|
|
364
394
|
continue;
|
|
365
395
|
}
|
|
366
396
|
if (pg.tailId) {
|
|
@@ -368,6 +398,8 @@ export async function runDream(opts) {
|
|
|
368
398
|
lastDreamMessageId: pg.tailId,
|
|
369
399
|
lastDreamAt: nowIso,
|
|
370
400
|
messageCount: pg.beforeCount,
|
|
401
|
+
failureCount: 0,
|
|
402
|
+
lastFailureAt: null,
|
|
371
403
|
});
|
|
372
404
|
}
|
|
373
405
|
}
|
|
@@ -399,6 +431,35 @@ export async function runDream(opts) {
|
|
|
399
431
|
|
|
400
432
|
// ─── helpers ──────────────────────────────────────────────────
|
|
401
433
|
|
|
434
|
+
function summarizeExtractionErrors(errors) {
|
|
435
|
+
const details = errors
|
|
436
|
+
.slice(0, 3)
|
|
437
|
+
.map(item => `${item.scope}: ${String(item.error || 'unknown error').slice(0, 300)}`)
|
|
438
|
+
.join('; ');
|
|
439
|
+
return `Dream segment extraction failed for ${errors.length} scope(s): ${details}`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function dreamRetryAt(state, limits) {
|
|
443
|
+
const failureCount = Math.max(0, Number(state?.failureCount) || 0);
|
|
444
|
+
const failedAt = Date.parse(state?.lastFailureAt || '');
|
|
445
|
+
if (failureCount === 0 || !Number.isFinite(failedAt)) return null;
|
|
446
|
+
const hours = Math.min(
|
|
447
|
+
limits.DREAM_FAILURE_BACKOFF_MAX_HOURS,
|
|
448
|
+
limits.DREAM_FAILURE_BACKOFF_HOURS * (2 ** Math.max(0, failureCount - 1)),
|
|
449
|
+
);
|
|
450
|
+
return failedAt + hours * 60 * 60 * 1000;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function recordDreamFailure(root, sessionId, state, nowIso) {
|
|
454
|
+
await writeSessionState(root, sessionId, {
|
|
455
|
+
lastDreamMessageId: state?.lastDreamMessageId || null,
|
|
456
|
+
lastDreamAt: state?.lastDreamAt || null,
|
|
457
|
+
messageCount: Number(state?.messageCount) || 0,
|
|
458
|
+
failureCount: Math.max(0, Number(state?.failureCount) || 0) + 1,
|
|
459
|
+
lastFailureAt: nowIso,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
402
463
|
function lastMessageId(messages) {
|
|
403
464
|
if (!Array.isArray(messages) || messages.length === 0) return null;
|
|
404
465
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
@@ -50,13 +50,16 @@ export async function extractAndWriteMemorySegments(opts) {
|
|
|
50
50
|
if (typeof opts.llm !== 'function') throw new Error('extractAndWriteMemorySegments: llm required');
|
|
51
51
|
|
|
52
52
|
const messages = normalizeMessages(opts.messages || []);
|
|
53
|
-
if (messages.length === 0)
|
|
53
|
+
if (messages.length === 0) {
|
|
54
|
+
return { scopes: 0, segments: 0, attemptedScopes: 0, successfulScopes: 0, errors: [] };
|
|
55
|
+
}
|
|
54
56
|
const allowedSourceIds = new Set(messages.map(message => message.id));
|
|
55
57
|
|
|
56
58
|
const targetScopes = normalizeTargetScopes(opts.sessionId, opts.targets || []);
|
|
57
59
|
const now = opts.nowIso ? opts.nowIso() : new Date().toISOString();
|
|
58
60
|
let segmentCount = 0;
|
|
59
61
|
let scopeCount = 0;
|
|
62
|
+
let successfulScopes = 0;
|
|
60
63
|
const errors = [];
|
|
61
64
|
|
|
62
65
|
for (const scope of targetScopes) {
|
|
@@ -72,6 +75,7 @@ export async function extractAndWriteMemorySegments(opts) {
|
|
|
72
75
|
allowedSourceIds,
|
|
73
76
|
maxPromptChars: opts.limits?.MAX_DREAM_PROMPT_CHARS,
|
|
74
77
|
});
|
|
78
|
+
successfulScopes += 1;
|
|
75
79
|
} catch (err) {
|
|
76
80
|
errors.push({ scope, error: err.message, rawSnippet: err.rawSnippet || '' });
|
|
77
81
|
}
|
|
@@ -92,7 +96,13 @@ export async function extractAndWriteMemorySegments(opts) {
|
|
|
92
96
|
scopeCount += 1;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
scopes: scopeCount,
|
|
101
|
+
segments: segmentCount,
|
|
102
|
+
attemptedScopes: targetScopes.length,
|
|
103
|
+
successfulScopes,
|
|
104
|
+
errors,
|
|
105
|
+
};
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
async function extractScopeSegments({ scope, sessionId, messages, llm, language, now, allowedSourceIds, maxPromptChars }) {
|
|
@@ -44,6 +44,7 @@ import { runDream } from './runner.js';
|
|
|
44
44
|
import { createDreamScheduler } from './schedule.js';
|
|
45
45
|
import { parseMessage, parseSeqFromId } from '../conversation/persist.js';
|
|
46
46
|
import { loadSessionConfig, resolveSessionConfig } from '../sessions/session-config.js';
|
|
47
|
+
import { parseModelRef, resolveMaxOutputTokens, resolveModel } from '../models.js';
|
|
47
48
|
import { listSessions as listSessionMetas } from '../sessions/session-store.js';
|
|
48
49
|
import { readSessionState } from './state.js';
|
|
49
50
|
import { boundDreamPrompt } from './segment.js';
|
|
@@ -380,12 +381,16 @@ function makeLlm(session) {
|
|
|
380
381
|
const dreamLimits = loadLimitsFromConfig(effectiveConfig);
|
|
381
382
|
const boundedPrompt = boundDreamPrompt(prompt, dreamLimits.MAX_DREAM_PROMPT_CHARS);
|
|
382
383
|
|
|
384
|
+
const { modelId } = parseModelRef(model);
|
|
385
|
+
const modelMaxOutput = resolveMaxOutputTokens(modelId, effectiveConfig);
|
|
386
|
+
const maxTokens = Math.min(modelMaxOutput, dreamLimits.MAX_DREAM_OUTPUT_TOKENS);
|
|
383
387
|
const r = await adapter.call({
|
|
384
388
|
model,
|
|
385
389
|
system: effectiveSystem,
|
|
386
390
|
messages: [{ role: 'user', content: boundedPrompt }],
|
|
387
|
-
maxTokens
|
|
388
|
-
|
|
391
|
+
maxTokens,
|
|
392
|
+
effort: effectiveConfig?.modelEffort || undefined,
|
|
393
|
+
effortSource: effectiveConfig?.modelEffort ? 'user' : undefined,
|
|
389
394
|
});
|
|
390
395
|
|
|
391
396
|
// Emit complete loop event (request + response) to the debug panel.
|
|
@@ -417,7 +422,7 @@ function makeLlm(session) {
|
|
|
417
422
|
usage,
|
|
418
423
|
latencyMs,
|
|
419
424
|
ttfbMs: null,
|
|
420
|
-
stopReason: 'end_turn',
|
|
425
|
+
stopReason: r?.stopReason || 'end_turn',
|
|
421
426
|
rawRequest: null,
|
|
422
427
|
rawResponse: null,
|
|
423
428
|
});
|
|
@@ -426,6 +431,13 @@ function makeLlm(session) {
|
|
|
426
431
|
session._dreamProgressSink(loopEvent);
|
|
427
432
|
}
|
|
428
433
|
|
|
434
|
+
if (r?.stopReason === 'max_tokens') {
|
|
435
|
+
const err = new Error(`Dream ${pass} response exceeded the ${maxTokens}-token output limit`);
|
|
436
|
+
err.code = 'DREAM_OUTPUT_TRUNCATED';
|
|
437
|
+
err.rawSnippet = typeof r.text === 'string' ? r.text.slice(-1000) : '';
|
|
438
|
+
throw err;
|
|
439
|
+
}
|
|
440
|
+
|
|
429
441
|
return (r && r.text) ? r.text : '';
|
|
430
442
|
};
|
|
431
443
|
}
|
|
@@ -433,10 +445,34 @@ function makeLlm(session) {
|
|
|
433
445
|
|
|
434
446
|
function resolveDreamSessionConfig(session, sessionId) {
|
|
435
447
|
const base = session?.config || {};
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
448
|
+
const sessionConfig = sessionId && session?.yeaftDir
|
|
449
|
+
? loadSessionConfig(session.yeaftDir, sessionId)
|
|
450
|
+
: null;
|
|
451
|
+
const effective = sessionConfig && Object.keys(sessionConfig).length > 0
|
|
452
|
+
? resolveSessionConfig(base, sessionConfig)
|
|
453
|
+
: { ...base };
|
|
454
|
+
const model = effective.primaryModel || effective.model;
|
|
455
|
+
if (!model) return effective;
|
|
456
|
+
|
|
457
|
+
const { providerName, modelId } = parseModelRef(model);
|
|
458
|
+
const catalogEntry = Array.isArray(effective.availableModels)
|
|
459
|
+
? effective.availableModels.find(entry => entry?.ref === model
|
|
460
|
+
|| (entry?.id === modelId && (!providerName || entry?.provider === providerName)))
|
|
461
|
+
: null;
|
|
462
|
+
const registryInfo = resolveModel(modelId);
|
|
463
|
+
const modelInfo = {
|
|
464
|
+
...(registryInfo || {}),
|
|
465
|
+
...(catalogEntry || {}),
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// The live Session object belongs to the Session that triggered Dream. A
|
|
469
|
+
// target Session may select another model, so its cached modelInfo and token
|
|
470
|
+
// limits are not valid here. Rebuild modelInfo from the target catalog entry
|
|
471
|
+
// and let the resolver use the bare model id for registry/models.dev lookup.
|
|
472
|
+
return {
|
|
473
|
+
...effective,
|
|
474
|
+
modelInfo: Object.keys(modelInfo).length > 0 ? modelInfo : null,
|
|
475
|
+
};
|
|
440
476
|
}
|
|
441
477
|
|
|
442
478
|
|
package/yeaft/dream/state.js
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ~/.yeaft/memory/sessions/<id>/.dream-state
|
|
10
10
|
*
|
|
11
|
-
* A
|
|
11
|
+
* A small line-oriented text file:
|
|
12
12
|
*
|
|
13
13
|
* lastDreamMessageId: m-1024
|
|
14
14
|
* lastDreamAt: 2026-04-28T03:07:00Z
|
|
15
15
|
* messageCount: 491
|
|
16
|
+
* failureCount: 0
|
|
17
|
+
* lastFailureAt:
|
|
16
18
|
*
|
|
17
19
|
* Fields are independent of each other; missing fields default to
|
|
18
20
|
* empty / null / 0. The file is rewritten atomically every dream.
|
|
@@ -60,11 +62,17 @@ const DREAM_BLOCK_CLOSE = '<!-- /dream-state -->';
|
|
|
60
62
|
*
|
|
61
63
|
* @param {string} root — memory root, e.g. ~/.yeaft/memory
|
|
62
64
|
* @param {string} sessionId
|
|
63
|
-
* @returns {Promise<{ lastDreamMessageId: string|null, lastDreamAt: string|null, messageCount: number }>}
|
|
65
|
+
* @returns {Promise<{ lastDreamMessageId: string|null, lastDreamAt: string|null, messageCount: number, failureCount: number, lastFailureAt: string|null }>}
|
|
64
66
|
*/
|
|
65
67
|
export async function readSessionState(root, sessionId) {
|
|
66
68
|
const abs = join(root, 'sessions', sessionId, STATE_FILE);
|
|
67
|
-
const empty = {
|
|
69
|
+
const empty = {
|
|
70
|
+
lastDreamMessageId: null,
|
|
71
|
+
lastDreamAt: null,
|
|
72
|
+
messageCount: 0,
|
|
73
|
+
failureCount: 0,
|
|
74
|
+
lastFailureAt: null,
|
|
75
|
+
};
|
|
68
76
|
let raw;
|
|
69
77
|
try { raw = await fsp.readFile(abs, 'utf8'); }
|
|
70
78
|
catch (err) {
|
|
@@ -80,16 +88,20 @@ export async function readSessionState(root, sessionId) {
|
|
|
80
88
|
*
|
|
81
89
|
* @param {string} root
|
|
82
90
|
* @param {string} sessionId
|
|
83
|
-
* @param {{ lastDreamMessageId?: string|null, lastDreamAt?: string|null, messageCount?: number }} state
|
|
91
|
+
* @param {{ lastDreamMessageId?: string|null, lastDreamAt?: string|null, messageCount?: number, failureCount?: number, lastFailureAt?: string|null }} state
|
|
84
92
|
*/
|
|
85
93
|
export async function writeSessionState(root, sessionId, state) {
|
|
86
94
|
const dir = join(root, 'sessions', sessionId);
|
|
87
95
|
await fsp.mkdir(dir, { recursive: true });
|
|
88
96
|
const abs = join(dir, STATE_FILE);
|
|
97
|
+
const current = existsSync(abs) ? await readSessionState(root, sessionId) : {};
|
|
98
|
+
const merged = { ...current, ...state };
|
|
89
99
|
const body =
|
|
90
|
-
`lastDreamMessageId: ${
|
|
91
|
-
`lastDreamAt: ${
|
|
92
|
-
`messageCount: ${Number.isFinite(
|
|
100
|
+
`lastDreamMessageId: ${merged.lastDreamMessageId == null ? '' : merged.lastDreamMessageId}\n` +
|
|
101
|
+
`lastDreamAt: ${merged.lastDreamAt == null ? '' : merged.lastDreamAt}\n` +
|
|
102
|
+
`messageCount: ${Number.isFinite(merged.messageCount) ? merged.messageCount : 0}\n` +
|
|
103
|
+
`failureCount: ${Number.isFinite(merged.failureCount) ? Math.max(0, Math.floor(merged.failureCount)) : 0}\n` +
|
|
104
|
+
`lastFailureAt: ${merged.lastFailureAt == null ? '' : merged.lastFailureAt}\n`;
|
|
93
105
|
await atomicWrite(abs, body);
|
|
94
106
|
}
|
|
95
107
|
|
|
@@ -99,7 +111,13 @@ export async function writeSessionState(root, sessionId, state) {
|
|
|
99
111
|
* @param {string} raw
|
|
100
112
|
*/
|
|
101
113
|
function parseSessionState(raw) {
|
|
102
|
-
const out = {
|
|
114
|
+
const out = {
|
|
115
|
+
lastDreamMessageId: null,
|
|
116
|
+
lastDreamAt: null,
|
|
117
|
+
messageCount: 0,
|
|
118
|
+
failureCount: 0,
|
|
119
|
+
lastFailureAt: null,
|
|
120
|
+
};
|
|
103
121
|
const lines = String(raw || '').split(/\r?\n/);
|
|
104
122
|
for (const ln of lines) {
|
|
105
123
|
const m = /^(\w[\w-]*)\s*:\s*(.*)$/.exec(ln);
|
|
@@ -111,7 +129,10 @@ function parseSessionState(raw) {
|
|
|
111
129
|
else if (k === 'messageCount') {
|
|
112
130
|
const n = Number(v);
|
|
113
131
|
out.messageCount = Number.isFinite(n) ? n : 0;
|
|
114
|
-
}
|
|
132
|
+
} else if (k === 'failureCount') {
|
|
133
|
+
const n = Number(v);
|
|
134
|
+
out.failureCount = Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
|
|
135
|
+
} else if (k === 'lastFailureAt') out.lastFailureAt = v || null;
|
|
115
136
|
}
|
|
116
137
|
return out;
|
|
117
138
|
}
|
package/yeaft/engine.js
CHANGED
|
@@ -2177,6 +2177,12 @@ export class Engine {
|
|
|
2177
2177
|
memoryInjection = amsContext.snapshotBlock;
|
|
2178
2178
|
}
|
|
2179
2179
|
const loadedMemoryForDebug = loadedMemoryDebugEntries(amsContext?.snapshot);
|
|
2180
|
+
const loadedMemoryMetaForDebug = {
|
|
2181
|
+
recallLimit: resolveMemoryRecallLimit(this.#config),
|
|
2182
|
+
recallCandidates: Number.isFinite(recallResult?.meta?.hitCount)
|
|
2183
|
+
? recallResult.meta.hitCount
|
|
2184
|
+
: (recallResult && Array.isArray(recallResult.entries) ? recallResult.entries.length : 0),
|
|
2185
|
+
};
|
|
2180
2186
|
|
|
2181
2187
|
// Diagnostic payload for the Dream debug panel. The full AMS Resident
|
|
2182
2188
|
// layer can include user and per-VP summaries, but the browser-facing
|
|
@@ -2443,12 +2449,7 @@ export class Engine {
|
|
|
2443
2449
|
type: 'memory_used',
|
|
2444
2450
|
turnId: queryTurnId,
|
|
2445
2451
|
loaded: loadedMemoryForDebug,
|
|
2446
|
-
meta:
|
|
2447
|
-
recallLimit: resolveMemoryRecallLimit(this.#config),
|
|
2448
|
-
recallCandidates: Number.isFinite(recallResult?.meta?.hitCount)
|
|
2449
|
-
? recallResult.meta.hitCount
|
|
2450
|
-
: (recallResult && Array.isArray(recallResult.entries) ? recallResult.entries.length : 0),
|
|
2451
|
-
},
|
|
2452
|
+
meta: loadedMemoryMetaForDebug,
|
|
2452
2453
|
};
|
|
2453
2454
|
}
|
|
2454
2455
|
|
|
@@ -2580,6 +2581,10 @@ export class Engine {
|
|
|
2580
2581
|
// from `messages.find(role==='user')` would always return turn
|
|
2581
2582
|
// 1's prompt and mislabel every subsequent Turn header.
|
|
2582
2583
|
userPrompt: userQuestionPreview,
|
|
2584
|
+
// Persist the exact AMS projection that produced memoryInjection. Live
|
|
2585
|
+
// memory_used events are only progress signals and disappear on reload.
|
|
2586
|
+
memoryLoaded: loadedMemoryForDebug,
|
|
2587
|
+
memoryLoadedMeta: loadedMemoryMetaForDebug,
|
|
2583
2588
|
});
|
|
2584
2589
|
|
|
2585
2590
|
const startTime = Date.now();
|
|
@@ -4611,6 +4616,18 @@ export class Engine {
|
|
|
4611
4616
|
// Loop back to call adapter again with tool results
|
|
4612
4617
|
}
|
|
4613
4618
|
|
|
4619
|
+
// Store the final provider-call count on the response row itself. Debug
|
|
4620
|
+
// events are transient; the response card must retain the count after a
|
|
4621
|
+
// reload without inventing a second counter.
|
|
4622
|
+
const llmCountMessage = lastPersistedAssistantTextMessage || lastPersistedAssistantMessage;
|
|
4623
|
+
if (llmCountMessage && typeof this.#conversationStore?.update === 'function') {
|
|
4624
|
+
const updated = this.#conversationStore.update(llmCountMessage, {
|
|
4625
|
+
llmCallCount: turnNumber,
|
|
4626
|
+
});
|
|
4627
|
+
if (updated?.id === lastPersistedAssistantTextMessage?.id) lastPersistedAssistantTextMessage = updated;
|
|
4628
|
+
if (updated?.id === lastPersistedAssistantMessage?.id) lastPersistedAssistantMessage = updated;
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4614
4631
|
// feat-6af5f9f1 PR B: turn closed. Emits final totals so the debug
|
|
4615
4632
|
// panel can show "Turn done · 4 loops · 12.4s · 5.0k tok" without
|
|
4616
4633
|
// having to reduce the loops itself. Always fires (every break path
|
package/yeaft/llm/adapter.js
CHANGED
|
@@ -651,7 +651,7 @@ export class LLMAdapter {
|
|
|
651
651
|
* Make a single model call without tools (for side queries like summarization).
|
|
652
652
|
*
|
|
653
653
|
* @param {{ model: string, system: string, messages: UnifiedMessage[], maxTokens?: number, signal?: AbortSignal }} params
|
|
654
|
-
* @returns {Promise<{ text: string, usage: { inputTokens: number, outputTokens: number } }>}
|
|
654
|
+
* @returns {Promise<{ text: string, stopReason?: 'end_turn'|'tool_use'|'max_tokens', usage: { inputTokens: number, outputTokens: number } }>}
|
|
655
655
|
*/
|
|
656
656
|
async call(params) { // eslint-disable-line no-unused-vars
|
|
657
657
|
throw new Error('call() must be implemented by subclass');
|
package/yeaft/llm/anthropic.js
CHANGED
|
@@ -594,6 +594,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
594
594
|
const usage = result.usage || {};
|
|
595
595
|
return {
|
|
596
596
|
text,
|
|
597
|
+
stopReason: this.#mapStopReason(result, false),
|
|
597
598
|
usage: {
|
|
598
599
|
inputTokens: usage.input_tokens || 0,
|
|
599
600
|
outputTokens: usage.output_tokens || 0,
|
package/yeaft/llm/router.js
CHANGED
|
@@ -668,7 +668,7 @@ export class AdapterRouter extends LLMAdapter {
|
|
|
668
668
|
* Make a single model call — routes to the correct provider adapter.
|
|
669
669
|
*
|
|
670
670
|
* @param {import('./adapter.js').CallParams} params
|
|
671
|
-
* @returns {Promise<{ text: string, usage: { inputTokens: number, outputTokens: number } }>}
|
|
671
|
+
* @returns {Promise<{ text: string, stopReason?: 'end_turn'|'tool_use'|'max_tokens', usage: { inputTokens: number, outputTokens: number } }>}
|
|
672
672
|
*/
|
|
673
673
|
async call(params) {
|
|
674
674
|
const dispatchSnapshot = this.#captureDispatchSnapshot();
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -1349,6 +1349,7 @@ function projectPersistedToHistoryEntry(m, { includeReflections = false } = {})
|
|
|
1349
1349
|
if (m.turnId) entry.turnId = m.turnId;
|
|
1350
1350
|
if (m.imageAssetAnchor) entry.imageAssetAnchor = true;
|
|
1351
1351
|
if (m.responseKind === 'progress' || m.responseKind === 'result') entry.responseKind = m.responseKind;
|
|
1352
|
+
if (Number.isInteger(m.llmCallCount) && m.llmCallCount > 0) entry.llmCallCount = m.llmCallCount;
|
|
1352
1353
|
if (m.incomplete === true) entry.incomplete = true;
|
|
1353
1354
|
if (typeof m.stopReason === 'string' && m.stopReason) entry.stopReason = m.stopReason;
|
|
1354
1355
|
if (m.sessionId) entry.sessionId = m.sessionId;
|
|
@@ -1489,6 +1490,7 @@ function projectVisibleHistoryChunkMessages(messages = []) {
|
|
|
1489
1490
|
...(Array.isArray(m.images) && m.images.length > 0 ? { images: m.images } : {}),
|
|
1490
1491
|
...(m.speakerVpId ? { speakerVpId: m.speakerVpId } : {}),
|
|
1491
1492
|
...(m.responseKind === 'progress' || m.responseKind === 'result' ? { responseKind: m.responseKind } : {}),
|
|
1493
|
+
...(Number.isInteger(m.llmCallCount) && m.llmCallCount > 0 ? { llmCallCount: m.llmCallCount } : {}),
|
|
1492
1494
|
...(m.incomplete === true ? { incomplete: true } : {}),
|
|
1493
1495
|
...(typeof m.stopReason === 'string' && m.stopReason ? { stopReason: m.stopReason } : {}),
|
|
1494
1496
|
...(Array.isArray(m.todos) ? { todos: m.todos } : {}),
|