bots-garden/mini-mepublic Fork
⑂ feature/open-terminal
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. fd5d2a7 · on feature/open-terminal · k33g · yesterday
write-a-wasm-hook.md · 77 lines · 3.6 KBmarkdown
Blame HistoryOpen raw

Écrire un hook sous forme de module WebAssembly

Ce guide montre comment écrire un hook qui s'exécute comme module WebAssembly à l'intérieur de mm, dans un bac à sable, au lieu d'un script : rien à installer à côté de mm, aucun processus lancé à chaque événement, et un hook qui ne voit aucun fichier, aucune variable d'environnement et aucun réseau, sauf ce que son entrée lui ouvre. Il suppose que vous savez comment fonctionnent les hooks et que TinyGo est installé.

Étapes

  1. Écrivez le hook comme un programme Go ordinaire. Il lit sur stdin le même objet JSON qu'un hook script, et écrit sur stdout la même réponse — ou rien, pour laisser le tour continuer. Celui-ci refuse git push --force :

    // hooks-src/no_force_push/main.go
    package main
    
    import (
    	"encoding/json"
    	"os"
    	"regexp"
    )
    
    var forcePush = regexp.MustCompile(`\bgit\s+push\b.*(--force|-f\b)`)
    
    func main() {
    	var in struct {
    		Tool struct {
    			Input struct {
    				Command string `json:"command"`
    			} `json:"input"`
    		} `json:"tool"`
    	}
    	if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
    		os.Exit(2)
    	}
    	if forcePush.MatchString(in.Tool.Input.Command) {
    		json.NewEncoder(os.Stdout).Encode(map[string]string{
    			"action": "deny",
    			"reason": "force-push is not allowed in this project",
    		})
    	}
    }
    
  2. Compilez-le en module WASI :

    tinygo build -o hooks/no_force_push.wasm -target=wasip1 -no-debug ./hooks-src/no_force_push
    
  3. Déclarez-le avec wasm: à la place de command: ; matcher, timeout et onError fonctionnent comme pour un script :

    hooks:
      preToolCall:
        - matcher: ^bash$
          wasm: ./hooks/no_force_push.wasm
          onError: fail
    

    Le chemin du module est relatif au fichier d'agent.

  4. Lancez l'agent avec displayHooks: true dans le fichier, et demandez quelque chose qui lance bash. Après la réponse, chaque exécution du hook est listée sous le nom de son module :

      🪝 1. preToolCall bash · no_force_push.wasm → deny
    

Variantes

  • Un hook qui lit ou écrit des fichiers. Ouvrez les dossiers dont il a besoin, relatifs au répertoire de travail : read: [rules] (lecture seule), write: [logs] (lecture-écriture). Il les ouvre sous le même nom (logs/hooks-audit.jsonl) ; rien d'autre n'existe pour lui. Un dossier doit exister quand le hook s'exécute.
  • Un module pour plusieurs événements. Lisez event dans l'entrée, ou la variable MM_HOOK_EVENT — la seule que voit le module.
  • Un hook qui a besoin de tout le répertoire de travail (pour en lister les fichiers, par exemple) ou du réseau reste un script : un hook wasm peut ouvrir des dossiers à l'intérieur du répertoire de travail, pas le répertoire lui-même, et n'a pas de réseau.
  • Les chemins dans le module. Écrivez-les propres : logs/a.jsonl, pas ./logs/a.jsonl — la couche WASI de TinyGo refuse ./ et ...
  • La première exécution d'un processus compile le module (quelques centaines de millisecondes) ; les suivantes prennent bien moins d'une milliseconde.

Voir aussi

  • Un exemple complet — un filtre de secrets, une garde pour bash et un journal d'audit, les hooks de demos/hooks réécrits en modules : demos/wasm-hooks/
  • Chaque clé, ce que voit le module, les échecs : référence des hooks
  • Pourquoi, et ce qui a été mesuré : Hooks — explication
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# Écrire un hook sous forme de module WebAssembly

