@evermore.work/server 2026.509.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1045) hide show
  1. package/dist/adapters/builtin-adapter-types.d.ts +5 -0
  2. package/dist/adapters/builtin-adapter-types.d.ts.map +1 -0
  3. package/dist/adapters/builtin-adapter-types.js +17 -0
  4. package/dist/adapters/builtin-adapter-types.js.map +1 -0
  5. package/dist/adapters/codex-models.d.ts +5 -0
  6. package/dist/adapters/codex-models.d.ts.map +1 -0
  7. package/dist/adapters/codex-models.js +105 -0
  8. package/dist/adapters/codex-models.js.map +1 -0
  9. package/dist/adapters/cursor-models.d.ts +13 -0
  10. package/dist/adapters/cursor-models.d.ts.map +1 -0
  11. package/dist/adapters/cursor-models.js +148 -0
  12. package/dist/adapters/cursor-models.js.map +1 -0
  13. package/dist/adapters/http/execute.d.ts +3 -0
  14. package/dist/adapters/http/execute.d.ts.map +1 -0
  15. package/dist/adapters/http/execute.js +51 -0
  16. package/dist/adapters/http/execute.js.map +1 -0
  17. package/dist/adapters/http/execute.test.d.ts +2 -0
  18. package/dist/adapters/http/execute.test.d.ts.map +1 -0
  19. package/dist/adapters/http/execute.test.js +40 -0
  20. package/dist/adapters/http/execute.test.js.map +1 -0
  21. package/dist/adapters/http/index.d.ts +3 -0
  22. package/dist/adapters/http/index.d.ts.map +1 -0
  23. package/dist/adapters/http/index.js +20 -0
  24. package/dist/adapters/http/index.js.map +1 -0
  25. package/dist/adapters/http/test.d.ts +3 -0
  26. package/dist/adapters/http/test.d.ts.map +1 -0
  27. package/dist/adapters/http/test.js +106 -0
  28. package/dist/adapters/http/test.js.map +1 -0
  29. package/dist/adapters/index.d.ts +4 -0
  30. package/dist/adapters/index.d.ts.map +1 -0
  31. package/dist/adapters/index.js +3 -0
  32. package/dist/adapters/index.js.map +1 -0
  33. package/dist/adapters/plugin-loader.d.ts +28 -0
  34. package/dist/adapters/plugin-loader.d.ts.map +1 -0
  35. package/dist/adapters/plugin-loader.js +196 -0
  36. package/dist/adapters/plugin-loader.js.map +1 -0
  37. package/dist/adapters/process/execute.d.ts +3 -0
  38. package/dist/adapters/process/execute.d.ts.map +1 -0
  39. package/dist/adapters/process/execute.js +70 -0
  40. package/dist/adapters/process/execute.js.map +1 -0
  41. package/dist/adapters/process/index.d.ts +3 -0
  42. package/dist/adapters/process/index.d.ts.map +1 -0
  43. package/dist/adapters/process/index.js +23 -0
  44. package/dist/adapters/process/index.js.map +1 -0
  45. package/dist/adapters/process/test.d.ts +3 -0
  46. package/dist/adapters/process/test.d.ts.map +1 -0
  47. package/dist/adapters/process/test.js +77 -0
  48. package/dist/adapters/process/test.js.map +1 -0
  49. package/dist/adapters/registry.d.ts +69 -0
  50. package/dist/adapters/registry.d.ts.map +1 -0
  51. package/dist/adapters/registry.js +566 -0
  52. package/dist/adapters/registry.js.map +1 -0
  53. package/dist/adapters/types.d.ts +2 -0
  54. package/dist/adapters/types.d.ts.map +1 -0
  55. package/dist/adapters/types.js +2 -0
  56. package/dist/adapters/types.js.map +1 -0
  57. package/dist/adapters/utils.d.ts +43 -0
  58. package/dist/adapters/utils.d.ts.map +1 -0
  59. package/dist/adapters/utils.js +52 -0
  60. package/dist/adapters/utils.js.map +1 -0
  61. package/dist/agent-auth-jwt.d.ts +14 -0
  62. package/dist/agent-auth-jwt.d.ts.map +1 -0
  63. package/dist/agent-auth-jwt.js +117 -0
  64. package/dist/agent-auth-jwt.js.map +1 -0
  65. package/dist/app.d.ts +43 -0
  66. package/dist/app.d.ts.map +1 -0
  67. package/dist/app.js +373 -0
  68. package/dist/app.js.map +1 -0
  69. package/dist/attachment-types.d.ts +23 -0
  70. package/dist/attachment-types.d.ts.map +1 -0
  71. package/dist/attachment-types.js +91 -0
  72. package/dist/attachment-types.js.map +1 -0
  73. package/dist/auth/better-auth.d.ts +33 -0
  74. package/dist/auth/better-auth.d.ts.map +1 -0
  75. package/dist/auth/better-auth.js +133 -0
  76. package/dist/auth/better-auth.js.map +1 -0
  77. package/dist/board-claim.d.ts +23 -0
  78. package/dist/board-claim.d.ts.map +1 -0
  79. package/dist/board-claim.js +115 -0
  80. package/dist/board-claim.js.map +1 -0
  81. package/dist/config-file.d.ts +3 -0
  82. package/dist/config-file.d.ts.map +1 -0
  83. package/dist/config-file.js +16 -0
  84. package/dist/config-file.js.map +1 -0
  85. package/dist/config.d.ts +44 -0
  86. package/dist/config.d.ts.map +1 -0
  87. package/dist/config.js +226 -0
  88. package/dist/config.js.map +1 -0
  89. package/dist/dev-runner-worktree.d.ts +15 -0
  90. package/dist/dev-runner-worktree.d.ts.map +1 -0
  91. package/dist/dev-runner-worktree.js +68 -0
  92. package/dist/dev-runner-worktree.js.map +1 -0
  93. package/dist/dev-server-status.d.ts +27 -0
  94. package/dist/dev-server-status.d.ts.map +1 -0
  95. package/dist/dev-server-status.js +74 -0
  96. package/dist/dev-server-status.js.map +1 -0
  97. package/dist/dev-watch-ignore.d.ts +2 -0
  98. package/dist/dev-watch-ignore.d.ts.map +1 -0
  99. package/dist/dev-watch-ignore.js +36 -0
  100. package/dist/dev-watch-ignore.js.map +1 -0
  101. package/dist/errors.d.ts +12 -0
  102. package/dist/errors.d.ts.map +1 -0
  103. package/dist/errors.js +28 -0
  104. package/dist/errors.js.map +1 -0
  105. package/dist/home-paths.d.ts +17 -0
  106. package/dist/home-paths.d.ts.map +1 -0
  107. package/dist/home-paths.js +75 -0
  108. package/dist/home-paths.js.map +1 -0
  109. package/dist/index.d.ts +10 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +753 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/lib/join-request-dedupe.d.ts +11 -0
  114. package/dist/lib/join-request-dedupe.d.ts.map +1 -0
  115. package/dist/lib/join-request-dedupe.js +49 -0
  116. package/dist/lib/join-request-dedupe.js.map +1 -0
  117. package/dist/log-redaction.d.ts +11 -0
  118. package/dist/log-redaction.d.ts.map +1 -0
  119. package/dist/log-redaction.js +122 -0
  120. package/dist/log-redaction.js.map +1 -0
  121. package/dist/middleware/auth.d.ts +12 -0
  122. package/dist/middleware/auth.d.ts.map +1 -0
  123. package/dist/middleware/auth.js +302 -0
  124. package/dist/middleware/auth.js.map +1 -0
  125. package/dist/middleware/board-mutation-guard.d.ts +3 -0
  126. package/dist/middleware/board-mutation-guard.d.ts.map +1 -0
  127. package/dist/middleware/board-mutation-guard.js +67 -0
  128. package/dist/middleware/board-mutation-guard.js.map +1 -0
  129. package/dist/middleware/error-handler.d.ts +17 -0
  130. package/dist/middleware/error-handler.d.ts.map +1 -0
  131. package/dist/middleware/error-handler.js +45 -0
  132. package/dist/middleware/error-handler.js.map +1 -0
  133. package/dist/middleware/http-log-policy.d.ts +2 -0
  134. package/dist/middleware/http-log-policy.d.ts.map +1 -0
  135. package/dist/middleware/http-log-policy.js +52 -0
  136. package/dist/middleware/http-log-policy.js.map +1 -0
  137. package/dist/middleware/index.d.ts +4 -0
  138. package/dist/middleware/index.d.ts.map +1 -0
  139. package/dist/middleware/index.js +4 -0
  140. package/dist/middleware/index.js.map +1 -0
  141. package/dist/middleware/logger.d.ts +4 -0
  142. package/dist/middleware/logger.d.ts.map +1 -0
  143. package/dist/middleware/logger.js +92 -0
  144. package/dist/middleware/logger.js.map +1 -0
  145. package/dist/middleware/private-hostname-guard.d.ts +11 -0
  146. package/dist/middleware/private-hostname-guard.d.ts.map +1 -0
  147. package/dist/middleware/private-hostname-guard.js +78 -0
  148. package/dist/middleware/private-hostname-guard.js.map +1 -0
  149. package/dist/middleware/validate.d.ts +4 -0
  150. package/dist/middleware/validate.d.ts.map +1 -0
  151. package/dist/middleware/validate.js +7 -0
  152. package/dist/middleware/validate.js.map +1 -0
  153. package/dist/onboarding-assets/ceo/AGENTS.md +59 -0
  154. package/dist/onboarding-assets/ceo/HEARTBEAT.md +85 -0
  155. package/dist/onboarding-assets/ceo/SOUL.md +33 -0
  156. package/dist/onboarding-assets/ceo/TOOLS.md +3 -0
  157. package/dist/onboarding-assets/default/AGENTS.md +17 -0
  158. package/dist/paths.d.ts +3 -0
  159. package/dist/paths.d.ts.map +1 -0
  160. package/dist/paths.js +31 -0
  161. package/dist/paths.js.map +1 -0
  162. package/dist/realtime/live-events-ws.d.ts +28 -0
  163. package/dist/realtime/live-events-ws.d.ts.map +1 -0
  164. package/dist/realtime/live-events-ws.js +187 -0
  165. package/dist/realtime/live-events-ws.js.map +1 -0
  166. package/dist/redaction.d.ts +5 -0
  167. package/dist/redaction.d.ts.map +1 -0
  168. package/dist/redaction.js +98 -0
  169. package/dist/redaction.js.map +1 -0
  170. package/dist/routes/access.d.ts +82 -0
  171. package/dist/routes/access.d.ts.map +1 -0
  172. package/dist/routes/access.js +3411 -0
  173. package/dist/routes/access.js.map +1 -0
  174. package/dist/routes/activity.d.ts +3 -0
  175. package/dist/routes/activity.d.ts.map +1 -0
  176. package/dist/routes/activity.js +90 -0
  177. package/dist/routes/activity.js.map +1 -0
  178. package/dist/routes/adapters.d.ts +16 -0
  179. package/dist/routes/adapters.d.ts.map +1 -0
  180. package/dist/routes/adapters.js +527 -0
  181. package/dist/routes/adapters.js.map +1 -0
  182. package/dist/routes/agents.d.ts +6 -0
  183. package/dist/routes/agents.d.ts.map +1 -0
  184. package/dist/routes/agents.js +2753 -0
  185. package/dist/routes/agents.js.map +1 -0
  186. package/dist/routes/approvals.d.ts +6 -0
  187. package/dist/routes/approvals.d.ts.map +1 -0
  188. package/dist/routes/approvals.js +300 -0
  189. package/dist/routes/approvals.js.map +1 -0
  190. package/dist/routes/assets.d.ts +4 -0
  191. package/dist/routes/assets.d.ts.map +1 -0
  192. package/dist/routes/assets.js +309 -0
  193. package/dist/routes/assets.js.map +1 -0
  194. package/dist/routes/auth.d.ts +3 -0
  195. package/dist/routes/auth.d.ts.map +1 -0
  196. package/dist/routes/auth.js +82 -0
  197. package/dist/routes/auth.js.map +1 -0
  198. package/dist/routes/authz.d.ts +19 -0
  199. package/dist/routes/authz.d.ts.map +1 -0
  200. package/dist/routes/authz.js +75 -0
  201. package/dist/routes/authz.js.map +1 -0
  202. package/dist/routes/companies.d.ts +4 -0
  203. package/dist/routes/companies.d.ts.map +1 -0
  204. package/dist/routes/companies.js +359 -0
  205. package/dist/routes/companies.js.map +1 -0
  206. package/dist/routes/company-skills.d.ts +3 -0
  207. package/dist/routes/company-skills.d.ts.map +1 -0
  208. package/dist/routes/company-skills.js +258 -0
  209. package/dist/routes/company-skills.js.map +1 -0
  210. package/dist/routes/costs.d.ts +11 -0
  211. package/dist/routes/costs.d.ts.map +1 -0
  212. package/dist/routes/costs.js +285 -0
  213. package/dist/routes/costs.js.map +1 -0
  214. package/dist/routes/dashboard.d.ts +3 -0
  215. package/dist/routes/dashboard.d.ts.map +1 -0
  216. package/dist/routes/dashboard.js +15 -0
  217. package/dist/routes/dashboard.js.map +1 -0
  218. package/dist/routes/environment-selection.d.ts +13 -0
  219. package/dist/routes/environment-selection.d.ts.map +1 -0
  220. package/dist/routes/environment-selection.js +30 -0
  221. package/dist/routes/environment-selection.js.map +1 -0
  222. package/dist/routes/environments.d.ts +6 -0
  223. package/dist/routes/environments.d.ts.map +1 -0
  224. package/dist/routes/environments.js +401 -0
  225. package/dist/routes/environments.js.map +1 -0
  226. package/dist/routes/execution-workspaces.d.ts +3 -0
  227. package/dist/routes/execution-workspaces.d.ts.map +1 -0
  228. package/dist/routes/execution-workspaces.js +536 -0
  229. package/dist/routes/execution-workspaces.js.map +1 -0
  230. package/dist/routes/goals.d.ts +3 -0
  231. package/dist/routes/goals.d.ts.map +1 -0
  232. package/dist/routes/goals.js +101 -0
  233. package/dist/routes/goals.js.map +1 -0
  234. package/dist/routes/health.d.ts +9 -0
  235. package/dist/routes/health.d.ts.map +1 -0
  236. package/dist/routes/health.js +114 -0
  237. package/dist/routes/health.js.map +1 -0
  238. package/dist/routes/inbox-dismissals.d.ts +3 -0
  239. package/dist/routes/inbox-dismissals.d.ts.map +1 -0
  240. package/dist/routes/inbox-dismissals.js +58 -0
  241. package/dist/routes/inbox-dismissals.js.map +1 -0
  242. package/dist/routes/index.d.ts +22 -0
  243. package/dist/routes/index.d.ts.map +1 -0
  244. package/dist/routes/index.js +22 -0
  245. package/dist/routes/index.js.map +1 -0
  246. package/dist/routes/instance-database-backups.d.ts +15 -0
  247. package/dist/routes/instance-database-backups.d.ts.map +1 -0
  248. package/dist/routes/instance-database-backups.js +12 -0
  249. package/dist/routes/instance-database-backups.js.map +1 -0
  250. package/dist/routes/instance-settings.d.ts +3 -0
  251. package/dist/routes/instance-settings.d.ts.map +1 -0
  252. package/dist/routes/instance-settings.js +110 -0
  253. package/dist/routes/instance-settings.js.map +1 -0
  254. package/dist/routes/issue-tree-control.d.ts +3 -0
  255. package/dist/routes/issue-tree-control.d.ts.map +1 -0
  256. package/dist/routes/issue-tree-control.js +363 -0
  257. package/dist/routes/issue-tree-control.js.map +1 -0
  258. package/dist/routes/issues-checkout-wakeup.d.ts +9 -0
  259. package/dist/routes/issues-checkout-wakeup.d.ts.map +1 -0
  260. package/dist/routes/issues-checkout-wakeup.js +12 -0
  261. package/dist/routes/issues-checkout-wakeup.js.map +1 -0
  262. package/dist/routes/issues.d.ts +23 -0
  263. package/dist/routes/issues.d.ts.map +1 -0
  264. package/dist/routes/issues.js +3886 -0
  265. package/dist/routes/issues.js.map +1 -0
  266. package/dist/routes/llms.d.ts +3 -0
  267. package/dist/routes/llms.d.ts.map +1 -0
  268. package/dist/routes/llms.js +80 -0
  269. package/dist/routes/llms.js.map +1 -0
  270. package/dist/routes/org-chart-svg.d.ts +25 -0
  271. package/dist/routes/org-chart-svg.d.ts.map +1 -0
  272. package/dist/routes/org-chart-svg.js +656 -0
  273. package/dist/routes/org-chart-svg.js.map +1 -0
  274. package/dist/routes/plugin-ui-static.d.ts +69 -0
  275. package/dist/routes/plugin-ui-static.d.ts.map +1 -0
  276. package/dist/routes/plugin-ui-static.js +411 -0
  277. package/dist/routes/plugin-ui-static.js.map +1 -0
  278. package/dist/routes/plugins.d.ts +121 -0
  279. package/dist/routes/plugins.d.ts.map +1 -0
  280. package/dist/routes/plugins.js +2192 -0
  281. package/dist/routes/plugins.js.map +1 -0
  282. package/dist/routes/projects.d.ts +3 -0
  283. package/dist/routes/projects.d.ts.map +1 -0
  284. package/dist/routes/projects.js +566 -0
  285. package/dist/routes/projects.js.map +1 -0
  286. package/dist/routes/routines.d.ts +6 -0
  287. package/dist/routes/routines.d.ts.map +1 -0
  288. package/dist/routes/routines.js +417 -0
  289. package/dist/routes/routines.js.map +1 -0
  290. package/dist/routes/secrets.d.ts +3 -0
  291. package/dist/routes/secrets.d.ts.map +1 -0
  292. package/dist/routes/secrets.js +128 -0
  293. package/dist/routes/secrets.js.map +1 -0
  294. package/dist/routes/sidebar-badges.d.ts +3 -0
  295. package/dist/routes/sidebar-badges.d.ts.map +1 -0
  296. package/dist/routes/sidebar-badges.js +68 -0
  297. package/dist/routes/sidebar-badges.js.map +1 -0
  298. package/dist/routes/sidebar-preferences.d.ts +3 -0
  299. package/dist/routes/sidebar-preferences.d.ts.map +1 -0
  300. package/dist/routes/sidebar-preferences.js +63 -0
  301. package/dist/routes/sidebar-preferences.js.map +1 -0
  302. package/dist/routes/user-profiles.d.ts +3 -0
  303. package/dist/routes/user-profiles.d.ts.map +1 -0
  304. package/dist/routes/user-profiles.js +337 -0
  305. package/dist/routes/user-profiles.js.map +1 -0
  306. package/dist/routes/workspace-command-authz.d.ts +14 -0
  307. package/dist/routes/workspace-command-authz.d.ts.map +1 -0
  308. package/dist/routes/workspace-command-authz.js +83 -0
  309. package/dist/routes/workspace-command-authz.js.map +1 -0
  310. package/dist/routes/workspace-runtime-service-authz.d.ts +12 -0
  311. package/dist/routes/workspace-runtime-service-authz.d.ts.map +1 -0
  312. package/dist/routes/workspace-runtime-service-authz.js +96 -0
  313. package/dist/routes/workspace-runtime-service-authz.js.map +1 -0
  314. package/dist/runtime-api.d.ts +19 -0
  315. package/dist/runtime-api.d.ts.map +1 -0
  316. package/dist/runtime-api.js +137 -0
  317. package/dist/runtime-api.js.map +1 -0
  318. package/dist/secrets/external-stub-providers.d.ts +5 -0
  319. package/dist/secrets/external-stub-providers.d.ts.map +1 -0
  320. package/dist/secrets/external-stub-providers.js +21 -0
  321. package/dist/secrets/external-stub-providers.js.map +1 -0
  322. package/dist/secrets/local-encrypted-provider.d.ts +3 -0
  323. package/dist/secrets/local-encrypted-provider.d.ts.map +1 -0
  324. package/dist/secrets/local-encrypted-provider.js +116 -0
  325. package/dist/secrets/local-encrypted-provider.js.map +1 -0
  326. package/dist/secrets/provider-registry.d.ts +5 -0
  327. package/dist/secrets/provider-registry.d.ts.map +1 -0
  328. package/dist/secrets/provider-registry.js +20 -0
  329. package/dist/secrets/provider-registry.js.map +1 -0
  330. package/dist/secrets/types.d.ts +21 -0
  331. package/dist/secrets/types.d.ts.map +1 -0
  332. package/dist/secrets/types.js +2 -0
  333. package/dist/secrets/types.js.map +1 -0
  334. package/dist/services/access.d.ts +171 -0
  335. package/dist/services/access.d.ts.map +1 -0
  336. package/dist/services/access.js +522 -0
  337. package/dist/services/access.js.map +1 -0
  338. package/dist/services/activity-log.d.ts +19 -0
  339. package/dist/services/activity-log.d.ts.map +1 -0
  340. package/dist/services/activity-log.js +99 -0
  341. package/dist/services/activity-log.js.map +1 -0
  342. package/dist/services/activity.d.ts +462 -0
  343. package/dist/services/activity.d.ts.map +1 -0
  344. package/dist/services/activity.js +443 -0
  345. package/dist/services/activity.js.map +1 -0
  346. package/dist/services/adapter-plugin-store.d.ts +36 -0
  347. package/dist/services/adapter-plugin-store.d.ts.map +1 -0
  348. package/dist/services/adapter-plugin-store.js +154 -0
  349. package/dist/services/adapter-plugin-store.js.map +1 -0
  350. package/dist/services/agent-instructions.d.ts +91 -0
  351. package/dist/services/agent-instructions.d.ts.map +1 -0
  352. package/dist/services/agent-instructions.js +580 -0
  353. package/dist/services/agent-instructions.js.map +1 -0
  354. package/dist/services/agent-permissions.d.ts +6 -0
  355. package/dist/services/agent-permissions.d.ts.map +1 -0
  356. package/dist/services/agent-permissions.js +18 -0
  357. package/dist/services/agent-permissions.js.map +1 -0
  358. package/dist/services/agent-start-lock.d.ts +2 -0
  359. package/dist/services/agent-start-lock.d.ts.map +1 -0
  360. package/dist/services/agent-start-lock.js +43 -0
  361. package/dist/services/agent-start-lock.js.map +1 -0
  362. package/dist/services/agents.d.ts +2253 -0
  363. package/dist/services/agents.d.ts.map +1 -0
  364. package/dist/services/agents.js +609 -0
  365. package/dist/services/agents.js.map +1 -0
  366. package/dist/services/approvals.d.ts +546 -0
  367. package/dist/services/approvals.d.ts.map +1 -0
  368. package/dist/services/approvals.js +212 -0
  369. package/dist/services/approvals.js.map +1 -0
  370. package/dist/services/assets.d.ts +33 -0
  371. package/dist/services/assets.d.ts.map +1 -0
  372. package/dist/services/assets.js +17 -0
  373. package/dist/services/assets.js.map +1 -0
  374. package/dist/services/board-auth.d.ts +239 -0
  375. package/dist/services/board-auth.d.ts.map +1 -0
  376. package/dist/services/board-auth.js +300 -0
  377. package/dist/services/board-auth.js.map +1 -0
  378. package/dist/services/budgets.d.ts +38 -0
  379. package/dist/services/budgets.d.ts.map +1 -0
  380. package/dist/services/budgets.js +784 -0
  381. package/dist/services/budgets.js.map +1 -0
  382. package/dist/services/companies.d.ts +154 -0
  383. package/dist/services/companies.d.ts.map +1 -0
  384. package/dist/services/companies.js +267 -0
  385. package/dist/services/companies.js.map +1 -0
  386. package/dist/services/company-export-readme.d.ts +17 -0
  387. package/dist/services/company-export-readme.d.ts.map +1 -0
  388. package/dist/services/company-export-readme.js +148 -0
  389. package/dist/services/company-export-readme.js.map +1 -0
  390. package/dist/services/company-member-roles.d.ts +9 -0
  391. package/dist/services/company-member-roles.d.ts.map +1 -0
  392. package/dist/services/company-member-roles.js +46 -0
  393. package/dist/services/company-member-roles.js.map +1 -0
  394. package/dist/services/company-portability.d.ts +24 -0
  395. package/dist/services/company-portability.d.ts.map +1 -0
  396. package/dist/services/company-portability.js +4076 -0
  397. package/dist/services/company-portability.js.map +1 -0
  398. package/dist/services/company-search-rate-limit.d.ts +22 -0
  399. package/dist/services/company-search-rate-limit.d.ts.map +1 -0
  400. package/dist/services/company-search-rate-limit.js +38 -0
  401. package/dist/services/company-search-rate-limit.js.map +1 -0
  402. package/dist/services/company-search.d.ts +8 -0
  403. package/dist/services/company-search.d.ts.map +1 -0
  404. package/dist/services/company-search.js +626 -0
  405. package/dist/services/company-search.js.map +1 -0
  406. package/dist/services/company-skills.d.ts +77 -0
  407. package/dist/services/company-skills.d.ts.map +1 -0
  408. package/dist/services/company-skills.js +2120 -0
  409. package/dist/services/company-skills.js.map +1 -0
  410. package/dist/services/costs.d.ts +127 -0
  411. package/dist/services/costs.d.ts.map +1 -0
  412. package/dist/services/costs.js +409 -0
  413. package/dist/services/costs.js.map +1 -0
  414. package/dist/services/cron.d.ts +80 -0
  415. package/dist/services/cron.d.ts.map +1 -0
  416. package/dist/services/cron.js +300 -0
  417. package/dist/services/cron.js.map +1 -0
  418. package/dist/services/dashboard.d.ts +34 -0
  419. package/dist/services/dashboard.d.ts.map +1 -0
  420. package/dist/services/dashboard.js +142 -0
  421. package/dist/services/dashboard.js.map +1 -0
  422. package/dist/services/default-agent-instructions.d.ts +9 -0
  423. package/dist/services/default-agent-instructions.d.ts.map +1 -0
  424. package/dist/services/default-agent-instructions.js +20 -0
  425. package/dist/services/default-agent-instructions.js.map +1 -0
  426. package/dist/services/documents.d.ts +199 -0
  427. package/dist/services/documents.d.ts.map +1 -0
  428. package/dist/services/documents.js +411 -0
  429. package/dist/services/documents.js.map +1 -0
  430. package/dist/services/environment-config.d.ts +43 -0
  431. package/dist/services/environment-config.d.ts.map +1 -0
  432. package/dist/services/environment-config.js +388 -0
  433. package/dist/services/environment-config.js.map +1 -0
  434. package/dist/services/environment-execution-target.d.ts +21 -0
  435. package/dist/services/environment-execution-target.d.ts.map +1 -0
  436. package/dist/services/environment-execution-target.js +119 -0
  437. package/dist/services/environment-execution-target.js.map +1 -0
  438. package/dist/services/environment-probe.d.ts +9 -0
  439. package/dist/services/environment-probe.d.ts.map +1 -0
  440. package/dist/services/environment-probe.js +106 -0
  441. package/dist/services/environment-probe.js.map +1 -0
  442. package/dist/services/environment-run-orchestrator.d.ts +124 -0
  443. package/dist/services/environment-run-orchestrator.d.ts.map +1 -0
  444. package/dist/services/environment-run-orchestrator.js +392 -0
  445. package/dist/services/environment-run-orchestrator.js.map +1 -0
  446. package/dist/services/environment-runtime.d.ts +90 -0
  447. package/dist/services/environment-runtime.d.ts.map +1 -0
  448. package/dist/services/environment-runtime.js +934 -0
  449. package/dist/services/environment-runtime.js.map +1 -0
  450. package/dist/services/environments.d.ts +36 -0
  451. package/dist/services/environments.d.ts.map +1 -0
  452. package/dist/services/environments.js +260 -0
  453. package/dist/services/environments.js.map +1 -0
  454. package/dist/services/execution-workspace-policy.d.ts +30 -0
  455. package/dist/services/execution-workspace-policy.d.ts.map +1 -0
  456. package/dist/services/execution-workspace-policy.js +195 -0
  457. package/dist/services/execution-workspace-policy.js.map +1 -0
  458. package/dist/services/execution-workspaces.d.ts +30 -0
  459. package/dist/services/execution-workspaces.d.ts.map +1 -0
  460. package/dist/services/execution-workspaces.js +635 -0
  461. package/dist/services/execution-workspaces.js.map +1 -0
  462. package/dist/services/feedback-redaction.d.ts +23 -0
  463. package/dist/services/feedback-redaction.d.ts.map +1 -0
  464. package/dist/services/feedback-redaction.js +150 -0
  465. package/dist/services/feedback-redaction.js.map +1 -0
  466. package/dist/services/feedback-share-client.d.ts +9 -0
  467. package/dist/services/feedback-share-client.d.ts.map +1 -0
  468. package/dist/services/feedback-share-client.js +46 -0
  469. package/dist/services/feedback-share-client.js.map +1 -0
  470. package/dist/services/feedback.d.ts +93 -0
  471. package/dist/services/feedback.d.ts.map +1 -0
  472. package/dist/services/feedback.js +1717 -0
  473. package/dist/services/feedback.js.map +1 -0
  474. package/dist/services/finance.d.ts +93 -0
  475. package/dist/services/finance.d.ts.map +1 -0
  476. package/dist/services/finance.js +120 -0
  477. package/dist/services/finance.js.map +1 -0
  478. package/dist/services/github-fetch.d.ts +4 -0
  479. package/dist/services/github-fetch.d.ts.map +1 -0
  480. package/dist/services/github-fetch.js +23 -0
  481. package/dist/services/github-fetch.js.map +1 -0
  482. package/dist/services/goals.d.ts +433 -0
  483. package/dist/services/goals.d.ts.map +1 -0
  484. package/dist/services/goals.js +54 -0
  485. package/dist/services/goals.js.map +1 -0
  486. package/dist/services/heartbeat-run-summary.d.ts +7 -0
  487. package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
  488. package/dist/services/heartbeat-run-summary.js +84 -0
  489. package/dist/services/heartbeat-run-summary.js.map +1 -0
  490. package/dist/services/heartbeat-stop-metadata.d.ts +28 -0
  491. package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -0
  492. package/dist/services/heartbeat-stop-metadata.js +86 -0
  493. package/dist/services/heartbeat-stop-metadata.js.map +1 -0
  494. package/dist/services/heartbeat-stop-metadata.test.d.ts +2 -0
  495. package/dist/services/heartbeat-stop-metadata.test.d.ts.map +1 -0
  496. package/dist/services/heartbeat-stop-metadata.test.js +93 -0
  497. package/dist/services/heartbeat-stop-metadata.test.js.map +1 -0
  498. package/dist/services/heartbeat.d.ts +1484 -0
  499. package/dist/services/heartbeat.d.ts.map +1 -0
  500. package/dist/services/heartbeat.js +7557 -0
  501. package/dist/services/heartbeat.js.map +1 -0
  502. package/dist/services/hire-hook.d.ts +14 -0
  503. package/dist/services/hire-hook.d.ts.map +1 -0
  504. package/dist/services/hire-hook.js +85 -0
  505. package/dist/services/hire-hook.js.map +1 -0
  506. package/dist/services/inbox-dismissals.d.ts +22 -0
  507. package/dist/services/inbox-dismissals.d.ts.map +1 -0
  508. package/dist/services/inbox-dismissals.js +33 -0
  509. package/dist/services/inbox-dismissals.js.map +1 -0
  510. package/dist/services/index.d.ts +44 -0
  511. package/dist/services/index.d.ts.map +1 -0
  512. package/dist/services/index.js +44 -0
  513. package/dist/services/index.js.map +1 -0
  514. package/dist/services/instance-settings.d.ts +11 -0
  515. package/dist/services/instance-settings.d.ts.map +1 -0
  516. package/dist/services/instance-settings.js +138 -0
  517. package/dist/services/instance-settings.js.map +1 -0
  518. package/dist/services/invite-grants.d.ts +15 -0
  519. package/dist/services/invite-grants.d.ts.map +1 -0
  520. package/dist/services/invite-grants.js +50 -0
  521. package/dist/services/invite-grants.js.map +1 -0
  522. package/dist/services/issue-approvals.d.ts +56 -0
  523. package/dist/services/issue-approvals.d.ts.map +1 -0
  524. package/dist/services/issue-approvals.js +153 -0
  525. package/dist/services/issue-approvals.js.map +1 -0
  526. package/dist/services/issue-assignment-wakeup.d.ts +29 -0
  527. package/dist/services/issue-assignment-wakeup.d.ts.map +1 -0
  528. package/dist/services/issue-assignment-wakeup.js +22 -0
  529. package/dist/services/issue-assignment-wakeup.js.map +1 -0
  530. package/dist/services/issue-continuation-summary.d.ts +66 -0
  531. package/dist/services/issue-continuation-summary.d.ts.map +1 -0
  532. package/dist/services/issue-continuation-summary.js +212 -0
  533. package/dist/services/issue-continuation-summary.js.map +1 -0
  534. package/dist/services/issue-execution-policy.d.ts +93 -0
  535. package/dist/services/issue-execution-policy.d.ts.map +1 -0
  536. package/dist/services/issue-execution-policy.js +838 -0
  537. package/dist/services/issue-execution-policy.js.map +1 -0
  538. package/dist/services/issue-goal-fallback.d.ts +18 -0
  539. package/dist/services/issue-goal-fallback.d.ts.map +1 -0
  540. package/dist/services/issue-goal-fallback.js +33 -0
  541. package/dist/services/issue-goal-fallback.js.map +1 -0
  542. package/dist/services/issue-liveness.d.ts +3 -0
  543. package/dist/services/issue-liveness.d.ts.map +1 -0
  544. package/dist/services/issue-liveness.js +2 -0
  545. package/dist/services/issue-liveness.js.map +1 -0
  546. package/dist/services/issue-references.d.ts +21 -0
  547. package/dist/services/issue-references.d.ts.map +1 -0
  548. package/dist/services/issue-references.js +318 -0
  549. package/dist/services/issue-references.js.map +1 -0
  550. package/dist/services/issue-thread-interactions.d.ts +76 -0
  551. package/dist/services/issue-thread-interactions.d.ts.map +1 -0
  552. package/dist/services/issue-thread-interactions.js +923 -0
  553. package/dist/services/issue-thread-interactions.js.map +1 -0
  554. package/dist/services/issue-thread-interactions.test.d.ts +2 -0
  555. package/dist/services/issue-thread-interactions.test.d.ts.map +1 -0
  556. package/dist/services/issue-thread-interactions.test.js +195 -0
  557. package/dist/services/issue-thread-interactions.test.js.map +1 -0
  558. package/dist/services/issue-tree-control.d.ts +89 -0
  559. package/dist/services/issue-tree-control.d.ts.map +1 -0
  560. package/dist/services/issue-tree-control.js +933 -0
  561. package/dist/services/issue-tree-control.js.map +1 -0
  562. package/dist/services/issues.d.ts +710 -0
  563. package/dist/services/issues.d.ts.map +1 -0
  564. package/dist/services/issues.js +3199 -0
  565. package/dist/services/issues.js.map +1 -0
  566. package/dist/services/json-schema-secret-refs.d.ts +5 -0
  567. package/dist/services/json-schema-secret-refs.d.ts.map +1 -0
  568. package/dist/services/json-schema-secret-refs.js +67 -0
  569. package/dist/services/json-schema-secret-refs.js.map +1 -0
  570. package/dist/services/live-events.d.ts +17 -0
  571. package/dist/services/live-events.d.ts.map +1 -0
  572. package/dist/services/live-events.js +33 -0
  573. package/dist/services/live-events.js.map +1 -0
  574. package/dist/services/local-service-supervisor.d.ts +56 -0
  575. package/dist/services/local-service-supervisor.d.ts.map +1 -0
  576. package/dist/services/local-service-supervisor.js +284 -0
  577. package/dist/services/local-service-supervisor.js.map +1 -0
  578. package/dist/services/plugin-capability-validator.d.ts +108 -0
  579. package/dist/services/plugin-capability-validator.d.ts.map +1 -0
  580. package/dist/services/plugin-capability-validator.js +313 -0
  581. package/dist/services/plugin-capability-validator.js.map +1 -0
  582. package/dist/services/plugin-config-validator.d.ts +26 -0
  583. package/dist/services/plugin-config-validator.d.ts.map +1 -0
  584. package/dist/services/plugin-config-validator.js +41 -0
  585. package/dist/services/plugin-config-validator.js.map +1 -0
  586. package/dist/services/plugin-database.d.ts +49 -0
  587. package/dist/services/plugin-database.d.ts.map +1 -0
  588. package/dist/services/plugin-database.js +440 -0
  589. package/dist/services/plugin-database.js.map +1 -0
  590. package/dist/services/plugin-dev-watcher.d.ts +30 -0
  591. package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
  592. package/dist/services/plugin-dev-watcher.js +241 -0
  593. package/dist/services/plugin-dev-watcher.js.map +1 -0
  594. package/dist/services/plugin-environment-driver.d.ts +124 -0
  595. package/dist/services/plugin-environment-driver.d.ts.map +1 -0
  596. package/dist/services/plugin-environment-driver.js +224 -0
  597. package/dist/services/plugin-environment-driver.js.map +1 -0
  598. package/dist/services/plugin-event-bus.d.ts +149 -0
  599. package/dist/services/plugin-event-bus.d.ts.map +1 -0
  600. package/dist/services/plugin-event-bus.js +258 -0
  601. package/dist/services/plugin-event-bus.js.map +1 -0
  602. package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
  603. package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
  604. package/dist/services/plugin-host-service-cleanup.js +37 -0
  605. package/dist/services/plugin-host-service-cleanup.js.map +1 -0
  606. package/dist/services/plugin-host-services.d.ts +17 -0
  607. package/dist/services/plugin-host-services.d.ts.map +1 -0
  608. package/dist/services/plugin-host-services.js +1861 -0
  609. package/dist/services/plugin-host-services.js.map +1 -0
  610. package/dist/services/plugin-job-coordinator.d.ts +81 -0
  611. package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
  612. package/dist/services/plugin-job-coordinator.js +172 -0
  613. package/dist/services/plugin-job-coordinator.js.map +1 -0
  614. package/dist/services/plugin-job-scheduler.d.ts +163 -0
  615. package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
  616. package/dist/services/plugin-job-scheduler.js +454 -0
  617. package/dist/services/plugin-job-scheduler.js.map +1 -0
  618. package/dist/services/plugin-job-store.d.ts +208 -0
  619. package/dist/services/plugin-job-store.d.ts.map +1 -0
  620. package/dist/services/plugin-job-store.js +350 -0
  621. package/dist/services/plugin-job-store.js.map +1 -0
  622. package/dist/services/plugin-lifecycle.d.ts +203 -0
  623. package/dist/services/plugin-lifecycle.d.ts.map +1 -0
  624. package/dist/services/plugin-lifecycle.js +476 -0
  625. package/dist/services/plugin-lifecycle.js.map +1 -0
  626. package/dist/services/plugin-loader.d.ts +445 -0
  627. package/dist/services/plugin-loader.d.ts.map +1 -0
  628. package/dist/services/plugin-loader.js +1273 -0
  629. package/dist/services/plugin-loader.js.map +1 -0
  630. package/dist/services/plugin-local-folders.d.ts +48 -0
  631. package/dist/services/plugin-local-folders.d.ts.map +1 -0
  632. package/dist/services/plugin-local-folders.js +461 -0
  633. package/dist/services/plugin-local-folders.js.map +1 -0
  634. package/dist/services/plugin-log-retention.d.ts +20 -0
  635. package/dist/services/plugin-log-retention.d.ts.map +1 -0
  636. package/dist/services/plugin-log-retention.js +63 -0
  637. package/dist/services/plugin-log-retention.js.map +1 -0
  638. package/dist/services/plugin-managed-agents.d.ts +15 -0
  639. package/dist/services/plugin-managed-agents.d.ts.map +1 -0
  640. package/dist/services/plugin-managed-agents.js +414 -0
  641. package/dist/services/plugin-managed-agents.js.map +1 -0
  642. package/dist/services/plugin-managed-routines.d.ts +41 -0
  643. package/dist/services/plugin-managed-routines.d.ts.map +1 -0
  644. package/dist/services/plugin-managed-routines.js +416 -0
  645. package/dist/services/plugin-managed-routines.js.map +1 -0
  646. package/dist/services/plugin-manifest-validator.d.ts +79 -0
  647. package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
  648. package/dist/services/plugin-manifest-validator.js +84 -0
  649. package/dist/services/plugin-manifest-validator.js.map +1 -0
  650. package/dist/services/plugin-registry.d.ts +2550 -0
  651. package/dist/services/plugin-registry.d.ts.map +1 -0
  652. package/dist/services/plugin-registry.js +581 -0
  653. package/dist/services/plugin-registry.js.map +1 -0
  654. package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
  655. package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
  656. package/dist/services/plugin-runtime-sandbox.js +154 -0
  657. package/dist/services/plugin-runtime-sandbox.js.map +1 -0
  658. package/dist/services/plugin-secrets-handler.d.ts +81 -0
  659. package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
  660. package/dist/services/plugin-secrets-handler.js +231 -0
  661. package/dist/services/plugin-secrets-handler.js.map +1 -0
  662. package/dist/services/plugin-state-store.d.ts +92 -0
  663. package/dist/services/plugin-state-store.d.ts.map +1 -0
  664. package/dist/services/plugin-state-store.js +190 -0
  665. package/dist/services/plugin-state-store.js.map +1 -0
  666. package/dist/services/plugin-stream-bus.d.ts +29 -0
  667. package/dist/services/plugin-stream-bus.d.ts.map +1 -0
  668. package/dist/services/plugin-stream-bus.js +48 -0
  669. package/dist/services/plugin-stream-bus.js.map +1 -0
  670. package/dist/services/plugin-tool-dispatcher.d.ts +180 -0
  671. package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
  672. package/dist/services/plugin-tool-dispatcher.js +224 -0
  673. package/dist/services/plugin-tool-dispatcher.js.map +1 -0
  674. package/dist/services/plugin-tool-registry.d.ts +192 -0
  675. package/dist/services/plugin-tool-registry.d.ts.map +1 -0
  676. package/dist/services/plugin-tool-registry.js +224 -0
  677. package/dist/services/plugin-tool-registry.js.map +1 -0
  678. package/dist/services/plugin-worker-manager.d.ts +262 -0
  679. package/dist/services/plugin-worker-manager.d.ts.map +1 -0
  680. package/dist/services/plugin-worker-manager.js +836 -0
  681. package/dist/services/plugin-worker-manager.js.map +1 -0
  682. package/dist/services/productivity-review.d.ts +83 -0
  683. package/dist/services/productivity-review.d.ts.map +1 -0
  684. package/dist/services/productivity-review.js +652 -0
  685. package/dist/services/productivity-review.js.map +1 -0
  686. package/dist/services/project-workspace-runtime-config.d.ts +4 -0
  687. package/dist/services/project-workspace-runtime-config.d.ts.map +1 -0
  688. package/dist/services/project-workspace-runtime-config.js +54 -0
  689. package/dist/services/project-workspace-runtime-config.js.map +1 -0
  690. package/dist/services/projects.d.ts +99 -0
  691. package/dist/services/projects.d.ts.map +1 -0
  692. package/dist/services/projects.js +879 -0
  693. package/dist/services/projects.js.map +1 -0
  694. package/dist/services/quota-windows.d.ts +9 -0
  695. package/dist/services/quota-windows.d.ts.map +1 -0
  696. package/dist/services/quota-windows.js +56 -0
  697. package/dist/services/quota-windows.js.map +1 -0
  698. package/dist/services/recovery/index.d.ts +10 -0
  699. package/dist/services/recovery/index.d.ts.map +1 -0
  700. package/dist/services/recovery/index.js +6 -0
  701. package/dist/services/recovery/index.js.map +1 -0
  702. package/dist/services/recovery/issue-graph-liveness.d.ts +85 -0
  703. package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -0
  704. package/dist/services/recovery/issue-graph-liveness.js +343 -0
  705. package/dist/services/recovery/issue-graph-liveness.js.map +1 -0
  706. package/dist/services/recovery/model-profile-hint.d.ts +8 -0
  707. package/dist/services/recovery/model-profile-hint.d.ts.map +1 -0
  708. package/dist/services/recovery/model-profile-hint.js +11 -0
  709. package/dist/services/recovery/model-profile-hint.js.map +1 -0
  710. package/dist/services/recovery/origins.d.ts +36 -0
  711. package/dist/services/recovery/origins.d.ts.map +1 -0
  712. package/dist/services/recovery/origins.js +45 -0
  713. package/dist/services/recovery/origins.js.map +1 -0
  714. package/dist/services/recovery/pause-hold-guard.d.ts +6 -0
  715. package/dist/services/recovery/pause-hold-guard.d.ts.map +1 -0
  716. package/dist/services/recovery/pause-hold-guard.js +6 -0
  717. package/dist/services/recovery/pause-hold-guard.js.map +1 -0
  718. package/dist/services/recovery/run-liveness-continuations.d.ts +50 -0
  719. package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -0
  720. package/dist/services/recovery/run-liveness-continuations.js +117 -0
  721. package/dist/services/recovery/run-liveness-continuations.js.map +1 -0
  722. package/dist/services/recovery/service.d.ts +195 -0
  723. package/dist/services/recovery/service.d.ts.map +1 -0
  724. package/dist/services/recovery/service.js +2210 -0
  725. package/dist/services/recovery/service.js.map +1 -0
  726. package/dist/services/recovery/successful-run-handoff.d.ts +87 -0
  727. package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -0
  728. package/dist/services/recovery/successful-run-handoff.js +297 -0
  729. package/dist/services/recovery/successful-run-handoff.js.map +1 -0
  730. package/dist/services/recovery/successful-run-handoff.test.d.ts +2 -0
  731. package/dist/services/recovery/successful-run-handoff.test.d.ts.map +1 -0
  732. package/dist/services/recovery/successful-run-handoff.test.js +267 -0
  733. package/dist/services/recovery/successful-run-handoff.test.js.map +1 -0
  734. package/dist/services/routines.d.ts +166 -0
  735. package/dist/services/routines.d.ts.map +1 -0
  736. package/dist/services/routines.js +1937 -0
  737. package/dist/services/routines.js.map +1 -0
  738. package/dist/services/run-continuations.d.ts +3 -0
  739. package/dist/services/run-continuations.d.ts.map +1 -0
  740. package/dist/services/run-continuations.js +2 -0
  741. package/dist/services/run-continuations.js.map +1 -0
  742. package/dist/services/run-liveness.d.ts +46 -0
  743. package/dist/services/run-liveness.d.ts.map +1 -0
  744. package/dist/services/run-liveness.js +275 -0
  745. package/dist/services/run-liveness.js.map +1 -0
  746. package/dist/services/run-log-store.d.ts +34 -0
  747. package/dist/services/run-log-store.d.ts.map +1 -0
  748. package/dist/services/run-log-store.js +111 -0
  749. package/dist/services/run-log-store.js.map +1 -0
  750. package/dist/services/sandbox-provider-runtime.d.ts +132 -0
  751. package/dist/services/sandbox-provider-runtime.d.ts.map +1 -0
  752. package/dist/services/sandbox-provider-runtime.js +216 -0
  753. package/dist/services/sandbox-provider-runtime.js.map +1 -0
  754. package/dist/services/secrets.d.ts +515 -0
  755. package/dist/services/secrets.d.ts.map +1 -0
  756. package/dist/services/secrets.js +290 -0
  757. package/dist/services/secrets.js.map +1 -0
  758. package/dist/services/sidebar-badges.d.ts +14 -0
  759. package/dist/services/sidebar-badges.d.ts.map +1 -0
  760. package/dist/services/sidebar-badges.js +48 -0
  761. package/dist/services/sidebar-badges.js.map +1 -0
  762. package/dist/services/sidebar-preferences.d.ts +9 -0
  763. package/dist/services/sidebar-preferences.d.ts.map +1 -0
  764. package/dist/services/sidebar-preferences.js +82 -0
  765. package/dist/services/sidebar-preferences.js.map +1 -0
  766. package/dist/services/work-products.d.ts +14 -0
  767. package/dist/services/work-products.d.ts.map +1 -0
  768. package/dist/services/work-products.js +100 -0
  769. package/dist/services/work-products.js.map +1 -0
  770. package/dist/services/workspace-operation-log-store.d.ts +33 -0
  771. package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
  772. package/dist/services/workspace-operation-log-store.js +110 -0
  773. package/dist/services/workspace-operation-log-store.js.map +1 -0
  774. package/dist/services/workspace-operations.d.ts +44 -0
  775. package/dist/services/workspace-operations.d.ts.map +1 -0
  776. package/dist/services/workspace-operations.js +211 -0
  777. package/dist/services/workspace-operations.js.map +1 -0
  778. package/dist/services/workspace-realization.d.ts +33 -0
  779. package/dist/services/workspace-realization.d.ts.map +1 -0
  780. package/dist/services/workspace-realization.js +221 -0
  781. package/dist/services/workspace-realization.js.map +1 -0
  782. package/dist/services/workspace-runtime-read-model.d.ts +92 -0
  783. package/dist/services/workspace-runtime-read-model.d.ts.map +1 -0
  784. package/dist/services/workspace-runtime-read-model.js +67 -0
  785. package/dist/services/workspace-runtime-read-model.js.map +1 -0
  786. package/dist/services/workspace-runtime.d.ts +238 -0
  787. package/dist/services/workspace-runtime.d.ts.map +1 -0
  788. package/dist/services/workspace-runtime.js +2388 -0
  789. package/dist/services/workspace-runtime.js.map +1 -0
  790. package/dist/startup-banner.d.ts +32 -0
  791. package/dist/startup-banner.d.ts.map +1 -0
  792. package/dist/startup-banner.js +118 -0
  793. package/dist/startup-banner.js.map +1 -0
  794. package/dist/storage/index.d.ts +6 -0
  795. package/dist/storage/index.d.ts.map +1 -0
  796. package/dist/storage/index.js +29 -0
  797. package/dist/storage/index.js.map +1 -0
  798. package/dist/storage/local-disk-provider.d.ts +3 -0
  799. package/dist/storage/local-disk-provider.d.ts.map +1 -0
  800. package/dist/storage/local-disk-provider.js +79 -0
  801. package/dist/storage/local-disk-provider.js.map +1 -0
  802. package/dist/storage/provider-registry.d.ts +4 -0
  803. package/dist/storage/provider-registry.d.ts.map +1 -0
  804. package/dist/storage/provider-registry.js +15 -0
  805. package/dist/storage/provider-registry.js.map +1 -0
  806. package/dist/storage/s3-provider.d.ts +11 -0
  807. package/dist/storage/s3-provider.d.ts.map +1 -0
  808. package/dist/storage/s3-provider.js +123 -0
  809. package/dist/storage/s3-provider.js.map +1 -0
  810. package/dist/storage/service.d.ts +3 -0
  811. package/dist/storage/service.d.ts.map +1 -0
  812. package/dist/storage/service.js +120 -0
  813. package/dist/storage/service.js.map +1 -0
  814. package/dist/storage/types.d.ts +55 -0
  815. package/dist/storage/types.d.ts.map +1 -0
  816. package/dist/storage/types.js +2 -0
  817. package/dist/storage/types.js.map +1 -0
  818. package/dist/telemetry.d.ts +6 -0
  819. package/dist/telemetry.d.ts.map +1 -0
  820. package/dist/telemetry.js +20 -0
  821. package/dist/telemetry.js.map +1 -0
  822. package/dist/ui-branding.d.ts +13 -0
  823. package/dist/ui-branding.d.ts.map +1 -0
  824. package/dist/ui-branding.js +187 -0
  825. package/dist/ui-branding.js.map +1 -0
  826. package/dist/version.d.ts +2 -0
  827. package/dist/version.d.ts.map +1 -0
  828. package/dist/version.js +5 -0
  829. package/dist/version.js.map +1 -0
  830. package/dist/vite-html-renderer.d.ts +18 -0
  831. package/dist/vite-html-renderer.d.ts.map +1 -0
  832. package/dist/vite-html-renderer.js +61 -0
  833. package/dist/vite-html-renderer.js.map +1 -0
  834. package/dist/worktree-config.d.ts +19 -0
  835. package/dist/worktree-config.d.ts.map +1 -0
  836. package/dist/worktree-config.js +368 -0
  837. package/dist/worktree-config.js.map +1 -0
  838. package/package.json +90 -0
  839. package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
  840. package/skills/evermore/SKILL.md +366 -0
  841. package/skills/evermore/references/api-reference.md +899 -0
  842. package/skills/evermore/references/company-skills.md +193 -0
  843. package/skills/evermore/references/issue-workspaces.md +80 -0
  844. package/skills/evermore/references/routines.md +187 -0
  845. package/skills/evermore/references/workflows.md +141 -0
  846. package/skills/evermore-converting-plans-to-tasks/SKILL.md +42 -0
  847. package/skills/evermore-create-agent/SKILL.md +163 -0
  848. package/skills/evermore-create-agent/references/agent-instruction-templates.md +123 -0
  849. package/skills/evermore-create-agent/references/agents/coder.md +64 -0
  850. package/skills/evermore-create-agent/references/agents/qa.md +88 -0
  851. package/skills/evermore-create-agent/references/agents/securityengineer.md +135 -0
  852. package/skills/evermore-create-agent/references/agents/uxdesigner.md +115 -0
  853. package/skills/evermore-create-agent/references/api-reference.md +110 -0
  854. package/skills/evermore-create-agent/references/baseline-role-guide.md +168 -0
  855. package/skills/evermore-create-agent/references/draft-review-checklist.md +95 -0
  856. package/skills/evermore-create-plugin/SKILL.md +101 -0
  857. package/skills/evermore-dev/SKILL.md +267 -0
  858. package/skills/para-memory-files/SKILL.md +104 -0
  859. package/skills/para-memory-files/references/schemas.md +35 -0
  860. package/skills/terminal-bench-loop/SKILL.md +236 -0
  861. package/ui-dist/android-chrome-192x192.png +0 -0
  862. package/ui-dist/android-chrome-512x512.png +0 -0
  863. package/ui-dist/apple-touch-icon.png +0 -0
  864. package/ui-dist/assets/_basePickBy-Ds9oHp1_.js +1 -0
  865. package/ui-dist/assets/_baseUniq-CHYwQyJ_.js +1 -0
  866. package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
  867. package/ui-dist/assets/arc-CiUKtBzk.js +1 -0
  868. package/ui-dist/assets/architectureDiagram-VXUJARFQ-CAW2b6pT.js +36 -0
  869. package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
  870. package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
  871. package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
  872. package/ui-dist/assets/blockDiagram-VD42YOAC-1Rk6YCcn.js +122 -0
  873. package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  874. package/ui-dist/assets/c4Diagram-YG6GDRKO-DF5RJtyZ.js +10 -0
  875. package/ui-dist/assets/channel-D7SqxhNi.js +1 -0
  876. package/ui-dist/assets/chunk-4BX2VUAB-BSZDJAxk.js +1 -0
  877. package/ui-dist/assets/chunk-55IACEB6-DgVOW-V3.js +1 -0
  878. package/ui-dist/assets/chunk-B4BG7PRW-C1sGAq6t.js +165 -0
  879. package/ui-dist/assets/chunk-DI55MBZ5-DZyfq3VK.js +220 -0
  880. package/ui-dist/assets/chunk-FMBD7UC4-D6K9nYXi.js +15 -0
  881. package/ui-dist/assets/chunk-QN33PNHL-BJ0Ni2l9.js +1 -0
  882. package/ui-dist/assets/chunk-QZHKN3VN-Cwjr0vxG.js +1 -0
  883. package/ui-dist/assets/chunk-TZMSLE5B-C2RGCkyV.js +1 -0
  884. package/ui-dist/assets/classDiagram-2ON5EDUG-Cx1PlXXb.js +1 -0
  885. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-Cx1PlXXb.js +1 -0
  886. package/ui-dist/assets/clike-B9uivgTg.js +1 -0
  887. package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
  888. package/ui-dist/assets/clone-5ZE-SRxk.js +1 -0
  889. package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
  890. package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
  891. package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
  892. package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
  893. package/ui-dist/assets/cose-bilkent-S5V4N54A-CW0Mh3DC.js +1 -0
  894. package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
  895. package/ui-dist/assets/css-BnMrqG3P.js +1 -0
  896. package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
  897. package/ui-dist/assets/cytoscape.esm-jbPEKk2Y.js +321 -0
  898. package/ui-dist/assets/d-pRatUO7H.js +1 -0
  899. package/ui-dist/assets/dagre-6UL2VRFP-BXDbyGyw.js +4 -0
  900. package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  901. package/ui-dist/assets/diagram-PSM6KHXK-BHW-b3P9.js +24 -0
  902. package/ui-dist/assets/diagram-QEK2KX5R-vjGsaMmX.js +43 -0
  903. package/ui-dist/assets/diagram-S2PKOQOG-BHVhRqBj.js +24 -0
  904. package/ui-dist/assets/diff-DbItnlRl.js +1 -0
  905. package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
  906. package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
  907. package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
  908. package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
  909. package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
  910. package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
  911. package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
  912. package/ui-dist/assets/erDiagram-Q2GNP2WA-DwsdEDB2.js +60 -0
  913. package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
  914. package/ui-dist/assets/factor-kuTfRLto.js +1 -0
  915. package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
  916. package/ui-dist/assets/flowDiagram-NV44I4VS-Bv7drkZu.js +162 -0
  917. package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
  918. package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
  919. package/ui-dist/assets/ganttDiagram-JELNMOA3-DyYAGyjV.js +267 -0
  920. package/ui-dist/assets/gas-Bneqetm1.js +1 -0
  921. package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
  922. package/ui-dist/assets/gitGraphDiagram-V2S2FVAM-DmKJJ2X0.js +65 -0
  923. package/ui-dist/assets/graph-BI12oqz9.js +1 -0
  924. package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
  925. package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
  926. package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
  927. package/ui-dist/assets/http-DBlCnlav.js +1 -0
  928. package/ui-dist/assets/idl-BEugSyMb.js +1 -0
  929. package/ui-dist/assets/index-B1YmgKZD.js +1 -0
  930. package/ui-dist/assets/index-B5EG1mbW.js +1 -0
  931. package/ui-dist/assets/index-B63DPkk7.js +2 -0
  932. package/ui-dist/assets/index-BAalUM2u.js +1 -0
  933. package/ui-dist/assets/index-BCyQqUEQ.js +1 -0
  934. package/ui-dist/assets/index-BSRsyahM.js +1 -0
  935. package/ui-dist/assets/index-B_Iu4zUd.js +1 -0
  936. package/ui-dist/assets/index-C-BdZUIH.js +1 -0
  937. package/ui-dist/assets/index-CGgUnSQj.js +6 -0
  938. package/ui-dist/assets/index-COTIEysQ.js +1 -0
  939. package/ui-dist/assets/index-ChSqseHR.js +1 -0
  940. package/ui-dist/assets/index-Ckf1hADU.js +534 -0
  941. package/ui-dist/assets/index-D4M1TSCO.js +3 -0
  942. package/ui-dist/assets/index-DCTk2CN-.js +7 -0
  943. package/ui-dist/assets/index-DI-wyUUr.js +1 -0
  944. package/ui-dist/assets/index-DNtLqZ-D.js +1 -0
  945. package/ui-dist/assets/index-DSRR_614.css +1 -0
  946. package/ui-dist/assets/index-DarmgkJv.js +1 -0
  947. package/ui-dist/assets/index-DdcxF71a.js +1 -0
  948. package/ui-dist/assets/index-DfwWaMga.js +1 -0
  949. package/ui-dist/assets/index-SzUviW57.js +13 -0
  950. package/ui-dist/assets/index-VjZhELb6.js +1 -0
  951. package/ui-dist/assets/index-mEcmy8wG.js +1 -0
  952. package/ui-dist/assets/index-q7ldlDv6.js +1 -0
  953. package/ui-dist/assets/infoDiagram-HS3SLOUP-CqAxMRbC.js +2 -0
  954. package/ui-dist/assets/init-Gi6I4Gst.js +1 -0
  955. package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
  956. package/ui-dist/assets/journeyDiagram-XKPGCS4Q-DUJiudtY.js +139 -0
  957. package/ui-dist/assets/julia-DuME0IfC.js +1 -0
  958. package/ui-dist/assets/kanban-definition-3W4ZIXB7-CiRgJ4X2.js +89 -0
  959. package/ui-dist/assets/katex-B95LWT_Q.js +261 -0
  960. package/ui-dist/assets/layout-DJ8V2pqt.js +1 -0
  961. package/ui-dist/assets/linear-BwnNUuyZ.js +1 -0
  962. package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
  963. package/ui-dist/assets/lua-BgMRiT3U.js +1 -0
  964. package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
  965. package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
  966. package/ui-dist/assets/mermaid.core-DLCiCWj4.js +250 -0
  967. package/ui-dist/assets/mindmap-definition-VGOIOE7T-epIiGA01.js +68 -0
  968. package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
  969. package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
  970. package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
  971. package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
  972. package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
  973. package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
  974. package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
  975. package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
  976. package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
  977. package/ui-dist/assets/ordinal-Cboi1Yqb.js +1 -0
  978. package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
  979. package/ui-dist/assets/pascal--L3eBynH.js +1 -0
  980. package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
  981. package/ui-dist/assets/pieDiagram-ADFJNKIX-CHx4pJ9Y.js +30 -0
  982. package/ui-dist/assets/pig-CevX1Tat.js +1 -0
  983. package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
  984. package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
  985. package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
  986. package/ui-dist/assets/pug-DeIclll2.js +1 -0
  987. package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
  988. package/ui-dist/assets/python-BuPzkPfP.js +1 -0
  989. package/ui-dist/assets/q-pXgVlZs6.js +1 -0
  990. package/ui-dist/assets/quadrantDiagram-AYHSOK5B-D0tXmGxE.js +7 -0
  991. package/ui-dist/assets/r-B6wPVr8A.js +1 -0
  992. package/ui-dist/assets/requirementDiagram-UZGBJVZJ-BgFc9Xc4.js +64 -0
  993. package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
  994. package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
  995. package/ui-dist/assets/sankeyDiagram-TZEHDZUN-DqntX5wV.js +10 -0
  996. package/ui-dist/assets/sas-B4kiWyti.js +1 -0
  997. package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
  998. package/ui-dist/assets/sequenceDiagram-WL72ISMW-BfIJeGAT.js +145 -0
  999. package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
  1000. package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
  1001. package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
  1002. package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
  1003. package/ui-dist/assets/solr-DehyRSwq.js +1 -0
  1004. package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
  1005. package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  1006. package/ui-dist/assets/sql-D0XecflT.js +1 -0
  1007. package/ui-dist/assets/stateDiagram-FKZM4ZOC-CBlsmcoW.js +1 -0
  1008. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-DJtH2tCS.js +1 -0
  1009. package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
  1010. package/ui-dist/assets/stylus-B533Al4x.js +1 -0
  1011. package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
  1012. package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
  1013. package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
  1014. package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  1015. package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
  1016. package/ui-dist/assets/timeline-definition-IT6M3QCI-C2XyT4Lo.js +61 -0
  1017. package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
  1018. package/ui-dist/assets/treemap-GDKQZRPO-DvTrxs0e.js +154 -0
  1019. package/ui-dist/assets/troff-wAsdV37c.js +1 -0
  1020. package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
  1021. package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  1022. package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
  1023. package/ui-dist/assets/vb-CmGdzxic.js +1 -0
  1024. package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
  1025. package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
  1026. package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
  1027. package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
  1028. package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
  1029. package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
  1030. package/ui-dist/assets/xychartDiagram-PRI3JC2R-pTizTbG0.js +7 -0
  1031. package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
  1032. package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
  1033. package/ui-dist/brands/opencode-logo-dark-square.svg +18 -0
  1034. package/ui-dist/brands/opencode-logo-light-square.svg +18 -0
  1035. package/ui-dist/favicon-16x16.png +0 -0
  1036. package/ui-dist/favicon-32x32.png +0 -0
  1037. package/ui-dist/favicon.ico +0 -0
  1038. package/ui-dist/favicon.svg +9 -0
  1039. package/ui-dist/index.html +49 -0
  1040. package/ui-dist/site.webmanifest +30 -0
  1041. package/ui-dist/sw.js +42 -0
  1042. package/ui-dist/worktree-favicon-16x16.png +0 -0
  1043. package/ui-dist/worktree-favicon-32x32.png +0 -0
  1044. package/ui-dist/worktree-favicon.ico +0 -0
  1045. package/ui-dist/worktree-favicon.svg +9 -0
