Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,72 @@ Full documentation: [`docs/adapters/kimi-code.md`](docs/adapters/kimi-code.md)

</details>

<details>
<summary><strong>Mistral Vibe</strong> — MCP + hooks (TOML config, native <code>[[hooks]]</code> array tables)</summary>

**Prerequisites:** Python 3.12+, [Mistral Vibe](https://github.com/mistralai/mistral-vibe) installed (`uv tool install mistral-vibe`), Node.js >= 22.5 (or Bun).

1. Install context-mode:

```bash
npm install -g context-mode
```

2. Add context-mode as an MCP server. Add to `~/.vibe/config.toml` (or `$VIBE_HOME/config.toml`):

```toml
[[mcp_servers]]
name = "context-mode"
transport = "stdio"
command = "context-mode"
```

3. Add hooks to `~/.vibe/hooks.toml` (or `$VIBE_HOME/hooks.toml`):

```toml
[[hooks]]
name = "context-mode-pretool"
type = "pre_tool"
match = "bash"
command = "context-mode hook mistral-vibe pretooluse"
timeout = 30.0
strict = false
description = "Route bash commands through context-mode sandbox"

[[hooks]]
name = "context-mode-posttool"
type = "post_tool"
command = "context-mode hook mistral-vibe posttooluse"
timeout = 30.0
strict = false
description = "Capture session events for context-mode"
```

Or run `context-mode upgrade` (with `CONTEXT_MODE_PLATFORM=mistral-vibe`) to have context-mode manage the `hooks.toml` entries for you. The upgrade flow preserves any non-managed hooks (e.g. a user-installed `rtk-rewrite`).

4. Restart Vibe and verify MCP with `ctx stats`.

5. (Optional) Copy the routing instructions file for your project:

```bash
cp "$(npm root -g)/context-mode/configs/mistral-vibe/AGENTS.md" ./AGENTS.md
```

Vibe reads `AGENTS.md` from the project root by default.

**Verify:** In a Vibe session, type `ctx stats`. Context-mode tools (namespaced as `context-mode_ctx_*`) should appear and respond.

**Routing:** `pre_tool` intercepts bash and rewrites `curl`/`wget`/inline HTTP to sandbox tools. `post_tool` captures session events. Auto-detected via `$VIBE_HOME` env var or the presence of `~/.vibe/`. MCP `clientInfo.name` is NOT reliable — Vibe uses the MCP SDK default `"mcp"` because it does not override `client_info`; detection relies on env/config-dir signals instead.

**Known limitations:**
- Vibe only exposes `pre_tool`, `post_tool`, and `post_agent`. There is no `PreCompact`, `SessionStart`, `UserPromptSubmit`, or `Stop` equivalent — full session-continuity across compactions is not available; the routing block in `AGENTS.md` provides model-side memory queries as a workaround.
- The `pre_tool` `match` field is one string per `[[hooks]]` block. context-mode installs a single `bash`-scoped hook; register additional blocks pointing at the same `pretooluse` command if you need broader tool coverage.
- Coexists with `rtk`: keep `context-mode-pretool` first in `hooks.toml` declaration order so redirected commands never reach rtk.

Full documentation: [`docs/platform-support.md#mistral-vibe`](docs/platform-support.md#mistral-vibe)

</details>

<details>
<summary><strong>Qwen Code</strong> — MCP + hooks (identical wire protocol to Claude Code)</summary>

Expand Down
357 changes: 181 additions & 176 deletions cli.bundle.mjs

Large diffs are not rendered by default.

80 changes: 78 additions & 2 deletions docs/platform-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ This document provides a comprehensive comparison of all platforms supported by

## Overview

context-mode supports 17 client platforms, plus the OpenClaw gateway integration, across three hook paradigms:
context-mode supports 18 client platforms, plus the OpenClaw gateway integration, across three hook paradigms:

| Paradigm | Platforms |
|----------|-----------|
| **JSON stdin/stdout** | Claude Code, Gemini CLI, VS Code Copilot, JetBrains Copilot, GitHub Copilot CLI, Cursor, Codex CLI, Qwen Code, Kimi Code, Antigravity CLI (`agy`), Kiro |
| **JSON stdin/stdout** | Claude Code, Gemini CLI, VS Code Copilot, JetBrains Copilot, GitHub Copilot CLI, Cursor, Codex CLI, Qwen Code, Kimi Code, Mistral Vibe, Antigravity CLI (`agy`), Kiro |
| **TS Plugin** | OpenCode, KiloCode, OpenClaw |
| **MCP-only** | Antigravity, Zed, Pi, OMP (Oh My Pi) |

Expand Down Expand Up @@ -301,6 +301,82 @@ context-mode hook kimi stop

---

### Mistral Vibe

**Status:** Supported (JSON stdin/stdout hooks + MCP)

**Hook Paradigm:** JSON stdin/stdout (`[[hooks]]` array tables in TOML)

Mistral Vibe is Mistral AI's official open-source coding CLI ([mistralai/mistral-vibe](https://github.com/mistralai/mistral-vibe), package `mistral-vibe` on PyPI, command `vibe`). It exposes three lifecycle hooks (`pre_tool`, `post_tool`, `post_agent`) as subprocesses with JSON payloads on stdin, and full MCP support via stdio/HTTP/streamable-http transports declared under `[[mcp_servers]]` in `config.toml`.

Compared to Claude Code / Kimi, Vibe has a narrower hook surface: no `PreCompact`, `SessionStart`, `UserPromptSubmit`, or `Stop` equivalents. Session-restore-after-compaction relies on model-side memory queries (see `configs/mistral-vibe/AGENTS.md`) rather than a hook-driven snapshot pipeline. All other context-mode features (routing enforcement, sandbox tools, session-event capture) work end-to-end.

**Hook Names:**
- `pre_tool` — fires before a tool call; supports rewrite via `hook_specific_output.tool_input` and deny via `decision: "deny"`
- `post_tool` — fires after tool completion; supports append-only text via `hook_specific_output.additional_context`
- `post_agent` — fires after each agent turn (not used by context-mode today)

**Blocking:** `{"decision":"deny","reason":"..."}` on stdout, exit 0

**Arg Modification:** `{"hook_specific_output":{"tool_input":{...}}}` on stdout, exit 0

**Output Modification:** Append-only via `hook_specific_output.additional_context` in `post_tool` (no full-output rewrite surface)

**Context Injection:** Not supported (`system_message` is a UI banner, not model-visible context)

**Configuration:**
- MCP: `~/.vibe/config.toml` `[[mcp_servers]]` (or `$VIBE_HOME/config.toml`)
- Hooks: `~/.vibe/hooks.toml` `[[hooks]]` (or `$VIBE_HOME/hooks.toml`)
- Sessions: `~/.vibe/context-mode/sessions/`
- Instruction file: project-root `AGENTS.md` (Vibe's default)

**Hook Commands:**
```
context-mode hook mistral-vibe pretooluse
context-mode hook mistral-vibe posttooluse
```

**Example `hooks.toml`:**
```toml
[[hooks]]
name = "context-mode-pretool"
type = "pre_tool"
match = "bash"
command = "context-mode hook mistral-vibe pretooluse"
timeout = 30.0
strict = false
description = "Route bash commands through context-mode sandbox"

[[hooks]]
name = "context-mode-posttool"
type = "post_tool"
command = "context-mode hook mistral-vibe posttooluse"
timeout = 30.0
strict = false
description = "Capture session events for context-mode"
```

**Example `config.toml` MCP entry:**
```toml
[[mcp_servers]]
name = "context-mode"
transport = "stdio"
command = "context-mode"
```

**Detection:**
- `$VIBE_HOME` env var (consumer-set — `detect: false` so an unrelated shell does not misclassify a non-Vibe host as Vibe)
- `~/.vibe/` directory presence (medium confidence)
- MCP `clientInfo.name` is NOT reliable: Vibe uses the MCP SDK's default `Implementation(name="mcp", version="0.1.0")` because it does not pass a `client_info` argument to `ClientSession` (see `vibe/core/tools/mcp/tools.py`). The `client-map.ts` entries for `"mistral-vibe"` / `"Mistral Vibe"` / `"vibe"` are placeholders that will match if Vibe ever ships a proper client_info.

**Known Issues / Caveats:**
- No `PreCompact`, `SessionStart`, `UserPromptSubmit`, or `Stop` hook — full session continuity across compactions is not available; the routing block in `AGENTS.md` provides model-side memory queries as a workaround.
- `pre_tool` `match` is a single string per `[[hooks]]` block. context-mode installs one bash-scoped hook to minimize overhead on lightweight tools. If you need broader tool coverage, register additional `[[hooks]]` blocks pointing at the same `pretooluse` command with different `match` values.
- Coexistence with `rtk` (which also registers a `pre_tool bash` hook): Vibe runs hooks in TOML declaration order. Keep `context-mode-pretool` first in `hooks.toml` so redirected commands never reach rtk. Both can coexist; context-mode's redirect is a no-op passthrough when it doesn't apply.
- Session id is delivered in the hook stdin JSON payload (`session_id`) rather than an env var — the adapter falls back to `vibe-ppid-<pid>` when the payload lacks one.

---

### Qwen Code

**Status:** Supported (MCP + hooks — identical wire protocol to Claude Code)
Expand Down
15 changes: 15 additions & 0 deletions hooks/core/formatters.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,21 @@ export const formatters = {
agent_message: additionalContext,
}),
},

"mistral-vibe": {
deny: (reason) => ({
decision: "deny",
reason: reason ?? "Blocked by context-mode hook",
}),
ask: () => null,
modify: (updatedInput) => ({
hook_specific_output: { tool_input: updatedInput ?? {} },
system_message: "context-mode: routed to sandbox",
}),
context: (additionalContext) => ({
system_message: `context-mode: ${additionalContext}`,
}),
},
};

