osborn 0.9.228 → 0.9.229
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/pipeline-fastbrain.js +73 -56
- package/dist/session-store.d.ts +6 -0
- package/dist/session-store.js +24 -0
- package/package.json +1 -1
|
@@ -147,43 +147,56 @@ async function executeTool(name, args, sessionId, workingDir, agentControl) {
|
|
|
147
147
|
// SEARCH HELPERS
|
|
148
148
|
// ============================================================
|
|
149
149
|
async function executeSearch(phrases, sessionId, workingDir) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
seenLines.add(key);
|
|
164
|
-
return true;
|
|
165
|
-
});
|
|
166
|
-
if (newResults.length > 0) {
|
|
167
|
-
sections.push(`["${phrase}" — ${newResults.length} matches]`);
|
|
168
|
-
for (const r of newResults) {
|
|
169
|
-
const parts = r.content.split('|');
|
|
170
|
-
if (parts.length >= 6) {
|
|
171
|
-
matchedRefs.push({ lineNum: parseInt(parts[0], 10), byteOffset: parseInt(parts[1], 10), source: parts[3] });
|
|
172
|
-
sections.push(r.content);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
150
|
+
// DB-FIRST: query the recall store (full untruncated text + hybrid keyword+vector).
|
|
151
|
+
// Vector/hybrid is PREFERRED; recall() degrades to keyword INSIDE when the vec table is
|
|
152
|
+
// empty or no embedder is available. This replaces the old ripgrep-over-search-index.txt
|
|
153
|
+
// two-step (grep summary → read JSONL by offset) with one call that already returns full text.
|
|
154
|
+
const { storeExists, openStore, recall } = await import('./session-store.js');
|
|
155
|
+
const dbPath = storeExists(sessionId, workingDir);
|
|
156
|
+
if (dbPath) {
|
|
157
|
+
let embed = undefined;
|
|
158
|
+
try {
|
|
159
|
+
if (process.env.OSBORN_EMBED !== '0') {
|
|
160
|
+
const { getEmbedder } = await import('./embedder.js');
|
|
161
|
+
// Tight timeout so a slow/cold embed never blows the fast-brain deadline → keyword.
|
|
162
|
+
embed = (await getEmbedder(2500)) ?? undefined;
|
|
175
163
|
}
|
|
176
164
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
165
|
+
catch { }
|
|
166
|
+
// Best-effort: a recall/decompress throw must degrade to the ripgrep fallback below,
|
|
167
|
+
// never collapse the whole fast-brain turn (mirrors buildRecallInjection's swallow).
|
|
168
|
+
let dbResult = null;
|
|
169
|
+
const db = openStore(dbPath, { readonly: true });
|
|
170
|
+
try {
|
|
171
|
+
const query = phrases.slice(0, 6).join(' ');
|
|
172
|
+
const hits = await recall(db, query, { mode: embed ? 'hybrid' : 'keyword', topK: 12, embed });
|
|
173
|
+
if (!hits.length) {
|
|
174
|
+
dbResult = `No matches for: ${phrases.join(', ')}`;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const sections = [];
|
|
178
|
+
for (const h of hits) {
|
|
179
|
+
const src = `${h.source} L${h.lineNum} · ${h.msgType}${h.toolName ? `:${h.toolName}` : ''} · ${h.matchedBy}`;
|
|
180
|
+
let body = h.text.replace(/\n{3,}/g, '\n\n').trim();
|
|
181
|
+
if (body.length > 2000)
|
|
182
|
+
body = body.slice(0, 2000) + ' …';
|
|
183
|
+
sections.push(`[${src}]${h.ts ? ' ' + h.ts : ''}\n${body}`);
|
|
184
|
+
}
|
|
185
|
+
dbResult = sections.join('\n\n');
|
|
182
186
|
}
|
|
183
|
-
catch { }
|
|
184
187
|
}
|
|
185
|
-
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.warn('[fast-brain] recall DB search failed, falling back to raw JSONL:', err?.message || err);
|
|
190
|
+
// dbResult stays null → fall through to the ripgrep fallback below
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
db.close();
|
|
194
|
+
}
|
|
195
|
+
if (dbResult !== null)
|
|
196
|
+
return dbResult;
|
|
186
197
|
}
|
|
198
|
+
// FALLBACK: store not built yet (brand-new session before the first sweep) → raw-JSONL ripgrep.
|
|
199
|
+
const { ripgrepSearch } = await import('./jsonl-search.js');
|
|
187
200
|
const { getSessionPaths } = await import('./session-access.js');
|
|
188
201
|
const paths = getSessionPaths(sessionId, workingDir);
|
|
189
202
|
if (!paths.exists)
|
|
@@ -199,31 +212,33 @@ async function executeSearch(phrases, sessionId, workingDir) {
|
|
|
199
212
|
return sections.length > 0 ? sections.join('\n') : `No matches for: ${phrases.join(', ')}`;
|
|
200
213
|
}
|
|
201
214
|
async function getRecentEntries(sessionId, workingDir, _, count) {
|
|
202
|
-
|
|
203
|
-
const {
|
|
204
|
-
const
|
|
205
|
-
if (!
|
|
215
|
+
// Newest content rows straight from the recall DB (append-only, so highest id = latest).
|
|
216
|
+
const { storeExists, openStore, recentRows } = await import('./session-store.js');
|
|
217
|
+
const dbPath = storeExists(sessionId, workingDir);
|
|
218
|
+
if (!dbPath)
|
|
206
219
|
return 'Index not built yet.';
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
const db = openStore(dbPath, { readonly: true });
|
|
221
|
+
try {
|
|
222
|
+
const rows = recentRows(db, count); // newest first
|
|
223
|
+
if (!rows.length)
|
|
224
|
+
return 'No entries yet.';
|
|
225
|
+
const out = [`[RECENT — last ${rows.length} entries, newest first]`];
|
|
226
|
+
for (const h of rows) {
|
|
227
|
+
const src = `${h.source} L${h.lineNum} · ${h.msgType}${h.toolName ? `:${h.toolName}` : ''}`;
|
|
228
|
+
let body = h.text.replace(/\n{3,}/g, '\n\n').trim();
|
|
229
|
+
if (body.length > 1500)
|
|
230
|
+
body = body.slice(0, 1500) + ' …';
|
|
231
|
+
out.push(`[${src}]${h.ts ? ' ' + h.ts : ''}\n${body}`);
|
|
216
232
|
}
|
|
233
|
+
return out.join('\n\n');
|
|
217
234
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
catch { }
|
|
235
|
+
catch (err) {
|
|
236
|
+
console.warn('[fast-brain] recent-entries read failed:', err?.message || err);
|
|
237
|
+
return 'Could not read recent entries.';
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
db.close();
|
|
225
241
|
}
|
|
226
|
-
return summaries.join('\n');
|
|
227
242
|
}
|
|
228
243
|
// ============================================================
|
|
229
244
|
// SYSTEM PROMPT
|
|
@@ -249,9 +264,11 @@ function buildSystemPrompt(chatHistory, researchContext) {
|
|
|
249
264
|
`or use vague references ("that thing", "the error"). Interpret intent, not just words.`,
|
|
250
265
|
``,
|
|
251
266
|
`== RESULTS FORMAT ==`,
|
|
252
|
-
`Each line
|
|
253
|
-
` source
|
|
254
|
-
`
|
|
267
|
+
`Each hit is a header line then its FULL untruncated text:`,
|
|
268
|
+
` [<source> L<lineNum> · <msgType>[:<tool>] · <matchedBy>] <timestamp>`,
|
|
269
|
+
` <full text of that message/tool result>`,
|
|
270
|
+
` source: "main" = conversation, "agent-XXXX" = sub-agent research.`,
|
|
271
|
+
` matchedBy: keyword | vector | both (how recall found it — vector/hybrid is preferred).`,
|
|
255
272
|
``,
|
|
256
273
|
`== HOW TO SEARCH ==`,
|
|
257
274
|
`Think about what words people ACTUALLY USED when this topic came up.`,
|
package/dist/session-store.d.ts
CHANGED
|
@@ -88,3 +88,9 @@ export interface RecallOpts {
|
|
|
88
88
|
* Falls back to keyword automatically when no embeddings/embedder are available.
|
|
89
89
|
*/
|
|
90
90
|
export declare function recall(db: Database.Database, query: string, opts?: RecallOpts): Promise<RecallHit[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Return the most recent N content rows (highest id = latest, append-only order),
|
|
93
|
+
* newest first, as RecallHit-shaped objects. Powers fast-brain's "recent entries"
|
|
94
|
+
* path. Encapsulates decompress so callers never touch the blob/compression.
|
|
95
|
+
*/
|
|
96
|
+
export declare function recentRows(db: Database.Database, count: number): RecallHit[];
|
package/dist/session-store.js
CHANGED
|
@@ -571,3 +571,27 @@ export async function recall(db, query, opts) {
|
|
|
571
571
|
}
|
|
572
572
|
return hits;
|
|
573
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* Return the most recent N content rows (highest id = latest, append-only order),
|
|
576
|
+
* newest first, as RecallHit-shaped objects. Powers fast-brain's "recent entries"
|
|
577
|
+
* path. Encapsulates decompress so callers never touch the blob/compression.
|
|
578
|
+
*/
|
|
579
|
+
export function recentRows(db, count) {
|
|
580
|
+
const rows = db.prepare(`SELECT id, source, line_num, byte_offset, ts, msg_type, model, git_branch, cwd, tool_name, blob
|
|
581
|
+
FROM content ORDER BY id DESC LIMIT ?`).all(Math.max(1, count));
|
|
582
|
+
return rows.map((row) => ({
|
|
583
|
+
id: row.id,
|
|
584
|
+
source: row.source,
|
|
585
|
+
lineNum: row.line_num,
|
|
586
|
+
byteOffset: row.byte_offset,
|
|
587
|
+
ts: row.ts,
|
|
588
|
+
msgType: row.msg_type,
|
|
589
|
+
model: row.model,
|
|
590
|
+
gitBranch: row.git_branch,
|
|
591
|
+
cwd: row.cwd,
|
|
592
|
+
toolName: row.tool_name,
|
|
593
|
+
text: decompress(row.blob),
|
|
594
|
+
score: 0,
|
|
595
|
+
matchedBy: 'keyword',
|
|
596
|
+
}));
|
|
597
|
+
}
|