From 92747e5f46ec7fdd72e0fa3d5a3f3a9fefdfe1b0 Mon Sep 17 00:00:00 2001 From: conoremclaughlin Date: Sat, 4 Jul 2026 00:45:01 +0200 Subject: [PATCH 1/5] fix: inject full inkwell MCP server definition in Codex spawns (by Wren) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodexRunner passed -c overrides for mcp_servers.inkwell.env_http_headers but relied on ~/.codex/config.toml containing the [mcp_servers.inkwell] entry with the url. That entry drifted (renamed to pcp, stale port), so the header-only overrides created a partial MCP server with no url — Codex fails startup with 'Error loading config.toml: invalid transport' and every Lumen trigger died. Now the runner injects the url itself (http://localhost:/mcp) so spawns are self-contained, same principle as the InkRunner INK_ACCESS_TOKEN fix: server spawns must not depend on mutable user-level config files. Co-Authored-By: Wren --- packages/api/src/services/sessions/codex-runner.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/api/src/services/sessions/codex-runner.ts b/packages/api/src/services/sessions/codex-runner.ts index cdf86fbc..917fa4ad 100644 --- a/packages/api/src/services/sessions/codex-runner.ts +++ b/packages/api/src/services/sessions/codex-runner.ts @@ -20,6 +20,7 @@ import type { ToolCall, } from './types.js'; import { formatInjectedContext } from './context-builder.js'; +import { env } from '../../config/env.js'; import { logger } from '../../utils/logger.js'; import { resolveBinaryPath, buildSpawnPath } from './resolve-binary.js'; import { @@ -137,9 +138,16 @@ export class CodexRunner implements IRunner { args.push('--json'); args.push('-c', `model_instructions_file=${promptPath}`); - // Ink session headers — Codex resolves env var names to values at runtime. - // The server key must match what's in .codex/config.toml (mcp_servers.inkwell). + // Ink MCP server — fully defined via -c overrides so the spawn never + // depends on the user's ~/.codex/config.toml having (or keeping) the + // entry. A partial entry (headers without url) makes Codex fail with + // "Error loading config.toml: invalid transport". const codexServerKey = 'inkwell'; + args.push( + '-c', + `mcp_servers.${codexServerKey}.url="http://localhost:${env.MCP_HTTP_PORT}/mcp"` + ); + // Session headers — Codex resolves env var names to values at runtime. args.push('-c', `mcp_servers.${codexServerKey}.env_http_headers.x-ink-context="INK_CONTEXT"`); args.push('-c', `mcp_servers.${codexServerKey}.env_http_headers.x-ink-agent-id="AGENT_ID"`); args.push( From 616bf21d547f947d1f9f3019ba4cb815038f6de3 Mon Sep 17 00:00:00 2001 From: conoremclaughlin Date: Sat, 4 Jul 2026 00:53:13 +0200 Subject: [PATCH 2/5] docs: use 'inkwell' as the MCP server key in skill config examples (by Wren) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per convention: all MCP server config entries are named 'inkwell', not 'pcp' — drifted/pcp-named entries in ~/.codex/config.toml caused the 'invalid transport' spawn failures. Docs should not seed the old name. Co-Authored-By: Wren --- packages/api/src/skills/builtin/pcp/SKILL.md | 4 ++-- packages/openclaw-plugin/skills/SKILL.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api/src/skills/builtin/pcp/SKILL.md b/packages/api/src/skills/builtin/pcp/SKILL.md index ee244d5c..c4a71b5e 100644 --- a/packages/api/src/skills/builtin/pcp/SKILL.md +++ b/packages/api/src/skills/builtin/pcp/SKILL.md @@ -55,7 +55,7 @@ Inkwell tools are available via MCP. If the Inkwell MCP server is not already co ```json { "mcpServers": { - "pcp": { + "inkwell": { "command": "node", "args": ["/path/to/pcp/packages/api/dist/index.js"], "env": { "MCP_TRANSPORT": "stdio" } @@ -69,7 +69,7 @@ Inkwell tools are available via MCP. If the Inkwell MCP server is not already co ```json { "mcpServers": { - "pcp": { + "inkwell": { "url": "http://localhost:3001/mcp", "headers": { "Authorization": "Bearer ${INK_ACCESS_TOKEN}" diff --git a/packages/openclaw-plugin/skills/SKILL.md b/packages/openclaw-plugin/skills/SKILL.md index 870ba833..35c1a8f8 100644 --- a/packages/openclaw-plugin/skills/SKILL.md +++ b/packages/openclaw-plugin/skills/SKILL.md @@ -55,7 +55,7 @@ PCP tools are available via MCP. If the PCP MCP server is not already configured ```json { "mcpServers": { - "pcp": { + "inkwell": { "command": "node", "args": ["/path/to/pcp/packages/api/dist/index.js"], "env": { "MCP_TRANSPORT": "stdio" } @@ -69,7 +69,7 @@ PCP tools are available via MCP. If the PCP MCP server is not already configured ```json { "mcpServers": { - "pcp": { + "inkwell": { "url": "http://localhost:3001/mcp", "headers": { "Authorization": "Bearer ${INK_ACCESS_TOKEN}" From f047094668cb7ddc68e0c94b85699786d9b98142 Mon Sep 17 00:00:00 2001 From: conoremclaughlin Date: Sat, 4 Jul 2026 01:02:52 +0200 Subject: [PATCH 3/5] docs: make project prefix mandatory for cross-repo threadKeys (by Wren) The pr:inktrade-supabase-auth thread baked the project into the identifier slot, which defeats prefix-based route patterns. Docs now state the rule explicitly: project goes in the prefix slot (inktrade:pr:42), REQUIRED for any thread about work outside this repo, plus a sender-rules entry and a note that each repo's AGENTS.md should carry this section. Co-Authored-By: Wren --- AGENTS.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6c4c73c7..c7b5fa68 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -272,18 +272,23 @@ When sending messages to other SBs via `send_to_inbox`, use `threadKey` to maint | `task:` | PCP task coordination | `task:abc123` | | `thread:` | Multi-step conversation with no natural key | `thread:perf-audit` | -### Cross-Project threadKeys +### Cross-Project threadKeys (MANDATORY outside Inkwell) When working across multiple repos/projects, prefix the threadKey with the project name to avoid collisions. The format is `::`. -| Context | threadKey | -| ----------------------------- | ------------------------- | -| PR in Inkwell (this repo) | `pr:389` | -| PR in a different project | `inktrade:pr:42` | -| Issue in another project | `openclaw:issue:15` | -| Cross-project spec discussion | `inktrade:spec:valuation` | +| Context | threadKey | +| ----------------------------- | ------------------------------- | +| PR in Inkwell (this repo) | `pr:389` | +| PR in a different project | `inktrade:pr:42` | +| Issue in another project | `openclaw:issue:15` | +| Cross-project spec discussion | `inktrade:spec:valuation` | +| Branch in another project | `inktrade:branch:supabase-auth` | -Within the Inkwell repo, the project prefix is optional — `pr:389` is unambiguous. For any thread that references work in a different repo, always include the project prefix so trigger routing and session matching work correctly across boundaries. +Within the Inkwell repo, the project prefix is optional — `pr:389` is unambiguous. For any thread that references work in a **different repo, the project prefix is REQUIRED** — `pr:12` in two repos is a routing collision, and studio route patterns (`inktrade:pr:*`) can only target project-scoped keys. + +The project goes in the **prefix slot, never the identifier**: `inktrade:pr:42`, not `pr:inktrade-42` or `pr:inktrade-supabase-auth`. Baking the project into the identifier defeats pattern matching and prefix-based routing. + +Each repo's AGENTS.md should carry this threadKey section so agents working there natively derive project-prefixed keys. ### Sender Rules @@ -292,6 +297,7 @@ Within the Inkwell repo, the project prefix is optional — `pr:389` is unambigu 3. **DERIVE** the key from the most specific reference. If a PR review involves spec changes, use `pr:` (the actionable unit), not `spec:`. 4. **Keep identifiers stable** — use PR numbers, not PR titles. Use spec URI slugs, not descriptions. 5. If no natural key exists for a multi-step conversation, use `thread:` with a descriptive slug. +6. **Prefix with the project** for any thread about work outside this repo: `::` (see Cross-Project threadKeys above). ### Recipient Rules From b9d0f0e5d2a9f30143d4a4235c9952fba5c198ed Mon Sep 17 00:00:00 2001 From: conoremclaughlin Date: Sat, 4 Jul 2026 19:22:54 +0200 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20desktop=20app=20default=20port=20300?= =?UTF-8?q?2=20=E2=80=94=20dashboard=20is=20INK=5FPORT=5FBASE+1=20(by=20Wr?= =?UTF-8?q?en)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scaffold defaulted to localhost:3000, but the web dashboard runs on port base+1 (3002 with the default base 3001). Conor's first launch hit the waiting page forever. Default, docs, and fallback placeholder all corrected; ~/.ink/desktop.json still overrides. Co-Authored-By: Wren --- packages/desktop/README.md | 6 +++--- packages/desktop/src-tauri/src/lib.rs | 6 +++--- packages/desktop/ui/index.html | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/desktop/README.md b/packages/desktop/README.md index fb0faa1a..37e2e73d 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -4,7 +4,7 @@ Inkwell desktop app — a **thin Tauri v2 native shell** over the locally runnin ## Architecture -The app does **not** bundle the web app. It opens a native window pointed at the local Next.js server (default `http://localhost:3000`, i.e. `INK_PORT_BASE - 1`; the API stays on 3001). Everything you see is the same dashboard you'd see in a browser — the desktop app just adds native chrome: +The app does **not** bundle the web app. It opens a native window pointed at the local Next.js server (default `http://localhost:3002`, i.e. `INK_PORT_BASE + 1`; the API stays on 3001). Everything you see is the same dashboard you'd see in a browser — the desktop app just adds native chrome: - **Window** titled "Inkwell" with the standard macOS menu (Edit/Copy/Paste bindings work in the webview). - **Server-not-running fallback**: on launch the window shows a bundled static page (`ui/index.html`) — never a blank white window. A background thread in the Rust shell polls the server's TCP port every 1.5s and navigates the webview to the dashboard the moment it's reachable. @@ -19,11 +19,11 @@ Optional config file at `~/.ink/desktop.json`: ```json { "host": "localhost", - "port": 3000 + "port": 3002 } ``` -Both fields are optional — defaults are `localhost:3000`. If you run the dashboard on a different port base (e.g. `INK_PORT_BASE=4001` → web on 4002), set `"port": 4002`. Invalid JSON falls back to defaults (with a warning on stderr). +Both fields are optional — defaults are `localhost:3002`. If you run the dashboard on a different port base (e.g. `INK_PORT_BASE=4001` → web on 4002), set `"port": 4002`. Invalid JSON falls back to defaults (with a warning on stderr). ## Prerequisites diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs index 4a10f603..d89236ce 100644 --- a/packages/desktop/src-tauri/src/lib.rs +++ b/packages/desktop/src-tauri/src/lib.rs @@ -1,7 +1,7 @@ //! Inkwell desktop shell. //! //! A thin native wrapper around the locally running Inkwell web dashboard -//! (Next.js, default `http://localhost:3000`). The app does NOT bundle the web +//! (Next.js, default `http://localhost:3002`). The app does NOT bundle the web //! app — it points the webview at the local dev/prod server. //! //! On launch the window shows a bundled "waiting for server" page while a @@ -20,14 +20,14 @@ use tauri::{AppHandle, Manager, Url, WebviewUrl, WebviewWindowBuilder, WindowEve const MAIN_WINDOW: &str = "main"; const DEFAULT_HOST: &str = "localhost"; /// Web dashboard default port (`INK_PORT_BASE - 1`, i.e. 3001 - 1). -const DEFAULT_PORT: u16 = 3000; +const DEFAULT_PORT: u16 = 3002; const POLL_INTERVAL: Duration = Duration::from_millis(1500); const CONNECT_TIMEOUT: Duration = Duration::from_millis(800); /// Optional user config at `~/.ink/desktop.json`: /// /// ```json -/// { "host": "localhost", "port": 3000 } +/// { "host": "localhost", "port": 3002 } /// ``` #[derive(Debug, Clone, Default, Deserialize)] #[serde(default)] diff --git a/packages/desktop/ui/index.html b/packages/desktop/ui/index.html index 035b30bb..9a3536ba 100644 --- a/packages/desktop/ui/index.html +++ b/packages/desktop/ui/index.html @@ -115,7 +115,7 @@

Waiting for the Inkwell server

This app is a shell over your locally running dashboard at - http://localhost:3000 + http://localhost:3002

Start it with yarn dev in the Inkwell repo — this window will connect From 04c988b45ecffe65e1854e172e36117c6f7c31c5 Mon Sep 17 00:00:00 2001 From: conoremclaughlin Date: Sun, 5 Jul 2026 14:15:53 +0200 Subject: [PATCH 5/5] fix: container-aware MCP host in codex url injection + lib.rs comment (by Wren) Lumen's blocker on PR #430: the injected mcp_servers.inkwell.url hardcoded localhost, which inside a Docker sandbox resolves to the container and bypasses the orchestrator's host.docker.internal rewrite. The host now follows config.container, matching the orchestrator's .mcp.json convention. Also corrects the lib.rs port-derivation comment (INK_PORT_BASE + 1). Co-Authored-By: Wren --- packages/api/src/services/sessions/codex-runner.ts | 5 ++++- packages/desktop/src-tauri/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/api/src/services/sessions/codex-runner.ts b/packages/api/src/services/sessions/codex-runner.ts index 917fa4ad..3ea38a49 100644 --- a/packages/api/src/services/sessions/codex-runner.ts +++ b/packages/api/src/services/sessions/codex-runner.ts @@ -142,10 +142,13 @@ export class CodexRunner implements IRunner { // depends on the user's ~/.codex/config.toml having (or keeping) the // entry. A partial entry (headers without url) makes Codex fail with // "Error loading config.toml: invalid transport". + // Inside a Docker sandbox, loopback resolves to the container, so use + // host.docker.internal (matches the orchestrator's .mcp.json rewrite). const codexServerKey = 'inkwell'; + const mcpHost = config.container ? 'host.docker.internal' : 'localhost'; args.push( '-c', - `mcp_servers.${codexServerKey}.url="http://localhost:${env.MCP_HTTP_PORT}/mcp"` + `mcp_servers.${codexServerKey}.url="http://${mcpHost}:${env.MCP_HTTP_PORT}/mcp"` ); // Session headers — Codex resolves env var names to values at runtime. args.push('-c', `mcp_servers.${codexServerKey}.env_http_headers.x-ink-context="INK_CONTEXT"`); diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs index d89236ce..a5627969 100644 --- a/packages/desktop/src-tauri/src/lib.rs +++ b/packages/desktop/src-tauri/src/lib.rs @@ -19,7 +19,7 @@ use tauri::{AppHandle, Manager, Url, WebviewUrl, WebviewWindowBuilder, WindowEve const MAIN_WINDOW: &str = "main"; const DEFAULT_HOST: &str = "localhost"; -/// Web dashboard default port (`INK_PORT_BASE - 1`, i.e. 3001 - 1). +/// Web dashboard default port (`INK_PORT_BASE + 1`, i.e. 3001 + 1). const DEFAULT_PORT: u16 = 3002; const POLL_INTERVAL: Duration = Duration::from_millis(1500); const CONNECT_TIMEOUT: Duration = Duration::from_millis(800);