@mseep/open-computer-use 1.0.0

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 (769) hide show
  1. package/.coderabbit.yaml +25 -0
  2. package/.dockerignore +95 -0
  3. package/.env.example +137 -0
  4. package/.githooks/pre-commit +68 -0
  5. package/.github/CODEOWNERS +125 -0
  6. package/.github/ISSUE_TEMPLATE/adr-proposal.md +41 -0
  7. package/.github/ISSUE_TEMPLATE/bug-report.md +49 -0
  8. package/.github/ISSUE_TEMPLATE/component-proposal.md +38 -0
  9. package/.github/ISSUE_TEMPLATE/config.yml +15 -0
  10. package/.github/ISSUE_TEMPLATE/dependency-proposal.md +59 -0
  11. package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  12. package/.github/ISSUE_TEMPLATE/nfr-proposal.md +44 -0
  13. package/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  14. package/.github/codeql/codeql-config.yml +11 -0
  15. package/.github/codeql/extensions/security-models/python-sanitizers.model.yml +17 -0
  16. package/.github/codeql/extensions/security-models/qlpack.yml +7 -0
  17. package/.github/dependabot.yml +23 -0
  18. package/.github/security-exceptions.yml +23 -0
  19. package/.github/workflows/build.yml +420 -0
  20. package/.github/workflows/codeql.yml +33 -0
  21. package/.github/workflows/contracts-lint.yml +90 -0
  22. package/.github/workflows/docs-lint.yml +151 -0
  23. package/.github/workflows/helm.yml +131 -0
  24. package/.github/workflows/identity-lint.yml +30 -0
  25. package/.github/workflows/release-chart.yml +177 -0
  26. package/.github/workflows/release.yml +95 -0
  27. package/.github/workflows/security.yml +332 -0
  28. package/.github/workflows/stale.yml +31 -0
  29. package/.github/workflows/supply-chain.yml +242 -0
  30. package/.gitleaks.toml +53 -0
  31. package/.markdownlint.yaml +51 -0
  32. package/.semgrepignore +85 -0
  33. package/.vale/styles/Architecture/ap13-data-class-substrate.yml +12 -0
  34. package/.vale/styles/Architecture/banned-phrases.yml +23 -0
  35. package/.vale/styles/Architecture/banned-vocab.yml +23 -0
  36. package/.vale/styles/Architecture/marketing-tone.yml +19 -0
  37. package/.vale.ini +18 -0
  38. package/CHANGELOG.md +411 -0
  39. package/CLAUDE.md +218 -0
  40. package/CONTRIBUTING.md +82 -0
  41. package/Dockerfile +676 -0
  42. package/LICENSE +98 -0
  43. package/LICENSE-APACHE +202 -0
  44. package/LICENSE-MIT +21 -0
  45. package/NOTICE +36 -0
  46. package/README.md +516 -0
  47. package/SECURITY.md +45 -0
  48. package/THIRD-PARTY-LICENSES.md +14 -0
  49. package/apt-packages.txt +108 -0
  50. package/computer-use-server/.dockerignore +13 -0
  51. package/computer-use-server/Dockerfile +44 -0
  52. package/computer-use-server/README.md +84 -0
  53. package/computer-use-server/app.py +1544 -0
  54. package/computer-use-server/bin/list-subagent-models +449 -0
  55. package/computer-use-server/cli-defaults/README.md +31 -0
  56. package/computer-use-server/cli-defaults/codex.json +7 -0
  57. package/computer-use-server/cli-defaults/opencode.json +18 -0
  58. package/computer-use-server/cli_adapters/__init__.py +46 -0
  59. package/computer-use-server/cli_adapters/claude.py +163 -0
  60. package/computer-use-server/cli_adapters/codex.py +163 -0
  61. package/computer-use-server/cli_adapters/opencode.py +169 -0
  62. package/computer-use-server/cli_adapters/result.py +34 -0
  63. package/computer-use-server/cli_runtime.py +316 -0
  64. package/computer-use-server/context_vars.py +24 -0
  65. package/computer-use-server/docker_manager.py +1100 -0
  66. package/computer-use-server/docs_html.py +12 -0
  67. package/computer-use-server/mcp_resources.py +170 -0
  68. package/computer-use-server/mcp_tools.py +1430 -0
  69. package/computer-use-server/requirements.txt +17 -0
  70. package/computer-use-server/security.py +50 -0
  71. package/computer-use-server/skill_manager.py +664 -0
  72. package/computer-use-server/static/browser-viewer.js +445 -0
  73. package/computer-use-server/static/chart.umd.js +14 -0
  74. package/computer-use-server/static/docs.html +203 -0
  75. package/computer-use-server/static/github-dark.min.css +10 -0
  76. package/computer-use-server/static/github.min.css +10 -0
  77. package/computer-use-server/static/highlight.min.js +1213 -0
  78. package/computer-use-server/static/highlightjs-line-numbers.min.js +1 -0
  79. package/computer-use-server/static/icons.js +74 -0
  80. package/computer-use-server/static/jszip.min.js +13 -0
  81. package/computer-use-server/static/katex/auto-render.min.js +1 -0
  82. package/computer-use-server/static/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  83. package/computer-use-server/static/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  84. package/computer-use-server/static/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  85. package/computer-use-server/static/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  86. package/computer-use-server/static/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  87. package/computer-use-server/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  88. package/computer-use-server/static/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  89. package/computer-use-server/static/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  90. package/computer-use-server/static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  91. package/computer-use-server/static/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  92. package/computer-use-server/static/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  93. package/computer-use-server/static/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  94. package/computer-use-server/static/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  95. package/computer-use-server/static/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  96. package/computer-use-server/static/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  97. package/computer-use-server/static/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  98. package/computer-use-server/static/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  99. package/computer-use-server/static/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  100. package/computer-use-server/static/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  101. package/computer-use-server/static/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  102. package/computer-use-server/static/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  103. package/computer-use-server/static/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  104. package/computer-use-server/static/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  105. package/computer-use-server/static/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  106. package/computer-use-server/static/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  107. package/computer-use-server/static/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  108. package/computer-use-server/static/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  109. package/computer-use-server/static/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  110. package/computer-use-server/static/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  111. package/computer-use-server/static/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  112. package/computer-use-server/static/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  113. package/computer-use-server/static/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  114. package/computer-use-server/static/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  115. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  116. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  117. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  118. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  119. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  120. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  121. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  122. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  123. package/computer-use-server/static/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  124. package/computer-use-server/static/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  125. package/computer-use-server/static/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  126. package/computer-use-server/static/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  127. package/computer-use-server/static/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  128. package/computer-use-server/static/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  129. package/computer-use-server/static/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  130. package/computer-use-server/static/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  131. package/computer-use-server/static/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  132. package/computer-use-server/static/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  133. package/computer-use-server/static/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  134. package/computer-use-server/static/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  135. package/computer-use-server/static/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  136. package/computer-use-server/static/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  137. package/computer-use-server/static/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  138. package/computer-use-server/static/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  139. package/computer-use-server/static/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  140. package/computer-use-server/static/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  141. package/computer-use-server/static/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  142. package/computer-use-server/static/katex/katex.min.css +1 -0
  143. package/computer-use-server/static/katex/katex.min.js +1 -0
  144. package/computer-use-server/static/locale.js +242 -0
  145. package/computer-use-server/static/mammoth.browser.min.js +21 -0
  146. package/computer-use-server/static/marked.min.js +6 -0
  147. package/computer-use-server/static/mermaid.min.js +2811 -0
  148. package/computer-use-server/static/pdf.min.js +22 -0
  149. package/computer-use-server/static/pdf.worker.min.js +22 -0
  150. package/computer-use-server/static/pptxviewjs.min.js +1 -0
  151. package/computer-use-server/static/preact-htm.min.js +1 -0
  152. package/computer-use-server/static/preview.css +1030 -0
  153. package/computer-use-server/static/preview.js +1522 -0
  154. package/computer-use-server/static/xlsx.full.min.js +22 -0
  155. package/computer-use-server/static/xterm-addon-fit.min.js +2 -0
  156. package/computer-use-server/static/xterm-addon-web-links.min.js +2 -0
  157. package/computer-use-server/static/xterm.css +218 -0
  158. package/computer-use-server/static/xterm.min.js +2 -0
  159. package/computer-use-server/system_prompt.py +761 -0
  160. package/computer-use-server/uploads.py +82 -0
  161. package/contracts/README.md +53 -0
  162. package/contracts/audit/audit-fanin.asyncapi.yaml +407 -0
  163. package/contracts/exec/exec-channel.schema.json +240 -0
  164. package/contracts/mcp/2025-06-18/ocu-constraints.schema.json +178 -0
  165. package/contracts/storage/file-artifact-api.schema.json +390 -0
  166. package/contracts/storage/file-ops.schema.json +217 -0
  167. package/contracts/storage/mount-config.schema.json +197 -0
  168. package/cron/Dockerfile +15 -0
  169. package/cron/cleanup-quick.sh +21 -0
  170. package/cron/cleanup.sh +127 -0
  171. package/data/outputs/.gitkeep +0 -0
  172. package/data/uploads/.gitkeep +0 -0
  173. package/docker-compose.test.yml +54 -0
  174. package/docker-compose.webui.yml +77 -0
  175. package/docker-compose.yml +96 -0
  176. package/docs/CLOUD.md +29 -0
  177. package/docs/COMPARISON.md +128 -0
  178. package/docs/DOCKER.md +469 -0
  179. package/docs/DYNAMIC-SKILLS.md +77 -0
  180. package/docs/FEATURES.md +100 -0
  181. package/docs/INSTALL.md +111 -0
  182. package/docs/KNOWN-BUGS.md +86 -0
  183. package/docs/MCP.md +320 -0
  184. package/docs/SCREENSHOTS.md +39 -0
  185. package/docs/SKILLS-USER-GUIDE.md +86 -0
  186. package/docs/SKILLS.md +483 -0
  187. package/docs/TERMINAL-TAB.md +56 -0
  188. package/docs/architecture/02-trust-boundaries.md +224 -0
  189. package/docs/architecture/03-c4-context.md +61 -0
  190. package/docs/architecture/04-bounded-contexts.md +119 -0
  191. package/docs/architecture/05-c4-container.md +88 -0
  192. package/docs/architecture/06-threat-model.md +172 -0
  193. package/docs/architecture/08-contracts.md +105 -0
  194. package/docs/architecture/MANIFESTO.md +38 -0
  195. package/docs/architecture/PROCESS.md +64 -0
  196. package/docs/architecture/README.md +37 -0
  197. package/docs/architecture/adr/0000-template.md +65 -0
  198. package/docs/architecture/adr/0001-layer-0-gate-legacy-exclusion.md +75 -0
  199. package/docs/architecture/adr/0002-session-view-descriptor.md +57 -0
  200. package/docs/architecture/adr/0003-sandbox-runtime-tier-ladder.md +63 -0
  201. package/docs/architecture/adr/0004-operator-authentication-substrate.md +63 -0
  202. package/docs/architecture/adr/0005-egress-credential-delivery-envoy-sds.md +62 -0
  203. package/docs/architecture/adr/0006-egress-forward-proxy-substrate.md +65 -0
  204. package/docs/architecture/adr/0007-egress-auth-mechanism.md +72 -0
  205. package/docs/architecture/adr/0008-session-egress-attribution.md +59 -0
  206. package/docs/architecture/adr/0009-audit-pipeline-pluggable-by-contract.md +76 -0
  207. package/docs/architecture/adr/0010-storage-backend-pluggable-adapter.md +60 -0
  208. package/docs/architecture/adr/0011-storage-egress-lane.md +67 -0
  209. package/docs/architecture/adr/0012-implementation-language.md +67 -0
  210. package/docs/architecture/adr/0020-sandbox-image-provisioning.md +82 -0
  211. package/docs/architecture/adr/README.md +53 -0
  212. package/docs/architecture/compliance/.gitkeep +0 -0
  213. package/docs/architecture/components/00-overview.md +42 -0
  214. package/docs/architecture/components/0000-template.md +50 -0
  215. package/docs/architecture/components/01-mcp-gateway.md +80 -0
  216. package/docs/architecture/components/02-control-operator-api.md +80 -0
  217. package/docs/architecture/components/04-storage-broker.md +104 -0
  218. package/docs/architecture/components/05-session-sandbox.md +93 -0
  219. package/docs/architecture/components/06-egress-trust-edge.md +95 -0
  220. package/docs/architecture/components/07-audit-pipeline.md +110 -0
  221. package/docs/architecture/diagrams/.gitkeep +0 -0
  222. package/docs/architecture/diagrams/02-trust-boundaries.mmd +111 -0
  223. package/docs/architecture/diagrams/06-threat-model.mmd +41 -0
  224. package/docs/architecture/diagrams/08-contracts.mmd +47 -0
  225. package/docs/architecture/diagrams/c4-container.mmd +59 -0
  226. package/docs/architecture/diagrams/c4-context.mmd +46 -0
  227. package/docs/architecture/glossary.md +172 -0
  228. package/docs/architecture/manifesto/.gitkeep +0 -0
  229. package/docs/architecture/manifesto/01-audience-and-buyer.md +57 -0
  230. package/docs/architecture/manifesto/02-nfrs.md +325 -0
  231. package/docs/architecture/manifesto/03-non-negotiables.md +35 -0
  232. package/docs/architecture/manifesto/04-non-goals.md +23 -0
  233. package/docs/architecture/manifesto/05-licensing-posture.md +61 -0
  234. package/docs/architecture/manifesto/06-starter-mode-policy.md +49 -0
  235. package/docs/architecture/manifesto/07-governance.md +60 -0
  236. package/docs/architecture/primitives-backlog.md +51 -0
  237. package/docs/architecture.svg +117 -0
  238. package/docs/claude-code-gateway.md +173 -0
  239. package/docs/cli-config-templates.md +240 -0
  240. package/docs/data-flow.svg +72 -0
  241. package/docs/demo-landing-page.gif +0 -0
  242. package/docs/demo-qwen-trending.gif +0 -0
  243. package/docs/dynamic-skills.svg +77 -0
  244. package/docs/file-flow.svg +126 -0
  245. package/docs/future-architecture/README.md +152 -0
  246. package/docs/future-architecture/adr/0001-control-plane-language-go.md +80 -0
  247. package/docs/future-architecture/adr/0002-guest-agent-language-go.md +84 -0
  248. package/docs/future-architecture/adr/0003-docker-poc-first-then-k8s.md +37 -0
  249. package/docs/future-architecture/adr/0004-pluggable-runtime-via-runtimeclass.md +34 -0
  250. package/docs/future-architecture/adr/0005-mcp-as-control-plane-gateway.md +34 -0
  251. package/docs/future-architecture/adr/0006-no-agpl-no-bsl-dependencies.md +41 -0
  252. package/docs/future-architecture/adr/0007-superseded-by-future-architecture.md +37 -0
  253. package/docs/future-architecture/adr/0008-internal-grpc-external-rest-mcp.md +106 -0
  254. package/docs/future-architecture/adr/0009-external-protocol-dialects.md +94 -0
  255. package/docs/future-architecture/adr/0010-lambda-as-inspiration-not-runtime.md +86 -0
  256. package/docs/future-architecture/adr/0011-kata-as-first-class-dind-runtime.md +84 -0
  257. package/docs/future-architecture/antipatterns.md +552 -0
  258. package/docs/future-architecture/architecture/01-layers.md +109 -0
  259. package/docs/future-architecture/architecture/02-layer4-control-plane.md +122 -0
  260. package/docs/future-architecture/architecture/03-layer3-providers.md +174 -0
  261. package/docs/future-architecture/architecture/04-layer2-runtimes.md +114 -0
  262. package/docs/future-architecture/architecture/04b-credential-broker.md +153 -0
  263. package/docs/future-architecture/architecture/05-layer1-guest-agent.md +138 -0
  264. package/docs/future-architecture/architecture/06-storage.md +134 -0
  265. package/docs/future-architecture/architecture/07-security.md +194 -0
  266. package/docs/future-architecture/architecture/08-networking.md +149 -0
  267. package/docs/future-architecture/architecture/09-templates.md +122 -0
  268. package/docs/future-architecture/architecture/10-observability.md +121 -0
  269. package/docs/future-architecture/design-notes.md +72 -0
  270. package/docs/future-architecture/gaps.md +281 -0
  271. package/docs/future-architecture/phase-template.md +123 -0
  272. package/docs/future-architecture/references.md +225 -0
  273. package/docs/future-architecture/research/01-kata-containers.md +100 -0
  274. package/docs/future-architecture/research/02-e2b-infra.md +133 -0
  275. package/docs/future-architecture/research/03-coder.md +115 -0
  276. package/docs/future-architecture/research/04-cloud-hypervisor.md +99 -0
  277. package/docs/future-architecture/research/05-firecracker.md +114 -0
  278. package/docs/future-architecture/research/06-agent-sandbox.md +142 -0
  279. package/docs/future-architecture/research/07-chromedp.md +78 -0
  280. package/docs/future-architecture/research/08-microsandbox.md +78 -0
  281. package/docs/future-architecture/research/09-agentbox.md +135 -0
  282. package/docs/future-architecture/research/10-sysbox.md +100 -0
  283. package/docs/future-architecture/research/11-firecracker-containerd.md +93 -0
  284. package/docs/future-architecture/research/12-docker-socket-proxy.md +59 -0
  285. package/docs/future-architecture/research/14-e2b-desktop-and-surf.md +107 -0
  286. package/docs/future-architecture/research/18-open-webui-terminals-observed.md +135 -0
  287. package/docs/future-architecture/research/bank-buyer.md +96 -0
  288. package/docs/future-architecture/research/enthusiast-audience.md +106 -0
  289. package/docs/future-architecture/research/proof-uipath-anthropic-2026-05.md +76 -0
  290. package/docs/future-architecture/research/widemoat-thesis-advisor.md +124 -0
  291. package/docs/future-architecture/roadmap.md +438 -0
  292. package/docs/kata-runtime.md +267 -0
  293. package/docs/kubernetes.md +86 -0
  294. package/docs/logo.png +0 -0
  295. package/docs/multi-cli.md +161 -0
  296. package/docs/openwebui-filter.md +134 -0
  297. package/docs/roadmap/implementation-roadmap.md +104 -0
  298. package/docs/sandbox-contents.svg +229 -0
  299. package/docs/screenshots/01-create-document.png +0 -0
  300. package/docs/screenshots/02-file-preview.png +0 -0
  301. package/docs/screenshots/03-browser-viewer.png +0 -0
  302. package/docs/screenshots/04-sub-agent-terminal.png +0 -0
  303. package/docs/screenshots/05-chat-overview.png +0 -0
  304. package/docs/screenshots/06-sub-agent-dashboard.png +0 -0
  305. package/docs/screenshots/07-frontend-design-skill.png +0 -0
  306. package/docs/screenshots/08-pptx-skill.png +0 -0
  307. package/docs/screenshots/09-skill-creator.png +0 -0
  308. package/docs/screenshots/10-data-chart.png +0 -0
  309. package/docs/shared-browser.svg +102 -0
  310. package/docs/system-prompt.md +113 -0
  311. package/docs/terminal-flow.svg +69 -0
  312. package/examples/helm/README.md +20 -0
  313. package/examples/helm/standalone/values.yaml +49 -0
  314. package/examples/helm/with-open-webui/README.md +99 -0
  315. package/examples/helm/with-open-webui/values-computer-use.yaml +32 -0
  316. package/examples/helm/with-open-webui/values-open-webui.yaml +67 -0
  317. package/fonts/NotoEmoji-Regular.ttf +0 -0
  318. package/helm/computer-use-server/.helmignore +17 -0
  319. package/helm/computer-use-server/Chart.yaml +32 -0
  320. package/helm/computer-use-server/README.md +211 -0
  321. package/helm/computer-use-server/templates/NOTES.txt +66 -0
  322. package/helm/computer-use-server/templates/_helpers.tpl +115 -0
  323. package/helm/computer-use-server/templates/configmap-dind-init.yaml +82 -0
  324. package/helm/computer-use-server/templates/configmap.yaml +18 -0
  325. package/helm/computer-use-server/templates/deployment.yaml +248 -0
  326. package/helm/computer-use-server/templates/ingress.yaml +38 -0
  327. package/helm/computer-use-server/templates/networkpolicy.yaml +50 -0
  328. package/helm/computer-use-server/templates/pdb.yaml +16 -0
  329. package/helm/computer-use-server/templates/pvc-data.yaml +20 -0
  330. package/helm/computer-use-server/templates/pvc-skills-cache.yaml +20 -0
  331. package/helm/computer-use-server/templates/pvc-user-data.yaml +20 -0
  332. package/helm/computer-use-server/templates/pvc-var-lib-docker.yaml +27 -0
  333. package/helm/computer-use-server/templates/secret.yaml +23 -0
  334. package/helm/computer-use-server/templates/service.yaml +22 -0
  335. package/helm/computer-use-server/templates/serviceaccount.yaml +15 -0
  336. package/helm/computer-use-server/templates/tests/test-health.yaml +23 -0
  337. package/helm/computer-use-server/values.schema.json +183 -0
  338. package/helm/computer-use-server/values.yaml +297 -0
  339. package/lychee.toml +36 -0
  340. package/openwebui/Dockerfile +52 -0
  341. package/openwebui/README.md +38 -0
  342. package/openwebui/functions/README.md +48 -0
  343. package/openwebui/functions/computer_link_filter.py +487 -0
  344. package/openwebui/init.sh +305 -0
  345. package/openwebui/patches/README.md +44 -0
  346. package/openwebui/patches/fix_artifacts_auto_show.py +441 -0
  347. package/openwebui/patches/fix_attached_files_position.py +87 -0
  348. package/openwebui/patches/fix_large_tool_args.py +156 -0
  349. package/openwebui/patches/fix_large_tool_results.py +289 -0
  350. package/openwebui/patches/fix_preview_url_detection.py +230 -0
  351. package/openwebui/patches/fix_skip_embedding_chat_files.py +229 -0
  352. package/openwebui/patches/fix_skip_rag_files_native_fc.py +100 -0
  353. package/openwebui/patches/fix_tool_loop_errors.py +510 -0
  354. package/package.json +39 -0
  355. package/requirements.txt +112 -0
  356. package/scripts/check-config.sh +141 -0
  357. package/scripts/docs-lint/ai-slop-detector.sh +202 -0
  358. package/scripts/docs-lint/architecture-tree-whitelist.sh +131 -0
  359. package/scripts/docs-lint/ascii-diagram-detector.sh +58 -0
  360. package/scripts/docs-lint/front-matter-validator.sh +97 -0
  361. package/scripts/docs-lint/gitignored-ref-detector.sh +122 -0
  362. package/scripts/docs-lint/identity-email-detector.sh +48 -0
  363. package/scripts/docs-lint/test-linters.sh +354 -0
  364. package/scripts/docs-lint/wc-budget.sh +61 -0
  365. package/scripts/githooks/pre-push +75 -0
  366. package/server.json +13 -0
  367. package/settings-wrapper/Dockerfile +9 -0
  368. package/settings-wrapper/README.md +119 -0
  369. package/settings-wrapper/app.py +113 -0
  370. package/settings-wrapper/requirements.txt +2 -0
  371. package/settings-wrapper/skills.json +25 -0
  372. package/skills/README.md +46 -0
  373. package/skills/examples/algorithmic-art/SKILL.md +405 -0
  374. package/skills/examples/algorithmic-art/templates/generator_template.js +223 -0
  375. package/skills/examples/algorithmic-art/templates/viewer.html +601 -0
  376. package/skills/examples/artifacts-builder/SKILL.md +74 -0
  377. package/skills/examples/artifacts-builder/scripts/bundle-artifact.sh +54 -0
  378. package/skills/examples/artifacts-builder/scripts/init-artifact.sh +322 -0
  379. package/skills/examples/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  380. package/skills/examples/canvas-design/LICENSE.txt +202 -0
  381. package/skills/examples/canvas-design/SKILL.md +130 -0
  382. package/skills/examples/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  383. package/skills/examples/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  384. package/skills/examples/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  385. package/skills/examples/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  386. package/skills/examples/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  387. package/skills/examples/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  388. package/skills/examples/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  389. package/skills/examples/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  390. package/skills/examples/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  391. package/skills/examples/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  392. package/skills/examples/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  393. package/skills/examples/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  394. package/skills/examples/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  395. package/skills/examples/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  396. package/skills/examples/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  397. package/skills/examples/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  398. package/skills/examples/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  399. package/skills/examples/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  400. package/skills/examples/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  401. package/skills/examples/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  402. package/skills/examples/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  403. package/skills/examples/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  404. package/skills/examples/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  405. package/skills/examples/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  406. package/skills/examples/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  407. package/skills/examples/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  408. package/skills/examples/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  409. package/skills/examples/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  410. package/skills/examples/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  411. package/skills/examples/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  412. package/skills/examples/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  413. package/skills/examples/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  414. package/skills/examples/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  415. package/skills/examples/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  416. package/skills/examples/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  417. package/skills/examples/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  418. package/skills/examples/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  419. package/skills/examples/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  420. package/skills/examples/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  421. package/skills/examples/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  422. package/skills/examples/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  423. package/skills/examples/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  424. package/skills/examples/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  425. package/skills/examples/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  426. package/skills/examples/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  427. package/skills/examples/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  428. package/skills/examples/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  429. package/skills/examples/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  430. package/skills/examples/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  431. package/skills/examples/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  432. package/skills/examples/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  433. package/skills/examples/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  434. package/skills/examples/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  435. package/skills/examples/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  436. package/skills/examples/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  437. package/skills/examples/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  438. package/skills/examples/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  439. package/skills/examples/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  440. package/skills/examples/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  441. package/skills/examples/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  442. package/skills/examples/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  443. package/skills/examples/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  444. package/skills/examples/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  445. package/skills/examples/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  446. package/skills/examples/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  447. package/skills/examples/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  448. package/skills/examples/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  449. package/skills/examples/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  450. package/skills/examples/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  451. package/skills/examples/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  452. package/skills/examples/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  453. package/skills/examples/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  454. package/skills/examples/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  455. package/skills/examples/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  456. package/skills/examples/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  457. package/skills/examples/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  458. package/skills/examples/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  459. package/skills/examples/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  460. package/skills/examples/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  461. package/skills/examples/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  462. package/skills/examples/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  463. package/skills/examples/copy-editing/SKILL.md +447 -0
  464. package/skills/examples/copy-editing/evals/evals.json +89 -0
  465. package/skills/examples/copy-editing/references/plain-english-alternatives.md +394 -0
  466. package/skills/examples/internal-comms/LICENSE.txt +202 -0
  467. package/skills/examples/internal-comms/SKILL.md +32 -0
  468. package/skills/examples/internal-comms/examples/3p-updates.md +47 -0
  469. package/skills/examples/internal-comms/examples/company-newsletter.md +65 -0
  470. package/skills/examples/internal-comms/examples/faq-answers.md +30 -0
  471. package/skills/examples/internal-comms/examples/general-comms.md +16 -0
  472. package/skills/examples/mcp-builder/SKILL.md +328 -0
  473. package/skills/examples/mcp-builder/reference/evaluation.md +602 -0
  474. package/skills/examples/mcp-builder/reference/mcp_best_practices.md +915 -0
  475. package/skills/examples/mcp-builder/reference/node_mcp_server.md +916 -0
  476. package/skills/examples/mcp-builder/reference/python_mcp_server.md +752 -0
  477. package/skills/examples/mcp-builder/scripts/connections.py +151 -0
  478. package/skills/examples/mcp-builder/scripts/evaluation.py +373 -0
  479. package/skills/examples/mcp-builder/scripts/example_evaluation.xml +22 -0
  480. package/skills/examples/mcp-builder/scripts/requirements.txt +2 -0
  481. package/skills/examples/product-marketing-context/SKILL.md +241 -0
  482. package/skills/examples/product-marketing-context/evals/evals.json +85 -0
  483. package/skills/examples/single-cell-rna-qc/SKILL.md +175 -0
  484. package/skills/examples/single-cell-rna-qc/references/scverse_qc_guidelines.md +186 -0
  485. package/skills/examples/single-cell-rna-qc/scripts/qc_analysis.py +232 -0
  486. package/skills/examples/single-cell-rna-qc/scripts/qc_core.py +233 -0
  487. package/skills/examples/single-cell-rna-qc/scripts/qc_plotting.py +235 -0
  488. package/skills/examples/skill-creator/SKILL.md +355 -0
  489. package/skills/examples/skill-creator/references/output-patterns.md +82 -0
  490. package/skills/examples/skill-creator/references/workflows.md +28 -0
  491. package/skills/examples/skill-creator/scripts/init_skill.py +303 -0
  492. package/skills/examples/skill-creator/scripts/package_skill.py +110 -0
  493. package/skills/examples/skill-creator/scripts/quick_validate.py +95 -0
  494. package/skills/examples/slack-gif-creator/SKILL.md +254 -0
  495. package/skills/examples/slack-gif-creator/core/easing.py +234 -0
  496. package/skills/examples/slack-gif-creator/core/frame_composer.py +176 -0
  497. package/skills/examples/slack-gif-creator/core/gif_builder.py +269 -0
  498. package/skills/examples/slack-gif-creator/core/validators.py +136 -0
  499. package/skills/examples/slack-gif-creator/requirements.txt +4 -0
  500. package/skills/examples/social-content/SKILL.md +278 -0
  501. package/skills/examples/social-content/evals/evals.json +92 -0
  502. package/skills/examples/social-content/references/platforms.md +170 -0
  503. package/skills/examples/social-content/references/post-templates.md +177 -0
  504. package/skills/examples/social-content/references/reverse-engineering.md +195 -0
  505. package/skills/examples/theme-factory/SKILL.md +59 -0
  506. package/skills/examples/theme-factory/theme-showcase.pdf +0 -0
  507. package/skills/examples/theme-factory/themes/arctic-frost.md +19 -0
  508. package/skills/examples/theme-factory/themes/botanical-garden.md +19 -0
  509. package/skills/examples/theme-factory/themes/desert-rose.md +19 -0
  510. package/skills/examples/theme-factory/themes/forest-canopy.md +19 -0
  511. package/skills/examples/theme-factory/themes/golden-hour.md +19 -0
  512. package/skills/examples/theme-factory/themes/midnight-galaxy.md +19 -0
  513. package/skills/examples/theme-factory/themes/modern-minimalist.md +19 -0
  514. package/skills/examples/theme-factory/themes/ocean-depths.md +19 -0
  515. package/skills/examples/theme-factory/themes/sunset-boulevard.md +19 -0
  516. package/skills/examples/theme-factory/themes/tech-innovation.md +19 -0
  517. package/skills/examples/web-artifacts-builder/LICENSE.txt +202 -0
  518. package/skills/examples/web-artifacts-builder/SKILL.md +74 -0
  519. package/skills/examples/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
  520. package/skills/examples/web-artifacts-builder/scripts/init-artifact.sh +322 -0
  521. package/skills/examples/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  522. package/skills/examples/writing-skills/SKILL.md +655 -0
  523. package/skills/examples/writing-skills/anthropic-best-practices.md +1150 -0
  524. package/skills/examples/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  525. package/skills/examples/writing-skills/graphviz-conventions.dot +172 -0
  526. package/skills/examples/writing-skills/persuasion-principles.md +187 -0
  527. package/skills/examples/writing-skills/render-graphs.js +168 -0
  528. package/skills/examples/writing-skills/testing-skills-with-subagents.md +384 -0
  529. package/skills/public/describe-image/SKILL.md +105 -0
  530. package/skills/public/describe-image/scripts/describe.py +389 -0
  531. package/skills/public/doc-coauthoring/SKILL.md +375 -0
  532. package/skills/public/docx/LICENSE.txt +30 -0
  533. package/skills/public/docx/SKILL.md +199 -0
  534. package/skills/public/docx/docx-js.md +350 -0
  535. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  536. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  537. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  538. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  539. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  540. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  541. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  542. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  543. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  544. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  545. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  546. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  547. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  548. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  549. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  550. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  551. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  552. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  553. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  554. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  555. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  556. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  557. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  558. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  559. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  560. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  561. package/skills/public/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  562. package/skills/public/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  563. package/skills/public/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  564. package/skills/public/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  565. package/skills/public/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  566. package/skills/public/docx/ooxml/schemas/mce/mc.xsd +75 -0
  567. package/skills/public/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  568. package/skills/public/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  569. package/skills/public/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  570. package/skills/public/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  571. package/skills/public/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  572. package/skills/public/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  573. package/skills/public/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  574. package/skills/public/docx/ooxml/scripts/pack.py +159 -0
  575. package/skills/public/docx/ooxml/scripts/unpack.py +29 -0
  576. package/skills/public/docx/ooxml/scripts/validate.py +69 -0
  577. package/skills/public/docx/ooxml/scripts/validation/__init__.py +15 -0
  578. package/skills/public/docx/ooxml/scripts/validation/base.py +951 -0
  579. package/skills/public/docx/ooxml/scripts/validation/docx.py +274 -0
  580. package/skills/public/docx/ooxml/scripts/validation/pptx.py +315 -0
  581. package/skills/public/docx/ooxml/scripts/validation/redlining.py +279 -0
  582. package/skills/public/docx/ooxml.md +632 -0
  583. package/skills/public/docx/scripts/__init__.py +1 -0
  584. package/skills/public/docx/scripts/document.py +1292 -0
  585. package/skills/public/docx/scripts/templates/comments.xml +3 -0
  586. package/skills/public/docx/scripts/templates/commentsExtended.xml +3 -0
  587. package/skills/public/docx/scripts/templates/commentsExtensible.xml +3 -0
  588. package/skills/public/docx/scripts/templates/commentsIds.xml +3 -0
  589. package/skills/public/docx/scripts/templates/people.xml +3 -0
  590. package/skills/public/docx/scripts/utilities.py +374 -0
  591. package/skills/public/file-reading/LICENSE.txt +30 -0
  592. package/skills/public/file-reading/SKILL.md +350 -0
  593. package/skills/public/frontend-design/LICENSE.txt +177 -0
  594. package/skills/public/frontend-design/SKILL.md +42 -0
  595. package/skills/public/gitlab-explorer/SKILL.md +174 -0
  596. package/skills/public/gitlab-explorer/references/git-commands.md +323 -0
  597. package/skills/public/gitlab-explorer/references/glab-commands.md +282 -0
  598. package/skills/public/gitlab-explorer/scripts/check_gitlab_auth.sh +109 -0
  599. package/skills/public/pdf/FORMS.md +205 -0
  600. package/skills/public/pdf/REFERENCE.md +612 -0
  601. package/skills/public/pdf/SKILL.md +364 -0
  602. package/skills/public/pdf/scripts/check_bounding_boxes.py +70 -0
  603. package/skills/public/pdf/scripts/check_bounding_boxes_test.py +226 -0
  604. package/skills/public/pdf/scripts/check_fillable_fields.py +12 -0
  605. package/skills/public/pdf/scripts/convert_pdf_to_images.py +35 -0
  606. package/skills/public/pdf/scripts/create_validation_image.py +41 -0
  607. package/skills/public/pdf/scripts/extract_form_field_info.py +152 -0
  608. package/skills/public/pdf/scripts/fill_fillable_fields.py +114 -0
  609. package/skills/public/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  610. package/skills/public/pdf-reading/LICENSE.txt +30 -0
  611. package/skills/public/pdf-reading/REFERENCE.md +196 -0
  612. package/skills/public/pdf-reading/SKILL.md +305 -0
  613. package/skills/public/playwright-cli/SKILL.md +278 -0
  614. package/skills/public/playwright-cli/references/request-mocking.md +87 -0
  615. package/skills/public/playwright-cli/references/running-code.md +232 -0
  616. package/skills/public/playwright-cli/references/session-management.md +169 -0
  617. package/skills/public/playwright-cli/references/storage-state.md +275 -0
  618. package/skills/public/playwright-cli/references/test-generation.md +88 -0
  619. package/skills/public/playwright-cli/references/tracing.md +139 -0
  620. package/skills/public/playwright-cli/references/video-recording.md +43 -0
  621. package/skills/public/pptx/LICENSE.txt +30 -0
  622. package/skills/public/pptx/SKILL.md +484 -0
  623. package/skills/public/pptx/css.md +335 -0
  624. package/skills/public/pptx/html2pptx.md +893 -0
  625. package/skills/public/pptx/html2pptx.tgz +0 -0
  626. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  627. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  628. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  629. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  630. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  631. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  632. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  633. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  634. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  635. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  636. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  637. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  638. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  639. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  640. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  641. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  642. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  643. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  644. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  645. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  646. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  647. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  648. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  649. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  650. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  651. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  652. package/skills/public/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  653. package/skills/public/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  654. package/skills/public/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  655. package/skills/public/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  656. package/skills/public/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  657. package/skills/public/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  658. package/skills/public/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  659. package/skills/public/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  660. package/skills/public/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  661. package/skills/public/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  662. package/skills/public/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  663. package/skills/public/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  664. package/skills/public/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  665. package/skills/public/pptx/ooxml/scripts/pack.py +159 -0
  666. package/skills/public/pptx/ooxml/scripts/unpack.py +29 -0
  667. package/skills/public/pptx/ooxml/scripts/validate.py +69 -0
  668. package/skills/public/pptx/ooxml/scripts/validation/__init__.py +15 -0
  669. package/skills/public/pptx/ooxml/scripts/validation/base.py +951 -0
  670. package/skills/public/pptx/ooxml/scripts/validation/docx.py +274 -0
  671. package/skills/public/pptx/ooxml/scripts/validation/pptx.py +315 -0
  672. package/skills/public/pptx/ooxml/scripts/validation/redlining.py +279 -0
  673. package/skills/public/pptx/ooxml.md +427 -0
  674. package/skills/public/pptx/scripts/inventory.py +1020 -0
  675. package/skills/public/pptx/scripts/rearrange.py +231 -0
  676. package/skills/public/pptx/scripts/replace.py +385 -0
  677. package/skills/public/pptx/scripts/thumbnail.py +450 -0
  678. package/skills/public/skill-creator/SKILL.md +356 -0
  679. package/skills/public/skill-creator/references/output-patterns.md +82 -0
  680. package/skills/public/skill-creator/references/workflows.md +28 -0
  681. package/skills/public/skill-creator/scripts/init_skill.py +303 -0
  682. package/skills/public/skill-creator/scripts/package_skill.py +110 -0
  683. package/skills/public/skill-creator/scripts/quick_validate.py +95 -0
  684. package/skills/public/sub-agent/SKILL.md +186 -0
  685. package/skills/public/sub-agent/references/security-review.md +153 -0
  686. package/skills/public/sub-agent/references/usage.md +207 -0
  687. package/skills/public/sub-agent/scripts/list_subagent_models.sh +22 -0
  688. package/skills/public/test-driven-development/SKILL.md +371 -0
  689. package/skills/public/test-driven-development/testing-anti-patterns.md +299 -0
  690. package/skills/public/webapp-testing/LICENSE.txt +202 -0
  691. package/skills/public/webapp-testing/SKILL.md +96 -0
  692. package/skills/public/webapp-testing/examples/console_logging.py +35 -0
  693. package/skills/public/webapp-testing/examples/element_discovery.py +40 -0
  694. package/skills/public/webapp-testing/examples/static_html_automation.py +33 -0
  695. package/skills/public/webapp-testing/scripts/with_server.py +106 -0
  696. package/skills/public/xlsx/LICENSE.txt +30 -0
  697. package/skills/public/xlsx/SKILL.md +316 -0
  698. package/skills/public/xlsx/preview_data.py +93 -0
  699. package/skills/public/xlsx/recalc.py +178 -0
  700. package/tests/README.md +42 -0
  701. package/tests/fixtures/cli/claude_v0.9.2.0_argv.json +46 -0
  702. package/tests/fixtures/cli/claude_v0.9.2.0_stdout.json +32 -0
  703. package/tests/fixtures/cli/codex_run.jsonl +4 -0
  704. package/tests/fixtures/cli/opencode_run.jsonl +6 -0
  705. package/tests/integration/README.md +56 -0
  706. package/tests/integration/conftest.py +280 -0
  707. package/tests/integration/pytest.ini +13 -0
  708. package/tests/integration/test_mcp_auth.py +85 -0
  709. package/tests/integration/test_mcp_tools.py +101 -0
  710. package/tests/integration/test_workspace_lifecycle.py +125 -0
  711. package/tests/orchestrator/mock_llm_server.py +343 -0
  712. package/tests/orchestrator/test_cli_adapters.py +566 -0
  713. package/tests/orchestrator/test_cli_adapters_live.py +527 -0
  714. package/tests/orchestrator/test_cli_runtime.py +451 -0
  715. package/tests/orchestrator/test_docker_manager.py +302 -0
  716. package/tests/orchestrator/test_dynamic_instructions.py +69 -0
  717. package/tests/orchestrator/test_mcp_resources.py +140 -0
  718. package/tests/orchestrator/test_mcp_tools.py +224 -0
  719. package/tests/orchestrator/test_passthrough_isolation.py +201 -0
  720. package/tests/orchestrator/test_readme_in_container.py +76 -0
  721. package/tests/orchestrator/test_render_cache.py +84 -0
  722. package/tests/orchestrator/test_runtime_cli_endpoint.py +108 -0
  723. package/tests/orchestrator/test_single_user_mode.py +212 -0
  724. package/tests/orchestrator/test_startup_warnings.py +123 -0
  725. package/tests/orchestrator/test_sub_agent_dispatch.py +327 -0
  726. package/tests/orchestrator/test_subagent_claude_compat.py +367 -0
  727. package/tests/orchestrator/test_system_prompt_endpoint.py +191 -0
  728. package/tests/orchestrator/test_tool_descriptions.py +52 -0
  729. package/tests/orchestrator/test_view_image.py +201 -0
  730. package/tests/patches/conftest.py +30 -0
  731. package/tests/patches/fixtures/__init__.py +10 -0
  732. package/tests/patches/fixtures/middleware_v0.9.1.py +5057 -0
  733. package/tests/patches/fixtures/middleware_v0.9.2.py +5120 -0
  734. package/tests/patches/fixtures/retrieval_v0.9.1.py +2684 -0
  735. package/tests/patches/fixtures/retrieval_v0.9.2.py +2700 -0
  736. package/tests/patches/test_fix_attached_files_position.py +118 -0
  737. package/tests/patches/test_fix_large_tool_args.py +130 -0
  738. package/tests/patches/test_fix_large_tool_results.py +531 -0
  739. package/tests/patches/test_fix_skip_embedding_chat_files.py +160 -0
  740. package/tests/patches/test_fix_skip_rag_files_native_fc.py +120 -0
  741. package/tests/patches/test_fix_tool_loop_errors.py +128 -0
  742. package/tests/security/test_path_traversal_app.py +132 -0
  743. package/tests/security/test_path_traversal_docker.py +36 -0
  744. package/tests/security/test_path_traversal_settings.py +87 -0
  745. package/tests/security/test_safe_path_util.py +166 -0
  746. package/tests/security/test_xss_preview.py +46 -0
  747. package/tests/test-default-model-resolution.py +136 -0
  748. package/tests/test-docker-image.sh +358 -0
  749. package/tests/test-list-subagent-models.sh +421 -0
  750. package/tests/test-mcp-endpoint-live.sh +92 -0
  751. package/tests/test-mcp-native-surface.sh +213 -0
  752. package/tests/test-no-cyrillic.sh +135 -0
  753. package/tests/test-opencode-error-mapping.py +130 -0
  754. package/tests/test-pr88-skills.sh +305 -0
  755. package/tests/test-project-structure.sh +202 -0
  756. package/tests/test-single-user-mode.sh +269 -0
  757. package/tests/test-skill-no-hardcoded-models.sh +65 -0
  758. package/tests/test-subagent-cli-surface.py +137 -0
  759. package/tests/test-subagent-runtime.sh +109 -0
  760. package/tests/test_codex_toml_converter.py +204 -0
  761. package/tests/test_default_resolver_no_legacy_global.py +159 -0
  762. package/tests/test_filter.py +648 -0
  763. package/tests/test_init_sh_unchanged.sh +49 -0
  764. package/tests/test_opencode_alias_map_drop.py +144 -0
  765. package/tests/test_requirements.py +91 -0
  766. package/tests/test_subagent_docstring.py +193 -0
  767. package/tests/test_tools.py +34 -0
  768. package/vendor/extract-text/README.md +46 -0
  769. package/vendor/extract-text/extract-text +0 -0
