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.
Files changed (37) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.claude/skills/markdown-to-pdf/SKILL.md +29 -0
  3. package/.claude/skills/pdf-to-markdown/SKILL.md +28 -0
  4. package/.claude/skills/playwright-browser/SKILL.md +75 -0
  5. package/.claude/skills/youtube-transcript/SKILL.md +24 -0
  6. package/dist/claude-llm.d.ts +29 -1
  7. package/dist/claude-llm.js +346 -79
  8. package/dist/config.d.ts +6 -2
  9. package/dist/config.js +6 -1
  10. package/dist/fast-brain.d.ts +124 -12
  11. package/dist/fast-brain.js +1361 -96
  12. package/dist/index-3-2-26-legacy.d.ts +1 -0
  13. package/dist/index-3-2-26-legacy.js +2233 -0
  14. package/dist/index.js +889 -394
  15. package/dist/jsonl-search.d.ts +66 -0
  16. package/dist/jsonl-search.js +274 -0
  17. package/dist/leagcyprompts2.d.ts +0 -0
  18. package/dist/leagcyprompts2.js +573 -0
  19. package/dist/pipeline-direct-llm.d.ts +77 -0
  20. package/dist/pipeline-direct-llm.js +216 -0
  21. package/dist/pipeline-fastbrain.d.ts +45 -0
  22. package/dist/pipeline-fastbrain.js +367 -0
  23. package/dist/prompts-2-25-26.d.ts +0 -0
  24. package/dist/prompts-2-25-26.js +518 -0
  25. package/dist/prompts-3-2-26.d.ts +78 -0
  26. package/dist/prompts-3-2-26.js +1319 -0
  27. package/dist/prompts.d.ts +83 -8
  28. package/dist/prompts.js +1990 -374
  29. package/dist/session-access.d.ts +60 -2
  30. package/dist/session-access.js +172 -2
  31. package/dist/summary-index.d.ts +87 -0
  32. package/dist/summary-index.js +570 -0
  33. package/dist/turn-detector-shim.d.ts +24 -0
  34. package/dist/turn-detector-shim.js +83 -0
  35. package/dist/voice-io.d.ts +9 -3
  36. package/dist/voice-io.js +39 -20
  37. 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>;