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