Ce guide montre comment écrire un hook qui s'exécute comme module WebAssembly à l'intérieur de mm, dans un bac à sable, au lieu d'un script : rien à installer à côté de mm, aucun processus lancé à chaque événement, et un hook qui ne voit aucun fichier, aucune variable d'environnement et aucun réseau, sauf ce que son entrée lui ouvre. Il suppose que vous savez [comment fonctionnent les hooks](guard-the-agent-with-hooks.md) et que [TinyGo](https://tinygo.org) est installé.

## Étapes

1. Écrivez le hook comme un programme Go ordinaire. Il lit sur stdin le même objet JSON qu'un hook script, et écrit sur stdout la même réponse — ou rien, pour laisser le tour continuer. Celui-ci refuse `git push --force` :

   ```go
   // hooks-src/no_force_push/main.go
   package main

   import (
   	"encoding/json"
   	"os"
   	"regexp"
   )

   var forcePush = regexp.MustCompile(`\bgit\s+push\b.*(--force|-f\b)`)

   func main() {
   	var in struct {
   		Tool struct {
   			Input struct {
   				Command string `json:"command"`
   			} `json:"input"`
   		} `json:"tool"`
   	}
   	if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
   		os.Exit(2)
   	}
   	if forcePush.MatchString(in.Tool.Input.Command) {
   		json.NewEncoder(os.Stdout).Encode(map[string]string{
   			"action": "deny",
   			"reason": "force-push is not allowed in this project",
   		})
   	}
   }
   ```

2. Compilez-le en module WASI :

   ```bash
   tinygo build -o hooks/no_force_push.wasm -target=wasip1 -no-debug ./hooks-src/no_force_push
   ```

3. Déclarez-le avec `wasm:` à la place de `command:` ; `matcher`, `timeout` et `onError` fonctionnent comme pour un script :

   ```yaml
   hooks:
     preToolCall:
       - matcher: ^bash$
         wasm: ./hooks/no_force_push.wasm
         onError: fail
   ```

   Le chemin du module est relatif au fichier d'agent.

4. Lancez l'agent avec `displayHooks: true` dans le fichier, et demandez quelque chose qui lance `bash`. Après la réponse, chaque exécution du hook est listée sous le nom de son module :

   ```text
     🪝 1. preToolCall bash · no_force_push.wasm → deny
   ```

## Variantes

- **Un hook qui lit ou écrit des fichiers.** Ouvrez les dossiers dont il a besoin, relatifs au répertoire de travail : `read: [rules]` (lecture seule), `write: [logs]` (lecture-écriture). Il les ouvre sous le même nom (`logs/hooks-audit.jsonl`) ; rien d'autre n'existe pour lui. Un dossier doit exister quand le hook s'exécute.
- **Un module pour plusieurs événements.** Lisez `event` dans l'entrée, ou la variable `MM_HOOK_EVENT` — la seule que voit le module.
- **Un hook qui a besoin de tout le répertoire de travail** (pour en lister les fichiers, par exemple) ou du réseau reste un script : un hook wasm peut ouvrir des dossiers à l'intérieur du répertoire de travail, pas le répertoire lui-même, et n'a pas de réseau.
- **Les chemins dans le module.** Écrivez-les propres : `logs/a.jsonl`, pas `./logs/a.jsonl` — la couche WASI de TinyGo refuse `./` et `..`.
- **La première exécution d'un processus** compile le module (quelques centaines de millisecondes) ; les suivantes prennent bien moins d'une milliseconde.

## Voir aussi

- Un exemple complet — un filtre de secrets, une garde pour `bash` et un journal d'audit, les hooks de `demos/hooks` réécrits en modules : `demos/wasm-hooks/`
- Chaque clé, ce que voit le module, les échecs : [référence des hooks](../reference/hooks.md#hooks-wasm)
- Pourquoi, et ce qui a été mesuré : [Hooks — explication](../explanation/hooks.md#des-hooks-en-modules-webassembly)