added
.tickets/issues/0109-new-does-not-clear-plans-in-window-and-acp.yaml +99 -0 | new file mode 100644 | ||
| @@ -0,0 +1,99 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 109 | |
| 3 | +title: "/new clears the plans in the line mode only — not in the window, not over ACP" | |
| 4 | +state: closed | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-21T08:00:00.000Z | |
| 9 | +updatedAt: 2026-10-21T10:00:00.000Z | |
| 10 | +labels: | |
| 11 | + - kind::bug | |
| 12 | +milestone: v0.9.0 | |
| 13 | +epic: Builtin tools | |
| 14 | +related: | |
| 15 | + - 108 | |
| 16 | + - 110 | |
| 17 | +body: | | |
| 18 | + ## The problem | |
| 19 | + | |
| 20 | + `/new` means "a new session: forget the conversation". In the line mode it | |
| 21 | + also forgets the plans: `internal/agent/agent.go` startNewSession calls | |
| 22 | + `tools.ClearAllPlans()`. The two other front ends do not: | |
| 23 | + | |
| 24 | + - the window: `internal/tui/commands.go` runner.newSession resets the | |
| 25 | + history, the token count, the loop detector and the session file — not | |
| 26 | + the plans; | |
| 27 | + - ACP: `internal/acp/acp.go` front.startNewSession archives the | |
| 28 | + conversation and resets the history — not the plans. | |
| 29 | + | |
| 30 | + So after `/new` in the window or in an editor, the conversation is empty | |
| 31 | + but the plan of the previous task is still there: the next `plan list` | |
| 32 | + returns it, an unnamed `create` is called `user-request-2`, and a model that | |
| 33 | + reads the old plan may go back to tasks of a conversation the user just | |
| 34 | + closed. The same `/new` behaves differently depending on where it is typed | |
| 35 | + — the line mode is right, the two others are not. | |
| 36 | + | |
| 37 | + (Over ACP the call would today clear EVERY session's plans, since the store | |
| 38 | + is shared — ticket 108. The two fixes go together.) | |
| 39 | + | |
| 40 | + ## What fixing it solves | |
| 41 | + | |
| 42 | + - `/new` forgets the plans in the three front ends, as it forgets the | |
| 43 | + history: a new task starts with no leftover plan. | |
| 44 | + - One rule for all front ends, as the shared command table promises. | |
| 45 | + | |
| 46 | + ## Direction (to confirm) | |
| 47 | + | |
| 48 | + Call the plan reset from the shared new-session path (or from each of the | |
| 49 | + two missing ones), and — once ticket 108 is done — reset only the plans of | |
| 50 | + the session being renewed. Decide too what /resume should do with the | |
| 51 | + plans in memory (today: nothing, in every front end). | |
| 52 | +tasks: | |
| 53 | + - id: 1 | |
| 54 | + title: "Clear the plans on /new in the window and over ACP (after or with ticket 108, so an ACP /new clears only its own session) — with tests" | |
| 55 | + state: closed | |
| 56 | + priority: none | |
| 57 | + author: | |
| 58 | + name: k33g | |
| 59 | + email: ph.charriere@gmail.com | |
| 60 | + createdAt: 2026-10-21T08:00:00.000Z | |
| 61 | + updatedAt: 2026-10-21T08:00:00.000Z | |
| 62 | + - id: 2 | |
| 63 | + title: "Decide and document what /resume and session/load do with the plans in memory" | |
| 64 | + state: closed | |
| 65 | + priority: none | |
| 66 | + author: | |
| 67 | + name: k33g | |
| 68 | + email: ph.charriere@gmail.com | |
| 69 | + createdAt: 2026-10-21T08:00:00.000Z | |
| 70 | + updatedAt: 2026-10-21T08:00:00.000Z | |
| 71 | + - id: 3 | |
| 72 | + title: "Docs EN+FR: reference/tools.md plan › Storage row; reference/slash-commands.md /new" | |
| 73 | + state: closed | |
| 74 | + priority: none | |
| 75 | + author: | |
| 76 | + name: k33g | |
| 77 | + email: ph.charriere@gmail.com | |
| 78 | + createdAt: 2026-10-21T08:00:00.000Z | |
| 79 | + updatedAt: 2026-10-21T08:00:00.000Z | |
| 80 | +comments: | |
| 81 | + - id: 1 | |
| 82 | + author: | |
| 83 | + name: k33g | |
| 84 | + email: ph.charriere@gmail.com | |
| 85 | + createdAt: 2026-10-21T10:00:00.000Z | |
| 86 | + body: | | |
| 87 | + Fixed on fix/plan-mode. Decision (task 2): plans belong to a conversation | |
| 88 | + and are not saved, so a conversation that changes starts with no plan: | |
| 89 | + - line mode: /new (already) and /resume → tools.ClearPlans(""); | |
| 90 | + - window: runner.newSession and runner.resume → tools.ClearPlans(""); | |
| 91 | + - ACP: startNewSession → tools.ClearPlans(sid) (this thread only); | |
| 92 | + session/load and session/resume (the shared restore) → | |
| 93 | + tools.ClearPlans(id). | |
| 94 | + - Tests: tui TestRunnerNewAndResumeClearThePlans (fails without the fix: | |
| 95 | + checked by removing the two calls), acp | |
| 96 | + TestNewOverACPClearsOnlyItsThreadsPlans (over the wire, two sessions), | |
| 97 | + acp TestLoadSessionStartsWithNoPlan. | |
| 98 | + - Docs EN+FR: reference/tools.md plan › Storage; slash-commands /new | |
| 99 | + (terminal + ACP) and /resume. | |
| new file mode 100644 | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 109 | ||
| 3 | +title: "/new clears the plans in the line mode only — not in the window, not over ACP" | ||
| 4 | +state: closed | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-21T08:00:00.000Z | ||
| 9 | +updatedAt: 2026-10-21T10:00:00.000Z | ||
| 10 | +labels: | ||
| 11 | + - kind::bug | ||
| 12 | +milestone: v0.9.0 | ||
| 13 | +epic: Builtin tools | ||
| 14 | +related: | ||
| 15 | + - 108 | ||
| 16 | + - 110 | ||
| 17 | +body: | | ||
| 18 | + ## The problem | ||
| 19 | + | ||
| 20 | + `/new` means "a new session: forget the conversation". In the line mode it | ||
| 21 | + also forgets the plans: `internal/agent/agent.go` startNewSession calls | ||
| 22 | + `tools.ClearAllPlans()`. The two other front ends do not: | ||
| 23 | + | ||
| 24 | + - the window: `internal/tui/commands.go` runner.newSession resets the | ||
| 25 | + history, the token count, the loop detector and the session file — not | ||
| 26 | + the plans; | ||
| 27 | + - ACP: `internal/acp/acp.go` front.startNewSession archives the | ||
| 28 | + conversation and resets the history — not the plans. | ||
| 29 | + | ||
| 30 | + So after `/new` in the window or in an editor, the conversation is empty | ||
| 31 | + but the plan of the previous task is still there: the next `plan list` | ||
| 32 | + returns it, an unnamed `create` is called `user-request-2`, and a model that | ||
| 33 | + reads the old plan may go back to tasks of a conversation the user just | ||
| 34 | + closed. The same `/new` behaves differently depending on where it is typed | ||
| 35 | + — the line mode is right, the two others are not. | ||
| 36 | + | ||
| 37 | + (Over ACP the call would today clear EVERY session's plans, since the store | ||
| 38 | + is shared — ticket 108. The two fixes go together.) | ||
| 39 | + | ||
| 40 | + ## What fixing it solves | ||
| 41 | + | ||
| 42 | + - `/new` forgets the plans in the three front ends, as it forgets the | ||
| 43 | + history: a new task starts with no leftover plan. | ||
| 44 | + - One rule for all front ends, as the shared command table promises. | ||
| 45 | + | ||
| 46 | + ## Direction (to confirm) | ||
| 47 | + | ||
| 48 | + Call the plan reset from the shared new-session path (or from each of the | ||
| 49 | + two missing ones), and — once ticket 108 is done — reset only the plans of | ||
| 50 | + the session being renewed. Decide too what /resume should do with the | ||
| 51 | + plans in memory (today: nothing, in every front end). | ||
| 52 | +tasks: | ||
| 53 | + - id: 1 | ||
| 54 | + title: "Clear the plans on /new in the window and over ACP (after or with ticket 108, so an ACP /new clears only its own session) — with tests" | ||
| 55 | + state: closed | ||
| 56 | + priority: none | ||
| 57 | + author: | ||
| 58 | + name: k33g | ||
| 59 | + email: ph.charriere@gmail.com | ||
| 60 | + createdAt: 2026-10-21T08:00:00.000Z | ||
| 61 | + updatedAt: 2026-10-21T08:00:00.000Z | ||
| 62 | + - id: 2 | ||
| 63 | + title: "Decide and document what /resume and session/load do with the plans in memory" | ||
| 64 | + state: closed | ||
| 65 | + priority: none | ||
| 66 | + author: | ||
| 67 | + name: k33g | ||
| 68 | + email: ph.charriere@gmail.com | ||
| 69 | + createdAt: 2026-10-21T08:00:00.000Z | ||
| 70 | + updatedAt: 2026-10-21T08:00:00.000Z | ||
| 71 | + - id: 3 | ||
| 72 | + title: "Docs EN+FR: reference/tools.md plan › Storage row; reference/slash-commands.md /new" | ||
| 73 | + state: closed | ||
| 74 | + priority: none | ||
| 75 | + author: | ||
| 76 | + name: k33g | ||
| 77 | + email: ph.charriere@gmail.com | ||
| 78 | + createdAt: 2026-10-21T08:00:00.000Z | ||
| 79 | + updatedAt: 2026-10-21T08:00:00.000Z | ||
| 80 | +comments: | ||
| 81 | + - id: 1 | ||
| 82 | + author: | ||
| 83 | + name: k33g | ||
| 84 | + email: ph.charriere@gmail.com | ||
| 85 | + createdAt: 2026-10-21T10:00:00.000Z | ||
| 86 | + body: | | ||
| 87 | + Fixed on fix/plan-mode. Decision (task 2): plans belong to a conversation | ||
| 88 | + and are not saved, so a conversation that changes starts with no plan: | ||
| 89 | + - line mode: /new (already) and /resume → tools.ClearPlans(""); | ||
| 90 | + - window: runner.newSession and runner.resume → tools.ClearPlans(""); | ||
| 91 | + - ACP: startNewSession → tools.ClearPlans(sid) (this thread only); | ||
| 92 | + session/load and session/resume (the shared restore) → | ||
| 93 | + tools.ClearPlans(id). | ||
| 94 | + - Tests: tui TestRunnerNewAndResumeClearThePlans (fails without the fix: | ||
| 95 | + checked by removing the two calls), acp | ||
| 96 | + TestNewOverACPClearsOnlyItsThreadsPlans (over the wire, two sessions), | ||
| 97 | + acp TestLoadSessionStartsWithNoPlan. | ||
| 98 | + - Docs EN+FR: reference/tools.md plan › Storage; slash-commands /new | ||
| 99 | + (terminal + ACP) and /resume. | ||
added
.tickets/issues/0110-plans-how-to-promises-progress-lines-mm-never-prints.yaml +134 -0 | new file mode 100644 | ||
| @@ -0,0 +1,134 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 110 | |
| 3 | +title: "Plans how-to shows progress lines (📋 [2/3] …) that mm never prints" | |
| 4 | +state: closed | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-21T08:00:00.000Z | |
| 9 | +updatedAt: 2026-10-21T10:00:00.000Z | |
| 10 | +labels: | |
| 11 | + - kind::bug | |
| 12 | +milestone: v0.9.0 | |
| 13 | +epic: Documentation | |
| 14 | +related: | |
| 15 | + - 108 | |
| 16 | + - 109 | |
| 17 | +body: | | |
| 18 | + ## The problem | |
| 19 | + | |
| 20 | + docs/en/how-to/manage-plans-and-tasks.md (and its French twin) promises | |
| 21 | + that mm shows where the agent is in a plan "after each response", with | |
| 22 | + lines like: | |
| 23 | + | |
| 24 | + 📋 [2/3] 🔄 Task 2: Add a license (in_progress) | |
| 25 | + 📋 [3/3] ✅ All tasks complete! | |
| 26 | + | |
| 27 | + and builds most of the page on them ("The 📋 line shows you exactly where | |
| 28 | + the agent is in your plan", worked examples, a "status line format" | |
| 29 | + section). No code prints those lines. What mm actually does | |
| 30 | + (internal/tools/plan.go): | |
| 31 | + | |
| 32 | + - line mode: it prints ONE line before each call, `📋 plan: <action> | |
| 33 | + <name>` (e.g. `📋 plan: update user-request-1`), and nothing after: the | |
| 34 | + result is not echoed; | |
| 35 | + - the result — JSON text holding a `status` field such as | |
| 36 | + `[2/3] 🔄 Task 2: Add a license (in_progress)` — goes to the MODEL. The | |
| 37 | + tool's description asks the model to "include [it] in your response to | |
| 38 | + the user"; a model that does shows a similar line, without the 📋, in its | |
| 39 | + own words; one that does not shows nothing; | |
| 40 | + - window and editor: the call appears as a tool call, its raw JSON result | |
| 41 | + in the tool's frame. | |
| 42 | + | |
| 43 | + The page also announces a `/plans` command ("not yet implemented in v1 — | |
| 44 | + coming in v2") with a sample output, in a section titled "Plan Management | |
| 45 | + Commands". | |
| 46 | + | |
| 47 | + So a user who reads the guide expects a progress line mm never draws, and | |
| 48 | + cannot tell whether a missing line means a bug, an old version, or a model | |
| 49 | + that skipped it. | |
| 50 | + | |
| 51 | + ## What fixing it solves | |
| 52 | + | |
| 53 | + - The guide describes what the user will see, front end by front end, and | |
| 54 | + says plainly that progress shown in the answer depends on the model. | |
| 55 | + - If progress lines ARE wanted, that is a feature to decide (mm printing | |
| 56 | + the status after each plan call, or after each answer), not something | |
| 57 | + the docs pretend exists. | |
| 58 | + | |
| 59 | + ## Direction (to decide) | |
| 60 | + | |
| 61 | + Either rewrite the how-to (EN+FR) to match the code — the reference | |
| 62 | + section written on 2026-10-21 already does —, or implement the promised | |
| 63 | + line (e.g. print the `status` line after each plan action in the line mode | |
| 64 | + and as a notice in the window) and then document it. The `/plans` section | |
| 65 | + should go, or become a ticket of its own. | |
| 66 | +tasks: | |
| 67 | + - id: 1 | |
| 68 | + title: "Decide: rewrite the how-to to match mm, or implement a progress line mm prints itself" | |
| 69 | + state: closed | |
| 70 | + priority: none | |
| 71 | + author: | |
| 72 | + name: k33g | |
| 73 | + email: ph.charriere@gmail.com | |
| 74 | + createdAt: 2026-10-21T08:00:00.000Z | |
| 75 | + updatedAt: 2026-10-21T08:00:00.000Z | |
| 76 | + - id: 2 | |
| 77 | + title: "Docs EN+FR: manage-plans-and-tasks.md matches what each front end shows; the /plans section removed or turned into a ticket" | |
| 78 | + state: closed | |
| 79 | + priority: none | |
| 80 | + author: | |
| 81 | + name: k33g | |
| 82 | + email: ph.charriere@gmail.com | |
| 83 | + createdAt: 2026-10-21T08:00:00.000Z | |
| 84 | + updatedAt: 2026-10-21T08:00:00.000Z | |
| 85 | +comments: | |
| 86 | + - id: 1 | |
| 87 | + author: | |
| 88 | + name: k33g | |
| 89 | + email: ph.charriere@gmail.com | |
| 90 | + createdAt: 2026-10-21T10:00:00.000Z | |
| 91 | + body: | | |
| 92 | + Fixed on fix/plan-mode. Decision (task 1): implement the line, then | |
| 93 | + document it. | |
| 94 | + - internal/tools/plan.go progressLine: after every plan action whose result | |
| 95 | + has a status (create — which now returns one —, update, next, status) mm | |
| 96 | + shows `📋 <status line>`: printed in the line mode, a notice in the window | |
| 97 | + (ui.Noticer); an editor shows the status in the tool call's result. | |
| 98 | + - Making it visible exposed a pre-existing defect (live run, Qwen3.5 4B): | |
| 99 | + the status line followed plan.Current, which only `next` moved, so after | |
| 100 | + `update 1 done` it still showed task 1, and a later `next` put the | |
| 101 | + finished task 1 back in progress. Fixed here, since the progress shown | |
| 102 | + must be right: the line shows the first task in progress, else the first | |
| 103 | + open one (`Plan finished: D done, F failed` when over with failures); | |
| 104 | + `update … in_progress` makes the task current; `next` skips finished | |
| 105 | + tasks. TestRunPlanAction encoded the old behaviour (next after update 1 | |
| 106 | + in_progress restarted task 1) — expectation corrected; quote escaping | |
| 107 | + kept in its own test. | |
| 108 | + - Cosmetic: the call title of an unnamed plan no longer ends with a space | |
| 109 | + (planTitle). | |
| 110 | + - Live (mm -p, Qwen3.5 4B): 📋 [1/2] … open → 📋 [2/2] … open → 📋 [2/2] | |
| 111 | + All tasks complete!, files written. | |
| 112 | + - Docs EN+FR: how-to manage-plans-and-tasks rewritten (450 → 64 lines: what | |
| 113 | + each front end shows, the measured sample, steering in words, variants, | |
| 114 | + troubleshooting; the /plans section removed — no such command), now in | |
| 115 | + both indexes; reference/tools.md plan (create/update/next/status line/ | |
| 116 | + display rows); root README Plans line. | |
| 117 | + - id: 2 | |
| 118 | + author: | |
| 119 | + name: k33g | |
| 120 | + email: ph.charriere@gmail.com | |
| 121 | + createdAt: 2026-10-21T11:00:00.000Z | |
| 122 | + body: | | |
| 123 | + Follow-up, found by demos/plans (user's request: a plans demo): Qwen3.5 | |
| 124 | + 4B never passed `name` after create; every update failed ("no plan | |
| 125 | + named ''") and the turn ended in runaway tool calls. An action without | |
| 126 | + a name now works on the conversation's most recently created plan | |
| 127 | + (PlanStore.LatestName, by creation order — CreatedAt has one-second | |
| 128 | + precision); the field's description said "Defaults to 'user-request'", | |
| 129 | + a name no plan has. Test TestPlanActionWithoutANameUsesTheLatestPlan. | |
| 130 | + Docs EN+FR: reference plan › name. demos/plans measured: 4-step Go | |
| 131 | + program 3/3 (go test passes), failing push 3/3 "Plan finished: 2 done, | |
| 132 | + 1 failed" once the prompt forbids stand-ins (before: marked done 3/3; in | |
| 133 | + the run inspected, a bare repo created in /tmp and force-pushed). | |
| 134 | + | |
| new file mode 100644 | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 110 | ||
| 3 | +title: "Plans how-to shows progress lines (📋 [2/3] …) that mm never prints" | ||
| 4 | +state: closed | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-21T08:00:00.000Z | ||
| 9 | +updatedAt: 2026-10-21T10:00:00.000Z | ||
| 10 | +labels: | ||
| 11 | + - kind::bug | ||
| 12 | +milestone: v0.9.0 | ||
| 13 | +epic: Documentation | ||
| 14 | +related: | ||
| 15 | + - 108 | ||
| 16 | + - 109 | ||
| 17 | +body: | | ||
| 18 | + ## The problem | ||
| 19 | + | ||
| 20 | + docs/en/how-to/manage-plans-and-tasks.md (and its French twin) promises | ||
| 21 | + that mm shows where the agent is in a plan "after each response", with | ||
| 22 | + lines like: | ||
| 23 | + | ||
| 24 | + 📋 [2/3] 🔄 Task 2: Add a license (in_progress) | ||
| 25 | + 📋 [3/3] ✅ All tasks complete! | ||
| 26 | + | ||
| 27 | + and builds most of the page on them ("The 📋 line shows you exactly where | ||
| 28 | + the agent is in your plan", worked examples, a "status line format" | ||
| 29 | + section). No code prints those lines. What mm actually does | ||
| 30 | + (internal/tools/plan.go): | ||
| 31 | + | ||
| 32 | + - line mode: it prints ONE line before each call, `📋 plan: <action> | ||
| 33 | + <name>` (e.g. `📋 plan: update user-request-1`), and nothing after: the | ||
| 34 | + result is not echoed; | ||
| 35 | + - the result — JSON text holding a `status` field such as | ||
| 36 | + `[2/3] 🔄 Task 2: Add a license (in_progress)` — goes to the MODEL. The | ||
| 37 | + tool's description asks the model to "include [it] in your response to | ||
| 38 | + the user"; a model that does shows a similar line, without the 📋, in its | ||
| 39 | + own words; one that does not shows nothing; | ||
| 40 | + - window and editor: the call appears as a tool call, its raw JSON result | ||
| 41 | + in the tool's frame. | ||
| 42 | + | ||
| 43 | + The page also announces a `/plans` command ("not yet implemented in v1 — | ||
| 44 | + coming in v2") with a sample output, in a section titled "Plan Management | ||
| 45 | + Commands". | ||
| 46 | + | ||
| 47 | + So a user who reads the guide expects a progress line mm never draws, and | ||
| 48 | + cannot tell whether a missing line means a bug, an old version, or a model | ||
| 49 | + that skipped it. | ||
| 50 | + | ||
| 51 | + ## What fixing it solves | ||
| 52 | + | ||
| 53 | + - The guide describes what the user will see, front end by front end, and | ||
| 54 | + says plainly that progress shown in the answer depends on the model. | ||
| 55 | + - If progress lines ARE wanted, that is a feature to decide (mm printing | ||
| 56 | + the status after each plan call, or after each answer), not something | ||
| 57 | + the docs pretend exists. | ||
| 58 | + | ||
| 59 | + ## Direction (to decide) | ||
| 60 | + | ||
| 61 | + Either rewrite the how-to (EN+FR) to match the code — the reference | ||
| 62 | + section written on 2026-10-21 already does —, or implement the promised | ||
| 63 | + line (e.g. print the `status` line after each plan action in the line mode | ||
| 64 | + and as a notice in the window) and then document it. The `/plans` section | ||
| 65 | + should go, or become a ticket of its own. | ||
| 66 | +tasks: | ||
| 67 | + - id: 1 | ||
| 68 | + title: "Decide: rewrite the how-to to match mm, or implement a progress line mm prints itself" | ||
| 69 | + state: closed | ||
| 70 | + priority: none | ||
| 71 | + author: | ||
| 72 | + name: k33g | ||
| 73 | + email: ph.charriere@gmail.com | ||
| 74 | + createdAt: 2026-10-21T08:00:00.000Z | ||
| 75 | + updatedAt: 2026-10-21T08:00:00.000Z | ||
| 76 | + - id: 2 | ||
| 77 | + title: "Docs EN+FR: manage-plans-and-tasks.md matches what each front end shows; the /plans section removed or turned into a ticket" | ||
| 78 | + state: closed | ||
| 79 | + priority: none | ||
| 80 | + author: | ||
| 81 | + name: k33g | ||
| 82 | + email: ph.charriere@gmail.com | ||
| 83 | + createdAt: 2026-10-21T08:00:00.000Z | ||
| 84 | + updatedAt: 2026-10-21T08:00:00.000Z | ||
| 85 | +comments: | ||
| 86 | + - id: 1 | ||
| 87 | + author: | ||
| 88 | + name: k33g | ||
| 89 | + email: ph.charriere@gmail.com | ||
| 90 | + createdAt: 2026-10-21T10:00:00.000Z | ||
| 91 | + body: | | ||
| 92 | + Fixed on fix/plan-mode. Decision (task 1): implement the line, then | ||
| 93 | + document it. | ||
| 94 | + - internal/tools/plan.go progressLine: after every plan action whose result | ||
| 95 | + has a status (create — which now returns one —, update, next, status) mm | ||
| 96 | + shows `📋 <status line>`: printed in the line mode, a notice in the window | ||
| 97 | + (ui.Noticer); an editor shows the status in the tool call's result. | ||
| 98 | + - Making it visible exposed a pre-existing defect (live run, Qwen3.5 4B): | ||
| 99 | + the status line followed plan.Current, which only `next` moved, so after | ||
| 100 | + `update 1 done` it still showed task 1, and a later `next` put the | ||
| 101 | + finished task 1 back in progress. Fixed here, since the progress shown | ||
| 102 | + must be right: the line shows the first task in progress, else the first | ||
| 103 | + open one (`Plan finished: D done, F failed` when over with failures); | ||
| 104 | + `update … in_progress` makes the task current; `next` skips finished | ||
| 105 | + tasks. TestRunPlanAction encoded the old behaviour (next after update 1 | ||
| 106 | + in_progress restarted task 1) — expectation corrected; quote escaping | ||
| 107 | + kept in its own test. | ||
| 108 | + - Cosmetic: the call title of an unnamed plan no longer ends with a space | ||
| 109 | + (planTitle). | ||
| 110 | + - Live (mm -p, Qwen3.5 4B): 📋 [1/2] … open → 📋 [2/2] … open → 📋 [2/2] | ||
| 111 | + All tasks complete!, files written. | ||
| 112 | + - Docs EN+FR: how-to manage-plans-and-tasks rewritten (450 → 64 lines: what | ||
| 113 | + each front end shows, the measured sample, steering in words, variants, | ||
| 114 | + troubleshooting; the /plans section removed — no such command), now in | ||
| 115 | + both indexes; reference/tools.md plan (create/update/next/status line/ | ||
| 116 | + display rows); root README Plans line. | ||
| 117 | + - id: 2 | ||
| 118 | + author: | ||
| 119 | + name: k33g | ||
| 120 | + email: ph.charriere@gmail.com | ||
| 121 | + createdAt: 2026-10-21T11:00:00.000Z | ||
| 122 | + body: | | ||
| 123 | + Follow-up, found by demos/plans (user's request: a plans demo): Qwen3.5 | ||
| 124 | + 4B never passed `name` after create; every update failed ("no plan | ||
| 125 | + named ''") and the turn ended in runaway tool calls. An action without | ||
| 126 | + a name now works on the conversation's most recently created plan | ||
| 127 | + (PlanStore.LatestName, by creation order — CreatedAt has one-second | ||
| 128 | + precision); the field's description said "Defaults to 'user-request'", | ||
| 129 | + a name no plan has. Test TestPlanActionWithoutANameUsesTheLatestPlan. | ||
| 130 | + Docs EN+FR: reference plan › name. demos/plans measured: 4-step Go | ||
| 131 | + program 3/3 (go test passes), failing push 3/3 "Plan finished: 2 done, | ||
| 132 | + 1 failed" once the prompt forbids stand-ins (before: marked done 3/3; in | ||
| 133 | + the run inspected, a bare repo created in /tmp and force-pushed). | ||
| 134 | + | ||
added
.tickets/issues/0111-using-the-settings-guide-create-a-claude-skill.yaml +15 -0 | new file mode 100644 | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 111 | |
| 3 | +title: Using the settings guide create a Claude skill | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-21T04:27:19.433Z | |
| 9 | +updatedAt: 2026-10-21T04:41:59.368Z | |
| 10 | +labels: [] | |
| 11 | +milestone: v1.0.0 | |
| 12 | +body: | | |
| 13 | + the Claude skill will produce a descriptive doc about the model and a config file (`agent.yaml`) | |
| 14 | +tasks: [] | |
| 15 | +comments: [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 111 | ||
| 3 | +title: Using the settings guide create a Claude skill | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-21T04:27:19.433Z | ||
| 9 | +updatedAt: 2026-10-21T04:41:59.368Z | ||
| 10 | +labels: [] | ||
| 11 | +milestone: v1.0.0 | ||
| 12 | +body: | | ||
| 13 | + the Claude skill will produce a descriptive doc about the model and a config file (`agent.yaml`) | ||
| 14 | +tasks: [] | ||
| 15 | +comments: [] | ||
added
.tickets/issues/0112-add-a-help-window-in-tui-mode-for-the-ui-shortcu.yaml +15 -0 | new file mode 100644 | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 112 | |
| 3 | +title: Add a help window in TUI mode for the UI shortcuts | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-21T04:43:03.511Z | |
| 9 | +updatedAt: 2026-10-21T04:43:13.927Z | |
| 10 | +labels: | |
| 11 | + - kind::feature | |
| 12 | +milestone: v0.9.0 | |
| 13 | +body: "" | |
| 14 | +tasks: [] | |
| 15 | +comments: [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 112 | ||
| 3 | +title: Add a help window in TUI mode for the UI shortcuts | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-21T04:43:03.511Z | ||
| 9 | +updatedAt: 2026-10-21T04:43:13.927Z | ||
| 10 | +labels: | ||
| 11 | + - kind::feature | ||
| 12 | +milestone: v0.9.0 | ||
| 13 | +body: "" | ||
| 14 | +tasks: [] | ||
| 15 | +comments: [] | ||
modified
README.md +6 -4 | @@ -65,7 +65,7 @@ Type a question at the `>` prompt. Each command the model runs is shown with its | ||
| 65 | 65 | mm -tui |
| 66 | 66 | ``` |
| 67 | 67 | |
| 68 | -The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn, **F1** lists the keys. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked). | |
| 68 | +The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn (or clears the box when nothing runs), **F1** lists the keys. With sessions on, **↑** in the box brings back your earlier questions, from this session and the project's previous ones. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked). | |
| 69 | 69 | |
| 70 | 70 | ### 3. Inside an editor (ACP) |
| 71 | 71 | |
| @@ -96,7 +96,7 @@ Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand | ||
| 96 | 96 | `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required. |
| 97 | 97 | |
| 98 | 98 | 3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked. |
| 99 | -4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu; `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu). | |
| 99 | +4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu). | |
| 100 | 100 | 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there. |
| 101 | 101 | |
| 102 | 102 |  |
| @@ -114,11 +114,11 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In | ||
| 114 | 114 | - **Project instructions**: an `AGENTS.md` in the project directory is added to the system prompt of every conversation. See [Give the agent your project's instructions](docs/en/how-to/give-project-instructions.md). |
| 115 | 115 | - **Answers as JSON**: declare the shape of the answer as Go types under `structuredOutput`, and `mm -p "…" | jq` works. See [Get answers as JSON](docs/en/how-to/get-json-answers.md). |
| 116 | 116 | - **MCP servers**: their tools, wrapped as mm tools. See the [MCP tutorial](docs/en/tutorials/mcp-getting-started.md). |
| 117 | -- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md). | |
| 117 | +- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in the window, ↑ recalls your earlier questions to send them again; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md). | |
| 118 | 118 | - **Trace**: `AGENT_TRACE=true mm …` writes every request to the model and every response to `.mm/traces/`. See [See the JSON sent to the model](docs/en/how-to/inspect-model-traffic.md). |
| 119 | 119 | - **Hooks**: your own commands — in any language — run before a question reaches the model, before and after each tool call, after the turn, and around each hand-off to another agent; they can deny a call, stop or rewrite a question, add context, mask what leaves for another agent, replace what comes back, or log. `displayHooks: true` shows each hook that ran and what it decided, after the answer (an editor shows each one as a tool call). See [Guard the agent with hooks](docs/en/how-to/guard-the-agent-with-hooks.md), [Control the hand-offs to other agents with hooks](docs/en/how-to/control-agent-calls-with-hooks.md), `demos/hooks/` and `demos/hooks-ask/`. |
| 120 | 120 | - **Guard models**: have every question checked by guard models (IBM's Granite Guardian), one agent per risk, before your assistant answers — a `preGeneration` hook asks them without the conversation history, and the assistant refuses or answers according to their verdicts. See [Screen questions with guard models](docs/en/how-to/screen-questions-with-guard-models.md), `demos/guard/` and `demos/guards/`. |
| 121 | -- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan and show its progress after each answer. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md). | |
| 121 | +- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan; mm shows its progress (`📋 [2/3] …`) after each plan action — per conversation, cleared by `/new` and `/resume`. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md). | |
| 122 | 122 | - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md). |
| 123 | 123 | - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`. |
| 124 | 124 | - **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`. |
| @@ -156,6 +156,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h | ||
| 156 | 156 | | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks | |
| 157 | 157 | | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results | |
| 158 | 158 | | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results | |
| 159 | +| `demos/plans/` | the `plan` tool at work: Planner (Qwen3.5 4B on llmman) builds a tiny Go program in four steps — module, code, test, README — while mm shows `📋 [C/N] …` after each plan action; a failing variant ends on `Plan finished: 2 done, 1 failed`; measured results | | |
| 159 | 160 | | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results | |
| 160 | 161 | | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find | |
| 161 | 162 | | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results | |
| @@ -163,6 +164,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h | ||
| 163 | 164 | ## Learn more |
| 164 | 165 | |
| 165 | 166 | - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French. |
| 167 | +- [Release notes](release-notes/README.md) — what changed in each version, in English and French. | |
| 166 | 168 | - [`guides/`](guides/) — practical guides; for now, the agent settings for small models, in [English](guides/agent-settings-for-small-models.md) and [French](guides/reglages-agents-petits-modeles.md): timeouts, `maxTurns`, `maxOutput`, `agentsMd.maxChars`, context compression, sampling — what each one does, its default, and how to set it for a small local model. |
| 167 | 169 | - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool. |
| 168 | 170 | - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md). |
| @@ -65,7 +65,7 @@ Type a question at the `>` prompt. Each command the model runs is shown with its | |||
| 65 | mm -tui | 65 | mm -tui |
| 66 | ``` | 66 | ``` |
| 67 | 67 | ||
| 68 | -The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn, **F1** lists the keys. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked). | 68 | +The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn (or clears the box when nothing runs), **F1** lists the keys. With sessions on, **↑** in the box brings back your earlier questions, from this session and the project's previous ones. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked). |
| 69 | 69 | ||
| 70 | ### 3. Inside an editor (ACP) | 70 | ### 3. Inside an editor (ACP) |
| 71 | 71 | ||
| @@ -96,7 +96,7 @@ Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand | |||
| 96 | `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required. | 96 | `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required. |
| 97 | 97 | ||
| 98 | 3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked. | 98 | 3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked. |
| 99 | -4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu; `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu). | 99 | +4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu). |
| 100 | 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there. | 100 | 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there. |
| 101 | 101 | ||
| 102 |  | 102 |  |
| @@ -114,11 +114,11 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In | |||
| 114 | - **Project instructions**: an `AGENTS.md` in the project directory is added to the system prompt of every conversation. See [Give the agent your project's instructions](docs/en/how-to/give-project-instructions.md). | 114 | - **Project instructions**: an `AGENTS.md` in the project directory is added to the system prompt of every conversation. See [Give the agent your project's instructions](docs/en/how-to/give-project-instructions.md). |
| 115 | - **Answers as JSON**: declare the shape of the answer as Go types under `structuredOutput`, and `mm -p "…" | jq` works. See [Get answers as JSON](docs/en/how-to/get-json-answers.md). | 115 | - **Answers as JSON**: declare the shape of the answer as Go types under `structuredOutput`, and `mm -p "…" | jq` works. See [Get answers as JSON](docs/en/how-to/get-json-answers.md). |
| 116 | - **MCP servers**: their tools, wrapped as mm tools. See the [MCP tutorial](docs/en/tutorials/mcp-getting-started.md). | 116 | - **MCP servers**: their tools, wrapped as mm tools. See the [MCP tutorial](docs/en/tutorials/mcp-getting-started.md). |
| 117 | -- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md). | 117 | +- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in the window, ↑ recalls your earlier questions to send them again; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md). |
| 118 | - **Trace**: `AGENT_TRACE=true mm …` writes every request to the model and every response to `.mm/traces/`. See [See the JSON sent to the model](docs/en/how-to/inspect-model-traffic.md). | 118 | - **Trace**: `AGENT_TRACE=true mm …` writes every request to the model and every response to `.mm/traces/`. See [See the JSON sent to the model](docs/en/how-to/inspect-model-traffic.md). |
| 119 | - **Hooks**: your own commands — in any language — run before a question reaches the model, before and after each tool call, after the turn, and around each hand-off to another agent; they can deny a call, stop or rewrite a question, add context, mask what leaves for another agent, replace what comes back, or log. `displayHooks: true` shows each hook that ran and what it decided, after the answer (an editor shows each one as a tool call). See [Guard the agent with hooks](docs/en/how-to/guard-the-agent-with-hooks.md), [Control the hand-offs to other agents with hooks](docs/en/how-to/control-agent-calls-with-hooks.md), `demos/hooks/` and `demos/hooks-ask/`. | 119 | - **Hooks**: your own commands — in any language — run before a question reaches the model, before and after each tool call, after the turn, and around each hand-off to another agent; they can deny a call, stop or rewrite a question, add context, mask what leaves for another agent, replace what comes back, or log. `displayHooks: true` shows each hook that ran and what it decided, after the answer (an editor shows each one as a tool call). See [Guard the agent with hooks](docs/en/how-to/guard-the-agent-with-hooks.md), [Control the hand-offs to other agents with hooks](docs/en/how-to/control-agent-calls-with-hooks.md), `demos/hooks/` and `demos/hooks-ask/`. |
| 120 | - **Guard models**: have every question checked by guard models (IBM's Granite Guardian), one agent per risk, before your assistant answers — a `preGeneration` hook asks them without the conversation history, and the assistant refuses or answers according to their verdicts. See [Screen questions with guard models](docs/en/how-to/screen-questions-with-guard-models.md), `demos/guard/` and `demos/guards/`. | 120 | - **Guard models**: have every question checked by guard models (IBM's Granite Guardian), one agent per risk, before your assistant answers — a `preGeneration` hook asks them without the conversation history, and the assistant refuses or answers according to their verdicts. See [Screen questions with guard models](docs/en/how-to/screen-questions-with-guard-models.md), `demos/guard/` and `demos/guards/`. |
| 121 | -- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan and show its progress after each answer. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md). | 121 | +- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan; mm shows its progress (`📋 [2/3] …`) after each plan action — per conversation, cleared by `/new` and `/resume`. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md). |
| 122 | - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md). | 122 | - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md). |
| 123 | - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`. | 123 | - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`. |
| 124 | - **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`. | 124 | - **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`. |
| @@ -156,6 +156,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h | |||
| 156 | | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks | | 156 | | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks | |
| 157 | | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results | | 157 | | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results | |
| 158 | | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results | | 158 | | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results | |
| 159 | +| `demos/plans/` | the `plan` tool at work: Planner (Qwen3.5 4B on llmman) builds a tiny Go program in four steps — module, code, test, README — while mm shows `📋 [C/N] …` after each plan action; a failing variant ends on `Plan finished: 2 done, 1 failed`; measured results | | ||
| 159 | | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results | | 160 | | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results | |
| 160 | | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find | | 161 | | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find | |
| 161 | | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results | | 162 | | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results | |
| @@ -163,6 +164,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h | |||
| 163 | ## Learn more | 164 | ## Learn more |
| 164 | 165 | ||
| 165 | - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French. | 166 | - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French. |
| 167 | +- [Release notes](release-notes/README.md) — what changed in each version, in English and French. | ||
| 166 | - [`guides/`](guides/) — practical guides; for now, the agent settings for small models, in [English](guides/agent-settings-for-small-models.md) and [French](guides/reglages-agents-petits-modeles.md): timeouts, `maxTurns`, `maxOutput`, `agentsMd.maxChars`, context compression, sampling — what each one does, its default, and how to set it for a small local model. | 168 | - [`guides/`](guides/) — practical guides; for now, the agent settings for small models, in [English](guides/agent-settings-for-small-models.md) and [French](guides/reglages-agents-petits-modeles.md): timeouts, `maxTurns`, `maxOutput`, `agentsMd.maxChars`, context compression, sampling — what each one does, its default, and how to set it for a small local model. |
| 167 | - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool. | 169 | - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool. |
| 168 | - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md). | 170 | - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md). |
added
about_tools_test.go +21 -0 | new file mode 100644 | ||
| @@ -0,0 +1,21 @@ | ||
| 1 | +package main | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "strings" | |
| 5 | + "testing" | |
| 6 | + | |
| 7 | + "mm/internal/config" | |
| 8 | + "mm/internal/version" | |
| 9 | +) | |
| 10 | + | |
| 11 | +// --about lists the built-in tools by hand; every name the config reserves | |
| 12 | +// must be in it, so a new built-in tool cannot be forgotten there again (the | |
| 13 | +// list stopped at lsp for seven releases). | |
| 14 | +func TestAboutListsEveryBuiltinTool(t *testing.T) { | |
| 15 | + about := version.About() | |
| 16 | + for _, name := range config.BuiltinToolNames { | |
| 17 | + if !strings.Contains(about, name) { | |
| 18 | + t.Errorf("mm --about does not list the built-in tool %q", name) | |
| 19 | + } | |
| 20 | + } | |
| 21 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | +package main | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "strings" | ||
| 5 | + "testing" | ||
| 6 | + | ||
| 7 | + "mm/internal/config" | ||
| 8 | + "mm/internal/version" | ||
| 9 | +) | ||
| 10 | + | ||
| 11 | +// --about lists the built-in tools by hand; every name the config reserves | ||
| 12 | +// must be in it, so a new built-in tool cannot be forgotten there again (the | ||
| 13 | +// list stopped at lsp for seven releases). | ||
| 14 | +func TestAboutListsEveryBuiltinTool(t *testing.T) { | ||
| 15 | + about := version.About() | ||
| 16 | + for _, name := range config.BuiltinToolNames { | ||
| 17 | + if !strings.Contains(about, name) { | ||
| 18 | + t.Errorf("mm --about does not list the built-in tool %q", name) | ||
| 19 | + } | ||
| 20 | + } | ||
| 21 | +} | ||
added
demos/plans/README.en.md +71 -0 | new file mode 100644 | ||
| @@ -0,0 +1,71 @@ | ||
| 1 | +# mini-me demo: plans | |
| 2 | + | |
| 3 | +*[Version française](README.fr.md)* | |
| 4 | + | |
| 5 | +Planner builds a tiny Go program step by step — module, code, test, run the tests, README — and works through the steps with its `plan` tool. mm shows where the plan is after every plan action, whatever the model writes in its answer. The model is Qwen3.5 4B, served by [llmman](https://github.com/llmmanorg/llmman). Go must be on the `PATH`. | |
| 6 | + | |
| 7 | +## What is here | |
| 8 | + | |
| 9 | +| File | Role | | |
| 10 | +|---|---| | |
| 11 | +| `agent.yaml` | Planner: `planTool: true`, `bash` and the file tools; a system prompt that says how to use the plan (create, then `in_progress` / `done` / `failed` task by task) and to mark `failed`, not fake, a task that needs something missing. | | |
| 12 | +| `reset.sh` | Removes `./hello`, the project the agent builds, so the next run starts from nothing. | | |
| 13 | + | |
| 14 | +## Run the demo | |
| 15 | + | |
| 16 | +```bash | |
| 17 | +llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | |
| 18 | +cd demos/plans | |
| 19 | +./reset.sh | |
| 20 | +mm agent.yaml | |
| 21 | +``` | |
| 22 | + | |
| 23 | +Then ask: | |
| 24 | + | |
| 25 | +```text | |
| 26 | +Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program. | |
| 27 | +``` | |
| 28 | + | |
| 29 | +In the line mode the progress shows between the tool calls (output of a real run): | |
| 30 | + | |
| 31 | +```text | |
| 32 | +📋 plan: create | |
| 33 | +📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open) | |
| 34 | +📋 plan: update | |
| 35 | +📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open) | |
| 36 | +📋 plan: update | |
| 37 | +📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open) | |
| 38 | +📋 plan: update | |
| 39 | +📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open) | |
| 40 | +📋 plan: update | |
| 41 | +📋 [4/4] All tasks complete! | |
| 42 | +``` | |
| 43 | + | |
| 44 | +In the window (`mm -tui agent.yaml`) each `📋 [C/N] …` line is a notice under the tool call; in an editor the status is inside the tool call's result. | |
| 45 | + | |
| 46 | +To see a failure, ask for something that cannot be done here: | |
| 47 | + | |
| 48 | +```text | |
| 49 | +Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin. | |
| 50 | +``` | |
| 51 | + | |
| 52 | +The plan ends on `📋 [3/3] Plan finished: 2 done, 1 failed`. | |
| 53 | + | |
| 54 | +## Measured results (2026-10-21, `mm -p`, llmman) | |
| 55 | + | |
| 56 | +Qwen3.5 4B Q4_K_M, `temperature: 0`, thinking off, this `agent.yaml`, a fresh copy of the demo for every run. | |
| 57 | + | |
| 58 | +| Request | Plan shown | Result | | | |
| 59 | +|---|---|---|---| | |
| 60 | +| the 4 steps above | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md`; `go test` passes (checked after the run) | 3/3 | | |
| 61 | +| the 3 steps with a push to `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module built; the push marked `failed`, the answer says so | 3/3 | | |
| 62 | + | |
| 63 | +64 to 127 s for the 4 steps, 36 to 66 s for the failing case. | |
| 64 | + | |
| 65 | +## What this demo taught the plan tool | |
| 66 | + | |
| 67 | +- **A small model does not keep the plan's name.** In the first runs every `update` came without `name`, failed (`no plan named ''`), and the turn ended in runaway tool calls. Since then an action without a name works on the conversation's latest plan — for every agent, not only this one. | |
| 68 | +- **Progress is shown by mm.** The status line comes from the plan itself, after each action, so it is there even when the model writes nothing about it — and it follows the tasks however the model moves through them (here it marks each task `done` directly, without `in_progress`). | |
| 69 | +- **"Done" means the model says so.** Without the last paragraph of the prompt, asked to push to a remote that does not exist, the model marked the task done 3 times out of 3; in the run inspected, it had created a local bare repository in `/tmp` — outside the project folder — and force-pushed to it. With it, the task is marked `failed` (3/3). It still adds an example remote (`github.com/example/hello.git`) and tries to push before giving up; the push fails without credentials. | |
| 70 | + | |
| 71 | +See also: [How to manage plans and tasks](../../docs/en/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/en/reference/tools.md#plan). | |
| new file mode 100644 | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | +# mini-me demo: plans | ||
| 2 | + | ||
| 3 | +*[Version française](README.fr.md)* | ||
| 4 | + | ||
| 5 | +Planner builds a tiny Go program step by step — module, code, test, run the tests, README — and works through the steps with its `plan` tool. mm shows where the plan is after every plan action, whatever the model writes in its answer. The model is Qwen3.5 4B, served by [llmman](https://github.com/llmmanorg/llmman). Go must be on the `PATH`. | ||
| 6 | + | ||
| 7 | +## What is here | ||
| 8 | + | ||
| 9 | +| File | Role | | ||
| 10 | +|---|---| | ||
| 11 | +| `agent.yaml` | Planner: `planTool: true`, `bash` and the file tools; a system prompt that says how to use the plan (create, then `in_progress` / `done` / `failed` task by task) and to mark `failed`, not fake, a task that needs something missing. | | ||
| 12 | +| `reset.sh` | Removes `./hello`, the project the agent builds, so the next run starts from nothing. | | ||
| 13 | + | ||
| 14 | +## Run the demo | ||
| 15 | + | ||
| 16 | +```bash | ||
| 17 | +llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | ||
| 18 | +cd demos/plans | ||
| 19 | +./reset.sh | ||
| 20 | +mm agent.yaml | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +Then ask: | ||
| 24 | + | ||
| 25 | +```text | ||
| 26 | +Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program. | ||
| 27 | +``` | ||
| 28 | + | ||
| 29 | +In the line mode the progress shows between the tool calls (output of a real run): | ||
| 30 | + | ||
| 31 | +```text | ||
| 32 | +📋 plan: create | ||
| 33 | +📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open) | ||
| 34 | +📋 plan: update | ||
| 35 | +📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open) | ||
| 36 | +📋 plan: update | ||
| 37 | +📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open) | ||
| 38 | +📋 plan: update | ||
| 39 | +📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open) | ||
| 40 | +📋 plan: update | ||
| 41 | +📋 [4/4] All tasks complete! | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +In the window (`mm -tui agent.yaml`) each `📋 [C/N] …` line is a notice under the tool call; in an editor the status is inside the tool call's result. | ||
| 45 | + | ||
| 46 | +To see a failure, ask for something that cannot be done here: | ||
| 47 | + | ||
| 48 | +```text | ||
| 49 | +Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin. | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +The plan ends on `📋 [3/3] Plan finished: 2 done, 1 failed`. | ||
| 53 | + | ||
| 54 | +## Measured results (2026-10-21, `mm -p`, llmman) | ||
| 55 | + | ||
| 56 | +Qwen3.5 4B Q4_K_M, `temperature: 0`, thinking off, this `agent.yaml`, a fresh copy of the demo for every run. | ||
| 57 | + | ||
| 58 | +| Request | Plan shown | Result | | | ||
| 59 | +|---|---|---|---| | ||
| 60 | +| the 4 steps above | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md`; `go test` passes (checked after the run) | 3/3 | | ||
| 61 | +| the 3 steps with a push to `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module built; the push marked `failed`, the answer says so | 3/3 | | ||
| 62 | + | ||
| 63 | +64 to 127 s for the 4 steps, 36 to 66 s for the failing case. | ||
| 64 | + | ||
| 65 | +## What this demo taught the plan tool | ||
| 66 | + | ||
| 67 | +- **A small model does not keep the plan's name.** In the first runs every `update` came without `name`, failed (`no plan named ''`), and the turn ended in runaway tool calls. Since then an action without a name works on the conversation's latest plan — for every agent, not only this one. | ||
| 68 | +- **Progress is shown by mm.** The status line comes from the plan itself, after each action, so it is there even when the model writes nothing about it — and it follows the tasks however the model moves through them (here it marks each task `done` directly, without `in_progress`). | ||
| 69 | +- **"Done" means the model says so.** Without the last paragraph of the prompt, asked to push to a remote that does not exist, the model marked the task done 3 times out of 3; in the run inspected, it had created a local bare repository in `/tmp` — outside the project folder — and force-pushed to it. With it, the task is marked `failed` (3/3). It still adds an example remote (`github.com/example/hello.git`) and tries to push before giving up; the push fails without credentials. | ||
| 70 | + | ||
| 71 | +See also: [How to manage plans and tasks](../../docs/en/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/en/reference/tools.md#plan). | ||
added
demos/plans/README.fr.md +71 -0 | new file mode 100644 | ||
| @@ -0,0 +1,71 @@ | ||
| 1 | +# Démo mini-me : les plans | |
| 2 | + | |
| 3 | +*[English version](README.en.md)* | |
| 4 | + | |
| 5 | +Planner construit un petit programme Go étape par étape — module, code, test, lancement des tests, README — et suit les étapes avec son outil `plan`. mm affiche où en est le plan après chaque action sur le plan, quoi que le modèle écrive dans sa réponse. Le modèle est Qwen3.5 4B, servi par [llmman](https://github.com/llmmanorg/llmman). Go doit être dans le `PATH`. | |
| 6 | + | |
| 7 | +## Contenu | |
| 8 | + | |
| 9 | +| Fichier | Rôle | | |
| 10 | +|---|---| | |
| 11 | +| `agent.yaml` | Planner : `planTool: true`, `bash` et les outils de fichiers ; un prompt système qui dit comment utiliser le plan (créer, puis `in_progress` / `done` / `failed` tâche par tâche) et de marquer `failed`, sans tricher, une tâche qui demande quelque chose qui manque. | | |
| 12 | +| `reset.sh` | Supprime `./hello`, le projet que l'agent construit, pour que l'exécution suivante reparte de zéro. | | |
| 13 | + | |
| 14 | +## Lancer la démo | |
| 15 | + | |
| 16 | +```bash | |
| 17 | +llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | |
| 18 | +cd demos/plans | |
| 19 | +./reset.sh | |
| 20 | +mm agent.yaml | |
| 21 | +``` | |
| 22 | + | |
| 23 | +Puis demandez : | |
| 24 | + | |
| 25 | +```text | |
| 26 | +Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program. | |
| 27 | +``` | |
| 28 | + | |
| 29 | +En mode ligne, la progression s'affiche entre les appels d'outils (sortie d'une exécution réelle) : | |
| 30 | + | |
| 31 | +```text | |
| 32 | +📋 plan: create | |
| 33 | +📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open) | |
| 34 | +📋 plan: update | |
| 35 | +📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open) | |
| 36 | +📋 plan: update | |
| 37 | +📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open) | |
| 38 | +📋 plan: update | |
| 39 | +📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open) | |
| 40 | +📋 plan: update | |
| 41 | +📋 [4/4] All tasks complete! | |
| 42 | +``` | |
| 43 | + | |
| 44 | +Dans la fenêtre (`mm -tui agent.yaml`), chaque ligne `📋 [C/N] …` est une notice sous l'appel d'outil ; dans un éditeur, le statut est dans le résultat de l'appel. | |
| 45 | + | |
| 46 | +Pour voir un échec, demandez quelque chose d'impossible ici : | |
| 47 | + | |
| 48 | +```text | |
| 49 | +Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin. | |
| 50 | +``` | |
| 51 | + | |
| 52 | +Le plan se termine sur `📋 [3/3] Plan finished: 2 done, 1 failed`. | |
| 53 | + | |
| 54 | +## Résultats mesurés (2026-10-21, `mm -p`, llmman) | |
| 55 | + | |
| 56 | +Qwen3.5 4B Q4_K_M, `temperature: 0`, réflexion désactivée, cet `agent.yaml`, une copie neuve de la démo à chaque exécution. | |
| 57 | + | |
| 58 | +| Demande | Plan affiché | Résultat | | | |
| 59 | +|---|---|---|---| | |
| 60 | +| les 4 étapes ci-dessus | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md` ; `go test` passe (vérifié après l'exécution) | 3/3 | | |
| 61 | +| les 3 étapes avec un push vers `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module construit ; le push marqué `failed`, la réponse le dit | 3/3 | | |
| 62 | + | |
| 63 | +De 64 à 127 s pour les 4 étapes, de 36 à 66 s pour le cas d'échec. | |
| 64 | + | |
| 65 | +## Ce que cette démo a appris à l'outil plan | |
| 66 | + | |
| 67 | +- **Un petit modèle ne retient pas le nom du plan.** Lors des premières exécutions, chaque `update` arrivait sans `name`, échouait (`no plan named ''`), et le tour finissait en appels d'outils emballés. Depuis, une action sans nom porte sur le dernier plan de la conversation — pour tous les agents, pas seulement celui-ci. | |
| 68 | +- **La progression est affichée par mm.** La ligne de statut vient du plan lui-même, après chaque action : elle est là même quand le modèle n'en dit rien — et elle suit les tâches quelle que soit la façon dont le modèle les parcourt (ici il marque chaque tâche `done` directement, sans `in_progress`). | |
| 69 | +- **« Fait » veut dire « le modèle le dit ».** Sans le dernier paragraphe du prompt, invité à pousser vers un dépôt distant qui n'existe pas, le modèle a marqué la tâche faite 3 fois sur 3 ; dans l'exécution examinée, il avait créé un dépôt nu local dans `/tmp` — hors du dossier du projet — et y avait poussé avec `--force`. Avec ce paragraphe, la tâche est marquée `failed` (3/3). Il ajoute encore un remote d'exemple (`github.com/example/hello.git`) et tente de pousser avant d'abandonner ; le push échoue faute d'identifiants. | |
| 70 | + | |
| 71 | +Voir aussi : [Gérer des plans et des tâches](../../docs/fr/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/fr/reference/tools.md#plan). | |
| new file mode 100644 | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | +# Démo mini-me : les plans | ||
| 2 | + | ||
| 3 | +*[English version](README.en.md)* | ||
| 4 | + | ||
| 5 | +Planner construit un petit programme Go étape par étape — module, code, test, lancement des tests, README — et suit les étapes avec son outil `plan`. mm affiche où en est le plan après chaque action sur le plan, quoi que le modèle écrive dans sa réponse. Le modèle est Qwen3.5 4B, servi par [llmman](https://github.com/llmmanorg/llmman). Go doit être dans le `PATH`. | ||
| 6 | + | ||
| 7 | +## Contenu | ||
| 8 | + | ||
| 9 | +| Fichier | Rôle | | ||
| 10 | +|---|---| | ||
| 11 | +| `agent.yaml` | Planner : `planTool: true`, `bash` et les outils de fichiers ; un prompt système qui dit comment utiliser le plan (créer, puis `in_progress` / `done` / `failed` tâche par tâche) et de marquer `failed`, sans tricher, une tâche qui demande quelque chose qui manque. | | ||
| 12 | +| `reset.sh` | Supprime `./hello`, le projet que l'agent construit, pour que l'exécution suivante reparte de zéro. | | ||
| 13 | + | ||
| 14 | +## Lancer la démo | ||
| 15 | + | ||
| 16 | +```bash | ||
| 17 | +llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | ||
| 18 | +cd demos/plans | ||
| 19 | +./reset.sh | ||
| 20 | +mm agent.yaml | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +Puis demandez : | ||
| 24 | + | ||
| 25 | +```text | ||
| 26 | +Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program. | ||
| 27 | +``` | ||
| 28 | + | ||
| 29 | +En mode ligne, la progression s'affiche entre les appels d'outils (sortie d'une exécution réelle) : | ||
| 30 | + | ||
| 31 | +```text | ||
| 32 | +📋 plan: create | ||
| 33 | +📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open) | ||
| 34 | +📋 plan: update | ||
| 35 | +📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open) | ||
| 36 | +📋 plan: update | ||
| 37 | +📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open) | ||
| 38 | +📋 plan: update | ||
| 39 | +📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open) | ||
| 40 | +📋 plan: update | ||
| 41 | +📋 [4/4] All tasks complete! | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +Dans la fenêtre (`mm -tui agent.yaml`), chaque ligne `📋 [C/N] …` est une notice sous l'appel d'outil ; dans un éditeur, le statut est dans le résultat de l'appel. | ||
| 45 | + | ||
| 46 | +Pour voir un échec, demandez quelque chose d'impossible ici : | ||
| 47 | + | ||
| 48 | +```text | ||
| 49 | +Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin. | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +Le plan se termine sur `📋 [3/3] Plan finished: 2 done, 1 failed`. | ||
| 53 | + | ||
| 54 | +## Résultats mesurés (2026-10-21, `mm -p`, llmman) | ||
| 55 | + | ||
| 56 | +Qwen3.5 4B Q4_K_M, `temperature: 0`, réflexion désactivée, cet `agent.yaml`, une copie neuve de la démo à chaque exécution. | ||
| 57 | + | ||
| 58 | +| Demande | Plan affiché | Résultat | | | ||
| 59 | +|---|---|---|---| | ||
| 60 | +| les 4 étapes ci-dessus | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md` ; `go test` passe (vérifié après l'exécution) | 3/3 | | ||
| 61 | +| les 3 étapes avec un push vers `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module construit ; le push marqué `failed`, la réponse le dit | 3/3 | | ||
| 62 | + | ||
| 63 | +De 64 à 127 s pour les 4 étapes, de 36 à 66 s pour le cas d'échec. | ||
| 64 | + | ||
| 65 | +## Ce que cette démo a appris à l'outil plan | ||
| 66 | + | ||
| 67 | +- **Un petit modèle ne retient pas le nom du plan.** Lors des premières exécutions, chaque `update` arrivait sans `name`, échouait (`no plan named ''`), et le tour finissait en appels d'outils emballés. Depuis, une action sans nom porte sur le dernier plan de la conversation — pour tous les agents, pas seulement celui-ci. | ||
| 68 | +- **La progression est affichée par mm.** La ligne de statut vient du plan lui-même, après chaque action : elle est là même quand le modèle n'en dit rien — et elle suit les tâches quelle que soit la façon dont le modèle les parcourt (ici il marque chaque tâche `done` directement, sans `in_progress`). | ||
| 69 | +- **« Fait » veut dire « le modèle le dit ».** Sans le dernier paragraphe du prompt, invité à pousser vers un dépôt distant qui n'existe pas, le modèle a marqué la tâche faite 3 fois sur 3 ; dans l'exécution examinée, il avait créé un dépôt nu local dans `/tmp` — hors du dossier du projet — et y avait poussé avec `--force`. Avec ce paragraphe, la tâche est marquée `failed` (3/3). Il ajoute encore un remote d'exemple (`github.com/example/hello.git`) et tente de pousser avant d'abandonner ; le push échoue faute d'identifiants. | ||
| 70 | + | ||
| 71 | +Voir aussi : [Gérer des plans et des tâches](../../docs/fr/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/fr/reference/tools.md#plan). | ||
added
demos/plans/agent.yaml +73 -0 | new file mode 100644 | ||
| @@ -0,0 +1,73 @@ | ||
| 1 | +# Plans — an agent that works through a list of tasks with its `plan` tool, | |
| 2 | +# and mm showing where it is after every plan action: | |
| 3 | +# | |
| 4 | +# 📋 plan: create | |
| 5 | +# 📋 [1/4] ⏳ Task 1: Create the Go module … (open) | |
| 6 | +# … | |
| 7 | +# 📋 [4/4] All tasks complete! | |
| 8 | +# | |
| 9 | +# The job: build a tiny Go program, step by step, in ./hello — module, code, | |
| 10 | +# test, run the tests, README. Needs Go on the PATH. | |
| 11 | +# | |
| 12 | +# llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | |
| 13 | +# llmman serve | |
| 14 | +# | |
| 15 | +# cd demos/plans | |
| 16 | +# ./reset.sh removes ./hello | |
| 17 | +# mm -tui agent.yaml or mm -p "…" agent.yaml | |
| 18 | + | |
| 19 | +provider: llmman | |
| 20 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | |
| 21 | +baseUrl: http://127.0.0.1:17434/v1 | |
| 22 | +fallback: http://host.docker.internal:17434/v1 | |
| 23 | +contextWindow: 16384 | |
| 24 | + | |
| 25 | +# The plan tool is on by default; written here because this demo is about it. | |
| 26 | +planTool: true | |
| 27 | +yolo: true | |
| 28 | +bashTool: true | |
| 29 | +bashTimeout: 2m | |
| 30 | +editTools: true | |
| 31 | +askUserTool: false | |
| 32 | +skillsDir: no-skills | |
| 33 | + | |
| 34 | +agentsMd: | |
| 35 | + enabled: false | |
| 36 | + | |
| 37 | +sampling: | |
| 38 | + temperature: 0.0 | |
| 39 | + parallel_tool_calls: false | |
| 40 | + max_tokens: 2048 | |
| 41 | + chat_template_kwargs: | |
| 42 | + enable_thinking: false | |
| 43 | + | |
| 44 | +maxTurns: 30 | |
| 45 | +watchdogTimeout: 2m | |
| 46 | +firstTokenTimeout: 3m | |
| 47 | +displayCommands: true | |
| 48 | + | |
| 49 | +acp: | |
| 50 | + name: planner | |
| 51 | + title: 📋 Planner (plans demo) | |
| 52 | + | |
| 53 | +system: | | |
| 54 | + Your name is Planner. You are a coding agent in a terminal, with a bash | |
| 55 | + tool, file tools and a plan tool. | |
| 56 | + | |
| 57 | + When the user gives you several tasks, work with a plan: | |
| 58 | + 1. Call plan with action "create" and one task per item of the user's | |
| 59 | + list, in order. Remember the plan name it returns. | |
| 60 | + 2. For each task, in order: call plan "update" with status | |
| 61 | + "in_progress", do the work with your other tools, then call plan | |
| 62 | + "update" with status "done" — or "failed" with a short message if it | |
| 63 | + could not be done, and go on with the next task. | |
| 64 | + 3. When every task is over, answer in two or three sentences: what was | |
| 65 | + done, and what failed if anything. | |
| 66 | + | |
| 67 | + Never mark a task done before its work is done. Run commands to check | |
| 68 | + what you did: do not guess their output. | |
| 69 | + | |
| 70 | + Work only inside the project folder. If a task needs something that does | |
| 71 | + not exist — a git remote, an account, a server, a file elsewhere — do not | |
| 72 | + create a stand-in to make it pass: mark the task "failed" with a message | |
| 73 | + saying what is missing, and go on. | |
| new file mode 100644 | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | +# Plans — an agent that works through a list of tasks with its `plan` tool, | ||
| 2 | +# and mm showing where it is after every plan action: | ||
| 3 | +# | ||
| 4 | +# 📋 plan: create | ||
| 5 | +# 📋 [1/4] ⏳ Task 1: Create the Go module … (open) | ||
| 6 | +# … | ||
| 7 | +# 📋 [4/4] All tasks complete! | ||
| 8 | +# | ||
| 9 | +# The job: build a tiny Go program, step by step, in ./hello — module, code, | ||
| 10 | +# test, run the tests, README. Needs Go on the PATH. | ||
| 11 | +# | ||
| 12 | +# llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | ||
| 13 | +# llmman serve | ||
| 14 | +# | ||
| 15 | +# cd demos/plans | ||
| 16 | +# ./reset.sh removes ./hello | ||
| 17 | +# mm -tui agent.yaml or mm -p "…" agent.yaml | ||
| 18 | + | ||
| 19 | +provider: llmman | ||
| 20 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | ||
| 21 | +baseUrl: http://127.0.0.1:17434/v1 | ||
| 22 | +fallback: http://host.docker.internal:17434/v1 | ||
| 23 | +contextWindow: 16384 | ||
| 24 | + | ||
| 25 | +# The plan tool is on by default; written here because this demo is about it. | ||
| 26 | +planTool: true | ||
| 27 | +yolo: true | ||
| 28 | +bashTool: true | ||
| 29 | +bashTimeout: 2m | ||
| 30 | +editTools: true | ||
| 31 | +askUserTool: false | ||
| 32 | +skillsDir: no-skills | ||
| 33 | + | ||
| 34 | +agentsMd: | ||
| 35 | + enabled: false | ||
| 36 | + | ||
| 37 | +sampling: | ||
| 38 | + temperature: 0.0 | ||
| 39 | + parallel_tool_calls: false | ||
| 40 | + max_tokens: 2048 | ||
| 41 | + chat_template_kwargs: | ||
| 42 | + enable_thinking: false | ||
| 43 | + | ||
| 44 | +maxTurns: 30 | ||
| 45 | +watchdogTimeout: 2m | ||
| 46 | +firstTokenTimeout: 3m | ||
| 47 | +displayCommands: true | ||
| 48 | + | ||
| 49 | +acp: | ||
| 50 | + name: planner | ||
| 51 | + title: 📋 Planner (plans demo) | ||
| 52 | + | ||
| 53 | +system: | | ||
| 54 | + Your name is Planner. You are a coding agent in a terminal, with a bash | ||
| 55 | + tool, file tools and a plan tool. | ||
| 56 | + | ||
| 57 | + When the user gives you several tasks, work with a plan: | ||
| 58 | + 1. Call plan with action "create" and one task per item of the user's | ||
| 59 | + list, in order. Remember the plan name it returns. | ||
| 60 | + 2. For each task, in order: call plan "update" with status | ||
| 61 | + "in_progress", do the work with your other tools, then call plan | ||
| 62 | + "update" with status "done" — or "failed" with a short message if it | ||
| 63 | + could not be done, and go on with the next task. | ||
| 64 | + 3. When every task is over, answer in two or three sentences: what was | ||
| 65 | + done, and what failed if anything. | ||
| 66 | + | ||
| 67 | + Never mark a task done before its work is done. Run commands to check | ||
| 68 | + what you did: do not guess their output. | ||
| 69 | + | ||
| 70 | + Work only inside the project folder. If a task needs something that does | ||
| 71 | + not exist — a git remote, an account, a server, a file elsewhere — do not | ||
| 72 | + create a stand-in to make it pass: mark the task "failed" with a message | ||
| 73 | + saying what is missing, and go on. | ||
added
demos/plans/reset.sh +7 -0 | new file mode 100755 | ||
| @@ -0,0 +1,7 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +# reset.sh — puts the demo back as it ships: removes the ./hello project the | |
| 3 | +# agent builds, so the next run starts from nothing. | |
| 4 | +set -eu | |
| 5 | +cd "$(dirname "$0")" | |
| 6 | +rm -rf hello | |
| 7 | +echo "reset: ./hello removed" | |
| new file mode 100755 | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +# reset.sh — puts the demo back as it ships: removes the ./hello project the | ||
| 3 | +# agent builds, so the next run starts from nothing. | ||
| 4 | +set -eu | ||
| 5 | +cd "$(dirname "$0")" | ||
| 6 | +rm -rf hello | ||
| 7 | +echo "reset: ./hello removed" | ||
modified
docs/diagrams/packages.drawio +1 -1 | @@ -1,3 +1,3 @@ | ||
| 1 | 1 | <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> |
| 2 | 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> |
| 3 | - <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json), the questions ↑ recalls, /memory and search_memory</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="saved" value="<b>internal/saved</b><br/><font style="font-size:10px">an answer to a new file: /save, save_answer</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="<b>internal/elicit</b><br/><font style="font-size:10px">ask_user's questions and answers: normalize, line answers, elicitation form schema</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="<b>internal/hooks</b><br/><font style="font-size:10px">the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_session" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_engine" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="<b>internal/docsearch</b><br/><font style="font-size:10px">BM25 over the text files of folders: /docs, search_docs</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="<b>internal/textfold</b><br/><font style="font-size:10px">case and accents folded, rune for rune</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile> | |
| \ No newline at end of file | ||
| 3 | + <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json), the questions ↑ recalls, /memory and search_memory</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="saved" value="<b>internal/saved</b><br/><font style="font-size:10px">an answer to a new file: /save, save_answer</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="<b>internal/elicit</b><br/><font style="font-size:10px">ask_user's questions and answers: normalize, line answers, elicitation form schema</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="<b>internal/hooks</b><br/><font style="font-size:10px">the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_session" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_engine" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="<b>internal/docsearch</b><br/><font style="font-size:10px">BM25 over the text files of folders: /docs, search_docs</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="<b>internal/textfold</b><br/><font style="font-size:10px">case and accents folded, rune for rune</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile> | |
| \ No newline at end of file | ||
| @@ -1,3 +1,3 @@ | |||
| 1 | <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> | 1 | <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> |
| 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> | 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> |
| 3 | - <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json), the questions ↑ recalls, /memory and search_memory</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="saved" value="<b>internal/saved</b><br/><font style="font-size:10px">an answer to a new file: /save, save_answer</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="<b>internal/elicit</b><br/><font style="font-size:10px">ask_user's questions and answers: normalize, line answers, elicitation form schema</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="<b>internal/hooks</b><br/><font style="font-size:10px">the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_session" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_engine" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="<b>internal/docsearch</b><br/><font style="font-size:10px">BM25 over the text files of folders: /docs, search_docs</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="<b>internal/textfold</b><br/><font style="font-size:10px">case and accents folded, rune for rune</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile> | ||
| \ No newline at end of file | \ No newline at end of file | ||
| 3 | + <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json), the questions ↑ recalls, /memory and search_memory</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="saved" value="<b>internal/saved</b><br/><font style="font-size:10px">an answer to a new file: /save, save_answer</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="<b>internal/elicit</b><br/><font style="font-size:10px">ask_user's questions and answers: normalize, line answers, elicitation form schema</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="<b>internal/hooks</b><br/><font style="font-size:10px">the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_session" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_engine" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="<b>internal/docsearch</b><br/><font style="font-size:10px">BM25 over the text files of folders: /docs, search_docs</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="<b>internal/textfold</b><br/><font style="font-size:10px">case and accents folded, rune for rune</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile> | ||
| \ No newline at end of file | \ No newline at end of file | ||
modified
docs/en/README.md +2 -1 | @@ -26,6 +26,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 26 | 26 | - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor |
| 27 | 27 | - [Add a skill](how-to/add-a-skill.md) |
| 28 | 28 | - [Declare a custom tool](how-to/declare-a-custom-tool.md) |
| 29 | +- [Manage plans and tasks](how-to/manage-plans-and-tasks.md) — the `plan` tool: give a list, follow the `📋` progress line, plans per conversation | |
| 29 | 30 | - [Save an answer to a file](how-to/save-an-answer.md) |
| 30 | 31 | - [Search a documentation folder](how-to/search-the-docs.md) — `/docs <words>`, the `search_docs` tool, `docSearch` (BM25, no model) |
| 31 | 32 | - [Search earlier sessions](how-to/search-earlier-sessions.md) — `/memory <words>`, the `search_memory` tool, `sessions.searchMemory` / `sessions.autoSearchMemory` |
| @@ -54,7 +55,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 54 | 55 | - [Command line](reference/cli.md) — flags, environment variables, start-up banner |
| 55 | 56 | - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys |
| 56 | 57 | - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule |
| 57 | -- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, declared tools | |
| 58 | +- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, declared tools | |
| 58 | 59 | - [Skill file format](reference/skill-format.md) |
| 59 | 60 | - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors |
| 60 | 61 | - [Agent Client Protocol surface](reference/acp.md) |
| @@ -26,6 +26,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 26 | - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor | 26 | - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor |
| 27 | - [Add a skill](how-to/add-a-skill.md) | 27 | - [Add a skill](how-to/add-a-skill.md) |
| 28 | - [Declare a custom tool](how-to/declare-a-custom-tool.md) | 28 | - [Declare a custom tool](how-to/declare-a-custom-tool.md) |
| 29 | +- [Manage plans and tasks](how-to/manage-plans-and-tasks.md) — the `plan` tool: give a list, follow the `📋` progress line, plans per conversation | ||
| 29 | - [Save an answer to a file](how-to/save-an-answer.md) | 30 | - [Save an answer to a file](how-to/save-an-answer.md) |
| 30 | - [Search a documentation folder](how-to/search-the-docs.md) — `/docs <words>`, the `search_docs` tool, `docSearch` (BM25, no model) | 31 | - [Search a documentation folder](how-to/search-the-docs.md) — `/docs <words>`, the `search_docs` tool, `docSearch` (BM25, no model) |
| 31 | - [Search earlier sessions](how-to/search-earlier-sessions.md) — `/memory <words>`, the `search_memory` tool, `sessions.searchMemory` / `sessions.autoSearchMemory` | 32 | - [Search earlier sessions](how-to/search-earlier-sessions.md) — `/memory <words>`, the `search_memory` tool, `sessions.searchMemory` / `sessions.autoSearchMemory` |
| @@ -54,7 +55,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 54 | - [Command line](reference/cli.md) — flags, environment variables, start-up banner | 55 | - [Command line](reference/cli.md) — flags, environment variables, start-up banner |
| 55 | - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys | 56 | - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys |
| 56 | - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule | 57 | - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule |
| 57 | -- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, declared tools | 58 | +- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, declared tools |
| 58 | - [Skill file format](reference/skill-format.md) | 59 | - [Skill file format](reference/skill-format.md) |
| 59 | - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors | 60 | - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors |
| 60 | - [Agent Client Protocol surface](reference/acp.md) | 61 | - [Agent Client Protocol surface](reference/acp.md) |
modified
docs/en/how-to/manage-plans-and-tasks.md +42 -427 | @@ -1,450 +1,65 @@ | ||
| 1 | ---- | |
| 2 | -title: Manage Plans and Tasks | |
| 3 | ---- | |
| 1 | +# How to manage plans and tasks | |
| 4 | 2 | |
| 5 | -# Manage Plans and Tasks | |
| 3 | +This guide shows how to have the agent work through a list of tasks with its `plan` tool, and how to follow its progress. The tool is on by default (`planTool: true`). | |
| 6 | 4 | |
| 7 | -Mini-me includes a built-in **plan** tool that lets the agent create, track, and manage multi-step todo lists. When you give the agent a list of tasks to do, it automatically creates a plan and tracks progress, showing you the current status after each response. | |
| 5 | +## Give the agent a list | |
| 8 | 6 | |
| 9 | -## Quick Example | |
| 7 | +Ask for several things at once, as a list: | |
| 10 | 8 | |
| 9 | +```text | |
| 10 | +Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B. | |
| 11 | 11 | ``` |
| 12 | -You: Do these 3 things: 1. Create a README file, 2. Add a license, 3. Commit the changes | |
| 13 | 12 | |
| 14 | -Agent: I'll handle this in 3 steps. Starting with: Create a README file | |
| 15 | -📋 plan: create user-request-1 | |
| 13 | +The model creates a plan from the list, works through it, and updates each task's status as it goes. Small models do it more reliably when you say so: "Use the plan tool. Do these…". | |
| 16 | 14 | |
| 17 | -Agent: README file created. | |
| 18 | -📋 [2/3] 🔄 Task 2: Add a license (in_progress) | |
| 15 | +## Follow the progress | |
| 19 | 16 | |
| 20 | -Agent: License added. | |
| 21 | -📋 [3/3] 🔄 Task 3: Commit the changes (in_progress) | |
| 17 | +mm shows where the plan is after every plan action that changes or reads its state (`create`, `update`, `next`, `status`) — whether or not the model mentions it in its answer. | |
| 22 | 18 | |
| 23 | -Agent: All changes committed. | |
| 24 | -📋 [3/3] ✅ All tasks complete! | |
| 25 | -``` | |
| 26 | - | |
| 27 | -The `📋` line shows you exactly where the agent is in your plan. | |
| 28 | - | |
| 29 | ---- | |
| 30 | - | |
| 31 | -## How It Works | |
| 32 | - | |
| 33 | -### The Plan Lifecycle | |
| 34 | - | |
| 35 | -1. **You state your request** as a list of tasks | |
| 36 | -2. **Agent detects the plan** and creates it using the `plan` tool | |
| 37 | -3. **Agent works on tasks** one by one, updating status | |
| 38 | -4. **Agent reports progress** after each action | |
| 39 | -5. **Plan completes** when all tasks are done | |
| 40 | - | |
| 41 | -### Status Icons | |
| 42 | - | |
| 43 | -| Icon | Meaning | | |
| 44 | -|------|---------| | |
| 45 | -| ⏳ | Task not started (open) | | |
| 46 | -| 🔄 | Task currently in progress | | |
| 47 | -| ✅ | Task completed successfully | | |
| 48 | -| ❌ | Task failed | | |
| 49 | - | |
| 50 | -### Status Line Format | |
| 51 | - | |
| 52 | -``` | |
| 53 | -📋 [current/total] icon Task N: description (status) | |
| 54 | -``` | |
| 55 | - | |
| 56 | -Examples: | |
| 57 | -- `📋 [1/5] ⏳ Task 1: Analyze the code (open)` - First task, not started yet | |
| 58 | -- `📋 [2/5] 🔄 Task 2: Fix the bugs (in_progress)` - Second task, currently working | |
| 59 | -- `📋 [5/5] ✅ All tasks complete!` - All done! | |
| 60 | -- `📋 [3/5] ❌ Task 3: Run tests (failed: connection timeout)` - Task failed with error | |
| 61 | - | |
| 62 | ---- | |
| 63 | - | |
| 64 | -## User Scenarios | |
| 65 | - | |
| 66 | -### Scenario 1: Numbered List | |
| 67 | - | |
| 68 | -**Your input:** | |
| 69 | -``` | |
| 70 | -Do these 5 things: | |
| 71 | -1. Check the current version | |
| 72 | -2. Update dependencies | |
| 73 | -3. Run tests | |
| 74 | -4. Fix any failures | |
| 75 | -5. Deploy to staging | |
| 76 | -``` | |
| 77 | - | |
| 78 | -**What happens:** | |
| 79 | -- Agent creates a plan with 5 tasks | |
| 80 | -- Works through each sequentially | |
| 81 | -- Shows progress: `📋 [1/5]`, `📋 [2/5]`, etc. | |
| 82 | -- Completes with: `📋 [5/5] ✅ All tasks complete!` | |
| 83 | - | |
| 84 | ---- | |
| 85 | - | |
| 86 | -### Scenario 2: Comma-Separated Tasks | |
| 87 | - | |
| 88 | -**Your input:** | |
| 89 | -``` | |
| 90 | -Set up the project: create the directory, initialize git, and add a README | |
| 91 | -``` | |
| 92 | - | |
| 93 | -**What happens:** | |
| 94 | -- Agent detects this as a multi-step request | |
| 95 | -- Creates a plan with 3 tasks | |
| 96 | -- Executes in order | |
| 97 | - | |
| 98 | ---- | |
| 99 | - | |
| 100 | -### Scenario 3: Multiple Plans | |
| 101 | - | |
| 102 | -You can have multiple active plans in a single session. | |
| 103 | - | |
| 104 | -**Your input:** | |
| 105 | -``` | |
| 106 | -First, fix the auth module: 1. Find the bug, 2. Write the fix, 3. Test it | |
| 107 | -``` | |
| 108 | - | |
| 109 | -Agent works on the auth plan, shows: `📋 [2/3] 🔄 Task 2: Write the fix (in_progress)` | |
| 110 | - | |
| 111 | -**Then you say:** | |
| 112 | -``` | |
| 113 | -Also, update the documentation: 1. Update README, 2. Update CHANGELOG | |
| 114 | -``` | |
| 115 | - | |
| 116 | -Agent creates a second plan and manages both independently. | |
| 117 | - | |
| 118 | -**To see all active plans:** | |
| 119 | -``` | |
| 120 | -What plans do I have? | |
| 121 | -``` | |
| 122 | - | |
| 123 | ---- | |
| 124 | - | |
| 125 | -## Interacting with Plans | |
| 126 | - | |
| 127 | -### Check Current Plan Status | |
| 128 | - | |
| 129 | -**Ask:** "What's the status of my plan?" | |
| 130 | - | |
| 131 | -**Agent responds:** | |
| 132 | -``` | |
| 133 | -Current plan: [2/3] 🔄 Task 2: Write the fix (in_progress). Next: Test it | |
| 134 | -``` | |
| 135 | - | |
| 136 | -### Skip a Task | |
| 137 | - | |
| 138 | -**Ask:** "Skip the test step, just deploy" | |
| 139 | - | |
| 140 | -**Agent:** | |
| 141 | -- Marks the current task as done | |
| 142 | -- Skips to the next task | |
| 143 | -- Confirms: "Skipping test step. Deploying to staging..." | |
| 144 | - | |
| 145 | -### Abandon a Plan | |
| 146 | - | |
| 147 | -**Ask:** "Never mind, I don't need this anymore" | |
| 148 | - | |
| 149 | -**Agent:** | |
| 150 | -- Asks for confirmation | |
| 151 | -- If you confirm, deletes the plan | |
| 152 | -- All progress is lost | |
| 153 | - | |
| 154 | ---- | |
| 155 | - | |
| 156 | -## Plan Management Commands | |
| 157 | - | |
| 158 | -While the plan tool is primarily model-driven (the agent uses it automatically), you can also interact with plans directly: | |
| 159 | - | |
| 160 | -### List All Plans | |
| 161 | - | |
| 162 | -**Command:** `/plans` (not yet implemented in v1 - coming in v2) | |
| 163 | - | |
| 164 | -**What it shows:** | |
| 165 | -``` | |
| 166 | -Active plans: | |
| 167 | -1. setup-project (2/5 tasks complete) | |
| 168 | -2. fix-bugs (0/3 tasks complete) | |
| 169 | -``` | |
| 170 | - | |
| 171 | -### View Plan Details | |
| 172 | - | |
| 173 | -**Ask the agent:** "Show me the details of plan X" | |
| 174 | - | |
| 175 | -**Agent responds:** Full task list with status for each task | |
| 176 | - | |
| 177 | -### Delete a Plan | |
| 178 | - | |
| 179 | -**Ask the agent:** "Delete plan X" | |
| 180 | - | |
| 181 | -**Agent:** Confirms and deletes the plan | |
| 182 | - | |
| 183 | ---- | |
| 184 | - | |
| 185 | -## Session Lifecycle | |
| 186 | - | |
| 187 | -### Plans and /new Command | |
| 188 | - | |
| 189 | -When you start a new session with `/new`: | |
| 190 | -- All active plans are **cleared** | |
| 191 | -- This is intentional: a new session means a fresh start | |
| 192 | -- If you want to continue an old plan, don't use `/new` | |
| 193 | - | |
| 194 | -### Plans and Agent Restart | |
| 195 | - | |
| 196 | -In v1 (current version): | |
| 197 | -- Plans are stored **in memory only** | |
| 198 | -- Plans are **lost** when the agent restarts | |
| 199 | -- This keeps v1 simple and fast | |
| 200 | - | |
| 201 | -In v2 (future): | |
| 202 | -- Plans will be persisted to session files | |
| 203 | -- Plans will survive agent restarts | |
| 204 | - | |
| 205 | ---- | |
| 206 | - | |
| 207 | -## The plan Tool (For Advanced Users) | |
| 208 | - | |
| 209 | -The `plan` tool is available for the model to call. It supports these actions: | |
| 210 | - | |
| 211 | -| Action | Purpose | Required Parameters | | |
| 212 | -|--------|---------|---------------------| | |
| 213 | -| `create` | Create a new plan | `tasks` (array of `{description}`) | | |
| 214 | -| `get` | Get plan details | `name` | | |
| 215 | -| `update` | Update task status | `name`, `taskId`, `status` | | |
| 216 | -| `next` | Move to next task | `name` | | |
| 217 | -| `list` | List all plans | none | | |
| 218 | -| `delete` | Delete a plan | `name` | | |
| 219 | -| `status` | Get status line | `name` | | |
| 19 | +- **Line mode** (`mm agent.yaml`, `mm -p`): one line before each call, one line after it. Measured on Qwen3.5 4B with the request above: | |
| 220 | 20 | |
| 221 | -### Status Values | |
| 21 | + ```text | |
| 22 | + 📋 plan: create | |
| 23 | + 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open) | |
| 24 | + 💾 write_file: a.txt (1 bytes) | |
| 25 | + 💾 write_file: b.txt (1 bytes) | |
| 26 | + 📋 plan: update user-request-1 | |
| 27 | + 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open) | |
| 28 | + 📋 plan: update user-request-1 | |
| 29 | + 📋 [2/2] All tasks complete! | |
| 30 | + ``` | |
| 222 | 31 | |
| 223 | -- `open` - Task not started | |
| 224 | -- `in_progress` - Task currently being worked on | |
| 225 | -- `done` - Task completed successfully | |
| 226 | -- `failed` - Task failed | |
| 32 | +- **Window** (`mm -tui`): each call is a tool call in the conversation, and the `📋 [C/N] …` line follows it as a notice. | |
| 33 | +- **Editor** (`mm -acp`): each call is a tool call; the status line is inside its result. | |
| 227 | 34 | |
| 228 | ---- | |
| 35 | +The status line shows the task being worked on — the first one in progress, else the first one still open: `[C/N] <icon> Task C: <description> (<status>)`. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. When every task is over it says `[N/N] All tasks complete!`, or `[N/N] Plan finished: D done, F failed` if some failed. | |
| 229 | 36 | |
| 230 | -## The plan-manager Skill | |
| 37 | +## Steer the plan | |
| 231 | 38 | |
| 232 | -The agent uses the `plan-manager` skill to know **when and how** to use the plan tool. This skill includes: | |
| 39 | +The model decides when to call the tool; you steer it in your own words: | |
| 233 | 40 | |
| 234 | -- **Detection patterns:** How to recognize when a user gives a multi-step request | |
| 235 | -- **Usage guidelines:** When to create plans, update tasks, move between tasks | |
| 236 | -- **Display format:** How to show plan status to the user | |
| 237 | -- **Error handling:** What to do when things go wrong | |
| 41 | +- **Where are we?** "What's the status of the plan?" — the model calls `status` and mm shows the line. | |
| 42 | +- **Skip a task.** "Skip the tests and go on." — the model can mark it and move on with `next`. | |
| 43 | +- **A task failed.** The model can mark it `failed`, with a note; the status line then moves to the next open task. | |
| 44 | +- **Drop the plan.** "Forget this plan." — the model can call `delete`. | |
| 238 | 45 | |
| 239 | -You don't need to manually load this skill - the agent does it automatically when appropriate. | |
| 46 | +There is no slash command for plans: they are the model's tool. | |
| 240 | 47 | |
| 241 | ---- | |
| 48 | +## Variants | |
| 242 | 49 | |
| 243 | -## Examples | |
| 244 | - | |
| 245 | -### Example 1: Bug Fixing Workflow | |
| 246 | - | |
| 247 | -``` | |
| 248 | -You: Fix these issues in the auth module: | |
| 249 | - 1. Check for null pointer exceptions | |
| 250 | - 2. Add input validation | |
| 251 | - 3. Write unit tests | |
| 252 | - 4. Update the documentation | |
| 253 | - | |
| 254 | -Agent: I'll handle this in 4 steps. Starting with: Check for null pointer exceptions | |
| 255 | -📋 plan: create user-request-1 | |
| 256 | - | |
| 257 | -... (agent checks code) ... | |
| 258 | - | |
| 259 | -Agent: Found 3 potential null pointer issues. | |
| 260 | -📋 [2/4] 🔄 Task 2: Add input validation (in_progress) | |
| 261 | - | |
| 262 | -... (agent adds validation) ... | |
| 263 | - | |
| 264 | -Agent: Input validation added to auth.go. | |
| 265 | -📋 [3/4] 🔄 Task 3: Write unit tests (in_progress) | |
| 266 | - | |
| 267 | -... (agent writes tests) ... | |
| 268 | - | |
| 269 | -Agent: Tests written and passing. | |
| 270 | -📋 [4/4] 🔄 Task 4: Update the documentation (in_progress) | |
| 271 | - | |
| 272 | -... (agent updates docs) ... | |
| 273 | - | |
| 274 | -Agent: Documentation updated with new validation requirements. | |
| 275 | -📋 [4/4] ✅ All tasks complete! | |
| 276 | -``` | |
| 277 | - | |
| 278 | ---- | |
| 279 | - | |
| 280 | -### Example 2: Project Setup | |
| 281 | - | |
| 282 | -``` | |
| 283 | -You: Set up a new Go project with: | |
| 284 | - 1. Module initialization | |
| 285 | - 2. Directory structure | |
| 286 | - 3. Main file | |
| 287 | - 4. Test file | |
| 288 | - | |
| 289 | -Agent: I'll set this up in 4 steps. Starting with: Module initialization | |
| 290 | -📋 plan: create user-request-1 | |
| 291 | - | |
| 292 | -Agent: Module initialized with go mod | |
| 293 | -📋 [2/4] 🔄 Task 2: Directory structure (in_progress) | |
| 294 | - | |
| 295 | -Agent: Directories created (cmd, internal, pkg, tests) | |
| 296 | -📋 [3/4] 🔄 Task 3: Main file (in_progress) | |
| 297 | - | |
| 298 | -Agent: Main file created with basic structure | |
| 299 | -📋 [4/4] 🔄 Task 4: Test file (in_progress) | |
| 300 | - | |
| 301 | -Agent: Test file created with sample tests | |
| 302 | -📋 [4/4] ✅ All tasks complete! | |
| 303 | -``` | |
| 304 | - | |
| 305 | ---- | |
| 306 | - | |
| 307 | -### Example 3: Code Review | |
| 308 | - | |
| 309 | -``` | |
| 310 | -You: Review this PR and: | |
| 311 | - 1. Check for security issues | |
| 312 | - 2. Verify tests pass | |
| 313 | - 3. Review the documentation | |
| 314 | - 4. Leave comments | |
| 315 | - | |
| 316 | -Agent: I'll review this PR in 4 steps. Starting with: Check for security issues | |
| 317 | -📋 plan: create user-request-1 | |
| 318 | - | |
| 319 | -Agent: No security issues found. | |
| 320 | -📋 [2/4] 🔄 Task 2: Verify tests pass (in_progress) | |
| 321 | - | |
| 322 | -Agent: All tests passing. | |
| 323 | -📋 [3/4] 🔄 Task 3: Review the documentation (in_progress) | |
| 324 | - | |
| 325 | -Agent: Documentation looks good, just needs a typo fix. | |
| 326 | -📋 [4/4] 🔄 Task 4: Leave comments (in_progress) | |
| 327 | - | |
| 328 | -Agent: Comments left on the PR. | |
| 329 | -📋 [4/4] ✅ All tasks complete! | |
| 330 | -``` | |
| 331 | - | |
| 332 | ---- | |
| 333 | - | |
| 334 | -### Example 4: With a Task Failure | |
| 335 | - | |
| 336 | -``` | |
| 337 | -You: Do these: | |
| 338 | - 1. Connect to the database | |
| 339 | - 2. Run the migration | |
| 340 | - 3. Import the data | |
| 341 | - | |
| 342 | -Agent: I'll handle this in 3 steps. Starting with: Connect to the database | |
| 343 | -📋 plan: create user-request-1 | |
| 344 | - | |
| 345 | -Agent: Database connection established. | |
| 346 | -📋 [2/3] 🔄 Task 2: Run the migration (in_progress) | |
| 347 | - | |
| 348 | -Agent: Migration completed. | |
| 349 | -📋 [3/3] 🔄 Task 3: Import the data (in_progress) | |
| 350 | - | |
| 351 | -Agent: Error: connection timeout to data source | |
| 352 | -📋 plan: update user-request-1 | |
| 353 | - | |
| 354 | -Agent: ⚠️ Task 3 failed: connection timeout to data source | |
| 355 | -📋 [3/3] ❌ Task 3: Import the data (failed: connection timeout) | |
| 356 | - | |
| 357 | -Agent: 2 of 3 tasks completed. The data import failed due to a connection timeout. Would you like me to retry, or skip this task? | |
| 358 | -``` | |
| 359 | - | |
| 360 | ---- | |
| 361 | - | |
| 362 | -## Configuration | |
| 363 | - | |
| 364 | -The plan tool can be enabled or disabled via the YAML configuration file: | |
| 365 | - | |
| 366 | -```yaml | |
| 367 | -# In your agent.yaml file | |
| 368 | -planTool: true # Enabled by default | |
| 369 | -``` | |
| 370 | - | |
| 371 | -To disable: | |
| 372 | -```yaml | |
| 373 | -planTool: false | |
| 374 | -``` | |
| 375 | - | |
| 376 | -When disabled, the agent will not have access to the `plan` tool and will not automatically create or track plans. | |
| 377 | - | |
| 378 | ---- | |
| 50 | +- **Several conversations in an editor.** Each thread has its own plans: a plan made in one Zed thread is not visible in another. | |
| 51 | +- **Starting over.** `/new` and `/resume` forget the plans in every front end; an editor's reload (`session/load`, `session/resume`) starts the thread with no plan. Plans are kept in memory only, never in the session file, and are lost when mm stops. | |
| 52 | +- **The `plan-manager` skill.** `skills/plan-manager` tells the model when to make a plan and how to move through it. Put it in the agent's `skillsDir` for a model that does not use the tool by itself. It also asks the model to repeat the status line in its answer; mm shows it anyway. | |
| 53 | +- **An agent that does not need plans.** Set `planTool: false`: the tool is not declared. Do it for an agent whose job is a single answer (writing a document, answering questions), where a small model may otherwise loop on plan calls. | |
| 379 | 54 | |
| 380 | 55 | ## Troubleshooting |
| 381 | 56 | |
| 382 | -### Agent Not Creating Plans | |
| 383 | - | |
| 384 | -If the agent isn't creating plans for your multi-step requests: | |
| 385 | - | |
| 386 | -1. **Make your request clearer** - Use numbered lists | |
| 387 | - - ❌ "Fix some things" | |
| 388 | - - ✅ "Do these: 1. Fix A, 2. Fix B, 3. Fix C" | |
| 389 | - | |
| 390 | -2. **Be more explicit** - Use words like "plan", "steps", "then" | |
| 391 | - - ✅ "My plan is to first do X, then do Y" | |
| 392 | - | |
| 393 | -3. **Check the skill is loaded** - The agent should automatically load the `plan-manager` skill when it detects a plan-like request | |
| 394 | - | |
| 395 | -### The Agent Keeps Calling `plan update` | |
| 396 | - | |
| 397 | -`update` changes the status of one task; it cannot change a plan's tasks. A model that sends an `update` without a valid `status` — often with a `tasks` list, trying to edit the plan — gets an answer that quotes its call back, names the missing field, says that `tasks` is only read by `create`, and gives a valid call: | |
| 398 | - | |
| 399 | -``` | |
| 400 | -[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}] | |
| 401 | -``` | |
| 402 | - | |
| 403 | -If a small model still repeats the call, set `planTool: false` for an agent whose job does not need plans (writing a document, answering questions): the model then works without them. | |
| 404 | - | |
| 405 | -### Plans Disappearing | |
| 406 | - | |
| 407 | -In v1, plans are stored in memory and will be lost when: | |
| 408 | -- You start a new session with `/new` | |
| 409 | -- The agent restarts | |
| 410 | -- You close and reopen the terminal | |
| 411 | - | |
| 412 | -This is expected behavior in v1. Session persistence is coming in v2. | |
| 413 | - | |
| 414 | ---- | |
| 415 | - | |
| 416 | -## Best Practices | |
| 417 | - | |
| 418 | -1. **Be specific** - The more detail in your task descriptions, the better the agent can execute them | |
| 419 | - | |
| 420 | -2. **Keep plans focused** - One plan per logical request. Don't mix unrelated tasks | |
| 421 | - | |
| 422 | -3. **Check progress** - The status line shows you exactly where the agent is | |
| 423 | - | |
| 424 | -4. **Ask for clarification** - If the agent seems stuck or confused, ask "What's the status?" | |
| 425 | - | |
| 426 | -5. **Start small** - For complex multi-step work, start with a few tasks and add more as needed | |
| 427 | - | |
| 428 | ---- | |
| 429 | - | |
| 430 | -## Comparison with Other Tools | |
| 431 | - | |
| 432 | -| Feature | plan tool | skills | slash commands | | |
| 433 | -|---------|-----------|--------|----------------| | |
| 434 | -| Purpose | Track multi-step work | Load procedures | User commands | | |
| 435 | -| Trigger | Automatic (model detects) | Explicit (`/skill`) | Explicit (`/command`) | | |
| 436 | -| Persistence (v1) | In-memory, session only | File-based | N/A | | |
| 437 | -| User interaction | Indirect (via model) | Indirect (model loads) | Direct | | |
| 438 | - | |
| 439 | -The `plan` tool complements other mini-me features: | |
| 440 | -- Use **skills** for complex, reusable procedures | |
| 441 | -- Use **plan tool** for ad-hoc multi-step requests | |
| 442 | -- Use **slash commands** for direct user actions | |
| 443 | - | |
| 444 | ---- | |
| 57 | +- **The agent does not create a plan.** Give a numbered list, and say "use the plan tool". With a small model, add the `plan-manager` skill. | |
| 58 | +- **The agent keeps calling `plan update`.** `update` changes the status of ONE task; it cannot change a plan's tasks. A call without a valid `status` gets an answer that quotes the call back, names the missing field and gives a valid call. If a small model still repeats it, set `planTool: false` for that agent. | |
| 445 | 59 | |
| 446 | -## See Also | |
| 60 | +## See also | |
| 447 | 61 | |
| 448 | -- [Slash Commands Reference](../reference/slash-commands.md) - For user commands | |
| 449 | -- [Tools Reference](../reference/tools.md) - For all built-in tools | |
| 450 | -- [Skills Overview](../explanation/skills.md) - For the skill system | |
| 62 | +- [Tools reference — `plan`](../reference/tools.md#plan): every action, field, result and error | |
| 63 | +- [Configuration reference](../reference/configuration.md): `planTool` | |
| 64 | +- [Slash commands reference](../reference/slash-commands.md): `/new`, `/resume` | |
| 65 | +- `demos/plans/` — a measured demo: a Go program built in four planned steps, and a plan that ends with a failed task | |
| @@ -1,450 +1,65 @@ | |||
| 1 | ---- | 1 | +# How to manage plans and tasks |
| 2 | -title: Manage Plans and Tasks | ||
| 3 | ---- | ||
| 4 | 2 | ||
| 5 | -# Manage Plans and Tasks | 3 | +This guide shows how to have the agent work through a list of tasks with its `plan` tool, and how to follow its progress. The tool is on by default (`planTool: true`). |
| 6 | 4 | ||
| 7 | -Mini-me includes a built-in **plan** tool that lets the agent create, track, and manage multi-step todo lists. When you give the agent a list of tasks to do, it automatically creates a plan and tracks progress, showing you the current status after each response. | 5 | +## Give the agent a list |
| 8 | 6 | ||
| 9 | -## Quick Example | 7 | +Ask for several things at once, as a list: |
| 10 | 8 | ||
| 9 | +```text | ||
| 10 | +Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B. | ||
| 11 | ``` | 11 | ``` |
| 12 | -You: Do these 3 things: 1. Create a README file, 2. Add a license, 3. Commit the changes | ||
| 13 | 12 | ||
| 14 | -Agent: I'll handle this in 3 steps. Starting with: Create a README file | 13 | +The model creates a plan from the list, works through it, and updates each task's status as it goes. Small models do it more reliably when you say so: "Use the plan tool. Do these…". |
| 15 | -📋 plan: create user-request-1 | ||
| 16 | 14 | ||
| 17 | -Agent: README file created. | 15 | +## Follow the progress |
| 18 | -📋 [2/3] 🔄 Task 2: Add a license (in_progress) | ||
| 19 | 16 | ||
| 20 | -Agent: License added. | 17 | +mm shows where the plan is after every plan action that changes or reads its state (`create`, `update`, `next`, `status`) — whether or not the model mentions it in its answer. |
| 21 | -📋 [3/3] 🔄 Task 3: Commit the changes (in_progress) | ||
| 22 | 18 | ||
| 23 | -Agent: All changes committed. | 19 | +- **Line mode** (`mm agent.yaml`, `mm -p`): one line before each call, one line after it. Measured on Qwen3.5 4B with the request above: |
| 24 | -📋 [3/3] ✅ All tasks complete! | ||
| 25 | -``` | ||
| 26 | - | ||
| 27 | -The `📋` line shows you exactly where the agent is in your plan. | ||
| 28 | - | ||
| 29 | ---- | ||
| 30 | - | ||
| 31 | -## How It Works | ||
| 32 | - | ||
| 33 | -### The Plan Lifecycle | ||
| 34 | - | ||
| 35 | -1. **You state your request** as a list of tasks | ||
| 36 | -2. **Agent detects the plan** and creates it using the `plan` tool | ||
| 37 | -3. **Agent works on tasks** one by one, updating status | ||
| 38 | -4. **Agent reports progress** after each action | ||
| 39 | -5. **Plan completes** when all tasks are done | ||
| 40 | - | ||
| 41 | -### Status Icons | ||
| 42 | - | ||
| 43 | -| Icon | Meaning | | ||
| 44 | -|------|---------| | ||
| 45 | -| ⏳ | Task not started (open) | | ||
| 46 | -| 🔄 | Task currently in progress | | ||
| 47 | -| ✅ | Task completed successfully | | ||
| 48 | -| ❌ | Task failed | | ||
| 49 | - | ||
| 50 | -### Status Line Format | ||
| 51 | - | ||
| 52 | -``` | ||
| 53 | -📋 [current/total] icon Task N: description (status) | ||
| 54 | -``` | ||
| 55 | - | ||
| 56 | -Examples: | ||
| 57 | -- `📋 [1/5] ⏳ Task 1: Analyze the code (open)` - First task, not started yet | ||
| 58 | -- `📋 [2/5] 🔄 Task 2: Fix the bugs (in_progress)` - Second task, currently working | ||
| 59 | -- `📋 [5/5] ✅ All tasks complete!` - All done! | ||
| 60 | -- `📋 [3/5] ❌ Task 3: Run tests (failed: connection timeout)` - Task failed with error | ||
| 61 | - | ||
| 62 | ---- | ||
| 63 | - | ||
| 64 | -## User Scenarios | ||
| 65 | - | ||
| 66 | -### Scenario 1: Numbered List | ||
| 67 | - | ||
| 68 | -**Your input:** | ||
| 69 | -``` | ||
| 70 | -Do these 5 things: | ||
| 71 | -1. Check the current version | ||
| 72 | -2. Update dependencies | ||
| 73 | -3. Run tests | ||
| 74 | -4. Fix any failures | ||
| 75 | -5. Deploy to staging | ||
| 76 | -``` | ||
| 77 | - | ||
| 78 | -**What happens:** | ||
| 79 | -- Agent creates a plan with 5 tasks | ||
| 80 | -- Works through each sequentially | ||
| 81 | -- Shows progress: `📋 [1/5]`, `📋 [2/5]`, etc. | ||
| 82 | -- Completes with: `📋 [5/5] ✅ All tasks complete!` | ||
| 83 | - | ||
| 84 | ---- | ||
| 85 | - | ||
| 86 | -### Scenario 2: Comma-Separated Tasks | ||
| 87 | - | ||
| 88 | -**Your input:** | ||
| 89 | -``` | ||
| 90 | -Set up the project: create the directory, initialize git, and add a README | ||
| 91 | -``` | ||
| 92 | - | ||
| 93 | -**What happens:** | ||
| 94 | -- Agent detects this as a multi-step request | ||
| 95 | -- Creates a plan with 3 tasks | ||
| 96 | -- Executes in order | ||
| 97 | - | ||
| 98 | ---- | ||
| 99 | - | ||
| 100 | -### Scenario 3: Multiple Plans | ||
| 101 | - | ||
| 102 | -You can have multiple active plans in a single session. | ||
| 103 | - | ||
| 104 | -**Your input:** | ||
| 105 | -``` | ||
| 106 | -First, fix the auth module: 1. Find the bug, 2. Write the fix, 3. Test it | ||
| 107 | -``` | ||
| 108 | - | ||
| 109 | -Agent works on the auth plan, shows: `📋 [2/3] 🔄 Task 2: Write the fix (in_progress)` | ||
| 110 | - | ||
| 111 | -**Then you say:** | ||
| 112 | -``` | ||
| 113 | -Also, update the documentation: 1. Update README, 2. Update CHANGELOG | ||
| 114 | -``` | ||
| 115 | - | ||
| 116 | -Agent creates a second plan and manages both independently. | ||
| 117 | - | ||
| 118 | -**To see all active plans:** | ||
| 119 | -``` | ||
| 120 | -What plans do I have? | ||
| 121 | -``` | ||
| 122 | - | ||
| 123 | ---- | ||
| 124 | - | ||
| 125 | -## Interacting with Plans | ||
| 126 | - | ||
| 127 | -### Check Current Plan Status | ||
| 128 | - | ||
| 129 | -**Ask:** "What's the status of my plan?" | ||
| 130 | - | ||
| 131 | -**Agent responds:** | ||
| 132 | -``` | ||
| 133 | -Current plan: [2/3] 🔄 Task 2: Write the fix (in_progress). Next: Test it | ||
| 134 | -``` | ||
| 135 | - | ||
| 136 | -### Skip a Task | ||
| 137 | - | ||
| 138 | -**Ask:** "Skip the test step, just deploy" | ||
| 139 | - | ||
| 140 | -**Agent:** | ||
| 141 | -- Marks the current task as done | ||
| 142 | -- Skips to the next task | ||
| 143 | -- Confirms: "Skipping test step. Deploying to staging..." | ||
| 144 | - | ||
| 145 | -### Abandon a Plan | ||
| 146 | - | ||
| 147 | -**Ask:** "Never mind, I don't need this anymore" | ||
| 148 | - | ||
| 149 | -**Agent:** | ||
| 150 | -- Asks for confirmation | ||
| 151 | -- If you confirm, deletes the plan | ||
| 152 | -- All progress is lost | ||
| 153 | - | ||
| 154 | ---- | ||
| 155 | - | ||
| 156 | -## Plan Management Commands | ||
| 157 | - | ||
| 158 | -While the plan tool is primarily model-driven (the agent uses it automatically), you can also interact with plans directly: | ||
| 159 | - | ||
| 160 | -### List All Plans | ||
| 161 | - | ||
| 162 | -**Command:** `/plans` (not yet implemented in v1 - coming in v2) | ||
| 163 | - | ||
| 164 | -**What it shows:** | ||
| 165 | -``` | ||
| 166 | -Active plans: | ||
| 167 | -1. setup-project (2/5 tasks complete) | ||
| 168 | -2. fix-bugs (0/3 tasks complete) | ||
| 169 | -``` | ||
| 170 | - | ||
| 171 | -### View Plan Details | ||
| 172 | - | ||
| 173 | -**Ask the agent:** "Show me the details of plan X" | ||
| 174 | - | ||
| 175 | -**Agent responds:** Full task list with status for each task | ||
| 176 | - | ||
| 177 | -### Delete a Plan | ||
| 178 | - | ||
| 179 | -**Ask the agent:** "Delete plan X" | ||
| 180 | - | ||
| 181 | -**Agent:** Confirms and deletes the plan | ||
| 182 | - | ||
| 183 | ---- | ||
| 184 | - | ||
| 185 | -## Session Lifecycle | ||
| 186 | - | ||
| 187 | -### Plans and /new Command | ||
| 188 | - | ||
| 189 | -When you start a new session with `/new`: | ||
| 190 | -- All active plans are **cleared** | ||
| 191 | -- This is intentional: a new session means a fresh start | ||
| 192 | -- If you want to continue an old plan, don't use `/new` | ||
| 193 | - | ||
| 194 | -### Plans and Agent Restart | ||
| 195 | - | ||
| 196 | -In v1 (current version): | ||
| 197 | -- Plans are stored **in memory only** | ||
| 198 | -- Plans are **lost** when the agent restarts | ||
| 199 | -- This keeps v1 simple and fast | ||
| 200 | - | ||
| 201 | -In v2 (future): | ||
| 202 | -- Plans will be persisted to session files | ||
| 203 | -- Plans will survive agent restarts | ||
| 204 | - | ||
| 205 | ---- | ||
| 206 | - | ||
| 207 | -## The plan Tool (For Advanced Users) | ||
| 208 | - | ||
| 209 | -The `plan` tool is available for the model to call. It supports these actions: | ||
| 210 | - | ||
| 211 | -| Action | Purpose | Required Parameters | | ||
| 212 | -|--------|---------|---------------------| | ||
| 213 | -| `create` | Create a new plan | `tasks` (array of `{description}`) | | ||
| 214 | -| `get` | Get plan details | `name` | | ||
| 215 | -| `update` | Update task status | `name`, `taskId`, `status` | | ||
| 216 | -| `next` | Move to next task | `name` | | ||
| 217 | -| `list` | List all plans | none | | ||
| 218 | -| `delete` | Delete a plan | `name` | | ||
| 219 | -| `status` | Get status line | `name` | | ||
| 220 | 20 | ||
| 221 | -### Status Values | 21 | + ```text |
| 22 | + 📋 plan: create | ||
| 23 | + 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open) | ||
| 24 | + 💾 write_file: a.txt (1 bytes) | ||
| 25 | + 💾 write_file: b.txt (1 bytes) | ||
| 26 | + 📋 plan: update user-request-1 | ||
| 27 | + 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open) | ||
| 28 | + 📋 plan: update user-request-1 | ||
| 29 | + 📋 [2/2] All tasks complete! | ||
| 30 | + ``` | ||
| 222 | 31 | ||
| 223 | -- `open` - Task not started | 32 | +- **Window** (`mm -tui`): each call is a tool call in the conversation, and the `📋 [C/N] …` line follows it as a notice. |
| 224 | -- `in_progress` - Task currently being worked on | 33 | +- **Editor** (`mm -acp`): each call is a tool call; the status line is inside its result. |
| 225 | -- `done` - Task completed successfully | ||
| 226 | -- `failed` - Task failed | ||
| 227 | 34 | ||
| 228 | ---- | 35 | +The status line shows the task being worked on — the first one in progress, else the first one still open: `[C/N] <icon> Task C: <description> (<status>)`. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. When every task is over it says `[N/N] All tasks complete!`, or `[N/N] Plan finished: D done, F failed` if some failed. |
| 229 | 36 | ||
| 230 | -## The plan-manager Skill | 37 | +## Steer the plan |
| 231 | 38 | ||
| 232 | -The agent uses the `plan-manager` skill to know **when and how** to use the plan tool. This skill includes: | 39 | +The model decides when to call the tool; you steer it in your own words: |
| 233 | 40 | ||
| 234 | -- **Detection patterns:** How to recognize when a user gives a multi-step request | 41 | +- **Where are we?** "What's the status of the plan?" — the model calls `status` and mm shows the line. |
| 235 | -- **Usage guidelines:** When to create plans, update tasks, move between tasks | 42 | +- **Skip a task.** "Skip the tests and go on." — the model can mark it and move on with `next`. |
| 236 | -- **Display format:** How to show plan status to the user | 43 | +- **A task failed.** The model can mark it `failed`, with a note; the status line then moves to the next open task. |
| 237 | -- **Error handling:** What to do when things go wrong | 44 | +- **Drop the plan.** "Forget this plan." — the model can call `delete`. |
| 238 | 45 | ||
| 239 | -You don't need to manually load this skill - the agent does it automatically when appropriate. | 46 | +There is no slash command for plans: they are the model's tool. |
| 240 | 47 | ||
| 241 | ---- | 48 | +## Variants |
| 242 | 49 | ||
| 243 | -## Examples | 50 | +- **Several conversations in an editor.** Each thread has its own plans: a plan made in one Zed thread is not visible in another. |
| 244 | - | 51 | +- **Starting over.** `/new` and `/resume` forget the plans in every front end; an editor's reload (`session/load`, `session/resume`) starts the thread with no plan. Plans are kept in memory only, never in the session file, and are lost when mm stops. |
| 245 | -### Example 1: Bug Fixing Workflow | 52 | +- **The `plan-manager` skill.** `skills/plan-manager` tells the model when to make a plan and how to move through it. Put it in the agent's `skillsDir` for a model that does not use the tool by itself. It also asks the model to repeat the status line in its answer; mm shows it anyway. |
| 246 | - | 53 | +- **An agent that does not need plans.** Set `planTool: false`: the tool is not declared. Do it for an agent whose job is a single answer (writing a document, answering questions), where a small model may otherwise loop on plan calls. |
| 247 | -``` | ||
| 248 | -You: Fix these issues in the auth module: | ||
| 249 | - 1. Check for null pointer exceptions | ||
| 250 | - 2. Add input validation | ||
| 251 | - 3. Write unit tests | ||
| 252 | - 4. Update the documentation | ||
| 253 | - | ||
| 254 | -Agent: I'll handle this in 4 steps. Starting with: Check for null pointer exceptions | ||
| 255 | -📋 plan: create user-request-1 | ||
| 256 | - | ||
| 257 | -... (agent checks code) ... | ||
| 258 | - | ||
| 259 | -Agent: Found 3 potential null pointer issues. | ||
| 260 | -📋 [2/4] 🔄 Task 2: Add input validation (in_progress) | ||
| 261 | - | ||
| 262 | -... (agent adds validation) ... | ||
| 263 | - | ||
| 264 | -Agent: Input validation added to auth.go. | ||
| 265 | -📋 [3/4] 🔄 Task 3: Write unit tests (in_progress) | ||
| 266 | - | ||
| 267 | -... (agent writes tests) ... | ||
| 268 | - | ||
| 269 | -Agent: Tests written and passing. | ||
| 270 | -📋 [4/4] 🔄 Task 4: Update the documentation (in_progress) | ||
| 271 | - | ||
| 272 | -... (agent updates docs) ... | ||
| 273 | - | ||
| 274 | -Agent: Documentation updated with new validation requirements. | ||
| 275 | -📋 [4/4] ✅ All tasks complete! | ||
| 276 | -``` | ||
| 277 | - | ||
| 278 | ---- | ||
| 279 | - | ||
| 280 | -### Example 2: Project Setup | ||
| 281 | - | ||
| 282 | -``` | ||
| 283 | -You: Set up a new Go project with: | ||
| 284 | - 1. Module initialization | ||
| 285 | - 2. Directory structure | ||
| 286 | - 3. Main file | ||
| 287 | - 4. Test file | ||
| 288 | - | ||
| 289 | -Agent: I'll set this up in 4 steps. Starting with: Module initialization | ||
| 290 | -📋 plan: create user-request-1 | ||
| 291 | - | ||
| 292 | -Agent: Module initialized with go mod | ||
| 293 | -📋 [2/4] 🔄 Task 2: Directory structure (in_progress) | ||
| 294 | - | ||
| 295 | -Agent: Directories created (cmd, internal, pkg, tests) | ||
| 296 | -📋 [3/4] 🔄 Task 3: Main file (in_progress) | ||
| 297 | - | ||
| 298 | -Agent: Main file created with basic structure | ||
| 299 | -📋 [4/4] 🔄 Task 4: Test file (in_progress) | ||
| 300 | - | ||
| 301 | -Agent: Test file created with sample tests | ||
| 302 | -📋 [4/4] ✅ All tasks complete! | ||
| 303 | -``` | ||
| 304 | - | ||
| 305 | ---- | ||
| 306 | - | ||
| 307 | -### Example 3: Code Review | ||
| 308 | - | ||
| 309 | -``` | ||
| 310 | -You: Review this PR and: | ||
| 311 | - 1. Check for security issues | ||
| 312 | - 2. Verify tests pass | ||
| 313 | - 3. Review the documentation | ||
| 314 | - 4. Leave comments | ||
| 315 | - | ||
| 316 | -Agent: I'll review this PR in 4 steps. Starting with: Check for security issues | ||
| 317 | -📋 plan: create user-request-1 | ||
| 318 | - | ||
| 319 | -Agent: No security issues found. | ||
| 320 | -📋 [2/4] 🔄 Task 2: Verify tests pass (in_progress) | ||
| 321 | - | ||
| 322 | -Agent: All tests passing. | ||
| 323 | -📋 [3/4] 🔄 Task 3: Review the documentation (in_progress) | ||
| 324 | - | ||
| 325 | -Agent: Documentation looks good, just needs a typo fix. | ||
| 326 | -📋 [4/4] 🔄 Task 4: Leave comments (in_progress) | ||
| 327 | - | ||
| 328 | -Agent: Comments left on the PR. | ||
| 329 | -📋 [4/4] ✅ All tasks complete! | ||
| 330 | -``` | ||
| 331 | - | ||
| 332 | ---- | ||
| 333 | - | ||
| 334 | -### Example 4: With a Task Failure | ||
| 335 | - | ||
| 336 | -``` | ||
| 337 | -You: Do these: | ||
| 338 | - 1. Connect to the database | ||
| 339 | - 2. Run the migration | ||
| 340 | - 3. Import the data | ||
| 341 | - | ||
| 342 | -Agent: I'll handle this in 3 steps. Starting with: Connect to the database | ||
| 343 | -📋 plan: create user-request-1 | ||
| 344 | - | ||
| 345 | -Agent: Database connection established. | ||
| 346 | -📋 [2/3] 🔄 Task 2: Run the migration (in_progress) | ||
| 347 | - | ||
| 348 | -Agent: Migration completed. | ||
| 349 | -📋 [3/3] 🔄 Task 3: Import the data (in_progress) | ||
| 350 | - | ||
| 351 | -Agent: Error: connection timeout to data source | ||
| 352 | -📋 plan: update user-request-1 | ||
| 353 | - | ||
| 354 | -Agent: ⚠️ Task 3 failed: connection timeout to data source | ||
| 355 | -📋 [3/3] ❌ Task 3: Import the data (failed: connection timeout) | ||
| 356 | - | ||
| 357 | -Agent: 2 of 3 tasks completed. The data import failed due to a connection timeout. Would you like me to retry, or skip this task? | ||
| 358 | -``` | ||
| 359 | - | ||
| 360 | ---- | ||
| 361 | - | ||
| 362 | -## Configuration | ||
| 363 | - | ||
| 364 | -The plan tool can be enabled or disabled via the YAML configuration file: | ||
| 365 | - | ||
| 366 | -```yaml | ||
| 367 | -# In your agent.yaml file | ||
| 368 | -planTool: true # Enabled by default | ||
| 369 | -``` | ||
| 370 | - | ||
| 371 | -To disable: | ||
| 372 | -```yaml | ||
| 373 | -planTool: false | ||
| 374 | -``` | ||
| 375 | - | ||
| 376 | -When disabled, the agent will not have access to the `plan` tool and will not automatically create or track plans. | ||
| 377 | - | ||
| 378 | ---- | ||
| 379 | 54 | ||
| 380 | ## Troubleshooting | 55 | ## Troubleshooting |
| 381 | 56 | ||
| 382 | -### Agent Not Creating Plans | 57 | +- **The agent does not create a plan.** Give a numbered list, and say "use the plan tool". With a small model, add the `plan-manager` skill. |
| 383 | - | 58 | +- **The agent keeps calling `plan update`.** `update` changes the status of ONE task; it cannot change a plan's tasks. A call without a valid `status` gets an answer that quotes the call back, names the missing field and gives a valid call. If a small model still repeats it, set `planTool: false` for that agent. |
| 384 | -If the agent isn't creating plans for your multi-step requests: | ||
| 385 | - | ||
| 386 | -1. **Make your request clearer** - Use numbered lists | ||
| 387 | - - ❌ "Fix some things" | ||
| 388 | - - ✅ "Do these: 1. Fix A, 2. Fix B, 3. Fix C" | ||
| 389 | - | ||
| 390 | -2. **Be more explicit** - Use words like "plan", "steps", "then" | ||
| 391 | - - ✅ "My plan is to first do X, then do Y" | ||
| 392 | - | ||
| 393 | -3. **Check the skill is loaded** - The agent should automatically load the `plan-manager` skill when it detects a plan-like request | ||
| 394 | - | ||
| 395 | -### The Agent Keeps Calling `plan update` | ||
| 396 | - | ||
| 397 | -`update` changes the status of one task; it cannot change a plan's tasks. A model that sends an `update` without a valid `status` — often with a `tasks` list, trying to edit the plan — gets an answer that quotes its call back, names the missing field, says that `tasks` is only read by `create`, and gives a valid call: | ||
| 398 | - | ||
| 399 | -``` | ||
| 400 | -[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}] | ||
| 401 | -``` | ||
| 402 | - | ||
| 403 | -If a small model still repeats the call, set `planTool: false` for an agent whose job does not need plans (writing a document, answering questions): the model then works without them. | ||
| 404 | - | ||
| 405 | -### Plans Disappearing | ||
| 406 | - | ||
| 407 | -In v1, plans are stored in memory and will be lost when: | ||
| 408 | -- You start a new session with `/new` | ||
| 409 | -- The agent restarts | ||
| 410 | -- You close and reopen the terminal | ||
| 411 | - | ||
| 412 | -This is expected behavior in v1. Session persistence is coming in v2. | ||
| 413 | - | ||
| 414 | ---- | ||
| 415 | - | ||
| 416 | -## Best Practices | ||
| 417 | - | ||
| 418 | -1. **Be specific** - The more detail in your task descriptions, the better the agent can execute them | ||
| 419 | - | ||
| 420 | -2. **Keep plans focused** - One plan per logical request. Don't mix unrelated tasks | ||
| 421 | - | ||
| 422 | -3. **Check progress** - The status line shows you exactly where the agent is | ||
| 423 | - | ||
| 424 | -4. **Ask for clarification** - If the agent seems stuck or confused, ask "What's the status?" | ||
| 425 | - | ||
| 426 | -5. **Start small** - For complex multi-step work, start with a few tasks and add more as needed | ||
| 427 | - | ||
| 428 | ---- | ||
| 429 | - | ||
| 430 | -## Comparison with Other Tools | ||
| 431 | - | ||
| 432 | -| Feature | plan tool | skills | slash commands | | ||
| 433 | -|---------|-----------|--------|----------------| | ||
| 434 | -| Purpose | Track multi-step work | Load procedures | User commands | | ||
| 435 | -| Trigger | Automatic (model detects) | Explicit (`/skill`) | Explicit (`/command`) | | ||
| 436 | -| Persistence (v1) | In-memory, session only | File-based | N/A | | ||
| 437 | -| User interaction | Indirect (via model) | Indirect (model loads) | Direct | | ||
| 438 | - | ||
| 439 | -The `plan` tool complements other mini-me features: | ||
| 440 | -- Use **skills** for complex, reusable procedures | ||
| 441 | -- Use **plan tool** for ad-hoc multi-step requests | ||
| 442 | -- Use **slash commands** for direct user actions | ||
| 443 | - | ||
| 444 | ---- | ||
| 445 | 59 | ||
| 446 | -## See Also | 60 | +## See also |
| 447 | 61 | ||
| 448 | -- [Slash Commands Reference](../reference/slash-commands.md) - For user commands | 62 | +- [Tools reference — `plan`](../reference/tools.md#plan): every action, field, result and error |
| 449 | -- [Tools Reference](../reference/tools.md) - For all built-in tools | 63 | +- [Configuration reference](../reference/configuration.md): `planTool` |
| 450 | -- [Skills Overview](../explanation/skills.md) - For the skill system | 64 | +- [Slash commands reference](../reference/slash-commands.md): `/new`, `/resume` |
| 65 | +- `demos/plans/` — a measured demo: a Go program built in four planned steps, and a plan that ends with a failed task | ||
modified
docs/en/reference/slash-commands.md +3 -3 | @@ -9,9 +9,9 @@ | ||
| 9 | 9 | | `/quit` | Exits the program. | Cancels the generation, prints `Goodbye.`, exits. | |
| 10 | 10 | | `/abort` | Prints `⚠️ No generation in progress.` | Cancels the generation, prints `🛑 Aborting...`; the commands already run stay in the history. | |
| 11 | 11 | | `/compact` | Compresses the history now, regardless of the threshold; prints the `🗜️` report, or `🗜️ nothing to compact` when no turn is older than `context.keepLastTurns`. | Sent to the model as a new message (see below). | |
| 12 | -| `/new` | Clears the history back to the system prompt, resets the loop detector and forgets the server's last token count; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. | | |
| 12 | +| `/new` | Clears the history back to the system prompt, resets the loop detector, forgets the server's last token count and the conversation's plans; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. | | |
| 13 | 13 | | `/sessions` | Lists the project's stored sessions, newest first: id, last update, question count, first question, then ` · ⚠️ N error(s)` when the session recorded errors; `*` marks the current one. With `sessions.enabled: false`: `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Sent to the model as a new message. | |
| 14 | -| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector and the server's token count; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. | | |
| 14 | +| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector, the server's token count and the plans; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. | | |
| 15 | 15 | | `/help` | Prints the commands of this front end, one per line with their description, aligned. | Sent to the model as a new message. | |
| 16 | 16 | | `/copy [n\|all]` | Copies the last answer (`n`: the n-th from the end) to the clipboard through `pbcopy`/`wl-copy`/`xclip`/`xsel` or, failing that, OSC 52 when stdout is a terminal, and prints `📋 copied N chars (<road>)` or `⚠️ …`. `/copy all` is window-mode only: here it says so. | Sent to the model as a new message. | |
| 17 | 17 | | `/save <file> [n]` | Writes the last answer (`n`: the n-th from the end) as it is to a new file, creating the missing directories, and prints `💾 saved the last answer to <file> (N bytes)`. Never overwrites: an existing file gives `⚠️ <file> already exists — nothing was written; choose another name`. A file name with spaces is not supported. The model is not called. | Sent to the model as a new message. | |
| @@ -176,7 +176,7 @@ Two commands exist in ACP mode, three with `unloadModels` (the default): `/new`, | ||
| 176 | 176 | |
| 177 | 177 | | Command | Over ACP | |
| 178 | 178 | |---------|----------| |
| 179 | -| `/new` | Same as in the terminal: history back to the system prompt, token count forgotten; the session id, `cwd` and allow-always grants are kept. | | |
| 179 | +| `/new` | Same as in the terminal: history back to the system prompt, token count and this session's plans forgotten (other sessions keep theirs); the session id, `cwd` and allow-always grants are kept. | | |
| 180 | 180 | | `/compact` | Same as in the terminal, always forced: `🗜️ compressed …`, `🗜️ nothing to compact: …` or `[compact: failed, history kept: …]`. Waits for a running turn to finish first. | |
| 181 | 181 | | `/memory <words>` | With `sessions.enabled` and `sessions.searchMemory`, advertised with the input hint `<words>`: same as in the terminal, in the session's project (`sessions.dir` under its `cwd`), as the agent's text; the history is not touched. | |
| 182 | 182 | | `/docs <words>` | With `docSearch.searchDocs`, advertised with the input hint `<words>`: same as in the terminal, from the session's `cwd`, as the agent's text; the history is not touched. | |
| @@ -9,9 +9,9 @@ | |||
| 9 | | `/quit` | Exits the program. | Cancels the generation, prints `Goodbye.`, exits. | | 9 | | `/quit` | Exits the program. | Cancels the generation, prints `Goodbye.`, exits. | |
| 10 | | `/abort` | Prints `⚠️ No generation in progress.` | Cancels the generation, prints `🛑 Aborting...`; the commands already run stay in the history. | | 10 | | `/abort` | Prints `⚠️ No generation in progress.` | Cancels the generation, prints `🛑 Aborting...`; the commands already run stay in the history. | |
| 11 | | `/compact` | Compresses the history now, regardless of the threshold; prints the `🗜️` report, or `🗜️ nothing to compact` when no turn is older than `context.keepLastTurns`. | Sent to the model as a new message (see below). | | 11 | | `/compact` | Compresses the history now, regardless of the threshold; prints the `🗜️` report, or `🗜️ nothing to compact` when no turn is older than `context.keepLastTurns`. | Sent to the model as a new message (see below). | |
| 12 | -| `/new` | Clears the history back to the system prompt, resets the loop detector and forgets the server's last token count; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. | | 12 | +| `/new` | Clears the history back to the system prompt, resets the loop detector, forgets the server's last token count and the conversation's plans; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. | |
| 13 | | `/sessions` | Lists the project's stored sessions, newest first: id, last update, question count, first question, then ` · ⚠️ N error(s)` when the session recorded errors; `*` marks the current one. With `sessions.enabled: false`: `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Sent to the model as a new message. | | 13 | | `/sessions` | Lists the project's stored sessions, newest first: id, last update, question count, first question, then ` · ⚠️ N error(s)` when the session recorded errors; `*` marks the current one. With `sessions.enabled: false`: `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Sent to the model as a new message. | |
| 14 | -| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector and the server's token count; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. | | 14 | +| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector, the server's token count and the plans; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. | |
| 15 | | `/help` | Prints the commands of this front end, one per line with their description, aligned. | Sent to the model as a new message. | | 15 | | `/help` | Prints the commands of this front end, one per line with their description, aligned. | Sent to the model as a new message. | |
| 16 | | `/copy [n\|all]` | Copies the last answer (`n`: the n-th from the end) to the clipboard through `pbcopy`/`wl-copy`/`xclip`/`xsel` or, failing that, OSC 52 when stdout is a terminal, and prints `📋 copied N chars (<road>)` or `⚠️ …`. `/copy all` is window-mode only: here it says so. | Sent to the model as a new message. | | 16 | | `/copy [n\|all]` | Copies the last answer (`n`: the n-th from the end) to the clipboard through `pbcopy`/`wl-copy`/`xclip`/`xsel` or, failing that, OSC 52 when stdout is a terminal, and prints `📋 copied N chars (<road>)` or `⚠️ …`. `/copy all` is window-mode only: here it says so. | Sent to the model as a new message. | |
| 17 | | `/save <file> [n]` | Writes the last answer (`n`: the n-th from the end) as it is to a new file, creating the missing directories, and prints `💾 saved the last answer to <file> (N bytes)`. Never overwrites: an existing file gives `⚠️ <file> already exists — nothing was written; choose another name`. A file name with spaces is not supported. The model is not called. | Sent to the model as a new message. | | 17 | | `/save <file> [n]` | Writes the last answer (`n`: the n-th from the end) as it is to a new file, creating the missing directories, and prints `💾 saved the last answer to <file> (N bytes)`. Never overwrites: an existing file gives `⚠️ <file> already exists — nothing was written; choose another name`. A file name with spaces is not supported. The model is not called. | Sent to the model as a new message. | |
| @@ -176,7 +176,7 @@ Two commands exist in ACP mode, three with `unloadModels` (the default): `/new`, | |||
| 176 | 176 | ||
| 177 | | Command | Over ACP | | 177 | | Command | Over ACP | |
| 178 | |---------|----------| | 178 | |---------|----------| |
| 179 | -| `/new` | Same as in the terminal: history back to the system prompt, token count forgotten; the session id, `cwd` and allow-always grants are kept. | | 179 | +| `/new` | Same as in the terminal: history back to the system prompt, token count and this session's plans forgotten (other sessions keep theirs); the session id, `cwd` and allow-always grants are kept. | |
| 180 | | `/compact` | Same as in the terminal, always forced: `🗜️ compressed …`, `🗜️ nothing to compact: …` or `[compact: failed, history kept: …]`. Waits for a running turn to finish first. | | 180 | | `/compact` | Same as in the terminal, always forced: `🗜️ compressed …`, `🗜️ nothing to compact: …` or `[compact: failed, history kept: …]`. Waits for a running turn to finish first. | |
| 181 | | `/memory <words>` | With `sessions.enabled` and `sessions.searchMemory`, advertised with the input hint `<words>`: same as in the terminal, in the session's project (`sessions.dir` under its `cwd`), as the agent's text; the history is not touched. | | 181 | | `/memory <words>` | With `sessions.enabled` and `sessions.searchMemory`, advertised with the input hint `<words>`: same as in the terminal, in the session's project (`sessions.dir` under its `cwd`), as the agent's text; the history is not touched. | |
| 182 | | `/docs <words>` | With `docSearch.searchDocs`, advertised with the input hint `<words>`: same as in the terminal, from the session's `cwd`, as the agent's text; the history is not touched. | | 182 | | `/docs <words>` | With `docSearch.searchDocs`, advertised with the input hint `<words>`: same as in the terminal, from the session's `cwd`, as the agent's text; the history is not touched. | |
modified
docs/en/reference/tools.md +32 -0 | @@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines: | ||
| 132 | 132 | │ 6 │ } |
| 133 | 133 | ``` |
| 134 | 134 | |
| 135 | +## `plan` | |
| 136 | + | |
| 137 | +Declared when `planTool: true` (the default). Turns a list of tasks into a plan the model updates as it works. See [Manage plans and tasks](../how-to/manage-plans-and-tasks.md). | |
| 138 | + | |
| 139 | +| Field | Type | Description | | |
| 140 | +|-------|------|-------------| | |
| 141 | +| `action` | string | Required. `create`, `get`, `update`, `next`, `list`, `delete` or `status`. | | |
| 142 | +| `name` | string | The plan. For `create`, optional: a plan without a name is called `user-request-N` (N counts the conversation's plans). For the other actions, optional too: without it, the action works on the conversation's most recently created plan (`list` ignores it). | | |
| 143 | +| `tasks` | list of `{description, id?}` | For `create`: the tasks, in order. A task without an `id` gets its position (1, 2, …). Ignored by the other actions. | | |
| 144 | +| `taskId` | integer | For `update`: the task, from 1. | | |
| 145 | +| `status` | string | For `update`: `open`, `in_progress`, `done` or `failed`. | | |
| 146 | +| `message` | string | For `update`: a note kept on the task. | | |
| 147 | + | |
| 148 | +| Action | Does | Result (JSON text) | | |
| 149 | +|--------|------|--------------------| | |
| 150 | +| `create` | Creates the plan, every task `open`. Refused when the name is taken. | `result` (`Plan '<name>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total`; in the line mode, also the whole `plan`. | | |
| 151 | +| `get` | Reads one plan. | `result`, `plan` (name, tasks with id, description, status, start and end times, note). | | |
| 152 | +| `update` | Sets one task's status; `in_progress` records its start time (once) and makes it the current task, `done` and `failed` record its end time. | `result`, `status` (the status line, below). | | |
| 153 | +| `next` | Marks the current task `done` and starts the next one that is neither done nor failed (`in_progress`); a task already finished with `update` is skipped. | `result` (`Task N done. Next: Task M - <description>`, or `Plan '<name>' is complete! All N tasks done.`), `status`, `nextTask` when there is one. | | |
| 154 | +| `list` | Lists the plans held. | `result`, `plans` (names, oldest first). | | |
| 155 | +| `delete` | Removes a plan. | `result`. | | |
| 156 | +| `status` | Reads the status line. | `result` (`Plan '<name>': D/N tasks complete`), `status`. | | |
| 157 | + | |
| 158 | +| Aspect | Behaviour | | |
| 159 | +|--------|-----------| | |
| 160 | +| Status line | `[C/N] <icon> Task C: <description> (<status>)` for the task being worked on — the first one in progress, else the first one still open —, `[N/N] Plan finished: D done, F failed` when every task is over and some failed, `[N/N] All tasks complete!` when all are done. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. | | |
| 161 | +| Errors | Returned as text, never as a failed generation: `[error: no plans found for this session]`, `[error: no plan named '<name>' found]`, `[error: plan '<name>' already exists]`, `[error: no task with id N in plan '<name>']`, `[error: unknown action '<action>'. Valid actions: …]`. An `update` without a valid `status` quotes the call back, says that `tasks` is ignored by `update` when it was sent, and gives a call that works. | | |
| 162 | +| Storage | In memory, per conversation: the line mode and the window hold one; over ACP each session has its own, and one thread never sees another's plans. Plans are not written to the session file and are lost when mm stops. A conversation that changes starts with no plan: `/new` and `/resume` in every front end, `session/load` and `session/resume` over ACP. | | |
| 163 | +| Permission | `create`, `update`, `next` and `delete` are requested when `yolo: false`; a refusal returns `[plan: command rejected by the user]`. `get`, `list` and `status` never ask. | | |
| 164 | +| Display | Line mode: `📋 plan: <action> <name>` before the call, then `📋 <status line>` after every action that has one (`create`, `update`, `next`, `status`). Window: the call as a tool call, and `📋 <status line>` as a notice. Editor (ACP): the call as a tool call, the status inside its result. The progress is shown by mm, whether or not the model repeats it. | | |
| 165 | +| ACP and window | Tool call of kind `edit` for `create`, `update`, `next`, `delete`, `read` for the others; a result starting with `[error:` is a failed call. | | |
| 166 | + | |
| 135 | 167 | ## `lsp` |
| 136 | 168 | |
| 137 | 169 | Declared when `lsp.servers` lists at least one server whose command is on the `PATH` at start-up. A listed server that is not found is skipped with a `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; when none is found the tool is not declared and the banner says so. |
| @@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines: | |||
| 132 | │ 6 │ } | 132 | │ 6 │ } |
| 133 | ``` | 133 | ``` |
| 134 | 134 | ||
| 135 | +## `plan` | ||
| 136 | + | ||
| 137 | +Declared when `planTool: true` (the default). Turns a list of tasks into a plan the model updates as it works. See [Manage plans and tasks](../how-to/manage-plans-and-tasks.md). | ||
| 138 | + | ||
| 139 | +| Field | Type | Description | | ||
| 140 | +|-------|------|-------------| | ||
| 141 | +| `action` | string | Required. `create`, `get`, `update`, `next`, `list`, `delete` or `status`. | | ||
| 142 | +| `name` | string | The plan. For `create`, optional: a plan without a name is called `user-request-N` (N counts the conversation's plans). For the other actions, optional too: without it, the action works on the conversation's most recently created plan (`list` ignores it). | | ||
| 143 | +| `tasks` | list of `{description, id?}` | For `create`: the tasks, in order. A task without an `id` gets its position (1, 2, …). Ignored by the other actions. | | ||
| 144 | +| `taskId` | integer | For `update`: the task, from 1. | | ||
| 145 | +| `status` | string | For `update`: `open`, `in_progress`, `done` or `failed`. | | ||
| 146 | +| `message` | string | For `update`: a note kept on the task. | | ||
| 147 | + | ||
| 148 | +| Action | Does | Result (JSON text) | | ||
| 149 | +|--------|------|--------------------| | ||
| 150 | +| `create` | Creates the plan, every task `open`. Refused when the name is taken. | `result` (`Plan '<name>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total`; in the line mode, also the whole `plan`. | | ||
| 151 | +| `get` | Reads one plan. | `result`, `plan` (name, tasks with id, description, status, start and end times, note). | | ||
| 152 | +| `update` | Sets one task's status; `in_progress` records its start time (once) and makes it the current task, `done` and `failed` record its end time. | `result`, `status` (the status line, below). | | ||
| 153 | +| `next` | Marks the current task `done` and starts the next one that is neither done nor failed (`in_progress`); a task already finished with `update` is skipped. | `result` (`Task N done. Next: Task M - <description>`, or `Plan '<name>' is complete! All N tasks done.`), `status`, `nextTask` when there is one. | | ||
| 154 | +| `list` | Lists the plans held. | `result`, `plans` (names, oldest first). | | ||
| 155 | +| `delete` | Removes a plan. | `result`. | | ||
| 156 | +| `status` | Reads the status line. | `result` (`Plan '<name>': D/N tasks complete`), `status`. | | ||
| 157 | + | ||
| 158 | +| Aspect | Behaviour | | ||
| 159 | +|--------|-----------| | ||
| 160 | +| Status line | `[C/N] <icon> Task C: <description> (<status>)` for the task being worked on — the first one in progress, else the first one still open —, `[N/N] Plan finished: D done, F failed` when every task is over and some failed, `[N/N] All tasks complete!` when all are done. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. | | ||
| 161 | +| Errors | Returned as text, never as a failed generation: `[error: no plans found for this session]`, `[error: no plan named '<name>' found]`, `[error: plan '<name>' already exists]`, `[error: no task with id N in plan '<name>']`, `[error: unknown action '<action>'. Valid actions: …]`. An `update` without a valid `status` quotes the call back, says that `tasks` is ignored by `update` when it was sent, and gives a call that works. | | ||
| 162 | +| Storage | In memory, per conversation: the line mode and the window hold one; over ACP each session has its own, and one thread never sees another's plans. Plans are not written to the session file and are lost when mm stops. A conversation that changes starts with no plan: `/new` and `/resume` in every front end, `session/load` and `session/resume` over ACP. | | ||
| 163 | +| Permission | `create`, `update`, `next` and `delete` are requested when `yolo: false`; a refusal returns `[plan: command rejected by the user]`. `get`, `list` and `status` never ask. | | ||
| 164 | +| Display | Line mode: `📋 plan: <action> <name>` before the call, then `📋 <status line>` after every action that has one (`create`, `update`, `next`, `status`). Window: the call as a tool call, and `📋 <status line>` as a notice. Editor (ACP): the call as a tool call, the status inside its result. The progress is shown by mm, whether or not the model repeats it. | | ||
| 165 | +| ACP and window | Tool call of kind `edit` for `create`, `update`, `next`, `delete`, `read` for the others; a result starting with `[error:` is a failed call. | | ||
| 166 | + | ||
| 135 | ## `lsp` | 167 | ## `lsp` |
| 136 | 168 | ||
| 137 | Declared when `lsp.servers` lists at least one server whose command is on the `PATH` at start-up. A listed server that is not found is skipped with a `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; when none is found the tool is not declared and the banner says so. | 169 | Declared when `lsp.servers` lists at least one server whose command is on the `PATH` at start-up. A listed server that is not found is skipped with a `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; when none is found the tool is not declared and the banner says so. |
modified
docs/fr/README.md +2 -1 | @@ -26,6 +26,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 26 | 26 | - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le kit `mini-me` de [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), les mixins `go-toolchain` et `cli-tools` sous `sbx/`, mode fenêtre et éditeur |
| 27 | 27 | - [Ajouter un skill](how-to/add-a-skill.md) |
| 28 | 28 | - [Déclarer un outil personnalisé](how-to/declare-a-custom-tool.md) |
| 29 | +- [Gérer des plans et des tâches](how-to/manage-plans-and-tasks.md) — l'outil `plan` : donner une liste, suivre la ligne de progression `📋`, des plans par conversation | |
| 29 | 30 | - [Sauvegarder une réponse dans un fichier](how-to/save-an-answer.md) |
| 30 | 31 | - [Chercher dans un dossier de documentation](how-to/search-the-docs.md) — `/docs <mots>`, l'outil `search_docs`, `docSearch` (BM25, sans modèle) |
| 31 | 32 | - [Chercher dans les sessions précédentes](how-to/search-earlier-sessions.md) — `/memory <mots>`, l'outil `search_memory`, `sessions.searchMemory` / `sessions.autoSearchMemory` |
| @@ -54,7 +55,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 54 | 55 | - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage |
| 55 | 56 | - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre |
| 56 | 57 | - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation |
| 57 | -- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, outils déclarés | |
| 58 | +- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, outils déclarés | |
| 58 | 59 | - [Format d'un fichier de skill](reference/skill-format.md) |
| 59 | 60 | - [Les hooks](reference/hooks.md) — les six événements, le JSON qu'un hook lit et écrit, `matcher`, `timeout`, `onError`, erreurs de validation |
| 60 | 61 | - [Surface Agent Client Protocol](reference/acp.md) |
| @@ -26,6 +26,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 26 | - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le kit `mini-me` de [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), les mixins `go-toolchain` et `cli-tools` sous `sbx/`, mode fenêtre et éditeur | 26 | - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le kit `mini-me` de [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), les mixins `go-toolchain` et `cli-tools` sous `sbx/`, mode fenêtre et éditeur |
| 27 | - [Ajouter un skill](how-to/add-a-skill.md) | 27 | - [Ajouter un skill](how-to/add-a-skill.md) |
| 28 | - [Déclarer un outil personnalisé](how-to/declare-a-custom-tool.md) | 28 | - [Déclarer un outil personnalisé](how-to/declare-a-custom-tool.md) |
| 29 | +- [Gérer des plans et des tâches](how-to/manage-plans-and-tasks.md) — l'outil `plan` : donner une liste, suivre la ligne de progression `📋`, des plans par conversation | ||
| 29 | - [Sauvegarder une réponse dans un fichier](how-to/save-an-answer.md) | 30 | - [Sauvegarder une réponse dans un fichier](how-to/save-an-answer.md) |
| 30 | - [Chercher dans un dossier de documentation](how-to/search-the-docs.md) — `/docs <mots>`, l'outil `search_docs`, `docSearch` (BM25, sans modèle) | 31 | - [Chercher dans un dossier de documentation](how-to/search-the-docs.md) — `/docs <mots>`, l'outil `search_docs`, `docSearch` (BM25, sans modèle) |
| 31 | - [Chercher dans les sessions précédentes](how-to/search-earlier-sessions.md) — `/memory <mots>`, l'outil `search_memory`, `sessions.searchMemory` / `sessions.autoSearchMemory` | 32 | - [Chercher dans les sessions précédentes](how-to/search-earlier-sessions.md) — `/memory <mots>`, l'outil `search_memory`, `sessions.searchMemory` / `sessions.autoSearchMemory` |
| @@ -54,7 +55,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 54 | - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage | 55 | - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage |
| 55 | - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre | 56 | - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre |
| 56 | - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation | 57 | - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation |
| 57 | -- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, outils déclarés | 58 | +- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, outils déclarés |
| 58 | - [Format d'un fichier de skill](reference/skill-format.md) | 59 | - [Format d'un fichier de skill](reference/skill-format.md) |
| 59 | - [Les hooks](reference/hooks.md) — les six événements, le JSON qu'un hook lit et écrit, `matcher`, `timeout`, `onError`, erreurs de validation | 60 | - [Les hooks](reference/hooks.md) — les six événements, le JSON qu'un hook lit et écrit, `matcher`, `timeout`, `onError`, erreurs de validation |
| 60 | - [Surface Agent Client Protocol](reference/acp.md) | 61 | - [Surface Agent Client Protocol](reference/acp.md) |
modified
docs/fr/how-to/manage-plans-and-tasks.md +42 -427 | @@ -1,450 +1,65 @@ | ||
| 1 | ---- | |
| 2 | -title: Gérer les plans et les tâches | |
| 3 | ---- | |
| 1 | +# Gérer des plans et des tâches | |
| 4 | 2 | |
| 5 | -# Gérer les plans et les tâches | |
| 3 | +Ce guide montre comment faire travailler l'agent sur une liste de tâches avec son outil `plan`, et comment suivre sa progression. L'outil est activé par défaut (`planTool: true`). | |
| 6 | 4 | |
| 7 | -Mini-me inclut un outil intégré **plan** qui permet à l'agent de créer, suivre et gérer des listes de tâches multi-étapes. Lorsque vous donnez à l'agent une liste de tâches à effectuer, il crée automatiquement un plan et suit la progression, en vous montrant l'état actuel après chaque réponse. | |
| 5 | +## Donner une liste à l'agent | |
| 8 | 6 | |
| 9 | -## Exemple rapide | |
| 7 | +Demandez plusieurs choses à la fois, sous forme de liste : | |
| 10 | 8 | |
| 9 | +```text | |
| 10 | +Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B. | |
| 11 | 11 | ``` |
| 12 | -Vous : Fais ces 3 choses : 1. Créer un fichier README, 2. Ajouter une licence, 3. Valider les modifications | |
| 13 | 12 | |
| 14 | -Agent : Je vais m'en occuper en 3 étapes. En commençant par : Créer un fichier README | |
| 15 | -📋 plan: create user-request-1 | |
| 13 | +Le modèle crée un plan à partir de la liste, le suit, et met à jour le statut de chaque tâche au fur et à mesure. Les petits modèles le font plus sûrement quand on le leur dit : « Use the plan tool. Do these… ». | |
| 16 | 14 | |
| 17 | -Agent : Fichier README créé. | |
| 18 | -📋 [2/3] 🔄 Tâche 2 : Ajouter une licence (in_progress) | |
| 15 | +## Suivre la progression | |
| 19 | 16 | |
| 20 | -Agent : Licence ajoutée. | |
| 21 | -📋 [3/3] 🔄 Tâche 3 : Valider les modifications (in_progress) | |
| 17 | +mm affiche où en est le plan après chaque action qui change ou lit son état (`create`, `update`, `next`, `status`) — que le modèle en parle ou non dans sa réponse. | |
| 22 | 18 | |
| 23 | -Agent : Toutes les modifications ont été validées. | |
| 24 | -📋 [3/3] ✅ Toutes les tâches sont terminées ! | |
| 25 | -``` | |
| 26 | - | |
| 27 | -La ligne `📋` vous montre exactement où se trouve l'agent dans votre plan. | |
| 28 | - | |
| 29 | ---- | |
| 30 | - | |
| 31 | -## Fonctionnement | |
| 32 | - | |
| 33 | -### Cycle de vie d'un plan | |
| 34 | - | |
| 35 | -1. **Vous formulez votre demande** sous forme de liste de tâches | |
| 36 | -2. **L'agent détecte le plan** et le crée en utilisant l'outil `plan` | |
| 37 | -3. **L'agent travaille sur les tâches** une par une, en mettant à jour l'état | |
| 38 | -4. **L'agent signale la progression** après chaque action | |
| 39 | -5. **Le plan est terminé** lorsque toutes les tâches sont terminées | |
| 40 | - | |
| 41 | -### Icônes d'état | |
| 42 | - | |
| 43 | -| Icône | Signification | | |
| 44 | -|-------|--------------| | |
| 45 | -| ⏳ | Tâche non démarrée (ouverte) | | |
| 46 | -| 🔄 | Tâche en cours | | |
| 47 | -| ✅ | Tâche terminée avec succès | | |
| 48 | -| ❌ | Tâche en échec | | |
| 49 | - | |
| 50 | -### Format de la ligne d'état | |
| 51 | - | |
| 52 | -``` | |
| 53 | -📋 [courante/total] icône Tâche N : description (état) | |
| 54 | -``` | |
| 55 | - | |
| 56 | -Exemples : | |
| 57 | -- `📋 [1/5] ⏳ Tâche 1 : Analyser le code (open)` - Première tâche, non démarrée | |
| 58 | -- `📋 [2/5] 🔄 Tâche 2 : Corriger les bugs (in_progress)` - Deuxième tâche, en cours | |
| 59 | -- `📋 [5/5] ✅ Toutes les tâches sont terminées !` - Tout est terminé ! | |
| 60 | -- `📋 [3/5] ❌ Tâche 3 : Exécuter les tests (failed: timeout de connexion)` - Tâche en échec avec erreur | |
| 61 | - | |
| 62 | ---- | |
| 63 | - | |
| 64 | -## Scénarios utilisateur | |
| 65 | - | |
| 66 | -### Scénario 1 : Liste numérotée | |
| 67 | - | |
| 68 | -**Votre entrée :** | |
| 69 | -``` | |
| 70 | -Fais ces 5 choses : | |
| 71 | -1. Vérifier la version actuelle | |
| 72 | -2. Mettre à jour les dépendances | |
| 73 | -3. Exécuter les tests | |
| 74 | -4. Corriger les échecs | |
| 75 | -5. Déployer en staging | |
| 76 | -``` | |
| 77 | - | |
| 78 | -**Ce qui se passe :** | |
| 79 | -- L'agent crée un plan avec 5 tâches | |
| 80 | -- Travaille sur chaque tâche séquentiellement | |
| 81 | -- Affiche la progression : `📋 [1/5]`, `📋 [2/5]`, etc. | |
| 82 | -- Termine avec : `📋 [5/5] ✅ Toutes les tâches sont terminées !` | |
| 83 | - | |
| 84 | ---- | |
| 85 | - | |
| 86 | -### Scénario 2 : Tâches séparées par des virgules | |
| 87 | - | |
| 88 | -**Votre entrée :** | |
| 89 | -``` | |
| 90 | -Configurer le projet : créer le répertoire, initialiser git, et ajouter un README | |
| 91 | -``` | |
| 92 | - | |
| 93 | -**Ce qui se passe :** | |
| 94 | -- L'agent détecte qu'il s'agit d'une demande multi-étapes | |
| 95 | -- Crée un plan avec 3 tâches | |
| 96 | -- Exécute dans l'ordre | |
| 97 | - | |
| 98 | ---- | |
| 99 | - | |
| 100 | -### Scénario 3 : Plusieurs plans | |
| 101 | - | |
| 102 | -Vous pouvez avoir plusieurs plans actifs dans une seule session. | |
| 103 | - | |
| 104 | -**Votre entrée :** | |
| 105 | -``` | |
| 106 | -D'abord, corrige le module d'authentification : 1. Trouver le bug, 2. Écrire la correction, 3. Le tester | |
| 107 | -``` | |
| 108 | - | |
| 109 | -L'agent travaille sur le plan d'authentification, affiche : `📋 [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress)` | |
| 110 | - | |
| 111 | -**Ensuite vous dites :** | |
| 112 | -``` | |
| 113 | -Aussi, mettre à jour la documentation : 1. Mettre à jour le README, 2. Mettre à jour le CHANGELOG | |
| 114 | -``` | |
| 115 | - | |
| 116 | -L'agent crée un deuxième plan et gère les deux indépendamment. | |
| 117 | - | |
| 118 | -**Pour voir tous les plans actifs :** | |
| 119 | -``` | |
| 120 | -Quels plans ai-je ? | |
| 121 | -``` | |
| 122 | - | |
| 123 | ---- | |
| 124 | - | |
| 125 | -## Interagir avec les plans | |
| 126 | - | |
| 127 | -### Vérifier l'état actuel du plan | |
| 128 | - | |
| 129 | -**Demandez :** "Quel est l'état de mon plan ?" | |
| 130 | - | |
| 131 | -**L'agent répond :** | |
| 132 | -``` | |
| 133 | -Plan actuel : [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress). Suivant : Le tester | |
| 134 | -``` | |
| 135 | - | |
| 136 | -### Sauter une tâche | |
| 137 | - | |
| 138 | -**Demandez :** "Saute l'étape de test, déploie directement" | |
| 139 | - | |
| 140 | -**Agent :** | |
| 141 | -- Marque la tâche actuelle comme terminée | |
| 142 | -- Saute à la tâche suivante | |
| 143 | -- Confirme : "Je saute l'étape de test. Déploiement en staging..." | |
| 144 | - | |
| 145 | -### Abandonner un plan | |
| 146 | - | |
| 147 | -**Demandez :** "Peu importe, je n'en ai plus besoin" | |
| 148 | - | |
| 149 | -**Agent :** | |
| 150 | -- Demande confirmation | |
| 151 | -- Si vous confirmez, supprime le plan | |
| 152 | -- Toute la progression est perdue | |
| 153 | - | |
| 154 | ---- | |
| 155 | - | |
| 156 | -## Commandes de gestion des plans | |
| 157 | - | |
| 158 | -Bien que l'outil plan soit principalement piloté par le modèle (l'agent l'utilise automatiquement), vous pouvez également interagir directement avec les plans : | |
| 159 | - | |
| 160 | -### Lister tous les plans | |
| 161 | - | |
| 162 | -**Commande :** `/plans` (pas encore implémenté en v1 - arrive en v2) | |
| 163 | - | |
| 164 | -**Ce que cela affiche :** | |
| 165 | -``` | |
| 166 | -Plans actifs : | |
| 167 | -1. configuration-projet (2/5 tâches terminées) | |
| 168 | -2. corriger-bugs (0/3 tâches terminées) | |
| 169 | -``` | |
| 170 | - | |
| 171 | -### Voir les détails d'un plan | |
| 172 | - | |
| 173 | -**Demandez à l'agent :** "Montre-moi les détails du plan X" | |
| 174 | - | |
| 175 | -**L'agent répond :** Liste complète des tâches avec l'état pour chaque tâche | |
| 176 | - | |
| 177 | -### Supprimer un plan | |
| 178 | - | |
| 179 | -**Demandez à l'agent :** "Supprime le plan X" | |
| 180 | - | |
| 181 | -**Agent :** Confirme et supprime le plan | |
| 182 | - | |
| 183 | ---- | |
| 184 | - | |
| 185 | -## Cycle de vie de la session | |
| 186 | - | |
| 187 | -### Plans et commande /new | |
| 188 | - | |
| 189 | -Lorsque vous démarrez une nouvelle session avec `/new` : | |
| 190 | -- Tous les plans actifs sont **supprimés** | |
| 191 | -- C'est intentionnel : une nouvelle session signifie un nouveau départ | |
| 192 | -- Si vous voulez continuer un ancien plan, ne pas utiliser `/new` | |
| 193 | - | |
| 194 | -### Plans et redémarrage de l'agent | |
| 195 | - | |
| 196 | -En v1 (version actuelle) : | |
| 197 | -- Les plans sont stockés **uniquement en mémoire** | |
| 198 | -- Les plans sont **perdus** lorsque l'agent redémarre | |
| 199 | -- Cela garde la v1 simple et rapide | |
| 200 | - | |
| 201 | -En v2 (future) : | |
| 202 | -- Les plans seront persistés dans les fichiers de session | |
| 203 | -- Les plans survivront aux redémarrages de l'agent | |
| 204 | - | |
| 205 | ---- | |
| 206 | - | |
| 207 | -## L'outil plan (pour les utilisateurs avancés) | |
| 208 | - | |
| 209 | -L'outil `plan` est disponible pour que le modèle puisse l'appeler. Il prend en charge ces actions : | |
| 210 | - | |
| 211 | -| Action | Objectif | Paramètres requis | | |
| 212 | -|--------|----------|-------------------| | |
| 213 | -| `create` | Créer un nouveau plan | `tasks` (tableau de `{description}`) | | |
| 214 | -| `get` | Obtenir les détails d'un plan | `name` | | |
| 215 | -| `update` | Mettre à jour l'état d'une tâche | `name`, `taskId`, `status` | | |
| 216 | -| `next` | Passer à la tâche suivante | `name` | | |
| 217 | -| `list` | Lister tous les plans | aucun | | |
| 218 | -| `delete` | Supprimer un plan | `name` | | |
| 219 | -| `status` | Obtenir la ligne d'état | `name` | | |
| 220 | - | |
| 221 | -### Valeurs d'état | |
| 222 | - | |
| 223 | -- `open` - Tâche non démarrée | |
| 224 | -- `in_progress` - Tâche actuellement en cours | |
| 225 | -- `done` - Tâche terminée avec succès | |
| 226 | -- `failed` - Tâche en échec | |
| 227 | - | |
| 228 | ---- | |
| 229 | - | |
| 230 | -## La compétence plan-manager | |
| 231 | - | |
| 232 | -L'agent utilise la compétence `plan-manager` pour savoir **quand et comment** utiliser l'outil plan. Cette compétence inclut : | |
| 233 | - | |
| 234 | -- **Modèles de détection** : Comment reconnaître quand un utilisateur donne une demande multi-étapes | |
| 235 | -- **Directives d'utilisation** : Quand créer des plans, mettre à jour des tâches, passer entre les tâches | |
| 236 | -- **Format d'affichage** : Comment montrer l'état du plan à l'utilisateur | |
| 237 | -- **Gestion des erreurs** : Que faire quand les choses tournent mal | |
| 238 | - | |
| 239 | -Vous n'avez pas besoin de charger manuellement cette compétence - l'agent le fait automatiquement lorsque cela est approprié. | |
| 240 | - | |
| 241 | ---- | |
| 242 | - | |
| 243 | -## Exemples | |
| 244 | - | |
| 245 | -### Exemple 1 : Workflow de correction de bug | |
| 246 | - | |
| 247 | -``` | |
| 248 | -Vous : Corrige ces problèmes dans le module d'authentification : | |
| 249 | - 1. Vérifier les exceptions de pointeur nul | |
| 250 | - 2. Ajouter la validation des entrées | |
| 251 | - 3. Écrire des tests unitaires | |
| 252 | - 4. Mettre à jour la documentation | |
| 253 | - | |
| 254 | -Agent : Je vais m'en occuper en 4 étapes. En commençant par : Vérifier les exceptions de pointeur nul | |
| 255 | -📋 plan: create user-request-1 | |
| 256 | - | |
| 257 | -... (l'agent vérifie le code) ... | |
| 258 | - | |
| 259 | -Agent : 3 problèmes de pointeur nul potentiels trouvés. | |
| 260 | -📋 [2/4] 🔄 Tâche 2 : Ajouter la validation des entrées (in_progress) | |
| 261 | - | |
| 262 | -... (l'agent ajoute la validation) ... | |
| 263 | - | |
| 264 | -Agent : Validation des entrées ajoutée à auth.go. | |
| 265 | -📋 [3/4] 🔄 Tâche 3 : Écrire des tests unitaires (in_progress) | |
| 266 | - | |
| 267 | -... (l'agent écrit les tests) ... | |
| 268 | - | |
| 269 | -Agent : Tests écrits et réussis. | |
| 270 | -📋 [4/4] 🔄 Tâche 4 : Mettre à jour la documentation (in_progress) | |
| 271 | - | |
| 272 | -... (l'agent met à jour les docs) ... | |
| 273 | - | |
| 274 | -Agent : Documentation mise à jour avec les nouvelles exigences de validation. | |
| 275 | -📋 [4/4] ✅ Toutes les tâches sont terminées ! | |
| 276 | -``` | |
| 277 | - | |
| 278 | ---- | |
| 279 | - | |
| 280 | -### Exemple 2 : Configuration d'un projet | |
| 281 | - | |
| 282 | -``` | |
| 283 | -Vous : Configure un nouveau projet Go avec : | |
| 284 | - 1. Initialisation du module | |
| 285 | - 2. Structure des répertoires | |
| 286 | - 3. Fichier main | |
| 287 | - 4. Fichier de test | |
| 288 | - | |
| 289 | -Agent : Je vais configurer cela en 4 étapes. En commençant par : Initialisation du module | |
| 290 | -📋 plan: create user-request-1 | |
| 291 | - | |
| 292 | -Agent : Module initialisé avec go mod | |
| 293 | -📋 [2/4] 🔄 Tâche 2 : Structure des répertoires (in_progress) | |
| 294 | - | |
| 295 | -Agent : Répertoires créés (cmd, internal, pkg, tests) | |
| 296 | -📋 [3/4] 🔄 Tâche 3 : Fichier main (in_progress) | |
| 297 | - | |
| 298 | -Agent : Fichier main créé avec la structure de base | |
| 299 | -📋 [4/4] 🔄 Tâche 4 : Fichier de test (in_progress) | |
| 300 | - | |
| 301 | -Agent : Fichier de test créé avec des tests exemples | |
| 302 | -📋 [4/4] ✅ Toutes les tâches sont terminées ! | |
| 303 | -``` | |
| 304 | - | |
| 305 | ---- | |
| 306 | - | |
| 307 | -### Exemple 3 : Revue de code | |
| 308 | - | |
| 309 | -``` | |
| 310 | -Vous : Revoir cette PR et : | |
| 311 | - 1. Vérifier les problèmes de sécurité | |
| 312 | - 2. Vérifier que les tests passent | |
| 313 | - 3. Revoir la documentation | |
| 314 | - 4. Laisser des commentaires | |
| 315 | - | |
| 316 | -Agent : Je vais revoir cette PR en 4 étapes. En commençant par : Vérifier les problèmes de sécurité | |
| 317 | -📋 plan: create user-request-1 | |
| 318 | - | |
| 319 | -Agent : Aucun problème de sécurité trouvé. | |
| 320 | -📋 [2/4] 🔄 Tâche 2 : Vérifier que les tests passent (in_progress) | |
| 321 | - | |
| 322 | -Agent : Tous les tests passent. | |
| 323 | -📋 [3/4] 🔄 Tâche 3 : Revoir la documentation (in_progress) | |
| 324 | - | |
| 325 | -Agent : La documentation a l'air bonne, il faut juste corriger une faute de frappe. | |
| 326 | -📋 [4/4] 🔄 Tâche 4 : Laisser des commentaires (in_progress) | |
| 327 | - | |
| 328 | -Agent : Commentaires laissés sur la PR. | |
| 329 | -📋 [4/4] ✅ Toutes les tâches sont terminées ! | |
| 330 | -``` | |
| 331 | - | |
| 332 | ---- | |
| 333 | - | |
| 334 | -### Exemple 4 : Avec un échec de tâche | |
| 335 | - | |
| 336 | -``` | |
| 337 | -Vous : Fais ceci : | |
| 338 | - 1. Se connecter à la base de données | |
| 339 | - 2. Exécuter la migration | |
| 340 | - 3. Importer les données | |
| 341 | - | |
| 342 | -Agent : Je vais m'en occuper en 3 étapes. En commençant par : Se connecter à la base de données | |
| 343 | -📋 plan: create user-request-1 | |
| 344 | - | |
| 345 | -Agent : Connexion à la base de données établie. | |
| 346 | -📋 [2/3] 🔄 Tâche 2 : Exécuter la migration (in_progress) | |
| 347 | - | |
| 348 | -Agent : Migration terminée. | |
| 349 | -📋 [3/3] 🔄 Tâche 3 : Importer les données (in_progress) | |
| 350 | - | |
| 351 | -Agent : Erreur : timeout de connexion à la source de données | |
| 352 | -📋 plan: update user-request-1 | |
| 353 | - | |
| 354 | -Agent : ⚠️ La tâche 3 a échoué : timeout de connexion à la source de données | |
| 355 | -📋 [3/3] ❌ Tâche 3 : Importer les données (failed: connection timeout) | |
| 356 | - | |
| 357 | -Agent : 2 des 3 tâches sont terminées. L'import des données a échoué en raison d'un timeout de connexion. Voulez-vous que je réessaye, ou que je saute cette tâche ? | |
| 358 | -``` | |
| 359 | - | |
| 360 | ---- | |
| 361 | - | |
| 362 | -## Configuration | |
| 363 | - | |
| 364 | -L'outil plan peut être activé ou désactivé via le fichier de configuration YAML : | |
| 365 | - | |
| 366 | -```yaml | |
| 367 | -# Dans votre fichier agent.yaml | |
| 368 | -planTool: true # Activé par défaut | |
| 369 | -``` | |
| 370 | - | |
| 371 | -Pour désactiver : | |
| 372 | -```yaml | |
| 373 | -planTool: false | |
| 374 | -``` | |
| 375 | - | |
| 376 | -Lorsque désactivé, l'agent n'aura pas accès à l'outil `plan` et ne créera pas automatiquement de plans. | |
| 377 | - | |
| 378 | ---- | |
| 379 | - | |
| 380 | -## Résolution des problèmes | |
| 381 | - | |
| 382 | -### L'agent ne crée pas de plans | |
| 383 | - | |
| 384 | -Si l'agent ne crée pas de plans pour vos demandes multi-étapes : | |
| 385 | - | |
| 386 | -1. **Rendez votre demande plus claire** - Utilisez des listes numérotées | |
| 387 | - - ❌ "Corrige quelques choses" | |
| 388 | - - ✅ "Fais ceci : 1. Corrige A, 2. Corrige B, 3. Corrige C" | |
| 389 | - | |
| 390 | -2. **Soyez plus explicite** - Utilisez des mots comme "plan", "étapes", "puis" | |
| 391 | - - ✅ "Mon plan est de d'abord faire X, puis faire Y" | |
| 392 | - | |
| 393 | -3. **Vérifiez que la compétence est chargée** - L'agent devrait automatiquement charger la compétence `plan-manager` lorsqu'il détecte une demande de type plan | |
| 394 | - | |
| 395 | -### L'agent appelle `plan update` en boucle | |
| 396 | - | |
| 397 | -`update` change l'état d'une tâche ; il ne peut pas modifier les tâches d'un plan. Un modèle qui envoie un `update` sans `status` valide — souvent avec une liste `tasks`, pour essayer de modifier le plan — reçoit une réponse qui cite son appel, nomme le champ manquant, précise que `tasks` n'est lu que par `create`, et donne un appel valide : | |
| 398 | - | |
| 399 | -``` | |
| 400 | -[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}] | |
| 401 | -``` | |
| 402 | - | |
| 403 | -Si un petit modèle répète quand même l'appel, mettez `planTool: false` pour un agent dont le métier n'a pas besoin de plans (écrire un document, répondre à des questions) : le modèle travaille alors sans. | |
| 404 | - | |
| 405 | -### Les plans disparaissent | |
| 406 | - | |
| 407 | -En v1, les plans sont stockés en mémoire et seront perdus lorsque : | |
| 408 | -- Vous démarrez une nouvelle session avec `/new` | |
| 409 | -- L'agent redémarre | |
| 410 | -- Vous fermez et rouvrez le terminal | |
| 411 | - | |
| 412 | -C'est un comportement attendu en v1. La persistance des sessions arrive en v2. | |
| 413 | - | |
| 414 | ---- | |
| 19 | +- **Mode ligne** (`mm agent.yaml`, `mm -p`) : une ligne avant chaque appel, une ligne après. Mesuré sur Qwen3.5 4B avec la demande ci-dessus : | |
| 415 | 20 | |
| 416 | -## Bonnes pratiques | |
| 21 | + ```text | |
| 22 | + 📋 plan: create | |
| 23 | + 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open) | |
| 24 | + 💾 write_file: a.txt (1 bytes) | |
| 25 | + 💾 write_file: b.txt (1 bytes) | |
| 26 | + 📋 plan: update user-request-1 | |
| 27 | + 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open) | |
| 28 | + 📋 plan: update user-request-1 | |
| 29 | + 📋 [2/2] All tasks complete! | |
| 30 | + ``` | |
| 417 | 31 | |
| 418 | -1. **Soyez spécifique** - Plus il y a de détails dans vos descriptions de tâches, mieux l'agent peut les exécuter | |
| 32 | +- **Fenêtre** (`mm -tui`) : chaque appel est un appel d'outil dans la conversation, et la ligne `📋 [C/N] …` le suit en notice. | |
| 33 | +- **Éditeur** (`mm -acp`) : chaque appel est un appel d'outil ; la ligne de statut est dans son résultat. | |
| 419 | 34 | |
| 420 | -2. **Gardez les plans concentrés** - Un plan par demande logique. Ne mélangez pas des tâches sans rapport | |
| 35 | +La ligne de statut montre la tâche en cours de travail — la première en cours, sinon la première encore ouverte : `[C/N] <icône> Task C: <description> (<statut>)`. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. Quand toutes les tâches sont terminées, elle indique `[N/N] All tasks complete!`, ou `[N/N] Plan finished: D done, F failed` si certaines ont échoué. | |
| 421 | 36 | |
| 422 | -3. **Vérifiez la progression** - La ligne d'état vous montre exactement où se trouve l'agent | |
| 37 | +## Orienter le plan | |
| 423 | 38 | |
| 424 | -4. **Demandez des clarifications** - Si l'agent semble bloqué ou confus, demandez "Quel est l'état ?" | |
| 39 | +C'est le modèle qui décide quand appeler l'outil ; vous l'orientez avec vos propres mots : | |
| 425 | 40 | |
| 426 | -5. **Commencez petit** - Pour un travail multi-étapes complexe, commencez par quelques tâches et ajoutez-en davantage si nécessaire | |
| 41 | +- **Où en est-on ?** « Où en est le plan ? » — le modèle appelle `status` et mm affiche la ligne. | |
| 42 | +- **Sauter une tâche.** « Saute les tests et continue. » — le modèle peut la marquer et passer à la suivante avec `next`. | |
| 43 | +- **Une tâche a échoué.** Le modèle peut la marquer `failed`, avec une note ; la ligne de statut passe alors à la tâche ouverte suivante. | |
| 44 | +- **Abandonner le plan.** « Oublie ce plan. » — le modèle peut appeler `delete`. | |
| 427 | 45 | |
| 428 | ---- | |
| 46 | +Il n'y a pas de commande slash pour les plans : c'est l'outil du modèle. | |
| 429 | 47 | |
| 430 | -## Comparaison avec d'autres outils | |
| 48 | +## Variantes | |
| 431 | 49 | |
| 432 | -| Fonctionnalité | outil plan | compétences | commandes slash | | |
| 433 | -|---------------|------------|-------------|-----------------| | |
| 434 | -| Objectif | Suivre le travail multi-étapes | Charger des procédures | Commandes utilisateur | | |
| 435 | -| Déclenchement | Automatique (modèle détecte) | Explicite (`/skill`) | Explicite (`/command`) | | |
| 436 | -| Persistance (v1) | En mémoire, session uniquement | Basé sur des fichiers | N/A | | |
| 437 | -| Interaction utilisateur | Indirecte (via le modèle) | Indirecte (modèle charge) | Directe | | |
| 50 | +- **Plusieurs conversations dans un éditeur.** Chaque fil a ses propres plans : un plan créé dans un fil de Zed n'est pas visible dans un autre. | |
| 51 | +- **Repartir de zéro.** `/new` et `/resume` oublient les plans dans toutes les façades ; un rechargement depuis l'éditeur (`session/load`, `session/resume`) démarre le fil sans plan. Les plans ne sont gardés qu'en mémoire, jamais dans le fichier de session, et sont perdus à l'arrêt de mm. | |
| 52 | +- **Le skill `plan-manager`.** `skills/plan-manager` dit au modèle quand faire un plan et comment le parcourir. Mettez-le dans le `skillsDir` de l'agent pour un modèle qui n'utilise pas l'outil de lui-même. Il demande aussi au modèle de reprendre la ligne de statut dans sa réponse ; mm l'affiche de toute façon. | |
| 53 | +- **Un agent qui n'a pas besoin de plans.** Mettez `planTool: false` : l'outil n'est pas déclaré. Faites-le pour un agent dont le travail est une réponse unique (écrire un document, répondre à des questions), où un petit modèle pourrait sinon boucler sur des appels au plan. | |
| 438 | 54 | |
| 439 | -L'outil `plan` complète les autres fonctionnalités de mini-me : | |
| 440 | -- Utilisez les **compétences** pour des procédures complexes et réutilisables | |
| 441 | -- Utilisez l'**outil plan** pour des demandes multi-étapes ad-hoc | |
| 442 | -- Utilisez les **commandes slash** pour des actions utilisateur directes | |
| 55 | +## Dépannage | |
| 443 | 56 | |
| 444 | ---- | |
| 57 | +- **L'agent ne crée pas de plan.** Donnez une liste numérotée, et dites « use the plan tool ». Avec un petit modèle, ajoutez le skill `plan-manager`. | |
| 58 | +- **L'agent appelle `plan update` en boucle.** `update` change le statut d'UNE tâche ; il ne peut pas changer les tâches d'un plan. Un appel sans `status` valide reçoit une réponse qui reprend l'appel, nomme le champ manquant et donne un appel valide. Si un petit modèle le répète quand même, mettez `planTool: false` pour cet agent. | |
| 445 | 59 | |
| 446 | 60 | ## Voir aussi |
| 447 | 61 | |
| 448 | -- [Référence des commandes slash](../reference/slash-commands.md) - Pour les commandes utilisateur | |
| 449 | -- [Référence des outils](../reference/tools.md) - Pour tous les outils intégrés | |
| 450 | -- [Aperçu des compétences](../explanation/skills.md) - Pour le système de compétences | |
| 62 | +- [Référence des outils — `plan`](../reference/tools.md#plan) : chaque action, champ, résultat et erreur | |
| 63 | +- [Référence de la configuration](../reference/configuration.md) : `planTool` | |
| 64 | +- [Référence des commandes](../reference/slash-commands.md) : `/new`, `/resume` | |
| 65 | +- `demos/plans/` — une démo mesurée : un programme Go construit en quatre étapes planifiées, et un plan qui se termine sur une tâche en échec | |
| @@ -1,450 +1,65 @@ | |||
| 1 | ---- | 1 | +# Gérer des plans et des tâches |
| 2 | -title: Gérer les plans et les tâches | ||
| 3 | ---- | ||
| 4 | 2 | ||
| 5 | -# Gérer les plans et les tâches | 3 | +Ce guide montre comment faire travailler l'agent sur une liste de tâches avec son outil `plan`, et comment suivre sa progression. L'outil est activé par défaut (`planTool: true`). |
| 6 | 4 | ||
| 7 | -Mini-me inclut un outil intégré **plan** qui permet à l'agent de créer, suivre et gérer des listes de tâches multi-étapes. Lorsque vous donnez à l'agent une liste de tâches à effectuer, il crée automatiquement un plan et suit la progression, en vous montrant l'état actuel après chaque réponse. | 5 | +## Donner une liste à l'agent |
| 8 | 6 | ||
| 9 | -## Exemple rapide | 7 | +Demandez plusieurs choses à la fois, sous forme de liste : |
| 10 | 8 | ||
| 9 | +```text | ||
| 10 | +Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B. | ||
| 11 | ``` | 11 | ``` |
| 12 | -Vous : Fais ces 3 choses : 1. Créer un fichier README, 2. Ajouter une licence, 3. Valider les modifications | ||
| 13 | 12 | ||
| 14 | -Agent : Je vais m'en occuper en 3 étapes. En commençant par : Créer un fichier README | 13 | +Le modèle crée un plan à partir de la liste, le suit, et met à jour le statut de chaque tâche au fur et à mesure. Les petits modèles le font plus sûrement quand on le leur dit : « Use the plan tool. Do these… ». |
| 15 | -📋 plan: create user-request-1 | ||
| 16 | 14 | ||
| 17 | -Agent : Fichier README créé. | 15 | +## Suivre la progression |
| 18 | -📋 [2/3] 🔄 Tâche 2 : Ajouter une licence (in_progress) | ||
| 19 | 16 | ||
| 20 | -Agent : Licence ajoutée. | 17 | +mm affiche où en est le plan après chaque action qui change ou lit son état (`create`, `update`, `next`, `status`) — que le modèle en parle ou non dans sa réponse. |
| 21 | -📋 [3/3] 🔄 Tâche 3 : Valider les modifications (in_progress) | ||
| 22 | 18 | ||
| 23 | -Agent : Toutes les modifications ont été validées. | 19 | +- **Mode ligne** (`mm agent.yaml`, `mm -p`) : une ligne avant chaque appel, une ligne après. Mesuré sur Qwen3.5 4B avec la demande ci-dessus : |
| 24 | -📋 [3/3] ✅ Toutes les tâches sont terminées ! | ||
| 25 | -``` | ||
| 26 | - | ||
| 27 | -La ligne `📋` vous montre exactement où se trouve l'agent dans votre plan. | ||
| 28 | - | ||
| 29 | ---- | ||
| 30 | - | ||
| 31 | -## Fonctionnement | ||
| 32 | - | ||
| 33 | -### Cycle de vie d'un plan | ||
| 34 | - | ||
| 35 | -1. **Vous formulez votre demande** sous forme de liste de tâches | ||
| 36 | -2. **L'agent détecte le plan** et le crée en utilisant l'outil `plan` | ||
| 37 | -3. **L'agent travaille sur les tâches** une par une, en mettant à jour l'état | ||
| 38 | -4. **L'agent signale la progression** après chaque action | ||
| 39 | -5. **Le plan est terminé** lorsque toutes les tâches sont terminées | ||
| 40 | - | ||
| 41 | -### Icônes d'état | ||
| 42 | - | ||
| 43 | -| Icône | Signification | | ||
| 44 | -|-------|--------------| | ||
| 45 | -| ⏳ | Tâche non démarrée (ouverte) | | ||
| 46 | -| 🔄 | Tâche en cours | | ||
| 47 | -| ✅ | Tâche terminée avec succès | | ||
| 48 | -| ❌ | Tâche en échec | | ||
| 49 | - | ||
| 50 | -### Format de la ligne d'état | ||
| 51 | - | ||
| 52 | -``` | ||
| 53 | -📋 [courante/total] icône Tâche N : description (état) | ||
| 54 | -``` | ||
| 55 | - | ||
| 56 | -Exemples : | ||
| 57 | -- `📋 [1/5] ⏳ Tâche 1 : Analyser le code (open)` - Première tâche, non démarrée | ||
| 58 | -- `📋 [2/5] 🔄 Tâche 2 : Corriger les bugs (in_progress)` - Deuxième tâche, en cours | ||
| 59 | -- `📋 [5/5] ✅ Toutes les tâches sont terminées !` - Tout est terminé ! | ||
| 60 | -- `📋 [3/5] ❌ Tâche 3 : Exécuter les tests (failed: timeout de connexion)` - Tâche en échec avec erreur | ||
| 61 | - | ||
| 62 | ---- | ||
| 63 | - | ||
| 64 | -## Scénarios utilisateur | ||
| 65 | - | ||
| 66 | -### Scénario 1 : Liste numérotée | ||
| 67 | - | ||
| 68 | -**Votre entrée :** | ||
| 69 | -``` | ||
| 70 | -Fais ces 5 choses : | ||
| 71 | -1. Vérifier la version actuelle | ||
| 72 | -2. Mettre à jour les dépendances | ||
| 73 | -3. Exécuter les tests | ||
| 74 | -4. Corriger les échecs | ||
| 75 | -5. Déployer en staging | ||
| 76 | -``` | ||
| 77 | - | ||
| 78 | -**Ce qui se passe :** | ||
| 79 | -- L'agent crée un plan avec 5 tâches | ||
| 80 | -- Travaille sur chaque tâche séquentiellement | ||
| 81 | -- Affiche la progression : `📋 [1/5]`, `📋 [2/5]`, etc. | ||
| 82 | -- Termine avec : `📋 [5/5] ✅ Toutes les tâches sont terminées !` | ||
| 83 | - | ||
| 84 | ---- | ||
| 85 | - | ||
| 86 | -### Scénario 2 : Tâches séparées par des virgules | ||
| 87 | - | ||
| 88 | -**Votre entrée :** | ||
| 89 | -``` | ||
| 90 | -Configurer le projet : créer le répertoire, initialiser git, et ajouter un README | ||
| 91 | -``` | ||
| 92 | - | ||
| 93 | -**Ce qui se passe :** | ||
| 94 | -- L'agent détecte qu'il s'agit d'une demande multi-étapes | ||
| 95 | -- Crée un plan avec 3 tâches | ||
| 96 | -- Exécute dans l'ordre | ||
| 97 | - | ||
| 98 | ---- | ||
| 99 | - | ||
| 100 | -### Scénario 3 : Plusieurs plans | ||
| 101 | - | ||
| 102 | -Vous pouvez avoir plusieurs plans actifs dans une seule session. | ||
| 103 | - | ||
| 104 | -**Votre entrée :** | ||
| 105 | -``` | ||
| 106 | -D'abord, corrige le module d'authentification : 1. Trouver le bug, 2. Écrire la correction, 3. Le tester | ||
| 107 | -``` | ||
| 108 | - | ||
| 109 | -L'agent travaille sur le plan d'authentification, affiche : `📋 [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress)` | ||
| 110 | - | ||
| 111 | -**Ensuite vous dites :** | ||
| 112 | -``` | ||
| 113 | -Aussi, mettre à jour la documentation : 1. Mettre à jour le README, 2. Mettre à jour le CHANGELOG | ||
| 114 | -``` | ||
| 115 | - | ||
| 116 | -L'agent crée un deuxième plan et gère les deux indépendamment. | ||
| 117 | - | ||
| 118 | -**Pour voir tous les plans actifs :** | ||
| 119 | -``` | ||
| 120 | -Quels plans ai-je ? | ||
| 121 | -``` | ||
| 122 | - | ||
| 123 | ---- | ||
| 124 | - | ||
| 125 | -## Interagir avec les plans | ||
| 126 | - | ||
| 127 | -### Vérifier l'état actuel du plan | ||
| 128 | - | ||
| 129 | -**Demandez :** "Quel est l'état de mon plan ?" | ||
| 130 | - | ||
| 131 | -**L'agent répond :** | ||
| 132 | -``` | ||
| 133 | -Plan actuel : [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress). Suivant : Le tester | ||
| 134 | -``` | ||
| 135 | - | ||
| 136 | -### Sauter une tâche | ||
| 137 | - | ||
| 138 | -**Demandez :** "Saute l'étape de test, déploie directement" | ||
| 139 | - | ||
| 140 | -**Agent :** | ||
| 141 | -- Marque la tâche actuelle comme terminée | ||
| 142 | -- Saute à la tâche suivante | ||
| 143 | -- Confirme : "Je saute l'étape de test. Déploiement en staging..." | ||
| 144 | - | ||
| 145 | -### Abandonner un plan | ||
| 146 | - | ||
| 147 | -**Demandez :** "Peu importe, je n'en ai plus besoin" | ||
| 148 | - | ||
| 149 | -**Agent :** | ||
| 150 | -- Demande confirmation | ||
| 151 | -- Si vous confirmez, supprime le plan | ||
| 152 | -- Toute la progression est perdue | ||
| 153 | - | ||
| 154 | ---- | ||
| 155 | - | ||
| 156 | -## Commandes de gestion des plans | ||
| 157 | - | ||
| 158 | -Bien que l'outil plan soit principalement piloté par le modèle (l'agent l'utilise automatiquement), vous pouvez également interagir directement avec les plans : | ||
| 159 | - | ||
| 160 | -### Lister tous les plans | ||
| 161 | - | ||
| 162 | -**Commande :** `/plans` (pas encore implémenté en v1 - arrive en v2) | ||
| 163 | - | ||
| 164 | -**Ce que cela affiche :** | ||
| 165 | -``` | ||
| 166 | -Plans actifs : | ||
| 167 | -1. configuration-projet (2/5 tâches terminées) | ||
| 168 | -2. corriger-bugs (0/3 tâches terminées) | ||
| 169 | -``` | ||
| 170 | - | ||
| 171 | -### Voir les détails d'un plan | ||
| 172 | - | ||
| 173 | -**Demandez à l'agent :** "Montre-moi les détails du plan X" | ||
| 174 | - | ||
| 175 | -**L'agent répond :** Liste complète des tâches avec l'état pour chaque tâche | ||
| 176 | - | ||
| 177 | -### Supprimer un plan | ||
| 178 | - | ||
| 179 | -**Demandez à l'agent :** "Supprime le plan X" | ||
| 180 | - | ||
| 181 | -**Agent :** Confirme et supprime le plan | ||
| 182 | - | ||
| 183 | ---- | ||
| 184 | - | ||
| 185 | -## Cycle de vie de la session | ||
| 186 | - | ||
| 187 | -### Plans et commande /new | ||
| 188 | - | ||
| 189 | -Lorsque vous démarrez une nouvelle session avec `/new` : | ||
| 190 | -- Tous les plans actifs sont **supprimés** | ||
| 191 | -- C'est intentionnel : une nouvelle session signifie un nouveau départ | ||
| 192 | -- Si vous voulez continuer un ancien plan, ne pas utiliser `/new` | ||
| 193 | - | ||
| 194 | -### Plans et redémarrage de l'agent | ||
| 195 | - | ||
| 196 | -En v1 (version actuelle) : | ||
| 197 | -- Les plans sont stockés **uniquement en mémoire** | ||
| 198 | -- Les plans sont **perdus** lorsque l'agent redémarre | ||
| 199 | -- Cela garde la v1 simple et rapide | ||
| 200 | - | ||
| 201 | -En v2 (future) : | ||
| 202 | -- Les plans seront persistés dans les fichiers de session | ||
| 203 | -- Les plans survivront aux redémarrages de l'agent | ||
| 204 | - | ||
| 205 | ---- | ||
| 206 | - | ||
| 207 | -## L'outil plan (pour les utilisateurs avancés) | ||
| 208 | - | ||
| 209 | -L'outil `plan` est disponible pour que le modèle puisse l'appeler. Il prend en charge ces actions : | ||
| 210 | - | ||
| 211 | -| Action | Objectif | Paramètres requis | | ||
| 212 | -|--------|----------|-------------------| | ||
| 213 | -| `create` | Créer un nouveau plan | `tasks` (tableau de `{description}`) | | ||
| 214 | -| `get` | Obtenir les détails d'un plan | `name` | | ||
| 215 | -| `update` | Mettre à jour l'état d'une tâche | `name`, `taskId`, `status` | | ||
| 216 | -| `next` | Passer à la tâche suivante | `name` | | ||
| 217 | -| `list` | Lister tous les plans | aucun | | ||
| 218 | -| `delete` | Supprimer un plan | `name` | | ||
| 219 | -| `status` | Obtenir la ligne d'état | `name` | | ||
| 220 | - | ||
| 221 | -### Valeurs d'état | ||
| 222 | - | ||
| 223 | -- `open` - Tâche non démarrée | ||
| 224 | -- `in_progress` - Tâche actuellement en cours | ||
| 225 | -- `done` - Tâche terminée avec succès | ||
| 226 | -- `failed` - Tâche en échec | ||
| 227 | - | ||
| 228 | ---- | ||
| 229 | - | ||
| 230 | -## La compétence plan-manager | ||
| 231 | - | ||
| 232 | -L'agent utilise la compétence `plan-manager` pour savoir **quand et comment** utiliser l'outil plan. Cette compétence inclut : | ||
| 233 | - | ||
| 234 | -- **Modèles de détection** : Comment reconnaître quand un utilisateur donne une demande multi-étapes | ||
| 235 | -- **Directives d'utilisation** : Quand créer des plans, mettre à jour des tâches, passer entre les tâches | ||
| 236 | -- **Format d'affichage** : Comment montrer l'état du plan à l'utilisateur | ||
| 237 | -- **Gestion des erreurs** : Que faire quand les choses tournent mal | ||
| 238 | - | ||
| 239 | -Vous n'avez pas besoin de charger manuellement cette compétence - l'agent le fait automatiquement lorsque cela est approprié. | ||
| 240 | - | ||
| 241 | ---- | ||
| 242 | - | ||
| 243 | -## Exemples | ||
| 244 | - | ||
| 245 | -### Exemple 1 : Workflow de correction de bug | ||
| 246 | - | ||
| 247 | -``` | ||
| 248 | -Vous : Corrige ces problèmes dans le module d'authentification : | ||
| 249 | - 1. Vérifier les exceptions de pointeur nul | ||
| 250 | - 2. Ajouter la validation des entrées | ||
| 251 | - 3. Écrire des tests unitaires | ||
| 252 | - 4. Mettre à jour la documentation | ||
| 253 | - | ||
| 254 | -Agent : Je vais m'en occuper en 4 étapes. En commençant par : Vérifier les exceptions de pointeur nul | ||
| 255 | -📋 plan: create user-request-1 | ||
| 256 | - | ||
| 257 | -... (l'agent vérifie le code) ... | ||
| 258 | - | ||
| 259 | -Agent : 3 problèmes de pointeur nul potentiels trouvés. | ||
| 260 | -📋 [2/4] 🔄 Tâche 2 : Ajouter la validation des entrées (in_progress) | ||
| 261 | - | ||
| 262 | -... (l'agent ajoute la validation) ... | ||
| 263 | - | ||
| 264 | -Agent : Validation des entrées ajoutée à auth.go. | ||
| 265 | -📋 [3/4] 🔄 Tâche 3 : Écrire des tests unitaires (in_progress) | ||
| 266 | - | ||
| 267 | -... (l'agent écrit les tests) ... | ||
| 268 | - | ||
| 269 | -Agent : Tests écrits et réussis. | ||
| 270 | -📋 [4/4] 🔄 Tâche 4 : Mettre à jour la documentation (in_progress) | ||
| 271 | - | ||
| 272 | -... (l'agent met à jour les docs) ... | ||
| 273 | - | ||
| 274 | -Agent : Documentation mise à jour avec les nouvelles exigences de validation. | ||
| 275 | -📋 [4/4] ✅ Toutes les tâches sont terminées ! | ||
| 276 | -``` | ||
| 277 | - | ||
| 278 | ---- | ||
| 279 | - | ||
| 280 | -### Exemple 2 : Configuration d'un projet | ||
| 281 | - | ||
| 282 | -``` | ||
| 283 | -Vous : Configure un nouveau projet Go avec : | ||
| 284 | - 1. Initialisation du module | ||
| 285 | - 2. Structure des répertoires | ||
| 286 | - 3. Fichier main | ||
| 287 | - 4. Fichier de test | ||
| 288 | - | ||
| 289 | -Agent : Je vais configurer cela en 4 étapes. En commençant par : Initialisation du module | ||
| 290 | -📋 plan: create user-request-1 | ||
| 291 | - | ||
| 292 | -Agent : Module initialisé avec go mod | ||
| 293 | -📋 [2/4] 🔄 Tâche 2 : Structure des répertoires (in_progress) | ||
| 294 | - | ||
| 295 | -Agent : Répertoires créés (cmd, internal, pkg, tests) | ||
| 296 | -📋 [3/4] 🔄 Tâche 3 : Fichier main (in_progress) | ||
| 297 | - | ||
| 298 | -Agent : Fichier main créé avec la structure de base | ||
| 299 | -📋 [4/4] 🔄 Tâche 4 : Fichier de test (in_progress) | ||
| 300 | - | ||
| 301 | -Agent : Fichier de test créé avec des tests exemples | ||
| 302 | -📋 [4/4] ✅ Toutes les tâches sont terminées ! | ||
| 303 | -``` | ||
| 304 | - | ||
| 305 | ---- | ||
| 306 | - | ||
| 307 | -### Exemple 3 : Revue de code | ||
| 308 | - | ||
| 309 | -``` | ||
| 310 | -Vous : Revoir cette PR et : | ||
| 311 | - 1. Vérifier les problèmes de sécurité | ||
| 312 | - 2. Vérifier que les tests passent | ||
| 313 | - 3. Revoir la documentation | ||
| 314 | - 4. Laisser des commentaires | ||
| 315 | - | ||
| 316 | -Agent : Je vais revoir cette PR en 4 étapes. En commençant par : Vérifier les problèmes de sécurité | ||
| 317 | -📋 plan: create user-request-1 | ||
| 318 | - | ||
| 319 | -Agent : Aucun problème de sécurité trouvé. | ||
| 320 | -📋 [2/4] 🔄 Tâche 2 : Vérifier que les tests passent (in_progress) | ||
| 321 | - | ||
| 322 | -Agent : Tous les tests passent. | ||
| 323 | -📋 [3/4] 🔄 Tâche 3 : Revoir la documentation (in_progress) | ||
| 324 | - | ||
| 325 | -Agent : La documentation a l'air bonne, il faut juste corriger une faute de frappe. | ||
| 326 | -📋 [4/4] 🔄 Tâche 4 : Laisser des commentaires (in_progress) | ||
| 327 | - | ||
| 328 | -Agent : Commentaires laissés sur la PR. | ||
| 329 | -📋 [4/4] ✅ Toutes les tâches sont terminées ! | ||
| 330 | -``` | ||
| 331 | - | ||
| 332 | ---- | ||
| 333 | - | ||
| 334 | -### Exemple 4 : Avec un échec de tâche | ||
| 335 | - | ||
| 336 | -``` | ||
| 337 | -Vous : Fais ceci : | ||
| 338 | - 1. Se connecter à la base de données | ||
| 339 | - 2. Exécuter la migration | ||
| 340 | - 3. Importer les données | ||
| 341 | - | ||
| 342 | -Agent : Je vais m'en occuper en 3 étapes. En commençant par : Se connecter à la base de données | ||
| 343 | -📋 plan: create user-request-1 | ||
| 344 | - | ||
| 345 | -Agent : Connexion à la base de données établie. | ||
| 346 | -📋 [2/3] 🔄 Tâche 2 : Exécuter la migration (in_progress) | ||
| 347 | - | ||
| 348 | -Agent : Migration terminée. | ||
| 349 | -📋 [3/3] 🔄 Tâche 3 : Importer les données (in_progress) | ||
| 350 | - | ||
| 351 | -Agent : Erreur : timeout de connexion à la source de données | ||
| 352 | -📋 plan: update user-request-1 | ||
| 353 | - | ||
| 354 | -Agent : ⚠️ La tâche 3 a échoué : timeout de connexion à la source de données | ||
| 355 | -📋 [3/3] ❌ Tâche 3 : Importer les données (failed: connection timeout) | ||
| 356 | - | ||
| 357 | -Agent : 2 des 3 tâches sont terminées. L'import des données a échoué en raison d'un timeout de connexion. Voulez-vous que je réessaye, ou que je saute cette tâche ? | ||
| 358 | -``` | ||
| 359 | - | ||
| 360 | ---- | ||
| 361 | - | ||
| 362 | -## Configuration | ||
| 363 | - | ||
| 364 | -L'outil plan peut être activé ou désactivé via le fichier de configuration YAML : | ||
| 365 | - | ||
| 366 | -```yaml | ||
| 367 | -# Dans votre fichier agent.yaml | ||
| 368 | -planTool: true # Activé par défaut | ||
| 369 | -``` | ||
| 370 | - | ||
| 371 | -Pour désactiver : | ||
| 372 | -```yaml | ||
| 373 | -planTool: false | ||
| 374 | -``` | ||
| 375 | - | ||
| 376 | -Lorsque désactivé, l'agent n'aura pas accès à l'outil `plan` et ne créera pas automatiquement de plans. | ||
| 377 | - | ||
| 378 | ---- | ||
| 379 | - | ||
| 380 | -## Résolution des problèmes | ||
| 381 | - | ||
| 382 | -### L'agent ne crée pas de plans | ||
| 383 | - | ||
| 384 | -Si l'agent ne crée pas de plans pour vos demandes multi-étapes : | ||
| 385 | - | ||
| 386 | -1. **Rendez votre demande plus claire** - Utilisez des listes numérotées | ||
| 387 | - - ❌ "Corrige quelques choses" | ||
| 388 | - - ✅ "Fais ceci : 1. Corrige A, 2. Corrige B, 3. Corrige C" | ||
| 389 | - | ||
| 390 | -2. **Soyez plus explicite** - Utilisez des mots comme "plan", "étapes", "puis" | ||
| 391 | - - ✅ "Mon plan est de d'abord faire X, puis faire Y" | ||
| 392 | - | ||
| 393 | -3. **Vérifiez que la compétence est chargée** - L'agent devrait automatiquement charger la compétence `plan-manager` lorsqu'il détecte une demande de type plan | ||
| 394 | - | ||
| 395 | -### L'agent appelle `plan update` en boucle | ||
| 396 | - | ||
| 397 | -`update` change l'état d'une tâche ; il ne peut pas modifier les tâches d'un plan. Un modèle qui envoie un `update` sans `status` valide — souvent avec une liste `tasks`, pour essayer de modifier le plan — reçoit une réponse qui cite son appel, nomme le champ manquant, précise que `tasks` n'est lu que par `create`, et donne un appel valide : | ||
| 398 | - | ||
| 399 | -``` | ||
| 400 | -[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}] | ||
| 401 | -``` | ||
| 402 | - | ||
| 403 | -Si un petit modèle répète quand même l'appel, mettez `planTool: false` pour un agent dont le métier n'a pas besoin de plans (écrire un document, répondre à des questions) : le modèle travaille alors sans. | ||
| 404 | - | ||
| 405 | -### Les plans disparaissent | ||
| 406 | - | ||
| 407 | -En v1, les plans sont stockés en mémoire et seront perdus lorsque : | ||
| 408 | -- Vous démarrez une nouvelle session avec `/new` | ||
| 409 | -- L'agent redémarre | ||
| 410 | -- Vous fermez et rouvrez le terminal | ||
| 411 | - | ||
| 412 | -C'est un comportement attendu en v1. La persistance des sessions arrive en v2. | ||
| 413 | - | ||
| 414 | ---- | ||
| 415 | 20 | ||
| 416 | -## Bonnes pratiques | 21 | + ```text |
| 22 | + 📋 plan: create | ||
| 23 | + 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open) | ||
| 24 | + 💾 write_file: a.txt (1 bytes) | ||
| 25 | + 💾 write_file: b.txt (1 bytes) | ||
| 26 | + 📋 plan: update user-request-1 | ||
| 27 | + 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open) | ||
| 28 | + 📋 plan: update user-request-1 | ||
| 29 | + 📋 [2/2] All tasks complete! | ||
| 30 | + ``` | ||
| 417 | 31 | ||
| 418 | -1. **Soyez spécifique** - Plus il y a de détails dans vos descriptions de tâches, mieux l'agent peut les exécuter | 32 | +- **Fenêtre** (`mm -tui`) : chaque appel est un appel d'outil dans la conversation, et la ligne `📋 [C/N] …` le suit en notice. |
| 33 | +- **Éditeur** (`mm -acp`) : chaque appel est un appel d'outil ; la ligne de statut est dans son résultat. | ||
| 419 | 34 | ||
| 420 | -2. **Gardez les plans concentrés** - Un plan par demande logique. Ne mélangez pas des tâches sans rapport | 35 | +La ligne de statut montre la tâche en cours de travail — la première en cours, sinon la première encore ouverte : `[C/N] <icône> Task C: <description> (<statut>)`. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. Quand toutes les tâches sont terminées, elle indique `[N/N] All tasks complete!`, ou `[N/N] Plan finished: D done, F failed` si certaines ont échoué. |
| 421 | 36 | ||
| 422 | -3. **Vérifiez la progression** - La ligne d'état vous montre exactement où se trouve l'agent | 37 | +## Orienter le plan |
| 423 | 38 | ||
| 424 | -4. **Demandez des clarifications** - Si l'agent semble bloqué ou confus, demandez "Quel est l'état ?" | 39 | +C'est le modèle qui décide quand appeler l'outil ; vous l'orientez avec vos propres mots : |
| 425 | 40 | ||
| 426 | -5. **Commencez petit** - Pour un travail multi-étapes complexe, commencez par quelques tâches et ajoutez-en davantage si nécessaire | 41 | +- **Où en est-on ?** « Où en est le plan ? » — le modèle appelle `status` et mm affiche la ligne. |
| 42 | +- **Sauter une tâche.** « Saute les tests et continue. » — le modèle peut la marquer et passer à la suivante avec `next`. | ||
| 43 | +- **Une tâche a échoué.** Le modèle peut la marquer `failed`, avec une note ; la ligne de statut passe alors à la tâche ouverte suivante. | ||
| 44 | +- **Abandonner le plan.** « Oublie ce plan. » — le modèle peut appeler `delete`. | ||
| 427 | 45 | ||
| 428 | ---- | 46 | +Il n'y a pas de commande slash pour les plans : c'est l'outil du modèle. |
| 429 | 47 | ||
| 430 | -## Comparaison avec d'autres outils | 48 | +## Variantes |
| 431 | 49 | ||
| 432 | -| Fonctionnalité | outil plan | compétences | commandes slash | | 50 | +- **Plusieurs conversations dans un éditeur.** Chaque fil a ses propres plans : un plan créé dans un fil de Zed n'est pas visible dans un autre. |
| 433 | -|---------------|------------|-------------|-----------------| | 51 | +- **Repartir de zéro.** `/new` et `/resume` oublient les plans dans toutes les façades ; un rechargement depuis l'éditeur (`session/load`, `session/resume`) démarre le fil sans plan. Les plans ne sont gardés qu'en mémoire, jamais dans le fichier de session, et sont perdus à l'arrêt de mm. |
| 434 | -| Objectif | Suivre le travail multi-étapes | Charger des procédures | Commandes utilisateur | | 52 | +- **Le skill `plan-manager`.** `skills/plan-manager` dit au modèle quand faire un plan et comment le parcourir. Mettez-le dans le `skillsDir` de l'agent pour un modèle qui n'utilise pas l'outil de lui-même. Il demande aussi au modèle de reprendre la ligne de statut dans sa réponse ; mm l'affiche de toute façon. |
| 435 | -| Déclenchement | Automatique (modèle détecte) | Explicite (`/skill`) | Explicite (`/command`) | | 53 | +- **Un agent qui n'a pas besoin de plans.** Mettez `planTool: false` : l'outil n'est pas déclaré. Faites-le pour un agent dont le travail est une réponse unique (écrire un document, répondre à des questions), où un petit modèle pourrait sinon boucler sur des appels au plan. |
| 436 | -| Persistance (v1) | En mémoire, session uniquement | Basé sur des fichiers | N/A | | ||
| 437 | -| Interaction utilisateur | Indirecte (via le modèle) | Indirecte (modèle charge) | Directe | | ||
| 438 | 54 | ||
| 439 | -L'outil `plan` complète les autres fonctionnalités de mini-me : | 55 | +## Dépannage |
| 440 | -- Utilisez les **compétences** pour des procédures complexes et réutilisables | ||
| 441 | -- Utilisez l'**outil plan** pour des demandes multi-étapes ad-hoc | ||
| 442 | -- Utilisez les **commandes slash** pour des actions utilisateur directes | ||
| 443 | 56 | ||
| 444 | ---- | 57 | +- **L'agent ne crée pas de plan.** Donnez une liste numérotée, et dites « use the plan tool ». Avec un petit modèle, ajoutez le skill `plan-manager`. |
| 58 | +- **L'agent appelle `plan update` en boucle.** `update` change le statut d'UNE tâche ; il ne peut pas changer les tâches d'un plan. Un appel sans `status` valide reçoit une réponse qui reprend l'appel, nomme le champ manquant et donne un appel valide. Si un petit modèle le répète quand même, mettez `planTool: false` pour cet agent. | ||
| 445 | 59 | ||
| 446 | ## Voir aussi | 60 | ## Voir aussi |
| 447 | 61 | ||
| 448 | -- [Référence des commandes slash](../reference/slash-commands.md) - Pour les commandes utilisateur | 62 | +- [Référence des outils — `plan`](../reference/tools.md#plan) : chaque action, champ, résultat et erreur |
| 449 | -- [Référence des outils](../reference/tools.md) - Pour tous les outils intégrés | 63 | +- [Référence de la configuration](../reference/configuration.md) : `planTool` |
| 450 | -- [Aperçu des compétences](../explanation/skills.md) - Pour le système de compétences | 64 | +- [Référence des commandes](../reference/slash-commands.md) : `/new`, `/resume` |
| 65 | +- `demos/plans/` — une démo mesurée : un programme Go construit en quatre étapes planifiées, et un plan qui se termine sur une tâche en échec | ||
modified
docs/fr/reference/slash-commands.md +3 -3 | @@ -9,9 +9,9 @@ | ||
| 9 | 9 | | `/quit` | Quitte le programme. | Annule la génération, affiche `Goodbye.`, quitte. | |
| 10 | 10 | | `/abort` | Affiche `⚠️ No generation in progress.` | Annule la génération, affiche `🛑 Aborting...` ; les commandes déjà exécutées restent dans l'historique. | |
| 11 | 11 | | `/compact` | Compresse l'historique immédiatement, sans tenir compte du seuil ; affiche le rapport `🗜️`, ou `🗜️ nothing to compact` quand aucun tour n'est plus ancien que `context.keepLastTurns`. | Envoyée au modèle comme nouveau message (voir ci-dessous). | |
| 12 | -| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles et oublie le dernier compte de tokens du serveur ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. | | |
| 12 | +| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles, oublie le dernier compte de tokens du serveur et les plans de la conversation ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. | | |
| 13 | 13 | | `/sessions` | Liste les sessions enregistrées du projet, la plus récente d'abord : identifiant, dernière mise à jour, nombre de questions, première question, puis ` · ⚠️ N error(s)` quand la session a enregistré des erreurs ; `*` marque la session courante. Avec `sessions.enabled: false` : `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Envoyée au modèle comme nouveau message. | |
| 14 | -| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles et le compte de tokens du serveur ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. | | |
| 14 | +| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles, le compte de tokens du serveur et les plans ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. | | |
| 15 | 15 | | `/help` | Affiche les commandes de cette façade, une par ligne avec leur description, alignées. | Envoyée au modèle comme nouveau message. | |
| 16 | 16 | | `/save <fichier> [n]` | Écrit la dernière réponse (`n` : la n-ième depuis la fin) telle quelle dans un nouveau fichier, en créant les répertoires manquants, et affiche `💾 saved the last answer to <fichier> (N bytes)`. N'écrase jamais : un fichier existant donne `⚠️ <fichier> already exists — nothing was written; choose another name`. Un nom de fichier avec des espaces n'est pas pris en charge. Le modèle n'est pas appelé. | Envoyée au modèle comme nouveau message. | |
| 17 | 17 | | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory` : cherche dans les sessions enregistrées du projet et affiche `🔎 N hit(s) for "<mots>" in <rép>`, puis deux lignes par résultat — `<id de session> · <date> · turn <n> · question\|answer\|summary` et le texte autour des mots trouvés, plus `→ <réponse>` sous une question (la réponse sur laquelle son tour s'est terminé) — ou `🔎 no hit for "<mots>" in <rép>`. La casse et les accents ne comptent pas ; les mots d'une ou deux lettres sont ignorés à côté de mots plus longs ; un résultat contient au moins un des mots ; les résultats sont classés par nombre de mots contenus, puis du plus récent au plus ancien ; 10 au plus. Les questions, les réponses et les résumés de `/compact` sont cherchés, pas les appels d'outils ; la conversation en cours est exclue. Sans mots : `⚠️ nothing to search: give one or more words`. Le modèle n'est pas appelé ; l'historique n'est pas touché. | Envoyée au modèle comme nouveau message. | |
| @@ -176,7 +176,7 @@ Deux commandes existent en mode ACP, trois avec `unloadModels` (le défaut) : `/ | ||
| 176 | 176 | |
| 177 | 177 | | Commande | En ACP | |
| 178 | 178 | |----------|--------| |
| 179 | -| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens oublié ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. | | |
| 179 | +| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens et plans de cette session oubliés (les autres sessions gardent les leurs) ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. | | |
| 180 | 180 | | `/compact` | Comme dans le terminal, toujours forcée : `🗜️ compressed …`, `🗜️ nothing to compact: …` ou `[compact: failed, history kept: …]`. Attend d'abord la fin d'un tour en cours. | |
| 181 | 181 | | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, dans le projet de la session (`sessions.dir` sous son `cwd`), comme texte de l'agent ; l'historique n'est pas touché. | |
| 182 | 182 | | `/docs <mots>` | Avec `docSearch.searchDocs`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, depuis le `cwd` de la session, comme texte de l'agent ; l'historique n'est pas touché. | |
| @@ -9,9 +9,9 @@ | |||
| 9 | | `/quit` | Quitte le programme. | Annule la génération, affiche `Goodbye.`, quitte. | | 9 | | `/quit` | Quitte le programme. | Annule la génération, affiche `Goodbye.`, quitte. | |
| 10 | | `/abort` | Affiche `⚠️ No generation in progress.` | Annule la génération, affiche `🛑 Aborting...` ; les commandes déjà exécutées restent dans l'historique. | | 10 | | `/abort` | Affiche `⚠️ No generation in progress.` | Annule la génération, affiche `🛑 Aborting...` ; les commandes déjà exécutées restent dans l'historique. | |
| 11 | | `/compact` | Compresse l'historique immédiatement, sans tenir compte du seuil ; affiche le rapport `🗜️`, ou `🗜️ nothing to compact` quand aucun tour n'est plus ancien que `context.keepLastTurns`. | Envoyée au modèle comme nouveau message (voir ci-dessous). | | 11 | | `/compact` | Compresse l'historique immédiatement, sans tenir compte du seuil ; affiche le rapport `🗜️`, ou `🗜️ nothing to compact` quand aucun tour n'est plus ancien que `context.keepLastTurns`. | Envoyée au modèle comme nouveau message (voir ci-dessous). | |
| 12 | -| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles et oublie le dernier compte de tokens du serveur ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. | | 12 | +| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles, oublie le dernier compte de tokens du serveur et les plans de la conversation ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. | |
| 13 | | `/sessions` | Liste les sessions enregistrées du projet, la plus récente d'abord : identifiant, dernière mise à jour, nombre de questions, première question, puis ` · ⚠️ N error(s)` quand la session a enregistré des erreurs ; `*` marque la session courante. Avec `sessions.enabled: false` : `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Envoyée au modèle comme nouveau message. | | 13 | | `/sessions` | Liste les sessions enregistrées du projet, la plus récente d'abord : identifiant, dernière mise à jour, nombre de questions, première question, puis ` · ⚠️ N error(s)` quand la session a enregistré des erreurs ; `*` marque la session courante. Avec `sessions.enabled: false` : `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Envoyée au modèle comme nouveau message. | |
| 14 | -| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles et le compte de tokens du serveur ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. | | 14 | +| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles, le compte de tokens du serveur et les plans ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. | |
| 15 | | `/help` | Affiche les commandes de cette façade, une par ligne avec leur description, alignées. | Envoyée au modèle comme nouveau message. | | 15 | | `/help` | Affiche les commandes de cette façade, une par ligne avec leur description, alignées. | Envoyée au modèle comme nouveau message. | |
| 16 | | `/save <fichier> [n]` | Écrit la dernière réponse (`n` : la n-ième depuis la fin) telle quelle dans un nouveau fichier, en créant les répertoires manquants, et affiche `💾 saved the last answer to <fichier> (N bytes)`. N'écrase jamais : un fichier existant donne `⚠️ <fichier> already exists — nothing was written; choose another name`. Un nom de fichier avec des espaces n'est pas pris en charge. Le modèle n'est pas appelé. | Envoyée au modèle comme nouveau message. | | 16 | | `/save <fichier> [n]` | Écrit la dernière réponse (`n` : la n-ième depuis la fin) telle quelle dans un nouveau fichier, en créant les répertoires manquants, et affiche `💾 saved the last answer to <fichier> (N bytes)`. N'écrase jamais : un fichier existant donne `⚠️ <fichier> already exists — nothing was written; choose another name`. Un nom de fichier avec des espaces n'est pas pris en charge. Le modèle n'est pas appelé. | Envoyée au modèle comme nouveau message. | |
| 17 | | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory` : cherche dans les sessions enregistrées du projet et affiche `🔎 N hit(s) for "<mots>" in <rép>`, puis deux lignes par résultat — `<id de session> · <date> · turn <n> · question\|answer\|summary` et le texte autour des mots trouvés, plus `→ <réponse>` sous une question (la réponse sur laquelle son tour s'est terminé) — ou `🔎 no hit for "<mots>" in <rép>`. La casse et les accents ne comptent pas ; les mots d'une ou deux lettres sont ignorés à côté de mots plus longs ; un résultat contient au moins un des mots ; les résultats sont classés par nombre de mots contenus, puis du plus récent au plus ancien ; 10 au plus. Les questions, les réponses et les résumés de `/compact` sont cherchés, pas les appels d'outils ; la conversation en cours est exclue. Sans mots : `⚠️ nothing to search: give one or more words`. Le modèle n'est pas appelé ; l'historique n'est pas touché. | Envoyée au modèle comme nouveau message. | | 17 | | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory` : cherche dans les sessions enregistrées du projet et affiche `🔎 N hit(s) for "<mots>" in <rép>`, puis deux lignes par résultat — `<id de session> · <date> · turn <n> · question\|answer\|summary` et le texte autour des mots trouvés, plus `→ <réponse>` sous une question (la réponse sur laquelle son tour s'est terminé) — ou `🔎 no hit for "<mots>" in <rép>`. La casse et les accents ne comptent pas ; les mots d'une ou deux lettres sont ignorés à côté de mots plus longs ; un résultat contient au moins un des mots ; les résultats sont classés par nombre de mots contenus, puis du plus récent au plus ancien ; 10 au plus. Les questions, les réponses et les résumés de `/compact` sont cherchés, pas les appels d'outils ; la conversation en cours est exclue. Sans mots : `⚠️ nothing to search: give one or more words`. Le modèle n'est pas appelé ; l'historique n'est pas touché. | Envoyée au modèle comme nouveau message. | |
| @@ -176,7 +176,7 @@ Deux commandes existent en mode ACP, trois avec `unloadModels` (le défaut) : `/ | |||
| 176 | 176 | ||
| 177 | | Commande | En ACP | | 177 | | Commande | En ACP | |
| 178 | |----------|--------| | 178 | |----------|--------| |
| 179 | -| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens oublié ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. | | 179 | +| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens et plans de cette session oubliés (les autres sessions gardent les leurs) ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. | |
| 180 | | `/compact` | Comme dans le terminal, toujours forcée : `🗜️ compressed …`, `🗜️ nothing to compact: …` ou `[compact: failed, history kept: …]`. Attend d'abord la fin d'un tour en cours. | | 180 | | `/compact` | Comme dans le terminal, toujours forcée : `🗜️ compressed …`, `🗜️ nothing to compact: …` ou `[compact: failed, history kept: …]`. Attend d'abord la fin d'un tour en cours. | |
| 181 | | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, dans le projet de la session (`sessions.dir` sous son `cwd`), comme texte de l'agent ; l'historique n'est pas touché. | | 181 | | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, dans le projet de la session (`sessions.dir` sous son `cwd`), comme texte de l'agent ; l'historique n'est pas touché. | |
| 182 | | `/docs <mots>` | Avec `docSearch.searchDocs`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, depuis le `cwd` de la session, comme texte de l'agent ; l'historique n'est pas touché. | | 182 | | `/docs <mots>` | Avec `docSearch.searchDocs`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, depuis le `cwd` de la session, comme texte de l'agent ; l'historique n'est pas touché. | |
modified
docs/fr/reference/tools.md +32 -0 | @@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines: | ||
| 132 | 132 | │ 6 │ } |
| 133 | 133 | ``` |
| 134 | 134 | |
| 135 | +## `plan` | |
| 136 | + | |
| 137 | +Déclaré quand `planTool: true` (le défaut). Transforme une liste de tâches en un plan que le modèle met à jour au fil du travail. Voir [Gérer des plans et des tâches](../how-to/manage-plans-and-tasks.md). | |
| 138 | + | |
| 139 | +| Champ | Type | Description | | |
| 140 | +|-------|------|-------------| | |
| 141 | +| `action` | chaîne | Obligatoire. `create`, `get`, `update`, `next`, `list`, `delete` ou `status`. | | |
| 142 | +| `name` | chaîne | Le plan. Pour `create`, facultatif : un plan sans nom s'appelle `user-request-N` (N compte les plans de la conversation). Pour les autres actions, facultatif aussi : sans lui, l'action porte sur le dernier plan créé dans la conversation (`list` l'ignore). | | |
| 143 | +| `tasks` | liste de `{description, id?}` | Pour `create` : les tâches, dans l'ordre. Une tâche sans `id` reçoit sa position (1, 2, …). Ignoré par les autres actions. | | |
| 144 | +| `taskId` | entier | Pour `update` : la tâche, à partir de 1. | | |
| 145 | +| `status` | chaîne | Pour `update` : `open`, `in_progress`, `done` ou `failed`. | | |
| 146 | +| `message` | chaîne | Pour `update` : une note gardée sur la tâche. | | |
| 147 | + | |
| 148 | +| Action | Effet | Résultat (texte JSON) | | |
| 149 | +|--------|-------|-----------------------| | |
| 150 | +| `create` | Crée le plan, chaque tâche `open`. Refusé si le nom est déjà pris. | `result` (`Plan '<nom>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total` ; en mode ligne, aussi le `plan` entier. | | |
| 151 | +| `get` | Lit un plan. | `result`, `plan` (nom, tâches avec id, description, statut, heures de début et de fin, note). | | |
| 152 | +| `update` | Fixe le statut d'une tâche ; `in_progress` enregistre son heure de début (une fois) et en fait la tâche courante, `done` et `failed` enregistrent son heure de fin. | `result`, `status` (la ligne de statut, ci-dessous). | | |
| 153 | +| `next` | Marque la tâche courante `done` et démarre la suivante qui n'est ni faite ni en échec (`in_progress`) ; une tâche déjà terminée avec `update` est sautée. | `result` (`Task N done. Next: Task M - <description>`, ou `Plan '<nom>' is complete! All N tasks done.`), `status`, `nextTask` s'il y en a une. | | |
| 154 | +| `list` | Liste les plans existants. | `result`, `plans` (noms, du plus ancien au plus récent). | | |
| 155 | +| `delete` | Supprime un plan. | `result`. | | |
| 156 | +| `status` | Lit la ligne de statut. | `result` (`Plan '<nom>': D/N tasks complete`), `status`. | | |
| 157 | + | |
| 158 | +| Aspect | Comportement | | |
| 159 | +|--------|--------------| | |
| 160 | +| Ligne de statut | `[C/N] <icône> Task C: <description> (<statut>)` pour la tâche en cours de travail — la première en cours, sinon la première encore ouverte —, `[N/N] Plan finished: D done, F failed` quand toutes les tâches sont terminées et que certaines ont échoué, `[N/N] All tasks complete!` quand toutes sont faites. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. | | |
| 161 | +| Erreurs | Renvoyées comme texte, jamais comme un échec de génération : `[error: no plans found for this session]`, `[error: no plan named '<nom>' found]`, `[error: plan '<nom>' already exists]`, `[error: no task with id N in plan '<nom>']`, `[error: unknown action '<action>'. Valid actions: …]`. Un `update` sans `status` valide renvoie l'appel reçu, précise que `tasks` est ignoré par `update` quand il a été envoyé, et donne un appel qui fonctionne. | | |
| 162 | +| Stockage | En mémoire, par conversation : le mode ligne et la fenêtre en tiennent une ; en ACP chaque session a la sienne, et un fil ne voit jamais les plans d'un autre. Les plans ne sont pas écrits dans le fichier de session et sont perdus à l'arrêt de mm. Une conversation qui change repart sans plan : `/new` et `/resume` dans toutes les façades, `session/load` et `session/resume` en ACP. | | |
| 163 | +| Permission | `create`, `update`, `next` et `delete` sont demandés quand `yolo: false` ; un refus renvoie `[plan: command rejected by the user]`. `get`, `list` et `status` ne demandent jamais. | | |
| 164 | +| Affichage | Mode ligne : `📋 plan: <action> <nom>` avant l'appel, puis `📋 <ligne de statut>` après chaque action qui en a une (`create`, `update`, `next`, `status`). Fenêtre : l'appel comme appel d'outil, et `📋 <ligne de statut>` en notice. Éditeur (ACP) : l'appel comme appel d'outil, le statut dans son résultat. La progression est affichée par mm, que le modèle la reprenne ou non. | | |
| 165 | +| ACP et fenêtre | Appel d'outil de type `edit` pour `create`, `update`, `next`, `delete`, `read` pour les autres ; un résultat commençant par `[error:` est un appel en échec. | | |
| 166 | + | |
| 135 | 167 | ## `lsp` |
| 136 | 168 | |
| 137 | 169 | Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur le `PATH` au démarrage. Un serveur listé mais introuvable est ignoré avec un `[warning: lsp server <nom> (<commande>) not found on the PATH — skipped]` ; quand aucun n'est trouvé, l'outil n'est pas déclaré et la bannière le dit. |
| @@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines: | |||
| 132 | │ 6 │ } | 132 | │ 6 │ } |
| 133 | ``` | 133 | ``` |
| 134 | 134 | ||
| 135 | +## `plan` | ||
| 136 | + | ||
| 137 | +Déclaré quand `planTool: true` (le défaut). Transforme une liste de tâches en un plan que le modèle met à jour au fil du travail. Voir [Gérer des plans et des tâches](../how-to/manage-plans-and-tasks.md). | ||
| 138 | + | ||
| 139 | +| Champ | Type | Description | | ||
| 140 | +|-------|------|-------------| | ||
| 141 | +| `action` | chaîne | Obligatoire. `create`, `get`, `update`, `next`, `list`, `delete` ou `status`. | | ||
| 142 | +| `name` | chaîne | Le plan. Pour `create`, facultatif : un plan sans nom s'appelle `user-request-N` (N compte les plans de la conversation). Pour les autres actions, facultatif aussi : sans lui, l'action porte sur le dernier plan créé dans la conversation (`list` l'ignore). | | ||
| 143 | +| `tasks` | liste de `{description, id?}` | Pour `create` : les tâches, dans l'ordre. Une tâche sans `id` reçoit sa position (1, 2, …). Ignoré par les autres actions. | | ||
| 144 | +| `taskId` | entier | Pour `update` : la tâche, à partir de 1. | | ||
| 145 | +| `status` | chaîne | Pour `update` : `open`, `in_progress`, `done` ou `failed`. | | ||
| 146 | +| `message` | chaîne | Pour `update` : une note gardée sur la tâche. | | ||
| 147 | + | ||
| 148 | +| Action | Effet | Résultat (texte JSON) | | ||
| 149 | +|--------|-------|-----------------------| | ||
| 150 | +| `create` | Crée le plan, chaque tâche `open`. Refusé si le nom est déjà pris. | `result` (`Plan '<nom>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total` ; en mode ligne, aussi le `plan` entier. | | ||
| 151 | +| `get` | Lit un plan. | `result`, `plan` (nom, tâches avec id, description, statut, heures de début et de fin, note). | | ||
| 152 | +| `update` | Fixe le statut d'une tâche ; `in_progress` enregistre son heure de début (une fois) et en fait la tâche courante, `done` et `failed` enregistrent son heure de fin. | `result`, `status` (la ligne de statut, ci-dessous). | | ||
| 153 | +| `next` | Marque la tâche courante `done` et démarre la suivante qui n'est ni faite ni en échec (`in_progress`) ; une tâche déjà terminée avec `update` est sautée. | `result` (`Task N done. Next: Task M - <description>`, ou `Plan '<nom>' is complete! All N tasks done.`), `status`, `nextTask` s'il y en a une. | | ||
| 154 | +| `list` | Liste les plans existants. | `result`, `plans` (noms, du plus ancien au plus récent). | | ||
| 155 | +| `delete` | Supprime un plan. | `result`. | | ||
| 156 | +| `status` | Lit la ligne de statut. | `result` (`Plan '<nom>': D/N tasks complete`), `status`. | | ||
| 157 | + | ||
| 158 | +| Aspect | Comportement | | ||
| 159 | +|--------|--------------| | ||
| 160 | +| Ligne de statut | `[C/N] <icône> Task C: <description> (<statut>)` pour la tâche en cours de travail — la première en cours, sinon la première encore ouverte —, `[N/N] Plan finished: D done, F failed` quand toutes les tâches sont terminées et que certaines ont échoué, `[N/N] All tasks complete!` quand toutes sont faites. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. | | ||
| 161 | +| Erreurs | Renvoyées comme texte, jamais comme un échec de génération : `[error: no plans found for this session]`, `[error: no plan named '<nom>' found]`, `[error: plan '<nom>' already exists]`, `[error: no task with id N in plan '<nom>']`, `[error: unknown action '<action>'. Valid actions: …]`. Un `update` sans `status` valide renvoie l'appel reçu, précise que `tasks` est ignoré par `update` quand il a été envoyé, et donne un appel qui fonctionne. | | ||
| 162 | +| Stockage | En mémoire, par conversation : le mode ligne et la fenêtre en tiennent une ; en ACP chaque session a la sienne, et un fil ne voit jamais les plans d'un autre. Les plans ne sont pas écrits dans le fichier de session et sont perdus à l'arrêt de mm. Une conversation qui change repart sans plan : `/new` et `/resume` dans toutes les façades, `session/load` et `session/resume` en ACP. | | ||
| 163 | +| Permission | `create`, `update`, `next` et `delete` sont demandés quand `yolo: false` ; un refus renvoie `[plan: command rejected by the user]`. `get`, `list` et `status` ne demandent jamais. | | ||
| 164 | +| Affichage | Mode ligne : `📋 plan: <action> <nom>` avant l'appel, puis `📋 <ligne de statut>` après chaque action qui en a une (`create`, `update`, `next`, `status`). Fenêtre : l'appel comme appel d'outil, et `📋 <ligne de statut>` en notice. Éditeur (ACP) : l'appel comme appel d'outil, le statut dans son résultat. La progression est affichée par mm, que le modèle la reprenne ou non. | | ||
| 165 | +| ACP et fenêtre | Appel d'outil de type `edit` pour `create`, `update`, `next`, `delete`, `read` pour les autres ; un résultat commençant par `[error:` est un appel en échec. | | ||
| 166 | + | ||
| 135 | ## `lsp` | 167 | ## `lsp` |
| 136 | 168 | ||
| 137 | Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur le `PATH` au démarrage. Un serveur listé mais introuvable est ignoré avec un `[warning: lsp server <nom> (<commande>) not found on the PATH — skipped]` ; quand aucun n'est trouvé, l'outil n'est pas déclaré et la bannière le dit. | 169 | Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur le `PATH` au démarrage. Un serveur listé mais introuvable est ignoré avec un `[warning: lsp server <nom> (<commande>) not found on the PATH — skipped]` ; quand aucun n'est trouvé, l'outil n'est pas déclaré et la bannière le dit. |
modified
internal/acp/acp.go +2 -0 | @@ -41,6 +41,7 @@ import ( | ||
| 41 | 41 | history "mm/internal/session" |
| 42 | 42 | "mm/internal/skills" |
| 43 | 43 | "mm/internal/store" |
| 44 | + "mm/internal/tools" | |
| 44 | 45 | "mm/internal/ui" |
| 45 | 46 | "mm/internal/version" |
| 46 | 47 | |
| @@ -573,6 +574,7 @@ func skillCommand(input string) string { | ||
| 573 | 574 | func (f *front) startNewSession(ctx context.Context, sid sdk.SessionId, s *session) sdk.PromptResponse { |
| 574 | 575 | f.archive(sid, s) |
| 575 | 576 | forgotten := resetSession(s, f.system) |
| 577 | + tools.ClearPlans(string(sid)) // this thread's plans only (tickets 108, 109) | |
| 576 | 578 | f.e.ForgetInputTokens() |
| 577 | 579 | s.usage.lastUsed = 0 // measured on the history just dropped; the totals stay, the tokens were spent |
| 578 | 580 | f.persist(sid, s) |
| @@ -41,6 +41,7 @@ import ( | |||
| 41 | history "mm/internal/session" | 41 | history "mm/internal/session" |
| 42 | "mm/internal/skills" | 42 | "mm/internal/skills" |
| 43 | "mm/internal/store" | 43 | "mm/internal/store" |
| 44 | + "mm/internal/tools" | ||
| 44 | "mm/internal/ui" | 45 | "mm/internal/ui" |
| 45 | "mm/internal/version" | 46 | "mm/internal/version" |
| 46 | 47 | ||
| @@ -573,6 +574,7 @@ func skillCommand(input string) string { | |||
| 573 | func (f *front) startNewSession(ctx context.Context, sid sdk.SessionId, s *session) sdk.PromptResponse { | 574 | func (f *front) startNewSession(ctx context.Context, sid sdk.SessionId, s *session) sdk.PromptResponse { |
| 574 | f.archive(sid, s) | 575 | f.archive(sid, s) |
| 575 | forgotten := resetSession(s, f.system) | 576 | forgotten := resetSession(s, f.system) |
| 577 | + tools.ClearPlans(string(sid)) // this thread's plans only (tickets 108, 109) | ||
| 576 | f.e.ForgetInputTokens() | 578 | f.e.ForgetInputTokens() |
| 577 | s.usage.lastUsed = 0 // measured on the history just dropped; the totals stay, the tokens were spent | 579 | s.usage.lastUsed = 0 // measured on the history just dropped; the totals stay, the tokens were spent |
| 578 | f.persist(sid, s) | 580 | f.persist(sid, s) |
modified
internal/acp/persist.go +4 -0 | @@ -10,6 +10,7 @@ import ( | ||
| 10 | 10 | "mm/internal/agentsmd" |
| 11 | 11 | history "mm/internal/session" |
| 12 | 12 | "mm/internal/store" |
| 13 | + "mm/internal/tools" | |
| 13 | 14 | |
| 14 | 15 | sdk "github.com/coder/acp-go-sdk" |
| 15 | 16 | ) |
| @@ -81,6 +82,9 @@ func (f *front) restore(method string, id sdk.SessionId, cwd string) (*session, | ||
| 81 | 82 | f.mu.Lock() |
| 82 | 83 | f.sessions[id] = s |
| 83 | 84 | f.mu.Unlock() |
| 85 | + // Plans are not in the session file: a restored thread starts with none, | |
| 86 | + // never with what this process held under the same id (ticket 109). | |
| 87 | + tools.ClearPlans(string(id)) | |
| 84 | 88 | return s, nil |
| 85 | 89 | } |
| 86 | 90 | |
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | "mm/internal/agentsmd" | 10 | "mm/internal/agentsmd" |
| 11 | history "mm/internal/session" | 11 | history "mm/internal/session" |
| 12 | "mm/internal/store" | 12 | "mm/internal/store" |
| 13 | + "mm/internal/tools" | ||
| 13 | 14 | ||
| 14 | sdk "github.com/coder/acp-go-sdk" | 15 | sdk "github.com/coder/acp-go-sdk" |
| 15 | ) | 16 | ) |
| @@ -81,6 +82,9 @@ func (f *front) restore(method string, id sdk.SessionId, cwd string) (*session, | |||
| 81 | f.mu.Lock() | 82 | f.mu.Lock() |
| 82 | f.sessions[id] = s | 83 | f.sessions[id] = s |
| 83 | f.mu.Unlock() | 84 | f.mu.Unlock() |
| 85 | + // Plans are not in the session file: a restored thread starts with none, | ||
| 86 | + // never with what this process held under the same id (ticket 109). | ||
| 87 | + tools.ClearPlans(string(id)) | ||
| 84 | return s, nil | 88 | return s, nil |
| 85 | } | 89 | } |
| 86 | 90 | ||
added
internal/acp/plans_test.go +102 -0 | new file mode 100644 | ||
| @@ -0,0 +1,102 @@ | ||
| 1 | +package acp | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "context" | |
| 5 | + "path/filepath" | |
| 6 | + "strings" | |
| 7 | + "testing" | |
| 8 | + | |
| 9 | + "mm/internal/detector" | |
| 10 | + "mm/internal/engine" | |
| 11 | + "mm/internal/store" | |
| 12 | + "mm/internal/tools" | |
| 13 | + "mm/internal/ui" | |
| 14 | + | |
| 15 | + "github.com/firebase/genkit/go/genkit" | |
| 16 | +) | |
| 17 | + | |
| 18 | +// threadOf stands for a turn of one ACP session, as far as the plan tool can | |
| 19 | +// tell: a sink that names its conversation and draws nothing. | |
| 20 | +type threadOf struct{ id string } | |
| 21 | + | |
| 22 | +func (t threadOf) ConversationID() string { return t.id } | |
| 23 | +func (threadOf) Text(string) {} | |
| 24 | +func (threadOf) Reasoning(string) {} | |
| 25 | +func (threadOf) ToolStart(ui.ToolEvent) {} | |
| 26 | +func (threadOf) Allow(context.Context, ui.ToolEvent) bool { return true } | |
| 27 | +func (threadOf) ToolRunning(string) {} | |
| 28 | +func (threadOf) ToolEnd(string, ui.ToolResult) {} | |
| 29 | +func (threadOf) WorkDir() string { return "" } | |
| 30 | + | |
| 31 | +// /new in one editor thread clears that thread's plans and leaves the other | |
| 32 | +// threads' alone (tickets 108, 109). | |
| 33 | +func TestNewOverACPClearsOnlyItsThreadsPlans(t *testing.T) { | |
| 34 | + tools.ClearAllPlansForTesting() | |
| 35 | + t.Cleanup(tools.ClearAllPlansForTesting) | |
| 36 | + g := genkit.Init(context.Background()) | |
| 37 | + tools.PlanTool(g, detector.NewLoopDetector(10, 3)) | |
| 38 | + plan := genkit.LookupTool(g, "plan") | |
| 39 | + run := func(thread string, input map[string]any) string { | |
| 40 | + ui.SetActive(threadOf{id: thread}) | |
| 41 | + defer ui.SetActive(nil) | |
| 42 | + out, err := plan.RunRaw(context.Background(), input) | |
| 43 | + if err != nil { | |
| 44 | + t.Fatal(err) | |
| 45 | + } | |
| 46 | + return out.(string) | |
| 47 | + } | |
| 48 | + | |
| 49 | + w := newWire(t, "sys") | |
| 50 | + frontOf(t, w).e = &engine.Engine{} | |
| 51 | + open := func(id int) string { | |
| 52 | + w.send(id, "session/new", `{"cwd":"`+t.TempDir()+`","mcpServers":[]}`) | |
| 53 | + _, resp := w.until(id) | |
| 54 | + return resp["result"].(map[string]any)["sessionId"].(string) | |
| 55 | + } | |
| 56 | + a, b := open(1), open(2) | |
| 57 | + tasks := []any{map[string]any{"description": "x"}} | |
| 58 | + run(a, map[string]any{"action": "create", "name": "pa", "tasks": tasks}) | |
| 59 | + run(b, map[string]any{"action": "create", "name": "pb", "tasks": tasks}) | |
| 60 | + | |
| 61 | + w.send(3, "session/prompt", `{"sessionId":"`+a+`","prompt":[{"type":"text","text":"/new"}]}`) | |
| 62 | + w.until(3) | |
| 63 | + if out := run(a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") { | |
| 64 | + t.Errorf("thread A after its /new: %s", out) | |
| 65 | + } | |
| 66 | + if out := run(b, map[string]any{"action": "list"}); !strings.Contains(out, `"pb"`) { | |
| 67 | + t.Errorf("thread B lost its plan to A's /new: %s", out) | |
| 68 | + } | |
| 69 | +} | |
| 70 | + | |
| 71 | +// A thread restored by session/load starts with no plan, even when this | |
| 72 | +// process held plans under the same id: plans are not in the session file. | |
| 73 | +func TestLoadSessionStartsWithNoPlan(t *testing.T) { | |
| 74 | + withSessions(t) | |
| 75 | + tools.ClearAllPlansForTesting() | |
| 76 | + t.Cleanup(tools.ClearAllPlansForTesting) | |
| 77 | + g := genkit.Init(context.Background()) | |
| 78 | + tools.PlanTool(g, detector.NewLoopDetector(10, 3)) | |
| 79 | + plan := genkit.LookupTool(g, "plan") | |
| 80 | + const id = "20260916-100000-aaaaaaaa" | |
| 81 | + run := func(input map[string]any) string { | |
| 82 | + ui.SetActive(threadOf{id: id}) | |
| 83 | + defer ui.SetActive(nil) | |
| 84 | + out, err := plan.RunRaw(context.Background(), input) | |
| 85 | + if err != nil { | |
| 86 | + t.Fatal(err) | |
| 87 | + } | |
| 88 | + return out.(string) | |
| 89 | + } | |
| 90 | + cwd := t.TempDir() | |
| 91 | + if err := store.Open(filepath.Join(cwd, ".mm", "sessions")).Save(&store.Session{ID: id, Cwd: cwd, Messages: storedHistory()}); err != nil { | |
| 92 | + t.Fatal(err) | |
| 93 | + } | |
| 94 | + run(map[string]any{"action": "create", "name": "stale", "tasks": []any{map[string]any{"description": "x"}}}) | |
| 95 | + | |
| 96 | + w := newWire(t, "sys") | |
| 97 | + w.send(1, "session/load", `{"sessionId":"`+id+`","cwd":"`+cwd+`","mcpServers":[]}`) | |
| 98 | + w.until(1) | |
| 99 | + if out := run(map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") { | |
| 100 | + t.Errorf("after session/load: %s", out) | |
| 101 | + } | |
| 102 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | +package acp | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "context" | ||
| 5 | + "path/filepath" | ||
| 6 | + "strings" | ||
| 7 | + "testing" | ||
| 8 | + | ||
| 9 | + "mm/internal/detector" | ||
| 10 | + "mm/internal/engine" | ||
| 11 | + "mm/internal/store" | ||
| 12 | + "mm/internal/tools" | ||
| 13 | + "mm/internal/ui" | ||
| 14 | + | ||
| 15 | + "github.com/firebase/genkit/go/genkit" | ||
| 16 | +) | ||
| 17 | + | ||
| 18 | +// threadOf stands for a turn of one ACP session, as far as the plan tool can | ||
| 19 | +// tell: a sink that names its conversation and draws nothing. | ||
| 20 | +type threadOf struct{ id string } | ||
| 21 | + | ||
| 22 | +func (t threadOf) ConversationID() string { return t.id } | ||
| 23 | +func (threadOf) Text(string) {} | ||
| 24 | +func (threadOf) Reasoning(string) {} | ||
| 25 | +func (threadOf) ToolStart(ui.ToolEvent) {} | ||
| 26 | +func (threadOf) Allow(context.Context, ui.ToolEvent) bool { return true } | ||
| 27 | +func (threadOf) ToolRunning(string) {} | ||
| 28 | +func (threadOf) ToolEnd(string, ui.ToolResult) {} | ||
| 29 | +func (threadOf) WorkDir() string { return "" } | ||
| 30 | + | ||
| 31 | +// /new in one editor thread clears that thread's plans and leaves the other | ||
| 32 | +// threads' alone (tickets 108, 109). | ||
| 33 | +func TestNewOverACPClearsOnlyItsThreadsPlans(t *testing.T) { | ||
| 34 | + tools.ClearAllPlansForTesting() | ||
| 35 | + t.Cleanup(tools.ClearAllPlansForTesting) | ||
| 36 | + g := genkit.Init(context.Background()) | ||
| 37 | + tools.PlanTool(g, detector.NewLoopDetector(10, 3)) | ||
| 38 | + plan := genkit.LookupTool(g, "plan") | ||
| 39 | + run := func(thread string, input map[string]any) string { | ||
| 40 | + ui.SetActive(threadOf{id: thread}) | ||
| 41 | + defer ui.SetActive(nil) | ||
| 42 | + out, err := plan.RunRaw(context.Background(), input) | ||
| 43 | + if err != nil { | ||
| 44 | + t.Fatal(err) | ||
| 45 | + } | ||
| 46 | + return out.(string) | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + w := newWire(t, "sys") | ||
| 50 | + frontOf(t, w).e = &engine.Engine{} | ||
| 51 | + open := func(id int) string { | ||
| 52 | + w.send(id, "session/new", `{"cwd":"`+t.TempDir()+`","mcpServers":[]}`) | ||
| 53 | + _, resp := w.until(id) | ||
| 54 | + return resp["result"].(map[string]any)["sessionId"].(string) | ||
| 55 | + } | ||
| 56 | + a, b := open(1), open(2) | ||
| 57 | + tasks := []any{map[string]any{"description": "x"}} | ||
| 58 | + run(a, map[string]any{"action": "create", "name": "pa", "tasks": tasks}) | ||
| 59 | + run(b, map[string]any{"action": "create", "name": "pb", "tasks": tasks}) | ||
| 60 | + | ||
| 61 | + w.send(3, "session/prompt", `{"sessionId":"`+a+`","prompt":[{"type":"text","text":"/new"}]}`) | ||
| 62 | + w.until(3) | ||
| 63 | + if out := run(a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") { | ||
| 64 | + t.Errorf("thread A after its /new: %s", out) | ||
| 65 | + } | ||
| 66 | + if out := run(b, map[string]any{"action": "list"}); !strings.Contains(out, `"pb"`) { | ||
| 67 | + t.Errorf("thread B lost its plan to A's /new: %s", out) | ||
| 68 | + } | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +// A thread restored by session/load starts with no plan, even when this | ||
| 72 | +// process held plans under the same id: plans are not in the session file. | ||
| 73 | +func TestLoadSessionStartsWithNoPlan(t *testing.T) { | ||
| 74 | + withSessions(t) | ||
| 75 | + tools.ClearAllPlansForTesting() | ||
| 76 | + t.Cleanup(tools.ClearAllPlansForTesting) | ||
| 77 | + g := genkit.Init(context.Background()) | ||
| 78 | + tools.PlanTool(g, detector.NewLoopDetector(10, 3)) | ||
| 79 | + plan := genkit.LookupTool(g, "plan") | ||
| 80 | + const id = "20260916-100000-aaaaaaaa" | ||
| 81 | + run := func(input map[string]any) string { | ||
| 82 | + ui.SetActive(threadOf{id: id}) | ||
| 83 | + defer ui.SetActive(nil) | ||
| 84 | + out, err := plan.RunRaw(context.Background(), input) | ||
| 85 | + if err != nil { | ||
| 86 | + t.Fatal(err) | ||
| 87 | + } | ||
| 88 | + return out.(string) | ||
| 89 | + } | ||
| 90 | + cwd := t.TempDir() | ||
| 91 | + if err := store.Open(filepath.Join(cwd, ".mm", "sessions")).Save(&store.Session{ID: id, Cwd: cwd, Messages: storedHistory()}); err != nil { | ||
| 92 | + t.Fatal(err) | ||
| 93 | + } | ||
| 94 | + run(map[string]any{"action": "create", "name": "stale", "tasks": []any{map[string]any{"description": "x"}}}) | ||
| 95 | + | ||
| 96 | + w := newWire(t, "sys") | ||
| 97 | + w.send(1, "session/load", `{"sessionId":"`+id+`","cwd":"`+cwd+`","mcpServers":[]}`) | ||
| 98 | + w.until(1) | ||
| 99 | + if out := run(map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") { | ||
| 100 | + t.Errorf("after session/load: %s", out) | ||
| 101 | + } | ||
| 102 | +} | ||
modified
internal/acp/sink.go +4 -0 | @@ -53,6 +53,10 @@ func (k *sink) Reasoning(text string) { | ||
| 53 | 53 | // there, and relative file paths resolve against it (ui.Resolve). |
| 54 | 54 | func (k *sink) WorkDir() string { return k.s.cwd } |
| 55 | 55 | |
| 56 | +// ConversationID is the ACP session id: what keeps one thread's plans out of | |
| 57 | +// another's (ui.Conversation). | |
| 58 | +func (k *sink) ConversationID() string { return string(k.sid) } | |
| 59 | + | |
| 56 | 60 | // ToolStart announces the call, status pending, before anything runs — the |
| 57 | 61 | // same promise as the terminal's 🛠️ line: it prints BEFORE execution, so its |
| 58 | 62 | // absence proves nothing ran. |
| @@ -53,6 +53,10 @@ func (k *sink) Reasoning(text string) { | |||
| 53 | // there, and relative file paths resolve against it (ui.Resolve). | 53 | // there, and relative file paths resolve against it (ui.Resolve). |
| 54 | func (k *sink) WorkDir() string { return k.s.cwd } | 54 | func (k *sink) WorkDir() string { return k.s.cwd } |
| 55 | 55 | ||
| 56 | +// ConversationID is the ACP session id: what keeps one thread's plans out of | ||
| 57 | +// another's (ui.Conversation). | ||
| 58 | +func (k *sink) ConversationID() string { return string(k.sid) } | ||
| 59 | + | ||
| 56 | // ToolStart announces the call, status pending, before anything runs — the | 60 | // ToolStart announces the call, status pending, before anything runs — the |
| 57 | // same promise as the terminal's 🛠️ line: it prints BEFORE execution, so its | 61 | // same promise as the terminal's 🛠️ line: it prints BEFORE execution, so its |
| 58 | // absence proves nothing ran. | 62 | // absence proves nothing ran. |
modified
internal/agent/agent.go +3 -2 | @@ -484,8 +484,8 @@ func startNewSession(e *engine.Engine, d *detector.LoopDetector, system string, | ||
| 484 | 484 | forgotten := session.Forgotten(old) |
| 485 | 485 | e.ForgetInputTokens() |
| 486 | 486 | d.Reset() |
| 487 | - // Clear any active plans when starting a new session | |
| 488 | - tools.ClearAllPlans() | |
| 487 | + // Plans belong to the conversation /new just closed (ticket 109). | |
| 488 | + tools.ClearPlans("") | |
| 489 | 489 | dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) |
| 490 | 490 | return session.Fresh(agentsmd.System(system, "")) |
| 491 | 491 | } |
| @@ -551,6 +551,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa | ||
| 551 | 551 | messages = resumed |
| 552 | 552 | l.d.Reset() |
| 553 | 553 | l.e.ForgetInputTokens() |
| 554 | + tools.ClearPlans("") // the plans in memory were the previous conversation's | |
| 554 | 555 | fmt.Println(line) |
| 555 | 556 | default: |
| 556 | 557 | return messages, false |
| @@ -484,8 +484,8 @@ func startNewSession(e *engine.Engine, d *detector.LoopDetector, system string, | |||
| 484 | forgotten := session.Forgotten(old) | 484 | forgotten := session.Forgotten(old) |
| 485 | e.ForgetInputTokens() | 485 | e.ForgetInputTokens() |
| 486 | d.Reset() | 486 | d.Reset() |
| 487 | - // Clear any active plans when starting a new session | 487 | + // Plans belong to the conversation /new just closed (ticket 109). |
| 488 | - tools.ClearAllPlans() | 488 | + tools.ClearPlans("") |
| 489 | dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) | 489 | dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) |
| 490 | return session.Fresh(agentsmd.System(system, "")) | 490 | return session.Fresh(agentsmd.System(system, "")) |
| 491 | } | 491 | } |
| @@ -551,6 +551,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa | |||
| 551 | messages = resumed | 551 | messages = resumed |
| 552 | l.d.Reset() | 552 | l.d.Reset() |
| 553 | l.e.ForgetInputTokens() | 553 | l.e.ForgetInputTokens() |
| 554 | + tools.ClearPlans("") // the plans in memory were the previous conversation's | ||
| 554 | fmt.Println(line) | 555 | fmt.Println(line) |
| 555 | default: | 556 | default: |
| 556 | return messages, false | 557 | return messages, false |
modified
internal/tools/plan.go +118 -37 | @@ -38,6 +38,7 @@ type Plan struct { | ||
| 38 | 38 | Tasks []PlanTask `json:"tasks"` |
| 39 | 39 | Current int `json:"current"` // 0 = not started, 1 = first task, etc. |
| 40 | 40 | CreatedAt string `json:"createdAt"` // ISO timestamp |
| 41 | + order int // creation order in the store: CreatedAt has one-second precision | |
| 41 | 42 | } |
| 42 | 43 | |
| 43 | 44 | // PlanTask represents a single task within a plan. |
| @@ -55,7 +56,7 @@ type planInput struct { | ||
| 55 | 56 | // Action is required: create, get, update, next, list, delete, status |
| 56 | 57 | Action string `json:"action" jsonschema_description:"Required. One of: create, get, update, next, list, delete, status"` |
| 57 | 58 | // Name is the plan identifier (default: 'default' for unnamed plans) |
| 58 | - Name string `json:"name,omitempty" jsonschema_description:"Plan name. Defaults to 'user-request' if not provided."` | |
| 59 | + Name string `json:"name,omitempty" jsonschema_description:"Plan name. For create, optional (a name is chosen). For the other actions, optional: without it, the most recently created plan is used."` | |
| 59 | 60 | // Tasks is used with 'create' action to define the plan's tasks |
| 60 | 61 | Tasks []PlanTaskInput `json:"tasks,omitempty" jsonschema_description:"For create action: list of tasks to add"` |
| 61 | 62 | // TaskID identifies which task for update/next actions (1-based) |
| @@ -91,6 +92,7 @@ type PlanStore struct { | ||
| 91 | 92 | mu sync.RWMutex |
| 92 | 93 | plans map[string]map[string]*Plan // sessionID -> planName -> Plan |
| 93 | 94 | nextID map[string]int // sessionID -> next task ID |
| 95 | + made int // plans created so far, for Plan.order | |
| 94 | 96 | } |
| 95 | 97 | |
| 96 | 98 | var planStore = &PlanStore{ |
| @@ -98,14 +100,13 @@ var planStore = &PlanStore{ | ||
| 98 | 100 | nextID: make(map[string]int), |
| 99 | 101 | } |
| 100 | 102 | |
| 101 | -// getSessionID extracts a session identifier from the tool context. | |
| 102 | -// For simplicity in v1, we use a placeholder since the actual session ID | |
| 103 | -// isn't easily accessible from tool context. In practice, plans are cleared | |
| 104 | -// on /new command via ClearPlans(). | |
| 103 | +// getSessionID is the conversation the plan call belongs to (ticket 108): the | |
| 104 | +// ACP session id, or "" in the line mode and the window, which hold one | |
| 105 | +// conversation at a time. It used to be a constant, so every thread an | |
| 106 | +// editor opened in one mm process saw — and could change — the others' | |
| 107 | +// plans. | |
| 105 | 108 | func getSessionID() string { |
| 106 | - // v1: use a single session for simplicity | |
| 107 | - // v2: extract from context when available | |
| 108 | - return "current-session" | |
| 109 | + return ui.ConversationID() | |
| 109 | 110 | } |
| 110 | 111 | |
| 111 | 112 | // newPlanName generates a unique plan name if none is provided. |
| @@ -153,11 +154,13 @@ func (s *PlanStore) CreatePlan(name string, tasks []PlanTaskInput) (*Plan, strin | ||
| 153 | 154 | } |
| 154 | 155 | } |
| 155 | 156 | |
| 157 | + s.made++ | |
| 156 | 158 | plan := &Plan{ |
| 157 | 159 | Name: name, |
| 158 | 160 | Tasks: planTasks, |
| 159 | 161 | Current: 0, |
| 160 | 162 | CreatedAt: time.Now().UTC().Format(time.RFC3339), |
| 163 | + order: s.made, | |
| 161 | 164 | } |
| 162 | 165 | |
| 163 | 166 | s.plans[sessionID][name] = plan |
| @@ -246,6 +249,9 @@ func (s *PlanStore) UpdateTask(name string, taskID int, status string, message s | ||
| 246 | 249 | task := &plan.Tasks[taskIndex] |
| 247 | 250 | task.Status = status |
| 248 | 251 | task.Notes = message |
| 252 | + if status == "in_progress" { | |
| 253 | + plan.Current = taskIndex + 1 // so that a later `next` moves on from here | |
| 254 | + } | |
| 249 | 255 | |
| 250 | 256 | // Set timestamps |
| 251 | 257 | now := time.Now().UTC().Format(time.RFC3339) |
| @@ -286,8 +292,12 @@ func (s *PlanStore) NextTask(name string) (*Plan, *PlanTask, string, error) { | ||
| 286 | 292 | plan.Tasks[plan.Current-1].CompletedAt = time.Now().UTC().Format(time.RFC3339) |
| 287 | 293 | } |
| 288 | 294 | |
| 289 | - // Move to next | |
| 295 | + // Move to the next task that still needs work: a task the model already | |
| 296 | + // marked done with `update` is skipped, never started again. | |
| 290 | 297 | plan.Current++ |
| 298 | + for plan.Current <= len(plan.Tasks) && finished(plan.Tasks[plan.Current-1]) { | |
| 299 | + plan.Current++ | |
| 300 | + } | |
| 291 | 301 | |
| 292 | 302 | // Return next task |
| 293 | 303 | if plan.Current <= len(plan.Tasks) { |
| @@ -345,28 +355,42 @@ func (s *PlanStore) GetStatus(name string) (string, string, error) { | ||
| 345 | 355 | } |
| 346 | 356 | } |
| 347 | 357 | |
| 348 | - // Format status line | |
| 349 | - // Current=0 means not started yet, Task 1 is next | |
| 350 | - // Current>0 means we've started, Task Current is the next one | |
| 351 | - if plan.Current == 0 { | |
| 352 | - // Not started, show Task 1 | |
| 353 | - task := plan.Tasks[0] | |
| 354 | - return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", 1, len(plan.Tasks), statusIcon(task.Status), 1, task.Description, task.Status), | |
| 355 | - fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil | |
| 358 | + // The line shows the task being worked on — the first in progress, else | |
| 359 | + // the first still open — whatever the model used to get there: `update` | |
| 360 | + // does not move plan.Current, so following Current alone showed task 1 | |
| 361 | + // for a plan whose task 1 was done and task 2 in progress (ticket 110). | |
| 362 | + summary := fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)) | |
| 363 | + if i := workingTask(plan); i >= 0 { | |
| 364 | + task := plan.Tasks[i] | |
| 365 | + return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", i+1, len(plan.Tasks), statusIcon(task.Status), i+1, task.Description, task.Status), summary, nil | |
| 356 | 366 | } |
| 357 | - | |
| 358 | - // Current > 0, show Task Current | |
| 359 | - if plan.Current <= len(plan.Tasks) { | |
| 360 | - task := plan.Tasks[plan.Current-1] | |
| 361 | - return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", plan.Current, len(plan.Tasks), statusIcon(task.Status), plan.Current, task.Description, task.Status), | |
| 362 | - fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil | |
| 367 | + if doneCount < len(plan.Tasks) { | |
| 368 | + failed := len(plan.Tasks) - doneCount | |
| 369 | + return fmt.Sprintf("[%d/%d] Plan finished: %d done, %d failed", len(plan.Tasks), len(plan.Tasks), doneCount, failed), summary, nil | |
| 363 | 370 | } |
| 364 | - | |
| 365 | - // All tasks complete | |
| 366 | 371 | return fmt.Sprintf("[%d/%d] All tasks complete!", len(plan.Tasks), len(plan.Tasks)), |
| 367 | 372 | fmt.Sprintf("Plan '%s': All %d tasks done ✅", name, len(plan.Tasks)), nil |
| 368 | 373 | } |
| 369 | 374 | |
| 375 | +// workingTask is the index of the task a plan is at: the first in progress, | |
| 376 | +// else the first still open; -1 when every task is done or failed. | |
| 377 | +func workingTask(plan *Plan) int { | |
| 378 | + for i, t := range plan.Tasks { | |
| 379 | + if t.Status == "in_progress" { | |
| 380 | + return i | |
| 381 | + } | |
| 382 | + } | |
| 383 | + for i, t := range plan.Tasks { | |
| 384 | + if t.Status == "open" { | |
| 385 | + return i | |
| 386 | + } | |
| 387 | + } | |
| 388 | + return -1 | |
| 389 | +} | |
| 390 | + | |
| 391 | +// finished reports whether a task needs no more work. | |
| 392 | +func finished(t PlanTask) bool { return t.Status == "done" || t.Status == "failed" } | |
| 393 | + | |
| 370 | 394 | // statusIcon is the display mark for a task status. |
| 371 | 395 | func statusIcon(status string) string { |
| 372 | 396 | switch status { |
| @@ -381,13 +405,29 @@ func statusIcon(status string) string { | ||
| 381 | 405 | } |
| 382 | 406 | } |
| 383 | 407 | |
| 384 | -// ClearPlans removes all plans for the current session. | |
| 385 | -// Called when /new command is issued. | |
| 386 | -func (s *PlanStore) ClearPlans() { | |
| 387 | - sessionID := getSessionID() | |
| 408 | +// LatestName is the name of the conversation's most recently created plan, | |
| 409 | +// "" when it has none. It is what an action without a name means: a small | |
| 410 | +// model does not keep the name `create` returned — measured on Qwen3.5 4B | |
| 411 | +// (demos/plans, 2026-10-21), every `update` came without one, failed with | |
| 412 | +// "no plan named ”", and the turn ended in runaway tool calls. | |
| 413 | +func (s *PlanStore) LatestName() string { | |
| 414 | + conversation := getSessionID() | |
| 415 | + s.mu.RLock() | |
| 416 | + defer s.mu.RUnlock() | |
| 417 | + latest, order := "", -1 | |
| 418 | + for name, p := range s.plans[conversation] { | |
| 419 | + if p.order > order { | |
| 420 | + latest, order = name, p.order | |
| 421 | + } | |
| 422 | + } | |
| 423 | + return latest | |
| 424 | +} | |
| 425 | + | |
| 426 | +// ClearPlans removes the plans of one conversation. | |
| 427 | +func (s *PlanStore) ClearPlans(conversation string) { | |
| 388 | 428 | s.mu.Lock() |
| 389 | 429 | defer s.mu.Unlock() |
| 390 | - delete(s.plans, sessionID) | |
| 430 | + delete(s.plans, conversation) | |
| 391 | 431 | } |
| 392 | 432 | |
| 393 | 433 | // ClearAllPlans removes all plans. Used for testing. |
| @@ -434,16 +474,23 @@ Always show the user the current plan status using the status action.` | ||
| 434 | 474 | // Terminal/REPL: announce the call |
| 435 | 475 | spinner.Suspend(func() { |
| 436 | 476 | printMu.Lock() |
| 437 | - fmt.Printf("\n📋 plan: %s %s\n", in.Action, in.Name) | |
| 477 | + fmt.Printf("\n📋 %s\n", planTitle(in)) | |
| 438 | 478 | printMu.Unlock() |
| 439 | 479 | }) |
| 440 | - if planChanges(in.Action) && !confirmed(tctx, "plan", fmt.Sprintf("plan: %s %s", in.Action, in.Name)) { | |
| 480 | + if planChanges(in.Action) && !confirmed(tctx, "plan", planTitle(in)) { | |
| 441 | 481 | inputJSON, _ := json.Marshal(in) |
| 442 | 482 | return refuse(d, "plan", string(inputJSON), "[plan: command rejected by the user]") |
| 443 | 483 | } |
| 444 | 484 | |
| 445 | 485 | spinner.Start("Running") |
| 446 | 486 | result := runPlanAction(in, true) |
| 487 | + if line := progressLine(result); line != "" { | |
| 488 | + spinner.Suspend(func() { | |
| 489 | + printMu.Lock() | |
| 490 | + fmt.Println(line) | |
| 491 | + printMu.Unlock() | |
| 492 | + }) | |
| 493 | + } | |
| 447 | 494 | |
| 448 | 495 | // Record in loop detector |
| 449 | 496 | inputJSON, _ := json.Marshal(in) |
| @@ -451,6 +498,26 @@ Always show the user the current plan status using the status action.` | ||
| 451 | 498 | }) |
| 452 | 499 | } |
| 453 | 500 | |
| 501 | +// planTitle names a call: `plan: update user-request-1`, `plan: list`. | |
| 502 | +func planTitle(in planInput) string { | |
| 503 | + return strings.TrimSpace("plan: " + in.Action + " " + in.Name) | |
| 504 | +} | |
| 505 | + | |
| 506 | +// progressLine is what mm shows the user after a plan action (ticket 110): | |
| 507 | +// the plan's status line — `📋 [2/3] 🔄 Task 2: Add a license | |
| 508 | +// (in_progress)` — taken from the result, "" when the action has none (get, | |
| 509 | +// list, delete, an error). Shown by mm itself, so the progress does not | |
| 510 | +// depend on the model repeating it. | |
| 511 | +func progressLine(result string) string { | |
| 512 | + var out struct { | |
| 513 | + Status string `json:"status"` | |
| 514 | + } | |
| 515 | + if json.Unmarshal([]byte(result), &out) != nil || out.Status == "" { | |
| 516 | + return "" | |
| 517 | + } | |
| 518 | + return "📋 " + out.Status | |
| 519 | +} | |
| 520 | + | |
| 454 | 521 | // planChanges reports whether a plan action changes a plan — the ones that |
| 455 | 522 | // ask for permission when yolo is off. |
| 456 | 523 | func planChanges(action string) bool { |
| @@ -467,7 +534,7 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl | ||
| 467 | 534 | ev := ui.ToolEvent{ |
| 468 | 535 | ID: ui.NextCallID(), |
| 469 | 536 | Tool: "plan", |
| 470 | - Title: fmt.Sprintf("plan: %s %s", in.Action, in.Name), | |
| 537 | + Title: planTitle(in), | |
| 471 | 538 | Kind: eventKind, |
| 472 | 539 | Input: map[string]any{"action": in.Action, "name": in.Name, "taskId": in.TaskID, "status": in.Status}, |
| 473 | 540 | } |
| @@ -492,16 +559,30 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl | ||
| 492 | 559 | failed := strings.HasPrefix(result, "[error:") |
| 493 | 560 | |
| 494 | 561 | s.ToolEnd(ev.ID, ui.ToolResult{Output: result, Failed: failed}) |
| 562 | + // The window shows the progress as a notice; an editor has none, and | |
| 563 | + // shows the status inside the tool call's result. | |
| 564 | + if n, ok := s.(ui.Noticer); ok { | |
| 565 | + if line := progressLine(result); line != "" { | |
| 566 | + n.Notice(line) | |
| 567 | + } | |
| 568 | + } | |
| 495 | 569 | |
| 496 | 570 | // Record in loop detector |
| 497 | 571 | inputJSON, _ := json.Marshal(in) |
| 498 | 572 | return record(d, "plan", string(inputJSON), result) |
| 499 | 573 | } |
| 500 | 574 | |
| 501 | -// ClearAllPlans clears all plans for the current session. | |
| 502 | -// Called when a new session starts (/new command). | |
| 503 | -func ClearAllPlans() { | |
| 504 | - planStore.ClearPlans() | |
| 575 | +// ClearPlans forgets the plans of a conversation: "" for the line mode and | |
| 576 | +// the window, the session id over ACP (ui.ConversationID). Plans are not | |
| 577 | +// saved in the session file, so every front end calls it when the | |
| 578 | +// conversation changes — /new, /resume, and over ACP session/load and | |
| 579 | +// session/resume (ticket 109): a new or restored conversation starts with | |
| 580 | +// no plan, never with the previous one's. | |
| 581 | +// | |
| 582 | +// tools.ClearPlans("") // the terminal's /new | |
| 583 | +// tools.ClearPlans(string(sid)) // an ACP session's /new | |
| 584 | +func ClearPlans(conversation string) { | |
| 585 | + planStore.ClearPlans(conversation) | |
| 505 | 586 | } |
| 506 | 587 | |
| 507 | 588 | // ClearAllPlansForTesting exposes ClearAllPlans for tests. |
| @@ -38,6 +38,7 @@ type Plan struct { | |||
| 38 | Tasks []PlanTask `json:"tasks"` | 38 | Tasks []PlanTask `json:"tasks"` |
| 39 | Current int `json:"current"` // 0 = not started, 1 = first task, etc. | 39 | Current int `json:"current"` // 0 = not started, 1 = first task, etc. |
| 40 | CreatedAt string `json:"createdAt"` // ISO timestamp | 40 | CreatedAt string `json:"createdAt"` // ISO timestamp |
| 41 | + order int // creation order in the store: CreatedAt has one-second precision | ||
| 41 | } | 42 | } |
| 42 | 43 | ||
| 43 | // PlanTask represents a single task within a plan. | 44 | // PlanTask represents a single task within a plan. |
| @@ -55,7 +56,7 @@ type planInput struct { | |||
| 55 | // Action is required: create, get, update, next, list, delete, status | 56 | // Action is required: create, get, update, next, list, delete, status |
| 56 | Action string `json:"action" jsonschema_description:"Required. One of: create, get, update, next, list, delete, status"` | 57 | Action string `json:"action" jsonschema_description:"Required. One of: create, get, update, next, list, delete, status"` |
| 57 | // Name is the plan identifier (default: 'default' for unnamed plans) | 58 | // Name is the plan identifier (default: 'default' for unnamed plans) |
| 58 | - Name string `json:"name,omitempty" jsonschema_description:"Plan name. Defaults to 'user-request' if not provided."` | 59 | + Name string `json:"name,omitempty" jsonschema_description:"Plan name. For create, optional (a name is chosen). For the other actions, optional: without it, the most recently created plan is used."` |
| 59 | // Tasks is used with 'create' action to define the plan's tasks | 60 | // Tasks is used with 'create' action to define the plan's tasks |
| 60 | Tasks []PlanTaskInput `json:"tasks,omitempty" jsonschema_description:"For create action: list of tasks to add"` | 61 | Tasks []PlanTaskInput `json:"tasks,omitempty" jsonschema_description:"For create action: list of tasks to add"` |
| 61 | // TaskID identifies which task for update/next actions (1-based) | 62 | // TaskID identifies which task for update/next actions (1-based) |
| @@ -91,6 +92,7 @@ type PlanStore struct { | |||
| 91 | mu sync.RWMutex | 92 | mu sync.RWMutex |
| 92 | plans map[string]map[string]*Plan // sessionID -> planName -> Plan | 93 | plans map[string]map[string]*Plan // sessionID -> planName -> Plan |
| 93 | nextID map[string]int // sessionID -> next task ID | 94 | nextID map[string]int // sessionID -> next task ID |
| 95 | + made int // plans created so far, for Plan.order | ||
| 94 | } | 96 | } |
| 95 | 97 | ||
| 96 | var planStore = &PlanStore{ | 98 | var planStore = &PlanStore{ |
| @@ -98,14 +100,13 @@ var planStore = &PlanStore{ | |||
| 98 | nextID: make(map[string]int), | 100 | nextID: make(map[string]int), |
| 99 | } | 101 | } |
| 100 | 102 | ||
| 101 | -// getSessionID extracts a session identifier from the tool context. | 103 | +// getSessionID is the conversation the plan call belongs to (ticket 108): the |
| 102 | -// For simplicity in v1, we use a placeholder since the actual session ID | 104 | +// ACP session id, or "" in the line mode and the window, which hold one |
| 103 | -// isn't easily accessible from tool context. In practice, plans are cleared | 105 | +// conversation at a time. It used to be a constant, so every thread an |
| 104 | -// on /new command via ClearPlans(). | 106 | +// editor opened in one mm process saw — and could change — the others' |
| 107 | +// plans. | ||
| 105 | func getSessionID() string { | 108 | func getSessionID() string { |
| 106 | - // v1: use a single session for simplicity | 109 | + return ui.ConversationID() |
| 107 | - // v2: extract from context when available | ||
| 108 | - return "current-session" | ||
| 109 | } | 110 | } |
| 110 | 111 | ||
| 111 | // newPlanName generates a unique plan name if none is provided. | 112 | // newPlanName generates a unique plan name if none is provided. |
| @@ -153,11 +154,13 @@ func (s *PlanStore) CreatePlan(name string, tasks []PlanTaskInput) (*Plan, strin | |||
| 153 | } | 154 | } |
| 154 | } | 155 | } |
| 155 | 156 | ||
| 157 | + s.made++ | ||
| 156 | plan := &Plan{ | 158 | plan := &Plan{ |
| 157 | Name: name, | 159 | Name: name, |
| 158 | Tasks: planTasks, | 160 | Tasks: planTasks, |
| 159 | Current: 0, | 161 | Current: 0, |
| 160 | CreatedAt: time.Now().UTC().Format(time.RFC3339), | 162 | CreatedAt: time.Now().UTC().Format(time.RFC3339), |
| 163 | + order: s.made, | ||
| 161 | } | 164 | } |
| 162 | 165 | ||
| 163 | s.plans[sessionID][name] = plan | 166 | s.plans[sessionID][name] = plan |
| @@ -246,6 +249,9 @@ func (s *PlanStore) UpdateTask(name string, taskID int, status string, message s | |||
| 246 | task := &plan.Tasks[taskIndex] | 249 | task := &plan.Tasks[taskIndex] |
| 247 | task.Status = status | 250 | task.Status = status |
| 248 | task.Notes = message | 251 | task.Notes = message |
| 252 | + if status == "in_progress" { | ||
| 253 | + plan.Current = taskIndex + 1 // so that a later `next` moves on from here | ||
| 254 | + } | ||
| 249 | 255 | ||
| 250 | // Set timestamps | 256 | // Set timestamps |
| 251 | now := time.Now().UTC().Format(time.RFC3339) | 257 | now := time.Now().UTC().Format(time.RFC3339) |
| @@ -286,8 +292,12 @@ func (s *PlanStore) NextTask(name string) (*Plan, *PlanTask, string, error) { | |||
| 286 | plan.Tasks[plan.Current-1].CompletedAt = time.Now().UTC().Format(time.RFC3339) | 292 | plan.Tasks[plan.Current-1].CompletedAt = time.Now().UTC().Format(time.RFC3339) |
| 287 | } | 293 | } |
| 288 | 294 | ||
| 289 | - // Move to next | 295 | + // Move to the next task that still needs work: a task the model already |
| 296 | + // marked done with `update` is skipped, never started again. | ||
| 290 | plan.Current++ | 297 | plan.Current++ |
| 298 | + for plan.Current <= len(plan.Tasks) && finished(plan.Tasks[plan.Current-1]) { | ||
| 299 | + plan.Current++ | ||
| 300 | + } | ||
| 291 | 301 | ||
| 292 | // Return next task | 302 | // Return next task |
| 293 | if plan.Current <= len(plan.Tasks) { | 303 | if plan.Current <= len(plan.Tasks) { |
| @@ -345,28 +355,42 @@ func (s *PlanStore) GetStatus(name string) (string, string, error) { | |||
| 345 | } | 355 | } |
| 346 | } | 356 | } |
| 347 | 357 | ||
| 348 | - // Format status line | 358 | + // The line shows the task being worked on — the first in progress, else |
| 349 | - // Current=0 means not started yet, Task 1 is next | 359 | + // the first still open — whatever the model used to get there: `update` |
| 350 | - // Current>0 means we've started, Task Current is the next one | 360 | + // does not move plan.Current, so following Current alone showed task 1 |
| 351 | - if plan.Current == 0 { | 361 | + // for a plan whose task 1 was done and task 2 in progress (ticket 110). |
| 352 | - // Not started, show Task 1 | 362 | + summary := fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)) |
| 353 | - task := plan.Tasks[0] | 363 | + if i := workingTask(plan); i >= 0 { |
| 354 | - return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", 1, len(plan.Tasks), statusIcon(task.Status), 1, task.Description, task.Status), | 364 | + task := plan.Tasks[i] |
| 355 | - fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil | 365 | + return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", i+1, len(plan.Tasks), statusIcon(task.Status), i+1, task.Description, task.Status), summary, nil |
| 356 | } | 366 | } |
| 357 | - | 367 | + if doneCount < len(plan.Tasks) { |
| 358 | - // Current > 0, show Task Current | 368 | + failed := len(plan.Tasks) - doneCount |
| 359 | - if plan.Current <= len(plan.Tasks) { | 369 | + return fmt.Sprintf("[%d/%d] Plan finished: %d done, %d failed", len(plan.Tasks), len(plan.Tasks), doneCount, failed), summary, nil |
| 360 | - task := plan.Tasks[plan.Current-1] | ||
| 361 | - return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", plan.Current, len(plan.Tasks), statusIcon(task.Status), plan.Current, task.Description, task.Status), | ||
| 362 | - fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil | ||
| 363 | } | 370 | } |
| 364 | - | ||
| 365 | - // All tasks complete | ||
| 366 | return fmt.Sprintf("[%d/%d] All tasks complete!", len(plan.Tasks), len(plan.Tasks)), | 371 | return fmt.Sprintf("[%d/%d] All tasks complete!", len(plan.Tasks), len(plan.Tasks)), |
| 367 | fmt.Sprintf("Plan '%s': All %d tasks done ✅", name, len(plan.Tasks)), nil | 372 | fmt.Sprintf("Plan '%s': All %d tasks done ✅", name, len(plan.Tasks)), nil |
| 368 | } | 373 | } |
| 369 | 374 | ||
| 375 | +// workingTask is the index of the task a plan is at: the first in progress, | ||
| 376 | +// else the first still open; -1 when every task is done or failed. | ||
| 377 | +func workingTask(plan *Plan) int { | ||
| 378 | + for i, t := range plan.Tasks { | ||
| 379 | + if t.Status == "in_progress" { | ||
| 380 | + return i | ||
| 381 | + } | ||
| 382 | + } | ||
| 383 | + for i, t := range plan.Tasks { | ||
| 384 | + if t.Status == "open" { | ||
| 385 | + return i | ||
| 386 | + } | ||
| 387 | + } | ||
| 388 | + return -1 | ||
| 389 | +} | ||
| 390 | + | ||
| 391 | +// finished reports whether a task needs no more work. | ||
| 392 | +func finished(t PlanTask) bool { return t.Status == "done" || t.Status == "failed" } | ||
| 393 | + | ||
| 370 | // statusIcon is the display mark for a task status. | 394 | // statusIcon is the display mark for a task status. |
| 371 | func statusIcon(status string) string { | 395 | func statusIcon(status string) string { |
| 372 | switch status { | 396 | switch status { |
| @@ -381,13 +405,29 @@ func statusIcon(status string) string { | |||
| 381 | } | 405 | } |
| 382 | } | 406 | } |
| 383 | 407 | ||
| 384 | -// ClearPlans removes all plans for the current session. | 408 | +// LatestName is the name of the conversation's most recently created plan, |
| 385 | -// Called when /new command is issued. | 409 | +// "" when it has none. It is what an action without a name means: a small |
| 386 | -func (s *PlanStore) ClearPlans() { | 410 | +// model does not keep the name `create` returned — measured on Qwen3.5 4B |
| 387 | - sessionID := getSessionID() | 411 | +// (demos/plans, 2026-10-21), every `update` came without one, failed with |
| 412 | +// "no plan named ”", and the turn ended in runaway tool calls. | ||
| 413 | +func (s *PlanStore) LatestName() string { | ||
| 414 | + conversation := getSessionID() | ||
| 415 | + s.mu.RLock() | ||
| 416 | + defer s.mu.RUnlock() | ||
| 417 | + latest, order := "", -1 | ||
| 418 | + for name, p := range s.plans[conversation] { | ||
| 419 | + if p.order > order { | ||
| 420 | + latest, order = name, p.order | ||
| 421 | + } | ||
| 422 | + } | ||
| 423 | + return latest | ||
| 424 | +} | ||
| 425 | + | ||
| 426 | +// ClearPlans removes the plans of one conversation. | ||
| 427 | +func (s *PlanStore) ClearPlans(conversation string) { | ||
| 388 | s.mu.Lock() | 428 | s.mu.Lock() |
| 389 | defer s.mu.Unlock() | 429 | defer s.mu.Unlock() |
| 390 | - delete(s.plans, sessionID) | 430 | + delete(s.plans, conversation) |
| 391 | } | 431 | } |
| 392 | 432 | ||
| 393 | // ClearAllPlans removes all plans. Used for testing. | 433 | // ClearAllPlans removes all plans. Used for testing. |
| @@ -434,16 +474,23 @@ Always show the user the current plan status using the status action.` | |||
| 434 | // Terminal/REPL: announce the call | 474 | // Terminal/REPL: announce the call |
| 435 | spinner.Suspend(func() { | 475 | spinner.Suspend(func() { |
| 436 | printMu.Lock() | 476 | printMu.Lock() |
| 437 | - fmt.Printf("\n📋 plan: %s %s\n", in.Action, in.Name) | 477 | + fmt.Printf("\n📋 %s\n", planTitle(in)) |
| 438 | printMu.Unlock() | 478 | printMu.Unlock() |
| 439 | }) | 479 | }) |
| 440 | - if planChanges(in.Action) && !confirmed(tctx, "plan", fmt.Sprintf("plan: %s %s", in.Action, in.Name)) { | 480 | + if planChanges(in.Action) && !confirmed(tctx, "plan", planTitle(in)) { |
| 441 | inputJSON, _ := json.Marshal(in) | 481 | inputJSON, _ := json.Marshal(in) |
| 442 | return refuse(d, "plan", string(inputJSON), "[plan: command rejected by the user]") | 482 | return refuse(d, "plan", string(inputJSON), "[plan: command rejected by the user]") |
| 443 | } | 483 | } |
| 444 | 484 | ||
| 445 | spinner.Start("Running") | 485 | spinner.Start("Running") |
| 446 | result := runPlanAction(in, true) | 486 | result := runPlanAction(in, true) |
| 487 | + if line := progressLine(result); line != "" { | ||
| 488 | + spinner.Suspend(func() { | ||
| 489 | + printMu.Lock() | ||
| 490 | + fmt.Println(line) | ||
| 491 | + printMu.Unlock() | ||
| 492 | + }) | ||
| 493 | + } | ||
| 447 | 494 | ||
| 448 | // Record in loop detector | 495 | // Record in loop detector |
| 449 | inputJSON, _ := json.Marshal(in) | 496 | inputJSON, _ := json.Marshal(in) |
| @@ -451,6 +498,26 @@ Always show the user the current plan status using the status action.` | |||
| 451 | }) | 498 | }) |
| 452 | } | 499 | } |
| 453 | 500 | ||
| 501 | +// planTitle names a call: `plan: update user-request-1`, `plan: list`. | ||
| 502 | +func planTitle(in planInput) string { | ||
| 503 | + return strings.TrimSpace("plan: " + in.Action + " " + in.Name) | ||
| 504 | +} | ||
| 505 | + | ||
| 506 | +// progressLine is what mm shows the user after a plan action (ticket 110): | ||
| 507 | +// the plan's status line — `📋 [2/3] 🔄 Task 2: Add a license | ||
| 508 | +// (in_progress)` — taken from the result, "" when the action has none (get, | ||
| 509 | +// list, delete, an error). Shown by mm itself, so the progress does not | ||
| 510 | +// depend on the model repeating it. | ||
| 511 | +func progressLine(result string) string { | ||
| 512 | + var out struct { | ||
| 513 | + Status string `json:"status"` | ||
| 514 | + } | ||
| 515 | + if json.Unmarshal([]byte(result), &out) != nil || out.Status == "" { | ||
| 516 | + return "" | ||
| 517 | + } | ||
| 518 | + return "📋 " + out.Status | ||
| 519 | +} | ||
| 520 | + | ||
| 454 | // planChanges reports whether a plan action changes a plan — the ones that | 521 | // planChanges reports whether a plan action changes a plan — the ones that |
| 455 | // ask for permission when yolo is off. | 522 | // ask for permission when yolo is off. |
| 456 | func planChanges(action string) bool { | 523 | func planChanges(action string) bool { |
| @@ -467,7 +534,7 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl | |||
| 467 | ev := ui.ToolEvent{ | 534 | ev := ui.ToolEvent{ |
| 468 | ID: ui.NextCallID(), | 535 | ID: ui.NextCallID(), |
| 469 | Tool: "plan", | 536 | Tool: "plan", |
| 470 | - Title: fmt.Sprintf("plan: %s %s", in.Action, in.Name), | 537 | + Title: planTitle(in), |
| 471 | Kind: eventKind, | 538 | Kind: eventKind, |
| 472 | Input: map[string]any{"action": in.Action, "name": in.Name, "taskId": in.TaskID, "status": in.Status}, | 539 | Input: map[string]any{"action": in.Action, "name": in.Name, "taskId": in.TaskID, "status": in.Status}, |
| 473 | } | 540 | } |
| @@ -492,16 +559,30 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl | |||
| 492 | failed := strings.HasPrefix(result, "[error:") | 559 | failed := strings.HasPrefix(result, "[error:") |
| 493 | 560 | ||
| 494 | s.ToolEnd(ev.ID, ui.ToolResult{Output: result, Failed: failed}) | 561 | s.ToolEnd(ev.ID, ui.ToolResult{Output: result, Failed: failed}) |
| 562 | + // The window shows the progress as a notice; an editor has none, and | ||
| 563 | + // shows the status inside the tool call's result. | ||
| 564 | + if n, ok := s.(ui.Noticer); ok { | ||
| 565 | + if line := progressLine(result); line != "" { | ||
| 566 | + n.Notice(line) | ||
| 567 | + } | ||
| 568 | + } | ||
| 495 | 569 | ||
| 496 | // Record in loop detector | 570 | // Record in loop detector |
| 497 | inputJSON, _ := json.Marshal(in) | 571 | inputJSON, _ := json.Marshal(in) |
| 498 | return record(d, "plan", string(inputJSON), result) | 572 | return record(d, "plan", string(inputJSON), result) |
| 499 | } | 573 | } |
| 500 | 574 | ||
| 501 | -// ClearAllPlans clears all plans for the current session. | 575 | +// ClearPlans forgets the plans of a conversation: "" for the line mode and |
| 502 | -// Called when a new session starts (/new command). | 576 | +// the window, the session id over ACP (ui.ConversationID). Plans are not |
| 503 | -func ClearAllPlans() { | 577 | +// saved in the session file, so every front end calls it when the |
| 504 | - planStore.ClearPlans() | 578 | +// conversation changes — /new, /resume, and over ACP session/load and |
| 579 | +// session/resume (ticket 109): a new or restored conversation starts with | ||
| 580 | +// no plan, never with the previous one's. | ||
| 581 | +// | ||
| 582 | +// tools.ClearPlans("") // the terminal's /new | ||
| 583 | +// tools.ClearPlans(string(sid)) // an ACP session's /new | ||
| 584 | +func ClearPlans(conversation string) { | ||
| 585 | + planStore.ClearPlans(conversation) | ||
| 505 | } | 586 | } |
| 506 | 587 | ||
| 507 | // ClearAllPlansForTesting exposes ClearAllPlans for tests. | 588 | // ClearAllPlansForTesting exposes ClearAllPlans for tests. |
modified
internal/tools/plan_actions.go +5 -1 | @@ -25,6 +25,9 @@ func runPlanAction(in planInput, withPlan bool) string { | ||
| 25 | 25 | if in.Action == "create" { |
| 26 | 26 | return planCreate(in, withPlan) |
| 27 | 27 | } |
| 28 | + if in.Name == "" && in.Action != "list" { | |
| 29 | + in.Name = planStore.LatestName() | |
| 30 | + } | |
| 28 | 31 | if run, ok := planActions[in.Action]; ok { |
| 29 | 32 | return run(in) |
| 30 | 33 | } |
| @@ -47,7 +50,8 @@ func planCreate(in planInput, withPlan bool) string { | ||
| 47 | 50 | if err != nil { |
| 48 | 51 | return planError(err) |
| 49 | 52 | } |
| 50 | - out := PlanOutput{Result: msg, PlanName: plan.Name, Current: plan.Current, Total: len(plan.Tasks)} | |
| 53 | + statusLine, _, _ := planStore.GetStatus(plan.Name) | |
| 54 | + out := PlanOutput{Result: msg, PlanName: plan.Name, Status: statusLine, Current: plan.Current, Total: len(plan.Tasks)} | |
| 51 | 55 | if withPlan { |
| 52 | 56 | out.Plan = plan |
| 53 | 57 | } |
| @@ -25,6 +25,9 @@ func runPlanAction(in planInput, withPlan bool) string { | |||
| 25 | if in.Action == "create" { | 25 | if in.Action == "create" { |
| 26 | return planCreate(in, withPlan) | 26 | return planCreate(in, withPlan) |
| 27 | } | 27 | } |
| 28 | + if in.Name == "" && in.Action != "list" { | ||
| 29 | + in.Name = planStore.LatestName() | ||
| 30 | + } | ||
| 28 | if run, ok := planActions[in.Action]; ok { | 31 | if run, ok := planActions[in.Action]; ok { |
| 29 | return run(in) | 32 | return run(in) |
| 30 | } | 33 | } |
| @@ -47,7 +50,8 @@ func planCreate(in planInput, withPlan bool) string { | |||
| 47 | if err != nil { | 50 | if err != nil { |
| 48 | return planError(err) | 51 | return planError(err) |
| 49 | } | 52 | } |
| 50 | - out := PlanOutput{Result: msg, PlanName: plan.Name, Current: plan.Current, Total: len(plan.Tasks)} | 53 | + statusLine, _, _ := planStore.GetStatus(plan.Name) |
| 54 | + out := PlanOutput{Result: msg, PlanName: plan.Name, Status: statusLine, Current: plan.Current, Total: len(plan.Tasks)} | ||
| 51 | if withPlan { | 55 | if withPlan { |
| 52 | out.Plan = plan | 56 | out.Plan = plan |
| 53 | } | 57 | } |
modified
internal/tools/plan_actions_test.go +58 -2 | @@ -2,6 +2,7 @@ package tools | ||
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "encoding/json" |
| 5 | + "strconv" | |
| 5 | 6 | "strings" |
| 6 | 7 | "testing" |
| 7 | 8 | ) |
| @@ -29,8 +30,10 @@ func TestRunPlanAction(t *testing.T) { | ||
| 29 | 30 | }{ |
| 30 | 31 | {planInput{Action: "get", Name: "p1"}, `"plan":`}, |
| 31 | 32 | {planInput{Action: "update", Name: "p1", TaskID: 1, Status: "in_progress"}, `"status":"[1/2]`}, |
| 32 | - {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":1,"description":"say \"hi\""}`}, | |
| 33 | - {planInput{Action: "status", Name: "p1"}, `"status":"[1/2]`}, | |
| 33 | + // next ends the task in progress (1) and starts the following one — | |
| 34 | + // it used to start task 1 again (ticket 110). | |
| 35 | + {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":2,"description":"B"}`}, | |
| 36 | + {planInput{Action: "status", Name: "p1"}, `"status":"[2/2] 🔄 Task 2: B (in_progress)"`}, | |
| 34 | 37 | {planInput{Action: "list"}, `"plans":["`}, |
| 35 | 38 | {planInput{Action: "delete", Name: "p2"}, `{"result":"`}, |
| 36 | 39 | {planInput{Action: "get", Name: "p2"}, "[error: "}, |
| @@ -83,3 +86,56 @@ func TestPlanUpdateWithAnUnknownStatus(t *testing.T) { | ||
| 83 | 86 | t.Errorf("answer %s lacks the default example", got) |
| 84 | 87 | } |
| 85 | 88 | } |
| 89 | + | |
| 90 | +// A description with quotes stays valid JSON in next's nextTask. | |
| 91 | +func TestPlanNextEscapesTheDescription(t *testing.T) { | |
| 92 | + ClearAllPlansForTesting() | |
| 93 | + t.Cleanup(ClearAllPlansForTesting) | |
| 94 | + runPlanAction(planInput{Action: "create", Name: "q", Tasks: []PlanTaskInput{{Description: `say "hi"`}}}, true) | |
| 95 | + if got := runPlanAction(planInput{Action: "next", Name: "q"}, true); !strings.Contains(got, `"nextTask":{"id":1,"description":"say \"hi\""}`) { | |
| 96 | + t.Errorf("next: %s", got) | |
| 97 | + } | |
| 98 | +} | |
| 99 | + | |
| 100 | +// The status line follows the tasks, however the model moved through them: | |
| 101 | +// marking task 1 done with update, then next, starts task 2 (it used to put | |
| 102 | +// the finished task 1 back in progress); a plan whose tasks are all over | |
| 103 | +// with failures says so. | |
| 104 | +func TestPlanStatusFollowsTheTasks(t *testing.T) { | |
| 105 | + ClearAllPlansForTesting() | |
| 106 | + t.Cleanup(ClearAllPlansForTesting) | |
| 107 | + runPlanAction(planInput{Action: "create", Name: "s", Tasks: []PlanTaskInput{{Description: "one"}, {Description: "two"}}}, true) | |
| 108 | + for _, c := range []struct { | |
| 109 | + in planInput | |
| 110 | + want string | |
| 111 | + }{ | |
| 112 | + {planInput{Action: "update", Name: "s", TaskID: 1, Status: "done"}, "[2/2] ⏳ Task 2: two (open)"}, | |
| 113 | + {planInput{Action: "next", Name: "s"}, "[2/2] 🔄 Task 2: two (in_progress)"}, | |
| 114 | + {planInput{Action: "update", Name: "s", TaskID: 2, Status: "failed"}, "[2/2] Plan finished: 1 done, 1 failed"}, | |
| 115 | + {planInput{Action: "update", Name: "s", TaskID: 2, Status: "done"}, "[2/2] All tasks complete!"}, | |
| 116 | + } { | |
| 117 | + if got := runPlanAction(c.in, true); !strings.Contains(got, `"status":"`+c.want+`"`) { | |
| 118 | + t.Errorf("%s %d %s: %s, want status %q", c.in.Action, c.in.TaskID, c.in.Status, got, c.want) | |
| 119 | + } | |
| 120 | + } | |
| 121 | +} | |
| 122 | + | |
| 123 | +// An action without a name works on the conversation's latest plan — a | |
| 124 | +// small model does not keep the name create returned (demos/plans). | |
| 125 | +func TestPlanActionWithoutANameUsesTheLatestPlan(t *testing.T) { | |
| 126 | + ClearAllPlansForTesting() | |
| 127 | + t.Cleanup(ClearAllPlansForTesting) | |
| 128 | + if got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "done"}, true); !strings.Contains(got, "[error: no plans found") { | |
| 129 | + t.Errorf("no plan yet: %s", got) | |
| 130 | + } | |
| 131 | + for i := range 11 { // past user-request-9, where names stop sorting by number | |
| 132 | + runPlanAction(planInput{Action: "create", Tasks: []PlanTaskInput{{Description: "t" + strconv.Itoa(i)}}}, true) | |
| 133 | + } | |
| 134 | + got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "in_progress"}, true) | |
| 135 | + if !strings.Contains(got, "in plan 'user-request-11'") || !strings.Contains(got, "Task 1: t10 (in_progress)") { | |
| 136 | + t.Errorf("update without a name: %s", got) | |
| 137 | + } | |
| 138 | + if got := runPlanAction(planInput{Action: "status"}, true); !strings.Contains(got, "user-request-11") { | |
| 139 | + t.Errorf("status without a name: %s", got) | |
| 140 | + } | |
| 141 | +} | |
| @@ -2,6 +2,7 @@ package tools | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | 4 | "encoding/json" |
| 5 | + "strconv" | ||
| 5 | "strings" | 6 | "strings" |
| 6 | "testing" | 7 | "testing" |
| 7 | ) | 8 | ) |
| @@ -29,8 +30,10 @@ func TestRunPlanAction(t *testing.T) { | |||
| 29 | }{ | 30 | }{ |
| 30 | {planInput{Action: "get", Name: "p1"}, `"plan":`}, | 31 | {planInput{Action: "get", Name: "p1"}, `"plan":`}, |
| 31 | {planInput{Action: "update", Name: "p1", TaskID: 1, Status: "in_progress"}, `"status":"[1/2]`}, | 32 | {planInput{Action: "update", Name: "p1", TaskID: 1, Status: "in_progress"}, `"status":"[1/2]`}, |
| 32 | - {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":1,"description":"say \"hi\""}`}, | 33 | + // next ends the task in progress (1) and starts the following one — |
| 33 | - {planInput{Action: "status", Name: "p1"}, `"status":"[1/2]`}, | 34 | + // it used to start task 1 again (ticket 110). |
| 35 | + {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":2,"description":"B"}`}, | ||
| 36 | + {planInput{Action: "status", Name: "p1"}, `"status":"[2/2] 🔄 Task 2: B (in_progress)"`}, | ||
| 34 | {planInput{Action: "list"}, `"plans":["`}, | 37 | {planInput{Action: "list"}, `"plans":["`}, |
| 35 | {planInput{Action: "delete", Name: "p2"}, `{"result":"`}, | 38 | {planInput{Action: "delete", Name: "p2"}, `{"result":"`}, |
| 36 | {planInput{Action: "get", Name: "p2"}, "[error: "}, | 39 | {planInput{Action: "get", Name: "p2"}, "[error: "}, |
| @@ -83,3 +86,56 @@ func TestPlanUpdateWithAnUnknownStatus(t *testing.T) { | |||
| 83 | t.Errorf("answer %s lacks the default example", got) | 86 | t.Errorf("answer %s lacks the default example", got) |
| 84 | } | 87 | } |
| 85 | } | 88 | } |
| 89 | + | ||
| 90 | +// A description with quotes stays valid JSON in next's nextTask. | ||
| 91 | +func TestPlanNextEscapesTheDescription(t *testing.T) { | ||
| 92 | + ClearAllPlansForTesting() | ||
| 93 | + t.Cleanup(ClearAllPlansForTesting) | ||
| 94 | + runPlanAction(planInput{Action: "create", Name: "q", Tasks: []PlanTaskInput{{Description: `say "hi"`}}}, true) | ||
| 95 | + if got := runPlanAction(planInput{Action: "next", Name: "q"}, true); !strings.Contains(got, `"nextTask":{"id":1,"description":"say \"hi\""}`) { | ||
| 96 | + t.Errorf("next: %s", got) | ||
| 97 | + } | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +// The status line follows the tasks, however the model moved through them: | ||
| 101 | +// marking task 1 done with update, then next, starts task 2 (it used to put | ||
| 102 | +// the finished task 1 back in progress); a plan whose tasks are all over | ||
| 103 | +// with failures says so. | ||
| 104 | +func TestPlanStatusFollowsTheTasks(t *testing.T) { | ||
| 105 | + ClearAllPlansForTesting() | ||
| 106 | + t.Cleanup(ClearAllPlansForTesting) | ||
| 107 | + runPlanAction(planInput{Action: "create", Name: "s", Tasks: []PlanTaskInput{{Description: "one"}, {Description: "two"}}}, true) | ||
| 108 | + for _, c := range []struct { | ||
| 109 | + in planInput | ||
| 110 | + want string | ||
| 111 | + }{ | ||
| 112 | + {planInput{Action: "update", Name: "s", TaskID: 1, Status: "done"}, "[2/2] ⏳ Task 2: two (open)"}, | ||
| 113 | + {planInput{Action: "next", Name: "s"}, "[2/2] 🔄 Task 2: two (in_progress)"}, | ||
| 114 | + {planInput{Action: "update", Name: "s", TaskID: 2, Status: "failed"}, "[2/2] Plan finished: 1 done, 1 failed"}, | ||
| 115 | + {planInput{Action: "update", Name: "s", TaskID: 2, Status: "done"}, "[2/2] All tasks complete!"}, | ||
| 116 | + } { | ||
| 117 | + if got := runPlanAction(c.in, true); !strings.Contains(got, `"status":"`+c.want+`"`) { | ||
| 118 | + t.Errorf("%s %d %s: %s, want status %q", c.in.Action, c.in.TaskID, c.in.Status, got, c.want) | ||
| 119 | + } | ||
| 120 | + } | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +// An action without a name works on the conversation's latest plan — a | ||
| 124 | +// small model does not keep the name create returned (demos/plans). | ||
| 125 | +func TestPlanActionWithoutANameUsesTheLatestPlan(t *testing.T) { | ||
| 126 | + ClearAllPlansForTesting() | ||
| 127 | + t.Cleanup(ClearAllPlansForTesting) | ||
| 128 | + if got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "done"}, true); !strings.Contains(got, "[error: no plans found") { | ||
| 129 | + t.Errorf("no plan yet: %s", got) | ||
| 130 | + } | ||
| 131 | + for i := range 11 { // past user-request-9, where names stop sorting by number | ||
| 132 | + runPlanAction(planInput{Action: "create", Tasks: []PlanTaskInput{{Description: "t" + strconv.Itoa(i)}}}, true) | ||
| 133 | + } | ||
| 134 | + got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "in_progress"}, true) | ||
| 135 | + if !strings.Contains(got, "in plan 'user-request-11'") || !strings.Contains(got, "Task 1: t10 (in_progress)") { | ||
| 136 | + t.Errorf("update without a name: %s", got) | ||
| 137 | + } | ||
| 138 | + if got := runPlanAction(planInput{Action: "status"}, true); !strings.Contains(got, "user-request-11") { | ||
| 139 | + t.Errorf("status without a name: %s", got) | ||
| 140 | + } | ||
| 141 | +} | ||
added
internal/tools/plan_conversation_test.go +131 -0 | new file mode 100644 | ||
| @@ -0,0 +1,131 @@ | ||
| 1 | +package tools | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "context" | |
| 5 | + "io" | |
| 6 | + "os" | |
| 7 | + "strings" | |
| 8 | + "testing" | |
| 9 | + | |
| 10 | + "mm/internal/detector" | |
| 11 | + "mm/internal/ui" | |
| 12 | + | |
| 13 | + "github.com/firebase/genkit/go/genkit" | |
| 14 | +) | |
| 15 | + | |
| 16 | +// threadSink is an ACP-like sink: one conversation among several, with no | |
| 17 | +// notices (an editor shows the status inside the tool call's result). | |
| 18 | +type threadSink struct { | |
| 19 | + recordingSink | |
| 20 | + id string | |
| 21 | +} | |
| 22 | + | |
| 23 | +func (k *threadSink) ConversationID() string { return k.id } | |
| 24 | + | |
| 25 | +// windowSink is a window-like sink: one conversation, and notices. | |
| 26 | +type windowSink struct { | |
| 27 | + recordingSink | |
| 28 | + notices []string | |
| 29 | +} | |
| 30 | + | |
| 31 | +func (k *windowSink) Notice(text string) { k.notices = append(k.notices, text) } | |
| 32 | + | |
| 33 | +func runPlan(t *testing.T, g *genkit.Genkit, sink ui.Sink, input map[string]any) string { | |
| 34 | + t.Helper() | |
| 35 | + ui.SetActive(sink) | |
| 36 | + defer ui.SetActive(nil) | |
| 37 | + out, err := genkit.LookupTool(g, "plan").RunRaw(context.Background(), input) | |
| 38 | + if err != nil { | |
| 39 | + t.Fatal(err) | |
| 40 | + } | |
| 41 | + return out.(string) | |
| 42 | +} | |
| 43 | + | |
| 44 | +// Ticket 108: two editor threads served by one process keep their own plans | |
| 45 | +// — names, lists and reads never cross — and clearing one thread's plans | |
| 46 | +// leaves the other's. | |
| 47 | +func TestPlansArePerConversation(t *testing.T) { | |
| 48 | + ClearAllPlansForTesting() | |
| 49 | + t.Cleanup(ClearAllPlansForTesting) | |
| 50 | + g := genkit.Init(context.Background()) | |
| 51 | + PlanTool(g, detector.NewLoopDetector(10, 3)) | |
| 52 | + a, b := &threadSink{id: "thread-a"}, &threadSink{id: "thread-b"} | |
| 53 | + tasks := []any{map[string]any{"description": "one"}, map[string]any{"description": "two"}} | |
| 54 | + | |
| 55 | + if out := runPlan(t, g, a, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) { | |
| 56 | + t.Fatalf("thread A create: %s", out) | |
| 57 | + } | |
| 58 | + if out := runPlan(t, g, b, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) { | |
| 59 | + t.Errorf("thread B's first plan is not user-request-1 — the names count A's plans: %s", out) | |
| 60 | + } | |
| 61 | + runPlan(t, g, b, map[string]any{"action": "create", "name": "only-in-b", "tasks": tasks}) | |
| 62 | + if out := runPlan(t, g, a, map[string]any{"action": "list"}); strings.Contains(out, "only-in-b") || !strings.Contains(out, "Found 1 plan(s)") { | |
| 63 | + t.Errorf("thread A lists B's plans: %s", out) | |
| 64 | + } | |
| 65 | + if out := runPlan(t, g, a, map[string]any{"action": "get", "name": "only-in-b"}); !strings.Contains(out, "no plan named 'only-in-b' found") { | |
| 66 | + t.Errorf("thread A reads B's plan: %s", out) | |
| 67 | + } | |
| 68 | + | |
| 69 | + ClearPlans("thread-a") | |
| 70 | + if out := runPlan(t, g, a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") { | |
| 71 | + t.Errorf("thread A after its /new: %s", out) | |
| 72 | + } | |
| 73 | + if out := runPlan(t, g, b, map[string]any{"action": "list"}); !strings.Contains(out, "Found 2 plan(s)") { | |
| 74 | + t.Errorf("clearing A touched B: %s", out) | |
| 75 | + } | |
| 76 | +} | |
| 77 | + | |
| 78 | +// Ticket 110: mm shows the plan's progress itself — the status line, after | |
| 79 | +// every action that has one, create included — as a notice in the window. | |
| 80 | +// An editor's sink has no notices: nothing extra is sent there. | |
| 81 | +func TestPlanProgressIsShownByMm(t *testing.T) { | |
| 82 | + ClearAllPlansForTesting() | |
| 83 | + t.Cleanup(ClearAllPlansForTesting) | |
| 84 | + g := genkit.Init(context.Background()) | |
| 85 | + PlanTool(g, detector.NewLoopDetector(10, 3)) | |
| 86 | + w := &windowSink{} | |
| 87 | + tasks := []any{map[string]any{"description": "Create a README"}, map[string]any{"description": "Add a license"}} | |
| 88 | + | |
| 89 | + runPlan(t, g, w, map[string]any{"action": "create", "name": "p", "tasks": tasks}) | |
| 90 | + runPlan(t, g, w, map[string]any{"action": "next", "name": "p"}) | |
| 91 | + runPlan(t, g, w, map[string]any{"action": "next", "name": "p"}) | |
| 92 | + runPlan(t, g, w, map[string]any{"action": "list"}) | |
| 93 | + want := []string{ | |
| 94 | + "📋 [1/2] ⏳ Task 1: Create a README (open)", | |
| 95 | + "📋 [1/2] 🔄 Task 1: Create a README (in_progress)", // next starts task 1 | |
| 96 | + "📋 [2/2] 🔄 Task 2: Add a license (in_progress)", // next: 1 done, 2 started | |
| 97 | + } | |
| 98 | + if strings.Join(w.notices, "\n") != strings.Join(want, "\n") { | |
| 99 | + t.Errorf("notices:\n%s\nwant:\n%s", strings.Join(w.notices, "\n"), strings.Join(want, "\n")) | |
| 100 | + } | |
| 101 | + if got := progressLine(`[error: no plan named 'x' found]`); got != "" { | |
| 102 | + t.Errorf("an error gave a progress line: %q", got) | |
| 103 | + } | |
| 104 | +} | |
| 105 | + | |
| 106 | +// In the line mode (no sink) the progress line is printed after the call | |
| 107 | +// line, on stdout. | |
| 108 | +func TestPlanProgressLineInTheLineMode(t *testing.T) { | |
| 109 | + ClearAllPlansForTesting() | |
| 110 | + t.Cleanup(ClearAllPlansForTesting) | |
| 111 | + yoloOn(t) | |
| 112 | + g := genkit.Init(context.Background()) | |
| 113 | + PlanTool(g, detector.NewLoopDetector(10, 3)) | |
| 114 | + | |
| 115 | + r, w, err := os.Pipe() | |
| 116 | + if err != nil { | |
| 117 | + t.Fatal(err) | |
| 118 | + } | |
| 119 | + stdout := os.Stdout | |
| 120 | + os.Stdout = w | |
| 121 | + _, err = genkit.LookupTool(g, "plan").RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "Write the README"}}}) | |
| 122 | + os.Stdout = stdout | |
| 123 | + _ = w.Close() | |
| 124 | + if err != nil { | |
| 125 | + t.Fatal(err) | |
| 126 | + } | |
| 127 | + printed, _ := io.ReadAll(r) | |
| 128 | + if got := string(printed); !strings.Contains(got, "📋 plan: create p\n") || !strings.Contains(got, "📋 [1/1] ⏳ Task 1: Write the README (open)\n") { | |
| 129 | + t.Errorf("printed:\n%s", got) | |
| 130 | + } | |
| 131 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | +package tools | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "context" | ||
| 5 | + "io" | ||
| 6 | + "os" | ||
| 7 | + "strings" | ||
| 8 | + "testing" | ||
| 9 | + | ||
| 10 | + "mm/internal/detector" | ||
| 11 | + "mm/internal/ui" | ||
| 12 | + | ||
| 13 | + "github.com/firebase/genkit/go/genkit" | ||
| 14 | +) | ||
| 15 | + | ||
| 16 | +// threadSink is an ACP-like sink: one conversation among several, with no | ||
| 17 | +// notices (an editor shows the status inside the tool call's result). | ||
| 18 | +type threadSink struct { | ||
| 19 | + recordingSink | ||
| 20 | + id string | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +func (k *threadSink) ConversationID() string { return k.id } | ||
| 24 | + | ||
| 25 | +// windowSink is a window-like sink: one conversation, and notices. | ||
| 26 | +type windowSink struct { | ||
| 27 | + recordingSink | ||
| 28 | + notices []string | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +func (k *windowSink) Notice(text string) { k.notices = append(k.notices, text) } | ||
| 32 | + | ||
| 33 | +func runPlan(t *testing.T, g *genkit.Genkit, sink ui.Sink, input map[string]any) string { | ||
| 34 | + t.Helper() | ||
| 35 | + ui.SetActive(sink) | ||
| 36 | + defer ui.SetActive(nil) | ||
| 37 | + out, err := genkit.LookupTool(g, "plan").RunRaw(context.Background(), input) | ||
| 38 | + if err != nil { | ||
| 39 | + t.Fatal(err) | ||
| 40 | + } | ||
| 41 | + return out.(string) | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +// Ticket 108: two editor threads served by one process keep their own plans | ||
| 45 | +// — names, lists and reads never cross — and clearing one thread's plans | ||
| 46 | +// leaves the other's. | ||
| 47 | +func TestPlansArePerConversation(t *testing.T) { | ||
| 48 | + ClearAllPlansForTesting() | ||
| 49 | + t.Cleanup(ClearAllPlansForTesting) | ||
| 50 | + g := genkit.Init(context.Background()) | ||
| 51 | + PlanTool(g, detector.NewLoopDetector(10, 3)) | ||
| 52 | + a, b := &threadSink{id: "thread-a"}, &threadSink{id: "thread-b"} | ||
| 53 | + tasks := []any{map[string]any{"description": "one"}, map[string]any{"description": "two"}} | ||
| 54 | + | ||
| 55 | + if out := runPlan(t, g, a, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) { | ||
| 56 | + t.Fatalf("thread A create: %s", out) | ||
| 57 | + } | ||
| 58 | + if out := runPlan(t, g, b, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) { | ||
| 59 | + t.Errorf("thread B's first plan is not user-request-1 — the names count A's plans: %s", out) | ||
| 60 | + } | ||
| 61 | + runPlan(t, g, b, map[string]any{"action": "create", "name": "only-in-b", "tasks": tasks}) | ||
| 62 | + if out := runPlan(t, g, a, map[string]any{"action": "list"}); strings.Contains(out, "only-in-b") || !strings.Contains(out, "Found 1 plan(s)") { | ||
| 63 | + t.Errorf("thread A lists B's plans: %s", out) | ||
| 64 | + } | ||
| 65 | + if out := runPlan(t, g, a, map[string]any{"action": "get", "name": "only-in-b"}); !strings.Contains(out, "no plan named 'only-in-b' found") { | ||
| 66 | + t.Errorf("thread A reads B's plan: %s", out) | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + ClearPlans("thread-a") | ||
| 70 | + if out := runPlan(t, g, a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") { | ||
| 71 | + t.Errorf("thread A after its /new: %s", out) | ||
| 72 | + } | ||
| 73 | + if out := runPlan(t, g, b, map[string]any{"action": "list"}); !strings.Contains(out, "Found 2 plan(s)") { | ||
| 74 | + t.Errorf("clearing A touched B: %s", out) | ||
| 75 | + } | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +// Ticket 110: mm shows the plan's progress itself — the status line, after | ||
| 79 | +// every action that has one, create included — as a notice in the window. | ||
| 80 | +// An editor's sink has no notices: nothing extra is sent there. | ||
| 81 | +func TestPlanProgressIsShownByMm(t *testing.T) { | ||
| 82 | + ClearAllPlansForTesting() | ||
| 83 | + t.Cleanup(ClearAllPlansForTesting) | ||
| 84 | + g := genkit.Init(context.Background()) | ||
| 85 | + PlanTool(g, detector.NewLoopDetector(10, 3)) | ||
| 86 | + w := &windowSink{} | ||
| 87 | + tasks := []any{map[string]any{"description": "Create a README"}, map[string]any{"description": "Add a license"}} | ||
| 88 | + | ||
| 89 | + runPlan(t, g, w, map[string]any{"action": "create", "name": "p", "tasks": tasks}) | ||
| 90 | + runPlan(t, g, w, map[string]any{"action": "next", "name": "p"}) | ||
| 91 | + runPlan(t, g, w, map[string]any{"action": "next", "name": "p"}) | ||
| 92 | + runPlan(t, g, w, map[string]any{"action": "list"}) | ||
| 93 | + want := []string{ | ||
| 94 | + "📋 [1/2] ⏳ Task 1: Create a README (open)", | ||
| 95 | + "📋 [1/2] 🔄 Task 1: Create a README (in_progress)", // next starts task 1 | ||
| 96 | + "📋 [2/2] 🔄 Task 2: Add a license (in_progress)", // next: 1 done, 2 started | ||
| 97 | + } | ||
| 98 | + if strings.Join(w.notices, "\n") != strings.Join(want, "\n") { | ||
| 99 | + t.Errorf("notices:\n%s\nwant:\n%s", strings.Join(w.notices, "\n"), strings.Join(want, "\n")) | ||
| 100 | + } | ||
| 101 | + if got := progressLine(`[error: no plan named 'x' found]`); got != "" { | ||
| 102 | + t.Errorf("an error gave a progress line: %q", got) | ||
| 103 | + } | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +// In the line mode (no sink) the progress line is printed after the call | ||
| 107 | +// line, on stdout. | ||
| 108 | +func TestPlanProgressLineInTheLineMode(t *testing.T) { | ||
| 109 | + ClearAllPlansForTesting() | ||
| 110 | + t.Cleanup(ClearAllPlansForTesting) | ||
| 111 | + yoloOn(t) | ||
| 112 | + g := genkit.Init(context.Background()) | ||
| 113 | + PlanTool(g, detector.NewLoopDetector(10, 3)) | ||
| 114 | + | ||
| 115 | + r, w, err := os.Pipe() | ||
| 116 | + if err != nil { | ||
| 117 | + t.Fatal(err) | ||
| 118 | + } | ||
| 119 | + stdout := os.Stdout | ||
| 120 | + os.Stdout = w | ||
| 121 | + _, err = genkit.LookupTool(g, "plan").RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "Write the README"}}}) | ||
| 122 | + os.Stdout = stdout | ||
| 123 | + _ = w.Close() | ||
| 124 | + if err != nil { | ||
| 125 | + t.Fatal(err) | ||
| 126 | + } | ||
| 127 | + printed, _ := io.ReadAll(r) | ||
| 128 | + if got := string(printed); !strings.Contains(got, "📋 plan: create p\n") || !strings.Contains(got, "📋 [1/1] ⏳ Task 1: Write the README (open)\n") { | ||
| 129 | + t.Errorf("printed:\n%s", got) | ||
| 130 | + } | ||
| 131 | +} | ||
modified
internal/tools/plan_test.go +1 -1 | @@ -429,7 +429,7 @@ func TestPlanStore_ClearPlans(t *testing.T) { | ||
| 429 | 429 | } |
| 430 | 430 | |
| 431 | 431 | // Clear plans |
| 432 | - store.ClearPlans() | |
| 432 | + store.ClearPlans("") | |
| 433 | 433 | |
| 434 | 434 | // Verify plans cleared |
| 435 | 435 | plans, _, _ = store.ListPlans() |
| @@ -429,7 +429,7 @@ func TestPlanStore_ClearPlans(t *testing.T) { | |||
| 429 | } | 429 | } |
| 430 | 430 | ||
| 431 | // Clear plans | 431 | // Clear plans |
| 432 | - store.ClearPlans() | 432 | + store.ClearPlans("") |
| 433 | 433 | ||
| 434 | // Verify plans cleared | 434 | // Verify plans cleared |
| 435 | plans, _, _ = store.ListPlans() | 435 | plans, _, _ = store.ListPlans() |
modified
internal/tui/commands.go +3 -0 | @@ -13,6 +13,7 @@ import ( | ||
| 13 | 13 | "mm/internal/docsearch" |
| 14 | 14 | "mm/internal/session" |
| 15 | 15 | "mm/internal/store" |
| 16 | + "mm/internal/tools" | |
| 16 | 17 | ) |
| 17 | 18 | |
| 18 | 19 | // submit is what Enter does with the box: a slash command or a question. |
| @@ -159,6 +160,7 @@ func (r *runner) newSession() { | ||
| 159 | 160 | r.opts.Model.ForgetInputTokens() |
| 160 | 161 | r.opts.Detector.Reset() |
| 161 | 162 | r.keep.Renew() |
| 163 | + tools.ClearPlans("") // plans belong to the conversation just closed (ticket 109) | |
| 162 | 164 | r.t.Notice(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) |
| 163 | 165 | } |
| 164 | 166 | |
| @@ -171,6 +173,7 @@ func (r *runner) resume(id string) { | ||
| 171 | 173 | return |
| 172 | 174 | } |
| 173 | 175 | r.messages = msgs |
| 176 | + tools.ClearPlans("") // the plans in memory were the previous conversation's | |
| 174 | 177 | r.opts.Detector.Reset() |
| 175 | 178 | r.opts.Model.ForgetInputTokens() |
| 176 | 179 | r.t.Notice(line) |
| @@ -13,6 +13,7 @@ import ( | |||
| 13 | "mm/internal/docsearch" | 13 | "mm/internal/docsearch" |
| 14 | "mm/internal/session" | 14 | "mm/internal/session" |
| 15 | "mm/internal/store" | 15 | "mm/internal/store" |
| 16 | + "mm/internal/tools" | ||
| 16 | ) | 17 | ) |
| 17 | 18 | ||
| 18 | // submit is what Enter does with the box: a slash command or a question. | 19 | // submit is what Enter does with the box: a slash command or a question. |
| @@ -159,6 +160,7 @@ func (r *runner) newSession() { | |||
| 159 | r.opts.Model.ForgetInputTokens() | 160 | r.opts.Model.ForgetInputTokens() |
| 160 | r.opts.Detector.Reset() | 161 | r.opts.Detector.Reset() |
| 161 | r.keep.Renew() | 162 | r.keep.Renew() |
| 163 | + tools.ClearPlans("") // plans belong to the conversation just closed (ticket 109) | ||
| 162 | r.t.Notice(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) | 164 | r.t.Notice(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) |
| 163 | } | 165 | } |
| 164 | 166 | ||
| @@ -171,6 +173,7 @@ func (r *runner) resume(id string) { | |||
| 171 | return | 173 | return |
| 172 | } | 174 | } |
| 173 | r.messages = msgs | 175 | r.messages = msgs |
| 176 | + tools.ClearPlans("") // the plans in memory were the previous conversation's | ||
| 174 | r.opts.Detector.Reset() | 177 | r.opts.Detector.Reset() |
| 175 | r.opts.Model.ForgetInputTokens() | 178 | r.opts.Model.ForgetInputTokens() |
| 176 | r.t.Notice(line) | 179 | r.t.Notice(line) |
modified
internal/tui/turn_test.go +42 -0 | @@ -16,9 +16,11 @@ import ( | ||
| 16 | 16 | mmhooks "mm/internal/hooks" |
| 17 | 17 | "mm/internal/session" |
| 18 | 18 | "mm/internal/store" |
| 19 | + "mm/internal/tools" | |
| 19 | 20 | "mm/internal/ui" |
| 20 | 21 | |
| 21 | 22 | "github.com/firebase/genkit/go/ai" |
| 23 | + "github.com/firebase/genkit/go/genkit" | |
| 22 | 24 | ) |
| 23 | 25 | |
| 24 | 26 | // fakeModel answers with a canned text; its first call can block until |
| @@ -654,3 +656,43 @@ func TestRunnerDocs(t *testing.T) { | ||
| 654 | 656 | t.Errorf("notices:\n%s", n) |
| 655 | 657 | } |
| 656 | 658 | } |
| 659 | + | |
| 660 | +// /new and /resume start the next conversation with no plan: the plans in | |
| 661 | +// memory belonged to the one just closed (ticket 109). | |
| 662 | +func TestRunnerNewAndResumeClearThePlans(t *testing.T) { | |
| 663 | + tools.ClearAllPlansForTesting() | |
| 664 | + t.Cleanup(tools.ClearAllPlansForTesting) | |
| 665 | + g := genkit.Init(context.Background()) | |
| 666 | + tools.PlanTool(g, detector.NewLoopDetector(10, 3)) | |
| 667 | + plan := genkit.LookupTool(g, "plan") | |
| 668 | + list := func() string { | |
| 669 | + out, err := plan.RunRaw(context.Background(), map[string]any{"action": "list"}) | |
| 670 | + if err != nil { | |
| 671 | + t.Fatal(err) | |
| 672 | + } | |
| 673 | + return out.(string) | |
| 674 | + } | |
| 675 | + create := func() { | |
| 676 | + if _, err := plan.RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "x"}}}); err != nil { | |
| 677 | + t.Fatal(err) | |
| 678 | + } | |
| 679 | + } | |
| 680 | + | |
| 681 | + r, _ := newTestRunner(t, &fakeModel{}) | |
| 682 | + create() | |
| 683 | + r.newSession() | |
| 684 | + if out := list(); !strings.Contains(out, "No active plans") { | |
| 685 | + t.Errorf("after /new: %s", out) | |
| 686 | + } | |
| 687 | + | |
| 688 | + config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"} | |
| 689 | + r.keep = store.NewKeeper(r.opts.Cwd) | |
| 690 | + if err := store.Open(r.keep.Dir()).Save(&store.Session{ID: "20260101-000000-aaaaaaaa", Messages: []*ai.Message{ai.NewUserTextMessage("old question")}}); err != nil { | |
| 691 | + t.Fatal(err) | |
| 692 | + } | |
| 693 | + create() | |
| 694 | + r.resume("20260101-000000-aaaaaaaa") | |
| 695 | + if out := list(); !strings.Contains(out, "No active plans") { | |
| 696 | + t.Errorf("after /resume: %s", out) | |
| 697 | + } | |
| 698 | +} | |
| @@ -16,9 +16,11 @@ import ( | |||
| 16 | mmhooks "mm/internal/hooks" | 16 | mmhooks "mm/internal/hooks" |
| 17 | "mm/internal/session" | 17 | "mm/internal/session" |
| 18 | "mm/internal/store" | 18 | "mm/internal/store" |
| 19 | + "mm/internal/tools" | ||
| 19 | "mm/internal/ui" | 20 | "mm/internal/ui" |
| 20 | 21 | ||
| 21 | "github.com/firebase/genkit/go/ai" | 22 | "github.com/firebase/genkit/go/ai" |
| 23 | + "github.com/firebase/genkit/go/genkit" | ||
| 22 | ) | 24 | ) |
| 23 | 25 | ||
| 24 | // fakeModel answers with a canned text; its first call can block until | 26 | // fakeModel answers with a canned text; its first call can block until |
| @@ -654,3 +656,43 @@ func TestRunnerDocs(t *testing.T) { | |||
| 654 | t.Errorf("notices:\n%s", n) | 656 | t.Errorf("notices:\n%s", n) |
| 655 | } | 657 | } |
| 656 | } | 658 | } |
| 659 | + | ||
| 660 | +// /new and /resume start the next conversation with no plan: the plans in | ||
| 661 | +// memory belonged to the one just closed (ticket 109). | ||
| 662 | +func TestRunnerNewAndResumeClearThePlans(t *testing.T) { | ||
| 663 | + tools.ClearAllPlansForTesting() | ||
| 664 | + t.Cleanup(tools.ClearAllPlansForTesting) | ||
| 665 | + g := genkit.Init(context.Background()) | ||
| 666 | + tools.PlanTool(g, detector.NewLoopDetector(10, 3)) | ||
| 667 | + plan := genkit.LookupTool(g, "plan") | ||
| 668 | + list := func() string { | ||
| 669 | + out, err := plan.RunRaw(context.Background(), map[string]any{"action": "list"}) | ||
| 670 | + if err != nil { | ||
| 671 | + t.Fatal(err) | ||
| 672 | + } | ||
| 673 | + return out.(string) | ||
| 674 | + } | ||
| 675 | + create := func() { | ||
| 676 | + if _, err := plan.RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "x"}}}); err != nil { | ||
| 677 | + t.Fatal(err) | ||
| 678 | + } | ||
| 679 | + } | ||
| 680 | + | ||
| 681 | + r, _ := newTestRunner(t, &fakeModel{}) | ||
| 682 | + create() | ||
| 683 | + r.newSession() | ||
| 684 | + if out := list(); !strings.Contains(out, "No active plans") { | ||
| 685 | + t.Errorf("after /new: %s", out) | ||
| 686 | + } | ||
| 687 | + | ||
| 688 | + config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"} | ||
| 689 | + r.keep = store.NewKeeper(r.opts.Cwd) | ||
| 690 | + if err := store.Open(r.keep.Dir()).Save(&store.Session{ID: "20260101-000000-aaaaaaaa", Messages: []*ai.Message{ai.NewUserTextMessage("old question")}}); err != nil { | ||
| 691 | + t.Fatal(err) | ||
| 692 | + } | ||
| 693 | + create() | ||
| 694 | + r.resume("20260101-000000-aaaaaaaa") | ||
| 695 | + if out := list(); !strings.Contains(out, "No active plans") { | ||
| 696 | + t.Errorf("after /resume: %s", out) | ||
| 697 | + } | ||
| 698 | +} | ||
modified
internal/ui/ui.go +20 -0 | @@ -114,6 +114,26 @@ type Elicitor interface { | ||
| 114 | 114 | Elicit(ctx context.Context, ev ToolEvent, f elicit.Form) elicit.Result |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | +// Conversation is the optional side of a Sink that serves one conversation | |
| 118 | +// among several in the same process — ACP, where one mm serves every thread | |
| 119 | +// the editor opens. A tool that keeps state per conversation (the plan tool) | |
| 120 | +// reads it through ConversationID. | |
| 121 | +type Conversation interface { | |
| 122 | + ConversationID() string | |
| 123 | +} | |
| 124 | + | |
| 125 | +// ConversationID names the conversation the turn in progress belongs to: the | |
| 126 | +// active sink's Conversation, or "" for the front ends that hold one | |
| 127 | +// conversation at a time (the line mode, the window). | |
| 128 | +// | |
| 129 | +// key := ui.ConversationID() // "20261021-…" over ACP, "" in a terminal | |
| 130 | +func ConversationID() string { | |
| 131 | + if c, ok := ActiveSink().(Conversation); ok { | |
| 132 | + return c.ConversationID() | |
| 133 | + } | |
| 134 | + return "" | |
| 135 | +} | |
| 136 | + | |
| 117 | 137 | // Noticer is the optional side of a Sink that shows a one-line notice — a |
| 118 | 138 | // remark about the turn that is neither the answer nor a tool call, such as |
| 119 | 139 | // a hook that failed. The window has one (a dim transcript line); without |
| @@ -114,6 +114,26 @@ type Elicitor interface { | |||
| 114 | Elicit(ctx context.Context, ev ToolEvent, f elicit.Form) elicit.Result | 114 | Elicit(ctx context.Context, ev ToolEvent, f elicit.Form) elicit.Result |
| 115 | } | 115 | } |
| 116 | 116 | ||
| 117 | +// Conversation is the optional side of a Sink that serves one conversation | ||
| 118 | +// among several in the same process — ACP, where one mm serves every thread | ||
| 119 | +// the editor opens. A tool that keeps state per conversation (the plan tool) | ||
| 120 | +// reads it through ConversationID. | ||
| 121 | +type Conversation interface { | ||
| 122 | + ConversationID() string | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +// ConversationID names the conversation the turn in progress belongs to: the | ||
| 126 | +// active sink's Conversation, or "" for the front ends that hold one | ||
| 127 | +// conversation at a time (the line mode, the window). | ||
| 128 | +// | ||
| 129 | +// key := ui.ConversationID() // "20261021-…" over ACP, "" in a terminal | ||
| 130 | +func ConversationID() string { | ||
| 131 | + if c, ok := ActiveSink().(Conversation); ok { | ||
| 132 | + return c.ConversationID() | ||
| 133 | + } | ||
| 134 | + return "" | ||
| 135 | +} | ||
| 136 | + | ||
| 117 | // Noticer is the optional side of a Sink that shows a one-line notice — a | 137 | // Noticer is the optional side of a Sink that shows a one-line notice — a |
| 118 | // remark about the turn that is neither the answer nor a tool call, such as | 138 | // remark about the turn that is neither the answer nor a tool call, such as |
| 119 | // a hook that failed. The window has one (a dim transcript line); without | 139 | // a hook that failed. The window has one (a dim transcript line); without |
modified
internal/version/about.go +4 -2 | @@ -26,8 +26,10 @@ func About() string { | ||
| 26 | 26 | b.WriteString(" mm [config.yaml] a terminal REPL, line by line\n") |
| 27 | 27 | b.WriteString(" mm -tui [config.yaml] one terminal window: conversation, input box, status bar\n") |
| 28 | 28 | b.WriteString(" mm -acp the Agent Client Protocol on stdio, for Zed and other editors\n\n") |
| 29 | - b.WriteString("Tools the model can call: bash, read_skill, read_file, write_file, edit_file, lsp.\n") | |
| 30 | - b.WriteString("Model servers: Docker Model Runner, llama.cpp — anything speaking the OpenAI API.\n\n") | |
| 29 | + b.WriteString("Built-in tools: bash, read_file, write_file, edit_file, read_skill, plan, ask_user, unload_model;\n") | |
| 30 | + b.WriteString(" when the config turns them on: lsp, fetch_content, ask_agent, save_answer, search_memory, search_docs.\n") | |
| 31 | + b.WriteString(" Plus the agent file's own tools (tools:) and MCP servers' tools.\n") | |
| 32 | + b.WriteString("Model servers: Docker Model Runner, llmman, Ollama, llama.cpp — anything speaking the OpenAI API.\n\n") | |
| 31 | 33 | fmt.Fprintf(&b, "%s\n", Home) |
| 32 | 34 | b.WriteString("Docs: docs/en (English), docs/fr (français) · mm -h for the flags") |
| 33 | 35 | return b.String() |
| @@ -26,8 +26,10 @@ func About() string { | |||
| 26 | b.WriteString(" mm [config.yaml] a terminal REPL, line by line\n") | 26 | b.WriteString(" mm [config.yaml] a terminal REPL, line by line\n") |
| 27 | b.WriteString(" mm -tui [config.yaml] one terminal window: conversation, input box, status bar\n") | 27 | b.WriteString(" mm -tui [config.yaml] one terminal window: conversation, input box, status bar\n") |
| 28 | b.WriteString(" mm -acp the Agent Client Protocol on stdio, for Zed and other editors\n\n") | 28 | b.WriteString(" mm -acp the Agent Client Protocol on stdio, for Zed and other editors\n\n") |
| 29 | - b.WriteString("Tools the model can call: bash, read_skill, read_file, write_file, edit_file, lsp.\n") | 29 | + b.WriteString("Built-in tools: bash, read_file, write_file, edit_file, read_skill, plan, ask_user, unload_model;\n") |
| 30 | - b.WriteString("Model servers: Docker Model Runner, llama.cpp — anything speaking the OpenAI API.\n\n") | 30 | + b.WriteString(" when the config turns them on: lsp, fetch_content, ask_agent, save_answer, search_memory, search_docs.\n") |
| 31 | + b.WriteString(" Plus the agent file's own tools (tools:) and MCP servers' tools.\n") | ||
| 32 | + b.WriteString("Model servers: Docker Model Runner, llmman, Ollama, llama.cpp — anything speaking the OpenAI API.\n\n") | ||
| 31 | fmt.Fprintf(&b, "%s\n", Home) | 33 | fmt.Fprintf(&b, "%s\n", Home) |
| 32 | b.WriteString("Docs: docs/en (English), docs/fr (français) · mm -h for the flags") | 34 | b.WriteString("Docs: docs/en (English), docs/fr (français) · mm -h for the flags") |
| 33 | return b.String() | 35 | return b.String() |
modified
release-notes/v0.8.0.en.md +1 -0 | @@ -47,6 +47,7 @@ Search what was said before and what is written down: recall your earlier questi | ||
| 47 | 47 | ## Fixed |
| 48 | 48 | |
| 49 | 49 | - **Line mode: only the first of several piped lines was read** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` or `mm agent.yaml < commands.txt` ran the first line and silently dropped the others. All of them now run, and a last line without a newline is no longer lost. Typing at the keyboard was not affected. |
| 50 | +- **`mm --about` listed only six tools and two model servers.** It now lists every built-in tool — the ones on by default and the ones the configuration turns on — and the four servers (Docker Model Runner, llmman, Ollama, llama.cpp). A test now fails if a new built-in tool is missing from it. | |
| 50 | 51 | |
| 51 | 52 | ## Demos |
| 52 | 53 | |
| @@ -47,6 +47,7 @@ Search what was said before and what is written down: recall your earlier questi | |||
| 47 | ## Fixed | 47 | ## Fixed |
| 48 | 48 | ||
| 49 | - **Line mode: only the first of several piped lines was read** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` or `mm agent.yaml < commands.txt` ran the first line and silently dropped the others. All of them now run, and a last line without a newline is no longer lost. Typing at the keyboard was not affected. | 49 | - **Line mode: only the first of several piped lines was read** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` or `mm agent.yaml < commands.txt` ran the first line and silently dropped the others. All of them now run, and a last line without a newline is no longer lost. Typing at the keyboard was not affected. |
| 50 | +- **`mm --about` listed only six tools and two model servers.** It now lists every built-in tool — the ones on by default and the ones the configuration turns on — and the four servers (Docker Model Runner, llmman, Ollama, llama.cpp). A test now fails if a new built-in tool is missing from it. | ||
| 50 | 51 | ||
| 51 | ## Demos | 52 | ## Demos |
| 52 | 53 | ||
modified
release-notes/v0.8.0.fr.md +1 -0 | @@ -47,6 +47,7 @@ Retrouver ce qui a été dit et ce qui est écrit : rappeler ses questions préc | ||
| 47 | 47 | ## Corrigé |
| 48 | 48 | |
| 49 | 49 | - **Mode ligne : seule la première de plusieurs lignes reçues par un pipe était lue** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` ou `mm agent.yaml < commandes.txt` exécutait la première ligne et ignorait les autres sans rien dire. Elles s'exécutent maintenant toutes, et une dernière ligne sans retour à la ligne n'est plus perdue. La saisie au clavier n'était pas touchée. |
| 50 | +- **`mm --about` ne listait que six outils et deux serveurs de modèles.** Il liste maintenant tous les outils intégrés — ceux actifs par défaut et ceux que la configuration active — et les quatre serveurs (Docker Model Runner, llmman, Ollama, llama.cpp). Un test échoue désormais si un nouvel outil intégré y manque. | |
| 50 | 51 | |
| 51 | 52 | ## Démos |
| 52 | 53 | |
| @@ -47,6 +47,7 @@ Retrouver ce qui a été dit et ce qui est écrit : rappeler ses questions préc | |||
| 47 | ## Corrigé | 47 | ## Corrigé |
| 48 | 48 | ||
| 49 | - **Mode ligne : seule la première de plusieurs lignes reçues par un pipe était lue** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` ou `mm agent.yaml < commandes.txt` exécutait la première ligne et ignorait les autres sans rien dire. Elles s'exécutent maintenant toutes, et une dernière ligne sans retour à la ligne n'est plus perdue. La saisie au clavier n'était pas touchée. | 49 | - **Mode ligne : seule la première de plusieurs lignes reçues par un pipe était lue** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` ou `mm agent.yaml < commandes.txt` exécutait la première ligne et ignorait les autres sans rien dire. Elles s'exécutent maintenant toutes, et une dernière ligne sans retour à la ligne n'est plus perdue. La saisie au clavier n'était pas touchée. |
| 50 | +- **`mm --about` ne listait que six outils et deux serveurs de modèles.** Il liste maintenant tous les outils intégrés — ceux actifs par défaut et ceux que la configuration active — et les quatre serveurs (Docker Model Runner, llmman, Ollama, llama.cpp). Un test échoue désormais si un nouvel outil intégré y manque. | ||
| 50 | 51 | ||
| 51 | ## Démos | 52 | ## Démos |
| 52 | 53 | ||
added
tools_reference_test.go +26 -0 | new file mode 100644 | ||
| @@ -0,0 +1,26 @@ | ||
| 1 | +package main | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "os" | |
| 5 | + "strings" | |
| 6 | + "testing" | |
| 7 | + | |
| 8 | + "mm/internal/config" | |
| 9 | +) | |
| 10 | + | |
| 11 | +// Every built-in tool has its own section in the tools reference, in both | |
| 12 | +// languages — `plan` went without one for months, documented only in a | |
| 13 | +// how-to. | |
| 14 | +func TestEveryBuiltinToolHasAReferenceSection(t *testing.T) { | |
| 15 | + for _, page := range []string{"docs/en/reference/tools.md", "docs/fr/reference/tools.md"} { | |
| 16 | + data, err := os.ReadFile(page) | |
| 17 | + if err != nil { | |
| 18 | + t.Fatal(err) | |
| 19 | + } | |
| 20 | + for _, name := range config.BuiltinToolNames { | |
| 21 | + if !strings.Contains(string(data), "\n## `"+name+"`\n") { | |
| 22 | + t.Errorf("%s has no \"## `%s`\" section", page, name) | |
| 23 | + } | |
| 24 | + } | |
| 25 | + } | |
| 26 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | +package main | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "os" | ||
| 5 | + "strings" | ||
| 6 | + "testing" | ||
| 7 | + | ||
| 8 | + "mm/internal/config" | ||
| 9 | +) | ||
| 10 | + | ||
| 11 | +// Every built-in tool has its own section in the tools reference, in both | ||
| 12 | +// languages — `plan` went without one for months, documented only in a | ||
| 13 | +// how-to. | ||
| 14 | +func TestEveryBuiltinToolHasAReferenceSection(t *testing.T) { | ||
| 15 | + for _, page := range []string{"docs/en/reference/tools.md", "docs/fr/reference/tools.md"} { | ||
| 16 | + data, err := os.ReadFile(page) | ||
| 17 | + if err != nil { | ||
| 18 | + t.Fatal(err) | ||
| 19 | + } | ||
| 20 | + for _, name := range config.BuiltinToolNames { | ||
| 21 | + if !strings.Contains(string(data), "\n## `"+name+"`\n") { | ||
| 22 | + t.Errorf("%s has no \"## `%s`\" section", page, name) | ||
| 23 | + } | ||
| 24 | + } | ||
| 25 | + } | ||
| 26 | +} | ||