@oyasmi/pipiclaw 0.5.1 → 0.5.2
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/agent/channel-runner.d.ts +46 -0
- package/dist/agent/channel-runner.d.ts.map +1 -0
- package/dist/agent/channel-runner.js +434 -0
- package/dist/agent/channel-runner.js.map +1 -0
- package/dist/agent/index.d.ts +4 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +3 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/progress-formatter.d.ts +5 -0
- package/dist/agent/progress-formatter.d.ts.map +1 -0
- package/dist/agent/progress-formatter.js +53 -0
- package/dist/agent/progress-formatter.js.map +1 -0
- package/dist/agent/run-queue.d.ts +8 -0
- package/dist/agent/run-queue.d.ts.map +1 -0
- package/dist/agent/run-queue.js +27 -0
- package/dist/agent/run-queue.js.map +1 -0
- package/dist/agent/runner-factory.d.ts +4 -0
- package/dist/agent/runner-factory.d.ts.map +1 -0
- package/dist/agent/runner-factory.js +11 -0
- package/dist/agent/runner-factory.js.map +1 -0
- package/dist/agent/session-events.d.ts +15 -0
- package/dist/agent/session-events.d.ts.map +1 -0
- package/dist/agent/session-events.js +216 -0
- package/dist/agent/session-events.js.map +1 -0
- package/dist/agent/type-guards.d.ts +23 -0
- package/dist/agent/type-guards.d.ts.map +1 -0
- package/dist/agent/type-guards.js +107 -0
- package/dist/agent/type-guards.js.map +1 -0
- package/dist/agent/types.d.ts +161 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +23 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/agent.d.ts +2 -15
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +1 -781
- package/dist/agent.js.map +1 -1
- package/dist/context.d.ts +58 -14
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +50 -7
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/main.js +5 -404
- package/dist/main.js.map +1 -1
- package/dist/memory/bootstrap.d.ts +7 -0
- package/dist/memory/bootstrap.d.ts.map +1 -0
- package/dist/memory/bootstrap.js +47 -0
- package/dist/memory/bootstrap.js.map +1 -0
- package/dist/{memory-candidates.d.ts → memory/candidates.d.ts} +2 -1
- package/dist/memory/candidates.d.ts.map +1 -0
- package/dist/{memory-candidates.js → memory/candidates.js} +34 -21
- package/dist/memory/candidates.js.map +1 -0
- package/dist/memory/chinese-words.d.ts +2 -0
- package/dist/memory/chinese-words.d.ts.map +1 -0
- package/dist/memory/chinese-words.js +210 -0
- package/dist/memory/chinese-words.js.map +1 -0
- package/dist/{memory-consolidation.d.ts → memory/consolidation.d.ts} +1 -1
- package/dist/memory/consolidation.d.ts.map +1 -0
- package/dist/{memory-consolidation.js → memory/consolidation.js} +27 -35
- package/dist/memory/consolidation.js.map +1 -0
- package/dist/{memory-files.d.ts → memory/files.d.ts} +1 -6
- package/dist/memory/files.d.ts.map +1 -0
- package/dist/{memory-files.js → memory/files.js} +12 -36
- package/dist/memory/files.js.map +1 -0
- package/dist/{memory-lifecycle.d.ts → memory/lifecycle.d.ts} +24 -6
- package/dist/memory/lifecycle.d.ts.map +1 -0
- package/dist/memory/lifecycle.js +247 -0
- package/dist/memory/lifecycle.js.map +1 -0
- package/dist/{memory-recall.d.ts → memory/recall.d.ts} +2 -2
- package/dist/memory/recall.d.ts.map +1 -0
- package/dist/memory/recall.js +435 -0
- package/dist/memory/recall.js.map +1 -0
- package/dist/{session-memory.d.ts → memory/session.d.ts} +2 -1
- package/dist/memory/session.d.ts.map +1 -0
- package/dist/{session-memory.js → memory/session.js} +32 -62
- package/dist/memory/session.js.map +1 -0
- package/dist/runtime/bootstrap.d.ts +48 -0
- package/dist/runtime/bootstrap.d.ts.map +1 -0
- package/dist/runtime/bootstrap.js +451 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/delivery.d.ts.map +1 -0
- package/dist/{delivery.js → runtime/delivery.js} +1 -1
- package/dist/runtime/delivery.js.map +1 -0
- package/dist/{dingtalk.d.ts → runtime/dingtalk.d.ts} +10 -0
- package/dist/runtime/dingtalk.d.ts.map +1 -0
- package/dist/{dingtalk.js → runtime/dingtalk.js} +87 -27
- package/dist/runtime/dingtalk.js.map +1 -0
- package/dist/runtime/events.d.ts.map +1 -0
- package/dist/{events.js → runtime/events.js} +1 -1
- package/dist/runtime/events.js.map +1 -0
- package/dist/{store.d.ts → runtime/store.d.ts} +5 -0
- package/dist/runtime/store.d.ts.map +1 -0
- package/dist/{store.js → runtime/store.js} +60 -19
- package/dist/runtime/store.js.map +1 -0
- package/dist/shared/markdown-sections.d.ts +7 -0
- package/dist/shared/markdown-sections.d.ts.map +1 -0
- package/dist/{markdown-sections.js → shared/markdown-sections.js} +10 -3
- package/dist/shared/markdown-sections.js.map +1 -0
- package/dist/shared/text-utils.d.ts +10 -0
- package/dist/shared/text-utils.d.ts.map +1 -0
- package/dist/shared/text-utils.js +37 -0
- package/dist/shared/text-utils.js.map +1 -0
- package/dist/shared/type-guards.d.ts +6 -0
- package/dist/shared/type-guards.d.ts.map +1 -0
- package/dist/shared/type-guards.js +13 -0
- package/dist/shared/type-guards.js.map +1 -0
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/sidecar-worker.d.ts.map +1 -1
- package/dist/sidecar-worker.js +1 -7
- package/dist/sidecar-worker.js.map +1 -1
- package/dist/{sub-agents.d.ts → subagents/discovery.d.ts} +1 -1
- package/dist/subagents/discovery.d.ts.map +1 -0
- package/dist/{sub-agents.js → subagents/discovery.js} +3 -3
- package/dist/subagents/discovery.js.map +1 -0
- package/dist/{tools/subagent.d.ts → subagents/tool.d.ts} +3 -16
- package/dist/{tools/subagent.d.ts.map → subagents/tool.d.ts.map} +1 -1
- package/dist/{tools/subagent.js → subagents/tool.js} +17 -38
- package/dist/subagents/tool.js.map +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/docs/memory-audit.md +330 -0
- package/docs/memory-optimization-round2.md +319 -0
- package/package.json +1 -1
- package/dist/delivery.d.ts.map +0 -1
- package/dist/delivery.js.map +0 -1
- package/dist/dingtalk.d.ts.map +0 -1
- package/dist/dingtalk.js.map +0 -1
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/markdown-sections.d.ts +0 -6
- package/dist/markdown-sections.d.ts.map +0 -1
- package/dist/markdown-sections.js.map +0 -1
- package/dist/memory-candidates.d.ts.map +0 -1
- package/dist/memory-candidates.js.map +0 -1
- package/dist/memory-consolidation.d.ts.map +0 -1
- package/dist/memory-consolidation.js.map +0 -1
- package/dist/memory-files.d.ts.map +0 -1
- package/dist/memory-files.js.map +0 -1
- package/dist/memory-lifecycle.d.ts.map +0 -1
- package/dist/memory-lifecycle.js +0 -150
- package/dist/memory-lifecycle.js.map +0 -1
- package/dist/memory-recall.d.ts.map +0 -1
- package/dist/memory-recall.js +0 -218
- package/dist/memory-recall.js.map +0 -1
- package/dist/session-memory-files.d.ts +0 -2
- package/dist/session-memory-files.d.ts.map +0 -1
- package/dist/session-memory-files.js +0 -2
- package/dist/session-memory-files.js.map +0 -1
- package/dist/session-memory.d.ts.map +0 -1
- package/dist/session-memory.js.map +0 -1
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/sub-agents.d.ts.map +0 -1
- package/dist/sub-agents.js.map +0 -1
- package/dist/tools/subagent.js.map +0 -1
- package/docs/proj-review.md +0 -188
- package/docs/test-supplementation-plan.md +0 -553
- /package/dist/{delivery.d.ts → runtime/delivery.d.ts} +0 -0
- /package/dist/{events.d.ts → runtime/events.d.ts} +0 -0
- /package/docs/{memory-rfc.md → specs/001-implement-memory/memory-rfc.md} +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-analyse.txt +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-design.txt +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-phase1-plan.txt +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/design.md +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/interfaces-and-tests.md +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/spec.md +0 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { parseJsonObject } from "../llm-json.js";
|
|
2
|
+
import { HAN_REGEX } from "../shared/text-utils.js";
|
|
3
|
+
import { runSidecarTask } from "../sidecar-worker.js";
|
|
4
|
+
import { buildMemoryCandidates } from "./candidates.js";
|
|
5
|
+
import { COMMON_CHINESE_WORDS } from "./chinese-words.js";
|
|
6
|
+
const RERANK_SYSTEM_PROMPT = `You are selecting which memory snippets are most relevant to the current user turn.
|
|
7
|
+
|
|
8
|
+
Return strict JSON only:
|
|
9
|
+
{
|
|
10
|
+
"selectedIds": ["candidate-id"]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
- Select only snippets that are clearly useful for answering the current turn.
|
|
15
|
+
- Prefer current work state, constraints, active files, recent corrections, and durable decisions.
|
|
16
|
+
- If nothing is clearly useful, return an empty array.
|
|
17
|
+
- Do not rewrite the candidates. Only return candidate ids.`;
|
|
18
|
+
const TOKEN_PART_REGEX = /[\p{Script=Han}]+|[\p{L}\p{N}_./-]+/gu;
|
|
19
|
+
const ASCII_SPLIT_REGEX = /[._/-]+/g;
|
|
20
|
+
const MEMORY_RECALL_RERANK_TIMEOUT_MS = 8_000;
|
|
21
|
+
const RERANK_CONTENT_CLIP = 800;
|
|
22
|
+
const MAX_HAN_WORD_LENGTH = Array.from(COMMON_CHINESE_WORDS).reduce((max, word) => Math.max(max, word.length), 2);
|
|
23
|
+
const LATIN_STOP_WORDS = new Set([
|
|
24
|
+
"a",
|
|
25
|
+
"an",
|
|
26
|
+
"and",
|
|
27
|
+
"are",
|
|
28
|
+
"as",
|
|
29
|
+
"at",
|
|
30
|
+
"be",
|
|
31
|
+
"been",
|
|
32
|
+
"being",
|
|
33
|
+
"by",
|
|
34
|
+
"can",
|
|
35
|
+
"could",
|
|
36
|
+
"did",
|
|
37
|
+
"do",
|
|
38
|
+
"does",
|
|
39
|
+
"doing",
|
|
40
|
+
"for",
|
|
41
|
+
"from",
|
|
42
|
+
"had",
|
|
43
|
+
"has",
|
|
44
|
+
"have",
|
|
45
|
+
"here",
|
|
46
|
+
"how",
|
|
47
|
+
"i",
|
|
48
|
+
"in",
|
|
49
|
+
"is",
|
|
50
|
+
"it",
|
|
51
|
+
"its",
|
|
52
|
+
"me",
|
|
53
|
+
"my",
|
|
54
|
+
"of",
|
|
55
|
+
"on",
|
|
56
|
+
"or",
|
|
57
|
+
"our",
|
|
58
|
+
"please",
|
|
59
|
+
"should",
|
|
60
|
+
"that",
|
|
61
|
+
"the",
|
|
62
|
+
"their",
|
|
63
|
+
"them",
|
|
64
|
+
"there",
|
|
65
|
+
"these",
|
|
66
|
+
"they",
|
|
67
|
+
"this",
|
|
68
|
+
"to",
|
|
69
|
+
"was",
|
|
70
|
+
"we",
|
|
71
|
+
"were",
|
|
72
|
+
"what",
|
|
73
|
+
"when",
|
|
74
|
+
"where",
|
|
75
|
+
"which",
|
|
76
|
+
"who",
|
|
77
|
+
"why",
|
|
78
|
+
"with",
|
|
79
|
+
"would",
|
|
80
|
+
"you",
|
|
81
|
+
"your",
|
|
82
|
+
]);
|
|
83
|
+
function containsHanText(text) {
|
|
84
|
+
return HAN_REGEX.test(text);
|
|
85
|
+
}
|
|
86
|
+
function tokenizeHanPart(part) {
|
|
87
|
+
const chars = Array.from(part);
|
|
88
|
+
const tokens = [];
|
|
89
|
+
for (let index = 0; index < chars.length;) {
|
|
90
|
+
let matched = "";
|
|
91
|
+
const maxLength = Math.min(MAX_HAN_WORD_LENGTH, chars.length - index);
|
|
92
|
+
for (let size = maxLength; size >= 2; size--) {
|
|
93
|
+
const candidate = chars.slice(index, index + size).join("");
|
|
94
|
+
if (COMMON_CHINESE_WORDS.has(candidate)) {
|
|
95
|
+
matched = candidate;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (matched) {
|
|
100
|
+
tokens.push(matched);
|
|
101
|
+
index += Array.from(matched).length;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
index++;
|
|
105
|
+
}
|
|
106
|
+
for (let index = 0; index <= chars.length - 2; index++) {
|
|
107
|
+
tokens.push(chars.slice(index, index + 2).join(""));
|
|
108
|
+
}
|
|
109
|
+
return Array.from(new Set(tokens));
|
|
110
|
+
}
|
|
111
|
+
function tokenizeAsciiPart(part) {
|
|
112
|
+
const tokens = [];
|
|
113
|
+
const normalized = part.toLowerCase();
|
|
114
|
+
const segments = normalized.split(ASCII_SPLIT_REGEX).filter(Boolean);
|
|
115
|
+
if (normalized.length >= 2 && !LATIN_STOP_WORDS.has(normalized)) {
|
|
116
|
+
tokens.push(normalized);
|
|
117
|
+
}
|
|
118
|
+
for (const segment of segments) {
|
|
119
|
+
if (segment.length >= 2 && !LATIN_STOP_WORDS.has(segment)) {
|
|
120
|
+
tokens.push(segment);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return tokens;
|
|
124
|
+
}
|
|
125
|
+
function tokenize(text) {
|
|
126
|
+
const parts = text.toLowerCase().match(TOKEN_PART_REGEX) ?? [];
|
|
127
|
+
const tokens = [];
|
|
128
|
+
for (const part of parts) {
|
|
129
|
+
if (containsHanText(part)) {
|
|
130
|
+
tokens.push(...tokenizeHanPart(part));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
tokens.push(...tokenizeAsciiPart(part));
|
|
134
|
+
}
|
|
135
|
+
return Array.from(new Set(tokens));
|
|
136
|
+
}
|
|
137
|
+
function buildTokenSet(text) {
|
|
138
|
+
return new Set(tokenize(text));
|
|
139
|
+
}
|
|
140
|
+
function computeTokenMatchStats(queryTokens, text) {
|
|
141
|
+
if (queryTokens.length === 0 || !text.trim()) {
|
|
142
|
+
return { matchedCount: 0, coverage: 0 };
|
|
143
|
+
}
|
|
144
|
+
const haystack = buildTokenSet(text);
|
|
145
|
+
let matchedCount = 0;
|
|
146
|
+
for (const token of queryTokens) {
|
|
147
|
+
if (haystack.has(token)) {
|
|
148
|
+
matchedCount++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
matchedCount,
|
|
153
|
+
coverage: matchedCount / queryTokens.length,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function collectMatchingQueryTokens(queryTokens, texts) {
|
|
157
|
+
const haystack = new Set();
|
|
158
|
+
for (const text of texts) {
|
|
159
|
+
for (const token of tokenize(text)) {
|
|
160
|
+
haystack.add(token);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const matches = new Set();
|
|
164
|
+
for (const token of queryTokens) {
|
|
165
|
+
if (haystack.has(token)) {
|
|
166
|
+
matches.add(token);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return matches;
|
|
170
|
+
}
|
|
171
|
+
function computeExactMatchBoost(query, candidate) {
|
|
172
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
173
|
+
if (!normalizedQuery) {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
const minLength = containsHanText(normalizedQuery) ? 2 : 4;
|
|
177
|
+
if (normalizedQuery.length < minLength) {
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
let boost = 0;
|
|
181
|
+
const scoringFields = [
|
|
182
|
+
[candidate.title, 12],
|
|
183
|
+
[candidate.searchText ?? candidate.content, 8],
|
|
184
|
+
[candidate.path, 4],
|
|
185
|
+
];
|
|
186
|
+
for (const [field, value] of scoringFields) {
|
|
187
|
+
if (field.toLowerCase().includes(normalizedQuery)) {
|
|
188
|
+
boost += value;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return boost;
|
|
192
|
+
}
|
|
193
|
+
function computeRecencyBoost(timestamp) {
|
|
194
|
+
if (!timestamp)
|
|
195
|
+
return 0;
|
|
196
|
+
const timestampMs = Date.parse(timestamp);
|
|
197
|
+
if (!Number.isFinite(timestampMs))
|
|
198
|
+
return 0;
|
|
199
|
+
const ageMs = Date.now() - timestampMs;
|
|
200
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
201
|
+
if (ageMs <= dayMs)
|
|
202
|
+
return 6;
|
|
203
|
+
if (ageMs <= 7 * dayMs)
|
|
204
|
+
return 4;
|
|
205
|
+
if (ageMs <= 30 * dayMs)
|
|
206
|
+
return 2;
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
function detectQueryIntents(query) {
|
|
210
|
+
const intents = new Set();
|
|
211
|
+
if (/\b(now|current|currently|status)\b/i.test(query) || /(现在|当前|目前|正在|状态)/u.test(query)) {
|
|
212
|
+
intents.add("current-state");
|
|
213
|
+
}
|
|
214
|
+
if (/\b(next|follow-?up|todo|plan)\b/i.test(query) || /(下一步|接下来|后续|怎么办|怎么做|该查什么)/u.test(query)) {
|
|
215
|
+
intents.add("next-steps");
|
|
216
|
+
}
|
|
217
|
+
if (/\b(constraint|requirement|guardrail|compatible|compatibility)\b/i.test(query) ||
|
|
218
|
+
/(约束|限制|要求|兼容|注意事项)/u.test(query)) {
|
|
219
|
+
intents.add("constraints");
|
|
220
|
+
}
|
|
221
|
+
if (/\b(decision|decided|why)\b/i.test(query) || /(决策|决定|方案|为什么)/u.test(query)) {
|
|
222
|
+
intents.add("decisions");
|
|
223
|
+
}
|
|
224
|
+
if (/\b(error|bug|failure|issue|regression)\b/i.test(query) || /(错误|异常|失败|问题|缺陷|回归)/u.test(query)) {
|
|
225
|
+
intents.add("errors");
|
|
226
|
+
}
|
|
227
|
+
if (/\b(history|previous|before|earlier|past)\b/i.test(query) || /(历史|之前|以前|过去|早先|曾经)/u.test(query)) {
|
|
228
|
+
intents.add("history");
|
|
229
|
+
}
|
|
230
|
+
return intents;
|
|
231
|
+
}
|
|
232
|
+
function computeSectionIntentBoost(intents, candidate) {
|
|
233
|
+
const kind = candidate.sectionKind ?? "";
|
|
234
|
+
let boost = 0;
|
|
235
|
+
if (intents.has("current-state") && kind === "current state")
|
|
236
|
+
boost += 10;
|
|
237
|
+
if (intents.has("next-steps") && kind === "next steps")
|
|
238
|
+
boost += 10;
|
|
239
|
+
if (intents.has("constraints") && kind.includes("constraint"))
|
|
240
|
+
boost += 8;
|
|
241
|
+
if (intents.has("decisions") && kind.includes("decision"))
|
|
242
|
+
boost += 8;
|
|
243
|
+
if (intents.has("errors") && kind === "errors & corrections")
|
|
244
|
+
boost += 8;
|
|
245
|
+
if (intents.has("history") && candidate.source === "channel-history")
|
|
246
|
+
boost += 8;
|
|
247
|
+
return boost;
|
|
248
|
+
}
|
|
249
|
+
function compareScoredCandidates(a, b) {
|
|
250
|
+
return (b.score - a.score ||
|
|
251
|
+
b.lexicalMatchCount - a.lexicalMatchCount ||
|
|
252
|
+
b.candidate.priority - a.candidate.priority ||
|
|
253
|
+
a.candidate.title.localeCompare(b.candidate.title));
|
|
254
|
+
}
|
|
255
|
+
function scoreCandidate(query, queryTokens, intents, candidate) {
|
|
256
|
+
const searchText = candidate.searchText ?? candidate.content;
|
|
257
|
+
const titleStats = computeTokenMatchStats(queryTokens, candidate.title);
|
|
258
|
+
const contentStats = computeTokenMatchStats(queryTokens, searchText);
|
|
259
|
+
const pathStats = computeTokenMatchStats(queryTokens, candidate.path);
|
|
260
|
+
const matchedTokens = collectMatchingQueryTokens(queryTokens, [candidate.title, searchText, candidate.path]);
|
|
261
|
+
const exactBoost = computeExactMatchBoost(query, candidate);
|
|
262
|
+
const intentBoost = computeSectionIntentBoost(intents, candidate);
|
|
263
|
+
const overallCoverage = queryTokens.length > 0 ? matchedTokens.size / queryTokens.length : 0;
|
|
264
|
+
const lexicalScore = overallCoverage * 48 +
|
|
265
|
+
titleStats.coverage * 18 +
|
|
266
|
+
contentStats.coverage * 22 +
|
|
267
|
+
pathStats.coverage * 8 +
|
|
268
|
+
exactBoost;
|
|
269
|
+
const structuralScore = candidate.priority + intentBoost + computeRecencyBoost(candidate.timestamp);
|
|
270
|
+
if (matchedTokens.size === 0 && exactBoost === 0) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
candidate,
|
|
275
|
+
score: lexicalScore * (1 + structuralScore / 100),
|
|
276
|
+
lexicalMatchCount: matchedTokens.size,
|
|
277
|
+
intentBoost,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function seedIntentCandidates(request, candidates, existing, intents, queryTokens) {
|
|
281
|
+
if (intents.size === 0) {
|
|
282
|
+
return existing;
|
|
283
|
+
}
|
|
284
|
+
const seen = new Set(existing.map(({ candidate }) => candidate.id));
|
|
285
|
+
const seeded = [...existing];
|
|
286
|
+
const limit = Math.max(request.maxCandidates, request.maxInjected);
|
|
287
|
+
const intentCandidates = candidates
|
|
288
|
+
.map((candidate) => ({
|
|
289
|
+
candidate,
|
|
290
|
+
intentBoost: computeSectionIntentBoost(intents, candidate),
|
|
291
|
+
}))
|
|
292
|
+
.filter(({ candidate, intentBoost }) => intentBoost > 0 && !seen.has(candidate.id))
|
|
293
|
+
.sort((a, b) => b.intentBoost - a.intentBoost ||
|
|
294
|
+
b.candidate.priority - a.candidate.priority ||
|
|
295
|
+
a.candidate.title.localeCompare(b.candidate.title));
|
|
296
|
+
for (const { candidate, intentBoost } of intentCandidates) {
|
|
297
|
+
const matchedTokens = collectMatchingQueryTokens(queryTokens, [
|
|
298
|
+
candidate.title,
|
|
299
|
+
candidate.searchText ?? candidate.content,
|
|
300
|
+
]);
|
|
301
|
+
if (matchedTokens.size === 0) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
seeded.push({
|
|
305
|
+
candidate,
|
|
306
|
+
score: (intentBoost + matchedTokens.size * 8) *
|
|
307
|
+
(1 + (candidate.priority + computeRecencyBoost(candidate.timestamp)) / 100),
|
|
308
|
+
lexicalMatchCount: matchedTokens.size,
|
|
309
|
+
intentBoost,
|
|
310
|
+
});
|
|
311
|
+
seen.add(candidate.id);
|
|
312
|
+
if (seeded.length >= limit) {
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return seeded;
|
|
317
|
+
}
|
|
318
|
+
async function rerankCandidates(request, candidates) {
|
|
319
|
+
if ((!request.rerankWithModel && !request.autoRerank) || candidates.length <= request.maxInjected) {
|
|
320
|
+
return candidates;
|
|
321
|
+
}
|
|
322
|
+
const renderedCandidates = candidates
|
|
323
|
+
.map(({ candidate, score, lexicalMatchCount, intentBoost }) => {
|
|
324
|
+
const clippedContent = candidate.content.length > RERANK_CONTENT_CLIP
|
|
325
|
+
? `${candidate.content.slice(0, RERANK_CONTENT_CLIP)}...`
|
|
326
|
+
: candidate.content;
|
|
327
|
+
return [
|
|
328
|
+
`id: ${candidate.id}`,
|
|
329
|
+
`source: ${candidate.source}`,
|
|
330
|
+
`title: ${candidate.title}`,
|
|
331
|
+
`path: ${candidate.path}`,
|
|
332
|
+
`score: ${score}`,
|
|
333
|
+
`lexicalMatchCount: ${lexicalMatchCount}`,
|
|
334
|
+
`intentBoost: ${intentBoost}`,
|
|
335
|
+
`content: ${clippedContent}`,
|
|
336
|
+
].join("\n");
|
|
337
|
+
})
|
|
338
|
+
.join("\n\n---\n\n");
|
|
339
|
+
try {
|
|
340
|
+
const result = await runSidecarTask({
|
|
341
|
+
name: "memory-recall-rerank",
|
|
342
|
+
model: request.model,
|
|
343
|
+
resolveApiKey: request.resolveApiKey,
|
|
344
|
+
systemPrompt: RERANK_SYSTEM_PROMPT,
|
|
345
|
+
prompt: `User turn:\n${request.query.trim()}\n\nCandidates:\n${renderedCandidates}`,
|
|
346
|
+
timeoutMs: MEMORY_RECALL_RERANK_TIMEOUT_MS,
|
|
347
|
+
parse: (text) => {
|
|
348
|
+
const parsed = parseJsonObject(text);
|
|
349
|
+
return Array.isArray(parsed.selectedIds)
|
|
350
|
+
? parsed.selectedIds.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
351
|
+
: [];
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
const selectedIds = new Set(result.output);
|
|
355
|
+
if (selectedIds.size === 0) {
|
|
356
|
+
return candidates;
|
|
357
|
+
}
|
|
358
|
+
const selected = candidates.filter(({ candidate }) => selectedIds.has(candidate.id));
|
|
359
|
+
return selected.length > 0 ? selected : candidates;
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return candidates;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function renderRecallResult(items, maxChars) {
|
|
366
|
+
if (items.length === 0) {
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
const lines = ["<runtime_context>", "Relevant context for this turn:"];
|
|
370
|
+
for (const item of items) {
|
|
371
|
+
lines.push("");
|
|
372
|
+
lines.push(`[${item.source}/${item.title}]`);
|
|
373
|
+
lines.push(`Path: ${item.path}`);
|
|
374
|
+
lines.push(item.content);
|
|
375
|
+
}
|
|
376
|
+
lines.push("</runtime_context>");
|
|
377
|
+
const rendered = lines.join("\n");
|
|
378
|
+
if (rendered.length <= maxChars) {
|
|
379
|
+
return rendered;
|
|
380
|
+
}
|
|
381
|
+
const clippedLines = ["<runtime_context>", "Relevant context for this turn:"];
|
|
382
|
+
let usedChars = clippedLines.join("\n").length + "</runtime_context>".length + 2;
|
|
383
|
+
for (const item of items) {
|
|
384
|
+
const block = ["", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content].join("\n");
|
|
385
|
+
if (usedChars + block.length > maxChars) {
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
clippedLines.push("", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content);
|
|
389
|
+
usedChars += block.length;
|
|
390
|
+
}
|
|
391
|
+
clippedLines.push("</runtime_context>");
|
|
392
|
+
return clippedLines.join("\n");
|
|
393
|
+
}
|
|
394
|
+
export async function recallRelevantMemory(request) {
|
|
395
|
+
const query = request.query.trim();
|
|
396
|
+
if (!query) {
|
|
397
|
+
return { items: [], renderedText: "" };
|
|
398
|
+
}
|
|
399
|
+
const candidates = await buildMemoryCandidates({
|
|
400
|
+
workspaceDir: request.workspaceDir,
|
|
401
|
+
channelDir: request.channelDir,
|
|
402
|
+
cache: request.candidateCache,
|
|
403
|
+
});
|
|
404
|
+
const filteredCandidates = request.allowedSources?.length
|
|
405
|
+
? candidates.filter((candidate) => request.allowedSources?.includes(candidate.source))
|
|
406
|
+
: candidates;
|
|
407
|
+
if (filteredCandidates.length === 0) {
|
|
408
|
+
return { items: [], renderedText: "" };
|
|
409
|
+
}
|
|
410
|
+
const queryTokens = tokenize(query);
|
|
411
|
+
const queryIntents = detectQueryIntents(query);
|
|
412
|
+
const scored = filteredCandidates
|
|
413
|
+
.map((candidate) => scoreCandidate(query, queryTokens, queryIntents, candidate))
|
|
414
|
+
.filter((candidate) => candidate !== null)
|
|
415
|
+
.sort(compareScoredCandidates);
|
|
416
|
+
const shortlist = seedIntentCandidates(request, filteredCandidates, scored, queryIntents, queryTokens)
|
|
417
|
+
.sort(compareScoredCandidates)
|
|
418
|
+
.slice(0, Math.max(request.maxCandidates, request.maxInjected));
|
|
419
|
+
if (shortlist.length === 0) {
|
|
420
|
+
return { items: [], renderedText: "" };
|
|
421
|
+
}
|
|
422
|
+
const reranked = await rerankCandidates(request, shortlist);
|
|
423
|
+
const items = reranked.slice(0, request.maxInjected).map(({ candidate, score }) => ({
|
|
424
|
+
source: candidate.source,
|
|
425
|
+
path: candidate.path,
|
|
426
|
+
title: candidate.title,
|
|
427
|
+
content: candidate.content,
|
|
428
|
+
score,
|
|
429
|
+
}));
|
|
430
|
+
return {
|
|
431
|
+
items,
|
|
432
|
+
renderedText: renderRecallResult(items, request.maxChars),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall.js","sourceRoot":"","sources":["../../src/memory/recall.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model } from \"@mariozechner/pi-ai\";\nimport { parseJsonObject } from \"../llm-json.js\";\nimport { HAN_REGEX } from \"../shared/text-utils.js\";\nimport { runSidecarTask } from \"../sidecar-worker.js\";\nimport { buildMemoryCandidates, type MemoryCandidate, type MemoryCandidateCache } from \"./candidates.js\";\nimport { COMMON_CHINESE_WORDS } from \"./chinese-words.js\";\n\nexport interface RecallRequest {\n\tquery: string;\n\tworkspaceDir: string;\n\tchannelDir: string;\n\tallowedSources?: MemoryCandidate[\"source\"][];\n\tmaxCandidates: number;\n\tmaxInjected: number;\n\tmaxChars: number;\n\trerankWithModel: boolean;\n\tautoRerank?: boolean;\n\tmodel: Model<Api>;\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tcandidateCache?: MemoryCandidateCache;\n}\n\nexport interface RecalledMemory {\n\tsource: MemoryCandidate[\"source\"];\n\tpath: string;\n\ttitle: string;\n\tcontent: string;\n\tscore: number;\n}\n\nexport interface RecallResult {\n\titems: RecalledMemory[];\n\trenderedText: string;\n}\n\ntype QueryIntent = \"current-state\" | \"next-steps\" | \"constraints\" | \"decisions\" | \"errors\" | \"history\";\n\ninterface TokenMatchStats {\n\tmatchedCount: number;\n\tcoverage: number;\n}\n\ninterface ScoredCandidate {\n\tcandidate: MemoryCandidate;\n\tscore: number;\n\tlexicalMatchCount: number;\n\tintentBoost: number;\n}\n\nconst RERANK_SYSTEM_PROMPT = `You are selecting which memory snippets are most relevant to the current user turn.\n\nReturn strict JSON only:\n{\n \"selectedIds\": [\"candidate-id\"]\n}\n\nRules:\n- Select only snippets that are clearly useful for answering the current turn.\n- Prefer current work state, constraints, active files, recent corrections, and durable decisions.\n- If nothing is clearly useful, return an empty array.\n- Do not rewrite the candidates. Only return candidate ids.`;\n\nconst TOKEN_PART_REGEX = /[\\p{Script=Han}]+|[\\p{L}\\p{N}_./-]+/gu;\nconst ASCII_SPLIT_REGEX = /[._/-]+/g;\nconst MEMORY_RECALL_RERANK_TIMEOUT_MS = 8_000;\nconst RERANK_CONTENT_CLIP = 800;\nconst MAX_HAN_WORD_LENGTH = Array.from(COMMON_CHINESE_WORDS).reduce((max, word) => Math.max(max, word.length), 2);\nconst LATIN_STOP_WORDS = new Set([\n\t\"a\",\n\t\"an\",\n\t\"and\",\n\t\"are\",\n\t\"as\",\n\t\"at\",\n\t\"be\",\n\t\"been\",\n\t\"being\",\n\t\"by\",\n\t\"can\",\n\t\"could\",\n\t\"did\",\n\t\"do\",\n\t\"does\",\n\t\"doing\",\n\t\"for\",\n\t\"from\",\n\t\"had\",\n\t\"has\",\n\t\"have\",\n\t\"here\",\n\t\"how\",\n\t\"i\",\n\t\"in\",\n\t\"is\",\n\t\"it\",\n\t\"its\",\n\t\"me\",\n\t\"my\",\n\t\"of\",\n\t\"on\",\n\t\"or\",\n\t\"our\",\n\t\"please\",\n\t\"should\",\n\t\"that\",\n\t\"the\",\n\t\"their\",\n\t\"them\",\n\t\"there\",\n\t\"these\",\n\t\"they\",\n\t\"this\",\n\t\"to\",\n\t\"was\",\n\t\"we\",\n\t\"were\",\n\t\"what\",\n\t\"when\",\n\t\"where\",\n\t\"which\",\n\t\"who\",\n\t\"why\",\n\t\"with\",\n\t\"would\",\n\t\"you\",\n\t\"your\",\n]);\n\nfunction containsHanText(text: string): boolean {\n\treturn HAN_REGEX.test(text);\n}\n\nfunction tokenizeHanPart(part: string): string[] {\n\tconst chars = Array.from(part);\n\tconst tokens: string[] = [];\n\n\tfor (let index = 0; index < chars.length; ) {\n\t\tlet matched = \"\";\n\t\tconst maxLength = Math.min(MAX_HAN_WORD_LENGTH, chars.length - index);\n\t\tfor (let size = maxLength; size >= 2; size--) {\n\t\t\tconst candidate = chars.slice(index, index + size).join(\"\");\n\t\t\tif (COMMON_CHINESE_WORDS.has(candidate)) {\n\t\t\t\tmatched = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (matched) {\n\t\t\ttokens.push(matched);\n\t\t\tindex += Array.from(matched).length;\n\t\t\tcontinue;\n\t\t}\n\t\tindex++;\n\t}\n\n\tfor (let index = 0; index <= chars.length - 2; index++) {\n\t\ttokens.push(chars.slice(index, index + 2).join(\"\"));\n\t}\n\n\treturn Array.from(new Set(tokens));\n}\n\nfunction tokenizeAsciiPart(part: string): string[] {\n\tconst tokens: string[] = [];\n\tconst normalized = part.toLowerCase();\n\tconst segments = normalized.split(ASCII_SPLIT_REGEX).filter(Boolean);\n\n\tif (normalized.length >= 2 && !LATIN_STOP_WORDS.has(normalized)) {\n\t\ttokens.push(normalized);\n\t}\n\n\tfor (const segment of segments) {\n\t\tif (segment.length >= 2 && !LATIN_STOP_WORDS.has(segment)) {\n\t\t\ttokens.push(segment);\n\t\t}\n\t}\n\n\treturn tokens;\n}\n\nfunction tokenize(text: string): string[] {\n\tconst parts = text.toLowerCase().match(TOKEN_PART_REGEX) ?? [];\n\tconst tokens: string[] = [];\n\tfor (const part of parts) {\n\t\tif (containsHanText(part)) {\n\t\t\ttokens.push(...tokenizeHanPart(part));\n\t\t\tcontinue;\n\t\t}\n\t\ttokens.push(...tokenizeAsciiPart(part));\n\t}\n\treturn Array.from(new Set(tokens));\n}\n\nfunction buildTokenSet(text: string): Set<string> {\n\treturn new Set(tokenize(text));\n}\n\nfunction computeTokenMatchStats(queryTokens: string[], text: string): TokenMatchStats {\n\tif (queryTokens.length === 0 || !text.trim()) {\n\t\treturn { matchedCount: 0, coverage: 0 };\n\t}\n\n\tconst haystack = buildTokenSet(text);\n\tlet matchedCount = 0;\n\tfor (const token of queryTokens) {\n\t\tif (haystack.has(token)) {\n\t\t\tmatchedCount++;\n\t\t}\n\t}\n\n\treturn {\n\t\tmatchedCount,\n\t\tcoverage: matchedCount / queryTokens.length,\n\t};\n}\n\nfunction collectMatchingQueryTokens(queryTokens: string[], texts: string[]): Set<string> {\n\tconst haystack = new Set<string>();\n\tfor (const text of texts) {\n\t\tfor (const token of tokenize(text)) {\n\t\t\thaystack.add(token);\n\t\t}\n\t}\n\n\tconst matches = new Set<string>();\n\tfor (const token of queryTokens) {\n\t\tif (haystack.has(token)) {\n\t\t\tmatches.add(token);\n\t\t}\n\t}\n\treturn matches;\n}\n\nfunction computeExactMatchBoost(query: string, candidate: MemoryCandidate): number {\n\tconst normalizedQuery = query.trim().toLowerCase();\n\tif (!normalizedQuery) {\n\t\treturn 0;\n\t}\n\n\tconst minLength = containsHanText(normalizedQuery) ? 2 : 4;\n\tif (normalizedQuery.length < minLength) {\n\t\treturn 0;\n\t}\n\n\tlet boost = 0;\n\tconst scoringFields: Array<[string, number]> = [\n\t\t[candidate.title, 12],\n\t\t[candidate.searchText ?? candidate.content, 8],\n\t\t[candidate.path, 4],\n\t];\n\tfor (const [field, value] of scoringFields) {\n\t\tif (field.toLowerCase().includes(normalizedQuery)) {\n\t\t\tboost += value;\n\t\t}\n\t}\n\treturn boost;\n}\n\nfunction computeRecencyBoost(timestamp: string | undefined): number {\n\tif (!timestamp) return 0;\n\tconst timestampMs = Date.parse(timestamp);\n\tif (!Number.isFinite(timestampMs)) return 0;\n\n\tconst ageMs = Date.now() - timestampMs;\n\tconst dayMs = 24 * 60 * 60 * 1000;\n\tif (ageMs <= dayMs) return 6;\n\tif (ageMs <= 7 * dayMs) return 4;\n\tif (ageMs <= 30 * dayMs) return 2;\n\treturn 0;\n}\n\nfunction detectQueryIntents(query: string): Set<QueryIntent> {\n\tconst intents = new Set<QueryIntent>();\n\tif (/\\b(now|current|currently|status)\\b/i.test(query) || /(现在|当前|目前|正在|状态)/u.test(query)) {\n\t\tintents.add(\"current-state\");\n\t}\n\tif (/\\b(next|follow-?up|todo|plan)\\b/i.test(query) || /(下一步|接下来|后续|怎么办|怎么做|该查什么)/u.test(query)) {\n\t\tintents.add(\"next-steps\");\n\t}\n\tif (\n\t\t/\\b(constraint|requirement|guardrail|compatible|compatibility)\\b/i.test(query) ||\n\t\t/(约束|限制|要求|兼容|注意事项)/u.test(query)\n\t) {\n\t\tintents.add(\"constraints\");\n\t}\n\tif (/\\b(decision|decided|why)\\b/i.test(query) || /(决策|决定|方案|为什么)/u.test(query)) {\n\t\tintents.add(\"decisions\");\n\t}\n\tif (/\\b(error|bug|failure|issue|regression)\\b/i.test(query) || /(错误|异常|失败|问题|缺陷|回归)/u.test(query)) {\n\t\tintents.add(\"errors\");\n\t}\n\tif (/\\b(history|previous|before|earlier|past)\\b/i.test(query) || /(历史|之前|以前|过去|早先|曾经)/u.test(query)) {\n\t\tintents.add(\"history\");\n\t}\n\treturn intents;\n}\n\nfunction computeSectionIntentBoost(intents: Set<QueryIntent>, candidate: MemoryCandidate): number {\n\tconst kind = candidate.sectionKind ?? \"\";\n\tlet boost = 0;\n\n\tif (intents.has(\"current-state\") && kind === \"current state\") boost += 10;\n\tif (intents.has(\"next-steps\") && kind === \"next steps\") boost += 10;\n\tif (intents.has(\"constraints\") && kind.includes(\"constraint\")) boost += 8;\n\tif (intents.has(\"decisions\") && kind.includes(\"decision\")) boost += 8;\n\tif (intents.has(\"errors\") && kind === \"errors & corrections\") boost += 8;\n\tif (intents.has(\"history\") && candidate.source === \"channel-history\") boost += 8;\n\treturn boost;\n}\n\nfunction compareScoredCandidates(a: ScoredCandidate, b: ScoredCandidate): number {\n\treturn (\n\t\tb.score - a.score ||\n\t\tb.lexicalMatchCount - a.lexicalMatchCount ||\n\t\tb.candidate.priority - a.candidate.priority ||\n\t\ta.candidate.title.localeCompare(b.candidate.title)\n\t);\n}\n\nfunction scoreCandidate(\n\tquery: string,\n\tqueryTokens: string[],\n\tintents: Set<QueryIntent>,\n\tcandidate: MemoryCandidate,\n): ScoredCandidate | null {\n\tconst searchText = candidate.searchText ?? candidate.content;\n\tconst titleStats = computeTokenMatchStats(queryTokens, candidate.title);\n\tconst contentStats = computeTokenMatchStats(queryTokens, searchText);\n\tconst pathStats = computeTokenMatchStats(queryTokens, candidate.path);\n\tconst matchedTokens = collectMatchingQueryTokens(queryTokens, [candidate.title, searchText, candidate.path]);\n\tconst exactBoost = computeExactMatchBoost(query, candidate);\n\tconst intentBoost = computeSectionIntentBoost(intents, candidate);\n\tconst overallCoverage = queryTokens.length > 0 ? matchedTokens.size / queryTokens.length : 0;\n\tconst lexicalScore =\n\t\toverallCoverage * 48 +\n\t\ttitleStats.coverage * 18 +\n\t\tcontentStats.coverage * 22 +\n\t\tpathStats.coverage * 8 +\n\t\texactBoost;\n\tconst structuralScore = candidate.priority + intentBoost + computeRecencyBoost(candidate.timestamp);\n\n\tif (matchedTokens.size === 0 && exactBoost === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tcandidate,\n\t\tscore: lexicalScore * (1 + structuralScore / 100),\n\t\tlexicalMatchCount: matchedTokens.size,\n\t\tintentBoost,\n\t};\n}\n\nfunction seedIntentCandidates(\n\trequest: RecallRequest,\n\tcandidates: MemoryCandidate[],\n\texisting: ScoredCandidate[],\n\tintents: Set<QueryIntent>,\n\tqueryTokens: string[],\n): ScoredCandidate[] {\n\tif (intents.size === 0) {\n\t\treturn existing;\n\t}\n\n\tconst seen = new Set(existing.map(({ candidate }) => candidate.id));\n\tconst seeded = [...existing];\n\tconst limit = Math.max(request.maxCandidates, request.maxInjected);\n\n\tconst intentCandidates = candidates\n\t\t.map((candidate) => ({\n\t\t\tcandidate,\n\t\t\tintentBoost: computeSectionIntentBoost(intents, candidate),\n\t\t}))\n\t\t.filter(({ candidate, intentBoost }) => intentBoost > 0 && !seen.has(candidate.id))\n\t\t.sort(\n\t\t\t(a, b) =>\n\t\t\t\tb.intentBoost - a.intentBoost ||\n\t\t\t\tb.candidate.priority - a.candidate.priority ||\n\t\t\t\ta.candidate.title.localeCompare(b.candidate.title),\n\t\t);\n\n\tfor (const { candidate, intentBoost } of intentCandidates) {\n\t\tconst matchedTokens = collectMatchingQueryTokens(queryTokens, [\n\t\t\tcandidate.title,\n\t\t\tcandidate.searchText ?? candidate.content,\n\t\t]);\n\t\tif (matchedTokens.size === 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tseeded.push({\n\t\t\tcandidate,\n\t\t\tscore:\n\t\t\t\t(intentBoost + matchedTokens.size * 8) *\n\t\t\t\t(1 + (candidate.priority + computeRecencyBoost(candidate.timestamp)) / 100),\n\t\t\tlexicalMatchCount: matchedTokens.size,\n\t\t\tintentBoost,\n\t\t});\n\t\tseen.add(candidate.id);\n\t\tif (seeded.length >= limit) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn seeded;\n}\n\nasync function rerankCandidates(request: RecallRequest, candidates: ScoredCandidate[]): Promise<ScoredCandidate[]> {\n\tif ((!request.rerankWithModel && !request.autoRerank) || candidates.length <= request.maxInjected) {\n\t\treturn candidates;\n\t}\n\n\tconst renderedCandidates = candidates\n\t\t.map(({ candidate, score, lexicalMatchCount, intentBoost }) => {\n\t\t\tconst clippedContent =\n\t\t\t\tcandidate.content.length > RERANK_CONTENT_CLIP\n\t\t\t\t\t? `${candidate.content.slice(0, RERANK_CONTENT_CLIP)}...`\n\t\t\t\t\t: candidate.content;\n\t\t\treturn [\n\t\t\t\t`id: ${candidate.id}`,\n\t\t\t\t`source: ${candidate.source}`,\n\t\t\t\t`title: ${candidate.title}`,\n\t\t\t\t`path: ${candidate.path}`,\n\t\t\t\t`score: ${score}`,\n\t\t\t\t`lexicalMatchCount: ${lexicalMatchCount}`,\n\t\t\t\t`intentBoost: ${intentBoost}`,\n\t\t\t\t`content: ${clippedContent}`,\n\t\t\t].join(\"\\n\");\n\t\t})\n\t\t.join(\"\\n\\n---\\n\\n\");\n\n\ttry {\n\t\tconst result = await runSidecarTask({\n\t\t\tname: \"memory-recall-rerank\",\n\t\t\tmodel: request.model,\n\t\t\tresolveApiKey: request.resolveApiKey,\n\t\t\tsystemPrompt: RERANK_SYSTEM_PROMPT,\n\t\t\tprompt: `User turn:\\n${request.query.trim()}\\n\\nCandidates:\\n${renderedCandidates}`,\n\t\t\ttimeoutMs: MEMORY_RECALL_RERANK_TIMEOUT_MS,\n\t\t\tparse: (text) => {\n\t\t\t\tconst parsed = parseJsonObject(text) as { selectedIds?: unknown };\n\t\t\t\treturn Array.isArray(parsed.selectedIds)\n\t\t\t\t\t? parsed.selectedIds.filter((id): id is string => typeof id === \"string\" && id.trim().length > 0)\n\t\t\t\t\t: [];\n\t\t\t},\n\t\t});\n\n\t\tconst selectedIds = new Set(result.output);\n\t\tif (selectedIds.size === 0) {\n\t\t\treturn candidates;\n\t\t}\n\n\t\tconst selected = candidates.filter(({ candidate }) => selectedIds.has(candidate.id));\n\t\treturn selected.length > 0 ? selected : candidates;\n\t} catch {\n\t\treturn candidates;\n\t}\n}\n\nfunction renderRecallResult(items: RecalledMemory[], maxChars: number): string {\n\tif (items.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines: string[] = [\"<runtime_context>\", \"Relevant context for this turn:\"];\n\tfor (const item of items) {\n\t\tlines.push(\"\");\n\t\tlines.push(`[${item.source}/${item.title}]`);\n\t\tlines.push(`Path: ${item.path}`);\n\t\tlines.push(item.content);\n\t}\n\tlines.push(\"</runtime_context>\");\n\n\tconst rendered = lines.join(\"\\n\");\n\tif (rendered.length <= maxChars) {\n\t\treturn rendered;\n\t}\n\n\tconst clippedLines = [\"<runtime_context>\", \"Relevant context for this turn:\"];\n\tlet usedChars = clippedLines.join(\"\\n\").length + \"</runtime_context>\".length + 2;\n\tfor (const item of items) {\n\t\tconst block = [\"\", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content].join(\"\\n\");\n\t\tif (usedChars + block.length > maxChars) {\n\t\t\tbreak;\n\t\t}\n\t\tclippedLines.push(\"\", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content);\n\t\tusedChars += block.length;\n\t}\n\tclippedLines.push(\"</runtime_context>\");\n\treturn clippedLines.join(\"\\n\");\n}\n\nexport async function recallRelevantMemory(request: RecallRequest): Promise<RecallResult> {\n\tconst query = request.query.trim();\n\tif (!query) {\n\t\treturn { items: [], renderedText: \"\" };\n\t}\n\n\tconst candidates = await buildMemoryCandidates({\n\t\tworkspaceDir: request.workspaceDir,\n\t\tchannelDir: request.channelDir,\n\t\tcache: request.candidateCache,\n\t});\n\tconst filteredCandidates = request.allowedSources?.length\n\t\t? candidates.filter((candidate) => request.allowedSources?.includes(candidate.source))\n\t\t: candidates;\n\tif (filteredCandidates.length === 0) {\n\t\treturn { items: [], renderedText: \"\" };\n\t}\n\n\tconst queryTokens = tokenize(query);\n\tconst queryIntents = detectQueryIntents(query);\n\tconst scored = filteredCandidates\n\t\t.map((candidate) => scoreCandidate(query, queryTokens, queryIntents, candidate))\n\t\t.filter((candidate): candidate is ScoredCandidate => candidate !== null)\n\t\t.sort(compareScoredCandidates);\n\n\tconst shortlist = seedIntentCandidates(request, filteredCandidates, scored, queryIntents, queryTokens)\n\t\t.sort(compareScoredCandidates)\n\t\t.slice(0, Math.max(request.maxCandidates, request.maxInjected));\n\n\tif (shortlist.length === 0) {\n\t\treturn { items: [], renderedText: \"\" };\n\t}\n\n\tconst reranked = await rerankCandidates(request, shortlist);\n\tconst items = reranked.slice(0, request.maxInjected).map(({ candidate, score }) => ({\n\t\tsource: candidate.source,\n\t\tpath: candidate.path,\n\t\ttitle: candidate.title,\n\t\tcontent: candidate.content,\n\t\tscore,\n\t}));\n\n\treturn {\n\t\titems,\n\t\trenderedText: renderRecallResult(items, request.maxChars),\n\t};\n}\n"]}
|
|
@@ -16,7 +16,8 @@ export interface SessionMemoryUpdateOptions {
|
|
|
16
16
|
messages: AgentMessage[];
|
|
17
17
|
model: Model<Api>;
|
|
18
18
|
resolveApiKey: (model: Model<Api>) => Promise<string>;
|
|
19
|
+
timeoutMs?: number;
|
|
19
20
|
}
|
|
20
21
|
export declare function renderSessionMemory(state: SessionMemoryState): string;
|
|
21
22
|
export declare function updateChannelSessionMemory(options: SessionMemoryUpdateOptions): Promise<SessionMemoryState>;
|
|
22
|
-
//# sourceMappingURL=session
|
|
23
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/memory/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAW,KAAK,EAAE,MAAM,qBAAqB,CAAC;AA4C/D,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AA8JD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAyBrE;AAcD,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA4BjH"}
|