@paperclipai_dld/server 2026.319.0-canary.3

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 (702) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/codex-models.d.ts +4 -0
  3. package/dist/adapters/codex-models.d.ts.map +1 -0
  4. package/dist/adapters/codex-models.js +98 -0
  5. package/dist/adapters/codex-models.js.map +1 -0
  6. package/dist/adapters/cursor-models.d.ts +13 -0
  7. package/dist/adapters/cursor-models.d.ts.map +1 -0
  8. package/dist/adapters/cursor-models.js +148 -0
  9. package/dist/adapters/cursor-models.js.map +1 -0
  10. package/dist/adapters/http/execute.d.ts +3 -0
  11. package/dist/adapters/http/execute.d.ts.map +1 -0
  12. package/dist/adapters/http/execute.js +39 -0
  13. package/dist/adapters/http/execute.js.map +1 -0
  14. package/dist/adapters/http/index.d.ts +3 -0
  15. package/dist/adapters/http/index.d.ts.map +1 -0
  16. package/dist/adapters/http/index.js +20 -0
  17. package/dist/adapters/http/index.js.map +1 -0
  18. package/dist/adapters/http/test.d.ts +3 -0
  19. package/dist/adapters/http/test.d.ts.map +1 -0
  20. package/dist/adapters/http/test.js +106 -0
  21. package/dist/adapters/http/test.js.map +1 -0
  22. package/dist/adapters/index.d.ts +4 -0
  23. package/dist/adapters/index.d.ts.map +1 -0
  24. package/dist/adapters/index.js +3 -0
  25. package/dist/adapters/index.js.map +1 -0
  26. package/dist/adapters/process/execute.d.ts +3 -0
  27. package/dist/adapters/process/execute.d.ts.map +1 -0
  28. package/dist/adapters/process/execute.js +63 -0
  29. package/dist/adapters/process/execute.js.map +1 -0
  30. package/dist/adapters/process/index.d.ts +3 -0
  31. package/dist/adapters/process/index.d.ts.map +1 -0
  32. package/dist/adapters/process/index.js +23 -0
  33. package/dist/adapters/process/index.js.map +1 -0
  34. package/dist/adapters/process/test.d.ts +3 -0
  35. package/dist/adapters/process/test.d.ts.map +1 -0
  36. package/dist/adapters/process/test.js +77 -0
  37. package/dist/adapters/process/test.js.map +1 -0
  38. package/dist/adapters/registry.d.ts +9 -0
  39. package/dist/adapters/registry.d.ts.map +1 -0
  40. package/dist/adapters/registry.js +142 -0
  41. package/dist/adapters/registry.js.map +1 -0
  42. package/dist/adapters/types.d.ts +2 -0
  43. package/dist/adapters/types.d.ts.map +1 -0
  44. package/dist/adapters/types.js +2 -0
  45. package/dist/adapters/types.js.map +1 -0
  46. package/dist/adapters/utils.d.ts +10 -0
  47. package/dist/adapters/utils.d.ts.map +1 -0
  48. package/dist/adapters/utils.js +14 -0
  49. package/dist/adapters/utils.js.map +1 -0
  50. package/dist/agent-auth-jwt.d.ts +14 -0
  51. package/dist/agent-auth-jwt.d.ts.map +1 -0
  52. package/dist/agent-auth-jwt.js +117 -0
  53. package/dist/agent-auth-jwt.js.map +1 -0
  54. package/dist/app.d.ts +25 -0
  55. package/dist/app.d.ts.map +1 -0
  56. package/dist/app.js +259 -0
  57. package/dist/app.js.map +1 -0
  58. package/dist/attachment-types.d.ts +33 -0
  59. package/dist/attachment-types.d.ts.map +1 -0
  60. package/dist/attachment-types.js +67 -0
  61. package/dist/attachment-types.js.map +1 -0
  62. package/dist/auth/better-auth.d.ts +24 -0
  63. package/dist/auth/better-auth.d.ts.map +1 -0
  64. package/dist/auth/better-auth.js +108 -0
  65. package/dist/auth/better-auth.js.map +1 -0
  66. package/dist/board-claim.d.ts +23 -0
  67. package/dist/board-claim.d.ts.map +1 -0
  68. package/dist/board-claim.js +115 -0
  69. package/dist/board-claim.js.map +1 -0
  70. package/dist/config-file.d.ts +3 -0
  71. package/dist/config-file.d.ts.map +1 -0
  72. package/dist/config-file.js +16 -0
  73. package/dist/config-file.js.map +1 -0
  74. package/dist/config.d.ts +38 -0
  75. package/dist/config.d.ts.map +1 -0
  76. package/dist/config.js +162 -0
  77. package/dist/config.js.map +1 -0
  78. package/dist/errors.d.ts +12 -0
  79. package/dist/errors.d.ts.map +1 -0
  80. package/dist/errors.js +28 -0
  81. package/dist/errors.js.map +1 -0
  82. package/dist/home-paths.d.ts +17 -0
  83. package/dist/home-paths.d.ts.map +1 -0
  84. package/dist/home-paths.js +75 -0
  85. package/dist/home-paths.js.map +1 -0
  86. package/dist/index.d.ts +10 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +581 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/log-redaction.d.ts +10 -0
  91. package/dist/log-redaction.d.ts.map +1 -0
  92. package/dist/log-redaction.js +110 -0
  93. package/dist/log-redaction.js.map +1 -0
  94. package/dist/middleware/auth.d.ts +12 -0
  95. package/dist/middleware/auth.d.ts.map +1 -0
  96. package/dist/middleware/auth.js +124 -0
  97. package/dist/middleware/auth.js.map +1 -0
  98. package/dist/middleware/board-mutation-guard.d.ts +3 -0
  99. package/dist/middleware/board-mutation-guard.d.ts.map +1 -0
  100. package/dist/middleware/board-mutation-guard.js +60 -0
  101. package/dist/middleware/board-mutation-guard.js.map +1 -0
  102. package/dist/middleware/error-handler.d.ts +17 -0
  103. package/dist/middleware/error-handler.d.ts.map +1 -0
  104. package/dist/middleware/error-handler.js +37 -0
  105. package/dist/middleware/error-handler.js.map +1 -0
  106. package/dist/middleware/index.d.ts +4 -0
  107. package/dist/middleware/index.d.ts.map +1 -0
  108. package/dist/middleware/index.js +4 -0
  109. package/dist/middleware/index.js.map +1 -0
  110. package/dist/middleware/logger.d.ts +4 -0
  111. package/dist/middleware/logger.d.ts.map +1 -0
  112. package/dist/middleware/logger.js +87 -0
  113. package/dist/middleware/logger.js.map +1 -0
  114. package/dist/middleware/private-hostname-guard.d.ts +11 -0
  115. package/dist/middleware/private-hostname-guard.d.ts.map +1 -0
  116. package/dist/middleware/private-hostname-guard.js +78 -0
  117. package/dist/middleware/private-hostname-guard.js.map +1 -0
  118. package/dist/middleware/validate.d.ts +4 -0
  119. package/dist/middleware/validate.d.ts.map +1 -0
  120. package/dist/middleware/validate.js +7 -0
  121. package/dist/middleware/validate.js.map +1 -0
  122. package/dist/paths.d.ts +3 -0
  123. package/dist/paths.d.ts.map +1 -0
  124. package/dist/paths.js +31 -0
  125. package/dist/paths.js.map +1 -0
  126. package/dist/realtime/live-events-ws.d.ts +28 -0
  127. package/dist/realtime/live-events-ws.d.ts.map +1 -0
  128. package/dist/realtime/live-events-ws.js +187 -0
  129. package/dist/realtime/live-events-ws.js.map +1 -0
  130. package/dist/redaction.d.ts +4 -0
  131. package/dist/redaction.d.ts.map +1 -0
  132. package/dist/redaction.js +63 -0
  133. package/dist/redaction.js.map +1 -0
  134. package/dist/routes/access.d.ts +56 -0
  135. package/dist/routes/access.d.ts.map +1 -0
  136. package/dist/routes/access.js +2124 -0
  137. package/dist/routes/access.js.map +1 -0
  138. package/dist/routes/activity.d.ts +3 -0
  139. package/dist/routes/activity.d.ts.map +1 -0
  140. package/dist/routes/activity.js +78 -0
  141. package/dist/routes/activity.js.map +1 -0
  142. package/dist/routes/agents.d.ts +4 -0
  143. package/dist/routes/agents.d.ts.map +1 -0
  144. package/dist/routes/agents.js +1390 -0
  145. package/dist/routes/agents.js.map +1 -0
  146. package/dist/routes/approvals.d.ts +3 -0
  147. package/dist/routes/approvals.d.ts.map +1 -0
  148. package/dist/routes/approvals.js +275 -0
  149. package/dist/routes/approvals.js.map +1 -0
  150. package/dist/routes/assets.d.ts +4 -0
  151. package/dist/routes/assets.d.ts.map +1 -0
  152. package/dist/routes/assets.js +309 -0
  153. package/dist/routes/assets.js.map +1 -0
  154. package/dist/routes/authz.d.ts +15 -0
  155. package/dist/routes/authz.d.ts.map +1 -0
  156. package/dist/routes/authz.js +40 -0
  157. package/dist/routes/authz.js.map +1 -0
  158. package/dist/routes/companies.d.ts +3 -0
  159. package/dist/routes/companies.d.ts.map +1 -0
  160. package/dist/routes/companies.js +174 -0
  161. package/dist/routes/companies.js.map +1 -0
  162. package/dist/routes/costs.d.ts +3 -0
  163. package/dist/routes/costs.d.ts.map +1 -0
  164. package/dist/routes/costs.js +268 -0
  165. package/dist/routes/costs.js.map +1 -0
  166. package/dist/routes/dashboard.d.ts +3 -0
  167. package/dist/routes/dashboard.d.ts.map +1 -0
  168. package/dist/routes/dashboard.js +15 -0
  169. package/dist/routes/dashboard.js.map +1 -0
  170. package/dist/routes/execution-workspaces.d.ts +3 -0
  171. package/dist/routes/execution-workspaces.d.ts.map +1 -0
  172. package/dist/routes/execution-workspaces.js +165 -0
  173. package/dist/routes/execution-workspaces.js.map +1 -0
  174. package/dist/routes/goals.d.ts +3 -0
  175. package/dist/routes/goals.d.ts.map +1 -0
  176. package/dist/routes/goals.js +95 -0
  177. package/dist/routes/goals.js.map +1 -0
  178. package/dist/routes/health.d.ts +9 -0
  179. package/dist/routes/health.d.ts.map +1 -0
  180. package/dist/routes/health.js +51 -0
  181. package/dist/routes/health.js.map +1 -0
  182. package/dist/routes/index.d.ts +16 -0
  183. package/dist/routes/index.d.ts.map +1 -0
  184. package/dist/routes/index.js +16 -0
  185. package/dist/routes/index.js.map +1 -0
  186. package/dist/routes/instance-settings.d.ts +3 -0
  187. package/dist/routes/instance-settings.d.ts.map +1 -0
  188. package/dist/routes/instance-settings.js +46 -0
  189. package/dist/routes/instance-settings.js.map +1 -0
  190. package/dist/routes/issues-checkout-wakeup.d.ts +9 -0
  191. package/dist/routes/issues-checkout-wakeup.d.ts.map +1 -0
  192. package/dist/routes/issues-checkout-wakeup.js +12 -0
  193. package/dist/routes/issues-checkout-wakeup.js.map +1 -0
  194. package/dist/routes/issues.d.ts +4 -0
  195. package/dist/routes/issues.d.ts.map +1 -0
  196. package/dist/routes/issues.js +1431 -0
  197. package/dist/routes/issues.js.map +1 -0
  198. package/dist/routes/llms.d.ts +3 -0
  199. package/dist/routes/llms.d.ts.map +1 -0
  200. package/dist/routes/llms.js +78 -0
  201. package/dist/routes/llms.js.map +1 -0
  202. package/dist/routes/plugin-ui-static.d.ts +69 -0
  203. package/dist/routes/plugin-ui-static.d.ts.map +1 -0
  204. package/dist/routes/plugin-ui-static.js +411 -0
  205. package/dist/routes/plugin-ui-static.js.map +1 -0
  206. package/dist/routes/plugins.d.ts +120 -0
  207. package/dist/routes/plugins.d.ts.map +1 -0
  208. package/dist/routes/plugins.js +1784 -0
  209. package/dist/routes/plugins.js.map +1 -0
  210. package/dist/routes/projects.d.ts +3 -0
  211. package/dist/routes/projects.d.ts.map +1 -0
  212. package/dist/routes/projects.js +257 -0
  213. package/dist/routes/projects.js.map +1 -0
  214. package/dist/routes/secrets.d.ts +3 -0
  215. package/dist/routes/secrets.d.ts.map +1 -0
  216. package/dist/routes/secrets.js +128 -0
  217. package/dist/routes/secrets.js.map +1 -0
  218. package/dist/routes/sidebar-badges.d.ts +3 -0
  219. package/dist/routes/sidebar-badges.d.ts.map +1 -0
  220. package/dist/routes/sidebar-badges.js +45 -0
  221. package/dist/routes/sidebar-badges.js.map +1 -0
  222. package/dist/secrets/external-stub-providers.d.ts +5 -0
  223. package/dist/secrets/external-stub-providers.d.ts.map +1 -0
  224. package/dist/secrets/external-stub-providers.js +21 -0
  225. package/dist/secrets/external-stub-providers.js.map +1 -0
  226. package/dist/secrets/local-encrypted-provider.d.ts +3 -0
  227. package/dist/secrets/local-encrypted-provider.d.ts.map +1 -0
  228. package/dist/secrets/local-encrypted-provider.js +116 -0
  229. package/dist/secrets/local-encrypted-provider.js.map +1 -0
  230. package/dist/secrets/provider-registry.d.ts +5 -0
  231. package/dist/secrets/provider-registry.d.ts.map +1 -0
  232. package/dist/secrets/provider-registry.js +20 -0
  233. package/dist/secrets/provider-registry.js.map +1 -0
  234. package/dist/secrets/types.d.ts +21 -0
  235. package/dist/secrets/types.d.ts.map +1 -0
  236. package/dist/secrets/types.js +2 -0
  237. package/dist/secrets/types.js.map +1 -0
  238. package/dist/services/access.d.ts +81 -0
  239. package/dist/services/access.d.ts.map +1 -0
  240. package/dist/services/access.js +187 -0
  241. package/dist/services/access.js.map +1 -0
  242. package/dist/services/activity-log.d.ts +17 -0
  243. package/dist/services/activity-log.d.ts.map +1 -0
  244. package/dist/services/activity-log.js +68 -0
  245. package/dist/services/activity-log.js.map +1 -0
  246. package/dist/services/activity.d.ts +764 -0
  247. package/dist/services/activity.d.ts.map +1 -0
  248. package/dist/services/activity.js +105 -0
  249. package/dist/services/activity.js.map +1 -0
  250. package/dist/services/agent-permissions.d.ts +6 -0
  251. package/dist/services/agent-permissions.d.ts.map +1 -0
  252. package/dist/services/agent-permissions.js +18 -0
  253. package/dist/services/agent-permissions.js.map +1 -0
  254. package/dist/services/agents.d.ts +1530 -0
  255. package/dist/services/agents.d.ts.map +1 -0
  256. package/dist/services/agents.js +566 -0
  257. package/dist/services/agents.js.map +1 -0
  258. package/dist/services/approvals.d.ts +546 -0
  259. package/dist/services/approvals.d.ts.map +1 -0
  260. package/dist/services/approvals.js +206 -0
  261. package/dist/services/approvals.js.map +1 -0
  262. package/dist/services/assets.d.ts +33 -0
  263. package/dist/services/assets.d.ts.map +1 -0
  264. package/dist/services/assets.js +17 -0
  265. package/dist/services/assets.js.map +1 -0
  266. package/dist/services/budgets.d.ts +38 -0
  267. package/dist/services/budgets.d.ts.map +1 -0
  268. package/dist/services/budgets.js +784 -0
  269. package/dist/services/budgets.js.map +1 -0
  270. package/dist/services/companies.d.ts +124 -0
  271. package/dist/services/companies.d.ts.map +1 -0
  272. package/dist/services/companies.js +256 -0
  273. package/dist/services/companies.js.map +1 -0
  274. package/dist/services/company-portability.d.ts +8 -0
  275. package/dist/services/company-portability.d.ts.map +1 -0
  276. package/dist/services/company-portability.js +867 -0
  277. package/dist/services/company-portability.js.map +1 -0
  278. package/dist/services/costs.d.ts +114 -0
  279. package/dist/services/costs.d.ts.map +1 -0
  280. package/dist/services/costs.js +294 -0
  281. package/dist/services/costs.js.map +1 -0
  282. package/dist/services/cron.d.ts +80 -0
  283. package/dist/services/cron.d.ts.map +1 -0
  284. package/dist/services/cron.js +300 -0
  285. package/dist/services/cron.js.map +1 -0
  286. package/dist/services/dashboard.d.ts +26 -0
  287. package/dist/services/dashboard.d.ts.map +1 -0
  288. package/dist/services/dashboard.js +98 -0
  289. package/dist/services/dashboard.js.map +1 -0
  290. package/dist/services/documents.d.ts +164 -0
  291. package/dist/services/documents.d.ts.map +1 -0
  292. package/dist/services/documents.js +382 -0
  293. package/dist/services/documents.js.map +1 -0
  294. package/dist/services/execution-workspace-policy.d.ts +20 -0
  295. package/dist/services/execution-workspace-policy.d.ts.map +1 -0
  296. package/dist/services/execution-workspace-policy.js +165 -0
  297. package/dist/services/execution-workspace-policy.js.map +1 -0
  298. package/dist/services/execution-workspaces.d.ts +19 -0
  299. package/dist/services/execution-workspaces.d.ts.map +1 -0
  300. package/dist/services/execution-workspaces.js +87 -0
  301. package/dist/services/execution-workspaces.js.map +1 -0
  302. package/dist/services/finance.d.ts +93 -0
  303. package/dist/services/finance.d.ts.map +1 -0
  304. package/dist/services/finance.js +120 -0
  305. package/dist/services/finance.js.map +1 -0
  306. package/dist/services/goals.d.ts +433 -0
  307. package/dist/services/goals.d.ts.map +1 -0
  308. package/dist/services/goals.js +54 -0
  309. package/dist/services/goals.js.map +1 -0
  310. package/dist/services/heartbeat-run-summary.d.ts +2 -0
  311. package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
  312. package/dist/services/heartbeat-run-summary.js +30 -0
  313. package/dist/services/heartbeat-run-summary.js.map +1 -0
  314. package/dist/services/heartbeat.d.ts +766 -0
  315. package/dist/services/heartbeat.d.ts.map +1 -0
  316. package/dist/services/heartbeat.js +3024 -0
  317. package/dist/services/heartbeat.js.map +1 -0
  318. package/dist/services/hire-hook.d.ts +14 -0
  319. package/dist/services/hire-hook.d.ts.map +1 -0
  320. package/dist/services/hire-hook.js +85 -0
  321. package/dist/services/hire-hook.js.map +1 -0
  322. package/dist/services/index.d.ts +29 -0
  323. package/dist/services/index.d.ts.map +1 -0
  324. package/dist/services/index.js +29 -0
  325. package/dist/services/index.js.map +1 -0
  326. package/dist/services/instance-settings.d.ts +9 -0
  327. package/dist/services/instance-settings.d.ts.map +1 -0
  328. package/dist/services/instance-settings.js +80 -0
  329. package/dist/services/instance-settings.js.map +1 -0
  330. package/dist/services/issue-approvals.d.ts +56 -0
  331. package/dist/services/issue-approvals.d.ts.map +1 -0
  332. package/dist/services/issue-approvals.js +153 -0
  333. package/dist/services/issue-approvals.js.map +1 -0
  334. package/dist/services/issue-goal-fallback.d.ts +15 -0
  335. package/dist/services/issue-goal-fallback.d.ts.map +1 -0
  336. package/dist/services/issue-goal-fallback.js +15 -0
  337. package/dist/services/issue-goal-fallback.js.map +1 -0
  338. package/dist/services/issues.d.ts +532 -0
  339. package/dist/services/issues.d.ts.map +1 -0
  340. package/dist/services/issues.js +1308 -0
  341. package/dist/services/issues.js.map +1 -0
  342. package/dist/services/live-events.d.ts +17 -0
  343. package/dist/services/live-events.d.ts.map +1 -0
  344. package/dist/services/live-events.js +33 -0
  345. package/dist/services/live-events.js.map +1 -0
  346. package/dist/services/plugin-capability-validator.d.ts +108 -0
  347. package/dist/services/plugin-capability-validator.d.ts.map +1 -0
  348. package/dist/services/plugin-capability-validator.js +268 -0
  349. package/dist/services/plugin-capability-validator.js.map +1 -0
  350. package/dist/services/plugin-config-validator.d.ts +26 -0
  351. package/dist/services/plugin-config-validator.d.ts.map +1 -0
  352. package/dist/services/plugin-config-validator.js +41 -0
  353. package/dist/services/plugin-config-validator.js.map +1 -0
  354. package/dist/services/plugin-dev-watcher.d.ts +30 -0
  355. package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
  356. package/dist/services/plugin-dev-watcher.js +241 -0
  357. package/dist/services/plugin-dev-watcher.js.map +1 -0
  358. package/dist/services/plugin-event-bus.d.ts +149 -0
  359. package/dist/services/plugin-event-bus.d.ts.map +1 -0
  360. package/dist/services/plugin-event-bus.js +258 -0
  361. package/dist/services/plugin-event-bus.js.map +1 -0
  362. package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
  363. package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
  364. package/dist/services/plugin-host-service-cleanup.js +37 -0
  365. package/dist/services/plugin-host-service-cleanup.js.map +1 -0
  366. package/dist/services/plugin-host-services.d.ts +13 -0
  367. package/dist/services/plugin-host-services.d.ts.map +1 -0
  368. package/dist/services/plugin-host-services.js +969 -0
  369. package/dist/services/plugin-host-services.js.map +1 -0
  370. package/dist/services/plugin-job-coordinator.d.ts +81 -0
  371. package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
  372. package/dist/services/plugin-job-coordinator.js +172 -0
  373. package/dist/services/plugin-job-coordinator.js.map +1 -0
  374. package/dist/services/plugin-job-scheduler.d.ts +163 -0
  375. package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
  376. package/dist/services/plugin-job-scheduler.js +454 -0
  377. package/dist/services/plugin-job-scheduler.js.map +1 -0
  378. package/dist/services/plugin-job-store.d.ts +208 -0
  379. package/dist/services/plugin-job-store.d.ts.map +1 -0
  380. package/dist/services/plugin-job-store.js +350 -0
  381. package/dist/services/plugin-job-store.js.map +1 -0
  382. package/dist/services/plugin-lifecycle.d.ts +203 -0
  383. package/dist/services/plugin-lifecycle.d.ts.map +1 -0
  384. package/dist/services/plugin-lifecycle.js +476 -0
  385. package/dist/services/plugin-lifecycle.js.map +1 -0
  386. package/dist/services/plugin-loader.d.ts +441 -0
  387. package/dist/services/plugin-loader.d.ts.map +1 -0
  388. package/dist/services/plugin-loader.js +1192 -0
  389. package/dist/services/plugin-loader.js.map +1 -0
  390. package/dist/services/plugin-log-retention.d.ts +20 -0
  391. package/dist/services/plugin-log-retention.d.ts.map +1 -0
  392. package/dist/services/plugin-log-retention.js +63 -0
  393. package/dist/services/plugin-log-retention.js.map +1 -0
  394. package/dist/services/plugin-manifest-validator.d.ts +79 -0
  395. package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
  396. package/dist/services/plugin-manifest-validator.js +84 -0
  397. package/dist/services/plugin-manifest-validator.js.map +1 -0
  398. package/dist/services/plugin-registry.d.ts +2542 -0
  399. package/dist/services/plugin-registry.d.ts.map +1 -0
  400. package/dist/services/plugin-registry.js +539 -0
  401. package/dist/services/plugin-registry.js.map +1 -0
  402. package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
  403. package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
  404. package/dist/services/plugin-runtime-sandbox.js +154 -0
  405. package/dist/services/plugin-runtime-sandbox.js.map +1 -0
  406. package/dist/services/plugin-secrets-handler.d.ts +81 -0
  407. package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
  408. package/dist/services/plugin-secrets-handler.js +275 -0
  409. package/dist/services/plugin-secrets-handler.js.map +1 -0
  410. package/dist/services/plugin-state-store.d.ts +92 -0
  411. package/dist/services/plugin-state-store.d.ts.map +1 -0
  412. package/dist/services/plugin-state-store.js +190 -0
  413. package/dist/services/plugin-state-store.js.map +1 -0
  414. package/dist/services/plugin-stream-bus.d.ts +29 -0
  415. package/dist/services/plugin-stream-bus.d.ts.map +1 -0
  416. package/dist/services/plugin-stream-bus.js +48 -0
  417. package/dist/services/plugin-stream-bus.js.map +1 -0
  418. package/dist/services/plugin-tool-dispatcher.d.ts +180 -0
  419. package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
  420. package/dist/services/plugin-tool-dispatcher.js +224 -0
  421. package/dist/services/plugin-tool-dispatcher.js.map +1 -0
  422. package/dist/services/plugin-tool-registry.d.ts +192 -0
  423. package/dist/services/plugin-tool-registry.d.ts.map +1 -0
  424. package/dist/services/plugin-tool-registry.js +224 -0
  425. package/dist/services/plugin-tool-registry.js.map +1 -0
  426. package/dist/services/plugin-worker-manager.d.ts +260 -0
  427. package/dist/services/plugin-worker-manager.d.ts.map +1 -0
  428. package/dist/services/plugin-worker-manager.js +835 -0
  429. package/dist/services/plugin-worker-manager.js.map +1 -0
  430. package/dist/services/projects.d.ts +87 -0
  431. package/dist/services/projects.d.ts.map +1 -0
  432. package/dist/services/projects.js +656 -0
  433. package/dist/services/projects.js.map +1 -0
  434. package/dist/services/quota-windows.d.ts +9 -0
  435. package/dist/services/quota-windows.d.ts.map +1 -0
  436. package/dist/services/quota-windows.js +56 -0
  437. package/dist/services/quota-windows.js.map +1 -0
  438. package/dist/services/run-log-store.d.ts +34 -0
  439. package/dist/services/run-log-store.d.ts.map +1 -0
  440. package/dist/services/run-log-store.js +109 -0
  441. package/dist/services/run-log-store.js.map +1 -0
  442. package/dist/services/secrets.d.ts +510 -0
  443. package/dist/services/secrets.d.ts.map +1 -0
  444. package/dist/services/secrets.js +288 -0
  445. package/dist/services/secrets.js.map +1 -0
  446. package/dist/services/sidebar-badges.d.ts +9 -0
  447. package/dist/services/sidebar-badges.d.ts.map +1 -0
  448. package/dist/services/sidebar-badges.js +33 -0
  449. package/dist/services/sidebar-badges.js.map +1 -0
  450. package/dist/services/work-products.d.ts +14 -0
  451. package/dist/services/work-products.d.ts.map +1 -0
  452. package/dist/services/work-products.js +100 -0
  453. package/dist/services/work-products.js.map +1 -0
  454. package/dist/services/workspace-operation-log-store.d.ts +33 -0
  455. package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
  456. package/dist/services/workspace-operation-log-store.js +110 -0
  457. package/dist/services/workspace-operation-log-store.js.map +1 -0
  458. package/dist/services/workspace-operations.d.ts +44 -0
  459. package/dist/services/workspace-operations.d.ts.map +1 -0
  460. package/dist/services/workspace-operations.js +204 -0
  461. package/dist/services/workspace-operations.js.map +1 -0
  462. package/dist/services/workspace-runtime.d.ts +164 -0
  463. package/dist/services/workspace-runtime.d.ts.map +1 -0
  464. package/dist/services/workspace-runtime.js +1235 -0
  465. package/dist/services/workspace-runtime.js.map +1 -0
  466. package/dist/startup-banner.d.ts +31 -0
  467. package/dist/startup-banner.d.ts.map +1 -0
  468. package/dist/startup-banner.js +117 -0
  469. package/dist/startup-banner.js.map +1 -0
  470. package/dist/storage/index.d.ts +6 -0
  471. package/dist/storage/index.d.ts.map +1 -0
  472. package/dist/storage/index.js +29 -0
  473. package/dist/storage/index.js.map +1 -0
  474. package/dist/storage/local-disk-provider.d.ts +3 -0
  475. package/dist/storage/local-disk-provider.d.ts.map +1 -0
  476. package/dist/storage/local-disk-provider.js +79 -0
  477. package/dist/storage/local-disk-provider.js.map +1 -0
  478. package/dist/storage/provider-registry.d.ts +4 -0
  479. package/dist/storage/provider-registry.d.ts.map +1 -0
  480. package/dist/storage/provider-registry.js +15 -0
  481. package/dist/storage/provider-registry.js.map +1 -0
  482. package/dist/storage/s3-provider.d.ts +11 -0
  483. package/dist/storage/s3-provider.d.ts.map +1 -0
  484. package/dist/storage/s3-provider.js +123 -0
  485. package/dist/storage/s3-provider.js.map +1 -0
  486. package/dist/storage/service.d.ts +3 -0
  487. package/dist/storage/service.d.ts.map +1 -0
  488. package/dist/storage/service.js +120 -0
  489. package/dist/storage/service.js.map +1 -0
  490. package/dist/storage/types.d.ts +55 -0
  491. package/dist/storage/types.d.ts.map +1 -0
  492. package/dist/storage/types.js +2 -0
  493. package/dist/storage/types.js.map +1 -0
  494. package/dist/ui-branding.d.ts +13 -0
  495. package/dist/ui-branding.d.ts.map +1 -0
  496. package/dist/ui-branding.js +187 -0
  497. package/dist/ui-branding.js.map +1 -0
  498. package/dist/version.d.ts +2 -0
  499. package/dist/version.d.ts.map +1 -0
  500. package/dist/version.js +5 -0
  501. package/dist/version.js.map +1 -0
  502. package/dist/wallet/connie-wallet.d.ts +8 -0
  503. package/dist/wallet/connie-wallet.d.ts.map +1 -0
  504. package/dist/wallet/connie-wallet.js +28 -0
  505. package/dist/wallet/connie-wallet.js.map +1 -0
  506. package/dist/wallet/signer-service.d.ts +46 -0
  507. package/dist/wallet/signer-service.d.ts.map +1 -0
  508. package/dist/wallet/signer-service.js +51 -0
  509. package/dist/wallet/signer-service.js.map +1 -0
  510. package/package.json +89 -0
  511. package/skills/paperclip/SKILL.md +310 -0
  512. package/skills/paperclip/references/api-reference.md +561 -0
  513. package/skills/paperclip-create-agent/SKILL.md +139 -0
  514. package/skills/paperclip-create-agent/references/api-reference.md +95 -0
  515. package/skills/paperclip-create-plugin/SKILL.md +101 -0
  516. package/skills/para-memory-files/SKILL.md +104 -0
  517. package/skills/para-memory-files/references/schemas.md +35 -0
  518. package/ui-dist/android-chrome-192x192.png +0 -0
  519. package/ui-dist/android-chrome-512x512.png +0 -0
  520. package/ui-dist/apple-touch-icon.png +0 -0
  521. package/ui-dist/assets/_basePickBy-C3SapTV-.js +1 -0
  522. package/ui-dist/assets/_baseUniq-tAa_W7wz.js +1 -0
  523. package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
  524. package/ui-dist/assets/arc-1aKL9N1m.js +1 -0
  525. package/ui-dist/assets/architectureDiagram-VXUJARFQ-C-BWMHir.js +36 -0
  526. package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
  527. package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
  528. package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
  529. package/ui-dist/assets/blockDiagram-VD42YOAC-DiZzNyCp.js +122 -0
  530. package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  531. package/ui-dist/assets/c4Diagram-YG6GDRKO-BxkN6K8Z.js +10 -0
  532. package/ui-dist/assets/channel-g6sWbOF_.js +1 -0
  533. package/ui-dist/assets/chunk-4BX2VUAB-dEQLWXMP.js +1 -0
  534. package/ui-dist/assets/chunk-55IACEB6-DOlf6bGB.js +1 -0
  535. package/ui-dist/assets/chunk-B4BG7PRW-D82iJIh4.js +165 -0
  536. package/ui-dist/assets/chunk-DI55MBZ5-Dvu2BjZt.js +220 -0
  537. package/ui-dist/assets/chunk-FMBD7UC4-zLtBDqIV.js +15 -0
  538. package/ui-dist/assets/chunk-QN33PNHL-BsDTCIez.js +1 -0
  539. package/ui-dist/assets/chunk-QZHKN3VN-CVk0cJt0.js +1 -0
  540. package/ui-dist/assets/chunk-TZMSLE5B-BkF4gaW2.js +1 -0
  541. package/ui-dist/assets/classDiagram-2ON5EDUG-D_SGf4i1.js +1 -0
  542. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-D_SGf4i1.js +1 -0
  543. package/ui-dist/assets/clike-B9uivgTg.js +1 -0
  544. package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
  545. package/ui-dist/assets/clone-CvdGFEjL.js +1 -0
  546. package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
  547. package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
  548. package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
  549. package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
  550. package/ui-dist/assets/cose-bilkent-S5V4N54A-DhXJS1Wr.js +1 -0
  551. package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
  552. package/ui-dist/assets/css-BnMrqG3P.js +1 -0
  553. package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
  554. package/ui-dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  555. package/ui-dist/assets/d-pRatUO7H.js +1 -0
  556. package/ui-dist/assets/dagre-6UL2VRFP-BgSr9M5y.js +4 -0
  557. package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  558. package/ui-dist/assets/diagram-PSM6KHXK-RVw3ElKR.js +24 -0
  559. package/ui-dist/assets/diagram-QEK2KX5R-C3K5KNnR.js +43 -0
  560. package/ui-dist/assets/diagram-S2PKOQOG-L2G5bc-i.js +24 -0
  561. package/ui-dist/assets/diff-DbItnlRl.js +1 -0
  562. package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
  563. package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
  564. package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
  565. package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
  566. package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
  567. package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
  568. package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
  569. package/ui-dist/assets/erDiagram-Q2GNP2WA-B-fEopEW.js +60 -0
  570. package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
  571. package/ui-dist/assets/factor-kuTfRLto.js +1 -0
  572. package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
  573. package/ui-dist/assets/flowDiagram-NV44I4VS-B67TmiaU.js +162 -0
  574. package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
  575. package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
  576. package/ui-dist/assets/ganttDiagram-JELNMOA3-DyPUX6hg.js +267 -0
  577. package/ui-dist/assets/gas-Bneqetm1.js +1 -0
  578. package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
  579. package/ui-dist/assets/gitGraphDiagram-V2S2FVAM--OZSHgOs.js +65 -0
  580. package/ui-dist/assets/graph-BuZXpro8.js +1 -0
  581. package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
  582. package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
  583. package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
  584. package/ui-dist/assets/http-DBlCnlav.js +1 -0
  585. package/ui-dist/assets/idl-BEugSyMb.js +1 -0
  586. package/ui-dist/assets/index-1BDMTcZo.css +1 -0
  587. package/ui-dist/assets/index-5qKrPxpA.js +1 -0
  588. package/ui-dist/assets/index-64g9Tgj8.js +1 -0
  589. package/ui-dist/assets/index-B2BWcxI3.js +1 -0
  590. package/ui-dist/assets/index-BBnLF7s9.js +1 -0
  591. package/ui-dist/assets/index-BcGr-t2T.js +3 -0
  592. package/ui-dist/assets/index-BvFjgW1S.js +1 -0
  593. package/ui-dist/assets/index-Bwu5E0tU.js +1120 -0
  594. package/ui-dist/assets/index-CWdVcwY-.js +7 -0
  595. package/ui-dist/assets/index-CcZ3jKyu.js +1 -0
  596. package/ui-dist/assets/index-CftQkMtr.js +1 -0
  597. package/ui-dist/assets/index-CiuvxTr1.js +13 -0
  598. package/ui-dist/assets/index-CvJFaRY-.js +1 -0
  599. package/ui-dist/assets/index-D2Vi4KQ1.js +1 -0
  600. package/ui-dist/assets/index-D9ei6U88.js +6 -0
  601. package/ui-dist/assets/index-DJHKsR5g.js +1 -0
  602. package/ui-dist/assets/index-DSGN-qDr.js +1 -0
  603. package/ui-dist/assets/index-DVPlvZkH.js +1 -0
  604. package/ui-dist/assets/index-DowQVAaY.js +1 -0
  605. package/ui-dist/assets/index-Dr_PpBCY.js +1 -0
  606. package/ui-dist/assets/index-Ivk3iEUq.js +2 -0
  607. package/ui-dist/assets/index-OVXAhlMW.js +1 -0
  608. package/ui-dist/assets/index-ff_wE0-y.js +1 -0
  609. package/ui-dist/assets/index-tIsOsmOg.js +1 -0
  610. package/ui-dist/assets/infoDiagram-HS3SLOUP-CorcfaW7.js +2 -0
  611. package/ui-dist/assets/init-Gi6I4Gst.js +1 -0
  612. package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
  613. package/ui-dist/assets/journeyDiagram-XKPGCS4Q-D3K55-qo.js +139 -0
  614. package/ui-dist/assets/julia-DuME0IfC.js +1 -0
  615. package/ui-dist/assets/kanban-definition-3W4ZIXB7-47soc494.js +89 -0
  616. package/ui-dist/assets/katex-O9d3_IXG.js +261 -0
  617. package/ui-dist/assets/layout-mxhxSLBl.js +1 -0
  618. package/ui-dist/assets/linear-D5Y7uAaH.js +1 -0
  619. package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
  620. package/ui-dist/assets/lua-BgMRiT3U.js +1 -0
  621. package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
  622. package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
  623. package/ui-dist/assets/mermaid.core-7SNUCoBq.js +256 -0
  624. package/ui-dist/assets/mindmap-definition-VGOIOE7T-C37ba4sG.js +68 -0
  625. package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
  626. package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
  627. package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
  628. package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
  629. package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
  630. package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
  631. package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
  632. package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
  633. package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
  634. package/ui-dist/assets/ordinal-Cboi1Yqb.js +1 -0
  635. package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
  636. package/ui-dist/assets/pascal--L3eBynH.js +1 -0
  637. package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
  638. package/ui-dist/assets/pieDiagram-ADFJNKIX-D78e-tVE.js +30 -0
  639. package/ui-dist/assets/pig-CevX1Tat.js +1 -0
  640. package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
  641. package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
  642. package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
  643. package/ui-dist/assets/pug-DeIclll2.js +1 -0
  644. package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
  645. package/ui-dist/assets/python-BuPzkPfP.js +1 -0
  646. package/ui-dist/assets/q-pXgVlZs6.js +1 -0
  647. package/ui-dist/assets/quadrantDiagram-AYHSOK5B-CGERMcfi.js +7 -0
  648. package/ui-dist/assets/r-B6wPVr8A.js +1 -0
  649. package/ui-dist/assets/requirementDiagram-UZGBJVZJ-vZWAyhU1.js +64 -0
  650. package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
  651. package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
  652. package/ui-dist/assets/sankeyDiagram-TZEHDZUN-C51wCQX7.js +10 -0
  653. package/ui-dist/assets/sas-B4kiWyti.js +1 -0
  654. package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
  655. package/ui-dist/assets/sequenceDiagram-WL72ISMW-eCSbKUaE.js +145 -0
  656. package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
  657. package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
  658. package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
  659. package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
  660. package/ui-dist/assets/solr-DehyRSwq.js +1 -0
  661. package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
  662. package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  663. package/ui-dist/assets/sql-D0XecflT.js +1 -0
  664. package/ui-dist/assets/stateDiagram-FKZM4ZOC-H3lI51qU.js +1 -0
  665. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3--Q50auwn.js +1 -0
  666. package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
  667. package/ui-dist/assets/stylus-B533Al4x.js +1 -0
  668. package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
  669. package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
  670. package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
  671. package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  672. package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
  673. package/ui-dist/assets/timeline-definition-IT6M3QCI-CP9CNQLU.js +61 -0
  674. package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
  675. package/ui-dist/assets/treemap-GDKQZRPO-BfRhErz4.js +162 -0
  676. package/ui-dist/assets/troff-wAsdV37c.js +1 -0
  677. package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
  678. package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  679. package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
  680. package/ui-dist/assets/vb-CmGdzxic.js +1 -0
  681. package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
  682. package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
  683. package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
  684. package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
  685. package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
  686. package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
  687. package/ui-dist/assets/xychartDiagram-PRI3JC2R-BSy7gZ_Q.js +7 -0
  688. package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
  689. package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
  690. package/ui-dist/brands/opencode-logo-dark-square.svg +18 -0
  691. package/ui-dist/brands/opencode-logo-light-square.svg +18 -0
  692. package/ui-dist/favicon-16x16.png +0 -0
  693. package/ui-dist/favicon-32x32.png +0 -0
  694. package/ui-dist/favicon.ico +0 -0
  695. package/ui-dist/favicon.svg +9 -0
  696. package/ui-dist/index.html +48 -0
  697. package/ui-dist/site.webmanifest +30 -0
  698. package/ui-dist/sw.js +42 -0
  699. package/ui-dist/worktree-favicon-16x16.png +0 -0
  700. package/ui-dist/worktree-favicon-32x32.png +0 -0
  701. package/ui-dist/worktree-favicon.ico +0 -0
  702. package/ui-dist/worktree-favicon.svg +9 -0
