micro-models-agent 0.51.1 → 0.52.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/cli/commands.js +162 -38
- package/dist/cli/completer.js +5 -5
- package/dist/cli/main.js +42 -54
- package/dist/cli/repl-commands.js +138 -38
- package/dist/cli/repl.js +175 -89
- package/dist/cli/run-result.js +11 -0
- package/dist/cli/security-commands.js +6 -6
- package/dist/cli/setup.js +21 -15
- package/dist/config/config.js +54 -27
- package/dist/config/defaults.js +17 -0
- package/dist/config/domains.js +179 -0
- package/dist/config/index.js +2 -1
- package/dist/config/security.js +28 -8
- package/dist/core/agent.js +162 -30
- package/dist/core/bootstrap.js +94 -17
- package/dist/core/crash-handler.js +51 -0
- package/dist/core/environment.js +199 -0
- package/dist/core/session-logger.js +60 -6
- package/dist/core/version.js +2 -0
- package/dist/i18n/en.json +120 -39
- package/dist/i18n/ru.json +91 -10
- package/dist/llm/openai-compat.js +191 -53
- package/dist/llm/orchestrator.js +5 -3
- package/dist/logger/app-logger.js +50 -4
- package/dist/main.js +1288 -635
- package/dist/modules/browser/session.js +4 -0
- package/dist/modules/certification/cli.js +58 -19
- package/dist/modules/certification/loader.js +2 -1
- package/dist/modules/certification/manifest.js +22 -14
- package/dist/modules/certification/runner.js +91 -5
- package/dist/modules/certification/scenarios.js +290 -7
- package/dist/modules/context/fact-extractor.js +6 -0
- package/dist/modules/context/manager.js +19 -2
- package/dist/modules/execution/audit-runners.js +61 -7
- package/dist/modules/execution/execution-plugin.js +219 -60
- package/dist/modules/execution/module.js +207 -18
- package/dist/modules/execution/moe-executor.js +33 -20
- package/dist/modules/execution/plan-store.js +39 -0
- package/dist/modules/execution/plan-tool.js +188 -19
- package/dist/modules/execution/planner.js +27 -23
- package/dist/modules/execution/stuck-detector.js +244 -8
- package/dist/modules/execution/tracker.js +8 -6
- package/dist/modules/execution/verifier.js +15 -2
- package/dist/modules/hallucination/detector.js +4 -0
- package/dist/modules/hallucination/factual.js +45 -5
- package/dist/modules/indexer/module.js +1 -0
- package/dist/modules/lsp/client.js +123 -12
- package/dist/modules/lsp/index.js +1 -1
- package/dist/modules/lsp/module.js +30 -2
- package/dist/modules/lsp/probe.js +11 -1
- package/dist/modules/lsp/startup-check.js +5 -2
- package/dist/modules/plugins/builtin/lint-on-write.js +144 -41
- package/dist/modules/plugins/manager.js +57 -13
- package/dist/modules/pricing/index.js +61 -0
- package/dist/modules/pricing/prices.js +129 -0
- package/dist/modules/providers/create.js +22 -0
- package/dist/modules/providers/fallback.js +79 -0
- package/dist/modules/providers/health.js +46 -0
- package/dist/modules/providers/index.js +5 -0
- package/dist/modules/providers/manager.js +161 -0
- package/dist/modules/providers/presets.js +128 -0
- package/dist/modules/providers/registry.js +22 -0
- package/dist/modules/providers/types.js +1 -0
- package/dist/modules/registry.js +1 -0
- package/dist/modules/security/command-validator.js +14 -0
- package/dist/modules/security/encryption.js +6 -6
- package/dist/modules/security/network-validator.js +17 -0
- package/dist/modules/security/path-validator.js +22 -26
- package/dist/modules/session/store.js +10 -10
- package/dist/tools/approve.js +1 -0
- package/dist/tools/attach-image.js +12 -0
- package/dist/tools/bash.js +27 -4
- package/dist/tools/browser.js +1 -0
- package/dist/tools/chunk-query.js +1 -0
- package/dist/tools/create-dir.js +1 -0
- package/dist/tools/delete-file.js +1 -0
- package/dist/tools/download-file.js +1 -0
- package/dist/tools/edit-file.js +2 -1
- package/dist/tools/enable-tools.js +1 -0
- package/dist/tools/executor.js +17 -7
- package/dist/tools/file-info.js +1 -0
- package/dist/tools/glob-tool.js +1 -0
- package/dist/tools/grep-tool.js +54 -13
- package/dist/tools/list-dir.js +1 -0
- package/dist/tools/load-skill.js +1 -0
- package/dist/tools/mcp-call.js +1 -0
- package/dist/tools/move-file.js +1 -0
- package/dist/tools/path-utils.js +51 -1
- package/dist/tools/pipeline-run.js +1 -0
- package/dist/tools/process-kill.js +11 -0
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +9 -0
- package/dist/tools/question.js +1 -0
- package/dist/tools/read-file.js +94 -6
- package/dist/tools/recall.js +1 -0
- package/dist/tools/remember.js +1 -0
- package/dist/tools/scope-check.js +7 -5
- package/dist/tools/search-history.js +1 -0
- package/dist/tools/subagent.js +4 -4
- package/dist/tools/web-browse.js +1 -0
- package/dist/tools/web-fetch.js +27 -6
- package/dist/tools/web-search.js +70 -43
- package/dist/tools/write-file.js +1 -0
- package/dist/ui/line-editor.js +142 -23
- package/dist/ui/line-math.js +8 -4
- package/dist/ui/renderer.js +57 -7
- package/package.json +50 -48
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t } from "../../i18n/index";
|
|
2
|
-
import { PlanCreator } from "./planner";
|
|
2
|
+
import { PlanCreator, getPlanProgress } from "./planner";
|
|
3
3
|
import { checkPlanCoverage } from "./plan-coverage";
|
|
4
|
+
import { stepTypecheckGate } from "./execution-plugin";
|
|
4
5
|
/**
|
|
5
6
|
* The plan / todo / verify tool definitions for the execution module. The
|
|
6
7
|
* handlers were extracted verbatim from ExecutionModule.getToolDefinitions() —
|
|
@@ -11,14 +12,25 @@ export function createPlanToolDefinitions(deps) {
|
|
|
11
12
|
return [
|
|
12
13
|
{
|
|
13
14
|
name: "plan",
|
|
15
|
+
icon: "📋",
|
|
14
16
|
alwaysOn: true,
|
|
15
|
-
description: 'Create, update, show, abort, list, switch, or re-plan multi-step plans.\n\nActions:\n- create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.\n- update: Mark step status (done/failed/skipped), or rebuild plan with new steps.\n- show: Print current plan checklist.\n- abort: Archive current plan and clear active slot.\n- list: Show all plans (active, drafts, archived) with progress.\n- switch: Make a different plan active (by plan id).\n- re-plan: Iterative replanning: keep completed steps, replace remaining with new steps.\n\nWrite CONCRETE steps with exact file paths and commands:\n- Specify WHICH files to create with exact paths (e.g. "create src/components/Header.tsx with navigation and logo")\n- Specify WHICH packages to install (e.g. "run npm install react react-dom")\n- Specify WHICH CLI commands to run with exact arguments\n- Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)\n- Good: "Создать src/components/Header.tsx с навигацией и логотипом"\n- Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)\n- For steps that REMOVE files, pass kinds: ["delete"] aligned with steps (the done-gate verifies those files are gone instead of demanding they exist).',
|
|
17
|
+
description: 'Create, update, show, abort, list, switch, delete, purge, or re-plan multi-step plans.\n\nActions:\n- create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.\n- update: Mark step status (done/failed/skipped), or rebuild plan with new steps.\n- show: Print current plan checklist.\n- abort: Archive the current plan (or the plan given by id) and clear the active slot.\n- list: Show all plans (active, drafts, archived) with progress.\n- switch: Make a different plan active (by plan id).\n- re-plan: Iterative replanning: keep completed steps, replace remaining with new steps.\n- delete: Permanently delete a plan by id (plan delete id=plan_xxx).\n- purge: Delete ALL plans (active, drafts, archived).\n\nWrite CONCRETE steps with exact file paths and commands:\n- Specify WHICH files to create with exact paths (e.g. "create src/components/Header.tsx with navigation and logo")\n- Specify WHICH packages to install (e.g. "run npm install react react-dom")\n- Specify WHICH CLI commands to run with exact arguments\n- Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)\n- Good: "Создать src/components/Header.tsx с навигацией и логотипом"\n- Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)\n- For steps that REMOVE files, pass kinds: ["delete"] aligned with steps (the done-gate verifies those files are gone instead of demanding they exist).',
|
|
16
18
|
parameters: {
|
|
17
19
|
type: "object",
|
|
18
20
|
properties: {
|
|
19
21
|
action: {
|
|
20
22
|
type: "string",
|
|
21
|
-
enum: [
|
|
23
|
+
enum: [
|
|
24
|
+
"create",
|
|
25
|
+
"update",
|
|
26
|
+
"show",
|
|
27
|
+
"abort",
|
|
28
|
+
"list",
|
|
29
|
+
"switch",
|
|
30
|
+
"re-plan",
|
|
31
|
+
"delete",
|
|
32
|
+
"purge",
|
|
33
|
+
],
|
|
22
34
|
},
|
|
23
35
|
title: { type: "string" },
|
|
24
36
|
steps: { type: "array", items: { type: "string" } },
|
|
@@ -46,6 +58,8 @@ export function createPlanToolDefinitions(deps) {
|
|
|
46
58
|
const namePart = m.name ? ` (${m.name})` : "";
|
|
47
59
|
return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
|
|
48
60
|
});
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push(t("plan.list_legend"));
|
|
49
63
|
return {
|
|
50
64
|
success: true,
|
|
51
65
|
output: `${t("plan.list_header")}\n${lines.join("\n")}`,
|
|
@@ -63,6 +77,19 @@ export function createPlanToolDefinitions(deps) {
|
|
|
63
77
|
output: t("plan.not_found", { id: planId }),
|
|
64
78
|
};
|
|
65
79
|
}
|
|
80
|
+
// Switching to the plan that is already active must be a no-op, not
|
|
81
|
+
// a duplicate (observed: switch on the active plan preserved the
|
|
82
|
+
// active.json and re-set the same plan, leaving two copies).
|
|
83
|
+
if (found.status === "active") {
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
output: t("plan.already_active", {
|
|
87
|
+
id: found.plan.id,
|
|
88
|
+
title: found.plan.title,
|
|
89
|
+
}),
|
|
90
|
+
display: deps.trackerRef.current?.toPromptBlock(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
66
93
|
deps.preserveActive();
|
|
67
94
|
if (found.status === "draft") {
|
|
68
95
|
deps.store.removeDraft(found.plan.id);
|
|
@@ -71,7 +98,10 @@ export function createPlanToolDefinitions(deps) {
|
|
|
71
98
|
deps.store.removeArchived(found.plan.id);
|
|
72
99
|
}
|
|
73
100
|
deps.setPlan(found.plan);
|
|
74
|
-
|
|
101
|
+
// Display from the tracker (setPlan syncs the pointer past any
|
|
102
|
+
// done/skipped steps) so "current: step N" points at the first
|
|
103
|
+
// pending step of the switched-to plan, not always step 1.
|
|
104
|
+
const display = deps.trackerRef.current?.toPromptBlock();
|
|
75
105
|
return {
|
|
76
106
|
success: true,
|
|
77
107
|
output: t("plan.switched", {
|
|
@@ -94,7 +124,7 @@ export function createPlanToolDefinitions(deps) {
|
|
|
94
124
|
const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
|
|
95
125
|
const keptCount = replanned.steps.length - newSteps.length;
|
|
96
126
|
deps.setPlan(replanned);
|
|
97
|
-
const display =
|
|
127
|
+
const display = deps.trackerRef.current?.toPromptBlock();
|
|
98
128
|
return {
|
|
99
129
|
success: true,
|
|
100
130
|
output: t("plan.replanned", {
|
|
@@ -220,13 +250,14 @@ export function createPlanToolDefinitions(deps) {
|
|
|
220
250
|
if (tracker.isComplete()) {
|
|
221
251
|
const plan = tracker.getPlan();
|
|
222
252
|
deps.store.archivePlan(plan);
|
|
253
|
+
deps.recordCompleted(plan); // final audit still verifies it
|
|
223
254
|
deps.trackerRef.current = null;
|
|
224
|
-
const
|
|
255
|
+
const p = getPlanProgress(plan);
|
|
225
256
|
return {
|
|
226
257
|
success: true,
|
|
227
258
|
output: t("plan.completed_archived", {
|
|
228
259
|
id: plan.id,
|
|
229
|
-
done: String(
|
|
260
|
+
done: String(p.settled),
|
|
230
261
|
total: String(plan.steps.length),
|
|
231
262
|
}),
|
|
232
263
|
};
|
|
@@ -272,6 +303,29 @@ export function createPlanToolDefinitions(deps) {
|
|
|
272
303
|
// intent. "create" (default) → named files must exist; "delete"
|
|
273
304
|
// → named files must be GONE. Intent is model-declared (kind),
|
|
274
305
|
// never keyword-inferred (rule #10).
|
|
306
|
+
//
|
|
307
|
+
// Auto-switch: when kind is "create" but ALL named files are gone,
|
|
308
|
+
// check if the agent actually deleted them (via delete_file/bash rm).
|
|
309
|
+
// Only then switch to "delete" — files that were never created
|
|
310
|
+
// (create-kind steps) remain blocked so the agent must create them.
|
|
311
|
+
if (status === "done" && target.kind !== "delete") {
|
|
312
|
+
const missing = deps.missingStepDeliverables(target);
|
|
313
|
+
if (missing.length > 0) {
|
|
314
|
+
const existing = deps.stillExistingDeliverables(target);
|
|
315
|
+
const allGone = deps.hasStepDeliverables(target) && existing.length === 0;
|
|
316
|
+
if (allGone) {
|
|
317
|
+
// Confirm the agent actually deleted these files (not just
|
|
318
|
+
// never created them) before auto-switching.
|
|
319
|
+
const wasDeleted = deps.wasFileDeleted
|
|
320
|
+
? missing.every((f) => deps.wasFileDeleted(f))
|
|
321
|
+
: false;
|
|
322
|
+
if (wasDeleted) {
|
|
323
|
+
target.kind = "delete";
|
|
324
|
+
deps.store.saveActive(tracker.getPlan());
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
275
329
|
if (status === "done") {
|
|
276
330
|
if (target.kind === "delete") {
|
|
277
331
|
const leftovers = deps.stillExistingDeliverables(target);
|
|
@@ -298,26 +352,52 @@ export function createPlanToolDefinitions(deps) {
|
|
|
298
352
|
}
|
|
299
353
|
}
|
|
300
354
|
}
|
|
355
|
+
// Typecheck gate: "done" while the last edit still fails to compile
|
|
356
|
+
// is a self-certification the harness must refuse (observed: steps
|
|
357
|
+
// marked done with the project typecheck failing on every write —
|
|
358
|
+
// a "fix it later" cascade that never got fixed). The model must
|
|
359
|
+
// either fix the errors (a clean re-edit clears the map) or mark
|
|
360
|
+
// the step skipped.
|
|
361
|
+
if (status === "done") {
|
|
362
|
+
const blocked = stepTypecheckGate(deps.typecheckFailures, target.description);
|
|
363
|
+
if (blocked) {
|
|
364
|
+
return {
|
|
365
|
+
success: false,
|
|
366
|
+
output: t("plan.typecheck_failed", {
|
|
367
|
+
step: String(stepId),
|
|
368
|
+
error: blocked,
|
|
369
|
+
}),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
301
373
|
tracker.updateStepStatus(stepId, status);
|
|
302
374
|
if (args.note)
|
|
303
375
|
tracker.addNote(Number(args.step), String(args.note));
|
|
304
376
|
tracker.syncCurrentStep();
|
|
305
377
|
deps.store.saveActive(tracker.getPlan());
|
|
378
|
+
// A "done" step that names no file tokens has a vacuous gate — its
|
|
379
|
+
// completion cannot be auto-verified. Inform the model so it can
|
|
380
|
+
// run an explicit check or add file paths to the step description.
|
|
381
|
+
const vacuousGate = status === "done" && !deps.hasStepDeliverables(target);
|
|
382
|
+
const vacuousNote = vacuousGate
|
|
383
|
+
? `\n${t("plan.no_deliverables", { step: String(stepId) })}`
|
|
384
|
+
: "";
|
|
306
385
|
// If this update completed the last step → auto-archive. This breaks
|
|
307
386
|
// the loop where a finished plan stays active and anchors the model
|
|
308
387
|
// to "continue step N, do NOT create a new plan".
|
|
309
388
|
if (tracker.isComplete()) {
|
|
310
389
|
const plan = tracker.getPlan();
|
|
311
390
|
deps.store.archivePlan(plan);
|
|
391
|
+
deps.recordCompleted(plan); // final audit still verifies it
|
|
312
392
|
deps.trackerRef.current = null;
|
|
313
|
-
const
|
|
393
|
+
const p = getPlanProgress(plan);
|
|
314
394
|
return {
|
|
315
395
|
success: true,
|
|
316
396
|
output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${t("plan.completed_archived", {
|
|
317
397
|
id: plan.id,
|
|
318
|
-
done: String(
|
|
398
|
+
done: String(p.settled),
|
|
319
399
|
total: String(plan.steps.length),
|
|
320
|
-
})}`,
|
|
400
|
+
})}${vacuousNote}`,
|
|
321
401
|
};
|
|
322
402
|
}
|
|
323
403
|
const progress = tracker.getProgressString();
|
|
@@ -325,15 +405,26 @@ export function createPlanToolDefinitions(deps) {
|
|
|
325
405
|
_ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${tracker.getCurrentStepIndex() + 1}`);
|
|
326
406
|
return {
|
|
327
407
|
success: true,
|
|
328
|
-
output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}`,
|
|
408
|
+
output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}${vacuousNote}`,
|
|
329
409
|
display,
|
|
330
410
|
};
|
|
331
411
|
}
|
|
332
412
|
if (action === "update" &&
|
|
333
413
|
Array.isArray(args.steps) &&
|
|
334
414
|
args.steps.length > 0 &&
|
|
335
|
-
|
|
415
|
+
!args.step) {
|
|
336
416
|
const tracker = deps.trackerRef.current;
|
|
417
|
+
if (!tracker) {
|
|
418
|
+
return { success: false, output: t("plan.no_active") };
|
|
419
|
+
}
|
|
420
|
+
// Rebuilding a plan that already has progress silently discards
|
|
421
|
+
// every status (ses_mt4fn58c: the model sent its whole step list
|
|
422
|
+
// with ✅-emoji statuses expecting progress marks and got a fresh
|
|
423
|
+
// plan back). A not-yet-started plan is safe to rebuild.
|
|
424
|
+
const progressed = tracker.getPlan().steps.some((s) => s.status !== "pending");
|
|
425
|
+
if (progressed) {
|
|
426
|
+
return { success: false, output: t("plan.bulk_update_rejected") };
|
|
427
|
+
}
|
|
337
428
|
const title = String(args.title || tracker.getPlan().title);
|
|
338
429
|
const steps = args.steps.map(String);
|
|
339
430
|
const parsed = deps.parseKinds(args, steps);
|
|
@@ -342,7 +433,7 @@ export function createPlanToolDefinitions(deps) {
|
|
|
342
433
|
}
|
|
343
434
|
const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
|
|
344
435
|
deps.setPlan(plan);
|
|
345
|
-
const display =
|
|
436
|
+
const display = deps.trackerRef.current?.toPromptBlock();
|
|
346
437
|
const output = t("plan.updated", {
|
|
347
438
|
title,
|
|
348
439
|
steps: String(steps.length),
|
|
@@ -354,15 +445,91 @@ export function createPlanToolDefinitions(deps) {
|
|
|
354
445
|
};
|
|
355
446
|
}
|
|
356
447
|
if (action === "abort") {
|
|
448
|
+
const planId = args.id ? String(args.id) : "";
|
|
449
|
+
if (planId) {
|
|
450
|
+
// Abort a SPECIFIC plan (not the active one). Draft → archive,
|
|
451
|
+
// active → archive + clear tracker. Archived plans are already
|
|
452
|
+
// terminal — there is nothing to abort.
|
|
453
|
+
const found = deps.store.find(planId);
|
|
454
|
+
if (!found) {
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
output: t("plan.not_found", { id: planId }),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (found.status === "archived") {
|
|
461
|
+
return {
|
|
462
|
+
success: false,
|
|
463
|
+
output: t("plan.already_archived", { id: planId }),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
deps.store.archivePlan(found.plan); // clears active if it was active
|
|
467
|
+
if (found.status === "active") {
|
|
468
|
+
deps.trackerRef.current = null;
|
|
469
|
+
deps.clearCompleted();
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
success: true,
|
|
473
|
+
output: t("plan.aborted_id", { id: found.plan.id }),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
357
476
|
const tracker = deps.trackerRef.current;
|
|
358
|
-
if (tracker) {
|
|
359
|
-
|
|
477
|
+
if (!tracker) {
|
|
478
|
+
// Abort with no active plan and no id — previously returned
|
|
479
|
+
// success:true, which made the model believe it aborted a plan
|
|
480
|
+
// that did not exist (observed: 3× abort on an empty list).
|
|
481
|
+
return { success: false, output: t("plan.no_active") };
|
|
360
482
|
}
|
|
483
|
+
deps.store.archivePlan(tracker.getPlan());
|
|
361
484
|
deps.trackerRef.current = null;
|
|
362
485
|
deps.store.clearActive();
|
|
486
|
+
deps.clearCompleted();
|
|
363
487
|
return { success: true, output: t("plan.aborted") };
|
|
364
488
|
}
|
|
489
|
+
if (action === "delete") {
|
|
490
|
+
const planId = String(args.id || "");
|
|
491
|
+
if (!planId) {
|
|
492
|
+
return { success: false, output: t("plan.delete_no_id") };
|
|
493
|
+
}
|
|
494
|
+
const deleted = deps.store.deletePlan(planId);
|
|
495
|
+
if (!deleted) {
|
|
496
|
+
return {
|
|
497
|
+
success: false,
|
|
498
|
+
output: t("plan.not_found", { id: planId }),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
if (deleted === "active") {
|
|
502
|
+
deps.trackerRef.current = null;
|
|
503
|
+
deps.clearCompleted();
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
success: true,
|
|
507
|
+
output: t("plan.deleted", { id: planId }),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (action === "purge") {
|
|
511
|
+
const count = deps.store.purgeAll();
|
|
512
|
+
deps.trackerRef.current = null;
|
|
513
|
+
deps.clearCompleted();
|
|
514
|
+
return {
|
|
515
|
+
success: true,
|
|
516
|
+
output: t("plan.purged", { count: String(count) }),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
365
519
|
if (!deps.trackerRef.current) {
|
|
520
|
+
// Update with an id that points at a draft/archived plan: say the
|
|
521
|
+
// plan is read-only instead of a bare "No active plan" (observed:
|
|
522
|
+
// 10+ iterations re-updating an archived plan and getting the same
|
|
523
|
+
// unhelpful answer back).
|
|
524
|
+
if (args.id) {
|
|
525
|
+
const found = deps.store.find(String(args.id));
|
|
526
|
+
if (found && found.status !== "active") {
|
|
527
|
+
return {
|
|
528
|
+
success: false,
|
|
529
|
+
output: t("plan.readonly", { id: found.plan.id, status: found.status }),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
366
533
|
return { success: false, output: t("plan.no_active") };
|
|
367
534
|
}
|
|
368
535
|
return {
|
|
@@ -373,6 +540,7 @@ export function createPlanToolDefinitions(deps) {
|
|
|
373
540
|
},
|
|
374
541
|
{
|
|
375
542
|
name: "todo",
|
|
543
|
+
icon: "☑️",
|
|
376
544
|
alwaysOn: true,
|
|
377
545
|
description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
|
|
378
546
|
parameters: {
|
|
@@ -450,7 +618,7 @@ export function createPlanToolDefinitions(deps) {
|
|
|
450
618
|
});
|
|
451
619
|
output += ` (${doneCount}/${subs.length})`;
|
|
452
620
|
if (doneCount === subs.length) {
|
|
453
|
-
output += `\
|
|
621
|
+
output += `\n${t("plan.subtasks_done", { stepId: String(currentStep.id) })}`;
|
|
454
622
|
}
|
|
455
623
|
return { success: true, output, display };
|
|
456
624
|
}
|
|
@@ -458,10 +626,10 @@ export function createPlanToolDefinitions(deps) {
|
|
|
458
626
|
const subs = currentStep.subtasks ?? [];
|
|
459
627
|
const lines = subs.length
|
|
460
628
|
? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`)
|
|
461
|
-
: ["
|
|
629
|
+
: [t("plan.no_subtasks")];
|
|
462
630
|
return {
|
|
463
631
|
success: true,
|
|
464
|
-
output:
|
|
632
|
+
output: `${t("plan.step_label", { stepId: String(currentStep.id), description: currentStep.description })}\n${lines.join("\n")}`,
|
|
465
633
|
};
|
|
466
634
|
}
|
|
467
635
|
return {
|
|
@@ -472,6 +640,7 @@ export function createPlanToolDefinitions(deps) {
|
|
|
472
640
|
},
|
|
473
641
|
{
|
|
474
642
|
name: "verify",
|
|
643
|
+
icon: "✅",
|
|
475
644
|
alwaysOn: true,
|
|
476
645
|
description: "Run verification for the current step. Checks files mentioned in the step description.",
|
|
477
646
|
parameters: {
|
|
@@ -487,7 +656,7 @@ export function createPlanToolDefinitions(deps) {
|
|
|
487
656
|
const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
|
|
488
657
|
if (!step)
|
|
489
658
|
return { success: false, output: t("plan.step_not_found") };
|
|
490
|
-
const result = await deps.verifier.verifyStep(step.description);
|
|
659
|
+
const result = await deps.verifier.verifyStep(step.description, step.kind);
|
|
491
660
|
if (result.noFiles) {
|
|
492
661
|
return {
|
|
493
662
|
success: true,
|
|
@@ -6,26 +6,20 @@ function generatePlanId() {
|
|
|
6
6
|
}
|
|
7
7
|
return `plan_${id}`;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Single source of truth for plan progress counting (ses_mt4fn58c: `show`
|
|
11
|
+
* said "6/7 done, complete" while `list` said "7/7" for the same plan —
|
|
12
|
+
* one counted only "done", the other counted done+skipped). A skipped step
|
|
13
|
+
* needs no further work, so it counts as settled everywhere.
|
|
14
|
+
*/
|
|
15
|
+
export function getPlanProgress(plan) {
|
|
16
|
+
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
17
|
+
const skipped = plan.steps.filter((s) => s.status === "skipped").length;
|
|
18
|
+
const total = plan.steps.length;
|
|
19
|
+
const settled = done + skipped;
|
|
20
|
+
return { done, skipped, total, settled, terminal: total > 0 && settled === total };
|
|
21
|
+
}
|
|
9
22
|
export class PlanCreator {
|
|
10
|
-
static isMultiStep(task) {
|
|
11
|
-
const fileCount = (task.match(/\b[\w./-]+\.[a-z]+\b/gi) || []).length;
|
|
12
|
-
if (fileCount > 1)
|
|
13
|
-
return true;
|
|
14
|
-
const actionWords = [
|
|
15
|
-
"implement",
|
|
16
|
-
"create",
|
|
17
|
-
"add",
|
|
18
|
-
"build",
|
|
19
|
-
"setup",
|
|
20
|
-
"configure",
|
|
21
|
-
"write",
|
|
22
|
-
"make",
|
|
23
|
-
"develop",
|
|
24
|
-
];
|
|
25
|
-
const words = task.split(/\s+/);
|
|
26
|
-
const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
|
|
27
|
-
return hasActionWord && words.length > 8;
|
|
28
|
-
}
|
|
29
23
|
static createPlan(title, stepDescriptions, baseDir, kinds) {
|
|
30
24
|
const stepCount = stepDescriptions.length;
|
|
31
25
|
return {
|
|
@@ -46,16 +40,21 @@ export class PlanCreator {
|
|
|
46
40
|
const baseTitle = plan.title.replace(/^\[\d+[^]]*\]\s*/, "");
|
|
47
41
|
const newTitle = title || baseTitle;
|
|
48
42
|
const totalSteps = kept.length + newSteps.length;
|
|
49
|
-
const added = newSteps.map((desc
|
|
50
|
-
id:
|
|
43
|
+
const added = newSteps.map((desc) => ({
|
|
44
|
+
id: -1, // renumbered below
|
|
51
45
|
description: desc,
|
|
52
46
|
status: "pending",
|
|
53
47
|
kind: "create",
|
|
54
48
|
}));
|
|
49
|
+
// Renumber every step (kept + added) sequentially. Kept steps keep their
|
|
50
|
+
// status and kind but may collide with the added ids when the original
|
|
51
|
+
// plan skipped/pending steps in the middle (e.g. done 1, done 3 → kept
|
|
52
|
+
// [1,3], added [2…] collides with kept id 3).
|
|
53
|
+
const steps = [...kept, ...added].map((s, i) => ({ ...s, id: i + 1 }));
|
|
55
54
|
return {
|
|
56
55
|
id: plan.id,
|
|
57
56
|
title: `[${totalSteps}] ${newTitle}`,
|
|
58
|
-
steps
|
|
57
|
+
steps,
|
|
59
58
|
createdAt: plan.createdAt,
|
|
60
59
|
baseDir: plan.baseDir,
|
|
61
60
|
name: plan.name,
|
|
@@ -63,10 +62,15 @@ export class PlanCreator {
|
|
|
63
62
|
}
|
|
64
63
|
static toPromptBlock(plan, currentStepIndex) {
|
|
65
64
|
const date = plan.createdAt.slice(0, 10);
|
|
65
|
+
const p = getPlanProgress(plan);
|
|
66
|
+
const skippedPart = p.skipped > 0 ? `, ${p.skipped} skipped` : "";
|
|
67
|
+
const progress = p.terminal
|
|
68
|
+
? `${p.settled}/${p.total} done${skippedPart}, complete`
|
|
69
|
+
: `${p.settled}/${p.total} done${skippedPart}, current: step ${currentStepIndex + 1}`;
|
|
66
70
|
const lines = [
|
|
67
71
|
`[${plan.id}] ${plan.title}`,
|
|
68
72
|
`Dir: ${plan.baseDir}`,
|
|
69
|
-
`Created: ${date} | Progress: ${
|
|
73
|
+
`Created: ${date} | Progress: ${progress}`,
|
|
70
74
|
``,
|
|
71
75
|
];
|
|
72
76
|
for (const step of plan.steps) {
|