beecork 1.3.11 → 1.4.1

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.
@@ -1,160 +0,0 @@
1
- import { PipeAnthropicClient } from './anthropic-client.js';
2
- import { PipeMemoryStore } from './memory-store.js';
3
- import { scanForProjects } from './project-scanner.js';
4
- import { parseTabMessage } from '../util/text.js';
5
- import { logger } from '../util/logger.js';
6
- export class PipeBrain {
7
- client;
8
- memory;
9
- config;
10
- tabManager;
11
- notifyCallback = null;
12
- constructor(config, tabManager) {
13
- this.config = config;
14
- this.tabManager = tabManager;
15
- this.client = new PipeAnthropicClient(config.pipe.anthropicApiKey, config.pipe.routingModel, config.pipe.complexModel);
16
- this.memory = new PipeMemoryStore();
17
- }
18
- setNotifyCallback(cb) {
19
- this.notifyCallback = cb;
20
- }
21
- /** Main entry point: process a user message with intelligence */
22
- async process(message, context) {
23
- const decisions = [];
24
- // Step 1: Route the message to the right tab/project
25
- const route = await this.route(message, decisions);
26
- // Step 2: Ensure the tab exists with the right working directory
27
- if (route.projectPath) {
28
- this.tabManager.ensureTab(route.tabName, route.projectPath);
29
- this.memory.updateProjectLastUsed(route.projectPath);
30
- }
31
- // Step 3: Send to Claude Code
32
- let result;
33
- try {
34
- result = await this.tabManager.sendMessage(route.tabName, message, { skipExtraction: true, projectPath: route.projectPath ?? undefined });
35
- }
36
- catch (err) {
37
- return {
38
- tabName: route.tabName,
39
- response: { text: `Error: ${err instanceof Error ? err.message : err}`, error: true, costUsd: 0, durationMs: 0 },
40
- decisions,
41
- goalStatus: null,
42
- };
43
- }
44
- // Step 4: Evaluate if the goal was achieved
45
- let goalStatus = null;
46
- if (!result.error && result.text && result.durationMs > 3000) {
47
- goalStatus = await this.evaluateAndFollowUp(message, result, route.tabName, decisions);
48
- }
49
- // Step 5: Learn from the conversation (fire and forget)
50
- this.learn(route.tabName, message, result.text).catch(err => {
51
- logger.error('Pipe learning failed:', err);
52
- });
53
- return {
54
- tabName: route.tabName,
55
- response: { text: result.text, error: result.error, costUsd: result.costUsd, durationMs: result.durationMs },
56
- decisions,
57
- goalStatus,
58
- };
59
- }
60
- /** Route a message to the right project/tab */
61
- async route(message, decisions) {
62
- // Check for manual /tab override first
63
- if (message.startsWith('/tab ')) {
64
- const parsed = parseTabMessage(message);
65
- if (parsed.tabName !== 'default') {
66
- decisions.push(`📌 Manual routing to "${parsed.tabName}"`);
67
- return { tabName: parsed.tabName, projectPath: null, confidence: 1.0, reason: 'Manual override', needsConfirmation: false };
68
- }
69
- }
70
- const projects = this.memory.getProjects();
71
- const recentRouting = this.memory.getRecentRouting(5);
72
- // If no projects and no API key, just use default
73
- if (projects.length === 0 || !this.config.pipe.anthropicApiKey) {
74
- decisions.push('📍 Routing to default tab (no projects discovered)');
75
- return { tabName: 'default', projectPath: null, confidence: 1.0, reason: 'No projects', needsConfirmation: false };
76
- }
77
- try {
78
- const route = await this.client.route(message, projects, recentRouting);
79
- // Record the routing decision
80
- this.memory.recordRouting(message, route.tabName, route.projectPath, route.confidence);
81
- if (route.confidence >= this.config.pipe.confidenceThreshold) {
82
- decisions.push(`🧠 Routing to "${route.tabName}" (${Math.round(route.confidence * 100)}% confidence) — ${route.reason}`);
83
- }
84
- else {
85
- decisions.push(`🤔 Low confidence routing to "${route.tabName}" (${Math.round(route.confidence * 100)}%) — ${route.reason}. Using default.`);
86
- route.tabName = 'default';
87
- }
88
- return route;
89
- }
90
- catch (err) {
91
- logger.error('Pipe routing failed, using default:', err);
92
- decisions.push('⚠️ Routing failed, using default tab');
93
- return { tabName: 'default', projectPath: null, confidence: 0.5, reason: 'Routing error', needsConfirmation: false };
94
- }
95
- }
96
- /** Evaluate if the goal was achieved and send follow-ups if needed */
97
- async evaluateAndFollowUp(originalGoal, lastResult, tabName, decisions) {
98
- let currentResult = lastResult;
99
- let followUpCount = 0;
100
- const maxFollowUps = this.config.pipe.maxFollowUps;
101
- while (followUpCount < maxFollowUps) {
102
- try {
103
- const evaluation = await this.client.evaluateGoal(originalGoal, currentResult.text);
104
- if (evaluation.status === 'done') {
105
- decisions.push(`✅ Goal achieved: ${evaluation.reason}`);
106
- return evaluation;
107
- }
108
- if (evaluation.status === 'failed') {
109
- decisions.push(`❌ Goal failed: ${evaluation.reason}`);
110
- return evaluation;
111
- }
112
- // PARTIAL — send follow-up
113
- followUpCount++;
114
- const followUpMsg = evaluation.followUp || `Continue working on the original goal: "${originalGoal}". You haven't finished yet.`;
115
- decisions.push(`🔄 Follow-up ${followUpCount}/${maxFollowUps}: ${evaluation.reason}`);
116
- // Notify user about the follow-up
117
- await this.notifyCallback?.(`🔄 [${tabName}] Sending follow-up (${followUpCount}/${maxFollowUps}): ${evaluation.reason}`);
118
- // Send follow-up to Claude Code
119
- currentResult = await this.tabManager.sendMessage(tabName, followUpMsg);
120
- if (currentResult.error) {
121
- decisions.push(`❌ Follow-up failed with error`);
122
- return { status: 'failed', reason: currentResult.text, followUp: null };
123
- }
124
- }
125
- catch (err) {
126
- logger.error('Goal evaluation failed:', err);
127
- return { status: 'done', reason: 'Evaluation error — assuming done', followUp: null };
128
- }
129
- }
130
- decisions.push(`⚠️ Max follow-ups (${maxFollowUps}) reached`);
131
- return { status: 'partial', reason: `Reached max ${maxFollowUps} follow-ups`, followUp: null };
132
- }
133
- /** Learn from a completed conversation */
134
- async learn(tabName, userMessage, response) {
135
- if (!response || response.length < 100)
136
- return;
137
- try {
138
- const existingFacts = this.memory.getKnowledge('', 10);
139
- const entries = await this.client.extractKnowledge(`User: ${userMessage}\n\nAssistant: ${response}`, existingFacts);
140
- for (const entry of entries) {
141
- entry.tabName = tabName;
142
- this.memory.addKnowledge(entry);
143
- }
144
- if (entries.length > 0) {
145
- logger.info(`Pipe learned ${entries.length} facts from ${tabName}`);
146
- }
147
- }
148
- catch (err) {
149
- logger.error('Pipe learning error:', err);
150
- }
151
- }
152
- /** Discover projects on the filesystem */
153
- async discoverProjects() {
154
- const projects = scanForProjects(this.config.pipe.projectScanPaths);
155
- for (const project of projects) {
156
- this.memory.upsertProject(project);
157
- }
158
- return projects.length;
159
- }
160
- }
@@ -1,10 +0,0 @@
1
- import type { Project, KnowledgeEntry } from './types.js';
2
- export declare class PipeMemoryStore {
3
- getProjects(): Project[];
4
- upsertProject(project: Project): void;
5
- updateProjectLastUsed(path: string): void;
6
- getRecentRouting(limit?: number): string[];
7
- recordRouting(messagePreview: string, tabName: string, projectPath: string | null, confidence: number): void;
8
- addKnowledge(entry: KnowledgeEntry): void;
9
- getKnowledge(query: string, limit?: number): string[];
10
- }
@@ -1,50 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
- import { getDb } from '../db/index.js';
3
- export class PipeMemoryStore {
4
- // ─── Projects ───
5
- getProjects() {
6
- const db = getDb();
7
- const rows = db.prepare('SELECT * FROM projects ORDER BY last_used_at DESC NULLS LAST').all();
8
- return rows.map(r => ({
9
- id: r.id,
10
- name: r.name,
11
- path: r.path,
12
- type: r.type || 'user-project',
13
- lastUsedAt: r.last_used_at,
14
- createdAt: r.created_at,
15
- }));
16
- }
17
- upsertProject(project) {
18
- const db = getDb();
19
- db.prepare(`INSERT INTO projects (id, name, path, type)
20
- VALUES (?, ?, ?, ?)
21
- ON CONFLICT(name) DO UPDATE SET
22
- path=excluded.path, type=excluded.type, last_used_at=datetime('now')
23
- `).run(project.id || uuidv4(), project.name, project.path, project.type || 'user-project');
24
- }
25
- updateProjectLastUsed(path) {
26
- const db = getDb();
27
- db.prepare('UPDATE projects SET last_used_at = datetime("now") WHERE path = ?').run(path);
28
- }
29
- // ─── Routing History ───
30
- getRecentRouting(limit = 10) {
31
- const db = getDb();
32
- const rows = db.prepare('SELECT message_preview, tab_name, confidence FROM routing_history ORDER BY created_at DESC LIMIT ?').all(limit);
33
- return rows.map(r => `"${r.message_preview}" → ${r.tab_name} (${Math.round(r.confidence * 100)}%)`);
34
- }
35
- recordRouting(messagePreview, tabName, projectPath, confidence) {
36
- const db = getDb();
37
- db.prepare('INSERT INTO routing_history (message_preview, tab_name, project_path, confidence) VALUES (?, ?, ?, ?)').run(messagePreview.slice(0, 200), tabName, projectPath, confidence);
38
- }
39
- // ─── Knowledge ───
40
- addKnowledge(entry) {
41
- const db = getDb();
42
- const content = entry.category ? `[${entry.category}] ${entry.content}` : entry.content;
43
- db.prepare('INSERT INTO memories (content, tab_name, source) VALUES (?, ?, ?)').run(content, entry.tabName, entry.source);
44
- }
45
- getKnowledge(query, limit = 20) {
46
- const db = getDb();
47
- const rows = db.prepare('SELECT content FROM memories WHERE content LIKE ? ORDER BY created_at DESC LIMIT ?').all(`%${query}%`, limit);
48
- return rows.map(r => r.content);
49
- }
50
- }
@@ -1,6 +0,0 @@
1
- import type { Project } from './types.js';
2
- /**
3
- * Read projects from the database (populated by discoverProjects() at daemon startup).
4
- * Falls back to an empty list if the DB is not yet initialized.
5
- */
6
- export declare function scanForProjects(_scanPaths: string[]): Project[];
@@ -1,26 +0,0 @@
1
- import { getDb } from '../db/index.js';
2
- import { logger } from '../util/logger.js';
3
- /**
4
- * Read projects from the database (populated by discoverProjects() at daemon startup).
5
- * Falls back to an empty list if the DB is not yet initialized.
6
- */
7
- export function scanForProjects(_scanPaths) {
8
- try {
9
- const db = getDb();
10
- const rows = db.prepare('SELECT * FROM projects ORDER BY last_used_at DESC').all();
11
- const projects = rows.map(r => ({
12
- id: r.id,
13
- name: r.name,
14
- path: r.path,
15
- type: r.type,
16
- lastUsedAt: r.last_used_at,
17
- createdAt: r.created_at,
18
- }));
19
- logger.info(`Project scanner: found ${projects.length} projects from DB`);
20
- return projects;
21
- }
22
- catch (err) {
23
- logger.warn('Project scanner: failed to read from DB, returning empty list:', err);
24
- return [];
25
- }
26
- }
@@ -1,46 +0,0 @@
1
- export interface Project {
2
- id: string;
3
- name: string;
4
- path: string;
5
- type: string;
6
- lastUsedAt: string;
7
- createdAt: string;
8
- /** Populated at scan time, not persisted in DB */
9
- description?: string;
10
- /** Populated at scan time, not persisted in DB */
11
- languages?: string[];
12
- }
13
- export interface RouteDecision {
14
- tabName: string;
15
- projectPath: string | null;
16
- confidence: number;
17
- reason: string;
18
- needsConfirmation: boolean;
19
- }
20
- export interface GoalEvaluation {
21
- status: 'done' | 'partial' | 'failed';
22
- reason: string;
23
- followUp: string | null;
24
- }
25
- export interface KnowledgeEntry {
26
- content: string;
27
- category: 'project' | 'preference' | 'decision' | 'fact';
28
- tabName: string | null;
29
- source: 'scan' | 'conversation' | 'user' | 'pipe';
30
- }
31
- export interface ChatContext {
32
- chatId: number;
33
- userId: number;
34
- messageId: number;
35
- }
36
- export interface PipeResult {
37
- tabName: string;
38
- response: {
39
- text: string;
40
- error: boolean;
41
- costUsd: number;
42
- durationMs: number;
43
- };
44
- decisions: string[];
45
- goalStatus: GoalEvaluation | null;
46
- }
@@ -1 +0,0 @@
1
- export {};