@yeaft/webchat-agent 0.1.874 → 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/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/dream-v2/triage.js
CHANGED
|
@@ -50,15 +50,15 @@ function triageSystem(language) {
|
|
|
50
50
|
* structure of the diff.
|
|
51
51
|
*
|
|
52
52
|
* Inputs:
|
|
53
|
-
* -
|
|
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 {{
|
|
58
|
+
* @param {{ sessionId: string, messages: Array<object> }} args
|
|
59
59
|
* @returns {Array<{ kind: 'update', scope: string }>}
|
|
60
60
|
*/
|
|
61
|
-
export function applyHardRules({
|
|
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 (
|
|
85
|
-
add(`group/${
|
|
86
|
-
add(`group/${
|
|
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) &&
|
|
95
|
-
add(`group/${
|
|
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 {{
|
|
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
|
-
|
|
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
|
-
*
|
|
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({
|
|
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({
|
|
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 (!
|
|
184
|
-
if (!isValidTopic({ kind: 'group-topic',
|
|
185
|
-
const scope = `group/${
|
|
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
|
-
*
|
|
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({
|
|
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
|
-
*
|
|
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({
|
|
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',
|
|
231
|
+
if (onProgress) onProgress({ phase: 'triage', sessionId, segment: i, total: segments.length });
|
|
232
232
|
const segActions = await triageOneSegment({
|
|
233
|
-
|
|
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 './
|
|
24
|
-
import { readProjectDoc, pickProjectDocFile, DEFAULT_PROJECT_DOC_MAX_BYTES } from './
|
|
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 (
|
|
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
|
-
|
|
173
|
+
sessionId = null,
|
|
174
174
|
} = {}) {
|
|
175
|
-
if (!
|
|
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
|
-
*
|
|
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.
|
|
257
|
-
out.push({ scope: `group/${args.
|
|
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
|
-
#
|
|
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
|
|
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,
|
|
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 ${
|
|
427
|
-
// its (
|
|
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.#
|
|
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
|
|
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 {{
|
|
564
|
+
* @param {{sessionId?: string, vpId?: string, language?: string}} ctx
|
|
565
565
|
* @returns {Promise<{user:string, group:string, vp:string}>}
|
|
566
566
|
*/
|
|
567
|
-
async #loadLayerASummaries({
|
|
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
|
-
|
|
573
|
-
? readScopeSummary({ kind: 'group', id:
|
|
572
|
+
sessionId
|
|
573
|
+
? readScopeSummary({ kind: 'group', id: sessionId }, { root: memoryRoot, language }).catch(() => '')
|
|
574
574
|
: Promise.resolve(''),
|
|
575
|
-
vpId &&
|
|
576
|
-
? readScopeSummary({ kind: 'group-vp',
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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 {{
|
|
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
|
-
|
|
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.#
|
|
1005
|
+
if (this.#sessionId && this.#vpId
|
|
1006
1006
|
&& typeof this.#conversationStore.readCompactSummaryFor === 'function') {
|
|
1007
|
-
return this.#conversationStore.readCompactSummaryFor(this.#
|
|
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} [
|
|
1025
|
+
* @param {string} [sessionId]
|
|
1026
1026
|
* @param {boolean} [userAlreadyPersisted]
|
|
1027
1027
|
*/
|
|
1028
|
-
#persistMessages(userContent, assistantContent, toolCalls,
|
|
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
|
|
1044
|
-
...(
|
|
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
|
-
...(
|
|
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
|
|
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.#
|
|
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.#
|
|
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,
|
|
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
|
|
1122
|
-
|| messages.find(m => m && typeof m.
|
|
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
|
-
|
|
1129
|
+
sessionId,
|
|
1130
1130
|
});
|
|
1131
|
-
if (
|
|
1131
|
+
if (sessionId && groupContextGate?.usedFallbackContextWindow) {
|
|
1132
1132
|
this.#trace.log?.('group_context_window_fallback', {
|
|
1133
|
-
|
|
1133
|
+
sessionId,
|
|
1134
1134
|
model: this.#config.model,
|
|
1135
1135
|
contextWindow: groupContextGate.contextWindow,
|
|
1136
1136
|
threshold: groupContextGate.threshold,
|
|
1137
1137
|
});
|
|
1138
1138
|
}
|
|
1139
|
-
if (
|
|
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:
|
|
1146
|
-
maxMessages:
|
|
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.#
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1522
|
+
sessionId,
|
|
1523
1523
|
});
|
|
1524
1524
|
const groupReflectionAllowed = groupReflectionGate.allowed === true;
|
|
1525
|
-
if (
|
|
1525
|
+
if (sessionId && groupReflectionGate?.usedFallbackContextWindow) {
|
|
1526
1526
|
this.#trace.log?.('group_context_window_fallback', {
|
|
1527
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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) {
|