overarch-linux-arm64 0.8.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/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/bin/overarch.d.ts +2 -0
- package/dist/bin/overarch.js +178 -0
- package/dist/src/agent/context.d.ts +14 -0
- package/dist/src/agent/context.js +200 -0
- package/dist/src/agent/context_compressor.d.ts +21 -0
- package/dist/src/agent/context_compressor.js +90 -0
- package/dist/src/agent/manifest.d.ts +22 -0
- package/dist/src/agent/manifest.js +131 -0
- package/dist/src/agent/planner.d.ts +38 -0
- package/dist/src/agent/planner.js +118 -0
- package/dist/src/agent/runtime.d.ts +59 -0
- package/dist/src/agent/runtime.js +400 -0
- package/dist/src/cli/approval.d.ts +13 -0
- package/dist/src/cli/approval.js +71 -0
- package/dist/src/cli/args.d.ts +15 -0
- package/dist/src/cli/args.js +47 -0
- package/dist/src/cli/commands/audit.d.ts +10 -0
- package/dist/src/cli/commands/audit.js +79 -0
- package/dist/src/cli/commands/companion.d.ts +1 -0
- package/dist/src/cli/commands/companion.js +143 -0
- package/dist/src/cli/commands/config.d.ts +1 -0
- package/dist/src/cli/commands/config.js +135 -0
- package/dist/src/cli/commands/doctor.d.ts +1 -0
- package/dist/src/cli/commands/doctor.js +217 -0
- package/dist/src/cli/commands/init.d.ts +1 -0
- package/dist/src/cli/commands/init.js +134 -0
- package/dist/src/cli/commands/memory.d.ts +2 -0
- package/dist/src/cli/commands/memory.js +98 -0
- package/dist/src/cli/commands/onboarding.d.ts +3 -0
- package/dist/src/cli/commands/onboarding.js +223 -0
- package/dist/src/cli/commands/sessions.d.ts +1 -0
- package/dist/src/cli/commands/sessions.js +22 -0
- package/dist/src/cli/commands/status.d.ts +1 -0
- package/dist/src/cli/commands/status.js +37 -0
- package/dist/src/cli/commands/update.d.ts +9 -0
- package/dist/src/cli/commands/update.js +105 -0
- package/dist/src/cli/repl.d.ts +3 -0
- package/dist/src/cli/repl.js +821 -0
- package/dist/src/cli/ui.d.ts +53 -0
- package/dist/src/cli/ui.js +215 -0
- package/dist/src/companion/petdex_adapter.d.ts +31 -0
- package/dist/src/companion/petdex_adapter.js +171 -0
- package/dist/src/companion/petdex_bridge.d.ts +65 -0
- package/dist/src/companion/petdex_bridge.js +356 -0
- package/dist/src/companion/types.d.ts +57 -0
- package/dist/src/companion/types.js +11 -0
- package/dist/src/config/index.d.ts +24 -0
- package/dist/src/config/index.js +237 -0
- package/dist/src/config/types.d.ts +58 -0
- package/dist/src/config/types.js +7 -0
- package/dist/src/core/diagnostics.d.ts +48 -0
- package/dist/src/core/diagnostics.js +115 -0
- package/dist/src/core/index.d.ts +23 -0
- package/dist/src/core/index.js +23 -0
- package/dist/src/core/mission.d.ts +55 -0
- package/dist/src/core/mission.js +173 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/learning/memory_manager.d.ts +26 -0
- package/dist/src/learning/memory_manager.js +211 -0
- package/dist/src/learning/types.d.ts +54 -0
- package/dist/src/learning/types.js +1 -0
- package/dist/src/mcp/registry.d.ts +17 -0
- package/dist/src/mcp/registry.js +97 -0
- package/dist/src/mcp/types.d.ts +13 -0
- package/dist/src/mcp/types.js +1 -0
- package/dist/src/providers/anthropic.d.ts +10 -0
- package/dist/src/providers/anthropic.js +95 -0
- package/dist/src/providers/base.d.ts +15 -0
- package/dist/src/providers/base.js +108 -0
- package/dist/src/providers/factory.d.ts +28 -0
- package/dist/src/providers/factory.js +142 -0
- package/dist/src/providers/gemini.d.ts +10 -0
- package/dist/src/providers/gemini.js +97 -0
- package/dist/src/providers/openai.d.ts +9 -0
- package/dist/src/providers/openai.js +85 -0
- package/dist/src/providers/openrouter.d.ts +5 -0
- package/dist/src/providers/openrouter.js +14 -0
- package/dist/src/providers/registry.d.ts +19 -0
- package/dist/src/providers/registry.js +209 -0
- package/dist/src/providers/types.d.ts +59 -0
- package/dist/src/providers/types.js +1 -0
- package/dist/src/providers/validator.d.ts +24 -0
- package/dist/src/providers/validator.js +161 -0
- package/dist/src/security/audit.d.ts +56 -0
- package/dist/src/security/audit.js +154 -0
- package/dist/src/security/guardrails.d.ts +9 -0
- package/dist/src/security/guardrails.js +32 -0
- package/dist/src/security/permissions.d.ts +12 -0
- package/dist/src/security/permissions.js +68 -0
- package/dist/src/security/policy.d.ts +220 -0
- package/dist/src/security/policy.js +720 -0
- package/dist/src/session/manager.d.ts +22 -0
- package/dist/src/session/manager.js +172 -0
- package/dist/src/session/types.d.ts +36 -0
- package/dist/src/session/types.js +1 -0
- package/dist/src/tools/checkpoint.d.ts +26 -0
- package/dist/src/tools/checkpoint.js +160 -0
- package/dist/src/tools/diagnostics.d.ts +2 -0
- package/dist/src/tools/diagnostics.js +125 -0
- package/dist/src/tools/diff.d.ts +14 -0
- package/dist/src/tools/diff.js +124 -0
- package/dist/src/tools/filesystem.d.ts +2 -0
- package/dist/src/tools/filesystem.js +224 -0
- package/dist/src/tools/git.d.ts +2 -0
- package/dist/src/tools/git.js +138 -0
- package/dist/src/tools/markdown.d.ts +33 -0
- package/dist/src/tools/markdown.js +190 -0
- package/dist/src/tools/project.d.ts +2 -0
- package/dist/src/tools/project.js +63 -0
- package/dist/src/tools/registry.d.ts +14 -0
- package/dist/src/tools/registry.js +230 -0
- package/dist/src/tools/search.d.ts +2 -0
- package/dist/src/tools/search.js +136 -0
- package/dist/src/tools/shell.d.ts +2 -0
- package/dist/src/tools/shell.js +74 -0
- package/dist/src/tools/symbols.d.ts +30 -0
- package/dist/src/tools/symbols.js +155 -0
- package/dist/src/tools/types.d.ts +28 -0
- package/dist/src/tools/types.js +1 -0
- package/dist/src/tools/web_search.d.ts +14 -0
- package/dist/src/tools/web_search.js +154 -0
- package/install.sh +29 -0
- package/overarch +15 -0
- package/package.json +28 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
export class MemoryManager {
|
|
5
|
+
projectMemoryDir;
|
|
6
|
+
globalMemoryDir;
|
|
7
|
+
cwd;
|
|
8
|
+
constructor(cwd = process.cwd()) {
|
|
9
|
+
this.cwd = cwd;
|
|
10
|
+
this.projectMemoryDir = path.join(cwd, '.overarch', 'memory');
|
|
11
|
+
this.globalMemoryDir = path.join(os.homedir(), '.overarch');
|
|
12
|
+
}
|
|
13
|
+
async ensureDirs() {
|
|
14
|
+
await fs.mkdir(this.projectMemoryDir, { recursive: true });
|
|
15
|
+
await fs.mkdir(this.globalMemoryDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
// ── Project Memory ──
|
|
18
|
+
async getProjectMemory() {
|
|
19
|
+
await this.ensureDirs();
|
|
20
|
+
const filePath = path.join(this.projectMemoryDir, 'project.json');
|
|
21
|
+
try {
|
|
22
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
23
|
+
return JSON.parse(data);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {
|
|
27
|
+
conventions: [],
|
|
28
|
+
importantFiles: [],
|
|
29
|
+
frameworks: [],
|
|
30
|
+
buildCommands: [],
|
|
31
|
+
testCommands: [],
|
|
32
|
+
deploymentPatterns: [],
|
|
33
|
+
recurringErrors: [],
|
|
34
|
+
decisions: []
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async saveProjectMemory(memory) {
|
|
39
|
+
await this.ensureDirs();
|
|
40
|
+
const filePath = path.join(this.projectMemoryDir, 'project.json');
|
|
41
|
+
await fs.writeFile(filePath, JSON.stringify(memory, null, 2), 'utf8');
|
|
42
|
+
}
|
|
43
|
+
// ── User Memory ──
|
|
44
|
+
async getUserMemory() {
|
|
45
|
+
await this.ensureDirs();
|
|
46
|
+
const filePath = path.join(this.globalMemoryDir, 'user_memory.json');
|
|
47
|
+
try {
|
|
48
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
49
|
+
return JSON.parse(data);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return {
|
|
53
|
+
codingPreferences: [],
|
|
54
|
+
preferredWorkflows: [],
|
|
55
|
+
conventions: [],
|
|
56
|
+
frequentlyUsedCommands: [],
|
|
57
|
+
communicationPreferences: []
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async saveUserMemory(memory) {
|
|
62
|
+
await this.ensureDirs();
|
|
63
|
+
const filePath = path.join(this.globalMemoryDir, 'user_memory.json');
|
|
64
|
+
await fs.writeFile(filePath, JSON.stringify(memory, null, 2), 'utf8');
|
|
65
|
+
}
|
|
66
|
+
// ── Lessons (Episodic Memory) ──
|
|
67
|
+
async getLessons(scope = 'all') {
|
|
68
|
+
await this.ensureDirs();
|
|
69
|
+
const lessons = [];
|
|
70
|
+
// Project lessons
|
|
71
|
+
if (scope === 'all' || scope === 'project') {
|
|
72
|
+
try {
|
|
73
|
+
const pFile = path.join(this.projectMemoryDir, 'lessons.json');
|
|
74
|
+
const data = await fs.readFile(pFile, 'utf8');
|
|
75
|
+
const list = JSON.parse(data);
|
|
76
|
+
lessons.push(...list.map(l => ({ ...l, scope: 'project' })));
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
// Global lessons
|
|
81
|
+
if (scope === 'all' || scope === 'global') {
|
|
82
|
+
try {
|
|
83
|
+
const gFile = path.join(this.globalMemoryDir, 'lessons.json');
|
|
84
|
+
const data = await fs.readFile(gFile, 'utf8');
|
|
85
|
+
const list = JSON.parse(data);
|
|
86
|
+
lessons.push(...list.map(l => ({ ...l, scope: 'global' })));
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
}
|
|
90
|
+
return lessons.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
91
|
+
}
|
|
92
|
+
async recordLesson(lesson) {
|
|
93
|
+
await this.ensureDirs();
|
|
94
|
+
const id = `les_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
95
|
+
const fullLesson = {
|
|
96
|
+
id,
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
sourceCwd: this.cwd,
|
|
99
|
+
...lesson
|
|
100
|
+
};
|
|
101
|
+
const targetFile = fullLesson.scope === 'global'
|
|
102
|
+
? path.join(this.globalMemoryDir, 'lessons.json')
|
|
103
|
+
: path.join(this.projectMemoryDir, 'lessons.json');
|
|
104
|
+
let existing = [];
|
|
105
|
+
try {
|
|
106
|
+
const data = await fs.readFile(targetFile, 'utf8');
|
|
107
|
+
existing = JSON.parse(data);
|
|
108
|
+
}
|
|
109
|
+
catch { }
|
|
110
|
+
existing.unshift(fullLesson);
|
|
111
|
+
await fs.writeFile(targetFile, JSON.stringify(existing, null, 2), 'utf8');
|
|
112
|
+
return fullLesson;
|
|
113
|
+
}
|
|
114
|
+
async learnQuick(lessonText, tags = []) {
|
|
115
|
+
return this.recordLesson({
|
|
116
|
+
problem: 'Direct user guidance / insight',
|
|
117
|
+
investigation: 'User specified engineering instruction or lesson',
|
|
118
|
+
resolution: 'Saved to persistent episodic memory',
|
|
119
|
+
lesson: lessonText,
|
|
120
|
+
tags: tags.length > 0 ? tags : ['user-taught', 'best-practice'],
|
|
121
|
+
scope: 'project'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// ── Search & Selective Retrieval ──
|
|
125
|
+
async search(query) {
|
|
126
|
+
const q = query.toLowerCase().trim();
|
|
127
|
+
const words = q.split(/\s+/).filter(w => w.length > 2);
|
|
128
|
+
const allLessons = await this.getLessons('all');
|
|
129
|
+
const projectMem = await this.getProjectMemory();
|
|
130
|
+
const matchedLessons = allLessons.filter(l => {
|
|
131
|
+
const text = `${l.problem} ${l.investigation} ${l.resolution} ${l.lesson} ${l.tags.join(' ')}`.toLowerCase();
|
|
132
|
+
return text.includes(q) || words.some(w => text.includes(w));
|
|
133
|
+
});
|
|
134
|
+
const matchedConventions = projectMem.conventions.filter(c => c.toLowerCase().includes(q) || words.some(w => c.toLowerCase().includes(w)));
|
|
135
|
+
const matchedDecisions = projectMem.decisions.filter(d => `${d.decision} ${d.rationale}`.toLowerCase().includes(q) || words.some(w => `${d.decision} ${d.rationale}`.toLowerCase().includes(w)));
|
|
136
|
+
return {
|
|
137
|
+
lessons: matchedLessons,
|
|
138
|
+
conventions: matchedConventions,
|
|
139
|
+
decisions: matchedDecisions
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Selectively retrieves relevant memory for the active task.
|
|
144
|
+
* Keeps token count bounded so full memory is never dumped blindly into prompts.
|
|
145
|
+
*/
|
|
146
|
+
async retrieveRelevantContext(taskPrompt, maxItems = 3) {
|
|
147
|
+
const searchRes = await this.search(taskPrompt);
|
|
148
|
+
const projectMem = await this.getProjectMemory();
|
|
149
|
+
const userMem = await this.getUserMemory();
|
|
150
|
+
const relevantLessons = searchRes.lessons.slice(0, maxItems);
|
|
151
|
+
const relevantConventions = projectMem.conventions.slice(0, 3);
|
|
152
|
+
const relevantUserPrefs = userMem.codingPreferences.slice(0, 3);
|
|
153
|
+
const lines = [];
|
|
154
|
+
if (projectMem.architecture) {
|
|
155
|
+
lines.push(`- Architecture: ${projectMem.architecture}`);
|
|
156
|
+
}
|
|
157
|
+
if (relevantConventions.length > 0) {
|
|
158
|
+
lines.push(`- Project Conventions: ${relevantConventions.join('; ')}`);
|
|
159
|
+
}
|
|
160
|
+
if (relevantLessons.length > 0) {
|
|
161
|
+
lines.push('- Lessons from previous tasks:');
|
|
162
|
+
relevantLessons.forEach(l => {
|
|
163
|
+
lines.push(` • ${l.lesson} (Context: ${l.problem} → ${l.resolution})`);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (relevantUserPrefs.length > 0) {
|
|
167
|
+
lines.push(`- User Preferences: ${relevantUserPrefs.join('; ')}`);
|
|
168
|
+
}
|
|
169
|
+
const formattedPromptInjection = lines.length > 0
|
|
170
|
+
? `\n<persistent_project_memory>\n${lines.join('\n')}\n</persistent_project_memory>`
|
|
171
|
+
: '';
|
|
172
|
+
const estimatedTokens = Math.ceil(formattedPromptInjection.length / 4);
|
|
173
|
+
return {
|
|
174
|
+
lessons: relevantLessons,
|
|
175
|
+
projectConventions: relevantConventions,
|
|
176
|
+
userPreferences: relevantUserPrefs,
|
|
177
|
+
architectureNotes: projectMem.architecture,
|
|
178
|
+
formattedPromptInjection,
|
|
179
|
+
estimatedTokens
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async forget(id) {
|
|
183
|
+
await this.ensureDirs();
|
|
184
|
+
let removed = false;
|
|
185
|
+
// Check project lessons
|
|
186
|
+
try {
|
|
187
|
+
const pFile = path.join(this.projectMemoryDir, 'lessons.json');
|
|
188
|
+
const data = await fs.readFile(pFile, 'utf8');
|
|
189
|
+
const list = JSON.parse(data);
|
|
190
|
+
const filtered = list.filter(l => l.id !== id);
|
|
191
|
+
if (filtered.length !== list.length) {
|
|
192
|
+
await fs.writeFile(pFile, JSON.stringify(filtered, null, 2), 'utf8');
|
|
193
|
+
removed = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch { }
|
|
197
|
+
// Check global lessons
|
|
198
|
+
try {
|
|
199
|
+
const gFile = path.join(this.globalMemoryDir, 'lessons.json');
|
|
200
|
+
const data = await fs.readFile(gFile, 'utf8');
|
|
201
|
+
const list = JSON.parse(data);
|
|
202
|
+
const filtered = list.filter(l => l.id !== id);
|
|
203
|
+
if (filtered.length !== list.length) {
|
|
204
|
+
await fs.writeFile(gFile, JSON.stringify(filtered, null, 2), 'utf8');
|
|
205
|
+
removed = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch { }
|
|
209
|
+
return removed;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface EpisodicLesson {
|
|
2
|
+
id: string;
|
|
3
|
+
timestamp: string;
|
|
4
|
+
problem: string;
|
|
5
|
+
investigation: string;
|
|
6
|
+
resolution: string;
|
|
7
|
+
lesson: string;
|
|
8
|
+
tags: string[];
|
|
9
|
+
scope: 'project' | 'global';
|
|
10
|
+
sourceCwd?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ProjectMemory {
|
|
13
|
+
architecture?: string;
|
|
14
|
+
conventions: string[];
|
|
15
|
+
importantFiles: string[];
|
|
16
|
+
frameworks: string[];
|
|
17
|
+
buildCommands: string[];
|
|
18
|
+
testCommands: string[];
|
|
19
|
+
deploymentPatterns: string[];
|
|
20
|
+
recurringErrors: string[];
|
|
21
|
+
decisions: Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
decision: string;
|
|
24
|
+
date: string;
|
|
25
|
+
rationale: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
export interface UserMemory {
|
|
29
|
+
codingPreferences: string[];
|
|
30
|
+
preferredWorkflows: string[];
|
|
31
|
+
conventions: string[];
|
|
32
|
+
frequentlyUsedCommands: string[];
|
|
33
|
+
communicationPreferences: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface SessionMemoryRecord {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
task: string;
|
|
38
|
+
plan: string[];
|
|
39
|
+
completedSteps: string[];
|
|
40
|
+
failedAttempts: string[];
|
|
41
|
+
discoveries: string[];
|
|
42
|
+
modifiedFiles: string[];
|
|
43
|
+
startedAt: string;
|
|
44
|
+
updatedAt: string;
|
|
45
|
+
status: 'active' | 'completed' | 'interrupted' | 'failed';
|
|
46
|
+
}
|
|
47
|
+
export interface RelevantMemoryContext {
|
|
48
|
+
lessons: EpisodicLesson[];
|
|
49
|
+
projectConventions: string[];
|
|
50
|
+
userPreferences: string[];
|
|
51
|
+
architectureNotes?: string;
|
|
52
|
+
formattedPromptInjection: string;
|
|
53
|
+
estimatedTokens: number;
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Tool } from '../tools/types.js';
|
|
2
|
+
import { McpServerConfig, McpToolAdapter } from './types.js';
|
|
3
|
+
export declare class McpRegistry {
|
|
4
|
+
private servers;
|
|
5
|
+
private tools;
|
|
6
|
+
constructor();
|
|
7
|
+
registerServer(config: McpServerConfig): void;
|
|
8
|
+
listServers(): McpServerConfig[];
|
|
9
|
+
registerTool(adapter: McpToolAdapter): void;
|
|
10
|
+
listTools(): McpToolAdapter[];
|
|
11
|
+
toOverarchTools(): Tool[];
|
|
12
|
+
/**
|
|
13
|
+
* Register default built-in MCP tool adapters for developer services
|
|
14
|
+
*/
|
|
15
|
+
private registerBuiltinAdapters;
|
|
16
|
+
}
|
|
17
|
+
export declare const mcpRegistry: McpRegistry;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { PermissionTier } from '../config/types.js';
|
|
2
|
+
export class McpRegistry {
|
|
3
|
+
servers = new Map();
|
|
4
|
+
tools = new Map();
|
|
5
|
+
constructor() {
|
|
6
|
+
this.registerBuiltinAdapters();
|
|
7
|
+
}
|
|
8
|
+
registerServer(config) {
|
|
9
|
+
this.servers.set(config.name, config);
|
|
10
|
+
}
|
|
11
|
+
listServers() {
|
|
12
|
+
return Array.from(this.servers.values());
|
|
13
|
+
}
|
|
14
|
+
registerTool(adapter) {
|
|
15
|
+
this.tools.set(adapter.definition.name, adapter);
|
|
16
|
+
}
|
|
17
|
+
listTools() {
|
|
18
|
+
return Array.from(this.tools.values());
|
|
19
|
+
}
|
|
20
|
+
toOverarchTools() {
|
|
21
|
+
return Array.from(this.tools.values()).map(adapter => ({
|
|
22
|
+
definition: adapter.definition,
|
|
23
|
+
handler: adapter.handler
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Register default built-in MCP tool adapters for developer services
|
|
28
|
+
*/
|
|
29
|
+
registerBuiltinAdapters() {
|
|
30
|
+
// 1. GitHub Integration Tool
|
|
31
|
+
this.registerTool({
|
|
32
|
+
serverName: 'github',
|
|
33
|
+
definition: {
|
|
34
|
+
name: 'mcp_github_query',
|
|
35
|
+
description: 'Query GitHub issues, pull requests, or commits via MCP',
|
|
36
|
+
tier: PermissionTier.ReadOnly,
|
|
37
|
+
parameters: [
|
|
38
|
+
{ name: 'action', type: 'string', description: 'Action: list_issues, list_prs, view_pr', required: true, enum: ['list_issues', 'list_prs', 'view_pr'] },
|
|
39
|
+
{ name: 'repo', type: 'string', description: 'Repository e.g. owner/repo', required: false },
|
|
40
|
+
{ name: 'id', type: 'number', description: 'Issue or PR number', required: false }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
handler: async (params) => {
|
|
44
|
+
const ghToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
45
|
+
if (!ghToken) {
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
output: `[MCP GitHub] Query simulated (Set GITHUB_TOKEN to connect live): action=${params.action} repo=${params.repo || 'local'}`
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
success: true,
|
|
53
|
+
output: `[MCP GitHub] Queried ${params.action} on ${params.repo || 'local repository'}`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// 2. Database Explorer Tool
|
|
58
|
+
this.registerTool({
|
|
59
|
+
serverName: 'database',
|
|
60
|
+
definition: {
|
|
61
|
+
name: 'mcp_db_inspect',
|
|
62
|
+
description: 'Inspect database tables and schema via MCP connector',
|
|
63
|
+
tier: PermissionTier.ReadOnly,
|
|
64
|
+
parameters: [
|
|
65
|
+
{ name: 'connectionString', type: 'string', description: 'Database connection or path', required: false },
|
|
66
|
+
{ name: 'table', type: 'string', description: 'Table name to inspect', required: false }
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
handler: async (params) => {
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
output: `[MCP Database] Schema inspection: table=${params.table || 'all'} connected via MCP adapter.`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// 3. Issue Tracker Tool
|
|
77
|
+
this.registerTool({
|
|
78
|
+
serverName: 'issue_tracker',
|
|
79
|
+
definition: {
|
|
80
|
+
name: 'mcp_issue_tracker',
|
|
81
|
+
description: 'Read or update project issues and task tickets via MCP',
|
|
82
|
+
tier: PermissionTier.SafeMutation,
|
|
83
|
+
parameters: [
|
|
84
|
+
{ name: 'action', type: 'string', description: 'Action: get_ticket, list_tickets, update_ticket', required: true, enum: ['get_ticket', 'list_tickets', 'update_ticket'] },
|
|
85
|
+
{ name: 'ticketId', type: 'string', description: 'Ticket ID e.g. OVR-101', required: false }
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
handler: async (params) => {
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
output: `[MCP Issue Tracker] Action ${params.action} processed for ticket ${params.ticketId || 'all'}.`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export const mcpRegistry = new McpRegistry();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ToolDefinition, ToolHandler } from '../tools/types.js';
|
|
2
|
+
export interface McpServerConfig {
|
|
3
|
+
name: string;
|
|
4
|
+
command?: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface McpToolAdapter {
|
|
10
|
+
serverName: string;
|
|
11
|
+
definition: ToolDefinition;
|
|
12
|
+
handler: ToolHandler;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseProvider } from './base.js';
|
|
2
|
+
import { CompletionRequest, CompletionResponse, StreamChunk } from './types.js';
|
|
3
|
+
export declare class AnthropicProvider extends BaseProvider {
|
|
4
|
+
constructor(apiKey: string, baseUrl?: string);
|
|
5
|
+
protected headers(): Record<string, string>;
|
|
6
|
+
protected getEndpoint(request: CompletionRequest): string;
|
|
7
|
+
protected buildRequestBody(request: CompletionRequest): Record<string, unknown>;
|
|
8
|
+
protected parseResponse(body: any): CompletionResponse;
|
|
9
|
+
protected parseStreamChunk(dataStr: string, buffer: Record<string, unknown>): StreamChunk | null;
|
|
10
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { BaseProvider } from './base.js';
|
|
2
|
+
export class AnthropicProvider extends BaseProvider {
|
|
3
|
+
constructor(apiKey, baseUrl = 'https://api.anthropic.com/v1') {
|
|
4
|
+
super('anthropic', apiKey, baseUrl);
|
|
5
|
+
}
|
|
6
|
+
headers() {
|
|
7
|
+
return {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
'x-api-key': this.apiKey,
|
|
10
|
+
'anthropic-version': '2023-06-01'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
getEndpoint(request) {
|
|
14
|
+
return `${this.baseUrl.replace(/\/$/, '')}/messages`;
|
|
15
|
+
}
|
|
16
|
+
buildRequestBody(request) {
|
|
17
|
+
const systemMessages = request.messages.filter(m => m.role === 'system').map(m => m.content);
|
|
18
|
+
const system = systemMessages.length > 0 ? systemMessages.join('\n') : undefined;
|
|
19
|
+
const messages = request.messages
|
|
20
|
+
.filter(m => m.role !== 'system')
|
|
21
|
+
.map(m => ({
|
|
22
|
+
role: m.role === 'tool' ? 'user' : (m.role === 'assistant' ? 'assistant' : 'user'),
|
|
23
|
+
content: m.content
|
|
24
|
+
}));
|
|
25
|
+
const tools = request.tools?.map(t => ({
|
|
26
|
+
name: t.function.name,
|
|
27
|
+
description: t.function.description,
|
|
28
|
+
input_schema: t.function.parameters
|
|
29
|
+
}));
|
|
30
|
+
return {
|
|
31
|
+
model: request.model,
|
|
32
|
+
messages,
|
|
33
|
+
...(system ? { system } : {}),
|
|
34
|
+
max_tokens: request.maxTokens || 4096,
|
|
35
|
+
...(tools && tools.length > 0 ? { tools } : {}),
|
|
36
|
+
...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
|
|
37
|
+
stream: request.stream
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
parseResponse(body) {
|
|
41
|
+
const contentBlocks = body.content || [];
|
|
42
|
+
let content = '';
|
|
43
|
+
const toolCalls = [];
|
|
44
|
+
for (const block of contentBlocks) {
|
|
45
|
+
if (block.type === 'text') {
|
|
46
|
+
content += block.text;
|
|
47
|
+
}
|
|
48
|
+
else if (block.type === 'tool_use') {
|
|
49
|
+
toolCalls.push({
|
|
50
|
+
id: block.id,
|
|
51
|
+
name: block.name,
|
|
52
|
+
arguments: block.input
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
content,
|
|
58
|
+
toolCalls,
|
|
59
|
+
usage: body.usage ? {
|
|
60
|
+
promptTokens: body.usage.input_tokens,
|
|
61
|
+
completionTokens: body.usage.output_tokens,
|
|
62
|
+
totalTokens: body.usage.input_tokens + body.usage.output_tokens
|
|
63
|
+
} : undefined,
|
|
64
|
+
finishReason: body.stop_reason === 'tool_use' ? 'tool_calls' : (body.stop_reason === 'end_turn' ? 'stop' : 'stop')
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
parseStreamChunk(dataStr, buffer) {
|
|
68
|
+
try {
|
|
69
|
+
const data = JSON.parse(dataStr);
|
|
70
|
+
if (data.type === 'content_block_start') {
|
|
71
|
+
if (data.content_block.type === 'tool_use') {
|
|
72
|
+
return {
|
|
73
|
+
type: 'tool_call_start',
|
|
74
|
+
toolCall: { id: data.content_block.id, name: data.content_block.name }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (data.type === 'content_block_delta') {
|
|
79
|
+
if (data.delta.type === 'text_delta') {
|
|
80
|
+
return { type: 'text', content: data.delta.text };
|
|
81
|
+
}
|
|
82
|
+
else if (data.delta.type === 'input_json_delta') {
|
|
83
|
+
return { type: 'tool_call_args', toolCall: { arguments: data.delta.partial_json } };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (data.type === 'message_stop') {
|
|
87
|
+
return { type: 'done' };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CompletionRequest, CompletionResponse, StreamChunk, LLMProvider } from './types.js';
|
|
2
|
+
export declare abstract class BaseProvider implements LLMProvider {
|
|
3
|
+
name: string;
|
|
4
|
+
protected apiKey: string;
|
|
5
|
+
protected baseUrl: string;
|
|
6
|
+
constructor(name: string, apiKey: string, baseUrl: string);
|
|
7
|
+
protected headers(): Record<string, string>;
|
|
8
|
+
protected abstract buildRequestBody(request: CompletionRequest): Record<string, unknown>;
|
|
9
|
+
protected abstract parseResponse(body: unknown): CompletionResponse;
|
|
10
|
+
protected abstract parseStreamChunk(line: string, buffer: Record<string, unknown>): StreamChunk | null;
|
|
11
|
+
private fetchWithRetry;
|
|
12
|
+
complete(request: CompletionRequest): Promise<CompletionResponse>;
|
|
13
|
+
stream(request: CompletionRequest): AsyncIterable<StreamChunk>;
|
|
14
|
+
protected getEndpoint(request: CompletionRequest): string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export class BaseProvider {
|
|
2
|
+
name;
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(name, apiKey, baseUrl) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.apiKey = apiKey;
|
|
8
|
+
this.baseUrl = baseUrl;
|
|
9
|
+
}
|
|
10
|
+
headers() {
|
|
11
|
+
return {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async fetchWithRetry(url, options, maxRetries = 3) {
|
|
17
|
+
let delay = 1000;
|
|
18
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(url, options);
|
|
21
|
+
if (response.ok)
|
|
22
|
+
return response;
|
|
23
|
+
// Retryable HTTP status codes (rate limits, server errors)
|
|
24
|
+
const isRetryable = response.status === 429 || response.status >= 500;
|
|
25
|
+
if (!isRetryable || attempt === maxRetries) {
|
|
26
|
+
const text = await response.text();
|
|
27
|
+
let errorMessage = text;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(text);
|
|
30
|
+
errorMessage = parsed.error?.message || parsed.message || text;
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
throw new Error(`[${this.name}] API Error ${response.status}: ${errorMessage}`);
|
|
34
|
+
}
|
|
35
|
+
// Wait with jitter before retrying
|
|
36
|
+
const jitter = Math.floor(Math.random() * 200);
|
|
37
|
+
await new Promise(r => setTimeout(r, delay + jitter));
|
|
38
|
+
delay *= 2;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
if (attempt === maxRetries || !err.message.includes('API Error')) {
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
await new Promise(r => setTimeout(r, delay));
|
|
45
|
+
delay *= 2;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`[${this.name}] Max retries (${maxRetries}) exceeded`);
|
|
49
|
+
}
|
|
50
|
+
async complete(request) {
|
|
51
|
+
const url = this.getEndpoint(request);
|
|
52
|
+
const body = this.buildRequestBody(request);
|
|
53
|
+
const response = await this.fetchWithRetry(url, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: this.headers(),
|
|
56
|
+
body: JSON.stringify(body)
|
|
57
|
+
});
|
|
58
|
+
const data = await response.json();
|
|
59
|
+
return this.parseResponse(data);
|
|
60
|
+
}
|
|
61
|
+
async *stream(request) {
|
|
62
|
+
request.stream = true;
|
|
63
|
+
const url = this.getEndpoint(request);
|
|
64
|
+
const body = this.buildRequestBody(request);
|
|
65
|
+
const response = await this.fetchWithRetry(url, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: this.headers(),
|
|
68
|
+
body: JSON.stringify(body)
|
|
69
|
+
});
|
|
70
|
+
if (!response.body) {
|
|
71
|
+
throw new Error(`[${this.name}] Expected a stream in the response`);
|
|
72
|
+
}
|
|
73
|
+
const reader = response.body.getReader();
|
|
74
|
+
const decoder = new TextDecoder('utf-8');
|
|
75
|
+
let bufferStr = '';
|
|
76
|
+
const buffer = {};
|
|
77
|
+
try {
|
|
78
|
+
while (true) {
|
|
79
|
+
const { done, value } = await reader.read();
|
|
80
|
+
if (done)
|
|
81
|
+
break;
|
|
82
|
+
bufferStr += decoder.decode(value, { stream: true });
|
|
83
|
+
const lines = bufferStr.split('\n');
|
|
84
|
+
bufferStr = lines.pop() || '';
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const trimmed = line.trim();
|
|
87
|
+
if (!trimmed || !trimmed.startsWith('data: '))
|
|
88
|
+
continue;
|
|
89
|
+
const dataStr = trimmed.slice(6).trim();
|
|
90
|
+
if (dataStr === '[DONE]') {
|
|
91
|
+
yield { type: 'done' };
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const chunk = this.parseStreamChunk(dataStr, buffer);
|
|
95
|
+
if (chunk) {
|
|
96
|
+
yield chunk;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
reader.releaseLock();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
getEndpoint(request) {
|
|
106
|
+
return this.baseUrl;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LLMProvider, ModelInfo, CompletionRequest, CompletionResponse, StreamChunk } from './types.js';
|
|
2
|
+
import { CANONICAL_MODELS, SUPPORTED_PROVIDERS, ProviderDefinition, normalizeModel } from './registry.js';
|
|
3
|
+
export { CANONICAL_MODELS, SUPPORTED_PROVIDERS, ProviderDefinition, normalizeModel };
|
|
4
|
+
import { ProviderConfig } from '../config/types.js';
|
|
5
|
+
export declare const MODEL_REGISTRY: ModelInfo[];
|
|
6
|
+
export declare class OfflineProvider implements LLMProvider {
|
|
7
|
+
name: string;
|
|
8
|
+
complete(request: CompletionRequest): Promise<CompletionResponse>;
|
|
9
|
+
stream(request: CompletionRequest): AsyncIterable<StreamChunk>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createProvider(providerName: string, config?: ProviderConfig): LLMProvider;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a raw model string, alias, or provider/model into a canonical structure.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveModel(modelId: string, preferredProvider?: string): {
|
|
16
|
+
provider: string;
|
|
17
|
+
model: string;
|
|
18
|
+
displayName: string;
|
|
19
|
+
} | undefined;
|
|
20
|
+
export declare function listModels(providerFilter?: string): ModelInfo[];
|
|
21
|
+
export declare function listProviders(): ProviderDefinition[];
|
|
22
|
+
/**
|
|
23
|
+
* Validates whether an API key is structurally and functionally plausible.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateProviderKey(providerName: string, apiKey: string): Promise<{
|
|
26
|
+
valid: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
}>;
|