osborn 0.5.2 → 0.5.5
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/.claude/settings.local.json +9 -0
- package/.claude/skills/markdown-to-pdf/SKILL.md +29 -0
- package/.claude/skills/pdf-to-markdown/SKILL.md +28 -0
- package/.claude/skills/playwright-browser/SKILL.md +75 -0
- package/.claude/skills/youtube-transcript/SKILL.md +24 -0
- package/dist/claude-llm.d.ts +29 -1
- package/dist/claude-llm.js +346 -79
- package/dist/config.d.ts +6 -2
- package/dist/config.js +6 -1
- package/dist/fast-brain.d.ts +124 -12
- package/dist/fast-brain.js +1361 -96
- package/dist/index-3-2-26-legacy.d.ts +1 -0
- package/dist/index-3-2-26-legacy.js +2233 -0
- package/dist/index.js +889 -394
- package/dist/jsonl-search.d.ts +66 -0
- package/dist/jsonl-search.js +274 -0
- package/dist/leagcyprompts2.d.ts +0 -0
- package/dist/leagcyprompts2.js +573 -0
- package/dist/pipeline-direct-llm.d.ts +77 -0
- package/dist/pipeline-direct-llm.js +216 -0
- package/dist/pipeline-fastbrain.d.ts +45 -0
- package/dist/pipeline-fastbrain.js +367 -0
- package/dist/prompts-2-25-26.d.ts +0 -0
- package/dist/prompts-2-25-26.js +518 -0
- package/dist/prompts-3-2-26.d.ts +78 -0
- package/dist/prompts-3-2-26.js +1319 -0
- package/dist/prompts.d.ts +83 -8
- package/dist/prompts.js +1990 -374
- package/dist/session-access.d.ts +60 -2
- package/dist/session-access.js +172 -2
- package/dist/summary-index.d.ts +87 -0
- package/dist/summary-index.js +570 -0
- package/dist/turn-detector-shim.d.ts +24 -0
- package/dist/turn-detector-shim.js +83 -0
- package/dist/voice-io.d.ts +9 -3
- package/dist/voice-io.js +39 -20
- package/package.json +18 -11
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Direct LLM — Wraps ClaudeLLM with parallel Gemini fast brain
|
|
3
|
+
*
|
|
4
|
+
* In pipeline mode, every user message fires two tracks simultaneously:
|
|
5
|
+
* Track A: Claude SDK (unchanged) — speaks the answer via TTS
|
|
6
|
+
* Track B: Gemini fast brain (new) — searches JSONL memory, sends result to UI only
|
|
7
|
+
*
|
|
8
|
+
* Phase 1 (current): Gemini is silent — results go to frontend panel for monitoring
|
|
9
|
+
* Phase 2 (future): Gemini speaks first, Claude suppressed when Gemini has HIGH confidence
|
|
10
|
+
*/
|
|
11
|
+
import { llm, type APIConnectOptions } from '@livekit/agents';
|
|
12
|
+
import { type ClaudeLLMOptions } from './claude-llm.js';
|
|
13
|
+
import { EventEmitter } from 'events';
|
|
14
|
+
export interface InterruptionContext {
|
|
15
|
+
spokenText: string;
|
|
16
|
+
recentMessages: string;
|
|
17
|
+
}
|
|
18
|
+
export interface PipelineDirectOptions extends ClaudeLLMOptions {
|
|
19
|
+
onFastBrainResult?: (result: FastBrainPanelResult) => void;
|
|
20
|
+
getChatHistory?: () => {
|
|
21
|
+
role: string;
|
|
22
|
+
content: string;
|
|
23
|
+
}[];
|
|
24
|
+
getResearchContext?: () => string | undefined;
|
|
25
|
+
/** Returns pending interruption context and clears it (consumed once). null = no pending interruption. */
|
|
26
|
+
getAndConsumeInterruptionContext?: () => InterruptionContext | null;
|
|
27
|
+
}
|
|
28
|
+
export interface FastBrainPanelResult {
|
|
29
|
+
question: string;
|
|
30
|
+
answer: string;
|
|
31
|
+
type: string;
|
|
32
|
+
elapsedMs: number;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
toolsUsed: string[];
|
|
35
|
+
}
|
|
36
|
+
export declare class PipelineDirectLLM extends llm.LLM {
|
|
37
|
+
#private;
|
|
38
|
+
constructor(opts: PipelineDirectOptions);
|
|
39
|
+
/** Stop the index watcher (call on disconnect/session switch) */
|
|
40
|
+
stopIndexWatcher(): void;
|
|
41
|
+
get events(): EventEmitter;
|
|
42
|
+
get sessionId(): string | null;
|
|
43
|
+
get model(): string;
|
|
44
|
+
get isResumingSession(): boolean;
|
|
45
|
+
label(): string;
|
|
46
|
+
setResumeSessionId(id: string | null): void;
|
|
47
|
+
setContinueSession(e: boolean): void;
|
|
48
|
+
resetForSessionSwitch(): void;
|
|
49
|
+
respondToPermission(allow: boolean, msg?: string): void;
|
|
50
|
+
hasPendingPermission(): boolean;
|
|
51
|
+
getPendingPermission(): {
|
|
52
|
+
toolName: string;
|
|
53
|
+
input: any;
|
|
54
|
+
};
|
|
55
|
+
getMcpServers(): Record<string, import("@anthropic-ai/claude-agent-sdk").McpServerConfig>;
|
|
56
|
+
setMcpServers(s: any): void;
|
|
57
|
+
interruptAgent(): Promise<boolean>;
|
|
58
|
+
abortAgent(): void;
|
|
59
|
+
rewindAgent(checkpointId?: string): Promise<boolean>;
|
|
60
|
+
hasActiveAgent(): boolean;
|
|
61
|
+
/** Send a new prompt to Claude via direct chat() — event listeners stay attached */
|
|
62
|
+
sendPrompt(prompt: string): void;
|
|
63
|
+
enableMcpServer(k: string, c: any): void;
|
|
64
|
+
disableMcpServer(k: string): void;
|
|
65
|
+
getLatestCheckpoint(): string;
|
|
66
|
+
getFirstCheckpoint(): string;
|
|
67
|
+
getCheckpoints(): string[];
|
|
68
|
+
clearCheckpoints(): void;
|
|
69
|
+
hasCheckpoints(): boolean;
|
|
70
|
+
chat({ chatCtx, toolCtx, connOptions, abortController, }: {
|
|
71
|
+
chatCtx: llm.ChatContext;
|
|
72
|
+
toolCtx?: llm.ToolContext;
|
|
73
|
+
connOptions?: APIConnectOptions;
|
|
74
|
+
abortController?: AbortController;
|
|
75
|
+
}): llm.LLMStream;
|
|
76
|
+
}
|
|
77
|
+
export declare function createPipelineDirectLLM(opts: PipelineDirectOptions): PipelineDirectLLM;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Direct LLM — Wraps ClaudeLLM with parallel Gemini fast brain
|
|
3
|
+
*
|
|
4
|
+
* In pipeline mode, every user message fires two tracks simultaneously:
|
|
5
|
+
* Track A: Claude SDK (unchanged) — speaks the answer via TTS
|
|
6
|
+
* Track B: Gemini fast brain (new) — searches JSONL memory, sends result to UI only
|
|
7
|
+
*
|
|
8
|
+
* Phase 1 (current): Gemini is silent — results go to frontend panel for monitoring
|
|
9
|
+
* Phase 2 (future): Gemini speaks first, Claude suppressed when Gemini has HIGH confidence
|
|
10
|
+
*/
|
|
11
|
+
import { llm, DEFAULT_API_CONNECT_OPTIONS } from '@livekit/agents';
|
|
12
|
+
import { ClaudeLLM } from './claude-llm.js';
|
|
13
|
+
import { askPipelineFastBrain } from './pipeline-fastbrain.js';
|
|
14
|
+
import { buildSummaryIndex, startIndexWatcher } from './summary-index.js';
|
|
15
|
+
export class PipelineDirectLLM extends llm.LLM {
|
|
16
|
+
#claudeLLM;
|
|
17
|
+
#opts;
|
|
18
|
+
#turnAbort = null;
|
|
19
|
+
#indexWatcher = null;
|
|
20
|
+
#indexBuilding = false;
|
|
21
|
+
constructor(opts) {
|
|
22
|
+
super();
|
|
23
|
+
this.#claudeLLM = new ClaudeLLM(opts);
|
|
24
|
+
this.#opts = opts;
|
|
25
|
+
}
|
|
26
|
+
/** Stop the index watcher (call on disconnect/session switch) */
|
|
27
|
+
stopIndexWatcher() {
|
|
28
|
+
if (this.#indexWatcher) {
|
|
29
|
+
this.#indexWatcher.stop();
|
|
30
|
+
this.#indexWatcher = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Proxy all properties
|
|
34
|
+
get events() { return this.#claudeLLM.events; }
|
|
35
|
+
get sessionId() { return this.#claudeLLM.sessionId; }
|
|
36
|
+
get model() { return this.#claudeLLM.model; }
|
|
37
|
+
get isResumingSession() { return this.#claudeLLM.isResumingSession; }
|
|
38
|
+
label() { return 'pipeline-direct'; }
|
|
39
|
+
// Proxy all methods
|
|
40
|
+
setResumeSessionId(id) { this.#claudeLLM.setResumeSessionId(id); }
|
|
41
|
+
setContinueSession(e) { this.#claudeLLM.setContinueSession(e); }
|
|
42
|
+
resetForSessionSwitch() { this.#claudeLLM.resetForSessionSwitch(); }
|
|
43
|
+
respondToPermission(allow, msg) { this.#claudeLLM.respondToPermission(allow, msg); }
|
|
44
|
+
hasPendingPermission() { return this.#claudeLLM.hasPendingPermission(); }
|
|
45
|
+
getPendingPermission() { return this.#claudeLLM.getPendingPermission(); }
|
|
46
|
+
getMcpServers() { return this.#claudeLLM.getMcpServers(); }
|
|
47
|
+
setMcpServers(s) { this.#claudeLLM.setMcpServers(s); }
|
|
48
|
+
// Agent control — proxied to ClaudeLLM for fast brain access
|
|
49
|
+
async interruptAgent() { return this.#claudeLLM.interruptQuery(); }
|
|
50
|
+
abortAgent() { this.#claudeLLM.abortQuery(); }
|
|
51
|
+
async rewindAgent(checkpointId) { return this.#claudeLLM.rewindToCheckpoint(checkpointId); }
|
|
52
|
+
hasActiveAgent() { return this.#claudeLLM.hasActiveQuery(); }
|
|
53
|
+
/** Send a new prompt to Claude via direct chat() — event listeners stay attached */
|
|
54
|
+
sendPrompt(prompt) {
|
|
55
|
+
console.log(`📋 [pipeline] Sending prompt to Claude (${prompt.length} chars)`);
|
|
56
|
+
const chatCtx = new llm.ChatContext();
|
|
57
|
+
chatCtx.addMessage({ role: 'user', content: prompt });
|
|
58
|
+
this.#claudeLLM.chat({ chatCtx });
|
|
59
|
+
}
|
|
60
|
+
enableMcpServer(k, c) { this.#claudeLLM.enableMcpServer(k, c); }
|
|
61
|
+
disableMcpServer(k) { this.#claudeLLM.disableMcpServer(k); }
|
|
62
|
+
getLatestCheckpoint() { return this.#claudeLLM.getLatestCheckpoint(); }
|
|
63
|
+
getFirstCheckpoint() { return this.#claudeLLM.getFirstCheckpoint(); }
|
|
64
|
+
getCheckpoints() { return this.#claudeLLM.getCheckpoints(); }
|
|
65
|
+
clearCheckpoints() { this.#claudeLLM.clearCheckpoints(); }
|
|
66
|
+
hasCheckpoints() { return this.#claudeLLM.hasCheckpoints(); }
|
|
67
|
+
#chatCallCount = 0;
|
|
68
|
+
chat({ chatCtx, toolCtx, connOptions = DEFAULT_API_CONNECT_OPTIONS, abortController, }) {
|
|
69
|
+
const callN = ++this.#chatCallCount;
|
|
70
|
+
// Extract user text for fast brain
|
|
71
|
+
let userText = '';
|
|
72
|
+
for (let i = chatCtx.items.length - 1; i >= 0; i--) {
|
|
73
|
+
const item = chatCtx.items[i];
|
|
74
|
+
if (item.type === 'message' && item.role === 'user') {
|
|
75
|
+
if (Array.isArray(item.content)) {
|
|
76
|
+
userText = item.content.filter((c) => typeof c === 'string').join('\n');
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
console.log(`📥 [pipeline] chat() call #${callN}: "${userText.substring(0, 60)}"`);
|
|
82
|
+
// Check for pending interruption context — enrich user message if interrupted
|
|
83
|
+
const interruptCtx = this.#opts.getAndConsumeInterruptionContext?.();
|
|
84
|
+
if (interruptCtx && userText.trim()) {
|
|
85
|
+
console.log(`🔇 [pipeline] Enriching user message with interruption context`);
|
|
86
|
+
// Interrupt Claude's current work before sending enriched message
|
|
87
|
+
this.#claudeLLM.interruptQuery().catch(() => { });
|
|
88
|
+
// Replace user message in chatCtx with context-enriched version
|
|
89
|
+
const enrichedMessage = [
|
|
90
|
+
`[INTERRUPTED] The user interrupted your response mid-speech.`,
|
|
91
|
+
``,
|
|
92
|
+
`What the user heard before cutoff:`,
|
|
93
|
+
`"${interruptCtx.spokenText}"`,
|
|
94
|
+
``,
|
|
95
|
+
`Your recent messages (full untruncated — you wrote these):`,
|
|
96
|
+
interruptCtx.recentMessages || '(no recent messages found)',
|
|
97
|
+
``,
|
|
98
|
+
`User's message: "${userText}"`,
|
|
99
|
+
``,
|
|
100
|
+
`Handle naturally:`,
|
|
101
|
+
`- If it's a quick side question, answer it then continue where you left off (restart sub-agents if needed)`,
|
|
102
|
+
`- If they want to change direction, follow their lead`,
|
|
103
|
+
`- Don't repeat what was already spoken unless it makes sense to clarify`,
|
|
104
|
+
`- Reference unspoken content naturally if relevant`,
|
|
105
|
+
].join('\n');
|
|
106
|
+
// Modify the last user message in chatCtx
|
|
107
|
+
for (let i = chatCtx.items.length - 1; i >= 0; i--) {
|
|
108
|
+
const item = chatCtx.items[i];
|
|
109
|
+
if (item.type === 'message' && item.role === 'user') {
|
|
110
|
+
item.content = [enrichedMessage];
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Fire Claude
|
|
116
|
+
const claudeStream = this.#claudeLLM.chat({ chatCtx, toolCtx, connOptions, abortController });
|
|
117
|
+
// Fire pipeline fast brain in background — no await, no blocking
|
|
118
|
+
if (userText.trim()) {
|
|
119
|
+
this.#firePipelineFastBrain(userText);
|
|
120
|
+
}
|
|
121
|
+
return claudeStream;
|
|
122
|
+
}
|
|
123
|
+
async #firePipelineFastBrain(userText) {
|
|
124
|
+
// Abort stale turn
|
|
125
|
+
if (this.#turnAbort)
|
|
126
|
+
this.#turnAbort.abort();
|
|
127
|
+
this.#turnAbort = new AbortController();
|
|
128
|
+
const signal = this.#turnAbort.signal;
|
|
129
|
+
const startMs = Date.now();
|
|
130
|
+
// Wait for SDK to assign session ID — listen for event instead of polling
|
|
131
|
+
// Large sessions (22MB+) can take 10-15s for SDK to replay JSONL
|
|
132
|
+
let sessionId = this.#claudeLLM.sessionId;
|
|
133
|
+
if (!sessionId) {
|
|
134
|
+
sessionId = await new Promise((resolve) => {
|
|
135
|
+
// Listen for the session_id event from SDK
|
|
136
|
+
const onSessionId = (data) => {
|
|
137
|
+
resolve(data.sessionId);
|
|
138
|
+
};
|
|
139
|
+
this.#claudeLLM.events.once('session_id', onSessionId);
|
|
140
|
+
// Safety timeout — don't wait forever
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
this.#claudeLLM.events.removeListener('session_id', onSessionId);
|
|
143
|
+
resolve(this.#claudeLLM.sessionId || 'pending');
|
|
144
|
+
}, 15000);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const workingDir = this.#opts.workingDirectory || process.cwd();
|
|
148
|
+
const sessionBaseDir = this.#opts.sessionBaseDir || workingDir;
|
|
149
|
+
// Build summary index on first question (async, non-blocking for subsequent questions)
|
|
150
|
+
if (!this.#indexWatcher && !this.#indexBuilding && sessionId !== 'pending') {
|
|
151
|
+
this.#indexBuilding = true;
|
|
152
|
+
try {
|
|
153
|
+
const startBuild = Date.now();
|
|
154
|
+
const state = buildSummaryIndex(sessionId, workingDir, sessionBaseDir, (msg) => console.log(`🔍 [index] ${msg}`));
|
|
155
|
+
this.#indexWatcher = startIndexWatcher(sessionId, workingDir, sessionBaseDir, state);
|
|
156
|
+
console.log(`🔍 [index] Built + watching in ${Date.now() - startBuild}ms`);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
console.error('🔍 [index] Build failed:', err?.message);
|
|
160
|
+
}
|
|
161
|
+
this.#indexBuilding = false;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
console.log(`🧠⚡ [pipeline] Fast brain: "${userText.substring(0, 60)}"`);
|
|
165
|
+
const result = await askPipelineFastBrain(workingDir, sessionId, userText, {
|
|
166
|
+
chatHistory: this.#opts.getChatHistory?.() || [],
|
|
167
|
+
researchContext: this.#opts.getResearchContext?.(),
|
|
168
|
+
sessionBaseDir,
|
|
169
|
+
agentControl: {
|
|
170
|
+
interrupt: () => this.#claudeLLM.interruptQuery(),
|
|
171
|
+
abort: () => this.#claudeLLM.abortQuery(),
|
|
172
|
+
hasActiveAgent: () => this.#claudeLLM.hasActiveQuery(),
|
|
173
|
+
getRecentUserMessages: (count) => {
|
|
174
|
+
const history = this.#opts.getChatHistory?.() || [];
|
|
175
|
+
return history
|
|
176
|
+
.filter(t => t.role === 'user')
|
|
177
|
+
.slice(-count)
|
|
178
|
+
.map(t => t.content);
|
|
179
|
+
},
|
|
180
|
+
sendPrompt: (prompt) => {
|
|
181
|
+
// Direct call to ClaudeLLM.chat() — event listeners (tts_say, tool_use, etc.) still attached
|
|
182
|
+
// skipTTSQueue mode: tts_say events → index.ts → session.say() — works independently
|
|
183
|
+
console.log(`🧠⚡ [control] Sending new prompt to Claude (${prompt.length} chars)`);
|
|
184
|
+
const chatCtx = new llm.ChatContext();
|
|
185
|
+
chatCtx.addMessage({ role: 'user', content: prompt });
|
|
186
|
+
this.#claudeLLM.chat({ chatCtx });
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
if (signal.aborted)
|
|
191
|
+
return;
|
|
192
|
+
const elapsedMs = Date.now() - startMs;
|
|
193
|
+
console.log(`🧠⚡ [pipeline] ${result.type} in ${elapsedMs}ms [${result.toolsUsed.join(',')}]: "${result.script.substring(0, 80)}"`);
|
|
194
|
+
this.#opts.onFastBrainResult?.({
|
|
195
|
+
question: userText,
|
|
196
|
+
answer: result.script,
|
|
197
|
+
type: result.type,
|
|
198
|
+
elapsedMs,
|
|
199
|
+
timestamp: Date.now(),
|
|
200
|
+
toolsUsed: result.toolsUsed,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
if (err?.name === 'AbortError')
|
|
205
|
+
return;
|
|
206
|
+
console.error('❌ [pipeline] Fast brain error:', err?.message);
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
if (this.#turnAbort?.signal === signal)
|
|
210
|
+
this.#turnAbort = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export function createPipelineDirectLLM(opts) {
|
|
215
|
+
return new PipelineDirectLLM(opts);
|
|
216
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pipeline-fastbrain.ts — Pipeline Fast Brain (Agent with AFC)
|
|
3
|
+
*
|
|
4
|
+
* Uses Gemini Flash as an AGENT with Automatic Function Calling (AFC).
|
|
5
|
+
* One generateContent() call handles everything:
|
|
6
|
+
* - Gemini decides IF it needs to search (skips for greetings/follow-ups)
|
|
7
|
+
* - Gemini decides WHAT to search (smart phrase selection)
|
|
8
|
+
* - Gemini can multi-step: search → not enough → refine → search again
|
|
9
|
+
* - AFC handles the tool loop internally (up to 3 rounds)
|
|
10
|
+
*
|
|
11
|
+
* Tools:
|
|
12
|
+
* search_session — ripgrep the summary index + read full content via byte offsets
|
|
13
|
+
*
|
|
14
|
+
* No separate phrase extraction call. No manual tool loop. One API invocation.
|
|
15
|
+
*/
|
|
16
|
+
export interface PipelineFastBrainResult {
|
|
17
|
+
script: string;
|
|
18
|
+
type: 'answer' | 'research_needed' | 'acknowledgment' | 'error';
|
|
19
|
+
toolsUsed: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface PipelineFastBrainOptions {
|
|
22
|
+
chatHistory?: {
|
|
23
|
+
role: string;
|
|
24
|
+
content: string;
|
|
25
|
+
}[];
|
|
26
|
+
researchContext?: string;
|
|
27
|
+
sessionBaseDir?: string;
|
|
28
|
+
agentControl?: AgentControlCallbacks;
|
|
29
|
+
}
|
|
30
|
+
/** Clear the pipeline fast brain session (call on disconnect/reconnect) */
|
|
31
|
+
export declare function clearPipelineFastBrainSession(): void;
|
|
32
|
+
/** No-op — kept for backward compatibility with index.ts import */
|
|
33
|
+
export declare function prewarmBM25Index(_sessionId: string, _workingDir: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Create a CallableTool that wraps ripgrep search of the summary index
|
|
36
|
+
* + byte-offset full content reads from raw JSONL.
|
|
37
|
+
*/
|
|
38
|
+
export interface AgentControlCallbacks {
|
|
39
|
+
interrupt: () => Promise<boolean>;
|
|
40
|
+
abort: () => void;
|
|
41
|
+
hasActiveAgent: () => boolean;
|
|
42
|
+
getRecentUserMessages: (count: number) => string[];
|
|
43
|
+
sendPrompt: (prompt: string) => void;
|
|
44
|
+
}
|
|
45
|
+
export declare function askPipelineFastBrain(workingDir: string, sessionId: string, question: string, opts?: PipelineFastBrainOptions): Promise<PipelineFastBrainResult>;
|