@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/merge.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* [
|
|
14
14
|
* {
|
|
15
|
-
*
|
|
15
|
+
* sessionId: 'g-eng',
|
|
16
16
|
* diff: [<message>, ...], // the per-group source diff
|
|
17
17
|
* // (already truncated/segmented if needed)
|
|
18
18
|
* actions: [
|
|
@@ -32,36 +32,36 @@
|
|
|
32
32
|
* { target: 'user',
|
|
33
33
|
* kind: 'update', // 'update' wins over 'create' if any group says update
|
|
34
34
|
* sources: [
|
|
35
|
-
* {
|
|
36
|
-
* {
|
|
35
|
+
* { sessionId: 'g-eng', diff: [...] },
|
|
36
|
+
* { sessionId: 'g-life', diff: [...] },
|
|
37
37
|
* ],
|
|
38
38
|
* },
|
|
39
39
|
* { target: 'topic/life/parenting',
|
|
40
40
|
* kind: 'create', // create only if every contributing group said create
|
|
41
|
-
* sources: [{
|
|
41
|
+
* sources: [{ sessionId: 'g-life', diff: [...] }],
|
|
42
42
|
* },
|
|
43
43
|
* ...
|
|
44
44
|
* ]
|
|
45
45
|
*
|
|
46
46
|
* Determinism contract:
|
|
47
47
|
* - Targets are returned sorted alphabetically by target path.
|
|
48
|
-
* - Within a target, sources are sorted by
|
|
48
|
+
* - Within a target, sources are sorted by sessionId.
|
|
49
49
|
* This makes the debug-panel output predictable across runs.
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Merge per-group triage outputs into per-target apply units.
|
|
54
54
|
*
|
|
55
|
-
* @param {Array<{
|
|
56
|
-
* @returns {Array<{ target: string, kind: 'update'|'create', sources: Array<{
|
|
55
|
+
* @param {Array<{ sessionId: string, diff: any, actions: Array<{ kind: 'update'|'create', scope: string }> }>} groupTriages
|
|
56
|
+
* @returns {Array<{ target: string, kind: 'update'|'create', sources: Array<{ sessionId: string, diff: any }> }>}
|
|
57
57
|
*/
|
|
58
58
|
export function mergeByTarget(groupTriages) {
|
|
59
59
|
const byTarget = new Map();
|
|
60
60
|
for (const g of (groupTriages || [])) {
|
|
61
|
-
const
|
|
61
|
+
const sessionId = g && g.sessionId;
|
|
62
62
|
const diff = g && g.diff;
|
|
63
63
|
const actions = Array.isArray(g && g.actions) ? g.actions : [];
|
|
64
|
-
if (!
|
|
64
|
+
if (!sessionId) continue;
|
|
65
65
|
for (const a of actions) {
|
|
66
66
|
if (!a || !a.scope) continue;
|
|
67
67
|
const k = a.kind === 'create' ? 'create' : 'update';
|
|
@@ -75,13 +75,13 @@ export function mergeByTarget(groupTriages) {
|
|
|
75
75
|
if (k === 'update') entry.kind = 'update';
|
|
76
76
|
// Avoid duplicate (target, group) pairs — should never happen
|
|
77
77
|
// in normal triage but be defensive.
|
|
78
|
-
if (!entry.sources.some(s => s.
|
|
79
|
-
entry.sources.push({
|
|
78
|
+
if (!entry.sources.some(s => s.sessionId === sessionId)) {
|
|
79
|
+
entry.sources.push({ sessionId, diff });
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
const out = Array.from(byTarget.values());
|
|
84
84
|
out.sort((a, b) => a.target.localeCompare(b.target));
|
|
85
|
-
for (const e of out) e.sources.sort((a, b) => a.
|
|
85
|
+
for (const e of out) e.sources.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
86
86
|
return out;
|
|
87
87
|
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
# Dream Extract —
|
|
1
|
+
# Dream Extract — Session Scope
|
|
2
2
|
|
|
3
3
|
You are extracting **memory segments** from a conversation between the
|
|
4
4
|
user and a Yeaft AI companion. This pass focuses on a specific
|
|
5
|
-
**`
|
|
5
|
+
**`session/<id>` scope**: long-lived facts about one collaboration session
|
|
6
6
|
(a project team, a study cohort, a working set of people/agents).
|
|
7
7
|
|
|
8
|
-
The target
|
|
8
|
+
The target session id is provided as `{{sessionId}}`.
|
|
9
9
|
|
|
10
|
-
## What to extract for `
|
|
10
|
+
## What to extract for `session/<id>` scope
|
|
11
11
|
|
|
12
|
-
- **purpose** — what this
|
|
13
|
-
- **members** — people, VPs, and roles in the
|
|
12
|
+
- **purpose** — what this session exists to do, its charter / mission
|
|
13
|
+
- **members** — people, VPs, and roles in the session, and what each is
|
|
14
14
|
responsible for
|
|
15
|
-
- **conventions** — how the
|
|
15
|
+
- **conventions** — how the session works (rituals, cadences, naming,
|
|
16
16
|
channels, languages used)
|
|
17
17
|
- **shared decisions** — durable agreements ("we ship on Fridays",
|
|
18
18
|
"all PRs need two reviewers")
|
|
19
|
-
- **shared context** — domain knowledge the whole
|
|
20
|
-
- **relations** — other
|
|
19
|
+
- **shared context** — domain knowledge the whole session relies on
|
|
20
|
+
- **relations** — other sessions, features, or topics this session owns or
|
|
21
21
|
depends on
|
|
22
22
|
- **lessons** — collective takeaways ("we tried X in Q1, it didn't
|
|
23
23
|
scale, switched to Y")
|
|
@@ -28,7 +28,7 @@ The target group id is provided as `{{groupId}}`.
|
|
|
28
28
|
- Single-VP traits — those go to that VP's `vp/<id>` scope.
|
|
29
29
|
- Feature-specific implementation detail — those go to
|
|
30
30
|
`feature/<id>` scope.
|
|
31
|
-
- Transient status updates — only durable
|
|
31
|
+
- Transient status updates — only durable session facts.
|
|
32
32
|
|
|
33
33
|
## Segment shape
|
|
34
34
|
|
|
@@ -45,7 +45,7 @@ Reply with a JSON array of segment objects:
|
|
|
45
45
|
"kind": "decision",
|
|
46
46
|
"tags": ["process", "review"],
|
|
47
47
|
"sourceMessages": ["m_201"],
|
|
48
|
-
"body": "
|
|
48
|
+
"body": "Session {{sessionId}} decided every PR touching the payments
|
|
49
49
|
module needs sign-off from both the security VP and the payments
|
|
50
50
|
feature owner before merge. Rationale: a near-miss in March."
|
|
51
51
|
}
|
|
@@ -54,4 +54,4 @@ Reply with a JSON array of segment objects:
|
|
|
54
54
|
|
|
55
55
|
`kind` ∈ {`fact`, `preference`, `decision`, `lesson`, `relation`,
|
|
56
56
|
`goal`, `context`}. `scope` is filled in by the runner — do not include
|
|
57
|
-
it. If nothing
|
|
57
|
+
it. If nothing session-scope is in this batch, return `[]`.
|
|
@@ -20,7 +20,7 @@ const FILES = {
|
|
|
20
20
|
// H2.e — per-scope segment extraction prompts (one per scope family)
|
|
21
21
|
extractUser: 'extract-user.md',
|
|
22
22
|
extractVp: 'extract-vp.md',
|
|
23
|
-
|
|
23
|
+
extractSession: 'extract-session.md',
|
|
24
24
|
extractTopic: 'extract-topic.md',
|
|
25
25
|
// H2.e — per-scope summary compression
|
|
26
26
|
summarizeScope: 'summarize-scope.md',
|
|
@@ -46,10 +46,10 @@ export function extractTemplateForScope(scope) {
|
|
|
46
46
|
if (/^group\/[^/]+\/vp\//.test(scope)) return 'extractVp';
|
|
47
47
|
if (/^group\/[^/]+\/topic\//.test(scope)) return 'extractTopic';
|
|
48
48
|
if (/^group\/[^/]+\/user(?:\/|$)/.test(scope)) return 'extractUser';
|
|
49
|
-
if (scope.startsWith('group/')) return '
|
|
49
|
+
if (scope.startsWith('group/')) return 'extractSession';
|
|
50
50
|
// Chat-isolated scopes: same template family as groups.
|
|
51
51
|
if (/^chat\/[^/]+\/vp\//.test(scope)) return 'extractVp';
|
|
52
|
-
if (scope.startsWith('chat/')) return '
|
|
52
|
+
if (scope.startsWith('chat/')) return 'extractSession';
|
|
53
53
|
// Legacy top-level vp/topic scopes (archived to .legacy/ on boot — kept
|
|
54
54
|
// here defensively in case something still constructs the old strings).
|
|
55
55
|
if (scope.startsWith('vp/')) return 'extractVp';
|
package/yeaft/dream-v2/runner.js
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* trigger
|
|
7
7
|
* ↓
|
|
8
|
-
* enumerateGroups() via opts.
|
|
8
|
+
* enumerateGroups() via opts.listSessions()
|
|
9
9
|
* ↓
|
|
10
10
|
* for each group with newCount ≥ MIN_NEW_PER_GROUP (auto)
|
|
11
11
|
* or > 0 (manual)
|
|
12
12
|
* or prior messages in a scoped manual group rerun:
|
|
13
|
-
* loadDiff() via opts.loadGroupDiff(
|
|
13
|
+
* loadDiff() via opts.loadGroupDiff(sessionId, sinceId)
|
|
14
14
|
* applyOverlap() via opts.loadOverlapPreamble(...)
|
|
15
15
|
* segment() segmentDiff(...)
|
|
16
16
|
* triageGroupSegments() → group-local actions[]
|
|
@@ -58,10 +58,10 @@ import { tsForBackup, pruneOldSnapshots } from './snapshot.js';
|
|
|
58
58
|
* @property {boolean} [manual=false] — manual trigger overrides newCount<20 skip
|
|
59
59
|
* @property {string[]} [scopeFilter] — optional: only dream these targets; scoped manual group triggers rerun the current group when there are prior messages but no new cursor delta ('*' allowed)
|
|
60
60
|
* @property {(req: {pass:string, prompt:string, system:string}) => Promise<string>} llm
|
|
61
|
-
* @property {() => Promise<Array<string>>}
|
|
62
|
-
* @property {(
|
|
63
|
-
* @property {(
|
|
64
|
-
* @property {(
|
|
61
|
+
* @property {() => Promise<Array<string>>} listSessions — return all group ids (incl. '_no-group')
|
|
62
|
+
* @property {(sessionId: string) => Promise<number>} countMessages — total message count for a group
|
|
63
|
+
* @property {(sessionId: string, sinceMessageId: string|null) => Promise<Array<object>>} loadGroupDiff
|
|
64
|
+
* @property {(sessionId: string, beforeMessageId: string|null, count: number) => Promise<Array<object>>} loadOverlapPreamble
|
|
65
65
|
* @property {() => Promise<Array<{path:string, summary:string}>>} [listTopicSummaries]
|
|
66
66
|
* @property {(target: string) => Promise<Array<{path:string, summary:string}>>} [siblingTopicsFor]
|
|
67
67
|
* @property {(event: object) => void} [onProgress]
|
|
@@ -87,7 +87,7 @@ export async function runDream(opts) {
|
|
|
87
87
|
onProgress({ phase: 'start', manual: !!opts.manual, ts });
|
|
88
88
|
|
|
89
89
|
// 1. enumerate groups
|
|
90
|
-
const
|
|
90
|
+
const sessionIds = await safeCall(opts.listSessions, []);
|
|
91
91
|
const filter = Array.isArray(opts.scopeFilter) ? new Set(opts.scopeFilter) : null;
|
|
92
92
|
const groupFilter = deriveGroupFilter(filter);
|
|
93
93
|
const groupsReport = [];
|
|
@@ -97,53 +97,53 @@ export async function runDream(opts) {
|
|
|
97
97
|
// 2. per-group: skip / segment / triage
|
|
98
98
|
// Topic summaries are now per-group (group/<g>/topic/...), so resolve
|
|
99
99
|
// them inside the per-group loop instead of once up front.
|
|
100
|
-
const resolveTopicSummaries = async (
|
|
100
|
+
const resolveTopicSummaries = async (sessionId) => {
|
|
101
101
|
if (opts.listTopicSummaries) {
|
|
102
|
-
return await safeCall(() => opts.listTopicSummaries(
|
|
102
|
+
return await safeCall(() => opts.listTopicSummaries(sessionId), []);
|
|
103
103
|
}
|
|
104
|
-
return await defaultListTopicSummaries(opts.root,
|
|
104
|
+
return await defaultListTopicSummaries(opts.root, sessionId, opts.language).catch(() => []);
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
for (const
|
|
107
|
+
for (const sessionId of sessionIds) {
|
|
108
108
|
// Current-group manual dream passes are the one case where scopeFilter
|
|
109
109
|
// must constrain enumeration too: clicking the conversation header means
|
|
110
110
|
// "dream this group now", not "triage every group and then only apply
|
|
111
111
|
// group/<id>". Pure target filters such as ['user'] still triage every
|
|
112
112
|
// group so their hard-rule actions can contribute to the requested scope.
|
|
113
|
-
if (groupFilter && !groupFilter.has(
|
|
114
|
-
groupsReport.push({
|
|
113
|
+
if (groupFilter && !groupFilter.has(sessionId)) {
|
|
114
|
+
groupsReport.push({ sessionId, new: 0, status: 'skipped', reason: 'scope-filtered' });
|
|
115
115
|
continue;
|
|
116
116
|
}
|
|
117
|
-
const state = await readGroupState(opts.root,
|
|
118
|
-
const beforeCount = await safeCall(() => opts.countMessages(
|
|
117
|
+
const state = await readGroupState(opts.root, sessionId);
|
|
118
|
+
const beforeCount = await safeCall(() => opts.countMessages(sessionId), 0);
|
|
119
119
|
const newCount = Math.max(0, beforeCount - (state.messageCount || 0));
|
|
120
120
|
|
|
121
121
|
const rerunScopedManual = !!opts.manual
|
|
122
122
|
&& groupFilter
|
|
123
|
-
&& groupFilter.has(
|
|
123
|
+
&& groupFilter.has(sessionId)
|
|
124
124
|
&& newCount === 0
|
|
125
125
|
&& beforeCount > 0;
|
|
126
126
|
|
|
127
127
|
if (newCount === 0 && !rerunScopedManual) {
|
|
128
|
-
groupsReport.push({
|
|
128
|
+
groupsReport.push({ sessionId, new: 0, status: 'skipped', reason: 'no-new-messages' });
|
|
129
129
|
continue;
|
|
130
130
|
}
|
|
131
131
|
if (!opts.manual && newCount < limits.MIN_NEW_PER_GROUP) {
|
|
132
|
-
groupsReport.push({
|
|
132
|
+
groupsReport.push({ sessionId, new: newCount, status: 'skipped', reason: 'below-threshold' });
|
|
133
133
|
continue;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
onProgress({ phase: 'load-diff',
|
|
136
|
+
onProgress({ phase: 'load-diff', sessionId });
|
|
137
137
|
const diffCursor = rerunScopedManual ? null : state.lastDreamMessageId;
|
|
138
|
-
const diffNew = await safeCall(() => opts.loadGroupDiff(
|
|
138
|
+
const diffNew = await safeCall(() => opts.loadGroupDiff(sessionId, diffCursor), []);
|
|
139
139
|
if (!diffNew || diffNew.length === 0) {
|
|
140
|
-
groupsReport.push({
|
|
140
|
+
groupsReport.push({ sessionId, new: newCount, status: 'skipped', reason: 'empty-diff' });
|
|
141
141
|
continue;
|
|
142
142
|
}
|
|
143
143
|
const overlapMessages = state.lastDreamMessageId && !rerunScopedManual
|
|
144
144
|
? await safeCall(
|
|
145
145
|
() => opts.loadOverlapPreamble
|
|
146
|
-
? opts.loadOverlapPreamble(
|
|
146
|
+
? opts.loadOverlapPreamble(sessionId, state.lastDreamMessageId, limits.DREAM_OVERLAP)
|
|
147
147
|
: [],
|
|
148
148
|
[],
|
|
149
149
|
)
|
|
@@ -153,13 +153,13 @@ export async function runDream(opts) {
|
|
|
153
153
|
const fullDiff = [...taggedOverlap, ...taggedNew];
|
|
154
154
|
|
|
155
155
|
const segments = segmentDiff(fullDiff, limits.MAX_DIFF_TOKENS_PER_TRIAGE, limits.DREAM_OVERLAP);
|
|
156
|
-
onProgress({ phase: 'triage',
|
|
156
|
+
onProgress({ phase: 'triage', sessionId, status: 'running', segments: segments.length });
|
|
157
157
|
|
|
158
158
|
let actions;
|
|
159
159
|
try {
|
|
160
|
-
const topicSummaries = await resolveTopicSummaries(
|
|
160
|
+
const topicSummaries = await resolveTopicSummaries(sessionId);
|
|
161
161
|
actions = await triageGroupSegments({
|
|
162
|
-
|
|
162
|
+
sessionId,
|
|
163
163
|
segments,
|
|
164
164
|
topicSummaries,
|
|
165
165
|
llm: opts.llm,
|
|
@@ -167,12 +167,12 @@ export async function runDream(opts) {
|
|
|
167
167
|
language: opts.language,
|
|
168
168
|
});
|
|
169
169
|
} catch (err) {
|
|
170
|
-
groupsReport.push({
|
|
171
|
-
onProgress({ phase: 'triage',
|
|
170
|
+
groupsReport.push({ sessionId, new: newCount, status: 'error', error: err.message });
|
|
171
|
+
onProgress({ phase: 'triage', sessionId, status: 'error', error: err.message });
|
|
172
172
|
// Journal the failure on disk so operators can see WHY dream is
|
|
173
173
|
// not advancing without having to enable `config.debug`. Best-
|
|
174
174
|
// effort — `writeDreamError` swallows its own I/O errors.
|
|
175
|
-
await writeDreamError(opts.root, `group/${
|
|
175
|
+
await writeDreamError(opts.root, `group/${sessionId}`, {
|
|
176
176
|
phase: 'triage',
|
|
177
177
|
message: err.message,
|
|
178
178
|
stack: err.stack,
|
|
@@ -180,12 +180,12 @@ export async function runDream(opts) {
|
|
|
180
180
|
continue;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
onProgress({ phase: 'triage',
|
|
184
|
-
groupTriages.push({
|
|
183
|
+
onProgress({ phase: 'triage', sessionId, status: 'done', actions: actions.length });
|
|
184
|
+
groupTriages.push({ sessionId, diff: fullDiff, actions });
|
|
185
185
|
|
|
186
186
|
const tailId = lastMessageId(diffNew);
|
|
187
|
-
processedGroups.push({
|
|
188
|
-
groupsReport.push({
|
|
187
|
+
processedGroups.push({ sessionId, tailId, beforeCount, newCount, segments: segments.length, actions: actions.length });
|
|
188
|
+
groupsReport.push({ sessionId, new: newCount, segments: segments.length, actions: actions.length, status: 'triaged', rerun: rerunScopedManual || undefined });
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
// 3. merge
|
|
@@ -238,12 +238,12 @@ export async function runDream(opts) {
|
|
|
238
238
|
// retries.)
|
|
239
239
|
const successfulTargets = new Set(targetsReport.filter(r => r.status === 'done').map(r => r.target));
|
|
240
240
|
for (const pg of processedGroups) {
|
|
241
|
-
const contributed = (groupTriages.find(g => g.
|
|
241
|
+
const contributed = (groupTriages.find(g => g.sessionId === pg.sessionId) || { actions: [] })
|
|
242
242
|
.actions.map(a => a.scope);
|
|
243
243
|
const anySuccess = contributed.some(t => successfulTargets.has(t));
|
|
244
244
|
if (!anySuccess) continue;
|
|
245
245
|
if (pg.tailId) {
|
|
246
|
-
await writeGroupState(opts.root, pg.
|
|
246
|
+
await writeGroupState(opts.root, pg.sessionId, {
|
|
247
247
|
lastDreamMessageId: pg.tailId,
|
|
248
248
|
lastDreamAt: nowIso,
|
|
249
249
|
messageCount: pg.beforeCount,
|
|
@@ -297,7 +297,7 @@ function deriveGroupFilter(filter) {
|
|
|
297
297
|
|
|
298
298
|
function sourceGroupId(mergedTarget) {
|
|
299
299
|
const src = Array.isArray(mergedTarget?.sources) ? mergedTarget.sources[0] : null;
|
|
300
|
-
return src && typeof src.
|
|
300
|
+
return src && typeof src.sessionId === 'string' ? src.sessionId : '';
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
async function safeCall(fn, fallback) {
|
|
@@ -310,12 +310,12 @@ async function safeCall(fn, fallback) {
|
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
async function defaultListTopicSummaries(root,
|
|
313
|
+
async function defaultListTopicSummaries(root, sessionId, language) {
|
|
314
314
|
const all = await listScopes({ root });
|
|
315
315
|
const out = [];
|
|
316
316
|
for (const sc of all) {
|
|
317
317
|
if (sc.kind !== 'group-topic') continue;
|
|
318
|
-
if (sc.
|
|
318
|
+
if (sc.sessionId !== sessionId) continue;
|
|
319
319
|
const summary = await readSummary(sc, { root, language });
|
|
320
320
|
out.push({ path: sc.path.join('/'), summary });
|
|
321
321
|
}
|
|
@@ -141,7 +141,7 @@ export function segmentDiff(diff, maxTokens = MAX_DIFF_TOKENS_PER_TRIAGE, overla
|
|
|
141
141
|
/**
|
|
142
142
|
* Decide whether a merged apply target needs to be split into batches.
|
|
143
143
|
*
|
|
144
|
-
* @param {{ memoryMd?: string, summaryMd?: string, sources: Array<{
|
|
144
|
+
* @param {{ memoryMd?: string, summaryMd?: string, sources: Array<{ sessionId: string, diff: any }> }} merged
|
|
145
145
|
* @param {number} [maxTokens=MAX_APPLY_TOKENS]
|
|
146
146
|
*/
|
|
147
147
|
export function needsBatchedApply(merged, maxTokens = MAX_APPLY_TOKENS) {
|
|
@@ -165,9 +165,9 @@ function totalApplyTokens(merged) {
|
|
|
165
165
|
* goes into its own batch — we never split a source diff here (segment
|
|
166
166
|
* happens earlier, in triage).
|
|
167
167
|
*
|
|
168
|
-
* @param {{ memoryMd?: string, summaryMd?: string, sources: Array<{
|
|
168
|
+
* @param {{ memoryMd?: string, summaryMd?: string, sources: Array<{ sessionId: string, diff: any }> }} merged
|
|
169
169
|
* @param {number} [maxTokens=MAX_APPLY_TOKENS]
|
|
170
|
-
* @returns {Array<{
|
|
170
|
+
* @returns {Array<{ sessionId: string, diff: any }[]>}
|
|
171
171
|
*/
|
|
172
172
|
export function batchSourcesForApply(merged, maxTokens = MAX_APPLY_TOKENS) {
|
|
173
173
|
const sources = Array.isArray(merged.sources) ? merged.sources : [];
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* load-bearing for the debug panel. Renaming a field on this side
|
|
16
16
|
* silently degrades that UI to a generic JSON dump.
|
|
17
17
|
*
|
|
18
|
-
* dream_turn_open: { type:'turn_open', turnId, userPrompt, vpId,
|
|
18
|
+
* dream_turn_open: { type:'turn_open', turnId, userPrompt, vpId, sessionId, at }
|
|
19
19
|
* dream_loop: { type:'loop', turnId, loopNumber, pass, model,
|
|
20
20
|
* systemPrompt: string,
|
|
21
21
|
* messages: [{ role:'user', content:string }],
|
|
@@ -35,19 +35,19 @@
|
|
|
35
35
|
* `kind, memoryMdPreview, summaryMdPreview, memoryMdLength,
|
|
36
36
|
* summaryMdLength` (see apply.js).
|
|
37
37
|
*
|
|
38
|
-
* `
|
|
38
|
+
* `sessionId` may be inherited via `stampDreamScope()` when a scope is active.
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
import { join } from 'path';
|
|
42
42
|
import { runDream } from './runner.js';
|
|
43
43
|
import { createDreamScheduler } from './schedule.js';
|
|
44
|
-
import {
|
|
44
|
+
import { listSessions, openSession } from '../sessions/session-store.js';
|
|
45
45
|
import { readGroupState } from './state.js';
|
|
46
46
|
import { DREAM_NUDGE_AFTER_MESSAGES, DREAM_INTERVAL_HOURS } from './limits.js';
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Build the per-call options for runDream. Pure: takes a session and returns
|
|
50
|
-
* the closures runDream needs (
|
|
50
|
+
* the closures runDream needs (listSessions, countMessages, loadGroupDiff, etc.).
|
|
51
51
|
*
|
|
52
52
|
* @param {Object} session — the live Session object from loadSession()
|
|
53
53
|
* @param {(event: object) => void} [onProgress]
|
|
@@ -56,19 +56,19 @@ import { DREAM_NUDGE_AFTER_MESSAGES, DREAM_INTERVAL_HOURS } from './limits.js';
|
|
|
56
56
|
export function buildRunDreamOpts(session, onProgress) {
|
|
57
57
|
const yeaftDir = session.yeaftDir;
|
|
58
58
|
const memoryRoot = join(yeaftDir, 'memory');
|
|
59
|
-
const
|
|
59
|
+
const sessionsRoot = join(yeaftDir, 'sessions');
|
|
60
60
|
|
|
61
61
|
return {
|
|
62
62
|
root: memoryRoot,
|
|
63
63
|
language: session.config?.language || 'en',
|
|
64
64
|
llm: makeLlm(session),
|
|
65
|
-
|
|
66
|
-
try { return
|
|
65
|
+
listSessions: async () => {
|
|
66
|
+
try { return listSessions(sessionsRoot).map(g => g.id); }
|
|
67
67
|
catch { return []; }
|
|
68
68
|
},
|
|
69
69
|
countMessages: async (gid) => {
|
|
70
70
|
try {
|
|
71
|
-
const h =
|
|
71
|
+
const h = openSession(sessionsRoot, gid);
|
|
72
72
|
let n = 0;
|
|
73
73
|
for (const _m of h.streamMessages()) n += 1;
|
|
74
74
|
return n;
|
|
@@ -76,7 +76,7 @@ export function buildRunDreamOpts(session, onProgress) {
|
|
|
76
76
|
},
|
|
77
77
|
loadGroupDiff: async (gid, sinceId) => {
|
|
78
78
|
try {
|
|
79
|
-
const h =
|
|
79
|
+
const h = openSession(sessionsRoot, gid);
|
|
80
80
|
const out = [];
|
|
81
81
|
let started = !sinceId;
|
|
82
82
|
for (const m of h.streamMessages()) {
|
|
@@ -91,7 +91,7 @@ export function buildRunDreamOpts(session, onProgress) {
|
|
|
91
91
|
},
|
|
92
92
|
loadOverlapPreamble: async (gid, beforeId, n) => {
|
|
93
93
|
try {
|
|
94
|
-
const h =
|
|
94
|
+
const h = openSession(sessionsRoot, gid);
|
|
95
95
|
const buf = [];
|
|
96
96
|
for (const m of h.streamMessages()) {
|
|
97
97
|
if (m.id === beforeId) break;
|
|
@@ -199,8 +199,8 @@ function makeLlm(session) {
|
|
|
199
199
|
|
|
200
200
|
function stampDreamScope(session, evt) {
|
|
201
201
|
if (!evt || typeof evt !== 'object') return evt;
|
|
202
|
-
if (evt.
|
|
203
|
-
return { ...evt,
|
|
202
|
+
if (evt.sessionId || !session?._dreamActiveGroupId) return evt;
|
|
203
|
+
return { ...evt, sessionId: session._dreamActiveGroupId };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
function finiteNumber(v) {
|
|
@@ -326,7 +326,7 @@ export function createV2DreamScheduler(session) {
|
|
|
326
326
|
turnId,
|
|
327
327
|
userPrompt: '[dream] automatic memory consolidation',
|
|
328
328
|
vpId: null,
|
|
329
|
-
|
|
329
|
+
sessionId: null,
|
|
330
330
|
at: startedAt,
|
|
331
331
|
});
|
|
332
332
|
persistDreamTrace(session, 'dream_turn_open', turnOpen);
|
|
@@ -341,7 +341,7 @@ export function createV2DreamScheduler(session) {
|
|
|
341
341
|
}).then((result) => {
|
|
342
342
|
// Bug 2: emit turn_close when the dream pass completes.
|
|
343
343
|
result.trigger = opts.manual ? 'manual' : 'auto';
|
|
344
|
-
if (session._dreamActiveGroupId && !result.
|
|
344
|
+
if (session._dreamActiveGroupId && !result.sessionId) result.sessionId = session._dreamActiveGroupId;
|
|
345
345
|
const metrics = finalizeDreamMetrics(session._dreamMetrics, Date.now() - startedAt);
|
|
346
346
|
result.metrics = metrics;
|
|
347
347
|
result.durationMs = metrics.durationMs;
|
|
@@ -449,9 +449,9 @@ export function createV2DreamScheduler(session) {
|
|
|
449
449
|
export async function bootInitEmptyGroups(args) {
|
|
450
450
|
const out = { triggered: [] };
|
|
451
451
|
if (!args || !args.memoryIndex || !args.dreamScheduler) return out;
|
|
452
|
-
const
|
|
452
|
+
const sessionsRoot = join(args.yeaftDir, 'sessions');
|
|
453
453
|
let ids;
|
|
454
|
-
try { ids =
|
|
454
|
+
try { ids = listSessions(sessionsRoot).map(g => g.id); }
|
|
455
455
|
catch { return out; }
|
|
456
456
|
const empty = [];
|
|
457
457
|
for (const gid of ids) {
|
|
@@ -461,7 +461,7 @@ export async function bootInitEmptyGroups(args) {
|
|
|
461
461
|
if (segCount > 0) continue;
|
|
462
462
|
let hasMessages = false;
|
|
463
463
|
try {
|
|
464
|
-
const h =
|
|
464
|
+
const h = openSession(sessionsRoot, gid);
|
|
465
465
|
// Any message at all is enough — pull the first record off the
|
|
466
466
|
// iterator and stop.
|
|
467
467
|
const first = h.streamMessages().next();
|
|
@@ -516,18 +516,18 @@ export async function bootCatchUpStaleDream(args) {
|
|
|
516
516
|
if (!args || !args.dreamScheduler) return out;
|
|
517
517
|
|
|
518
518
|
const memoryRoot = join(args.yeaftDir, 'memory');
|
|
519
|
-
const
|
|
519
|
+
const sessionsRoot = join(args.yeaftDir, 'sessions');
|
|
520
520
|
const intervalMs = (args.intervalHours ?? DREAM_INTERVAL_HOURS) * 60 * 60 * 1000;
|
|
521
521
|
const now = args.now ?? Date.now();
|
|
522
522
|
|
|
523
|
-
let
|
|
524
|
-
try {
|
|
523
|
+
let sessionIds;
|
|
524
|
+
try { sessionIds = listSessions(sessionsRoot).map(g => g.id); }
|
|
525
525
|
catch { return out; }
|
|
526
526
|
|
|
527
527
|
// Find the newest lastDreamAt across all groups.
|
|
528
528
|
let newestAt = null;
|
|
529
529
|
let anyTraffic = false;
|
|
530
|
-
for (const gid of
|
|
530
|
+
for (const gid of sessionIds) {
|
|
531
531
|
let st;
|
|
532
532
|
try { st = await readGroupState(memoryRoot, gid); }
|
|
533
533
|
catch { continue; }
|
|
@@ -537,7 +537,7 @@ export async function bootCatchUpStaleDream(args) {
|
|
|
537
537
|
}
|
|
538
538
|
if (!anyTraffic) {
|
|
539
539
|
try {
|
|
540
|
-
const h =
|
|
540
|
+
const h = openSession(sessionsRoot, gid);
|
|
541
541
|
const first = h.streamMessages().next();
|
|
542
542
|
if (!first.done) anyTraffic = true;
|
|
543
543
|
} catch { /* keep going */ }
|
package/yeaft/dream-v2/state.js
CHANGED
|
@@ -59,11 +59,11 @@ const DREAM_BLOCK_CLOSE = '<!-- /dream-state -->';
|
|
|
59
59
|
* Read a group's .dream-state. Missing file → defaults.
|
|
60
60
|
*
|
|
61
61
|
* @param {string} root — memory root, e.g. ~/.yeaft/memory
|
|
62
|
-
* @param {string}
|
|
62
|
+
* @param {string} sessionId
|
|
63
63
|
* @returns {Promise<{ lastDreamMessageId: string|null, lastDreamAt: string|null, messageCount: number }>}
|
|
64
64
|
*/
|
|
65
|
-
export async function readGroupState(root,
|
|
66
|
-
const abs = join(root, 'group',
|
|
65
|
+
export async function readGroupState(root, sessionId) {
|
|
66
|
+
const abs = join(root, 'group', sessionId, STATE_FILE);
|
|
67
67
|
const empty = { lastDreamMessageId: null, lastDreamAt: null, messageCount: 0 };
|
|
68
68
|
let raw;
|
|
69
69
|
try { raw = await fsp.readFile(abs, 'utf8'); }
|
|
@@ -76,11 +76,11 @@ export async function readGroupState(root, groupId) {
|
|
|
76
76
|
* absent. Unknown fields are ignored.
|
|
77
77
|
*
|
|
78
78
|
* @param {string} root
|
|
79
|
-
* @param {string}
|
|
79
|
+
* @param {string} sessionId
|
|
80
80
|
* @param {{ lastDreamMessageId?: string|null, lastDreamAt?: string|null, messageCount?: number }} state
|
|
81
81
|
*/
|
|
82
|
-
export async function writeGroupState(root,
|
|
83
|
-
const dir = join(root, 'group',
|
|
82
|
+
export async function writeGroupState(root, sessionId, state) {
|
|
83
|
+
const dir = join(root, 'group', sessionId);
|
|
84
84
|
await fsp.mkdir(dir, { recursive: true });
|
|
85
85
|
const abs = join(dir, STATE_FILE);
|
|
86
86
|
const body =
|
|
@@ -131,7 +131,7 @@ function parseGroupState(raw) {
|
|
|
131
131
|
/**
|
|
132
132
|
* Resolve a memoryRoot + scope-string to the scope directory.
|
|
133
133
|
* The scope string is the same shape dream-v2 already uses internally:
|
|
134
|
-
* `'user'`, `'vp/<vpId>'`, `'group/<
|
|
134
|
+
* `'user'`, `'vp/<vpId>'`, `'group/<sessionId>'`, `'feature/<id>'`, etc.
|
|
135
135
|
*
|
|
136
136
|
* Pure path-join; does NOT create the directory. The writer creates it.
|
|
137
137
|
*
|