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.
Files changed (234) hide show
  1. handler_ai-0.1.0.dist-info/METADATA +60 -0
  2. handler_ai-0.1.0.dist-info/RECORD +234 -0
  3. handler_ai-0.1.0.dist-info/WHEEL +4 -0
  4. handler_ai-0.1.0.dist-info/entry_points.txt +6 -0
  5. handler_cli/__init__.py +1 -0
  6. handler_cli/account_commands.py +314 -0
  7. handler_cli/addon_commands.py +128 -0
  8. handler_cli/agent_commands.py +139 -0
  9. handler_cli/app_commands.py +175 -0
  10. handler_cli/app_window.py +85 -0
  11. handler_cli/automation_commands.py +309 -0
  12. handler_cli/bridge.py +42 -0
  13. handler_cli/cli.py +326 -0
  14. handler_cli/client.py +81 -0
  15. handler_cli/daemon_commands.py +108 -0
  16. handler_cli/image_commands.py +102 -0
  17. handler_cli/inbox_commands.py +33 -0
  18. handler_cli/output.py +12 -0
  19. handler_cli/project_commands.py +63 -0
  20. handler_cli/provider_commands.py +113 -0
  21. handler_cli/secret_commands.py +64 -0
  22. handler_cli/session_bridge.py +93 -0
  23. handler_cli/session_commands.py +485 -0
  24. handler_cli/setup.py +120 -0
  25. handler_cli/terminal.py +280 -0
  26. handler_cli/terminal_client.py +37 -0
  27. handler_contracts/__init__.py +642 -0
  28. handler_contracts/account.py +36 -0
  29. handler_contracts/acp_endpoint.py +69 -0
  30. handler_contracts/addon.py +147 -0
  31. handler_contracts/agent_spec.py +75 -0
  32. handler_contracts/automation.py +285 -0
  33. handler_contracts/common.py +229 -0
  34. handler_contracts/control.py +1208 -0
  35. handler_contracts/harness.py +300 -0
  36. handler_contracts/miniapp.py +141 -0
  37. handler_contracts/provider.py +82 -0
  38. handler_contracts/remote.py +167 -0
  39. handler_contracts/sandbox.py +199 -0
  40. handler_contracts/schemas.py +313 -0
  41. handler_contracts/terminal.py +32 -0
  42. handler_control/__init__.py +7 -0
  43. handler_control/access.py +62 -0
  44. handler_control/accounts.py +279 -0
  45. handler_control/addon_install.py +268 -0
  46. handler_control/addons.py +118 -0
  47. handler_control/agent_state.py +46 -0
  48. handler_control/agents.py +257 -0
  49. handler_control/api/__init__.py +137 -0
  50. handler_control/api/accounts.py +231 -0
  51. handler_control/api/acp.py +116 -0
  52. handler_control/api/addons.py +95 -0
  53. handler_control/api/agents.py +150 -0
  54. handler_control/api/apps.py +104 -0
  55. handler_control/api/automations.py +128 -0
  56. handler_control/api/bindings.py +105 -0
  57. handler_control/api/builtin_agents.py +22 -0
  58. handler_control/api/caches.py +79 -0
  59. handler_control/api/catalog.py +197 -0
  60. handler_control/api/common.py +205 -0
  61. handler_control/api/contexts.py +88 -0
  62. handler_control/api/folders.py +51 -0
  63. handler_control/api/images.py +164 -0
  64. handler_control/api/inbox.py +21 -0
  65. handler_control/api/projects.py +170 -0
  66. handler_control/api/providers.py +107 -0
  67. handler_control/api/remote.py +45 -0
  68. handler_control/api/secrets.py +177 -0
  69. handler_control/api/sessions.py +192 -0
  70. handler_control/api/skills.py +270 -0
  71. handler_control/app_api.py +84 -0
  72. handler_control/app_http.py +214 -0
  73. handler_control/app_remote.py +206 -0
  74. handler_control/apps.py +867 -0
  75. handler_control/auth.py +48 -0
  76. handler_control/automation_config.py +255 -0
  77. handler_control/automations.py +1134 -0
  78. handler_control/bindings.py +149 -0
  79. handler_control/builds.py +341 -0
  80. handler_control/builtin_agents.py +82 -0
  81. handler_control/caches.py +113 -0
  82. handler_control/checks.py +287 -0
  83. handler_control/compose.py +317 -0
  84. handler_control/config_files.py +41 -0
  85. handler_control/contexts.py +75 -0
  86. handler_control/daemon.py +64 -0
  87. handler_control/data/automation/bin/handler-llm +85 -0
  88. handler_control/data/automations/env-issues/automation.toml +17 -0
  89. handler_control/data/automations/env-issues/extract.py +73 -0
  90. handler_control/data/automations/env-issues/findings.schema.json +28 -0
  91. handler_control/data/automations/env-issues/prompt.md +19 -0
  92. handler_control/data/builtin-agents/skill-creator/agent.toml +10 -0
  93. handler_control/data/builtin-agents/skill-creator/instructions.md +5 -0
  94. handler_control/data/builtin-agents/skill-creator/skills/handler-skills/SKILL.md +44 -0
  95. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/LICENSE.txt +202 -0
  96. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/SKILL.md +485 -0
  97. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/analyzer.md +274 -0
  98. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/comparator.md +202 -0
  99. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/grader.md +223 -0
  100. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/assets/eval_review.html +146 -0
  101. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  102. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  103. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/references/schemas.md +430 -0
  104. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/__init__.py +0 -0
  105. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  106. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/generate_report.py +326 -0
  107. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/improve_description.py +247 -0
  108. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/package_skill.py +136 -0
  109. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/quick_validate.py +103 -0
  110. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/run_eval.py +310 -0
  111. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/run_loop.py +328 -0
  112. handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/utils.py +47 -0
  113. handler_control/data/builtin-agents/skill-review/agent.toml +13 -0
  114. handler_control/data/builtin-agents/skill-review/instructions.md +30 -0
  115. handler_control/data/builtin-agents/skill-review/interface.json +83 -0
  116. handler_control/data/interfaces/llm/v1/interface.toml +17 -0
  117. handler_control/data/interfaces/llm/v1/tools.json +138 -0
  118. handler_control/data/templates/README.md +10 -0
  119. handler_control/data/templates/providers/anthropic-api.toml +18 -0
  120. handler_control/data/templates/providers/llamacpp.toml +210 -0
  121. handler_control/data/templates/providers/mistral-api.toml +14 -0
  122. handler_control/data/templates/providers/ollama.toml +10 -0
  123. handler_control/data/templates/providers/openai-api.toml +13 -0
  124. handler_control/data/templates/providers/openrouter.toml +10 -0
  125. handler_control/desktop.py +68 -0
  126. handler_control/display.py +62 -0
  127. handler_control/files.py +23 -0
  128. handler_control/folders.py +68 -0
  129. handler_control/git_read.py +39 -0
  130. handler_control/image_check.py +322 -0
  131. handler_control/image_updates.py +174 -0
  132. handler_control/inbox.py +70 -0
  133. handler_control/interfaces.py +151 -0
  134. handler_control/knowledge.py +237 -0
  135. handler_control/launcher.py +356 -0
  136. handler_control/llm.py +194 -0
  137. handler_control/migrations/__init__.py +62 -0
  138. handler_control/policy.py +305 -0
  139. handler_control/projects.py +266 -0
  140. handler_control/providers.py +180 -0
  141. handler_control/relay.py +133 -0
  142. handler_control/remote.py +888 -0
  143. handler_control/remote_crypto.py +97 -0
  144. handler_control/runtime.py +140 -0
  145. handler_control/sandbox_process.py +224 -0
  146. handler_control/schedules.py +58 -0
  147. handler_control/secrets.py +242 -0
  148. handler_control/service_router.py +180 -0
  149. handler_control/services.py +92 -0
  150. handler_control/session_files.py +429 -0
  151. handler_control/sessions.py +1116 -0
  152. handler_control/settings.py +129 -0
  153. handler_control/shortcuts.py +80 -0
  154. handler_control/skill_scan.py +88 -0
  155. handler_control/skill_sources.py +61 -0
  156. handler_control/skills.py +378 -0
  157. handler_control/terminal.py +79 -0
  158. handler_control/terminals.py +108 -0
  159. handler_control/transcript.py +174 -0
  160. handler_control/web-ui/assets/index-1w5RPsfA.js +190 -0
  161. handler_control/web-ui/assets/index-dWnzCLzQ.css +1 -0
  162. handler_control/web-ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  163. handler_control/web-ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  164. handler_control/web-ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  165. handler_control/web-ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  166. handler_control/web-ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  167. handler_control/web-ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  168. handler_control/web-ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  169. handler_control/web-ui/favicon.svg +1 -0
  170. handler_control/web-ui/index.html +15 -0
  171. handler_control/webpush.py +90 -0
  172. handler_control/webui.py +105 -0
  173. handler_harness/__init__.py +13 -0
  174. handler_harness/addons.py +129 -0
  175. handler_harness/catalog.py +116 -0
  176. handler_harness/cli.py +109 -0
  177. handler_harness/data/addons/README.md +44 -0
  178. handler_harness/data/addons/miniapp/Containerfile +20 -0
  179. handler_harness/data/addons/miniapp/PROVISIONING.md +16 -0
  180. handler_harness/data/addons/miniapp/addon.toml +16 -0
  181. handler_harness/data/addons/miniapp/skills/miniapp/SKILL.md +140 -0
  182. handler_harness/data/addons/miniapp/skills/miniapp/template/.gitignore +2 -0
  183. handler_harness/data/addons/miniapp/skills/miniapp/template/app.toml +15 -0
  184. handler_harness/data/addons/miniapp/skills/miniapp/template/server/db.ts +30 -0
  185. handler_harness/data/addons/miniapp/skills/miniapp/template/server/deno.json +7 -0
  186. handler_harness/data/addons/miniapp/skills/miniapp/template/server/handler.ts +26 -0
  187. handler_harness/data/addons/miniapp/skills/miniapp/template/server/main.ts +18 -0
  188. handler_harness/data/addons/miniapp/skills/miniapp/template/server/main_test.ts +32 -0
  189. handler_harness/data/addons/miniapp/skills/miniapp/template/server/migrations/001_notes.sql +6 -0
  190. handler_harness/data/addons/miniapp/skills/miniapp/template/server/routes.ts +32 -0
  191. handler_harness/data/addons/miniapp/skills/miniapp/template/ui/deno.json +12 -0
  192. handler_harness/data/addons/miniapp/skills/miniapp/template/ui/index.html +12 -0
  193. handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/App.svelte +44 -0
  194. handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/api.ts +16 -0
  195. handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/app.css +24 -0
  196. handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/main.ts +5 -0
  197. handler_harness/data/addons/miniapp/skills/miniapp/template/ui/vite.config.ts +17 -0
  198. handler_harness/data/catalog/README.md +12 -0
  199. handler_harness/data/catalog/playwright/Containerfile +28 -0
  200. handler_harness/data/catalog/playwright/PROVISIONING.md +53 -0
  201. handler_harness/data/catalog/playwright/addon.toml +25 -0
  202. handler_harness/data/catalog/playwright/handler/playwright-mcp-check.mjs +23 -0
  203. handler_harness/data/catalog/playwright/handler/playwright-mcp.mjs +61 -0
  204. handler_harness/data/catalog/playwright/skills/browser/SKILL.md +41 -0
  205. handler_harness/data/catalog/shopify/Containerfile +22 -0
  206. handler_harness/data/catalog/shopify/PROVISIONING.md +49 -0
  207. handler_harness/data/catalog/shopify/addon.toml +39 -0
  208. handler_harness/data/catalog/shopify/skills/shopify/SKILL.md +41 -0
  209. handler_harness/data/handler-sandbox-env +30 -0
  210. handler_harness/data/harnesses/README.md +47 -0
  211. handler_harness/data/harnesses/claude-code/Containerfile +27 -0
  212. handler_harness/data/harnesses/claude-code/harness.toml +95 -0
  213. handler_harness/data/harnesses/codex/Containerfile +26 -0
  214. handler_harness/data/harnesses/codex/harness.toml +103 -0
  215. handler_harness/data/harnesses/pi/Containerfile +27 -0
  216. handler_harness/data/harnesses/pi/harness.toml +65 -0
  217. handler_harness/data/harnesses/vibe/Containerfile +19 -0
  218. handler_harness/data/harnesses/vibe/harness.toml +96 -0
  219. handler_harness/data/toolbox/Containerfile +56 -0
  220. handler_harness/data/toolbox/README.md +24 -0
  221. handler_harness/data/toolbox/toolbox.toml +41 -0
  222. handler_harness/images.py +62 -0
  223. handler_harness/providers.py +300 -0
  224. handler_harness/render.py +472 -0
  225. handler_sandbox_podman/__init__.py +1 -0
  226. handler_sandbox_podman/caches.py +97 -0
  227. handler_sandbox_podman/check.py +121 -0
  228. handler_sandbox_podman/cli.py +239 -0
  229. handler_sandbox_podman/egress.py +118 -0
  230. handler_sandbox_podman/images.py +199 -0
  231. handler_sandbox_podman/launch.py +474 -0
  232. handler_sandbox_podman/podman.py +108 -0
  233. handler_sandbox_podman/proxy/Containerfile +6 -0
  234. handler_sandbox_podman/proxy/addon.py +255 -0
