@yeaft/webchat-agent 0.1.873 → 0.1.875
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 +20 -13
- package/package.json +1 -1
- package/providers/copilot-models.js +105 -89
- package/providers/copilot.js +3 -1
- package/yeaft/attachments.js +2 -2
- package/yeaft/cli.js +13 -13
- package/yeaft/compact/compactor.js +20 -20
- package/yeaft/conversation/persist.js +95 -95
- package/yeaft/debug-trace.js +12 -12
- package/yeaft/dream-v2/apply.js +15 -15
- package/yeaft/dream-v2/merge.js +12 -12
- package/yeaft/dream-v2/prompts/{extract-group.md → extract-session.md} +12 -12
- package/yeaft/dream-v2/prompts/index.js +3 -3
- package/yeaft/dream-v2/prompts/triage-pass1.md +1 -1
- package/yeaft/dream-v2/runner.js +37 -37
- package/yeaft/dream-v2/segment.js +3 -3
- package/yeaft/dream-v2/session-wiring.js +22 -22
- package/yeaft/dream-v2/state.js +7 -7
- package/yeaft/dream-v2/triage.js +22 -22
- package/yeaft/engine.js +65 -65
- package/yeaft/memory/ams-registry.js +22 -22
- package/yeaft/memory/seed-backfill.js +9 -9
- package/yeaft/memory/store-v2.js +27 -27
- package/yeaft/prompts.js +9 -9
- package/yeaft/routing/loop-guard.js +14 -14
- package/yeaft/routing/router.js +5 -5
- package/yeaft/session.js +5 -5
- package/yeaft/sessions/coordinator.js +96 -20
- package/yeaft/{groups → sessions}/ids.js +3 -3
- package/yeaft/{groups → sessions}/index.js +28 -28
- package/yeaft/sessions/pre-flow.js +178 -42
- package/yeaft/{groups → sessions}/seed-default.js +19 -19
- package/yeaft/{groups/group-config.js → sessions/session-config.js} +29 -29
- package/yeaft/{groups/group-crud.js → sessions/session-crud.js} +113 -113
- package/yeaft/sessions/session-store.js +85 -154
- package/yeaft/stop-hooks.js +4 -4
- package/yeaft/tools/todo-write.js +1 -1
- package/yeaft/tools/types.js +2 -2
- package/yeaft/vp/registry.js +1 -1
- package/yeaft/vp/vp-crud.js +1 -1
- package/yeaft/vp-status-broker.js +28 -28
- package/yeaft/web-bridge.js +411 -398
- package/yeaft/groups/coordinator.js +0 -221
- package/yeaft/groups/group-store.js +0 -212
- package/yeaft/groups/pre-flow.js +0 -329
- /package/yeaft/{groups → sessions}/feature-flag.js +0 -0
- /package/yeaft/{groups → sessions}/project-doc.js +0 -0
- /package/yeaft/{groups → sessions}/roster.js +0 -0
package/yeaft/groups/pre-flow.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* groups/pre-flow.js — explicit pre-flow stage for Yeaft.
|
|
3
|
-
*
|
|
4
|
-
* Pre-flow is the "before any VP runs" stage. It owns:
|
|
5
|
-
*
|
|
6
|
-
* (1) VP selection — which VP(s) respond to this user turn?
|
|
7
|
-
* Pure function `selectRespondingVps({meta, fromUser, mentions,
|
|
8
|
-
* sender, taskMembers, fanOutCap})` that mirrors the legacy
|
|
9
|
-
* coordinator dispatch matrix: mention → broadcast → fallback to
|
|
10
|
-
* defaultVpId, with VP-authored messages routed via the explicit
|
|
11
|
-
* route_forward tool instead of free-text @-mentions.
|
|
12
|
-
*
|
|
13
|
-
* (2) Memory recall — what memory gets pre-injected into each
|
|
14
|
-
* responding VP's prompt? Thin wrapper around
|
|
15
|
-
* memory/preflow.js's FTS5 recall.
|
|
16
|
-
*
|
|
17
|
-
* Commit C will flip the caller (web-bridge.js) to fan out responding
|
|
18
|
-
* VPs in parallel via Promise.all.
|
|
19
|
-
*
|
|
20
|
-
* Why one module: a single import surface for the full pre-flow stage,
|
|
21
|
-
* a stable seam for the engine, and a place to format FTS hits into the
|
|
22
|
-
* {profile, entries, formatted} shape the engine already consumes.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { runPreflow as runFtsPreflow } from '../memory/preflow.js';
|
|
26
|
-
import { resolveFallbackVp, resolveMemberId } from './roster.js';
|
|
27
|
-
|
|
28
|
-
/** Matches `@vp-id` where id is [A-Za-z0-9_-]+. Captures the id. */
|
|
29
|
-
const MENTION_RE = /(^|\s)@([A-Za-z0-9_][A-Za-z0-9_-]*)/g;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Extract an ordered, unique list of @-mentions from a text string.
|
|
33
|
-
* Recognises the literal token `@all` as broadcast.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} text
|
|
36
|
-
* @returns {string[]}
|
|
37
|
-
*/
|
|
38
|
-
export function parseMentions(text) {
|
|
39
|
-
if (!text || typeof text !== 'string') return [];
|
|
40
|
-
const out = [];
|
|
41
|
-
const seen = new Set();
|
|
42
|
-
MENTION_RE.lastIndex = 0;
|
|
43
|
-
let m;
|
|
44
|
-
while ((m = MENTION_RE.exec(text))) {
|
|
45
|
-
const id = m[2];
|
|
46
|
-
if (seen.has(id)) continue;
|
|
47
|
-
seen.add(id);
|
|
48
|
-
out.push(id);
|
|
49
|
-
}
|
|
50
|
-
return out;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @typedef {object} SelectionInput
|
|
55
|
-
* @property {object} meta GroupHandle meta (roster + defaultVpId)
|
|
56
|
-
* @property {boolean} fromUser true = user-authored; false = VP-authored
|
|
57
|
-
* @property {string[]} mentions Already-parsed @-mentions
|
|
58
|
-
* @property {string=} sender VP id when fromUser=false
|
|
59
|
-
* @property {number} [fanOutCap=16]
|
|
60
|
-
* @property {string[]=} taskMembers When set, restricts dispatch to this list
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @typedef {object} SelectionResult
|
|
65
|
-
* @property {string[]} dispatched VP ids that should respond
|
|
66
|
-
* @property {string|null} fallback The fallback vp, if any
|
|
67
|
-
* @property {Array<{vpId?:string,error:string}>} errors
|
|
68
|
-
* @property {'mention'|'broadcast'|'fallback'|'vp-author-no-text-routing'|'no-default'} reason
|
|
69
|
-
* @property {boolean=} truncatedAtFanOutCap
|
|
70
|
-
*/
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Pure VP-selection step of pre-flow. Returns ids only — caller owns
|
|
74
|
-
* persistence + envelope construction + deliver().
|
|
75
|
-
*
|
|
76
|
-
* @param {SelectionInput} input
|
|
77
|
-
* @returns {SelectionResult}
|
|
78
|
-
*/
|
|
79
|
-
export function selectRespondingVps(input) {
|
|
80
|
-
const meta = input.meta;
|
|
81
|
-
if (!meta) {
|
|
82
|
-
return { dispatched: [], fallback: null, errors: [{ error: 'no_group_meta' }], reason: 'no-default' };
|
|
83
|
-
}
|
|
84
|
-
const fanOutCap = Number.isFinite(input.fanOutCap) ? input.fanOutCap : 16;
|
|
85
|
-
const taskMembers = Array.isArray(input.taskMembers) ? input.taskMembers : null;
|
|
86
|
-
const mentions = Array.isArray(input.mentions) ? input.mentions : [];
|
|
87
|
-
|
|
88
|
-
// VP-authored messages: never auto-route through @-mentions; VPs hand
|
|
89
|
-
// off through the explicit route_forward tool instead.
|
|
90
|
-
if (!input.fromUser) {
|
|
91
|
-
return {
|
|
92
|
-
dispatched: [],
|
|
93
|
-
fallback: null,
|
|
94
|
-
errors: [],
|
|
95
|
-
reason: 'vp-author-no-text-routing',
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// @all broadcast — fan out to every roster member except the sender,
|
|
100
|
-
// honouring fanOutCap and taskMembers.
|
|
101
|
-
if (mentions.includes('all')) {
|
|
102
|
-
const roster = meta.roster.filter((v) => v !== input.sender).slice(0, fanOutCap);
|
|
103
|
-
const scoped = taskMembers ? roster.filter((v) => taskMembers.includes(v)) : roster;
|
|
104
|
-
return {
|
|
105
|
-
dispatched: scoped,
|
|
106
|
-
fallback: null,
|
|
107
|
-
errors: [],
|
|
108
|
-
reason: 'broadcast',
|
|
109
|
-
truncatedAtFanOutCap: meta.roster.length - 1 > fanOutCap,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Explicit @-mentions
|
|
114
|
-
if (mentions.length > 0) {
|
|
115
|
-
const dispatched = [];
|
|
116
|
-
const errors = [];
|
|
117
|
-
for (const vpId of mentions) {
|
|
118
|
-
const canonicalVpId = resolveMemberId(meta, vpId);
|
|
119
|
-
if (!canonicalVpId) {
|
|
120
|
-
errors.push({ vpId, error: 'not_in_roster' });
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (taskMembers && !taskMembers.includes(canonicalVpId)) {
|
|
124
|
-
errors.push({ vpId, error: 'not_in_task_members' });
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (!dispatched.includes(canonicalVpId)) dispatched.push(canonicalVpId);
|
|
128
|
-
}
|
|
129
|
-
return { dispatched, fallback: null, errors, reason: 'mention' };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// No @-mention → fallback to defaultVpId (architecture G2)
|
|
133
|
-
const fallback = resolveFallbackVp(meta);
|
|
134
|
-
if (!fallback) {
|
|
135
|
-
return {
|
|
136
|
-
dispatched: [],
|
|
137
|
-
fallback: null,
|
|
138
|
-
errors: [{ error: 'no_default_vp' }],
|
|
139
|
-
reason: 'no-default',
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
if (taskMembers && !taskMembers.includes(fallback)) {
|
|
143
|
-
return {
|
|
144
|
-
dispatched: [],
|
|
145
|
-
fallback: null,
|
|
146
|
-
errors: [{ vpId: fallback, error: 'not_in_task_members' }],
|
|
147
|
-
reason: 'no-default',
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
dispatched: [fallback],
|
|
152
|
-
fallback,
|
|
153
|
-
errors: [],
|
|
154
|
-
reason: 'fallback',
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Build the heading for a single scope's formatted memory block.
|
|
161
|
-
*
|
|
162
|
-
* Heading style is the original recall-v2 format, kept so the system
|
|
163
|
-
* prompt the LLM sees stays stable across the FTS migration.
|
|
164
|
-
*
|
|
165
|
-
* @param {string} scope
|
|
166
|
-
* @returns {string}
|
|
167
|
-
*/
|
|
168
|
-
function scopeHeading(scope) {
|
|
169
|
-
if (scope === 'user') return '## Memory: User';
|
|
170
|
-
// Nested chat scopes first.
|
|
171
|
-
let m = /^chat\/([^/]+)\/vp\/(.+)$/.exec(scope);
|
|
172
|
-
if (m) return `## Memory: VP ${m[2]}`;
|
|
173
|
-
m = /^chat\/([^/]+)$/.exec(scope);
|
|
174
|
-
if (m) return `## Memory: Chat ${m[1]}`;
|
|
175
|
-
// Nested group scopes.
|
|
176
|
-
m = /^group\/([^/]+)\/vp\/(.+)$/.exec(scope);
|
|
177
|
-
if (m) return `## Memory: VP ${m[2]}`;
|
|
178
|
-
m = /^group\/([^/]+)\/user$/.exec(scope);
|
|
179
|
-
if (m) return `## Memory: Group ${m[1]} (user)`;
|
|
180
|
-
m = /^group\/([^/]+)\/feature\/(.+)$/.exec(scope);
|
|
181
|
-
if (m) return `## Memory: Feature ${m[2]}`;
|
|
182
|
-
m = /^group\/([^/]+)\/topic\/(.+)$/.exec(scope);
|
|
183
|
-
if (m) return `## Memory: Topic ${m[2]}`;
|
|
184
|
-
if (scope.startsWith('group/')) return `## Memory: Group ${scope.slice(6)}`;
|
|
185
|
-
if (scope.startsWith('vp/')) return `## Memory: VP ${scope.slice(3)}`;
|
|
186
|
-
if (scope.startsWith('feature/')) return `## Memory: Feature ${scope.slice(8)}`;
|
|
187
|
-
if (scope.startsWith('topic/')) return `## Memory: Topic ${scope.slice(6)}`;
|
|
188
|
-
return `## Memory: ${scope}`;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Format FTS picked segments into the prompt-ready string.
|
|
193
|
-
*
|
|
194
|
-
* Picked segments are grouped by scope (preserving the FTS rerank
|
|
195
|
-
* order within each scope group), then rendered as markdown blocks
|
|
196
|
-
* with one heading per scope.
|
|
197
|
-
*
|
|
198
|
-
* @param {Array<{scope: string, body: string, tags?: string[], kind?: string}>} picked
|
|
199
|
-
* @returns {string}
|
|
200
|
-
*/
|
|
201
|
-
export function formatPickedForInjection(picked) {
|
|
202
|
-
if (!picked || picked.length === 0) return '';
|
|
203
|
-
const byScope = new Map();
|
|
204
|
-
// Preserve insertion order (which is rerank order within each scope).
|
|
205
|
-
for (const seg of picked) {
|
|
206
|
-
const scope = seg.scope || 'unknown';
|
|
207
|
-
if (!byScope.has(scope)) byScope.set(scope, []);
|
|
208
|
-
byScope.get(scope).push(seg);
|
|
209
|
-
}
|
|
210
|
-
const parts = [];
|
|
211
|
-
for (const [scope, segs] of byScope.entries()) {
|
|
212
|
-
parts.push(scopeHeading(scope));
|
|
213
|
-
for (const s of segs) {
|
|
214
|
-
const body = (s.body || '').trim();
|
|
215
|
-
if (body) parts.push(body);
|
|
216
|
-
}
|
|
217
|
-
parts.push(''); // blank line between scopes
|
|
218
|
-
}
|
|
219
|
-
return parts.join('\n').trim();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* @typedef {object} MemoryPreflowOptions
|
|
224
|
-
* @property {string} userMsg The user's message
|
|
225
|
-
* @property {string} [groupId] Active group, if any
|
|
226
|
-
* @property {string} [vpId] Responding VP id, if any
|
|
227
|
-
* @property {string} [featureId] Active feature, if any
|
|
228
|
-
* @property {string[]} [extraScopes] Additional scopes to include
|
|
229
|
-
* @property {string[]} [currentTags] Contextual tags for rerank
|
|
230
|
-
* @property {number} [topK] Max FTS rows fetched (default 50)
|
|
231
|
-
* @property {number} [budgetTokens] Token budget for picked segments
|
|
232
|
-
*/
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* @typedef {object} MemoryPreflowResult
|
|
236
|
-
* @property {string} profile User-scope summary (best-effort)
|
|
237
|
-
* @property {object[]} entries Picked segments (raw)
|
|
238
|
-
* @property {string} formatted Prompt-ready string
|
|
239
|
-
* @property {object} meta Raw FTS preflow metadata
|
|
240
|
-
*/
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Build the canonical scope list for a given (groupId, vpId).
|
|
244
|
-
* Always includes 'user'. The order is significant — preflow.js's scope
|
|
245
|
-
* filter accepts/rejects by membership, and the formatter renders in
|
|
246
|
-
* order.
|
|
247
|
-
*
|
|
248
|
-
* (2026-05-13: `featureId` scope dropped along with the Feature system.)
|
|
249
|
-
*
|
|
250
|
-
* @param {{groupId?: string, vpId?: string, extra?: string[]}} ctx
|
|
251
|
-
* @returns {string[]}
|
|
252
|
-
*/
|
|
253
|
-
export function buildRelevantScopes({ groupId, chatId, vpId, extra } = {}) {
|
|
254
|
-
const scopes = ['user'];
|
|
255
|
-
if (chatId) {
|
|
256
|
-
scopes.push(`chat/${chatId}`);
|
|
257
|
-
if (vpId) scopes.push(`chat/${chatId}/vp/${vpId}`);
|
|
258
|
-
} else if (groupId) {
|
|
259
|
-
scopes.push(`group/${groupId}`);
|
|
260
|
-
scopes.push(`group/${groupId}/user`);
|
|
261
|
-
if (vpId) scopes.push(`group/${groupId}/vp/${vpId}`);
|
|
262
|
-
}
|
|
263
|
-
if (Array.isArray(extra)) {
|
|
264
|
-
for (const s of extra) {
|
|
265
|
-
if (s && !scopes.includes(s)) scopes.push(s);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return scopes;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Run memory pre-flow for one VP turn. Thin wrapper around
|
|
273
|
-
* `memory/preflow.js::runPreflow` that:
|
|
274
|
-
*
|
|
275
|
-
* - resolves canonical scope list from {groupId, vpId, featureId},
|
|
276
|
-
* - invokes FTS5 recall,
|
|
277
|
-
* - formats picked segments for prompt injection.
|
|
278
|
-
*
|
|
279
|
-
* Returns the engine-consumable {profile, entries, formatted, meta}
|
|
280
|
-
* shape so the existing recall pipeline can swap in without changes.
|
|
281
|
-
*
|
|
282
|
-
* @param {import('../memory/index-db.js').SegmentIndex} index
|
|
283
|
-
* @param {MemoryPreflowOptions} opts
|
|
284
|
-
* @returns {MemoryPreflowResult}
|
|
285
|
-
*/
|
|
286
|
-
export function runMemoryPreflow(index, opts) {
|
|
287
|
-
if (!index) {
|
|
288
|
-
return { profile: '', entries: [], formatted: '', meta: { skipped: 'no-index' } };
|
|
289
|
-
}
|
|
290
|
-
const userMsg = (opts?.userMsg || '').trim();
|
|
291
|
-
if (!userMsg) {
|
|
292
|
-
return { profile: '', entries: [], formatted: '', meta: { skipped: 'no-user-msg' } };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const relevantScopes = buildRelevantScopes({
|
|
296
|
-
groupId: opts.groupId,
|
|
297
|
-
chatId: opts.chatId,
|
|
298
|
-
vpId: opts.vpId,
|
|
299
|
-
extra: opts.extraScopes,
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
const result = runFtsPreflow(index, {
|
|
303
|
-
userMsg,
|
|
304
|
-
relevantScopes,
|
|
305
|
-
ownVpId: opts.vpId || null,
|
|
306
|
-
currentTags: opts.currentTags || [],
|
|
307
|
-
topK: opts.topK,
|
|
308
|
-
budgetTokens: opts.budgetTokens,
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
// Best-effort profile: pick any user-scope segment body.
|
|
312
|
-
const userSeg = (result.picked || []).find(p => p.scope === 'user');
|
|
313
|
-
const profile = userSeg ? (userSeg.body || '').trim() : '';
|
|
314
|
-
|
|
315
|
-
const formatted = formatPickedForInjection(result.picked || []);
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
profile,
|
|
319
|
-
entries: result.picked || [],
|
|
320
|
-
formatted,
|
|
321
|
-
meta: {
|
|
322
|
-
keywords: result.keywords,
|
|
323
|
-
ftsQuery: result.ftsQuery,
|
|
324
|
-
pickedTokens: result.pickedTokens,
|
|
325
|
-
droppedCount: result.droppedCount,
|
|
326
|
-
hitCount: (result.hits || []).length,
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|