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,229 @@
1
+ """Building blocks shared by several contracts (docs/dev/contracts.md)."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Annotated, Literal
6
+ from urllib.parse import urlparse
7
+
8
+ from pydantic import AfterValidator, BaseModel, BeforeValidator, ConfigDict, Field
9
+ from pydantic.alias_generators import to_camel
10
+
11
+
12
+ def _absolute_path(p: str) -> str:
13
+ if not p.startswith("/"):
14
+ raise ValueError("must be an absolute path")
15
+ if p != "/" and any(seg in ("", ".", "..") for seg in p[1:].split("/")):
16
+ raise ValueError("must be normalized (no empty, '.' or '..' segments, no trailing slash)")
17
+ return p
18
+
19
+
20
+ def _expand_home(p: object) -> object:
21
+ """'~' and '~/rel' → the home of the user reading the document (the daemon's user)."""
22
+ if isinstance(p, str) and (p == "~" or p.startswith("~/")):
23
+ return str(Path.home()) + p[1:]
24
+ return p
25
+
26
+
27
+ def _home_path(p: str) -> str:
28
+ if not (p == "~" or p.startswith("~/") or p.startswith("/")):
29
+ raise ValueError("must be absolute or start with '~/'")
30
+ if any(seg in (".", "..") for seg in p.split("/")[1:]):
31
+ raise ValueError("must not contain '.' or '..' segments")
32
+ return p
33
+
34
+
35
+ _EGRESS_HOST_RE = re.compile(r"^(\*\.)?([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$")
36
+ _OCTET = r"(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)"
37
+ _IPV4_RE = re.compile(rf"^({_OCTET}\.){{3}}{_OCTET}$")
38
+ _PORT_RE = re.compile(r"^([1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d\d|655[0-2]\d|6553[0-5])$")
39
+
40
+
41
+ def _egress_entry(e: str) -> str:
42
+ host, sep, port = e.rpartition(":")
43
+ if not (sep and _PORT_RE.match(port) and (_EGRESS_HOST_RE.match(host) or _IPV4_RE.match(host))):
44
+ raise ValueError(
45
+ "must be HOST:PORT, HOST a lowercase hostname (optionally prefixed with '*.') or an "
46
+ "IPv4 address, and an explicit port (443 for https, 80 for http)"
47
+ )
48
+ return e
49
+
50
+
51
+ def egress_target(url: str) -> str:
52
+ """The allow-list entry a URL needs: its lowercased host and port, the scheme's default port
53
+ (https 443, http 80) when the URL has none."""
54
+ parsed = urlparse(url)
55
+ port = parsed.port or {"https": 443, "http": 80}[parsed.scheme]
56
+ return f"{(parsed.hostname or '').lower()}:{port}"
57
+
58
+
59
+ AbsolutePath = Annotated[str, AfterValidator(_absolute_path)]
60
+ """Absolute, normalized path: starts with '/', no '.', '..' or empty segments."""
61
+
62
+ HostPath = Annotated[str, BeforeValidator(_expand_home), AfterValidator(_absolute_path)]
63
+ """Host path in a hand-written document (providers, bindings, mini app manifests): an
64
+ AbsolutePath, or '~' / '~/rel' for the host user's home, expanded when the document is read."""
65
+
66
+ HomePath = Annotated[str, AfterValidator(_home_path)]
67
+ """Sandbox path that may be relative to the harness home: '/abs', '~' or '~/rel'."""
68
+
69
+ EgressEntry = Annotated[str, AfterValidator(_egress_entry)]
70
+ """Allow-list entry (A.3), always with its port: 'example.com:443' exact host, '*.example.com:443'
71
+ subdomains only, '192.168.1.20:11434' an IPv4 address (e.g. a model server on the LAN). No default
72
+ port: https is 443, http 80, and any other port is written out."""
73
+
74
+ EnvName = Annotated[str, Field(pattern=r"^[A-Za-z_][A-Za-z0-9_]*$")]
75
+ EnvMap = dict[EnvName, str]
76
+
77
+ PermissionMode = Literal["ask", "accept-edits", "bypass"]
78
+ PERMISSION_MODES: tuple[PermissionMode, ...] = ("ask", "accept-edits", "bypass")
79
+ NetworkMode = Literal["none", "filtered", "open"]
80
+ # D.8: a project's agents use the global caches (with other projects), their own, or none
81
+ CacheSharing = Literal["global", "project", "off"]
82
+ Enforcement = Literal["enforce", "log-only"]
83
+ Confinement = Literal["strict", "nested", "privileged"]
84
+ """How locked down a container is (A.5): "strict" = the A3 hardening; "nested" = what running
85
+ containers inside it needs (podman-in-podman); "privileged" = no confinement beyond the user
86
+ namespace, about as safe as no sandbox."""
87
+
88
+
89
+ class Document(BaseModel):
90
+ """Exchanged document: camelCase on the wire, unknown fields ignored (contracts §0)."""
91
+
92
+ model_config = ConfigDict(
93
+ alias_generator=to_camel,
94
+ populate_by_name=True,
95
+ extra="ignore",
96
+ frozen=True,
97
+ )
98
+
99
+
100
+ ResourceName = Annotated[str, Field(min_length=1, pattern=r"^[a-z0-9][a-z0-9_.-]*$")]
101
+ """The name of an account, a provider or a binding: its file's stem."""
102
+
103
+ ANY_CONTEXT = "any"
104
+ """In an account's or a provider's contexts: usable in every project (a local model)."""
105
+
106
+
107
+ def _not_any(name: str) -> str:
108
+ if name.lower() == ANY_CONTEXT:
109
+ raise ValueError(f"'{ANY_CONTEXT}' is not a context's name: it means every context")
110
+ return name
111
+
112
+
113
+ _CONTEXT = Field(min_length=1, max_length=32, pattern=r"^[A-Za-z0-9]([A-Za-z0-9 _-]*[A-Za-z0-9])?$")
114
+ ContextName = Annotated[str, _CONTEXT, AfterValidator(_not_any)]
115
+ """A project context (D.19): "Work", "Personal", a client's name."""
116
+ AccessContext = Annotated[str, _CONTEXT]
117
+ """Where an account or a provider may be used (D.19): a context's name, or `any`."""
118
+ ContextColor = Literal["blue", "violet", "cyan", "pink", "slate"]
119
+ """A context's colour (D.19): never the accent's nor a status's (orange, green, amber, red)."""
120
+
121
+
122
+ class StrictDocument(Document):
123
+ """Hand-written document (accounts, providers, bindings, mini app manifests): unknown keys
124
+ are errors (typo protection)."""
125
+
126
+ model_config = ConfigDict(extra="forbid")
127
+
128
+
129
+ class EnvSecret(StrictDocument):
130
+ env: EnvName
131
+
132
+
133
+ class FileSecret(StrictDocument):
134
+ file: HostPath
135
+
136
+
137
+ class CommandSecret(StrictDocument):
138
+ command: Annotated[list[Annotated[str, Field(min_length=1)]], Field(min_length=1)]
139
+
140
+
141
+ # A value in handler's secret store (D.14), named after what owns it: `providers/openrouter/apiKey`,
142
+ # `apps/notes/TOKEN`. Segments never start with a dot.
143
+ SecretPath = Annotated[
144
+ str,
145
+ Field(
146
+ max_length=256,
147
+ pattern=r"^[A-Za-z0-9_-][A-Za-z0-9_.-]*(/[A-Za-z0-9_-][A-Za-z0-9_.-]*)*$",
148
+ ),
149
+ ]
150
+
151
+
152
+ class StoreSecret(StrictDocument):
153
+ secret: SecretPath
154
+
155
+
156
+ SecretRef = EnvSecret | FileSecret | CommandSecret | StoreSecret
157
+ """Reference to a secret, never the value itself (E2)."""
158
+
159
+
160
+ class FileContent(StrictDocument):
161
+ content: str
162
+
163
+
164
+ FileSource = FileSecret | EnvSecret | FileContent
165
+ """Where a config file's content comes from: a host file (`file`), an environment variable of
166
+ the daemon (`env`), or inline in the provider (`content`)."""
167
+
168
+
169
+ # app: H.3; subject: the handler object a run works on (D.18), read-only
170
+ MountKind = Literal["scope", "state", "cache", "config", "knowledge", "app", "subject"]
171
+
172
+
173
+ class Mount(Document):
174
+ """Filesystem mount as seen by the sandbox (A.1). Targets are absolute."""
175
+
176
+ source: AbsolutePath
177
+ target: AbsolutePath
178
+ read_only: bool
179
+ kind: MountKind
180
+
181
+
182
+ class GeneratedFile(Document):
183
+ """File generated by the platform: mounted read-only into the sandbox, or — with `seed` —
184
+ copied into a writable path before the process starts, so the harness may rewrite it
185
+ (changes are discarded with the sandbox). A seeded file must lie under a writable path."""
186
+
187
+ target: AbsolutePath
188
+ content: str
189
+ mode: Annotated[str, Field(pattern=r"^0?[0-7]{3}$")] | None = None
190
+ seed: bool = False
191
+
192
+
193
+ class NameValue(StrictDocument):
194
+ name: str
195
+ value: str
196
+
197
+
198
+ class StdioMcpServer(StrictDocument):
199
+ name: Annotated[str, Field(min_length=1)]
200
+ command: Annotated[str, Field(min_length=1)]
201
+ args: list[str] = []
202
+ env: list[NameValue] = []
203
+
204
+
205
+ class HttpMcpServer(StrictDocument):
206
+ type: Literal["http", "sse"]
207
+ name: Annotated[str, Field(min_length=1)]
208
+ url: Annotated[str, Field(pattern=r"^https?://")]
209
+ headers: list[NameValue] = []
210
+
211
+
212
+ McpServer = StdioMcpServer | HttpMcpServer
213
+ """MCP server, same shape as ACP `session/new` `mcpServers` entries."""
214
+
215
+ McpTransport = Literal["stdio", "http", "sse"]
216
+
217
+
218
+ def mcp_transport(server: McpServer) -> McpTransport:
219
+ return "stdio" if isinstance(server, StdioMcpServer) else server.type
220
+
221
+
222
+ class Resources(StrictDocument):
223
+ memory: Annotated[str, Field(pattern=r"^\d+[kmgKMG]?$")] | None = None
224
+ cpus: Annotated[str, Field(pattern=r"^\d+(\.\d+)?$")] | None = None
225
+
226
+
227
+ def is_within(path: str, root: str) -> bool:
228
+ """True when `path` equals `root` or is below it (both already normalized)."""
229
+ return path == root or path.startswith("/" if root == "/" else f"{root}/")