osborn 0.9.229 → 0.9.230
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/claude-llm.js +4 -4
- package/dist/index.js +15 -49
- package/dist/pipeline-direct-llm.d.ts +0 -2
- package/dist/pipeline-direct-llm.js +27 -51
- package/dist/session-store.d.ts +3 -1
- package/dist/session-store.js +8 -2
- package/package.json +1 -1
package/dist/claude-llm.js
CHANGED
|
@@ -358,7 +358,7 @@ export const NAMED_AGENTS = {
|
|
|
358
358
|
'finding patterns, reading multiple files, searching for examples.',
|
|
359
359
|
'Returns structured findings — does NOT make decisions or edit files.',
|
|
360
360
|
'Use this for ANY task that needs more than 2 tool calls to gather information.',
|
|
361
|
-
'GROUNDED: reads the session
|
|
361
|
+
'GROUNDED: reads the session recall store (osborn-recall) for prior findings before researching.',
|
|
362
362
|
].join(' '),
|
|
363
363
|
tools: ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'Task'],
|
|
364
364
|
grounded: true, // applyGrounding() injects the osborn-recall command + ensures Bash
|
|
@@ -419,7 +419,7 @@ export const NAMED_AGENTS = {
|
|
|
419
419
|
'Think hard about complex problems. Consider multiple approaches. Identify risks and edge cases.',
|
|
420
420
|
'',
|
|
421
421
|
'## Session context',
|
|
422
|
-
'
|
|
422
|
+
'You are deliberately NOT grounded (no raw session recall) — that keeps your analysis fresh and unbiased. Work from the STRUCTURED SUMMARIES the orchestrator hands you in your brief; do not attempt to pull raw session history yourself. If you need more context, ask the orchestrator for a structured summary rather than raw dumps.',
|
|
423
423
|
'',
|
|
424
424
|
'## How to work',
|
|
425
425
|
'1. Read and understand the full context before forming an opinion.',
|
|
@@ -461,7 +461,7 @@ export const NAMED_AGENTS = {
|
|
|
461
461
|
'Handles ALL file operations: code, config, docs, scripts, data files.',
|
|
462
462
|
'VERIFY-FIRST workflow: checks assumptions before making changes, runs tests after.',
|
|
463
463
|
'If anything is unclear, asks the main agent for clarification before touching files.',
|
|
464
|
-
'GROUNDED: reads the session
|
|
464
|
+
'GROUNDED: reads the session recall store (osborn-recall) before editing to avoid contradicting prior decisions.',
|
|
465
465
|
].join(' '),
|
|
466
466
|
tools: ['Read', 'Write', 'Edit', 'MultiEdit', 'Bash', 'Glob', 'Grep', 'NotebookRead', 'NotebookEdit'],
|
|
467
467
|
grounded: true, // applyGrounding() injects the osborn-recall command
|
|
@@ -612,7 +612,7 @@ export const NAMED_AGENTS = {
|
|
|
612
612
|
'ordered sequence of atomic writer-safe steps. Returns a self-contained brief the writer',
|
|
613
613
|
'can execute without further clarification. Slow but thorough — only use for genuinely',
|
|
614
614
|
'complex multi-file changes or when the approach is uncertain.',
|
|
615
|
-
'GROUNDED: reads the session
|
|
615
|
+
'GROUNDED: reads the session recall store (osborn-recall) before planning to respect prior decisions.',
|
|
616
616
|
].join(' '),
|
|
617
617
|
tools: ['Read', 'Glob', 'Grep', 'WebSearch'],
|
|
618
618
|
grounded: true, // applyGrounding() injects the osborn-recall command + adds Bash
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,6 @@ import { loadConfig, getMcpServers, getEnabledMcpServerNames, getVoiceMode, getD
|
|
|
37
37
|
import { createSTT, createTTS } from './voice-io.js';
|
|
38
38
|
import { createClaudeLLM, NAMED_AGENTS, applyTurbo } from './claude-llm.js';
|
|
39
39
|
import { clearPipelineFastBrainSession, prewarmBM25Index } from './pipeline-fastbrain.js';
|
|
40
|
-
import { getIndexPath, buildSummaryIndex } from './summary-index.js';
|
|
41
40
|
import { ensureClaudeAuth, applyAuthFallback } from './claude-auth.js';
|
|
42
41
|
import { createSmitheryProxy, destroySmitheryProxy, parseSmitheryUrl, isSmitheryUrl, SmitheryAuthorizationError } from './smithery-proxy.js';
|
|
43
42
|
import { DIRECT_MODE_PROMPT } from './prompts.js';
|
|
@@ -1823,47 +1822,29 @@ function startApiServer(workingDir, port) {
|
|
|
1823
1822
|
* OpenAI handles full history (30 exchanges, 2000 char content).
|
|
1824
1823
|
*/
|
|
1825
1824
|
/**
|
|
1826
|
-
*
|
|
1825
|
+
* Note to the voice agent's ChatContext on resume that prior conversation
|
|
1826
|
+
* history is searchable via the recall store.
|
|
1827
1827
|
*
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
* Only exception: first-ever resume of a session that was never indexed (e.g. a
|
|
1834
|
-
* session started before the index watcher was added). In that case we build once.
|
|
1828
|
+
* Native --resume already replays the session into the SDK, so this is not a
|
|
1829
|
+
* context-injection path — it just reminds the agent that the FULL prior history
|
|
1830
|
+
* (this + previous sessions) is queryable through the `osborn-recall` command,
|
|
1831
|
+
* which reads the per-session recall store (session.db). We no longer inject the
|
|
1832
|
+
* old flat search-index.txt path or its `lineNum|…` line format (retired).
|
|
1835
1833
|
*/
|
|
1836
|
-
function injectSessionIndexIntoChatCtx(agent, sessionId,
|
|
1834
|
+
function injectSessionIndexIntoChatCtx(agent, sessionId, _workingDir) {
|
|
1837
1835
|
if (!agent || !sessionId)
|
|
1838
1836
|
return;
|
|
1839
1837
|
try {
|
|
1840
|
-
let indexPath = getIndexPath(sessionId, workingDir);
|
|
1841
|
-
if (!indexPath) {
|
|
1842
|
-
// First-ever resume with no index — build once, then point at it
|
|
1843
|
-
console.log(`🗂️ No index for ${sessionId.slice(0, 8)} — building (one-time)...`);
|
|
1844
|
-
try {
|
|
1845
|
-
const state = buildSummaryIndex(sessionId, workingDir);
|
|
1846
|
-
indexPath = (state.indexPath && existsSync(state.indexPath) && statSync(state.indexPath).size > 0)
|
|
1847
|
-
? state.indexPath : null;
|
|
1848
|
-
}
|
|
1849
|
-
catch {
|
|
1850
|
-
return;
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
if (!indexPath)
|
|
1854
|
-
return;
|
|
1855
|
-
// Pass the path — don't read the file. Native --resume already loaded the session;
|
|
1856
|
-
// this is just a reference the agent can use to search history if asked.
|
|
1857
1838
|
const chatCtx = agent.chatCtx.copy();
|
|
1858
1839
|
chatCtx.addMessage({
|
|
1859
1840
|
role: 'user',
|
|
1860
|
-
content: `[Session resume]
|
|
1841
|
+
content: `[Session resume] Prior conversation history is searchable via the \`osborn-recall\` command (hybrid keyword + semantic recall over the session store). Use it if the user asks about earlier work.`,
|
|
1861
1842
|
});
|
|
1862
1843
|
agent.updateChatCtx(chatCtx);
|
|
1863
|
-
console.log(`🗂️ Session
|
|
1844
|
+
console.log(`🗂️ Session recall note injected for ${sessionId.slice(0, 8)}`);
|
|
1864
1845
|
}
|
|
1865
1846
|
catch (err) {
|
|
1866
|
-
console.log('⚠️ Failed to inject session
|
|
1847
|
+
console.log('⚠️ Failed to inject session recall note:', err);
|
|
1867
1848
|
}
|
|
1868
1849
|
}
|
|
1869
1850
|
// Main function
|
|
@@ -2172,21 +2153,10 @@ async function main() {
|
|
|
2172
2153
|
return;
|
|
2173
2154
|
try {
|
|
2174
2155
|
const llm = currentLLM;
|
|
2175
|
-
//
|
|
2176
|
-
//
|
|
2177
|
-
//
|
|
2178
|
-
//
|
|
2179
|
-
// three cleanup sites (Disconnected, previous-session-cleanup,
|
|
2180
|
-
// ParticipantDisconnected) call, but it previously only aborted the
|
|
2181
|
-
// SDK subprocess — leaving the interval (and the whole graph) alive and
|
|
2182
|
-
// uncollectable on every disconnect/reconnect. A reconnect-heavy session
|
|
2183
|
-
// (e.g. 15 reconnects from a frontend redeploy) leaked 15 timers + 15
|
|
2184
|
-
// retained graphs, each re-reading JSONL every 10s, until the node heap
|
|
2185
|
-
// OOM'd (~980MB) and the process crashed. Stopping the watcher here lets
|
|
2186
|
-
// the abandoned graph be GC'd. Duck-typed: only PipelineDirectLLM has it.
|
|
2187
|
-
if (typeof llm.stopIndexWatcher === 'function') {
|
|
2188
|
-
llm.stopIndexWatcher();
|
|
2189
|
-
}
|
|
2156
|
+
// NOTE (fast-brain migration): the old summary-index 10s setInterval watcher
|
|
2157
|
+
// was retired — fast-brain now reads the recall store (session.db) directly,
|
|
2158
|
+
// so there is no per-LLM timer holding the PipelineDirectLLM → ClaudeLLM graph
|
|
2159
|
+
// alive anymore. We only abort the SDK subprocess here.
|
|
2190
2160
|
if (typeof llm.abortQuery === 'function') {
|
|
2191
2161
|
llm.abortQuery();
|
|
2192
2162
|
}
|
|
@@ -4576,10 +4546,6 @@ async function main() {
|
|
|
4576
4546
|
try {
|
|
4577
4547
|
// Reuse existing session ID so Claude SDK resumes where it left off
|
|
4578
4548
|
const recoverySessionId = currentLLM?.sessionId || resumeSessionId;
|
|
4579
|
-
// Stop old index watcher if it exists
|
|
4580
|
-
if (currentLLM && 'stopIndexWatcher' in currentLLM) {
|
|
4581
|
-
currentLLM.stopIndexWatcher();
|
|
4582
|
-
}
|
|
4583
4549
|
let result;
|
|
4584
4550
|
{
|
|
4585
4551
|
// Pipeline mode: recreate PipelineDirectLLM wrapper with fast brain
|
|
@@ -46,8 +46,6 @@ export declare class PipelineDirectLLM extends llm.LLM {
|
|
|
46
46
|
#private;
|
|
47
47
|
suppressMeetingTTS: boolean;
|
|
48
48
|
constructor(opts: PipelineDirectOptions);
|
|
49
|
-
/** Stop the index watcher (call on disconnect/session switch) */
|
|
50
|
-
stopIndexWatcher(): void;
|
|
51
49
|
get events(): EventEmitter;
|
|
52
50
|
get sessionId(): string | null;
|
|
53
51
|
get model(): string;
|
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
* Phase 2 (future): Gemini speaks first, Claude suppressed when Gemini has HIGH confidence
|
|
10
10
|
*/
|
|
11
11
|
import { llm, DEFAULT_API_CONNECT_OPTIONS } from '@livekit/agents';
|
|
12
|
-
import { readFileSync } from 'fs';
|
|
13
12
|
import { ClaudeLLM } from './claude-llm.js';
|
|
14
13
|
import { askPipelineFastBrain } from './pipeline-fastbrain.js';
|
|
15
|
-
import {
|
|
14
|
+
import { storeExists, openStore, recentRows } from './session-store.js';
|
|
16
15
|
// ============================================================
|
|
17
16
|
// SESSION TAIL — compact recent-conversation block prepended to every user turn
|
|
18
17
|
// so the orchestrator never loses the thread across context compaction.
|
|
@@ -27,31 +26,33 @@ function buildSessionTail(sessionId, workingDir) {
|
|
|
27
26
|
return '';
|
|
28
27
|
const maxLines = parseInt(process.env.OSBORN_SESSION_TAIL_COUNT || '3000', 10);
|
|
29
28
|
try {
|
|
30
|
-
const
|
|
31
|
-
if (!
|
|
32
|
-
return '';
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
convLines.
|
|
29
|
+
const dbPath = storeExists(sessionId, workingDir);
|
|
30
|
+
if (!dbPath)
|
|
31
|
+
return ''; // no store → prior empty-index behavior
|
|
32
|
+
const db = openStore(dbPath, { readonly: true });
|
|
33
|
+
try {
|
|
34
|
+
// Pull the most recent user+assistant rows (filtered in SQL, so we only
|
|
35
|
+
// decompress rows we keep), newest-first, then reverse to chronological.
|
|
36
|
+
const rows = recentRows(db, maxLines, ['user', 'assistant']);
|
|
37
|
+
const convLines = [];
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
if (row.msgType !== 'user' && row.msgType !== 'assistant')
|
|
40
|
+
continue;
|
|
41
|
+
const timestamp = (row.ts || '').substring(0, 19);
|
|
42
|
+
const speaker = row.msgType === 'user' ? 'User' : 'Assistant';
|
|
43
|
+
const summary = row.text.replace(/[\n\r\t]+/g, ' ').replace(/\s+/g, ' ').trim().substring(0, 150);
|
|
44
|
+
convLines.push(`${timestamp} ${speaker}: ${summary}`);
|
|
45
|
+
}
|
|
46
|
+
// recentRows is newest-first — reverse to chronological, then keep the tail.
|
|
47
|
+
convLines.reverse();
|
|
48
|
+
const kept = convLines.slice(-maxLines);
|
|
49
|
+
if (kept.length === 0)
|
|
50
|
+
return '';
|
|
51
|
+
return ['<session_tail>', ...kept, '</session_tail>'].join('\n');
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
db.close();
|
|
50
55
|
}
|
|
51
|
-
const kept = convLines.slice(-maxLines);
|
|
52
|
-
if (kept.length === 0)
|
|
53
|
-
return '';
|
|
54
|
-
return ['<session_tail>', ...kept, '</session_tail>'].join('\n');
|
|
55
56
|
}
|
|
56
57
|
catch {
|
|
57
58
|
return '';
|
|
@@ -61,8 +62,6 @@ export class PipelineDirectLLM extends llm.LLM {
|
|
|
61
62
|
#claudeLLM;
|
|
62
63
|
#opts;
|
|
63
64
|
#turnAbort = null;
|
|
64
|
-
#indexWatcher = null;
|
|
65
|
-
#indexBuilding = false;
|
|
66
65
|
// Set to true when PostCompact fires — cleared after injecting the tail once
|
|
67
66
|
// on the next real user turn (seam-only injection, never per-turn).
|
|
68
67
|
#postCompactionTailPending = false;
|
|
@@ -88,13 +87,6 @@ export class PipelineDirectLLM extends llm.LLM {
|
|
|
88
87
|
this.#claudeLLM = new ClaudeLLM(chainedOpts);
|
|
89
88
|
this.#opts = chainedOpts;
|
|
90
89
|
}
|
|
91
|
-
/** Stop the index watcher (call on disconnect/session switch) */
|
|
92
|
-
stopIndexWatcher() {
|
|
93
|
-
if (this.#indexWatcher) {
|
|
94
|
-
this.#indexWatcher.stop();
|
|
95
|
-
this.#indexWatcher = null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
90
|
// Proxy all properties
|
|
99
91
|
get events() { return this.#claudeLLM.events; }
|
|
100
92
|
get sessionId() { return this.#claudeLLM.sessionId; }
|
|
@@ -105,8 +97,6 @@ export class PipelineDirectLLM extends llm.LLM {
|
|
|
105
97
|
setResumeSessionId(id) { this.#claudeLLM.setResumeSessionId(id); }
|
|
106
98
|
setContinueSession(e) { this.#claudeLLM.setContinueSession(e); }
|
|
107
99
|
resetForSessionSwitch() {
|
|
108
|
-
this.stopIndexWatcher();
|
|
109
|
-
this.#indexBuilding = false;
|
|
110
100
|
this.#claudeLLM.resetForSessionSwitch();
|
|
111
101
|
}
|
|
112
102
|
respondToPermission(allow, msg) { this.#claudeLLM.respondToPermission(allow, msg); }
|
|
@@ -326,20 +316,6 @@ export class PipelineDirectLLM extends llm.LLM {
|
|
|
326
316
|
}
|
|
327
317
|
const workingDir = this.#opts.workingDirectory || process.cwd();
|
|
328
318
|
const sessionBaseDir = this.#opts.sessionBaseDir || workingDir;
|
|
329
|
-
// Build summary index on first question (async, non-blocking for subsequent questions)
|
|
330
|
-
if (!this.#indexWatcher && !this.#indexBuilding && sessionId !== 'pending') {
|
|
331
|
-
this.#indexBuilding = true;
|
|
332
|
-
try {
|
|
333
|
-
const startBuild = Date.now();
|
|
334
|
-
const state = buildSummaryIndex(sessionId, workingDir, undefined, (msg) => console.log(`🔍 [index] ${msg}`));
|
|
335
|
-
this.#indexWatcher = startIndexWatcher(sessionId, workingDir, undefined, state);
|
|
336
|
-
console.log(`🔍 [index] Built + watching in ${Date.now() - startBuild}ms`);
|
|
337
|
-
}
|
|
338
|
-
catch (err) {
|
|
339
|
-
console.error('🔍 [index] Build failed:', err?.message);
|
|
340
|
-
}
|
|
341
|
-
this.#indexBuilding = false;
|
|
342
|
-
}
|
|
343
319
|
// NOTE: the embedded session.db write-through was CONSOLIDATED into the main agent's
|
|
344
320
|
// UserPromptSubmit hook (claude-llm.ts) — one canonical trigger per real user submission,
|
|
345
321
|
// main-thread only, sweeping main + all sub-agent JSONLs. Kept out of the per-turn pipeline
|
package/dist/session-store.d.ts
CHANGED
|
@@ -92,5 +92,7 @@ export declare function recall(db: Database.Database, query: string, opts?: Reca
|
|
|
92
92
|
* Return the most recent N content rows (highest id = latest, append-only order),
|
|
93
93
|
* newest first, as RecallHit-shaped objects. Powers fast-brain's "recent entries"
|
|
94
94
|
* path. Encapsulates decompress so callers never touch the blob/compression.
|
|
95
|
+
* `msgTypes` optionally filters by msg_type IN SQL — so callers that only want
|
|
96
|
+
* user/assistant rows don't decompress rows they'd discard.
|
|
95
97
|
*/
|
|
96
|
-
export declare function recentRows(db: Database.Database, count: number): RecallHit[];
|
|
98
|
+
export declare function recentRows(db: Database.Database, count: number, msgTypes?: string[]): RecallHit[];
|
package/dist/session-store.js
CHANGED
|
@@ -575,10 +575,16 @@ export async function recall(db, query, opts) {
|
|
|
575
575
|
* Return the most recent N content rows (highest id = latest, append-only order),
|
|
576
576
|
* newest first, as RecallHit-shaped objects. Powers fast-brain's "recent entries"
|
|
577
577
|
* path. Encapsulates decompress so callers never touch the blob/compression.
|
|
578
|
+
* `msgTypes` optionally filters by msg_type IN SQL — so callers that only want
|
|
579
|
+
* user/assistant rows don't decompress rows they'd discard.
|
|
578
580
|
*/
|
|
579
|
-
export function recentRows(db, count) {
|
|
581
|
+
export function recentRows(db, count, msgTypes) {
|
|
582
|
+
const filter = msgTypes && msgTypes.length
|
|
583
|
+
? `WHERE msg_type IN (${msgTypes.map(() => '?').join(', ')})`
|
|
584
|
+
: '';
|
|
585
|
+
const params = msgTypes && msgTypes.length ? [...msgTypes, Math.max(1, count)] : [Math.max(1, count)];
|
|
580
586
|
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(
|
|
587
|
+
FROM content ${filter} ORDER BY id DESC LIMIT ?`).all(...params);
|
|
582
588
|
return rows.map((row) => ({
|
|
583
589
|
id: row.id,
|
|
584
590
|
source: row.source,
|