@stackmemoryai/stackmemory 0.5.64 → 0.5.66
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 +68 -345
- package/bin/claude-sm +1 -1
- package/bin/claude-smd +1 -1
- package/bin/codex-smd +1 -1
- package/dist/src/cli/commands/skills.js +104 -0
- package/dist/src/cli/commands/skills.js.map +2 -2
- package/dist/src/cli/index.js +304 -1
- package/dist/src/cli/index.js.map +2 -2
- package/dist/src/integrations/mcp/pending-utils.js +33 -0
- package/dist/src/integrations/mcp/pending-utils.js.map +7 -0
- package/dist/src/integrations/mcp/server.js +565 -1
- package/dist/src/integrations/mcp/server.js.map +2 -2
- package/dist/src/orchestrators/multimodal/constants.js +17 -0
- package/dist/src/orchestrators/multimodal/constants.js.map +7 -0
- package/dist/src/orchestrators/multimodal/harness.js +293 -0
- package/dist/src/orchestrators/multimodal/harness.js.map +7 -0
- package/dist/src/orchestrators/multimodal/providers.js +85 -0
- package/dist/src/orchestrators/multimodal/providers.js.map +7 -0
- package/dist/src/orchestrators/multimodal/types.js +5 -0
- package/dist/src/orchestrators/multimodal/types.js.map +7 -0
- package/dist/src/orchestrators/multimodal/utils.js +25 -0
- package/dist/src/orchestrators/multimodal/utils.js.map +7 -0
- package/package.json +12 -9
- package/scripts/claude-code-wrapper.sh +18 -30
- package/scripts/demos/ralph-integration-demo.ts +14 -13
- package/scripts/demos/trace-demo.ts +7 -21
- package/scripts/demos/trace-test.ts +20 -8
- package/scripts/verify-dist.cjs +83 -0
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
AddAnchorSchema,
|
|
14
14
|
CreateTaskSchema
|
|
15
15
|
} from "./schemas.js";
|
|
16
|
-
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
16
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
|
|
17
|
+
import { compactPlan } from "../../orchestrators/multimodal/utils.js";
|
|
18
|
+
import { filterPending } from "./pending-utils.js";
|
|
17
19
|
import { join, dirname } from "path";
|
|
18
20
|
import { execSync } from "child_process";
|
|
19
21
|
import { FrameManager } from "../../core/context/index.js";
|
|
@@ -55,6 +57,7 @@ class LocalStackMemoryMCP {
|
|
|
55
57
|
contextRetrieval;
|
|
56
58
|
discoveryHandlers;
|
|
57
59
|
diffMemHandlers;
|
|
60
|
+
pendingPlans = /* @__PURE__ */ new Map();
|
|
58
61
|
constructor() {
|
|
59
62
|
this.projectRoot = this.findProjectRoot();
|
|
60
63
|
this.projectId = this.getProjectId();
|
|
@@ -97,6 +100,7 @@ class LocalStackMemoryMCP {
|
|
|
97
100
|
this.diffMemHandlers = new DiffMemHandlers();
|
|
98
101
|
this.setupHandlers();
|
|
99
102
|
this.loadInitialContext();
|
|
103
|
+
this.loadPendingPlans();
|
|
100
104
|
this.browserMCP.initialize(this.server).catch((error) => {
|
|
101
105
|
logger.error("Failed to initialize Browser MCP", error);
|
|
102
106
|
});
|
|
@@ -262,6 +266,199 @@ ${summary}...`, 0.8);
|
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
268
|
},
|
|
269
|
+
{
|
|
270
|
+
name: "plan_and_code",
|
|
271
|
+
description: "Generate a plan (Claude), attempt implementation (Codex/Claude), and return JSON result. Quiet by default.",
|
|
272
|
+
inputSchema: {
|
|
273
|
+
type: "object",
|
|
274
|
+
properties: {
|
|
275
|
+
task: { type: "string", description: "Task description" },
|
|
276
|
+
implementer: {
|
|
277
|
+
type: "string",
|
|
278
|
+
enum: ["codex", "claude"],
|
|
279
|
+
default: "codex",
|
|
280
|
+
description: "Which agent implements code"
|
|
281
|
+
},
|
|
282
|
+
maxIters: {
|
|
283
|
+
type: "number",
|
|
284
|
+
default: 2,
|
|
285
|
+
description: "Retry loop iterations"
|
|
286
|
+
},
|
|
287
|
+
execute: {
|
|
288
|
+
type: "boolean",
|
|
289
|
+
default: false,
|
|
290
|
+
description: "Actually call implementer (otherwise dry-run)"
|
|
291
|
+
},
|
|
292
|
+
record: {
|
|
293
|
+
type: "boolean",
|
|
294
|
+
default: false,
|
|
295
|
+
description: "Record plan & critique into StackMemory context"
|
|
296
|
+
},
|
|
297
|
+
recordFrame: {
|
|
298
|
+
type: "boolean",
|
|
299
|
+
default: false,
|
|
300
|
+
description: "Record as real frame with anchors"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
required: ["task"]
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: "plan_gate",
|
|
308
|
+
description: "Phase 1: Generate a plan and return an approvalId for later execution",
|
|
309
|
+
inputSchema: {
|
|
310
|
+
type: "object",
|
|
311
|
+
properties: {
|
|
312
|
+
task: { type: "string", description: "Task description" },
|
|
313
|
+
plannerModel: {
|
|
314
|
+
type: "string",
|
|
315
|
+
description: "Claude model (optional)"
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
required: ["task"]
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "approve_plan",
|
|
323
|
+
description: "Phase 2: Execute a previously generated plan by approvalId (runs implement + critique)",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
approvalId: {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "Id from plan_gate"
|
|
330
|
+
},
|
|
331
|
+
implementer: {
|
|
332
|
+
type: "string",
|
|
333
|
+
enum: ["codex", "claude"],
|
|
334
|
+
default: "codex",
|
|
335
|
+
description: "Which agent implements code"
|
|
336
|
+
},
|
|
337
|
+
maxIters: { type: "number", default: 2 },
|
|
338
|
+
recordFrame: { type: "boolean", default: true },
|
|
339
|
+
execute: { type: "boolean", default: true }
|
|
340
|
+
},
|
|
341
|
+
required: ["approvalId"]
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "pending_list",
|
|
346
|
+
description: "List pending approval-gated plans (supports filters)",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: {
|
|
350
|
+
taskContains: {
|
|
351
|
+
type: "string",
|
|
352
|
+
description: "Filter tasks containing this substring"
|
|
353
|
+
},
|
|
354
|
+
olderThanMs: {
|
|
355
|
+
type: "number",
|
|
356
|
+
description: "Only items older than this age (ms)"
|
|
357
|
+
},
|
|
358
|
+
newerThanMs: {
|
|
359
|
+
type: "number",
|
|
360
|
+
description: "Only items newer than this age (ms)"
|
|
361
|
+
},
|
|
362
|
+
sort: {
|
|
363
|
+
type: "string",
|
|
364
|
+
enum: ["asc", "desc"],
|
|
365
|
+
description: "Sort by createdAt"
|
|
366
|
+
},
|
|
367
|
+
limit: { type: "number", description: "Max items to return" }
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "pending_clear",
|
|
373
|
+
description: "Clear pending approval-gated plans (by id, all, or olderThanMs)",
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: "object",
|
|
376
|
+
properties: {
|
|
377
|
+
approvalId: {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "Clear a single approval by id"
|
|
380
|
+
},
|
|
381
|
+
all: {
|
|
382
|
+
type: "boolean",
|
|
383
|
+
description: "Clear all pending approvals",
|
|
384
|
+
default: false
|
|
385
|
+
},
|
|
386
|
+
olderThanMs: {
|
|
387
|
+
type: "number",
|
|
388
|
+
description: "Clear approvals older than this age (ms)"
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "pending_show",
|
|
395
|
+
description: "Show a pending plan by approvalId",
|
|
396
|
+
inputSchema: {
|
|
397
|
+
type: "object",
|
|
398
|
+
properties: {
|
|
399
|
+
approvalId: {
|
|
400
|
+
type: "string",
|
|
401
|
+
description: "Approval id from plan_gate"
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
required: ["approvalId"]
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "plan_only",
|
|
409
|
+
description: "Generate an implementation plan (Claude) and return JSON only",
|
|
410
|
+
inputSchema: {
|
|
411
|
+
type: "object",
|
|
412
|
+
properties: {
|
|
413
|
+
task: { type: "string", description: "Task description" },
|
|
414
|
+
plannerModel: {
|
|
415
|
+
type: "string",
|
|
416
|
+
description: "Claude model for planning (optional)"
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
required: ["task"]
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "call_codex",
|
|
424
|
+
description: "Invoke Codex via codex-sm with a prompt and args; dry-run by default",
|
|
425
|
+
inputSchema: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {
|
|
428
|
+
prompt: { type: "string", description: "Prompt for Codex" },
|
|
429
|
+
args: {
|
|
430
|
+
type: "array",
|
|
431
|
+
items: { type: "string" },
|
|
432
|
+
description: "Additional CLI args for codex-sm"
|
|
433
|
+
},
|
|
434
|
+
execute: {
|
|
435
|
+
type: "boolean",
|
|
436
|
+
default: false,
|
|
437
|
+
description: "Actually run codex-sm (otherwise dry-run)"
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
required: ["prompt"]
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "call_claude",
|
|
445
|
+
description: "Invoke Claude with a prompt (Anthropic SDK)",
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: "object",
|
|
448
|
+
properties: {
|
|
449
|
+
prompt: { type: "string", description: "Prompt for Claude" },
|
|
450
|
+
model: {
|
|
451
|
+
type: "string",
|
|
452
|
+
description: "Claude model (optional)"
|
|
453
|
+
},
|
|
454
|
+
system: {
|
|
455
|
+
type: "string",
|
|
456
|
+
description: "System prompt (optional)"
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
required: ["prompt"]
|
|
460
|
+
}
|
|
461
|
+
},
|
|
265
462
|
{
|
|
266
463
|
name: "add_decision",
|
|
267
464
|
description: "Record a decision or important information",
|
|
@@ -940,6 +1137,30 @@ ${summary}...`, 0.8);
|
|
|
940
1137
|
case "compress_old_traces":
|
|
941
1138
|
result = await this.handleCompressOldTraces(args);
|
|
942
1139
|
break;
|
|
1140
|
+
case "plan_only":
|
|
1141
|
+
result = await this.handlePlanOnly(args);
|
|
1142
|
+
break;
|
|
1143
|
+
case "call_codex":
|
|
1144
|
+
result = await this.handleCallCodex(args);
|
|
1145
|
+
break;
|
|
1146
|
+
case "call_claude":
|
|
1147
|
+
result = await this.handleCallClaude(args);
|
|
1148
|
+
break;
|
|
1149
|
+
case "plan_gate":
|
|
1150
|
+
result = await this.handlePlanGate(args);
|
|
1151
|
+
break;
|
|
1152
|
+
case "approve_plan":
|
|
1153
|
+
result = await this.handleApprovePlan(args);
|
|
1154
|
+
break;
|
|
1155
|
+
case "pending_list":
|
|
1156
|
+
result = await this.handlePendingList();
|
|
1157
|
+
break;
|
|
1158
|
+
case "pending_clear":
|
|
1159
|
+
result = await this.handlePendingClear(args);
|
|
1160
|
+
break;
|
|
1161
|
+
case "pending_show":
|
|
1162
|
+
result = await this.handlePendingShow(args);
|
|
1163
|
+
break;
|
|
943
1164
|
case "smart_context":
|
|
944
1165
|
result = await this.handleSmartContext(args);
|
|
945
1166
|
break;
|
|
@@ -1008,6 +1229,349 @@ ${summary}...`, 0.8);
|
|
|
1008
1229
|
}
|
|
1009
1230
|
);
|
|
1010
1231
|
}
|
|
1232
|
+
// Handle plan_and_code tool by invoking the mm harness
|
|
1233
|
+
async handlePlanAndCode(args) {
|
|
1234
|
+
const { runSpike } = await import("../../orchestrators/multimodal/harness.js");
|
|
1235
|
+
const plannerModel = process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
|
|
1236
|
+
const reviewerModel = process.env["STACKMEMORY_MM_REVIEWER_MODEL"] || plannerModel;
|
|
1237
|
+
const implementer = args.implementer || process.env["STACKMEMORY_MM_IMPLEMENTER"] || "codex";
|
|
1238
|
+
const maxIters = Number(
|
|
1239
|
+
args.maxIters ?? process.env["STACKMEMORY_MM_MAX_ITERS"] ?? 2
|
|
1240
|
+
);
|
|
1241
|
+
const execute = Boolean(args.execute);
|
|
1242
|
+
const record = Boolean(args.record);
|
|
1243
|
+
const recordFrame = Boolean(args.recordFrame);
|
|
1244
|
+
const compact = Boolean(args.compact);
|
|
1245
|
+
const task = String(args.task || "Plan and implement change");
|
|
1246
|
+
const result = await runSpike(
|
|
1247
|
+
{
|
|
1248
|
+
task,
|
|
1249
|
+
repoPath: this.projectRoot
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
plannerModel,
|
|
1253
|
+
reviewerModel,
|
|
1254
|
+
implementer: implementer === "claude" ? "claude" : "codex",
|
|
1255
|
+
maxIters: isFinite(maxIters) ? Math.max(1, maxIters) : 2,
|
|
1256
|
+
dryRun: !execute,
|
|
1257
|
+
auditDir: void 0,
|
|
1258
|
+
recordFrame
|
|
1259
|
+
}
|
|
1260
|
+
);
|
|
1261
|
+
if (record || recordFrame) {
|
|
1262
|
+
try {
|
|
1263
|
+
const planSummary = result.plan.summary || task;
|
|
1264
|
+
this.addContext("decision", `Plan: ${planSummary}`, 0.8);
|
|
1265
|
+
const approved = result.critique?.approved ? "approved" : "needs_changes";
|
|
1266
|
+
this.addContext("decision", `Critique: ${approved}`, 0.6);
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
const payload = compact ? { ...result, plan: compactPlan(result.plan) } : result;
|
|
1271
|
+
return {
|
|
1272
|
+
content: [
|
|
1273
|
+
{
|
|
1274
|
+
type: "text",
|
|
1275
|
+
text: JSON.stringify({ ok: true, result: payload })
|
|
1276
|
+
}
|
|
1277
|
+
],
|
|
1278
|
+
isError: false
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
async handlePlanOnly(args) {
|
|
1282
|
+
const { runPlanOnly } = await import("../../orchestrators/multimodal/harness.js");
|
|
1283
|
+
const task = String(args.task || "Plan change");
|
|
1284
|
+
const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
|
|
1285
|
+
const plan = await runPlanOnly(
|
|
1286
|
+
{ task, repoPath: this.projectRoot },
|
|
1287
|
+
{ plannerModel }
|
|
1288
|
+
);
|
|
1289
|
+
return {
|
|
1290
|
+
content: [
|
|
1291
|
+
{
|
|
1292
|
+
type: "text",
|
|
1293
|
+
text: JSON.stringify({ ok: true, plan })
|
|
1294
|
+
}
|
|
1295
|
+
],
|
|
1296
|
+
isError: false
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
async handleCallCodex(args) {
|
|
1300
|
+
const { callCodexCLI } = await import("../../orchestrators/multimodal/providers.js");
|
|
1301
|
+
const prompt = String(args.prompt || "");
|
|
1302
|
+
const extraArgs = Array.isArray(args.args) ? args.args : [];
|
|
1303
|
+
const execute = Boolean(args.execute);
|
|
1304
|
+
const resp = callCodexCLI(prompt, extraArgs, !execute);
|
|
1305
|
+
return {
|
|
1306
|
+
content: [
|
|
1307
|
+
{
|
|
1308
|
+
type: "text",
|
|
1309
|
+
text: JSON.stringify({
|
|
1310
|
+
ok: resp.ok,
|
|
1311
|
+
command: resp.command,
|
|
1312
|
+
output: resp.output
|
|
1313
|
+
})
|
|
1314
|
+
}
|
|
1315
|
+
],
|
|
1316
|
+
isError: false
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
async handleCallClaude(args) {
|
|
1320
|
+
const { callClaude } = await import("../../orchestrators/multimodal/providers.js");
|
|
1321
|
+
const prompt = String(args.prompt || "");
|
|
1322
|
+
const model = args.model || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
|
|
1323
|
+
const system = args.system || "You are a precise assistant. Return plain text unless asked for JSON.";
|
|
1324
|
+
const text = await callClaude(prompt, { model, system });
|
|
1325
|
+
return {
|
|
1326
|
+
content: [
|
|
1327
|
+
{
|
|
1328
|
+
type: "text",
|
|
1329
|
+
text: JSON.stringify({ ok: true, text })
|
|
1330
|
+
}
|
|
1331
|
+
],
|
|
1332
|
+
isError: false
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
// Pending plan persistence (best-effort)
|
|
1336
|
+
getPendingStoreDir() {
|
|
1337
|
+
return join(this.projectRoot, ".stackmemory", "build");
|
|
1338
|
+
}
|
|
1339
|
+
getPendingStorePath() {
|
|
1340
|
+
return join(this.getPendingStoreDir(), "pending.json");
|
|
1341
|
+
}
|
|
1342
|
+
loadPendingPlans() {
|
|
1343
|
+
try {
|
|
1344
|
+
const file = this.getPendingStorePath();
|
|
1345
|
+
let sourceFile = file;
|
|
1346
|
+
if (!existsSync(file)) {
|
|
1347
|
+
const legacy = join(
|
|
1348
|
+
this.projectRoot,
|
|
1349
|
+
".stackmemory",
|
|
1350
|
+
"mm-spike",
|
|
1351
|
+
"pending.json"
|
|
1352
|
+
);
|
|
1353
|
+
if (existsSync(legacy)) sourceFile = legacy;
|
|
1354
|
+
else return;
|
|
1355
|
+
}
|
|
1356
|
+
const data = JSON.parse(readFileSync(sourceFile, "utf-8"));
|
|
1357
|
+
if (data && typeof data === "object") {
|
|
1358
|
+
this.pendingPlans = new Map(Object.entries(data));
|
|
1359
|
+
if (sourceFile !== file) this.savePendingPlans();
|
|
1360
|
+
}
|
|
1361
|
+
} catch {
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
savePendingPlans() {
|
|
1365
|
+
try {
|
|
1366
|
+
const dir = this.getPendingStoreDir();
|
|
1367
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1368
|
+
const file = this.getPendingStorePath();
|
|
1369
|
+
const obj = Object.fromEntries(this.pendingPlans);
|
|
1370
|
+
writeFileSync(file, JSON.stringify(obj, null, 2));
|
|
1371
|
+
} catch {
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
async handlePlanGate(args) {
|
|
1375
|
+
const { runPlanOnly } = await import("../../orchestrators/multimodal/harness.js");
|
|
1376
|
+
const task = String(args.task || "Plan change");
|
|
1377
|
+
const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
|
|
1378
|
+
const plan = await runPlanOnly(
|
|
1379
|
+
{ task, repoPath: this.projectRoot },
|
|
1380
|
+
{ plannerModel }
|
|
1381
|
+
);
|
|
1382
|
+
const approvalId = `appr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1383
|
+
this.pendingPlans.set(approvalId, { task, plan, createdAt: Date.now() });
|
|
1384
|
+
this.savePendingPlans();
|
|
1385
|
+
const compact = Boolean(args.compact);
|
|
1386
|
+
const planOut = compact ? compactPlan(plan) : plan;
|
|
1387
|
+
return {
|
|
1388
|
+
content: [
|
|
1389
|
+
{
|
|
1390
|
+
type: "text",
|
|
1391
|
+
text: JSON.stringify({ ok: true, approvalId, plan: planOut })
|
|
1392
|
+
}
|
|
1393
|
+
],
|
|
1394
|
+
isError: false
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
async handleApprovePlan(args) {
|
|
1398
|
+
const { runSpike } = await import("../../orchestrators/multimodal/harness.js");
|
|
1399
|
+
const approvalId = String(args.approvalId || "");
|
|
1400
|
+
const pending = this.pendingPlans.get(approvalId);
|
|
1401
|
+
if (!pending) {
|
|
1402
|
+
return {
|
|
1403
|
+
content: [
|
|
1404
|
+
{
|
|
1405
|
+
type: "text",
|
|
1406
|
+
text: JSON.stringify({ ok: false, error: "Invalid approvalId" })
|
|
1407
|
+
}
|
|
1408
|
+
],
|
|
1409
|
+
isError: false
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
const implementer = args.implementer || process.env["STACKMEMORY_MM_IMPLEMENTER"] || "codex";
|
|
1413
|
+
const maxIters = Number(
|
|
1414
|
+
args.maxIters ?? process.env["STACKMEMORY_MM_MAX_ITERS"] ?? 2
|
|
1415
|
+
);
|
|
1416
|
+
const recordFrame = args.recordFrame !== false;
|
|
1417
|
+
const execute = args.execute !== false;
|
|
1418
|
+
const result = await runSpike(
|
|
1419
|
+
{ task: pending.task, repoPath: this.projectRoot },
|
|
1420
|
+
{
|
|
1421
|
+
plannerModel: process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest",
|
|
1422
|
+
reviewerModel: process.env["STACKMEMORY_MM_REVIEWER_MODEL"] || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest",
|
|
1423
|
+
implementer: implementer === "claude" ? "claude" : "codex",
|
|
1424
|
+
maxIters: isFinite(maxIters) ? Math.max(1, maxIters) : 2,
|
|
1425
|
+
dryRun: !execute,
|
|
1426
|
+
recordFrame
|
|
1427
|
+
}
|
|
1428
|
+
);
|
|
1429
|
+
this.pendingPlans.delete(approvalId);
|
|
1430
|
+
this.savePendingPlans();
|
|
1431
|
+
const compact = Boolean(args.compact);
|
|
1432
|
+
const payload = compact ? { ...result, plan: compactPlan(result.plan) } : result;
|
|
1433
|
+
return {
|
|
1434
|
+
content: [
|
|
1435
|
+
{
|
|
1436
|
+
type: "text",
|
|
1437
|
+
text: JSON.stringify({ ok: true, approvalId, result: payload })
|
|
1438
|
+
}
|
|
1439
|
+
],
|
|
1440
|
+
isError: false
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
async handlePendingList(args) {
|
|
1444
|
+
const schema = z.object({
|
|
1445
|
+
taskContains: z.string().optional(),
|
|
1446
|
+
olderThanMs: z.number().optional(),
|
|
1447
|
+
newerThanMs: z.number().optional(),
|
|
1448
|
+
sort: z.enum(["asc", "desc"]).optional(),
|
|
1449
|
+
limit: z.number().int().positive().optional()
|
|
1450
|
+
}).optional();
|
|
1451
|
+
const parsed = schema.safeParse(args);
|
|
1452
|
+
if (args && !parsed.success) {
|
|
1453
|
+
return {
|
|
1454
|
+
content: [
|
|
1455
|
+
{
|
|
1456
|
+
type: "text",
|
|
1457
|
+
text: JSON.stringify({
|
|
1458
|
+
ok: false,
|
|
1459
|
+
error: "Invalid arguments",
|
|
1460
|
+
details: parsed.error.issues
|
|
1461
|
+
})
|
|
1462
|
+
}
|
|
1463
|
+
],
|
|
1464
|
+
isError: false
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
const a = parsed.success && parsed.data ? parsed.data : {};
|
|
1468
|
+
const now = Date.now();
|
|
1469
|
+
let items = Array.from(this.pendingPlans.entries()).map(
|
|
1470
|
+
([approvalId, data]) => ({
|
|
1471
|
+
approvalId,
|
|
1472
|
+
task: data?.task,
|
|
1473
|
+
createdAt: Number(data?.createdAt || 0) || null
|
|
1474
|
+
})
|
|
1475
|
+
);
|
|
1476
|
+
items = filterPending(items, a, now);
|
|
1477
|
+
return {
|
|
1478
|
+
content: [
|
|
1479
|
+
{ type: "text", text: JSON.stringify({ ok: true, pending: items }) }
|
|
1480
|
+
],
|
|
1481
|
+
isError: false
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
async handlePendingClear(args) {
|
|
1485
|
+
const removed = [];
|
|
1486
|
+
const now = Date.now();
|
|
1487
|
+
const all = Boolean(args?.all);
|
|
1488
|
+
const approvalId = args?.approvalId ? String(args.approvalId) : void 0;
|
|
1489
|
+
const olderThanMs = Number.isFinite(Number(args?.olderThanMs)) ? Number(args.olderThanMs) : void 0;
|
|
1490
|
+
if (all) {
|
|
1491
|
+
for (const id of this.pendingPlans.keys()) removed.push(id);
|
|
1492
|
+
this.pendingPlans.clear();
|
|
1493
|
+
this.savePendingPlans();
|
|
1494
|
+
return {
|
|
1495
|
+
content: [
|
|
1496
|
+
{ type: "text", text: JSON.stringify({ ok: true, removed }) }
|
|
1497
|
+
],
|
|
1498
|
+
isError: false
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
if (approvalId) {
|
|
1502
|
+
if (this.pendingPlans.has(approvalId)) {
|
|
1503
|
+
this.pendingPlans.delete(approvalId);
|
|
1504
|
+
removed.push(approvalId);
|
|
1505
|
+
this.savePendingPlans();
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
content: [
|
|
1509
|
+
{ type: "text", text: JSON.stringify({ ok: true, removed }) }
|
|
1510
|
+
],
|
|
1511
|
+
isError: false
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
if (olderThanMs !== void 0 && olderThanMs >= 0) {
|
|
1515
|
+
for (const [id, data] of this.pendingPlans.entries()) {
|
|
1516
|
+
const ts = Number(data?.createdAt || 0);
|
|
1517
|
+
if (ts && now - ts > olderThanMs) {
|
|
1518
|
+
this.pendingPlans.delete(id);
|
|
1519
|
+
removed.push(id);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
this.savePendingPlans();
|
|
1523
|
+
return {
|
|
1524
|
+
content: [
|
|
1525
|
+
{ type: "text", text: JSON.stringify({ ok: true, removed }) }
|
|
1526
|
+
],
|
|
1527
|
+
isError: false
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
content: [
|
|
1532
|
+
{
|
|
1533
|
+
type: "text",
|
|
1534
|
+
text: JSON.stringify({
|
|
1535
|
+
ok: false,
|
|
1536
|
+
error: "Specify approvalId, all=true, or olderThanMs"
|
|
1537
|
+
})
|
|
1538
|
+
}
|
|
1539
|
+
],
|
|
1540
|
+
isError: false
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
async handlePendingShow(args) {
|
|
1544
|
+
const approvalId = String(args?.approvalId || "");
|
|
1545
|
+
const data = this.pendingPlans.get(approvalId);
|
|
1546
|
+
if (!data) {
|
|
1547
|
+
return {
|
|
1548
|
+
content: [
|
|
1549
|
+
{
|
|
1550
|
+
type: "text",
|
|
1551
|
+
text: JSON.stringify({ ok: false, error: "Invalid approvalId" })
|
|
1552
|
+
}
|
|
1553
|
+
],
|
|
1554
|
+
isError: false
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
const compact = Boolean(args.compact);
|
|
1558
|
+
const planOut = compact ? compactPlan(data.plan) : data.plan;
|
|
1559
|
+
return {
|
|
1560
|
+
content: [
|
|
1561
|
+
{
|
|
1562
|
+
type: "text",
|
|
1563
|
+
text: JSON.stringify({
|
|
1564
|
+
ok: true,
|
|
1565
|
+
approvalId,
|
|
1566
|
+
task: data.task,
|
|
1567
|
+
plan: planOut,
|
|
1568
|
+
createdAt: data.createdAt || null
|
|
1569
|
+
})
|
|
1570
|
+
}
|
|
1571
|
+
],
|
|
1572
|
+
isError: false
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1011
1575
|
async handleGetContext(args) {
|
|
1012
1576
|
const { query: query2 = "", limit = 10 } = args;
|
|
1013
1577
|
const contexts = Array.from(this.contexts.values()).sort((a, b) => b.importance - a.importance).slice(0, limit);
|