@yeaft/webchat-agent 0.1.661 → 0.1.663
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/package.json +1 -1
- package/unify/cli.js +0 -10
- package/unify/engine.js +232 -5
- package/unify/memory/ams-registry.js +279 -0
- package/unify/session.js +38 -53
- package/unify/stop-hooks.js +8 -44
- package/unify/memory/dream-prompt.js +0 -272
- package/unify/memory/dream.js +0 -783
- package/unify/memory/migrate-r6-to-v2.js +0 -462
- package/unify/router/vp-planner.js +0 -341
package/unify/session.js
CHANGED
|
@@ -32,18 +32,23 @@ import { Engine } from './engine.js';
|
|
|
32
32
|
// SegmentIndex (SQLite FTS5 over memory.md) and passes it to the
|
|
33
33
|
// Engine. Engine.#recallMemory routes pre-turn recall through
|
|
34
34
|
// groups/pre-flow.js → memory/preflow.js (the previous per-scope
|
|
35
|
-
// file reader recall-v2.js has been deleted).
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
35
|
+
// file reader recall-v2.js has been deleted).
|
|
36
|
+
//
|
|
37
|
+
// GC.1 follow-up: when memoryIndex is wired we also open an
|
|
38
|
+
// AmsRegistry. The registry caches per-group ActiveMemorySet
|
|
39
|
+
// instances and persists their identity-only state under
|
|
40
|
+
// `~/.yeaft/memory/groups/<gid>/ams.json` so a deactivated group
|
|
41
|
+
// resumes with the same onDemand/recent membership it had on
|
|
42
|
+
// disconnect. Engine.#runQuery uses the registry to populate the
|
|
43
|
+
// AMS each turn and to run `memory/adjust.js` post-turn.
|
|
39
44
|
import { ensureDefaultGroupIfEmpty } from './groups/group-crud.js';
|
|
40
45
|
import { seedDefaultVps } from './vp/seed-defaults.js';
|
|
41
46
|
import { createV2DreamScheduler } from './dream-v2/session-wiring.js';
|
|
42
47
|
import { openSegmentIndex } from './memory/index-db.js';
|
|
43
48
|
import { syncAll as syncSegmentIndex } from './memory/segment-sync.js';
|
|
44
|
-
import {
|
|
49
|
+
import { openAmsRegistry } from './memory/ams-registry.js';
|
|
45
50
|
import { join } from 'path';
|
|
46
|
-
import { existsSync as existsSyncSafe, readFileSync as readFileSyncSafe
|
|
51
|
+
import { existsSync as existsSyncSafe, readFileSync as readFileSyncSafe } from 'fs';
|
|
47
52
|
|
|
48
53
|
/**
|
|
49
54
|
* @typedef {Object} SessionOptions
|
|
@@ -140,53 +145,10 @@ export async function loadSession(options = {}) {
|
|
|
140
145
|
}
|
|
141
146
|
} catch { /* never let this warn path block session load */ }
|
|
142
147
|
|
|
143
|
-
// ─── 2.2
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// the worst case on re-run is "no R6 dirs found, exit clean".
|
|
148
|
-
// Failure here MUST NOT block session boot — we log + continue;
|
|
149
|
-
// dream will gradually backfill v2 from group diffs.
|
|
150
|
-
try {
|
|
151
|
-
if (config?.memoryV2 === true && !config?._readOnly) {
|
|
152
|
-
const memoryRoot = join(yeaftDir, 'memory');
|
|
153
|
-
const stateFile = join(yeaftDir, '.memory-v2-migration.json');
|
|
154
|
-
let alreadyMigrated = false;
|
|
155
|
-
if (existsSyncSafe(stateFile)) {
|
|
156
|
-
try {
|
|
157
|
-
const state = JSON.parse(readFileSyncSafe(stateFile, 'utf8') || '{}');
|
|
158
|
-
alreadyMigrated = Boolean(state && state.completedAt);
|
|
159
|
-
} catch { /* malformed state → re-run; migration is idempotent */ }
|
|
160
|
-
}
|
|
161
|
-
const hasR6 = existsSyncSafe(join(memoryRoot, 'groups'))
|
|
162
|
-
|| existsSyncSafe(join(memoryRoot, 'features'));
|
|
163
|
-
if (!alreadyMigrated && hasR6) {
|
|
164
|
-
console.log('[Yeaft] memoryV2 on + R6 layout detected — running one-shot migration…');
|
|
165
|
-
const result = await migrateR6toV2({ root: memoryRoot, apply: true });
|
|
166
|
-
try {
|
|
167
|
-
writeFileSyncSafe(stateFile, JSON.stringify({
|
|
168
|
-
completedAt: new Date().toISOString(),
|
|
169
|
-
migratedScopes: result.migratedScopes,
|
|
170
|
-
skippedScopes: result.skippedScopes,
|
|
171
|
-
errors: result.errors,
|
|
172
|
-
backedUpTo: result.backedUpTo,
|
|
173
|
-
}, null, 2));
|
|
174
|
-
} catch { /* state file write is best-effort */ }
|
|
175
|
-
console.log(`[Yeaft] memory v2 migration done — ${result.migratedScopes} scopes migrated, backup at ${result.backedUpTo}`);
|
|
176
|
-
} else if (!alreadyMigrated && !hasR6) {
|
|
177
|
-
// Fresh user, no R6 to migrate. Mark as done so we don't keep checking.
|
|
178
|
-
try {
|
|
179
|
-
writeFileSyncSafe(stateFile, JSON.stringify({
|
|
180
|
-
completedAt: new Date().toISOString(),
|
|
181
|
-
migratedScopes: 0,
|
|
182
|
-
note: 'no R6 layout present — fresh v2',
|
|
183
|
-
}, null, 2));
|
|
184
|
-
} catch { /* best-effort */ }
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} catch (err) {
|
|
188
|
-
console.warn(`[Yeaft] memory v2 migration skipped due to error: ${err?.message || err}`);
|
|
189
|
-
}
|
|
148
|
+
// ─── 2.2 R6 → v2 auto-migration retired ───────────────
|
|
149
|
+
// The R6 shard layout is gone — memory writes go through
|
|
150
|
+
// dream-v2 directly. Existing users have already migrated
|
|
151
|
+
// (state file in ~/.yeaft/.memory-v2-migration.json).
|
|
190
152
|
|
|
191
153
|
// ─── 2a. Permission pre-check ─────────────────────────
|
|
192
154
|
// If the data dir is not writable, mark session as read-only.
|
|
@@ -250,6 +212,22 @@ export async function loadSession(options = {}) {
|
|
|
250
212
|
}
|
|
251
213
|
}
|
|
252
214
|
|
|
215
|
+
// ─── 5-ams. (GC.1 follow-up) Group-keyed AMS registry ────
|
|
216
|
+
// The registry caches one ActiveMemorySet per groupId and
|
|
217
|
+
// persists their state to disk so a deactivated group can be
|
|
218
|
+
// reactivated with the same onDemand/recent membership it had
|
|
219
|
+
// on disconnect. Without memoryIndex we have nothing to
|
|
220
|
+
// re-hydrate against, so the registry is left null in that case.
|
|
221
|
+
let amsRegistry = null;
|
|
222
|
+
if (memoryIndex && !config._readOnly) {
|
|
223
|
+
try {
|
|
224
|
+
amsRegistry = openAmsRegistry({ yeaftDir, memoryIndex, config });
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.warn(`[Yeaft] Failed to open AMS registry (adjust disabled): ${err?.message || err}`);
|
|
227
|
+
amsRegistry = null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
253
231
|
// ─── 5a. Initialize feature store ──────────────────────
|
|
254
232
|
initFeatureStore(yeaftDir, { readOnly: config._readOnly || false });
|
|
255
233
|
|
|
@@ -315,6 +293,7 @@ export async function loadSession(options = {}) {
|
|
|
315
293
|
memoryStore,
|
|
316
294
|
memoryShardStore,
|
|
317
295
|
memoryIndex,
|
|
296
|
+
amsRegistry,
|
|
318
297
|
toolRegistry,
|
|
319
298
|
skillManager,
|
|
320
299
|
mcpManager,
|
|
@@ -372,6 +351,11 @@ export async function loadSession(options = {}) {
|
|
|
372
351
|
} catch {
|
|
373
352
|
// Best-effort cleanup
|
|
374
353
|
}
|
|
354
|
+
try {
|
|
355
|
+
if (amsRegistry) amsRegistry.persistAll();
|
|
356
|
+
} catch {
|
|
357
|
+
// Best-effort cleanup
|
|
358
|
+
}
|
|
375
359
|
}
|
|
376
360
|
|
|
377
361
|
return {
|
|
@@ -388,6 +372,7 @@ export async function loadSession(options = {}) {
|
|
|
388
372
|
trace,
|
|
389
373
|
yeaftDir,
|
|
390
374
|
status,
|
|
375
|
+
amsRegistry,
|
|
391
376
|
shutdown,
|
|
392
377
|
// task-325c: user-initiated abort API. Delegates to web-bridge which
|
|
393
378
|
// owns the single AbortController. Lazy-imported to avoid a hard cycle
|
package/unify/stop-hooks.js
CHANGED
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
* Runs after each query loop completes:
|
|
5
5
|
* 1. Persist messages to conversation/messages/
|
|
6
6
|
* 2. Consolidate check (compact + extract) — only when budget exceeded
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* Dream V2 owns all background memory maintenance (scope summaries +
|
|
9
|
+
* memory writes via dream-v2/session-wiring.js → createV2DreamScheduler);
|
|
10
|
+
* the legacy `memory/dream.js` gate that used to fire here was retired
|
|
11
|
+
* alongside recall-r6.
|
|
9
12
|
*
|
|
10
13
|
* Reference: yeaft-unify-core-systems.md §4.4
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import { shouldConsolidate, consolidate } from './memory/consolidate.js';
|
|
14
|
-
import { checkDreamGate, incrementQueryCount, dream } from './memory/dream.js';
|
|
15
17
|
import { isPermissionError } from './init.js';
|
|
16
18
|
|
|
17
19
|
/** Track whether we've already warned about permission issues in stop hooks. */
|
|
@@ -59,7 +61,6 @@ export async function runStopHooks(context) {
|
|
|
59
61
|
const result = {
|
|
60
62
|
messagesPersisted: 0,
|
|
61
63
|
consolidated: false,
|
|
62
|
-
dreamTriggered: false,
|
|
63
64
|
errors: [],
|
|
64
65
|
};
|
|
65
66
|
|
|
@@ -164,45 +165,9 @@ export async function runStopHooks(context) {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
// 3.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
incrementQueryCount(yeaftDir);
|
|
171
|
-
}
|
|
172
|
-
} catch (err) {
|
|
173
|
-
if (isPermissionError(err)) {
|
|
174
|
-
// Silent — already warned about permission issues
|
|
175
|
-
} else {
|
|
176
|
-
result.errors.push(`Dream counter failed: ${err.message}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// 4. Dream gate check (fire-and-forget, background)
|
|
181
|
-
try {
|
|
182
|
-
if (yeaftDir && memoryStore && adapter) {
|
|
183
|
-
const gate = checkDreamGate(yeaftDir);
|
|
184
|
-
if (gate.shouldDream) {
|
|
185
|
-
result.dreamTriggered = true;
|
|
186
|
-
// Fire and forget — dream runs in background
|
|
187
|
-
dream({
|
|
188
|
-
yeaftDir,
|
|
189
|
-
memoryStore,
|
|
190
|
-
conversationStore,
|
|
191
|
-
adapter,
|
|
192
|
-
config,
|
|
193
|
-
}).catch(err => {
|
|
194
|
-
trace?.logEvent({
|
|
195
|
-
eventType: 'dream_error',
|
|
196
|
-
eventData: { error: err.message },
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
} catch (err) {
|
|
202
|
-
if (!isPermissionError(err)) {
|
|
203
|
-
result.errors.push(`Dream gate check failed: ${err.message}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
168
|
+
// 3. Dream V2 owns background scope-memory maintenance via the session
|
|
169
|
+
// dream scheduler (createV2DreamScheduler). No legacy dream gate is
|
|
170
|
+
// invoked here; the scheduler decides when to run on its own cadence.
|
|
206
171
|
|
|
207
172
|
return result;
|
|
208
173
|
}
|
|
@@ -211,6 +176,5 @@ export async function runStopHooks(context) {
|
|
|
211
176
|
* @typedef {Object} StopHookResult
|
|
212
177
|
* @property {number} messagesPersisted — how many messages were persisted
|
|
213
178
|
* @property {boolean} consolidated — whether consolidation ran
|
|
214
|
-
* @property {boolean} dreamTriggered — whether dream was triggered
|
|
215
179
|
* @property {string[]} errors — any non-fatal errors
|
|
216
180
|
*/
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dream-prompt.js — Dream prompt templates for each phase
|
|
3
|
-
*
|
|
4
|
-
* Dream has 5 phases:
|
|
5
|
-
* Phase 1: Orient — assess current memory state
|
|
6
|
-
* Phase 2: Gather — collect recent context
|
|
7
|
-
* Phase 3: Merge — combine duplicates, update outdated
|
|
8
|
-
* Phase 4: Prune — remove stale/low-value entries
|
|
9
|
-
* Phase 5: Promote — extract patterns, update profile
|
|
10
|
-
*
|
|
11
|
-
* Reference: yeaft-unify-core-systems.md §3.3
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Build the Orient phase prompt (Phase 1).
|
|
16
|
-
* The LLM assesses the current memory state and identifies issues.
|
|
17
|
-
*
|
|
18
|
-
* @param {{ memorySummary: string, profileContent: string, entryCount: number }} context
|
|
19
|
-
* @returns {string}
|
|
20
|
-
*/
|
|
21
|
-
export function buildOrientPrompt({ memorySummary, profileContent, entryCount }) {
|
|
22
|
-
return `You are in Dream Mode — Phase 1: Orient.
|
|
23
|
-
|
|
24
|
-
Your task is to assess the current state of the memory store and identify what needs attention.
|
|
25
|
-
|
|
26
|
-
## Current Memory State
|
|
27
|
-
|
|
28
|
-
${memorySummary}
|
|
29
|
-
|
|
30
|
-
## MEMORY.md (User Profile)
|
|
31
|
-
|
|
32
|
-
${profileContent || '(empty)'}
|
|
33
|
-
|
|
34
|
-
## Assessment Instructions
|
|
35
|
-
|
|
36
|
-
Review the memory state and provide:
|
|
37
|
-
1. **Redundancies**: Are there entries that overlap or say the same thing?
|
|
38
|
-
2. **Outdated info**: Are there entries that might be stale or no longer relevant?
|
|
39
|
-
3. **Gaps**: Is there important context missing from MEMORY.md?
|
|
40
|
-
4. **Quality**: Are entries well-categorized (kind, scope, tags)?
|
|
41
|
-
|
|
42
|
-
Return your assessment as JSON:
|
|
43
|
-
{
|
|
44
|
-
"redundantGroups": [["entry-a", "entry-b"]],
|
|
45
|
-
"potentiallyStale": ["entry-name-1"],
|
|
46
|
-
"profileGaps": ["missing X context"],
|
|
47
|
-
"qualityIssues": ["entry-y has wrong kind"],
|
|
48
|
-
"overallHealth": "good" | "needs-attention" | "poor",
|
|
49
|
-
"suggestedActions": ["merge entries about X", "prune stale context entries"]
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
Return ONLY valid JSON, no other text.`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Build the Gather phase prompt (Phase 2).
|
|
57
|
-
* Collects recent compact summaries and completed task summaries.
|
|
58
|
-
*
|
|
59
|
-
* @param {{ recentCompact: string, completedTasks: object[], orientResult: object }} context
|
|
60
|
-
* @returns {string}
|
|
61
|
-
*/
|
|
62
|
-
export function buildGatherPrompt({ recentCompact, completedTasks, orientResult }) {
|
|
63
|
-
const taskSummaries = completedTasks.length > 0
|
|
64
|
-
? completedTasks.map(t => `- [${t.id}] ${t.description}: ${t.summary || '(no summary)'}`).join('\n')
|
|
65
|
-
: '(no recently completed tasks)';
|
|
66
|
-
|
|
67
|
-
return `You are in Dream Mode — Phase 2: Gather.
|
|
68
|
-
|
|
69
|
-
Your task is to identify what new information should be incorporated into long-term memory.
|
|
70
|
-
|
|
71
|
-
## Recent Conversation Summary (compact.md)
|
|
72
|
-
|
|
73
|
-
${recentCompact || '(no recent summaries)'}
|
|
74
|
-
|
|
75
|
-
## Recently Completed Tasks
|
|
76
|
-
|
|
77
|
-
${taskSummaries}
|
|
78
|
-
|
|
79
|
-
## Orient Assessment
|
|
80
|
-
|
|
81
|
-
${JSON.stringify(orientResult, null, 2)}
|
|
82
|
-
|
|
83
|
-
## Instructions
|
|
84
|
-
|
|
85
|
-
From the recent conversations and tasks, identify:
|
|
86
|
-
1. **New facts** worth remembering (project structure, tech decisions)
|
|
87
|
-
2. **New preferences** expressed by the user
|
|
88
|
-
3. **New skills/lessons** learned during tasks
|
|
89
|
-
4. **Context updates** (project progress, status changes)
|
|
90
|
-
|
|
91
|
-
Return as JSON:
|
|
92
|
-
{
|
|
93
|
-
"newEntries": [
|
|
94
|
-
{ "name": "slug-name", "kind": "fact|preference|skill|lesson|context|relation", "scope": "path", "tags": ["tag1", "tag2"], "importance": "high|normal|low", "content": "description" }
|
|
95
|
-
],
|
|
96
|
-
"updatesToExisting": [
|
|
97
|
-
{ "entryName": "existing-slug", "updates": { "content": "updated text", "tags": ["new-tag"] } }
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
Return ONLY valid JSON, no other text.`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Build the Merge phase prompt (Phase 3).
|
|
106
|
-
*
|
|
107
|
-
* @param {{ duplicateGroups: object[][], gatherResult: object }} context
|
|
108
|
-
* @returns {string}
|
|
109
|
-
*/
|
|
110
|
-
export function buildMergePrompt({ duplicateGroups, gatherResult }) {
|
|
111
|
-
const groupDescriptions = duplicateGroups.map((group, i) => {
|
|
112
|
-
const entries = group.map(e =>
|
|
113
|
-
` - [${e.name}] kind=${e.kind}, scope=${e.scope}, tags=[${(e.tags || []).join(', ')}]\n ${(e.content || '').slice(0, 200)}`
|
|
114
|
-
).join('\n');
|
|
115
|
-
return `Group ${i + 1}:\n${entries}`;
|
|
116
|
-
}).join('\n\n');
|
|
117
|
-
|
|
118
|
-
return `You are in Dream Mode — Phase 3: Merge.
|
|
119
|
-
|
|
120
|
-
Your task is to merge duplicate/overlapping entries into single, richer entries.
|
|
121
|
-
|
|
122
|
-
## Potentially Duplicate Groups
|
|
123
|
-
|
|
124
|
-
${groupDescriptions || '(no duplicates detected)'}
|
|
125
|
-
|
|
126
|
-
## New Entries from Gather Phase
|
|
127
|
-
|
|
128
|
-
${JSON.stringify(gatherResult?.newEntries || [], null, 2)}
|
|
129
|
-
|
|
130
|
-
## Instructions
|
|
131
|
-
|
|
132
|
-
For each duplicate group:
|
|
133
|
-
1. Decide if they should be merged (combine info) or kept separate (different enough)
|
|
134
|
-
2. For merges, create a single entry that preserves all important info from both
|
|
135
|
-
3. List which old entries should be deleted after merge
|
|
136
|
-
|
|
137
|
-
Also process the new entries from Gather — check if any overlap with existing entries.
|
|
138
|
-
|
|
139
|
-
Return as JSON:
|
|
140
|
-
{
|
|
141
|
-
"merges": [
|
|
142
|
-
{
|
|
143
|
-
"merged": { "name": "new-slug", "kind": "...", "scope": "...", "tags": [], "importance": "...", "content": "..." },
|
|
144
|
-
"deleteOriginals": ["old-entry-1", "old-entry-2"]
|
|
145
|
-
}
|
|
146
|
-
],
|
|
147
|
-
"newEntries": [
|
|
148
|
-
{ "name": "...", "kind": "...", "scope": "...", "tags": [], "importance": "...", "content": "..." }
|
|
149
|
-
],
|
|
150
|
-
"updates": [
|
|
151
|
-
{ "entryName": "existing-slug", "updates": { "content": "updated text" } }
|
|
152
|
-
]
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
Return ONLY valid JSON, no other text.`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Build the Prune phase prompt (Phase 4).
|
|
160
|
-
*
|
|
161
|
-
* @param {{ staleEntries: object[], entryCount: number, maxEntries: number }} context
|
|
162
|
-
* @returns {string}
|
|
163
|
-
*/
|
|
164
|
-
export function buildPrunePrompt({ staleEntries, entryCount, maxEntries }) {
|
|
165
|
-
const staleDescriptions = staleEntries.map(e =>
|
|
166
|
-
`- [${e.name}] kind=${e.kind}, scope=${e.scope}, freq=${e.frequency || 1}, days_since_update=${e._daysSinceUpdate}\n ${(e.content || '').slice(0, 150)}`
|
|
167
|
-
).join('\n');
|
|
168
|
-
|
|
169
|
-
return `You are in Dream Mode — Phase 4: Prune.
|
|
170
|
-
|
|
171
|
-
Your task is to remove stale, low-value, or redundant entries.
|
|
172
|
-
|
|
173
|
-
## Potentially Stale Entries (${staleEntries.length} found)
|
|
174
|
-
|
|
175
|
-
${staleDescriptions || '(none detected)'}
|
|
176
|
-
|
|
177
|
-
## Capacity
|
|
178
|
-
|
|
179
|
-
Current entries: ${entryCount}
|
|
180
|
-
Maximum allowed: ${maxEntries}
|
|
181
|
-
${entryCount > maxEntries ? `⚠️ OVER CAPACITY by ${entryCount - maxEntries} entries — must prune aggressively` : 'Within capacity'}
|
|
182
|
-
|
|
183
|
-
## Prune Guidelines
|
|
184
|
-
|
|
185
|
-
Delete entries that are:
|
|
186
|
-
- **Outdated context**: Project status from weeks ago
|
|
187
|
-
- **Never recalled**: frequency=1 and old — nobody needs it
|
|
188
|
-
- **Too vague**: "user mentioned something about X" without useful detail
|
|
189
|
-
- **Redundant with profile**: If MEMORY.md already captures it
|
|
190
|
-
- **Re-derivable**: Info that can be obtained by running a command (e.g., "Node version is 20")
|
|
191
|
-
|
|
192
|
-
KEEP entries that are:
|
|
193
|
-
- High importance or high frequency
|
|
194
|
-
- Recent preferences or lessons
|
|
195
|
-
- Facts about project structure (hard to re-discover)
|
|
196
|
-
|
|
197
|
-
Return as JSON:
|
|
198
|
-
{
|
|
199
|
-
"toDelete": ["entry-name-1", "entry-name-2"],
|
|
200
|
-
"reasoning": {
|
|
201
|
-
"entry-name-1": "outdated context from 45 days ago",
|
|
202
|
-
"entry-name-2": "never recalled, too vague"
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
Return ONLY valid JSON, no other text.`;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Build the Promote phase prompt (Phase 5).
|
|
211
|
-
*
|
|
212
|
-
* @param {{ entries: object[], profileContent: string, scopesSummary: string }} context
|
|
213
|
-
* @returns {string}
|
|
214
|
-
*/
|
|
215
|
-
export function buildPromotePrompt({ entries, profileContent, scopesSummary }) {
|
|
216
|
-
// Find entries that might form patterns
|
|
217
|
-
const highFreq = entries
|
|
218
|
-
.filter(e => (e.frequency || 1) >= 3)
|
|
219
|
-
.map(e => `- [${e.name}] kind=${e.kind}, freq=${e.frequency}, scope=${e.scope}: ${(e.content || '').slice(0, 150)}`)
|
|
220
|
-
.join('\n');
|
|
221
|
-
|
|
222
|
-
const lessons = entries
|
|
223
|
-
.filter(e => e.kind === 'lesson')
|
|
224
|
-
.map(e => `- [${e.name}] scope=${e.scope}: ${(e.content || '').slice(0, 150)}`)
|
|
225
|
-
.join('\n');
|
|
226
|
-
|
|
227
|
-
return `You are in Dream Mode — Phase 5: Promote.
|
|
228
|
-
|
|
229
|
-
Your task is to identify patterns and update the user profile.
|
|
230
|
-
|
|
231
|
-
## High-Frequency Entries (recalled ≥3 times)
|
|
232
|
-
|
|
233
|
-
${highFreq || '(none)'}
|
|
234
|
-
|
|
235
|
-
## All Lessons
|
|
236
|
-
|
|
237
|
-
${lessons || '(none)'}
|
|
238
|
-
|
|
239
|
-
## Current MEMORY.md Profile
|
|
240
|
-
|
|
241
|
-
${profileContent || '(empty)'}
|
|
242
|
-
|
|
243
|
-
## Scopes
|
|
244
|
-
|
|
245
|
-
${scopesSummary}
|
|
246
|
-
|
|
247
|
-
## Instructions
|
|
248
|
-
|
|
249
|
-
1. **Pattern promotion**: If multiple entries share a pattern, create a higher-level insight
|
|
250
|
-
- Example: 3 entries about "user corrects indentation" → 1 preference: "default to 2-space indent"
|
|
251
|
-
2. **Profile update**: Update MEMORY.md sections based on accumulated knowledge
|
|
252
|
-
- Keep MEMORY.md under 200 lines
|
|
253
|
-
- Sections: Facts, Preferences, Project Context, Skills, Lessons
|
|
254
|
-
3. **Scope promotion**: If a lesson applies across projects, promote scope to parent or global
|
|
255
|
-
|
|
256
|
-
Return as JSON:
|
|
257
|
-
{
|
|
258
|
-
"profileUpdates": {
|
|
259
|
-
"Facts": ["- New fact line 1"],
|
|
260
|
-
"Preferences": ["- New preference line"],
|
|
261
|
-
"Project Context": [],
|
|
262
|
-
"Skills": [],
|
|
263
|
-
"Lessons": []
|
|
264
|
-
},
|
|
265
|
-
"promotedEntries": [
|
|
266
|
-
{ "name": "...", "kind": "...", "scope": "global", "tags": [], "importance": "high", "content": "..." }
|
|
267
|
-
],
|
|
268
|
-
"entriesToDelete": ["entry-that-was-promoted-to-profile"]
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
Return ONLY valid JSON, no other text.`;
|
|
272
|
-
}
|