mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-19 09:44:07 +00:00
feat(tui): OCP_TUI_FULL_TOOLS gate — -p-equivalent tools for single-user TUI (#135)
* feat(tui): OCP_TUI_FULL_TOOLS gate — -p-equivalent tool surface for single-user TUI Lets a SINGLE-USER / trusted TUI deployment run a tool-using / MCP agent (e.g. an OpenClaw assistant) on the subscription pool. When OCP_TUI_FULL_TOOLS=1, buildTuiCmd grants the interactive session the SAME tool surface as the -p A-path — --allowedTools (+ optional --mcp-config / --dangerously-skip-permissions, read from the same CLAUDE_ALLOWED_TOOLS / CLAUDE_MCP_CONFIG / CLAUDE_SKIP_PERMISSIONS env as buildCliArgs) — instead of the default MCP-walled, built-in-tools-only set. Motivation: the default TUI tool wall (--strict-mcp-config --disallowedTools mcp__*) exists for multi-tenant safety, but it also blocks a trusted single-operator agent from its MCP tools — forcing tool-using agents onto the metered -p pool after 2026-06-15. This gate resolves that for the single-user case. Safe to gate ON only because TUI is hard-incompatible with AUTH_MODE=multi (server.mjs refuses to boot, see existing guard), so this can NEVER widen a guest's tool surface. Default (gate unset) is unchanged: MCP wall + built-in tools only. Verified live on PI231 through the real OCP TUI path (spike 2026-06-02): - built-in tools: claude created + read a file on the host (2 tool_use entries); - MCP: claude invoked a custom MCP server's tool (mcp__spike__echo_marker) and returned its output; - billing: entrypoint=cli (subscription pool) WITH full tools + MCP; - completion: end_turn, no permission stall, no hang. Tests: +1 (full-tools branch: --allowedTools present + MCP wall dropped; skip-permissions supersedes; mcp-config threaded). 195 pass. README: new OCP_TUI_FULL_TOOLS env var entry. ALIGNMENT.md: changes lib/tui/session.mjs, not server.mjs — server.mjs hard requirements do not trigger. Mirrors buildCliArgs() permissions logic; the flags are documented Claude Code CLI flags, not invented endpoints. cli.js citation N/A under Rule 2. ADR 0007 (A-path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tui): shq operator tool tokens in full-tools shell string (reviewer Finding 2) Independent reviewer (APPROVE WITH CHANGES) caught that buildTuiCmd returns a SHELL STRING (run by tmux via sh -c), unlike buildCliArgs which returns an argv array to spawn(). Operator-supplied CLAUDE_ALLOWED_TOOLS can be a scoped specifier such as "Bash(npm run test:*)" or "Read(~/**)", whose ( ) * ~ would break / inject the shell command if pasted bare. Now shq() each allowed-tool token (operator-self-injection only — guests cannot reach TUI per the multi-mode boot guard, but it's a real correctness bug). Also: tightened the README OCP_TUI_FULL_TOOLS wording per reviewer Finding 1 — the precise safety property is "no guest key can reach the TUI path (multi-mode boot is a hard exit)", and noted the AUTH_MODE=shared + OCP_TUI_ALLOW_LAN trust model is unchanged. Test: +scoped-specifier assertions (token shq'd; never appears unquoted). 195 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+31
-2
@@ -212,13 +212,42 @@ export function buildTuiCmd(claudeBin, model, sessionId, ehome, entrypointMode)
|
||||
if (entrypointMode === "cli") sets.push("CLAUDE_CODE_ENTRYPOINT=cli");
|
||||
else if (entrypointMode === "auto") unset.push("CLAUDE_CODE_ENTRYPOINT"); // let claude self-classify via TTY
|
||||
const envPrefix = ["env", ...unset.map((u) => `-u ${u}`), ...sets].join(" ");
|
||||
|
||||
// Tool surface.
|
||||
// DEFAULT (safe): hard-disable MCP (--strict-mcp-config + --disallowedTools mcp__*);
|
||||
// built-in tools stay on, acceptable for single-user A-path.
|
||||
// OCP_TUI_FULL_TOOLS=1: grant the SAME tool surface as the -p A-path
|
||||
// (--allowedTools [+ --mcp-config] [+ --dangerously-skip-permissions]), so a
|
||||
// SINGLE-USER / trusted TUI deployment can run a tool-using agent (e.g. an OpenClaw
|
||||
// assistant that needs Bash/Read/Write/MCP) on the subscription pool. This mirrors
|
||||
// buildCliArgs() in server.mjs. Safe to gate ON only because TUI is hard-incompatible
|
||||
// with AUTH_MODE=multi (server.mjs refuses to boot), so it can never widen a guest's
|
||||
// surface. Env mirrors server.mjs's CLAUDE_ALLOWED_TOOLS / _SKIP_PERMISSIONS / _MCP_CONFIG.
|
||||
let toolArgs;
|
||||
if (process.env.OCP_TUI_FULL_TOOLS === "1") {
|
||||
toolArgs = [];
|
||||
if (process.env.CLAUDE_SKIP_PERMISSIONS === "true") {
|
||||
toolArgs.push("--dangerously-skip-permissions");
|
||||
} else {
|
||||
const allowed = (process.env.CLAUDE_ALLOWED_TOOLS ||
|
||||
"Bash,Read,Write,Edit,Glob,Grep,WebSearch,WebFetch,Agent")
|
||||
.split(",").map((s) => s.trim()).filter(Boolean);
|
||||
// shq EACH token: buildTuiCmd returns a SHELL STRING (run by tmux via sh -c), unlike
|
||||
// buildCliArgs which returns an argv array to spawn(). claude accepts scoped specifiers
|
||||
// like "Bash(npm run test:*)" / "Read(~/**)" whose ( ) * ~ would break/inject the shell
|
||||
// command if pasted bare. (operator-self-injection only — guests can't reach TUI.)
|
||||
if (allowed.length) toolArgs.push("--allowedTools", ...allowed.map(shq));
|
||||
}
|
||||
if (process.env.CLAUDE_MCP_CONFIG) toolArgs.push("--mcp-config", shq(process.env.CLAUDE_MCP_CONFIG));
|
||||
} else {
|
||||
toolArgs = ["--strict-mcp-config", "--disallowedTools", shq("mcp__*")];
|
||||
}
|
||||
return [
|
||||
envPrefix,
|
||||
shq(claudeBin),
|
||||
"--model", shq(model),
|
||||
"--session-id", sessionId,
|
||||
"--strict-mcp-config",
|
||||
"--disallowedTools", shq("mcp__*"),
|
||||
...toolArgs,
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user