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,836 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { Puzzle, ArrowLeft, ShieldAlert, ActivitySquare, CheckCircle, XCircle, Loader2, Clock, Cpu, Webhook, CalendarClock, AlertTriangle } from "lucide-react";
4
+ import { useCompany } from "@/context/CompanyContext";
5
+ import { useBreadcrumbs } from "@/context/BreadcrumbContext";
6
+ import { Link, Navigate, useParams } from "@/lib/router";
7
+ import { PluginSlotMount, usePluginSlots } from "@/plugins/slots";
8
+ import { pluginsApi } from "@/api/plugins";
9
+ import { queryKeys } from "@/lib/queryKeys";
10
+ import { Button } from "@/components/ui/button";
11
+ import { Badge } from "@/components/ui/badge";
12
+ import {
13
+ Card,
14
+ CardContent,
15
+ CardDescription,
16
+ CardHeader,
17
+ CardTitle,
18
+ } from "@/components/ui/card";
19
+ import { Separator } from "@/components/ui/separator";
20
+ import { Tabs, TabsContent } from "@/components/ui/tabs";
21
+ import { PageTabBar } from "@/components/PageTabBar";
22
+ import {
23
+ JsonSchemaForm,
24
+ validateJsonSchemaForm,
25
+ getDefaultValues,
26
+ type JsonSchemaNode,
27
+ } from "@/components/JsonSchemaForm";
28
+
29
+ /**
30
+ * PluginSettings page component.
31
+ *
32
+ * Detailed settings and diagnostics page for a single installed plugin.
33
+ * Navigated to from {@link PluginManager} via the Settings gear icon.
34
+ *
35
+ * Displays:
36
+ * - Plugin identity: display name, id, version, description, categories.
37
+ * - Manifest-declared capabilities (what data and features the plugin can access).
38
+ * - Health check results (only for `ready` plugins; polled every 30 seconds).
39
+ * - Runtime dashboard: worker status/uptime, recent job runs, webhook deliveries.
40
+ * - Auto-generated config form from `instanceConfigSchema` (when no custom settings page).
41
+ * - Plugin-contributed settings UI via `<PluginSlotOutlet type="settingsPage" />`.
42
+ *
43
+ * Data flow:
44
+ * - `GET /api/plugins/:pluginId` — plugin record (refreshes on mount).
45
+ * - `GET /api/plugins/:pluginId/health` — health diagnostics (polling).
46
+ * Only fetched when `plugin.status === "ready"`.
47
+ * - `GET /api/plugins/:pluginId/dashboard` — aggregated runtime dashboard data (polling).
48
+ * - `GET /api/plugins/:pluginId/config` — current config values.
49
+ * - `POST /api/plugins/:pluginId/config` — save config values.
50
+ * - `POST /api/plugins/:pluginId/config/test` — test configuration.
51
+ *
52
+ * URL params:
53
+ * - `companyPrefix` — the company slug (for breadcrumb links).
54
+ * - `pluginId` — UUID of the plugin to display.
55
+ *
56
+ * @see PluginManager — parent list page.
57
+ * @see doc/plugins/PLUGIN_SPEC.md §13 — Plugin Health Checks.
58
+ * @see doc/plugins/PLUGIN_SPEC.md §19.8 — Plugin Settings UI.
59
+ */
60
+ export function PluginSettings() {
61
+ const { selectedCompany, selectedCompanyId } = useCompany();
62
+ const { setBreadcrumbs } = useBreadcrumbs();
63
+ const { companyPrefix, pluginId } = useParams<{ companyPrefix?: string; pluginId: string }>();
64
+ const [activeTab, setActiveTab] = useState<"configuration" | "status">("configuration");
65
+
66
+ const { data: plugin, isLoading: pluginLoading } = useQuery({
67
+ queryKey: queryKeys.plugins.detail(pluginId!),
68
+ queryFn: () => pluginsApi.get(pluginId!),
69
+ enabled: !!pluginId,
70
+ });
71
+
72
+ const { data: healthData, isLoading: healthLoading } = useQuery({
73
+ queryKey: queryKeys.plugins.health(pluginId!),
74
+ queryFn: () => pluginsApi.health(pluginId!),
75
+ enabled: !!pluginId && plugin?.status === "ready",
76
+ refetchInterval: 30000,
77
+ });
78
+
79
+ const { data: dashboardData } = useQuery({
80
+ queryKey: queryKeys.plugins.dashboard(pluginId!),
81
+ queryFn: () => pluginsApi.dashboard(pluginId!),
82
+ enabled: !!pluginId,
83
+ refetchInterval: 30000,
84
+ });
85
+
86
+ const { data: recentLogs } = useQuery({
87
+ queryKey: queryKeys.plugins.logs(pluginId!),
88
+ queryFn: () => pluginsApi.logs(pluginId!, { limit: 50 }),
89
+ enabled: !!pluginId && plugin?.status === "ready",
90
+ refetchInterval: 30000,
91
+ });
92
+
93
+ // Fetch existing config for the plugin
94
+ const configSchema = plugin?.manifestJson?.instanceConfigSchema as JsonSchemaNode | undefined;
95
+ const hasConfigSchema = configSchema && configSchema.properties && Object.keys(configSchema.properties).length > 0;
96
+
97
+ const { data: configData, isLoading: configLoading } = useQuery({
98
+ queryKey: queryKeys.plugins.config(pluginId!),
99
+ queryFn: () => pluginsApi.getConfig(pluginId!),
100
+ enabled: !!pluginId && !!hasConfigSchema,
101
+ });
102
+
103
+ const { slots } = usePluginSlots({
104
+ slotTypes: ["settingsPage"],
105
+ companyId: selectedCompanyId,
106
+ enabled: !!selectedCompanyId,
107
+ });
108
+
109
+ // Filter slots to only show settings pages for this specific plugin
110
+ const pluginSlots = slots.filter((slot) => slot.pluginId === pluginId);
111
+
112
+ // If the plugin has a custom settingsPage slot, prefer that over auto-generated form
113
+ const hasCustomSettingsPage = pluginSlots.length > 0;
114
+
115
+ useEffect(() => {
116
+ setBreadcrumbs([
117
+ { label: selectedCompany?.name ?? "Company", href: "/dashboard" },
118
+ { label: "Settings", href: "/instance/settings/heartbeats" },
119
+ { label: "Plugins", href: "/instance/settings/plugins" },
120
+ { label: plugin?.manifestJson?.displayName ?? plugin?.packageName ?? "Plugin Details" },
121
+ ]);
122
+ }, [selectedCompany?.name, setBreadcrumbs, companyPrefix, plugin]);
123
+
124
+ useEffect(() => {
125
+ setActiveTab("configuration");
126
+ }, [pluginId]);
127
+
128
+ if (pluginLoading) {
129
+ return <div className="p-4 text-sm text-muted-foreground">Loading plugin details...</div>;
130
+ }
131
+
132
+ if (!plugin) {
133
+ return <Navigate to="/instance/settings/plugins" replace />;
134
+ }
135
+
136
+ const displayStatus = plugin.status;
137
+ const statusVariant =
138
+ plugin.status === "ready"
139
+ ? "default"
140
+ : plugin.status === "error"
141
+ ? "destructive"
142
+ : "secondary";
143
+ const pluginDescription = plugin.manifestJson.description || "No description provided.";
144
+ const pluginCapabilities = plugin.manifestJson.capabilities ?? [];
145
+
146
+ return (
147
+ <div className="space-y-6 max-w-5xl">
148
+ <div className="flex items-center gap-4">
149
+ <Link to="/instance/settings/plugins">
150
+ <Button variant="outline" size="icon" className="h-8 w-8">
151
+ <ArrowLeft className="h-4 w-4" />
152
+ </Button>
153
+ </Link>
154
+ <div className="flex items-center gap-2">
155
+ <Puzzle className="h-6 w-6 text-muted-foreground" />
156
+ <h1 className="text-xl font-semibold">{plugin.manifestJson.displayName ?? plugin.packageName}</h1>
157
+ <Badge variant={statusVariant} className="ml-2">
158
+ {displayStatus}
159
+ </Badge>
160
+ <Badge variant="outline" className="ml-1">
161
+ v{plugin.manifestJson.version ?? plugin.version}
162
+ </Badge>
163
+ </div>
164
+ </div>
165
+
166
+ <Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as "configuration" | "status")} className="space-y-6">
167
+ <PageTabBar
168
+ align="start"
169
+ items={[
170
+ { value: "configuration", label: "Configuration" },
171
+ { value: "status", label: "Status" },
172
+ ]}
173
+ value={activeTab}
174
+ onValueChange={(value) => setActiveTab(value as "configuration" | "status")}
175
+ />
176
+
177
+ <TabsContent value="configuration" className="space-y-6">
178
+ <div className="space-y-8">
179
+ <section className="space-y-5">
180
+ <h2 className="text-base font-semibold">About</h2>
181
+ <div className="grid gap-8 lg:grid-cols-[minmax(0,1.4fr)_minmax(220px,0.8fr)]">
182
+ <div className="space-y-2">
183
+ <h3 className="text-sm font-medium text-muted-foreground">Description</h3>
184
+ <p className="text-sm leading-6 text-foreground/90">{pluginDescription}</p>
185
+ </div>
186
+ <div className="space-y-4 text-sm">
187
+ <div className="space-y-1.5">
188
+ <h3 className="font-medium text-muted-foreground">Author</h3>
189
+ <p className="text-foreground">{plugin.manifestJson.author}</p>
190
+ </div>
191
+ <div className="space-y-2">
192
+ <h3 className="font-medium text-muted-foreground">Categories</h3>
193
+ <div className="flex flex-wrap gap-2">
194
+ {plugin.categories.length > 0 ? (
195
+ plugin.categories.map((category) => (
196
+ <Badge key={category} variant="outline" className="capitalize">
197
+ {category}
198
+ </Badge>
199
+ ))
200
+ ) : (
201
+ <span className="text-foreground">None</span>
202
+ )}
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </section>
208
+
209
+ <Separator />
210
+
211
+ <section className="space-y-4">
212
+ <div className="space-y-1">
213
+ <h2 className="text-base font-semibold">Settings</h2>
214
+ </div>
215
+ {hasCustomSettingsPage ? (
216
+ <div className="space-y-3">
217
+ {pluginSlots.map((slot) => (
218
+ <PluginSlotMount
219
+ key={`${slot.pluginKey}:${slot.id}`}
220
+ slot={slot}
221
+ context={{
222
+ companyId: selectedCompanyId,
223
+ companyPrefix: companyPrefix ?? null,
224
+ }}
225
+ missingBehavior="placeholder"
226
+ />
227
+ ))}
228
+ </div>
229
+ ) : hasConfigSchema ? (
230
+ <PluginConfigForm
231
+ pluginId={pluginId!}
232
+ schema={configSchema!}
233
+ initialValues={configData?.configJson}
234
+ isLoading={configLoading}
235
+ pluginStatus={plugin.status}
236
+ supportsConfigTest={(plugin as unknown as { supportsConfigTest?: boolean }).supportsConfigTest === true}
237
+ />
238
+ ) : (
239
+ <p className="text-sm text-muted-foreground">
240
+ This plugin does not require any settings.
241
+ </p>
242
+ )}
243
+ </section>
244
+ </div>
245
+ </TabsContent>
246
+
247
+ <TabsContent value="status" className="space-y-6">
248
+ <div className="grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_320px]">
249
+ <div className="space-y-6">
250
+ <Card>
251
+ <CardHeader>
252
+ <CardTitle className="text-base flex items-center gap-1.5">
253
+ <Cpu className="h-4 w-4" />
254
+ Runtime Dashboard
255
+ </CardTitle>
256
+ <CardDescription>
257
+ Worker process, scheduled jobs, and webhook deliveries
258
+ </CardDescription>
259
+ </CardHeader>
260
+ <CardContent className="space-y-6">
261
+ {dashboardData ? (
262
+ <>
263
+ <div>
264
+ <h3 className="text-sm font-medium mb-3 flex items-center gap-1.5">
265
+ <Cpu className="h-3.5 w-3.5 text-muted-foreground" />
266
+ Worker Process
267
+ </h3>
268
+ {dashboardData.worker ? (
269
+ <div className="grid grid-cols-2 gap-3 text-sm">
270
+ <div className="flex justify-between">
271
+ <span className="text-muted-foreground">Status</span>
272
+ <Badge variant={dashboardData.worker.status === "running" ? "default" : "secondary"}>
273
+ {dashboardData.worker.status}
274
+ </Badge>
275
+ </div>
276
+ <div className="flex justify-between">
277
+ <span className="text-muted-foreground">PID</span>
278
+ <span className="font-mono text-xs">{dashboardData.worker.pid ?? "—"}</span>
279
+ </div>
280
+ <div className="flex justify-between">
281
+ <span className="text-muted-foreground">Uptime</span>
282
+ <span className="text-xs">{formatUptime(dashboardData.worker.uptime)}</span>
283
+ </div>
284
+ <div className="flex justify-between">
285
+ <span className="text-muted-foreground">Pending RPCs</span>
286
+ <span className="text-xs">{dashboardData.worker.pendingRequests}</span>
287
+ </div>
288
+ {dashboardData.worker.totalCrashes > 0 && (
289
+ <>
290
+ <div className="flex justify-between col-span-2">
291
+ <span className="text-muted-foreground flex items-center gap-1">
292
+ <AlertTriangle className="h-3 w-3 text-amber-500" />
293
+ Crashes
294
+ </span>
295
+ <span className="text-xs">
296
+ {dashboardData.worker.consecutiveCrashes} consecutive / {dashboardData.worker.totalCrashes} total
297
+ </span>
298
+ </div>
299
+ {dashboardData.worker.lastCrashAt && (
300
+ <div className="flex justify-between col-span-2">
301
+ <span className="text-muted-foreground">Last Crash</span>
302
+ <span className="text-xs">{formatTimestamp(dashboardData.worker.lastCrashAt)}</span>
303
+ </div>
304
+ )}
305
+ </>
306
+ )}
307
+ </div>
308
+ ) : (
309
+ <p className="text-sm text-muted-foreground italic">No worker process registered.</p>
310
+ )}
311
+ </div>
312
+
313
+ <Separator />
314
+
315
+ <div>
316
+ <h3 className="text-sm font-medium mb-3 flex items-center gap-1.5">
317
+ <CalendarClock className="h-3.5 w-3.5 text-muted-foreground" />
318
+ Recent Job Runs
319
+ </h3>
320
+ {dashboardData.recentJobRuns.length > 0 ? (
321
+ <div className="space-y-2">
322
+ {dashboardData.recentJobRuns.map((run) => (
323
+ <div
324
+ key={run.id}
325
+ className="flex items-center justify-between gap-2 rounded-md bg-muted/50 px-2 py-1.5 text-sm"
326
+ >
327
+ <div className="flex min-w-0 items-center gap-2">
328
+ <JobStatusDot status={run.status} />
329
+ <span className="truncate font-mono text-xs" title={run.jobKey ?? run.jobId}>
330
+ {run.jobKey ?? run.jobId.slice(0, 8)}
331
+ </span>
332
+ <Badge variant="outline" className="px-1 py-0 text-[10px]">
333
+ {run.trigger}
334
+ </Badge>
335
+ </div>
336
+ <div className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
337
+ {run.durationMs != null ? <span>{formatDuration(run.durationMs)}</span> : null}
338
+ <span title={run.createdAt}>{formatRelativeTime(run.createdAt)}</span>
339
+ </div>
340
+ </div>
341
+ ))}
342
+ </div>
343
+ ) : (
344
+ <p className="text-sm text-muted-foreground italic">No job runs recorded yet.</p>
345
+ )}
346
+ </div>
347
+
348
+ <Separator />
349
+
350
+ <div>
351
+ <h3 className="text-sm font-medium mb-3 flex items-center gap-1.5">
352
+ <Webhook className="h-3.5 w-3.5 text-muted-foreground" />
353
+ Recent Webhook Deliveries
354
+ </h3>
355
+ {dashboardData.recentWebhookDeliveries.length > 0 ? (
356
+ <div className="space-y-2">
357
+ {dashboardData.recentWebhookDeliveries.map((delivery) => (
358
+ <div
359
+ key={delivery.id}
360
+ className="flex items-center justify-between gap-2 rounded-md bg-muted/50 px-2 py-1.5 text-sm"
361
+ >
362
+ <div className="flex min-w-0 items-center gap-2">
363
+ <DeliveryStatusDot status={delivery.status} />
364
+ <span className="truncate font-mono text-xs" title={delivery.webhookKey}>
365
+ {delivery.webhookKey}
366
+ </span>
367
+ </div>
368
+ <div className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
369
+ {delivery.durationMs != null ? <span>{formatDuration(delivery.durationMs)}</span> : null}
370
+ <span title={delivery.createdAt}>{formatRelativeTime(delivery.createdAt)}</span>
371
+ </div>
372
+ </div>
373
+ ))}
374
+ </div>
375
+ ) : (
376
+ <p className="text-sm text-muted-foreground italic">No webhook deliveries recorded yet.</p>
377
+ )}
378
+ </div>
379
+
380
+ <div className="flex items-center gap-1.5 border-t border-border/50 pt-2 text-xs text-muted-foreground">
381
+ <Clock className="h-3 w-3" />
382
+ Last checked: {new Date(dashboardData.checkedAt).toLocaleTimeString()}
383
+ </div>
384
+ </>
385
+ ) : (
386
+ <p className="text-sm text-muted-foreground">
387
+ Runtime diagnostics are unavailable right now.
388
+ </p>
389
+ )}
390
+ </CardContent>
391
+ </Card>
392
+
393
+ {recentLogs && recentLogs.length > 0 ? (
394
+ <Card>
395
+ <CardHeader>
396
+ <CardTitle className="text-base flex items-center gap-1.5">
397
+ <ActivitySquare className="h-4 w-4" />
398
+ Recent Logs
399
+ </CardTitle>
400
+ <CardDescription>Last {recentLogs.length} log entries</CardDescription>
401
+ </CardHeader>
402
+ <CardContent>
403
+ <div className="max-h-64 space-y-1 overflow-y-auto font-mono text-xs">
404
+ {recentLogs.map((entry) => (
405
+ <div
406
+ key={entry.id}
407
+ className={`flex gap-2 py-0.5 ${
408
+ entry.level === "error"
409
+ ? "text-destructive"
410
+ : entry.level === "warn"
411
+ ? "text-yellow-600 dark:text-yellow-400"
412
+ : entry.level === "debug"
413
+ ? "text-muted-foreground/60"
414
+ : "text-muted-foreground"
415
+ }`}
416
+ >
417
+ <span className="shrink-0 text-muted-foreground/50">{new Date(entry.createdAt).toLocaleTimeString()}</span>
418
+ <Badge variant="outline" className="h-4 shrink-0 px-1 text-[10px]">{entry.level}</Badge>
419
+ <span className="truncate" title={entry.message}>{entry.message}</span>
420
+ </div>
421
+ ))}
422
+ </div>
423
+ </CardContent>
424
+ </Card>
425
+ ) : null}
426
+ </div>
427
+
428
+ <div className="space-y-6">
429
+ <Card>
430
+ <CardHeader>
431
+ <CardTitle className="text-base flex items-center gap-1.5">
432
+ <ActivitySquare className="h-4 w-4" />
433
+ Health Status
434
+ </CardTitle>
435
+ </CardHeader>
436
+ <CardContent>
437
+ {healthLoading ? (
438
+ <p className="text-sm text-muted-foreground">Checking health...</p>
439
+ ) : healthData ? (
440
+ <div className="space-y-4 text-sm">
441
+ <div className="flex items-center justify-between">
442
+ <span className="text-muted-foreground">Overall</span>
443
+ <Badge variant={healthData.healthy ? "default" : "destructive"}>
444
+ {healthData.status}
445
+ </Badge>
446
+ </div>
447
+
448
+ {healthData.checks.length > 0 ? (
449
+ <div className="space-y-2 border-t border-border/50 pt-2">
450
+ {healthData.checks.map((check, i) => (
451
+ <div key={i} className="flex items-start justify-between gap-2">
452
+ <span className="truncate text-muted-foreground" title={check.name}>
453
+ {check.name}
454
+ </span>
455
+ {check.passed ? (
456
+ <CheckCircle className="h-4 w-4 shrink-0 text-green-500" />
457
+ ) : (
458
+ <XCircle className="h-4 w-4 shrink-0 text-destructive" />
459
+ )}
460
+ </div>
461
+ ))}
462
+ </div>
463
+ ) : null}
464
+
465
+ {healthData.lastError ? (
466
+ <div className="break-words rounded border border-destructive/20 bg-destructive/10 p-2 text-xs text-destructive">
467
+ {healthData.lastError}
468
+ </div>
469
+ ) : null}
470
+ </div>
471
+ ) : (
472
+ <div className="space-y-3 text-sm text-muted-foreground">
473
+ <div className="flex items-center justify-between">
474
+ <span>Lifecycle</span>
475
+ <Badge variant={statusVariant}>{displayStatus}</Badge>
476
+ </div>
477
+ <p>Health checks run once the plugin is ready.</p>
478
+ {plugin.lastError ? (
479
+ <div className="break-words rounded border border-destructive/20 bg-destructive/10 p-2 text-xs text-destructive">
480
+ {plugin.lastError}
481
+ </div>
482
+ ) : null}
483
+ </div>
484
+ )}
485
+ </CardContent>
486
+ </Card>
487
+
488
+ <Card>
489
+ <CardHeader>
490
+ <CardTitle className="text-base">Details</CardTitle>
491
+ </CardHeader>
492
+ <CardContent className="space-y-3 text-sm text-muted-foreground">
493
+ <div className="flex justify-between gap-3">
494
+ <span>Plugin ID</span>
495
+ <span className="font-mono text-xs text-right">{plugin.id}</span>
496
+ </div>
497
+ <div className="flex justify-between gap-3">
498
+ <span>Plugin Key</span>
499
+ <span className="font-mono text-xs text-right">{plugin.pluginKey}</span>
500
+ </div>
501
+ <div className="flex justify-between gap-3">
502
+ <span>NPM Package</span>
503
+ <span className="max-w-[170px] truncate text-right text-xs" title={plugin.packageName}>
504
+ {plugin.packageName}
505
+ </span>
506
+ </div>
507
+ <div className="flex justify-between gap-3">
508
+ <span>Version</span>
509
+ <span className="text-right text-foreground">v{plugin.manifestJson.version ?? plugin.version}</span>
510
+ </div>
511
+ </CardContent>
512
+ </Card>
513
+
514
+ <Card>
515
+ <CardHeader>
516
+ <CardTitle className="text-base flex items-center gap-1.5">
517
+ <ShieldAlert className="h-4 w-4" />
518
+ Permissions
519
+ </CardTitle>
520
+ </CardHeader>
521
+ <CardContent>
522
+ {pluginCapabilities.length > 0 ? (
523
+ <ul className="space-y-2 text-sm text-muted-foreground">
524
+ {pluginCapabilities.map((cap) => (
525
+ <li key={cap} className="rounded-md bg-muted/40 px-2.5 py-2 font-mono text-xs text-foreground/85">
526
+ {cap}
527
+ </li>
528
+ ))}
529
+ </ul>
530
+ ) : (
531
+ <p className="text-sm text-muted-foreground italic">No special permissions requested.</p>
532
+ )}
533
+ </CardContent>
534
+ </Card>
535
+ </div>
536
+ </div>
537
+ </TabsContent>
538
+ </Tabs>
539
+ </div>
540
+ );
541
+ }
542
+
543
+ // ---------------------------------------------------------------------------
544
+ // PluginConfigForm — auto-generated form for instanceConfigSchema
545
+ // ---------------------------------------------------------------------------
546
+
547
+ interface PluginConfigFormProps {
548
+ pluginId: string;
549
+ schema: JsonSchemaNode;
550
+ initialValues?: Record<string, unknown>;
551
+ isLoading?: boolean;
552
+ /** Current plugin lifecycle status — "Test Configuration" only available when `ready`. */
553
+ pluginStatus?: string;
554
+ /** Whether the plugin worker implements `validateConfig`. */
555
+ supportsConfigTest?: boolean;
556
+ }
557
+
558
+ /**
559
+ * Inner component that manages form state, validation, save, and "Test Configuration"
560
+ * for the auto-generated plugin config form.
561
+ *
562
+ * Separated from PluginSettings to isolate re-render scope — only the form
563
+ * re-renders on field changes, not the entire page.
564
+ */
565
+ function PluginConfigForm({ pluginId, schema, initialValues, isLoading, pluginStatus, supportsConfigTest }: PluginConfigFormProps) {
566
+ const queryClient = useQueryClient();
567
+
568
+ // Form values: start with saved values, fall back to schema defaults
569
+ const [values, setValues] = useState<Record<string, unknown>>(() => ({
570
+ ...getDefaultValues(schema),
571
+ ...(initialValues ?? {}),
572
+ }));
573
+
574
+ // Sync when saved config loads asynchronously — only on first load so we
575
+ // don't overwrite in-progress user edits if the query refetches (e.g. on
576
+ // window focus).
577
+ const hasHydratedRef = useRef(false);
578
+ useEffect(() => {
579
+ if (initialValues && !hasHydratedRef.current) {
580
+ hasHydratedRef.current = true;
581
+ setValues({
582
+ ...getDefaultValues(schema),
583
+ ...initialValues,
584
+ });
585
+ }
586
+ }, [initialValues, schema]);
587
+
588
+ const [errors, setErrors] = useState<Record<string, string>>({});
589
+ const [saveMessage, setSaveMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
590
+ const [testResult, setTestResult] = useState<{ type: "success" | "error"; text: string } | null>(null);
591
+
592
+ // Dirty tracking: compare against initial values
593
+ const isDirty = JSON.stringify(values) !== JSON.stringify({
594
+ ...getDefaultValues(schema),
595
+ ...(initialValues ?? {}),
596
+ });
597
+
598
+ // Save mutation
599
+ const saveMutation = useMutation({
600
+ mutationFn: (configJson: Record<string, unknown>) =>
601
+ pluginsApi.saveConfig(pluginId, configJson),
602
+ onSuccess: () => {
603
+ setSaveMessage({ type: "success", text: "Configuration saved." });
604
+ setTestResult(null);
605
+ queryClient.invalidateQueries({ queryKey: queryKeys.plugins.config(pluginId) });
606
+ // Clear success message after 3s
607
+ setTimeout(() => setSaveMessage(null), 3000);
608
+ },
609
+ onError: (err: Error) => {
610
+ setSaveMessage({ type: "error", text: err.message || "Failed to save configuration." });
611
+ },
612
+ });
613
+
614
+ // Test configuration mutation
615
+ const testMutation = useMutation({
616
+ mutationFn: (configJson: Record<string, unknown>) =>
617
+ pluginsApi.testConfig(pluginId, configJson),
618
+ onSuccess: (result) => {
619
+ if (result.valid) {
620
+ setTestResult({ type: "success", text: "Configuration test passed." });
621
+ } else {
622
+ setTestResult({ type: "error", text: result.message || "Configuration test failed." });
623
+ }
624
+ },
625
+ onError: (err: Error) => {
626
+ setTestResult({ type: "error", text: err.message || "Configuration test failed." });
627
+ },
628
+ });
629
+
630
+ const handleChange = useCallback((newValues: Record<string, unknown>) => {
631
+ setValues(newValues);
632
+ // Clear field-level errors as the user types
633
+ setErrors({});
634
+ setSaveMessage(null);
635
+ }, []);
636
+
637
+ const handleSave = useCallback(() => {
638
+ // Validate before saving
639
+ const validationErrors = validateJsonSchemaForm(schema, values);
640
+ if (Object.keys(validationErrors).length > 0) {
641
+ setErrors(validationErrors);
642
+ return;
643
+ }
644
+ setErrors({});
645
+ saveMutation.mutate(values);
646
+ }, [schema, values, saveMutation]);
647
+
648
+ const handleTestConnection = useCallback(() => {
649
+ // Validate before testing
650
+ const validationErrors = validateJsonSchemaForm(schema, values);
651
+ if (Object.keys(validationErrors).length > 0) {
652
+ setErrors(validationErrors);
653
+ return;
654
+ }
655
+ setErrors({});
656
+ setTestResult(null);
657
+ testMutation.mutate(values);
658
+ }, [schema, values, testMutation]);
659
+
660
+ if (isLoading) {
661
+ return (
662
+ <div className="flex items-center gap-2 text-sm text-muted-foreground py-4">
663
+ <Loader2 className="h-4 w-4 animate-spin" />
664
+ Loading configuration...
665
+ </div>
666
+ );
667
+ }
668
+
669
+ return (
670
+ <div className="space-y-4">
671
+ <JsonSchemaForm
672
+ schema={schema}
673
+ values={values}
674
+ onChange={handleChange}
675
+ errors={errors}
676
+ disabled={saveMutation.isPending}
677
+ />
678
+
679
+ {/* Status messages */}
680
+ {saveMessage && (
681
+ <div
682
+ className={`text-sm p-2 rounded border ${
683
+ saveMessage.type === "success"
684
+ ? "text-green-700 bg-green-50 border-green-200 dark:text-green-400 dark:bg-green-950/30 dark:border-green-900"
685
+ : "text-destructive bg-destructive/10 border-destructive/20"
686
+ }`}
687
+ >
688
+ {saveMessage.text}
689
+ </div>
690
+ )}
691
+
692
+ {testResult && (
693
+ <div
694
+ className={`text-sm p-2 rounded border ${
695
+ testResult.type === "success"
696
+ ? "text-green-700 bg-green-50 border-green-200 dark:text-green-400 dark:bg-green-950/30 dark:border-green-900"
697
+ : "text-destructive bg-destructive/10 border-destructive/20"
698
+ }`}
699
+ >
700
+ {testResult.text}
701
+ </div>
702
+ )}
703
+
704
+ {/* Action buttons */}
705
+ <div className="flex items-center gap-2 pt-2">
706
+ <Button
707
+ onClick={handleSave}
708
+ disabled={saveMutation.isPending || !isDirty}
709
+ size="sm"
710
+ >
711
+ {saveMutation.isPending ? (
712
+ <>
713
+ <Loader2 className="h-3.5 w-3.5 animate-spin" />
714
+ Saving...
715
+ </>
716
+ ) : (
717
+ "Save Configuration"
718
+ )}
719
+ </Button>
720
+ {pluginStatus === "ready" && supportsConfigTest && (
721
+ <Button
722
+ variant="outline"
723
+ onClick={handleTestConnection}
724
+ disabled={testMutation.isPending}
725
+ size="sm"
726
+ >
727
+ {testMutation.isPending ? (
728
+ <>
729
+ <Loader2 className="h-3.5 w-3.5 animate-spin" />
730
+ Testing...
731
+ </>
732
+ ) : (
733
+ "Test Configuration"
734
+ )}
735
+ </Button>
736
+ )}
737
+ </div>
738
+ </div>
739
+ );
740
+ }
741
+
742
+ // ---------------------------------------------------------------------------
743
+ // Dashboard helper components and formatting utilities
744
+ // ---------------------------------------------------------------------------
745
+
746
+ /**
747
+ * Format an uptime value (in milliseconds) to a human-readable string.
748
+ */
749
+ function formatUptime(uptimeMs: number | null): string {
750
+ if (uptimeMs == null) return "—";
751
+ const totalSeconds = Math.floor(uptimeMs / 1000);
752
+ if (totalSeconds < 60) return `${totalSeconds}s`;
753
+ const minutes = Math.floor(totalSeconds / 60);
754
+ if (minutes < 60) return `${minutes}m ${totalSeconds % 60}s`;
755
+ const hours = Math.floor(minutes / 60);
756
+ if (hours < 24) return `${hours}h ${minutes % 60}m`;
757
+ const days = Math.floor(hours / 24);
758
+ return `${days}d ${hours % 24}h`;
759
+ }
760
+
761
+ /**
762
+ * Format a duration in milliseconds to a compact display string.
763
+ */
764
+ function formatDuration(ms: number): string {
765
+ if (ms < 1000) return `${ms}ms`;
766
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
767
+ return `${(ms / 60000).toFixed(1)}m`;
768
+ }
769
+
770
+ /**
771
+ * Format an ISO timestamp to a relative time string (e.g., "2m ago").
772
+ */
773
+ function formatRelativeTime(isoString: string): string {
774
+ const now = Date.now();
775
+ const then = new Date(isoString).getTime();
776
+ const diffMs = now - then;
777
+
778
+ if (diffMs < 0) return "just now";
779
+ const seconds = Math.floor(diffMs / 1000);
780
+ if (seconds < 60) return `${seconds}s ago`;
781
+ const minutes = Math.floor(seconds / 60);
782
+ if (minutes < 60) return `${minutes}m ago`;
783
+ const hours = Math.floor(minutes / 60);
784
+ if (hours < 24) return `${hours}h ago`;
785
+ const days = Math.floor(hours / 24);
786
+ return `${days}d ago`;
787
+ }
788
+
789
+ /**
790
+ * Format a unix timestamp (ms since epoch) to a locale string.
791
+ */
792
+ function formatTimestamp(epochMs: number): string {
793
+ return new Date(epochMs).toLocaleString();
794
+ }
795
+
796
+ /**
797
+ * Status indicator dot for job run statuses.
798
+ */
799
+ function JobStatusDot({ status }: { status: string }) {
800
+ const colorClass =
801
+ status === "success" || status === "succeeded"
802
+ ? "bg-green-500"
803
+ : status === "failed"
804
+ ? "bg-red-500"
805
+ : status === "running"
806
+ ? "bg-blue-500 animate-pulse"
807
+ : status === "cancelled"
808
+ ? "bg-gray-400"
809
+ : "bg-amber-500"; // queued, pending
810
+ return (
811
+ <span
812
+ className={`inline-block h-2 w-2 rounded-full shrink-0 ${colorClass}`}
813
+ title={status}
814
+ />
815
+ );
816
+ }
817
+
818
+ /**
819
+ * Status indicator dot for webhook delivery statuses.
820
+ */
821
+ function DeliveryStatusDot({ status }: { status: string }) {
822
+ const colorClass =
823
+ status === "processed" || status === "success"
824
+ ? "bg-green-500"
825
+ : status === "failed"
826
+ ? "bg-red-500"
827
+ : status === "received"
828
+ ? "bg-blue-500"
829
+ : "bg-amber-500"; // pending
830
+ return (
831
+ <span
832
+ className={`inline-block h-2 w-2 rounded-full shrink-0 ${colorClass}`}
833
+ title={status}
834
+ />
835
+ );
836
+ }