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,1392 @@
1
+ import { useEffect, useState, useRef, useCallback, useMemo } from "react";
2
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
3
+ import type { AdapterEnvironmentTestResult } from "@corporateai/shared";
4
+ import { useLocation, useNavigate, useParams } from "@/lib/router";
5
+ import { useDialog } from "../context/DialogContext";
6
+ import { useCompany } from "../context/CompanyContext";
7
+ import { companiesApi } from "../api/companies";
8
+ import { goalsApi } from "../api/goals";
9
+ import { agentsApi } from "../api/agents";
10
+ import { issuesApi } from "../api/issues";
11
+ import { projectsApi } from "../api/projects";
12
+ import { queryKeys } from "../lib/queryKeys";
13
+ import { Dialog, DialogPortal } from "@/components/ui/dialog";
14
+ import {
15
+ Popover,
16
+ PopoverContent,
17
+ PopoverTrigger
18
+ } from "@/components/ui/popover";
19
+ import { Button } from "@/components/ui/button";
20
+ import { cn } from "../lib/utils";
21
+ import {
22
+ extractModelName,
23
+ extractProviderIdWithFallback
24
+ } from "../lib/model-utils";
25
+ import { getUIAdapter } from "../adapters";
26
+ import { defaultCreateValues } from "./agent-config-defaults";
27
+ import { parseOnboardingGoalInput } from "../lib/onboarding-goal";
28
+ import {
29
+ buildOnboardingIssuePayload,
30
+ buildOnboardingProjectPayload,
31
+ selectDefaultCompanyGoalId
32
+ } from "../lib/onboarding-launch";
33
+ import {
34
+ DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX,
35
+ DEFAULT_CODEX_LOCAL_MODEL
36
+ } from "@corporateai/adapter-codex-local";
37
+ import { DEFAULT_CURSOR_LOCAL_MODEL } from "@corporateai/adapter-cursor-local";
38
+ import { DEFAULT_GEMINI_LOCAL_MODEL } from "@corporateai/adapter-gemini-local";
39
+ import { resolveRouteOnboardingOptions } from "../lib/onboarding-route";
40
+ import { AsciiArtAnimation } from "./AsciiArtAnimation";
41
+ import { OpenCodeLogoIcon } from "./OpenCodeLogoIcon";
42
+ import {
43
+ Building2,
44
+ Bot,
45
+ Code,
46
+ Gem,
47
+ ListTodo,
48
+ Rocket,
49
+ ArrowLeft,
50
+ ArrowRight,
51
+ Terminal,
52
+ Sparkles,
53
+ MousePointer2,
54
+ Check,
55
+ Loader2,
56
+ ChevronDown,
57
+ X
58
+ } from "lucide-react";
59
+
60
+ type Step = 1 | 2 | 3 | 4;
61
+ type AdapterType =
62
+ | "claude_local"
63
+ | "codex_local"
64
+ | "gemini_local"
65
+ | "opencode_local"
66
+ | "pi_local"
67
+ | "cursor"
68
+ | "http"
69
+ | "openclaw_gateway";
70
+
71
+ const DEFAULT_TASK_DESCRIPTION = `You are the CEO. You set the direction for the company.
72
+
73
+ - hire a founding engineer
74
+ - write a hiring plan
75
+ - break the roadmap into concrete tasks and start delegating work`;
76
+
77
+ export function OnboardingWizard() {
78
+ const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog();
79
+ const { companies, setSelectedCompanyId, loading: companiesLoading } = useCompany();
80
+ const queryClient = useQueryClient();
81
+ const navigate = useNavigate();
82
+ const location = useLocation();
83
+ const { companyPrefix } = useParams<{ companyPrefix?: string }>();
84
+ const [routeDismissed, setRouteDismissed] = useState(false);
85
+
86
+ const routeOnboardingOptions =
87
+ companyPrefix && companiesLoading
88
+ ? null
89
+ : resolveRouteOnboardingOptions({
90
+ pathname: location.pathname,
91
+ companyPrefix,
92
+ companies,
93
+ });
94
+ const effectiveOnboardingOpen =
95
+ onboardingOpen || (routeOnboardingOptions !== null && !routeDismissed);
96
+ const effectiveOnboardingOptions = onboardingOpen
97
+ ? onboardingOptions
98
+ : routeOnboardingOptions ?? {};
99
+
100
+ const initialStep = effectiveOnboardingOptions.initialStep ?? 1;
101
+ const existingCompanyId = effectiveOnboardingOptions.companyId;
102
+
103
+ const [step, setStep] = useState<Step>(initialStep);
104
+ const [loading, setLoading] = useState(false);
105
+ const [error, setError] = useState<string | null>(null);
106
+ const [modelOpen, setModelOpen] = useState(false);
107
+ const [modelSearch, setModelSearch] = useState("");
108
+
109
+ // Step 1
110
+ const [companyName, setCompanyName] = useState("");
111
+ const [companyGoal, setCompanyGoal] = useState("");
112
+
113
+ // Step 2
114
+ const [agentName, setAgentName] = useState("CEO");
115
+ const [adapterType, setAdapterType] = useState<AdapterType>("claude_local");
116
+ const [model, setModel] = useState("");
117
+ const [command, setCommand] = useState("");
118
+ const [args, setArgs] = useState("");
119
+ const [url, setUrl] = useState("");
120
+ const [adapterEnvResult, setAdapterEnvResult] =
121
+ useState<AdapterEnvironmentTestResult | null>(null);
122
+ const [adapterEnvError, setAdapterEnvError] = useState<string | null>(null);
123
+ const [adapterEnvLoading, setAdapterEnvLoading] = useState(false);
124
+ const [forceUnsetAnthropicApiKey, setForceUnsetAnthropicApiKey] =
125
+ useState(false);
126
+ const [unsetAnthropicLoading, setUnsetAnthropicLoading] = useState(false);
127
+ const [showMoreAdapters, setShowMoreAdapters] = useState(false);
128
+
129
+ // Step 3
130
+ const [taskTitle, setTaskTitle] = useState(
131
+ "Hire your first engineer and create a hiring plan"
132
+ );
133
+ const [taskDescription, setTaskDescription] = useState(
134
+ DEFAULT_TASK_DESCRIPTION
135
+ );
136
+
137
+ // Auto-grow textarea for task description
138
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
139
+ const autoResizeTextarea = useCallback(() => {
140
+ const el = textareaRef.current;
141
+ if (!el) return;
142
+ el.style.height = "auto";
143
+ el.style.height = el.scrollHeight + "px";
144
+ }, []);
145
+
146
+ // Created entity IDs — pre-populate from existing company when skipping step 1
147
+ const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(
148
+ existingCompanyId ?? null
149
+ );
150
+ const [createdCompanyPrefix, setCreatedCompanyPrefix] = useState<
151
+ string | null
152
+ >(null);
153
+ const [createdCompanyGoalId, setCreatedCompanyGoalId] = useState<string | null>(
154
+ null
155
+ );
156
+ const [createdAgentId, setCreatedAgentId] = useState<string | null>(null);
157
+ const [createdProjectId, setCreatedProjectId] = useState<string | null>(null);
158
+ const [createdIssueRef, setCreatedIssueRef] = useState<string | null>(null);
159
+
160
+ useEffect(() => {
161
+ setRouteDismissed(false);
162
+ }, [location.pathname]);
163
+
164
+ // Sync step and company when onboarding opens with options.
165
+ // Keep this independent from company-list refreshes so Step 1 completion
166
+ // doesn't get reset after creating a company.
167
+ useEffect(() => {
168
+ if (!effectiveOnboardingOpen) return;
169
+ const cId = effectiveOnboardingOptions.companyId ?? null;
170
+ setStep(effectiveOnboardingOptions.initialStep ?? 1);
171
+ setCreatedCompanyId(cId);
172
+ setCreatedCompanyPrefix(null);
173
+ setCreatedCompanyGoalId(null);
174
+ setCreatedProjectId(null);
175
+ setCreatedAgentId(null);
176
+ setCreatedIssueRef(null);
177
+ }, [
178
+ effectiveOnboardingOpen,
179
+ effectiveOnboardingOptions.companyId,
180
+ effectiveOnboardingOptions.initialStep
181
+ ]);
182
+
183
+ // Backfill issue prefix for an existing company once companies are loaded.
184
+ useEffect(() => {
185
+ if (!effectiveOnboardingOpen || !createdCompanyId || createdCompanyPrefix) return;
186
+ const company = companies.find((c) => c.id === createdCompanyId);
187
+ if (company) setCreatedCompanyPrefix(company.issuePrefix);
188
+ }, [effectiveOnboardingOpen, createdCompanyId, createdCompanyPrefix, companies]);
189
+
190
+ // Resize textarea when step 3 is shown or description changes
191
+ useEffect(() => {
192
+ if (step === 3) autoResizeTextarea();
193
+ }, [step, taskDescription, autoResizeTextarea]);
194
+
195
+ const {
196
+ data: adapterModels,
197
+ error: adapterModelsError,
198
+ isLoading: adapterModelsLoading,
199
+ isFetching: adapterModelsFetching
200
+ } = useQuery({
201
+ queryKey: createdCompanyId
202
+ ? queryKeys.agents.adapterModels(createdCompanyId, adapterType)
203
+ : ["agents", "none", "adapter-models", adapterType],
204
+ queryFn: () => agentsApi.adapterModels(createdCompanyId!, adapterType),
205
+ enabled: Boolean(createdCompanyId) && effectiveOnboardingOpen && step === 2
206
+ });
207
+ const isLocalAdapter =
208
+ adapterType === "claude_local" ||
209
+ adapterType === "codex_local" ||
210
+ adapterType === "gemini_local" ||
211
+ adapterType === "opencode_local" ||
212
+ adapterType === "pi_local" ||
213
+ adapterType === "cursor";
214
+ const effectiveAdapterCommand =
215
+ command.trim() ||
216
+ (adapterType === "codex_local"
217
+ ? "codex"
218
+ : adapterType === "gemini_local"
219
+ ? "gemini"
220
+ : adapterType === "pi_local"
221
+ ? "pi"
222
+ : adapterType === "cursor"
223
+ ? "agent"
224
+ : adapterType === "opencode_local"
225
+ ? "opencode"
226
+ : "claude");
227
+
228
+ useEffect(() => {
229
+ if (step !== 2) return;
230
+ setAdapterEnvResult(null);
231
+ setAdapterEnvError(null);
232
+ }, [step, adapterType, model, command, args, url]);
233
+
234
+ const selectedModel = (adapterModels ?? []).find((m) => m.id === model);
235
+ const hasAnthropicApiKeyOverrideCheck =
236
+ adapterEnvResult?.checks.some(
237
+ (check) =>
238
+ check.code === "claude_anthropic_api_key_overrides_subscription"
239
+ ) ?? false;
240
+ const shouldSuggestUnsetAnthropicApiKey =
241
+ adapterType === "claude_local" &&
242
+ adapterEnvResult?.status === "fail" &&
243
+ hasAnthropicApiKeyOverrideCheck;
244
+ const filteredModels = useMemo(() => {
245
+ const query = modelSearch.trim().toLowerCase();
246
+ return (adapterModels ?? []).filter((entry) => {
247
+ if (!query) return true;
248
+ const provider = extractProviderIdWithFallback(entry.id, "");
249
+ return (
250
+ entry.id.toLowerCase().includes(query) ||
251
+ entry.label.toLowerCase().includes(query) ||
252
+ provider.toLowerCase().includes(query)
253
+ );
254
+ });
255
+ }, [adapterModels, modelSearch]);
256
+ const groupedModels = useMemo(() => {
257
+ if (adapterType !== "opencode_local") {
258
+ return [
259
+ {
260
+ provider: "models",
261
+ entries: [...filteredModels].sort((a, b) => a.id.localeCompare(b.id))
262
+ }
263
+ ];
264
+ }
265
+ const groups = new Map<string, Array<{ id: string; label: string }>>();
266
+ for (const entry of filteredModels) {
267
+ const provider = extractProviderIdWithFallback(entry.id);
268
+ const bucket = groups.get(provider) ?? [];
269
+ bucket.push(entry);
270
+ groups.set(provider, bucket);
271
+ }
272
+ return Array.from(groups.entries())
273
+ .sort(([a], [b]) => a.localeCompare(b))
274
+ .map(([provider, entries]) => ({
275
+ provider,
276
+ entries: [...entries].sort((a, b) => a.id.localeCompare(b.id))
277
+ }));
278
+ }, [filteredModels, adapterType]);
279
+
280
+ function reset() {
281
+ setStep(1);
282
+ setLoading(false);
283
+ setError(null);
284
+ setCompanyName("");
285
+ setCompanyGoal("");
286
+ setAgentName("CEO");
287
+ setAdapterType("claude_local");
288
+ setModel("");
289
+ setCommand("");
290
+ setArgs("");
291
+ setUrl("");
292
+ setAdapterEnvResult(null);
293
+ setAdapterEnvError(null);
294
+ setAdapterEnvLoading(false);
295
+ setForceUnsetAnthropicApiKey(false);
296
+ setUnsetAnthropicLoading(false);
297
+ setTaskTitle("Hire your first engineer and create a hiring plan");
298
+ setTaskDescription(DEFAULT_TASK_DESCRIPTION);
299
+ setCreatedCompanyId(null);
300
+ setCreatedCompanyPrefix(null);
301
+ setCreatedCompanyGoalId(null);
302
+ setCreatedAgentId(null);
303
+ setCreatedProjectId(null);
304
+ setCreatedIssueRef(null);
305
+ }
306
+
307
+ function handleClose() {
308
+ reset();
309
+ closeOnboarding();
310
+ }
311
+
312
+ function buildAdapterConfig(): Record<string, unknown> {
313
+ const adapter = getUIAdapter(adapterType);
314
+ const config = adapter.buildAdapterConfig({
315
+ ...defaultCreateValues,
316
+ adapterType,
317
+ model:
318
+ adapterType === "codex_local"
319
+ ? model || DEFAULT_CODEX_LOCAL_MODEL
320
+ : adapterType === "gemini_local"
321
+ ? model || DEFAULT_GEMINI_LOCAL_MODEL
322
+ : adapterType === "cursor"
323
+ ? model || DEFAULT_CURSOR_LOCAL_MODEL
324
+ : model,
325
+ command,
326
+ args,
327
+ url,
328
+ dangerouslySkipPermissions:
329
+ adapterType === "claude_local" || adapterType === "opencode_local",
330
+ dangerouslyBypassSandbox:
331
+ adapterType === "codex_local"
332
+ ? DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX
333
+ : defaultCreateValues.dangerouslyBypassSandbox
334
+ });
335
+ if (adapterType === "claude_local" && forceUnsetAnthropicApiKey) {
336
+ const env =
337
+ typeof config.env === "object" &&
338
+ config.env !== null &&
339
+ !Array.isArray(config.env)
340
+ ? { ...(config.env as Record<string, unknown>) }
341
+ : {};
342
+ env.ANTHROPIC_API_KEY = { type: "plain", value: "" };
343
+ config.env = env;
344
+ }
345
+ return config;
346
+ }
347
+
348
+ async function runAdapterEnvironmentTest(
349
+ adapterConfigOverride?: Record<string, unknown>
350
+ ): Promise<AdapterEnvironmentTestResult | null> {
351
+ if (!createdCompanyId) {
352
+ setAdapterEnvError(
353
+ "Create or select a company before testing adapter environment."
354
+ );
355
+ return null;
356
+ }
357
+ setAdapterEnvLoading(true);
358
+ setAdapterEnvError(null);
359
+ try {
360
+ const result = await agentsApi.testEnvironment(
361
+ createdCompanyId,
362
+ adapterType,
363
+ {
364
+ adapterConfig: adapterConfigOverride ?? buildAdapterConfig()
365
+ }
366
+ );
367
+ setAdapterEnvResult(result);
368
+ return result;
369
+ } catch (err) {
370
+ setAdapterEnvError(
371
+ err instanceof Error ? err.message : "Adapter environment test failed"
372
+ );
373
+ return null;
374
+ } finally {
375
+ setAdapterEnvLoading(false);
376
+ }
377
+ }
378
+
379
+ async function handleStep1Next() {
380
+ setLoading(true);
381
+ setError(null);
382
+ try {
383
+ const company = await companiesApi.create({ name: companyName.trim() });
384
+ setCreatedCompanyId(company.id);
385
+ setCreatedCompanyPrefix(company.issuePrefix);
386
+ setSelectedCompanyId(company.id);
387
+ queryClient.invalidateQueries({ queryKey: queryKeys.companies.all });
388
+
389
+ if (companyGoal.trim()) {
390
+ const parsedGoal = parseOnboardingGoalInput(companyGoal);
391
+ const goal = await goalsApi.create(company.id, {
392
+ title: parsedGoal.title,
393
+ ...(parsedGoal.description
394
+ ? { description: parsedGoal.description }
395
+ : {}),
396
+ level: "company",
397
+ status: "active"
398
+ });
399
+ setCreatedCompanyGoalId(goal.id);
400
+ queryClient.invalidateQueries({
401
+ queryKey: queryKeys.goals.list(company.id)
402
+ });
403
+ } else {
404
+ setCreatedCompanyGoalId(null);
405
+ }
406
+
407
+ setStep(2);
408
+ } catch (err) {
409
+ setError(err instanceof Error ? err.message : "Failed to create company");
410
+ } finally {
411
+ setLoading(false);
412
+ }
413
+ }
414
+
415
+ async function handleStep2Next() {
416
+ if (!createdCompanyId) return;
417
+ setLoading(true);
418
+ setError(null);
419
+ try {
420
+ if (adapterType === "opencode_local") {
421
+ const selectedModelId = model.trim();
422
+ if (!selectedModelId) {
423
+ setError(
424
+ "OpenCode requires an explicit model in provider/model format."
425
+ );
426
+ return;
427
+ }
428
+ if (adapterModelsError) {
429
+ setError(
430
+ adapterModelsError instanceof Error
431
+ ? adapterModelsError.message
432
+ : "Failed to load OpenCode models."
433
+ );
434
+ return;
435
+ }
436
+ if (adapterModelsLoading || adapterModelsFetching) {
437
+ setError(
438
+ "OpenCode models are still loading. Please wait and try again."
439
+ );
440
+ return;
441
+ }
442
+ const discoveredModels = adapterModels ?? [];
443
+ if (!discoveredModels.some((entry) => entry.id === selectedModelId)) {
444
+ setError(
445
+ discoveredModels.length === 0
446
+ ? "No OpenCode models discovered. Run `opencode models` and authenticate providers."
447
+ : `Configured OpenCode model is unavailable: ${selectedModelId}`
448
+ );
449
+ return;
450
+ }
451
+ }
452
+
453
+ if (isLocalAdapter) {
454
+ const result = adapterEnvResult ?? (await runAdapterEnvironmentTest());
455
+ if (!result) return;
456
+ }
457
+
458
+ const agent = await agentsApi.create(createdCompanyId, {
459
+ name: agentName.trim(),
460
+ role: "ceo",
461
+ adapterType,
462
+ adapterConfig: buildAdapterConfig(),
463
+ runtimeConfig: {
464
+ heartbeat: {
465
+ enabled: true,
466
+ intervalSec: 3600,
467
+ wakeOnDemand: true,
468
+ cooldownSec: 10,
469
+ maxConcurrentRuns: 1
470
+ }
471
+ }
472
+ });
473
+ setCreatedAgentId(agent.id);
474
+ queryClient.invalidateQueries({
475
+ queryKey: queryKeys.agents.list(createdCompanyId)
476
+ });
477
+ setStep(3);
478
+ } catch (err) {
479
+ setError(err instanceof Error ? err.message : "Failed to create agent");
480
+ } finally {
481
+ setLoading(false);
482
+ }
483
+ }
484
+
485
+ async function handleUnsetAnthropicApiKey() {
486
+ if (!createdCompanyId || unsetAnthropicLoading) return;
487
+ setUnsetAnthropicLoading(true);
488
+ setError(null);
489
+ setAdapterEnvError(null);
490
+ setForceUnsetAnthropicApiKey(true);
491
+
492
+ const configWithUnset = (() => {
493
+ const config = buildAdapterConfig();
494
+ const env =
495
+ typeof config.env === "object" &&
496
+ config.env !== null &&
497
+ !Array.isArray(config.env)
498
+ ? { ...(config.env as Record<string, unknown>) }
499
+ : {};
500
+ env.ANTHROPIC_API_KEY = { type: "plain", value: "" };
501
+ config.env = env;
502
+ return config;
503
+ })();
504
+
505
+ try {
506
+ if (createdAgentId) {
507
+ await agentsApi.update(
508
+ createdAgentId,
509
+ { adapterConfig: configWithUnset },
510
+ createdCompanyId
511
+ );
512
+ queryClient.invalidateQueries({
513
+ queryKey: queryKeys.agents.list(createdCompanyId)
514
+ });
515
+ }
516
+
517
+ const result = await runAdapterEnvironmentTest(configWithUnset);
518
+ if (result?.status === "fail") {
519
+ setError(
520
+ "Retried with ANTHROPIC_API_KEY unset in adapter config, but the environment test is still failing."
521
+ );
522
+ }
523
+ } catch (err) {
524
+ setError(
525
+ err instanceof Error
526
+ ? err.message
527
+ : "Failed to unset ANTHROPIC_API_KEY and retry."
528
+ );
529
+ } finally {
530
+ setUnsetAnthropicLoading(false);
531
+ }
532
+ }
533
+
534
+ async function handleStep3Next() {
535
+ if (!createdCompanyId || !createdAgentId) return;
536
+ setError(null);
537
+ setStep(4);
538
+ }
539
+
540
+ async function handleLaunch() {
541
+ if (!createdCompanyId || !createdAgentId) return;
542
+ setLoading(true);
543
+ setError(null);
544
+ try {
545
+ let goalId = createdCompanyGoalId;
546
+ if (!goalId) {
547
+ const goals = await goalsApi.list(createdCompanyId);
548
+ goalId = selectDefaultCompanyGoalId(goals);
549
+ setCreatedCompanyGoalId(goalId);
550
+ }
551
+
552
+ let projectId = createdProjectId;
553
+ if (!projectId) {
554
+ const project = await projectsApi.create(
555
+ createdCompanyId,
556
+ buildOnboardingProjectPayload(goalId)
557
+ );
558
+ projectId = project.id;
559
+ setCreatedProjectId(projectId);
560
+ queryClient.invalidateQueries({
561
+ queryKey: queryKeys.projects.list(createdCompanyId)
562
+ });
563
+ }
564
+
565
+ let issueRef = createdIssueRef;
566
+ if (!issueRef) {
567
+ const issue = await issuesApi.create(
568
+ createdCompanyId,
569
+ buildOnboardingIssuePayload({
570
+ title: taskTitle,
571
+ description: taskDescription,
572
+ assigneeAgentId: createdAgentId,
573
+ projectId,
574
+ goalId
575
+ })
576
+ );
577
+ issueRef = issue.identifier ?? issue.id;
578
+ setCreatedIssueRef(issueRef);
579
+ queryClient.invalidateQueries({
580
+ queryKey: queryKeys.issues.list(createdCompanyId)
581
+ });
582
+ }
583
+
584
+ setSelectedCompanyId(createdCompanyId);
585
+ reset();
586
+ closeOnboarding();
587
+ navigate(
588
+ createdCompanyPrefix
589
+ ? `/${createdCompanyPrefix}/issues/${issueRef}`
590
+ : `/issues/${issueRef}`
591
+ );
592
+ } catch (err) {
593
+ setError(err instanceof Error ? err.message : "Failed to create task");
594
+ } finally {
595
+ setLoading(false);
596
+ }
597
+ }
598
+
599
+ function handleKeyDown(e: React.KeyboardEvent) {
600
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
601
+ e.preventDefault();
602
+ if (step === 1 && companyName.trim()) handleStep1Next();
603
+ else if (step === 2 && agentName.trim()) handleStep2Next();
604
+ else if (step === 3 && taskTitle.trim()) handleStep3Next();
605
+ else if (step === 4) handleLaunch();
606
+ }
607
+ }
608
+
609
+ if (!effectiveOnboardingOpen) return null;
610
+
611
+ return (
612
+ <Dialog
613
+ open={effectiveOnboardingOpen}
614
+ onOpenChange={(open) => {
615
+ if (!open) {
616
+ setRouteDismissed(true);
617
+ handleClose();
618
+ }
619
+ }}
620
+ >
621
+ <DialogPortal>
622
+ {/* Plain div instead of DialogOverlay — Radix's overlay wraps in
623
+ RemoveScroll which blocks wheel events on our custom (non-DialogContent)
624
+ scroll container. A plain div preserves the background without scroll-locking. */}
625
+ <div className="fixed inset-0 z-50 bg-background" />
626
+ <div className="fixed inset-0 z-50 flex" onKeyDown={handleKeyDown}>
627
+ {/* Close button */}
628
+ <button
629
+ onClick={handleClose}
630
+ className="absolute top-4 left-4 z-10 rounded-sm p-1.5 text-muted-foreground/60 hover:text-foreground transition-colors"
631
+ >
632
+ <X className="h-5 w-5" />
633
+ <span className="sr-only">Close</span>
634
+ </button>
635
+
636
+ {/* Left half — form */}
637
+ <div
638
+ className={cn(
639
+ "w-full flex flex-col overflow-y-auto transition-[width] duration-500 ease-in-out",
640
+ step === 1 ? "md:w-1/2" : "md:w-full"
641
+ )}
642
+ >
643
+ <div className="w-full max-w-md mx-auto my-auto px-8 py-12 shrink-0">
644
+ {/* Progress tabs */}
645
+ <div className="flex items-center gap-0 mb-8 border-b border-border">
646
+ {(
647
+ [
648
+ { step: 1 as Step, label: "Company", icon: Building2 },
649
+ { step: 2 as Step, label: "Agent", icon: Bot },
650
+ { step: 3 as Step, label: "Task", icon: ListTodo },
651
+ { step: 4 as Step, label: "Launch", icon: Rocket }
652
+ ] as const
653
+ ).map(({ step: s, label, icon: Icon }) => (
654
+ <button
655
+ key={s}
656
+ type="button"
657
+ onClick={() => setStep(s)}
658
+ className={cn(
659
+ "flex items-center gap-1.5 px-3 py-2 text-xs font-medium border-b-2 -mb-px transition-colors cursor-pointer",
660
+ s === step
661
+ ? "border-foreground text-foreground"
662
+ : "border-transparent text-muted-foreground hover:text-foreground/70 hover:border-border"
663
+ )}
664
+ >
665
+ <Icon className="h-3.5 w-3.5" />
666
+ {label}
667
+ </button>
668
+ ))}
669
+ </div>
670
+
671
+ {/* Step content */}
672
+ {step === 1 && (
673
+ <div className="space-y-5">
674
+ <div className="flex items-center gap-3 mb-1">
675
+ <div className="bg-muted/50 p-2">
676
+ <Building2 className="h-5 w-5 text-muted-foreground" />
677
+ </div>
678
+ <div>
679
+ <h3 className="font-medium">Name your company</h3>
680
+ <p className="text-xs text-muted-foreground">
681
+ This is the organization your agents will work for.
682
+ </p>
683
+ </div>
684
+ </div>
685
+ <div className="mt-3 group">
686
+ <label
687
+ className={cn(
688
+ "text-xs mb-1 block transition-colors",
689
+ companyName.trim()
690
+ ? "text-foreground"
691
+ : "text-muted-foreground group-focus-within:text-foreground"
692
+ )}
693
+ >
694
+ Company name
695
+ </label>
696
+ <input
697
+ className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
698
+ placeholder="Acme Corp"
699
+ value={companyName}
700
+ onChange={(e) => setCompanyName(e.target.value)}
701
+ autoFocus
702
+ />
703
+ </div>
704
+ <div className="group">
705
+ <label
706
+ className={cn(
707
+ "text-xs mb-1 block transition-colors",
708
+ companyGoal.trim()
709
+ ? "text-foreground"
710
+ : "text-muted-foreground group-focus-within:text-foreground"
711
+ )}
712
+ >
713
+ Mission / goal (optional)
714
+ </label>
715
+ <textarea
716
+ className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50 resize-none min-h-[60px]"
717
+ placeholder="What is this company trying to achieve?"
718
+ value={companyGoal}
719
+ onChange={(e) => setCompanyGoal(e.target.value)}
720
+ />
721
+ </div>
722
+ </div>
723
+ )}
724
+
725
+ {step === 2 && (
726
+ <div className="space-y-5">
727
+ <div className="flex items-center gap-3 mb-1">
728
+ <div className="bg-muted/50 p-2">
729
+ <Bot className="h-5 w-5 text-muted-foreground" />
730
+ </div>
731
+ <div>
732
+ <h3 className="font-medium">Create your first agent</h3>
733
+ <p className="text-xs text-muted-foreground">
734
+ Choose how this agent will run tasks.
735
+ </p>
736
+ </div>
737
+ </div>
738
+ <div>
739
+ <label className="text-xs text-muted-foreground mb-1 block">
740
+ Agent name
741
+ </label>
742
+ <input
743
+ className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
744
+ placeholder="CEO"
745
+ value={agentName}
746
+ onChange={(e) => setAgentName(e.target.value)}
747
+ autoFocus
748
+ />
749
+ </div>
750
+
751
+ {/* Adapter type radio cards */}
752
+ <div>
753
+ <label className="text-xs text-muted-foreground mb-2 block">
754
+ Adapter type
755
+ </label>
756
+ <div className="grid grid-cols-2 gap-2">
757
+ {[
758
+ {
759
+ value: "claude_local" as const,
760
+ label: "Claude Code",
761
+ icon: Sparkles,
762
+ desc: "Local Claude agent",
763
+ recommended: true
764
+ },
765
+ {
766
+ value: "codex_local" as const,
767
+ label: "Codex",
768
+ icon: Code,
769
+ desc: "Local Codex agent",
770
+ recommended: true
771
+ }
772
+ ].map((opt) => (
773
+ <button
774
+ key={opt.value}
775
+ className={cn(
776
+ "flex flex-col items-center gap-1.5 rounded-md border p-3 text-xs transition-colors relative",
777
+ adapterType === opt.value
778
+ ? "border-foreground bg-accent"
779
+ : "border-border hover:bg-accent/50"
780
+ )}
781
+ onClick={() => {
782
+ const nextType = opt.value as AdapterType;
783
+ setAdapterType(nextType);
784
+ if (nextType === "codex_local" && !model) {
785
+ setModel(DEFAULT_CODEX_LOCAL_MODEL);
786
+ }
787
+ if (nextType !== "codex_local") {
788
+ setModel("");
789
+ }
790
+ }}
791
+ >
792
+ {opt.recommended && (
793
+ <span className="absolute -top-1.5 right-1.5 bg-green-500 text-white text-[9px] font-semibold px-1.5 py-0.5 rounded-full leading-none">
794
+ Recommended
795
+ </span>
796
+ )}
797
+ <opt.icon className="h-4 w-4" />
798
+ <span className="font-medium">{opt.label}</span>
799
+ <span className="text-muted-foreground text-[10px]">
800
+ {opt.desc}
801
+ </span>
802
+ </button>
803
+ ))}
804
+ </div>
805
+
806
+ <button
807
+ className="flex items-center gap-1.5 mt-3 text-xs text-muted-foreground hover:text-foreground transition-colors"
808
+ onClick={() => setShowMoreAdapters((v) => !v)}
809
+ >
810
+ <ChevronDown
811
+ className={cn(
812
+ "h-3 w-3 transition-transform",
813
+ showMoreAdapters ? "rotate-0" : "-rotate-90"
814
+ )}
815
+ />
816
+ More Agent Adapter Types
817
+ </button>
818
+
819
+ {showMoreAdapters && (
820
+ <div className="grid grid-cols-2 gap-2 mt-2">
821
+ {[
822
+ {
823
+ value: "gemini_local" as const,
824
+ label: "Gemini CLI",
825
+ icon: Gem,
826
+ desc: "Local Gemini agent"
827
+ },
828
+ {
829
+ value: "opencode_local" as const,
830
+ label: "OpenCode",
831
+ icon: OpenCodeLogoIcon,
832
+ desc: "Local multi-provider agent"
833
+ },
834
+ {
835
+ value: "pi_local" as const,
836
+ label: "Pi",
837
+ icon: Terminal,
838
+ desc: "Local Pi agent"
839
+ },
840
+ {
841
+ value: "cursor" as const,
842
+ label: "Cursor",
843
+ icon: MousePointer2,
844
+ desc: "Local Cursor agent"
845
+ },
846
+ {
847
+ value: "openclaw_gateway" as const,
848
+ label: "OpenClaw Gateway",
849
+ icon: Bot,
850
+ desc: "Invoke OpenClaw via gateway protocol",
851
+ comingSoon: true,
852
+ disabledLabel: "Configure OpenClaw within the App"
853
+ }
854
+ ].map((opt) => (
855
+ <button
856
+ key={opt.value}
857
+ disabled={!!opt.comingSoon}
858
+ className={cn(
859
+ "flex flex-col items-center gap-1.5 rounded-md border p-3 text-xs transition-colors relative",
860
+ opt.comingSoon
861
+ ? "border-border opacity-40 cursor-not-allowed"
862
+ : adapterType === opt.value
863
+ ? "border-foreground bg-accent"
864
+ : "border-border hover:bg-accent/50"
865
+ )}
866
+ onClick={() => {
867
+ if (opt.comingSoon) return;
868
+ const nextType = opt.value as AdapterType;
869
+ setAdapterType(nextType);
870
+ if (nextType === "gemini_local" && !model) {
871
+ setModel(DEFAULT_GEMINI_LOCAL_MODEL);
872
+ return;
873
+ }
874
+ if (nextType === "cursor" && !model) {
875
+ setModel(DEFAULT_CURSOR_LOCAL_MODEL);
876
+ return;
877
+ }
878
+ if (nextType === "opencode_local") {
879
+ if (!model.includes("/")) {
880
+ setModel("");
881
+ }
882
+ return;
883
+ }
884
+ setModel("");
885
+ }}
886
+ >
887
+ <opt.icon className="h-4 w-4" />
888
+ <span className="font-medium">{opt.label}</span>
889
+ <span className="text-muted-foreground text-[10px]">
890
+ {opt.comingSoon
891
+ ? (opt as { disabledLabel?: string })
892
+ .disabledLabel ?? "Coming soon"
893
+ : opt.desc}
894
+ </span>
895
+ </button>
896
+ ))}
897
+ </div>
898
+ )}
899
+ </div>
900
+
901
+ {/* Conditional adapter fields */}
902
+ {(adapterType === "claude_local" ||
903
+ adapterType === "codex_local" ||
904
+ adapterType === "gemini_local" ||
905
+ adapterType === "opencode_local" ||
906
+ adapterType === "pi_local" ||
907
+ adapterType === "cursor") && (
908
+ <div className="space-y-3">
909
+ <div>
910
+ <label className="text-xs text-muted-foreground mb-1 block">
911
+ Model
912
+ </label>
913
+ <Popover
914
+ open={modelOpen}
915
+ onOpenChange={(next) => {
916
+ setModelOpen(next);
917
+ if (!next) setModelSearch("");
918
+ }}
919
+ >
920
+ <PopoverTrigger asChild>
921
+ <button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-sm hover:bg-accent/50 transition-colors w-full justify-between">
922
+ <span
923
+ className={cn(
924
+ !model && "text-muted-foreground"
925
+ )}
926
+ >
927
+ {selectedModel
928
+ ? selectedModel.label
929
+ : model ||
930
+ (adapterType === "opencode_local"
931
+ ? "Select model (required)"
932
+ : "Default")}
933
+ </span>
934
+ <ChevronDown className="h-3 w-3 text-muted-foreground" />
935
+ </button>
936
+ </PopoverTrigger>
937
+ <PopoverContent
938
+ className="w-[var(--radix-popover-trigger-width)] p-1"
939
+ align="start"
940
+ >
941
+ <input
942
+ className="w-full px-2 py-1.5 text-xs bg-transparent outline-none border-b border-border mb-1 placeholder:text-muted-foreground/50"
943
+ placeholder="Search models..."
944
+ value={modelSearch}
945
+ onChange={(e) => setModelSearch(e.target.value)}
946
+ autoFocus
947
+ />
948
+ {adapterType !== "opencode_local" && (
949
+ <button
950
+ className={cn(
951
+ "flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50",
952
+ !model && "bg-accent"
953
+ )}
954
+ onClick={() => {
955
+ setModel("");
956
+ setModelOpen(false);
957
+ }}
958
+ >
959
+ Default
960
+ </button>
961
+ )}
962
+ <div className="max-h-[240px] overflow-y-auto">
963
+ {groupedModels.map((group) => (
964
+ <div
965
+ key={group.provider}
966
+ className="mb-1 last:mb-0"
967
+ >
968
+ {adapterType === "opencode_local" && (
969
+ <div className="px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground">
970
+ {group.provider} ({group.entries.length})
971
+ </div>
972
+ )}
973
+ {group.entries.map((m) => (
974
+ <button
975
+ key={m.id}
976
+ className={cn(
977
+ "flex items-center w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50",
978
+ m.id === model && "bg-accent"
979
+ )}
980
+ onClick={() => {
981
+ setModel(m.id);
982
+ setModelOpen(false);
983
+ }}
984
+ >
985
+ <span
986
+ className="block w-full text-left truncate"
987
+ title={m.id}
988
+ >
989
+ {adapterType === "opencode_local"
990
+ ? extractModelName(m.id)
991
+ : m.label}
992
+ </span>
993
+ </button>
994
+ ))}
995
+ </div>
996
+ ))}
997
+ </div>
998
+ {filteredModels.length === 0 && (
999
+ <p className="px-2 py-1.5 text-xs text-muted-foreground">
1000
+ No models discovered.
1001
+ </p>
1002
+ )}
1003
+ </PopoverContent>
1004
+ </Popover>
1005
+ </div>
1006
+ </div>
1007
+ )}
1008
+
1009
+ {isLocalAdapter && (
1010
+ <div className="space-y-2 rounded-md border border-border p-3">
1011
+ <div className="flex items-center justify-between gap-2">
1012
+ <div>
1013
+ <p className="text-xs font-medium">
1014
+ Adapter environment check
1015
+ </p>
1016
+ <p className="text-[11px] text-muted-foreground">
1017
+ Runs a live probe that asks the adapter CLI to
1018
+ respond with hello.
1019
+ </p>
1020
+ </div>
1021
+ <Button
1022
+ size="sm"
1023
+ variant="outline"
1024
+ className="h-7 px-2.5 text-xs"
1025
+ disabled={adapterEnvLoading}
1026
+ onClick={() => void runAdapterEnvironmentTest()}
1027
+ >
1028
+ {adapterEnvLoading ? "Testing..." : "Test now"}
1029
+ </Button>
1030
+ </div>
1031
+
1032
+ {adapterEnvError && (
1033
+ <div className="rounded-md border border-destructive/30 bg-destructive/10 px-2.5 py-2 text-[11px] text-destructive">
1034
+ {adapterEnvError}
1035
+ </div>
1036
+ )}
1037
+
1038
+ {adapterEnvResult &&
1039
+ adapterEnvResult.status === "pass" ? (
1040
+ <div className="flex items-center gap-2 rounded-md border border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10 px-3 py-2 text-xs text-green-700 dark:text-green-300 animate-in fade-in slide-in-from-bottom-1 duration-300">
1041
+ <Check className="h-3.5 w-3.5 shrink-0" />
1042
+ <span className="font-medium">Passed</span>
1043
+ </div>
1044
+ ) : adapterEnvResult ? (
1045
+ <AdapterEnvironmentResult result={adapterEnvResult} />
1046
+ ) : null}
1047
+
1048
+ {shouldSuggestUnsetAnthropicApiKey && (
1049
+ <div className="rounded-md border border-amber-300/60 bg-amber-50/40 px-2.5 py-2 space-y-2">
1050
+ <p className="text-[11px] text-amber-900/90 leading-relaxed">
1051
+ Claude failed while{" "}
1052
+ <span className="font-mono">ANTHROPIC_API_KEY</span>{" "}
1053
+ is set. You can clear it in this CEO adapter config
1054
+ and retry the probe.
1055
+ </p>
1056
+ <Button
1057
+ size="sm"
1058
+ variant="outline"
1059
+ className="h-7 px-2.5 text-xs"
1060
+ disabled={
1061
+ adapterEnvLoading || unsetAnthropicLoading
1062
+ }
1063
+ onClick={() => void handleUnsetAnthropicApiKey()}
1064
+ >
1065
+ {unsetAnthropicLoading
1066
+ ? "Retrying..."
1067
+ : "Unset ANTHROPIC_API_KEY"}
1068
+ </Button>
1069
+ </div>
1070
+ )}
1071
+
1072
+ {adapterEnvResult && adapterEnvResult.status === "fail" && (
1073
+ <div className="rounded-md border border-border/70 bg-muted/20 px-2.5 py-2 text-[11px] space-y-1.5">
1074
+ <p className="font-medium">Manual debug</p>
1075
+ <p className="text-muted-foreground font-mono break-all">
1076
+ {adapterType === "cursor"
1077
+ ? `${effectiveAdapterCommand} -p --mode ask --output-format json \"Respond with hello.\"`
1078
+ : adapterType === "codex_local"
1079
+ ? `${effectiveAdapterCommand} exec --json -`
1080
+ : adapterType === "gemini_local"
1081
+ ? `${effectiveAdapterCommand} --output-format json "Respond with hello."`
1082
+ : adapterType === "opencode_local"
1083
+ ? `${effectiveAdapterCommand} run --format json "Respond with hello."`
1084
+ : `${effectiveAdapterCommand} --print - --output-format stream-json --verbose`}
1085
+ </p>
1086
+ <p className="text-muted-foreground">
1087
+ Prompt:{" "}
1088
+ <span className="font-mono">Respond with hello.</span>
1089
+ </p>
1090
+ {adapterType === "cursor" ||
1091
+ adapterType === "codex_local" ||
1092
+ adapterType === "gemini_local" ||
1093
+ adapterType === "opencode_local" ? (
1094
+ <p className="text-muted-foreground">
1095
+ If auth fails, set{" "}
1096
+ <span className="font-mono">
1097
+ {adapterType === "cursor"
1098
+ ? "CURSOR_API_KEY"
1099
+ : adapterType === "gemini_local"
1100
+ ? "GEMINI_API_KEY"
1101
+ : "OPENAI_API_KEY"}
1102
+ </span>{" "}
1103
+ in env or run{" "}
1104
+ <span className="font-mono">
1105
+ {adapterType === "cursor"
1106
+ ? "agent login"
1107
+ : adapterType === "codex_local"
1108
+ ? "codex login"
1109
+ : adapterType === "gemini_local"
1110
+ ? "gemini auth"
1111
+ : "opencode auth login"}
1112
+ </span>
1113
+ .
1114
+ </p>
1115
+ ) : (
1116
+ <p className="text-muted-foreground">
1117
+ If login is required, run{" "}
1118
+ <span className="font-mono">claude login</span>{" "}
1119
+ and retry.
1120
+ </p>
1121
+ )}
1122
+ </div>
1123
+ )}
1124
+ </div>
1125
+ )}
1126
+
1127
+ {(adapterType === "http" ||
1128
+ adapterType === "openclaw_gateway") && (
1129
+ <div>
1130
+ <label className="text-xs text-muted-foreground mb-1 block">
1131
+ {adapterType === "openclaw_gateway"
1132
+ ? "Gateway URL"
1133
+ : "Webhook URL"}
1134
+ </label>
1135
+ <input
1136
+ className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm font-mono outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
1137
+ placeholder={
1138
+ adapterType === "openclaw_gateway"
1139
+ ? "ws://127.0.0.1:18789"
1140
+ : "https://..."
1141
+ }
1142
+ value={url}
1143
+ onChange={(e) => setUrl(e.target.value)}
1144
+ />
1145
+ </div>
1146
+ )}
1147
+ </div>
1148
+ )}
1149
+
1150
+ {step === 3 && (
1151
+ <div className="space-y-5">
1152
+ <div className="flex items-center gap-3 mb-1">
1153
+ <div className="bg-muted/50 p-2">
1154
+ <ListTodo className="h-5 w-5 text-muted-foreground" />
1155
+ </div>
1156
+ <div>
1157
+ <h3 className="font-medium">Give it something to do</h3>
1158
+ <p className="text-xs text-muted-foreground">
1159
+ Give your agent a small task to start with — a bug fix,
1160
+ a research question, writing a script.
1161
+ </p>
1162
+ </div>
1163
+ </div>
1164
+ <div>
1165
+ <label className="text-xs text-muted-foreground mb-1 block">
1166
+ Task title
1167
+ </label>
1168
+ <input
1169
+ className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
1170
+ placeholder="e.g. Research competitor pricing"
1171
+ value={taskTitle}
1172
+ onChange={(e) => setTaskTitle(e.target.value)}
1173
+ autoFocus
1174
+ />
1175
+ </div>
1176
+ <div>
1177
+ <label className="text-xs text-muted-foreground mb-1 block">
1178
+ Description (optional)
1179
+ </label>
1180
+ <textarea
1181
+ ref={textareaRef}
1182
+ className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50 resize-none min-h-[120px] max-h-[300px] overflow-y-auto"
1183
+ placeholder="Add more detail about what the agent should do..."
1184
+ value={taskDescription}
1185
+ onChange={(e) => setTaskDescription(e.target.value)}
1186
+ />
1187
+ </div>
1188
+ </div>
1189
+ )}
1190
+
1191
+ {step === 4 && (
1192
+ <div className="space-y-5">
1193
+ <div className="flex items-center gap-3 mb-1">
1194
+ <div className="bg-muted/50 p-2">
1195
+ <Rocket className="h-5 w-5 text-muted-foreground" />
1196
+ </div>
1197
+ <div>
1198
+ <h3 className="font-medium">Ready to launch</h3>
1199
+ <p className="text-xs text-muted-foreground">
1200
+ Everything is set up. Launching now will create the
1201
+ starter task, wake the agent, and open the issue.
1202
+ </p>
1203
+ </div>
1204
+ </div>
1205
+ <div className="border border-border divide-y divide-border">
1206
+ <div className="flex items-center gap-3 px-3 py-2.5">
1207
+ <Building2 className="h-4 w-4 text-muted-foreground shrink-0" />
1208
+ <div className="flex-1 min-w-0">
1209
+ <p className="text-sm font-medium truncate">
1210
+ {companyName}
1211
+ </p>
1212
+ <p className="text-xs text-muted-foreground">Company</p>
1213
+ </div>
1214
+ <Check className="h-4 w-4 text-green-500 shrink-0" />
1215
+ </div>
1216
+ <div className="flex items-center gap-3 px-3 py-2.5">
1217
+ <Bot className="h-4 w-4 text-muted-foreground shrink-0" />
1218
+ <div className="flex-1 min-w-0">
1219
+ <p className="text-sm font-medium truncate">
1220
+ {agentName}
1221
+ </p>
1222
+ <p className="text-xs text-muted-foreground">
1223
+ {getUIAdapter(adapterType).label}
1224
+ </p>
1225
+ </div>
1226
+ <Check className="h-4 w-4 text-green-500 shrink-0" />
1227
+ </div>
1228
+ <div className="flex items-center gap-3 px-3 py-2.5">
1229
+ <ListTodo className="h-4 w-4 text-muted-foreground shrink-0" />
1230
+ <div className="flex-1 min-w-0">
1231
+ <p className="text-sm font-medium truncate">
1232
+ {taskTitle}
1233
+ </p>
1234
+ <p className="text-xs text-muted-foreground">Task</p>
1235
+ </div>
1236
+ <Check className="h-4 w-4 text-green-500 shrink-0" />
1237
+ </div>
1238
+ </div>
1239
+ </div>
1240
+ )}
1241
+
1242
+ {/* Error */}
1243
+ {error && (
1244
+ <div className="mt-3">
1245
+ <p className="text-xs text-destructive">{error}</p>
1246
+ </div>
1247
+ )}
1248
+
1249
+ {/* Footer navigation */}
1250
+ <div className="flex items-center justify-between mt-8">
1251
+ <div>
1252
+ {step > 1 && step > (onboardingOptions.initialStep ?? 1) && (
1253
+ <Button
1254
+ variant="ghost"
1255
+ size="sm"
1256
+ onClick={() => setStep((step - 1) as Step)}
1257
+ disabled={loading}
1258
+ >
1259
+ <ArrowLeft className="h-3.5 w-3.5 mr-1" />
1260
+ Back
1261
+ </Button>
1262
+ )}
1263
+ </div>
1264
+ <div className="flex items-center gap-2">
1265
+ {step === 1 && (
1266
+ <Button
1267
+ size="sm"
1268
+ disabled={!companyName.trim() || loading}
1269
+ onClick={handleStep1Next}
1270
+ >
1271
+ {loading ? (
1272
+ <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" />
1273
+ ) : (
1274
+ <ArrowRight className="h-3.5 w-3.5 mr-1" />
1275
+ )}
1276
+ {loading ? "Creating..." : "Next"}
1277
+ </Button>
1278
+ )}
1279
+ {step === 2 && (
1280
+ <Button
1281
+ size="sm"
1282
+ disabled={
1283
+ !agentName.trim() || loading || adapterEnvLoading
1284
+ }
1285
+ onClick={handleStep2Next}
1286
+ >
1287
+ {loading ? (
1288
+ <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" />
1289
+ ) : (
1290
+ <ArrowRight className="h-3.5 w-3.5 mr-1" />
1291
+ )}
1292
+ {loading ? "Creating..." : "Next"}
1293
+ </Button>
1294
+ )}
1295
+ {step === 3 && (
1296
+ <Button
1297
+ size="sm"
1298
+ disabled={!taskTitle.trim() || loading}
1299
+ onClick={handleStep3Next}
1300
+ >
1301
+ {loading ? (
1302
+ <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" />
1303
+ ) : (
1304
+ <ArrowRight className="h-3.5 w-3.5 mr-1" />
1305
+ )}
1306
+ {loading ? "Creating..." : "Next"}
1307
+ </Button>
1308
+ )}
1309
+ {step === 4 && (
1310
+ <Button size="sm" disabled={loading} onClick={handleLaunch}>
1311
+ {loading ? (
1312
+ <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" />
1313
+ ) : (
1314
+ <ArrowRight className="h-3.5 w-3.5 mr-1" />
1315
+ )}
1316
+ {loading ? "Creating..." : "Create & Open Issue"}
1317
+ </Button>
1318
+ )}
1319
+ </div>
1320
+ </div>
1321
+ </div>
1322
+ </div>
1323
+
1324
+ {/* Right half — ASCII art (hidden on mobile) */}
1325
+ <div
1326
+ className={cn(
1327
+ "hidden md:block overflow-hidden bg-[#1d1d1d] transition-[width,opacity] duration-500 ease-in-out",
1328
+ step === 1 ? "w-1/2 opacity-100" : "w-0 opacity-0"
1329
+ )}
1330
+ >
1331
+ <AsciiArtAnimation />
1332
+ </div>
1333
+ </div>
1334
+ </DialogPortal>
1335
+ </Dialog>
1336
+ );
1337
+ }
1338
+
1339
+ function AdapterEnvironmentResult({
1340
+ result
1341
+ }: {
1342
+ result: AdapterEnvironmentTestResult;
1343
+ }) {
1344
+ const statusLabel =
1345
+ result.status === "pass"
1346
+ ? "Passed"
1347
+ : result.status === "warn"
1348
+ ? "Warnings"
1349
+ : "Failed";
1350
+ const statusClass =
1351
+ result.status === "pass"
1352
+ ? "text-green-700 dark:text-green-300 border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10"
1353
+ : result.status === "warn"
1354
+ ? "text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10"
1355
+ : "text-red-700 dark:text-red-300 border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10";
1356
+
1357
+ return (
1358
+ <div className={`rounded-md border px-2.5 py-2 text-[11px] ${statusClass}`}>
1359
+ <div className="flex items-center justify-between gap-2">
1360
+ <span className="font-medium">{statusLabel}</span>
1361
+ <span className="opacity-80">
1362
+ {new Date(result.testedAt).toLocaleTimeString()}
1363
+ </span>
1364
+ </div>
1365
+ <div className="mt-1.5 space-y-1">
1366
+ {result.checks.map((check, idx) => (
1367
+ <div
1368
+ key={`${check.code}-${idx}`}
1369
+ className="leading-relaxed break-words"
1370
+ >
1371
+ <span className="font-medium uppercase tracking-wide opacity-80">
1372
+ {check.level}
1373
+ </span>
1374
+ <span className="mx-1 opacity-60">·</span>
1375
+ <span>{check.message}</span>
1376
+ {check.detail && (
1377
+ <span className="block opacity-75 break-all">
1378
+ ({check.detail})
1379
+ </span>
1380
+ )}
1381
+ {check.hint && (
1382
+ <span className="block opacity-90 break-words">
1383
+ Hint: {check.hint}
1384
+ </span>
1385
+ )}
1386
+ </div>
1387
+ ))}
1388
+ </div>
1389
+ </div>
1390
+ );
1391
+ }
1392
+