@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
@@ -50,15 +50,15 @@ function triageSystem(language) {
50
50
  * structure of the diff.
51
51
  *
52
52
  * Inputs:
53
- * - groupId: the active group ('_no-group' is allowed and skips the
53
+ * - sessionId: the active group ('_no-session' is allowed and skips the
54
54
  * `group/<id>` entry — by convention the virtual group has no scope
55
55
  * of its own).
56
56
  * - messages: the diff (already overlap-prefixed if applicable).
57
57
  *
58
- * @param {{ groupId: string, messages: Array<object> }} args
58
+ * @param {{ sessionId: string, messages: Array<object> }} args
59
59
  * @returns {Array<{ kind: 'update', scope: string }>}
60
60
  */
61
- export function applyHardRules({ groupId, chatId, messages }) {
61
+ export function applyHardRules({ sessionId, chatId, messages }) {
62
62
  const out = new Map();
63
63
  const add = (scope) => { if (!out.has(scope)) out.set(scope, { kind: 'update', scope }); };
64
64
 
@@ -81,9 +81,9 @@ export function applyHardRules({ groupId, chatId, messages }) {
81
81
  }
82
82
 
83
83
  // active group + its per-group user layer, except the virtual _no-group bucket.
84
- if (groupId && groupId !== '_no-group') {
85
- add(`group/${groupId}`);
86
- add(`group/${groupId}/user`);
84
+ if (sessionId && sessionId !== '_no-session') {
85
+ add(`group/${sessionId}`);
86
+ add(`group/${sessionId}/user`);
87
87
  }
88
88
 
89
89
  for (const m of (messages || [])) {
@@ -91,8 +91,8 @@ export function applyHardRules({ groupId, chatId, messages }) {
91
91
  // Active VP: any assistant message's vpId — now group-internal.
92
92
  if (m.role === 'assistant') {
93
93
  const vp = m.vpId || (m.author && /^vp:(.+)$/.exec(m.author)?.[1]);
94
- if (vp && /^[A-Za-z0-9_\-.一-鿿]+$/.test(vp) && groupId && groupId !== '_no-group') {
95
- add(`group/${groupId}/vp/${vp}`);
94
+ if (vp && /^[A-Za-z0-9_\-.一-鿿]+$/.test(vp) && sessionId && sessionId !== '_no-session') {
95
+ add(`group/${sessionId}/vp/${vp}`);
96
96
  }
97
97
  }
98
98
  }
@@ -105,7 +105,7 @@ export function applyHardRules({ groupId, chatId, messages }) {
105
105
  /**
106
106
  * Build the prompt used for Pass-1.
107
107
  *
108
- * @param {{ groupId: string, messages: Array<object>, topicSummaries: Array<{ path: string, summary: string }> }} ctx
108
+ * @param {{ sessionId: string, messages: Array<object>, topicSummaries: Array<{ path: string, summary: string }> }} ctx
109
109
  */
110
110
  export function buildPass1Prompt(ctx) {
111
111
  const topicSummaries = (!ctx.topicSummaries || ctx.topicSummaries.length === 0)
@@ -119,7 +119,7 @@ export function buildPass1Prompt(ctx) {
119
119
  conv.push('');
120
120
  }
121
121
  return render('triagePass1', {
122
- groupId: ctx.groupId,
122
+ sessionId: ctx.sessionId,
123
123
  topicSummaries,
124
124
  conversation: conv.join('\n').trimEnd(),
125
125
  }, { language: ctx.language });
@@ -144,16 +144,16 @@ export function buildPass2Prompt(ctx) {
144
144
  * Run soft classification for one segment of one group's diff.
145
145
  *
146
146
  * @param {{
147
- * groupId: string,
147
+ * sessionId: string,
148
148
  * messages: Array<object>,
149
149
  * topicSummaries: Array<{ path: string, summary: string }>,
150
150
  * llm: (req: { pass: string, prompt: string, system: string }) => Promise<string>,
151
151
  * }} args
152
152
  * @returns {Promise<Array<{ kind: 'update'|'create', scope: string }>>}
153
153
  */
154
- export async function classifySoft({ groupId, messages, topicSummaries, llm, language }) {
154
+ export async function classifySoft({ sessionId, messages, topicSummaries, llm, language }) {
155
155
  if (!llm) throw new Error('triage.classifySoft: llm callable required');
156
- const pass1Prompt = buildPass1Prompt({ groupId, messages, topicSummaries, language });
156
+ const pass1Prompt = buildPass1Prompt({ sessionId, messages, topicSummaries, language });
157
157
  const pass1Raw = await llm({ pass: 'triage-pass1', prompt: pass1Prompt, system: triageSystem(language) });
158
158
  const pass1 = parseJsonSafe(pass1Raw);
159
159
  const out = [];
@@ -180,9 +180,9 @@ export async function classifySoft({ groupId, messages, topicSummaries, llm, lan
180
180
  const path = String(pass2.path || '').trim();
181
181
  if (!path) continue;
182
182
  const segs = path.split('/').filter(Boolean);
183
- if (!groupId || groupId === '_no-group') continue;
184
- if (!isValidTopic({ kind: 'group-topic', groupId, path: segs })) continue;
185
- const scope = `group/${groupId}/topic/${segs.join('/')}`;
183
+ if (!sessionId || sessionId === '_no-session') continue;
184
+ if (!isValidTopic({ kind: 'group-topic', sessionId, path: segs })) continue;
185
+ const scope = `group/${sessionId}/topic/${segs.join('/')}`;
186
186
  if (pass2.decision === 'match') {
187
187
  out.push({ kind: 'update', scope });
188
188
  } else if (pass2.decision === 'new') {
@@ -197,7 +197,7 @@ export async function classifySoft({ groupId, messages, topicSummaries, llm, lan
197
197
  * Dedupes by scope — `update` wins if any source said update.
198
198
  *
199
199
  * @param {{
200
- * groupId: string,
200
+ * sessionId: string,
201
201
  * messages: Array<object>,
202
202
  * topicSummaries: Array<{ path: string, summary: string }>,
203
203
  * llm: (req: { pass: string, prompt: string, system: string }) => Promise<string>,
@@ -205,7 +205,7 @@ export async function classifySoft({ groupId, messages, topicSummaries, llm, lan
205
205
  * @returns {Promise<Array<{ kind: 'update'|'create', scope: string }>>}
206
206
  */
207
207
  export async function triageOneSegment(args) {
208
- const hard = applyHardRules({ groupId: args.groupId, messages: args.messages });
208
+ const hard = applyHardRules({ sessionId: args.sessionId, messages: args.messages });
209
209
  const soft = await classifySoft(args);
210
210
  return dedupeActions([...hard, ...soft]);
211
211
  }
@@ -215,7 +215,7 @@ export async function triageOneSegment(args) {
215
215
  * Runs each segment serially, accumulates and dedupes actions.
216
216
  *
217
217
  * @param {{
218
- * groupId: string,
218
+ * sessionId: string,
219
219
  * segments: Array<{ messages: Array<object> }>,
220
220
  * topicSummaries: Array<{ path: string, summary: string }>,
221
221
  * llm: (req: { pass: string, prompt: string, system: string }) => Promise<string>,
@@ -223,14 +223,14 @@ export async function triageOneSegment(args) {
223
223
  * }} args
224
224
  * @returns {Promise<Array<{ kind: 'update'|'create', scope: string }>>}
225
225
  */
226
- export async function triageGroupSegments({ groupId, segments, topicSummaries, llm, onProgress, language }) {
226
+ export async function triageGroupSegments({ sessionId, segments, topicSummaries, llm, onProgress, language }) {
227
227
  let acc = [];
228
228
  let i = 0;
229
229
  for (const seg of (segments || [])) {
230
230
  i += 1;
231
- if (onProgress) onProgress({ phase: 'triage', groupId, segment: i, total: segments.length });
231
+ if (onProgress) onProgress({ phase: 'triage', sessionId, segment: i, total: segments.length });
232
232
  const segActions = await triageOneSegment({
233
- groupId,
233
+ sessionId,
234
234
  messages: seg.messages,
235
235
  topicSummaries,
236
236
  llm,
package/yeaft/engine.js CHANGED
@@ -20,8 +20,8 @@
20
20
  import { randomUUID } from 'crypto';
21
21
  import { buildSystemPrompt, buildWorkerPrompt } from './prompts.js';
22
22
  import { LLMContextError, LLMAbortError } from './llm/adapter.js';
23
- import { runMemoryPreflow, buildRelevantScopes } from './groups/pre-flow.js';
24
- import { readProjectDoc, pickProjectDocFile, DEFAULT_PROJECT_DOC_MAX_BYTES } from './groups/project-doc.js';
23
+ import { runMemoryPreflow, buildRelevantScopes } from './sessions/pre-flow.js';
24
+ import { readProjectDoc, pickProjectDocFile, DEFAULT_PROJECT_DOC_MAX_BYTES } from './sessions/project-doc.js';
25
25
  import { shouldConsolidate, partitionMessages } from './memory/consolidate.js';
26
26
  import { runCompact as runCompactOrchestrator } from './compact/orchestrator.js';
27
27
  import { evaluateCompactTriggers } from './compact/triggers.js';
@@ -32,7 +32,7 @@ import { runAdjust } from './memory/adjust.js';
32
32
  import { isVpSeedBackfillStub } from './memory/seed-backfill.js';
33
33
  import { runStopHooks } from './stop-hooks.js';
34
34
  // Default thread marker for legacy / non-group flows. Group VP runtime may
35
- // pass a real threadId per (groupId, vpId, threadId) engine instance.
35
+ // pass a real threadId per (sessionId, vpId, threadId) engine instance.
36
36
  const MAIN_THREAD_ID = 'main';
37
37
  import { pickEffort, parseEffortPrefix } from './effort.js';
38
38
  import { DEFAULT_CONTEXT_WINDOW, normalizeEffort, resolveContextWindow, resolveModel } from './models.js';
@@ -170,9 +170,9 @@ export function shouldAllowGroupReflection({
170
170
  messages = [],
171
171
  model = null,
172
172
  config = {},
173
- groupId = null,
173
+ sessionId = null,
174
174
  } = {}) {
175
- if (!groupId) {
175
+ if (!sessionId) {
176
176
  return {
177
177
  allowed: true,
178
178
  compactAllowed: true,
@@ -243,7 +243,7 @@ export function shouldAllowGroupReflection({
243
243
  * `#loadLayerASummaries`. Cross-VP context flows through onDemand recall.
244
244
  *
245
245
  * @param {{
246
- * groupId?: string|null,
246
+ * sessionId?: string|null,
247
247
  * ownVpId?: string|null,
248
248
  * summaries: { user?: string, group?: string, vp?: string }
249
249
  * }} args
@@ -253,8 +253,8 @@ export function buildResidentEntries(args) {
253
253
  const summaries = (args && args.summaries) || {};
254
254
  const out = [];
255
255
  if (summaries.user) out.push({ scope: 'user', summary: summaries.user });
256
- if (args.groupId && summaries.group) {
257
- out.push({ scope: `group/${args.groupId}`, summary: summaries.group });
256
+ if (args.sessionId && summaries.group) {
257
+ out.push({ scope: `group/${args.sessionId}`, summary: summaries.group });
258
258
  }
259
259
  if (args.ownVpId && summaries.vp && !isVpSeedBackfillStub(summaries.vp)) {
260
260
  out.push({ scope: `vp/${args.ownVpId}`, summary: summaries.vp });
@@ -303,7 +303,7 @@ export class Engine {
303
303
  /** @type {string|null} */
304
304
  #yeaftDir;
305
305
  /** @type {string|null} — set when this engine is bound to a specific group (per-VP fan-out path). */
306
- #groupId = null;
306
+ #sessionId = null;
307
307
  /** @type {string|null} — set when this engine is bound to a specific VP (per-VP fan-out path). */
308
308
  #vpId = null;
309
309
  /** @type {string|null} — set when this engine is bound to a chat session (Chat Mode). */
@@ -372,7 +372,7 @@ export class Engine {
372
372
 
373
373
  /**
374
374
  * Per-group "adjust has run at least once this engine lifetime" flag.
375
- * Keyed by groupId (or 'default'). The first turn always runs adjust;
375
+ * Keyed by sessionId (or 'default'). The first turn always runs adjust;
376
376
  * subsequent turns only run on budget pressure or new memory.
377
377
  * @type {Map<string, boolean>}
378
378
  */
@@ -408,7 +408,7 @@ export class Engine {
408
408
  * toolStats?: import('./stats/tool-usage.js').ToolUsageStats,
409
409
  * }} params
410
410
  */
411
- constructor({ adapter, trace, config, conversationStore, memoryIndex, amsRegistry, toolRegistry, skillManager, mcpManager, yeaftDir, toolStats = null, groupId = null, vpId = null, chatId = null }) {
411
+ constructor({ adapter, trace, config, conversationStore, memoryIndex, amsRegistry, toolRegistry, skillManager, mcpManager, yeaftDir, toolStats = null, sessionId = null, vpId = null, chatId = null }) {
412
412
  this.#adapter = adapter;
413
413
  this.#trace = trace;
414
414
  this.#config = config;
@@ -423,12 +423,12 @@ export class Engine {
423
423
  this.#yeaftDir = yeaftDir || null;
424
424
  this.#toolStats = toolStats || null;
425
425
  // Per-VP fan-out (2026-06-01): engine instances in the group path are
426
- // keyed by ${groupId}::${vpId}::${threadId}, so binding the engine to
427
- // its (groupId, vpId) pair at construction lets post-turn compact
426
+ // keyed by ${sessionId}::${vpId}::${threadId}, so binding the engine to
427
+ // its (sessionId, vpId) pair at construction lets post-turn compact
428
428
  // scope its read/write to THIS VP's view of the conversation instead
429
429
  // of clobbering a session-global compact.md. Legacy / sub-agent
430
430
  // callers leave both null → fall back to the global file.
431
- this.#groupId = (typeof groupId === 'string' && groupId) ? groupId : null;
431
+ this.#sessionId = (typeof sessionId === 'string' && sessionId) ? sessionId : null;
432
432
  this.#vpId = (typeof vpId === 'string' && vpId) ? vpId : null;
433
433
  this.#chatId = (typeof chatId === 'string' && chatId) ? chatId : null;
434
434
 
@@ -554,26 +554,26 @@ export class Engine {
554
554
  *
555
555
  * Scopes:
556
556
  * - user → `user/summary.md` (always attempted)
557
- * - group <gid> → `groups/<gid>/summary.md` (if groupId)
557
+ * - group <gid> → `groups/<gid>/summary.md` (if sessionId)
558
558
  * - vp <vpId> → `vp/<vpId>/summary.md` (if vpId)
559
559
  *
560
560
  * Each fetch is best-effort — missing files / read errors return ''. The
561
561
  * dream tick (Phase 6) is what populates these; on a fresh install they
562
562
  * all return ''.
563
563
  *
564
- * @param {{groupId?: string, vpId?: string, language?: string}} ctx
564
+ * @param {{sessionId?: string, vpId?: string, language?: string}} ctx
565
565
  * @returns {Promise<{user:string, group:string, vp:string}>}
566
566
  */
567
- async #loadLayerASummaries({ groupId, vpId, language } = {}) {
567
+ async #loadLayerASummaries({ sessionId, vpId, language } = {}) {
568
568
  if (!this.#yeaftDir) return { user: '', group: '', vp: '' };
569
569
  const memoryRoot = `${this.#yeaftDir}/memory`;
570
570
  const tasks = [
571
571
  readScopeSummary({ kind: 'user' }, { root: memoryRoot, language }).catch(() => ''),
572
- groupId
573
- ? readScopeSummary({ kind: 'group', id: groupId }, { root: memoryRoot, language }).catch(() => '')
572
+ sessionId
573
+ ? readScopeSummary({ kind: 'group', id: sessionId }, { root: memoryRoot, language }).catch(() => '')
574
574
  : Promise.resolve(''),
575
- vpId && groupId
576
- ? readScopeSummary({ kind: 'group-vp', groupId, id: vpId }, { root: memoryRoot, language }).catch(() => '')
575
+ vpId && sessionId
576
+ ? readScopeSummary({ kind: 'group-vp', sessionId, id: vpId }, { root: memoryRoot, language }).catch(() => '')
577
577
  : Promise.resolve(''),
578
578
  ];
579
579
  const [user, group, vp] = await Promise.all(tasks);
@@ -585,7 +585,7 @@ export class Engine {
585
585
  * to call when the AMS registry isn't wired (returns null).
586
586
  *
587
587
  * @param {{
588
- * groupId?: string,
588
+ * sessionId?: string,
589
589
  * ownVpId?: string|null,
590
590
  * summaries: { user?: string, group?: string, vp?: string },
591
591
  * recallEntries: object[],
@@ -600,7 +600,7 @@ export class Engine {
600
600
  */
601
601
  #prepareAms(args) {
602
602
  if (!this.#amsRegistry) return null;
603
- const groupKey = args.groupId || 'default';
603
+ const groupKey = args.sessionId || 'default';
604
604
  const ownVpId = args.ownVpId || null;
605
605
  const ams = this.#amsRegistry.getOrCreate(groupKey, { ownVpId });
606
606
 
@@ -616,7 +616,7 @@ export class Engine {
616
616
  // (a) Resident: rebuild from the same scope summaries the worker
617
617
  // prompt is already going to see.
618
618
  const residentEntries = buildResidentEntries({
619
- groupId: args.groupId,
619
+ sessionId: args.sessionId,
620
620
  ownVpId,
621
621
  summaries: args.summaries || {},
622
622
  });
@@ -630,7 +630,7 @@ export class Engine {
630
630
  const snapshotBlock = this.#renderAmsSnapshot(ams, this.#config.language || 'en');
631
631
 
632
632
  const scopes = buildRelevantScopes({
633
- groupId: args.groupId,
633
+ sessionId: args.sessionId,
634
634
  vpId: ownVpId,
635
635
  });
636
636
 
@@ -763,12 +763,12 @@ export class Engine {
763
763
  * @param {string} args.memoryInjection — prebuilt Memory block from AMS
764
764
  * @param {object} [args.vpPersona]
765
765
  * @param {object} [args.activeScope] — DESIGN-PROMPT §3 ④ structured scope summary
766
- * @param {string} [args.groupAnnouncement]
766
+ * @param {string} [args.sessionAnnouncement]
767
767
  * @param {string} [args.projectDoc] — resolved CLAUDE.md / AGENTS.md text (already truncated)
768
768
  * @param {object} [args.taskCtx] — legacy task-context sub-block (optional)
769
769
  * @returns {string}
770
770
  */
771
- #buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, groupAnnouncement, projectDoc, taskCtx } = {}) {
771
+ #buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectDoc, taskCtx } = {}) {
772
772
  // Get relevant skill content if SkillManager is wired
773
773
  let skillContent = '';
774
774
  if (this.#skillManager && prompt) {
@@ -787,7 +787,7 @@ export class Engine {
787
787
  skillContent,
788
788
  vpPersona,
789
789
  activeScope,
790
- groupAnnouncement,
790
+ sessionAnnouncement,
791
791
  projectDoc,
792
792
  taskCtx,
793
793
  // Worker-shape harness is descriptive metadata for human inspection;
@@ -965,7 +965,7 @@ export class Engine {
965
965
  * without injection.
966
966
  *
967
967
  * @param {string} prompt
968
- * @param {{ groupId?: string, vpId?: string }} [ctx]
968
+ * @param {{ sessionId?: string, vpId?: string }} [ctx]
969
969
  * @returns {Promise<{ profile: string, entries: object[], formatted: string }|null>}
970
970
  */
971
971
  async #recallMemory(prompt, ctx = {}) {
@@ -974,7 +974,7 @@ export class Engine {
974
974
  try {
975
975
  const result = runMemoryPreflow(this.#memoryIndex, {
976
976
  userMsg: prompt,
977
- groupId: ctx.groupId,
977
+ sessionId: ctx.sessionId,
978
978
  chatId: ctx.chatId || this.#chatId,
979
979
  vpId: ctx.vpId,
980
980
  });
@@ -1002,9 +1002,9 @@ export class Engine {
1002
1002
  && typeof this.#conversationStore.readCompactSummaryForChat === 'function') {
1003
1003
  return this.#conversationStore.readCompactSummaryForChat(this.#chatId, this.#vpId);
1004
1004
  }
1005
- if (this.#groupId && this.#vpId
1005
+ if (this.#sessionId && this.#vpId
1006
1006
  && typeof this.#conversationStore.readCompactSummaryFor === 'function') {
1007
- return this.#conversationStore.readCompactSummaryFor(this.#groupId, this.#vpId);
1007
+ return this.#conversationStore.readCompactSummaryFor(this.#sessionId, this.#vpId);
1008
1008
  }
1009
1009
  return this.#conversationStore.readCompactSummary();
1010
1010
  }
@@ -1022,10 +1022,10 @@ export class Engine {
1022
1022
  * @param {string} userContent
1023
1023
  * @param {string} assistantContent
1024
1024
  * @param {object[]} [toolCalls]
1025
- * @param {string} [groupId]
1025
+ * @param {string} [sessionId]
1026
1026
  * @param {boolean} [userAlreadyPersisted]
1027
1027
  */
1028
- #persistMessages(userContent, assistantContent, toolCalls, groupId, userAlreadyPersisted = false) {
1028
+ #persistMessages(userContent, assistantContent, toolCalls, sessionId, userAlreadyPersisted = false) {
1029
1029
  if (!this.#conversationStore) return;
1030
1030
  if (this.#config._readOnly) return;
1031
1031
 
@@ -1040,8 +1040,8 @@ export class Engine {
1040
1040
  role: 'user',
1041
1041
  content: userContent,
1042
1042
  threadId,
1043
- // Bug 6: stamp groupId/chatId so history replay can route by container.
1044
- ...(groupId ? { groupId } : {}),
1043
+ // Bug 6: stamp sessionId/chatId so history replay can route by container.
1044
+ ...(sessionId ? { sessionId } : {}),
1045
1045
  ...(this.#chatId ? { chatId: this.#chatId } : {}),
1046
1046
  });
1047
1047
  }
@@ -1052,7 +1052,7 @@ export class Engine {
1052
1052
  content: assistantContent,
1053
1053
  model: this.#config.model,
1054
1054
  threadId,
1055
- ...(groupId ? { groupId } : {}),
1055
+ ...(sessionId ? { sessionId } : {}),
1056
1056
  ...(this.#chatId ? { chatId: this.#chatId } : {}),
1057
1057
  };
1058
1058
  if (toolCalls && toolCalls.length > 0) {
@@ -1098,52 +1098,52 @@ export class Engine {
1098
1098
  // its context — user prompts + every VP's assistant text, with other
1099
1099
  // VPs' tool calls/results stripped (see persist.loadGroupHistoryForVp).
1100
1100
  //
1101
- // Legacy / sub-agent callers (no groupId/vpId pair) keep the global
1101
+ // Legacy / sub-agent callers (no sessionId/vpId pair) keep the global
1102
1102
  // loadAll() behaviour so we don't break those flows.
1103
1103
  let messages;
1104
1104
  const scopedChat = !!(this.#chatId && this.#vpId
1105
1105
  && typeof conversationStore.loadChatHistoryForVp === 'function');
1106
- const scoped = !scopedChat && !!(this.#groupId && this.#vpId
1106
+ const scoped = !scopedChat && !!(this.#sessionId && this.#vpId
1107
1107
  && typeof conversationStore.loadGroupHistoryForVp === 'function');
1108
1108
  try {
1109
1109
  messages = scopedChat
1110
1110
  ? conversationStore.loadChatHistoryForVp(this.#chatId, this.#vpId)
1111
1111
  : scoped
1112
- ? conversationStore.loadGroupHistoryForVp(this.#groupId, this.#vpId)
1112
+ ? conversationStore.loadGroupHistoryForVp(this.#sessionId, this.#vpId)
1113
1113
  : conversationStore.loadAll();
1114
1114
  } catch { return null; }
1115
1115
  if (!Array.isArray(messages) || messages.length === 0) return null;
1116
1116
 
1117
1117
  const tokenCount = conversationStore.hotTokens();
1118
- // In the scoped path, groupId is the engine's binding (authoritative).
1118
+ // In the scoped path, sessionId is the engine's binding (authoritative).
1119
1119
  // In the legacy path, fall back to scanning the messages (best-effort,
1120
1120
  // used only for the group context-window gate).
1121
- const groupId = this.#groupId
1122
- || messages.find(m => m && typeof m.groupId === 'string' && m.groupId)?.groupId
1121
+ const sessionId = this.#sessionId
1122
+ || messages.find(m => m && typeof m.sessionId === 'string' && m.sessionId)?.sessionId
1123
1123
  || null;
1124
1124
  const groupContextGate = shouldAllowGroupReflection({
1125
1125
  system: '',
1126
1126
  messages,
1127
1127
  model: this.#config.model,
1128
1128
  config: this.#config,
1129
- groupId,
1129
+ sessionId,
1130
1130
  });
1131
- if (groupId && groupContextGate?.usedFallbackContextWindow) {
1131
+ if (sessionId && groupContextGate?.usedFallbackContextWindow) {
1132
1132
  this.#trace.log?.('group_context_window_fallback', {
1133
- groupId,
1133
+ sessionId,
1134
1134
  model: this.#config.model,
1135
1135
  contextWindow: groupContextGate.contextWindow,
1136
1136
  threshold: groupContextGate.threshold,
1137
1137
  });
1138
1138
  }
1139
- if (groupId && !groupContextGate.compactAllowed) return null;
1139
+ if (sessionId && !groupContextGate.compactAllowed) return null;
1140
1140
 
1141
1141
  const trig = evaluateCompactTriggers({
1142
1142
  messages,
1143
1143
  tokenCount,
1144
1144
  contextLimit: this.#config.maxContextTokens || 200000,
1145
- tokenRatio: groupId ? GROUP_CONTEXT_PRESSURE_RATIO : undefined,
1146
- maxMessages: groupId ? Number.POSITIVE_INFINITY : undefined,
1145
+ tokenRatio: sessionId ? GROUP_CONTEXT_PRESSURE_RATIO : undefined,
1146
+ maxMessages: sessionId ? Number.POSITIVE_INFINITY : undefined,
1147
1147
  });
1148
1148
  if (!trig.trigger) return null;
1149
1149
 
@@ -1233,7 +1233,7 @@ export class Engine {
1233
1233
  if (scopedChat && typeof conversationStore.replaceCompactSummaryForChat === 'function') {
1234
1234
  conversationStore.replaceCompactSummaryForChat(this.#chatId, this.#vpId, out.compactSummary);
1235
1235
  } else if (scoped && typeof conversationStore.replaceCompactSummaryFor === 'function') {
1236
- conversationStore.replaceCompactSummaryFor(this.#groupId, this.#vpId, out.compactSummary);
1236
+ conversationStore.replaceCompactSummaryFor(this.#sessionId, this.#vpId, out.compactSummary);
1237
1237
  } else {
1238
1238
  conversationStore.replaceCompactSummary(out.compactSummary);
1239
1239
  }
@@ -1310,7 +1310,7 @@ export class Engine {
1310
1310
  * string-prompt shape (no regression for existing callers).
1311
1311
  * @yields {EngineEvent}
1312
1312
  */
1313
- async *query({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan, groupAnnouncement, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, threadId = MAIN_THREAD_ID, drainPendingUserMessages = null } = {}) {
1313
+ async *query({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, vpPlan, sessionAnnouncement, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, threadId = MAIN_THREAD_ID, drainPendingUserMessages = null } = {}) {
1314
1314
  if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
1315
1315
  yield {
1316
1316
  type: 'error',
@@ -1370,7 +1370,7 @@ export class Engine {
1370
1370
 
1371
1371
  try {
1372
1372
  this.#currentThreadId = threadId || MAIN_THREAD_ID;
1373
- yield* this.#runQuery({ prompt: effectivePrompt, promptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan, groupAnnouncement, workDir, userAlreadyPersisted, getCurrentTodos, setCurrentTodos, threadId: this.#currentThreadId, drainPendingUserMessages });
1373
+ yield* this.#runQuery({ prompt: effectivePrompt, promptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, vpPlan, sessionAnnouncement, workDir, userAlreadyPersisted, getCurrentTodos, setCurrentTodos, threadId: this.#currentThreadId, drainPendingUserMessages });
1374
1374
  } finally {
1375
1375
  if (signal) {
1376
1376
  try { signal.removeEventListener('abort', onExternalAbort); } catch { /* ignore */ }
@@ -1390,7 +1390,7 @@ export class Engine {
1390
1390
  * in a try/finally without indenting the whole loop.
1391
1391
  * @private
1392
1392
  */
1393
- async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan, groupAnnouncement, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, threadId = MAIN_THREAD_ID, drainPendingUserMessages = null }) {
1393
+ async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, vpPlan, sessionAnnouncement, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, threadId = MAIN_THREAD_ID, drainPendingUserMessages = null }) {
1394
1394
 
1395
1395
  // ─── Pre-query: FTS5 Memory Recall + AMS snapshot ─────
1396
1396
  // Memory has a SINGLE render outlet now (DESIGN-PROMPT §3 ③):
@@ -1405,7 +1405,7 @@ export class Engine {
1405
1405
  let recallEntryCount = 0;
1406
1406
 
1407
1407
  const recallResult = await this.#recallMemory(prompt, {
1408
- groupId,
1408
+ sessionId,
1409
1409
  vpId: vpPersona && typeof vpPersona === 'object' && typeof vpPersona.vpId === 'string'
1410
1410
  ? vpPersona.vpId
1411
1411
  : (typeof senderVpId === 'string' ? senderVpId : undefined),
@@ -1421,7 +1421,7 @@ export class Engine {
1421
1421
  // here so we can pass them into #prepareAms. (Rolling per-scope
1422
1422
  // synopsis maintained by the dream tick.) Failures are non-fatal.
1423
1423
  const summaries = await this.#loadLayerASummaries({
1424
- groupId,
1424
+ sessionId,
1425
1425
  vpId: vpPersona && typeof vpPersona === 'object' && typeof vpPersona.vpId === 'string'
1426
1426
  ? vpPersona.vpId
1427
1427
  : (typeof senderVpId === 'string' ? senderVpId : undefined),
@@ -1440,7 +1440,7 @@ export class Engine {
1440
1440
  ? vpPersona.vpId
1441
1441
  : (typeof senderVpId === 'string' ? senderVpId : null);
1442
1442
  const amsContext = this.#prepareAms({
1443
- groupId,
1443
+ sessionId,
1444
1444
  ownVpId: ownVpIdForAms,
1445
1445
  summaries,
1446
1446
  recallEntries: recallResult ? (recallResult.entries || []) : [],
@@ -1454,7 +1454,7 @@ export class Engine {
1454
1454
  // info. Long-form scope content lives in AMS — this block carries
1455
1455
  // only IDs + tiny labels. (Feature scope retired 2026-05-13.)
1456
1456
  const activeScope = {
1457
- groupId: groupId || '',
1457
+ sessionId: sessionId || '',
1458
1458
  vpId: ownVpIdForAms || '',
1459
1459
  envelope: inboundEnvelope || null,
1460
1460
  };
@@ -1466,7 +1466,7 @@ export class Engine {
1466
1466
  memoryInjection,
1467
1467
  vpPersona,
1468
1468
  activeScope,
1469
- groupAnnouncement,
1469
+ sessionAnnouncement,
1470
1470
  projectDoc,
1471
1471
  });
1472
1472
 
@@ -1519,12 +1519,12 @@ export class Engine {
1519
1519
  messages: conversationMessages,
1520
1520
  model: this.#config.model,
1521
1521
  config: this.#config,
1522
- groupId,
1522
+ sessionId,
1523
1523
  });
1524
1524
  const groupReflectionAllowed = groupReflectionGate.allowed === true;
1525
- if (groupId && groupReflectionGate?.usedFallbackContextWindow) {
1525
+ if (sessionId && groupReflectionGate?.usedFallbackContextWindow) {
1526
1526
  this.#trace.log?.('group_context_window_fallback', {
1527
- groupId,
1527
+ sessionId,
1528
1528
  model: this.#config.model,
1529
1529
  contextWindow: groupReflectionGate.contextWindow,
1530
1530
  threshold: groupReflectionGate.threshold,
@@ -1589,7 +1589,7 @@ export class Engine {
1589
1589
  threadId,
1590
1590
  userPrompt: userQuestionPreview,
1591
1591
  vpId: queryVpId,
1592
- groupId: groupId || null,
1592
+ sessionId: sessionId || null,
1593
1593
  at: queryStartedAt,
1594
1594
  };
1595
1595
 
@@ -1648,7 +1648,7 @@ export class Engine {
1648
1648
  // fix-vp-multi-thread (bug 4): stamp routing context so the
1649
1649
  // debug-trace SQL row carries enough info to be filtered by
1650
1650
  // group / thread / VP later when the panel hydrates from disk.
1651
- groupId: groupId || null,
1651
+ sessionId: sessionId || null,
1652
1652
  vpId: queryVpId || null,
1653
1653
  threadId: threadId || null,
1654
1654
  // Persist the user prompt EXPLICITLY rather than reconstruct it
@@ -2141,7 +2141,7 @@ export class Engine {
2141
2141
  trace: this.#trace,
2142
2142
  // Bug 6: tag persisted messages with the originating group so
2143
2143
  // history replay can re-stamp them on reload.
2144
- groupId,
2144
+ sessionId,
2145
2145
  threadId,
2146
2146
  // Multi-VP fan-out (history-dedup): skip the user-row append
2147
2147
  // in stop-hooks when the orchestrator already wrote it once
@@ -2155,7 +2155,7 @@ export class Engine {
2155
2155
  }
2156
2156
  } else {
2157
2157
  // Legacy path (no yeaftDir → use old behavior)
2158
- this.#persistMessages(prompt, fullResponseText, assistantMsg.toolCalls, groupId, userAlreadyPersisted);
2158
+ this.#persistMessages(prompt, fullResponseText, assistantMsg.toolCalls, sessionId, userAlreadyPersisted);
2159
2159
 
2160
2160
  const consolidated = await this.#maybeConsolidate();
2161
2161
  if (consolidated && consolidated.archivedCount > 0) {