Add permission modes and container introspection backend

Permission modes: replaces the binary full_permissions flag with a
PermissionMode enum (Plan/Default/AcceptEdits/Bypass). Flag mapping is
defined once in PermissionMode::cli_args() and used by the terminal, the
web terminal, and the scheduler:
  Plan        -> --permission-mode plan
  Default     -> (no flag)
  AcceptEdits -> --permission-mode acceptEdits
  Bypass      -> --dangerously-skip-permissions
Choices verified against `claude --permission-mode` on 2.1.226.

full_permissions is retained and effective_permission_mode() falls back
to it, so existing projects.json needs no migration.

Bug fix: triple-c-task-runner ran `claude -p ... --dangerously-skip-
permissions` unconditionally, ignoring the project's setting entirely.
It now reads TRIPLE_C_PERMISSION_MODE, which is injected into the
container, added to the reserved env blocklist, propagated through the
entrypoint's cron env filter, and tracked by a new
triple-c.permission-mode label so a change forces recreation.

Introspection: new commands/inspect_commands.rs exposes read-only views
into the container over docker exec — Claude sessions (parsed from
~/.claude/projects/<cwd>/<uuid>.jsonl), installed capabilities (skills,
agents, commands, hooks, plugins, natively-configured MCP servers), and
the triple-c-scheduler task list, logs and notifications.

Task/session ids are validated against a strict allowlist and every
parameterized call runs as a bare argv vector via bollard, so no shell
is involved. Stopped containers return empty results rather than errors.

No UI yet; that lands with the Project Home view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-09 10:51:34 -07:00
co-authored by Claude Opus 5
parent d0bb631d4d
commit 0ac4e5030c
11 changed files with 1193 additions and 21 deletions
+27 -1
View File
@@ -1,5 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import type { Project, ProjectPath, ContainerInfo, SiblingContainer, AppSettings, UpdateInfo, ImageUpdateInfo, FileEntry, WebTerminalInfo, SttStatus, InstallOptions } from "./types";
import type { Project, ProjectPath, ContainerInfo, SiblingContainer, AppSettings, UpdateInfo, ImageUpdateInfo, FileEntry, WebTerminalInfo, SttStatus, InstallOptions, ClaudeSession, ContainerCapabilities, ScheduledTask, SchedulerNotification } from "./types";
// Docker
export const checkDocker = () => invoke<boolean>("check_docker");
@@ -107,3 +107,29 @@ export const transcribeAudio = (audioData: number[]) =>
export const detectInstallOptions = () =>
invoke<InstallOptions>("detect_install_options");
export const runDockerInstall = () => invoke<void>("run_docker_install");
// Container introspection — sessions
export const listClaudeSessions = (projectId: string) =>
invoke<ClaudeSession[]>("list_claude_sessions", { projectId });
export const resumeSessionCommand = (projectId: string, sessionId: string) =>
invoke<string>("resume_session_command", { projectId, sessionId });
// Container introspection — capabilities
export const listContainerCapabilities = (projectId: string) =>
invoke<ContainerCapabilities>("list_container_capabilities", { projectId });
// Container introspection — scheduler
export const listScheduledTasks = (projectId: string) =>
invoke<ScheduledTask[]>("list_scheduled_tasks", { projectId });
export const getScheduledTaskLog = (projectId: string, taskId: string, tailLines?: number) =>
invoke<string>("get_scheduled_task_log", { projectId, taskId, tailLines });
export const setScheduledTaskEnabled = (projectId: string, taskId: string, enabled: boolean) =>
invoke<string>("set_scheduled_task_enabled", { projectId, taskId, enabled });
export const runScheduledTaskNow = (projectId: string, taskId: string) =>
invoke<string>("run_scheduled_task_now", { projectId, taskId });
export const removeScheduledTask = (projectId: string, taskId: string) =>
invoke<string>("remove_scheduled_task", { projectId, taskId });
export const getSchedulerNotifications = (projectId: string) =>
invoke<SchedulerNotification[]>("get_scheduler_notifications", { projectId });
export const clearSchedulerNotifications = (projectId: string) =>
invoke<void>("clear_scheduler_notifications", { projectId });
+73
View File
@@ -27,7 +27,11 @@ export interface Project {
allow_docker_access: boolean;
sandbox_mode_enabled: boolean;
mission_control_enabled: boolean;
/** Legacy binary permission flag; superseded by `permission_mode`, kept for
* existing projects.json data. */
full_permissions: boolean;
/** null = not set → falls back to `full_permissions` (true → "bypass"). */
permission_mode: PermissionMode | null;
ssh_key_path: string | null;
git_token: string | null;
git_user_name: string | null;
@@ -50,6 +54,9 @@ export type ProjectStatus =
export type Backend = "anthropic" | "bedrock" | "ollama" | "open_ai_compatible";
/** Mirrors Rust `PermissionMode` (serde camelCase). */
export type PermissionMode = "plan" | "default" | "acceptEdits" | "bypass";
export type BedrockAuthMethod = "static_credentials" | "profile" | "bearer_token";
export interface BedrockConfig {
@@ -220,3 +227,69 @@ export interface InstallOptions {
manual_steps: string[];
post_install_notes: string[];
}
// Container introspection (read-only) — see src-tauri/src/commands/inspect_commands.rs
/** A Claude Code session transcript stored on the container's config volume. */
export interface ClaudeSession {
id: string;
/** User-set display name (`claude -n <name>`), if any. */
name: string | null;
/** Claude's auto-generated title, else the session's last prompt. */
summary: string | null;
last_modified: string;
size_bytes: number;
message_count: number;
cwd: string | null;
}
export type CapabilityScope = "user" | "project";
export interface CapabilityItem {
name: string;
description: string | null;
scope: CapabilityScope;
}
export interface CapabilityGroup {
count: number;
items: CapabilityItem[];
}
export interface ContainerCapabilities {
skills: CapabilityGroup;
agents: CapabilityGroup;
commands: CapabilityGroup;
/** One item per hook event; `count` totals the individual handlers. */
hooks: CapabilityGroup;
plugins: CapabilityGroup;
mcp_servers: CapabilityGroup;
}
export interface ScheduledTask {
id: string;
name: string;
prompt: string;
/** Cron expression (one-shot tasks are stored as cron too — see `at`). */
schedule: string;
task_type: "recurring" | "once";
/** Original `--at` value (`"YYYY-MM-DD HH:MM"`) for one-shot tasks. */
at: string | null;
enabled: boolean;
working_dir: string;
created_at: string | null;
last_run: string | null;
/** Known only for enabled one-shot tasks; cron is not evaluated. */
next_run: string | null;
}
export interface SchedulerNotification {
task_id: string;
task_name: string | null;
status: string | null;
time: string | null;
task_type: string | null;
summary: string | null;
body: string;
created_at: string;
}