@yeaft/webchat-agent 0.1.847 → 0.1.849
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/apply.js +37 -1
- package/unify/dream-v2/session-wiring.js +29 -0
- package/unify/groups/pre-flow.js +5 -4
- package/unify/groups/roster.js +27 -0
- package/unify/routing/router.js +12 -11
package/package.json
CHANGED
package/unify/dream-v2/apply.js
CHANGED
|
@@ -228,7 +228,23 @@ export async function applyMergedTarget(merged, opts) {
|
|
|
228
228
|
await writeMemory(scope, stamped, { root: opts.root });
|
|
229
229
|
await writeSummary(scope, summaryMd || '', { root: opts.root, language: opts.language });
|
|
230
230
|
|
|
231
|
-
if (opts.onProgress)
|
|
231
|
+
if (opts.onProgress) {
|
|
232
|
+
// feat-dream-debug-detail: surface a truncated copy of what was
|
|
233
|
+
// actually written so the debug panel can show "what segments were
|
|
234
|
+
// generated" instead of just "done". The full bytes are on disk
|
|
235
|
+
// anyway — this preview is for at-a-glance debugging.
|
|
236
|
+
opts.onProgress({
|
|
237
|
+
phase: 'apply',
|
|
238
|
+
target: merged.target,
|
|
239
|
+
status: 'done',
|
|
240
|
+
batches: batchesUsed,
|
|
241
|
+
kind: merged.kind,
|
|
242
|
+
memoryMdPreview: truncateForDebug(stamped),
|
|
243
|
+
summaryMdPreview: truncateForDebug(summaryMd || ''),
|
|
244
|
+
memoryMdLength: (stamped || '').length,
|
|
245
|
+
summaryMdLength: (summaryMd || '').length,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
232
248
|
return { target: merged.target, kind: merged.kind, batches: batchesUsed };
|
|
233
249
|
}
|
|
234
250
|
|
|
@@ -246,3 +262,23 @@ function scopeRelDir(scope) {
|
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
function oneLine(s) { return String(s || '').replace(/\s+/g, ' ').trim().slice(0, 200); }
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Per-field truncation cap for debug previews emitted on `apply/done`.
|
|
268
|
+
* Keep this small — the dream panel only needs a recognisable snippet.
|
|
269
|
+
* Total worst-case payload is `PREVIEW_MAX * 2 * targets_per_run` per
|
|
270
|
+
* dream pass; with N=50 targets that's ~200 KB. The full bytes are on
|
|
271
|
+
* disk under <root>/<scope>/{memory,summary}.md anyway — these previews
|
|
272
|
+
* are for at-a-glance debugging only.
|
|
273
|
+
*/
|
|
274
|
+
const PREVIEW_MAX = 2048;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Truncate a markdown blob for inclusion in a debug-panel cell. Adds a
|
|
278
|
+
* "…(+N chars)" marker so the user knows it was cut.
|
|
279
|
+
*/
|
|
280
|
+
function truncateForDebug(s, max = PREVIEW_MAX) {
|
|
281
|
+
const str = String(s || '');
|
|
282
|
+
if (str.length <= max) return str;
|
|
283
|
+
return str.slice(0, max) + `…(+${str.length - max} chars)`;
|
|
284
|
+
}
|
|
@@ -7,6 +7,35 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Memory v2 is the only path. The legacy `config.memoryV2` opt-out flag was
|
|
9
9
|
* retired (task-710) — the wiring is unconditional.
|
|
10
|
+
*
|
|
11
|
+
* ─────────────────────────────────────────────────────────────────────
|
|
12
|
+
* Wire contract — DreamEvent (consumed by web/components/UnifyDebugPanel.js)
|
|
13
|
+
* ─────────────────────────────────────────────────────────────────────
|
|
14
|
+
* The events persisted to trace_events with these event_type values are
|
|
15
|
+
* load-bearing for the debug panel. Renaming a field on this side
|
|
16
|
+
* silently degrades that UI to a generic JSON dump.
|
|
17
|
+
*
|
|
18
|
+
* dream_turn_open: { type:'turn_open', turnId, userPrompt, vpId, groupId, at }
|
|
19
|
+
* dream_loop: { type:'loop', turnId, loopNumber, pass, model,
|
|
20
|
+
* systemPrompt: string,
|
|
21
|
+
* messages: [{ role:'user', content:string }],
|
|
22
|
+
* response: string,
|
|
23
|
+
* toolCalls: [], usage: { inputTokens, outputTokens, totalTokens },
|
|
24
|
+
* latencyMs, ttfbMs, stopReason, rawRequest, rawResponse }
|
|
25
|
+
* dream_turn_close: { type:'turn_close', turnId, totalMs, totalTokens, loopCount,
|
|
26
|
+
* metrics: { llmCallCount, inputTokens, outputTokens,
|
|
27
|
+
* totalTokens, durationMs, passBreakdown:{[pass]:{
|
|
28
|
+
* llmCallCount, inputTokens, outputTokens,
|
|
29
|
+
* totalTokens, durationMs }} } }
|
|
30
|
+
* dream_run: { type:'dream_run', turnId, phase:'result',
|
|
31
|
+
* status:'done'|'error', metrics, resultSummary:{ groups,
|
|
32
|
+
* targets, error, skipped, skippedReason } }
|
|
33
|
+
* dream_progress: runner-emitted phase events (`start`/`load-diff`/`triage`/
|
|
34
|
+
* `merge`/`apply`/`done`). The `apply/done` variant carries
|
|
35
|
+
* `kind, memoryMdPreview, summaryMdPreview, memoryMdLength,
|
|
36
|
+
* summaryMdLength` (see apply.js).
|
|
37
|
+
*
|
|
38
|
+
* `groupId` may be inherited via `stampDreamScope()` when a scope is active.
|
|
10
39
|
*/
|
|
11
40
|
|
|
12
41
|
import { join } from 'path';
|
package/unify/groups/pre-flow.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { runPreflow as runFtsPreflow } from '../memory/preflow.js';
|
|
26
|
-
import {
|
|
26
|
+
import { resolveFallbackVp, resolveMemberId } from './roster.js';
|
|
27
27
|
|
|
28
28
|
/** Matches `@vp-id` where id is [A-Za-z0-9_-]+. Captures the id. */
|
|
29
29
|
const MENTION_RE = /(^|\s)@([A-Za-z0-9_][A-Za-z0-9_-]*)/g;
|
|
@@ -115,15 +115,16 @@ export function selectRespondingVps(input) {
|
|
|
115
115
|
const dispatched = [];
|
|
116
116
|
const errors = [];
|
|
117
117
|
for (const vpId of mentions) {
|
|
118
|
-
|
|
118
|
+
const canonicalVpId = resolveMemberId(meta, vpId);
|
|
119
|
+
if (!canonicalVpId) {
|
|
119
120
|
errors.push({ vpId, error: 'not_in_roster' });
|
|
120
121
|
continue;
|
|
121
122
|
}
|
|
122
|
-
if (taskMembers && !taskMembers.includes(
|
|
123
|
+
if (taskMembers && !taskMembers.includes(canonicalVpId)) {
|
|
123
124
|
errors.push({ vpId, error: 'not_in_task_members' });
|
|
124
125
|
continue;
|
|
125
126
|
}
|
|
126
|
-
dispatched.push(
|
|
127
|
+
if (!dispatched.includes(canonicalVpId)) dispatched.push(canonicalVpId);
|
|
127
128
|
}
|
|
128
129
|
return { dispatched, fallback: null, errors, reason: 'mention' };
|
|
129
130
|
}
|
package/unify/groups/roster.js
CHANGED
|
@@ -55,6 +55,33 @@ export function isMember(meta, vpId) {
|
|
|
55
55
|
return meta.roster.includes(vpId);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a user/tool supplied VP target to its canonical roster id.
|
|
60
|
+
*
|
|
61
|
+
* The persisted roster stores canonical VP ids such as `linus`, while UI
|
|
62
|
+
* mentions and route_forward callers may still send the display-style
|
|
63
|
+
* `vp-linus` alias. Prefer exact roster ids first so a real `vp-foo` member
|
|
64
|
+
* is never collapsed to `foo`.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} meta
|
|
67
|
+
* @param {string} target
|
|
68
|
+
* @returns {string|null}
|
|
69
|
+
*/
|
|
70
|
+
export function resolveMemberId(meta, target) {
|
|
71
|
+
if (!meta || !Array.isArray(meta.roster) || typeof target !== 'string') {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (meta.roster.includes(target)) return target;
|
|
75
|
+
|
|
76
|
+
const prefix = 'vp-';
|
|
77
|
+
if (target.startsWith(prefix)) {
|
|
78
|
+
const unprefixed = target.slice(prefix.length);
|
|
79
|
+
if (meta.roster.includes(unprefixed)) return unprefixed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
58
85
|
/**
|
|
59
86
|
* Resolve which VP should answer a message with no explicit @-mention.
|
|
60
87
|
* Per architecture G2: defaultVpId if set, else roster[0], else null.
|
package/unify/routing/router.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* (d) Tool schema uses defineTool (agent/unify/tools/types.js).
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import {
|
|
28
|
+
import { resolveMemberId } from '../groups/roster.js';
|
|
29
29
|
import { createLoopGuard, extendCausedBy } from './loop-guard.js';
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -86,19 +86,20 @@ export function createRouter(deps = {}) {
|
|
|
86
86
|
if (typeof text !== 'string' || text.length === 0) {
|
|
87
87
|
return { ok: false, error: 'text_required' };
|
|
88
88
|
}
|
|
89
|
-
if (to === from) {
|
|
90
|
-
return { ok: false, error: 'self_forward_rejected' };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
89
|
const meta = coordinator.group.getMeta();
|
|
94
90
|
if (!meta) return { ok: false, error: 'group_not_initialised' };
|
|
95
91
|
|
|
96
92
|
// Roster membership — `all` is reserved broadcast sentinel handled by
|
|
97
|
-
// coordinator; anything else must
|
|
98
|
-
// a VP-friendly error before hitting Coordinator.
|
|
99
|
-
|
|
93
|
+
// coordinator; anything else must resolve to a real member so we fail fast
|
|
94
|
+
// with a VP-friendly error before hitting Coordinator. `vp-<id>` is a
|
|
95
|
+
// tolerated UI/tool alias for canonical roster ids such as `linus`.
|
|
96
|
+
const targetVpId = to === 'all' ? 'all' : resolveMemberId(meta, to);
|
|
97
|
+
if (targetVpId !== 'all' && !targetVpId) {
|
|
100
98
|
return { ok: false, error: 'target_not_in_roster' };
|
|
101
99
|
}
|
|
100
|
+
if (targetVpId === from) {
|
|
101
|
+
return { ok: false, error: 'self_forward_rejected' };
|
|
102
|
+
}
|
|
102
103
|
|
|
103
104
|
// Build the causedBy chain BEFORE constructing the synthetic user-like
|
|
104
105
|
// message. We don't know the new msgId yet (coordinator mints it on
|
|
@@ -110,7 +111,7 @@ export function createRouter(deps = {}) {
|
|
|
110
111
|
// Loop guard: for broadcast, use 'all' as the target key so one VP
|
|
111
112
|
// spamming @all still gets throttled even if each cycle hits different
|
|
112
113
|
// member inboxes.
|
|
113
|
-
const guardKey =
|
|
114
|
+
const guardKey = targetVpId;
|
|
114
115
|
const verdict = guard.check({
|
|
115
116
|
groupId: meta.id,
|
|
116
117
|
targetVpId: guardKey,
|
|
@@ -131,9 +132,9 @@ export function createRouter(deps = {}) {
|
|
|
131
132
|
// stamp + `synthetic` marker let Coordinator's `selectRespondingVps`
|
|
132
133
|
// still treat this like a routed turn (target VPs need to respond) even
|
|
133
134
|
// though role is now 'assistant'.
|
|
134
|
-
const injectText =
|
|
135
|
+
const injectText = targetVpId === 'all'
|
|
135
136
|
? `@all ${text}`
|
|
136
|
-
: `@${
|
|
137
|
+
: `@${targetVpId} ${text}`;
|
|
137
138
|
|
|
138
139
|
const report = coordinator.ingest(
|
|
139
140
|
{
|