@@ -0,0 +1,3411 @@
1
+ import { createHash, generateKeyPairSync, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { lookup as dnsLookup } from "node:dns/promises";
3
+ import fs from "node:fs";
4
+ import { request as httpRequest } from "node:http";
5
+ import { request as httpsRequest } from "node:https";
6
+ import { isIP } from "node:net";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { Router } from "express";
10
+ import { and, desc, eq, gt, inArray, isNotNull, isNull, lte, ne, sql } from "drizzle-orm";
11
+ import { assets, agentApiKeys, authUsers, companies, companyLogos, companyMemberships, instanceUserRoles, invites, joinRequests, principalPermissionGrants, } from "@evermore.work/db";
12
+ import { acceptInviteSchema, createCliAuthChallengeSchema, claimJoinRequestApiKeySchema, createCompanyInviteSchema, createOpenClawInvitePromptSchema, listCompanyInvitesQuerySchema, listJoinRequestsQuerySchema, resolveCliAuthChallengeSchema, searchAdminUsersQuerySchema, updateCompanyMemberWithPermissionsSchema, updateCompanyMemberSchema, archiveCompanyMemberSchema, updateMemberPermissionsSchema, updateUserCompanyAccessSchema, PERMISSION_KEYS } from "@evermore.work/shared";
13
+ import { forbidden, conflict, notFound, unauthorized, badRequest } from "../errors.js";
14
+ import { logger } from "../middleware/logger.js";
15
+ import { validate } from "../middleware/validate.js";
16
+ import { collectReachableInterfaceHosts } from "../runtime-api.js";
17
+ import { accessService, agentService, boardAuthService, deduplicateAgentName, logActivity, notifyHireApproved } from "../services/index.js";
18
+ import { grantsForHumanRole, normalizeHumanRole, resolveHumanInviteRole, } from "../services/company-member-roles.js";
19
+ import { humanJoinGrantsFromDefaults } from "../services/invite-grants.js";
20
+ import { collapseDuplicatePendingHumanJoinRequests, findReusableHumanJoinRequest, } from "../lib/join-request-dedupe.js";
21
+ import { assertAuthenticated, assertCompanyAccess } from "./authz.js";
22
+ import { claimBoardOwnership, inspectBoardClaimChallenge } from "../board-claim.js";
23
+ import { getStorageService } from "../storage/index.js";
24
+ function hashToken(token) {
25
+ return createHash("sha256").update(token).digest("hex");
26
+ }
27
+ const INVITE_TOKEN_PREFIX = "evr_invite_";
28
+ const INVITE_TOKEN_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
29
+ const INVITE_TOKEN_SUFFIX_LENGTH = 8;
30
+ const INVITE_TOKEN_MAX_RETRIES = 5;
31
+ const COMPANY_INVITE_TTL_MS = 72 * 60 * 60 * 1000;
32
+ const INVITE_RESOLUTION_DNS_TIMEOUT_MS = 3_000;
33
+ function createInviteToken() {
34
+ const bytes = randomBytes(INVITE_TOKEN_SUFFIX_LENGTH);
35
+ let suffix = "";
36
+ for (let idx = 0; idx < INVITE_TOKEN_SUFFIX_LENGTH; idx += 1) {
37
+ suffix += INVITE_TOKEN_ALPHABET[bytes[idx] % INVITE_TOKEN_ALPHABET.length];
38
+ }
39
+ return `${INVITE_TOKEN_PREFIX}${suffix}`;
40
+ }
41
+ function createClaimSecret() {
42
+ return `evr_claim_${randomBytes(24).toString("hex")}`;
43
+ }
44
+ export function companyInviteExpiresAt(nowMs = Date.now()) {
45
+ return new Date(nowMs + COMPANY_INVITE_TTL_MS);
46
+ }
47
+ function tokenHashesMatch(left, right) {
48
+ const leftBytes = Buffer.from(left, "utf8");
49
+ const rightBytes = Buffer.from(right, "utf8");
50
+ return (leftBytes.length === rightBytes.length &&
51
+ timingSafeEqual(leftBytes, rightBytes));
52
+ }
53
+ function requestBaseUrl(req) {
54
+ const forwardedProto = req.header("x-forwarded-proto");
55
+ const proto = forwardedProto?.split(",")[0]?.trim() || req.protocol || "http";
56
+ const host = req.header("x-forwarded-host")?.split(",")[0]?.trim() || req.header("host");
57
+ if (!host)
58
+ return "";
59
+ return `${proto}://${host}`;
60
+ }
61
+ function buildCliAuthApprovalPath(challengeId, token) {
62
+ return `/cli-auth/${challengeId}?token=${encodeURIComponent(token)}`;
63
+ }
64
+ function readSkillMarkdown(skillName) {
65
+ const normalized = skillName.trim().toLowerCase();
66
+ if (normalized !== "evermore" &&
67
+ normalized !== "evermore-create-agent" &&
68
+ normalized !== "evermore-create-plugin" &&
69
+ normalized !== "evermore-converting-plans-to-tasks" &&
70
+ normalized !== "para-memory-files")
71
+ return null;
72
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
73
+ const candidates = [
74
+ path.resolve(moduleDir, "../../skills", normalized, "SKILL.md"), // published: dist/routes/ -> <pkg>/skills/
75
+ path.resolve(process.cwd(), "skills", normalized, "SKILL.md"), // cwd (e.g. monorepo root)
76
+ path.resolve(moduleDir, "../../../skills", normalized, "SKILL.md") // dev: src/routes/ -> repo root/skills/
77
+ ];
78
+ for (const skillPath of candidates) {
79
+ try {
80
+ return fs.readFileSync(skillPath, "utf8");
81
+ }
82
+ catch {
83
+ // Continue to next candidate.
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ /** Resolve the Evermore repo skills directory (built-in / managed skills). */
89
+ function resolveEvermoreSkillsDir() {
90
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
91
+ const candidates = [
92
+ path.resolve(moduleDir, "../../skills"), // published
93
+ path.resolve(process.cwd(), "skills"), // cwd (monorepo root)
94
+ path.resolve(moduleDir, "../../../skills"), // dev
95
+ ];
96
+ for (const candidate of candidates) {
97
+ try {
98
+ if (fs.statSync(candidate).isDirectory())
99
+ return candidate;
100
+ }
101
+ catch { /* skip */ }
102
+ }
103
+ return null;
104
+ }
105
+ /** Parse YAML frontmatter from a SKILL.md file to extract the description. */
106
+ function parseSkillFrontmatter(markdown) {
107
+ const match = markdown.match(/^---\n([\s\S]*?)\n---/);
108
+ if (!match)
109
+ return { description: "" };
110
+ const yaml = match[1];
111
+ // Extract description — handles both single-line and multi-line YAML values
112
+ const descMatch = yaml.match(/^description:\s*(?:>\s*\n((?:\s{2,}[^\n]*\n?)+)|[|]\s*\n((?:\s{2,}[^\n]*\n?)+)|["']?(.*?)["']?\s*$)/m);
113
+ if (!descMatch)
114
+ return { description: "" };
115
+ const raw = descMatch[1] ?? descMatch[2] ?? descMatch[3] ?? "";
116
+ return {
117
+ description: raw
118
+ .split("\n")
119
+ .map((l) => l.trim())
120
+ .filter(Boolean)
121
+ .join(" ")
122
+ .trim(),
123
+ };
124
+ }
125
+ /** Discover all available Claude Code skills from ~/.claude/skills/. */
126
+ function listAvailableSkills() {
127
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
128
+ const claudeSkillsDir = path.join(homeDir, ".claude", "skills");
129
+ const evermoreSkillsDir = resolveEvermoreSkillsDir();
130
+ // Build set of Evermore-managed skill names
131
+ const evermoreSkillNames = new Set();
132
+ if (evermoreSkillsDir) {
133
+ try {
134
+ for (const entry of fs.readdirSync(evermoreSkillsDir, { withFileTypes: true })) {
135
+ if (entry.isDirectory())
136
+ evermoreSkillNames.add(entry.name);
137
+ }
138
+ }
139
+ catch { /* skip */ }
140
+ }
141
+ const skills = [];
142
+ try {
143
+ const entries = fs.readdirSync(claudeSkillsDir, { withFileTypes: true });
144
+ for (const entry of entries) {
145
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
146
+ continue;
147
+ if (entry.name.startsWith("."))
148
+ continue;
149
+ const skillMdPath = path.join(claudeSkillsDir, entry.name, "SKILL.md");
150
+ let description = "";
151
+ try {
152
+ const md = fs.readFileSync(skillMdPath, "utf8");
153
+ description = parseSkillFrontmatter(md).description;
154
+ }
155
+ catch { /* no SKILL.md or unreadable */ }
156
+ skills.push({
157
+ name: entry.name,
158
+ description,
159
+ isEvermoreManaged: evermoreSkillNames.has(entry.name),
160
+ });
161
+ }
162
+ }
163
+ catch { /* ~/.claude/skills/ doesn't exist */ }
164
+ skills.sort((a, b) => a.name.localeCompare(b.name));
165
+ return skills;
166
+ }
167
+ function toJoinRequestResponse(row) {
168
+ const { claimSecretHash: _claimSecretHash, ...safe } = row;
169
+ return safe;
170
+ }
171
+ function isPlainObject(value) {
172
+ return typeof value === "object" && value !== null && !Array.isArray(value);
173
+ }
174
+ function isLoopbackHost(hostname) {
175
+ const value = hostname.trim().toLowerCase();
176
+ return value === "localhost" || value === "127.0.0.1" || value === "::1";
177
+ }
178
+ function normalizeHostname(value) {
179
+ if (!value)
180
+ return null;
181
+ const trimmed = value.trim();
182
+ if (!trimmed)
183
+ return null;
184
+ if (trimmed.startsWith("[")) {
185
+ const end = trimmed.indexOf("]");
186
+ return end > 1
187
+ ? trimmed.slice(1, end).toLowerCase()
188
+ : trimmed.toLowerCase();
189
+ }
190
+ const firstColon = trimmed.indexOf(":");
191
+ if (firstColon > -1)
192
+ return trimmed.slice(0, firstColon).toLowerCase();
193
+ return trimmed.toLowerCase();
194
+ }
195
+ function normalizeHeaderValue(value, depth = 0) {
196
+ const direct = nonEmptyTrimmedString(value);
197
+ if (direct)
198
+ return direct;
199
+ if (!isPlainObject(value) || depth >= 3)
200
+ return null;
201
+ const candidateKeys = [
202
+ "value",
203
+ "token",
204
+ "secret",
205
+ "apiKey",
206
+ "api_key",
207
+ "auth",
208
+ "authToken",
209
+ "auth_token",
210
+ "accessToken",
211
+ "access_token",
212
+ "authorization",
213
+ "bearer",
214
+ "header",
215
+ "raw",
216
+ "text",
217
+ "string"
218
+ ];
219
+ for (const key of candidateKeys) {
220
+ if (!Object.prototype.hasOwnProperty.call(value, key))
221
+ continue;
222
+ const normalized = normalizeHeaderValue(value[key], depth + 1);
223
+ if (normalized)
224
+ return normalized;
225
+ }
226
+ const entries = Object.entries(value);
227
+ if (entries.length === 1) {
228
+ const [singleKey, singleValue] = entries[0];
229
+ const normalizedKey = singleKey.trim().toLowerCase();
230
+ if (normalizedKey !== "type" &&
231
+ normalizedKey !== "version" &&
232
+ normalizedKey !== "secretid" &&
233
+ normalizedKey !== "secret_id") {
234
+ const normalized = normalizeHeaderValue(singleValue, depth + 1);
235
+ if (normalized)
236
+ return normalized;
237
+ }
238
+ }
239
+ return null;
240
+ }
241
+ function extractHeaderEntries(input) {
242
+ if (isPlainObject(input)) {
243
+ return Object.entries(input);
244
+ }
245
+ if (!Array.isArray(input)) {
246
+ return [];
247
+ }
248
+ const entries = [];
249
+ for (const item of input) {
250
+ if (Array.isArray(item)) {
251
+ const key = nonEmptyTrimmedString(item[0]);
252
+ if (!key)
253
+ continue;
254
+ entries.push([key, item[1]]);
255
+ continue;
256
+ }
257
+ if (!isPlainObject(item))
258
+ continue;
259
+ const mapped = item;
260
+ const explicitKey = nonEmptyTrimmedString(mapped.key) ??
261
+ nonEmptyTrimmedString(mapped.name) ??
262
+ nonEmptyTrimmedString(mapped.header);
263
+ if (explicitKey) {
264
+ const explicitValue = Object.prototype.hasOwnProperty.call(mapped, "value")
265
+ ? mapped.value
266
+ : Object.prototype.hasOwnProperty.call(mapped, "token")
267
+ ? mapped.token
268
+ : Object.prototype.hasOwnProperty.call(mapped, "secret")
269
+ ? mapped.secret
270
+ : mapped;
271
+ entries.push([explicitKey, explicitValue]);
272
+ continue;
273
+ }
274
+ const singleEntry = Object.entries(mapped);
275
+ if (singleEntry.length === 1) {
276
+ entries.push(singleEntry[0]);
277
+ }
278
+ }
279
+ return entries;
280
+ }
281
+ function normalizeHeaderMap(input) {
282
+ const entries = extractHeaderEntries(input);
283
+ if (entries.length === 0)
284
+ return undefined;
285
+ const out = {};
286
+ for (const [key, value] of entries) {
287
+ const normalizedValue = normalizeHeaderValue(value);
288
+ if (!normalizedValue)
289
+ continue;
290
+ const trimmedKey = key.trim();
291
+ const trimmedValue = normalizedValue.trim();
292
+ if (!trimmedKey || !trimmedValue)
293
+ continue;
294
+ out[trimmedKey] = trimmedValue;
295
+ }
296
+ return Object.keys(out).length > 0 ? out : undefined;
297
+ }
298
+ function nonEmptyTrimmedString(value) {
299
+ if (typeof value !== "string")
300
+ return null;
301
+ const trimmed = value.trim();
302
+ return trimmed.length > 0 ? trimmed : null;
303
+ }
304
+ function headerMapHasKeyIgnoreCase(headers, targetKey) {
305
+ const normalizedTarget = targetKey.trim().toLowerCase();
306
+ return Object.keys(headers).some((key) => key.trim().toLowerCase() === normalizedTarget);
307
+ }
308
+ function headerMapGetIgnoreCase(headers, targetKey) {
309
+ const normalizedTarget = targetKey.trim().toLowerCase();
310
+ const key = Object.keys(headers).find((candidate) => candidate.trim().toLowerCase() === normalizedTarget);
311
+ if (!key)
312
+ return null;
313
+ const value = headers[key];
314
+ return typeof value === "string" ? value : null;
315
+ }
316
+ function tokenFromAuthorizationHeader(rawHeader) {
317
+ const trimmed = nonEmptyTrimmedString(rawHeader);
318
+ if (!trimmed)
319
+ return null;
320
+ const bearerMatch = trimmed.match(/^bearer\s+(.+)$/i);
321
+ if (bearerMatch?.[1]) {
322
+ return nonEmptyTrimmedString(bearerMatch[1]);
323
+ }
324
+ return trimmed;
325
+ }
326
+ function parseBooleanLike(value) {
327
+ if (typeof value === "boolean")
328
+ return value;
329
+ if (typeof value !== "string")
330
+ return null;
331
+ const normalized = value.trim().toLowerCase();
332
+ if (normalized === "true" || normalized === "1")
333
+ return true;
334
+ if (normalized === "false" || normalized === "0")
335
+ return false;
336
+ return null;
337
+ }
338
+ function generateEd25519PrivateKeyPem() {
339
+ const generated = generateKeyPairSync("ed25519");
340
+ return generated.privateKey
341
+ .export({ type: "pkcs8", format: "pem" })
342
+ .toString();
343
+ }
344
+ export function buildJoinDefaultsPayloadForAccept(input) {
345
+ if (input.adapterType !== "openclaw_gateway") {
346
+ return input.defaultsPayload;
347
+ }
348
+ const merged = isPlainObject(input.defaultsPayload)
349
+ ? { ...input.defaultsPayload }
350
+ : {};
351
+ if (!nonEmptyTrimmedString(merged.evermoreApiUrl)) {
352
+ const legacyEvermoreApiUrl = nonEmptyTrimmedString(input.evermoreApiUrl);
353
+ if (legacyEvermoreApiUrl)
354
+ merged.evermoreApiUrl = legacyEvermoreApiUrl;
355
+ }
356
+ const mergedHeaders = normalizeHeaderMap(merged.headers) ?? {};
357
+ const inboundOpenClawAuthHeader = nonEmptyTrimmedString(input.inboundOpenClawAuthHeader);
358
+ const inboundOpenClawTokenHeader = nonEmptyTrimmedString(input.inboundOpenClawTokenHeader);
359
+ if (inboundOpenClawTokenHeader &&
360
+ !headerMapHasKeyIgnoreCase(mergedHeaders, "x-openclaw-token")) {
361
+ mergedHeaders["x-openclaw-token"] = inboundOpenClawTokenHeader;
362
+ }
363
+ if (inboundOpenClawAuthHeader &&
364
+ !headerMapHasKeyIgnoreCase(mergedHeaders, "x-openclaw-auth")) {
365
+ mergedHeaders["x-openclaw-auth"] = inboundOpenClawAuthHeader;
366
+ }
367
+ if (Object.keys(mergedHeaders).length > 0) {
368
+ merged.headers = mergedHeaders;
369
+ }
370
+ else {
371
+ delete merged.headers;
372
+ }
373
+ const discoveredToken = headerMapGetIgnoreCase(mergedHeaders, "x-openclaw-token") ??
374
+ headerMapGetIgnoreCase(mergedHeaders, "x-openclaw-auth") ??
375
+ tokenFromAuthorizationHeader(headerMapGetIgnoreCase(mergedHeaders, "authorization"));
376
+ if (discoveredToken &&
377
+ !headerMapHasKeyIgnoreCase(mergedHeaders, "x-openclaw-token")) {
378
+ mergedHeaders["x-openclaw-token"] = discoveredToken;
379
+ }
380
+ return Object.keys(merged).length > 0 ? merged : null;
381
+ }
382
+ export function mergeJoinDefaultsPayloadForReplay(existingDefaultsPayload, nextDefaultsPayload) {
383
+ if (!isPlainObject(existingDefaultsPayload) &&
384
+ !isPlainObject(nextDefaultsPayload)) {
385
+ return nextDefaultsPayload ?? existingDefaultsPayload;
386
+ }
387
+ if (!isPlainObject(existingDefaultsPayload)) {
388
+ return nextDefaultsPayload;
389
+ }
390
+ if (!isPlainObject(nextDefaultsPayload)) {
391
+ return existingDefaultsPayload;
392
+ }
393
+ const merged = {
394
+ ...existingDefaultsPayload,
395
+ ...nextDefaultsPayload
396
+ };
397
+ const existingHeaders = normalizeHeaderMap(existingDefaultsPayload.headers);
398
+ const nextHeaders = normalizeHeaderMap(nextDefaultsPayload.headers);
399
+ if (existingHeaders || nextHeaders) {
400
+ merged.headers = {
401
+ ...(existingHeaders ?? {}),
402
+ ...(nextHeaders ?? {})
403
+ };
404
+ }
405
+ else if (Object.prototype.hasOwnProperty.call(merged, "headers")) {
406
+ delete merged.headers;
407
+ }
408
+ return merged;
409
+ }
410
+ export function canReplayOpenClawGatewayInviteAccept(input) {
411
+ if (input.requestType !== "agent" ||
412
+ input.adapterType !== "openclaw_gateway") {
413
+ return false;
414
+ }
415
+ if (!input.existingJoinRequest) {
416
+ return false;
417
+ }
418
+ if (input.existingJoinRequest.requestType !== "agent" ||
419
+ input.existingJoinRequest.adapterType !== "openclaw_gateway") {
420
+ return false;
421
+ }
422
+ return (input.existingJoinRequest.status === "pending_approval" ||
423
+ input.existingJoinRequest.status === "approved");
424
+ }
425
+ function summarizeSecretForLog(value) {
426
+ const trimmed = nonEmptyTrimmedString(value);
427
+ if (!trimmed)
428
+ return null;
429
+ return {
430
+ present: true,
431
+ length: trimmed.length,
432
+ sha256Prefix: hashToken(trimmed).slice(0, 12)
433
+ };
434
+ }
435
+ function summarizeOpenClawGatewayDefaultsForLog(defaultsPayload) {
436
+ const defaults = isPlainObject(defaultsPayload)
437
+ ? defaultsPayload
438
+ : null;
439
+ const headers = defaults ? normalizeHeaderMap(defaults.headers) : undefined;
440
+ const gatewayTokenValue = headers
441
+ ? headerMapGetIgnoreCase(headers, "x-openclaw-token") ??
442
+ headerMapGetIgnoreCase(headers, "x-openclaw-auth") ??
443
+ tokenFromAuthorizationHeader(headerMapGetIgnoreCase(headers, "authorization"))
444
+ : null;
445
+ return {
446
+ present: Boolean(defaults),
447
+ keys: defaults ? Object.keys(defaults).sort() : [],
448
+ url: defaults ? nonEmptyTrimmedString(defaults.url) : null,
449
+ evermoreApiUrl: defaults
450
+ ? nonEmptyTrimmedString(defaults.evermoreApiUrl)
451
+ : null,
452
+ headerKeys: headers ? Object.keys(headers).sort() : [],
453
+ sessionKeyStrategy: defaults
454
+ ? nonEmptyTrimmedString(defaults.sessionKeyStrategy)
455
+ : null,
456
+ disableDeviceAuth: defaults
457
+ ? parseBooleanLike(defaults.disableDeviceAuth)
458
+ : null,
459
+ waitTimeoutMs: defaults && typeof defaults.waitTimeoutMs === "number"
460
+ ? defaults.waitTimeoutMs
461
+ : null,
462
+ devicePrivateKeyPem: defaults
463
+ ? summarizeSecretForLog(defaults.devicePrivateKeyPem)
464
+ : null,
465
+ gatewayToken: summarizeSecretForLog(gatewayTokenValue)
466
+ };
467
+ }
468
+ export function normalizeAgentDefaultsForJoin(input) {
469
+ const fatalErrors = [];
470
+ const diagnostics = [];
471
+ if (input.adapterType !== "openclaw_gateway") {
472
+ const normalized = isPlainObject(input.defaultsPayload)
473
+ ? input.defaultsPayload
474
+ : null;
475
+ return { normalized, diagnostics, fatalErrors };
476
+ }
477
+ if (!isPlainObject(input.defaultsPayload)) {
478
+ diagnostics.push({
479
+ code: "openclaw_gateway_defaults_missing",
480
+ level: "warn",
481
+ message: "No OpenClaw gateway config was provided in agentDefaultsPayload.",
482
+ hint: "Include agentDefaultsPayload.url and headers.x-openclaw-token for OpenClaw gateway joins."
483
+ });
484
+ fatalErrors.push("agentDefaultsPayload is required for adapterType=openclaw_gateway");
485
+ return {
486
+ normalized: null,
487
+ diagnostics,
488
+ fatalErrors
489
+ };
490
+ }
491
+ const defaults = input.defaultsPayload;
492
+ const normalized = {};
493
+ let gatewayUrl = null;
494
+ const rawGatewayUrl = nonEmptyTrimmedString(defaults.url);
495
+ if (!rawGatewayUrl) {
496
+ diagnostics.push({
497
+ code: "openclaw_gateway_url_missing",
498
+ level: "warn",
499
+ message: "OpenClaw gateway URL is missing.",
500
+ hint: "Set agentDefaultsPayload.url to ws:// or wss:// gateway URL."
501
+ });
502
+ fatalErrors.push("agentDefaultsPayload.url is required");
503
+ }
504
+ else {
505
+ try {
506
+ gatewayUrl = new URL(rawGatewayUrl);
507
+ if (gatewayUrl.protocol !== "ws:" && gatewayUrl.protocol !== "wss:") {
508
+ diagnostics.push({
509
+ code: "openclaw_gateway_url_protocol",
510
+ level: "warn",
511
+ message: `OpenClaw gateway URL must use ws:// or wss:// (got ${gatewayUrl.protocol}).`
512
+ });
513
+ fatalErrors.push("agentDefaultsPayload.url must use ws:// or wss:// for openclaw_gateway");
514
+ }
515
+ else {
516
+ normalized.url = gatewayUrl.toString();
517
+ diagnostics.push({
518
+ code: "openclaw_gateway_url_configured",
519
+ level: "info",
520
+ message: `Gateway endpoint set to ${gatewayUrl.toString()}`
521
+ });
522
+ }
523
+ }
524
+ catch {
525
+ diagnostics.push({
526
+ code: "openclaw_gateway_url_invalid",
527
+ level: "warn",
528
+ message: `Invalid OpenClaw gateway URL: ${rawGatewayUrl}`
529
+ });
530
+ fatalErrors.push("agentDefaultsPayload.url is not a valid URL");
531
+ }
532
+ }
533
+ const headers = normalizeHeaderMap(defaults.headers) ?? {};
534
+ const gatewayToken = headerMapGetIgnoreCase(headers, "x-openclaw-token") ??
535
+ headerMapGetIgnoreCase(headers, "x-openclaw-auth") ??
536
+ tokenFromAuthorizationHeader(headerMapGetIgnoreCase(headers, "authorization"));
537
+ if (gatewayToken && !headerMapHasKeyIgnoreCase(headers, "x-openclaw-token")) {
538
+ headers["x-openclaw-token"] = gatewayToken;
539
+ }
540
+ if (Object.keys(headers).length > 0) {
541
+ normalized.headers = headers;
542
+ }
543
+ if (!gatewayToken) {
544
+ diagnostics.push({
545
+ code: "openclaw_gateway_auth_header_missing",
546
+ level: "warn",
547
+ message: "Gateway auth token is missing from agent defaults.",
548
+ hint: "Set agentDefaultsPayload.headers.x-openclaw-token (or legacy x-openclaw-auth)."
549
+ });
550
+ fatalErrors.push("agentDefaultsPayload.headers.x-openclaw-token (or x-openclaw-auth) is required");
551
+ }
552
+ else if (gatewayToken.trim().length < 16) {
553
+ diagnostics.push({
554
+ code: "openclaw_gateway_auth_header_too_short",
555
+ level: "warn",
556
+ message: `Gateway auth token appears too short (${gatewayToken.trim().length} chars).`,
557
+ hint: "Use the full gateway auth token from ~/.openclaw/openclaw.json (typically long random string)."
558
+ });
559
+ fatalErrors.push("agentDefaultsPayload.headers.x-openclaw-token is too short; expected a full gateway token");
560
+ }
561
+ else {
562
+ diagnostics.push({
563
+ code: "openclaw_gateway_auth_header_configured",
564
+ level: "info",
565
+ message: "Gateway auth token configured."
566
+ });
567
+ }
568
+ if (isPlainObject(defaults.payloadTemplate)) {
569
+ normalized.payloadTemplate = defaults.payloadTemplate;
570
+ }
571
+ const parsedDisableDeviceAuth = parseBooleanLike(defaults.disableDeviceAuth);
572
+ const disableDeviceAuth = parsedDisableDeviceAuth === true;
573
+ if (parsedDisableDeviceAuth !== null) {
574
+ normalized.disableDeviceAuth = parsedDisableDeviceAuth;
575
+ }
576
+ const configuredDevicePrivateKeyPem = nonEmptyTrimmedString(defaults.devicePrivateKeyPem);
577
+ if (configuredDevicePrivateKeyPem) {
578
+ normalized.devicePrivateKeyPem = configuredDevicePrivateKeyPem;
579
+ diagnostics.push({
580
+ code: "openclaw_gateway_device_key_configured",
581
+ level: "info",
582
+ message: "Gateway device key configured. Pairing approvals should persist for this agent."
583
+ });
584
+ }
585
+ else if (!disableDeviceAuth) {
586
+ try {
587
+ normalized.devicePrivateKeyPem = generateEd25519PrivateKeyPem();
588
+ diagnostics.push({
589
+ code: "openclaw_gateway_device_key_generated",
590
+ level: "info",
591
+ message: "Generated persistent gateway device key for this join. Pairing approvals should persist for this agent."
592
+ });
593
+ }
594
+ catch (err) {
595
+ diagnostics.push({
596
+ code: "openclaw_gateway_device_key_generate_failed",
597
+ level: "warn",
598
+ message: `Failed to generate gateway device key: ${err instanceof Error ? err.message : String(err)}`,
599
+ hint: "Set agentDefaultsPayload.devicePrivateKeyPem explicitly or set disableDeviceAuth=true."
600
+ });
601
+ fatalErrors.push("Failed to generate gateway device key. Set devicePrivateKeyPem or disableDeviceAuth=true.");
602
+ }
603
+ }
604
+ const waitTimeoutMs = typeof defaults.waitTimeoutMs === "number" &&
605
+ Number.isFinite(defaults.waitTimeoutMs)
606
+ ? Math.floor(defaults.waitTimeoutMs)
607
+ : typeof defaults.waitTimeoutMs === "string"
608
+ ? Number.parseInt(defaults.waitTimeoutMs.trim(), 10)
609
+ : NaN;
610
+ if (Number.isFinite(waitTimeoutMs) && waitTimeoutMs > 0) {
611
+ normalized.waitTimeoutMs = waitTimeoutMs;
612
+ }
613
+ const timeoutSec = typeof defaults.timeoutSec === "number" && Number.isFinite(defaults.timeoutSec)
614
+ ? Math.floor(defaults.timeoutSec)
615
+ : typeof defaults.timeoutSec === "string"
616
+ ? Number.parseInt(defaults.timeoutSec.trim(), 10)
617
+ : NaN;
618
+ if (Number.isFinite(timeoutSec) && timeoutSec > 0) {
619
+ normalized.timeoutSec = timeoutSec;
620
+ }
621
+ const sessionKeyStrategy = nonEmptyTrimmedString(defaults.sessionKeyStrategy);
622
+ if (sessionKeyStrategy === "fixed" ||
623
+ sessionKeyStrategy === "issue" ||
624
+ sessionKeyStrategy === "run") {
625
+ normalized.sessionKeyStrategy = sessionKeyStrategy;
626
+ }
627
+ const sessionKey = nonEmptyTrimmedString(defaults.sessionKey);
628
+ if (sessionKey) {
629
+ normalized.sessionKey = sessionKey;
630
+ }
631
+ const role = nonEmptyTrimmedString(defaults.role);
632
+ if (role) {
633
+ normalized.role = role;
634
+ }
635
+ if (Array.isArray(defaults.scopes)) {
636
+ const scopes = defaults.scopes
637
+ .filter((entry) => typeof entry === "string")
638
+ .map((entry) => entry.trim())
639
+ .filter(Boolean);
640
+ if (scopes.length > 0) {
641
+ normalized.scopes = scopes;
642
+ }
643
+ }
644
+ const rawEvermoreApiUrl = typeof defaults.evermoreApiUrl === "string"
645
+ ? defaults.evermoreApiUrl.trim()
646
+ : "";
647
+ if (rawEvermoreApiUrl) {
648
+ try {
649
+ const parsedEvermoreApiUrl = new URL(rawEvermoreApiUrl);
650
+ if (parsedEvermoreApiUrl.protocol !== "http:" &&
651
+ parsedEvermoreApiUrl.protocol !== "https:") {
652
+ diagnostics.push({
653
+ code: "openclaw_gateway_evermore_api_url_protocol",
654
+ level: "warn",
655
+ message: `evermoreApiUrl must use http:// or https:// (got ${parsedEvermoreApiUrl.protocol}).`
656
+ });
657
+ }
658
+ else {
659
+ normalized.evermoreApiUrl = parsedEvermoreApiUrl.toString();
660
+ diagnostics.push({
661
+ code: "openclaw_gateway_evermore_api_url_configured",
662
+ level: "info",
663
+ message: `evermoreApiUrl set to ${parsedEvermoreApiUrl.toString()}`
664
+ });
665
+ }
666
+ }
667
+ catch {
668
+ diagnostics.push({
669
+ code: "openclaw_gateway_evermore_api_url_invalid",
670
+ level: "warn",
671
+ message: `Invalid evermoreApiUrl: ${rawEvermoreApiUrl}`
672
+ });
673
+ }
674
+ }
675
+ return { normalized, diagnostics, fatalErrors };
676
+ }
677
+ function toInviteSummaryResponse(req, token, invite, company = null) {
678
+ const companyInfo = typeof company === "string"
679
+ ? { name: company, brandColor: null, logoUrl: null }
680
+ : company;
681
+ const baseUrl = requestBaseUrl(req);
682
+ const invitePath = `/invite/${token}`;
683
+ const onboardingPath = `/api/invites/${token}/onboarding`;
684
+ const onboardingTextPath = `/api/invites/${token}/onboarding.txt`;
685
+ const skillIndexPath = `/api/invites/${token}/skills/index`;
686
+ const inviteMessage = extractInviteMessage(invite);
687
+ return {
688
+ id: invite.id,
689
+ companyId: invite.companyId,
690
+ companyName: companyInfo?.name ?? null,
691
+ companyLogoUrl: companyInfo?.logoUrl ?? null,
692
+ companyBrandColor: companyInfo?.brandColor ?? null,
693
+ inviteType: invite.inviteType,
694
+ allowedJoinTypes: invite.allowedJoinTypes,
695
+ humanRole: extractInviteHumanRole(invite),
696
+ expiresAt: invite.expiresAt,
697
+ invitePath,
698
+ inviteUrl: baseUrl ? `${baseUrl}${invitePath}` : invitePath,
699
+ onboardingPath,
700
+ onboardingUrl: baseUrl ? `${baseUrl}${onboardingPath}` : onboardingPath,
701
+ onboardingTextPath,
702
+ onboardingTextUrl: baseUrl
703
+ ? `${baseUrl}${onboardingTextPath}`
704
+ : onboardingTextPath,
705
+ skillIndexPath,
706
+ skillIndexUrl: baseUrl
707
+ ? `${baseUrl}${skillIndexPath}`
708
+ : skillIndexPath,
709
+ inviteMessage
710
+ };
711
+ }
712
+ function actorHasActiveUserMembership(req, companyId) {
713
+ return (req.actor.type === "board" &&
714
+ typeof req.actor.userId === "string" &&
715
+ Array.isArray(req.actor.memberships) &&
716
+ req.actor.memberships.some((membership) => membership.companyId === companyId && membership.status === "active"));
717
+ }
718
+ async function loadUsersById(db, userIds) {
719
+ if (userIds.length === 0)
720
+ return new Map();
721
+ const rows = await db
722
+ .select({
723
+ id: authUsers.id,
724
+ email: authUsers.email,
725
+ name: authUsers.name,
726
+ image: authUsers.image,
727
+ })
728
+ .from(authUsers)
729
+ .where(inArray(authUsers.id, userIds));
730
+ return new Map(rows.map((row) => [row.id, toUserProfile(row)]));
731
+ }
732
+ async function loadCompanyAccessSummary(req, access, companyId) {
733
+ if (req.actor.type !== "board") {
734
+ return {
735
+ currentUserRole: null,
736
+ canManageMembers: false,
737
+ canInviteUsers: false,
738
+ canApproveJoinRequests: false,
739
+ };
740
+ }
741
+ if (isLocalImplicit(req)) {
742
+ return {
743
+ currentUserRole: "owner",
744
+ canManageMembers: true,
745
+ canInviteUsers: true,
746
+ canApproveJoinRequests: true,
747
+ };
748
+ }
749
+ const userId = req.actor.userId ?? null;
750
+ const membership = userId ? await access.getMembership(companyId, "user", userId) : null;
751
+ const [canManageMembers, canInviteUsers, canApproveJoinRequests] = await Promise.all([
752
+ access.canUser(companyId, userId, "users:manage_permissions"),
753
+ access.canUser(companyId, userId, "users:invite"),
754
+ access.canUser(companyId, userId, "joins:approve"),
755
+ ]);
756
+ return {
757
+ currentUserRole: membership?.status === "active" && membership.membershipRole
758
+ ? normalizeHumanRole(membership.membershipRole, "operator")
759
+ : null,
760
+ canManageMembers,
761
+ canInviteUsers,
762
+ canApproveJoinRequests,
763
+ };
764
+ }
765
+ async function loadCompanyMemberRecords(db, companyId, options = {}) {
766
+ const members = await db
767
+ .select()
768
+ .from(companyMemberships)
769
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalType, "user"), options.includeArchived ? undefined : ne(companyMemberships.status, "archived")))
770
+ .orderBy(desc(companyMemberships.updatedAt));
771
+ const userIds = [...new Set(members.map((member) => member.principalId))];
772
+ const [userMap, grants] = await Promise.all([
773
+ loadUsersById(db, userIds),
774
+ userIds.length > 0
775
+ ? db
776
+ .select()
777
+ .from(principalPermissionGrants)
778
+ .where(and(eq(principalPermissionGrants.companyId, companyId), eq(principalPermissionGrants.principalType, "user"), inArray(principalPermissionGrants.principalId, userIds)))
779
+ : Promise.resolve([]),
780
+ ]);
781
+ const grantsByPrincipalId = new Map();
782
+ for (const grant of grants) {
783
+ const existing = grantsByPrincipalId.get(grant.principalId) ?? [];
784
+ existing.push(grant);
785
+ grantsByPrincipalId.set(grant.principalId, existing);
786
+ }
787
+ return members.map((member) => ({
788
+ ...member,
789
+ principalType: "user",
790
+ membershipRole: member.membershipRole
791
+ ? normalizeHumanRole(member.membershipRole, "operator")
792
+ : null,
793
+ user: userMap.get(member.principalId) ?? null,
794
+ grants: grantsByPrincipalId.get(member.principalId) ?? [],
795
+ }));
796
+ }
797
+ const humanRoleRank = {
798
+ viewer: 1,
799
+ operator: 2,
800
+ admin: 3,
801
+ owner: 4,
802
+ };
803
+ async function resolveActorHumanRole(req, access, companyId) {
804
+ if (req.actor.type !== "board")
805
+ return null;
806
+ if (isLocalImplicit(req) || req.actor.isInstanceAdmin)
807
+ return "owner";
808
+ const userId = req.actor.userId ?? null;
809
+ if (!userId)
810
+ return null;
811
+ const membership = await access.getMembership(companyId, "user", userId);
812
+ if (membership?.status !== "active" || !membership.membershipRole)
813
+ return null;
814
+ return normalizeHumanRole(membership.membershipRole, "operator");
815
+ }
816
+ async function getProtectedMemberReason(req, access, companyId, member, opts) {
817
+ if (member.principalType !== "user")
818
+ return "Only human company members can be removed.";
819
+ if (req.actor.type !== "board")
820
+ return "Board access is required to remove members.";
821
+ if (member.principalId === req.actor.userId)
822
+ return "You cannot remove yourself.";
823
+ const isTargetInstanceAdmin = opts?.instanceAdminUserIds
824
+ ? opts.instanceAdminUserIds.has(member.principalId)
825
+ : await access.isInstanceAdmin(member.principalId);
826
+ if (isTargetInstanceAdmin) {
827
+ return "Instance admins cannot be removed from company access.";
828
+ }
829
+ const targetRole = member.membershipRole
830
+ ? normalizeHumanRole(member.membershipRole, "operator")
831
+ : "operator";
832
+ if (opts?.operation === "archive") {
833
+ if (targetRole === "owner")
834
+ return "Board owners cannot be removed from company access.";
835
+ if (targetRole === "admin")
836
+ return "Company admins cannot be removed from company access.";
837
+ }
838
+ const actorRole = opts?.actorRole ?? await resolveActorHumanRole(req, access, companyId);
839
+ if (!actorRole)
840
+ return "Only active company members can remove users.";
841
+ if (humanRoleRank[targetRole] >= humanRoleRank[actorRole]) {
842
+ return "You can only remove users below your company role.";
843
+ }
844
+ return null;
845
+ }
846
+ async function assertCanManageCompanyMember(req, access, companyId, member, operation = "update") {
847
+ const reason = await getProtectedMemberReason(req, access, companyId, member, { operation });
848
+ if (reason)
849
+ throw forbidden(reason);
850
+ }
851
+ async function addCompanyMemberRemovalAccess(req, db, access, companyId, members) {
852
+ const actorRole = await resolveActorHumanRole(req, access, companyId);
853
+ const userIds = [...new Set(members
854
+ .filter((member) => member.principalType === "user")
855
+ .map((member) => member.principalId))];
856
+ const instanceAdminUserIds = userIds.length > 0
857
+ ? new Set(await db
858
+ .select({ userId: instanceUserRoles.userId })
859
+ .from(instanceUserRoles)
860
+ .where(and(inArray(instanceUserRoles.userId, userIds), eq(instanceUserRoles.role, "instance_admin")))
861
+ .then((rows) => rows.map((row) => row.userId)))
862
+ : new Set();
863
+ return Promise.all(members.map(async (member) => {
864
+ const reason = await getProtectedMemberReason(req, access, companyId, member, {
865
+ actorRole,
866
+ instanceAdminUserIds,
867
+ operation: "archive",
868
+ });
869
+ return {
870
+ ...member,
871
+ removal: {
872
+ canArchive: !reason,
873
+ reason,
874
+ },
875
+ };
876
+ }));
877
+ }
878
+ async function loadCompanyUserDirectory(db, companyId) {
879
+ const members = await db
880
+ .select({
881
+ principalId: companyMemberships.principalId,
882
+ status: companyMemberships.status,
883
+ })
884
+ .from(companyMemberships)
885
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.status, "active")))
886
+ .orderBy(desc(companyMemberships.updatedAt));
887
+ const userIds = [...new Set(members.map((member) => member.principalId))];
888
+ const userMap = await loadUsersById(db, userIds);
889
+ return members.map((member) => ({
890
+ principalId: member.principalId,
891
+ status: "active",
892
+ user: userMap.get(member.principalId) ?? null,
893
+ }));
894
+ }
895
+ function inviteStateWhereClause(state) {
896
+ const now = new Date();
897
+ switch (state) {
898
+ case "active":
899
+ return and(isNull(invites.revokedAt), isNull(invites.acceptedAt), gt(invites.expiresAt, now));
900
+ case "accepted":
901
+ return isNotNull(invites.acceptedAt);
902
+ case "expired":
903
+ return and(isNull(invites.revokedAt), isNull(invites.acceptedAt), lte(invites.expiresAt, now));
904
+ case "revoked":
905
+ return isNotNull(invites.revokedAt);
906
+ default:
907
+ return undefined;
908
+ }
909
+ }
910
+ async function loadCompanyInviteRecords(db, companyId, options) {
911
+ const whereClause = inviteStateWhereClause(options.state);
912
+ const rows = await db
913
+ .select()
914
+ .from(invites)
915
+ .where(whereClause ? and(eq(invites.companyId, companyId), whereClause) : eq(invites.companyId, companyId))
916
+ .orderBy(desc(invites.createdAt))
917
+ .limit(options.limit + 1)
918
+ .offset(options.offset);
919
+ const hasMore = rows.length > options.limit;
920
+ const visibleRows = hasMore ? rows.slice(0, options.limit) : rows;
921
+ const userIds = [
922
+ ...new Set(visibleRows
923
+ .map((invite) => invite.invitedByUserId)
924
+ .filter((value) => Boolean(value))),
925
+ ];
926
+ const [userMap, joinRows, companyName] = await Promise.all([
927
+ loadUsersById(db, userIds),
928
+ visibleRows.length
929
+ ? db
930
+ .select({ id: joinRequests.id, inviteId: joinRequests.inviteId })
931
+ .from(joinRequests)
932
+ .where(and(eq(joinRequests.companyId, companyId), inArray(joinRequests.inviteId, visibleRows.map((invite) => invite.id))))
933
+ : Promise.resolve([]),
934
+ db
935
+ .select({ name: companies.name })
936
+ .from(companies)
937
+ .where(eq(companies.id, companyId))
938
+ .then((companyRows) => companyRows[0]?.name ?? null),
939
+ ]);
940
+ const joinRequestIdByInviteId = new Map(joinRows.map((row) => [row.inviteId, row.id]));
941
+ return {
942
+ invites: visibleRows.map((invite) => ({
943
+ ...invite,
944
+ companyName,
945
+ humanRole: extractInviteHumanRole(invite),
946
+ inviteMessage: extractInviteMessage(invite),
947
+ state: inviteState(invite),
948
+ invitedByUser: invite.invitedByUserId
949
+ ? userMap.get(invite.invitedByUserId) ?? null
950
+ : null,
951
+ relatedJoinRequestId: joinRequestIdByInviteId.get(invite.id) ?? null,
952
+ })),
953
+ nextOffset: hasMore ? options.offset + options.limit : null,
954
+ };
955
+ }
956
+ async function loadJoinRequestRecords(db, companyId) {
957
+ const rows = collapseDuplicatePendingHumanJoinRequests(await db
958
+ .select()
959
+ .from(joinRequests)
960
+ .where(eq(joinRequests.companyId, companyId))
961
+ .orderBy(desc(joinRequests.createdAt)));
962
+ const inviteIds = [...new Set(rows.map((row) => row.inviteId))];
963
+ const inviteRows = inviteIds.length
964
+ ? await db
965
+ .select()
966
+ .from(invites)
967
+ .where(inArray(invites.id, inviteIds))
968
+ : [];
969
+ const userIds = [
970
+ ...new Set([
971
+ ...rows.map((row) => row.requestingUserId),
972
+ ...rows.map((row) => row.approvedByUserId),
973
+ ...rows.map((row) => row.rejectedByUserId),
974
+ ...inviteRows.map((invite) => invite.invitedByUserId),
975
+ ].filter((value) => Boolean(value))),
976
+ ];
977
+ const userMap = await loadUsersById(db, userIds);
978
+ const inviteMap = new Map(inviteRows.map((invite) => [invite.id, invite]));
979
+ return rows.map((row) => {
980
+ const invite = inviteMap.get(row.inviteId) ?? null;
981
+ return {
982
+ ...toJoinRequestResponse(row),
983
+ requesterUser: row.requestingUserId
984
+ ? userMap.get(row.requestingUserId) ?? null
985
+ : null,
986
+ approvedByUser: row.approvedByUserId
987
+ ? userMap.get(row.approvedByUserId) ?? null
988
+ : null,
989
+ rejectedByUser: row.rejectedByUserId
990
+ ? userMap.get(row.rejectedByUserId) ?? null
991
+ : null,
992
+ invite: invite
993
+ ? {
994
+ id: invite.id,
995
+ inviteType: invite.inviteType,
996
+ allowedJoinTypes: invite.allowedJoinTypes,
997
+ humanRole: extractInviteHumanRole(invite),
998
+ inviteMessage: extractInviteMessage(invite),
999
+ createdAt: invite.createdAt,
1000
+ expiresAt: invite.expiresAt,
1001
+ revokedAt: invite.revokedAt,
1002
+ acceptedAt: invite.acceptedAt,
1003
+ invitedByUser: invite.invitedByUserId
1004
+ ? userMap.get(invite.invitedByUserId) ?? null
1005
+ : null,
1006
+ }
1007
+ : null,
1008
+ };
1009
+ });
1010
+ }
1011
+ async function loadUserCompanyAccessResponse(db, access, userId) {
1012
+ const [memberships, user, isInstanceAdmin] = await Promise.all([
1013
+ access.listUserCompanyAccess(userId),
1014
+ db
1015
+ .select({
1016
+ id: authUsers.id,
1017
+ email: authUsers.email,
1018
+ name: authUsers.name,
1019
+ image: authUsers.image,
1020
+ })
1021
+ .from(authUsers)
1022
+ .where(eq(authUsers.id, userId))
1023
+ .then((rows) => rows[0] ?? null),
1024
+ access.isInstanceAdmin(userId),
1025
+ ]);
1026
+ const companyIds = [...new Set(memberships.map((membership) => membership.companyId))];
1027
+ const companyRows = companyIds.length
1028
+ ? await db
1029
+ .select({
1030
+ id: companies.id,
1031
+ name: companies.name,
1032
+ status: companies.status,
1033
+ })
1034
+ .from(companies)
1035
+ .where(inArray(companies.id, companyIds))
1036
+ : [];
1037
+ const companyMap = new Map(companyRows.map((company) => [company.id, company]));
1038
+ return {
1039
+ user: user
1040
+ ? {
1041
+ ...toUserProfile(user),
1042
+ isInstanceAdmin,
1043
+ }
1044
+ : null,
1045
+ companyAccess: memberships.map((membership) => {
1046
+ const company = companyMap.get(membership.companyId) ?? null;
1047
+ return {
1048
+ ...membership,
1049
+ principalType: "user",
1050
+ companyName: company?.name ?? null,
1051
+ companyStatus: company?.status ?? null,
1052
+ };
1053
+ }),
1054
+ };
1055
+ }
1056
+ function buildOnboardingDiscoveryDiagnostics(input) {
1057
+ const diagnostics = [];
1058
+ let apiHost = null;
1059
+ if (input.apiBaseUrl) {
1060
+ try {
1061
+ apiHost = normalizeHostname(new URL(input.apiBaseUrl).hostname);
1062
+ }
1063
+ catch {
1064
+ apiHost = null;
1065
+ }
1066
+ }
1067
+ const bindHost = normalizeHostname(input.bindHost);
1068
+ const allowSet = new Set(input.allowedHostnames
1069
+ .map((entry) => normalizeHostname(entry))
1070
+ .filter((entry) => Boolean(entry)));
1071
+ if (apiHost && isLoopbackHost(apiHost)) {
1072
+ diagnostics.push({
1073
+ code: "openclaw_onboarding_api_loopback",
1074
+ level: "warn",
1075
+ message: "Onboarding URL resolves to loopback hostname. Remote OpenClaw agents cannot reach localhost on your Evermore host.",
1076
+ hint: "Use a reachable hostname/IP (for example Tailscale hostname, Docker host alias, or public domain)."
1077
+ });
1078
+ }
1079
+ if (input.deploymentMode === "authenticated" &&
1080
+ input.deploymentExposure === "private" &&
1081
+ (!bindHost || isLoopbackHost(bindHost))) {
1082
+ diagnostics.push({
1083
+ code: "openclaw_onboarding_private_loopback_bind",
1084
+ level: "warn",
1085
+ message: "Evermore is bound to loopback in authenticated/private mode.",
1086
+ hint: "Use a reachable private bind mode such as `pnpm dev --bind lan` or `pnpm dev --bind tailnet` for private-network onboarding."
1087
+ });
1088
+ }
1089
+ if (input.deploymentMode === "authenticated" &&
1090
+ input.deploymentExposure === "private" &&
1091
+ apiHost &&
1092
+ !isLoopbackHost(apiHost) &&
1093
+ allowSet.size > 0 &&
1094
+ !allowSet.has(apiHost)) {
1095
+ diagnostics.push({
1096
+ code: "openclaw_onboarding_private_host_not_allowed",
1097
+ level: "warn",
1098
+ message: `Onboarding host "${apiHost}" is not in allowed hostnames for authenticated/private mode.`,
1099
+ hint: `Run pnpm evermore allowed-hostname ${apiHost}`
1100
+ });
1101
+ }
1102
+ return diagnostics;
1103
+ }
1104
+ function buildOnboardingConnectionCandidates(input) {
1105
+ let base = null;
1106
+ try {
1107
+ if (input.apiBaseUrl) {
1108
+ base = new URL(input.apiBaseUrl);
1109
+ }
1110
+ }
1111
+ catch {
1112
+ base = null;
1113
+ }
1114
+ const protocol = base?.protocol ?? "http:";
1115
+ const port = base?.port ? `:${base.port}` : "";
1116
+ const candidates = new Set();
1117
+ if (base) {
1118
+ candidates.add(base.origin);
1119
+ }
1120
+ const bindHost = normalizeHostname(input.bindHost);
1121
+ if (bindHost && !isLoopbackHost(bindHost)) {
1122
+ candidates.add(`${protocol}//${bindHost}${port}`);
1123
+ }
1124
+ for (const rawHost of input.allowedHostnames) {
1125
+ const host = normalizeHostname(rawHost);
1126
+ if (!host)
1127
+ continue;
1128
+ candidates.add(`${protocol}//${host}${port}`);
1129
+ }
1130
+ if (base && isLoopbackHost(base.hostname)) {
1131
+ candidates.add(`${protocol}//host.docker.internal${port}`);
1132
+ }
1133
+ for (const host of collectReachableInterfaceHosts()) {
1134
+ const formattedHost = host.includes(":") && !host.startsWith("[") && !host.endsWith("]") ? `[${host}]` : host;
1135
+ candidates.add(`${protocol}//${formattedHost}${port}`);
1136
+ }
1137
+ return Array.from(candidates);
1138
+ }
1139
+ function buildInviteOnboardingManifest(req, token, invite, opts) {
1140
+ const baseUrl = requestBaseUrl(req);
1141
+ const skillPath = `/api/invites/${token}/skills/evermore`;
1142
+ const skillUrl = baseUrl ? `${baseUrl}${skillPath}` : skillPath;
1143
+ const registrationEndpointPath = `/api/invites/${token}/accept`;
1144
+ const registrationEndpointUrl = baseUrl
1145
+ ? `${baseUrl}${registrationEndpointPath}`
1146
+ : registrationEndpointPath;
1147
+ const onboardingTextPath = `/api/invites/${token}/onboarding.txt`;
1148
+ const onboardingTextUrl = baseUrl
1149
+ ? `${baseUrl}${onboardingTextPath}`
1150
+ : onboardingTextPath;
1151
+ const discoveryDiagnostics = buildOnboardingDiscoveryDiagnostics({
1152
+ apiBaseUrl: baseUrl,
1153
+ deploymentMode: opts.deploymentMode,
1154
+ deploymentExposure: opts.deploymentExposure,
1155
+ bindHost: opts.bindHost,
1156
+ allowedHostnames: opts.allowedHostnames
1157
+ });
1158
+ const connectionCandidates = buildOnboardingConnectionCandidates({
1159
+ apiBaseUrl: baseUrl,
1160
+ bindHost: opts.bindHost,
1161
+ allowedHostnames: opts.allowedHostnames
1162
+ });
1163
+ return {
1164
+ invite: toInviteSummaryResponse(req, token, invite, opts.companyName ?? null),
1165
+ onboarding: {
1166
+ instructions: "Join as an OpenClaw Gateway agent, save your one-time claim secret, wait for board approval, then claim your API key. Save the claim response token to ~/.openclaw/workspace/evermore-claimed-api-key.json and load EVERMORE_API_KEY from that file before starting heartbeat loops. You MUST submit adapterType='openclaw_gateway', set agentDefaultsPayload.url to your ws:// or wss:// OpenClaw gateway endpoint, and include agentDefaultsPayload.headers.x-openclaw-token (or legacy x-openclaw-auth).",
1167
+ inviteMessage: extractInviteMessage(invite),
1168
+ recommendedAdapterType: "openclaw_gateway",
1169
+ requiredFields: {
1170
+ requestType: "agent",
1171
+ agentName: "Display name for this agent",
1172
+ adapterType: "Use 'openclaw_gateway' for OpenClaw Gateway agents",
1173
+ capabilities: "Optional capability summary",
1174
+ agentDefaultsPayload: "Adapter config for OpenClaw gateway. MUST include url (ws:// or wss://) and headers.x-openclaw-token (or legacy x-openclaw-auth). Optional fields: evermoreApiUrl, waitTimeoutMs, sessionKeyStrategy, sessionKey, role, scopes, disableDeviceAuth, devicePrivateKeyPem."
1175
+ },
1176
+ registrationEndpoint: {
1177
+ method: "POST",
1178
+ path: registrationEndpointPath,
1179
+ url: registrationEndpointUrl
1180
+ },
1181
+ claimEndpointTemplate: {
1182
+ method: "POST",
1183
+ path: "/api/join-requests/{requestId}/claim-api-key",
1184
+ body: {
1185
+ claimSecret: "one-time claim secret returned when the join request is created"
1186
+ }
1187
+ },
1188
+ connectivity: {
1189
+ deploymentMode: opts.deploymentMode,
1190
+ deploymentExposure: opts.deploymentExposure,
1191
+ bindHost: opts.bindHost,
1192
+ allowedHostnames: opts.allowedHostnames,
1193
+ connectionCandidates,
1194
+ diagnostics: discoveryDiagnostics,
1195
+ guidance: opts.deploymentMode === "authenticated" &&
1196
+ opts.deploymentExposure === "private"
1197
+ ? "If OpenClaw runs on another machine, ensure the Evermore hostname is reachable and allowed via `pnpm evermore allowed-hostname <host>`."
1198
+ : "Ensure OpenClaw can reach this Evermore API base URL for invite, claim, and skill bootstrap calls."
1199
+ },
1200
+ textInstructions: {
1201
+ path: onboardingTextPath,
1202
+ url: onboardingTextUrl,
1203
+ contentType: "text/plain"
1204
+ },
1205
+ skill: {
1206
+ name: "evermore",
1207
+ path: skillPath,
1208
+ url: skillUrl,
1209
+ installPath: "~/.openclaw/skills/evermore/SKILL.md"
1210
+ }
1211
+ }
1212
+ };
1213
+ }
1214
+ export function buildInviteOnboardingTextDocument(req, token, invite, opts) {
1215
+ const manifest = buildInviteOnboardingManifest(req, token, invite, opts);
1216
+ const onboarding = manifest.onboarding;
1217
+ const diagnostics = Array.isArray(onboarding.connectivity?.diagnostics)
1218
+ ? onboarding.connectivity.diagnostics
1219
+ : [];
1220
+ const lines = [];
1221
+ const appendBlock = (block) => {
1222
+ const trimmed = block.replace(/^\n/, "").replace(/\n\s*$/, "");
1223
+ const lineIndentation = trimmed
1224
+ .split("\n")
1225
+ .filter((line) => line.trim().length > 0)
1226
+ .map((line) => line.match(/^(\s*)/)?.[0].length ?? 0);
1227
+ const minIndent = lineIndentation.length > 0 ? Math.min(...lineIndentation) : 0;
1228
+ for (const line of trimmed.split("\n")) {
1229
+ lines.push(line.slice(minIndent));
1230
+ }
1231
+ };
1232
+ appendBlock(`
1233
+ # Evermore OpenClaw Gateway Onboarding
1234
+
1235
+ This document is meant to be readable by both humans and agents.
1236
+
1237
+ ## Invite
1238
+ - inviteType: ${invite.inviteType}
1239
+ - allowedJoinTypes: ${invite.allowedJoinTypes}
1240
+ - expiresAt: ${invite.expiresAt.toISOString()}
1241
+ `);
1242
+ if (manifest.invite.companyName) {
1243
+ lines.push(`- companyName: ${manifest.invite.companyName}`);
1244
+ }
1245
+ if (onboarding.inviteMessage) {
1246
+ appendBlock(`
1247
+ ## Message from inviter
1248
+ ${onboarding.inviteMessage}
1249
+ `);
1250
+ }
1251
+ appendBlock(`
1252
+ ## Step 0
1253
+
1254
+ Get the OpenClaw gateway auth token (THIS MUST BE SENT)
1255
+ Token lives in:
1256
+
1257
+ ~/.openclaw/openclaw.json -> gateway.auth.token
1258
+ Extract:
1259
+
1260
+ TOKEN="$(node -p 'require(process.env.HOME+\"/.openclaw/openclaw.json\").gateway.auth.token')"
1261
+ test -n "$TOKEN" || (echo "Missing TOKEN" && exit 1)
1262
+ test "\${#TOKEN}" -ge 16 || (echo "Gateway token unexpectedly short (\${#TOKEN})" && exit 1)
1263
+
1264
+ 3) IMPORTANT: Don't accidentally drop the token when generating JSON
1265
+ If you build JSON with Node, pass the token explicitly (argv), don't rely on an un-exported env var.
1266
+
1267
+ Safe payload build looks sort of like this (substitute where necessary):
1268
+
1269
+ BODY="$(node -e '
1270
+ const token = process.argv[1];
1271
+ if (!token) process.exit(2);
1272
+ const body = {
1273
+ requestType: "agent",
1274
+ agentName: "OpenClaw",
1275
+ adapterType: "openclaw_gateway",
1276
+ capabilities: "OpenClaw agent adapter",
1277
+ agentDefaultsPayload: {
1278
+ url: "ws://127.0.0.1:18789",
1279
+ evermoreApiUrl: "http://host.docker.internal:3100",
1280
+ headers: { "x-openclaw-token": token },
1281
+ waitTimeoutMs: 120000,
1282
+ sessionKeyStrategy: "issue",
1283
+ role: "operator",
1284
+ scopes: ["operator.admin"]
1285
+ }
1286
+ };
1287
+ process.stdout.write(JSON.stringify(body));
1288
+ ' "$TOKEN")"
1289
+
1290
+ ## Step 1: Submit agent join request
1291
+ ${onboarding.registrationEndpoint.method} ${onboarding.registrationEndpoint.url}
1292
+
1293
+ IMPORTANT: You MUST include agentDefaultsPayload.headers.x-openclaw-token with your gateway token.
1294
+ Legacy x-openclaw-auth is also accepted, but x-openclaw-token is preferred.
1295
+ Use adapterType "openclaw_gateway" and a ws:// or wss:// gateway URL.
1296
+ Pairing mode requirement:
1297
+ - Keep device auth enabled (recommended). If devicePrivateKeyPem is omitted, Evermore generates and persists one during join so pairing approvals are stable.
1298
+ - You may set disableDeviceAuth=true only for special environments that cannot support pairing.
1299
+ - First run may return "pairing required" once; approve the pending pairing request in OpenClaw, then retry.
1300
+ Do NOT use /v1/responses or /hooks/* in this gateway join flow.
1301
+
1302
+ Body (JSON):
1303
+ {
1304
+ "requestType": "agent",
1305
+ "agentName": "My OpenClaw Agent",
1306
+ "adapterType": "openclaw_gateway",
1307
+ "capabilities": "Optional summary",
1308
+ "agentDefaultsPayload": {
1309
+ "url": "wss://your-openclaw-gateway.example",
1310
+ "evermoreApiUrl": "https://evermore-hostname-your-agent-can-reach:3100",
1311
+ "headers": { "x-openclaw-token": "replace-me" },
1312
+ "waitTimeoutMs": 120000,
1313
+ "sessionKeyStrategy": "issue",
1314
+ "role": "operator",
1315
+ "scopes": ["operator.admin"]
1316
+ }
1317
+ }
1318
+
1319
+ Expected response includes:
1320
+ - request id
1321
+ - one-time claimSecret
1322
+ - claimApiKeyPath
1323
+
1324
+ ## Step 2: Wait for board approval
1325
+ The board approves the join request in Evermore before key claim is allowed.
1326
+
1327
+ ## Step 3: Claim API key (one-time)
1328
+ ${onboarding.claimEndpointTemplate.method} /api/join-requests/{requestId}/claim-api-key
1329
+
1330
+ Body (JSON):
1331
+ {
1332
+ "claimSecret": "<one-time-claim-secret>"
1333
+ }
1334
+
1335
+ On successful claim, save the full JSON response to:
1336
+
1337
+ - ~/.openclaw/workspace/evermore-claimed-api-key.json
1338
+ chmod 600 ~/.openclaw/workspace/evermore-claimed-api-key.json
1339
+
1340
+ And set the EVERMORE_API_KEY and EVERMORE_API_URL in your environment variables as specified here:
1341
+ https://docs.openclaw.ai/help/environment
1342
+
1343
+ e.g.
1344
+
1345
+ {
1346
+ env: {
1347
+ EVERMORE_API_KEY: "...",
1348
+ EVERMORE_API_URL: "...",
1349
+ },
1350
+ }
1351
+
1352
+ Then set EVERMORE_API_KEY and EVERMORE_API_URL from the saved token field for every heartbeat run.
1353
+
1354
+ Important:
1355
+ - claim secrets expire
1356
+ - claim secrets are single-use
1357
+ - claim fails before board approval
1358
+
1359
+ ## Step 4: Install Evermore skill in OpenClaw
1360
+ GET ${onboarding.skill.url}
1361
+ Install path: ${onboarding.skill.installPath}
1362
+
1363
+ Be sure to prepend your EVERMORE_API_URL to the top of your skill and note the path to your EVERMORE_API_URL
1364
+
1365
+ ## Text onboarding URL
1366
+ ${onboarding.textInstructions.url}
1367
+
1368
+ ## Connectivity guidance
1369
+ ${onboarding.connectivity?.guidance ??
1370
+ "Ensure Evermore is reachable from your OpenClaw runtime."}
1371
+ `);
1372
+ const connectionCandidates = Array.isArray(onboarding.connectivity?.connectionCandidates)
1373
+ ? onboarding.connectivity.connectionCandidates.filter((entry) => Boolean(entry))
1374
+ : [];
1375
+ if (connectionCandidates.length > 0) {
1376
+ lines.push("## Suggested Evermore base URLs to try");
1377
+ for (const candidate of connectionCandidates) {
1378
+ lines.push(`- ${candidate}`);
1379
+ }
1380
+ appendBlock(`
1381
+
1382
+ Test each candidate with:
1383
+ - GET <candidate>/api/health
1384
+ - set the first reachable candidate as agentDefaultsPayload.evermoreApiUrl when submitting your join request
1385
+
1386
+ If none are reachable: ask your human operator for a reachable hostname/address and help them update network configuration.
1387
+ For authenticated/private mode, they may need:
1388
+ - pnpm evermore allowed-hostname <host>
1389
+ - then restart Evermore and retry onboarding.
1390
+ `);
1391
+ }
1392
+ if (diagnostics.length > 0) {
1393
+ lines.push("## Connectivity diagnostics");
1394
+ for (const diag of diagnostics) {
1395
+ lines.push(`- [${diag.level}] ${diag.message}`);
1396
+ if (diag.hint)
1397
+ lines.push(` hint: ${diag.hint}`);
1398
+ }
1399
+ }
1400
+ appendBlock(`
1401
+
1402
+ ## Helpful endpoints
1403
+ ${onboarding.registrationEndpoint.path}
1404
+ ${onboarding.claimEndpointTemplate.path}
1405
+ ${onboarding.skill.path}
1406
+ ${manifest.invite.onboardingPath}
1407
+ `);
1408
+ return `${lines.join("\n")}\n`;
1409
+ }
1410
+ function extractInviteMessage(invite) {
1411
+ const rawDefaults = invite.defaultsPayload;
1412
+ if (!rawDefaults ||
1413
+ typeof rawDefaults !== "object" ||
1414
+ Array.isArray(rawDefaults)) {
1415
+ return null;
1416
+ }
1417
+ const rawMessage = rawDefaults.agentMessage;
1418
+ if (typeof rawMessage !== "string") {
1419
+ return null;
1420
+ }
1421
+ const trimmed = rawMessage.trim();
1422
+ return trimmed.length ? trimmed : null;
1423
+ }
1424
+ function mergeInviteDefaults(defaultsPayload, agentMessage, humanRole = null) {
1425
+ const merged = defaultsPayload && typeof defaultsPayload === "object"
1426
+ ? { ...defaultsPayload }
1427
+ : {};
1428
+ if (humanRole) {
1429
+ const existingHuman = isPlainObject(merged.human) ? { ...merged.human } : {};
1430
+ merged.human = {
1431
+ ...existingHuman,
1432
+ role: humanRole,
1433
+ grants: grantsForHumanRole(humanRole),
1434
+ };
1435
+ }
1436
+ if (agentMessage) {
1437
+ merged.agentMessage = agentMessage;
1438
+ }
1439
+ return Object.keys(merged).length ? merged : null;
1440
+ }
1441
+ function requestIp(req) {
1442
+ const forwarded = req.header("x-forwarded-for");
1443
+ if (forwarded) {
1444
+ const first = forwarded.split(",")[0]?.trim();
1445
+ if (first)
1446
+ return first;
1447
+ }
1448
+ return req.ip || "unknown";
1449
+ }
1450
+ function inviteExpired(invite) {
1451
+ return invite.expiresAt.getTime() <= Date.now();
1452
+ }
1453
+ function inviteState(invite) {
1454
+ if (invite.revokedAt)
1455
+ return "revoked";
1456
+ if (invite.acceptedAt)
1457
+ return "accepted";
1458
+ if (inviteExpired(invite))
1459
+ return "expired";
1460
+ return "active";
1461
+ }
1462
+ function extractInviteHumanRole(invite) {
1463
+ if (invite.allowedJoinTypes === "agent")
1464
+ return null;
1465
+ return resolveHumanInviteRole(invite.defaultsPayload);
1466
+ }
1467
+ function isLocalImplicit(req) {
1468
+ return req.actor.type === "board" && req.actor.source === "local_implicit";
1469
+ }
1470
+ function toUserProfile(user) {
1471
+ if (!user)
1472
+ return null;
1473
+ return {
1474
+ id: user.id,
1475
+ email: user.email ?? null,
1476
+ name: user.name ?? null,
1477
+ image: user.image ?? null,
1478
+ };
1479
+ }
1480
+ async function resolveActorEmail(db, req) {
1481
+ if (isLocalImplicit(req))
1482
+ return "local@evermore.local";
1483
+ const userId = req.actor.userId;
1484
+ if (!userId)
1485
+ return null;
1486
+ const user = await db
1487
+ .select({ email: authUsers.email })
1488
+ .from(authUsers)
1489
+ .where(eq(authUsers.id, userId))
1490
+ .then((rows) => rows[0] ?? null);
1491
+ return user?.email ?? null;
1492
+ }
1493
+ async function resolveAcceptedInviteJoinRequest(db, req, invite) {
1494
+ if (!invite?.acceptedAt)
1495
+ return null;
1496
+ const directJoinRequest = await db
1497
+ .select({
1498
+ requestType: joinRequests.requestType,
1499
+ status: joinRequests.status,
1500
+ requestingUserId: joinRequests.requestingUserId,
1501
+ requestEmailSnapshot: joinRequests.requestEmailSnapshot,
1502
+ })
1503
+ .from(joinRequests)
1504
+ .where(eq(joinRequests.inviteId, invite.id))
1505
+ .then((rows) => rows[0] ?? null);
1506
+ if (directJoinRequest)
1507
+ return directJoinRequest;
1508
+ if (!invite.companyId)
1509
+ return null;
1510
+ const actorRequestingUserId = isLocalImplicit(req)
1511
+ ? "local-board"
1512
+ : req.actor.userId ?? null;
1513
+ const actorEmail = await resolveActorEmail(db, req);
1514
+ if (!actorRequestingUserId && !actorEmail)
1515
+ return null;
1516
+ return findReusableHumanJoinRequest(await db
1517
+ .select({
1518
+ id: joinRequests.id,
1519
+ requestType: joinRequests.requestType,
1520
+ status: joinRequests.status,
1521
+ requestingUserId: joinRequests.requestingUserId,
1522
+ requestEmailSnapshot: joinRequests.requestEmailSnapshot,
1523
+ })
1524
+ .from(joinRequests)
1525
+ .where(and(eq(joinRequests.companyId, invite.companyId), eq(joinRequests.requestType, "human")))
1526
+ .orderBy(desc(joinRequests.createdAt)), {
1527
+ requestingUserId: actorRequestingUserId,
1528
+ requestEmailSnapshot: actorEmail,
1529
+ });
1530
+ }
1531
+ function grantsFromDefaults(defaultsPayload, key) {
1532
+ if (!defaultsPayload || typeof defaultsPayload !== "object")
1533
+ return [];
1534
+ const scoped = defaultsPayload[key];
1535
+ if (!scoped || typeof scoped !== "object")
1536
+ return [];
1537
+ const grants = scoped.grants;
1538
+ if (!Array.isArray(grants))
1539
+ return [];
1540
+ const validPermissionKeys = new Set(PERMISSION_KEYS);
1541
+ const result = [];
1542
+ for (const item of grants) {
1543
+ if (!item || typeof item !== "object")
1544
+ continue;
1545
+ const record = item;
1546
+ if (typeof record.permissionKey !== "string")
1547
+ continue;
1548
+ if (!validPermissionKeys.has(record.permissionKey))
1549
+ continue;
1550
+ result.push({
1551
+ permissionKey: record.permissionKey,
1552
+ scope: record.scope &&
1553
+ typeof record.scope === "object" &&
1554
+ !Array.isArray(record.scope)
1555
+ ? record.scope
1556
+ : null
1557
+ });
1558
+ }
1559
+ return result;
1560
+ }
1561
+ export function agentJoinGrantsFromDefaults(defaultsPayload) {
1562
+ const grants = grantsFromDefaults(defaultsPayload, "agent");
1563
+ if (grants.some((grant) => grant.permissionKey === "tasks:assign")) {
1564
+ return grants;
1565
+ }
1566
+ return [
1567
+ ...grants,
1568
+ {
1569
+ permissionKey: "tasks:assign",
1570
+ scope: null
1571
+ }
1572
+ ];
1573
+ }
1574
+ export function resolveJoinRequestAgentManagerId(candidates) {
1575
+ const ceoCandidates = candidates.filter((candidate) => candidate.role === "ceo");
1576
+ if (ceoCandidates.length === 0)
1577
+ return null;
1578
+ const rootCeo = ceoCandidates.find((candidate) => candidate.reportsTo === null);
1579
+ return (rootCeo ?? ceoCandidates[0] ?? null)?.id ?? null;
1580
+ }
1581
+ function isInviteTokenHashCollisionError(error) {
1582
+ const candidates = [
1583
+ error,
1584
+ error?.cause ?? null
1585
+ ];
1586
+ for (const candidate of candidates) {
1587
+ if (!candidate || typeof candidate !== "object")
1588
+ continue;
1589
+ const code = "code" in candidate && typeof candidate.code === "string"
1590
+ ? candidate.code
1591
+ : null;
1592
+ const message = "message" in candidate && typeof candidate.message === "string"
1593
+ ? candidate.message
1594
+ : "";
1595
+ const constraint = "constraint" in candidate && typeof candidate.constraint === "string"
1596
+ ? candidate.constraint
1597
+ : null;
1598
+ if (code !== "23505")
1599
+ continue;
1600
+ if (constraint === "invites_token_hash_unique_idx")
1601
+ return true;
1602
+ if (message.includes("invites_token_hash_unique_idx"))
1603
+ return true;
1604
+ }
1605
+ return false;
1606
+ }
1607
+ function isAbortError(error) {
1608
+ return error instanceof Error && error.name === "AbortError";
1609
+ }
1610
+ function parseIpv4Address(address) {
1611
+ const parts = address.split(".");
1612
+ if (parts.length !== 4)
1613
+ return null;
1614
+ const parsed = parts.map((part) => {
1615
+ if (!/^\d+$/.test(part))
1616
+ return NaN;
1617
+ return Number(part);
1618
+ });
1619
+ if (parsed.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
1620
+ return null;
1621
+ }
1622
+ return parsed;
1623
+ }
1624
+ function isPrivateOrReservedIpv4(address) {
1625
+ const octets = parseIpv4Address(address);
1626
+ if (!octets)
1627
+ return true;
1628
+ const [a, b, c] = octets;
1629
+ if (a === 0)
1630
+ return true;
1631
+ if (a === 10)
1632
+ return true;
1633
+ if (a === 100 && b >= 64 && b <= 127)
1634
+ return true;
1635
+ if (a === 127)
1636
+ return true;
1637
+ if (a === 169 && b === 254)
1638
+ return true;
1639
+ if (a === 172 && b >= 16 && b <= 31)
1640
+ return true;
1641
+ if (a === 192 && b === 0 && c === 0)
1642
+ return true;
1643
+ if (a === 192 && b === 168)
1644
+ return true;
1645
+ if (a === 192 && b === 0 && c === 2)
1646
+ return true;
1647
+ if (a === 192 && b === 88 && c === 99)
1648
+ return true;
1649
+ if (a === 198 && (b === 18 || b === 19))
1650
+ return true;
1651
+ if (a === 198 && b === 51 && c === 100)
1652
+ return true;
1653
+ if (a === 203 && b === 0 && c === 113)
1654
+ return true;
1655
+ if (a >= 224)
1656
+ return true;
1657
+ return false;
1658
+ }
1659
+ function parseMappedIpv4Hex(address) {
1660
+ const match = address.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
1661
+ if (!match)
1662
+ return null;
1663
+ const hi = Number.parseInt(match[1], 16);
1664
+ const lo = Number.parseInt(match[2], 16);
1665
+ if (!Number.isInteger(hi) || !Number.isInteger(lo))
1666
+ return null;
1667
+ return `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`;
1668
+ }
1669
+ function isPrivateOrReservedIpv6(address) {
1670
+ const lower = address.toLowerCase();
1671
+ if (lower.startsWith("::ffff:")) {
1672
+ const mappedIpv4 = lower.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
1673
+ if (mappedIpv4?.[1])
1674
+ return isPrivateOrReservedIpv4(mappedIpv4[1]);
1675
+ const mappedIpv4Hex = parseMappedIpv4Hex(lower);
1676
+ if (mappedIpv4Hex)
1677
+ return isPrivateOrReservedIpv4(mappedIpv4Hex);
1678
+ return true;
1679
+ }
1680
+ if (lower === "::" || lower === "::1")
1681
+ return true;
1682
+ if (lower.startsWith("fc") || lower.startsWith("fd"))
1683
+ return true;
1684
+ if (/^fe[89ab]/.test(lower))
1685
+ return true;
1686
+ if (lower.startsWith("ff"))
1687
+ return true;
1688
+ if (lower === "100::" || lower.startsWith("100:"))
1689
+ return true;
1690
+ if (lower.startsWith("2001:db8:") || lower === "2001:db8::")
1691
+ return true;
1692
+ if (lower.startsWith("2001:2:") || lower === "2001:2::")
1693
+ return true;
1694
+ if (lower.startsWith("2002:"))
1695
+ return true;
1696
+ if (lower.startsWith("64:ff9b:"))
1697
+ return true;
1698
+ return false;
1699
+ }
1700
+ function isPublicIpAddress(address) {
1701
+ const ipVersion = isIP(address);
1702
+ if (ipVersion === 4)
1703
+ return !isPrivateOrReservedIpv4(address);
1704
+ if (ipVersion === 6)
1705
+ return !isPrivateOrReservedIpv6(address);
1706
+ return false;
1707
+ }
1708
+ function hostnameForResolution(url) {
1709
+ return url.hostname.replace(/^\[|\]$/g, "");
1710
+ }
1711
+ async function defaultInviteResolutionLookup(hostname) {
1712
+ return dnsLookup(hostname, { all: true, verbatim: true });
1713
+ }
1714
+ async function defaultInviteResolutionHeadRequest(target, timeoutMs) {
1715
+ return new Promise((resolve, reject) => {
1716
+ const url = target.url;
1717
+ const request = url.protocol === "https:" ? httpsRequest : httpRequest;
1718
+ const options = {
1719
+ protocol: url.protocol,
1720
+ hostname: target.resolvedAddress,
1721
+ port: url.port || undefined,
1722
+ method: "HEAD",
1723
+ path: `${url.pathname}${url.search}`,
1724
+ headers: {
1725
+ Host: target.hostHeader
1726
+ }
1727
+ };
1728
+ if (target.tlsServername) {
1729
+ options.servername = target.tlsServername;
1730
+ }
1731
+ let settled = false;
1732
+ const req = request(options, (response) => {
1733
+ settled = true;
1734
+ response.resume();
1735
+ resolve({ httpStatus: response.statusCode ?? null });
1736
+ });
1737
+ req.setTimeout(timeoutMs, () => {
1738
+ if (settled)
1739
+ return;
1740
+ const error = new Error("Invite resolution probe timed out");
1741
+ error.name = "AbortError";
1742
+ req.destroy(error);
1743
+ });
1744
+ req.on("error", (error) => {
1745
+ if (settled)
1746
+ return;
1747
+ settled = true;
1748
+ reject(error);
1749
+ });
1750
+ req.end();
1751
+ });
1752
+ }
1753
+ const defaultInviteResolutionNetwork = {
1754
+ lookup: defaultInviteResolutionLookup,
1755
+ requestHead: defaultInviteResolutionHeadRequest
1756
+ };
1757
+ let inviteResolutionNetwork = defaultInviteResolutionNetwork;
1758
+ export function setInviteResolutionNetworkForTest(network) {
1759
+ inviteResolutionNetwork = network
1760
+ ? { ...defaultInviteResolutionNetwork, ...network }
1761
+ : defaultInviteResolutionNetwork;
1762
+ }
1763
+ async function lookupInviteResolutionHostname(hostname, network = inviteResolutionNetwork) {
1764
+ let timeout = null;
1765
+ try {
1766
+ return await Promise.race([
1767
+ network.lookup(hostname),
1768
+ new Promise((_, reject) => {
1769
+ timeout = setTimeout(() => reject(badRequest(`url hostname DNS lookup timed out after ${INVITE_RESOLUTION_DNS_TIMEOUT_MS}ms`)), INVITE_RESOLUTION_DNS_TIMEOUT_MS);
1770
+ })
1771
+ ]);
1772
+ }
1773
+ catch (error) {
1774
+ if (error instanceof Error && "status" in error)
1775
+ throw error;
1776
+ throw badRequest("url hostname could not be resolved");
1777
+ }
1778
+ finally {
1779
+ if (timeout)
1780
+ clearTimeout(timeout);
1781
+ }
1782
+ }
1783
+ async function resolveInviteResolutionTarget(url, network = inviteResolutionNetwork) {
1784
+ const hostname = hostnameForResolution(url);
1785
+ if (parseIpv4Address(hostname)) {
1786
+ if (!isPublicIpAddress(hostname)) {
1787
+ throw badRequest("url resolves to a private, local, multicast, or reserved address");
1788
+ }
1789
+ return {
1790
+ url,
1791
+ resolvedAddress: hostname,
1792
+ resolvedAddresses: [hostname],
1793
+ hostHeader: url.host,
1794
+ tlsServername: undefined,
1795
+ };
1796
+ }
1797
+ const literalIpVersion = isIP(hostname);
1798
+ if (literalIpVersion !== 0) {
1799
+ if (!isPublicIpAddress(hostname)) {
1800
+ throw badRequest("url resolves to a private, local, multicast, or reserved address");
1801
+ }
1802
+ return {
1803
+ url,
1804
+ resolvedAddress: hostname,
1805
+ resolvedAddresses: [hostname],
1806
+ hostHeader: url.host,
1807
+ tlsServername: undefined,
1808
+ };
1809
+ }
1810
+ const results = await lookupInviteResolutionHostname(hostname, network);
1811
+ if (results.length === 0) {
1812
+ throw badRequest("url hostname did not resolve to any addresses");
1813
+ }
1814
+ const resolvedAddresses = results.map((result) => result.address);
1815
+ const unsafeAddress = resolvedAddresses.find((address) => !isPublicIpAddress(address));
1816
+ if (unsafeAddress) {
1817
+ throw badRequest("url resolves to a private, local, multicast, or reserved address");
1818
+ }
1819
+ return {
1820
+ url,
1821
+ resolvedAddress: resolvedAddresses[0],
1822
+ resolvedAddresses,
1823
+ hostHeader: url.host,
1824
+ tlsServername: url.protocol === "https:" && isIP(hostname) === 0
1825
+ ? hostname
1826
+ : undefined
1827
+ };
1828
+ }
1829
+ async function probeInviteResolutionTarget(target, timeoutMs, network = inviteResolutionNetwork) {
1830
+ const startedAt = Date.now();
1831
+ try {
1832
+ const response = await network.requestHead(target, timeoutMs);
1833
+ const durationMs = Date.now() - startedAt;
1834
+ if (response.httpStatus !== null &&
1835
+ ((response.httpStatus >= 200 && response.httpStatus < 300) ||
1836
+ response.httpStatus === 401 ||
1837
+ response.httpStatus === 403 ||
1838
+ response.httpStatus === 404 ||
1839
+ response.httpStatus === 405 ||
1840
+ response.httpStatus === 422 ||
1841
+ response.httpStatus === 500 ||
1842
+ response.httpStatus === 501)) {
1843
+ return {
1844
+ status: "reachable",
1845
+ method: "HEAD",
1846
+ durationMs,
1847
+ httpStatus: response.httpStatus,
1848
+ message: `Webhook endpoint responded to HEAD with HTTP ${response.httpStatus}.`
1849
+ };
1850
+ }
1851
+ return {
1852
+ status: "unreachable",
1853
+ method: "HEAD",
1854
+ durationMs,
1855
+ httpStatus: response.httpStatus,
1856
+ message: response.httpStatus === null
1857
+ ? "Webhook endpoint probe did not return an HTTP status."
1858
+ : `Webhook endpoint probe returned HTTP ${response.httpStatus}.`
1859
+ };
1860
+ }
1861
+ catch (error) {
1862
+ const durationMs = Date.now() - startedAt;
1863
+ if (isAbortError(error)) {
1864
+ return {
1865
+ status: "timeout",
1866
+ method: "HEAD",
1867
+ durationMs,
1868
+ httpStatus: null,
1869
+ message: `Webhook endpoint probe timed out after ${timeoutMs}ms.`
1870
+ };
1871
+ }
1872
+ return {
1873
+ status: "unreachable",
1874
+ method: "HEAD",
1875
+ durationMs,
1876
+ httpStatus: null,
1877
+ message: error instanceof Error
1878
+ ? error.message
1879
+ : "Webhook endpoint probe failed."
1880
+ };
1881
+ }
1882
+ }
1883
+ export function accessRoutes(db, opts) {
1884
+ const router = Router();
1885
+ const access = accessService(db);
1886
+ const boardAuth = boardAuthService(db);
1887
+ const agents = agentService(db);
1888
+ const routeInviteResolutionNetwork = opts.inviteResolutionNetwork
1889
+ ? { ...defaultInviteResolutionNetwork, ...opts.inviteResolutionNetwork }
1890
+ : inviteResolutionNetwork;
1891
+ async function assertInstanceAdmin(req) {
1892
+ if (req.actor.type !== "board")
1893
+ throw unauthorized();
1894
+ if (isLocalImplicit(req))
1895
+ return;
1896
+ const allowed = await access.isInstanceAdmin(req.actor.userId);
1897
+ if (!allowed)
1898
+ throw forbidden("Instance admin required");
1899
+ }
1900
+ router.get("/board-claim/:token", async (req, res) => {
1901
+ const token = req.params.token.trim();
1902
+ const code = typeof req.query.code === "string" ? req.query.code.trim() : undefined;
1903
+ if (!token)
1904
+ throw notFound("Board claim challenge not found");
1905
+ const challenge = inspectBoardClaimChallenge(token, code);
1906
+ if (challenge.status === "invalid")
1907
+ throw notFound("Board claim challenge not found");
1908
+ res.json(challenge);
1909
+ });
1910
+ router.post("/board-claim/:token/claim", async (req, res) => {
1911
+ const token = req.params.token.trim();
1912
+ const code = typeof req.body?.code === "string" ? req.body.code.trim() : undefined;
1913
+ if (!token)
1914
+ throw notFound("Board claim challenge not found");
1915
+ if (!code)
1916
+ throw badRequest("Claim code is required");
1917
+ if (req.actor.type !== "board" ||
1918
+ req.actor.source !== "session" ||
1919
+ !req.actor.userId) {
1920
+ throw unauthorized("Sign in before claiming board ownership");
1921
+ }
1922
+ const claimed = await claimBoardOwnership(db, {
1923
+ token,
1924
+ code,
1925
+ userId: req.actor.userId
1926
+ });
1927
+ if (claimed.status === "invalid")
1928
+ throw notFound("Board claim challenge not found");
1929
+ if (claimed.status === "expired")
1930
+ throw conflict("Board claim challenge expired. Restart server to generate a new one.");
1931
+ if (claimed.status === "claimed") {
1932
+ res.json({
1933
+ claimed: true,
1934
+ userId: claimed.claimedByUserId ?? req.actor.userId
1935
+ });
1936
+ return;
1937
+ }
1938
+ throw conflict("Board claim challenge is no longer available");
1939
+ });
1940
+ router.post("/cli-auth/challenges", validate(createCliAuthChallengeSchema), async (req, res) => {
1941
+ const created = await boardAuth.createCliAuthChallenge(req.body);
1942
+ const approvalPath = buildCliAuthApprovalPath(created.challenge.id, created.challengeSecret);
1943
+ const baseUrl = requestBaseUrl(req);
1944
+ res.status(201).json({
1945
+ id: created.challenge.id,
1946
+ token: created.challengeSecret,
1947
+ boardApiToken: created.pendingBoardToken,
1948
+ approvalPath,
1949
+ approvalUrl: baseUrl ? `${baseUrl}${approvalPath}` : null,
1950
+ pollPath: `/cli-auth/challenges/${created.challenge.id}`,
1951
+ expiresAt: created.challenge.expiresAt.toISOString(),
1952
+ suggestedPollIntervalMs: 1000,
1953
+ });
1954
+ });
1955
+ router.get("/cli-auth/challenges/:id", async (req, res) => {
1956
+ const id = req.params.id.trim();
1957
+ const token = typeof req.query.token === "string" ? req.query.token.trim() : "";
1958
+ if (!id || !token)
1959
+ throw notFound("CLI auth challenge not found");
1960
+ const challenge = await boardAuth.describeCliAuthChallenge(id, token);
1961
+ if (!challenge)
1962
+ throw notFound("CLI auth challenge not found");
1963
+ const isSignedInBoardUser = req.actor.type === "board" &&
1964
+ (req.actor.source === "session" || isLocalImplicit(req)) &&
1965
+ Boolean(req.actor.userId);
1966
+ const canApprove = isSignedInBoardUser &&
1967
+ (challenge.requestedAccess !== "instance_admin_required" ||
1968
+ isLocalImplicit(req) ||
1969
+ Boolean(req.actor.isInstanceAdmin));
1970
+ res.json({
1971
+ ...challenge,
1972
+ requiresSignIn: !isSignedInBoardUser,
1973
+ canApprove,
1974
+ currentUserId: req.actor.type === "board" ? req.actor.userId ?? null : null,
1975
+ });
1976
+ });
1977
+ router.post("/cli-auth/challenges/:id/approve", validate(resolveCliAuthChallengeSchema), async (req, res) => {
1978
+ const id = req.params.id.trim();
1979
+ if (req.actor.type !== "board" ||
1980
+ (!req.actor.userId && !isLocalImplicit(req))) {
1981
+ throw unauthorized("Sign in before approving CLI access");
1982
+ }
1983
+ const userId = req.actor.userId ?? "local-board";
1984
+ const approved = await boardAuth.approveCliAuthChallenge(id, req.body.token, userId);
1985
+ if (approved.status === "approved") {
1986
+ const companyIds = await boardAuth.resolveBoardActivityCompanyIds({
1987
+ userId,
1988
+ requestedCompanyId: approved.challenge.requestedCompanyId,
1989
+ boardApiKeyId: approved.challenge.boardApiKeyId,
1990
+ });
1991
+ for (const companyId of companyIds) {
1992
+ await logActivity(db, {
1993
+ companyId,
1994
+ actorType: "user",
1995
+ actorId: userId,
1996
+ action: "board_api_key.created",
1997
+ entityType: "user",
1998
+ entityId: userId,
1999
+ details: {
2000
+ boardApiKeyId: approved.challenge.boardApiKeyId,
2001
+ requestedAccess: approved.challenge.requestedAccess,
2002
+ requestedCompanyId: approved.challenge.requestedCompanyId,
2003
+ challengeId: approved.challenge.id,
2004
+ },
2005
+ });
2006
+ }
2007
+ }
2008
+ res.json({
2009
+ approved: approved.status === "approved",
2010
+ status: approved.status,
2011
+ userId,
2012
+ keyId: approved.challenge.boardApiKeyId ?? null,
2013
+ expiresAt: approved.challenge.expiresAt.toISOString(),
2014
+ });
2015
+ });
2016
+ router.post("/cli-auth/challenges/:id/cancel", validate(resolveCliAuthChallengeSchema), async (req, res) => {
2017
+ const id = req.params.id.trim();
2018
+ const cancelled = await boardAuth.cancelCliAuthChallenge(id, req.body.token);
2019
+ res.json({
2020
+ status: cancelled.status,
2021
+ cancelled: cancelled.status === "cancelled",
2022
+ });
2023
+ });
2024
+ router.get("/cli-auth/me", async (req, res) => {
2025
+ if (req.actor.type !== "board" || !req.actor.userId) {
2026
+ throw unauthorized("Board authentication required");
2027
+ }
2028
+ const accessSnapshot = await boardAuth.resolveBoardAccess(req.actor.userId);
2029
+ res.json({
2030
+ user: accessSnapshot.user,
2031
+ userId: req.actor.userId,
2032
+ isInstanceAdmin: accessSnapshot.isInstanceAdmin,
2033
+ companyIds: accessSnapshot.companyIds,
2034
+ memberships: accessSnapshot.memberships,
2035
+ source: req.actor.source ?? "none",
2036
+ keyId: req.actor.source === "board_key" ? req.actor.keyId ?? null : null,
2037
+ });
2038
+ });
2039
+ router.post("/cli-auth/revoke-current", async (req, res) => {
2040
+ if (req.actor.type !== "board" || req.actor.source !== "board_key") {
2041
+ throw badRequest("Current board API key context is required");
2042
+ }
2043
+ const key = await boardAuth.assertCurrentBoardKey(req.actor.keyId, req.actor.userId);
2044
+ await boardAuth.revokeBoardApiKey(key.id);
2045
+ const companyIds = await boardAuth.resolveBoardActivityCompanyIds({
2046
+ userId: key.userId,
2047
+ boardApiKeyId: key.id,
2048
+ });
2049
+ for (const companyId of companyIds) {
2050
+ await logActivity(db, {
2051
+ companyId,
2052
+ actorType: "user",
2053
+ actorId: key.userId,
2054
+ action: "board_api_key.revoked",
2055
+ entityType: "user",
2056
+ entityId: key.userId,
2057
+ details: {
2058
+ boardApiKeyId: key.id,
2059
+ revokedVia: "cli_auth_logout",
2060
+ },
2061
+ });
2062
+ }
2063
+ res.json({ revoked: true, keyId: key.id });
2064
+ });
2065
+ async function assertCompanyPermission(req, companyId, permissionKey) {
2066
+ assertCompanyAccess(req, companyId);
2067
+ if (req.actor.type === "agent") {
2068
+ if (!req.actor.agentId)
2069
+ throw forbidden();
2070
+ const allowed = await access.hasPermission(companyId, "agent", req.actor.agentId, permissionKey);
2071
+ if (!allowed)
2072
+ throw forbidden("Permission denied");
2073
+ return;
2074
+ }
2075
+ if (req.actor.type !== "board")
2076
+ throw unauthorized();
2077
+ if (isLocalImplicit(req))
2078
+ return;
2079
+ const allowed = await access.canUser(companyId, req.actor.userId, permissionKey);
2080
+ if (!allowed)
2081
+ throw forbidden("Permission denied");
2082
+ }
2083
+ async function assertCanGenerateOpenClawInvitePrompt(req, companyId) {
2084
+ assertCompanyAccess(req, companyId);
2085
+ if (req.actor.type === "agent") {
2086
+ if (!req.actor.agentId)
2087
+ throw forbidden("Agent authentication required");
2088
+ const actorAgent = await agents.getById(req.actor.agentId);
2089
+ if (!actorAgent || actorAgent.companyId !== companyId) {
2090
+ throw forbidden("Agent key cannot access another company");
2091
+ }
2092
+ if (actorAgent.role !== "ceo") {
2093
+ throw forbidden("Only CEO agents can generate OpenClaw invite prompts");
2094
+ }
2095
+ return;
2096
+ }
2097
+ if (req.actor.type !== "board")
2098
+ throw unauthorized();
2099
+ if (isLocalImplicit(req))
2100
+ return;
2101
+ const allowed = await access.canUser(companyId, req.actor.userId, "users:invite");
2102
+ if (!allowed)
2103
+ throw forbidden("Permission denied");
2104
+ }
2105
+ async function createCompanyInviteForCompany(input) {
2106
+ const normalizedAgentMessage = typeof input.agentMessage === "string"
2107
+ ? input.agentMessage.trim() || null
2108
+ : null;
2109
+ const effectiveHumanRole = input.allowedJoinTypes === "agent"
2110
+ ? null
2111
+ : input.humanRole ?? "operator";
2112
+ const insertValues = {
2113
+ companyId: input.companyId,
2114
+ inviteType: "company_join",
2115
+ allowedJoinTypes: input.allowedJoinTypes,
2116
+ defaultsPayload: mergeInviteDefaults(input.defaultsPayload ?? null, normalizedAgentMessage, effectiveHumanRole),
2117
+ expiresAt: companyInviteExpiresAt(),
2118
+ invitedByUserId: input.req.actor.userId ?? null
2119
+ };
2120
+ let token = null;
2121
+ let created = null;
2122
+ for (let attempt = 0; attempt < INVITE_TOKEN_MAX_RETRIES; attempt += 1) {
2123
+ const candidateToken = createInviteToken();
2124
+ try {
2125
+ const row = await db
2126
+ .insert(invites)
2127
+ .values({
2128
+ ...insertValues,
2129
+ tokenHash: hashToken(candidateToken)
2130
+ })
2131
+ .returning()
2132
+ .then((rows) => rows[0]);
2133
+ token = candidateToken;
2134
+ created = row;
2135
+ break;
2136
+ }
2137
+ catch (error) {
2138
+ if (!isInviteTokenHashCollisionError(error)) {
2139
+ throw error;
2140
+ }
2141
+ }
2142
+ }
2143
+ if (!token || !created) {
2144
+ throw conflict("Failed to generate a unique invite token. Please retry.");
2145
+ }
2146
+ return { token, created, normalizedAgentMessage };
2147
+ }
2148
+ async function getInviteCompanyBranding(companyId, inviteToken = null) {
2149
+ if (!companyId) {
2150
+ return { name: null, brandColor: null, logoAssetId: null, logoUrl: null };
2151
+ }
2152
+ const company = await db
2153
+ .select({
2154
+ name: companies.name,
2155
+ brandColor: companies.brandColor,
2156
+ logoAssetId: companyLogos.assetId,
2157
+ })
2158
+ .from(companies)
2159
+ .leftJoin(companyLogos, eq(companyLogos.companyId, companies.id))
2160
+ .where(eq(companies.id, companyId))
2161
+ .then((rows) => rows[0] ?? null);
2162
+ let logoUrl = null;
2163
+ if (inviteToken && company?.logoAssetId) {
2164
+ const logoAsset = await getInviteLogoAsset(companyId);
2165
+ if (logoAsset?.companyId) {
2166
+ try {
2167
+ const storage = getStorageService();
2168
+ const logoObject = await storage.headObject(logoAsset.companyId, logoAsset.objectKey);
2169
+ if (logoObject.exists) {
2170
+ logoUrl = `/api/invites/${inviteToken}/logo`;
2171
+ }
2172
+ }
2173
+ catch (err) {
2174
+ logger.warn({
2175
+ err,
2176
+ companyId,
2177
+ logoAssetId: company.logoAssetId,
2178
+ }, "invite logo storage check failed");
2179
+ }
2180
+ }
2181
+ }
2182
+ return {
2183
+ name: company?.name ?? null,
2184
+ brandColor: company?.brandColor ?? null,
2185
+ logoAssetId: company?.logoAssetId ?? null,
2186
+ logoUrl,
2187
+ };
2188
+ }
2189
+ async function getInviteLogoAsset(companyId) {
2190
+ if (!companyId)
2191
+ return null;
2192
+ const logoAsset = await db
2193
+ .select({
2194
+ companyId: companies.id,
2195
+ objectKey: assets.objectKey,
2196
+ contentType: assets.contentType,
2197
+ byteSize: assets.byteSize,
2198
+ originalFilename: assets.originalFilename,
2199
+ })
2200
+ .from(companies)
2201
+ .leftJoin(companyLogos, eq(companyLogos.companyId, companies.id))
2202
+ .leftJoin(assets, eq(assets.id, companyLogos.assetId))
2203
+ .where(eq(companies.id, companyId))
2204
+ .then((rows) => rows[0] ?? null);
2205
+ if (!logoAsset?.objectKey)
2206
+ return null;
2207
+ return {
2208
+ companyId: logoAsset.companyId,
2209
+ objectKey: logoAsset.objectKey,
2210
+ contentType: logoAsset.contentType,
2211
+ byteSize: logoAsset.byteSize,
2212
+ originalFilename: logoAsset.originalFilename,
2213
+ };
2214
+ }
2215
+ router.get("/skills/available", (req, res) => {
2216
+ assertAuthenticated(req);
2217
+ res.json({ skills: listAvailableSkills() });
2218
+ });
2219
+ router.get("/skills/index", (req, res) => {
2220
+ assertAuthenticated(req);
2221
+ res.json({
2222
+ skills: [
2223
+ { name: "evermore", path: "/api/skills/evermore" },
2224
+ {
2225
+ name: "para-memory-files",
2226
+ path: "/api/skills/para-memory-files"
2227
+ },
2228
+ {
2229
+ name: "evermore-create-agent",
2230
+ path: "/api/skills/evermore-create-agent"
2231
+ },
2232
+ {
2233
+ name: "evermore-converting-plans-to-tasks",
2234
+ path: "/api/skills/evermore-converting-plans-to-tasks"
2235
+ }
2236
+ ]
2237
+ });
2238
+ });
2239
+ router.get("/skills/:skillName", (req, res) => {
2240
+ assertAuthenticated(req);
2241
+ const skillName = req.params.skillName.trim().toLowerCase();
2242
+ const markdown = readSkillMarkdown(skillName);
2243
+ if (!markdown)
2244
+ throw notFound("Skill not found");
2245
+ res.type("text/markdown").send(markdown);
2246
+ });
2247
+ router.post("/companies/:companyId/invites", validate(createCompanyInviteSchema), async (req, res) => {
2248
+ const companyId = req.params.companyId;
2249
+ await assertCompanyPermission(req, companyId, "users:invite");
2250
+ const { token, created, normalizedAgentMessage } = await createCompanyInviteForCompany({
2251
+ req,
2252
+ companyId,
2253
+ allowedJoinTypes: req.body.allowedJoinTypes,
2254
+ humanRole: req.body.humanRole ?? null,
2255
+ defaultsPayload: req.body.defaultsPayload ?? null,
2256
+ agentMessage: req.body.agentMessage ?? null
2257
+ });
2258
+ await logActivity(db, {
2259
+ companyId,
2260
+ actorType: req.actor.type === "agent" ? "agent" : "user",
2261
+ actorId: req.actor.type === "agent"
2262
+ ? req.actor.agentId ?? "unknown-agent"
2263
+ : req.actor.userId ?? "board",
2264
+ action: "invite.created",
2265
+ entityType: "invite",
2266
+ entityId: created.id,
2267
+ details: {
2268
+ inviteType: created.inviteType,
2269
+ allowedJoinTypes: created.allowedJoinTypes,
2270
+ expiresAt: created.expiresAt.toISOString(),
2271
+ humanRole: extractInviteHumanRole(created),
2272
+ hasAgentMessage: Boolean(normalizedAgentMessage)
2273
+ }
2274
+ });
2275
+ const companyBranding = await getInviteCompanyBranding(created.companyId, token);
2276
+ const inviteSummary = toInviteSummaryResponse(req, token, created, companyBranding);
2277
+ res.status(201).json({
2278
+ ...created,
2279
+ token,
2280
+ invitePath: inviteSummary.invitePath,
2281
+ inviteUrl: inviteSummary.inviteUrl,
2282
+ companyName: companyBranding.name,
2283
+ onboardingTextPath: inviteSummary.onboardingTextPath,
2284
+ onboardingTextUrl: inviteSummary.onboardingTextUrl,
2285
+ inviteMessage: inviteSummary.inviteMessage
2286
+ });
2287
+ });
2288
+ router.post("/companies/:companyId/openclaw/invite-prompt", validate(createOpenClawInvitePromptSchema), async (req, res) => {
2289
+ const companyId = req.params.companyId;
2290
+ await assertCanGenerateOpenClawInvitePrompt(req, companyId);
2291
+ const { token, created, normalizedAgentMessage } = await createCompanyInviteForCompany({
2292
+ req,
2293
+ companyId,
2294
+ allowedJoinTypes: "agent",
2295
+ humanRole: null,
2296
+ defaultsPayload: null,
2297
+ agentMessage: req.body.agentMessage ?? null
2298
+ });
2299
+ await logActivity(db, {
2300
+ companyId,
2301
+ actorType: req.actor.type === "agent" ? "agent" : "user",
2302
+ actorId: req.actor.type === "agent"
2303
+ ? req.actor.agentId ?? "unknown-agent"
2304
+ : req.actor.userId ?? "board",
2305
+ action: "invite.openclaw_prompt_created",
2306
+ entityType: "invite",
2307
+ entityId: created.id,
2308
+ details: {
2309
+ inviteType: created.inviteType,
2310
+ allowedJoinTypes: created.allowedJoinTypes,
2311
+ expiresAt: created.expiresAt.toISOString(),
2312
+ hasAgentMessage: Boolean(normalizedAgentMessage)
2313
+ }
2314
+ });
2315
+ const companyBranding = await getInviteCompanyBranding(created.companyId, token);
2316
+ const inviteSummary = toInviteSummaryResponse(req, token, created, companyBranding);
2317
+ res.status(201).json({
2318
+ ...created,
2319
+ token,
2320
+ invitePath: inviteSummary.invitePath,
2321
+ inviteUrl: inviteSummary.inviteUrl,
2322
+ companyName: companyBranding.name,
2323
+ onboardingTextPath: inviteSummary.onboardingTextPath,
2324
+ onboardingTextUrl: inviteSummary.onboardingTextUrl,
2325
+ inviteMessage: inviteSummary.inviteMessage
2326
+ });
2327
+ });
2328
+ router.get("/invites/:token", async (req, res) => {
2329
+ const token = req.params.token.trim();
2330
+ if (!token)
2331
+ throw notFound("Invite not found");
2332
+ const invite = await db
2333
+ .select()
2334
+ .from(invites)
2335
+ .where(eq(invites.tokenHash, hashToken(token)))
2336
+ .then((rows) => rows[0] ?? null);
2337
+ const inviteJoinRequest = await resolveAcceptedInviteJoinRequest(db, req, invite);
2338
+ if (!invite ||
2339
+ invite.revokedAt ||
2340
+ inviteExpired(invite) ||
2341
+ (invite.acceptedAt && !inviteJoinRequest)) {
2342
+ throw notFound("Invite not found");
2343
+ }
2344
+ const companyBranding = await getInviteCompanyBranding(invite.companyId, token);
2345
+ const inviterName = invite.invitedByUserId
2346
+ ? await loadUsersById(db, [invite.invitedByUserId]).then((m) => m.get(invite.invitedByUserId)?.name ?? null)
2347
+ : null;
2348
+ res.json({
2349
+ ...toInviteSummaryResponse(req, token, invite, companyBranding),
2350
+ invitedByUserName: inviterName,
2351
+ joinRequestStatus: inviteJoinRequest?.status ?? null,
2352
+ joinRequestType: inviteJoinRequest?.requestType ?? null,
2353
+ });
2354
+ });
2355
+ router.get("/invites/:token/logo", async (req, res, next) => {
2356
+ const token = req.params.token.trim();
2357
+ if (!token)
2358
+ throw notFound("Invite not found");
2359
+ const invite = await db
2360
+ .select()
2361
+ .from(invites)
2362
+ .where(eq(invites.tokenHash, hashToken(token)))
2363
+ .then((rows) => rows[0] ?? null);
2364
+ const inviteJoinRequest = await resolveAcceptedInviteJoinRequest(db, req, invite);
2365
+ if (!invite ||
2366
+ invite.revokedAt ||
2367
+ inviteExpired(invite) ||
2368
+ (invite.acceptedAt && !inviteJoinRequest)) {
2369
+ throw notFound("Invite not found");
2370
+ }
2371
+ const logoAsset = await getInviteLogoAsset(invite.companyId);
2372
+ if (!logoAsset || !logoAsset.companyId) {
2373
+ throw notFound("Invite logo not found");
2374
+ }
2375
+ const companyId = logoAsset.companyId;
2376
+ const storage = getStorageService();
2377
+ const logoHead = await storage.headObject(companyId, logoAsset.objectKey);
2378
+ if (!logoHead.exists) {
2379
+ throw notFound("Invite logo not found");
2380
+ }
2381
+ const object = await storage.getObject(companyId, logoAsset.objectKey);
2382
+ const responseContentType = logoAsset.contentType ||
2383
+ logoHead.contentType ||
2384
+ object.contentType ||
2385
+ "application/octet-stream";
2386
+ res.setHeader("Content-Type", responseContentType);
2387
+ res.setHeader("Content-Length", String(logoAsset.byteSize || logoHead.contentLength || object.contentLength || 0));
2388
+ res.setHeader("Cache-Control", "private, max-age=60");
2389
+ res.setHeader("X-Content-Type-Options", "nosniff");
2390
+ if (responseContentType === "image/svg+xml") {
2391
+ res.setHeader("Content-Security-Policy", "sandbox; default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'");
2392
+ }
2393
+ const filename = logoAsset.originalFilename ?? "company-logo";
2394
+ res.setHeader("Content-Disposition", `inline; filename=\"${filename.replaceAll("\"", "")}\"`);
2395
+ object.stream.on("error", (err) => {
2396
+ next(err);
2397
+ });
2398
+ object.stream.pipe(res);
2399
+ });
2400
+ router.get("/invites/:token/onboarding", async (req, res) => {
2401
+ const token = req.params.token.trim();
2402
+ if (!token)
2403
+ throw notFound("Invite not found");
2404
+ const invite = await db
2405
+ .select()
2406
+ .from(invites)
2407
+ .where(eq(invites.tokenHash, hashToken(token)))
2408
+ .then((rows) => rows[0] ?? null);
2409
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
2410
+ throw notFound("Invite not found");
2411
+ }
2412
+ const companyBranding = await getInviteCompanyBranding(invite.companyId);
2413
+ res.json(buildInviteOnboardingManifest(req, token, invite, {
2414
+ ...opts,
2415
+ companyName: companyBranding.name
2416
+ }));
2417
+ });
2418
+ router.get("/invites/:token/onboarding.txt", async (req, res) => {
2419
+ const token = req.params.token.trim();
2420
+ if (!token)
2421
+ throw notFound("Invite not found");
2422
+ const invite = await db
2423
+ .select()
2424
+ .from(invites)
2425
+ .where(eq(invites.tokenHash, hashToken(token)))
2426
+ .then((rows) => rows[0] ?? null);
2427
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
2428
+ throw notFound("Invite not found");
2429
+ }
2430
+ const companyBranding = await getInviteCompanyBranding(invite.companyId);
2431
+ res
2432
+ .type("text/plain; charset=utf-8")
2433
+ .send(buildInviteOnboardingTextDocument(req, token, invite, {
2434
+ ...opts,
2435
+ companyName: companyBranding.name
2436
+ }));
2437
+ });
2438
+ router.get("/invites/:token/skills/index", async (req, res) => {
2439
+ const token = req.params.token.trim();
2440
+ if (!token)
2441
+ throw notFound("Invite not found");
2442
+ const invite = await db
2443
+ .select()
2444
+ .from(invites)
2445
+ .where(eq(invites.tokenHash, hashToken(token)))
2446
+ .then((rows) => rows[0] ?? null);
2447
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
2448
+ throw notFound("Invite not found");
2449
+ }
2450
+ res.json({
2451
+ skills: [
2452
+ {
2453
+ name: "evermore",
2454
+ path: `/api/invites/${token}/skills/evermore`,
2455
+ },
2456
+ ],
2457
+ });
2458
+ });
2459
+ router.get("/invites/:token/skills/:skillName", async (req, res) => {
2460
+ const token = req.params.token.trim();
2461
+ if (!token)
2462
+ throw notFound("Invite not found");
2463
+ const invite = await db
2464
+ .select()
2465
+ .from(invites)
2466
+ .where(eq(invites.tokenHash, hashToken(token)))
2467
+ .then((rows) => rows[0] ?? null);
2468
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
2469
+ throw notFound("Invite not found");
2470
+ }
2471
+ const skillName = req.params.skillName.trim().toLowerCase();
2472
+ if (skillName !== "evermore")
2473
+ throw notFound("Skill not found");
2474
+ const markdown = readSkillMarkdown(skillName);
2475
+ if (!markdown)
2476
+ throw notFound("Skill not found");
2477
+ res.type("text/markdown").send(markdown);
2478
+ });
2479
+ router.get("/invites/:token/test-resolution", async (req, res) => {
2480
+ const token = req.params.token.trim();
2481
+ if (!token)
2482
+ throw notFound("Invite not found");
2483
+ const invite = await db
2484
+ .select()
2485
+ .from(invites)
2486
+ .where(eq(invites.tokenHash, hashToken(token)))
2487
+ .then((rows) => rows[0] ?? null);
2488
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
2489
+ throw notFound("Invite not found");
2490
+ }
2491
+ const rawUrl = typeof req.query.url === "string" ? req.query.url.trim() : "";
2492
+ if (!rawUrl)
2493
+ throw badRequest("url query parameter is required");
2494
+ let target;
2495
+ try {
2496
+ target = new URL(rawUrl);
2497
+ }
2498
+ catch {
2499
+ throw badRequest("url must be an absolute http(s) URL");
2500
+ }
2501
+ if (target.protocol !== "http:" && target.protocol !== "https:") {
2502
+ throw badRequest("url must use http or https");
2503
+ }
2504
+ const parsedTimeoutMs = typeof req.query.timeoutMs === "string"
2505
+ ? Number(req.query.timeoutMs)
2506
+ : NaN;
2507
+ const timeoutMs = Number.isFinite(parsedTimeoutMs)
2508
+ ? Math.max(1000, Math.min(15000, Math.floor(parsedTimeoutMs)))
2509
+ : 5000;
2510
+ const resolvedTarget = await resolveInviteResolutionTarget(target, routeInviteResolutionNetwork);
2511
+ const probe = await probeInviteResolutionTarget(resolvedTarget, timeoutMs, routeInviteResolutionNetwork);
2512
+ res.json({
2513
+ inviteId: invite.id,
2514
+ testResolutionPath: `/api/invites/${token}/test-resolution`,
2515
+ requestedUrl: target.toString(),
2516
+ timeoutMs,
2517
+ ...probe
2518
+ });
2519
+ });
2520
+ router.post("/invites/:token/accept", validate(acceptInviteSchema), async (req, res) => {
2521
+ const token = req.params.token.trim();
2522
+ if (!token)
2523
+ throw notFound("Invite not found");
2524
+ const invite = await db
2525
+ .select()
2526
+ .from(invites)
2527
+ .where(eq(invites.tokenHash, hashToken(token)))
2528
+ .then((rows) => rows[0] ?? null);
2529
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
2530
+ throw notFound("Invite not found");
2531
+ }
2532
+ const inviteAlreadyAccepted = Boolean(invite.acceptedAt);
2533
+ const existingJoinRequestForInvite = inviteAlreadyAccepted
2534
+ ? await db
2535
+ .select()
2536
+ .from(joinRequests)
2537
+ .where(eq(joinRequests.inviteId, invite.id))
2538
+ .then((rows) => rows[0] ?? null)
2539
+ : null;
2540
+ if (invite.inviteType === "bootstrap_ceo") {
2541
+ if (inviteAlreadyAccepted)
2542
+ throw notFound("Invite not found");
2543
+ if (req.body.requestType !== "human") {
2544
+ throw badRequest("Bootstrap invite requires human request type");
2545
+ }
2546
+ if (req.actor.type !== "board" ||
2547
+ (!req.actor.userId && !isLocalImplicit(req))) {
2548
+ throw unauthorized("Authenticated user required for bootstrap acceptance");
2549
+ }
2550
+ const userId = req.actor.userId ?? "local-board";
2551
+ const existingAdmin = await access.isInstanceAdmin(userId);
2552
+ if (!existingAdmin) {
2553
+ await access.promoteInstanceAdmin(userId);
2554
+ }
2555
+ const updatedInvite = await db
2556
+ .update(invites)
2557
+ .set({ acceptedAt: new Date(), updatedAt: new Date() })
2558
+ .where(eq(invites.id, invite.id))
2559
+ .returning()
2560
+ .then((rows) => rows[0] ?? invite);
2561
+ res.status(202).json({
2562
+ inviteId: updatedInvite.id,
2563
+ inviteType: updatedInvite.inviteType,
2564
+ bootstrapAccepted: true,
2565
+ userId
2566
+ });
2567
+ return;
2568
+ }
2569
+ const requestType = req.body.requestType;
2570
+ const companyId = invite.companyId;
2571
+ if (!companyId)
2572
+ throw conflict("Invite is missing company scope");
2573
+ if (invite.allowedJoinTypes !== "both" &&
2574
+ invite.allowedJoinTypes !== requestType) {
2575
+ throw badRequest(`Invite does not allow ${requestType} joins`);
2576
+ }
2577
+ if (requestType === "human" && req.actor.type !== "board") {
2578
+ throw unauthorized("Human invite acceptance requires authenticated user");
2579
+ }
2580
+ if (requestType === "human" &&
2581
+ !req.actor.userId &&
2582
+ !isLocalImplicit(req)) {
2583
+ throw unauthorized("Authenticated user is required");
2584
+ }
2585
+ if (requestType === "human" &&
2586
+ actorHasActiveUserMembership(req, companyId)) {
2587
+ throw conflict("You already belong to this company");
2588
+ }
2589
+ if (requestType === "agent" && !req.body.agentName) {
2590
+ if (!inviteAlreadyAccepted ||
2591
+ !existingJoinRequestForInvite?.agentName) {
2592
+ throw badRequest("agentName is required for agent join requests");
2593
+ }
2594
+ }
2595
+ const adapterType = req.body.adapterType ?? null;
2596
+ if (inviteAlreadyAccepted &&
2597
+ !canReplayOpenClawGatewayInviteAccept({
2598
+ requestType,
2599
+ adapterType,
2600
+ existingJoinRequest: existingJoinRequestForInvite
2601
+ })) {
2602
+ throw notFound("Invite not found");
2603
+ }
2604
+ const replayJoinRequestId = inviteAlreadyAccepted
2605
+ ? existingJoinRequestForInvite?.id ?? null
2606
+ : null;
2607
+ if (inviteAlreadyAccepted && !replayJoinRequestId) {
2608
+ throw conflict("Join request not found");
2609
+ }
2610
+ const replayMergedDefaults = inviteAlreadyAccepted
2611
+ ? mergeJoinDefaultsPayloadForReplay(existingJoinRequestForInvite?.agentDefaultsPayload ?? null, req.body.agentDefaultsPayload ?? null)
2612
+ : req.body.agentDefaultsPayload ?? null;
2613
+ const gatewayDefaultsPayload = requestType === "agent"
2614
+ ? buildJoinDefaultsPayloadForAccept({
2615
+ adapterType,
2616
+ defaultsPayload: replayMergedDefaults,
2617
+ evermoreApiUrl: req.body.evermoreApiUrl ?? null,
2618
+ inboundOpenClawAuthHeader: req.header("x-openclaw-auth") ?? null,
2619
+ inboundOpenClawTokenHeader: req.header("x-openclaw-token") ?? null
2620
+ })
2621
+ : null;
2622
+ const joinDefaults = requestType === "agent"
2623
+ ? normalizeAgentDefaultsForJoin({
2624
+ adapterType,
2625
+ defaultsPayload: gatewayDefaultsPayload,
2626
+ deploymentMode: opts.deploymentMode,
2627
+ deploymentExposure: opts.deploymentExposure,
2628
+ bindHost: opts.bindHost,
2629
+ allowedHostnames: opts.allowedHostnames
2630
+ })
2631
+ : {
2632
+ normalized: null,
2633
+ diagnostics: [],
2634
+ fatalErrors: []
2635
+ };
2636
+ if (requestType === "agent" && joinDefaults.fatalErrors.length > 0) {
2637
+ throw badRequest(joinDefaults.fatalErrors.join("; "));
2638
+ }
2639
+ if (requestType === "agent" && adapterType === "openclaw_gateway") {
2640
+ logger.info({
2641
+ inviteId: invite.id,
2642
+ joinRequestDiagnostics: joinDefaults.diagnostics.map((diag) => ({
2643
+ code: diag.code,
2644
+ level: diag.level
2645
+ })),
2646
+ normalizedAgentDefaults: summarizeOpenClawGatewayDefaultsForLog(joinDefaults.normalized)
2647
+ }, "invite accept normalized OpenClaw gateway defaults");
2648
+ }
2649
+ const claimSecret = requestType === "agent" && !inviteAlreadyAccepted
2650
+ ? createClaimSecret()
2651
+ : null;
2652
+ const claimSecretHash = claimSecret ? hashToken(claimSecret) : null;
2653
+ const claimSecretExpiresAt = claimSecret
2654
+ ? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
2655
+ : null;
2656
+ const actorEmail = requestType === "human" ? await resolveActorEmail(db, req) : null;
2657
+ const existingHumanJoinRequest = requestType === "human"
2658
+ ? findReusableHumanJoinRequest(await db
2659
+ .select()
2660
+ .from(joinRequests)
2661
+ .where(and(eq(joinRequests.companyId, companyId), eq(joinRequests.requestType, "human")))
2662
+ .orderBy(desc(joinRequests.createdAt)), {
2663
+ requestingUserId: req.actor.userId ?? "local-board",
2664
+ requestEmailSnapshot: actorEmail
2665
+ })
2666
+ : null;
2667
+ const created = !inviteAlreadyAccepted
2668
+ ? existingHumanJoinRequest
2669
+ ? await db.transaction(async (tx) => {
2670
+ await tx
2671
+ .update(invites)
2672
+ .set({ acceptedAt: new Date(), updatedAt: new Date() })
2673
+ .where(and(eq(invites.id, invite.id), isNull(invites.acceptedAt), isNull(invites.revokedAt)));
2674
+ return existingHumanJoinRequest;
2675
+ })
2676
+ : await db.transaction(async (tx) => {
2677
+ await tx
2678
+ .update(invites)
2679
+ .set({ acceptedAt: new Date(), updatedAt: new Date() })
2680
+ .where(and(eq(invites.id, invite.id), isNull(invites.acceptedAt), isNull(invites.revokedAt)));
2681
+ const row = await tx
2682
+ .insert(joinRequests)
2683
+ .values({
2684
+ inviteId: invite.id,
2685
+ companyId,
2686
+ requestType,
2687
+ status: "pending_approval",
2688
+ requestIp: requestIp(req),
2689
+ requestingUserId: requestType === "human"
2690
+ ? req.actor.userId ?? "local-board"
2691
+ : null,
2692
+ requestEmailSnapshot: requestType === "human" ? actorEmail : null,
2693
+ agentName: requestType === "agent" ? req.body.agentName : null,
2694
+ adapterType: requestType === "agent" ? adapterType : null,
2695
+ capabilities: requestType === "agent"
2696
+ ? req.body.capabilities ?? null
2697
+ : null,
2698
+ agentDefaultsPayload: requestType === "agent" ? joinDefaults.normalized : null,
2699
+ claimSecretHash,
2700
+ claimSecretExpiresAt
2701
+ })
2702
+ .returning()
2703
+ .then((rows) => rows[0]);
2704
+ return row;
2705
+ })
2706
+ : await db
2707
+ .update(joinRequests)
2708
+ .set({
2709
+ requestIp: requestIp(req),
2710
+ agentName: requestType === "agent"
2711
+ ? req.body.agentName ??
2712
+ existingJoinRequestForInvite?.agentName ??
2713
+ null
2714
+ : null,
2715
+ capabilities: requestType === "agent"
2716
+ ? req.body.capabilities ??
2717
+ existingJoinRequestForInvite?.capabilities ??
2718
+ null
2719
+ : null,
2720
+ adapterType: requestType === "agent" ? adapterType : null,
2721
+ agentDefaultsPayload: requestType === "agent" ? joinDefaults.normalized : null,
2722
+ updatedAt: new Date()
2723
+ })
2724
+ .where(eq(joinRequests.id, replayJoinRequestId))
2725
+ .returning()
2726
+ .then((rows) => rows[0]);
2727
+ if (!created) {
2728
+ throw conflict("Join request not found");
2729
+ }
2730
+ if (inviteAlreadyAccepted &&
2731
+ requestType === "agent" &&
2732
+ adapterType === "openclaw_gateway" &&
2733
+ created.status === "approved" &&
2734
+ created.createdAgentId) {
2735
+ const existingAgent = await agents.getById(created.createdAgentId);
2736
+ if (!existingAgent) {
2737
+ throw conflict("Approved join request agent not found");
2738
+ }
2739
+ const existingAdapterConfig = isPlainObject(existingAgent.adapterConfig)
2740
+ ? existingAgent.adapterConfig
2741
+ : {};
2742
+ const nextAdapterConfig = {
2743
+ ...existingAdapterConfig,
2744
+ ...(joinDefaults.normalized ?? {})
2745
+ };
2746
+ const updatedAgent = await agents.update(created.createdAgentId, {
2747
+ adapterType,
2748
+ adapterConfig: nextAdapterConfig
2749
+ });
2750
+ if (!updatedAgent) {
2751
+ throw conflict("Approved join request agent not found");
2752
+ }
2753
+ await logActivity(db, {
2754
+ companyId,
2755
+ actorType: req.actor.type === "agent" ? "agent" : "user",
2756
+ actorId: req.actor.type === "agent"
2757
+ ? req.actor.agentId ?? "invite-agent"
2758
+ : req.actor.userId ?? "board",
2759
+ action: "agent.updated_from_join_replay",
2760
+ entityType: "agent",
2761
+ entityId: updatedAgent.id,
2762
+ details: { inviteId: invite.id, joinRequestId: created.id }
2763
+ });
2764
+ }
2765
+ if (requestType === "agent" && adapterType === "openclaw_gateway") {
2766
+ const expectedDefaults = summarizeOpenClawGatewayDefaultsForLog(joinDefaults.normalized);
2767
+ const persistedDefaults = summarizeOpenClawGatewayDefaultsForLog(created.agentDefaultsPayload);
2768
+ const missingPersistedFields = [];
2769
+ if (expectedDefaults.url && !persistedDefaults.url)
2770
+ missingPersistedFields.push("url");
2771
+ if (expectedDefaults.evermoreApiUrl &&
2772
+ !persistedDefaults.evermoreApiUrl) {
2773
+ missingPersistedFields.push("evermoreApiUrl");
2774
+ }
2775
+ if (expectedDefaults.gatewayToken && !persistedDefaults.gatewayToken) {
2776
+ missingPersistedFields.push("headers.x-openclaw-token");
2777
+ }
2778
+ if (expectedDefaults.devicePrivateKeyPem &&
2779
+ !persistedDefaults.devicePrivateKeyPem) {
2780
+ missingPersistedFields.push("devicePrivateKeyPem");
2781
+ }
2782
+ if (expectedDefaults.headerKeys.length > 0 &&
2783
+ persistedDefaults.headerKeys.length === 0) {
2784
+ missingPersistedFields.push("headers");
2785
+ }
2786
+ logger.info({
2787
+ inviteId: invite.id,
2788
+ joinRequestId: created.id,
2789
+ joinRequestStatus: created.status,
2790
+ expectedDefaults,
2791
+ persistedDefaults,
2792
+ diagnostics: joinDefaults.diagnostics.map((diag) => ({
2793
+ code: diag.code,
2794
+ level: diag.level,
2795
+ message: diag.message,
2796
+ hint: diag.hint ?? null
2797
+ }))
2798
+ }, "invite accept persisted OpenClaw gateway join request");
2799
+ if (missingPersistedFields.length > 0) {
2800
+ logger.warn({
2801
+ inviteId: invite.id,
2802
+ joinRequestId: created.id,
2803
+ missingPersistedFields
2804
+ }, "invite accept detected missing persisted OpenClaw gateway defaults");
2805
+ }
2806
+ }
2807
+ await logActivity(db, {
2808
+ companyId,
2809
+ actorType: req.actor.type === "agent" ? "agent" : "user",
2810
+ actorId: req.actor.type === "agent"
2811
+ ? req.actor.agentId ?? "invite-agent"
2812
+ : req.actor.userId ??
2813
+ (requestType === "agent" ? "invite-anon" : "board"),
2814
+ action: inviteAlreadyAccepted
2815
+ ? "join.request_replayed"
2816
+ : "join.requested",
2817
+ entityType: "join_request",
2818
+ entityId: created.id,
2819
+ details: {
2820
+ requestType,
2821
+ requestIp: requestIp(req),
2822
+ inviteReplay: inviteAlreadyAccepted,
2823
+ reusedExistingJoinRequest: Boolean(existingHumanJoinRequest) && !inviteAlreadyAccepted
2824
+ }
2825
+ });
2826
+ const response = toJoinRequestResponse(created);
2827
+ if (claimSecret) {
2828
+ const companyBranding = await getInviteCompanyBranding(invite.companyId);
2829
+ const onboardingManifest = buildInviteOnboardingManifest(req, token, invite, {
2830
+ ...opts,
2831
+ companyName: companyBranding.name
2832
+ });
2833
+ res.status(202).json({
2834
+ ...response,
2835
+ claimSecret,
2836
+ claimApiKeyPath: `/api/join-requests/${created.id}/claim-api-key`,
2837
+ onboarding: onboardingManifest.onboarding,
2838
+ diagnostics: joinDefaults.diagnostics
2839
+ });
2840
+ return;
2841
+ }
2842
+ res.status(202).json({
2843
+ ...response,
2844
+ ...(joinDefaults.diagnostics.length > 0
2845
+ ? { diagnostics: joinDefaults.diagnostics }
2846
+ : {})
2847
+ });
2848
+ });
2849
+ router.post("/invites/:inviteId/revoke", async (req, res) => {
2850
+ const id = req.params.inviteId;
2851
+ const invite = await db
2852
+ .select()
2853
+ .from(invites)
2854
+ .where(eq(invites.id, id))
2855
+ .then((rows) => rows[0] ?? null);
2856
+ if (!invite)
2857
+ throw notFound("Invite not found");
2858
+ if (invite.inviteType === "bootstrap_ceo") {
2859
+ await assertInstanceAdmin(req);
2860
+ }
2861
+ else {
2862
+ if (!invite.companyId)
2863
+ throw conflict("Invite is missing company scope");
2864
+ await assertCompanyPermission(req, invite.companyId, "users:invite");
2865
+ }
2866
+ if (invite.acceptedAt)
2867
+ throw conflict("Invite already consumed");
2868
+ if (invite.revokedAt)
2869
+ return res.json(invite);
2870
+ const revoked = await db
2871
+ .update(invites)
2872
+ .set({ revokedAt: new Date(), updatedAt: new Date() })
2873
+ .where(eq(invites.id, id))
2874
+ .returning()
2875
+ .then((rows) => rows[0]);
2876
+ if (invite.companyId) {
2877
+ await logActivity(db, {
2878
+ companyId: invite.companyId,
2879
+ actorType: req.actor.type === "agent" ? "agent" : "user",
2880
+ actorId: req.actor.type === "agent"
2881
+ ? req.actor.agentId ?? "unknown-agent"
2882
+ : req.actor.userId ?? "board",
2883
+ action: "invite.revoked",
2884
+ entityType: "invite",
2885
+ entityId: id
2886
+ });
2887
+ }
2888
+ res.json(revoked);
2889
+ });
2890
+ router.get("/companies/:companyId/invites", async (req, res) => {
2891
+ const companyId = req.params.companyId;
2892
+ await assertCompanyPermission(req, companyId, "users:invite");
2893
+ const query = listCompanyInvitesQuerySchema.parse(req.query);
2894
+ const invitesForCompany = await loadCompanyInviteRecords(db, companyId, query);
2895
+ res.json(invitesForCompany);
2896
+ });
2897
+ router.get("/companies/:companyId/join-requests", async (req, res) => {
2898
+ const companyId = req.params.companyId;
2899
+ await assertCompanyPermission(req, companyId, "joins:approve");
2900
+ const query = listJoinRequestsQuerySchema.parse(req.query);
2901
+ const all = await loadJoinRequestRecords(db, companyId);
2902
+ const filtered = all.filter((row) => {
2903
+ if (query.status && row.status !== query.status)
2904
+ return false;
2905
+ if (query.requestType && row.requestType !== query.requestType)
2906
+ return false;
2907
+ return true;
2908
+ });
2909
+ res.json(filtered);
2910
+ });
2911
+ router.post("/companies/:companyId/join-requests/:requestId/approve", async (req, res) => {
2912
+ const companyId = req.params.companyId;
2913
+ const requestId = req.params.requestId;
2914
+ await assertCompanyPermission(req, companyId, "joins:approve");
2915
+ const existing = await db
2916
+ .select()
2917
+ .from(joinRequests)
2918
+ .where(and(eq(joinRequests.companyId, companyId), eq(joinRequests.id, requestId)))
2919
+ .then((rows) => rows[0] ?? null);
2920
+ if (!existing)
2921
+ throw notFound("Join request not found");
2922
+ if (existing.status !== "pending_approval")
2923
+ throw conflict("Join request is not pending");
2924
+ const invite = await db
2925
+ .select()
2926
+ .from(invites)
2927
+ .where(eq(invites.id, existing.inviteId))
2928
+ .then((rows) => rows[0] ?? null);
2929
+ if (!invite)
2930
+ throw notFound("Invite not found");
2931
+ let createdAgentId = existing.createdAgentId ?? null;
2932
+ if (existing.requestType === "human") {
2933
+ if (!existing.requestingUserId)
2934
+ throw conflict("Join request missing user identity");
2935
+ const membershipRole = resolveHumanInviteRole(invite.defaultsPayload);
2936
+ await access.ensureMembership(companyId, "user", existing.requestingUserId, membershipRole, "active");
2937
+ const grants = humanJoinGrantsFromDefaults(invite.defaultsPayload, membershipRole);
2938
+ await access.setPrincipalGrants(companyId, "user", existing.requestingUserId, grants, req.actor.userId ?? null);
2939
+ }
2940
+ else {
2941
+ const existingAgents = await agents.list(companyId);
2942
+ const managerId = resolveJoinRequestAgentManagerId(existingAgents);
2943
+ if (!managerId) {
2944
+ throw conflict("Join request cannot be approved because this company has no active CEO");
2945
+ }
2946
+ const agentName = deduplicateAgentName(existing.agentName ?? "New Agent", existingAgents.map((a) => ({
2947
+ id: a.id,
2948
+ name: a.name,
2949
+ status: a.status
2950
+ })));
2951
+ const created = await agents.create(companyId, {
2952
+ name: agentName,
2953
+ role: "general",
2954
+ title: null,
2955
+ status: "idle",
2956
+ reportsTo: managerId,
2957
+ capabilities: existing.capabilities ?? null,
2958
+ adapterType: existing.adapterType ?? "process",
2959
+ adapterConfig: existing.agentDefaultsPayload &&
2960
+ typeof existing.agentDefaultsPayload === "object"
2961
+ ? existing.agentDefaultsPayload
2962
+ : {},
2963
+ runtimeConfig: {},
2964
+ budgetMonthlyCents: 0,
2965
+ spentMonthlyCents: 0,
2966
+ permissions: {},
2967
+ lastHeartbeatAt: null,
2968
+ metadata: null
2969
+ });
2970
+ createdAgentId = created.id;
2971
+ await access.ensureMembership(companyId, "agent", created.id, "member", "active");
2972
+ const grants = agentJoinGrantsFromDefaults(invite.defaultsPayload);
2973
+ await access.setPrincipalGrants(companyId, "agent", created.id, grants, req.actor.userId ?? null);
2974
+ }
2975
+ const approved = await db
2976
+ .update(joinRequests)
2977
+ .set({
2978
+ status: "approved",
2979
+ approvedByUserId: req.actor.userId ?? (isLocalImplicit(req) ? "local-board" : null),
2980
+ approvedAt: new Date(),
2981
+ createdAgentId,
2982
+ updatedAt: new Date()
2983
+ })
2984
+ .where(eq(joinRequests.id, requestId))
2985
+ .returning()
2986
+ .then((rows) => rows[0]);
2987
+ await logActivity(db, {
2988
+ companyId,
2989
+ actorType: "user",
2990
+ actorId: req.actor.userId ?? "board",
2991
+ action: "join.approved",
2992
+ entityType: "join_request",
2993
+ entityId: requestId,
2994
+ details: { requestType: existing.requestType, createdAgentId }
2995
+ });
2996
+ if (createdAgentId) {
2997
+ void notifyHireApproved(db, {
2998
+ companyId,
2999
+ agentId: createdAgentId,
3000
+ source: "join_request",
3001
+ sourceId: requestId,
3002
+ approvedAt: new Date()
3003
+ }).catch(() => { });
3004
+ }
3005
+ res.json(toJoinRequestResponse(approved));
3006
+ });
3007
+ router.post("/companies/:companyId/join-requests/:requestId/reject", async (req, res) => {
3008
+ const companyId = req.params.companyId;
3009
+ const requestId = req.params.requestId;
3010
+ await assertCompanyPermission(req, companyId, "joins:approve");
3011
+ const existing = await db
3012
+ .select()
3013
+ .from(joinRequests)
3014
+ .where(and(eq(joinRequests.companyId, companyId), eq(joinRequests.id, requestId)))
3015
+ .then((rows) => rows[0] ?? null);
3016
+ if (!existing)
3017
+ throw notFound("Join request not found");
3018
+ if (existing.status !== "pending_approval")
3019
+ throw conflict("Join request is not pending");
3020
+ const rejected = await db
3021
+ .update(joinRequests)
3022
+ .set({
3023
+ status: "rejected",
3024
+ rejectedByUserId: req.actor.userId ?? (isLocalImplicit(req) ? "local-board" : null),
3025
+ rejectedAt: new Date(),
3026
+ updatedAt: new Date()
3027
+ })
3028
+ .where(eq(joinRequests.id, requestId))
3029
+ .returning()
3030
+ .then((rows) => rows[0]);
3031
+ await logActivity(db, {
3032
+ companyId,
3033
+ actorType: "user",
3034
+ actorId: req.actor.userId ?? "board",
3035
+ action: "join.rejected",
3036
+ entityType: "join_request",
3037
+ entityId: requestId,
3038
+ details: { requestType: existing.requestType }
3039
+ });
3040
+ res.json(toJoinRequestResponse(rejected));
3041
+ });
3042
+ router.post("/join-requests/:requestId/claim-api-key", validate(claimJoinRequestApiKeySchema), async (req, res) => {
3043
+ const requestId = req.params.requestId;
3044
+ const presentedClaimSecretHash = hashToken(req.body.claimSecret);
3045
+ const joinRequest = await db
3046
+ .select()
3047
+ .from(joinRequests)
3048
+ .where(eq(joinRequests.id, requestId))
3049
+ .then((rows) => rows[0] ?? null);
3050
+ if (!joinRequest)
3051
+ throw notFound("Join request not found");
3052
+ if (joinRequest.requestType !== "agent")
3053
+ throw badRequest("Only agent join requests can claim API keys");
3054
+ if (joinRequest.status !== "approved")
3055
+ throw conflict("Join request must be approved before key claim");
3056
+ if (!joinRequest.createdAgentId)
3057
+ throw conflict("Join request has no created agent");
3058
+ if (!joinRequest.claimSecretHash)
3059
+ throw conflict("Join request is missing claim secret metadata");
3060
+ if (!tokenHashesMatch(joinRequest.claimSecretHash, presentedClaimSecretHash)) {
3061
+ throw forbidden("Invalid claim secret");
3062
+ }
3063
+ if (joinRequest.claimSecretExpiresAt &&
3064
+ joinRequest.claimSecretExpiresAt.getTime() <= Date.now()) {
3065
+ throw conflict("Claim secret expired");
3066
+ }
3067
+ if (joinRequest.claimSecretConsumedAt)
3068
+ throw conflict("Claim secret already used");
3069
+ const existingKey = await db
3070
+ .select({ id: agentApiKeys.id })
3071
+ .from(agentApiKeys)
3072
+ .where(eq(agentApiKeys.agentId, joinRequest.createdAgentId))
3073
+ .then((rows) => rows[0] ?? null);
3074
+ if (existingKey)
3075
+ throw conflict("API key already claimed");
3076
+ const consumed = await db
3077
+ .update(joinRequests)
3078
+ .set({ claimSecretConsumedAt: new Date(), updatedAt: new Date() })
3079
+ .where(and(eq(joinRequests.id, requestId), isNull(joinRequests.claimSecretConsumedAt)))
3080
+ .returning({ id: joinRequests.id })
3081
+ .then((rows) => rows[0] ?? null);
3082
+ if (!consumed)
3083
+ throw conflict("Claim secret already used");
3084
+ const created = await agents.createApiKey(joinRequest.createdAgentId, "initial-join-key");
3085
+ await logActivity(db, {
3086
+ companyId: joinRequest.companyId,
3087
+ actorType: "system",
3088
+ actorId: "join-claim",
3089
+ action: "agent_api_key.claimed",
3090
+ entityType: "agent_api_key",
3091
+ entityId: created.id,
3092
+ details: {
3093
+ agentId: joinRequest.createdAgentId,
3094
+ joinRequestId: requestId
3095
+ }
3096
+ });
3097
+ res.status(201).json({
3098
+ keyId: created.id,
3099
+ token: created.token,
3100
+ agentId: joinRequest.createdAgentId,
3101
+ createdAt: created.createdAt
3102
+ });
3103
+ });
3104
+ router.get("/companies/:companyId/members", async (req, res) => {
3105
+ const companyId = req.params.companyId;
3106
+ await assertCompanyPermission(req, companyId, "users:manage_permissions");
3107
+ const [members, currentAccess] = await Promise.all([
3108
+ loadCompanyMemberRecords(db, companyId),
3109
+ loadCompanyAccessSummary(req, access, companyId),
3110
+ ]);
3111
+ res.json({
3112
+ members: await addCompanyMemberRemovalAccess(req, db, access, companyId, members),
3113
+ access: currentAccess,
3114
+ });
3115
+ });
3116
+ router.get("/companies/:companyId/user-directory", async (req, res) => {
3117
+ const companyId = req.params.companyId;
3118
+ assertCompanyAccess(req, companyId);
3119
+ const users = await loadCompanyUserDirectory(db, companyId);
3120
+ res.json({ users });
3121
+ });
3122
+ router.patch("/companies/:companyId/members/:memberId", validate(updateCompanyMemberSchema), async (req, res) => {
3123
+ const companyId = req.params.companyId;
3124
+ const memberId = req.params.memberId;
3125
+ await assertCompanyPermission(req, companyId, "users:manage_permissions");
3126
+ const memberToUpdate = await access.getMemberById(companyId, memberId);
3127
+ if (!memberToUpdate)
3128
+ throw notFound("Member not found");
3129
+ await assertCanManageCompanyMember(req, access, companyId, memberToUpdate);
3130
+ const updated = await db.transaction(async (tx) => {
3131
+ await tx.execute(sql `
3132
+ select ${companyMemberships.id}
3133
+ from ${companyMemberships}
3134
+ where ${companyMemberships.companyId} = ${companyId}
3135
+ and ${companyMemberships.principalType} = 'user'
3136
+ and ${companyMemberships.status} = 'active'
3137
+ and ${companyMemberships.membershipRole} = 'owner'
3138
+ for update
3139
+ `);
3140
+ const existing = await tx
3141
+ .select()
3142
+ .from(companyMemberships)
3143
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.id, memberId)))
3144
+ .then((rows) => rows[0] ?? null);
3145
+ if (!existing)
3146
+ return null;
3147
+ const nextMembershipRole = req.body.membershipRole !== undefined
3148
+ ? req.body.membershipRole
3149
+ : existing.membershipRole;
3150
+ const nextStatus = req.body.status ?? existing.status;
3151
+ if (existing.principalType === "user" &&
3152
+ existing.status === "active" &&
3153
+ existing.membershipRole === "owner" &&
3154
+ (nextStatus !== "active" || nextMembershipRole !== "owner")) {
3155
+ const activeOwnerCount = await tx
3156
+ .select({ id: companyMemberships.id })
3157
+ .from(companyMemberships)
3158
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.status, "active"), eq(companyMemberships.membershipRole, "owner")))
3159
+ .then((rows) => rows.length);
3160
+ if (activeOwnerCount <= 1) {
3161
+ throw conflict("Cannot remove the last active owner");
3162
+ }
3163
+ }
3164
+ return tx
3165
+ .update(companyMemberships)
3166
+ .set({
3167
+ membershipRole: nextMembershipRole,
3168
+ status: nextStatus,
3169
+ updatedAt: new Date(),
3170
+ })
3171
+ .where(eq(companyMemberships.id, existing.id))
3172
+ .returning()
3173
+ .then((rows) => rows[0] ?? existing);
3174
+ });
3175
+ if (!updated)
3176
+ throw notFound("Member not found");
3177
+ await logActivity(db, {
3178
+ companyId,
3179
+ actorType: "user",
3180
+ actorId: req.actor.userId ?? "board",
3181
+ action: "company_member.updated",
3182
+ entityType: "company_membership",
3183
+ entityId: memberId,
3184
+ details: {
3185
+ membershipRole: updated.membershipRole,
3186
+ status: updated.status,
3187
+ },
3188
+ });
3189
+ const member = (await loadCompanyMemberRecords(db, companyId)).find((entry) => entry.id === memberId);
3190
+ if (!member)
3191
+ throw notFound("Member not found");
3192
+ res.json(member);
3193
+ });
3194
+ router.patch("/companies/:companyId/members/:memberId/role-and-grants", validate(updateCompanyMemberWithPermissionsSchema), async (req, res) => {
3195
+ const companyId = req.params.companyId;
3196
+ const memberId = req.params.memberId;
3197
+ await assertCompanyPermission(req, companyId, "users:manage_permissions");
3198
+ const memberToUpdate = await access.getMemberById(companyId, memberId);
3199
+ if (!memberToUpdate)
3200
+ throw notFound("Member not found");
3201
+ await assertCanManageCompanyMember(req, access, companyId, memberToUpdate);
3202
+ const updated = await db.transaction(async (tx) => {
3203
+ await tx.execute(sql `
3204
+ select ${companyMemberships.id}
3205
+ from ${companyMemberships}
3206
+ where ${companyMemberships.companyId} = ${companyId}
3207
+ and ${companyMemberships.principalType} = 'user'
3208
+ and ${companyMemberships.status} = 'active'
3209
+ and ${companyMemberships.membershipRole} = 'owner'
3210
+ for update
3211
+ `);
3212
+ const existing = await tx
3213
+ .select()
3214
+ .from(companyMemberships)
3215
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.id, memberId)))
3216
+ .then((rows) => rows[0] ?? null);
3217
+ if (!existing)
3218
+ return null;
3219
+ const nextMembershipRole = req.body.membershipRole !== undefined
3220
+ ? req.body.membershipRole
3221
+ : existing.membershipRole;
3222
+ const nextStatus = req.body.status ?? existing.status;
3223
+ if (existing.principalType === "user" &&
3224
+ existing.status === "active" &&
3225
+ existing.membershipRole === "owner" &&
3226
+ (nextStatus !== "active" || nextMembershipRole !== "owner")) {
3227
+ const activeOwnerCount = await tx
3228
+ .select({ id: companyMemberships.id })
3229
+ .from(companyMemberships)
3230
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.status, "active"), eq(companyMemberships.membershipRole, "owner")))
3231
+ .then((rows) => rows.length);
3232
+ if (activeOwnerCount <= 1) {
3233
+ throw conflict("Cannot remove the last active owner");
3234
+ }
3235
+ }
3236
+ const now = new Date();
3237
+ const updatedMember = await tx
3238
+ .update(companyMemberships)
3239
+ .set({
3240
+ membershipRole: nextMembershipRole,
3241
+ status: nextStatus,
3242
+ updatedAt: now,
3243
+ })
3244
+ .where(eq(companyMemberships.id, existing.id))
3245
+ .returning()
3246
+ .then((rows) => rows[0] ?? existing);
3247
+ await tx
3248
+ .delete(principalPermissionGrants)
3249
+ .where(and(eq(principalPermissionGrants.companyId, companyId), eq(principalPermissionGrants.principalType, existing.principalType), eq(principalPermissionGrants.principalId, existing.principalId)));
3250
+ const grants = (req.body.grants ?? []);
3251
+ if (grants.length > 0) {
3252
+ await tx.insert(principalPermissionGrants).values(grants.map((grant) => ({
3253
+ companyId,
3254
+ principalType: existing.principalType,
3255
+ principalId: existing.principalId,
3256
+ permissionKey: grant.permissionKey,
3257
+ scope: grant.scope ?? null,
3258
+ grantedByUserId: req.actor.userId ?? null,
3259
+ createdAt: now,
3260
+ updatedAt: now,
3261
+ })));
3262
+ }
3263
+ return updatedMember;
3264
+ });
3265
+ if (!updated)
3266
+ throw notFound("Member not found");
3267
+ await logActivity(db, {
3268
+ companyId,
3269
+ actorType: "user",
3270
+ actorId: req.actor.userId ?? "board",
3271
+ action: "company_member.access_updated",
3272
+ entityType: "company_membership",
3273
+ entityId: memberId,
3274
+ details: {
3275
+ membershipRole: updated.membershipRole,
3276
+ status: updated.status,
3277
+ grantCount: req.body.grants?.length ?? 0,
3278
+ },
3279
+ });
3280
+ const member = (await loadCompanyMemberRecords(db, companyId)).find((entry) => entry.id === memberId);
3281
+ if (!member)
3282
+ throw notFound("Member not found");
3283
+ res.json(member);
3284
+ });
3285
+ router.post("/companies/:companyId/members/:memberId/archive", validate(archiveCompanyMemberSchema), async (req, res) => {
3286
+ const companyId = req.params.companyId;
3287
+ const memberId = req.params.memberId;
3288
+ await assertCompanyPermission(req, companyId, "users:manage_permissions");
3289
+ const memberToArchive = await access.getMemberById(companyId, memberId);
3290
+ if (!memberToArchive)
3291
+ throw notFound("Member not found");
3292
+ await assertCanManageCompanyMember(req, access, companyId, memberToArchive, "archive");
3293
+ const result = await access.archiveMember(companyId, memberId, {
3294
+ reassignment: req.body.reassignment ?? null,
3295
+ });
3296
+ if (!result)
3297
+ throw notFound("Member not found");
3298
+ await logActivity(db, {
3299
+ companyId,
3300
+ actorType: "user",
3301
+ actorId: req.actor.userId ?? "board",
3302
+ action: "company_member.archived",
3303
+ entityType: "company_membership",
3304
+ entityId: memberId,
3305
+ details: {
3306
+ principalId: result.member.principalId,
3307
+ reassignedIssueCount: result.reassignedIssueCount,
3308
+ reassignment: req.body.reassignment ?? null,
3309
+ },
3310
+ });
3311
+ const member = (await loadCompanyMemberRecords(db, companyId, { includeArchived: true })).find((entry) => entry.id === memberId);
3312
+ if (!member)
3313
+ throw notFound("Member not found");
3314
+ res.json({
3315
+ member,
3316
+ reassignedIssueCount: result.reassignedIssueCount,
3317
+ });
3318
+ });
3319
+ router.patch("/companies/:companyId/members/:memberId/permissions", validate(updateMemberPermissionsSchema), async (req, res) => {
3320
+ const companyId = req.params.companyId;
3321
+ const memberId = req.params.memberId;
3322
+ await assertCompanyPermission(req, companyId, "users:manage_permissions");
3323
+ const memberToUpdate = await access.getMemberById(companyId, memberId);
3324
+ if (!memberToUpdate)
3325
+ throw notFound("Member not found");
3326
+ await assertCanManageCompanyMember(req, access, companyId, memberToUpdate);
3327
+ const updated = await access.setMemberPermissions(companyId, memberId, req.body.grants ?? [], req.actor.userId ?? null);
3328
+ if (!updated)
3329
+ throw notFound("Member not found");
3330
+ await logActivity(db, {
3331
+ companyId,
3332
+ actorType: "user",
3333
+ actorId: req.actor.userId ?? "board",
3334
+ action: "company_member.permissions_updated",
3335
+ entityType: "company_membership",
3336
+ entityId: memberId,
3337
+ details: {
3338
+ grantCount: req.body.grants?.length ?? 0,
3339
+ },
3340
+ });
3341
+ const member = (await loadCompanyMemberRecords(db, companyId)).find((entry) => entry.id === memberId);
3342
+ if (!member)
3343
+ throw notFound("Member not found");
3344
+ res.json(member);
3345
+ });
3346
+ router.post("/admin/users/:userId/promote-instance-admin", async (req, res) => {
3347
+ await assertInstanceAdmin(req);
3348
+ const userId = req.params.userId;
3349
+ const result = await access.promoteInstanceAdmin(userId);
3350
+ res.status(201).json(result);
3351
+ });
3352
+ router.get("/admin/users", async (req, res) => {
3353
+ await assertInstanceAdmin(req);
3354
+ const query = searchAdminUsersQuerySchema.parse(req.query);
3355
+ const needle = query.query.trim().toLowerCase();
3356
+ const users = await db
3357
+ .select({
3358
+ id: authUsers.id,
3359
+ email: authUsers.email,
3360
+ name: authUsers.name,
3361
+ image: authUsers.image,
3362
+ })
3363
+ .from(authUsers)
3364
+ .orderBy(desc(authUsers.updatedAt));
3365
+ const filteredUsers = needle
3366
+ ? users.filter((user) => [user.name, user.email]
3367
+ .filter((value) => Boolean(value))
3368
+ .some((value) => value.toLowerCase().includes(needle)))
3369
+ : users;
3370
+ const userIds = filteredUsers.slice(0, 50).map((user) => user.id);
3371
+ const memberships = userIds.length
3372
+ ? await db
3373
+ .select({
3374
+ principalId: companyMemberships.principalId,
3375
+ })
3376
+ .from(companyMemberships)
3377
+ .where(and(eq(companyMemberships.principalType, "user"), eq(companyMemberships.status, "active"), inArray(companyMemberships.principalId, userIds)))
3378
+ : [];
3379
+ const membershipCountByUserId = new Map();
3380
+ for (const membership of memberships) {
3381
+ membershipCountByUserId.set(membership.principalId, (membershipCountByUserId.get(membership.principalId) ?? 0) + 1);
3382
+ }
3383
+ const adminIds = new Set(await Promise.all(userIds.map(async (userId) => (await access.isInstanceAdmin(userId)) ? userId : null)).then((values) => values.filter((value) => Boolean(value))));
3384
+ res.json(filteredUsers.slice(0, 50).map((user) => ({
3385
+ ...toUserProfile(user),
3386
+ isInstanceAdmin: adminIds.has(user.id),
3387
+ activeCompanyMembershipCount: membershipCountByUserId.get(user.id) ?? 0,
3388
+ })));
3389
+ });
3390
+ router.post("/admin/users/:userId/demote-instance-admin", async (req, res) => {
3391
+ await assertInstanceAdmin(req);
3392
+ const userId = req.params.userId;
3393
+ const removed = await access.demoteInstanceAdmin(userId);
3394
+ if (!removed)
3395
+ throw notFound("Instance admin role not found");
3396
+ res.json(removed);
3397
+ });
3398
+ router.get("/admin/users/:userId/company-access", async (req, res) => {
3399
+ await assertInstanceAdmin(req);
3400
+ const userId = req.params.userId;
3401
+ res.json(await loadUserCompanyAccessResponse(db, access, userId));
3402
+ });
3403
+ router.put("/admin/users/:userId/company-access", validate(updateUserCompanyAccessSchema), async (req, res) => {
3404
+ await assertInstanceAdmin(req);
3405
+ const userId = req.params.userId;
3406
+ await access.setUserCompanyAccess(userId, req.body.companyIds ?? [], { actorUserId: req.actor.userId ?? null });
3407
+ res.json(await loadUserCompanyAccessResponse(db, access, userId));
3408
+ });
3409
+ return router;
3410
+ }
3411
+ //# sourceMappingURL=access.js.map