@stackmemoryai/stackmemory 1.0.1 → 1.2.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/dist/src/cli/commands/audit.js +134 -0
- package/dist/src/cli/commands/bench.js +252 -0
- package/dist/src/cli/commands/dashboard.js +2 -1
- package/dist/src/cli/commands/stats.js +118 -0
- package/dist/src/cli/index.js +6 -0
- package/dist/src/core/config/feature-flags.js +7 -1
- package/dist/src/core/context/enhanced-rehydration.js +24 -5
- package/dist/src/core/extensions/cerebras-adapter.js +28 -0
- package/dist/src/core/extensions/deepinfra-adapter.js +32 -0
- package/dist/src/core/extensions/provider-adapter.js +33 -240
- package/dist/src/core/models/complexity-scorer.js +154 -0
- package/dist/src/core/models/model-router.js +230 -36
- package/dist/src/core/models/provider-pricing.js +63 -0
- package/dist/src/core/models/sensitive-guard.js +112 -0
- package/dist/src/core/monitoring/feedback-loops.js +88 -0
- package/dist/src/hooks/schemas.js +12 -1
- package/dist/src/integrations/anthropic/batch-client.js +256 -0
- package/dist/src/integrations/anthropic/client.js +87 -72
- package/dist/src/integrations/claude-code/subagent-client.js +133 -12
- package/dist/src/integrations/graphiti/client.js +16 -4
- package/dist/src/integrations/mcp/handlers/index.js +25 -1
- package/dist/src/integrations/mcp/handlers/provider-handlers.js +227 -0
- package/dist/src/integrations/mcp/server.js +207 -1
- package/dist/src/integrations/mcp/tool-definitions.js +38 -1
- package/dist/src/orchestrators/multimodal/baselines.js +128 -0
- package/dist/src/orchestrators/multimodal/constants.js +9 -1
- package/dist/src/orchestrators/multimodal/harness.js +86 -6
- package/dist/src/orchestrators/multimodal/providers.js +113 -2
- package/dist/src/skills/recursive-agent-orchestrator.js +15 -10
- package/dist/src/utils/fuzzy-edit.js +162 -0
- package/package.json +5 -1
|
@@ -32,12 +32,19 @@ class GraphitiClient {
|
|
|
32
32
|
const res = await fetch(`${this.endpoint}${path}`, {
|
|
33
33
|
...options,
|
|
34
34
|
signal: controller.signal,
|
|
35
|
-
headers: {
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...options.headers || {}
|
|
38
|
+
}
|
|
36
39
|
});
|
|
37
40
|
clearTimeout(timeoutId);
|
|
38
41
|
if (!res.ok) {
|
|
39
42
|
const msg = await res.text().catch(() => res.statusText);
|
|
40
|
-
throw new GraphitiClientError(
|
|
43
|
+
throw new GraphitiClientError(
|
|
44
|
+
`Request failed: ${msg}`,
|
|
45
|
+
"HTTP_ERROR",
|
|
46
|
+
res.status
|
|
47
|
+
);
|
|
41
48
|
}
|
|
42
49
|
return await res.json();
|
|
43
50
|
} catch (err) {
|
|
@@ -53,7 +60,10 @@ class GraphitiClient {
|
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
clearTimeout(timeoutId);
|
|
56
|
-
throw new GraphitiClientError(
|
|
63
|
+
throw new GraphitiClientError(
|
|
64
|
+
lastError?.message || "Network error",
|
|
65
|
+
"NETWORK_ERROR"
|
|
66
|
+
);
|
|
57
67
|
}
|
|
58
68
|
// Episodes
|
|
59
69
|
async upsertEpisode(episode) {
|
|
@@ -91,7 +101,9 @@ class GraphitiClient {
|
|
|
91
101
|
// Health/status
|
|
92
102
|
async getStatus() {
|
|
93
103
|
try {
|
|
94
|
-
return await this.request(
|
|
104
|
+
return await this.request(
|
|
105
|
+
`/status?namespace=${encodeURIComponent(this.namespace)}`
|
|
106
|
+
);
|
|
95
107
|
} catch {
|
|
96
108
|
return { connected: false };
|
|
97
109
|
}
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
DiscoveryHandlers
|
|
17
17
|
} from "./discovery-handlers.js";
|
|
18
|
+
import {
|
|
19
|
+
ProviderHandlers
|
|
20
|
+
} from "./provider-handlers.js";
|
|
18
21
|
import {
|
|
19
22
|
ContextHandlers as ContextHandlers2
|
|
20
23
|
} from "./context-handlers.js";
|
|
@@ -23,11 +26,13 @@ import {
|
|
|
23
26
|
LinearHandlers as LinearHandlers2
|
|
24
27
|
} from "./linear-handlers.js";
|
|
25
28
|
import { TraceHandlers as TraceHandlers2 } from "./trace-handlers.js";
|
|
29
|
+
import { ProviderHandlers as ProviderHandlers2 } from "./provider-handlers.js";
|
|
26
30
|
class MCPHandlerFactory {
|
|
27
31
|
contextHandlers;
|
|
28
32
|
taskHandlers;
|
|
29
33
|
linearHandlers;
|
|
30
34
|
traceHandlers;
|
|
35
|
+
providerHandlers;
|
|
31
36
|
constructor(deps) {
|
|
32
37
|
this.contextHandlers = new ContextHandlers2({
|
|
33
38
|
frameManager: deps.frameManager
|
|
@@ -45,6 +50,7 @@ class MCPHandlerFactory {
|
|
|
45
50
|
traceDetector: deps.traceDetector,
|
|
46
51
|
browserMCP: deps.browserMCP
|
|
47
52
|
});
|
|
53
|
+
this.providerHandlers = new ProviderHandlers2();
|
|
48
54
|
}
|
|
49
55
|
/**
|
|
50
56
|
* Get handler for a specific tool
|
|
@@ -111,6 +117,19 @@ class MCPHandlerFactory {
|
|
|
111
117
|
return this.traceHandlers.handleStopBrowserDebug.bind(
|
|
112
118
|
this.traceHandlers
|
|
113
119
|
);
|
|
120
|
+
// Provider handlers
|
|
121
|
+
case "delegate_to_model":
|
|
122
|
+
return this.providerHandlers.handleDelegateToModel.bind(
|
|
123
|
+
this.providerHandlers
|
|
124
|
+
);
|
|
125
|
+
case "batch_submit":
|
|
126
|
+
return this.providerHandlers.handleBatchSubmit.bind(
|
|
127
|
+
this.providerHandlers
|
|
128
|
+
);
|
|
129
|
+
case "batch_check":
|
|
130
|
+
return this.providerHandlers.handleBatchCheck.bind(
|
|
131
|
+
this.providerHandlers
|
|
132
|
+
);
|
|
114
133
|
default:
|
|
115
134
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
116
135
|
}
|
|
@@ -144,7 +163,11 @@ class MCPHandlerFactory {
|
|
|
144
163
|
"start_browser_debug",
|
|
145
164
|
"take_screenshot",
|
|
146
165
|
"execute_script",
|
|
147
|
-
"stop_browser_debug"
|
|
166
|
+
"stop_browser_debug",
|
|
167
|
+
// Provider tools (conditionally active)
|
|
168
|
+
"delegate_to_model",
|
|
169
|
+
"batch_submit",
|
|
170
|
+
"batch_check"
|
|
148
171
|
];
|
|
149
172
|
}
|
|
150
173
|
/**
|
|
@@ -159,6 +182,7 @@ export {
|
|
|
159
182
|
DiscoveryHandlers,
|
|
160
183
|
LinearHandlers,
|
|
161
184
|
MCPHandlerFactory,
|
|
185
|
+
ProviderHandlers,
|
|
162
186
|
TaskHandlers,
|
|
163
187
|
TraceHandlers
|
|
164
188
|
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { logger } from "../../../core/monitoring/logger.js";
|
|
6
|
+
import { isFeatureEnabled } from "../../../core/config/feature-flags.js";
|
|
7
|
+
import {
|
|
8
|
+
createProvider
|
|
9
|
+
} from "../../../core/extensions/provider-adapter.js";
|
|
10
|
+
import {
|
|
11
|
+
getOptimalProvider
|
|
12
|
+
} from "../../../core/models/model-router.js";
|
|
13
|
+
import { scoreComplexity } from "../../../core/models/complexity-scorer.js";
|
|
14
|
+
import {
|
|
15
|
+
AnthropicBatchClient
|
|
16
|
+
} from "../../anthropic/batch-client.js";
|
|
17
|
+
function errorResponse(err) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(err, null, 2) }]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function classifyApiError(error, provider) {
|
|
23
|
+
const msg = error.message || String(error);
|
|
24
|
+
const status = error.status || (msg.match(/(\d{3})/)?.[1] ? parseInt(msg.match(/(\d{3})/)[1]) : void 0);
|
|
25
|
+
if (status === 429) {
|
|
26
|
+
return {
|
|
27
|
+
errorType: "rate_limit",
|
|
28
|
+
message: msg,
|
|
29
|
+
recommendation: `Rate limited by ${provider}. Retry after a delay or switch to a different provider.`,
|
|
30
|
+
provider
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (status && status >= 500) {
|
|
34
|
+
return {
|
|
35
|
+
errorType: "server_error",
|
|
36
|
+
message: msg,
|
|
37
|
+
recommendation: `${provider} returned a server error. Try a different provider or retry later.`,
|
|
38
|
+
provider
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
errorType: "api_error",
|
|
43
|
+
message: msg,
|
|
44
|
+
recommendation: `API call to ${provider} failed. Check the model name, API key, and base URL.`,
|
|
45
|
+
provider
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
class ProviderHandlers {
|
|
49
|
+
batchClient;
|
|
50
|
+
constructor(_deps) {
|
|
51
|
+
}
|
|
52
|
+
getBatchClient() {
|
|
53
|
+
if (!this.batchClient) {
|
|
54
|
+
this.batchClient = new AnthropicBatchClient();
|
|
55
|
+
}
|
|
56
|
+
return this.batchClient;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* delegate_to_model — route a prompt to a specific provider+model
|
|
60
|
+
*/
|
|
61
|
+
async handleDelegateToModel(args) {
|
|
62
|
+
if (!isFeatureEnabled("multiProvider")) {
|
|
63
|
+
return errorResponse({
|
|
64
|
+
errorType: "feature_disabled",
|
|
65
|
+
message: "Multi-provider routing is disabled.",
|
|
66
|
+
recommendation: "Set STACKMEMORY_MULTI_PROVIDER=true to enable multi-provider routing."
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const taskType = args.taskType || "default";
|
|
70
|
+
const preference = args.provider;
|
|
71
|
+
const complexity = scoreComplexity(args.prompt);
|
|
72
|
+
const optimal = getOptimalProvider(taskType, preference, {
|
|
73
|
+
task: args.prompt
|
|
74
|
+
});
|
|
75
|
+
logger.info("delegate_to_model routing", {
|
|
76
|
+
taskType,
|
|
77
|
+
complexity: complexity.tier,
|
|
78
|
+
score: complexity.score,
|
|
79
|
+
provider: optimal.provider
|
|
80
|
+
});
|
|
81
|
+
const providerModel = args.model || optimal.model;
|
|
82
|
+
const apiKey = process.env[optimal.apiKeyEnv] || "";
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
return errorResponse({
|
|
85
|
+
errorType: "missing_api_key",
|
|
86
|
+
message: `No API key found for ${optimal.provider} (env: ${optimal.apiKeyEnv})`,
|
|
87
|
+
recommendation: `Set the ${optimal.apiKeyEnv} environment variable or choose a different provider.`,
|
|
88
|
+
provider: optimal.provider
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const adapter = createProvider(optimal.provider, {
|
|
93
|
+
apiKey,
|
|
94
|
+
baseUrl: optimal.baseUrl
|
|
95
|
+
});
|
|
96
|
+
const messages = [{ role: "user", content: args.prompt }];
|
|
97
|
+
const result = await adapter.complete(messages, {
|
|
98
|
+
model: providerModel,
|
|
99
|
+
maxTokens: args.maxTokens || 4096,
|
|
100
|
+
temperature: args.temperature,
|
|
101
|
+
system: args.system
|
|
102
|
+
});
|
|
103
|
+
const text = result.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: JSON.stringify(
|
|
109
|
+
{
|
|
110
|
+
provider: optimal.provider,
|
|
111
|
+
model: providerModel,
|
|
112
|
+
response: text,
|
|
113
|
+
usage: result.usage
|
|
114
|
+
},
|
|
115
|
+
null,
|
|
116
|
+
2
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
} catch (error) {
|
|
122
|
+
logger.error("delegate_to_model failed", { error: error.message });
|
|
123
|
+
return errorResponse(classifyApiError(error, optimal.provider));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* batch_submit — submit prompts to Anthropic Batch API
|
|
128
|
+
*/
|
|
129
|
+
async handleBatchSubmit(args) {
|
|
130
|
+
if (!isFeatureEnabled("multiProvider")) {
|
|
131
|
+
return errorResponse({
|
|
132
|
+
errorType: "feature_disabled",
|
|
133
|
+
message: "Multi-provider routing is disabled.",
|
|
134
|
+
recommendation: "Set STACKMEMORY_MULTI_PROVIDER=true to enable multi-provider routing."
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const batchClient = this.getBatchClient();
|
|
139
|
+
const requests = args.prompts.map((p) => ({
|
|
140
|
+
custom_id: p.id,
|
|
141
|
+
params: {
|
|
142
|
+
model: p.model || "claude-sonnet-4-5-20250929",
|
|
143
|
+
max_tokens: p.maxTokens || 4096,
|
|
144
|
+
messages: [{ role: "user", content: p.prompt }],
|
|
145
|
+
system: p.system
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
const batchId = await batchClient.submit(requests, args.description);
|
|
149
|
+
return {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: JSON.stringify(
|
|
154
|
+
{
|
|
155
|
+
batchId,
|
|
156
|
+
status: "submitted",
|
|
157
|
+
requestCount: requests.length
|
|
158
|
+
},
|
|
159
|
+
null,
|
|
160
|
+
2
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
};
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return errorResponse({
|
|
167
|
+
errorType: "batch_error",
|
|
168
|
+
message: error.message,
|
|
169
|
+
recommendation: "Check ANTHROPIC_API_KEY and batch request format."
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* batch_check — poll status / retrieve results
|
|
175
|
+
*/
|
|
176
|
+
async handleBatchCheck(args) {
|
|
177
|
+
if (!isFeatureEnabled("multiProvider")) {
|
|
178
|
+
return errorResponse({
|
|
179
|
+
errorType: "feature_disabled",
|
|
180
|
+
message: "Multi-provider routing is disabled.",
|
|
181
|
+
recommendation: "Set STACKMEMORY_MULTI_PROVIDER=true to enable multi-provider routing."
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const batchClient = this.getBatchClient();
|
|
186
|
+
const job = await batchClient.poll(args.batchId);
|
|
187
|
+
if (args.retrieve && job.processing_status === "ended") {
|
|
188
|
+
const results = await batchClient.retrieve(args.batchId);
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: JSON.stringify({ job, results }, null, 2)
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: JSON.stringify(
|
|
203
|
+
{
|
|
204
|
+
batchId: args.batchId,
|
|
205
|
+
status: job.processing_status,
|
|
206
|
+
counts: job.request_counts,
|
|
207
|
+
createdAt: job.created_at,
|
|
208
|
+
endedAt: job.ended_at
|
|
209
|
+
},
|
|
210
|
+
null,
|
|
211
|
+
2
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return errorResponse({
|
|
218
|
+
errorType: "batch_error",
|
|
219
|
+
message: error.message,
|
|
220
|
+
recommendation: "Check the batchId is valid and ANTHROPIC_API_KEY is set."
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
export {
|
|
226
|
+
ProviderHandlers
|
|
227
|
+
};
|
|
@@ -30,6 +30,7 @@ import { TraceDetector } from "../../core/trace/trace-detector.js";
|
|
|
30
30
|
import { LLMContextRetrieval } from "../../core/retrieval/index.js";
|
|
31
31
|
import { DiscoveryHandlers } from "./handlers/discovery-handlers.js";
|
|
32
32
|
import { DiffMemHandlers } from "./handlers/diffmem-handlers.js";
|
|
33
|
+
import { fuzzyEdit } from "../../utils/fuzzy-edit.js";
|
|
33
34
|
import { v4 as uuidv4 } from "uuid";
|
|
34
35
|
import {
|
|
35
36
|
DEFAULT_PLANNER_MODEL,
|
|
@@ -62,6 +63,7 @@ class LocalStackMemoryMCP {
|
|
|
62
63
|
contextRetrieval;
|
|
63
64
|
discoveryHandlers;
|
|
64
65
|
diffMemHandlers;
|
|
66
|
+
providerHandlers = null;
|
|
65
67
|
pendingPlans = /* @__PURE__ */ new Map();
|
|
66
68
|
constructor() {
|
|
67
69
|
this.projectRoot = this.findProjectRoot();
|
|
@@ -91,6 +93,18 @@ class LocalStackMemoryMCP {
|
|
|
91
93
|
influence_score REAL,
|
|
92
94
|
timestamp INTEGER DEFAULT (unixepoch())
|
|
93
95
|
);
|
|
96
|
+
|
|
97
|
+
CREATE TABLE IF NOT EXISTS edit_telemetry (
|
|
98
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
99
|
+
timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
100
|
+
session_id TEXT,
|
|
101
|
+
tool_name TEXT NOT NULL,
|
|
102
|
+
file_path TEXT,
|
|
103
|
+
success INTEGER NOT NULL DEFAULT 1,
|
|
104
|
+
error_type TEXT,
|
|
105
|
+
error_message TEXT
|
|
106
|
+
);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_edit_telemetry_ts ON edit_telemetry(timestamp);
|
|
94
108
|
`);
|
|
95
109
|
this.frameManager = new FrameManager(this.db, this.projectId);
|
|
96
110
|
this.initLinearIfEnabled();
|
|
@@ -122,6 +136,7 @@ class LocalStackMemoryMCP {
|
|
|
122
136
|
projectRoot: this.projectRoot
|
|
123
137
|
});
|
|
124
138
|
this.diffMemHandlers = new DiffMemHandlers();
|
|
139
|
+
this.initProviderHandlers();
|
|
125
140
|
this.setupHandlers();
|
|
126
141
|
this.loadInitialContext();
|
|
127
142
|
this.loadPendingPlans();
|
|
@@ -243,6 +258,16 @@ ${summary}...`, 0.8);
|
|
|
243
258
|
this.contexts.set(ctx.id, ctx);
|
|
244
259
|
});
|
|
245
260
|
}
|
|
261
|
+
async initProviderHandlers() {
|
|
262
|
+
if (!isFeatureEnabled("multiProvider")) return;
|
|
263
|
+
try {
|
|
264
|
+
const { ProviderHandlers } = await import("./handlers/provider-handlers.js");
|
|
265
|
+
this.providerHandlers = new ProviderHandlers();
|
|
266
|
+
logger.info("Provider handlers initialized (multiProvider enabled)");
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.warn("Failed to initialize provider handlers", { error });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
246
271
|
setupHandlers() {
|
|
247
272
|
this.server.setRequestHandler(
|
|
248
273
|
z.object({
|
|
@@ -1047,7 +1072,108 @@ ${summary}...`, 0.8);
|
|
|
1047
1072
|
type: "object",
|
|
1048
1073
|
properties: {}
|
|
1049
1074
|
}
|
|
1050
|
-
}
|
|
1075
|
+
},
|
|
1076
|
+
// Provider tools (only active when STACKMEMORY_MULTI_PROVIDER=true)
|
|
1077
|
+
...isFeatureEnabled("multiProvider") ? [
|
|
1078
|
+
{
|
|
1079
|
+
name: "delegate_to_model",
|
|
1080
|
+
description: "Route a prompt to a specific provider/model. Uses smart cost-based routing by default.",
|
|
1081
|
+
inputSchema: {
|
|
1082
|
+
type: "object",
|
|
1083
|
+
properties: {
|
|
1084
|
+
prompt: {
|
|
1085
|
+
type: "string",
|
|
1086
|
+
description: "The prompt to send"
|
|
1087
|
+
},
|
|
1088
|
+
provider: {
|
|
1089
|
+
type: "string",
|
|
1090
|
+
enum: [
|
|
1091
|
+
"anthropic",
|
|
1092
|
+
"cerebras",
|
|
1093
|
+
"deepinfra",
|
|
1094
|
+
"openai",
|
|
1095
|
+
"openrouter"
|
|
1096
|
+
],
|
|
1097
|
+
description: "Override provider (auto-routes if omitted)"
|
|
1098
|
+
},
|
|
1099
|
+
model: {
|
|
1100
|
+
type: "string",
|
|
1101
|
+
description: "Override model name"
|
|
1102
|
+
},
|
|
1103
|
+
taskType: {
|
|
1104
|
+
type: "string",
|
|
1105
|
+
enum: [
|
|
1106
|
+
"linting",
|
|
1107
|
+
"context",
|
|
1108
|
+
"code",
|
|
1109
|
+
"testing",
|
|
1110
|
+
"review",
|
|
1111
|
+
"plan"
|
|
1112
|
+
],
|
|
1113
|
+
description: "Task type for auto-routing"
|
|
1114
|
+
},
|
|
1115
|
+
maxTokens: {
|
|
1116
|
+
type: "number",
|
|
1117
|
+
description: "Max tokens"
|
|
1118
|
+
},
|
|
1119
|
+
temperature: { type: "number" },
|
|
1120
|
+
system: {
|
|
1121
|
+
type: "string",
|
|
1122
|
+
description: "System prompt"
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
required: ["prompt"]
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
name: "batch_submit",
|
|
1130
|
+
description: "Submit prompts to Anthropic Batch API (50% discount, async)",
|
|
1131
|
+
inputSchema: {
|
|
1132
|
+
type: "object",
|
|
1133
|
+
properties: {
|
|
1134
|
+
prompts: {
|
|
1135
|
+
type: "array",
|
|
1136
|
+
items: {
|
|
1137
|
+
type: "object",
|
|
1138
|
+
properties: {
|
|
1139
|
+
id: { type: "string" },
|
|
1140
|
+
prompt: { type: "string" },
|
|
1141
|
+
model: { type: "string" },
|
|
1142
|
+
maxTokens: { type: "number" },
|
|
1143
|
+
system: { type: "string" }
|
|
1144
|
+
},
|
|
1145
|
+
required: ["id", "prompt"]
|
|
1146
|
+
},
|
|
1147
|
+
description: "Array of prompts to batch"
|
|
1148
|
+
},
|
|
1149
|
+
description: {
|
|
1150
|
+
type: "string",
|
|
1151
|
+
description: "Batch job description"
|
|
1152
|
+
}
|
|
1153
|
+
},
|
|
1154
|
+
required: ["prompts"]
|
|
1155
|
+
}
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
name: "batch_check",
|
|
1159
|
+
description: "Check status or retrieve results for a batch job",
|
|
1160
|
+
inputSchema: {
|
|
1161
|
+
type: "object",
|
|
1162
|
+
properties: {
|
|
1163
|
+
batchId: {
|
|
1164
|
+
type: "string",
|
|
1165
|
+
description: "Batch job ID"
|
|
1166
|
+
},
|
|
1167
|
+
retrieve: {
|
|
1168
|
+
type: "boolean",
|
|
1169
|
+
description: "Retrieve results if complete",
|
|
1170
|
+
default: false
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1173
|
+
required: ["batchId"]
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
] : []
|
|
1051
1177
|
]
|
|
1052
1178
|
};
|
|
1053
1179
|
}
|
|
@@ -1195,6 +1321,58 @@ ${summary}...`, 0.8);
|
|
|
1195
1321
|
case "diffmem_status":
|
|
1196
1322
|
result = await this.diffMemHandlers.handleStatus();
|
|
1197
1323
|
break;
|
|
1324
|
+
case "sm_edit":
|
|
1325
|
+
result = await this.handleSmEdit(args);
|
|
1326
|
+
break;
|
|
1327
|
+
// Provider tools
|
|
1328
|
+
case "delegate_to_model":
|
|
1329
|
+
if (!this.providerHandlers) {
|
|
1330
|
+
result = {
|
|
1331
|
+
content: [
|
|
1332
|
+
{
|
|
1333
|
+
type: "text",
|
|
1334
|
+
text: "Multi-provider routing is disabled."
|
|
1335
|
+
}
|
|
1336
|
+
]
|
|
1337
|
+
};
|
|
1338
|
+
} else {
|
|
1339
|
+
result = await this.providerHandlers.handleDelegateToModel(
|
|
1340
|
+
args
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
break;
|
|
1344
|
+
case "batch_submit":
|
|
1345
|
+
if (!this.providerHandlers) {
|
|
1346
|
+
result = {
|
|
1347
|
+
content: [
|
|
1348
|
+
{
|
|
1349
|
+
type: "text",
|
|
1350
|
+
text: "Multi-provider routing is disabled."
|
|
1351
|
+
}
|
|
1352
|
+
]
|
|
1353
|
+
};
|
|
1354
|
+
} else {
|
|
1355
|
+
result = await this.providerHandlers.handleBatchSubmit(
|
|
1356
|
+
args
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
break;
|
|
1360
|
+
case "batch_check":
|
|
1361
|
+
if (!this.providerHandlers) {
|
|
1362
|
+
result = {
|
|
1363
|
+
content: [
|
|
1364
|
+
{
|
|
1365
|
+
type: "text",
|
|
1366
|
+
text: "Multi-provider routing is disabled."
|
|
1367
|
+
}
|
|
1368
|
+
]
|
|
1369
|
+
};
|
|
1370
|
+
} else {
|
|
1371
|
+
result = await this.providerHandlers.handleBatchCheck(
|
|
1372
|
+
args
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
break;
|
|
1198
1376
|
default:
|
|
1199
1377
|
throw new Error(`Unknown tool: ${name}`);
|
|
1200
1378
|
}
|
|
@@ -2639,6 +2817,34 @@ ${typeBreakdown}`
|
|
|
2639
2817
|
};
|
|
2640
2818
|
}
|
|
2641
2819
|
}
|
|
2820
|
+
async handleSmEdit(args) {
|
|
2821
|
+
const {
|
|
2822
|
+
file_path: filePath,
|
|
2823
|
+
old_string: oldString,
|
|
2824
|
+
new_string: newString,
|
|
2825
|
+
threshold = 0.85
|
|
2826
|
+
} = args;
|
|
2827
|
+
if (!filePath || !oldString || newString === void 0) {
|
|
2828
|
+
throw new Error("file_path, old_string, and new_string are required");
|
|
2829
|
+
}
|
|
2830
|
+
const { readFileSync: readFileSync2, writeFileSync: writeFileSync2 } = await import("fs");
|
|
2831
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
2832
|
+
const editResult = fuzzyEdit(content, oldString, newString, threshold);
|
|
2833
|
+
if (!editResult) {
|
|
2834
|
+
return {
|
|
2835
|
+
success: false,
|
|
2836
|
+
error: "No match found above threshold",
|
|
2837
|
+
threshold
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
writeFileSync2(filePath, editResult.content, "utf-8");
|
|
2841
|
+
return {
|
|
2842
|
+
success: true,
|
|
2843
|
+
method: editResult.match.method,
|
|
2844
|
+
confidence: editResult.match.confidence,
|
|
2845
|
+
matchedText: editResult.match.matchedText.length > 200 ? editResult.match.matchedText.slice(0, 200) + "..." : editResult.match.matchedText
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2642
2848
|
async start() {
|
|
2643
2849
|
const transport = new StdioServerTransport();
|
|
2644
2850
|
await this.server.connect(transport);
|
|
@@ -12,7 +12,8 @@ class MCPToolDefinitions {
|
|
|
12
12
|
...this.getTaskTools(),
|
|
13
13
|
...this.getLinearTools(),
|
|
14
14
|
...this.getTraceTools(),
|
|
15
|
-
...this.getDiscoveryTools()
|
|
15
|
+
...this.getDiscoveryTools(),
|
|
16
|
+
...this.getEditTools()
|
|
16
17
|
];
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
@@ -670,6 +671,40 @@ class MCPToolDefinitions {
|
|
|
670
671
|
}
|
|
671
672
|
];
|
|
672
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Edit tools (fuzzy edit fallback)
|
|
676
|
+
*/
|
|
677
|
+
getEditTools() {
|
|
678
|
+
return [
|
|
679
|
+
{
|
|
680
|
+
name: "sm_edit",
|
|
681
|
+
description: "Fuzzy file edit \u2014 fallback when Claude Code's Edit tool fails on whitespace or indentation mismatches. Uses four-tier matching: exact, whitespace-normalized, indentation-insensitive, and line-level fuzzy (Levenshtein).",
|
|
682
|
+
inputSchema: {
|
|
683
|
+
type: "object",
|
|
684
|
+
properties: {
|
|
685
|
+
file_path: {
|
|
686
|
+
type: "string",
|
|
687
|
+
description: "Absolute path to the file to edit"
|
|
688
|
+
},
|
|
689
|
+
old_string: {
|
|
690
|
+
type: "string",
|
|
691
|
+
description: "The text to find and replace"
|
|
692
|
+
},
|
|
693
|
+
new_string: {
|
|
694
|
+
type: "string",
|
|
695
|
+
description: "The replacement text"
|
|
696
|
+
},
|
|
697
|
+
threshold: {
|
|
698
|
+
type: "number",
|
|
699
|
+
default: 0.85,
|
|
700
|
+
description: "Minimum similarity threshold for fuzzy matching (0-1). Default 0.85."
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
required: ["file_path", "old_string", "new_string"]
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
];
|
|
707
|
+
}
|
|
673
708
|
/**
|
|
674
709
|
* Get tool definition by name
|
|
675
710
|
*/
|
|
@@ -691,6 +726,8 @@ class MCPToolDefinitions {
|
|
|
691
726
|
return this.getTraceTools();
|
|
692
727
|
case "discovery":
|
|
693
728
|
return this.getDiscoveryTools();
|
|
729
|
+
case "edit":
|
|
730
|
+
return this.getEditTools();
|
|
694
731
|
default:
|
|
695
732
|
return [];
|
|
696
733
|
}
|