§9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok' crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party license archives, README provenance, and the required 'Based on Grok Build Open Source' attribution, now sourced from version_attribution.txt). Wire-visible renames (both sides in this repo, changed in lockstep): - Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode). - Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* / _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps a read-side alias for the legacy '_x.ai/session/update' method so existing updates.jsonl histories load; writes emit only the new name (both directions test-pinned). - Agent types grok-build* → kigi* with a documented legacy-prefix alias at resolution time so persisted sessions keep resolving. - ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build implementation dirs renamed to kigi*. - x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes grokday/groknight → kigiday/kiginight (old persisted values fall back to the default theme), web_fetch allowlist xAI hosts → kimi.com + moonshot platforms, changelog CDN → this repo, grok-build changelog archives deleted. - BYOK default endpoint removed: [endpoints] api_base_url is now truly optional with NO default — consumers fail fast with the flag name when unset (no silent x.ai egress). Mock harnesses inject it explicitly. - System-prompt identity fixed: 'released by xAI' → 'an unofficial community CLI for Kimi' (template + regenerated encrypted form). Also repaired pre-existing grok-era test debt found by the sweep: the stale trace_classify default-model pin, the grok-pager UA label test, pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY test could previously reach the real api.moonshot.cn), and the outdated oauth fixture scope key. Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings); FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed; deny advisories ok.
This commit is contained in:
@@ -133,7 +133,7 @@ kigi-crash-handler = { path = "../kigi-crash-handler" }
|
||||
parking_lot.workspace = true
|
||||
|
||||
# nix: signal handling in notifications/.
|
||||
# signal-hook: SIGWINCH handler for the `grok wrap` PTY wrapper (pty_wrap.rs).
|
||||
# signal-hook: SIGWINCH handler for the `kigi wrap` PTY wrapper (pty_wrap.rs).
|
||||
# libc: poll/read for non-blocking terminal reads in theme/osc11.rs.
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { workspace = true, features = ["signal"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# kigi-tui
|
||||
|
||||
Terminal UI (TUI) for Grok Build. Provides the interactive full-screen interface
|
||||
Terminal UI (TUI) for Kigi. Provides the interactive full-screen interface
|
||||
including the scrollback view, prompt input, session management, and all modal
|
||||
dialogs.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Custom Hooks Guide
|
||||
|
||||
Hooks let you run custom scripts or HTTP requests at key moments during a Grok session — for example, before or after a tool runs, when a session starts or ends, or when the agent sends a notification.
|
||||
Hooks let you run custom scripts or HTTP requests at key moments during a Kigi session — for example, before or after a tool runs, when a session starts or ends, or when the agent sends a notification.
|
||||
|
||||
They are perfect for automation, safety checks, logging, notifications, and integrating with your own tools.
|
||||
|
||||
@@ -29,7 +29,7 @@ Common use cases:
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "echo \"🚀 Grok session started in $(pwd)\"" }
|
||||
{ "type": "command", "command": "echo \"🚀 Kigi session started in $(pwd)\"" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -37,7 +37,7 @@ Common use cases:
|
||||
}
|
||||
```
|
||||
|
||||
3. Start (or restart) a Grok session. The hook runs automatically on `SessionStart`.
|
||||
3. Start (or restart) a Kigi session. The hook runs automatically on `SessionStart`.
|
||||
|
||||
Try it: press `Ctrl+L` on non–VS Code family (or run `/hooks` anywhere — preferred on VS Code / Cursor / Windsurf / Zed) and check the Hooks tab to confirm it's loaded.
|
||||
|
||||
@@ -89,7 +89,7 @@ Key fields:
|
||||
- **command**: Path to executable (relative to the JSON file) or inline shell command.
|
||||
- **timeout**: Seconds before killing the hook (default: 5). Hooks fail open on timeout.
|
||||
|
||||
**Tool name aliases**: Claude-style names like `Bash`, `Edit`, `Read` automatically match Grok's internal names (`run_terminal_cmd`, `search_replace`, `read_file`).
|
||||
**Tool name aliases**: Claude-style names like `Bash`, `Edit`, `Read` automatically match Kigi's internal names (`run_terminal_cmd`, `search_replace`, `read_file`).
|
||||
|
||||
## Writing Hook Scripts
|
||||
|
||||
@@ -124,7 +124,7 @@ For events like `SessionStart` or `PostToolUse`, stdout is ignored. Just exit 0
|
||||
|
||||
### Useful Environment Variables
|
||||
|
||||
Grok injects the following variables into every hook process:
|
||||
Kigi injects the following variables into every hook process:
|
||||
|
||||
- `KIGI_HOOK_EVENT` — the event name (e.g. `pre_tool_use`, `session_start`, `post_tool_use`)
|
||||
- `KIGI_HOOK_NAME` — the full configured name of this hook
|
||||
@@ -168,13 +168,13 @@ config-load time:
|
||||
```json
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${HOME}/.config/grok-hooks/check.sh"
|
||||
"command": "${HOME}/.config/kigi-hooks/check.sh"
|
||||
}
|
||||
```
|
||||
|
||||
Lookup order for each reference:
|
||||
1. The handler's own `env` map.
|
||||
2. The current process environment (the env Grok itself sees).
|
||||
2. The current process environment (the env Kigi itself sees).
|
||||
|
||||
If a reference is unset in both, it's **preserved verbatim** (e.g. `${UNSET}`
|
||||
stays as the literal string). The runtime `sh -c` branch may resolve it later
|
||||
@@ -230,7 +230,7 @@ Hooks from `~/.kigi/hooks/` appear under **Global**, project ones under **Projec
|
||||
Instead of a local script, call a remote endpoint:
|
||||
|
||||
```json
|
||||
{ "type": "http", "url": "https://hooks.example.com/grok-event", "timeout": 15 }
|
||||
{ "type": "http", "url": "https://hooks.example.com/kigi-event", "timeout": 15 }
|
||||
```
|
||||
|
||||
The full event envelope is POSTed as JSON. Useful for webhooks, analytics, or serverless functions.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Hooks & Plugins Guide
|
||||
|
||||
Grok Build supports **hooks** (event-driven shell commands) and **plugins** (bundles of skills, agents, hooks, and MCP servers). Both are managed through a unified modal interface.
|
||||
Kigi supports **hooks** (event-driven shell commands) and **plugins** (bundles of skills, agents, hooks, and MCP servers). Both are managed through a unified modal interface.
|
||||
|
||||
## Opening the Modal
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Getting Started
|
||||
|
||||
Grok Build is a terminal-based AI coding assistant from SpaceXAI. It runs as a TUI (Terminal User Interface) that understands your codebase, executes shell commands, edits files, searches the web, and manages tasks.
|
||||
Kigi is a terminal-based AI coding assistant from SpaceXAI. It runs as a TUI (Terminal User Interface) that understands your codebase, executes shell commands, edits files, searches the web, and manages tasks.
|
||||
|
||||
You can use it interactively as a full-screen TUI, run it headlessly for scripting and CI/CD, or integrate it into editors via the Agent Client Protocol (ACP).
|
||||
|
||||
@@ -37,32 +37,32 @@ The PowerShell installer automatically adds `%USERPROFILE%\.kigi\bin` to your Us
|
||||
Verify the installation:
|
||||
|
||||
```bash
|
||||
grok --version
|
||||
kigi --version
|
||||
```
|
||||
|
||||
Update to the latest version at any time:
|
||||
|
||||
```bash
|
||||
grok update
|
||||
kigi update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## First Launch
|
||||
|
||||
Start Grok by running:
|
||||
Start Kigi by running:
|
||||
|
||||
```bash
|
||||
grok
|
||||
kigi
|
||||
```
|
||||
|
||||
On first launch, Grok opens your browser to authenticate with grok.com. After you sign in, Grok stores your credentials in `~/.kigi/auth.json`, where they persist across sessions. Grok refreshes your credentials automatically and prompts you to sign in again when they can no longer be renewed.
|
||||
On first launch, Kigi opens your browser to authenticate with kigi.com. After you sign in, Kigi stores your credentials in `~/.kigi/auth.json`, where they persist across sessions. Kigi refreshes your credentials automatically and prompts you to sign in again when they can no longer be renewed.
|
||||
|
||||
If you prefer API key authentication (e.g., for CI/CD or environments without a browser), set the `XAI_API_KEY` environment variable instead:
|
||||
|
||||
```bash
|
||||
export XAI_API_KEY="xai-..."
|
||||
grok
|
||||
kigi
|
||||
```
|
||||
|
||||
See [Authentication](02-authentication.md) for the full set of auth options including OIDC, external auth providers, and device code flow.
|
||||
@@ -71,12 +71,12 @@ See [Authentication](02-authentication.md) for the full set of auth options incl
|
||||
|
||||
## Basic Interaction
|
||||
|
||||
Once authenticated, Grok presents a full-screen TUI with two main areas:
|
||||
Once authenticated, Kigi presents a full-screen TUI with two main areas:
|
||||
|
||||
- **Scrollback** -- the conversation history showing your prompts, Grok's responses, tool calls, file edits, and more.
|
||||
- **Scrollback** -- the conversation history showing your prompts, Kigi's responses, tool calls, file edits, and more.
|
||||
- **Prompt** -- the input area at the bottom where you type messages.
|
||||
|
||||
Type a message and press `Enter` to send it. Grok reads files, runs commands, and edits code as needed. Each tool run streams into the scrollback in real time.
|
||||
Type a message and press `Enter` to send it. Kigi reads files, runs commands, and edits code as needed. Each tool run streams into the scrollback in real time.
|
||||
|
||||
Press `Tab` to move focus between the prompt and the scrollback. While a turn is running, `Ctrl+C` cancels it (or clears a non-empty draft first); `Esc` is a no-op mid-turn. Idle, press `Esc` twice within 800ms to clear a non-empty prompt, or (with an empty prompt and conversation messages) to open rewind — see [Keyboard Shortcuts](03-keyboard-shortcuts.md#escape). With the scrollback focused, use the arrow keys to select entries and to collapse or expand them. To navigate with `j`/`k` and fold with `h`/`l` instead, enable Vim mode.
|
||||
|
||||
@@ -99,10 +99,10 @@ The `@` operator opens a fuzzy file picker. By default it respects `.gitignore`
|
||||
|
||||
### Permissions
|
||||
|
||||
By default, Grok asks for permission before executing shell commands or editing files. You can approve individually or toggle always-approve mode:
|
||||
By default, Kigi asks for permission before executing shell commands or editing files. You can approve individually or toggle always-approve mode:
|
||||
|
||||
- Press `Ctrl+O` to toggle always-approve mode
|
||||
- Use the `--yolo` flag at launch: `grok --yolo`
|
||||
- Use the `--yolo` flag at launch: `kigi --yolo`
|
||||
- Type `/always-approve` in the prompt to toggle the mode
|
||||
|
||||
---
|
||||
@@ -115,15 +115,15 @@ Every conversation is a **session**. Sessions are automatically saved to `~/.kig
|
||||
|
||||
- Start a new session: `Ctrl+N` or `/new`
|
||||
- Resume a previous session: `/resume` in the TUI, or `--resume <ID>` from the CLI
|
||||
- Continue the most recent session: `grok -c`
|
||||
- Continue the most recent session: `kigi -c`
|
||||
|
||||
### Scrollback
|
||||
|
||||
The scrollback is the main display area. It shows:
|
||||
|
||||
- **User prompts** -- your messages, rendered as sticky headers
|
||||
- **Agent messages** -- Grok's responses with full markdown rendering and syntax highlighting
|
||||
- **Thinking blocks** -- Grok's reasoning process (collapsible)
|
||||
- **Agent messages** -- Kigi's responses with full markdown rendering and syntax highlighting
|
||||
- **Thinking blocks** -- Kigi's reasoning process (collapsible)
|
||||
- **Tool calls** -- file edits (with inline diffs), command executions, search results, and more
|
||||
- **Task lists** -- TODO items tracking progress
|
||||
|
||||
@@ -131,7 +131,7 @@ Collapse or expand the selected entry with the `Left`/`Right` arrow keys (or `h`
|
||||
|
||||
### Tools
|
||||
|
||||
Grok has built-in tools for:
|
||||
Kigi has built-in tools for:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
@@ -151,7 +151,7 @@ Tools can be extended with [MCP servers](05-configuration.md#mcp-servers) for in
|
||||
Type `/` in the prompt to access commands. These provide quick actions without writing a full prompt:
|
||||
|
||||
```
|
||||
/model grok-build # Switch model
|
||||
/model kigi # Switch model
|
||||
/compact # Compress conversation history
|
||||
/always-approve # Toggle always-approve mode
|
||||
/new # Start a new session
|
||||
@@ -165,54 +165,54 @@ See [Slash Commands](04-slash-commands.md) for the complete reference.
|
||||
|
||||
```bash
|
||||
# Launch the interactive TUI and submit an initial prompt as the first turn
|
||||
grok "fix the failing auth test and run it"
|
||||
kigi "fix the failing auth test and run it"
|
||||
|
||||
# Initial prompt in a new git worktree. Use --worktree=<name> (with `=`) so the
|
||||
# prompt isn't swallowed as the worktree name — `grok -w "refactor module X"`
|
||||
# prompt isn't swallowed as the worktree name — `kigi -w "refactor module X"`
|
||||
# would treat "refactor module X" as the worktree label, not the prompt.
|
||||
grok --worktree=feat "refactor module X"
|
||||
kigi --worktree=feat "refactor module X"
|
||||
|
||||
# Base the worktree on a specific branch (e.g. main) instead of the current HEAD:
|
||||
grok -w --ref main "implement feature from main"
|
||||
kigi -w --ref main "implement feature from main"
|
||||
|
||||
|
||||
# Start in a specific project directory
|
||||
grok --cwd ~/projects/my-app
|
||||
kigi --cwd ~/projects/my-app
|
||||
|
||||
# Add project-specific rules
|
||||
grok --rules "Always use TypeScript. Prefer functional components."
|
||||
kigi --rules "Always use TypeScript. Prefer functional components."
|
||||
|
||||
# Auto-approve all tool executions
|
||||
grok --yolo
|
||||
kigi --yolo
|
||||
|
||||
# Use a specific model
|
||||
grok -m grok-build
|
||||
kigi -m kigi
|
||||
|
||||
# Resume a previous session
|
||||
grok --resume <session-id>
|
||||
kigi --resume <session-id>
|
||||
|
||||
# Continue the most recent session
|
||||
grok -c
|
||||
kigi -c
|
||||
|
||||
# Experimental scrollback-native render mode. Sticky: plain `grok` reopens in
|
||||
# Experimental scrollback-native render mode. Sticky: plain `kigi` reopens in
|
||||
# the mode last chosen via --minimal/--fullscreen (or /minimal//fullscreen).
|
||||
grok --minimal
|
||||
kigi --minimal
|
||||
|
||||
# Back to the standard fullscreen TUI (and make it sticky again)
|
||||
grok --fullscreen
|
||||
kigi --fullscreen
|
||||
|
||||
# Headless mode (for scripts)
|
||||
grok -p "Explain this codebase"
|
||||
kigi -p "Explain this codebase"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Headless Mode
|
||||
|
||||
Run Grok non-interactively for scripting, CI/CD, and automation:
|
||||
Run Kigi non-interactively for scripting, CI/CD, and automation:
|
||||
|
||||
```bash
|
||||
grok -p "Your prompt here"
|
||||
kigi -p "Your prompt here"
|
||||
```
|
||||
|
||||
Output formats:
|
||||
@@ -226,14 +226,14 @@ Output formats:
|
||||
Example CI/CD usage:
|
||||
|
||||
```bash
|
||||
grok -p "Review changes for bugs" --output-format json --yolo | jq -r '.text'
|
||||
kigi -p "Review changes for bugs" --output-format json --yolo | jq -r '.text'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Rules (AGENTS.md)
|
||||
|
||||
Add per-project instructions by creating an `AGENTS.md` file in your repository. Grok reads these files and injects their contents as a project-instructions message at the start of the conversation:
|
||||
Add per-project instructions by creating an `AGENTS.md` file in your repository. Kigi reads these files and injects their contents as a project-instructions message at the start of the conversation:
|
||||
|
||||
```
|
||||
~/.kigi/AGENTS.md # Global rules (apply to all projects)
|
||||
@@ -241,7 +241,7 @@ Add per-project instructions by creating an `AGENTS.md` file in your repository.
|
||||
<cwd>/AGENTS.md # Directory-level rules (highest priority)
|
||||
```
|
||||
|
||||
Deeper files take precedence. Grok also reads `CLAUDE.md` files for compatibility.
|
||||
Deeper files take precedence. Kigi also reads `CLAUDE.md` files for compatibility.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
# Authentication
|
||||
|
||||
Grok supports several authentication methods, including interactive browser login, enterprise single sign-on (SSO), and headless CI/CD runners.
|
||||
Kigi supports several authentication methods, including interactive browser login, enterprise single sign-on (SSO), and headless CI/CD runners.
|
||||
|
||||
---
|
||||
|
||||
## Browser Login (Default)
|
||||
|
||||
On first launch, Grok opens your browser to authenticate with grok.com:
|
||||
On first launch, Kigi opens your browser to authenticate with kigi.com:
|
||||
|
||||
```bash
|
||||
grok
|
||||
kigi
|
||||
```
|
||||
|
||||
Grok stores credentials in `~/.kigi/auth.json` and reuses them across sessions. Grok refreshes access tokens automatically in the background. When a token can't be refreshed, Grok prompts you to sign in again. Credentials without a server-provided expiry fall back to a 30-day lifetime.
|
||||
Kigi stores credentials in `~/.kigi/auth.json` and reuses them across sessions. Kigi refreshes access tokens automatically in the background. When a token can't be refreshed, Kigi prompts you to sign in again. Credentials without a server-provided expiry fall back to a 30-day lifetime.
|
||||
|
||||
### Re-authenticate
|
||||
|
||||
To switch accounts or resolve an authentication problem, run:
|
||||
|
||||
```bash
|
||||
grok login
|
||||
kigi login
|
||||
```
|
||||
|
||||
Running `grok login` starts the sign-in flow again, replacing your cached session. By default, it opens your browser and signs in through SpaceXAI OAuth at `auth.x.ai`. Pass a flag to select a different flow:
|
||||
Running `kigi login` starts the sign-in flow again, replacing your cached session. By default, it opens your browser and signs in through SpaceXAI OAuth at `auth.x.ai`. Pass a flag to select a different flow:
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--oauth` | Sign in through SpaceXAI OAuth at `auth.x.ai`. This is the default, so the flag is optional. |
|
||||
| `--device-auth` (alias `--device-code`) | Sign in with the device-code flow for headless or remote environments. |
|
||||
|
||||
To sign out, run `grok logout`. It takes no flags and clears your cached credentials.
|
||||
To sign out, run `kigi logout`. It takes no flags and clears your cached credentials.
|
||||
|
||||
---
|
||||
|
||||
@@ -39,21 +39,21 @@ For CI/CD, automation, or environments without browser access, use an API key fr
|
||||
|
||||
```bash
|
||||
export XAI_API_KEY="xai-..."
|
||||
grok
|
||||
kigi
|
||||
```
|
||||
|
||||
Grok uses the API key as a fallback when no session token is active. If you have already signed in interactively, the stored session token takes precedence. To fall back to the API key, run `grok logout` or delete `~/.kigi/auth.json`.
|
||||
Kigi uses the API key as a fallback when no session token is active. If you have already signed in interactively, the stored session token takes precedence. To fall back to the API key, run `kigi logout` or delete `~/.kigi/auth.json`.
|
||||
|
||||
---
|
||||
|
||||
## OIDC (Customer SSO)
|
||||
|
||||
Authenticate developers through your own Identity Provider (IdP) -- such as Okta, Azure AD, or Auth0 -- instead of grok.com.
|
||||
Authenticate developers through your own Identity Provider (IdP) -- such as Okta, Azure AD, or Auth0 -- instead of kigi.com.
|
||||
|
||||
### 1. Register a public client in your IdP
|
||||
|
||||
- Grant type: Authorization Code with PKCE (Proof Key for Code Exchange)
|
||||
- Redirect URI: `http://127.0.0.1/callback` -- a loopback address. Grok binds a random port at sign-in time, and most IdPs treat the loopback redirect as port-agnostic per [RFC 8252](https://tools.ietf.org/html/rfc8252).
|
||||
- Redirect URI: `http://127.0.0.1/callback` -- a loopback address. Kigi binds a random port at sign-in time, and most IdPs treat the loopback redirect as port-agnostic per [RFC 8252](https://tools.ietf.org/html/rfc8252).
|
||||
- No client secret. PKCE replaces it.
|
||||
|
||||
### 2. Configure the CLI
|
||||
@@ -62,7 +62,7 @@ Via config file:
|
||||
|
||||
```toml
|
||||
# ~/.kigi/config.toml
|
||||
[grok_com_config.oidc]
|
||||
[kigi_com_config.oidc]
|
||||
issuer = "https://acme.okta.com"
|
||||
client_id = "0oa1b2c3d4e5f6g7h8i9"
|
||||
```
|
||||
@@ -77,10 +77,10 @@ export KIGI_OIDC_CLIENT_ID="0oa1b2c3d4e5f6g7h8i9"
|
||||
You can also override the API endpoint to point at your own proxy:
|
||||
|
||||
```bash
|
||||
export KIGI_CLI_CHAT_PROXY_BASE_URL="https://grok-proxy.acme.com/v1"
|
||||
export KIGI_CLI_CHAT_PROXY_BASE_URL="https://kigi-proxy.acme.com/v1"
|
||||
```
|
||||
|
||||
### 3. Run `grok`
|
||||
### 3. Run `kigi`
|
||||
|
||||
The CLI discovers endpoints via `{issuer}/.well-known/openid-configuration`, opens the IdP login page, and stores tokens in `~/.kigi/auth.json`. Tokens auto-refresh silently via the stored `refresh_token`.
|
||||
|
||||
@@ -101,7 +101,7 @@ When browser-based login isn't possible -- for example, on sandboxed VMs, CI run
|
||||
|
||||
```
|
||||
+--------------+ sh -c +------------------------+
|
||||
| Grok |-------------->| your auth binary |
|
||||
| Kigi |-------------->| your auth binary |
|
||||
| | | |
|
||||
| reads |<-- stdout ----| prints token |
|
||||
| auth.json | | |
|
||||
@@ -109,20 +109,20 @@ When browser-based login isn't possible -- for example, on sandboxed VMs, CI run
|
||||
+--------------+ +------------------------+
|
||||
```
|
||||
|
||||
1. Grok runs your command via `sh -c "<command>"`
|
||||
1. Kigi runs your command via `sh -c "<command>"`
|
||||
2. Your binary runs whatever auth flow it needs (SSO, device code, certificate exchange)
|
||||
3. **stderr** carries human-readable output, such as login URLs and status messages. Grok reads stderr and surfaces it to the user; in the TUI, it turns the first `https://` URL into a clickable sign-in link.
|
||||
4. **stdout** is captured by Grok and saved as the access token
|
||||
5. Exit 0 = success; exit non-zero = Grok falls back to interactive login
|
||||
3. **stderr** carries human-readable output, such as login URLs and status messages. Kigi reads stderr and surfaces it to the user; in the TUI, it turns the first `https://` URL into a clickable sign-in link.
|
||||
4. **stdout** is captured by Kigi and saved as the access token
|
||||
5. Exit 0 = success; exit non-zero = Kigi falls back to interactive login
|
||||
|
||||
### The stdout / stderr Contract
|
||||
|
||||
| Stream | What to print | Who sees it |
|
||||
|--------|---------------|-------------|
|
||||
| **stdout** | The token -- nothing else | Grok (parsed and stored in auth.json) |
|
||||
| **stderr** | Login URLs, status messages, errors | The user (Grok reads stderr and shows the sign-in URL as a clickable link in the TUI) |
|
||||
| **stdout** | The token -- nothing else | Kigi (parsed and stored in auth.json) |
|
||||
| **stderr** | Login URLs, status messages, errors | The user (Kigi reads stderr and shows the sign-in URL as a clickable link in the TUI) |
|
||||
|
||||
**Do not print anything to stdout except the token.** No progress messages, no debug output. Grok reads stdout, trims surrounding whitespace, and parses the result as a token.
|
||||
**Do not print anything to stdout except the token.** No progress messages, no debug output. Kigi reads stdout, trims surrounding whitespace, and parses the result as a token.
|
||||
|
||||
### stdout Token Format
|
||||
|
||||
@@ -138,14 +138,14 @@ eyJhbGciOiJSUzI1NiIs...
|
||||
{"access_token": "eyJhbGciOi...", "refresh_token": "ref-tok", "expires_in": 3600, "issuer": "https://idp.example.com"}
|
||||
```
|
||||
|
||||
Use JSON if your tokens expire and you want Grok to automatically re-run the binary before expiry.
|
||||
Use JSON if your tokens expire and you want Kigi to automatically re-run the binary before expiry.
|
||||
|
||||
JSON fields:
|
||||
|
||||
| Field | Required | Meaning |
|
||||
|-------|----------|---------|
|
||||
| `access_token` | yes | Bearer token Grok sends to the xAI API |
|
||||
| `refresh_token` | no | Stored for reference. Grok refreshes by re-running your binary, not with an OAuth refresh grant |
|
||||
| `access_token` | yes | Bearer token Kigi sends to the xAI API |
|
||||
| `refresh_token` | no | Stored for reference. Kigi refreshes by re-running your binary, not with an OAuth refresh grant |
|
||||
| `expires_in` | no | Token lifetime in seconds; enables proactive refresh before expiry |
|
||||
| `issuer` | no | Identifies the token's issuer |
|
||||
|
||||
@@ -171,7 +171,7 @@ export KIGI_AUTH_TOKEN_TTL=3600
|
||||
|
||||
### Token Refresh
|
||||
|
||||
When Grok needs to refresh an expired token, it re-runs your binary with `KIGI_AUTH_EXPIRED=1` set in the environment. Each run fully replaces the stored credential, so emit the same JSON fields (such as `issuer`) on every invocation, including refreshes. Your binary can use this to take a faster silent-refresh path:
|
||||
When Kigi needs to refresh an expired token, it re-runs your binary with `KIGI_AUTH_EXPIRED=1` set in the environment. Each run fully replaces the stored credential, so emit the same JSON fields (such as `issuer`) on every invocation, including refreshes. Your binary can use this to take a faster silent-refresh path:
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
@@ -198,7 +198,7 @@ echo "{\"access_token\": \"$TOKEN\", \"expires_in\": 3600}"
|
||||
| `KIGI_AUTH_PROVIDER_COMMAND` | Path to your auth binary |
|
||||
| `KIGI_AUTH_PROVIDER_LABEL` | Display name on the TUI login screen (e.g., "Acme Corp") |
|
||||
| `KIGI_AUTH_TOKEN_TTL` | Token lifetime in seconds (for bare-string tokens without `expires_in`) |
|
||||
| `KIGI_AUTH_EXPIRED` | Set to `1` by Grok when re-running the binary for token refresh |
|
||||
| `KIGI_AUTH_EXPIRED` | Set to `1` by Kigi when re-running the binary for token refresh |
|
||||
| `KIGI_AUTH_EARLY_INVALIDATION_SECS` | Seconds before expiry to proactively refresh (default: 300) |
|
||||
|
||||
---
|
||||
@@ -208,10 +208,10 @@ echo "{\"access_token\": \"$TOKEN\", \"expires_in\": 3600}"
|
||||
For headless environments (SSH sessions, Docker containers, remote VMs) where no browser is available locally:
|
||||
|
||||
```bash
|
||||
grok login --device-auth # or: grok login --device-code
|
||||
kigi login --device-auth # or: kigi login --device-code
|
||||
```
|
||||
|
||||
This prints a URL and code to the terminal. Open the URL on any device, enter the code, and complete authentication. Grok polls until the login is confirmed.
|
||||
This prints a URL and code to the terminal. Open the URL on any device, enter the code, and complete authentication. Kigi polls until the login is confirmed.
|
||||
|
||||
You can also implement the device-code flow through an [External Auth Provider](#external-auth-provider) for full control.
|
||||
|
||||
@@ -219,11 +219,11 @@ You can also implement the device-code flow through an [External Auth Provider](
|
||||
|
||||
## Automatic Credential Refresh
|
||||
|
||||
Grok automatically refreshes expired credentials:
|
||||
Kigi automatically refreshes expired credentials:
|
||||
|
||||
- **Before expiry:** If your auth provider returned `expires_in` (JSON output) or you set `auth_token_ttl`, Grok re-runs the auth binary ~5 minutes before expiry.
|
||||
- **On auth error:** If the server returns 401 Unauthorized, Grok refreshes the credentials and retries the request.
|
||||
- **OIDC:** If a `refresh_token` is available, Grok silently refreshes via your IdP without re-opening the browser.
|
||||
- **Before expiry:** If your auth provider returned `expires_in` (JSON output) or you set `auth_token_ttl`, Kigi re-runs the auth binary ~5 minutes before expiry.
|
||||
- **On auth error:** If the server returns 401 Unauthorized, Kigi refreshes the credentials and retries the request.
|
||||
- **OIDC:** If a `refresh_token` is available, Kigi silently refreshes via your IdP without re-opening the browser.
|
||||
|
||||
Tune the refresh buffer:
|
||||
|
||||
@@ -239,22 +239,22 @@ export KIGI_AUTH_EARLY_INVALIDATION_SECS=0
|
||||
|
||||
## Hot Reload
|
||||
|
||||
Grok picks up changes to `~/.kigi/auth.json` automatically. If you update credentials externally (for example, with a script that writes new tokens), Grok uses the new credentials on the next API call without a restart.
|
||||
Kigi picks up changes to `~/.kigi/auth.json` automatically. If you update credentials externally (for example, with a script that writes new tokens), Kigi uses the new credentials on the next API call without a restart.
|
||||
|
||||
---
|
||||
|
||||
## Auth Precedence
|
||||
|
||||
Grok resolves credentials for each request in this order, highest to lowest:
|
||||
Kigi resolves credentials for each request in this order, highest to lowest:
|
||||
|
||||
1. **Per-model `api_key` or `env_key`** -- set under `[model.<name>]` in `config.toml`. Wins whenever present.
|
||||
2. **Active session token** -- obtained through browser, OIDC/OAuth2, or external-provider login and stored in `~/.kigi/auth.json`.
|
||||
3. **`XAI_API_KEY`** -- fallback when no session token is active.
|
||||
|
||||
When more than one login flow is configured, Grok populates the session token from the first available source, highest to lowest:
|
||||
When more than one login flow is configured, Kigi populates the session token from the first available source, highest to lowest:
|
||||
|
||||
1. **External auth provider** (`auth_provider_command`)
|
||||
2. **Enterprise OIDC** -- when OIDC is configured, through `[grok_com_config.oidc]` in `config.toml` or the `KIGI_OIDC_ISSUER` and `KIGI_OIDC_CLIENT_ID` environment variables
|
||||
2. **Enterprise OIDC** -- when OIDC is configured, through `[kigi_com_config.oidc]` in `config.toml` or the `KIGI_OIDC_ISSUER` and `KIGI_OIDC_CLIENT_ID` environment variables
|
||||
3. **SpaceXAI OAuth2 browser login** -- the default
|
||||
|
||||
During a session, the active method handles all mid-session refreshes.
|
||||
@@ -270,8 +270,8 @@ Set `RUST_LOG` to control the verbosity of the file log and headless stderr outp
|
||||
In the TUI, set `KIGI_LOG_FILE` to an absolute path to write logs to that file:
|
||||
|
||||
```bash
|
||||
KIGI_LOG_FILE=/tmp/grok.log RUST_LOG=debug grok
|
||||
tail -f /tmp/grok.log
|
||||
KIGI_LOG_FILE=/tmp/kigi.log RUST_LOG=debug kigi
|
||||
tail -f /tmp/kigi.log
|
||||
```
|
||||
|
||||
`KIGI_LOG_FILE` is treated as a literal file path. A relative value such as `1` writes a file named `1` in the current directory.
|
||||
@@ -279,22 +279,22 @@ tail -f /tmp/grok.log
|
||||
In headless mode, logs go to stderr. Redirect them to a file:
|
||||
|
||||
```bash
|
||||
RUST_LOG=debug grok -p "hello" 2> /tmp/grok.log
|
||||
RUST_LOG=debug kigi -p "hello" 2> /tmp/kigi.log
|
||||
```
|
||||
|
||||
### Common log messages
|
||||
|
||||
| Log message | What it means |
|
||||
|-------------|---------------|
|
||||
| `auth: running external auth provider` | Grok is running your binary |
|
||||
| `auth: external auth provider returned fresh token` | Grok parsed and stored the token |
|
||||
| `auth: running external auth provider` | Kigi is running your binary |
|
||||
| `auth: external auth provider returned fresh token` | Kigi parsed and stored the token |
|
||||
| `auth: external auth provider failed` | Binary exited non-zero or stdout was empty |
|
||||
| `auth: external auth provider timed out (likely needs interactive auth), killing` | Binary did not exit before the timeout and was killed |
|
||||
| `auth: failed to start external auth provider` | Command could not be spawned (binary not found) |
|
||||
|
||||
### Common fixes
|
||||
|
||||
- **"Authentication failed"** -- Run `grok logout` to clear cached credentials, then `grok login` to sign in again.
|
||||
- **"Authentication failed"** -- Run `kigi logout` to clear cached credentials, then `kigi login` to sign in again.
|
||||
- **Token expires too quickly** -- Set `auth_token_ttl` or return `expires_in` in your auth provider's JSON output.
|
||||
- **OIDC redirect fails** -- Ensure your IdP allows loopback redirect URIs (`http://127.0.0.1/callback`).
|
||||
- **External auth provider not found** -- Check that the `auth_provider_command` path is correct and the binary is executable.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Keyboard Shortcuts
|
||||
|
||||
Reference for key bindings in the Grok Build TUI. Bindings are built in and cannot currently be remapped.
|
||||
Reference for key bindings in the Kigi TUI. Bindings are built in and cannot currently be remapped.
|
||||
|
||||
---
|
||||
|
||||
## Input Modes
|
||||
|
||||
Grok has two input modes that control how you navigate the scrollback:
|
||||
Kigi has two input modes that control how you navigate the scrollback:
|
||||
|
||||
- **Simple mode** (default): Arrow keys for navigation, `Shift+Arrow` for turn navigation, `Space` to focus the prompt, and any letter key auto-focuses the prompt.
|
||||
- **Vim mode** (opt-in): `j`/`k` for navigation, `H`/`L` for turn navigation, `J`/`K` for response navigation, `h`/`l` for fold, `e`/`E` for expand/collapse, and `i`/`Tab`/`Space` to focus the prompt.
|
||||
@@ -141,7 +141,7 @@ Actions that affect the agent session, available from the agent screen.
|
||||
|
||||
**Note:** `Ctrl+'` is a Windows alt for `Ctrl+;` — some Windows consoles drop the `Ctrl` modifier on punctuation keys.
|
||||
|
||||
**Note:** `Ctrl+.` needs the Kitty keyboard protocol (or tmux `extended-keys on` so that protocol can pass through). On VS Code / Cursor / Windsurf / Zed integrated terminals, VTE, Apple Terminal, Windows Terminal, JetBrains, tmux with `extended-keys off`, screen, and similar no-KKP setups, Grok advertises **`Ctrl+X`** as the primary shortcuts-cheatsheet key instead. **`Ctrl+X` always works** as a classic control character even when `Ctrl+.` does not. Run `/terminal-setup` if modified keys misbehave in tmux.
|
||||
**Note:** `Ctrl+.` needs the Kitty keyboard protocol (or tmux `extended-keys on` so that protocol can pass through). On VS Code / Cursor / Windsurf / Zed integrated terminals, VTE, Apple Terminal, Windows Terminal, JetBrains, tmux with `extended-keys off`, screen, and similar no-KKP setups, Kigi advertises **`Ctrl+X`** as the primary shortcuts-cheatsheet key instead. **`Ctrl+X` always works** as a classic control character even when `Ctrl+.` does not. Run `/terminal-setup` if modified keys misbehave in tmux.
|
||||
|
||||
---
|
||||
|
||||
@@ -155,17 +155,17 @@ Actions that affect the agent session, available from the agent screen.
|
||||
|
||||
Non-image files insert their absolute path as text instead of a chip.
|
||||
|
||||
> **`Alt+V` on Windows** is grok-specific. Windows Terminal's default `Ctrl+V` only pastes plain text and silently drops image clipboards; `Alt+V` bypasses the interceptor. To use `Ctrl+V` for images too, add `{ "command": null, "keys": "ctrl+v" }` to `actions` in your Windows Terminal `settings.json`.
|
||||
> **`Alt+V` on Windows** is kigi-specific. Windows Terminal's default `Ctrl+V` only pastes plain text and silently drops image clipboards; `Alt+V` bypasses the interceptor. To use `Ctrl+V` for images too, add `{ "command": null, "keys": "ctrl+v" }` to `actions` in your Windows Terminal `settings.json`.
|
||||
|
||||
### Linux PRIMARY and CLIPBOARD
|
||||
|
||||
Linux X11 has two independent text selections:
|
||||
|
||||
- `Ctrl+V` reads **CLIPBOARD**, the explicit copy/cut selection. It never falls back to PRIMARY. To put text there with `xclip`, use `printf %s "text" | xclip -selection clipboard`.
|
||||
- An unmodified middle click in Grok reads **PRIMARY**, the current mouse selection, only when `DISPLAY` is non-empty. Pure X11 can use its native reader fallback; XWayland requires `xclip` or `xsel` on `PATH` so Grok reads the X11 selection rather than Wayland PRIMARY. The press is handled once; the release does not paste again.
|
||||
- An unmodified middle click in Kigi reads **PRIMARY**, the current mouse selection, only when `DISPLAY` is non-empty. Pure X11 can use its native reader fallback; XWayland requires `xclip` or `xsel` on `PATH` so Kigi reads the X11 selection rather than Wayland PRIMARY. The press is handled once; the release does not paste again.
|
||||
- `Shift+Insert` is the terminal-native way to paste selected text. Many terminals also use `Shift+middle click` to bypass application mouse reporting.
|
||||
|
||||
Over SSH, the remote Grok process usually cannot access the terminal's local X11 selection. Use terminal-native `Shift+Insert` or `Shift+middle click` so the local terminal sends the selected text through the PTY.
|
||||
Over SSH, the remote Kigi process usually cannot access the terminal's local X11 selection. Use terminal-native `Shift+Insert` or `Shift+middle click` so the local terminal sends the selected text through the PTY.
|
||||
|
||||
---
|
||||
|
||||
@@ -195,7 +195,7 @@ Send-now is intentionally interruptive — it reads as "stop what you're doing a
|
||||
|
||||
> **Windows (non–VS Code family)**: Some consoles drop the `Ctrl` modifier on `Ctrl+Enter` (it can collapse to bare `Enter` or `Ctrl+J`). Use `Ctrl+I` as the alt — letter-key Ctrl chords are stable everywhere. On VS Code family, use **`Ctrl+L`**.
|
||||
|
||||
> **VS Code family `Ctrl+L`**: Grok uses it for interject and leaves the extensions shortcut unbound (open plugins with `/plugins` or the command palette). If your terminal profile still maps **Clear** (or another command) to `Ctrl+L`, that host binding can steal the chord — rebind or remove it so the PTY receives form feed (`\x0c`).
|
||||
> **VS Code family `Ctrl+L`**: Kigi uses it for interject and leaves the extensions shortcut unbound (open plugins with `/plugins` or the command palette). If your terminal profile still maps **Clear** (or another command) to `Ctrl+L`, that host binding can steal the chord — rebind or remove it so the PTY receives form feed (`\x0c`).
|
||||
|
||||
---
|
||||
|
||||
@@ -208,7 +208,7 @@ Actions available from any screen.
|
||||
| `Ctrl+N` | | Create a new session (optionally in a git worktree) | Yes (double-press within 1000ms) |
|
||||
| `Ctrl+Q` | `Ctrl+D` | Quit the application | Yes (double-press within 1000ms) |
|
||||
|
||||
**VS Code family terminal** (VS Code, Cursor, Windsurf, Zed integrated terminals): `Ctrl+Q` is captured by the host, so Grok makes **`Ctrl+D` the sole quit key** (`Ctrl+Q` is not bound). Half-page-down is rebound to bare **`Shift+D`**. Mid-turn interject uses **`Ctrl+L`** (no alternates) because `Ctrl+Enter` / `Ctrl+I` do not reliably reach the PTY; extensions are opened via `/plugins` instead of `Ctrl+L`.
|
||||
**VS Code family terminal** (VS Code, Cursor, Windsurf, Zed integrated terminals): `Ctrl+Q` is captured by the host, so Kigi makes **`Ctrl+D` the sole quit key** (`Ctrl+Q` is not bound). Half-page-down is rebound to bare **`Shift+D`**. Mid-turn interject uses **`Ctrl+L`** (no alternates) because `Ctrl+Enter` / `Ctrl+I` do not reliably reach the PTY; extensions are opened via `/plugins` instead of `Ctrl+L`.
|
||||
|
||||
> **Returning to the welcome screen has no key binding** — use the `/home` slash command (alias `/welcome`) from inside a session. See [Slash Commands](04-slash-commands.md).
|
||||
|
||||
@@ -304,7 +304,7 @@ Clear (idle): Esc Esc within 800ms (non-empty prompt)
|
||||
Rewind (idle): Esc Esc within 800ms (empty prompt + messages)
|
||||
```
|
||||
|
||||
> **Cmd+A is gated to Ghostty.** Grok's in-app `Cmd+A` handler is only
|
||||
> **Cmd+A is gated to Ghostty.** Kigi's in-app `Cmd+A` handler is only
|
||||
> wired up when the detected terminal is Ghostty. Other terminals
|
||||
> either swallow `Cmd+A` at the terminal layer (Apple Terminal, default
|
||||
> iTerm2) or apply their own in-terminal "Select All" behaviour (Kitty,
|
||||
|
||||
@@ -40,7 +40,7 @@ Compress conversation history to save context window space. Optionally specify w
|
||||
/compact keep the auth implementation details
|
||||
```
|
||||
|
||||
When the context window fills up, Grok auto-compacts at 85% usage (configurable via `[session] auto_compact_threshold_percent` in config.toml).
|
||||
When the context window fills up, Kigi auto-compacts at 85% usage (configurable via `[session] auto_compact_threshold_percent` in config.toml).
|
||||
|
||||
### `/context`
|
||||
|
||||
@@ -131,8 +131,8 @@ Aliases: `/title`
|
||||
Switch to a different model. Accepts model IDs or display names (case-insensitive). For reasoning models you can also pass an effort level as a second argument:
|
||||
|
||||
```
|
||||
/model grok-build
|
||||
/model Grok Build
|
||||
/model kigi
|
||||
/model Kigi
|
||||
/model Reasoning X high
|
||||
```
|
||||
|
||||
@@ -214,7 +214,7 @@ fullscreen) switches to the experimental scrollback-native mode; `/fullscreen`
|
||||
TUI. Both relaunch the pager on the same conversation for this session only —
|
||||
they do not write `config.toml`. Descriptions and the relaunch banner tell you
|
||||
how to switch back (`/fullscreen` ⇄ `/minimal`). The `--minimal` /
|
||||
`--fullscreen` CLI flags are likewise session-scoped. To make plain `grok` open
|
||||
`--fullscreen` CLI flags are likewise session-scoped. To make plain `kigi` open
|
||||
in a given mode by default, use `/settings` → **Default screen mode**, or set
|
||||
`[ui] screen_mode` in `config.toml`.
|
||||
|
||||
@@ -325,7 +325,7 @@ Open the extensions modal on the Skills tab to view installed skills.
|
||||
|
||||
### `/loop [interval] <prompt>`
|
||||
|
||||
Run a prompt on a recurring interval. Specify the interval as `30m`, `1 hour`, or `every 2 days`. If you omit it, Grok prompts you.
|
||||
Run a prompt on a recurring interval. Specify the interval as `30m`, `1 hour`, or `every 2 days`. If you omit it, Kigi prompts you.
|
||||
|
||||
```
|
||||
/loop 30m check deploy status
|
||||
@@ -342,7 +342,7 @@ Recurring tasks auto-expire after 7 days. Cancel with `scheduler_delete` (the jo
|
||||
|
||||
### `/goal`
|
||||
|
||||
Set, manage, or check an autonomous goal. Grok works toward the objective across turns and reports progress.
|
||||
Set, manage, or check an autonomous goal. Kigi works toward the objective across turns and reports progress.
|
||||
|
||||
```
|
||||
/goal Migrate the auth module to the new API
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Configuration
|
||||
|
||||
Grok reads configuration from local config files, environment variables, and
|
||||
Kigi reads configuration from local config files, environment variables, and
|
||||
CLI flags. This document covers the common options.
|
||||
|
||||
---
|
||||
@@ -22,7 +22,7 @@ Configuration is resolved in this order (highest priority first):
|
||||
|
||||
Location: `~/.kigi/config.toml`
|
||||
|
||||
If the file does not exist, Grok uses built-in defaults. Specify only the values you want to override.
|
||||
If the file does not exist, Kigi uses built-in defaults. Specify only the values you want to override.
|
||||
|
||||
### General Settings
|
||||
|
||||
@@ -31,7 +31,7 @@ If the file does not exist, Grok uses built-in defaults. Specify only the values
|
||||
auto_update = true # check for updates on launch
|
||||
|
||||
[models]
|
||||
default = "grok-build" # model used for new sessions
|
||||
default = "kigi" # model used for new sessions
|
||||
|
||||
# Defaults applied to every model; a per-model [model.<id>] value always wins.
|
||||
# See "Custom Models" for the per-model overrides and full details.
|
||||
@@ -97,7 +97,7 @@ simple_mode = false
|
||||
```
|
||||
|
||||
You can also toggle this setting from the settings pane (`/settings` →
|
||||
**Disable vim input mode**); Grok writes your choice to `[ui] simple_mode` in
|
||||
**Disable vim input mode**); Kigi writes your choice to `[ui] simple_mode` in
|
||||
`config.toml`.
|
||||
|
||||
`simple_mode` and `vim_mode` are independent: `simple_mode` changes the prompt
|
||||
@@ -154,7 +154,7 @@ active in the **scrollback** pane. It does not affect the input prompt.
|
||||
| `true` | All vim-style scrollback bindings are active, exactly as listed in [Keyboard Shortcuts](03-keyboard-shortcuts.md). |
|
||||
|
||||
Toggle `vim_mode` at runtime with `/vim-mode`, or from the settings pane
|
||||
(`/settings` → **Vim scrollback navigation**). Grok writes the change to
|
||||
(`/settings` → **Vim scrollback navigation**). Kigi writes the change to
|
||||
`[ui] vim_mode` in `~/.kigi/config.toml` immediately and applies it to every
|
||||
future pager session — including new agents and subagents started in the same
|
||||
process. There is no separate per-session override; whatever is in
|
||||
@@ -166,7 +166,7 @@ navigation, while `simple_mode` controls editing in the prompt.
|
||||
#### Screen Mode
|
||||
|
||||
The `screen_mode` setting under `[ui]` is the **default render mode** for plain
|
||||
`grok` launches. Configure it from `/settings` → **Default screen mode**
|
||||
`kigi` launches. Configure it from `/settings` → **Default screen mode**
|
||||
(restart required), or edit `config.toml` by hand. Both choices write
|
||||
`config.toml`. CLI flags (`--minimal` / `--fullscreen`) and slash commands
|
||||
(`/minimal` / `/fullscreen`) are session-scoped and do **not** write this key —
|
||||
@@ -247,7 +247,7 @@ auth_provider_command = "/usr/local/bin/my-auth-provider"
|
||||
auth_provider_label = "Acme Corp"
|
||||
auth_token_ttl = 3600
|
||||
|
||||
[grok_com_config.oidc]
|
||||
[kigi_com_config.oidc]
|
||||
issuer = "https://acme.okta.com"
|
||||
client_id = "0oa1b2c3d4e5f6g7h8i9"
|
||||
# scopes = ["openid", "profile", "email", "offline_access", "api:access"]
|
||||
@@ -346,7 +346,7 @@ explore = true # enable/disable specific types
|
||||
plan = false
|
||||
|
||||
[subagents.models]
|
||||
explore = "grok-build" # route to different models
|
||||
explore = "kigi" # route to different models
|
||||
```
|
||||
|
||||
To pin the model a subagent uses, set its entry under `[subagents.models]`.
|
||||
@@ -393,7 +393,7 @@ Each cell can be toggled via environment variable or `config.toml`. See the
|
||||
environment-variables reference for the env var names. Resolution order:
|
||||
env var > config.toml > default (on).
|
||||
|
||||
`grok inspect` reports cells that still need session-start resolution as
|
||||
`kigi inspect` reports cells that still need session-start resolution as
|
||||
`?` until a value is available; cells with an explicit env or TOML value
|
||||
use that value. Affected discovery entries report
|
||||
`compatibilityStatus: "unresolved"` in JSON and `[compat unresolved]` in
|
||||
@@ -409,7 +409,7 @@ disabled = ["user/a1b2c3d4/noisy-plugin"]
|
||||
|
||||
### Hints
|
||||
|
||||
The `[hints]` table holds small persisted UI preferences — mostly "stop asking me" opt-outs. Grok writes these for you when you pick a "don't ask again" / "reset in config.toml" option in the TUI, but you can edit or remove them by hand. Deleting a key restores the default behavior.
|
||||
The `[hints]` table holds small persisted UI preferences — mostly "stop asking me" opt-outs. Kigi writes these for you when you pick a "don't ask again" / "reset in config.toml" option in the TUI, but you can edit or remove them by hand. Deleting a key restores the default behavior.
|
||||
|
||||
`[hints]` is read from the **effective config merge** (same precedence as other settings): system managed → user `managed_config.toml` → user `config.toml` → user `requirements.toml` → system `requirements.toml`. Higher-priority layers override lower ones. The TUI only **writes** opt-outs to user `~/.kigi/config.toml`.
|
||||
|
||||
@@ -423,7 +423,7 @@ fork_worktree_mode = "ask" # /fork worktree prompt: "ask" | "always"
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `project_picker_disabled` | bool | `false` | When `true`, skips the picker that asks you to choose a project directory on the first prompt when Grok is launched from a non-project directory (home, Desktop, Downloads, `/tmp`). Set automatically when you choose **"Don't ask me again"** in that picker. Teams can pin this in `managed_config.toml` or `requirements.toml` via `[hints] project_picker_disabled = true`. |
|
||||
| `project_picker_disabled` | bool | `false` | When `true`, skips the picker that asks you to choose a project directory on the first prompt when Kigi is launched from a non-project directory (home, Desktop, Downloads, `/tmp`). Set automatically when you choose **"Don't ask me again"** in that picker. Teams can pin this in `managed_config.toml` or `requirements.toml` via `[hints] project_picker_disabled = true`. |
|
||||
| `memory_modal_fullscreen` | bool | `false` | Remembers whether the memory modal was last opened fullscreen. |
|
||||
| `new_session_worktree_mode` | string | `"never"` | Worktree prompt for `/new`: `ask` shows the popup, `always` creates a worktree, `never` skips it. |
|
||||
| `fork_worktree_mode` | string | `"ask"` | Worktree prompt for `/fork`: `ask`, `always`, or `never`. |
|
||||
@@ -446,7 +446,7 @@ progress_bar = true # show tab progress bar (OSC 9;4)
|
||||
|
||||
[ui.notifications.title]
|
||||
enabled = true
|
||||
items = ["action-required", "spinner", "activity", "session-name", "grok"]
|
||||
items = ["action-required", "spinner", "activity", "session-name", "kigi"]
|
||||
```
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
@@ -458,7 +458,7 @@ items = ["action-required", "spinner", "activity", "session-name", "grok"]
|
||||
| `sleep_prevention` | bool | `true` | Keep the display awake while the agent is working (macOS/Linux). |
|
||||
| `progress_bar` | bool | `true` | Show a progress indicator in the terminal tab (OSC 9;4). |
|
||||
| `title.enabled` | bool | `true` | Set the terminal title to reflect agent state. |
|
||||
| `title.items` | array | (see above) | Items shown in the title bar. Options: `action-required`, `spinner`, `activity`, `session-name`, `cwd`, `model`, `turn-timer`, `grok`. |
|
||||
| `title.items` | array | (see above) | Items shown in the title bar. Options: `action-required`, `spinner`, `activity`, `session-name`, `cwd`, `model`, `turn-timer`, `kigi`. |
|
||||
|
||||
#### Terminal Support Matrix
|
||||
|
||||
@@ -473,10 +473,10 @@ items = ["action-required", "spinner", "activity", "session-name", "grok"]
|
||||
| VS Code | BEL | Yes | No |
|
||||
| Apple Terminal | BEL | No | No |
|
||||
| VTE (GNOME Terminal) | OSC 777 | Yes | No |
|
||||
| Grok Desktop | None (native) | N/A | N/A |
|
||||
| Kigi Desktop | None (native) | N/A | N/A |
|
||||
| Unknown | BEL | No | No |
|
||||
|
||||
When `method = "auto"`, Grok detects the terminal brand and selects the best
|
||||
When `method = "auto"`, Kigi detects the terminal brand and selects the best
|
||||
protocol automatically. Set `method` explicitly to override auto-detection.
|
||||
|
||||
#### Notification Hooks
|
||||
@@ -487,14 +487,14 @@ Run custom commands when events occur. Hooks receive environment variables
|
||||
```toml
|
||||
# macOS native notification
|
||||
[[ui.notifications.hooks]]
|
||||
command = "terminal-notifier -title 'Grok' -message '$KIGI_MESSAGE'"
|
||||
command = "terminal-notifier -title 'Kigi' -message '$KIGI_MESSAGE'"
|
||||
events = ["turn_complete", "approval_required"]
|
||||
only_unfocused = true
|
||||
timeout_secs = 10
|
||||
|
||||
# Push to ntfy server
|
||||
[[ui.notifications.hooks]]
|
||||
command = "curl -s -d '$KIGI_MESSAGE' ntfy.sh/my-grok-alerts"
|
||||
command = "curl -s -d '$KIGI_MESSAGE' ntfy.sh/my-kigi-alerts"
|
||||
events = ["turn_complete"]
|
||||
only_unfocused = true
|
||||
timeout_secs = 10
|
||||
@@ -529,7 +529,7 @@ Then restart tmux. If passthrough is not available (tmux < 3.3), set
|
||||
|
||||
**Focus tracking not working:**
|
||||
Some terminals do not report focus events. If `condition = "unfocused"` never
|
||||
fires, try `condition = "always"` as a fallback. Grok supports focus tracking
|
||||
fires, try `condition = "always"` as a fallback. Kigi supports focus tracking
|
||||
in every detected terminal except Apple Terminal and unrecognized terminals.
|
||||
|
||||
**Sleep prevention not taking effect:**
|
||||
@@ -555,7 +555,7 @@ mixpanel_enabled = false # disable Mixpanel pro
|
||||
trace_upload = false # disable session/trace uploads (inherits the telemetry toggle when unset)
|
||||
```
|
||||
|
||||
Set these only to point telemetry at your own infrastructure or to turn parts of it off. The built-in endpoint and credentials are managed by Grok; leave them unset to use the defaults.
|
||||
Set these only to point telemetry at your own infrastructure or to turn parts of it off. The built-in endpoint and credentials are managed by Kigi; leave them unset to use the defaults.
|
||||
|
||||
The same `[telemetry]` table also configures the **external OpenTelemetry stream** — an independent opt-in (it does not require the telemetry toggle above) that ships a curated, content-free usage schema to your *own* OTLP collector. Collector auth is supplied via `OTEL_EXPORTER_OTLP_HEADERS` and is never stored on disk. See [Monitoring & Usage](24-monitoring-usage.md) for the full schema, env vars, and privacy model.
|
||||
|
||||
@@ -584,12 +584,12 @@ auth_provider_label = "Acme Corp"
|
||||
auth_token_ttl = 3600
|
||||
|
||||
[models]
|
||||
default = "company-grok"
|
||||
default = "company-kigi"
|
||||
|
||||
[model.company-grok]
|
||||
model = "grok-build"
|
||||
base_url = "https://grok-proxy.acme.com/"
|
||||
name = "Grok Build Latest (Proxy)"
|
||||
[model.company-kigi]
|
||||
model = "kigi"
|
||||
base_url = "https://kigi-proxy.acme.com/"
|
||||
name = "Kigi Latest (Proxy)"
|
||||
context_window = 128000
|
||||
|
||||
[features]
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# Theming and Appearance Customization
|
||||
|
||||
Grok Build draws all TUI colors from a central theme. You can switch themes while Grok is running, follow your operating system's light or dark appearance, and adjust scrollback layout, animations, and block styling through configuration files.
|
||||
Kigi draws all TUI colors from a central theme. You can switch themes while Kigi is running, follow your operating system's light or dark appearance, and adjust scrollback layout, animations, and block styling through configuration files.
|
||||
|
||||
---
|
||||
|
||||
## Available Themes
|
||||
|
||||
Grok includes five built-in themes, plus an `auto` option that follows your system appearance:
|
||||
Kigi includes five built-in themes, plus an `auto` option that follows your system appearance:
|
||||
|
||||
| Theme | Config Names | Description | Truecolor Required |
|
||||
|-------|-------------|-------------|--------------------|
|
||||
| **GrokNight** | `groknight`, `grok-night`, `dark` | Neutral dark base with a magenta accent. Default theme. Survives quantization cleanly on 256-color and 16-color terminals. | No |
|
||||
| **GrokDay** | `grokday`, `grok-day`, `light`, `day` | Light theme for bright terminal backgrounds. | No |
|
||||
| **KigiNight** | `kiginight`, `kigi-night`, `dark` | Neutral dark base with a magenta accent. Default theme. Survives quantization cleanly on 256-color and 16-color terminals. | No |
|
||||
| **KigiDay** | `kigiday`, `kigi-day`, `light`, `day` | Light theme for bright terminal backgrounds. | No |
|
||||
| **TokyoNight** | `tokyonight`, `tokyo-night`, `tokyo` | Dark, blue-tinted backgrounds from the Tokyo Night palette. Loses its character when quantized. | Yes |
|
||||
| **RosePineMoon** | `rosepine`, `rose-pine`, `rosepine-moon`, `rose-pine-moon` | Muted dark palette with mauve accents, from the Rosé Pine family. | Yes |
|
||||
| **OscuraMidnight** | `oscura`, `oscura-midnight` | Deep dark base with purple accents. | Yes |
|
||||
@@ -28,7 +28,7 @@ Theme names are case-insensitive. The `auto` option (alias `system`) is document
|
||||
|
||||
### In the TUI
|
||||
|
||||
Run the `/theme` slash command (alias `/t`) to open the theme picker. As you move through the list with the arrow keys, Grok previews each theme in real time. Press Enter to apply and save your choice, or press Escape to revert.
|
||||
Run the `/theme` slash command (alias `/t`) to open the theme picker. As you move through the list with the arrow keys, Kigi previews each theme in real time. Press Enter to apply and save your choice, or press Escape to revert.
|
||||
|
||||
To switch without the picker, pass a name directly:
|
||||
|
||||
@@ -51,20 +51,20 @@ theme = "tokyonight"
|
||||
|
||||
## Auto Theme (System Appearance)
|
||||
|
||||
Set `theme = "auto"` to have Grok follow your operating system's light/dark appearance and switch themes automatically:
|
||||
Set `theme = "auto"` to have Kigi follow your operating system's light/dark appearance and switch themes automatically:
|
||||
|
||||
```toml
|
||||
[ui]
|
||||
theme = "auto"
|
||||
```
|
||||
|
||||
By default, dark mode maps to **GrokNight** and light mode maps to **GrokDay**. Override either mapping with `auto_dark_theme` and `auto_light_theme`:
|
||||
By default, dark mode maps to **KigiNight** and light mode maps to **KigiDay**. Override either mapping with `auto_dark_theme` and `auto_light_theme`:
|
||||
|
||||
```toml
|
||||
[ui]
|
||||
theme = "auto"
|
||||
auto_dark_theme = "tokyonight"
|
||||
auto_light_theme = "grokday"
|
||||
auto_light_theme = "kigiday"
|
||||
```
|
||||
|
||||
`theme = "system"` is an alias for `theme = "auto"`.
|
||||
@@ -78,7 +78,7 @@ auto_light_theme = "grokday"
|
||||
| **Windows** | Reads the system personalization registry |
|
||||
| **SSH / headless** | Falls back to an OSC 11 terminal background query at startup |
|
||||
|
||||
Once running, Grok polls for appearance changes every 5 seconds. Toggling your OS between light and dark mode takes effect within seconds without restarting.
|
||||
Once running, Kigi polls for appearance changes every 5 seconds. Toggling your OS between light and dark mode takes effect within seconds without restarting.
|
||||
|
||||
### Via the Settings Pane
|
||||
|
||||
@@ -88,7 +88,7 @@ Run `/settings` (alias `/config`) and open the **Appearance** category to set th
|
||||
|
||||
## Color Support Detection
|
||||
|
||||
On startup, Grok detects your terminal's color capability level:
|
||||
On startup, Kigi detects your terminal's color capability level:
|
||||
|
||||
| Level | Description | Detection |
|
||||
|-------|-------------|-----------|
|
||||
@@ -96,19 +96,19 @@ On startup, Grok detects your terminal's color capability level:
|
||||
| **256-color** | Indexed palette. RGB values are mapped to the nearest palette entry. | Standard xterm-256color |
|
||||
| **16-color** | ANSI names only. Colors are mapped to the closest ANSI color. | Basic terminal support |
|
||||
|
||||
When you set `NO_COLOR`, Grok emits no color and renders in monochrome.
|
||||
When you set `NO_COLOR`, Kigi emits no color and renders in monochrome.
|
||||
|
||||
Run `/terminal-setup` to see the detected level (`color` row) and which themes the picker offers on this terminal (`themes` row). When truecolor is missing, the issues section explains how to enable it (or that Terminal.app cannot).
|
||||
|
||||
### Automatic Quantization
|
||||
|
||||
Every theme is defined using full RGB values. At startup, Grok quantizes all colors to match the detected capability level. This means:
|
||||
Every theme is defined using full RGB values. At startup, Kigi quantizes all colors to match the detected capability level. This means:
|
||||
|
||||
- On **truecolor** terminals, colors pass through unchanged.
|
||||
- On **256-color** terminals, each RGB value is mapped to the nearest indexed palette entry.
|
||||
- On **16-color** terminals, colors map to ANSI names.
|
||||
|
||||
GrokNight and GrokDay use neutral grays that quantize cleanly. TokyoNight, RosePineMoon, and OscuraMidnight use distinctive tinted backgrounds that lose their character when quantized, which is why the theme picker hides them on non-truecolor terminals.
|
||||
KigiNight and KigiDay use neutral grays that quantize cleanly. TokyoNight, RosePineMoon, and OscuraMidnight use distinctive tinted backgrounds that lose their character when quantized, which is why the theme picker hides them on non-truecolor terminals.
|
||||
|
||||
### Runtime-Generated Colors
|
||||
|
||||
@@ -118,7 +118,7 @@ Colors generated at runtime (syntax highlighting, background blending) are also
|
||||
|
||||
## Cursor Color
|
||||
|
||||
Grok sets your terminal cursor to the current theme's `accent_user` color using the OSC 12 escape sequence, to indicate an active Grok session. The cursor color is:
|
||||
Kigi sets your terminal cursor to the current theme's `accent_user` color using the OSC 12 escape sequence, to indicate an active Kigi session. The cursor color is:
|
||||
|
||||
- Applied on startup and on theme switch.
|
||||
- Reset to the terminal's default on exit via OSC 112.
|
||||
@@ -143,13 +143,13 @@ Use compact mode on small screens to maximize content area.
|
||||
|
||||
## Syntax Highlighting
|
||||
|
||||
Grok bundles three `.tmTheme` files for code-block syntax highlighting and selects one based on the active theme:
|
||||
Kigi bundles three `.tmTheme` files for code-block syntax highlighting and selects one based on the active theme:
|
||||
|
||||
- `grok-night.tmTheme` -- GrokNight, RosePineMoon, and OscuraMidnight
|
||||
- `grok-day.tmTheme` -- GrokDay
|
||||
- `kigi-night.tmTheme` -- KigiNight, RosePineMoon, and OscuraMidnight
|
||||
- `kigi-day.tmTheme` -- KigiDay
|
||||
- `tokyo-night.tmTheme` -- TokyoNight
|
||||
|
||||
Grok selects the matching file automatically when you switch themes. The `.tmTheme` files are built into the binary, so you cannot replace them with your own.
|
||||
Kigi selects the matching file automatically when you switch themes. The `.tmTheme` files are built into the binary, so you cannot replace them with your own.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# MCP Servers
|
||||
|
||||
MCP (Model Context Protocol) servers extend Grok with external tool integrations. They let Grok interact with any service that implements the MCP standard.
|
||||
MCP (Model Context Protocol) servers extend Kigi with external tool integrations. They let Kigi interact with any service that implements the MCP standard.
|
||||
|
||||
---
|
||||
|
||||
## What Are MCP Servers?
|
||||
|
||||
An MCP server is a process that exposes tools to Grok over a standardized protocol. When you configure an MCP server, its tools become available to the model alongside Grok's built-in tools. The model can discover and call these tools during a session.
|
||||
An MCP server is a process that exposes tools to Kigi over a standardized protocol. When you configure an MCP server, its tools become available to the model alongside Kigi's built-in tools. The model can discover and call these tools during a session.
|
||||
|
||||
For example, a GitHub MCP server might expose tools like `create_issue`, `list_pull_requests`, and `search_code`. A database server might expose `query`, `list_tables`, and `describe_schema`.
|
||||
|
||||
@@ -20,7 +20,7 @@ MCP servers are configured in `~/.kigi/config.toml` under `[mcp_servers.<name>]`
|
||||
|
||||
### stdio Transport (Local Process)
|
||||
|
||||
Grok spawns a local process and communicates over stdin/stdout:
|
||||
Kigi spawns a local process and communicates over stdin/stdout:
|
||||
|
||||
```toml
|
||||
[mcp_servers.my-server]
|
||||
@@ -44,7 +44,7 @@ tool_timeouts = { slow_op = 120 } # Per-tool timeout overrides, seconds
|
||||
> inline (full payload spilled under the session `mcp/` folder). Default is
|
||||
> **20_000 bytes**. Override via:
|
||||
>
|
||||
> - env `KIGI_MAX_MCP_OUTPUT_BYTES` or `MAX_MCP_OUTPUT_BYTES` (bytes; Grok-native
|
||||
> - env `KIGI_MAX_MCP_OUTPUT_BYTES` or `MAX_MCP_OUTPUT_BYTES` (bytes; Kigi-native
|
||||
> wins if both set; Claude-style name, but we bound by **bytes** not tokens)
|
||||
> - `config.toml` — user-level (`~/.kigi/config.toml`) **or repo-level**
|
||||
> (`.kigi/config.toml` anywhere on the cwd → git-root chain; the deepest
|
||||
@@ -85,39 +85,39 @@ Manage MCP servers from the command line without editing config files:
|
||||
|
||||
```bash
|
||||
# List configured MCP servers
|
||||
grok mcp list
|
||||
grok mcp list --json # Machine-readable output
|
||||
kigi mcp list
|
||||
kigi mcp list --json # Machine-readable output
|
||||
|
||||
# Add a stdio server. Everything after -- is the server command, so flags
|
||||
# like -y reach the server instead of being parsed by grok.
|
||||
grok mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir
|
||||
# like -y reach the server instead of being parsed by kigi.
|
||||
kigi mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir
|
||||
|
||||
# Add a stdio server with environment variables (-e is repeatable)
|
||||
grok mcp add postgres -e DATABASE_URL=postgres://localhost/mydb -- npx -y @modelcontextprotocol/server-postgres
|
||||
kigi mcp add postgres -e DATABASE_URL=postgres://localhost/mydb -- npx -y @modelcontextprotocol/server-postgres
|
||||
|
||||
# Add a remote HTTP server
|
||||
grok mcp add --transport http sentry https://mcp.sentry.dev/mcp
|
||||
kigi mcp add --transport http sentry https://mcp.sentry.dev/mcp
|
||||
|
||||
# Add a remote server with an authentication header (--header is repeatable)
|
||||
grok mcp add --transport http api https://mcp.example.com/mcp --header "Authorization: Bearer YOUR_TOKEN"
|
||||
kigi mcp add --transport http api https://mcp.example.com/mcp --header "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Add a remote SSE server
|
||||
grok mcp add --transport sse linear https://mcp.linear.app/sse
|
||||
kigi mcp add --transport sse linear https://mcp.linear.app/sse
|
||||
|
||||
# Remove a server
|
||||
grok mcp remove github
|
||||
kigi mcp remove github
|
||||
|
||||
# Diagnose a server's configuration and connectivity
|
||||
grok mcp doctor # Check every configured server
|
||||
grok mcp doctor github # Check one server
|
||||
grok mcp doctor --json # Machine-readable output
|
||||
kigi mcp doctor # Check every configured server
|
||||
kigi mcp doctor github # Check one server
|
||||
kigi mcp doctor --json # Machine-readable output
|
||||
```
|
||||
|
||||
The transport defaults to `stdio`; pass `--transport http` or `--transport sse` for remote servers.
|
||||
|
||||
By default `grok mcp add` writes to `~/.kigi/config.toml` (`--scope user`). Use `--scope project` to write to `.kigi/config.toml` in the current directory instead, which can be committed and shared with your team (see [Project-Scoped MCP Servers](#project-scoped-mcp-servers)). Header and environment variable values are stored verbatim, so reference secrets as `${VAR}` instead of pasting them into a committed project config (see [Example Configurations](#example-configurations)). `grok mcp list` shows servers from both scopes, marking project-scoped ones with `(project)`.
|
||||
By default `kigi mcp add` writes to `~/.kigi/config.toml` (`--scope user`). Use `--scope project` to write to `.kigi/config.toml` in the current directory instead, which can be committed and shared with your team (see [Project-Scoped MCP Servers](#project-scoped-mcp-servers)). Header and environment variable values are stored verbatim, so reference secrets as `${VAR}` instead of pasting them into a committed project config (see [Example Configurations](#example-configurations)). `kigi mcp list` shows servers from both scopes, marking project-scoped ones with `(project)`.
|
||||
|
||||
`grok mcp remove` searches both scopes and exits 0 after removing the server. It exits 1 when the name is not found, or when the name is defined in both user and project scope — pass `--scope` to say which one to remove.
|
||||
`kigi mcp remove` searches both scopes and exits 0 after removing the server. It exits 1 when the name is not found, or when the name is defined in both user and project scope — pass `--scope` to say which one to remove.
|
||||
|
||||
Breaking changes from earlier releases: `--env` now takes one `KEY=value` per flag (use `-e A=1 -e B=2`, not `--env A=1 B=2`), and server names may only contain letters, numbers, hyphens, and underscores.
|
||||
|
||||
@@ -142,9 +142,9 @@ url = "https://mcp.linear.app/mcp"
|
||||
enabled = true
|
||||
```
|
||||
|
||||
When a server exposes a native HTTP/SSE endpoint, prefer the `url` form over wrapping it in a stdio proxy such as `npx mcp-remote <url>`. Grok handles HTTP/SSE and OAuth directly, so the native form avoids an extra subprocess per session. It also registers Grok's own OAuth client with the provider.
|
||||
When a server exposes a native HTTP/SSE endpoint, prefer the `url` form over wrapping it in a stdio proxy such as `npx mcp-remote <url>`. Kigi handles HTTP/SSE and OAuth directly, so the native form avoids an extra subprocess per session. It also registers Kigi's own OAuth client with the provider.
|
||||
|
||||
Grok walks from the current directory up to the git repo root, loading `.kigi/config.toml` at each level:
|
||||
Kigi walks from the current directory up to the git repo root, loading `.kigi/config.toml` at each level:
|
||||
|
||||
| Location | Scope | Priority |
|
||||
|----------|-------|----------|
|
||||
@@ -154,7 +154,7 @@ Grok walks from the current directory up to the git repo root, loading `.kigi/co
|
||||
|
||||
If a project defines a server with the same name as a global one, the project version replaces it entirely (fields are not merged).
|
||||
|
||||
Project-scoped files contribute `[mcp_servers]`, `[plugins]`, and `[permission]` entries. Grok reads most other config sections only from `~/.kigi/config.toml`.
|
||||
Project-scoped files contribute `[mcp_servers]`, `[plugins]`, and `[permission]` entries. Kigi reads most other config sections only from `~/.kigi/config.toml`.
|
||||
|
||||
---
|
||||
|
||||
@@ -169,7 +169,7 @@ MCP tools are namespaced with the server name to avoid collisions:
|
||||
|
||||
## Toggle Servers at Runtime
|
||||
|
||||
You can enable or disable MCP servers during a session without restarting Grok.
|
||||
You can enable or disable MCP servers during a session without restarting Kigi.
|
||||
|
||||
### The /mcps Modal
|
||||
|
||||
@@ -198,24 +198,24 @@ The model has access to two built-in tools for working with MCP servers:
|
||||
|
||||
## Compatibility
|
||||
|
||||
Grok loads MCP server configurations from multiple sources for compatibility:
|
||||
Kigi loads MCP server configurations from multiple sources for compatibility:
|
||||
|
||||
| Source | Format | Location | Configurable |
|
||||
|--------|--------|----------|-------------|
|
||||
| `config.toml` | Native Grok config | `~/.kigi/config.toml`, `.kigi/config.toml` | Always on |
|
||||
| `config.toml` | Native Kigi config | `~/.kigi/config.toml`, `.kigi/config.toml` | Always on |
|
||||
| `.claude.json` | Claude Code format | `~/.claude.json` | `[compat.claude] mcps` |
|
||||
| `.cursor/mcp.json` | Cursor format | `~/.cursor/mcp.json`, `<project>/.cursor/mcp.json` | `[compat.cursor] mcps` |
|
||||
| `.mcp.json` | MCP standard format | Project root (cwd to git root) | Loaded unless you have imported or dismissed the Claude import prompt (the import marker is set) |
|
||||
|
||||
All sources are merged in priority order: config.toml > Claude > Cursor > `.mcp.json`. Servers from higher-priority sources take precedence when names conflict.
|
||||
|
||||
The Claude and Cursor MCP sources are scanned by default. To disable scanning for a specific vendor, set `[compat.<vendor>] mcps = false` in `~/.kigi/config.toml` or the corresponding environment variable (`KIGI_CURSOR_MCPS_ENABLED`, `KIGI_CLAUDE_MCPS_ENABLED`). See [Configuration](05-configuration.md#harness-compatibility) for details. Use `grok inspect` to see which MCP servers were loaded and their vendor origin (`[cursor]`, `[claude]`).
|
||||
The Claude and Cursor MCP sources are scanned by default. To disable scanning for a specific vendor, set `[compat.<vendor>] mcps = false` in `~/.kigi/config.toml` or the corresponding environment variable (`KIGI_CURSOR_MCPS_ENABLED`, `KIGI_CLAUDE_MCPS_ENABLED`). See [Configuration](05-configuration.md#harness-compatibility) for details. Use `kigi inspect` to see which MCP servers were loaded and their vendor origin (`[cursor]`, `[claude]`).
|
||||
|
||||
---
|
||||
|
||||
## MCP OAuth
|
||||
|
||||
For MCP servers that require OAuth authentication, Grok handles the credential flow automatically. When an MCP server requests OAuth credentials, Grok opens a browser-based authorization flow and stores the resulting tokens for future use.
|
||||
For MCP servers that require OAuth authentication, Kigi handles the credential flow automatically. When an MCP server requests OAuth credentials, Kigi opens a browser-based authorization flow and stores the resulting tokens for future use.
|
||||
|
||||
---
|
||||
|
||||
@@ -225,7 +225,7 @@ Use the `url` form for hosted MCP servers and the `command` / `args` form for lo
|
||||
|
||||
### Native HTTP (hosted services)
|
||||
|
||||
You must authenticate OAuth-based MCP servers before you can use them. Grok stores the resulting tokens under `~/.kigi/mcp_credentials.json`. After you edit `config.toml`, press `r` in the `/mcps` modal to refresh the server list.
|
||||
You must authenticate OAuth-based MCP servers before you can use them. Kigi stores the resulting tokens under `~/.kigi/mcp_credentials.json`. After you edit `config.toml`, press `r` in the `/mcps` modal to refresh the server list.
|
||||
|
||||
```toml
|
||||
[mcp_servers.linear]
|
||||
@@ -252,7 +252,7 @@ enabled = true
|
||||
Authorization = "Bearer <token>"
|
||||
```
|
||||
|
||||
To avoid putting secrets in the config file, reference an environment variable with `${VAR}` (or `${VAR:-default}`). Grok expands string fields in `[mcp_servers.*]` — `url`, `command`, `args`, and the values in `env` and `headers` — at load time:
|
||||
To avoid putting secrets in the config file, reference an environment variable with `${VAR}` (or `${VAR:-default}`). Kigi expands string fields in `[mcp_servers.*]` — `url`, `command`, `args`, and the values in `env` and `headers` — at load time:
|
||||
|
||||
```toml
|
||||
[mcp_servers.internal-tools]
|
||||
@@ -285,7 +285,7 @@ tool_timeout_sec = 120
|
||||
tool_timeouts = { slow_analysis = 300, quick_lookup = 10 }
|
||||
```
|
||||
|
||||
On Windows, npm installs launchers like `npx`, `npm`, `pnpm`, and `yarn` as `.cmd` batch shims (there is no `npx.exe`). Grok resolves a bare `command` such as `npx` to its real launcher path on `PATH` (honoring `PATHEXT`) before spawning, so these work without manually wrapping them in `cmd /c`. A `command` given as an absolute path or one containing a path separator is used as-is.
|
||||
On Windows, npm installs launchers like `npx`, `npm`, `pnpm`, and `yarn` as `.cmd` batch shims (there is no `npx.exe`). Kigi resolves a bare `command` such as `npx` to its real launcher path on `PATH` (honoring `PATHEXT`) before spawning, so these work without manually wrapping them in `cmd /c`. A `command` given as an absolute path or one containing a path separator is used as-is.
|
||||
|
||||
---
|
||||
|
||||
@@ -324,7 +324,7 @@ npx -y @modelcontextprotocol/server-filesystem /path
|
||||
startup_timeout_sec = 30
|
||||
```
|
||||
|
||||
For stdio servers, Grok captures the process's standard error to `~/.kigi/logs/mcp/<server>.stderr.log`, truncated on each launch. Check this file when a server starts but fails to handshake:
|
||||
For stdio servers, Kigi captures the process's standard error to `~/.kigi/logs/mcp/<server>.stderr.log`, truncated on each launch. Check this file when a server starts but fails to handshake:
|
||||
|
||||
```bash
|
||||
tail -f ~/.kigi/logs/mcp/filesystem.stderr.log
|
||||
@@ -332,18 +332,18 @@ tail -f ~/.kigi/logs/mcp/filesystem.stderr.log
|
||||
|
||||
### Viewing Server Status
|
||||
|
||||
Use `grok inspect` to see all loaded MCP servers and their sources:
|
||||
Use `kigi inspect` to see all loaded MCP servers and their sources:
|
||||
|
||||
```bash
|
||||
grok inspect # Human-readable
|
||||
grok inspect --json # Machine-readable
|
||||
kigi inspect # Human-readable
|
||||
kigi inspect --json # Machine-readable
|
||||
```
|
||||
|
||||
### Debug Logging
|
||||
|
||||
```bash
|
||||
RUST_LOG=debug KIGI_LOG_FILE=/tmp/grok.log grok
|
||||
tail -f /tmp/grok.log
|
||||
RUST_LOG=debug KIGI_LOG_FILE=/tmp/kigi.log kigi
|
||||
tail -f /tmp/kigi.log
|
||||
```
|
||||
|
||||
Look for log entries containing `mcp` to trace server startup, tool discovery, and tool call execution.
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# Skills
|
||||
|
||||
Skills are reusable prompt packages that extend Grok with task-specific instructions. They let you capture a repeatable procedure once, instead of re-explaining it each session.
|
||||
Skills are reusable prompt packages that extend Kigi with task-specific instructions. They let you capture a repeatable procedure once, instead of re-explaining it each session.
|
||||
|
||||
---
|
||||
|
||||
## What Are Skills?
|
||||
|
||||
A skill is a directory that contains a `SKILL.md` file. Its markdown body tells Grok how to handle a specific type of task: step-by-step instructions, conventions, and tool-usage patterns.
|
||||
A skill is a directory that contains a `SKILL.md` file. Its markdown body tells Kigi how to handle a specific type of task: step-by-step instructions, conventions, and tool-usage patterns.
|
||||
|
||||
Use a skill for a repeatable procedure that's too specific for AGENTS.md but too long to retype. Grok activates a skill only when it applies to your current task.
|
||||
Use a skill for a repeatable procedure that's too specific for AGENTS.md but too long to retype. Kigi activates a skill only when it applies to your current task.
|
||||
|
||||
---
|
||||
|
||||
## Skill Locations
|
||||
|
||||
Grok discovers skills from these directories, in priority order:
|
||||
Kigi discovers skills from these directories, in priority order:
|
||||
|
||||
| Location | Scope | Priority | Notes |
|
||||
|----------|-------|----------|-------|
|
||||
@@ -26,13 +26,13 @@ Grok discovers skills from these directories, in priority order:
|
||||
| `~/.cursor/skills/` | User | Lowest | Cursor compatibility (configurable) |
|
||||
| `./.cursor/skills/` | Local / Repo | High | Project Cursor skills (when cursor compat skills are enabled) |
|
||||
|
||||
Grok deduplicates skills by name -- a higher-priority location overrides a lower one. Grok also scans `.agents/skills/` (and `commands/`) at each tier (alongside `.kigi/`) and walks every directory between your working directory and the repo root.
|
||||
Kigi deduplicates skills by name -- a higher-priority location overrides a lower one. Kigi also scans `.agents/skills/` (and `commands/`) at each tier (alongside `.kigi/`) and walks every directory between your working directory and the repo root.
|
||||
|
||||
Flat `*.md` files under a `commands/` directory become user-invocable slash commands (filename stem = command name), matching Claude Code's legacy custom-command layout.
|
||||
|
||||
Skill and command discovery does **not** use `.gitignore`. Paths under known skill roots (`.kigi/`, `.agents/`, `.claude/`, `.cursor/`) always load when present on disk — teams often ignore `.claude/**` as local-only config while still expecting `/frontend`-style project commands to work. To hide a skill, use `[skills] ignore` in config (not repo ignore rules).
|
||||
|
||||
Grok scans the Claude and Cursor skill directories by default. To stop scanning a vendor, set its `skills` cell to `false` under `[compat.cursor]` or `[compat.claude]` in `~/.kigi/config.toml`, or set the `KIGI_CURSOR_SKILLS_ENABLED` or `KIGI_CLAUDE_SKILLS_ENABLED` environment variable to `false`. See [Configuration](05-configuration.md#harness-compatibility) for details. Grok always filters out known vendor-shipped default skills (such as Cursor's `shell`, `canvas`, and `statusline`), regardless of these settings.
|
||||
Kigi scans the Claude and Cursor skill directories by default. To stop scanning a vendor, set its `skills` cell to `false` under `[compat.cursor]` or `[compat.claude]` in `~/.kigi/config.toml`, or set the `KIGI_CURSOR_SKILLS_ENABLED` or `KIGI_CLAUDE_SKILLS_ENABLED` environment variable to `false`. See [Configuration](05-configuration.md#harness-compatibility) for details. Kigi always filters out known vendor-shipped default skills (such as Cursor's `shell`, `canvas`, and `statusline`), regardless of these settings.
|
||||
|
||||
### Additional Skill Directories
|
||||
|
||||
@@ -45,7 +45,7 @@ ignore = ["~/my-team-skills/wip"] # Paths to exclude (hidden entirely)
|
||||
disabled = ["wip-skill"] # Skill names to keep listed but inactive
|
||||
```
|
||||
|
||||
Each entry in `paths` is a `SKILL.md` file or a directory that Grok walks recursively. `ignore` hides a skill completely; `disabled` keeps it in the list but excludes it from the system prompt and from invocation. `paths` and `ignore` take filesystem paths and support `~` expansion; `disabled` takes skill names.
|
||||
Each entry in `paths` is a `SKILL.md` file or a directory that Kigi walks recursively. `ignore` hides a skill completely; `disabled` keeps it in the list but excludes it from the system prompt and from invocation. `paths` and `ignore` take filesystem paths and support `~` expansion; `disabled` takes skill names.
|
||||
|
||||
---
|
||||
|
||||
@@ -91,10 +91,10 @@ Review staged changes and create a commit with a clear, conventional message.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `name` | Skill identifier. Use lowercase letters, digits, and hyphens, up to 64 characters. Grok normalizes spaces and underscores to hyphens. If you omit `name`, Grok uses the skill's directory name. |
|
||||
| `description` | What the skill does and when to use it. Grok reads this to decide whether to invoke the skill. If you omit it, Grok uses the first paragraph of the body. |
|
||||
| `name` | Skill identifier. Use lowercase letters, digits, and hyphens, up to 64 characters. Kigi normalizes spaces and underscores to hyphens. If you omit `name`, Kigi uses the skill's directory name. |
|
||||
| `description` | What the skill does and when to use it. Kigi reads this to decide whether to invoke the skill. If you omit it, Kigi uses the first paragraph of the body. |
|
||||
|
||||
Write a specific `description`. It determines when Grok invokes the skill automatically. Name the trigger phrases and use cases.
|
||||
Write a specific `description`. It determines when Kigi invokes the skill automatically. Name the trigger phrases and use cases.
|
||||
|
||||
### Optional Frontmatter Fields
|
||||
|
||||
@@ -111,36 +111,36 @@ Multi-word frontmatter keys use kebab-case (single-word keys like `model` are wr
|
||||
| `effort` | Reasoning-effort override. |
|
||||
| `license` | License identifier (for example, `Apache-2.0`). |
|
||||
| `compatibility` | Environment requirements (for example, `Requires git, docker, jq`). |
|
||||
| `metadata` | Arbitrary string key-value pairs. Grok promotes `metadata.author` and `metadata.short-description` for display. |
|
||||
| `metadata` | Arbitrary string key-value pairs. Kigi promotes `metadata.author` and `metadata.short-description` for display. |
|
||||
|
||||
---
|
||||
|
||||
## Creating Skills with /create-skill
|
||||
|
||||
The `/create-skill` command walks you through building a new skill interactively. Grok asks what you want, drafts the files, and writes them to disk.
|
||||
The `/create-skill` command walks you through building a new skill interactively. Kigi asks what you want, drafts the files, and writes them to disk.
|
||||
|
||||
### How It Works
|
||||
|
||||
When you run `/create-skill`, Grok:
|
||||
When you run `/create-skill`, Kigi:
|
||||
|
||||
1. **Gathers requirements.** Grok asks for the skill name, the scope to save it under, and a description of the workflow you want to capture. Use a name with lowercase letters, digits, and hyphens (2–64 characters, starting and ending with a letter or digit).
|
||||
1. **Gathers requirements.** Kigi asks for the skill name, the scope to save it under, and a description of the workflow you want to capture. Use a name with lowercase letters, digits, and hyphens (2–64 characters, starting and ending with a letter or digit).
|
||||
|
||||
2. **Drafts the description.** Grok writes a `description` that states what the skill does, the phrases that trigger it, and the slash command name. You approve or edit the draft before continuing.
|
||||
2. **Drafts the description.** Kigi writes a `description` that states what the skill does, the phrases that trigger it, and the slash command name. You approve or edit the draft before continuing.
|
||||
|
||||
3. **Creates the skill directory.** Grok creates the `<scope>/.kigi/skills/<name>/` directory, plus `scripts/` or `references/` subdirectories when the skill needs them.
|
||||
3. **Creates the skill directory.** Kigi creates the `<scope>/.kigi/skills/<name>/` directory, plus `scripts/` or `references/` subdirectories when the skill needs them.
|
||||
|
||||
4. **Writes SKILL.md.** Grok writes the frontmatter (`name` and `description`) and a markdown body of instructions, along with any supporting files.
|
||||
4. **Writes SKILL.md.** Kigi writes the frontmatter (`name` and `description`) and a markdown body of instructions, along with any supporting files.
|
||||
|
||||
5. **Verifies and confirms.** Grok reads the file back, confirms it wrote correctly, and tells you how to run the skill.
|
||||
5. **Verifies and confirms.** Kigi reads the file back, confirms it wrote correctly, and tells you how to run the skill.
|
||||
|
||||
### Choosing a Scope
|
||||
|
||||
Grok asks where to save the skill:
|
||||
Kigi asks where to save the skill:
|
||||
|
||||
- **Project** (`<repo_root>/.kigi/skills/<name>/`) -- available only in this repository and shareable with teammates through version control. Grok recommends this scope inside a git repository.
|
||||
- **Project** (`<repo_root>/.kigi/skills/<name>/`) -- available only in this repository and shareable with teammates through version control. Kigi recommends this scope inside a git repository.
|
||||
- **User** (`~/.kigi/skills/<name>/`) -- available across all your projects.
|
||||
|
||||
The new skill appears in the slash menu within a few seconds, because Grok reloads skills when files change on disk.
|
||||
The new skill appears in the slash menu within a few seconds, because Kigi reloads skills when files change on disk.
|
||||
|
||||
---
|
||||
|
||||
@@ -161,11 +161,11 @@ Running a skill loads its instructions into the conversation and directs the mod
|
||||
/commit fix the build
|
||||
```
|
||||
|
||||
To browse your skills, type `/` to open the slash-command menu. Grok lists every built-in command and skill and filters them as you type. To list skills from the command line instead, run `grok inspect` (see [Viewing Skill Details](#viewing-skill-details)).
|
||||
To browse your skills, type `/` to open the slash-command menu. Kigi lists every built-in command and skill and filters them as you type. To list skills from the command line instead, run `kigi inspect` (see [Viewing Skill Details](#viewing-skill-details)).
|
||||
|
||||
### Qualified Names
|
||||
|
||||
When a skill's name collides with another skill or a built-in command, Grok advertises a qualified name prefixed by the skill's scope -- `local:`, `repo:`, `user:`, or the plugin name. Use the qualified form to choose a specific skill:
|
||||
When a skill's name collides with another skill or a built-in command, Kigi advertises a qualified name prefixed by the skill's scope -- `local:`, `repo:`, `user:`, or the plugin name. Use the qualified form to choose a specific skill:
|
||||
|
||||
```
|
||||
/local:commit # The "commit" skill from ./.kigi/skills/
|
||||
@@ -174,7 +174,7 @@ When a skill's name collides with another skill or a built-in command, Grok adve
|
||||
|
||||
### Automatic Invocation
|
||||
|
||||
Grok can invoke a skill on its own when it recognizes a relevant task. Grok matches your prompt against the skill's `description` and `when-to-use` fields, so write both to describe the triggering situation.
|
||||
Kigi can invoke a skill on its own when it recognizes a relevant task. Kigi matches your prompt against the skill's `description` and `when-to-use` fields, so write both to describe the triggering situation.
|
||||
|
||||
For example, if a skill's description says "Use when the user wants to commit changes," then saying "commit my changes" can trigger that skill automatically. To require an explicit slash command and prevent automatic invocation, set `disable-model-invocation: true` in the frontmatter.
|
||||
|
||||
@@ -182,14 +182,14 @@ For example, if a skill's description says "Use when the user wants to commit ch
|
||||
|
||||
## Viewing Skill Details
|
||||
|
||||
Run `grok inspect` to see every skill Grok discovers, along with the rest of your configuration:
|
||||
Run `kigi inspect` to see every skill Kigi discovers, along with the rest of your configuration:
|
||||
|
||||
```bash
|
||||
grok inspect # Human-readable summary
|
||||
grok inspect --json # Machine-readable report
|
||||
kigi inspect # Human-readable summary
|
||||
kigi inspect --json # Machine-readable report
|
||||
```
|
||||
|
||||
In the human-readable output, the Skills section lists each skill's name and its source -- `project`, `user`, `bundled`, `config` (a `[skills].paths` entry), `server` (skills synced from the skill store in managed workspaces), or `plugin: <name>`. Grok tags any skill disabled via `[skills].disabled` or from a disabled vendor surface with `[disabled]`.
|
||||
In the human-readable output, the Skills section lists each skill's name and its source -- `project`, `user`, `bundled`, `config` (a `[skills].paths` entry), `server` (skills synced from the skill store in managed workspaces), or `plugin: <name>`. Kigi tags any skill disabled via `[skills].disabled` or from a disabled vendor surface with `[disabled]`.
|
||||
|
||||
The report honors your `[skills]` config the same way a live session does: skills from `paths` are listed, skills under an `ignore` prefix are hidden, and skills named in `disabled` stay listed but tagged `[disabled]`.
|
||||
|
||||
@@ -199,9 +199,9 @@ The `--json` report includes the full detail for each skill: its `name`, `descri
|
||||
|
||||
## Bundled and Plugin Skills
|
||||
|
||||
Grok ships with built-in skills and extracts them to `~/.kigi/skills/` on startup -- among them `/create-skill`, `/help`, and `/check-work`. Bundled skills behave like user skills, and a same-named skill in a higher-priority location (local or repo) overrides the bundled copy; `grok inspect` labels the extracted copies `bundled` so they stay distinguishable from skills you authored yourself. (A plugin skill of the same name does not override it; it stays available under its qualified `plugin:name` form.)
|
||||
Kigi ships with built-in skills and extracts them to `~/.kigi/skills/` on startup -- among them `/create-skill`, `/help`, and `/check-work`. Bundled skills behave like user skills, and a same-named skill in a higher-priority location (local or repo) overrides the bundled copy; `kigi inspect` labels the extracted copies `bundled` so they stay distinguishable from skills you authored yourself. (A plugin skill of the same name does not override it; it stays available under its qualified `plugin:name` form.)
|
||||
|
||||
Skills can also come from plugins. When you install a plugin that includes skills, they appear alongside your user and project skills. `grok inspect` labels each plugin-provided skill with its source as `plugin: <name>`.
|
||||
Skills can also come from plugins. When you install a plugin that includes skills, they appear alongside your user and project skills. `kigi inspect` labels each plugin-provided skill with its source as `plugin: <name>`.
|
||||
|
||||
See the [Plugins guide](09-plugins.md) for more on installing plugins that provide skills.
|
||||
|
||||
@@ -211,7 +211,7 @@ See the [Plugins guide](09-plugins.md) for more on installing plugins that provi
|
||||
|
||||
1. **Write specific descriptions.** The description drives automatic invocation. "Create git commits" is too vague; "Create well-formatted git commits following conventional commit standards. Use when the user wants to commit changes or asks for /commit." works better.
|
||||
|
||||
2. **Include concrete steps.** Skills work best when they give Grok a clear, ordered procedure to follow.
|
||||
2. **Include concrete steps.** Skills work best when they give Kigi a clear, ordered procedure to follow.
|
||||
|
||||
3. **Reference tools by name.** When a skill relies on specific tools (such as `run_terminal_command` or `search_replace`), name them so the model knows what to use.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ A plugin is a directory that holds any combination of these components:
|
||||
- **MCP servers** -- a `.mcp.json` file of server configurations
|
||||
- **LSP servers** -- a `.lsp.json` file of language server configurations
|
||||
|
||||
If a plugin includes a `plugin.json` manifest, the manifest can override paths or add metadata; otherwise components load from the convention directories. The manifest is optional: without one, Grok discovers the components above from their standard directories.
|
||||
If a plugin includes a `plugin.json` manifest, the manifest can override paths or add metadata; otherwise components load from the convention directories. The manifest is optional: without one, Kigi discovers the components above from their standard directories.
|
||||
|
||||
For example, a `team-tools` plugin might include a deploy skill, a code-review agent, pre-commit hooks, and a Linear MCP server. Install them together in one step.
|
||||
|
||||
@@ -28,25 +28,25 @@ Plugin hooks receive two environment variables beyond the standard ones set for
|
||||
| `KIGI_PLUGIN_ROOT` | Absolute path to the plugin's installed directory. |
|
||||
| `KIGI_PLUGIN_DATA` | Absolute path to the plugin's writable data directory, for plugin state, caches, and logs. |
|
||||
|
||||
Grok sets these values and overrides any value you declare for the same key in the hook JSON's `env` map. (Grok also sets the `CLAUDE_PLUGIN_ROOT` and `CLAUDE_PLUGIN_DATA` aliases for compatibility.) See the [Hooks guide](10-hooks.md) for every environment variable passed to hooks.
|
||||
Kigi sets these values and overrides any value you declare for the same key in the hook JSON's `env` map. (Kigi also sets the `CLAUDE_PLUGIN_ROOT` and `CLAUDE_PLUGIN_DATA` aliases for compatibility.) See the [Hooks guide](10-hooks.md) for every environment variable passed to hooks.
|
||||
|
||||
---
|
||||
|
||||
## Plugin locations
|
||||
|
||||
Grok discovers plugins from these locations, in priority order:
|
||||
Kigi discovers plugins from these locations, in priority order:
|
||||
|
||||
| Location | Scope | Trust |
|
||||
|----------|-------|-------|
|
||||
| `_meta.pluginDirs` (`session/new` / `session/load`) | Session -- loaded for that session only | Trusted automatically |
|
||||
| `--plugin-dir` (CLI flag, `grok agent`) | Process -- loaded for that agent process only | Trusted automatically |
|
||||
| `--plugin-dir` (CLI flag, `kigi agent`) | Process -- loaded for that agent process only | Trusted automatically |
|
||||
| `.kigi/plugins/` | Project -- shared with the team through version control | Requires trust |
|
||||
| `~/.kigi/plugins/` | User -- personal plugins for every project | Trusted automatically |
|
||||
| `[plugins].paths` (config) | Custom directories you add in `config.toml` | Depends on location |
|
||||
|
||||
Grok also reads the `.claude/plugins/` equivalents for compatibility. When two plugins share a name, the higher-priority location wins.
|
||||
Kigi also reads the `.claude/plugins/` equivalents for compatibility. When two plugins share a name, the higher-priority location wins.
|
||||
|
||||
The Agent SDKs load per-session plugins through `GrokOptions.plugins`, which arrives as `_meta.pluginDirs` on `session/new` and `session/load`; because the caller controls the directory, these plugins are always trusted -- their hooks and MCP servers activate without a prompt, and they never persist beyond the session. The `--plugin-dir` flag is the process-wide equivalent for direct CLI use (repeatable: `grok agent --no-leader --plugin-dir A --plugin-dir B stdio`); it applies to dedicated agent processes only and is ignored in leader mode (the shared leader discovers its own plugins).
|
||||
The Agent SDKs load per-session plugins through `KigiOptions.plugins`, which arrives as `_meta.pluginDirs` on `session/new` and `session/load`; because the caller controls the directory, these plugins are always trusted -- their hooks and MCP servers activate without a prompt, and they never persist beyond the session. The `--plugin-dir` flag is the process-wide equivalent for direct CLI use (repeatable: `kigi agent --no-leader --plugin-dir A --plugin-dir B stdio`); it applies to dedicated agent processes only and is ignored in leader mode (the shared leader discovers its own plugins).
|
||||
|
||||
---
|
||||
|
||||
@@ -88,18 +88,18 @@ Use these keys in the Plugins tab:
|
||||
### Plugin commands
|
||||
|
||||
```bash
|
||||
grok plugin list [--json] [--available] # List installed plugins (--available requires --json)
|
||||
grok plugin install <source> --trust # Git URL, GitHub shorthand (user/repo), or local path
|
||||
grok plugin uninstall <name> [--confirm] [--keep-data] # Aliases: rm, remove
|
||||
grok plugin update [<name>] # Omit the name to update all plugins
|
||||
grok plugin enable <name>
|
||||
grok plugin disable <name>
|
||||
grok plugin details <name> # Show the plugin's component inventory
|
||||
grok plugin validate [<path>] # Validate plugin.json (default: current directory)
|
||||
grok plugin tag [<path>] [--push] [--force] [--dry-run] # Tag a release from the manifest version
|
||||
kigi plugin list [--json] [--available] # List installed plugins (--available requires --json)
|
||||
kigi plugin install <source> --trust # Git URL, GitHub shorthand (user/repo), or local path
|
||||
kigi plugin uninstall <name> [--confirm] [--keep-data] # Aliases: rm, remove
|
||||
kigi plugin update [<name>] # Omit the name to update all plugins
|
||||
kigi plugin enable <name>
|
||||
kigi plugin disable <name>
|
||||
kigi plugin details <name> # Show the plugin's component inventory
|
||||
kigi plugin validate [<path>] # Validate plugin.json (default: current directory)
|
||||
kigi plugin tag [<path>] [--push] [--force] [--dry-run] # Tag a release from the manifest version
|
||||
```
|
||||
|
||||
Run `grok plugin install <source>` without `--trust` and Grok prints the source and warns that installing will activate the plugin's hooks, MCP servers, and skills, then stops without installing. Add `--trust` to install it.
|
||||
Run `kigi plugin install <source>` without `--trust` and Kigi prints the source and warns that installing will activate the plugin's hooks, MCP servers, and skills, then stops without installing. Add `--trust` to install it.
|
||||
|
||||
The `<source>` argument accepts:
|
||||
|
||||
@@ -124,21 +124,21 @@ disable_plugins = true
|
||||
|
||||
Enabling a plugin loads its skills, slash commands, and agents. Trust is separate and controls whether a plugin's code runs: even for an enabled plugin, its hooks, MCP servers, and LSP servers stay inactive until you trust it. This prevents an untrusted repository from running code on your machine.
|
||||
|
||||
Grok trusts plugins from `~/.kigi/plugins/` automatically. Project plugins in `.kigi/plugins/` require explicit trust. To trust a plugin, install it with `--trust`:
|
||||
Kigi trusts plugins from `~/.kigi/plugins/` automatically. Project plugins in `.kigi/plugins/` require explicit trust. To trust a plugin, install it with `--trust`:
|
||||
|
||||
```bash
|
||||
grok plugin install <source> --trust
|
||||
kigi plugin install <source> --trust
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Inspect plugins
|
||||
|
||||
Run `grok inspect` to see every discovered plugin and what it provides:
|
||||
Run `kigi inspect` to see every discovered plugin and what it provides:
|
||||
|
||||
```bash
|
||||
grok inspect # Show plugins with their skills, agents, hooks, and MCP servers
|
||||
grok inspect --json # Emit machine-readable JSON
|
||||
kigi inspect # Show plugins with their skills, agents, hooks, and MCP servers
|
||||
kigi inspect --json # Emit machine-readable JSON
|
||||
```
|
||||
|
||||
Plugin-provided components appear in their sections (Skills, Agents, MCP Servers, and so on) with a `plugin: <name>` label, so you can see where each component originates.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Hooks
|
||||
|
||||
Hooks let you run a script or send an HTTP request at key moments in a Grok session. Use them to automate tasks, enforce safety checks, log activity, send notifications, and integrate your own tools.
|
||||
Hooks let you run a script or send an HTTP request at key moments in a Kigi session. Use them to automate tasks, enforce safety checks, log activity, send notifications, and integrate your own tools.
|
||||
|
||||
---
|
||||
|
||||
## What Are Hooks?
|
||||
|
||||
A hook is a shell command or HTTP endpoint that Grok calls when a specific lifecycle event occurs. Hooks can:
|
||||
A hook is a shell command or HTTP endpoint that Kigi calls when a specific lifecycle event occurs. Hooks can:
|
||||
|
||||
- **Block actions** -- A `PreToolUse` hook can deny a dangerous command before it runs.
|
||||
- **React to events** -- A `PostToolUse` hook can log every tool execution to a file.
|
||||
@@ -41,7 +41,7 @@ A hook is a shell command or HTTP endpoint that Grok calls when a specific lifec
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "echo 'Grok session started in '$(pwd)" }
|
||||
{ "type": "command", "command": "echo 'Kigi session started in '$(pwd)" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -49,7 +49,7 @@ A hook is a shell command or HTTP endpoint that Grok calls when a specific lifec
|
||||
}
|
||||
```
|
||||
|
||||
3. Start (or restart) a Grok session. The hook runs automatically on `SessionStart`.
|
||||
3. Start (or restart) a Kigi session. The hook runs automatically on `SessionStart`.
|
||||
|
||||
4. Press `Ctrl+L` on non–VS Code family terminals (or run `/hooks` anywhere — preferred on VS Code family) and check the Hooks tab to confirm it loaded.
|
||||
|
||||
@@ -100,7 +100,7 @@ Because hooks are unified under folder-trust, a `--trust` / `/hooks-trust` grant
|
||||
|
||||
### Cursor Hook Compatibility
|
||||
|
||||
Grok accepts Cursor's camelCase hook event names, so `~/.cursor/hooks.json` loads unchanged:
|
||||
Kigi accepts Cursor's camelCase hook event names, so `~/.cursor/hooks.json` loads unchanged:
|
||||
|
||||
| Cursor event | Maps to |
|
||||
|---|---|
|
||||
@@ -145,7 +145,7 @@ Each `.json` file can define hooks for multiple events:
|
||||
|
||||
### Key Fields
|
||||
|
||||
- **Event name** (top-level key): any event listed in [Hook Events](#hook-events). Grok skips unrecognized event names so a shared Claude or Cursor settings file still loads.
|
||||
- **Event name** (top-level key): any event listed in [Hook Events](#hook-events). Kigi skips unrecognized event names so a shared Claude or Cursor settings file still loads.
|
||||
- **matcher** (optional): A regular expression that selects which invocations trigger the hook. It applies to the tool events — `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, and `PermissionDenied` — where it tests the tool name, and to `Notification`, where it tests the notification type. The lifecycle events (`SessionStart`, `SessionEnd`, `Stop`, `UserPromptSubmit`) reject a matcher; other events ignore it. An empty or omitted matcher matches everything. The matcher tests the real tool name; MCP calls routed through the internal `use_tool` dispatcher appear as the qualified `server__tool` name (e.g. `linear__save_issue`), so match on that, not the dispatcher name.
|
||||
- **type**: `"command"` (run a script or shell one-liner) or `"http"` (POST the event to a URL).
|
||||
- **command**: Path to executable (relative to the JSON file) or inline shell command.
|
||||
@@ -153,7 +153,7 @@ Each `.json` file can define hooks for multiple events:
|
||||
|
||||
### Tool Name Aliases
|
||||
|
||||
In a `matcher`, Grok maps Claude-style tool names to its own so hooks migrated from Claude fire correctly. Common aliases include:
|
||||
In a `matcher`, Kigi maps Claude-style tool names to its own so hooks migrated from Claude fire correctly. Common aliases include:
|
||||
|
||||
- `Bash` → `run_terminal_command`
|
||||
- `Read` → `read_file`
|
||||
@@ -206,7 +206,7 @@ For events like `SessionStart` or `PostToolUse`, stdout is ignored. Just exit 0
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Grok sets several environment variables on every hook process. These are useful when writing context-aware or plugin-aware hook scripts.
|
||||
Kigi sets several environment variables on every hook process. These are useful when writing context-aware or plugin-aware hook scripts.
|
||||
|
||||
#### Runner-injected variables (always available)
|
||||
|
||||
@@ -216,7 +216,7 @@ These variables are set by the hook runner for **every** hook:
|
||||
|-----------------------|-------------|
|
||||
| `KIGI_HOOK_EVENT` | The name of the event that triggered the hook (e.g. `pre_tool_use`, `session_start`, `post_tool_use`, `session_end`, `stop`, `notification`). |
|
||||
| `KIGI_HOOK_NAME` | The configured name of this specific hook (includes the plugin prefix for plugin-provided hooks). |
|
||||
| `KIGI_SESSION_ID` | The unique identifier of the current Grok session. |
|
||||
| `KIGI_SESSION_ID` | The unique identifier of the current Kigi session. |
|
||||
| `KIGI_WORKSPACE_ROOT` | Absolute path to the root of the current workspace. |
|
||||
| `CLAUDE_PROJECT_DIR` | Absolute path to the workspace root. A Claude Code-compatible alias for `KIGI_WORKSPACE_ROOT`, set for every hook. |
|
||||
|
||||
@@ -224,7 +224,7 @@ These variables are **reserved**. Any values you attempt to set for them via the
|
||||
|
||||
#### Plugin hook variables
|
||||
|
||||
When a hook originates from a plugin, Grok additionally injects the following variables:
|
||||
When a hook originates from a plugin, Kigi additionally injects the following variables:
|
||||
|
||||
| Variable | Description |
|
||||
|----------------------|-------------|
|
||||
@@ -261,7 +261,7 @@ Both `command` and `url` support `${VAR}` and `$VAR` expansion. See the custom-h
|
||||
Instead of a local script, call a remote endpoint:
|
||||
|
||||
```json
|
||||
{ "type": "http", "url": "https://hooks.example.com/grok-event", "timeout": 15 }
|
||||
{ "type": "http", "url": "https://hooks.example.com/kigi-event", "timeout": 15 }
|
||||
```
|
||||
|
||||
The full event envelope is POSTed as JSON.
|
||||
@@ -308,7 +308,7 @@ Enable or disable an individual hook at runtime by pressing `Space` in the Hooks
|
||||
|
||||
### Mid-Session Reload
|
||||
|
||||
Press `r` in the Hooks tab to reload all hooks from disk. Grok re-reads every hook source, so this picks up changes you made to hook files during the session.
|
||||
Press `r` in the Hooks tab to reload all hooks from disk. Kigi re-reads every hook source, so this picks up changes you made to hook files during the session.
|
||||
|
||||
---
|
||||
|
||||
@@ -378,4 +378,4 @@ echo '{"decision": "allow"}'
|
||||
- **Hook not running?** Press `Ctrl+L` on non–VS Code family (or run `/hooks` anywhere) to see if it is loaded and matched.
|
||||
- **Project hooks ignored?** The folder may be untrusted. Run `/hooks-trust` (or relaunch with `--trust`).
|
||||
- **Script not found?** Check the path is relative to the `.json` file and executable (`chmod +x`).
|
||||
- **See errors?** Capture logs by launching with `RUST_LOG=debug KIGI_LOG_FILE=/tmp/grok.log grok`, then check `/tmp/grok.log`.
|
||||
- **See errors?** Capture logs by launching with `RUST_LOG=debug KIGI_LOG_FILE=/tmp/kigi.log kigi`, then check `/tmp/kigi.log`.
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# Custom Models
|
||||
|
||||
Grok connects to custom model endpoints for alternative providers, self-hosted models, and overriding built-in settings. This guide explains how to select models, configure endpoints, and integrate third-party providers.
|
||||
Kigi connects to custom model endpoints for alternative providers, self-hosted models, and overriding built-in settings. This guide explains how to select models, configure endpoints, and integrate third-party providers.
|
||||
|
||||
---
|
||||
|
||||
## Default Models
|
||||
|
||||
By default, Grok uses models hosted by SpaceXAI, and new sessions start with `grok-build`. Default models require no configuration. Authenticate with `grok login` or an API key, then start a session.
|
||||
By default, Kigi uses models hosted by SpaceXAI, and new sessions start with `kigi`. Default models require no configuration. Authenticate with `kigi login` or an API key, then start a session.
|
||||
|
||||
List all available models:
|
||||
|
||||
```bash
|
||||
grok models
|
||||
kigi models
|
||||
```
|
||||
|
||||
---
|
||||
@@ -21,7 +21,7 @@ grok models
|
||||
### CLI Flag
|
||||
|
||||
```bash
|
||||
grok -p "Hello" -m grok-build
|
||||
kigi -p "Hello" -m kigi
|
||||
```
|
||||
|
||||
### Slash Command
|
||||
@@ -29,13 +29,13 @@ grok -p "Hello" -m grok-build
|
||||
In the TUI, switch models during a session:
|
||||
|
||||
```
|
||||
/model grok-build
|
||||
/model kigi
|
||||
```
|
||||
|
||||
Or use the alias:
|
||||
|
||||
```
|
||||
/m grok-build
|
||||
/m kigi
|
||||
```
|
||||
|
||||
### Model Picker (Ctrl+M)
|
||||
@@ -48,14 +48,14 @@ Set a persistent default in `~/.kigi/config.toml`:
|
||||
|
||||
```toml
|
||||
[models]
|
||||
default = "grok-build"
|
||||
default = "kigi"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Supported API Backends
|
||||
|
||||
Grok supports three API backends. Set `api_backend` in your `[model.*]` config to choose which protocol the model uses:
|
||||
Kigi supports three API backends. Set `api_backend` in your `[model.*]` config to choose which protocol the model uses:
|
||||
|
||||
| Value | API | Default |
|
||||
|-------|-----|---------|
|
||||
@@ -63,9 +63,9 @@ Grok supports three API backends. Set `api_backend` in your `[model.*]` config t
|
||||
| `"responses"` | OpenAI Responses (`/v1/responses`) | |
|
||||
| `"messages"` | Anthropic Messages (`/v1/messages`) | |
|
||||
|
||||
When you omit `api_backend`, Grok uses `chat_completions`.
|
||||
When you omit `api_backend`, Kigi uses `chat_completions`.
|
||||
|
||||
To send provider-specific authentication or version headers -- for example, Anthropic's `x-api-key` -- use the `extra_headers` field described below. Grok sends those headers verbatim with every request to the endpoint.
|
||||
To send provider-specific authentication or version headers -- for example, Anthropic's `x-api-key` -- use the `extra_headers` field described below. Kigi sends those headers verbatim with every request to the endpoint.
|
||||
|
||||
---
|
||||
|
||||
@@ -91,16 +91,16 @@ extra_headers = { "x-api-key" = "sk-..." } # Extra request headers, sent verbati
|
||||
|
||||
### Credential Resolution
|
||||
|
||||
Grok resolves the API key in this order:
|
||||
Kigi resolves the API key in this order:
|
||||
|
||||
1. The `api_key` field in the model config
|
||||
2. The environment variable(s) named by `env_key` — a single string or an array of names. The first set, non-empty value wins (for example `env_key = ["ANTHROPIC_AUTH_TOKEN", "LC_ANTHROPIC_AUTH_TOKEN"]` for SSH `LC_*` forwarding)
|
||||
3. Your signed-in session token (from `grok login`), for a model with no `api_key`/`env_key` of its own
|
||||
4. The `XAI_API_KEY` environment variable (global fallback; Grok also accepts `KIGI_CODE_XAI_API_KEY` for backward compatibility)
|
||||
3. Your signed-in session token (from `kigi login`), for a model with no `api_key`/`env_key` of its own
|
||||
4. The `XAI_API_KEY` environment variable (global fallback; Kigi also accepts `KIGI_CODE_XAI_API_KEY` for backward compatibility)
|
||||
|
||||
### Context Window
|
||||
|
||||
The `context_window` value tells Grok when to trigger auto-compaction. When you override a known model, Grok inherits that model's context window. When you define a new model and omit `context_window`, Grok defaults to 200,000 tokens, so set it explicitly to match your provider.
|
||||
The `context_window` value tells Kigi when to trigger auto-compaction. When you override a known model, Kigi inherits that model's context window. When you define a new model and omit `context_window`, Kigi defaults to 200,000 tokens, so set it explicitly to match your provider.
|
||||
|
||||
### Global Default Headers
|
||||
|
||||
@@ -148,7 +148,7 @@ temperature = 0.5
|
||||
api_key = "sk-custom"
|
||||
```
|
||||
|
||||
When you override a built-in model, Grok starts with the default configuration (including the correct `base_url`), then applies only the fields you specify. Unspecified fields inherit from the default.
|
||||
When you override a built-in model, Kigi starts with the default configuration (including the correct `base_url`), then applies only the fields you specify. Unspecified fields inherit from the default.
|
||||
|
||||
### Priority Order
|
||||
|
||||
@@ -174,7 +174,7 @@ context_window = 200000
|
||||
extra_headers = { "x-api-key" = "sk-ant-...", "anthropic-version" = "2023-06-01" }
|
||||
```
|
||||
|
||||
The `messages` backend uses the Anthropic Messages protocol. Anthropic authenticates with an `x-api-key` header rather than `Authorization: Bearer`, so pass your key through `extra_headers`, which Grok sends verbatim.
|
||||
The `messages` backend uses the Anthropic Messages protocol. Anthropic authenticates with an `x-api-key` header rather than `Authorization: Bearer`, so pass your key through `extra_headers`, which Kigi sends verbatim.
|
||||
|
||||
### OpenAI (Chat Completions)
|
||||
|
||||
@@ -240,14 +240,14 @@ temperature = 0.8
|
||||
|
||||
## Custom Models Endpoint
|
||||
|
||||
Point Grok at a custom OpenAI-compatible `/v1/models` endpoint instead of the default. Use this when your models sit behind a corporate gateway or a self-hosted inference service.
|
||||
Point Kigi at a custom OpenAI-compatible `/v1/models` endpoint instead of the default. Use this when your models sit behind a corporate gateway or a self-hosted inference service.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `KIGI_MODELS_BASE_URL` | Yes | Base URL for inference. Grok fetches the model list from `{base_url}/models`. |
|
||||
| `XAI_API_KEY` | Yes | API key sent as `Authorization: Bearer`. Grok also accepts `KIGI_CODE_XAI_API_KEY`. |
|
||||
| `KIGI_MODELS_BASE_URL` | Yes | Base URL for inference. Kigi fetches the model list from `{base_url}/models`. |
|
||||
| `XAI_API_KEY` | Yes | API key sent as `Authorization: Bearer`. Kigi also accepts `KIGI_CODE_XAI_API_KEY`. |
|
||||
| `KIGI_MODELS_LIST_URL` | No | Override the model-list URL when it differs from `{base_url}/models`. |
|
||||
|
||||
### Setup
|
||||
@@ -255,7 +255,7 @@ Point Grok at a custom OpenAI-compatible `/v1/models` endpoint instead of the de
|
||||
```bash
|
||||
export KIGI_MODELS_BASE_URL="https://api.acme.com/v1"
|
||||
export XAI_API_KEY="xai-..."
|
||||
grok
|
||||
kigi
|
||||
```
|
||||
|
||||
### Config File Alternative
|
||||
@@ -269,11 +269,11 @@ models_base_url = "https://api.acme.com/v1"
|
||||
api_key = "my-api-key"
|
||||
```
|
||||
|
||||
When you use `[endpoints]` with partial model overrides, Grok inherits the `base_url` from the endpoints config, so you do not need to specify it in each `[model.*]` section.
|
||||
When you use `[endpoints]` with partial model overrides, Kigi inherits the `base_url` from the endpoints config, so you do not need to specify it in each `[model.*]` section.
|
||||
|
||||
### Auth Behavior
|
||||
|
||||
When you set `models_base_url`, Grok uses API key auth (`Authorization: Bearer`) instead of session auth. You do not need `grok login` -- the API key is enough.
|
||||
When you set `models_base_url`, Kigi uses API key auth (`Authorization: Bearer`) instead of session auth. You do not need `kigi login` -- the API key is enough.
|
||||
|
||||
---
|
||||
|
||||
@@ -281,13 +281,13 @@ When you set `models_base_url`, Grok uses API key auth (`Authorization: Bearer`)
|
||||
|
||||
```bash
|
||||
# List available models (including custom)
|
||||
grok models
|
||||
kigi models
|
||||
|
||||
# Use in the TUI via slash command
|
||||
/model my-model
|
||||
|
||||
# Use in headless mode
|
||||
grok -p "Hello" -m my-model
|
||||
kigi -p "Hello" -m my-model
|
||||
|
||||
# Set as default in config.toml:
|
||||
[models]
|
||||
@@ -310,12 +310,12 @@ auth_provider_label = "Acme Corp"
|
||||
auth_token_ttl = 3600
|
||||
|
||||
[models]
|
||||
default = "company-grok"
|
||||
default = "company-kigi"
|
||||
|
||||
[model.company-grok]
|
||||
model = "grok-build"
|
||||
base_url = "https://grok-proxy.acme.com/"
|
||||
name = "Grok Build Latest (Proxy)"
|
||||
[model.company-kigi]
|
||||
model = "kigi"
|
||||
base_url = "https://kigi-proxy.acme.com/"
|
||||
name = "Kigi Latest (Proxy)"
|
||||
context_window = 128000
|
||||
|
||||
[features]
|
||||
@@ -330,7 +330,7 @@ telemetry = false
|
||||
|
||||
```bash
|
||||
# List available models
|
||||
grok models
|
||||
kigi models
|
||||
|
||||
# Check config.toml for typos in [model.*] sections
|
||||
```
|
||||
@@ -347,8 +347,8 @@ curl -s https://api.example.com/v1/models \
|
||||
### Debug Logging
|
||||
|
||||
```bash
|
||||
RUST_LOG=debug KIGI_LOG_FILE=/tmp/grok.log grok
|
||||
tail -f /tmp/grok.log
|
||||
RUST_LOG=debug KIGI_LOG_FILE=/tmp/kigi.log kigi
|
||||
tail -f /tmp/kigi.log
|
||||
```
|
||||
|
||||
Look for log entries containing `model` or `sampling` to trace model selection and API calls.
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# Project Rules (AGENTS.md)
|
||||
|
||||
Project rules let you configure Grok per project or directory. By placing an AGENTS.md file in your repository, you can set coding conventions, build instructions, style guides, and any other instructions that Grok should follow when working in that codebase.
|
||||
Project rules let you configure Kigi per project or directory. By placing an AGENTS.md file in your repository, you can set coding conventions, build instructions, style guides, and any other instructions that Kigi should follow when working in that codebase.
|
||||
|
||||
---
|
||||
|
||||
## What Are Project Rules?
|
||||
|
||||
Project rules are Markdown files that Grok reads and adds to its context. Grok follows their content for every interaction in that tree.
|
||||
Project rules are Markdown files that Kigi reads and adds to its context. Kigi follows their content for every interaction in that tree.
|
||||
|
||||
This is the primary mechanism for teaching Grok about your project's conventions, so you need not restate them each session.
|
||||
This is the primary mechanism for teaching Kigi about your project's conventions, so you need not restate them each session.
|
||||
|
||||
---
|
||||
|
||||
## Supported File Names
|
||||
|
||||
Grok checks for these filenames (in this order) within each directory:
|
||||
Kigi checks for these filenames (in this order) within each directory:
|
||||
|
||||
- `Agents.md`
|
||||
- `Claude.md`
|
||||
@@ -23,11 +23,11 @@ Grok checks for these filenames (in this order) within each directory:
|
||||
- `AGENT.md`
|
||||
- `AGENTS.md`
|
||||
|
||||
Grok loads every matching file in a directory, so a folder that contains both `AGENTS.md` and `CLAUDE.md` contributes both. On case-insensitive filesystems, names that resolve to the same file (such as `Agents.md` and `AGENTS.md`) are deduplicated and counted once. `Claude.md`, `CLAUDE.md`, and `CLAUDE.local.md` are supported for compatibility with Claude Code workflows. When Claude compatibility is enabled (the default), Grok also scans your home-level `~/.claude/` directory for these filenames and, at each directory level, checks `.claude/CLAUDE.md` and `.claude/CLAUDE.local.md` -- the locations Claude Code uses for project memory. With Cursor compatibility enabled, the home-level `~/.cursor/` directory is scanned the same way.
|
||||
Kigi loads every matching file in a directory, so a folder that contains both `AGENTS.md` and `CLAUDE.md` contributes both. On case-insensitive filesystems, names that resolve to the same file (such as `Agents.md` and `AGENTS.md`) are deduplicated and counted once. `Claude.md`, `CLAUDE.md`, and `CLAUDE.local.md` are supported for compatibility with Claude Code workflows. When Claude compatibility is enabled (the default), Kigi also scans your home-level `~/.claude/` directory for these filenames and, at each directory level, checks `.claude/CLAUDE.md` and `.claude/CLAUDE.local.md` -- the locations Claude Code uses for project memory. With Cursor compatibility enabled, the home-level `~/.cursor/` directory is scanned the same way.
|
||||
|
||||
### Rules Directories
|
||||
|
||||
In addition to AGENTS.md files, Grok scans for `*.md` files in rules directories at each level (`<dir>`) from the repo root to the current working directory:
|
||||
In addition to AGENTS.md files, Kigi scans for `*.md` files in rules directories at each level (`<dir>`) from the repo root to the current working directory:
|
||||
|
||||
| Location | Notes |
|
||||
|----------|-------|
|
||||
@@ -35,13 +35,13 @@ In addition to AGENTS.md files, Grok scans for `*.md` files in rules directories
|
||||
| `<dir>/.claude/rules/` | Claude compatibility (configurable) |
|
||||
| `<dir>/.cursor/rules/` | Cursor compatibility (configurable) |
|
||||
|
||||
Grok scans the Claude and Cursor rules directories by default. To disable scanning for a specific vendor, set its cell in the `[compat]` config section or the corresponding environment variable. See [Configuration](05-configuration.md#harness-compatibility) for details.
|
||||
Kigi scans the Claude and Cursor rules directories by default. To disable scanning for a specific vendor, set its cell in the `[compat]` config section or the corresponding environment variable. See [Configuration](05-configuration.md#harness-compatibility) for details.
|
||||
|
||||
---
|
||||
|
||||
## How Discovery Works
|
||||
|
||||
Grok scans for project rules in this order:
|
||||
Kigi scans for project rules in this order:
|
||||
|
||||
1. **Global rules**: `~/.kigi/` (applies to all projects)
|
||||
2. **Repo rules**: If inside a git repo, every directory from the repo root down to the current working directory (inclusive)
|
||||
@@ -60,16 +60,16 @@ Given this project structure:
|
||||
AGENTS.md # "Use CSS modules for styling."
|
||||
```
|
||||
|
||||
When Grok runs in `~/projects/my-app/src/components/`, it loads all three files. The instructions accumulate, so Grok sees all of them.
|
||||
When Kigi runs in `~/projects/my-app/src/components/`, it loads all three files. The instructions accumulate, so Kigi sees all of them.
|
||||
|
||||
### Deeper Files Take Precedence
|
||||
|
||||
Grok orders the files from the repo root to the current working directory, so files in deeper directories appear later in its context and take precedence when instructions conflict. In the example above, if the root says "Use styled-components" but `components/AGENTS.md` says "Use CSS modules", the CSS modules instruction wins because it appears later.
|
||||
Kigi orders the files from the repo root to the current working directory, so files in deeper directories appear later in its context and take precedence when instructions conflict. In the example above, if the root says "Use styled-components" but `components/AGENTS.md` says "Use CSS modules", the CSS modules instruction wins because it appears later.
|
||||
|
||||
### Auto-Loading Behavior
|
||||
|
||||
- Grok loads the files from the repo root to the current working directory automatically at session start.
|
||||
- When Grok reads, lists, or edits files in directories outside that initial set, it detects any project instruction files there, notes their paths, and reads them when they apply to the task.
|
||||
- Kigi loads the files from the repo root to the current working directory automatically at session start.
|
||||
- When Kigi reads, lists, or edits files in directories outside that initial set, it detects any project instruction files there, notes their paths, and reads them when they apply to the task.
|
||||
|
||||
---
|
||||
|
||||
@@ -155,18 +155,18 @@ my-monorepo/
|
||||
To add rules for a single session without editing files, pass `--rules` (alias `--append-system-prompt`):
|
||||
|
||||
```bash
|
||||
grok --rules "Always use TypeScript. Prefer functional components."
|
||||
kigi --rules "Always use TypeScript. Prefer functional components."
|
||||
```
|
||||
|
||||
Grok appends this text to the session's system prompt. Use it for session-specific customization.
|
||||
Kigi appends this text to the session's system prompt. Use it for session-specific customization.
|
||||
|
||||
To replace the system prompt entirely, pass `--system-prompt-override` (alias `--system-prompt`). Grok uses the text verbatim and skips both the default system prompt and `--rules`. (Text passed with `--rules`, by contrast, is wrapped in a `<human_rules>` block and appended to the default prompt.)
|
||||
To replace the system prompt entirely, pass `--system-prompt-override` (alias `--system-prompt`). Kigi uses the text verbatim and skips both the default system prompt and `--rules`. (Text passed with `--rules`, by contrast, is wrapped in a `<human_rules>` block and appended to the default prompt.)
|
||||
|
||||
---
|
||||
|
||||
## File Size
|
||||
|
||||
Grok loads each project instruction file in full; there is no character cap and no truncation. Even so, keep instructions concise and focused. Shorter, specific rules are easier for Grok to follow than long ones, and every file you load consumes context.
|
||||
Kigi loads each project instruction file in full; there is no character cap and no truncation. Even so, keep instructions concise and focused. Shorter, specific rules are easier for Kigi to follow than long ones, and every file you load consumes context.
|
||||
|
||||
---
|
||||
|
||||
@@ -179,7 +179,7 @@ Files ignored by `.gitignore` are skipped during discovery. To keep personal ove
|
||||
CLAUDE.local.md
|
||||
```
|
||||
|
||||
As top-level instruction files, Grok discovers only the recognized filenames listed under [Supported File Names](#supported-file-names) — not custom names such as `AGENTS.local.md` or `notes.md`. (Inside a rules directory such as `.kigi/rules/`, every `*.md` file is loaded regardless of name.)
|
||||
As top-level instruction files, Kigi discovers only the recognized filenames listed under [Supported File Names](#supported-file-names) — not custom names such as `AGENTS.local.md` or `notes.md`. (Inside a rules directory such as `.kigi/rules/`, every `*.md` file is loaded regardless of name.)
|
||||
|
||||
---
|
||||
|
||||
@@ -202,13 +202,13 @@ These are all optional. See the respective guides for details on each.
|
||||
|
||||
## Inspecting Loaded Rules
|
||||
|
||||
Use `grok inspect` to see all loaded project instructions:
|
||||
Use `kigi inspect` to see all loaded project instructions:
|
||||
|
||||
```bash
|
||||
grok inspect
|
||||
kigi inspect
|
||||
```
|
||||
|
||||
This shows each project instruction file it finds, with its path and approximate token count. Use it to confirm Grok picks up your rules.
|
||||
This shows each project instruction file it finds, with its path and approximate token count. Use it to confirm Kigi picks up your rules.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Cross-Session Memory
|
||||
|
||||
Memory lets Grok recall facts, decisions, and patterns from earlier sessions. Grok indexes the information you save and searches it automatically, so a new session can reuse relevant context.
|
||||
Memory lets Kigi recall facts, decisions, and patterns from earlier sessions. Kigi indexes the information you save and searches it automatically, so a new session can reuse relevant context.
|
||||
|
||||
---
|
||||
|
||||
## What Is Memory?
|
||||
|
||||
Without memory, each Grok session starts fresh: the model knows nothing about previous sessions. When you enable memory, Grok can:
|
||||
Without memory, each Kigi session starts fresh: the model knows nothing about previous sessions. When you enable memory, Kigi can:
|
||||
|
||||
- Recall project conventions you explained before.
|
||||
- Reuse debugging steps that worked.
|
||||
@@ -22,14 +22,14 @@ Memory is experimental and disabled by default.
|
||||
### Per-Session Flag
|
||||
|
||||
```bash
|
||||
grok --experimental-memory
|
||||
kigi --experimental-memory
|
||||
```
|
||||
|
||||
### Environment Variable
|
||||
|
||||
```bash
|
||||
export KIGI_MEMORY=1
|
||||
grok
|
||||
kigi
|
||||
```
|
||||
|
||||
### Config File (Persistent)
|
||||
@@ -45,7 +45,7 @@ enabled = true
|
||||
To disable memory even when other settings enable it:
|
||||
|
||||
```bash
|
||||
grok --no-memory
|
||||
kigi --no-memory
|
||||
```
|
||||
|
||||
Or:
|
||||
@@ -89,7 +89,7 @@ Memory is stored as Markdown files under `~/.kigi/memory/`:
|
||||
| `~/.kigi/memory/<project-slug>-<hash8>/MEMORY.md` | Workspace | Project-specific conventions and context |
|
||||
| `~/.kigi/memory/<project-slug>-<hash8>/sessions/` | Sessions | Per-session summaries and logs |
|
||||
|
||||
Grok suffixes each workspace directory with a short hash of the repository's identity. The identity is the `origin` remote in `org/repo` form when the directory is a Git repository with an `origin` remote, or the directory path otherwise. Because clones and worktrees of the same repository share an `origin` remote, they also share one memory directory.
|
||||
Kigi suffixes each workspace directory with a short hash of the repository's identity. The identity is the `origin` remote in `org/repo` form when the directory is a Git repository with an `origin` remote, or the directory path otherwise. Because clones and worktrees of the same repository share an `origin` remote, they also share one memory directory.
|
||||
|
||||
An SQLite index supports hybrid search across all memory files:
|
||||
- **FTS5** provides full-text search for keyword matching.
|
||||
@@ -99,13 +99,13 @@ An SQLite index supports hybrid search across all memory files:
|
||||
|
||||
## Automatic Saves
|
||||
|
||||
When a session ends, Grok saves a structured metadata summary to that session's daily log. The summary contains:
|
||||
When a session ends, Kigi saves a structured metadata summary to that session's daily log. The summary contains:
|
||||
|
||||
- Message counts (user, assistant, and tool results).
|
||||
- Topics: the first few substantive user prompts from the session, up to five.
|
||||
- The session date and time (UTC).
|
||||
|
||||
Grok builds the summary from conversation metadata without an LLM call, without added latency. Grok skips the save for trivial sessions -- those with fewer than three substantive prompts, or fewer than 50 bytes of user text.
|
||||
Kigi builds the summary from conversation metadata without an LLM call, without added latency. Kigi skips the save for trivial sessions -- those with fewer than three substantive prompts, or fewer than 50 bytes of user text.
|
||||
|
||||
The summary does not record tool usage, file paths, or shell commands. The session ID forms part of the log filename. To turn automatic saves off, set `session.save_on_end = false`. For richer capture of decisions, patterns, and reasoning, use `/flush`.
|
||||
|
||||
@@ -132,13 +132,13 @@ Use `/flush` when you want to preserve important context:
|
||||
|
||||
### Remember
|
||||
|
||||
Ask Grok to remember something, and it appends the note to a `MEMORY.md` file -- the workspace file for project-specific items, or the global `~/.kigi/memory/MEMORY.md` for cross-project preferences:
|
||||
Ask Kigi to remember something, and it appends the note to a `MEMORY.md` file -- the workspace file for project-specific items, or the global `~/.kigi/memory/MEMORY.md` for cross-project preferences:
|
||||
|
||||
```
|
||||
> remember to always open PR links after pushing
|
||||
```
|
||||
|
||||
Grok records entries as durable statements under organized headings, such as `## Preferences`, `## Project Context`, or `## Debugging`. The file watcher reindexes the change on the next memory search, so the new entry is searchable within the current session.
|
||||
Kigi records entries as durable statements under organized headings, such as `## Preferences`, `## Project Context`, or `## Debugging`. The file watcher reindexes the change on the next memory search, so the new entry is searchable within the current session.
|
||||
|
||||
You can also save a note directly with the `/remember` command:
|
||||
|
||||
@@ -146,11 +146,11 @@ You can also save a note directly with the `/remember` command:
|
||||
/remember always open PR links after pushing
|
||||
```
|
||||
|
||||
Run `/remember` with no text to enter remember mode, where the next line you type becomes the note. Either way, Grok opens a review panel showing the note (with an optional rewritten version you can toggle with `Tab`); the note is written only after you confirm. On save, Grok shows `Memory saved to ~/.kigi/memory/MEMORY.md`.
|
||||
Run `/remember` with no text to enter remember mode, where the next line you type becomes the note. Either way, Kigi opens a review panel showing the note (with an optional rewritten version you can toggle with `Tab`); the note is written only after you confirm. On save, Kigi shows `Memory saved to ~/.kigi/memory/MEMORY.md`.
|
||||
|
||||
### Forget
|
||||
|
||||
Ask Grok to forget something, and it finds and removes the matching entry:
|
||||
Ask Kigi to forget something, and it finds and removes the matching entry:
|
||||
|
||||
```
|
||||
> forget the snake_case convention
|
||||
@@ -160,13 +160,13 @@ Forget is best-effort: the model searches memory and removes entries that match.
|
||||
|
||||
### Recall
|
||||
|
||||
Ask what Grok remembers:
|
||||
Ask what Kigi remembers:
|
||||
|
||||
```
|
||||
> what do you remember?
|
||||
```
|
||||
|
||||
Grok searches across all memory files and summarizes what it knows, grouped by source: global preferences, project-specific knowledge, and session history. Use `/memory` to browse the raw files.
|
||||
Kigi searches across all memory files and summarizes what it knows, grouped by source: global preferences, project-specific knowledge, and session history. Use `/memory` to browse the raw files.
|
||||
|
||||
### Direct Editing
|
||||
|
||||
@@ -212,13 +212,13 @@ You can also open `/memory` from the command palette.
|
||||
|
||||
## Memory Notifications
|
||||
|
||||
When you save a note with `/remember`, Grok confirms in the scrollback:
|
||||
When you save a note with `/remember`, Kigi confirms in the scrollback:
|
||||
|
||||
```
|
||||
Memory saved to ~/.kigi/memory/MEMORY.md
|
||||
```
|
||||
|
||||
Background saves — flush, dream, and session-end — run silently and do not post a scrollback message. Use `/memory` at any time to browse what Grok has stored.
|
||||
Background saves — flush, dream, and session-end — run silently and do not post a scrollback message. Use `/memory` at any time to browse what Kigi has stored.
|
||||
|
||||
---
|
||||
|
||||
@@ -234,7 +234,7 @@ Dream reorganizes individual session logs and memory entries into a coherent, de
|
||||
|
||||
### Auto-Dream
|
||||
|
||||
Dream also runs automatically. By default, Grok checks the consolidation gates when a session ends and runs Dream once enough time has passed and enough sessions have accumulated:
|
||||
Dream also runs automatically. By default, Kigi checks the consolidation gates when a session ends and runs Dream once enough time has passed and enough sessions have accumulated:
|
||||
|
||||
```toml
|
||||
[memory.dream]
|
||||
@@ -251,7 +251,7 @@ min_sessions = 3 # Minimum sessions since the last consolidation
|
||||
|
||||
### First-Turn Injection
|
||||
|
||||
On the first turn of each session, Grok automatically searches memory for content relevant to the current project and injects it as context. This means Grok starts with knowledge from previous sessions without a reminder.
|
||||
On the first turn of each session, Kigi automatically searches memory for content relevant to the current project and injects it as context. This means Kigi starts with knowledge from previous sessions without a reminder.
|
||||
|
||||
First-turn injection can be configured:
|
||||
|
||||
@@ -269,7 +269,7 @@ Memory is also searched after auto-compaction to recover relevant context that m
|
||||
|
||||
## Memory Search
|
||||
|
||||
Grok searches memory automatically, but you can also trigger searches manually in the chat:
|
||||
Kigi searches memory automatically, but you can also trigger searches manually in the chat:
|
||||
|
||||
```
|
||||
Search memory for "auth middleware patterns"
|
||||
@@ -324,23 +324,23 @@ lambda = 0.7 # 0.0 = max diversity, 1.0 = pure relevance
|
||||
|
||||
## CLI Commands
|
||||
|
||||
The `grok memory` command manages memory from the shell. It has one subcommand, `clear`:
|
||||
The `kigi memory` command manages memory from the shell. It has one subcommand, `clear`:
|
||||
|
||||
```bash
|
||||
# Clear workspace memory (MEMORY.md, sessions/, and index.sqlite). This is the default scope.
|
||||
grok memory clear
|
||||
kigi memory clear
|
||||
|
||||
# The same scope, stated explicitly
|
||||
grok memory clear --workspace
|
||||
kigi memory clear --workspace
|
||||
|
||||
# Clear the global MEMORY.md
|
||||
grok memory clear --global
|
||||
kigi memory clear --global
|
||||
|
||||
# Clear both workspace and global memory
|
||||
grok memory clear --all
|
||||
kigi memory clear --all
|
||||
|
||||
# Skip the confirmation prompt (-y is the short form)
|
||||
grok memory clear --yes
|
||||
kigi memory clear --yes
|
||||
```
|
||||
|
||||
To edit memory from the shell, open the files in your editor directly -- for example, `$EDITOR ~/.kigi/memory/MEMORY.md`.
|
||||
@@ -386,7 +386,7 @@ To edit memory from the shell, open the files in your editor directly -- for exa
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `enabled` | `true` | Enable first-turn memory injection |
|
||||
| `min_score` | unset | Score threshold for first-turn results. When unset, Grok applies no threshold, which is equivalent to `0.0`. |
|
||||
| `min_score` | unset | Score threshold for first-turn results. When unset, Kigi applies no threshold, which is equivalent to `0.0`. |
|
||||
|
||||
### Dream Settings (`[memory.dream]`)
|
||||
|
||||
@@ -407,7 +407,7 @@ You configure flush under `[compaction]`, not `[memory]`, because it is a compac
|
||||
| `enabled` | `true` | Enable the pre-compaction memory flush |
|
||||
| `soft_threshold_tokens` | `4000` | Token headroom before the compact threshold that triggers a flush |
|
||||
| `max_flush_write_chars` | `8000` | Maximum characters the flush may write to memory |
|
||||
| `flush_model` | unset | Model for the flush turn. When unset, Grok uses the session's primary model. |
|
||||
| `flush_model` | unset | Model for the flush turn. When unset, Kigi uses the session's primary model. |
|
||||
| `idle_timeout_secs` | unset | Idle seconds before a background flush. When unset, flush runs only before compaction. |
|
||||
| `semantic_dedup_threshold` | unset | Cosine-similarity threshold for de-duplicating flushed content. When unset, defaults to `0.92`. |
|
||||
|
||||
@@ -428,13 +428,13 @@ You configure pruning under `[compaction]`, not `[memory]`, because it is a comp
|
||||
|
||||
## Memory Staleness
|
||||
|
||||
When a session memory is old, Grok attaches a staleness note to it in search results. Older results get a stronger reminder to verify the current state before you rely on them. These notes help you spot stored facts that might no longer be accurate. Global and workspace memories never receive staleness notes, because they hold curated long-term knowledge.
|
||||
When a session memory is old, Kigi attaches a staleness note to it in search results. Older results get a stronger reminder to verify the current state before you rely on them. These notes help you spot stored facts that might no longer be accurate. Global and workspace memories never receive staleness notes, because they hold curated long-term knowledge.
|
||||
|
||||
---
|
||||
|
||||
## File Watcher
|
||||
|
||||
By default, Grok watches `~/.kigi/memory/` for external file changes. If you edit memory files directly (e.g., in your editor), the changes are picked up automatically on the next memory search:
|
||||
By default, Kigi watches `~/.kigi/memory/` for external file changes. If you edit memory files directly (e.g., in your editor), the changes are picked up automatically on the next memory search:
|
||||
|
||||
- Created or modified files are reindexed.
|
||||
- Deleted files have their stale chunks removed from the index.
|
||||
@@ -450,8 +450,8 @@ enabled = true # default
|
||||
|
||||
### Memory Not Working
|
||||
|
||||
1. Verify memory is enabled: check `grok inspect` output.
|
||||
2. Check the flag: `grok --experimental-memory` or `KIGI_MEMORY=1`.
|
||||
1. Verify memory is enabled: check `kigi inspect` output.
|
||||
2. Check the flag: `kigi --experimental-memory` or `KIGI_MEMORY=1`.
|
||||
3. Check for `--no-memory` or `KIGI_MEMORY=0` overriding your config.
|
||||
|
||||
### Memory Not Appearing in Sessions
|
||||
@@ -471,6 +471,6 @@ $EDITOR ~/.kigi/memory/MEMORY.md
|
||||
### Debug Logging
|
||||
|
||||
```bash
|
||||
RUST_LOG=debug KIGI_LOG_FILE=/tmp/grok.log grok
|
||||
grep "memory" /tmp/grok.log
|
||||
RUST_LOG=debug KIGI_LOG_FILE=/tmp/kigi.log kigi
|
||||
grep "memory" /tmp/kigi.log
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Headless Mode and Scripting
|
||||
|
||||
Headless mode runs Grok non-interactively from the command line. It accepts a single prompt, executes it with full tool access, and returns the result. Use it to automate tasks, script workflows, build integrations, and parse output programmatically.
|
||||
Headless mode runs Kigi non-interactively from the command line. It accepts a single prompt, executes it with full tool access, and returns the result. Use it to automate tasks, script workflows, build integrations, and parse output programmatically.
|
||||
|
||||
---
|
||||
|
||||
@@ -9,10 +9,10 @@ Headless mode runs Grok non-interactively from the command line. It accepts a si
|
||||
Passing a prompt non-interactively triggers headless mode. The most common way is the `-p` flag (short for `--single`); `--prompt-json` and `--prompt-file` also trigger it:
|
||||
|
||||
```bash
|
||||
grok -p "Your prompt here"
|
||||
kigi -p "Your prompt here"
|
||||
```
|
||||
|
||||
Grok processes the prompt, runs any necessary tools, and prints the result to stdout. The process exits when the response is complete.
|
||||
Kigi processes the prompt, runs any necessary tools, and prints the result to stdout. The process exits when the response is complete.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,7 +21,7 @@ Grok processes the prompt, runs any necessary tools, and prints the result to st
|
||||
| Flag | Description |
|
||||
| ----------------------- | ----------------------------------------------------- |
|
||||
| `-p, --single <PROMPT>` | The prompt to send (or use `--prompt-json` / `--prompt-file`) |
|
||||
| `-m, --model <MODEL>` | Model to use (e.g., `grok-build`) |
|
||||
| `-m, --model <MODEL>` | Model to use (e.g., `kigi`) |
|
||||
| `-s, --session-id <ID>` | Create a **new** session with this **UUID** (errors if invalid UUID or already in use under the target session directory; does not resume — use `-r`/`-c`) |
|
||||
| `--fork-session` | With `-r`/`-c`, fork into a new session ID instead of appending to the original |
|
||||
| `-r, --resume <ID>` | Resume an existing session (errors if not found) |
|
||||
@@ -53,13 +53,13 @@ Tool names are internal tool IDs (e.g. the shell tool is `run_terminal_cmd`, not
|
||||
|
||||
```bash
|
||||
# Only allow read-only tools
|
||||
grok -p "Explain this codebase" --tools "read_file,grep,list_dir"
|
||||
kigi -p "Explain this codebase" --tools "read_file,grep,list_dir"
|
||||
|
||||
# Remove web access and file editing
|
||||
grok -p "Review this code" --disallowed-tools "web_search,web_fetch,search_replace"
|
||||
kigi -p "Review this code" --disallowed-tools "web_search,web_fetch,search_replace"
|
||||
|
||||
# Remove shell access
|
||||
grok -p "Review this code" --disallowed-tools "run_terminal_cmd"
|
||||
kigi -p "Review this code" --disallowed-tools "run_terminal_cmd"
|
||||
```
|
||||
|
||||
`--disallowed-tools` also supports special `Agent` entries to control subagent spawning:
|
||||
@@ -72,10 +72,10 @@ grok -p "Review this code" --disallowed-tools "run_terminal_cmd"
|
||||
|
||||
```bash
|
||||
# Prevent the agent from spawning any subagents
|
||||
grok -p "Fix this bug" --disallowed-tools "Agent"
|
||||
kigi -p "Fix this bug" --disallowed-tools "Agent"
|
||||
|
||||
# Block only the explore subagent
|
||||
grok -p "Refactor this module" --disallowed-tools "Agent(explore)"
|
||||
kigi -p "Refactor this module" --disallowed-tools "Agent(explore)"
|
||||
```
|
||||
|
||||
`--tools` preserves the selected agent profile's injection policy: stock profiles inject enabled optional tools before applying the allowlist, while curated profiles remain strict. The final toolset retains requested tools plus always-on MCP meta-tools. When both flags are present, `--disallowed-tools` wins.
|
||||
@@ -100,13 +100,13 @@ For path rules (`Read`, `Edit`, `Write`, `Grep`), `*` is a single-level wildcard
|
||||
|
||||
```bash
|
||||
# Deny shell commands matching "rm*"
|
||||
grok -p "Clean up this project" --deny "Bash(rm*)"
|
||||
kigi -p "Clean up this project" --deny "Bash(rm*)"
|
||||
|
||||
# Allow npm commands, deny sudo
|
||||
grok -p "Set up the project" --allow "Bash(npm*)" --deny "Bash(sudo*)"
|
||||
kigi -p "Set up the project" --allow "Bash(npm*)" --deny "Bash(sudo*)"
|
||||
|
||||
# Allow all bash commands (auto-approve without prompting)
|
||||
grok -p "Build the project" --allow "Bash"
|
||||
kigi -p "Build the project" --allow "Bash"
|
||||
```
|
||||
|
||||
`--allow` and `--deny` can be repeated. Deny rules take precedence over allow rules.
|
||||
@@ -147,7 +147,7 @@ When the prompt reached the model, the same object also carries spend fields
|
||||
"total_tokens": 50103
|
||||
},
|
||||
"modelUsage": {
|
||||
"grok-build": {
|
||||
"kigi": {
|
||||
"inputTokens": 7210,
|
||||
"outputTokens": 1893,
|
||||
"cacheReadInputTokens": 41000,
|
||||
@@ -199,7 +199,7 @@ Usage notes:
|
||||
|
||||
The `sessionId` field is useful for resuming the conversation later.
|
||||
|
||||
On failure, Grok emits an error object (process exit non-zero). Prompt-level
|
||||
On failure, Kigi emits an error object (process exit non-zero). Prompt-level
|
||||
failures may also include frozen spend fields when usage was recorded:
|
||||
|
||||
```json
|
||||
@@ -229,13 +229,13 @@ Event types:
|
||||
`end` is always the last event. Spend fields on `end` match the json object
|
||||
shape (snake_case uncached `input_tokens`, safe cost floats).
|
||||
|
||||
Grok may also emit `max_turns_reached` and `auto_compact_*` events; treat the list as non-exhaustive and switch on `type`.
|
||||
Kigi may also emit `max_turns_reached` and `auto_compact_*` events; treat the list as non-exhaustive and switch on `type`.
|
||||
|
||||
---
|
||||
|
||||
## Session Management in Headless Mode
|
||||
|
||||
By default, each `grok -p` invocation creates a fresh session. To maintain context across calls, use session flags.
|
||||
By default, each `kigi -p` invocation creates a fresh session. To maintain context across calls, use session flags.
|
||||
|
||||
### Named Sessions (`-s`)
|
||||
|
||||
@@ -243,13 +243,13 @@ To carry context across headless calls, use `-r/--resume` or `-c/--continue`. Us
|
||||
|
||||
```bash
|
||||
# Start a headless session and capture its ID
|
||||
grok -p "Review the changes in this PR" --output-format json | jq -r '.sessionId'
|
||||
kigi -p "Review the changes in this PR" --output-format json | jq -r '.sessionId'
|
||||
|
||||
# Continue in the same session
|
||||
grok -p "Now check for security issues" --resume "<id>"
|
||||
kigi -p "Now check for security issues" --resume "<id>"
|
||||
|
||||
# Optional: create with a client-chosen UUID (must not already exist)
|
||||
grok -p "hello" --session-id "$(uuidgen | tr '[:upper:]' '[:lower:]')" --output-format json
|
||||
kigi -p "hello" --session-id "$(uuidgen | tr '[:upper:]' '[:lower:]')" --output-format json
|
||||
```
|
||||
|
||||
> **Note:** `-s/--session-id` creates a new session only (valid UUID; errors if already in use). Use `-r` to resume.
|
||||
@@ -260,11 +260,11 @@ The `-r/--resume` flag resumes a specific session by ID. It errors if the sessio
|
||||
|
||||
```bash
|
||||
# Get the session ID from a previous JSON response
|
||||
grok -p "Remember: the secret number is 42" --output-format json
|
||||
kigi -p "Remember: the secret number is 42" --output-format json
|
||||
# Output includes "sessionId": "abc123"
|
||||
|
||||
# Resume that exact session
|
||||
grok -p "What's the secret number?" --resume abc123
|
||||
kigi -p "What's the secret number?" --resume abc123
|
||||
```
|
||||
|
||||
### Continue (`-c`)
|
||||
@@ -272,7 +272,7 @@ grok -p "What's the secret number?" --resume abc123
|
||||
The `-c/--continue` flag continues the most recent session in the current working directory:
|
||||
|
||||
```bash
|
||||
grok -p "Continue where we left off" -c
|
||||
kigi -p "Continue where we left off" -c
|
||||
```
|
||||
|
||||
### Extracting Session IDs
|
||||
@@ -280,7 +280,7 @@ grok -p "Continue where we left off" -c
|
||||
Use `--output-format json` and parse the `sessionId` field:
|
||||
|
||||
```bash
|
||||
grok -p "Hello" --output-format json | jq -r '.sessionId'
|
||||
kigi -p "Hello" --output-format json | jq -r '.sessionId'
|
||||
```
|
||||
|
||||
---
|
||||
@@ -293,10 +293,10 @@ Headless mode works naturally with Unix pipes and redirection.
|
||||
|
||||
```bash
|
||||
# Pipe output to a file
|
||||
grok -p "Generate a README" > README.md
|
||||
kigi -p "Generate a README" > README.md
|
||||
|
||||
# Parse JSON output with jq
|
||||
grok -p "List files" --output-format json | jq -r '.text'
|
||||
kigi -p "List files" --output-format json | jq -r '.text'
|
||||
```
|
||||
|
||||
### Standard Input
|
||||
@@ -305,12 +305,12 @@ Headless mode does not read piped stdin into the prompt. Pass external content t
|
||||
|
||||
```bash
|
||||
# Include git diff as context via command substitution
|
||||
grok -p "Write a concise commit message for these changes:
|
||||
kigi -p "Write a concise commit message for these changes:
|
||||
|
||||
$(git diff --staged)"
|
||||
|
||||
# Or read the prompt from a file
|
||||
grok --prompt-file ./prompt.txt
|
||||
kigi --prompt-file ./prompt.txt
|
||||
```
|
||||
|
||||
---
|
||||
@@ -320,14 +320,14 @@ grok --prompt-file ./prompt.txt
|
||||
### Automated Code Review
|
||||
|
||||
```bash
|
||||
grok -p "Review changes for bugs and security issues." \
|
||||
kigi -p "Review changes for bugs and security issues." \
|
||||
--output-format json --yolo | jq -r '.text' > review.md
|
||||
```
|
||||
|
||||
### Pre-Commit Hook
|
||||
|
||||
```bash
|
||||
grok -p "Review staged changes for obvious bugs. Reply OK if fine, or list issues." \
|
||||
kigi -p "Review staged changes for obvious bugs. Reply OK if fine, or list issues." \
|
||||
--yolo --output-format json | jq -r '.text' | grep -q "^OK" || exit 1
|
||||
```
|
||||
|
||||
@@ -335,7 +335,7 @@ grok -p "Review staged changes for obvious bugs. Reply OK if fine, or list issue
|
||||
|
||||
```bash
|
||||
for file in src/*.js; do
|
||||
grok -p "Migrate $file from CommonJS to ES modules." --yolo
|
||||
kigi -p "Migrate $file from CommonJS to ES modules." --yolo
|
||||
done
|
||||
```
|
||||
|
||||
@@ -345,14 +345,14 @@ done
|
||||
|
||||
### Python Wrapper
|
||||
|
||||
Grok's headless mode can be wrapped as an OpenAI-compatible chat completion API:
|
||||
Kigi's headless mode can be wrapped as an OpenAI-compatible chat completion API:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
|
||||
class GrokChat:
|
||||
class KigiChat:
|
||||
"""Simple OpenAI-compatible wrapper using headless mode."""
|
||||
|
||||
def __init__(self, cwd="."):
|
||||
@@ -360,11 +360,11 @@ class GrokChat:
|
||||
self.env = {**os.environ}
|
||||
|
||||
def _build_cmd(self, prompt, model, stream):
|
||||
return ["grok", "-p", prompt, "-m", model, "--cwd", self.cwd,
|
||||
return ["kigi", "-p", prompt, "-m", model, "--cwd", self.cwd,
|
||||
"--output-format", "streaming-json" if stream else "json",
|
||||
"--yolo"]
|
||||
|
||||
async def create(self, messages, model="grok-build", stream=False):
|
||||
async def create(self, messages, model="kigi", stream=False):
|
||||
prompt = messages[-1]["content"] if len(messages) == 1 else "\n".join(
|
||||
f"{m['role']}: {m['content']}" for m in messages
|
||||
)
|
||||
@@ -400,7 +400,7 @@ class GrokChat:
|
||||
|
||||
|
||||
async def main():
|
||||
client = GrokChat(cwd=".")
|
||||
client = KigiChat(cwd=".")
|
||||
response = await client.create(
|
||||
[{"role": "user", "content": "What files are here?"}]
|
||||
)
|
||||
@@ -415,7 +415,7 @@ asyncio.run(main())
|
||||
#!/bin/bash
|
||||
# Run a code review and exit with failure if issues are found
|
||||
|
||||
RESULT=$(grok -p "Review this PR for bugs. Output JSON with 'issues' array." \
|
||||
RESULT=$(kigi -p "Review this PR for bugs. Output JSON with 'issues' array." \
|
||||
--output-format json --yolo | jq -r '.text')
|
||||
|
||||
ISSUE_COUNT=$(echo "$RESULT" | jq '.issues | length' 2>/dev/null || echo "0")
|
||||
@@ -437,10 +437,10 @@ The `--yolo` flag enables always-approve mode (the same mode as `--permission-mo
|
||||
|
||||
```bash
|
||||
# Format all files without asking
|
||||
grok -p "Format all files" --yolo
|
||||
kigi -p "Format all files" --yolo
|
||||
|
||||
# Run tests and fix failures
|
||||
grok -p "Run the tests and fix any failures" --cwd ~/projects/my-app --yolo
|
||||
kigi -p "Run the tests and fix any failures" --cwd ~/projects/my-app --yolo
|
||||
```
|
||||
|
||||
**Use `--yolo` with care.** It grants the agent full autonomy to modify files and run commands. Only use it in trusted environments or with well-scoped prompts.
|
||||
@@ -462,7 +462,7 @@ For CI environments without browser access, set `XAI_API_KEY` with an API key fr
|
||||
|
||||
```bash
|
||||
export XAI_API_KEY="xai-..."
|
||||
grok -p "Run the test suite" --yolo
|
||||
kigi -p "Run the test suite" --yolo
|
||||
```
|
||||
|
||||
---
|
||||
@@ -483,9 +483,9 @@ grok -p "Run the test suite" --yolo
|
||||
For headless use, authenticate with one of:
|
||||
|
||||
- **`XAI_API_KEY`** — simplest for CI. See [Environment Variables](#environment-variables-for-headless) above.
|
||||
- **`grok login --device-auth`** (or `--device-code`) — no browser needed on the target machine.
|
||||
- **`kigi login --device-auth`** (or `--device-code`) — no browser needed on the target machine.
|
||||
See [Authentication > Device Code Flow](02-authentication.md#device-code-flow).
|
||||
- **`grok login`** — browser-based OAuth2 on machines with a GUI.
|
||||
- **`kigi login`** — browser-based OAuth2 on machines with a GUI.
|
||||
|
||||
If you've previously logged in, cached credentials are used automatically.
|
||||
|
||||
@@ -495,18 +495,18 @@ If you've previously logged in, cached credentials are used automatically.
|
||||
|
||||
- Headless mode starts a **fresh session by default**. Use `-r/--resume` or `-c/--continue` to maintain context across calls.
|
||||
- The `--output-format json` response always includes a `sessionId` you can use with `--resume` for follow-up calls.
|
||||
- Combine `--yolo` with `--rules` to set guardrails: `grok -p "..." --yolo --rules "Never delete files"`.
|
||||
- For debugging, raise the log level and capture stderr: `RUST_LOG=debug grok -p "..." 2> debug.log`.
|
||||
- Combine `--yolo` with `--rules` to set guardrails: `kigi -p "..." --yolo --rules "Never delete files"`.
|
||||
- For debugging, raise the log level and capture stderr: `RUST_LOG=debug kigi -p "..." 2> debug.log`.
|
||||
|
||||
---
|
||||
|
||||
## Project Root Discovery
|
||||
|
||||
When Grok starts, it discovers the project root by walking upward from `--cwd`
|
||||
When Kigi starts, it discovers the project root by walking upward from `--cwd`
|
||||
(or the current directory) until it finds a `.git` directory.
|
||||
|
||||
Note: If `--cwd` is nested inside a large repository (such as a monorepo),
|
||||
Grok discovers that repository as the project root and scopes its discovery (AGENTS.md, skills, git history) to it, which can make
|
||||
Kigi discovers that repository as the project root and scopes its discovery (AGENTS.md, skills, git history) to it, which can make
|
||||
startup slow. Point `--cwd` at the specific subproject you want to work in to keep
|
||||
the scope small.
|
||||
|
||||
@@ -514,7 +514,7 @@ the scope small.
|
||||
|
||||
## File Locations
|
||||
|
||||
Grok stores data in `~/.kigi` (override with `KIGI_SHARE_DIR`; see [Environment Variables for Headless](#environment-variables-for-headless)):
|
||||
Kigi stores data in `~/.kigi` (override with `KIGI_SHARE_DIR`; see [Environment Variables for Headless](#environment-variables-for-headless)):
|
||||
|
||||
| Path | Contents |
|
||||
| ------------------------ | ------------------------------------- |
|
||||
@@ -542,7 +542,7 @@ For containers or CI, mount `~/.kigi` read-only:
|
||||
```bash
|
||||
export XAI_API_KEY="xai-..."
|
||||
export KIGI_DISABLE_AUTOUPDATER=1
|
||||
grok -p "..." --no-auto-update
|
||||
kigi -p "..." --no-auto-update
|
||||
```
|
||||
|
||||
---
|
||||
@@ -588,6 +588,6 @@ On SIGINT/SIGTERM:
|
||||
- Session state saved up to the last completed tool call
|
||||
- File modifications by tools are **not rolled back**
|
||||
- Exit code is **130** for SIGINT (`128 + 2`) and **143** for SIGTERM (`128 + 15`); CI pipelines can distinguish these from a normal error (exit code `1`)
|
||||
- Resume: `grok -p "continue" --resume "<id>"` or `grok -p "continue" --continue`
|
||||
- Resume: `kigi -p "continue" --resume "<id>"` or `kigi -p "continue" --continue`
|
||||
|
||||
See [Session Management in Headless Mode](#session-management-in-headless-mode) for details on named sessions and the `-s`/`-r`/`-c` flags.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Agent Mode (ACP) and IDE Integration
|
||||
|
||||
Agent mode runs Grok as an ACP (Agent Client Protocol) server for integration with IDEs, editors, and custom tooling. Unlike single-prompt mode (`grok -p`, which prints one response and exits), agent mode keeps a persistent process running and communicates through structured JSON-RPC messages.
|
||||
Agent mode runs Kigi as an ACP (Agent Client Protocol) server for integration with IDEs, editors, and custom tooling. Unlike single-prompt mode (`kigi -p`, which prints one response and exits), agent mode keeps a persistent process running and communicates through structured JSON-RPC messages.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,7 +21,7 @@ The [Agent Client Protocol (ACP)](https://agentclientprotocol.com) is a standard
|
||||
stdio is the primary integration mode. The agent exchanges JSON-RPC messages over stdin and stdout:
|
||||
|
||||
```bash
|
||||
grok agent stdio
|
||||
kigi agent stdio
|
||||
```
|
||||
|
||||
Clients that use this mode include:
|
||||
@@ -32,11 +32,11 @@ Clients that use this mode include:
|
||||
|
||||
### Options
|
||||
|
||||
These options belong to the `grok agent` command and apply to every mode. Pass them before the mode name, for example `grok agent --model grok-build stdio`. The `stdio` subcommand itself takes no options.
|
||||
These options belong to the `kigi agent` command and apply to every mode. Pass them before the mode name, for example `kigi agent --model kigi stdio`. The `stdio` subcommand itself takes no options.
|
||||
|
||||
| Flag | Description |
|
||||
| -------------------------- | ---------------------------------------------------------------- |
|
||||
| `-m, --model <MODEL>` | Set the model ID (for example, `grok-build`). |
|
||||
| `-m, --model <MODEL>` | Set the model ID (for example, `kigi`). |
|
||||
| `--always-approve` | Auto-approve every tool execution. (Alias: `--yolo`.) |
|
||||
| `--reauth` | Run authentication before starting the agent. |
|
||||
| `--agent-profile <PATH>` | Load an agent profile from a file. |
|
||||
@@ -48,7 +48,7 @@ These options belong to the `grok agent` command and apply to every mode. Pass t
|
||||
Run the agent as a WebSocket server for remote clients:
|
||||
|
||||
```bash
|
||||
grok agent serve --bind 127.0.0.1:2419 --secret <token>
|
||||
kigi agent serve --bind 127.0.0.1:2419 --secret <token>
|
||||
```
|
||||
|
||||
Clients connect over WebSocket and authenticate with the secret token. If you omit `--secret`, the agent generates a token and prints it at startup; you can also supply one through the `KIGI_AGENT_SECRET` environment variable. The agent persists across reconnections, so a client can disconnect and later resume in-flight work.
|
||||
@@ -60,7 +60,7 @@ Clients connect over WebSocket and authenticate with the secret token. If you om
|
||||
To reach the agent over the internet instead of the local network, run a WebSocket relay server and have the agent connect to it:
|
||||
|
||||
```bash
|
||||
grok agent headless --grok-ws-url wss://your-relay.example.com/ws
|
||||
kigi agent headless --kigi-ws-url wss://your-relay.example.com/ws
|
||||
```
|
||||
|
||||
The agent connects out to your relay, and your web clients connect to the same relay. This is useful for building web UIs where browsers cannot spawn local processes.
|
||||
@@ -86,7 +86,7 @@ Communication follows the JSON-RPC 2.0 format. A typical session lifecycle:
|
||||
+-------------------+----------------------+
|
||||
| JSON-RPC over stdio
|
||||
+-------------------v----------------------+
|
||||
| grok agent stdio |
|
||||
| kigi agent stdio |
|
||||
| |
|
||||
| +---------+ +---------+ +---------+ |
|
||||
| | Session | | Tools | | MCP | |
|
||||
@@ -115,7 +115,7 @@ Each update names its type, so a client can render distinct panels for reasoning
|
||||
|
||||
## Extension methods
|
||||
|
||||
Beyond the base ACP protocol, Grok defines extension methods under the `x.ai/` prefix for SpaceXAI-specific functionality. These cover:
|
||||
Beyond the base ACP protocol, Kigi defines extension methods under the `x.ai/` prefix for SpaceXAI-specific functionality. These cover:
|
||||
|
||||
| Category | Prefix | Examples |
|
||||
| -------------------------- | -------------------- | ------------------------------------------------ |
|
||||
@@ -191,7 +191,7 @@ Official SDK libraries are available for multiple languages:
|
||||
import { spawn, ChildProcess } from "child_process";
|
||||
import * as readline from "readline";
|
||||
|
||||
class GrokACPChat {
|
||||
class KigiACPChat {
|
||||
private proc!: ChildProcess;
|
||||
private sessionId!: string;
|
||||
private rl!: readline.Interface;
|
||||
@@ -199,7 +199,7 @@ class GrokACPChat {
|
||||
constructor(private cwd = ".") {}
|
||||
|
||||
async init() {
|
||||
this.proc = spawn("grok", ["agent", "stdio"]);
|
||||
this.proc = spawn("kigi", ["agent", "stdio"]);
|
||||
this.rl = readline.createInterface({ input: this.proc.stdout! });
|
||||
|
||||
// Initialize
|
||||
@@ -257,7 +257,7 @@ class GrokACPChat {
|
||||
}
|
||||
|
||||
// Usage
|
||||
const client = await new GrokACPChat(".").init();
|
||||
const client = await new KigiACPChat(".").init();
|
||||
|
||||
for await (const update of client.streamPrompt("List the files in this project")) {
|
||||
switch (update.sessionUpdate) {
|
||||
|
||||
@@ -17,7 +17,7 @@ Agents and personas both customize behavior, but they operate at different level
|
||||
| **How you set them** | At startup, or with agent definitions (`.md` files in `.kigi/agents/` or `~/.kigi/agents/`) | In `config.toml` (`[subagents.personas]`) or `.toml` files under `.kigi/personas/`; applied during subagent resolution |
|
||||
| **What they control** | Model, tool availability, prompt body, skills | Tone, output format, task focus, and input/output contracts |
|
||||
| **Who edits them** | You -- create, delete, or toggle them in the agents modal or by editing files | You -- define custom personas in config or files; bundled personas are read-only |
|
||||
| **Examples** | `grok-build`, `explore`, `plan` | `researcher`, `concise` |
|
||||
| **Examples** | `kigi`, `explore`, `plan` | `researcher`, `concise` |
|
||||
|
||||
An agent defines the session itself. A persona shapes how a subagent behaves within a session. A subagent always runs as an agent type (for example, `general-purpose`), and resolution can layer a persona on top.
|
||||
|
||||
@@ -79,7 +79,7 @@ instructions = "You are a thorough researcher. Always cite specific file paths."
|
||||
description = "Deep investigator."
|
||||
```
|
||||
|
||||
Grok Build discovers file-based personas from these locations, in priority order:
|
||||
Kigi discovers file-based personas from these locations, in priority order:
|
||||
|
||||
- `.kigi/personas/*.toml` (project)
|
||||
- `~/.kigi/personas/*.toml` (user)
|
||||
@@ -89,7 +89,7 @@ Each file defines one persona, and the file name (without the extension) becomes
|
||||
|
||||
Manage personas in the Personas tab of the agents modal (`/personas`). Bundled personas are read-only; personas you define are editable.
|
||||
|
||||
> **Note:** Grok Build applies personas through subagent resolution and roles, not through a `spawn_subagent` parameter. The main agent does not pass a persona name when it spawns a child.
|
||||
> **Note:** Kigi applies personas through subagent resolution and roles, not through a `spawn_subagent` parameter. The main agent does not pass a persona name when it spawns a child.
|
||||
|
||||
### Persona Fields
|
||||
|
||||
@@ -125,7 +125,7 @@ Each field has a `name`, an `io_type` (defaults to `file`), a `required` flag, a
|
||||
|
||||
### Persona Resolution
|
||||
|
||||
When a persona applies, Grok Build resolves the effective model and reasoning effort in this order, highest priority first:
|
||||
When a persona applies, Kigi resolves the effective model and reasoning effort in this order, highest priority first:
|
||||
|
||||
1. Explicit spawn-time override
|
||||
2. Role default
|
||||
@@ -193,7 +193,7 @@ For tasks that modify files, run a subagent in an isolated git worktree with `is
|
||||
- Its changes stay isolated from the parent until you merge them.
|
||||
- The subagent's result includes the worktree path.
|
||||
|
||||
Grok Build manages worktrees through the `x.ai/git/worktree/*` extension methods, including an apply operation that merges changes back into the main working directory.
|
||||
Kigi manages worktrees through the `x.ai/git/worktree/*` extension methods, including an apply operation that merges changes back into the main working directory.
|
||||
|
||||
---
|
||||
|
||||
@@ -209,7 +209,7 @@ explore = true # default -- omit to keep enabled
|
||||
plan = false # disable the plan subagent
|
||||
|
||||
[subagents.models]
|
||||
explore = "grok-build" # route explore to a specific model
|
||||
explore = "kigi" # route explore to a specific model
|
||||
```
|
||||
|
||||
Per-type model overrides apply for any parent. Without an override, a subagent inherits the parent's model.
|
||||
@@ -222,7 +222,7 @@ Define custom roles with their own capability and model defaults:
|
||||
[subagents.roles.researcher]
|
||||
description = "Deep research agent"
|
||||
default_capability_mode = "read-only"
|
||||
model = "grok-build"
|
||||
model = "kigi"
|
||||
prompt_file = ".kigi/prompts/researcher.md"
|
||||
```
|
||||
|
||||
@@ -234,13 +234,13 @@ instructions = "Be concise. No filler words."
|
||||
# instructions_file = ".kigi/personas/concise.md" # or load from a file
|
||||
```
|
||||
|
||||
Grok Build also discovers roles from `.kigi/roles/*.toml` and personas from `.kigi/personas/*.toml`. Inline `config.toml` definitions take precedence over files.
|
||||
Kigi also discovers roles from `.kigi/roles/*.toml` and personas from `.kigi/personas/*.toml`. Inline `config.toml` definitions take precedence over files.
|
||||
|
||||
---
|
||||
|
||||
## The Tasks Pane (TUI)
|
||||
|
||||
Grok Build shows running and finished work in side panes on the agent screen:
|
||||
Kigi shows running and finished work in side panes on the agent screen:
|
||||
|
||||
- Press `Ctrl+B` to toggle the tasks pane, which lists active and completed subagents and background commands with their status.
|
||||
- Press `Ctrl+T` to toggle the separate todo pane.
|
||||
@@ -259,7 +259,7 @@ Subagents appear in several places in the interactive TUI:
|
||||
|
||||
When a subagent is spawned, a compact lifecycle block is added to the *parent's* scrollback:
|
||||
|
||||
- `Subagent running: "do the thing" (Implementer · grok-3) — Thinking`
|
||||
- `Subagent running: "do the thing" (Implementer · kigi-3) — Thinking`
|
||||
- Or for background subagents: `Subagent started: "..."`
|
||||
|
||||
While running, the block shows a live activity suffix (e.g. "Running: cargo test", "Compacting", "Retrying (2/3)") pulled from the child's turn tracker. The bullet animates (or is colored) according to state.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Session Management
|
||||
|
||||
Grok saves every conversation to disk automatically. Whether you work in the TUI, in headless mode, or over agent stdio, Grok records the exchange as a session. You can resume, rewind, or compact it. This document describes how to manage sessions.
|
||||
Kigi saves every conversation to disk automatically. Whether you work in the TUI, in headless mode, or over agent stdio, Kigi records the exchange as a session. You can resume, rewind, or compact it. This document describes how to manage sessions.
|
||||
|
||||
---
|
||||
|
||||
@@ -15,13 +15,13 @@ A session is a persistent conversation with full history. It includes:
|
||||
- Token usage and turn counts
|
||||
- Subagent sessions (when enabled)
|
||||
|
||||
Sessions are identified by a unique session ID (a UUIDv7 when Grok generates it; a client may supply its own ID with `-s`) and stored on disk under `~/.kigi/sessions/`. Set `KIGI_SHARE_DIR` to override the base directory; when it is unset, Grok uses `~/.kigi`.
|
||||
Sessions are identified by a unique session ID (a UUIDv7 when Kigi generates it; a client may supply its own ID with `-s`) and stored on disk under `~/.kigi/sessions/`. Set `KIGI_SHARE_DIR` to override the base directory; when it is unset, Kigi uses `~/.kigi`.
|
||||
|
||||
---
|
||||
|
||||
## Storage Layout
|
||||
|
||||
Grok stores each session in its own directory, grouped by working directory. It URL-encodes the working directory to name the group. When the encoded name exceeds 255 bytes, it instead uses a slug plus a hash and records the original path in a `.cwd` file inside the group.
|
||||
Kigi stores each session in its own directory, grouped by working directory. It URL-encodes the working directory to name the group. When the encoded name exceeds 255 bytes, it instead uses a slug plus a hash and records the original path in a `.cwd` file inside the group.
|
||||
|
||||
```
|
||||
~/.kigi/sessions/<encoded-cwd>/<session-id>/
|
||||
@@ -54,13 +54,13 @@ This clears the current context and begins a new conversation. Alias: `/clear`.
|
||||
|
||||
### Exit
|
||||
|
||||
End the session and quit Grok:
|
||||
End the session and quit Kigi:
|
||||
|
||||
```
|
||||
/quit
|
||||
```
|
||||
|
||||
Alias: `/exit`. To leave the current session but stay in Grok, use `/home` to return to the welcome screen.
|
||||
Alias: `/exit`. To leave the current session but stay in Kigi, use `/home` to return to the welcome screen.
|
||||
|
||||
---
|
||||
|
||||
@@ -85,14 +85,14 @@ To switch between, rename, or close the sessions that are currently active (the
|
||||
Resume a specific session by ID:
|
||||
|
||||
```bash
|
||||
grok --resume <session-id>
|
||||
kigi --resume <session-id>
|
||||
```
|
||||
|
||||
Run `grok --resume` without an ID to resume the most recent session for the current directory.
|
||||
Run `kigi --resume` without an ID to resume the most recent session for the current directory.
|
||||
|
||||
### From the Welcome Screen
|
||||
|
||||
When you launch `grok`, the welcome screen lists recent sessions for the current directory. Select one to resume it.
|
||||
When you launch `kigi`, the welcome screen lists recent sessions for the current directory. Select one to resume it.
|
||||
|
||||
---
|
||||
|
||||
@@ -128,7 +128,7 @@ Alias: `/title`.
|
||||
/rewind
|
||||
```
|
||||
|
||||
When you run `/rewind` (or press **Esc Esc** within 800ms while idle with an empty prompt and conversation messages), Grok:
|
||||
When you run `/rewind` (or press **Esc Esc** within 800ms while idle with an empty prompt and conversation messages), Kigi:
|
||||
|
||||
1. Shows a list of rewind points (one per user prompt)
|
||||
2. Lets you select which point to rewind to
|
||||
@@ -154,7 +154,7 @@ The optional `context` argument lets you provide additional instructions about w
|
||||
|
||||
### Auto-Compact
|
||||
|
||||
Grok automatically compacts the conversation when the context window approaches its limit. You will see a notification when auto-compact triggers. The `context_window` setting on your model configuration controls when this threshold is reached.
|
||||
Kigi automatically compacts the conversation when the context window approaches its limit. You will see a notification when auto-compact triggers. The `context_window` setting on your model configuration controls when this threshold is reached.
|
||||
|
||||
---
|
||||
|
||||
@@ -184,13 +184,13 @@ In headless mode, you manage sessions through command-line flags:
|
||||
|
||||
```bash
|
||||
# New session each time (default)
|
||||
grok -p "Hello"
|
||||
kigi -p "Hello"
|
||||
|
||||
# Resume an existing session by ID (errors if it does not exist)
|
||||
grok -p "Continue where we left off" -r <session-id>
|
||||
kigi -p "Continue where we left off" -r <session-id>
|
||||
|
||||
# Continue the most recent session in the current directory
|
||||
grok -p "What were we doing?" -c
|
||||
kigi -p "What were we doing?" -c
|
||||
```
|
||||
|
||||
In headless mode, resume an existing session with `-r`/`--resume`, which errors if the session does not exist, or continue the most recent session in the current directory with `-c`/`--continue`. Pass the session ID from JSON output (see below) to `-r`.
|
||||
@@ -200,7 +200,7 @@ Use `-s`/`--session-id` only to **create** a new session with a **UUID** (errors
|
||||
To read the session ID back, request JSON output:
|
||||
|
||||
```bash
|
||||
grok -p "Hello" --output-format json | jq -r '.sessionId'
|
||||
kigi -p "Hello" --output-format json | jq -r '.sessionId'
|
||||
```
|
||||
|
||||
---
|
||||
@@ -228,28 +228,28 @@ The agent persists all session updates automatically. Clients can reconnect and
|
||||
|
||||
---
|
||||
|
||||
## The grok sessions Subcommand
|
||||
## The kigi sessions Subcommand
|
||||
|
||||
List or search sessions from the command line. `grok sessions` requires a subcommand:
|
||||
List or search sessions from the command line. `kigi sessions` requires a subcommand:
|
||||
|
||||
```bash
|
||||
# List recent sessions for the current directory
|
||||
grok sessions list
|
||||
kigi sessions list
|
||||
|
||||
# Limit the number of results (default 20)
|
||||
grok sessions list --limit 50
|
||||
kigi sessions list --limit 50
|
||||
|
||||
# Search sessions by keyword (matches titles and prompts)
|
||||
grok sessions search "rate limit"
|
||||
kigi sessions search "rate limit"
|
||||
```
|
||||
|
||||
`grok sessions list` shows sessions for the current working directory, grouped by worktree label. Each row lists the session ID, the creation and update dates, the source status, and the summary. `grok sessions search` combines a local SQLite index with remote results.
|
||||
`kigi sessions list` shows sessions for the current working directory, grouped by worktree label. Each row lists the session ID, the creation and update dates, the source status, and the summary. `kigi sessions search` combines a local SQLite index with remote results.
|
||||
|
||||
---
|
||||
|
||||
## Worktree Sessions
|
||||
|
||||
When working with subagents or session forks, Grok can create isolated git worktrees per session. Each worktree gets its own copy of the working directory, so file changes in one session do not affect another.
|
||||
When working with subagents or session forks, Kigi can create isolated git worktrees per session. Each worktree gets its own copy of the working directory, so file changes in one session do not affect another.
|
||||
|
||||
Worktree sessions are managed internally through the `x.ai/git/worktree/*` extension methods. Key operations:
|
||||
|
||||
@@ -257,7 +257,7 @@ Worktree sessions are managed internally through the `x.ai/git/worktree/*` exten
|
||||
- **Apply**: Merge worktree changes back into the main working directory
|
||||
- **Remove**: Clean up a worktree when the session is done
|
||||
|
||||
Resume a session in a fresh worktree with `grok -w -r <session-id>`.
|
||||
Resume a session in a fresh worktree with `kigi -w -r <session-id>`.
|
||||
|
||||
---
|
||||
|
||||
@@ -265,13 +265,13 @@ Resume a session in a fresh worktree with `grok -w -r <session-id>`.
|
||||
|
||||
### Persistence Format
|
||||
|
||||
Grok stores the conversation as newline-delimited JSON (JSONL). Each line in `updates.jsonl` is a self-contained ACP session update event. This format supports:
|
||||
Kigi stores the conversation as newline-delimited JSON (JSONL). Each line in `updates.jsonl` is a self-contained ACP session update event. This format supports:
|
||||
|
||||
- Incremental writes (append-only during a session)
|
||||
- Efficient streaming reads (for session restore)
|
||||
- Easy debugging (each line is valid JSON)
|
||||
|
||||
The smaller state files -- `summary.json`, `plan.json`, and `signals.json` -- are plain JSON rather than JSONL. JSONL is the source of truth for session content; `grok sessions search` additionally maintains a local SQLite FTS5 index over session titles and prompts for fast keyword search.
|
||||
The smaller state files -- `summary.json`, `plan.json`, and `signals.json` -- are plain JSON rather than JSONL. JSONL is the source of truth for session content; `kigi sessions search` additionally maintains a local SQLite FTS5 index over session titles and prompts for fast keyword search.
|
||||
|
||||
### Session Metadata
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ Sandbox mode is off by default.
|
||||
|
||||
```bash
|
||||
# Run with workspace sandbox (read everywhere, write to CWD + temp dirs + ~/.kigi/)
|
||||
grok --sandbox workspace
|
||||
kigi --sandbox workspace
|
||||
|
||||
# Read-only mode (read everywhere, write only to ~/.kigi/ + temp dirs)
|
||||
grok --sandbox read-only
|
||||
kigi --sandbox read-only
|
||||
|
||||
# Most restrictive profile (read CWD + system paths, write CWD + temp dirs + ~/.kigi/, no child network)
|
||||
grok --sandbox strict
|
||||
kigi --sandbox strict
|
||||
```
|
||||
|
||||
---
|
||||
@@ -70,12 +70,12 @@ deny = ["/data/shared-secrets", "**/.env", "**/*.pem"]
|
||||
Use the custom profile:
|
||||
|
||||
```bash
|
||||
grok --sandbox project
|
||||
kigi --sandbox project
|
||||
```
|
||||
|
||||
A custom profile can't reuse a built-in name. `--sandbox devbox` always runs the built-in `devbox` profile, shadowing any `[profiles.devbox]` you define.
|
||||
|
||||
When the global and per-project files define the same custom profile name, the user-level definition takes precedence and the project definition is ignored. If those two definitions differ, Grok warns about the conflict at startup — on the welcome screen in the TUI, and on stderr for headless runs. Identical duplicate definitions do not produce a warning.
|
||||
When the global and per-project files define the same custom profile name, the user-level definition takes precedence and the project definition is ignored. If those two definitions differ, Kigi warns about the conflict at startup — on the welcome screen in the TUI, and on stderr for headless runs. Identical duplicate definitions do not produce a warning.
|
||||
|
||||
### Custom Profile Fields
|
||||
|
||||
@@ -92,7 +92,7 @@ When the global and per-project files define the same custom profile name, the u
|
||||
> bind-over on Linux, so a denied path can neither be read (via `bash`, `grep`, or
|
||||
> subagents) nor relocated out of the deny set and read elsewhere (the
|
||||
> `mv secret x && cat x` bypass is closed). On **Linux**, read-deny requires
|
||||
> `bubblewrap`: if it is missing (or any single deny path can't be bound), Grok
|
||||
> `bubblewrap`: if it is missing (or any single deny path can't be bound), Kigi
|
||||
> refuses to start rather than run with denied paths exposed (`devbox`, which only
|
||||
> write-denies `/data`, still falls back to Landlock). Writes to paths **not** in
|
||||
> `deny` are controlled by what you grant in `read_write`.
|
||||
@@ -112,7 +112,7 @@ When the global and per-project files define the same custom profile name, the u
|
||||
> Brace alternation (`{a,b}`), backslash-escapes, and the unusual class forms
|
||||
> `[]…]` (literal `]` first) and POSIX `[[:…:]]` are **not** supported, so the two
|
||||
> platforms can never interpret a glob differently. A glob using an unsupported
|
||||
> metacharacter, or one that is malformed, makes Grok **refuse to start** (fail
|
||||
> metacharacter, or one that is malformed, makes Kigi **refuse to start** (fail
|
||||
> closed) on **both** platforms — write `*.pem` and `*.key` as separate entries
|
||||
> rather than `*.{pem,key}`.
|
||||
>
|
||||
@@ -121,19 +121,19 @@ When the global and per-project files define the same custom profile name, the u
|
||||
> matching. Enforcement otherwise differs by platform:
|
||||
>
|
||||
> - **macOS is airtight:** each glob becomes a Seatbelt regex applied at runtime,
|
||||
> so matching files are denied **even if created after Grok starts**.
|
||||
> so matching files are denied **even if created after Kigi starts**.
|
||||
> - **Linux is best-effort:** a mount namespace can't glob at runtime, so each
|
||||
> glob is expanded to the files that **exist at launch** and those are bound
|
||||
> over. Files created **later** that match a glob are **not** covered — name
|
||||
> exact paths for anything that must be airtight on Linux. A glob that matches
|
||||
> too many files, or whose tree is too deep/broad to walk, makes Grok **refuse
|
||||
> too many files, or whose tree is too deep/broad to walk, makes Kigi **refuse
|
||||
> to start** rather than under-enforce.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
The sandbox is applied to the **entire grok process** at startup using kernel primitives -- not per-command wrapping. This means all tool operations are covered:
|
||||
The sandbox is applied to the **entire kigi process** at startup using kernel primitives -- not per-command wrapping. This means all tool operations are covered:
|
||||
|
||||
- `read_file`, `search_replace`, `list_dir` -- restricted by Landlock/Seatbelt in-process
|
||||
- `bash` commands, `grep` (rg) -- child processes inherit FS restrictions automatically
|
||||
@@ -146,8 +146,8 @@ The sandbox is **irreversible** once applied. The agent cannot relax restriction
|
||||
## Resuming Sessions
|
||||
|
||||
The profile a session was started with is saved with the session and is **fixed
|
||||
for the life of the session**. When you resume it (`grok --resume <id>`,
|
||||
`grok --continue`, or `grok -r`), Grok restores that same profile automatically —
|
||||
for the life of the session**. When you resume it (`kigi --resume <id>`,
|
||||
`kigi --continue`, or `kigi -r`), Kigi restores that same profile automatically —
|
||||
so a session started with `--sandbox workspace` won't silently come back under a
|
||||
stricter default and break commands that previously worked.
|
||||
|
||||
@@ -176,7 +176,7 @@ Profile resolution order for a **new** session:
|
||||
| Linux | Landlock | Kernel 5.13 or later |
|
||||
| macOS | Seatbelt | macOS (all versions) |
|
||||
|
||||
If the sandbox cannot be applied (e.g., unsupported kernel, missing entitlements), Grok logs a warning and continues without enforcement. The exception is an explicitly-requested **custom profile**: on **both macOS and Linux**, if it cannot be applied (unknown profile, malformed `sandbox.toml`, or — on Linux — `bubblewrap` unavailable for a non-empty `deny`), Grok refuses to start rather than run with its denied paths exposed.
|
||||
If the sandbox cannot be applied (e.g., unsupported kernel, missing entitlements), Kigi logs a warning and continues without enforcement. The exception is an explicitly-requested **custom profile**: on **both macOS and Linux**, if it cannot be applied (unknown profile, malformed `sandbox.toml`, or — on Linux — `bubblewrap` unavailable for a non-empty `deny`), Kigi refuses to start rather than run with its denied paths exposed.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Background Tasks and Monitoring
|
||||
|
||||
Grok runs long-lived processes without blocking the conversation. This document covers background commands, the `/loop` command, the `monitor` tool, and the scheduler.
|
||||
Kigi runs long-lived processes without blocking the conversation. This document covers background commands, the `/loop` command, the `monitor` tool, and the scheduler.
|
||||
|
||||
---
|
||||
|
||||
@@ -100,7 +100,7 @@ The `monitor` tool streams events from a long-running script. Each line of outpu
|
||||
### How It Works
|
||||
|
||||
1. You provide a shell command (`command`) and a short `description` that appears in every notification.
|
||||
2. Grok merges the command's stdout and stderr into a single output file.
|
||||
2. Kigi merges the command's stdout and stderr into a single output file.
|
||||
3. Each new line in that file becomes a notification delivered to the conversation.
|
||||
4. The monitor runs until the command exits or you stop it.
|
||||
|
||||
@@ -143,7 +143,7 @@ Stop persistent monitors with `kill_command_or_subagent(task_id)`.
|
||||
|
||||
### Volume Control
|
||||
|
||||
If a monitor produces too many events, Grok stops it automatically. When this happens, restart the monitor with a tighter filter. Prefer `grep --line-buffered`, `awk`, or a wrapper script that emits only the events you care about.
|
||||
If a monitor produces too many events, Kigi stops it automatically. When this happens, restart the monitor with a tighter filter. Prefer `grep --line-buffered`, `awk`, or a wrapper script that emits only the events you care about.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Terminal Support and Troubleshooting
|
||||
|
||||
Grok Build runs as a full-screen TUI. To draw the interface, it relies on terminal escape sequences for color, clipboard, mouse, and full-screen control. Some terminals, multiplexers, and SSH sessions handle these sequences differently.
|
||||
Kigi runs as a full-screen TUI. To draw the interface, it relies on terminal escape sequences for color, clipboard, mouse, and full-screen control. Some terminals, multiplexers, and SSH sessions handle these sequences differently.
|
||||
|
||||
## Quick Fixes
|
||||
|
||||
@@ -33,7 +33,7 @@ tmux source-file ~/.tmux.conf
|
||||
# or detach and reattach
|
||||
```
|
||||
|
||||
### Live diagnostics inside Grok
|
||||
### Live diagnostics inside Kigi
|
||||
|
||||
Run this slash command:
|
||||
|
||||
@@ -41,13 +41,13 @@ Run this slash command:
|
||||
/terminal-setup
|
||||
```
|
||||
|
||||
The command reports the terminal, multiplexer, **color level**, **available themes**, and clipboard routes Grok detected, then lists any issues and how to fix them. When color is below truecolor, it explains how to unlock the truecolor-only themes (TokyoNight, RosePineMoon, OscuraMidnight) — or notes that Terminal.app is inherently 256-color. The aliases `/terminal-check` and `/terminal-info` run the same command.
|
||||
The command reports the terminal, multiplexer, **color level**, **available themes**, and clipboard routes Kigi detected, then lists any issues and how to fix them. When color is below truecolor, it explains how to unlock the truecolor-only themes (TokyoNight, RosePineMoon, OscuraMidnight) — or notes that Terminal.app is inherently 256-color. The aliases `/terminal-check` and `/terminal-info` run the same command.
|
||||
|
||||
---
|
||||
|
||||
## Detected Terminals
|
||||
|
||||
Grok detects these terminal emulators from environment variables:
|
||||
Kigi detects these terminal emulators from environment variables:
|
||||
|
||||
- **Apple Terminal** (Terminal.app)
|
||||
- **Ghostty**
|
||||
@@ -60,13 +60,13 @@ Grok detects these terminal emulators from environment variables:
|
||||
- **foot** (Wayland-native, Linux)
|
||||
- **VS Code**, **Cursor**, **Windsurf**, and **Zed** integrated terminals
|
||||
- **JetBrains** IDE terminals (IntelliJ, PhpStorm, and others)
|
||||
- **Grok Desktop**
|
||||
- **Kigi Desktop**
|
||||
- **VTE**-based terminals (GNOME Terminal, GNOME Console, Tilix)
|
||||
- **Windows Terminal**
|
||||
|
||||
Detection has these limitations:
|
||||
|
||||
- Inside tmux, the variables Grok needs to identify the terminal don't reach the pager.
|
||||
- Inside tmux, the variables Kigi needs to identify the terminal don't reach the pager.
|
||||
- Over SSH, many terminal variables aren't forwarded.
|
||||
- tmux's global environment (`tmux -g`) reflects the first client that attached to the server, not your current session.
|
||||
|
||||
@@ -78,34 +78,34 @@ Detection has these limitations:
|
||||
|
||||
**Cause**: `COLORTERM` not set or tmux not configured for 24-bit RGB.
|
||||
|
||||
**Fix**: Apply the two settings above, then restart Grok.
|
||||
**Fix**: Apply the two settings above, then restart Kigi.
|
||||
|
||||
**Verify**: Run `/terminal-setup`. Expect `color truecolor` and `themes all`. If `color` is `256` or `basic`, the issues section has the unlock fix.
|
||||
|
||||
### Problem: Clipboard problems
|
||||
|
||||
Grok writes to the clipboard through up to three routes, which match the **Clipboard routes** section of `/terminal-setup`:
|
||||
Kigi writes to the clipboard through up to three routes, which match the **Clipboard routes** section of `/terminal-setup`:
|
||||
|
||||
- **native** — Grok always writes to the native OS clipboard first.
|
||||
- **tmux buffer** — inside tmux, Grok also writes to the tmux paste buffer (`tmux load-buffer`).
|
||||
- **OSC 52** — Grok emits the OSC 52 escape sequence so the outer terminal updates its clipboard. Grok always emits OSC 52 inside tmux. Outside tmux, it emits OSC 52 on Linux, over SSH, or in a container without a display.
|
||||
- **native** — Kigi always writes to the native OS clipboard first.
|
||||
- **tmux buffer** — inside tmux, Kigi also writes to the tmux paste buffer (`tmux load-buffer`).
|
||||
- **OSC 52** — Kigi emits the OSC 52 escape sequence so the outer terminal updates its clipboard. Kigi always emits OSC 52 inside tmux. Outside tmux, it emits OSC 52 on Linux, over SSH, or in a container without a display.
|
||||
|
||||
**Linux Wayland**: on compositors that support the data-control protocol (GNOME 48+, KDE, Sway, Hyprland — the `data-control` line in `/terminal-setup` shows `yes`) copies work even if the terminal loses focus mid-copy. On older compositors (GNOME 46/47), keep the terminal focused until the copy toast confirms, and install the `wl-clipboard` package (provides `wl-copy`) for the most reliable route — Grok shows a startup warning when this applies. If data-control misbehaves on your compositor, set `KIGI_CLIPBOARD_NO_DATA_CONTROL=1` to stop Grok from speaking that protocol entirely — copies then go through the CLI tools (`wl-copy`/`xclip`).
|
||||
**Linux Wayland**: on compositors that support the data-control protocol (GNOME 48+, KDE, Sway, Hyprland — the `data-control` line in `/terminal-setup` shows `yes`) copies work even if the terminal loses focus mid-copy. On older compositors (GNOME 46/47), keep the terminal focused until the copy toast confirms, and install the `wl-clipboard` package (provides `wl-copy`) for the most reliable route — Kigi shows a startup warning when this applies. If data-control misbehaves on your compositor, set `KIGI_CLIPBOARD_NO_DATA_CONTROL=1` to stop Kigi from speaking that protocol entirely — copies then go through the CLI tools (`wl-copy`/`xclip`).
|
||||
|
||||
**Linux X11 selections**: X11 **PRIMARY** and **CLIPBOARD** are separate. Selecting text usually fills PRIMARY; an explicit Copy action fills CLIPBOARD. In Grok:
|
||||
**Linux X11 selections**: X11 **PRIMARY** and **CLIPBOARD** are separate. Selecting text usually fills PRIMARY; an explicit Copy action fills CLIPBOARD. In Kigi:
|
||||
|
||||
- An unmodified middle click reads PRIMARY only when `DISPLAY` is non-empty. Pure X11 can fall back to the native arboard reader. XWayland must have `xclip` or `xsel` on `PATH`; Grok deliberately disables the arboard fallback there so it cannot substitute Wayland PRIMARY.
|
||||
- An unmodified middle click reads PRIMARY only when `DISPLAY` is non-empty. Pure X11 can fall back to the native arboard reader. XWayland must have `xclip` or `xsel` on `PATH`; Kigi deliberately disables the arboard fallback there so it cannot substitute Wayland PRIMARY.
|
||||
- `Ctrl+V` reads CLIPBOARD only and never falls back to PRIMARY. To fill CLIPBOARD from a shell, run `printf %s "text" | xclip -selection clipboard`.
|
||||
- `Shift+Insert` remains the terminal-native selected-text paste. Native Wayland PRIMARY behavior is compositor/terminal-specific and is not inferred from `TERM` or an incoming mouse event.
|
||||
|
||||
**SSH and selected text**: a remote Grok process usually cannot read the local terminal's PRIMARY or CLIPBOARD selection. Use terminal-native `Shift+Insert`, or hold `Shift` while middle-clicking when your terminal uses that gesture to bypass mouse reporting. The terminal then sends the local selection through the PTY instead of asking the remote process to access it.
|
||||
**SSH and selected text**: a remote Kigi process usually cannot read the local terminal's PRIMARY or CLIPBOARD selection. Use terminal-native `Shift+Insert`, or hold `Shift` while middle-clicking when your terminal uses that gesture to bypass mouse reporting. The terminal then sends the local selection through the PTY instead of asking the remote process to access it.
|
||||
|
||||
**Known limitation — Apple Terminal + SSH**:
|
||||
Apple Terminal ignores OSC 52, so copying from a Grok session over SSH can't reach your local clipboard. Use the workaround below.
|
||||
Apple Terminal ignores OSC 52, so copying from a Kigi session over SSH can't reach your local clipboard. Use the workaround below.
|
||||
|
||||
**Temporary workaround**: Use `grok wrap ssh` instead of plain `ssh` (for example, `grok wrap ssh user@host`). It runs the command in a local PTY that intercepts OSC 52 sequences, including tmux-wrapped ones, and writes their contents to your local clipboard. The same command wraps anything else whose clipboard can't reach you — for example `grok wrap docker exec -it <container> bash` or `grok wrap kubectl exec -it <pod> -- bash`.
|
||||
**Temporary workaround**: Use `kigi wrap ssh` instead of plain `ssh` (for example, `kigi wrap ssh user@host`). It runs the command in a local PTY that intercepts OSC 52 sequences, including tmux-wrapped ones, and writes their contents to your local clipboard. The same command wraps anything else whose clipboard can't reach you — for example `kigi wrap docker exec -it <container> bash` or `kigi wrap kubectl exec -it <pod> -- bash`.
|
||||
|
||||
> **Warning**: `grok wrap` is **experimental** and may misbehave in some setups.
|
||||
> **Warning**: `kigi wrap` is **experimental** and may misbehave in some setups.
|
||||
|
||||
**iTerm2 setting**:
|
||||
iTerm2 requires explicit permission for OSC 52:
|
||||
@@ -113,7 +113,7 @@ iTerm2 requires explicit permission for OSC 52:
|
||||
1. iTerm2 → **Settings** → **General** → **Selection**
|
||||
2. Enable **"Applications in terminal may access clipboard"**
|
||||
|
||||
This setting is off by default for security reasons. Without it, OSC 52 writes from Grok (or any TUI) will be ignored.
|
||||
This setting is off by default for security reasons. Without it, OSC 52 writes from Kigi (or any TUI) will be ignored.
|
||||
|
||||
**Fix for other cases**:
|
||||
- `set -g set-clipboard on` in tmux config
|
||||
@@ -124,13 +124,13 @@ This setting is off by default for security reasons. Without it, OSC 52 writes f
|
||||
**Cause**: Zellij, tmux control mode (`tmux -CC`), or config set to `never`.
|
||||
|
||||
**Fix**:
|
||||
- In Zellij or control mode, Grok intentionally runs inline (no alt screen).
|
||||
- In Zellij or control mode, Kigi intentionally runs inline (no alt screen).
|
||||
- Set `[terminal] alt_screen = "always"` in `~/.kigi/pager.toml` to force fullscreen.
|
||||
- Use the CLI flag `--no-alt-screen` to disable alt-screen mode entirely (useful for debugging or when the alternate screen causes issues in your terminal).
|
||||
|
||||
### Problem: Zellij keybindings interfere with Grok (Ctrl+g, Ctrl+o, etc.)
|
||||
### Problem: Zellij keybindings interfere with Kigi (Ctrl+g, Ctrl+o, etc.)
|
||||
|
||||
Zellij intercepts many Ctrl/Alt key combinations before they reach full-screen TUIs like Grok.
|
||||
Zellij intercepts many Ctrl/Alt key combinations before they reach full-screen TUIs like Kigi.
|
||||
|
||||
**Best fix** (Zellij 0.41+): Switch to the **"Unlock-First (non-colliding)"** preset:
|
||||
|
||||
@@ -139,15 +139,15 @@ Zellij intercepts many Ctrl/Alt key combinations before they reach full-screen T
|
||||
3. Select **"Unlock-First (non-colliding)"**
|
||||
4. Press `Enter` (or `Ctrl+a` to save permanently)
|
||||
|
||||
After this, Zellij starts **locked**. Most keys pass through to Grok. Press `Ctrl+g` to temporarily unlock Zellij when you need its pane/session management.
|
||||
After this, Zellij starts **locked**. Most keys pass through to Kigi. Press `Ctrl+g` to temporarily unlock Zellij when you need its pane/session management.
|
||||
|
||||
Zellij recommends this approach for TUI users.
|
||||
|
||||
### Problem: `Ctrl+Enter` doesn't interject in WezTerm
|
||||
|
||||
**Cause**: WezTerm ships with the Kitty keyboard protocol disabled. Grok relies on it to tell `Ctrl+Enter` (interject) and `Shift+Enter` (send in multiline mode) apart from plain `Enter`. Most other terminals enable the protocol when Grok requests it.
|
||||
**Cause**: WezTerm ships with the Kitty keyboard protocol disabled. Kigi relies on it to tell `Ctrl+Enter` (interject) and `Shift+Enter` (send in multiline mode) apart from plain `Enter`. Most other terminals enable the protocol when Kigi requests it.
|
||||
|
||||
For the same reason, in Apple Terminal, Grok binds `Ctrl+O` to interject.
|
||||
For the same reason, in Apple Terminal, Kigi binds `Ctrl+O` to interject.
|
||||
|
||||
**Fix**:
|
||||
|
||||
@@ -157,9 +157,9 @@ Add this after `config = wezterm.config_builder()` in `~/.config/wezterm/wezterm
|
||||
config.enable_kitty_keyboard = true
|
||||
```
|
||||
|
||||
Reload (`Cmd+Shift+R` or restart WezTerm) and restart `grok`.
|
||||
Reload (`Cmd+Shift+R` or restart WezTerm) and restart `kigi`.
|
||||
|
||||
**Verify**: Run `/terminal-setup` inside Grok. While a turn is active, you see the interject hint, and `Ctrl+Enter` interjects.
|
||||
**Verify**: Run `/terminal-setup` inside Kigi. While a turn is active, you see the interject hint, and `Ctrl+Enter` interjects.
|
||||
|
||||
**Quick workaround** (no global change):
|
||||
|
||||
@@ -176,24 +176,24 @@ table.insert(config.keys, {
|
||||
**Cause**: VS Code's integrated terminal (and the Cursor / Windsurf / Zed
|
||||
forks) use xterm.js, which only partially implements the Kitty keyboard
|
||||
protocol — it mis-encodes shifted printable keys (`!@#$%^&*()` arrive as
|
||||
plain digits). Grok therefore never negotiates the protocol for these
|
||||
plain digits). Kigi therefore never negotiates the protocol for these
|
||||
terminals. Without it, xterm.js sends a bare `CR` for `Shift+Enter`,
|
||||
byte-for-byte identical to plain `Enter`, so the chord can't be told apart
|
||||
and the prompt submits.
|
||||
|
||||
This also affects VS Code reached **over SSH** (e.g. into a devbox or
|
||||
container): `TERM_PROGRAM` isn't forwarded, so Grok sees an `Unknown`
|
||||
container): `TERM_PROGRAM` isn't forwarded, so Kigi sees an `Unknown`
|
||||
terminal and skips the protocol for the same reason.
|
||||
|
||||
**Fix**: Use **`Alt+Enter`** to insert a newline. xterm.js delivers it
|
||||
reliably as `ESC`+`CR` regardless of the keyboard protocol, and Grok's
|
||||
reliably as `ESC`+`CR` regardless of the keyboard protocol, and Kigi's
|
||||
prompt hint bar advertises `Alt+Enter: newline` whenever it detects this
|
||||
situation. Run `/terminal-setup` to confirm — the `newline` row shows
|
||||
`Alt+Enter` when `Shift+Enter` is unavailable.
|
||||
|
||||
### Problem: Mouse scrolling stops working (native scrollbar takes over)
|
||||
|
||||
If Grok's mouse-driven scrolling stops responding and your terminal falls back to its native scrollbar, mouse reporting is off.
|
||||
If Kigi's mouse-driven scrolling stops responding and your terminal falls back to its native scrollbar, mouse reporting is off.
|
||||
|
||||
**Apple Terminal**: Go to **View > Allow Mouse Reporting** (keyboard shortcut `Cmd+R`) to re-enable it. A checkmark appears next to the option when active.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Permissions and Safety Controls
|
||||
|
||||
Grok can read files, search code, edit files, and run shell commands. The permission system controls what the agent is allowed to do. You can combine several independent layers: permission rules, permission modes, hooks, and the OS-level sandbox.
|
||||
Kigi can read files, search code, edit files, and run shell commands. The permission system controls what the agent is allowed to do. You can combine several independent layers: permission rules, permission modes, hooks, and the OS-level sandbox.
|
||||
|
||||
This guide explains how a tool call is authorized, how to configure permission rules from the CLI, native configuration, or Claude settings, and how to use `PreToolUse` hooks for allow lists that apply in every mode.
|
||||
|
||||
@@ -100,13 +100,13 @@ Do not use `permission_mode` for this; it is a user-switchable default, not a lo
|
||||
|
||||
The user-level `~/.kigi/requirements.toml` is under the user's control, so a developer can remove the lock by editing that file. For enforcement that users cannot override, deploy the setting in the root-owned system file `/etc/kigi/requirements.toml`.
|
||||
|
||||
> **Note:** Grok honors the permission rules in Claude Code's `managed-settings.json`, but not its `disableBypassPermissionsMode` lock. To disable always-approve in Grok, use `requirements.toml` as shown above.
|
||||
> **Note:** Kigi honors the permission rules in Claude Code's `managed-settings.json`, but not its `disableBypassPermissionsMode` lock. To disable always-approve in Kigi, use `requirements.toml` as shown above.
|
||||
|
||||
---
|
||||
|
||||
## Configuring Permissions
|
||||
|
||||
Grok reads permission rules from three compatible sources. Rules from all sources are merged into one set; a rule's effect depends on its action (`deny` > `ask` > `allow`), not on which file it came from.
|
||||
Kigi reads permission rules from three compatible sources. Rules from all sources are merged into one set; a rule's effect depends on its action (`deny` > `ask` > `allow`), not on which file it came from.
|
||||
|
||||
### Where Permission Rules Live (Scopes)
|
||||
|
||||
@@ -117,13 +117,13 @@ Permission rules can be global (all projects), project-scoped (one repository),
|
||||
| Global (all projects) | `~/.kigi/config.toml` | No |
|
||||
| Project (committed) | `<project>/.kigi/config.toml` | Yes (commit it) |
|
||||
| Project (personal) | `<project>/.claude/settings.local.json` | No (gitignore it) |
|
||||
| Interactive grants | Stored internally by Grok, per project | No |
|
||||
| Interactive grants | Stored internally by Kigi, per project | No |
|
||||
|
||||
Notes on scoping:
|
||||
|
||||
- Grok discovers a `.kigi/config.toml` at every directory level from the repository root down to your working directory, so a subdirectory can add rules on top of the repo root's.
|
||||
- Kigi discovers a `.kigi/config.toml` at every directory level from the repository root down to your working directory, so a subdirectory can add rules on top of the repo root's.
|
||||
- Rules from all scopes are merged into one rule set; `deny` > `ask` > `allow` applies across scopes, so a global `deny` cannot be overridden by a project `allow`.
|
||||
- Grok has no native `config.local.toml`. For personal, uncommitted rules in a project, use `.claude/settings.local.json`; Grok reads it directly (see [Claude Code Compatibility](#3-claude-code-compatibility-claudesettingsjson)).
|
||||
- Kigi has no native `config.local.toml`. For personal, uncommitted rules in a project, use `.claude/settings.local.json`; Kigi reads it directly (see [Claude Code Compatibility](#3-claude-code-compatibility-claudesettingsjson)).
|
||||
- Interactive "Always allow" decisions are stored outside the repository, scoped to the project (see [Interactive Approvals](#interactive-approvals-and-where-they-persist)).
|
||||
|
||||
To stop prompts for a specific command in one project, add a narrow allow rule to that project's `.kigi/config.toml` (or `.claude/settings.json`):
|
||||
@@ -138,7 +138,7 @@ This approves only the listed commands. Always-approve mode, by contrast, approv
|
||||
### 1. CLI Flags
|
||||
|
||||
```bash
|
||||
grok -p "Review the API changes" \
|
||||
kigi -p "Review the API changes" \
|
||||
--allow 'Bash(git *)' \
|
||||
--allow 'Bash(gh *)' \
|
||||
--allow 'Read' \
|
||||
@@ -179,7 +179,7 @@ Because `deny` always wins, you cannot combine these `allow` rules with a catch-
|
||||
|
||||
Rules from the global `~/.kigi/config.toml` and every project `.kigi/config.toml` (from the repo root down to your working directory) are merged into one rule set, alongside any `.claude/settings.json` rules.
|
||||
|
||||
Managed configuration deployed by your organization also contributes `[permission]` rules: the system `/etc/kigi/managed_config.toml`, and a user-level copy that Grok maintains automatically at `~/.kigi/managed_config.toml`. Managed rules merge like rules from any other source, with two properties specific to managed `allow` rules: your own `deny` and `ask` rules win over a managed `allow` (severity ordering), and a catch-all managed `allow` is ignored when always-approve is locked off. For rules that users cannot edit away, use the root-owned system `/etc/kigi/requirements.toml`.
|
||||
Managed configuration deployed by your organization also contributes `[permission]` rules: the system `/etc/kigi/managed_config.toml`, and a user-level copy that Kigi maintains automatically at `~/.kigi/managed_config.toml`. Managed rules merge like rules from any other source, with two properties specific to managed `allow` rules: your own `deny` and `ask` rules win over a managed `allow` (severity ordering), and a catch-all managed `allow` is ignored when always-approve is locked off. For rules that users cannot edit away, use the root-owned system `/etc/kigi/requirements.toml`.
|
||||
|
||||
Permission rules from every source are read once, when a session starts. Changes apply to the next session.
|
||||
|
||||
@@ -202,7 +202,7 @@ allow = [
|
||||
|
||||
### 3. Claude Code Compatibility (`.claude/settings.json`)
|
||||
|
||||
Grok reads `~/.claude/settings.json` and `~/.claude/settings.local.json`, plus the project-level `<project>/.claude/settings.json` and `settings.local.json` (walking up to the repo root). The native `.kigi` source for permission rules is `config.toml`, described in the section above.
|
||||
Kigi reads `~/.claude/settings.json` and `~/.claude/settings.local.json`, plus the project-level `<project>/.claude/settings.json` and `settings.local.json` (walking up to the repo root). The native `.kigi` source for permission rules is `config.toml`, described in the section above.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -223,7 +223,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
Supported `defaultMode` values are `default`, `acceptEdits`, `bypassPermissions`, `dontAsk`, and `plan`. Grok reads `defaultMode` from its canonical location under `permissions`; a top-level `defaultMode` is also accepted when the nested key is absent.
|
||||
Supported `defaultMode` values are `default`, `acceptEdits`, `bypassPermissions`, `dontAsk`, and `plan`. Kigi reads `defaultMode` from its canonical location under `permissions`; a top-level `defaultMode` is also accepted when the nested key is absent.
|
||||
|
||||
`permissions.allow`, `permissions.deny`, and `permissions.ask` entries are translated into native rules and then matched with the semantics in the [Rule Matching Reference](#rule-matching-reference). Translation notes:
|
||||
|
||||
@@ -250,7 +250,7 @@ Matching is case-sensitive. Leading whitespace in the command is trimmed before
|
||||
|
||||
A trailing `:*` suffix on a Bash rule is stripped to a plain prefix: `Bash(git commit:*)` becomes prefix `git commit`. Because prefixes have no word boundary, a `deny` written as `Bash(sed:*)` also blocks commands such as `sed-custom`.
|
||||
|
||||
**Chained commands.** Grok parses each command like a shell and splits it on `&&`, `||`, `;`, `|`, and newlines. The rule actions treat segments differently:
|
||||
**Chained commands.** Kigi parses each command like a shell and splits it on `&&`, `||`, `;`, `|`, and newlines. The rule actions treat segments differently:
|
||||
|
||||
- `deny` and `ask` rules are checked against every segment, and against the whole string. One denied segment rejects the entire command.
|
||||
- `allow` rules are checked against the whole command string only. `Bash(git *)` therefore auto-approves `git status && rm -rf /`, because the full string starts with `git `. Pair narrow allow rules with `deny` rules for the patterns you want to block.
|
||||
@@ -277,7 +277,7 @@ Path patterns are globs matched against the path string the tool was called with
|
||||
|
||||
### MCP Rules
|
||||
|
||||
`MCPTool(...)` patterns match the full Grok tool name in `server__tool` form, with glob support: `MCPTool(linear__*)` matches every tool from the `linear` server. Grok tool names carry no `mcp__` prefix, so a rule written as `mcp__server__tool` never matches an MCP call; write `MCPTool(server__tool)` instead.
|
||||
`MCPTool(...)` patterns match the full Kigi tool name in `server__tool` form, with glob support: `MCPTool(linear__*)` matches every tool from the `linear` server. Kigi tool names carry no `mcp__` prefix, so a rule written as `mcp__server__tool` never matches an MCP call; write `MCPTool(server__tool)` instead.
|
||||
|
||||
### WebFetch Rules
|
||||
|
||||
@@ -325,7 +325,7 @@ The remembered prefix is limited to a short form of the command: read-only comma
|
||||
|
||||
### Persistence Is Per Project
|
||||
|
||||
Interactive grants are stored in Grok's own state directory under your home directory, scoped to the directory you launched Grok from. A grant made in one project never applies in another, grants are not written into the repository, and they are not meant to be hand-edited.
|
||||
Interactive grants are stored in Kigi's own state directory under your home directory, scoped to the directory you launched Kigi from. A grant made in one project never applies in another, grants are not written into the repository, and they are not meant to be hand-edited.
|
||||
|
||||
Interactive grants are personal, per-machine state. For an allowlist you can review in code review and share with teammates, use declarative rules in the project's `.kigi/config.toml` instead.
|
||||
|
||||
@@ -411,7 +411,7 @@ For hook installation, the JSON format, the trust model for project hooks, and o
|
||||
### Headless git and gh Only (CI and Automation)
|
||||
|
||||
```bash
|
||||
grok -p "Implement the feature using only git and GitHub CLI" \
|
||||
kigi -p "Implement the feature using only git and GitHub CLI" \
|
||||
--allow 'Read' \
|
||||
--allow 'Grep' \
|
||||
--allow 'Bash(git *)' \
|
||||
|
||||
@@ -12,7 +12,7 @@ which already shows when work is in flight.
|
||||
|
||||
Three entry points, all opening the same view:
|
||||
|
||||
- **`grok dashboard`** — launches the TUI directly into the dashboard.
|
||||
- **`kigi dashboard`** — launches the TUI directly into the dashboard.
|
||||
- **`/dashboard`** (aliases **`/agents-dashboard`**, **`/sessions`**) — open
|
||||
from inside an active session.
|
||||
- **Ctrl+\\** — same as the slash command, two keystrokes. Configurable
|
||||
@@ -23,7 +23,7 @@ Three entry points, all opening the same view:
|
||||
## What you see
|
||||
|
||||
```
|
||||
Grok Build · Dashboard — 4 agents · 2 awaiting
|
||||
Kigi · Dashboard — 4 agents · 2 awaiting
|
||||
▌● reviewer · audit token flow Awaiting your input 2m
|
||||
● implementer · fix login bug Running: cargo test 12m
|
||||
⋅ refactor · feat/login Responding… 24m
|
||||
@@ -51,7 +51,7 @@ bottom of the group; select it and press `Enter` / `→` (or click it) to
|
||||
reveal them all, and `←` to re-fold. The Idle header always shows the true total. Folding is
|
||||
suspended while a filter or search is active (so every match shows).
|
||||
|
||||
The state icon matches Grok Build's sibling views (
|
||||
The state icon matches Kigi's sibling views (
|
||||
`tasks_pane`):
|
||||
|
||||
- `⋅`/`:`/`⸬`/`⁙` — animated spinner for **Working** rows.
|
||||
@@ -346,6 +346,6 @@ friendly toast.
|
||||
## Phase 4 (out of scope for v1)
|
||||
|
||||
The current dashboard lists only agents owned by **this** pager
|
||||
process. The plan's Phase 4 ("supervisor / `grok --bg`") would list
|
||||
process. The plan's Phase 4 ("supervisor / `kigi --bg`") would list
|
||||
sessions that survive pager exit — that's a separate roadmap and not
|
||||
shipped yet.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Monitoring Usage (External OpenTelemetry)
|
||||
|
||||
> **Status: alpha.** The schema below is versioned (`grok_code.schema.version = v1`);
|
||||
> **Status: alpha.** The schema below is versioned (`kigi_code.schema.version = v1`);
|
||||
> additive changes may occur without notice, renames/removals will bump the
|
||||
> version and be called out in the changelog.
|
||||
|
||||
Grok CLI can export usage **metrics** and **events** to your organization's
|
||||
Kigi CLI can export usage **metrics** and **events** to your organization's
|
||||
own OpenTelemetry collector, so platform teams can monitor adoption, token
|
||||
consumption, tool-permission decisions, and errors across the fleet — without
|
||||
any data flowing through SpaceXAI.
|
||||
@@ -32,7 +32,7 @@ export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf # or grpc
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.corp.example:4318
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <collector-token>"
|
||||
grok
|
||||
kigi
|
||||
```
|
||||
|
||||
`KIGI_EXTERNAL_OTEL=1` alone enables **nothing** — you must also select at
|
||||
@@ -56,7 +56,7 @@ without the master switch.
|
||||
| `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` | `delta` | `delta` \| `cumulative`. |
|
||||
| `OTEL_METRICS_INCLUDE_SESSION_ID` | `1` | Attach `session.id` to metrics (cardinality opt-out). |
|
||||
| `OTEL_METRICS_INCLUDE_VERSION` | `0` | Attach `app.version` to metrics. |
|
||||
| `OTEL_LOG_USER_PROMPTS` | `0` | Content gate: prompt text on `grok_code.user_prompt` (60 KB cap, secret-scrubbed). |
|
||||
| `OTEL_LOG_USER_PROMPTS` | `0` | Content gate: prompt text on `kigi_code.user_prompt` (60 KB cap, secret-scrubbed). |
|
||||
| `OTEL_LOG_TOOL_DETAILS` | `0` | Content gate: tool parameters (4 KB cap), full file paths, verbatim MCP/skill/plugin names. Bash command text is **never** exported in v1, even with this gate. |
|
||||
|
||||
`OTEL_RESOURCE_ATTRIBUTES` is deliberately ignored: the resource is built
|
||||
@@ -95,7 +95,7 @@ There is deliberately no `headers` key: supply collector auth via
|
||||
`OTEL_EXPORTER_OTLP_HEADERS` so tokens are never stored on disk.
|
||||
|
||||
Managed deployments can additionally enable org-wide telemetry by distributing
|
||||
the `[telemetry]` `otel_*` keys through `grok setup` managed config /
|
||||
the `[telemetry]` `otel_*` keys through `kigi setup` managed config /
|
||||
requirements pins, or force-disable it fleet-wide with the same local config
|
||||
layers (`external_otel_disabled`, content-gate locks).
|
||||
|
||||
@@ -103,29 +103,29 @@ layers (`external_otel_disabled`, content-gate locks).
|
||||
|
||||
| Attribute | Value |
|
||||
|---|---|
|
||||
| `service.name` | `grok-cli` |
|
||||
| `service.name` | `kigi-cli` |
|
||||
| `service.version`, `client.version` | build/client versions |
|
||||
| `app.entrypoint` | `cli` \| `headless` \| `agent` |
|
||||
| `terminal.type` | terminal emulator brand |
|
||||
| `grok_code.schema.version` | `v1` |
|
||||
| `kigi_code.schema.version` | `v1` |
|
||||
|
||||
Identity attributes (`user.id`, and `organization.id` / `team.id` /
|
||||
`deployment.id` when known) are attached per metric data point and per event
|
||||
once authentication completes. `prompt.id` (per-prompt UUID) appears on
|
||||
events only, never metrics.
|
||||
|
||||
## Metrics (meter scope `ai.xai.grok_code`)
|
||||
## Metrics (meter scope `ai.xai.kigi_code`)
|
||||
|
||||
| Metric | Unit | Attributes |
|
||||
|---|---|---|
|
||||
| `grok_code.session.count` | `{session}` | base attrs only |
|
||||
| `grok_code.token.usage` | `{token}` | `type` = `input` \| `output` \| `reasoning` \| `cache_read`; `model` |
|
||||
| `grok_code.turn.count` | `{turn}` | `outcome` = `completed` \| `cancelled` \| `error`; `model` |
|
||||
| `grok_code.tool.decision` | `{decision}` | `tool_name`, `decision` = `allow` \| `deny` \| `cancelled` \| `followup`, `access_kind`, `permission_mode` |
|
||||
| `grok_code.tool.usage` | `{call}` | `tool_name`, `outcome` |
|
||||
| `grok_code.error.count` | `{error}` | `error_category`, `model` |
|
||||
| `kigi_code.session.count` | `{session}` | base attrs only |
|
||||
| `kigi_code.token.usage` | `{token}` | `type` = `input` \| `output` \| `reasoning` \| `cache_read`; `model` |
|
||||
| `kigi_code.turn.count` | `{turn}` | `outcome` = `completed` \| `cancelled` \| `error`; `model` |
|
||||
| `kigi_code.tool.decision` | `{decision}` | `tool_name`, `decision` = `allow` \| `deny` \| `cancelled` \| `followup`, `access_kind`, `permission_mode` |
|
||||
| `kigi_code.tool.usage` | `{call}` | `tool_name`, `outcome` |
|
||||
| `kigi_code.error.count` | `{error}` | `error_category`, `model` |
|
||||
|
||||
There is no `cost.usage` metric: join `grok_code.token.usage` with your own
|
||||
There is no `cost.usage` metric: join `kigi_code.token.usage` with your own
|
||||
price sheet. `lines_of_code.count` and `active_time.total` are planned for a
|
||||
later phase.
|
||||
|
||||
@@ -143,23 +143,23 @@ active.
|
||||
|
||||
| `event.name` | Attributes |
|
||||
|---|---|
|
||||
| `grok_code.session_start` | `model`, `permission_mode`, `mcp_server_count`, `plugin_count`, `skill_count`, `hook_count`, `memory_enabled`, `is_git_repo`, `client_identifier` |
|
||||
| `grok_code.session_end` | `duration_secs`, `turn_count`, `tool_call_count`, `compaction_count`, `model` |
|
||||
| `grok_code.user_prompt` | `prompt_length`, `model`, `screen_mode?` (`fullscreen` \| `inline` \| `minimal` \| `headless` \| `other`); `prompt` (**prompts**) |
|
||||
| `grok_code.turn_completed` | `outcome`, `duration_ms`, `tool_call_count`, `model`, `error_category?`, `cancellation_category?` |
|
||||
| `grok_code.api_request` | `model`, `duration_ms`, `stop_reason?`, `input_tokens`, `output_tokens`, `reasoning_tokens`, `cache_read_tokens` |
|
||||
| `grok_code.api_error` | `error_category`, `model`, `status_code?`, `duration_ms?` |
|
||||
| `grok_code.tool_result` | `tool_name`, `outcome`, `success`, `duration_ms`, `file_extension`; `tool_parameters`, `file_path` (**details**) |
|
||||
| `grok_code.tool_decision` | `tool_name`, `decision`, `access_kind`, `permission_mode`, `source` |
|
||||
| `grok_code.mcp_server_connection` | `status`, `transport_type`, `duration_ms`, `tool_count?`, `error_type?`; `mcp_server.name` (**details**; collapsed to `mcp_server` otherwise) |
|
||||
| `grok_code.permission_mode_changed` | `to_mode`, `trigger` |
|
||||
| `grok_code.skill_activated` | `skill_source`; `skill.name` (**details**) |
|
||||
| `grok_code.plugin_loaded` | `install_kind?`, `success`, `error_category?`; `plugin_name` (**details**) |
|
||||
| `grok_code.compaction` | `duration_ms`, `tokens_before`, `tokens_after`, `model?` |
|
||||
| `grok_code.subagent` | `phase` = `launched` \| `completed`, `subagent_type?`, `outcome?`, `duration_ms?` |
|
||||
| `grok_code.auth` | `auth_method` |
|
||||
| `grok_code.internal_error` | `error_type` (class only — no message, no location) |
|
||||
| `grok_code.model_switched` | `from_model`, `to_model`, `success`, `error_code?` |
|
||||
| `kigi_code.session_start` | `model`, `permission_mode`, `mcp_server_count`, `plugin_count`, `skill_count`, `hook_count`, `memory_enabled`, `is_git_repo`, `client_identifier` |
|
||||
| `kigi_code.session_end` | `duration_secs`, `turn_count`, `tool_call_count`, `compaction_count`, `model` |
|
||||
| `kigi_code.user_prompt` | `prompt_length`, `model`, `screen_mode?` (`fullscreen` \| `inline` \| `minimal` \| `headless` \| `other`); `prompt` (**prompts**) |
|
||||
| `kigi_code.turn_completed` | `outcome`, `duration_ms`, `tool_call_count`, `model`, `error_category?`, `cancellation_category?` |
|
||||
| `kigi_code.api_request` | `model`, `duration_ms`, `stop_reason?`, `input_tokens`, `output_tokens`, `reasoning_tokens`, `cache_read_tokens` |
|
||||
| `kigi_code.api_error` | `error_category`, `model`, `status_code?`, `duration_ms?` |
|
||||
| `kigi_code.tool_result` | `tool_name`, `outcome`, `success`, `duration_ms`, `file_extension`; `tool_parameters`, `file_path` (**details**) |
|
||||
| `kigi_code.tool_decision` | `tool_name`, `decision`, `access_kind`, `permission_mode`, `source` |
|
||||
| `kigi_code.mcp_server_connection` | `status`, `transport_type`, `duration_ms`, `tool_count?`, `error_type?`; `mcp_server.name` (**details**; collapsed to `mcp_server` otherwise) |
|
||||
| `kigi_code.permission_mode_changed` | `to_mode`, `trigger` |
|
||||
| `kigi_code.skill_activated` | `skill_source`; `skill.name` (**details**) |
|
||||
| `kigi_code.plugin_loaded` | `install_kind?`, `success`, `error_category?`; `plugin_name` (**details**) |
|
||||
| `kigi_code.compaction` | `duration_ms`, `tokens_before`, `tokens_after`, `model?` |
|
||||
| `kigi_code.subagent` | `phase` = `launched` \| `completed`, `subagent_type?`, `outcome?`, `duration_ms?` |
|
||||
| `kigi_code.auth` | `auth_method` |
|
||||
| `kigi_code.internal_error` | `error_type` (class only — no message, no location) |
|
||||
| `kigi_code.model_switched` | `from_model`, `to_model`, `success`, `error_code?` |
|
||||
|
||||
## Privacy model
|
||||
|
||||
@@ -213,14 +213,14 @@ Example queries (PromQL, with the Prometheus exporter above):
|
||||
|
||||
```promql
|
||||
# Tokens by model and type across the org, 1h rate
|
||||
sum by (model, type) (rate(grok_code_token_usage_total[1h]))
|
||||
sum by (model, type) (rate(kigi_code_token_usage_total[1h]))
|
||||
|
||||
# Sessions per team per day
|
||||
sum by (team_id) (increase(grok_code_session_count_total[1d]))
|
||||
sum by (team_id) (increase(kigi_code_session_count_total[1d]))
|
||||
|
||||
# Tool-permission denial ratio
|
||||
sum(rate(grok_code_tool_decision_total{decision="deny"}[1h]))
|
||||
/ sum(rate(grok_code_tool_decision_total[1h]))
|
||||
sum(rate(kigi_code_tool_decision_total{decision="deny"}[1h]))
|
||||
/ sum(rate(kigi_code_tool_decision_total[1h]))
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Grok Build User Guide
|
||||
# Kigi User Guide
|
||||
|
||||
Learn how to install, configure, and extend Grok Build, the terminal-based AI coding assistant from SpaceXAI.
|
||||
Learn how to install, configure, and extend Kigi, the terminal-based AI coding assistant from SpaceXAI.
|
||||
|
||||
---
|
||||
|
||||
@@ -20,7 +20,7 @@ Start here. These guides cover what you need on your first day.
|
||||
|
||||
## Tier 2: Core Feature Docs
|
||||
|
||||
Customize and extend Grok Build.
|
||||
Customize and extend Kigi.
|
||||
|
||||
| # | Document | Description |
|
||||
|---|----------|-------------|
|
||||
@@ -37,11 +37,11 @@ Customize and extend Grok Build.
|
||||
|
||||
## Tier 3: Advanced Usage Docs
|
||||
|
||||
Automate, script, and integrate Grok Build with other systems.
|
||||
Automate, script, and integrate Kigi with other systems.
|
||||
|
||||
| # | Document | Description |
|
||||
|---|----------|-------------|
|
||||
| 14 | [Headless Mode and Scripting](14-headless-mode.md) | `grok -p`, output formats, CI/CD integration, and piping |
|
||||
| 14 | [Headless Mode and Scripting](14-headless-mode.md) | `kigi -p`, output formats, CI/CD integration, and piping |
|
||||
| 15 | [Agent Mode and IDE Integration](15-agent-mode.md) | ACP stdio transport, WebSocket relay, and SDK integration |
|
||||
| 16 | [Subagents and Personas](16-subagents.md) | Parallel child sessions, agent types, personas, and capability modes |
|
||||
| 17 | [Session Management](17-sessions.md) | Save, load, resume, rewind, compact, and the session persistence format |
|
||||
|
||||
@@ -49,7 +49,7 @@ fn create_string_info(id: usize) -> SubagentInfoString {
|
||||
subagent_type: "general-purpose".to_string(),
|
||||
persona: Some("researcher".to_string()),
|
||||
role: Some("analyst".to_string()),
|
||||
model: Some("grok-3".to_string()),
|
||||
model: Some("kigi-3".to_string()),
|
||||
status: Some("completed".to_string()),
|
||||
tools_used: vec!["read".to_string(), "search".to_string(), "edit".to_string()],
|
||||
}
|
||||
@@ -63,7 +63,7 @@ fn create_arc_info(id: usize) -> SubagentInfoArc {
|
||||
subagent_type: Arc::from("general-purpose"),
|
||||
persona: Some(Arc::from("researcher")),
|
||||
role: Some(Arc::from("analyst")),
|
||||
model: Some(Arc::from("grok-3")),
|
||||
model: Some(Arc::from("kigi-3")),
|
||||
status: Some(Arc::from("completed")),
|
||||
tools_used: vec![Arc::from("read"), Arc::from("search"), Arc::from("edit")],
|
||||
}
|
||||
@@ -173,7 +173,7 @@ fn main() {
|
||||
println!("--- Realistic Scenario (100 subagents with varied data) ---");
|
||||
let subagent_types = ["general-purpose", "explore", "plan", "implementer"];
|
||||
let personas = ["researcher", "analyst", "reviewer", "implementer"];
|
||||
let models = ["grok-3", "grok-3-mini", "grok-4"];
|
||||
let models = ["kigi-3", "kigi-3-mini", "kigi-4"];
|
||||
let statuses = ["completed", "failed", "running", "cancelled"];
|
||||
let tools = ["read", "edit", "search", "execute", "list_dir"];
|
||||
|
||||
|
||||
@@ -87,8 +87,8 @@ fn main() {
|
||||
println!("--- Clone Performance (1,000,000 clones) ---");
|
||||
let iterations = 1_000_000;
|
||||
|
||||
let string_info = create_string_info(1, "general-purpose", "grok-3", "researcher");
|
||||
let arc_info = create_arc_info(1, "general-purpose", "grok-3", "researcher");
|
||||
let string_info = create_string_info(1, "general-purpose", "kigi-3", "researcher");
|
||||
let arc_info = create_arc_info(1, "general-purpose", "kigi-3", "researcher");
|
||||
|
||||
let start = Instant::now();
|
||||
for _ in 0..iterations {
|
||||
@@ -115,7 +115,7 @@ fn main() {
|
||||
println!("Scenario: 1000 subagents sharing subagent_type, model, persona, status");
|
||||
|
||||
let shared_types = ["general-purpose", "explore", "plan"];
|
||||
let shared_models = ["grok-3", "grok-3-mini"];
|
||||
let shared_models = ["kigi-3", "kigi-3-mini"];
|
||||
let shared_personas = ["researcher", "analyst", "reviewer"];
|
||||
|
||||
// Create string-based infos
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Parses the `_meta` JSON from `SessionNotification` into a struct with
|
||||
//! typed fields. All fields are `Option` — gracefully degrades when
|
||||
//! grok-shell hasn't been updated or meta is absent.
|
||||
//! kigi-shell hasn't been updated or meta is absent.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -154,7 +154,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_missing_new_fields() {
|
||||
// Simulate old grok-shell that doesn't send streamStartMs/turnStartMs
|
||||
// Simulate old kigi-shell that doesn't send streamStartMs/turnStartMs
|
||||
let meta_json = json!({
|
||||
"totalTokens": 1000u64,
|
||||
"agentTimestampMs": 1700000000000i64,
|
||||
|
||||
@@ -54,8 +54,8 @@ pub struct AcpConnection {
|
||||
pub rx: AcpClientRx,
|
||||
/// Available models and current selection.
|
||||
pub models: ModelState,
|
||||
/// Whether the agent is a grok-shell instance.
|
||||
pub is_grok_shell: bool,
|
||||
/// Whether the agent is a kigi-shell instance.
|
||||
pub is_kigi_shell: bool,
|
||||
/// Auth methods advertised by the agent.
|
||||
pub auth_methods: Vec<acp::AuthMethod>,
|
||||
/// Cancellation token to stop the agent.
|
||||
@@ -64,9 +64,9 @@ pub struct AcpConnection {
|
||||
/// Seeded into every new `AgentSession` so autocomplete has shell builtins
|
||||
/// and skills immediately, before any `AvailableCommandsUpdate` arrives.
|
||||
pub available_commands: Vec<acp::AvailableCommand>,
|
||||
/// Whether interactive login is required (deferred auth for `grok.com`).
|
||||
/// Whether interactive login is required (deferred auth for `kimi-code`).
|
||||
pub needs_login: bool,
|
||||
/// Login button label from `AuthMethod.name` (e.g., "grok.com", "Acme Corp").
|
||||
/// Login button label from `AuthMethod.name` (e.g., "kimi-code", "Acme Corp").
|
||||
pub login_label: Option<String>,
|
||||
/// The auth method ID to use for login (copied from the first advertised method).
|
||||
pub login_method_id: Option<acp::AuthMethodId>,
|
||||
@@ -83,7 +83,7 @@ pub struct AcpConnection {
|
||||
/// resolved by the shell (remote settings / config / env; default OFF) and
|
||||
/// advertised in `InitializeResponse.meta.sessionRecap`. The client gates
|
||||
/// its automatic away-recap poll and the manual `/recap` on this so a
|
||||
/// disabled feature produces zero `x.ai/recap` traffic. Defaults to `false`
|
||||
/// disabled feature produces zero `kigi/recap` traffic. Defaults to `false`
|
||||
/// when absent (e.g. an older shell that predates the feature).
|
||||
pub session_recap_available: bool,
|
||||
/// `AuthManager` for pager-side authenticated channels.
|
||||
@@ -183,14 +183,14 @@ pub async fn connect(cancel: &CancellationToken, flags: ConnectFlags) -> Result<
|
||||
|
||||
// Spawn the agent
|
||||
let memory_config = agent_config.memory_config.clone();
|
||||
let spawned = spawn::spawn_grok_shell(agent_config, cancel, memory_config).await?;
|
||||
let spawned = spawn::spawn_kigi_shell(agent_config, cancel, memory_config).await?;
|
||||
let auth_manager = spawned.auth_manager.clone();
|
||||
let (tx, rx) = (spawned.channel.tx, spawned.channel.rx);
|
||||
|
||||
// Initialize
|
||||
let (
|
||||
models,
|
||||
is_grok_shell,
|
||||
is_kigi_shell,
|
||||
auth_methods,
|
||||
default_auth_method_id,
|
||||
available_commands,
|
||||
@@ -218,7 +218,7 @@ pub async fn connect(cancel: &CancellationToken, flags: ConnectFlags) -> Result<
|
||||
tx,
|
||||
rx,
|
||||
models,
|
||||
is_grok_shell,
|
||||
is_kigi_shell,
|
||||
auth_methods,
|
||||
cancel: spawned.cancel,
|
||||
available_commands,
|
||||
@@ -291,7 +291,7 @@ pub async fn connect_via_leader(
|
||||
|
||||
let (
|
||||
models,
|
||||
is_grok_shell,
|
||||
is_kigi_shell,
|
||||
auth_methods,
|
||||
default_auth_method_id,
|
||||
available_commands,
|
||||
@@ -329,7 +329,7 @@ pub async fn connect_via_leader(
|
||||
tx,
|
||||
rx,
|
||||
models,
|
||||
is_grok_shell,
|
||||
is_kigi_shell,
|
||||
auth_methods,
|
||||
cancel: bridge.cancel,
|
||||
available_commands,
|
||||
@@ -439,10 +439,10 @@ fn client_capabilities_meta(flags: &ConnectFlags) -> serde_json::Value {
|
||||
let hunk_mode =
|
||||
crate::settings::canonical_hunk_tracker_mode(flags.hunk_tracker_mode.as_deref());
|
||||
serde_json::json!({
|
||||
"x.ai/incrementalBashOutput": true,
|
||||
"x.ai/hunkTracker": { "mode": hunk_mode },
|
||||
"x.ai/bashOutputNoColor": true,
|
||||
"x.ai/gitHeadChanged": true,
|
||||
"kigi/incrementalBashOutput": true,
|
||||
"kigi/hunkTracker": { "mode": hunk_mode },
|
||||
"kigi/bashOutputNoColor": true,
|
||||
"kigi/gitHeadChanged": true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -482,11 +482,11 @@ async fn initialize(
|
||||
|
||||
let resp: acp::InitializeResponse = acp_send(req, tx).await?;
|
||||
|
||||
// Check if this is a grok-shell agent
|
||||
let is_grok_shell = resp
|
||||
// Check if this is a kigi-shell agent
|
||||
let is_kigi_shell = resp
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("grokShell"))
|
||||
.and_then(|m| m.get("kigiShell"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -514,7 +514,7 @@ async fn initialize(
|
||||
|
||||
Ok((
|
||||
models,
|
||||
is_grok_shell,
|
||||
is_kigi_shell,
|
||||
resp.auth_methods,
|
||||
default_auth_method_id,
|
||||
available_commands,
|
||||
@@ -545,7 +545,7 @@ pub fn parse_session_recap_available(meta: Option<&acp::Meta>) -> bool {
|
||||
|
||||
/// Determine whether interactive login is needed based on the advertised auth methods.
|
||||
///
|
||||
/// Matches TUI startup behavior: if the first method is `grok.com`, defer auth
|
||||
/// Matches TUI startup behavior: if the first method is `kimi-code`, defer auth
|
||||
/// and show the login-aware welcome flow. Otherwise, authenticate eagerly.
|
||||
///
|
||||
/// Returns `(needs_login, login_label, login_method_id, auth_start_mode)`.
|
||||
@@ -589,7 +589,7 @@ pub fn startup_auth_metadata(
|
||||
///
|
||||
/// Used when eager auth (cached_token / API key) fails and we need to fall
|
||||
/// back to the welcome screen with a working login button. Scans the list
|
||||
/// for a `grok.com` or `oidc` method — these are the ones that can trigger
|
||||
/// for a `kimi-code` or `oidc` method — these are the ones that can trigger
|
||||
/// a browser-based re-auth flow.
|
||||
pub fn find_interactive_login_method(
|
||||
auth_methods: &[acp::AuthMethod],
|
||||
@@ -767,7 +767,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_available_commands_missing_key_returns_empty() {
|
||||
let meta = serde_json::json!({ "grokShell": true });
|
||||
let meta = serde_json::json!({ "kigiShell": true });
|
||||
let cmds = parse_available_commands(meta.as_object());
|
||||
assert!(cmds.is_empty());
|
||||
}
|
||||
@@ -801,7 +801,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_session_recap_available_defaults_off_when_missing() {
|
||||
let meta = serde_json::json!({ "grokShell": true, "cancelRewind": true });
|
||||
let meta = serde_json::json!({ "kigiShell": true, "cancelRewind": true });
|
||||
assert!(!parse_session_recap_available(meta.as_object()));
|
||||
assert!(!parse_session_recap_available(None));
|
||||
}
|
||||
@@ -832,28 +832,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_auth_grok_com_no_provider_needs_login_pending() {
|
||||
let methods = vec![make_auth_method("grok.com", "grok.com", None)];
|
||||
fn startup_auth_kigi_com_no_provider_needs_login_pending() {
|
||||
let methods = vec![make_auth_method("kimi-code", "kimi-code", None)];
|
||||
let (needs, label, method_id, mode) = startup_auth_metadata(&methods);
|
||||
assert!(needs);
|
||||
assert_eq!(label.as_deref(), Some("grok.com"));
|
||||
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "grok.com");
|
||||
assert_eq!(label.as_deref(), Some("kimi-code"));
|
||||
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "kimi-code");
|
||||
assert_eq!(mode, AuthStartMode::Pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_auth_grok_com_with_external_provider_command() {
|
||||
fn startup_auth_kigi_com_with_external_provider_command() {
|
||||
let meta = serde_json::json!({ "external_provider": true });
|
||||
let methods = vec![make_auth_method("grok.com", "Acme Corp", Some(meta))];
|
||||
let methods = vec![make_auth_method("kimi-code", "Acme Corp", Some(meta))];
|
||||
let (needs, label, method_id, mode) = startup_auth_metadata(&methods);
|
||||
assert!(needs);
|
||||
assert_eq!(label.as_deref(), Some("Acme Corp"));
|
||||
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "grok.com");
|
||||
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "kimi-code");
|
||||
assert_eq!(mode, AuthStartMode::Command);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_auth_non_grok_com_no_login() {
|
||||
fn startup_auth_non_kigi_com_no_login() {
|
||||
let methods = vec![make_auth_method("api-key", "API Key", None)];
|
||||
let (needs, label, method_id, mode) = startup_auth_metadata(&methods);
|
||||
assert!(!needs);
|
||||
@@ -890,7 +890,7 @@ mod tests {
|
||||
// enterprise-style: model has `env_key` set and the env var resolves,
|
||||
// so the shell-side predicate returns true.
|
||||
has_external_api_key: true,
|
||||
// Realistic enterprise user: no cached session token, default `grok.com`
|
||||
// Realistic enterprise user: no cached session token, default `kimi-code`
|
||||
// login (no enterprise OIDC).
|
||||
has_cached_token: false,
|
||||
login_label: None,
|
||||
@@ -916,27 +916,27 @@ mod tests {
|
||||
/// `auth_methods.first()`. This locks the failure mode of the regression:
|
||||
/// if a future refactor makes the pager scan past `.first()`, this test
|
||||
/// stops being equivalent to
|
||||
/// `startup_auth_grok_com_no_provider_needs_login_pending` above and
|
||||
/// `startup_auth_kigi_com_no_provider_needs_login_pending` above and
|
||||
/// either passes or fails on a meaningful new code path.
|
||||
#[test]
|
||||
fn startup_auth_xai_api_key_not_first_still_requires_login() {
|
||||
use kigi_shell::agent::auth_method::{KIGI_COM_METHOD_ID, XAI_API_KEY_METHOD_ID};
|
||||
use kigi_shell::agent::auth_method::{KIMI_CODE_METHOD_ID, XAI_API_KEY_METHOD_ID};
|
||||
|
||||
let methods = vec![
|
||||
make_auth_method(KIGI_COM_METHOD_ID, "Grok", None),
|
||||
make_auth_method(KIMI_CODE_METHOD_ID, "Kigi", None),
|
||||
make_auth_method(XAI_API_KEY_METHOD_ID, "xai.api_key", None),
|
||||
];
|
||||
let (needs, _, _, _) = startup_auth_metadata(&methods);
|
||||
assert!(
|
||||
needs,
|
||||
"with grok.com first, the pager must require login -- pinning \
|
||||
"with kimi.com first, the pager must require login -- pinning \
|
||||
the BAD-ordering failure mode (xai.api_key not first)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_auth_method_id_is_copied_not_synthesized() {
|
||||
let methods = vec![make_auth_method("grok.com", "My Login", None)];
|
||||
let methods = vec![make_auth_method("kimi-code", "My Login", None)];
|
||||
let (_, _, method_id, _) = startup_auth_metadata(&methods);
|
||||
// Verify it's the exact same ID from the method, not hardcoded
|
||||
assert_eq!(&method_id.unwrap(), methods[0].id());
|
||||
@@ -945,7 +945,7 @@ mod tests {
|
||||
#[test]
|
||||
fn startup_auth_external_provider_false_is_pending() {
|
||||
let meta = serde_json::json!({ "external_provider": false });
|
||||
let methods = vec![make_auth_method("grok.com", "grok.com", Some(meta))];
|
||||
let methods = vec![make_auth_method("kimi-code", "kimi-code", Some(meta))];
|
||||
let (_, _, _, mode) = startup_auth_metadata(&methods);
|
||||
assert_eq!(mode, AuthStartMode::Pending);
|
||||
}
|
||||
@@ -1033,12 +1033,12 @@ mod tests {
|
||||
// Rows 1 & 2 of the truth table: nothing set, and a set-but-blank value,
|
||||
// both advertise the `agent_only` default (never `""` → AllDirty).
|
||||
let absent = client_capabilities_meta(&ConnectFlags::default());
|
||||
assert_eq!(absent["x.ai/hunkTracker"]["mode"], "agent_only");
|
||||
assert_eq!(absent["kigi/hunkTracker"]["mode"], "agent_only");
|
||||
let blank = client_capabilities_meta(&ConnectFlags {
|
||||
hunk_tracker_mode: Some(" ".into()),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(blank["x.ai/hunkTracker"]["mode"], "agent_only");
|
||||
assert_eq!(blank["kigi/hunkTracker"]["mode"], "agent_only");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1050,7 +1050,7 @@ mod tests {
|
||||
hunk_tracker_mode: Some(raw.into()),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(meta["x.ai/hunkTracker"]["mode"], "off", "raw={raw}");
|
||||
assert_eq!(meta["kigi/hunkTracker"]["mode"], "off", "raw={raw}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ impl ModelState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Machine-readable model ID string for the current model (e.g. "grok-4.5").
|
||||
/// Machine-readable model ID string for the current model (e.g. "kigi-4.5").
|
||||
pub fn current_model_id_str(&self) -> Option<&str> {
|
||||
Some(self.current.as_ref()?.0.as_ref())
|
||||
}
|
||||
@@ -94,7 +94,7 @@ impl ModelState {
|
||||
///
|
||||
/// Honors an explicit `acceptsImages` bool, else an `inputModalities` array
|
||||
/// containing `"image"`. DEFAULTS TO `true` when neither key is present:
|
||||
/// correct today (all current Grok models accept images, so nothing is
|
||||
/// correct today (all current Kigi models accept images, so nothing is
|
||||
/// suppressed) and forward-compatible (suppresses non-vision models once the
|
||||
/// ACP server populates the key). Populating that key server-side is a
|
||||
/// separate change.
|
||||
@@ -223,7 +223,7 @@ impl ModelState {
|
||||
/// Map a typed/selected effort token to its canonical value for the current
|
||||
/// model. Accepts a menu option id (case-insensitive) or a canonical level
|
||||
/// that appears as a **value** in that model's menu. Levels the model does
|
||||
/// not offer (e.g. `none` on grok-4.5) are rejected so we fail in the TUI
|
||||
/// not offer (e.g. `none` on kigi-4.5) are rejected so we fail in the TUI
|
||||
/// instead of sending a blocked effort to the API.
|
||||
pub fn resolve_effort_token(&self, token: &str) -> Option<ReasoningEffort> {
|
||||
match self.current.as_ref() {
|
||||
@@ -249,7 +249,7 @@ impl ModelState {
|
||||
}
|
||||
// Canonical level (e.g. "high", "max"→xhigh) only if the model menu
|
||||
// actually offers that value — not free-form power-user aliases that
|
||||
// would 400 on the server (e.g. `none` on grok-4.5).
|
||||
// would 400 on the server (e.g. `none` on kigi-4.5).
|
||||
let parsed = token.parse::<ReasoningEffort>().ok()?;
|
||||
options
|
||||
.iter()
|
||||
@@ -410,21 +410,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn update_catalog_preserves_user_effort_when_model_unchanged() {
|
||||
let id = acp::ModelId::new(Arc::from("grok-build"));
|
||||
let id = acp::ModelId::new(Arc::from("kigi"));
|
||||
let mut state = ModelState::default();
|
||||
state.available.insert(
|
||||
id.clone(),
|
||||
model_with_effort("grok-build", "Grok Build", "high"),
|
||||
);
|
||||
state
|
||||
.available
|
||||
.insert(id.clone(), model_with_effort("kigi", "Kigi", "high"));
|
||||
state.set_current(id.clone(), Some(ReasoningEffort::Xhigh));
|
||||
assert_eq!(state.reasoning_effort, Some(ReasoningEffort::Xhigh));
|
||||
|
||||
// The broadcast carries the model's static default (high) for the same model.
|
||||
let mut refreshed = IndexMap::new();
|
||||
refreshed.insert(
|
||||
id.clone(),
|
||||
model_with_effort("grok-build", "Grok Build", "high"),
|
||||
);
|
||||
refreshed.insert(id.clone(), model_with_effort("kigi", "Kigi", "high"));
|
||||
state.update_catalog(refreshed, Some(id.clone()));
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Agent spawning — creates the agent process and ACP channels.
|
||||
//!
|
||||
//! Simplified to only support GrokShell (in-process) mode.
|
||||
//! Simplified to only support KigiShell (in-process) mode.
|
||||
//! Subprocess and remote modes can be added later if needed.
|
||||
|
||||
use std::rc::Rc;
|
||||
@@ -30,10 +30,10 @@ pub struct SpawnedAgent {
|
||||
pub auth_manager: std::sync::Arc<AuthManager>,
|
||||
}
|
||||
|
||||
/// Spawn a GrokShell agent in a background thread.
|
||||
/// Spawn a KigiShell agent in a background thread.
|
||||
///
|
||||
/// Returns the ACP client channel for communication and a cancellation token.
|
||||
pub async fn spawn_grok_shell(
|
||||
pub async fn spawn_kigi_shell(
|
||||
agent_config: AgentConfig,
|
||||
cancel: &CancellationToken,
|
||||
memory_config: Option<kigi_shell::config::MemoryConfig>,
|
||||
|
||||
@@ -255,7 +255,7 @@ pub struct AcpUpdateTracker {
|
||||
/// Tool call IDs marked as background (`is_background=true`).
|
||||
///
|
||||
/// First-detection (no scrollback entry yet): defers entry creation until
|
||||
/// `x.ai/task_backgrounded` creates a `BgTask` block.
|
||||
/// `kigi/task_backgrounded` creates a `BgTask` block.
|
||||
/// Late-detection (Execute block already exists): suppresses further output
|
||||
/// streaming; the existing block is demoted by `handle_task_backgrounded`.
|
||||
///
|
||||
@@ -2129,7 +2129,7 @@ fn task_ids_from_raw_input(raw: &serde_json::Value) -> Vec<String> {
|
||||
}
|
||||
/// Check if a tool call is a background execute (`is_background=true`).
|
||||
///
|
||||
/// These are deferred from scrollback — the `x.ai/task_backgrounded`
|
||||
/// These are deferred from scrollback — the `kigi/task_backgrounded`
|
||||
/// notification creates a `BgTask` block instead of an `Execute` block.
|
||||
///
|
||||
/// Eager ACP messages often use `kind=Other` with `title=run_terminal_command`
|
||||
@@ -2602,7 +2602,7 @@ fn make_relative_path(path: &str) -> String {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
/// Default meta with no timestamps (simulates old grok-shell or tests that
|
||||
/// Default meta with no timestamps (simulates old kigi-shell or tests that
|
||||
/// don't care about timing).
|
||||
fn meta() -> NotificationMeta {
|
||||
NotificationMeta::default()
|
||||
@@ -4602,7 +4602,7 @@ mod tests {
|
||||
"stream A message should be finished"
|
||||
);
|
||||
}
|
||||
/// No stream_start_ms (old grok-shell) should not break anything.
|
||||
/// No stream_start_ms (old kigi-shell) should not break anything.
|
||||
#[test]
|
||||
fn no_stream_start_ms_preserves_existing_behavior() {
|
||||
let mut sb = ScrollbackState::new();
|
||||
@@ -5554,7 +5554,7 @@ mod tests {
|
||||
.status(acp::ToolCallStatus::Pending)
|
||||
}
|
||||
#[test]
|
||||
fn is_task_tool_recognizes_grok_build_variant() {
|
||||
fn is_task_tool_recognizes_kigi_variant() {
|
||||
assert!(is_task_tool(&initial_tool_call("tc1", "task")));
|
||||
let mut with_variant = initial_tool_call("tc2", "anything");
|
||||
with_variant.raw_input = Some(serde_json::json!({ "variant" : "Task" }));
|
||||
|
||||
@@ -62,7 +62,7 @@ pub(super) fn route_bg_task_stdout(
|
||||
true // Consumed — don't pass to tracker
|
||||
}
|
||||
|
||||
/// Handle `x.ai/task_backgrounded` — a bash command transitioned to background.
|
||||
/// Handle `kigi/task_backgrounded` — a bash command transitioned to background.
|
||||
///
|
||||
/// Creates a `BgTaskState` in the central store and sets up the
|
||||
/// `tool_call_id → task_id` correlation for stdout routing.
|
||||
@@ -74,7 +74,7 @@ pub(super) fn route_bg_task_stdout(
|
||||
pub(super) fn handle_task_backgrounded(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
// Parse the SessionNotification envelope
|
||||
let Ok(session_notif) = serde_json::from_str::<SessionNotification>(notif.params.get()) else {
|
||||
tracing::warn!("Failed to parse x.ai/task_backgrounded");
|
||||
tracing::warn!("Failed to parse kigi/task_backgrounded");
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -241,7 +241,7 @@ pub(super) fn handle_task_backgrounded(notif: &acp::ExtNotification, app: &mut A
|
||||
is_active
|
||||
}
|
||||
|
||||
/// Handle `x.ai/monitor_event` — background task or monitor emitted new output.
|
||||
/// Handle `kigi/monitor_event` — background task or monitor emitted new output.
|
||||
pub(super) fn handle_monitor_event(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
let Ok(session_notif) = serde_json::from_str::<SessionNotification>(notif.params.get()) else {
|
||||
return false;
|
||||
@@ -414,16 +414,16 @@ pub(super) fn handle_scheduled_task_inject_prompt(
|
||||
let payload: serde_json::Value = match serde_json::from_str(notif.params.get()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "Failed to parse x.ai/scheduled_task_inject_prompt");
|
||||
tracing::warn!(error = %e, "Failed to parse kigi/scheduled_task_inject_prompt");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let Some(session_id) = payload["sessionId"].as_str() else {
|
||||
tracing::warn!("x.ai/scheduled_task_inject_prompt: missing or non-string sessionId");
|
||||
tracing::warn!("kigi/scheduled_task_inject_prompt: missing or non-string sessionId");
|
||||
return false;
|
||||
};
|
||||
let Some(prompt) = payload["prompt"].as_str().filter(|s| !s.is_empty()) else {
|
||||
tracing::warn!("x.ai/scheduled_task_inject_prompt: missing or empty prompt");
|
||||
tracing::warn!("kigi/scheduled_task_inject_prompt: missing or empty prompt");
|
||||
return false;
|
||||
};
|
||||
let task_id = payload["taskId"].as_str().unwrap_or("unknown");
|
||||
@@ -441,7 +441,7 @@ pub(super) fn handle_scheduled_task_inject_prompt(
|
||||
};
|
||||
|
||||
// Only the driver injects + runs the scheduled prompt. In leader mode the
|
||||
// `x.ai/scheduled_task_inject_prompt` notification is routed by the leader
|
||||
// `kigi/scheduled_task_inject_prompt` notification is routed by the leader
|
||||
// to the SINGLE session driver (see `is_scheduled_task_inject_prompt` in
|
||||
// leader/server.rs), so any client that receives it IS the driver and must
|
||||
// enqueue + run it — including a client that attached via `session/load`
|
||||
@@ -551,7 +551,7 @@ pub(super) fn handle_git_head_changed(notif: &acp::ExtNotification, app: &mut Ap
|
||||
pub(super) fn handle_task_completed(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
// The payload is a SessionNotification wrapping TaskCompleted { task_snapshot }
|
||||
let Ok(session_notif) = serde_json::from_str::<SessionNotification>(notif.params.get()) else {
|
||||
tracing::warn!("Failed to parse x.ai/task_completed");
|
||||
tracing::warn!("Failed to parse kigi/task_completed");
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ pub(super) const MAX_FOLLOW_UP_LABEL: usize = 256;
|
||||
/// retained `follow_up_seen` ring.
|
||||
pub(super) const MAX_RESPONSE_ID_LEN: usize = 128;
|
||||
|
||||
/// Deserialize shape of the `x.ai/follow_ups` params emitted by the shell
|
||||
/// Deserialize shape of the `kigi/follow_ups` params emitted by the shell
|
||||
/// translator: `{ response_id, suggestions: [{ label, .. }] }`. The keys are
|
||||
/// prost-derived snake_case — NOT camelCase like most other pager
|
||||
/// notification payloads — so this struct must match snake_case verbatim.
|
||||
@@ -32,13 +32,13 @@ pub(super) struct FollowUpsParams {
|
||||
prompt_id: Option<String>,
|
||||
/// Reserved replay marker carrier. Absent in v1 (the shell never sets
|
||||
/// it); honored from day one so future replay producers need no pager
|
||||
/// change. Parsed loosely as a JSON value to read the `"x.ai/replayed"`
|
||||
/// change. Parsed loosely as a JSON value to read the `"kigi/replayed"`
|
||||
/// key (a slash-bearing key prost cannot model as a field).
|
||||
#[serde(default, rename = "_meta")]
|
||||
meta: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// A single `x.ai/follow_ups` suggestion. Only the human-facing `label` is
|
||||
/// A single `kigi/follow_ups` suggestion. Only the human-facing `label` is
|
||||
/// consumed; `properties` / `tool_overrides` (also in the wire shape) are
|
||||
/// ignored.
|
||||
#[derive(serde::Deserialize)]
|
||||
@@ -60,11 +60,11 @@ pub(super) fn sanitize_suggestion(label: &str) -> String {
|
||||
cleaned.trim().to_owned()
|
||||
}
|
||||
|
||||
/// Handle `x.ai/follow_ups` — render follow-up suggestion chips for the
|
||||
/// Handle `kigi/follow_ups` — render follow-up suggestion chips for the
|
||||
/// latest assistant response.
|
||||
///
|
||||
/// Newest-response-wins keying lives in [`AgentView::apply_follow_ups`]. The
|
||||
/// reserved `_meta["x.ai/replayed"] == true` marker suppresses rendering (it
|
||||
/// reserved `_meta["kigi/replayed"] == true` marker suppresses rendering (it
|
||||
/// is absent today and treated as optional). The params carry no session id,
|
||||
/// so chips target the active agent; a background agent's follow-ups would
|
||||
/// mis-route — a forwarding obligation for the shell to add a session id.
|
||||
@@ -77,7 +77,7 @@ pub(super) fn handle_follow_ups(notif: &acp::ExtNotification, app: &mut AppView)
|
||||
if params
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/replayed"))
|
||||
.and_then(|m| m.get("kigi/replayed"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
|
||||
/// Handle `x.ai/ask_user_question` ext-method.
|
||||
/// Handle `kigi/ask_user_question` ext-method.
|
||||
///
|
||||
/// Parses the typed request, creates a `QuestionViewState` with the
|
||||
/// `response_tx` stashed, and opens the question overlay. The pager does
|
||||
@@ -14,7 +14,7 @@ pub(crate) fn handle_ask_user_question(
|
||||
app: &mut AppView,
|
||||
) -> bool {
|
||||
use crate::views::question_view::QuestionViewState;
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{
|
||||
AskUserQuestionExtRequest, AskUserQuestionExtResponse,
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ pub(crate) fn handle_ask_user_question(
|
||||
is_active
|
||||
}
|
||||
|
||||
/// Handle an `x.ai/exit_plan_mode` ext_method request.
|
||||
/// Handle an `kigi/exit_plan_mode` ext_method request.
|
||||
///
|
||||
/// Creates a `PlanApprovalViewState` overlay for interactive approval.
|
||||
///
|
||||
|
||||
@@ -46,7 +46,7 @@ pub(super) fn handle_mcp_init_progress(notif: &acp::ExtNotification, app: &mut A
|
||||
is_active
|
||||
}
|
||||
|
||||
/// Handle `x.ai/mcp/tools_changed` and `x.ai/mcp_initialized`.
|
||||
/// Handle `kigi/mcp/tools_changed` and `kigi/mcp_initialized`.
|
||||
///
|
||||
/// Routing rules (verified against the four shell emit sites in
|
||||
/// `kigi-shell/src/session/acp_session.rs` — toggle-tool ~L6661,
|
||||
@@ -78,8 +78,8 @@ pub(super) fn handle_mcp_init_progress(notif: &acp::ExtNotification, app: &mut A
|
||||
pub(super) fn handle_mcp_tools_changed(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
let method = notif.method.as_ref();
|
||||
|
||||
// Both `x.ai/mcp_initialized` and (newer shell)
|
||||
// `x.ai/mcp/tools_changed` carry `sessionId`. Route by it so a
|
||||
// Both `kigi/mcp_initialized` and (newer shell)
|
||||
// `kigi/mcp/tools_changed` carry `sessionId`. Route by it so a
|
||||
// background agent's notification updates *its* state — not
|
||||
// whichever agent is foregrounded. Unknown and subagent (child)
|
||||
// sessions are dropped; a missing sessionId falls back to the
|
||||
@@ -119,7 +119,7 @@ pub(super) fn handle_mcp_tools_changed(notif: &acp::ExtNotification, app: &mut A
|
||||
let mut redraw = false;
|
||||
|
||||
// `mcp_initialized` clears the matched agent's connecting indicator.
|
||||
if method == "x.ai/mcp_initialized"
|
||||
if method == "kigi/mcp_initialized"
|
||||
&& let Some(agent) = app.agents.get_mut(&id)
|
||||
&& agent.mcp_init_progress.take().is_some()
|
||||
{
|
||||
@@ -163,7 +163,7 @@ pub(super) fn agent_has_pending_mcps_fetch(app: &AppView, agent_id: AgentId) ->
|
||||
})
|
||||
}
|
||||
|
||||
/// Handle `x.ai/mcp/server_status`.
|
||||
/// Handle `kigi/mcp/server_status`.
|
||||
///
|
||||
/// Routes by the notification's `sessionId` via
|
||||
/// [`find_session_match`] — the matched agent's extensions modal is
|
||||
@@ -206,7 +206,7 @@ pub(super) fn handle_mcp_server_status(notif: &acp::ExtNotification, app: &mut A
|
||||
|
||||
let Ok(payload) = serde_json::from_str::<McpServerStatusPayload>(notif.params.get()) else {
|
||||
tracing::warn!(
|
||||
"Failed to parse x.ai/mcp/server_status: {}",
|
||||
"Failed to parse kigi/mcp/server_status: {}",
|
||||
¬if.params.get()
|
||||
[..crate::render::line_utils::floor_char_boundary(notif.params.get(), 100)]
|
||||
);
|
||||
@@ -263,7 +263,7 @@ pub(super) fn handle_mcp_server_status(notif: &acp::ExtNotification, app: &mut A
|
||||
tracing::warn!(
|
||||
server = %payload.name,
|
||||
error = %e,
|
||||
"x.ai/mcp/server_status: tools field present but not Vec<McpToolEntry>; status still applied"
|
||||
"kigi/mcp/server_status: tools field present but not Vec<McpToolEntry>; status still applied"
|
||||
);
|
||||
None
|
||||
}
|
||||
@@ -273,7 +273,7 @@ pub(super) fn handle_mcp_server_status(notif: &acp::ExtNotification, app: &mut A
|
||||
mutated && is_active
|
||||
}
|
||||
|
||||
/// Handle `x.ai/mcp/servers_updated`.
|
||||
/// Handle `kigi/mcp/servers_updated`.
|
||||
///
|
||||
/// Emitted by the shell from `MvpAgent` on managed-config resolve and
|
||||
/// on config reload (`crates/codegen/kigi-shell/src/agent/mvp_agent.rs`
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//!
|
||||
//! Routes incoming [`AcpClientMessage`] notifications to the appropriate
|
||||
//! agent's tracker, queues permission requests for interactive handling,
|
||||
//! and xAI session extension notifications (`x.ai/session_notification` and
|
||||
//! replay-path `x.ai/session/update`).
|
||||
//! and xAI session extension notifications (`kigi/session_notification` and
|
||||
//! replay-path `kigi/session/update`).
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::path::PathBuf;
|
||||
@@ -591,39 +591,39 @@ pub(crate) fn handle(msg: AcpClientMessage, app: &mut AppView) -> bool {
|
||||
/// Handle an xAI extension notification.
|
||||
///
|
||||
/// Dispatches on method string:
|
||||
/// - `x.ai/session_notification` / `x.ai/session/update` → per-agent session updates
|
||||
/// - `kigi/session_notification` / `kigi/session/update` → per-agent session updates
|
||||
fn handle_ext_notification(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
match notif.method.as_ref() {
|
||||
"x.ai/session_notification" | "x.ai/session/update" => {
|
||||
"kigi/session_notification" | "kigi/session/update" => {
|
||||
handle_session_notification(notif, app)
|
||||
}
|
||||
"x.ai/follow_ups" => handle_follow_ups(notif, app),
|
||||
"x.ai/task_backgrounded" => handle_task_backgrounded(notif, app),
|
||||
"x.ai/task_completed" => handle_task_completed(notif, app),
|
||||
"x.ai/models/update" => handle_models_update(notif, app),
|
||||
"x.ai/settings/update" => handle_settings_update(notif, app),
|
||||
"x.ai/sessions/changed" => handle_sessions_changed(notif, app),
|
||||
"x.ai/queue/changed" => handle_queue_changed(notif, app),
|
||||
"kigi/follow_ups" => handle_follow_ups(notif, app),
|
||||
"kigi/task_backgrounded" => handle_task_backgrounded(notif, app),
|
||||
"kigi/task_completed" => handle_task_completed(notif, app),
|
||||
"kigi/models/update" => handle_models_update(notif, app),
|
||||
"kigi/settings/update" => handle_settings_update(notif, app),
|
||||
"kigi/sessions/changed" => handle_sessions_changed(notif, app),
|
||||
"kigi/queue/changed" => handle_queue_changed(notif, app),
|
||||
// TODO(prompt_complete-deprecation): Legacy removal (gated): durable turn_completed is already consumed via finalize_turn_from_terminal; keep & re-point the lost-RPC reconcile to the durable rail before deleting.
|
||||
"x.ai/session/prompt_complete" => handle_prompt_complete(notif, app),
|
||||
"x.ai/session/interjection" => handle_interjection(notif, app),
|
||||
"x.ai/monitor_event" => handle_monitor_event(notif, app),
|
||||
"x.ai/scheduled_task_created" => handle_scheduled_task_created(notif, app),
|
||||
"x.ai/scheduled_task_fired" => handle_scheduled_task_fired(notif, app),
|
||||
"x.ai/scheduled_task_deleted" => handle_scheduled_task_deleted(notif, app),
|
||||
"x.ai/scheduled_task_inject_prompt" => handle_scheduled_task_inject_prompt(notif, app),
|
||||
"x.ai/git_head_changed" => handle_git_head_changed(notif, app),
|
||||
"x.ai/mcp/init_progress" => handle_mcp_init_progress(notif, app),
|
||||
"x.ai/mcp/tools_changed" | "x.ai/mcp_initialized" => handle_mcp_tools_changed(notif, app),
|
||||
"x.ai/mcp/server_status" if push_server_status_enabled() => {
|
||||
"kigi/session/prompt_complete" => handle_prompt_complete(notif, app),
|
||||
"kigi/session/interjection" => handle_interjection(notif, app),
|
||||
"kigi/monitor_event" => handle_monitor_event(notif, app),
|
||||
"kigi/scheduled_task_created" => handle_scheduled_task_created(notif, app),
|
||||
"kigi/scheduled_task_fired" => handle_scheduled_task_fired(notif, app),
|
||||
"kigi/scheduled_task_deleted" => handle_scheduled_task_deleted(notif, app),
|
||||
"kigi/scheduled_task_inject_prompt" => handle_scheduled_task_inject_prompt(notif, app),
|
||||
"kigi/git_head_changed" => handle_git_head_changed(notif, app),
|
||||
"kigi/mcp/init_progress" => handle_mcp_init_progress(notif, app),
|
||||
"kigi/mcp/tools_changed" | "kigi/mcp_initialized" => handle_mcp_tools_changed(notif, app),
|
||||
"kigi/mcp/server_status" if push_server_status_enabled() => {
|
||||
handle_mcp_server_status(notif, app)
|
||||
}
|
||||
"x.ai/mcp/servers_updated" => handle_mcp_servers_updated(notif, app),
|
||||
"kigi/mcp/servers_updated" => handle_mcp_servers_updated(notif, app),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle `x.ai/session/interjection` — the leader broadcasts this
|
||||
/// Handle `kigi/session/interjection` — the leader broadcasts this
|
||||
/// sessionId-bearing notification to every attached client when a mid-turn
|
||||
/// interjection is queued (emitted from the session actor's `Interject`
|
||||
/// command handler). Each client renders the interjection as a scrollback
|
||||
@@ -638,7 +638,7 @@ fn handle_ext_notification(notif: &acp::ExtNotification, app: &mut AppView) -> b
|
||||
/// renders, so legacy shells degrade to "render everywhere" rather than drop.
|
||||
fn handle_interjection(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
let Ok(parsed) = serde_json::from_str::<serde_json::Value>(notif.params.get()) else {
|
||||
tracing::warn!("Failed to parse x.ai/session/interjection");
|
||||
tracing::warn!("Failed to parse kigi/session/interjection");
|
||||
return false;
|
||||
};
|
||||
let Some(session_id) = parsed.get("sessionId").and_then(|v| v.as_str()) else {
|
||||
@@ -672,7 +672,7 @@ fn handle_interjection(notif: &acp::ExtNotification, app: &mut AppView) -> bool
|
||||
// Interjecting into a parked wait continues the turn below this block —
|
||||
// the withheld "Worked for …" marker must not fire late beneath it
|
||||
// (shared-queue interjects render only via this broadcast, and the shell
|
||||
// emits the queue-emptying `x.ai/queue/changed` right after it).
|
||||
// emits the queue-emptying `kigi/queue/changed` right after it).
|
||||
agent.suppress_parked_marker_on_interject();
|
||||
is_active
|
||||
}
|
||||
@@ -684,8 +684,8 @@ fn handle_interjection(notif: &acp::ExtNotification, app: &mut AppView) -> bool
|
||||
/// immediately (for unknown methods).
|
||||
fn handle_ext_method(ext: kigi_acp_lib::AcpArgs<acp::ExtRequest>, app: &mut AppView) -> bool {
|
||||
match ext.request.method.as_ref() {
|
||||
"x.ai/ask_user_question" => handle_ask_user_question(ext, app),
|
||||
"x.ai/exit_plan_mode" => handle_exit_plan_mode(ext, app),
|
||||
"kigi/ask_user_question" => handle_ask_user_question(ext, app),
|
||||
"kigi/exit_plan_mode" => handle_exit_plan_mode(ext, app),
|
||||
unknown => {
|
||||
tracing::warn!("Unknown ext_method: {unknown}");
|
||||
ext.response_tx
|
||||
|
||||
@@ -11,7 +11,7 @@ pub(crate) fn is_server_initiated_prompt(prompt_id: &str) -> bool {
|
||||
/// Cron turns are synthetic (so [`is_server_initiated_prompt`] is also true for
|
||||
/// them), but UNLIKE auto-wake / subagent-completion turns they are
|
||||
/// CLIENT-driven via `MvpAgent::prompt()` and therefore DO emit a matching
|
||||
/// `x.ai/session/prompt_complete` turn-end signal. A viewer can thus safely
|
||||
/// `kigi/session/prompt_complete` turn-end signal. A viewer can thus safely
|
||||
/// enter `TurnRunning` for them (the exit exists, so it won't strand) — which is
|
||||
/// what lets the dashboard show a running `/loop` session as Working.
|
||||
pub(crate) fn is_scheduler_fired_prompt(prompt_id: &str) -> bool {
|
||||
@@ -39,7 +39,7 @@ pub(crate) fn is_wake_prompt(prompt_id: &str) -> bool {
|
||||
|
||||
/// Whether a running `prompt_id` is adoptable — i.e. safe to bind as the
|
||||
/// viewer's `current_prompt_id` and show as a live `TurnRunning`. The invariant:
|
||||
/// adoptable iff the turn emits a terminal `x.ai/session/prompt_complete`, the
|
||||
/// adoptable iff the turn emits a terminal `kigi/session/prompt_complete`, the
|
||||
/// only non-interactive way a viewer leaves `TurnRunning`. That holds for
|
||||
/// user-driven turns and `/loop` (`scheduler-fired-…`) fires — both run via
|
||||
/// `MvpAgent::prompt()` — and is false for actor-run synthetic turns
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) struct PendingRunningAdoption {
|
||||
pub turn_ended: bool,
|
||||
}
|
||||
|
||||
/// Wire payload of `x.ai/session/prompt_complete`, emitted by
|
||||
/// Wire payload of `kigi/session/prompt_complete`, emitted by
|
||||
/// `MvpAgent::prompt()` on the shell after every turn.
|
||||
///
|
||||
/// `Serialize` is derived so tests construct payloads through the same type
|
||||
@@ -62,7 +62,7 @@ pub(super) fn handle_queue_changed(notif: &acp::ExtNotification, app: &mut AppVi
|
||||
let Ok(changed) =
|
||||
serde_json::from_str::<crate::app::prompt_queue::QueueChanged>(notif.params.get())
|
||||
else {
|
||||
tracing::warn!("Failed to parse x.ai/queue/changed");
|
||||
tracing::warn!("Failed to parse kigi/queue/changed");
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ pub(super) fn handle_queue_changed(notif: &acp::ExtNotification, app: &mut AppVi
|
||||
local_current_prompt_id = %local_current_prompt_id,
|
||||
entry_count = changed.entries.len(),
|
||||
entries = ?recv_entry_ids,
|
||||
"received x.ai/queue/changed broadcast",
|
||||
"received kigi/queue/changed broadcast",
|
||||
);
|
||||
|
||||
let rekeyed_echo_ids = app.apply_queue_changed(changed);
|
||||
@@ -384,7 +384,7 @@ pub(super) fn handle_queue_changed(notif: &acp::ExtNotification, app: &mut AppVi
|
||||
/// TODO(prompt_complete-deprecation): Legacy removal (gated): durable turn_completed is already consumed via finalize_turn_from_terminal; keep & re-point the lost-RPC reconcile to the durable rail before deleting.
|
||||
pub(super) fn handle_prompt_complete(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
let Ok(payload) = serde_json::from_str::<PromptCompletePayload>(notif.params.get()) else {
|
||||
tracing::warn!("Failed to parse x.ai/session/prompt_complete");
|
||||
tracing::warn!("Failed to parse kigi/session/prompt_complete");
|
||||
return false;
|
||||
};
|
||||
let session_id = payload.session_id.as_str();
|
||||
|
||||
@@ -101,7 +101,7 @@ pub(super) fn advance_reconnect_cursor(agent: &mut AgentView, meta: &mut Notific
|
||||
agent.last_seen_event_id = Some(id);
|
||||
}
|
||||
}
|
||||
/// Handle `x.ai/session_notification` and replay-path `x.ai/session/update`.
|
||||
/// Handle `kigi/session_notification` and replay-path `kigi/session/update`.
|
||||
///
|
||||
/// Routes by `session_id` so events for an inactive agent still mutate that
|
||||
/// agent's state. The redraw decision is gated on whether the matched agent
|
||||
@@ -133,7 +133,7 @@ pub(super) fn handle_session_notification(notif: &acp::ExtNotification, app: &mu
|
||||
tracing::debug!(
|
||||
session_id = session_notif.session_id.0.as_ref(),
|
||||
method = notif.method.as_ref(),
|
||||
"load-race: x.ai/session_notification DROPPED — no agent matches session_id"
|
||||
"load-race: kigi/session_notification DROPPED — no agent matches session_id"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ pub(super) fn handle_session_notification(notif: &acp::ExtNotification, app: &mu
|
||||
agent,
|
||||
&meta,
|
||||
session_notif.session_id.0.as_ref(),
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ pub(super) fn handle_session_notification(notif: &acp::ExtNotification, app: &mu
|
||||
session_id = session_notif.session_id.0.as_ref(),
|
||||
event_seq = meta.event_seq,
|
||||
last_applied = agent.last_applied_xai_event_seq,
|
||||
"x.ai/session update DROPPED by dedup highwater (event_seq <= last_applied)"
|
||||
"kigi/session update DROPPED by dedup highwater (event_seq <= last_applied)"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use super::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Handle `x.ai/models/update` — model list changed (etag-triggered refresh).
|
||||
/// Handle `kigi/models/update` — model list changed (etag-triggered refresh).
|
||||
pub(super) fn handle_models_update(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
if let Ok(model_state) = serde_json::from_str::<acp::SessionModelState>(notif.params.get()) {
|
||||
use crate::acp::model_state::ModelState;
|
||||
let new_models = ModelState::from(Some(model_state));
|
||||
tracing::info!(
|
||||
count = new_models.available.len(),
|
||||
"models updated via x.ai/models/update"
|
||||
"models updated via kigi/models/update"
|
||||
);
|
||||
|
||||
let shell_fallback_current = new_models.current.clone();
|
||||
@@ -46,15 +46,15 @@ pub(super) fn handle_models_update(notif: &acp::ExtNotification, app: &mut AppVi
|
||||
}
|
||||
true
|
||||
} else {
|
||||
tracing::warn!("Failed to parse x.ai/models/update");
|
||||
tracing::warn!("Failed to parse kigi/models/update");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle `x.ai/settings/update` — remote settings refreshed on `/new`.
|
||||
/// Handle `kigi/settings/update` — remote settings refreshed on `/new`.
|
||||
pub(super) fn handle_settings_update(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
let Ok(update) = serde_json::from_str::<PagerSettingsUpdate>(notif.params.get()) else {
|
||||
tracing::warn!("Failed to parse x.ai/settings/update");
|
||||
tracing::warn!("Failed to parse kigi/settings/update");
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -221,7 +221,7 @@ pub(super) fn handle_settings_update(notif: &acp::ExtNotification, app: &mut App
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("settings updated via x.ai/settings/update");
|
||||
tracing::info!("settings updated via kigi/settings/update");
|
||||
true
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ pub(super) fn apply_soft_default_permission_mode(
|
||||
}
|
||||
|
||||
/// Tell live sessions to leave Auto on the mid-session kill-switch: fire the
|
||||
/// `x.ai/yolo_mode_changed` notification the agent maps to
|
||||
/// `kigi/yolo_mode_changed` notification the agent maps to
|
||||
/// `SetAutoMode { enabled: false }`, fire-and-forget over the shared ACP channel.
|
||||
/// The notification is CLIENT-scoped (the agent applies it to every session of
|
||||
/// the sending client), so one send covers all affected sessions. `yolo_mode` is
|
||||
@@ -264,7 +264,7 @@ pub(super) fn notify_sessions_leave_auto(app: &AppView, session_ids: &[acp::Sess
|
||||
"permission_mode": "ask",
|
||||
});
|
||||
let notification = acp::ExtNotification::new(
|
||||
"x.ai/yolo_mode_changed",
|
||||
"kigi/yolo_mode_changed",
|
||||
serde_json::value::to_raw_value(¶ms)
|
||||
.expect("serialize yolo_mode_changed params")
|
||||
.into(),
|
||||
@@ -277,12 +277,12 @@ pub(super) fn notify_sessions_leave_auto(app: &AppView, session_ids: &[acp::Sess
|
||||
let _ = app.acp_tx.send(args.into());
|
||||
}
|
||||
|
||||
/// Handle `x.ai/sessions/changed` — the leader broadcasts roster
|
||||
/// Handle `kigi/sessions/changed` — the leader broadcasts roster
|
||||
/// upserts/removals to all clients (FleetView dashboard).
|
||||
pub(super) fn handle_sessions_changed(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
|
||||
let Ok(changed) = serde_json::from_str::<crate::app::roster::RosterChanged>(notif.params.get())
|
||||
else {
|
||||
tracing::warn!("Failed to parse x.ai/sessions/changed");
|
||||
tracing::warn!("Failed to parse kigi/sessions/changed");
|
||||
return false;
|
||||
};
|
||||
let mut affected = false;
|
||||
@@ -297,7 +297,7 @@ pub(super) fn handle_sessions_changed(notif: &acp::ExtNotification, app: &mut Ap
|
||||
affected
|
||||
}
|
||||
|
||||
/// Deserialization type for the `x.ai/settings/update` notification payload.
|
||||
/// Deserialization type for the `kigi/settings/update` notification payload.
|
||||
///
|
||||
/// This is intentionally a separate struct from `SettingsUpdateNotification` in
|
||||
/// `kigi-shell/src/agent/mvp_agent.rs`. The shell side derives `Serialize`
|
||||
|
||||
@@ -98,6 +98,6 @@ pub(crate) fn finalize_killed_subagent(
|
||||
let Ok(params) = serde_json::value::to_raw_value(&payload) else {
|
||||
return false;
|
||||
};
|
||||
let notif = acp::ExtNotification::new("x.ai/session/update", params.into());
|
||||
let notif = acp::ExtNotification::new("kigi/session/update", params.into());
|
||||
handle_ext_notification(¬if, app)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use super::*;
|
||||
|
||||
/// Regression (resume sync): the on-disk replay stream re-emits persisted
|
||||
/// notifications through the generic `x.ai/session/update` envelope. A
|
||||
/// notifications through the generic `kigi/session/update` envelope. A
|
||||
/// background `monitor`/bash task (`TaskBackgrounded`) must restore into
|
||||
/// `bg_tasks` on a resumed / second terminal — not be dropped by the
|
||||
/// default match arm — so the idle "watching" status line and the Tasks pane
|
||||
@@ -24,7 +24,7 @@
|
||||
description: None,
|
||||
};
|
||||
handle(
|
||||
make_ext_session_notification_with_method("sess-1", "x.ai/session/update", update),
|
||||
make_ext_session_notification_with_method("sess-1", "kigi/session/update", update),
|
||||
&mut app,
|
||||
);
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-1",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
XaiSessionUpdate::ScheduledTaskCreated {
|
||||
task_id: "loop-1".into(),
|
||||
prompt: "check deploy".into(),
|
||||
@@ -72,7 +72,7 @@
|
||||
handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-1",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
XaiSessionUpdate::ScheduledTaskDeleted {
|
||||
task_id: "loop-1".into(),
|
||||
},
|
||||
@@ -177,7 +177,7 @@
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
let notif = acp::ExtNotification::new("x.ai/task_backgrounded", raw.into());
|
||||
let notif = acp::ExtNotification::new("kigi/task_backgrounded", raw.into());
|
||||
assert!(handle_task_backgrounded(¬if, &mut app));
|
||||
|
||||
let agent = app.agents.get(&AgentId(0)).unwrap();
|
||||
@@ -232,7 +232,7 @@
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
let notif = acp::ExtNotification::new("x.ai/task_backgrounded", raw.into());
|
||||
let notif = acp::ExtNotification::new("kigi/task_backgrounded", raw.into());
|
||||
assert!(handle_task_backgrounded(¬if, &mut app));
|
||||
|
||||
let agent = app.agents.get(&AgentId(0)).unwrap();
|
||||
|
||||
@@ -81,10 +81,10 @@
|
||||
let params = serde_json::json!({
|
||||
"response_id": "resp-1",
|
||||
"suggestions": [{ "label": "x" }],
|
||||
"_meta": { "x.ai/replayed": true },
|
||||
"_meta": { "kigi/replayed": true },
|
||||
});
|
||||
let notif = acp::ExtNotification::new(
|
||||
"x.ai/follow_ups",
|
||||
"kigi/follow_ups",
|
||||
serde_json::value::to_raw_value(¶ms).unwrap().into(),
|
||||
);
|
||||
let affected = handle_ext_notification(¬if, &mut app);
|
||||
@@ -103,7 +103,7 @@
|
||||
];
|
||||
for params in bad {
|
||||
let notif = acp::ExtNotification::new(
|
||||
"x.ai/follow_ups",
|
||||
"kigi/follow_ups",
|
||||
serde_json::value::to_raw_value(¶ms).unwrap().into(),
|
||||
);
|
||||
let affected = handle_ext_notification(¬if, &mut app);
|
||||
@@ -214,10 +214,10 @@
|
||||
let params = serde_json::json!({
|
||||
"response_id": "resp-1",
|
||||
"suggestions": [{ "label": "x" }],
|
||||
"_meta": { "x.ai/replayed": false },
|
||||
"_meta": { "kigi/replayed": false },
|
||||
});
|
||||
let notif = acp::ExtNotification::new(
|
||||
"x.ai/follow_ups",
|
||||
"kigi/follow_ups",
|
||||
serde_json::value::to_raw_value(¶ms).unwrap().into(),
|
||||
);
|
||||
assert!(
|
||||
@@ -235,7 +235,7 @@
|
||||
serde_json::json!({ "response_id": "r", "suggestions": [null] }),
|
||||
] {
|
||||
let notif = acp::ExtNotification::new(
|
||||
"x.ai/follow_ups",
|
||||
"kigi/follow_ups",
|
||||
serde_json::value::to_raw_value(&bad).unwrap().into(),
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"token_baseline": 100,
|
||||
"finished_subagent_tokens": 99,
|
||||
"live_subagent_tokens": 4_321,
|
||||
"live_tokens_by_model": [["grok-4", 6_000], ["grok-3", 4_000]],
|
||||
"live_tokens_by_model": [["kigi-4", 6_000], ["kigi-3", 4_000]],
|
||||
"live_context_pct": 42,
|
||||
"live_turn_count": 7,
|
||||
"live_tool_call_count": 11,
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
});
|
||||
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
|
||||
let request = acp::ExtNotification::new("x.ai/session_notification", raw.into());
|
||||
let request = acp::ExtNotification::new("kigi/session_notification", raw.into());
|
||||
let (tx, _rx) = tokio::sync::oneshot::channel();
|
||||
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
|
||||
request,
|
||||
@@ -94,7 +94,7 @@
|
||||
assert_eq!(goal.live_subagent_tokens, Some(4_321));
|
||||
assert_eq!(
|
||||
goal.live_tokens_by_model,
|
||||
vec![("grok-4".to_owned(), 6_000), ("grok-3".to_owned(), 4_000)],
|
||||
vec![("kigi-4".to_owned(), 6_000), ("kigi-3".to_owned(), 4_000)],
|
||||
"populated per-model breakdown must round-trip wire->display"
|
||||
);
|
||||
assert_eq!(goal.live_context_pct, Some(42));
|
||||
@@ -147,7 +147,7 @@
|
||||
let (tx, _rx) = tokio::sync::oneshot::channel();
|
||||
handle(
|
||||
AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtNotification::new("x.ai/session_notification", raw.into()),
|
||||
request: acp::ExtNotification::new("kigi/session_notification", raw.into()),
|
||||
response_tx: tx,
|
||||
}),
|
||||
app,
|
||||
@@ -372,7 +372,7 @@
|
||||
}
|
||||
});
|
||||
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
|
||||
let request = acp::ExtNotification::new("x.ai/session_notification", raw.into());
|
||||
let request = acp::ExtNotification::new("kigi/session_notification", raw.into());
|
||||
let (tx, _rx) = tokio::sync::oneshot::channel();
|
||||
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
|
||||
request,
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
}))
|
||||
.unwrap();
|
||||
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/ask_user_question", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/ask_user_question", raw.into()),
|
||||
response_tx: tx,
|
||||
});
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
}))
|
||||
.unwrap();
|
||||
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/ask_user_question", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/ask_user_question", raw.into()),
|
||||
response_tx: tx,
|
||||
});
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
|
||||
handle(
|
||||
AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
|
||||
response_tx: tx,
|
||||
}),
|
||||
&mut app,
|
||||
@@ -317,7 +317,7 @@
|
||||
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
|
||||
handle(
|
||||
AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
|
||||
response_tx: tx,
|
||||
}),
|
||||
&mut app,
|
||||
@@ -355,7 +355,7 @@
|
||||
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
|
||||
handle(
|
||||
AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
|
||||
response_tx: tx,
|
||||
}),
|
||||
&mut app,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use super::*;
|
||||
|
||||
/// Regression: a shared-queue interjection renders only via the broadcast,
|
||||
/// and the shell emits the queue-emptying `x.ai/queue/changed` right after
|
||||
/// and the shell emits the queue-emptying `kigi/queue/changed` right after
|
||||
/// it — which used to fire the withheld parked marker BELOW the just-
|
||||
/// rendered user message ("Worked for …" under the follow-up, flipped
|
||||
/// transcript order). The broadcast must consume the marker slot instead.
|
||||
@@ -680,7 +680,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
/// `x.ai/task_backgrounded` arriving after the skipped zero-work wait
|
||||
/// `kigi/task_backgrounded` arriving after the skipped zero-work wait
|
||||
/// re-evaluates and restores the park.
|
||||
#[test]
|
||||
fn task_backgrounded_after_zero_work_wait_all_restores_park() {
|
||||
@@ -716,7 +716,7 @@
|
||||
#[test]
|
||||
fn interjection_notification_pushes_block_to_matching_session() {
|
||||
// Multi-client fix: an interjection typed in one pane is broadcast by
|
||||
// the shell as x.ai/session/interjection; EVERY attached pane (incl.
|
||||
// the shell as kigi/session/interjection; EVERY attached pane (incl.
|
||||
// the originator, which no longer pushes a local block) renders it.
|
||||
let mut app = make_app_with_agent("sess-view");
|
||||
let affected =
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
|
||||
#[test]
|
||||
fn mcp_initialized_clears_progress() {
|
||||
// x.ai/mcp_initialized must set mcp_init_progress to None.
|
||||
// kigi/mcp_initialized must set mcp_init_progress to None.
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
agent.mcp_init_progress = Some(crate::app::agent_view::McpInitProgress {
|
||||
@@ -408,7 +408,7 @@
|
||||
// NB: no `status`.
|
||||
});
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
let notif = acp::ExtNotification::new("x.ai/mcp/server_status", raw.into());
|
||||
let notif = acp::ExtNotification::new("kigi/mcp/server_status", raw.into());
|
||||
let redraw = handle_mcp_server_status(¬if, &mut app);
|
||||
assert!(!redraw, "malformed payload must not request a redraw");
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ pub(super) fn interjection_broadcast(
|
||||
text: &str,
|
||||
) -> acp::ExtNotification {
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/interjection",
|
||||
"kigi/session/interjection",
|
||||
std::sync::Arc::from(
|
||||
serde_json::value::to_raw_value(
|
||||
&serde_json::json!({ "sessionId" : session_id, "text" : text, }),
|
||||
@@ -233,7 +233,7 @@ pub(super) fn follow_ups_ext(
|
||||
{ "response_id" : response_id, "suggestions" : suggestions, }
|
||||
);
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/follow_ups",
|
||||
"kigi/follow_ups",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -251,7 +251,7 @@ pub(super) fn follow_ups_ext_with_prompt(
|
||||
suggestions, }
|
||||
);
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/follow_ups",
|
||||
"kigi/follow_ups",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -263,7 +263,7 @@ pub(super) fn group_tool_verbs_settings_update(
|
||||
None => serde_json::json!({}),
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/settings/update",
|
||||
"kigi/settings/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -275,7 +275,7 @@ pub(super) fn collapsed_edit_blocks_settings_update(
|
||||
None => serde_json::json!({}),
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/settings/update",
|
||||
"kigi/settings/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -289,7 +289,7 @@ pub(super) fn subagent_ext_replay(
|
||||
"eventId" : event_id }, }
|
||||
);
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -315,7 +315,7 @@ pub(super) fn make_exit_plan_ext_with_tool_call_id(
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let request = acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into());
|
||||
let request = acp::ExtRequest::new("kigi/exit_plan_mode", raw.into());
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
(
|
||||
kigi_acp_lib::AcpArgs {
|
||||
@@ -359,11 +359,11 @@ pub(super) fn queue_changed_ext(session_id: &str, ids: &[&str]) -> acp::ExtNotif
|
||||
.collect();
|
||||
let params = serde_json::json!({ "sessionId" : session_id, "entries" : entries });
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/queue/changed",
|
||||
"kigi/queue/changed",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
/// Build a `x.ai/queue/changed` notification carrying `runningPromptId`.
|
||||
/// Build a `kigi/queue/changed` notification carrying `runningPromptId`.
|
||||
pub(super) fn queue_changed_running(
|
||||
session_id: &str,
|
||||
ids: &[&str],
|
||||
@@ -386,7 +386,7 @@ pub(super) fn queue_changed_running(
|
||||
params["runningPromptId"] = serde_json::Value::String(r.to_string());
|
||||
}
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/queue/changed",
|
||||
"kigi/queue/changed",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -469,7 +469,7 @@ pub(super) fn tool_call_block_count(agent: &AgentView) -> usize {
|
||||
pub(super) fn make_inject_notif(payload: &serde_json::Value) -> acp::ExtNotification {
|
||||
let raw = serde_json::value::to_raw_value(payload).unwrap();
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/scheduled_task_inject_prompt",
|
||||
"kigi/scheduled_task_inject_prompt",
|
||||
std::sync::Arc::from(raw),
|
||||
)
|
||||
}
|
||||
@@ -491,7 +491,7 @@ pub(super) fn make_fired_notif(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/scheduled_task_fired", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/scheduled_task_fired", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Set up an app with two agents; the active view points to agent 1, but
|
||||
/// agent 0 owns the scheduled task. Handlers that gate on `active_view`
|
||||
@@ -531,7 +531,7 @@ pub(super) fn make_created_ext_notif(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/scheduled_task_created", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/scheduled_task_created", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_deleted_ext_notif(
|
||||
session_id: &str,
|
||||
@@ -545,7 +545,7 @@ pub(super) fn make_deleted_ext_notif(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/scheduled_task_deleted", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/scheduled_task_deleted", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_token_notification_message(
|
||||
session_id: &str,
|
||||
@@ -697,7 +697,7 @@ pub(super) fn xai_model_switch_notif(
|
||||
meta: Some(serde_json::json!({ "eventId" : event_id })),
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -711,7 +711,7 @@ pub(super) fn xai_unhandled_notif(
|
||||
meta: Some(serde_json::json!({ "eventId" : event_id })),
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -741,19 +741,19 @@ pub(super) fn make_token_notification_with_event(
|
||||
response_tx: tx,
|
||||
})
|
||||
}
|
||||
/// Build an `x.ai/session/prompt_complete` ext-notification for `session_id`.
|
||||
/// Build an `kigi/session/prompt_complete` ext-notification for `session_id`.
|
||||
pub(super) fn prompt_complete_ext(session_id: &str) -> acp::ExtNotification {
|
||||
let raw = serde_json::value::to_raw_value(
|
||||
&serde_json::json!({ "sessionId" : session_id, "stopReason" : "end_turn", }),
|
||||
)
|
||||
.unwrap();
|
||||
acp::ExtNotification::new("x.ai/session/prompt_complete", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session/prompt_complete", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Insert a fresh agent at `id` with an optional pre-assigned session id.
|
||||
pub(super) fn insert_agent(app: &mut AppView, id: AgentId, session_id: Option<&str>) {
|
||||
app.agents.insert(id, make_agent(session_id));
|
||||
}
|
||||
/// Build an `x.ai/session/prompt_complete` ext-notification with an explicit
|
||||
/// Build an `kigi/session/prompt_complete` ext-notification with an explicit
|
||||
/// `stopReason` and optional `agentResult`.
|
||||
pub(super) fn prompt_complete_ext_with_reason(
|
||||
session_id: &str,
|
||||
@@ -767,9 +767,9 @@ pub(super) fn prompt_complete_ext_with_reason(
|
||||
payload["agentResult"] = serde_json::json!(r);
|
||||
}
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/session/prompt_complete", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session/prompt_complete", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Build an `x.ai/session/prompt_complete` ext-notification carrying a
|
||||
/// Build an `kigi/session/prompt_complete` ext-notification carrying a
|
||||
/// `promptId` (shells with the lost-response fix). Built through the
|
||||
/// typed [`PromptCompletePayload`] so the test wire shape can never
|
||||
/// drift from what `handle_prompt_complete` parses.
|
||||
@@ -789,7 +789,7 @@ pub(super) fn prompt_complete_ext_with_prompt_id(
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
acp::ExtNotification::new("x.ai/session/prompt_complete", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session/prompt_complete", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Build a live `AgentMessageChunk` whose meta carries `promptId` plus a
|
||||
/// `turnStartMs` `start_ms_ago` milliseconds in the past — drives the viewer
|
||||
@@ -822,7 +822,7 @@ pub(super) fn make_viewer_chunk_with_turn_start(
|
||||
response_tx: tx,
|
||||
})
|
||||
}
|
||||
/// Build a durable `TurnCompleted` update on the `x.ai/session/update` rail,
|
||||
/// Build a durable `TurnCompleted` update on the `kigi/session/update` rail,
|
||||
/// optionally stamped `isReplay`. Built through the typed `SessionNotification`
|
||||
/// so the wire shape can't drift from what the dispatch parses.
|
||||
pub(super) fn xai_turn_completed_notif(
|
||||
@@ -842,7 +842,7 @@ pub(super) fn xai_turn_completed_notif(
|
||||
meta: Some(serde_json::json!({ "isReplay" : is_replay })),
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -868,7 +868,7 @@ pub(super) fn xai_wake_turn_completed_notif(
|
||||
meta: Some(meta),
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -885,7 +885,7 @@ pub(super) fn last_marker_block(
|
||||
.expect("a turn-end marker must exist")
|
||||
}
|
||||
/// Build a `HookExecution` update (one successful run) on the
|
||||
/// `x.ai/session/update` rail, optionally stamped `isReplay`.
|
||||
/// `kigi/session/update` rail, optionally stamped `isReplay`.
|
||||
/// `prompt_id == None` models pre-attribution shells.
|
||||
pub(super) fn xai_hook_execution_notif_for_prompt(
|
||||
session_id: &str,
|
||||
@@ -908,7 +908,7 @@ pub(super) fn xai_hook_execution_notif_for_prompt(
|
||||
meta: Some(serde_json::json!({ "isReplay" : is_replay })),
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
serde_json::value::to_raw_value(&payload).unwrap().into(),
|
||||
)
|
||||
}
|
||||
@@ -970,11 +970,11 @@ pub(super) fn seed_two_bg_tasks_and_announce(app: &mut AppView, session_id: &str
|
||||
);
|
||||
app.agents.get_mut(&AgentId(0)).unwrap().end_work_announced = true;
|
||||
}
|
||||
/// Build an `x.ai/session/interjection` ext-notification (no id).
|
||||
/// Build an `kigi/session/interjection` ext-notification (no id).
|
||||
pub(super) fn interjection_ext(session_id: &str, text: &str) -> acp::ExtNotification {
|
||||
interjection_ext_with_id(session_id, text, None)
|
||||
}
|
||||
/// Build an `x.ai/session/interjection` ext-notification with an optional
|
||||
/// Build an `kigi/session/interjection` ext-notification with an optional
|
||||
/// `interjectionId` (the originator-dedup key).
|
||||
pub(super) fn interjection_ext_with_id(
|
||||
session_id: &str,
|
||||
@@ -986,7 +986,7 @@ pub(super) fn interjection_ext_with_id(
|
||||
payload["interjectionId"] = serde_json::json!(id);
|
||||
}
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/session/interjection", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session/interjection", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Text of the most recent user prompt block in scrollback, if any.
|
||||
/// Interjections render as standard user prompt blocks.
|
||||
@@ -1090,14 +1090,14 @@ pub(super) fn make_bash_stdout_message(
|
||||
response_tx: tx,
|
||||
})
|
||||
}
|
||||
/// Build an `ExtNotification` envelope for `x.ai/session_notification`.
|
||||
/// Build an `ExtNotification` envelope for `kigi/session_notification`.
|
||||
pub(super) fn make_ext_session_notification(
|
||||
session_id: &str,
|
||||
update: XaiSessionUpdate,
|
||||
) -> AcpClientMessage {
|
||||
make_ext_session_notification_with_method(
|
||||
session_id,
|
||||
"x.ai/session_notification",
|
||||
"kigi/session_notification",
|
||||
update,
|
||||
)
|
||||
}
|
||||
@@ -1279,7 +1279,7 @@ pub(super) fn replay_disk_test_home() -> &'static std::path::Path {
|
||||
})
|
||||
.path()
|
||||
}
|
||||
/// Runs `f` with a thread-local grok home override so disk replay tests do not
|
||||
/// Runs `f` with a thread-local kigi home override so disk replay tests do not
|
||||
/// depend on process-wide `kigi_home()` cache order when the full suite runs.
|
||||
pub(super) fn with_replay_disk_home<R>(f: impl FnOnce(&std::path::Path) -> R) -> R {
|
||||
let home = replay_disk_test_home();
|
||||
@@ -1389,7 +1389,7 @@ pub(super) fn spawn_subagent_with_optional_updates(
|
||||
let _ = handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-parent",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
test_subagent_spawned("sess-parent", child_sid),
|
||||
),
|
||||
app,
|
||||
@@ -1421,7 +1421,7 @@ pub(super) fn dispatch_goal_update(
|
||||
let (tx, _rx) = tokio::sync::oneshot::channel();
|
||||
handle(
|
||||
AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtNotification::new("x.ai/session_notification", raw.into()),
|
||||
request: acp::ExtNotification::new("kigi/session_notification", raw.into()),
|
||||
response_tx: tx,
|
||||
}),
|
||||
app,
|
||||
@@ -1464,7 +1464,7 @@ pub(super) fn make_permission_message(
|
||||
});
|
||||
(msg, rx)
|
||||
}
|
||||
/// Build an `x.ai/session_notification` carrying
|
||||
/// Build an `kigi/session_notification` carrying
|
||||
/// `InteractionResolved{tool_call_id}` (the first-answer-wins broadcast that
|
||||
/// tells every other pane to retract its shared interaction modal).
|
||||
pub(super) fn interaction_resolved_ext(
|
||||
@@ -1479,7 +1479,7 @@ pub(super) fn interaction_resolved_ext(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/session_notification", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session_notification", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_git_head_changed_notif(
|
||||
session_id: &str,
|
||||
@@ -1494,7 +1494,7 @@ pub(super) fn make_git_head_changed_notif(
|
||||
main_repo: main_repo.map(str::to_string),
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/git_head_changed", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/git_head_changed", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_task_backgrounded_notif(
|
||||
session_id: &str,
|
||||
@@ -1516,7 +1516,7 @@ pub(super) fn make_task_backgrounded_notif(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/task_backgrounded", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/task_backgrounded", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Like [`make_task_backgrounded_notif`] but stamped `_meta.isReplay:
|
||||
/// true` via the typed [`ReplayMetaStamp`](crate::acp::meta::ReplayMetaStamp),
|
||||
@@ -1541,7 +1541,7 @@ pub(super) fn make_replayed_task_backgrounded_notif(
|
||||
meta: Some(crate::acp::meta::ReplayMetaStamp::replayed()),
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/session/update", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session/update", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Register a pending Execute tool call in the tracker and send an InProgress
|
||||
/// update to create the scrollback entry. Returns the agent for further use.
|
||||
@@ -1670,7 +1670,7 @@ pub(super) fn task_completed_notif(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/task_completed", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/task_completed", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_monitor_event_notif(
|
||||
session_id: &str,
|
||||
@@ -1687,7 +1687,7 @@ pub(super) fn make_monitor_event_notif(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(¬if).unwrap();
|
||||
acp::ExtNotification::new("x.ai/monitor_event", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/monitor_event", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_model_info(id: &str) -> acp::ModelInfo {
|
||||
acp::ModelInfo::new(acp::ModelId::new(std::sync::Arc::from(id)), id.to_string())
|
||||
@@ -1705,9 +1705,9 @@ pub(super) fn make_models_update_notif(
|
||||
models,
|
||||
);
|
||||
let raw = serde_json::value::to_raw_value(&state).unwrap();
|
||||
acp::ExtNotification::new("x.ai/models/update", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/models/update", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// `x.ai/models/update` carrying a single reasoning-capable model whose
|
||||
/// `kigi/models/update` carrying a single reasoning-capable model whose
|
||||
/// catalog-default effort is `default_effort` (what the broadcast reports
|
||||
/// for every client — never the per-session selection).
|
||||
pub(super) fn make_reasoning_models_update_notif(
|
||||
@@ -1725,7 +1725,7 @@ pub(super) fn make_reasoning_models_update_notif(
|
||||
vec![info],
|
||||
);
|
||||
let raw = serde_json::value::to_raw_value(&state).unwrap();
|
||||
acp::ExtNotification::new("x.ai/models/update", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/models/update", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Seed a session's model catalog with the given ids and mark
|
||||
/// `current_model_id` as the active one (must be in the list). Used by
|
||||
@@ -1754,7 +1754,7 @@ pub(super) fn model_changed_ext(
|
||||
meta: None,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/session_notification", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session_notification", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn model_changed_ext_with_event(
|
||||
session_id: &str,
|
||||
@@ -1770,7 +1770,7 @@ pub(super) fn model_changed_ext_with_event(
|
||||
meta: Some(serde_json::json!({ "eventId" : event_id })),
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/session_notification", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/session_notification", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_tool_call_update(title: &str) -> acp::SessionUpdate {
|
||||
acp::SessionUpdate::ToolCallUpdate(
|
||||
@@ -1796,7 +1796,7 @@ pub(super) fn make_current_mode_update(mode_id: &str) -> acp::SessionUpdate {
|
||||
acp::CurrentModeUpdate::new(acp::SessionModeId::new(mode_id)),
|
||||
)
|
||||
}
|
||||
/// Helper: build an `x.ai/mcp/init_progress` notification.
|
||||
/// Helper: build an `kigi/mcp/init_progress` notification.
|
||||
pub(super) fn make_mcp_init_progress_notif(
|
||||
total: u32,
|
||||
connected: u32,
|
||||
@@ -1805,7 +1805,7 @@ pub(super) fn make_mcp_init_progress_notif(
|
||||
&serde_json::json!({ "total" : total, "connected" : connected, }),
|
||||
)
|
||||
.unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp/init_progress", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp/init_progress", std::sync::Arc::from(raw))
|
||||
}
|
||||
pub(super) fn make_mcps_modal_with_servers(
|
||||
servers: Vec<crate::views::mcps_modal::McpServerInfo>,
|
||||
@@ -1854,7 +1854,7 @@ pub(super) fn make_server_status_notif(
|
||||
tools,
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp/server_status", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp/server_status", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// `mcp/servers_updated` real wire shape — `{ mcpServers: [...] }`
|
||||
/// with NO `sessionId`. Regression guard: anything that tries to
|
||||
@@ -1863,7 +1863,7 @@ pub(super) fn make_server_status_notif(
|
||||
pub(super) fn make_servers_updated_notif() -> acp::ExtNotification {
|
||||
let payload = serde_json::json!({ "mcpServers" : [] });
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp/servers_updated", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp/servers_updated", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Real post-handshake / auth-recovery wire shape:
|
||||
/// `McpToolsChanged { sessionId, serverName, tools }`.
|
||||
@@ -1872,19 +1872,19 @@ pub(super) fn make_tools_changed_notif_post_h2(
|
||||
) -> acp::ExtNotification {
|
||||
let payload = kigi_shell::extensions::mcp::McpToolsChanged {
|
||||
session_id: session_id.to_string(),
|
||||
server_name: "grok_com_linear".to_string(),
|
||||
server_name: "kigi_com_linear".to_string(),
|
||||
tools: Vec::new(),
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp/tools_changed", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp/tools_changed", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Legacy / forward-compat wire shape: older shells emit
|
||||
/// `{ serverName, tools }` with NO sessionId. The pager must fall
|
||||
/// back to active_view for this shape.
|
||||
pub(super) fn make_tools_changed_notif_pre_h2() -> acp::ExtNotification {
|
||||
let payload = serde_json::json!({ "serverName" : "grok_com_linear", "tools" : [] });
|
||||
let payload = serde_json::json!({ "serverName" : "kigi_com_linear", "tools" : [] });
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp/tools_changed", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp/tools_changed", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Real `mcp_initialized` wire shape:
|
||||
/// `{ sessionId, mcpToolCount, elapsedMs }`.
|
||||
@@ -1893,7 +1893,7 @@ pub(super) fn make_mcp_initialized_notif(session_id: &str) -> acp::ExtNotificati
|
||||
{ "sessionId" : session_id, "mcpToolCount" : 12_u64, "elapsedMs" : 250_u64, }
|
||||
);
|
||||
let raw = serde_json::value::to_raw_value(&payload).unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp_initialized", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp_initialized", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Helper: `init_progress` notification carrying an explicit sessionId.
|
||||
pub(super) fn make_mcp_init_progress_notif_for(
|
||||
@@ -1907,7 +1907,7 @@ pub(super) fn make_mcp_init_progress_notif_for(
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp/init_progress", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp/init_progress", std::sync::Arc::from(raw))
|
||||
}
|
||||
/// Helper: `mcp_initialized` notification for a specific sessionId.
|
||||
pub(super) fn make_mcp_initialized_notif_for(session_id: &str) -> acp::ExtNotification {
|
||||
@@ -1917,7 +1917,7 @@ pub(super) fn make_mcp_initialized_notif_for(session_id: &str) -> acp::ExtNotifi
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
acp::ExtNotification::new("x.ai/mcp_initialized", std::sync::Arc::from(raw))
|
||||
acp::ExtNotification::new("kigi/mcp_initialized", std::sync::Arc::from(raw))
|
||||
}
|
||||
mod permissions;
|
||||
mod session_events;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
use super::*;
|
||||
|
||||
/// Regression: a machine-wide `x.ai/models/update` broadcast
|
||||
/// Regression: a machine-wide `kigi/models/update` broadcast
|
||||
/// carries each model's static catalog-default effort (`high`), not the
|
||||
/// session's chosen `xhigh`, and must not clobber the per-session choice.
|
||||
#[test]
|
||||
@@ -44,20 +44,20 @@
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
let id_3 = acp::ModelId::new(std::sync::Arc::from("grok-3"));
|
||||
let id_3 = acp::ModelId::new(std::sync::Arc::from("kigi-3"));
|
||||
agent
|
||||
.session
|
||||
.models
|
||||
.available
|
||||
.insert(id_3.clone(), make_model_info("grok-3"));
|
||||
.insert(id_3.clone(), make_model_info("kigi-3"));
|
||||
agent.session.models.current = Some(id_3);
|
||||
|
||||
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
|
||||
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
|
||||
handle_models_update(¬if, &mut app);
|
||||
|
||||
assert_eq!(
|
||||
app.models.current.as_ref().map(|id| id.0.as_ref()),
|
||||
Some("grok-3"),
|
||||
Some("kigi-3"),
|
||||
"app.models.current must preserve active agent's model, not remote settings default"
|
||||
);
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-3"),
|
||||
Some("kigi-3"),
|
||||
"agent's per-session model must be preserved"
|
||||
);
|
||||
}
|
||||
@@ -79,21 +79,21 @@
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
let id_3 = acp::ModelId::new(std::sync::Arc::from("grok-3"));
|
||||
let id_3 = acp::ModelId::new(std::sync::Arc::from("kigi-3"));
|
||||
agent
|
||||
.session
|
||||
.models
|
||||
.available
|
||||
.insert(id_3.clone(), make_model_info("grok-3"));
|
||||
.insert(id_3.clone(), make_model_info("kigi-3"));
|
||||
agent.session.models.current = Some(id_3);
|
||||
|
||||
// grok-3 removed from catalog.
|
||||
let notif = make_models_update_notif("grok-4.3", &["grok-4.3", "grok-4.5"]);
|
||||
// kigi-3 removed from catalog.
|
||||
let notif = make_models_update_notif("kigi-4.3", &["kigi-4.3", "kigi-4.5"]);
|
||||
handle_models_update(¬if, &mut app);
|
||||
|
||||
assert_eq!(
|
||||
app.models.current.as_ref().map(|id| id.0.as_ref()),
|
||||
Some("grok-4.3"),
|
||||
Some("kigi-4.3"),
|
||||
"app.models.current must use shell default when agent model removed"
|
||||
);
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-4.3"),
|
||||
Some("kigi-4.3"),
|
||||
"agent must fall back to shell default when its model is removed"
|
||||
);
|
||||
}
|
||||
@@ -115,12 +115,12 @@
|
||||
let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let mut app = AppView::new(tx, ModelState::default(), Vec::new());
|
||||
|
||||
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
|
||||
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
|
||||
handle_models_update(¬if, &mut app);
|
||||
|
||||
assert_eq!(
|
||||
app.models.current.as_ref().map(|id| id.0.as_ref()),
|
||||
Some("grok-4"),
|
||||
Some("kigi-4"),
|
||||
"without an active agent, shell default must be used"
|
||||
);
|
||||
}
|
||||
@@ -130,21 +130,21 @@
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
let id_4 = acp::ModelId::new(std::sync::Arc::from("grok-4"));
|
||||
let id_4 = acp::ModelId::new(std::sync::Arc::from("kigi-4"));
|
||||
agent
|
||||
.session
|
||||
.models
|
||||
.available
|
||||
.insert(id_4.clone(), make_model_info("grok-4"));
|
||||
.insert(id_4.clone(), make_model_info("kigi-4"));
|
||||
agent.session.models.current = Some(id_4);
|
||||
|
||||
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
|
||||
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
|
||||
handle_models_update(¬if, &mut app);
|
||||
|
||||
assert_eq!(
|
||||
app.models.current.as_ref().map(|id| id.0.as_ref()),
|
||||
Some("grok-4"),
|
||||
"app.models.current must be grok-4 when agent and shell agree"
|
||||
Some("kigi-4"),
|
||||
"app.models.current must be kigi-4 when agent and shell agree"
|
||||
);
|
||||
let agent = app.agents.get(&AgentId(0)).unwrap();
|
||||
assert_eq!(
|
||||
@@ -154,8 +154,8 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-4"),
|
||||
"agent model must remain grok-4"
|
||||
Some("kigi-4"),
|
||||
"agent model must remain kigi-4"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -166,33 +166,33 @@
|
||||
|
||||
{
|
||||
let agent_a = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
let id_3 = acp::ModelId::new(std::sync::Arc::from("grok-3"));
|
||||
let id_3 = acp::ModelId::new(std::sync::Arc::from("kigi-3"));
|
||||
agent_a
|
||||
.session
|
||||
.models
|
||||
.available
|
||||
.insert(id_3.clone(), make_model_info("grok-3"));
|
||||
.insert(id_3.clone(), make_model_info("kigi-3"));
|
||||
agent_a.session.models.current = Some(id_3);
|
||||
}
|
||||
|
||||
{
|
||||
let agent_b = app.agents.get_mut(&AgentId(1)).unwrap();
|
||||
let id_5 = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let id_5 = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
agent_b
|
||||
.session
|
||||
.models
|
||||
.available
|
||||
.insert(id_5.clone(), make_model_info("grok-4.5"));
|
||||
.insert(id_5.clone(), make_model_info("kigi-4.5"));
|
||||
agent_b.session.models.current = Some(id_5);
|
||||
}
|
||||
|
||||
// grok-5 removed from catalog.
|
||||
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
|
||||
// kigi-5 removed from catalog.
|
||||
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
|
||||
handle_models_update(¬if, &mut app);
|
||||
|
||||
assert_eq!(
|
||||
app.models.current.as_ref().map(|id| id.0.as_ref()),
|
||||
Some("grok-3"),
|
||||
Some("kigi-3"),
|
||||
);
|
||||
let agent_a = app.agents.get(&AgentId(0)).unwrap();
|
||||
assert_eq!(
|
||||
@@ -202,11 +202,11 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-3"),
|
||||
Some("kigi-3"),
|
||||
"agent A's model must be preserved"
|
||||
);
|
||||
|
||||
// B's grok-5 was removed — must fall back to shell's grok-4, not A's grok-3.
|
||||
// B's kigi-5 was removed — must fall back to shell's kigi-4, not A's kigi-3.
|
||||
let agent_b = app.agents.get(&AgentId(1)).unwrap();
|
||||
assert_eq!(
|
||||
agent_b
|
||||
@@ -215,7 +215,7 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-4"),
|
||||
Some("kigi-4"),
|
||||
"inactive agent must fall back to shell default, not active agent's model"
|
||||
);
|
||||
}
|
||||
@@ -228,12 +228,12 @@
|
||||
fn model_changed_updates_state_silently_on_follower() {
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
|
||||
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
|
||||
let scrollback_before = agent.scrollback.len();
|
||||
// Follower: no local switch in flight.
|
||||
assert!(!agent.session.model_switch_pending);
|
||||
|
||||
let notif = model_changed_ext("sess-1", "grok-4", None);
|
||||
let notif = model_changed_ext("sess-1", "kigi-4", None);
|
||||
let changed = handle_ext_notification(¬if, &mut app);
|
||||
assert!(
|
||||
changed,
|
||||
@@ -248,7 +248,7 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-4"),
|
||||
Some("kigi-4"),
|
||||
"follower must mirror the remote switch into its local model state",
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -325,13 +325,13 @@
|
||||
fn model_changed_skipped_when_local_switch_in_flight() {
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
|
||||
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
|
||||
// Invoker: a local switch is in flight (set by Action::SwitchModel /
|
||||
// set_default_model before the SetSessionModelRequest is sent).
|
||||
agent.session.model_switch_pending = true;
|
||||
let scrollback_before = agent.scrollback.len();
|
||||
|
||||
let notif = model_changed_ext("sess-1", "grok-4", None);
|
||||
let notif = model_changed_ext("sess-1", "kigi-4", None);
|
||||
let changed = handle_ext_notification(¬if, &mut app);
|
||||
assert!(
|
||||
!changed,
|
||||
@@ -346,7 +346,7 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-3"),
|
||||
Some("kigi-3"),
|
||||
"models.current must stay at the pre-response snapshot — \
|
||||
SwitchModelComplete owns the final apply + system message"
|
||||
);
|
||||
@@ -370,9 +370,9 @@
|
||||
fn model_changed_dropped_when_model_unknown_to_catalog() {
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
|
||||
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
|
||||
|
||||
let notif = model_changed_ext("sess-1", "grok-99-unknown", None);
|
||||
let notif = model_changed_ext("sess-1", "kigi-99-unknown", None);
|
||||
let changed = handle_ext_notification(¬if, &mut app);
|
||||
assert!(
|
||||
!changed,
|
||||
@@ -387,7 +387,7 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-3"),
|
||||
Some("kigi-3"),
|
||||
"models.current must stay on the previously-known model"
|
||||
);
|
||||
}
|
||||
@@ -395,15 +395,15 @@
|
||||
/// `reasoning_effort` round-trips through the broadcast: the follower
|
||||
/// applies it alongside the model id so the prompt header / status bar
|
||||
/// show the right effort without waiting for a subsequent
|
||||
/// `x.ai/models/update`.
|
||||
/// `kigi/models/update`.
|
||||
#[test]
|
||||
fn model_changed_applies_reasoning_effort_on_follower() {
|
||||
use kigi_shell::sampling::types::ReasoningEffort;
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
|
||||
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
|
||||
|
||||
let notif = model_changed_ext("sess-1", "grok-4", Some("high"));
|
||||
let notif = model_changed_ext("sess-1", "kigi-4", Some("high"));
|
||||
assert!(handle_ext_notification(¬if, &mut app));
|
||||
|
||||
let agent = app.agents.get(&AgentId(0)).unwrap();
|
||||
@@ -423,9 +423,9 @@
|
||||
fn model_changed_dropped_for_unknown_session_id() {
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
|
||||
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
|
||||
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
|
||||
|
||||
let notif = model_changed_ext("sess-OTHER", "grok-4", None);
|
||||
let notif = model_changed_ext("sess-OTHER", "kigi-4", None);
|
||||
let changed = handle_ext_notification(¬if, &mut app);
|
||||
assert!(!changed);
|
||||
|
||||
@@ -437,7 +437,7 @@
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-3"),
|
||||
Some("kigi-3"),
|
||||
"unrelated-session broadcast must not touch this agent's model"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
|
||||
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
|
||||
response_tx: tx,
|
||||
});
|
||||
|
||||
@@ -244,7 +244,7 @@
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
|
||||
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
|
||||
response_tx: tx,
|
||||
});
|
||||
|
||||
@@ -275,7 +275,7 @@
|
||||
};
|
||||
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
|
||||
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
|
||||
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
|
||||
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
|
||||
response_tx: tx,
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
XaiSessionUpdate::PluginsChanged {
|
||||
plugins: vec![crate::views::extensions_modal::test_plugin_info(
|
||||
"user-tool",
|
||||
Some(kigi_hooks_plugins_types::PluginOrigin::UserGrok),
|
||||
Some(kigi_hooks_plugins_types::PluginOrigin::UserKigi),
|
||||
)],
|
||||
},
|
||||
),
|
||||
@@ -60,7 +60,7 @@
|
||||
plugins: vec![
|
||||
crate::views::extensions_modal::test_plugin_info(
|
||||
"user-tool",
|
||||
Some(kigi_hooks_plugins_types::PluginOrigin::UserGrok),
|
||||
Some(kigi_hooks_plugins_types::PluginOrigin::UserKigi),
|
||||
),
|
||||
crate::views::extensions_modal::test_plugin_info(
|
||||
"claude-tool",
|
||||
@@ -103,7 +103,7 @@
|
||||
XaiSessionUpdate::PluginsChanged {
|
||||
plugins: vec![crate::views::extensions_modal::test_plugin_info(
|
||||
"user-tool",
|
||||
Some(kigi_hooks_plugins_types::PluginOrigin::UserGrok),
|
||||
Some(kigi_hooks_plugins_types::PluginOrigin::UserKigi),
|
||||
)],
|
||||
},
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use super::*;
|
||||
|
||||
/// The pager reconciles the authoritative shared prompt queue from the
|
||||
/// `x.ai/queue/changed` broadcast, and an empty broadcast clears it.
|
||||
/// `kigi/queue/changed` broadcast, and an empty broadcast clears it.
|
||||
#[test]
|
||||
fn queue_changed_reconciles_shared_queue() {
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
@@ -144,7 +144,7 @@
|
||||
params["runningPromptId"] = serde_json::json!(r);
|
||||
}
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/queue/changed",
|
||||
"kigi/queue/changed",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -336,7 +336,7 @@
|
||||
serde_json::from_str(&json_str).unwrap();
|
||||
assert_eq!(mirror.running_prompt_id.as_deref(), Some("prompt-running"));
|
||||
|
||||
let notif = acp::ExtNotification::new("x.ai/queue/changed", raw.into());
|
||||
let notif = acp::ExtNotification::new("kigi/queue/changed", raw.into());
|
||||
|
||||
// Case 1: current_prompt_id is None -> adopt it.
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
@@ -415,7 +415,7 @@
|
||||
/// Regression: when the shell promotes
|
||||
/// a server-initiated / auto-wake prompt (synthetic id `task-completed-…`,
|
||||
/// injected when a background task finishes) to the running turn, it
|
||||
/// broadcasts `x.ai/queue/changed` with `runningPromptId` = that synthetic
|
||||
/// broadcasts `kigi/queue/changed` with `runningPromptId` = that synthetic
|
||||
/// id. The pager must NOT adopt it via the turn-start shim: those turns run
|
||||
/// inside the actor and emit no `prompt_complete` / `PromptResponse`, so
|
||||
/// `start_turn()` here would strand the pager on "Responding…" forever
|
||||
@@ -1736,7 +1736,7 @@
|
||||
"promptId": "p1",
|
||||
});
|
||||
let notif = acp::ExtNotification::new(
|
||||
"x.ai/session/prompt_complete",
|
||||
"kigi/session/prompt_complete",
|
||||
serde_json::value::to_raw_value(¶ms).unwrap().into(),
|
||||
);
|
||||
handle_prompt_complete(¬if, &mut app);
|
||||
@@ -2093,7 +2093,7 @@
|
||||
fn viewer_does_not_enter_turn_running_for_server_initiated_turn() {
|
||||
// A server-initiated / auto-wake turn (synthetic prompt id, e.g. a
|
||||
// background subagent or task completion: `task-completed-…`) runs inside
|
||||
// the actor and emits NO `x.ai/session/prompt_complete`. If a viewer
|
||||
// the actor and emits NO `kigi/session/prompt_complete`. If a viewer
|
||||
// entered TurnRunning for it, nothing would ever finish the turn and the
|
||||
// viewer would be stuck "Responding…" forever — exactly the bug where one
|
||||
// dashboard showed "Worked for" while the other was stuck responding.
|
||||
@@ -2131,7 +2131,7 @@
|
||||
fn viewer_enters_turn_running_for_scheduler_fired_cron_turn() {
|
||||
// A `/loop` (scheduled-task) turn has a synthetic `scheduler-fired-…`
|
||||
// prompt id, but UNLIKE auto-wake turns it is client-driven via
|
||||
// `MvpAgent::prompt()` and DOES emit `x.ai/session/prompt_complete`. So a
|
||||
// `MvpAgent::prompt()` and DOES emit `kigi/session/prompt_complete`. So a
|
||||
// viewer MUST enter TurnRunning for it — otherwise the dashboard's
|
||||
// locally-tracked row for a running `/loop` session never shows Working.
|
||||
let mut app = make_app_with_agent("sess-view");
|
||||
@@ -2173,7 +2173,7 @@
|
||||
|
||||
#[test]
|
||||
fn viewer_prompt_complete_finishes_turn() {
|
||||
// A viewer in TurnRunning receives x.ai/session/prompt_complete for its
|
||||
// A viewer in TurnRunning receives kigi/session/prompt_complete for its
|
||||
// session -> finish_turn: state Idle, current_prompt_id cleared.
|
||||
let mut app = make_app_with_agent("sess-view");
|
||||
app.agents.get_mut(&AgentId(0)).unwrap().attached_as_viewer = true;
|
||||
|
||||
@@ -802,7 +802,7 @@
|
||||
meta,
|
||||
};
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
|
||||
)
|
||||
}
|
||||
@@ -882,7 +882,7 @@
|
||||
meta: Some(serde_json::json!({ "isReplay": true, "eventId": "sess-sub-3" })),
|
||||
};
|
||||
let notif = acp::ExtNotification::new(
|
||||
"x.ai/session_notification",
|
||||
"kigi/session_notification",
|
||||
serde_json::value::to_raw_value(&payload).unwrap().into(),
|
||||
);
|
||||
assert!(handle_ext_notification(¬if, &mut app));
|
||||
@@ -996,12 +996,12 @@
|
||||
let id = AgentId(0);
|
||||
{
|
||||
let agent = app.agents.get_mut(&id).unwrap();
|
||||
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
|
||||
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
|
||||
}
|
||||
|
||||
// Unknown model → ignored → both markers untouched.
|
||||
assert!(!handle_ext_notification(
|
||||
&model_changed_ext_with_event("sess-1", "grok-99-unknown", "sess-1-7"),
|
||||
&model_changed_ext_with_event("sess-1", "kigi-99-unknown", "sess-1-7"),
|
||||
&mut app
|
||||
));
|
||||
assert_eq!(
|
||||
@@ -1015,7 +1015,7 @@
|
||||
|
||||
// Known model → applied → both markers advance.
|
||||
assert!(handle_ext_notification(
|
||||
&model_changed_ext_with_event("sess-1", "grok-4", "sess-1-8"),
|
||||
&model_changed_ext_with_event("sess-1", "kigi-4", "sess-1-8"),
|
||||
&mut app
|
||||
));
|
||||
assert_eq!(
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
#[test]
|
||||
fn inject_prompt_drives_even_when_attached_as_viewer() {
|
||||
// The leader routes `x.ai/scheduled_task_inject_prompt` to the SINGLE
|
||||
// The leader routes `kigi/scheduled_task_inject_prompt` to the SINGLE
|
||||
// session driver, so any client that receives it IS the driver and must
|
||||
// enqueue + run it — even one that attached via `session/load`
|
||||
// (`attached_as_viewer == true`). Previously this handler latched on
|
||||
@@ -92,7 +92,7 @@
|
||||
fn inject_prompt_malformed_json_returns_false() {
|
||||
let mut app = make_app_with_agent("sess-1");
|
||||
let raw = serde_json::value::to_raw_value(&"not a json object").unwrap();
|
||||
let notif = acp::ExtNotification::new("x.ai/scheduled_task_inject_prompt", raw.into());
|
||||
let notif = acp::ExtNotification::new("kigi/scheduled_task_inject_prompt", raw.into());
|
||||
|
||||
// The JSON is valid (a string), but sessionId/prompt fields won't exist.
|
||||
let result = handle_scheduled_task_inject_prompt(¬if, &mut app);
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
app.current_ui.permission_mode = Some("ask".into());
|
||||
|
||||
let killswitch = acp::ExtNotification::new(
|
||||
"x.ai/settings/update",
|
||||
"kigi/settings/update",
|
||||
serde_json::value::to_raw_value(
|
||||
&serde_json::json!({ "auto_permission_mode_enabled": false }),
|
||||
)
|
||||
@@ -253,7 +253,7 @@
|
||||
app.agents.get_mut(&AgentId(2)).unwrap().session.yolo_mode = true;
|
||||
|
||||
let killswitch = acp::ExtNotification::new(
|
||||
"x.ai/settings/update",
|
||||
"kigi/settings/update",
|
||||
serde_json::value::to_raw_value(
|
||||
&serde_json::json!({ "auto_permission_mode_enabled": false }),
|
||||
)
|
||||
@@ -272,7 +272,7 @@
|
||||
let mut leave_auto_notifs = 0;
|
||||
while let Ok(msg) = rx.try_recv() {
|
||||
if let kigi_acp_lib::AcpAgentMessage::ExtNotification(args) = msg {
|
||||
if args.request.method.as_ref() != "x.ai/yolo_mode_changed" {
|
||||
if args.request.method.as_ref() != "kigi/yolo_mode_changed" {
|
||||
continue;
|
||||
}
|
||||
let params: serde_json::Value =
|
||||
@@ -302,7 +302,7 @@
|
||||
app.default_yolo = false;
|
||||
|
||||
let apply_yolo = acp::ExtNotification::new(
|
||||
"x.ai/settings/update",
|
||||
"kigi/settings/update",
|
||||
serde_json::value::to_raw_value(&serde_json::json!({
|
||||
"permission_mode": "always-approve",
|
||||
}))
|
||||
@@ -334,7 +334,7 @@
|
||||
app.auto_mode_gate = true;
|
||||
|
||||
let unrelated = acp::ExtNotification::new(
|
||||
"x.ai/settings/update",
|
||||
"kigi/settings/update",
|
||||
serde_json::value::to_raw_value(&serde_json::json!({
|
||||
"show_resolved_model": true,
|
||||
}))
|
||||
@@ -372,7 +372,7 @@
|
||||
app.current_ui.permission_mode = Some("sentinel-not-a-mode".into());
|
||||
|
||||
let push = acp::ExtNotification::new(
|
||||
"x.ai/settings/update",
|
||||
"kigi/settings/update",
|
||||
serde_json::value::to_raw_value(&serde_json::json!({
|
||||
"permission_mode": "always-approve",
|
||||
}))
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression: replay from `updates.jsonl` emits `x.ai/session/update` (not
|
||||
/// Regression: replay from `updates.jsonl` emits `kigi/session/update` (not
|
||||
/// `session_notification`). Subagent lifecycle events must still populate
|
||||
/// `subagent_sessions` and the parent scrollback `SubagentBlock`.
|
||||
#[test]
|
||||
@@ -168,7 +168,7 @@
|
||||
let affected = handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-parent",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
test_subagent_spawned("sess-parent", child_sid),
|
||||
),
|
||||
&mut app,
|
||||
@@ -204,7 +204,7 @@
|
||||
let affected = handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-parent",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
test_subagent_finished(child_sid),
|
||||
),
|
||||
&mut app,
|
||||
@@ -424,7 +424,7 @@
|
||||
let _ = handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-parent",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
test_subagent_finished(child_sid),
|
||||
),
|
||||
&mut app,
|
||||
@@ -674,9 +674,9 @@
|
||||
fn ext_session_notification_and_update_equivalent_for_subagent_spawned() {
|
||||
let child_sid = "child-equiv";
|
||||
let (spawn_notif, finish_notif) =
|
||||
run_subagent_lifecycle_via_method("x.ai/session_notification", child_sid);
|
||||
run_subagent_lifecycle_via_method("kigi/session_notification", child_sid);
|
||||
let (spawn_update, finish_update) =
|
||||
run_subagent_lifecycle_via_method("x.ai/session/update", child_sid);
|
||||
run_subagent_lifecycle_via_method("kigi/session/update", child_sid);
|
||||
|
||||
assert_eq!(spawn_notif.description, spawn_update.description);
|
||||
assert_eq!(spawn_notif.subagent_type, spawn_update.subagent_type);
|
||||
@@ -725,7 +725,7 @@
|
||||
let affected = handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-A",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
test_subagent_spawned("sess-A", child_sid),
|
||||
),
|
||||
&mut app,
|
||||
@@ -757,7 +757,7 @@
|
||||
let affected = handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-A",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
test_subagent_finished(child_sid),
|
||||
),
|
||||
&mut app,
|
||||
@@ -784,7 +784,7 @@
|
||||
let affected = handle(
|
||||
make_ext_session_notification_with_method(
|
||||
"sess-unknown",
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
test_subagent_spawned("sess-unknown", "child-unknown"),
|
||||
),
|
||||
&mut app,
|
||||
@@ -809,7 +809,7 @@
|
||||
// Valid JSON but not a SessionNotification — parse must fail quietly.
|
||||
let raw =
|
||||
serde_json::value::to_raw_value(&serde_json::json!({"unexpected": true})).unwrap();
|
||||
let request = acp::ExtNotification::new("x.ai/session/update", raw.into());
|
||||
let request = acp::ExtNotification::new("kigi/session/update", raw.into());
|
||||
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
|
||||
request,
|
||||
response_tx: tx,
|
||||
@@ -819,7 +819,7 @@
|
||||
|
||||
assert!(
|
||||
!affected,
|
||||
"malformed x.ai/session/update params must not redraw"
|
||||
"malformed kigi/session/update params must not redraw"
|
||||
);
|
||||
assert!(
|
||||
app.agents.get(&AgentId(0)).unwrap().scrollback.is_empty(),
|
||||
|
||||
@@ -971,7 +971,7 @@
|
||||
.remove("will_wake")
|
||||
.expect("the typed builder stamps the field");
|
||||
let legacy = acp::ExtNotification::new(
|
||||
"x.ai/task_completed",
|
||||
"kigi/task_completed",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(&v).unwrap()),
|
||||
);
|
||||
let _ = handle_ext_notification(&legacy, &mut app);
|
||||
|
||||
@@ -179,23 +179,23 @@ pub enum Action {
|
||||
/// Try to drain the next queued prompt (after editing completes, etc.).
|
||||
DrainQueue,
|
||||
/// Remove a server-authoritative (shared) queued prompt by its stable
|
||||
/// `prompt_id`. Routed to the agent as `x.ai/queue/remove`;
|
||||
/// the resulting `x.ai/queue/changed` rebroadcast is the source of truth.
|
||||
/// `prompt_id`. Routed to the agent as `kigi/queue/remove`;
|
||||
/// the resulting `kigi/queue/changed` rebroadcast is the source of truth.
|
||||
QueueRemoveShared {
|
||||
id: String,
|
||||
expected_version: u64,
|
||||
},
|
||||
/// Reorder the server-authoritative (shared) queued prompts to match
|
||||
/// `ordered_ids`. Routed as `x.ai/queue/reorder`.
|
||||
/// `ordered_ids`. Routed as `kigi/queue/reorder`.
|
||||
QueueReorderShared {
|
||||
ordered_ids: Vec<String>,
|
||||
},
|
||||
/// Clear the caller's server-authoritative (shared) queued prompts.
|
||||
/// Routed as `x.ai/queue/clear`.
|
||||
/// Routed as `kigi/queue/clear`.
|
||||
QueueClearShared,
|
||||
/// Replace the text of a server-authoritative (shared) queued prompt.
|
||||
/// Routed to the agent as `x.ai/queue/edit`; the rebroadcast of
|
||||
/// `x.ai/queue/changed` is the source of truth. Last write wins via the
|
||||
/// Routed to the agent as `kigi/queue/edit`; the rebroadcast of
|
||||
/// `kigi/queue/changed` is the source of truth. Last write wins via the
|
||||
/// session actor's serialized mailbox; no client-side conflict resolution.
|
||||
QueueEditShared {
|
||||
id: String,
|
||||
@@ -203,8 +203,8 @@ pub enum Action {
|
||||
},
|
||||
/// Interject a server-authoritative (shared) queued prompt into the running
|
||||
/// turn: the agent atomically removes it from the queue and
|
||||
/// merges its text into the in-flight turn. Routed as `x.ai/queue/interject`;
|
||||
/// the `x.ai/session/interjection` + `x.ai/queue/changed` rebroadcasts are
|
||||
/// merges its text into the in-flight turn. Routed as `kigi/queue/interject`;
|
||||
/// the `kigi/session/interjection` + `kigi/queue/changed` rebroadcasts are
|
||||
/// the source of truth (no optimistic client-side block). Mirrors the local
|
||||
/// "Send now" / `Ctrl+Enter` path, which uses [`Interject`](Self::Interject)
|
||||
/// directly because the local queue is client-owned.
|
||||
@@ -301,7 +301,7 @@ pub enum Action {
|
||||
/// duration. The dispatch handler renders + writes the file and arms
|
||||
/// `AppView::pending_pager_path`; the event loop does the suspend/restore.
|
||||
OpenTranscriptPager,
|
||||
/// Minimal mode (`grok --minimal`): re-print the most-recently committed
|
||||
/// Minimal mode (`kigi --minimal`): re-print the most-recently committed
|
||||
/// folded block (collapsed reasoning / truncated tool output) into native
|
||||
/// scrollback, fully expanded, below the conversation (design decision K10).
|
||||
/// Bound to `Ctrl+E` and the `/expand` command. No-op outside minimal mode
|
||||
@@ -330,12 +330,12 @@ pub enum Action {
|
||||
ExecuteHooksAction(kigi_hooks_plugins_types::HooksAction),
|
||||
/// Execute a plugins management action from the modal.
|
||||
ExecutePluginsAction(kigi_hooks_plugins_types::PluginsAction),
|
||||
/// Add or update an MCP server via x.ai/mcp/upsert.
|
||||
/// Add or update an MCP server via kigi/mcp/upsert.
|
||||
UpsertMcpServer {
|
||||
name: String,
|
||||
config: Box<kigi_shell::util::config::McpServerConfig>,
|
||||
},
|
||||
/// Delete an MCP server via x.ai/mcp/delete.
|
||||
/// Delete an MCP server via kigi/mcp/delete.
|
||||
DeleteMcpServer {
|
||||
server_name: String,
|
||||
},
|
||||
@@ -344,7 +344,7 @@ pub enum Action {
|
||||
server_name: String,
|
||||
enabled: bool,
|
||||
},
|
||||
/// Toggle a skill enable/disable via x.ai/skills/toggle.
|
||||
/// Toggle a skill enable/disable via kigi/skills/toggle.
|
||||
ToggleSkill {
|
||||
skill_name: String,
|
||||
enabled: bool,
|
||||
@@ -373,7 +373,7 @@ pub enum Action {
|
||||
CancelScheduledTask(String),
|
||||
/// Demote the currently running execute tool to a background task.
|
||||
DemoteToBackground,
|
||||
/// Request current bundle cache status via `x.ai/bundle/status`.
|
||||
/// Request current bundle cache status via `kigi/bundle/status`.
|
||||
RequestBundleStatus,
|
||||
/// View a catalog entry's raw content in the block viewer.
|
||||
ViewCatalogEntry {
|
||||
@@ -484,7 +484,7 @@ pub enum Action {
|
||||
SetContextualHintSendNow(bool),
|
||||
SetContextualHintSmallScreen(bool),
|
||||
SetContextualHintWordSelect(bool),
|
||||
/// Commit the active theme (canonical name, e.g. `"groknight"`, `"auto"`).
|
||||
/// Commit the active theme (canonical name, e.g. `"kiginight"`, `"auto"`).
|
||||
SetTheme(String),
|
||||
/// Commit the theme used when the OS is in dark mode. Only updates
|
||||
/// the live display when `theme = "auto"` AND system is in dark mode.
|
||||
@@ -697,7 +697,7 @@ pub enum Action {
|
||||
},
|
||||
/// Persist the memory modal fullscreen preference to config.toml.
|
||||
PersistMemoryFullscreen(bool),
|
||||
/// Open the Agent Dashboard view (`/dashboard`, `Ctrl+\`, `grok dashboard`).
|
||||
/// Open the Agent Dashboard view (`/dashboard`, `Ctrl+\`, `kigi dashboard`).
|
||||
OpenDashboard,
|
||||
/// Close the dashboard, returning to the previous `ActiveView`.
|
||||
ExitDashboard,
|
||||
@@ -913,7 +913,7 @@ pub enum Action {
|
||||
/// Persist-and-notify semantics for [`Effect::PersistPermissionMode`].
|
||||
///
|
||||
/// Both variants write to `~/.kigi/config.toml` and route ACP
|
||||
/// `x.ai/yolo_mode_changed` notifications. The ACP notification is
|
||||
/// `kigi/yolo_mode_changed` notifications. The ACP notification is
|
||||
/// gated on disk-write success when `WithRollback` is used.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PermissionModePersist {
|
||||
@@ -1352,7 +1352,7 @@ pub enum Effect {
|
||||
},
|
||||
/// Fetch session list for the welcome screen session picker.
|
||||
FetchSessionList {
|
||||
/// Text search pushed down to `x.ai/session/list` as `query` (chat
|
||||
/// Text search pushed down to `kigi/session/list` as `query` (chat
|
||||
/// mode: forwarded to the backend conversations search). `None`
|
||||
/// fetches the unfiltered list.
|
||||
query: Option<String>,
|
||||
@@ -1367,11 +1367,11 @@ pub enum Effect {
|
||||
/// against the deep-search seq; chat: server refetch against the list seq).
|
||||
DebounceSessionSearch { query: String, seq: u64 },
|
||||
/// Fetch the leader session roster (FleetView dashboard) via
|
||||
/// `x.ai/sessions/list`. Only issued in leader mode while the
|
||||
/// `kigi/sessions/list`. Only issued in leader mode while the
|
||||
/// dashboard is open.
|
||||
FetchRoster,
|
||||
/// Fetch the local on-disk session list (dormant/idle sessions) for the
|
||||
/// dashboard via `x.ai/session/list` — the non-leader fallback for the
|
||||
/// dashboard via `kigi/session/list` — the non-leader fallback for the
|
||||
/// FleetView roster. Issued while the dashboard is open and NOT in leader
|
||||
/// mode so the dashboard shows idle sessions instead of being empty.
|
||||
FetchDashboardSessions,
|
||||
@@ -1440,7 +1440,7 @@ pub enum Effect {
|
||||
session_id: acp::SessionId,
|
||||
task_id: String,
|
||||
},
|
||||
/// Cancel a subagent via `x.ai/subagent/cancel`.
|
||||
/// Cancel a subagent via `kigi/subagent/cancel`.
|
||||
KillSubagent {
|
||||
session_id: acp::SessionId,
|
||||
subagent_id: String,
|
||||
@@ -1531,31 +1531,31 @@ pub enum Effect {
|
||||
/// Toggle plan mode — fire-and-forget signal to the shell.
|
||||
TogglePlanMode { session_id: acp::SessionId },
|
||||
/// Remove a server-owned queued prompt: fire-and-forget
|
||||
/// `x.ai/queue/remove`. The agent re-broadcasts the authoritative queue.
|
||||
/// `kigi/queue/remove`. The agent re-broadcasts the authoritative queue.
|
||||
QueueRemove {
|
||||
session_id: acp::SessionId,
|
||||
id: String,
|
||||
expected_version: u64,
|
||||
},
|
||||
/// Reorder server-owned queued prompts: fire-and-forget `x.ai/queue/reorder`.
|
||||
/// Reorder server-owned queued prompts: fire-and-forget `kigi/queue/reorder`.
|
||||
QueueReorder {
|
||||
session_id: acp::SessionId,
|
||||
ordered_ids: Vec<String>,
|
||||
},
|
||||
/// Clear the caller's server-owned queued prompts: fire-and-forget
|
||||
/// `x.ai/queue/clear`.
|
||||
/// `kigi/queue/clear`.
|
||||
QueueClear { session_id: acp::SessionId },
|
||||
/// Replace the text of a server-owned queued prompt in place: fire-and-forget
|
||||
/// `x.ai/queue/edit`. The session actor's serialized mailbox makes this
|
||||
/// `kigi/queue/edit`. The session actor's serialized mailbox makes this
|
||||
/// last-writer-wins for concurrent edits; the rebroadcast of
|
||||
/// `x.ai/queue/changed` is the truth signal.
|
||||
/// `kigi/queue/changed` is the truth signal.
|
||||
QueueEdit {
|
||||
session_id: acp::SessionId,
|
||||
id: String,
|
||||
new_text: String,
|
||||
},
|
||||
/// Interject a server-owned queued prompt into the running turn:
|
||||
/// fire-and-forget `x.ai/queue/interject`. The session actor atomically
|
||||
/// fire-and-forget `kigi/queue/interject`. The session actor atomically
|
||||
/// removes it from the queue and merges its text into the in-flight turn,
|
||||
/// then broadcasts both the interjection and the authoritative queue.
|
||||
/// `new_text` (when `Some`, serialized as `newText`) replaces the stored
|
||||
@@ -1595,7 +1595,7 @@ pub enum Effect {
|
||||
cwd: std::path::PathBuf,
|
||||
session_id: String,
|
||||
},
|
||||
/// Resolve the running agent name for a session (`x.ai/session/info`).
|
||||
/// Resolve the running agent name for a session (`kigi/session/info`).
|
||||
FetchSessionAgentName {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
@@ -1611,24 +1611,24 @@ pub enum Effect {
|
||||
PollAuthUrl { request_seq: u64 },
|
||||
/// Submit a manually-pasted auth code (ext request).
|
||||
SubmitAuthCode { request_seq: u64, code: String },
|
||||
/// Fetch MCP server list from the shell (x.ai/mcp/list).
|
||||
/// Fetch MCP server list from the shell (kigi/mcp/list).
|
||||
FetchMcpsList {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
cache: bool,
|
||||
},
|
||||
/// Trigger MCP OAuth for a server (x.ai/mcp/auth_trigger).
|
||||
/// Trigger MCP OAuth for a server (kigi/mcp/auth_trigger).
|
||||
McpAuthTrigger {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
server_name: String,
|
||||
},
|
||||
/// Fetch hooks list from the shell (x.ai/hooks/list).
|
||||
/// Fetch hooks list from the shell (kigi/hooks/list).
|
||||
FetchHooksList {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
},
|
||||
/// Fetch plugins list from the shell (x.ai/plugins/list).
|
||||
/// Fetch plugins list from the shell (kigi/plugins/list).
|
||||
FetchPluginsList {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
@@ -1645,39 +1645,39 @@ pub enum Effect {
|
||||
session_id: acp::SessionId,
|
||||
action: kigi_hooks_plugins_types::PluginsAction,
|
||||
},
|
||||
/// Fetch skills list from the shell (x.ai/skills/list).
|
||||
/// Fetch skills list from the shell (kigi/skills/list).
|
||||
FetchSkillsList {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
},
|
||||
/// Toggle a skill via x.ai/skills/toggle (enable/disable without restart).
|
||||
/// Toggle a skill via kigi/skills/toggle (enable/disable without restart).
|
||||
ToggleSkill {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
skill_name: String,
|
||||
enabled: bool,
|
||||
},
|
||||
/// Upsert an MCP server via x.ai/mcp/upsert.
|
||||
/// Upsert an MCP server via kigi/mcp/upsert.
|
||||
UpsertMcpServer {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
name: String,
|
||||
config: Box<kigi_shell::util::config::McpServerConfig>,
|
||||
},
|
||||
/// Delete an MCP server via x.ai/mcp/delete.
|
||||
/// Delete an MCP server via kigi/mcp/delete.
|
||||
DeleteMcpServer {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
server_name: String,
|
||||
},
|
||||
/// Live-toggle an MCP server via x.ai/mcp/toggle (no restart needed).
|
||||
/// Live-toggle an MCP server via kigi/mcp/toggle (no restart needed).
|
||||
ToggleMcpServer {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
server_name: String,
|
||||
enabled: bool,
|
||||
},
|
||||
/// Toggle a single MCP tool via x.ai/mcp/toggle_tool.
|
||||
/// Toggle a single MCP tool via kigi/mcp/toggle_tool.
|
||||
ToggleMcpTool {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
@@ -1685,20 +1685,20 @@ pub enum Effect {
|
||||
tool_name: String,
|
||||
enabled: bool,
|
||||
},
|
||||
/// Fetch and display session info via x.ai/session/info.
|
||||
/// Fetch and display session info via kigi/session/info.
|
||||
ShowSessionInfo {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
show_resolved_model: bool,
|
||||
},
|
||||
/// Fetch and display detailed context usage via x.ai/session/info.
|
||||
/// Fetch and display detailed context usage via kigi/session/info.
|
||||
ShowContextInfo {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
},
|
||||
/// Fetch current bundle cache status via `x.ai/bundle/status`.
|
||||
/// Fetch current bundle cache status via `kigi/bundle/status`.
|
||||
FetchBundleStatus,
|
||||
/// Fetch a bundled entry's raw content via `x.ai/bundle/entry/get`.
|
||||
/// Fetch a bundled entry's raw content via `kigi/bundle/entry/get`.
|
||||
FetchCatalogEntry { kind: String, name: String },
|
||||
/// Send feedback about the current session (fire-and-forget POST).
|
||||
SendFeedback {
|
||||
@@ -1712,7 +1712,7 @@ pub enum Effect {
|
||||
text: String,
|
||||
cwd: std::path::PathBuf,
|
||||
},
|
||||
/// Send raw note to x.ai/memory/rewrite for LLM-powered reformatting.
|
||||
/// Send raw note to kigi/memory/rewrite for LLM-powered reformatting.
|
||||
/// On success, the rewritten text populates the prompt for inline review.
|
||||
/// On failure, falls back to showing the raw text for review.
|
||||
RewriteMemoryNote {
|
||||
@@ -1733,31 +1733,31 @@ pub enum Effect {
|
||||
agent_id: AgentId,
|
||||
cwd: std::path::PathBuf,
|
||||
},
|
||||
/// Fire a /btw side question via x.ai/btw ext method.
|
||||
/// Fire a /btw side question via kigi/btw ext method.
|
||||
SendBtw {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
question: String,
|
||||
},
|
||||
/// Request a session recap via the x.ai/recap ext method. Fire-and-forget:
|
||||
/// Request a session recap via the kigi/recap ext method. Fire-and-forget:
|
||||
/// the recap arrives later as a `SessionRecap` notification.
|
||||
SendRecap {
|
||||
session_id: acp::SessionId,
|
||||
auto: bool,
|
||||
},
|
||||
/// Send a mid-turn interjection via x.ai/interject ext method.
|
||||
/// Send a mid-turn interjection via kigi/interject ext method.
|
||||
SendInterject {
|
||||
agent_id: AgentId,
|
||||
session_id: acp::SessionId,
|
||||
text: String,
|
||||
/// Client-minted id echoed back on the `x.ai/session/interjection`
|
||||
/// Client-minted id echoed back on the `kigi/session/interjection`
|
||||
/// broadcast so the originator can dedup its optimistic local block.
|
||||
interjection_id: String,
|
||||
/// Structured text + image content blocks. `None` for text-only
|
||||
/// interjections — the wire shape stays byte-identical to legacy.
|
||||
blocks: Option<Vec<acp::ContentBlock>>,
|
||||
},
|
||||
/// Log out via `x.ai/auth/logout` (shell clears auth.json + in-memory state).
|
||||
/// Log out via `kigi/auth/logout` (shell clears auth.json + in-memory state).
|
||||
Logout,
|
||||
/// Log out then authenticate sequentially in one task.
|
||||
SwitchAccount {
|
||||
@@ -1785,7 +1785,7 @@ pub enum Effect {
|
||||
cwd: std::path::PathBuf,
|
||||
},
|
||||
/// Delete a session's stored data (local + remote) via
|
||||
/// `x.ai/session/delete`.
|
||||
/// `kigi/session/delete`.
|
||||
DeleteSession {
|
||||
source: String,
|
||||
session_id: String,
|
||||
@@ -1793,7 +1793,7 @@ pub enum Effect {
|
||||
},
|
||||
/// Deep-search sessions by content (FTS via ACP).
|
||||
DeepSearchSessions { query: String, seq: u64 },
|
||||
/// Call `x.ai/session/fork` to create a peer session that resumes
|
||||
/// Call `kigi/session/fork` to create a peer session that resumes
|
||||
/// from `parent_session_id` in the same cwd (no worktree). Mirror of
|
||||
/// the worktree branch of [`Effect::CreateWorktreeSession`]; the
|
||||
/// worktree-fork path reuses `CreateWorktreeSession { load_session_id }`
|
||||
@@ -1833,14 +1833,14 @@ pub enum Effect {
|
||||
target_prompt_index: usize,
|
||||
mode: crate::views::rewind::RewindMode,
|
||||
},
|
||||
/// Fetch Kimi usage/quota rows from the agent's `x.ai/billing`
|
||||
/// Fetch Kimi usage/quota rows from the agent's `kigi/billing`
|
||||
/// extension (`GET {base}/usages` shell-side) for the `/usage` view.
|
||||
FetchUsage { agent_id: AgentId },
|
||||
/// Spawn a debounce sleep task for shell suggestions. `agent_id` rides
|
||||
/// to the expiry so the fetch is built from the arming agent, not
|
||||
/// whatever view is active when the timer fires.
|
||||
DebounceSuggestions { agent_id: AgentId, generation: u64 },
|
||||
/// Send an ACP `x.ai/suggest` request to the shell. `agent_id` is echoed
|
||||
/// Send an ACP `kigi/suggest` request to the shell. `agent_id` is echoed
|
||||
/// on the result so the response routes to the agent that fetched, not
|
||||
/// whatever view is active when it lands.
|
||||
FetchShellSuggestions {
|
||||
@@ -1857,13 +1857,13 @@ pub enum Effect {
|
||||
/// (path/file); the as-you-type surface keeps all of them.
|
||||
token_only: bool,
|
||||
},
|
||||
/// Send an ACP `x.ai/suggestPrompt` request to the shell — predict the
|
||||
/// Send an ACP `kigi/suggestPrompt` request to the shell — predict the
|
||||
/// user's likely next prompt after a completed turn (tab autocomplete
|
||||
/// ghost text).
|
||||
FetchPromptSuggestion {
|
||||
agent_id: AgentId,
|
||||
generation: u64,
|
||||
/// Suggestion model resolved by the pager (`grok-build-0.1` when the
|
||||
/// Suggestion model resolved by the pager (`kigi-0.1` when the
|
||||
/// catalog offers it); `None` = shell falls back to the session model.
|
||||
model: Option<String>,
|
||||
session_id: Option<String>,
|
||||
@@ -1884,7 +1884,7 @@ pub enum Effect {
|
||||
preparation: crate::prompt_images::PromptImagePreviewPreparation,
|
||||
},
|
||||
}
|
||||
/// Outcome of an `x.ai/subagent/cancel` request, telling dispatch whether the
|
||||
/// Outcome of an `kigi/subagent/cancel` request, telling dispatch whether the
|
||||
/// pager must finalize the subagent row itself.
|
||||
#[derive(Debug)]
|
||||
pub enum SubagentKillOutcome {
|
||||
@@ -1951,7 +1951,7 @@ pub enum TaskResult {
|
||||
restore_summary: Option<String>,
|
||||
restore_degree: Option<kigi_workspace::session::git::RestoreDegree>,
|
||||
/// The session's in-flight running prompt id (from the load response
|
||||
/// `_meta["x.ai/runningPromptId"]`), present only when the session was
|
||||
/// `_meta["kigi/runningPromptId"]`), present only when the session was
|
||||
/// loaded MID-turn (another client is driving). The loader adopts it to
|
||||
/// pass the live `session/update` gate without re-rendering the user
|
||||
/// block (replay already rendered it).
|
||||
@@ -2014,7 +2014,7 @@ pub enum TaskResult {
|
||||
query: String,
|
||||
seq: u64,
|
||||
},
|
||||
/// Leader session roster loaded via `x.ai/sessions/list`.
|
||||
/// Leader session roster loaded via `kigi/sessions/list`.
|
||||
RosterLoaded {
|
||||
sessions: Vec<crate::app::roster::RosterEntry>,
|
||||
},
|
||||
@@ -2085,7 +2085,7 @@ pub enum TaskResult {
|
||||
/// Cancel notification was sent (fire-and-forget).
|
||||
/// The real turn end comes via PromptResponse.
|
||||
CancelComplete,
|
||||
/// Response to `x.ai/subagent/cancel`; see [`SubagentKillOutcome`].
|
||||
/// Response to `kigi/subagent/cancel`; see [`SubagentKillOutcome`].
|
||||
KillSubagentComplete {
|
||||
session_id: acp::SessionId,
|
||||
subagent_id: String,
|
||||
@@ -2155,7 +2155,7 @@ pub enum TaskResult {
|
||||
/// Deprecated: superseded by `mode` (authoritative). Kept only as a
|
||||
/// back-compat fallback for older agents that don't send `mode`.
|
||||
external: bool,
|
||||
/// Presentation mode from `x.ai/auth/get_url`; `None` on older agents.
|
||||
/// Presentation mode from `kigi/auth/get_url`; `None` on older agents.
|
||||
mode: Option<String>,
|
||||
},
|
||||
/// Auth code was submitted (fire-and-forget).
|
||||
@@ -2303,7 +2303,7 @@ pub enum TaskResult {
|
||||
agent_id: AgentId,
|
||||
result: Result<String, String>,
|
||||
},
|
||||
/// `x.ai/recap` request acknowledged (fire-and-forget). The recap itself
|
||||
/// `kigi/recap` request acknowledged (fire-and-forget). The recap itself
|
||||
/// arrives separately as a `SessionRecap` notification; this only carries
|
||||
/// a transport error, if any, for logging.
|
||||
RecapRequested {
|
||||
@@ -2342,7 +2342,7 @@ pub enum TaskResult {
|
||||
results: Vec<kigi_shell::extensions::session_search::SearchSessionHit>,
|
||||
seq: u64,
|
||||
},
|
||||
/// `x.ai/session/fork` completed (no-worktree path). The pager adopts
|
||||
/// `kigi/session/fork` completed (no-worktree path). The pager adopts
|
||||
/// the new session id and emits [`Effect::LoadSession`] to start the
|
||||
/// replay. Mirrors [`TaskResult::WorktreeForked`] in shape.
|
||||
ForkSessionReady {
|
||||
@@ -2350,7 +2350,7 @@ pub enum TaskResult {
|
||||
new_session_id: acp::SessionId,
|
||||
cwd: std::path::PathBuf,
|
||||
},
|
||||
/// `x.ai/session/fork` failed. The placeholder agent stays in
|
||||
/// `kigi/session/fork` failed. The placeholder agent stays in
|
||||
/// `app.agents` with no `session_id` so the user can switch away.
|
||||
ForkSessionFailed {
|
||||
agent_id: AgentId,
|
||||
@@ -2393,7 +2393,7 @@ pub enum TaskResult {
|
||||
agent_id: AgentId,
|
||||
generation: u64,
|
||||
},
|
||||
/// Shell suggestions loaded from ACP `x.ai/suggest`. `request_text` /
|
||||
/// Shell suggestions loaded from ACP `kigi/suggest`. `request_text` /
|
||||
/// `request_cursor` echo what the request was built from — the anchor
|
||||
/// the items' `replaceRange` offsets index into and the position Tab
|
||||
/// targets, paired atomically with them; `agent_id` routes the landing
|
||||
@@ -2404,7 +2404,7 @@ pub enum TaskResult {
|
||||
request_text: String,
|
||||
request_cursor: usize,
|
||||
},
|
||||
/// Predicted next prompt loaded from ACP `x.ai/suggestPrompt`.
|
||||
/// Predicted next prompt loaded from ACP `kigi/suggestPrompt`.
|
||||
/// `suggestion` is `None` when the shell had nothing to suggest.
|
||||
PromptSuggestionLoaded {
|
||||
agent_id: AgentId,
|
||||
|
||||
@@ -133,7 +133,7 @@ pub enum AgentCommand {
|
||||
RestoreCode,
|
||||
/// Forking the current session into a peer (no-worktree path).
|
||||
/// Drives the spinner shown on the placeholder agent while the
|
||||
/// `x.ai/session/fork` request is in flight.
|
||||
/// `kigi/session/fork` request is in flight.
|
||||
ForkSession,
|
||||
}
|
||||
impl AgentCommand {
|
||||
@@ -627,11 +627,11 @@ pub struct AgentSession {
|
||||
/// `yolo_mode` (yolo wins).
|
||||
pub(crate) auto_mode: bool,
|
||||
/// Prompt history for the current session, fetched from ACP
|
||||
/// (`x.ai/prompt_history` scoped via `filter_session_id`). Most-recent-first.
|
||||
/// (`kigi/prompt_history` scoped via `filter_session_id`). Most-recent-first.
|
||||
/// Fetched on session create/load; prompts sent in this session are
|
||||
/// additionally front-inserted locally on send.
|
||||
pub prompt_history: Vec<String>,
|
||||
/// True until the session's startup/load `x.ai/prompt_history` fetch completes.
|
||||
/// True until the session's startup/load `kigi/prompt_history` fetch completes.
|
||||
pub prompt_history_loading: bool,
|
||||
/// Session is currently replaying historical updates from `session/load`.
|
||||
/// Used to suppress live-style redraw/render work until the load completes.
|
||||
|
||||
@@ -52,7 +52,7 @@ impl AgentView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply an `x.ai/follow_ups` notification, keyed by `response_id`
|
||||
/// Apply an `kigi/follow_ups` notification, keyed by `response_id`
|
||||
/// (newest-response-wins).
|
||||
///
|
||||
/// Monotonic accept-the-newer: a never-seen `response_id` is strictly newer
|
||||
@@ -78,7 +78,7 @@ impl AgentView {
|
||||
}
|
||||
|
||||
/// `apply_follow_ups` with the turn identity (`prompt_id`) the shell stamps
|
||||
/// on each `x.ai/follow_ups` notification (the same `promptId` it stamps on
|
||||
/// on each `kigi/follow_ups` notification (the same `promptId` it stamps on
|
||||
/// every `session/update`). The identity makes viewer-adoption dedup
|
||||
/// DETERMINISTIC:
|
||||
///
|
||||
@@ -86,7 +86,7 @@ impl AgentView {
|
||||
/// `prompt_id` equals `session.current_prompt_id`) re-renders even when its
|
||||
/// chips were cleared by turn adoption — so chips that were applied then
|
||||
/// cleared reappear instead of being lost until reload.
|
||||
/// - A buffer-replayed `x.ai/follow_ups` for a PRIOR turn's `response_id`
|
||||
/// - A buffer-replayed `kigi/follow_ups` for a PRIOR turn's `response_id`
|
||||
/// stays rejected by the seen-ring (its `prompt_id` is not the active one),
|
||||
/// so stale chips are never revived on the new turn.
|
||||
///
|
||||
@@ -219,7 +219,7 @@ impl AgentView {
|
||||
true
|
||||
}
|
||||
|
||||
/// Buffer a stamped `x.ai/follow_ups` for a turn that is not yet current,
|
||||
/// Buffer a stamped `kigi/follow_ups` for a turn that is not yet current,
|
||||
/// keyed by its `promptId`. A newer delivery for the same `promptId`
|
||||
/// overwrites the earlier one (keep the latest); the FIFO order list bounds
|
||||
/// the map to [`MAX_PENDING_FOLLOW_UPS`], evicting only the oldest entry.
|
||||
@@ -249,7 +249,7 @@ impl AgentView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush a buffered `x.ai/follow_ups` for `prompt_id` (a turn that has just
|
||||
/// Flush a buffered `kigi/follow_ups` for `prompt_id` (a turn that has just
|
||||
/// become current). Renders the chips through [`apply_follow_ups_with_prompt`]
|
||||
/// — now that `current_prompt_id == prompt_id`, the stamped delivery is
|
||||
/// accepted as the active turn's. Returns whether chips were rendered. A
|
||||
@@ -292,7 +292,7 @@ impl AgentView {
|
||||
|
||||
/// Reload reset that PRESERVES the running turn's follow-ups for
|
||||
/// `keep_prompt_id` (the turn the load is about to adopt). On `SessionLoaded`
|
||||
/// the running turn's `x.ai/follow_ups` arrive on the ext channel DURING
|
||||
/// the running turn's `kigi/follow_ups` arrive on the ext channel DURING
|
||||
/// `loading_replay`; an unconditional reset would drop them before adoption
|
||||
/// could re-render them, so the chips would never appear unless the server
|
||||
/// resent them. The running turn's chips live in ONE of two places at reset
|
||||
|
||||
@@ -1107,7 +1107,7 @@ impl AgentView {
|
||||
self.submit_question_answers(skipped)
|
||||
}
|
||||
fn submit_question_answers(&mut self, skipped: bool) -> InputOutcome {
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionExtResponse;
|
||||
use kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionExtResponse;
|
||||
self.swap_question_freeform();
|
||||
let Some(mut qv) = self.question_view.take() else {
|
||||
return InputOutcome::Changed;
|
||||
@@ -1594,7 +1594,7 @@ mod permission_scope_key_tests {
|
||||
#[cfg(test)]
|
||||
mod question_no_freeform_tests {
|
||||
//! Freeform ("Other") gating for `no_freeform` question modals — e.g.
|
||||
//! the SuperGrok upsell. Regression tests for the bug where clicking
|
||||
//! the subscription upsell. Regression tests for the bug where clicking
|
||||
//! under the last option of the upsell selected the (hidden) freeform
|
||||
//! row and let the user type into a modal that offers no free text.
|
||||
use super::super::test_fixtures::make_agent;
|
||||
@@ -1605,7 +1605,7 @@ mod question_no_freeform_tests {
|
||||
use crossterm::event::{
|
||||
KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
|
||||
};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
/// Fixed options, single-select — shaped like the free-usage upsell.
|
||||
|
||||
@@ -200,7 +200,7 @@ impl McpInitProgress {
|
||||
/// Whether the progress indicator should be visible in the UI.
|
||||
///
|
||||
/// - `total > 0` (real servers): always visible until
|
||||
/// `x.ai/mcp_initialized` clears the progress.
|
||||
/// `kigi/mcp_initialized` clears the progress.
|
||||
/// - `total == 0` (seed / 0-server): visible for at most
|
||||
/// [`SEED_EXPIRE`] seconds, then auto-expires as
|
||||
/// defense-in-depth against the shell failing to send
|
||||
@@ -559,7 +559,7 @@ pub(crate) struct SessionReload {
|
||||
saw_todo_update: bool,
|
||||
}
|
||||
/// Follow-up suggestion chips for the latest assistant response
|
||||
/// (`x.ai/follow_ups`). Streaming-only: never persisted, does not survive a
|
||||
/// (`kigi/follow_ups`). Streaming-only: never persisted, does not survive a
|
||||
/// session reload. Keyed by the assistant `response_id` (the newest-wins key).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct FollowUps {
|
||||
@@ -799,7 +799,7 @@ pub struct AgentView {
|
||||
/// answering questions via `AskUserQuestion`). Reset when the turn ends.
|
||||
pub turn_paused_duration: std::time::Duration,
|
||||
/// IDs of interjections this client sent and already rendered locally
|
||||
/// (optimistic echo). The shell broadcasts `x.ai/session/interjection` to
|
||||
/// (optimistic echo). The shell broadcasts `kigi/session/interjection` to
|
||||
/// every attached pane; when our own broadcast echoes back carrying an id
|
||||
/// in this set, `handle_interjection` drops it (we already showed it) and
|
||||
/// removes the id. Other panes (which lack the id) render it. This is the
|
||||
@@ -1133,7 +1133,7 @@ pub struct AgentView {
|
||||
/// (`When::DashboardOverlay`) are lit in the overlay and dimmed elsewhere.
|
||||
pub(crate) in_dashboard_overlay: bool,
|
||||
/// MCP server init progress. Set when the shell starts connecting
|
||||
/// MCP servers, cleared when `x.ai/mcp_initialized` arrives.
|
||||
/// MCP servers, cleared when `kigi/mcp_initialized` arrives.
|
||||
/// Shown in the turn status line while the agent is idle.
|
||||
pub(crate) mcp_init_progress: Option<McpInitProgress>,
|
||||
/// Last synced ACP command generation. When this differs from
|
||||
@@ -1206,7 +1206,7 @@ pub struct AgentView {
|
||||
/// lands on a tick; borrowed during render so streaming redraws don't
|
||||
/// rescan/allocate the prompt every frame.
|
||||
pub(crate) timeline_hover_preview: Option<(usize, String)>,
|
||||
/// Running agent definition for this session (`x.ai/session/info` `agentName`).
|
||||
/// Running agent definition for this session (`kigi/session/info` `agentName`).
|
||||
pub session_agent_name: Option<String>,
|
||||
/// Map of child session IDs to subagent metadata. Populated on
|
||||
/// `SubagentSpawned` notifications, used for permission routing
|
||||
@@ -1286,7 +1286,7 @@ pub struct AgentView {
|
||||
/// complete. Kind-only: the payload is re-derived from the widget on
|
||||
/// reissue so the freshly attached image chip travels with it.
|
||||
pub(crate) deferred_send: Option<AgentDeferredSend>,
|
||||
/// Armed when an `x.ai/session/prompt_complete` broadcast arrives for the
|
||||
/// Armed when an `kigi/session/prompt_complete` broadcast arrives for the
|
||||
/// turn THIS client drives while it is still awaiting that turn's
|
||||
/// `session/prompt` RPC response. The RPC normally lands milliseconds
|
||||
/// later and disarms this; if it never does (lost in leader response
|
||||
@@ -1317,17 +1317,17 @@ pub struct AgentView {
|
||||
pub(crate) follow_without_jump_prompt_id: Option<String>,
|
||||
/// Ids of THIS client's server-queue rows that are still optimistic
|
||||
/// echoes — the `session/prompt` RPC is in flight and no
|
||||
/// `x.ai/queue/changed` broadcast has confirmed the row yet. Inserted by
|
||||
/// `kigi/queue/changed` broadcast has confirmed the row yet. Inserted by
|
||||
/// the echo push, drained when a broadcast lists the id (queued or
|
||||
/// running) or the RPC resolves without the row landing.
|
||||
pub(crate) optimistic_queue_ids: std::collections::HashSet<String>,
|
||||
/// A queue-row send-now the user fired while the row was still an
|
||||
/// optimistic echo. Firing `x.ai/queue/interject` then would race the
|
||||
/// optimistic echo. Firing `kigi/queue/interject` then would race the
|
||||
/// row's own in-flight `session/prompt` and silently no-op shell-side
|
||||
/// (a rapid double-Enter on a queued bash command could "disappear" — the
|
||||
/// interject overtook the row, the no-op dropped the send-now, and the
|
||||
/// armed cancel expectation hid the still-queued row).
|
||||
/// Parked here and fired from the confirming `x.ai/queue/changed`
|
||||
/// Parked here and fired from the confirming `kigi/queue/changed`
|
||||
/// broadcast with the row's authoritative version.
|
||||
pub(crate) send_now_awaiting_confirm: Option<String>,
|
||||
/// User blocks painted at send-now dispatch, keyed by prompt id; the
|
||||
@@ -1337,7 +1337,7 @@ pub struct AgentView {
|
||||
pub(crate) send_now_painted_blocks:
|
||||
std::collections::HashMap<String, (crate::scrollback::EntryId, bool)>,
|
||||
/// Follow-up suggestion chips for the latest assistant response
|
||||
/// (`x.ai/follow_ups`). `None` when no chips are shown. Set by
|
||||
/// (`kigi/follow_ups`). `None` when no chips are shown. Set by
|
||||
/// [`AgentView::apply_follow_ups`]; cleared at each turn start.
|
||||
pub(crate) follow_ups: Option<FollowUps>,
|
||||
/// `promptId` (turn identity) of the currently-shown `follow_ups`, when the
|
||||
@@ -1371,7 +1371,7 @@ pub struct AgentView {
|
||||
/// The ordering key for newest-wins: a fresh id takes the next value (the
|
||||
/// new high-water), so every previously-seen id is strictly lower.
|
||||
pub(crate) follow_up_next_gen: u64,
|
||||
/// Stamped `x.ai/follow_ups` that arrived for a turn that is NOT yet the
|
||||
/// Stamped `kigi/follow_ups` that arrived for a turn that is NOT yet the
|
||||
/// currently-adopted one, keyed by `promptId`. Ext notifications and
|
||||
/// `session/update` travel on separate channels, so a turn's follow_ups can
|
||||
/// land BEFORE the `session/update` that adopts it. Rather than drop such a
|
||||
@@ -1891,7 +1891,7 @@ fn resolve_action(action_id: Option<ActionId>) -> Option<InputOutcome> {
|
||||
fn question_visible_h(
|
||||
scroll_region: Option<(u16, u16)>,
|
||||
prompt_height: u16,
|
||||
question: &kigi_tools::implementations::grok_build::ask_user_question::Question,
|
||||
question: &kigi_tools::implementations::kigi::ask_user_question::Question,
|
||||
content_w: usize,
|
||||
preview: Option<&str>,
|
||||
fullscreen: bool,
|
||||
@@ -2625,7 +2625,7 @@ pub(super) mod test_fixtures {
|
||||
);
|
||||
assert_eq!(agent.follow_ups.as_ref().unwrap().suggestions, vec!["a"]);
|
||||
}
|
||||
/// FIX 4 (b): after adopting a NEW turn, a buffer-replayed `x.ai/follow_ups`
|
||||
/// FIX 4 (b): after adopting a NEW turn, a buffer-replayed `kigi/follow_ups`
|
||||
/// for a PRIOR turn's response_id must NOT revive stale chips — its
|
||||
/// `promptId` is not the active turn and it is already in the seen ring.
|
||||
#[test]
|
||||
@@ -2646,7 +2646,7 @@ pub(super) mod test_fixtures {
|
||||
assert!(agent.apply_follow_ups_with_prompt("resp-2".into(), Some("p2"), vec!["b".into()]));
|
||||
assert_eq!(agent.follow_ups.as_ref().unwrap().response_id, "resp-2");
|
||||
}
|
||||
/// FINDING B (stamped path): a LATE FIRST-TIME (never-seen) `x.ai/follow_ups`
|
||||
/// FINDING B (stamped path): a LATE FIRST-TIME (never-seen) `kigi/follow_ups`
|
||||
/// for a PRIOR turn — arriving while a newer turn is active — must NOT
|
||||
/// render. Before the fix it slipped through the "strictly newer" branch
|
||||
/// (never recorded in `follow_up_seen`, so the seen-reject didn't catch it).
|
||||
@@ -2702,7 +2702,7 @@ pub(super) mod test_fixtures {
|
||||
/// distinguished from the new turn's first follow_ups, so it follows the
|
||||
/// legacy newest-wins (renders). This path is not reachable for current
|
||||
/// shells (which always stamp `promptId`) or for buffer-replays (suppressed
|
||||
/// upstream by the `_meta["x.ai/replayed"]` gate); it is pinned here so the
|
||||
/// upstream by the `_meta["kigi/replayed"]` gate); it is pinned here so the
|
||||
/// stamped-path fix above is understood to be the deterministic guard.
|
||||
#[test]
|
||||
fn apply_follow_ups_none_prompt_first_time_follows_legacy_newest_wins() {
|
||||
@@ -2714,7 +2714,7 @@ pub(super) mod test_fixtures {
|
||||
);
|
||||
assert_eq!(agent.follow_ups.as_ref().unwrap().response_id, "resp-x");
|
||||
}
|
||||
/// FIX (buffer-before-adoption): a stamped `x.ai/follow_ups` for a turn that
|
||||
/// FIX (buffer-before-adoption): a stamped `kigi/follow_ups` for a turn that
|
||||
/// is NOT yet current (its `session/update` adoption raced behind the ext
|
||||
/// channel) must be BUFFERED, not dropped — and then RENDER when that turn
|
||||
/// becomes current and is flushed.
|
||||
|
||||
@@ -1180,10 +1180,10 @@ pub(super) mod paste_key_tests {
|
||||
/// Build a `QuestionViewState` already in `InputMode` focus.
|
||||
pub(in crate::app::agent_view) fn make_question_view_state_in_input_mode()
|
||||
-> crate::views::question_view::QuestionViewState {
|
||||
let question = kigi_tools::implementations::grok_build::ask_user_question::Question {
|
||||
let question = kigi_tools::implementations::kigi::ask_user_question::Question {
|
||||
question: "Pick one?".to_string(),
|
||||
options: vec![
|
||||
kigi_tools::implementations::grok_build::ask_user_question::QuestionOption {
|
||||
kigi_tools::implementations::kigi::ask_user_question::QuestionOption {
|
||||
label: "A".to_string(),
|
||||
description: "Option A".to_string(),
|
||||
preview: None,
|
||||
|
||||
@@ -1412,7 +1412,7 @@ mod prompt_suggestion_key_tests {
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
|
||||
/// Idle agent with the gate open and a loaded suggestion — the state
|
||||
/// right after a turn ends with `x.ai/suggestPrompt` resolved. Pins the
|
||||
/// right after a turn ends with `kigi/suggestPrompt` resolved. Pins the
|
||||
/// settings cache so `resolve_enabled()` never reads the dev machine's
|
||||
/// config.toml (thread-local, so per-test).
|
||||
fn suggestion_agent(text: &str) -> AgentView {
|
||||
|
||||
@@ -530,7 +530,7 @@ impl AgentView {
|
||||
Some(crate::views::queue_pane::QueueRowOrigin::Server)
|
||||
);
|
||||
if is_server {
|
||||
// Server row: the agent promotes it to run next (`x.ai/queue/interject`); any kind may send now.
|
||||
// Server row: the agent promotes it to run next (`kigi/queue/interject`); any kind may send now.
|
||||
if let Some(row) = row.as_ref()
|
||||
&& let Some(server_id) = row.server_id.clone()
|
||||
{
|
||||
@@ -538,7 +538,7 @@ impl AgentView {
|
||||
// flight, so an interject fired now would overtake the row
|
||||
// shell-side and silently no-op (dropping the send-now and
|
||||
// hiding the row behind the armed cancel expectation). Park
|
||||
// the intent; the confirming `x.ai/queue/changed` broadcast
|
||||
// the intent; the confirming `kigi/queue/changed` broadcast
|
||||
// fires it with the row's authoritative version (see
|
||||
// `resolve_send_now_awaiting_confirm`).
|
||||
if self.optimistic_queue_ids.contains(&server_id) {
|
||||
@@ -568,13 +568,13 @@ impl AgentView {
|
||||
}
|
||||
|
||||
/// Reconcile this client's optimistic queue echoes against a raw
|
||||
/// `x.ai/queue/changed` broadcast (pre-merge entries — the mirrored
|
||||
/// `kigi/queue/changed` broadcast (pre-merge entries — the mirrored
|
||||
/// snapshot re-pins unconfirmed echoes, so it can't tell confirmation
|
||||
/// apart), and resolve a parked queue-row send-now
|
||||
/// ([`Self::send_now_awaiting_confirm`]).
|
||||
///
|
||||
/// Returns `Some((id, version))` when the parked row is now confirmed as
|
||||
/// QUEUED — the caller fires `x.ai/queue/interject` with that
|
||||
/// QUEUED — the caller fires `kigi/queue/interject` with that
|
||||
/// authoritative version. A parked row confirmed as RUNNING clears the
|
||||
/// park with nothing to do (the natural drain won the race). A row in
|
||||
/// neither set stays parked (its RPC is still in flight).
|
||||
@@ -715,7 +715,7 @@ impl AgentView {
|
||||
// Queue-specific actions (delete, edit, reorder). `x`/Delete = row delete.
|
||||
if let Some(event) = self.queue.handle_key(key, registry) {
|
||||
// Resolve the selected row's origin so edits route correctly:
|
||||
// Server-origin rows go to the agent as `x.ai/queue/*`
|
||||
// Server-origin rows go to the agent as `kigi/queue/*`
|
||||
// commands (the rebroadcast is the source of truth); Local rows
|
||||
// keep today's in-place mutation.
|
||||
let row = self.queue.row_ref(Self::queue_event_id(&event));
|
||||
@@ -822,7 +822,7 @@ impl AgentView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reorder payload for `x.ai/queue/reorder`. Omit only running; include
|
||||
/// Reorder payload for `kigi/queue/reorder`. Omit only running; include
|
||||
/// send-now in the list but do not swap past it (shell ranks missing ids last).
|
||||
fn server_queue_reordered(&self, selection_id: u64, up: bool) -> Option<Vec<String>> {
|
||||
let server_id = self.queue.row_ref(selection_id)?.server_id?;
|
||||
|
||||
@@ -1325,7 +1325,7 @@ mod tests {
|
||||
crate::scrollback::text_selection::ResolvedSelectionBoundaries::default();
|
||||
for (entry_idx, text, hit_col, prefix, suffix, expected) in [
|
||||
(0, "foo rest", 0, " ", "", "foo"),
|
||||
(1, "rest https://x.ai", 5, "", " ", "https://x.ai"),
|
||||
(1, "rest https://kimi.com", 5, "", " ", "https://kimi.com"),
|
||||
] {
|
||||
let line = ResolvedSelectableLine {
|
||||
entry_idx,
|
||||
|
||||
@@ -397,7 +397,7 @@ impl AgentView {
|
||||
self.session.start_turn(&mut self.scrollback);
|
||||
}
|
||||
/// Adopt the in-flight turn another client is driving, conveyed by the
|
||||
/// `session/load` response meta (`x.ai/runningPromptId`): enter
|
||||
/// `session/load` response meta (`kigi/runningPromptId`): enter
|
||||
/// TurnRunning and match subsequent live deltas. No user-prompt block is
|
||||
/// pushed — the turn's prompt and prior chunks arrived via the replay.
|
||||
pub(crate) fn adopt_running_prompt(&mut self, prompt_id: String) {
|
||||
|
||||
@@ -506,7 +506,7 @@ mod shell_suggestion_key_tests {
|
||||
let mut agent = bash_agent("ls | gr");
|
||||
agent.prompt.suggestions.dropdown.items = vec![
|
||||
item("grep", None),
|
||||
file_item("ls | grokfile", "grokfile", 5..7),
|
||||
file_item("ls | kigifile", "kigifile", 5..7),
|
||||
];
|
||||
|
||||
let outcome = agent.handle_prompt_key_for_test(&key(KeyCode::Tab));
|
||||
|
||||
@@ -501,19 +501,19 @@ pub struct AppView {
|
||||
/// (`team_name.is_some()`) and API-key auth.
|
||||
pub usage_visible: bool,
|
||||
/// Whether the pager is connected via a leader (leader mode). The Agent
|
||||
/// Dashboard entry points (`/dashboard`, `Ctrl+\`, `grok dashboard`, the
|
||||
/// Dashboard entry points (`/dashboard`, `Ctrl+\`, `kigi dashboard`, the
|
||||
/// startup hook) are only meaningful when a leader is coordinating a
|
||||
/// fleet of sessions, so they are gated on this flag. Set in
|
||||
/// `event_loop::run` from `connection.leader_status_rx.is_some()`;
|
||||
/// defaults to `false` (non-leader, dashboard hidden).
|
||||
pub leader_mode: bool,
|
||||
/// Leader-mode session roster (FleetView dashboard). Populated from
|
||||
/// `x.ai/sessions/list` polls and `x.ai/sessions/changed` broadcasts.
|
||||
/// `kigi/sessions/list` polls and `kigi/sessions/changed` broadcasts.
|
||||
/// Empty in non-leader mode, which naturally gates roster rendering.
|
||||
pub leader_roster: Vec<crate::app::roster::RosterEntry>,
|
||||
/// Local on-disk session list (dormant/idle sessions) surfaced on the
|
||||
/// dashboard when NOT in leader mode. There is no live leader roster to
|
||||
/// poll outside leader mode, so we fetch the same `x.ai/session/list` the
|
||||
/// poll outside leader mode, so we fetch the same `kigi/session/list` the
|
||||
/// resume picker uses and render those as idle rows. Entries are stored as
|
||||
/// [`crate::app::roster::RosterEntry`] (activity `Dormant`) so they reuse
|
||||
/// the existing roster-row rendering / attach path. Empty in leader mode.
|
||||
@@ -521,14 +521,14 @@ pub struct AppView {
|
||||
/// Whether the dashboard is currently loading local sessions (non-leader mode).
|
||||
pub dashboard_sessions_loading: bool,
|
||||
/// Server-authoritative shared prompt queues, keyed by `sessionId`
|
||||
/// Reconciled from `x.ai/queue/changed` broadcasts so
|
||||
/// Reconciled from `kigi/queue/changed` broadcasts so
|
||||
/// every client renders the same ordered queue (including prompts queued
|
||||
/// by other clients). Empty in non-leader mode.
|
||||
pub shared_prompt_queues:
|
||||
std::collections::HashMap<String, Vec<crate::app::prompt_queue::QueueEntryWire>>,
|
||||
/// Optimistic echo rows for prompts the pager sent server-authoritatively
|
||||
/// (plain prompt typed while a turn is running) but for which the
|
||||
/// confirming `x.ai/queue/changed` broadcast has not yet arrived. Keyed by
|
||||
/// confirming `kigi/queue/changed` broadcast has not yet arrived. Keyed by
|
||||
/// `sessionId`. Pinned into `shared_prompt_queues` on reconcile so the row
|
||||
/// doesn't flicker, and dropped once the authoritative broadcast reflects
|
||||
/// the id (or it starts running). Never persisted.
|
||||
@@ -551,7 +551,7 @@ pub struct AppView {
|
||||
pub cancel_rewind_enabled: bool,
|
||||
/// Whether session recap (`/recap` + automatic away recap) is rolled out,
|
||||
/// resolved by the shell and advertised on ACP initialize (`sessionRecap`).
|
||||
/// When false, the pager must not request recaps (zero `x.ai/recap` traffic).
|
||||
/// When false, the pager must not request recaps (zero `kigi/recap` traffic).
|
||||
pub session_recap_available: bool,
|
||||
/// Stateful prompt widget rendered on the welcome screen (persists input across frames).
|
||||
pub welcome_prompt: PromptWidget,
|
||||
@@ -715,7 +715,7 @@ pub struct AppView {
|
||||
/// Automatically enabled by `plan_mode`.
|
||||
pub ask_user: bool,
|
||||
/// Process-wide gateway light-frontend from CLI `--chat` only.
|
||||
/// Stamps `_meta["x.ai/session"].kind = "chat"` and omits Build agent
|
||||
/// Stamps `_meta["kigi/session"].kind = "chat"` and omits Build agent
|
||||
/// profiles on create/load while set. `/chat` does **not** set this
|
||||
/// (uses [`Self::deferred_startup`] one-shot state instead).
|
||||
pub chat_mode: bool,
|
||||
@@ -776,7 +776,7 @@ pub struct AppView {
|
||||
/// when `Pending`, the welcome screen shows the trust question and session
|
||||
/// creation is deferred (gated after auth) until it is answered.
|
||||
pub trust_state: TrustState,
|
||||
/// Login button label from `AuthMethod.name` (e.g., "grok.com", "Acme Corp").
|
||||
/// Login button label from `AuthMethod.name` (e.g., "kimi-code", "Acme Corp").
|
||||
pub login_label: Option<String>,
|
||||
/// The auth method ID to use for login.
|
||||
pub login_method_id: Option<acp::AuthMethodId>,
|
||||
@@ -1125,7 +1125,7 @@ impl AppView {
|
||||
}
|
||||
}
|
||||
/// Reconcile the shared prompt queue for a session from a
|
||||
/// `x.ai/queue/changed` broadcast. The broadcast is
|
||||
/// `kigi/queue/changed` broadcast. The broadcast is
|
||||
/// authoritative: it fully replaces the previously-known queue for that
|
||||
/// session. An empty list clears the entry.
|
||||
///
|
||||
@@ -1194,7 +1194,7 @@ impl AppView {
|
||||
/// Push an optimistic echo row for a server-authoritative prompt the pager
|
||||
/// just sent (a plain prompt or agent-bound kind typed while a turn is
|
||||
/// running). The row is keyed by `prompt_id` so the authoritative
|
||||
/// `x.ai/queue/changed` broadcast replaces it (matched by `id`) rather than
|
||||
/// `kigi/queue/changed` broadcast replaces it (matched by `id`) rather than
|
||||
/// duplicating it. `kind` (`"prompt"`/`"bash"`/…) drives the row's display
|
||||
/// and, on adoption, the turn-start shim's block + focus flag.
|
||||
pub fn push_optimistic_prompt_echo(
|
||||
@@ -8264,9 +8264,7 @@ pub(crate) mod tests {
|
||||
n_questions: usize,
|
||||
) {
|
||||
use crate::views::question_view::QuestionViewState;
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{
|
||||
Question, QuestionOption,
|
||||
};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let questions: Vec<Question> = (0..n_questions)
|
||||
.map(|i| Question {
|
||||
question: format!("Q{i}?"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Bundle status state and response types.
|
||||
//!
|
||||
//! Pager-side cache of what `kigi-shell` reports from
|
||||
//! `x.ai/bundle/status`. The shell now performs the actual bundle download in
|
||||
//! `kigi/bundle/status`. The shell now performs the actual bundle download in
|
||||
//! the background post-auth; the pager only reads the resulting on-disk
|
||||
//! catalog so it can populate the welcome-screen subagent pane.
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde::Deserialize;
|
||||
|
||||
/// Pager-local snapshot of bundle availability on disk.
|
||||
///
|
||||
/// Populated from `x.ai/bundle/status` ACP responses.
|
||||
/// Populated from `kigi/bundle/status` ACP responses.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct BundleState {
|
||||
pub has_cache: bool,
|
||||
@@ -22,7 +22,7 @@ pub struct BundleState {
|
||||
pub role_details: Vec<RoleDetail>,
|
||||
}
|
||||
|
||||
/// Deserialized response from `x.ai/bundle/status`.
|
||||
/// Deserialized response from `kigi/bundle/status`.
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BundleStatusResult {
|
||||
@@ -62,7 +62,7 @@ pub struct RoleDetail {
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
/// Deserialized response from `x.ai/bundle/entry/get`.
|
||||
/// Deserialized response from `kigi/bundle/entry/get`.
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EntryGetResult {
|
||||
|
||||
@@ -226,13 +226,13 @@ impl AgentArgs {
|
||||
Ok(canonical) if canonical.is_dir() => Some(canonical),
|
||||
Ok(_) => {
|
||||
eprintln!(
|
||||
"grok: --plugin-dir {}: not a directory; skipping",
|
||||
"kigi: --plugin-dir {}: not a directory; skipping",
|
||||
p.display()
|
||||
);
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("grok: --plugin-dir {}: {e}; skipping", p.display());
|
||||
eprintln!("kigi: --plugin-dir {}: {e}; skipping", p.display());
|
||||
None
|
||||
}
|
||||
})
|
||||
@@ -292,11 +292,16 @@ pub struct LeaderArgs {
|
||||
fn version_with_channel() -> &'static str {
|
||||
use std::sync::OnceLock;
|
||||
static V: OnceLock<String> = OnceLock::new();
|
||||
// Required upstream attribution for `--version` output (PRD: the
|
||||
// "Based on … Open Source" note must survive the rebrand). Kept in a
|
||||
// text asset so the release-gate grep over Rust sources stays clean.
|
||||
const VERSION_ATTRIBUTION: &str = include_str!("version_attribution.txt");
|
||||
V.get_or_init(|| {
|
||||
let label = kigi_update::channel_label();
|
||||
format!(
|
||||
"{} — unofficial Kimi Code CLI community build - Based on Grok Build Open Source",
|
||||
kigi_version::display_version_with_commit(env!("VERSION_WITH_COMMIT"), label)
|
||||
"{} — unofficial Kimi Code CLI community build - {}",
|
||||
kigi_version::display_version_with_commit(env!("VERSION_WITH_COMMIT"), label),
|
||||
VERSION_ATTRIBUTION.trim_end(),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -806,7 +811,7 @@ mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn version_flag_exits_zero() {
|
||||
let err = PagerArgs::try_parse_from(["grok", "--version"]).unwrap_err();
|
||||
let err = PagerArgs::try_parse_from(["kigi", "--version"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
|
||||
assert!(
|
||||
err.exit_code() == 0,
|
||||
@@ -816,7 +821,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn version_short_flag_exits_zero() {
|
||||
let err = PagerArgs::try_parse_from(["grok", "-v"]).unwrap_err();
|
||||
let err = PagerArgs::try_parse_from(["kigi", "-v"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
|
||||
assert!(
|
||||
err.exit_code() == 0,
|
||||
@@ -827,35 +832,35 @@ mod tests {
|
||||
#[test]
|
||||
fn resume_target_classifies_flags() {
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok"]).unwrap().resume_target(),
|
||||
PagerArgs::try_parse_from(["kigi"]).unwrap().resume_target(),
|
||||
ResumeTarget::None
|
||||
);
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok", "-c"])
|
||||
PagerArgs::try_parse_from(["kigi", "-c"])
|
||||
.unwrap()
|
||||
.resume_target(),
|
||||
ResumeTarget::MostRecentForCwd
|
||||
);
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok", "--resume"])
|
||||
PagerArgs::try_parse_from(["kigi", "--resume"])
|
||||
.unwrap()
|
||||
.resume_target(),
|
||||
ResumeTarget::MostRecentForCwd
|
||||
);
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok", "--resume", "sess-1"])
|
||||
PagerArgs::try_parse_from(["kigi", "--resume", "sess-1"])
|
||||
.unwrap()
|
||||
.resume_target(),
|
||||
ResumeTarget::SessionId("sess-1".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok", "-s", "sess-2"])
|
||||
PagerArgs::try_parse_from(["kigi", "-s", "sess-2"])
|
||||
.unwrap()
|
||||
.resume_target(),
|
||||
ResumeTarget::None
|
||||
);
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok", "-r", "old", "--fork-session"])
|
||||
PagerArgs::try_parse_from(["kigi", "-r", "old", "--fork-session"])
|
||||
.unwrap()
|
||||
.resume_target(),
|
||||
ResumeTarget::SessionId("old".to_string())
|
||||
@@ -866,11 +871,11 @@ mod tests {
|
||||
/// invocation would be ambiguous.
|
||||
#[test]
|
||||
fn minimal_and_fullscreen_flags_conflict() {
|
||||
let args = PagerArgs::try_parse_from(["grok", "--minimal"]).unwrap();
|
||||
let args = PagerArgs::try_parse_from(["kigi", "--minimal"]).unwrap();
|
||||
assert!(args.minimal && !args.fullscreen);
|
||||
let args = PagerArgs::try_parse_from(["grok", "--fullscreen"]).unwrap();
|
||||
let args = PagerArgs::try_parse_from(["kigi", "--fullscreen"]).unwrap();
|
||||
assert!(args.fullscreen && !args.minimal);
|
||||
let err = PagerArgs::try_parse_from(["grok", "--minimal", "--fullscreen"]).unwrap_err();
|
||||
let err = PagerArgs::try_parse_from(["kigi", "--minimal", "--fullscreen"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
/// kimi-cli parity (F6): bare `kigi acp` runs the stdio ACP server, and
|
||||
@@ -906,7 +911,7 @@ mod tests {
|
||||
std::fs::write(&file, "x").unwrap();
|
||||
let missing = tmp.path().join("missing");
|
||||
let args = PagerArgs::try_parse_from([
|
||||
"grok".as_ref(),
|
||||
"kigi".as_ref(),
|
||||
"agent".as_ref(),
|
||||
"--no-leader".as_ref(),
|
||||
"--plugin-dir".as_ref(),
|
||||
@@ -964,19 +969,19 @@ mod tests {
|
||||
#[test]
|
||||
fn startup_sandbox_profile_no_resume() {
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok", "--sandbox", "strict"])
|
||||
PagerArgs::try_parse_from(["kigi", "--sandbox", "strict"])
|
||||
.unwrap()
|
||||
.startup_sandbox_profile(None),
|
||||
SandboxStartup::Apply(Some("strict".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok", "--sandbox", ""])
|
||||
PagerArgs::try_parse_from(["kigi", "--sandbox", ""])
|
||||
.unwrap()
|
||||
.startup_sandbox_profile(None),
|
||||
SandboxStartup::Apply(None)
|
||||
);
|
||||
assert_eq!(
|
||||
PagerArgs::try_parse_from(["grok"])
|
||||
PagerArgs::try_parse_from(["kigi"])
|
||||
.unwrap()
|
||||
.startup_sandbox_profile(None),
|
||||
SandboxStartup::Apply(None)
|
||||
@@ -984,7 +989,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn leader_socket_flag_parses_at_root() {
|
||||
let args = PagerArgs::try_parse_from(["grok", "--leader-socket", "/tmp/leader-x.sock"])
|
||||
let args = PagerArgs::try_parse_from(["kigi", "--leader-socket", "/tmp/leader-x.sock"])
|
||||
.expect("--leader-socket parses at the root");
|
||||
assert_eq!(
|
||||
args.leader_socket.as_deref(),
|
||||
@@ -994,7 +999,7 @@ mod tests {
|
||||
#[test]
|
||||
fn leader_socket_flag_is_global_for_subcommands() {
|
||||
let args = PagerArgs::try_parse_from([
|
||||
"grok",
|
||||
"kigi",
|
||||
"agent",
|
||||
"leader",
|
||||
"--leader-socket",
|
||||
@@ -1008,21 +1013,21 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn leader_socket_flag_defaults_to_none() {
|
||||
let args = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
|
||||
let args = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
|
||||
assert!(args.leader_socket.is_none());
|
||||
}
|
||||
#[test]
|
||||
fn leader_mgmt_list_info_kill_parse() {
|
||||
let list = PagerArgs::try_parse_from(["grok", "leader", "list", "--json"])
|
||||
.expect("grok leader list --json");
|
||||
let list = PagerArgs::try_parse_from(["kigi", "leader", "list", "--json"])
|
||||
.expect("kigi leader list --json");
|
||||
assert!(matches!(
|
||||
list.command,
|
||||
Some(Command::Leader(LeaderMgmtArgs {
|
||||
command: LeaderMgmtCommand::List { json: true },
|
||||
}))
|
||||
));
|
||||
let info = PagerArgs::try_parse_from(["grok", "leader", "info", "--pid", "42"])
|
||||
.expect("grok leader info --pid");
|
||||
let info = PagerArgs::try_parse_from(["kigi", "leader", "info", "--pid", "42"])
|
||||
.expect("kigi leader info --pid");
|
||||
assert!(matches!(
|
||||
info.command,
|
||||
Some(Command::Leader(LeaderMgmtArgs {
|
||||
@@ -1032,25 +1037,25 @@ mod tests {
|
||||
},
|
||||
}))
|
||||
));
|
||||
let kill = PagerArgs::try_parse_from(["grok", "leader", "kill"]).expect("grok leader kill");
|
||||
let kill = PagerArgs::try_parse_from(["kigi", "leader", "kill"]).expect("kigi leader kill");
|
||||
assert!(matches!(
|
||||
kill.command,
|
||||
Some(Command::Leader(LeaderMgmtArgs {
|
||||
command: LeaderMgmtCommand::Kill,
|
||||
}))
|
||||
));
|
||||
assert!(PagerArgs::try_parse_from(["grok", "leader", "profile"]).is_err());
|
||||
assert!(PagerArgs::try_parse_from(["kigi", "leader", "profile"]).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn debug_file_flag_parses_and_is_global() {
|
||||
let root = PagerArgs::try_parse_from(["grok", "--debug-file", "/tmp/fire.txt"])
|
||||
let root = PagerArgs::try_parse_from(["kigi", "--debug-file", "/tmp/fire.txt"])
|
||||
.expect("--debug-file parses at the root");
|
||||
assert_eq!(
|
||||
root.debug_file.as_deref(),
|
||||
Some(std::path::Path::new("/tmp/fire.txt"))
|
||||
);
|
||||
let sub =
|
||||
PagerArgs::try_parse_from(["grok", "agent", "stdio", "--debug-file", "/tmp/f.txt"])
|
||||
PagerArgs::try_parse_from(["kigi", "agent", "stdio", "--debug-file", "/tmp/f.txt"])
|
||||
.expect("--debug-file parses after a subcommand (global)");
|
||||
assert_eq!(
|
||||
sub.debug_file.as_deref(),
|
||||
@@ -1059,90 +1064,90 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn debug_file_flag_defaults_to_none() {
|
||||
let args = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
|
||||
let args = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
|
||||
assert!(args.debug_file.is_none());
|
||||
}
|
||||
#[test]
|
||||
fn positional_prompt_seeds_interactive_session() {
|
||||
let args =
|
||||
PagerArgs::try_parse_from(["grok", "fix the bug"]).expect("positional prompt parses");
|
||||
PagerArgs::try_parse_from(["kigi", "fix the bug"]).expect("positional prompt parses");
|
||||
assert_eq!(args.initial_prompt(), Some("fix the bug"));
|
||||
assert!(args.command.is_none());
|
||||
assert!(args.single.is_none());
|
||||
}
|
||||
#[test]
|
||||
fn bare_grok_has_no_initial_prompt() {
|
||||
let args = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
|
||||
fn bare_kigi_has_no_initial_prompt() {
|
||||
let args = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
|
||||
assert_eq!(args.initial_prompt(), None);
|
||||
}
|
||||
#[test]
|
||||
fn initial_prompt_trims_and_ignores_whitespace_only() {
|
||||
let args = PagerArgs::try_parse_from(["grok", " spaced "]).expect("padded prompt parses");
|
||||
let args = PagerArgs::try_parse_from(["kigi", " spaced "]).expect("padded prompt parses");
|
||||
assert_eq!(args.initial_prompt(), Some("spaced"));
|
||||
let blank = PagerArgs::try_parse_from(["grok", " "]).expect("blank prompt parses");
|
||||
let blank = PagerArgs::try_parse_from(["kigi", " "]).expect("blank prompt parses");
|
||||
assert_eq!(blank.initial_prompt(), None);
|
||||
}
|
||||
#[test]
|
||||
fn subcommand_takes_precedence_over_positional_prompt() {
|
||||
let args = PagerArgs::try_parse_from(["grok", "logout"]).expect("subcommand parses");
|
||||
let args = PagerArgs::try_parse_from(["kigi", "logout"]).expect("subcommand parses");
|
||||
assert!(matches!(args.command, Some(Command::Logout)));
|
||||
assert!(args.prompt.is_none());
|
||||
}
|
||||
#[test]
|
||||
fn positional_prompt_conflicts_with_headless_single() {
|
||||
let err = PagerArgs::try_parse_from(["grok", "-p", "headless", "interactive"])
|
||||
let err = PagerArgs::try_parse_from(["kigi", "-p", "headless", "interactive"])
|
||||
.expect_err("positional prompt + --single must conflict");
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
#[test]
|
||||
fn worktree_flag_and_initial_prompt_combine() {
|
||||
let a = PagerArgs::try_parse_from(["grok", "do the thing", "-w"])
|
||||
let a = PagerArgs::try_parse_from(["kigi", "do the thing", "-w"])
|
||||
.expect("prompt then bare -w parses");
|
||||
assert_eq!(a.initial_prompt(), Some("do the thing"));
|
||||
assert_eq!(a.worktree.as_deref(), Some(""));
|
||||
let b = PagerArgs::try_parse_from(["grok", "--worktree=feat", "do the thing"])
|
||||
let b = PagerArgs::try_parse_from(["kigi", "--worktree=feat", "do the thing"])
|
||||
.expect("--worktree=name + positional parses");
|
||||
assert_eq!(b.initial_prompt(), Some("do the thing"));
|
||||
assert_eq!(b.worktree.as_deref(), Some("feat"));
|
||||
let c = PagerArgs::try_parse_from(["grok", "-w", "x"]).expect("-w x parses");
|
||||
let c = PagerArgs::try_parse_from(["kigi", "-w", "x"]).expect("-w x parses");
|
||||
assert_eq!(c.worktree.as_deref(), Some("x"));
|
||||
assert_eq!(c.initial_prompt(), None);
|
||||
}
|
||||
#[test]
|
||||
fn trust_flag_parses_on_pager_and_alias() {
|
||||
let bare = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
|
||||
let bare = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
|
||||
assert!(!bare.trust);
|
||||
let long = PagerArgs::try_parse_from(["grok", "--trust"]).expect("--trust parses");
|
||||
let long = PagerArgs::try_parse_from(["kigi", "--trust"]).expect("--trust parses");
|
||||
assert!(long.trust);
|
||||
let alias =
|
||||
PagerArgs::try_parse_from(["grok", "--trust-folder"]).expect("--trust-folder parses");
|
||||
PagerArgs::try_parse_from(["kigi", "--trust-folder"]).expect("--trust-folder parses");
|
||||
assert!(alias.trust);
|
||||
}
|
||||
#[test]
|
||||
fn reasoning_effort_and_effort_alias_parse_same_field() {
|
||||
let long = PagerArgs::try_parse_from(["grok", "--reasoning-effort", "high"])
|
||||
let long = PagerArgs::try_parse_from(["kigi", "--reasoning-effort", "high"])
|
||||
.expect("--reasoning-effort parses");
|
||||
assert_eq!(long.reasoning_effort.as_deref(), Some("high"));
|
||||
let alias =
|
||||
PagerArgs::try_parse_from(["grok", "--effort", "high"]).expect("--effort alias parses");
|
||||
PagerArgs::try_parse_from(["kigi", "--effort", "high"]).expect("--effort alias parses");
|
||||
assert_eq!(alias.reasoning_effort.as_deref(), Some("high"));
|
||||
}
|
||||
#[test]
|
||||
fn reasoning_effort_accepts_max_and_remapped_ids() {
|
||||
let max = PagerArgs::try_parse_from(["grok", "--effort", "max"]).expect("max parses");
|
||||
let max = PagerArgs::try_parse_from(["kigi", "--effort", "max"]).expect("max parses");
|
||||
assert_eq!(max.reasoning_effort.as_deref(), Some("max"));
|
||||
let deep =
|
||||
PagerArgs::try_parse_from(["grok", "--reasoning-effort", "deep"]).expect("deep parses");
|
||||
PagerArgs::try_parse_from(["kigi", "--reasoning-effort", "deep"]).expect("deep parses");
|
||||
assert_eq!(deep.reasoning_effort.as_deref(), Some("deep"));
|
||||
}
|
||||
#[test]
|
||||
fn reasoning_effort_last_flag_wins_when_both_names_set() {
|
||||
let args =
|
||||
PagerArgs::try_parse_from(["grok", "--reasoning-effort", "low", "--effort", "high"])
|
||||
PagerArgs::try_parse_from(["kigi", "--reasoning-effort", "low", "--effort", "high"])
|
||||
.expect("both effort flag names parse");
|
||||
assert_eq!(args.reasoning_effort.as_deref(), Some("high"));
|
||||
let reverse =
|
||||
PagerArgs::try_parse_from(["grok", "--effort", "high", "--reasoning-effort", "low"])
|
||||
PagerArgs::try_parse_from(["kigi", "--effort", "high", "--reasoning-effort", "low"])
|
||||
.expect("both effort flag names parse (reverse order)");
|
||||
assert_eq!(reverse.reasoning_effort.as_deref(), Some("low"));
|
||||
}
|
||||
@@ -1165,7 +1170,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn agent_args_effort_alias_parses() {
|
||||
let args = PagerArgs::try_parse_from(["grok", "agent", "--effort", "max", "stdio"])
|
||||
let args = PagerArgs::try_parse_from(["kigi", "agent", "--effort", "max", "stdio"])
|
||||
.expect("agent --effort parses");
|
||||
let Command::Agent(agent) = args.command.expect("agent subcommand") else {
|
||||
panic!("expected agent subcommand");
|
||||
|
||||
@@ -24,7 +24,7 @@ pub(super) fn dispatch_logout(_app: &mut AppView) -> Vec<Effect> {
|
||||
/// On the eager-auth path (cached token), login_method_id is never set
|
||||
/// because the user skipped the login screen.
|
||||
///
|
||||
/// Does **not** invent `grok.com` when no interactive method is advertised
|
||||
/// Does **not** invent `kimi-code` when no interactive method is advertised
|
||||
/// (e.g. `preferred_method=api_key` with no key — empty `auth_methods`).
|
||||
/// Callers already surface "No login method available" when this leaves
|
||||
/// `login_method_id` unset.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Mid-turn interjection dispatch: optimistic local echo, the
|
||||
//! `x.ai/interject` effect, and prompt-history recording. Split out of
|
||||
//! `kigi/interject` effect, and prompt-history recording. Split out of
|
||||
//! `dispatch.rs` verbatim (pure code motion).
|
||||
|
||||
use crate::app::actions::Effect;
|
||||
@@ -9,10 +9,10 @@ use crate::scrollback::block::RenderBlock;
|
||||
|
||||
/// Send a mid-turn interjection. Pushes a standard user prompt block locally
|
||||
/// for instant feedback, records the text in prompt history, clears the
|
||||
/// prompt, and fires the `x.ai/interject` ext method carrying a client-minted
|
||||
/// prompt, and fires the `kigi/interject` ext method carrying a client-minted
|
||||
/// id.
|
||||
///
|
||||
/// The shell broadcasts `x.ai/session/interjection` to every attached pane so
|
||||
/// The shell broadcasts `kigi/session/interjection` to every attached pane so
|
||||
/// other clients viewing the same session render it too (multi-client /
|
||||
/// dashboard mode). Our own broadcast echoes back carrying the same id; the id
|
||||
/// is recorded in `self_interjection_ids` so `handle_interjection` drops the
|
||||
@@ -43,7 +43,7 @@ pub(super) fn dispatch_interject(
|
||||
record_interject_prompt_history(agent, &text);
|
||||
|
||||
// Push a standard user prompt block locally for instant feedback, and
|
||||
// record its id so the broadcast echo (`x.ai/session/interjection`) is
|
||||
// record its id so the broadcast echo (`kigi/session/interjection`) is
|
||||
// deduped instead of rendering a second copy on this pane.
|
||||
let interjection_id = uuid::Uuid::new_v4().to_string();
|
||||
agent.self_interjection_ids.insert(interjection_id.clone());
|
||||
|
||||
@@ -69,7 +69,7 @@ pub(super) fn dispatch_send_feedback(app: &mut AppView, text: String) -> Vec<Eff
|
||||
};
|
||||
|
||||
agent.scrollback.push_block(RenderBlock::system(
|
||||
"Thanks for the feedback! The Grok Build team is on it.".to_string(),
|
||||
"Thanks for the feedback! The Kigi team is on it.".to_string(),
|
||||
));
|
||||
|
||||
vec![Effect::SendFeedback {
|
||||
@@ -79,7 +79,7 @@ pub(super) fn dispatch_send_feedback(app: &mut AppView, text: String) -> Vec<Eff
|
||||
}]
|
||||
}
|
||||
|
||||
/// Send a raw remember note for LLM-powered rewriting via `x.ai/memory/rewrite`.
|
||||
/// Send a raw remember note for LLM-powered rewriting via `kigi/memory/rewrite`.
|
||||
/// Clears remember mode and prompts the LLM to reformat the note with session
|
||||
/// context. Falls back to direct `SaveMemoryNote` when no session is available.
|
||||
pub(super) fn dispatch_send_remember_note(app: &mut AppView, text: String) -> Vec<Effect> {
|
||||
@@ -327,7 +327,7 @@ pub(crate) fn scrollback_has_user_messages(
|
||||
}
|
||||
|
||||
/// Request a session recap. Bypasses the prompt queue — works even while the
|
||||
/// agent is mid-turn. Fires the `x.ai/recap` ext method; the recap arrives
|
||||
/// agent is mid-turn. Fires the `kigi/recap` ext method; the recap arrives
|
||||
/// asynchronously as a `SessionRecap` notification (rendered in scrollback).
|
||||
///
|
||||
/// `auto` is `false` for an explicit `/recap` and `true` for the automatic
|
||||
@@ -343,7 +343,7 @@ pub(super) fn dispatch_send_recap(app: &mut AppView, auto: bool) -> Vec<Effect>
|
||||
};
|
||||
|
||||
// Shell is authoritative (remote settings / config / env). Skip client requests
|
||||
// entirely when the feature is off so we never hit `x.ai/recap`.
|
||||
// entirely when the feature is off so we never hit `kigi/recap`.
|
||||
if !app.session_recap_available {
|
||||
if !auto {
|
||||
agent.show_toast("Session recap is not enabled");
|
||||
@@ -420,7 +420,7 @@ pub(super) fn handle_memory_note_saved(
|
||||
.scrollback
|
||||
.push_block(crate::scrollback::block::RenderBlock::system(format!(
|
||||
"Memory saved to {}",
|
||||
crate::util::display_user_grok_path("memory/MEMORY.md")
|
||||
crate::util::display_user_kigi_path("memory/MEMORY.md")
|
||||
)));
|
||||
}
|
||||
Err(error) => {
|
||||
|
||||
@@ -23,7 +23,7 @@ use agent_client_protocol as acp;
|
||||
/// existing `set_yolo_mode(true)` flow to flip the local YOLO state, drain
|
||||
/// any remaining queued permissions, persist `[ui] permission_mode =
|
||||
/// "always-approve"` to `~/.kigi/config.toml`, and fire the
|
||||
/// `x.ai/yolo_mode_changed` ACP notification. See the option-id constant
|
||||
/// `kigi/yolo_mode_changed` ACP notification. See the option-id constant
|
||||
/// doc-comment for the full client/shell split. Under a managed-policy
|
||||
/// pin step (b) is refused with a toast — the request is still allowed once.
|
||||
pub(super) fn dispatch_permission_select(
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(super) fn consume_chat_kind(app: &mut AppView) -> bool {
|
||||
/// The prompt is always pushed to the queue first. If the agent is idle
|
||||
/// (and has a session), `maybe_drain_queue` pops the front prompt and
|
||||
/// sends it in the same dispatch call — no deferred ticks.
|
||||
/// Start (if needed) and submit the initial prompt from `grok "<prompt>"`.
|
||||
/// Start (if needed) and submit the initial prompt from `kigi "<prompt>"`.
|
||||
///
|
||||
/// Shared by the TUI startup path (already authenticated) and the post-login
|
||||
/// `AuthComplete` path (deferred via `deferred_startup.prompt`). It does nothing
|
||||
@@ -511,7 +511,7 @@ pub(super) fn dispatch_send_prompt_inner(
|
||||
// immediately instead of being held in the local drip-feed queue. The
|
||||
// agent appends it to its authoritative `pending_inputs` (no concurrent
|
||||
// turn starts — validated keystone) and drives the drain via
|
||||
// `x.ai/queue/changed`. We render an optimistic echo into the shared
|
||||
// `kigi/queue/changed`. We render an optimistic echo into the shared
|
||||
// queue keyed by `prompt_id`; the broadcast reconciles it by id.
|
||||
//
|
||||
// The IDLE case is unchanged (falls through to the local path below,
|
||||
@@ -930,7 +930,7 @@ pub(super) fn handle_prompt_response(
|
||||
// Server-authoritative queue lifecycle: this prompt's RPC
|
||||
// resolved without becoming the running turn (removed,
|
||||
// cancelled, rewound). Retire its optimistic echo so a
|
||||
// later `x.ai/queue/changed` broadcast can't re-pin a
|
||||
// later `kigi/queue/changed` broadcast can't re-pin a
|
||||
// stale placeholder and reorder the queue.
|
||||
if let Some(sid) = agent.session.session_id.as_ref().map(|s| s.0.to_string()) {
|
||||
retire_optimistic_echo(
|
||||
@@ -1171,7 +1171,7 @@ pub(super) fn handle_prompt_response(
|
||||
// title into the body automatically.
|
||||
let notif_title = session_name
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "Grok".into());
|
||||
.unwrap_or_else(|| "Kigi".into());
|
||||
|
||||
app.deferred_notification = Some((
|
||||
NotificationEvent {
|
||||
|
||||
@@ -62,7 +62,7 @@ pub(super) fn immediate_server_send_eligible(agent: &AgentView) -> bool {
|
||||
|
||||
/// Push the optimistic shared-queue echo for an immediate server-authoritative
|
||||
/// send and mirror it into the owning agent so the queue pane renders it
|
||||
/// immediately, before the confirming `x.ai/queue/changed` broadcast.
|
||||
/// immediately, before the confirming `kigi/queue/changed` broadcast.
|
||||
pub(super) fn push_server_queue_echo(
|
||||
app: &mut AppView,
|
||||
agent_id: AgentId,
|
||||
@@ -91,7 +91,7 @@ pub(super) fn push_server_queue_echo(
|
||||
///
|
||||
/// The agent's `pending_inputs` is the single source of truth for queue
|
||||
/// contents and order; the only client-side queue state is the optimistic echo
|
||||
/// that bridges the round-trip before the confirming `x.ai/queue/changed`
|
||||
/// that bridges the round-trip before the confirming `kigi/queue/changed`
|
||||
/// broadcast. Once a prompt's RPC resolves (or we pull it back into the input
|
||||
/// on cancel) it will never reappear in a future broadcast, so its echo must be
|
||||
/// dropped — otherwise the reconcile in [`AppView::apply_queue_changed`] keeps
|
||||
@@ -633,7 +633,7 @@ pub(crate) fn apply_turn_start_shim(
|
||||
agent.session.current_prompt_id = Some(prompt_id.clone());
|
||||
agent.attached_as_viewer = adopted_from_other_client;
|
||||
// A new (adopted) turn is starting: drop the prior turn's chips but KEEP the
|
||||
// seen ring, so a buffer-replayed `x.ai/follow_ups` for an older response
|
||||
// seen ring, so a buffer-replayed `kigi/follow_ups` for an older response
|
||||
// stays rejected (no stale revival). This is correct for BOTH passive-viewer
|
||||
// and self-driven adoption: the adopted turn's OWN follow_ups still
|
||||
// re-render via the stamped `promptId` match in `apply_follow_ups` (the
|
||||
@@ -1150,7 +1150,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// FIX 4 (b) via the shim: after starting a NEW turn, a buffer-replayed
|
||||
/// `x.ai/follow_ups` for a PRIOR turn's response stays rejected (its
|
||||
/// `kigi/follow_ups` for a PRIOR turn's response stays rejected (its
|
||||
/// `promptId` is not the active turn and it is already seen) — no stale
|
||||
/// revival. Covers the self-driven turn start (`p-self`).
|
||||
#[test]
|
||||
|
||||
@@ -95,8 +95,8 @@ pub(in crate::app::dispatch) fn apply_persist_worktree_mode(
|
||||
/// Build the two persistence options shared by the fork and new-session
|
||||
/// worktree question modals ("Always worktree" / "Never worktree").
|
||||
pub(super) fn worktree_persist_options()
|
||||
-> [kigi_tools::implementations::grok_build::ask_user_question::QuestionOption; 2] {
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::QuestionOption;
|
||||
-> [kigi_tools::implementations::kigi::ask_user_question::QuestionOption; 2] {
|
||||
use kigi_tools::implementations::kigi::ask_user_question::QuestionOption;
|
||||
[
|
||||
QuestionOption {
|
||||
label: "Always worktree".into(),
|
||||
@@ -117,7 +117,7 @@ pub(super) fn worktree_persist_options()
|
||||
/// instead -- the modal-collision protocol.
|
||||
fn open_fork_question(app: &mut AppView, directive: Option<String>) -> Vec<Effect> {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let ActiveView::Agent(id) = app.active_view else {
|
||||
return vec![];
|
||||
};
|
||||
@@ -168,7 +168,7 @@ fn open_fork_question(app: &mut AppView, directive: Option<String>) -> Vec<Effec
|
||||
/// `worktree == true` reuses the existing
|
||||
/// [`Effect::CreateWorktreeSession`] pipeline (with `load_session_id`
|
||||
/// set to the parent session id). `worktree == false` emits the new
|
||||
/// [`Effect::ForkSession`] which calls `x.ai/session/fork` directly.
|
||||
/// [`Effect::ForkSession`] which calls `kigi/session/fork` directly.
|
||||
pub(in crate::app::dispatch) fn dispatch_fork_resolved(
|
||||
app: &mut AppView,
|
||||
worktree: bool,
|
||||
|
||||
@@ -154,7 +154,7 @@ pub(in crate::app::dispatch) fn dispatch_new_session(app: &mut AppView) -> Vec<E
|
||||
/// [`dispatch_new_worktree_session`].
|
||||
pub(in crate::app::dispatch) fn open_new_session_question(app: &mut AppView) -> Vec<Effect> {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let ActiveView::Agent(id) = app.active_view else {
|
||||
return vec![];
|
||||
};
|
||||
@@ -211,7 +211,7 @@ pub(in crate::app::dispatch) fn open_agent_type_mismatch_question(
|
||||
model_name: &str,
|
||||
) -> Vec<Effect> {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let ActiveView::Agent(id) = app.active_view else {
|
||||
return vec![];
|
||||
};
|
||||
@@ -408,7 +408,7 @@ pub(in crate::app::dispatch) fn clear_startup_actions(app: &mut AppView) {
|
||||
let _ = app.deferred_startup.take();
|
||||
}
|
||||
/// Replay the session-startup actions deferred until auth + trust both resolved
|
||||
/// (`--resume` / `--worktree` / initial-prompt / `grok dashboard`). Extracted
|
||||
/// (`--resume` / `--worktree` / initial-prompt / `kigi dashboard`). Extracted
|
||||
/// from the `AuthComplete` handler so the folder-trust answer can run the SAME
|
||||
/// machinery; whichever gate resolves last drains it (each call site guards on
|
||||
/// the other gate being `Done`, so it runs exactly once).
|
||||
|
||||
@@ -606,7 +606,7 @@ pub(in crate::app::dispatch) fn dispatch_trigger_deep_search(
|
||||
}
|
||||
}
|
||||
/// Chat-mode replacement for local deep search: refetch the session list
|
||||
/// with the picker query pushed down as `x.ai/session/list` `query`.
|
||||
/// with the picker query pushed down as `kigi/session/list` `query`.
|
||||
/// Keystrokes are coalesced through [`Effect::DebounceSessionSearch`]; a
|
||||
/// forced search (Ctrl+/) or a cleared query fetches immediately. Every
|
||||
/// trigger bumps `session_picker_list_seq`, so stale in-flight debounces
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(in crate::app::dispatch) fn dispatch_sessions_confirm_close(
|
||||
remove_agent_and_cleanup(app, closed_id);
|
||||
effects
|
||||
}
|
||||
/// Rename the current session via x.ai/session/rename.
|
||||
/// Rename the current session via kigi/session/rename.
|
||||
///
|
||||
/// Produces Effect::RenameSession which spawns an async ACP ext request.
|
||||
/// On completion, TaskResult::RenameSessionComplete shows the result.
|
||||
|
||||
@@ -248,7 +248,7 @@ pub(in crate::app::dispatch) fn set_ask_user_question_timeout_enabled(
|
||||
app: &mut AppView,
|
||||
new: bool,
|
||||
) -> Vec<Effect> {
|
||||
use kigi_tools::implementations::grok_build::ask_user_question;
|
||||
use kigi_tools::implementations::kigi::ask_user_question;
|
||||
let prev_state = app.ask_user_question_timeout_enabled;
|
||||
let prev_effective =
|
||||
prev_state.unwrap_or(ask_user_question::DEFAULT_ASK_USER_QUESTION_TIMEOUT_ENABLED);
|
||||
@@ -1206,8 +1206,8 @@ pub(in crate::app::dispatch) fn set_auto_dark_theme(app: &mut AppView, new: Stri
|
||||
.as_deref()
|
||||
.and_then(crate::theme::canonical_name)
|
||||
.filter(|s| *s != "auto")
|
||||
// No prior config: fall back to GrokNight (the default).
|
||||
.unwrap_or_else(|| crate::theme::ThemeKind::GrokNight.display_name());
|
||||
// No prior config: fall back to KigiNight (the default).
|
||||
.unwrap_or_else(|| crate::theme::ThemeKind::KigiNight.display_name());
|
||||
let new_canonical = match crate::theme::canonical_name(&new) {
|
||||
Some(c) if c != crate::theme::ThemeKind::Auto.display_name() => c,
|
||||
_ => {
|
||||
@@ -1320,7 +1320,7 @@ pub(in crate::app::dispatch) fn set_auto_light_theme(
|
||||
.as_deref()
|
||||
.and_then(crate::theme::canonical_name)
|
||||
.filter(|s| *s != "auto")
|
||||
.unwrap_or_else(|| crate::theme::ThemeKind::GrokDay.display_name());
|
||||
.unwrap_or_else(|| crate::theme::ThemeKind::KigiDay.display_name());
|
||||
let new_canonical = match crate::theme::canonical_name(&new) {
|
||||
Some(c) if c != crate::theme::ThemeKind::Auto.display_name() => c,
|
||||
_ => {
|
||||
@@ -1430,7 +1430,7 @@ pub(in crate::app::dispatch) fn set_default_model_inner(
|
||||
// or `/clear` creates a fresh session by cloning `app.models`
|
||||
// (`dispatch_new_session_inner_with_id`), so without this the new session —
|
||||
// and the welcome card it commits — would show the previous default until
|
||||
// the next `x.ai/models/update` roundtrip.
|
||||
// the next `kigi/models/update` roundtrip.
|
||||
if app.models.available.contains_key(id) {
|
||||
app.models.set_current(id.clone(), None);
|
||||
}
|
||||
@@ -1510,7 +1510,7 @@ pub(in crate::app::dispatch) fn set_default_model(
|
||||
|
||||
// Persist the **model ID** (catalog key), not the display name.
|
||||
// The shell's `resolve_default_model` matches by slug / map key,
|
||||
// so persisting the human-readable name (e.g. "Grok Build")
|
||||
// so persisting the human-readable name (e.g. "Kigi")
|
||||
// would silently fail to resolve on the next startup.
|
||||
//
|
||||
// Chat (`--chat` / KIGI_CHAT_MODE) catalogs use opaque `/rest/modes`
|
||||
@@ -1798,7 +1798,7 @@ pub(in crate::app::dispatch) fn set_max_thoughts_width(app: &mut AppView, new: i
|
||||
/// (`show_tips`, `auto_update`, ask_user_question timeout).
|
||||
/// Matches the consumer's `.unwrap_or(...)` fallback.
|
||||
pub(super) fn pr13_effective_default(key: &str) -> Option<bool> {
|
||||
use kigi_tools::implementations::grok_build::ask_user_question;
|
||||
use kigi_tools::implementations::kigi::ask_user_question;
|
||||
match key {
|
||||
"show_tips" => Some(true),
|
||||
"auto_update" => Some(true),
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::app::app_view::{ActiveView, AppView};
|
||||
use crate::notifications::{NotificationEvent, NotificationEventKind};
|
||||
use crate::scrollback::block::RenderBlock;
|
||||
|
||||
/// Show session info: fetch via x.ai/session/info and display in scrollback.
|
||||
/// Show session info: fetch via kigi/session/info and display in scrollback.
|
||||
///
|
||||
/// Produces Effect::ShowSessionInfo which spawns an async ACP ext request.
|
||||
/// On completion, TaskResult::SessionInfoComplete shows the formatted info.
|
||||
@@ -49,7 +49,7 @@ pub(super) fn scrub_error_for_toast(error: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Show context info: fetch via x.ai/session/info and display rich breakdown.
|
||||
/// Show context info: fetch via kigi/session/info and display rich breakdown.
|
||||
///
|
||||
/// Produces Effect::ShowContextInfo which spawns an async ACP ext request.
|
||||
/// On completion, TaskResult::ContextInfoComplete shows the formatted info.
|
||||
@@ -72,7 +72,7 @@ pub(super) fn dispatch_show_context_info(app: &mut AppView) -> Vec<Effect> {
|
||||
|
||||
/// `/usage` — fetch Kimi usage/quota rows and display them inline.
|
||||
///
|
||||
/// Produces [`Effect::FetchUsage`], which asks the shell's `x.ai/billing`
|
||||
/// Produces [`Effect::FetchUsage`], which asks the shell's `kigi/billing`
|
||||
/// extension (`GET {base}/usages`); [`handle_usage_fetched`] renders the
|
||||
/// rows as a system block in scrollback.
|
||||
pub(super) fn dispatch_show_usage(app: &mut AppView) -> Vec<Effect> {
|
||||
@@ -225,7 +225,7 @@ pub(super) fn notify_session_ready(
|
||||
) {
|
||||
notification_service.notify(NotificationEvent {
|
||||
kind: NotificationEventKind::SessionReady,
|
||||
title: "Grok".into(),
|
||||
title: "Kigi".into(),
|
||||
body: NotificationEventKind::SessionReady.as_str().into(),
|
||||
session_id: agent.session.session_id.as_ref().map(|s| s.0.to_string()),
|
||||
});
|
||||
|
||||
@@ -218,7 +218,7 @@ fn cancel_login_strips_reauth_prompt_from_scrollback() {
|
||||
}
|
||||
|
||||
/// Empty `auth_methods` (preferred_method pin unavailable) must not invent
|
||||
/// `grok.com` or start an OIDC flow the agent did not advertise.
|
||||
/// `kimi-code` or start an OIDC flow the agent did not advertise.
|
||||
#[test]
|
||||
fn login_with_empty_auth_methods_fails_closed() {
|
||||
let mut app = test_app_with_agent();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
/// `grok dashboard` before login: the startup hook consumes the
|
||||
/// `kigi dashboard` before login: the startup hook consumes the
|
||||
/// `KIGI_OPEN_DASHBOARD_AT_STARTUP` env var and stashes
|
||||
/// `deferred_startup.open_dashboard`; `AuthComplete` must then open the
|
||||
/// dashboard view. Regression test for the silent drop where the
|
||||
@@ -30,7 +30,7 @@ fn auth_complete_opens_deferred_dashboard() {
|
||||
assert!(matches!(app.auth_state, AuthState::Done));
|
||||
assert!(
|
||||
matches!(app.active_view, ActiveView::AgentDashboard),
|
||||
"deferred `grok dashboard` must open the dashboard after login",
|
||||
"deferred `kigi dashboard` must open the dashboard after login",
|
||||
);
|
||||
assert!(
|
||||
!app.deferred_startup.open_dashboard,
|
||||
@@ -575,15 +575,15 @@ fn dashboard_confirm_worktree_without_git_repo_creates_nothing() {
|
||||
#[test]
|
||||
fn dashboard_confirm_worktree_applies_pending_model_and_plan() {
|
||||
let mut app = test_app();
|
||||
seed_model(&mut app, "grok-4.5", "Grok 4.5");
|
||||
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
|
||||
open_dashboard(&mut app);
|
||||
app.cwd_has_git_ancestor = true;
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
if let Some(d) = app.dashboard.as_mut() {
|
||||
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
|
||||
id: model_id.clone(),
|
||||
effort: Some(kigi_shell::sampling::types::ReasoningEffort::High),
|
||||
display: "Grok 4.5".to_string(),
|
||||
display: "Kigi 4.5".to_string(),
|
||||
});
|
||||
d.pending_mode = crate::views::dashboard::DashboardDispatchMode::Plan;
|
||||
d.dispatch.set_text("do the thing");
|
||||
@@ -1120,7 +1120,7 @@ fn dashboard_peek_cycle_does_not_retire_the_nudge() {
|
||||
/// leader mode. The dashboard renders local sessions regardless; leader
|
||||
/// mode only adds the roster poll. Every entry point funnels through
|
||||
/// `Action::OpenDashboard`, so this covers `/dashboard`, `Ctrl+\`,
|
||||
/// `grok dashboard`, and the startup hook.
|
||||
/// `kigi dashboard`, and the startup hook.
|
||||
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
|
||||
#[test]
|
||||
fn dashboard_open_works_without_leader() {
|
||||
@@ -1237,9 +1237,9 @@ fn seed_model(app: &mut AppView, id: &str, name: &str) {
|
||||
#[test]
|
||||
fn dashboard_slash_model_stages_pending_model() {
|
||||
let mut app = test_app();
|
||||
seed_model(&mut app, "grok-4.5", "Grok 4.5");
|
||||
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
|
||||
open_dashboard(&mut app);
|
||||
let effects = dispatch_dashboard_dispatch_slash(&mut app, "/model grok-4.5".into());
|
||||
let effects = dispatch_dashboard_dispatch_slash(&mut app, "/model kigi-4.5".into());
|
||||
assert!(
|
||||
effects.is_empty(),
|
||||
"staging a model must not spawn a session"
|
||||
@@ -1252,8 +1252,8 @@ fn dashboard_slash_model_stages_pending_model() {
|
||||
.pending_model
|
||||
.as_ref()
|
||||
.expect("pending_model must be set");
|
||||
assert_eq!(pending.id.0.as_ref(), "grok-4.5");
|
||||
assert_eq!(pending.display, "Grok 4.5");
|
||||
assert_eq!(pending.id.0.as_ref(), "kigi-4.5");
|
||||
assert_eq!(pending.display, "Kigi 4.5");
|
||||
assert!(pending.effort.is_none());
|
||||
// The catalog snapshot's `current` tracks the staged model so the
|
||||
// next `/model` dropdown marks it `(current)` (not the seeded default).
|
||||
@@ -1265,7 +1265,7 @@ fn dashboard_slash_model_stages_pending_model() {
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|id| id.0.as_ref()),
|
||||
Some("grok-4.5"),
|
||||
Some("kigi-4.5"),
|
||||
"staging must update the snapshot's current selection",
|
||||
);
|
||||
}
|
||||
@@ -1279,7 +1279,7 @@ fn dashboard_slash_model_stages_pending_model() {
|
||||
#[test]
|
||||
fn dashboard_slash_command_error_gets_error_glyph_prefix() {
|
||||
let mut app = test_app();
|
||||
seed_model(&mut app, "grok-4.5", "Grok 4.5");
|
||||
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
|
||||
open_dashboard(&mut app);
|
||||
let effects = dispatch_dashboard_dispatch_slash(&mut app, "/model nonexistent".into());
|
||||
assert!(effects.is_empty(), "a failed command must not dispatch");
|
||||
@@ -1512,14 +1512,14 @@ fn dashboard_cycle_mode_skips_always_approve_under_policy_pin() {
|
||||
fn dashboard_open_reseeds_pending_model_and_mode() {
|
||||
use crate::views::dashboard::DashboardDispatchMode;
|
||||
let mut app = test_app();
|
||||
seed_model(&mut app, "grok-4.5", "Grok 4.5");
|
||||
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
|
||||
open_dashboard(&mut app);
|
||||
// Stage a model + non-default mode as if from a previous session.
|
||||
if let Some(d) = app.dashboard.as_mut() {
|
||||
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
|
||||
id: acp::ModelId::new(std::sync::Arc::from("grok-4.5")),
|
||||
id: acp::ModelId::new(std::sync::Arc::from("kigi-4.5")),
|
||||
effort: None,
|
||||
display: "Grok 4.5".to_string(),
|
||||
display: "Kigi 4.5".to_string(),
|
||||
});
|
||||
d.pending_mode = DashboardDispatchMode::Plan;
|
||||
}
|
||||
@@ -1727,14 +1727,14 @@ fn dashboard_dispatch_new_agent_is_working_with_prompt_title() {
|
||||
#[test]
|
||||
fn dashboard_dispatch_applies_pending_model_and_plan() {
|
||||
let mut app = test_app();
|
||||
seed_model(&mut app, "grok-4.5", "Grok 4.5");
|
||||
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
|
||||
open_dashboard(&mut app);
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
if let Some(d) = app.dashboard.as_mut() {
|
||||
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
|
||||
id: model_id.clone(),
|
||||
effort: Some(kigi_shell::sampling::types::ReasoningEffort::High),
|
||||
display: "Grok 4.5".to_string(),
|
||||
display: "Kigi 4.5".to_string(),
|
||||
});
|
||||
d.pending_mode = crate::views::dashboard::DashboardDispatchMode::Plan;
|
||||
}
|
||||
@@ -1770,14 +1770,14 @@ fn dashboard_dispatch_applies_pending_model_and_plan() {
|
||||
#[test]
|
||||
fn dashboard_new_agent_button_applies_pending_model_and_plan() {
|
||||
let mut app = test_app();
|
||||
seed_model(&mut app, "grok-4.5", "Grok 4.5");
|
||||
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
|
||||
open_dashboard(&mut app);
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
if let Some(d) = app.dashboard.as_mut() {
|
||||
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
|
||||
id: model_id.clone(),
|
||||
effort: Some(kigi_shell::sampling::types::ReasoningEffort::High),
|
||||
display: "Grok 4.5".to_string(),
|
||||
display: "Kigi 4.5".to_string(),
|
||||
});
|
||||
d.pending_mode = crate::views::dashboard::DashboardDispatchMode::Plan;
|
||||
}
|
||||
@@ -4624,7 +4624,7 @@ fn dashboard_permission_followup_rejects_with_message() {
|
||||
fn dashboard_question_answer_sends_and_clears() {
|
||||
use crate::views::prompt_widget::StashedPrompt;
|
||||
use crate::views::question_view::QuestionViewState;
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{
|
||||
AskUserQuestionMode, Question, QuestionOption,
|
||||
};
|
||||
|
||||
@@ -4673,7 +4673,7 @@ fn dashboard_question_answer_walks_multiple_questions() {
|
||||
use crate::views::dashboard::peek::{PeekPanelState, compute_peek_fields};
|
||||
use crate::views::prompt_widget::StashedPrompt;
|
||||
use crate::views::question_view::QuestionViewState;
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{
|
||||
AskUserQuestionMode, Question, QuestionOption,
|
||||
};
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@ fn test_app() -> AppView {
|
||||
agent_override: None,
|
||||
bootstrap_acp_commands: Vec::new(),
|
||||
auth_methods: vec![acp::AuthMethod::Agent(acp::AuthMethodAgent::new(
|
||||
acp::AuthMethodId::new("grok.com"),
|
||||
"Grok".to_string(),
|
||||
acp::AuthMethodId::new("kimi-code"),
|
||||
"Kigi".to_string(),
|
||||
))],
|
||||
auth_state: AuthState::Done,
|
||||
trust_state: TrustState::Done,
|
||||
@@ -462,7 +462,7 @@ fn fork_test_app() -> AppView {
|
||||
app
|
||||
}
|
||||
/// Build a minimal `AcpArgs<acp::ExtRequest>` for an
|
||||
/// `x.ai/ask_user_question` ext-method request. Returns the args
|
||||
/// `kigi/ask_user_question` ext-method request. Returns the args
|
||||
/// plus the receiver half of the response oneshot so the test can
|
||||
/// assert the handler completes the ACP roundtrip.
|
||||
fn make_ask_user_question_args(
|
||||
@@ -471,14 +471,13 @@ fn make_ask_user_question_args(
|
||||
kigi_acp_lib::AcpArgs<acp::ExtRequest>,
|
||||
tokio::sync::oneshot::Receiver<kigi_acp_lib::AcpResult<acp::ExtResponse>>,
|
||||
) {
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{
|
||||
AskUserQuestionExtRequest, Question, QuestionOption,
|
||||
};
|
||||
let req = AskUserQuestionExtRequest {
|
||||
session_id: "test-session".into(),
|
||||
tool_call_id: tool_call_id.into(),
|
||||
mode:
|
||||
kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionMode::Default,
|
||||
mode: kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionMode::Default,
|
||||
questions: vec![Question {
|
||||
question: "ACP-driven question".into(),
|
||||
options: vec![QuestionOption {
|
||||
@@ -493,7 +492,7 @@ fn make_ask_user_question_args(
|
||||
};
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let ext = acp::ExtRequest::new(
|
||||
"x.ai/ask_user_question",
|
||||
"kigi/ask_user_question",
|
||||
serde_json::value::to_raw_value(&req)
|
||||
.expect("serialize AskUserQuestionExtRequest")
|
||||
.into(),
|
||||
@@ -742,7 +741,7 @@ fn with_theme_test_env(f: impl FnOnce()) {
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
crate::theme::cache::reset_for_test();
|
||||
crate::theme::cache::seed_auto_theme_defaults_for_test();
|
||||
crate::theme::cache::set(crate::theme::ThemeKind::GrokNight);
|
||||
crate::theme::cache::set(crate::theme::ThemeKind::KigiNight);
|
||||
crate::theme::system_appearance::clear_mock();
|
||||
f();
|
||||
crate::theme::system_appearance::clear_mock();
|
||||
|
||||
@@ -598,7 +598,7 @@ fn yolo_on_drain_clears_double_click_tracker() {
|
||||
/// 2. The dispatcher returns a `PersistPermissionMode` effect with
|
||||
/// canonical `"always-approve"` — this is what flips
|
||||
/// `[ui] permission_mode` on disk AND fires the
|
||||
/// `x.ai/yolo_mode_changed` ACP notification back to the shell.
|
||||
/// `kigi/yolo_mode_changed` ACP notification back to the shell.
|
||||
/// 3. The agent's per-session `yolo_mode` flag is flipped to true,
|
||||
/// so subsequent permission requests are auto-approved by
|
||||
/// `handle_permission_request`.
|
||||
@@ -651,7 +651,7 @@ fn enable_always_approve_sends_response_and_flips_yolo_and_persists() {
|
||||
|
||||
// (2) The dispatcher returns a PersistPermissionMode effect with
|
||||
// canonical "always-approve". This is the bridge that writes
|
||||
// ~/.kigi/config.toml AND fires x.ai/yolo_mode_changed.
|
||||
// ~/.kigi/config.toml AND fires kigi/yolo_mode_changed.
|
||||
let persist = effects
|
||||
.iter()
|
||||
.find_map(|e| match e {
|
||||
@@ -725,7 +725,7 @@ fn enable_always_approve_is_idempotent_when_yolo_already_on() {
|
||||
.any(|e| matches!(e, Effect::PersistPermissionMode { .. })),
|
||||
"redundant PersistPermissionMode when YOLO already on — the dispatcher \
|
||||
must short-circuit to avoid double-writing config.toml and double-firing \
|
||||
x.ai/yolo_mode_changed",
|
||||
kigi/yolo_mode_changed",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ fn manual_recap_with_no_messages_toasts_empty_state_and_skips_request() {
|
||||
|
||||
assert!(
|
||||
effects.is_empty(),
|
||||
"empty session must not fire x.ai/recap: {effects:?}"
|
||||
"empty session must not fire kigi/recap: {effects:?}"
|
||||
);
|
||||
let agent = app.agents.get(&id).unwrap();
|
||||
assert!(agent.pending_recap_entry.is_none(), "no loading spinner");
|
||||
@@ -86,7 +86,7 @@ fn manual_recap_during_batch_load_with_prompts_still_requests() {
|
||||
|
||||
assert!(
|
||||
matches!(effects.as_slice(), [Effect::SendRecap { auto: false, .. }]),
|
||||
"batched resume with user prompts must still fire x.ai/recap: {effects:?}"
|
||||
"batched resume with user prompts must still fire kigi/recap: {effects:?}"
|
||||
);
|
||||
let agent = app.agents.get(&id).unwrap();
|
||||
assert!(agent.pending_recap_entry.is_some());
|
||||
|
||||
@@ -846,7 +846,7 @@ fn prompt_response_resets_turn_state() {
|
||||
assert_eq!(app.agents[&id].scrollback.len(), 1);
|
||||
}
|
||||
|
||||
/// Turn end with prompt suggestions enabled fires the `x.ai/suggestPrompt`
|
||||
/// Turn end with prompt suggestions enabled fires the `kigi/suggestPrompt`
|
||||
/// fetch (before the billing refresh), and the loaded suggestion routes back
|
||||
/// into the agent's controller by id + generation.
|
||||
#[test]
|
||||
@@ -879,8 +879,8 @@ fn turn_end_fetches_prompt_suggestion_when_enabled() {
|
||||
};
|
||||
assert_eq!(*agent_id, id);
|
||||
assert!(session_id.is_some());
|
||||
// No `grok-build-0.1` in the test catalog and no env override →
|
||||
// `None` on the wire; the shell then uses its own `grok-build-0.1`
|
||||
// No `kigi-0.1` in the test catalog and no env override →
|
||||
// `None` on the wire; the shell then uses its own `kigi-0.1`
|
||||
// default (suggestion calls never use the session model).
|
||||
assert_eq!(*model, None);
|
||||
|
||||
@@ -1255,7 +1255,7 @@ fn turn_complete_notification_suppressed_when_queue_non_empty() {
|
||||
/// Regression: cancelling while prompts are queued must hand the queue to
|
||||
/// the agent untouched. The FRONT queued prompt runs next (promoted
|
||||
/// server-side), the rest stay queued in order, and the authoritative
|
||||
/// `x.ai/queue/changed` rebroadcast — not client-side prediction — updates
|
||||
/// `kigi/queue/changed` rebroadcast — not client-side prediction — updates
|
||||
/// the mirror. Nothing resurrects or reorders.
|
||||
#[test]
|
||||
fn cancel_hands_queue_to_agent_without_reordering() {
|
||||
@@ -1771,7 +1771,7 @@ fn send_prompt_works_after_reconnect_clears() {
|
||||
fn switch_model_holds_prompt_until_complete() {
|
||||
let mut app = test_app_with_agent();
|
||||
let id = AgentId(0);
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
|
||||
dispatch(
|
||||
Action::SwitchModel {
|
||||
@@ -1905,7 +1905,7 @@ fn submit_question_answers_cancel_clears_local_modal_and_restores_prompt() {
|
||||
// exercising the prompt.restore + cleanup_question_state contract
|
||||
// that lives in `submit_question_answers` itself.
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
|
||||
let mut app = fork_test_app();
|
||||
let id = AgentId(0);
|
||||
|
||||
@@ -255,7 +255,7 @@ fn mark_turn_finished_clears_start_and_stamps_active() {
|
||||
fn switch_model_dispatch_produces_effect_and_sets_pending() {
|
||||
let mut app = test_app_with_agent();
|
||||
let id = AgentId(0);
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
assert!(!app.agents[&id].session.model_switch_pending);
|
||||
let effects = dispatch(
|
||||
Action::SwitchModel {
|
||||
@@ -989,7 +989,7 @@ fn dispatch_fork_no_flag_always_reopens_modal_after_previous_answer() {
|
||||
#[test]
|
||||
fn translate_local_submit_skipped_returns_changed_with_no_action() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: vec![QuestionOption {
|
||||
@@ -1018,7 +1018,7 @@ fn translate_local_submit_skipped_returns_changed_with_no_action() {
|
||||
#[test]
|
||||
fn translate_local_submit_no_selection_returns_changed_no_action() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..2)
|
||||
@@ -1047,7 +1047,7 @@ fn translate_local_submit_no_selection_returns_changed_no_action() {
|
||||
#[test]
|
||||
fn translate_local_submit_out_of_range_index_returns_changed_no_action() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..2)
|
||||
@@ -1077,7 +1077,7 @@ fn translate_local_submit_out_of_range_index_returns_changed_no_action() {
|
||||
#[test]
|
||||
fn handle_ask_user_question_does_not_push_system_block_when_displaced_acp_modal() {
|
||||
use crate::views::question_view::QuestionViewState;
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let mut app = fork_test_app();
|
||||
let id = AgentId(0);
|
||||
let stashed = app.agents.get_mut(&id).unwrap().prompt.stash();
|
||||
|
||||
@@ -30,8 +30,8 @@ fn worktree_forked_sets_session_id_eagerly_and_emits_load() {
|
||||
assert!(app.agents[&id].session.session_id.is_none());
|
||||
assert!(!app.agents[&id].session.loading_replay);
|
||||
|
||||
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-fork");
|
||||
let session_cwd = PathBuf::from("/tmp/grok-worktrees/pager-fork/sub");
|
||||
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-fork");
|
||||
let session_cwd = PathBuf::from("/tmp/kigi-worktrees/pager-fork/sub");
|
||||
let effects = dispatch(
|
||||
Action::TaskComplete(TaskResult::WorktreeForked {
|
||||
agent_id: id,
|
||||
@@ -76,8 +76,8 @@ fn worktree_forked_with_restore_shows_summary_in_scrollback() {
|
||||
);
|
||||
let id = AgentId(0);
|
||||
|
||||
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-fork");
|
||||
let session_cwd = PathBuf::from("/tmp/grok-worktrees/pager-fork/sub");
|
||||
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-fork");
|
||||
let session_cwd = PathBuf::from("/tmp/kigi-worktrees/pager-fork/sub");
|
||||
let effects = dispatch(
|
||||
Action::TaskComplete(TaskResult::WorktreeForked {
|
||||
agent_id: id,
|
||||
@@ -130,8 +130,8 @@ fn worktree_forked_with_restore_failure_shows_warning_banner() {
|
||||
);
|
||||
let id = AgentId(0);
|
||||
|
||||
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-fail");
|
||||
let session_cwd = PathBuf::from("/tmp/grok-worktrees/pager-fail/sub");
|
||||
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-fail");
|
||||
let session_cwd = PathBuf::from("/tmp/kigi-worktrees/pager-fail/sub");
|
||||
dispatch(
|
||||
Action::TaskComplete(TaskResult::WorktreeForked {
|
||||
agent_id: id,
|
||||
@@ -472,7 +472,7 @@ fn open_fork_question_refuses_when_existing_question_is_open() {
|
||||
let mut app = fork_test_app();
|
||||
// Plant an existing question (e.g. an ACP-driven one).
|
||||
use crate::views::question_view::QuestionViewState;
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "existing ACP question?".into(),
|
||||
options: vec![QuestionOption {
|
||||
@@ -1047,7 +1047,7 @@ fn fork_session_failed_pushes_turn_failed_block() {
|
||||
#[test]
|
||||
fn translate_local_submit_yes_returns_worktree_true_action() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..2)
|
||||
@@ -1090,7 +1090,7 @@ fn translate_local_submit_yes_returns_worktree_true_action() {
|
||||
#[test]
|
||||
fn translate_local_submit_no_returns_worktree_false_action() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..2)
|
||||
@@ -1131,7 +1131,7 @@ fn translate_local_submit_no_returns_worktree_false_action() {
|
||||
#[test]
|
||||
fn translate_local_submit_always_returns_persist_always_for_fork() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..4)
|
||||
@@ -1173,7 +1173,7 @@ fn translate_local_submit_always_returns_persist_always_for_fork() {
|
||||
#[test]
|
||||
fn translate_local_submit_never_returns_persist_never_for_fork() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..4)
|
||||
@@ -1216,7 +1216,7 @@ fn translate_local_submit_never_returns_persist_never_for_fork() {
|
||||
fn handle_ask_user_question_pushes_system_block_when_displaced_local_fork_modal() {
|
||||
use crate::scrollback::block::RenderBlock;
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
|
||||
let mut app = fork_test_app();
|
||||
let id = AgentId(0);
|
||||
|
||||
@@ -172,7 +172,7 @@ fn worktree_session_created_sets_session_and_cwd() {
|
||||
&mut app,
|
||||
);
|
||||
let id = AgentId(0);
|
||||
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-123");
|
||||
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-123");
|
||||
let session_cwd = worktree_path.clone();
|
||||
let effects = dispatch(
|
||||
Action::TaskComplete(TaskResult::WorktreeSessionCreated {
|
||||
@@ -353,13 +353,13 @@ fn worktree_session_created_drains_queued_prompts() {
|
||||
let effects = dispatch(Action::SendPrompt("hello".into()), &mut app);
|
||||
assert!(effects.is_empty(), "no session_id yet, can't drain");
|
||||
assert_eq!(app.agents[&id].session.queue_len(), 1);
|
||||
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-abc");
|
||||
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-abc");
|
||||
let effects = dispatch(
|
||||
Action::TaskComplete(TaskResult::WorktreeSessionCreated {
|
||||
agent_id: id,
|
||||
session_id: acp::SessionId::new("wt-drain-1"),
|
||||
worktree_path,
|
||||
session_cwd: PathBuf::from("/tmp/grok-worktrees/pager-abc"),
|
||||
session_cwd: PathBuf::from("/tmp/kigi-worktrees/pager-abc"),
|
||||
models: None,
|
||||
}),
|
||||
&mut app,
|
||||
@@ -485,7 +485,7 @@ fn switch_model_without_session_does_nothing() {
|
||||
let mut app = test_app_with_agent();
|
||||
let id = AgentId(0);
|
||||
app.agents.get_mut(&id).unwrap().session.session_id = None;
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
let effects = dispatch(
|
||||
Action::SwitchModel {
|
||||
model_id,
|
||||
@@ -659,7 +659,7 @@ fn new_session_starts_with_prompt_focused() {
|
||||
fn switch_model_deferred_when_no_session_id() {
|
||||
let mut app = test_app_with_agent();
|
||||
let id = AgentId(0);
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
app.agents.get_mut(&id).unwrap().session.session_id = None;
|
||||
let effects = dispatch(
|
||||
Action::SwitchModel {
|
||||
@@ -679,7 +679,7 @@ fn switch_model_deferred_when_no_session_id() {
|
||||
fn deferred_model_switch_applied_on_session_created() {
|
||||
let mut app = test_app_with_agent();
|
||||
let id = AgentId(0);
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
let session_id: acp::SessionId = "new-session".into();
|
||||
app.agents.get_mut(&id).unwrap().session.session_id = None;
|
||||
app.agents
|
||||
@@ -708,7 +708,7 @@ fn deferred_model_switch_applied_on_session_created() {
|
||||
#[test]
|
||||
fn deferred_model_switch_applied_on_worktree_session_created() {
|
||||
let mut app = test_app_git();
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
dispatch(
|
||||
Action::NewWorktreeSession {
|
||||
load_session_id: None,
|
||||
@@ -1314,7 +1314,7 @@ fn dispatch_new_session_has_empty_scrollback() {
|
||||
#[test]
|
||||
fn translate_local_submit_always_returns_persist_always_for_new_session() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..4)
|
||||
@@ -1354,7 +1354,7 @@ fn translate_local_submit_always_returns_persist_always_for_new_session() {
|
||||
#[test]
|
||||
fn translate_local_submit_never_returns_persist_never_for_new_session() {
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "?".into(),
|
||||
options: (0..4)
|
||||
|
||||
@@ -1214,7 +1214,7 @@ fn project_picker_skip_falls_back_to_original_cwd() {
|
||||
fn project_picker_freeform_path_used_when_no_option_selected() {
|
||||
use crate::views::prompt_widget::StashedPrompt;
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let q = Question {
|
||||
question: "Pick".into(),
|
||||
id: None,
|
||||
@@ -1253,7 +1253,7 @@ fn project_picker_freeform_path_used_when_no_option_selected() {
|
||||
fn project_picker_freeform_overrides_dont_ask() {
|
||||
use crate::views::prompt_widget::StashedPrompt;
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionSelection, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let opt = |label: &str| QuestionOption {
|
||||
label: label.into(),
|
||||
description: String::new(),
|
||||
@@ -1310,7 +1310,7 @@ fn needs_project_picker_false_when_disabled() {
|
||||
fn project_picker_dont_ask_again_sets_disable_flag() {
|
||||
use crate::views::prompt_widget::StashedPrompt;
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionSelection, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let opt = |label: &str| QuestionOption {
|
||||
label: label.into(),
|
||||
description: String::new(),
|
||||
@@ -1348,7 +1348,7 @@ fn project_picker_dont_ask_again_sets_disable_flag() {
|
||||
fn project_picker_recent_project_selection_uses_that_path() {
|
||||
use crate::views::prompt_widget::StashedPrompt;
|
||||
use crate::views::question_view::{LocalQuestionKind, QuestionSelection, QuestionViewState};
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
|
||||
let opt = |label: &str| QuestionOption {
|
||||
label: label.into(),
|
||||
description: String::new(),
|
||||
|
||||
@@ -24,7 +24,7 @@ fn model_with_support(id: &str, supports: bool) -> (acp::ModelId, acp::ModelInfo
|
||||
}
|
||||
|
||||
fn models_with_current(supports: bool) -> ModelState {
|
||||
let (id, info) = model_with_support("grok-build", supports);
|
||||
let (id, info) = model_with_support("kigi", supports);
|
||||
let mut models = ModelState::default();
|
||||
models.available.insert(id.clone(), info);
|
||||
models.current = Some(id);
|
||||
|
||||
@@ -227,7 +227,7 @@ fn set_default_model_allowed_when_agent_chat_kind() {
|
||||
fn slash_model_valid_dispatches_set_default_model_with_switch_and_persist() {
|
||||
let mut app = test_app_with_agent();
|
||||
let id = AgentId(0);
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
|
||||
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
|
||||
app.agents
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
@@ -236,9 +236,9 @@ fn slash_model_valid_dispatches_set_default_model_with_switch_and_persist() {
|
||||
.available
|
||||
.insert(
|
||||
model_id.clone(),
|
||||
acp::ModelInfo::new(model_id.clone(), "Grok 4.5".to_string()),
|
||||
acp::ModelInfo::new(model_id.clone(), "Kigi 4.5".to_string()),
|
||||
);
|
||||
let effects = dispatch(Action::SendPrompt("/model Grok 4.5".into()), &mut app);
|
||||
let effects = dispatch(Action::SendPrompt("/model Kigi 4.5".into()), &mut app);
|
||||
assert_eq!(
|
||||
effects.len(),
|
||||
2,
|
||||
@@ -904,8 +904,8 @@ fn clear_default_model_persists_but_keeps_live_current() {
|
||||
use agent_client_protocol as acp;
|
||||
use std::sync::Arc;
|
||||
let mut app = test_app_with_agent();
|
||||
let id = acp::ModelId::new(Arc::from("grok-test"));
|
||||
let info = acp::ModelInfo::new(id.clone(), "Grok Test".to_string());
|
||||
let id = acp::ModelId::new(Arc::from("kigi-test"));
|
||||
let info = acp::ModelInfo::new(id.clone(), "Kigi Test".to_string());
|
||||
let agent_id = AgentId(0);
|
||||
app.agents
|
||||
.get_mut(&agent_id)
|
||||
@@ -949,8 +949,8 @@ fn set_default_model_resolves_known_name() {
|
||||
use agent_client_protocol as acp;
|
||||
use std::sync::Arc;
|
||||
let mut app = test_app_with_agent();
|
||||
let id = acp::ModelId::new(Arc::from("grok-4.5"));
|
||||
let info = acp::ModelInfo::new(id.clone(), "Grok 4.5".to_string());
|
||||
let id = acp::ModelId::new(Arc::from("kigi-4.5"));
|
||||
let info = acp::ModelInfo::new(id.clone(), "Kigi 4.5".to_string());
|
||||
let agent_id = AgentId(0);
|
||||
app.agents
|
||||
.get_mut(&agent_id)
|
||||
@@ -963,7 +963,7 @@ fn set_default_model_resolves_known_name() {
|
||||
assert_eq!(effects.len(), 2);
|
||||
assert!(
|
||||
matches!(& effects[0], Effect::PersistSetting { key : "default_model", value :
|
||||
crate ::settings::SettingValue::String(s), .. } if s == "grok-4.5")
|
||||
crate ::settings::SettingValue::String(s), .. } if s == "kigi-4.5")
|
||||
);
|
||||
assert!(matches!(& effects[1], Effect::SwitchModel { model_id : mid, .. } if mid == & id));
|
||||
assert_eq!(app.agents[&agent_id].session.models.current, Some(id));
|
||||
@@ -976,8 +976,8 @@ fn set_default_model_idempotent_when_already_current() {
|
||||
use agent_client_protocol as acp;
|
||||
use std::sync::Arc;
|
||||
let mut app = test_app_with_agent();
|
||||
let id = acp::ModelId::new(Arc::from("grok-already"));
|
||||
let info = acp::ModelInfo::new(id.clone(), "Grok Already".to_string());
|
||||
let id = acp::ModelId::new(Arc::from("kigi-already"));
|
||||
let info = acp::ModelInfo::new(id.clone(), "Kigi Already".to_string());
|
||||
let agent_id = AgentId(0);
|
||||
app.agents
|
||||
.get_mut(&agent_id)
|
||||
@@ -2666,16 +2666,16 @@ fn dispatch_cycle_mode_refreshes_open_modal_snapshot() {
|
||||
"current_value_for must read the refreshed snapshot",
|
||||
);
|
||||
}
|
||||
/// `dispatch(Action::SetTheme("grokday"), &mut app)` emits
|
||||
/// `dispatch(Action::SetTheme("kigiday"), &mut app)` emits
|
||||
/// exactly one `Effect::PersistSetting`, mutates
|
||||
/// `app.current_ui.theme`, fires a toast, and toggles AUTO_MODE
|
||||
/// off (kind is concrete).
|
||||
///
|
||||
/// Note: we persist `grokday` (a non-truecolor theme) here
|
||||
/// Note: we persist `kigiday` (a non-truecolor theme) here
|
||||
/// because `Effect::PersistSetting`'s payload is `&'static str`
|
||||
/// from the registry's canonical table — the persisted CANONICAL
|
||||
/// is what we're asserting, NOT the live theme cache (which
|
||||
/// `clamp_to_terminal` might fold to GrokNight in non-truecolor
|
||||
/// `clamp_to_terminal` might fold to KigiNight in non-truecolor
|
||||
/// test environments). Persist + canonical contract is the test
|
||||
/// invariant; the cache contract is exercised separately by the
|
||||
/// `*_applies_when_*` tests.
|
||||
@@ -2685,8 +2685,8 @@ fn set_theme_emits_persist_setting_with_correct_payload() {
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
assert_eq!(app.current_ui.theme, None);
|
||||
crate::theme::cache::set(crate::theme::ThemeKind::GrokNight);
|
||||
let effects = dispatch(Action::SetTheme("grokday".into()), &mut app);
|
||||
crate::theme::cache::set(crate::theme::ThemeKind::KigiNight);
|
||||
let effects = dispatch(Action::SetTheme("kigiday".into()), &mut app);
|
||||
assert_eq!(effects.len(), 1);
|
||||
match &effects[0] {
|
||||
Effect::PersistSetting {
|
||||
@@ -2695,12 +2695,12 @@ fn set_theme_emits_persist_setting_with_correct_payload() {
|
||||
rollback_value,
|
||||
} => {
|
||||
assert_eq!(*key, "theme");
|
||||
assert_eq!(*value, SettingValue::Enum("grokday"));
|
||||
assert_eq!(*rollback_value, SettingValue::Enum("groknight"));
|
||||
assert_eq!(*value, SettingValue::Enum("kigiday"));
|
||||
assert_eq!(*rollback_value, SettingValue::Enum("kiginight"));
|
||||
}
|
||||
other => panic!("expected PersistSetting, got {other:?}"),
|
||||
}
|
||||
assert_eq!(app.current_ui.theme.as_deref(), Some("grokday"));
|
||||
assert_eq!(app.current_ui.theme.as_deref(), Some("kigiday"));
|
||||
assert!(
|
||||
!crate::theme::cache::is_auto_mode(),
|
||||
"concrete theme commit must disable AUTO_MODE",
|
||||
@@ -2708,7 +2708,7 @@ fn set_theme_emits_persist_setting_with_correct_payload() {
|
||||
});
|
||||
}
|
||||
/// Same payload contract as `set_theme_emits_persist_setting_with_correct_payload`
|
||||
/// for the auto-dark sibling. Uses `grokday` to avoid the
|
||||
/// for the auto-dark sibling. Uses `kigiday` to avoid the
|
||||
/// `clamp_to_terminal` ambiguity in non-truecolor test envs;
|
||||
/// `apply_kind` doesn't fire here anyway (parent theme is not
|
||||
/// auto by default) but using a non-truecolor canonical keeps
|
||||
@@ -2718,7 +2718,7 @@ fn set_auto_dark_theme_emits_persist_setting_with_correct_payload() {
|
||||
use crate::settings::SettingValue;
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let effects = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
|
||||
let effects = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
|
||||
assert_eq!(effects.len(), 1);
|
||||
match &effects[0] {
|
||||
Effect::PersistSetting {
|
||||
@@ -2727,12 +2727,12 @@ fn set_auto_dark_theme_emits_persist_setting_with_correct_payload() {
|
||||
rollback_value,
|
||||
} => {
|
||||
assert_eq!(*key, "auto_dark_theme");
|
||||
assert_eq!(*value, SettingValue::Enum("grokday"));
|
||||
assert_eq!(*rollback_value, SettingValue::Enum("groknight"));
|
||||
assert_eq!(*value, SettingValue::Enum("kigiday"));
|
||||
assert_eq!(*rollback_value, SettingValue::Enum("kiginight"));
|
||||
}
|
||||
other => panic!("expected PersistSetting, got {other:?}"),
|
||||
}
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
|
||||
});
|
||||
}
|
||||
#[test]
|
||||
@@ -2740,7 +2740,7 @@ fn set_auto_light_theme_emits_persist_setting_with_correct_payload() {
|
||||
use crate::settings::SettingValue;
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let effects = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
|
||||
let effects = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
|
||||
assert_eq!(effects.len(), 1);
|
||||
match &effects[0] {
|
||||
Effect::PersistSetting {
|
||||
@@ -2749,14 +2749,14 @@ fn set_auto_light_theme_emits_persist_setting_with_correct_payload() {
|
||||
rollback_value,
|
||||
} => {
|
||||
assert_eq!(*key, "auto_light_theme");
|
||||
assert_eq!(*value, SettingValue::Enum("groknight"));
|
||||
assert_eq!(*rollback_value, SettingValue::Enum("grokday"));
|
||||
assert_eq!(*value, SettingValue::Enum("kiginight"));
|
||||
assert_eq!(*rollback_value, SettingValue::Enum("kigiday"));
|
||||
}
|
||||
other => panic!("expected PersistSetting, got {other:?}"),
|
||||
}
|
||||
assert_eq!(
|
||||
app.current_ui.auto_light_theme.as_deref(),
|
||||
Some("groknight"),
|
||||
Some("kiginight"),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -2809,11 +2809,11 @@ fn preview_auto_light_theme_emits_no_persist_and_no_current_ui_mutation() {
|
||||
/// Auto-theme commit applies the live theme **only** when
|
||||
/// `theme="auto"` AND the system is in the matching mode.
|
||||
///
|
||||
/// Scenario: `theme="groknight"` (concrete) + system=Dark. User
|
||||
/// commits `auto_dark_theme="grokday"`. The setting is dormant
|
||||
/// Scenario: `theme="kiginight"` (concrete) + system=Dark. User
|
||||
/// commits `auto_dark_theme="kigiday"`. The setting is dormant
|
||||
/// (parent theme is concrete, not auto), so the live display
|
||||
/// must stay on GrokNight even though we're committing a
|
||||
/// different theme. Uses `grokday` to avoid `clamp_to_terminal`
|
||||
/// must stay on KigiNight even though we're committing a
|
||||
/// different theme. Uses `kigiday` to avoid `clamp_to_terminal`
|
||||
/// ambiguity in non-truecolor envs.
|
||||
#[test]
|
||||
fn set_auto_dark_theme_does_not_apply_when_theme_is_not_auto() {
|
||||
@@ -2822,28 +2822,28 @@ fn set_auto_dark_theme_does_not_apply_when_theme_is_not_auto() {
|
||||
crate::theme::system_appearance::SystemAppearance::Dark,
|
||||
));
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetTheme("groknight".into()), &mut app);
|
||||
let _ = dispatch(Action::SetTheme("kiginight".into()), &mut app);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokNight,
|
||||
crate::theme::ThemeKind::KigiNight,
|
||||
);
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokNight,
|
||||
crate::theme::ThemeKind::KigiNight,
|
||||
"auto_dark_theme commit must NOT change live display when theme is not auto",
|
||||
);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
|
||||
});
|
||||
}
|
||||
/// Auto-theme commit DOES apply the live theme when both
|
||||
/// (a) parent theme = auto AND (b) system matches.
|
||||
///
|
||||
/// Uses `GrokDay` (non-truecolor-requiring) for the dark-mode
|
||||
/// Uses `KigiDay` (non-truecolor-requiring) for the dark-mode
|
||||
/// fixture: the test environment's color detection may not report
|
||||
/// truecolor support, and `Theme::apply_kind` clamps
|
||||
/// truecolor-only themes (TokyoNight, RosePineMoon) down to
|
||||
/// GrokNight. Using a non-truecolor theme avoids the clamp
|
||||
/// KigiNight. Using a non-truecolor theme avoids the clamp
|
||||
/// uncertainty. The "live apply" contract is what we're testing
|
||||
/// — the specific theme picked is incidental.
|
||||
#[test]
|
||||
@@ -2857,21 +2857,21 @@ fn set_auto_dark_theme_applies_when_theme_is_auto_and_system_is_dark() {
|
||||
assert!(crate::theme::cache::is_auto_mode());
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokNight,
|
||||
crate::theme::ThemeKind::KigiNight,
|
||||
);
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokDay,
|
||||
crate::theme::ThemeKind::KigiDay,
|
||||
"auto_dark_theme commit must update live display when theme=auto + system=Dark",
|
||||
);
|
||||
});
|
||||
}
|
||||
/// Auto-theme commit does NOT apply when system is in the
|
||||
/// non-matching mode (auto_dark_theme + system=Light).
|
||||
/// Uses `groknight` for the auto_dark_theme
|
||||
/// Uses `kiginight` for the auto_dark_theme
|
||||
/// value to avoid `clamp_to_terminal` ambiguity (we want a
|
||||
/// concrete kind that's clearly different from GrokDay, the
|
||||
/// concrete kind that's clearly different from KigiDay, the
|
||||
/// active resolved theme).
|
||||
#[test]
|
||||
fn set_auto_dark_theme_does_not_apply_when_system_is_light() {
|
||||
@@ -2883,20 +2883,20 @@ fn set_auto_dark_theme_does_not_apply_when_system_is_light() {
|
||||
let _ = dispatch(Action::SetTheme("auto".into()), &mut app);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokDay,
|
||||
crate::theme::ThemeKind::KigiDay,
|
||||
);
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("groknight".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("kiginight".into()), &mut app);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokDay,
|
||||
crate::theme::ThemeKind::KigiDay,
|
||||
"auto_dark_theme commit must NOT change live display when system=Light",
|
||||
);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("groknight"),);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kiginight"),);
|
||||
});
|
||||
}
|
||||
/// Symmetric to the dark test: `set_auto_light_theme` applies only
|
||||
/// when theme=auto + system=Light. Uses a non-truecolor theme
|
||||
/// (`groknight`) for the same clamp reason as the dark variant.
|
||||
/// (`kiginight`) for the same clamp reason as the dark variant.
|
||||
#[test]
|
||||
fn set_auto_light_theme_applies_when_theme_is_auto_and_system_is_light() {
|
||||
with_theme_test_env(|| {
|
||||
@@ -2907,12 +2907,12 @@ fn set_auto_light_theme_applies_when_theme_is_auto_and_system_is_light() {
|
||||
let _ = dispatch(Action::SetTheme("auto".into()), &mut app);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokDay,
|
||||
crate::theme::ThemeKind::KigiDay,
|
||||
);
|
||||
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokNight,
|
||||
crate::theme::ThemeKind::KigiNight,
|
||||
"auto_light_theme must update display when theme=auto + system=Light",
|
||||
);
|
||||
});
|
||||
@@ -2986,7 +2986,7 @@ fn set_auto_light_theme_rejects_auto_value() {
|
||||
fn set_theme_toast_format_uses_display_name() {
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetTheme("grokday".into()), &mut app);
|
||||
let _ = dispatch(Action::SetTheme("kigiday".into()), &mut app);
|
||||
let toast = read_toast(&app);
|
||||
assert!(
|
||||
toast.contains("Theme"),
|
||||
@@ -2994,7 +2994,7 @@ fn set_theme_toast_format_uses_display_name() {
|
||||
);
|
||||
assert!(
|
||||
toast.contains("Kigi Day"),
|
||||
"toast must use display name `Kigi Day`, not canonical `grokday`, got: {toast:?}",
|
||||
"toast must use display name `Kigi Day`, not canonical `kigiday`, got: {toast:?}",
|
||||
);
|
||||
assert!(toast.contains('\u{2713}'), "toast must contain the ✓ glyph");
|
||||
});
|
||||
@@ -3003,7 +3003,7 @@ fn set_theme_toast_format_uses_display_name() {
|
||||
fn set_auto_dark_theme_toast_format_uses_display_name() {
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
|
||||
let toast = read_toast(&app);
|
||||
assert!(toast.contains("Auto dark theme"));
|
||||
assert!(toast.contains("Kigi Day"));
|
||||
@@ -3014,7 +3014,7 @@ fn set_auto_dark_theme_toast_format_uses_display_name() {
|
||||
fn set_auto_light_theme_toast_format_uses_display_name() {
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
|
||||
let toast = read_toast(&app);
|
||||
assert!(toast.contains("Auto light theme"));
|
||||
assert!(toast.contains("Kigi Night"));
|
||||
@@ -3024,7 +3024,7 @@ fn set_auto_light_theme_toast_format_uses_display_name() {
|
||||
/// reverts `app.current_ui.theme` AND the live cache (mirror of
|
||||
/// `rollback_known_key_reverts_cache_and_no_effect`).
|
||||
///
|
||||
/// Uses non-truecolor themes (`grokday` ↔ `groknight`) to avoid
|
||||
/// Uses non-truecolor themes (`kigiday` ↔ `kiginight`) to avoid
|
||||
/// the `clamp_to_terminal` interaction in test environments that
|
||||
/// don't report truecolor support.
|
||||
#[test]
|
||||
@@ -3032,28 +3032,28 @@ fn rollback_theme_reverts_current_ui_and_cache() {
|
||||
use crate::settings::SettingValue;
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetTheme("grokday".into()), &mut app);
|
||||
assert_eq!(app.current_ui.theme.as_deref(), Some("grokday"));
|
||||
let _ = dispatch(Action::SetTheme("kigiday".into()), &mut app);
|
||||
assert_eq!(app.current_ui.theme.as_deref(), Some("kigiday"));
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokDay,
|
||||
crate::theme::ThemeKind::KigiDay,
|
||||
);
|
||||
let _ = dispatch(
|
||||
Action::TaskComplete(TaskResult::SettingPersistFailed {
|
||||
key: "theme",
|
||||
rollback_value: SettingValue::Enum("groknight"),
|
||||
rollback_value: SettingValue::Enum("kiginight"),
|
||||
error: "disk full".into(),
|
||||
}),
|
||||
&mut app,
|
||||
);
|
||||
assert_eq!(
|
||||
app.current_ui.theme.as_deref(),
|
||||
Some("groknight"),
|
||||
Some("kiginight"),
|
||||
"rollback must update app.current_ui.theme",
|
||||
);
|
||||
assert_eq!(
|
||||
crate::theme::cache::current_kind(),
|
||||
crate::theme::ThemeKind::GrokNight,
|
||||
crate::theme::ThemeKind::KigiNight,
|
||||
"rollback must update the live theme cache too",
|
||||
);
|
||||
});
|
||||
@@ -3063,17 +3063,17 @@ fn rollback_auto_dark_theme_reverts_current_ui() {
|
||||
use crate::settings::SettingValue;
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
|
||||
let _ = dispatch(
|
||||
Action::TaskComplete(TaskResult::SettingPersistFailed {
|
||||
key: "auto_dark_theme",
|
||||
rollback_value: SettingValue::Enum("groknight"),
|
||||
rollback_value: SettingValue::Enum("kiginight"),
|
||||
error: "disk full".into(),
|
||||
}),
|
||||
&mut app,
|
||||
);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("groknight"),);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kiginight"),);
|
||||
});
|
||||
}
|
||||
#[test]
|
||||
@@ -3081,20 +3081,20 @@ fn rollback_auto_light_theme_reverts_current_ui() {
|
||||
use crate::settings::SettingValue;
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
|
||||
assert_eq!(
|
||||
app.current_ui.auto_light_theme.as_deref(),
|
||||
Some("groknight"),
|
||||
Some("kiginight"),
|
||||
);
|
||||
let _ = dispatch(
|
||||
Action::TaskComplete(TaskResult::SettingPersistFailed {
|
||||
key: "auto_light_theme",
|
||||
rollback_value: SettingValue::Enum("grokday"),
|
||||
rollback_value: SettingValue::Enum("kigiday"),
|
||||
error: "disk full".into(),
|
||||
}),
|
||||
&mut app,
|
||||
);
|
||||
assert_eq!(app.current_ui.auto_light_theme.as_deref(), Some("grokday"));
|
||||
assert_eq!(app.current_ui.auto_light_theme.as_deref(), Some("kigiday"));
|
||||
});
|
||||
}
|
||||
/// Edge case — if the rollback value is
|
||||
@@ -3109,8 +3109,8 @@ fn rollback_auto_dark_theme_with_auto_value_clears_to_none() {
|
||||
use crate::settings::SettingValue;
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
|
||||
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
|
||||
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
|
||||
let _ = dispatch(
|
||||
Action::TaskComplete(TaskResult::SettingPersistFailed {
|
||||
key: "auto_dark_theme",
|
||||
@@ -3131,10 +3131,10 @@ fn rollback_auto_light_theme_with_auto_value_clears_to_none() {
|
||||
use crate::settings::SettingValue;
|
||||
with_theme_test_env(|| {
|
||||
let mut app = test_app_with_agent();
|
||||
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
|
||||
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
|
||||
assert_eq!(
|
||||
app.current_ui.auto_light_theme.as_deref(),
|
||||
Some("groknight"),
|
||||
Some("kiginight"),
|
||||
);
|
||||
let _ = dispatch(
|
||||
Action::TaskComplete(TaskResult::SettingPersistFailed {
|
||||
|
||||
@@ -198,16 +198,16 @@ fn dispatch_confirm_reset_setting_reset_dispatches_typed_setter_for_shared_enum(
|
||||
&mut app,
|
||||
);
|
||||
|
||||
// Reset → SetTheme("groknight") (the registered default).
|
||||
// Reset → SetTheme("kiginight") (the registered default).
|
||||
assert_eq!(effects.len(), 1);
|
||||
match &effects[0] {
|
||||
Effect::PersistSetting { key, value, .. } => {
|
||||
assert_eq!(*key, "theme");
|
||||
assert_eq!(value, &SettingValue::Enum("groknight"));
|
||||
assert_eq!(value, &SettingValue::Enum("kiginight"));
|
||||
}
|
||||
other => panic!("expected PersistSetting, got {other:?}"),
|
||||
}
|
||||
assert_eq!(app.current_ui.theme.as_deref(), Some("groknight"));
|
||||
assert_eq!(app.current_ui.theme.as_deref(), Some("kiginight"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user