@@ -0,0 +1,1100 @@
1
+ # SPDX-License-Identifier: FSL-1.1-Apache-2.0
2
+ # Copyright (c) 2025 Open Computer Use Contributors
3
+ """
4
+ Docker container management for Computer Use.
5
+
6
+ Handles:
7
+ - Docker client initialization (local socket)
8
+ - Container lifecycle (get/create/start)
9
+ - Network management (compose network, CDP proxy)
10
+ - Command execution (bash, python with stdin)
11
+ - Shutdown timer (idle timeout)
12
+
13
+ Extracted from mcp_tools.py to reduce file size and separate concerns.
14
+ """
15
+
16
+ import os
17
+ import sys
18
+ import re
19
+ import json
20
+ import shlex
21
+ import time
22
+ import datetime
23
+ from pathlib import Path
24
+ from types import SimpleNamespace
25
+ from typing import Optional
26
+
27
+ import aiohttp
28
+ import docker
29
+ from docker.utils.socket import frames_iter, demux_adaptor, consume_socket_output
30
+
31
+ import skill_manager
32
+ from context_vars import (
33
+ current_chat_id, current_user_email, current_user_name,
34
+ current_gitlab_token, current_gitlab_host,
35
+ current_anthropic_auth_token, current_anthropic_base_url,
36
+ current_mcp_tokens_url, current_mcp_tokens_api_key, current_mcp_servers,
37
+ )
38
+ from system_prompt import render_system_prompt_sync
39
+
40
+ DOCKER_SOCKET = os.getenv("DOCKER_SOCKET", "unix:///var/run/docker.sock")
41
+ DOCKER_IMAGE = os.getenv("DOCKER_IMAGE", "open-computer-use:latest")
42
+ CONTAINER_MEM_LIMIT = os.getenv("CONTAINER_MEM_LIMIT", "2g")
43
+ CONTAINER_CPU_LIMIT = float(os.getenv("CONTAINER_CPU_LIMIT", "1.0"))
44
+ COMMAND_TIMEOUT = int(os.getenv("COMMAND_TIMEOUT", "120"))
45
+ ENABLE_NETWORK = os.getenv("ENABLE_NETWORK", "true").lower() == "true"
46
+ USER_DATA_BASE_PATH = os.getenv("USER_DATA_BASE_PATH", "/tmp/computer-use-data")
47
+ # Public URL of the orchestrator — the single source of truth for browser-facing
48
+ # preview/archive links. Baked into /system-prompt so the model writes correct
49
+ # clickable URLs, and returned to the Open WebUI filter via the X-Public-Base-URL
50
+ # response header so outlet() decorations also use it.
51
+ #
52
+ # Internal-DNS default is only reachable from inside the compose network. Users
53
+ # must override with a browser-reachable URL (http://localhost:8081 for local
54
+ # dev, https://cu.example.com for prod) for the preview panel to work.
55
+ # See docs/openwebui-filter.md.
56
+ PUBLIC_BASE_URL_DEFAULT = "http://computer-use-server:8081"
57
+ # Normalize: treat empty string as unset (docker-compose's `${VAR:-}` always sets
58
+ # the env var, so os.getenv's default only fires when VAR is truly absent —
59
+ # empty string would otherwise bypass the startup warning). Also strip any
60
+ # trailing slash so downstream concatenations never produce `//files/...`.
61
+ PUBLIC_BASE_URL = (os.getenv("PUBLIC_BASE_URL") or PUBLIC_BASE_URL_DEFAULT).rstrip("/")
62
+ CONTAINER_IDLE_TIMEOUT = int(os.getenv("CONTAINER_IDLE_TIMEOUT", "600"))
63
+ DEBUG_LOGGING = os.getenv("DEBUG_LOGGING", "false").lower() == "true"
64
+ ORCHESTRATOR_CONTAINER_NAME = os.getenv("ORCHESTRATOR_CONTAINER_NAME", "computer-use-server")
65
+ BASE_DATA_DIR = Path(os.getenv("BASE_DATA_DIR", "/data"))
66
+
67
+ # MCP Tokens Wrapper for GitLab token fetching
68
+ MCP_TOKENS_URL = os.getenv("MCP_TOKENS_URL", "")
69
+ MCP_TOKENS_API_KEY = os.getenv("MCP_TOKENS_API_KEY", "")
70
+
71
+ # Sub-agent configuration — per-CLI default models (D-03/D-04).
72
+ # The legacy single SUB_AGENT_DEFAULT_MODEL global was removed in Phase 2;
73
+ # the deprecation grace window from Phase 1 D-10 is over. The per-CLI env
74
+ # vars (CLAUDE_/CODEX_/OPENCODE_SUB_AGENT_DEFAULT_MODEL) are read directly
75
+ # by the resolver in cli_runtime.py — no module-level constants needed
76
+ # here. The resolver raises a clear ValueError when caller passes no model
77
+ # AND the per-CLI env is unset (opencode/codex only; claude falls back to
78
+ # the canonical 'sonnet' alias).
79
+ SUB_AGENT_MAX_TURNS = int(os.getenv("SUB_AGENT_MAX_TURNS", "25"))
80
+ SUB_AGENT_TIMEOUT = int(os.getenv("SUB_AGENT_TIMEOUT", "3600"))
81
+
82
+ # Anthropic API (shared LiteLLM proxy key — fallback when no header provided)
83
+ # NB: os.getenv falls back to the default only when the var is UNSET. In docker
84
+ # compose with `${VAR:-}` the var is always set to "", so treat empty == unset.
85
+ ANTHROPIC_AUTH_TOKEN = os.getenv("ANTHROPIC_AUTH_TOKEN", "")
86
+ ANTHROPIC_BASE_URL = os.getenv("ANTHROPIC_BASE_URL") or "https://api.anthropic.com"
87
+
88
+ # Claude Code model ID overrides (pass through only when set on host — GATEWAY-02)
89
+ ANTHROPIC_MODEL = os.getenv("ANTHROPIC_MODEL", "")
90
+ ANTHROPIC_DEFAULT_SONNET_MODEL = os.getenv("ANTHROPIC_DEFAULT_SONNET_MODEL", "")
91
+ ANTHROPIC_DEFAULT_OPUS_MODEL = os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL", "")
92
+ ANTHROPIC_DEFAULT_HAIKU_MODEL = os.getenv("ANTHROPIC_DEFAULT_HAIKU_MODEL", "")
93
+ CLAUDE_CODE_SUBAGENT_MODEL = os.getenv("CLAUDE_CODE_SUBAGENT_MODEL", "")
94
+ # Claude Code gateway compatibility flags (set to "1" to disable — GATEWAY-02)
95
+ CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = os.getenv("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS", "")
96
+ DISABLE_PROMPT_CACHING = os.getenv("DISABLE_PROMPT_CACHING", "")
97
+ DISABLE_PROMPT_CACHING_SONNET = os.getenv("DISABLE_PROMPT_CACHING_SONNET", "")
98
+ DISABLE_PROMPT_CACHING_OPUS = os.getenv("DISABLE_PROMPT_CACHING_OPUS", "")
99
+ DISABLE_PROMPT_CACHING_HAIKU = os.getenv("DISABLE_PROMPT_CACHING_HAIKU", "")
100
+
101
+ # Tuple (not dict) for deterministic iteration order in tests — GATEWAY-03.
102
+ CLAUDE_CODE_PASSTHROUGH_ENVS = (
103
+ ("ANTHROPIC_MODEL", ANTHROPIC_MODEL),
104
+ ("ANTHROPIC_DEFAULT_SONNET_MODEL", ANTHROPIC_DEFAULT_SONNET_MODEL),
105
+ ("ANTHROPIC_DEFAULT_OPUS_MODEL", ANTHROPIC_DEFAULT_OPUS_MODEL),
106
+ ("ANTHROPIC_DEFAULT_HAIKU_MODEL", ANTHROPIC_DEFAULT_HAIKU_MODEL),
107
+ ("CLAUDE_CODE_SUBAGENT_MODEL", CLAUDE_CODE_SUBAGENT_MODEL),
108
+ ("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS", CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS),
109
+ ("DISABLE_PROMPT_CACHING", DISABLE_PROMPT_CACHING),
110
+ ("DISABLE_PROMPT_CACHING_SONNET", DISABLE_PROMPT_CACHING_SONNET),
111
+ ("DISABLE_PROMPT_CACHING_OPUS", DISABLE_PROMPT_CACHING_OPUS),
112
+ ("DISABLE_PROMPT_CACHING_HAIKU", DISABLE_PROMPT_CACHING_HAIKU),
113
+ )
114
+
115
+ # Codex passthrough envs (Phase 6 — only injected when SUBAGENT_CLI=codex).
116
+ # Per AUTH-01 — closes Pitfall 1 (auth bleed across CLIs).
117
+ CODEX_PASSTHROUGH_ENVS = (
118
+ ("OPENAI_API_KEY", os.getenv("OPENAI_API_KEY", "")),
119
+ ("OPENAI_BASE_URL", os.getenv("OPENAI_BASE_URL", "")),
120
+ ("CODEX_MODEL", os.getenv("CODEX_MODEL", "")),
121
+ ("AZURE_OPENAI_API_KEY", os.getenv("AZURE_OPENAI_API_KEY", "")),
122
+ ("AZURE_OPENAI_ENDPOINT", os.getenv("AZURE_OPENAI_ENDPOINT", "")),
123
+ ("AZURE_OPENAI_API_VERSION", os.getenv("AZURE_OPENAI_API_VERSION", "")),
124
+ # Operator-supplied codex config override (see docs/cli-config-templates.md
125
+ # "Codex — custom OpenAI-compat gateway" recipe). Appended to the canonical
126
+ # ~/.codex/config.toml block by the Dockerfile entrypoint when set, so
127
+ # operators can route codex through a self-hosted gateway without forking.
128
+ # Without this entry the override never crosses the orchestrator → sandbox
129
+ # boundary. Same gap as #77; included here for codex parity.
130
+ ("CODEX_CONFIG_EXTRA", os.getenv("CODEX_CONFIG_EXTRA", "")),
131
+ )
132
+
133
+ # OpenCode passthrough envs (Phase 6 — only injected when SUBAGENT_CLI=opencode).
134
+ # Includes OPENAI_API_KEY and ANTHROPIC_API_KEY because OpenCode itself supports
135
+ # multiple providers; the allowlist is per-CLI, not per-provider.
136
+ OPENCODE_PASSTHROUGH_ENVS = (
137
+ ("OPENROUTER_API_KEY", os.getenv("OPENROUTER_API_KEY", "")),
138
+ ("OPENAI_API_KEY", os.getenv("OPENAI_API_KEY", "")),
139
+ ("ANTHROPIC_API_KEY", os.getenv("ANTHROPIC_API_KEY", "")),
140
+ ("OPENCODE_MODEL", os.getenv("OPENCODE_MODEL", "")),
141
+ # Operator-supplied OpenCode config override (see docs/cli-config-templates.md
142
+ # "OpenCode — custom OpenAI-compat provider" recipe). Replaces /tmp/opencode.json
143
+ # verbatim when set, so operators can route the opencode sub-agent through a
144
+ # self-hosted gateway (LiteLLM, OpenLLM, etc.) for proxy-only deployments.
145
+ # Without this entry the override never crosses the orchestrator → sandbox
146
+ # boundary and the entrypoint heredoc falls through to the canonical
147
+ # 3-provider default. Closes #77.
148
+ ("OPENCODE_CONFIG_EXTRA", os.getenv("OPENCODE_CONFIG_EXTRA", "")),
149
+ )
150
+
151
+ # Sub-agent CLI runtime selector (CLI-01, CLI-02). Read once at module load
152
+ # and propagated to every spawned container via extra_env (D5 shape a).
153
+ # Empty/unset → "claude" (backwards-compat invariant). Invalid value → hard
154
+ # fail at module load (D1) so a typo in .env is visible in the very first
155
+ # `docker compose up` log line, never silently runs the wrong CLI.
156
+ _ALLOWED_CLIS = {"claude", "codex", "opencode"}
157
+ _raw_subagent_cli = os.getenv("SUBAGENT_CLI", "").strip().lower()
158
+ if _raw_subagent_cli and _raw_subagent_cli not in _ALLOWED_CLIS:
159
+ print(
160
+ f"[computer-use-server] FATAL: SUBAGENT_CLI={_raw_subagent_cli!r} "
161
+ f"is not one of {{claude, codex, opencode}}.",
162
+ file=sys.stderr,
163
+ )
164
+ sys.exit(1)
165
+ SUBAGENT_CLI = _raw_subagent_cli or "claude"
166
+
167
+ # Active passthrough set selected by SUBAGENT_CLI — AUTH-01 / Pitfall 1.
168
+ # Single source of truth for "which auth env vars cross the orchestrator->sandbox
169
+ # boundary for this runtime". `_create_container` reads this once per container.
170
+ _PASSTHROUGH_BY_CLI = {
171
+ "claude": CLAUDE_CODE_PASSTHROUGH_ENVS,
172
+ "codex": CODEX_PASSTHROUGH_ENVS,
173
+ "opencode": OPENCODE_PASSTHROUGH_ENVS,
174
+ }
175
+
176
+ # Vision API for describe-image / upd-processing skills
177
+ VISION_API_KEY = os.getenv("VISION_API_KEY", "")
178
+ VISION_API_URL = os.getenv("VISION_API_URL", "")
179
+ VISION_MODEL = os.getenv("VISION_MODEL", "gpt-4o")
180
+
181
+
182
+ def warn_if_public_base_url_is_default() -> bool:
183
+ """Emit a one-time startup warning when PUBLIC_BASE_URL is still the
184
+ hardcoded internal-DNS default.
185
+
186
+ The default (http://computer-use-server:8081) is only reachable from inside
187
+ the compose network. Since the public URL is now baked into /system-prompt
188
+ and returned to the filter via response header, a default value means the
189
+ preview panel will never appear — the browser cannot resolve the internal
190
+ DNS name.
191
+
192
+ Returns True if a warning was emitted (useful for tests), False otherwise.
193
+ Called once from FastAPI lifespan startup — do not call per-request.
194
+ """
195
+ if PUBLIC_BASE_URL == PUBLIC_BASE_URL_DEFAULT:
196
+ print(
197
+ "[computer-use-server] WARNING: PUBLIC_BASE_URL is still the "
198
+ f"hardcoded default ({PUBLIC_BASE_URL_DEFAULT!r}). This URL is only "
199
+ "reachable from inside the compose network — the Open WebUI preview "
200
+ "panel will never appear until you set it to a browser-reachable URL.\n"
201
+ " Fix: in .env, set PUBLIC_BASE_URL=http://<browser-reachable-host>:8081.\n"
202
+ " Docs: https://github.com/Wide-Moat/open-computer-use/blob/main/docs/openwebui-filter.md"
203
+ )
204
+ return True
205
+ return False
206
+
207
+
208
+ def warn_if_mcp_api_key_missing() -> bool:
209
+ """Emit a one-time startup warning when MCP_API_KEY is empty.
210
+
211
+ An empty MCP_API_KEY makes every /mcp endpoint publicly callable without
212
+ authentication — fine for local dev, dangerous for any deployment the
213
+ internet can reach. Warn loudly so the condition does not silently survive
214
+ a prod rollout.
215
+
216
+ Returns True if a warning was emitted (useful for tests), False otherwise.
217
+ Called once from FastAPI lifespan startup — do not call per-request.
218
+ """
219
+ if not os.getenv("MCP_API_KEY", ""):
220
+ print(
221
+ "[computer-use-server] WARNING: MCP_API_KEY is empty — the /mcp "
222
+ "endpoints accept ANY caller with no auth. Acceptable for local "
223
+ "development, unsafe for anything reachable from the internet.\n"
224
+ " Fix: set MCP_API_KEY in .env to a long random string and mirror "
225
+ "it in the Open WebUI tool Valve (Admin → Tools → Computer Use → "
226
+ "Valves → MCP_API_KEY)."
227
+ )
228
+ return True
229
+ return False
230
+
231
+
232
+ def warn_subagent_cli() -> bool:
233
+ """Emit a one-line banner naming the active sub-agent CLI runtime.
234
+
235
+ Always prints (informational, not gated on a default) so operators have
236
+ visible confirmation that SUBAGENT_CLI took effect after a docker compose
237
+ restart. Mirrors warn_if_public_base_url_is_default's bool-return
238
+ contract so app.py lifespan can collect emission flags for future
239
+ telemetry. Closes the UX gap from PITFALLS.md UX table row 1.
240
+
241
+ Returns True (always emitted, kept for symmetry with sibling warn_*).
242
+ Called once from FastAPI lifespan startup — do not call per-request.
243
+ """
244
+ print(f"[MCP] Sub-agent runtime: {SUBAGENT_CLI}")
245
+ return True
246
+
247
+
248
+ async def _fetch_gitlab_token(email: str, mcp_tokens_url: str, mcp_tokens_api_key: str) -> Optional[str]:
249
+ """
250
+ Fetch decrypted GitLab token from MCP Tokens Wrapper service.
251
+
252
+ Args:
253
+ email: User email address
254
+ mcp_tokens_url: URL of MCP Tokens Wrapper service
255
+ mcp_tokens_api_key: Internal API key for authentication
256
+
257
+ Returns:
258
+ GitLab token string or None if not found/error
259
+ """
260
+ if not mcp_tokens_api_key:
261
+ print("[GITLAB] MCP_TOKENS_API_KEY not configured, skipping token fetch")
262
+ return None
263
+
264
+ if not email:
265
+ print("[GITLAB] No email provided, skipping token fetch")
266
+ return None
267
+
268
+ url = f"{mcp_tokens_url}/api/internal/tokens/{email}/gitlab"
269
+ headers = {"X-Internal-Api-Key": mcp_tokens_api_key}
270
+
271
+ try:
272
+ async with aiohttp.ClientSession() as session:
273
+ async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as response:
274
+ if response.status == 200:
275
+ data = await response.json()
276
+ token = data.get("token")
277
+ if token:
278
+ print(f"[GITLAB] Token fetched for {email}")
279
+ return token
280
+ elif response.status == 404:
281
+ print(f"[GITLAB] No token found for {email}")
282
+ else:
283
+ print(f"[GITLAB] Error fetching token: HTTP {response.status}")
284
+ except asyncio.TimeoutError:
285
+ print(f"[GITLAB] Timeout fetching token for {email}")
286
+ except Exception as e:
287
+ print(f"[GITLAB] Error fetching token: {e}")
288
+
289
+ async def _ensure_gitlab_token():
290
+ """
291
+ Ensure GitLab token is available, fetching from MCP Tokens Wrapper if needed.
292
+
293
+ Priority:
294
+ 1. Token from header (current_gitlab_token already set)
295
+ 2. Fetch from MCP Tokens Wrapper by user email
296
+ 3. No token (continue without GitLab auth)
297
+ """
298
+ # If token already set from header, use it
299
+ if current_gitlab_token.get():
300
+ return
301
+
302
+ # Try to fetch from MCP Tokens Wrapper
303
+ user_email = current_user_email.get()
304
+ mcp_tokens_url = current_mcp_tokens_url.get() or MCP_TOKENS_URL
305
+ mcp_tokens_api_key = current_mcp_tokens_api_key.get() or MCP_TOKENS_API_KEY
306
+
307
+ if user_email and mcp_tokens_url and mcp_tokens_api_key:
308
+ token = await _fetch_gitlab_token(user_email, mcp_tokens_url, mcp_tokens_api_key)
309
+ if token:
310
+ current_gitlab_token.set(token)
311
+
312
+
313
+
314
+ # Global Docker client (lazy init)
315
+ _docker_client: Optional[docker.DockerClient] = None
316
+
317
+
318
+
319
+ def build_mcp_config(server_names_csv: str, base_url: Optional[str], user_email: str = "") -> dict | None:
320
+ """Build Claude Code ~/.mcp.json config from comma-separated server names.
321
+
322
+ URLs are templated as {base_url}/mcp/{server_name} (LiteLLM MCP proxy pattern).
323
+ Authorization uses ANTHROPIC_AUTH_TOKEN env var (resolved inside container at write time).
324
+
325
+ Returns dict ready for json.dumps, or None if no servers specified.
326
+
327
+ ``base_url`` may be None or empty; both fall back to the module-level
328
+ ANTHROPIC_BASE_URL constant so callers can pass the ContextVar value
329
+ directly without a manual fallback.
330
+ """
331
+ # Blocklist: prevent recursive sub_agent loops
332
+ BLOCKED_SERVERS = {"docker_ai", "docker-ai"}
333
+
334
+ names = [s.strip() for s in server_names_csv.split(",") if s.strip() and s.strip() not in BLOCKED_SERVERS]
335
+ if not names:
336
+ return None
337
+
338
+ base = (base_url or ANTHROPIC_BASE_URL or "https://api.anthropic.com").rstrip("/")
339
+ servers = {}
340
+ for name in names:
341
+ servers[name] = {
342
+ "type": "http",
343
+ "url": f"{base}/mcp/{name}",
344
+ "headers": {
345
+ "x-openwebui-user-email": user_email,
346
+ },
347
+ }
348
+ return {"mcpServers": servers}
349
+
350
+
351
+ def build_mcp_config_write_script(mcp_config: dict) -> str:
352
+ """Build a shell command that writes ~/.mcp.json inside a container.
353
+
354
+ ANTHROPIC_AUTH_TOKEN is resolved from the container's env at runtime,
355
+ so no secrets are baked into the script itself.
356
+ Uses base64 to avoid shell/JSON escaping issues.
357
+ """
358
+ import base64
359
+ config_b64 = base64.b64encode(json.dumps(mcp_config).encode()).decode()
360
+ return (
361
+ f"python3 -c '"
362
+ f"import json,os,base64;"
363
+ f"c=json.loads(base64.b64decode(\"{config_b64}\"));"
364
+ f"k=os.environ.get(\"ANTHROPIC_AUTH_TOKEN\",\"\");"
365
+ f"[s[\"headers\"].__setitem__(\"Authorization\",\"Bearer \"+k)"
366
+ f" for s in c[\"mcpServers\"].values() if \"headers\" in s];"
367
+ f"json.dump(c,open(os.path.expanduser(\"~/.mcp.json\"),\"w\"),indent=2);"
368
+ # Auto-approve MCP servers in settings.local.json so Claude Code doesn't ask
369
+ f"p=os.path.expanduser(\"~/.claude/settings.local.json\");"
370
+ f"sl=json.load(open(p)) if os.path.exists(p) else {{}};"
371
+ f"sl[\"enabledMcpjsonServers\"]=list(c[\"mcpServers\"].keys());"
372
+ f"json.dump(sl,open(p,\"w\"),indent=2)"
373
+ f"'"
374
+ )
375
+
376
+
377
+ def get_docker_client() -> docker.DockerClient:
378
+ """Get or create Docker client connected to local socket."""
379
+ global _docker_client
380
+ if _docker_client is None:
381
+ _docker_client = docker.DockerClient(base_url=DOCKER_SOCKET)
382
+ _docker_client.ping()
383
+ print(f"[MCP] Connected to Docker at {DOCKER_SOCKET}")
384
+ return _docker_client
385
+
386
+
387
+ def _build_container_env(extra_env: Optional[dict] = None) -> dict:
388
+ """Build environment variables dict for container."""
389
+ env = {
390
+ "NPM_CONFIG_PREFIX": "/usr/local/lib/node_modules_global",
391
+ }
392
+ if extra_env:
393
+ env.update(extra_env)
394
+ return env
395
+
396
+
397
+ # Cached compose network name (detected once, reused)
398
+ _compose_network_name: Optional[str] = None
399
+
400
+
401
+ def _get_compose_network_name(force_refresh: bool = False) -> Optional[str]:
402
+ """Find the Docker compose network that computer-use-orchestrator is on (for CDP proxy access)."""
403
+ global _compose_network_name
404
+ if _compose_network_name is not None and not force_refresh:
405
+ return _compose_network_name
406
+
407
+ client = get_docker_client()
408
+ try:
409
+ fs = client.containers.get(ORCHESTRATOR_CONTAINER_NAME)
410
+ fs.reload()
411
+ for name in fs.attrs["NetworkSettings"]["Networks"]:
412
+ if name != "bridge":
413
+ _compose_network_name = name
414
+ print(f"[MCP] Detected compose network: {name}")
415
+ return name
416
+ except Exception as e:
417
+ print(f"[MCP] Could not detect compose network: {e}")
418
+ return None
419
+
420
+
421
+ def get_container_cdp_address(chat_id: str) -> Optional[str]:
422
+ """Get the IP address of a chat's container on the compose network (for CDP proxy).
423
+
424
+ After deploy (docker-compose down/up), running containers may still be on the
425
+ old compose network with an unreachable IP. This function detects the mismatch
426
+ and reconnects the container to the current compose network.
427
+ """
428
+ chat_id = chat_id.lower()
429
+ client = get_docker_client()
430
+ sanitized_id = re.sub(r'[^a-zA-Z0-9_.-]', '-', chat_id)
431
+ container_name = f"owui-chat-{sanitized_id}"
432
+ try:
433
+ c = client.containers.get(container_name)
434
+ c.reload()
435
+ if c.status != "running":
436
+ return None
437
+
438
+ compose_net = _get_compose_network_name()
439
+ networks = c.attrs["NetworkSettings"]["Networks"]
440
+
441
+ # If container is on the current compose network, use that IP
442
+ if compose_net and compose_net in networks:
443
+ ip = networks[compose_net].get("IPAddress")
444
+ if ip:
445
+ return ip
446
+
447
+ # Container running but NOT on compose network → fix and retry
448
+ if compose_net and compose_net not in networks:
449
+ print(f"[MCP] {container_name} not on compose network {compose_net}, reconnecting...")
450
+ _fix_dead_networks(client, c)
451
+ c.reload()
452
+ networks = c.attrs["NetworkSettings"]["Networks"]
453
+ if compose_net in networks:
454
+ ip = networks[compose_net].get("IPAddress")
455
+ if ip:
456
+ return ip
457
+
458
+ # Fallback: first non-bridge IP
459
+ for net_name, net_data in networks.items():
460
+ if net_name != "bridge" and net_data.get("IPAddress"):
461
+ return net_data["IPAddress"]
462
+ ip = c.attrs["NetworkSettings"]["IPAddress"]
463
+ return ip if ip else None
464
+ except Exception:
465
+ return None
466
+
467
+
468
+ def _fix_dead_networks(client, container):
469
+ """Disconnect from dead networks and reconnect to compose network.
470
+
471
+ After docker-compose down/up (deploy), networks are recreated with new IDs.
472
+ Stopped containers still reference old (dead) networks, causing start() to fail.
473
+ Same logic as restart-container endpoint in app.py.
474
+ """
475
+ try:
476
+ container.reload()
477
+ old_nets = list(container.attrs.get("NetworkSettings", {}).get("Networks", {}).keys())
478
+ for net_name in old_nets:
479
+ try:
480
+ net = client.networks.get(net_name)
481
+ net.disconnect(container, force=True)
482
+ except Exception:
483
+ pass # Network already dead — ignore
484
+ compose_net = _get_compose_network_name(force_refresh=True)
485
+ if compose_net:
486
+ try:
487
+ net = client.networks.get(compose_net)
488
+ net.connect(container)
489
+ except Exception as e:
490
+ print(f"[MCP] Warning: could not connect to {compose_net}: {e}")
491
+ except Exception as e:
492
+ print(f"[MCP] Warning: network fix failed: {e}")
493
+
494
+
495
+ def _get_or_create_container(chat_id: str) -> docker.models.containers.Container:
496
+ """Get existing container or create new one for this chat."""
497
+ chat_id = chat_id.lower()
498
+ client = get_docker_client()
499
+
500
+ # Sanitize chat_id for Docker container naming
501
+ sanitized_id = re.sub(r'[^a-zA-Z0-9_.-]', '-', chat_id)
502
+ container_name = f"owui-chat-{sanitized_id}"
503
+
504
+ try:
505
+ container = client.containers.get(container_name)
506
+ container.reload()
507
+
508
+ if container.status == "exited":
509
+ _fix_dead_networks(client, container)
510
+ container.start()
511
+ print(f"[MCP] Started existing container: {container_name}")
512
+ elif container.status == "running":
513
+ if DEBUG_LOGGING:
514
+ print(f"[MCP] Reusing running container: {container_name}")
515
+ else:
516
+ container.start()
517
+ print(f"[MCP] Started container in state '{container.status}': {container_name}")
518
+
519
+ return container
520
+
521
+ except docker.errors.NotFound:
522
+ print(f"[MCP] Creating new container: {container_name}")
523
+ return _create_container(chat_id, container_name)
524
+
525
+
526
+ def _create_container(chat_id: str, container_name: str) -> docker.models.containers.Container:
527
+ """Create a new persistent container for this chat."""
528
+ client = get_docker_client()
529
+
530
+ # Build extra env from context variables
531
+ extra_env = {
532
+ "GITLAB_HOST": current_gitlab_host.get(),
533
+ }
534
+
535
+ gitlab_token = current_gitlab_token.get()
536
+ if gitlab_token:
537
+ extra_env["GITLAB_TOKEN"] = gitlab_token
538
+ print(f"[MCP] Injecting GITLAB_TOKEN into container environment")
539
+
540
+ # Phase 3 gateway-path injection — only active when SUBAGENT_CLI=claude
541
+ # (AUTH-01: no Anthropic gateway vars bleed into codex/opencode containers).
542
+ if SUBAGENT_CLI == "claude":
543
+ anthropic_key = current_anthropic_auth_token.get() or ANTHROPIC_AUTH_TOKEN
544
+ anthropic_base = current_anthropic_base_url.get() or ANTHROPIC_BASE_URL
545
+ if anthropic_key:
546
+ extra_env["ANTHROPIC_AUTH_TOKEN"] = anthropic_key
547
+ extra_env["ANTHROPIC_BASE_URL"] = anthropic_base
548
+
549
+ # Inject only the active CLI's auth allowlist (AUTH-01 / Pitfall 1: no
550
+ # auth bleed across CLIs — e.g. when SUBAGENT_CLI=opencode, OPENAI_API_KEY
551
+ # and OPENROUTER_API_KEY land in extra_env but ANTHROPIC_* gateway vars
552
+ # do NOT, even if set on the host).
553
+ for _name, _value in _PASSTHROUGH_BY_CLI[SUBAGENT_CLI]:
554
+ if _value:
555
+ extra_env[_name] = _value
556
+
557
+ # Sub-agent runtime selector (CLI-01) — propagated to every container so
558
+ # the Phase 7 .bashrc autostart `exec "${SUBAGENT_CLI:-claude}"` can read it
559
+ # and `docker inspect <sandbox>` shows the chosen runtime in Env.
560
+ extra_env["SUBAGENT_CLI"] = SUBAGENT_CLI
561
+
562
+ # OpenCode reads its config from $OPENCODE_CONFIG. Pin it to /tmp so docker
563
+ # exec'd subprocesses (e.g. mcp_tools.sub_agent dispatch) inherit it — the
564
+ # entrypoint `export OPENCODE_CONFIG=/tmp/opencode.json` only affects the
565
+ # entrypoint shell session, NOT subsequent `docker exec` invocations.
566
+ # Without this pin, OpenCode would fall back to ~/.local/share/opencode/auth.json
567
+ # and reopen the Pitfall 7 leak vector. ROADMAP success #2: `docker inspect`
568
+ # must show this env in the container Env.
569
+ if SUBAGENT_CLI == "opencode":
570
+ extra_env["OPENCODE_CONFIG"] = "/tmp/opencode.json"
571
+ # Propagate request-scoped X-Anthropic-Api-Key into the env name OpenCode
572
+ # expects (`{env:ANTHROPIC_API_KEY}` per docs/multi-cli.md and the
573
+ # entrypoint heredoc in Dockerfile). Without this, header-authenticated
574
+ # runs lose their credential when SUBAGENT_CLI=opencode because the claude
575
+ # branch above is not active. Process-level ANTHROPIC_AUTH_TOKEN env is
576
+ # the host-level fallback (covered by OPENCODE_PASSTHROUGH_ENVS — but the
577
+ # request-scoped header path was missed). Per CodeRabbit PR#75 review.
578
+ request_scoped_anthropic = current_anthropic_auth_token.get()
579
+ if request_scoped_anthropic:
580
+ extra_env["ANTHROPIC_API_KEY"] = request_scoped_anthropic
581
+
582
+ # Vision API for describe-image / upd-processing skills
583
+ if VISION_API_KEY:
584
+ extra_env["VISION_API_KEY"] = VISION_API_KEY
585
+ extra_env["VISION_API_URL"] = VISION_API_URL
586
+ extra_env["VISION_MODEL"] = VISION_MODEL
587
+
588
+ user_name = current_user_name.get()
589
+ user_email = current_user_email.get()
590
+ if user_name:
591
+ extra_env["GIT_AUTHOR_NAME"] = user_name
592
+ extra_env["GIT_COMMITTER_NAME"] = user_name
593
+ if user_email:
594
+ extra_env["GIT_AUTHOR_EMAIL"] = user_email
595
+ extra_env["GIT_COMMITTER_EMAIL"] = user_email
596
+ # Anthropic-specific custom header — only emit for the claude runtime
597
+ # so codex / opencode containers do not get spurious anthropic env.
598
+ if SUBAGENT_CLI == "claude":
599
+ extra_env["ANTHROPIC_CUSTOM_HEADERS"] = f"x-openwebui-user-email: {user_email}"
600
+
601
+ # Workspace volume for this chat
602
+ workspace_volume = f"chat-{chat_id}-workspace"
603
+
604
+ # Host paths for user data
605
+ chat_data_path = os.path.join(USER_DATA_BASE_PATH, chat_id)
606
+ uploads_path = os.path.join(chat_data_path, "uploads")
607
+ outputs_path = os.path.join(chat_data_path, "outputs")
608
+
609
+ # Create directories on Docker host with correct permissions
610
+ try:
611
+ print(f"[MCP] Creating directories: {uploads_path}, {outputs_path}")
612
+ client.containers.run(
613
+ image=DOCKER_IMAGE,
614
+ command=f"bash -c 'mkdir -p {shlex.quote(uploads_path)} {shlex.quote(outputs_path)} && chmod -R 777 {shlex.quote(chat_data_path)}'",
615
+ volumes={"/tmp": {"bind": "/tmp", "mode": "rw"}},
616
+ remove=True,
617
+ detach=False,
618
+ user="root"
619
+ )
620
+ except Exception as e:
621
+ print(f"[MCP] Warning: Failed to create directories: {e}")
622
+
623
+ # Check if using custom image (has entrypoint) or standard image
624
+ use_entrypoint = "computer-use" in DOCKER_IMAGE or "open-computer-use" in DOCKER_IMAGE
625
+
626
+ if use_entrypoint:
627
+ # Production: use entrypoint script
628
+ command = ["bash", "-c", "/home/assistant/.entrypoint.sh bash -c 'trap \"exit 0\" SIGTERM SIGINT; tail -f /dev/null & wait $!'"]
629
+ working_dir = "/home/assistant"
630
+ user = "assistant:assistant"
631
+ else:
632
+ # Development/test: simple bash loop
633
+ command = ["bash", "-c", "trap 'exit 0' SIGTERM SIGINT; tail -f /dev/null & wait $!"]
634
+ working_dir = "/root"
635
+ user = None # Use image default
636
+
637
+ config = {
638
+ "image": DOCKER_IMAGE,
639
+ "name": container_name,
640
+ "hostname": f"chat-{chat_id[:8]}",
641
+ "command": command,
642
+ "detach": True,
643
+ "stdin_open": True,
644
+ "tty": True,
645
+ "mem_limit": CONTAINER_MEM_LIMIT,
646
+ "nano_cpus": int(CONTAINER_CPU_LIMIT * 1_000_000_000),
647
+ "working_dir": working_dir,
648
+ "environment": _build_container_env(extra_env),
649
+ "volumes": {
650
+ workspace_volume: {"bind": working_dir, "mode": "rw"},
651
+ uploads_path: {"bind": "/mnt/user-data/uploads", "mode": "ro"},
652
+ outputs_path: {"bind": "/mnt/user-data/outputs", "mode": "rw"},
653
+ **skill_manager.get_skill_mounts(
654
+ skill_manager.get_user_skills_sync(current_user_email.get())
655
+ ),
656
+ },
657
+ "labels": {
658
+ "managed-by": "mcp-computer-use-orchestrator",
659
+ "chat-id": chat_id,
660
+ "tool": "computer-use-mcp"
661
+ },
662
+ "security_opt": ["no-new-privileges:true"],
663
+ }
664
+
665
+ if user:
666
+ config["user"] = user
667
+
668
+ if not ENABLE_NETWORK:
669
+ config["network_disabled"] = True
670
+
671
+ try:
672
+ container = client.containers.create(**config)
673
+ except docker.errors.APIError as e:
674
+ if e.status_code == 409:
675
+ # Container name exists (stale after deploy) — remove and retry
676
+ print(f"[MCP] [409] Removing stale container: {container_name}")
677
+ try:
678
+ old = client.containers.get(container_name)
679
+ old.remove(force=True)
680
+ except Exception:
681
+ pass
682
+ container = client.containers.create(**config)
683
+ else:
684
+ raise
685
+ container.start()
686
+
687
+ # Connect to compose network so computer-use-orchestrator can proxy CDP (port 9222) to this container
688
+ try:
689
+ compose_net = _get_compose_network_name()
690
+ if compose_net:
691
+ client.networks.get(compose_net).connect(container)
692
+ if DEBUG_LOGGING:
693
+ print(f"[MCP] Connected {container_name} to network {compose_net}")
694
+ except Exception as e:
695
+ print(f"[MCP] Warning: Could not connect to compose network: {e}")
696
+
697
+ print(f"[MCP] Created and started new container: {container_name}")
698
+
699
+ # Save metadata for resurrection after container removal by cron
700
+ save_container_meta(
701
+ chat_id,
702
+ current_user_email.get(),
703
+ current_user_name.get(),
704
+ current_mcp_servers.get(),
705
+ )
706
+
707
+ # Write MCP config on creation so terminal users have it immediately
708
+ try:
709
+ mcp_servers_str = current_mcp_servers.get()
710
+ if mcp_servers_str:
711
+ mcp_cfg = build_mcp_config(
712
+ mcp_servers_str,
713
+ current_anthropic_base_url.get(),
714
+ current_user_email.get() or "",
715
+ )
716
+ if mcp_cfg:
717
+ write_cmd = build_mcp_config_write_script(mcp_cfg)
718
+ _execute_bash(container, write_cmd, 15)
719
+ print(f"[MCP] Wrote MCP config on container creation: {mcp_servers_str}")
720
+ except Exception as e:
721
+ print(f"[MCP] Warning: MCP setup on create failed: {e}")
722
+
723
+ # Tier 2 — write /home/assistant/README.md with the rendered system prompt
724
+ # so the model can always recover its environment via `view` regardless of
725
+ # what the client did (or didn't do) with prompts/get and InitializeResult.
726
+ #
727
+ # Safe to call asyncio.run here: _create_container runs inside
728
+ # asyncio.to_thread (see all call sites in mcp_tools.py) → worker thread
729
+ # with no running event loop → no nested-loop error.
730
+ try:
731
+ _, workdir = _get_container_user_and_workdir()
732
+ readme_text = render_system_prompt_sync(chat_id, current_user_email.get())
733
+ _write_file_to_container(container, workdir, "README.md", readme_text)
734
+ print(f"[MCP] Wrote {workdir}/README.md ({len(readme_text)} chars)")
735
+ except Exception as e:
736
+ print(f"[MCP] Warning: README.md write failed: {e}")
737
+
738
+ # Tier 6 — initial sync of uploaded files into MCP resources registry.
739
+ # Lazy import to avoid circular (mcp_resources → mcp_tools → docker_manager).
740
+ try:
741
+ from mcp_resources import sync_chat_resources_sync
742
+ n = sync_chat_resources_sync(chat_id)
743
+ if n:
744
+ print(f"[MCP] Registered {n} upload resource(s) for chat {chat_id}")
745
+ except Exception as e:
746
+ print(f"[MCP] Warning: MCP resources sync failed: {e}")
747
+
748
+ # Pitfall 7 defense — scrub OpenCode auth.json from volume on container
749
+ # creation (handles resurrected containers from previous opencode-auth-login
750
+ # experiments). Best-effort — silent on failure (absence is normal).
751
+ try:
752
+ container.exec_run(
753
+ "rm -f /home/assistant/.local/share/opencode/auth.json",
754
+ user="assistant",
755
+ )
756
+ except Exception:
757
+ pass
758
+
759
+ return container
760
+
761
+
762
+ def _write_file_to_container(container, dirpath: str, filename: str, text: str) -> None:
763
+ """
764
+ Write a UTF-8 text file into the container at `dirpath/filename` using
765
+ Docker's put_archive API. Cleaner than `exec cat > file` — no shell
766
+ escaping, no interference from shell initialisation.
767
+ """
768
+ import io, tarfile, time as _t
769
+ data = text.encode("utf-8")
770
+ buf = io.BytesIO()
771
+ with tarfile.open(fileobj=buf, mode="w") as tar:
772
+ info = tarfile.TarInfo(name=filename)
773
+ info.size = len(data)
774
+ info.mtime = int(_t.time())
775
+ info.mode = 0o644
776
+ tar.addfile(info, io.BytesIO(data))
777
+ buf.seek(0)
778
+ # put_archive returns False on extraction failure (e.g. dirpath does not
779
+ # exist) and True on success. Without this check the caller logs success
780
+ # even though the file was never written. APIError still propagates as
781
+ # an exception per docker-py docs.
782
+ if not container.put_archive(dirpath, buf.getvalue()):
783
+ raise RuntimeError(
784
+ f"put_archive returned False writing {dirpath}/{filename} "
785
+ f"to container {container.short_id} — target dir may not exist"
786
+ )
787
+
788
+
789
+ def _get_container_user_and_workdir() -> tuple:
790
+ """Get user and workdir based on Docker image type."""
791
+ use_entrypoint = "computer-use" in DOCKER_IMAGE or "open-computer-use" in DOCKER_IMAGE
792
+ if use_entrypoint:
793
+ return "assistant", "/home/assistant"
794
+ else:
795
+ return None, "/root" # None = use container default
796
+
797
+
798
+ def _reset_shutdown_timer(container, timeout: int = None):
799
+ """Reset container auto-shutdown timer.
800
+
801
+ Args:
802
+ container: Docker container instance
803
+ timeout: Custom timeout in seconds. If None, uses CONTAINER_IDLE_TIMEOUT.
804
+ Used by long-running commands (e.g. sub_agent) to prevent
805
+ the idle timer from killing the container mid-execution.
806
+ """
807
+ user, _ = _get_container_user_and_workdir()
808
+ exec_kwargs = {} if user is None else {"user": user}
809
+
810
+ effective_timeout = timeout if timeout else CONTAINER_IDLE_TIMEOUT
811
+
812
+ # Atomic timer reset: flock serializes concurrent resets so only one timer exists.
813
+ # The outer bash PID (MYPID=$$) is tracked in the file.
814
+ # When a new reset arrives: kill children (sleep) FIRST, then the bash parent.
815
+ # Order matters: killing bash first reparents sleep to PID 1, making pkill -P miss it.
816
+ # flock is released before sleep starts, so it doesn't block future resets.
817
+ timer_cmd = (
818
+ f"bash -c '"
819
+ f"MYPID=$$; "
820
+ f"(flock -x 9; "
821
+ f"OLD=$(cat /tmp/.shutdown-timer-pid 2>/dev/null); "
822
+ f'[ -n "$OLD" ] && pkill -P "$OLD" 2>/dev/null; '
823
+ f'[ -n "$OLD" ] && kill "$OLD" 2>/dev/null; '
824
+ f"echo $MYPID > /tmp/.shutdown-timer-pid"
825
+ f") 9>/tmp/.shutdown-timer-lock; "
826
+ f"sleep {effective_timeout} && kill 1"
827
+ f"'"
828
+ )
829
+ container.exec_run(timer_cmd, detach=True, **exec_kwargs)
830
+
831
+
832
+ def _execute_bash(container, command: str, timeout: int = None) -> dict:
833
+ """Execute bash command in container with timeout."""
834
+ user, workdir = _get_container_user_and_workdir()
835
+
836
+ try:
837
+ cmd_timeout = timeout if timeout is not None else COMMAND_TIMEOUT
838
+ # Ensure shutdown timer won't kill container before command finishes
839
+ shutdown_timeout = max(CONTAINER_IDLE_TIMEOUT, cmd_timeout + 60)
840
+ _reset_shutdown_timer(container, shutdown_timeout)
841
+ timed_command = f"timeout {cmd_timeout} bash -c {shlex.quote(command)}"
842
+
843
+ exec_result = container.exec_run(
844
+ cmd=["bash", "-c", timed_command],
845
+ stdout=True,
846
+ stderr=True,
847
+ demux=True,
848
+ workdir=workdir
849
+ )
850
+
851
+ stdout_data, stderr_data = exec_result.output if exec_result.output else (b"", b"")
852
+ stdout = stdout_data.decode("utf-8", errors="replace") if stdout_data else ""
853
+ stderr = stderr_data.decode("utf-8", errors="replace") if stderr_data else ""
854
+
855
+ output = ""
856
+ if stdout:
857
+ output += stdout
858
+ if stderr:
859
+ if output:
860
+ output += "\n"
861
+ output += stderr
862
+
863
+ if exec_result.exit_code == 124:
864
+ output += f"\n[Command timed out after {cmd_timeout} seconds]"
865
+
866
+ return {
867
+ "exit_code": exec_result.exit_code,
868
+ "output": output,
869
+ "success": exec_result.exit_code == 0
870
+ }
871
+
872
+ except Exception as e:
873
+ return {
874
+ "exit_code": -1,
875
+ "output": f"Execution error: {str(e)}",
876
+ "success": False
877
+ }
878
+
879
+
880
+ # ---------------------------------------------------------------------------
881
+ # ADAPT-05 / Phase 5: capture variant of _execute_bash.
882
+ #
883
+ # _execute_bash returns {output, exit_code, success} where stdout+stderr are
884
+ # concatenated. Adapter parse_result(stdout, stderr, returncode) needs them
885
+ # separated, so cli_runtime.dispatch uses this helper instead. Same docker
886
+ # exec semantics (timeout, shutdown-timer reset, demux=True), different
887
+ # return shape.
888
+ #
889
+ # Returns a SimpleNamespace so callers can do `.stdout`, `.stderr`,
890
+ # `.returncode` (matches subprocess.CompletedProcess shape — adapter parsers
891
+ # are written against that idiom). SimpleNamespace is imported at the top of
892
+ # the module (PEP 8 — do NOT inline the import here).
893
+ # ---------------------------------------------------------------------------
894
+ def _execute_bash_capture(container, command: str, timeout: int = None):
895
+ """Execute bash in container; return SimpleNamespace(stdout, stderr, returncode).
896
+
897
+ Stdout/stderr are kept separate (unlike _execute_bash which concatenates).
898
+ Used by cli_runtime.dispatch to feed adapter.parse_result, which is
899
+ written against the subprocess.CompletedProcess (stdout, stderr,
900
+ returncode) shape.
901
+
902
+ SECURITY (Phase 5 threat model T-05-05-01): the `command` argument is
903
+ passed straight to bash -c via shlex.quote — caller is responsible for
904
+ having shlex.quote'd every shell-significant value. cli_runtime.dispatch
905
+ constructs the command from `shlex.quote`'d argv elements; do not call
906
+ this helper with operator-controlled raw strings.
907
+ """
908
+ user, workdir = _get_container_user_and_workdir()
909
+ try:
910
+ cmd_timeout = timeout if timeout is not None else COMMAND_TIMEOUT
911
+ shutdown_timeout = max(CONTAINER_IDLE_TIMEOUT, cmd_timeout + 60)
912
+ _reset_shutdown_timer(container, shutdown_timeout)
913
+ timed_command = f"timeout {cmd_timeout} bash -c {shlex.quote(command)}"
914
+
915
+ exec_result = container.exec_run(
916
+ cmd=["bash", "-c", timed_command],
917
+ stdout=True,
918
+ stderr=True,
919
+ demux=True,
920
+ workdir=workdir,
921
+ )
922
+
923
+ stdout_data, stderr_data = exec_result.output if exec_result.output else (b"", b"")
924
+ stdout = stdout_data.decode("utf-8", errors="replace") if stdout_data else ""
925
+ stderr = stderr_data.decode("utf-8", errors="replace") if stderr_data else ""
926
+
927
+ return SimpleNamespace(
928
+ stdout=stdout,
929
+ stderr=stderr,
930
+ returncode=exec_result.exit_code,
931
+ )
932
+ except Exception as e:
933
+ return SimpleNamespace(
934
+ stdout="",
935
+ stderr=f"Execution error: {str(e)}",
936
+ returncode=-1,
937
+ )
938
+
939
+
940
+ def execute_bash_streaming(container, command: str, timeout: int, on_output_line=None) -> dict:
941
+ """Execute bash in container with streaming output.
942
+
943
+ Calls on_output_line(line) for each non-empty output line as it arrives.
944
+ Returns dict with output (full text), exit_code, success.
945
+ """
946
+ user, workdir = _get_container_user_and_workdir()
947
+ try:
948
+ cmd_timeout = timeout - 5 if timeout > 10 else timeout
949
+ shutdown_timeout = max(CONTAINER_IDLE_TIMEOUT, cmd_timeout + 60)
950
+ _reset_shutdown_timer(container, shutdown_timeout)
951
+
952
+ timed_command = f"timeout {cmd_timeout} bash -c {shlex.quote(command)}"
953
+ client = container.client
954
+
955
+ exec_id = client.api.exec_create(
956
+ container.id,
957
+ ["bash", "-c", timed_command],
958
+ stdout=True,
959
+ stderr=True,
960
+ workdir=workdir,
961
+ )["Id"]
962
+
963
+ chunks = []
964
+ remainder = ""
965
+ for chunk in client.api.exec_start(exec_id, stream=True):
966
+ decoded = chunk.decode("utf-8", errors="replace")
967
+ chunks.append(decoded)
968
+ if on_output_line:
969
+ lines = (remainder + decoded).split("\n")
970
+ remainder = lines[-1]
971
+ for line in lines[:-1]:
972
+ stripped = line.strip()
973
+ if stripped:
974
+ on_output_line(stripped[:120])
975
+
976
+ if on_output_line and remainder.strip():
977
+ on_output_line(remainder.strip()[:120])
978
+
979
+ output = "".join(chunks)
980
+ info = client.api.exec_inspect(exec_id)
981
+ exit_code = info.get("ExitCode") or 0
982
+
983
+ if exit_code == 124:
984
+ output += f"\n[Command timed out after {cmd_timeout} seconds]"
985
+
986
+ return {"output": output, "exit_code": exit_code, "success": exit_code == 0}
987
+
988
+ except Exception as e:
989
+ return {"exit_code": -1, "output": f"Execution error: {str(e)}", "success": False}
990
+
991
+
992
+ def _get_meta_path(chat_id: str) -> Path:
993
+ """Path to .meta.json for this chat on the host filesystem."""
994
+ from security import sanitize_chat_id
995
+ chat_id = sanitize_chat_id(chat_id)
996
+ return BASE_DATA_DIR / chat_id / ".meta.json"
997
+
998
+
999
+ def save_container_meta(chat_id: str, user_email: str, user_name: str,
1000
+ mcp_servers: str):
1001
+ """Persist non-secret metadata needed to recreate a container after removal.
1002
+
1003
+ NO secrets/tokens stored — they come from computer-use-orchestrator ENV at resurrect time.
1004
+ """
1005
+ meta = {
1006
+ "user_email": user_email or "",
1007
+ "user_name": user_name or "",
1008
+ "mcp_servers": mcp_servers or "",
1009
+ "created_at": datetime.datetime.utcnow().isoformat() + "Z",
1010
+ }
1011
+ meta_path = _get_meta_path(chat_id)
1012
+ try:
1013
+ meta_path.parent.mkdir(parents=True, exist_ok=True)
1014
+ meta_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2))
1015
+ print(f"[META] Saved metadata: {meta_path}")
1016
+ except Exception as e:
1017
+ print(f"[META] Warning: failed to save metadata: {e}")
1018
+
1019
+
1020
+ def load_container_meta(chat_id: str) -> Optional[dict]:
1021
+ """Load saved metadata for container recreation. Returns dict or None."""
1022
+ meta_path = _get_meta_path(chat_id)
1023
+ try:
1024
+ if meta_path.exists():
1025
+ return json.loads(meta_path.read_text())
1026
+ except Exception as e:
1027
+ print(f"[META] Warning: failed to load metadata: {e}")
1028
+ return None
1029
+
1030
+
1031
+ def _execute_python_with_stdin(container, script: str, data: str) -> dict:
1032
+ """Execute Python script in container with data passed through stdin."""
1033
+ import socket as sock_module
1034
+
1035
+ _reset_shutdown_timer(container)
1036
+ user, workdir = _get_container_user_and_workdir()
1037
+
1038
+ try:
1039
+ exec_create_kwargs = {
1040
+ "stdin": True,
1041
+ "stdout": True,
1042
+ "stderr": True,
1043
+ "workdir": workdir,
1044
+ }
1045
+ if user:
1046
+ exec_create_kwargs["user"] = user
1047
+
1048
+ exec_id = container.client.api.exec_create(
1049
+ container.id,
1050
+ ["timeout", str(COMMAND_TIMEOUT), "python3", "-c", script],
1051
+ **exec_create_kwargs
1052
+ )['Id']
1053
+
1054
+ sock = container.client.api.exec_start(exec_id, socket=True)
1055
+
1056
+ data_bytes = data.encode('utf-8')
1057
+
1058
+ if hasattr(sock, '_sock'):
1059
+ sock._sock.sendall(data_bytes)
1060
+ sock._sock.shutdown(sock_module.SHUT_WR)
1061
+ else:
1062
+ sock.sendall(data_bytes)
1063
+ if hasattr(sock, 'shutdown_write'):
1064
+ sock.shutdown_write()
1065
+
1066
+ gen = frames_iter(sock, tty=False)
1067
+ gen = (demux_adaptor(*frame) for frame in gen)
1068
+ stdout_data, stderr_data = consume_socket_output(gen, demux=True)
1069
+
1070
+ sock.close()
1071
+
1072
+ exec_info = container.client.api.exec_inspect(exec_id)
1073
+ exit_code = exec_info['ExitCode']
1074
+
1075
+ stdout = stdout_data.decode("utf-8", errors="replace") if stdout_data else ""
1076
+ stderr = stderr_data.decode("utf-8", errors="replace") if stderr_data else ""
1077
+
1078
+ output = ""
1079
+ if stdout:
1080
+ output += stdout
1081
+ if stderr:
1082
+ if output:
1083
+ output += "\n"
1084
+ output += stderr
1085
+
1086
+ if exit_code == 124:
1087
+ output += f"\n[Command timed out after {COMMAND_TIMEOUT} seconds]"
1088
+
1089
+ return {
1090
+ "exit_code": exit_code,
1091
+ "output": output,
1092
+ "success": exit_code == 0
1093
+ }
1094
+
1095
+ except Exception as e:
1096
+ return {
1097
+ "exit_code": -1,
1098
+ "output": f"Execution error: {str(e)}",
1099
+ "success": False
1100
+ }