// Keep in sync with the identical agyContextReason in
Expand Down
4 changes: 4 additions & 0 deletions hooks/core/platform-detect.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const PLATFORM_ENV_VARS_MIRROR = [
["pi", ["PI_PROJECT_DIR"]],
// openclaw — no auto-set process env vars; falls through to default
// kiro — no auto-set process env vars; falls through to default
// mistral-vibe — VIBE_HOME is consumer-set; detection at MCP layer uses
// the ~/.vibe/ config-dir tier. Listed here only so hook scripts pick
// it up when a user explicitly points VIBE_HOME at a relocated install.
["mistral-vibe", ["VIBE_HOME"]],
];

export function detectPlatformFromEnv(env = process.env) {
Expand Down
1 change: 1 addition & 0 deletions hooks/core/tool-naming.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const TOOL_PREFIXES = {
"openclaw": (tool) => tool,
"pi": (tool) => tool,
"qwen-code": (tool) => `mcp__context-mode__${tool}`,
"mistral-vibe": (tool) => `context-mode_${tool}`,
};

/**
Expand Down
59 changes: 59 additions & 0 deletions hooks/mistral-vibe/posttooluse.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node
import "../suppress-stderr.mjs";
import "../ensure-deps.mjs";
/**
* Mistral Vibe PostToolUse hook — session event capture.
*
* Captures session events from tool calls and stores them in the per-project
* SessionDB for later resume snapshot building. Fire-and-forget: swallows all
* errors so the hook never blocks Vibe.
*/

import { readStdin } from "../core/stdin.mjs";
import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";

const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);

