corporateai 0.0.1

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 (704) hide show
  1. package/.dockerignore +10 -0
  2. package/.env.example +3 -0
  3. package/.github/workflows/publish-cli.yml +49 -0
  4. package/.mailmap +1 -0
  5. package/AGENTS.md +148 -0
  6. package/CONTRIBUTING.md +75 -0
  7. package/Dockerfile +59 -0
  8. package/Dockerfile.onboard-smoke +42 -0
  9. package/LICENSE +21 -0
  10. package/README.md +93 -0
  11. package/cli/esbuild.config.mjs +11 -0
  12. package/cli/package.json +24 -0
  13. package/cli/scripts/build-cli.mjs +5 -0
  14. package/cli/src/index.ts +27 -0
  15. package/docker-compose.quickstart.yml +18 -0
  16. package/docker-compose.untrusted-review.yml +33 -0
  17. package/docker-compose.yml +38 -0
  18. package/package.json +56 -0
  19. package/patches/embedded-postgres@18.1.0-beta.16.patch +0 -0
  20. package/pnpm-workspace.yaml +4 -0
  21. package/releases/.gitkeep +0 -0
  22. package/releases/v0.0.1.md +36 -0
  23. package/releases/v0.2.7.md +15 -0
  24. package/releases/v0.3.0.md +54 -0
  25. package/releases/v0.3.1.md +55 -0
  26. package/releases/v2026.318.0.md +66 -0
  27. package/releases/v2026.325.0.md +78 -0
  28. package/report/2026-03-13-08-46-token-optimization-implementation.md +48 -0
  29. package/scripts/backup-db.sh +17 -0
  30. package/scripts/build-npm.sh +80 -0
  31. package/scripts/check-forbidden-tokens.mjs +115 -0
  32. package/scripts/clean-onboard-git.sh +14 -0
  33. package/scripts/clean-onboard-npm.sh +13 -0
  34. package/scripts/clean-onboard-ref.sh +86 -0
  35. package/scripts/create-github-release.sh +99 -0
  36. package/scripts/dev-runner-paths.mjs +38 -0
  37. package/scripts/dev-runner.mjs +606 -0
  38. package/scripts/docker-onboard-smoke.sh +306 -0
  39. package/scripts/ensure-plugin-build-deps.mjs +47 -0
  40. package/scripts/generate-company-assets.ts +365 -0
  41. package/scripts/generate-npm-package-json.mjs +113 -0
  42. package/scripts/generate-org-chart-images.ts +694 -0
  43. package/scripts/generate-org-chart-satori-comparison.ts +225 -0
  44. package/scripts/generate-ui-package-json.mjs +31 -0
  45. package/scripts/kill-dev.sh +71 -0
  46. package/scripts/migrate-inline-env-secrets.ts +126 -0
  47. package/scripts/prepare-server-ui-dist.sh +22 -0
  48. package/scripts/provision-worktree.sh +333 -0
  49. package/scripts/release-lib.sh +306 -0
  50. package/scripts/release-package-map.mjs +169 -0
  51. package/scripts/release.sh +312 -0
  52. package/scripts/rollback-latest.sh +111 -0
  53. package/scripts/smoke/openclaw-docker-ui.sh +329 -0
  54. package/scripts/smoke/openclaw-gateway-e2e.sh +954 -0
  55. package/scripts/smoke/openclaw-join.sh +295 -0
  56. package/scripts/smoke/openclaw-sse-standalone.sh +146 -0
  57. package/scripts/workspace-compat.mjs +60 -0
  58. package/server/CHANGELOG.md +130 -0
  59. package/server/package.json +96 -0
  60. package/server/scripts/copy-onboarding-assets.mjs +10 -0
  61. package/server/scripts/dev-watch.ts +33 -0
  62. package/server/src/__tests__/activity-routes.test.ts +70 -0
  63. package/server/src/__tests__/adapter-models.test.ts +105 -0
  64. package/server/src/__tests__/adapter-session-codecs.test.ts +194 -0
  65. package/server/src/__tests__/agent-auth-jwt.test.ts +79 -0
  66. package/server/src/__tests__/agent-instructions-routes.test.ts +318 -0
  67. package/server/src/__tests__/agent-instructions-service.test.ts +361 -0
  68. package/server/src/__tests__/agent-permissions-routes.test.ts +275 -0
  69. package/server/src/__tests__/agent-shortname-collision.test.ts +69 -0
  70. package/server/src/__tests__/agent-skill-contract.test.ts +50 -0
  71. package/server/src/__tests__/agent-skills-routes.test.ts +462 -0
  72. package/server/src/__tests__/app-hmr-port.test.ts +19 -0
  73. package/server/src/__tests__/approval-routes-idempotency.test.ts +110 -0
  74. package/server/src/__tests__/approvals-service.test.ts +107 -0
  75. package/server/src/__tests__/assets.test.ts +250 -0
  76. package/server/src/__tests__/attachment-types.test.ts +97 -0
  77. package/server/src/__tests__/board-mutation-guard.test.ts +105 -0
  78. package/server/src/__tests__/budgets-service.test.ts +311 -0
  79. package/server/src/__tests__/claude-local-adapter-environment.test.ts +92 -0
  80. package/server/src/__tests__/claude-local-adapter.test.ts +31 -0
  81. package/server/src/__tests__/claude-local-skill-sync.test.ts +111 -0
  82. package/server/src/__tests__/cli-auth-routes.test.ts +230 -0
  83. package/server/src/__tests__/codex-local-adapter-environment.test.ts +143 -0
  84. package/server/src/__tests__/codex-local-adapter.test.ts +253 -0
  85. package/server/src/__tests__/codex-local-execute.test.ts +391 -0
  86. package/server/src/__tests__/codex-local-skill-injection.test.ts +175 -0
  87. package/server/src/__tests__/codex-local-skill-sync.test.ts +123 -0
  88. package/server/src/__tests__/companies-route-path-guard.test.ts +56 -0
  89. package/server/src/__tests__/company-branding-route.test.ts +196 -0
  90. package/server/src/__tests__/company-portability-routes.test.ts +175 -0
  91. package/server/src/__tests__/company-portability.test.ts +2186 -0
  92. package/server/src/__tests__/company-skills-routes.test.ts +113 -0
  93. package/server/src/__tests__/company-skills.test.ts +229 -0
  94. package/server/src/__tests__/costs-service.test.ts +226 -0
  95. package/server/src/__tests__/cursor-local-adapter-environment.test.ts +196 -0
  96. package/server/src/__tests__/cursor-local-adapter.test.ts +406 -0
  97. package/server/src/__tests__/cursor-local-execute.test.ts +263 -0
  98. package/server/src/__tests__/cursor-local-skill-injection.test.ts +104 -0
  99. package/server/src/__tests__/cursor-local-skill-sync.test.ts +145 -0
  100. package/server/src/__tests__/dev-runner-paths.test.ts +25 -0
  101. package/server/src/__tests__/dev-server-status.test.ts +66 -0
  102. package/server/src/__tests__/dev-watch-ignore.test.ts +42 -0
  103. package/server/src/__tests__/documents.test.ts +29 -0
  104. package/server/src/__tests__/error-handler.test.ts +53 -0
  105. package/server/src/__tests__/execution-workspace-policy.test.ts +170 -0
  106. package/server/src/__tests__/forbidden-tokens.test.ts +77 -0
  107. package/server/src/__tests__/gemini-local-adapter-environment.test.ts +135 -0
  108. package/server/src/__tests__/gemini-local-adapter.test.ts +190 -0
  109. package/server/src/__tests__/gemini-local-execute.test.ts +172 -0
  110. package/server/src/__tests__/gemini-local-skill-sync.test.ts +90 -0
  111. package/server/src/__tests__/health.test.ts +16 -0
  112. package/server/src/__tests__/heartbeat-process-recovery.test.ts +256 -0
  113. package/server/src/__tests__/heartbeat-run-summary.test.ts +33 -0
  114. package/server/src/__tests__/heartbeat-workspace-session.test.ts +334 -0
  115. package/server/src/__tests__/helpers/embedded-postgres.ts +7 -0
  116. package/server/src/__tests__/hire-hook.test.ts +181 -0
  117. package/server/src/__tests__/instance-settings-routes.test.ts +156 -0
  118. package/server/src/__tests__/invite-accept-gateway-defaults.test.ts +119 -0
  119. package/server/src/__tests__/invite-accept-replay.test.ts +92 -0
  120. package/server/src/__tests__/invite-expiry.test.ts +10 -0
  121. package/server/src/__tests__/invite-join-grants.test.ts +57 -0
  122. package/server/src/__tests__/invite-join-manager.test.ts +33 -0
  123. package/server/src/__tests__/invite-onboarding-text.test.ts +116 -0
  124. package/server/src/__tests__/issue-comment-reopen-routes.test.ts +146 -0
  125. package/server/src/__tests__/issue-goal-fallback.test.ts +99 -0
  126. package/server/src/__tests__/issues-checkout-wakeup.test.ts +48 -0
  127. package/server/src/__tests__/issues-goal-context-routes.test.ts +187 -0
  128. package/server/src/__tests__/issues-service.test.ts +317 -0
  129. package/server/src/__tests__/issues-user-context.test.ts +113 -0
  130. package/server/src/__tests__/log-redaction.test.ts +74 -0
  131. package/server/src/__tests__/monthly-spend-service.test.ts +90 -0
  132. package/server/src/__tests__/normalize-agent-mention-token.test.ts +41 -0
  133. package/server/src/__tests__/openclaw-gateway-adapter.test.ts +626 -0
  134. package/server/src/__tests__/openclaw-invite-prompt-route.test.ts +192 -0
  135. package/server/src/__tests__/opencode-local-adapter-environment.test.ts +97 -0
  136. package/server/src/__tests__/opencode-local-adapter.test.ts +226 -0
  137. package/server/src/__tests__/opencode-local-skill-sync.test.ts +91 -0
  138. package/server/src/__tests__/paperclip-env.test.ts +58 -0
  139. package/server/src/__tests__/paperclip-skill-utils.test.ts +63 -0
  140. package/server/src/__tests__/pi-local-adapter-environment.test.ts +102 -0
  141. package/server/src/__tests__/pi-local-skill-sync.test.ts +95 -0
  142. package/server/src/__tests__/plugin-dev-watcher.test.ts +68 -0
  143. package/server/src/__tests__/plugin-worker-manager.test.ts +43 -0
  144. package/server/src/__tests__/private-hostname-guard.test.ts +56 -0
  145. package/server/src/__tests__/project-shortname-resolution.test.ts +45 -0
  146. package/server/src/__tests__/quota-windows-service.test.ts +56 -0
  147. package/server/src/__tests__/quota-windows.test.ts +1109 -0
  148. package/server/src/__tests__/redaction.test.ts +66 -0
  149. package/server/src/__tests__/routines-e2e.test.ts +276 -0
  150. package/server/src/__tests__/routines-routes.test.ts +271 -0
  151. package/server/src/__tests__/routines-service.test.ts +424 -0
  152. package/server/src/__tests__/storage-local-provider.test.ts +78 -0
  153. package/server/src/__tests__/ui-branding.test.ts +82 -0
  154. package/server/src/__tests__/work-products.test.ts +95 -0
  155. package/server/src/__tests__/workspace-runtime.test.ts +1131 -0
  156. package/server/src/__tests__/worktree-config.test.ts +426 -0
  157. package/server/src/adapters/codex-models.ts +105 -0
  158. package/server/src/adapters/cursor-models.ts +171 -0
  159. package/server/src/adapters/http/execute.ts +42 -0
  160. package/server/src/adapters/http/index.ts +21 -0
  161. package/server/src/adapters/http/test.ts +116 -0
  162. package/server/src/adapters/index.ts +18 -0
  163. package/server/src/adapters/process/execute.ts +77 -0
  164. package/server/src/adapters/process/index.ts +24 -0
  165. package/server/src/adapters/process/test.ts +89 -0
  166. package/server/src/adapters/registry.ts +225 -0
  167. package/server/src/adapters/server-utils-compat.ts +57 -0
  168. package/server/src/adapters/types.ts +30 -0
  169. package/server/src/adapters/utils.ts +48 -0
  170. package/server/src/agent-auth-jwt.ts +141 -0
  171. package/server/src/app.ts +321 -0
  172. package/server/src/attachment-types.ts +74 -0
  173. package/server/src/auth/better-auth.ts +148 -0
  174. package/server/src/board-claim.ts +150 -0
  175. package/server/src/config-file.ts +17 -0
  176. package/server/src/config.ts +260 -0
  177. package/server/src/dev-server-status.ts +103 -0
  178. package/server/src/dev-watch-ignore.ts +36 -0
  179. package/server/src/errors.ts +34 -0
  180. package/server/src/home-paths.ts +95 -0
  181. package/server/src/index.ts +799 -0
  182. package/server/src/log-redaction.ts +146 -0
  183. package/server/src/middleware/auth.ts +178 -0
  184. package/server/src/middleware/board-mutation-guard.ts +66 -0
  185. package/server/src/middleware/error-handler.ts +71 -0
  186. package/server/src/middleware/index.ts +3 -0
  187. package/server/src/middleware/logger.ts +90 -0
  188. package/server/src/middleware/private-hostname-guard.ts +92 -0
  189. package/server/src/middleware/validate.ts +9 -0
  190. package/server/src/onboarding-assets/ceo/AGENTS.md +54 -0
  191. package/server/src/onboarding-assets/ceo/HEARTBEAT.md +72 -0
  192. package/server/src/onboarding-assets/ceo/SOUL.md +33 -0
  193. package/server/src/onboarding-assets/ceo/TOOLS.md +3 -0
  194. package/server/src/onboarding-assets/default/AGENTS.md +3 -0
  195. package/server/src/paths.ts +34 -0
  196. package/server/src/realtime/live-events-ws.ts +274 -0
  197. package/server/src/redaction.ts +59 -0
  198. package/server/src/routes/access.ts +2888 -0
  199. package/server/src/routes/activity.ts +89 -0
  200. package/server/src/routes/agents.ts +2313 -0
  201. package/server/src/routes/approvals.ts +346 -0
  202. package/server/src/routes/assets.ts +341 -0
  203. package/server/src/routes/authz.ts +52 -0
  204. package/server/src/routes/companies.ts +343 -0
  205. package/server/src/routes/company-skills.ts +300 -0
  206. package/server/src/routes/costs.ts +335 -0
  207. package/server/src/routes/dashboard.ts +19 -0
  208. package/server/src/routes/execution-workspaces.ts +182 -0
  209. package/server/src/routes/goals.ts +107 -0
  210. package/server/src/routes/health.ts +94 -0
  211. package/server/src/routes/index.ts +17 -0
  212. package/server/src/routes/instance-settings.ts +95 -0
  213. package/server/src/routes/issues-checkout-wakeup.ts +14 -0
  214. package/server/src/routes/issues.ts +1680 -0
  215. package/server/src/routes/llms.ts +86 -0
  216. package/server/src/routes/org-chart-svg.ts +777 -0
  217. package/server/src/routes/plugin-ui-static.ts +497 -0
  218. package/server/src/routes/plugins.ts +2220 -0
  219. package/server/src/routes/projects.ts +295 -0
  220. package/server/src/routes/routines.ts +300 -0
  221. package/server/src/routes/secrets.ts +166 -0
  222. package/server/src/routes/sidebar-badges.ts +52 -0
  223. package/server/src/secrets/external-stub-providers.ts +32 -0
  224. package/server/src/secrets/local-encrypted-provider.ts +135 -0
  225. package/server/src/secrets/provider-registry.ts +31 -0
  226. package/server/src/secrets/types.ts +23 -0
  227. package/server/src/services/access.ts +381 -0
  228. package/server/src/services/activity-log.ts +95 -0
  229. package/server/src/services/activity.ts +164 -0
  230. package/server/src/services/agent-instructions.ts +735 -0
  231. package/server/src/services/agent-permissions.ts +27 -0
  232. package/server/src/services/agents.ts +694 -0
  233. package/server/src/services/approvals.ts +273 -0
  234. package/server/src/services/assets.ts +23 -0
  235. package/server/src/services/board-auth.ts +355 -0
  236. package/server/src/services/budgets.ts +959 -0
  237. package/server/src/services/companies.ts +313 -0
  238. package/server/src/services/company-export-readme.ts +173 -0
  239. package/server/src/services/company-portability.ts +4263 -0
  240. package/server/src/services/company-skills.ts +2356 -0
  241. package/server/src/services/costs.ts +365 -0
  242. package/server/src/services/cron.ts +373 -0
  243. package/server/src/services/dashboard.ts +110 -0
  244. package/server/src/services/default-agent-instructions.ts +27 -0
  245. package/server/src/services/documents.ts +434 -0
  246. package/server/src/services/execution-workspace-policy.ts +210 -0
  247. package/server/src/services/execution-workspaces.ts +100 -0
  248. package/server/src/services/finance.ts +135 -0
  249. package/server/src/services/goals.ts +81 -0
  250. package/server/src/services/heartbeat-run-summary.ts +35 -0
  251. package/server/src/services/heartbeat.ts +3863 -0
  252. package/server/src/services/hire-hook.ts +114 -0
  253. package/server/src/services/index.ts +32 -0
  254. package/server/src/services/instance-settings.ts +138 -0
  255. package/server/src/services/issue-approvals.ts +175 -0
  256. package/server/src/services/issue-assignment-wakeup.ts +48 -0
  257. package/server/src/services/issue-goal-fallback.ts +56 -0
  258. package/server/src/services/issues.ts +1828 -0
  259. package/server/src/services/live-events.ts +55 -0
  260. package/server/src/services/plugin-capability-validator.ts +450 -0
  261. package/server/src/services/plugin-config-validator.ts +55 -0
  262. package/server/src/services/plugin-dev-watcher.ts +339 -0
  263. package/server/src/services/plugin-event-bus.ts +413 -0
  264. package/server/src/services/plugin-host-service-cleanup.ts +59 -0
  265. package/server/src/services/plugin-host-services.ts +1132 -0
  266. package/server/src/services/plugin-job-coordinator.ts +261 -0
  267. package/server/src/services/plugin-job-scheduler.ts +753 -0
  268. package/server/src/services/plugin-job-store.ts +466 -0
  269. package/server/src/services/plugin-lifecycle.ts +822 -0
  270. package/server/src/services/plugin-loader.ts +1955 -0
  271. package/server/src/services/plugin-log-retention.ts +87 -0
  272. package/server/src/services/plugin-manifest-validator.ts +164 -0
  273. package/server/src/services/plugin-registry.ts +683 -0
  274. package/server/src/services/plugin-runtime-sandbox.ts +222 -0
  275. package/server/src/services/plugin-secrets-handler.ts +355 -0
  276. package/server/src/services/plugin-state-store.ts +238 -0
  277. package/server/src/services/plugin-stream-bus.ts +81 -0
  278. package/server/src/services/plugin-tool-dispatcher.ts +449 -0
  279. package/server/src/services/plugin-tool-registry.ts +450 -0
  280. package/server/src/services/plugin-worker-manager.ts +1343 -0
  281. package/server/src/services/projects.ts +860 -0
  282. package/server/src/services/quota-windows.ts +65 -0
  283. package/server/src/services/routines.ts +1269 -0
  284. package/server/src/services/run-log-store.ts +156 -0
  285. package/server/src/services/secrets.ts +370 -0
  286. package/server/src/services/sidebar-badges.ts +56 -0
  287. package/server/src/services/work-products.ts +124 -0
  288. package/server/src/services/workspace-operation-log-store.ts +156 -0
  289. package/server/src/services/workspace-operations.ts +262 -0
  290. package/server/src/services/workspace-runtime.ts +1565 -0
  291. package/server/src/startup-banner.ts +176 -0
  292. package/server/src/storage/index.ts +35 -0
  293. package/server/src/storage/local-disk-provider.ts +89 -0
  294. package/server/src/storage/provider-registry.ts +18 -0
  295. package/server/src/storage/s3-provider.ts +153 -0
  296. package/server/src/storage/service.ts +131 -0
  297. package/server/src/storage/types.ts +63 -0
  298. package/server/src/ui-branding.ts +217 -0
  299. package/server/src/version.ts +10 -0
  300. package/server/src/worktree-config.ts +468 -0
  301. package/server/tsconfig.json +9 -0
  302. package/server/vitest.config.ts +7 -0
  303. package/skills/paperclip/SKILL.md +365 -0
  304. package/skills/paperclip/references/api-reference.md +647 -0
  305. package/skills/paperclip/references/company-skills.md +193 -0
  306. package/skills/paperclip-create-agent/SKILL.md +142 -0
  307. package/skills/paperclip-create-agent/references/api-reference.md +105 -0
  308. package/skills/paperclip-create-plugin/SKILL.md +102 -0
  309. package/skills/para-memory-files/SKILL.md +104 -0
  310. package/skills/para-memory-files/references/schemas.md +35 -0
  311. package/tests/e2e/onboarding.spec.ts +142 -0
  312. package/tests/e2e/playwright.config.ts +35 -0
  313. package/tests/release-smoke/docker-auth-onboarding.spec.ts +141 -0
  314. package/tests/release-smoke/playwright.config.ts +28 -0
  315. package/tsconfig.base.json +18 -0
  316. package/tsconfig.json +18 -0
  317. package/ui/README.md +12 -0
  318. package/ui/components.json +21 -0
  319. package/ui/index.html +47 -0
  320. package/ui/package.json +73 -0
  321. package/ui/public/android-chrome-192x192.png +0 -0
  322. package/ui/public/android-chrome-512x512.png +0 -0
  323. package/ui/public/apple-touch-icon.png +0 -0
  324. package/ui/public/brands/opencode-logo-dark-square.svg +18 -0
  325. package/ui/public/brands/opencode-logo-light-square.svg +18 -0
  326. package/ui/public/favicon-16x16.png +0 -0
  327. package/ui/public/favicon-32x32.png +0 -0
  328. package/ui/public/favicon.ico +0 -0
  329. package/ui/public/favicon.svg +9 -0
  330. package/ui/public/site.webmanifest +30 -0
  331. package/ui/public/sprites/1-D-1.png +0 -0
  332. package/ui/public/sprites/1-D-2.png +0 -0
  333. package/ui/public/sprites/1-D-3.png +0 -0
  334. package/ui/public/sprites/1-L-1.png +0 -0
  335. package/ui/public/sprites/1-R-1.png +0 -0
  336. package/ui/public/sprites/10-D-1.png +0 -0
  337. package/ui/public/sprites/10-D-2.png +0 -0
  338. package/ui/public/sprites/10-D-3.png +0 -0
  339. package/ui/public/sprites/10-L-1.png +0 -0
  340. package/ui/public/sprites/10-R-1.png +0 -0
  341. package/ui/public/sprites/11-D-1.png +0 -0
  342. package/ui/public/sprites/11-D-2.png +0 -0
  343. package/ui/public/sprites/11-D-3.png +0 -0
  344. package/ui/public/sprites/11-L-1.png +0 -0
  345. package/ui/public/sprites/11-R-1.png +0 -0
  346. package/ui/public/sprites/12-D-1.png +0 -0
  347. package/ui/public/sprites/12-D-2.png +0 -0
  348. package/ui/public/sprites/12-D-3.png +0 -0
  349. package/ui/public/sprites/12-L-1.png +0 -0
  350. package/ui/public/sprites/12-R-1.png +0 -0
  351. package/ui/public/sprites/13-D-1.png +0 -0
  352. package/ui/public/sprites/13-D-2.png +0 -0
  353. package/ui/public/sprites/13-D-3.png +0 -0
  354. package/ui/public/sprites/13-L-1.png +0 -0
  355. package/ui/public/sprites/13-R-1.png +0 -0
  356. package/ui/public/sprites/14-D-1.png +0 -0
  357. package/ui/public/sprites/14-D-2.png +0 -0
  358. package/ui/public/sprites/14-D-3.png +0 -0
  359. package/ui/public/sprites/14-L-1.png +0 -0
  360. package/ui/public/sprites/14-R-1.png +0 -0
  361. package/ui/public/sprites/2-D-1.png +0 -0
  362. package/ui/public/sprites/2-D-2.png +0 -0
  363. package/ui/public/sprites/2-D-3.png +0 -0
  364. package/ui/public/sprites/2-L-1.png +0 -0
  365. package/ui/public/sprites/2-R-1.png +0 -0
  366. package/ui/public/sprites/3-D-1.png +0 -0
  367. package/ui/public/sprites/3-D-2.png +0 -0
  368. package/ui/public/sprites/3-D-3.png +0 -0
  369. package/ui/public/sprites/3-L-1.png +0 -0
  370. package/ui/public/sprites/3-R-1.png +0 -0
  371. package/ui/public/sprites/4-D-1.png +0 -0
  372. package/ui/public/sprites/4-D-2.png +0 -0
  373. package/ui/public/sprites/4-D-3.png +0 -0
  374. package/ui/public/sprites/4-L-1.png +0 -0
  375. package/ui/public/sprites/4-R-1.png +0 -0
  376. package/ui/public/sprites/5-D-1.png +0 -0
  377. package/ui/public/sprites/5-D-2.png +0 -0
  378. package/ui/public/sprites/5-D-3.png +0 -0
  379. package/ui/public/sprites/5-L-1.png +0 -0
  380. package/ui/public/sprites/5-R-1.png +0 -0
  381. package/ui/public/sprites/6-D-1.png +0 -0
  382. package/ui/public/sprites/6-D-2.png +0 -0
  383. package/ui/public/sprites/6-D-3.png +0 -0
  384. package/ui/public/sprites/6-L-1.png +0 -0
  385. package/ui/public/sprites/6-R-1.png +0 -0
  386. package/ui/public/sprites/7-D-1.png +0 -0
  387. package/ui/public/sprites/7-D-2.png +0 -0
  388. package/ui/public/sprites/7-D-3.png +0 -0
  389. package/ui/public/sprites/7-L-1.png +0 -0
  390. package/ui/public/sprites/7-R-1.png +0 -0
  391. package/ui/public/sprites/8-D-1.png +0 -0
  392. package/ui/public/sprites/8-D-2.png +0 -0
  393. package/ui/public/sprites/8-D-3.png +0 -0
  394. package/ui/public/sprites/8-L-1.png +0 -0
  395. package/ui/public/sprites/8-R-1.png +0 -0
  396. package/ui/public/sprites/9-D-1.png +0 -0
  397. package/ui/public/sprites/9-D-2.png +0 -0
  398. package/ui/public/sprites/9-D-3.png +0 -0
  399. package/ui/public/sprites/9-L-1.png +0 -0
  400. package/ui/public/sprites/9-R-1.png +0 -0
  401. package/ui/public/sprites/ceo-lobster.png +0 -0
  402. package/ui/public/sw.js +42 -0
  403. package/ui/public/worktree-favicon-16x16.png +0 -0
  404. package/ui/public/worktree-favicon-32x32.png +0 -0
  405. package/ui/public/worktree-favicon.ico +0 -0
  406. package/ui/public/worktree-favicon.svg +9 -0
  407. package/ui/src/App.tsx +354 -0
  408. package/ui/src/adapters/claude-local/config-fields.tsx +138 -0
  409. package/ui/src/adapters/claude-local/index.ts +13 -0
  410. package/ui/src/adapters/codex-local/config-fields.tsx +104 -0
  411. package/ui/src/adapters/codex-local/index.ts +13 -0
  412. package/ui/src/adapters/cursor/config-fields.tsx +49 -0
  413. package/ui/src/adapters/cursor/index.ts +13 -0
  414. package/ui/src/adapters/gemini-local/config-fields.tsx +51 -0
  415. package/ui/src/adapters/gemini-local/index.ts +13 -0
  416. package/ui/src/adapters/http/build-config.ts +9 -0
  417. package/ui/src/adapters/http/config-fields.tsx +38 -0
  418. package/ui/src/adapters/http/index.ts +12 -0
  419. package/ui/src/adapters/http/parse-stdout.ts +5 -0
  420. package/ui/src/adapters/index.ts +9 -0
  421. package/ui/src/adapters/local-workspace-runtime-fields.tsx +5 -0
  422. package/ui/src/adapters/openclaw-gateway/config-fields.tsx +237 -0
  423. package/ui/src/adapters/openclaw-gateway/index.ts +13 -0
  424. package/ui/src/adapters/opencode-local/config-fields.tsx +72 -0
  425. package/ui/src/adapters/opencode-local/index.ts +13 -0
  426. package/ui/src/adapters/pi-local/config-fields.tsx +49 -0
  427. package/ui/src/adapters/pi-local/index.ts +13 -0
  428. package/ui/src/adapters/process/build-config.ts +18 -0
  429. package/ui/src/adapters/process/config-fields.tsx +77 -0
  430. package/ui/src/adapters/process/index.ts +12 -0
  431. package/ui/src/adapters/process/parse-stdout.ts +5 -0
  432. package/ui/src/adapters/registry.ts +34 -0
  433. package/ui/src/adapters/runtime-json-fields.tsx +122 -0
  434. package/ui/src/adapters/transcript.test.ts +30 -0
  435. package/ui/src/adapters/transcript.ts +62 -0
  436. package/ui/src/adapters/types.ts +34 -0
  437. package/ui/src/api/access.ts +160 -0
  438. package/ui/src/api/activity.ts +37 -0
  439. package/ui/src/api/agents.ts +194 -0
  440. package/ui/src/api/approvals.ts +25 -0
  441. package/ui/src/api/assets.ts +30 -0
  442. package/ui/src/api/auth.ts +74 -0
  443. package/ui/src/api/budgets.ts +21 -0
  444. package/ui/src/api/client.ts +50 -0
  445. package/ui/src/api/companies.ts +59 -0
  446. package/ui/src/api/companySkills.ts +55 -0
  447. package/ui/src/api/costs.ts +60 -0
  448. package/ui/src/api/dashboard.ts +7 -0
  449. package/ui/src/api/execution-workspaces.ts +27 -0
  450. package/ui/src/api/goals.ts +12 -0
  451. package/ui/src/api/health.ts +41 -0
  452. package/ui/src/api/heartbeats.ts +62 -0
  453. package/ui/src/api/index.ts +18 -0
  454. package/ui/src/api/instanceSettings.ts +19 -0
  455. package/ui/src/api/issues.ts +115 -0
  456. package/ui/src/api/plugins.ts +424 -0
  457. package/ui/src/api/projects.ts +34 -0
  458. package/ui/src/api/routines.ts +59 -0
  459. package/ui/src/api/secrets.ts +26 -0
  460. package/ui/src/api/sidebarBadges.ts +7 -0
  461. package/ui/src/components/AccountingModelCard.tsx +69 -0
  462. package/ui/src/components/ActiveAgentsPanel.tsx +157 -0
  463. package/ui/src/components/ActivityCharts.tsx +264 -0
  464. package/ui/src/components/ActivityRow.tsx +147 -0
  465. package/ui/src/components/AgentActionButtons.tsx +51 -0
  466. package/ui/src/components/AgentConfigForm.tsx +1468 -0
  467. package/ui/src/components/AgentIconPicker.tsx +81 -0
  468. package/ui/src/components/AgentProperties.tsx +107 -0
  469. package/ui/src/components/ApprovalCard.tsx +107 -0
  470. package/ui/src/components/ApprovalPayload.tsx +134 -0
  471. package/ui/src/components/AsciiArtAnimation.tsx +355 -0
  472. package/ui/src/components/BillerSpendCard.tsx +146 -0
  473. package/ui/src/components/BreadcrumbBar.tsx +113 -0
  474. package/ui/src/components/BudgetIncidentCard.tsx +101 -0
  475. package/ui/src/components/BudgetPolicyCard.tsx +220 -0
  476. package/ui/src/components/BudgetSidebarMarker.tsx +13 -0
  477. package/ui/src/components/ClaudeSubscriptionPanel.tsx +141 -0
  478. package/ui/src/components/CodexSubscriptionPanel.tsx +158 -0
  479. package/ui/src/components/CommandPalette.tsx +239 -0
  480. package/ui/src/components/CommentThread.tsx +503 -0
  481. package/ui/src/components/CompanyPatternIcon.tsx +212 -0
  482. package/ui/src/components/CompanyRail.tsx +329 -0
  483. package/ui/src/components/CompanySwitcher.tsx +81 -0
  484. package/ui/src/components/CopyText.tsx +56 -0
  485. package/ui/src/components/DevRestartBanner.tsx +89 -0
  486. package/ui/src/components/EmptyState.tsx +27 -0
  487. package/ui/src/components/EntityRow.tsx +69 -0
  488. package/ui/src/components/FilterBar.tsx +39 -0
  489. package/ui/src/components/FinanceBillerCard.tsx +45 -0
  490. package/ui/src/components/FinanceKindCard.tsx +44 -0
  491. package/ui/src/components/FinanceTimelineCard.tsx +72 -0
  492. package/ui/src/components/GoalProperties.tsx +165 -0
  493. package/ui/src/components/GoalTree.tsx +118 -0
  494. package/ui/src/components/Identity.tsx +39 -0
  495. package/ui/src/components/InlineEditor.tsx +248 -0
  496. package/ui/src/components/InlineEntitySelector.tsx +206 -0
  497. package/ui/src/components/InstanceSidebar.tsx +53 -0
  498. package/ui/src/components/IssueDocumentsSection.tsx +892 -0
  499. package/ui/src/components/IssueProperties.tsx +621 -0
  500. package/ui/src/components/IssueRow.tsx +149 -0
  501. package/ui/src/components/IssueWorkspaceCard.tsx +404 -0
  502. package/ui/src/components/IssuesList.tsx +889 -0
  503. package/ui/src/components/JsonSchemaForm.tsx +1048 -0
  504. package/ui/src/components/KanbanBoard.tsx +275 -0
  505. package/ui/src/components/Layout.tsx +441 -0
  506. package/ui/src/components/LiveRunWidget.tsx +160 -0
  507. package/ui/src/components/MarkdownBody.test.tsx +50 -0
  508. package/ui/src/components/MarkdownBody.tsx +152 -0
  509. package/ui/src/components/MarkdownEditor.tsx +622 -0
  510. package/ui/src/components/MetricCard.tsx +53 -0
  511. package/ui/src/components/MobileBottomNav.tsx +123 -0
  512. package/ui/src/components/NewAgentDialog.tsx +223 -0
  513. package/ui/src/components/NewGoalDialog.tsx +283 -0
  514. package/ui/src/components/NewIssueDialog.tsx +1473 -0
  515. package/ui/src/components/NewProjectDialog.tsx +451 -0
  516. package/ui/src/components/OnboardingWizard.tsx +1392 -0
  517. package/ui/src/components/OpenCodeLogoIcon.tsx +22 -0
  518. package/ui/src/components/PackageFileTree.tsx +318 -0
  519. package/ui/src/components/PageSkeleton.tsx +180 -0
  520. package/ui/src/components/PageTabBar.tsx +45 -0
  521. package/ui/src/components/PathInstructionsModal.tsx +143 -0
  522. package/ui/src/components/PriorityIcon.tsx +77 -0
  523. package/ui/src/components/ProjectProperties.tsx +1127 -0
  524. package/ui/src/components/PropertiesPanel.tsx +29 -0
  525. package/ui/src/components/ProviderQuotaCard.tsx +417 -0
  526. package/ui/src/components/QuotaBar.tsx +65 -0
  527. package/ui/src/components/ReportsToPicker.tsx +127 -0
  528. package/ui/src/components/ScheduleEditor.tsx +344 -0
  529. package/ui/src/components/ScrollToBottom.tsx +79 -0
  530. package/ui/src/components/Sidebar.tsx +130 -0
  531. package/ui/src/components/SidebarAgents.tsx +146 -0
  532. package/ui/src/components/SidebarNavItem.tsx +92 -0
  533. package/ui/src/components/SidebarProjects.tsx +234 -0
  534. package/ui/src/components/SidebarSection.tsx +17 -0
  535. package/ui/src/components/StatusBadge.tsx +15 -0
  536. package/ui/src/components/StatusIcon.tsx +71 -0
  537. package/ui/src/components/SwipeToArchive.tsx +152 -0
  538. package/ui/src/components/ToastViewport.tsx +99 -0
  539. package/ui/src/components/WorktreeBanner.tsx +25 -0
  540. package/ui/src/components/agent-config-defaults.ts +31 -0
  541. package/ui/src/components/agent-config-primitives.tsx +476 -0
  542. package/ui/src/components/transcript/RunTranscriptView.test.tsx +84 -0
  543. package/ui/src/components/transcript/RunTranscriptView.tsx +1015 -0
  544. package/ui/src/components/transcript/useLiveRunTranscripts.ts +297 -0
  545. package/ui/src/components/ui/avatar.tsx +107 -0
  546. package/ui/src/components/ui/badge.tsx +48 -0
  547. package/ui/src/components/ui/breadcrumb.tsx +109 -0
  548. package/ui/src/components/ui/button.tsx +64 -0
  549. package/ui/src/components/ui/card.tsx +92 -0
  550. package/ui/src/components/ui/checkbox.tsx +32 -0
  551. package/ui/src/components/ui/collapsible.tsx +33 -0
  552. package/ui/src/components/ui/command.tsx +194 -0
  553. package/ui/src/components/ui/dialog.tsx +156 -0
  554. package/ui/src/components/ui/dropdown-menu.tsx +257 -0
  555. package/ui/src/components/ui/input.tsx +21 -0
  556. package/ui/src/components/ui/label.tsx +22 -0
  557. package/ui/src/components/ui/popover.tsx +88 -0
  558. package/ui/src/components/ui/scroll-area.tsx +56 -0
  559. package/ui/src/components/ui/select.tsx +188 -0
  560. package/ui/src/components/ui/separator.tsx +28 -0
  561. package/ui/src/components/ui/sheet.tsx +143 -0
  562. package/ui/src/components/ui/skeleton.tsx +13 -0
  563. package/ui/src/components/ui/tabs.tsx +89 -0
  564. package/ui/src/components/ui/textarea.tsx +18 -0
  565. package/ui/src/components/ui/tooltip.tsx +57 -0
  566. package/ui/src/components/visual-office/AgentAvatar.tsx +99 -0
  567. package/ui/src/components/visual-office/OfficeViewExact.tsx +417 -0
  568. package/ui/src/components/visual-office/i18n.ts +52 -0
  569. package/ui/src/components/visual-office/office-view/CliUsagePanel.tsx +240 -0
  570. package/ui/src/components/visual-office/office-view/VirtualPadOverlay.tsx +104 -0
  571. package/ui/src/components/visual-office/office-view/buildScene-break-room.ts +248 -0
  572. package/ui/src/components/visual-office/office-view/buildScene-ceo-hallway.ts +345 -0
  573. package/ui/src/components/visual-office/office-view/buildScene-department-agent.ts +242 -0
  574. package/ui/src/components/visual-office/office-view/buildScene-departments.ts +360 -0
  575. package/ui/src/components/visual-office/office-view/buildScene-final-layers.ts +113 -0
  576. package/ui/src/components/visual-office/office-view/buildScene-types.ts +91 -0
  577. package/ui/src/components/visual-office/office-view/buildScene.ts +232 -0
  578. package/ui/src/components/visual-office/office-view/drawing-core.ts +374 -0
  579. package/ui/src/components/visual-office/office-view/drawing-furniture-a.ts +338 -0
  580. package/ui/src/components/visual-office/office-view/drawing-furniture-b.ts +241 -0
  581. package/ui/src/components/visual-office/office-view/model.ts +301 -0
  582. package/ui/src/components/visual-office/office-view/officeTicker.ts +455 -0
  583. package/ui/src/components/visual-office/office-view/officeTickerRoomAndDelivery.ts +133 -0
  584. package/ui/src/components/visual-office/office-view/themes-locale.ts +460 -0
  585. package/ui/src/components/visual-office/office-view/useCliUsage.ts +37 -0
  586. package/ui/src/components/visual-office/office-view/useOfficeDeliveryEffects.ts +465 -0
  587. package/ui/src/components/visual-office/office-view/useOfficePixiRuntime.ts +282 -0
  588. package/ui/src/components/visual-office/types.ts +123 -0
  589. package/ui/src/context/BreadcrumbContext.tsx +44 -0
  590. package/ui/src/context/CompanyContext.tsx +151 -0
  591. package/ui/src/context/DialogContext.tsx +135 -0
  592. package/ui/src/context/LiveUpdatesProvider.test.ts +119 -0
  593. package/ui/src/context/LiveUpdatesProvider.tsx +760 -0
  594. package/ui/src/context/PanelContext.tsx +73 -0
  595. package/ui/src/context/SidebarContext.tsx +43 -0
  596. package/ui/src/context/ThemeContext.tsx +83 -0
  597. package/ui/src/context/ToastContext.tsx +172 -0
  598. package/ui/src/fixtures/runTranscriptFixtures.ts +226 -0
  599. package/ui/src/hooks/useAgentOrder.ts +105 -0
  600. package/ui/src/hooks/useAutosaveIndicator.ts +72 -0
  601. package/ui/src/hooks/useCompanyPageMemory.test.ts +90 -0
  602. package/ui/src/hooks/useCompanyPageMemory.ts +79 -0
  603. package/ui/src/hooks/useDateRange.ts +120 -0
  604. package/ui/src/hooks/useInboxBadge.ts +132 -0
  605. package/ui/src/hooks/useKeyboardShortcuts.ts +40 -0
  606. package/ui/src/hooks/useProjectOrder.ts +106 -0
  607. package/ui/src/index.css +770 -0
  608. package/ui/src/lib/agent-icons.ts +99 -0
  609. package/ui/src/lib/agent-order.ts +107 -0
  610. package/ui/src/lib/agent-skills-state.test.ts +90 -0
  611. package/ui/src/lib/agent-skills-state.ts +41 -0
  612. package/ui/src/lib/assignees.test.ts +92 -0
  613. package/ui/src/lib/assignees.ts +82 -0
  614. package/ui/src/lib/color-contrast.ts +107 -0
  615. package/ui/src/lib/company-export-selection.test.ts +41 -0
  616. package/ui/src/lib/company-export-selection.ts +57 -0
  617. package/ui/src/lib/company-page-memory.ts +65 -0
  618. package/ui/src/lib/company-portability-sidebar.test.ts +101 -0
  619. package/ui/src/lib/company-portability-sidebar.ts +62 -0
  620. package/ui/src/lib/company-routes.ts +88 -0
  621. package/ui/src/lib/company-selection.test.ts +34 -0
  622. package/ui/src/lib/company-selection.ts +18 -0
  623. package/ui/src/lib/groupBy.ts +11 -0
  624. package/ui/src/lib/inbox.test.ts +404 -0
  625. package/ui/src/lib/inbox.ts +292 -0
  626. package/ui/src/lib/instance-settings.test.ts +26 -0
  627. package/ui/src/lib/instance-settings.ts +25 -0
  628. package/ui/src/lib/issueDetailBreadcrumb.ts +24 -0
  629. package/ui/src/lib/legacy-agent-config.test.ts +40 -0
  630. package/ui/src/lib/legacy-agent-config.ts +17 -0
  631. package/ui/src/lib/mention-aware-link-node.test.ts +50 -0
  632. package/ui/src/lib/mention-aware-link-node.ts +67 -0
  633. package/ui/src/lib/mention-chips.ts +168 -0
  634. package/ui/src/lib/mention-deletion.test.ts +87 -0
  635. package/ui/src/lib/mention-deletion.ts +143 -0
  636. package/ui/src/lib/model-utils.ts +16 -0
  637. package/ui/src/lib/onboarding-goal.test.ts +22 -0
  638. package/ui/src/lib/onboarding-goal.ts +18 -0
  639. package/ui/src/lib/onboarding-launch.test.ts +131 -0
  640. package/ui/src/lib/onboarding-launch.ts +54 -0
  641. package/ui/src/lib/onboarding-route.test.ts +80 -0
  642. package/ui/src/lib/onboarding-route.ts +51 -0
  643. package/ui/src/lib/portable-files.ts +42 -0
  644. package/ui/src/lib/project-order.ts +71 -0
  645. package/ui/src/lib/queryKeys.ts +140 -0
  646. package/ui/src/lib/recent-assignees.ts +36 -0
  647. package/ui/src/lib/router.tsx +76 -0
  648. package/ui/src/lib/routine-trigger-patch.test.ts +72 -0
  649. package/ui/src/lib/routine-trigger-patch.ts +31 -0
  650. package/ui/src/lib/status-colors.ts +108 -0
  651. package/ui/src/lib/timeAgo.ts +31 -0
  652. package/ui/src/lib/utils.ts +168 -0
  653. package/ui/src/lib/worktree-branding.ts +65 -0
  654. package/ui/src/lib/zip.test.ts +289 -0
  655. package/ui/src/lib/zip.ts +284 -0
  656. package/ui/src/main.tsx +67 -0
  657. package/ui/src/pages/Activity.tsx +141 -0
  658. package/ui/src/pages/AgentDetail.tsx +4053 -0
  659. package/ui/src/pages/Agents.tsx +415 -0
  660. package/ui/src/pages/ApprovalDetail.tsx +369 -0
  661. package/ui/src/pages/Approvals.tsx +132 -0
  662. package/ui/src/pages/Auth.tsx +180 -0
  663. package/ui/src/pages/BoardClaim.tsx +125 -0
  664. package/ui/src/pages/CliAuth.tsx +184 -0
  665. package/ui/src/pages/Companies.tsx +297 -0
  666. package/ui/src/pages/CompanyExport.tsx +1019 -0
  667. package/ui/src/pages/CompanyImport.tsx +1355 -0
  668. package/ui/src/pages/CompanySettings.tsx +661 -0
  669. package/ui/src/pages/CompanySkills.tsx +1171 -0
  670. package/ui/src/pages/Costs.tsx +1103 -0
  671. package/ui/src/pages/Dashboard.tsx +388 -0
  672. package/ui/src/pages/DesignGuide.tsx +1330 -0
  673. package/ui/src/pages/ExecutionWorkspaceDetail.tsx +82 -0
  674. package/ui/src/pages/GoalDetail.tsx +197 -0
  675. package/ui/src/pages/Goals.tsx +63 -0
  676. package/ui/src/pages/Inbox.tsx +1291 -0
  677. package/ui/src/pages/InstanceExperimentalSettings.tsx +139 -0
  678. package/ui/src/pages/InstanceGeneralSettings.tsx +104 -0
  679. package/ui/src/pages/InstanceSettings.tsx +284 -0
  680. package/ui/src/pages/InviteLanding.tsx +320 -0
  681. package/ui/src/pages/IssueDetail.tsx +1201 -0
  682. package/ui/src/pages/Issues.tsx +116 -0
  683. package/ui/src/pages/MyIssues.tsx +72 -0
  684. package/ui/src/pages/NewAgent.tsx +353 -0
  685. package/ui/src/pages/NotFound.tsx +66 -0
  686. package/ui/src/pages/Org.tsx +132 -0
  687. package/ui/src/pages/OrgChart.tsx +447 -0
  688. package/ui/src/pages/PluginManager.tsx +510 -0
  689. package/ui/src/pages/PluginPage.tsx +156 -0
  690. package/ui/src/pages/PluginSettings.tsx +836 -0
  691. package/ui/src/pages/ProjectDetail.tsx +633 -0
  692. package/ui/src/pages/Projects.tsx +87 -0
  693. package/ui/src/pages/RoutineDetail.tsx +1022 -0
  694. package/ui/src/pages/Routines.tsx +661 -0
  695. package/ui/src/pages/RunTranscriptUxLab.tsx +334 -0
  696. package/ui/src/pages/VisualOffice.tsx +243 -0
  697. package/ui/src/plugins/bridge-init.ts +69 -0
  698. package/ui/src/plugins/bridge.ts +476 -0
  699. package/ui/src/plugins/launchers.tsx +834 -0
  700. package/ui/src/plugins/slots.tsx +855 -0
  701. package/ui/tsconfig.json +21 -0
  702. package/ui/vite.config.ts +23 -0
  703. package/ui/vitest.config.ts +14 -0
  704. package/vitest.config.ts +11 -0