@@ -0,0 +1,18 @@
1
+ apiVersion = "handler.provider/v1"
2
+ description = "Anthropic API"
3
+ apiKey = { secret = "key" }
4
+
5
+ [endpoints]
6
+ anthropic = "https://api.anthropic.com"
7
+
8
+ [[models]]
9
+ id = "claude-sonnet-5-5"
10
+ name = "Claude Sonnet 5.5"
11
+ reasoning = true
12
+ images = true
13
+
14
+ [[models]]
15
+ id = "claude-opus-5-5"
16
+ name = "Claude Opus 5.5"
17
+ reasoning = true
18
+ images = true
@@ -0,0 +1,210 @@
1
+ # llama.cpp's llama-server (or llama-swap) on this machine: it must listen where the sandbox reaches
2
+ # it (llama-server --host 0.0.0.0 --jinja). One endpoint per protocol it speaks.
3
+ apiVersion = "handler.provider/v1"
4
+ description = "llama.cpp on this machine"
5
+
6
+ [endpoints]
7
+ openai-chat = "http://host.containers.internal:3000/v1"
8
+ responses = "http://host.containers.internal:3000/v1"
9
+
10
+ [[models]]
11
+ id = "qwen3.8-27B"
12
+ name = "Qwen3.8 27B (local)"
13
+ contextWindow = 200000
14
+ maxOutput = 32768
15
+ reasoning = true
16
+ temperature = 0.2
17
+
18
+ # What only pi knows about the model: the thinking level turns Qwen's thinking on or off.
19
+ [models.native.pi.compat]
20
+ thinkingFormat = "qwen-chat-template"
21
+ maxTokensField = "max_tokens"
22
+
23
+ # How Codex talks to this model: its system prompt (Codex has none for a model it does not know).
24
+ [files.codex]
25
+ instructions_md = { content = '''
26
+ You are a coding agent running in Codex, a terminal-based coding assistant, on a model served locally by the user. You run in a sandboxed container on the user's machine, with the user's project mounted in it. You are expected to be precise, safe, and helpful.
27
+
28
+ Your capabilities:
29
+
30
+ - Receive user prompts and other context provided by the harness, such as files in the workspace.
31
+ - Communicate with the user by streaming thinking & responses, and by making & updating plans.
32
+ - Emit function calls to run terminal commands, which is also how you read and edit files. Commands run without asking the user: the container is the safety boundary.
33
+ - Use the tools of MCP servers the user configured (e.g. a web browser) and the skills the user installed.
34
+
35
+ # How you work
36
+
37
+ ## Personality
38
+
39
+ Your default personality and tone is concise, direct, and friendly. You communicate efficiently, always keeping the user clearly informed about ongoing actions without unnecessary detail. You always prioritize actionable guidance, clearly stating assumptions, environment prerequisites, and next steps. Unless explicitly asked, you avoid excessively verbose explanations about your work.
40
+
41
+ # AGENTS.md spec
42
+ - Repos often contain AGENTS.md files. These files can appear anywhere within the repository.
43
+ - These files are a way for humans to give you (the agent) instructions or tips for working within the container.
44
+ - Some examples might be: coding conventions, info about how code is organized, or instructions for how to run or test code.
45
+ - Instructions in AGENTS.md files:
46
+ - The scope of an AGENTS.md file is the entire directory tree rooted at the folder that contains it.
47
+ - For every file you touch, you must obey instructions in any AGENTS.md file whose scope includes that file.
48
+ - Instructions about code style, structure, naming, etc. apply only to code within the AGENTS.md file's scope, unless the file states otherwise.
49
+ - More-deeply-nested AGENTS.md files take precedence in the case of conflicting instructions.
50
+ - Direct system/developer/user instructions (as part of a prompt) take precedence over AGENTS.md instructions.
51
+ - The contents of the AGENTS.md file at the root of the repo and any directories from the CWD up to the root are included with the developer message and don't need to be re-read. When working in a subdirectory of CWD, or a directory outside the CWD, check for any AGENTS.md files that may be applicable.
52
+
53
+ ## Skills and MCP tools
54
+
55
+ - Skills are instructions the user installed for a kind of task. When a skill matches the task, read its SKILL.md first and follow it: it describes the tools and steps that work in this environment.
56
+ - MCP tools (e.g. `browser_navigate`, `browser_snapshot` from a `playwright` server) are listed among your tools. When a task matches one, call it directly. Do not rebuild what it does with shell commands (e.g. do not launch a browser from the shell when browser tools exist).
57
+ - `list_mcp_resources` lists resources, not tools: an MCP server with tools may have no resources.
58
+
59
+ ## Responsiveness
60
+
61
+ ### Preamble messages
62
+
63
+ Before making tool calls, send a brief preamble to the user explaining what you're about to do. When sending preamble messages, follow these principles and examples:
64
+
65
+ - **Logically group related actions**: if you're about to run several related commands, describe them together in one preamble rather than sending a separate note for each.
66
+ - **Keep it concise**: be no more than 1-2 sentences, focused on immediate, tangible next steps. (8–12 words for quick updates).
67
+ - **Build on prior context**: if this is not your first tool call, use the preamble message to connect the dots with what's been done so far and create a sense of momentum and clarity for the user to understand your next actions.
68
+ - **Exception**: Avoid adding a preamble for every trivial read (e.g., `cat` a single file) unless it's part of a larger grouped action.
69
+
70
+ **Examples:**
71
+
72
+ - "I've explored the repo; now checking the API route definitions."
73
+ - "Next, I'll update the config and the related tests."
74
+ - "I'm about to scaffold the CLI commands and helper functions."
75
+ - "Config looks tidy. Next up is updating helpers to keep things in sync."
76
+
77
+ ## Planning
78
+
79
+ You have access to an `update_plan` tool which tracks steps and progress and renders them to the user. Using the tool helps demonstrate that you've understood the task and convey how you're approaching it. Plans can help to make complex, ambiguous, or multi-phase work clearer and more collaborative for the user. A good plan should break the task into meaningful, logically ordered steps that are easy to verify as you go.
80
+
81
+ Note that plans are not for padding out simple work with filler steps or stating the obvious. The content of your plan should not involve doing anything that you aren't capable of doing (i.e. don't try to test things that you can't test). Do not use plans for simple or single-step queries that you can just do or answer immediately.
82
+
83
+ Do not repeat the full contents of the plan after an `update_plan` call — the harness already displays it. Instead, summarize the change made and highlight any important context or next step.
84
+
85
+ Before running a command, consider whether or not you have completed the previous step, and make sure to mark it as completed before moving on to the next step. It may be the case that you complete all steps in your plan after a single pass of implementation. If this is the case, you can simply mark all the planned steps as completed. Sometimes, you may need to change plans in the middle of a task: call `update_plan` with the updated plan and make sure to provide an `explanation` of the rationale when doing so.
86
+
87
+ Use a plan when:
88
+
89
+ - The task is non-trivial and will require multiple actions over a long time horizon.
90
+ - There are logical phases or dependencies where sequencing matters.
91
+ - The work has ambiguity that benefits from outlining high-level goals.
92
+ - You want intermediate checkpoints for feedback and validation.
93
+ - When the user asked you to do more than one thing in a single prompt
94
+ - The user has asked you to use the plan tool (aka "TODOs")
95
+ - You generate additional steps while working, and plan to do them before yielding to the user
96
+
97
+ **High-quality plan:**
98
+
99
+ 1. Add CLI entry with file args
100
+ 2. Parse Markdown via CommonMark library
101
+ 3. Apply semantic HTML template
102
+ 4. Handle code blocks, images, links
103
+ 5. Add error handling for invalid files
104
+
105
+ **Low-quality plan:**
106
+
107
+ 1. Create CLI tool
108
+ 2. Add Markdown parser
109
+ 3. Convert to HTML
110
+
111
+ If you need to write a plan, only write high quality plans, not low quality ones.
112
+
113
+ ## Task execution
114
+
115
+ You are a coding agent. Please keep going until the query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved. Autonomously resolve the query to the best of your ability, using the tools available to you, before coming back to the user. Do NOT guess or make up an answer.
116
+
117
+ You MUST adhere to the following criteria when solving queries:
118
+
119
+ - Working on the repo(s) in the current environment is allowed, even if they are proprietary.
120
+ - Analyzing code for vulnerabilities is allowed.
121
+ - Showing user code and tool call details is allowed.
122
+ - Edit files with shell commands. For a new file or a full rewrite, write it with a heredoc (`cat > path <<'EOF'` … `EOF`). For a targeted change, use a short `python3` script that replaces an exact snippet and fails if the snippet is not found exactly once; avoid `sed -i` for multi-line or complex edits. After editing, check the result with `git diff -- path` (or `sed -n` on the changed lines).
123
+
124
+ If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. AGENTS.md) may override these guidelines:
125
+
126
+ - Fix the problem at the root cause rather than applying surface-level patches, when possible.
127
+ - Avoid unneeded complexity in your solution.
128
+ - Do not attempt to fix unrelated bugs or broken tests. It is not your responsibility to fix them. (You may mention them to the user in your final message though.)
129
+ - Update documentation as necessary.
130
+ - Keep changes consistent with the style of the existing codebase. Changes should be minimal and focused on the task.
131
+ - Use `git log` and `git blame` to search the history of the codebase if additional context is required.
132
+ - NEVER add copyright or license headers unless specifically requested.
133
+ - Do not `git commit` your changes or create new git branches unless explicitly requested.
134
+ - Do not add inline comments within code unless explicitly requested.
135
+ - Do not use one-letter variable names unless explicitly requested.
136
+ - NEVER output inline citations like "【F:README.md†L5-L14】" in your outputs. The UI is not able to render these so they will just be broken. Instead, output file paths as described below.
137
+
138
+ ## Validating your work
139
+
140
+ If the codebase has tests or the ability to build or run, use them to verify that your work is complete. Commands run without asking the user, so run tests, linters and builds proactively.
141
+
142
+ When testing, your philosophy should be to start as specific as possible to the code you changed so that you can catch issues efficiently, then make your way to broader tests as you build confidence. If there's no test for the code you changed, and if the adjacent patterns in the codebases show that there's a logical place for you to add a test, you may do so. However, do not add tests to codebases with no tests.
143
+
144
+ Similarly, once you're confident in correctness, you can suggest or use formatting commands to ensure that your code is well formatted. If there are issues you can iterate up to 3 times to get formatting right, but if you still can't manage it's better to save the user time and present them a correct solution where you call out the formatting in your final message. If the codebase does not have a formatter configured, do not add one.
145
+
146
+ For all of testing, running, building, and formatting, do not attempt to fix unrelated bugs. It is not your responsibility to fix them. (You may mention them to the user in your final message though.)
147
+
148
+ Never report that something works before you have seen it work: a command's exit code and output, a test result, a page actually loaded. If a check fails, read the error before retrying, and do not repeat an attempt that already failed the same way.
149
+
150
+ ## Ambition vs. precision
151
+
152
+ For tasks that have no prior context (i.e. the user is starting something brand new), you should feel free to be ambitious and demonstrate creativity with your implementation.
153
+
154
+ If you're operating in an existing codebase, you should make sure you do exactly what the user asks with surgical precision. Treat the surrounding codebase with respect, and don't overstep (i.e. changing filenames or variables unnecessarily). You should balance being sufficiently ambitious and proactive when completing tasks of this nature.
155
+
156
+ You should use judicious initiative to decide on the right level of detail and complexity to deliver based on the user's needs. This means showing good judgment that you're capable of doing the right extras without gold-plating. This might be demonstrated by high-value, creative touches when scope of the task is vague; while being surgical and targeted when scope is tightly specified.
157
+
158
+ ## Sharing progress updates
159
+
160
+ For especially longer tasks that you work on (i.e. requiring many tool calls, or a plan with multiple steps), you should provide progress updates back to the user at reasonable intervals. These updates should be structured as a concise sentence or two (no more than 8-10 words long) recapping progress so far in plain language: this update demonstrates your understanding of what needs to be done, progress so far (i.e. files explores, subtasks complete), and where you're going next.
161
+
162
+ Before doing large chunks of work that may incur latency as experienced by the user (i.e. writing a new file), you should send a concise message to the user with an update indicating what you're about to do to ensure they know what you're spending time on. Don't start editing or writing large files before informing the user what you are doing and why.
163
+
164
+ ## Presenting your work and final message
165
+
166
+ Your final message should read naturally, like an update from a concise teammate. For casual conversation, brainstorming tasks, or quick questions from the user, respond in a friendly, conversational tone. You should ask questions, suggest ideas, and adapt to the user's style. If you've finished a large amount of work, when describing what you've done to the user, you should follow the final answer formatting guidelines to communicate substantive changes. You don't need to add structured formatting for one-word answers, greetings, or purely conversational exchanges.
167
+
168
+ The user has access to the project you work on. As such there's no need to show the full contents of large files you have already written unless the user explicitly asks for them. Just reference the file path.
169
+
170
+ If there's something that you think you could help with as a logical next step, concisely ask the user if they want you to do so. Good examples of this are running tests, committing changes, or building out the next logical component. If there's something that you couldn't do but that the user might want to do (such as allowing a network host, or verifying changes by running the app), include those instructions succinctly.
171
+
172
+ Brevity is very important as a default. You should be very concise (i.e. no more than 10 lines), but can relax this requirement for tasks where additional detail and comprehensiveness is important for the user's understanding.
173
+
174
+ ### Final answer structure and style guidelines
175
+
176
+ Your messages are rendered as Markdown. Formatting should make results easy to scan, but not feel mechanical. Use judgment to decide how much structure adds value.
177
+
178
+ - **Headers**: only when they improve clarity; short (1–3 words), in `**Title Case**`.
179
+ - **Bullets**: `-` followed by a space; one line each when possible; merge related points; short lists (4–6) ordered by importance; no nesting.
180
+ - **Monospace**: wrap commands, file paths, env vars and code identifiers in backticks. Never mix monospace and bold.
181
+ - **File references**: a standalone inline-code path per reference, workspace-relative or absolute, with an optional 1-based line (`src/app.ts:42`). No `file://` or other URIs, no line ranges.
182
+ - **Tone**: collaborative and natural, like a coding partner handing off work; concise and factual; present tense, active voice; no filler or repetition.
183
+ - Match structure to complexity: multi-part results get grouped bullets; simple results a short list or paragraph; casual messages plain sentences.
184
+
185
+ # Tool Guidelines
186
+
187
+ ## Shell commands
188
+
189
+ When using the shell, you must adhere to the following guidelines:
190
+
191
+ - When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.)
192
+ - Read files in bounded chunks (`sed -n '1,200p' path`) rather than dumping large files: long output is truncated.
193
+ - Do not use python scripts to attempt to output larger chunks of a file.
194
+ - Processes started in the background of a command (`&`, `nohup`) are killed when that command ends. To keep a process running (a dev server, a watcher), start it with `exec_command` in the foreground: when it is still running after the wait, you get a session id, and `write_stdin` with that id reads its new output (send empty input to just poll).
195
+
196
+ ## Network
197
+
198
+ - The container reaches only the hosts the user allowed. When a request fails because a host is blocked (a proxy error, `ERR_TUNNEL_CONNECTION_FAILED`), do not try to work around it: tell the user which host is needed so they can allow it.
199
+ - Servers you start yourself are reachable at `http://127.0.0.1:<port>`.
200
+
201
+ ## `update_plan`
202
+
203
+ A tool named `update_plan` is available to you. You can use it to keep an up‑to‑date, step‑by‑step plan for the task.
204
+
205
+ To create a new plan, call `update_plan` with a short list of 1‑sentence steps (no more than 5-7 words each) with a `status` for each step (`pending`, `in_progress`, or `completed`).
206
+
207
+ When steps have been completed, use `update_plan` to mark each finished step as `completed` and the next step you are working on as `in_progress`. There should always be exactly one `in_progress` step until everything is done. You can mark multiple items as complete in a single `update_plan` call.
208
+
209
+ If all steps are complete, ensure you call `update_plan` to mark all steps as `completed`.
210
+ ''' }
@@ -0,0 +1,14 @@
1
+ apiVersion = "handler.provider/v1"
2
+ description = "Mistral API"
3
+ apiKey = { secret = "key" }
4
+
5
+ [endpoints]
6
+ openai-chat = "https://api.mistral.ai/v1"
7
+
8
+ [[models]]
9
+ id = "devstral-medium-latest"
10
+ name = "Devstral Medium"
11
+
12
+ [[models]]
13
+ id = "mistral-large-latest"
14
+ name = "Mistral Large"
@@ -0,0 +1,10 @@
1
+ # Ollama on this machine: it must listen where the sandbox reaches it (OLLAMA_HOST=0.0.0.0).
2
+ apiVersion = "handler.provider/v1"
3
+ description = "Ollama on this machine"
4
+
5
+ [endpoints]
6
+ openai-chat = "http://127.0.0.1:11434/v1"
7
+
8
+ [[models]]
9
+ id = "qwen3:8b"
10
+ name = "Qwen3 8B (a model you pulled)"
@@ -0,0 +1,13 @@
1
+ apiVersion = "handler.provider/v1"
2
+ description = "OpenAI API"
3
+ apiKey = { secret = "key" }
4
+
5
+ [endpoints]
6
+ responses = "https://api.openai.com/v1"
7
+ openai-chat = "https://api.openai.com/v1"
8
+
9
+ [[models]]
10
+ id = "gpt-5"
11
+ name = "GPT-5"
12
+ reasoning = true
13
+ images = true
@@ -0,0 +1,10 @@
1
+ apiVersion = "handler.provider/v1"
2
+ description = "OpenRouter"
3
+ apiKey = { secret = "key" }
4
+
5
+ [endpoints]
6
+ openai-chat = "https://openrouter.ai/api/v1"
7
+
8
+ [[models]]
9
+ id = "openrouter/auto"
10
+ name = "OpenRouter Auto"
@@ -0,0 +1,68 @@
1
+ """The person's desktop. Notifications (H.9): `notify-send`, else the freedesktop notification
2
+ service over `gdbus`; nothing when neither works (no desktop session). Best effort: a
3
+ notification is in handler's inbox anyway. A file or folder shown in the file manager (F.8)."""
4
+
5
+ import shutil
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ TIMEOUT = 5
10
+
11
+
12
+ def notify(title: str, body: str | None) -> bool:
13
+ if sender := shutil.which("notify-send"):
14
+ argv = [sender, "--app-name=handler", title, *([body] if body else [])]
15
+ elif gdbus := shutil.which("gdbus"):
16
+ argv = [
17
+ gdbus, "call", "--session",
18
+ "--dest", "org.freedesktop.Notifications",
19
+ "--object-path", "/org/freedesktop/Notifications",
20
+ "--method", "org.freedesktop.Notifications.Notify",
21
+ "handler", "0", "", _gvariant(title), _gvariant(body or ""), "[]", "{}", "-1",
22
+ ] # fmt: skip
23
+ else:
24
+ return False
25
+ try:
26
+ r = subprocess.run(argv, capture_output=True, timeout=TIMEOUT, check=False)
27
+ except (OSError, subprocess.TimeoutExpired):
28
+ return False
29
+ return r.returncode == 0
30
+
31
+
32
+ def reveal(path: Path, is_folder: bool) -> bool:
33
+ """`path` shown in the file manager, selected in its folder (freedesktop `FileManager1`),
34
+ never opened: a file the agent wrote could be a program. Without that service, its folder
35
+ (or itself, a folder) opened with `xdg-open`. False when nothing could show it."""
36
+ method = "ShowFolders" if is_folder else "ShowItems"
37
+ if gdbus := shutil.which("gdbus"):
38
+ argv = [
39
+ gdbus, "call", "--session",
40
+ "--dest", "org.freedesktop.FileManager1",
41
+ "--object-path", "/org/freedesktop/FileManager1",
42
+ "--method", f"org.freedesktop.FileManager1.{method}",
43
+ f"[{_gvariant(path.as_uri())}]", "''",
44
+ ] # fmt: skip
45
+ try:
46
+ r = subprocess.run(argv, capture_output=True, timeout=TIMEOUT, check=False)
47
+ if r.returncode == 0:
48
+ return True
49
+ except (OSError, subprocess.TimeoutExpired):
50
+ pass
51
+ if opener := shutil.which("xdg-open"):
52
+ folder = path if is_folder else path.parent
53
+ try: # it may wait for the file manager to exit
54
+ subprocess.Popen(
55
+ [opener, str(folder)],
56
+ stdin=subprocess.DEVNULL,
57
+ stdout=subprocess.DEVNULL,
58
+ stderr=subprocess.DEVNULL,
59
+ start_new_session=True,
60
+ )
61
+ except OSError:
62
+ return False
63
+ return True
64
+ return False
65
+
66
+
67
+ def _gvariant(s: str) -> str:
68
+ return "'" + s.replace("\\", "\\\\").replace("'", "\\'") + "'"
@@ -0,0 +1,62 @@
1
+ """The display an agent may give its sandboxes (contract A.6): the host's Wayland socket.
2
+
3
+ Found at each agent start (a compositor restart may change it), in this order:
4
+ $HANDLER_WAYLAND_SOCKET an explicit socket path
5
+ $WAYLAND_DISPLAY a name in $XDG_RUNTIME_DIR, or an absolute path
6
+ $XDG_RUNTIME_DIR/wayland-N the only one there
7
+ handlerd runs as a systemd user service, which only has WAYLAND_DISPLAY if the compositor exported
8
+ it to systemd; the last rule covers the usual case of one Wayland session.
9
+ """
10
+
11
+ import os
12
+ import stat
13
+ from collections.abc import Mapping
14
+ from pathlib import Path
15
+
16
+ from handler_contracts import WaylandDisplay
17
+
18
+
19
+ class DisplayError(RuntimeError):
20
+ """No usable Wayland display: the agent asked for one, it is never started without."""
21
+
22
+
23
+ def _is_socket(path: Path) -> bool:
24
+ try:
25
+ return stat.S_ISSOCK(path.stat().st_mode)
26
+ except OSError:
27
+ return False
28
+
29
+
30
+ def wayland_socket(env: Mapping[str, str] | None = None) -> str:
31
+ """The host's Wayland socket, absolute. Raises DisplayError."""
32
+ env = os.environ if env is None else env
33
+ runtime = Path(env.get("XDG_RUNTIME_DIR") or f"/run/user/{os.getuid()}")
34
+ if explicit := env.get("HANDLER_WAYLAND_SOCKET"):
35
+ candidates, origin = [Path(explicit)], "HANDLER_WAYLAND_SOCKET"
36
+ elif name := env.get("WAYLAND_DISPLAY"):
37
+ candidates, origin = [runtime / name], "WAYLAND_DISPLAY" # an absolute name wins the join
38
+ else:
39
+ candidates = sorted(p for p in runtime.glob("wayland-*") if _is_socket(p))
40
+ origin = f"{runtime}/wayland-*"
41
+ if len(candidates) > 1:
42
+ names = ", ".join(p.name for p in candidates)
43
+ raise DisplayError(
44
+ f"several Wayland displays in {runtime} ({names}): set HANDLER_WAYLAND_SOCKET "
45
+ "for handlerd to the one to use"
46
+ )
47
+ if not candidates or not _is_socket(candidates[0]):
48
+ where = candidates[0] if candidates else origin
49
+ raise DisplayError(f"no Wayland display ({where}): is a Wayland session running?")
50
+ return str(candidates[0])
51
+
52
+
53
+ def wayland(env: Mapping[str, str] | None = None) -> WaylandDisplay:
54
+ return WaylandDisplay(mode="wayland", socket=wayland_socket(env))
55
+
56
+
57
+ def available() -> bool:
58
+ try:
59
+ wayland_socket()
60
+ except DisplayError:
61
+ return False
62
+ return True
@@ -0,0 +1,23 @@
1
+ """The daemon's own files: writing them whole, atomically, readable by the user only (0600), and
2
+ saying in one line why one is invalid."""
3
+
4
+ import os
5
+ from pathlib import Path
6
+
7
+ from pydantic import ValidationError
8
+
9
+
10
+ def write_private(path: Path, text: str) -> None:
11
+ path.parent.mkdir(parents=True, exist_ok=True)
12
+ tmp = path.with_name(path.name + ".tmp")
13
+ fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
14
+ with os.fdopen(fd, "w") as f:
15
+ f.write(text)
16
+ tmp.replace(path)
17
+
18
+
19
+ def describe(error: ValidationError) -> str:
20
+ """One readable line: `field.path: message; …`."""
21
+ return "; ".join(
22
+ f"{'.'.join(str(p) for p in e['loc']) or 'file'}: {e['msg']}" for e in error.errors()
23
+ )
@@ -0,0 +1,68 @@
1
+ """Work folders (D.13): the folders agents may work in, owned by this computer. A project's folder
2
+ and an ad-hoc session's must be inside one (compose.check_scope). Stored as written (`~/…` kept)
3
+ in <config>/folders.toml, `roots = ["~/code"]`, edited in Settings → Folders; ~/code when unset.
4
+ Never one of the daemon's own folders, nor one containing them: an agent working there could use
5
+ the API as the user (contracts §2, invariant 5).
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import tomllib
11
+ from pathlib import Path
12
+
13
+ from handler_contracts import is_within
14
+
15
+ from .settings import Settings
16
+
17
+ CONFIG_FILE = "folders.toml"
18
+ DEFAULT = ["~/code"]
19
+
20
+
21
+ class FoldersError(ValueError):
22
+ """Work folders that cannot be saved (not absolute, the daemon's own)."""
23
+
24
+
25
+ def daemon_folders(settings: Settings) -> list[Path]:
26
+ """The daemon's own folders (its token, config, state and data): never given to an agent
27
+ through the API (contracts §2, invariant 5)."""
28
+ return [
29
+ settings.config_dir,
30
+ settings.bindings_dir,
31
+ settings.accounts_dir,
32
+ settings.providers_dir,
33
+ settings.state_dir,
34
+ settings.data_dir,
35
+ ]
36
+
37
+
38
+ def written(settings: Settings) -> list[str]:
39
+ """As saved, else the default. Read on every call: a change applies to the next check."""
40
+ try:
41
+ roots = tomllib.loads((settings.config_dir / CONFIG_FILE).read_text()).get("roots")
42
+ except (OSError, tomllib.TOMLDecodeError):
43
+ return list(DEFAULT)
44
+ return [r for r in roots if isinstance(r, str)] if isinstance(roots, list) else list(DEFAULT)
45
+
46
+
47
+ def roots(settings: Settings) -> list[str]:
48
+ """Absolute, symbolic links resolved."""
49
+ return [os.path.realpath(os.path.expanduser(r)) for r in written(settings)]
50
+
51
+
52
+ def save(settings: Settings, entries: list[str]) -> list[str]:
53
+ """Replace them (duplicates dropped, as written); FoldersError when one is not absolute or
54
+ is, contains or is inside one of the daemon's folders."""
55
+ guarded = [os.path.realpath(d) for d in daemon_folders(settings)]
56
+ kept = list(dict.fromkeys(e.strip() for e in entries))
57
+ for entry in kept:
58
+ if not os.path.isabs(os.path.expanduser(entry)):
59
+ raise FoldersError(f"{entry}: an absolute path, or one starting with ~/")
60
+ real = os.path.realpath(os.path.expanduser(entry))
61
+ if any(is_within(real, d) or is_within(d, real) for d in guarded):
62
+ raise FoldersError(f"{entry} holds or is inside the daemon's own files")
63
+ config = settings.config_dir / CONFIG_FILE
64
+ config.parent.mkdir(parents=True, exist_ok=True)
65
+ tmp = config.with_suffix(".tmp")
66
+ tmp.write_text(f"roots = {json.dumps(kept)}\n") # a JSON array of strings is a TOML one
67
+ tmp.replace(config)
68
+ return kept
@@ -0,0 +1,39 @@
1
+ """Reads a session folder's git repository for `session_files`, as a child process of handlerd:
2
+ `python -m handler_control.git_read head <folder>` prints the commit HEAD is at,
3
+ `… show <folder> <commit> <path>` the file's content at that commit; exit 1 when there is none.
4
+
5
+ The repository is the agent's: dulwich reads it and runs nothing from it (no hooks, filters,
6
+ fsmonitor, diff drivers, unlike git), and this process, not handlerd, bears what a malformed one
7
+ does (a link to a device, a huge file): memory is capped and handlerd stops waiting after a
8
+ while."""
9
+
10
+ import resource
11
+ import sys
12
+
13
+ from dulwich.object_store import tree_lookup_path
14
+ from dulwich.objects import Blob, Commit
15
+ from dulwich.repo import Repo
16
+
17
+ MEMORY = 1 << 30
18
+
19
+
20
+ def main(args: list[str]) -> int:
21
+ resource.setrlimit(resource.RLIMIT_AS, (MEMORY, MEMORY))
22
+ try:
23
+ with Repo(args[1]) as repo:
24
+ if args[0] == "head":
25
+ sys.stdout.buffer.write(repo.head())
26
+ return 0
27
+ commit = repo[args[2].encode()]
28
+ assert isinstance(commit, Commit)
29
+ _, sha = tree_lookup_path(repo.__getitem__, commit.tree, args[3].encode())
30
+ blob = repo[sha]
31
+ assert isinstance(blob, Blob)
32
+ sys.stdout.buffer.write(blob.as_raw_string())
33
+ return 0
34
+ except Exception: # whatever the repository holds: there is nothing to show
35
+ return 1
36
+
37
+
38
+ if __name__ == "__main__":
39
+ sys.exit(main(sys.argv[1:]))