@@ -0,0 +1,2124 @@
1
+ import { createHash, generateKeyPairSync, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Router } from "express";
6
+ import { and, eq, isNull, desc } from "drizzle-orm";
7
+ import { agentApiKeys, authUsers, invites, joinRequests } from "@paperclipai_dld/db";
8
+ import { acceptInviteSchema, claimJoinRequestApiKeySchema, createCompanyInviteSchema, createOpenClawInvitePromptSchema, listJoinRequestsQuerySchema, updateMemberPermissionsSchema, updateUserCompanyAccessSchema, PERMISSION_KEYS } from "@paperclipai_dld/shared";
9
+ import { forbidden, conflict, notFound, unauthorized, badRequest } from "../errors.js";
10
+ import { logger } from "../middleware/logger.js";
11
+ import { validate } from "../middleware/validate.js";
12
+ import { accessService, agentService, deduplicateAgentName, logActivity, notifyHireApproved } from "../services/index.js";
13
+ import { assertCompanyAccess } from "./authz.js";
14
+ import { claimBoardOwnership, inspectBoardClaimChallenge } from "../board-claim.js";
15
+ function hashToken(token) {
16
+ return createHash("sha256").update(token).digest("hex");
17
+ }
18
+ const INVITE_TOKEN_PREFIX = "pcp_invite_";
19
+ const INVITE_TOKEN_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
20
+ const INVITE_TOKEN_SUFFIX_LENGTH = 8;
21
+ const INVITE_TOKEN_MAX_RETRIES = 5;
22
+ const COMPANY_INVITE_TTL_MS = 10 * 60 * 1000;
23
+ function createInviteToken() {
24
+ const bytes = randomBytes(INVITE_TOKEN_SUFFIX_LENGTH);
25
+ let suffix = "";
26
+ for (let idx = 0; idx < INVITE_TOKEN_SUFFIX_LENGTH; idx += 1) {
27
+ suffix += INVITE_TOKEN_ALPHABET[bytes[idx] % INVITE_TOKEN_ALPHABET.length];
28
+ }
29
+ return `${INVITE_TOKEN_PREFIX}${suffix}`;
30
+ }
31
+ function createClaimSecret() {
32
+ return `pcp_claim_${randomBytes(24).toString("hex")}`;
33
+ }
34
+ export function companyInviteExpiresAt(nowMs = Date.now()) {
35
+ return new Date(nowMs + COMPANY_INVITE_TTL_MS);
36
+ }
37
+ function tokenHashesMatch(left, right) {
38
+ const leftBytes = Buffer.from(left, "utf8");
39
+ const rightBytes = Buffer.from(right, "utf8");
40
+ return (leftBytes.length === rightBytes.length &&
41
+ timingSafeEqual(leftBytes, rightBytes));
42
+ }
43
+ function requestBaseUrl(req) {
44
+ const forwardedProto = req.header("x-forwarded-proto");
45
+ const proto = forwardedProto?.split(",")[0]?.trim() || req.protocol || "http";
46
+ const host = req.header("x-forwarded-host")?.split(",")[0]?.trim() || req.header("host");
47
+ if (!host)
48
+ return "";
49
+ return `${proto}://${host}`;
50
+ }
51
+ function readSkillMarkdown(skillName) {
52
+ const normalized = skillName.trim().toLowerCase();
53
+ if (normalized !== "paperclip" &&
54
+ normalized !== "paperclip-create-agent" &&
55
+ normalized !== "paperclip-create-plugin" &&
56
+ normalized !== "para-memory-files")
57
+ return null;
58
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
59
+ const candidates = [
60
+ path.resolve(moduleDir, "../../skills", normalized, "SKILL.md"), // published: dist/routes/ -> <pkg>/skills/
61
+ path.resolve(process.cwd(), "skills", normalized, "SKILL.md"), // cwd (e.g. monorepo root)
62
+ path.resolve(moduleDir, "../../../skills", normalized, "SKILL.md") // dev: src/routes/ -> repo root/skills/
63
+ ];
64
+ for (const skillPath of candidates) {
65
+ try {
66
+ return fs.readFileSync(skillPath, "utf8");
67
+ }
68
+ catch {
69
+ // Continue to next candidate.
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ /** Resolve the Paperclip repo skills directory (built-in / managed skills). */
75
+ function resolvePaperclipSkillsDir() {
76
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
77
+ const candidates = [
78
+ path.resolve(moduleDir, "../../skills"), // published
79
+ path.resolve(process.cwd(), "skills"), // cwd (monorepo root)
80
+ path.resolve(moduleDir, "../../../skills"), // dev
81
+ ];
82
+ for (const candidate of candidates) {
83
+ try {
84
+ if (fs.statSync(candidate).isDirectory())
85
+ return candidate;
86
+ }
87
+ catch { /* skip */ }
88
+ }
89
+ return null;
90
+ }
91
+ /** Parse YAML frontmatter from a SKILL.md file to extract the description. */
92
+ function parseSkillFrontmatter(markdown) {
93
+ const match = markdown.match(/^---\n([\s\S]*?)\n---/);
94
+ if (!match)
95
+ return { description: "" };
96
+ const yaml = match[1];
97
+ // Extract description — handles both single-line and multi-line YAML values
98
+ const descMatch = yaml.match(/^description:\s*(?:>\s*\n((?:\s{2,}[^\n]*\n?)+)|[|]\s*\n((?:\s{2,}[^\n]*\n?)+)|["']?(.*?)["']?\s*$)/m);
99
+ if (!descMatch)
100
+ return { description: "" };
101
+ const raw = descMatch[1] ?? descMatch[2] ?? descMatch[3] ?? "";
102
+ return {
103
+ description: raw
104
+ .split("\n")
105
+ .map((l) => l.trim())
106
+ .filter(Boolean)
107
+ .join(" ")
108
+ .trim(),
109
+ };
110
+ }
111
+ /** Discover all available Claude Code skills from ~/.claude/skills/. */
112
+ function listAvailableSkills() {
113
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
114
+ const claudeSkillsDir = path.join(homeDir, ".claude", "skills");
115
+ const paperclipSkillsDir = resolvePaperclipSkillsDir();
116
+ // Build set of Paperclip-managed skill names
117
+ const paperclipSkillNames = new Set();
118
+ if (paperclipSkillsDir) {
119
+ try {
120
+ for (const entry of fs.readdirSync(paperclipSkillsDir, { withFileTypes: true })) {
121
+ if (entry.isDirectory())
122
+ paperclipSkillNames.add(entry.name);
123
+ }
124
+ }
125
+ catch { /* skip */ }
126
+ }
127
+ const skills = [];
128
+ try {
129
+ const entries = fs.readdirSync(claudeSkillsDir, { withFileTypes: true });
130
+ for (const entry of entries) {
131
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
132
+ continue;
133
+ if (entry.name.startsWith("."))
134
+ continue;
135
+ const skillMdPath = path.join(claudeSkillsDir, entry.name, "SKILL.md");
136
+ let description = "";
137
+ try {
138
+ const md = fs.readFileSync(skillMdPath, "utf8");
139
+ description = parseSkillFrontmatter(md).description;
140
+ }
141
+ catch { /* no SKILL.md or unreadable */ }
142
+ skills.push({
143
+ name: entry.name,
144
+ description,
145
+ isPaperclipManaged: paperclipSkillNames.has(entry.name),
146
+ });
147
+ }
148
+ }
149
+ catch { /* ~/.claude/skills/ doesn't exist */ }
150
+ skills.sort((a, b) => a.name.localeCompare(b.name));
151
+ return skills;
152
+ }
153
+ function toJoinRequestResponse(row) {
154
+ const { claimSecretHash: _claimSecretHash, ...safe } = row;
155
+ return safe;
156
+ }
157
+ function isPlainObject(value) {
158
+ return typeof value === "object" && value !== null && !Array.isArray(value);
159
+ }
160
+ function isLoopbackHost(hostname) {
161
+ const value = hostname.trim().toLowerCase();
162
+ return value === "localhost" || value === "127.0.0.1" || value === "::1";
163
+ }
164
+ function normalizeHostname(value) {
165
+ if (!value)
166
+ return null;
167
+ const trimmed = value.trim();
168
+ if (!trimmed)
169
+ return null;
170
+ if (trimmed.startsWith("[")) {
171
+ const end = trimmed.indexOf("]");
172
+ return end > 1
173
+ ? trimmed.slice(1, end).toLowerCase()
174
+ : trimmed.toLowerCase();
175
+ }
176
+ const firstColon = trimmed.indexOf(":");
177
+ if (firstColon > -1)
178
+ return trimmed.slice(0, firstColon).toLowerCase();
179
+ return trimmed.toLowerCase();
180
+ }
181
+ function normalizeHeaderValue(value, depth = 0) {
182
+ const direct = nonEmptyTrimmedString(value);
183
+ if (direct)
184
+ return direct;
185
+ if (!isPlainObject(value) || depth >= 3)
186
+ return null;
187
+ const candidateKeys = [
188
+ "value",
189
+ "token",
190
+ "secret",
191
+ "apiKey",
192
+ "api_key",
193
+ "auth",
194
+ "authToken",
195
+ "auth_token",
196
+ "accessToken",
197
+ "access_token",
198
+ "authorization",
199
+ "bearer",
200
+ "header",
201
+ "raw",
202
+ "text",
203
+ "string"
204
+ ];
205
+ for (const key of candidateKeys) {
206
+ if (!Object.prototype.hasOwnProperty.call(value, key))
207
+ continue;
208
+ const normalized = normalizeHeaderValue(value[key], depth + 1);
209
+ if (normalized)
210
+ return normalized;
211
+ }
212
+ const entries = Object.entries(value);
213
+ if (entries.length === 1) {
214
+ const [singleKey, singleValue] = entries[0];
215
+ const normalizedKey = singleKey.trim().toLowerCase();
216
+ if (normalizedKey !== "type" &&
217
+ normalizedKey !== "version" &&
218
+ normalizedKey !== "secretid" &&
219
+ normalizedKey !== "secret_id") {
220
+ const normalized = normalizeHeaderValue(singleValue, depth + 1);
221
+ if (normalized)
222
+ return normalized;
223
+ }
224
+ }
225
+ return null;
226
+ }
227
+ function extractHeaderEntries(input) {
228
+ if (isPlainObject(input)) {
229
+ return Object.entries(input);
230
+ }
231
+ if (!Array.isArray(input)) {
232
+ return [];
233
+ }
234
+ const entries = [];
235
+ for (const item of input) {
236
+ if (Array.isArray(item)) {
237
+ const key = nonEmptyTrimmedString(item[0]);
238
+ if (!key)
239
+ continue;
240
+ entries.push([key, item[1]]);
241
+ continue;
242
+ }
243
+ if (!isPlainObject(item))
244
+ continue;
245
+ const mapped = item;
246
+ const explicitKey = nonEmptyTrimmedString(mapped.key) ??
247
+ nonEmptyTrimmedString(mapped.name) ??
248
+ nonEmptyTrimmedString(mapped.header);
249
+ if (explicitKey) {
250
+ const explicitValue = Object.prototype.hasOwnProperty.call(mapped, "value")
251
+ ? mapped.value
252
+ : Object.prototype.hasOwnProperty.call(mapped, "token")
253
+ ? mapped.token
254
+ : Object.prototype.hasOwnProperty.call(mapped, "secret")
255
+ ? mapped.secret
256
+ : mapped;
257
+ entries.push([explicitKey, explicitValue]);
258
+ continue;
259
+ }
260
+ const singleEntry = Object.entries(mapped);
261
+ if (singleEntry.length === 1) {
262
+ entries.push(singleEntry[0]);
263
+ }
264
+ }
265
+ return entries;
266
+ }
267
+ function normalizeHeaderMap(input) {
268
+ const entries = extractHeaderEntries(input);
269
+ if (entries.length === 0)
270
+ return undefined;
271
+ const out = {};
272
+ for (const [key, value] of entries) {
273
+ const normalizedValue = normalizeHeaderValue(value);
274
+ if (!normalizedValue)
275
+ continue;
276
+ const trimmedKey = key.trim();
277
+ const trimmedValue = normalizedValue.trim();
278
+ if (!trimmedKey || !trimmedValue)
279
+ continue;
280
+ out[trimmedKey] = trimmedValue;
281
+ }
282
+ return Object.keys(out).length > 0 ? out : undefined;
283
+ }
284
+ function nonEmptyTrimmedString(value) {
285
+ if (typeof value !== "string")
286
+ return null;
287
+ const trimmed = value.trim();
288
+ return trimmed.length > 0 ? trimmed : null;
289
+ }
290
+ function headerMapHasKeyIgnoreCase(headers, targetKey) {
291
+ const normalizedTarget = targetKey.trim().toLowerCase();
292
+ return Object.keys(headers).some((key) => key.trim().toLowerCase() === normalizedTarget);
293
+ }
294
+ function headerMapGetIgnoreCase(headers, targetKey) {
295
+ const normalizedTarget = targetKey.trim().toLowerCase();
296
+ const key = Object.keys(headers).find((candidate) => candidate.trim().toLowerCase() === normalizedTarget);
297
+ if (!key)
298
+ return null;
299
+ const value = headers[key];
300
+ return typeof value === "string" ? value : null;
301
+ }
302
+ function tokenFromAuthorizationHeader(rawHeader) {
303
+ const trimmed = nonEmptyTrimmedString(rawHeader);
304
+ if (!trimmed)
305
+ return null;
306
+ const bearerMatch = trimmed.match(/^bearer\s+(.+)$/i);
307
+ if (bearerMatch?.[1]) {
308
+ return nonEmptyTrimmedString(bearerMatch[1]);
309
+ }
310
+ return trimmed;
311
+ }
312
+ function parseBooleanLike(value) {
313
+ if (typeof value === "boolean")
314
+ return value;
315
+ if (typeof value !== "string")
316
+ return null;
317
+ const normalized = value.trim().toLowerCase();
318
+ if (normalized === "true" || normalized === "1")
319
+ return true;
320
+ if (normalized === "false" || normalized === "0")
321
+ return false;
322
+ return null;
323
+ }
324
+ function generateEd25519PrivateKeyPem() {
325
+ const generated = generateKeyPairSync("ed25519");
326
+ return generated.privateKey
327
+ .export({ type: "pkcs8", format: "pem" })
328
+ .toString();
329
+ }
330
+ export function buildJoinDefaultsPayloadForAccept(input) {
331
+ if (input.adapterType !== "openclaw_gateway") {
332
+ return input.defaultsPayload;
333
+ }
334
+ const merged = isPlainObject(input.defaultsPayload)
335
+ ? { ...input.defaultsPayload }
336
+ : {};
337
+ if (!nonEmptyTrimmedString(merged.paperclipApiUrl)) {
338
+ const legacyPaperclipApiUrl = nonEmptyTrimmedString(input.paperclipApiUrl);
339
+ if (legacyPaperclipApiUrl)
340
+ merged.paperclipApiUrl = legacyPaperclipApiUrl;
341
+ }
342
+ const mergedHeaders = normalizeHeaderMap(merged.headers) ?? {};
343
+ const inboundOpenClawAuthHeader = nonEmptyTrimmedString(input.inboundOpenClawAuthHeader);
344
+ const inboundOpenClawTokenHeader = nonEmptyTrimmedString(input.inboundOpenClawTokenHeader);
345
+ if (inboundOpenClawTokenHeader &&
346
+ !headerMapHasKeyIgnoreCase(mergedHeaders, "x-openclaw-token")) {
347
+ mergedHeaders["x-openclaw-token"] = inboundOpenClawTokenHeader;
348
+ }
349
+ if (inboundOpenClawAuthHeader &&
350
+ !headerMapHasKeyIgnoreCase(mergedHeaders, "x-openclaw-auth")) {
351
+ mergedHeaders["x-openclaw-auth"] = inboundOpenClawAuthHeader;
352
+ }
353
+ if (Object.keys(mergedHeaders).length > 0) {
354
+ merged.headers = mergedHeaders;
355
+ }
356
+ else {
357
+ delete merged.headers;
358
+ }
359
+ const discoveredToken = headerMapGetIgnoreCase(mergedHeaders, "x-openclaw-token") ??
360
+ headerMapGetIgnoreCase(mergedHeaders, "x-openclaw-auth") ??
361
+ tokenFromAuthorizationHeader(headerMapGetIgnoreCase(mergedHeaders, "authorization"));
362
+ if (discoveredToken &&
363
+ !headerMapHasKeyIgnoreCase(mergedHeaders, "x-openclaw-token")) {
364
+ mergedHeaders["x-openclaw-token"] = discoveredToken;
365
+ }
366
+ return Object.keys(merged).length > 0 ? merged : null;
367
+ }
368
+ export function mergeJoinDefaultsPayloadForReplay(existingDefaultsPayload, nextDefaultsPayload) {
369
+ if (!isPlainObject(existingDefaultsPayload) &&
370
+ !isPlainObject(nextDefaultsPayload)) {
371
+ return nextDefaultsPayload ?? existingDefaultsPayload;
372
+ }
373
+ if (!isPlainObject(existingDefaultsPayload)) {
374
+ return nextDefaultsPayload;
375
+ }
376
+ if (!isPlainObject(nextDefaultsPayload)) {
377
+ return existingDefaultsPayload;
378
+ }
379
+ const merged = {
380
+ ...existingDefaultsPayload,
381
+ ...nextDefaultsPayload
382
+ };
383
+ const existingHeaders = normalizeHeaderMap(existingDefaultsPayload.headers);
384
+ const nextHeaders = normalizeHeaderMap(nextDefaultsPayload.headers);
385
+ if (existingHeaders || nextHeaders) {
386
+ merged.headers = {
387
+ ...(existingHeaders ?? {}),
388
+ ...(nextHeaders ?? {})
389
+ };
390
+ }
391
+ else if (Object.prototype.hasOwnProperty.call(merged, "headers")) {
392
+ delete merged.headers;
393
+ }
394
+ return merged;
395
+ }
396
+ export function canReplayOpenClawGatewayInviteAccept(input) {
397
+ if (input.requestType !== "agent" ||
398
+ input.adapterType !== "openclaw_gateway") {
399
+ return false;
400
+ }
401
+ if (!input.existingJoinRequest) {
402
+ return false;
403
+ }
404
+ if (input.existingJoinRequest.requestType !== "agent" ||
405
+ input.existingJoinRequest.adapterType !== "openclaw_gateway") {
406
+ return false;
407
+ }
408
+ return (input.existingJoinRequest.status === "pending_approval" ||
409
+ input.existingJoinRequest.status === "approved");
410
+ }
411
+ function summarizeSecretForLog(value) {
412
+ const trimmed = nonEmptyTrimmedString(value);
413
+ if (!trimmed)
414
+ return null;
415
+ return {
416
+ present: true,
417
+ length: trimmed.length,
418
+ sha256Prefix: hashToken(trimmed).slice(0, 12)
419
+ };
420
+ }
421
+ function summarizeOpenClawGatewayDefaultsForLog(defaultsPayload) {
422
+ const defaults = isPlainObject(defaultsPayload)
423
+ ? defaultsPayload
424
+ : null;
425
+ const headers = defaults ? normalizeHeaderMap(defaults.headers) : undefined;
426
+ const gatewayTokenValue = headers
427
+ ? headerMapGetIgnoreCase(headers, "x-openclaw-token") ??
428
+ headerMapGetIgnoreCase(headers, "x-openclaw-auth") ??
429
+ tokenFromAuthorizationHeader(headerMapGetIgnoreCase(headers, "authorization"))
430
+ : null;
431
+ return {
432
+ present: Boolean(defaults),
433
+ keys: defaults ? Object.keys(defaults).sort() : [],
434
+ url: defaults ? nonEmptyTrimmedString(defaults.url) : null,
435
+ paperclipApiUrl: defaults
436
+ ? nonEmptyTrimmedString(defaults.paperclipApiUrl)
437
+ : null,
438
+ headerKeys: headers ? Object.keys(headers).sort() : [],
439
+ sessionKeyStrategy: defaults
440
+ ? nonEmptyTrimmedString(defaults.sessionKeyStrategy)
441
+ : null,
442
+ disableDeviceAuth: defaults
443
+ ? parseBooleanLike(defaults.disableDeviceAuth)
444
+ : null,
445
+ waitTimeoutMs: defaults && typeof defaults.waitTimeoutMs === "number"
446
+ ? defaults.waitTimeoutMs
447
+ : null,
448
+ devicePrivateKeyPem: defaults
449
+ ? summarizeSecretForLog(defaults.devicePrivateKeyPem)
450
+ : null,
451
+ gatewayToken: summarizeSecretForLog(gatewayTokenValue)
452
+ };
453
+ }
454
+ export function normalizeAgentDefaultsForJoin(input) {
455
+ const fatalErrors = [];
456
+ const diagnostics = [];
457
+ if (input.adapterType !== "openclaw_gateway") {
458
+ const normalized = isPlainObject(input.defaultsPayload)
459
+ ? input.defaultsPayload
460
+ : null;
461
+ return { normalized, diagnostics, fatalErrors };
462
+ }
463
+ if (!isPlainObject(input.defaultsPayload)) {
464
+ diagnostics.push({
465
+ code: "openclaw_gateway_defaults_missing",
466
+ level: "warn",
467
+ message: "No OpenClaw gateway config was provided in agentDefaultsPayload.",
468
+ hint: "Include agentDefaultsPayload.url and headers.x-openclaw-token for OpenClaw gateway joins."
469
+ });
470
+ fatalErrors.push("agentDefaultsPayload is required for adapterType=openclaw_gateway");
471
+ return {
472
+ normalized: null,
473
+ diagnostics,
474
+ fatalErrors
475
+ };
476
+ }
477
+ const defaults = input.defaultsPayload;
478
+ const normalized = {};
479
+ let gatewayUrl = null;
480
+ const rawGatewayUrl = nonEmptyTrimmedString(defaults.url);
481
+ if (!rawGatewayUrl) {
482
+ diagnostics.push({
483
+ code: "openclaw_gateway_url_missing",
484
+ level: "warn",
485
+ message: "OpenClaw gateway URL is missing.",
486
+ hint: "Set agentDefaultsPayload.url to ws:// or wss:// gateway URL."
487
+ });
488
+ fatalErrors.push("agentDefaultsPayload.url is required");
489
+ }
490
+ else {
491
+ try {
492
+ gatewayUrl = new URL(rawGatewayUrl);
493
+ if (gatewayUrl.protocol !== "ws:" && gatewayUrl.protocol !== "wss:") {
494
+ diagnostics.push({
495
+ code: "openclaw_gateway_url_protocol",
496
+ level: "warn",
497
+ message: `OpenClaw gateway URL must use ws:// or wss:// (got ${gatewayUrl.protocol}).`
498
+ });
499
+ fatalErrors.push("agentDefaultsPayload.url must use ws:// or wss:// for openclaw_gateway");
500
+ }
501
+ else {
502
+ normalized.url = gatewayUrl.toString();
503
+ diagnostics.push({
504
+ code: "openclaw_gateway_url_configured",
505
+ level: "info",
506
+ message: `Gateway endpoint set to ${gatewayUrl.toString()}`
507
+ });
508
+ }
509
+ }
510
+ catch {
511
+ diagnostics.push({
512
+ code: "openclaw_gateway_url_invalid",
513
+ level: "warn",
514
+ message: `Invalid OpenClaw gateway URL: ${rawGatewayUrl}`
515
+ });
516
+ fatalErrors.push("agentDefaultsPayload.url is not a valid URL");
517
+ }
518
+ }
519
+ const headers = normalizeHeaderMap(defaults.headers) ?? {};
520
+ const gatewayToken = headerMapGetIgnoreCase(headers, "x-openclaw-token") ??
521
+ headerMapGetIgnoreCase(headers, "x-openclaw-auth") ??
522
+ tokenFromAuthorizationHeader(headerMapGetIgnoreCase(headers, "authorization"));
523
+ if (gatewayToken && !headerMapHasKeyIgnoreCase(headers, "x-openclaw-token")) {
524
+ headers["x-openclaw-token"] = gatewayToken;
525
+ }
526
+ if (Object.keys(headers).length > 0) {
527
+ normalized.headers = headers;
528
+ }
529
+ if (!gatewayToken) {
530
+ diagnostics.push({
531
+ code: "openclaw_gateway_auth_header_missing",
532
+ level: "warn",
533
+ message: "Gateway auth token is missing from agent defaults.",
534
+ hint: "Set agentDefaultsPayload.headers.x-openclaw-token (or legacy x-openclaw-auth)."
535
+ });
536
+ fatalErrors.push("agentDefaultsPayload.headers.x-openclaw-token (or x-openclaw-auth) is required");
537
+ }
538
+ else if (gatewayToken.trim().length < 16) {
539
+ diagnostics.push({
540
+ code: "openclaw_gateway_auth_header_too_short",
541
+ level: "warn",
542
+ message: `Gateway auth token appears too short (${gatewayToken.trim().length} chars).`,
543
+ hint: "Use the full gateway auth token from ~/.openclaw/openclaw.json (typically long random string)."
544
+ });
545
+ fatalErrors.push("agentDefaultsPayload.headers.x-openclaw-token is too short; expected a full gateway token");
546
+ }
547
+ else {
548
+ diagnostics.push({
549
+ code: "openclaw_gateway_auth_header_configured",
550
+ level: "info",
551
+ message: "Gateway auth token configured."
552
+ });
553
+ }
554
+ if (isPlainObject(defaults.payloadTemplate)) {
555
+ normalized.payloadTemplate = defaults.payloadTemplate;
556
+ }
557
+ const parsedDisableDeviceAuth = parseBooleanLike(defaults.disableDeviceAuth);
558
+ const disableDeviceAuth = parsedDisableDeviceAuth === true;
559
+ if (parsedDisableDeviceAuth !== null) {
560
+ normalized.disableDeviceAuth = parsedDisableDeviceAuth;
561
+ }
562
+ const configuredDevicePrivateKeyPem = nonEmptyTrimmedString(defaults.devicePrivateKeyPem);
563
+ if (configuredDevicePrivateKeyPem) {
564
+ normalized.devicePrivateKeyPem = configuredDevicePrivateKeyPem;
565
+ diagnostics.push({
566
+ code: "openclaw_gateway_device_key_configured",
567
+ level: "info",
568
+ message: "Gateway device key configured. Pairing approvals should persist for this agent."
569
+ });
570
+ }
571
+ else if (!disableDeviceAuth) {
572
+ try {
573
+ normalized.devicePrivateKeyPem = generateEd25519PrivateKeyPem();
574
+ diagnostics.push({
575
+ code: "openclaw_gateway_device_key_generated",
576
+ level: "info",
577
+ message: "Generated persistent gateway device key for this join. Pairing approvals should persist for this agent."
578
+ });
579
+ }
580
+ catch (err) {
581
+ diagnostics.push({
582
+ code: "openclaw_gateway_device_key_generate_failed",
583
+ level: "warn",
584
+ message: `Failed to generate gateway device key: ${err instanceof Error ? err.message : String(err)}`,
585
+ hint: "Set agentDefaultsPayload.devicePrivateKeyPem explicitly or set disableDeviceAuth=true."
586
+ });
587
+ fatalErrors.push("Failed to generate gateway device key. Set devicePrivateKeyPem or disableDeviceAuth=true.");
588
+ }
589
+ }
590
+ const waitTimeoutMs = typeof defaults.waitTimeoutMs === "number" &&
591
+ Number.isFinite(defaults.waitTimeoutMs)
592
+ ? Math.floor(defaults.waitTimeoutMs)
593
+ : typeof defaults.waitTimeoutMs === "string"
594
+ ? Number.parseInt(defaults.waitTimeoutMs.trim(), 10)
595
+ : NaN;
596
+ if (Number.isFinite(waitTimeoutMs) && waitTimeoutMs > 0) {
597
+ normalized.waitTimeoutMs = waitTimeoutMs;
598
+ }
599
+ const timeoutSec = typeof defaults.timeoutSec === "number" && Number.isFinite(defaults.timeoutSec)
600
+ ? Math.floor(defaults.timeoutSec)
601
+ : typeof defaults.timeoutSec === "string"
602
+ ? Number.parseInt(defaults.timeoutSec.trim(), 10)
603
+ : NaN;
604
+ if (Number.isFinite(timeoutSec) && timeoutSec > 0) {
605
+ normalized.timeoutSec = timeoutSec;
606
+ }
607
+ const sessionKeyStrategy = nonEmptyTrimmedString(defaults.sessionKeyStrategy);
608
+ if (sessionKeyStrategy === "fixed" ||
609
+ sessionKeyStrategy === "issue" ||
610
+ sessionKeyStrategy === "run") {
611
+ normalized.sessionKeyStrategy = sessionKeyStrategy;
612
+ }
613
+ const sessionKey = nonEmptyTrimmedString(defaults.sessionKey);
614
+ if (sessionKey) {
615
+ normalized.sessionKey = sessionKey;
616
+ }
617
+ const role = nonEmptyTrimmedString(defaults.role);
618
+ if (role) {
619
+ normalized.role = role;
620
+ }
621
+ if (Array.isArray(defaults.scopes)) {
622
+ const scopes = defaults.scopes
623
+ .filter((entry) => typeof entry === "string")
624
+ .map((entry) => entry.trim())
625
+ .filter(Boolean);
626
+ if (scopes.length > 0) {
627
+ normalized.scopes = scopes;
628
+ }
629
+ }
630
+ const rawPaperclipApiUrl = typeof defaults.paperclipApiUrl === "string"
631
+ ? defaults.paperclipApiUrl.trim()
632
+ : "";
633
+ if (rawPaperclipApiUrl) {
634
+ try {
635
+ const parsedPaperclipApiUrl = new URL(rawPaperclipApiUrl);
636
+ if (parsedPaperclipApiUrl.protocol !== "http:" &&
637
+ parsedPaperclipApiUrl.protocol !== "https:") {
638
+ diagnostics.push({
639
+ code: "openclaw_gateway_paperclip_api_url_protocol",
640
+ level: "warn",
641
+ message: `paperclipApiUrl must use http:// or https:// (got ${parsedPaperclipApiUrl.protocol}).`
642
+ });
643
+ }
644
+ else {
645
+ normalized.paperclipApiUrl = parsedPaperclipApiUrl.toString();
646
+ diagnostics.push({
647
+ code: "openclaw_gateway_paperclip_api_url_configured",
648
+ level: "info",
649
+ message: `paperclipApiUrl set to ${parsedPaperclipApiUrl.toString()}`
650
+ });
651
+ }
652
+ }
653
+ catch {
654
+ diagnostics.push({
655
+ code: "openclaw_gateway_paperclip_api_url_invalid",
656
+ level: "warn",
657
+ message: `Invalid paperclipApiUrl: ${rawPaperclipApiUrl}`
658
+ });
659
+ }
660
+ }
661
+ return { normalized, diagnostics, fatalErrors };
662
+ }
663
+ function toInviteSummaryResponse(req, token, invite) {
664
+ const baseUrl = requestBaseUrl(req);
665
+ const onboardingPath = `/api/invites/${token}/onboarding`;
666
+ const onboardingTextPath = `/api/invites/${token}/onboarding.txt`;
667
+ const inviteMessage = extractInviteMessage(invite);
668
+ return {
669
+ id: invite.id,
670
+ companyId: invite.companyId,
671
+ inviteType: invite.inviteType,
672
+ allowedJoinTypes: invite.allowedJoinTypes,
673
+ expiresAt: invite.expiresAt,
674
+ onboardingPath,
675
+ onboardingUrl: baseUrl ? `${baseUrl}${onboardingPath}` : onboardingPath,
676
+ onboardingTextPath,
677
+ onboardingTextUrl: baseUrl
678
+ ? `${baseUrl}${onboardingTextPath}`
679
+ : onboardingTextPath,
680
+ skillIndexPath: "/api/skills/index",
681
+ skillIndexUrl: baseUrl
682
+ ? `${baseUrl}/api/skills/index`
683
+ : "/api/skills/index",
684
+ inviteMessage
685
+ };
686
+ }
687
+ function buildOnboardingDiscoveryDiagnostics(input) {
688
+ const diagnostics = [];
689
+ let apiHost = null;
690
+ if (input.apiBaseUrl) {
691
+ try {
692
+ apiHost = normalizeHostname(new URL(input.apiBaseUrl).hostname);
693
+ }
694
+ catch {
695
+ apiHost = null;
696
+ }
697
+ }
698
+ const bindHost = normalizeHostname(input.bindHost);
699
+ const allowSet = new Set(input.allowedHostnames
700
+ .map((entry) => normalizeHostname(entry))
701
+ .filter((entry) => Boolean(entry)));
702
+ if (apiHost && isLoopbackHost(apiHost)) {
703
+ diagnostics.push({
704
+ code: "openclaw_onboarding_api_loopback",
705
+ level: "warn",
706
+ message: "Onboarding URL resolves to loopback hostname. Remote OpenClaw agents cannot reach localhost on your Paperclip host.",
707
+ hint: "Use a reachable hostname/IP (for example Tailscale hostname, Docker host alias, or public domain)."
708
+ });
709
+ }
710
+ if (input.deploymentMode === "authenticated" &&
711
+ input.deploymentExposure === "private" &&
712
+ (!bindHost || isLoopbackHost(bindHost))) {
713
+ diagnostics.push({
714
+ code: "openclaw_onboarding_private_loopback_bind",
715
+ level: "warn",
716
+ message: "Paperclip is bound to loopback in authenticated/private mode.",
717
+ hint: "Run with a reachable bind host or use pnpm dev --tailscale-auth for private-network onboarding."
718
+ });
719
+ }
720
+ if (input.deploymentMode === "authenticated" &&
721
+ input.deploymentExposure === "private" &&
722
+ apiHost &&
723
+ !isLoopbackHost(apiHost) &&
724
+ allowSet.size > 0 &&
725
+ !allowSet.has(apiHost)) {
726
+ diagnostics.push({
727
+ code: "openclaw_onboarding_private_host_not_allowed",
728
+ level: "warn",
729
+ message: `Onboarding host "${apiHost}" is not in allowed hostnames for authenticated/private mode.`,
730
+ hint: `Run pnpm paperclipai allowed-hostname ${apiHost}`
731
+ });
732
+ }
733
+ return diagnostics;
734
+ }
735
+ function buildOnboardingConnectionCandidates(input) {
736
+ let base = null;
737
+ try {
738
+ if (input.apiBaseUrl) {
739
+ base = new URL(input.apiBaseUrl);
740
+ }
741
+ }
742
+ catch {
743
+ base = null;
744
+ }
745
+ const protocol = base?.protocol ?? "http:";
746
+ const port = base?.port ? `:${base.port}` : "";
747
+ const candidates = new Set();
748
+ if (base) {
749
+ candidates.add(base.origin);
750
+ }
751
+ const bindHost = normalizeHostname(input.bindHost);
752
+ if (bindHost && !isLoopbackHost(bindHost)) {
753
+ candidates.add(`${protocol}//${bindHost}${port}`);
754
+ }
755
+ for (const rawHost of input.allowedHostnames) {
756
+ const host = normalizeHostname(rawHost);
757
+ if (!host)
758
+ continue;
759
+ candidates.add(`${protocol}//${host}${port}`);
760
+ }
761
+ if (base && isLoopbackHost(base.hostname)) {
762
+ candidates.add(`${protocol}//host.docker.internal${port}`);
763
+ }
764
+ return Array.from(candidates);
765
+ }
766
+ function buildInviteOnboardingManifest(req, token, invite, opts) {
767
+ const baseUrl = requestBaseUrl(req);
768
+ const skillPath = "/api/skills/paperclip";
769
+ const skillUrl = baseUrl ? `${baseUrl}${skillPath}` : skillPath;
770
+ const registrationEndpointPath = `/api/invites/${token}/accept`;
771
+ const registrationEndpointUrl = baseUrl
772
+ ? `${baseUrl}${registrationEndpointPath}`
773
+ : registrationEndpointPath;
774
+ const onboardingTextPath = `/api/invites/${token}/onboarding.txt`;
775
+ const onboardingTextUrl = baseUrl
776
+ ? `${baseUrl}${onboardingTextPath}`
777
+ : onboardingTextPath;
778
+ const discoveryDiagnostics = buildOnboardingDiscoveryDiagnostics({
779
+ apiBaseUrl: baseUrl,
780
+ deploymentMode: opts.deploymentMode,
781
+ deploymentExposure: opts.deploymentExposure,
782
+ bindHost: opts.bindHost,
783
+ allowedHostnames: opts.allowedHostnames
784
+ });
785
+ const connectionCandidates = buildOnboardingConnectionCandidates({
786
+ apiBaseUrl: baseUrl,
787
+ bindHost: opts.bindHost,
788
+ allowedHostnames: opts.allowedHostnames
789
+ });
790
+ return {
791
+ invite: toInviteSummaryResponse(req, token, invite),
792
+ onboarding: {
793
+ 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/paperclip-claimed-api-key.json and load PAPERCLIP_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).",
794
+ inviteMessage: extractInviteMessage(invite),
795
+ recommendedAdapterType: "openclaw_gateway",
796
+ requiredFields: {
797
+ requestType: "agent",
798
+ agentName: "Display name for this agent",
799
+ adapterType: "Use 'openclaw_gateway' for OpenClaw Gateway agents",
800
+ capabilities: "Optional capability summary",
801
+ agentDefaultsPayload: "Adapter config for OpenClaw gateway. MUST include url (ws:// or wss://) and headers.x-openclaw-token (or legacy x-openclaw-auth). Optional fields: paperclipApiUrl, waitTimeoutMs, sessionKeyStrategy, sessionKey, role, scopes, disableDeviceAuth, devicePrivateKeyPem."
802
+ },
803
+ registrationEndpoint: {
804
+ method: "POST",
805
+ path: registrationEndpointPath,
806
+ url: registrationEndpointUrl
807
+ },
808
+ claimEndpointTemplate: {
809
+ method: "POST",
810
+ path: "/api/join-requests/{requestId}/claim-api-key",
811
+ body: {
812
+ claimSecret: "one-time claim secret returned when the join request is created"
813
+ }
814
+ },
815
+ connectivity: {
816
+ deploymentMode: opts.deploymentMode,
817
+ deploymentExposure: opts.deploymentExposure,
818
+ bindHost: opts.bindHost,
819
+ allowedHostnames: opts.allowedHostnames,
820
+ connectionCandidates,
821
+ diagnostics: discoveryDiagnostics,
822
+ guidance: opts.deploymentMode === "authenticated" &&
823
+ opts.deploymentExposure === "private"
824
+ ? "If OpenClaw runs on another machine, ensure the Paperclip hostname is reachable and allowed via `pnpm paperclipai allowed-hostname <host>`."
825
+ : "Ensure OpenClaw can reach this Paperclip API base URL for invite, claim, and skill bootstrap calls."
826
+ },
827
+ textInstructions: {
828
+ path: onboardingTextPath,
829
+ url: onboardingTextUrl,
830
+ contentType: "text/plain"
831
+ },
832
+ skill: {
833
+ name: "paperclip",
834
+ path: skillPath,
835
+ url: skillUrl,
836
+ installPath: "~/.openclaw/skills/paperclip/SKILL.md"
837
+ }
838
+ }
839
+ };
840
+ }
841
+ export function buildInviteOnboardingTextDocument(req, token, invite, opts) {
842
+ const manifest = buildInviteOnboardingManifest(req, token, invite, opts);
843
+ const onboarding = manifest.onboarding;
844
+ const diagnostics = Array.isArray(onboarding.connectivity?.diagnostics)
845
+ ? onboarding.connectivity.diagnostics
846
+ : [];
847
+ const lines = [];
848
+ const appendBlock = (block) => {
849
+ const trimmed = block.replace(/^\n/, "").replace(/\n\s*$/, "");
850
+ const lineIndentation = trimmed
851
+ .split("\n")
852
+ .filter((line) => line.trim().length > 0)
853
+ .map((line) => line.match(/^(\s*)/)?.[0].length ?? 0);
854
+ const minIndent = lineIndentation.length > 0 ? Math.min(...lineIndentation) : 0;
855
+ for (const line of trimmed.split("\n")) {
856
+ lines.push(line.slice(minIndent));
857
+ }
858
+ };
859
+ appendBlock(`
860
+ # Paperclip OpenClaw Gateway Onboarding
861
+
862
+ This document is meant to be readable by both humans and agents.
863
+
864
+ ## Invite
865
+ - inviteType: ${invite.inviteType}
866
+ - allowedJoinTypes: ${invite.allowedJoinTypes}
867
+ - expiresAt: ${invite.expiresAt.toISOString()}
868
+ `);
869
+ if (onboarding.inviteMessage) {
870
+ appendBlock(`
871
+ ## Message from inviter
872
+ ${onboarding.inviteMessage}
873
+ `);
874
+ }
875
+ appendBlock(`
876
+ ## Step 0
877
+
878
+ Get the OpenClaw gateway auth token (THIS MUST BE SENT)
879
+ Token lives in:
880
+
881
+ ~/.openclaw/openclaw.json -> gateway.auth.token
882
+ Extract:
883
+
884
+ TOKEN="$(node -p 'require(process.env.HOME+\"/.openclaw/openclaw.json\").gateway.auth.token')"
885
+ test -n "$TOKEN" || (echo "Missing TOKEN" && exit 1)
886
+ test "\${#TOKEN}" -ge 16 || (echo "Gateway token unexpectedly short (\${#TOKEN})" && exit 1)
887
+
888
+ 3) IMPORTANT: Don't accidentally drop the token when generating JSON
889
+ If you build JSON with Node, pass the token explicitly (argv), don't rely on an un-exported env var.
890
+
891
+ Safe payload build looks sort of like this (substitute where necessary):
892
+
893
+ BODY="$(node -e '
894
+ const token = process.argv[1];
895
+ if (!token) process.exit(2);
896
+ const body = {
897
+ requestType: "agent",
898
+ agentName: "OpenClaw",
899
+ adapterType: "openclaw_gateway",
900
+ capabilities: "OpenClaw agent adapter",
901
+ agentDefaultsPayload: {
902
+ url: "ws://127.0.0.1:18789",
903
+ paperclipApiUrl: "http://host.docker.internal:3100",
904
+ headers: { "x-openclaw-token": token },
905
+ waitTimeoutMs: 120000,
906
+ sessionKeyStrategy: "issue",
907
+ role: "operator",
908
+ scopes: ["operator.admin"]
909
+ }
910
+ };
911
+ process.stdout.write(JSON.stringify(body));
912
+ ' "$TOKEN")"
913
+
914
+ ## Step 1: Submit agent join request
915
+ ${onboarding.registrationEndpoint.method} ${onboarding.registrationEndpoint.url}
916
+
917
+ IMPORTANT: You MUST include agentDefaultsPayload.headers.x-openclaw-token with your gateway token.
918
+ Legacy x-openclaw-auth is also accepted, but x-openclaw-token is preferred.
919
+ Use adapterType "openclaw_gateway" and a ws:// or wss:// gateway URL.
920
+ Pairing mode requirement:
921
+ - Keep device auth enabled (recommended). If devicePrivateKeyPem is omitted, Paperclip generates and persists one during join so pairing approvals are stable.
922
+ - You may set disableDeviceAuth=true only for special environments that cannot support pairing.
923
+ - First run may return "pairing required" once; approve the pending pairing request in OpenClaw, then retry.
924
+ Do NOT use /v1/responses or /hooks/* in this gateway join flow.
925
+
926
+ Body (JSON):
927
+ {
928
+ "requestType": "agent",
929
+ "agentName": "My OpenClaw Agent",
930
+ "adapterType": "openclaw_gateway",
931
+ "capabilities": "Optional summary",
932
+ "agentDefaultsPayload": {
933
+ "url": "wss://your-openclaw-gateway.example",
934
+ "paperclipApiUrl": "https://paperclip-hostname-your-agent-can-reach:3100",
935
+ "headers": { "x-openclaw-token": "replace-me" },
936
+ "waitTimeoutMs": 120000,
937
+ "sessionKeyStrategy": "issue",
938
+ "role": "operator",
939
+ "scopes": ["operator.admin"]
940
+ }
941
+ }
942
+
943
+ Expected response includes:
944
+ - request id
945
+ - one-time claimSecret
946
+ - claimApiKeyPath
947
+
948
+ ## Step 2: Wait for board approval
949
+ The board approves the join request in Paperclip before key claim is allowed.
950
+
951
+ ## Step 3: Claim API key (one-time)
952
+ ${onboarding.claimEndpointTemplate.method} /api/join-requests/{requestId}/claim-api-key
953
+
954
+ Body (JSON):
955
+ {
956
+ "claimSecret": "<one-time-claim-secret>"
957
+ }
958
+
959
+ On successful claim, save the full JSON response to:
960
+
961
+ - ~/.openclaw/workspace/paperclip-claimed-api-key.json
962
+ chmod 600 ~/.openclaw/workspace/paperclip-claimed-api-key.json
963
+
964
+ And set the PAPERCLIP_API_KEY and PAPERCLIP_API_URL in your environment variables as specified here:
965
+ https://docs.openclaw.ai/help/environment
966
+
967
+ e.g.
968
+
969
+ {
970
+ env: {
971
+ PAPERCLIP_API_KEY: "...",
972
+ PAPERCLIP_API_URL: "...",
973
+ },
974
+ }
975
+
976
+ Then set PAPERCLIP_API_KEY and PAPERCLIP_API_URL from the saved token field for every heartbeat run.
977
+
978
+ Important:
979
+ - claim secrets expire
980
+ - claim secrets are single-use
981
+ - claim fails before board approval
982
+
983
+ ## Step 4: Install Paperclip skill in OpenClaw
984
+ GET ${onboarding.skill.url}
985
+ Install path: ${onboarding.skill.installPath}
986
+
987
+ Be sure to prepend your PAPERCLIP_API_URL to the top of your skill and note the path to your PAPERCLIP_API_URL
988
+
989
+ ## Text onboarding URL
990
+ ${onboarding.textInstructions.url}
991
+
992
+ ## Connectivity guidance
993
+ ${onboarding.connectivity?.guidance ??
994
+ "Ensure Paperclip is reachable from your OpenClaw runtime."}
995
+ `);
996
+ const connectionCandidates = Array.isArray(onboarding.connectivity?.connectionCandidates)
997
+ ? onboarding.connectivity.connectionCandidates.filter((entry) => Boolean(entry))
998
+ : [];
999
+ if (connectionCandidates.length > 0) {
1000
+ lines.push("## Suggested Paperclip base URLs to try");
1001
+ for (const candidate of connectionCandidates) {
1002
+ lines.push(`- ${candidate}`);
1003
+ }
1004
+ appendBlock(`
1005
+
1006
+ Test each candidate with:
1007
+ - GET <candidate>/api/health
1008
+ - set the first reachable candidate as agentDefaultsPayload.paperclipApiUrl when submitting your join request
1009
+
1010
+ If none are reachable: ask your human operator for a reachable hostname/address and help them update network configuration.
1011
+ For authenticated/private mode, they may need:
1012
+ - pnpm paperclipai allowed-hostname <host>
1013
+ - then restart Paperclip and retry onboarding.
1014
+ `);
1015
+ }
1016
+ if (diagnostics.length > 0) {
1017
+ lines.push("## Connectivity diagnostics");
1018
+ for (const diag of diagnostics) {
1019
+ lines.push(`- [${diag.level}] ${diag.message}`);
1020
+ if (diag.hint)
1021
+ lines.push(` hint: ${diag.hint}`);
1022
+ }
1023
+ }
1024
+ appendBlock(`
1025
+
1026
+ ## Helpful endpoints
1027
+ ${onboarding.registrationEndpoint.path}
1028
+ ${onboarding.claimEndpointTemplate.path}
1029
+ ${onboarding.skill.path}
1030
+ ${manifest.invite.onboardingPath}
1031
+ `);
1032
+ return `${lines.join("\n")}\n`;
1033
+ }
1034
+ function extractInviteMessage(invite) {
1035
+ const rawDefaults = invite.defaultsPayload;
1036
+ if (!rawDefaults ||
1037
+ typeof rawDefaults !== "object" ||
1038
+ Array.isArray(rawDefaults)) {
1039
+ return null;
1040
+ }
1041
+ const rawMessage = rawDefaults.agentMessage;
1042
+ if (typeof rawMessage !== "string") {
1043
+ return null;
1044
+ }
1045
+ const trimmed = rawMessage.trim();
1046
+ return trimmed.length ? trimmed : null;
1047
+ }
1048
+ function mergeInviteDefaults(defaultsPayload, agentMessage) {
1049
+ const merged = defaultsPayload && typeof defaultsPayload === "object"
1050
+ ? { ...defaultsPayload }
1051
+ : {};
1052
+ if (agentMessage) {
1053
+ merged.agentMessage = agentMessage;
1054
+ }
1055
+ return Object.keys(merged).length ? merged : null;
1056
+ }
1057
+ function requestIp(req) {
1058
+ const forwarded = req.header("x-forwarded-for");
1059
+ if (forwarded) {
1060
+ const first = forwarded.split(",")[0]?.trim();
1061
+ if (first)
1062
+ return first;
1063
+ }
1064
+ return req.ip || "unknown";
1065
+ }
1066
+ function inviteExpired(invite) {
1067
+ return invite.expiresAt.getTime() <= Date.now();
1068
+ }
1069
+ function isLocalImplicit(req) {
1070
+ return req.actor.type === "board" && req.actor.source === "local_implicit";
1071
+ }
1072
+ async function resolveActorEmail(db, req) {
1073
+ if (isLocalImplicit(req))
1074
+ return "local@paperclip.local";
1075
+ const userId = req.actor.userId;
1076
+ if (!userId)
1077
+ return null;
1078
+ const user = await db
1079
+ .select({ email: authUsers.email })
1080
+ .from(authUsers)
1081
+ .where(eq(authUsers.id, userId))
1082
+ .then((rows) => rows[0] ?? null);
1083
+ return user?.email ?? null;
1084
+ }
1085
+ function grantsFromDefaults(defaultsPayload, key) {
1086
+ if (!defaultsPayload || typeof defaultsPayload !== "object")
1087
+ return [];
1088
+ const scoped = defaultsPayload[key];
1089
+ if (!scoped || typeof scoped !== "object")
1090
+ return [];
1091
+ const grants = scoped.grants;
1092
+ if (!Array.isArray(grants))
1093
+ return [];
1094
+ const validPermissionKeys = new Set(PERMISSION_KEYS);
1095
+ const result = [];
1096
+ for (const item of grants) {
1097
+ if (!item || typeof item !== "object")
1098
+ continue;
1099
+ const record = item;
1100
+ if (typeof record.permissionKey !== "string")
1101
+ continue;
1102
+ if (!validPermissionKeys.has(record.permissionKey))
1103
+ continue;
1104
+ result.push({
1105
+ permissionKey: record.permissionKey,
1106
+ scope: record.scope &&
1107
+ typeof record.scope === "object" &&
1108
+ !Array.isArray(record.scope)
1109
+ ? record.scope
1110
+ : null
1111
+ });
1112
+ }
1113
+ return result;
1114
+ }
1115
+ export function resolveJoinRequestAgentManagerId(candidates) {
1116
+ const ceoCandidates = candidates.filter((candidate) => candidate.role === "ceo");
1117
+ if (ceoCandidates.length === 0)
1118
+ return null;
1119
+ const rootCeo = ceoCandidates.find((candidate) => candidate.reportsTo === null);
1120
+ return (rootCeo ?? ceoCandidates[0] ?? null)?.id ?? null;
1121
+ }
1122
+ function isInviteTokenHashCollisionError(error) {
1123
+ const candidates = [
1124
+ error,
1125
+ error?.cause ?? null
1126
+ ];
1127
+ for (const candidate of candidates) {
1128
+ if (!candidate || typeof candidate !== "object")
1129
+ continue;
1130
+ const code = "code" in candidate && typeof candidate.code === "string"
1131
+ ? candidate.code
1132
+ : null;
1133
+ const message = "message" in candidate && typeof candidate.message === "string"
1134
+ ? candidate.message
1135
+ : "";
1136
+ const constraint = "constraint" in candidate && typeof candidate.constraint === "string"
1137
+ ? candidate.constraint
1138
+ : null;
1139
+ if (code !== "23505")
1140
+ continue;
1141
+ if (constraint === "invites_token_hash_unique_idx")
1142
+ return true;
1143
+ if (message.includes("invites_token_hash_unique_idx"))
1144
+ return true;
1145
+ }
1146
+ return false;
1147
+ }
1148
+ function isAbortError(error) {
1149
+ return error instanceof Error && error.name === "AbortError";
1150
+ }
1151
+ async function probeInviteResolutionTarget(url, timeoutMs) {
1152
+ const startedAt = Date.now();
1153
+ const controller = new AbortController();
1154
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1155
+ try {
1156
+ const response = await fetch(url, {
1157
+ method: "HEAD",
1158
+ redirect: "manual",
1159
+ signal: controller.signal
1160
+ });
1161
+ const durationMs = Date.now() - startedAt;
1162
+ if (response.ok ||
1163
+ response.status === 401 ||
1164
+ response.status === 403 ||
1165
+ response.status === 404 ||
1166
+ response.status === 405 ||
1167
+ response.status === 422 ||
1168
+ response.status === 500 ||
1169
+ response.status === 501) {
1170
+ return {
1171
+ status: "reachable",
1172
+ method: "HEAD",
1173
+ durationMs,
1174
+ httpStatus: response.status,
1175
+ message: `Webhook endpoint responded to HEAD with HTTP ${response.status}.`
1176
+ };
1177
+ }
1178
+ return {
1179
+ status: "unreachable",
1180
+ method: "HEAD",
1181
+ durationMs,
1182
+ httpStatus: response.status,
1183
+ message: `Webhook endpoint probe returned HTTP ${response.status}.`
1184
+ };
1185
+ }
1186
+ catch (error) {
1187
+ const durationMs = Date.now() - startedAt;
1188
+ if (isAbortError(error)) {
1189
+ return {
1190
+ status: "timeout",
1191
+ method: "HEAD",
1192
+ durationMs,
1193
+ httpStatus: null,
1194
+ message: `Webhook endpoint probe timed out after ${timeoutMs}ms.`
1195
+ };
1196
+ }
1197
+ return {
1198
+ status: "unreachable",
1199
+ method: "HEAD",
1200
+ durationMs,
1201
+ httpStatus: null,
1202
+ message: error instanceof Error
1203
+ ? error.message
1204
+ : "Webhook endpoint probe failed."
1205
+ };
1206
+ }
1207
+ finally {
1208
+ clearTimeout(timeout);
1209
+ }
1210
+ }
1211
+ export function accessRoutes(db, opts) {
1212
+ const router = Router();
1213
+ const access = accessService(db);
1214
+ const agents = agentService(db);
1215
+ async function assertInstanceAdmin(req) {
1216
+ if (req.actor.type !== "board")
1217
+ throw unauthorized();
1218
+ if (isLocalImplicit(req))
1219
+ return;
1220
+ const allowed = await access.isInstanceAdmin(req.actor.userId);
1221
+ if (!allowed)
1222
+ throw forbidden("Instance admin required");
1223
+ }
1224
+ router.get("/board-claim/:token", async (req, res) => {
1225
+ const token = req.params.token.trim();
1226
+ const code = typeof req.query.code === "string" ? req.query.code.trim() : undefined;
1227
+ if (!token)
1228
+ throw notFound("Board claim challenge not found");
1229
+ const challenge = inspectBoardClaimChallenge(token, code);
1230
+ if (challenge.status === "invalid")
1231
+ throw notFound("Board claim challenge not found");
1232
+ res.json(challenge);
1233
+ });
1234
+ router.post("/board-claim/:token/claim", async (req, res) => {
1235
+ const token = req.params.token.trim();
1236
+ const code = typeof req.body?.code === "string" ? req.body.code.trim() : undefined;
1237
+ if (!token)
1238
+ throw notFound("Board claim challenge not found");
1239
+ if (!code)
1240
+ throw badRequest("Claim code is required");
1241
+ if (req.actor.type !== "board" ||
1242
+ req.actor.source !== "session" ||
1243
+ !req.actor.userId) {
1244
+ throw unauthorized("Sign in before claiming board ownership");
1245
+ }
1246
+ const claimed = await claimBoardOwnership(db, {
1247
+ token,
1248
+ code,
1249
+ userId: req.actor.userId
1250
+ });
1251
+ if (claimed.status === "invalid")
1252
+ throw notFound("Board claim challenge not found");
1253
+ if (claimed.status === "expired")
1254
+ throw conflict("Board claim challenge expired. Restart server to generate a new one.");
1255
+ if (claimed.status === "claimed") {
1256
+ res.json({
1257
+ claimed: true,
1258
+ userId: claimed.claimedByUserId ?? req.actor.userId
1259
+ });
1260
+ return;
1261
+ }
1262
+ throw conflict("Board claim challenge is no longer available");
1263
+ });
1264
+ async function assertCompanyPermission(req, companyId, permissionKey) {
1265
+ assertCompanyAccess(req, companyId);
1266
+ if (req.actor.type === "agent") {
1267
+ if (!req.actor.agentId)
1268
+ throw forbidden();
1269
+ const allowed = await access.hasPermission(companyId, "agent", req.actor.agentId, permissionKey);
1270
+ if (!allowed)
1271
+ throw forbidden("Permission denied");
1272
+ return;
1273
+ }
1274
+ if (req.actor.type !== "board")
1275
+ throw unauthorized();
1276
+ if (isLocalImplicit(req))
1277
+ return;
1278
+ const allowed = await access.canUser(companyId, req.actor.userId, permissionKey);
1279
+ if (!allowed)
1280
+ throw forbidden("Permission denied");
1281
+ }
1282
+ async function assertCanGenerateOpenClawInvitePrompt(req, companyId) {
1283
+ assertCompanyAccess(req, companyId);
1284
+ if (req.actor.type === "agent") {
1285
+ if (!req.actor.agentId)
1286
+ throw forbidden("Agent authentication required");
1287
+ const actorAgent = await agents.getById(req.actor.agentId);
1288
+ if (!actorAgent || actorAgent.companyId !== companyId) {
1289
+ throw forbidden("Agent key cannot access another company");
1290
+ }
1291
+ if (actorAgent.role !== "ceo") {
1292
+ throw forbidden("Only CEO agents can generate OpenClaw invite prompts");
1293
+ }
1294
+ return;
1295
+ }
1296
+ if (req.actor.type !== "board")
1297
+ throw unauthorized();
1298
+ if (isLocalImplicit(req))
1299
+ return;
1300
+ const allowed = await access.canUser(companyId, req.actor.userId, "users:invite");
1301
+ if (!allowed)
1302
+ throw forbidden("Permission denied");
1303
+ }
1304
+ async function createCompanyInviteForCompany(input) {
1305
+ const normalizedAgentMessage = typeof input.agentMessage === "string"
1306
+ ? input.agentMessage.trim() || null
1307
+ : null;
1308
+ const insertValues = {
1309
+ companyId: input.companyId,
1310
+ inviteType: "company_join",
1311
+ allowedJoinTypes: input.allowedJoinTypes,
1312
+ defaultsPayload: mergeInviteDefaults(input.defaultsPayload ?? null, normalizedAgentMessage),
1313
+ expiresAt: companyInviteExpiresAt(),
1314
+ invitedByUserId: input.req.actor.userId ?? null
1315
+ };
1316
+ let token = null;
1317
+ let created = null;
1318
+ for (let attempt = 0; attempt < INVITE_TOKEN_MAX_RETRIES; attempt += 1) {
1319
+ const candidateToken = createInviteToken();
1320
+ try {
1321
+ const row = await db
1322
+ .insert(invites)
1323
+ .values({
1324
+ ...insertValues,
1325
+ tokenHash: hashToken(candidateToken)
1326
+ })
1327
+ .returning()
1328
+ .then((rows) => rows[0]);
1329
+ token = candidateToken;
1330
+ created = row;
1331
+ break;
1332
+ }
1333
+ catch (error) {
1334
+ if (!isInviteTokenHashCollisionError(error)) {
1335
+ throw error;
1336
+ }
1337
+ }
1338
+ }
1339
+ if (!token || !created) {
1340
+ throw conflict("Failed to generate a unique invite token. Please retry.");
1341
+ }
1342
+ return { token, created, normalizedAgentMessage };
1343
+ }
1344
+ router.get("/skills/available", (_req, res) => {
1345
+ res.json({ skills: listAvailableSkills() });
1346
+ });
1347
+ router.get("/skills/index", (_req, res) => {
1348
+ res.json({
1349
+ skills: [
1350
+ { name: "paperclip", path: "/api/skills/paperclip" },
1351
+ {
1352
+ name: "para-memory-files",
1353
+ path: "/api/skills/para-memory-files"
1354
+ },
1355
+ {
1356
+ name: "paperclip-create-agent",
1357
+ path: "/api/skills/paperclip-create-agent"
1358
+ }
1359
+ ]
1360
+ });
1361
+ });
1362
+ router.get("/skills/:skillName", (req, res) => {
1363
+ const skillName = req.params.skillName.trim().toLowerCase();
1364
+ const markdown = readSkillMarkdown(skillName);
1365
+ if (!markdown)
1366
+ throw notFound("Skill not found");
1367
+ res.type("text/markdown").send(markdown);
1368
+ });
1369
+ router.post("/companies/:companyId/invites", validate(createCompanyInviteSchema), async (req, res) => {
1370
+ const companyId = req.params.companyId;
1371
+ await assertCompanyPermission(req, companyId, "users:invite");
1372
+ const { token, created, normalizedAgentMessage } = await createCompanyInviteForCompany({
1373
+ req,
1374
+ companyId,
1375
+ allowedJoinTypes: req.body.allowedJoinTypes,
1376
+ defaultsPayload: req.body.defaultsPayload ?? null,
1377
+ agentMessage: req.body.agentMessage ?? null
1378
+ });
1379
+ await logActivity(db, {
1380
+ companyId,
1381
+ actorType: req.actor.type === "agent" ? "agent" : "user",
1382
+ actorId: req.actor.type === "agent"
1383
+ ? req.actor.agentId ?? "unknown-agent"
1384
+ : req.actor.userId ?? "board",
1385
+ action: "invite.created",
1386
+ entityType: "invite",
1387
+ entityId: created.id,
1388
+ details: {
1389
+ inviteType: created.inviteType,
1390
+ allowedJoinTypes: created.allowedJoinTypes,
1391
+ expiresAt: created.expiresAt.toISOString(),
1392
+ hasAgentMessage: Boolean(normalizedAgentMessage)
1393
+ }
1394
+ });
1395
+ const inviteSummary = toInviteSummaryResponse(req, token, created);
1396
+ res.status(201).json({
1397
+ ...created,
1398
+ token,
1399
+ inviteUrl: `/invite/${token}`,
1400
+ onboardingTextPath: inviteSummary.onboardingTextPath,
1401
+ onboardingTextUrl: inviteSummary.onboardingTextUrl,
1402
+ inviteMessage: inviteSummary.inviteMessage
1403
+ });
1404
+ });
1405
+ router.post("/companies/:companyId/openclaw/invite-prompt", validate(createOpenClawInvitePromptSchema), async (req, res) => {
1406
+ const companyId = req.params.companyId;
1407
+ await assertCanGenerateOpenClawInvitePrompt(req, companyId);
1408
+ const { token, created, normalizedAgentMessage } = await createCompanyInviteForCompany({
1409
+ req,
1410
+ companyId,
1411
+ allowedJoinTypes: "agent",
1412
+ defaultsPayload: null,
1413
+ agentMessage: req.body.agentMessage ?? null
1414
+ });
1415
+ await logActivity(db, {
1416
+ companyId,
1417
+ actorType: req.actor.type === "agent" ? "agent" : "user",
1418
+ actorId: req.actor.type === "agent"
1419
+ ? req.actor.agentId ?? "unknown-agent"
1420
+ : req.actor.userId ?? "board",
1421
+ action: "invite.openclaw_prompt_created",
1422
+ entityType: "invite",
1423
+ entityId: created.id,
1424
+ details: {
1425
+ inviteType: created.inviteType,
1426
+ allowedJoinTypes: created.allowedJoinTypes,
1427
+ expiresAt: created.expiresAt.toISOString(),
1428
+ hasAgentMessage: Boolean(normalizedAgentMessage)
1429
+ }
1430
+ });
1431
+ const inviteSummary = toInviteSummaryResponse(req, token, created);
1432
+ res.status(201).json({
1433
+ ...created,
1434
+ token,
1435
+ inviteUrl: `/invite/${token}`,
1436
+ onboardingTextPath: inviteSummary.onboardingTextPath,
1437
+ onboardingTextUrl: inviteSummary.onboardingTextUrl,
1438
+ inviteMessage: inviteSummary.inviteMessage
1439
+ });
1440
+ });
1441
+ router.get("/invites/:token", async (req, res) => {
1442
+ const token = req.params.token.trim();
1443
+ if (!token)
1444
+ throw notFound("Invite not found");
1445
+ const invite = await db
1446
+ .select()
1447
+ .from(invites)
1448
+ .where(eq(invites.tokenHash, hashToken(token)))
1449
+ .then((rows) => rows[0] ?? null);
1450
+ if (!invite ||
1451
+ invite.revokedAt ||
1452
+ invite.acceptedAt ||
1453
+ inviteExpired(invite)) {
1454
+ throw notFound("Invite not found");
1455
+ }
1456
+ res.json(toInviteSummaryResponse(req, token, invite));
1457
+ });
1458
+ router.get("/invites/:token/onboarding", async (req, res) => {
1459
+ const token = req.params.token.trim();
1460
+ if (!token)
1461
+ throw notFound("Invite not found");
1462
+ const invite = await db
1463
+ .select()
1464
+ .from(invites)
1465
+ .where(eq(invites.tokenHash, hashToken(token)))
1466
+ .then((rows) => rows[0] ?? null);
1467
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
1468
+ throw notFound("Invite not found");
1469
+ }
1470
+ res.json(buildInviteOnboardingManifest(req, token, invite, opts));
1471
+ });
1472
+ router.get("/invites/:token/onboarding.txt", async (req, res) => {
1473
+ const token = req.params.token.trim();
1474
+ if (!token)
1475
+ throw notFound("Invite not found");
1476
+ const invite = await db
1477
+ .select()
1478
+ .from(invites)
1479
+ .where(eq(invites.tokenHash, hashToken(token)))
1480
+ .then((rows) => rows[0] ?? null);
1481
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
1482
+ throw notFound("Invite not found");
1483
+ }
1484
+ res
1485
+ .type("text/plain; charset=utf-8")
1486
+ .send(buildInviteOnboardingTextDocument(req, token, invite, opts));
1487
+ });
1488
+ router.get("/invites/:token/test-resolution", async (req, res) => {
1489
+ const token = req.params.token.trim();
1490
+ if (!token)
1491
+ throw notFound("Invite not found");
1492
+ const invite = await db
1493
+ .select()
1494
+ .from(invites)
1495
+ .where(eq(invites.tokenHash, hashToken(token)))
1496
+ .then((rows) => rows[0] ?? null);
1497
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
1498
+ throw notFound("Invite not found");
1499
+ }
1500
+ const rawUrl = typeof req.query.url === "string" ? req.query.url.trim() : "";
1501
+ if (!rawUrl)
1502
+ throw badRequest("url query parameter is required");
1503
+ let target;
1504
+ try {
1505
+ target = new URL(rawUrl);
1506
+ }
1507
+ catch {
1508
+ throw badRequest("url must be an absolute http(s) URL");
1509
+ }
1510
+ if (target.protocol !== "http:" && target.protocol !== "https:") {
1511
+ throw badRequest("url must use http or https");
1512
+ }
1513
+ const parsedTimeoutMs = typeof req.query.timeoutMs === "string"
1514
+ ? Number(req.query.timeoutMs)
1515
+ : NaN;
1516
+ const timeoutMs = Number.isFinite(parsedTimeoutMs)
1517
+ ? Math.max(1000, Math.min(15000, Math.floor(parsedTimeoutMs)))
1518
+ : 5000;
1519
+ const probe = await probeInviteResolutionTarget(target, timeoutMs);
1520
+ res.json({
1521
+ inviteId: invite.id,
1522
+ testResolutionPath: `/api/invites/${token}/test-resolution`,
1523
+ requestedUrl: target.toString(),
1524
+ timeoutMs,
1525
+ ...probe
1526
+ });
1527
+ });
1528
+ router.post("/invites/:token/accept", validate(acceptInviteSchema), async (req, res) => {
1529
+ const token = req.params.token.trim();
1530
+ if (!token)
1531
+ throw notFound("Invite not found");
1532
+ const invite = await db
1533
+ .select()
1534
+ .from(invites)
1535
+ .where(eq(invites.tokenHash, hashToken(token)))
1536
+ .then((rows) => rows[0] ?? null);
1537
+ if (!invite || invite.revokedAt || inviteExpired(invite)) {
1538
+ throw notFound("Invite not found");
1539
+ }
1540
+ const inviteAlreadyAccepted = Boolean(invite.acceptedAt);
1541
+ const existingJoinRequestForInvite = inviteAlreadyAccepted
1542
+ ? await db
1543
+ .select()
1544
+ .from(joinRequests)
1545
+ .where(eq(joinRequests.inviteId, invite.id))
1546
+ .then((rows) => rows[0] ?? null)
1547
+ : null;
1548
+ if (invite.inviteType === "bootstrap_ceo") {
1549
+ if (inviteAlreadyAccepted)
1550
+ throw notFound("Invite not found");
1551
+ if (req.body.requestType !== "human") {
1552
+ throw badRequest("Bootstrap invite requires human request type");
1553
+ }
1554
+ if (req.actor.type !== "board" ||
1555
+ (!req.actor.userId && !isLocalImplicit(req))) {
1556
+ throw unauthorized("Authenticated user required for bootstrap acceptance");
1557
+ }
1558
+ const userId = req.actor.userId ?? "local-board";
1559
+ const existingAdmin = await access.isInstanceAdmin(userId);
1560
+ if (!existingAdmin) {
1561
+ await access.promoteInstanceAdmin(userId);
1562
+ }
1563
+ const updatedInvite = await db
1564
+ .update(invites)
1565
+ .set({ acceptedAt: new Date(), updatedAt: new Date() })
1566
+ .where(eq(invites.id, invite.id))
1567
+ .returning()
1568
+ .then((rows) => rows[0] ?? invite);
1569
+ res.status(202).json({
1570
+ inviteId: updatedInvite.id,
1571
+ inviteType: updatedInvite.inviteType,
1572
+ bootstrapAccepted: true,
1573
+ userId
1574
+ });
1575
+ return;
1576
+ }
1577
+ const requestType = req.body.requestType;
1578
+ const companyId = invite.companyId;
1579
+ if (!companyId)
1580
+ throw conflict("Invite is missing company scope");
1581
+ if (invite.allowedJoinTypes !== "both" &&
1582
+ invite.allowedJoinTypes !== requestType) {
1583
+ throw badRequest(`Invite does not allow ${requestType} joins`);
1584
+ }
1585
+ if (requestType === "human" && req.actor.type !== "board") {
1586
+ throw unauthorized("Human invite acceptance requires authenticated user");
1587
+ }
1588
+ if (requestType === "human" &&
1589
+ !req.actor.userId &&
1590
+ !isLocalImplicit(req)) {
1591
+ throw unauthorized("Authenticated user is required");
1592
+ }
1593
+ if (requestType === "agent" && !req.body.agentName) {
1594
+ if (!inviteAlreadyAccepted ||
1595
+ !existingJoinRequestForInvite?.agentName) {
1596
+ throw badRequest("agentName is required for agent join requests");
1597
+ }
1598
+ }
1599
+ const adapterType = req.body.adapterType ?? null;
1600
+ if (inviteAlreadyAccepted &&
1601
+ !canReplayOpenClawGatewayInviteAccept({
1602
+ requestType,
1603
+ adapterType,
1604
+ existingJoinRequest: existingJoinRequestForInvite
1605
+ })) {
1606
+ throw notFound("Invite not found");
1607
+ }
1608
+ const replayJoinRequestId = inviteAlreadyAccepted
1609
+ ? existingJoinRequestForInvite?.id ?? null
1610
+ : null;
1611
+ if (inviteAlreadyAccepted && !replayJoinRequestId) {
1612
+ throw conflict("Join request not found");
1613
+ }
1614
+ const replayMergedDefaults = inviteAlreadyAccepted
1615
+ ? mergeJoinDefaultsPayloadForReplay(existingJoinRequestForInvite?.agentDefaultsPayload ?? null, req.body.agentDefaultsPayload ?? null)
1616
+ : req.body.agentDefaultsPayload ?? null;
1617
+ const gatewayDefaultsPayload = requestType === "agent"
1618
+ ? buildJoinDefaultsPayloadForAccept({
1619
+ adapterType,
1620
+ defaultsPayload: replayMergedDefaults,
1621
+ paperclipApiUrl: req.body.paperclipApiUrl ?? null,
1622
+ inboundOpenClawAuthHeader: req.header("x-openclaw-auth") ?? null,
1623
+ inboundOpenClawTokenHeader: req.header("x-openclaw-token") ?? null
1624
+ })
1625
+ : null;
1626
+ const joinDefaults = requestType === "agent"
1627
+ ? normalizeAgentDefaultsForJoin({
1628
+ adapterType,
1629
+ defaultsPayload: gatewayDefaultsPayload,
1630
+ deploymentMode: opts.deploymentMode,
1631
+ deploymentExposure: opts.deploymentExposure,
1632
+ bindHost: opts.bindHost,
1633
+ allowedHostnames: opts.allowedHostnames
1634
+ })
1635
+ : {
1636
+ normalized: null,
1637
+ diagnostics: [],
1638
+ fatalErrors: []
1639
+ };
1640
+ if (requestType === "agent" && joinDefaults.fatalErrors.length > 0) {
1641
+ throw badRequest(joinDefaults.fatalErrors.join("; "));
1642
+ }
1643
+ if (requestType === "agent" && adapterType === "openclaw_gateway") {
1644
+ logger.info({
1645
+ inviteId: invite.id,
1646
+ joinRequestDiagnostics: joinDefaults.diagnostics.map((diag) => ({
1647
+ code: diag.code,
1648
+ level: diag.level
1649
+ })),
1650
+ normalizedAgentDefaults: summarizeOpenClawGatewayDefaultsForLog(joinDefaults.normalized)
1651
+ }, "invite accept normalized OpenClaw gateway defaults");
1652
+ }
1653
+ const claimSecret = requestType === "agent" && !inviteAlreadyAccepted
1654
+ ? createClaimSecret()
1655
+ : null;
1656
+ const claimSecretHash = claimSecret ? hashToken(claimSecret) : null;
1657
+ const claimSecretExpiresAt = claimSecret
1658
+ ? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
1659
+ : null;
1660
+ const actorEmail = requestType === "human" ? await resolveActorEmail(db, req) : null;
1661
+ const created = !inviteAlreadyAccepted
1662
+ ? await db.transaction(async (tx) => {
1663
+ await tx
1664
+ .update(invites)
1665
+ .set({ acceptedAt: new Date(), updatedAt: new Date() })
1666
+ .where(and(eq(invites.id, invite.id), isNull(invites.acceptedAt), isNull(invites.revokedAt)));
1667
+ const row = await tx
1668
+ .insert(joinRequests)
1669
+ .values({
1670
+ inviteId: invite.id,
1671
+ companyId,
1672
+ requestType,
1673
+ status: "pending_approval",
1674
+ requestIp: requestIp(req),
1675
+ requestingUserId: requestType === "human"
1676
+ ? req.actor.userId ?? "local-board"
1677
+ : null,
1678
+ requestEmailSnapshot: requestType === "human" ? actorEmail : null,
1679
+ agentName: requestType === "agent" ? req.body.agentName : null,
1680
+ adapterType: requestType === "agent" ? adapterType : null,
1681
+ capabilities: requestType === "agent"
1682
+ ? req.body.capabilities ?? null
1683
+ : null,
1684
+ agentDefaultsPayload: requestType === "agent" ? joinDefaults.normalized : null,
1685
+ claimSecretHash,
1686
+ claimSecretExpiresAt
1687
+ })
1688
+ .returning()
1689
+ .then((rows) => rows[0]);
1690
+ return row;
1691
+ })
1692
+ : await db
1693
+ .update(joinRequests)
1694
+ .set({
1695
+ requestIp: requestIp(req),
1696
+ agentName: requestType === "agent"
1697
+ ? req.body.agentName ??
1698
+ existingJoinRequestForInvite?.agentName ??
1699
+ null
1700
+ : null,
1701
+ capabilities: requestType === "agent"
1702
+ ? req.body.capabilities ??
1703
+ existingJoinRequestForInvite?.capabilities ??
1704
+ null
1705
+ : null,
1706
+ adapterType: requestType === "agent" ? adapterType : null,
1707
+ agentDefaultsPayload: requestType === "agent" ? joinDefaults.normalized : null,
1708
+ updatedAt: new Date()
1709
+ })
1710
+ .where(eq(joinRequests.id, replayJoinRequestId))
1711
+ .returning()
1712
+ .then((rows) => rows[0]);
1713
+ if (!created) {
1714
+ throw conflict("Join request not found");
1715
+ }
1716
+ if (inviteAlreadyAccepted &&
1717
+ requestType === "agent" &&
1718
+ adapterType === "openclaw_gateway" &&
1719
+ created.status === "approved" &&
1720
+ created.createdAgentId) {
1721
+ const existingAgent = await agents.getById(created.createdAgentId);
1722
+ if (!existingAgent) {
1723
+ throw conflict("Approved join request agent not found");
1724
+ }
1725
+ const existingAdapterConfig = isPlainObject(existingAgent.adapterConfig)
1726
+ ? existingAgent.adapterConfig
1727
+ : {};
1728
+ const nextAdapterConfig = {
1729
+ ...existingAdapterConfig,
1730
+ ...(joinDefaults.normalized ?? {})
1731
+ };
1732
+ const updatedAgent = await agents.update(created.createdAgentId, {
1733
+ adapterType,
1734
+ adapterConfig: nextAdapterConfig
1735
+ });
1736
+ if (!updatedAgent) {
1737
+ throw conflict("Approved join request agent not found");
1738
+ }
1739
+ await logActivity(db, {
1740
+ companyId,
1741
+ actorType: req.actor.type === "agent" ? "agent" : "user",
1742
+ actorId: req.actor.type === "agent"
1743
+ ? req.actor.agentId ?? "invite-agent"
1744
+ : req.actor.userId ?? "board",
1745
+ action: "agent.updated_from_join_replay",
1746
+ entityType: "agent",
1747
+ entityId: updatedAgent.id,
1748
+ details: { inviteId: invite.id, joinRequestId: created.id }
1749
+ });
1750
+ }
1751
+ if (requestType === "agent" && adapterType === "openclaw_gateway") {
1752
+ const expectedDefaults = summarizeOpenClawGatewayDefaultsForLog(joinDefaults.normalized);
1753
+ const persistedDefaults = summarizeOpenClawGatewayDefaultsForLog(created.agentDefaultsPayload);
1754
+ const missingPersistedFields = [];
1755
+ if (expectedDefaults.url && !persistedDefaults.url)
1756
+ missingPersistedFields.push("url");
1757
+ if (expectedDefaults.paperclipApiUrl &&
1758
+ !persistedDefaults.paperclipApiUrl) {
1759
+ missingPersistedFields.push("paperclipApiUrl");
1760
+ }
1761
+ if (expectedDefaults.gatewayToken && !persistedDefaults.gatewayToken) {
1762
+ missingPersistedFields.push("headers.x-openclaw-token");
1763
+ }
1764
+ if (expectedDefaults.devicePrivateKeyPem &&
1765
+ !persistedDefaults.devicePrivateKeyPem) {
1766
+ missingPersistedFields.push("devicePrivateKeyPem");
1767
+ }
1768
+ if (expectedDefaults.headerKeys.length > 0 &&
1769
+ persistedDefaults.headerKeys.length === 0) {
1770
+ missingPersistedFields.push("headers");
1771
+ }
1772
+ logger.info({
1773
+ inviteId: invite.id,
1774
+ joinRequestId: created.id,
1775
+ joinRequestStatus: created.status,
1776
+ expectedDefaults,
1777
+ persistedDefaults,
1778
+ diagnostics: joinDefaults.diagnostics.map((diag) => ({
1779
+ code: diag.code,
1780
+ level: diag.level,
1781
+ message: diag.message,
1782
+ hint: diag.hint ?? null
1783
+ }))
1784
+ }, "invite accept persisted OpenClaw gateway join request");
1785
+ if (missingPersistedFields.length > 0) {
1786
+ logger.warn({
1787
+ inviteId: invite.id,
1788
+ joinRequestId: created.id,
1789
+ missingPersistedFields
1790
+ }, "invite accept detected missing persisted OpenClaw gateway defaults");
1791
+ }
1792
+ }
1793
+ await logActivity(db, {
1794
+ companyId,
1795
+ actorType: req.actor.type === "agent" ? "agent" : "user",
1796
+ actorId: req.actor.type === "agent"
1797
+ ? req.actor.agentId ?? "invite-agent"
1798
+ : req.actor.userId ??
1799
+ (requestType === "agent" ? "invite-anon" : "board"),
1800
+ action: inviteAlreadyAccepted
1801
+ ? "join.request_replayed"
1802
+ : "join.requested",
1803
+ entityType: "join_request",
1804
+ entityId: created.id,
1805
+ details: {
1806
+ requestType,
1807
+ requestIp: created.requestIp,
1808
+ inviteReplay: inviteAlreadyAccepted
1809
+ }
1810
+ });
1811
+ const response = toJoinRequestResponse(created);
1812
+ if (claimSecret) {
1813
+ const onboardingManifest = buildInviteOnboardingManifest(req, token, invite, opts);
1814
+ res.status(202).json({
1815
+ ...response,
1816
+ claimSecret,
1817
+ claimApiKeyPath: `/api/join-requests/${created.id}/claim-api-key`,
1818
+ onboarding: onboardingManifest.onboarding,
1819
+ diagnostics: joinDefaults.diagnostics
1820
+ });
1821
+ return;
1822
+ }
1823
+ res.status(202).json({
1824
+ ...response,
1825
+ ...(joinDefaults.diagnostics.length > 0
1826
+ ? { diagnostics: joinDefaults.diagnostics }
1827
+ : {})
1828
+ });
1829
+ });
1830
+ router.post("/invites/:inviteId/revoke", async (req, res) => {
1831
+ const id = req.params.inviteId;
1832
+ const invite = await db
1833
+ .select()
1834
+ .from(invites)
1835
+ .where(eq(invites.id, id))
1836
+ .then((rows) => rows[0] ?? null);
1837
+ if (!invite)
1838
+ throw notFound("Invite not found");
1839
+ if (invite.inviteType === "bootstrap_ceo") {
1840
+ await assertInstanceAdmin(req);
1841
+ }
1842
+ else {
1843
+ if (!invite.companyId)
1844
+ throw conflict("Invite is missing company scope");
1845
+ await assertCompanyPermission(req, invite.companyId, "users:invite");
1846
+ }
1847
+ if (invite.acceptedAt)
1848
+ throw conflict("Invite already consumed");
1849
+ if (invite.revokedAt)
1850
+ return res.json(invite);
1851
+ const revoked = await db
1852
+ .update(invites)
1853
+ .set({ revokedAt: new Date(), updatedAt: new Date() })
1854
+ .where(eq(invites.id, id))
1855
+ .returning()
1856
+ .then((rows) => rows[0]);
1857
+ if (invite.companyId) {
1858
+ await logActivity(db, {
1859
+ companyId: invite.companyId,
1860
+ actorType: req.actor.type === "agent" ? "agent" : "user",
1861
+ actorId: req.actor.type === "agent"
1862
+ ? req.actor.agentId ?? "unknown-agent"
1863
+ : req.actor.userId ?? "board",
1864
+ action: "invite.revoked",
1865
+ entityType: "invite",
1866
+ entityId: id
1867
+ });
1868
+ }
1869
+ res.json(revoked);
1870
+ });
1871
+ router.get("/companies/:companyId/join-requests", async (req, res) => {
1872
+ const companyId = req.params.companyId;
1873
+ await assertCompanyPermission(req, companyId, "joins:approve");
1874
+ const query = listJoinRequestsQuerySchema.parse(req.query);
1875
+ const all = await db
1876
+ .select()
1877
+ .from(joinRequests)
1878
+ .where(eq(joinRequests.companyId, companyId))
1879
+ .orderBy(desc(joinRequests.createdAt));
1880
+ const filtered = all.filter((row) => {
1881
+ if (query.status && row.status !== query.status)
1882
+ return false;
1883
+ if (query.requestType && row.requestType !== query.requestType)
1884
+ return false;
1885
+ return true;
1886
+ });
1887
+ res.json(filtered.map(toJoinRequestResponse));
1888
+ });
1889
+ router.post("/companies/:companyId/join-requests/:requestId/approve", async (req, res) => {
1890
+ const companyId = req.params.companyId;
1891
+ const requestId = req.params.requestId;
1892
+ await assertCompanyPermission(req, companyId, "joins:approve");
1893
+ const existing = await db
1894
+ .select()
1895
+ .from(joinRequests)
1896
+ .where(and(eq(joinRequests.companyId, companyId), eq(joinRequests.id, requestId)))
1897
+ .then((rows) => rows[0] ?? null);
1898
+ if (!existing)
1899
+ throw notFound("Join request not found");
1900
+ if (existing.status !== "pending_approval")
1901
+ throw conflict("Join request is not pending");
1902
+ const invite = await db
1903
+ .select()
1904
+ .from(invites)
1905
+ .where(eq(invites.id, existing.inviteId))
1906
+ .then((rows) => rows[0] ?? null);
1907
+ if (!invite)
1908
+ throw notFound("Invite not found");
1909
+ let createdAgentId = existing.createdAgentId ?? null;
1910
+ if (existing.requestType === "human") {
1911
+ if (!existing.requestingUserId)
1912
+ throw conflict("Join request missing user identity");
1913
+ await access.ensureMembership(companyId, "user", existing.requestingUserId, "member", "active");
1914
+ const grants = grantsFromDefaults(invite.defaultsPayload, "human");
1915
+ await access.setPrincipalGrants(companyId, "user", existing.requestingUserId, grants, req.actor.userId ?? null);
1916
+ }
1917
+ else {
1918
+ const existingAgents = await agents.list(companyId);
1919
+ const managerId = resolveJoinRequestAgentManagerId(existingAgents);
1920
+ if (!managerId) {
1921
+ throw conflict("Join request cannot be approved because this company has no active CEO");
1922
+ }
1923
+ const agentName = deduplicateAgentName(existing.agentName ?? "New Agent", existingAgents.map((a) => ({
1924
+ id: a.id,
1925
+ name: a.name,
1926
+ status: a.status
1927
+ })));
1928
+ const created = await agents.create(companyId, {
1929
+ name: agentName,
1930
+ role: "general",
1931
+ title: null,
1932
+ status: "idle",
1933
+ reportsTo: managerId,
1934
+ capabilities: existing.capabilities ?? null,
1935
+ adapterType: existing.adapterType ?? "process",
1936
+ adapterConfig: existing.agentDefaultsPayload &&
1937
+ typeof existing.agentDefaultsPayload === "object"
1938
+ ? existing.agentDefaultsPayload
1939
+ : {},
1940
+ runtimeConfig: {},
1941
+ budgetMonthlyCents: 0,
1942
+ spentMonthlyCents: 0,
1943
+ permissions: {},
1944
+ lastHeartbeatAt: null,
1945
+ metadata: null
1946
+ });
1947
+ createdAgentId = created.id;
1948
+ await access.ensureMembership(companyId, "agent", created.id, "member", "active");
1949
+ const grants = grantsFromDefaults(invite.defaultsPayload, "agent");
1950
+ await access.setPrincipalGrants(companyId, "agent", created.id, grants, req.actor.userId ?? null);
1951
+ }
1952
+ const approved = await db
1953
+ .update(joinRequests)
1954
+ .set({
1955
+ status: "approved",
1956
+ approvedByUserId: req.actor.userId ?? (isLocalImplicit(req) ? "local-board" : null),
1957
+ approvedAt: new Date(),
1958
+ createdAgentId,
1959
+ updatedAt: new Date()
1960
+ })
1961
+ .where(eq(joinRequests.id, requestId))
1962
+ .returning()
1963
+ .then((rows) => rows[0]);
1964
+ await logActivity(db, {
1965
+ companyId,
1966
+ actorType: "user",
1967
+ actorId: req.actor.userId ?? "board",
1968
+ action: "join.approved",
1969
+ entityType: "join_request",
1970
+ entityId: requestId,
1971
+ details: { requestType: existing.requestType, createdAgentId }
1972
+ });
1973
+ if (createdAgentId) {
1974
+ void notifyHireApproved(db, {
1975
+ companyId,
1976
+ agentId: createdAgentId,
1977
+ source: "join_request",
1978
+ sourceId: requestId,
1979
+ approvedAt: new Date()
1980
+ }).catch(() => { });
1981
+ }
1982
+ res.json(toJoinRequestResponse(approved));
1983
+ });
1984
+ router.post("/companies/:companyId/join-requests/:requestId/reject", async (req, res) => {
1985
+ const companyId = req.params.companyId;
1986
+ const requestId = req.params.requestId;
1987
+ await assertCompanyPermission(req, companyId, "joins:approve");
1988
+ const existing = await db
1989
+ .select()
1990
+ .from(joinRequests)
1991
+ .where(and(eq(joinRequests.companyId, companyId), eq(joinRequests.id, requestId)))
1992
+ .then((rows) => rows[0] ?? null);
1993
+ if (!existing)
1994
+ throw notFound("Join request not found");
1995
+ if (existing.status !== "pending_approval")
1996
+ throw conflict("Join request is not pending");
1997
+ const rejected = await db
1998
+ .update(joinRequests)
1999
+ .set({
2000
+ status: "rejected",
2001
+ rejectedByUserId: req.actor.userId ?? (isLocalImplicit(req) ? "local-board" : null),
2002
+ rejectedAt: new Date(),
2003
+ updatedAt: new Date()
2004
+ })
2005
+ .where(eq(joinRequests.id, requestId))
2006
+ .returning()
2007
+ .then((rows) => rows[0]);
2008
+ await logActivity(db, {
2009
+ companyId,
2010
+ actorType: "user",
2011
+ actorId: req.actor.userId ?? "board",
2012
+ action: "join.rejected",
2013
+ entityType: "join_request",
2014
+ entityId: requestId,
2015
+ details: { requestType: existing.requestType }
2016
+ });
2017
+ res.json(toJoinRequestResponse(rejected));
2018
+ });
2019
+ router.post("/join-requests/:requestId/claim-api-key", validate(claimJoinRequestApiKeySchema), async (req, res) => {
2020
+ const requestId = req.params.requestId;
2021
+ const presentedClaimSecretHash = hashToken(req.body.claimSecret);
2022
+ const joinRequest = await db
2023
+ .select()
2024
+ .from(joinRequests)
2025
+ .where(eq(joinRequests.id, requestId))
2026
+ .then((rows) => rows[0] ?? null);
2027
+ if (!joinRequest)
2028
+ throw notFound("Join request not found");
2029
+ if (joinRequest.requestType !== "agent")
2030
+ throw badRequest("Only agent join requests can claim API keys");
2031
+ if (joinRequest.status !== "approved")
2032
+ throw conflict("Join request must be approved before key claim");
2033
+ if (!joinRequest.createdAgentId)
2034
+ throw conflict("Join request has no created agent");
2035
+ if (!joinRequest.claimSecretHash)
2036
+ throw conflict("Join request is missing claim secret metadata");
2037
+ if (!tokenHashesMatch(joinRequest.claimSecretHash, presentedClaimSecretHash)) {
2038
+ throw forbidden("Invalid claim secret");
2039
+ }
2040
+ if (joinRequest.claimSecretExpiresAt &&
2041
+ joinRequest.claimSecretExpiresAt.getTime() <= Date.now()) {
2042
+ throw conflict("Claim secret expired");
2043
+ }
2044
+ if (joinRequest.claimSecretConsumedAt)
2045
+ throw conflict("Claim secret already used");
2046
+ const existingKey = await db
2047
+ .select({ id: agentApiKeys.id })
2048
+ .from(agentApiKeys)
2049
+ .where(eq(agentApiKeys.agentId, joinRequest.createdAgentId))
2050
+ .then((rows) => rows[0] ?? null);
2051
+ if (existingKey)
2052
+ throw conflict("API key already claimed");
2053
+ const consumed = await db
2054
+ .update(joinRequests)
2055
+ .set({ claimSecretConsumedAt: new Date(), updatedAt: new Date() })
2056
+ .where(and(eq(joinRequests.id, requestId), isNull(joinRequests.claimSecretConsumedAt)))
2057
+ .returning({ id: joinRequests.id })
2058
+ .then((rows) => rows[0] ?? null);
2059
+ if (!consumed)
2060
+ throw conflict("Claim secret already used");
2061
+ const created = await agents.createApiKey(joinRequest.createdAgentId, "initial-join-key");
2062
+ await logActivity(db, {
2063
+ companyId: joinRequest.companyId,
2064
+ actorType: "system",
2065
+ actorId: "join-claim",
2066
+ action: "agent_api_key.claimed",
2067
+ entityType: "agent_api_key",
2068
+ entityId: created.id,
2069
+ details: {
2070
+ agentId: joinRequest.createdAgentId,
2071
+ joinRequestId: requestId
2072
+ }
2073
+ });
2074
+ res.status(201).json({
2075
+ keyId: created.id,
2076
+ token: created.token,
2077
+ agentId: joinRequest.createdAgentId,
2078
+ createdAt: created.createdAt
2079
+ });
2080
+ });
2081
+ router.get("/companies/:companyId/members", async (req, res) => {
2082
+ const companyId = req.params.companyId;
2083
+ await assertCompanyPermission(req, companyId, "users:manage_permissions");
2084
+ const members = await access.listMembers(companyId);
2085
+ res.json(members);
2086
+ });
2087
+ router.patch("/companies/:companyId/members/:memberId/permissions", validate(updateMemberPermissionsSchema), async (req, res) => {
2088
+ const companyId = req.params.companyId;
2089
+ const memberId = req.params.memberId;
2090
+ await assertCompanyPermission(req, companyId, "users:manage_permissions");
2091
+ const updated = await access.setMemberPermissions(companyId, memberId, req.body.grants ?? [], req.actor.userId ?? null);
2092
+ if (!updated)
2093
+ throw notFound("Member not found");
2094
+ res.json(updated);
2095
+ });
2096
+ router.post("/admin/users/:userId/promote-instance-admin", async (req, res) => {
2097
+ await assertInstanceAdmin(req);
2098
+ const userId = req.params.userId;
2099
+ const result = await access.promoteInstanceAdmin(userId);
2100
+ res.status(201).json(result);
2101
+ });
2102
+ router.post("/admin/users/:userId/demote-instance-admin", async (req, res) => {
2103
+ await assertInstanceAdmin(req);
2104
+ const userId = req.params.userId;
2105
+ const removed = await access.demoteInstanceAdmin(userId);
2106
+ if (!removed)
2107
+ throw notFound("Instance admin role not found");
2108
+ res.json(removed);
2109
+ });
2110
+ router.get("/admin/users/:userId/company-access", async (req, res) => {
2111
+ await assertInstanceAdmin(req);
2112
+ const userId = req.params.userId;
2113
+ const memberships = await access.listUserCompanyAccess(userId);
2114
+ res.json(memberships);
2115
+ });
2116
+ router.put("/admin/users/:userId/company-access", validate(updateUserCompanyAccessSchema), async (req, res) => {
2117
+ await assertInstanceAdmin(req);
2118
+ const userId = req.params.userId;
2119
+ const memberships = await access.setUserCompanyAccess(userId, req.body.companyIds ?? []);
2120
+ res.json(memberships);
2121
+ });
2122
+ return router;
2123
+ }
2124
+ //# sourceMappingURL=access.js.map