Skip to content

feat(grok): manage Grok plugins through grok plugin - #602

Merged
Tryanks merged 3 commits into
mainfrom
feat/grok-plugins
Oct 6, 2026
Merged

Tryanks merged 3 commits into
mainfrom
feat/grok-plugins

Conversation

@Tryanks

@Tryanks Tryanks commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Summary

Grok plugin management behind the provider-native plugin contract, through grok plugin only.

 crates/agent/src/
 ├── grok.rs
+│   └── grok/plugins.rs            # list / install / uninstall / update / marketplaces via `grok plugin`
 ├── lib.rs
+│   Caps[Grok].plugin_management   # Install, Uninstall, Update; marketplaces; ReloadOrNextSession
+│   ApplyNote::ReloadOrNextSession # skills/commands/hooks/MCP apply after /reload-plugins or next session
 └── tests/fixtures/plugins/
+    └── grok/                      # recorded grok 1.0.46 output, COMMANDS.md, `* -text`

Install consent follows the Claude path's rule: --trust is passed only after the user accepted the challenge whose sha256 is the SHA-256 of Grok's own refusal text.

install alpha@mkt
  grok plugin install alpha@mkt            → exit 1, refusal text
  AcceptCommand { command, sha256(text) }
  on accept(sha256)
    if sha256 != sha256(fresh refusal)     → challenge again, nothing installed
    grok plugin install alpha@mkt --trust  → Done

Deliberately not offered, because the CLI cannot report or scope them honestly: enable/disable (no grok plugin read reports enablement; grok inspect reports a disabled plugin as enabled), uninstall of one plugin from a multi-plugin repository (Grok removes the whole repository), update of path installs. A direct install's source kind comes from grok plugin details (kind: local: / kind: git:), never from host path syntax.

Evidence

Every operation through the production path (examples/probe plugins grok) against the real grok 1.0.46 in a throwaway GROK_HOME, each followed by a native read:

  • add marketplace (path and file://) → grok plugin marketplace list --json shows local / git
  • install: first run → AcceptCommand{sha256: 73bbb8b2…}, native list []; stale hash → challenge again, native list []; correct hash → Installed 1 plugin(s) from mkt: alpha, native list shows alpha 1.0.0
  • update after a version bump → updated 1.0.0 → 1.1.0 (local) and 1.0.0 → 1.2.0 (git); native list agrees
  • uninstall → native details zeta → Plugin "zeta" not found
  • remove marketplace with installed plugins → listing's confirm set ["alpha@mkt"] matches Grok's uninstalled 1 plugin(s): alpha-…

Settings → Plugins, Grok section (light, default width): entries with their kind badge (Git / local path), user scope, install path, "enable state unknown", and the apply note. Not looked at: dark theme, narrow width.

Tests replay recorded CLI output (provenance in COMMANDS.md): listing ids/actions/confirm sets, direct-install kind from details, --trust only for an accepted current hash, argv never consents and applies GROK_HOME, update outcome as Grok prints it.

Checks (macOS): cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo nextest run --workspace --locked (936 passed, 11 skipped), cargo machete, wasm / iOS-sim / Android checks with -D warnings, and Windows cross-compile Clippy for agent, services and runtime. Each commit builds and passes Clippy.

Two tests failed on Windows in an earlier run of this code and were settled per CONTRIBUTING: source-kind classification was a product defect (host path syntax) → now read from details; the sha256 mismatch was the checkout converting the hashed fixture to CRLF → .gitattributes pins those fixtures. This PR's CI is their first Windows run since.

Merge Danger

Door: two-way — revert restores PluginManagement::NONE for Grok; no persisted state is written by Tcode (Grok's own files stay the truth).

Blast Radius: Grok-only

Off by default: per-provider plugin management defaults to off except Codex, so nothing runs until the user enables Grok in Settings → Plugins. Not verified: Windows execution of the plugin tests (CI), a real xAI login, and whether a running session picks up changes without /reload-plugins (the apply note says reload or next session, as observed).

Grok's native plugin system behind the provider plugin contract, through
its non-interactive CLI only, one bounded process per command (null stdin;
with an inherited non-terminal stdin `grok plugin` fails with ENXIO).
Verified against grok 1.0.46 in throwaway HOME/GROK_HOME, each operation
followed by a native read (fixtures and provenance in
tests/fixtures/plugins/grok).

- Listing: `plugin list --json --available` and `plugin marketplace list
  --json`, plus `plugin details` per installed plugin (its description and
  native text). Marketplace plugins are `name@marketplace`, as `install`
  takes them; the other commands take the plain name. Grok installs for the
  user only.
- Install: without `--trust` Grok explains what installing activates and
  prints the command that proceeds. That becomes the existing AcceptCommand
  challenge (Grok's command and text, bound by their SHA-256), and
  `--trust` is passed only on a re-run whose challenge still hashes to the
  accepted value; anything else is shown again.
- Update is offered for marketplace installs (it refreshes a git
  marketplace's checkout itself); a path install is a live link that
  `update` leaves as it is.
- Uninstall is not offered for a plugin of a multi-plugin repository:
  Grok then needs `--confirm` and removes them all, and the contract's
  uninstall carries no consent. `--confirm` and `--force` are never passed.
- Marketplaces: add and remove. Removal uninstalls the marketplace's
  plugins, which the listing reports so the host confirms first.
- Enable/Disable are left out: no `grok plugin` read reports enablement,
  and `grok inspect` (text and --json) reports a disabled plugin as
  enabled; a session confirmed the disabled plugin did not load.

ApplyNote gains ReloadOrNextSession: against a running session, an install
(skills, commands, hooks, an MCP server) and an update both applied after
`/reload-plugins`, and in sessions started afterwards, but not before.
…ils`

A plugin installed from a path or a URL has no marketplace, and the
listing classified it by whether its `source` parsed as an absolute path
on the host. Grok reports the kind itself: `plugin details`, which the
listing already runs for every installed plugin, prints `kind: local:
<path>` or `kind: git: <url>` per repository. The kind now comes from
there, so it no longer depends on the host's path syntax (on Windows the
recorded `/tmp/…` source read as a git URL), and a git install is
classified from Grok's word instead of by elimination.

Recorded against grok 1.0.46: the same two-plugin repository installed
from a `file://` URL (`plugin-list-git-install.json`,
`details-git-install.txt`), and a relative path install, whose source Grok
makes absolute against its cwd.
…eckout

The install challenge's SHA-256 is taken over Grok's stderr exactly as
printed. A checkout that converts line endings (core.autocrlf on Windows)
turned the recorded LF into CRLF, so the test hashed bytes Grok never
printed. The fixtures are now never converted.

The product needs no change for this: the challenge and its acceptance are
both hashed on the host from the same Grok's output, so they agree
whatever line endings Grok prints.
@Tryanks
Tryanks merged commit f7f09b9 into main Oct 6, 2026
7 checks passed
@Tryanks
Tryanks deleted the feat/grok-plugins branch October 6, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant