beecork 1.7.1 → 2.0.0
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/README.md +52 -111
- package/dist/index.js +1394 -581
- package/package.json +26 -54
- package/dist/capabilities/index.d.ts +0 -3
- package/dist/capabilities/index.js +0 -2
- package/dist/capabilities/manager.d.ts +0 -13
- package/dist/capabilities/manager.js +0 -115
- package/dist/capabilities/packs.d.ts +0 -2
- package/dist/capabilities/packs.js +0 -54
- package/dist/capabilities/types.d.ts +0 -21
- package/dist/capabilities/types.js +0 -1
- package/dist/channels/admin.d.ts +0 -10
- package/dist/channels/admin.js +0 -20
- package/dist/channels/command-handler.d.ts +0 -19
- package/dist/channels/command-handler.js +0 -235
- package/dist/channels/discord.d.ts +0 -17
- package/dist/channels/discord.js +0 -266
- package/dist/channels/index.d.ts +0 -7
- package/dist/channels/index.js +0 -6
- package/dist/channels/loader.d.ts +0 -7
- package/dist/channels/loader.js +0 -87
- package/dist/channels/pipeline.d.ts +0 -50
- package/dist/channels/pipeline.js +0 -92
- package/dist/channels/registry.d.ts +0 -36
- package/dist/channels/registry.js +0 -80
- package/dist/channels/send-helpers.d.ts +0 -19
- package/dist/channels/send-helpers.js +0 -21
- package/dist/channels/telegram.d.ts +0 -47
- package/dist/channels/telegram.js +0 -649
- package/dist/channels/types.d.ts +0 -41
- package/dist/channels/types.js +0 -1
- package/dist/channels/voice-state.d.ts +0 -29
- package/dist/channels/voice-state.js +0 -45
- package/dist/channels/webhook.d.ts +0 -16
- package/dist/channels/webhook.js +0 -222
- package/dist/channels/whatsapp.d.ts +0 -28
- package/dist/channels/whatsapp.js +0 -323
- package/dist/cli/capabilities.d.ts +0 -3
- package/dist/cli/capabilities.js +0 -72
- package/dist/cli/channel.d.ts +0 -3
- package/dist/cli/channel.js +0 -202
- package/dist/cli/commands.d.ts +0 -15
- package/dist/cli/commands.js +0 -279
- package/dist/cli/doctor.d.ts +0 -1
- package/dist/cli/doctor.js +0 -208
- package/dist/cli/handoff.d.ts +0 -8
- package/dist/cli/handoff.js +0 -33
- package/dist/cli/helpers.d.ts +0 -2
- package/dist/cli/helpers.js +0 -21
- package/dist/cli/mcp.d.ts +0 -3
- package/dist/cli/mcp.js +0 -47
- package/dist/cli/media.d.ts +0 -2
- package/dist/cli/media.js +0 -125
- package/dist/cli/setup.d.ts +0 -1
- package/dist/cli/setup.js +0 -239
- package/dist/cli/store.d.ts +0 -3
- package/dist/cli/store.js +0 -108
- package/dist/config.d.ts +0 -11
- package/dist/config.js +0 -115
- package/dist/cron/scheduler.d.ts +0 -22
- package/dist/cron/scheduler.js +0 -220
- package/dist/cron/store.d.ts +0 -12
- package/dist/cron/store.js +0 -83
- package/dist/daemon.d.ts +0 -1
- package/dist/daemon.js +0 -345
- package/dist/dashboard/html.d.ts +0 -1
- package/dist/dashboard/html.js +0 -849
- package/dist/dashboard/routes.d.ts +0 -17
- package/dist/dashboard/routes.js +0 -623
- package/dist/dashboard/server.d.ts +0 -1
- package/dist/dashboard/server.js +0 -104
- package/dist/db/connection.d.ts +0 -29
- package/dist/db/connection.js +0 -37
- package/dist/db/index.d.ts +0 -14
- package/dist/db/index.js +0 -127
- package/dist/db/migrations.d.ts +0 -2
- package/dist/db/migrations.js +0 -384
- package/dist/delegation/manager.d.ts +0 -18
- package/dist/delegation/manager.js +0 -89
- package/dist/index.d.ts +0 -2
- package/dist/knowledge/index.d.ts +0 -2
- package/dist/knowledge/index.js +0 -1
- package/dist/knowledge/manager.d.ts +0 -17
- package/dist/knowledge/manager.js +0 -160
- package/dist/knowledge/types.d.ts +0 -7
- package/dist/knowledge/types.js +0 -1
- package/dist/machines/forwarder.d.ts +0 -7
- package/dist/machines/forwarder.js +0 -35
- package/dist/machines/index.d.ts +0 -1
- package/dist/machines/index.js +0 -1
- package/dist/machines/registry.d.ts +0 -15
- package/dist/machines/registry.js +0 -46
- package/dist/mcp/handlers.d.ts +0 -37
- package/dist/mcp/handlers.js +0 -520
- package/dist/mcp/server.d.ts +0 -2
- package/dist/mcp/server.js +0 -86
- package/dist/mcp/tool-definitions.d.ts +0 -1225
- package/dist/mcp/tool-definitions.js +0 -412
- package/dist/mcp/validate.d.ts +0 -23
- package/dist/mcp/validate.js +0 -65
- package/dist/media/factory.d.ts +0 -6
- package/dist/media/factory.js +0 -36
- package/dist/media/generators/dall-e.d.ts +0 -10
- package/dist/media/generators/dall-e.js +0 -41
- package/dist/media/generators/elevenlabs-music.d.ts +0 -9
- package/dist/media/generators/elevenlabs-music.js +0 -34
- package/dist/media/generators/elevenlabs-sfx.d.ts +0 -9
- package/dist/media/generators/elevenlabs-sfx.js +0 -33
- package/dist/media/generators/kling.d.ts +0 -9
- package/dist/media/generators/kling.js +0 -47
- package/dist/media/generators/lyria.d.ts +0 -10
- package/dist/media/generators/lyria.js +0 -41
- package/dist/media/generators/nano-banana.d.ts +0 -10
- package/dist/media/generators/nano-banana.js +0 -45
- package/dist/media/generators/poll-util.d.ts +0 -12
- package/dist/media/generators/poll-util.js +0 -22
- package/dist/media/generators/recraft.d.ts +0 -9
- package/dist/media/generators/recraft.js +0 -52
- package/dist/media/generators/runway.d.ts +0 -9
- package/dist/media/generators/runway.js +0 -49
- package/dist/media/generators/stable-diffusion.d.ts +0 -9
- package/dist/media/generators/stable-diffusion.js +0 -35
- package/dist/media/generators/veo.d.ts +0 -9
- package/dist/media/generators/veo.js +0 -32
- package/dist/media/index.d.ts +0 -6
- package/dist/media/index.js +0 -8
- package/dist/media/loader.d.ts +0 -8
- package/dist/media/loader.js +0 -46
- package/dist/media/store.d.ts +0 -17
- package/dist/media/store.js +0 -69
- package/dist/media/types.d.ts +0 -43
- package/dist/media/types.js +0 -1
- package/dist/memory/extractor.d.ts +0 -5
- package/dist/memory/extractor.js +0 -157
- package/dist/notifications/index.d.ts +0 -4
- package/dist/notifications/index.js +0 -21
- package/dist/notifications/ntfy.d.ts +0 -9
- package/dist/notifications/ntfy.js +0 -25
- package/dist/notifications/pushover.d.ts +0 -9
- package/dist/notifications/pushover.js +0 -26
- package/dist/notifications/types.d.ts +0 -5
- package/dist/notifications/types.js +0 -1
- package/dist/notifications/webhook-provider.d.ts +0 -9
- package/dist/notifications/webhook-provider.js +0 -24
- package/dist/observability/analytics.d.ts +0 -30
- package/dist/observability/analytics.js +0 -118
- package/dist/pipe/anthropic-client.d.ts +0 -9
- package/dist/pipe/anthropic-client.js +0 -50
- package/dist/pipe/brain.d.ts +0 -16
- package/dist/pipe/brain.js +0 -89
- package/dist/pipe/memory-store.d.ts +0 -8
- package/dist/pipe/memory-store.js +0 -39
- package/dist/pipe/project-scanner.d.ts +0 -6
- package/dist/pipe/project-scanner.js +0 -26
- package/dist/pipe/types.d.ts +0 -34
- package/dist/pipe/types.js +0 -1
- package/dist/projects/index.d.ts +0 -4
- package/dist/projects/index.js +0 -2
- package/dist/projects/manager.d.ts +0 -13
- package/dist/projects/manager.js +0 -166
- package/dist/projects/router.d.ts +0 -17
- package/dist/projects/router.js +0 -246
- package/dist/projects/types.d.ts +0 -19
- package/dist/projects/types.js +0 -1
- package/dist/service/install.d.ts +0 -4
- package/dist/service/install.js +0 -146
- package/dist/service/templates.d.ts +0 -4
- package/dist/service/templates.js +0 -52
- package/dist/service/windows.d.ts +0 -8
- package/dist/service/windows.js +0 -59
- package/dist/session/budget-guard.d.ts +0 -20
- package/dist/session/budget-guard.js +0 -31
- package/dist/session/circuit-breaker.d.ts +0 -19
- package/dist/session/circuit-breaker.js +0 -91
- package/dist/session/context-compactor.d.ts +0 -32
- package/dist/session/context-compactor.js +0 -45
- package/dist/session/context-monitor.d.ts +0 -15
- package/dist/session/context-monitor.js +0 -44
- package/dist/session/handoff.d.ts +0 -21
- package/dist/session/handoff.js +0 -50
- package/dist/session/manager.d.ts +0 -60
- package/dist/session/manager.js +0 -416
- package/dist/session/memory-store.d.ts +0 -29
- package/dist/session/memory-store.js +0 -45
- package/dist/session/message-queue.d.ts +0 -28
- package/dist/session/message-queue.js +0 -52
- package/dist/session/pending-dispatcher.d.ts +0 -31
- package/dist/session/pending-dispatcher.js +0 -120
- package/dist/session/pending-store.d.ts +0 -60
- package/dist/session/pending-store.js +0 -118
- package/dist/session/stale-session.d.ts +0 -31
- package/dist/session/stale-session.js +0 -45
- package/dist/session/subprocess.d.ts +0 -32
- package/dist/session/subprocess.js +0 -230
- package/dist/session/tab-store.d.ts +0 -28
- package/dist/session/tab-store.js +0 -78
- package/dist/session/tool-classifier.d.ts +0 -4
- package/dist/session/tool-classifier.js +0 -56
- package/dist/tasks/scheduler.d.ts +0 -45
- package/dist/tasks/scheduler.js +0 -329
- package/dist/tasks/store.d.ts +0 -11
- package/dist/tasks/store.js +0 -103
- package/dist/timeline/index.d.ts +0 -3
- package/dist/timeline/index.js +0 -2
- package/dist/timeline/logger.d.ts +0 -8
- package/dist/timeline/logger.js +0 -11
- package/dist/timeline/query.d.ts +0 -11
- package/dist/timeline/query.js +0 -67
- package/dist/timeline/types.d.ts +0 -12
- package/dist/timeline/types.js +0 -1
- package/dist/types.d.ts +0 -225
- package/dist/types.js +0 -3
- package/dist/users/index.d.ts +0 -2
- package/dist/users/index.js +0 -1
- package/dist/users/service.d.ts +0 -17
- package/dist/users/service.js +0 -46
- package/dist/util/auto-heal.d.ts +0 -43
- package/dist/util/auto-heal.js +0 -107
- package/dist/util/install-info.d.ts +0 -37
- package/dist/util/install-info.js +0 -80
- package/dist/util/logger.d.ts +0 -18
- package/dist/util/logger.js +0 -118
- package/dist/util/paths.d.ts +0 -13
- package/dist/util/paths.js +0 -55
- package/dist/util/platform.d.ts +0 -2
- package/dist/util/platform.js +0 -11
- package/dist/util/progress.d.ts +0 -34
- package/dist/util/progress.js +0 -133
- package/dist/util/rate-limiter.d.ts +0 -14
- package/dist/util/rate-limiter.js +0 -50
- package/dist/util/retry.d.ts +0 -2
- package/dist/util/retry.js +0 -18
- package/dist/util/text.d.ts +0 -32
- package/dist/util/text.js +0 -101
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -4
- package/dist/voice/index.d.ts +0 -17
- package/dist/voice/index.js +0 -42
- package/dist/voice/stt.d.ts +0 -16
- package/dist/voice/stt.js +0 -57
- package/dist/voice/tts.d.ts +0 -23
- package/dist/voice/tts.js +0 -84
- package/dist/watchers/evaluator.d.ts +0 -5
- package/dist/watchers/evaluator.js +0 -45
- package/dist/watchers/index.d.ts +0 -4
- package/dist/watchers/index.js +0 -3
- package/dist/watchers/scheduler.d.ts +0 -20
- package/dist/watchers/scheduler.js +0 -183
- package/dist/watchers/store.d.ts +0 -10
- package/dist/watchers/store.js +0 -56
- package/dist/watchers/types.d.ts +0 -15
- package/dist/watchers/types.js +0 -1
- package/scripts/postinstall.mjs +0 -50
package/dist/pipe/brain.js
DELETED
|
@@ -1,89 +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
|
-
constructor(config, tabManager) {
|
|
12
|
-
this.config = config;
|
|
13
|
-
this.tabManager = tabManager;
|
|
14
|
-
this.client = new PipeAnthropicClient(config.pipe.anthropicApiKey, config.pipe.routingModel);
|
|
15
|
-
this.memory = new PipeMemoryStore();
|
|
16
|
-
}
|
|
17
|
-
/** Main entry point: route a message to the right tab and send it to Claude. */
|
|
18
|
-
async process(message, _context) {
|
|
19
|
-
const decisions = [];
|
|
20
|
-
// Step 1: Route the message to the right tab/project
|
|
21
|
-
const route = await this.route(message, decisions);
|
|
22
|
-
// Step 2: Ensure the tab exists with the right working directory
|
|
23
|
-
if (route.projectPath) {
|
|
24
|
-
this.tabManager.ensureTab(route.tabName, route.projectPath);
|
|
25
|
-
this.memory.updateProjectLastUsed(route.projectPath);
|
|
26
|
-
}
|
|
27
|
-
// Step 3: Send to Claude Code
|
|
28
|
-
let result;
|
|
29
|
-
try {
|
|
30
|
-
result = await this.tabManager.sendMessage(route.tabName, message, { projectPath: route.projectPath ?? undefined });
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
return {
|
|
34
|
-
tabName: route.tabName,
|
|
35
|
-
response: { text: `Error: ${err instanceof Error ? err.message : err}`, error: true, costUsd: 0, durationMs: 0 },
|
|
36
|
-
decisions,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
tabName: route.tabName,
|
|
41
|
-
response: { text: result.text, error: result.error, costUsd: result.costUsd, durationMs: result.durationMs },
|
|
42
|
-
decisions,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/** Route a message to the right project/tab */
|
|
46
|
-
async route(message, decisions) {
|
|
47
|
-
// Check for manual /tab override first
|
|
48
|
-
if (message.startsWith('/tab ')) {
|
|
49
|
-
const parsed = parseTabMessage(message);
|
|
50
|
-
if (parsed.tabName !== 'default') {
|
|
51
|
-
decisions.push(`📌 Manual routing to "${parsed.tabName}"`);
|
|
52
|
-
return { tabName: parsed.tabName, projectPath: null, confidence: 1.0, reason: 'Manual override', needsConfirmation: false };
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const projects = this.memory.getProjects();
|
|
56
|
-
const recentRouting = this.memory.getRecentRouting(5);
|
|
57
|
-
// If no projects and no API key, just use default
|
|
58
|
-
if (projects.length === 0 || !this.config.pipe.anthropicApiKey) {
|
|
59
|
-
decisions.push('📍 Routing to default tab (no projects discovered)');
|
|
60
|
-
return { tabName: 'default', projectPath: null, confidence: 1.0, reason: 'No projects', needsConfirmation: false };
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const route = await this.client.route(message, projects, recentRouting);
|
|
64
|
-
// Record the routing decision
|
|
65
|
-
this.memory.recordRouting(message, route.tabName, route.projectPath, route.confidence);
|
|
66
|
-
if (route.confidence >= this.config.pipe.confidenceThreshold) {
|
|
67
|
-
decisions.push(`🧠 Routing to "${route.tabName}" (${Math.round(route.confidence * 100)}% confidence) — ${route.reason}`);
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
decisions.push(`🤔 Low confidence routing to "${route.tabName}" (${Math.round(route.confidence * 100)}%) — ${route.reason}. Using default.`);
|
|
71
|
-
route.tabName = 'default';
|
|
72
|
-
}
|
|
73
|
-
return route;
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
logger.error('Pipe routing failed, using default:', err);
|
|
77
|
-
decisions.push('⚠️ Routing failed, using default tab');
|
|
78
|
-
return { tabName: 'default', projectPath: null, confidence: 0.5, reason: 'Routing error', needsConfirmation: false };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/** Discover projects on the filesystem */
|
|
82
|
-
async discoverProjects() {
|
|
83
|
-
const projects = scanForProjects(this.config.pipe.projectScanPaths);
|
|
84
|
-
for (const project of projects) {
|
|
85
|
-
this.memory.upsertProject(project);
|
|
86
|
-
}
|
|
87
|
-
return projects.length;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Project } 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
|
-
}
|
|
@@ -1,39 +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
|
-
}
|
|
@@ -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
|
-
}
|
package/dist/pipe/types.d.ts
DELETED
|
@@ -1,34 +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 ChatContext {
|
|
21
|
-
chatId: number;
|
|
22
|
-
userId: number;
|
|
23
|
-
messageId: number;
|
|
24
|
-
}
|
|
25
|
-
export interface PipeResult {
|
|
26
|
-
tabName: string;
|
|
27
|
-
response: {
|
|
28
|
-
text: string;
|
|
29
|
-
error: boolean;
|
|
30
|
-
costUsd: number;
|
|
31
|
-
durationMs: number;
|
|
32
|
-
};
|
|
33
|
-
decisions: string[];
|
|
34
|
-
}
|
package/dist/pipe/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/projects/index.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export type { Project, RouteDecision, RoutingContext } from './types.js';
|
|
2
|
-
export { discoverProjects, createProject, listProjects, getProject, ensureCategory, touchProject, } from './manager.js';
|
|
3
|
-
export { routeMessage, setUserContext, resolveProjectRoute } from './router.js';
|
|
4
|
-
export type { RouteResult } from './router.js';
|
package/dist/projects/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Project } from './types.js';
|
|
2
|
-
/** Discover projects in scan paths (look for git repos, package.json, etc.) */
|
|
3
|
-
export declare function discoverProjects(scanPaths?: string[]): Project[];
|
|
4
|
-
/** Create a new project. parentDir must resolve under an allowed root. */
|
|
5
|
-
export declare function createProject(name: string, parentDir?: string): Project;
|
|
6
|
-
/** Ensure a managed category exists (lazy creation) */
|
|
7
|
-
export declare function ensureCategory(name: string): Project;
|
|
8
|
-
/** List all projects */
|
|
9
|
-
export declare function listProjects(): Project[];
|
|
10
|
-
/** Get a project by name */
|
|
11
|
-
export declare function getProject(name: string): Project | null;
|
|
12
|
-
/** Update last used timestamp */
|
|
13
|
-
export declare function touchProject(name: string): void;
|
package/dist/projects/manager.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
-
import { getDb } from '../db/index.js';
|
|
5
|
-
import { getConfig } from '../config.js';
|
|
6
|
-
import { expandHome } from '../util/paths.js';
|
|
7
|
-
import { logger } from '../util/logger.js';
|
|
8
|
-
import { invalidateProjectCache } from './router.js';
|
|
9
|
-
function rowToProject(r) {
|
|
10
|
-
return {
|
|
11
|
-
id: r.id,
|
|
12
|
-
name: r.name,
|
|
13
|
-
path: r.path,
|
|
14
|
-
type: r.type,
|
|
15
|
-
lastUsedAt: r.last_used_at,
|
|
16
|
-
createdAt: r.created_at,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/** Get the workspace root from config (module-local) */
|
|
20
|
-
function getWorkspaceRoot() {
|
|
21
|
-
const config = getConfig();
|
|
22
|
-
// Use the default tab's workingDir as workspace root
|
|
23
|
-
const root = config.tabs?.default?.workingDir || process.env.HOME || '';
|
|
24
|
-
return expandHome(root);
|
|
25
|
-
}
|
|
26
|
-
/** Get the managed workspace path (.beecork/ under workspace root) (module-local) */
|
|
27
|
-
function getManagedWorkspace() {
|
|
28
|
-
return path.join(getWorkspaceRoot(), '.beecork');
|
|
29
|
-
}
|
|
30
|
-
/** Discover projects in scan paths (look for git repos, package.json, etc.) */
|
|
31
|
-
export function discoverProjects(scanPaths) {
|
|
32
|
-
const paths = scanPaths || [getWorkspaceRoot()];
|
|
33
|
-
const projects = [];
|
|
34
|
-
const db = getDb();
|
|
35
|
-
for (let scanPath of paths) {
|
|
36
|
-
scanPath = expandHome(scanPath);
|
|
37
|
-
if (!fs.existsSync(scanPath))
|
|
38
|
-
continue;
|
|
39
|
-
try {
|
|
40
|
-
const entries = fs.readdirSync(scanPath, { withFileTypes: true });
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
if (!entry.isDirectory())
|
|
43
|
-
continue;
|
|
44
|
-
if (entry.name.startsWith('.'))
|
|
45
|
-
continue; // Skip hidden dirs
|
|
46
|
-
if (entry.name === 'node_modules')
|
|
47
|
-
continue;
|
|
48
|
-
const dirPath = path.join(scanPath, entry.name);
|
|
49
|
-
// Check if it looks like a project (has .git, package.json, or similar)
|
|
50
|
-
const isProject = fs.existsSync(path.join(dirPath, '.git')) ||
|
|
51
|
-
fs.existsSync(path.join(dirPath, 'package.json')) ||
|
|
52
|
-
fs.existsSync(path.join(dirPath, 'Cargo.toml')) ||
|
|
53
|
-
fs.existsSync(path.join(dirPath, 'go.mod')) ||
|
|
54
|
-
fs.existsSync(path.join(dirPath, 'requirements.txt')) ||
|
|
55
|
-
fs.existsSync(path.join(dirPath, 'pyproject.toml')) ||
|
|
56
|
-
fs.existsSync(path.join(dirPath, 'CLAUDE.md'));
|
|
57
|
-
if (isProject) {
|
|
58
|
-
projects.push({
|
|
59
|
-
id: uuidv4(),
|
|
60
|
-
name: entry.name,
|
|
61
|
-
path: dirPath,
|
|
62
|
-
type: 'user-project',
|
|
63
|
-
lastUsedAt: new Date().toISOString(),
|
|
64
|
-
createdAt: new Date().toISOString(),
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch (err) {
|
|
70
|
-
logger.warn(`Failed to scan ${scanPath}:`, err);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// Upsert into database
|
|
74
|
-
for (const project of projects) {
|
|
75
|
-
db.prepare(`
|
|
76
|
-
INSERT INTO projects (id, name, path, type) VALUES (?, ?, ?, ?)
|
|
77
|
-
ON CONFLICT(name) DO UPDATE SET path = excluded.path, last_used_at = datetime('now')
|
|
78
|
-
`).run(project.id, project.name, project.path, project.type);
|
|
79
|
-
}
|
|
80
|
-
invalidateProjectCache();
|
|
81
|
-
return projects;
|
|
82
|
-
}
|
|
83
|
-
/** Create a new project. parentDir must resolve under an allowed root. */
|
|
84
|
-
export function createProject(name, parentDir) {
|
|
85
|
-
const requestedParent = parentDir || getWorkspaceRoot();
|
|
86
|
-
const resolvedParent = path.resolve(expandHome(requestedParent));
|
|
87
|
-
// Allowlist: parent must resolve under workspace root or one of the configured scan paths.
|
|
88
|
-
const config = getConfig();
|
|
89
|
-
const allowedRoots = [getWorkspaceRoot(), ...(config.projectScanPaths ?? [])].map((r) => path.resolve(expandHome(r)));
|
|
90
|
-
const isAllowed = allowedRoots.some((root) => resolvedParent === root || resolvedParent.startsWith(root + path.sep));
|
|
91
|
-
if (!isAllowed) {
|
|
92
|
-
throw new Error(`Project parent directory must be under workspace root or a configured scan path. Allowed: ${allowedRoots.join(', ')}`);
|
|
93
|
-
}
|
|
94
|
-
// Sanitize name to prevent path traversal
|
|
95
|
-
const safeName = path.basename(name.replace(/\.\./g, ''));
|
|
96
|
-
if (!safeName)
|
|
97
|
-
throw new Error('Invalid project name');
|
|
98
|
-
const projectPath = path.resolve(resolvedParent, safeName);
|
|
99
|
-
// Ensure resolved path is within the parent directory
|
|
100
|
-
if (!projectPath.startsWith(resolvedParent)) {
|
|
101
|
-
throw new Error('Project path must be within the parent directory');
|
|
102
|
-
}
|
|
103
|
-
if (fs.existsSync(projectPath)) {
|
|
104
|
-
// Folder already exists — just register it
|
|
105
|
-
logger.info(`Project folder already exists: ${projectPath}`);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
109
|
-
logger.info(`Created project folder: ${projectPath}`);
|
|
110
|
-
}
|
|
111
|
-
const db = getDb();
|
|
112
|
-
const id = uuidv4();
|
|
113
|
-
db.prepare(`
|
|
114
|
-
INSERT INTO projects (id, name, path, type) VALUES (?, ?, ?, ?)
|
|
115
|
-
ON CONFLICT(name) DO UPDATE SET path = excluded.path
|
|
116
|
-
`).run(id, name, projectPath, 'user-project');
|
|
117
|
-
invalidateProjectCache();
|
|
118
|
-
return {
|
|
119
|
-
id,
|
|
120
|
-
name,
|
|
121
|
-
path: projectPath,
|
|
122
|
-
type: 'user-project',
|
|
123
|
-
lastUsedAt: new Date().toISOString(),
|
|
124
|
-
createdAt: new Date().toISOString(),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
/** Ensure a managed category exists (lazy creation) */
|
|
128
|
-
export function ensureCategory(name) {
|
|
129
|
-
const categoryPath = path.join(getManagedWorkspace(), name);
|
|
130
|
-
fs.mkdirSync(categoryPath, { recursive: true });
|
|
131
|
-
const db = getDb();
|
|
132
|
-
const existing = db
|
|
133
|
-
.prepare('SELECT * FROM projects WHERE name = ? AND type = ?')
|
|
134
|
-
.get(name, 'category');
|
|
135
|
-
if (existing)
|
|
136
|
-
return rowToProject(existing);
|
|
137
|
-
const id = uuidv4();
|
|
138
|
-
db.prepare('INSERT INTO projects (id, name, path, type) VALUES (?, ?, ?, ?)').run(id, name, categoryPath, 'category');
|
|
139
|
-
invalidateProjectCache();
|
|
140
|
-
return {
|
|
141
|
-
id,
|
|
142
|
-
name,
|
|
143
|
-
path: categoryPath,
|
|
144
|
-
type: 'category',
|
|
145
|
-
lastUsedAt: new Date().toISOString(),
|
|
146
|
-
createdAt: new Date().toISOString(),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
/** List all projects */
|
|
150
|
-
export function listProjects() {
|
|
151
|
-
const db = getDb();
|
|
152
|
-
const rows = db
|
|
153
|
-
.prepare('SELECT * FROM projects ORDER BY type, last_used_at DESC')
|
|
154
|
-
.all();
|
|
155
|
-
return rows.map(rowToProject);
|
|
156
|
-
}
|
|
157
|
-
/** Get a project by name */
|
|
158
|
-
export function getProject(name) {
|
|
159
|
-
const db = getDb();
|
|
160
|
-
const row = db.prepare('SELECT * FROM projects WHERE name = ?').get(name);
|
|
161
|
-
return row ? rowToProject(row) : null;
|
|
162
|
-
}
|
|
163
|
-
/** Update last used timestamp */
|
|
164
|
-
export function touchProject(name) {
|
|
165
|
-
getDb().prepare("UPDATE projects SET last_used_at = datetime('now') WHERE name = ?").run(name);
|
|
166
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { RouteDecision, RoutingContext } from './types.js';
|
|
2
|
-
/** Invalidate the project cache. Call after createProject/discoverProjects/etc. */
|
|
3
|
-
export declare function invalidateProjectCache(): void;
|
|
4
|
-
/** Route a message to the right project and tab */
|
|
5
|
-
export declare function routeMessage(message: string, context?: RoutingContext): RouteDecision;
|
|
6
|
-
/** Update current context for a user */
|
|
7
|
-
export declare function setUserContext(userId: string, projectName: string, tabName: string): void;
|
|
8
|
-
export interface RouteResult {
|
|
9
|
-
effectiveTabName: string;
|
|
10
|
-
projectPath?: string;
|
|
11
|
-
confirmationMessage?: string;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Shared project routing logic — resolves which tab/project to use for a message.
|
|
15
|
-
* Pulled out of channels/ so all routing decisions live in one place.
|
|
16
|
-
*/
|
|
17
|
-
export declare function resolveProjectRoute(rawPrompt: string, tabName: string, text: string, userId: string): Promise<RouteResult>;
|
package/dist/projects/router.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { getDb } from '../db/index.js';
|
|
2
|
-
import { listProjects, getProject, ensureCategory, touchProject } from './manager.js';
|
|
3
|
-
// Category keywords for non-project routing
|
|
4
|
-
const CATEGORY_KEYWORDS = {
|
|
5
|
-
research: ['research', 'find out', 'look into', 'compare', 'investigate', 'analyze', 'study'],
|
|
6
|
-
planning: ['plan', 'schedule', 'organize', 'roadmap', 'strategy', 'outline', 'agenda'],
|
|
7
|
-
};
|
|
8
|
-
// Per-user current context tracking (in-memory, resets on daemon restart)
|
|
9
|
-
const userContext = new Map();
|
|
10
|
-
// Project list cache. listProjects() ran on every inbound message and re-did a
|
|
11
|
-
// full SELECT + ORDER BY across the projects table; with N projects each call
|
|
12
|
-
// then re-lowercased every name. The cache is invalidated explicitly via
|
|
13
|
-
// invalidateProjectCache() at the few sites that mutate the table.
|
|
14
|
-
let projectsCache = null;
|
|
15
|
-
const PROJECTS_CACHE_TTL_MS = 30_000;
|
|
16
|
-
function getUserProjectsCached() {
|
|
17
|
-
const now = Date.now();
|
|
18
|
-
if (projectsCache && projectsCache.expiresAt > now)
|
|
19
|
-
return projectsCache.user;
|
|
20
|
-
const user = listProjects()
|
|
21
|
-
.filter((p) => p.type === 'user-project')
|
|
22
|
-
.map((p) => ({ ...p, nameLower: p.name.toLowerCase() }));
|
|
23
|
-
projectsCache = { user, expiresAt: now + PROJECTS_CACHE_TTL_MS };
|
|
24
|
-
return user;
|
|
25
|
-
}
|
|
26
|
-
/** Invalidate the project cache. Call after createProject/discoverProjects/etc. */
|
|
27
|
-
export function invalidateProjectCache() {
|
|
28
|
-
projectsCache = null;
|
|
29
|
-
}
|
|
30
|
-
/** Route a message to the right project and tab */
|
|
31
|
-
export function routeMessage(message, context) {
|
|
32
|
-
const projects = getUserProjectsCached();
|
|
33
|
-
const userId = context?.userId || 'default';
|
|
34
|
-
const messageLower = message.toLowerCase();
|
|
35
|
-
// 1. Check for explicit project mention by name
|
|
36
|
-
const mentionedProject = findMentionedProject(messageLower, projects);
|
|
37
|
-
if (mentionedProject) {
|
|
38
|
-
const { tabName, exists } = resolveTabInProject(mentionedProject);
|
|
39
|
-
touchProject(mentionedProject.name);
|
|
40
|
-
recordRouting(message, mentionedProject.name);
|
|
41
|
-
return {
|
|
42
|
-
project: mentionedProject,
|
|
43
|
-
tabName,
|
|
44
|
-
isNewTab: !exists,
|
|
45
|
-
confidence: 0.95,
|
|
46
|
-
needsConfirmation: false,
|
|
47
|
-
reason: `Message mentions project "${mentionedProject.name}"`,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
// 2. Check if continuing current context
|
|
51
|
-
const currentCtx = userContext.get(userId);
|
|
52
|
-
if (currentCtx && Date.now() - currentCtx.updatedAt < 10 * 60 * 1000) {
|
|
53
|
-
// 10 min window
|
|
54
|
-
const project = getProject(currentCtx.projectName);
|
|
55
|
-
if (project) {
|
|
56
|
-
touchProject(project.name);
|
|
57
|
-
return {
|
|
58
|
-
project,
|
|
59
|
-
tabName: currentCtx.tabName,
|
|
60
|
-
isNewTab: false,
|
|
61
|
-
confidence: 0.7,
|
|
62
|
-
needsConfirmation: false,
|
|
63
|
-
reason: `Continuing in "${project.name}" (recent context)`,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// 3. Check routing preferences (learned patterns)
|
|
68
|
-
const learned = checkLearnedRouting(messageLower);
|
|
69
|
-
if (learned) {
|
|
70
|
-
const project = getProject(learned.projectName);
|
|
71
|
-
if (project && learned.confidence >= 0.9) {
|
|
72
|
-
const { tabName, exists } = resolveTabInProject(project);
|
|
73
|
-
touchProject(project.name);
|
|
74
|
-
return {
|
|
75
|
-
project,
|
|
76
|
-
tabName,
|
|
77
|
-
isNewTab: !exists,
|
|
78
|
-
confidence: learned.confidence,
|
|
79
|
-
needsConfirmation: false,
|
|
80
|
-
reason: `Learned pattern → "${project.name}"`,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
// 4. Multiple projects could match — ask user
|
|
85
|
-
const possibleMatches = findPossibleMatches(messageLower, projects);
|
|
86
|
-
if (possibleMatches.length > 1) {
|
|
87
|
-
return {
|
|
88
|
-
project: possibleMatches[0],
|
|
89
|
-
tabName: possibleMatches[0].name,
|
|
90
|
-
isNewTab: false,
|
|
91
|
-
confidence: 0.5,
|
|
92
|
-
needsConfirmation: true,
|
|
93
|
-
reason: `Multiple projects could match: ${possibleMatches.map((p) => p.name).join(', ')}`,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
// 5. Check for category keywords (non-project)
|
|
97
|
-
const category = detectCategory(messageLower);
|
|
98
|
-
if (category) {
|
|
99
|
-
const project = ensureCategory(category);
|
|
100
|
-
const tabName = category;
|
|
101
|
-
return {
|
|
102
|
-
project,
|
|
103
|
-
tabName,
|
|
104
|
-
isNewTab: !tabExists(tabName),
|
|
105
|
-
confidence: 0.8,
|
|
106
|
-
needsConfirmation: false,
|
|
107
|
-
reason: `Detected category: ${category}`,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
// 6. Default to general
|
|
111
|
-
const general = ensureCategory('general');
|
|
112
|
-
return {
|
|
113
|
-
project: general,
|
|
114
|
-
tabName: 'general',
|
|
115
|
-
isNewTab: !tabExists('general'),
|
|
116
|
-
confidence: 0.6,
|
|
117
|
-
needsConfirmation: false,
|
|
118
|
-
reason: 'Default routing to general',
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
/** Update current context for a user */
|
|
122
|
-
export function setUserContext(userId, projectName, tabName) {
|
|
123
|
-
userContext.set(userId, { projectName, tabName, updatedAt: Date.now() });
|
|
124
|
-
// Evict oldest entry when map grows too large
|
|
125
|
-
if (userContext.size > 1000) {
|
|
126
|
-
let oldestKey = '';
|
|
127
|
-
let oldestTime = Infinity;
|
|
128
|
-
for (const [key, val] of userContext) {
|
|
129
|
-
if (val.updatedAt < oldestTime) {
|
|
130
|
-
oldestTime = val.updatedAt;
|
|
131
|
-
oldestKey = key;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (oldestKey)
|
|
135
|
-
userContext.delete(oldestKey);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
/** Find a project explicitly mentioned by name in the message (messageLower already lower-cased) */
|
|
139
|
-
function findMentionedProject(messageLower, projects) {
|
|
140
|
-
for (const project of projects) {
|
|
141
|
-
if (messageLower.includes(project.nameLower))
|
|
142
|
-
return project;
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
/** Find possible project matches (for ambiguity) */
|
|
147
|
-
function findPossibleMatches(messageLower, projects) {
|
|
148
|
-
const words = messageLower.split(/\s+/);
|
|
149
|
-
return projects.filter((p) => {
|
|
150
|
-
const nameParts = p.nameLower.split(/[-_]/);
|
|
151
|
-
return nameParts.some((part) => words.includes(part));
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Resolve which tab to use within a project. Coalesces the previous
|
|
156
|
-
* "find tabs + tabExists" into one query — the caller used to fire both
|
|
157
|
-
* back-to-back, and the working_dir scan had no covering index.
|
|
158
|
-
* (Index added by migration v28; see src/db/migrations.ts.)
|
|
159
|
-
*/
|
|
160
|
-
function resolveTabInProject(project) {
|
|
161
|
-
const db = getDb();
|
|
162
|
-
const row = db
|
|
163
|
-
.prepare('SELECT name FROM tabs WHERE working_dir = ? ORDER BY last_activity_at DESC LIMIT 1')
|
|
164
|
-
.get(project.path);
|
|
165
|
-
return row ? { tabName: row.name, exists: true } : { tabName: project.name, exists: false };
|
|
166
|
-
}
|
|
167
|
-
/** Check if a tab exists in the database */
|
|
168
|
-
function tabExists(tabName) {
|
|
169
|
-
const db = getDb();
|
|
170
|
-
const row = db.prepare('SELECT 1 FROM tabs WHERE name = ?').get(tabName);
|
|
171
|
-
return !!row;
|
|
172
|
-
}
|
|
173
|
-
/** Detect a category from keywords (messageLower already lower-cased) */
|
|
174
|
-
function detectCategory(messageLower) {
|
|
175
|
-
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
|
|
176
|
-
if (keywords.some((kw) => messageLower.includes(kw)))
|
|
177
|
-
return category;
|
|
178
|
-
}
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
/** Record a successful routing for learning */
|
|
182
|
-
function recordRouting(message, projectName) {
|
|
183
|
-
const db = getDb();
|
|
184
|
-
// Extract key words from message for pattern matching
|
|
185
|
-
const words = message
|
|
186
|
-
.toLowerCase()
|
|
187
|
-
.split(/\s+/)
|
|
188
|
-
.filter((w) => w.length > 3)
|
|
189
|
-
.slice(0, 5);
|
|
190
|
-
const pattern = words.join(' ');
|
|
191
|
-
if (!pattern)
|
|
192
|
-
return;
|
|
193
|
-
db.prepare(`
|
|
194
|
-
INSERT INTO routing_preferences (pattern, project_name)
|
|
195
|
-
VALUES (?, ?)
|
|
196
|
-
ON CONFLICT(pattern, project_name) DO UPDATE SET hit_count = hit_count + 1
|
|
197
|
-
`).run(pattern, projectName);
|
|
198
|
-
}
|
|
199
|
-
/** Check learned routing preferences (messageLower already lower-cased) */
|
|
200
|
-
function checkLearnedRouting(messageLower) {
|
|
201
|
-
const db = getDb();
|
|
202
|
-
const words = messageLower.split(/\s+/).filter((w) => w.length > 3);
|
|
203
|
-
if (words.length === 0)
|
|
204
|
-
return null;
|
|
205
|
-
// Fetch all preferences in a single query and match in JS
|
|
206
|
-
const allPrefs = db
|
|
207
|
-
.prepare('SELECT pattern, project_name, confidence, hit_count FROM routing_preferences WHERE hit_count >= 3 ORDER BY hit_count DESC LIMIT 200')
|
|
208
|
-
.all();
|
|
209
|
-
for (const pref of allPrefs) {
|
|
210
|
-
// pref.pattern was written via .toLowerCase() in recordRouting(), so no
|
|
211
|
-
// re-lowercase here (saves N allocations per inbound message).
|
|
212
|
-
const patternLower = pref.pattern;
|
|
213
|
-
if (words.some((word) => patternLower.includes(word) || word.includes(patternLower))) {
|
|
214
|
-
return { projectName: pref.project_name, confidence: Math.min(pref.confidence, 0.9) };
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Shared project routing logic — resolves which tab/project to use for a message.
|
|
221
|
-
* Pulled out of channels/ so all routing decisions live in one place.
|
|
222
|
-
*/
|
|
223
|
-
export async function resolveProjectRoute(rawPrompt, tabName, text, userId) {
|
|
224
|
-
if (tabName !== 'default' || text.startsWith('/tab ')) {
|
|
225
|
-
return { effectiveTabName: tabName };
|
|
226
|
-
}
|
|
227
|
-
try {
|
|
228
|
-
const decision = routeMessage(rawPrompt, { userId });
|
|
229
|
-
if (decision.needsConfirmation) {
|
|
230
|
-
const projects = listProjects().filter((p) => p.type === 'user-project');
|
|
231
|
-
const options = projects.map((p, i) => `${i + 1}) ${p.name}`).join('\n');
|
|
232
|
-
return {
|
|
233
|
-
effectiveTabName: tabName,
|
|
234
|
-
confirmationMessage: `Which project?\n${options}\n\nReply with the number, or just send your message with /project <name> first.`,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
setUserContext(userId, decision.project.name, decision.tabName);
|
|
238
|
-
return {
|
|
239
|
-
effectiveTabName: decision.tabName,
|
|
240
|
-
projectPath: decision.project.path,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
return { effectiveTabName: tabName };
|
|
245
|
-
}
|
|
246
|
-
}
|