@xopcai/xopc 0.0.24 → 0.0.25
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/dist/extensions/feishu/xopc.extension.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-C_bPhtBs.js +216 -0
- package/dist/gateway/static/root/assets/{agents-CiZMJZRp.js.map → agents-C_bPhtBs.js.map} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-tZz69XM3.js → apps-page-DbzO48lg.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-tZz69XM3.js.map → apps-page-DbzO48lg.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-CeGoU9v8.js +9 -0
- package/dist/gateway/static/root/assets/{channels-settings-BAvk9-aK.js.map → channels-settings-CeGoU9v8.js.map} +1 -1
- package/dist/gateway/static/root/assets/cron-page-DpEYUvxB.js +2 -0
- package/dist/gateway/static/root/assets/{cron-page-CANqvhK7.js.map → cron-page-DpEYUvxB.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-utils-DyOO6TdN.js → cron-utils-Cvv0F3pa.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-utils-DyOO6TdN.js.map → cron-utils-Cvv0F3pa.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-Brod9LF3.js → dist-C41N3YrO.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-Brod9LF3.js.map → dist-C41N3YrO.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CDD7ozsC.js → extension-debug-page-CkkYZjNP.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-CDD7ozsC.js.map → extension-debug-page-CkkYZjNP.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-UUFMjoWf.js → extension-page-BjUIPVNG.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-UUFMjoWf.js.map → extension-page-BjUIPVNG.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CP9JNc4m.js → extension-settings-page-CwuFDOdk.js} +2 -2
- package/dist/gateway/static/root/assets/extension-settings-page-CwuFDOdk.js.map +1 -0
- package/dist/gateway/static/root/assets/{index-BZvlG48D.js → index-DwzwDCjW.js} +56 -56
- package/dist/gateway/static/root/assets/{index-BZvlG48D.js.map → index-DwzwDCjW.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-dhtHG1nU.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-BtwGPuw2.js +2 -0
- package/dist/gateway/static/root/assets/{logs-page-Cr0eCGb4.js.map → logs-page-BtwGPuw2.js.map} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-4rKFDn2k.js +2 -0
- package/dist/gateway/static/root/assets/{sessions-page-DwLHN5GJ.js.map → sessions-page-4rKFDn2k.js.map} +1 -1
- package/dist/gateway/static/root/assets/settings-page-iYLSxQYc.js +2 -0
- package/dist/gateway/static/root/assets/settings-page-iYLSxQYc.js.map +1 -0
- package/dist/gateway/static/root/assets/skills-page-_siDuHeF.js +3 -0
- package/dist/gateway/static/root/assets/{skills-page-DgBYvH6B.js.map → skills-page-_siDuHeF.js.map} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/memory/dreaming/config.d.ts +20 -0
- package/dist/src/agent/memory/dreaming/config.js +44 -0
- package/dist/src/agent/memory/dreaming/config.js.map +1 -0
- package/dist/src/agent/memory/dreaming/constants.d.ts +8 -0
- package/dist/src/agent/memory/dreaming/constants.js +14 -0
- package/dist/src/agent/memory/dreaming/constants.js.map +1 -0
- package/dist/src/agent/memory/dreaming/deep-promotion.d.ts +22 -0
- package/dist/src/agent/memory/dreaming/deep-promotion.js +337 -0
- package/dist/src/agent/memory/dreaming/deep-promotion.js.map +1 -0
- package/dist/src/agent/memory/dreaming/preview.d.ts +26 -0
- package/dist/src/agent/memory/dreaming/preview.js +176 -0
- package/dist/src/agent/memory/dreaming/preview.js.map +1 -0
- package/dist/src/agent/memory/dreaming/short-term-store.d.ts +45 -0
- package/dist/src/agent/memory/dreaming/short-term-store.js +187 -0
- package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -0
- package/dist/src/agent/orchestration/agent-orchestrator.js +17 -0
- package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
- package/dist/src/agent/service.d.ts +6 -0
- package/dist/src/agent/service.js +52 -0
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/tools/dreaming-tool.d.ts +7 -0
- package/dist/src/agent/tools/dreaming-tool.js +102 -0
- package/dist/src/agent/tools/dreaming-tool.js.map +1 -0
- package/dist/src/agent/tools/factory.js +5 -0
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/index.d.ts +1 -0
- package/dist/src/agent/tools/index.js +2 -1
- package/dist/src/agent/tools/memory-tool.js +9 -2
- package/dist/src/agent/tools/memory-tool.js.map +1 -1
- package/dist/src/config/schema.d.ts +39 -0
- package/dist/src/config/schema.js +16 -1
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +13 -0
- package/dist/src/gateway/hono/routes/config.js +48 -0
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- package/dist/src/gateway/hono/routes/dreaming.d.ts +3 -0
- package/dist/src/gateway/hono/routes/dreaming.js +198 -0
- package/dist/src/gateway/hono/routes/dreaming.js.map +1 -0
- package/dist/src/gateway/hono/routes/index.js +2 -0
- package/dist/src/gateway/hono/routes/index.js.map +1 -1
- package/dist/src/gateway/lock.js +1 -1
- package/dist/src/gateway/service.js +7 -0
- package/dist/src/gateway/service.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-CiZMJZRp.js +0 -216
- package/dist/gateway/static/root/assets/channels-settings-BAvk9-aK.js +0 -9
- package/dist/gateway/static/root/assets/cron-page-CANqvhK7.js +0 -2
- package/dist/gateway/static/root/assets/extension-settings-page-CP9JNc4m.js.map +0 -1
- package/dist/gateway/static/root/assets/index-DxkgyT8R.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-Cr0eCGb4.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-DwLHN5GJ.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-B3O3R0E4.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-B3O3R0E4.js.map +0 -1
- package/dist/gateway/static/root/assets/skills-page-DgBYvH6B.js +0 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type DreamingStoreEntry } from './short-term-store.js';
|
|
2
|
+
export type DreamingDeepConfig = {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
minScore: number;
|
|
5
|
+
minRecallCount: number;
|
|
6
|
+
limit: number;
|
|
7
|
+
};
|
|
8
|
+
export type DreamingPromotionCandidate = DreamingStoreEntry & {
|
|
9
|
+
avgScore: number;
|
|
10
|
+
score: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function runDreamingDeepPromotion(params: {
|
|
13
|
+
workspaceDir: string;
|
|
14
|
+
config?: Partial<DreamingDeepConfig>;
|
|
15
|
+
now?: Date;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
ok: boolean;
|
|
18
|
+
reason: string;
|
|
19
|
+
candidates: number;
|
|
20
|
+
applied: number;
|
|
21
|
+
memoryPath: string;
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { createLogger } from "../../../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../../../utils/logger.js";
|
|
3
|
+
import { DREAMING_LAST_RUN_RELATIVE, MEMORY_MD_FILENAME } from "./constants.js";
|
|
4
|
+
import { loadDreamingStore, saveDreamingStore, withDreamingPromotionLock } from "./short-term-store.js";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
//#region src/agent/memory/dreaming/deep-promotion.ts
|
|
9
|
+
init_logger();
|
|
10
|
+
const log = createLogger("Dreaming:Deep");
|
|
11
|
+
function clampScore(value) {
|
|
12
|
+
if (!Number.isFinite(value)) return 0;
|
|
13
|
+
return Math.max(0, Math.min(1, value));
|
|
14
|
+
}
|
|
15
|
+
function formatIsoDay(now) {
|
|
16
|
+
return now.toISOString().slice(0, 10);
|
|
17
|
+
}
|
|
18
|
+
function compareCandidates(a, b) {
|
|
19
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
20
|
+
if (b.recallCount !== a.recallCount) return b.recallCount - a.recallCount;
|
|
21
|
+
const aMs = Date.parse(a.lastRecalledAt);
|
|
22
|
+
const bMs = Date.parse(b.lastRecalledAt);
|
|
23
|
+
if (Number.isFinite(aMs) || Number.isFinite(bMs)) {
|
|
24
|
+
if (bMs !== aMs) return bMs - aMs;
|
|
25
|
+
}
|
|
26
|
+
return a.path.localeCompare(b.path);
|
|
27
|
+
}
|
|
28
|
+
function normalizeSnippetForHash(snippet) {
|
|
29
|
+
return snippet.trim().replace(/\s+/g, " ").replace(/[“”]/g, "\"").replace(/[‘’]/g, "'").toLowerCase().slice(0, 512);
|
|
30
|
+
}
|
|
31
|
+
function snippetHash(snippet) {
|
|
32
|
+
const normalized = normalizeSnippetForHash(snippet);
|
|
33
|
+
return createHash("sha1").update(normalized).digest("hex").slice(0, 12);
|
|
34
|
+
}
|
|
35
|
+
function markerForPromotion(key, hash) {
|
|
36
|
+
return `<!-- xopc-memory-promotion key="${key}" hash="${hash}" -->`;
|
|
37
|
+
}
|
|
38
|
+
function extractPromotionMarkers(memoryText) {
|
|
39
|
+
const keys = /* @__PURE__ */ new Set();
|
|
40
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
41
|
+
for (const match of memoryText.matchAll(/<!--\s*xopc-memory-promotion\b([\s\S]*?)-->/gi)) {
|
|
42
|
+
const body = match[1] ?? "";
|
|
43
|
+
const k = body.match(/\bkey\s*=\s*"([^"]+)"/i)?.[1]?.trim();
|
|
44
|
+
const h = body.match(/\bhash\s*=\s*"([^"]+)"/i)?.[1]?.trim();
|
|
45
|
+
if (k) keys.add(k);
|
|
46
|
+
if (h) hashes.add(h);
|
|
47
|
+
}
|
|
48
|
+
for (const match of memoryText.matchAll(/<!--\s*xopc-memory-promotion:([^\n]+?)\s*-->/gi)) {
|
|
49
|
+
const key = match[1]?.trim();
|
|
50
|
+
if (key) keys.add(key);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
keys,
|
|
54
|
+
hashes
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function isContaminatedSnippet(snippet) {
|
|
58
|
+
const s = snippet.trim();
|
|
59
|
+
if (!s) return true;
|
|
60
|
+
const lower = s.toLowerCase();
|
|
61
|
+
if ([
|
|
62
|
+
/\b(system|assistant|tool)\s*:/i,
|
|
63
|
+
/<\s*(system|assistant|tool)\b/i,
|
|
64
|
+
/<!--\s*xopc-memory-promotion\b/i,
|
|
65
|
+
/tool_call_id|toolcallid|function_call|arguments\s*:\s*\{/i,
|
|
66
|
+
/"tool"\s*:\s*|\btool_name\b|\btool\b\s*results?/i,
|
|
67
|
+
/you are (an|a)\s+(ai|assistant)|follow these instructions/i,
|
|
68
|
+
/begin\s+(system prompt|instructions)|end\s+(system prompt|instructions)/i,
|
|
69
|
+
/```/i,
|
|
70
|
+
/\b__xopc_/i
|
|
71
|
+
].some((p) => p.test(s))) return true;
|
|
72
|
+
if ((s.match(/[{}[\]]/g) ?? []).length >= 12) return true;
|
|
73
|
+
if ((lower.match(/https?:\/\//g) ?? []).length >= 3) return true;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
async function readFileLines(fullPath) {
|
|
77
|
+
try {
|
|
78
|
+
return (await fs.readFile(fullPath, "utf-8")).split(/\r?\n/);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (err?.code === "ENOENT") return null;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function sliceRange(lines, startLine, endLine) {
|
|
85
|
+
const startIdx = Math.max(0, startLine - 1);
|
|
86
|
+
const endIdx = Math.min(lines.length, endLine);
|
|
87
|
+
if (startIdx >= endIdx) return "";
|
|
88
|
+
return lines.slice(startIdx, endIdx).map((l) => l.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim().slice(0, 360);
|
|
89
|
+
}
|
|
90
|
+
async function rehydrateSnippet(params) {
|
|
91
|
+
const lines = await readFileLines(path.join(params.workspaceDir, params.candidate.path));
|
|
92
|
+
if (!lines) return null;
|
|
93
|
+
const startLine = Math.max(1, Math.floor(params.candidate.startLine));
|
|
94
|
+
const endLine = Math.max(startLine, Math.floor(params.candidate.endLine));
|
|
95
|
+
const snippet = sliceRange(lines, startLine, endLine);
|
|
96
|
+
if (!snippet) return null;
|
|
97
|
+
return {
|
|
98
|
+
snippet,
|
|
99
|
+
startLine,
|
|
100
|
+
endLine
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function resolveDefaultConfig(overrides) {
|
|
104
|
+
const enabled = overrides?.enabled === true;
|
|
105
|
+
const minScore = typeof overrides?.minScore === "number" ? overrides.minScore : .8;
|
|
106
|
+
const minRecallCount = typeof overrides?.minRecallCount === "number" ? overrides.minRecallCount : 3;
|
|
107
|
+
const limit = typeof overrides?.limit === "number" ? overrides.limit : 10;
|
|
108
|
+
return {
|
|
109
|
+
enabled,
|
|
110
|
+
minScore: clampScore(minScore),
|
|
111
|
+
minRecallCount: Math.max(1, Math.floor(minRecallCount)),
|
|
112
|
+
limit: Math.max(0, Math.floor(limit))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function writeLastRun(params) {
|
|
116
|
+
const fullPath = path.join(params.workspaceDir, DREAMING_LAST_RUN_RELATIVE);
|
|
117
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
118
|
+
const tmp = `${fullPath}.${process.pid}.${Date.now()}.tmp`;
|
|
119
|
+
await fs.writeFile(tmp, `${JSON.stringify(params.lastRun, null, 2)}\n`, "utf-8");
|
|
120
|
+
await fs.rename(tmp, fullPath);
|
|
121
|
+
}
|
|
122
|
+
async function runDreamingDeepPromotion(params) {
|
|
123
|
+
const cfg = resolveDefaultConfig(params.config);
|
|
124
|
+
const now = params.now ?? /* @__PURE__ */ new Date();
|
|
125
|
+
const startedAt = now.toISOString();
|
|
126
|
+
const runId = `${startedAt}:${process.pid}:${Math.random().toString(16).slice(2)}`;
|
|
127
|
+
const memoryPath = path.join(params.workspaceDir, MEMORY_MD_FILENAME);
|
|
128
|
+
if (!cfg.enabled) {
|
|
129
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
130
|
+
await writeLastRun({
|
|
131
|
+
workspaceDir: params.workspaceDir,
|
|
132
|
+
lastRun: {
|
|
133
|
+
version: 1,
|
|
134
|
+
runId,
|
|
135
|
+
startedAt,
|
|
136
|
+
finishedAt,
|
|
137
|
+
ok: true,
|
|
138
|
+
reason: "dreaming mvp disabled",
|
|
139
|
+
config: cfg,
|
|
140
|
+
candidates: 0,
|
|
141
|
+
applied: 0,
|
|
142
|
+
memoryPath
|
|
143
|
+
}
|
|
144
|
+
}).catch(() => void 0);
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
reason: "dreaming mvp disabled",
|
|
148
|
+
candidates: 0,
|
|
149
|
+
applied: 0,
|
|
150
|
+
memoryPath
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (cfg.limit === 0) {
|
|
154
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
155
|
+
await writeLastRun({
|
|
156
|
+
workspaceDir: params.workspaceDir,
|
|
157
|
+
lastRun: {
|
|
158
|
+
version: 1,
|
|
159
|
+
runId,
|
|
160
|
+
startedAt,
|
|
161
|
+
finishedAt,
|
|
162
|
+
ok: true,
|
|
163
|
+
reason: "dreaming mvp limit=0",
|
|
164
|
+
config: cfg,
|
|
165
|
+
candidates: 0,
|
|
166
|
+
applied: 0,
|
|
167
|
+
memoryPath
|
|
168
|
+
}
|
|
169
|
+
}).catch(() => void 0);
|
|
170
|
+
return {
|
|
171
|
+
ok: true,
|
|
172
|
+
reason: "dreaming mvp limit=0",
|
|
173
|
+
candidates: 0,
|
|
174
|
+
applied: 0,
|
|
175
|
+
memoryPath
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const result = await withDreamingPromotionLock(params.workspaceDir, async () => {
|
|
180
|
+
const { store } = await loadDreamingStore({ workspaceDir: params.workspaceDir });
|
|
181
|
+
const ranked = Object.values(store.entries ?? {}).filter((e) => {
|
|
182
|
+
if (!e || typeof e !== "object") return false;
|
|
183
|
+
if (e.promotedAt) return false;
|
|
184
|
+
if (!e.path || !e.path.startsWith("memory/")) return false;
|
|
185
|
+
if (e.recallCount < cfg.minRecallCount) return false;
|
|
186
|
+
return clampScore(e.recallCount > 0 ? e.totalScore / e.recallCount : 0) >= cfg.minScore;
|
|
187
|
+
}).map((e) => {
|
|
188
|
+
const avgScore = e.recallCount > 0 ? clampScore(e.totalScore / e.recallCount) : 0;
|
|
189
|
+
const score = clampScore(avgScore + clampScore(Math.log1p(e.recallCount) / Math.log1p(10)) * .12);
|
|
190
|
+
return {
|
|
191
|
+
...e,
|
|
192
|
+
avgScore,
|
|
193
|
+
score
|
|
194
|
+
};
|
|
195
|
+
}).sort(compareCandidates).slice(0, cfg.limit);
|
|
196
|
+
if (ranked.length === 0) return {
|
|
197
|
+
ok: true,
|
|
198
|
+
reason: "no eligible candidates",
|
|
199
|
+
candidates: 0,
|
|
200
|
+
applied: 0,
|
|
201
|
+
memoryPath
|
|
202
|
+
};
|
|
203
|
+
const existing = await fs.readFile(memoryPath, "utf-8").catch((err) => {
|
|
204
|
+
if (err?.code === "ENOENT") return "";
|
|
205
|
+
throw err;
|
|
206
|
+
});
|
|
207
|
+
const existingMarkers = extractPromotionMarkers(existing);
|
|
208
|
+
const appliedCandidates = [];
|
|
209
|
+
for (const candidate of ranked) {
|
|
210
|
+
if (existingMarkers.keys.has(candidate.key)) {
|
|
211
|
+
const entry = store.entries[candidate.key];
|
|
212
|
+
if (entry && !entry.promotedAt) entry.promotedAt = now.toISOString();
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const rehydrated = await rehydrateSnippet({
|
|
216
|
+
workspaceDir: params.workspaceDir,
|
|
217
|
+
candidate
|
|
218
|
+
});
|
|
219
|
+
if (!rehydrated) continue;
|
|
220
|
+
if (isContaminatedSnippet(rehydrated.snippet)) continue;
|
|
221
|
+
const hash = snippetHash(rehydrated.snippet);
|
|
222
|
+
if (existingMarkers.hashes.has(hash)) {
|
|
223
|
+
const entry = store.entries[candidate.key];
|
|
224
|
+
if (entry && !entry.promotedAt) entry.promotedAt = now.toISOString();
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
appliedCandidates.push({
|
|
228
|
+
key: candidate.key,
|
|
229
|
+
hash,
|
|
230
|
+
snippet: rehydrated.snippet,
|
|
231
|
+
path: candidate.path,
|
|
232
|
+
startLine: rehydrated.startLine,
|
|
233
|
+
endLine: rehydrated.endLine,
|
|
234
|
+
score: candidate.score,
|
|
235
|
+
recallCount: candidate.recallCount,
|
|
236
|
+
avgScore: candidate.avgScore
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (appliedCandidates.length === 0) {
|
|
240
|
+
await saveDreamingStore({
|
|
241
|
+
workspaceDir: params.workspaceDir,
|
|
242
|
+
store
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
reason: "candidates were stale or already applied",
|
|
247
|
+
candidates: ranked.length,
|
|
248
|
+
applied: 0,
|
|
249
|
+
memoryPath
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const day = formatIsoDay(now);
|
|
253
|
+
const header = existing.trim().length > 0 ? "" : "# Long-Term Memory\n\n";
|
|
254
|
+
const sectionLines = [
|
|
255
|
+
"",
|
|
256
|
+
`## Promoted From Short-Term Memory (${day})`,
|
|
257
|
+
""
|
|
258
|
+
];
|
|
259
|
+
for (const c of appliedCandidates) {
|
|
260
|
+
const src = `${c.path}:${c.startLine}-${c.endLine}`;
|
|
261
|
+
sectionLines.push(markerForPromotion(c.key, c.hash));
|
|
262
|
+
sectionLines.push(`- ${c.snippet} [score=${c.score.toFixed(3)} recalls=${c.recallCount} avg=${c.avgScore.toFixed(3)} source=${src}]`);
|
|
263
|
+
}
|
|
264
|
+
sectionLines.push("");
|
|
265
|
+
const next = `${header}${existing.endsWith("\n") || existing.length === 0 ? existing : `${existing}\n`}${sectionLines.join("\n")}`;
|
|
266
|
+
await fs.writeFile(memoryPath, next, "utf-8");
|
|
267
|
+
const nowIso = now.toISOString();
|
|
268
|
+
for (const c of appliedCandidates) {
|
|
269
|
+
const entry = store.entries[c.key];
|
|
270
|
+
if (entry) {
|
|
271
|
+
entry.promotedAt = nowIso;
|
|
272
|
+
entry.snippet = c.snippet;
|
|
273
|
+
entry.startLine = c.startLine;
|
|
274
|
+
entry.endLine = c.endLine;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
store.updatedAt = nowIso;
|
|
278
|
+
await saveDreamingStore({
|
|
279
|
+
workspaceDir: params.workspaceDir,
|
|
280
|
+
store
|
|
281
|
+
});
|
|
282
|
+
log.info({
|
|
283
|
+
workspaceDir: params.workspaceDir,
|
|
284
|
+
candidates: ranked.length,
|
|
285
|
+
applied: appliedCandidates.length
|
|
286
|
+
}, "Dreaming deep promotion complete");
|
|
287
|
+
return {
|
|
288
|
+
ok: true,
|
|
289
|
+
reason: "applied promotions",
|
|
290
|
+
candidates: ranked.length,
|
|
291
|
+
applied: appliedCandidates.length,
|
|
292
|
+
memoryPath
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
296
|
+
await writeLastRun({
|
|
297
|
+
workspaceDir: params.workspaceDir,
|
|
298
|
+
lastRun: {
|
|
299
|
+
version: 1,
|
|
300
|
+
runId,
|
|
301
|
+
startedAt,
|
|
302
|
+
finishedAt,
|
|
303
|
+
ok: result.ok,
|
|
304
|
+
reason: result.reason,
|
|
305
|
+
config: cfg,
|
|
306
|
+
candidates: result.candidates,
|
|
307
|
+
applied: result.applied,
|
|
308
|
+
memoryPath: result.memoryPath
|
|
309
|
+
}
|
|
310
|
+
}).catch(() => void 0);
|
|
311
|
+
return result;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
314
|
+
const em = err instanceof Error ? err.message : String(err);
|
|
315
|
+
await writeLastRun({
|
|
316
|
+
workspaceDir: params.workspaceDir,
|
|
317
|
+
lastRun: {
|
|
318
|
+
version: 1,
|
|
319
|
+
runId,
|
|
320
|
+
startedAt,
|
|
321
|
+
finishedAt,
|
|
322
|
+
ok: false,
|
|
323
|
+
reason: "error",
|
|
324
|
+
config: cfg,
|
|
325
|
+
candidates: 0,
|
|
326
|
+
applied: 0,
|
|
327
|
+
memoryPath,
|
|
328
|
+
errorMessage: em
|
|
329
|
+
}
|
|
330
|
+
}).catch(() => void 0);
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
export { runDreamingDeepPromotion };
|
|
336
|
+
|
|
337
|
+
//# sourceMappingURL=deep-promotion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-promotion.js","names":[],"sources":["../../../../../src/agent/memory/dreaming/deep-promotion.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { MEMORY_MD_FILENAME } from './constants.js';\nimport {\n loadDreamingStore,\n saveDreamingStore,\n withDreamingPromotionLock,\n type DreamingStoreEntry,\n} from './short-term-store.js';\nimport { DREAMING_LAST_RUN_RELATIVE } from './constants.js';\n\nconst log = createLogger('Dreaming:Deep');\n\nexport type DreamingDeepConfig = {\n enabled: boolean;\n minScore: number;\n minRecallCount: number;\n limit: number;\n};\n\nexport type DreamingPromotionCandidate = DreamingStoreEntry & {\n avgScore: number;\n score: number;\n};\n\nfunction clampScore(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction formatIsoDay(now: Date): string {\n return now.toISOString().slice(0, 10);\n}\n\nfunction compareCandidates(a: DreamingPromotionCandidate, b: DreamingPromotionCandidate): number {\n if (b.score !== a.score) return b.score - a.score;\n if (b.recallCount !== a.recallCount) return b.recallCount - a.recallCount;\n const aMs = Date.parse(a.lastRecalledAt);\n const bMs = Date.parse(b.lastRecalledAt);\n if (Number.isFinite(aMs) || Number.isFinite(bMs)) {\n if (bMs !== aMs) return bMs - aMs;\n }\n return a.path.localeCompare(b.path);\n}\n\nfunction normalizeSnippetForHash(snippet: string): string {\n return snippet\n .trim()\n .replace(/\\s+/g, ' ')\n .replace(/[“”]/g, '\"')\n .replace(/[‘’]/g, \"'\")\n .toLowerCase()\n .slice(0, 512);\n}\n\nfunction snippetHash(snippet: string): string {\n const normalized = normalizeSnippetForHash(snippet);\n return createHash('sha1').update(normalized).digest('hex').slice(0, 12);\n}\n\nfunction markerForPromotion(key: string, hash: string): string {\n // Quote values to allow spaces/colons in key.\n return `<!-- xopc-memory-promotion key=\"${key}\" hash=\"${hash}\" -->`;\n}\n\nfunction extractPromotionMarkers(memoryText: string): { keys: Set<string>; hashes: Set<string> } {\n const keys = new Set<string>();\n const hashes = new Set<string>();\n\n // New format: <!-- xopc-memory-promotion key=\"...\" hash=\"...\" -->\n for (const match of memoryText.matchAll(/<!--\\s*xopc-memory-promotion\\b([\\s\\S]*?)-->/gi)) {\n const body = match[1] ?? '';\n const k = body.match(/\\bkey\\s*=\\s*\"([^\"]+)\"/i)?.[1]?.trim();\n const h = body.match(/\\bhash\\s*=\\s*\"([^\"]+)\"/i)?.[1]?.trim();\n if (k) keys.add(k);\n if (h) hashes.add(h);\n }\n\n // Legacy format: <!-- xopc-memory-promotion:<key> -->\n for (const match of memoryText.matchAll(/<!--\\s*xopc-memory-promotion:([^\\n]+?)\\s*-->/gi)) {\n const key = match[1]?.trim();\n if (key) keys.add(key);\n }\n\n return { keys, hashes };\n}\n\nfunction isContaminatedSnippet(snippet: string): boolean {\n const s = snippet.trim();\n if (!s) return true;\n const lower = s.toLowerCase();\n\n // Obvious tool/system/prompt artifacts.\n const patterns: RegExp[] = [\n /\\b(system|assistant|tool)\\s*:/i,\n /<\\s*(system|assistant|tool)\\b/i,\n /<!--\\s*xopc-memory-promotion\\b/i,\n /tool_call_id|toolcallid|function_call|arguments\\s*:\\s*\\{/i,\n /\"tool\"\\s*:\\s*|\\btool_name\\b|\\btool\\b\\s*results?/i,\n /you are (an|a)\\s+(ai|assistant)|follow these instructions/i,\n /begin\\s+(system prompt|instructions)|end\\s+(system prompt|instructions)/i,\n /```/i,\n /\\b__xopc_/i,\n ];\n if (patterns.some((p) => p.test(s))) return true;\n\n // JSON-ish blocks or obvious dumps.\n const braceCount = (s.match(/[{}[\\]]/g) ?? []).length;\n if (braceCount >= 12) return true;\n\n // Very link-heavy / log-like.\n const urlCount = (lower.match(/https?:\\/\\//g) ?? []).length;\n if (urlCount >= 3) return true;\n\n return false;\n}\n\nasync function readFileLines(fullPath: string): Promise<string[] | null> {\n try {\n const raw = await fs.readFile(fullPath, 'utf-8');\n return raw.split(/\\r?\\n/);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code === 'ENOENT') return null;\n throw err;\n }\n}\n\nfunction sliceRange(lines: string[], startLine: number, endLine: number): string {\n const startIdx = Math.max(0, startLine - 1);\n const endIdx = Math.min(lines.length, endLine);\n if (startIdx >= endIdx) return '';\n return lines\n .slice(startIdx, endIdx)\n .map((l) => l.trim())\n .filter(Boolean)\n .join(' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 360);\n}\n\nasync function rehydrateSnippet(params: {\n workspaceDir: string;\n candidate: DreamingPromotionCandidate;\n}): Promise<{ snippet: string; startLine: number; endLine: number } | null> {\n const fullPath = path.join(params.workspaceDir, params.candidate.path);\n const lines = await readFileLines(fullPath);\n if (!lines) return null;\n\n const startLine = Math.max(1, Math.floor(params.candidate.startLine));\n const endLine = Math.max(startLine, Math.floor(params.candidate.endLine));\n const snippet = sliceRange(lines, startLine, endLine);\n if (!snippet) return null;\n return { snippet, startLine, endLine };\n}\n\nfunction resolveDefaultConfig(overrides?: Partial<DreamingDeepConfig>): DreamingDeepConfig {\n const enabled = overrides?.enabled === true;\n const minScore = typeof overrides?.minScore === 'number' ? overrides.minScore : 0.8;\n const minRecallCount =\n typeof overrides?.minRecallCount === 'number' ? overrides.minRecallCount : 3;\n const limit = typeof overrides?.limit === 'number' ? overrides.limit : 10;\n return {\n enabled,\n minScore: clampScore(minScore),\n minRecallCount: Math.max(1, Math.floor(minRecallCount)),\n limit: Math.max(0, Math.floor(limit)),\n };\n}\n\ntype DreamingLastRun = {\n version: 1;\n runId: string;\n startedAt: string;\n finishedAt: string;\n ok: boolean;\n reason: string;\n config: DreamingDeepConfig;\n candidates: number;\n applied: number;\n memoryPath: string;\n errorMessage?: string;\n};\n\nasync function writeLastRun(params: { workspaceDir: string; lastRun: DreamingLastRun }): Promise<void> {\n const fullPath = path.join(params.workspaceDir, DREAMING_LAST_RUN_RELATIVE);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n const tmp = `${fullPath}.${process.pid}.${Date.now()}.tmp`;\n await fs.writeFile(tmp, `${JSON.stringify(params.lastRun, null, 2)}\\n`, 'utf-8');\n await fs.rename(tmp, fullPath);\n}\n\nexport async function runDreamingDeepPromotion(params: {\n workspaceDir: string;\n config?: Partial<DreamingDeepConfig>;\n now?: Date;\n}): Promise<{\n ok: boolean;\n reason: string;\n candidates: number;\n applied: number;\n memoryPath: string;\n}> {\n const cfg = resolveDefaultConfig(params.config);\n const now = params.now ?? new Date();\n const startedAt = now.toISOString();\n const runId = `${startedAt}:${process.pid}:${Math.random().toString(16).slice(2)}`;\n const memoryPath = path.join(params.workspaceDir, MEMORY_MD_FILENAME);\n\n if (!cfg.enabled) {\n const finishedAt = new Date().toISOString();\n await writeLastRun({\n workspaceDir: params.workspaceDir,\n lastRun: {\n version: 1,\n runId,\n startedAt,\n finishedAt,\n ok: true,\n reason: 'dreaming mvp disabled',\n config: cfg,\n candidates: 0,\n applied: 0,\n memoryPath,\n },\n }).catch(() => undefined);\n return { ok: true, reason: 'dreaming mvp disabled', candidates: 0, applied: 0, memoryPath };\n }\n if (cfg.limit === 0) {\n const finishedAt = new Date().toISOString();\n await writeLastRun({\n workspaceDir: params.workspaceDir,\n lastRun: {\n version: 1,\n runId,\n startedAt,\n finishedAt,\n ok: true,\n reason: 'dreaming mvp limit=0',\n config: cfg,\n candidates: 0,\n applied: 0,\n memoryPath,\n },\n }).catch(() => undefined);\n return { ok: true, reason: 'dreaming mvp limit=0', candidates: 0, applied: 0, memoryPath };\n }\n\n try {\n const result = await withDreamingPromotionLock(params.workspaceDir, async () => {\n const { store } = await loadDreamingStore({ workspaceDir: params.workspaceDir });\n\n const all = Object.values(store.entries ?? {}).filter((e): e is DreamingStoreEntry => {\n if (!e || typeof e !== 'object') return false;\n if (e.promotedAt) return false;\n if (!e.path || !e.path.startsWith('memory/')) return false;\n if (e.recallCount < cfg.minRecallCount) return false;\n const avg = e.recallCount > 0 ? e.totalScore / e.recallCount : 0;\n return clampScore(avg) >= cfg.minScore;\n });\n\n const ranked: DreamingPromotionCandidate[] = all\n .map((e) => {\n const avgScore = e.recallCount > 0 ? clampScore(e.totalScore / e.recallCount) : 0;\n // Score: avgScore primary, with mild recallCount reinforcement.\n const reinforcement = clampScore(Math.log1p(e.recallCount) / Math.log1p(10)) * 0.12;\n const score = clampScore(avgScore + reinforcement);\n return { ...e, avgScore, score };\n })\n .sort(compareCandidates)\n .slice(0, cfg.limit);\n\n if (ranked.length === 0) {\n return { ok: true, reason: 'no eligible candidates', candidates: 0, applied: 0, memoryPath };\n }\n\n const existing = await fs.readFile(memoryPath, 'utf-8').catch((err: unknown) => {\n if ((err as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') return '';\n throw err;\n });\n const existingMarkers = extractPromotionMarkers(existing);\n\n const appliedCandidates: Array<{\n key: string;\n hash: string;\n snippet: string;\n path: string;\n startLine: number;\n endLine: number;\n score: number;\n recallCount: number;\n avgScore: number;\n }> = [];\n\n for (const candidate of ranked) {\n if (existingMarkers.keys.has(candidate.key)) {\n // Treat as already applied; mark promotedAt for idempotency and keep going.\n const entry = store.entries[candidate.key];\n if (entry && !entry.promotedAt) {\n entry.promotedAt = now.toISOString();\n }\n continue;\n }\n const rehydrated = await rehydrateSnippet({ workspaceDir: params.workspaceDir, candidate });\n if (!rehydrated) {\n continue;\n }\n if (isContaminatedSnippet(rehydrated.snippet)) {\n continue;\n }\n const hash = snippetHash(rehydrated.snippet);\n if (existingMarkers.hashes.has(hash)) {\n // Already promoted (possibly from a different key/line range). Mark promoted for idempotency.\n const entry = store.entries[candidate.key];\n if (entry && !entry.promotedAt) {\n entry.promotedAt = now.toISOString();\n }\n continue;\n }\n appliedCandidates.push({\n key: candidate.key,\n hash,\n snippet: rehydrated.snippet,\n path: candidate.path,\n startLine: rehydrated.startLine,\n endLine: rehydrated.endLine,\n score: candidate.score,\n recallCount: candidate.recallCount,\n avgScore: candidate.avgScore,\n });\n }\n\n if (appliedCandidates.length === 0) {\n await saveDreamingStore({ workspaceDir: params.workspaceDir, store });\n return {\n ok: true,\n reason: 'candidates were stale or already applied',\n candidates: ranked.length,\n applied: 0,\n memoryPath,\n };\n }\n\n const day = formatIsoDay(now);\n const header = existing.trim().length > 0 ? '' : '# Long-Term Memory\\n\\n';\n const sectionLines: string[] = ['', `## Promoted From Short-Term Memory (${day})`, ''];\n for (const c of appliedCandidates) {\n const src = `${c.path}:${c.startLine}-${c.endLine}`;\n sectionLines.push(markerForPromotion(c.key, c.hash));\n sectionLines.push(\n `- ${c.snippet} [score=${c.score.toFixed(3)} recalls=${c.recallCount} avg=${c.avgScore.toFixed(3)} source=${src}]`,\n );\n }\n sectionLines.push('');\n\n const next = `${header}${existing.endsWith('\\n') || existing.length === 0 ? existing : `${existing}\\n`}${sectionLines.join('\\n')}`;\n await fs.writeFile(memoryPath, next, 'utf-8');\n\n const nowIso = now.toISOString();\n for (const c of appliedCandidates) {\n const entry = store.entries[c.key];\n if (entry) {\n entry.promotedAt = nowIso;\n entry.snippet = c.snippet;\n entry.startLine = c.startLine;\n entry.endLine = c.endLine;\n }\n }\n store.updatedAt = nowIso;\n await saveDreamingStore({ workspaceDir: params.workspaceDir, store });\n\n log.info(\n { workspaceDir: params.workspaceDir, candidates: ranked.length, applied: appliedCandidates.length },\n 'Dreaming deep promotion complete',\n );\n\n return {\n ok: true,\n reason: 'applied promotions',\n candidates: ranked.length,\n applied: appliedCandidates.length,\n memoryPath,\n };\n });\n\n const finishedAt = new Date().toISOString();\n await writeLastRun({\n workspaceDir: params.workspaceDir,\n lastRun: {\n version: 1,\n runId,\n startedAt,\n finishedAt,\n ok: result.ok,\n reason: result.reason,\n config: cfg,\n candidates: result.candidates,\n applied: result.applied,\n memoryPath: result.memoryPath,\n },\n }).catch(() => undefined);\n\n return result;\n } catch (err) {\n const finishedAt = new Date().toISOString();\n const em = err instanceof Error ? err.message : String(err);\n await writeLastRun({\n workspaceDir: params.workspaceDir,\n lastRun: {\n version: 1,\n runId,\n startedAt,\n finishedAt,\n ok: false,\n reason: 'error',\n config: cfg,\n candidates: 0,\n applied: 0,\n memoryPath,\n errorMessage: em,\n },\n }).catch(() => undefined);\n throw err;\n }\n}\n\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DreamingDeepConfig } from './deep-promotion.js';
|
|
2
|
+
type PreviewItem = {
|
|
3
|
+
key: string;
|
|
4
|
+
hash: string;
|
|
5
|
+
snippet: string;
|
|
6
|
+
path: string;
|
|
7
|
+
startLine: number;
|
|
8
|
+
endLine: number;
|
|
9
|
+
score: number;
|
|
10
|
+
avgScore: number;
|
|
11
|
+
recallCount: number;
|
|
12
|
+
alreadyPromotedByKey: boolean;
|
|
13
|
+
alreadyPromotedByHash: boolean;
|
|
14
|
+
skippedReason: string | null;
|
|
15
|
+
};
|
|
16
|
+
export declare function previewDreamingDeepPromotion(params: {
|
|
17
|
+
workspaceDir: string;
|
|
18
|
+
config?: Partial<DreamingDeepConfig>;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
reason: string;
|
|
23
|
+
items: PreviewItem[];
|
|
24
|
+
memoryPath: string;
|
|
25
|
+
}>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { MEMORY_MD_FILENAME } from "./constants.js";
|
|
2
|
+
import { loadDreamingStore } from "./short-term-store.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
//#region src/agent/memory/dreaming/preview.ts
|
|
7
|
+
function clamp01(value) {
|
|
8
|
+
if (!Number.isFinite(value)) return 0;
|
|
9
|
+
return Math.max(0, Math.min(1, value));
|
|
10
|
+
}
|
|
11
|
+
function compare(a, b) {
|
|
12
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
13
|
+
if (b.recallCount !== a.recallCount) return b.recallCount - a.recallCount;
|
|
14
|
+
const aMs = Date.parse(a.lastRecalledAt);
|
|
15
|
+
const bMs = Date.parse(b.lastRecalledAt);
|
|
16
|
+
if (Number.isFinite(aMs) || Number.isFinite(bMs)) {
|
|
17
|
+
if (bMs !== aMs) return bMs - aMs;
|
|
18
|
+
}
|
|
19
|
+
return a.path.localeCompare(b.path);
|
|
20
|
+
}
|
|
21
|
+
async function readFileLines(fullPath) {
|
|
22
|
+
try {
|
|
23
|
+
return (await fs.readFile(fullPath, "utf-8")).split(/\r?\n/);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (err?.code === "ENOENT") return null;
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function sliceRange(lines, startLine, endLine) {
|
|
30
|
+
const startIdx = Math.max(0, startLine - 1);
|
|
31
|
+
const endIdx = Math.min(lines.length, endLine);
|
|
32
|
+
if (startIdx >= endIdx) return "";
|
|
33
|
+
return lines.slice(startIdx, endIdx).map((l) => l.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim().slice(0, 360);
|
|
34
|
+
}
|
|
35
|
+
function normalizeSnippetForHash(snippet) {
|
|
36
|
+
return snippet.trim().replace(/\s+/g, " ").replace(/[“”]/g, "\"").replace(/[‘’]/g, "'").toLowerCase().slice(0, 512);
|
|
37
|
+
}
|
|
38
|
+
function snippetHash(snippet) {
|
|
39
|
+
return createHash("sha1").update(normalizeSnippetForHash(snippet)).digest("hex").slice(0, 12);
|
|
40
|
+
}
|
|
41
|
+
function extractPromotionMarkers(memoryText) {
|
|
42
|
+
const keys = /* @__PURE__ */ new Set();
|
|
43
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const match of memoryText.matchAll(/<!--\s*xopc-memory-promotion\b([\s\S]*?)-->/gi)) {
|
|
45
|
+
const body = match[1] ?? "";
|
|
46
|
+
const k = body.match(/\bkey\s*=\s*"([^"]+)"/i)?.[1]?.trim();
|
|
47
|
+
const h = body.match(/\bhash\s*=\s*"([^"]+)"/i)?.[1]?.trim();
|
|
48
|
+
if (k) keys.add(k);
|
|
49
|
+
if (h) hashes.add(h);
|
|
50
|
+
}
|
|
51
|
+
for (const match of memoryText.matchAll(/<!--\s*xopc-memory-promotion:([^\n]+?)\s*-->/gi)) {
|
|
52
|
+
const key = match[1]?.trim();
|
|
53
|
+
if (key) keys.add(key);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
keys,
|
|
57
|
+
hashes
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function isContaminatedSnippet(snippet) {
|
|
61
|
+
const s = snippet.trim();
|
|
62
|
+
if (!s) return true;
|
|
63
|
+
const lower = s.toLowerCase();
|
|
64
|
+
if ([
|
|
65
|
+
/\b(system|assistant|tool)\s*:/i,
|
|
66
|
+
/<\s*(system|assistant|tool)\b/i,
|
|
67
|
+
/<!--\s*xopc-memory-promotion\b/i,
|
|
68
|
+
/tool_call_id|toolcallid|function_call|arguments\s*:\s*\{/i,
|
|
69
|
+
/"tool"\s*:\s*|\btool_name\b|\btool\b\s*results?/i,
|
|
70
|
+
/you are (an|a)\s+(ai|assistant)|follow these instructions/i,
|
|
71
|
+
/begin\s+(system prompt|instructions)|end\s+(system prompt|instructions)/i,
|
|
72
|
+
/```/i,
|
|
73
|
+
/\b__xopc_/i
|
|
74
|
+
].some((p) => p.test(s))) return true;
|
|
75
|
+
if ((s.match(/[{}[\]]/g) ?? []).length >= 12) return true;
|
|
76
|
+
if ((lower.match(/https?:\/\//g) ?? []).length >= 3) return true;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
function resolveDefaultConfig(overrides) {
|
|
80
|
+
const enabled = overrides?.enabled === true;
|
|
81
|
+
const minScore = typeof overrides?.minScore === "number" ? overrides.minScore : .8;
|
|
82
|
+
const minRecallCount = typeof overrides?.minRecallCount === "number" ? overrides.minRecallCount : 3;
|
|
83
|
+
const limit = typeof overrides?.limit === "number" ? overrides.limit : 10;
|
|
84
|
+
return {
|
|
85
|
+
enabled,
|
|
86
|
+
minScore: clamp01(minScore),
|
|
87
|
+
minRecallCount: Math.max(1, Math.floor(minRecallCount)),
|
|
88
|
+
limit: Math.max(0, Math.floor(limit))
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function previewDreamingDeepPromotion(params) {
|
|
92
|
+
const cfg = resolveDefaultConfig(params.config);
|
|
93
|
+
const memoryPath = path.join(params.workspaceDir, MEMORY_MD_FILENAME);
|
|
94
|
+
if (!cfg.enabled) return {
|
|
95
|
+
ok: true,
|
|
96
|
+
reason: "dreaming disabled",
|
|
97
|
+
items: [],
|
|
98
|
+
memoryPath
|
|
99
|
+
};
|
|
100
|
+
const { store } = await loadDreamingStore({ workspaceDir: params.workspaceDir });
|
|
101
|
+
const ranked = Object.values(store.entries ?? {}).filter((e) => {
|
|
102
|
+
if (!e || typeof e !== "object") return false;
|
|
103
|
+
if (e.promotedAt) return false;
|
|
104
|
+
if (!e.path || !e.path.startsWith("memory/")) return false;
|
|
105
|
+
if (e.recallCount < cfg.minRecallCount) return false;
|
|
106
|
+
return clamp01(e.recallCount > 0 ? e.totalScore / e.recallCount : 0) >= cfg.minScore;
|
|
107
|
+
}).map((e) => {
|
|
108
|
+
const avgScore = e.recallCount > 0 ? clamp01(e.totalScore / e.recallCount) : 0;
|
|
109
|
+
const score = clamp01(avgScore + clamp01(Math.log1p(e.recallCount) / Math.log1p(10)) * .12);
|
|
110
|
+
return {
|
|
111
|
+
...e,
|
|
112
|
+
avgScore,
|
|
113
|
+
score
|
|
114
|
+
};
|
|
115
|
+
}).sort(compare);
|
|
116
|
+
const markers = extractPromotionMarkers(await fs.readFile(memoryPath, "utf-8").catch((err) => {
|
|
117
|
+
if (err?.code === "ENOENT") return "";
|
|
118
|
+
throw err;
|
|
119
|
+
}));
|
|
120
|
+
const limit = Math.min(Math.max(params.limit ?? 20, 1), 50);
|
|
121
|
+
const out = [];
|
|
122
|
+
const scanCap = Math.min(ranked.length, Math.max(limit * 3, limit));
|
|
123
|
+
for (const candidate of ranked.slice(0, scanCap)) {
|
|
124
|
+
if (markers.keys.has(candidate.key)) {
|
|
125
|
+
out.push({
|
|
126
|
+
key: candidate.key,
|
|
127
|
+
hash: "",
|
|
128
|
+
snippet: "",
|
|
129
|
+
path: candidate.path,
|
|
130
|
+
startLine: candidate.startLine,
|
|
131
|
+
endLine: candidate.endLine,
|
|
132
|
+
score: candidate.score,
|
|
133
|
+
avgScore: candidate.avgScore,
|
|
134
|
+
recallCount: candidate.recallCount,
|
|
135
|
+
alreadyPromotedByKey: true,
|
|
136
|
+
alreadyPromotedByHash: false,
|
|
137
|
+
skippedReason: "already promoted (key)"
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const lines = await readFileLines(path.join(params.workspaceDir, candidate.path));
|
|
142
|
+
if (!lines) continue;
|
|
143
|
+
const startLine = Math.max(1, Math.floor(candidate.startLine));
|
|
144
|
+
const endLine = Math.max(startLine, Math.floor(candidate.endLine));
|
|
145
|
+
const snippet = sliceRange(lines, startLine, endLine);
|
|
146
|
+
if (!snippet) continue;
|
|
147
|
+
if (isContaminatedSnippet(snippet)) continue;
|
|
148
|
+
const hash = snippetHash(snippet);
|
|
149
|
+
const alreadyPromotedByHash = markers.hashes.has(hash);
|
|
150
|
+
out.push({
|
|
151
|
+
key: candidate.key,
|
|
152
|
+
hash,
|
|
153
|
+
snippet,
|
|
154
|
+
path: candidate.path,
|
|
155
|
+
startLine,
|
|
156
|
+
endLine,
|
|
157
|
+
score: candidate.score,
|
|
158
|
+
avgScore: candidate.avgScore,
|
|
159
|
+
recallCount: candidate.recallCount,
|
|
160
|
+
alreadyPromotedByKey: false,
|
|
161
|
+
alreadyPromotedByHash,
|
|
162
|
+
skippedReason: alreadyPromotedByHash ? "already promoted (hash)" : null
|
|
163
|
+
});
|
|
164
|
+
if (out.filter((x) => !x.skippedReason).length >= limit) break;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
ok: true,
|
|
168
|
+
reason: "ok",
|
|
169
|
+
items: out,
|
|
170
|
+
memoryPath
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
export { previewDreamingDeepPromotion };
|
|
175
|
+
|
|
176
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","names":[],"sources":["../../../../../src/agent/memory/dreaming/preview.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport type { DreamingDeepConfig } from './deep-promotion.js';\nimport { loadDreamingStore, type DreamingStoreEntry } from './short-term-store.js';\nimport { MEMORY_MD_FILENAME } from './constants.js';\n\ntype PreviewItem = {\n key: string;\n hash: string;\n snippet: string;\n path: string;\n startLine: number;\n endLine: number;\n score: number;\n avgScore: number;\n recallCount: number;\n alreadyPromotedByKey: boolean;\n alreadyPromotedByHash: boolean;\n skippedReason: string | null;\n};\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction compare(a: { score: number; recallCount: number; lastRecalledAt: string; path: string }, b: { score: number; recallCount: number; lastRecalledAt: string; path: string }): number {\n if (b.score !== a.score) return b.score - a.score;\n if (b.recallCount !== a.recallCount) return b.recallCount - a.recallCount;\n const aMs = Date.parse(a.lastRecalledAt);\n const bMs = Date.parse(b.lastRecalledAt);\n if (Number.isFinite(aMs) || Number.isFinite(bMs)) {\n if (bMs !== aMs) return bMs - aMs;\n }\n return a.path.localeCompare(b.path);\n}\n\nasync function readFileLines(fullPath: string): Promise<string[] | null> {\n try {\n const raw = await fs.readFile(fullPath, 'utf-8');\n return raw.split(/\\r?\\n/);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code === 'ENOENT') return null;\n throw err;\n }\n}\n\nfunction sliceRange(lines: string[], startLine: number, endLine: number): string {\n const startIdx = Math.max(0, startLine - 1);\n const endIdx = Math.min(lines.length, endLine);\n if (startIdx >= endIdx) return '';\n return lines\n .slice(startIdx, endIdx)\n .map((l) => l.trim())\n .filter(Boolean)\n .join(' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 360);\n}\n\nfunction normalizeSnippetForHash(snippet: string): string {\n return snippet\n .trim()\n .replace(/\\s+/g, ' ')\n .replace(/[“”]/g, '\"')\n .replace(/[‘’]/g, \"'\")\n .toLowerCase()\n .slice(0, 512);\n}\n\nfunction snippetHash(snippet: string): string {\n return createHash('sha1').update(normalizeSnippetForHash(snippet)).digest('hex').slice(0, 12);\n}\n\nfunction extractPromotionMarkers(memoryText: string): { keys: Set<string>; hashes: Set<string> } {\n const keys = new Set<string>();\n const hashes = new Set<string>();\n for (const match of memoryText.matchAll(/<!--\\s*xopc-memory-promotion\\b([\\s\\S]*?)-->/gi)) {\n const body = match[1] ?? '';\n const k = body.match(/\\bkey\\s*=\\s*\"([^\"]+)\"/i)?.[1]?.trim();\n const h = body.match(/\\bhash\\s*=\\s*\"([^\"]+)\"/i)?.[1]?.trim();\n if (k) keys.add(k);\n if (h) hashes.add(h);\n }\n for (const match of memoryText.matchAll(/<!--\\s*xopc-memory-promotion:([^\\n]+?)\\s*-->/gi)) {\n const key = match[1]?.trim();\n if (key) keys.add(key);\n }\n return { keys, hashes };\n}\n\nfunction isContaminatedSnippet(snippet: string): boolean {\n const s = snippet.trim();\n if (!s) return true;\n const lower = s.toLowerCase();\n const patterns: RegExp[] = [\n /\\b(system|assistant|tool)\\s*:/i,\n /<\\s*(system|assistant|tool)\\b/i,\n /<!--\\s*xopc-memory-promotion\\b/i,\n /tool_call_id|toolcallid|function_call|arguments\\s*:\\s*\\{/i,\n /\"tool\"\\s*:\\s*|\\btool_name\\b|\\btool\\b\\s*results?/i,\n /you are (an|a)\\s+(ai|assistant)|follow these instructions/i,\n /begin\\s+(system prompt|instructions)|end\\s+(system prompt|instructions)/i,\n /```/i,\n /\\b__xopc_/i,\n ];\n if (patterns.some((p) => p.test(s))) return true;\n const braceCount = (s.match(/[{}[\\]]/g) ?? []).length;\n if (braceCount >= 12) return true;\n const urlCount = (lower.match(/https?:\\/\\//g) ?? []).length;\n if (urlCount >= 3) return true;\n return false;\n}\n\nfunction resolveDefaultConfig(overrides?: Partial<DreamingDeepConfig>): DreamingDeepConfig {\n const enabled = overrides?.enabled === true;\n const minScore = typeof overrides?.minScore === 'number' ? overrides.minScore : 0.8;\n const minRecallCount = typeof overrides?.minRecallCount === 'number' ? overrides.minRecallCount : 3;\n const limit = typeof overrides?.limit === 'number' ? overrides.limit : 10;\n return {\n enabled,\n minScore: clamp01(minScore),\n minRecallCount: Math.max(1, Math.floor(minRecallCount)),\n limit: Math.max(0, Math.floor(limit)),\n };\n}\n\nexport async function previewDreamingDeepPromotion(params: {\n workspaceDir: string;\n config?: Partial<DreamingDeepConfig>;\n limit?: number;\n}): Promise<{ ok: boolean; reason: string; items: PreviewItem[]; memoryPath: string }> {\n const cfg = resolveDefaultConfig(params.config);\n const memoryPath = path.join(params.workspaceDir, MEMORY_MD_FILENAME);\n if (!cfg.enabled) return { ok: true, reason: 'dreaming disabled', items: [], memoryPath };\n\n const { store } = await loadDreamingStore({ workspaceDir: params.workspaceDir });\n const all = Object.values(store.entries ?? {}).filter((e): e is DreamingStoreEntry => {\n if (!e || typeof e !== 'object') return false;\n if (e.promotedAt) return false;\n if (!e.path || !e.path.startsWith('memory/')) return false;\n if (e.recallCount < cfg.minRecallCount) return false;\n const avg = e.recallCount > 0 ? e.totalScore / e.recallCount : 0;\n return clamp01(avg) >= cfg.minScore;\n });\n\n const ranked = all\n .map((e) => {\n const avgScore = e.recallCount > 0 ? clamp01(e.totalScore / e.recallCount) : 0;\n const reinforcement = clamp01(Math.log1p(e.recallCount) / Math.log1p(10)) * 0.12;\n const score = clamp01(avgScore + reinforcement);\n return { ...e, avgScore, score };\n })\n .sort(compare);\n\n const existing = await fs.readFile(memoryPath, 'utf-8').catch((err: unknown) => {\n if ((err as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') return '';\n throw err;\n });\n const markers = extractPromotionMarkers(existing);\n\n const limit = Math.min(Math.max(params.limit ?? 20, 1), 50);\n const out: PreviewItem[] = [];\n\n // Scan more than limit so filtering doesn’t yield empty previews.\n const scanCap = Math.min(ranked.length, Math.max(limit * 3, limit));\n for (const candidate of ranked.slice(0, scanCap)) {\n const alreadyPromotedByKey = markers.keys.has(candidate.key);\n if (alreadyPromotedByKey) {\n out.push({\n key: candidate.key,\n hash: '',\n snippet: '',\n path: candidate.path,\n startLine: candidate.startLine,\n endLine: candidate.endLine,\n score: candidate.score,\n avgScore: candidate.avgScore,\n recallCount: candidate.recallCount,\n alreadyPromotedByKey: true,\n alreadyPromotedByHash: false,\n skippedReason: 'already promoted (key)',\n });\n continue;\n }\n\n const fullPath = path.join(params.workspaceDir, candidate.path);\n const lines = await readFileLines(fullPath);\n if (!lines) continue;\n const startLine = Math.max(1, Math.floor(candidate.startLine));\n const endLine = Math.max(startLine, Math.floor(candidate.endLine));\n const snippet = sliceRange(lines, startLine, endLine);\n if (!snippet) continue;\n if (isContaminatedSnippet(snippet)) continue;\n const hash = snippetHash(snippet);\n const alreadyPromotedByHash = markers.hashes.has(hash);\n out.push({\n key: candidate.key,\n hash,\n snippet,\n path: candidate.path,\n startLine,\n endLine,\n score: candidate.score,\n avgScore: candidate.avgScore,\n recallCount: candidate.recallCount,\n alreadyPromotedByKey: false,\n alreadyPromotedByHash,\n skippedReason: alreadyPromotedByHash ? 'already promoted (hash)' : null,\n });\n if (out.filter((x) => !x.skippedReason).length >= limit) break;\n }\n\n return { ok: true, reason: 'ok', items: out, memoryPath };\n}\n\n"],"mappings":"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"}
|