handler-ai 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- handler_ai-0.1.0.dist-info/METADATA +60 -0
- handler_ai-0.1.0.dist-info/RECORD +234 -0
- handler_ai-0.1.0.dist-info/WHEEL +4 -0
- handler_ai-0.1.0.dist-info/entry_points.txt +6 -0
- handler_cli/__init__.py +1 -0
- handler_cli/account_commands.py +314 -0
- handler_cli/addon_commands.py +128 -0
- handler_cli/agent_commands.py +139 -0
- handler_cli/app_commands.py +175 -0
- handler_cli/app_window.py +85 -0
- handler_cli/automation_commands.py +309 -0
- handler_cli/bridge.py +42 -0
- handler_cli/cli.py +326 -0
- handler_cli/client.py +81 -0
- handler_cli/daemon_commands.py +108 -0
- handler_cli/image_commands.py +102 -0
- handler_cli/inbox_commands.py +33 -0
- handler_cli/output.py +12 -0
- handler_cli/project_commands.py +63 -0
- handler_cli/provider_commands.py +113 -0
- handler_cli/secret_commands.py +64 -0
- handler_cli/session_bridge.py +93 -0
- handler_cli/session_commands.py +485 -0
- handler_cli/setup.py +120 -0
- handler_cli/terminal.py +280 -0
- handler_cli/terminal_client.py +37 -0
- handler_contracts/__init__.py +642 -0
- handler_contracts/account.py +36 -0
- handler_contracts/acp_endpoint.py +69 -0
- handler_contracts/addon.py +147 -0
- handler_contracts/agent_spec.py +75 -0
- handler_contracts/automation.py +285 -0
- handler_contracts/common.py +229 -0
- handler_contracts/control.py +1208 -0
- handler_contracts/harness.py +300 -0
- handler_contracts/miniapp.py +141 -0
- handler_contracts/provider.py +82 -0
- handler_contracts/remote.py +167 -0
- handler_contracts/sandbox.py +199 -0
- handler_contracts/schemas.py +313 -0
- handler_contracts/terminal.py +32 -0
- handler_control/__init__.py +7 -0
- handler_control/access.py +62 -0
- handler_control/accounts.py +279 -0
- handler_control/addon_install.py +268 -0
- handler_control/addons.py +118 -0
- handler_control/agent_state.py +46 -0
- handler_control/agents.py +257 -0
- handler_control/api/__init__.py +137 -0
- handler_control/api/accounts.py +231 -0
- handler_control/api/acp.py +116 -0
- handler_control/api/addons.py +95 -0
- handler_control/api/agents.py +150 -0
- handler_control/api/apps.py +104 -0
- handler_control/api/automations.py +128 -0
- handler_control/api/bindings.py +105 -0
- handler_control/api/builtin_agents.py +22 -0
- handler_control/api/caches.py +79 -0
- handler_control/api/catalog.py +197 -0
- handler_control/api/common.py +205 -0
- handler_control/api/contexts.py +88 -0
- handler_control/api/folders.py +51 -0
- handler_control/api/images.py +164 -0
- handler_control/api/inbox.py +21 -0
- handler_control/api/projects.py +170 -0
- handler_control/api/providers.py +107 -0
- handler_control/api/remote.py +45 -0
- handler_control/api/secrets.py +177 -0
- handler_control/api/sessions.py +192 -0
- handler_control/api/skills.py +270 -0
- handler_control/app_api.py +84 -0
- handler_control/app_http.py +214 -0
- handler_control/app_remote.py +206 -0
- handler_control/apps.py +867 -0
- handler_control/auth.py +48 -0
- handler_control/automation_config.py +255 -0
- handler_control/automations.py +1134 -0
- handler_control/bindings.py +149 -0
- handler_control/builds.py +341 -0
- handler_control/builtin_agents.py +82 -0
- handler_control/caches.py +113 -0
- handler_control/checks.py +287 -0
- handler_control/compose.py +317 -0
- handler_control/config_files.py +41 -0
- handler_control/contexts.py +75 -0
- handler_control/daemon.py +64 -0
- handler_control/data/automation/bin/handler-llm +85 -0
- handler_control/data/automations/env-issues/automation.toml +17 -0
- handler_control/data/automations/env-issues/extract.py +73 -0
- handler_control/data/automations/env-issues/findings.schema.json +28 -0
- handler_control/data/automations/env-issues/prompt.md +19 -0
- handler_control/data/builtin-agents/skill-creator/agent.toml +10 -0
- handler_control/data/builtin-agents/skill-creator/instructions.md +5 -0
- handler_control/data/builtin-agents/skill-creator/skills/handler-skills/SKILL.md +44 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/LICENSE.txt +202 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/SKILL.md +485 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/analyzer.md +274 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/comparator.md +202 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/grader.md +223 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/assets/eval_review.html +146 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/references/schemas.md +430 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/__init__.py +0 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/generate_report.py +326 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/improve_description.py +247 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/package_skill.py +136 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/quick_validate.py +103 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/run_eval.py +310 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/run_loop.py +328 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/utils.py +47 -0
- handler_control/data/builtin-agents/skill-review/agent.toml +13 -0
- handler_control/data/builtin-agents/skill-review/instructions.md +30 -0
- handler_control/data/builtin-agents/skill-review/interface.json +83 -0
- handler_control/data/interfaces/llm/v1/interface.toml +17 -0
- handler_control/data/interfaces/llm/v1/tools.json +138 -0
- handler_control/data/templates/README.md +10 -0
- handler_control/data/templates/providers/anthropic-api.toml +18 -0
- handler_control/data/templates/providers/llamacpp.toml +210 -0
- handler_control/data/templates/providers/mistral-api.toml +14 -0
- handler_control/data/templates/providers/ollama.toml +10 -0
- handler_control/data/templates/providers/openai-api.toml +13 -0
- handler_control/data/templates/providers/openrouter.toml +10 -0
- handler_control/desktop.py +68 -0
- handler_control/display.py +62 -0
- handler_control/files.py +23 -0
- handler_control/folders.py +68 -0
- handler_control/git_read.py +39 -0
- handler_control/image_check.py +322 -0
- handler_control/image_updates.py +174 -0
- handler_control/inbox.py +70 -0
- handler_control/interfaces.py +151 -0
- handler_control/knowledge.py +237 -0
- handler_control/launcher.py +356 -0
- handler_control/llm.py +194 -0
- handler_control/migrations/__init__.py +62 -0
- handler_control/policy.py +305 -0
- handler_control/projects.py +266 -0
- handler_control/providers.py +180 -0
- handler_control/relay.py +133 -0
- handler_control/remote.py +888 -0
- handler_control/remote_crypto.py +97 -0
- handler_control/runtime.py +140 -0
- handler_control/sandbox_process.py +224 -0
- handler_control/schedules.py +58 -0
- handler_control/secrets.py +242 -0
- handler_control/service_router.py +180 -0
- handler_control/services.py +92 -0
- handler_control/session_files.py +429 -0
- handler_control/sessions.py +1116 -0
- handler_control/settings.py +129 -0
- handler_control/shortcuts.py +80 -0
- handler_control/skill_scan.py +88 -0
- handler_control/skill_sources.py +61 -0
- handler_control/skills.py +378 -0
- handler_control/terminal.py +79 -0
- handler_control/terminals.py +108 -0
- handler_control/transcript.py +174 -0
- handler_control/web-ui/assets/index-1w5RPsfA.js +190 -0
- handler_control/web-ui/assets/index-dWnzCLzQ.css +1 -0
- handler_control/web-ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- handler_control/web-ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- handler_control/web-ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- handler_control/web-ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- handler_control/web-ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- handler_control/web-ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- handler_control/web-ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- handler_control/web-ui/favicon.svg +1 -0
- handler_control/web-ui/index.html +15 -0
- handler_control/webpush.py +90 -0
- handler_control/webui.py +105 -0
- handler_harness/__init__.py +13 -0
- handler_harness/addons.py +129 -0
- handler_harness/catalog.py +116 -0
- handler_harness/cli.py +109 -0
- handler_harness/data/addons/README.md +44 -0
- handler_harness/data/addons/miniapp/Containerfile +20 -0
- handler_harness/data/addons/miniapp/PROVISIONING.md +16 -0
- handler_harness/data/addons/miniapp/addon.toml +16 -0
- handler_harness/data/addons/miniapp/skills/miniapp/SKILL.md +140 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/.gitignore +2 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/app.toml +15 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/db.ts +30 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/deno.json +7 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/handler.ts +26 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/main.ts +18 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/main_test.ts +32 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/migrations/001_notes.sql +6 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/routes.ts +32 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/deno.json +12 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/index.html +12 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/App.svelte +44 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/api.ts +16 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/app.css +24 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/main.ts +5 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/vite.config.ts +17 -0
- handler_harness/data/catalog/README.md +12 -0
- handler_harness/data/catalog/playwright/Containerfile +28 -0
- handler_harness/data/catalog/playwright/PROVISIONING.md +53 -0
- handler_harness/data/catalog/playwright/addon.toml +25 -0
- handler_harness/data/catalog/playwright/handler/playwright-mcp-check.mjs +23 -0
- handler_harness/data/catalog/playwright/handler/playwright-mcp.mjs +61 -0
- handler_harness/data/catalog/playwright/skills/browser/SKILL.md +41 -0
- handler_harness/data/catalog/shopify/Containerfile +22 -0
- handler_harness/data/catalog/shopify/PROVISIONING.md +49 -0
- handler_harness/data/catalog/shopify/addon.toml +39 -0
- handler_harness/data/catalog/shopify/skills/shopify/SKILL.md +41 -0
- handler_harness/data/handler-sandbox-env +30 -0
- handler_harness/data/harnesses/README.md +47 -0
- handler_harness/data/harnesses/claude-code/Containerfile +27 -0
- handler_harness/data/harnesses/claude-code/harness.toml +95 -0
- handler_harness/data/harnesses/codex/Containerfile +26 -0
- handler_harness/data/harnesses/codex/harness.toml +103 -0
- handler_harness/data/harnesses/pi/Containerfile +27 -0
- handler_harness/data/harnesses/pi/harness.toml +65 -0
- handler_harness/data/harnesses/vibe/Containerfile +19 -0
- handler_harness/data/harnesses/vibe/harness.toml +96 -0
- handler_harness/data/toolbox/Containerfile +56 -0
- handler_harness/data/toolbox/README.md +24 -0
- handler_harness/data/toolbox/toolbox.toml +41 -0
- handler_harness/images.py +62 -0
- handler_harness/providers.py +300 -0
- handler_harness/render.py +472 -0
- handler_sandbox_podman/__init__.py +1 -0
- handler_sandbox_podman/caches.py +97 -0
- handler_sandbox_podman/check.py +121 -0
- handler_sandbox_podman/cli.py +239 -0
- handler_sandbox_podman/egress.py +118 -0
- handler_sandbox_podman/images.py +199 -0
- handler_sandbox_podman/launch.py +474 -0
- handler_sandbox_podman/podman.py +108 -0
- handler_sandbox_podman/proxy/Containerfile +6 -0
- handler_sandbox_podman/proxy/addon.py +255 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Claude Code harness image: toolbox + the ACP adapter for the Claude Agent SDK.
|
|
2
|
+
# The SDK ships the native Claude Code binary as an npm optional dependency (one package per
|
|
3
|
+
# platform); it is also linked as `claude`, the terminal program of `handler terminal` (D.16), so both
|
|
4
|
+
# run the same version on the same history.
|
|
5
|
+
#
|
|
6
|
+
# Not pinned: every rebuild (`handler image rebuild claude-code`, or the web UI) installs the latest release.
|
|
7
|
+
# What was installed is recorded in /usr/share/handler/version (contract B.6); the build fails if
|
|
8
|
+
# the versions cannot be read, so a package layout change is noticed.
|
|
9
|
+
ARG TOOLBOX_IMAGE=localhost/handler-ai-toolbox:latest
|
|
10
|
+
FROM ${TOOLBOX_IMAGE}
|
|
11
|
+
|
|
12
|
+
RUN npm install -g --no-fund --no-audit @agentclientprotocol/claude-agent-acp \
|
|
13
|
+
&& npm cache clean --force \
|
|
14
|
+
&& command -v claude-agent-acp \
|
|
15
|
+
&& adapter=/usr/lib/node_modules/@agentclientprotocol/claude-agent-acp \
|
|
16
|
+
&& sdk="$adapter/node_modules/@anthropic-ai/claude-agent-sdk" \
|
|
17
|
+
&& acp_version="$(jq -er .version "$adapter/package.json")" \
|
|
18
|
+
&& claude_version="$(jq -er .claudeCodeVersion "$sdk/package.json")" \
|
|
19
|
+
&& for bin in "$adapter"/node_modules/@anthropic-ai/claude-agent-sdk-linux-*/claude; do \
|
|
20
|
+
"$bin" --version >/dev/null 2>&1 && ln -s "$bin" /usr/local/bin/claude && break; \
|
|
21
|
+
done \
|
|
22
|
+
&& claude --version \
|
|
23
|
+
&& mkdir -p /usr/share/handler \
|
|
24
|
+
&& echo "Claude Code ${claude_version} (claude-agent-acp ${acp_version})" > /usr/share/handler/version \
|
|
25
|
+
&& rm -rf /home/agent # build-time npm logs (npm runs as root with HOME=/home/agent)
|
|
26
|
+
|
|
27
|
+
LABEL org.opencontainers.image.title="handler-ai-claude-code"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Claude Code harness descriptor (contract B, handler.harness/v1).
|
|
2
|
+
apiVersion = "handler.harness/v1"
|
|
3
|
+
name = "claude-code"
|
|
4
|
+
product = "Claude Code"
|
|
5
|
+
image = "localhost/handler-ai-claude-code" # no tag: launches use :latest (contract B.6)
|
|
6
|
+
acpCommand = ["claude-agent-acp"]
|
|
7
|
+
home = "/home/agent"
|
|
8
|
+
|
|
9
|
+
# The hosts it reaches belong to what it runs on: the login below, or a provider (its endpoint).
|
|
10
|
+
# A provider it can use speaks:
|
|
11
|
+
providerProtocols = ["anthropic"] # ANTHROPIC_BASE_URL + key + model (verified)
|
|
12
|
+
|
|
13
|
+
modelOption = "model" # ACP config option id (config options: mode, model, effort, fast)
|
|
14
|
+
|
|
15
|
+
# User skills (verified: claude-agent-acp loads settingSources "user"): each skill of the
|
|
16
|
+
# agent and its addons is mounted read-only at ~/.claude/skills/<folder name>.
|
|
17
|
+
skillsDir = "~/.claude/skills"
|
|
18
|
+
|
|
19
|
+
# MCP servers passed in ACP session/new (its addons'): claude-agent-acp
|
|
20
|
+
# advertises mcpCapabilities http and sse; stdio is mandatory in ACP. It starts stdio servers at
|
|
21
|
+
# session/new with its own environment, minus its token (verified 2026-09-26): mcpEnv "inherit".
|
|
22
|
+
mcpTransports = ["stdio", "http", "sse"]
|
|
23
|
+
|
|
24
|
+
[env]
|
|
25
|
+
NODE_USE_ENV_PROXY = "1" # Node's fetch only honours HTTPS_PROXY with this
|
|
26
|
+
DISABLE_AUTOUPDATER = "1" # the image is the unit of upgrade, not the container
|
|
27
|
+
# Privacy (verified in the binary, 2.1.283): the strongest of its three levels. No telemetry,
|
|
28
|
+
# Sentry error reports, GrowthBook feature flags, /feedback or update checks; only the API.
|
|
29
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1"
|
|
30
|
+
CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY = "1" # gated on its own, not by the line above
|
|
31
|
+
|
|
32
|
+
# Settings and history: the agent's own (handler keeps them).
|
|
33
|
+
# The login is not here: an account's token (below) reaches it as an env var.
|
|
34
|
+
[stateSlots.config_dir]
|
|
35
|
+
target = "~/.claude"
|
|
36
|
+
kind = "dir"
|
|
37
|
+
required = false
|
|
38
|
+
|
|
39
|
+
[stateSlots.config_json]
|
|
40
|
+
target = "~/.claude.json"
|
|
41
|
+
kind = "file"
|
|
42
|
+
required = false
|
|
43
|
+
# Its terminal UI (`handler terminal`) onboards a new ~/.claude.json, and onboarding asks to log in
|
|
44
|
+
# even with the account's token set (verified, 2.1.286): the account brings the login, so the
|
|
45
|
+
# file starts onboarded.
|
|
46
|
+
initial = "{\"hasCompletedOnboarding\": true}\n"
|
|
47
|
+
|
|
48
|
+
# An account: a Claude subscription's long-lived token (verified 2026-09-30, Claude Code 2.1.285).
|
|
49
|
+
# The person runs `claude setup-token` themselves (Anthropic's own sign-in, in their browser) and
|
|
50
|
+
# gives handler the token it prints, or a reference to where they keep it: handler never runs the sign-in
|
|
51
|
+
# nor reads its output (Anthropic's terms, D.9). A token lasts a year and has no refresh, so any
|
|
52
|
+
# number of sandboxes can share it. (`/login` would leave ~/.claude/.credentials.json with a
|
|
53
|
+
# refresh token instead, which several sandboxes sharing one account could invalidate for each
|
|
54
|
+
# other.)
|
|
55
|
+
[login]
|
|
56
|
+
subscription = "Claude Pro or Max"
|
|
57
|
+
# Unattended use (an automation's agent run): refused. A Claude subscription is for interactive
|
|
58
|
+
# use by its owner (plan limits "assume ordinary, individual usage"): sessions you start and follow,
|
|
59
|
+
# never runs nobody is there for (docs/dev/ui-ux.md "Vendors' terms").
|
|
60
|
+
unattended = "refused"
|
|
61
|
+
terms = "https://www.anthropic.com/legal/consumer-terms"
|
|
62
|
+
checked = 2026-10-05
|
|
63
|
+
token = "CLAUDE_CODE_OAUTH_TOKEN"
|
|
64
|
+
tokenCommand = "claude setup-token"
|
|
65
|
+
# its sign-in page may show a code to paste back into `claude setup-token` (when the browser
|
|
66
|
+
# cannot call it back): that code is not the token, refused as "401 Invalid bearer token"
|
|
67
|
+
tokenPrefix = "sk-ant-oat01-"
|
|
68
|
+
tokenLifetimeDays = 365
|
|
69
|
+
hosts = ["api.anthropic.com:443", "platform.claude.com:443"]
|
|
70
|
+
help = "It is Claude Code's own sign-in, with your Claude subscription, for your own use (Anthropic's terms). Run it in a terminal where Claude Code is installed and sign in on the page it opens (a code that page shows goes back into that terminal), then give handler the token it prints at the end (sk-ant-oat01-…)."
|
|
71
|
+
# The token says nothing of the subscription: Claude Code reads its plan from the env (verified,
|
|
72
|
+
# 2.1.286: without it, "Claude API" and an API user's default model).
|
|
73
|
+
plans = ["pro", "max", "team", "enterprise"]
|
|
74
|
+
planEnv = "CLAUDE_CODE_SUBSCRIPTION_TYPE"
|
|
75
|
+
|
|
76
|
+
# Neutral permission mode → ACP session mode (decision 4).
|
|
77
|
+
[permissions]
|
|
78
|
+
ask = { sessionMode = "default" }
|
|
79
|
+
accept-edits = { sessionMode = "acceptEdits" }
|
|
80
|
+
bypass = { sessionMode = "bypassPermissions" }
|
|
81
|
+
|
|
82
|
+
# `handler terminal` (D.16): Claude Code's own terminal UI instead of the ACP adapter; the same binary
|
|
83
|
+
# (the SDK's, linked by the Containerfile). The agent's settings reach it as flags.
|
|
84
|
+
[terminal]
|
|
85
|
+
command = ["claude"]
|
|
86
|
+
modelFlag = "--model"
|
|
87
|
+
mcpConfigFlag = "--mcp-config"
|
|
88
|
+
instructionsFlag = "--append-system-prompt" # the agent's instructions, after its own prompt
|
|
89
|
+
[terminal.permissions]
|
|
90
|
+
ask = ["--permission-mode", "default"]
|
|
91
|
+
accept-edits = ["--permission-mode", "acceptEdits"]
|
|
92
|
+
bypass = ["--dangerously-skip-permissions"]
|
|
93
|
+
|
|
94
|
+
[accountConnectors]
|
|
95
|
+
offEnv = { ENABLE_CLAUDEAI_MCP_SERVERS = "false" } # verified: hides claude.ai connectors
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Codex harness image: toolbox + codex-acp, the ACP adapter for OpenAI Codex. It starts
|
|
2
|
+
# `codex app-server`; the Codex binary comes with it (its @openai/codex dependency).
|
|
3
|
+
# codex-acp starts handler-codex instead (CODEX_PATH, harness.toml): that Codex with privacy config
|
|
4
|
+
# overrides. `-c` beats config.toml; unset, analytics are on and metrics go to OpenAI (statsig).
|
|
5
|
+
#
|
|
6
|
+
# Not pinned: every rebuild (`handler image rebuild codex`, or the web UI) installs the latest release.
|
|
7
|
+
# What was installed is recorded in /usr/share/handler/version (contract B.6); the build fails if
|
|
8
|
+
# the versions cannot be read, so a package layout change is noticed.
|
|
9
|
+
ARG TOOLBOX_IMAGE=localhost/handler-ai-toolbox:latest
|
|
10
|
+
FROM ${TOOLBOX_IMAGE}
|
|
11
|
+
|
|
12
|
+
RUN npm install -g --no-fund --no-audit @agentclientprotocol/codex-acp \
|
|
13
|
+
&& npm cache clean --force \
|
|
14
|
+
&& command -v codex-acp \
|
|
15
|
+
&& adapter=/usr/lib/node_modules/@agentclientprotocol/codex-acp \
|
|
16
|
+
&& test -f "$adapter/node_modules/@openai/codex/bin/codex.js" \
|
|
17
|
+
&& printf '#!/bin/sh\nexec node %s -c analytics.enabled=false -c feedback.enabled=false -c check_for_update_on_startup=false -c otel.metrics_exporter=none "$@"\n' \
|
|
18
|
+
"$adapter/node_modules/@openai/codex/bin/codex.js" > /usr/local/bin/handler-codex \
|
|
19
|
+
&& chmod 755 /usr/local/bin/handler-codex \
|
|
20
|
+
&& acp_version="$(jq -er .version "$adapter/package.json")" \
|
|
21
|
+
&& codex_version="$(jq -er .version "$adapter/node_modules/@openai/codex/package.json")" \
|
|
22
|
+
&& mkdir -p /usr/share/handler \
|
|
23
|
+
&& echo "Codex ${codex_version} (codex-acp ${acp_version})" > /usr/share/handler/version \
|
|
24
|
+
&& rm -rf /home/agent # build-time npm logs (npm runs as root with HOME=/home/agent)
|
|
25
|
+
|
|
26
|
+
LABEL org.opencontainers.image.title="handler-ai-codex"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# OpenAI Codex harness descriptor (contract B, handler.harness/v1). ACP through the codex-acp
|
|
2
|
+
# adapter, which drives `codex app-server`.
|
|
3
|
+
apiVersion = "handler.harness/v1"
|
|
4
|
+
name = "codex"
|
|
5
|
+
product = "Codex"
|
|
6
|
+
image = "localhost/handler-ai-codex" # no tag: launches use :latest (contract B.6)
|
|
7
|
+
acpCommand = ["codex-acp"]
|
|
8
|
+
home = "/home/agent"
|
|
9
|
+
|
|
10
|
+
# The hosts it reaches belong to what it runs on: the login below, or a provider (its endpoint).
|
|
11
|
+
# A provider it can use speaks:
|
|
12
|
+
providerProtocols = ["responses"]
|
|
13
|
+
|
|
14
|
+
# ACP config options (codex-acp): mode, collaboration_mode, model, reasoning_effort, fast-mode.
|
|
15
|
+
modelOption = "model"
|
|
16
|
+
|
|
17
|
+
# User skills (Codex loads $CODEX_HOME/skills, CODEX_HOME = ~/.codex): each skill of the
|
|
18
|
+
# agent and its addons is mounted read-only at ~/.codex/skills/<folder name>.
|
|
19
|
+
skillsDir = "~/.codex/skills"
|
|
20
|
+
# MCP servers of session/new: codex-acp advertises mcpCapabilities http (no sse) and hands them to
|
|
21
|
+
# Codex, which starts stdio ones with a reduced environment (HOME, PATH, USER, LANG… and the
|
|
22
|
+
# entry's env): "sandbox", render wraps them with handler-sandbox-env.
|
|
23
|
+
mcpTransports = ["stdio", "http"]
|
|
24
|
+
mcpEnv = "sandbox"
|
|
25
|
+
|
|
26
|
+
# Codex writes its state under ~/.codex (auth.json after an API-key login, sessions, logs,
|
|
27
|
+
# its sqlite state): writable, including when files are mounted inside it.
|
|
28
|
+
writablePaths = ["~/.codex"]
|
|
29
|
+
|
|
30
|
+
[env]
|
|
31
|
+
CODEX_HOME = "/home/agent/.codex"
|
|
32
|
+
NO_BROWSER = "1" # headless: no browser ChatGPT login among the ACP auth methods
|
|
33
|
+
# Without a ChatGPT login (auth_json), session/new logs in with the provider's key instead of
|
|
34
|
+
# answering auth_required.
|
|
35
|
+
DEFAULT_AUTH_REQUEST = '{"methodId": "api-key"}'
|
|
36
|
+
# codex-acp starts `$CODEX_PATH app-server`: handler-codex is Codex with privacy overrides (analytics,
|
|
37
|
+
# feedback uploads, update check and metrics to ab.chatgpt.com off; see the Containerfile). They
|
|
38
|
+
# are config keys, not env: Codex has no env switch for them.
|
|
39
|
+
CODEX_PATH = "handler-codex"
|
|
40
|
+
|
|
41
|
+
# Codex's config.toml (model, model_provider, [model_providers.*] for a local model…). Codex
|
|
42
|
+
# rewrites it (e.g. trusted projects): copied, not mounted; changes discarded.
|
|
43
|
+
[configFiles.config_toml]
|
|
44
|
+
target = "~/.codex/config.toml"
|
|
45
|
+
description = "Codex config.toml"
|
|
46
|
+
seed = true
|
|
47
|
+
|
|
48
|
+
# Model metadata for models Codex does not know (a local model): context window, reasoning
|
|
49
|
+
# levels, tools. config.toml points at it: model_catalog_json =
|
|
50
|
+
# "~/.codex/model-catalog.json". Without it, Codex warns and uses fallback metadata.
|
|
51
|
+
[configFiles.model_catalog_json]
|
|
52
|
+
target = "~/.codex/model-catalog.json"
|
|
53
|
+
description = "Codex model catalog (model_catalog_json)"
|
|
54
|
+
|
|
55
|
+
# The system prompt, replacing the model's (config.toml: model_instructions_file =
|
|
56
|
+
# "~/.codex/instructions.md"). For a model in model_catalog_json, which has none of its own.
|
|
57
|
+
[configFiles.instructions_md]
|
|
58
|
+
target = "~/.codex/instructions.md"
|
|
59
|
+
description = "Codex system prompt (model_instructions_file)"
|
|
60
|
+
|
|
61
|
+
# Credentials of a ChatGPT login (the account's, see [login]). Codex refreshes the tokens in it,
|
|
62
|
+
# so it is bound writable.
|
|
63
|
+
[stateSlots.auth_json]
|
|
64
|
+
target = "~/.codex/auth.json"
|
|
65
|
+
kind = "file"
|
|
66
|
+
required = false
|
|
67
|
+
credential = true
|
|
68
|
+
|
|
69
|
+
# Session history (Codex writes its rollouts to $CODEX_HOME/sessions/YYYY/MM/DD/*.jsonl). Per
|
|
70
|
+
# session: the daemon binds it to the session's own folder (contracts F.2), so a stopped session
|
|
71
|
+
# can be resumed with ACP `session/load`.
|
|
72
|
+
[stateSlots.sessions]
|
|
73
|
+
target = "~/.codex/sessions"
|
|
74
|
+
kind = "dir"
|
|
75
|
+
required = false
|
|
76
|
+
perSession = true
|
|
77
|
+
|
|
78
|
+
# An account: a ChatGPT plan (verified 2026-09-30, Codex 0.159.2). `codex login` (on the host,
|
|
79
|
+
# D.9) opens the sign-in page, which comes back to it on localhost. Its credential store is the OS
|
|
80
|
+
# keyring when there is one, so it is told to write the file, in CODEX_HOME.
|
|
81
|
+
[login]
|
|
82
|
+
subscription = "ChatGPT"
|
|
83
|
+
# Unattended use (an automation's agent run): its terms not read yet.
|
|
84
|
+
unattended = "check-terms"
|
|
85
|
+
terms = "https://openai.com/policies/terms-of-use/"
|
|
86
|
+
command = ["npx", "--yes", "@openai/codex", "-c", "cli_auth_credentials_store=file", "login"]
|
|
87
|
+
env = { CODEX_HOME = "{dir}" }
|
|
88
|
+
file = "auth_json"
|
|
89
|
+
hosts = ["chatgpt.com:443", "auth.openai.com:443"]
|
|
90
|
+
help = "Sign in on the page it opens (or the link it prints)."
|
|
91
|
+
|
|
92
|
+
# Neutral permission mode → ACP session mode (codex-acp: read-only, agent, agent-full-access).
|
|
93
|
+
# None asks before an edit: "read-only" (despite its id) edits the project and runs commands in
|
|
94
|
+
# Codex's workspace-write sandbox without asking, and asks the user before anything outside it;
|
|
95
|
+
# that is "accept-edits". "ask" is left out: an agent asking for it is refused. ("agent" lets
|
|
96
|
+
# a model approve instead of the user: not mapped.) "agent-full-access" never asks and drops
|
|
97
|
+
# Codex's own sandbox (the container is the boundary).
|
|
98
|
+
# In a container Codex's sandbox cannot start (bubblewrap needs user namespaces, verified
|
|
99
|
+
# 2026-09-27 under the default `strict` confinement): in "read-only", each sandboxed command or
|
|
100
|
+
# edit fails and Codex asks to run it unsandboxed, so it asks more, never less.
|
|
101
|
+
[permissions]
|
|
102
|
+
accept-edits = { sessionMode = "read-only" }
|
|
103
|
+
bypass = { sessionMode = "agent-full-access" }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# pi harness image: toolbox + the pi coding agent + pi-acp, its ACP adapter (spawns `pi --mode rpc`)
|
|
2
|
+
# + pi-mcp-adapter, the pi extension that gives it MCP servers. pi-acp starts handler-pi instead of pi
|
|
3
|
+
# (PI_ACP_PI_COMMAND, harness.toml): pi with that extension, which reads the servers `render`
|
|
4
|
+
# writes to ~/.config/mcp/mcp.json.
|
|
5
|
+
#
|
|
6
|
+
# Not pinned: every rebuild (`handler image rebuild pi`, or the web UI) installs the latest releases.
|
|
7
|
+
# What was installed is recorded in /usr/share/handler/version (contract B.6); the build fails if
|
|
8
|
+
# the versions cannot be read, so a package rename is noticed.
|
|
9
|
+
ARG TOOLBOX_IMAGE=localhost/handler-ai-toolbox:latest
|
|
10
|
+
FROM ${TOOLBOX_IMAGE}
|
|
11
|
+
|
|
12
|
+
RUN npm install -g --no-fund --no-audit @earendil-works/pi-coding-agent pi-acp pi-mcp-adapter \
|
|
13
|
+
&& npm cache clean --force \
|
|
14
|
+
&& command -v pi \
|
|
15
|
+
&& command -v pi-acp \
|
|
16
|
+
&& modules=/usr/lib/node_modules \
|
|
17
|
+
&& test -f "$modules/pi-mcp-adapter/index.ts" \
|
|
18
|
+
&& printf '#!/bin/sh\nexec pi --extension %s "$@"\n' "$modules/pi-mcp-adapter" > /usr/local/bin/handler-pi \
|
|
19
|
+
&& chmod 755 /usr/local/bin/handler-pi \
|
|
20
|
+
&& pi_version="$(jq -er .version "$modules/@earendil-works/pi-coding-agent/package.json")" \
|
|
21
|
+
&& acp_version="$(jq -er .version "$modules/pi-acp/package.json")" \
|
|
22
|
+
&& mcp_version="$(jq -er .version "$modules/pi-mcp-adapter/package.json")" \
|
|
23
|
+
&& mkdir -p /usr/share/handler \
|
|
24
|
+
&& echo "pi ${pi_version} (pi-acp ${acp_version}, pi-mcp-adapter ${mcp_version})" > /usr/share/handler/version \
|
|
25
|
+
&& rm -rf /home/agent # build-time npm logs (npm runs as root with HOME=/home/agent)
|
|
26
|
+
|
|
27
|
+
LABEL org.opencontainers.image.title="handler-ai-pi"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# pi harness descriptor (contract B, handler.harness/v1). ACP through the pi-acp adapter.
|
|
2
|
+
apiVersion = "handler.harness/v1"
|
|
3
|
+
name = "pi"
|
|
4
|
+
product = "pi"
|
|
5
|
+
image = "localhost/handler-ai-pi" # no tag: launches use :latest (contract B.6)
|
|
6
|
+
acpCommand = ["pi-acp"]
|
|
7
|
+
home = "/home/agent"
|
|
8
|
+
|
|
9
|
+
# No account: pi has no subscription of its own (its /login signs in to other vendors'), so it runs
|
|
10
|
+
# on a provider, whose endpoint is the host it reaches. A provider it can use speaks:
|
|
11
|
+
providerProtocols = ["openai-chat"] # models.json + settings.json
|
|
12
|
+
|
|
13
|
+
# ACP config options (pi-acp): model, thought_level. Its ACP session modes are thinking levels.
|
|
14
|
+
modelOption = "model"
|
|
15
|
+
|
|
16
|
+
# User skills (verified: pi-acp and pi load <agent-dir>/skills, agent dir = ~/.pi/agent): each
|
|
17
|
+
# skill of the agent and its addons is mounted read-only at ~/.pi/agent/skills/<folder name>.
|
|
18
|
+
skillsDir = "~/.pi/agent/skills"
|
|
19
|
+
# MCP servers (its addons'): pi has no MCP of its own and pi-acp ignores
|
|
20
|
+
# session/new's mcpServers. The image runs pi with the pi-mcp-adapter extension (handler-pi), which
|
|
21
|
+
# reads ~/.config/mcp/mcp.json: render writes the servers there. It starts stdio servers with pi's
|
|
22
|
+
# whole environment, provider keys included ("sandbox": render wraps them with handler-sandbox-env to
|
|
23
|
+
# drop them); http servers fall back to SSE.
|
|
24
|
+
mcpTransports = ["stdio", "http", "sse"]
|
|
25
|
+
mcpEnv = "sandbox"
|
|
26
|
+
mcpConfigFile = "~/.config/mcp/mcp.json"
|
|
27
|
+
|
|
28
|
+
# pi writes its state under ~/.pi/agent (sessions, trust.json, settings.json) and pi-acp its
|
|
29
|
+
# session map under ~/.pi/pi-acp: writable, including when files are mounted inside it.
|
|
30
|
+
writablePaths = ["~/.pi"]
|
|
31
|
+
|
|
32
|
+
[env]
|
|
33
|
+
NODE_USE_ENV_PROXY = "1" # Node's fetch only honours HTTPS_PROXY with this
|
|
34
|
+
PI_ACP_PI_COMMAND = "handler-pi" # pi with the pi-mcp-adapter extension (see the Containerfile)
|
|
35
|
+
PI_SKIP_VERSION_CHECK = "1" # the image is the unit of upgrade (and pi.dev is not in egress)
|
|
36
|
+
PI_TELEMETRY = "0" # no install report to pi.dev, no attribution headers to providers
|
|
37
|
+
PI_OFFLINE = "1" # no startup network: version check, model catalog refresh from pi.dev,
|
|
38
|
+
# fd/rg downloads (the toolbox has them), package update checks
|
|
39
|
+
|
|
40
|
+
# pi's settings.json (defaultProvider, defaultModel, quietStartup…). pi rewrites it (e.g. the
|
|
41
|
+
# model chosen over ACP): copied, not mounted; changes discarded.
|
|
42
|
+
[configFiles.settings_json]
|
|
43
|
+
target = "~/.pi/agent/settings.json"
|
|
44
|
+
description = "pi settings.json"
|
|
45
|
+
seed = true
|
|
46
|
+
|
|
47
|
+
# Custom providers and models, e.g. a local OpenAI-compatible endpoint.
|
|
48
|
+
[configFiles.models_json]
|
|
49
|
+
target = "~/.pi/agent/models.json"
|
|
50
|
+
description = "pi models.json"
|
|
51
|
+
|
|
52
|
+
# Session history (verified: pi writes <agent dir>/sessions/<cwd>/*.jsonl, and pi-acp's
|
|
53
|
+
# session/load, when its ~/.pi/pi-acp session map does not know the id, finds the file by scanning
|
|
54
|
+
# that folder). Per session: the daemon binds it to the session's own folder (contracts F.2), so a
|
|
55
|
+
# stopped session can be resumed with ACP `session/load`. A settings.json `sessionDir` defeats it.
|
|
56
|
+
[stateSlots.sessions]
|
|
57
|
+
target = "~/.pi/agent/sessions"
|
|
58
|
+
kind = "dir"
|
|
59
|
+
required = false
|
|
60
|
+
perSession = true
|
|
61
|
+
|
|
62
|
+
# pi never asks before a tool call (docs/security.md: "it does not ask for approval"), so only
|
|
63
|
+
# "bypass" is honest. "ask" and "accept-edits" are left out: an agent asking for them is refused.
|
|
64
|
+
[permissions]
|
|
65
|
+
bypass = {}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Mistral Vibe harness image: toolbox + mistral-vibe (speaks ACP natively via `vibe-acp`).
|
|
2
|
+
#
|
|
3
|
+
# Not pinned: every rebuild (`handler image rebuild vibe`, or the web UI) installs the latest release.
|
|
4
|
+
# What was installed is recorded in /usr/share/handler/version (contract B.6).
|
|
5
|
+
ARG TOOLBOX_IMAGE=localhost/handler-ai-toolbox:latest
|
|
6
|
+
FROM ${TOOLBOX_IMAGE}
|
|
7
|
+
|
|
8
|
+
ENV UV_TOOL_DIR=/opt/uv-tools \
|
|
9
|
+
UV_TOOL_BIN_DIR=/usr/local/bin \
|
|
10
|
+
UV_PYTHON_INSTALL_DIR=/opt/uv-python
|
|
11
|
+
RUN uv tool install --python 3.12 mistral-vibe \
|
|
12
|
+
&& command -v vibe-acp \
|
|
13
|
+
&& version="$(uv tool list | sed -n 's/^mistral-vibe v//p')" \
|
|
14
|
+
&& test -n "$version" \
|
|
15
|
+
&& mkdir -p /usr/share/handler \
|
|
16
|
+
&& echo "Mistral Vibe ${version}" > /usr/share/handler/version \
|
|
17
|
+
&& rm -rf /home/agent # build-time caches (uv runs as root with HOME=/home/agent)
|
|
18
|
+
|
|
19
|
+
LABEL org.opencontainers.image.title="handler-ai-vibe"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Mistral Vibe harness descriptor (contract B, handler.harness/v1).
|
|
2
|
+
apiVersion = "handler.harness/v1"
|
|
3
|
+
name = "vibe"
|
|
4
|
+
product = "Mistral Vibe"
|
|
5
|
+
image = "localhost/handler-ai-vibe" # no tag: launches use :latest (contract B.6)
|
|
6
|
+
acpCommand = ["vibe-acp"]
|
|
7
|
+
home = "/home/agent"
|
|
8
|
+
|
|
9
|
+
# The hosts it reaches belong to what it runs on: the login below, or a provider (its endpoint).
|
|
10
|
+
# A provider it can use speaks:
|
|
11
|
+
providerProtocols = ["openai-chat"] # [[providers]] + [[models]] in config.toml
|
|
12
|
+
|
|
13
|
+
modelOption = "model" # ACP config option id (probed: mode, model, thinking)
|
|
14
|
+
|
|
15
|
+
# User skills (verified: vibe GLOBAL_SKILLS_DIR = VIBE_HOME/skills): each skill of the agent
|
|
16
|
+
# and its addons is mounted read-only at ~/.vibe/skills/<folder name>.
|
|
17
|
+
skillsDir = "~/.vibe/skills"
|
|
18
|
+
# MCP servers of session/new (verified 2026-09-26): vibe-acp starts stdio ones at session/new, with
|
|
19
|
+
# only HOME, PATH and the entry's env ("sandbox": render wraps them with handler-sandbox-env so they
|
|
20
|
+
# get the sandbox's environment). It advertises no mcpCapabilities: no http or sse.
|
|
21
|
+
mcpTransports = ["stdio"]
|
|
22
|
+
mcpEnv = "sandbox"
|
|
23
|
+
|
|
24
|
+
# Vibe writes its own state under ~/.vibe (trusted_folders.toml, logs…): it must be writable by
|
|
25
|
+
# the agent, including when files are mounted inside it (config.toml, skills).
|
|
26
|
+
writablePaths = ["~/.vibe"]
|
|
27
|
+
|
|
28
|
+
[env]
|
|
29
|
+
VIBE_CONFIG_DIR = "/home/agent/.vibe"
|
|
30
|
+
# Privacy (VIBE_* env beats config.toml, verified in mistral-vibe 2.25.8): enable_telemetry
|
|
31
|
+
# gates its usage events, Sentry error reports and GrowthBook experiments (and OTel traces).
|
|
32
|
+
VIBE_ENABLE_TELEMETRY = "false"
|
|
33
|
+
VIBE_ENABLE_UPDATE_CHECKS = "false" # the image is the unit of upgrade
|
|
34
|
+
VIBE_ENABLE_AUTO_UPDATE = "false"
|
|
35
|
+
|
|
36
|
+
# Vibe's configuration (providers, models, active_model, MCP servers…), e.g. to use a local
|
|
37
|
+
# model. Written from the agent's provider (handler_harness/providers.py), or given whole by the
|
|
38
|
+
# provider's [files.vibe] (a host file, an env var or inline). Without a provider (a Mistral
|
|
39
|
+
# account) Vibe uses its defaults.
|
|
40
|
+
[configFiles.config_toml]
|
|
41
|
+
target = "~/.vibe/config.toml"
|
|
42
|
+
description = "Vibe config.toml"
|
|
43
|
+
seed = true # Vibe rewrites it (e.g. model chosen over ACP): copied, not mounted; changes discarded
|
|
44
|
+
|
|
45
|
+
# Vibe keeps the rest of its state in the (ephemeral) home.
|
|
46
|
+
|
|
47
|
+
# Session history (verified: vibe.core.paths.SESSION_LOG_DIR = ~/.vibe/logs/session). Per
|
|
48
|
+
# session: the daemon binds it to the session's own folder (contracts F.2), so a stopped session
|
|
49
|
+
# can be resumed with ACP `session/load` and an agent sees only its own history.
|
|
50
|
+
[stateSlots.sessions]
|
|
51
|
+
target = "~/.vibe/logs/session"
|
|
52
|
+
kind = "dir"
|
|
53
|
+
required = false
|
|
54
|
+
perSession = true
|
|
55
|
+
|
|
56
|
+
# The key Vibe's sign-in registers, in the dotenv file it reads at start (the account's, see
|
|
57
|
+
# [login]). Vibe does not rewrite it.
|
|
58
|
+
[stateSlots.env_file]
|
|
59
|
+
target = "~/.vibe/.env"
|
|
60
|
+
kind = "file"
|
|
61
|
+
required = false
|
|
62
|
+
readOnly = true
|
|
63
|
+
credential = true
|
|
64
|
+
|
|
65
|
+
# An account: a Mistral account's Vibe key (verified 2026-09-30, mistral-vibe 2.25.8). `vibe
|
|
66
|
+
# --setup` (on the host, D.9) opens the sign-in page; its home is handler's folder, and without a
|
|
67
|
+
# keyring the key goes to its .env (else to the desktop's keyring).
|
|
68
|
+
[login]
|
|
69
|
+
subscription = "Mistral"
|
|
70
|
+
# Unattended use (an automation's agent run): its terms not read yet.
|
|
71
|
+
unattended = "check-terms"
|
|
72
|
+
terms = "https://mistral.ai/terms"
|
|
73
|
+
command = ["uvx", "--python", "3.12", "--from", "mistral-vibe", "vibe", "--setup"]
|
|
74
|
+
env = { VIBE_HOME = "{dir}", PYTHON_KEYRING_BACKEND = "keyring.backends.fail.Keyring" }
|
|
75
|
+
file = "env_file"
|
|
76
|
+
hosts = ["api.mistral.ai:443"]
|
|
77
|
+
help = "Choose to sign in with your Mistral account: its page opens in your browser. Then quit."
|
|
78
|
+
|
|
79
|
+
# Neutral permission mode → ACP session mode (probed: ask, plan, accept-edits, auto-approve).
|
|
80
|
+
[permissions]
|
|
81
|
+
ask = { sessionMode = "ask" }
|
|
82
|
+
accept-edits = { sessionMode = "accept-edits" }
|
|
83
|
+
bypass = { sessionMode = "auto-approve" }
|
|
84
|
+
|
|
85
|
+
# D.16: the harness's own terminal program (`handler terminal`). No model or MCP-config flags: VIBE_*
|
|
86
|
+
# env vars override its config.toml (verified mistral-vibe 2.25.8; env MCP servers are added to
|
|
87
|
+
# the config's, by name); the model is an alias of its config, as over ACP.
|
|
88
|
+
[terminal]
|
|
89
|
+
command = ["vibe"]
|
|
90
|
+
modelEnv = "VIBE_ACTIVE_MODEL"
|
|
91
|
+
mcpServersEnv = "VIBE_MCP_SERVERS"
|
|
92
|
+
[terminal.permissions]
|
|
93
|
+
ask = ["--agent", "ask"]
|
|
94
|
+
accept-edits = ["--agent", "accept-edits"]
|
|
95
|
+
bypass = ["--agent", "auto-approve"]
|
|
96
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# handler toolbox — the development environment agents work in. Yours to edit (README.md).
|
|
2
|
+
#
|
|
3
|
+
# Harness-independent: every harness and addon image is built FROM this one, so Claude Code,
|
|
4
|
+
# Vibe, … all get the same tools. Add tools here, then rebuild the toolbox (web UI or
|
|
5
|
+
# `handler image rebuild toolbox`): every harness and addon image is rebuilt on it and checked before any
|
|
6
|
+
# switches.
|
|
7
|
+
#
|
|
8
|
+
# Runtime conventions (set by handler-sandbox-podman, contract A3):
|
|
9
|
+
# - runs as the host user's uid/gid (keep-id), HOME=/home/agent on a private tmpfs;
|
|
10
|
+
# - toolchain state (~/.cargo, ~/.m2, ~/.npm, ~/.cache, ~/.local, podman's images, …) comes from
|
|
11
|
+
# the cache mounts declared in toolbox.toml (each project chooses whether they are shared),
|
|
12
|
+
# so nothing user-specific is baked in here;
|
|
13
|
+
# - chromium (and fonts) for web development and the playwright addon
|
|
14
|
+
# (docs/dev/design/browser.md §6): its own sandbox cannot start under `strict`, so it runs
|
|
15
|
+
# with --no-sandbox (the container is the boundary); headed only when the sandbox is given a
|
|
16
|
+
# display (contract A.6);
|
|
17
|
+
# - wl-clipboard: the clipboard of the display, when there is one (A.6), e.g. a harness's
|
|
18
|
+
# terminal UI pasting an image (`wl-paste`);
|
|
19
|
+
# - podman is here for podman in podman, which works only when the agent asks for
|
|
20
|
+
# `confinement = "nested"` (or `privileged`, contract A.5); the launcher then provides
|
|
21
|
+
# /etc/subuid, /etc/subgid and the inner podman's configuration (fuse-overlayfs, cgroupfs,
|
|
22
|
+
# events and `podman logs` in files: no systemd nor journald). Under the default `strict` it
|
|
23
|
+
# cannot start containers.
|
|
24
|
+
FROM docker.io/library/archlinux:latest
|
|
25
|
+
|
|
26
|
+
RUN pacman -Syu --noconfirm --needed \
|
|
27
|
+
base-devel gcc pkgconf make \
|
|
28
|
+
git openssh ca-certificates curl wget unzip zip tar \
|
|
29
|
+
ripgrep fd jq less which diffutils findutils procps-ng \
|
|
30
|
+
jdk25-openjdk maven \
|
|
31
|
+
python python-pip uv \
|
|
32
|
+
nodejs npm \
|
|
33
|
+
rustup sccache \
|
|
34
|
+
podman fuse-overlayfs passt \
|
|
35
|
+
chromium ttf-liberation ttf-dejavu \
|
|
36
|
+
wl-clipboard \
|
|
37
|
+
&& pacman -Scc --noconfirm
|
|
38
|
+
|
|
39
|
+
# Inner rootless podman (`nested`): newuidmap/newgidmap need their file capabilities, which
|
|
40
|
+
# pacman's install hook cannot set during an image build.
|
|
41
|
+
RUN setcap cap_setuid=ep /usr/bin/newuidmap \
|
|
42
|
+
&& setcap cap_setgid=ep /usr/bin/newgidmap
|
|
43
|
+
|
|
44
|
+
# Bun is not in the Arch repositories: official installer, into /usr/local (not ~/.bun, which is
|
|
45
|
+
# a cache mount at runtime).
|
|
46
|
+
ARG BUN_VERSION=bun-v1.4.2
|
|
47
|
+
RUN curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash -s "${BUN_VERSION}" \
|
|
48
|
+
&& rm -rf /usr/local/install
|
|
49
|
+
|
|
50
|
+
ENV LANG=C.UTF-8 \
|
|
51
|
+
HOME=/home/agent \
|
|
52
|
+
PATH=/home/agent/.cargo/bin:/home/agent/.local/bin:/home/agent/.bun/bin:/usr/local/bin:/usr/bin:/usr/sbin \
|
|
53
|
+
JAVA_HOME=/usr/lib/jvm/default
|
|
54
|
+
|
|
55
|
+
LABEL org.opencontainers.image.title="handler-ai-toolbox" \
|
|
56
|
+
org.opencontainers.image.description="Development environment for handler agent sandboxes"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# toolbox — `localhost/handler-ai-toolbox:latest`
|
|
2
|
+
|
|
3
|
+
The development environment agents work in, **independent of the harness**: every harness
|
|
4
|
+
and addon image is built `FROM` it, so all agents get the same tools. This folder is yours: handlerd
|
|
5
|
+
copied it to `~/.config/handler-ai/toolbox/` from its default on first start and never changes it
|
|
6
|
+
afterwards.
|
|
7
|
+
|
|
8
|
+
Default contents (Arch Linux): base-devel, gcc, pkgconf, git, JDK 25 + Maven, Python + pip + uv,
|
|
9
|
+
Node + npm, Bun, rustup + sccache, ripgrep, fd, jq, curl, Chromium, wl-clipboard (the display's
|
|
10
|
+
clipboard, contract A.6), podman.
|
|
11
|
+
|
|
12
|
+
- Add a tool to `Containerfile`, then `handler image rebuild toolbox` (or **Rebuild** in the web UI): it
|
|
13
|
+
also updates the system packages, rebuilds every harness and addon image on top, checks them,
|
|
14
|
+
then switches them all, or none.
|
|
15
|
+
- Toolchain *state* is not baked in: `toolbox.toml` declares the caches (`~/.cargo`, `~/.rustup`,
|
|
16
|
+
`~/.bun`, `~/.m2`, `~/.npm`, `~/.cache`, `~/.local`, inner podman's image store) mounted at
|
|
17
|
+
runtime, kept per project or global as each project chooses (contract D.8). A new toolchain with state to keep adds its table there
|
|
18
|
+
(`target`, `about`, `general` for a folder any tool writes to), or Settings → Caches does.
|
|
19
|
+
|
|
20
|
+
What Handler needs from it (contract B.6): a glibc system with `sh`; what the harness and addon
|
|
21
|
+
recipes install with (`npm`, `uv`, `jq`); `python3` for harnesses whose MCP servers run through
|
|
22
|
+
`handler-sandbox-env`; `chromium` for the playwright addon; podman with setcap'd
|
|
23
|
+
`newuidmap`/`newgidmap` only for agents with `confinement = "nested"`. Something missing shows as
|
|
24
|
+
a failed rebuild: nothing switches.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# What the toolbox declares besides its image (contract B.6).
|
|
2
|
+
#
|
|
3
|
+
# caches: the toolchains' state kept between sessions, one table per cache: target, the home
|
|
4
|
+
# path it is mounted at; about, what it holds; general = true for a folder any tool writes to
|
|
5
|
+
# (~/.cache), not one tool's. Each project chooses whether its agents keep their own, use the
|
|
6
|
+
# global ones with other projects (a write channel between projects), or get none (D.8).
|
|
7
|
+
|
|
8
|
+
[caches.cargo]
|
|
9
|
+
target = "~/.cargo"
|
|
10
|
+
about = "Rust crates, and the commands cargo installs (on PATH)"
|
|
11
|
+
|
|
12
|
+
[caches.rustup]
|
|
13
|
+
target = "~/.rustup"
|
|
14
|
+
about = "Rust toolchains"
|
|
15
|
+
|
|
16
|
+
[caches.bun]
|
|
17
|
+
target = "~/.bun"
|
|
18
|
+
about = "Bun's packages, and the commands it installs (on PATH)"
|
|
19
|
+
|
|
20
|
+
[caches.m2]
|
|
21
|
+
target = "~/.m2"
|
|
22
|
+
about = "Maven packages"
|
|
23
|
+
|
|
24
|
+
[caches.npm]
|
|
25
|
+
target = "~/.npm"
|
|
26
|
+
about = "npm packages"
|
|
27
|
+
|
|
28
|
+
[caches.cache]
|
|
29
|
+
target = "~/.cache"
|
|
30
|
+
about = "What tools cache: uv and pip packages, Playwright's browsers…"
|
|
31
|
+
general = true
|
|
32
|
+
|
|
33
|
+
[caches.local]
|
|
34
|
+
target = "~/.local"
|
|
35
|
+
about = "What is installed for the user: uv's tools and Pythons, pip --user, pipx; their commands are on PATH"
|
|
36
|
+
general = true
|
|
37
|
+
|
|
38
|
+
[caches.podman]
|
|
39
|
+
target = "~/.local/share/containers/storage"
|
|
40
|
+
about = "Container images and build layers (agents that run containers)"
|
|
41
|
+
general = true
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Harness and addon images: their names, how each is built, where it records its version.
|
|
2
|
+
|
|
3
|
+
Image layering (docs/dev/code-map.md):
|
|
4
|
+
images/toolbox/Containerfile → localhost/handler-ai-toolbox:<tag> (dev tools)
|
|
5
|
+
harnesses/<name>/Containerfile → <descriptor.image>:<tag> (the agent, FROM the toolbox)
|
|
6
|
+
addons/<name>/Containerfile → <manifest.image>:<tag> (a tool, built in the toolbox,
|
|
7
|
+
shipped FROM scratch, G.1)
|
|
8
|
+
|
|
9
|
+
Tags (contract B.6): agents launch `:latest`, the only tag. A rebuild (handlerd, D.4) builds an
|
|
10
|
+
untagged image from the files on disk, checks it, then tags it `:latest`. Harness recipes
|
|
11
|
+
are not version-pinned: a rebuild installs the latest release and the image records what it got
|
|
12
|
+
in IMAGE_VERSION_FILE.
|
|
13
|
+
|
|
14
|
+
Only data: the runtime builds and queries images (sandbox's `image` verbs, A.7, run by handlerd).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from handler_contracts import ADDON_VERSION_FILE, IMAGE_VERSION_FILE
|
|
21
|
+
|
|
22
|
+
from .addons import Addon
|
|
23
|
+
from .catalog import Harness, toolbox_dir
|
|
24
|
+
|
|
25
|
+
TOOLBOX = "localhost/handler-ai-toolbox"
|
|
26
|
+
LATEST = "latest"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def ref(image: str, tag: str) -> str:
|
|
30
|
+
return f"{image}:{tag}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class Recipe:
|
|
35
|
+
"""What a rebuild builds (D.4, G.4), for the runtime's `image build`."""
|
|
36
|
+
|
|
37
|
+
context: Path # holds the Containerfile
|
|
38
|
+
build_args: dict[str, str]
|
|
39
|
+
pull: bool # pull the base image again: system updates
|
|
40
|
+
no_cache: bool # every step runs again: the agent's or tool's latest release
|
|
41
|
+
no_intermediate: bool # keep only the final image (an addon's build stage would stay behind)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def toolbox_recipe() -> Recipe:
|
|
45
|
+
return Recipe(toolbox_dir(), {}, pull=True, no_cache=True, no_intermediate=False)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def recipe(unit: Harness | Addon, toolbox: str) -> Recipe:
|
|
49
|
+
"""A harness or an addon image, FROM the toolbox (a reference or an image id)."""
|
|
50
|
+
return Recipe(
|
|
51
|
+
unit.directory,
|
|
52
|
+
{"TOOLBOX_IMAGE": toolbox},
|
|
53
|
+
pull=False,
|
|
54
|
+
no_cache=True,
|
|
55
|
+
no_intermediate=isinstance(unit, Addon),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def version_file(unit: Harness | Addon) -> str:
|
|
60
|
+
"""Where the image records what it installed: B.6 for a harness; an addon's `version` (G.1) is
|
|
61
|
+
at the root of its image, which is mounted at its mount path."""
|
|
62
|
+
return f"/{ADDON_VERSION_FILE}" if isinstance(unit, Addon) else IMAGE_VERSION_FILE
|