@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.
Files changed (66) hide show
  1. package/README.md +69 -346
  2. package/bin/claude-sm +1 -1
  3. package/bin/claude-smd +1 -1
  4. package/bin/codex-sm +6 -0
  5. package/bin/codex-smd +1 -1
  6. package/bin/opencode-sm +1 -1
  7. package/dist/src/cli/claude-sm.js +162 -25
  8. package/dist/src/cli/claude-sm.js.map +2 -2
  9. package/dist/src/cli/commands/ping.js +14 -0
  10. package/dist/src/cli/commands/ping.js.map +7 -0
  11. package/dist/src/cli/commands/ralph.js +103 -1
  12. package/dist/src/cli/commands/ralph.js.map +2 -2
  13. package/dist/src/cli/commands/retrieval.js +1 -1
  14. package/dist/src/cli/commands/retrieval.js.map +2 -2
  15. package/dist/src/cli/commands/skills.js +300 -1
  16. package/dist/src/cli/commands/skills.js.map +2 -2
  17. package/dist/src/cli/index.js +362 -20
  18. package/dist/src/cli/index.js.map +2 -2
  19. package/dist/src/core/digest/types.js +1 -1
  20. package/dist/src/core/digest/types.js.map +1 -1
  21. package/dist/src/core/extensions/provider-adapter.js +2 -5
  22. package/dist/src/core/extensions/provider-adapter.js.map +2 -2
  23. package/dist/src/core/retrieval/llm-provider.js +2 -2
  24. package/dist/src/core/retrieval/llm-provider.js.map +1 -1
  25. package/dist/src/core/retrieval/types.js +1 -1
  26. package/dist/src/core/retrieval/types.js.map +1 -1
  27. package/dist/src/features/sweep/pty-wrapper.js +15 -5
  28. package/dist/src/features/sweep/pty-wrapper.js.map +2 -2
  29. package/dist/src/features/workers/tmux-manager.js +71 -0
  30. package/dist/src/features/workers/tmux-manager.js.map +7 -0
  31. package/dist/src/features/workers/worker-registry.js +52 -0
  32. package/dist/src/features/workers/worker-registry.js.map +7 -0
  33. package/dist/src/integrations/linear/webhook-handler.js +82 -0
  34. package/dist/src/integrations/linear/webhook-handler.js.map +2 -2
  35. package/dist/src/integrations/mcp/pending-utils.js +33 -0
  36. package/dist/src/integrations/mcp/pending-utils.js.map +7 -0
  37. package/dist/src/integrations/mcp/server.js +571 -1
  38. package/dist/src/integrations/mcp/server.js.map +2 -2
  39. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +2 -2
  40. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js.map +2 -2
  41. package/dist/src/orchestrators/multimodal/constants.js +17 -0
  42. package/dist/src/orchestrators/multimodal/constants.js.map +7 -0
  43. package/dist/src/orchestrators/multimodal/harness.js +292 -0
  44. package/dist/src/orchestrators/multimodal/harness.js.map +7 -0
  45. package/dist/src/orchestrators/multimodal/providers.js +98 -0
  46. package/dist/src/orchestrators/multimodal/providers.js.map +7 -0
  47. package/dist/src/orchestrators/multimodal/types.js +5 -0
  48. package/dist/src/orchestrators/multimodal/types.js.map +7 -0
  49. package/dist/src/orchestrators/multimodal/utils.js +25 -0
  50. package/dist/src/orchestrators/multimodal/utils.js.map +7 -0
  51. package/dist/src/skills/claude-skills.js +116 -1
  52. package/dist/src/skills/claude-skills.js.map +2 -2
  53. package/dist/src/skills/linear-task-runner.js +262 -0
  54. package/dist/src/skills/linear-task-runner.js.map +7 -0
  55. package/dist/src/skills/recursive-agent-orchestrator.js +114 -85
  56. package/dist/src/skills/recursive-agent-orchestrator.js.map +2 -2
  57. package/dist/src/skills/spec-generator-skill.js +441 -0
  58. package/dist/src/skills/spec-generator-skill.js.map +7 -0
  59. package/package.json +14 -9
  60. package/scripts/claude-code-wrapper.sh +18 -30
  61. package/scripts/demos/ralph-integration-demo.ts +14 -13
  62. package/scripts/demos/trace-demo.ts +7 -21
  63. package/scripts/demos/trace-test.ts +20 -8
  64. package/scripts/install-claude-hooks.sh +2 -2
  65. package/scripts/verify-dist.cjs +83 -0
  66. 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);