wasm custom tools and hooks #41
Merged@k33g wants to merge
feature/wasm-custom-tools into mainMerged as
b8680ab3ae7d.75 files changed
modified
.tickets/issues/0046-add-builtin-tools-as-plugins-with-wasm.yaml +40 -5 | @@ -1,15 +1,18 @@ | ||
| 1 | 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. |
| 2 | 2 | id: 46 |
| 3 | 3 | title: Add builtin tools as plugins with wasm |
| 4 | -state: open | |
| 4 | +state: closed | |
| 5 | 5 | author: |
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-09-27T02:30:34.975Z |
| 9 | -updatedAt: 2026-10-21T00:00:00.000Z | |
| 9 | +updatedAt: 2026-10-21T23:59:59.000Z | |
| 10 | 10 | labels: [] |
| 11 | 11 | milestone: v0.9.0 |
| 12 | 12 | epic: Builtin tools |
| 13 | +related: | |
| 14 | + - 119 | |
| 15 | + - 120 | |
| 13 | 16 | body: | |
| 14 | 17 | example: safe / unsafe with wasm -> LlamaGuard |
| 15 | 18 | |
| @@ -25,8 +28,8 @@ tasks: | ||
| 25 | 28 | createdAt: 2026-10-20T07:42:43.652Z |
| 26 | 29 | updatedAt: 2026-10-21T00:00:00.000Z |
| 27 | 30 | - id: 2 |
| 28 | - title: "Experiment (not started): bash-guard as a WASM hook vs the Node one — time per call, size, capability set, author experience" | |
| 29 | - state: open | |
| 31 | + title: "Experiment (done in ticket 120): bash-guard as a WASM hook vs the Node one — time per call, size, capability set, author experience" | |
| 32 | + state: closed | |
| 30 | 33 | priority: none |
| 31 | 34 | author: |
| 32 | 35 | name: k33g |
| @@ -98,4 +101,36 @@ comments: | ||
| 98 | 101 | a WASM hook with a minimal capability set, compare time per call, |
| 99 | 102 | size and the hook author's experience with the Node version. If it |
| 100 | 103 | pays, extend to declared tools, then to the LlamaGuard example. |
| 101 | - | |
| 104 | + - id: 2 | |
| 105 | + author: | |
| 106 | + name: k33g | |
| 107 | + email: ph.charriere@gmail.com | |
| 108 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 109 | + body: | | |
| 110 | + First step done in ticket 119: declared tools as WASM modules (wazero, | |
| 111 | + TinyGo-built WASI commands, JSON on stdin, `read:` / `write:` folders, | |
| 112 | + nothing else visible), with demos/wasm-tools. Measured there: compile | |
| 113 | + 176–183 ms once per process, 0.2–0.5 ms per call (vs ~62 ms for a | |
| 114 | + Node hook, comment 1); binary +3.9 MB. Task 2 (bash-guard as a WASM | |
| 115 | + HOOK) is still open: hooks are not WASM yet. | |
| 116 | + - id: 3 | |
| 117 | + author: | |
| 118 | + name: k33g | |
| 119 | + email: ph.charriere@gmail.com | |
| 120 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 121 | + body: | | |
| 122 | + Task 2 done in ticket 120 (hooks as WASM modules). bash-guard as a | |
| 123 | + TinyGo module vs demos/hooks/hooks/bash-guard.js, same denied call, | |
| 124 | + through hooks.Runner, 20 runs after a first one, twice, linux/arm64: | |
| 125 | + - time per call: Node v22 median 65–67 ms (first 68–102 ms); WASM | |
| 126 | + median 0.56–0.58 ms, first run 234–248 ms (the compilation, once | |
| 127 | + per mm process); | |
| 128 | + - size: 954 bytes of JS (+ Node) vs a 770 KB module (regexp + JSON); | |
| 129 | + - capabilities: nothing but its stdin and MM_HOOK_EVENT, vs all the | |
| 130 | + user's rights; | |
| 131 | + - author experience: same program shape (JSON in, JSON out), plus a | |
| 132 | + TinyGo build step; paths must be clean inside a module. | |
| 133 | + What stays a script: hooks that list the working directory or need | |
| 134 | + the network (guard models). With 119 (tools) and 120 (hooks), what | |
| 135 | + this ticket proposed is covered; providers and front ends were judged | |
| 136 | + not useful as plugins (comment 1). | |
| @@ -1,15 +1,18 @@ | |||
| 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: 46 | 2 | id: 46 |
| 3 | title: Add builtin tools as plugins with wasm | 3 | title: Add builtin tools as plugins with wasm |
| 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-09-27T02:30:34.975Z | 8 | createdAt: 2026-09-27T02:30:34.975Z |
| 9 | -updatedAt: 2026-10-21T00:00:00.000Z | 9 | +updatedAt: 2026-10-21T23:59:59.000Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | milestone: v0.9.0 | 11 | milestone: v0.9.0 |
| 12 | epic: Builtin tools | 12 | epic: Builtin tools |
| 13 | +related: | ||
| 14 | + - 119 | ||
| 15 | + - 120 | ||
| 13 | body: | | 16 | body: | |
| 14 | example: safe / unsafe with wasm -> LlamaGuard | 17 | example: safe / unsafe with wasm -> LlamaGuard |
| 15 | 18 | ||
| @@ -25,8 +28,8 @@ tasks: | |||
| 25 | createdAt: 2026-10-20T07:42:43.652Z | 28 | createdAt: 2026-10-20T07:42:43.652Z |
| 26 | updatedAt: 2026-10-21T00:00:00.000Z | 29 | updatedAt: 2026-10-21T00:00:00.000Z |
| 27 | - id: 2 | 30 | - id: 2 |
| 28 | - title: "Experiment (not started): bash-guard as a WASM hook vs the Node one — time per call, size, capability set, author experience" | 31 | + title: "Experiment (done in ticket 120): bash-guard as a WASM hook vs the Node one — time per call, size, capability set, author experience" |
| 29 | - state: open | 32 | + state: closed |
| 30 | priority: none | 33 | priority: none |
| 31 | author: | 34 | author: |
| 32 | name: k33g | 35 | name: k33g |
| @@ -98,4 +101,36 @@ comments: | |||
| 98 | a WASM hook with a minimal capability set, compare time per call, | 101 | a WASM hook with a minimal capability set, compare time per call, |
| 99 | size and the hook author's experience with the Node version. If it | 102 | size and the hook author's experience with the Node version. If it |
| 100 | pays, extend to declared tools, then to the LlamaGuard example. | 103 | pays, extend to declared tools, then to the LlamaGuard example. |
| 101 | - | 104 | + - id: 2 |
| 105 | + author: | ||
| 106 | + name: k33g | ||
| 107 | + email: ph.charriere@gmail.com | ||
| 108 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 109 | + body: | | ||
| 110 | + First step done in ticket 119: declared tools as WASM modules (wazero, | ||
| 111 | + TinyGo-built WASI commands, JSON on stdin, `read:` / `write:` folders, | ||
| 112 | + nothing else visible), with demos/wasm-tools. Measured there: compile | ||
| 113 | + 176–183 ms once per process, 0.2–0.5 ms per call (vs ~62 ms for a | ||
| 114 | + Node hook, comment 1); binary +3.9 MB. Task 2 (bash-guard as a WASM | ||
| 115 | + HOOK) is still open: hooks are not WASM yet. | ||
| 116 | + - id: 3 | ||
| 117 | + author: | ||
| 118 | + name: k33g | ||
| 119 | + email: ph.charriere@gmail.com | ||
| 120 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 121 | + body: | | ||
| 122 | + Task 2 done in ticket 120 (hooks as WASM modules). bash-guard as a | ||
| 123 | + TinyGo module vs demos/hooks/hooks/bash-guard.js, same denied call, | ||
| 124 | + through hooks.Runner, 20 runs after a first one, twice, linux/arm64: | ||
| 125 | + - time per call: Node v22 median 65–67 ms (first 68–102 ms); WASM | ||
| 126 | + median 0.56–0.58 ms, first run 234–248 ms (the compilation, once | ||
| 127 | + per mm process); | ||
| 128 | + - size: 954 bytes of JS (+ Node) vs a 770 KB module (regexp + JSON); | ||
| 129 | + - capabilities: nothing but its stdin and MM_HOOK_EVENT, vs all the | ||
| 130 | + user's rights; | ||
| 131 | + - author experience: same program shape (JSON in, JSON out), plus a | ||
| 132 | + TinyGo build step; paths must be clean inside a module. | ||
| 133 | + What stays a script: hooks that list the working directory or need | ||
| 134 | + the network (guard models). With 119 (tools) and 120 (hooks), what | ||
| 135 | + this ticket proposed is covered; providers and front ends were judged | ||
| 136 | + not useful as plugins (comment 1). | ||
added
.tickets/issues/0116-how-to-use-mm-for-code-completion.yaml +76 -0 | new file mode 100644 | ||
| @@ -0,0 +1,76 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 116 | |
| 3 | +title: How to use mm for code completion? | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-21T11:38:42.420Z | |
| 9 | +updatedAt: 2026-10-21T23:59:59.000Z | |
| 10 | +labels: | |
| 11 | + - wontfix | |
| 12 | +body: | | |
| 13 | + Inline code completion in the IDEs (VS Code, Zed, …): ghost text while | |
| 14 | + typing, Copilot-style. Can mm provide it? | |
| 15 | +tasks: [] | |
| 16 | +comments: | |
| 17 | + - id: 1 | |
| 18 | + author: | |
| 19 | + name: k33g | |
| 20 | + email: ph.charriere@gmail.com | |
| 21 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 22 | + body: | | |
| 23 | + Answer (2026-10-21): wontfix — inline completion is not mm's job, and | |
| 24 | + ACP could not carry it anyway. The IDE's completion feature should | |
| 25 | + talk to a model server directly; mm stays in the agent panel. Taken | |
| 26 | + out of v0.9.0, labelled wontfix (user's decision). | |
| 27 | + | |
| 28 | + ## Why not mm | |
| 29 | + - The protocol. ACP (how mm talks to editors) is sessions and | |
| 30 | + prompts: no method or capability for inline completion was found; | |
| 31 | + the only "autocomplete" in ACP clients is slash-command completion | |
| 32 | + in the chat panel. Inline completion lives in the editor's own | |
| 33 | + completion feature (or LSP). | |
| 34 | + https://codecompanion.olimorris.dev/usage/acp-protocol | |
| 35 | + https://mintlify.com/agentclientprotocol/typescript-sdk/concepts/protocol-overview | |
| 36 | + - Latency and shape. A completion fires on every typing pause and | |
| 37 | + must answer in a few hundred ms, with a "prefix + suffix" | |
| 38 | + (fill-in-the-middle, FIM) prompt. An mm turn is a system prompt, | |
| 39 | + tool schemas, a history, sometimes several round trips: seconds. | |
| 40 | + | |
| 41 | + ## What the editors do today (web research, 2026-10-21, not tried here) | |
| 42 | + - Zed: since February 2026 "edit predictions" accept several | |
| 43 | + providers, Ollama among them; the official docs say it is set up | |
| 44 | + separately from the agent — so mm in the agent panel and a local | |
| 45 | + completion model coexist. | |
| 46 | + https://zed.dev/blog/edit-prediction-providers | |
| 47 | + https://zed.dev/docs/ai/use-a-local-model | |
| 48 | + https://www.alternativeto.net/news/2026/2/zed-editor-adds-multiple-edit-prediction-providers-and-pluggable-architecture/ | |
| 49 | + - VS Code: Continue's tab autocomplete with Ollama and a small code | |
| 50 | + model (e.g. qwen2.5-coder:1.5b) is the best documented path. One | |
| 51 | + single source claims Continue was acquired and its repo frozen in | |
| 52 | + June 2026 — it contradicts the July 2026 docs: to check. Other | |
| 53 | + extensions exist (Ollama Copilot). | |
| 54 | + https://gitea.varghacsongor.hu/GithubMirror/continue/src/commit/4fd276fbb7c8baad3feb0351df1acccfd21fb8d4/docs/customize/deep-dives/autocomplete.mdx | |
| 55 | + https://tech-insider.org/fr/ollama-vscode-assistant-ia-local-2026/ | |
| 56 | + https://awesome.ecosyste.ms/projects/github.com%2Fgnana997%2Follama-copilot | |
| 57 | + | |
| 58 | + ## Measured on llmman (this sandbox, 2026-10-21, Qwen3.5 0.8B) | |
| 59 | + | endpoint | result | | |
| 60 | + | /v1/completions (raw) | works: 0.11–0.17 s for 12 tokens, warm | | |
| 61 | + | /infill (llama.cpp FIM, used by llama.vim / llama.vscode) | 404, not exposed | | |
| 62 | + | /api/generate + "suffix" (Ollama FIM) | 400: llmman does not support the "suffix" field of /api/generate | | |
| 63 | + So today an editor's completion needs a real Ollama or llama-server | |
| 64 | + beside llmman; llmman is fine for the agent. Possible request to | |
| 65 | + llmman: accept "suffix" or expose /infill. | |
| 66 | + | |
| 67 | + ## If anything is done later | |
| 68 | + - Docs only (EN+FR): an explanation (why not mm; how mm and a | |
| 69 | + completion model share one editor) and a how-to (Zed edit | |
| 70 | + predictions / VS Code Continue on Ollama or llama-server with a | |
| 71 | + small FIM-trained model, mm in the agent panel). It needs a real | |
| 72 | + try in an editor — not possible in this sandbox. | |
| 73 | + - A separate idea, not this ticket: mm's possible contribution would | |
| 74 | + be project context (neighbouring files, LSP symbols, search_docs) | |
| 75 | + injected into a FIM prompt — what makes Copilot more than a bare | |
| 76 | + model. A project of its own. | |
| new file mode 100644 | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 116 | ||
| 3 | +title: How to use mm for code completion? | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-21T11:38:42.420Z | ||
| 9 | +updatedAt: 2026-10-21T23:59:59.000Z | ||
| 10 | +labels: | ||
| 11 | + - wontfix | ||
| 12 | +body: | | ||
| 13 | + Inline code completion in the IDEs (VS Code, Zed, …): ghost text while | ||
| 14 | + typing, Copilot-style. Can mm provide it? | ||
| 15 | +tasks: [] | ||
| 16 | +comments: | ||
| 17 | + - id: 1 | ||
| 18 | + author: | ||
| 19 | + name: k33g | ||
| 20 | + email: ph.charriere@gmail.com | ||
| 21 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 22 | + body: | | ||
| 23 | + Answer (2026-10-21): wontfix — inline completion is not mm's job, and | ||
| 24 | + ACP could not carry it anyway. The IDE's completion feature should | ||
| 25 | + talk to a model server directly; mm stays in the agent panel. Taken | ||
| 26 | + out of v0.9.0, labelled wontfix (user's decision). | ||
| 27 | + | ||
| 28 | + ## Why not mm | ||
| 29 | + - The protocol. ACP (how mm talks to editors) is sessions and | ||
| 30 | + prompts: no method or capability for inline completion was found; | ||
| 31 | + the only "autocomplete" in ACP clients is slash-command completion | ||
| 32 | + in the chat panel. Inline completion lives in the editor's own | ||
| 33 | + completion feature (or LSP). | ||
| 34 | + https://codecompanion.olimorris.dev/usage/acp-protocol | ||
| 35 | + https://mintlify.com/agentclientprotocol/typescript-sdk/concepts/protocol-overview | ||
| 36 | + - Latency and shape. A completion fires on every typing pause and | ||
| 37 | + must answer in a few hundred ms, with a "prefix + suffix" | ||
| 38 | + (fill-in-the-middle, FIM) prompt. An mm turn is a system prompt, | ||
| 39 | + tool schemas, a history, sometimes several round trips: seconds. | ||
| 40 | + | ||
| 41 | + ## What the editors do today (web research, 2026-10-21, not tried here) | ||
| 42 | + - Zed: since February 2026 "edit predictions" accept several | ||
| 43 | + providers, Ollama among them; the official docs say it is set up | ||
| 44 | + separately from the agent — so mm in the agent panel and a local | ||
| 45 | + completion model coexist. | ||
| 46 | + https://zed.dev/blog/edit-prediction-providers | ||
| 47 | + https://zed.dev/docs/ai/use-a-local-model | ||
| 48 | + https://www.alternativeto.net/news/2026/2/zed-editor-adds-multiple-edit-prediction-providers-and-pluggable-architecture/ | ||
| 49 | + - VS Code: Continue's tab autocomplete with Ollama and a small code | ||
| 50 | + model (e.g. qwen2.5-coder:1.5b) is the best documented path. One | ||
| 51 | + single source claims Continue was acquired and its repo frozen in | ||
| 52 | + June 2026 — it contradicts the July 2026 docs: to check. Other | ||
| 53 | + extensions exist (Ollama Copilot). | ||
| 54 | + https://gitea.varghacsongor.hu/GithubMirror/continue/src/commit/4fd276fbb7c8baad3feb0351df1acccfd21fb8d4/docs/customize/deep-dives/autocomplete.mdx | ||
| 55 | + https://tech-insider.org/fr/ollama-vscode-assistant-ia-local-2026/ | ||
| 56 | + https://awesome.ecosyste.ms/projects/github.com%2Fgnana997%2Follama-copilot | ||
| 57 | + | ||
| 58 | + ## Measured on llmman (this sandbox, 2026-10-21, Qwen3.5 0.8B) | ||
| 59 | + | endpoint | result | | ||
| 60 | + | /v1/completions (raw) | works: 0.11–0.17 s for 12 tokens, warm | | ||
| 61 | + | /infill (llama.cpp FIM, used by llama.vim / llama.vscode) | 404, not exposed | | ||
| 62 | + | /api/generate + "suffix" (Ollama FIM) | 400: llmman does not support the "suffix" field of /api/generate | | ||
| 63 | + So today an editor's completion needs a real Ollama or llama-server | ||
| 64 | + beside llmman; llmman is fine for the agent. Possible request to | ||
| 65 | + llmman: accept "suffix" or expose /infill. | ||
| 66 | + | ||
| 67 | + ## If anything is done later | ||
| 68 | + - Docs only (EN+FR): an explanation (why not mm; how mm and a | ||
| 69 | + completion model share one editor) and a how-to (Zed edit | ||
| 70 | + predictions / VS Code Continue on Ollama or llama-server with a | ||
| 71 | + small FIM-trained model, mm in the agent panel). It needs a real | ||
| 72 | + try in an editor — not possible in this sandbox. | ||
| 73 | + - A separate idea, not this ticket: mm's possible contribution would | ||
| 74 | + be project context (neighbouring files, LSP symbols, search_docs) | ||
| 75 | + injected into a FIM prompt — what makes Copilot more than a bare | ||
| 76 | + model. A project of its own. | ||
deleted
.tickets/issues/0116-how-to-use-mm-for-completion.yaml +0 -14 | deleted file mode 100644 | ||
| @@ -1,14 +0,0 @@ | ||
| 1 | -# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | -id: 116 | |
| 3 | -title: How to use mm for completion? | |
| 4 | -state: open | |
| 5 | -author: | |
| 6 | - name: k33g | |
| 7 | - email: ph.charriere@gmail.com | |
| 8 | -createdAt: 2026-10-21T11:38:42.420Z | |
| 9 | -updatedAt: 2026-10-21T11:44:57.748Z | |
| 10 | -labels: [] | |
| 11 | -milestone: v0.9.0 | |
| 12 | -body: "" | |
| 13 | -tasks: [] | |
| 14 | -comments: [] | |
| deleted file mode 100644 | |||
| @@ -1,14 +0,0 @@ | |||
| 1 | -# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | -id: 116 | ||
| 3 | -title: How to use mm for completion? | ||
| 4 | -state: open | ||
| 5 | -author: | ||
| 6 | - name: k33g | ||
| 7 | - email: ph.charriere@gmail.com | ||
| 8 | -createdAt: 2026-10-21T11:38:42.420Z | ||
| 9 | -updatedAt: 2026-10-21T11:44:57.748Z | ||
| 10 | -labels: [] | ||
| 11 | -milestone: v0.9.0 | ||
| 12 | -body: "" | ||
| 13 | -tasks: [] | ||
| 14 | -comments: [] | ||
added
.tickets/issues/0119-custom-tools-as-wasm-modules.yaml +123 -0 | new file mode 100644 | ||
| @@ -0,0 +1,123 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 119 | |
| 3 | +title: Custom tools as WASM modules (wazero) | |
| 4 | +state: closed | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-21T23:59:59.000Z | |
| 9 | +updatedAt: 2026-10-21T23:59:59.000Z | |
| 10 | +labels: [] | |
| 11 | +milestone: v0.9.0 | |
| 12 | +epic: Builtin tools | |
| 13 | +related: | |
| 14 | + - 46 | |
| 15 | + - 120 | |
| 16 | +body: | | |
| 17 | + A declared tool (ticket 88, docs/en/how-to/declare-a-custom-tool.md) runs | |
| 18 | + a program with ALL the user's rights. This ticket adds a second kind of | |
| 19 | + declared tool: a WebAssembly module run inside mm by wazero (pure Go, so | |
| 20 | + mm stays a static, CGO-free binary) — the first measured step of ticket | |
| 21 | + 46's "sandbox by capability". | |
| 22 | + | |
| 23 | + User's decisions (2026-10-21): | |
| 24 | + - The module is an ordinary WASI command (TinyGo `-target=wasip1`, a | |
| 25 | + `main`). The call's arguments — checked, typed, defaults applied, as | |
| 26 | + for a `command` tool — arrive as ONE JSON object on stdin; stdout and | |
| 27 | + stderr are the result; a non-zero exit is a failure. Same shape as the | |
| 28 | + hooks. No argv template. | |
| 29 | + - Capabilities: nothing by default — no file, no environment variable, | |
| 30 | + no network. An entry may open project folders: `read: [notes]` | |
| 31 | + (read-only) and `write: [out]`. | |
| 32 | + - A demo in demos/, modules built with TinyGo. | |
| 33 | + | |
| 34 | + ```yaml | |
| 35 | + tools: | |
| 36 | + - name: word_stats | |
| 37 | + description: Count the words, lines and characters of a text. | |
| 38 | + parameters: | |
| 39 | + text: { type: string } | |
| 40 | + wasm: ./tools/word_stats.wasm | |
| 41 | + ``` | |
| 42 | +tasks: | |
| 43 | + - id: 1 | |
| 44 | + title: "config: `wasm`, `read`, `write` on a tools entry, validated (wasm XOR command)" | |
| 45 | + state: closed | |
| 46 | + priority: none | |
| 47 | + author: | |
| 48 | + name: k33g | |
| 49 | + email: ph.charriere@gmail.com | |
| 50 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 51 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 52 | + - id: 2 | |
| 53 | + title: "runtime: wazero runner (compile once, JSON on stdin, timeout, mounted folders), wired into the declared tool in every front end" | |
| 54 | + state: closed | |
| 55 | + priority: none | |
| 56 | + author: | |
| 57 | + name: k33g | |
| 58 | + email: ph.charriere@gmail.com | |
| 59 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 60 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 61 | + - id: 3 | |
| 62 | + title: "demos/wasm-tools: TinyGo modules, build script, agent.yaml, live run" | |
| 63 | + state: closed | |
| 64 | + priority: none | |
| 65 | + author: | |
| 66 | + name: k33g | |
| 67 | + email: ph.charriere@gmail.com | |
| 68 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 69 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 70 | + - id: 4 | |
| 71 | + title: "docs EN+FR (how-to, reference, explanation), diagram, ticket 46" | |
| 72 | + state: closed | |
| 73 | + priority: none | |
| 74 | + author: | |
| 75 | + name: k33g | |
| 76 | + email: ph.charriere@gmail.com | |
| 77 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 78 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 79 | +comments: | |
| 80 | + - id: 1 | |
| 81 | + author: | |
| 82 | + name: k33g | |
| 83 | + email: ph.charriere@gmail.com | |
| 84 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 85 | + body: | | |
| 86 | + Implemented on feature/wasm-custom-tools (uncommitted). | |
| 87 | + | |
| 88 | + - config (tools.go): `wasm` (relative to the agent file, resolved at | |
| 89 | + load, must exist), `read` / `write` (relative to the working | |
| 90 | + directory; refused: absolute, `.`, above it, twice; only with | |
| 91 | + wasm); command XOR wasm. | |
| 92 | + - internal/wasmtool (new): one wazero runtime per process, each module | |
| 93 | + compiled once on its first call, a fresh instance per call (several | |
| 94 | + may overlap). Sandbox: stdin = the JSON, args = the module name, no | |
| 95 | + env, real clock + crypto rand, folders mounted at /<name> (read-only | |
| 96 | + or not), 256 MiB memory cap, output kept up to 1 MiB, timeout and | |
| 97 | + cancellation stop the module. Errors as text, in runArgv's shape. | |
| 98 | + - tools/declared.go: a `job` (shown text + run) built by prepare(); | |
| 99 | + the line and evented paths are unchanged otherwise (consent, loop | |
| 100 | + detector, display). callValues now returns typed values: argv gets | |
| 101 | + them as text, wasm as JSON. | |
| 102 | + - Spike measurements (linux/arm64 sandbox): compile of a ~550 KB | |
| 103 | + TinyGo module 176–183 ms once; a call 0.2–0.5 ms. mm binary 38.97 | |
| 104 | + → 42.87 MB. CGO_ENABLED=0 cross-builds of the 5 release targets ok. | |
| 105 | + - Trap: TinyGo's WASI layer refuses "./notes/a.md" and ".." — modules | |
| 106 | + must clean paths (path.Join); documented. | |
| 107 | + - Tests: wasmtool (10, on testdata/probe.wasm, 145 KB, built from | |
| 108 | + testdata/probe/main.go without encoding/json), config (resolution, | |
| 109 | + refusals), tools (typed JSON, folders in the session directory, | |
| 110 | + consent and invalid calls). | |
| 111 | + - Demo demos/wasm-tools: Qwen3.5 4B on llmman, word_stats / | |
| 112 | + search_notes / read_note (read notes/) / save_note (write out/); | |
| 113 | + one live `mm -p` run each, all right tool first call; read_note | |
| 114 | + with ../agent.yaml refused by the sandbox. Prebuilt modules | |
| 115 | + committed (550–593 KB each), ./build.sh rebuilds them. | |
| 116 | + - Docs EN+FR: new how-to write-a-wasm-tool, reference configuration | |
| 117 | + (keys, errors, example) and tools (Wasm tools table), explanation | |
| 118 | + declared-tools (Wasm tools section; ticket 46 alternative | |
| 119 | + rewritten), declare-a-custom-tool variant, indexes, README demo | |
| 120 | + row, packages.drawio (wasmtool, wazero). | |
| 121 | + - Not done: the window and ACP paths seen live (unit-tested through | |
| 122 | + the evented path only); quality gate (qlty absent); modules from | |
| 123 | + other languages. | |
| new file mode 100644 | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 119 | ||
| 3 | +title: Custom tools as WASM modules (wazero) | ||
| 4 | +state: closed | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-21T23:59:59.000Z | ||
| 9 | +updatedAt: 2026-10-21T23:59:59.000Z | ||
| 10 | +labels: [] | ||
| 11 | +milestone: v0.9.0 | ||
| 12 | +epic: Builtin tools | ||
| 13 | +related: | ||
| 14 | + - 46 | ||
| 15 | + - 120 | ||
| 16 | +body: | | ||
| 17 | + A declared tool (ticket 88, docs/en/how-to/declare-a-custom-tool.md) runs | ||
| 18 | + a program with ALL the user's rights. This ticket adds a second kind of | ||
| 19 | + declared tool: a WebAssembly module run inside mm by wazero (pure Go, so | ||
| 20 | + mm stays a static, CGO-free binary) — the first measured step of ticket | ||
| 21 | + 46's "sandbox by capability". | ||
| 22 | + | ||
| 23 | + User's decisions (2026-10-21): | ||
| 24 | + - The module is an ordinary WASI command (TinyGo `-target=wasip1`, a | ||
| 25 | + `main`). The call's arguments — checked, typed, defaults applied, as | ||
| 26 | + for a `command` tool — arrive as ONE JSON object on stdin; stdout and | ||
| 27 | + stderr are the result; a non-zero exit is a failure. Same shape as the | ||
| 28 | + hooks. No argv template. | ||
| 29 | + - Capabilities: nothing by default — no file, no environment variable, | ||
| 30 | + no network. An entry may open project folders: `read: [notes]` | ||
| 31 | + (read-only) and `write: [out]`. | ||
| 32 | + - A demo in demos/, modules built with TinyGo. | ||
| 33 | + | ||
| 34 | + ```yaml | ||
| 35 | + tools: | ||
| 36 | + - name: word_stats | ||
| 37 | + description: Count the words, lines and characters of a text. | ||
| 38 | + parameters: | ||
| 39 | + text: { type: string } | ||
| 40 | + wasm: ./tools/word_stats.wasm | ||
| 41 | + ``` | ||
| 42 | +tasks: | ||
| 43 | + - id: 1 | ||
| 44 | + title: "config: `wasm`, `read`, `write` on a tools entry, validated (wasm XOR command)" | ||
| 45 | + state: closed | ||
| 46 | + priority: none | ||
| 47 | + author: | ||
| 48 | + name: k33g | ||
| 49 | + email: ph.charriere@gmail.com | ||
| 50 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 51 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 52 | + - id: 2 | ||
| 53 | + title: "runtime: wazero runner (compile once, JSON on stdin, timeout, mounted folders), wired into the declared tool in every front end" | ||
| 54 | + state: closed | ||
| 55 | + priority: none | ||
| 56 | + author: | ||
| 57 | + name: k33g | ||
| 58 | + email: ph.charriere@gmail.com | ||
| 59 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 60 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 61 | + - id: 3 | ||
| 62 | + title: "demos/wasm-tools: TinyGo modules, build script, agent.yaml, live run" | ||
| 63 | + state: closed | ||
| 64 | + priority: none | ||
| 65 | + author: | ||
| 66 | + name: k33g | ||
| 67 | + email: ph.charriere@gmail.com | ||
| 68 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 69 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 70 | + - id: 4 | ||
| 71 | + title: "docs EN+FR (how-to, reference, explanation), diagram, ticket 46" | ||
| 72 | + state: closed | ||
| 73 | + priority: none | ||
| 74 | + author: | ||
| 75 | + name: k33g | ||
| 76 | + email: ph.charriere@gmail.com | ||
| 77 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 78 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 79 | +comments: | ||
| 80 | + - id: 1 | ||
| 81 | + author: | ||
| 82 | + name: k33g | ||
| 83 | + email: ph.charriere@gmail.com | ||
| 84 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 85 | + body: | | ||
| 86 | + Implemented on feature/wasm-custom-tools (uncommitted). | ||
| 87 | + | ||
| 88 | + - config (tools.go): `wasm` (relative to the agent file, resolved at | ||
| 89 | + load, must exist), `read` / `write` (relative to the working | ||
| 90 | + directory; refused: absolute, `.`, above it, twice; only with | ||
| 91 | + wasm); command XOR wasm. | ||
| 92 | + - internal/wasmtool (new): one wazero runtime per process, each module | ||
| 93 | + compiled once on its first call, a fresh instance per call (several | ||
| 94 | + may overlap). Sandbox: stdin = the JSON, args = the module name, no | ||
| 95 | + env, real clock + crypto rand, folders mounted at /<name> (read-only | ||
| 96 | + or not), 256 MiB memory cap, output kept up to 1 MiB, timeout and | ||
| 97 | + cancellation stop the module. Errors as text, in runArgv's shape. | ||
| 98 | + - tools/declared.go: a `job` (shown text + run) built by prepare(); | ||
| 99 | + the line and evented paths are unchanged otherwise (consent, loop | ||
| 100 | + detector, display). callValues now returns typed values: argv gets | ||
| 101 | + them as text, wasm as JSON. | ||
| 102 | + - Spike measurements (linux/arm64 sandbox): compile of a ~550 KB | ||
| 103 | + TinyGo module 176–183 ms once; a call 0.2–0.5 ms. mm binary 38.97 | ||
| 104 | + → 42.87 MB. CGO_ENABLED=0 cross-builds of the 5 release targets ok. | ||
| 105 | + - Trap: TinyGo's WASI layer refuses "./notes/a.md" and ".." — modules | ||
| 106 | + must clean paths (path.Join); documented. | ||
| 107 | + - Tests: wasmtool (10, on testdata/probe.wasm, 145 KB, built from | ||
| 108 | + testdata/probe/main.go without encoding/json), config (resolution, | ||
| 109 | + refusals), tools (typed JSON, folders in the session directory, | ||
| 110 | + consent and invalid calls). | ||
| 111 | + - Demo demos/wasm-tools: Qwen3.5 4B on llmman, word_stats / | ||
| 112 | + search_notes / read_note (read notes/) / save_note (write out/); | ||
| 113 | + one live `mm -p` run each, all right tool first call; read_note | ||
| 114 | + with ../agent.yaml refused by the sandbox. Prebuilt modules | ||
| 115 | + committed (550–593 KB each), ./build.sh rebuilds them. | ||
| 116 | + - Docs EN+FR: new how-to write-a-wasm-tool, reference configuration | ||
| 117 | + (keys, errors, example) and tools (Wasm tools table), explanation | ||
| 118 | + declared-tools (Wasm tools section; ticket 46 alternative | ||
| 119 | + rewritten), declare-a-custom-tool variant, indexes, README demo | ||
| 120 | + row, packages.drawio (wasmtool, wazero). | ||
| 121 | + - Not done: the window and ACP paths seen live (unit-tested through | ||
| 122 | + the evented path only); quality gate (qlty absent); modules from | ||
| 123 | + other languages. | ||
added
.tickets/issues/0120-hooks-as-wasm-modules.yaml +123 -0 | new file mode 100644 | ||
| @@ -0,0 +1,123 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 120 | |
| 3 | +title: Hooks as WASM modules (wazero) | |
| 4 | +state: closed | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-21T23:59:59.000Z | |
| 9 | +updatedAt: 2026-10-21T23:59:59.000Z | |
| 10 | +labels: [] | |
| 11 | +milestone: v0.9.0 | |
| 12 | +epic: Builtin tools | |
| 13 | +related: | |
| 14 | + - 46 | |
| 15 | + - 119 | |
| 16 | +body: | | |
| 17 | + The same thing as ticket 119, for hooks: a hook entry may name a | |
| 18 | + WebAssembly module (`wasm:`) instead of a shell command (`command:`). | |
| 19 | + mm runs it itself with wazero, in the sandbox of ticket 119. | |
| 20 | + | |
| 21 | + A hook already has the right contract: one JSON object on stdin, an | |
| 22 | + optional JSON reply on stdout ({"action": "deny", "reason": …}). So the | |
| 23 | + module is an ordinary WASI command (TinyGo `-target=wasip1`), reading the | |
| 24 | + same input and printing the same reply as a script hook. | |
| 25 | + | |
| 26 | + - Nothing visible by default; `read:` / `write:` folders of the working | |
| 27 | + directory, same rules as tools. | |
| 28 | + - Environment: MM_HOOK_EVENT only (one module can serve several events, | |
| 29 | + as with scripts). Not MM_CONFIG_DIR: a host path means nothing inside | |
| 30 | + the sandbox. | |
| 31 | + - timeout, onError, matcher: unchanged. | |
| 32 | + - This is ticket 46's task 2: bash-guard as a WASM hook vs the Node one — | |
| 33 | + time per call, size, capabilities, author experience. | |
| 34 | + | |
| 35 | + ```yaml | |
| 36 | + hooks: | |
| 37 | + preToolCall: | |
| 38 | + - matcher: ^bash$ | |
| 39 | + wasm: ./hooks/bash_guard.wasm | |
| 40 | + onError: fail | |
| 41 | + ``` | |
| 42 | +tasks: | |
| 43 | + - id: 1 | |
| 44 | + title: "config: `wasm`, `read`, `write` on a hook entry (wasm XOR command), validation shared with tools" | |
| 45 | + state: closed | |
| 46 | + priority: none | |
| 47 | + author: | |
| 48 | + name: k33g | |
| 49 | + email: ph.charriere@gmail.com | |
| 50 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 51 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 52 | + - id: 2 | |
| 53 | + title: "runtime: wasmtool Exec (stdout and stderr apart, env), the hooks runner dispatches command / wasm" | |
| 54 | + state: closed | |
| 55 | + priority: none | |
| 56 | + author: | |
| 57 | + name: k33g | |
| 58 | + email: ph.charriere@gmail.com | |
| 59 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 60 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 61 | + - id: 3 | |
| 62 | + title: "demos/wasm-hooks: no_secrets, bash_guard, audit in TinyGo; measured against the Node hooks of demos/hooks" | |
| 63 | + state: closed | |
| 64 | + priority: none | |
| 65 | + author: | |
| 66 | + name: k33g | |
| 67 | + email: ph.charriere@gmail.com | |
| 68 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 69 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 70 | + - id: 4 | |
| 71 | + title: "docs EN+FR, diagram, tickets 46 and 119" | |
| 72 | + state: closed | |
| 73 | + priority: none | |
| 74 | + author: | |
| 75 | + name: k33g | |
| 76 | + email: ph.charriere@gmail.com | |
| 77 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 78 | + updatedAt: 2026-10-21T23:59:59.000Z | |
| 79 | +comments: | |
| 80 | + - id: 1 | |
| 81 | + author: | |
| 82 | + name: k33g | |
| 83 | + email: ph.charriere@gmail.com | |
| 84 | + createdAt: 2026-10-21T23:59:59.000Z | |
| 85 | + body: | | |
| 86 | + Implemented on feature/wasm-custom-tools (same branch as 119, | |
| 87 | + uncommitted). | |
| 88 | + | |
| 89 | + - config: Hook gets `wasm` (resolved against the agent file's | |
| 90 | + directory in resolveHooks), `read`, `write`; command XOR wasm; | |
| 91 | + IsWasm, Folders, Label (command or module file name — used by the | |
| 92 | + failure line and displayHooks). Folder and module checks are now | |
| 93 | + ONE function shared with tools (validateSandbox); its messages lost | |
| 94 | + their prefix, so the tool one reads `tools[N] (name): wasm: no | |
| 95 | + module at …` (docs updated). | |
| 96 | + - wasmtool: Exec(ctx, call, stdout, stderr) with typed ends | |
| 97 | + (TimeoutError, ErrCancelled, ExitError); Run (tools) is built on | |
| 98 | + it; Call.Env; `Default` runner shared by tools and hooks (a module | |
| 99 | + is compiled once per process). | |
| 100 | + - hooks: runHook dispatches to runCommand or runWasm; runWasm keeps | |
| 101 | + runCommand's caps (reply 1 MiB, stderr 300 bytes) and error texts | |
| 102 | + (`timed out after`, `cancelled`), a non-zero exit reads `exit code | |
| 103 | + <n>: <stderr>`. Env given to the module: MM_HOOK_EVENT only. | |
| 104 | + - Tests: hooks/wasm_test.go (reply honoured, env = the event variable | |
| 105 | + alone, folders, onError warn/fail + timeout), wasmtool Exec test, | |
| 106 | + config tests; the probe module got three hook replies (deny, | |
| 107 | + deny-env, deny-read). | |
| 108 | + - Demo demos/wasm-hooks: no_secrets, bash_guard, audit (write: [logs]) | |
| 109 | + in TinyGo, Gemma 4 E4B on llmman; live `mm -p`: password stopped, | |
| 110 | + rm -rf denied (old-drafts/ intact), audit lines written; | |
| 111 | + displayHooks lists the module names. add-context not ported: it | |
| 112 | + lists the working directory, which a module cannot open. | |
| 113 | + - Measured (ticket 46 task 2), bash guard through hooks.Runner, 20 | |
| 114 | + runs after a first, twice, linux/arm64: Node v22 first 68–102 ms, | |
| 115 | + median 65–67 ms; WASM first 234–248 ms (compile), median 0.56–0.58 | |
| 116 | + ms. Size: 954-byte .js vs 770 KB .wasm. | |
| 117 | + - Docs EN+FR: new how-to write-a-wasm-hook; reference hooks (entry | |
| 118 | + keys, Wasm hooks table, failures, report and display, validation | |
| 119 | + errors, example); explanation hooks (new section); guard how-to | |
| 120 | + variant; configuration (hooks row, tool error text); indexes; | |
| 121 | + README demo row; packages.drawio (hooks → wasmtool). | |
| 122 | + - Not done: hooks over ACP / in the window seen live (same runner, | |
| 123 | + unit-tested); quality gate (qlty absent). | |
| new file mode 100644 | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 120 | ||
| 3 | +title: Hooks as WASM modules (wazero) | ||
| 4 | +state: closed | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-21T23:59:59.000Z | ||
| 9 | +updatedAt: 2026-10-21T23:59:59.000Z | ||
| 10 | +labels: [] | ||
| 11 | +milestone: v0.9.0 | ||
| 12 | +epic: Builtin tools | ||
| 13 | +related: | ||
| 14 | + - 46 | ||
| 15 | + - 119 | ||
| 16 | +body: | | ||
| 17 | + The same thing as ticket 119, for hooks: a hook entry may name a | ||
| 18 | + WebAssembly module (`wasm:`) instead of a shell command (`command:`). | ||
| 19 | + mm runs it itself with wazero, in the sandbox of ticket 119. | ||
| 20 | + | ||
| 21 | + A hook already has the right contract: one JSON object on stdin, an | ||
| 22 | + optional JSON reply on stdout ({"action": "deny", "reason": …}). So the | ||
| 23 | + module is an ordinary WASI command (TinyGo `-target=wasip1`), reading the | ||
| 24 | + same input and printing the same reply as a script hook. | ||
| 25 | + | ||
| 26 | + - Nothing visible by default; `read:` / `write:` folders of the working | ||
| 27 | + directory, same rules as tools. | ||
| 28 | + - Environment: MM_HOOK_EVENT only (one module can serve several events, | ||
| 29 | + as with scripts). Not MM_CONFIG_DIR: a host path means nothing inside | ||
| 30 | + the sandbox. | ||
| 31 | + - timeout, onError, matcher: unchanged. | ||
| 32 | + - This is ticket 46's task 2: bash-guard as a WASM hook vs the Node one — | ||
| 33 | + time per call, size, capabilities, author experience. | ||
| 34 | + | ||
| 35 | + ```yaml | ||
| 36 | + hooks: | ||
| 37 | + preToolCall: | ||
| 38 | + - matcher: ^bash$ | ||
| 39 | + wasm: ./hooks/bash_guard.wasm | ||
| 40 | + onError: fail | ||
| 41 | + ``` | ||
| 42 | +tasks: | ||
| 43 | + - id: 1 | ||
| 44 | + title: "config: `wasm`, `read`, `write` on a hook entry (wasm XOR command), validation shared with tools" | ||
| 45 | + state: closed | ||
| 46 | + priority: none | ||
| 47 | + author: | ||
| 48 | + name: k33g | ||
| 49 | + email: ph.charriere@gmail.com | ||
| 50 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 51 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 52 | + - id: 2 | ||
| 53 | + title: "runtime: wasmtool Exec (stdout and stderr apart, env), the hooks runner dispatches command / wasm" | ||
| 54 | + state: closed | ||
| 55 | + priority: none | ||
| 56 | + author: | ||
| 57 | + name: k33g | ||
| 58 | + email: ph.charriere@gmail.com | ||
| 59 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 60 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 61 | + - id: 3 | ||
| 62 | + title: "demos/wasm-hooks: no_secrets, bash_guard, audit in TinyGo; measured against the Node hooks of demos/hooks" | ||
| 63 | + state: closed | ||
| 64 | + priority: none | ||
| 65 | + author: | ||
| 66 | + name: k33g | ||
| 67 | + email: ph.charriere@gmail.com | ||
| 68 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 69 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 70 | + - id: 4 | ||
| 71 | + title: "docs EN+FR, diagram, tickets 46 and 119" | ||
| 72 | + state: closed | ||
| 73 | + priority: none | ||
| 74 | + author: | ||
| 75 | + name: k33g | ||
| 76 | + email: ph.charriere@gmail.com | ||
| 77 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 78 | + updatedAt: 2026-10-21T23:59:59.000Z | ||
| 79 | +comments: | ||
| 80 | + - id: 1 | ||
| 81 | + author: | ||
| 82 | + name: k33g | ||
| 83 | + email: ph.charriere@gmail.com | ||
| 84 | + createdAt: 2026-10-21T23:59:59.000Z | ||
| 85 | + body: | | ||
| 86 | + Implemented on feature/wasm-custom-tools (same branch as 119, | ||
| 87 | + uncommitted). | ||
| 88 | + | ||
| 89 | + - config: Hook gets `wasm` (resolved against the agent file's | ||
| 90 | + directory in resolveHooks), `read`, `write`; command XOR wasm; | ||
| 91 | + IsWasm, Folders, Label (command or module file name — used by the | ||
| 92 | + failure line and displayHooks). Folder and module checks are now | ||
| 93 | + ONE function shared with tools (validateSandbox); its messages lost | ||
| 94 | + their prefix, so the tool one reads `tools[N] (name): wasm: no | ||
| 95 | + module at …` (docs updated). | ||
| 96 | + - wasmtool: Exec(ctx, call, stdout, stderr) with typed ends | ||
| 97 | + (TimeoutError, ErrCancelled, ExitError); Run (tools) is built on | ||
| 98 | + it; Call.Env; `Default` runner shared by tools and hooks (a module | ||
| 99 | + is compiled once per process). | ||
| 100 | + - hooks: runHook dispatches to runCommand or runWasm; runWasm keeps | ||
| 101 | + runCommand's caps (reply 1 MiB, stderr 300 bytes) and error texts | ||
| 102 | + (`timed out after`, `cancelled`), a non-zero exit reads `exit code | ||
| 103 | + <n>: <stderr>`. Env given to the module: MM_HOOK_EVENT only. | ||
| 104 | + - Tests: hooks/wasm_test.go (reply honoured, env = the event variable | ||
| 105 | + alone, folders, onError warn/fail + timeout), wasmtool Exec test, | ||
| 106 | + config tests; the probe module got three hook replies (deny, | ||
| 107 | + deny-env, deny-read). | ||
| 108 | + - Demo demos/wasm-hooks: no_secrets, bash_guard, audit (write: [logs]) | ||
| 109 | + in TinyGo, Gemma 4 E4B on llmman; live `mm -p`: password stopped, | ||
| 110 | + rm -rf denied (old-drafts/ intact), audit lines written; | ||
| 111 | + displayHooks lists the module names. add-context not ported: it | ||
| 112 | + lists the working directory, which a module cannot open. | ||
| 113 | + - Measured (ticket 46 task 2), bash guard through hooks.Runner, 20 | ||
| 114 | + runs after a first, twice, linux/arm64: Node v22 first 68–102 ms, | ||
| 115 | + median 65–67 ms; WASM first 234–248 ms (compile), median 0.56–0.58 | ||
| 116 | + ms. Size: 954-byte .js vs 770 KB .wasm. | ||
| 117 | + - Docs EN+FR: new how-to write-a-wasm-hook; reference hooks (entry | ||
| 118 | + keys, Wasm hooks table, failures, report and display, validation | ||
| 119 | + errors, example); explanation hooks (new section); guard how-to | ||
| 120 | + variant; configuration (hooks row, tool error text); indexes; | ||
| 121 | + README demo row; packages.drawio (hooks → wasmtool). | ||
| 122 | + - Not done: hooks over ACP / in the window seen live (same runner, | ||
| 123 | + unit-tested); quality gate (qlty absent). | ||
modified
README.md +2 -0 | @@ -161,6 +161,8 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h | ||
| 161 | 161 | | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results | |
| 162 | 162 | | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find | |
| 163 | 163 | | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results | |
| 164 | +| `demos/wasm-tools/` | a notes desk acting only through four WebAssembly tools built with TinyGo and run by mm in a sandbox: one pure computation, two that may only read `notes/`, one that may only write `out/`; Qwen3.5 4B on llmman | | |
| 165 | +| `demos/wasm-hooks/` | the hooks of `demos/hooks` (secret filter, `bash` guard, audit log) rewritten as WebAssembly modules built with TinyGo and run by mm in a sandbox — nothing to install; Gemma 4 E4B on llmman, measured against the Node versions | | |
| 164 | 166 | |
| 165 | 167 | ## Learn more |
| 166 | 168 | |
| @@ -161,6 +161,8 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h | |||
| 161 | | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results | | 161 | | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results | |
| 162 | | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find | | 162 | | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find | |
| 163 | | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results | | 163 | | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results | |
| 164 | +| `demos/wasm-tools/` | a notes desk acting only through four WebAssembly tools built with TinyGo and run by mm in a sandbox: one pure computation, two that may only read `notes/`, one that may only write `out/`; Qwen3.5 4B on llmman | | ||
| 165 | +| `demos/wasm-hooks/` | the hooks of `demos/hooks` (secret filter, `bash` guard, audit log) rewritten as WebAssembly modules built with TinyGo and run by mm in a sandbox — nothing to install; Gemma 4 E4B on llmman, measured against the Node versions | | ||
| 164 | 166 | ||
| 165 | ## Learn more | 167 | ## Learn more |
| 166 | 168 | ||
added
demos/wasm-hooks/README.en.md +69 -0 | new file mode 100644 | ||
| @@ -0,0 +1,69 @@ | ||
| 1 | +# WASM hooks — the hooks demo, as WebAssembly modules | |
| 2 | + | |
| 3 | +*[Version française](README.fr.md)* | |
| 4 | + | |
| 5 | +The agent of [`demos/hooks`](../hooks/README.en.md) — a small shell assistant — with three of its hooks rewritten in Go and built with TinyGo as **WebAssembly modules**. mm runs them itself (wazero), in a sandbox: no file, no network, of the environment only `MM_HOOK_EVENT` — except the folders an entry opens. **Nothing to install beside mm**: no Node.js. | |
| 6 | + | |
| 7 | +| Hook | Event | What it may see | What it shows | | |
| 8 | +|---|---|---|---| | |
| 9 | +| `hooks/no_secrets.wasm` | `preGeneration` | nothing but the question | **stop** a turn: a question holding a password or an API key never reaches the model | | |
| 10 | +| `hooks/bash_guard.wasm` | `preToolCall` (`matcher: ^bash$`) | nothing but the call | **deny** a tool call: `rm -r`, `sudo`, `curl … \| sh` never run, and the model reads why | | |
| 11 | +| `hooks/audit.wasm` | `postToolCall` + `postGeneration` | `logs/`, read-write | **observe**: one JSON line per tool call and per turn in `logs/hooks-audit.jsonl` | | |
| 12 | + | |
| 13 | +`add-context.js` is not ported: it lists the working directory, and a module can open folders inside it, not the directory itself. | |
| 14 | + | |
| 15 | +Model: **Gemma 4 E4B** (`hf.co/unsloth/gemma-4-E4B-it-GGUF:Q4_K_M`) on **llmman**. | |
| 16 | + | |
| 17 | +## Run | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +cd demos/wasm-hooks/playground | |
| 21 | +mm ../agent.yaml | |
| 22 | +``` | |
| 23 | + | |
| 24 | +The modules are relative to `agent.yaml`; `logs/` is taken from the working directory, `playground/`. The `.wasm` files in `hooks/` are ready to use; to rebuild them after changing `hooks-src/`, with [TinyGo](https://tinygo.org) installed: `./build.sh`. | |
| 25 | + | |
| 26 | +## Seen on 2026-10-21 | |
| 27 | + | |
| 28 | +`mm -p "<question>" ../agent.yaml` from `playground/`: | |
| 29 | + | |
| 30 | +``` | |
| 31 | +> My password: hunter2 — can you store it in a file? | |
| 32 | +🤖 🔐 Your message looks like it contains a password. It was not sent to the model. Remove it and ask again. | |
| 33 | + | |
| 34 | +> Delete the old-drafts directory with rm -rf. | |
| 35 | +🪝 bash [denied by a hook: recursive deletion (rm -r) is not allowed] | |
| 36 | +🤖 The command was refused because recursive deletion (`rm -r`) is not allowed by a hook. | |
| 37 | + | |
| 38 | +> How many lines are in todo.txt? | |
| 39 | +🛠️ bash: wc -l todo.txt | |
| 40 | + │ 2 todo.txt | |
| 41 | +🤖 There are 2 lines in todo.txt. | |
| 42 | +``` | |
| 43 | + | |
| 44 | +`old-drafts/` was still there, and `logs/hooks-audit.jsonl` held one line per turn and one for the `wc` call. With `displayHooks: true`, the second question lists: | |
| 45 | + | |
| 46 | +``` | |
| 47 | + 🪝 1. preGeneration · no_secrets.wasm → continue | |
| 48 | + 🪝 2. preToolCall bash · bash_guard.wasm → deny | |
| 49 | + 🪝 3. postGeneration · audit.wasm → continue | |
| 50 | +``` | |
| 51 | + | |
| 52 | +## Against the Node.js hooks | |
| 53 | + | |
| 54 | +The `bash` guard, the same three rules, run through mm's hooks runner on the same denied call, 20 runs after a first one, twice (linux/arm64 sandbox): | |
| 55 | + | |
| 56 | +| | First run | Median of 20 | Size | | |
| 57 | +|---|---|---|---| | |
| 58 | +| `demos/hooks/hooks/bash-guard.js` (Node v22) | 68–102 ms | 65–67 ms | 954 bytes, plus Node.js | | |
| 59 | +| `hooks/bash_guard.wasm` | 234–248 ms (the compilation, once per mm process) | 0.56–0.58 ms | 770 KB | | |
| 60 | + | |
| 61 | +## Files | |
| 62 | + | |
| 63 | +- `agent.yaml` — the agent and its `hooks:` (`wasm:`, `write:`) | |
| 64 | +- `hooks-src/` — the Go sources of the hooks (their own Go module, built by TinyGo) | |
| 65 | +- `hooks/` — the built modules | |
| 66 | +- `playground/` — where the agent works; `logs/` is written by the audit hook (git-ignored) | |
| 67 | +- `build.sh` — rebuilds `hooks/` from `hooks-src/` | |
| 68 | + | |
| 69 | +See [How to write a hook as a WebAssembly module](../../docs/en/how-to/write-a-wasm-hook.md). | |
| new file mode 100644 | |||
| @@ -0,0 +1,69 @@ | |||
| 1 | +# WASM hooks — the hooks demo, as WebAssembly modules | ||
| 2 | + | ||
| 3 | +*[Version française](README.fr.md)* | ||
| 4 | + | ||
| 5 | +The agent of [`demos/hooks`](../hooks/README.en.md) — a small shell assistant — with three of its hooks rewritten in Go and built with TinyGo as **WebAssembly modules**. mm runs them itself (wazero), in a sandbox: no file, no network, of the environment only `MM_HOOK_EVENT` — except the folders an entry opens. **Nothing to install beside mm**: no Node.js. | ||
| 6 | + | ||
| 7 | +| Hook | Event | What it may see | What it shows | | ||
| 8 | +|---|---|---|---| | ||
| 9 | +| `hooks/no_secrets.wasm` | `preGeneration` | nothing but the question | **stop** a turn: a question holding a password or an API key never reaches the model | | ||
| 10 | +| `hooks/bash_guard.wasm` | `preToolCall` (`matcher: ^bash$`) | nothing but the call | **deny** a tool call: `rm -r`, `sudo`, `curl … \| sh` never run, and the model reads why | | ||
| 11 | +| `hooks/audit.wasm` | `postToolCall` + `postGeneration` | `logs/`, read-write | **observe**: one JSON line per tool call and per turn in `logs/hooks-audit.jsonl` | | ||
| 12 | + | ||
| 13 | +`add-context.js` is not ported: it lists the working directory, and a module can open folders inside it, not the directory itself. | ||
| 14 | + | ||
| 15 | +Model: **Gemma 4 E4B** (`hf.co/unsloth/gemma-4-E4B-it-GGUF:Q4_K_M`) on **llmman**. | ||
| 16 | + | ||
| 17 | +## Run | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +cd demos/wasm-hooks/playground | ||
| 21 | +mm ../agent.yaml | ||
| 22 | +``` | ||
| 23 | + | ||
| 24 | +The modules are relative to `agent.yaml`; `logs/` is taken from the working directory, `playground/`. The `.wasm` files in `hooks/` are ready to use; to rebuild them after changing `hooks-src/`, with [TinyGo](https://tinygo.org) installed: `./build.sh`. | ||
| 25 | + | ||
| 26 | +## Seen on 2026-10-21 | ||
| 27 | + | ||
| 28 | +`mm -p "<question>" ../agent.yaml` from `playground/`: | ||
| 29 | + | ||
| 30 | +``` | ||
| 31 | +> My password: hunter2 — can you store it in a file? | ||
| 32 | +🤖 🔐 Your message looks like it contains a password. It was not sent to the model. Remove it and ask again. | ||
| 33 | + | ||
| 34 | +> Delete the old-drafts directory with rm -rf. | ||
| 35 | +🪝 bash [denied by a hook: recursive deletion (rm -r) is not allowed] | ||
| 36 | +🤖 The command was refused because recursive deletion (`rm -r`) is not allowed by a hook. | ||
| 37 | + | ||
| 38 | +> How many lines are in todo.txt? | ||
| 39 | +🛠️ bash: wc -l todo.txt | ||
| 40 | + │ 2 todo.txt | ||
| 41 | +🤖 There are 2 lines in todo.txt. | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +`old-drafts/` was still there, and `logs/hooks-audit.jsonl` held one line per turn and one for the `wc` call. With `displayHooks: true`, the second question lists: | ||
| 45 | + | ||
| 46 | +``` | ||
| 47 | + 🪝 1. preGeneration · no_secrets.wasm → continue | ||
| 48 | + 🪝 2. preToolCall bash · bash_guard.wasm → deny | ||
| 49 | + 🪝 3. postGeneration · audit.wasm → continue | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +## Against the Node.js hooks | ||
| 53 | + | ||
| 54 | +The `bash` guard, the same three rules, run through mm's hooks runner on the same denied call, 20 runs after a first one, twice (linux/arm64 sandbox): | ||
| 55 | + | ||
| 56 | +| | First run | Median of 20 | Size | | ||
| 57 | +|---|---|---|---| | ||
| 58 | +| `demos/hooks/hooks/bash-guard.js` (Node v22) | 68–102 ms | 65–67 ms | 954 bytes, plus Node.js | | ||
| 59 | +| `hooks/bash_guard.wasm` | 234–248 ms (the compilation, once per mm process) | 0.56–0.58 ms | 770 KB | | ||
| 60 | + | ||
| 61 | +## Files | ||
| 62 | + | ||
| 63 | +- `agent.yaml` — the agent and its `hooks:` (`wasm:`, `write:`) | ||
| 64 | +- `hooks-src/` — the Go sources of the hooks (their own Go module, built by TinyGo) | ||
| 65 | +- `hooks/` — the built modules | ||
| 66 | +- `playground/` — where the agent works; `logs/` is written by the audit hook (git-ignored) | ||
| 67 | +- `build.sh` — rebuilds `hooks/` from `hooks-src/` | ||
| 68 | + | ||
| 69 | +See [How to write a hook as a WebAssembly module](../../docs/en/how-to/write-a-wasm-hook.md). | ||
added
demos/wasm-hooks/README.fr.md +69 -0 | new file mode 100644 | ||
| @@ -0,0 +1,69 @@ | ||
| 1 | +# Hooks WASM — la démo des hooks, en modules WebAssembly | |
| 2 | + | |
| 3 | +*[English version](README.en.md)* | |
| 4 | + | |
| 5 | +L'agent de [`demos/hooks`](../hooks/README.fr.md) — un petit assistant shell — avec trois de ses hooks réécrits en Go et construits avec TinyGo en **modules WebAssembly**. mm les exécute lui-même (wazero), dans un bac à sable : aucun fichier, pas de réseau, de l'environnement seulement `MM_HOOK_EVENT` — sauf les dossiers qu'une entrée ouvre. **Rien à installer à côté de mm** : pas de Node.js. | |
| 6 | + | |
| 7 | +| Hook | Événement | Ce qu'il peut voir | Ce qu'il montre | | |
| 8 | +|---|---|---|---| | |
| 9 | +| `hooks/no_secrets.wasm` | `preGeneration` | rien d'autre que la question | **arrêter** un tour : une question qui contient un mot de passe ou une clé d'API n'atteint jamais le modèle | | |
| 10 | +| `hooks/bash_guard.wasm` | `preToolCall` (`matcher: ^bash$`) | rien d'autre que l'appel | **refuser** un appel d'outil : `rm -r`, `sudo`, `curl … \| sh` ne s'exécutent jamais, et le modèle lit pourquoi | | |
| 11 | +| `hooks/audit.wasm` | `postToolCall` + `postGeneration` | `logs/`, en lecture-écriture | **observer** : une ligne JSON par appel d'outil et par tour dans `logs/hooks-audit.jsonl` | | |
| 12 | + | |
| 13 | +`add-context.js` n'est pas porté : il liste le répertoire de travail, et un module peut ouvrir des dossiers à l'intérieur, pas le répertoire lui-même. | |
| 14 | + | |
| 15 | +Modèle : **Gemma 4 E4B** (`hf.co/unsloth/gemma-4-E4B-it-GGUF:Q4_K_M`) sur **llmman**. | |
| 16 | + | |
| 17 | +## Lancer | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +cd demos/wasm-hooks/playground | |
| 21 | +mm ../agent.yaml | |
| 22 | +``` | |
| 23 | + | |
| 24 | +Les modules sont relatifs à `agent.yaml` ; `logs/` est pris dans le répertoire de travail, `playground/`. Les fichiers `.wasm` de `hooks/` sont prêts à l'emploi ; pour les reconstruire après avoir modifié `hooks-src/`, avec [TinyGo](https://tinygo.org) installé : `./build.sh`. | |
| 25 | + | |
| 26 | +## Vu le 2026-10-21 | |
| 27 | + | |
| 28 | +`mm -p "<question>" ../agent.yaml` depuis `playground/` : | |
| 29 | + | |
| 30 | +``` | |
| 31 | +> My password: hunter2 — can you store it in a file? | |
| 32 | +🤖 🔐 Your message looks like it contains a password. It was not sent to the model. Remove it and ask again. | |
| 33 | + | |
| 34 | +> Delete the old-drafts directory with rm -rf. | |
| 35 | +🪝 bash [denied by a hook: recursive deletion (rm -r) is not allowed] | |
| 36 | +🤖 The command was refused because recursive deletion (`rm -r`) is not allowed by a hook. | |
| 37 | + | |
| 38 | +> How many lines are in todo.txt? | |
| 39 | +🛠️ bash: wc -l todo.txt | |
| 40 | + │ 2 todo.txt | |
| 41 | +🤖 There are 2 lines in todo.txt. | |
| 42 | +``` | |
| 43 | + | |
| 44 | +`old-drafts/` était toujours là, et `logs/hooks-audit.jsonl` contenait une ligne par tour et une pour l'appel `wc`. Avec `displayHooks: true`, la deuxième question liste : | |
| 45 | + | |
| 46 | +``` | |
| 47 | + 🪝 1. preGeneration · no_secrets.wasm → continue | |
| 48 | + 🪝 2. preToolCall bash · bash_guard.wasm → deny | |
| 49 | + 🪝 3. postGeneration · audit.wasm → continue | |
| 50 | +``` | |
| 51 | + | |
| 52 | +## Face aux hooks Node.js | |
| 53 | + | |
| 54 | +La garde `bash`, les trois mêmes règles, exécutée par le lanceur de hooks de mm sur le même appel refusé, 20 exécutions après une première, deux fois (sandbox linux/arm64) : | |
| 55 | + | |
| 56 | +| | Première exécution | Médiane de 20 | Taille | | |
| 57 | +|---|---|---|---| | |
| 58 | +| `demos/hooks/hooks/bash-guard.js` (Node v22) | 68 à 102 ms | 65 à 67 ms | 954 octets, plus Node.js | | |
| 59 | +| `hooks/bash_guard.wasm` | 234 à 248 ms (la compilation, une fois par processus mm) | 0,56 à 0,58 ms | 770 Ko | | |
| 60 | + | |
| 61 | +## Fichiers | |
| 62 | + | |
| 63 | +- `agent.yaml` — l'agent et ses `hooks:` (`wasm:`, `write:`) | |
| 64 | +- `hooks-src/` — les sources Go des hooks (leur propre module Go, construit par TinyGo) | |
| 65 | +- `hooks/` — les modules construits | |
| 66 | +- `playground/` — là où l'agent travaille ; `logs/` est écrit par le hook d'audit (ignoré par git) | |
| 67 | +- `build.sh` — reconstruit `hooks/` à partir de `hooks-src/` | |
| 68 | + | |
| 69 | +Voir [Écrire un hook sous forme de module WebAssembly](../../docs/fr/how-to/write-a-wasm-hook.md). | |
| new file mode 100644 | |||
| @@ -0,0 +1,69 @@ | |||
| 1 | +# Hooks WASM — la démo des hooks, en modules WebAssembly | ||
| 2 | + | ||
| 3 | +*[English version](README.en.md)* | ||
| 4 | + | ||
| 5 | +L'agent de [`demos/hooks`](../hooks/README.fr.md) — un petit assistant shell — avec trois de ses hooks réécrits en Go et construits avec TinyGo en **modules WebAssembly**. mm les exécute lui-même (wazero), dans un bac à sable : aucun fichier, pas de réseau, de l'environnement seulement `MM_HOOK_EVENT` — sauf les dossiers qu'une entrée ouvre. **Rien à installer à côté de mm** : pas de Node.js. | ||
| 6 | + | ||
| 7 | +| Hook | Événement | Ce qu'il peut voir | Ce qu'il montre | | ||
| 8 | +|---|---|---|---| | ||
| 9 | +| `hooks/no_secrets.wasm` | `preGeneration` | rien d'autre que la question | **arrêter** un tour : une question qui contient un mot de passe ou une clé d'API n'atteint jamais le modèle | | ||
| 10 | +| `hooks/bash_guard.wasm` | `preToolCall` (`matcher: ^bash$`) | rien d'autre que l'appel | **refuser** un appel d'outil : `rm -r`, `sudo`, `curl … \| sh` ne s'exécutent jamais, et le modèle lit pourquoi | | ||
| 11 | +| `hooks/audit.wasm` | `postToolCall` + `postGeneration` | `logs/`, en lecture-écriture | **observer** : une ligne JSON par appel d'outil et par tour dans `logs/hooks-audit.jsonl` | | ||
| 12 | + | ||
| 13 | +`add-context.js` n'est pas porté : il liste le répertoire de travail, et un module peut ouvrir des dossiers à l'intérieur, pas le répertoire lui-même. | ||
| 14 | + | ||
| 15 | +Modèle : **Gemma 4 E4B** (`hf.co/unsloth/gemma-4-E4B-it-GGUF:Q4_K_M`) sur **llmman**. | ||
| 16 | + | ||
| 17 | +## Lancer | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +cd demos/wasm-hooks/playground | ||
| 21 | +mm ../agent.yaml | ||
| 22 | +``` | ||
| 23 | + | ||
| 24 | +Les modules sont relatifs à `agent.yaml` ; `logs/` est pris dans le répertoire de travail, `playground/`. Les fichiers `.wasm` de `hooks/` sont prêts à l'emploi ; pour les reconstruire après avoir modifié `hooks-src/`, avec [TinyGo](https://tinygo.org) installé : `./build.sh`. | ||
| 25 | + | ||
| 26 | +## Vu le 2026-10-21 | ||
| 27 | + | ||
| 28 | +`mm -p "<question>" ../agent.yaml` depuis `playground/` : | ||
| 29 | + | ||
| 30 | +``` | ||
| 31 | +> My password: hunter2 — can you store it in a file? | ||
| 32 | +🤖 🔐 Your message looks like it contains a password. It was not sent to the model. Remove it and ask again. | ||
| 33 | + | ||
| 34 | +> Delete the old-drafts directory with rm -rf. | ||
| 35 | +🪝 bash [denied by a hook: recursive deletion (rm -r) is not allowed] | ||
| 36 | +🤖 The command was refused because recursive deletion (`rm -r`) is not allowed by a hook. | ||
| 37 | + | ||
| 38 | +> How many lines are in todo.txt? | ||
| 39 | +🛠️ bash: wc -l todo.txt | ||
| 40 | + │ 2 todo.txt | ||
| 41 | +🤖 There are 2 lines in todo.txt. | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +`old-drafts/` était toujours là, et `logs/hooks-audit.jsonl` contenait une ligne par tour et une pour l'appel `wc`. Avec `displayHooks: true`, la deuxième question liste : | ||
| 45 | + | ||
| 46 | +``` | ||
| 47 | + 🪝 1. preGeneration · no_secrets.wasm → continue | ||
| 48 | + 🪝 2. preToolCall bash · bash_guard.wasm → deny | ||
| 49 | + 🪝 3. postGeneration · audit.wasm → continue | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +## Face aux hooks Node.js | ||
| 53 | + | ||
| 54 | +La garde `bash`, les trois mêmes règles, exécutée par le lanceur de hooks de mm sur le même appel refusé, 20 exécutions après une première, deux fois (sandbox linux/arm64) : | ||
| 55 | + | ||
| 56 | +| | Première exécution | Médiane de 20 | Taille | | ||
| 57 | +|---|---|---|---| | ||
| 58 | +| `demos/hooks/hooks/bash-guard.js` (Node v22) | 68 à 102 ms | 65 à 67 ms | 954 octets, plus Node.js | | ||
| 59 | +| `hooks/bash_guard.wasm` | 234 à 248 ms (la compilation, une fois par processus mm) | 0,56 à 0,58 ms | 770 Ko | | ||
| 60 | + | ||
| 61 | +## Fichiers | ||
| 62 | + | ||
| 63 | +- `agent.yaml` — l'agent et ses `hooks:` (`wasm:`, `write:`) | ||
| 64 | +- `hooks-src/` — les sources Go des hooks (leur propre module Go, construit par TinyGo) | ||
| 65 | +- `hooks/` — les modules construits | ||
| 66 | +- `playground/` — là où l'agent travaille ; `logs/` est écrit par le hook d'audit (ignoré par git) | ||
| 67 | +- `build.sh` — reconstruit `hooks/` à partir de `hooks-src/` | ||
| 68 | + | ||
| 69 | +Voir [Écrire un hook sous forme de module WebAssembly](../../docs/fr/how-to/write-a-wasm-hook.md). | ||
added
demos/wasm-hooks/agent.yaml +64 -0 | new file mode 100644 | ||
| @@ -0,0 +1,64 @@ | ||
| 1 | +# Agent settings — the hooks demo again (demos/hooks), with its hooks written | |
| 2 | +# as WebAssembly modules instead of Node.js scripts (ticket 120). Each hook is | |
| 3 | +# built with TinyGo (hooks-src/ → hooks/, ./build.sh) and run by mm itself, in | |
| 4 | +# a sandbox: no file, no environment variable but MM_HOOK_EVENT, no network — | |
| 5 | +# except the folders its entry opens. Nothing to install beside mm. | |
| 6 | +# | |
| 7 | +# cd demos/wasm-hooks/playground | |
| 8 | +# mm ../agent.yaml | |
| 9 | +provider: llmman | |
| 10 | +model: hf.co/unsloth/gemma-4-E4B-it-GGUF:Q4_K_M | |
| 11 | + | |
| 12 | +sampling: | |
| 13 | + temperature: 0.2 | |
| 14 | + parallel_tool_calls: false | |
| 15 | + | |
| 16 | +firstTokenTimeout: 3m | |
| 17 | +watchdogTimeout: 3m | |
| 18 | +maxTurns: 8 | |
| 19 | + | |
| 20 | +bashTool: true | |
| 21 | +editTools: false | |
| 22 | +planTool: false | |
| 23 | +fetchContentTool: false | |
| 24 | +askUserTool: false | |
| 25 | +skillsDir: "" | |
| 26 | + | |
| 27 | +yolo: true | |
| 28 | + | |
| 29 | +agentsMd: | |
| 30 | + enabled: false | |
| 31 | + | |
| 32 | +acp: | |
| 33 | + name: wasm-hooks-demo | |
| 34 | + title: 🪝 WASM hooks demo | |
| 35 | + | |
| 36 | +hooks: | |
| 37 | + preGeneration: | |
| 38 | + # onError: fail — a guard that cannot run must not let the question through. | |
| 39 | + # No folder: it sees the question, nothing else. | |
| 40 | + - wasm: ./hooks/no_secrets.wasm | |
| 41 | + timeout: 5s | |
| 42 | + onError: fail | |
| 43 | + preToolCall: | |
| 44 | + - matcher: ^bash$ | |
| 45 | + wasm: ./hooks/bash_guard.wasm | |
| 46 | + timeout: 5s | |
| 47 | + onError: fail | |
| 48 | + postToolCall: | |
| 49 | + # Writes into logs/ only (relative to the working directory: playground/). | |
| 50 | + # onError: ignore — losing an audit line must not disturb the session. | |
| 51 | + - wasm: ./hooks/audit.wasm | |
| 52 | + write: [logs] | |
| 53 | + onError: ignore | |
| 54 | + postGeneration: | |
| 55 | + - wasm: ./hooks/audit.wasm | |
| 56 | + write: [logs] | |
| 57 | + onError: ignore | |
| 58 | + | |
| 59 | +system: | | |
| 60 | + You are a shell assistant working in the current directory. | |
| 61 | + Use the bash tool to look at files and to run commands; never guess what a | |
| 62 | + file contains. Answer in one or two short sentences, in the user's language. | |
| 63 | + If a tool result says "[denied by a hook: …]", the command was refused: | |
| 64 | + tell the user why, and do not try to get around the refusal. | |
| new file mode 100644 | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | +# Agent settings — the hooks demo again (demos/hooks), with its hooks written | ||
| 2 | +# as WebAssembly modules instead of Node.js scripts (ticket 120). Each hook is | ||
| 3 | +# built with TinyGo (hooks-src/ → hooks/, ./build.sh) and run by mm itself, in | ||
| 4 | +# a sandbox: no file, no environment variable but MM_HOOK_EVENT, no network — | ||
| 5 | +# except the folders its entry opens. Nothing to install beside mm. | ||
| 6 | +# | ||
| 7 | +# cd demos/wasm-hooks/playground | ||
| 8 | +# mm ../agent.yaml | ||
| 9 | +provider: llmman | ||
| 10 | +model: hf.co/unsloth/gemma-4-E4B-it-GGUF:Q4_K_M | ||
| 11 | + | ||
| 12 | +sampling: | ||
| 13 | + temperature: 0.2 | ||
| 14 | + parallel_tool_calls: false | ||
| 15 | + | ||
| 16 | +firstTokenTimeout: 3m | ||
| 17 | +watchdogTimeout: 3m | ||
| 18 | +maxTurns: 8 | ||
| 19 | + | ||
| 20 | +bashTool: true | ||
| 21 | +editTools: false | ||
| 22 | +planTool: false | ||
| 23 | +fetchContentTool: false | ||
| 24 | +askUserTool: false | ||
| 25 | +skillsDir: "" | ||
| 26 | + | ||
| 27 | +yolo: true | ||
| 28 | + | ||
| 29 | +agentsMd: | ||
| 30 | + enabled: false | ||
| 31 | + | ||
| 32 | +acp: | ||
| 33 | + name: wasm-hooks-demo | ||
| 34 | + title: 🪝 WASM hooks demo | ||
| 35 | + | ||
| 36 | +hooks: | ||
| 37 | + preGeneration: | ||
| 38 | + # onError: fail — a guard that cannot run must not let the question through. | ||
| 39 | + # No folder: it sees the question, nothing else. | ||
| 40 | + - wasm: ./hooks/no_secrets.wasm | ||
| 41 | + timeout: 5s | ||
| 42 | + onError: fail | ||
| 43 | + preToolCall: | ||
| 44 | + - matcher: ^bash$ | ||
| 45 | + wasm: ./hooks/bash_guard.wasm | ||
| 46 | + timeout: 5s | ||
| 47 | + onError: fail | ||
| 48 | + postToolCall: | ||
| 49 | + # Writes into logs/ only (relative to the working directory: playground/). | ||
| 50 | + # onError: ignore — losing an audit line must not disturb the session. | ||
| 51 | + - wasm: ./hooks/audit.wasm | ||
| 52 | + write: [logs] | ||
| 53 | + onError: ignore | ||
| 54 | + postGeneration: | ||
| 55 | + - wasm: ./hooks/audit.wasm | ||
| 56 | + write: [logs] | ||
| 57 | + onError: ignore | ||
| 58 | + | ||
| 59 | +system: | | ||
| 60 | + You are a shell assistant working in the current directory. | ||
| 61 | + Use the bash tool to look at files and to run commands; never guess what a | ||
| 62 | + file contains. Answer in one or two short sentences, in the user's language. | ||
| 63 | + If a tool result says "[denied by a hook: …]", the command was refused: | ||
| 64 | + tell the user why, and do not try to get around the refusal. | ||
added
demos/wasm-hooks/build.sh +10 -0 | new file mode 100755 | ||
| @@ -0,0 +1,10 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +# Builds the demo's WASM hooks with TinyGo: hooks-src/<name>/ → hooks/<name>.wasm. | |
| 3 | +# cd demos/wasm-hooks && ./build.sh | |
| 4 | +set -eu | |
| 5 | +cd "$(dirname "$0")/hooks-src" | |
| 6 | +for dir in */; do | |
| 7 | + name=${dir%/} | |
| 8 | + tinygo build -o "../hooks/$name.wasm" -target=wasip1 -no-debug "./$name" | |
| 9 | + echo "built hooks/$name.wasm ($(wc -c < "../hooks/$name.wasm") bytes)" | |
| 10 | +done | |
| new file mode 100755 | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +# Builds the demo's WASM hooks with TinyGo: hooks-src/<name>/ → hooks/<name>.wasm. | ||
| 3 | +# cd demos/wasm-hooks && ./build.sh | ||
| 4 | +set -eu | ||
| 5 | +cd "$(dirname "$0")/hooks-src" | ||
| 6 | +for dir in */; do | ||
| 7 | + name=${dir%/} | ||
| 8 | + tinygo build -o "../hooks/$name.wasm" -target=wasip1 -no-debug "./$name" | ||
| 9 | + echo "built hooks/$name.wasm ($(wc -c < "../hooks/$name.wasm") bytes)" | ||
| 10 | +done | ||
added
demos/wasm-hooks/hooks-src/audit/main.go +60 -0 | new file mode 100644 | ||
| @@ -0,0 +1,60 @@ | ||
| 1 | +// audit is a postToolCall and postGeneration hook: it appends one JSON line | |
| 2 | +// per event to logs/hooks-audit.jsonl. Its entry opens logs/ for writing — | |
| 3 | +// the only folder it can see, so it could not read the files it audits. | |
| 4 | +// | |
| 5 | +// It only observes, so it prints nothing on stdout. One module serves two | |
| 6 | +// events: the event's name is in the input (and in MM_HOOK_EVENT). | |
| 7 | +package main | |
| 8 | + | |
| 9 | +import ( | |
| 10 | + "encoding/json" | |
| 11 | + "fmt" | |
| 12 | + "os" | |
| 13 | + "time" | |
| 14 | +) | |
| 15 | + | |
| 16 | +type input struct { | |
| 17 | + Event string `json:"event"` | |
| 18 | + Prompt string `json:"prompt"` | |
| 19 | + Tool *struct { | |
| 20 | + Name string `json:"name"` | |
| 21 | + Input any `json:"input"` | |
| 22 | + } `json:"tool"` | |
| 23 | + Output string `json:"output"` | |
| 24 | + Answer string `json:"answer"` | |
| 25 | + Error string `json:"error"` | |
| 26 | +} | |
| 27 | + | |
| 28 | +func clip(s string, n int) string { | |
| 29 | + if r := []rune(s); len(r) > n { | |
| 30 | + return string(r[:n]) | |
| 31 | + } | |
| 32 | + return s | |
| 33 | +} | |
| 34 | + | |
| 35 | +func main() { | |
| 36 | + var in input | |
| 37 | + if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil { | |
| 38 | + fmt.Fprintln(os.Stderr, "audit: bad input:", err) | |
| 39 | + os.Exit(2) | |
| 40 | + } | |
| 41 | + line := map[string]any{"at": time.Now().UTC().Format(time.RFC3339), "event": in.Event} | |
| 42 | + if in.Event == "postToolCall" && in.Tool != nil { | |
| 43 | + line["tool"], line["input"], line["output"] = in.Tool.Name, in.Tool.Input, clip(in.Output, 200) | |
| 44 | + } else { | |
| 45 | + line["prompt"], line["answer"] = in.Prompt, clip(in.Answer, 200) | |
| 46 | + if in.Error != "" { | |
| 47 | + line["error"] = in.Error | |
| 48 | + } | |
| 49 | + } | |
| 50 | + f, err := os.OpenFile("logs/hooks-audit.jsonl", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) | |
| 51 | + if err != nil { | |
| 52 | + fmt.Fprintln(os.Stderr, "audit: cannot open the log:", err) | |
| 53 | + os.Exit(1) | |
| 54 | + } | |
| 55 | + defer f.Close() | |
| 56 | + if err := json.NewEncoder(f).Encode(line); err != nil { | |
| 57 | + fmt.Fprintln(os.Stderr, "audit: cannot write the log:", err) | |
| 58 | + os.Exit(1) | |
| 59 | + } | |
| 60 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | +// audit is a postToolCall and postGeneration hook: it appends one JSON line | ||
| 2 | +// per event to logs/hooks-audit.jsonl. Its entry opens logs/ for writing — | ||
| 3 | +// the only folder it can see, so it could not read the files it audits. | ||
| 4 | +// | ||
| 5 | +// It only observes, so it prints nothing on stdout. One module serves two | ||
| 6 | +// events: the event's name is in the input (and in MM_HOOK_EVENT). | ||
| 7 | +package main | ||
| 8 | + | ||
| 9 | +import ( | ||
| 10 | + "encoding/json" | ||
| 11 | + "fmt" | ||
| 12 | + "os" | ||
| 13 | + "time" | ||
| 14 | +) | ||
| 15 | + | ||
| 16 | +type input struct { | ||
| 17 | + Event string `json:"event"` | ||
| 18 | + Prompt string `json:"prompt"` | ||
| 19 | + Tool *struct { | ||
| 20 | + Name string `json:"name"` | ||
| 21 | + Input any `json:"input"` | ||
| 22 | + } `json:"tool"` | ||
| 23 | + Output string `json:"output"` | ||
| 24 | + Answer string `json:"answer"` | ||
| 25 | + Error string `json:"error"` | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +func clip(s string, n int) string { | ||
| 29 | + if r := []rune(s); len(r) > n { | ||
| 30 | + return string(r[:n]) | ||
| 31 | + } | ||
| 32 | + return s | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +func main() { | ||
| 36 | + var in input | ||
| 37 | + if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil { | ||
| 38 | + fmt.Fprintln(os.Stderr, "audit: bad input:", err) | ||
| 39 | + os.Exit(2) | ||
| 40 | + } | ||
| 41 | + line := map[string]any{"at": time.Now().UTC().Format(time.RFC3339), "event": in.Event} | ||
| 42 | + if in.Event == "postToolCall" && in.Tool != nil { | ||
| 43 | + line["tool"], line["input"], line["output"] = in.Tool.Name, in.Tool.Input, clip(in.Output, 200) | ||
| 44 | + } else { | ||
| 45 | + line["prompt"], line["answer"] = in.Prompt, clip(in.Answer, 200) | ||
| 46 | + if in.Error != "" { | ||
| 47 | + line["error"] = in.Error | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + f, err := os.OpenFile("logs/hooks-audit.jsonl", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) | ||
| 51 | + if err != nil { | ||
| 52 | + fmt.Fprintln(os.Stderr, "audit: cannot open the log:", err) | ||
| 53 | + os.Exit(1) | ||
| 54 | + } | ||
| 55 | + defer f.Close() | ||
| 56 | + if err := json.NewEncoder(f).Encode(line); err != nil { | ||
| 57 | + fmt.Fprintln(os.Stderr, "audit: cannot write the log:", err) | ||
| 58 | + os.Exit(1) | ||
| 59 | + } | ||
| 60 | +} | ||
added
demos/wasm-hooks/hooks-src/bash_guard/main.go +44 -0 | new file mode 100644 | ||
| @@ -0,0 +1,44 @@ | ||
| 1 | +// bash_guard is a preToolCall hook (matcher ^bash$): it refuses dangerous | |
| 2 | +// shell commands. The same rules as demos/hooks/hooks/bash-guard.js, as a | |
| 3 | +// WASM module that sees no file and no environment. | |
| 4 | +// | |
| 5 | +// mm writes {"event":"preToolCall","tool":{"name":"bash","input":{"command":"…"}},…}. | |
| 6 | +// Printing {"action":"deny","reason":"…"} means the command never runs; the | |
| 7 | +// model reads "[denied by a hook: <reason>]". Printing nothing lets it run. | |
| 8 | +package main | |
| 9 | + | |
| 10 | +import ( | |
| 11 | + "encoding/json" | |
| 12 | + "fmt" | |
| 13 | + "os" | |
| 14 | + "regexp" | |
| 15 | +) | |
| 16 | + | |
| 17 | +var rules = []struct { | |
| 18 | + reason string | |
| 19 | + pattern *regexp.Regexp | |
| 20 | +}{ | |
| 21 | + {"recursive deletion (rm -r) is not allowed", regexp.MustCompile(`\brm\s+(-\w*\s+)*-\w*[rR]`)}, | |
| 22 | + {"sudo is not allowed", regexp.MustCompile(`\bsudo\b`)}, | |
| 23 | + {"piping a download into a shell is not allowed", regexp.MustCompile(`\b(curl|wget)\b[^|]*\|\s*(ba|z)?sh\b`)}, | |
| 24 | +} | |
| 25 | + | |
| 26 | +func main() { | |
| 27 | + var in struct { | |
| 28 | + Tool struct { | |
| 29 | + Input struct { | |
| 30 | + Command string `json:"command"` | |
| 31 | + } `json:"input"` | |
| 32 | + } `json:"tool"` | |
| 33 | + } | |
| 34 | + if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil { | |
| 35 | + fmt.Fprintln(os.Stderr, "bash_guard: bad input:", err) | |
| 36 | + os.Exit(2) | |
| 37 | + } | |
| 38 | + for _, r := range rules { | |
| 39 | + if r.pattern.MatchString(in.Tool.Input.Command) { | |
| 40 | + json.NewEncoder(os.Stdout).Encode(map[string]string{"action": "deny", "reason": r.reason}) | |
| 41 | + return | |
| 42 | + } | |
| 43 | + } | |
| 44 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | +// bash_guard is a preToolCall hook (matcher ^bash$): it refuses dangerous | ||
| 2 | +// shell commands. The same rules as demos/hooks/hooks/bash-guard.js, as a | ||
| 3 | +// WASM module that sees no file and no environment. | ||
| 4 | +// | ||
| 5 | +// mm writes {"event":"preToolCall","tool":{"name":"bash","input":{"command":"…"}},…}. | ||
| 6 | +// Printing {"action":"deny","reason":"…"} means the command never runs; the | ||
| 7 | +// model reads "[denied by a hook: <reason>]". Printing nothing lets it run. | ||
| 8 | +package main | ||
| 9 | + | ||
| 10 | +import ( | ||
| 11 | + "encoding/json" | ||
| 12 | + "fmt" | ||
| 13 | + "os" | ||
| 14 | + "regexp" | ||
| 15 | +) | ||
| 16 | + | ||
| 17 | +var rules = []struct { | ||
| 18 | + reason string | ||
| 19 | + pattern *regexp.Regexp | ||
| 20 | +}{ | ||
| 21 | + {"recursive deletion (rm -r) is not allowed", regexp.MustCompile(`\brm\s+(-\w*\s+)*-\w*[rR]`)}, | ||
| 22 | + {"sudo is not allowed", regexp.MustCompile(`\bsudo\b`)}, | ||
| 23 | + {"piping a download into a shell is not allowed", regexp.MustCompile(`\b(curl|wget)\b[^|]*\|\s*(ba|z)?sh\b`)}, | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +func main() { | ||
| 27 | + var in struct { | ||
| 28 | + Tool struct { | ||
| 29 | + Input struct { | ||
| 30 | + Command string `json:"command"` | ||
| 31 | + } `json:"input"` | ||
| 32 | + } `json:"tool"` | ||
| 33 | + } | ||
| 34 | + if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil { | ||
| 35 | + fmt.Fprintln(os.Stderr, "bash_guard: bad input:", err) | ||
| 36 | + os.Exit(2) | ||
| 37 | + } | ||
| 38 | + for _, r := range rules { | ||
| 39 | + if r.pattern.MatchString(in.Tool.Input.Command) { | ||
| 40 | + json.NewEncoder(os.Stdout).Encode(map[string]string{"action": "deny", "reason": r.reason}) | ||
| 41 | + return | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | +} | ||
added
demos/wasm-hooks/hooks-src/go.mod +6 -0 | new file mode 100644 | ||
| @@ -0,0 +1,6 @@ | ||
| 1 | +// The sources of the demo's WASM hooks. Their own module, so that `go build | |
| 2 | +// ./...` in mini-me does not build them: they are built with TinyGo | |
| 3 | +// (../build.sh). | |
| 4 | +module wasmhooks | |
| 5 | + | |
| 6 | +go 1.24 | |
| new file mode 100644 | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | +// The sources of the demo's WASM hooks. Their own module, so that `go build | ||
| 2 | +// ./...` in mini-me does not build them: they are built with TinyGo | ||
| 3 | +// (../build.sh). | ||
| 4 | +module wasmhooks | ||
| 5 | + | ||
| 6 | +go 1.24 | ||
added
demos/wasm-hooks/hooks-src/no_secrets/main.go +45 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-hooks/hooks/audit.wasm +0 -0 | new file mode 100755 | ||
| Binary files /dev/null and b/demos/wasm-hooks/hooks/audit.wasm differ | ||
| new file mode 100755 | |||
| Binary files /dev/null and b/demos/wasm-hooks/hooks/audit.wasm differ | Binary files /dev/null and b/demos/wasm-hooks/hooks/audit.wasm differ | ||
added
demos/wasm-hooks/hooks/bash_guard.wasm +0 -0 | new file mode 100755 | ||
| Binary files /dev/null and b/demos/wasm-hooks/hooks/bash_guard.wasm differ | ||
| new file mode 100755 | |||
| Binary files /dev/null and b/demos/wasm-hooks/hooks/bash_guard.wasm differ | Binary files /dev/null and b/demos/wasm-hooks/hooks/bash_guard.wasm differ | ||
added
demos/wasm-hooks/hooks/no_secrets.wasm +0 -0 | new file mode 100755 | ||
| Binary files /dev/null and b/demos/wasm-hooks/hooks/no_secrets.wasm differ | ||
| new file mode 100755 | |||
| Binary files /dev/null and b/demos/wasm-hooks/hooks/no_secrets.wasm differ | Binary files /dev/null and b/demos/wasm-hooks/hooks/no_secrets.wasm differ | ||
added
demos/wasm-hooks/playground/logs/.gitignore +3 -0 | new file mode 100644 | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# Written by the audit hook: everything here is generated. | |
| 2 | +* | |
| 3 | +!.gitignore | |
| new file mode 100644 | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | +# Written by the audit hook: everything here is generated. | ||
| 2 | +* | ||
| 3 | +!.gitignore | ||
added
demos/wasm-hooks/playground/notes.md +3 -0 | new file mode 100644 | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# Notes | |
| 2 | + | |
| 3 | +The meeting is on Friday. | |
| new file mode 100644 | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | +# Notes | ||
| 2 | + | ||
| 3 | +The meeting is on Friday. | ||
added
demos/wasm-hooks/playground/old-drafts/draft1.txt +1 -0 | new file mode 100644 | ||
| @@ -0,0 +1 @@ | ||
| 1 | +draft 1 | |
| new file mode 100644 | |||
| @@ -0,0 +1 @@ | |||
| 1 | +draft 1 | ||
added
demos/wasm-hooks/playground/todo.txt +2 -0 | new file mode 100644 | ||
| @@ -0,0 +1,2 @@ | ||
| 1 | +Buy milk | |
| 2 | +Call the plumber | |
| new file mode 100644 | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | +Buy milk | ||
| 2 | +Call the plumber | ||
added
demos/wasm-tools/README.en.md +77 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/README.fr.md +77 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/agent.yaml +63 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/build.sh +10 -0 | new file mode 100755 | ||
| @@ -0,0 +1,10 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +# Builds the demo's WASM tools with TinyGo: tools-src/<name>/ → tools/<name>.wasm. | |
| 3 | +# cd demos/wasm-tools && ./build.sh | |
| 4 | +set -eu | |
| 5 | +cd "$(dirname "$0")/tools-src" | |
| 6 | +for dir in */; do | |
| 7 | + name=${dir%/} | |
| 8 | + tinygo build -o "../tools/$name.wasm" -target=wasip1 -no-debug "./$name" | |
| 9 | + echo "built tools/$name.wasm ($(wc -c < "../tools/$name.wasm") bytes)" | |
| 10 | +done | |
| new file mode 100755 | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +# Builds the demo's WASM tools with TinyGo: tools-src/<name>/ → tools/<name>.wasm. | ||
| 3 | +# cd demos/wasm-tools && ./build.sh | ||
| 4 | +set -eu | ||
| 5 | +cd "$(dirname "$0")/tools-src" | ||
| 6 | +for dir in */; do | ||
| 7 | + name=${dir%/} | ||
| 8 | + tinygo build -o "../tools/$name.wasm" -target=wasip1 -no-debug "./$name" | ||
| 9 | + echo "built tools/$name.wasm ($(wc -c < "../tools/$name.wasm") bytes)" | ||
| 10 | +done | ||
added
demos/wasm-tools/notes/release-plan.md +5 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/notes/tinygo.md +6 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/notes/wazero.md +6 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/out/.gitignore +3 -0 | new file mode 100644 | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# Written by save_note: everything here is generated. | |
| 2 | +* | |
| 3 | +!.gitignore | |
| new file mode 100644 | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | +# Written by save_note: everything here is generated. | ||
| 2 | +* | ||
| 3 | +!.gitignore | ||
added
demos/wasm-tools/tools-src/go.mod +6 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools-src/read_note/main.go +35 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools-src/save_note/main.go +54 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools-src/search_notes/main.go +59 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools-src/word_stats/main.go +58 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools/read_note.wasm +0 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools/save_note.wasm +0 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools/search_notes.wasm +0 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
demos/wasm-tools/tools/word_stats.wasm +0 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/diagrams/packages.drawio +1 -1 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/README.md +2 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/explanation/declared-tools.md +13 -1 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/explanation/hooks.md +10 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/how-to/declare-a-custom-tool.md +1 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/how-to/guard-the-agent-with-hooks.md +1 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
docs/en/how-to/write-a-wasm-hook.md +77 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
docs/en/how-to/write-a-wasm-tool.md +89 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/reference/configuration.md +20 -4 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/reference/hooks.md +34 -4 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/en/reference/tools.md +21 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/README.md +2 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/explanation/declared-tools.md +13 -1 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/explanation/hooks.md +10 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/how-to/declare-a-custom-tool.md +1 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/how-to/guard-the-agent-with-hooks.md +1 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
docs/fr/how-to/write-a-wasm-hook.md +77 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
docs/fr/how-to/write-a-wasm-tool.md +89 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/reference/configuration.md +20 -4 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/reference/hooks.md +34 -4 Large diff collapsed to keep the page fast — load it when you need it.
modified
docs/fr/reference/tools.md +21 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
go.mod +1 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
go.sum +2 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/config/config.go +1 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/config/hooks.go +52 -2 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/config/hooks_test.go +44 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/config/tools.go +115 -4 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/config/tools_test.go +56 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/hooks/exec.go +46 -2 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/hooks/hooks.go +2 -2 Large diff collapsed to keep the page fast — load it when you need it.
added
internal/hooks/wasm_test.go +92 -0 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/tools/declared.go +95 -17 Large diff collapsed to keep the page fast — load it when you need it.
modified
internal/tools/declared_test.go +75 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
internal/wasmtool/testdata/probe.wasm +0 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
internal/wasmtool/testdata/probe/main.go +89 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
internal/wasmtool/wasmtool.go +276 -0 Large diff collapsed to keep the page fast — load it when you need it.
added
internal/wasmtool/wasmtool_test.go +206 -0 Large diff collapsed to keep the page fast — load it when you need it.