modified
.tickets/issues/0068-recall-the-last-commands-last.yaml +127 -5 | @@ -1,14 +1,136 @@ | ||
| 1 | 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. |
| 2 | 2 | id: 68 |
| 3 | 3 | title: Recall the last commands (/last) |
| 4 | -state: open | |
| 4 | +state: closed | |
| 5 | 5 | author: |
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-10-02T07:01:13.955Z |
| 9 | -updatedAt: 2026-10-06T13:14:55.214Z | |
| 9 | +updatedAt: 2026-10-20T11:00:00.000Z | |
| 10 | 10 | labels: [] |
| 11 | +milestone: v0.8.0 | |
| 11 | 12 | epic: Improve TUI |
| 12 | -body: "" | |
| 13 | -tasks: [] | |
| 14 | -comments: [] | |
| 13 | +body: | | |
| 14 | + in --tui mode, with the arrows up and down, recall the last user questions if | |
| 15 | + ``` | |
| 16 | + sessions: | |
| 17 | + enabled: true | |
| 18 | + ``` | |
| 19 | + | |
| 20 | + propose something to clear the question, if the user wants to type something else | |
| 21 | +tasks: | |
| 22 | + - id: 1 | |
| 23 | + title: "Agree on the source of the questions, the keys and how to clear the box" | |
| 24 | + state: closed | |
| 25 | + priority: none | |
| 26 | + author: | |
| 27 | + name: k33g | |
| 28 | + email: ph.charriere@gmail.com | |
| 29 | + createdAt: 2026-10-20T08:00:00.000Z | |
| 30 | + updatedAt: 2026-10-20T08:00:00.000Z | |
| 31 | + - id: 2 | |
| 32 | + title: "store: Keeper.Questions(current) — user messages, current session first then earlier sessions newest first, summaries and mm notes skipped, consecutive duplicates dropped, capped — with tests" | |
| 33 | + state: closed | |
| 34 | + priority: none | |
| 35 | + author: | |
| 36 | + name: k33g | |
| 37 | + email: ph.charriere@gmail.com | |
| 38 | + createdAt: 2026-10-20T08:00:00.000Z | |
| 39 | + updatedAt: 2026-10-20T08:00:00.000Z | |
| 40 | + - id: 3 | |
| 41 | + title: "tui: up on the first row of the box recalls the previous question, down on the last row the next one, then the draft; scrolling unchanged when nothing to recall — with tests" | |
| 42 | + state: closed | |
| 43 | + priority: none | |
| 44 | + author: | |
| 45 | + name: k33g | |
| 46 | + email: ph.charriere@gmail.com | |
| 47 | + createdAt: 2026-10-20T08:00:00.000Z | |
| 48 | + updatedAt: 2026-10-20T08:00:00.000Z | |
| 49 | + - id: 4 | |
| 50 | + title: "tui: Escape when idle with text in the box clears it — with tests" | |
| 51 | + state: closed | |
| 52 | + priority: none | |
| 53 | + author: | |
| 54 | + name: k33g | |
| 55 | + email: ph.charriere@gmail.com | |
| 56 | + createdAt: 2026-10-20T08:00:00.000Z | |
| 57 | + updatedAt: 2026-10-20T08:00:00.000Z | |
| 58 | + - id: 5 | |
| 59 | + title: "Quality gate, docs EN+FR (reference keys, how-to sessions), package READMEs, .memory" | |
| 60 | + state: closed | |
| 61 | + priority: none | |
| 62 | + author: | |
| 63 | + name: k33g | |
| 64 | + email: ph.charriere@gmail.com | |
| 65 | + createdAt: 2026-10-20T08:00:00.000Z | |
| 66 | + updatedAt: 2026-10-20T08:00:00.000Z | |
| 67 | +comments: | |
| 68 | + - id: 1 | |
| 69 | + author: | |
| 70 | + name: k33g | |
| 71 | + email: ph.charriere@gmail.com | |
| 72 | + createdAt: 2026-10-20T08:00:00.000Z | |
| 73 | + body: | | |
| 74 | + Decided by the user (2026-10-20): | |
| 75 | + - source: the user messages already stored in the session files (no new | |
| 76 | + field) — so a recalled question carries what mm stored with it | |
| 77 | + ([attached file: …] lines, pastes in full); slash commands are not in | |
| 78 | + the files and are not recalled; | |
| 79 | + - keys: up/down in the box (up on its first row, down on its last); the | |
| 80 | + conversation still scrolls with PgUp/PgDn, the wheel, and the arrows | |
| 81 | + after Tab; | |
| 82 | + - clearing: Escape when no turn runs and the box is not empty clears it | |
| 83 | + (Escape during a turn still cancels it); down past the newest question | |
| 84 | + also gives back the draft. | |
| 85 | + Branch feature/last-question (created by the user). | |
| 86 | + - id: 2 | |
| 87 | + author: | |
| 88 | + name: k33g | |
| 89 | + email: ph.charriere@gmail.com | |
| 90 | + createdAt: 2026-10-20T09:00:00.000Z | |
| 91 | + body: | | |
| 92 | + Tasks 2-4 done on feature/last-question (go test ./... green): | |
| 93 | + - store/questions.go: Keeper.Questions(current) — newest first, the | |
| 94 | + conversation in memory then the other session files (Store.all, which | |
| 95 | + List now uses too); skips /compact summaries, mm's notes | |
| 96 | + (engine.IsNote: "[mm: …" retry notes, "[prompt blocked by a hook…"), | |
| 97 | + empty texts, what a preGeneration hook appended (cut at | |
| 98 | + hooks.ContextLabel); consecutive duplicates once; capped at 200. | |
| 99 | + - tui/recall.go: ↑ on the box's first row / ↓ on its last; fetched once | |
| 100 | + per walk (hooks.questions → runner.questions); ↓ past the newest gives | |
| 101 | + back the draft (pastes included); editing ends the walk; nothing to | |
| 102 | + recall → the arrows scroll as before; a recalled "@…" does not open | |
| 103 | + the picker. | |
| 104 | + - Found while testing: a turn that fails or is cancelled before | |
| 105 | + answering drops its question from the history (session.AfterTurn), | |
| 106 | + so the stored-messages source alone could not recall it — the one you | |
| 107 | + most want to retry. The runner keeps it (runner.asked) until the next | |
| 108 | + question; it is in memory only, not in the session file. | |
| 109 | + - Escape: idle with text in the box → clears it (and the walk); during a | |
| 110 | + turn → cancels as before; empty idle box → the old notice. F1 help | |
| 111 | + line updated. | |
| 112 | + - id: 3 | |
| 113 | + author: | |
| 114 | + name: k33g | |
| 115 | + email: ph.charriere@gmail.com | |
| 116 | + createdAt: 2026-10-20T10:00:00.000Z | |
| 117 | + body: | | |
| 118 | + Docs EN+FR: how-to/use-the-window-mode (new section "Recall an earlier | |
| 119 | + question"), how-to/keep-sessions (variant), reference/slash-commands | |
| 120 | + (Esc and ↑↓ rows), explanation/persistent-sessions (why the stored | |
| 121 | + questions, the failed-turn gap, the rejected "inputs" field); | |
| 122 | + docs/diagrams/packages.drawio: store's import edges (it had none). | |
| 123 | + Live check: mm -tui driven through a pty, no model server, one stored | |
| 124 | + session → ↑ gave its last question, ↑ the one before, Esc cleared the | |
| 125 | + box, Esc on the empty box gave "No generation in progress". | |
| 126 | + Task 5 stays open: the qlty quality gate was NOT run (qlty is not in this | |
| 127 | + sandbox — kits/qlty needed); gofmt and go vet clean, go test ./... green. | |
| 128 | + - id: 4 | |
| 129 | + author: | |
| 130 | + name: k33g | |
| 131 | + email: ph.charriere@gmail.com | |
| 132 | + createdAt: 2026-10-20T11:00:00.000Z | |
| 133 | + body: | | |
| 134 | + Closed at the user's request ("c'est ok"). Task 5 closed with the qlty | |
| 135 | + quality gate still NOT run (qlty absent from the sandbox) — to run with | |
| 136 | + kits/qlty at the next quality pass. | |
| @@ -1,14 +1,136 @@ | |||
| 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. |
| 2 | id: 68 | 2 | id: 68 |
| 3 | title: Recall the last commands (/last) | 3 | title: Recall the last commands (/last) |
| 4 | -state: open | 4 | +state: closed |
| 5 | author: | 5 | author: |
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-10-02T07:01:13.955Z | 8 | createdAt: 2026-10-02T07:01:13.955Z |
| 9 | -updatedAt: 2026-10-06T13:14:55.214Z | 9 | +updatedAt: 2026-10-20T11:00:00.000Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | +milestone: v0.8.0 | ||
| 11 | epic: Improve TUI | 12 | epic: Improve TUI |
| 12 | -body: "" | 13 | +body: | |
| 13 | -tasks: [] | 14 | + in --tui mode, with the arrows up and down, recall the last user questions if |
| 14 | -comments: [] | 15 | + ``` |
| 16 | + sessions: | ||
| 17 | + enabled: true | ||
| 18 | + ``` | ||
| 19 | + | ||
| 20 | + propose something to clear the question, if the user wants to type something else | ||
| 21 | +tasks: | ||
| 22 | + - id: 1 | ||
| 23 | + title: "Agree on the source of the questions, the keys and how to clear the box" | ||
| 24 | + state: closed | ||
| 25 | + priority: none | ||
| 26 | + author: | ||
| 27 | + name: k33g | ||
| 28 | + email: ph.charriere@gmail.com | ||
| 29 | + createdAt: 2026-10-20T08:00:00.000Z | ||
| 30 | + updatedAt: 2026-10-20T08:00:00.000Z | ||
| 31 | + - id: 2 | ||
| 32 | + title: "store: Keeper.Questions(current) — user messages, current session first then earlier sessions newest first, summaries and mm notes skipped, consecutive duplicates dropped, capped — with tests" | ||
| 33 | + state: closed | ||
| 34 | + priority: none | ||
| 35 | + author: | ||
| 36 | + name: k33g | ||
| 37 | + email: ph.charriere@gmail.com | ||
| 38 | + createdAt: 2026-10-20T08:00:00.000Z | ||
| 39 | + updatedAt: 2026-10-20T08:00:00.000Z | ||
| 40 | + - id: 3 | ||
| 41 | + title: "tui: up on the first row of the box recalls the previous question, down on the last row the next one, then the draft; scrolling unchanged when nothing to recall — with tests" | ||
| 42 | + state: closed | ||
| 43 | + priority: none | ||
| 44 | + author: | ||
| 45 | + name: k33g | ||
| 46 | + email: ph.charriere@gmail.com | ||
| 47 | + createdAt: 2026-10-20T08:00:00.000Z | ||
| 48 | + updatedAt: 2026-10-20T08:00:00.000Z | ||
| 49 | + - id: 4 | ||
| 50 | + title: "tui: Escape when idle with text in the box clears it — with tests" | ||
| 51 | + state: closed | ||
| 52 | + priority: none | ||
| 53 | + author: | ||
| 54 | + name: k33g | ||
| 55 | + email: ph.charriere@gmail.com | ||
| 56 | + createdAt: 2026-10-20T08:00:00.000Z | ||
| 57 | + updatedAt: 2026-10-20T08:00:00.000Z | ||
| 58 | + - id: 5 | ||
| 59 | + title: "Quality gate, docs EN+FR (reference keys, how-to sessions), package READMEs, .memory" | ||
| 60 | + state: closed | ||
| 61 | + priority: none | ||
| 62 | + author: | ||
| 63 | + name: k33g | ||
| 64 | + email: ph.charriere@gmail.com | ||
| 65 | + createdAt: 2026-10-20T08:00:00.000Z | ||
| 66 | + updatedAt: 2026-10-20T08:00:00.000Z | ||
| 67 | +comments: | ||
| 68 | + - id: 1 | ||
| 69 | + author: | ||
| 70 | + name: k33g | ||
| 71 | + email: ph.charriere@gmail.com | ||
| 72 | + createdAt: 2026-10-20T08:00:00.000Z | ||
| 73 | + body: | | ||
| 74 | + Decided by the user (2026-10-20): | ||
| 75 | + - source: the user messages already stored in the session files (no new | ||
| 76 | + field) — so a recalled question carries what mm stored with it | ||
| 77 | + ([attached file: …] lines, pastes in full); slash commands are not in | ||
| 78 | + the files and are not recalled; | ||
| 79 | + - keys: up/down in the box (up on its first row, down on its last); the | ||
| 80 | + conversation still scrolls with PgUp/PgDn, the wheel, and the arrows | ||
| 81 | + after Tab; | ||
| 82 | + - clearing: Escape when no turn runs and the box is not empty clears it | ||
| 83 | + (Escape during a turn still cancels it); down past the newest question | ||
| 84 | + also gives back the draft. | ||
| 85 | + Branch feature/last-question (created by the user). | ||
| 86 | + - id: 2 | ||
| 87 | + author: | ||
| 88 | + name: k33g | ||
| 89 | + email: ph.charriere@gmail.com | ||
| 90 | + createdAt: 2026-10-20T09:00:00.000Z | ||
| 91 | + body: | | ||
| 92 | + Tasks 2-4 done on feature/last-question (go test ./... green): | ||
| 93 | + - store/questions.go: Keeper.Questions(current) — newest first, the | ||
| 94 | + conversation in memory then the other session files (Store.all, which | ||
| 95 | + List now uses too); skips /compact summaries, mm's notes | ||
| 96 | + (engine.IsNote: "[mm: …" retry notes, "[prompt blocked by a hook…"), | ||
| 97 | + empty texts, what a preGeneration hook appended (cut at | ||
| 98 | + hooks.ContextLabel); consecutive duplicates once; capped at 200. | ||
| 99 | + - tui/recall.go: ↑ on the box's first row / ↓ on its last; fetched once | ||
| 100 | + per walk (hooks.questions → runner.questions); ↓ past the newest gives | ||
| 101 | + back the draft (pastes included); editing ends the walk; nothing to | ||
| 102 | + recall → the arrows scroll as before; a recalled "@…" does not open | ||
| 103 | + the picker. | ||
| 104 | + - Found while testing: a turn that fails or is cancelled before | ||
| 105 | + answering drops its question from the history (session.AfterTurn), | ||
| 106 | + so the stored-messages source alone could not recall it — the one you | ||
| 107 | + most want to retry. The runner keeps it (runner.asked) until the next | ||
| 108 | + question; it is in memory only, not in the session file. | ||
| 109 | + - Escape: idle with text in the box → clears it (and the walk); during a | ||
| 110 | + turn → cancels as before; empty idle box → the old notice. F1 help | ||
| 111 | + line updated. | ||
| 112 | + - id: 3 | ||
| 113 | + author: | ||
| 114 | + name: k33g | ||
| 115 | + email: ph.charriere@gmail.com | ||
| 116 | + createdAt: 2026-10-20T10:00:00.000Z | ||
| 117 | + body: | | ||
| 118 | + Docs EN+FR: how-to/use-the-window-mode (new section "Recall an earlier | ||
| 119 | + question"), how-to/keep-sessions (variant), reference/slash-commands | ||
| 120 | + (Esc and ↑↓ rows), explanation/persistent-sessions (why the stored | ||
| 121 | + questions, the failed-turn gap, the rejected "inputs" field); | ||
| 122 | + docs/diagrams/packages.drawio: store's import edges (it had none). | ||
| 123 | + Live check: mm -tui driven through a pty, no model server, one stored | ||
| 124 | + session → ↑ gave its last question, ↑ the one before, Esc cleared the | ||
| 125 | + box, Esc on the empty box gave "No generation in progress". | ||
| 126 | + Task 5 stays open: the qlty quality gate was NOT run (qlty is not in this | ||
| 127 | + sandbox — kits/qlty needed); gofmt and go vet clean, go test ./... green. | ||
| 128 | + - id: 4 | ||
| 129 | + author: | ||
| 130 | + name: k33g | ||
| 131 | + email: ph.charriere@gmail.com | ||
| 132 | + createdAt: 2026-10-20T11:00:00.000Z | ||
| 133 | + body: | | ||
| 134 | + Closed at the user's request ("c'est ok"). Task 5 closed with the qlty | ||
| 135 | + quality gate still NOT run (qlty absent from the sandbox) — to run with | ||
| 136 | + kits/qlty at the next quality pass. | ||
modified
.tickets/issues/0089-autonomous-agent.yaml +1 -2 | @@ -6,9 +6,8 @@ author: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-10-16T15:36:08.133Z |
| 9 | -updatedAt: 2026-10-18T15:16:03.149Z | |
| 9 | +updatedAt: 2026-10-20T05:36:48.607Z | |
| 10 | 10 | labels: [] |
| 11 | -milestone: v0.8.0 | |
| 12 | 11 | related: |
| 13 | 12 | - 90 |
| 14 | 13 | body: | |
| @@ -6,9 +6,8 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-10-16T15:36:08.133Z | 8 | createdAt: 2026-10-16T15:36:08.133Z |
| 9 | -updatedAt: 2026-10-18T15:16:03.149Z | 9 | +updatedAt: 2026-10-20T05:36:48.607Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | -milestone: v0.8.0 | ||
| 12 | related: | 11 | related: |
| 13 | - 90 | 12 | - 90 |
| 14 | body: | | 13 | body: | |
modified
.tickets/issues/0090-agent-identity.yaml +1 -2 | @@ -6,9 +6,8 @@ author: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-10-16T15:37:32.062Z |
| 9 | -updatedAt: 2026-10-18T15:15:45.705Z | |
| 9 | +updatedAt: 2026-10-20T05:37:03.730Z | |
| 10 | 10 | labels: [] |
| 11 | -milestone: v0.8.0 | |
| 12 | 11 | body: | |
| 13 | 12 | Des idées pour gérer cela ? |
| 14 | 13 | avec l'existant (est ce que un seul useur peut avoir plusieurs identités sur GitHub par exemple) |
| @@ -6,9 +6,8 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-10-16T15:37:32.062Z | 8 | createdAt: 2026-10-16T15:37:32.062Z |
| 9 | -updatedAt: 2026-10-18T15:15:45.705Z | 9 | +updatedAt: 2026-10-20T05:37:03.730Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | -milestone: v0.8.0 | ||
| 12 | body: | | 11 | body: | |
| 13 | Des idées pour gérer cela ? | 12 | Des idées pour gérer cela ? |
| 14 | avec l'existant (est ce que un seul useur peut avoir plusieurs identités sur GitHub par exemple) | 13 | avec l'existant (est ce que un seul useur peut avoir plusieurs identités sur GitHub par exemple) |
modified
.tickets/issues/0096-in-guides-add-a-tips-document-eg-how-to-resume-a.yaml +1 -2 | @@ -6,11 +6,10 @@ author: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-10-18T09:26:47.068Z |
| 9 | -updatedAt: 2026-10-18T13:49:23.480Z | |
| 9 | +updatedAt: 2026-10-20T05:37:26.481Z | |
| 10 | 10 | labels: |
| 11 | 11 | - priority::medium |
| 12 | 12 | - documentation |
| 13 | -milestone: v0.8.0 | |
| 14 | 13 | body: | |
| 15 | 14 | example: when the agent (or the model) is "stuck"... |
| 16 | 15 | tasks: [] |
| @@ -6,11 +6,10 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-10-18T09:26:47.068Z | 8 | createdAt: 2026-10-18T09:26:47.068Z |
| 9 | -updatedAt: 2026-10-18T13:49:23.480Z | 9 | +updatedAt: 2026-10-20T05:37:26.481Z |
| 10 | labels: | 10 | labels: |
| 11 | - priority::medium | 11 | - priority::medium |
| 12 | - documentation | 12 | - documentation |
| 13 | -milestone: v0.8.0 | ||
| 14 | body: | | 13 | body: | |
| 15 | example: when the agent (or the model) is "stuck"... | 14 | example: when the agent (or the model) is "stuck"... |
| 16 | tasks: [] | 15 | tasks: [] |
modified
.tickets/issues/0097-generate-an-agent-yaml-file-with-rhe-cli.yaml +2 -2 | @@ -6,9 +6,9 @@ author: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-10-18T12:31:14.544Z |
| 9 | -updatedAt: 2026-10-18T13:26:23.314Z | |
| 9 | +updatedAt: 2026-10-20T05:38:52.918Z | |
| 10 | 10 | labels: [] |
| 11 | -milestone: v0.8.0 | |
| 11 | +milestone: v0.9.0 | |
| 12 | 12 | related: |
| 13 | 13 | - 98 |
| 14 | 14 | body: "" |
| @@ -6,9 +6,9 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-10-18T12:31:14.544Z | 8 | createdAt: 2026-10-18T12:31:14.544Z |
| 9 | -updatedAt: 2026-10-18T13:26:23.314Z | 9 | +updatedAt: 2026-10-20T05:38:52.918Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | -milestone: v0.8.0 | 11 | +milestone: v0.9.0 |
| 12 | related: | 12 | related: |
| 13 | - 98 | 13 | - 98 |
| 14 | body: "" | 14 | body: "" |
modified
.tickets/milestones.yaml +4 -2 | @@ -22,11 +22,13 @@ milestones: | ||
| 22 | 22 | state: closed |
| 23 | 23 | dueOn: 2026-10-28 |
| 24 | 24 | - title: v0.6.0 |
| 25 | - state: open | |
| 25 | + state: closed | |
| 26 | 26 | dueOn: 2026-10-28 |
| 27 | 27 | - title: v0.7.0 |
| 28 | - state: open | |
| 28 | + state: closed | |
| 29 | 29 | dueOn: 2026-10-28 |
| 30 | 30 | - title: v0.8.0 |
| 31 | 31 | state: open |
| 32 | 32 | dueOn: 2026-10-28 |
| 33 | + - title: v0.9.0 | |
| 34 | + state: open | |
| @@ -22,11 +22,13 @@ milestones: | |||
| 22 | state: closed | 22 | state: closed |
| 23 | dueOn: 2026-10-28 | 23 | dueOn: 2026-10-28 |
| 24 | - title: v0.6.0 | 24 | - title: v0.6.0 |
| 25 | - state: open | 25 | + state: closed |
| 26 | dueOn: 2026-10-28 | 26 | dueOn: 2026-10-28 |
| 27 | - title: v0.7.0 | 27 | - title: v0.7.0 |
| 28 | - state: open | 28 | + state: closed |
| 29 | dueOn: 2026-10-28 | 29 | dueOn: 2026-10-28 |
| 30 | - title: v0.8.0 | 30 | - title: v0.8.0 |
| 31 | state: open | 31 | state: open |
| 32 | dueOn: 2026-10-28 | 32 | dueOn: 2026-10-28 |
| 33 | + - title: v0.9.0 | ||
| 34 | + state: open | ||
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)</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></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</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></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)</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></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</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></root></mxGraphModel></diagram></mxfile> | ||
| \ No newline at end of file | \ No newline at end of file | ||
modified
docs/en/explanation/persistent-sessions.md +7 -0 | @@ -38,6 +38,12 @@ The array sits beside `messages`, not inside it, on purpose. The model never rea | ||
| 38 | 38 | |
| 39 | 39 | What it is for: asking afterwards, with the file in hand, why a run went wrong — `jq .errors <file>` for a human, or the file handed to a coding agent asked to explain the failures and propose changes to the code or the YAML (a longer `watchdogTimeout`, a different model, a smaller `maxOutput`). The `/sessions` listing and the `/resume` report show ` · ⚠️ N error(s)` so a session worth investigating stands out. The trail is capped at 200 records, oldest first out, so a run that fails on every turn does not grow its file without end. |
| 40 | 40 | |
| 41 | +## Why the window's ↑ reads the stored questions | |
| 42 | + | |
| 43 | +In the window, ↑ on the box's first row recalls the questions already asked (ticket 68), and only when sessions are on. The questions are read from the session files themselves — the `user` messages of the conversation in memory, then of the project's other files, newest first — not from a separate list. That was the user's choice: nothing new is written, the recall covers every session the project has kept, including those written before the feature existed, and it disappears with them when the directory is cleaned. | |
| 44 | + | |
| 45 | +The price is that a recalled question is what the model read, not exactly what was typed: the `[attached file: …]` lines an `@path` added, a paste in full rather than its token, an `/ask` in its `[asked …]` form; slash commands, never sent to the model, are not there at all. What mm itself wrote as a user message is left out — a `/compact` summary, the note after a broken tool call, the marker of a prompt a hook blocked — and so is the context a `preGeneration` hook appended. One gap the files cannot fill: a turn that fails before answering drops its question from the history, and that is the question one most wants to retry. The window keeps it in memory until the next question, so ↑ still finds it; it is not written anywhere. | |
| 46 | + | |
| 41 | 47 | ## Rejected alternatives |
| 42 | 48 | |
| 43 | 49 | - **Resume the latest session automatically at start.** Rejected for the stale-context reason above, and because docker agent does not do it either. |
| @@ -46,6 +52,7 @@ What it is for: asking afterwards, with the file in hand, why a run went wrong | ||
| 46 | 52 | - **`session/resume` instead of `session/load`.** Rejected: it relies on the editor having kept the thread, and when the choice was made it was not in the stable spec. Since ticket 95 it is offered beside `session/load`, not instead of it. |
| 47 | 53 | - **A `/resume` slash command over ACP.** Rejected (ticket 95): the editor draws the thread, so an old conversation replayed into an open thread would land after what it already shows, and the file would no longer match the editor's session id. Listing sessions to the editor is the protocol's way. |
| 48 | 54 | - **Replaying only text messages.** Rejected by the user: the tool calls are the part of a coding session worth seeing again. |
| 55 | +- **A separate list of typed inputs in the session file (ticket 68).** It would have recalled exactly what was typed — slash commands and paste tokens included — but it was a new field to keep in step with the history, and empty for every existing file. The user preferred the stored messages. | |
| 49 | 56 | - **Recording errors as messages in the history.** Rejected: the model would read them, `session/load` would have to replay them as something, and an old file would load differently from a new one. A separate field costs nothing on load and changes nothing for the model. |
| 50 | 57 | |
| 51 | 58 | ## How it relates to the rest |
| @@ -38,6 +38,12 @@ The array sits beside `messages`, not inside it, on purpose. The model never rea | |||
| 38 | 38 | ||
| 39 | What it is for: asking afterwards, with the file in hand, why a run went wrong — `jq .errors <file>` for a human, or the file handed to a coding agent asked to explain the failures and propose changes to the code or the YAML (a longer `watchdogTimeout`, a different model, a smaller `maxOutput`). The `/sessions` listing and the `/resume` report show ` · ⚠️ N error(s)` so a session worth investigating stands out. The trail is capped at 200 records, oldest first out, so a run that fails on every turn does not grow its file without end. | 39 | What it is for: asking afterwards, with the file in hand, why a run went wrong — `jq .errors <file>` for a human, or the file handed to a coding agent asked to explain the failures and propose changes to the code or the YAML (a longer `watchdogTimeout`, a different model, a smaller `maxOutput`). The `/sessions` listing and the `/resume` report show ` · ⚠️ N error(s)` so a session worth investigating stands out. The trail is capped at 200 records, oldest first out, so a run that fails on every turn does not grow its file without end. |
| 40 | 40 | ||
| 41 | +## Why the window's ↑ reads the stored questions | ||
| 42 | + | ||
| 43 | +In the window, ↑ on the box's first row recalls the questions already asked (ticket 68), and only when sessions are on. The questions are read from the session files themselves — the `user` messages of the conversation in memory, then of the project's other files, newest first — not from a separate list. That was the user's choice: nothing new is written, the recall covers every session the project has kept, including those written before the feature existed, and it disappears with them when the directory is cleaned. | ||
| 44 | + | ||
| 45 | +The price is that a recalled question is what the model read, not exactly what was typed: the `[attached file: …]` lines an `@path` added, a paste in full rather than its token, an `/ask` in its `[asked …]` form; slash commands, never sent to the model, are not there at all. What mm itself wrote as a user message is left out — a `/compact` summary, the note after a broken tool call, the marker of a prompt a hook blocked — and so is the context a `preGeneration` hook appended. One gap the files cannot fill: a turn that fails before answering drops its question from the history, and that is the question one most wants to retry. The window keeps it in memory until the next question, so ↑ still finds it; it is not written anywhere. | ||
| 46 | + | ||
| 41 | ## Rejected alternatives | 47 | ## Rejected alternatives |
| 42 | 48 | ||
| 43 | - **Resume the latest session automatically at start.** Rejected for the stale-context reason above, and because docker agent does not do it either. | 49 | - **Resume the latest session automatically at start.** Rejected for the stale-context reason above, and because docker agent does not do it either. |
| @@ -46,6 +52,7 @@ What it is for: asking afterwards, with the file in hand, why a run went wrong | |||
| 46 | - **`session/resume` instead of `session/load`.** Rejected: it relies on the editor having kept the thread, and when the choice was made it was not in the stable spec. Since ticket 95 it is offered beside `session/load`, not instead of it. | 52 | - **`session/resume` instead of `session/load`.** Rejected: it relies on the editor having kept the thread, and when the choice was made it was not in the stable spec. Since ticket 95 it is offered beside `session/load`, not instead of it. |
| 47 | - **A `/resume` slash command over ACP.** Rejected (ticket 95): the editor draws the thread, so an old conversation replayed into an open thread would land after what it already shows, and the file would no longer match the editor's session id. Listing sessions to the editor is the protocol's way. | 53 | - **A `/resume` slash command over ACP.** Rejected (ticket 95): the editor draws the thread, so an old conversation replayed into an open thread would land after what it already shows, and the file would no longer match the editor's session id. Listing sessions to the editor is the protocol's way. |
| 48 | - **Replaying only text messages.** Rejected by the user: the tool calls are the part of a coding session worth seeing again. | 54 | - **Replaying only text messages.** Rejected by the user: the tool calls are the part of a coding session worth seeing again. |
| 55 | +- **A separate list of typed inputs in the session file (ticket 68).** It would have recalled exactly what was typed — slash commands and paste tokens included — but it was a new field to keep in step with the history, and empty for every existing file. The user preferred the stored messages. | ||
| 49 | - **Recording errors as messages in the history.** Rejected: the model would read them, `session/load` would have to replay them as something, and an old file would load differently from a new one. A separate field costs nothing on load and changes nothing for the model. | 56 | - **Recording errors as messages in the history.** Rejected: the model would read them, `session/load` would have to replay them as something, and an old file would load differently from a new one. A separate field costs nothing on load and changes nothing for the model. |
| 50 | 57 | ||
| 51 | ## How it relates to the rest | 58 | ## How it relates to the rest |
modified
docs/en/how-to/keep-sessions.md +1 -0 | @@ -94,6 +94,7 @@ This guide shows how to make `mm` save every conversation of a project and get o | ||
| 94 | 94 | - **A project you cannot write to.** The banner or the `[acp]` log says `not saved` with the reason; the conversation goes on unpersisted. |
| 95 | 95 | - **Compression.** A `/compact` or an automatic compression rewrites the file with the shortened history; a resumed or reloaded session starts from the summary, like the live one did. The `errors` array is left as it is. |
| 96 | 96 | - **Errors over ACP.** The editor shows the failure in its own way; the record still goes to the thread's file, and `/new` moves it to the archived conversation with the history it belongs to. |
| 97 | +- **Ask an earlier question again (window).** In `mm -tui`, ↑ on the box's first row recalls the questions of this session, then of the earlier ones. See [Recall an earlier question](use-the-window-mode.md#recall-an-earlier-question). | |
| 97 | 98 | - **Starting fresh on purpose.** Nothing to do: every start is a new session. The old ones only come back on `/resume` or on an editor reload. |
| 98 | 99 | |
| 99 | 100 | ## See also |
| @@ -94,6 +94,7 @@ This guide shows how to make `mm` save every conversation of a project and get o | |||
| 94 | - **A project you cannot write to.** The banner or the `[acp]` log says `not saved` with the reason; the conversation goes on unpersisted. | 94 | - **A project you cannot write to.** The banner or the `[acp]` log says `not saved` with the reason; the conversation goes on unpersisted. |
| 95 | - **Compression.** A `/compact` or an automatic compression rewrites the file with the shortened history; a resumed or reloaded session starts from the summary, like the live one did. The `errors` array is left as it is. | 95 | - **Compression.** A `/compact` or an automatic compression rewrites the file with the shortened history; a resumed or reloaded session starts from the summary, like the live one did. The `errors` array is left as it is. |
| 96 | - **Errors over ACP.** The editor shows the failure in its own way; the record still goes to the thread's file, and `/new` moves it to the archived conversation with the history it belongs to. | 96 | - **Errors over ACP.** The editor shows the failure in its own way; the record still goes to the thread's file, and `/new` moves it to the archived conversation with the history it belongs to. |
| 97 | +- **Ask an earlier question again (window).** In `mm -tui`, ↑ on the box's first row recalls the questions of this session, then of the earlier ones. See [Recall an earlier question](use-the-window-mode.md#recall-an-earlier-question). | ||
| 97 | - **Starting fresh on purpose.** Nothing to do: every start is a new session. The old ones only come back on `/resume` or on an editor reload. | 98 | - **Starting fresh on purpose.** Nothing to do: every start is a new session. The old ones only come back on `/resume` or on an editor reload. |
| 98 | 99 | ||
| 99 | ## See also | 100 | ## See also |
modified
docs/en/how-to/use-the-window-mode.md +10 -0 | @@ -71,6 +71,16 @@ Two details follow from "a paste is data you brought, not something you typed": | ||
| 71 | 71 | |
| 72 | 72 | Pastes shorter than the threshold are inserted as text, exactly as before. Bracketed paste must be supported by the terminal for any of this to work: Terminal.app, iTerm2, Ghostty and kitty support it; inside **tmux**, a paste is passed through as bracketed only when the outer terminal and tmux both support it (tmux does since 1.8; `set -g` needs no option for pass-through, but a `paste-buffer` without `-p` sends the text as plain keys, which then arrive as typed characters and are inserted one by one). |
| 73 | 73 | |
| 74 | +## Recall an earlier question | |
| 75 | + | |
| 76 | +With `sessions.enabled: true`, the box remembers what you asked. | |
| 77 | + | |
| 78 | +1. With the focus on the box, press **↑** on its first row: the last question you asked comes back in the box. Press ↑ again for the one before, and so on through this session, then through the project's earlier sessions, newest first. | |
| 79 | +2. Press **↓** on the box's last row to go forward again. Past the newest question, ↓ gives back what you were typing before you started looking back. | |
| 80 | +3. Edit the question if you want, and press **Enter** to send it. Or press **Esc** to clear the box and type something else — when no turn is running, Esc empties the box instead of cancelling. | |
| 81 | + | |
| 82 | +A recalled question is the text mm stored: an `@path` question comes back with its `[attached file: …]` lines, a collapsed paste comes back as its full text, and an `/ask` as `[asked <agent> (…): …]`. Slash commands are not stored and are not recalled. A question whose turn failed or was cancelled is recalled until you ask the next one, even though it is not kept in the session file. With sessions off, or before anything was asked, ↑ on the first row scrolls the conversation as it always did. | |
| 83 | + | |
| 74 | 84 | ## See also |
| 75 | 85 | |
| 76 | 86 | - [Command line](../reference/cli.md) — the `-tui` flag and the window-mode specifics |
| @@ -71,6 +71,16 @@ Two details follow from "a paste is data you brought, not something you typed": | |||
| 71 | 71 | ||
| 72 | Pastes shorter than the threshold are inserted as text, exactly as before. Bracketed paste must be supported by the terminal for any of this to work: Terminal.app, iTerm2, Ghostty and kitty support it; inside **tmux**, a paste is passed through as bracketed only when the outer terminal and tmux both support it (tmux does since 1.8; `set -g` needs no option for pass-through, but a `paste-buffer` without `-p` sends the text as plain keys, which then arrive as typed characters and are inserted one by one). | 72 | Pastes shorter than the threshold are inserted as text, exactly as before. Bracketed paste must be supported by the terminal for any of this to work: Terminal.app, iTerm2, Ghostty and kitty support it; inside **tmux**, a paste is passed through as bracketed only when the outer terminal and tmux both support it (tmux does since 1.8; `set -g` needs no option for pass-through, but a `paste-buffer` without `-p` sends the text as plain keys, which then arrive as typed characters and are inserted one by one). |
| 73 | 73 | ||
| 74 | +## Recall an earlier question | ||
| 75 | + | ||
| 76 | +With `sessions.enabled: true`, the box remembers what you asked. | ||
| 77 | + | ||
| 78 | +1. With the focus on the box, press **↑** on its first row: the last question you asked comes back in the box. Press ↑ again for the one before, and so on through this session, then through the project's earlier sessions, newest first. | ||
| 79 | +2. Press **↓** on the box's last row to go forward again. Past the newest question, ↓ gives back what you were typing before you started looking back. | ||
| 80 | +3. Edit the question if you want, and press **Enter** to send it. Or press **Esc** to clear the box and type something else — when no turn is running, Esc empties the box instead of cancelling. | ||
| 81 | + | ||
| 82 | +A recalled question is the text mm stored: an `@path` question comes back with its `[attached file: …]` lines, a collapsed paste comes back as its full text, and an `/ask` as `[asked <agent> (…): …]`. Slash commands are not stored and are not recalled. A question whose turn failed or was cancelled is recalled until you ask the next one, even though it is not kept in the session file. With sessions off, or before anything was asked, ↑ on the first row scrolls the conversation as it always did. | ||
| 83 | + | ||
| 74 | ## See also | 84 | ## See also |
| 75 | 85 | ||
| 76 | - [Command line](../reference/cli.md) — the `-tui` flag and the window-mode specifics | 86 | - [Command line](../reference/cli.md) — the `-tui` flag and the window-mode specifics |
modified
docs/en/reference/slash-commands.md +2 -2 | @@ -96,10 +96,10 @@ The keys: | ||
| 96 | 96 | |-----|--------| |
| 97 | 97 | | `Enter` | Sends the box (nothing on an empty box). With the picker open on an incomplete name, completes it instead. | |
| 98 | 98 | | `Ctrl-J`, `Alt-Enter` | Inserts a newline in the box. | |
| 99 | -| `Esc` | Cancels the running turn (same as `/abort`); closes the picker; rejects a pending permission. | | |
| 99 | +| `Esc` | Cancels the running turn (same as `/abort`); when no turn runs and the box is not empty, clears the box instead; closes the picker; rejects a pending permission. | | |
| 100 | 100 | | `Ctrl-C` | Cancels the running turn; quits when idle — and quits too when the turn has already been asked to stop and has not. | |
| 101 | 101 | | `Tab` | Moves the focus between the box and the conversation; completes the picker's choice when it is open (`/`, `@` or `$`). | |
| 102 | -| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows — and scroll the conversation from its first/last row. On the conversation: scroll one line. In the picker: move the choice. | | |
| 102 | +| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows. From the first row, `↑` recalls the previous question (sessions on: this session, then the earlier ones, newest first); from the last row during a recall, `↓` the next one, then the text typed before. With nothing to recall, they scroll the conversation from the first/last row. On the conversation: scroll one line. In the picker: move the choice. | | |
| 103 | 103 | | `PgUp` `PgDn` | Scroll one page. | |
| 104 | 104 | | `Home` `End` | In the box: start/end of the line. On the conversation: top / bottom (bottom resumes following). | |
| 105 | 105 | | `Ctrl-L` | Clears the displayed conversation; the history is kept. | |
| @@ -96,10 +96,10 @@ The keys: | |||
| 96 | |-----|--------| | 96 | |-----|--------| |
| 97 | | `Enter` | Sends the box (nothing on an empty box). With the picker open on an incomplete name, completes it instead. | | 97 | | `Enter` | Sends the box (nothing on an empty box). With the picker open on an incomplete name, completes it instead. | |
| 98 | | `Ctrl-J`, `Alt-Enter` | Inserts a newline in the box. | | 98 | | `Ctrl-J`, `Alt-Enter` | Inserts a newline in the box. | |
| 99 | -| `Esc` | Cancels the running turn (same as `/abort`); closes the picker; rejects a pending permission. | | 99 | +| `Esc` | Cancels the running turn (same as `/abort`); when no turn runs and the box is not empty, clears the box instead; closes the picker; rejects a pending permission. | |
| 100 | | `Ctrl-C` | Cancels the running turn; quits when idle — and quits too when the turn has already been asked to stop and has not. | | 100 | | `Ctrl-C` | Cancels the running turn; quits when idle — and quits too when the turn has already been asked to stop and has not. | |
| 101 | | `Tab` | Moves the focus between the box and the conversation; completes the picker's choice when it is open (`/`, `@` or `$`). | | 101 | | `Tab` | Moves the focus between the box and the conversation; completes the picker's choice when it is open (`/`, `@` or `$`). | |
| 102 | -| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows — and scroll the conversation from its first/last row. On the conversation: scroll one line. In the picker: move the choice. | | 102 | +| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows. From the first row, `↑` recalls the previous question (sessions on: this session, then the earlier ones, newest first); from the last row during a recall, `↓` the next one, then the text typed before. With nothing to recall, they scroll the conversation from the first/last row. On the conversation: scroll one line. In the picker: move the choice. | |
| 103 | | `PgUp` `PgDn` | Scroll one page. | | 103 | | `PgUp` `PgDn` | Scroll one page. | |
| 104 | | `Home` `End` | In the box: start/end of the line. On the conversation: top / bottom (bottom resumes following). | | 104 | | `Home` `End` | In the box: start/end of the line. On the conversation: top / bottom (bottom resumes following). | |
| 105 | | `Ctrl-L` | Clears the displayed conversation; the history is kept. | | 105 | | `Ctrl-L` | Clears the displayed conversation; the history is kept. | |
modified
docs/fr/explanation/persistent-sessions.md +7 -0 | @@ -38,6 +38,12 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le | ||
| 38 | 38 | |
| 39 | 39 | À quoi cela sert : demander après coup, le fichier en main, pourquoi une exécution a mal tourné — `jq .errors <fichier>` pour un humain, ou le fichier confié à un agent de code à qui l'on demande d'expliquer les échecs et de proposer des changements de code ou de YAML (un `watchdogTimeout` plus long, un autre modèle, un `maxOutput` plus petit). La liste `/sessions` et le compte rendu de `/resume` affichent ` · ⚠️ N error(s)` pour qu'une session à examiner se remarque. La trace est plafonnée à 200 enregistrements, les plus anciens sortent d'abord, pour qu'une exécution qui échoue à chaque tour ne fasse pas grossir son fichier sans fin. |
| 40 | 40 | |
| 41 | +## Pourquoi le ↑ de la fenêtre lit les questions enregistrées | |
| 42 | + | |
| 43 | +Dans la fenêtre, ↑ depuis la première rangée de la zone rappelle les questions déjà posées (ticket 68), et seulement quand les sessions sont activées. Les questions sont lues dans les fichiers de session eux-mêmes — les messages `user` de la conversation en mémoire, puis des autres fichiers du projet, du plus récent au plus ancien — et non dans une liste à part. C'est le choix de l'utilisateur : rien de nouveau n'est écrit, le rappel couvre toutes les sessions que le projet a gardées, y compris celles écrites avant la fonctionnalité, et il disparaît avec elles quand on vide le répertoire. | |
| 44 | + | |
| 45 | +La contrepartie : une question rappelée est ce que le modèle a lu, pas exactement ce qui a été tapé — les lignes `[attached file: …]` ajoutées par un `@chemin`, un collage en entier plutôt que son jeton, un `/ask` sous sa forme `[asked …]` ; les commandes `/`, jamais envoyées au modèle, n'y sont pas du tout. Ce que mm a écrit lui-même comme message utilisateur est écarté — un résumé de `/compact`, la note après un appel d'outil cassé, le marqueur d'un prompt bloqué par un hook — de même que le contexte ajouté par un hook `preGeneration`. Un trou que les fichiers ne peuvent pas combler : un tour qui échoue avant de répondre retire sa question de l'historique, et c'est justement celle qu'on veut relancer. La fenêtre la garde en mémoire jusqu'à la question suivante, pour que ↑ la retrouve ; elle n'est écrite nulle part. | |
| 46 | + | |
| 41 | 47 | ## Alternatives écartées |
| 42 | 48 | |
| 43 | 49 | - **Reprendre automatiquement la dernière session au lancement.** Écartée pour la raison du contexte périmé, et parce que docker agent ne le fait pas non plus. |
| @@ -46,6 +52,7 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le | ||
| 46 | 52 | - **`session/resume` plutôt que `session/load`.** Écarté : dépendant d'un éditeur qui aurait gardé le fil, et hors de la spécification stable quand le choix a été fait. Depuis le ticket 95, il est proposé à côté de `session/load`, pas à sa place. |
| 47 | 53 | - **Une commande `/resume` en ACP.** Écartée (ticket 95) : c'est l'éditeur qui dessine le fil, donc une ancienne conversation rejouée dans un fil ouvert arriverait après ce qu'il affiche déjà, et le fichier ne correspondrait plus à l'identifiant de session de l'éditeur. Lister les sessions à l'éditeur est la voie du protocole. |
| 48 | 54 | - **Ne rejouer que les messages texte.** Écarté par l'utilisateur : les appels d'outils sont la partie d'une session de code qui vaut d'être revue. |
| 55 | +- **Une liste à part des saisies dans le fichier de session (ticket 68).** Elle aurait rappelé exactement ce qui a été tapé — commandes `/` et jetons de collage compris — mais c'était un nouveau champ à tenir en phase avec l'historique, vide pour tous les fichiers existants. L'utilisateur a préféré les messages enregistrés. | |
| 49 | 56 | - **Enregistrer les erreurs comme des messages de l'historique.** Écarté : le modèle les lirait, `session/load` devrait les rejouer sous une forme ou une autre, et un vieux fichier se chargerait autrement qu'un nouveau. Un champ séparé ne coûte rien au chargement et ne change rien pour le modèle. |
| 50 | 57 | |
| 51 | 58 | ## Liens avec le reste |
| @@ -38,6 +38,12 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le | |||
| 38 | 38 | ||
| 39 | À quoi cela sert : demander après coup, le fichier en main, pourquoi une exécution a mal tourné — `jq .errors <fichier>` pour un humain, ou le fichier confié à un agent de code à qui l'on demande d'expliquer les échecs et de proposer des changements de code ou de YAML (un `watchdogTimeout` plus long, un autre modèle, un `maxOutput` plus petit). La liste `/sessions` et le compte rendu de `/resume` affichent ` · ⚠️ N error(s)` pour qu'une session à examiner se remarque. La trace est plafonnée à 200 enregistrements, les plus anciens sortent d'abord, pour qu'une exécution qui échoue à chaque tour ne fasse pas grossir son fichier sans fin. | 39 | À quoi cela sert : demander après coup, le fichier en main, pourquoi une exécution a mal tourné — `jq .errors <fichier>` pour un humain, ou le fichier confié à un agent de code à qui l'on demande d'expliquer les échecs et de proposer des changements de code ou de YAML (un `watchdogTimeout` plus long, un autre modèle, un `maxOutput` plus petit). La liste `/sessions` et le compte rendu de `/resume` affichent ` · ⚠️ N error(s)` pour qu'une session à examiner se remarque. La trace est plafonnée à 200 enregistrements, les plus anciens sortent d'abord, pour qu'une exécution qui échoue à chaque tour ne fasse pas grossir son fichier sans fin. |
| 40 | 40 | ||
| 41 | +## Pourquoi le ↑ de la fenêtre lit les questions enregistrées | ||
| 42 | + | ||
| 43 | +Dans la fenêtre, ↑ depuis la première rangée de la zone rappelle les questions déjà posées (ticket 68), et seulement quand les sessions sont activées. Les questions sont lues dans les fichiers de session eux-mêmes — les messages `user` de la conversation en mémoire, puis des autres fichiers du projet, du plus récent au plus ancien — et non dans une liste à part. C'est le choix de l'utilisateur : rien de nouveau n'est écrit, le rappel couvre toutes les sessions que le projet a gardées, y compris celles écrites avant la fonctionnalité, et il disparaît avec elles quand on vide le répertoire. | ||
| 44 | + | ||
| 45 | +La contrepartie : une question rappelée est ce que le modèle a lu, pas exactement ce qui a été tapé — les lignes `[attached file: …]` ajoutées par un `@chemin`, un collage en entier plutôt que son jeton, un `/ask` sous sa forme `[asked …]` ; les commandes `/`, jamais envoyées au modèle, n'y sont pas du tout. Ce que mm a écrit lui-même comme message utilisateur est écarté — un résumé de `/compact`, la note après un appel d'outil cassé, le marqueur d'un prompt bloqué par un hook — de même que le contexte ajouté par un hook `preGeneration`. Un trou que les fichiers ne peuvent pas combler : un tour qui échoue avant de répondre retire sa question de l'historique, et c'est justement celle qu'on veut relancer. La fenêtre la garde en mémoire jusqu'à la question suivante, pour que ↑ la retrouve ; elle n'est écrite nulle part. | ||
| 46 | + | ||
| 41 | ## Alternatives écartées | 47 | ## Alternatives écartées |
| 42 | 48 | ||
| 43 | - **Reprendre automatiquement la dernière session au lancement.** Écartée pour la raison du contexte périmé, et parce que docker agent ne le fait pas non plus. | 49 | - **Reprendre automatiquement la dernière session au lancement.** Écartée pour la raison du contexte périmé, et parce que docker agent ne le fait pas non plus. |
| @@ -46,6 +52,7 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le | |||
| 46 | - **`session/resume` plutôt que `session/load`.** Écarté : dépendant d'un éditeur qui aurait gardé le fil, et hors de la spécification stable quand le choix a été fait. Depuis le ticket 95, il est proposé à côté de `session/load`, pas à sa place. | 52 | - **`session/resume` plutôt que `session/load`.** Écarté : dépendant d'un éditeur qui aurait gardé le fil, et hors de la spécification stable quand le choix a été fait. Depuis le ticket 95, il est proposé à côté de `session/load`, pas à sa place. |
| 47 | - **Une commande `/resume` en ACP.** Écartée (ticket 95) : c'est l'éditeur qui dessine le fil, donc une ancienne conversation rejouée dans un fil ouvert arriverait après ce qu'il affiche déjà, et le fichier ne correspondrait plus à l'identifiant de session de l'éditeur. Lister les sessions à l'éditeur est la voie du protocole. | 53 | - **Une commande `/resume` en ACP.** Écartée (ticket 95) : c'est l'éditeur qui dessine le fil, donc une ancienne conversation rejouée dans un fil ouvert arriverait après ce qu'il affiche déjà, et le fichier ne correspondrait plus à l'identifiant de session de l'éditeur. Lister les sessions à l'éditeur est la voie du protocole. |
| 48 | - **Ne rejouer que les messages texte.** Écarté par l'utilisateur : les appels d'outils sont la partie d'une session de code qui vaut d'être revue. | 54 | - **Ne rejouer que les messages texte.** Écarté par l'utilisateur : les appels d'outils sont la partie d'une session de code qui vaut d'être revue. |
| 55 | +- **Une liste à part des saisies dans le fichier de session (ticket 68).** Elle aurait rappelé exactement ce qui a été tapé — commandes `/` et jetons de collage compris — mais c'était un nouveau champ à tenir en phase avec l'historique, vide pour tous les fichiers existants. L'utilisateur a préféré les messages enregistrés. | ||
| 49 | - **Enregistrer les erreurs comme des messages de l'historique.** Écarté : le modèle les lirait, `session/load` devrait les rejouer sous une forme ou une autre, et un vieux fichier se chargerait autrement qu'un nouveau. Un champ séparé ne coûte rien au chargement et ne change rien pour le modèle. | 56 | - **Enregistrer les erreurs comme des messages de l'historique.** Écarté : le modèle les lirait, `session/load` devrait les rejouer sous une forme ou une autre, et un vieux fichier se chargerait autrement qu'un nouveau. Un champ séparé ne coûte rien au chargement et ne change rien pour le modèle. |
| 50 | 57 | ||
| 51 | ## Liens avec le reste | 58 | ## Liens avec le reste |
modified
docs/fr/how-to/keep-sessions.md +1 -0 | @@ -94,6 +94,7 @@ Ce guide montre comment faire enregistrer par `mm` chaque conversation d'un proj | ||
| 94 | 94 | - **Un projet en lecture seule.** La bannière ou le journal `[acp]` indique `not saved` avec la raison ; la conversation continue sans être enregistrée. |
| 95 | 95 | - **Compression.** Un `/compact` ou une compression automatique réécrit le fichier avec l'historique raccourci ; une session reprise ou rechargée repart du résumé, comme la session en direct. Le tableau `errors` reste tel quel. |
| 96 | 96 | - **Erreurs en ACP.** L'éditeur montre l'échec à sa manière ; l'enregistrement va quand même dans le fichier du fil, et `/new` le déplace dans la conversation archivée avec l'historique auquel il appartient. |
| 97 | +- **Reposer une question précédente (fenêtre).** Dans `mm -tui`, ↑ depuis la première rangée de la zone rappelle les questions de cette session, puis des précédentes. Voir [Rappeler une question précédente](use-the-window-mode.md#rappeler-une-question-précédente). | |
| 97 | 98 | - **Repartir de zéro volontairement.** Rien à faire : chaque lancement est une nouvelle session. Les anciennes ne reviennent que sur `/resume` ou sur un rechargement depuis l'éditeur. |
| 98 | 99 | |
| 99 | 100 | ## Voir aussi |
| @@ -94,6 +94,7 @@ Ce guide montre comment faire enregistrer par `mm` chaque conversation d'un proj | |||
| 94 | - **Un projet en lecture seule.** La bannière ou le journal `[acp]` indique `not saved` avec la raison ; la conversation continue sans être enregistrée. | 94 | - **Un projet en lecture seule.** La bannière ou le journal `[acp]` indique `not saved` avec la raison ; la conversation continue sans être enregistrée. |
| 95 | - **Compression.** Un `/compact` ou une compression automatique réécrit le fichier avec l'historique raccourci ; une session reprise ou rechargée repart du résumé, comme la session en direct. Le tableau `errors` reste tel quel. | 95 | - **Compression.** Un `/compact` ou une compression automatique réécrit le fichier avec l'historique raccourci ; une session reprise ou rechargée repart du résumé, comme la session en direct. Le tableau `errors` reste tel quel. |
| 96 | - **Erreurs en ACP.** L'éditeur montre l'échec à sa manière ; l'enregistrement va quand même dans le fichier du fil, et `/new` le déplace dans la conversation archivée avec l'historique auquel il appartient. | 96 | - **Erreurs en ACP.** L'éditeur montre l'échec à sa manière ; l'enregistrement va quand même dans le fichier du fil, et `/new` le déplace dans la conversation archivée avec l'historique auquel il appartient. |
| 97 | +- **Reposer une question précédente (fenêtre).** Dans `mm -tui`, ↑ depuis la première rangée de la zone rappelle les questions de cette session, puis des précédentes. Voir [Rappeler une question précédente](use-the-window-mode.md#rappeler-une-question-précédente). | ||
| 97 | - **Repartir de zéro volontairement.** Rien à faire : chaque lancement est une nouvelle session. Les anciennes ne reviennent que sur `/resume` ou sur un rechargement depuis l'éditeur. | 98 | - **Repartir de zéro volontairement.** Rien à faire : chaque lancement est une nouvelle session. Les anciennes ne reviennent que sur `/resume` ou sur un rechargement depuis l'éditeur. |
| 98 | 99 | ||
| 99 | ## Voir aussi | 100 | ## Voir aussi |
modified
docs/fr/how-to/use-the-window-mode.md +10 -0 | @@ -71,6 +71,16 @@ Deux détails découlent de « un collage est une donnée que vous apportez, pas | ||
| 71 | 71 | |
| 72 | 72 | Les collages plus courts que le seuil sont insérés comme du texte, exactement comme avant. Le terminal doit prendre en charge le collage encadré (bracketed paste) pour que tout cela fonctionne : Terminal.app, iTerm2, Ghostty et kitty le font ; dans **tmux**, un collage n'arrive encadré que si le terminal extérieur et tmux le prennent en charge tous deux (tmux le fait depuis la 1.8 ; aucune option `set -g` n'est nécessaire pour le passage, mais un `paste-buffer` sans `-p` envoie le texte comme des touches ordinaires, qui arrivent alors comme des caractères tapés et sont insérés un à un). |
| 73 | 73 | |
| 74 | +## Rappeler une question précédente | |
| 75 | + | |
| 76 | +Avec `sessions.enabled: true`, la zone de saisie se souvient de ce que vous avez demandé. | |
| 77 | + | |
| 78 | +1. Le focus sur la zone, appuyez sur **↑** depuis sa première rangée : votre dernière question revient dans la zone. Appuyez encore sur ↑ pour la précédente, et ainsi de suite dans cette session, puis dans les sessions précédentes du projet, de la plus récente à la plus ancienne. | |
| 79 | +2. Appuyez sur **↓** depuis la dernière rangée de la zone pour revenir vers les plus récentes. Après la plus récente, ↓ rend ce que vous tapiez avant de remonter. | |
| 80 | +3. Modifiez la question si besoin, puis appuyez sur **Entrée** pour l'envoyer. Ou appuyez sur **Échap** pour vider la zone et taper autre chose — quand aucun tour ne tourne, Échap vide la zone au lieu d'annuler. | |
| 81 | + | |
| 82 | +Une question rappelée est le texte que mm a enregistré : une question avec `@chemin` revient avec ses lignes `[attached file: …]`, un collage replié revient en texte complet, un `/ask` sous la forme `[asked <agent> (…): …]`. Les commandes `/` ne sont pas enregistrées et ne sont pas rappelées. La question d'un tour qui a échoué ou a été annulé reste rappelable jusqu'à la question suivante, bien qu'elle ne soit pas gardée dans le fichier de session. Sessions désactivées, ou avant toute question, ↑ depuis la première rangée fait défiler la conversation comme avant. | |
| 83 | + | |
| 74 | 84 | ## Voir aussi |
| 75 | 85 | |
| 76 | 86 | - [Ligne de commande](../reference/cli.md) — l'option `-tui` et les spécificités du mode fenêtre |
| @@ -71,6 +71,16 @@ Deux détails découlent de « un collage est une donnée que vous apportez, pas | |||
| 71 | 71 | ||
| 72 | Les collages plus courts que le seuil sont insérés comme du texte, exactement comme avant. Le terminal doit prendre en charge le collage encadré (bracketed paste) pour que tout cela fonctionne : Terminal.app, iTerm2, Ghostty et kitty le font ; dans **tmux**, un collage n'arrive encadré que si le terminal extérieur et tmux le prennent en charge tous deux (tmux le fait depuis la 1.8 ; aucune option `set -g` n'est nécessaire pour le passage, mais un `paste-buffer` sans `-p` envoie le texte comme des touches ordinaires, qui arrivent alors comme des caractères tapés et sont insérés un à un). | 72 | Les collages plus courts que le seuil sont insérés comme du texte, exactement comme avant. Le terminal doit prendre en charge le collage encadré (bracketed paste) pour que tout cela fonctionne : Terminal.app, iTerm2, Ghostty et kitty le font ; dans **tmux**, un collage n'arrive encadré que si le terminal extérieur et tmux le prennent en charge tous deux (tmux le fait depuis la 1.8 ; aucune option `set -g` n'est nécessaire pour le passage, mais un `paste-buffer` sans `-p` envoie le texte comme des touches ordinaires, qui arrivent alors comme des caractères tapés et sont insérés un à un). |
| 73 | 73 | ||
| 74 | +## Rappeler une question précédente | ||
| 75 | + | ||
| 76 | +Avec `sessions.enabled: true`, la zone de saisie se souvient de ce que vous avez demandé. | ||
| 77 | + | ||
| 78 | +1. Le focus sur la zone, appuyez sur **↑** depuis sa première rangée : votre dernière question revient dans la zone. Appuyez encore sur ↑ pour la précédente, et ainsi de suite dans cette session, puis dans les sessions précédentes du projet, de la plus récente à la plus ancienne. | ||
| 79 | +2. Appuyez sur **↓** depuis la dernière rangée de la zone pour revenir vers les plus récentes. Après la plus récente, ↓ rend ce que vous tapiez avant de remonter. | ||
| 80 | +3. Modifiez la question si besoin, puis appuyez sur **Entrée** pour l'envoyer. Ou appuyez sur **Échap** pour vider la zone et taper autre chose — quand aucun tour ne tourne, Échap vide la zone au lieu d'annuler. | ||
| 81 | + | ||
| 82 | +Une question rappelée est le texte que mm a enregistré : une question avec `@chemin` revient avec ses lignes `[attached file: …]`, un collage replié revient en texte complet, un `/ask` sous la forme `[asked <agent> (…): …]`. Les commandes `/` ne sont pas enregistrées et ne sont pas rappelées. La question d'un tour qui a échoué ou a été annulé reste rappelable jusqu'à la question suivante, bien qu'elle ne soit pas gardée dans le fichier de session. Sessions désactivées, ou avant toute question, ↑ depuis la première rangée fait défiler la conversation comme avant. | ||
| 83 | + | ||
| 74 | ## Voir aussi | 84 | ## Voir aussi |
| 75 | 85 | ||
| 76 | - [Ligne de commande](../reference/cli.md) — l'option `-tui` et les spécificités du mode fenêtre | 86 | - [Ligne de commande](../reference/cli.md) — l'option `-tui` et les spécificités du mode fenêtre |
modified
docs/fr/reference/slash-commands.md +2 -2 | @@ -96,10 +96,10 @@ Les touches : | ||
| 96 | 96 | |--------|--------| |
| 97 | 97 | | `Entrée` | Envoie la zone (rien sur une zone vide). Avec le sélecteur ouvert sur un nom incomplet, le complète à la place. | |
| 98 | 98 | | `Ctrl-J`, `Alt-Entrée` | Insère un saut de ligne dans la zone. | |
| 99 | -| `Échap` | Annule le tour en cours (comme `/abort`) ; ferme le sélecteur ; refuse une permission en attente. | | |
| 99 | +| `Échap` | Annule le tour en cours (comme `/abort`) ; quand aucun tour ne tourne et que la zone n'est pas vide, vide la zone à la place ; ferme le sélecteur ; refuse une permission en attente. | | |
| 100 | 100 | | `Ctrl-C` | Annule le tour en cours ; quitte au repos — et quitte aussi lorsque le tour a déjà reçu l'ordre de s'arrêter et ne l'a pas fait. | |
| 101 | 101 | | `Tab` | Déplace le focus entre la zone et la conversation ; complète le choix du sélecteur quand il est ouvert (`/`, `@` ou `$`). | |
| 102 | -| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées — et font défiler la conversation depuis sa première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. | | |
| 102 | +| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées. Depuis la première rangée, `↑` rappelle la question précédente (sessions activées : cette session, puis les précédentes, de la plus récente à la plus ancienne) ; depuis la dernière rangée pendant un rappel, `↓` la suivante, puis le texte tapé avant. Sans rien à rappeler, ils font défiler la conversation depuis la première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. | | |
| 103 | 103 | | `PgUp` `PgDn` | Défilent d'une page. | |
| 104 | 104 | | `Début` `Fin` | Dans la zone : début/fin de ligne. Sur la conversation : haut / bas (le bas reprend le suivi). | |
| 105 | 105 | | `Ctrl-L` | Efface la conversation affichée ; l'historique est conservé. | |
| @@ -96,10 +96,10 @@ Les touches : | |||
| 96 | |--------|--------| | 96 | |--------|--------| |
| 97 | | `Entrée` | Envoie la zone (rien sur une zone vide). Avec le sélecteur ouvert sur un nom incomplet, le complète à la place. | | 97 | | `Entrée` | Envoie la zone (rien sur une zone vide). Avec le sélecteur ouvert sur un nom incomplet, le complète à la place. | |
| 98 | | `Ctrl-J`, `Alt-Entrée` | Insère un saut de ligne dans la zone. | | 98 | | `Ctrl-J`, `Alt-Entrée` | Insère un saut de ligne dans la zone. | |
| 99 | -| `Échap` | Annule le tour en cours (comme `/abort`) ; ferme le sélecteur ; refuse une permission en attente. | | 99 | +| `Échap` | Annule le tour en cours (comme `/abort`) ; quand aucun tour ne tourne et que la zone n'est pas vide, vide la zone à la place ; ferme le sélecteur ; refuse une permission en attente. | |
| 100 | | `Ctrl-C` | Annule le tour en cours ; quitte au repos — et quitte aussi lorsque le tour a déjà reçu l'ordre de s'arrêter et ne l'a pas fait. | | 100 | | `Ctrl-C` | Annule le tour en cours ; quitte au repos — et quitte aussi lorsque le tour a déjà reçu l'ordre de s'arrêter et ne l'a pas fait. | |
| 101 | | `Tab` | Déplace le focus entre la zone et la conversation ; complète le choix du sélecteur quand il est ouvert (`/`, `@` ou `$`). | | 101 | | `Tab` | Déplace le focus entre la zone et la conversation ; complète le choix du sélecteur quand il est ouvert (`/`, `@` ou `$`). | |
| 102 | -| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées — et font défiler la conversation depuis sa première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. | | 102 | +| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées. Depuis la première rangée, `↑` rappelle la question précédente (sessions activées : cette session, puis les précédentes, de la plus récente à la plus ancienne) ; depuis la dernière rangée pendant un rappel, `↓` la suivante, puis le texte tapé avant. Sans rien à rappeler, ils font défiler la conversation depuis la première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. | |
| 103 | | `PgUp` `PgDn` | Défilent d'une page. | | 103 | | `PgUp` `PgDn` | Défilent d'une page. | |
| 104 | | `Début` `Fin` | Dans la zone : début/fin de ligne. Sur la conversation : haut / bas (le bas reprend le suivi). | | 104 | | `Début` `Fin` | Dans la zone : début/fin de ligne. Sur la conversation : haut / bas (le bas reprend le suivi). | |
| 105 | | `Ctrl-L` | Efface la conversation affichée ; l'historique est conservé. | | 105 | | `Ctrl-L` | Efface la conversation affichée ; l'historique est conservé. | |
added
internal/engine/notes.go +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +package engine | |
| 2 | + | |
| 3 | +import "strings" | |
| 4 | + | |
| 5 | +// argsRetryPrefix opens the note argsRetryNote hands the model in place of a | |
| 6 | +// tool call whose arguments did not parse. | |
| 7 | +const argsRetryPrefix = "[mm: " | |
| 8 | + | |
| 9 | +// IsNote reports whether a user message's text was written by mm itself — | |
| 10 | +// the retry note after a broken tool call, the marker left where a hook | |
| 11 | +// blocked a prompt — rather than typed by the user. A front end recalling | |
| 12 | +// the questions it was asked skips these. | |
| 13 | +// | |
| 14 | +// engine.IsNote("[prompt blocked by a hook: secret]") // true | |
| 15 | +// engine.IsNote("explain main.go") // false | |
| 16 | +func IsNote(text string) bool { | |
| 17 | + return strings.HasPrefix(text, argsRetryPrefix) || strings.HasPrefix(text, blockedPrompt) | |
| 18 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +package engine | ||
| 2 | + | ||
| 3 | +import "strings" | ||
| 4 | + | ||
| 5 | +// argsRetryPrefix opens the note argsRetryNote hands the model in place of a | ||
| 6 | +// tool call whose arguments did not parse. | ||
| 7 | +const argsRetryPrefix = "[mm: " | ||
| 8 | + | ||
| 9 | +// IsNote reports whether a user message's text was written by mm itself — | ||
| 10 | +// the retry note after a broken tool call, the marker left where a hook | ||
| 11 | +// blocked a prompt — rather than typed by the user. A front end recalling | ||
| 12 | +// the questions it was asked skips these. | ||
| 13 | +// | ||
| 14 | +// engine.IsNote("[prompt blocked by a hook: secret]") // true | ||
| 15 | +// engine.IsNote("explain main.go") // false | ||
| 16 | +func IsNote(text string) bool { | ||
| 17 | + return strings.HasPrefix(text, argsRetryPrefix) || strings.HasPrefix(text, blockedPrompt) | ||
| 18 | +} | ||
added
internal/engine/notes_test.go +23 -0 | new file mode 100644 | ||
| @@ -0,0 +1,23 @@ | ||
| 1 | +package engine | |
| 2 | + | |
| 3 | +import "testing" | |
| 4 | + | |
| 5 | +// The notes mm writes into the history are recognised from the very messages | |
| 6 | +// that write them, so a reworded note cannot slip past IsNote. | |
| 7 | +func TestIsNoteRecognisesMmOwnMessages(t *testing.T) { | |
| 8 | + for _, text := range []string{ | |
| 9 | + argsRetryNote(true).Text(), | |
| 10 | + argsRetryNote(false).Text(), | |
| 11 | + blockedMarker(""), | |
| 12 | + blockedMarker("a secret"), | |
| 13 | + } { | |
| 14 | + if !IsNote(text) { | |
| 15 | + t.Errorf("IsNote(%q) = false, want true", text) | |
| 16 | + } | |
| 17 | + } | |
| 18 | + for _, text := range []string{"explain main.go", "[draft] fix the tests", ""} { | |
| 19 | + if IsNote(text) { | |
| 20 | + t.Errorf("IsNote(%q) = true, want false", text) | |
| 21 | + } | |
| 22 | + } | |
| 23 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | +package engine | ||
| 2 | + | ||
| 3 | +import "testing" | ||
| 4 | + | ||
| 5 | +// The notes mm writes into the history are recognised from the very messages | ||
| 6 | +// that write them, so a reworded note cannot slip past IsNote. | ||
| 7 | +func TestIsNoteRecognisesMmOwnMessages(t *testing.T) { | ||
| 8 | + for _, text := range []string{ | ||
| 9 | + argsRetryNote(true).Text(), | ||
| 10 | + argsRetryNote(false).Text(), | ||
| 11 | + blockedMarker(""), | ||
| 12 | + blockedMarker("a secret"), | ||
| 13 | + } { | ||
| 14 | + if !IsNote(text) { | ||
| 15 | + t.Errorf("IsNote(%q) = false, want true", text) | ||
| 16 | + } | ||
| 17 | + } | ||
| 18 | + for _, text := range []string{"explain main.go", "[draft] fix the tests", ""} { | ||
| 19 | + if IsNote(text) { | ||
| 20 | + t.Errorf("IsNote(%q) = true, want false", text) | ||
| 21 | + } | ||
| 22 | + } | ||
| 23 | +} | ||
modified
internal/engine/toolargs.go +1 -1 | @@ -71,7 +71,7 @@ func pieceChars() int { | ||
| 71 | 71 | // the call never became one. |
| 72 | 72 | func argsRetryNote(truncated bool) *ai.Message { |
| 73 | 73 | var b strings.Builder |
| 74 | - b.WriteString("[mm: your last tool call was not run: ") | |
| 74 | + b.WriteString(argsRetryPrefix + "your last tool call was not run: ") | |
| 75 | 75 | if truncated { |
| 76 | 76 | b.WriteString("it was cut off before its end") |
| 77 | 77 | if mt := maxTokens(); mt != "" { |
| @@ -71,7 +71,7 @@ func pieceChars() int { | |||
| 71 | // the call never became one. | 71 | // the call never became one. |
| 72 | func argsRetryNote(truncated bool) *ai.Message { | 72 | func argsRetryNote(truncated bool) *ai.Message { |
| 73 | var b strings.Builder | 73 | var b strings.Builder |
| 74 | - b.WriteString("[mm: your last tool call was not run: ") | 74 | + b.WriteString(argsRetryPrefix + "your last tool call was not run: ") |
| 75 | if truncated { | 75 | if truncated { |
| 76 | b.WriteString("it was cut off before its end") | 76 | b.WriteString("it was cut off before its end") |
| 77 | if mt := maxTokens(); mt != "" { | 77 | if mt := maxTokens(); mt != "" { |
added
internal/store/questions.go +71 -0 | new file mode 100644 | ||
| @@ -0,0 +1,71 @@ | ||
| 1 | +package store | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "strings" | |
| 5 | + | |
| 6 | + "mm/internal/compact" | |
| 7 | + "mm/internal/engine" | |
| 8 | + "mm/internal/hooks" | |
| 9 | + | |
| 10 | + "github.com/firebase/genkit/go/ai" | |
| 11 | +) | |
| 12 | + | |
| 13 | +// maxQuestions caps what Questions returns: enough to scroll back through | |
| 14 | +// days of work, few enough that a long-lived project does not hand the | |
| 15 | +// window thousands of entries to step through. | |
| 16 | +const maxQuestions = 200 | |
| 17 | + | |
| 18 | +// Questions is what the window's ↑ steps back through (ticket 68): the user | |
| 19 | +// questions of the conversation in progress, newest first, then those of the | |
| 20 | +// project's other stored sessions, the most recently updated first. They are | |
| 21 | +// read from the stored history, so a question comes back as mm kept it — | |
| 22 | +// with its `[attached file: …]` lines and its pastes in full. What the user | |
| 23 | +// never typed is left out: a /compact summary, mm's own notes (engine.IsNote), | |
| 24 | +// and the context a preGeneration hook appended. Two identical questions in | |
| 25 | +// a row are kept once. A nil Keeper — sessions off — has nothing to recall. | |
| 26 | +// | |
| 27 | +// recall := keep.Questions(messages) // recall[0] is the last question asked | |
| 28 | +func (k *Keeper) Questions(current []*ai.Message) []string { | |
| 29 | + if k == nil { | |
| 30 | + return nil | |
| 31 | + } | |
| 32 | + var out []string | |
| 33 | + add := func(msgs []*ai.Message) bool { | |
| 34 | + for i := len(msgs) - 1; i >= 0; i-- { | |
| 35 | + q, ok := question(msgs[i]) | |
| 36 | + if !ok || (len(out) > 0 && out[len(out)-1] == q) { | |
| 37 | + continue | |
| 38 | + } | |
| 39 | + if out = append(out, q); len(out) == maxQuestions { | |
| 40 | + return false | |
| 41 | + } | |
| 42 | + } | |
| 43 | + return true | |
| 44 | + } | |
| 45 | + if !add(current) { | |
| 46 | + return out | |
| 47 | + } | |
| 48 | + for _, sess := range k.st.all() { | |
| 49 | + if k.rec != nil && sess.ID == k.rec.ID { | |
| 50 | + continue // its questions are in current, as they stand now | |
| 51 | + } | |
| 52 | + if !add(sess.Messages) { | |
| 53 | + break | |
| 54 | + } | |
| 55 | + } | |
| 56 | + return out | |
| 57 | +} | |
| 58 | + | |
| 59 | +// question is the text a user message holds as the user typed it, and | |
| 60 | +// whether it is a question at all. | |
| 61 | +func question(m *ai.Message) (string, bool) { | |
| 62 | + if m == nil || m.Role != ai.RoleUser || compact.IsSummary(m) { | |
| 63 | + return "", false | |
| 64 | + } | |
| 65 | + text, _, _ := strings.Cut(m.Text(), "\n\n"+hooks.ContextLabel) | |
| 66 | + text = strings.TrimSpace(text) | |
| 67 | + if text == "" || engine.IsNote(text) { | |
| 68 | + return "", false | |
| 69 | + } | |
| 70 | + return text, true | |
| 71 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | +package store | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "strings" | ||
| 5 | + | ||
| 6 | + "mm/internal/compact" | ||
| 7 | + "mm/internal/engine" | ||
| 8 | + "mm/internal/hooks" | ||
| 9 | + | ||
| 10 | + "github.com/firebase/genkit/go/ai" | ||
| 11 | +) | ||
| 12 | + | ||
| 13 | +// maxQuestions caps what Questions returns: enough to scroll back through | ||
| 14 | +// days of work, few enough that a long-lived project does not hand the | ||
| 15 | +// window thousands of entries to step through. | ||
| 16 | +const maxQuestions = 200 | ||
| 17 | + | ||
| 18 | +// Questions is what the window's ↑ steps back through (ticket 68): the user | ||
| 19 | +// questions of the conversation in progress, newest first, then those of the | ||
| 20 | +// project's other stored sessions, the most recently updated first. They are | ||
| 21 | +// read from the stored history, so a question comes back as mm kept it — | ||
| 22 | +// with its `[attached file: …]` lines and its pastes in full. What the user | ||
| 23 | +// never typed is left out: a /compact summary, mm's own notes (engine.IsNote), | ||
| 24 | +// and the context a preGeneration hook appended. Two identical questions in | ||
| 25 | +// a row are kept once. A nil Keeper — sessions off — has nothing to recall. | ||
| 26 | +// | ||
| 27 | +// recall := keep.Questions(messages) // recall[0] is the last question asked | ||
| 28 | +func (k *Keeper) Questions(current []*ai.Message) []string { | ||
| 29 | + if k == nil { | ||
| 30 | + return nil | ||
| 31 | + } | ||
| 32 | + var out []string | ||
| 33 | + add := func(msgs []*ai.Message) bool { | ||
| 34 | + for i := len(msgs) - 1; i >= 0; i-- { | ||
| 35 | + q, ok := question(msgs[i]) | ||
| 36 | + if !ok || (len(out) > 0 && out[len(out)-1] == q) { | ||
| 37 | + continue | ||
| 38 | + } | ||
| 39 | + if out = append(out, q); len(out) == maxQuestions { | ||
| 40 | + return false | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | + return true | ||
| 44 | + } | ||
| 45 | + if !add(current) { | ||
| 46 | + return out | ||
| 47 | + } | ||
| 48 | + for _, sess := range k.st.all() { | ||
| 49 | + if k.rec != nil && sess.ID == k.rec.ID { | ||
| 50 | + continue // its questions are in current, as they stand now | ||
| 51 | + } | ||
| 52 | + if !add(sess.Messages) { | ||
| 53 | + break | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + return out | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +// question is the text a user message holds as the user typed it, and | ||
| 60 | +// whether it is a question at all. | ||
| 61 | +func question(m *ai.Message) (string, bool) { | ||
| 62 | + if m == nil || m.Role != ai.RoleUser || compact.IsSummary(m) { | ||
| 63 | + return "", false | ||
| 64 | + } | ||
| 65 | + text, _, _ := strings.Cut(m.Text(), "\n\n"+hooks.ContextLabel) | ||
| 66 | + text = strings.TrimSpace(text) | ||
| 67 | + if text == "" || engine.IsNote(text) { | ||
| 68 | + return "", false | ||
| 69 | + } | ||
| 70 | + return text, true | ||
| 71 | +} | ||
added
internal/store/questions_test.go +103 -0 | new file mode 100644 | ||
| @@ -0,0 +1,103 @@ | ||
| 1 | +package store | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "encoding/json" | |
| 5 | + "path/filepath" | |
| 6 | + "reflect" | |
| 7 | + "testing" | |
| 8 | + "time" | |
| 9 | + | |
| 10 | + "mm/internal/compact" | |
| 11 | + "mm/internal/hooks" | |
| 12 | + | |
| 13 | + "github.com/firebase/genkit/go/ai" | |
| 14 | +) | |
| 15 | + | |
| 16 | +// stored writes a session file as it would be after a save at `updated`: | |
| 17 | +// Save stamps the wall clock, which would make the order of two files saved | |
| 18 | +// in a row depend on the clock's resolution. | |
| 19 | +func stored(t *testing.T, k *Keeper, id string, updated time.Time, msgs []*ai.Message) { | |
| 20 | + t.Helper() | |
| 21 | + data, err := json.Marshal(&Session{ID: id, UpdatedAt: updated, Messages: msgs}) | |
| 22 | + if err != nil { | |
| 23 | + t.Fatal(err) | |
| 24 | + } | |
| 25 | + if err := writeAtomic(filepath.Join(k.Dir(), id+".json"), data); err != nil { | |
| 26 | + t.Fatal(err) | |
| 27 | + } | |
| 28 | +} | |
| 29 | + | |
| 30 | +func newQuestionsKeeper(t *testing.T) *Keeper { | |
| 31 | + t.Helper() | |
| 32 | + withSessions(t, true) | |
| 33 | + k := NewKeeper(t.TempDir()) | |
| 34 | + if err := k.Save(nil); err != nil { // creates the directory and the current file | |
| 35 | + t.Fatal(err) | |
| 36 | + } | |
| 37 | + return k | |
| 38 | +} | |
| 39 | + | |
| 40 | +func TestQuestionsNilKeeperHasNothingToRecall(t *testing.T) { | |
| 41 | + var k *Keeper | |
| 42 | + if got := k.Questions(talk(nil, "hello")); got != nil { | |
| 43 | + t.Errorf("nil keeper recalled %q", got) | |
| 44 | + } | |
| 45 | +} | |
| 46 | + | |
| 47 | +// The conversation in progress comes first, newest first; then the other | |
| 48 | +// sessions, the most recently updated first. The current session's own file | |
| 49 | +// is not read again: the history in memory is the one that counts. | |
| 50 | +func TestQuestionsOrderCurrentThenEarlierSessions(t *testing.T) { | |
| 51 | + k := newQuestionsKeeper(t) | |
| 52 | + now := time.Now().UTC() | |
| 53 | + stored(t, k, "20260101-000000-aaaaaaaa", now.Add(-2*time.Hour), talk(talk(nil, "oldest a"), "oldest b")) | |
| 54 | + stored(t, k, "20260102-000000-bbbbbbbb", now.Add(-time.Hour), talk(nil, "yesterday")) | |
| 55 | + stored(t, k, k.CurrentID(), now, talk(nil, "stale copy of the current file")) | |
| 56 | + | |
| 57 | + current := talk(talk(session0(), "first today"), "second today") | |
| 58 | + want := []string{"second today", "first today", "yesterday", "oldest b", "oldest a"} | |
| 59 | + if got := k.Questions(current); !reflect.DeepEqual(got, want) { | |
| 60 | + t.Errorf("Questions = %q, want %q", got, want) | |
| 61 | + } | |
| 62 | +} | |
| 63 | + | |
| 64 | +// What the user never typed is not recalled; what a hook added to a question | |
| 65 | +// is cut off it; a question asked twice in a row comes back once. | |
| 66 | +func TestQuestionsSkipWhatTheUserDidNotType(t *testing.T) { | |
| 67 | + k := newQuestionsKeeper(t) | |
| 68 | + msgs := session0() | |
| 69 | + msgs = append(msgs, compact.SummaryPair("notes", 4, 2, 0)...) | |
| 70 | + msgs = talk(msgs, "fix the build") | |
| 71 | + msgs = talk(msgs, "fix the build") | |
| 72 | + msgs = talk(msgs, "[mm: your last tool call was not run: …]") | |
| 73 | + msgs = talk(msgs, "[prompt blocked by a hook: secret]") | |
| 74 | + msgs = talk(msgs, " ") | |
| 75 | + msgs = talk(msgs, "run the tests\n\n"+hooks.ContextLabel+"branch: main") | |
| 76 | + msgs = append(msgs, nil) | |
| 77 | + | |
| 78 | + want := []string{"run the tests", "fix the build"} | |
| 79 | + if got := k.Questions(msgs); !reflect.DeepEqual(got, want) { | |
| 80 | + t.Errorf("Questions = %q, want %q", got, want) | |
| 81 | + } | |
| 82 | +} | |
| 83 | + | |
| 84 | +func TestQuestionsAreCapped(t *testing.T) { | |
| 85 | + k := newQuestionsKeeper(t) | |
| 86 | + var current []*ai.Message | |
| 87 | + for i := range maxQuestions + 10 { | |
| 88 | + current = talk(current, "question "+string(rune('a'+i%26))+time.Duration(i).String()) | |
| 89 | + } | |
| 90 | + stored(t, k, "20260101-000000-aaaaaaaa", time.Now().UTC(), talk(nil, "never reached")) | |
| 91 | + got := k.Questions(current) | |
| 92 | + if len(got) != maxQuestions { | |
| 93 | + t.Fatalf("len = %d, want %d", len(got), maxQuestions) | |
| 94 | + } | |
| 95 | + if got[0] != current[len(current)-2].Text() { | |
| 96 | + t.Errorf("first = %q, want the last question asked", got[0]) | |
| 97 | + } | |
| 98 | +} | |
| 99 | + | |
| 100 | +// session0 is a fresh history: the system prompt alone. | |
| 101 | +func session0() []*ai.Message { | |
| 102 | + return []*ai.Message{ai.NewSystemTextMessage("system")} | |
| 103 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,103 @@ | |||
| 1 | +package store | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "encoding/json" | ||
| 5 | + "path/filepath" | ||
| 6 | + "reflect" | ||
| 7 | + "testing" | ||
| 8 | + "time" | ||
| 9 | + | ||
| 10 | + "mm/internal/compact" | ||
| 11 | + "mm/internal/hooks" | ||
| 12 | + | ||
| 13 | + "github.com/firebase/genkit/go/ai" | ||
| 14 | +) | ||
| 15 | + | ||
| 16 | +// stored writes a session file as it would be after a save at `updated`: | ||
| 17 | +// Save stamps the wall clock, which would make the order of two files saved | ||
| 18 | +// in a row depend on the clock's resolution. | ||
| 19 | +func stored(t *testing.T, k *Keeper, id string, updated time.Time, msgs []*ai.Message) { | ||
| 20 | + t.Helper() | ||
| 21 | + data, err := json.Marshal(&Session{ID: id, UpdatedAt: updated, Messages: msgs}) | ||
| 22 | + if err != nil { | ||
| 23 | + t.Fatal(err) | ||
| 24 | + } | ||
| 25 | + if err := writeAtomic(filepath.Join(k.Dir(), id+".json"), data); err != nil { | ||
| 26 | + t.Fatal(err) | ||
| 27 | + } | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +func newQuestionsKeeper(t *testing.T) *Keeper { | ||
| 31 | + t.Helper() | ||
| 32 | + withSessions(t, true) | ||
| 33 | + k := NewKeeper(t.TempDir()) | ||
| 34 | + if err := k.Save(nil); err != nil { // creates the directory and the current file | ||
| 35 | + t.Fatal(err) | ||
| 36 | + } | ||
| 37 | + return k | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +func TestQuestionsNilKeeperHasNothingToRecall(t *testing.T) { | ||
| 41 | + var k *Keeper | ||
| 42 | + if got := k.Questions(talk(nil, "hello")); got != nil { | ||
| 43 | + t.Errorf("nil keeper recalled %q", got) | ||
| 44 | + } | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +// The conversation in progress comes first, newest first; then the other | ||
| 48 | +// sessions, the most recently updated first. The current session's own file | ||
| 49 | +// is not read again: the history in memory is the one that counts. | ||
| 50 | +func TestQuestionsOrderCurrentThenEarlierSessions(t *testing.T) { | ||
| 51 | + k := newQuestionsKeeper(t) | ||
| 52 | + now := time.Now().UTC() | ||
| 53 | + stored(t, k, "20260101-000000-aaaaaaaa", now.Add(-2*time.Hour), talk(talk(nil, "oldest a"), "oldest b")) | ||
| 54 | + stored(t, k, "20260102-000000-bbbbbbbb", now.Add(-time.Hour), talk(nil, "yesterday")) | ||
| 55 | + stored(t, k, k.CurrentID(), now, talk(nil, "stale copy of the current file")) | ||
| 56 | + | ||
| 57 | + current := talk(talk(session0(), "first today"), "second today") | ||
| 58 | + want := []string{"second today", "first today", "yesterday", "oldest b", "oldest a"} | ||
| 59 | + if got := k.Questions(current); !reflect.DeepEqual(got, want) { | ||
| 60 | + t.Errorf("Questions = %q, want %q", got, want) | ||
| 61 | + } | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +// What the user never typed is not recalled; what a hook added to a question | ||
| 65 | +// is cut off it; a question asked twice in a row comes back once. | ||
| 66 | +func TestQuestionsSkipWhatTheUserDidNotType(t *testing.T) { | ||
| 67 | + k := newQuestionsKeeper(t) | ||
| 68 | + msgs := session0() | ||
| 69 | + msgs = append(msgs, compact.SummaryPair("notes", 4, 2, 0)...) | ||
| 70 | + msgs = talk(msgs, "fix the build") | ||
| 71 | + msgs = talk(msgs, "fix the build") | ||
| 72 | + msgs = talk(msgs, "[mm: your last tool call was not run: …]") | ||
| 73 | + msgs = talk(msgs, "[prompt blocked by a hook: secret]") | ||
| 74 | + msgs = talk(msgs, " ") | ||
| 75 | + msgs = talk(msgs, "run the tests\n\n"+hooks.ContextLabel+"branch: main") | ||
| 76 | + msgs = append(msgs, nil) | ||
| 77 | + | ||
| 78 | + want := []string{"run the tests", "fix the build"} | ||
| 79 | + if got := k.Questions(msgs); !reflect.DeepEqual(got, want) { | ||
| 80 | + t.Errorf("Questions = %q, want %q", got, want) | ||
| 81 | + } | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +func TestQuestionsAreCapped(t *testing.T) { | ||
| 85 | + k := newQuestionsKeeper(t) | ||
| 86 | + var current []*ai.Message | ||
| 87 | + for i := range maxQuestions + 10 { | ||
| 88 | + current = talk(current, "question "+string(rune('a'+i%26))+time.Duration(i).String()) | ||
| 89 | + } | ||
| 90 | + stored(t, k, "20260101-000000-aaaaaaaa", time.Now().UTC(), talk(nil, "never reached")) | ||
| 91 | + got := k.Questions(current) | ||
| 92 | + if len(got) != maxQuestions { | ||
| 93 | + t.Fatalf("len = %d, want %d", len(got), maxQuestions) | ||
| 94 | + } | ||
| 95 | + if got[0] != current[len(current)-2].Text() { | ||
| 96 | + t.Errorf("first = %q, want the last question asked", got[0]) | ||
| 97 | + } | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +// session0 is a fresh history: the system prompt alone. | ||
| 101 | +func session0() []*ai.Message { | ||
| 102 | + return []*ai.Message{ai.NewSystemTextMessage("system")} | ||
| 103 | +} | ||
modified
internal/store/store.go +12 -2 | @@ -217,11 +217,21 @@ func (s *Store) Load(id string) (*Session, error) { | ||
| 217 | 217 | // directory is an empty list; a file that cannot be decoded is skipped — one |
| 218 | 218 | // corrupt session must not hide the others. |
| 219 | 219 | func (s *Store) List() []Summary { |
| 220 | + var out []Summary | |
| 221 | + for _, sess := range s.all() { | |
| 222 | + out = append(out, summarize(sess)) | |
| 223 | + } | |
| 224 | + return out | |
| 225 | +} | |
| 226 | + | |
| 227 | +// all loads every stored session, most recently updated first, with List's | |
| 228 | +// rules: a missing directory is no session, an unreadable file is skipped. | |
| 229 | +func (s *Store) all() []*Session { | |
| 220 | 230 | entries, err := os.ReadDir(s.dir) |
| 221 | 231 | if err != nil { |
| 222 | 232 | return nil |
| 223 | 233 | } |
| 224 | - var out []Summary | |
| 234 | + var out []*Session | |
| 225 | 235 | for _, e := range entries { |
| 226 | 236 | name := e.Name() |
| 227 | 237 | if e.IsDir() || !strings.HasSuffix(name, ".json") { |
| @@ -231,7 +241,7 @@ func (s *Store) List() []Summary { | ||
| 231 | 241 | if err != nil { |
| 232 | 242 | continue |
| 233 | 243 | } |
| 234 | - out = append(out, summarize(sess)) | |
| 244 | + out = append(out, sess) | |
| 235 | 245 | } |
| 236 | 246 | sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt.After(out[j].UpdatedAt) }) |
| 237 | 247 | return out |
| @@ -217,11 +217,21 @@ func (s *Store) Load(id string) (*Session, error) { | |||
| 217 | // directory is an empty list; a file that cannot be decoded is skipped — one | 217 | // directory is an empty list; a file that cannot be decoded is skipped — one |
| 218 | // corrupt session must not hide the others. | 218 | // corrupt session must not hide the others. |
| 219 | func (s *Store) List() []Summary { | 219 | func (s *Store) List() []Summary { |
| 220 | + var out []Summary | ||
| 221 | + for _, sess := range s.all() { | ||
| 222 | + out = append(out, summarize(sess)) | ||
| 223 | + } | ||
| 224 | + return out | ||
| 225 | +} | ||
| 226 | + | ||
| 227 | +// all loads every stored session, most recently updated first, with List's | ||
| 228 | +// rules: a missing directory is no session, an unreadable file is skipped. | ||
| 229 | +func (s *Store) all() []*Session { | ||
| 220 | entries, err := os.ReadDir(s.dir) | 230 | entries, err := os.ReadDir(s.dir) |
| 221 | if err != nil { | 231 | if err != nil { |
| 222 | return nil | 232 | return nil |
| 223 | } | 233 | } |
| 224 | - var out []Summary | 234 | + var out []*Session |
| 225 | for _, e := range entries { | 235 | for _, e := range entries { |
| 226 | name := e.Name() | 236 | name := e.Name() |
| 227 | if e.IsDir() || !strings.HasSuffix(name, ".json") { | 237 | if e.IsDir() || !strings.HasSuffix(name, ".json") { |
| @@ -231,7 +241,7 @@ func (s *Store) List() []Summary { | |||
| 231 | if err != nil { | 241 | if err != nil { |
| 232 | continue | 242 | continue |
| 233 | } | 243 | } |
| 234 | - out = append(out, summarize(sess)) | 244 | + out = append(out, sess) |
| 235 | } | 245 | } |
| 236 | sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt.After(out[j].UpdatedAt) }) | 246 | sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt.After(out[j].UpdatedAt) }) |
| 237 | return out | 247 | return out |
modified
internal/tui/commands.go +1 -1 | @@ -118,7 +118,7 @@ func (r *runner) unload(model string) { | ||
| 118 | 118 | |
| 119 | 119 | // help lists the window's commands and keys. |
| 120 | 120 | func (r *runner) help() { |
| 121 | - r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file") | |
| 121 | + r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn (idle: clears the box) · ↑/↓ recall earlier questions (sessions on) · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file") | |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | 124 | // quitNow ends the loop on its next turn. |
| @@ -118,7 +118,7 @@ func (r *runner) unload(model string) { | |||
| 118 | 118 | ||
| 119 | // help lists the window's commands and keys. | 119 | // help lists the window's commands and keys. |
| 120 | func (r *runner) help() { | 120 | func (r *runner) help() { |
| 121 | - r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file") | 121 | + r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn (idle: clears the box) · ↑/↓ recall earlier questions (sessions on) · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file") |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | // quitNow ends the loop on its next turn. | 124 | // quitNow ends the loop on its next turn. |
modified
internal/tui/events.go +20 -4 | @@ -120,7 +120,7 @@ func (v *View) handleWindowKey(ev *tcell.EventKey) bool { | ||
| 120 | 120 | v.on.quit() |
| 121 | 121 | } |
| 122 | 122 | case ev.Key() == tcell.KeyEscape: |
| 123 | - v.abortTurn() | |
| 123 | + v.escape() | |
| 124 | 124 | case ev.Key() == tcell.KeyCtrlL: |
| 125 | 125 | v.clearScreen() |
| 126 | 126 | case ev.Key() == tcell.KeyF1: |
| @@ -155,7 +155,21 @@ func (v *View) cancelTurn() bool { | ||
| 155 | 155 | return v.on.cancel != nil && v.on.cancel() |
| 156 | 156 | } |
| 157 | 157 | |
| 158 | -// abortTurn is Escape: the same cancellation, with the runner saying what | |
| 158 | +// escape is Escape: it cancels the turn in progress; when none runs and the | |
| 159 | +// box holds something — a recalled question, a draft — it clears the box | |
| 160 | +// instead (ticket 68), so a look back costs one key to undo. An empty box | |
| 161 | +// with nothing running keeps the old answer: abort says there is nothing to | |
| 162 | +// stop. | |
| 163 | +func (v *View) escape() { | |
| 164 | + if v.busy == "" && v.Input() != "" { | |
| 165 | + v.setInput("") | |
| 166 | + v.recall = recall{} | |
| 167 | + return | |
| 168 | + } | |
| 169 | + v.abortTurn() | |
| 170 | +} | |
| 171 | + | |
| 172 | +// abortTurn is Escape during a turn: the same cancellation, with the runner saying what | |
| 159 | 173 | // happened — including that the turn is not listening any more. |
| 160 | 174 | func (v *View) abortTurn() { |
| 161 | 175 | if v.on.abort != nil { |
| @@ -183,9 +197,11 @@ func (v *View) handleInputKey(ev *tcell.EventKey) bool { | ||
| 183 | 197 | case tcell.KeyEnd: |
| 184 | 198 | v.column = len(v.runes()) |
| 185 | 199 | case tcell.KeyUp: |
| 186 | - return v.moveRow(-1) // false on the first row: scroll the conversation instead | |
| 200 | + // On the first row: the previous question (recall.go), else scroll | |
| 201 | + // the conversation. | |
| 202 | + return v.moveRow(-1) || v.recallStep(true) | |
| 187 | 203 | case tcell.KeyDown: |
| 188 | - return v.moveRow(1) | |
| 204 | + return v.moveRow(1) || v.recallStep(false) | |
| 189 | 205 | default: |
| 190 | 206 | return false |
| 191 | 207 | } |
| @@ -120,7 +120,7 @@ func (v *View) handleWindowKey(ev *tcell.EventKey) bool { | |||
| 120 | v.on.quit() | 120 | v.on.quit() |
| 121 | } | 121 | } |
| 122 | case ev.Key() == tcell.KeyEscape: | 122 | case ev.Key() == tcell.KeyEscape: |
| 123 | - v.abortTurn() | 123 | + v.escape() |
| 124 | case ev.Key() == tcell.KeyCtrlL: | 124 | case ev.Key() == tcell.KeyCtrlL: |
| 125 | v.clearScreen() | 125 | v.clearScreen() |
| 126 | case ev.Key() == tcell.KeyF1: | 126 | case ev.Key() == tcell.KeyF1: |
| @@ -155,7 +155,21 @@ func (v *View) cancelTurn() bool { | |||
| 155 | return v.on.cancel != nil && v.on.cancel() | 155 | return v.on.cancel != nil && v.on.cancel() |
| 156 | } | 156 | } |
| 157 | 157 | ||
| 158 | -// abortTurn is Escape: the same cancellation, with the runner saying what | 158 | +// escape is Escape: it cancels the turn in progress; when none runs and the |
| 159 | +// box holds something — a recalled question, a draft — it clears the box | ||
| 160 | +// instead (ticket 68), so a look back costs one key to undo. An empty box | ||
| 161 | +// with nothing running keeps the old answer: abort says there is nothing to | ||
| 162 | +// stop. | ||
| 163 | +func (v *View) escape() { | ||
| 164 | + if v.busy == "" && v.Input() != "" { | ||
| 165 | + v.setInput("") | ||
| 166 | + v.recall = recall{} | ||
| 167 | + return | ||
| 168 | + } | ||
| 169 | + v.abortTurn() | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +// abortTurn is Escape during a turn: the same cancellation, with the runner saying what | ||
| 159 | // happened — including that the turn is not listening any more. | 173 | // happened — including that the turn is not listening any more. |
| 160 | func (v *View) abortTurn() { | 174 | func (v *View) abortTurn() { |
| 161 | if v.on.abort != nil { | 175 | if v.on.abort != nil { |
| @@ -183,9 +197,11 @@ func (v *View) handleInputKey(ev *tcell.EventKey) bool { | |||
| 183 | case tcell.KeyEnd: | 197 | case tcell.KeyEnd: |
| 184 | v.column = len(v.runes()) | 198 | v.column = len(v.runes()) |
| 185 | case tcell.KeyUp: | 199 | case tcell.KeyUp: |
| 186 | - return v.moveRow(-1) // false on the first row: scroll the conversation instead | 200 | + // On the first row: the previous question (recall.go), else scroll |
| 201 | + // the conversation. | ||
| 202 | + return v.moveRow(-1) || v.recallStep(true) | ||
| 187 | case tcell.KeyDown: | 203 | case tcell.KeyDown: |
| 188 | - return v.moveRow(1) | 204 | + return v.moveRow(1) || v.recallStep(false) |
| 189 | default: | 205 | default: |
| 190 | return false | 206 | return false |
| 191 | } | 207 | } |
added
internal/tui/recall.go +74 -0 | new file mode 100644 | ||
| @@ -0,0 +1,74 @@ | ||
| 1 | +package tui | |
| 2 | + | |
| 3 | +import "maps" | |
| 4 | + | |
| 5 | +// Recalling the questions already asked (ticket 68): with the focus on the | |
| 6 | +// box, ↑ on its first row puts the previous question in it, ↓ on its last | |
| 7 | +// row the next one, and ↓ past the newest gives back what was being typed | |
| 8 | +// before. The questions come from the runner (hooks.questions), which reads | |
| 9 | +// them from the stored sessions — so there is nothing to recall, and the | |
| 10 | +// arrows scroll the conversation as before, when sessions are off. | |
| 11 | +// | |
| 12 | +// A recalled question is ordinary text: edit it and send it, or press Escape | |
| 13 | +// to clear the box (events.go). Editing ends the walk: the next ↑ starts | |
| 14 | +// again from the newest question, with the edited text as the draft. | |
| 15 | +type recall struct { | |
| 16 | + entries []string // newest first, as fetched when the walk started | |
| 17 | + at int // index in entries of what the box shows; -1: the draft | |
| 18 | + shown string // the box as the walk last left it, to notice an edit | |
| 19 | + draft string // what was typed before the walk started | |
| 20 | + pastes map[rune]paste | |
| 21 | +} | |
| 22 | + | |
| 23 | +// walking reports whether the box still shows what the walk put there. | |
| 24 | +func (rc *recall) walking(input string) bool { | |
| 25 | + return rc.entries != nil && input == rc.shown | |
| 26 | +} | |
| 27 | + | |
| 28 | +// recallStep moves the walk one question older (↑) or newer (↓) and reports | |
| 29 | +// whether it used the key. It does not when there is nothing to recall, or | |
| 30 | +// on ↓ with no walk in progress: the arrow then scrolls the conversation. | |
| 31 | +// At the oldest question ↑ is used and does nothing, so the conversation | |
| 32 | +// does not jump under a question being looked at. | |
| 33 | +func (v *View) recallStep(older bool) bool { | |
| 34 | + rc := &v.recall | |
| 35 | + if !rc.walking(v.Input()) { | |
| 36 | + if !older || !v.startRecall() { | |
| 37 | + return false | |
| 38 | + } | |
| 39 | + } | |
| 40 | + next := rc.at - 1 | |
| 41 | + if older { | |
| 42 | + next = rc.at + 1 | |
| 43 | + } | |
| 44 | + switch { | |
| 45 | + case next >= len(rc.entries): | |
| 46 | + return true | |
| 47 | + case next < 0: | |
| 48 | + v.setInput(rc.draft) | |
| 49 | + v.pastes.blocks = maps.Clone(rc.pastes) | |
| 50 | + v.recall = recall{} | |
| 51 | + return true | |
| 52 | + } | |
| 53 | + rc.at = next | |
| 54 | + v.setInput(rc.entries[next]) | |
| 55 | + rc.shown = v.Input() | |
| 56 | + // A question that ends on "@…" or "$…" would open the picker, and the | |
| 57 | + // picker takes the arrows: the walk must keep them. | |
| 58 | + v.pick.dismiss(rc.shown) | |
| 59 | + return true | |
| 60 | +} | |
| 61 | + | |
| 62 | +// startRecall fetches the questions and keeps the draft aside; false when | |
| 63 | +// there is nothing to recall. | |
| 64 | +func (v *View) startRecall() bool { | |
| 65 | + if v.on.questions == nil { | |
| 66 | + return false | |
| 67 | + } | |
| 68 | + entries := v.on.questions() | |
| 69 | + if len(entries) == 0 { | |
| 70 | + return false | |
| 71 | + } | |
| 72 | + v.recall = recall{entries: entries, at: -1, draft: v.Input(), pastes: maps.Clone(v.pastes.blocks)} | |
| 73 | + return true | |
| 74 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | +package tui | ||
| 2 | + | ||
| 3 | +import "maps" | ||
| 4 | + | ||
| 5 | +// Recalling the questions already asked (ticket 68): with the focus on the | ||
| 6 | +// box, ↑ on its first row puts the previous question in it, ↓ on its last | ||
| 7 | +// row the next one, and ↓ past the newest gives back what was being typed | ||
| 8 | +// before. The questions come from the runner (hooks.questions), which reads | ||
| 9 | +// them from the stored sessions — so there is nothing to recall, and the | ||
| 10 | +// arrows scroll the conversation as before, when sessions are off. | ||
| 11 | +// | ||
| 12 | +// A recalled question is ordinary text: edit it and send it, or press Escape | ||
| 13 | +// to clear the box (events.go). Editing ends the walk: the next ↑ starts | ||
| 14 | +// again from the newest question, with the edited text as the draft. | ||
| 15 | +type recall struct { | ||
| 16 | + entries []string // newest first, as fetched when the walk started | ||
| 17 | + at int // index in entries of what the box shows; -1: the draft | ||
| 18 | + shown string // the box as the walk last left it, to notice an edit | ||
| 19 | + draft string // what was typed before the walk started | ||
| 20 | + pastes map[rune]paste | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +// walking reports whether the box still shows what the walk put there. | ||
| 24 | +func (rc *recall) walking(input string) bool { | ||
| 25 | + return rc.entries != nil && input == rc.shown | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +// recallStep moves the walk one question older (↑) or newer (↓) and reports | ||
| 29 | +// whether it used the key. It does not when there is nothing to recall, or | ||
| 30 | +// on ↓ with no walk in progress: the arrow then scrolls the conversation. | ||
| 31 | +// At the oldest question ↑ is used and does nothing, so the conversation | ||
| 32 | +// does not jump under a question being looked at. | ||
| 33 | +func (v *View) recallStep(older bool) bool { | ||
| 34 | + rc := &v.recall | ||
| 35 | + if !rc.walking(v.Input()) { | ||
| 36 | + if !older || !v.startRecall() { | ||
| 37 | + return false | ||
| 38 | + } | ||
| 39 | + } | ||
| 40 | + next := rc.at - 1 | ||
| 41 | + if older { | ||
| 42 | + next = rc.at + 1 | ||
| 43 | + } | ||
| 44 | + switch { | ||
| 45 | + case next >= len(rc.entries): | ||
| 46 | + return true | ||
| 47 | + case next < 0: | ||
| 48 | + v.setInput(rc.draft) | ||
| 49 | + v.pastes.blocks = maps.Clone(rc.pastes) | ||
| 50 | + v.recall = recall{} | ||
| 51 | + return true | ||
| 52 | + } | ||
| 53 | + rc.at = next | ||
| 54 | + v.setInput(rc.entries[next]) | ||
| 55 | + rc.shown = v.Input() | ||
| 56 | + // A question that ends on "@…" or "$…" would open the picker, and the | ||
| 57 | + // picker takes the arrows: the walk must keep them. | ||
| 58 | + v.pick.dismiss(rc.shown) | ||
| 59 | + return true | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +// startRecall fetches the questions and keeps the draft aside; false when | ||
| 63 | +// there is nothing to recall. | ||
| 64 | +func (v *View) startRecall() bool { | ||
| 65 | + if v.on.questions == nil { | ||
| 66 | + return false | ||
| 67 | + } | ||
| 68 | + entries := v.on.questions() | ||
| 69 | + if len(entries) == 0 { | ||
| 70 | + return false | ||
| 71 | + } | ||
| 72 | + v.recall = recall{entries: entries, at: -1, draft: v.Input(), pastes: maps.Clone(v.pastes.blocks)} | ||
| 73 | + return true | ||
| 74 | +} | ||
added
internal/tui/recall_test.go +226 -0 | new file mode 100644 | ||
| @@ -0,0 +1,226 @@ | ||
| 1 | +package tui | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "strings" | |
| 5 | + "testing" | |
| 6 | + | |
| 7 | + "mm/internal/config" | |
| 8 | + "mm/internal/store" | |
| 9 | + | |
| 10 | + "github.com/gdamore/tcell/v2" | |
| 11 | +) | |
| 12 | + | |
| 13 | +// recallHarness is a window whose ↑ recalls the given questions, newest | |
| 14 | +// first, and counts how often it asked for them. | |
| 15 | +func recallHarness(t *testing.T, questions ...string) (*harness, *int) { | |
| 16 | + t.Helper() | |
| 17 | + hs := newHarness(t, 60, 12) | |
| 18 | + asked := 0 | |
| 19 | + hs.v.on.questions = func() []string { asked++; return questions } | |
| 20 | + return hs, &asked | |
| 21 | +} | |
| 22 | + | |
| 23 | +func (hs *harness) up() { hs.key(tcell.KeyUp, tcell.ModNone) } | |
| 24 | +func (hs *harness) down() { hs.key(tcell.KeyDown, tcell.ModNone) } | |
| 25 | + | |
| 26 | +// ↑ walks back through the questions, ↓ forward, and ↓ past the newest gives | |
| 27 | +// back what was being typed — the draft is not lost to a look back. | |
| 28 | +func TestRecallWalksBackAndForthAndRestoresTheDraft(t *testing.T) { | |
| 29 | + hs, asked := recallHarness(t, "newest", "middle", "oldest") | |
| 30 | + hs.typeText("half a thought") | |
| 31 | + | |
| 32 | + for _, want := range []string{"newest", "middle", "oldest", "oldest"} { | |
| 33 | + hs.up() | |
| 34 | + if got := hs.v.Input(); got != want { | |
| 35 | + t.Fatalf("after ↑: box = %q, want %q", got, want) | |
| 36 | + } | |
| 37 | + } | |
| 38 | + for _, want := range []string{"middle", "newest", "half a thought"} { | |
| 39 | + hs.down() | |
| 40 | + if got := hs.v.Input(); got != want { | |
| 41 | + t.Fatalf("after ↓: box = %q, want %q", got, want) | |
| 42 | + } | |
| 43 | + } | |
| 44 | + if *asked != 1 { | |
| 45 | + t.Errorf("questions fetched %d times during one walk, want 1", *asked) | |
| 46 | + } | |
| 47 | + if hs.v.column != len([]rune("half a thought")) { | |
| 48 | + t.Errorf("cursor at column %d, want the end of the draft", hs.v.column) | |
| 49 | + } | |
| 50 | +} | |
| 51 | + | |
| 52 | +// A recalled question can be edited and sent like anything typed; the walk | |
| 53 | +// ends with the send, and the next ↑ starts from the newest question again. | |
| 54 | +func TestRecalledQuestionIsEditedAndSent(t *testing.T) { | |
| 55 | + hs, asked := recallHarness(t, "run the tests", "fix the build") | |
| 56 | + hs.up() | |
| 57 | + hs.up() | |
| 58 | + hs.typeText(" again") | |
| 59 | + hs.key(tcell.KeyEnter, tcell.ModNone) | |
| 60 | + if len(hs.sent) != 1 || hs.sent[0] != "fix the build again" { | |
| 61 | + t.Fatalf("sent %q", hs.sent) | |
| 62 | + } | |
| 63 | + hs.up() | |
| 64 | + if got := hs.v.Input(); got != "run the tests" { | |
| 65 | + t.Errorf("after the send, ↑ shows %q, want the newest question", got) | |
| 66 | + } | |
| 67 | + if *asked != 2 { | |
| 68 | + t.Errorf("questions fetched %d times, want once per walk (2)", *asked) | |
| 69 | + } | |
| 70 | +} | |
| 71 | + | |
| 72 | +// Editing a recalled question ends the walk: ↑ then starts over from the | |
| 73 | +// newest question, and ↓ past it gives back the edited text, not the old | |
| 74 | +// draft. | |
| 75 | +func TestEditingEndsTheWalk(t *testing.T) { | |
| 76 | + hs, _ := recallHarness(t, "newest", "oldest") | |
| 77 | + hs.up() | |
| 78 | + hs.up() | |
| 79 | + hs.typeText("!") | |
| 80 | + hs.up() | |
| 81 | + if got := hs.v.Input(); got != "newest" { | |
| 82 | + t.Fatalf("after an edit, ↑ shows %q, want the newest question", got) | |
| 83 | + } | |
| 84 | + hs.down() | |
| 85 | + if got := hs.v.Input(); got != "oldest!" { | |
| 86 | + t.Errorf("↓ past the newest gives %q, want the edited text", got) | |
| 87 | + } | |
| 88 | +} | |
| 89 | + | |
| 90 | +// Nothing to recall — sessions off, or a project with no question yet — and | |
| 91 | +// the arrows keep their old meaning: the conversation scrolls, the box stays. | |
| 92 | +func TestNothingToRecallScrollsTheConversation(t *testing.T) { | |
| 93 | + hs, _ := recallHarness(t) | |
| 94 | + for i := range 40 { | |
| 95 | + hs.v.t.Notice(strings.Repeat("line ", 3) + string(rune('a'+i%26))) | |
| 96 | + } | |
| 97 | + hs.rows() | |
| 98 | + hs.typeText("draft") | |
| 99 | + hs.up() | |
| 100 | + if hs.v.Input() != "draft" { | |
| 101 | + t.Errorf("box = %q, want the draft untouched", hs.v.Input()) | |
| 102 | + } | |
| 103 | + if hs.v.follow { | |
| 104 | + t.Error("↑ with nothing to recall did not scroll the conversation up") | |
| 105 | + } | |
| 106 | +} | |
| 107 | + | |
| 108 | +// ↓ with no walk in progress scrolls, as before: it never recalls. | |
| 109 | +func TestDownWithoutAWalkDoesNotRecall(t *testing.T) { | |
| 110 | + hs, asked := recallHarness(t, "newest") | |
| 111 | + hs.down() | |
| 112 | + if hs.v.Input() != "" || *asked != 0 { | |
| 113 | + t.Errorf("↓ alone recalled: box %q, fetched %d", hs.v.Input(), *asked) | |
| 114 | + } | |
| 115 | +} | |
| 116 | + | |
| 117 | +// A multi-line question is walked row by row before the walk moves on: ↑ on | |
| 118 | +// its last row goes up inside it, and only its first row recalls the next. | |
| 119 | +func TestMultiLineQuestionIsWalkedRowByRow(t *testing.T) { | |
| 120 | + hs, _ := recallHarness(t, "line one\nline two", "older") | |
| 121 | + hs.up() | |
| 122 | + if hs.v.Input() != "line one\nline two" || hs.v.cursor != 1 { | |
| 123 | + t.Fatalf("box %q, cursor on line %d", hs.v.Input(), hs.v.cursor) | |
| 124 | + } | |
| 125 | + hs.up() | |
| 126 | + if hs.v.Input() != "line one\nline two" || hs.v.cursor != 0 { | |
| 127 | + t.Fatalf("↑ on the last row: box %q, cursor on line %d", hs.v.Input(), hs.v.cursor) | |
| 128 | + } | |
| 129 | + hs.up() | |
| 130 | + if hs.v.Input() != "older" { | |
| 131 | + t.Errorf("↑ on the first row: box %q, want the older question", hs.v.Input()) | |
| 132 | + } | |
| 133 | +} | |
| 134 | + | |
| 135 | +// A question that ends on a mention would open the picker, which takes the | |
| 136 | +// arrows; the walk keeps them. | |
| 137 | +func TestRecalledMentionDoesNotOpenThePicker(t *testing.T) { | |
| 138 | + hs, _ := recallHarness(t, "read @", "older") | |
| 139 | + hs.up() | |
| 140 | + if hs.v.pickerOpen() { | |
| 141 | + t.Fatal("the picker opened on a recalled question") | |
| 142 | + } | |
| 143 | + hs.up() | |
| 144 | + if hs.v.Input() != "older" { | |
| 145 | + t.Errorf("box %q, want the older question", hs.v.Input()) | |
| 146 | + } | |
| 147 | +} | |
| 148 | + | |
| 149 | +// A draft holding a collapsed paste gets it back, token and text. | |
| 150 | +func TestDraftPasteSurvivesTheWalk(t *testing.T) { | |
| 151 | + hs, _ := recallHarness(t, "newest") | |
| 152 | + long := strings.Repeat("pasted line\n", 5) | |
| 153 | + hs.paste(long) | |
| 154 | + hs.up() | |
| 155 | + hs.down() | |
| 156 | + hs.key(tcell.KeyEnter, tcell.ModNone) | |
| 157 | + if len(hs.sent) != 1 || hs.sent[0] != long { | |
| 158 | + t.Errorf("sent %q, want the pasted text", hs.sent) | |
| 159 | + } | |
| 160 | +} | |
| 161 | + | |
| 162 | +// The runner's side: with sessions on, the question of the turn in progress | |
| 163 | +// is the first one recalled, then the history's; with sessions off, nothing. | |
| 164 | +func TestRunnerQuestions(t *testing.T) { | |
| 165 | + r, _ := newTestRunner(t, &fakeModel{answer: "ok", blockFirst: true}) | |
| 166 | + if got := r.questions(); got != nil { | |
| 167 | + t.Errorf("sessions off: questions = %q", got) | |
| 168 | + } | |
| 169 | + config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"} | |
| 170 | + r.keep = store.NewKeeper(r.opts.Cwd) | |
| 171 | + | |
| 172 | + r.submit(promptOf("first")) | |
| 173 | + if got := r.questions(); len(got) != 1 || got[0] != "first" { | |
| 174 | + t.Errorf("during the turn: questions = %q, want the question in flight", got) | |
| 175 | + } | |
| 176 | + r.cancelTurn() | |
| 177 | + waitIdle(t, r) | |
| 178 | + // The cancelled turn left no trace in the history; its question can | |
| 179 | + // still be recalled, to retry it. | |
| 180 | + if got := r.questions(); len(got) != 1 || got[0] != "first" { | |
| 181 | + t.Errorf("after a cancelled turn: questions = %q, want it still there", got) | |
| 182 | + } | |
| 183 | + r.submit(promptOf("first")) | |
| 184 | + waitIdle(t, r) | |
| 185 | + r.submit(promptOf("second")) | |
| 186 | + waitIdle(t, r) | |
| 187 | + if got := r.questions(); len(got) != 2 || got[0] != "second" || got[1] != "first" { | |
| 188 | + t.Errorf("after two turns: questions = %q", got) | |
| 189 | + } | |
| 190 | +} | |
| 191 | + | |
| 192 | +// Escape clears the box when no turn runs — a recalled question or a draft, | |
| 193 | +// pastes with it — and ends the walk; during a turn it still cancels, the | |
| 194 | +// box untouched. | |
| 195 | +func TestEscapeClearsTheBoxWhenIdle(t *testing.T) { | |
| 196 | + hs, _ := recallHarness(t, "newest", "oldest") | |
| 197 | + aborted := 0 | |
| 198 | + hs.v.on.abort = func() { aborted++ } | |
| 199 | + | |
| 200 | + hs.paste(strings.Repeat("pasted line\n", 5)) | |
| 201 | + hs.up() | |
| 202 | + hs.key(tcell.KeyEscape, tcell.ModNone) | |
| 203 | + if hs.v.Input() != "" || aborted != 0 { | |
| 204 | + t.Fatalf("idle Escape: box %q, aborted %d", hs.v.Input(), aborted) | |
| 205 | + } | |
| 206 | + if len(hs.v.pastes.blocks) != 0 { | |
| 207 | + t.Errorf("idle Escape kept %d paste(s)", len(hs.v.pastes.blocks)) | |
| 208 | + } | |
| 209 | + hs.up() | |
| 210 | + if hs.v.Input() != "newest" { | |
| 211 | + t.Errorf("after Escape, ↑ shows %q, want a fresh walk from the newest", hs.v.Input()) | |
| 212 | + } | |
| 213 | + | |
| 214 | + hs.key(tcell.KeyEscape, tcell.ModNone) | |
| 215 | + hs.key(tcell.KeyEscape, tcell.ModNone) // empty box, nothing running: the old answer | |
| 216 | + if aborted != 1 { | |
| 217 | + t.Errorf("Escape on an empty idle box: aborted %d, want 1", aborted) | |
| 218 | + } | |
| 219 | + | |
| 220 | + hs.v.busy = "thinking" | |
| 221 | + hs.typeText("queued") | |
| 222 | + hs.key(tcell.KeyEscape, tcell.ModNone) | |
| 223 | + if hs.v.Input() != "queued" || aborted != 2 { | |
| 224 | + t.Errorf("Escape during a turn: box %q, aborted %d", hs.v.Input(), aborted) | |
| 225 | + } | |
| 226 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,226 @@ | |||
| 1 | +package tui | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "strings" | ||
| 5 | + "testing" | ||
| 6 | + | ||
| 7 | + "mm/internal/config" | ||
| 8 | + "mm/internal/store" | ||
| 9 | + | ||
| 10 | + "github.com/gdamore/tcell/v2" | ||
| 11 | +) | ||
| 12 | + | ||
| 13 | +// recallHarness is a window whose ↑ recalls the given questions, newest | ||
| 14 | +// first, and counts how often it asked for them. | ||
| 15 | +func recallHarness(t *testing.T, questions ...string) (*harness, *int) { | ||
| 16 | + t.Helper() | ||
| 17 | + hs := newHarness(t, 60, 12) | ||
| 18 | + asked := 0 | ||
| 19 | + hs.v.on.questions = func() []string { asked++; return questions } | ||
| 20 | + return hs, &asked | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +func (hs *harness) up() { hs.key(tcell.KeyUp, tcell.ModNone) } | ||
| 24 | +func (hs *harness) down() { hs.key(tcell.KeyDown, tcell.ModNone) } | ||
| 25 | + | ||
| 26 | +// ↑ walks back through the questions, ↓ forward, and ↓ past the newest gives | ||
| 27 | +// back what was being typed — the draft is not lost to a look back. | ||
| 28 | +func TestRecallWalksBackAndForthAndRestoresTheDraft(t *testing.T) { | ||
| 29 | + hs, asked := recallHarness(t, "newest", "middle", "oldest") | ||
| 30 | + hs.typeText("half a thought") | ||
| 31 | + | ||
| 32 | + for _, want := range []string{"newest", "middle", "oldest", "oldest"} { | ||
| 33 | + hs.up() | ||
| 34 | + if got := hs.v.Input(); got != want { | ||
| 35 | + t.Fatalf("after ↑: box = %q, want %q", got, want) | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | + for _, want := range []string{"middle", "newest", "half a thought"} { | ||
| 39 | + hs.down() | ||
| 40 | + if got := hs.v.Input(); got != want { | ||
| 41 | + t.Fatalf("after ↓: box = %q, want %q", got, want) | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | + if *asked != 1 { | ||
| 45 | + t.Errorf("questions fetched %d times during one walk, want 1", *asked) | ||
| 46 | + } | ||
| 47 | + if hs.v.column != len([]rune("half a thought")) { | ||
| 48 | + t.Errorf("cursor at column %d, want the end of the draft", hs.v.column) | ||
| 49 | + } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +// A recalled question can be edited and sent like anything typed; the walk | ||
| 53 | +// ends with the send, and the next ↑ starts from the newest question again. | ||
| 54 | +func TestRecalledQuestionIsEditedAndSent(t *testing.T) { | ||
| 55 | + hs, asked := recallHarness(t, "run the tests", "fix the build") | ||
| 56 | + hs.up() | ||
| 57 | + hs.up() | ||
| 58 | + hs.typeText(" again") | ||
| 59 | + hs.key(tcell.KeyEnter, tcell.ModNone) | ||
| 60 | + if len(hs.sent) != 1 || hs.sent[0] != "fix the build again" { | ||
| 61 | + t.Fatalf("sent %q", hs.sent) | ||
| 62 | + } | ||
| 63 | + hs.up() | ||
| 64 | + if got := hs.v.Input(); got != "run the tests" { | ||
| 65 | + t.Errorf("after the send, ↑ shows %q, want the newest question", got) | ||
| 66 | + } | ||
| 67 | + if *asked != 2 { | ||
| 68 | + t.Errorf("questions fetched %d times, want once per walk (2)", *asked) | ||
| 69 | + } | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +// Editing a recalled question ends the walk: ↑ then starts over from the | ||
| 73 | +// newest question, and ↓ past it gives back the edited text, not the old | ||
| 74 | +// draft. | ||
| 75 | +func TestEditingEndsTheWalk(t *testing.T) { | ||
| 76 | + hs, _ := recallHarness(t, "newest", "oldest") | ||
| 77 | + hs.up() | ||
| 78 | + hs.up() | ||
| 79 | + hs.typeText("!") | ||
| 80 | + hs.up() | ||
| 81 | + if got := hs.v.Input(); got != "newest" { | ||
| 82 | + t.Fatalf("after an edit, ↑ shows %q, want the newest question", got) | ||
| 83 | + } | ||
| 84 | + hs.down() | ||
| 85 | + if got := hs.v.Input(); got != "oldest!" { | ||
| 86 | + t.Errorf("↓ past the newest gives %q, want the edited text", got) | ||
| 87 | + } | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +// Nothing to recall — sessions off, or a project with no question yet — and | ||
| 91 | +// the arrows keep their old meaning: the conversation scrolls, the box stays. | ||
| 92 | +func TestNothingToRecallScrollsTheConversation(t *testing.T) { | ||
| 93 | + hs, _ := recallHarness(t) | ||
| 94 | + for i := range 40 { | ||
| 95 | + hs.v.t.Notice(strings.Repeat("line ", 3) + string(rune('a'+i%26))) | ||
| 96 | + } | ||
| 97 | + hs.rows() | ||
| 98 | + hs.typeText("draft") | ||
| 99 | + hs.up() | ||
| 100 | + if hs.v.Input() != "draft" { | ||
| 101 | + t.Errorf("box = %q, want the draft untouched", hs.v.Input()) | ||
| 102 | + } | ||
| 103 | + if hs.v.follow { | ||
| 104 | + t.Error("↑ with nothing to recall did not scroll the conversation up") | ||
| 105 | + } | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +// ↓ with no walk in progress scrolls, as before: it never recalls. | ||
| 109 | +func TestDownWithoutAWalkDoesNotRecall(t *testing.T) { | ||
| 110 | + hs, asked := recallHarness(t, "newest") | ||
| 111 | + hs.down() | ||
| 112 | + if hs.v.Input() != "" || *asked != 0 { | ||
| 113 | + t.Errorf("↓ alone recalled: box %q, fetched %d", hs.v.Input(), *asked) | ||
| 114 | + } | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +// A multi-line question is walked row by row before the walk moves on: ↑ on | ||
| 118 | +// its last row goes up inside it, and only its first row recalls the next. | ||
| 119 | +func TestMultiLineQuestionIsWalkedRowByRow(t *testing.T) { | ||
| 120 | + hs, _ := recallHarness(t, "line one\nline two", "older") | ||
| 121 | + hs.up() | ||
| 122 | + if hs.v.Input() != "line one\nline two" || hs.v.cursor != 1 { | ||
| 123 | + t.Fatalf("box %q, cursor on line %d", hs.v.Input(), hs.v.cursor) | ||
| 124 | + } | ||
| 125 | + hs.up() | ||
| 126 | + if hs.v.Input() != "line one\nline two" || hs.v.cursor != 0 { | ||
| 127 | + t.Fatalf("↑ on the last row: box %q, cursor on line %d", hs.v.Input(), hs.v.cursor) | ||
| 128 | + } | ||
| 129 | + hs.up() | ||
| 130 | + if hs.v.Input() != "older" { | ||
| 131 | + t.Errorf("↑ on the first row: box %q, want the older question", hs.v.Input()) | ||
| 132 | + } | ||
| 133 | +} | ||
| 134 | + | ||
| 135 | +// A question that ends on a mention would open the picker, which takes the | ||
| 136 | +// arrows; the walk keeps them. | ||
| 137 | +func TestRecalledMentionDoesNotOpenThePicker(t *testing.T) { | ||
| 138 | + hs, _ := recallHarness(t, "read @", "older") | ||
| 139 | + hs.up() | ||
| 140 | + if hs.v.pickerOpen() { | ||
| 141 | + t.Fatal("the picker opened on a recalled question") | ||
| 142 | + } | ||
| 143 | + hs.up() | ||
| 144 | + if hs.v.Input() != "older" { | ||
| 145 | + t.Errorf("box %q, want the older question", hs.v.Input()) | ||
| 146 | + } | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +// A draft holding a collapsed paste gets it back, token and text. | ||
| 150 | +func TestDraftPasteSurvivesTheWalk(t *testing.T) { | ||
| 151 | + hs, _ := recallHarness(t, "newest") | ||
| 152 | + long := strings.Repeat("pasted line\n", 5) | ||
| 153 | + hs.paste(long) | ||
| 154 | + hs.up() | ||
| 155 | + hs.down() | ||
| 156 | + hs.key(tcell.KeyEnter, tcell.ModNone) | ||
| 157 | + if len(hs.sent) != 1 || hs.sent[0] != long { | ||
| 158 | + t.Errorf("sent %q, want the pasted text", hs.sent) | ||
| 159 | + } | ||
| 160 | +} | ||
| 161 | + | ||
| 162 | +// The runner's side: with sessions on, the question of the turn in progress | ||
| 163 | +// is the first one recalled, then the history's; with sessions off, nothing. | ||
| 164 | +func TestRunnerQuestions(t *testing.T) { | ||
| 165 | + r, _ := newTestRunner(t, &fakeModel{answer: "ok", blockFirst: true}) | ||
| 166 | + if got := r.questions(); got != nil { | ||
| 167 | + t.Errorf("sessions off: questions = %q", got) | ||
| 168 | + } | ||
| 169 | + config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"} | ||
| 170 | + r.keep = store.NewKeeper(r.opts.Cwd) | ||
| 171 | + | ||
| 172 | + r.submit(promptOf("first")) | ||
| 173 | + if got := r.questions(); len(got) != 1 || got[0] != "first" { | ||
| 174 | + t.Errorf("during the turn: questions = %q, want the question in flight", got) | ||
| 175 | + } | ||
| 176 | + r.cancelTurn() | ||
| 177 | + waitIdle(t, r) | ||
| 178 | + // The cancelled turn left no trace in the history; its question can | ||
| 179 | + // still be recalled, to retry it. | ||
| 180 | + if got := r.questions(); len(got) != 1 || got[0] != "first" { | ||
| 181 | + t.Errorf("after a cancelled turn: questions = %q, want it still there", got) | ||
| 182 | + } | ||
| 183 | + r.submit(promptOf("first")) | ||
| 184 | + waitIdle(t, r) | ||
| 185 | + r.submit(promptOf("second")) | ||
| 186 | + waitIdle(t, r) | ||
| 187 | + if got := r.questions(); len(got) != 2 || got[0] != "second" || got[1] != "first" { | ||
| 188 | + t.Errorf("after two turns: questions = %q", got) | ||
| 189 | + } | ||
| 190 | +} | ||
| 191 | + | ||
| 192 | +// Escape clears the box when no turn runs — a recalled question or a draft, | ||
| 193 | +// pastes with it — and ends the walk; during a turn it still cancels, the | ||
| 194 | +// box untouched. | ||
| 195 | +func TestEscapeClearsTheBoxWhenIdle(t *testing.T) { | ||
| 196 | + hs, _ := recallHarness(t, "newest", "oldest") | ||
| 197 | + aborted := 0 | ||
| 198 | + hs.v.on.abort = func() { aborted++ } | ||
| 199 | + | ||
| 200 | + hs.paste(strings.Repeat("pasted line\n", 5)) | ||
| 201 | + hs.up() | ||
| 202 | + hs.key(tcell.KeyEscape, tcell.ModNone) | ||
| 203 | + if hs.v.Input() != "" || aborted != 0 { | ||
| 204 | + t.Fatalf("idle Escape: box %q, aborted %d", hs.v.Input(), aborted) | ||
| 205 | + } | ||
| 206 | + if len(hs.v.pastes.blocks) != 0 { | ||
| 207 | + t.Errorf("idle Escape kept %d paste(s)", len(hs.v.pastes.blocks)) | ||
| 208 | + } | ||
| 209 | + hs.up() | ||
| 210 | + if hs.v.Input() != "newest" { | ||
| 211 | + t.Errorf("after Escape, ↑ shows %q, want a fresh walk from the newest", hs.v.Input()) | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + hs.key(tcell.KeyEscape, tcell.ModNone) | ||
| 215 | + hs.key(tcell.KeyEscape, tcell.ModNone) // empty box, nothing running: the old answer | ||
| 216 | + if aborted != 1 { | ||
| 217 | + t.Errorf("Escape on an empty idle box: aborted %d, want 1", aborted) | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + hs.v.busy = "thinking" | ||
| 221 | + hs.typeText("queued") | ||
| 222 | + hs.key(tcell.KeyEscape, tcell.ModNone) | ||
| 223 | + if hs.v.Input() != "queued" || aborted != 2 { | ||
| 224 | + t.Errorf("Escape during a turn: box %q, aborted %d", hs.v.Input(), aborted) | ||
| 225 | + } | ||
| 226 | +} | ||
modified
internal/tui/tui.go +1 -0 | @@ -73,6 +73,7 @@ func newApp(screen tcell.Screen, opts Options) *app { | ||
| 73 | 73 | answer: r.answer, |
| 74 | 74 | answerForm: r.answerForm, |
| 75 | 75 | copy: func() { r.copy("") }, |
| 76 | + questions: r.questions, | |
| 76 | 77 | }) |
| 77 | 78 | v.previewLines = config.Cfg.PreviewLines |
| 78 | 79 | v.skills, v.cwd = opts.Skills, opts.Cwd |
| @@ -73,6 +73,7 @@ func newApp(screen tcell.Screen, opts Options) *app { | |||
| 73 | answer: r.answer, | 73 | answer: r.answer, |
| 74 | answerForm: r.answerForm, | 74 | answerForm: r.answerForm, |
| 75 | copy: func() { r.copy("") }, | 75 | copy: func() { r.copy("") }, |
| 76 | + questions: r.questions, | ||
| 76 | }) | 77 | }) |
| 77 | v.previewLines = config.Cfg.PreviewLines | 78 | v.previewLines = config.Cfg.PreviewLines |
| 78 | v.skills, v.cwd = opts.Skills, opts.Cwd | 79 | v.skills, v.cwd = opts.Skills, opts.Cwd |
modified
internal/tui/turn.go +22 -0 | @@ -3,6 +3,7 @@ package tui | ||
| 3 | 3 | import ( |
| 4 | 4 | "context" |
| 5 | 5 | "fmt" |
| 6 | + "slices" | |
| 6 | 7 | "strings" |
| 7 | 8 | "sync" |
| 8 | 9 | "sync/atomic" |
| @@ -114,6 +115,12 @@ type runner struct { | ||
| 114 | 115 | cancelled atomic.Bool |
| 115 | 116 | results chan outcome |
| 116 | 117 | pending Prompt // input to run once the cancelled turn has settled |
| 118 | + // asked is the question of the turn in progress, as the model reads it: | |
| 119 | + // not in messages until the turn settles, yet the first one ↑ recalls. | |
| 120 | + // A turn that fails before answering drops its question from the | |
| 121 | + // history (session.AfterTurn); it stays here until the next question, | |
| 122 | + // so ↑ can bring it back to retry. | |
| 123 | + asked string | |
| 117 | 124 | |
| 118 | 125 | askMu sync.Mutex |
| 119 | 126 | ask *permission |
| @@ -166,6 +173,7 @@ func (r *runner) startTurn(p Prompt) { | ||
| 166 | 173 | // whatever procedure it happens to name. |
| 167 | 174 | expanded, refs := skills.ExpandRefs(expanded, r.opts.Skills) |
| 168 | 175 | input := p.Expand(expanded) |
| 176 | + r.asked = input | |
| 169 | 177 | for _, ref := range refs { |
| 170 | 178 | r.t.Notice("📖 $" + ref.Name) |
| 171 | 179 | } |
| @@ -292,6 +300,9 @@ func (r *runner) apply(res outcome) { | ||
| 292 | 300 | return |
| 293 | 301 | } |
| 294 | 302 | r.messages = session.AfterTurn(res.messages, res.before, res.resp, res.history, res.err) |
| 303 | + if len(r.messages) >= res.before { | |
| 304 | + r.asked = "" // in the history now; a failed turn's is not, and stays recallable | |
| 305 | + } | |
| 295 | 306 | r.save() |
| 296 | 307 | if res.compactFailed != "" { |
| 297 | 308 | r.record(store.NewFailure(res.messages, store.FailCompaction, res.compactFailed, nil)) |
| @@ -378,6 +389,17 @@ func (r *runner) record(f store.Failure) { | ||
| 378 | 389 | } |
| 379 | 390 | } |
| 380 | 391 | |
| 392 | +// questions is what ↑ recalls in the box (recall.go): the questions of | |
| 393 | +// this session, the one in progress first, then the stored sessions'. None | |
| 394 | +// when sessions are off. | |
| 395 | +func (r *runner) questions() []string { | |
| 396 | + msgs := r.messages | |
| 397 | + if r.asked != "" { | |
| 398 | + msgs = append(slices.Clone(msgs), ai.NewUserTextMessage(r.asked)) | |
| 399 | + } | |
| 400 | + return r.keep.Questions(msgs) | |
| 401 | +} | |
| 402 | + | |
| 381 | 403 | // header is the top line: who is answering, how full the context is, which |
| 382 | 404 | // session is being saved. During an /ask, the agent asked is the one |
| 383 | 405 | // answering, so it names that agent and its model instead. |
| @@ -3,6 +3,7 @@ package tui | |||
| 3 | import ( | 3 | import ( |
| 4 | "context" | 4 | "context" |
| 5 | "fmt" | 5 | "fmt" |
| 6 | + "slices" | ||
| 6 | "strings" | 7 | "strings" |
| 7 | "sync" | 8 | "sync" |
| 8 | "sync/atomic" | 9 | "sync/atomic" |
| @@ -114,6 +115,12 @@ type runner struct { | |||
| 114 | cancelled atomic.Bool | 115 | cancelled atomic.Bool |
| 115 | results chan outcome | 116 | results chan outcome |
| 116 | pending Prompt // input to run once the cancelled turn has settled | 117 | pending Prompt // input to run once the cancelled turn has settled |
| 118 | + // asked is the question of the turn in progress, as the model reads it: | ||
| 119 | + // not in messages until the turn settles, yet the first one ↑ recalls. | ||
| 120 | + // A turn that fails before answering drops its question from the | ||
| 121 | + // history (session.AfterTurn); it stays here until the next question, | ||
| 122 | + // so ↑ can bring it back to retry. | ||
| 123 | + asked string | ||
| 117 | 124 | ||
| 118 | askMu sync.Mutex | 125 | askMu sync.Mutex |
| 119 | ask *permission | 126 | ask *permission |
| @@ -166,6 +173,7 @@ func (r *runner) startTurn(p Prompt) { | |||
| 166 | // whatever procedure it happens to name. | 173 | // whatever procedure it happens to name. |
| 167 | expanded, refs := skills.ExpandRefs(expanded, r.opts.Skills) | 174 | expanded, refs := skills.ExpandRefs(expanded, r.opts.Skills) |
| 168 | input := p.Expand(expanded) | 175 | input := p.Expand(expanded) |
| 176 | + r.asked = input | ||
| 169 | for _, ref := range refs { | 177 | for _, ref := range refs { |
| 170 | r.t.Notice("📖 $" + ref.Name) | 178 | r.t.Notice("📖 $" + ref.Name) |
| 171 | } | 179 | } |
| @@ -292,6 +300,9 @@ func (r *runner) apply(res outcome) { | |||
| 292 | return | 300 | return |
| 293 | } | 301 | } |
| 294 | r.messages = session.AfterTurn(res.messages, res.before, res.resp, res.history, res.err) | 302 | r.messages = session.AfterTurn(res.messages, res.before, res.resp, res.history, res.err) |
| 303 | + if len(r.messages) >= res.before { | ||
| 304 | + r.asked = "" // in the history now; a failed turn's is not, and stays recallable | ||
| 305 | + } | ||
| 295 | r.save() | 306 | r.save() |
| 296 | if res.compactFailed != "" { | 307 | if res.compactFailed != "" { |
| 297 | r.record(store.NewFailure(res.messages, store.FailCompaction, res.compactFailed, nil)) | 308 | r.record(store.NewFailure(res.messages, store.FailCompaction, res.compactFailed, nil)) |
| @@ -378,6 +389,17 @@ func (r *runner) record(f store.Failure) { | |||
| 378 | } | 389 | } |
| 379 | } | 390 | } |
| 380 | 391 | ||
| 392 | +// questions is what ↑ recalls in the box (recall.go): the questions of | ||
| 393 | +// this session, the one in progress first, then the stored sessions'. None | ||
| 394 | +// when sessions are off. | ||
| 395 | +func (r *runner) questions() []string { | ||
| 396 | + msgs := r.messages | ||
| 397 | + if r.asked != "" { | ||
| 398 | + msgs = append(slices.Clone(msgs), ai.NewUserTextMessage(r.asked)) | ||
| 399 | + } | ||
| 400 | + return r.keep.Questions(msgs) | ||
| 401 | +} | ||
| 402 | + | ||
| 381 | // header is the top line: who is answering, how full the context is, which | 403 | // header is the top line: who is answering, how full the context is, which |
| 382 | // session is being saved. During an /ask, the agent asked is the one | 404 | // session is being saved. During an /ask, the agent asked is the one |
| 383 | // answering, so it names that agent and its model instead. | 405 | // answering, so it names that agent and its model instead. |
modified
internal/tui/view.go +7 -2 | @@ -62,6 +62,9 @@ type hooks struct { | ||
| 62 | 62 | answerForm func(elicit.Result) |
| 63 | 63 | // copy is Ctrl-Y: the last answer to the clipboard. |
| 64 | 64 | copy func() |
| 65 | + // questions is what ↑ recalls in the box, newest first (recall.go); | |
| 66 | + // empty when sessions are off. | |
| 67 | + questions func() []string | |
| 65 | 68 | } |
| 66 | 69 | |
| 67 | 70 | // View is the window: header, conversation, rule, input box, status bar, |
| @@ -107,8 +110,9 @@ type View struct { | ||
| 107 | 110 | // Off by default so the terminal's own text selection keeps working. |
| 108 | 111 | mouse bool |
| 109 | 112 | |
| 110 | - pick picker | |
| 111 | - now func() time.Time | |
| 113 | + pick picker | |
| 114 | + recall recall | |
| 115 | + now func() time.Time | |
| 112 | 116 | } |
| 113 | 117 | |
| 114 | 118 | // newView returns a window drawing on screen, reading t. |
| @@ -150,6 +154,7 @@ func (v *View) send() { | ||
| 150 | 154 | } |
| 151 | 155 | prompt := v.pastes.snapshot(text) |
| 152 | 156 | v.setInput("") |
| 157 | + v.recall = recall{} | |
| 153 | 158 | v.follow = true |
| 154 | 159 | v.pick.dismiss("") |
| 155 | 160 | if v.on.send != nil { |
| @@ -62,6 +62,9 @@ type hooks struct { | |||
| 62 | answerForm func(elicit.Result) | 62 | answerForm func(elicit.Result) |
| 63 | // copy is Ctrl-Y: the last answer to the clipboard. | 63 | // copy is Ctrl-Y: the last answer to the clipboard. |
| 64 | copy func() | 64 | copy func() |
| 65 | + // questions is what ↑ recalls in the box, newest first (recall.go); | ||
| 66 | + // empty when sessions are off. | ||
| 67 | + questions func() []string | ||
| 65 | } | 68 | } |
| 66 | 69 | ||
| 67 | // View is the window: header, conversation, rule, input box, status bar, | 70 | // View is the window: header, conversation, rule, input box, status bar, |
| @@ -107,8 +110,9 @@ type View struct { | |||
| 107 | // Off by default so the terminal's own text selection keeps working. | 110 | // Off by default so the terminal's own text selection keeps working. |
| 108 | mouse bool | 111 | mouse bool |
| 109 | 112 | ||
| 110 | - pick picker | 113 | + pick picker |
| 111 | - now func() time.Time | 114 | + recall recall |
| 115 | + now func() time.Time | ||
| 112 | } | 116 | } |
| 113 | 117 | ||
| 114 | // newView returns a window drawing on screen, reading t. | 118 | // newView returns a window drawing on screen, reading t. |
| @@ -150,6 +154,7 @@ func (v *View) send() { | |||
| 150 | } | 154 | } |
| 151 | prompt := v.pastes.snapshot(text) | 155 | prompt := v.pastes.snapshot(text) |
| 152 | v.setInput("") | 156 | v.setInput("") |
| 157 | + v.recall = recall{} | ||
| 153 | v.follow = true | 158 | v.follow = true |
| 154 | v.pick.dismiss("") | 159 | v.pick.dismiss("") |
| 155 | if v.on.send != nil { | 160 | if v.on.send != nil { |
added
release-notes/README.md +33 -0 | new file mode 100644 | ||
| @@ -0,0 +1,33 @@ | ||
| 1 | +# Release notes | |
| 2 | + | |
| 3 | +One note per tag, in English (`.en.md`) and French (`.fr.md`). Rebuilt on 2026-10-18 from the diffs between tags, the tickets and the project history: the commit messages say nothing. | |
| 4 | + | |
| 5 | +| Version | Date | Summary | | | |
| 6 | +|---|---|---|---| | |
| 7 | +| [v0.7.0](v0.7.0.en.md) | 2026-10-18 | Free a model's memory, see which hooks ran, pick up an earlier session from your editor, and screen questions with guard models. | [FR](v0.7.0.fr.md) | | |
| 8 | +| [v0.6.0](v0.6.0.en.md) | 2026-10-17 | Your own tools declared in the agent file, and answers saved to a file without being written again. | [FR](v0.6.0.fr.md) | | |
| 9 | +| [v0.5.0](v0.5.0.en.md) | 2026-10-16 | Hooks: your own scripts around generations, tool calls and calls to other agents. | [FR](v0.5.0.fr.md) | | |
| 10 | +| [v0.4.0](v0.4.0.en.md) | 2026-10-12 | The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`. | [FR](v0.4.0.fr.md) | | |
| 11 | +| [v0.3.0](v0.3.0.en.md) | 2026-10-09 | Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`. | [FR](v0.3.0.fr.md) | | |
| 12 | +| [v0.2.6](v0.2.6.en.md) | 2026-10-03 | An agent can ask another mm agent. | [FR](v0.2.6.fr.md) | | |
| 13 | +| [v0.2.5](v0.2.5.en.md) | 2026-10-01 | Two new model servers: llmman and Ollama. | [FR](v0.2.5.fr.md) | | |
| 14 | +| [v0.2.4](v0.2.4.en.md) | 2026-09-30 | The server's prompt cache works again from one question to the next. | [FR](v0.2.4.fr.md) | | |
| 15 | +| [v0.2.3](v0.2.3.en.md) | 2026-09-29 | A built-in tool that reads web pages as Markdown. | [FR](v0.2.3.fr.md) | | |
| 16 | +| [v0.2.2](v0.2.2.en.md) | 2026-09-28 | **No change to mm's code.** Sample configurations and Docker Sandbox kits v3. | [FR](v0.2.2.fr.md) | | |
| 17 | +| [v0.2.1](v0.2.1.en.md) | 2026-09-27 | Compression that works with thinking models. | [FR](v0.2.1.fr.md) | | |
| 18 | +| [v0.2.0](v0.2.0.en.md) | 2026-09-26 | A long prompt is no longer taken for a hang. | [FR](v0.2.0.fr.md) | | |
| 19 | +| [v0.1.9](v0.1.9.en.md) | 2026-09-26 | ## Fixed | [FR](v0.1.9.fr.md) | | |
| 20 | +| [v0.1.8](v0.1.8.en.md) | 2026-09-26 | Token usage in editors, and tool calls cut off by `max_tokens`. | [FR](v0.1.8.fr.md) | | |
| 21 | +| [v0.1.7](v0.1.7.en.md) | 2026-09-25 | You can choose the name an editor sees. | [FR](v0.1.7.fr.md) | | |
| 22 | +| [v0.1.6](v0.1.6.en.md) | 2026-09-23 | MCP servers now work through the sbx gateway. | [FR](v0.1.6.fr.md) | | |
| 23 | +| [v0.1.5](v0.1.5.en.md) | 2026-09-23 | ## Fixed | [FR](v0.1.5.fr.md) | | |
| 24 | +| [v0.1.4](v0.1.4.en.md) | 2026-09-23 | MCP servers in a Docker sandbox, and a new authentication type. | [FR](v0.1.4.fr.md) | | |
| 25 | +| [v0.1.3](v0.1.3.en.md) | 2026-09-23 | **No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string. | [FR](v0.1.3.fr.md) | | |
| 26 | +| [v0.1.2](v0.1.2.en.md) | 2026-09-23 | mini-me can use MCP servers. | [FR](v0.1.2.fr.md) | | |
| 27 | +| [v0.1.1](v0.1.1.en.md) | 2026-09-22 | A plan tool, and a one-shot prompt mode. | [FR](v0.1.1.fr.md) | | |
| 28 | +| [v0.1.0](v0.1.0.en.md) | 2026-09-21 | The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.** | [FR](v0.1.0.fr.md) | | |
| 29 | +| [v0.0.4](v0.0.4.en.md) | 2026-09-21 | A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.** | [FR](v0.0.4.fr.md) | | |
| 30 | +| [v0.0.3](v0.0.3.en.md) | 2026-09-21 | Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker San | [FR](v0.0.3.fr.md) | | |
| 31 | +| [v0.0.2](v0.0.2.en.md) | 2026-09-18 | Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops. | [FR](v0.0.2.fr.md) | | |
| 32 | +| [v0.0.1](v0.0.1.en.md) | 2026-09-17 | The `bash` tool can be turned off. | [FR](v0.0.1.fr.md) | | |
| 33 | +| [v0.0.0](v0.0.0.en.md) | 2026-09-17 | The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard smal | [FR](v0.0.0.fr.md) | | |
| new file mode 100644 | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | +# Release notes | ||
| 2 | + | ||
| 3 | +One note per tag, in English (`.en.md`) and French (`.fr.md`). Rebuilt on 2026-10-18 from the diffs between tags, the tickets and the project history: the commit messages say nothing. | ||
| 4 | + | ||
| 5 | +| Version | Date | Summary | | | ||
| 6 | +|---|---|---|---| | ||
| 7 | +| [v0.7.0](v0.7.0.en.md) | 2026-10-18 | Free a model's memory, see which hooks ran, pick up an earlier session from your editor, and screen questions with guard models. | [FR](v0.7.0.fr.md) | | ||
| 8 | +| [v0.6.0](v0.6.0.en.md) | 2026-10-17 | Your own tools declared in the agent file, and answers saved to a file without being written again. | [FR](v0.6.0.fr.md) | | ||
| 9 | +| [v0.5.0](v0.5.0.en.md) | 2026-10-16 | Hooks: your own scripts around generations, tool calls and calls to other agents. | [FR](v0.5.0.fr.md) | | ||
| 10 | +| [v0.4.0](v0.4.0.en.md) | 2026-10-12 | The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`. | [FR](v0.4.0.fr.md) | | ||
| 11 | +| [v0.3.0](v0.3.0.en.md) | 2026-10-09 | Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`. | [FR](v0.3.0.fr.md) | | ||
| 12 | +| [v0.2.6](v0.2.6.en.md) | 2026-10-03 | An agent can ask another mm agent. | [FR](v0.2.6.fr.md) | | ||
| 13 | +| [v0.2.5](v0.2.5.en.md) | 2026-10-01 | Two new model servers: llmman and Ollama. | [FR](v0.2.5.fr.md) | | ||
| 14 | +| [v0.2.4](v0.2.4.en.md) | 2026-09-30 | The server's prompt cache works again from one question to the next. | [FR](v0.2.4.fr.md) | | ||
| 15 | +| [v0.2.3](v0.2.3.en.md) | 2026-09-29 | A built-in tool that reads web pages as Markdown. | [FR](v0.2.3.fr.md) | | ||
| 16 | +| [v0.2.2](v0.2.2.en.md) | 2026-09-28 | **No change to mm's code.** Sample configurations and Docker Sandbox kits v3. | [FR](v0.2.2.fr.md) | | ||
| 17 | +| [v0.2.1](v0.2.1.en.md) | 2026-09-27 | Compression that works with thinking models. | [FR](v0.2.1.fr.md) | | ||
| 18 | +| [v0.2.0](v0.2.0.en.md) | 2026-09-26 | A long prompt is no longer taken for a hang. | [FR](v0.2.0.fr.md) | | ||
| 19 | +| [v0.1.9](v0.1.9.en.md) | 2026-09-26 | ## Fixed | [FR](v0.1.9.fr.md) | | ||
| 20 | +| [v0.1.8](v0.1.8.en.md) | 2026-09-26 | Token usage in editors, and tool calls cut off by `max_tokens`. | [FR](v0.1.8.fr.md) | | ||
| 21 | +| [v0.1.7](v0.1.7.en.md) | 2026-09-25 | You can choose the name an editor sees. | [FR](v0.1.7.fr.md) | | ||
| 22 | +| [v0.1.6](v0.1.6.en.md) | 2026-09-23 | MCP servers now work through the sbx gateway. | [FR](v0.1.6.fr.md) | | ||
| 23 | +| [v0.1.5](v0.1.5.en.md) | 2026-09-23 | ## Fixed | [FR](v0.1.5.fr.md) | | ||
| 24 | +| [v0.1.4](v0.1.4.en.md) | 2026-09-23 | MCP servers in a Docker sandbox, and a new authentication type. | [FR](v0.1.4.fr.md) | | ||
| 25 | +| [v0.1.3](v0.1.3.en.md) | 2026-09-23 | **No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string. | [FR](v0.1.3.fr.md) | | ||
| 26 | +| [v0.1.2](v0.1.2.en.md) | 2026-09-23 | mini-me can use MCP servers. | [FR](v0.1.2.fr.md) | | ||
| 27 | +| [v0.1.1](v0.1.1.en.md) | 2026-09-22 | A plan tool, and a one-shot prompt mode. | [FR](v0.1.1.fr.md) | | ||
| 28 | +| [v0.1.0](v0.1.0.en.md) | 2026-09-21 | The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.** | [FR](v0.1.0.fr.md) | | ||
| 29 | +| [v0.0.4](v0.0.4.en.md) | 2026-09-21 | A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.** | [FR](v0.0.4.fr.md) | | ||
| 30 | +| [v0.0.3](v0.0.3.en.md) | 2026-09-21 | Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker San | [FR](v0.0.3.fr.md) | | ||
| 31 | +| [v0.0.2](v0.0.2.en.md) | 2026-09-18 | Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops. | [FR](v0.0.2.fr.md) | | ||
| 32 | +| [v0.0.1](v0.0.1.en.md) | 2026-09-17 | The `bash` tool can be turned off. | [FR](v0.0.1.fr.md) | | ||
| 33 | +| [v0.0.0](v0.0.0.en.md) | 2026-09-17 | The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard smal | [FR](v0.0.0.fr.md) | | ||
added
release-notes/v0.0.0.en.md +61 -0 | new file mode 100644 | ||
| @@ -0,0 +1,61 @@ | ||
| 1 | +# mini-me v0.0.0 | |
| 2 | + | |
| 3 | +*[Version française](v0.0.0.fr.md)* | |
| 4 | + | |
| 5 | +The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard small models against hallucinations, silent failures and endless loops. | |
| 6 | + | |
| 7 | +## What is in it | |
| 8 | + | |
| 9 | +### Three front ends, one loop | |
| 10 | + | |
| 11 | +- **Line mode**: `mm` in a terminal. | |
| 12 | +- **Window mode**: `mm -tui`, or `tui: true` in the configuration. It is a single window on tcell: | |
| 13 | + - the conversation on top and a 3-row input box; | |
| 14 | + - Enter sends the message, Ctrl-J adds a new line, Esc cancels the turn; | |
| 15 | + - a `/` command picker; | |
| 16 | + - with `permissions: ask`, `bash`, `write_file` and `edit_file` wait for your answer. | |
| 17 | +- **Editor mode**: `mm -acp` speaks the Agent Client Protocol (Zed and other ACP editors). Commands are advertised in the editor's slash menu. | |
| 18 | + | |
| 19 | +### Model servers | |
| 20 | + | |
| 21 | +- **Providers**: Docker Model Runner (`dmr`) and llama.cpp (`llamacpp`), both through the OpenAI-compatible API. | |
| 22 | +- **Start-up checks**: mm checks that the server answers, finds the context window, and warns when the model is missing or has no chat template. | |
| 23 | + | |
| 24 | +### Tools and skills | |
| 25 | + | |
| 26 | +- **Tools**: `bash`, `read_file`, `write_file`, `edit_file`. | |
| 27 | +- **Skills** (`read_skill`): stored as `<name>.md` or `<name>/SKILL.md`. A relative `skillsDir` is resolved from the start directory, or next to the configuration file in editor mode. The banner warns, with the full path, when no skill is found. | |
| 28 | +- **`@path` in a question** attaches a file or a directory: `📎` in the terminal, `resource_link` in an editor. | |
| 29 | + | |
| 30 | +### Commands | |
| 31 | + | |
| 32 | +- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` and `/copy [n|all]`. | |
| 33 | +- In an editor: `/new` and `/compact`. | |
| 34 | + | |
| 35 | +### Sessions and context | |
| 36 | + | |
| 37 | +- **Persistent sessions, per project** (`sessions.enabled`, under `.mm/sessions`): | |
| 38 | + - `/sessions` and `/resume` in the terminal; | |
| 39 | + - `session/load` in an editor, which replays the conversation. Zed's "Reload Agent" uses it. | |
| 40 | +- **Context compression** (`context.*`): a summary plus the last raw turns, triggered automatically in the line mode or by hand with `/compact`. | |
| 41 | + | |
| 42 | +### Safeguards | |
| 43 | + | |
| 44 | +- A **loop detector** for repeated identical tool calls. | |
| 45 | +- A **watchdog** (`watchdogTimeout`) for a stream that has gone silent. | |
| 46 | +- `maxTurns` and `maxOutput`. | |
| 47 | + | |
| 48 | +### Window mode extras | |
| 49 | + | |
| 50 | +- **Native text selection** works: the mouse is free by default, and F2 toggles mouse capture. | |
| 51 | +- **Copying**: Ctrl-Y and `/copy` copy an answer, through `pbcopy`, `wl-copy`, `xclip` or `xsel` first, then OSC 52. | |
| 52 | +- **Long pastes** fold into a `[Pasted #1 · 50 lines · 1.0k chars]` token. The model still receives the full text. | |
| 53 | + | |
| 54 | +## Distribution | |
| 55 | + | |
| 56 | +- **Binaries** for macOS arm64, Linux amd64/arm64 and Windows amd64/arm64, built by the release workflow (`go build`, `CGO_ENABLED=0`), with `SHA256SUMS`. | |
| 57 | +- **`install.sh`** builds `mm` and copies it to `/usr/local/bin`. | |
| 58 | + | |
| 59 | +## Documentation | |
| 60 | + | |
| 61 | +- **EN + FR documentation**, organised the Diátaxis way: a getting-started tutorial, how-to guides, reference (CLI, configuration, tools, slash commands, ACP, skill format) and explanations (architecture, providers, compression, sessions). | |
| new file mode 100644 | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | +# mini-me v0.0.0 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.0.0.fr.md)* | ||
| 4 | + | ||
| 5 | +The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard small models against hallucinations, silent failures and endless loops. | ||
| 6 | + | ||
| 7 | +## What is in it | ||
| 8 | + | ||
| 9 | +### Three front ends, one loop | ||
| 10 | + | ||
| 11 | +- **Line mode**: `mm` in a terminal. | ||
| 12 | +- **Window mode**: `mm -tui`, or `tui: true` in the configuration. It is a single window on tcell: | ||
| 13 | + - the conversation on top and a 3-row input box; | ||
| 14 | + - Enter sends the message, Ctrl-J adds a new line, Esc cancels the turn; | ||
| 15 | + - a `/` command picker; | ||
| 16 | + - with `permissions: ask`, `bash`, `write_file` and `edit_file` wait for your answer. | ||
| 17 | +- **Editor mode**: `mm -acp` speaks the Agent Client Protocol (Zed and other ACP editors). Commands are advertised in the editor's slash menu. | ||
| 18 | + | ||
| 19 | +### Model servers | ||
| 20 | + | ||
| 21 | +- **Providers**: Docker Model Runner (`dmr`) and llama.cpp (`llamacpp`), both through the OpenAI-compatible API. | ||
| 22 | +- **Start-up checks**: mm checks that the server answers, finds the context window, and warns when the model is missing or has no chat template. | ||
| 23 | + | ||
| 24 | +### Tools and skills | ||
| 25 | + | ||
| 26 | +- **Tools**: `bash`, `read_file`, `write_file`, `edit_file`. | ||
| 27 | +- **Skills** (`read_skill`): stored as `<name>.md` or `<name>/SKILL.md`. A relative `skillsDir` is resolved from the start directory, or next to the configuration file in editor mode. The banner warns, with the full path, when no skill is found. | ||
| 28 | +- **`@path` in a question** attaches a file or a directory: `📎` in the terminal, `resource_link` in an editor. | ||
| 29 | + | ||
| 30 | +### Commands | ||
| 31 | + | ||
| 32 | +- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` and `/copy [n|all]`. | ||
| 33 | +- In an editor: `/new` and `/compact`. | ||
| 34 | + | ||
| 35 | +### Sessions and context | ||
| 36 | + | ||
| 37 | +- **Persistent sessions, per project** (`sessions.enabled`, under `.mm/sessions`): | ||
| 38 | + - `/sessions` and `/resume` in the terminal; | ||
| 39 | + - `session/load` in an editor, which replays the conversation. Zed's "Reload Agent" uses it. | ||
| 40 | +- **Context compression** (`context.*`): a summary plus the last raw turns, triggered automatically in the line mode or by hand with `/compact`. | ||
| 41 | + | ||
| 42 | +### Safeguards | ||
| 43 | + | ||
| 44 | +- A **loop detector** for repeated identical tool calls. | ||
| 45 | +- A **watchdog** (`watchdogTimeout`) for a stream that has gone silent. | ||
| 46 | +- `maxTurns` and `maxOutput`. | ||
| 47 | + | ||
| 48 | +### Window mode extras | ||
| 49 | + | ||
| 50 | +- **Native text selection** works: the mouse is free by default, and F2 toggles mouse capture. | ||
| 51 | +- **Copying**: Ctrl-Y and `/copy` copy an answer, through `pbcopy`, `wl-copy`, `xclip` or `xsel` first, then OSC 52. | ||
| 52 | +- **Long pastes** fold into a `[Pasted #1 · 50 lines · 1.0k chars]` token. The model still receives the full text. | ||
| 53 | + | ||
| 54 | +## Distribution | ||
| 55 | + | ||
| 56 | +- **Binaries** for macOS arm64, Linux amd64/arm64 and Windows amd64/arm64, built by the release workflow (`go build`, `CGO_ENABLED=0`), with `SHA256SUMS`. | ||
| 57 | +- **`install.sh`** builds `mm` and copies it to `/usr/local/bin`. | ||
| 58 | + | ||
| 59 | +## Documentation | ||
| 60 | + | ||
| 61 | +- **EN + FR documentation**, organised the Diátaxis way: a getting-started tutorial, how-to guides, reference (CLI, configuration, tools, slash commands, ACP, skill format) and explanations (architecture, providers, compression, sessions). | ||
added
release-notes/v0.0.0.fr.md +61 -0 | new file mode 100644 | ||
| @@ -0,0 +1,61 @@ | ||
| 1 | +# mini-me v0.0.0 | |
| 2 | + | |
| 3 | +*[English version](v0.0.0.en.md)* | |
| 4 | + | |
| 5 | +La première version de mini-me (`mm`) : un agent de code minimal pour modèles locaux. Sa boucle a un outil principal (`bash`) et protège les petits modèles contre les hallucinations, les échecs silencieux et les boucles sans fin. | |
| 6 | + | |
| 7 | +## Contenu | |
| 8 | + | |
| 9 | +### Trois interfaces, une seule boucle | |
| 10 | + | |
| 11 | +- **Mode ligne** : `mm` dans un terminal. | |
| 12 | +- **Mode fenêtre** : `mm -tui`, ou `tui: true` dans la configuration. C'est une fenêtre unique sur tcell : | |
| 13 | + - la conversation en haut et une zone de saisie de 3 lignes ; | |
| 14 | + - Entrée envoie le message, Ctrl-J ajoute une nouvelle ligne, Échap annule le tour ; | |
| 15 | + - un sélecteur de commandes `/` ; | |
| 16 | + - avec `permissions: ask`, `bash`, `write_file` et `edit_file` attendent votre réponse. | |
| 17 | +- **Mode éditeur** : `mm -acp` parle l'Agent Client Protocol (Zed et les autres éditeurs ACP). Les commandes apparaissent dans le menu slash de l'éditeur. | |
| 18 | + | |
| 19 | +### Serveurs de modèles | |
| 20 | + | |
| 21 | +- **Fournisseurs** : Docker Model Runner (`dmr`) et llama.cpp (`llamacpp`), tous deux via l'API compatible OpenAI. | |
| 22 | +- **Vérifications au démarrage** : mm vérifie que le serveur répond, trouve la fenêtre de contexte, et prévient si le modèle est absent ou n'a pas de chat template. | |
| 23 | + | |
| 24 | +### Outils et skills | |
| 25 | + | |
| 26 | +- **Outils** : `bash`, `read_file`, `write_file`, `edit_file`. | |
| 27 | +- **Skills** (`read_skill`) : rangés en `<nom>.md` ou en `<nom>/SKILL.md`. Un `skillsDir` relatif est résolu depuis le répertoire de lancement, ou à côté du fichier de configuration en mode éditeur. La bannière prévient, avec le chemin complet, quand aucun skill n'est trouvé. | |
| 28 | +- **`@chemin` dans une question** joint un fichier ou un répertoire : `📎` dans le terminal, `resource_link` dans un éditeur. | |
| 29 | + | |
| 30 | +### Commandes | |
| 31 | + | |
| 32 | +- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` et `/copy [n|all]`. | |
| 33 | +- Dans un éditeur : `/new` et `/compact`. | |
| 34 | + | |
| 35 | +### Sessions et contexte | |
| 36 | + | |
| 37 | +- **Sessions persistantes, par projet** (`sessions.enabled`, dans `.mm/sessions`) : | |
| 38 | + - `/sessions` et `/resume` dans le terminal ; | |
| 39 | + - `session/load` dans un éditeur, qui rejoue la conversation. Le « Reload Agent » de Zed s'en sert. | |
| 40 | +- **Compression du contexte** (`context.*`) : un résumé plus les derniers tours bruts, déclenchée automatiquement en mode ligne ou à la main avec `/compact`. | |
| 41 | + | |
| 42 | +### Garde-fous | |
| 43 | + | |
| 44 | +- Un **détecteur de boucles** pour les appels d'outil identiques répétés. | |
| 45 | +- Un **chien de garde** (`watchdogTimeout`) pour un flux devenu silencieux. | |
| 46 | +- `maxTurns` et `maxOutput`. | |
| 47 | + | |
| 48 | +### En plus dans le mode fenêtre | |
| 49 | + | |
| 50 | +- **La sélection native du texte** fonctionne : la souris est libre par défaut, et F2 active ou coupe sa capture. | |
| 51 | +- **Copie** : Ctrl-Y et `/copy` copient une réponse, avec `pbcopy`, `wl-copy`, `xclip` ou `xsel` d'abord, puis OSC 52. | |
| 52 | +- **Les longs collages** se replient en un jeton `[Pasted #1 · 50 lines · 1.0k chars]`. Le modèle reçoit quand même le texte complet. | |
| 53 | + | |
| 54 | +## Distribution | |
| 55 | + | |
| 56 | +- **Binaires** pour macOS arm64, Linux amd64/arm64 et Windows amd64/arm64, construits par le workflow de release (`go build`, `CGO_ENABLED=0`), avec `SHA256SUMS`. | |
| 57 | +- **`install.sh`** construit `mm` et le copie dans `/usr/local/bin`. | |
| 58 | + | |
| 59 | +## Documentation | |
| 60 | + | |
| 61 | +- **Documentation EN + FR**, organisée selon Diátaxis : un tutoriel de démarrage, des guides pratiques, une référence (CLI, configuration, outils, commandes slash, ACP, format des skills) et des explications (architecture, fournisseurs, compression, sessions). | |
| new file mode 100644 | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | +# mini-me v0.0.0 | ||
| 2 | + | ||
| 3 | +*[English version](v0.0.0.en.md)* | ||
| 4 | + | ||
| 5 | +La première version de mini-me (`mm`) : un agent de code minimal pour modèles locaux. Sa boucle a un outil principal (`bash`) et protège les petits modèles contre les hallucinations, les échecs silencieux et les boucles sans fin. | ||
| 6 | + | ||
| 7 | +## Contenu | ||
| 8 | + | ||
| 9 | +### Trois interfaces, une seule boucle | ||
| 10 | + | ||
| 11 | +- **Mode ligne** : `mm` dans un terminal. | ||
| 12 | +- **Mode fenêtre** : `mm -tui`, ou `tui: true` dans la configuration. C'est une fenêtre unique sur tcell : | ||
| 13 | + - la conversation en haut et une zone de saisie de 3 lignes ; | ||
| 14 | + - Entrée envoie le message, Ctrl-J ajoute une nouvelle ligne, Échap annule le tour ; | ||
| 15 | + - un sélecteur de commandes `/` ; | ||
| 16 | + - avec `permissions: ask`, `bash`, `write_file` et `edit_file` attendent votre réponse. | ||
| 17 | +- **Mode éditeur** : `mm -acp` parle l'Agent Client Protocol (Zed et les autres éditeurs ACP). Les commandes apparaissent dans le menu slash de l'éditeur. | ||
| 18 | + | ||
| 19 | +### Serveurs de modèles | ||
| 20 | + | ||
| 21 | +- **Fournisseurs** : Docker Model Runner (`dmr`) et llama.cpp (`llamacpp`), tous deux via l'API compatible OpenAI. | ||
| 22 | +- **Vérifications au démarrage** : mm vérifie que le serveur répond, trouve la fenêtre de contexte, et prévient si le modèle est absent ou n'a pas de chat template. | ||
| 23 | + | ||
| 24 | +### Outils et skills | ||
| 25 | + | ||
| 26 | +- **Outils** : `bash`, `read_file`, `write_file`, `edit_file`. | ||
| 27 | +- **Skills** (`read_skill`) : rangés en `<nom>.md` ou en `<nom>/SKILL.md`. Un `skillsDir` relatif est résolu depuis le répertoire de lancement, ou à côté du fichier de configuration en mode éditeur. La bannière prévient, avec le chemin complet, quand aucun skill n'est trouvé. | ||
| 28 | +- **`@chemin` dans une question** joint un fichier ou un répertoire : `📎` dans le terminal, `resource_link` dans un éditeur. | ||
| 29 | + | ||
| 30 | +### Commandes | ||
| 31 | + | ||
| 32 | +- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` et `/copy [n|all]`. | ||
| 33 | +- Dans un éditeur : `/new` et `/compact`. | ||
| 34 | + | ||
| 35 | +### Sessions et contexte | ||
| 36 | + | ||
| 37 | +- **Sessions persistantes, par projet** (`sessions.enabled`, dans `.mm/sessions`) : | ||
| 38 | + - `/sessions` et `/resume` dans le terminal ; | ||
| 39 | + - `session/load` dans un éditeur, qui rejoue la conversation. Le « Reload Agent » de Zed s'en sert. | ||
| 40 | +- **Compression du contexte** (`context.*`) : un résumé plus les derniers tours bruts, déclenchée automatiquement en mode ligne ou à la main avec `/compact`. | ||
| 41 | + | ||
| 42 | +### Garde-fous | ||
| 43 | + | ||
| 44 | +- Un **détecteur de boucles** pour les appels d'outil identiques répétés. | ||
| 45 | +- Un **chien de garde** (`watchdogTimeout`) pour un flux devenu silencieux. | ||
| 46 | +- `maxTurns` et `maxOutput`. | ||
| 47 | + | ||
| 48 | +### En plus dans le mode fenêtre | ||
| 49 | + | ||
| 50 | +- **La sélection native du texte** fonctionne : la souris est libre par défaut, et F2 active ou coupe sa capture. | ||
| 51 | +- **Copie** : Ctrl-Y et `/copy` copient une réponse, avec `pbcopy`, `wl-copy`, `xclip` ou `xsel` d'abord, puis OSC 52. | ||
| 52 | +- **Les longs collages** se replient en un jeton `[Pasted #1 · 50 lines · 1.0k chars]`. Le modèle reçoit quand même le texte complet. | ||
| 53 | + | ||
| 54 | +## Distribution | ||
| 55 | + | ||
| 56 | +- **Binaires** pour macOS arm64, Linux amd64/arm64 et Windows amd64/arm64, construits par le workflow de release (`go build`, `CGO_ENABLED=0`), avec `SHA256SUMS`. | ||
| 57 | +- **`install.sh`** construit `mm` et le copie dans `/usr/local/bin`. | ||
| 58 | + | ||
| 59 | +## Documentation | ||
| 60 | + | ||
| 61 | +- **Documentation EN + FR**, organisée selon Diátaxis : un tutoriel de démarrage, des guides pratiques, une référence (CLI, configuration, outils, commandes slash, ACP, format des skills) et des explications (architecture, fournisseurs, compression, sessions). | ||
added
release-notes/v0.0.1.en.md +16 -0 | new file mode 100644 | ||
| @@ -0,0 +1,16 @@ | ||
| 1 | +# mini-me v0.0.1 | |
| 2 | + | |
| 3 | +*[Version française](v0.0.1.fr.md)* | |
| 4 | + | |
| 5 | +The `bash` tool can be turned off. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`bashTool`** (default `true`). With `bashTool: false`, the model can still read and edit files and load skills, but it never runs a command. | |
| 10 | + - The banner shows `tools: none` and warns when no tool is declared at all. | |
| 11 | + - If your system prompt still announces a bash tool, reword it. | |
| 12 | + | |
| 13 | +## Changed | |
| 14 | + | |
| 15 | +- **`README.md`** rewritten: what mini-me is, how to install it, the three modes, Zed and the other ACP editors. | |
| 16 | +- **Comments in `agent.llamacpp.yaml`** simplified to match `agent.dmr.yaml`. `--jinja` is no longer presented as required. | |
| new file mode 100644 | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | +# mini-me v0.0.1 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.0.1.fr.md)* | ||
| 4 | + | ||
| 5 | +The `bash` tool can be turned off. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`bashTool`** (default `true`). With `bashTool: false`, the model can still read and edit files and load skills, but it never runs a command. | ||
| 10 | + - The banner shows `tools: none` and warns when no tool is declared at all. | ||
| 11 | + - If your system prompt still announces a bash tool, reword it. | ||
| 12 | + | ||
| 13 | +## Changed | ||
| 14 | + | ||
| 15 | +- **`README.md`** rewritten: what mini-me is, how to install it, the three modes, Zed and the other ACP editors. | ||
| 16 | +- **Comments in `agent.llamacpp.yaml`** simplified to match `agent.dmr.yaml`. `--jinja` is no longer presented as required. | ||
added
release-notes/v0.0.1.fr.md +16 -0 | new file mode 100644 | ||
| @@ -0,0 +1,16 @@ | ||
| 1 | +# mini-me v0.0.1 | |
| 2 | + | |
| 3 | +*[English version](v0.0.1.en.md)* | |
| 4 | + | |
| 5 | +On peut désactiver l'outil `bash`. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **`bashTool`** (`true` par défaut). Avec `bashTool: false`, le modèle peut toujours lire et modifier des fichiers et charger des skills, mais il n'exécute jamais de commande. | |
| 10 | + - La bannière affiche `tools: none` et prévient quand aucun outil n'est déclaré. | |
| 11 | + - Si votre prompt système annonce encore un outil bash, reformulez-le. | |
| 12 | + | |
| 13 | +## Changements | |
| 14 | + | |
| 15 | +- **`README.md`** réécrit : ce qu'est mini-me, comment l'installer, les trois modes, Zed et les autres éditeurs ACP. | |
| 16 | +- **Commentaires d'`agent.llamacpp.yaml`** simplifiés comme ceux d'`agent.dmr.yaml`. `--jinja` n'est plus présenté comme obligatoire. | |
| new file mode 100644 | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | +# mini-me v0.0.1 | ||
| 2 | + | ||
| 3 | +*[English version](v0.0.1.en.md)* | ||
| 4 | + | ||
| 5 | +On peut désactiver l'outil `bash`. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **`bashTool`** (`true` par défaut). Avec `bashTool: false`, le modèle peut toujours lire et modifier des fichiers et charger des skills, mais il n'exécute jamais de commande. | ||
| 10 | + - La bannière affiche `tools: none` et prévient quand aucun outil n'est déclaré. | ||
| 11 | + - Si votre prompt système annonce encore un outil bash, reformulez-le. | ||
| 12 | + | ||
| 13 | +## Changements | ||
| 14 | + | ||
| 15 | +- **`README.md`** réécrit : ce qu'est mini-me, comment l'installer, les trois modes, Zed et les autres éditeurs ACP. | ||
| 16 | +- **Commentaires d'`agent.llamacpp.yaml`** simplifiés comme ceux d'`agent.dmr.yaml`. `--jinja` n'est plus présenté comme obligatoire. | ||
added
release-notes/v0.0.2.en.md +37 -0 | new file mode 100644 | ||
| @@ -0,0 +1,37 @@ | ||
| 1 | +# mini-me v0.0.2 | |
| 2 | + | |
| 3 | +*[Version française](v0.0.2.fr.md)* | |
| 4 | + | |
| 5 | +Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +### LSP support (#17) | |
| 10 | + | |
| 11 | +- **New `lsp` tool** with six actions: `definition`, `references`, `hover`, `symbols`, `diagnostics` and `rename`. | |
| 12 | + - The model names the `symbol`; columns are optional. | |
| 13 | + - `rename` asks for permission, like file edits. | |
| 14 | +- **Configuration**: list your servers under `lsp.servers` (`name`, `command`, `args`, `extensions`). The file extension picks the server. A server missing from the `PATH` is skipped with a warning. | |
| 15 | +- **`agent.lsp.yaml`**: a ready-made configuration. | |
| 16 | +- **`skills/lsp-navigation`**: a skill for code navigation. | |
| 17 | +- **New how-to guide**: Navigate code with LSP (EN + FR). It covers seven servers. | |
| 18 | + | |
| 19 | +### Errors recorded in the session (#21) | |
| 20 | + | |
| 21 | +- **New `errors` array** in the session file: failed generations, failed compactions and failed `/resume`. The model never reads it. A turn you cancel is not counted as an error. | |
| 22 | +- **`/sessions` and `/resume`** show `⚠️ N error(s)`. | |
| 23 | + | |
| 24 | +## Fixed | |
| 25 | + | |
| 26 | +- **Window mode**: the input box now wraps on words (#20). ↑ and ↓ move between rows. | |
| 27 | +- **Loops**: | |
| 28 | + - after 3 more identical calls once the loop is flagged, the turn stops with an explained error, also kept in the session; | |
| 29 | + - a tool call whose arguments run away (repeating patterns, or more than 256 KB) is cut while it streams, and the spinner shows `Composing a tool call`. | |
| 30 | +- **`edit_file`**: when `old` appears several times, the refusal now gives the line numbers of the occurrences. | |
| 31 | +- **`lsp rename`**: | |
| 32 | + - without `new_name`, the error says which call to send next; | |
| 33 | + - a mangled `symbol` is cleaned up, and `"Greet Hello"` is read as a rename from `Greet` to `Hello`. | |
| 34 | + | |
| 35 | +## Upgrading | |
| 36 | + | |
| 37 | +- **Go 1.26 or later** is now needed to build mm (required by `go.lsp.dev`). | |
| new file mode 100644 | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | +# mini-me v0.0.2 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.0.2.fr.md)* | ||
| 4 | + | ||
| 5 | +Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +### LSP support (#17) | ||
| 10 | + | ||
| 11 | +- **New `lsp` tool** with six actions: `definition`, `references`, `hover`, `symbols`, `diagnostics` and `rename`. | ||
| 12 | + - The model names the `symbol`; columns are optional. | ||
| 13 | + - `rename` asks for permission, like file edits. | ||
| 14 | +- **Configuration**: list your servers under `lsp.servers` (`name`, `command`, `args`, `extensions`). The file extension picks the server. A server missing from the `PATH` is skipped with a warning. | ||
| 15 | +- **`agent.lsp.yaml`**: a ready-made configuration. | ||
| 16 | +- **`skills/lsp-navigation`**: a skill for code navigation. | ||
| 17 | +- **New how-to guide**: Navigate code with LSP (EN + FR). It covers seven servers. | ||
| 18 | + | ||
| 19 | +### Errors recorded in the session (#21) | ||
| 20 | + | ||
| 21 | +- **New `errors` array** in the session file: failed generations, failed compactions and failed `/resume`. The model never reads it. A turn you cancel is not counted as an error. | ||
| 22 | +- **`/sessions` and `/resume`** show `⚠️ N error(s)`. | ||
| 23 | + | ||
| 24 | +## Fixed | ||
| 25 | + | ||
| 26 | +- **Window mode**: the input box now wraps on words (#20). ↑ and ↓ move between rows. | ||
| 27 | +- **Loops**: | ||
| 28 | + - after 3 more identical calls once the loop is flagged, the turn stops with an explained error, also kept in the session; | ||
| 29 | + - a tool call whose arguments run away (repeating patterns, or more than 256 KB) is cut while it streams, and the spinner shows `Composing a tool call`. | ||
| 30 | +- **`edit_file`**: when `old` appears several times, the refusal now gives the line numbers of the occurrences. | ||
| 31 | +- **`lsp rename`**: | ||
| 32 | + - without `new_name`, the error says which call to send next; | ||
| 33 | + - a mangled `symbol` is cleaned up, and `"Greet Hello"` is read as a rename from `Greet` to `Hello`. | ||
| 34 | + | ||
| 35 | +## Upgrading | ||
| 36 | + | ||
| 37 | +- **Go 1.26 or later** is now needed to build mm (required by `go.lsp.dev`). | ||
added
release-notes/v0.0.2.fr.md +37 -0 | new file mode 100644 | ||
| @@ -0,0 +1,37 @@ | ||
| 1 | +# mini-me v0.0.2 | |
| 2 | + | |
| 3 | +*[English version](v0.0.2.en.md)* | |
| 4 | + | |
| 5 | +Des serveurs de langage derrière un nouvel outil `lsp`, les erreurs gardées dans le fichier de session, et des garde-fous plus solides contre un modèle qui boucle. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +### Support LSP (#17) | |
| 10 | + | |
| 11 | +- **Nouvel outil `lsp`** avec six actions : `definition`, `references`, `hover`, `symbols`, `diagnostics` et `rename`. | |
| 12 | + - Le modèle nomme le `symbol` ; les colonnes sont facultatives. | |
| 13 | + - `rename` demande la permission, comme les modifications de fichiers. | |
| 14 | +- **Configuration** : listez vos serveurs sous `lsp.servers` (`name`, `command`, `args`, `extensions`). L'extension du fichier choisit le serveur. Un serveur absent du `PATH` est ignoré, avec un avertissement. | |
| 15 | +- **`agent.lsp.yaml`** : une configuration prête à l'emploi. | |
| 16 | +- **`skills/lsp-navigation`** : un skill pour naviguer dans le code. | |
| 17 | +- **Nouveau guide pratique** : Naviguer dans le code avec LSP (EN + FR). Il couvre sept serveurs. | |
| 18 | + | |
| 19 | +### Erreurs enregistrées dans la session (#21) | |
| 20 | + | |
| 21 | +- **Nouveau tableau `errors`** dans le fichier de session : générations échouées, compressions échouées et `/resume` échoués. Le modèle ne le lit jamais. Un tour que vous annulez ne compte pas comme une erreur. | |
| 22 | +- **`/sessions` et `/resume`** affichent `⚠️ N error(s)`. | |
| 23 | + | |
| 24 | +## Corrigé | |
| 25 | + | |
| 26 | +- **Mode fenêtre** : la zone de saisie revient maintenant à la ligne sur les mots (#20). ↑ et ↓ passent d'une ligne à l'autre. | |
| 27 | +- **Boucles** : | |
| 28 | + - après 3 appels identiques de plus une fois la boucle signalée, le tour s'arrête avec une erreur expliquée, gardée aussi dans la session ; | |
| 29 | + - un appel d'outil dont les arguments s'emballent (motifs qui se répètent, ou plus de 256 Ko) est coupé pendant qu'il arrive, et le spinner affiche `Composing a tool call`. | |
| 30 | +- **`edit_file`** : quand `old` apparaît plusieurs fois, le refus donne maintenant les numéros de ligne des occurrences. | |
| 31 | +- **`lsp rename`** : | |
| 32 | + - sans `new_name`, l'erreur dit quel appel envoyer ensuite ; | |
| 33 | + - un `symbol` abîmé est nettoyé, et `"Greet Hello"` est lu comme un renommage de `Greet` en `Hello`. | |
| 34 | + | |
| 35 | +## Mise à jour | |
| 36 | + | |
| 37 | +- **Go 1.26 ou plus récent** est maintenant nécessaire pour construire mm (exigé par `go.lsp.dev`). | |
| new file mode 100644 | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | +# mini-me v0.0.2 | ||
| 2 | + | ||
| 3 | +*[English version](v0.0.2.en.md)* | ||
| 4 | + | ||
| 5 | +Des serveurs de langage derrière un nouvel outil `lsp`, les erreurs gardées dans le fichier de session, et des garde-fous plus solides contre un modèle qui boucle. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +### Support LSP (#17) | ||
| 10 | + | ||
| 11 | +- **Nouvel outil `lsp`** avec six actions : `definition`, `references`, `hover`, `symbols`, `diagnostics` et `rename`. | ||
| 12 | + - Le modèle nomme le `symbol` ; les colonnes sont facultatives. | ||
| 13 | + - `rename` demande la permission, comme les modifications de fichiers. | ||
| 14 | +- **Configuration** : listez vos serveurs sous `lsp.servers` (`name`, `command`, `args`, `extensions`). L'extension du fichier choisit le serveur. Un serveur absent du `PATH` est ignoré, avec un avertissement. | ||
| 15 | +- **`agent.lsp.yaml`** : une configuration prête à l'emploi. | ||
| 16 | +- **`skills/lsp-navigation`** : un skill pour naviguer dans le code. | ||
| 17 | +- **Nouveau guide pratique** : Naviguer dans le code avec LSP (EN + FR). Il couvre sept serveurs. | ||
| 18 | + | ||
| 19 | +### Erreurs enregistrées dans la session (#21) | ||
| 20 | + | ||
| 21 | +- **Nouveau tableau `errors`** dans le fichier de session : générations échouées, compressions échouées et `/resume` échoués. Le modèle ne le lit jamais. Un tour que vous annulez ne compte pas comme une erreur. | ||
| 22 | +- **`/sessions` et `/resume`** affichent `⚠️ N error(s)`. | ||
| 23 | + | ||
| 24 | +## Corrigé | ||
| 25 | + | ||
| 26 | +- **Mode fenêtre** : la zone de saisie revient maintenant à la ligne sur les mots (#20). ↑ et ↓ passent d'une ligne à l'autre. | ||
| 27 | +- **Boucles** : | ||
| 28 | + - après 3 appels identiques de plus une fois la boucle signalée, le tour s'arrête avec une erreur expliquée, gardée aussi dans la session ; | ||
| 29 | + - un appel d'outil dont les arguments s'emballent (motifs qui se répètent, ou plus de 256 Ko) est coupé pendant qu'il arrive, et le spinner affiche `Composing a tool call`. | ||
| 30 | +- **`edit_file`** : quand `old` apparaît plusieurs fois, le refus donne maintenant les numéros de ligne des occurrences. | ||
| 31 | +- **`lsp rename`** : | ||
| 32 | + - sans `new_name`, l'erreur dit quel appel envoyer ensuite ; | ||
| 33 | + - un `symbol` abîmé est nettoyé, et `"Greet Hello"` est lu comme un renommage de `Greet` en `Hello`. | ||
| 34 | + | ||
| 35 | +## Mise à jour | ||
| 36 | + | ||
| 37 | +- **Go 1.26 ou plus récent** est maintenant nécessaire pour construire mm (exigé par `go.lsp.dev`). | ||
added
release-notes/v0.0.3.en.md +51 -0 | new file mode 100644 | ||
| @@ -0,0 +1,51 @@ | ||
| 1 | +# mini-me v0.0.3 | |
| 2 | + | |
| 3 | +*[Version française](v0.0.3.fr.md)* | |
| 4 | + | |
| 5 | +Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker Sandboxes. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +### Thinking models (#29) | |
| 10 | + | |
| 11 | +- **The reasoning is shown**: dim and in italics in the terminal, as a separate block in the window, and as `agent_thought_chunk` in an editor. `showThinking: false` hides it. | |
| 12 | +- **The watchdog no longer kills a model that only reasons.** Before this fix, a turn that reasoned for longer than `watchdogTimeout` was killed as "stalled", because reasoning-only chunks were not counted as activity. | |
| 13 | + | |
| 14 | +### Version and about (#2, #22, #23) | |
| 15 | + | |
| 16 | +- **`mm --version`** prints the version, and **`mm --about`** prints one screen about mm. | |
| 17 | +- **The version is visible**: at the start of the banner (`mm v0.0.3 | config: …`) and in the window's status bar. | |
| 18 | + | |
| 19 | +### Window mode | |
| 20 | + | |
| 21 | +- **Pickers** (#37, #38): | |
| 22 | + - an `@` file picker, usable anywhere in a sentence: Tab walks into a directory; | |
| 23 | + - a `$` skill picker: `$name` stays in front, followed by the skill's parameters. | |
| 24 | +- **Shorter model id in the header** (#24): `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` becomes `qwen3.5-2b:Q4_K_M`. | |
| 25 | + | |
| 26 | +### Skills (#15) | |
| 27 | + | |
| 28 | +- **Lookup by name**: skills are looked up by the name in their catalogue, not by their file path. | |
| 29 | +- **Keywords**: the `keywords:` line is now read and sent with the catalogue (8 keywords at most). | |
| 30 | +- **New `bg-jobs` skill**: the shipped prompts already mentioned it, but it did not exist. | |
| 31 | +- **Banner warnings** for a skill with no description, a skill name used twice, and keywords cut off. | |
| 32 | +- **`📖` count**: now shown even at zero, and it counts only the skills actually loaded. | |
| 33 | +- **In an editor**: skills appear in the slash menu as `/<skill> [parameters]`. | |
| 34 | +- **A routing evaluation** (`internal/eval`, enabled with `MM_EVAL`) measures which skill the model picks for each request. | |
| 35 | + | |
| 36 | +### Docker Sandboxes | |
| 37 | + | |
| 38 | +- A **template and two kits** (`mm-tui`, `mm-acp`) under `docker-sbx/`. | |
| 39 | +- **New how-to guide**: Run in a Docker sandbox. | |
| 40 | + | |
| 41 | +## Fixed | |
| 42 | + | |
| 43 | +- **A stuck turn can always be left in the window** (#26): | |
| 44 | + - `/quit` and `/abort` act at once instead of waiting in the queue; | |
| 45 | + - a second Ctrl-C quits. | |
| 46 | +- **`bash` and background jobs**: a command that starts a background job no longer blocks the tool for ever. The job keeps running. | |
| 47 | +- **`edit_file` tolerates whitespace** (#27): tabs against spaces, and spaces at the start or end of the text. | |
| 48 | + - The match must still be unique. | |
| 49 | + - The new text is re-indented the way the file is. | |
| 50 | + - The headline says when whitespace was ignored. | |
| 51 | + - A failed match quotes the closest lines of the file. | |
| new file mode 100644 | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | +# mini-me v0.0.3 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.0.3.fr.md)* | ||
| 4 | + | ||
| 5 | +Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker Sandboxes. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +### Thinking models (#29) | ||
| 10 | + | ||
| 11 | +- **The reasoning is shown**: dim and in italics in the terminal, as a separate block in the window, and as `agent_thought_chunk` in an editor. `showThinking: false` hides it. | ||
| 12 | +- **The watchdog no longer kills a model that only reasons.** Before this fix, a turn that reasoned for longer than `watchdogTimeout` was killed as "stalled", because reasoning-only chunks were not counted as activity. | ||
| 13 | + | ||
| 14 | +### Version and about (#2, #22, #23) | ||
| 15 | + | ||
| 16 | +- **`mm --version`** prints the version, and **`mm --about`** prints one screen about mm. | ||
| 17 | +- **The version is visible**: at the start of the banner (`mm v0.0.3 | config: …`) and in the window's status bar. | ||
| 18 | + | ||
| 19 | +### Window mode | ||
| 20 | + | ||
| 21 | +- **Pickers** (#37, #38): | ||
| 22 | + - an `@` file picker, usable anywhere in a sentence: Tab walks into a directory; | ||
| 23 | + - a `$` skill picker: `$name` stays in front, followed by the skill's parameters. | ||
| 24 | +- **Shorter model id in the header** (#24): `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` becomes `qwen3.5-2b:Q4_K_M`. | ||
| 25 | + | ||
| 26 | +### Skills (#15) | ||
| 27 | + | ||
| 28 | +- **Lookup by name**: skills are looked up by the name in their catalogue, not by their file path. | ||
| 29 | +- **Keywords**: the `keywords:` line is now read and sent with the catalogue (8 keywords at most). | ||
| 30 | +- **New `bg-jobs` skill**: the shipped prompts already mentioned it, but it did not exist. | ||
| 31 | +- **Banner warnings** for a skill with no description, a skill name used twice, and keywords cut off. | ||
| 32 | +- **`📖` count**: now shown even at zero, and it counts only the skills actually loaded. | ||
| 33 | +- **In an editor**: skills appear in the slash menu as `/<skill> [parameters]`. | ||
| 34 | +- **A routing evaluation** (`internal/eval`, enabled with `MM_EVAL`) measures which skill the model picks for each request. | ||
| 35 | + | ||
| 36 | +### Docker Sandboxes | ||
| 37 | + | ||
| 38 | +- A **template and two kits** (`mm-tui`, `mm-acp`) under `docker-sbx/`. | ||
| 39 | +- **New how-to guide**: Run in a Docker sandbox. | ||
| 40 | + | ||
| 41 | +## Fixed | ||
| 42 | + | ||
| 43 | +- **A stuck turn can always be left in the window** (#26): | ||
| 44 | + - `/quit` and `/abort` act at once instead of waiting in the queue; | ||
| 45 | + - a second Ctrl-C quits. | ||
| 46 | +- **`bash` and background jobs**: a command that starts a background job no longer blocks the tool for ever. The job keeps running. | ||
| 47 | +- **`edit_file` tolerates whitespace** (#27): tabs against spaces, and spaces at the start or end of the text. | ||
| 48 | + - The match must still be unique. | ||
| 49 | + - The new text is re-indented the way the file is. | ||
| 50 | + - The headline says when whitespace was ignored. | ||
| 51 | + - A failed match quotes the closest lines of the file. | ||
added
release-notes/v0.0.3.fr.md +51 -0 | new file mode 100644 | ||
| @@ -0,0 +1,51 @@ | ||
| 1 | +# mini-me v0.0.3 | |
| 2 | + | |
| 3 | +*[English version](v0.0.3.en.md)* | |
| 4 | + | |
| 5 | +Les modèles qui réfléchissent, une version visible, une fenêtre qu'on peut toujours quitter, un `edit_file` qui tolère les espaces, des skills plus malins, et les Docker Sandboxes. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +### Modèles qui réfléchissent (#29) | |
| 10 | + | |
| 11 | +- **Le raisonnement est affiché** : grisé et en italique dans le terminal, dans un bloc à part dans la fenêtre, et en `agent_thought_chunk` dans un éditeur. `showThinking: false` le masque. | |
| 12 | +- **Le chien de garde ne tue plus un modèle qui ne fait que raisonner.** Avant ce correctif, un tour qui raisonnait plus longtemps que `watchdogTimeout` était tué comme « bloqué », parce que les fragments de raisonnement ne comptaient pas comme une activité. | |
| 13 | + | |
| 14 | +### Version et « à propos » (#2, #22, #23) | |
| 15 | + | |
| 16 | +- **`mm --version`** affiche la version, et **`mm --about`** affiche un écran de présentation de mm. | |
| 17 | +- **La version est visible** : au début de la bannière (`mm v0.0.3 | config: …`) et dans la barre d'état de la fenêtre. | |
| 18 | + | |
| 19 | +### Mode fenêtre | |
| 20 | + | |
| 21 | +- **Sélecteurs** (#37, #38) : | |
| 22 | + - un sélecteur de fichiers `@`, utilisable n'importe où dans une phrase : Tab entre dans un répertoire ; | |
| 23 | + - un sélecteur de skills `$` : `$nom` reste en tête, suivi des paramètres du skill. | |
| 24 | +- **Identifiant du modèle raccourci dans l'en-tête** (#24) : `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` devient `qwen3.5-2b:Q4_K_M`. | |
| 25 | + | |
| 26 | +### Skills (#15) | |
| 27 | + | |
| 28 | +- **Recherche par nom** : les skills sont cherchés par le nom de leur catalogue, plus par leur chemin de fichier. | |
| 29 | +- **Mots-clés** : la ligne `keywords:` est maintenant lue et envoyée avec le catalogue (8 mots-clés au plus). | |
| 30 | +- **Nouveau skill `bg-jobs`** : les prompts livrés le mentionnaient déjà, mais il n'existait pas. | |
| 31 | +- **Avertissements dans la bannière** pour un skill sans description, un nom de skill utilisé deux fois et des mots-clés tronqués. | |
| 32 | +- **Compteur `📖`** : affiché maintenant même à zéro, et il ne compte que les skills réellement chargés. | |
| 33 | +- **Dans un éditeur** : les skills apparaissent dans le menu slash sous la forme `/<skill> [paramètres]`. | |
| 34 | +- **Une évaluation du routage** (`internal/eval`, activée par `MM_EVAL`) mesure quel skill le modèle choisit pour chaque demande. | |
| 35 | + | |
| 36 | +### Docker Sandboxes | |
| 37 | + | |
| 38 | +- Un **template et deux kits** (`mm-tui`, `mm-acp`) dans `docker-sbx/`. | |
| 39 | +- **Nouveau guide pratique** : Lancer dans un Docker sandbox. | |
| 40 | + | |
| 41 | +## Corrigé | |
| 42 | + | |
| 43 | +- **On peut toujours quitter un tour bloqué dans la fenêtre** (#26) : | |
| 44 | + - `/quit` et `/abort` agissent tout de suite au lieu d'attendre dans la file ; | |
| 45 | + - un second Ctrl-C quitte. | |
| 46 | +- **`bash` et les tâches de fond** : une commande qui lance une tâche de fond ne bloque plus l'outil pour toujours. La tâche continue de tourner. | |
| 47 | +- **`edit_file` tolère les espaces** (#27) : tabulations contre espaces, et espaces au début ou à la fin du texte. | |
| 48 | + - La correspondance doit toujours être unique. | |
| 49 | + - Le nouveau texte est réindenté comme le fichier. | |
| 50 | + - Le titre du résultat dit quand des espaces ont été ignorés. | |
| 51 | + - Une recherche ratée cite les lignes les plus proches du fichier. | |
| new file mode 100644 | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | +# mini-me v0.0.3 | ||
| 2 | + | ||
| 3 | +*[English version](v0.0.3.en.md)* | ||
| 4 | + | ||
| 5 | +Les modèles qui réfléchissent, une version visible, une fenêtre qu'on peut toujours quitter, un `edit_file` qui tolère les espaces, des skills plus malins, et les Docker Sandboxes. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +### Modèles qui réfléchissent (#29) | ||
| 10 | + | ||
| 11 | +- **Le raisonnement est affiché** : grisé et en italique dans le terminal, dans un bloc à part dans la fenêtre, et en `agent_thought_chunk` dans un éditeur. `showThinking: false` le masque. | ||
| 12 | +- **Le chien de garde ne tue plus un modèle qui ne fait que raisonner.** Avant ce correctif, un tour qui raisonnait plus longtemps que `watchdogTimeout` était tué comme « bloqué », parce que les fragments de raisonnement ne comptaient pas comme une activité. | ||
| 13 | + | ||
| 14 | +### Version et « à propos » (#2, #22, #23) | ||
| 15 | + | ||
| 16 | +- **`mm --version`** affiche la version, et **`mm --about`** affiche un écran de présentation de mm. | ||
| 17 | +- **La version est visible** : au début de la bannière (`mm v0.0.3 | config: …`) et dans la barre d'état de la fenêtre. | ||
| 18 | + | ||
| 19 | +### Mode fenêtre | ||
| 20 | + | ||
| 21 | +- **Sélecteurs** (#37, #38) : | ||
| 22 | + - un sélecteur de fichiers `@`, utilisable n'importe où dans une phrase : Tab entre dans un répertoire ; | ||
| 23 | + - un sélecteur de skills `$` : `$nom` reste en tête, suivi des paramètres du skill. | ||
| 24 | +- **Identifiant du modèle raccourci dans l'en-tête** (#24) : `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` devient `qwen3.5-2b:Q4_K_M`. | ||
| 25 | + | ||
| 26 | +### Skills (#15) | ||
| 27 | + | ||
| 28 | +- **Recherche par nom** : les skills sont cherchés par le nom de leur catalogue, plus par leur chemin de fichier. | ||
| 29 | +- **Mots-clés** : la ligne `keywords:` est maintenant lue et envoyée avec le catalogue (8 mots-clés au plus). | ||
| 30 | +- **Nouveau skill `bg-jobs`** : les prompts livrés le mentionnaient déjà, mais il n'existait pas. | ||
| 31 | +- **Avertissements dans la bannière** pour un skill sans description, un nom de skill utilisé deux fois et des mots-clés tronqués. | ||
| 32 | +- **Compteur `📖`** : affiché maintenant même à zéro, et il ne compte que les skills réellement chargés. | ||
| 33 | +- **Dans un éditeur** : les skills apparaissent dans le menu slash sous la forme `/<skill> [paramètres]`. | ||
| 34 | +- **Une évaluation du routage** (`internal/eval`, activée par `MM_EVAL`) mesure quel skill le modèle choisit pour chaque demande. | ||
| 35 | + | ||
| 36 | +### Docker Sandboxes | ||
| 37 | + | ||
| 38 | +- Un **template et deux kits** (`mm-tui`, `mm-acp`) dans `docker-sbx/`. | ||
| 39 | +- **Nouveau guide pratique** : Lancer dans un Docker sandbox. | ||
| 40 | + | ||
| 41 | +## Corrigé | ||
| 42 | + | ||
| 43 | +- **On peut toujours quitter un tour bloqué dans la fenêtre** (#26) : | ||
| 44 | + - `/quit` et `/abort` agissent tout de suite au lieu d'attendre dans la file ; | ||
| 45 | + - un second Ctrl-C quitte. | ||
| 46 | +- **`bash` et les tâches de fond** : une commande qui lance une tâche de fond ne bloque plus l'outil pour toujours. La tâche continue de tourner. | ||
| 47 | +- **`edit_file` tolère les espaces** (#27) : tabulations contre espaces, et espaces au début ou à la fin du texte. | ||
| 48 | + - La correspondance doit toujours être unique. | ||
| 49 | + - Le nouveau texte est réindenté comme le fichier. | ||
| 50 | + - Le titre du résultat dit quand des espaces ont été ignorés. | ||
| 51 | + - Une recherche ratée cite les lignes les plus proches du fichier. | ||
added
release-notes/v0.0.4.en.md +9 -0 | new file mode 100644 | ||
| @@ -0,0 +1,9 @@ | ||
| 1 | +# mini-me v0.0.4 | |
| 2 | + | |
| 3 | +*[Version française](v0.0.4.fr.md)* | |
| 4 | + | |
| 5 | +A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.** | |
| 6 | + | |
| 7 | +## Changed | |
| 8 | + | |
| 9 | +- **CI, Docker Hub job**: the job that publishes the sandbox template and kits now cleans up the credentials before using them (stray newlines and spaces). It refuses an e-mail address used as the Docker handle, and prints diagnostics about the path to Docker Hub. | |
| new file mode 100644 | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | +# mini-me v0.0.4 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.0.4.fr.md)* | ||
| 4 | + | ||
| 5 | +A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.** | ||
| 6 | + | ||
| 7 | +## Changed | ||
| 8 | + | ||
| 9 | +- **CI, Docker Hub job**: the job that publishes the sandbox template and kits now cleans up the credentials before using them (stray newlines and spaces). It refuses an e-mail address used as the Docker handle, and prints diagnostics about the path to Docker Hub. | ||
added
release-notes/v0.0.4.fr.md +9 -0 | new file mode 100644 | ||
| @@ -0,0 +1,9 @@ | ||
| 1 | +# mini-me v0.0.4 | |
| 2 | + | |
| 3 | +*[English version](v0.0.4.en.md)* | |
| 4 | + | |
| 5 | +Seulement une correction de la chaîne de release : **le binaire `mm` est le même qu'en v0.0.3.** | |
| 6 | + | |
| 7 | +## Changements | |
| 8 | + | |
| 9 | +- **CI, tâche Docker Hub** : la tâche qui publie le template et les kits du sandbox nettoie maintenant les identifiants avant de s'en servir (retours à la ligne et espaces en trop). Elle refuse une adresse e-mail utilisée comme identifiant Docker, et affiche un diagnostic du chemin vers Docker Hub. | |
| new file mode 100644 | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | +# mini-me v0.0.4 | ||
| 2 | + | ||
| 3 | +*[English version](v0.0.4.en.md)* | ||
| 4 | + | ||
| 5 | +Seulement une correction de la chaîne de release : **le binaire `mm` est le même qu'en v0.0.3.** | ||
| 6 | + | ||
| 7 | +## Changements | ||
| 8 | + | ||
| 9 | +- **CI, tâche Docker Hub** : la tâche qui publie le template et les kits du sandbox nettoie maintenant les identifiants avant de s'en servir (retours à la ligne et espaces en trop). Elle refuse une adresse e-mail utilisée comme identifiant Docker, et affiche un diagnostic du chemin vers Docker Hub. | ||
added
release-notes/v0.1.0.en.md +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# mini-me v0.1.0 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.0.fr.md)* | |
| 4 | + | |
| 5 | +The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.** | |
| 6 | + | |
| 7 | +## Changed | |
| 8 | + | |
| 9 | +- **The Docker Hub job is removed from the release workflow** (it failed at `docker login` on the CI runner). | |
| 10 | +- **New script `docker-sbx/publish.sh`** builds the multi-arch template and pushes the two kits (#35). | |
| 11 | + - It takes the version from `release.env` or from its argument. | |
| 12 | + - It needs `DOCKER_HANDLE` and `DOCKER_PAT`. | |
| 13 | +- **The sandbox how-to guide (EN + FR)** explains how to publish a version. | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# mini-me v0.1.0 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.0.fr.md)* | ||
| 4 | + | ||
| 5 | +The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.** | ||
| 6 | + | ||
| 7 | +## Changed | ||
| 8 | + | ||
| 9 | +- **The Docker Hub job is removed from the release workflow** (it failed at `docker login` on the CI runner). | ||
| 10 | +- **New script `docker-sbx/publish.sh`** builds the multi-arch template and pushes the two kits (#35). | ||
| 11 | + - It takes the version from `release.env` or from its argument. | ||
| 12 | + - It needs `DOCKER_HANDLE` and `DOCKER_PAT`. | ||
| 13 | +- **The sandbox how-to guide (EN + FR)** explains how to publish a version. | ||
added
release-notes/v0.1.0.fr.md +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# mini-me v0.1.0 | |
| 2 | + | |
| 3 | +*[English version](v0.1.0.en.md)* | |
| 4 | + | |
| 5 | +Les images Docker Sandbox se publient maintenant à la main. **Le binaire `mm` est le même qu'en v0.0.4.** | |
| 6 | + | |
| 7 | +## Changements | |
| 8 | + | |
| 9 | +- **La tâche Docker Hub est retirée du workflow de release** (elle échouait au `docker login` sur le runner de la CI). | |
| 10 | +- **Nouveau script `docker-sbx/publish.sh`** : il construit le template multi-architecture et pousse les deux kits (#35). | |
| 11 | + - Il prend la version dans `release.env` ou en argument. | |
| 12 | + - Il a besoin de `DOCKER_HANDLE` et `DOCKER_PAT`. | |
| 13 | +- **Le guide pratique du sandbox (EN + FR)** explique comment publier une version. | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# mini-me v0.1.0 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.0.en.md)* | ||
| 4 | + | ||
| 5 | +Les images Docker Sandbox se publient maintenant à la main. **Le binaire `mm` est le même qu'en v0.0.4.** | ||
| 6 | + | ||
| 7 | +## Changements | ||
| 8 | + | ||
| 9 | +- **La tâche Docker Hub est retirée du workflow de release** (elle échouait au `docker login` sur le runner de la CI). | ||
| 10 | +- **Nouveau script `docker-sbx/publish.sh`** : il construit le template multi-architecture et pousse les deux kits (#35). | ||
| 11 | + - Il prend la version dans `release.env` ou en argument. | ||
| 12 | + - Il a besoin de `DOCKER_HANDLE` et `DOCKER_PAT`. | ||
| 13 | +- **Le guide pratique du sandbox (EN + FR)** explique comment publier une version. | ||
added
release-notes/v0.1.1.en.md +19 -0 | new file mode 100644 | ||
| @@ -0,0 +1,19 @@ | ||
| 1 | +# mini-me v0.1.1 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.1.fr.md)* | |
| 4 | + | |
| 5 | +A plan tool, and a one-shot prompt mode. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`plan` tool** (#25): the agent creates and follows a list of tasks for a multi-step request. After each answer, a `📋 [2/3] …` line shows the progress. | |
| 10 | + - It is turned on with `planTool` (default `true`). | |
| 11 | + - A `plan-manager` skill ships with it. | |
| 12 | +- **`-p` / `-prompt`** (#32): runs one prompt and exits. There is no banner and no saved session. | |
| 13 | + - The configuration file can come before or after the flag: `mm -p "…" agent.yaml`. | |
| 14 | +- **New how-to guides** (EN + FR): Manage plans and tasks, Run a single prompt. | |
| 15 | + | |
| 16 | +## Docs | |
| 17 | + | |
| 18 | +- **`INSTALL.MACOS.md`**: installing on macOS. | |
| 19 | +- **Sandboxes**: guides for using the sandbox from Zed and from the Turbo editors. | |
| new file mode 100644 | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | +# mini-me v0.1.1 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.1.fr.md)* | ||
| 4 | + | ||
| 5 | +A plan tool, and a one-shot prompt mode. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`plan` tool** (#25): the agent creates and follows a list of tasks for a multi-step request. After each answer, a `📋 [2/3] …` line shows the progress. | ||
| 10 | + - It is turned on with `planTool` (default `true`). | ||
| 11 | + - A `plan-manager` skill ships with it. | ||
| 12 | +- **`-p` / `-prompt`** (#32): runs one prompt and exits. There is no banner and no saved session. | ||
| 13 | + - The configuration file can come before or after the flag: `mm -p "…" agent.yaml`. | ||
| 14 | +- **New how-to guides** (EN + FR): Manage plans and tasks, Run a single prompt. | ||
| 15 | + | ||
| 16 | +## Docs | ||
| 17 | + | ||
| 18 | +- **`INSTALL.MACOS.md`**: installing on macOS. | ||
| 19 | +- **Sandboxes**: guides for using the sandbox from Zed and from the Turbo editors. | ||
added
release-notes/v0.1.1.fr.md +19 -0 | new file mode 100644 | ||
| @@ -0,0 +1,19 @@ | ||
| 1 | +# mini-me v0.1.1 | |
| 2 | + | |
| 3 | +*[English version](v0.1.1.en.md)* | |
| 4 | + | |
| 5 | +Un outil de plan, et un mode prompt unique. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **Outil `plan`** (#25) : l'agent crée et suit une liste de tâches pour une demande en plusieurs étapes. Après chaque réponse, une ligne `📋 [2/3] …` montre l'avancement. | |
| 10 | + - Il s'active avec `planTool` (`true` par défaut). | |
| 11 | + - Un skill `plan-manager` est livré avec. | |
| 12 | +- **`-p` / `-prompt`** (#32) : lance un seul prompt et quitte. Il n'y a pas de bannière ni de session enregistrée. | |
| 13 | + - Le fichier de configuration peut venir avant ou après l'option : `mm -p "…" agent.yaml`. | |
| 14 | +- **Nouveaux guides pratiques** (EN + FR) : Gérer des plans et des tâches, Lancer un seul prompt. | |
| 15 | + | |
| 16 | +## Documentation | |
| 17 | + | |
| 18 | +- **`INSTALL.MACOS.md`** : installer sur macOS. | |
| 19 | +- **Sandboxes** : des guides pour utiliser le sandbox depuis Zed et depuis les éditeurs Turbo. | |
| new file mode 100644 | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | +# mini-me v0.1.1 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.1.en.md)* | ||
| 4 | + | ||
| 5 | +Un outil de plan, et un mode prompt unique. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **Outil `plan`** (#25) : l'agent crée et suit une liste de tâches pour une demande en plusieurs étapes. Après chaque réponse, une ligne `📋 [2/3] …` montre l'avancement. | ||
| 10 | + - Il s'active avec `planTool` (`true` par défaut). | ||
| 11 | + - Un skill `plan-manager` est livré avec. | ||
| 12 | +- **`-p` / `-prompt`** (#32) : lance un seul prompt et quitte. Il n'y a pas de bannière ni de session enregistrée. | ||
| 13 | + - Le fichier de configuration peut venir avant ou après l'option : `mm -p "…" agent.yaml`. | ||
| 14 | +- **Nouveaux guides pratiques** (EN + FR) : Gérer des plans et des tâches, Lancer un seul prompt. | ||
| 15 | + | ||
| 16 | +## Documentation | ||
| 17 | + | ||
| 18 | +- **`INSTALL.MACOS.md`** : installer sur macOS. | ||
| 19 | +- **Sandboxes** : des guides pour utiliser le sandbox depuis Zed et depuis les éditeurs Turbo. | ||
added
release-notes/v0.1.2.en.md +16 -0 | new file mode 100644 | ||
| @@ -0,0 +1,16 @@ | ||
| 1 | +# mini-me v0.1.2 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.2.fr.md)* | |
| 4 | + | |
| 5 | +mini-me can use MCP servers. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **MCP client** (#4), configured under `mcp:`: | |
| 10 | + - **Transport**: HTTP. Stdio is not implemented yet. | |
| 11 | + - **Authentication**: `none`, `bearer` (token from an environment variable) or `api_key` (custom header). | |
| 12 | + - **Per server**: `headers`, `timeout` and a `tools` filter. | |
| 13 | + - **Permissions**: `mcp.permissions` defaults to `ask`, and can be set per server. | |
| 14 | + - **Start-up**: tools are discovered within `discoveryTimeout`. | |
| 15 | +- **`agent.mcp-example.yaml`**: an example configuration. | |
| 16 | +- **MCP documentation**: a tutorial, how-to guides (Context7, GitHub, Wikipedia), reference (configuration, authentication, available tools) and explanations (architecture, design decisions). | |
| new file mode 100644 | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | +# mini-me v0.1.2 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.2.fr.md)* | ||
| 4 | + | ||
| 5 | +mini-me can use MCP servers. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **MCP client** (#4), configured under `mcp:`: | ||
| 10 | + - **Transport**: HTTP. Stdio is not implemented yet. | ||
| 11 | + - **Authentication**: `none`, `bearer` (token from an environment variable) or `api_key` (custom header). | ||
| 12 | + - **Per server**: `headers`, `timeout` and a `tools` filter. | ||
| 13 | + - **Permissions**: `mcp.permissions` defaults to `ask`, and can be set per server. | ||
| 14 | + - **Start-up**: tools are discovered within `discoveryTimeout`. | ||
| 15 | +- **`agent.mcp-example.yaml`**: an example configuration. | ||
| 16 | +- **MCP documentation**: a tutorial, how-to guides (Context7, GitHub, Wikipedia), reference (configuration, authentication, available tools) and explanations (architecture, design decisions). | ||
added
release-notes/v0.1.2.fr.md +16 -0 | new file mode 100644 | ||
| @@ -0,0 +1,16 @@ | ||
| 1 | +# mini-me v0.1.2 | |
| 2 | + | |
| 3 | +*[English version](v0.1.2.en.md)* | |
| 4 | + | |
| 5 | +mini-me peut utiliser des serveurs MCP. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **Client MCP** (#4), configuré sous `mcp:` : | |
| 10 | + - **Transport** : HTTP. Le stdio n'est pas encore implémenté. | |
| 11 | + - **Authentification** : `none`, `bearer` (jeton lu dans une variable d'environnement) ou `api_key` (en-tête personnalisé). | |
| 12 | + - **Par serveur** : `headers`, `timeout` et un filtre `tools`. | |
| 13 | + - **Permissions** : `mcp.permissions` vaut `ask` par défaut, et se règle par serveur. | |
| 14 | + - **Au démarrage** : les outils sont découverts dans la limite de `discoveryTimeout`. | |
| 15 | +- **`agent.mcp-example.yaml`** : un exemple de configuration. | |
| 16 | +- **Documentation MCP** : un tutoriel, des guides pratiques (Context7, GitHub, Wikipedia), une référence (configuration, authentification, outils disponibles) et des explications (architecture, choix de conception). | |
| new file mode 100644 | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | +# mini-me v0.1.2 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.2.en.md)* | ||
| 4 | + | ||
| 5 | +mini-me peut utiliser des serveurs MCP. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **Client MCP** (#4), configuré sous `mcp:` : | ||
| 10 | + - **Transport** : HTTP. Le stdio n'est pas encore implémenté. | ||
| 11 | + - **Authentification** : `none`, `bearer` (jeton lu dans une variable d'environnement) ou `api_key` (en-tête personnalisé). | ||
| 12 | + - **Par serveur** : `headers`, `timeout` et un filtre `tools`. | ||
| 13 | + - **Permissions** : `mcp.permissions` vaut `ask` par défaut, et se règle par serveur. | ||
| 14 | + - **Au démarrage** : les outils sont découverts dans la limite de `discoveryTimeout`. | ||
| 15 | +- **`agent.mcp-example.yaml`** : un exemple de configuration. | ||
| 16 | +- **Documentation MCP** : un tutoriel, des guides pratiques (Context7, GitHub, Wikipedia), une référence (configuration, authentification, outils disponibles) et des explications (architecture, choix de conception). | ||
added
release-notes/v0.1.3.en.md +5 -0 | new file mode 100644 | ||
| @@ -0,0 +1,5 @@ | ||
| 1 | +# mini-me v0.1.3 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.3.fr.md)* | |
| 4 | + | |
| 5 | +**No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string. | |
| new file mode 100644 | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | +# mini-me v0.1.3 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.3.fr.md)* | ||
| 4 | + | ||
| 5 | +**No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string. | ||
added
release-notes/v0.1.3.fr.md +5 -0 | new file mode 100644 | ||
| @@ -0,0 +1,5 @@ | ||
| 1 | +# mini-me v0.1.3 | |
| 2 | + | |
| 3 | +*[English version](v0.1.3.en.md)* | |
| 4 | + | |
| 5 | +**Aucun changement dans mm lui-même.** Une correction d'une ligne dans le programme d'exemple de `demo/` : il nomme maintenant son modèle par une simple chaîne. | |
| new file mode 100644 | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | +# mini-me v0.1.3 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.3.en.md)* | ||
| 4 | + | ||
| 5 | +**Aucun changement dans mm lui-même.** Une correction d'une ligne dans le programme d'exemple de `demo/` : il nomme maintenant son modèle par une simple chaîne. | ||
added
release-notes/v0.1.4.en.md +11 -0 | new file mode 100644 | ||
| @@ -0,0 +1,11 @@ | ||
| 1 | +# mini-me v0.1.4 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.4.fr.md)* | |
| 4 | + | |
| 5 | +MCP servers in a Docker sandbox, and a new authentication type. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **The sbx MCP gateway is picked up automatically**: when `MCP_GATEWAY_URL` is set, mm adds it as an MCP server named `sbx-gateway`, next to the configured servers. | |
| 10 | +- **MCP authentication type `oauth2`**: the token is read from an environment variable (already obtained). New keys: `oauthClientId`, `oauthClientSecretEnv` and `oauthScopes`. | |
| 11 | +- **GitHub MCP demo configurations**: `demo/agent.dmr.github-oauth.yaml` and `demo/agent.dmr.sbx.yaml`, with `demo/MCP-GITHUB.md`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | +# mini-me v0.1.4 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.4.fr.md)* | ||
| 4 | + | ||
| 5 | +MCP servers in a Docker sandbox, and a new authentication type. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **The sbx MCP gateway is picked up automatically**: when `MCP_GATEWAY_URL` is set, mm adds it as an MCP server named `sbx-gateway`, next to the configured servers. | ||
| 10 | +- **MCP authentication type `oauth2`**: the token is read from an environment variable (already obtained). New keys: `oauthClientId`, `oauthClientSecretEnv` and `oauthScopes`. | ||
| 11 | +- **GitHub MCP demo configurations**: `demo/agent.dmr.github-oauth.yaml` and `demo/agent.dmr.sbx.yaml`, with `demo/MCP-GITHUB.md`. | ||
added
release-notes/v0.1.4.fr.md +11 -0 | new file mode 100644 | ||
| @@ -0,0 +1,11 @@ | ||
| 1 | +# mini-me v0.1.4 | |
| 2 | + | |
| 3 | +*[English version](v0.1.4.en.md)* | |
| 4 | + | |
| 5 | +Des serveurs MCP dans un Docker sandbox, et un nouveau type d'authentification. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **La passerelle MCP de sbx est reprise automatiquement** : quand `MCP_GATEWAY_URL` est définie, mm l'ajoute comme serveur MCP nommé `sbx-gateway`, à côté des serveurs configurés. | |
| 10 | +- **Type d'authentification MCP `oauth2`** : le jeton est lu dans une variable d'environnement (déjà obtenu). Nouvelles clés : `oauthClientId`, `oauthClientSecretEnv` et `oauthScopes`. | |
| 11 | +- **Configurations de démo pour le MCP GitHub** : `demo/agent.dmr.github-oauth.yaml` et `demo/agent.dmr.sbx.yaml`, avec `demo/MCP-GITHUB.md`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | +# mini-me v0.1.4 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.4.en.md)* | ||
| 4 | + | ||
| 5 | +Des serveurs MCP dans un Docker sandbox, et un nouveau type d'authentification. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **La passerelle MCP de sbx est reprise automatiquement** : quand `MCP_GATEWAY_URL` est définie, mm l'ajoute comme serveur MCP nommé `sbx-gateway`, à côté des serveurs configurés. | ||
| 10 | +- **Type d'authentification MCP `oauth2`** : le jeton est lu dans une variable d'environnement (déjà obtenu). Nouvelles clés : `oauthClientId`, `oauthClientSecretEnv` et `oauthScopes`. | ||
| 11 | +- **Configurations de démo pour le MCP GitHub** : `demo/agent.dmr.github-oauth.yaml` et `demo/agent.dmr.sbx.yaml`, avec `demo/MCP-GITHUB.md`. | ||
added
release-notes/v0.1.5.en.md +7 -0 | new file mode 100644 | ||
| @@ -0,0 +1,7 @@ | ||
| 1 | +# mini-me v0.1.5 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.5.fr.md)* | |
| 4 | + | |
| 5 | +## Fixed | |
| 6 | + | |
| 7 | +- **sbx MCP gateway**: mm now reads the token from `MCP_SENTINEL_TOKEN_NAME`, the variable sbx actually injects, instead of `MCP_GATEWAY_TOKEN`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | +# mini-me v0.1.5 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.5.fr.md)* | ||
| 4 | + | ||
| 5 | +## Fixed | ||
| 6 | + | ||
| 7 | +- **sbx MCP gateway**: mm now reads the token from `MCP_SENTINEL_TOKEN_NAME`, the variable sbx actually injects, instead of `MCP_GATEWAY_TOKEN`. | ||
added
release-notes/v0.1.5.fr.md +7 -0 | new file mode 100644 | ||
| @@ -0,0 +1,7 @@ | ||
| 1 | +# mini-me v0.1.5 | |
| 2 | + | |
| 3 | +*[English version](v0.1.5.en.md)* | |
| 4 | + | |
| 5 | +## Corrigé | |
| 6 | + | |
| 7 | +- **Passerelle MCP de sbx** : mm lit maintenant le jeton dans `MCP_SENTINEL_TOKEN_NAME`, la variable que sbx injecte vraiment, au lieu de `MCP_GATEWAY_TOKEN`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | +# mini-me v0.1.5 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.5.en.md)* | ||
| 4 | + | ||
| 5 | +## Corrigé | ||
| 6 | + | ||
| 7 | +- **Passerelle MCP de sbx** : mm lit maintenant le jeton dans `MCP_SENTINEL_TOKEN_NAME`, la variable que sbx injecte vraiment, au lieu de `MCP_GATEWAY_TOKEN`. | ||
added
release-notes/v0.1.6.en.md +14 -0 | new file mode 100644 | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +# mini-me v0.1.6 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.6.fr.md)* | |
| 4 | + | |
| 5 | +MCP servers now work through the sbx gateway. | |
| 6 | + | |
| 7 | +## Fixed | |
| 8 | + | |
| 9 | +- **The MCP HTTP client opens a real MCP session.** It sends `initialize` first and reuses `Mcp-Session-Id`. When the session is lost (404), it re-initialises once. It closes the session with `DELETE`, and reports HTTP errors with their status. | |
| 10 | + - Before this fix, a stateful server such as the sbx gateway answered every call with `method "tools/list" is invalid during session initialization`. | |
| 11 | + | |
| 12 | +## Docs | |
| 13 | + | |
| 14 | +- **New section** in the sandbox how-to guide (EN + FR): "MCP servers through the sbx gateway". | |
| new file mode 100644 | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | +# mini-me v0.1.6 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.6.fr.md)* | ||
| 4 | + | ||
| 5 | +MCP servers now work through the sbx gateway. | ||
| 6 | + | ||
| 7 | +## Fixed | ||
| 8 | + | ||
| 9 | +- **The MCP HTTP client opens a real MCP session.** It sends `initialize` first and reuses `Mcp-Session-Id`. When the session is lost (404), it re-initialises once. It closes the session with `DELETE`, and reports HTTP errors with their status. | ||
| 10 | + - Before this fix, a stateful server such as the sbx gateway answered every call with `method "tools/list" is invalid during session initialization`. | ||
| 11 | + | ||
| 12 | +## Docs | ||
| 13 | + | ||
| 14 | +- **New section** in the sandbox how-to guide (EN + FR): "MCP servers through the sbx gateway". | ||
added
release-notes/v0.1.6.fr.md +14 -0 | new file mode 100644 | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +# mini-me v0.1.6 | |
| 2 | + | |
| 3 | +*[English version](v0.1.6.en.md)* | |
| 4 | + | |
| 5 | +Les serveurs MCP fonctionnent maintenant à travers la passerelle de sbx. | |
| 6 | + | |
| 7 | +## Corrigé | |
| 8 | + | |
| 9 | +- **Le client MCP HTTP ouvre une vraie session MCP.** Il envoie d'abord `initialize` et réutilise `Mcp-Session-Id`. Quand la session est perdue (404), il se réinitialise une fois. Il ferme la session avec `DELETE`, et remonte les erreurs HTTP avec leur statut. | |
| 10 | + - Avant ce correctif, un serveur à état comme la passerelle de sbx répondait à chaque appel `method "tools/list" is invalid during session initialization`. | |
| 11 | + | |
| 12 | +## Documentation | |
| 13 | + | |
| 14 | +- **Nouvelle section** dans le guide pratique du sandbox (EN + FR) : « Serveurs MCP via la passerelle sbx ». | |
| new file mode 100644 | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | +# mini-me v0.1.6 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.6.en.md)* | ||
| 4 | + | ||
| 5 | +Les serveurs MCP fonctionnent maintenant à travers la passerelle de sbx. | ||
| 6 | + | ||
| 7 | +## Corrigé | ||
| 8 | + | ||
| 9 | +- **Le client MCP HTTP ouvre une vraie session MCP.** Il envoie d'abord `initialize` et réutilise `Mcp-Session-Id`. Quand la session est perdue (404), il se réinitialise une fois. Il ferme la session avec `DELETE`, et remonte les erreurs HTTP avec leur statut. | ||
| 10 | + - Avant ce correctif, un serveur à état comme la passerelle de sbx répondait à chaque appel `method "tools/list" is invalid during session initialization`. | ||
| 11 | + | ||
| 12 | +## Documentation | ||
| 13 | + | ||
| 14 | +- **Nouvelle section** dans le guide pratique du sandbox (EN + FR) : « Serveurs MCP via la passerelle sbx ». | ||
added
release-notes/v0.1.7.en.md +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +# mini-me v0.1.7 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.7.fr.md)* | |
| 4 | + | |
| 5 | +You can choose the name an editor sees. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`acp:` block** with `name` and `title`: how `mm -acp` introduces itself to the editor. The defaults are the former values (`bob`, `Bob (bash-first agent)`). | |
| 10 | + | |
| 11 | +## Changed | |
| 12 | + | |
| 13 | +- **Over ACP, mm now reports its own version** (`mm --version`), instead of the fixed `0.11.0`. An editor's log and a bug report now name the same build. | |
| 14 | + | |
| 15 | +## Docs | |
| 16 | + | |
| 17 | +- **Editor how-to guide** (EN + FR): changing the name, and a one-liner that shows what `initialize` answers. | |
| 18 | +- **The GitHub-MCP-in-sbx procedure**: `demo/MCP-GITHUB-SBX.md` (FR) and `.en.md`, plus the script `docker-sbx/mcp/github-mcp.sh`. The script runs the official GitHub server, read-only by default, and takes the token from the environment, the macOS keychain or `gh`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +# mini-me v0.1.7 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.7.fr.md)* | ||
| 4 | + | ||
| 5 | +You can choose the name an editor sees. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`acp:` block** with `name` and `title`: how `mm -acp` introduces itself to the editor. The defaults are the former values (`bob`, `Bob (bash-first agent)`). | ||
| 10 | + | ||
| 11 | +## Changed | ||
| 12 | + | ||
| 13 | +- **Over ACP, mm now reports its own version** (`mm --version`), instead of the fixed `0.11.0`. An editor's log and a bug report now name the same build. | ||
| 14 | + | ||
| 15 | +## Docs | ||
| 16 | + | ||
| 17 | +- **Editor how-to guide** (EN + FR): changing the name, and a one-liner that shows what `initialize` answers. | ||
| 18 | +- **The GitHub-MCP-in-sbx procedure**: `demo/MCP-GITHUB-SBX.md` (FR) and `.en.md`, plus the script `docker-sbx/mcp/github-mcp.sh`. The script runs the official GitHub server, read-only by default, and takes the token from the environment, the macOS keychain or `gh`. | ||
added
release-notes/v0.1.7.fr.md +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +# mini-me v0.1.7 | |
| 2 | + | |
| 3 | +*[English version](v0.1.7.en.md)* | |
| 4 | + | |
| 5 | +On peut choisir le nom que voit un éditeur. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **Bloc `acp:`** avec `name` et `title` : la façon dont `mm -acp` se présente à l'éditeur. Les valeurs par défaut sont les anciennes (`bob`, `Bob (bash-first agent)`). | |
| 10 | + | |
| 11 | +## Changements | |
| 12 | + | |
| 13 | +- **En ACP, mm annonce maintenant sa propre version** (`mm --version`), au lieu du `0.11.0` figé. Le journal de l'éditeur et un rapport de bug nomment maintenant le même binaire. | |
| 14 | + | |
| 15 | +## Documentation | |
| 16 | + | |
| 17 | +- **Guide pratique de l'éditeur** (EN + FR) : changer le nom, et une commande d'une ligne qui montre ce que répond `initialize`. | |
| 18 | +- **La procédure MCP GitHub dans sbx** : `demo/MCP-GITHUB-SBX.md` (FR) et `.en.md`, plus le script `docker-sbx/mcp/github-mcp.sh`. Le script lance le serveur GitHub officiel, en lecture seule par défaut, et prend le jeton dans l'environnement, le trousseau macOS ou `gh`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +# mini-me v0.1.7 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.7.en.md)* | ||
| 4 | + | ||
| 5 | +On peut choisir le nom que voit un éditeur. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **Bloc `acp:`** avec `name` et `title` : la façon dont `mm -acp` se présente à l'éditeur. Les valeurs par défaut sont les anciennes (`bob`, `Bob (bash-first agent)`). | ||
| 10 | + | ||
| 11 | +## Changements | ||
| 12 | + | ||
| 13 | +- **En ACP, mm annonce maintenant sa propre version** (`mm --version`), au lieu du `0.11.0` figé. Le journal de l'éditeur et un rapport de bug nomment maintenant le même binaire. | ||
| 14 | + | ||
| 15 | +## Documentation | ||
| 16 | + | ||
| 17 | +- **Guide pratique de l'éditeur** (EN + FR) : changer le nom, et une commande d'une ligne qui montre ce que répond `initialize`. | ||
| 18 | +- **La procédure MCP GitHub dans sbx** : `demo/MCP-GITHUB-SBX.md` (FR) et `.en.md`, plus le script `docker-sbx/mcp/github-mcp.sh`. Le script lance le serveur GitHub officiel, en lecture seule par défaut, et prend le jeton dans l'environnement, le trousseau macOS ou `gh`. | ||
added
release-notes/v0.1.8.en.md +14 -0 | new file mode 100644 | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +# mini-me v0.1.8 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.8.fr.md)* | |
| 4 | + | |
| 5 | +Token usage in editors, and tool calls cut off by `max_tokens`. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **ACP `usage_update`**: after each model call, mm sends the tokens used and the context window. Each prompt response carries `usage` totals. | |
| 10 | + - In editor mode, mm asks the server for the context window again before each turn, as long as it is unknown. | |
| 11 | + | |
| 12 | +## Fixed | |
| 13 | + | |
| 14 | +- **Truncated tool-call arguments.** When `max_tokens` cuts the arguments of a call (a long `write_file`, for example), the turn no longer ends on a parse error. The call is handed back to the model twice with a note, then the turn ends with a one-line explanation. | |
| new file mode 100644 | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | +# mini-me v0.1.8 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.8.fr.md)* | ||
| 4 | + | ||
| 5 | +Token usage in editors, and tool calls cut off by `max_tokens`. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **ACP `usage_update`**: after each model call, mm sends the tokens used and the context window. Each prompt response carries `usage` totals. | ||
| 10 | + - In editor mode, mm asks the server for the context window again before each turn, as long as it is unknown. | ||
| 11 | + | ||
| 12 | +## Fixed | ||
| 13 | + | ||
| 14 | +- **Truncated tool-call arguments.** When `max_tokens` cuts the arguments of a call (a long `write_file`, for example), the turn no longer ends on a parse error. The call is handed back to the model twice with a note, then the turn ends with a one-line explanation. | ||
added
release-notes/v0.1.8.fr.md +14 -0 | new file mode 100644 | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +# mini-me v0.1.8 | |
| 2 | + | |
| 3 | +*[English version](v0.1.8.en.md)* | |
| 4 | + | |
| 5 | +La consommation de tokens dans les éditeurs, et les appels d'outil coupés par `max_tokens`. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **`usage_update` en ACP** : après chaque appel au modèle, mm envoie les tokens utilisés et la fenêtre de contexte. Chaque réponse à un prompt porte les totaux `usage`. | |
| 10 | + - En mode éditeur, mm redemande la fenêtre de contexte au serveur avant chaque tour, tant qu'elle est inconnue. | |
| 11 | + | |
| 12 | +## Corrigé | |
| 13 | + | |
| 14 | +- **Arguments d'appel d'outil tronqués.** Quand `max_tokens` coupe les arguments d'un appel (un long `write_file`, par exemple), le tour ne se termine plus sur une erreur d'analyse. L'appel est rendu deux fois au modèle avec une note, puis le tour s'arrête avec une explication d'une ligne. | |
| new file mode 100644 | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | +# mini-me v0.1.8 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.8.en.md)* | ||
| 4 | + | ||
| 5 | +La consommation de tokens dans les éditeurs, et les appels d'outil coupés par `max_tokens`. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **`usage_update` en ACP** : après chaque appel au modèle, mm envoie les tokens utilisés et la fenêtre de contexte. Chaque réponse à un prompt porte les totaux `usage`. | ||
| 10 | + - En mode éditeur, mm redemande la fenêtre de contexte au serveur avant chaque tour, tant qu'elle est inconnue. | ||
| 11 | + | ||
| 12 | +## Corrigé | ||
| 13 | + | ||
| 14 | +- **Arguments d'appel d'outil tronqués.** Quand `max_tokens` coupe les arguments d'un appel (un long `write_file`, par exemple), le tour ne se termine plus sur une erreur d'analyse. L'appel est rendu deux fois au modèle avec une note, puis le tour s'arrête avec une explication d'une ligne. | ||
added
release-notes/v0.1.9.en.md +7 -0 | new file mode 100644 | ||
| @@ -0,0 +1,7 @@ | ||
| 1 | +# mini-me v0.1.9 | |
| 2 | + | |
| 3 | +*[Version française](v0.1.9.fr.md)* | |
| 4 | + | |
| 5 | +## Fixed | |
| 6 | + | |
| 7 | +- **A stream that breaks partway through.** When llama.cpp sends an SSE `error:` event, which openai-go turned into `unexpected end of JSON input`, mm keeps the server's message and retries the model call once. If it fails again, the explanation quotes the server; a cut connection is reported as such. | |
| new file mode 100644 | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | +# mini-me v0.1.9 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.1.9.fr.md)* | ||
| 4 | + | ||
| 5 | +## Fixed | ||
| 6 | + | ||
| 7 | +- **A stream that breaks partway through.** When llama.cpp sends an SSE `error:` event, which openai-go turned into `unexpected end of JSON input`, mm keeps the server's message and retries the model call once. If it fails again, the explanation quotes the server; a cut connection is reported as such. | ||
added
release-notes/v0.1.9.fr.md +7 -0 | new file mode 100644 | ||
| @@ -0,0 +1,7 @@ | ||
| 1 | +# mini-me v0.1.9 | |
| 2 | + | |
| 3 | +*[English version](v0.1.9.en.md)* | |
| 4 | + | |
| 5 | +## Corrigé | |
| 6 | + | |
| 7 | +- **Un flux qui se coupe en cours de route.** Quand llama.cpp envoie un événement SSE `error:`, qu'openai-go transformait en `unexpected end of JSON input`, mm garde le message du serveur et refait l'appel au modèle une fois. S'il échoue encore, l'explication cite le serveur ; une connexion coupée est signalée comme telle. | |
| new file mode 100644 | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | +# mini-me v0.1.9 | ||
| 2 | + | ||
| 3 | +*[English version](v0.1.9.en.md)* | ||
| 4 | + | ||
| 5 | +## Corrigé | ||
| 6 | + | ||
| 7 | +- **Un flux qui se coupe en cours de route.** Quand llama.cpp envoie un événement SSE `error:`, qu'openai-go transformait en `unexpected end of JSON input`, mm garde le message du serveur et refait l'appel au modèle une fois. S'il échoue encore, l'explication cite le serveur ; une connexion coupée est signalée comme telle. | ||
added
release-notes/v0.2.0.en.md +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +# mini-me v0.2.0 | |
| 2 | + | |
| 3 | +*[Version française](v0.2.0.fr.md)* | |
| 4 | + | |
| 5 | +A long prompt is no longer taken for a hang. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`firstTokenTimeout`** (default `3m`): the silence tolerated before a model call's first token, while the server reads the prompt. It is never shorter than `watchdogTimeout`. | |
| 10 | +- **Prompt progress keeps the turn alive**: on llama.cpp-style servers, mm asks for `return_progress`, and each `prompt_progress` counts as a sign of life. | |
| 11 | + | |
| 12 | +## Fixed | |
| 13 | + | |
| 14 | +- **Long prompts**: a turn with a long prompt (about 25k tokens) was killed as "stalled" by the 30 s watchdog while the server was still reading it. | |
| 15 | + | |
| 16 | +## Upgrading | |
| 17 | + | |
| 18 | +- **The old advice to raise `watchdogTimeout` for long prompts is gone** from the shipped YAML files: `firstTokenTimeout` now covers that wait. | |
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +# mini-me v0.2.0 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.2.0.fr.md)* | ||
| 4 | + | ||
| 5 | +A long prompt is no longer taken for a hang. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`firstTokenTimeout`** (default `3m`): the silence tolerated before a model call's first token, while the server reads the prompt. It is never shorter than `watchdogTimeout`. | ||
| 10 | +- **Prompt progress keeps the turn alive**: on llama.cpp-style servers, mm asks for `return_progress`, and each `prompt_progress` counts as a sign of life. | ||
| 11 | + | ||
| 12 | +## Fixed | ||
| 13 | + | ||
| 14 | +- **Long prompts**: a turn with a long prompt (about 25k tokens) was killed as "stalled" by the 30 s watchdog while the server was still reading it. | ||
| 15 | + | ||
| 16 | +## Upgrading | ||
| 17 | + | ||
| 18 | +- **The old advice to raise `watchdogTimeout` for long prompts is gone** from the shipped YAML files: `firstTokenTimeout` now covers that wait. | ||
added
release-notes/v0.2.0.fr.md +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +# mini-me v0.2.0 | |
| 2 | + | |
| 3 | +*[English version](v0.2.0.en.md)* | |
| 4 | + | |
| 5 | +Un long prompt n'est plus pris pour un blocage. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **`firstTokenTimeout`** (`3m` par défaut) : le silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt. Il n'est jamais plus court que `watchdogTimeout`. | |
| 10 | +- **La progression du prompt garde le tour en vie** : sur les serveurs de type llama.cpp, mm demande `return_progress`, et chaque `prompt_progress` compte comme un signe de vie. | |
| 11 | + | |
| 12 | +## Corrigé | |
| 13 | + | |
| 14 | +- **Longs prompts** : un tour avec un long prompt (environ 25k tokens) était tué comme « bloqué » par le chien de garde de 30 s pendant que le serveur le lisait encore. | |
| 15 | + | |
| 16 | +## Mise à jour | |
| 17 | + | |
| 18 | +- **L'ancien conseil d'augmenter `watchdogTimeout` pour les longs prompts disparaît** des fichiers YAML livrés : `firstTokenTimeout` couvre maintenant cette attente. | |
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +# mini-me v0.2.0 | ||
| 2 | + | ||
| 3 | +*[English version](v0.2.0.en.md)* | ||
| 4 | + | ||
| 5 | +Un long prompt n'est plus pris pour un blocage. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **`firstTokenTimeout`** (`3m` par défaut) : le silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt. Il n'est jamais plus court que `watchdogTimeout`. | ||
| 10 | +- **La progression du prompt garde le tour en vie** : sur les serveurs de type llama.cpp, mm demande `return_progress`, et chaque `prompt_progress` compte comme un signe de vie. | ||
| 11 | + | ||
| 12 | +## Corrigé | ||
| 13 | + | ||
| 14 | +- **Longs prompts** : un tour avec un long prompt (environ 25k tokens) était tué comme « bloqué » par le chien de garde de 30 s pendant que le serveur le lisait encore. | ||
| 15 | + | ||
| 16 | +## Mise à jour | ||
| 17 | + | ||
| 18 | +- **L'ancien conseil d'augmenter `watchdogTimeout` pour les longs prompts disparaît** des fichiers YAML livrés : `firstTokenTimeout` couvre maintenant cette attente. | ||
added
release-notes/v0.2.1.en.md +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# mini-me v0.2.1 | |
| 2 | + | |
| 3 | +*[Version française](v0.2.1.fr.md)* | |
| 4 | + | |
| 5 | +Compression that works with thinking models. | |
| 6 | + | |
| 7 | +## Fixed | |
| 8 | + | |
| 9 | +- **Empty compression summaries with a thinking model**: the reasoning used up the whole `summaryMaxTokens`. The summary request now turns reasoning off on `dmr` and `llamacpp`. When a summary still comes back empty, the error says why. | |
| 10 | + | |
| 11 | +## New | |
| 12 | + | |
| 13 | +- **Session files** now record the `provider` and the `model`, and each recorded error names its model. | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# mini-me v0.2.1 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.2.1.fr.md)* | ||
| 4 | + | ||
| 5 | +Compression that works with thinking models. | ||
| 6 | + | ||
| 7 | +## Fixed | ||
| 8 | + | ||
| 9 | +- **Empty compression summaries with a thinking model**: the reasoning used up the whole `summaryMaxTokens`. The summary request now turns reasoning off on `dmr` and `llamacpp`. When a summary still comes back empty, the error says why. | ||
| 10 | + | ||
| 11 | +## New | ||
| 12 | + | ||
| 13 | +- **Session files** now record the `provider` and the `model`, and each recorded error names its model. | ||
added
release-notes/v0.2.1.fr.md +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# mini-me v0.2.1 | |
| 2 | + | |
| 3 | +*[English version](v0.2.1.en.md)* | |
| 4 | + | |
| 5 | +Une compression qui marche avec les modèles qui réfléchissent. | |
| 6 | + | |
| 7 | +## Corrigé | |
| 8 | + | |
| 9 | +- **Résumés de compression vides avec un modèle qui réfléchit** : le raisonnement consommait tout `summaryMaxTokens`. La demande de résumé coupe maintenant le raisonnement sur `dmr` et `llamacpp`. Quand un résumé revient quand même vide, l'erreur dit pourquoi. | |
| 10 | + | |
| 11 | +## Nouveautés | |
| 12 | + | |
| 13 | +- **Les fichiers de session** enregistrent maintenant le `provider` et le `model`, et chaque erreur enregistrée nomme son modèle. | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# mini-me v0.2.1 | ||
| 2 | + | ||
| 3 | +*[English version](v0.2.1.en.md)* | ||
| 4 | + | ||
| 5 | +Une compression qui marche avec les modèles qui réfléchissent. | ||
| 6 | + | ||
| 7 | +## Corrigé | ||
| 8 | + | ||
| 9 | +- **Résumés de compression vides avec un modèle qui réfléchit** : le raisonnement consommait tout `summaryMaxTokens`. La demande de résumé coupe maintenant le raisonnement sur `dmr` et `llamacpp`. Quand un résumé revient quand même vide, l'erreur dit pourquoi. | ||
| 10 | + | ||
| 11 | +## Nouveautés | ||
| 12 | + | ||
| 13 | +- **Les fichiers de session** enregistrent maintenant le `provider` et le `model`, et chaque erreur enregistrée nomme son modèle. | ||
added
release-notes/v0.2.2.en.md +11 -0 | new file mode 100644 | ||
| @@ -0,0 +1,11 @@ | ||
| 1 | +# mini-me v0.2.2 | |
| 2 | + | |
| 3 | +*[Version française](v0.2.2.fr.md)* | |
| 4 | + | |
| 5 | +**No change to mm's code.** Sample configurations and Docker Sandbox kits v3. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`config.samples/`**: five configurations tuned for one model each (Gemma, gpt-oss, Qwen3.6 on DMR; Ornith and Qwen3.8 on llama.cpp). | |
| 10 | +- **`docker-sbx-v3/`**: the two kits (`mm-tui`, `mm-acp`) in the sandbox-kit v3 format, with build, check (`kit-tck`) and publish scripts. `docker-sbx/` (v2) is unchanged. | |
| 11 | +- **The kit directories are no longer ignored by git.** | |
| new file mode 100644 | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | +# mini-me v0.2.2 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.2.2.fr.md)* | ||
| 4 | + | ||
| 5 | +**No change to mm's code.** Sample configurations and Docker Sandbox kits v3. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`config.samples/`**: five configurations tuned for one model each (Gemma, gpt-oss, Qwen3.6 on DMR; Ornith and Qwen3.8 on llama.cpp). | ||
| 10 | +- **`docker-sbx-v3/`**: the two kits (`mm-tui`, `mm-acp`) in the sandbox-kit v3 format, with build, check (`kit-tck`) and publish scripts. `docker-sbx/` (v2) is unchanged. | ||
| 11 | +- **The kit directories are no longer ignored by git.** | ||
added
release-notes/v0.2.2.fr.md +11 -0 | new file mode 100644 | ||
| @@ -0,0 +1,11 @@ | ||
| 1 | +# mini-me v0.2.2 | |
| 2 | + | |
| 3 | +*[English version](v0.2.2.en.md)* | |
| 4 | + | |
| 5 | +**Aucun changement dans le code de mm.** Des exemples de configuration et les kits Docker Sandbox v3. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **`config.samples/`** : cinq configurations réglées chacune pour un modèle (Gemma, gpt-oss, Qwen3.6 sur DMR ; Ornith et Qwen3.8 sur llama.cpp). | |
| 10 | +- **`docker-sbx-v3/`** : les deux kits (`mm-tui`, `mm-acp`) au format sandbox-kit v3, avec des scripts de construction, de vérification (`kit-tck`) et de publication. `docker-sbx/` (v2) ne change pas. | |
| 11 | +- **Les répertoires des kits ne sont plus ignorés par git.** | |
| new file mode 100644 | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | +# mini-me v0.2.2 | ||
| 2 | + | ||
| 3 | +*[English version](v0.2.2.en.md)* | ||
| 4 | + | ||
| 5 | +**Aucun changement dans le code de mm.** Des exemples de configuration et les kits Docker Sandbox v3. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **`config.samples/`** : cinq configurations réglées chacune pour un modèle (Gemma, gpt-oss, Qwen3.6 sur DMR ; Ornith et Qwen3.8 sur llama.cpp). | ||
| 10 | +- **`docker-sbx-v3/`** : les deux kits (`mm-tui`, `mm-acp`) au format sandbox-kit v3, avec des scripts de construction, de vérification (`kit-tck`) et de publication. `docker-sbx/` (v2) ne change pas. | ||
| 11 | +- **Les répertoires des kits ne sont plus ignorés par git.** | ||
added
release-notes/v0.2.3.en.md +17 -0 | new file mode 100644 | ||
| @@ -0,0 +1,17 @@ | ||
| 1 | +# mini-me v0.2.3 | |
| 2 | + | |
| 3 | +*[Version française](v0.2.3.fr.md)* | |
| 4 | + | |
| 5 | +A built-in tool that reads web pages as Markdown. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`fetch_content`**: fetches a URL and converts the page to Markdown. It is turned on with **`fetchContentTool`**, off by default because it is the only built-in tool that reaches the network. The shipped `agent.dmr.yaml` turns it on. | |
| 10 | + - **`output`** saves the Markdown to a file and returns one line instead of the page, so the page does not fill the context. | |
| 11 | + - **Other options**: `baseUrl`, `tables` (`html` or `flatten`), `strikethrough` and `taskList`. | |
| 12 | + - **Encodings** are detected and the text is converted to UTF-8. | |
| 13 | + - **`robots.txt` is respected.** | |
| 14 | + - **URLs are repaired**: `https//…` and a bare host both work. | |
| 15 | + - **Errors** come back as `[fetch_content: error N (class) — …]`. | |
| 16 | + - **Permission**: asked only when the tool writes a file. | |
| 17 | +- **New how-to guide and explanation** (EN + FR): Fetch a web page. | |
| new file mode 100644 | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | +# mini-me v0.2.3 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.2.3.fr.md)* | ||
| 4 | + | ||
| 5 | +A built-in tool that reads web pages as Markdown. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`fetch_content`**: fetches a URL and converts the page to Markdown. It is turned on with **`fetchContentTool`**, off by default because it is the only built-in tool that reaches the network. The shipped `agent.dmr.yaml` turns it on. | ||
| 10 | + - **`output`** saves the Markdown to a file and returns one line instead of the page, so the page does not fill the context. | ||
| 11 | + - **Other options**: `baseUrl`, `tables` (`html` or `flatten`), `strikethrough` and `taskList`. | ||
| 12 | + - **Encodings** are detected and the text is converted to UTF-8. | ||
| 13 | + - **`robots.txt` is respected.** | ||
| 14 | + - **URLs are repaired**: `https//…` and a bare host both work. | ||
| 15 | + - **Errors** come back as `[fetch_content: error N (class) — …]`. | ||
| 16 | + - **Permission**: asked only when the tool writes a file. | ||
| 17 | +- **New how-to guide and explanation** (EN + FR): Fetch a web page. | ||
added
release-notes/v0.2.3.fr.md +17 -0 | new file mode 100644 | ||
| @@ -0,0 +1,17 @@ | ||
| 1 | +# mini-me v0.2.3 | |
| 2 | + | |
| 3 | +*[English version](v0.2.3.en.md)* | |
| 4 | + | |
| 5 | +Un outil intégré qui lit les pages web en Markdown. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **`fetch_content`** : récupère une URL et convertit la page en Markdown. Il s'active avec **`fetchContentTool`**, désactivé par défaut parce que c'est le seul outil intégré qui va sur le réseau. L'`agent.dmr.yaml` livré l'active. | |
| 10 | + - **`output`** enregistre le Markdown dans un fichier et renvoie une seule ligne au lieu de la page, pour que la page ne remplisse pas le contexte. | |
| 11 | + - **Autres options** : `baseUrl`, `tables` (`html` ou `flatten`), `strikethrough` et `taskList`. | |
| 12 | + - **Les encodages** sont détectés et le texte est converti en UTF-8. | |
| 13 | + - **`robots.txt` est respecté.** | |
| 14 | + - **Les URL sont réparées** : `https//…` et un simple nom d'hôte marchent tous les deux. | |
| 15 | + - **Les erreurs** reviennent sous la forme `[fetch_content: error N (class) — …]`. | |
| 16 | + - **Permission** : demandée seulement quand l'outil écrit un fichier. | |
| 17 | +- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Récupérer une page web. | |
| new file mode 100644 | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | +# mini-me v0.2.3 | ||
| 2 | + | ||
| 3 | +*[English version](v0.2.3.en.md)* | ||
| 4 | + | ||
| 5 | +Un outil intégré qui lit les pages web en Markdown. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **`fetch_content`** : récupère une URL et convertit la page en Markdown. Il s'active avec **`fetchContentTool`**, désactivé par défaut parce que c'est le seul outil intégré qui va sur le réseau. L'`agent.dmr.yaml` livré l'active. | ||
| 10 | + - **`output`** enregistre le Markdown dans un fichier et renvoie une seule ligne au lieu de la page, pour que la page ne remplisse pas le contexte. | ||
| 11 | + - **Autres options** : `baseUrl`, `tables` (`html` ou `flatten`), `strikethrough` et `taskList`. | ||
| 12 | + - **Les encodages** sont détectés et le texte est converti en UTF-8. | ||
| 13 | + - **`robots.txt` est respecté.** | ||
| 14 | + - **Les URL sont réparées** : `https//…` et un simple nom d'hôte marchent tous les deux. | ||
| 15 | + - **Les erreurs** reviennent sous la forme `[fetch_content: error N (class) — …]`. | ||
| 16 | + - **Permission** : demandée seulement quand l'outil écrit un fichier. | ||
| 17 | +- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Récupérer une page web. | ||
added
release-notes/v0.2.4.en.md +15 -0 | new file mode 100644 | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +# mini-me v0.2.4 | |
| 2 | + | |
| 3 | +*[Version française](v0.2.4.fr.md)* | |
| 4 | + | |
| 5 | +The server's prompt cache works again from one question to the next. | |
| 6 | + | |
| 7 | +## Fixed | |
| 8 | + | |
| 9 | +- **The tool order no longer changes on every question** (#53): Genkit built the tool list from a map, so the order changed at each question. mm now sorts the tools by name before every model call. | |
| 10 | + - On llama.cpp, from the second question onwards, about 3,400–3,900 tokens re-read in 7–9 s became about 20 tokens in 250 ms (measured on Mellum 12B). | |
| 11 | + | |
| 12 | +## Docs | |
| 13 | + | |
| 14 | +- **New how-to guide** (EN + FR): Verify the tool order. The scripts are in `tests/tool-order/`. | |
| 15 | +- **`config.samples/`**: the configurations turn on `fetchContentTool`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# mini-me v0.2.4 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.2.4.fr.md)* | ||
| 4 | + | ||
| 5 | +The server's prompt cache works again from one question to the next. | ||
| 6 | + | ||
| 7 | +## Fixed | ||
| 8 | + | ||
| 9 | +- **The tool order no longer changes on every question** (#53): Genkit built the tool list from a map, so the order changed at each question. mm now sorts the tools by name before every model call. | ||
| 10 | + - On llama.cpp, from the second question onwards, about 3,400–3,900 tokens re-read in 7–9 s became about 20 tokens in 250 ms (measured on Mellum 12B). | ||
| 11 | + | ||
| 12 | +## Docs | ||
| 13 | + | ||
| 14 | +- **New how-to guide** (EN + FR): Verify the tool order. The scripts are in `tests/tool-order/`. | ||
| 15 | +- **`config.samples/`**: the configurations turn on `fetchContentTool`. | ||
added
release-notes/v0.2.4.fr.md +15 -0 | new file mode 100644 | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +# mini-me v0.2.4 | |
| 2 | + | |
| 3 | +*[English version](v0.2.4.en.md)* | |
| 4 | + | |
| 5 | +Le cache de prompt du serveur fonctionne de nouveau d'une question à l'autre. | |
| 6 | + | |
| 7 | +## Corrigé | |
| 8 | + | |
| 9 | +- **L'ordre des outils ne change plus à chaque question** (#53) : Genkit construisait la liste des outils à partir d'une map, donc l'ordre changeait à chaque question. mm trie maintenant les outils par nom avant chaque appel au modèle. | |
| 10 | + - Sur llama.cpp, à partir de la deuxième question, environ 3 400 à 3 900 tokens relus en 7 à 9 s sont devenus environ 20 tokens en 250 ms (mesuré sur Mellum 12B). | |
| 11 | + | |
| 12 | +## Documentation | |
| 13 | + | |
| 14 | +- **Nouveau guide pratique** (EN + FR) : Vérifier l'ordre des outils. Les scripts sont dans `tests/tool-order/`. | |
| 15 | +- **`config.samples/`** : les configurations activent `fetchContentTool`. | |
| new file mode 100644 | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# mini-me v0.2.4 | ||
| 2 | + | ||
| 3 | +*[English version](v0.2.4.en.md)* | ||
| 4 | + | ||
| 5 | +Le cache de prompt du serveur fonctionne de nouveau d'une question à l'autre. | ||
| 6 | + | ||
| 7 | +## Corrigé | ||
| 8 | + | ||
| 9 | +- **L'ordre des outils ne change plus à chaque question** (#53) : Genkit construisait la liste des outils à partir d'une map, donc l'ordre changeait à chaque question. mm trie maintenant les outils par nom avant chaque appel au modèle. | ||
| 10 | + - Sur llama.cpp, à partir de la deuxième question, environ 3 400 à 3 900 tokens relus en 7 à 9 s sont devenus environ 20 tokens en 250 ms (mesuré sur Mellum 12B). | ||
| 11 | + | ||
| 12 | +## Documentation | ||
| 13 | + | ||
| 14 | +- **Nouveau guide pratique** (EN + FR) : Vérifier l'ordre des outils. Les scripts sont dans `tests/tool-order/`. | ||
| 15 | +- **`config.samples/`** : les configurations activent `fetchContentTool`. | ||
added
release-notes/v0.2.5.en.md +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# mini-me v0.2.5 | |
| 2 | + | |
| 3 | +*[Version française](v0.2.5.fr.md)* | |
| 4 | + | |
| 5 | +Two new model servers: llmman and Ollama. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`llmman` provider** (#60). The context window comes from `/api/ps` (the window actually served). Prompt progress keeps the turn alive while the server reads the prompt. | |
| 10 | +- **`ollama` provider** (#62). | |
| 11 | +- **Reasoning off in compression summaries** is set per provider: Ollama ignores `chat_template_kwargs`, so it gets its own setting. | |
| 12 | +- **Demos**: `demo-llmman/` (Qwen3.5 4B) and `demo-ollama/` (qwen3.5:2b). | |
| 13 | +- **New how-to guides** (EN + FR): Run with llmman, Run with Ollama. | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# mini-me v0.2.5 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.2.5.fr.md)* | ||
| 4 | + | ||
| 5 | +Two new model servers: llmman and Ollama. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`llmman` provider** (#60). The context window comes from `/api/ps` (the window actually served). Prompt progress keeps the turn alive while the server reads the prompt. | ||
| 10 | +- **`ollama` provider** (#62). | ||
| 11 | +- **Reasoning off in compression summaries** is set per provider: Ollama ignores `chat_template_kwargs`, so it gets its own setting. | ||
| 12 | +- **Demos**: `demo-llmman/` (Qwen3.5 4B) and `demo-ollama/` (qwen3.5:2b). | ||
| 13 | +- **New how-to guides** (EN + FR): Run with llmman, Run with Ollama. | ||
added
release-notes/v0.2.5.fr.md +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# mini-me v0.2.5 | |
| 2 | + | |
| 3 | +*[English version](v0.2.5.en.md)* | |
| 4 | + | |
| 5 | +Deux nouveaux serveurs de modèles : llmman et Ollama. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **Fournisseur `llmman`** (#60). La fenêtre de contexte vient de `/api/ps` (la fenêtre réellement servie). La progression du prompt garde le tour en vie pendant que le serveur lit le prompt. | |
| 10 | +- **Fournisseur `ollama`** (#62). | |
| 11 | +- **Raisonnement coupé dans les résumés de compression** : réglé par fournisseur, car Ollama ignore `chat_template_kwargs` et a donc son propre réglage. | |
| 12 | +- **Démos** : `demo-llmman/` (Qwen3.5 4B) et `demo-ollama/` (qwen3.5:2b). | |
| 13 | +- **Nouveaux guides pratiques** (EN + FR) : Lancer avec llmman, Lancer avec Ollama. | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# mini-me v0.2.5 | ||
| 2 | + | ||
| 3 | +*[English version](v0.2.5.en.md)* | ||
| 4 | + | ||
| 5 | +Deux nouveaux serveurs de modèles : llmman et Ollama. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **Fournisseur `llmman`** (#60). La fenêtre de contexte vient de `/api/ps` (la fenêtre réellement servie). La progression du prompt garde le tour en vie pendant que le serveur lit le prompt. | ||
| 10 | +- **Fournisseur `ollama`** (#62). | ||
| 11 | +- **Raisonnement coupé dans les résumés de compression** : réglé par fournisseur, car Ollama ignore `chat_template_kwargs` et a donc son propre réglage. | ||
| 12 | +- **Démos** : `demo-llmman/` (Qwen3.5 4B) et `demo-ollama/` (qwen3.5:2b). | ||
| 13 | +- **Nouveaux guides pratiques** (EN + FR) : Lancer avec llmman, Lancer avec Ollama. | ||
added
release-notes/v0.2.6.en.md +38 -0 | new file mode 100644 | ||
| @@ -0,0 +1,38 @@ | ||
| 1 | +# mini-me v0.2.6 | |
| 2 | + | |
| 3 | +*[Version française](v0.2.6.fr.md)* | |
| 4 | + | |
| 5 | +An agent can ask another mm agent. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +### Asking another agent (#66) | |
| 10 | + | |
| 11 | +- **`mmConfigs`**: a map of agent names to configuration files. | |
| 12 | +- **Commands**: | |
| 13 | + - `/ask-<name> <prompt>` sends the question alone; | |
| 14 | + - `/ask-with-context-<name> <prompt>` sends a transcript of the conversation first; | |
| 15 | + - in the terminal and the window, the generic `/ask <name>` works too. | |
| 16 | +- **How the asked agent runs**: | |
| 17 | + - each ask starts a fresh child `mm -acp`; | |
| 18 | + - its answer is streamed and kept in the history; | |
| 19 | + - its tool calls show as `<name> · <tool>`, and its permission requests go through your front end. | |
| 20 | +- **Limits**: the depth is limited to 2 (`MM_ASK_DEPTH`). Asked agents do not save sessions (`AGENT_SESSIONS=off`). | |
| 21 | + | |
| 22 | +### From a skill or a script (#73) | |
| 23 | + | |
| 24 | +- **`mm --ask <name> -p "…"`** and **`--ask-with-context <name>`**: one question, and only the answer on stdout. | |
| 25 | +- **`MM_BIN` and `MM_CONFIG`** are set for the commands `bash` runs, so a skill can call `"$MM_BIN" --ask …`. | |
| 26 | + | |
| 27 | +### Configuration | |
| 28 | + | |
| 29 | +- **`bashTimeout`** (default `30s`): how long one `bash` command may run. | |
| 30 | + | |
| 31 | +## Demos | |
| 32 | + | |
| 33 | +- **`demo-llmman/`**: two other agents (Jan, Lucy), a reviewer, and the skills `delegate-to-jan`, `second-opinion` and `compare-agents`. | |
| 34 | + | |
| 35 | +## Docs | |
| 36 | + | |
| 37 | +- **New how-to guides** (EN + FR): Ask another agent, Ask another agent from a skill. | |
| 38 | +- **New explanation** (EN + FR): Asking another agent. | |
| new file mode 100644 | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | +# mini-me v0.2.6 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.2.6.fr.md)* | ||
| 4 | + | ||
| 5 | +An agent can ask another mm agent. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +### Asking another agent (#66) | ||
| 10 | + | ||
| 11 | +- **`mmConfigs`**: a map of agent names to configuration files. | ||
| 12 | +- **Commands**: | ||
| 13 | + - `/ask-<name> <prompt>` sends the question alone; | ||
| 14 | + - `/ask-with-context-<name> <prompt>` sends a transcript of the conversation first; | ||
| 15 | + - in the terminal and the window, the generic `/ask <name>` works too. | ||
| 16 | +- **How the asked agent runs**: | ||
| 17 | + - each ask starts a fresh child `mm -acp`; | ||
| 18 | + - its answer is streamed and kept in the history; | ||
| 19 | + - its tool calls show as `<name> · <tool>`, and its permission requests go through your front end. | ||
| 20 | +- **Limits**: the depth is limited to 2 (`MM_ASK_DEPTH`). Asked agents do not save sessions (`AGENT_SESSIONS=off`). | ||
| 21 | + | ||
| 22 | +### From a skill or a script (#73) | ||
| 23 | + | ||
| 24 | +- **`mm --ask <name> -p "…"`** and **`--ask-with-context <name>`**: one question, and only the answer on stdout. | ||
| 25 | +- **`MM_BIN` and `MM_CONFIG`** are set for the commands `bash` runs, so a skill can call `"$MM_BIN" --ask …`. | ||
| 26 | + | ||
| 27 | +### Configuration | ||
| 28 | + | ||
| 29 | +- **`bashTimeout`** (default `30s`): how long one `bash` command may run. | ||
| 30 | + | ||
| 31 | +## Demos | ||
| 32 | + | ||
| 33 | +- **`demo-llmman/`**: two other agents (Jan, Lucy), a reviewer, and the skills `delegate-to-jan`, `second-opinion` and `compare-agents`. | ||
| 34 | + | ||
| 35 | +## Docs | ||
| 36 | + | ||
| 37 | +- **New how-to guides** (EN + FR): Ask another agent, Ask another agent from a skill. | ||
| 38 | +- **New explanation** (EN + FR): Asking another agent. | ||
added
release-notes/v0.2.6.fr.md +38 -0 | new file mode 100644 | ||
| @@ -0,0 +1,38 @@ | ||
| 1 | +# mini-me v0.2.6 | |
| 2 | + | |
| 3 | +*[English version](v0.2.6.en.md)* | |
| 4 | + | |
| 5 | +Un agent peut interroger un autre agent mm. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +### Interroger un autre agent (#66) | |
| 10 | + | |
| 11 | +- **`mmConfigs`** : une table qui associe des noms d'agents à des fichiers de configuration. | |
| 12 | +- **Commandes** : | |
| 13 | + - `/ask-<nom> <prompt>` envoie la question seule ; | |
| 14 | + - `/ask-with-context-<nom> <prompt>` envoie d'abord une transcription de la conversation ; | |
| 15 | + - dans le terminal et la fenêtre, le `/ask <nom>` générique marche aussi. | |
| 16 | +- **Comment tourne l'agent interrogé** : | |
| 17 | + - chaque question lance un nouveau `mm -acp` enfant ; | |
| 18 | + - sa réponse s'affiche en direct et reste dans l'historique ; | |
| 19 | + - ses appels d'outil s'affichent sous la forme `<nom> · <outil>`, et ses demandes de permission passent par votre interface. | |
| 20 | +- **Limites** : la profondeur est limitée à 2 (`MM_ASK_DEPTH`). Les agents interrogés n'enregistrent pas de session (`AGENT_SESSIONS=off`). | |
| 21 | + | |
| 22 | +### Depuis un skill ou un script (#73) | |
| 23 | + | |
| 24 | +- **`mm --ask <nom> -p "…"`** et **`--ask-with-context <nom>`** : une question, et seulement la réponse sur stdout. | |
| 25 | +- **`MM_BIN` et `MM_CONFIG`** sont définies pour les commandes que lance `bash` : un skill peut appeler `"$MM_BIN" --ask …`. | |
| 26 | + | |
| 27 | +### Configuration | |
| 28 | + | |
| 29 | +- **`bashTimeout`** (`30s` par défaut) : la durée maximale d'une commande `bash`. | |
| 30 | + | |
| 31 | +## Démos | |
| 32 | + | |
| 33 | +- **`demo-llmman/`** : deux autres agents (Jan, Lucy), un relecteur, et les skills `delegate-to-jan`, `second-opinion` et `compare-agents`. | |
| 34 | + | |
| 35 | +## Documentation | |
| 36 | + | |
| 37 | +- **Nouveaux guides pratiques** (EN + FR) : Interroger un autre agent, Interroger un autre agent depuis un skill. | |
| 38 | +- **Nouvelle explication** (EN + FR) : Interroger un autre agent. | |
| new file mode 100644 | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | +# mini-me v0.2.6 | ||
| 2 | + | ||
| 3 | +*[English version](v0.2.6.en.md)* | ||
| 4 | + | ||
| 5 | +Un agent peut interroger un autre agent mm. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +### Interroger un autre agent (#66) | ||
| 10 | + | ||
| 11 | +- **`mmConfigs`** : une table qui associe des noms d'agents à des fichiers de configuration. | ||
| 12 | +- **Commandes** : | ||
| 13 | + - `/ask-<nom> <prompt>` envoie la question seule ; | ||
| 14 | + - `/ask-with-context-<nom> <prompt>` envoie d'abord une transcription de la conversation ; | ||
| 15 | + - dans le terminal et la fenêtre, le `/ask <nom>` générique marche aussi. | ||
| 16 | +- **Comment tourne l'agent interrogé** : | ||
| 17 | + - chaque question lance un nouveau `mm -acp` enfant ; | ||
| 18 | + - sa réponse s'affiche en direct et reste dans l'historique ; | ||
| 19 | + - ses appels d'outil s'affichent sous la forme `<nom> · <outil>`, et ses demandes de permission passent par votre interface. | ||
| 20 | +- **Limites** : la profondeur est limitée à 2 (`MM_ASK_DEPTH`). Les agents interrogés n'enregistrent pas de session (`AGENT_SESSIONS=off`). | ||
| 21 | + | ||
| 22 | +### Depuis un skill ou un script (#73) | ||
| 23 | + | ||
| 24 | +- **`mm --ask <nom> -p "…"`** et **`--ask-with-context <nom>`** : une question, et seulement la réponse sur stdout. | ||
| 25 | +- **`MM_BIN` et `MM_CONFIG`** sont définies pour les commandes que lance `bash` : un skill peut appeler `"$MM_BIN" --ask …`. | ||
| 26 | + | ||
| 27 | +### Configuration | ||
| 28 | + | ||
| 29 | +- **`bashTimeout`** (`30s` par défaut) : la durée maximale d'une commande `bash`. | ||
| 30 | + | ||
| 31 | +## Démos | ||
| 32 | + | ||
| 33 | +- **`demo-llmman/`** : deux autres agents (Jan, Lucy), un relecteur, et les skills `delegate-to-jan`, `second-opinion` et `compare-agents`. | ||
| 34 | + | ||
| 35 | +## Documentation | ||
| 36 | + | ||
| 37 | +- **Nouveaux guides pratiques** (EN + FR) : Interroger un autre agent, Interroger un autre agent depuis un skill. | ||
| 38 | +- **Nouvelle explication** (EN + FR) : Interroger un autre agent. | ||
added
release-notes/v0.3.0.en.md +61 -0 | new file mode 100644 | ||
| @@ -0,0 +1,61 @@ | ||
| 1 | +# mini-me v0.3.0 | |
| 2 | + | |
| 3 | +*[Version française](v0.3.0.fr.md)* | |
| 4 | + | |
| 5 | +Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +### Yolo mode: confirm tool calls or not (#13, #59) | |
| 10 | + | |
| 11 | +- **`yolo`** (default `true`, overridden by `AGENT_YOLO`): | |
| 12 | + - `true`: tools run without asking; | |
| 13 | + - `false`: every call that changes something asks first, in every front end, including the line mode and an editor. | |
| 14 | +- **Four answers**: `a` once, `s` for the session, `r` reject this call, `n` never. | |
| 15 | +- **`-p` and `--ask` cannot ask**, so with `yolo: false` they refuse the call and say so. | |
| 16 | + | |
| 17 | +### Agents calling agents | |
| 18 | + | |
| 19 | +- **`ask_agent`** is a built-in tool: the model decides itself when to hand a task to an agent of `mmConfigs`. It takes `agent`, `prompt` and `with_context`. | |
| 20 | +- **`mmConfigs` long form**: `{path, description, yolo}`. | |
| 21 | + - The description feeds the tool. | |
| 22 | + - A per-agent `yolo` overrides the asker's. | |
| 23 | +- **Window labels**: the window labels answers and its header with `acp.title`, and switches the header to the asked agent while it works. | |
| 24 | + | |
| 25 | +### Model traffic | |
| 26 | + | |
| 27 | +- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) writes every request and response to `.mm/traces`. Headers are never written, so no API key ends up in the files. | |
| 28 | +- **Genkit Go 1.13.1** (#76). Extra `sampling` keys now reach the server. For example, `chat_template_kwargs: {enable_thinking: false}` turns a model's reasoning off (#67). Compression summaries always turn reasoning off. | |
| 29 | + | |
| 30 | +### JSON answers: `structuredOutput` (#80) | |
| 31 | + | |
| 32 | +- **Declare the answer's shape as Go types** in the YAML. mm sends the matching JSON Schema; with `-p` and `--ask`, only the JSON is printed on stdout. | |
| 33 | + - Comments become descriptions, and pointers or `omitempty` make a field optional. | |
| 34 | + - Unsupported constructs are configuration errors, with their line and column. | |
| 35 | + | |
| 36 | +### Project instructions: `AGENTS.md` (#81) | |
| 37 | + | |
| 38 | +- **mm reads `AGENTS.md`** from the working directory into the system prompt, under `PROJECT INSTRUCTIONS`. It is read at start-up, on `/new` and for each new editor session. | |
| 39 | +- **`agentsMd`** settings: `enabled` (on by default), `path` and `maxChars`. | |
| 40 | +- **The banner** shows the file and warns when it takes 20 % or more of the context window. | |
| 41 | + | |
| 42 | +## Fixed | |
| 43 | + | |
| 44 | +- **Start-up errors now go to stderr**, so `mm -p … | jq` is not fed an error message. | |
| 45 | + | |
| 46 | +## Demos | |
| 47 | + | |
| 48 | +- **`mini-mes/gopher`**: a Go specialist. | |
| 49 | +- **`mini-mes/riker`**: a coordinator, a specifier and a coder. | |
| 50 | +- **`demo-structured-output/`** and **`demo-agents-file/`**. | |
| 51 | +- **`demo/` is renamed `demo-dmr/`.** | |
| 52 | + | |
| 53 | +## Docs | |
| 54 | + | |
| 55 | +- **New how-to guides** (EN + FR): Confirm tool calls, Turn reasoning off, Let the agent call other agents, Inspect model traffic, Get JSON answers, Give project instructions. | |
| 56 | +- **New explanations** (EN + FR): yolo and permissions, structured output, project instructions. | |
| 57 | + | |
| 58 | +## Upgrading | |
| 59 | + | |
| 60 | +- **`permissions` is replaced by `yolo`.** `permissions` is still read, with a deprecation warning. | |
| 61 | +- **Editors no longer show a permission dialog by default**, since `yolo` defaults to `true`. Set `yolo: false` to get the dialogs back. | |
| new file mode 100644 | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | +# mini-me v0.3.0 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.3.0.fr.md)* | ||
| 4 | + | ||
| 5 | +Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +### Yolo mode: confirm tool calls or not (#13, #59) | ||
| 10 | + | ||
| 11 | +- **`yolo`** (default `true`, overridden by `AGENT_YOLO`): | ||
| 12 | + - `true`: tools run without asking; | ||
| 13 | + - `false`: every call that changes something asks first, in every front end, including the line mode and an editor. | ||
| 14 | +- **Four answers**: `a` once, `s` for the session, `r` reject this call, `n` never. | ||
| 15 | +- **`-p` and `--ask` cannot ask**, so with `yolo: false` they refuse the call and say so. | ||
| 16 | + | ||
| 17 | +### Agents calling agents | ||
| 18 | + | ||
| 19 | +- **`ask_agent`** is a built-in tool: the model decides itself when to hand a task to an agent of `mmConfigs`. It takes `agent`, `prompt` and `with_context`. | ||
| 20 | +- **`mmConfigs` long form**: `{path, description, yolo}`. | ||
| 21 | + - The description feeds the tool. | ||
| 22 | + - A per-agent `yolo` overrides the asker's. | ||
| 23 | +- **Window labels**: the window labels answers and its header with `acp.title`, and switches the header to the asked agent while it works. | ||
| 24 | + | ||
| 25 | +### Model traffic | ||
| 26 | + | ||
| 27 | +- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) writes every request and response to `.mm/traces`. Headers are never written, so no API key ends up in the files. | ||
| 28 | +- **Genkit Go 1.13.1** (#76). Extra `sampling` keys now reach the server. For example, `chat_template_kwargs: {enable_thinking: false}` turns a model's reasoning off (#67). Compression summaries always turn reasoning off. | ||
| 29 | + | ||
| 30 | +### JSON answers: `structuredOutput` (#80) | ||
| 31 | + | ||
| 32 | +- **Declare the answer's shape as Go types** in the YAML. mm sends the matching JSON Schema; with `-p` and `--ask`, only the JSON is printed on stdout. | ||
| 33 | + - Comments become descriptions, and pointers or `omitempty` make a field optional. | ||
| 34 | + - Unsupported constructs are configuration errors, with their line and column. | ||
| 35 | + | ||
| 36 | +### Project instructions: `AGENTS.md` (#81) | ||
| 37 | + | ||
| 38 | +- **mm reads `AGENTS.md`** from the working directory into the system prompt, under `PROJECT INSTRUCTIONS`. It is read at start-up, on `/new` and for each new editor session. | ||
| 39 | +- **`agentsMd`** settings: `enabled` (on by default), `path` and `maxChars`. | ||
| 40 | +- **The banner** shows the file and warns when it takes 20 % or more of the context window. | ||
| 41 | + | ||
| 42 | +## Fixed | ||
| 43 | + | ||
| 44 | +- **Start-up errors now go to stderr**, so `mm -p … | jq` is not fed an error message. | ||
| 45 | + | ||
| 46 | +## Demos | ||
| 47 | + | ||
| 48 | +- **`mini-mes/gopher`**: a Go specialist. | ||
| 49 | +- **`mini-mes/riker`**: a coordinator, a specifier and a coder. | ||
| 50 | +- **`demo-structured-output/`** and **`demo-agents-file/`**. | ||
| 51 | +- **`demo/` is renamed `demo-dmr/`.** | ||
| 52 | + | ||
| 53 | +## Docs | ||
| 54 | + | ||
| 55 | +- **New how-to guides** (EN + FR): Confirm tool calls, Turn reasoning off, Let the agent call other agents, Inspect model traffic, Get JSON answers, Give project instructions. | ||
| 56 | +- **New explanations** (EN + FR): yolo and permissions, structured output, project instructions. | ||
| 57 | + | ||
| 58 | +## Upgrading | ||
| 59 | + | ||
| 60 | +- **`permissions` is replaced by `yolo`.** `permissions` is still read, with a deprecation warning. | ||
| 61 | +- **Editors no longer show a permission dialog by default**, since `yolo` defaults to `true`. Set `yolo: false` to get the dialogs back. | ||
added
release-notes/v0.3.0.fr.md +61 -0 | new file mode 100644 | ||
| @@ -0,0 +1,61 @@ | ||
| 1 | +# mini-me v0.3.0 | |
| 2 | + | |
| 3 | +*[English version](v0.3.0.en.md)* | |
| 4 | + | |
| 5 | +Le mode yolo, Genkit 1.13.1, un outil `ask_agent`, les traces des échanges avec le modèle, des réponses JSON vérifiées contre des types Go, et `AGENTS.md`. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +### Mode yolo : confirmer ou non les appels d'outil (#13, #59) | |
| 10 | + | |
| 11 | +- **`yolo`** (`true` par défaut, remplacé par `AGENT_YOLO`) : | |
| 12 | + - `true` : les outils s'exécutent sans demander ; | |
| 13 | + - `false` : chaque appel qui change quelque chose demande d'abord, dans toutes les interfaces, y compris le mode ligne et un éditeur. | |
| 14 | +- **Quatre réponses** : `a` une fois, `s` pour la session, `r` refuser cet appel, `n` jamais. | |
| 15 | +- **`-p` et `--ask` ne peuvent pas demander** : avec `yolo: false`, ils refusent l'appel et le disent. | |
| 16 | + | |
| 17 | +### Des agents qui appellent des agents | |
| 18 | + | |
| 19 | +- **`ask_agent`** est un outil intégré : le modèle décide lui-même quand confier une tâche à un agent de `mmConfigs`. Il prend `agent`, `prompt` et `with_context`. | |
| 20 | +- **Forme longue de `mmConfigs`** : `{path, description, yolo}`. | |
| 21 | + - La description alimente l'outil. | |
| 22 | + - Un `yolo` par agent l'emporte sur celui de l'agent qui interroge. | |
| 23 | +- **Étiquettes de la fenêtre** : la fenêtre étiquette les réponses et son en-tête avec `acp.title`, et l'en-tête passe à l'agent interrogé pendant qu'il travaille. | |
| 24 | + | |
| 25 | +### Échanges avec le modèle | |
| 26 | + | |
| 27 | +- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) écrit chaque requête et chaque réponse dans `.mm/traces`. Les en-têtes ne sont jamais écrits : aucune clé d'API ne se retrouve dans les fichiers. | |
| 28 | +- **Genkit Go 1.13.1** (#76). Les clés `sampling` supplémentaires arrivent maintenant au serveur. Par exemple, `chat_template_kwargs: {enable_thinking: false}` coupe le raisonnement d'un modèle (#67). Les résumés de compression coupent toujours le raisonnement. | |
| 29 | + | |
| 30 | +### Réponses JSON : `structuredOutput` (#80) | |
| 31 | + | |
| 32 | +- **Déclarez la forme de la réponse avec des types Go** dans le YAML. mm envoie le JSON Schema correspondant ; avec `-p` et `--ask`, seul le JSON est écrit sur stdout. | |
| 33 | + - Les commentaires deviennent des descriptions, et un pointeur ou `omitempty` rend un champ facultatif. | |
| 34 | + - Les constructions non prises en charge sont des erreurs de configuration, avec leur ligne et leur colonne. | |
| 35 | + | |
| 36 | +### Instructions de projet : `AGENTS.md` (#81) | |
| 37 | + | |
| 38 | +- **mm lit `AGENTS.md`** dans le répertoire de travail et l'ajoute au prompt système, sous `PROJECT INSTRUCTIONS`. Il est lu au démarrage, à `/new` et pour chaque nouvelle session d'éditeur. | |
| 39 | +- **Réglages `agentsMd`** : `enabled` (activé par défaut), `path` et `maxChars`. | |
| 40 | +- **La bannière** affiche le fichier et prévient quand il occupe 20 % ou plus de la fenêtre de contexte. | |
| 41 | + | |
| 42 | +## Corrigé | |
| 43 | + | |
| 44 | +- **Les erreurs de démarrage vont maintenant sur stderr** : `mm -p … | jq` ne reçoit plus un message d'erreur. | |
| 45 | + | |
| 46 | +## Démos | |
| 47 | + | |
| 48 | +- **`mini-mes/gopher`** : un spécialiste Go. | |
| 49 | +- **`mini-mes/riker`** : un coordinateur, un spécificateur et un codeur. | |
| 50 | +- **`demo-structured-output/`** et **`demo-agents-file/`**. | |
| 51 | +- **`demo/` est renommé `demo-dmr/`.** | |
| 52 | + | |
| 53 | +## Documentation | |
| 54 | + | |
| 55 | +- **Nouveaux guides pratiques** (EN + FR) : Confirmer les appels d'outil, Couper le raisonnement, Laisser l'agent appeler d'autres agents, Inspecter les échanges avec le modèle, Obtenir des réponses JSON, Donner des instructions de projet. | |
| 56 | +- **Nouvelles explications** (EN + FR) : yolo et permissions, sortie structurée, instructions de projet. | |
| 57 | + | |
| 58 | +## Mise à jour | |
| 59 | + | |
| 60 | +- **`permissions` est remplacé par `yolo`.** `permissions` est encore lu, avec un avertissement d'obsolescence. | |
| 61 | +- **Les éditeurs n'affichent plus de demande de permission par défaut**, puisque `yolo` vaut `true` par défaut. Mettez `yolo: false` pour retrouver les demandes. | |
| new file mode 100644 | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | +# mini-me v0.3.0 | ||
| 2 | + | ||
| 3 | +*[English version](v0.3.0.en.md)* | ||
| 4 | + | ||
| 5 | +Le mode yolo, Genkit 1.13.1, un outil `ask_agent`, les traces des échanges avec le modèle, des réponses JSON vérifiées contre des types Go, et `AGENTS.md`. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +### Mode yolo : confirmer ou non les appels d'outil (#13, #59) | ||
| 10 | + | ||
| 11 | +- **`yolo`** (`true` par défaut, remplacé par `AGENT_YOLO`) : | ||
| 12 | + - `true` : les outils s'exécutent sans demander ; | ||
| 13 | + - `false` : chaque appel qui change quelque chose demande d'abord, dans toutes les interfaces, y compris le mode ligne et un éditeur. | ||
| 14 | +- **Quatre réponses** : `a` une fois, `s` pour la session, `r` refuser cet appel, `n` jamais. | ||
| 15 | +- **`-p` et `--ask` ne peuvent pas demander** : avec `yolo: false`, ils refusent l'appel et le disent. | ||
| 16 | + | ||
| 17 | +### Des agents qui appellent des agents | ||
| 18 | + | ||
| 19 | +- **`ask_agent`** est un outil intégré : le modèle décide lui-même quand confier une tâche à un agent de `mmConfigs`. Il prend `agent`, `prompt` et `with_context`. | ||
| 20 | +- **Forme longue de `mmConfigs`** : `{path, description, yolo}`. | ||
| 21 | + - La description alimente l'outil. | ||
| 22 | + - Un `yolo` par agent l'emporte sur celui de l'agent qui interroge. | ||
| 23 | +- **Étiquettes de la fenêtre** : la fenêtre étiquette les réponses et son en-tête avec `acp.title`, et l'en-tête passe à l'agent interrogé pendant qu'il travaille. | ||
| 24 | + | ||
| 25 | +### Échanges avec le modèle | ||
| 26 | + | ||
| 27 | +- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) écrit chaque requête et chaque réponse dans `.mm/traces`. Les en-têtes ne sont jamais écrits : aucune clé d'API ne se retrouve dans les fichiers. | ||
| 28 | +- **Genkit Go 1.13.1** (#76). Les clés `sampling` supplémentaires arrivent maintenant au serveur. Par exemple, `chat_template_kwargs: {enable_thinking: false}` coupe le raisonnement d'un modèle (#67). Les résumés de compression coupent toujours le raisonnement. | ||
| 29 | + | ||
| 30 | +### Réponses JSON : `structuredOutput` (#80) | ||
| 31 | + | ||
| 32 | +- **Déclarez la forme de la réponse avec des types Go** dans le YAML. mm envoie le JSON Schema correspondant ; avec `-p` et `--ask`, seul le JSON est écrit sur stdout. | ||
| 33 | + - Les commentaires deviennent des descriptions, et un pointeur ou `omitempty` rend un champ facultatif. | ||
| 34 | + - Les constructions non prises en charge sont des erreurs de configuration, avec leur ligne et leur colonne. | ||
| 35 | + | ||
| 36 | +### Instructions de projet : `AGENTS.md` (#81) | ||
| 37 | + | ||
| 38 | +- **mm lit `AGENTS.md`** dans le répertoire de travail et l'ajoute au prompt système, sous `PROJECT INSTRUCTIONS`. Il est lu au démarrage, à `/new` et pour chaque nouvelle session d'éditeur. | ||
| 39 | +- **Réglages `agentsMd`** : `enabled` (activé par défaut), `path` et `maxChars`. | ||
| 40 | +- **La bannière** affiche le fichier et prévient quand il occupe 20 % ou plus de la fenêtre de contexte. | ||
| 41 | + | ||
| 42 | +## Corrigé | ||
| 43 | + | ||
| 44 | +- **Les erreurs de démarrage vont maintenant sur stderr** : `mm -p … | jq` ne reçoit plus un message d'erreur. | ||
| 45 | + | ||
| 46 | +## Démos | ||
| 47 | + | ||
| 48 | +- **`mini-mes/gopher`** : un spécialiste Go. | ||
| 49 | +- **`mini-mes/riker`** : un coordinateur, un spécificateur et un codeur. | ||
| 50 | +- **`demo-structured-output/`** et **`demo-agents-file/`**. | ||
| 51 | +- **`demo/` est renommé `demo-dmr/`.** | ||
| 52 | + | ||
| 53 | +## Documentation | ||
| 54 | + | ||
| 55 | +- **Nouveaux guides pratiques** (EN + FR) : Confirmer les appels d'outil, Couper le raisonnement, Laisser l'agent appeler d'autres agents, Inspecter les échanges avec le modèle, Obtenir des réponses JSON, Donner des instructions de projet. | ||
| 56 | +- **Nouvelles explications** (EN + FR) : yolo et permissions, sortie structurée, instructions de projet. | ||
| 57 | + | ||
| 58 | +## Mise à jour | ||
| 59 | + | ||
| 60 | +- **`permissions` est remplacé par `yolo`.** `permissions` est encore lu, avec un avertissement d'obsolescence. | ||
| 61 | +- **Les éditeurs n'affichent plus de demande de permission par défaut**, puisque `yolo` vaut `true` par défaut. Mettez `yolo: false` pour retrouver les demandes. | ||
added
release-notes/v0.4.0.en.md +36 -0 | new file mode 100644 | ||
| @@ -0,0 +1,36 @@ | ||
| 1 | +# mini-me v0.4.0 | |
| 2 | + | |
| 3 | +*[Version française](v0.4.0.fr.md)* | |
| 4 | + | |
| 5 | +The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +- **`ask_user`** (#84): the model asks you up to 4 questions at once: pick one, pick several, type a text, or yes/no. | |
| 10 | + - **Where it shows**: numbered answers in the line mode, arrow keys in the window, `elicitation/create` in an editor. An editor that does not support elicitation gets a permission dialog instead. | |
| 11 | + - **Agents you ask**: their questions are relayed to your user. | |
| 12 | + - **Switches**: `askUserTool` (default `true`), `AGENT_ASK_USER`, and `mmConfigs.<name>.askUser`. | |
| 13 | + - It is never declared next to a `structuredOutput`. | |
| 14 | +- **Agent creator**: `agent.creator.yaml` and the skill `skills/create-agent`. They write an mm agent tuned to a given model, from the facts of its chat template. | |
| 15 | + | |
| 16 | +## Fixed | |
| 17 | + | |
| 18 | +- **MCP tools are declared with their parameters.** They used to reach the model with an empty schema, so llama.cpp parsed every call as `{}`. The model now sees a lenient copy of the input schema. | |
| 19 | + | |
| 20 | +## Demos | |
| 21 | + | |
| 22 | +- **All demos now live under `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …). | |
| 23 | +- **New demos**: | |
| 24 | + - `little-coder` (Go + htmx); | |
| 25 | + - `team` and `blog-team` (teams of agents); | |
| 26 | + - `translator`; | |
| 27 | + - `news-roundup` (#83, an MCP server over DuckDuckGo); | |
| 28 | + - `project-wizard` (`ask_user`); | |
| 29 | + - three agents in the style of other coding CLIs: `qwen-style` (Qwen Code's prompt), `gemini-style` (#85, Gemini CLI's prompt) and `codex-style` (#86, Codex CLI's prompt). | |
| 30 | +- **LSP for Go, Rust and Node** in `gemini-style` and `qwen-style`. | |
| 31 | + | |
| 32 | +## Docs | |
| 33 | + | |
| 34 | +- **New how-to guide and explanation** (EN + FR): Let the agent ask you. | |
| 35 | +- **Docker Sandboxes how-to** rewritten: the mini-me kit now lives in its own repository (`mini-me-kit`). | |
| 36 | +- **New `LICENSE` file** (MIT). | |
| new file mode 100644 | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | +# mini-me v0.4.0 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.4.0.fr.md)* | ||
| 4 | + | ||
| 5 | +The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +- **`ask_user`** (#84): the model asks you up to 4 questions at once: pick one, pick several, type a text, or yes/no. | ||
| 10 | + - **Where it shows**: numbered answers in the line mode, arrow keys in the window, `elicitation/create` in an editor. An editor that does not support elicitation gets a permission dialog instead. | ||
| 11 | + - **Agents you ask**: their questions are relayed to your user. | ||
| 12 | + - **Switches**: `askUserTool` (default `true`), `AGENT_ASK_USER`, and `mmConfigs.<name>.askUser`. | ||
| 13 | + - It is never declared next to a `structuredOutput`. | ||
| 14 | +- **Agent creator**: `agent.creator.yaml` and the skill `skills/create-agent`. They write an mm agent tuned to a given model, from the facts of its chat template. | ||
| 15 | + | ||
| 16 | +## Fixed | ||
| 17 | + | ||
| 18 | +- **MCP tools are declared with their parameters.** They used to reach the model with an empty schema, so llama.cpp parsed every call as `{}`. The model now sees a lenient copy of the input schema. | ||
| 19 | + | ||
| 20 | +## Demos | ||
| 21 | + | ||
| 22 | +- **All demos now live under `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …). | ||
| 23 | +- **New demos**: | ||
| 24 | + - `little-coder` (Go + htmx); | ||
| 25 | + - `team` and `blog-team` (teams of agents); | ||
| 26 | + - `translator`; | ||
| 27 | + - `news-roundup` (#83, an MCP server over DuckDuckGo); | ||
| 28 | + - `project-wizard` (`ask_user`); | ||
| 29 | + - three agents in the style of other coding CLIs: `qwen-style` (Qwen Code's prompt), `gemini-style` (#85, Gemini CLI's prompt) and `codex-style` (#86, Codex CLI's prompt). | ||
| 30 | +- **LSP for Go, Rust and Node** in `gemini-style` and `qwen-style`. | ||
| 31 | + | ||
| 32 | +## Docs | ||
| 33 | + | ||
| 34 | +- **New how-to guide and explanation** (EN + FR): Let the agent ask you. | ||
| 35 | +- **Docker Sandboxes how-to** rewritten: the mini-me kit now lives in its own repository (`mini-me-kit`). | ||
| 36 | +- **New `LICENSE` file** (MIT). | ||
added
release-notes/v0.4.0.fr.md +36 -0 | new file mode 100644 | ||
| @@ -0,0 +1,36 @@ | ||
| 1 | +# mini-me v0.4.0 | |
| 2 | + | |
| 3 | +*[English version](v0.4.0.en.md)* | |
| 4 | + | |
| 5 | +L'agent peut vous poser des questions, les outils MCP arrivent avec leurs paramètres, et les démos passent dans `demos/`. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +- **`ask_user`** (#84) : le modèle vous pose jusqu'à 4 questions à la fois : choisir une option, en choisir plusieurs, taper un texte, ou oui/non. | |
| 10 | + - **Où ça s'affiche** : réponses numérotées en mode ligne, flèches dans la fenêtre, `elicitation/create` dans un éditeur. Un éditeur qui ne prend pas en charge l'élicitation reçoit une demande de permission à la place. | |
| 11 | + - **Agents interrogés** : leurs questions sont relayées à votre utilisateur. | |
| 12 | + - **Réglages** : `askUserTool` (`true` par défaut), `AGENT_ASK_USER` et `mmConfigs.<nom>.askUser`. | |
| 13 | + - Il n'est jamais déclaré à côté d'un `structuredOutput`. | |
| 14 | +- **Créateur d'agents** : `agent.creator.yaml` et le skill `skills/create-agent`. Ils écrivent un agent mm adapté à un modèle donné, à partir des informations de son chat template. | |
| 15 | + | |
| 16 | +## Corrigé | |
| 17 | + | |
| 18 | +- **Les outils MCP sont déclarés avec leurs paramètres.** Ils arrivaient au modèle avec un schéma vide, et llama.cpp lisait donc chaque appel comme `{}`. Le modèle voit maintenant une copie souple du schéma d'entrée. | |
| 19 | + | |
| 20 | +## Démos | |
| 21 | + | |
| 22 | +- **Toutes les démos sont maintenant dans `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …). | |
| 23 | +- **Nouvelles démos** : | |
| 24 | + - `little-coder` (Go + htmx) ; | |
| 25 | + - `team` et `blog-team` (des équipes d'agents) ; | |
| 26 | + - `translator` ; | |
| 27 | + - `news-roundup` (#83, un serveur MCP au-dessus de DuckDuckGo) ; | |
| 28 | + - `project-wizard` (`ask_user`) ; | |
| 29 | + - trois agents dans le style d'autres CLI de code : `qwen-style` (le prompt de Qwen Code), `gemini-style` (#85, celui de Gemini CLI) et `codex-style` (#86, celui de Codex CLI). | |
| 30 | +- **LSP pour Go, Rust et Node** dans `gemini-style` et `qwen-style`. | |
| 31 | + | |
| 32 | +## Documentation | |
| 33 | + | |
| 34 | +- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Laisser l'agent vous poser des questions. | |
| 35 | +- **Guide Docker Sandboxes** réécrit : le kit mini-me vit maintenant dans son propre dépôt (`mini-me-kit`). | |
| 36 | +- **Nouveau fichier `LICENSE`** (MIT). | |
| new file mode 100644 | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | +# mini-me v0.4.0 | ||
| 2 | + | ||
| 3 | +*[English version](v0.4.0.en.md)* | ||
| 4 | + | ||
| 5 | +L'agent peut vous poser des questions, les outils MCP arrivent avec leurs paramètres, et les démos passent dans `demos/`. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +- **`ask_user`** (#84) : le modèle vous pose jusqu'à 4 questions à la fois : choisir une option, en choisir plusieurs, taper un texte, ou oui/non. | ||
| 10 | + - **Où ça s'affiche** : réponses numérotées en mode ligne, flèches dans la fenêtre, `elicitation/create` dans un éditeur. Un éditeur qui ne prend pas en charge l'élicitation reçoit une demande de permission à la place. | ||
| 11 | + - **Agents interrogés** : leurs questions sont relayées à votre utilisateur. | ||
| 12 | + - **Réglages** : `askUserTool` (`true` par défaut), `AGENT_ASK_USER` et `mmConfigs.<nom>.askUser`. | ||
| 13 | + - Il n'est jamais déclaré à côté d'un `structuredOutput`. | ||
| 14 | +- **Créateur d'agents** : `agent.creator.yaml` et le skill `skills/create-agent`. Ils écrivent un agent mm adapté à un modèle donné, à partir des informations de son chat template. | ||
| 15 | + | ||
| 16 | +## Corrigé | ||
| 17 | + | ||
| 18 | +- **Les outils MCP sont déclarés avec leurs paramètres.** Ils arrivaient au modèle avec un schéma vide, et llama.cpp lisait donc chaque appel comme `{}`. Le modèle voit maintenant une copie souple du schéma d'entrée. | ||
| 19 | + | ||
| 20 | +## Démos | ||
| 21 | + | ||
| 22 | +- **Toutes les démos sont maintenant dans `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …). | ||
| 23 | +- **Nouvelles démos** : | ||
| 24 | + - `little-coder` (Go + htmx) ; | ||
| 25 | + - `team` et `blog-team` (des équipes d'agents) ; | ||
| 26 | + - `translator` ; | ||
| 27 | + - `news-roundup` (#83, un serveur MCP au-dessus de DuckDuckGo) ; | ||
| 28 | + - `project-wizard` (`ask_user`) ; | ||
| 29 | + - trois agents dans le style d'autres CLI de code : `qwen-style` (le prompt de Qwen Code), `gemini-style` (#85, celui de Gemini CLI) et `codex-style` (#86, celui de Codex CLI). | ||
| 30 | +- **LSP pour Go, Rust et Node** dans `gemini-style` et `qwen-style`. | ||
| 31 | + | ||
| 32 | +## Documentation | ||
| 33 | + | ||
| 34 | +- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Laisser l'agent vous poser des questions. | ||
| 35 | +- **Guide Docker Sandboxes** réécrit : le kit mini-me vit maintenant dans son propre dépôt (`mini-me-kit`). | ||
| 36 | +- **Nouveau fichier `LICENSE`** (MIT). | ||
added
release-notes/v0.5.0.en.md +49 -0 | new file mode 100644 | ||
| @@ -0,0 +1,49 @@ | ||
| 1 | +# mini-me v0.5.0 | |
| 2 | + | |
| 3 | +*[Version française](v0.5.0.fr.md)* | |
| 4 | + | |
| 5 | +Hooks: your own scripts around generations, tool calls and calls to other agents. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +### Hooks (#36) | |
| 10 | + | |
| 11 | +- **`hooks:`** runs your own commands at four moments: `preGeneration`, `preToolCall`, `postToolCall` and `postGeneration`. | |
| 12 | +- **What a hook can do**: | |
| 13 | + - stop a question; | |
| 14 | + - add context for the model; | |
| 15 | + - deny or rewrite a tool call; | |
| 16 | + - add text after a tool's output; | |
| 17 | + - observe the end of a turn. | |
| 18 | +- **Protocol**: one JSON object on stdin, an optional JSON object on stdout. | |
| 19 | +- **Per hook**: `matcher` (a regular expression on the tool name), `timeout` and `onError`. | |
| 20 | +- **A stopped question is masked** in the history. | |
| 21 | +- **The banner** lists the hooks. | |
| 22 | + | |
| 23 | +### Hooks around calls to other agents (#87) | |
| 24 | + | |
| 25 | +- **`preAsk` and `postAsk`**, declared in the asking agent's file, with a `matcher` on the agent's name. They run for `ask_agent`, `/ask…` and `mm --ask`. | |
| 26 | + - `preAsk` can deny the call, rewrite the question or the conversation sent, or add context. | |
| 27 | + - `postAsk` can replace the answer or add context after it. | |
| 28 | + | |
| 29 | +## Fixed | |
| 30 | + | |
| 31 | +- **Hook input JSON is no longer HTML-escaped**: `&&` reached hooks as `&&`, which hid it from a plain text search. | |
| 32 | +- **A call denied by a hook** now names the command in its title. In the line mode, a denied `ask_agent` shows a line. | |
| 33 | +- **In an editor**, the plan tool's "unknown action" message now lists the valid actions. | |
| 34 | + | |
| 35 | +## Security | |
| 36 | + | |
| 37 | +- **Dependency updates** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) and the toolchain pinned to `go1.26.9`, to clear known CVEs reported by osv-scanner. | |
| 38 | + | |
| 39 | +## Demos | |
| 40 | + | |
| 41 | +- `hooks` (Node.js hooks); | |
| 42 | +- `hooks-ask`; | |
| 43 | +- `dungeon` (a role-playing team on Qwen3.5 4B); | |
| 44 | +- `coding-helpers` (Go and Rust documentation); | |
| 45 | +- `structured-output-pizza` (`structuredOutput` with Go types). | |
| 46 | + | |
| 47 | +## Docs | |
| 48 | + | |
| 49 | +- **New hooks pages** (EN + FR): reference, explanation, and the how-to guides "Guard the agent with hooks" and "Control agent calls with hooks". | |
| new file mode 100644 | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | +# mini-me v0.5.0 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.5.0.fr.md)* | ||
| 4 | + | ||
| 5 | +Hooks: your own scripts around generations, tool calls and calls to other agents. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +### Hooks (#36) | ||
| 10 | + | ||
| 11 | +- **`hooks:`** runs your own commands at four moments: `preGeneration`, `preToolCall`, `postToolCall` and `postGeneration`. | ||
| 12 | +- **What a hook can do**: | ||
| 13 | + - stop a question; | ||
| 14 | + - add context for the model; | ||
| 15 | + - deny or rewrite a tool call; | ||
| 16 | + - add text after a tool's output; | ||
| 17 | + - observe the end of a turn. | ||
| 18 | +- **Protocol**: one JSON object on stdin, an optional JSON object on stdout. | ||
| 19 | +- **Per hook**: `matcher` (a regular expression on the tool name), `timeout` and `onError`. | ||
| 20 | +- **A stopped question is masked** in the history. | ||
| 21 | +- **The banner** lists the hooks. | ||
| 22 | + | ||
| 23 | +### Hooks around calls to other agents (#87) | ||
| 24 | + | ||
| 25 | +- **`preAsk` and `postAsk`**, declared in the asking agent's file, with a `matcher` on the agent's name. They run for `ask_agent`, `/ask…` and `mm --ask`. | ||
| 26 | + - `preAsk` can deny the call, rewrite the question or the conversation sent, or add context. | ||
| 27 | + - `postAsk` can replace the answer or add context after it. | ||
| 28 | + | ||
| 29 | +## Fixed | ||
| 30 | + | ||
| 31 | +- **Hook input JSON is no longer HTML-escaped**: `&&` reached hooks as `&&`, which hid it from a plain text search. | ||
| 32 | +- **A call denied by a hook** now names the command in its title. In the line mode, a denied `ask_agent` shows a line. | ||
| 33 | +- **In an editor**, the plan tool's "unknown action" message now lists the valid actions. | ||
| 34 | + | ||
| 35 | +## Security | ||
| 36 | + | ||
| 37 | +- **Dependency updates** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) and the toolchain pinned to `go1.26.9`, to clear known CVEs reported by osv-scanner. | ||
| 38 | + | ||
| 39 | +## Demos | ||
| 40 | + | ||
| 41 | +- `hooks` (Node.js hooks); | ||
| 42 | +- `hooks-ask`; | ||
| 43 | +- `dungeon` (a role-playing team on Qwen3.5 4B); | ||
| 44 | +- `coding-helpers` (Go and Rust documentation); | ||
| 45 | +- `structured-output-pizza` (`structuredOutput` with Go types). | ||
| 46 | + | ||
| 47 | +## Docs | ||
| 48 | + | ||
| 49 | +- **New hooks pages** (EN + FR): reference, explanation, and the how-to guides "Guard the agent with hooks" and "Control agent calls with hooks". | ||
added
release-notes/v0.5.0.fr.md +49 -0 | new file mode 100644 | ||
| @@ -0,0 +1,49 @@ | ||
| 1 | +# mini-me v0.5.0 | |
| 2 | + | |
| 3 | +*[English version](v0.5.0.en.md)* | |
| 4 | + | |
| 5 | +Les hooks : vos propres scripts autour des générations, des appels d'outil et des appels à d'autres agents. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +### Hooks (#36) | |
| 10 | + | |
| 11 | +- **`hooks:`** lance vos propres commandes à quatre moments : `preGeneration`, `preToolCall`, `postToolCall` et `postGeneration`. | |
| 12 | +- **Ce qu'un hook peut faire** : | |
| 13 | + - arrêter une question ; | |
| 14 | + - ajouter du contexte pour le modèle ; | |
| 15 | + - refuser ou réécrire un appel d'outil ; | |
| 16 | + - ajouter du texte après la sortie d'un outil ; | |
| 17 | + - observer la fin d'un tour. | |
| 18 | +- **Protocole** : un objet JSON sur stdin, un objet JSON facultatif sur stdout. | |
| 19 | +- **Par hook** : `matcher` (une expression régulière sur le nom de l'outil), `timeout` et `onError`. | |
| 20 | +- **Une question arrêtée est masquée** dans l'historique. | |
| 21 | +- **La bannière** liste les hooks. | |
| 22 | + | |
| 23 | +### Hooks autour des appels à d'autres agents (#87) | |
| 24 | + | |
| 25 | +- **`preAsk` et `postAsk`**, déclarés dans le fichier de l'agent qui interroge, avec un `matcher` sur le nom de l'agent. Ils s'exécutent pour `ask_agent`, `/ask…` et `mm --ask`. | |
| 26 | + - `preAsk` peut refuser l'appel, réécrire la question ou la conversation envoyée, ou ajouter du contexte. | |
| 27 | + - `postAsk` peut remplacer la réponse ou ajouter du contexte après elle. | |
| 28 | + | |
| 29 | +## Corrigé | |
| 30 | + | |
| 31 | +- **Le JSON d'entrée des hooks n'est plus échappé pour le HTML** : `&&` arrivait aux hooks sous la forme `&&`, ce qui le cachait à une simple recherche de texte. | |
| 32 | +- **Un appel refusé par un hook** nomme maintenant la commande dans son titre. En mode ligne, un `ask_agent` refusé affiche une ligne. | |
| 33 | +- **Dans un éditeur**, le message « action inconnue » de l'outil de plan liste maintenant les actions valides. | |
| 34 | + | |
| 35 | +## Sécurité | |
| 36 | + | |
| 37 | +- **Mises à jour de dépendances** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) et toolchain épinglée sur `go1.26.9`, pour corriger les CVE connues signalées par osv-scanner. | |
| 38 | + | |
| 39 | +## Démos | |
| 40 | + | |
| 41 | +- `hooks` (des hooks en Node.js) ; | |
| 42 | +- `hooks-ask` ; | |
| 43 | +- `dungeon` (une équipe de jeu de rôle sur Qwen3.5 4B) ; | |
| 44 | +- `coding-helpers` (documentation Go et Rust) ; | |
| 45 | +- `structured-output-pizza` (`structuredOutput` avec des types Go). | |
| 46 | + | |
| 47 | +## Documentation | |
| 48 | + | |
| 49 | +- **Nouvelles pages sur les hooks** (EN + FR) : la référence, l'explication, et les guides pratiques « Protéger l'agent avec des hooks » et « Contrôler les relais avec des hooks ». | |
| new file mode 100644 | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | +# mini-me v0.5.0 | ||
| 2 | + | ||
| 3 | +*[English version](v0.5.0.en.md)* | ||
| 4 | + | ||
| 5 | +Les hooks : vos propres scripts autour des générations, des appels d'outil et des appels à d'autres agents. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +### Hooks (#36) | ||
| 10 | + | ||
| 11 | +- **`hooks:`** lance vos propres commandes à quatre moments : `preGeneration`, `preToolCall`, `postToolCall` et `postGeneration`. | ||
| 12 | +- **Ce qu'un hook peut faire** : | ||
| 13 | + - arrêter une question ; | ||
| 14 | + - ajouter du contexte pour le modèle ; | ||
| 15 | + - refuser ou réécrire un appel d'outil ; | ||
| 16 | + - ajouter du texte après la sortie d'un outil ; | ||
| 17 | + - observer la fin d'un tour. | ||
| 18 | +- **Protocole** : un objet JSON sur stdin, un objet JSON facultatif sur stdout. | ||
| 19 | +- **Par hook** : `matcher` (une expression régulière sur le nom de l'outil), `timeout` et `onError`. | ||
| 20 | +- **Une question arrêtée est masquée** dans l'historique. | ||
| 21 | +- **La bannière** liste les hooks. | ||
| 22 | + | ||
| 23 | +### Hooks autour des appels à d'autres agents (#87) | ||
| 24 | + | ||
| 25 | +- **`preAsk` et `postAsk`**, déclarés dans le fichier de l'agent qui interroge, avec un `matcher` sur le nom de l'agent. Ils s'exécutent pour `ask_agent`, `/ask…` et `mm --ask`. | ||
| 26 | + - `preAsk` peut refuser l'appel, réécrire la question ou la conversation envoyée, ou ajouter du contexte. | ||
| 27 | + - `postAsk` peut remplacer la réponse ou ajouter du contexte après elle. | ||
| 28 | + | ||
| 29 | +## Corrigé | ||
| 30 | + | ||
| 31 | +- **Le JSON d'entrée des hooks n'est plus échappé pour le HTML** : `&&` arrivait aux hooks sous la forme `&&`, ce qui le cachait à une simple recherche de texte. | ||
| 32 | +- **Un appel refusé par un hook** nomme maintenant la commande dans son titre. En mode ligne, un `ask_agent` refusé affiche une ligne. | ||
| 33 | +- **Dans un éditeur**, le message « action inconnue » de l'outil de plan liste maintenant les actions valides. | ||
| 34 | + | ||
| 35 | +## Sécurité | ||
| 36 | + | ||
| 37 | +- **Mises à jour de dépendances** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) et toolchain épinglée sur `go1.26.9`, pour corriger les CVE connues signalées par osv-scanner. | ||
| 38 | + | ||
| 39 | +## Démos | ||
| 40 | + | ||
| 41 | +- `hooks` (des hooks en Node.js) ; | ||
| 42 | +- `hooks-ask` ; | ||
| 43 | +- `dungeon` (une équipe de jeu de rôle sur Qwen3.5 4B) ; | ||
| 44 | +- `coding-helpers` (documentation Go et Rust) ; | ||
| 45 | +- `structured-output-pizza` (`structuredOutput` avec des types Go). | ||
| 46 | + | ||
| 47 | +## Documentation | ||
| 48 | + | ||
| 49 | +- **Nouvelles pages sur les hooks** (EN + FR) : la référence, l'explication, et les guides pratiques « Protéger l'agent avec des hooks » et « Contrôler les relais avec des hooks ». | ||
added
release-notes/v0.6.0.en.md +36 -0 | new file mode 100644 | ||
| @@ -0,0 +1,36 @@ | ||
| 1 | +# mini-me v0.6.0 | |
| 2 | + | |
| 3 | +*[Version française](v0.6.0.fr.md)* | |
| 4 | + | |
| 5 | +Your own tools declared in the agent file, and answers saved to a file without being written again. | |
| 6 | + | |
| 7 | +## New | |
| 8 | + | |
| 9 | +### Declared tools (#88) | |
| 10 | + | |
| 11 | +- **A `tools:` block** declares your own tools. Each one is a command, run as an argument list and **never through a shell**. | |
| 12 | +- **Parameters** can be `string`, `enum`, `boolean`, `integer` or `number`. `"3"` and `"true"` are accepted for numbers and booleans. | |
| 13 | +- **Invalid calls** are answered in text, quoting the call back. | |
| 14 | +- **Consent follows `yolo`.** | |
| 15 | +- **Names**: built-in tool names are reserved, even when the tool is turned off. A clash with an MCP tool is a warning. | |
| 16 | +- Measured with Mellum2 12B and Qwen3.5 4B: 30/30 right calls each, all in a single call. | |
| 17 | + | |
| 18 | +### Save an answer | |
| 19 | + | |
| 20 | +- **`/save <file> [n]`** writes the last answer (or the n-th from the end) to a new file. It never overwrites an existing file. | |
| 21 | +- **`save_answer`**: the model saves an answer it already gave by sending only the file name, not the text again. It is turned on with `saveAnswerTool`, off by default. | |
| 22 | + | |
| 23 | +## Fixed | |
| 24 | + | |
| 25 | +- **`plan update` without a status** (#91): the model sent the same call about 30 times, trying to edit the tasks. The answer now quotes the call, names the missing field, says that `tasks` is not read by `update`, and gives a call that works. | |
| 26 | +- **`install.sh`** removes the old binary before copying the new one. macOS could kill a binary that was overwritten in place. | |
| 27 | + | |
| 28 | +## Demos | |
| 29 | + | |
| 30 | +- `declared-tools` (an "ops desk" with four declared tools); | |
| 31 | +- `programming-answers` and `programming-answers-2` (mini-tutorials on Mellum2.1 Thinking). | |
| 32 | + | |
| 33 | +## Docs | |
| 34 | + | |
| 35 | +- **New how-to guides** (EN + FR): Declare a custom tool, Save an answer. | |
| 36 | +- **New explanation** (EN + FR): Declared tools. | |
| new file mode 100644 | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | +# mini-me v0.6.0 | ||
| 2 | + | ||
| 3 | +*[Version française](v0.6.0.fr.md)* | ||
| 4 | + | ||
| 5 | +Your own tools declared in the agent file, and answers saved to a file without being written again. | ||
| 6 | + | ||
| 7 | +## New | ||
| 8 | + | ||
| 9 | +### Declared tools (#88) | ||
| 10 | + | ||
| 11 | +- **A `tools:` block** declares your own tools. Each one is a command, run as an argument list and **never through a shell**. | ||
| 12 | +- **Parameters** can be `string`, `enum`, `boolean`, `integer` or `number`. `"3"` and `"true"` are accepted for numbers and booleans. | ||
| 13 | +- **Invalid calls** are answered in text, quoting the call back. | ||
| 14 | +- **Consent follows `yolo`.** | ||
| 15 | +- **Names**: built-in tool names are reserved, even when the tool is turned off. A clash with an MCP tool is a warning. | ||
| 16 | +- Measured with Mellum2 12B and Qwen3.5 4B: 30/30 right calls each, all in a single call. | ||
| 17 | + | ||
| 18 | +### Save an answer | ||
| 19 | + | ||
| 20 | +- **`/save <file> [n]`** writes the last answer (or the n-th from the end) to a new file. It never overwrites an existing file. | ||
| 21 | +- **`save_answer`**: the model saves an answer it already gave by sending only the file name, not the text again. It is turned on with `saveAnswerTool`, off by default. | ||
| 22 | + | ||
| 23 | +## Fixed | ||
| 24 | + | ||
| 25 | +- **`plan update` without a status** (#91): the model sent the same call about 30 times, trying to edit the tasks. The answer now quotes the call, names the missing field, says that `tasks` is not read by `update`, and gives a call that works. | ||
| 26 | +- **`install.sh`** removes the old binary before copying the new one. macOS could kill a binary that was overwritten in place. | ||
| 27 | + | ||
| 28 | +## Demos | ||
| 29 | + | ||
| 30 | +- `declared-tools` (an "ops desk" with four declared tools); | ||
| 31 | +- `programming-answers` and `programming-answers-2` (mini-tutorials on Mellum2.1 Thinking). | ||
| 32 | + | ||
| 33 | +## Docs | ||
| 34 | + | ||
| 35 | +- **New how-to guides** (EN + FR): Declare a custom tool, Save an answer. | ||
| 36 | +- **New explanation** (EN + FR): Declared tools. | ||
added
release-notes/v0.6.0.fr.md +36 -0 | new file mode 100644 | ||
| @@ -0,0 +1,36 @@ | ||
| 1 | +# mini-me v0.6.0 | |
| 2 | + | |
| 3 | +*[English version](v0.6.0.en.md)* | |
| 4 | + | |
| 5 | +Vos propres outils déclarés dans le fichier de l'agent, et des réponses enregistrées dans un fichier sans être réécrites. | |
| 6 | + | |
| 7 | +## Nouveautés | |
| 8 | + | |
| 9 | +### Outils déclarés (#88) | |
| 10 | + | |
| 11 | +- **Un bloc `tools:`** déclare vos propres outils. Chacun est une commande, lancée comme une liste d'arguments et **jamais à travers un shell**. | |
| 12 | +- **Les paramètres** peuvent être `string`, `enum`, `boolean`, `integer` ou `number`. `"3"` et `"true"` sont acceptés pour les nombres et les booléens. | |
| 13 | +- **Les appels invalides** reçoivent une réponse en texte qui cite l'appel. | |
| 14 | +- **Le consentement suit `yolo`.** | |
| 15 | +- **Noms** : les noms des outils intégrés sont réservés, même quand l'outil est désactivé. Un conflit avec un outil MCP donne un avertissement. | |
| 16 | +- Mesuré avec Mellum2 12B et Qwen3.5 4B : 30 appels justes sur 30 pour chacun, tous en un seul appel. | |
| 17 | + | |
| 18 | +### Enregistrer une réponse | |
| 19 | + | |
| 20 | +- **`/save <fichier> [n]`** écrit la dernière réponse (ou la n-ième en partant de la fin) dans un nouveau fichier. Il n'écrase jamais un fichier existant. | |
| 21 | +- **`save_answer`** : le modèle enregistre une réponse qu'il a déjà donnée en envoyant seulement le nom du fichier, pas le texte une seconde fois. Il s'active avec `saveAnswerTool`, désactivé par défaut. | |
| 22 | + | |
| 23 | +## Corrigé | |
| 24 | + | |
| 25 | +- **`plan update` sans statut** (#91) : le modèle envoyait le même appel une trentaine de fois en essayant de modifier les tâches. La réponse cite maintenant l'appel, nomme le champ manquant, dit que `tasks` n'est pas lu par `update`, et donne un appel qui marche. | |
| 26 | +- **`install.sh`** supprime l'ancien binaire avant de copier le nouveau. macOS pouvait tuer un binaire écrasé sur place. | |
| 27 | + | |
| 28 | +## Démos | |
| 29 | + | |
| 30 | +- `declared-tools` (un « guichet ops » avec quatre outils déclarés) ; | |
| 31 | +- `programming-answers` et `programming-answers-2` (des mini-tutoriels sur Mellum2.1 Thinking). | |
| 32 | + | |
| 33 | +## Documentation | |
| 34 | + | |
| 35 | +- **Nouveaux guides pratiques** (EN + FR) : Déclarer un outil personnalisé, Enregistrer une réponse. | |
| 36 | +- **Nouvelle explication** (EN + FR) : Les outils déclarés. | |
| new file mode 100644 | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | +# mini-me v0.6.0 | ||
| 2 | + | ||
| 3 | +*[English version](v0.6.0.en.md)* | ||
| 4 | + | ||
| 5 | +Vos propres outils déclarés dans le fichier de l'agent, et des réponses enregistrées dans un fichier sans être réécrites. | ||
| 6 | + | ||
| 7 | +## Nouveautés | ||
| 8 | + | ||
| 9 | +### Outils déclarés (#88) | ||
| 10 | + | ||
| 11 | +- **Un bloc `tools:`** déclare vos propres outils. Chacun est une commande, lancée comme une liste d'arguments et **jamais à travers un shell**. | ||
| 12 | +- **Les paramètres** peuvent être `string`, `enum`, `boolean`, `integer` ou `number`. `"3"` et `"true"` sont acceptés pour les nombres et les booléens. | ||
| 13 | +- **Les appels invalides** reçoivent une réponse en texte qui cite l'appel. | ||
| 14 | +- **Le consentement suit `yolo`.** | ||
| 15 | +- **Noms** : les noms des outils intégrés sont réservés, même quand l'outil est désactivé. Un conflit avec un outil MCP donne un avertissement. | ||
| 16 | +- Mesuré avec Mellum2 12B et Qwen3.5 4B : 30 appels justes sur 30 pour chacun, tous en un seul appel. | ||
| 17 | + | ||
| 18 | +### Enregistrer une réponse | ||
| 19 | + | ||
| 20 | +- **`/save <fichier> [n]`** écrit la dernière réponse (ou la n-ième en partant de la fin) dans un nouveau fichier. Il n'écrase jamais un fichier existant. | ||
| 21 | +- **`save_answer`** : le modèle enregistre une réponse qu'il a déjà donnée en envoyant seulement le nom du fichier, pas le texte une seconde fois. Il s'active avec `saveAnswerTool`, désactivé par défaut. | ||
| 22 | + | ||
| 23 | +## Corrigé | ||
| 24 | + | ||
| 25 | +- **`plan update` sans statut** (#91) : le modèle envoyait le même appel une trentaine de fois en essayant de modifier les tâches. La réponse cite maintenant l'appel, nomme le champ manquant, dit que `tasks` n'est pas lu par `update`, et donne un appel qui marche. | ||
| 26 | +- **`install.sh`** supprime l'ancien binaire avant de copier le nouveau. macOS pouvait tuer un binaire écrasé sur place. | ||
| 27 | + | ||
| 28 | +## Démos | ||
| 29 | + | ||
| 30 | +- `declared-tools` (un « guichet ops » avec quatre outils déclarés) ; | ||
| 31 | +- `programming-answers` et `programming-answers-2` (des mini-tutoriels sur Mellum2.1 Thinking). | ||
| 32 | + | ||
| 33 | +## Documentation | ||
| 34 | + | ||
| 35 | +- **Nouveaux guides pratiques** (EN + FR) : Déclarer un outil personnalisé, Enregistrer une réponse. | ||
| 36 | +- **Nouvelle explication** (EN + FR) : Les outils déclarés. | ||