function resolveVibeSessionRoot() {
const override = process.env.VIBE_HOME;
const base = override ? override : join(homedir(), ".vibe");
return join(base, "context-mode", "sessions");
}

try {
const raw = await readStdin();
const input = JSON.parse(raw);
const projectDir = input.cwd ?? process.cwd();
const sessionId = input.session_id ?? `vibe-ppid-${process.ppid}`;

const { extractEvents } = await loadExtract();
const { resolveProjectAttributions } = await loadProjectAttribution();
const { SessionDB, resolveSessionDbPath } = await loadSessionDB();

const dbPath = resolveSessionDbPath({
projectDir,
sessionsDir: resolveVibeSessionRoot(),
});
const db = new SessionDB({ dbPath });
db.ensureSession(sessionId, projectDir);

const events = extractEvents({
tool_name: input.tool_name ?? "",
tool_input: input.tool_input ?? {},
tool_response: input.tool_output_text ?? "",
tool_output: input.tool_output
? { ...input.tool_output, isError: input.tool_status === "failure" }
: undefined,
});

attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
db.close();
} catch {
// fire-and-forget
}

process.stdout.write(JSON.stringify({}) + "\n");
40 changes: 40 additions & 0 deletions hooks/mistral-vibe/pretooluse.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env node
import "../suppress-stderr.mjs";
/**
* Mistral Vibe PreToolUse hook for context-mode.
*
* Vibe wire contract (https://docs.mistral.ai/vibe/code/cli/hooks):
* stdin = JSON with session_id, hook_event_name, transcript_path, cwd,
* parent_session_id, tool_name, tool_call_id, tool_input
* stdout = JSON with optional decision/reason/system_message/
* hook_specific_output (see adapters/mistral-vibe/hooks.ts for
* the full schema)
* exit 0 = always (Vibe treats non-zero as hook failure and drops output)
*/

import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { readStdin } from "../core/stdin.mjs";
import { routePreToolUse, initSecurity } from "../core/routing.mjs";
import { formatDecision } from "../core/formatters.mjs";

const __hookDir = dirname(fileURLToPath(import.meta.url));
await initSecurity(resolve(__hookDir, "..", "..", "build"));

let input;
try {
input = JSON.parse(await readStdin());
} catch {
process.exit(0);
}

const tool = input.tool_name ?? "";
const toolInput = input.tool_input ?? {};
const projectDir = input.cwd ?? process.cwd();
const sessionId = input.session_id ?? `vibe-ppid-${process.ppid}`;

const decision = routePreToolUse(tool, toolInput, projectDir, "mistral-vibe", sessionId);
const response = formatDecision("mistral-vibe", decision);
if (response !== null && response !== undefined) {
process.stdout.write(JSON.stringify(response) + "\n");
}
Loading