@yeaft/webchat-agent 0.1.753 → 0.1.754
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 +1 -1
- package/unify/dream-v2/runner.js +17 -1
- package/unify/dream-v2/state.js +103 -2
- package/unify/web-bridge.js +34 -7
package/package.json
CHANGED
package/unify/dream-v2/runner.js
CHANGED
|
@@ -44,7 +44,7 @@ import { listScopes, readSummary } from '../memory/store-v2.js';
|
|
|
44
44
|
import {
|
|
45
45
|
DEFAULT_LIMITS,
|
|
46
46
|
} from './limits.js';
|
|
47
|
-
import { readGroupState, writeGroupState } from './state.js';
|
|
47
|
+
import { readGroupState, writeGroupState, writeDreamError } from './state.js';
|
|
48
48
|
import { segmentDiff, truncateMessage, estimateMessagesTokens } from './segment.js';
|
|
49
49
|
import { triageGroupSegments } from './triage.js';
|
|
50
50
|
import { mergeByTarget } from './merge.js';
|
|
@@ -148,6 +148,14 @@ export async function runDream(opts) {
|
|
|
148
148
|
} catch (err) {
|
|
149
149
|
groupsReport.push({ groupId, new: newCount, status: 'error', error: err.message });
|
|
150
150
|
onProgress({ phase: 'triage', groupId, status: 'error', error: err.message });
|
|
151
|
+
// Journal the failure on disk so operators can see WHY dream is
|
|
152
|
+
// not advancing without having to enable `config.debug`. Best-
|
|
153
|
+
// effort — `writeDreamError` swallows its own I/O errors.
|
|
154
|
+
await writeDreamError(opts.root, `group/${groupId}`, {
|
|
155
|
+
phase: 'triage',
|
|
156
|
+
message: err.message,
|
|
157
|
+
stack: err.stack,
|
|
158
|
+
});
|
|
151
159
|
continue;
|
|
152
160
|
}
|
|
153
161
|
|
|
@@ -191,6 +199,14 @@ export async function runDream(opts) {
|
|
|
191
199
|
error: err.message,
|
|
192
200
|
});
|
|
193
201
|
onProgress({ phase: 'apply', target: merged.target, status: 'error', error: err.message });
|
|
202
|
+
// Journal apply-stage failures into the target scope's directory
|
|
203
|
+
// (`<root>/<merged.target>/.dream-last-error.json`). Same rationale
|
|
204
|
+
// as the triage catch above.
|
|
205
|
+
await writeDreamError(opts.root, merged.target, {
|
|
206
|
+
phase: 'apply',
|
|
207
|
+
message: err.message,
|
|
208
|
+
stack: err.stack,
|
|
209
|
+
});
|
|
194
210
|
}
|
|
195
211
|
}
|
|
196
212
|
|
package/unify/dream-v2/state.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dream-v2/state.js.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Three pieces of state, tracked separately:
|
|
5
5
|
*
|
|
6
6
|
* 1. Per-group control state (used to decide whether a group enters
|
|
7
7
|
* triage and how far to advance the cursor):
|
|
@@ -31,13 +31,25 @@
|
|
|
31
31
|
* control-flow decision. We update it by replacing the existing
|
|
32
32
|
* block (if any) or appending a new one to the end of the file.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
34
|
+
* 3. Per-scope dream-error sink (added v0.1.754):
|
|
35
|
+
*
|
|
36
|
+
* ~/.yeaft/memory/<scope>/.dream-last-error.json
|
|
37
|
+
*
|
|
38
|
+
* Most-recent-wins JSON written unconditionally on every triage
|
|
39
|
+
* or apply failure (best-effort — never throws even when the I/O
|
|
40
|
+
* itself fails). The runner used to swallow these exceptions and
|
|
41
|
+
* the only sink was a `config.debug`-gated console.log; this file
|
|
42
|
+
* gives operators on-disk evidence regardless of debug. See
|
|
43
|
+
* `writeDreamError` / `readDreamError` below for the contract.
|
|
44
|
+
*
|
|
45
|
+
* All helpers are pure I/O; no LLM, no logic beyond parsing.
|
|
35
46
|
*/
|
|
36
47
|
|
|
37
48
|
import { promises as fsp, existsSync } from 'fs';
|
|
38
49
|
import { join, dirname } from 'path';
|
|
39
50
|
|
|
40
51
|
const STATE_FILE = '.dream-state';
|
|
52
|
+
const ERROR_FILE = '.dream-last-error.json';
|
|
41
53
|
const DREAM_BLOCK_OPEN = '<!-- dream-state -->';
|
|
42
54
|
const DREAM_BLOCK_CLOSE = '<!-- /dream-state -->';
|
|
43
55
|
|
|
@@ -101,6 +113,95 @@ function parseGroupState(raw) {
|
|
|
101
113
|
return out;
|
|
102
114
|
}
|
|
103
115
|
|
|
116
|
+
// ─── per-scope dream error sink ────────────────────────────────
|
|
117
|
+
//
|
|
118
|
+
// Why: dream-v2 silently swallowed exceptions at the triage / apply
|
|
119
|
+
// catch sites — the only sink was `trace.event('dream_progress', evt)`
|
|
120
|
+
// and a `config.debug`-gated `console.log` in `session-wiring.js`. With
|
|
121
|
+
// `debug=false` (the default), there was no on-disk evidence that a
|
|
122
|
+
// dream pass had ever failed: no `.dream-state` (because we only write
|
|
123
|
+
// it on success), no log file, nothing. The Resident layer's continued
|
|
124
|
+
// regurgitation of the bootstrap seed was the only symptom.
|
|
125
|
+
//
|
|
126
|
+
// `writeDreamError` writes `<memoryRoot>/<scope>/.dream-last-error.json`
|
|
127
|
+
// unconditionally on every catch (best-effort — write failures must not
|
|
128
|
+
// shadow the original error). Operators can then `ls ~/.yeaft/memory/
|
|
129
|
+
// group/<id>/` and see what blew up, without having to re-enable debug.
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve a memoryRoot + scope-string to the scope directory.
|
|
133
|
+
* The scope string is the same shape dream-v2 already uses internally:
|
|
134
|
+
* `'user'`, `'vp/<vpId>'`, `'group/<groupId>'`, `'feature/<id>'`, etc.
|
|
135
|
+
*
|
|
136
|
+
* Pure path-join; does NOT create the directory. The writer creates it.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} root
|
|
139
|
+
* @param {string} scope
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
export function scopeDirFor(root, scope) {
|
|
143
|
+
// Defensive: trim leading/trailing slashes so callers can pass either
|
|
144
|
+
// `'group/grp_fun'` or `/group/grp_fun/` — both land on the same dir.
|
|
145
|
+
const clean = String(scope || '').replace(/^\/+|\/+$/g, '');
|
|
146
|
+
return join(root, clean);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Best-effort write of the dream-error sink. Never throws — a failed
|
|
151
|
+
* write is silently swallowed because the caller is already in an
|
|
152
|
+
* error-handling path and we must not mask the original failure.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} root — memory root, e.g. ~/.yeaft/memory
|
|
155
|
+
* @param {string} scope — `'group/<id>'` for triage failures,
|
|
156
|
+
* `merged.target` for apply failures.
|
|
157
|
+
* @param {{ phase: string, message: string, stack?: string|null, at?: string }} info
|
|
158
|
+
* @returns {Promise<void>}
|
|
159
|
+
*/
|
|
160
|
+
export async function writeDreamError(root, scope, info) {
|
|
161
|
+
try {
|
|
162
|
+
const dir = scopeDirFor(root, scope);
|
|
163
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
164
|
+
const abs = join(dir, ERROR_FILE);
|
|
165
|
+
const at = (info && info.at) || new Date().toISOString();
|
|
166
|
+
// Trim stack to the first 5 frames — enough for diagnosis, small
|
|
167
|
+
// enough that the artifact stays human-readable. Missing/empty
|
|
168
|
+
// stack collapses to `null` rather than `""` so the artifact is
|
|
169
|
+
// cleaner for operators.
|
|
170
|
+
const rawStack = info && typeof info.stack === 'string' ? info.stack : '';
|
|
171
|
+
const stackLines = rawStack ? rawStack.split('\n').slice(0, 5) : [];
|
|
172
|
+
const body = JSON.stringify({
|
|
173
|
+
at,
|
|
174
|
+
scope,
|
|
175
|
+
phase: String(info?.phase || 'unknown'),
|
|
176
|
+
message: String(info?.message || ''),
|
|
177
|
+
stack: stackLines.length > 0 ? stackLines.join('\n') : null,
|
|
178
|
+
}, null, 2) + '\n';
|
|
179
|
+
await atomicWrite(abs, body);
|
|
180
|
+
} catch {
|
|
181
|
+
// Best-effort: swallow. The caller is already handling the real
|
|
182
|
+
// error; an inability to journal it must not shadow that.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Read the last dream error JSON for a scope, or null if absent. Used
|
|
188
|
+
* by the debug panel and by tests. Tolerates a malformed file by
|
|
189
|
+
* returning `{ raw: <body>, parseError: <message> }` instead of
|
|
190
|
+
* throwing.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} root
|
|
193
|
+
* @param {string} scope
|
|
194
|
+
* @returns {Promise<object|null>}
|
|
195
|
+
*/
|
|
196
|
+
export async function readDreamError(root, scope) {
|
|
197
|
+
const abs = join(scopeDirFor(root, scope), ERROR_FILE);
|
|
198
|
+
let raw;
|
|
199
|
+
try { raw = await fsp.readFile(abs, 'utf8'); }
|
|
200
|
+
catch (err) { if (err && err.code === 'ENOENT') return null; throw err; }
|
|
201
|
+
try { return JSON.parse(raw); }
|
|
202
|
+
catch (e) { return { raw, parseError: e.message }; }
|
|
203
|
+
}
|
|
204
|
+
|
|
104
205
|
// ─── per-scope marker (memory.md tail block) ───────────────────
|
|
105
206
|
|
|
106
207
|
/**
|
package/unify/web-bridge.js
CHANGED
|
@@ -2418,28 +2418,55 @@ export function __testGetRegisteredThreadIds() {
|
|
|
2418
2418
|
export const __testRaceWithEscalation = raceWithEscalation;
|
|
2419
2419
|
|
|
2420
2420
|
/**
|
|
2421
|
-
* Manual dream trigger
|
|
2421
|
+
* Manual dream trigger.
|
|
2422
|
+
*
|
|
2423
|
+
* Two call shapes, both routed through this single handler:
|
|
2424
|
+
*
|
|
2425
|
+
* { type: 'unify_dream_trigger', vpId } — per-VP trigger (legacy
|
|
2426
|
+
* VP-detail page button). Fires an unscoped dream pass; the result
|
|
2427
|
+
* event is tagged with `vpId` so the per-VP store row updates.
|
|
2428
|
+
*
|
|
2429
|
+
* { type: 'unify_dream_trigger', groupId } — per-GROUP trigger (new
|
|
2430
|
+
* in v0.1.754 — added so users can manually kick dream for a group
|
|
2431
|
+
* after seeing the Resident layer stuck on the bootstrap seed).
|
|
2432
|
+
* Fires a scope-filtered pass via `triggerDreamForScopes(['group/X'])`
|
|
2433
|
+
* so unrelated groups don't get processed; the result event is
|
|
2434
|
+
* tagged with `groupId` for the per-group UI row.
|
|
2435
|
+
*
|
|
2436
|
+
* Backwards-compat: when neither field is set, defaults to `vpId='default'`
|
|
2437
|
+
* which matches the pre-v0.1.754 behavior.
|
|
2422
2438
|
*/
|
|
2423
2439
|
export async function handleUnifyDreamTrigger(msg = {}) {
|
|
2440
|
+
// Resolve tag up-front so EVERY outbound envelope (including the
|
|
2441
|
+
// scheduler-uninitialised early-return below) carries `groupId` /
|
|
2442
|
+
// `vpId`. Without this the frontend's `applyDreamResult` couldn't
|
|
2443
|
+
// route the error event back to the right row and the per-group
|
|
2444
|
+
// "Run dream now" button would stay stuck on "Running…" forever
|
|
2445
|
+
// (review feedback from PR #757).
|
|
2446
|
+
const groupId = typeof msg.groupId === 'string' && msg.groupId ? msg.groupId : null;
|
|
2447
|
+
const vpId = !groupId ? (msg.vpId || 'default') : null;
|
|
2448
|
+
const tag = groupId ? { groupId } : { vpId };
|
|
2449
|
+
|
|
2424
2450
|
if (!session?.dreamScheduler) {
|
|
2425
2451
|
sendToServer({
|
|
2426
2452
|
type: 'unify_dream_result',
|
|
2453
|
+
...tag,
|
|
2427
2454
|
success: false,
|
|
2428
2455
|
error: 'Dream scheduler not initialized — session not loaded.',
|
|
2429
2456
|
});
|
|
2430
2457
|
return;
|
|
2431
2458
|
}
|
|
2432
2459
|
|
|
2433
|
-
const vpId = msg.vpId || 'default';
|
|
2434
|
-
|
|
2435
2460
|
try {
|
|
2436
2461
|
sendToServer({
|
|
2437
2462
|
type: 'unify_dream_status',
|
|
2438
|
-
|
|
2463
|
+
...tag,
|
|
2439
2464
|
status: 'running',
|
|
2440
2465
|
});
|
|
2441
2466
|
|
|
2442
|
-
const result =
|
|
2467
|
+
const result = groupId
|
|
2468
|
+
? await session.dreamScheduler.triggerDreamForScopes([`group/${groupId}`])
|
|
2469
|
+
: await session.dreamScheduler.triggerDreamNow();
|
|
2443
2470
|
|
|
2444
2471
|
// fix/dream-cadence-and-ui-trigger: derive a single "entries
|
|
2445
2472
|
// created" count for the UI bubble. The runner returns a richer
|
|
@@ -2457,7 +2484,7 @@ export async function handleUnifyDreamTrigger(msg = {}) {
|
|
|
2457
2484
|
// PR #743.
|
|
2458
2485
|
sendToServer({
|
|
2459
2486
|
type: 'unify_dream_result',
|
|
2460
|
-
|
|
2487
|
+
...tag,
|
|
2461
2488
|
...result,
|
|
2462
2489
|
success: !result.error && !result.skipped,
|
|
2463
2490
|
entriesCreated,
|
|
@@ -2466,7 +2493,7 @@ export async function handleUnifyDreamTrigger(msg = {}) {
|
|
|
2466
2493
|
} catch (err) {
|
|
2467
2494
|
sendToServer({
|
|
2468
2495
|
type: 'unify_dream_result',
|
|
2469
|
-
|
|
2496
|
+
...tag,
|
|
2470
2497
|
success: false,
|
|
2471
2498
|
error: err?.message || String(err),
|
|
2472
2499
|
});
|