pi-grok-agent 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,57 +2,99 @@
2
2
 
3
3
  Grok's agent. Pi's workflow.
4
4
 
5
- Run Grok Build as a model in the [Pi coding agent](https://github.com/earendil-works/pi).
6
- Grok keeps its native tools and history. You keep Pi's transcript, extensions, and permission dialogs.
5
+ ```sh
6
+ pi install npm:pi-grok-agent
7
+ ```
7
8
 
8
- [Quick start](#quick-start) · [First result](#first-result) · [Reference](docs/usage.md) · [Limitations](#limitations) · [Apache-2.0](LICENSE)
9
+ That is the whole installation. Then pick a Grok model in Pi's `/models` and send a message. See the [quick start](#quick-start).
9
10
 
10
- After [setup](#quick-start), select Grok like any Pi model:
11
+ This package runs [Grok Build](https://docs.x.ai/build/overview) as a model in the [Pi coding agent](https://github.com/earendil-works/pi). Grok keeps its own harness, its native tools, and its session history. Pi drives the turns and adds what it gives every model: the transcript, permission dialogs, tool gates, and extension tools.
11
12
 
12
- ```sh
13
- pi --model grok/grok-4.7
14
- ```
13
+ Two things this package is not:
14
+
15
+ - It connects the Grok Build agent over ACP, not the xAI chat-completions API. You need the Grok Build CLI and its own login.
16
+ - Its tool gates are not an operating-system sandbox. Grok runs with your user's permissions.
15
17
 
16
- Status: version 0.1.0. Developed and tested on Linux with Node.js 26.10.0, Pi 0.87.1, and Grok Build 1.0.41. Other versions and platforms are untested.
18
+ Version 0.1.1 on [npm](https://www.npmjs.com/package/pi-grok-agent) and as a [GitHub release](https://github.com/JangMan-J/pi-grok-agent/releases/tag/v0.1.1). Developed and tested on Linux with Node.js 26.10.0, Pi 0.87.1, and Grok Build 1.0.41. Other versions and platforms are untested. The recorded runs are in [docs/launch-verification.md](docs/launch-verification.md).
17
19
 
18
- This package connects an agent, not the xAI chat-completions API. It needs a logged-in Grok Build CLI and a local gateway.
19
- The tool gates are not an operating-system sandbox.
20
+ [Requirements](#requirements) · [Quick start](#quick-start) · [First result](#first-result) · [What Pi adds](#what-pi-adds) · [Models](#models) · [Limitations](#limitations) · [Safety](#safety) · [Reference](docs/usage.md)
20
21
 
21
22
  ## Requirements
22
23
 
23
24
  | Item | Requirement |
24
25
  | --- | --- |
25
- | Node.js and npm | Node.js 22.19 or later is Pi's minimum. This package is tested only on 26.10.0. |
26
- | [Pi](https://github.com/earendil-works/pi#quick-start) | Tested with 0.87.1. Other versions are untested. |
27
- | [Grok Build CLI](https://docs.x.ai/build/overview) | `grok` on `PATH`, or its path in `PI_GROK_BINARY`. |
28
- | Grok login | Run `/grok login` in Pi, or `grok login` in a terminal. Both use Grok's own sign-in, and Grok keeps the credential in `~/.grok/auth.json`. Pi's `/login` xAI entry is a separate login and does not sign in Grok Build. |
29
- | Free local port | `127.0.0.1:2419`, or another loopback port in `GROK_ACP_URL`. |
30
- | Optional | A terminal with inline image support. ImageMagick 7 (`magick`) to show JPEG, WebP, and GIF images inline. PNG needs no converter. |
26
+ | Grok account | A [grok.com](https://grok.com) account, any membership tier. A free account works: verified live on 2026-09-28 with Grok Build 1.0.41, where headless turns returned `PONG` and a read-only tool turn answered correctly ([docs/launch-verification.md](docs/launch-verification.md)). Grok usage counts against that account. |
27
+ | Grok Build CLI | `grok` on `PATH`, or its path in `PI_GROK_BINARY`. |
28
+ | Grok login | Only if Grok Build is not signed in yet. Run `/grok login` in Pi, or `grok login` in a terminal. Both use Grok's own device-code sign-in, and Grok keeps the credential in `~/.grok/auth.json`; Pi stores nothing (`src/login.ts`). Pi's `/login` xAI entry is a separate login and does not sign in Grok Build, and `XAI_API_KEY` does not replace it. |
29
+ | Pi | Tested with 0.87.1. Other versions are untested. [Install Pi first](https://github.com/earendil-works/pi#quick-start). |
30
+ | Node.js | 22.19 or later, Pi's minimum (`engines` in `package.json`). Only 26.10.0 is tested here. |
31
+ | Optional | A terminal with inline image support. ImageMagick 7 (`magick`) shows JPEG, WebP, and GIF inline; PNG needs no converter. |
31
32
 
32
- Grok usage counts against your Grok account. Pi shows the cost that Grok reports for each turn.
33
+ Pi shows the cost Grok reports for each turn. Which models your account may use is Grok's decision, not this package's — see [Models](#models).
33
34
 
34
35
  ## Quick start
35
36
 
36
- ```sh
37
- pi install npm:pi-grok-agent
38
- pi --model grok/grok-4.7
37
+ 1. Install the package:
38
+
39
+ ```sh
40
+ pi install npm:pi-grok-agent
41
+ ```
42
+
43
+ 2. Start Pi in your project and pick a Grok model in `/models`: Grok 4.7, Grok 4.7 Build Fast, Grok 4.6, or Grok 4.5.
44
+ 3. If Grok Build is not signed in, run `/grok login` and approve the code in your browser. Grok runs its own device-code sign-in and stores the credential in `~/.grok/auth.json`; Pi stores nothing (`src/login.ts`).
45
+ 4. Send a message and watch the `grok-tool` lines appear. See [First result](#first-result) for what a healthy first turn looks like.
46
+
47
+ The first Grok turn starts the local gateway that ships with the package and waits for it, about 5 seconds. The gateway listens on loopback at `127.0.0.1:2419`; `GROK_ACP_URL` picks another loopback port.
48
+
49
+ ![How Pi, the gateway, and Grok Build connect: one Pi process per terminal; one shared gateway on 127.0.0.1:2419 holding the WebSocket, the reverse-request guard, and the MCP relay; one grok agent bridge per Pi connection; one shared leader over ~/.grok](https://github.com/JangMan-J/pi-grok-agent/raw/main/docs/assets/integration-topology.png)
50
+
51
+ <details>
52
+ <summary>Diagram source (mermaid)</summary>
53
+
54
+ ```mermaid
55
+ flowchart LR
56
+ subgraph PI["Pi process (one per terminal)"]
57
+ direction TB
58
+ UI["Pi TUI<br/>transcript, dialogs, /models"]
59
+ EXT["pi-grok-agent extension<br/>provider grok/*"]
60
+ TOOLS["Pi extension tools"]
61
+ UI --- EXT
62
+ EXT --- TOOLS
63
+ end
64
+
65
+ subgraph GW["Gateway (one per machine, 127.0.0.1:2419)"]
66
+ direction TB
67
+ WS["WebSocket /ws<br/>bearer secret"]
68
+ GUARD["Reverse-request guard<br/>ack deadlines, fail closed"]
69
+ MCP["MCP relay<br/>HTTP /mcp/&lt;token&gt;"]
70
+ end
71
+
72
+ subgraph GROK["Grok Build"]
73
+ direction TB
74
+ BRIDGE["grok agent --leader stdio<br/>one bridge per Pi connection"]
75
+ LEADER["grok agent leader<br/>harness, native tools, subagents"]
76
+ STORE[("~/.grok<br/>login, sessions")]
77
+ BRIDGE --- LEADER
78
+ LEADER --- STORE
79
+ end
80
+
81
+ EXT == "ACP over WebSocket" ==> WS
82
+ WS == "stdio" ==> BRIDGE
83
+ WS -.- GUARD
84
+ LEADER -. "lent-tool calls (HTTP)" .-> MCP
85
+ MCP -. "_x.ai/mcp/sdk_call" .-> EXT
86
+ EXT -. "starts it if nothing listens" .-> GW
39
87
  ```
40
88
 
41
- The first Grok turn starts the local gateway that comes with the package and waits for it, about 5 seconds. The gateway keeps running after Pi exits, and every Pi process on the machine shares it. Its first start creates the shared secret `~/.pi/agent/grok-ws.secret` with mode 0600. [Gateway auto-start](docs/usage.md#gateway-auto-start) explains how to stop it, or how to run it yourself with `npm install -g pi-grok-agent` and `pi-grok-gateway`.
89
+ </details>
42
90
 
43
- Pi loads the extension at every start, for every model. To remove it, run `pi remove npm:pi-grok-agent`.
91
+ Pi drives the turn and Grok drives the callbacks, both over the Agent Client Protocol. [How it works →](docs/usage.md#components) · [Gateway guard →](docs/usage.md#gateway-guard) · [Design notes →](docs/first-class-model.md)
44
92
 
45
- ### From a clone
93
+ There is no global install and no gateway to start by hand: the recorded install runs used only `pi install npm:pi-grok-agent`, with nothing listening on that port and no `pi-grok-gateway` on `PATH` ([docs/launch-verification.md](docs/launch-verification.md), rows `G2, npm, one command` and `G2, public npm`).
46
94
 
47
- ```sh
48
- git clone https://github.com/JangMan-J/pi-grok-agent.git
49
- cd pi-grok-agent
50
- npm install --omit=dev
51
- npm run server # terminal A: the gateway
52
- pi -e . --model grok/grok-4.7 # terminal B: this Pi process only; or pi install . for every session
53
- ```
95
+ The gateway keeps running after Pi exits, and every Pi process on the machine shares it. Its first start creates the shared secret `~/.pi/agent/grok-ws.secret` with mode 0600 and logs to `~/.pi/agent/grok-ws.log` (`src/launch.ts`, `src/config.ts`). To stop it, disable auto-start, or run it yourself under a service manager, see [gateway auto-start](docs/usage.md#gateway-auto-start).
54
96
 
55
- `pi install git:github.com/JangMan-J/pi-grok-agent` also works. A clone auto-starts the gateway from its own checkout, so `npm run server` is optional. A git install uses the same code path, but that is not yet tested live.
97
+ Pi loads the extension at every start, for every model. To remove it, run `pi remove npm:pi-grok-agent`.
56
98
 
57
99
  ## First result
58
100
 
@@ -64,113 +106,206 @@ Read package.json and tell me the package name and the npm scripts. Do not chang
64
106
 
65
107
  Expected result:
66
108
 
67
- - One line for each Grok tool call, for example `✓ grok read_file …` or `✓ grok hashline_read …`, with its duration. Grok chooses the tool.
109
+ - One line for each Grok tool call, for example `✓ grok read_file …` or `✓ grok hashline_read …`, with its duration. Grok chooses the tool, so do not require a particular tool name.
68
110
  - Thinking text that contains `[grok <tool>]` lines.
69
- - An answer that names the package and its scripts.
111
+ - An answer that names the package and its scripts, with no edits.
70
112
  - A footer cost that comes from Grok's usage report.
71
113
 
114
+ This is a check for your installation, not a saved transcript. These display paths have source support in `src/model.ts` and `src/model/session.ts` and unit tests in `test/model.test.ts`.
115
+
72
116
  Then run `/grok debug`. It shows the gateway connection, the Grok session ID, the permission modes, token usage, and the lent Pi tools.
73
117
 
74
- If the result is different, see [Troubleshooting](docs/usage.md#troubleshooting). Please report what you saw, as described in [Feedback](#feedback).
118
+ If the result is different, see [Troubleshooting](docs/usage.md#troubleshooting). If the model is missing from `/models`, inspect Pi's extension load error. If sign-in fails, use `/grok login`, not Pi's xAI login. Please report what you saw, as described in [Feedback](#feedback).
119
+
120
+ ## What Pi adds
121
+
122
+ Grok Build already runs an agent with tools, and it can serve ACP to any client. What this package adds is the rest of Pi around that agent:
123
+
124
+ | You want to… | What happens | Basis |
125
+ | --- | --- | --- |
126
+ | Keep Grok's native tools | Grok executes the tools its own harness offers — file, shell, search, web, subagent, media. Pi records each call in the transcript and executes none of them. | `test/model.test.ts` |
127
+ | See Grok's work as you go | Each native call becomes thinking text plus a `grok-tool` entry, with its status and duration. | `src/model/session.ts` |
128
+ | Control edits from Pi | Pi gates Grok's tools before they run through a `pre_tool_use` hook. A read-only Pi session denies Grok's edits and shell; `/grok perms ask` confirms each call. | `src/model/hooks.ts`, `test/hooks.test.ts` |
129
+ | Decide inside Pi | Grok's permission prompts become Pi dialogs, and Grok's `ask_user_question` becomes one Pi dialog per question. | `src/model/permissions.ts`, `src/model/questions.ts`, `test/questions.test.ts` |
130
+ | Reuse Pi extension tools | Pi lends extension tools to Grok over MCP. Grok calls them as `pi__<name>`, Pi executes the call, and the result continues the same Grok turn. | `src/model/session.ts`, `evidence/model-probe.json` |
131
+ | Feed checks back to Grok | After a Grok edit, a syntax check runs on the file and a failure goes back to Grok in the same turn. A configured `stopCheck` can hold the end of a turn. | `src/model/hooks.ts`, `evidence/hooks-live.json` |
132
+
133
+ ### One turn
134
+
135
+ ![One Grok turn end to end: initialize and cached_token auth, session/new or session/load, model and reasoning_effort config options, a prompt carrying only the new messages, session/update streaming, the guarded pre_tool_use round trip with its ack deadline, permission and question dialogs, a lent-tool MCP call relayed as _x.ai/mcp/sdk_call, mid-turn interject, the stop hook, and the prompt result](https://github.com/JangMan-J/pi-grok-agent/raw/main/docs/assets/integration-one-turn.png)
136
+
137
+ <details>
138
+ <summary>Diagram source (mermaid)</summary>
139
+
140
+ ```mermaid
141
+ sequenceDiagram
142
+ autonumber
143
+ participant P as Pi (extension)
144
+ participant G as Gateway
145
+ participant L as Grok leader
146
+
147
+ P->>L: initialize, authenticate (cached_token), through the gateway
148
+ alt first turn of this Pi session
149
+ P->>L: session/new: rules = Pi system prompt, client hooks, MCP server "pi"
150
+ else after a reconnect
151
+ P->>L: session/load (same Grok session)
152
+ end
153
+ P->>L: session/set_config_option: model, reasoning_effort
154
+ P->>L: session/prompt: only the new messages
155
+
156
+ loop while Grok works
157
+ L-->>P: session/update: text, thinking, tool calls
158
+ Note over P: Grok's own tools become thinking lines<br/>and grok-tool entries, never Pi tool calls
159
+ L->>G: _x.ai/hooks/run (pre_tool_use)
160
+ G->>P: forwarded, with an ack deadline
161
+ P-->>G: pi/gate-ack, then allow or deny from Pi's tool gate
162
+ G-->>L: answer, or deny if Pi missed the deadline
163
+ opt Grok asks
164
+ L->>P: session/request_permission or _x.ai/ask_user_question (guarded like hooks)
165
+ P-->>L: answer from a Pi dialog, or cancelled if Pi is gone
166
+ end
167
+ opt Grok calls a lent Pi tool
168
+ L->>G: MCP tools/call over HTTP
169
+ G->>P: _x.ai/mcp/sdk_call
170
+ P-->>G: result from the Pi tool
171
+ G-->>L: HTTP response
172
+ end
173
+ opt you press Enter mid-turn
174
+ P->>L: _x.ai/interject
175
+ end
176
+ end
177
+ L->>G: _x.ai/hooks/run (stop)
178
+ G->>P: forwarded
179
+ P-->>L: continue, or block until the stop check passes
180
+ L-->>P: prompt result: stop reason, usage, cost
181
+ ```
75
182
 
76
- ## Why use this?
183
+ </details>
77
184
 
78
- For Pi users who want Grok Build's native tools in their existing agent workflow:
185
+ ### Gates, checks, and lent tools
79
186
 
80
- | You want to… | What this package adds |
81
- | --- | --- |
82
- | Keep Grok's native tools | Grok runs its own harness. Pi shows its tool calls in the transcript. |
83
- | Control edits from Pi | [Pi's tool gate](docs/usage.md#grok-permission-prompts) can deny Grok's edit and shell tools or ask before each call. |
84
- | Reuse Pi extension tools | [Lent tools](docs/usage.md#lent-pi-tools) reach Grok over MCP. Pi executes those calls. |
187
+ - Grok keeps the full tool results in its own context. Pi shows a shortened copy: 400 characters in the thinking stream, up to 8000 in the stored `grok-tool` entry, and up to 600 in the expanded entry (`src/model/session.ts`, `src/model.ts`). Lent Pi tool results go back to Grok complete.
188
+ - Gate precedence is fixed: a `denyGrokTools` entry always wins, an explicit `allowGrokTools` entry then allows — including past the read-only mirror — and only then does the capability mirror apply. By default, a Pi session without `edit` or `write` denies Grok's edit tools, and a session without `bash` denies Grok's shell (`capabilityGate` in `src/model/hooks.ts`).
189
+ - `/grok perms read-only`, `ask`, `auto`, and `yolo` change Pi's gate. Grok's own permission prompts are a separate layer ([details](docs/usage.md#grok-permission-prompts)).
190
+ - The default lent-tool policy is `extensions`, which excludes Pi's core tools — `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls` (`PI_CORE_TOOLS` in `src/config.ts`).
191
+ - The built-in post-edit syntax check covers TypeScript, JavaScript, Python, JSON, and Rust, chosen by file extension; anything else needs a configured `postEditCheck` (`BUILTIN_CHECKS` in `src/model/hooks.ts`).
85
192
 
86
- ```text
87
- Pi ⇄ local ACP gateway ⇄ Grok Build
88
- └─ native tools and agent history
89
- ```
193
+ ### Turn controls
194
+
195
+ - Pi's thinking level sets Grok's reasoning effort. Escape cancels the Grok turn (`src/model/provider.ts`, `src/model/session.ts`).
196
+ - Mid-turn Enter sends the text to Grok's `_x.ai/interject` method. Unit-tested against a mocked Grok (`test/steer.test.ts`); its effect on a live running turn is not yet verified, and a `grok-steer` entry proves dispatch, not that Grok acted on it. Alt+Enter queues a follow-up turn, as usual in Pi.
197
+ - Grok plan mode, `/goal`, and `/compact` are available through `/grok plan`, `/grok goal`, and `/grok compact`. Send a normal prompt first so a Grok session exists ([commands](docs/usage.md#slash-command-grok)).
198
+ - After a switch from `grok/*` to another model in the same Pi session, mid-turn Enter goes to that model. The stored Grok session is used again when you switch back (`test/extension.test.ts`).
199
+
200
+ ## Models
90
201
 
91
- The gateway carries the Agent Client Protocol (ACP) over WebSocket and stdio. [How it works →](docs/usage.md#components)
202
+ | Model ID | Name in `/models` | Reasoning efforts | Context window |
203
+ | --- | --- | --- | --- |
204
+ | `grok/grok-4.7` | Grok 4.7 | low, medium, high, xhigh | 500,000 tokens |
205
+ | `grok/grok-4.7-build-fast` | Grok 4.7 Build Fast | low, medium, high, xhigh | 500,000 tokens |
206
+ | `grok/grok-4.6` | Grok 4.6 | low, medium, high, xhigh | 500,000 tokens |
207
+ | `grok/grok-4.5` | Grok 4.5 | low, medium, high | 500,000 tokens |
92
208
 
93
- ### More controls
209
+ These are the IDs the extension registers (`MODEL_IDS` in `src/model.ts`), not a claim that your account may use all of them. Which models an account has is Grok's decision: `grok models` lists them, and the free account tested here lists only `grok-4.7`. The saved probes used `grok/grok-4.7`.
94
210
 
95
- - Grok's native tools do the work. Grok executes the tools its harness offers, such as file, shell, search, web, subagent, and media tools. Pi does not execute them.
96
- - Grok keeps the full tool results in its own context. Pi shows a shortened copy: 400 characters in the thinking stream, up to 8000 characters in the stored `grok-tool` entry, and up to 600 characters in the expanded entry.
97
- - Grok's permission prompts become Pi dialogs. Grok's `ask_user_question` becomes Pi dialogs, one per question.
98
- - Pi can gate Grok's tools. By default, a Pi session without `edit` or `write` denies Grok's edit tools, and a session without `bash` denies Grok's shell. `/grok perms read-only`, `ask`, `auto`, and `yolo` change this gate. A `denyGrokTools` entry always wins. Grok's own permission prompts are separate ([details](docs/usage.md#grok-permission-prompts)).
99
- - Optional checks run around Grok's tools. After a Grok edit, a syntax check runs on the file. A failure goes back to Grok in the same turn. A configured `stopCheck` can hold the end of a turn.
100
- - Pi can lend its extension tools to Grok. They appear to Grok as `pi__<name>`, and Pi executes them.
101
- - Mid-turn Enter sends the text to Grok's `_x.ai/interject` method. Its effect on the running turn is not yet verified live. Alt+Enter queues a follow-up turn, as usual in Pi.
102
- - Pi's thinking level sets Grok's reasoning effort. Escape cancels the Grok turn.
103
- - Grok plan mode, `/goal`, and `/compact` are available through `/grok plan`, `/grok goal`, and `/grok compact`.
211
+ In 0.1.0 the model picked in Pi was never sent to Grok, so every `grok/*` choice ran Grok's default. Fixed in 0.1.1: Pi sets Grok's `model` config option, and a model the account lacks fails the turn with the list of available models instead of quietly running another one.
104
212
 
105
- Models: `grok/grok-4.7`, `grok/grok-4.7-build-fast`, `grok/grok-4.6`, `grok/grok-4.5`. Each has a 500,000-token context window.
213
+ Pi's metadata also sets a 32,000-token output limit and a per-token cost of zero, because Grok's rates are not published to Pi; the per-turn cost comes from Grok's own usage report. Other extensions and settings can select these models by ID. The full control mapping is in [docs/usage.md](docs/usage.md#models-and-pi-controls).
106
214
 
107
215
  ## Generated images and video
108
216
 
109
- When a Grok tool result has the type `ImageGen`, `ImageEdit`, `ImageToVideo`, `ReferenceToVideo`, or `VideoGen`, Pi copies the file to `.pi/grok-images/` in the Pi working directory. That directory gets a `.gitignore` that ignores everything in it.
217
+ When a Grok tool result has the type `ImageGen`, `ImageEdit`, `ImageToVideo`, `ReferenceToVideo`, or `VideoGen`, Pi copies the file to `.pi/grok-images/` in the Pi working directory. That directory gets a `.gitignore` that ignores everything in it. If the copy fails or copying is off, the entry keeps Grok's original path (`copyMedia` in `src/model/session.ts`).
110
218
 
111
- After the turn, Pi shows a `grok-media` message with the file path. PNG, JPEG, WebP, and GIF images also show inline when the terminal supports images. PNG shows directly. Pi converts JPEG, WebP, and GIF to PNG with `magick` first. Without `magick`, you see only the path for those formats. Video files show as a path only. Pi does not play video.
219
+ After the turn, Pi shows a `grok-media` message with the file path. PNG, JPEG, WebP, and GIF images also show inline when the terminal supports images: PNG shows directly, and Pi converts JPEG, WebP, and GIF to PNG with `magick` first. Without `magick`, you see only the path for those formats. Video files show as a path only; Pi does not play video.
112
220
 
113
- Live probes cover `image_gen` only (`evidence/image-probe.json`, run recorded in [docs/launch-verification.md](docs/launch-verification.md)). The image-edit and video result types are recognized in code but not yet probed with a live Grok run. The media message is for display only. Pi does not send it back to Grok.
221
+ Live probes cover `image_gen` only ([evidence/image-probe.json](evidence/image-probe.json)). The image-edit and video result types are recognized in code but not yet probed with a live Grok run. The media message is for display only: the provider removes it from the prompt, so Grok does not receive its own image back.
114
222
 
115
- Images that you attach in Pi go to Grok as a temporary file path under the system temp directory (`pi-grok-images`). Grok reads the file with its own tools.
223
+ Images that you attach in Pi go to Grok as a temporary file under `pi-grok-images` in the system temp directory, written with mode 0600 (`src/model/provider.ts`). Grok reads the file with its own tools. The path route is the one that works: the probe also sent an ACP image block directly, and Grok did not see it.
116
224
 
117
225
  ## Limitations
118
226
 
119
227
  - The auto-started gateway runs until you stop it or log out. With auto-start off, the gateway must run before the first Grok turn; until it has created its secret file, Grok turns fail with a message that names the file and the command.
120
- - Run one gateway for each port and leader socket. A second `npm run server` with the default settings exits with `EADDRINUSE` and leaves the running gateway and its leader alone. [Run a separate gateway](docs/usage.md#run-a-second-isolated-gateway) for a demo or a test.
228
+ - Run one gateway for each port and leader socket. A second default launch exits with `EADDRINUSE` and leaves the running gateway and its leader alone. [Run a separate gateway](docs/usage.md#run-a-second-isolated-gateway) for a demo or a test.
121
229
  - Grok's native tool calls are not Pi tool calls. Pi records them as thinking text and `grok-tool` entries, and no model receives those entries.
122
- - Pi compaction and Grok compaction are separate. Pi sends only the new messages of each turn. Only when it creates a new Grok session does it also send the earlier Pi transcript as text, cut to the last 60,000 characters.
230
+ - Pi compaction and Grok compaction are separate. Pi sends only the new messages of each turn, and only a new Grok session also receives the earlier Pi transcript as text, cut to the last 60,000 characters. Pi's `/compact` does not compact Grok's history; use `/grok compact`.
123
231
  - Grok reads the lent Pi tool list once for each Grok session. A changed tool set needs a new Pi session.
124
- - A gateway restart loses the turn in progress. The next turn reconnects and loads the same Grok session.
125
- - Hashline edits (`hashline_read`, `hashline_edit`, `hashline_grep`) occur only when `~/.grok/config.toml` sets `[toolset] file_toolset = "hashline"`. Otherwise Grok uses tools such as `read_file` and `search_replace`.
126
- - After a switch from `grok/*` to another model in the same Pi session, mid-turn Enter goes to that model, not to the earlier Grok session. The Grok session stays stored and is used again when you switch back.
232
+ - A gateway restart loses the turn in progress. The next turn reconnects and loads the same Grok session. No saved probe covers that restart, so live recovery is unverified ([docs/launch-verification.md](docs/launch-verification.md) lists reconnect as not run).
233
+ - Hashline edits (`hashline_read`, `hashline_edit`, `hashline_grep`) occur only when `~/.grok/config.toml` sets `[toolset] file_toolset = "hashline"`. Otherwise Grok uses tools such as `read_file` and `search_replace`. That switch is Grok's own configuration; this repository does not test it, though `evidence/hooks-probe.json` does show hashline calls.
127
234
  - The provider does not call Pi's `onPayload` and `onResponse` stream hooks.
128
- - Cost per token is set to zero in the model metadata. The per-turn cost comes from Grok's report.
235
+ - Cost per token is zero in the model metadata. The per-turn cost comes from Grok's report, converted at 1e9 ticks per US dollar. That ratio is inferred by cross-check against SuperGrok rates and is not documented by Grok (`src/model/session.ts`). Without a usage report, usage and cost read as zero — that is not evidence of free usage.
129
236
 
130
237
  ## Safety
131
238
 
132
- - Grok runs with the permissions of your operating-system user. The Grok session directory is not a sandbox.
133
- - The gateway listens on loopback only and requires the bearer secret for the WebSocket. It never starts Grok with `--always-approve`. Grok sessions use Grok's `default` permission mode unless you set `grokMode`.
134
- - `/grok perms yolo`, `grokMode: "yolo"` or `"auto"`, and `headlessPermissions: "allow"` each remove a check. Use them only in a workspace you can lose. [usage.md](docs/usage.md#grok-permission-prompts) explains how they differ.
135
- - Pi sends its system prompt to Grok as session rules. Pi sends user messages and, for a new Grok session, the earlier Pi transcript.
136
- - `postEditCheck` and `stopCheck` run as shell commands (`bash -lc`) in the working directory.
137
- - Pi packages run code with your permissions. Read the source before you install it.
239
+ - Grok runs with the permissions of your operating-system user. The Grok session directory is not a sandbox, and Pi packages run code with your permissions. Read the source before you install it.
240
+ - The gateway listens on loopback only and requires the bearer secret for the WebSocket. It never starts Grok with `--always-approve`. Grok sessions use Grok's `default` permission mode unless you set `grokMode` (`scripts/server.ts`, `src/model/connection.ts`).
241
+ - `/grok perms yolo`, `grokMode: "yolo"` or `"auto"`, and `headlessPermissions: "allow"` each remove a different check. Use them only in a workspace you can lose. [usage.md](docs/usage.md#grok-permission-prompts) explains how they differ.
242
+ - Headless use does not imply approval. By default, headless Pi cancels Grok's permission prompts and returns a cancelled answer to questions (`src/model/permissions.ts`, `src/model/questions.ts`).
243
+ - Hook-handler errors fail open: a throwing handler continues the turn (`src/model/session.ts`). On timeout the gateway guard is stricter and differs by tier — it denies an unanswered pre-tool gate and rejects an unanswered permission prompt, but continues post-tool and stop hooks past their deadlines (`scripts/server.ts`). These are not equivalent guarantees.
244
+ - Pi sends its system prompt to Grok as session rules, plus user messages and, for a new Grok session, the earlier Pi transcript.
245
+ - `postEditCheck` and `stopCheck` run as shell commands (`bash -lc`) in the working directory. Treat those settings as executable code.
138
246
 
139
247
  Files and network endpoints are listed in [docs/usage.md](docs/usage.md#files-and-network).
140
248
 
249
+ ## What the saved probes establish
250
+
251
+ These are recorded results from one environment, not a guarantee for other versions or platforms. The 2026-09-28 run used Linux, Node.js 26.10.0, Pi 0.87.1, Grok Build 1.0.41, and `grok/grok-4.7`. Eight probes ran in sequence and every one exited 0. They ran against an isolated gateway on port 2429, with the production gateway on 2419 running and untouched throughout — so the saved results did not exercise the default endpoint. Full record: [docs/launch-verification.md](docs/launch-verification.md).
252
+
253
+ | Area | Saved result | Evidence |
254
+ | --- | --- | --- |
255
+ | Native execution | Grok read a token and wrote a file. Pi executed zero tools. This run used `headlessPermissions: "allow"`. | [model-live-gateway-extensions.json](evidence/model-live-gateway-extensions.json) |
256
+ | Pi controls | Read-only denial, post-edit repair, and a stop check passed. | [hooks-live.json](evidence/hooks-live.json) |
257
+ | Lent tools | Grok called a Pi-only tool and answered with the token it returned. | [model-probe.json](evidence/model-probe.json) |
258
+ | Gateway guard | No acknowledgment denied a write. Disconnect prevented a write. An acknowledged dialog accepted a later answer. | [gateway-guard-probe.json](evidence/gateway-guard-probe.json) |
259
+ | MCP gate | A tool marked read-only reached its server. An unmarked tool was denied. | [mcp-gate-probe.json](evidence/mcp-gate-probe.json) |
260
+ | Questions | The answer from a Pi dialog reached Grok. | [question-probe.json](evidence/question-probe.json) |
261
+ | Images | `image_gen` produced a JPEG. An attached image worked by file path, and did not work as an ACP image block. | [image-probe.json](evidence/image-probe.json) |
262
+
263
+ One caveat worth stating plainly: [hooks-probe.json](evidence/hooks-probe.json) records injected `additionalContext` and reports that it was not reflected in the answer. Context delivery alone does not prove that Grok follows it.
264
+
265
+ Live steering, restart recovery, image editing, and video generation remain unverified here. The saved results do not establish those capabilities.
266
+
141
267
  ## Documentation
142
268
 
143
- - [docs/usage.md](docs/usage.md): settings, lent tools, permissions, gateway guard, hooks, `/grok` commands, isolated gateway, checks, and troubleshooting.
144
- - [docs/first-class-model.md](docs/first-class-model.md): design, turn mapping, and the development record.
145
- - [docs/demo.md](docs/demo.md): a reproducible storyboard for a 30 to 60 second demo.
269
+ - [docs/usage.md](docs/usage.md) — settings, lent tools, permissions, the gateway guard, hooks, `/grok` commands, the isolated gateway, checks, and troubleshooting. Ships in the npm package.
270
+ - [docs/first-class-model.md](docs/first-class-model.md) — design, turn mapping, and the development record. Ships in the npm package.
271
+ - [docs/demo.md](docs/demo.md) — a reproducible storyboard for a 30 to 60 second demo. It is a recording plan, not an existing capture.
272
+ - [docs/launch-verification.md](docs/launch-verification.md) — the recorded live runs and install-path checks, with versions, behind the verified claims in this README.
273
+ - [AGENTS.md](AGENTS.md) — the source map, invariants, test commands, and the documentation-claim rule for changes to this repository.
274
+
275
+ On npmjs.com, relative links like these are rewritten to the matching file on GitHub, so they resolve on both pages. In an installed `node_modules` copy only `README.md`, `LICENSE`, `docs/usage.md`, and `docs/first-class-model.md` are present.
146
276
 
147
277
  ## Checks
148
278
 
279
+ These commands run from a clone of the repository, not from the installed package:
280
+
149
281
  ```sh
150
282
  npm install # development dependencies, including TypeScript
151
283
  npm run check # tsc --noEmit
152
284
  npm test # unit tests plus gateway tests against a fake grok binary, no Grok calls
153
285
  ```
154
286
 
155
- `test/gateway.test.ts` starts the real gateway with `test/fixtures/fake-grok.ts` as the Grok binary. It checks leader ownership at startup and shutdown, and the guard's answers on Pi's wire: one answer per request, the deadline for each tier, and fail-closed on disconnect.
156
-
157
- The live probes in `scripts/` use your Grok login, cost Grok usage, and write results to `evidence/`. Create that directory first. See [Live probes](docs/usage.md#live-probes).
287
+ `test/gateway.test.ts` starts the real gateway with `test/fixtures/fake-grok.ts` as the Grok binary, in a scratch `HOME`. It checks leader ownership at startup and shutdown, and the guard's answers on Pi's wire: one answer per request, the deadline for each tier, and fail-closed on disconnect. Nothing in `npm test` contacts Grok.
158
288
 
159
- ## Relation to other ACP clients
289
+ The rest of the unit tests cover the turn split around a lent tool call, abort and resend, prompt tail selection, display-only messages, usage mapping, tool classification and gates, `/grok perms` modes, guard tier validation, question dialogs, `/grok login` output parsing, steering against a mocked Grok, the steer handler across a model switch, `/grok` command timeout, and media copies.
160
290
 
161
- Grok Build can serve ACP to any ACP client. This package connects that agent to Pi as a model provider. Pi's transcript, dialogs, tool gates, lent tools, and model selection then apply to Grok's own harness.
291
+ Before packaging, run `npm pack --dry-run`: the `files` list in `package.json` decides the tarball. The live probes in `scripts/` use your Grok login, spend Grok usage, and write to `evidence/`, which you must create first. Run them only intentionally — see [live probes](docs/usage.md#live-probes). `scripts/reconnect-probe.ts` hardcodes the default port and stops that gateway, so it never runs isolated.
162
292
 
163
293
  ## For coding agents
164
294
 
165
- To evaluate or set up this package, use the [requirements](#requirements), [quick start](#quick-start), and [first-result check](#first-result).
166
- The [reference](docs/usage.md) lists exact settings and commands.
167
- Headless use needs attention: the default policy cancels Grok permission prompts, and questions receive a cancelled answer.
168
- Read [headless permission behavior](docs/usage.md#grok-permission-prompts) before unattended use. Do not assume automatic approval.
169
- For changes to this repository, read [AGENTS.md](AGENTS.md). It contains the source map and test commands.
295
+ To evaluate or set up this package, use [Requirements](#requirements), [Quick start](#quick-start), and the [first-result check](#first-result). The [reference](docs/usage.md) lists exact settings and commands.
296
+
297
+ Select a model by ID when you drive Pi non-interactively; the IDs are in [Models](#models). Headless use needs attention: the default policy cancels Grok permission prompts, and questions receive a cancelled answer. Read [headless permission behavior](docs/usage.md#grok-permission-prompts) before unattended use. Do not assume automatic approval.
298
+
299
+ For changes to this repository, read [AGENTS.md](AGENTS.md). It contains the source map, the invariants, and the rule that every documentation claim points to source, a unit test, or a probe result.
170
300
 
171
301
  ## Feedback
172
302
 
173
- [Report a first-run problem](https://github.com/JangMan-J/pi-grok-agent/issues/new?template=first-run.yml), or [open an issue](https://github.com/JangMan-J/pi-grok-agent/issues). Include the output of `node --version`, `pi --version`, and `grok --version`, the model ID, the prompt, what you expected, and what you saw. The `/grok debug` output helps.
303
+ [Report a first-run problem](https://github.com/JangMan-J/pi-grok-agent/issues/new?template=first-run.yml), or [open an issue](https://github.com/JangMan-J/pi-grok-agent/issues). Include:
304
+
305
+ - the output of `node --version`, `pi --version`, and `grok --version`;
306
+ - the model ID, the prompt, and the last setup step that succeeded;
307
+ - what you expected and what you saw;
308
+ - a redacted excerpt of `/grok debug`, if you have one.
174
309
 
175
310
  Before you post, remove secrets, tokens, session IDs, and private paths from all output. Do not attach raw logs or session files. Post a short excerpt that you have read, preferably from a synthetic demo project.
176
311
 
package/docs/usage.md CHANGED
@@ -67,6 +67,8 @@ A clone also auto-starts the gateway: it runs `scripts/server.ts` from the check
67
67
  | `grok/grok-4.6` | Grok 4.6 | low, medium, high, xhigh |
68
68
  | `grok/grok-4.5` | Grok 4.5 | low, medium, high |
69
69
 
70
+ Which of these a Grok account may use depends on the account. Grok reports the allowed models for each session; a free account on 2026-09-28 had only `grok-4.7`. Pi switches the Grok session to the model picked in `/models`. A model the account lacks fails the turn with the list of available models; before 0.1.1, Grok silently ran its default model instead.
71
+
70
72
  Each model has a 500,000-token context window and a 32,000-token output limit in Pi's metadata. Per-token cost is zero in the metadata. The turn cost comes from Grok's `turn_completed` report, converted at 1e9 ticks per US dollar. That ratio is inferred from Grok's rates. It is not documented by Grok.
71
73
 
72
74
  Pi controls work as usual:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-grok-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "Pi model provider for Grok Build: Grok keeps its native harness and tools while Pi drives the session over a local WebSocket ACP gateway",
@@ -129,6 +129,7 @@ export function createGrokStream(connection: GrokModelConnection, sessions: Sess
129
129
  session.piToolNames = piTools.map((t) => t.name);
130
130
  session.tools = selectPiTools(piTools, sessions.piTools ?? 'extensions');
131
131
  await session.attach(getCurrentSystemPrompt(context.messages) || undefined);
132
+ await session.applyModel(model.id); // before the effort: grok-4.5 has no xhigh
132
133
  await session.applyEffort(options?.reasoning); // Pi's thinking level drives Grok's reasoning_effort
133
134
  signal?.throwIfAborted();
134
135
  const { history, tail } = splitTail(context.messages);
@@ -135,13 +135,33 @@ export class GrokModelSession {
135
135
  if (this.activePrompt) { this.activePrompt = undefined; this.rejectParked('Grok connection dropped; the turn was lost.'); }
136
136
  this.reconnected = this.connection.lastDrop ?? 'reconnected';
137
137
  }
138
- const { sessionId } = await this.connection.attachSession({
138
+ const { sessionId, response } = await this.connection.attachSession({
139
139
  sessionId: this.grokSessionId, cwd: this.cwd, serverId: this.serverId, serverName: 'pi', rules,
140
140
  offerPiTools: this.tools.length > 0, grokMode: this.grokMode,
141
141
  handlers: { onUpdate: (n) => this.onUpdate(n), onMcp: (m) => this.onMcp(m), onPermission: (r) => this.permission(r), onHookRun: (p, gate) => this.onHookRun(p, gate), onHookEvent: (p) => { void this.onHookRun(p); }, onQuestion: (q) => this.ask(q), onSessionExt: (u) => this.onSessionExt(u) },
142
142
  });
143
143
  this.grokSessionId = sessionId;
144
144
  this.attachedGeneration = this.connection.generation;
145
+ // Grok reports the session's model and the models this account may use as the `model` config option.
146
+ const modelOption = ((response as { configOptions?: { id?: string; currentValue?: string; options?: { value?: string }[] }[] }).configOptions ?? []).find((o) => o.id === 'model');
147
+ if (modelOption) {
148
+ this.grokModel = modelOption.currentValue;
149
+ this.grokModels = (modelOption.options ?? []).map((o) => o.value).filter((v): v is string => !!v);
150
+ }
151
+ }
152
+
153
+ /** Grok's current model for this session, and the models the signed-in account may use, from the `model` config option. */
154
+ grokModel?: string;
155
+ grokModels?: string[];
156
+
157
+ /** Switch Grok to the model picked in Pi. Without this Grok runs its default model whatever Pi shows. */
158
+ async applyModel(modelId: string) {
159
+ if (modelId === this.grokModel) return;
160
+ if (this.grokModels?.length && !this.grokModels.includes(modelId)) {
161
+ throw new Error(`${modelId} is not available on this Grok account. Available: ${this.grokModels.join(', ')}. Pick one of those in /models.`);
162
+ }
163
+ await this.setConfigOption('model', modelId);
164
+ this.grokModel = modelId;
145
165
  }
146
166
 
147
167
  detach() {
@@ -436,20 +456,23 @@ export class GrokModelSession {
436
456
 
437
457
  /** Saved media path from a Grok media tool result (`{ type: "ImageGen", path, filename, session_folder }` and kin). */
438
458
  export function mediaPath(value: unknown): string | undefined {
439
- const v = (value ?? {}) as Record<string, any>;
459
+ const v = (value ?? {}) as { path?: unknown; type?: unknown };
440
460
  return typeof v.path === 'string' && /^(ImageGen|ImageEdit|ImageToVideo|ReferenceToVideo|VideoGen)$/.test(String(v.type ?? '')) ? v.path : undefined;
441
461
  }
442
462
 
463
+ /** The fields `resultText` looks at in a Grok tool result envelope. */
464
+ type ResultEnvelope = { FileContent?: { raw_output?: unknown; content?: unknown }; output?: unknown; stdout?: unknown; text?: unknown; content?: unknown; message?: unknown };
465
+
443
466
  /** Best-effort plain text from a Grok tool result envelope (e.g. ReadFile.FileContent.raw_output, or a string). */
444
467
  export function resultText(value: unknown, limit = 8000): string | undefined {
445
468
  if (value == null) return undefined;
446
469
  if (typeof value === 'string') return value.slice(0, limit);
447
470
  const media = mediaPath(value);
448
471
  if (media) return media;
449
- const v = value as Record<string, any>;
472
+ const v = value as ResultEnvelope;
450
473
  const nested = v.FileContent?.raw_output ?? v.FileContent?.content ?? v.output ?? v.stdout ?? v.text ?? v.content ?? v.message;
451
474
  if (typeof nested === 'string') return nested.slice(0, limit);
452
- if (Array.isArray(nested)) return nested.map((c) => (typeof c === 'string' ? c : c?.text ?? '')).join('').slice(0, limit) || undefined;
475
+ if (Array.isArray(nested)) return nested.map((c: unknown) => (typeof c === 'string' ? c : String((c as { text?: unknown } | null)?.text ?? ''))).join('').slice(0, limit) || undefined;
453
476
  return JSON.stringify(value).slice(0, limit);
454
477
  }
455
478