@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.
Files changed (48) hide show
  1. package/connection/message-router.js +20 -13
  2. package/package.json +1 -1
  3. package/providers/copilot-models.js +105 -89
  4. package/providers/copilot.js +3 -1
  5. package/yeaft/attachments.js +2 -2
  6. package/yeaft/cli.js +13 -13
  7. package/yeaft/compact/compactor.js +20 -20
  8. package/yeaft/conversation/persist.js +95 -95
  9. package/yeaft/debug-trace.js +12 -12
  10. package/yeaft/dream-v2/apply.js +15 -15
  11. package/yeaft/dream-v2/merge.js +12 -12
  12. package/yeaft/dream-v2/prompts/{extract-group.md → extract-session.md} +12 -12
  13. package/yeaft/dream-v2/prompts/index.js +3 -3
  14. package/yeaft/dream-v2/prompts/triage-pass1.md +1 -1
  15. package/yeaft/dream-v2/runner.js +37 -37
  16. package/yeaft/dream-v2/segment.js +3 -3
  17. package/yeaft/dream-v2/session-wiring.js +22 -22
  18. package/yeaft/dream-v2/state.js +7 -7
  19. package/yeaft/dream-v2/triage.js +22 -22
  20. package/yeaft/engine.js +65 -65
  21. package/yeaft/memory/ams-registry.js +22 -22
  22. package/yeaft/memory/seed-backfill.js +9 -9
  23. package/yeaft/memory/store-v2.js +27 -27
  24. package/yeaft/prompts.js +9 -9
  25. package/yeaft/routing/loop-guard.js +14 -14
  26. package/yeaft/routing/router.js +5 -5
  27. package/yeaft/session.js +5 -5
  28. package/yeaft/sessions/coordinator.js +96 -20
  29. package/yeaft/{groups → sessions}/ids.js +3 -3
  30. package/yeaft/{groups → sessions}/index.js +28 -28
  31. package/yeaft/sessions/pre-flow.js +178 -42
  32. package/yeaft/{groups → sessions}/seed-default.js +19 -19
  33. package/yeaft/{groups/group-config.js → sessions/session-config.js} +29 -29
  34. package/yeaft/{groups/group-crud.js → sessions/session-crud.js} +113 -113
  35. package/yeaft/sessions/session-store.js +85 -154
  36. package/yeaft/stop-hooks.js +4 -4
  37. package/yeaft/tools/todo-write.js +1 -1
  38. package/yeaft/tools/types.js +2 -2
  39. package/yeaft/vp/registry.js +1 -1
  40. package/yeaft/vp/vp-crud.js +1 -1
  41. package/yeaft/vp-status-broker.js +28 -28
  42. package/yeaft/web-bridge.js +411 -398
  43. package/yeaft/groups/coordinator.js +0 -221
  44. package/yeaft/groups/group-store.js +0 -212
  45. package/yeaft/groups/pre-flow.js +0 -329
  46. /package/yeaft/{groups → sessions}/feature-flag.js +0 -0
  47. /package/yeaft/{groups → sessions}/project-doc.js +0 -0
  48. /package/yeaft/{groups → sessions}/roster.js +0 -0
@@ -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