@@ -0,0 +1,954 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ log() {
5
+ echo "[openclaw-gateway-e2e] $*"
6
+ }
7
+
8
+ warn() {
9
+ echo "[openclaw-gateway-e2e] WARN: $*" >&2
10
+ }
11
+
12
+ fail() {
13
+ echo "[openclaw-gateway-e2e] ERROR: $*" >&2
14
+ exit 1
15
+ }
16
+
17
+ require_cmd() {
18
+ local cmd="$1"
19
+ command -v "$cmd" >/dev/null 2>&1 || fail "missing required command: $cmd"
20
+ }
21
+
22
+ require_cmd curl
23
+ require_cmd jq
24
+ require_cmd docker
25
+ require_cmd node
26
+ require_cmd shasum
27
+
28
+ PAPERCLIP_API_URL="${PAPERCLIP_API_URL:-http://127.0.0.1:3100}"
29
+ API_BASE="${PAPERCLIP_API_URL%/}/api"
30
+
31
+ COMPANY_SELECTOR="${COMPANY_SELECTOR:-CLA}"
32
+ OPENCLAW_AGENT_NAME="${OPENCLAW_AGENT_NAME:-OpenClaw Gateway Smoke Agent}"
33
+ OPENCLAW_GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-ws://127.0.0.1:18789}"
34
+ OPENCLAW_GATEWAY_TOKEN="${OPENCLAW_GATEWAY_TOKEN:-}"
35
+ OPENCLAW_TMP_DIR="${OPENCLAW_TMP_DIR:-${TMPDIR:-/tmp}}"
36
+ OPENCLAW_TMP_DIR="${OPENCLAW_TMP_DIR%/}"
37
+ OPENCLAW_TMP_DIR="${OPENCLAW_TMP_DIR:-/tmp}"
38
+ OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-${OPENCLAW_TMP_DIR}/openclaw-paperclip-smoke}"
39
+ OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-${OPENCLAW_CONFIG_DIR}/workspace}"
40
+ OPENCLAW_CONTAINER_NAME="${OPENCLAW_CONTAINER_NAME:-openclaw-docker-openclaw-gateway-1}"
41
+ OPENCLAW_IMAGE="${OPENCLAW_IMAGE:-openclaw:local}"
42
+ OPENCLAW_DOCKER_DIR="${OPENCLAW_DOCKER_DIR:-/tmp/openclaw-docker}"
43
+ OPENCLAW_RESET_DOCKER="${OPENCLAW_RESET_DOCKER:-1}"
44
+ OPENCLAW_BUILD="${OPENCLAW_BUILD:-1}"
45
+ OPENCLAW_WAIT_SECONDS="${OPENCLAW_WAIT_SECONDS:-60}"
46
+ OPENCLAW_RESET_STATE="${OPENCLAW_RESET_STATE:-1}"
47
+
48
+ PAPERCLIP_API_URL_FOR_OPENCLAW="${PAPERCLIP_API_URL_FOR_OPENCLAW:-http://host.docker.internal:3100}"
49
+ CASE_TIMEOUT_SEC="${CASE_TIMEOUT_SEC:-420}"
50
+ RUN_TIMEOUT_SEC="${RUN_TIMEOUT_SEC:-300}"
51
+ STRICT_CASES="${STRICT_CASES:-1}"
52
+ AUTO_INSTALL_SKILL="${AUTO_INSTALL_SKILL:-1}"
53
+ OPENCLAW_DIAG_DIR="${OPENCLAW_DIAG_DIR:-/tmp/openclaw-gateway-e2e-diag-$(date +%Y%m%d-%H%M%S)}"
54
+ OPENCLAW_ADAPTER_TIMEOUT_SEC="${OPENCLAW_ADAPTER_TIMEOUT_SEC:-120}"
55
+ OPENCLAW_ADAPTER_WAIT_TIMEOUT_MS="${OPENCLAW_ADAPTER_WAIT_TIMEOUT_MS:-120000}"
56
+ PAIRING_AUTO_APPROVE="${PAIRING_AUTO_APPROVE:-1}"
57
+ PAYLOAD_TEMPLATE_MESSAGE_APPEND="${PAYLOAD_TEMPLATE_MESSAGE_APPEND:-}"
58
+
59
+ AUTH_HEADERS=()
60
+ if [[ -n "${PAPERCLIP_AUTH_HEADER:-}" ]]; then
61
+ AUTH_HEADERS+=( -H "Authorization: ${PAPERCLIP_AUTH_HEADER}" )
62
+ fi
63
+ if [[ -n "${PAPERCLIP_COOKIE:-}" ]]; then
64
+ AUTH_HEADERS+=( -H "Cookie: ${PAPERCLIP_COOKIE}" )
65
+ PAPERCLIP_BROWSER_ORIGIN="${PAPERCLIP_BROWSER_ORIGIN:-${PAPERCLIP_API_URL%/}}"
66
+ AUTH_HEADERS+=( -H "Origin: ${PAPERCLIP_BROWSER_ORIGIN}" -H "Referer: ${PAPERCLIP_BROWSER_ORIGIN}/" )
67
+ fi
68
+
69
+ RESPONSE_CODE=""
70
+ RESPONSE_BODY=""
71
+ COMPANY_ID=""
72
+ AGENT_ID=""
73
+ AGENT_API_KEY=""
74
+ JOIN_REQUEST_ID=""
75
+ INVITE_ID=""
76
+ RUN_ID=""
77
+
78
+ CASE_A_ISSUE_ID=""
79
+ CASE_B_ISSUE_ID=""
80
+ CASE_C_ISSUE_ID=""
81
+ CASE_C_CREATED_ISSUE_ID=""
82
+
83
+ api_request() {
84
+ local method="$1"
85
+ local path="$2"
86
+ local data="${3-}"
87
+ local tmp
88
+ tmp="$(mktemp)"
89
+
90
+ local url
91
+ if [[ "$path" == http://* || "$path" == https://* ]]; then
92
+ url="$path"
93
+ elif [[ "$path" == /api/* ]]; then
94
+ url="${PAPERCLIP_API_URL%/}${path}"
95
+ else
96
+ url="${API_BASE}${path}"
97
+ fi
98
+
99
+ if [[ -n "$data" ]]; then
100
+ if (( ${#AUTH_HEADERS[@]} > 0 )); then
101
+ RESPONSE_CODE="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" "${AUTH_HEADERS[@]}" -H "Content-Type: application/json" "$url" --data "$data")"
102
+ else
103
+ RESPONSE_CODE="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" -H "Content-Type: application/json" "$url" --data "$data")"
104
+ fi
105
+ else
106
+ if (( ${#AUTH_HEADERS[@]} > 0 )); then
107
+ RESPONSE_CODE="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" "${AUTH_HEADERS[@]}" "$url")"
108
+ else
109
+ RESPONSE_CODE="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" "$url")"
110
+ fi
111
+ fi
112
+
113
+ RESPONSE_BODY="$(cat "$tmp")"
114
+ rm -f "$tmp"
115
+ }
116
+
117
+ capture_run_diagnostics() {
118
+ local run_id="$1"
119
+ local label="${2:-run}"
120
+ [[ -n "$run_id" ]] || return 0
121
+
122
+ mkdir -p "$OPENCLAW_DIAG_DIR"
123
+
124
+ api_request "GET" "/heartbeat-runs/${run_id}/events?limit=1000"
125
+ if [[ "$RESPONSE_CODE" == "200" ]]; then
126
+ printf "%s\n" "$RESPONSE_BODY" > "${OPENCLAW_DIAG_DIR}/${label}-${run_id}-events.json"
127
+ else
128
+ warn "could not fetch events for run ${run_id} (HTTP ${RESPONSE_CODE})"
129
+ fi
130
+
131
+ api_request "GET" "/heartbeat-runs/${run_id}/log?limitBytes=524288"
132
+ if [[ "$RESPONSE_CODE" == "200" ]]; then
133
+ printf "%s\n" "$RESPONSE_BODY" > "${OPENCLAW_DIAG_DIR}/${label}-${run_id}-log.json"
134
+ jq -r '.content // ""' <<<"$RESPONSE_BODY" > "${OPENCLAW_DIAG_DIR}/${label}-${run_id}-log.txt" 2>/dev/null || true
135
+ else
136
+ warn "could not fetch log for run ${run_id} (HTTP ${RESPONSE_CODE})"
137
+ fi
138
+ }
139
+
140
+ capture_issue_diagnostics() {
141
+ local issue_id="$1"
142
+ local label="${2:-issue}"
143
+ [[ -n "$issue_id" ]] || return 0
144
+ mkdir -p "$OPENCLAW_DIAG_DIR"
145
+
146
+ api_request "GET" "/issues/${issue_id}"
147
+ if [[ "$RESPONSE_CODE" == "200" ]]; then
148
+ printf "%s\n" "$RESPONSE_BODY" > "${OPENCLAW_DIAG_DIR}/${label}-${issue_id}.json"
149
+ fi
150
+
151
+ api_request "GET" "/issues/${issue_id}/comments"
152
+ if [[ "$RESPONSE_CODE" == "200" ]]; then
153
+ printf "%s\n" "$RESPONSE_BODY" > "${OPENCLAW_DIAG_DIR}/${label}-${issue_id}-comments.json"
154
+ fi
155
+ }
156
+
157
+ capture_openclaw_container_logs() {
158
+ mkdir -p "$OPENCLAW_DIAG_DIR"
159
+ local container
160
+ container="$(detect_openclaw_container || true)"
161
+ if [[ -z "$container" ]]; then
162
+ warn "could not detect OpenClaw container for diagnostics"
163
+ return 0
164
+ fi
165
+ docker logs --tail=1200 "$container" > "${OPENCLAW_DIAG_DIR}/openclaw-container.log" 2>&1 || true
166
+ }
167
+
168
+ assert_status() {
169
+ local expected="$1"
170
+ if [[ "$RESPONSE_CODE" != "$expected" ]]; then
171
+ echo "$RESPONSE_BODY" >&2
172
+ fail "expected HTTP ${expected}, got ${RESPONSE_CODE}"
173
+ fi
174
+ }
175
+
176
+ require_board_auth() {
177
+ if [[ ${#AUTH_HEADERS[@]} -eq 0 ]]; then
178
+ fail "board auth required. Set PAPERCLIP_COOKIE or PAPERCLIP_AUTH_HEADER."
179
+ fi
180
+ api_request "GET" "/companies"
181
+ if [[ "$RESPONSE_CODE" != "200" ]]; then
182
+ echo "$RESPONSE_BODY" >&2
183
+ fail "board auth invalid for /api/companies (HTTP ${RESPONSE_CODE})"
184
+ fi
185
+ }
186
+
187
+ maybe_cleanup_openclaw_docker() {
188
+ if [[ "$OPENCLAW_RESET_DOCKER" != "1" ]]; then
189
+ log "OPENCLAW_RESET_DOCKER=${OPENCLAW_RESET_DOCKER}; skipping docker cleanup"
190
+ return
191
+ fi
192
+
193
+ log "cleaning OpenClaw docker state"
194
+ if [[ -d "$OPENCLAW_DOCKER_DIR" ]]; then
195
+ docker compose -f "$OPENCLAW_DOCKER_DIR/docker-compose.yml" down --remove-orphans >/dev/null 2>&1 || true
196
+ fi
197
+ if docker ps -a --format '{{.Names}}' | grep -qx "$OPENCLAW_CONTAINER_NAME"; then
198
+ docker rm -f "$OPENCLAW_CONTAINER_NAME" >/dev/null 2>&1 || true
199
+ fi
200
+ docker image rm "$OPENCLAW_IMAGE" >/dev/null 2>&1 || true
201
+ }
202
+
203
+ start_openclaw_docker() {
204
+ log "starting clean OpenClaw docker"
205
+ OPENCLAW_CONFIG_DIR="$OPENCLAW_CONFIG_DIR" OPENCLAW_WORKSPACE_DIR="$OPENCLAW_WORKSPACE_DIR" \
206
+ OPENCLAW_RESET_STATE="$OPENCLAW_RESET_STATE" OPENCLAW_BUILD="$OPENCLAW_BUILD" OPENCLAW_WAIT_SECONDS="$OPENCLAW_WAIT_SECONDS" \
207
+ ./scripts/smoke/openclaw-docker-ui.sh
208
+ }
209
+
210
+ wait_http_ready() {
211
+ local url="$1"
212
+ local timeout_sec="$2"
213
+ local started_at now code
214
+ started_at="$(date +%s)"
215
+ while true; do
216
+ code="$(curl -sS -o /dev/null -w "%{http_code}" "$url" || true)"
217
+ if [[ "$code" == "200" ]]; then
218
+ return 0
219
+ fi
220
+ now="$(date +%s)"
221
+ if (( now - started_at >= timeout_sec )); then
222
+ return 1
223
+ fi
224
+ sleep 1
225
+ done
226
+ }
227
+
228
+ detect_openclaw_container() {
229
+ if docker ps --format '{{.Names}}' | grep -qx "$OPENCLAW_CONTAINER_NAME"; then
230
+ echo "$OPENCLAW_CONTAINER_NAME"
231
+ return 0
232
+ fi
233
+
234
+ local detected
235
+ detected="$(docker ps --format '{{.Names}}' | grep 'openclaw-gateway' | head -n1 || true)"
236
+ if [[ -n "$detected" ]]; then
237
+ echo "$detected"
238
+ return 0
239
+ fi
240
+
241
+ return 1
242
+ }
243
+
244
+ detect_gateway_token() {
245
+ if [[ -n "$OPENCLAW_GATEWAY_TOKEN" ]]; then
246
+ echo "$OPENCLAW_GATEWAY_TOKEN"
247
+ return 0
248
+ fi
249
+
250
+ local config_path
251
+ config_path="${OPENCLAW_CONFIG_DIR%/}/openclaw.json"
252
+ if [[ -f "$config_path" ]]; then
253
+ local token
254
+ token="$(jq -r '.gateway.auth.token // empty' "$config_path")"
255
+ if [[ -n "$token" ]]; then
256
+ echo "$token"
257
+ return 0
258
+ fi
259
+ fi
260
+
261
+ local container
262
+ container="$(detect_openclaw_container || true)"
263
+ if [[ -n "$container" ]]; then
264
+ local token_from_container
265
+ token_from_container="$(docker exec "$container" sh -lc "node -e 'const fs=require(\"fs\");const c=JSON.parse(fs.readFileSync(\"/home/node/.openclaw/openclaw.json\",\"utf8\"));process.stdout.write(c.gateway?.auth?.token||\"\");'" 2>/dev/null || true)"
266
+ if [[ -n "$token_from_container" ]]; then
267
+ echo "$token_from_container"
268
+ return 0
269
+ fi
270
+ fi
271
+
272
+ return 1
273
+ }
274
+
275
+ hash_prefix() {
276
+ local value="$1"
277
+ printf "%s" "$value" | shasum -a 256 | awk '{print $1}' | cut -c1-12
278
+ }
279
+
280
+ probe_gateway_ws() {
281
+ local url="$1"
282
+ local token="$2"
283
+
284
+ node - "$url" "$token" <<'NODE'
285
+ const WebSocket = require("ws");
286
+ const url = process.argv[2];
287
+ const token = process.argv[3];
288
+
289
+ const ws = new WebSocket(url, { headers: { Authorization: `Bearer ${token}` } });
290
+ const timeout = setTimeout(() => {
291
+ console.error("gateway probe timed out");
292
+ process.exit(2);
293
+ }, 8000);
294
+
295
+ ws.on("message", (raw) => {
296
+ try {
297
+ const message = JSON.parse(String(raw));
298
+ if (message?.type === "event" && message?.event === "connect.challenge") {
299
+ clearTimeout(timeout);
300
+ ws.close();
301
+ process.exit(0);
302
+ }
303
+ } catch {
304
+ // ignore
305
+ }
306
+ });
307
+
308
+ ws.on("error", (err) => {
309
+ clearTimeout(timeout);
310
+ console.error(err?.message || String(err));
311
+ process.exit(1);
312
+ });
313
+ NODE
314
+ }
315
+
316
+ resolve_company_id() {
317
+ api_request "GET" "/companies"
318
+ assert_status "200"
319
+
320
+ local selector
321
+ selector="$(printf "%s" "$COMPANY_SELECTOR" | tr '[:lower:]' '[:upper:]')"
322
+
323
+ COMPANY_ID="$(jq -r --arg sel "$selector" '
324
+ map(select(
325
+ ((.id // "") | ascii_upcase) == $sel or
326
+ ((.name // "") | ascii_upcase) == $sel or
327
+ ((.issuePrefix // "") | ascii_upcase) == $sel
328
+ ))
329
+ | .[0].id // empty
330
+ ' <<<"$RESPONSE_BODY")"
331
+
332
+ if [[ -z "$COMPANY_ID" ]]; then
333
+ local available
334
+ available="$(jq -r '.[] | "- id=\(.id) issuePrefix=\(.issuePrefix // "") name=\(.name // "")"' <<<"$RESPONSE_BODY")"
335
+ echo "$available" >&2
336
+ fail "could not find company for selector '${COMPANY_SELECTOR}'"
337
+ fi
338
+
339
+ log "resolved company ${COMPANY_ID} from selector ${COMPANY_SELECTOR}"
340
+ }
341
+
342
+ cleanup_openclaw_agents() {
343
+ api_request "GET" "/companies/${COMPANY_ID}/agents"
344
+ assert_status "200"
345
+
346
+ local ids
347
+ ids="$(jq -r '.[] | select((.adapterType == "openclaw" or .adapterType == "openclaw_gateway")) | .id' <<<"$RESPONSE_BODY")"
348
+ if [[ -z "$ids" ]]; then
349
+ log "no prior OpenClaw agents to cleanup"
350
+ return
351
+ fi
352
+
353
+ while IFS= read -r id; do
354
+ [[ -n "$id" ]] || continue
355
+ log "terminating prior OpenClaw agent ${id}"
356
+ api_request "POST" "/agents/${id}/terminate" "{}"
357
+ if [[ "$RESPONSE_CODE" != "200" && "$RESPONSE_CODE" != "404" ]]; then
358
+ warn "terminate ${id} returned HTTP ${RESPONSE_CODE}"
359
+ fi
360
+
361
+ api_request "DELETE" "/agents/${id}"
362
+ if [[ "$RESPONSE_CODE" != "200" && "$RESPONSE_CODE" != "404" ]]; then
363
+ warn "delete ${id} returned HTTP ${RESPONSE_CODE}"
364
+ fi
365
+ done <<<"$ids"
366
+ }
367
+
368
+ cleanup_pending_join_requests() {
369
+ api_request "GET" "/companies/${COMPANY_ID}/join-requests?status=pending_approval"
370
+ if [[ "$RESPONSE_CODE" != "200" ]]; then
371
+ warn "join-request cleanup skipped (HTTP ${RESPONSE_CODE})"
372
+ return
373
+ fi
374
+
375
+ local ids
376
+ ids="$(jq -r '.[] | select((.adapterType == "openclaw" or .adapterType == "openclaw_gateway")) | .id' <<<"$RESPONSE_BODY")"
377
+ if [[ -z "$ids" ]]; then
378
+ return
379
+ fi
380
+
381
+ while IFS= read -r request_id; do
382
+ [[ -n "$request_id" ]] || continue
383
+ log "rejecting stale pending join request ${request_id}"
384
+ api_request "POST" "/companies/${COMPANY_ID}/join-requests/${request_id}/reject" "{}"
385
+ if [[ "$RESPONSE_CODE" != "200" && "$RESPONSE_CODE" != "404" && "$RESPONSE_CODE" != "409" ]]; then
386
+ warn "reject ${request_id} returned HTTP ${RESPONSE_CODE}"
387
+ fi
388
+ done <<<"$ids"
389
+ }
390
+
391
+ create_and_approve_gateway_join() {
392
+ local gateway_token="$1"
393
+
394
+ local invite_payload
395
+ invite_payload="$(jq -nc '{allowedJoinTypes:"agent"}')"
396
+ api_request "POST" "/companies/${COMPANY_ID}/invites" "$invite_payload"
397
+ assert_status "201"
398
+
399
+ local invite_token
400
+ invite_token="$(jq -r '.token // empty' <<<"$RESPONSE_BODY")"
401
+ INVITE_ID="$(jq -r '.id // empty' <<<"$RESPONSE_BODY")"
402
+ [[ -n "$invite_token" && -n "$INVITE_ID" ]] || fail "invite creation missing token/id"
403
+
404
+ local join_payload
405
+ join_payload="$(jq -nc \
406
+ --arg name "$OPENCLAW_AGENT_NAME" \
407
+ --arg url "$OPENCLAW_GATEWAY_URL" \
408
+ --arg token "$gateway_token" \
409
+ --arg paperclipApiUrl "$PAPERCLIP_API_URL_FOR_OPENCLAW" \
410
+ --argjson timeoutSec "$OPENCLAW_ADAPTER_TIMEOUT_SEC" \
411
+ --argjson waitTimeoutMs "$OPENCLAW_ADAPTER_WAIT_TIMEOUT_MS" \
412
+ '{
413
+ requestType: "agent",
414
+ agentName: $name,
415
+ adapterType: "openclaw_gateway",
416
+ capabilities: "OpenClaw gateway smoke harness",
417
+ agentDefaultsPayload: {
418
+ url: $url,
419
+ headers: { "x-openclaw-token": $token },
420
+ role: "operator",
421
+ scopes: ["operator.admin"],
422
+ sessionKeyStrategy: "fixed",
423
+ sessionKey: "paperclip",
424
+ timeoutSec: $timeoutSec,
425
+ waitTimeoutMs: $waitTimeoutMs,
426
+ paperclipApiUrl: $paperclipApiUrl
427
+ }
428
+ }')"
429
+
430
+ api_request "POST" "/invites/${invite_token}/accept" "$join_payload"
431
+ assert_status "202"
432
+
433
+ JOIN_REQUEST_ID="$(jq -r '.id // empty' <<<"$RESPONSE_BODY")"
434
+ local claim_secret
435
+ claim_secret="$(jq -r '.claimSecret // empty' <<<"$RESPONSE_BODY")"
436
+ local claim_path
437
+ claim_path="$(jq -r '.claimApiKeyPath // empty' <<<"$RESPONSE_BODY")"
438
+ [[ -n "$JOIN_REQUEST_ID" && -n "$claim_secret" && -n "$claim_path" ]] || fail "join accept missing claim metadata"
439
+
440
+ log "approving join request ${JOIN_REQUEST_ID}"
441
+ api_request "POST" "/companies/${COMPANY_ID}/join-requests/${JOIN_REQUEST_ID}/approve" "{}"
442
+ assert_status "200"
443
+
444
+ AGENT_ID="$(jq -r '.createdAgentId // empty' <<<"$RESPONSE_BODY")"
445
+ [[ -n "$AGENT_ID" ]] || fail "join approval missing createdAgentId"
446
+
447
+ log "claiming one-time agent API key"
448
+ local claim_payload
449
+ claim_payload="$(jq -nc --arg secret "$claim_secret" '{claimSecret:$secret}')"
450
+ api_request "POST" "$claim_path" "$claim_payload"
451
+ assert_status "201"
452
+
453
+ AGENT_API_KEY="$(jq -r '.token // empty' <<<"$RESPONSE_BODY")"
454
+ [[ -n "$AGENT_API_KEY" ]] || fail "claim response missing token"
455
+
456
+ persist_claimed_key_artifacts "$RESPONSE_BODY"
457
+ inject_agent_api_key_payload_template
458
+ }
459
+
460
+ persist_claimed_key_artifacts() {
461
+ local claim_json="$1"
462
+ local workspace_dir="${OPENCLAW_CONFIG_DIR%/}/workspace"
463
+ local skill_dir="${OPENCLAW_CONFIG_DIR%/}/skills/paperclip"
464
+ local claimed_file="${workspace_dir}/paperclip-claimed-api-key.json"
465
+ local claimed_raw_file="${workspace_dir}/paperclip-claimed-api-key.raw.json"
466
+
467
+ mkdir -p "$workspace_dir" "$skill_dir"
468
+ local token
469
+ token="$(jq -r '.token // .apiKey // empty' <<<"$claim_json")"
470
+ [[ -n "$token" ]] || fail "claim response missing token/apiKey"
471
+
472
+ printf "%s\n" "$claim_json" > "$claimed_raw_file"
473
+ chmod 600 "$claimed_raw_file"
474
+
475
+ jq -nc --arg token "$token" '{ token: $token, apiKey: $token }' > "$claimed_file"
476
+ # Keep this readable for OpenClaw runtime users across sandbox/container contexts.
477
+ chmod 644 "$claimed_file"
478
+
479
+ local container
480
+ container="$(detect_openclaw_container || true)"
481
+ if [[ -n "$container" ]]; then
482
+ docker exec "$container" sh -lc "mkdir -p /home/node/.openclaw/workspace" >/dev/null 2>&1 || true
483
+ docker cp "$claimed_file" "${container}:/home/node/.openclaw/workspace/paperclip-claimed-api-key.json" >/dev/null 2>&1 || true
484
+ docker exec "$container" sh -lc "chmod 644 /home/node/.openclaw/workspace/paperclip-claimed-api-key.json" >/dev/null 2>&1 || true
485
+ fi
486
+
487
+ if [[ "$AUTO_INSTALL_SKILL" == "1" ]]; then
488
+ api_request "GET" "/skills/paperclip"
489
+ assert_status "200"
490
+ {
491
+ echo "PAPERCLIP_API_URL=${PAPERCLIP_API_URL_FOR_OPENCLAW}"
492
+ echo
493
+ printf "%s\n" "$RESPONSE_BODY"
494
+ } > "${skill_dir}/SKILL.md"
495
+ chmod 600 "${skill_dir}/SKILL.md"
496
+ fi
497
+
498
+ log "wrote claimed key artifacts to ${claimed_file} and ${claimed_raw_file}"
499
+ }
500
+
501
+ inject_agent_api_key_payload_template() {
502
+ [[ -n "$AGENT_ID" ]] || fail "inject_agent_api_key_payload_template requires AGENT_ID"
503
+ [[ -n "$AGENT_API_KEY" ]] || fail "inject_agent_api_key_payload_template requires AGENT_API_KEY"
504
+
505
+ api_request "GET" "/agents/${AGENT_ID}"
506
+ assert_status "200"
507
+
508
+ local base_message
509
+ base_message="Set PAPERCLIP_API_KEY=${AGENT_API_KEY} in your run context before running Paperclip heartbeat steps."
510
+ if [[ -n "$PAYLOAD_TEMPLATE_MESSAGE_APPEND" ]]; then
511
+ base_message="${base_message}\n\n${PAYLOAD_TEMPLATE_MESSAGE_APPEND}"
512
+ fi
513
+
514
+ local patch_payload
515
+ patch_payload="$(jq -c --arg message "$base_message" '
516
+ {adapterConfig: ((.adapterConfig // {}) + {
517
+ payloadTemplate: (((.adapterConfig // {}).payloadTemplate // {}) + {
518
+ message: $message
519
+ })
520
+ })}
521
+ ' <<<"$RESPONSE_BODY")"
522
+
523
+ api_request "PATCH" "/agents/${AGENT_ID}" "$patch_payload"
524
+ assert_status "200"
525
+ }
526
+
527
+ validate_joined_gateway_agent() {
528
+ local expected_gateway_token="$1"
529
+
530
+ api_request "GET" "/agents/${AGENT_ID}"
531
+ assert_status "200"
532
+
533
+ local adapter_type gateway_url configured_token disable_device_auth device_key_len
534
+ adapter_type="$(jq -r '.adapterType // empty' <<<"$RESPONSE_BODY")"
535
+ gateway_url="$(jq -r '.adapterConfig.url // empty' <<<"$RESPONSE_BODY")"
536
+ configured_token="$(jq -r '.adapterConfig.headers["x-openclaw-token"] // .adapterConfig.headers["x-openclaw-auth"] // empty' <<<"$RESPONSE_BODY")"
537
+ disable_device_auth="$(jq -r 'if .adapterConfig.disableDeviceAuth == true then "true" else "false" end' <<<"$RESPONSE_BODY")"
538
+ device_key_len="$(jq -r '(.adapterConfig.devicePrivateKeyPem // "" | length)' <<<"$RESPONSE_BODY")"
539
+
540
+ [[ "$adapter_type" == "openclaw_gateway" ]] || fail "joined agent adapterType is '${adapter_type}', expected 'openclaw_gateway'"
541
+ [[ "$gateway_url" =~ ^wss?:// ]] || fail "joined agent gateway url is invalid: '${gateway_url}'"
542
+ [[ -n "$configured_token" ]] || fail "joined agent missing adapterConfig.headers.x-openclaw-token"
543
+ if (( ${#configured_token} < 16 )); then
544
+ fail "joined agent gateway token looks too short (${#configured_token} chars)"
545
+ fi
546
+
547
+ local expected_hash configured_hash
548
+ expected_hash="$(hash_prefix "$expected_gateway_token")"
549
+ configured_hash="$(hash_prefix "$configured_token")"
550
+ if [[ "$expected_hash" != "$configured_hash" ]]; then
551
+ fail "joined agent gateway token hash mismatch (expected ${expected_hash}, got ${configured_hash})"
552
+ fi
553
+
554
+ [[ "$disable_device_auth" == "false" ]] || fail "joined agent has disableDeviceAuth=true; smoke requires device auth enabled with persistent key"
555
+ if (( device_key_len < 32 )); then
556
+ fail "joined agent missing persistent devicePrivateKeyPem (length=${device_key_len})"
557
+ fi
558
+
559
+ log "validated joined gateway agent config (token sha256 prefix ${configured_hash})"
560
+ }
561
+
562
+ run_log_contains_pairing_required() {
563
+ local run_id="$1"
564
+ api_request "GET" "/heartbeat-runs/${run_id}/log?limitBytes=262144"
565
+ if [[ "$RESPONSE_CODE" != "200" ]]; then
566
+ return 1
567
+ fi
568
+ local content
569
+ content="$(jq -r '.content // ""' <<<"$RESPONSE_BODY")"
570
+ grep -qi "pairing required" <<<"$content"
571
+ }
572
+
573
+ approve_latest_pairing_request() {
574
+ local gateway_token="$1"
575
+ local container
576
+ container="$(detect_openclaw_container || true)"
577
+ [[ -n "$container" ]] || return 1
578
+
579
+ log "approving latest gateway pairing request in ${container}"
580
+ local output
581
+ if output="$(docker exec \
582
+ -e OPENCLAW_GATEWAY_URL="$OPENCLAW_GATEWAY_URL" \
583
+ -e OPENCLAW_GATEWAY_TOKEN="$gateway_token" \
584
+ "$container" \
585
+ sh -lc 'openclaw devices approve --latest --json --url "$OPENCLAW_GATEWAY_URL" --token "$OPENCLAW_GATEWAY_TOKEN"' 2>&1)"; then
586
+ log "pairing approval response: $(printf "%s" "$output" | tr '\n' ' ' | cut -c1-400)"
587
+ return 0
588
+ fi
589
+
590
+ warn "pairing auto-approve failed: $(printf "%s" "$output" | tr '\n' ' ' | cut -c1-400)"
591
+ return 1
592
+ }
593
+
594
+ trigger_wakeup() {
595
+ local reason="$1"
596
+ local issue_id="${2:-}"
597
+
598
+ local payload
599
+ if [[ -n "$issue_id" ]]; then
600
+ payload="$(jq -nc --arg issueId "$issue_id" --arg reason "$reason" '{source:"on_demand",triggerDetail:"manual",reason:$reason,payload:{issueId:$issueId,taskId:$issueId}}')"
601
+ else
602
+ payload="$(jq -nc --arg reason "$reason" '{source:"on_demand",triggerDetail:"manual",reason:$reason}')"
603
+ fi
604
+
605
+ api_request "POST" "/agents/${AGENT_ID}/wakeup" "$payload"
606
+ if [[ "$RESPONSE_CODE" != "202" ]]; then
607
+ echo "$RESPONSE_BODY" >&2
608
+ fail "wakeup failed (HTTP ${RESPONSE_CODE})"
609
+ fi
610
+
611
+ RUN_ID="$(jq -r '.id // empty' <<<"$RESPONSE_BODY")"
612
+ if [[ -z "$RUN_ID" ]]; then
613
+ warn "wakeup response did not include run id; body: ${RESPONSE_BODY}"
614
+ fi
615
+ }
616
+
617
+ get_run_status() {
618
+ local run_id="$1"
619
+ api_request "GET" "/companies/${COMPANY_ID}/heartbeat-runs?agentId=${AGENT_ID}&limit=200"
620
+ if [[ "$RESPONSE_CODE" != "200" ]]; then
621
+ echo ""
622
+ return 0
623
+ fi
624
+ jq -r --arg runId "$run_id" '.[] | select(.id == $runId) | .status' <<<"$RESPONSE_BODY" | head -n1
625
+ }
626
+
627
+ wait_for_run_terminal() {
628
+ local run_id="$1"
629
+ local timeout_sec="$2"
630
+ local started now status
631
+
632
+ [[ -n "$run_id" ]] || fail "wait_for_run_terminal requires run id"
633
+ started="$(date +%s)"
634
+
635
+ while true; do
636
+ status="$(get_run_status "$run_id")"
637
+ if [[ "$status" == "succeeded" || "$status" == "failed" || "$status" == "timed_out" || "$status" == "cancelled" ]]; then
638
+ if [[ "$status" != "succeeded" ]]; then
639
+ capture_run_diagnostics "$run_id" "run-nonsuccess"
640
+ capture_openclaw_container_logs
641
+ fi
642
+ echo "$status"
643
+ return 0
644
+ fi
645
+
646
+ now="$(date +%s)"
647
+ if (( now - started >= timeout_sec )); then
648
+ capture_run_diagnostics "$run_id" "run-timeout"
649
+ capture_openclaw_container_logs
650
+ echo "timeout"
651
+ return 0
652
+ fi
653
+ sleep 2
654
+ done
655
+ }
656
+
657
+ get_issue_status() {
658
+ local issue_id="$1"
659
+ api_request "GET" "/issues/${issue_id}"
660
+ if [[ "$RESPONSE_CODE" != "200" ]]; then
661
+ echo ""
662
+ return 0
663
+ fi
664
+ jq -r '.status // empty' <<<"$RESPONSE_BODY"
665
+ }
666
+
667
+ wait_for_issue_terminal() {
668
+ local issue_id="$1"
669
+ local timeout_sec="$2"
670
+ local started now status
671
+ started="$(date +%s)"
672
+
673
+ while true; do
674
+ status="$(get_issue_status "$issue_id")"
675
+ if [[ "$status" == "done" || "$status" == "blocked" || "$status" == "cancelled" ]]; then
676
+ echo "$status"
677
+ return 0
678
+ fi
679
+
680
+ now="$(date +%s)"
681
+ if (( now - started >= timeout_sec )); then
682
+ echo "timeout"
683
+ return 0
684
+ fi
685
+ sleep 3
686
+ done
687
+ }
688
+
689
+ issue_comments_contain() {
690
+ local issue_id="$1"
691
+ local marker="$2"
692
+ api_request "GET" "/issues/${issue_id}/comments"
693
+ if [[ "$RESPONSE_CODE" != "200" ]]; then
694
+ echo "false"
695
+ return 0
696
+ fi
697
+ jq -r --arg marker "$marker" '[.[] | (.body // "") | contains($marker)] | any' <<<"$RESPONSE_BODY"
698
+ }
699
+
700
+ create_issue_for_case() {
701
+ local title="$1"
702
+ local description="$2"
703
+ local priority="${3:-high}"
704
+
705
+ local payload
706
+ payload="$(jq -nc \
707
+ --arg title "$title" \
708
+ --arg description "$description" \
709
+ --arg assignee "$AGENT_ID" \
710
+ --arg priority "$priority" \
711
+ '{title:$title,description:$description,status:"todo",priority:$priority,assigneeAgentId:$assignee}')"
712
+
713
+ api_request "POST" "/companies/${COMPANY_ID}/issues" "$payload"
714
+ assert_status "201"
715
+
716
+ local issue_id issue_identifier
717
+ issue_id="$(jq -r '.id // empty' <<<"$RESPONSE_BODY")"
718
+ issue_identifier="$(jq -r '.identifier // empty' <<<"$RESPONSE_BODY")"
719
+ [[ -n "$issue_id" ]] || fail "issue create missing id"
720
+
721
+ echo "${issue_id}|${issue_identifier}"
722
+ }
723
+
724
+ patch_agent_session_strategy_run() {
725
+ api_request "GET" "/agents/${AGENT_ID}"
726
+ assert_status "200"
727
+
728
+ local patch_payload
729
+ patch_payload="$(jq -c '{adapterConfig: ((.adapterConfig // {}) + {sessionKeyStrategy:"run"})}' <<<"$RESPONSE_BODY")"
730
+ api_request "PATCH" "/agents/${AGENT_ID}" "$patch_payload"
731
+ assert_status "200"
732
+ }
733
+
734
+ find_issue_by_query() {
735
+ local query="$1"
736
+ local encoded_query
737
+ encoded_query="$(jq -rn --arg q "$query" '$q|@uri')"
738
+ api_request "GET" "/companies/${COMPANY_ID}/issues?q=${encoded_query}"
739
+ if [[ "$RESPONSE_CODE" != "200" ]]; then
740
+ echo ""
741
+ return 0
742
+ fi
743
+ jq -r '.[] | .id' <<<"$RESPONSE_BODY" | head -n1
744
+ }
745
+
746
+ run_case_a() {
747
+ local marker="OPENCLAW_CASE_A_OK_$(date +%s)"
748
+ local description
749
+ description="Case A validation.\n\n1) Read this issue.\n2) Post a comment containing exactly: ${marker}\n3) Mark this issue done."
750
+
751
+ local created
752
+ created="$(create_issue_for_case "[OpenClaw Gateway Smoke] Case A" "$description")"
753
+ CASE_A_ISSUE_ID="${created%%|*}"
754
+ local case_identifier="${created##*|}"
755
+
756
+ log "case A issue ${CASE_A_ISSUE_ID} (${case_identifier})"
757
+ trigger_wakeup "openclaw_gateway_smoke_case_a" "$CASE_A_ISSUE_ID"
758
+
759
+ local run_status issue_status marker_found
760
+ if [[ -n "$RUN_ID" ]]; then
761
+ run_status="$(wait_for_run_terminal "$RUN_ID" "$RUN_TIMEOUT_SEC")"
762
+ log "case A run ${RUN_ID} status=${run_status}"
763
+ else
764
+ run_status="unknown"
765
+ fi
766
+
767
+ issue_status="$(wait_for_issue_terminal "$CASE_A_ISSUE_ID" "$CASE_TIMEOUT_SEC")"
768
+ marker_found="$(issue_comments_contain "$CASE_A_ISSUE_ID" "$marker")"
769
+ log "case A issue_status=${issue_status} marker_found=${marker_found}"
770
+
771
+ if [[ "$issue_status" != "done" || "$marker_found" != "true" ]]; then
772
+ capture_issue_diagnostics "$CASE_A_ISSUE_ID" "case-a"
773
+ if [[ -n "$RUN_ID" ]]; then
774
+ capture_run_diagnostics "$RUN_ID" "case-a"
775
+ fi
776
+ capture_openclaw_container_logs
777
+ fi
778
+
779
+ if [[ "$STRICT_CASES" == "1" ]]; then
780
+ [[ "$run_status" == "succeeded" ]] || fail "case A run did not succeed"
781
+ [[ "$issue_status" == "done" ]] || fail "case A issue did not reach done"
782
+ [[ "$marker_found" == "true" ]] || fail "case A marker not found in comments"
783
+ fi
784
+ }
785
+
786
+ run_case_b() {
787
+ local marker="OPENCLAW_CASE_B_OK_$(date +%s)"
788
+ local message_text="${marker}"
789
+ local description
790
+ description="Case B validation.\n\nUse the message tool to send this exact text to the user's main chat session in webchat:\n${message_text}\n\nAfter sending, post a Paperclip issue comment containing exactly: ${marker}\nThen mark this issue done."
791
+
792
+ local created
793
+ created="$(create_issue_for_case "[OpenClaw Gateway Smoke] Case B" "$description")"
794
+ CASE_B_ISSUE_ID="${created%%|*}"
795
+ local case_identifier="${created##*|}"
796
+
797
+ log "case B issue ${CASE_B_ISSUE_ID} (${case_identifier})"
798
+ trigger_wakeup "openclaw_gateway_smoke_case_b" "$CASE_B_ISSUE_ID"
799
+
800
+ local run_status issue_status marker_found
801
+ if [[ -n "$RUN_ID" ]]; then
802
+ run_status="$(wait_for_run_terminal "$RUN_ID" "$RUN_TIMEOUT_SEC")"
803
+ log "case B run ${RUN_ID} status=${run_status}"
804
+ else
805
+ run_status="unknown"
806
+ fi
807
+
808
+ issue_status="$(wait_for_issue_terminal "$CASE_B_ISSUE_ID" "$CASE_TIMEOUT_SEC")"
809
+ marker_found="$(issue_comments_contain "$CASE_B_ISSUE_ID" "$marker")"
810
+ log "case B issue_status=${issue_status} marker_found=${marker_found}"
811
+
812
+ if [[ "$issue_status" != "done" || "$marker_found" != "true" ]]; then
813
+ capture_issue_diagnostics "$CASE_B_ISSUE_ID" "case-b"
814
+ if [[ -n "$RUN_ID" ]]; then
815
+ capture_run_diagnostics "$RUN_ID" "case-b"
816
+ fi
817
+ capture_openclaw_container_logs
818
+ fi
819
+
820
+ warn "case B requires manual UX confirmation in OpenClaw main webchat: message '${message_text}' appears in main chat"
821
+
822
+ if [[ "$STRICT_CASES" == "1" ]]; then
823
+ [[ "$run_status" == "succeeded" ]] || fail "case B run did not succeed"
824
+ [[ "$issue_status" == "done" ]] || fail "case B issue did not reach done"
825
+ [[ "$marker_found" == "true" ]] || fail "case B marker not found in comments"
826
+ fi
827
+ }
828
+
829
+ run_case_c() {
830
+ patch_agent_session_strategy_run
831
+
832
+ local marker="OPENCLAW_CASE_C_CREATED_$(date +%s)"
833
+ local ack_marker="OPENCLAW_CASE_C_ACK_$(date +%s)"
834
+ local original_issue_reference="the original case issue you are currently reading"
835
+ local description
836
+ description="Case C validation.\n\nTreat this run as a fresh/new session.\nCreate a NEW Paperclip issue in this same company with title exactly:\n${marker}\nUse description: 'created by case C smoke'.\n\nThen post a comment on ${original_issue_reference} containing exactly: ${ack_marker}\nDo NOT post the ACK comment on the newly created issue.\nThen mark the original case issue done."
837
+
838
+ local created
839
+ created="$(create_issue_for_case "[OpenClaw Gateway Smoke] Case C" "$description")"
840
+ CASE_C_ISSUE_ID="${created%%|*}"
841
+ local case_identifier="${created##*|}"
842
+
843
+ log "case C issue ${CASE_C_ISSUE_ID} (${case_identifier})"
844
+ trigger_wakeup "openclaw_gateway_smoke_case_c" "$CASE_C_ISSUE_ID"
845
+
846
+ local run_status issue_status marker_found created_issue
847
+ if [[ -n "$RUN_ID" ]]; then
848
+ run_status="$(wait_for_run_terminal "$RUN_ID" "$RUN_TIMEOUT_SEC")"
849
+ log "case C run ${RUN_ID} status=${run_status}"
850
+ else
851
+ run_status="unknown"
852
+ fi
853
+
854
+ issue_status="$(wait_for_issue_terminal "$CASE_C_ISSUE_ID" "$CASE_TIMEOUT_SEC")"
855
+ marker_found="$(issue_comments_contain "$CASE_C_ISSUE_ID" "$ack_marker")"
856
+ created_issue="$(find_issue_by_query "$marker")"
857
+ if [[ "$created_issue" == "$CASE_C_ISSUE_ID" ]]; then
858
+ created_issue=""
859
+ fi
860
+ CASE_C_CREATED_ISSUE_ID="$created_issue"
861
+ log "case C issue_status=${issue_status} marker_found=${marker_found} created_issue_id=${CASE_C_CREATED_ISSUE_ID:-none}"
862
+
863
+ if [[ "$issue_status" != "done" || "$marker_found" != "true" || -z "$CASE_C_CREATED_ISSUE_ID" ]]; then
864
+ capture_issue_diagnostics "$CASE_C_ISSUE_ID" "case-c"
865
+ if [[ -n "$CASE_C_CREATED_ISSUE_ID" ]]; then
866
+ capture_issue_diagnostics "$CASE_C_CREATED_ISSUE_ID" "case-c-created"
867
+ fi
868
+ if [[ -n "$RUN_ID" ]]; then
869
+ capture_run_diagnostics "$RUN_ID" "case-c"
870
+ fi
871
+ capture_openclaw_container_logs
872
+ fi
873
+
874
+ if [[ "$STRICT_CASES" == "1" ]]; then
875
+ [[ "$run_status" == "succeeded" ]] || fail "case C run did not succeed"
876
+ [[ "$issue_status" == "done" ]] || fail "case C issue did not reach done"
877
+ [[ "$marker_found" == "true" ]] || fail "case C ack marker not found in comments"
878
+ [[ -n "$CASE_C_CREATED_ISSUE_ID" ]] || fail "case C did not create the expected new issue"
879
+ fi
880
+ }
881
+
882
+ main() {
883
+ log "starting OpenClaw gateway E2E smoke"
884
+ mkdir -p "$OPENCLAW_DIAG_DIR"
885
+ log "diagnostics dir: ${OPENCLAW_DIAG_DIR}"
886
+
887
+ wait_http_ready "${PAPERCLIP_API_URL%/}/api/health" 15 || fail "Paperclip API health endpoint not reachable"
888
+ api_request "GET" "/health"
889
+ assert_status "200"
890
+ log "paperclip health deploymentMode=$(jq -r '.deploymentMode // "unknown"' <<<"$RESPONSE_BODY") exposure=$(jq -r '.deploymentExposure // "unknown"' <<<"$RESPONSE_BODY")"
891
+
892
+ require_board_auth
893
+ resolve_company_id
894
+ cleanup_openclaw_agents
895
+ cleanup_pending_join_requests
896
+
897
+ maybe_cleanup_openclaw_docker
898
+ start_openclaw_docker
899
+ wait_http_ready "http://127.0.0.1:18789/" "$OPENCLAW_WAIT_SECONDS" || fail "OpenClaw HTTP health not reachable"
900
+
901
+ local gateway_token
902
+ gateway_token="$(detect_gateway_token || true)"
903
+ [[ -n "$gateway_token" ]] || fail "could not resolve OpenClaw gateway token"
904
+ log "resolved gateway token (sha256 prefix $(hash_prefix "$gateway_token"))"
905
+
906
+ log "probing gateway websocket challenge at ${OPENCLAW_GATEWAY_URL}"
907
+ probe_gateway_ws "$OPENCLAW_GATEWAY_URL" "$gateway_token"
908
+
909
+ create_and_approve_gateway_join "$gateway_token"
910
+ log "joined/approved agent ${AGENT_ID} invite=${INVITE_ID} joinRequest=${JOIN_REQUEST_ID}"
911
+ validate_joined_gateway_agent "$gateway_token"
912
+
913
+ local connect_status="unknown"
914
+ local connect_attempt
915
+ for connect_attempt in 1 2; do
916
+ trigger_wakeup "openclaw_gateway_smoke_connectivity_attempt_${connect_attempt}"
917
+ if [[ -z "$RUN_ID" ]]; then
918
+ connect_status="unknown"
919
+ break
920
+ fi
921
+ connect_status="$(wait_for_run_terminal "$RUN_ID" "$RUN_TIMEOUT_SEC")"
922
+ if [[ "$connect_status" == "succeeded" ]]; then
923
+ log "connectivity wake run ${RUN_ID} succeeded (attempt=${connect_attempt})"
924
+ break
925
+ fi
926
+
927
+ if [[ "$PAIRING_AUTO_APPROVE" == "1" && "$connect_attempt" -eq 1 ]] && run_log_contains_pairing_required "$RUN_ID"; then
928
+ log "connectivity run hit pairing gate; attempting one-time pairing approval"
929
+ approve_latest_pairing_request "$gateway_token" || fail "pairing approval failed after pairing-required run ${RUN_ID}"
930
+ sleep 2
931
+ continue
932
+ fi
933
+
934
+ fail "connectivity wake run failed: ${connect_status} (attempt=${connect_attempt}, runId=${RUN_ID})"
935
+ done
936
+ [[ "$connect_status" == "succeeded" ]] || fail "connectivity wake run did not succeed after retries"
937
+
938
+ run_case_a
939
+ run_case_b
940
+ run_case_c
941
+
942
+ log "success"
943
+ log "companyId=${COMPANY_ID}"
944
+ log "agentId=${AGENT_ID}"
945
+ log "inviteId=${INVITE_ID}"
946
+ log "joinRequestId=${JOIN_REQUEST_ID}"
947
+ log "caseA_issueId=${CASE_A_ISSUE_ID}"
948
+ log "caseB_issueId=${CASE_B_ISSUE_ID}"
949
+ log "caseC_issueId=${CASE_C_ISSUE_ID}"
950
+ log "caseC_createdIssueId=${CASE_C_CREATED_ISSUE_ID:-none}"
951
+ log "agentApiKeyPrefix=${AGENT_API_KEY:0:12}..."
952
+ }
953
+
954
+ main "$@"