194 lines
12 KiB
Markdown
194 lines
12 KiB
Markdown
# Verificare "function hooks" — Claude Code 2.1.274 (nativ, win32-x64)
|
|
|
|
Sursa de verificat: https://claudefa.st/blog/tools/hooks/function-hooks (blog tert, neoficial).
|
|
Metoda: (1) incercare de rulare reala `/plugin-types`; (2) cautare de siruri si context in
|
|
binarul nativ instalat, `C:\Users\mmari\.local\bin\claude.exe` (233691808 octeti, Bun-compiled,
|
|
comitul `1efcc1361e64`). Fiecare constatare de mai jos e marcata cu sursa ei.
|
|
|
|
## 1. Rularea comenzii cerute
|
|
|
|
```
|
|
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude -p "/plugin-types ./types"
|
|
```
|
|
|
|
Rezultat: **nu s-a generat niciun fisier**. Raspunsul modelului a fost text conversational
|
|
("Ready — no task given yet." / la reincercare cu `--output-format json`: `"I'm ready. What
|
|
would you like me to work on?"`). Niciun folder `types/` sau `.claude/types/` nu a aparut in
|
|
scratchpad dupa rulare (verificat cu `ls`).
|
|
|
|
**Cauza identificata, nu specifica function hooks**: am testat control cu `claude -p "/help"`
|
|
(comanda locala cea mai de baza din tot CLI-ul, intotdeauna inregistrata, fara nicio poarta de
|
|
feature-flag). A esuat identic — modelul a primit "/help" ca text mangled si a raspuns
|
|
conversational, in loc sa afiseze ajutorul. **Concluzie: invocarea `claude -p "<text>"` din acest
|
|
mediu nu ruteaza deloc prin dispecerul de comenzi locale ("slash commands")** — cererea merge
|
|
direct la model ca prompt text. Din `--debug-file`, header-ul de atribuire arata
|
|
`cc_entrypoint=sdk-cli`, adica sesiunea porneste prin calea SDK, nu prin REPL-ul interactiv complet;
|
|
acolo comenzile locale par sa nu fie interceptate. Deci esecul lui `/plugin-types` **nu e dovada
|
|
ca function hooks sunt dezactivate** — e o limitare a modului de invocare folosit aici, valabila
|
|
si pentru comenzi complet neutre ca `/help`. N-am putut testa varianta interactiva (fara TTY in
|
|
acest mediu).
|
|
|
|
`claude --help` si `claude plugin --help` (rulate separat, capturate integral) **nu listeaza**
|
|
nicio comanda `plugin-types` la nivel de CLI top-level — motivul e ca `/plugin-types` e o comanda
|
|
*in-sesiune* ("slash command"), nu o subcomanda a executabilului `claude`, deci absenta ei din
|
|
`--help` e normala si nu spune nimic despre activare.
|
|
|
|
## 2. Ce exista REAL in binar (cautare de siruri, cu offset de octet verificabil)
|
|
|
|
Nu s-au generat `.d.ts`, deci n-am putut citi declaratii TypeScript complete ca "dovada
|
|
canonica" ceruta in sarcina. In schimb, am gasit in binar cod sursa minificat (nu doar siruri
|
|
izolate) care confirma mecanismul de facto. Tot ce urmeaza e citat verbatim din binar, cu offset.
|
|
|
|
### 2a. Flag-ul si poarta lui reala
|
|
|
|
La offset ~103270720 (grep -a -b), blocul de cod al flag-ului:
|
|
|
|
```
|
|
var kYe="tengu_plugin_hooks_modules";
|
|
var Azt=()=>!1;
|
|
var AFe=()=>a.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS??P(kYe,Azt());
|
|
var h8=()=>AFe()&&!qb()&&!Br("hooks")&&!zm();
|
|
```
|
|
|
|
Interpretare directa din cod:
|
|
- flag-ul GrowthBook real se numeste **`tengu_plugin_hooks_modules`**, default **oprit** (`Azt=()=>!1`).
|
|
- `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS` e o suprascriere locala peste acel flag (`??`) — confirma
|
|
exact ce zice blogul despre numele variabilei de mediu.
|
|
- **dar** poarta finala folosita in productie, `h8()`, cere si `!qb() && !Br("hooks") && !zm()`
|
|
— trei conditii suplimentare. Numele acestor functii sunt minificate si reciclate in zeci de
|
|
module din bundle (acelasi nume `qb`/`Br`/`zm` apare cu corpuri complet diferite in alte
|
|
scope-uri), deci **nu pot atribui cu certitudine ce verifica exact aici** fara deminificare
|
|
completa — onest: aceasta e limita cercetarii, nu o concluzie.
|
|
- Alaturi, la acelasi offset, un tabel de atribuire a sursei flag-ului pentru afisare in UI:
|
|
`{override:"from a local override", payload:"from GrowthBook (this session's payload)",
|
|
disk:"from GrowthBook (the disk cache of an earlier session)", disabled:"from the default
|
|
(GrowthBook is off ...)"}` — confirma ca e un flag GrowthBook cu override local, exact
|
|
mecanismul descris de blog.
|
|
|
|
### 2b. Comanda `/plugin-types` — confirmata REAL, dar NEgatata de flag
|
|
|
|
La offset 203438732, definitia completa a comenzii (citat verbatim):
|
|
|
|
```
|
|
var mer=Object.freeze({type:"local",name:"plugin-types",
|
|
description:"Write claude-code.d.ts, claude-code-plugins.d.ts and claude-code-mcp.d.ts:
|
|
the plugin API's TypeScript declarations, the enabled plugins' type contracts and the
|
|
inputs of the connected MCP tools, for typing a hooks module against this session",
|
|
argumentHint:"[dir]", supportsNonInteractive:!0,
|
|
load:()=>import("B:/~BUN/root/chunk-0rpsm23r.js")});
|
|
```
|
|
|
|
Important: **acest obiect n-are nicio conditie `isEnabled`/gate legata de `h8()` sau de
|
|
flag** — e inregistrata neconditionat. Descrierea confirma explicit sintagma "hooks module"
|
|
din blog: comanda exista ca sa tipizeze "a hooks module against this session". Deci comanda in
|
|
sine e reala si documentata intern, indiferent de starea flag-ului; problema empirica de mai
|
|
sus (sectiunea 1) e doar despre modul de invocare `-p` din acest mediu.
|
|
|
|
### 2c. Lista REALA de evenimente si API-uri ("scan manifest"), gasita ca date, nu ca documentatie
|
|
|
|
La offset 223160560 exista un manifest static de tip "scan" folosit (aparent) pentru analiza
|
|
statica / sandboxing a unui modul de plugin, cu lista completa si explicita:
|
|
|
|
```js
|
|
scan: {
|
|
hooks: ["session.start","ui.render","command.run","ui.close","ui.focus","ui.scroll",
|
|
"tool.call","prompt.submit"],
|
|
calls: ["clock.after","clock.every","clock.now","command.register","fs.list","fs.read",
|
|
"fs.stat","process.run","session.id","session.messages","store.get","store.set",
|
|
"telemetry.log","telemetry.mark","ui.close","ui.invalidate","ui.log","ui.open",
|
|
"ui.resolve","ui.status"]
|
|
}
|
|
```
|
|
|
|
Aceasta e cea mai tare dovada gasita — e o **lista de date**, nu un string de proza, deci
|
|
foarte probabil chiar suprafata reala (sau foarte apropiata) a API-ului de hooks module:
|
|
|
|
- **evenimente de hook confirmate**: `session.start`, `ui.render`, `command.run`, `ui.close`,
|
|
`ui.focus`, `ui.scroll`, `tool.call`, `prompt.submit` (8 la numar).
|
|
- **`turn.complete` si `turn.start` NU sunt in lista de evenimente de hook**, desi cele doua
|
|
siruri exista in alta parte a binarului (24, respectiv 68 aparitii) — apartin mecanismului
|
|
intern de motor ("engine turn 1 start/end", vazut si in log-ul de debug la rularea reala),
|
|
nu suprafetei de hooks expuse pluginurilor.
|
|
- **`prompt.submit` e confirmat**, si separat, la offset 206959908, exista codul intern real
|
|
care implementeaza acest hook point: un pipeline `core`/`managed` cu posibilitate de a
|
|
rescrie textul promptului (`"prompt.submit: text rewritten by a hook (...)"`) sau de a-l
|
|
bloca (`"Prompt dropped by a hook: ..."`). Semnatura exacta gasita e apelul
|
|
`.prompt.submit({submission, origin, turnId, shouldWait})` — **nu am gasit litera `$` ca
|
|
alias/receiver exact** in siruri; forma `$.prompt.submit({text})` din blog e plauzibila ca
|
|
sugar-syntax peste acest mecanism, dar nu e confirmata verbatim.
|
|
- **API-uri de tip "calls" confirmate ca namespace-uri cu metode**: `fs.read/list/stat`
|
|
(deci `$.fs` probabil e un namespace, nu o valoare simpla), `store.get/set`,
|
|
`clock.after/every/now`, `session.id/messages` — coincide cu ce cerea sarcina sa verific
|
|
(`$.session.messages()`, `$.fs`, `$.store`, `$.clock.every`), dar iar, prefixul `$.` in sine
|
|
nu apare ca sir literal langa aceste nume — e o inferenta rezonabila, nu o citare directa.
|
|
|
|
### 2d. Tokeni / context / usage / compactare — CAUTATE EXPLICIT, NU GASITE
|
|
|
|
Am cautat explicit `token`, `usage`, `context`, `compact` in vecinatatea manifestului de mai
|
|
sus si in restul zonei de "function hooks". **Niciunul dintre cele 20 de `calls` sau 8 `hooks`
|
|
de mai sus nu are legatura cu tokeni, context window, usage sau compactare.** Sirurile
|
|
`contextWindow` (8 aparitii) si `compact` (405 aparitii) exista din abundenta in binar, dar in
|
|
alte module (afisarea usage-ului in `--output-format json`, autocompact intern al motorului
|
|
— vazut si in logul de debug: `autocompact: tokens=[REDACTED] level=ok effectiveWindow=980000`),
|
|
**nu ca hook sau call disponibil unui modul de function hooks**. Nu exista dovada in acest
|
|
binar ca un modul de hooks ar putea citi tokenii ramasi sau starea de compactare.
|
|
|
|
### 2e. Forma `register` si `hooks/hooks.json`
|
|
|
|
Doua descoperiri, ambele reale, dar **doua lucruri diferite**:
|
|
|
|
1. **`hooks.json` din binar (27 aparitii) e manifestul VECHI/existent de plugin-hooks**
|
|
(PreToolUse, SessionStart etc. — cel folosit deja de pluginul `ponytail` din acest proiect,
|
|
vazut in logul de debug: `Read manifest hooks for plugin ponytail (enabled=true):
|
|
./hooks/claude-codex-hooks.json`). Contextul din binar confirma: sirul e `"plugin
|
|
hooks.json"`, langa mesaje despre `SKILL.md` si actualizarea regulilor de auto-mode/permisiuni
|
|
— sistemul clasic de hooks pe evenimente de tool-use, nesuprapus cu "function hooks".
|
|
|
|
2. **Forma `register` pentru "function hooks" (modulul nou)**, gasita in doua exemple interne
|
|
reale de pluginuri deja construite cu acest mecanism:
|
|
- `tengu_quiet_dolphin` — "panoul de diff ca plugin: /diff", cu
|
|
`isAvailable:()=>At` unde `At=()=>h8()&&lu(Rr(),bt)` — **confirma ca h8() (poarta de
|
|
function hooks descrisa la 2a) chiar gateaza un plugin real deja construit**, nu doar un
|
|
flag mort.
|
|
- `tengu_tips_mod` — "spinner tips as a plugin", cu forma exacta:
|
|
```js
|
|
var P=(e)=>({register:(o)=>{e.registerHooks(o,x())}});
|
|
```
|
|
adica modulul exporta un `register(engine)` care apeleaza
|
|
`engine.registerHooks(implementareHooks, apiSuplimentar)`. E cea mai apropiata dovada
|
|
gasita de "forma exacta a functiei register", dar e exemplul unui plugin intern concret,
|
|
nu semnatura generica documentata explicit undeva ca atare.
|
|
|
|
## VERDICT
|
|
|
|
Se poate construi o bucla "prag context -> scrie handoff -> /clear -> reia din handoff" complet
|
|
automat, FARA interventia utilizatorului, folosind acest surface?
|
|
|
|
**Nu, nu cu ce am vazut efectiv in acest binar.** Motive, punct cu punct:
|
|
|
|
1. Suprafata de hooks confirmata (`session.start`, `ui.*`, `command.run`, `tool.call`,
|
|
`prompt.submit`) **nu contine niciun eveniment sau apel legat de prag de context, tokeni,
|
|
usage sau compactare** (sectiunea 2d) — deci un modul de hooks n-ar avea de unde sa afle
|
|
"am trecut de 200k tokeni" din interior. Ar trebui sa deduca asta indirect (ex. numarand
|
|
caractere din `session.messages()`), fara nicio garantie ca se potriveste cu tokenizarea
|
|
reala sau cu `autocompact` intern.
|
|
2. Nu exista niciun hook de tipul "inainte de compactare" / "sesiune se inchide" in lista —
|
|
deci nu exista un punct de agatare curat pentru "scrie handoff chiar inainte sa se piarda
|
|
contextul".
|
|
3. `/clear` (sau relansarea sesiunii) nu apare in `calls`-urile confirmate (`command.register`
|
|
exista, dar a inregistra o comanda noua nu e totuna cu a declansa `/clear` programatic din
|
|
interiorul unui hook).
|
|
4. Flag-ul e in continuare oprit implicit (`tengu_plugin_hooks_modules` default `!1`),
|
|
marcat "early access" chiar in textul din comanda `/plugin-types` insasi
|
|
("module 'claude-code', early access: it may change between releases") — deci si daca
|
|
API-ul de mai sus ar acoperi cazul, Anthropic il trateaza explicit ca nestabil.
|
|
5. **Nu am reusit sa validam empiric nimic din surface** (n-am putut genera `.d.ts` din cauza
|
|
limitarii de mediu descrise la sectiunea 1) — tot ce e mai sus e din citirea codului
|
|
minificat, nu din declaratii TypeScript generate si citite ca atare, cum cerea procedura.
|
|
|
|
Ce ar lipsi/ar trebui reincercat ca sa se stie sigur: rulare **interactiva** reala (TTY, nu
|
|
`-p`/SDK) cu `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1`, ca sa se vada daca `/plugin-types` chiar
|
|
scrie fisierele si daca `h8()` se evalueaza `true` in acel context — asta ar da acces la
|
|
declaratiile TypeScript reale si ar inchide toate necunoscutele de mai sus (in special conditiile
|
|
`qb()`/`Br("hooks")`/`zm()` neatribuite cu certitudine).
|