@yeaft/webchat-agent 0.1.665 → 0.1.667
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 +1 -24
- package/package.json +1 -1
- package/unify/cli.js +5 -84
- package/unify/config.js +2 -2
- package/unify/dream-v2/apply.js +1 -1
- package/unify/dream-v2/limits.js +1 -1
- package/unify/dream-v2/merge.js +1 -1
- package/unify/dream-v2/runner.js +2 -2
- package/unify/dream-v2/schedule.js +2 -2
- package/unify/dream-v2/segment.js +1 -1
- package/unify/dream-v2/session-wiring.js +2 -3
- package/unify/dream-v2/snapshot.js +1 -1
- package/unify/dream-v2/state.js +1 -1
- package/unify/dream-v2/triage.js +1 -1
- package/unify/engine.js +23 -133
- package/unify/eval/cases/memory.js +9 -142
- package/unify/features/summary.js +15 -98
- package/unify/index.js +0 -2
- package/unify/memory/ams.js +1 -1
- package/unify/memory/consolidate.js +10 -125
- package/unify/memory/segment-store.js +1 -1
- package/unify/memory/store-v2.js +6 -9
- package/unify/prompts.js +8 -50
- package/unify/session.js +2 -22
- package/unify/stop-hooks.js +8 -44
- package/unify/tools/index.js +0 -11
- package/unify/web-bridge.js +0 -98
- package/unify/memory/dream-shard.js +0 -722
- package/unify/memory/extract.js +0 -101
- package/unify/memory/layout.js +0 -358
- package/unify/memory/schema.js +0 -166
- package/unify/memory/shard-store.js +0 -373
- package/unify/memory/store.js +0 -578
- package/unify/memory/types.js +0 -139
- package/unify/memory/user-memory-store.js +0 -452
- package/unify/tools/memory-query.js +0 -134
- package/unify/tools/memory-read.js +0 -90
- package/unify/tools/memory-search.js +0 -140
- package/unify/tools/memory-trace.js +0 -135
- package/unify/tools/memory-write.js +0 -113
- package/unify/user-memory.js +0 -107
|
@@ -1,134 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* eval/cases/memory.js — Memory recall eval cases
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - Scope + tag filtering
|
|
7
|
-
* - LLM selection (when >7 candidates)
|
|
8
|
-
* - Fingerprint caching
|
|
9
|
-
* - Memory injection into system prompt
|
|
4
|
+
* Smoke-level evals for the H2-AMS memory pipeline. Detailed mocks live
|
|
5
|
+
* in unit tests; this file only declares the eval scenarios.
|
|
10
6
|
*/
|
|
11
7
|
|
|
12
|
-
import {
|
|
13
|
-
noError,
|
|
14
|
-
containsText,
|
|
15
|
-
custom,
|
|
16
|
-
} from '../runner.js';
|
|
17
|
-
|
|
18
|
-
// ─── Memory Recall Test Helpers ──────────────────────────────
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create an engine with pre-loaded memory entries for eval.
|
|
22
|
-
* Uses a mock MemoryStore that returns predefined entries.
|
|
23
|
-
*/
|
|
24
|
-
function createMockMemoryStore(entries) {
|
|
25
|
-
return {
|
|
26
|
-
readProfile: () => 'User is a senior TypeScript developer who prefers functional programming.',
|
|
27
|
-
readEntry: (name) => entries.find(e => e.name === name) || null,
|
|
28
|
-
readSection: () => '',
|
|
29
|
-
listEntries: () => entries,
|
|
30
|
-
findByFilter: ({ scope, tags, limit = 15 }) => {
|
|
31
|
-
// Simple scoring: scope match + tag overlap
|
|
32
|
-
return entries
|
|
33
|
-
.map(e => {
|
|
34
|
-
let score = 0;
|
|
35
|
-
if (scope && e.scope === scope) score += 3;
|
|
36
|
-
if (scope && e.scope === 'global') score += 1;
|
|
37
|
-
if (tags) {
|
|
38
|
-
for (const t of tags) {
|
|
39
|
-
if (e.tags && e.tags.includes(t)) score += 1;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return { ...e, _score: score };
|
|
43
|
-
})
|
|
44
|
-
.filter(e => e._score > 0)
|
|
45
|
-
.sort((a, b) => b._score - a._score)
|
|
46
|
-
.slice(0, limit);
|
|
47
|
-
},
|
|
48
|
-
bumpFrequency: () => {},
|
|
49
|
-
search: (keyword) => entries.filter(e =>
|
|
50
|
-
e.content.toLowerCase().includes(keyword.toLowerCase()) ||
|
|
51
|
-
e.name.toLowerCase().includes(keyword.toLowerCase()),
|
|
52
|
-
),
|
|
53
|
-
stats: () => ({ entryCount: entries.length, scopes: [], kinds: {} }),
|
|
54
|
-
writeEntry: () => 'test-entry',
|
|
55
|
-
writeEntries: () => [],
|
|
56
|
-
deleteEntry: () => true,
|
|
57
|
-
rebuildScopes: () => {},
|
|
58
|
-
addToSection: () => {},
|
|
59
|
-
writeProfile: () => {},
|
|
60
|
-
clear: () => {},
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const sampleMemoryEntries = [
|
|
65
|
-
{
|
|
66
|
-
name: 'typescript-strict-mode',
|
|
67
|
-
kind: 'preference',
|
|
68
|
-
scope: 'global',
|
|
69
|
-
tags: ['typescript', 'config', 'strict'],
|
|
70
|
-
importance: 'high',
|
|
71
|
-
frequency: 5,
|
|
72
|
-
content: 'User always uses TypeScript strict mode with noImplicitAny enabled.',
|
|
73
|
-
created_at: '2026-03-01T00:00:00Z',
|
|
74
|
-
updated_at: '2026-04-01T00:00:00Z',
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'prefers-vitest',
|
|
78
|
-
kind: 'preference',
|
|
79
|
-
scope: 'work/claude-web-chat',
|
|
80
|
-
tags: ['testing', 'vitest', 'framework'],
|
|
81
|
-
importance: 'normal',
|
|
82
|
-
frequency: 3,
|
|
83
|
-
content: 'User prefers vitest over jest for testing. Uses vitest for all new projects.',
|
|
84
|
-
created_at: '2026-03-15T00:00:00Z',
|
|
85
|
-
updated_at: '2026-04-01T00:00:00Z',
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: 'error-handling-pattern',
|
|
89
|
-
kind: 'lesson',
|
|
90
|
-
scope: 'global',
|
|
91
|
-
tags: ['error-handling', 'typescript', 'patterns'],
|
|
92
|
-
importance: 'high',
|
|
93
|
-
frequency: 4,
|
|
94
|
-
content: 'Always use Result<T, E> pattern instead of throwing exceptions. Wrap external API calls in try-catch and return Result.',
|
|
95
|
-
created_at: '2026-02-01T00:00:00Z',
|
|
96
|
-
updated_at: '2026-04-01T00:00:00Z',
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: 'project-structure',
|
|
100
|
-
kind: 'context',
|
|
101
|
-
scope: 'work/claude-web-chat',
|
|
102
|
-
tags: ['architecture', 'project', 'monorepo'],
|
|
103
|
-
importance: 'normal',
|
|
104
|
-
frequency: 2,
|
|
105
|
-
content: 'Project uses monorepo with agent/, server/, web/ directories. Agent code is in agent/unify/.',
|
|
106
|
-
created_at: '2026-01-01T00:00:00Z',
|
|
107
|
-
updated_at: '2026-03-01T00:00:00Z',
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: 'functional-programming',
|
|
111
|
-
kind: 'preference',
|
|
112
|
-
scope: 'global',
|
|
113
|
-
tags: ['functional', 'programming', 'style'],
|
|
114
|
-
importance: 'normal',
|
|
115
|
-
frequency: 6,
|
|
116
|
-
content: 'User prefers functional programming: pure functions, immutable data, map/filter/reduce over loops.',
|
|
117
|
-
created_at: '2026-01-15T00:00:00Z',
|
|
118
|
-
updated_at: '2026-04-05T00:00:00Z',
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: 'api-design-rest',
|
|
122
|
-
kind: 'skill',
|
|
123
|
-
scope: 'global',
|
|
124
|
-
tags: ['api', 'rest', 'design'],
|
|
125
|
-
importance: 'normal',
|
|
126
|
-
frequency: 1,
|
|
127
|
-
content: 'REST API conventions: use plural nouns, HTTP methods for CRUD, 2xx success, 4xx client error, 5xx server error.',
|
|
128
|
-
created_at: '2026-02-15T00:00:00Z',
|
|
129
|
-
updated_at: '2026-02-15T00:00:00Z',
|
|
130
|
-
},
|
|
131
|
-
];
|
|
8
|
+
import { noError, custom } from '../runner.js';
|
|
132
9
|
|
|
133
10
|
// ─── Eval Cases ──────────────────────────────────────────────
|
|
134
11
|
|
|
@@ -139,12 +16,8 @@ export const memoryCases = [
|
|
|
139
16
|
{
|
|
140
17
|
id: 'memory-profile-injection',
|
|
141
18
|
suite: 'memory',
|
|
142
|
-
description: 'System prompt should include
|
|
19
|
+
description: 'System prompt should include memory section after preflow',
|
|
143
20
|
prompt: 'Help me with a coding task',
|
|
144
|
-
setupEngine: (engine) => {
|
|
145
|
-
// We can't directly inject memoryStore here since Engine uses private fields
|
|
146
|
-
// Instead, this eval verifies via the adapter call log that system prompt contains memory
|
|
147
|
-
},
|
|
148
21
|
criteria: [
|
|
149
22
|
noError,
|
|
150
23
|
custom('has-response', 'Model produces a response', 5, (result) => ({
|
|
@@ -154,29 +27,23 @@ export const memoryCases = [
|
|
|
154
27
|
],
|
|
155
28
|
},
|
|
156
29
|
|
|
157
|
-
// ───
|
|
30
|
+
// ─── Recall Event ──────────────────────────────────────
|
|
158
31
|
|
|
159
32
|
{
|
|
160
33
|
id: 'memory-keyword-extraction',
|
|
161
34
|
suite: 'memory',
|
|
162
|
-
description: '
|
|
35
|
+
description: 'Recall event emitted when preflow has hits',
|
|
163
36
|
prompt: 'How should I handle TypeScript errors in my Express API?',
|
|
164
37
|
criteria: [
|
|
165
38
|
noError,
|
|
166
|
-
|
|
167
|
-
custom('recall-event', 'Recall event emitted (if memory store provided)', 3, (result) => {
|
|
168
|
-
// Without a real memory store this won't emit recall, so we check gracefully
|
|
39
|
+
custom('recall-event', 'Recall event emitted (if FTS index seeded)', 3, (result) => {
|
|
169
40
|
const recallEvent = result.events.find(e => e.type === 'recall');
|
|
170
41
|
return {
|
|
171
|
-
pass: true,
|
|
42
|
+
pass: true,
|
|
172
43
|
score: recallEvent ? 1 : 0.5,
|
|
173
|
-
reason: recallEvent ? `Recalled ${recallEvent.entryCount}
|
|
44
|
+
reason: recallEvent ? `Recalled ${recallEvent.entryCount} segments` : 'No FTS hits',
|
|
174
45
|
};
|
|
175
46
|
}),
|
|
176
47
|
],
|
|
177
48
|
},
|
|
178
49
|
];
|
|
179
|
-
|
|
180
|
-
// ─── Exported for direct import in unit tests ────────────────
|
|
181
|
-
|
|
182
|
-
export { createMockMemoryStore, sampleMemoryEntries };
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* summary.js —
|
|
2
|
+
* summary.js — Feature multi-VP collaboration summary protocol.
|
|
3
3
|
*
|
|
4
4
|
* Owns:
|
|
5
5
|
* - postSummary() — write a `type=summary` message to the group jsonl
|
|
6
|
-
* and run the extractor (B + C)
|
|
7
|
-
* - extractFeatureMemory() — turn a summary body into 2-5 feature-memory entries
|
|
8
|
-
* via 334f feature-memory shard lib (C)
|
|
9
6
|
* - buildSummaryReminder() — compute the §Δ31.4 3-AND soft reminder shape
|
|
10
|
-
*
|
|
11
|
-
* - buildFeatureCtxMemories() — assemble feature-memory top-5 (pinned + recent +
|
|
12
|
-
* tag relevance) for feature_ctx (E)
|
|
7
|
+
* - buildFeatureCtxMemories() — feature-memory top-5 (post-rip stub: returns [])
|
|
13
8
|
*
|
|
14
9
|
* Hard boundaries:
|
|
15
|
-
* - does NOT touch
|
|
16
|
-
* - does NOT
|
|
17
|
-
*
|
|
18
|
-
* the existing renderFeatureCtx contract)
|
|
19
|
-
* - does NOT self-loop-write VP-memory (extractor writes feature-memory only;
|
|
20
|
-
* VP-level synthesis is deferred to 334g dream)
|
|
21
|
-
* - softCap overflow does NOT create new shards (334f already routes into
|
|
22
|
-
* dream queue via projectDeriveHint; we just surface `needsRecompression`)
|
|
10
|
+
* - does NOT touch jsonl rotation internals (calls group.appendMessage)
|
|
11
|
+
* - does NOT write feature-memory shards — the H2-AMS rip retired the shard
|
|
12
|
+
* store; feature memory now flows through Dream V2's scope merge instead.
|
|
23
13
|
*/
|
|
24
14
|
|
|
25
15
|
import { join } from 'path';
|
|
26
|
-
import { openMemoryShardStore } from '../memory/shard-store.js';
|
|
27
|
-
import { AUTHORED_BY } from '../memory/schema.js';
|
|
28
16
|
|
|
29
17
|
// ─── §Δ31.4 soft-reminder thresholds ─────────────────────────────
|
|
30
18
|
/** Must be initiator AND members>1 AND (age≥20min OR turns≥10). */
|
|
@@ -119,36 +107,11 @@ export function postSummary(opts) {
|
|
|
119
107
|
},
|
|
120
108
|
});
|
|
121
109
|
|
|
122
|
-
// 2)
|
|
110
|
+
// 2) Feature-memory extraction is now owned by Dream V2 (per-group diff →
|
|
111
|
+
// triage → merge by target scope → atomic segments). The legacy in-line
|
|
112
|
+
// shard-store extractor was retired in the H2-AMS rip; we leave
|
|
113
|
+
// `memoryIds` empty so callers don't depend on inline-extracted IDs.
|
|
123
114
|
const memoryIds = [];
|
|
124
|
-
try {
|
|
125
|
-
const store = openMemoryShardStore(memoryDir, 'feature');
|
|
126
|
-
const raw = extractor(body) || [];
|
|
127
|
-
const bounded = clampExtracted(raw);
|
|
128
|
-
for (const [i, item] of bounded.entries()) {
|
|
129
|
-
const kind = EXTRACT_KINDS.includes(item.kind) ? item.kind : 'progress';
|
|
130
|
-
const shard = KIND_TO_SHARD[kind] || 'progress';
|
|
131
|
-
const id = `mem-${stored.id}-${i + 1}`;
|
|
132
|
-
store.put({
|
|
133
|
-
id,
|
|
134
|
-
shard,
|
|
135
|
-
kind,
|
|
136
|
-
featureId,
|
|
137
|
-
body: typeof item.body === 'string' ? item.body.trim() : '',
|
|
138
|
-
tags: Array.isArray(item.tags) ? item.tags.slice(0, 5) : [],
|
|
139
|
-
authoredBy: AUTHORED_BY.SUMMARY,
|
|
140
|
-
sourceRef: { featureId, msgIds: [stored.id] },
|
|
141
|
-
createdAt: new Date(now()).toISOString(),
|
|
142
|
-
});
|
|
143
|
-
memoryIds.push(id);
|
|
144
|
-
}
|
|
145
|
-
} catch (err) {
|
|
146
|
-
// Extractor failures must not fail the summary post; the message is
|
|
147
|
-
// already persisted (audit property). We return the empty memoryIds so
|
|
148
|
-
// callers can surface a warning if they want.
|
|
149
|
-
// eslint-disable-next-line no-console
|
|
150
|
-
console.warn('[task-334n] summary-extractor failed:', err?.message || err);
|
|
151
|
-
}
|
|
152
115
|
|
|
153
116
|
return {
|
|
154
117
|
message: stored,
|
|
@@ -241,62 +204,16 @@ export function buildSummaryReminder(input) {
|
|
|
241
204
|
|
|
242
205
|
/**
|
|
243
206
|
* Assemble feature-memory top-5 for 334e's `featureCtx.memories` field.
|
|
244
|
-
*
|
|
245
|
-
*
|
|
207
|
+
* Post-rip stub: the underlying shard store was retired in the H2-AMS rip;
|
|
208
|
+
* Dream V2 owns feature-scope memory now. Returning `[]` keeps the prompt
|
|
209
|
+
* shape valid until a follow-up wires the new scope-summary read path.
|
|
246
210
|
*
|
|
247
|
-
* @param {string} memoryDir
|
|
211
|
+
* @param {string} memoryDir unused (kept for callsite compatibility)
|
|
248
212
|
* @param {{ tags?: string[], top?: number }} [opts]
|
|
249
213
|
* @returns {Array<{body:string, shard:string, authoredBy?:string}>}
|
|
250
214
|
*/
|
|
251
|
-
export function buildFeatureCtxMemories(
|
|
252
|
-
|
|
253
|
-
const tagHints = Array.isArray(opts.tags) ? opts.tags : [];
|
|
254
|
-
const nowMs = typeof opts.now === 'number' ? opts.now : Date.now();
|
|
255
|
-
let results = [];
|
|
256
|
-
try {
|
|
257
|
-
const store = openMemoryShardStore(memoryDir, 'feature');
|
|
258
|
-
const q = store.query({});
|
|
259
|
-
const hits = (q.results || [])
|
|
260
|
-
.filter((r) => !r.supersededBy)
|
|
261
|
-
.map((r) => {
|
|
262
|
-
const full = store.get(r.id);
|
|
263
|
-
return {
|
|
264
|
-
id: r.id,
|
|
265
|
-
shard: r.shard || 'general',
|
|
266
|
-
body: full?.body || '',
|
|
267
|
-
tags: Array.isArray(r.tags) ? r.tags : [],
|
|
268
|
-
pinned: !!r.pinned,
|
|
269
|
-
createdAt: full?.createdAt || null,
|
|
270
|
-
authoredBy: full?.authoredBy || null,
|
|
271
|
-
};
|
|
272
|
-
})
|
|
273
|
-
.filter((r) => r.body && r.body.trim());
|
|
274
|
-
|
|
275
|
-
const score = (r) => {
|
|
276
|
-
let s = 0;
|
|
277
|
-
if (r.pinned) s += 1000;
|
|
278
|
-
if (r.createdAt) {
|
|
279
|
-
const ageMs = nowMs - new Date(r.createdAt).getTime();
|
|
280
|
-
const halfLifeMs = 24 * 60 * 60 * 1000;
|
|
281
|
-
s += Math.max(0, 100 * Math.pow(0.5, Math.max(0, ageMs) / halfLifeMs));
|
|
282
|
-
}
|
|
283
|
-
for (const t of tagHints) if (r.tags.includes(t)) s += 5;
|
|
284
|
-
return s;
|
|
285
|
-
};
|
|
286
|
-
hits.sort((a, b) => {
|
|
287
|
-
const ds = score(b) - score(a);
|
|
288
|
-
if (ds !== 0) return ds;
|
|
289
|
-
return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
|
|
290
|
-
});
|
|
291
|
-
results = hits.slice(0, top).map((r) => ({
|
|
292
|
-
body: r.body,
|
|
293
|
-
shard: r.shard,
|
|
294
|
-
...(r.authoredBy ? { authoredBy: r.authoredBy } : {}),
|
|
295
|
-
}));
|
|
296
|
-
} catch {
|
|
297
|
-
results = [];
|
|
298
|
-
}
|
|
299
|
-
return results;
|
|
215
|
+
export function buildFeatureCtxMemories(_memoryDir, _opts = {}) {
|
|
216
|
+
return [];
|
|
300
217
|
}
|
|
301
218
|
|
|
302
219
|
// ─── (F) related-feature ACL fail-closed gate ─────────────────────
|
package/unify/index.js
CHANGED
|
@@ -22,10 +22,8 @@ export { buildSystemPrompt, SUPPORTED_LANGUAGES } from './prompts.js';
|
|
|
22
22
|
export { Engine } from './engine.js';
|
|
23
23
|
export { ConversationStore, parseMessage, estimateTokens } from './conversation/persist.js';
|
|
24
24
|
export { searchMessages } from './conversation/search.js';
|
|
25
|
-
export { MemoryStore, parseEntry, serializeEntry, MEMORY_KINDS } from './memory/store.js';
|
|
26
25
|
|
|
27
26
|
// Phase 5: Advanced features
|
|
28
|
-
export { KINDS, KIND_PRIORITY, KIND_DESCRIPTIONS, IMPORTANCE_LEVELS, validateEntry, parseScopePath, getAncestorScopes, areScopesRelated } from './memory/types.js';
|
|
29
27
|
export { runStopHooks } from './stop-hooks.js';
|
|
30
28
|
export { MCPManager, createMCPManager } from './mcp.js';
|
|
31
29
|
export { SkillManager, createSkillManager, parseSkill, serializeSkill } from './skills.js';
|
package/unify/memory/ams.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* truth is `<scope>/memory.md` + `<scope>/summary.md`. AMS itself
|
|
12
12
|
* doesn't write to disk — that's Dream's job.
|
|
13
13
|
*
|
|
14
|
-
* Privacy
|
|
14
|
+
* Privacy: `vp/<other>` scopes are ALWAYS filtered out
|
|
15
15
|
* for any worker that isn't `<other>`. The owning code passes its own
|
|
16
16
|
* vpId at construction.
|
|
17
17
|
*/
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* consolidate.js —
|
|
2
|
+
* consolidate.js — Hot-window budget partitioning utilities.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 2. Extract memory entries → write to entries/ ("long-term memory")
|
|
4
|
+
* Reduced surface (PR-B rip): the legacy LLM-driven consolidate() pipeline
|
|
5
|
+
* (compact summary + entries-store extraction) has been retired. The only
|
|
6
|
+
* survivors are the pure functions used by the compact orchestrator:
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
8
|
+
* - shouldConsolidate(store, budget) — decide when to compact
|
|
9
|
+
* - partitionMessages(messages, budget) — split hot messages into
|
|
10
|
+
* toArchive / toKeep based on token budget
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Memory extraction is now owned by Dream V2 (per-group diff -> triage ->
|
|
13
|
+
* merge by target scope -> apply via segment-store + summary-store).
|
|
14
|
+
* Conversation summarisation lives in compact/orchestrator.js's hooks.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { extractMemories } from './extract.js';
|
|
18
|
-
import { pickEffort } from '../effort.js';
|
|
19
|
-
|
|
20
17
|
// ─── Constants ──────────────────────────────────────────────────
|
|
21
18
|
|
|
22
19
|
/** Default MESSAGE_TOKEN_BUDGET (context * 4%, default ~8192). */
|
|
@@ -28,8 +25,6 @@ export const COMPACT_KEEP_RATIO = 0.4;
|
|
|
28
25
|
/** Minimum messages to keep hot (newest). */
|
|
29
26
|
const MIN_KEEP_MESSAGES = 3;
|
|
30
27
|
|
|
31
|
-
// ─── Consolidate ────────────────────────────────────────────────
|
|
32
|
-
|
|
33
28
|
/**
|
|
34
29
|
* Check if consolidation should be triggered.
|
|
35
30
|
*
|
|
@@ -81,113 +76,3 @@ export function partitionMessages(messages, budget = DEFAULT_MESSAGE_TOKEN_BUDGE
|
|
|
81
76
|
toKeep: messages.slice(keepStart),
|
|
82
77
|
};
|
|
83
78
|
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Generate a compact summary of messages.
|
|
87
|
-
*
|
|
88
|
-
* @param {object[]} messages — messages to summarize
|
|
89
|
-
* @param {object} adapter — LLM adapter with .call()
|
|
90
|
-
* @param {object} config — { model }
|
|
91
|
-
* @returns {Promise<string>} — compact summary text
|
|
92
|
-
*/
|
|
93
|
-
async function generateSummary(messages, adapter, config) {
|
|
94
|
-
const conversation = messages.map(m => {
|
|
95
|
-
const prefix = m.role === 'user' ? 'User' : m.role === 'assistant' ? 'Assistant' : m.role;
|
|
96
|
-
return `[${prefix}]: ${(m.content || '').slice(0, 500)}`;
|
|
97
|
-
}).join('\n\n');
|
|
98
|
-
|
|
99
|
-
const system = 'You are a conversation summarizer. Summarize the conversation concisely in 2-3 paragraphs, preserving key decisions, facts, and context. Write in the same language as the conversation.';
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const result = await adapter.call({
|
|
103
|
-
model: config.model,
|
|
104
|
-
system,
|
|
105
|
-
messages: [{ role: 'user', content: `Summarize this conversation:\n\n${conversation}` }],
|
|
106
|
-
maxTokens: 1024,
|
|
107
|
-
// task-327c: consolidate is a high-complexity side-query; flag as
|
|
108
|
-
// 'max' effort so supported models use extended thinking / reasoning.
|
|
109
|
-
// Router/adapter silently drops the param for models that don't
|
|
110
|
-
// support thinking, or when UNIFY_THINKING_V1 is off.
|
|
111
|
-
effort: pickEffort({ scenario: 'consolidate' }),
|
|
112
|
-
});
|
|
113
|
-
return result.text.trim();
|
|
114
|
-
} catch {
|
|
115
|
-
// Fallback: simple concatenation of first/last messages
|
|
116
|
-
const first = messages[0]?.content?.slice(0, 200) || '';
|
|
117
|
-
const last = messages[messages.length - 1]?.content?.slice(0, 200) || '';
|
|
118
|
-
return `[Auto-summary failed] Started with: ${first}... Ended with: ${last}`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Run the full Consolidate pipeline.
|
|
124
|
-
*
|
|
125
|
-
* 1. Partition messages (what to archive vs keep)
|
|
126
|
-
* 2. Generate compact summary (LLM call)
|
|
127
|
-
* 3. Extract memory entries (LLM call)
|
|
128
|
-
* 4. Move archived messages to cold/
|
|
129
|
-
* 5. Update compact.md, index.md, scopes.md
|
|
130
|
-
*
|
|
131
|
-
* @param {{
|
|
132
|
-
* conversationStore: import('../conversation/persist.js').ConversationStore,
|
|
133
|
-
* memoryStore: import('./store.js').MemoryStore,
|
|
134
|
-
* adapter: object,
|
|
135
|
-
* config: object,
|
|
136
|
-
* budget?: number
|
|
137
|
-
* }} params
|
|
138
|
-
* @returns {Promise<{ compactSummary: string, extractedEntries: string[], archivedCount: number }>}
|
|
139
|
-
*/
|
|
140
|
-
export async function consolidate({ conversationStore, memoryStore, adapter, config, budget = DEFAULT_MESSAGE_TOKEN_BUDGET }) {
|
|
141
|
-
// Load all hot messages
|
|
142
|
-
const messages = conversationStore.loadAll();
|
|
143
|
-
|
|
144
|
-
if (messages.length <= MIN_KEEP_MESSAGES) {
|
|
145
|
-
return { compactSummary: '', extractedEntries: [], archivedCount: 0 };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Step 1: Partition
|
|
149
|
-
const { toArchive, toKeep } = partitionMessages(messages, budget);
|
|
150
|
-
|
|
151
|
-
if (toArchive.length === 0) {
|
|
152
|
-
return { compactSummary: '', extractedEntries: [], archivedCount: 0 };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Step 2: Generate compact summary
|
|
156
|
-
const compactSummary = await generateSummary(toArchive, adapter, config);
|
|
157
|
-
|
|
158
|
-
// Step 3: Extract memory entries
|
|
159
|
-
const extracted = await extractMemories({ messages: toArchive, adapter, config });
|
|
160
|
-
|
|
161
|
-
// Step 4: Move archived messages to cold
|
|
162
|
-
const archiveIds = toArchive.map(m => m.id).filter(Boolean);
|
|
163
|
-
conversationStore.moveToColdBatch(archiveIds);
|
|
164
|
-
|
|
165
|
-
// Step 5a: Update compact.md
|
|
166
|
-
if (compactSummary) {
|
|
167
|
-
conversationStore.updateCompactSummary(compactSummary);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Step 5b: Write extracted memory entries
|
|
171
|
-
const entryNames = [];
|
|
172
|
-
for (const entry of extracted) {
|
|
173
|
-
const slug = memoryStore.writeEntry(entry);
|
|
174
|
-
entryNames.push(slug);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Step 5c: Update index.md
|
|
178
|
-
const lastMsg = toKeep[toKeep.length - 1];
|
|
179
|
-
conversationStore.updateIndex({
|
|
180
|
-
lastMessageId: lastMsg?.id || null,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Step 5d: Rebuild scopes.md
|
|
184
|
-
if (entryNames.length > 0) {
|
|
185
|
-
memoryStore.rebuildScopes();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
compactSummary,
|
|
190
|
-
extractedEntries: entryNames,
|
|
191
|
-
archivedCount: archiveIds.length,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* segment blocks) and the SQLite segment index. This layer handles
|
|
6
6
|
* scope <-> file path mapping; the index layer is scope-agnostic.
|
|
7
7
|
*
|
|
8
|
-
* Path conventions
|
|
8
|
+
* Path conventions:
|
|
9
9
|
* ~/.yeaft/memory/user/memory.md
|
|
10
10
|
* ~/.yeaft/memory/vp/<id>/memory.md
|
|
11
11
|
* ~/.yeaft/memory/group/<id>/memory.md
|
package/unify/memory/store-v2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memory/store-v2.js —
|
|
2
|
+
* memory/store-v2.js — per-scope memory.md + summary.md (Layer-A storage).
|
|
3
3
|
*
|
|
4
4
|
* One pair of files per scope. No shards, no entries/, no index.md, no
|
|
5
5
|
* index.json. The five scope kinds — user, vp, group, feature, topic — share
|
|
@@ -29,18 +29,15 @@
|
|
|
29
29
|
* ACL:
|
|
30
30
|
* - This module enforces ONE ACL: `vp/<other>` paths are blocked when
|
|
31
31
|
* `currentVpId` is given and differs from `<other>`. Every other scope
|
|
32
|
-
* boundary is ACL-free
|
|
32
|
+
* boundary is ACL-free.
|
|
33
33
|
*
|
|
34
34
|
* What this module deliberately does NOT do:
|
|
35
35
|
* - No frontmatter parsing. memory.md and summary.md are pure markdown;
|
|
36
36
|
* the dream-state metadata block lives at the file's tail and is read
|
|
37
37
|
* by `dream-v2/state.js`, not here.
|
|
38
38
|
* - No LLM calls, no extraction, no summarisation. Pure I/O.
|
|
39
|
-
* - No legacy R6 fallback. The old MemoryStore (memory/store.js) and
|
|
40
|
-
* ScopeTree (memory/scope-tree.js) remain in service until PR-E swaps
|
|
41
|
-
* callers; this module is additive.
|
|
42
39
|
*
|
|
43
|
-
* Reference: agent/unify/memory/DESIGN-
|
|
40
|
+
* Reference: agent/unify/memory/DESIGN-H2-AMS.md.
|
|
44
41
|
*/
|
|
45
42
|
|
|
46
43
|
import {
|
|
@@ -228,8 +225,8 @@ export async function writeMemory(scope, content, opts = {}) {
|
|
|
228
225
|
}
|
|
229
226
|
|
|
230
227
|
/**
|
|
231
|
-
* Append to a scope's memory.md. Used by the rare "direct write" path
|
|
232
|
-
*
|
|
228
|
+
* Append to a scope's memory.md. Used by the rare "direct write" path;
|
|
229
|
+
* main flow is dream-driven rewrites.
|
|
233
230
|
*
|
|
234
231
|
* Append is non-atomic with concurrent readers in the strict sense, but a
|
|
235
232
|
* single appendFile of a small buffer is atomic at the kernel level on POSIX
|
|
@@ -316,7 +313,7 @@ export async function ensureScope(scope, opts = {}) {
|
|
|
316
313
|
|
|
317
314
|
/**
|
|
318
315
|
* Enumerate all scopes present on disk. Returns Scope shapes that round-trip
|
|
319
|
-
* back through `scopeDir`. Used by Triage
|
|
316
|
+
* back through `scopeDir`. Used by Triage to list candidate
|
|
320
317
|
* scopes for a group's diff.
|
|
321
318
|
*
|
|
322
319
|
* Walks shallowly:
|
package/unify/prompts.js
CHANGED
|
@@ -8,39 +8,13 @@
|
|
|
8
8
|
* and used to enrich the system prompt beyond the hardcoded fallbacks.
|
|
9
9
|
*
|
|
10
10
|
* Phase 2 additions:
|
|
11
|
-
* - Memory section (
|
|
11
|
+
* - Memory section (recalled segments via H2-AMS pre-flow)
|
|
12
12
|
* - Compact summary section (conversation history summary)
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - Legacy `memory={profile,entries}` param still supported for callers
|
|
19
|
-
* (tests, CLI) that have not migrated.
|
|
20
|
-
*
|
|
21
|
-
* task-334e additions (R6 §Δ24.5 / §Δ27.3 / §Δ31.4 / §Δ29.3):
|
|
22
|
-
* - `taskCtx` param → renders a `## task_ctx` block with:
|
|
23
|
-
* * task-memory top-5 bodies with semantic shard prefix `[shard]`
|
|
24
|
-
* (no sourceRef — memory_trace opens the trail on demand)
|
|
25
|
-
* * `### related tasks` sub-section — `relatedTaskIds` top-3 (sorted
|
|
26
|
-
* by updatedAt desc) + per-task top-2 memory; ACL-gated by
|
|
27
|
-
* `target.members` ∋ currentVpId (§Δ31.4)
|
|
28
|
-
* * `### summary reminder` soft nudge — condition (§Δ27.3):
|
|
29
|
-
* non-summary msgs ≥ 3 AND since-lastSummary > 15min AND
|
|
30
|
-
* currentVpId == task.initiatorVpId → emits a DYNAMIC hint
|
|
31
|
-
* - `userProfile` param → renders a `## user_profile` block. Stub
|
|
32
|
-
* implementation: when not passed explicitly, we fall back to reading
|
|
33
|
-
* `~/.yeaft/user/profile.json` ({ "content": "…string…" }) if present
|
|
34
|
-
* (§Δ29.3 placeholder until 334l wires real user-memory recall).
|
|
35
|
-
* - `coreMemory` param → renders a `## core_memory` block with recall
|
|
36
|
-
* top-7 memory bodies (no sourceRef) and a trailing meta line pointing
|
|
37
|
-
* at `memory_trace` as the way to open the original message.
|
|
38
|
-
*
|
|
39
|
-
* Hard constraints (task-334e contract):
|
|
40
|
-
* - Does NOT modify engine.js turn loop.
|
|
41
|
-
* - Does NOT implement memory_trace / open_source_message (task-334f).
|
|
42
|
-
* - Does NOT implement task_summary_post (task-334n).
|
|
43
|
-
* - Changes limited to prompts.js + templates/.
|
|
14
|
+
* Memory injection (H2-AMS):
|
|
15
|
+
* - `memoryInjection` carries prebuilt FTS-recall text from
|
|
16
|
+
* `memory/preflow.js` over the relevant scopes (user/group/vp/feature/
|
|
17
|
+
* global). Engine passes it every turn after preflow runs.
|
|
44
18
|
*
|
|
45
19
|
* Reference: yeaft-unify-system-prompt-budget.md — Static + Dynamic + Context layers
|
|
46
20
|
*/
|
|
@@ -368,27 +342,11 @@ export function buildSystemPrompt({
|
|
|
368
342
|
}
|
|
369
343
|
|
|
370
344
|
// ─── 6. Memory Section ─────────────────────────────────
|
|
345
|
+
// FTS5 pre-flow recall + AMS snapshot are concatenated upstream by the
|
|
346
|
+
// engine into a single `memoryInjection` block. The legacy entries-based
|
|
347
|
+
// memory.profile / memory.entries shape was retired in the H2-AMS rip.
|
|
371
348
|
if (memoryInjection && memoryInjection.trim()) {
|
|
372
|
-
// New path (task-287): prebuilt injection from memory/layout.buildMemoryInjection()
|
|
373
|
-
// Contains index.md + user-preferences.md + optional project header excerpt.
|
|
374
349
|
parts.push(memoryInjection.trim());
|
|
375
|
-
} else if (memory && (memory.profile || (memory.entries && memory.entries.length > 0))) {
|
|
376
|
-
// Legacy path — kept for callers (tests, CLI) that have not migrated yet.
|
|
377
|
-
const memoryParts = [lang.memoryHeader];
|
|
378
|
-
|
|
379
|
-
if (memory.profile) {
|
|
380
|
-
memoryParts.push(`${lang.profileHeader}\n${memory.profile}`);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (memory.entries && memory.entries.length > 0) {
|
|
384
|
-
const entryLines = memory.entries.map(e => {
|
|
385
|
-
const tags = (e.tags && e.tags.length > 0) ? ` [${e.tags.join(', ')}]` : '';
|
|
386
|
-
return `- **${e.name}** (${e.kind}): ${e.content}${tags}`;
|
|
387
|
-
});
|
|
388
|
-
memoryParts.push(`${lang.recalledHeader}\n${entryLines.join('\n')}`);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
parts.push(memoryParts.join('\n\n'));
|
|
392
350
|
}
|
|
393
351
|
|
|
394
352
|
// ─── 7. Compact Summary Section ────────────────────────
|