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,1127 @@
1
+ import { useState } from "react";
2
+ import { Link } from "@/lib/router";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import type { Project } from "@corporateai/shared";
5
+ import { StatusBadge } from "./StatusBadge";
6
+ import { cn, formatDate } from "../lib/utils";
7
+ import { goalsApi } from "../api/goals";
8
+ import { instanceSettingsApi } from "../api/instanceSettings";
9
+ import { projectsApi } from "../api/projects";
10
+ import { useCompany } from "../context/CompanyContext";
11
+ import { queryKeys } from "../lib/queryKeys";
12
+ import { statusBadge, statusBadgeDefault } from "../lib/status-colors";
13
+ import { Separator } from "@/components/ui/separator";
14
+ import { Button } from "@/components/ui/button";
15
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
16
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
17
+ import { AlertCircle, Archive, ArchiveRestore, Check, ExternalLink, Github, Loader2, Plus, Trash2, X } from "lucide-react";
18
+ import { ChoosePathButton } from "./PathInstructionsModal";
19
+ import { DraftInput } from "./agent-config-primitives";
20
+ import { InlineEditor } from "./InlineEditor";
21
+
22
+ const PROJECT_STATUSES = [
23
+ { value: "backlog", label: "Backlog" },
24
+ { value: "planned", label: "Planned" },
25
+ { value: "in_progress", label: "In Progress" },
26
+ { value: "completed", label: "Completed" },
27
+ { value: "cancelled", label: "Cancelled" },
28
+ ];
29
+
30
+ interface ProjectPropertiesProps {
31
+ project: Project;
32
+ onUpdate?: (data: Record<string, unknown>) => void;
33
+ onFieldUpdate?: (field: ProjectConfigFieldKey, data: Record<string, unknown>) => void;
34
+ getFieldSaveState?: (field: ProjectConfigFieldKey) => ProjectFieldSaveState;
35
+ onArchive?: (archived: boolean) => void;
36
+ archivePending?: boolean;
37
+ }
38
+
39
+ export type ProjectFieldSaveState = "idle" | "saving" | "saved" | "error";
40
+ export type ProjectConfigFieldKey =
41
+ | "name"
42
+ | "description"
43
+ | "status"
44
+ | "goals"
45
+ | "execution_workspace_enabled"
46
+ | "execution_workspace_default_mode"
47
+ | "execution_workspace_base_ref"
48
+ | "execution_workspace_branch_template"
49
+ | "execution_workspace_worktree_parent_dir"
50
+ | "execution_workspace_provision_command"
51
+ | "execution_workspace_teardown_command";
52
+
53
+ function SaveIndicator({ state }: { state: ProjectFieldSaveState }) {
54
+ if (state === "saving") {
55
+ return (
56
+ <span className="inline-flex items-center gap-1 text-[11px] text-muted-foreground">
57
+ <Loader2 className="h-3 w-3 animate-spin" />
58
+ Saving
59
+ </span>
60
+ );
61
+ }
62
+ if (state === "saved") {
63
+ return (
64
+ <span className="inline-flex items-center gap-1 text-[11px] text-green-600 dark:text-green-400">
65
+ <Check className="h-3 w-3" />
66
+ Saved
67
+ </span>
68
+ );
69
+ }
70
+ if (state === "error") {
71
+ return (
72
+ <span className="inline-flex items-center gap-1 text-[11px] text-destructive">
73
+ <AlertCircle className="h-3 w-3" />
74
+ Failed
75
+ </span>
76
+ );
77
+ }
78
+ return null;
79
+ }
80
+
81
+ function FieldLabel({
82
+ label,
83
+ state,
84
+ }: {
85
+ label: string;
86
+ state: ProjectFieldSaveState;
87
+ }) {
88
+ return (
89
+ <div className="flex items-center gap-1.5">
90
+ <span className="text-xs text-muted-foreground">{label}</span>
91
+ <SaveIndicator state={state} />
92
+ </div>
93
+ );
94
+ }
95
+
96
+ function PropertyRow({
97
+ label,
98
+ children,
99
+ alignStart = false,
100
+ valueClassName = "",
101
+ }: {
102
+ label: React.ReactNode;
103
+ children: React.ReactNode;
104
+ alignStart?: boolean;
105
+ valueClassName?: string;
106
+ }) {
107
+ return (
108
+ <div className={cn("flex gap-3 py-1.5", alignStart ? "items-start" : "items-center")}>
109
+ <div className="shrink-0 w-20">{label}</div>
110
+ <div className={cn("min-w-0 flex-1", alignStart ? "pt-0.5" : "flex items-center gap-1.5", valueClassName)}>
111
+ {children}
112
+ </div>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ function ProjectStatusPicker({ status, onChange }: { status: string; onChange: (status: string) => void }) {
118
+ const [open, setOpen] = useState(false);
119
+ const colorClass = statusBadge[status] ?? statusBadgeDefault;
120
+
121
+ return (
122
+ <Popover open={open} onOpenChange={setOpen}>
123
+ <PopoverTrigger asChild>
124
+ <button
125
+ className={cn(
126
+ "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium whitespace-nowrap shrink-0 cursor-pointer hover:opacity-80 transition-opacity",
127
+ colorClass,
128
+ )}
129
+ >
130
+ {status.replace("_", " ")}
131
+ </button>
132
+ </PopoverTrigger>
133
+ <PopoverContent className="w-40 p-1" align="start">
134
+ {PROJECT_STATUSES.map((s) => (
135
+ <Button
136
+ key={s.value}
137
+ variant="ghost"
138
+ size="sm"
139
+ className={cn("w-full justify-start gap-2 text-xs", s.value === status && "bg-accent")}
140
+ onClick={() => {
141
+ onChange(s.value);
142
+ setOpen(false);
143
+ }}
144
+ >
145
+ {s.label}
146
+ </Button>
147
+ ))}
148
+ </PopoverContent>
149
+ </Popover>
150
+ );
151
+ }
152
+
153
+ function ArchiveDangerZone({
154
+ project,
155
+ onArchive,
156
+ archivePending,
157
+ }: {
158
+ project: Project;
159
+ onArchive: (archived: boolean) => void;
160
+ archivePending?: boolean;
161
+ }) {
162
+ const [confirming, setConfirming] = useState(false);
163
+ const isArchive = !project.archivedAt;
164
+ const action = isArchive ? "Archive" : "Unarchive";
165
+
166
+ return (
167
+ <div className="space-y-3 rounded-md border border-destructive/40 bg-destructive/5 px-4 py-4">
168
+ <p className="text-sm text-muted-foreground">
169
+ {isArchive
170
+ ? "Archive this project to hide it from the sidebar and project selectors."
171
+ : "Unarchive this project to restore it in the sidebar and project selectors."}
172
+ </p>
173
+ {archivePending ? (
174
+ <Button size="sm" variant="destructive" disabled>
175
+ <Loader2 className="h-3 w-3 animate-spin mr-1" />
176
+ {isArchive ? "Archiving..." : "Unarchiving..."}
177
+ </Button>
178
+ ) : confirming ? (
179
+ <div className="flex items-center gap-2">
180
+ <span className="text-sm text-destructive font-medium">
181
+ {action} &ldquo;{project.name}&rdquo;?
182
+ </span>
183
+ <Button
184
+ size="sm"
185
+ variant="destructive"
186
+ onClick={() => {
187
+ setConfirming(false);
188
+ onArchive(isArchive);
189
+ }}
190
+ >
191
+ Confirm
192
+ </Button>
193
+ <Button
194
+ size="sm"
195
+ variant="outline"
196
+ onClick={() => setConfirming(false)}
197
+ >
198
+ Cancel
199
+ </Button>
200
+ </div>
201
+ ) : (
202
+ <Button
203
+ size="sm"
204
+ variant="destructive"
205
+ onClick={() => setConfirming(true)}
206
+ >
207
+ {isArchive ? (
208
+ <><Archive className="h-3 w-3 mr-1" />{action} project</>
209
+ ) : (
210
+ <><ArchiveRestore className="h-3 w-3 mr-1" />{action} project</>
211
+ )}
212
+ </Button>
213
+ )}
214
+ </div>
215
+ );
216
+ }
217
+
218
+ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSaveState, onArchive, archivePending }: ProjectPropertiesProps) {
219
+ const { selectedCompanyId } = useCompany();
220
+ const queryClient = useQueryClient();
221
+ const [goalOpen, setGoalOpen] = useState(false);
222
+ const [executionWorkspaceAdvancedOpen, setExecutionWorkspaceAdvancedOpen] = useState(false);
223
+ const [workspaceMode, setWorkspaceMode] = useState<"local" | "repo" | null>(null);
224
+ const [workspaceCwd, setWorkspaceCwd] = useState("");
225
+ const [workspaceRepoUrl, setWorkspaceRepoUrl] = useState("");
226
+ const [workspaceError, setWorkspaceError] = useState<string | null>(null);
227
+
228
+ const commitField = (field: ProjectConfigFieldKey, data: Record<string, unknown>) => {
229
+ if (onFieldUpdate) {
230
+ onFieldUpdate(field, data);
231
+ return;
232
+ }
233
+ onUpdate?.(data);
234
+ };
235
+ const fieldState = (field: ProjectConfigFieldKey): ProjectFieldSaveState => getFieldSaveState?.(field) ?? "idle";
236
+
237
+ const { data: allGoals } = useQuery({
238
+ queryKey: queryKeys.goals.list(selectedCompanyId!),
239
+ queryFn: () => goalsApi.list(selectedCompanyId!),
240
+ enabled: !!selectedCompanyId,
241
+ });
242
+ const { data: experimentalSettings } = useQuery({
243
+ queryKey: queryKeys.instance.experimentalSettings,
244
+ queryFn: () => instanceSettingsApi.getExperimental(),
245
+ });
246
+
247
+ const linkedGoalIds = project.goalIds.length > 0
248
+ ? project.goalIds
249
+ : project.goalId
250
+ ? [project.goalId]
251
+ : [];
252
+
253
+ const linkedGoals = project.goals.length > 0
254
+ ? project.goals
255
+ : linkedGoalIds.map((id) => ({
256
+ id,
257
+ title: allGoals?.find((g) => g.id === id)?.title ?? id.slice(0, 8),
258
+ }));
259
+
260
+ const availableGoals = (allGoals ?? []).filter((g) => !linkedGoalIds.includes(g.id));
261
+ const workspaces = project.workspaces ?? [];
262
+ const codebase = project.codebase;
263
+ const primaryCodebaseWorkspace = project.primaryWorkspace ?? null;
264
+ const hasAdditionalLegacyWorkspaces = workspaces.some((workspace) => workspace.id !== primaryCodebaseWorkspace?.id);
265
+ const executionWorkspacePolicy = project.executionWorkspacePolicy ?? null;
266
+ const executionWorkspacesEnabled = executionWorkspacePolicy?.enabled === true;
267
+ const isolatedWorkspacesEnabled = experimentalSettings?.enableIsolatedWorkspaces === true;
268
+ const executionWorkspaceDefaultMode =
269
+ executionWorkspacePolicy?.defaultMode === "isolated_workspace" ? "isolated_workspace" : "shared_workspace";
270
+ const executionWorkspaceStrategy = executionWorkspacePolicy?.workspaceStrategy ?? {
271
+ type: "git_worktree",
272
+ baseRef: "",
273
+ branchTemplate: "",
274
+ worktreeParentDir: "",
275
+ };
276
+
277
+ const invalidateProject = () => {
278
+ queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(project.id) });
279
+ if (project.urlKey !== project.id) {
280
+ queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(project.urlKey) });
281
+ }
282
+ if (selectedCompanyId) {
283
+ queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(selectedCompanyId) });
284
+ }
285
+ };
286
+
287
+ const createWorkspace = useMutation({
288
+ mutationFn: (data: Record<string, unknown>) => projectsApi.createWorkspace(project.id, data),
289
+ onSuccess: () => {
290
+ setWorkspaceCwd("");
291
+ setWorkspaceRepoUrl("");
292
+ setWorkspaceMode(null);
293
+ setWorkspaceError(null);
294
+ invalidateProject();
295
+ },
296
+ });
297
+
298
+ const removeWorkspace = useMutation({
299
+ mutationFn: (workspaceId: string) => projectsApi.removeWorkspace(project.id, workspaceId),
300
+ onSuccess: () => {
301
+ setWorkspaceCwd("");
302
+ setWorkspaceRepoUrl("");
303
+ setWorkspaceMode(null);
304
+ setWorkspaceError(null);
305
+ invalidateProject();
306
+ },
307
+ });
308
+ const updateWorkspace = useMutation({
309
+ mutationFn: ({ workspaceId, data }: { workspaceId: string; data: Record<string, unknown> }) =>
310
+ projectsApi.updateWorkspace(project.id, workspaceId, data),
311
+ onSuccess: () => {
312
+ setWorkspaceCwd("");
313
+ setWorkspaceRepoUrl("");
314
+ setWorkspaceMode(null);
315
+ setWorkspaceError(null);
316
+ invalidateProject();
317
+ },
318
+ });
319
+
320
+ const removeGoal = (goalId: string) => {
321
+ if (!onUpdate && !onFieldUpdate) return;
322
+ commitField("goals", { goalIds: linkedGoalIds.filter((id) => id !== goalId) });
323
+ };
324
+
325
+ const addGoal = (goalId: string) => {
326
+ if ((!onUpdate && !onFieldUpdate) || linkedGoalIds.includes(goalId)) return;
327
+ commitField("goals", { goalIds: [...linkedGoalIds, goalId] });
328
+ setGoalOpen(false);
329
+ };
330
+
331
+ const updateExecutionWorkspacePolicy = (patch: Record<string, unknown>) => {
332
+ if (!onUpdate && !onFieldUpdate) return;
333
+ return {
334
+ executionWorkspacePolicy: {
335
+ enabled: executionWorkspacesEnabled,
336
+ defaultMode: executionWorkspaceDefaultMode,
337
+ allowIssueOverride: executionWorkspacePolicy?.allowIssueOverride ?? true,
338
+ ...executionWorkspacePolicy,
339
+ ...patch,
340
+ },
341
+ };
342
+ };
343
+
344
+ const isAbsolutePath = (value: string) => value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value);
345
+
346
+ const isGitHubRepoUrl = (value: string) => {
347
+ try {
348
+ const parsed = new URL(value);
349
+ const host = parsed.hostname.toLowerCase();
350
+ if (host !== "github.com" && host !== "www.github.com") return false;
351
+ const segments = parsed.pathname.split("/").filter(Boolean);
352
+ return segments.length >= 2;
353
+ } catch {
354
+ return false;
355
+ }
356
+ };
357
+
358
+ const isSafeExternalUrl = (value: string | null | undefined) => {
359
+ if (!value) return false;
360
+ try {
361
+ const parsed = new URL(value);
362
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
363
+ } catch {
364
+ return false;
365
+ }
366
+ };
367
+
368
+ const formatRepoUrl = (value: string) => {
369
+ try {
370
+ const parsed = new URL(value);
371
+ const segments = parsed.pathname.split("/").filter(Boolean);
372
+ if (segments.length < 2) return parsed.host;
373
+ const owner = segments[0];
374
+ const repo = segments[1]?.replace(/\.git$/i, "");
375
+ if (!owner || !repo) return parsed.host;
376
+ return `${parsed.host}/${owner}/${repo}`;
377
+ } catch {
378
+ return value;
379
+ }
380
+ };
381
+
382
+ const deriveSourceType = (cwd: string | null, repoUrl: string | null) => {
383
+ if (repoUrl) return "git_repo";
384
+ if (cwd) return "local_path";
385
+ return undefined;
386
+ };
387
+
388
+ const persistCodebase = (patch: { cwd?: string | null; repoUrl?: string | null }) => {
389
+ const nextCwd = patch.cwd !== undefined ? patch.cwd : codebase.localFolder;
390
+ const nextRepoUrl = patch.repoUrl !== undefined ? patch.repoUrl : codebase.repoUrl;
391
+ if (!nextCwd && !nextRepoUrl) {
392
+ if (primaryCodebaseWorkspace) {
393
+ removeWorkspace.mutate(primaryCodebaseWorkspace.id);
394
+ }
395
+ return;
396
+ }
397
+
398
+ const data: Record<string, unknown> = {
399
+ ...(patch.cwd !== undefined ? { cwd: patch.cwd } : {}),
400
+ ...(patch.repoUrl !== undefined ? { repoUrl: patch.repoUrl } : {}),
401
+ ...(deriveSourceType(nextCwd, nextRepoUrl) ? { sourceType: deriveSourceType(nextCwd, nextRepoUrl) } : {}),
402
+ isPrimary: true,
403
+ };
404
+
405
+ if (primaryCodebaseWorkspace) {
406
+ updateWorkspace.mutate({ workspaceId: primaryCodebaseWorkspace.id, data });
407
+ return;
408
+ }
409
+
410
+ createWorkspace.mutate(data);
411
+ };
412
+
413
+ const submitLocalWorkspace = () => {
414
+ const cwd = workspaceCwd.trim();
415
+ if (!cwd) {
416
+ setWorkspaceError(null);
417
+ persistCodebase({ cwd: null });
418
+ return;
419
+ }
420
+ if (!isAbsolutePath(cwd)) {
421
+ setWorkspaceError("Local folder must be a full absolute path.");
422
+ return;
423
+ }
424
+ setWorkspaceError(null);
425
+ persistCodebase({ cwd });
426
+ };
427
+
428
+ const submitRepoWorkspace = () => {
429
+ const repoUrl = workspaceRepoUrl.trim();
430
+ if (!repoUrl) {
431
+ setWorkspaceError(null);
432
+ persistCodebase({ repoUrl: null });
433
+ return;
434
+ }
435
+ if (!isGitHubRepoUrl(repoUrl)) {
436
+ setWorkspaceError("Repo must use a valid GitHub repo URL.");
437
+ return;
438
+ }
439
+ setWorkspaceError(null);
440
+ persistCodebase({ repoUrl });
441
+ };
442
+
443
+ const clearLocalWorkspace = () => {
444
+ const confirmed = window.confirm(
445
+ codebase.repoUrl
446
+ ? "Clear local folder from this workspace?"
447
+ : "Delete this workspace local folder?",
448
+ );
449
+ if (!confirmed) return;
450
+ persistCodebase({ cwd: null });
451
+ };
452
+
453
+ const clearRepoWorkspace = () => {
454
+ const hasLocalFolder = Boolean(codebase.localFolder);
455
+ const confirmed = window.confirm(
456
+ hasLocalFolder
457
+ ? "Clear repo from this workspace?"
458
+ : "Delete this workspace repo?",
459
+ );
460
+ if (!confirmed) return;
461
+ if (primaryCodebaseWorkspace && hasLocalFolder) {
462
+ updateWorkspace.mutate({
463
+ workspaceId: primaryCodebaseWorkspace.id,
464
+ data: { repoUrl: null, repoRef: null, defaultRef: null, sourceType: deriveSourceType(codebase.localFolder, null) },
465
+ });
466
+ return;
467
+ }
468
+ persistCodebase({ repoUrl: null });
469
+ };
470
+
471
+ return (
472
+ <div>
473
+ <div className="space-y-1 pb-4">
474
+ <PropertyRow label={<FieldLabel label="Name" state={fieldState("name")} />}>
475
+ {onUpdate || onFieldUpdate ? (
476
+ <DraftInput
477
+ value={project.name}
478
+ onCommit={(name) => commitField("name", { name })}
479
+ immediate
480
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-sm outline-none"
481
+ placeholder="Project name"
482
+ />
483
+ ) : (
484
+ <span className="text-sm">{project.name}</span>
485
+ )}
486
+ </PropertyRow>
487
+ <PropertyRow
488
+ label={<FieldLabel label="Description" state={fieldState("description")} />}
489
+ alignStart
490
+ valueClassName="space-y-0.5"
491
+ >
492
+ {onUpdate || onFieldUpdate ? (
493
+ <InlineEditor
494
+ value={project.description ?? ""}
495
+ onSave={(description) => commitField("description", { description })}
496
+ as="p"
497
+ className="text-sm text-muted-foreground"
498
+ placeholder="Add a description..."
499
+ multiline
500
+ />
501
+ ) : (
502
+ <p className="text-sm text-muted-foreground">
503
+ {project.description?.trim() || "No description"}
504
+ </p>
505
+ )}
506
+ </PropertyRow>
507
+ <PropertyRow label={<FieldLabel label="Status" state={fieldState("status")} />}>
508
+ {onUpdate || onFieldUpdate ? (
509
+ <ProjectStatusPicker
510
+ status={project.status}
511
+ onChange={(status) => commitField("status", { status })}
512
+ />
513
+ ) : (
514
+ <StatusBadge status={project.status} />
515
+ )}
516
+ </PropertyRow>
517
+ {project.leadAgentId && (
518
+ <PropertyRow label="Lead">
519
+ <span className="text-sm font-mono">{project.leadAgentId.slice(0, 8)}</span>
520
+ </PropertyRow>
521
+ )}
522
+ <PropertyRow
523
+ label={<FieldLabel label="Goals" state={fieldState("goals")} />}
524
+ alignStart
525
+ valueClassName="space-y-2"
526
+ >
527
+ {linkedGoals.length > 0 && (
528
+ <div className="flex flex-wrap gap-1.5">
529
+ {linkedGoals.map((goal) => (
530
+ <span
531
+ key={goal.id}
532
+ className="inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs"
533
+ >
534
+ <Link to={`/goals/${goal.id}`} className="hover:underline max-w-[220px] truncate">
535
+ {goal.title}
536
+ </Link>
537
+ {(onUpdate || onFieldUpdate) && (
538
+ <button
539
+ className="text-muted-foreground hover:text-foreground"
540
+ type="button"
541
+ onClick={() => removeGoal(goal.id)}
542
+ aria-label={`Remove goal ${goal.title}`}
543
+ >
544
+ <X className="h-3 w-3" />
545
+ </button>
546
+ )}
547
+ </span>
548
+ ))}
549
+ </div>
550
+ )}
551
+ {(onUpdate || onFieldUpdate) && (
552
+ <Popover open={goalOpen} onOpenChange={setGoalOpen}>
553
+ <PopoverTrigger asChild>
554
+ <Button
555
+ variant="outline"
556
+ size="xs"
557
+ className={cn("h-6 w-fit px-2", linkedGoals.length > 0 && "ml-1")}
558
+ disabled={availableGoals.length === 0}
559
+ >
560
+ <Plus className="h-3 w-3 mr-1" />
561
+ Goal
562
+ </Button>
563
+ </PopoverTrigger>
564
+ <PopoverContent className="w-56 p-1" align="start">
565
+ {availableGoals.length === 0 ? (
566
+ <div className="px-2 py-1.5 text-xs text-muted-foreground">
567
+ All goals linked.
568
+ </div>
569
+ ) : (
570
+ availableGoals.map((goal) => (
571
+ <button
572
+ key={goal.id}
573
+ className="flex items-center w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50"
574
+ onClick={() => addGoal(goal.id)}
575
+ >
576
+ {goal.title}
577
+ </button>
578
+ ))
579
+ )}
580
+ </PopoverContent>
581
+ </Popover>
582
+ )}
583
+ </PropertyRow>
584
+ <PropertyRow label={<FieldLabel label="Created" state="idle" />}>
585
+ <span className="text-sm">{formatDate(project.createdAt)}</span>
586
+ </PropertyRow>
587
+ <PropertyRow label={<FieldLabel label="Updated" state="idle" />}>
588
+ <span className="text-sm">{formatDate(project.updatedAt)}</span>
589
+ </PropertyRow>
590
+ {project.targetDate && (
591
+ <PropertyRow label={<FieldLabel label="Target Date" state="idle" />}>
592
+ <span className="text-sm">{formatDate(project.targetDate)}</span>
593
+ </PropertyRow>
594
+ )}
595
+ </div>
596
+
597
+ <Separator className="my-4" />
598
+
599
+ <div className="space-y-1 py-4">
600
+ <div className="space-y-2">
601
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
602
+ <span>Codebase</span>
603
+ <Tooltip>
604
+ <TooltipTrigger asChild>
605
+ <button
606
+ type="button"
607
+ className="inline-flex h-4 w-4 items-center justify-center rounded-full border border-border text-[10px] text-muted-foreground hover:text-foreground"
608
+ aria-label="Codebase help"
609
+ >
610
+ ?
611
+ </button>
612
+ </TooltipTrigger>
613
+ <TooltipContent side="top">
614
+ Repo identifies the source of truth. Local folder is the default place agents write code.
615
+ </TooltipContent>
616
+ </Tooltip>
617
+ </div>
618
+ <div className="space-y-2 rounded-md border border-border/70 p-3">
619
+ <div className="space-y-1">
620
+ <div className="text-[11px] uppercase tracking-wide text-muted-foreground">Repo</div>
621
+ {codebase.repoUrl ? (
622
+ <div className="flex items-center justify-between gap-2">
623
+ {isSafeExternalUrl(codebase.repoUrl) ? (
624
+ <a
625
+ href={codebase.repoUrl}
626
+ target="_blank"
627
+ rel="noreferrer"
628
+ className="inline-flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground hover:underline"
629
+ >
630
+ <Github className="h-3 w-3 shrink-0" />
631
+ <span className="truncate">{formatRepoUrl(codebase.repoUrl)}</span>
632
+ <ExternalLink className="h-3 w-3 shrink-0" />
633
+ </a>
634
+ ) : (
635
+ <div className="inline-flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground">
636
+ <Github className="h-3 w-3 shrink-0" />
637
+ <span className="truncate">{codebase.repoUrl}</span>
638
+ </div>
639
+ )}
640
+ <div className="flex items-center gap-1">
641
+ <Button
642
+ variant="outline"
643
+ size="xs"
644
+ className="h-6 px-2"
645
+ onClick={() => {
646
+ setWorkspaceMode("repo");
647
+ setWorkspaceRepoUrl(codebase.repoUrl ?? "");
648
+ setWorkspaceError(null);
649
+ }}
650
+ >
651
+ Change repo
652
+ </Button>
653
+ <Button
654
+ variant="ghost"
655
+ size="icon-xs"
656
+ onClick={clearRepoWorkspace}
657
+ aria-label="Clear repo"
658
+ >
659
+ <Trash2 className="h-3 w-3" />
660
+ </Button>
661
+ </div>
662
+ </div>
663
+ ) : (
664
+ <div className="flex items-center justify-between gap-2">
665
+ <div className="text-xs text-muted-foreground">Not set.</div>
666
+ <Button
667
+ variant="outline"
668
+ size="xs"
669
+ className="h-6 px-2"
670
+ onClick={() => {
671
+ setWorkspaceMode("repo");
672
+ setWorkspaceRepoUrl(codebase.repoUrl ?? "");
673
+ setWorkspaceError(null);
674
+ }}
675
+ >
676
+ Set repo
677
+ </Button>
678
+ </div>
679
+ )}
680
+ </div>
681
+
682
+ <div className="space-y-1">
683
+ <div className="text-[11px] uppercase tracking-wide text-muted-foreground">Local folder</div>
684
+ <div className="flex items-center justify-between gap-2">
685
+ <div className="min-w-0 space-y-1">
686
+ <div className="min-w-0 truncate font-mono text-xs text-muted-foreground">
687
+ {codebase.effectiveLocalFolder}
688
+ </div>
689
+ {codebase.origin === "managed_checkout" && (
690
+ <div className="text-[11px] text-muted-foreground">Corporate-managed folder.</div>
691
+ )}
692
+ </div>
693
+ <div className="flex items-center gap-1">
694
+ <Button
695
+ variant="outline"
696
+ size="xs"
697
+ className="h-6 px-2"
698
+ onClick={() => {
699
+ setWorkspaceMode("local");
700
+ setWorkspaceCwd(codebase.localFolder ?? "");
701
+ setWorkspaceError(null);
702
+ }}
703
+ >
704
+ {codebase.localFolder ? "Change local folder" : "Set local folder"}
705
+ </Button>
706
+ {codebase.localFolder ? (
707
+ <Button
708
+ variant="ghost"
709
+ size="icon-xs"
710
+ onClick={clearLocalWorkspace}
711
+ aria-label="Clear local folder"
712
+ >
713
+ <Trash2 className="h-3 w-3" />
714
+ </Button>
715
+ ) : null}
716
+ </div>
717
+ </div>
718
+ </div>
719
+
720
+ {hasAdditionalLegacyWorkspaces && (
721
+ <div className="text-[11px] text-muted-foreground">
722
+ Additional legacy workspace records exist on this project. Corporate is using the primary workspace as the codebase view.
723
+ </div>
724
+ )}
725
+
726
+ {primaryCodebaseWorkspace?.runtimeServices && primaryCodebaseWorkspace.runtimeServices.length > 0 ? (
727
+ <div className="space-y-1">
728
+ {primaryCodebaseWorkspace.runtimeServices.map((service) => (
729
+ <div
730
+ key={service.id}
731
+ className="flex items-center justify-between gap-2 rounded-md border border-border/60 px-2 py-1"
732
+ >
733
+ <div className="min-w-0 space-y-0.5">
734
+ <div className="flex items-center gap-2">
735
+ <span className="text-[11px] font-medium">{service.serviceName}</span>
736
+ <span
737
+ className={cn(
738
+ "rounded-full px-1.5 py-0.5 text-[10px] uppercase tracking-wide",
739
+ service.status === "running"
740
+ ? "bg-green-500/15 text-green-700 dark:text-green-300"
741
+ : service.status === "failed"
742
+ ? "bg-red-500/15 text-red-700 dark:text-red-300"
743
+ : "bg-muted text-muted-foreground",
744
+ )}
745
+ >
746
+ {service.status}
747
+ </span>
748
+ </div>
749
+ <div className="text-[11px] text-muted-foreground">
750
+ {service.url ? (
751
+ <a
752
+ href={service.url}
753
+ target="_blank"
754
+ rel="noreferrer"
755
+ className="hover:text-foreground hover:underline"
756
+ >
757
+ {service.url}
758
+ </a>
759
+ ) : (
760
+ service.command ?? "No URL"
761
+ )}
762
+ </div>
763
+ </div>
764
+ <div className="text-[10px] text-muted-foreground whitespace-nowrap">
765
+ {service.lifecycle}
766
+ </div>
767
+ </div>
768
+ ))}
769
+ </div>
770
+ ) : null}
771
+ </div>
772
+ {workspaceMode === "local" && (
773
+ <div className="space-y-1.5 rounded-md border border-border p-2">
774
+ <div className="flex items-center gap-2">
775
+ <input
776
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
777
+ value={workspaceCwd}
778
+ onChange={(e) => setWorkspaceCwd(e.target.value)}
779
+ placeholder="/absolute/path/to/workspace"
780
+ />
781
+ <ChoosePathButton />
782
+ </div>
783
+ <div className="flex items-center gap-2">
784
+ <Button
785
+ variant="outline"
786
+ size="xs"
787
+ className="h-6 px-2"
788
+ disabled={(!workspaceCwd.trim() && !primaryCodebaseWorkspace) || createWorkspace.isPending || updateWorkspace.isPending}
789
+ onClick={submitLocalWorkspace}
790
+ >
791
+ Save
792
+ </Button>
793
+ <Button
794
+ variant="ghost"
795
+ size="xs"
796
+ className="h-6 px-2"
797
+ onClick={() => {
798
+ setWorkspaceMode(null);
799
+ setWorkspaceCwd("");
800
+ setWorkspaceError(null);
801
+ }}
802
+ >
803
+ Cancel
804
+ </Button>
805
+ </div>
806
+ </div>
807
+ )}
808
+ {workspaceMode === "repo" && (
809
+ <div className="space-y-1.5 rounded-md border border-border p-2">
810
+ <input
811
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs outline-none"
812
+ value={workspaceRepoUrl}
813
+ onChange={(e) => setWorkspaceRepoUrl(e.target.value)}
814
+ placeholder="https://github.com/org/repo"
815
+ />
816
+ <div className="flex items-center gap-2">
817
+ <Button
818
+ variant="outline"
819
+ size="xs"
820
+ className="h-6 px-2"
821
+ disabled={(!workspaceRepoUrl.trim() && !primaryCodebaseWorkspace) || createWorkspace.isPending || updateWorkspace.isPending}
822
+ onClick={submitRepoWorkspace}
823
+ >
824
+ Save
825
+ </Button>
826
+ <Button
827
+ variant="ghost"
828
+ size="xs"
829
+ className="h-6 px-2"
830
+ onClick={() => {
831
+ setWorkspaceMode(null);
832
+ setWorkspaceRepoUrl("");
833
+ setWorkspaceError(null);
834
+ }}
835
+ >
836
+ Cancel
837
+ </Button>
838
+ </div>
839
+ </div>
840
+ )}
841
+ {workspaceError && (
842
+ <p className="text-xs text-destructive">{workspaceError}</p>
843
+ )}
844
+ {createWorkspace.isError && (
845
+ <p className="text-xs text-destructive">Failed to save workspace.</p>
846
+ )}
847
+ {removeWorkspace.isError && (
848
+ <p className="text-xs text-destructive">Failed to delete workspace.</p>
849
+ )}
850
+ {updateWorkspace.isError && (
851
+ <p className="text-xs text-destructive">Failed to update workspace.</p>
852
+ )}
853
+ </div>
854
+
855
+ {isolatedWorkspacesEnabled ? (
856
+ <>
857
+ <Separator className="my-4" />
858
+
859
+ <div className="py-1.5 space-y-2">
860
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
861
+ <span>Execution Workspaces</span>
862
+ <Tooltip>
863
+ <TooltipTrigger asChild>
864
+ <button
865
+ type="button"
866
+ className="inline-flex h-4 w-4 items-center justify-center rounded-full border border-border text-[10px] text-muted-foreground hover:text-foreground"
867
+ aria-label="Execution workspaces help"
868
+ >
869
+ ?
870
+ </button>
871
+ </TooltipTrigger>
872
+ <TooltipContent side="top">
873
+ Project-owned defaults for isolated issue checkouts and execution workspace behavior.
874
+ </TooltipContent>
875
+ </Tooltip>
876
+ </div>
877
+ <div className="space-y-3">
878
+ <div className="flex items-center justify-between gap-3">
879
+ <div className="space-y-0.5">
880
+ <div className="flex items-center gap-2 text-sm font-medium">
881
+ <span>Enable isolated issue checkouts</span>
882
+ <SaveIndicator state={fieldState("execution_workspace_enabled")} />
883
+ </div>
884
+ <div className="text-xs text-muted-foreground">
885
+ Let issues choose between the project's primary checkout and an isolated execution workspace.
886
+ </div>
887
+ </div>
888
+ {onUpdate || onFieldUpdate ? (
889
+ <button
890
+ data-slot="toggle"
891
+ className={cn(
892
+ "relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
893
+ executionWorkspacesEnabled ? "bg-green-600" : "bg-muted",
894
+ )}
895
+ type="button"
896
+ onClick={() =>
897
+ commitField(
898
+ "execution_workspace_enabled",
899
+ updateExecutionWorkspacePolicy({ enabled: !executionWorkspacesEnabled })!,
900
+ )}
901
+ >
902
+ <span
903
+ className={cn(
904
+ "inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
905
+ executionWorkspacesEnabled ? "translate-x-4.5" : "translate-x-0.5",
906
+ )}
907
+ />
908
+ </button>
909
+ ) : (
910
+ <span className="text-xs text-muted-foreground">
911
+ {executionWorkspacesEnabled ? "Enabled" : "Disabled"}
912
+ </span>
913
+ )}
914
+ </div>
915
+
916
+ {executionWorkspacesEnabled ? (
917
+ <div className="space-y-3">
918
+ <div className="flex items-center justify-between gap-3">
919
+ <div className="space-y-0.5">
920
+ <div className="flex items-center gap-2 text-sm">
921
+ <span>New issues default to isolated checkout</span>
922
+ <SaveIndicator state={fieldState("execution_workspace_default_mode")} />
923
+ </div>
924
+ <div className="text-[11px] text-muted-foreground">
925
+ If disabled, new issues stay on the project's primary checkout unless someone opts in.
926
+ </div>
927
+ </div>
928
+ <button
929
+ data-slot="toggle"
930
+ className={cn(
931
+ "relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
932
+ executionWorkspaceDefaultMode === "isolated_workspace" ? "bg-green-600" : "bg-muted",
933
+ )}
934
+ type="button"
935
+ onClick={() =>
936
+ commitField(
937
+ "execution_workspace_default_mode",
938
+ updateExecutionWorkspacePolicy({
939
+ defaultMode:
940
+ executionWorkspaceDefaultMode === "isolated_workspace"
941
+ ? "shared_workspace"
942
+ : "isolated_workspace",
943
+ })!,
944
+ )}
945
+ >
946
+ <span
947
+ className={cn(
948
+ "inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
949
+ executionWorkspaceDefaultMode === "isolated_workspace"
950
+ ? "translate-x-4.5"
951
+ : "translate-x-0.5",
952
+ )}
953
+ />
954
+ </button>
955
+ </div>
956
+
957
+ <div className="border-t border-border/60 pt-2">
958
+ <button
959
+ type="button"
960
+ className="flex w-full items-center gap-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
961
+ onClick={() => setExecutionWorkspaceAdvancedOpen((open) => !open)}
962
+ >
963
+ {executionWorkspaceAdvancedOpen
964
+ ? "Hide advanced checkout settings"
965
+ : "Show advanced checkout settings"}
966
+ </button>
967
+ </div>
968
+
969
+ {executionWorkspaceAdvancedOpen ? (
970
+ <div className="space-y-3">
971
+ <div className="text-xs text-muted-foreground">
972
+ Host-managed implementation: <span className="text-foreground">Git worktree</span>
973
+ </div>
974
+ <div>
975
+ <div className="mb-1 flex items-center gap-1.5">
976
+ <label className="flex items-center gap-2 text-xs text-muted-foreground">
977
+ <span>Base ref</span>
978
+ <SaveIndicator state={fieldState("execution_workspace_base_ref")} />
979
+ </label>
980
+ </div>
981
+ <DraftInput
982
+ value={executionWorkspaceStrategy.baseRef ?? ""}
983
+ onCommit={(value) =>
984
+ commitField("execution_workspace_base_ref", {
985
+ ...updateExecutionWorkspacePolicy({
986
+ workspaceStrategy: {
987
+ ...executionWorkspaceStrategy,
988
+ type: "git_worktree",
989
+ baseRef: value || null,
990
+ },
991
+ })!,
992
+ })}
993
+ immediate
994
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
995
+ placeholder="origin/main"
996
+ />
997
+ </div>
998
+ <div>
999
+ <div className="mb-1 flex items-center gap-1.5">
1000
+ <label className="flex items-center gap-2 text-xs text-muted-foreground">
1001
+ <span>Branch template</span>
1002
+ <SaveIndicator state={fieldState("execution_workspace_branch_template")} />
1003
+ </label>
1004
+ </div>
1005
+ <DraftInput
1006
+ value={executionWorkspaceStrategy.branchTemplate ?? ""}
1007
+ onCommit={(value) =>
1008
+ commitField("execution_workspace_branch_template", {
1009
+ ...updateExecutionWorkspacePolicy({
1010
+ workspaceStrategy: {
1011
+ ...executionWorkspaceStrategy,
1012
+ type: "git_worktree",
1013
+ branchTemplate: value || null,
1014
+ },
1015
+ })!,
1016
+ })}
1017
+ immediate
1018
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
1019
+ placeholder="{{issue.identifier}}-{{slug}}"
1020
+ />
1021
+ </div>
1022
+ <div>
1023
+ <div className="mb-1 flex items-center gap-1.5">
1024
+ <label className="flex items-center gap-2 text-xs text-muted-foreground">
1025
+ <span>Worktree parent dir</span>
1026
+ <SaveIndicator state={fieldState("execution_workspace_worktree_parent_dir")} />
1027
+ </label>
1028
+ </div>
1029
+ <DraftInput
1030
+ value={executionWorkspaceStrategy.worktreeParentDir ?? ""}
1031
+ onCommit={(value) =>
1032
+ commitField("execution_workspace_worktree_parent_dir", {
1033
+ ...updateExecutionWorkspacePolicy({
1034
+ workspaceStrategy: {
1035
+ ...executionWorkspaceStrategy,
1036
+ type: "git_worktree",
1037
+ worktreeParentDir: value || null,
1038
+ },
1039
+ })!,
1040
+ })}
1041
+ immediate
1042
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
1043
+ placeholder=".paperclip/worktrees"
1044
+ />
1045
+ </div>
1046
+ <div>
1047
+ <div className="mb-1 flex items-center gap-1.5">
1048
+ <label className="flex items-center gap-2 text-xs text-muted-foreground">
1049
+ <span>Provision command</span>
1050
+ <SaveIndicator state={fieldState("execution_workspace_provision_command")} />
1051
+ </label>
1052
+ </div>
1053
+ <DraftInput
1054
+ value={executionWorkspaceStrategy.provisionCommand ?? ""}
1055
+ onCommit={(value) =>
1056
+ commitField("execution_workspace_provision_command", {
1057
+ ...updateExecutionWorkspacePolicy({
1058
+ workspaceStrategy: {
1059
+ ...executionWorkspaceStrategy,
1060
+ type: "git_worktree",
1061
+ provisionCommand: value || null,
1062
+ },
1063
+ })!,
1064
+ })}
1065
+ immediate
1066
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
1067
+ placeholder="bash ./scripts/provision-worktree.sh"
1068
+ />
1069
+ </div>
1070
+ <div>
1071
+ <div className="mb-1 flex items-center gap-1.5">
1072
+ <label className="flex items-center gap-2 text-xs text-muted-foreground">
1073
+ <span>Teardown command</span>
1074
+ <SaveIndicator state={fieldState("execution_workspace_teardown_command")} />
1075
+ </label>
1076
+ </div>
1077
+ <DraftInput
1078
+ value={executionWorkspaceStrategy.teardownCommand ?? ""}
1079
+ onCommit={(value) =>
1080
+ commitField("execution_workspace_teardown_command", {
1081
+ ...updateExecutionWorkspacePolicy({
1082
+ workspaceStrategy: {
1083
+ ...executionWorkspaceStrategy,
1084
+ type: "git_worktree",
1085
+ teardownCommand: value || null,
1086
+ },
1087
+ })!,
1088
+ })}
1089
+ immediate
1090
+ className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
1091
+ placeholder="bash ./scripts/teardown-worktree.sh"
1092
+ />
1093
+ </div>
1094
+ <p className="text-[11px] text-muted-foreground">
1095
+ Provision runs inside the derived worktree before agent execution. Teardown is stored here for
1096
+ future cleanup flows.
1097
+ </p>
1098
+ </div>
1099
+ ) : null}
1100
+ </div>
1101
+ ) : null}
1102
+ </div>
1103
+ </div>
1104
+ </>
1105
+ ) : null}
1106
+
1107
+ </div>
1108
+
1109
+ {onArchive && (
1110
+ <>
1111
+ <Separator className="my-4" />
1112
+ <div className="space-y-4 py-4">
1113
+ <div className="text-xs font-medium text-destructive uppercase tracking-wide">
1114
+ Danger Zone
1115
+ </div>
1116
+ <ArchiveDangerZone
1117
+ project={project}
1118
+ onArchive={onArchive}
1119
+ archivePending={archivePending}
1120
+ />
1121
+ </div>
1122
+ </>
1123
+ )}
1124
+ </div>
1125
+ );
1126
+ }
1127
+