agor-live 0.3.7

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 (297) hide show
  1. package/LICENSE +94 -0
  2. package/README.md +163 -0
  3. package/bin/agor-daemon.js +20 -0
  4. package/bin/agor.js +14 -0
  5. package/dist/cli/base-command.d.ts +29 -0
  6. package/dist/cli/base-command.js +41 -0
  7. package/dist/cli/commands/board/add-session.d.ts +15 -0
  8. package/dist/cli/commands/board/add-session.js +102 -0
  9. package/dist/cli/commands/board/list.d.ts +14 -0
  10. package/dist/cli/commands/board/list.js +74 -0
  11. package/dist/cli/commands/config/clear.d.ts +13 -0
  12. package/dist/cli/commands/config/clear.js +21 -0
  13. package/dist/cli/commands/config/get.d.ts +13 -0
  14. package/dist/cli/commands/config/get.js +41 -0
  15. package/dist/cli/commands/config/index.d.ts +13 -0
  16. package/dist/cli/commands/config/index.js +118 -0
  17. package/dist/cli/commands/config/set.d.ts +14 -0
  18. package/dist/cli/commands/config/set.js +50 -0
  19. package/dist/cli/commands/config/unset.d.ts +13 -0
  20. package/dist/cli/commands/config/unset.js +35 -0
  21. package/dist/cli/commands/daemon/index.d.ts +13 -0
  22. package/dist/cli/commands/daemon/index.js +65 -0
  23. package/dist/cli/commands/daemon/logs.d.ts +13 -0
  24. package/dist/cli/commands/daemon/logs.js +78 -0
  25. package/dist/cli/commands/daemon/restart.d.ts +13 -0
  26. package/dist/cli/commands/daemon/restart.js +177 -0
  27. package/dist/cli/commands/daemon/start.d.ts +13 -0
  28. package/dist/cli/commands/daemon/start.js +193 -0
  29. package/dist/cli/commands/daemon/status.d.ts +13 -0
  30. package/dist/cli/commands/daemon/status.js +93 -0
  31. package/dist/cli/commands/daemon/stop.d.ts +13 -0
  32. package/dist/cli/commands/daemon/stop.js +108 -0
  33. package/dist/cli/commands/init.d.ts +44 -0
  34. package/dist/cli/commands/init.js +459 -0
  35. package/dist/cli/commands/mcp/add.d.ts +26 -0
  36. package/dist/cli/commands/mcp/add.js +162 -0
  37. package/dist/cli/commands/mcp/list.d.ts +16 -0
  38. package/dist/cli/commands/mcp/list.js +89 -0
  39. package/dist/cli/commands/mcp/remove.d.ts +17 -0
  40. package/dist/cli/commands/mcp/remove.js +86 -0
  41. package/dist/cli/commands/mcp/show.d.ts +14 -0
  42. package/dist/cli/commands/mcp/show.js +131 -0
  43. package/dist/cli/commands/repo/add.d.ts +16 -0
  44. package/dist/cli/commands/repo/add.js +105 -0
  45. package/dist/cli/commands/repo/list.d.ts +17 -0
  46. package/dist/cli/commands/repo/list.js +99 -0
  47. package/dist/cli/commands/repo/rm.d.ts +17 -0
  48. package/dist/cli/commands/repo/rm.js +126 -0
  49. package/dist/cli/commands/repo/worktree/add.d.ts +21 -0
  50. package/dist/cli/commands/repo/worktree/add.js +145 -0
  51. package/dist/cli/commands/repo/worktree/list.d.ts +21 -0
  52. package/dist/cli/commands/repo/worktree/list.js +136 -0
  53. package/dist/cli/commands/session/list.d.ts +30 -0
  54. package/dist/cli/commands/session/list.js +204 -0
  55. package/dist/cli/commands/session/load-claude.d.ts +16 -0
  56. package/dist/cli/commands/session/load-claude.js +211 -0
  57. package/dist/cli/commands/user/create-admin.d.ts +13 -0
  58. package/dist/cli/commands/user/create-admin.js +65 -0
  59. package/dist/cli/commands/user/create.d.ts +16 -0
  60. package/dist/cli/commands/user/create.js +126 -0
  61. package/dist/cli/commands/user/delete.d.ts +16 -0
  62. package/dist/cli/commands/user/delete.js +77 -0
  63. package/dist/cli/commands/user/list.d.ts +13 -0
  64. package/dist/cli/commands/user/list.js +78 -0
  65. package/dist/cli/commands/user/update.d.ts +19 -0
  66. package/dist/cli/commands/user/update.js +149 -0
  67. package/dist/cli/hooks/command-not-found.d.ts +9 -0
  68. package/dist/cli/hooks/command-not-found.js +14 -0
  69. package/dist/cli/lib/banner.d.ts +25 -0
  70. package/dist/cli/lib/banner.js +25 -0
  71. package/dist/cli/lib/context.d.ts +27 -0
  72. package/dist/cli/lib/context.js +32 -0
  73. package/dist/cli/lib/daemon-manager.d.ts +48 -0
  74. package/dist/cli/lib/daemon-manager.js +109 -0
  75. package/dist/cli/lib/help.d.ts +13 -0
  76. package/dist/cli/lib/help.js +46 -0
  77. package/dist/core/agentic-tool-B_gFNpk5.d.ts +33 -0
  78. package/dist/core/agentic-tool-DsyX8diw.d.cts +33 -0
  79. package/dist/core/api/index.cjs +98 -0
  80. package/dist/core/api/index.d.cts +174 -0
  81. package/dist/core/api/index.d.ts +174 -0
  82. package/dist/core/api/index.js +62 -0
  83. package/dist/core/board-comment-BUm0fpmD.d.cts +134 -0
  84. package/dist/core/board-comment-gC_-twPx.d.ts +134 -0
  85. package/dist/core/claude/index.cjs +673 -0
  86. package/dist/core/claude/index.d.cts +124 -0
  87. package/dist/core/claude/index.d.ts +124 -0
  88. package/dist/core/claude/index.js +629 -0
  89. package/dist/core/config/browser.cjs +165 -0
  90. package/dist/core/config/browser.d.cts +289 -0
  91. package/dist/core/config/browser.d.ts +289 -0
  92. package/dist/core/config/browser.js +131 -0
  93. package/dist/core/config/index.cjs +518 -0
  94. package/dist/core/config/index.d.cts +246 -0
  95. package/dist/core/config/index.d.ts +246 -0
  96. package/dist/core/config/index.js +451 -0
  97. package/dist/core/db/index.cjs +3726 -0
  98. package/dist/core/db/index.d.cts +631 -0
  99. package/dist/core/db/index.d.ts +631 -0
  100. package/dist/core/db/index.js +3649 -0
  101. package/dist/core/dist/agentic-tool-B_gFNpk5.d.ts +33 -0
  102. package/dist/core/dist/agentic-tool-DsyX8diw.d.cts +33 -0
  103. package/dist/core/dist/api/index.cjs +98 -0
  104. package/dist/core/dist/api/index.d.cts +174 -0
  105. package/dist/core/dist/api/index.d.ts +174 -0
  106. package/dist/core/dist/api/index.js +62 -0
  107. package/dist/core/dist/board-comment-BUm0fpmD.d.cts +134 -0
  108. package/dist/core/dist/board-comment-gC_-twPx.d.ts +134 -0
  109. package/dist/core/dist/claude/index.cjs +673 -0
  110. package/dist/core/dist/claude/index.d.cts +124 -0
  111. package/dist/core/dist/claude/index.d.ts +124 -0
  112. package/dist/core/dist/claude/index.js +629 -0
  113. package/dist/core/dist/config/browser.cjs +165 -0
  114. package/dist/core/dist/config/browser.d.cts +289 -0
  115. package/dist/core/dist/config/browser.d.ts +289 -0
  116. package/dist/core/dist/config/browser.js +131 -0
  117. package/dist/core/dist/config/index.cjs +518 -0
  118. package/dist/core/dist/config/index.d.cts +246 -0
  119. package/dist/core/dist/config/index.d.ts +246 -0
  120. package/dist/core/dist/config/index.js +451 -0
  121. package/dist/core/dist/db/index.cjs +3726 -0
  122. package/dist/core/dist/db/index.d.cts +631 -0
  123. package/dist/core/dist/db/index.d.ts +631 -0
  124. package/dist/core/dist/db/index.js +3649 -0
  125. package/dist/core/dist/environment/variable-resolver.cjs +92 -0
  126. package/dist/core/dist/environment/variable-resolver.d.cts +52 -0
  127. package/dist/core/dist/environment/variable-resolver.d.ts +52 -0
  128. package/dist/core/dist/environment/variable-resolver.js +53 -0
  129. package/dist/core/dist/feathers/index.cjs +66 -0
  130. package/dist/core/dist/feathers/index.d.cts +7 -0
  131. package/dist/core/dist/feathers/index.d.ts +7 -0
  132. package/dist/core/dist/feathers/index.js +25 -0
  133. package/dist/core/dist/feathers-BzHEPnpl.d.cts +228 -0
  134. package/dist/core/dist/feathers-BzHEPnpl.d.ts +228 -0
  135. package/dist/core/dist/git/index.cjs +302 -0
  136. package/dist/core/dist/git/index.d.cts +137 -0
  137. package/dist/core/dist/git/index.d.ts +137 -0
  138. package/dist/core/dist/git/index.js +260 -0
  139. package/dist/core/dist/id-DMqyogFB.d.cts +131 -0
  140. package/dist/core/dist/id-DMqyogFB.d.ts +131 -0
  141. package/dist/core/dist/index.cjs +4653 -0
  142. package/dist/core/dist/index.d.cts +23 -0
  143. package/dist/core/dist/index.d.ts +23 -0
  144. package/dist/core/dist/index.js +4509 -0
  145. package/dist/core/dist/message-BoxZISHg.d.cts +120 -0
  146. package/dist/core/dist/message-DvBzHu7V.d.ts +120 -0
  147. package/dist/core/dist/permissions/index.cjs +112 -0
  148. package/dist/core/dist/permissions/index.d.cts +81 -0
  149. package/dist/core/dist/permissions/index.d.ts +81 -0
  150. package/dist/core/dist/permissions/index.js +85 -0
  151. package/dist/core/dist/repo-3CUrCRbq.d.cts +405 -0
  152. package/dist/core/dist/repo-CnvJ0B6-.d.ts +405 -0
  153. package/dist/core/dist/session-BPjJlVdZ.d.cts +429 -0
  154. package/dist/core/dist/session-wAzjHatv.d.ts +429 -0
  155. package/dist/core/dist/task-BIEgT1DK.d.cts +163 -0
  156. package/dist/core/dist/task-DuIfiUbW.d.ts +163 -0
  157. package/dist/core/dist/templates/handlebars-helpers.cjs +156 -0
  158. package/dist/core/dist/templates/handlebars-helpers.d.cts +45 -0
  159. package/dist/core/dist/templates/handlebars-helpers.d.ts +45 -0
  160. package/dist/core/dist/templates/handlebars-helpers.js +119 -0
  161. package/dist/core/dist/tools/claude/models.cjs +70 -0
  162. package/dist/core/dist/tools/claude/models.d.cts +27 -0
  163. package/dist/core/dist/tools/claude/models.d.ts +27 -0
  164. package/dist/core/dist/tools/claude/models.js +44 -0
  165. package/dist/core/dist/tools/index.cjs +3367 -0
  166. package/dist/core/dist/tools/index.d.cts +967 -0
  167. package/dist/core/dist/tools/index.d.ts +967 -0
  168. package/dist/core/dist/tools/index.js +3314 -0
  169. package/dist/core/dist/tools/models.cjs +119 -0
  170. package/dist/core/dist/tools/models.d.cts +47 -0
  171. package/dist/core/dist/tools/models.d.ts +47 -0
  172. package/dist/core/dist/tools/models.js +86 -0
  173. package/dist/core/dist/types/index.cjs +152 -0
  174. package/dist/core/dist/types/index.d.cts +214 -0
  175. package/dist/core/dist/types/index.d.ts +214 -0
  176. package/dist/core/dist/types/index.js +112 -0
  177. package/dist/core/dist/user-BmL3kFol.d.ts +50 -0
  178. package/dist/core/dist/user-eUuKj7yM.d.cts +50 -0
  179. package/dist/core/dist/utils/pricing.cjs +102 -0
  180. package/dist/core/dist/utils/pricing.d.cts +43 -0
  181. package/dist/core/dist/utils/pricing.d.ts +43 -0
  182. package/dist/core/dist/utils/pricing.js +75 -0
  183. package/dist/core/dist/worktrees-BzIxB1U6.d.cts +2745 -0
  184. package/dist/core/dist/worktrees-CYem1ya2.d.ts +2745 -0
  185. package/dist/core/environment/variable-resolver.cjs +92 -0
  186. package/dist/core/environment/variable-resolver.d.cts +52 -0
  187. package/dist/core/environment/variable-resolver.d.ts +52 -0
  188. package/dist/core/environment/variable-resolver.js +53 -0
  189. package/dist/core/feathers/index.cjs +66 -0
  190. package/dist/core/feathers/index.d.cts +7 -0
  191. package/dist/core/feathers/index.d.ts +7 -0
  192. package/dist/core/feathers/index.js +25 -0
  193. package/dist/core/feathers-BzHEPnpl.d.cts +228 -0
  194. package/dist/core/feathers-BzHEPnpl.d.ts +228 -0
  195. package/dist/core/git/index.cjs +302 -0
  196. package/dist/core/git/index.d.cts +137 -0
  197. package/dist/core/git/index.d.ts +137 -0
  198. package/dist/core/git/index.js +260 -0
  199. package/dist/core/id-DMqyogFB.d.cts +131 -0
  200. package/dist/core/id-DMqyogFB.d.ts +131 -0
  201. package/dist/core/index.cjs +4653 -0
  202. package/dist/core/index.d.cts +23 -0
  203. package/dist/core/index.d.ts +23 -0
  204. package/dist/core/index.js +4509 -0
  205. package/dist/core/message-BoxZISHg.d.cts +120 -0
  206. package/dist/core/message-DvBzHu7V.d.ts +120 -0
  207. package/dist/core/package.json +133 -0
  208. package/dist/core/permissions/index.cjs +112 -0
  209. package/dist/core/permissions/index.d.cts +81 -0
  210. package/dist/core/permissions/index.d.ts +81 -0
  211. package/dist/core/permissions/index.js +85 -0
  212. package/dist/core/repo-3CUrCRbq.d.cts +405 -0
  213. package/dist/core/repo-CnvJ0B6-.d.ts +405 -0
  214. package/dist/core/session-BPjJlVdZ.d.cts +429 -0
  215. package/dist/core/session-wAzjHatv.d.ts +429 -0
  216. package/dist/core/task-BIEgT1DK.d.cts +163 -0
  217. package/dist/core/task-DuIfiUbW.d.ts +163 -0
  218. package/dist/core/templates/handlebars-helpers.cjs +156 -0
  219. package/dist/core/templates/handlebars-helpers.d.cts +45 -0
  220. package/dist/core/templates/handlebars-helpers.d.ts +45 -0
  221. package/dist/core/templates/handlebars-helpers.js +119 -0
  222. package/dist/core/tools/claude/models.cjs +70 -0
  223. package/dist/core/tools/claude/models.d.cts +27 -0
  224. package/dist/core/tools/claude/models.d.ts +27 -0
  225. package/dist/core/tools/claude/models.js +44 -0
  226. package/dist/core/tools/index.cjs +3367 -0
  227. package/dist/core/tools/index.d.cts +967 -0
  228. package/dist/core/tools/index.d.ts +967 -0
  229. package/dist/core/tools/index.js +3314 -0
  230. package/dist/core/tools/models.cjs +119 -0
  231. package/dist/core/tools/models.d.cts +47 -0
  232. package/dist/core/tools/models.d.ts +47 -0
  233. package/dist/core/tools/models.js +86 -0
  234. package/dist/core/types/index.cjs +152 -0
  235. package/dist/core/types/index.d.cts +214 -0
  236. package/dist/core/types/index.d.ts +214 -0
  237. package/dist/core/types/index.js +112 -0
  238. package/dist/core/user-BmL3kFol.d.ts +50 -0
  239. package/dist/core/user-eUuKj7yM.d.cts +50 -0
  240. package/dist/core/utils/pricing.cjs +102 -0
  241. package/dist/core/utils/pricing.d.cts +43 -0
  242. package/dist/core/utils/pricing.d.ts +43 -0
  243. package/dist/core/utils/pricing.js +75 -0
  244. package/dist/core/worktrees-BzIxB1U6.d.cts +2745 -0
  245. package/dist/core/worktrees-CYem1ya2.d.ts +2745 -0
  246. package/dist/daemon/adapters/drizzle.d.ts +114 -0
  247. package/dist/daemon/adapters/drizzle.js +219 -0
  248. package/dist/daemon/declarations.d.ts +101 -0
  249. package/dist/daemon/declarations.js +0 -0
  250. package/dist/daemon/index.d.ts +2 -0
  251. package/dist/daemon/index.js +4093 -0
  252. package/dist/daemon/mcp/routes.d.ts +15 -0
  253. package/dist/daemon/mcp/routes.js +641 -0
  254. package/dist/daemon/mcp/tokens.d.ts +50 -0
  255. package/dist/daemon/mcp/tokens.js +85 -0
  256. package/dist/daemon/services/board-comments.d.ts +97 -0
  257. package/dist/daemon/services/board-comments.js +326 -0
  258. package/dist/daemon/services/board-objects.d.ts +71 -0
  259. package/dist/daemon/services/board-objects.js +117 -0
  260. package/dist/daemon/services/boards.d.ts +64 -0
  261. package/dist/daemon/services/boards.js +286 -0
  262. package/dist/daemon/services/config.d.ts +35 -0
  263. package/dist/daemon/services/config.js +68 -0
  264. package/dist/daemon/services/context.d.ts +55 -0
  265. package/dist/daemon/services/context.js +113 -0
  266. package/dist/daemon/services/health-monitor.d.ts +58 -0
  267. package/dist/daemon/services/health-monitor.js +158 -0
  268. package/dist/daemon/services/mcp-servers.d.ts +42 -0
  269. package/dist/daemon/services/mcp-servers.js +275 -0
  270. package/dist/daemon/services/messages.d.ts +49 -0
  271. package/dist/daemon/services/messages.js +269 -0
  272. package/dist/daemon/services/repos.d.ts +61 -0
  273. package/dist/daemon/services/repos.js +350 -0
  274. package/dist/daemon/services/session-mcp-servers.d.ts +56 -0
  275. package/dist/daemon/services/session-mcp-servers.js +51 -0
  276. package/dist/daemon/services/sessions.d.ts +64 -0
  277. package/dist/daemon/services/sessions.js +398 -0
  278. package/dist/daemon/services/tasks.d.ts +55 -0
  279. package/dist/daemon/services/tasks.js +318 -0
  280. package/dist/daemon/services/terminals.d.ts +75 -0
  281. package/dist/daemon/services/terminals.js +110 -0
  282. package/dist/daemon/services/users.d.ts +98 -0
  283. package/dist/daemon/services/users.js +177 -0
  284. package/dist/daemon/services/worktrees.d.ts +98 -0
  285. package/dist/daemon/services/worktrees.js +719 -0
  286. package/dist/daemon/strategies/anonymous.d.ts +20 -0
  287. package/dist/daemon/strategies/anonymous.js +32 -0
  288. package/dist/ui/assets/cc-CYmbalCD.png +0 -0
  289. package/dist/ui/assets/codex-4sLD1mVS.png +0 -0
  290. package/dist/ui/assets/cursor-BUy5pFVL.png +0 -0
  291. package/dist/ui/assets/gemini-ajOb7iAl.png +0 -0
  292. package/dist/ui/assets/index-Dc4ELxry.css +32 -0
  293. package/dist/ui/assets/index-KfIu8v4V.js +578 -0
  294. package/dist/ui/favicon.png +0 -0
  295. package/dist/ui/index.html +26 -0
  296. package/dist/ui/vite.svg +1 -0
  297. package/package.json +90 -0
@@ -0,0 +1,4653 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc2) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/lib/ids.ts
34
+ var ids_exports = {};
35
+ __export(ids_exports, {
36
+ IdResolutionError: () => IdResolutionError,
37
+ default: () => ids_default,
38
+ expandPrefix: () => expandPrefix,
39
+ findMinimumPrefixLength: () => findMinimumPrefixLength,
40
+ formatIdForDisplay: () => formatIdForDisplay,
41
+ formatShortId: () => formatShortId,
42
+ generateId: () => generateId,
43
+ isUniquePrefix: () => isUniquePrefix,
44
+ isValidShortID: () => isValidShortID,
45
+ isValidUUID: () => isValidUUID,
46
+ resolveShortId: () => resolveShortId,
47
+ shortId: () => shortId
48
+ });
49
+ function generateId() {
50
+ return (0, import_uuidv7.uuidv7)();
51
+ }
52
+ function isValidUUID(value) {
53
+ const uuidv7Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
54
+ return uuidv7Pattern.test(value);
55
+ }
56
+ function isValidShortID(value) {
57
+ return /^[0-9a-f]{8,32}$/i.test(value);
58
+ }
59
+ function shortId(uuid, length = 8) {
60
+ const cleanUuid = uuid.replace(/-/g, "");
61
+ return cleanUuid.slice(0, Math.min(length, 32));
62
+ }
63
+ function formatShortId(uuid, length = 8) {
64
+ return shortId(uuid, length);
65
+ }
66
+ function formatIdForDisplay(uuid, options = {}) {
67
+ if (options.verbose) {
68
+ return uuid;
69
+ }
70
+ return shortId(uuid, options.length);
71
+ }
72
+ function expandPrefix(prefix) {
73
+ const clean = prefix.replace(/-/g, "").toLowerCase();
74
+ if (clean.length === 0) {
75
+ throw new Error("ID prefix cannot be empty");
76
+ }
77
+ if (!isValidShortID(clean)) {
78
+ throw new Error(`Invalid ID prefix: ${prefix} (must be hexadecimal)`);
79
+ }
80
+ if (clean.length === 32) {
81
+ return formatUUIDWithHyphens(clean);
82
+ }
83
+ let formatted = "";
84
+ let pos = 0;
85
+ const sections = [8, 4, 4, 4, 12];
86
+ let offset = 0;
87
+ for (const sectionLength of sections) {
88
+ if (pos >= clean.length) break;
89
+ const section = clean.slice(pos, pos + sectionLength);
90
+ formatted += (offset > 0 ? "-" : "") + section;
91
+ pos += section.length;
92
+ if (section.length < sectionLength) {
93
+ return `${formatted}%`;
94
+ }
95
+ offset++;
96
+ }
97
+ return `${formatted}%`;
98
+ }
99
+ function formatUUIDWithHyphens(hex) {
100
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
101
+ }
102
+ function resolveShortId(prefix, entities) {
103
+ const cleanPrefix = prefix.replace(/-/g, "").toLowerCase();
104
+ const matches = entities.filter((e) => {
105
+ const cleanId = e.id.replace(/-/g, "").toLowerCase();
106
+ return cleanId.startsWith(cleanPrefix);
107
+ });
108
+ if (matches.length === 0) {
109
+ throw new IdResolutionError(
110
+ `No entity found with ID prefix: ${prefix}
111
+
112
+ Use 'agor <entity> list' to see available IDs.`,
113
+ "not_found",
114
+ prefix
115
+ );
116
+ }
117
+ if (matches.length === 1) {
118
+ return matches[0];
119
+ }
120
+ const suggestions = matches.slice(0, 10).map((m) => {
121
+ const description = getEntityDescription(m);
122
+ return ` - ${shortId(m.id, 12)}: ${description}`;
123
+ }).join("\n");
124
+ const ellipsis = matches.length > 10 ? `
125
+ ... and ${matches.length - 10} more` : "";
126
+ throw new IdResolutionError(
127
+ `Ambiguous ID prefix: ${prefix}
128
+
129
+ ${matches.length} matches found:
130
+ ${suggestions}${ellipsis}
131
+
132
+ Use a longer prefix to disambiguate.`,
133
+ "ambiguous",
134
+ prefix,
135
+ matches.map((m) => ({ id: m.id }))
136
+ );
137
+ }
138
+ function getEntityDescription(entity) {
139
+ if (entity.description) return entity.description;
140
+ if (entity.full_prompt) return truncate(entity.full_prompt, 60);
141
+ if (entity.name) return entity.name;
142
+ if (entity.agent) return `(${entity.agent} session)`;
143
+ return "(no description)";
144
+ }
145
+ function truncate(str, maxLength) {
146
+ if (str.length <= maxLength) return str;
147
+ return `${str.slice(0, maxLength - 3)}...`;
148
+ }
149
+ function findMinimumPrefixLength(ids) {
150
+ if (ids.length <= 1) return 8;
151
+ for (let length = 8; length <= 32; length++) {
152
+ const prefixes = new Set(ids.map((id) => shortId(id, length)));
153
+ if (prefixes.size === ids.length) {
154
+ return length;
155
+ }
156
+ }
157
+ return 32;
158
+ }
159
+ function isUniquePrefix(prefix, entities) {
160
+ try {
161
+ resolveShortId(prefix, entities);
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+ var import_uuidv7, IdResolutionError, ids_default;
168
+ var init_ids = __esm({
169
+ "src/lib/ids.ts"() {
170
+ "use strict";
171
+ import_uuidv7 = require("uuidv7");
172
+ IdResolutionError = class extends Error {
173
+ constructor(message, type, prefix, candidates) {
174
+ super(message);
175
+ this.type = type;
176
+ this.prefix = prefix;
177
+ this.candidates = candidates;
178
+ this.name = "IdResolutionError";
179
+ }
180
+ };
181
+ ids_default = {
182
+ generateId,
183
+ isValidUUID,
184
+ isValidShortID,
185
+ shortId,
186
+ formatShortId,
187
+ formatIdForDisplay,
188
+ expandPrefix,
189
+ resolveShortId,
190
+ findMinimumPrefixLength,
191
+ isUniquePrefix
192
+ };
193
+ }
194
+ });
195
+
196
+ // src/index.ts
197
+ var src_exports = {};
198
+ __export(src_exports, {
199
+ AmbiguousIdError: () => AmbiguousIdError,
200
+ BoardCommentsRepository: () => BoardCommentsRepository,
201
+ BoardObjectRepository: () => BoardObjectRepository,
202
+ BoardRepository: () => BoardRepository,
203
+ CommentAttachmentType: () => CommentAttachmentType,
204
+ DATABASE: () => DATABASE,
205
+ DEFAULT_ADMIN_USER: () => DEFAULT_ADMIN_USER,
206
+ DEFAULT_DB_PATH: () => DEFAULT_DB_PATH,
207
+ DatabaseConnectionError: () => DatabaseConnectionError,
208
+ ENVIRONMENT: () => ENVIRONMENT,
209
+ EntityNotFoundError: () => EntityNotFoundError,
210
+ GIT: () => GIT,
211
+ IdResolutionError: () => IdResolutionError,
212
+ MCPServerRepository: () => MCPServerRepository,
213
+ MessageRole: () => MessageRole,
214
+ MessagesRepository: () => MessagesRepository,
215
+ MigrationError: () => MigrationError,
216
+ PermissionScope: () => PermissionScope,
217
+ PermissionStatus: () => PermissionStatus,
218
+ RepoRepository: () => RepoRepository,
219
+ RepositoryError: () => RepositoryError,
220
+ SESSION: () => SESSION,
221
+ SessionMCPServerRepository: () => SessionMCPServerRepository,
222
+ SessionRepository: () => SessionRepository,
223
+ SessionStatus: () => SessionStatus,
224
+ TaskRepository: () => TaskRepository,
225
+ TaskStatus: () => TaskStatus,
226
+ WEBSOCKET: () => WEBSOCKET,
227
+ WorktreeRepository: () => WorktreeRepository,
228
+ and: () => import_drizzle_orm14.and,
229
+ boardComments: () => boardComments,
230
+ boardObjects: () => boardObjects,
231
+ boards: () => boards,
232
+ clearContext: () => clearContext,
233
+ cloneRepo: () => cloneRepo,
234
+ compare: () => compare,
235
+ createClient: () => createClient,
236
+ createDatabase: () => createDatabase,
237
+ createDefaultAdminUser: () => createDefaultAdminUser,
238
+ createLocalDatabase: () => createLocalDatabase,
239
+ createUser: () => createUser,
240
+ createWorktree: () => createWorktree,
241
+ desc: () => import_drizzle_orm14.desc,
242
+ eq: () => import_drizzle_orm14.eq,
243
+ extractRepoName: () => extractRepoName,
244
+ extractSlugFromUrl: () => extractSlugFromUrl,
245
+ formatRepoReference: () => formatRepoReference,
246
+ formatShortId: () => formatShortId,
247
+ generateId: () => generateId,
248
+ getAgorHome: () => getAgorHome,
249
+ getAllContext: () => getAllContext,
250
+ getCommentAttachmentType: () => getCommentAttachmentType,
251
+ getConfigPath: () => getConfigPath,
252
+ getConfigValue: () => getConfigValue,
253
+ getContext: () => getContext,
254
+ getCurrentBranch: () => getCurrentBranch,
255
+ getCurrentSha: () => getCurrentSha,
256
+ getDaemonUrl: () => getDaemonUrl,
257
+ getDefaultBranch: () => getDefaultBranch,
258
+ getDefaultConfig: () => getDefaultConfig,
259
+ getDefaultPermissionMode: () => getDefaultPermissionMode,
260
+ getDefaultRepoReference: () => getDefaultRepoReference,
261
+ getEffectiveConfig: () => getEffectiveConfig,
262
+ getGitState: () => getGitState,
263
+ getGroupedRepoReferenceOptions: () => getGroupedRepoReferenceOptions,
264
+ getRemoteBranches: () => getRemoteBranches,
265
+ getRemoteUrl: () => getRemoteUrl,
266
+ getRepoReferenceOptions: () => getRepoReferenceOptions,
267
+ getReposDir: () => getReposDir,
268
+ getUserByEmail: () => getUserByEmail,
269
+ getWorktreePath: () => getWorktreePath,
270
+ getWorktreesDir: () => getWorktreesDir,
271
+ groupReactions: () => groupReactions,
272
+ hasRemoteBranch: () => hasRemoteBranch,
273
+ hash: () => hash,
274
+ inArray: () => import_drizzle_orm14.inArray,
275
+ initConfig: () => initConfig,
276
+ initializeDatabase: () => initializeDatabase,
277
+ isClean: () => isClean,
278
+ isDaemonRunning: () => isDaemonRunning,
279
+ isDefined: () => isDefined,
280
+ isGitRepo: () => isGitRepo,
281
+ isNonEmptyString: () => isNonEmptyString,
282
+ isReply: () => isReply,
283
+ isResolvable: () => isResolvable,
284
+ isThreadRoot: () => isThreadRoot,
285
+ isValidSlug: () => isValidSlug,
286
+ like: () => import_drizzle_orm14.like,
287
+ listWorktrees: () => listWorktrees,
288
+ loadConfig: () => loadConfig,
289
+ mcpServers: () => mcpServers,
290
+ messages: () => messages,
291
+ or: () => import_drizzle_orm14.or,
292
+ parseRepoReference: () => parseRepoReference,
293
+ pruneWorktrees: () => pruneWorktrees,
294
+ removeWorktree: () => removeWorktree,
295
+ repos: () => repos,
296
+ resolveRepoReference: () => resolveRepoReference,
297
+ resolveShortId: () => resolveShortId,
298
+ resolveValue: () => resolveValue,
299
+ resolveValues: () => resolveValues,
300
+ runMigrations: () => runMigrations,
301
+ saveConfig: () => saveConfig,
302
+ seedInitialData: () => seedInitialData,
303
+ sessionMcpServers: () => sessionMcpServers,
304
+ sessions: () => sessions,
305
+ setConfigValue: () => setConfigValue,
306
+ setContext: () => setContext,
307
+ sql: () => import_drizzle_orm14.sql,
308
+ tasks: () => tasks,
309
+ unsetConfigValue: () => unsetConfigValue,
310
+ unsetContext: () => unsetContext,
311
+ userExists: () => userExists,
312
+ users: () => users,
313
+ worktrees: () => worktrees
314
+ });
315
+ module.exports = __toCommonJS(src_exports);
316
+
317
+ // src/api/index.ts
318
+ var import_authentication_client = __toESM(require("@feathersjs/authentication-client"), 1);
319
+ var import_feathers = require("@feathersjs/feathers");
320
+ var import_socketio_client = __toESM(require("@feathersjs/socketio-client"), 1);
321
+ var import_socket = __toESM(require("socket.io-client"), 1);
322
+ function createClient(url = "http://localhost:3030", autoConnect = true, options) {
323
+ const socket = (0, import_socket.default)(url, {
324
+ // Auto-connect by default for CLI, manual control for React hooks
325
+ autoConnect,
326
+ // Reconnection settings (less aggressive to prevent socket exhaustion)
327
+ reconnection: true,
328
+ reconnectionDelay: 1e3,
329
+ // Wait 1s before first reconnect attempt
330
+ reconnectionDelayMax: 2e3,
331
+ // Max 2s between attempts
332
+ reconnectionAttempts: 2,
333
+ // Only try 2 times before giving up (fast fail for CLI)
334
+ // Timeout settings
335
+ timeout: 2e3,
336
+ // 2s timeout for initial connection
337
+ // Transports (WebSocket preferred, fallback to polling)
338
+ transports: ["websocket", "polling"],
339
+ // Connection lifecycle settings
340
+ closeOnBeforeunload: true
341
+ // Close socket when page unloads
342
+ });
343
+ if (options?.verbose) {
344
+ let attemptCount = 0;
345
+ socket.on("connect_error", (error) => {
346
+ attemptCount++;
347
+ if (attemptCount === 1) {
348
+ console.error(`\u2717 Daemon not running at ${url}`);
349
+ console.error(` Retrying connection (${attemptCount}/2)...`);
350
+ } else {
351
+ console.error(` Retry ${attemptCount}/2 failed`);
352
+ }
353
+ });
354
+ socket.on("connect", () => {
355
+ if (attemptCount > 0) {
356
+ console.log("\u2713 Connected to daemon");
357
+ }
358
+ });
359
+ }
360
+ const client = (0, import_feathers.feathers)();
361
+ client.configure((0, import_socketio_client.default)(socket));
362
+ const storage = typeof globalThis !== "undefined" && "localStorage" in globalThis ? globalThis.localStorage : void 0;
363
+ client.configure((0, import_authentication_client.default)({ storage }));
364
+ client.io = socket;
365
+ return client;
366
+ }
367
+ async function isDaemonRunning(url = "http://localhost:3030") {
368
+ try {
369
+ const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(1e3) });
370
+ return response.ok;
371
+ } catch {
372
+ return false;
373
+ }
374
+ }
375
+
376
+ // src/config/config-manager.ts
377
+ var import_promises = __toESM(require("fs/promises"), 1);
378
+ var import_node_os = __toESM(require("os"), 1);
379
+ var import_node_path = __toESM(require("path"), 1);
380
+ var import_js_yaml = __toESM(require("js-yaml"), 1);
381
+ function getAgorHome() {
382
+ return import_node_path.default.join(import_node_os.default.homedir(), ".agor");
383
+ }
384
+ function getConfigPath() {
385
+ return import_node_path.default.join(getAgorHome(), "config.yaml");
386
+ }
387
+ async function ensureAgorHome() {
388
+ const agorHome = getAgorHome();
389
+ try {
390
+ await import_promises.default.access(agorHome);
391
+ } catch {
392
+ await import_promises.default.mkdir(agorHome, { recursive: true });
393
+ }
394
+ }
395
+ async function loadConfig() {
396
+ const configPath = getConfigPath();
397
+ try {
398
+ const content = await import_promises.default.readFile(configPath, "utf-8");
399
+ const config = import_js_yaml.default.load(content);
400
+ return config || {};
401
+ } catch (error) {
402
+ if (error.code === "ENOENT") {
403
+ return getDefaultConfig();
404
+ }
405
+ throw new Error(
406
+ `Failed to load config: ${error instanceof Error ? error.message : String(error)}`
407
+ );
408
+ }
409
+ }
410
+ async function saveConfig(config) {
411
+ await ensureAgorHome();
412
+ const configPath = getConfigPath();
413
+ const content = import_js_yaml.default.dump(config, {
414
+ indent: 2,
415
+ lineWidth: 120,
416
+ noRefs: true
417
+ });
418
+ await import_promises.default.writeFile(configPath, content, "utf-8");
419
+ }
420
+ function getDefaultConfig() {
421
+ return {
422
+ context: {},
423
+ defaults: {
424
+ board: "main",
425
+ agent: "claude-code"
426
+ },
427
+ display: {
428
+ tableStyle: "unicode",
429
+ colorOutput: true,
430
+ shortIdLength: 8
431
+ },
432
+ daemon: {
433
+ port: 3030,
434
+ host: "localhost",
435
+ allowAnonymous: true,
436
+ // Default: Allow anonymous access (local mode)
437
+ requireAuth: false
438
+ // Default: Do not require authentication
439
+ },
440
+ ui: {
441
+ port: 5173,
442
+ host: "localhost"
443
+ }
444
+ };
445
+ }
446
+ async function getContext(key) {
447
+ const config = await loadConfig();
448
+ return config.context?.[key];
449
+ }
450
+ async function setContext(key, value) {
451
+ const config = await loadConfig();
452
+ if (!config.context) {
453
+ config.context = {};
454
+ }
455
+ config.context[key] = value;
456
+ await saveConfig(config);
457
+ }
458
+ async function unsetContext(key) {
459
+ const config = await loadConfig();
460
+ if (config.context && key in config.context) {
461
+ delete config.context[key];
462
+ await saveConfig(config);
463
+ }
464
+ }
465
+ async function clearContext() {
466
+ const config = await loadConfig();
467
+ config.context = {};
468
+ await saveConfig(config);
469
+ }
470
+ async function getAllContext() {
471
+ const config = await loadConfig();
472
+ return config.context || {};
473
+ }
474
+ async function initConfig() {
475
+ const configPath = getConfigPath();
476
+ try {
477
+ await import_promises.default.access(configPath);
478
+ } catch {
479
+ await saveConfig(getDefaultConfig());
480
+ }
481
+ }
482
+ async function getConfigValue(key) {
483
+ const config = await loadConfig();
484
+ const defaults = getDefaultConfig();
485
+ const merged = {
486
+ ...defaults,
487
+ ...config,
488
+ defaults: { ...defaults.defaults, ...config.defaults },
489
+ display: { ...defaults.display, ...config.display },
490
+ daemon: { ...defaults.daemon, ...config.daemon },
491
+ ui: { ...defaults.ui, ...config.ui }
492
+ };
493
+ const parts = key.split(".");
494
+ let value = merged;
495
+ for (const part of parts) {
496
+ if (value && typeof value === "object" && part in value) {
497
+ value = value[part];
498
+ } else {
499
+ return void 0;
500
+ }
501
+ }
502
+ return value;
503
+ }
504
+ async function setConfigValue(key, value) {
505
+ const config = await loadConfig();
506
+ const parts = key.split(".");
507
+ if (parts.length === 1) {
508
+ if (!config.context) {
509
+ config.context = {};
510
+ }
511
+ config.context[parts[0]] = value;
512
+ } else {
513
+ const section = parts[0];
514
+ const _subKey = parts.slice(1).join(".");
515
+ if (!config[section]) {
516
+ config[section] = {};
517
+ }
518
+ if (parts.length === 2) {
519
+ config[section][parts[1]] = value;
520
+ } else {
521
+ throw new Error(`Nested keys beyond one level not supported: ${key}`);
522
+ }
523
+ }
524
+ await saveConfig(config);
525
+ }
526
+ async function unsetConfigValue(key) {
527
+ const config = await loadConfig();
528
+ const parts = key.split(".");
529
+ if (parts.length === 1) {
530
+ if (config.context && parts[0] in config.context) {
531
+ delete config.context[parts[0]];
532
+ }
533
+ } else if (parts.length === 2) {
534
+ const section = parts[0];
535
+ const subKey = parts[1];
536
+ if (config[section] && subKey in config[section]) {
537
+ delete config[section][subKey];
538
+ }
539
+ }
540
+ await saveConfig(config);
541
+ }
542
+ async function getDaemonUrl() {
543
+ const config = await loadConfig();
544
+ const defaults = getDefaultConfig();
545
+ const envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : void 0;
546
+ const port = envPort || config.daemon?.port || defaults.daemon?.port || 3030;
547
+ const host = config.daemon?.host || defaults.daemon?.host || "localhost";
548
+ return `http://${host}:${port}`;
549
+ }
550
+
551
+ // src/config/constants.ts
552
+ var ENVIRONMENT = {
553
+ /**
554
+ * Health check interval in milliseconds
555
+ * How often to poll environment health when status is 'running'
556
+ */
557
+ HEALTH_CHECK_INTERVAL_MS: 5e3,
558
+ // 5 seconds
559
+ /**
560
+ * Health check timeout in milliseconds
561
+ * How long to wait for health check response before considering it failed
562
+ */
563
+ HEALTH_CHECK_TIMEOUT_MS: 1e3,
564
+ // 1 second
565
+ /**
566
+ * Maximum number of log lines to store per worktree
567
+ * (Not stored in DB - reference for future log file implementation)
568
+ */
569
+ MAX_LOG_LINES: 100,
570
+ /**
571
+ * Process startup grace period in milliseconds
572
+ * How long to wait after starting before running health checks
573
+ */
574
+ STARTUP_GRACE_PERIOD_MS: 3e3,
575
+ // 3 seconds
576
+ /**
577
+ * Maximum consecutive health check failures before marking as 'error'
578
+ */
579
+ MAX_HEALTH_CHECK_FAILURES: 3
580
+ };
581
+ var WEBSOCKET = {
582
+ /**
583
+ * Cursor position broadcast throttle in milliseconds
584
+ */
585
+ CURSOR_THROTTLE_MS: 100,
586
+ /**
587
+ * Presence timeout in milliseconds (when to mark user as stale)
588
+ */
589
+ PRESENCE_TIMEOUT_MS: 1e4
590
+ // 10 seconds
591
+ };
592
+ var DATABASE = {
593
+ /**
594
+ * Batch size for bulk message inserts
595
+ */
596
+ MESSAGE_BATCH_SIZE: 100,
597
+ /**
598
+ * Batch size for bulk task inserts
599
+ */
600
+ TASK_BATCH_SIZE: 100,
601
+ /**
602
+ * Default pagination limit
603
+ */
604
+ DEFAULT_PAGE_SIZE: 50,
605
+ /**
606
+ * Maximum pagination limit
607
+ */
608
+ MAX_PAGE_SIZE: 100
609
+ };
610
+ var GIT = {
611
+ /**
612
+ * Default worktree base path (relative to ~/.agor)
613
+ */
614
+ WORKTREE_BASE_PATH: "worktrees",
615
+ /**
616
+ * Default repo clone path (relative to ~/.agor)
617
+ */
618
+ REPO_BASE_PATH: "repos"
619
+ };
620
+ var SESSION = {
621
+ /**
622
+ * Maximum number of messages to load initially
623
+ */
624
+ INITIAL_MESSAGE_LOAD: 100,
625
+ /**
626
+ * Message streaming chunk size
627
+ */
628
+ STREAMING_CHUNK_SIZE: 1
629
+ };
630
+
631
+ // src/config/context-resolver.ts
632
+ async function resolveValue(key, flagValue, config) {
633
+ if (flagValue !== void 0 && flagValue !== null && flagValue !== "") {
634
+ return flagValue;
635
+ }
636
+ const cfg = config || await loadConfig();
637
+ const contextValue = cfg.context?.[key];
638
+ if (contextValue !== void 0 && contextValue !== null && contextValue !== "") {
639
+ return contextValue;
640
+ }
641
+ const defaultValue = cfg.defaults?.[key];
642
+ if (defaultValue !== void 0 && defaultValue !== null && defaultValue !== "") {
643
+ return defaultValue;
644
+ }
645
+ return void 0;
646
+ }
647
+ async function resolveValues(keys, flagValues, config) {
648
+ const cfg = config || await loadConfig();
649
+ const resolved = {};
650
+ for (const key of keys) {
651
+ const value = await resolveValue(key, flagValues?.[key], cfg);
652
+ if (value !== void 0) {
653
+ resolved[key] = value;
654
+ }
655
+ }
656
+ return resolved;
657
+ }
658
+ async function getEffectiveConfig(config) {
659
+ const cfg = config || await loadConfig();
660
+ return {
661
+ // Context values
662
+ board: await resolveValue("board", void 0, cfg),
663
+ session: await resolveValue("session", void 0, cfg),
664
+ repo: await resolveValue("repo", void 0, cfg),
665
+ agent: await resolveValue("agent", void 0, cfg),
666
+ // Display settings
667
+ tableStyle: cfg.display?.tableStyle,
668
+ colorOutput: cfg.display?.colorOutput !== void 0 ? String(cfg.display.colorOutput) : void 0,
669
+ shortIdLength: cfg.display?.shortIdLength !== void 0 ? String(cfg.display.shortIdLength) : void 0
670
+ };
671
+ }
672
+
673
+ // src/config/repo-list.ts
674
+ function getRepoReferenceOptions(repos2, worktrees2 = []) {
675
+ const options = [];
676
+ const repoMap = new Map(repos2.map((repo) => [repo.repo_id, repo]));
677
+ for (const repo of repos2) {
678
+ options.push({
679
+ label: repo.slug,
680
+ value: repo.repo_id,
681
+ // Use repo_id as value
682
+ type: "managed",
683
+ slug: repo.slug,
684
+ description: `${repo.name} (bare repo)`
685
+ });
686
+ }
687
+ for (const worktree of worktrees2) {
688
+ const repo = repoMap.get(worktree.repo_id);
689
+ if (!repo) continue;
690
+ const reference = `${repo.slug}:${worktree.name}`;
691
+ options.push({
692
+ label: reference,
693
+ value: worktree.worktree_id,
694
+ // Use worktree_id as value
695
+ type: "managed-worktree",
696
+ slug: repo.slug,
697
+ worktree: worktree.name,
698
+ description: `${repo.name} - ${worktree.name} (${worktree.ref})`
699
+ });
700
+ }
701
+ return options;
702
+ }
703
+ function getGroupedRepoReferenceOptions(repos2, worktrees2 = []) {
704
+ const grouped = {};
705
+ const repoMap = new Map(repos2.map((repo) => [repo.repo_id, repo]));
706
+ for (const repo of repos2) {
707
+ const options = [];
708
+ options.push({
709
+ label: repo.slug,
710
+ value: repo.repo_id,
711
+ // Use repo_id as value
712
+ type: "managed",
713
+ slug: repo.slug,
714
+ description: `${repo.name} (bare repo)`
715
+ });
716
+ grouped[repo.slug] = options;
717
+ }
718
+ for (const worktree of worktrees2) {
719
+ const repo = repoMap.get(worktree.repo_id);
720
+ if (!repo) continue;
721
+ const reference = `${repo.slug}:${worktree.name}`;
722
+ if (!grouped[repo.slug]) {
723
+ grouped[repo.slug] = [];
724
+ }
725
+ grouped[repo.slug].push({
726
+ label: reference,
727
+ value: worktree.worktree_id,
728
+ // Use worktree_id as value
729
+ type: "managed-worktree",
730
+ slug: repo.slug,
731
+ worktree: worktree.name,
732
+ description: `${repo.name} - ${worktree.name} (${worktree.ref})`
733
+ });
734
+ }
735
+ return grouped;
736
+ }
737
+ function getDefaultRepoReference(repos2) {
738
+ if (repos2.length === 0) return void 0;
739
+ const firstRepo = repos2[0];
740
+ return firstRepo.slug;
741
+ }
742
+
743
+ // src/config/repo-reference.ts
744
+ function parseRepoReference(ref) {
745
+ if (ref.startsWith("/") || /^[A-Z]:\\/.test(ref)) {
746
+ return { type: "path", path: ref };
747
+ }
748
+ if (ref.includes(":")) {
749
+ const [slug, worktree] = ref.split(":", 2);
750
+ return { type: "managed-worktree", slug, worktree };
751
+ }
752
+ return { type: "managed", slug: ref };
753
+ }
754
+ function extractSlugFromUrl(url) {
755
+ const cleanUrl = url.endsWith(".git") ? url.slice(0, -4) : url;
756
+ if (cleanUrl.includes("@")) {
757
+ const match2 = cleanUrl.match(/:([^/]+\/[^/]+)$/);
758
+ if (match2) {
759
+ return match2[1];
760
+ }
761
+ }
762
+ const match = cleanUrl.match(/[:/]([^/]+\/[^/]+)$/);
763
+ if (match) {
764
+ return match[1];
765
+ }
766
+ const segments = cleanUrl.split("/").filter(Boolean);
767
+ if (segments.length >= 2) {
768
+ return `${segments[segments.length - 2]}/${segments[segments.length - 1]}`;
769
+ }
770
+ throw new Error(`Could not extract slug from URL: ${url}`);
771
+ }
772
+ function isValidSlug(slug) {
773
+ const slugPattern = /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/;
774
+ return slugPattern.test(slug);
775
+ }
776
+ async function resolveRepoReference(ref) {
777
+ const parsed = parseRepoReference(ref);
778
+ if (parsed.type === "path") {
779
+ return {
780
+ cwd: parsed.path,
781
+ managed_worktree: false
782
+ };
783
+ }
784
+ throw new Error(
785
+ `Repository lookup not implemented in this context. Parsed reference: ${JSON.stringify(parsed)}`
786
+ );
787
+ }
788
+ function formatRepoReference(slug, worktreeName) {
789
+ if (worktreeName) {
790
+ return `${slug}:${worktreeName}`;
791
+ }
792
+ return slug;
793
+ }
794
+
795
+ // src/db/index.ts
796
+ var import_drizzle_orm14 = require("drizzle-orm");
797
+ var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
798
+ init_ids();
799
+
800
+ // src/db/client.ts
801
+ var import_client = require("@libsql/client");
802
+ var import_libsql = require("drizzle-orm/libsql");
803
+
804
+ // src/db/schema.ts
805
+ var schema_exports = {};
806
+ __export(schema_exports, {
807
+ boardComments: () => boardComments,
808
+ boardObjects: () => boardObjects,
809
+ boards: () => boards,
810
+ mcpServers: () => mcpServers,
811
+ messages: () => messages,
812
+ repos: () => repos,
813
+ sessionMcpServers: () => sessionMcpServers,
814
+ sessions: () => sessions,
815
+ tasks: () => tasks,
816
+ users: () => users,
817
+ worktrees: () => worktrees
818
+ });
819
+ var import_drizzle_orm = require("drizzle-orm");
820
+ var import_sqlite_core = require("drizzle-orm/sqlite-core");
821
+ var sessions = (0, import_sqlite_core.sqliteTable)(
822
+ "sessions",
823
+ {
824
+ // Primary identity
825
+ session_id: (0, import_sqlite_core.text)("session_id", { length: 36 }).primaryKey(),
826
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
827
+ updated_at: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp_ms" }),
828
+ // User attribution
829
+ created_by: (0, import_sqlite_core.text)("created_by", { length: 36 }).notNull().default("anonymous"),
830
+ // Materialized for filtering/joins (cross-DB compatible)
831
+ status: (0, import_sqlite_core.text)("status", {
832
+ enum: ["idle", "running", "completed", "failed"]
833
+ }).notNull(),
834
+ agentic_tool: (0, import_sqlite_core.text)("agentic_tool", {
835
+ enum: ["claude-code", "cursor", "codex", "gemini"]
836
+ }).notNull(),
837
+ board_id: (0, import_sqlite_core.text)("board_id", { length: 36 }),
838
+ // NULL = no board
839
+ // Genealogy (materialized for tree queries)
840
+ parent_session_id: (0, import_sqlite_core.text)("parent_session_id", { length: 36 }),
841
+ forked_from_session_id: (0, import_sqlite_core.text)("forked_from_session_id", { length: 36 }),
842
+ // Worktree reference (REQUIRED: all sessions must have a worktree)
843
+ worktree_id: (0, import_sqlite_core.text)("worktree_id", { length: 36 }).notNull().references(() => worktrees.worktree_id, {
844
+ onDelete: "cascade"
845
+ // Cascade delete sessions when worktree is deleted
846
+ }),
847
+ // JSON blob for everything else (cross-DB via json() type)
848
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
849
+ },
850
+ (table) => ({
851
+ statusIdx: (0, import_sqlite_core.index)("sessions_status_idx").on(table.status),
852
+ agenticToolIdx: (0, import_sqlite_core.index)("sessions_agentic_tool_idx").on(table.agentic_tool),
853
+ boardIdx: (0, import_sqlite_core.index)("sessions_board_idx").on(table.board_id),
854
+ worktreeIdx: (0, import_sqlite_core.index)("sessions_worktree_idx").on(table.worktree_id),
855
+ createdIdx: (0, import_sqlite_core.index)("sessions_created_idx").on(table.created_at),
856
+ parentIdx: (0, import_sqlite_core.index)("sessions_parent_idx").on(table.parent_session_id),
857
+ forkedIdx: (0, import_sqlite_core.index)("sessions_forked_idx").on(table.forked_from_session_id)
858
+ })
859
+ );
860
+ var tasks = (0, import_sqlite_core.sqliteTable)(
861
+ "tasks",
862
+ {
863
+ task_id: (0, import_sqlite_core.text)("task_id", { length: 36 }).primaryKey(),
864
+ session_id: (0, import_sqlite_core.text)("session_id", { length: 36 }).notNull().references(() => sessions.session_id, { onDelete: "cascade" }),
865
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
866
+ completed_at: (0, import_sqlite_core.integer)("completed_at", { mode: "timestamp_ms" }),
867
+ status: (0, import_sqlite_core.text)("status", {
868
+ enum: [
869
+ "created",
870
+ "running",
871
+ "stopping",
872
+ "awaiting_permission",
873
+ "completed",
874
+ "failed",
875
+ "stopped"
876
+ ]
877
+ }).notNull(),
878
+ // User attribution
879
+ created_by: (0, import_sqlite_core.text)("created_by", { length: 36 }).notNull().default("anonymous"),
880
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
881
+ },
882
+ (table) => ({
883
+ sessionIdx: (0, import_sqlite_core.index)("tasks_session_idx").on(table.session_id),
884
+ statusIdx: (0, import_sqlite_core.index)("tasks_status_idx").on(table.status),
885
+ createdIdx: (0, import_sqlite_core.index)("tasks_created_idx").on(table.created_at)
886
+ })
887
+ );
888
+ var messages = (0, import_sqlite_core.sqliteTable)(
889
+ "messages",
890
+ {
891
+ // Primary identity
892
+ message_id: (0, import_sqlite_core.text)("message_id", { length: 36 }).primaryKey(),
893
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
894
+ // Foreign keys (materialized for indexes)
895
+ session_id: (0, import_sqlite_core.text)("session_id", { length: 36 }).notNull().references(() => sessions.session_id, { onDelete: "cascade" }),
896
+ task_id: (0, import_sqlite_core.text)("task_id", { length: 36 }).references(() => tasks.task_id, {
897
+ onDelete: "set null"
898
+ }),
899
+ // Materialized for queries
900
+ type: (0, import_sqlite_core.text)("type", {
901
+ enum: ["user", "assistant", "system", "file-history-snapshot", "permission_request"]
902
+ }).notNull(),
903
+ role: (0, import_sqlite_core.text)("role", {
904
+ enum: ["user", "assistant", "system"]
905
+ }).notNull(),
906
+ index: (0, import_sqlite_core.integer)("index").notNull(),
907
+ // Position in conversation (0-based)
908
+ timestamp: (0, import_sqlite_core.integer)("timestamp", { mode: "timestamp_ms" }).notNull(),
909
+ content_preview: (0, import_sqlite_core.text)("content_preview"),
910
+ // First 200 chars for list views
911
+ // Full data (JSON blob)
912
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
913
+ },
914
+ (table) => ({
915
+ // Indexes for efficient lookups
916
+ sessionIdx: (0, import_sqlite_core.index)("messages_session_id_idx").on(table.session_id),
917
+ taskIdx: (0, import_sqlite_core.index)("messages_task_id_idx").on(table.task_id),
918
+ sessionIndexIdx: (0, import_sqlite_core.index)("messages_session_index_idx").on(table.session_id, table.index)
919
+ })
920
+ );
921
+ var boards = (0, import_sqlite_core.sqliteTable)(
922
+ "boards",
923
+ {
924
+ board_id: (0, import_sqlite_core.text)("board_id", { length: 36 }).primaryKey(),
925
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
926
+ updated_at: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp_ms" }),
927
+ // User attribution
928
+ created_by: (0, import_sqlite_core.text)("created_by", { length: 36 }).notNull().default("anonymous"),
929
+ // Materialized for lookups
930
+ name: (0, import_sqlite_core.text)("name").notNull(),
931
+ slug: (0, import_sqlite_core.text)("slug").unique(),
932
+ // JSON blob for the rest
933
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
934
+ },
935
+ (table) => ({
936
+ nameIdx: (0, import_sqlite_core.index)("boards_name_idx").on(table.name),
937
+ slugIdx: (0, import_sqlite_core.index)("boards_slug_idx").on(table.slug)
938
+ })
939
+ );
940
+ var repos = (0, import_sqlite_core.sqliteTable)(
941
+ "repos",
942
+ {
943
+ repo_id: (0, import_sqlite_core.text)("repo_id", { length: 36 }).primaryKey(),
944
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
945
+ updated_at: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp_ms" }),
946
+ // Materialized for querying
947
+ slug: (0, import_sqlite_core.text)("slug").notNull().unique(),
948
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
949
+ },
950
+ (table) => ({
951
+ slugIdx: (0, import_sqlite_core.index)("repos_slug_idx").on(table.slug)
952
+ })
953
+ );
954
+ var worktrees = (0, import_sqlite_core.sqliteTable)(
955
+ "worktrees",
956
+ {
957
+ // Primary identity
958
+ worktree_id: (0, import_sqlite_core.text)("worktree_id", { length: 36 }).primaryKey(),
959
+ repo_id: (0, import_sqlite_core.text)("repo_id", { length: 36 }).notNull().references(() => repos.repo_id, { onDelete: "cascade" }),
960
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
961
+ updated_at: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp_ms" }),
962
+ // User attribution
963
+ created_by: (0, import_sqlite_core.text)("created_by", { length: 36 }).notNull().default("anonymous"),
964
+ // Materialized for queries
965
+ name: (0, import_sqlite_core.text)("name").notNull(),
966
+ // "feat-auth", "main"
967
+ ref: (0, import_sqlite_core.text)("ref").notNull(),
968
+ // Current branch/tag/commit
969
+ worktree_unique_id: (0, import_sqlite_core.integer)("worktree_unique_id").notNull(),
970
+ // Auto-assigned sequential ID for templates
971
+ // Board relationship (nullable - worktrees can exist without boards)
972
+ board_id: (0, import_sqlite_core.text)("board_id", { length: 36 }).references(() => boards.board_id, {
973
+ onDelete: "set null"
974
+ // If board is deleted, worktree remains but loses board association
975
+ }),
976
+ // JSON blob for everything else
977
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
978
+ },
979
+ (table) => ({
980
+ repoIdx: (0, import_sqlite_core.index)("worktrees_repo_idx").on(table.repo_id),
981
+ nameIdx: (0, import_sqlite_core.index)("worktrees_name_idx").on(table.name),
982
+ refIdx: (0, import_sqlite_core.index)("worktrees_ref_idx").on(table.ref),
983
+ boardIdx: (0, import_sqlite_core.index)("worktrees_board_idx").on(table.board_id),
984
+ createdIdx: (0, import_sqlite_core.index)("worktrees_created_idx").on(table.created_at),
985
+ updatedIdx: (0, import_sqlite_core.index)("worktrees_updated_idx").on(table.updated_at),
986
+ // Composite unique constraint (repo + name)
987
+ uniqueRepoName: (0, import_sqlite_core.index)("worktrees_repo_name_unique").on(table.repo_id, table.name)
988
+ })
989
+ );
990
+ var users = (0, import_sqlite_core.sqliteTable)(
991
+ "users",
992
+ {
993
+ // Primary identity
994
+ user_id: (0, import_sqlite_core.text)("user_id", { length: 36 }).primaryKey(),
995
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
996
+ updated_at: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp_ms" }),
997
+ // Materialized for auth lookups
998
+ email: (0, import_sqlite_core.text)("email").unique().notNull(),
999
+ password: (0, import_sqlite_core.text)("password").notNull(),
1000
+ // bcrypt hashed
1001
+ // Basic profile (materialized for display)
1002
+ name: (0, import_sqlite_core.text)("name"),
1003
+ emoji: (0, import_sqlite_core.text)("emoji"),
1004
+ role: (0, import_sqlite_core.text)("role", {
1005
+ enum: ["owner", "admin", "member", "viewer"]
1006
+ }).notNull().default("member"),
1007
+ // Onboarding state
1008
+ onboarding_completed: (0, import_sqlite_core.integer)("onboarding_completed", { mode: "boolean" }).notNull().default(false),
1009
+ // JSON blob for profile/preferences
1010
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
1011
+ },
1012
+ (table) => ({
1013
+ emailIdx: (0, import_sqlite_core.index)("users_email_idx").on(table.email)
1014
+ })
1015
+ );
1016
+ var mcpServers = (0, import_sqlite_core.sqliteTable)(
1017
+ "mcp_servers",
1018
+ {
1019
+ // Primary identity
1020
+ mcp_server_id: (0, import_sqlite_core.text)("mcp_server_id", { length: 36 }).primaryKey(),
1021
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
1022
+ updated_at: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp_ms" }),
1023
+ // Materialized for filtering
1024
+ name: (0, import_sqlite_core.text)("name").notNull(),
1025
+ // e.g., "filesystem", "sentry"
1026
+ transport: (0, import_sqlite_core.text)("transport", {
1027
+ enum: ["stdio", "http", "sse"]
1028
+ }).notNull(),
1029
+ scope: (0, import_sqlite_core.text)("scope", {
1030
+ enum: ["global", "team", "repo", "session"]
1031
+ }).notNull(),
1032
+ enabled: (0, import_sqlite_core.integer)("enabled", { mode: "boolean" }).notNull().default(true),
1033
+ // Scope foreign keys (materialized for indexes)
1034
+ owner_user_id: (0, import_sqlite_core.text)("owner_user_id", { length: 36 }),
1035
+ team_id: (0, import_sqlite_core.text)("team_id", { length: 36 }),
1036
+ repo_id: (0, import_sqlite_core.text)("repo_id", { length: 36 }).references(() => repos.repo_id, {
1037
+ onDelete: "cascade"
1038
+ }),
1039
+ session_id: (0, import_sqlite_core.text)("session_id", { length: 36 }).references(() => sessions.session_id, {
1040
+ onDelete: "cascade"
1041
+ }),
1042
+ // Source tracking (materialized for queries)
1043
+ source: (0, import_sqlite_core.text)("source", {
1044
+ enum: ["user", "imported", "agor"]
1045
+ }).notNull(),
1046
+ // JSON blob for configuration and capabilities
1047
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
1048
+ },
1049
+ (table) => ({
1050
+ nameIdx: (0, import_sqlite_core.index)("mcp_servers_name_idx").on(table.name),
1051
+ scopeIdx: (0, import_sqlite_core.index)("mcp_servers_scope_idx").on(table.scope),
1052
+ ownerIdx: (0, import_sqlite_core.index)("mcp_servers_owner_idx").on(table.owner_user_id),
1053
+ teamIdx: (0, import_sqlite_core.index)("mcp_servers_team_idx").on(table.team_id),
1054
+ repoIdx: (0, import_sqlite_core.index)("mcp_servers_repo_idx").on(table.repo_id),
1055
+ sessionIdx: (0, import_sqlite_core.index)("mcp_servers_session_idx").on(table.session_id),
1056
+ enabledIdx: (0, import_sqlite_core.index)("mcp_servers_enabled_idx").on(table.enabled)
1057
+ })
1058
+ );
1059
+ var boardObjects = (0, import_sqlite_core.sqliteTable)(
1060
+ "board_objects",
1061
+ {
1062
+ // Primary identity
1063
+ object_id: (0, import_sqlite_core.text)("object_id", { length: 36 }).primaryKey(),
1064
+ board_id: (0, import_sqlite_core.text)("board_id", { length: 36 }).notNull().references(() => boards.board_id, { onDelete: "cascade" }),
1065
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
1066
+ // Worktree reference
1067
+ worktree_id: (0, import_sqlite_core.text)("worktree_id", { length: 36 }).notNull().references(() => worktrees.worktree_id, {
1068
+ onDelete: "cascade"
1069
+ }),
1070
+ // Position data (JSON)
1071
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
1072
+ },
1073
+ (table) => ({
1074
+ boardIdx: (0, import_sqlite_core.index)("board_objects_board_idx").on(table.board_id),
1075
+ worktreeIdx: (0, import_sqlite_core.index)("board_objects_worktree_idx").on(table.worktree_id)
1076
+ })
1077
+ );
1078
+ var sessionMcpServers = (0, import_sqlite_core.sqliteTable)(
1079
+ "session_mcp_servers",
1080
+ {
1081
+ session_id: (0, import_sqlite_core.text)("session_id", { length: 36 }).notNull().references(() => sessions.session_id, { onDelete: "cascade" }),
1082
+ mcp_server_id: (0, import_sqlite_core.text)("mcp_server_id", { length: 36 }).notNull().references(() => mcpServers.mcp_server_id, { onDelete: "cascade" }),
1083
+ enabled: (0, import_sqlite_core.integer)("enabled", { mode: "boolean" }).notNull().default(true),
1084
+ added_at: (0, import_sqlite_core.integer)("added_at", { mode: "timestamp_ms" }).notNull()
1085
+ },
1086
+ (table) => ({
1087
+ // Composite primary key
1088
+ pk: (0, import_sqlite_core.index)("session_mcp_servers_pk").on(table.session_id, table.mcp_server_id),
1089
+ // Indexes for queries
1090
+ sessionIdx: (0, import_sqlite_core.index)("session_mcp_servers_session_idx").on(table.session_id),
1091
+ serverIdx: (0, import_sqlite_core.index)("session_mcp_servers_server_idx").on(table.mcp_server_id),
1092
+ enabledIdx: (0, import_sqlite_core.index)("session_mcp_servers_enabled_idx").on(table.session_id, table.enabled)
1093
+ })
1094
+ );
1095
+ var boardComments = (0, import_sqlite_core.sqliteTable)(
1096
+ "board_comments",
1097
+ {
1098
+ // Primary identity
1099
+ comment_id: (0, import_sqlite_core.text)("comment_id", { length: 36 }).primaryKey(),
1100
+ created_at: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp_ms" }).notNull(),
1101
+ updated_at: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp_ms" }),
1102
+ // Scoping & authorship
1103
+ board_id: (0, import_sqlite_core.text)("board_id", { length: 36 }).notNull().references(() => boards.board_id, { onDelete: "cascade" }),
1104
+ created_by: (0, import_sqlite_core.text)("created_by", { length: 36 }).notNull().default("anonymous"),
1105
+ // FLEXIBLE ATTACHMENTS (all optional)
1106
+ // Phase 1: board-level only (all NULL)
1107
+ // Phase 2: object attachments (session, task, message, worktree)
1108
+ // Phase 3: spatial positioning
1109
+ session_id: (0, import_sqlite_core.text)("session_id", { length: 36 }).references(() => sessions.session_id, {
1110
+ onDelete: "set null"
1111
+ }),
1112
+ task_id: (0, import_sqlite_core.text)("task_id", { length: 36 }).references(() => tasks.task_id, {
1113
+ onDelete: "set null"
1114
+ }),
1115
+ message_id: (0, import_sqlite_core.text)("message_id", { length: 36 }).references(() => messages.message_id, {
1116
+ onDelete: "set null"
1117
+ }),
1118
+ worktree_id: (0, import_sqlite_core.text)("worktree_id", { length: 36 }).references(() => worktrees.worktree_id, {
1119
+ onDelete: "set null"
1120
+ }),
1121
+ // Content (materialized for display)
1122
+ content: (0, import_sqlite_core.text)("content").notNull(),
1123
+ // Markdown-supported text
1124
+ content_preview: (0, import_sqlite_core.text)("content_preview").notNull(),
1125
+ // First 200 chars
1126
+ // Thread support (optional)
1127
+ parent_comment_id: (0, import_sqlite_core.text)("parent_comment_id", { length: 36 }),
1128
+ // Metadata (materialized for filtering)
1129
+ resolved: (0, import_sqlite_core.integer)("resolved", { mode: "boolean" }).notNull().default(false),
1130
+ edited: (0, import_sqlite_core.integer)("edited", { mode: "boolean" }).notNull().default(false),
1131
+ // Reactions (for BOTH thread roots and replies)
1132
+ // Stored as JSON array: [{ user_id: "abc", emoji: "👍" }, ...]
1133
+ // Display grouped by emoji: { "👍": ["alice", "bob"], "🎉": ["charlie"] }
1134
+ reactions: (0, import_sqlite_core.text)("reactions", { mode: "json" }).$type().notNull().default(import_drizzle_orm.sql`'[]'`),
1135
+ // JSON blob for advanced features
1136
+ data: (0, import_sqlite_core.text)("data", { mode: "json" }).$type().notNull()
1137
+ },
1138
+ (table) => ({
1139
+ boardIdx: (0, import_sqlite_core.index)("board_comments_board_idx").on(table.board_id),
1140
+ sessionIdx: (0, import_sqlite_core.index)("board_comments_session_idx").on(table.session_id),
1141
+ taskIdx: (0, import_sqlite_core.index)("board_comments_task_idx").on(table.task_id),
1142
+ messageIdx: (0, import_sqlite_core.index)("board_comments_message_idx").on(table.message_id),
1143
+ worktreeIdx: (0, import_sqlite_core.index)("board_comments_worktree_idx").on(table.worktree_id),
1144
+ createdByIdx: (0, import_sqlite_core.index)("board_comments_created_by_idx").on(table.created_by),
1145
+ parentIdx: (0, import_sqlite_core.index)("board_comments_parent_idx").on(table.parent_comment_id),
1146
+ createdIdx: (0, import_sqlite_core.index)("board_comments_created_idx").on(table.created_at),
1147
+ resolvedIdx: (0, import_sqlite_core.index)("board_comments_resolved_idx").on(table.resolved)
1148
+ })
1149
+ );
1150
+
1151
+ // src/db/client.ts
1152
+ var DatabaseConnectionError = class extends Error {
1153
+ constructor(message, cause) {
1154
+ super(message);
1155
+ this.cause = cause;
1156
+ this.name = "DatabaseConnectionError";
1157
+ }
1158
+ };
1159
+ function expandPath(path2) {
1160
+ if (path2.startsWith("~/")) {
1161
+ const home = process.env.HOME || process.env.USERPROFILE || "";
1162
+ return path2.replace("~", home);
1163
+ }
1164
+ return path2;
1165
+ }
1166
+ function createLibSQLClient(config) {
1167
+ try {
1168
+ let url = config.url;
1169
+ if (url.startsWith("file:")) {
1170
+ const filePath = url.slice(5);
1171
+ const expandedPath = expandPath(filePath);
1172
+ url = `file:${expandedPath}`;
1173
+ }
1174
+ const clientConfig = { url };
1175
+ if (config.authToken) {
1176
+ clientConfig.authToken = config.authToken;
1177
+ }
1178
+ if (config.syncUrl) {
1179
+ clientConfig.syncUrl = config.syncUrl;
1180
+ clientConfig.syncInterval = config.syncInterval ?? 60;
1181
+ }
1182
+ return (0, import_client.createClient)(clientConfig);
1183
+ } catch (error) {
1184
+ throw new DatabaseConnectionError(
1185
+ `Failed to create LibSQL client: ${error instanceof Error ? error.message : String(error)}`,
1186
+ error
1187
+ );
1188
+ }
1189
+ }
1190
+ function createDatabase(config) {
1191
+ const client = createLibSQLClient(config);
1192
+ return (0, import_libsql.drizzle)(client, { schema: schema_exports });
1193
+ }
1194
+ var DEFAULT_DB_PATH = "file:~/.agor/agor.db";
1195
+ function createLocalDatabase(customPath) {
1196
+ return createDatabase({ url: customPath ?? DEFAULT_DB_PATH });
1197
+ }
1198
+
1199
+ // src/db/migrate.ts
1200
+ var import_drizzle_orm2 = require("drizzle-orm");
1201
+ var import_migrator = require("drizzle-orm/libsql/migrator");
1202
+ var MigrationError = class extends Error {
1203
+ constructor(message, cause) {
1204
+ super(message);
1205
+ this.cause = cause;
1206
+ this.name = "MigrationError";
1207
+ }
1208
+ };
1209
+ async function tablesExist(db) {
1210
+ try {
1211
+ const result = await db.run(import_drizzle_orm2.sql`
1212
+ SELECT name FROM sqlite_master
1213
+ WHERE type='table' AND name IN ('sessions', 'tasks', 'boards', 'repos', 'worktrees', 'messages', 'users', 'board_comments')
1214
+ `);
1215
+ return result.rows.length > 0;
1216
+ } catch (error) {
1217
+ throw new MigrationError(
1218
+ `Failed to check if tables exist: ${error instanceof Error ? error.message : String(error)}`,
1219
+ error
1220
+ );
1221
+ }
1222
+ }
1223
+ async function tableExists(db, tableName) {
1224
+ try {
1225
+ const result = await db.run(import_drizzle_orm2.sql`
1226
+ SELECT name FROM sqlite_master
1227
+ WHERE type='table' AND name = ${tableName}
1228
+ `);
1229
+ return result.rows.length > 0;
1230
+ } catch (error) {
1231
+ throw new MigrationError(
1232
+ `Failed to check if table ${tableName} exists: ${error instanceof Error ? error.message : String(error)}`,
1233
+ error
1234
+ );
1235
+ }
1236
+ }
1237
+ async function createInitialSchema(db) {
1238
+ try {
1239
+ await db.run(import_drizzle_orm2.sql`
1240
+ CREATE TABLE IF NOT EXISTS sessions (
1241
+ session_id TEXT PRIMARY KEY,
1242
+ created_at INTEGER NOT NULL,
1243
+ updated_at INTEGER,
1244
+ created_by TEXT NOT NULL DEFAULT 'anonymous',
1245
+ status TEXT NOT NULL CHECK(status IN ('idle', 'running', 'completed', 'failed')),
1246
+ agentic_tool TEXT NOT NULL CHECK(agentic_tool IN ('claude-code', 'cursor', 'codex', 'gemini')),
1247
+ board_id TEXT,
1248
+ parent_session_id TEXT,
1249
+ forked_from_session_id TEXT,
1250
+ worktree_id TEXT NOT NULL,
1251
+ data TEXT NOT NULL,
1252
+ FOREIGN KEY (worktree_id) REFERENCES worktrees(worktree_id) ON DELETE CASCADE
1253
+ )
1254
+ `);
1255
+ await db.run(import_drizzle_orm2.sql`
1256
+ CREATE INDEX IF NOT EXISTS sessions_status_idx ON sessions(status)
1257
+ `);
1258
+ await db.run(import_drizzle_orm2.sql`
1259
+ CREATE INDEX IF NOT EXISTS sessions_agentic_tool_idx ON sessions(agentic_tool)
1260
+ `);
1261
+ await db.run(import_drizzle_orm2.sql`
1262
+ CREATE INDEX IF NOT EXISTS sessions_board_idx ON sessions(board_id)
1263
+ `);
1264
+ await db.run(import_drizzle_orm2.sql`
1265
+ CREATE INDEX IF NOT EXISTS sessions_worktree_idx ON sessions(worktree_id)
1266
+ `);
1267
+ await db.run(import_drizzle_orm2.sql`
1268
+ CREATE INDEX IF NOT EXISTS sessions_created_idx ON sessions(created_at)
1269
+ `);
1270
+ await db.run(import_drizzle_orm2.sql`
1271
+ CREATE INDEX IF NOT EXISTS sessions_parent_idx ON sessions(parent_session_id)
1272
+ `);
1273
+ await db.run(import_drizzle_orm2.sql`
1274
+ CREATE INDEX IF NOT EXISTS sessions_forked_idx ON sessions(forked_from_session_id)
1275
+ `);
1276
+ await db.run(import_drizzle_orm2.sql`
1277
+ CREATE TABLE IF NOT EXISTS tasks (
1278
+ task_id TEXT PRIMARY KEY,
1279
+ session_id TEXT NOT NULL,
1280
+ created_at INTEGER NOT NULL,
1281
+ completed_at INTEGER,
1282
+ status TEXT NOT NULL CHECK(status IN ('created', 'running', 'stopping', 'awaiting_permission', 'completed', 'failed', 'stopped')),
1283
+ created_by TEXT NOT NULL DEFAULT 'anonymous',
1284
+ data TEXT NOT NULL,
1285
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
1286
+ )
1287
+ `);
1288
+ await db.run(import_drizzle_orm2.sql`
1289
+ CREATE INDEX IF NOT EXISTS tasks_session_idx ON tasks(session_id)
1290
+ `);
1291
+ await db.run(import_drizzle_orm2.sql`
1292
+ CREATE INDEX IF NOT EXISTS tasks_status_idx ON tasks(status)
1293
+ `);
1294
+ await db.run(import_drizzle_orm2.sql`
1295
+ CREATE INDEX IF NOT EXISTS tasks_created_idx ON tasks(created_at)
1296
+ `);
1297
+ await db.run(import_drizzle_orm2.sql`
1298
+ CREATE TABLE IF NOT EXISTS boards (
1299
+ board_id TEXT PRIMARY KEY,
1300
+ created_at INTEGER NOT NULL,
1301
+ updated_at INTEGER,
1302
+ created_by TEXT NOT NULL DEFAULT 'anonymous',
1303
+ name TEXT NOT NULL,
1304
+ slug TEXT UNIQUE,
1305
+ data TEXT NOT NULL
1306
+ )
1307
+ `);
1308
+ await db.run(import_drizzle_orm2.sql`
1309
+ CREATE INDEX IF NOT EXISTS boards_name_idx ON boards(name)
1310
+ `);
1311
+ await db.run(import_drizzle_orm2.sql`
1312
+ CREATE INDEX IF NOT EXISTS boards_slug_idx ON boards(slug)
1313
+ `);
1314
+ await db.run(import_drizzle_orm2.sql`
1315
+ CREATE TABLE IF NOT EXISTS repos (
1316
+ repo_id TEXT PRIMARY KEY,
1317
+ created_at INTEGER NOT NULL,
1318
+ updated_at INTEGER,
1319
+ slug TEXT NOT NULL UNIQUE,
1320
+ data TEXT NOT NULL
1321
+ )
1322
+ `);
1323
+ await db.run(import_drizzle_orm2.sql`
1324
+ CREATE INDEX IF NOT EXISTS repos_slug_idx ON repos(slug)
1325
+ `);
1326
+ await db.run(import_drizzle_orm2.sql`
1327
+ CREATE TABLE IF NOT EXISTS worktrees (
1328
+ worktree_id TEXT PRIMARY KEY,
1329
+ repo_id TEXT NOT NULL,
1330
+ created_at INTEGER NOT NULL,
1331
+ updated_at INTEGER,
1332
+ created_by TEXT NOT NULL DEFAULT 'anonymous',
1333
+ name TEXT NOT NULL,
1334
+ ref TEXT NOT NULL,
1335
+ worktree_unique_id INTEGER NOT NULL,
1336
+ board_id TEXT,
1337
+ data TEXT NOT NULL,
1338
+ FOREIGN KEY (repo_id) REFERENCES repos(repo_id) ON DELETE CASCADE,
1339
+ FOREIGN KEY (board_id) REFERENCES boards(board_id) ON DELETE SET NULL
1340
+ )
1341
+ `);
1342
+ await db.run(import_drizzle_orm2.sql`
1343
+ CREATE INDEX IF NOT EXISTS worktrees_repo_idx ON worktrees(repo_id)
1344
+ `);
1345
+ await db.run(import_drizzle_orm2.sql`
1346
+ CREATE INDEX IF NOT EXISTS worktrees_name_idx ON worktrees(name)
1347
+ `);
1348
+ await db.run(import_drizzle_orm2.sql`
1349
+ CREATE INDEX IF NOT EXISTS worktrees_ref_idx ON worktrees(ref)
1350
+ `);
1351
+ await db.run(import_drizzle_orm2.sql`
1352
+ CREATE INDEX IF NOT EXISTS worktrees_board_idx ON worktrees(board_id)
1353
+ `);
1354
+ await db.run(import_drizzle_orm2.sql`
1355
+ CREATE INDEX IF NOT EXISTS worktrees_created_idx ON worktrees(created_at)
1356
+ `);
1357
+ await db.run(import_drizzle_orm2.sql`
1358
+ CREATE INDEX IF NOT EXISTS worktrees_updated_idx ON worktrees(updated_at)
1359
+ `);
1360
+ await db.run(import_drizzle_orm2.sql`
1361
+ CREATE INDEX IF NOT EXISTS worktrees_repo_name_unique ON worktrees(repo_id, name)
1362
+ `);
1363
+ await db.run(import_drizzle_orm2.sql`
1364
+ CREATE TABLE IF NOT EXISTS messages (
1365
+ message_id TEXT PRIMARY KEY,
1366
+ created_at INTEGER NOT NULL,
1367
+ session_id TEXT NOT NULL,
1368
+ task_id TEXT,
1369
+ type TEXT NOT NULL CHECK(type IN ('user', 'assistant', 'system', 'file-history-snapshot', 'permission_request')),
1370
+ role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
1371
+ "index" INTEGER NOT NULL,
1372
+ timestamp INTEGER NOT NULL,
1373
+ content_preview TEXT,
1374
+ data TEXT NOT NULL,
1375
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
1376
+ FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE SET NULL
1377
+ )
1378
+ `);
1379
+ await db.run(import_drizzle_orm2.sql`
1380
+ CREATE INDEX IF NOT EXISTS messages_session_id_idx ON messages(session_id)
1381
+ `);
1382
+ await db.run(import_drizzle_orm2.sql`
1383
+ CREATE INDEX IF NOT EXISTS messages_task_id_idx ON messages(task_id)
1384
+ `);
1385
+ await db.run(import_drizzle_orm2.sql`
1386
+ CREATE INDEX IF NOT EXISTS messages_session_index_idx ON messages(session_id, "index")
1387
+ `);
1388
+ await db.run(import_drizzle_orm2.sql`
1389
+ CREATE TABLE IF NOT EXISTS users (
1390
+ user_id TEXT PRIMARY KEY,
1391
+ created_at INTEGER NOT NULL,
1392
+ updated_at INTEGER,
1393
+ email TEXT UNIQUE NOT NULL,
1394
+ password TEXT NOT NULL,
1395
+ name TEXT,
1396
+ emoji TEXT,
1397
+ role TEXT NOT NULL DEFAULT 'member' CHECK(role IN ('owner', 'admin', 'member', 'viewer')),
1398
+ onboarding_completed INTEGER NOT NULL DEFAULT 0,
1399
+ data TEXT NOT NULL
1400
+ )
1401
+ `);
1402
+ await db.run(import_drizzle_orm2.sql`
1403
+ CREATE INDEX IF NOT EXISTS users_email_idx ON users(email)
1404
+ `);
1405
+ await db.run(import_drizzle_orm2.sql`
1406
+ CREATE TABLE IF NOT EXISTS mcp_servers (
1407
+ mcp_server_id TEXT PRIMARY KEY,
1408
+ created_at INTEGER NOT NULL,
1409
+ updated_at INTEGER,
1410
+ name TEXT NOT NULL,
1411
+ transport TEXT NOT NULL CHECK(transport IN ('stdio', 'http', 'sse')),
1412
+ scope TEXT NOT NULL CHECK(scope IN ('global', 'team', 'repo', 'session')),
1413
+ enabled INTEGER NOT NULL DEFAULT 1,
1414
+ owner_user_id TEXT,
1415
+ team_id TEXT,
1416
+ repo_id TEXT,
1417
+ session_id TEXT,
1418
+ source TEXT NOT NULL CHECK(source IN ('user', 'imported', 'agor')),
1419
+ data TEXT NOT NULL,
1420
+ FOREIGN KEY (repo_id) REFERENCES repos(repo_id) ON DELETE CASCADE,
1421
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
1422
+ )
1423
+ `);
1424
+ await db.run(import_drizzle_orm2.sql`
1425
+ CREATE INDEX IF NOT EXISTS mcp_servers_name_idx ON mcp_servers(name)
1426
+ `);
1427
+ await db.run(import_drizzle_orm2.sql`
1428
+ CREATE INDEX IF NOT EXISTS mcp_servers_scope_idx ON mcp_servers(scope)
1429
+ `);
1430
+ await db.run(import_drizzle_orm2.sql`
1431
+ CREATE INDEX IF NOT EXISTS mcp_servers_owner_idx ON mcp_servers(owner_user_id)
1432
+ `);
1433
+ await db.run(import_drizzle_orm2.sql`
1434
+ CREATE INDEX IF NOT EXISTS mcp_servers_team_idx ON mcp_servers(team_id)
1435
+ `);
1436
+ await db.run(import_drizzle_orm2.sql`
1437
+ CREATE INDEX IF NOT EXISTS mcp_servers_repo_idx ON mcp_servers(repo_id)
1438
+ `);
1439
+ await db.run(import_drizzle_orm2.sql`
1440
+ CREATE INDEX IF NOT EXISTS mcp_servers_session_idx ON mcp_servers(session_id)
1441
+ `);
1442
+ await db.run(import_drizzle_orm2.sql`
1443
+ CREATE INDEX IF NOT EXISTS mcp_servers_enabled_idx ON mcp_servers(enabled)
1444
+ `);
1445
+ await db.run(import_drizzle_orm2.sql`
1446
+ CREATE TABLE IF NOT EXISTS session_mcp_servers (
1447
+ session_id TEXT NOT NULL,
1448
+ mcp_server_id TEXT NOT NULL,
1449
+ enabled INTEGER NOT NULL DEFAULT 1,
1450
+ added_at INTEGER NOT NULL,
1451
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
1452
+ FOREIGN KEY (mcp_server_id) REFERENCES mcp_servers(mcp_server_id) ON DELETE CASCADE
1453
+ )
1454
+ `);
1455
+ await db.run(import_drizzle_orm2.sql`
1456
+ CREATE INDEX IF NOT EXISTS session_mcp_servers_pk ON session_mcp_servers(session_id, mcp_server_id)
1457
+ `);
1458
+ await db.run(import_drizzle_orm2.sql`
1459
+ CREATE INDEX IF NOT EXISTS session_mcp_servers_session_idx ON session_mcp_servers(session_id)
1460
+ `);
1461
+ await db.run(import_drizzle_orm2.sql`
1462
+ CREATE INDEX IF NOT EXISTS session_mcp_servers_server_idx ON session_mcp_servers(mcp_server_id)
1463
+ `);
1464
+ await db.run(import_drizzle_orm2.sql`
1465
+ CREATE INDEX IF NOT EXISTS session_mcp_servers_enabled_idx ON session_mcp_servers(session_id, enabled)
1466
+ `);
1467
+ await db.run(import_drizzle_orm2.sql`
1468
+ CREATE TABLE IF NOT EXISTS board_objects (
1469
+ object_id TEXT PRIMARY KEY,
1470
+ board_id TEXT NOT NULL,
1471
+ created_at INTEGER NOT NULL,
1472
+ worktree_id TEXT NOT NULL,
1473
+ data TEXT NOT NULL,
1474
+ FOREIGN KEY (board_id) REFERENCES boards(board_id) ON DELETE CASCADE,
1475
+ FOREIGN KEY (worktree_id) REFERENCES worktrees(worktree_id) ON DELETE CASCADE
1476
+ )
1477
+ `);
1478
+ await db.run(import_drizzle_orm2.sql`
1479
+ CREATE INDEX IF NOT EXISTS board_objects_board_idx ON board_objects(board_id)
1480
+ `);
1481
+ await db.run(import_drizzle_orm2.sql`
1482
+ CREATE INDEX IF NOT EXISTS board_objects_worktree_idx ON board_objects(worktree_id)
1483
+ `);
1484
+ await db.run(import_drizzle_orm2.sql`
1485
+ CREATE TABLE IF NOT EXISTS board_comments (
1486
+ comment_id TEXT PRIMARY KEY,
1487
+ created_at INTEGER NOT NULL,
1488
+ updated_at INTEGER,
1489
+ board_id TEXT NOT NULL,
1490
+ created_by TEXT NOT NULL DEFAULT 'anonymous',
1491
+ session_id TEXT,
1492
+ task_id TEXT,
1493
+ message_id TEXT,
1494
+ worktree_id TEXT,
1495
+ content TEXT NOT NULL,
1496
+ content_preview TEXT NOT NULL,
1497
+ parent_comment_id TEXT,
1498
+ resolved INTEGER NOT NULL DEFAULT 0,
1499
+ edited INTEGER NOT NULL DEFAULT 0,
1500
+ reactions TEXT NOT NULL DEFAULT '[]',
1501
+ data TEXT NOT NULL,
1502
+ FOREIGN KEY (board_id) REFERENCES boards(board_id) ON DELETE CASCADE,
1503
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE SET NULL,
1504
+ FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE SET NULL,
1505
+ FOREIGN KEY (message_id) REFERENCES messages(message_id) ON DELETE SET NULL,
1506
+ FOREIGN KEY (worktree_id) REFERENCES worktrees(worktree_id) ON DELETE SET NULL
1507
+ )
1508
+ `);
1509
+ await db.run(import_drizzle_orm2.sql`
1510
+ CREATE INDEX IF NOT EXISTS board_comments_board_idx ON board_comments(board_id)
1511
+ `);
1512
+ await db.run(import_drizzle_orm2.sql`
1513
+ CREATE INDEX IF NOT EXISTS board_comments_session_idx ON board_comments(session_id)
1514
+ `);
1515
+ await db.run(import_drizzle_orm2.sql`
1516
+ CREATE INDEX IF NOT EXISTS board_comments_task_idx ON board_comments(task_id)
1517
+ `);
1518
+ await db.run(import_drizzle_orm2.sql`
1519
+ CREATE INDEX IF NOT EXISTS board_comments_message_idx ON board_comments(message_id)
1520
+ `);
1521
+ await db.run(import_drizzle_orm2.sql`
1522
+ CREATE INDEX IF NOT EXISTS board_comments_worktree_idx ON board_comments(worktree_id)
1523
+ `);
1524
+ await db.run(import_drizzle_orm2.sql`
1525
+ CREATE INDEX IF NOT EXISTS board_comments_created_by_idx ON board_comments(created_by)
1526
+ `);
1527
+ await db.run(import_drizzle_orm2.sql`
1528
+ CREATE INDEX IF NOT EXISTS board_comments_parent_idx ON board_comments(parent_comment_id)
1529
+ `);
1530
+ await db.run(import_drizzle_orm2.sql`
1531
+ CREATE INDEX IF NOT EXISTS board_comments_created_idx ON board_comments(created_at)
1532
+ `);
1533
+ await db.run(import_drizzle_orm2.sql`
1534
+ CREATE INDEX IF NOT EXISTS board_comments_resolved_idx ON board_comments(resolved)
1535
+ `);
1536
+ } catch (error) {
1537
+ throw new MigrationError(
1538
+ `Failed to create initial schema: ${error instanceof Error ? error.message : String(error)}`,
1539
+ error
1540
+ );
1541
+ }
1542
+ }
1543
+ async function runMigrations(db, migrationsFolder = "./migrations") {
1544
+ try {
1545
+ const exists = await tablesExist(db);
1546
+ if (!exists) {
1547
+ console.log("Creating initial database schema...");
1548
+ await createInitialSchema(db);
1549
+ console.log("Initial schema created successfully");
1550
+ } else {
1551
+ console.log("Running migrations...");
1552
+ await (0, import_migrator.migrate)(db, { migrationsFolder });
1553
+ console.log("Migrations applied successfully");
1554
+ }
1555
+ } catch (error) {
1556
+ throw new MigrationError(
1557
+ `Migration failed: ${error instanceof Error ? error.message : String(error)}`,
1558
+ error
1559
+ );
1560
+ }
1561
+ }
1562
+ async function initializeDatabase(db) {
1563
+ try {
1564
+ const exists = await tablesExist(db);
1565
+ if (!exists) {
1566
+ console.log("Initializing database schema...");
1567
+ await createInitialSchema(db);
1568
+ console.log("Database initialized successfully");
1569
+ } else {
1570
+ console.log("Checking for schema updates...");
1571
+ const hasBoardComments = await tableExists(db, "board_comments");
1572
+ if (!hasBoardComments) {
1573
+ console.log(" Adding board_comments table...");
1574
+ await db.run(import_drizzle_orm2.sql`
1575
+ CREATE TABLE board_comments (
1576
+ comment_id TEXT PRIMARY KEY,
1577
+ created_at INTEGER NOT NULL,
1578
+ updated_at INTEGER,
1579
+ board_id TEXT NOT NULL,
1580
+ created_by TEXT NOT NULL DEFAULT 'anonymous',
1581
+ session_id TEXT,
1582
+ task_id TEXT,
1583
+ message_id TEXT,
1584
+ worktree_id TEXT,
1585
+ content TEXT NOT NULL,
1586
+ content_preview TEXT NOT NULL,
1587
+ parent_comment_id TEXT,
1588
+ resolved INTEGER NOT NULL DEFAULT 0,
1589
+ edited INTEGER NOT NULL DEFAULT 0,
1590
+ reactions TEXT NOT NULL DEFAULT '[]',
1591
+ data TEXT NOT NULL,
1592
+ FOREIGN KEY (board_id) REFERENCES boards(board_id) ON DELETE CASCADE,
1593
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE SET NULL,
1594
+ FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE SET NULL,
1595
+ FOREIGN KEY (message_id) REFERENCES messages(message_id) ON DELETE SET NULL,
1596
+ FOREIGN KEY (worktree_id) REFERENCES worktrees(worktree_id) ON DELETE SET NULL
1597
+ )
1598
+ `);
1599
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_board_idx ON board_comments(board_id)`);
1600
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_session_idx ON board_comments(session_id)`);
1601
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_task_idx ON board_comments(task_id)`);
1602
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_message_idx ON board_comments(message_id)`);
1603
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_worktree_idx ON board_comments(worktree_id)`);
1604
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_created_by_idx ON board_comments(created_by)`);
1605
+ await db.run(
1606
+ import_drizzle_orm2.sql`CREATE INDEX board_comments_parent_idx ON board_comments(parent_comment_id)`
1607
+ );
1608
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_created_idx ON board_comments(created_at)`);
1609
+ await db.run(import_drizzle_orm2.sql`CREATE INDEX board_comments_resolved_idx ON board_comments(resolved)`);
1610
+ console.log(" \u2705 board_comments table added");
1611
+ } else {
1612
+ try {
1613
+ const tableInfo = await db.run(import_drizzle_orm2.sql`PRAGMA table_info(board_comments)`);
1614
+ const hasReactionsColumn = tableInfo.rows.some(
1615
+ (row) => row.name === "reactions"
1616
+ );
1617
+ if (!hasReactionsColumn) {
1618
+ console.log(" Adding reactions column to board_comments...");
1619
+ await db.run(import_drizzle_orm2.sql`
1620
+ ALTER TABLE board_comments ADD COLUMN reactions TEXT NOT NULL DEFAULT '[]'
1621
+ `);
1622
+ console.log(" \u2705 reactions column added");
1623
+ }
1624
+ } catch (error) {
1625
+ console.error(" \u26A0\uFE0F Failed to add reactions column:", error);
1626
+ }
1627
+ }
1628
+ console.log("Schema up to date");
1629
+ }
1630
+ } catch (error) {
1631
+ throw new MigrationError(
1632
+ `Database initialization failed: ${error instanceof Error ? error.message : String(error)}`,
1633
+ error
1634
+ );
1635
+ }
1636
+ }
1637
+ async function seedInitialData(db) {
1638
+ try {
1639
+ const result = await db.run(import_drizzle_orm2.sql`
1640
+ SELECT board_id FROM boards WHERE slug = 'default'
1641
+ `);
1642
+ if (result.rows.length === 0) {
1643
+ const { generateId: generateId2 } = await Promise.resolve().then(() => (init_ids(), ids_exports));
1644
+ const boardId = generateId2();
1645
+ const now = Date.now();
1646
+ await db.run(import_drizzle_orm2.sql`
1647
+ INSERT INTO boards (board_id, name, slug, created_at, updated_at, created_by, data)
1648
+ VALUES (
1649
+ ${boardId},
1650
+ ${"Main Board"},
1651
+ ${"default"},
1652
+ ${now},
1653
+ ${now},
1654
+ ${"anonymous"},
1655
+ ${JSON.stringify({
1656
+ description: "Main board for all sessions",
1657
+ sessions: [],
1658
+ color: "#1677ff",
1659
+ icon: "\u2B50"
1660
+ })}
1661
+ )
1662
+ `);
1663
+ console.log("Main Board created");
1664
+ }
1665
+ } catch (error) {
1666
+ throw new MigrationError(
1667
+ `Failed to seed initial data: ${error instanceof Error ? error.message : String(error)}`,
1668
+ error
1669
+ );
1670
+ }
1671
+ }
1672
+
1673
+ // src/db/repositories/base.ts
1674
+ var RepositoryError = class extends Error {
1675
+ constructor(message, cause) {
1676
+ super(message);
1677
+ this.cause = cause;
1678
+ this.name = "RepositoryError";
1679
+ }
1680
+ };
1681
+ var EntityNotFoundError = class extends RepositoryError {
1682
+ constructor(entityType, id) {
1683
+ super(`${entityType} with ID '${id}' not found`);
1684
+ this.entityType = entityType;
1685
+ this.id = id;
1686
+ this.name = "EntityNotFoundError";
1687
+ }
1688
+ };
1689
+ var AmbiguousIdError = class extends RepositoryError {
1690
+ constructor(entityType, prefix, matches) {
1691
+ super(
1692
+ `Ambiguous ID prefix '${prefix}' for ${entityType} (${matches.length} matches: ${matches.slice(0, 3).join(", ")}${matches.length > 3 ? "..." : ""})`
1693
+ );
1694
+ this.entityType = entityType;
1695
+ this.prefix = prefix;
1696
+ this.matches = matches;
1697
+ this.name = "AmbiguousIdError";
1698
+ }
1699
+ };
1700
+
1701
+ // src/db/repositories/board-comments.ts
1702
+ var import_drizzle_orm3 = require("drizzle-orm");
1703
+ init_ids();
1704
+ function generatePreview(content) {
1705
+ return content.length > 200 ? content.slice(0, 200) + "..." : content;
1706
+ }
1707
+ var BoardCommentsRepository = class {
1708
+ constructor(db) {
1709
+ this.db = db;
1710
+ }
1711
+ /**
1712
+ * Convert database row to BoardComment type
1713
+ */
1714
+ rowToComment(row) {
1715
+ const data = row.data;
1716
+ const reactions = row.reactions ? typeof row.reactions === "string" ? JSON.parse(row.reactions) : row.reactions : [];
1717
+ return {
1718
+ comment_id: row.comment_id,
1719
+ board_id: row.board_id,
1720
+ created_by: row.created_by,
1721
+ content: row.content,
1722
+ content_preview: row.content_preview,
1723
+ session_id: row.session_id ? row.session_id : void 0,
1724
+ task_id: row.task_id ? row.task_id : void 0,
1725
+ message_id: row.message_id ? row.message_id : void 0,
1726
+ worktree_id: row.worktree_id ? row.worktree_id : void 0,
1727
+ parent_comment_id: row.parent_comment_id ? row.parent_comment_id : void 0,
1728
+ resolved: Boolean(row.resolved),
1729
+ edited: Boolean(row.edited),
1730
+ reactions,
1731
+ position: data.position,
1732
+ mentions: data.mentions ? data.mentions : void 0,
1733
+ created_at: new Date(row.created_at),
1734
+ updated_at: row.updated_at ? new Date(row.updated_at) : void 0
1735
+ };
1736
+ }
1737
+ /**
1738
+ * Convert BoardComment to database insert format
1739
+ */
1740
+ commentToInsert(comment) {
1741
+ const now = Date.now();
1742
+ const commentId = comment.comment_id ?? generateId();
1743
+ const contentPreview = comment.content_preview ?? (comment.content ? generatePreview(comment.content) : "");
1744
+ return {
1745
+ comment_id: commentId,
1746
+ board_id: comment.board_id,
1747
+ created_by: comment.created_by ?? "anonymous",
1748
+ content: comment.content ?? "",
1749
+ content_preview: contentPreview,
1750
+ session_id: comment.session_id ?? null,
1751
+ task_id: comment.task_id ?? null,
1752
+ message_id: comment.message_id ?? null,
1753
+ worktree_id: comment.worktree_id ?? null,
1754
+ parent_comment_id: comment.parent_comment_id ?? null,
1755
+ resolved: comment.resolved ?? false,
1756
+ edited: comment.edited ?? false,
1757
+ reactions: comment.reactions ?? [],
1758
+ created_at: new Date(comment.created_at ?? now),
1759
+ updated_at: comment.updated_at ? new Date(comment.updated_at) : null,
1760
+ data: {
1761
+ position: comment.position,
1762
+ mentions: comment.mentions
1763
+ }
1764
+ };
1765
+ }
1766
+ /**
1767
+ * Resolve short ID to full ID
1768
+ */
1769
+ async resolveId(id) {
1770
+ if (id.length === 36 && id.includes("-")) {
1771
+ return id;
1772
+ }
1773
+ const normalized = id.replace(/-/g, "").toLowerCase();
1774
+ const pattern = `${normalized}%`;
1775
+ const results = await this.db.select({ comment_id: boardComments.comment_id }).from(boardComments).where((0, import_drizzle_orm3.like)(boardComments.comment_id, pattern)).all();
1776
+ if (results.length === 0) {
1777
+ throw new EntityNotFoundError("BoardComment", id);
1778
+ }
1779
+ if (results.length > 1) {
1780
+ throw new AmbiguousIdError(
1781
+ "BoardComment",
1782
+ id,
1783
+ results.map((r) => formatShortId(r.comment_id))
1784
+ );
1785
+ }
1786
+ return results[0].comment_id;
1787
+ }
1788
+ /**
1789
+ * Create a new comment
1790
+ */
1791
+ async create(data) {
1792
+ try {
1793
+ const insert = this.commentToInsert(data);
1794
+ await this.db.insert(boardComments).values(insert);
1795
+ const row = await this.db.select().from(boardComments).where((0, import_drizzle_orm3.eq)(boardComments.comment_id, insert.comment_id)).get();
1796
+ if (!row) {
1797
+ throw new RepositoryError("Failed to retrieve created comment");
1798
+ }
1799
+ return this.rowToComment(row);
1800
+ } catch (error) {
1801
+ if (error instanceof RepositoryError) throw error;
1802
+ throw new RepositoryError(
1803
+ `Failed to create comment: ${error instanceof Error ? error.message : String(error)}`,
1804
+ error
1805
+ );
1806
+ }
1807
+ }
1808
+ /**
1809
+ * Find comment by ID (supports short ID)
1810
+ */
1811
+ async findById(id) {
1812
+ try {
1813
+ const fullId = await this.resolveId(id);
1814
+ const row = await this.db.select().from(boardComments).where((0, import_drizzle_orm3.eq)(boardComments.comment_id, fullId)).get();
1815
+ return row ? this.rowToComment(row) : null;
1816
+ } catch (error) {
1817
+ if (error instanceof EntityNotFoundError) return null;
1818
+ if (error instanceof AmbiguousIdError) throw error;
1819
+ throw new RepositoryError(
1820
+ `Failed to find comment: ${error instanceof Error ? error.message : String(error)}`,
1821
+ error
1822
+ );
1823
+ }
1824
+ }
1825
+ /**
1826
+ * Find all comments (optionally filtered by board, session, task, etc.)
1827
+ */
1828
+ async findAll(filters) {
1829
+ try {
1830
+ let query = this.db.select().from(boardComments);
1831
+ const conditions = [];
1832
+ if (filters?.board_id) {
1833
+ conditions.push((0, import_drizzle_orm3.eq)(boardComments.board_id, filters.board_id));
1834
+ }
1835
+ if (filters?.session_id !== void 0) {
1836
+ if (filters.session_id === null) {
1837
+ conditions.push((0, import_drizzle_orm3.isNull)(boardComments.session_id));
1838
+ } else {
1839
+ conditions.push((0, import_drizzle_orm3.eq)(boardComments.session_id, filters.session_id));
1840
+ }
1841
+ }
1842
+ if (filters?.task_id !== void 0) {
1843
+ if (filters.task_id === null) {
1844
+ conditions.push((0, import_drizzle_orm3.isNull)(boardComments.task_id));
1845
+ } else {
1846
+ conditions.push((0, import_drizzle_orm3.eq)(boardComments.task_id, filters.task_id));
1847
+ }
1848
+ }
1849
+ if (filters?.message_id !== void 0) {
1850
+ if (filters.message_id === null) {
1851
+ conditions.push((0, import_drizzle_orm3.isNull)(boardComments.message_id));
1852
+ } else {
1853
+ conditions.push((0, import_drizzle_orm3.eq)(boardComments.message_id, filters.message_id));
1854
+ }
1855
+ }
1856
+ if (filters?.worktree_id !== void 0) {
1857
+ if (filters.worktree_id === null) {
1858
+ conditions.push((0, import_drizzle_orm3.isNull)(boardComments.worktree_id));
1859
+ } else {
1860
+ conditions.push((0, import_drizzle_orm3.eq)(boardComments.worktree_id, filters.worktree_id));
1861
+ }
1862
+ }
1863
+ if (filters?.resolved !== void 0) {
1864
+ conditions.push((0, import_drizzle_orm3.eq)(boardComments.resolved, filters.resolved));
1865
+ }
1866
+ if (filters?.created_by) {
1867
+ conditions.push((0, import_drizzle_orm3.eq)(boardComments.created_by, filters.created_by));
1868
+ }
1869
+ if (conditions.length > 0) {
1870
+ query = query.where((0, import_drizzle_orm3.and)(...conditions));
1871
+ }
1872
+ const rows = await query.all();
1873
+ return rows.map((row) => this.rowToComment(row));
1874
+ } catch (error) {
1875
+ throw new RepositoryError(
1876
+ `Failed to find comments: ${error instanceof Error ? error.message : String(error)}`,
1877
+ error
1878
+ );
1879
+ }
1880
+ }
1881
+ /**
1882
+ * Update comment by ID
1883
+ */
1884
+ async update(id, updates) {
1885
+ try {
1886
+ const fullId = await this.resolveId(id);
1887
+ const current = await this.findById(fullId);
1888
+ if (!current) {
1889
+ throw new EntityNotFoundError("BoardComment", id);
1890
+ }
1891
+ const merged = { ...current, ...updates };
1892
+ if (updates.content && !updates.content_preview) {
1893
+ merged.content_preview = generatePreview(updates.content);
1894
+ }
1895
+ if (updates.content && updates.content !== current.content) {
1896
+ merged.edited = true;
1897
+ }
1898
+ const insert = this.commentToInsert(merged);
1899
+ await this.db.update(boardComments).set({
1900
+ content: insert.content,
1901
+ content_preview: insert.content_preview,
1902
+ session_id: insert.session_id,
1903
+ task_id: insert.task_id,
1904
+ message_id: insert.message_id,
1905
+ worktree_id: insert.worktree_id,
1906
+ parent_comment_id: insert.parent_comment_id,
1907
+ resolved: insert.resolved,
1908
+ edited: insert.edited,
1909
+ reactions: insert.reactions,
1910
+ updated_at: /* @__PURE__ */ new Date(),
1911
+ data: insert.data
1912
+ }).where((0, import_drizzle_orm3.eq)(boardComments.comment_id, fullId));
1913
+ const updated = await this.findById(fullId);
1914
+ if (!updated) {
1915
+ throw new RepositoryError("Failed to retrieve updated comment");
1916
+ }
1917
+ return updated;
1918
+ } catch (error) {
1919
+ if (error instanceof RepositoryError) throw error;
1920
+ if (error instanceof EntityNotFoundError) throw error;
1921
+ throw new RepositoryError(
1922
+ `Failed to update comment: ${error instanceof Error ? error.message : String(error)}`,
1923
+ error
1924
+ );
1925
+ }
1926
+ }
1927
+ /**
1928
+ * Delete comment by ID
1929
+ * If deleting a thread root, also deletes all replies (cascade)
1930
+ */
1931
+ async delete(id) {
1932
+ try {
1933
+ const fullId = await this.resolveId(id);
1934
+ await this.db.delete(boardComments).where((0, import_drizzle_orm3.eq)(boardComments.parent_comment_id, fullId)).run();
1935
+ const result = await this.db.delete(boardComments).where((0, import_drizzle_orm3.eq)(boardComments.comment_id, fullId)).run();
1936
+ if (result.rowsAffected === 0) {
1937
+ throw new EntityNotFoundError("BoardComment", id);
1938
+ }
1939
+ } catch (error) {
1940
+ if (error instanceof EntityNotFoundError) throw error;
1941
+ throw new RepositoryError(
1942
+ `Failed to delete comment: ${error instanceof Error ? error.message : String(error)}`,
1943
+ error
1944
+ );
1945
+ }
1946
+ }
1947
+ /**
1948
+ * Resolve comment (mark as resolved)
1949
+ */
1950
+ async resolve(id) {
1951
+ return this.update(id, { resolved: true });
1952
+ }
1953
+ /**
1954
+ * Unresolve comment (mark as unresolved)
1955
+ */
1956
+ async unresolve(id) {
1957
+ return this.update(id, { resolved: false });
1958
+ }
1959
+ /**
1960
+ * Find comments by board ID with optional filters
1961
+ */
1962
+ async findByBoard(boardId, filters) {
1963
+ return this.findAll({ board_id: boardId, ...filters });
1964
+ }
1965
+ /**
1966
+ * Find comments for a specific session
1967
+ */
1968
+ async findBySession(sessionId) {
1969
+ return this.findAll({ session_id: sessionId });
1970
+ }
1971
+ /**
1972
+ * Find comments for a specific task
1973
+ */
1974
+ async findByTask(taskId) {
1975
+ return this.findAll({ task_id: taskId });
1976
+ }
1977
+ /**
1978
+ * Find comments mentioning a specific user
1979
+ */
1980
+ async findMentions(userId, boardId) {
1981
+ const comments = await this.findAll({ board_id: boardId });
1982
+ return comments.filter((comment) => comment.mentions?.includes(userId));
1983
+ }
1984
+ /**
1985
+ * Batch create comments (for bulk operations)
1986
+ */
1987
+ async bulkCreate(comments) {
1988
+ try {
1989
+ const inserts = comments.map((comment) => this.commentToInsert(comment));
1990
+ await this.db.insert(boardComments).values(inserts);
1991
+ const commentIds = inserts.map((insert) => insert.comment_id);
1992
+ const rows = await this.db.select().from(boardComments).where(
1993
+ (0, import_drizzle_orm3.eq)(
1994
+ boardComments.comment_id,
1995
+ commentIds[0]
1996
+ // TODO: Support proper IN clause when available
1997
+ )
1998
+ ).all();
1999
+ return rows.map((row) => this.rowToComment(row));
2000
+ } catch (error) {
2001
+ throw new RepositoryError(
2002
+ `Failed to bulk create comments: ${error instanceof Error ? error.message : String(error)}`,
2003
+ error
2004
+ );
2005
+ }
2006
+ }
2007
+ // ============================================================================
2008
+ // Phase 2: Threading + Reactions
2009
+ // ============================================================================
2010
+ /**
2011
+ * Toggle a reaction on a comment
2012
+ * If user has already reacted with this emoji, remove it. Otherwise, add it.
2013
+ */
2014
+ async toggleReaction(commentId, userId, emoji) {
2015
+ try {
2016
+ const comment = await this.findById(commentId);
2017
+ if (!comment) {
2018
+ throw new EntityNotFoundError("BoardComment", commentId);
2019
+ }
2020
+ const reactions = comment.reactions || [];
2021
+ const existingIndex = reactions.findIndex((r) => r.user_id === userId && r.emoji === emoji);
2022
+ let updatedReactions;
2023
+ if (existingIndex >= 0) {
2024
+ updatedReactions = reactions.filter((_, i) => i !== existingIndex);
2025
+ } else {
2026
+ updatedReactions = [...reactions, { user_id: userId, emoji }];
2027
+ }
2028
+ return this.update(commentId, { reactions: updatedReactions });
2029
+ } catch (error) {
2030
+ if (error instanceof EntityNotFoundError) throw error;
2031
+ throw new RepositoryError(
2032
+ `Failed to toggle reaction: ${error instanceof Error ? error.message : String(error)}`,
2033
+ error
2034
+ );
2035
+ }
2036
+ }
2037
+ /**
2038
+ * Create a reply to a comment (thread root)
2039
+ * Validates that parent exists and is a thread root
2040
+ */
2041
+ async createReply(parentId, data) {
2042
+ try {
2043
+ const parent = await this.findById(parentId);
2044
+ if (!parent) {
2045
+ throw new EntityNotFoundError("BoardComment", parentId);
2046
+ }
2047
+ if (parent.parent_comment_id) {
2048
+ throw new RepositoryError(
2049
+ "Cannot reply to a reply. Replies can only be added to thread roots (2-layer limit)."
2050
+ );
2051
+ }
2052
+ const reply = {
2053
+ ...data,
2054
+ parent_comment_id: parent.comment_id,
2055
+ board_id: parent.board_id,
2056
+ // Inherit board_id from parent
2057
+ // Replies don't have attachments - they inherit context from parent
2058
+ session_id: void 0,
2059
+ task_id: void 0,
2060
+ message_id: void 0,
2061
+ worktree_id: void 0,
2062
+ position: void 0
2063
+ };
2064
+ return this.create(reply);
2065
+ } catch (error) {
2066
+ if (error instanceof EntityNotFoundError) throw error;
2067
+ if (error instanceof RepositoryError) throw error;
2068
+ throw new RepositoryError(
2069
+ `Failed to create reply: ${error instanceof Error ? error.message : String(error)}`,
2070
+ error
2071
+ );
2072
+ }
2073
+ }
2074
+ };
2075
+
2076
+ // src/db/repositories/board-objects.ts
2077
+ var import_drizzle_orm4 = require("drizzle-orm");
2078
+ init_ids();
2079
+ var BoardObjectRepository = class {
2080
+ constructor(db) {
2081
+ this.db = db;
2082
+ }
2083
+ /**
2084
+ * Find all board objects
2085
+ */
2086
+ async findAll() {
2087
+ try {
2088
+ const rows = await this.db.select().from(boardObjects).all();
2089
+ return rows.map(this.rowToEntity);
2090
+ } catch (error) {
2091
+ throw new RepositoryError(
2092
+ `Failed to find all board objects: ${error instanceof Error ? error.message : String(error)}`,
2093
+ error
2094
+ );
2095
+ }
2096
+ }
2097
+ /**
2098
+ * Find all board objects for a board
2099
+ */
2100
+ async findByBoardId(boardId) {
2101
+ try {
2102
+ const rows = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.board_id, boardId)).all();
2103
+ return rows.map(this.rowToEntity);
2104
+ } catch (error) {
2105
+ throw new RepositoryError(
2106
+ `Failed to find board objects: ${error instanceof Error ? error.message : String(error)}`,
2107
+ error
2108
+ );
2109
+ }
2110
+ }
2111
+ /**
2112
+ * Find board object by object ID
2113
+ */
2114
+ async findByObjectId(objectId) {
2115
+ try {
2116
+ const row = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId)).get();
2117
+ return row ? this.rowToEntity(row) : null;
2118
+ } catch (error) {
2119
+ throw new RepositoryError(
2120
+ `Failed to find board object by object_id: ${error instanceof Error ? error.message : String(error)}`,
2121
+ error
2122
+ );
2123
+ }
2124
+ }
2125
+ /**
2126
+ * Find board object by worktree ID
2127
+ */
2128
+ async findByWorktreeId(worktreeId) {
2129
+ try {
2130
+ const row = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.worktree_id, worktreeId)).get();
2131
+ return row ? this.rowToEntity(row) : null;
2132
+ } catch (error) {
2133
+ throw new RepositoryError(
2134
+ `Failed to find board object by worktree: ${error instanceof Error ? error.message : String(error)}`,
2135
+ error
2136
+ );
2137
+ }
2138
+ }
2139
+ /**
2140
+ * Create a board object (add worktree to board)
2141
+ */
2142
+ async create(data) {
2143
+ try {
2144
+ const existing = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.worktree_id, data.worktree_id)).get();
2145
+ if (existing) {
2146
+ throw new RepositoryError(`Worktree already on a board (object_id: ${existing.object_id})`);
2147
+ }
2148
+ const insert = {
2149
+ object_id: generateId(),
2150
+ board_id: data.board_id,
2151
+ worktree_id: data.worktree_id,
2152
+ created_at: /* @__PURE__ */ new Date(),
2153
+ data: {
2154
+ position: data.position,
2155
+ zone_id: data.zone_id
2156
+ }
2157
+ };
2158
+ await this.db.insert(boardObjects).values(insert);
2159
+ const row = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, insert.object_id)).get();
2160
+ if (!row) {
2161
+ throw new RepositoryError("Failed to retrieve created board object");
2162
+ }
2163
+ return this.rowToEntity(row);
2164
+ } catch (error) {
2165
+ if (error instanceof RepositoryError) throw error;
2166
+ throw new RepositoryError(
2167
+ `Failed to create board object: ${error instanceof Error ? error.message : String(error)}`,
2168
+ error
2169
+ );
2170
+ }
2171
+ }
2172
+ /**
2173
+ * Update position of board object (preserves zone_id)
2174
+ */
2175
+ async updatePosition(objectId, position) {
2176
+ try {
2177
+ const existing = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId)).get();
2178
+ if (!existing) {
2179
+ throw new EntityNotFoundError("BoardObject", objectId);
2180
+ }
2181
+ const existingData = typeof existing.data === "string" ? JSON.parse(existing.data) : existing.data;
2182
+ await this.db.update(boardObjects).set({
2183
+ data: {
2184
+ position,
2185
+ zone_id: existingData.zone_id
2186
+ }
2187
+ }).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId));
2188
+ const row = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId)).get();
2189
+ if (!row) {
2190
+ throw new RepositoryError("Failed to retrieve updated board object");
2191
+ }
2192
+ return this.rowToEntity(row);
2193
+ } catch (error) {
2194
+ if (error instanceof EntityNotFoundError) throw error;
2195
+ throw new RepositoryError(
2196
+ `Failed to update board object position: ${error instanceof Error ? error.message : String(error)}`,
2197
+ error
2198
+ );
2199
+ }
2200
+ }
2201
+ /**
2202
+ * Update zone pinning for board object
2203
+ */
2204
+ async updateZone(objectId, zoneId) {
2205
+ try {
2206
+ const existing = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId)).get();
2207
+ if (!existing) {
2208
+ throw new EntityNotFoundError("BoardObject", objectId);
2209
+ }
2210
+ const existingData = typeof existing.data === "string" ? JSON.parse(existing.data) : existing.data;
2211
+ await this.db.update(boardObjects).set({
2212
+ data: {
2213
+ position: existingData.position,
2214
+ // Convert null to undefined for consistency
2215
+ zone_id: zoneId === null ? void 0 : zoneId
2216
+ }
2217
+ }).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId));
2218
+ const row = await this.db.select().from(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId)).get();
2219
+ if (!row) {
2220
+ throw new RepositoryError("Failed to retrieve updated board object");
2221
+ }
2222
+ return this.rowToEntity(row);
2223
+ } catch (error) {
2224
+ if (error instanceof EntityNotFoundError) throw error;
2225
+ throw new RepositoryError(
2226
+ `Failed to update board object zone: ${error instanceof Error ? error.message : String(error)}`,
2227
+ error
2228
+ );
2229
+ }
2230
+ }
2231
+ /**
2232
+ * Remove board object (remove entity from board)
2233
+ */
2234
+ async remove(objectId) {
2235
+ try {
2236
+ const result = await this.db.delete(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.object_id, objectId)).run();
2237
+ if (result.rowsAffected === 0) {
2238
+ throw new EntityNotFoundError("BoardObject", objectId);
2239
+ }
2240
+ } catch (error) {
2241
+ if (error instanceof EntityNotFoundError) throw error;
2242
+ throw new RepositoryError(
2243
+ `Failed to remove board object: ${error instanceof Error ? error.message : String(error)}`,
2244
+ error
2245
+ );
2246
+ }
2247
+ }
2248
+ /**
2249
+ * Remove all board objects for a worktree
2250
+ */
2251
+ async removeByWorktreeId(worktreeId) {
2252
+ try {
2253
+ await this.db.delete(boardObjects).where((0, import_drizzle_orm4.eq)(boardObjects.worktree_id, worktreeId));
2254
+ } catch (error) {
2255
+ throw new RepositoryError(
2256
+ `Failed to remove board objects by worktree: ${error instanceof Error ? error.message : String(error)}`,
2257
+ error
2258
+ );
2259
+ }
2260
+ }
2261
+ /**
2262
+ * Convert database row to entity
2263
+ */
2264
+ rowToEntity(row) {
2265
+ const data = typeof row.data === "string" ? JSON.parse(row.data) : row.data;
2266
+ return {
2267
+ object_id: row.object_id,
2268
+ board_id: row.board_id,
2269
+ worktree_id: row.worktree_id,
2270
+ position: data.position,
2271
+ zone_id: data.zone_id,
2272
+ created_at: new Date(row.created_at).toISOString()
2273
+ };
2274
+ }
2275
+ };
2276
+
2277
+ // src/db/repositories/boards.ts
2278
+ var import_drizzle_orm5 = require("drizzle-orm");
2279
+ init_ids();
2280
+ var BoardRepository = class {
2281
+ constructor(db) {
2282
+ this.db = db;
2283
+ }
2284
+ /**
2285
+ * Convert database row to Board type
2286
+ */
2287
+ rowToBoard(row) {
2288
+ const data = row.data;
2289
+ return {
2290
+ board_id: row.board_id,
2291
+ name: row.name,
2292
+ slug: row.slug || void 0,
2293
+ created_at: new Date(row.created_at).toISOString(),
2294
+ last_updated: row.updated_at ? new Date(row.updated_at).toISOString() : new Date(row.created_at).toISOString(),
2295
+ created_by: row.created_by,
2296
+ ...data
2297
+ };
2298
+ }
2299
+ /**
2300
+ * Convert Board to database insert format
2301
+ */
2302
+ boardToInsert(board) {
2303
+ const now = Date.now();
2304
+ const boardId = board.board_id ?? generateId();
2305
+ return {
2306
+ board_id: boardId,
2307
+ name: board.name ?? "Untitled Board",
2308
+ slug: board.slug ?? null,
2309
+ created_at: new Date(board.created_at ?? now),
2310
+ updated_at: board.last_updated ? new Date(board.last_updated) : new Date(now),
2311
+ created_by: board.created_by ?? "anonymous",
2312
+ data: {
2313
+ description: board.description,
2314
+ color: board.color,
2315
+ icon: board.icon,
2316
+ objects: board.objects,
2317
+ custom_context: board.custom_context
2318
+ }
2319
+ };
2320
+ }
2321
+ /**
2322
+ * Resolve short ID to full ID
2323
+ */
2324
+ async resolveId(id) {
2325
+ if (id.length === 36 && id.includes("-")) {
2326
+ return id;
2327
+ }
2328
+ const normalized = id.replace(/-/g, "").toLowerCase();
2329
+ const pattern = `${normalized}%`;
2330
+ const results = await this.db.select({ board_id: boards.board_id }).from(boards).where((0, import_drizzle_orm5.like)(boards.board_id, pattern)).all();
2331
+ if (results.length === 0) {
2332
+ throw new EntityNotFoundError("Board", id);
2333
+ }
2334
+ if (results.length > 1) {
2335
+ throw new AmbiguousIdError(
2336
+ "Board",
2337
+ id,
2338
+ results.map((r) => formatShortId(r.board_id))
2339
+ );
2340
+ }
2341
+ return results[0].board_id;
2342
+ }
2343
+ /**
2344
+ * Create a new board
2345
+ */
2346
+ async create(data) {
2347
+ try {
2348
+ const insert = this.boardToInsert(data);
2349
+ await this.db.insert(boards).values(insert);
2350
+ const row = await this.db.select().from(boards).where((0, import_drizzle_orm5.eq)(boards.board_id, insert.board_id)).get();
2351
+ if (!row) {
2352
+ throw new RepositoryError("Failed to retrieve created board");
2353
+ }
2354
+ return this.rowToBoard(row);
2355
+ } catch (error) {
2356
+ if (error instanceof RepositoryError) throw error;
2357
+ throw new RepositoryError(
2358
+ `Failed to create board: ${error instanceof Error ? error.message : String(error)}`,
2359
+ error
2360
+ );
2361
+ }
2362
+ }
2363
+ /**
2364
+ * Find board by ID (supports short ID)
2365
+ */
2366
+ async findById(id) {
2367
+ try {
2368
+ const fullId = await this.resolveId(id);
2369
+ const row = await this.db.select().from(boards).where((0, import_drizzle_orm5.eq)(boards.board_id, fullId)).get();
2370
+ return row ? this.rowToBoard(row) : null;
2371
+ } catch (error) {
2372
+ if (error instanceof EntityNotFoundError) return null;
2373
+ if (error instanceof AmbiguousIdError) throw error;
2374
+ throw new RepositoryError(
2375
+ `Failed to find board: ${error instanceof Error ? error.message : String(error)}`,
2376
+ error
2377
+ );
2378
+ }
2379
+ }
2380
+ /**
2381
+ * Find board by slug
2382
+ */
2383
+ async findBySlug(slug) {
2384
+ try {
2385
+ const row = await this.db.select().from(boards).where((0, import_drizzle_orm5.eq)(boards.slug, slug)).get();
2386
+ return row ? this.rowToBoard(row) : null;
2387
+ } catch (error) {
2388
+ throw new RepositoryError(
2389
+ `Failed to find board by slug: ${error instanceof Error ? error.message : String(error)}`,
2390
+ error
2391
+ );
2392
+ }
2393
+ }
2394
+ /**
2395
+ * Find all boards
2396
+ */
2397
+ async findAll() {
2398
+ try {
2399
+ const rows = await this.db.select().from(boards).all();
2400
+ return rows.map((row) => this.rowToBoard(row));
2401
+ } catch (error) {
2402
+ throw new RepositoryError(
2403
+ `Failed to find all boards: ${error instanceof Error ? error.message : String(error)}`,
2404
+ error
2405
+ );
2406
+ }
2407
+ }
2408
+ /**
2409
+ * Update board by ID
2410
+ */
2411
+ async update(id, updates) {
2412
+ try {
2413
+ const fullId = await this.resolveId(id);
2414
+ const current = await this.findById(fullId);
2415
+ if (!current) {
2416
+ throw new EntityNotFoundError("Board", id);
2417
+ }
2418
+ const merged = { ...current, ...updates };
2419
+ const insert = this.boardToInsert(merged);
2420
+ await this.db.update(boards).set({
2421
+ name: insert.name,
2422
+ slug: insert.slug,
2423
+ updated_at: /* @__PURE__ */ new Date(),
2424
+ data: insert.data
2425
+ }).where((0, import_drizzle_orm5.eq)(boards.board_id, fullId));
2426
+ const updated = await this.findById(fullId);
2427
+ if (!updated) {
2428
+ throw new RepositoryError("Failed to retrieve updated board");
2429
+ }
2430
+ return updated;
2431
+ } catch (error) {
2432
+ if (error instanceof RepositoryError) throw error;
2433
+ if (error instanceof EntityNotFoundError) throw error;
2434
+ throw new RepositoryError(
2435
+ `Failed to update board: ${error instanceof Error ? error.message : String(error)}`,
2436
+ error
2437
+ );
2438
+ }
2439
+ }
2440
+ /**
2441
+ * Delete board by ID
2442
+ */
2443
+ async delete(id) {
2444
+ try {
2445
+ const fullId = await this.resolveId(id);
2446
+ const result = await this.db.delete(boards).where((0, import_drizzle_orm5.eq)(boards.board_id, fullId)).run();
2447
+ if (result.rowsAffected === 0) {
2448
+ throw new EntityNotFoundError("Board", id);
2449
+ }
2450
+ } catch (error) {
2451
+ if (error instanceof EntityNotFoundError) throw error;
2452
+ throw new RepositoryError(
2453
+ `Failed to delete board: ${error instanceof Error ? error.message : String(error)}`,
2454
+ error
2455
+ );
2456
+ }
2457
+ }
2458
+ /**
2459
+ * DEPRECATED: Add session to board
2460
+ * Use board-objects service instead
2461
+ */
2462
+ // async addSession(boardId: string, sessionId: string): Promise<Board> {
2463
+ // throw new RepositoryError('addSession is deprecated - use board-objects service');
2464
+ // }
2465
+ /**
2466
+ * DEPRECATED: Remove session from board
2467
+ * Use board-objects service instead
2468
+ */
2469
+ // async removeSession(boardId: string, sessionId: string): Promise<Board> {
2470
+ // throw new RepositoryError('removeSession is deprecated - use board-objects service');
2471
+ // }
2472
+ /**
2473
+ * Get default board (or create if doesn't exist)
2474
+ */
2475
+ async getDefault() {
2476
+ try {
2477
+ const defaultBoard = await this.findBySlug("default");
2478
+ if (defaultBoard) {
2479
+ return defaultBoard;
2480
+ }
2481
+ return this.create({
2482
+ name: "Main Board",
2483
+ slug: "default",
2484
+ description: "Main board for all sessions",
2485
+ color: "#1677ff",
2486
+ icon: "\u2B50"
2487
+ });
2488
+ } catch (error) {
2489
+ throw new RepositoryError(
2490
+ `Failed to get default board: ${error instanceof Error ? error.message : String(error)}`,
2491
+ error
2492
+ );
2493
+ }
2494
+ }
2495
+ /**
2496
+ * Atomically add or update a board object (text label or zone)
2497
+ *
2498
+ * Uses read-modify-write approach with proper serialization via update() method.
2499
+ */
2500
+ async upsertBoardObject(boardId, objectId, objectData) {
2501
+ try {
2502
+ const fullId = await this.resolveId(boardId);
2503
+ const current = await this.findById(fullId);
2504
+ if (!current) {
2505
+ throw new EntityNotFoundError("Board", boardId);
2506
+ }
2507
+ const updatedObjects = { ...current.objects || {}, [objectId]: objectData };
2508
+ return this.update(fullId, { objects: updatedObjects });
2509
+ } catch (error) {
2510
+ if (error instanceof RepositoryError) throw error;
2511
+ if (error instanceof EntityNotFoundError) throw error;
2512
+ throw new RepositoryError(
2513
+ `Failed to upsert board object: ${error instanceof Error ? error.message : String(error)}`,
2514
+ error
2515
+ );
2516
+ }
2517
+ }
2518
+ /**
2519
+ * Atomically remove a board object
2520
+ */
2521
+ async removeBoardObject(boardId, objectId) {
2522
+ try {
2523
+ const fullId = await this.resolveId(boardId);
2524
+ const current = await this.findById(fullId);
2525
+ if (!current) {
2526
+ throw new EntityNotFoundError("Board", boardId);
2527
+ }
2528
+ const updatedObjects = { ...current.objects || {} };
2529
+ delete updatedObjects[objectId];
2530
+ return this.update(fullId, { objects: updatedObjects });
2531
+ } catch (error) {
2532
+ if (error instanceof RepositoryError) throw error;
2533
+ if (error instanceof EntityNotFoundError) throw error;
2534
+ throw new RepositoryError(
2535
+ `Failed to remove board object: ${error instanceof Error ? error.message : String(error)}`,
2536
+ error
2537
+ );
2538
+ }
2539
+ }
2540
+ /**
2541
+ * Batch upsert multiple objects (sequential atomic updates)
2542
+ *
2543
+ * Note: Not a single transaction - each object is updated atomically.
2544
+ * This is safe for independent objects but may have partial failures.
2545
+ */
2546
+ async batchUpsertBoardObjects(boardId, objects) {
2547
+ try {
2548
+ for (const [objectId, objectData] of Object.entries(objects)) {
2549
+ await this.upsertBoardObject(boardId, objectId, objectData);
2550
+ }
2551
+ const fullId = await this.resolveId(boardId);
2552
+ const updated = await this.findById(fullId);
2553
+ if (!updated) {
2554
+ throw new RepositoryError("Failed to retrieve updated board");
2555
+ }
2556
+ return updated;
2557
+ } catch (error) {
2558
+ if (error instanceof RepositoryError) throw error;
2559
+ if (error instanceof EntityNotFoundError) throw error;
2560
+ throw new RepositoryError(
2561
+ `Failed to batch upsert board objects: ${error instanceof Error ? error.message : String(error)}`,
2562
+ error
2563
+ );
2564
+ }
2565
+ }
2566
+ /**
2567
+ * DEPRECATED: Delete a zone and handle associated sessions
2568
+ * TODO: Reimplement using board-objects table
2569
+ */
2570
+ async deleteZone(boardId, objectId, _deleteAssociatedSessions) {
2571
+ const updatedBoard = await this.removeBoardObject(boardId, objectId);
2572
+ return {
2573
+ board: updatedBoard,
2574
+ affectedSessions: []
2575
+ // No sessions to track yet
2576
+ };
2577
+ }
2578
+ };
2579
+
2580
+ // src/db/repositories/mcp-servers.ts
2581
+ var import_drizzle_orm6 = require("drizzle-orm");
2582
+ init_ids();
2583
+ var MCPServerRepository = class {
2584
+ constructor(db) {
2585
+ this.db = db;
2586
+ }
2587
+ /**
2588
+ * Convert database row to MCPServer type
2589
+ */
2590
+ rowToMCPServer(row) {
2591
+ return {
2592
+ mcp_server_id: row.mcp_server_id,
2593
+ name: row.name,
2594
+ transport: row.transport,
2595
+ scope: row.scope,
2596
+ enabled: Boolean(row.enabled),
2597
+ source: row.source,
2598
+ created_at: new Date(row.created_at),
2599
+ updated_at: row.updated_at ? new Date(row.updated_at) : new Date(row.created_at),
2600
+ // Optional fields from JSON data
2601
+ display_name: row.data.display_name,
2602
+ description: row.data.description,
2603
+ import_path: row.data.import_path,
2604
+ // Transport config
2605
+ command: row.data.command,
2606
+ args: row.data.args,
2607
+ url: row.data.url,
2608
+ env: row.data.env,
2609
+ // Scope foreign keys (nullable UUID strings - DB stores null, types expect undefined)
2610
+ owner_user_id: row.owner_user_id ?? void 0,
2611
+ team_id: row.team_id ?? void 0,
2612
+ repo_id: row.repo_id ?? void 0,
2613
+ session_id: row.session_id ?? void 0,
2614
+ // Capabilities
2615
+ tools: row.data.tools,
2616
+ resources: row.data.resources,
2617
+ prompts: row.data.prompts
2618
+ };
2619
+ }
2620
+ /**
2621
+ * Convert MCPServer to database insert format
2622
+ */
2623
+ mcpServerToInsert(data) {
2624
+ const now = Date.now();
2625
+ const serverId = "mcp_server_id" in data && data.mcp_server_id ? data.mcp_server_id : generateId();
2626
+ return {
2627
+ mcp_server_id: serverId,
2628
+ created_at: new Date(now),
2629
+ updated_at: new Date(now),
2630
+ // Materialized columns
2631
+ name: data.name,
2632
+ transport: data.transport,
2633
+ scope: data.scope,
2634
+ enabled: data.enabled ?? true,
2635
+ source: data.source ?? "user",
2636
+ // Scope foreign keys
2637
+ owner_user_id: data.owner_user_id ?? null,
2638
+ team_id: data.team_id ?? null,
2639
+ repo_id: data.repo_id ?? null,
2640
+ session_id: data.session_id ?? null,
2641
+ // JSON blob
2642
+ data: {
2643
+ display_name: data.display_name,
2644
+ description: data.description,
2645
+ import_path: data.import_path,
2646
+ command: data.command,
2647
+ args: data.args,
2648
+ url: data.url,
2649
+ env: data.env,
2650
+ tools: "tools" in data ? data.tools : void 0,
2651
+ resources: "resources" in data ? data.resources : void 0,
2652
+ prompts: "prompts" in data ? data.prompts : void 0
2653
+ }
2654
+ };
2655
+ }
2656
+ /**
2657
+ * Resolve short ID to full ID
2658
+ */
2659
+ async resolveId(id) {
2660
+ if (id.length === 36 && id.includes("-")) {
2661
+ return id;
2662
+ }
2663
+ const normalized = id.replace(/-/g, "").toLowerCase();
2664
+ const pattern = `${normalized}%`;
2665
+ const results = await this.db.select({ mcp_server_id: mcpServers.mcp_server_id }).from(mcpServers).where((0, import_drizzle_orm6.like)(mcpServers.mcp_server_id, pattern)).all();
2666
+ if (results.length === 0) {
2667
+ throw new EntityNotFoundError("MCPServer", id);
2668
+ }
2669
+ if (results.length > 1) {
2670
+ throw new AmbiguousIdError(
2671
+ "MCPServer",
2672
+ id,
2673
+ results.map((r) => formatShortId(r.mcp_server_id))
2674
+ );
2675
+ }
2676
+ return results[0].mcp_server_id;
2677
+ }
2678
+ /**
2679
+ * Create a new MCP server
2680
+ */
2681
+ async create(data) {
2682
+ try {
2683
+ const insert = this.mcpServerToInsert(data);
2684
+ await this.db.insert(mcpServers).values(insert);
2685
+ const row = await this.db.select().from(mcpServers).where((0, import_drizzle_orm6.eq)(mcpServers.mcp_server_id, insert.mcp_server_id)).get();
2686
+ if (!row) {
2687
+ throw new RepositoryError("Failed to retrieve created MCP server");
2688
+ }
2689
+ return this.rowToMCPServer(row);
2690
+ } catch (error) {
2691
+ if (error instanceof RepositoryError) throw error;
2692
+ throw new RepositoryError(
2693
+ `Failed to create MCP server: ${error instanceof Error ? error.message : String(error)}`,
2694
+ error
2695
+ );
2696
+ }
2697
+ }
2698
+ /**
2699
+ * Find MCP server by ID (supports short ID)
2700
+ */
2701
+ async findById(id) {
2702
+ try {
2703
+ const fullId = await this.resolveId(id);
2704
+ const row = await this.db.select().from(mcpServers).where((0, import_drizzle_orm6.eq)(mcpServers.mcp_server_id, fullId)).get();
2705
+ return row ? this.rowToMCPServer(row) : null;
2706
+ } catch (error) {
2707
+ if (error instanceof EntityNotFoundError) return null;
2708
+ if (error instanceof AmbiguousIdError) throw error;
2709
+ throw new RepositoryError(
2710
+ `Failed to find MCP server: ${error instanceof Error ? error.message : String(error)}`,
2711
+ error
2712
+ );
2713
+ }
2714
+ }
2715
+ /**
2716
+ * Find all MCP servers
2717
+ */
2718
+ async findAll(filters) {
2719
+ try {
2720
+ let query = this.db.select().from(mcpServers);
2721
+ const conditions = [];
2722
+ if (filters?.scope) {
2723
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.scope, filters.scope));
2724
+ }
2725
+ if (filters?.scopeId) {
2726
+ if (filters.scope === "global") {
2727
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.owner_user_id, filters.scopeId));
2728
+ } else if (filters.scope === "team") {
2729
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.team_id, filters.scopeId));
2730
+ } else if (filters.scope === "repo") {
2731
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.repo_id, filters.scopeId));
2732
+ } else if (filters.scope === "session") {
2733
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.session_id, filters.scopeId));
2734
+ }
2735
+ }
2736
+ if (filters?.transport) {
2737
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.transport, filters.transport));
2738
+ }
2739
+ if (filters?.enabled !== void 0) {
2740
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.enabled, filters.enabled));
2741
+ }
2742
+ if (filters?.source) {
2743
+ conditions.push((0, import_drizzle_orm6.eq)(mcpServers.source, filters.source));
2744
+ }
2745
+ if (conditions.length > 0) {
2746
+ query = query.where((0, import_drizzle_orm6.and)(...conditions));
2747
+ }
2748
+ const rows = await query.all();
2749
+ return rows.map((row) => this.rowToMCPServer(row));
2750
+ } catch (error) {
2751
+ throw new RepositoryError(
2752
+ `Failed to find MCP servers: ${error instanceof Error ? error.message : String(error)}`,
2753
+ error
2754
+ );
2755
+ }
2756
+ }
2757
+ /**
2758
+ * Find MCP servers by scope
2759
+ */
2760
+ async findByScope(scope, scopeId) {
2761
+ return this.findAll({ scope, scopeId });
2762
+ }
2763
+ /**
2764
+ * Update MCP server by ID
2765
+ */
2766
+ async update(id, updates) {
2767
+ try {
2768
+ const fullId = await this.resolveId(id);
2769
+ const current = await this.findById(fullId);
2770
+ if (!current) {
2771
+ throw new EntityNotFoundError("MCPServer", id);
2772
+ }
2773
+ const merged = { ...current, ...updates };
2774
+ const insert = this.mcpServerToInsert(merged);
2775
+ await this.db.update(mcpServers).set({
2776
+ enabled: insert.enabled,
2777
+ updated_at: /* @__PURE__ */ new Date(),
2778
+ data: insert.data
2779
+ }).where((0, import_drizzle_orm6.eq)(mcpServers.mcp_server_id, fullId));
2780
+ const updated = await this.findById(fullId);
2781
+ if (!updated) {
2782
+ throw new RepositoryError("Failed to retrieve updated MCP server");
2783
+ }
2784
+ return updated;
2785
+ } catch (error) {
2786
+ if (error instanceof RepositoryError) throw error;
2787
+ if (error instanceof EntityNotFoundError) throw error;
2788
+ throw new RepositoryError(
2789
+ `Failed to update MCP server: ${error instanceof Error ? error.message : String(error)}`,
2790
+ error
2791
+ );
2792
+ }
2793
+ }
2794
+ /**
2795
+ * Delete MCP server by ID
2796
+ */
2797
+ async delete(id) {
2798
+ try {
2799
+ const fullId = await this.resolveId(id);
2800
+ const result = await this.db.delete(mcpServers).where((0, import_drizzle_orm6.eq)(mcpServers.mcp_server_id, fullId)).run();
2801
+ if (result.rowsAffected === 0) {
2802
+ throw new EntityNotFoundError("MCPServer", id);
2803
+ }
2804
+ } catch (error) {
2805
+ if (error instanceof EntityNotFoundError) throw error;
2806
+ throw new RepositoryError(
2807
+ `Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`,
2808
+ error
2809
+ );
2810
+ }
2811
+ }
2812
+ /**
2813
+ * Count total MCP servers
2814
+ */
2815
+ async count(filters) {
2816
+ try {
2817
+ const servers = await this.findAll(filters);
2818
+ return servers.length;
2819
+ } catch (error) {
2820
+ throw new RepositoryError(
2821
+ `Failed to count MCP servers: ${error instanceof Error ? error.message : String(error)}`,
2822
+ error
2823
+ );
2824
+ }
2825
+ }
2826
+ };
2827
+
2828
+ // src/db/repositories/messages.ts
2829
+ var import_drizzle_orm7 = require("drizzle-orm");
2830
+ var MessagesRepository = class {
2831
+ constructor(db) {
2832
+ this.db = db;
2833
+ }
2834
+ /**
2835
+ * Convert database row to Message type
2836
+ */
2837
+ rowToMessage(row) {
2838
+ return {
2839
+ message_id: row.message_id,
2840
+ session_id: row.session_id,
2841
+ task_id: row.task_id ? row.task_id : void 0,
2842
+ type: row.type,
2843
+ role: row.role,
2844
+ index: row.index,
2845
+ timestamp: new Date(row.timestamp).toISOString(),
2846
+ content_preview: row.content_preview || "",
2847
+ content: row.data.content,
2848
+ tool_uses: row.data.tool_uses,
2849
+ metadata: row.data.metadata
2850
+ };
2851
+ }
2852
+ /**
2853
+ * Convert Message to database row
2854
+ */
2855
+ messageToRow(message) {
2856
+ return {
2857
+ message_id: message.message_id,
2858
+ created_at: /* @__PURE__ */ new Date(),
2859
+ session_id: message.session_id,
2860
+ task_id: message.task_id,
2861
+ type: message.type,
2862
+ role: message.role,
2863
+ index: message.index,
2864
+ timestamp: new Date(message.timestamp),
2865
+ content_preview: message.content_preview,
2866
+ data: {
2867
+ content: message.content,
2868
+ tool_uses: message.tool_uses,
2869
+ metadata: message.metadata
2870
+ }
2871
+ };
2872
+ }
2873
+ /**
2874
+ * Create a single message
2875
+ */
2876
+ async create(message) {
2877
+ const row = this.messageToRow(message);
2878
+ const [inserted] = await this.db.insert(messages).values(row).returning();
2879
+ return this.rowToMessage(inserted);
2880
+ }
2881
+ /**
2882
+ * Bulk insert messages (optimized for session loading)
2883
+ */
2884
+ async createMany(messageList) {
2885
+ const rows = messageList.map((m) => this.messageToRow(m));
2886
+ const inserted = await this.db.insert(messages).values(rows).returning();
2887
+ return inserted.map((r) => this.rowToMessage(r));
2888
+ }
2889
+ /**
2890
+ * Get message by ID
2891
+ */
2892
+ async findById(messageId) {
2893
+ const rows = await this.db.select().from(messages).where((0, import_drizzle_orm7.eq)(messages.message_id, messageId)).limit(1);
2894
+ return rows[0] ? this.rowToMessage(rows[0]) : null;
2895
+ }
2896
+ /**
2897
+ * Get all messages (used by FeathersJS service adapter)
2898
+ */
2899
+ async findAll() {
2900
+ const rows = await this.db.select().from(messages).orderBy(messages.index);
2901
+ return rows.map((r) => this.rowToMessage(r));
2902
+ }
2903
+ /**
2904
+ * Get all messages for a session (ordered by index)
2905
+ */
2906
+ async findBySessionId(sessionId) {
2907
+ const rows = await this.db.select().from(messages).where((0, import_drizzle_orm7.eq)(messages.session_id, sessionId)).orderBy(messages.index);
2908
+ return rows.map((r) => this.rowToMessage(r));
2909
+ }
2910
+ /**
2911
+ * Get all messages for a task (ordered by index)
2912
+ */
2913
+ async findByTaskId(taskId) {
2914
+ const rows = await this.db.select().from(messages).where((0, import_drizzle_orm7.eq)(messages.task_id, taskId)).orderBy(messages.index);
2915
+ return rows.map((r) => this.rowToMessage(r));
2916
+ }
2917
+ /**
2918
+ * Get messages in a range for a session
2919
+ * Used for task message_range queries
2920
+ */
2921
+ async findByRange(sessionId, startIndex, endIndex) {
2922
+ const rows = await this.db.select().from(messages).where((0, import_drizzle_orm7.eq)(messages.session_id, sessionId)).orderBy(messages.index);
2923
+ return rows.filter((r) => r.index >= startIndex && r.index <= endIndex).map((r) => this.rowToMessage(r));
2924
+ }
2925
+ /**
2926
+ * Update message (used by FeathersJS service adapter)
2927
+ */
2928
+ async update(messageId, updates) {
2929
+ const existing = await this.findById(messageId);
2930
+ if (!existing) {
2931
+ throw new Error(`Message ${messageId} not found`);
2932
+ }
2933
+ const updated = { ...existing, ...updates };
2934
+ const row = this.messageToRow(updated);
2935
+ const [result] = await this.db.update(messages).set(row).where((0, import_drizzle_orm7.eq)(messages.message_id, messageId)).returning();
2936
+ return this.rowToMessage(result);
2937
+ }
2938
+ /**
2939
+ * Update message task assignment
2940
+ */
2941
+ async assignToTask(messageId, taskId) {
2942
+ const [updated] = await this.db.update(messages).set({ task_id: taskId }).where((0, import_drizzle_orm7.eq)(messages.message_id, messageId)).returning();
2943
+ return this.rowToMessage(updated);
2944
+ }
2945
+ /**
2946
+ * Delete all messages for a session (cascades automatically via FK)
2947
+ */
2948
+ async deleteBySessionId(sessionId) {
2949
+ await this.db.delete(messages).where((0, import_drizzle_orm7.eq)(messages.session_id, sessionId));
2950
+ }
2951
+ /**
2952
+ * Delete a single message
2953
+ */
2954
+ async delete(messageId) {
2955
+ await this.db.delete(messages).where((0, import_drizzle_orm7.eq)(messages.message_id, messageId));
2956
+ }
2957
+ };
2958
+
2959
+ // src/db/repositories/repos.ts
2960
+ var import_drizzle_orm8 = require("drizzle-orm");
2961
+ init_ids();
2962
+ var RepoRepository = class {
2963
+ constructor(db) {
2964
+ this.db = db;
2965
+ }
2966
+ /**
2967
+ * Convert database row to Repo type
2968
+ */
2969
+ rowToRepo(row) {
2970
+ return {
2971
+ repo_id: row.repo_id,
2972
+ slug: row.slug,
2973
+ created_at: new Date(row.created_at).toISOString(),
2974
+ last_updated: row.updated_at ? new Date(row.updated_at).toISOString() : new Date(row.created_at).toISOString(),
2975
+ ...row.data
2976
+ };
2977
+ }
2978
+ /**
2979
+ * Convert Repo to database insert format
2980
+ */
2981
+ repoToInsert(repo) {
2982
+ const now = Date.now();
2983
+ const repoId = repo.repo_id ?? generateId();
2984
+ if (!repo.slug) {
2985
+ throw new RepositoryError("slug is required when creating a repo");
2986
+ }
2987
+ if (!repo.remote_url) {
2988
+ throw new RepositoryError("Repo must have a remote_url");
2989
+ }
2990
+ return {
2991
+ repo_id: repoId,
2992
+ slug: repo.slug,
2993
+ created_at: new Date(repo.created_at ?? now),
2994
+ updated_at: repo.last_updated ? new Date(repo.last_updated) : new Date(now),
2995
+ data: {
2996
+ name: repo.name ?? repo.slug,
2997
+ remote_url: repo.remote_url,
2998
+ local_path: repo.local_path ?? "",
2999
+ default_branch: repo.default_branch,
3000
+ environment_config: repo.environment_config
3001
+ }
3002
+ };
3003
+ }
3004
+ /**
3005
+ * Resolve short ID to full ID
3006
+ */
3007
+ async resolveId(id) {
3008
+ if (id.length === 36 && id.includes("-")) {
3009
+ return id;
3010
+ }
3011
+ const normalized = id.replace(/-/g, "").toLowerCase();
3012
+ const pattern = `${normalized}%`;
3013
+ const results = await this.db.select({ repo_id: repos.repo_id }).from(repos).where((0, import_drizzle_orm8.like)(repos.repo_id, pattern)).all();
3014
+ if (results.length === 0) {
3015
+ throw new EntityNotFoundError("Repo", id);
3016
+ }
3017
+ if (results.length > 1) {
3018
+ throw new AmbiguousIdError(
3019
+ "Repo",
3020
+ id,
3021
+ results.map((r) => formatShortId(r.repo_id))
3022
+ );
3023
+ }
3024
+ return results[0].repo_id;
3025
+ }
3026
+ /**
3027
+ * Create a new repo
3028
+ */
3029
+ async create(data) {
3030
+ try {
3031
+ const insert = this.repoToInsert(data);
3032
+ await this.db.insert(repos).values(insert);
3033
+ const row = await this.db.select().from(repos).where((0, import_drizzle_orm8.eq)(repos.repo_id, insert.repo_id)).get();
3034
+ if (!row) {
3035
+ throw new RepositoryError("Failed to retrieve created repo");
3036
+ }
3037
+ return this.rowToRepo(row);
3038
+ } catch (error) {
3039
+ if (error instanceof RepositoryError) throw error;
3040
+ throw new RepositoryError(
3041
+ `Failed to create repo: ${error instanceof Error ? error.message : String(error)}`,
3042
+ error
3043
+ );
3044
+ }
3045
+ }
3046
+ /**
3047
+ * Find repo by ID (supports short ID)
3048
+ */
3049
+ async findById(id) {
3050
+ try {
3051
+ const fullId = await this.resolveId(id);
3052
+ const row = await this.db.select().from(repos).where((0, import_drizzle_orm8.eq)(repos.repo_id, fullId)).get();
3053
+ return row ? this.rowToRepo(row) : null;
3054
+ } catch (error) {
3055
+ if (error instanceof EntityNotFoundError) return null;
3056
+ if (error instanceof AmbiguousIdError) throw error;
3057
+ throw new RepositoryError(
3058
+ `Failed to find repo: ${error instanceof Error ? error.message : String(error)}`,
3059
+ error
3060
+ );
3061
+ }
3062
+ }
3063
+ /**
3064
+ * Find repo by slug (exact match)
3065
+ */
3066
+ async findBySlug(slug) {
3067
+ try {
3068
+ const row = await this.db.select().from(repos).where((0, import_drizzle_orm8.eq)(repos.slug, slug)).get();
3069
+ return row ? this.rowToRepo(row) : null;
3070
+ } catch (error) {
3071
+ throw new RepositoryError(
3072
+ `Failed to find repo by slug: ${error instanceof Error ? error.message : String(error)}`,
3073
+ error
3074
+ );
3075
+ }
3076
+ }
3077
+ /**
3078
+ * Find all repos
3079
+ */
3080
+ async findAll() {
3081
+ try {
3082
+ const rows = await this.db.select().from(repos).all();
3083
+ return rows.map((row) => this.rowToRepo(row));
3084
+ } catch (error) {
3085
+ throw new RepositoryError(
3086
+ `Failed to find all repos: ${error instanceof Error ? error.message : String(error)}`,
3087
+ error
3088
+ );
3089
+ }
3090
+ }
3091
+ /**
3092
+ * Find managed repos only (DEPRECATED: all repos are managed now)
3093
+ *
3094
+ * Kept for backwards compatibility - returns all repos.
3095
+ */
3096
+ async findManaged() {
3097
+ return this.findAll();
3098
+ }
3099
+ /**
3100
+ * Update repo by ID
3101
+ */
3102
+ async update(id, updates) {
3103
+ try {
3104
+ const fullId = await this.resolveId(id);
3105
+ const current = await this.findById(fullId);
3106
+ if (!current) {
3107
+ throw new EntityNotFoundError("Repo", id);
3108
+ }
3109
+ const merged = { ...current, ...updates };
3110
+ const insert = this.repoToInsert(merged);
3111
+ await this.db.update(repos).set({
3112
+ slug: insert.slug,
3113
+ updated_at: /* @__PURE__ */ new Date(),
3114
+ data: insert.data
3115
+ }).where((0, import_drizzle_orm8.eq)(repos.repo_id, fullId));
3116
+ const updated = await this.findById(fullId);
3117
+ if (!updated) {
3118
+ throw new RepositoryError("Failed to retrieve updated repo");
3119
+ }
3120
+ return updated;
3121
+ } catch (error) {
3122
+ if (error instanceof RepositoryError) throw error;
3123
+ if (error instanceof EntityNotFoundError) throw error;
3124
+ throw new RepositoryError(
3125
+ `Failed to update repo: ${error instanceof Error ? error.message : String(error)}`,
3126
+ error
3127
+ );
3128
+ }
3129
+ }
3130
+ /**
3131
+ * Delete repo by ID
3132
+ */
3133
+ async delete(id) {
3134
+ try {
3135
+ const fullId = await this.resolveId(id);
3136
+ const result = await this.db.delete(repos).where((0, import_drizzle_orm8.eq)(repos.repo_id, fullId)).run();
3137
+ if (result.rowsAffected === 0) {
3138
+ throw new EntityNotFoundError("Repo", id);
3139
+ }
3140
+ } catch (error) {
3141
+ if (error instanceof EntityNotFoundError) throw error;
3142
+ throw new RepositoryError(
3143
+ `Failed to delete repo: ${error instanceof Error ? error.message : String(error)}`,
3144
+ error
3145
+ );
3146
+ }
3147
+ }
3148
+ /**
3149
+ * @deprecated Worktrees are now first-class entities in their own table.
3150
+ * Use WorktreeRepository instead.
3151
+ */
3152
+ async addWorktree() {
3153
+ throw new Error("addWorktree is deprecated. Use WorktreeRepository.create() instead.");
3154
+ }
3155
+ /**
3156
+ * @deprecated Worktrees are now first-class entities in their own table.
3157
+ * Use WorktreeRepository instead.
3158
+ */
3159
+ async removeWorktree() {
3160
+ throw new Error("removeWorktree is deprecated. Use WorktreeRepository.delete() instead.");
3161
+ }
3162
+ /**
3163
+ * Count total repos
3164
+ */
3165
+ async count() {
3166
+ try {
3167
+ const result = await this.db.select({ count: import_drizzle_orm8.sql`count(*)` }).from(repos).get();
3168
+ return result?.count ?? 0;
3169
+ } catch (error) {
3170
+ throw new RepositoryError(
3171
+ `Failed to count repos: ${error instanceof Error ? error.message : String(error)}`,
3172
+ error
3173
+ );
3174
+ }
3175
+ }
3176
+ };
3177
+
3178
+ // src/db/repositories/session-mcp-servers.ts
3179
+ var import_drizzle_orm10 = require("drizzle-orm");
3180
+
3181
+ // src/types/board-comment.ts
3182
+ var CommentAttachmentType = {
3183
+ MESSAGE: "message",
3184
+ TASK: "task",
3185
+ SESSION_SPATIAL: "session-spatial",
3186
+ SESSION: "session",
3187
+ WORKTREE: "worktree",
3188
+ BOARD_SPATIAL: "board-spatial",
3189
+ BOARD: "board"
3190
+ };
3191
+ function getCommentAttachmentType(comment) {
3192
+ if (comment.message_id) return CommentAttachmentType.MESSAGE;
3193
+ if (comment.task_id) return CommentAttachmentType.TASK;
3194
+ if (comment.session_id && comment.position?.relative)
3195
+ return CommentAttachmentType.SESSION_SPATIAL;
3196
+ if (comment.session_id) return CommentAttachmentType.SESSION;
3197
+ if (comment.worktree_id) return CommentAttachmentType.WORKTREE;
3198
+ if (comment.position?.absolute) return CommentAttachmentType.BOARD_SPATIAL;
3199
+ return CommentAttachmentType.BOARD;
3200
+ }
3201
+ function isThreadRoot(comment) {
3202
+ return !comment.parent_comment_id;
3203
+ }
3204
+ function isReply(comment) {
3205
+ return !!comment.parent_comment_id;
3206
+ }
3207
+ function isResolvable(comment) {
3208
+ return isThreadRoot(comment);
3209
+ }
3210
+ function groupReactions(reactions) {
3211
+ const grouped = {};
3212
+ for (const { emoji, user_id } of reactions) {
3213
+ if (!grouped[emoji]) {
3214
+ grouped[emoji] = [];
3215
+ }
3216
+ grouped[emoji].push(user_id);
3217
+ }
3218
+ return grouped;
3219
+ }
3220
+
3221
+ // src/types/message.ts
3222
+ var MessageRole = /* @__PURE__ */ ((MessageRole2) => {
3223
+ MessageRole2["USER"] = "user";
3224
+ MessageRole2["ASSISTANT"] = "assistant";
3225
+ MessageRole2["SYSTEM"] = "system";
3226
+ return MessageRole2;
3227
+ })(MessageRole || {});
3228
+ var PermissionScope = /* @__PURE__ */ ((PermissionScope2) => {
3229
+ PermissionScope2["ONCE"] = "once";
3230
+ PermissionScope2["SESSION"] = "session";
3231
+ PermissionScope2["PROJECT"] = "project";
3232
+ return PermissionScope2;
3233
+ })(PermissionScope || {});
3234
+ var PermissionStatus = /* @__PURE__ */ ((PermissionStatus2) => {
3235
+ PermissionStatus2["PENDING"] = "pending";
3236
+ PermissionStatus2["APPROVED"] = "approved";
3237
+ PermissionStatus2["DENIED"] = "denied";
3238
+ return PermissionStatus2;
3239
+ })(PermissionStatus || {});
3240
+
3241
+ // src/types/session.ts
3242
+ var SessionStatus = {
3243
+ IDLE: "idle",
3244
+ RUNNING: "running",
3245
+ COMPLETED: "completed",
3246
+ FAILED: "failed"
3247
+ };
3248
+ function getDefaultPermissionMode(agenticTool) {
3249
+ switch (agenticTool) {
3250
+ case "codex":
3251
+ return "auto";
3252
+ default:
3253
+ return "acceptEdits";
3254
+ }
3255
+ }
3256
+
3257
+ // src/types/task.ts
3258
+ var TaskStatus = {
3259
+ CREATED: "created",
3260
+ RUNNING: "running",
3261
+ STOPPING: "stopping",
3262
+ // Stop requested, waiting for SDK to halt
3263
+ AWAITING_PERMISSION: "awaiting_permission",
3264
+ COMPLETED: "completed",
3265
+ FAILED: "failed",
3266
+ STOPPED: "stopped"
3267
+ // User-requested stop (distinct from failed)
3268
+ };
3269
+
3270
+ // src/types/utils.ts
3271
+ function isDefined(value) {
3272
+ return value !== null && value !== void 0;
3273
+ }
3274
+ function isNonEmptyString(value) {
3275
+ return typeof value === "string" && value.trim().length > 0;
3276
+ }
3277
+
3278
+ // src/db/repositories/sessions.ts
3279
+ var import_drizzle_orm9 = require("drizzle-orm");
3280
+ init_ids();
3281
+ var SessionRepository = class {
3282
+ constructor(db) {
3283
+ this.db = db;
3284
+ }
3285
+ /**
3286
+ * Convert database row to Session type
3287
+ */
3288
+ rowToSession(row) {
3289
+ const genealogyData = row.data.genealogy || { children: [] };
3290
+ return {
3291
+ session_id: row.session_id,
3292
+ status: row.status,
3293
+ agentic_tool: row.agentic_tool,
3294
+ created_at: new Date(row.created_at).toISOString(),
3295
+ last_updated: row.updated_at ? new Date(row.updated_at).toISOString() : new Date(row.created_at).toISOString(),
3296
+ created_by: row.created_by,
3297
+ worktree_id: row.worktree_id,
3298
+ ...row.data,
3299
+ tasks: row.data.tasks.map((id) => id),
3300
+ genealogy: {
3301
+ parent_session_id: row.parent_session_id,
3302
+ forked_from_session_id: row.forked_from_session_id,
3303
+ fork_point_task_id: genealogyData.fork_point_task_id,
3304
+ spawn_point_task_id: genealogyData.spawn_point_task_id,
3305
+ children: genealogyData.children.map((id) => id)
3306
+ },
3307
+ permission_config: row.data.permission_config
3308
+ };
3309
+ }
3310
+ /**
3311
+ * Convert Session to database insert format
3312
+ */
3313
+ sessionToInsert(session) {
3314
+ const now = Date.now();
3315
+ const sessionId = session.session_id ?? generateId();
3316
+ if (!session.worktree_id) {
3317
+ throw new RepositoryError("Session must have a worktree_id");
3318
+ }
3319
+ return {
3320
+ session_id: sessionId,
3321
+ created_at: new Date(session.created_at ? session.created_at : now),
3322
+ updated_at: session.last_updated ? new Date(session.last_updated) : new Date(now),
3323
+ status: session.status ?? SessionStatus.IDLE,
3324
+ agentic_tool: session.agentic_tool ?? "claude-code",
3325
+ created_by: session.created_by ?? "anonymous",
3326
+ board_id: null,
3327
+ // Board ID tracked separately in boards.sessions array
3328
+ parent_session_id: session.genealogy?.parent_session_id ?? null,
3329
+ forked_from_session_id: session.genealogy?.forked_from_session_id ?? null,
3330
+ worktree_id: session.worktree_id,
3331
+ data: {
3332
+ agentic_tool_version: session.agentic_tool_version,
3333
+ sdk_session_id: session.sdk_session_id,
3334
+ // Preserve SDK session ID for conversation continuity
3335
+ mcp_token: session.mcp_token,
3336
+ // MCP authentication token for Agor self-access
3337
+ title: session.title,
3338
+ description: session.description,
3339
+ git_state: session.git_state ?? {
3340
+ ref: "main",
3341
+ base_sha: "",
3342
+ current_sha: ""
3343
+ },
3344
+ genealogy: session.genealogy ?? {
3345
+ children: []
3346
+ },
3347
+ contextFiles: session.contextFiles ?? [],
3348
+ tasks: session.tasks ?? [],
3349
+ message_count: session.message_count ?? 0,
3350
+ tool_use_count: session.tool_use_count ?? 0,
3351
+ permission_config: session.permission_config,
3352
+ model_config: session.model_config,
3353
+ custom_context: session.custom_context
3354
+ }
3355
+ };
3356
+ }
3357
+ /**
3358
+ * Resolve short ID to full ID
3359
+ */
3360
+ async resolveId(id) {
3361
+ if (id.length === 36 && id.includes("-")) {
3362
+ return id;
3363
+ }
3364
+ const normalized = id.replace(/-/g, "").toLowerCase();
3365
+ const pattern = `${normalized}%`;
3366
+ const results = await this.db.select({ session_id: sessions.session_id }).from(sessions).where((0, import_drizzle_orm9.like)(sessions.session_id, pattern)).all();
3367
+ if (results.length === 0) {
3368
+ throw new EntityNotFoundError("Session", id);
3369
+ }
3370
+ if (results.length > 1) {
3371
+ throw new AmbiguousIdError(
3372
+ "Session",
3373
+ id,
3374
+ results.map((r) => formatShortId(r.session_id))
3375
+ );
3376
+ }
3377
+ return results[0].session_id;
3378
+ }
3379
+ /**
3380
+ * Create a new session
3381
+ */
3382
+ async create(data) {
3383
+ try {
3384
+ const insert = this.sessionToInsert(data);
3385
+ await this.db.insert(sessions).values(insert);
3386
+ const row = await this.db.select().from(sessions).where((0, import_drizzle_orm9.eq)(sessions.session_id, insert.session_id)).get();
3387
+ if (!row) {
3388
+ throw new RepositoryError("Failed to retrieve created session");
3389
+ }
3390
+ return this.rowToSession(row);
3391
+ } catch (error) {
3392
+ if (error instanceof RepositoryError) throw error;
3393
+ throw new RepositoryError(
3394
+ `Failed to create session: ${error instanceof Error ? error.message : String(error)}`,
3395
+ error
3396
+ );
3397
+ }
3398
+ }
3399
+ /**
3400
+ * Find session by ID (supports short ID)
3401
+ */
3402
+ async findById(id) {
3403
+ try {
3404
+ const fullId = await this.resolveId(id);
3405
+ const row = await this.db.select().from(sessions).where((0, import_drizzle_orm9.eq)(sessions.session_id, fullId)).get();
3406
+ return row ? this.rowToSession(row) : null;
3407
+ } catch (error) {
3408
+ if (error instanceof EntityNotFoundError) return null;
3409
+ if (error instanceof AmbiguousIdError) throw error;
3410
+ throw new RepositoryError(
3411
+ `Failed to find session: ${error instanceof Error ? error.message : String(error)}`,
3412
+ error
3413
+ );
3414
+ }
3415
+ }
3416
+ /**
3417
+ * Find all sessions
3418
+ */
3419
+ async findAll() {
3420
+ try {
3421
+ const rows = await this.db.select().from(sessions).all();
3422
+ return rows.map((row) => this.rowToSession(row));
3423
+ } catch (error) {
3424
+ throw new RepositoryError(
3425
+ `Failed to find all sessions: ${error instanceof Error ? error.message : String(error)}`,
3426
+ error
3427
+ );
3428
+ }
3429
+ }
3430
+ /**
3431
+ * Find sessions by status
3432
+ */
3433
+ async findByStatus(status) {
3434
+ try {
3435
+ const rows = await this.db.select().from(sessions).where((0, import_drizzle_orm9.eq)(sessions.status, status)).all();
3436
+ return rows.map((row) => this.rowToSession(row));
3437
+ } catch (error) {
3438
+ throw new RepositoryError(
3439
+ `Failed to find sessions by status: ${error instanceof Error ? error.message : String(error)}`,
3440
+ error
3441
+ );
3442
+ }
3443
+ }
3444
+ /**
3445
+ * Find sessions by board ID
3446
+ */
3447
+ async findByBoard(_boardId) {
3448
+ try {
3449
+ const rows = await this.db.select().from(sessions).all();
3450
+ return rows.map((row) => this.rowToSession(row));
3451
+ } catch (error) {
3452
+ throw new RepositoryError(
3453
+ `Failed to find sessions by board: ${error instanceof Error ? error.message : String(error)}`,
3454
+ error
3455
+ );
3456
+ }
3457
+ }
3458
+ /**
3459
+ * Find child sessions (forked or spawned from this session)
3460
+ */
3461
+ async findChildren(sessionId) {
3462
+ try {
3463
+ const fullId = await this.resolveId(sessionId);
3464
+ const rows = await this.db.select().from(sessions).where(
3465
+ (0, import_drizzle_orm9.or)(
3466
+ import_drizzle_orm9.sql`json_extract(${sessions.data}, '$.genealogy.parent_session_id') = ${fullId}`,
3467
+ import_drizzle_orm9.sql`json_extract(${sessions.data}, '$.genealogy.forked_from_session_id') = ${fullId}`
3468
+ )
3469
+ ).all();
3470
+ return rows.map((row) => this.rowToSession(row));
3471
+ } catch (error) {
3472
+ throw new RepositoryError(
3473
+ `Failed to find child sessions: ${error instanceof Error ? error.message : String(error)}`,
3474
+ error
3475
+ );
3476
+ }
3477
+ }
3478
+ /**
3479
+ * Find ancestor sessions (parent chain)
3480
+ */
3481
+ async findAncestors(sessionId) {
3482
+ try {
3483
+ const fullId = await this.resolveId(sessionId);
3484
+ const ancestors = [];
3485
+ let currentSession = await this.findById(fullId);
3486
+ while (currentSession) {
3487
+ const parentId = currentSession.genealogy?.parent_session_id || currentSession.genealogy?.forked_from_session_id;
3488
+ if (!parentId) break;
3489
+ const parent = await this.findById(parentId);
3490
+ if (!parent) break;
3491
+ ancestors.push(parent);
3492
+ currentSession = parent;
3493
+ }
3494
+ return ancestors;
3495
+ } catch (error) {
3496
+ throw new RepositoryError(
3497
+ `Failed to find ancestor sessions: ${error instanceof Error ? error.message : String(error)}`,
3498
+ error
3499
+ );
3500
+ }
3501
+ }
3502
+ /**
3503
+ * Update session by ID
3504
+ */
3505
+ async update(id, updates) {
3506
+ try {
3507
+ const fullId = await this.resolveId(id);
3508
+ const current = await this.findById(fullId);
3509
+ if (!current) {
3510
+ throw new EntityNotFoundError("Session", id);
3511
+ }
3512
+ const merged = {
3513
+ ...current,
3514
+ ...updates
3515
+ };
3516
+ if (updates.permission_config) {
3517
+ console.log(`\u{1F4DD} [SessionRepository] Merging permission_config update`);
3518
+ console.log(
3519
+ ` Before merge - current.permission_config: ${JSON.stringify(current.permission_config)}`
3520
+ );
3521
+ console.log(` Update permission_config: ${JSON.stringify(updates.permission_config)}`);
3522
+ console.log(
3523
+ ` After merge - merged.permission_config: ${JSON.stringify(merged.permission_config)}`
3524
+ );
3525
+ }
3526
+ const insert = this.sessionToInsert(merged);
3527
+ console.log(`\u{1F5C4}\uFE0F [SessionRepository] Writing to DB:`);
3528
+ console.log(
3529
+ ` insert.data.permission_config: ${JSON.stringify(insert.data.permission_config)}`
3530
+ );
3531
+ await this.db.update(sessions).set({
3532
+ status: insert.status,
3533
+ updated_at: /* @__PURE__ */ new Date(),
3534
+ data: insert.data
3535
+ }).where((0, import_drizzle_orm9.eq)(sessions.session_id, fullId));
3536
+ console.log(`\u2705 [SessionRepository] DB update complete`);
3537
+ const updated = await this.findById(fullId);
3538
+ if (!updated) {
3539
+ throw new RepositoryError("Failed to retrieve updated session");
3540
+ }
3541
+ return updated;
3542
+ } catch (error) {
3543
+ if (error instanceof RepositoryError) throw error;
3544
+ if (error instanceof EntityNotFoundError) throw error;
3545
+ throw new RepositoryError(
3546
+ `Failed to update session: ${error instanceof Error ? error.message : String(error)}`,
3547
+ error
3548
+ );
3549
+ }
3550
+ }
3551
+ /**
3552
+ * Delete session by ID
3553
+ */
3554
+ async delete(id) {
3555
+ try {
3556
+ const fullId = await this.resolveId(id);
3557
+ const result = await this.db.delete(sessions).where((0, import_drizzle_orm9.eq)(sessions.session_id, fullId)).run();
3558
+ if (result.rowsAffected === 0) {
3559
+ throw new EntityNotFoundError("Session", id);
3560
+ }
3561
+ } catch (error) {
3562
+ if (error instanceof EntityNotFoundError) throw error;
3563
+ throw new RepositoryError(
3564
+ `Failed to delete session: ${error instanceof Error ? error.message : String(error)}`,
3565
+ error
3566
+ );
3567
+ }
3568
+ }
3569
+ /**
3570
+ * Find sessions with running tasks
3571
+ */
3572
+ async findRunning() {
3573
+ return this.findByStatus(SessionStatus.RUNNING);
3574
+ }
3575
+ /**
3576
+ * Count total sessions
3577
+ */
3578
+ async count() {
3579
+ try {
3580
+ const result = await this.db.select({ count: import_drizzle_orm9.sql`count(*)` }).from(sessions).get();
3581
+ return result?.count ?? 0;
3582
+ } catch (error) {
3583
+ throw new RepositoryError(
3584
+ `Failed to count sessions: ${error instanceof Error ? error.message : String(error)}`,
3585
+ error
3586
+ );
3587
+ }
3588
+ }
3589
+ };
3590
+
3591
+ // src/db/repositories/session-mcp-servers.ts
3592
+ var SessionMCPServerRepository = class {
3593
+ constructor(db) {
3594
+ this.db = db;
3595
+ this.sessionRepo = new SessionRepository(db);
3596
+ this.mcpServerRepo = new MCPServerRepository(db);
3597
+ }
3598
+ sessionRepo;
3599
+ mcpServerRepo;
3600
+ /**
3601
+ * Add MCP server to session
3602
+ */
3603
+ async addServer(sessionId, serverId) {
3604
+ try {
3605
+ const session = await this.sessionRepo.findById(sessionId);
3606
+ if (!session) {
3607
+ throw new EntityNotFoundError("Session", sessionId);
3608
+ }
3609
+ const server = await this.mcpServerRepo.findById(serverId);
3610
+ if (!server) {
3611
+ throw new EntityNotFoundError("MCPServer", serverId);
3612
+ }
3613
+ const existing = await this.db.select().from(sessionMcpServers).where(
3614
+ (0, import_drizzle_orm10.and)(
3615
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.session_id, sessionId),
3616
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.mcp_server_id, serverId)
3617
+ )
3618
+ ).get();
3619
+ if (existing) {
3620
+ await this.db.update(sessionMcpServers).set({ enabled: true }).where(
3621
+ (0, import_drizzle_orm10.and)(
3622
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.session_id, sessionId),
3623
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.mcp_server_id, serverId)
3624
+ )
3625
+ );
3626
+ return;
3627
+ }
3628
+ const insert = {
3629
+ session_id: sessionId,
3630
+ mcp_server_id: serverId,
3631
+ enabled: true,
3632
+ added_at: /* @__PURE__ */ new Date()
3633
+ };
3634
+ await this.db.insert(sessionMcpServers).values(insert);
3635
+ } catch (error) {
3636
+ if (error instanceof EntityNotFoundError) throw error;
3637
+ throw new RepositoryError(
3638
+ `Failed to add MCP server to session: ${error instanceof Error ? error.message : String(error)}`,
3639
+ error
3640
+ );
3641
+ }
3642
+ }
3643
+ /**
3644
+ * Remove MCP server from session
3645
+ */
3646
+ async removeServer(sessionId, serverId) {
3647
+ try {
3648
+ const result = await this.db.delete(sessionMcpServers).where(
3649
+ (0, import_drizzle_orm10.and)(
3650
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.session_id, sessionId),
3651
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.mcp_server_id, serverId)
3652
+ )
3653
+ ).run();
3654
+ if (result.rowsAffected === 0) {
3655
+ throw new EntityNotFoundError("SessionMCPServer", `${sessionId}/${serverId}`);
3656
+ }
3657
+ } catch (error) {
3658
+ if (error instanceof EntityNotFoundError) throw error;
3659
+ throw new RepositoryError(
3660
+ `Failed to remove MCP server from session: ${error instanceof Error ? error.message : String(error)}`,
3661
+ error
3662
+ );
3663
+ }
3664
+ }
3665
+ /**
3666
+ * Toggle MCP server enabled state for session
3667
+ */
3668
+ async toggleServer(sessionId, serverId, enabled) {
3669
+ try {
3670
+ const result = await this.db.update(sessionMcpServers).set({ enabled }).where(
3671
+ (0, import_drizzle_orm10.and)(
3672
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.session_id, sessionId),
3673
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.mcp_server_id, serverId)
3674
+ )
3675
+ ).run();
3676
+ if (result.rowsAffected === 0) {
3677
+ throw new EntityNotFoundError("SessionMCPServer", `${sessionId}/${serverId}`);
3678
+ }
3679
+ } catch (error) {
3680
+ if (error instanceof EntityNotFoundError) throw error;
3681
+ throw new RepositoryError(
3682
+ `Failed to toggle MCP server: ${error instanceof Error ? error.message : String(error)}`,
3683
+ error
3684
+ );
3685
+ }
3686
+ }
3687
+ /**
3688
+ * List MCP servers for a session
3689
+ */
3690
+ async listServers(sessionId, enabledOnly = false) {
3691
+ try {
3692
+ const conditions = [(0, import_drizzle_orm10.eq)(sessionMcpServers.session_id, sessionId)];
3693
+ if (enabledOnly) {
3694
+ conditions.push((0, import_drizzle_orm10.eq)(sessionMcpServers.enabled, true));
3695
+ }
3696
+ const relationships = await this.db.select().from(sessionMcpServers).where((0, import_drizzle_orm10.and)(...conditions)).all();
3697
+ const servers = [];
3698
+ for (const rel of relationships) {
3699
+ const server = await this.mcpServerRepo.findById(rel.mcp_server_id);
3700
+ if (server) {
3701
+ servers.push(server);
3702
+ }
3703
+ }
3704
+ return servers;
3705
+ } catch (error) {
3706
+ throw new RepositoryError(
3707
+ `Failed to list MCP servers for session: ${error instanceof Error ? error.message : String(error)}`,
3708
+ error
3709
+ );
3710
+ }
3711
+ }
3712
+ /**
3713
+ * Set MCP servers for a session (bulk operation)
3714
+ * Replaces existing relationships with new ones
3715
+ */
3716
+ async setServers(sessionId, serverIds) {
3717
+ try {
3718
+ const session = await this.sessionRepo.findById(sessionId);
3719
+ if (!session) {
3720
+ throw new EntityNotFoundError("Session", sessionId);
3721
+ }
3722
+ await this.db.delete(sessionMcpServers).where((0, import_drizzle_orm10.eq)(sessionMcpServers.session_id, sessionId));
3723
+ if (serverIds.length > 0) {
3724
+ const inserts = serverIds.map((serverId) => ({
3725
+ session_id: sessionId,
3726
+ mcp_server_id: serverId,
3727
+ enabled: true,
3728
+ added_at: /* @__PURE__ */ new Date()
3729
+ }));
3730
+ await this.db.insert(sessionMcpServers).values(inserts);
3731
+ }
3732
+ } catch (error) {
3733
+ if (error instanceof EntityNotFoundError) throw error;
3734
+ throw new RepositoryError(
3735
+ `Failed to set MCP servers for session: ${error instanceof Error ? error.message : String(error)}`,
3736
+ error
3737
+ );
3738
+ }
3739
+ }
3740
+ /**
3741
+ * Get relationship details
3742
+ */
3743
+ async getRelationship(sessionId, serverId) {
3744
+ try {
3745
+ const row = await this.db.select().from(sessionMcpServers).where(
3746
+ (0, import_drizzle_orm10.and)(
3747
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.session_id, sessionId),
3748
+ (0, import_drizzle_orm10.eq)(sessionMcpServers.mcp_server_id, serverId)
3749
+ )
3750
+ ).get();
3751
+ if (!row) {
3752
+ return null;
3753
+ }
3754
+ return {
3755
+ session_id: row.session_id,
3756
+ mcp_server_id: row.mcp_server_id,
3757
+ enabled: Boolean(row.enabled),
3758
+ added_at: new Date(row.added_at)
3759
+ };
3760
+ } catch (error) {
3761
+ throw new RepositoryError(
3762
+ `Failed to get relationship: ${error instanceof Error ? error.message : String(error)}`,
3763
+ error
3764
+ );
3765
+ }
3766
+ }
3767
+ /**
3768
+ * Count MCP servers for a session
3769
+ */
3770
+ async count(sessionId, enabledOnly = false) {
3771
+ try {
3772
+ const servers = await this.listServers(sessionId, enabledOnly);
3773
+ return servers.length;
3774
+ } catch (error) {
3775
+ throw new RepositoryError(
3776
+ `Failed to count MCP servers: ${error instanceof Error ? error.message : String(error)}`,
3777
+ error
3778
+ );
3779
+ }
3780
+ }
3781
+ };
3782
+
3783
+ // src/db/repositories/tasks.ts
3784
+ var import_drizzle_orm11 = require("drizzle-orm");
3785
+ init_ids();
3786
+ var TaskRepository = class {
3787
+ constructor(db) {
3788
+ this.db = db;
3789
+ }
3790
+ /**
3791
+ * Convert database row to Task type
3792
+ */
3793
+ rowToTask(row) {
3794
+ return {
3795
+ task_id: row.task_id,
3796
+ session_id: row.session_id,
3797
+ status: row.status,
3798
+ created_at: new Date(row.created_at).toISOString(),
3799
+ completed_at: row.completed_at ? new Date(row.completed_at).toISOString() : void 0,
3800
+ created_by: row.created_by,
3801
+ ...row.data
3802
+ };
3803
+ }
3804
+ /**
3805
+ * Convert Task to database insert format
3806
+ */
3807
+ taskToInsert(task) {
3808
+ const now = Date.now();
3809
+ const taskId = task.task_id ?? generateId();
3810
+ if (!task.session_id) {
3811
+ throw new RepositoryError("session_id is required when creating a task");
3812
+ }
3813
+ const git_state = task.git_state ?? {
3814
+ ref_at_start: "unknown",
3815
+ sha_at_start: "unknown"
3816
+ };
3817
+ return {
3818
+ task_id: taskId,
3819
+ session_id: task.session_id,
3820
+ created_at: new Date(now),
3821
+ // Always use server timestamp, ignore client-provided value
3822
+ completed_at: task.completed_at ? new Date(task.completed_at) : void 0,
3823
+ status: task.status ?? TaskStatus.CREATED,
3824
+ created_by: task.created_by ?? "anonymous",
3825
+ data: {
3826
+ description: task.description ?? "",
3827
+ full_prompt: task.full_prompt ?? task.description ?? "",
3828
+ message_range: task.message_range ?? {
3829
+ start_index: 0,
3830
+ end_index: 0,
3831
+ start_timestamp: new Date(now).toISOString()
3832
+ },
3833
+ git_state,
3834
+ model: task.model ?? "claude-sonnet-4-5",
3835
+ tool_use_count: task.tool_use_count ?? 0,
3836
+ usage: task.usage,
3837
+ // Token usage and cost tracking
3838
+ duration_ms: task.duration_ms,
3839
+ // Task execution duration
3840
+ agent_session_id: task.agent_session_id,
3841
+ // SDK session ID
3842
+ context_window: task.context_window,
3843
+ // Context window usage
3844
+ context_window_limit: task.context_window_limit,
3845
+ // Max context window
3846
+ report: task.report,
3847
+ permission_request: task.permission_request
3848
+ // Permission state for UI approval flow
3849
+ }
3850
+ };
3851
+ }
3852
+ /**
3853
+ * Resolve short ID to full ID
3854
+ */
3855
+ async resolveId(id) {
3856
+ if (id.length === 36 && id.includes("-")) {
3857
+ return id;
3858
+ }
3859
+ const normalized = id.replace(/-/g, "").toLowerCase();
3860
+ const pattern = `${normalized}%`;
3861
+ const results = await this.db.select({ task_id: tasks.task_id }).from(tasks).where((0, import_drizzle_orm11.like)(tasks.task_id, pattern)).all();
3862
+ if (results.length === 0) {
3863
+ throw new EntityNotFoundError("Task", id);
3864
+ }
3865
+ if (results.length > 1) {
3866
+ throw new AmbiguousIdError(
3867
+ "Task",
3868
+ id,
3869
+ results.map((r) => formatShortId(r.task_id))
3870
+ );
3871
+ }
3872
+ return results[0].task_id;
3873
+ }
3874
+ /**
3875
+ * Create a new task
3876
+ */
3877
+ async create(data) {
3878
+ try {
3879
+ const insert = this.taskToInsert(data);
3880
+ await this.db.insert(tasks).values(insert);
3881
+ const row = await this.db.select().from(tasks).where((0, import_drizzle_orm11.eq)(tasks.task_id, insert.task_id)).get();
3882
+ if (!row) {
3883
+ throw new RepositoryError("Failed to retrieve created task");
3884
+ }
3885
+ return this.rowToTask(row);
3886
+ } catch (error) {
3887
+ if (error instanceof RepositoryError) throw error;
3888
+ throw new RepositoryError(
3889
+ `Failed to create task: ${error instanceof Error ? error.message : String(error)}`,
3890
+ error
3891
+ );
3892
+ }
3893
+ }
3894
+ /**
3895
+ * Bulk create multiple tasks (for imports)
3896
+ */
3897
+ async createMany(taskList) {
3898
+ try {
3899
+ const inserts = taskList.map((task) => this.taskToInsert(task));
3900
+ await this.db.insert(tasks).values(inserts);
3901
+ const taskIds = inserts.map((t) => t.task_id);
3902
+ const rows = await this.db.select().from(tasks).where(import_drizzle_orm11.sql`${tasks.task_id} IN ${import_drizzle_orm11.sql.raw(`(${taskIds.map((id) => `'${id}'`).join(",")})`)}`);
3903
+ return rows.map((row) => this.rowToTask(row));
3904
+ } catch (error) {
3905
+ throw new RepositoryError(
3906
+ `Failed to bulk create tasks: ${error instanceof Error ? error.message : String(error)}`,
3907
+ error
3908
+ );
3909
+ }
3910
+ }
3911
+ /**
3912
+ * Find task by ID (supports short ID)
3913
+ */
3914
+ async findById(id) {
3915
+ try {
3916
+ const fullId = await this.resolveId(id);
3917
+ const row = await this.db.select().from(tasks).where((0, import_drizzle_orm11.eq)(tasks.task_id, fullId)).get();
3918
+ return row ? this.rowToTask(row) : null;
3919
+ } catch (error) {
3920
+ if (error instanceof EntityNotFoundError) return null;
3921
+ if (error instanceof AmbiguousIdError) throw error;
3922
+ throw new RepositoryError(
3923
+ `Failed to find task: ${error instanceof Error ? error.message : String(error)}`,
3924
+ error
3925
+ );
3926
+ }
3927
+ }
3928
+ /**
3929
+ * Find all tasks
3930
+ */
3931
+ async findAll() {
3932
+ try {
3933
+ const rows = await this.db.select().from(tasks).all();
3934
+ return rows.map((row) => this.rowToTask(row));
3935
+ } catch (error) {
3936
+ throw new RepositoryError(
3937
+ `Failed to find all tasks: ${error instanceof Error ? error.message : String(error)}`,
3938
+ error
3939
+ );
3940
+ }
3941
+ }
3942
+ /**
3943
+ * Find all tasks for a session
3944
+ */
3945
+ async findBySession(sessionId) {
3946
+ try {
3947
+ const rows = await this.db.select().from(tasks).where((0, import_drizzle_orm11.eq)(tasks.session_id, sessionId)).orderBy(tasks.created_at).all();
3948
+ return rows.map((row) => this.rowToTask(row));
3949
+ } catch (error) {
3950
+ throw new RepositoryError(
3951
+ `Failed to find tasks by session: ${error instanceof Error ? error.message : String(error)}`,
3952
+ error
3953
+ );
3954
+ }
3955
+ }
3956
+ /**
3957
+ * Find running tasks across all sessions
3958
+ */
3959
+ async findRunning() {
3960
+ try {
3961
+ const rows = await this.db.select().from(tasks).where((0, import_drizzle_orm11.eq)(tasks.status, TaskStatus.RUNNING)).all();
3962
+ return rows.map((row) => this.rowToTask(row));
3963
+ } catch (error) {
3964
+ throw new RepositoryError(
3965
+ `Failed to find running tasks: ${error instanceof Error ? error.message : String(error)}`,
3966
+ error
3967
+ );
3968
+ }
3969
+ }
3970
+ /**
3971
+ * Find tasks by status
3972
+ */
3973
+ async findByStatus(status) {
3974
+ try {
3975
+ const rows = await this.db.select().from(tasks).where((0, import_drizzle_orm11.eq)(tasks.status, status)).all();
3976
+ return rows.map((row) => this.rowToTask(row));
3977
+ } catch (error) {
3978
+ throw new RepositoryError(
3979
+ `Failed to find tasks by status: ${error instanceof Error ? error.message : String(error)}`,
3980
+ error
3981
+ );
3982
+ }
3983
+ }
3984
+ /**
3985
+ * Update task by ID
3986
+ */
3987
+ async update(id, updates) {
3988
+ try {
3989
+ const fullId = await this.resolveId(id);
3990
+ const current = await this.findById(fullId);
3991
+ if (!current) {
3992
+ throw new EntityNotFoundError("Task", id);
3993
+ }
3994
+ const merged = { ...current, ...updates };
3995
+ const insert = this.taskToInsert(merged);
3996
+ await this.db.update(tasks).set({
3997
+ status: insert.status,
3998
+ completed_at: insert.completed_at,
3999
+ data: insert.data
4000
+ }).where((0, import_drizzle_orm11.eq)(tasks.task_id, fullId));
4001
+ const updated = await this.findById(fullId);
4002
+ if (!updated) {
4003
+ throw new RepositoryError("Failed to retrieve updated task");
4004
+ }
4005
+ return updated;
4006
+ } catch (error) {
4007
+ if (error instanceof RepositoryError) throw error;
4008
+ if (error instanceof EntityNotFoundError) throw error;
4009
+ throw new RepositoryError(
4010
+ `Failed to update task: ${error instanceof Error ? error.message : String(error)}`,
4011
+ error
4012
+ );
4013
+ }
4014
+ }
4015
+ /**
4016
+ * Delete task by ID
4017
+ */
4018
+ async delete(id) {
4019
+ try {
4020
+ const fullId = await this.resolveId(id);
4021
+ const result = await this.db.delete(tasks).where((0, import_drizzle_orm11.eq)(tasks.task_id, fullId)).run();
4022
+ if (result.rowsAffected === 0) {
4023
+ throw new EntityNotFoundError("Task", id);
4024
+ }
4025
+ } catch (error) {
4026
+ if (error instanceof EntityNotFoundError) throw error;
4027
+ throw new RepositoryError(
4028
+ `Failed to delete task: ${error instanceof Error ? error.message : String(error)}`,
4029
+ error
4030
+ );
4031
+ }
4032
+ }
4033
+ /**
4034
+ * Count tasks for a session
4035
+ */
4036
+ async countBySession(sessionId) {
4037
+ try {
4038
+ const result = await this.db.select({ count: import_drizzle_orm11.sql`count(*)` }).from(tasks).where((0, import_drizzle_orm11.eq)(tasks.session_id, sessionId)).get();
4039
+ return result?.count ?? 0;
4040
+ } catch (error) {
4041
+ throw new RepositoryError(
4042
+ `Failed to count tasks: ${error instanceof Error ? error.message : String(error)}`,
4043
+ error
4044
+ );
4045
+ }
4046
+ }
4047
+ };
4048
+
4049
+ // src/db/repositories/worktrees.ts
4050
+ var import_drizzle_orm12 = require("drizzle-orm");
4051
+ init_ids();
4052
+ var WorktreeRepository = class {
4053
+ constructor(db) {
4054
+ this.db = db;
4055
+ }
4056
+ /**
4057
+ * Convert database row to Worktree type
4058
+ */
4059
+ rowToWorktree(row) {
4060
+ return {
4061
+ worktree_id: row.worktree_id,
4062
+ repo_id: row.repo_id,
4063
+ created_at: new Date(row.created_at).toISOString(),
4064
+ updated_at: row.updated_at ? new Date(row.updated_at).toISOString() : new Date(row.created_at).toISOString(),
4065
+ created_by: row.created_by,
4066
+ name: row.name,
4067
+ ref: row.ref,
4068
+ worktree_unique_id: row.worktree_unique_id,
4069
+ board_id: row.board_id ?? void 0,
4070
+ // Top-level column
4071
+ ...row.data,
4072
+ sessions: row.data.sessions || []
4073
+ };
4074
+ }
4075
+ /**
4076
+ * Convert Worktree to database insert format
4077
+ */
4078
+ worktreeToInsert(worktree) {
4079
+ const now = Date.now();
4080
+ const worktreeId = worktree.worktree_id ?? generateId();
4081
+ return {
4082
+ worktree_id: worktreeId,
4083
+ repo_id: worktree.repo_id,
4084
+ created_at: worktree.created_at ? new Date(worktree.created_at) : new Date(now),
4085
+ updated_at: new Date(now),
4086
+ created_by: worktree.created_by ?? "anonymous",
4087
+ name: worktree.name,
4088
+ ref: worktree.ref,
4089
+ worktree_unique_id: worktree.worktree_unique_id,
4090
+ // Required field
4091
+ // Explicitly convert undefined to null for Drizzle (undefined values are ignored in set())
4092
+ board_id: worktree.board_id === void 0 ? null : worktree.board_id || null,
4093
+ data: {
4094
+ path: worktree.path,
4095
+ base_ref: worktree.base_ref,
4096
+ base_sha: worktree.base_sha,
4097
+ last_commit_sha: worktree.last_commit_sha,
4098
+ tracking_branch: worktree.tracking_branch,
4099
+ new_branch: worktree.new_branch ?? false,
4100
+ issue_url: worktree.issue_url,
4101
+ pull_request_url: worktree.pull_request_url,
4102
+ notes: worktree.notes,
4103
+ environment_instance: worktree.environment_instance,
4104
+ sessions: worktree.sessions || [],
4105
+ last_used: worktree.last_used ?? new Date(now).toISOString(),
4106
+ custom_context: worktree.custom_context
4107
+ }
4108
+ };
4109
+ }
4110
+ /**
4111
+ * Create a new worktree
4112
+ */
4113
+ async create(worktree) {
4114
+ const insert = this.worktreeToInsert(worktree);
4115
+ const [row] = await this.db.insert(worktrees).values(insert).returning();
4116
+ return this.rowToWorktree(row);
4117
+ }
4118
+ /**
4119
+ * Find worktree by exact ID or short ID prefix
4120
+ */
4121
+ async findById(id) {
4122
+ if (id.length === 36 && id.includes("-")) {
4123
+ const [row] = await this.db.select().from(worktrees).where((0, import_drizzle_orm12.eq)(worktrees.worktree_id, id)).limit(1);
4124
+ return row ? this.rowToWorktree(row) : null;
4125
+ }
4126
+ const prefix = id.replace(/-/g, "").toLowerCase();
4127
+ const matches = await this.db.select().from(worktrees).where((0, import_drizzle_orm12.like)(worktrees.worktree_id, `${prefix}%`)).limit(2);
4128
+ if (matches.length === 0) return null;
4129
+ if (matches.length > 1) {
4130
+ throw new AmbiguousIdError(
4131
+ "Worktree",
4132
+ prefix,
4133
+ matches.map((m) => formatShortId(m.worktree_id))
4134
+ );
4135
+ }
4136
+ return this.rowToWorktree(matches[0]);
4137
+ }
4138
+ /**
4139
+ * Find all worktrees (with optional filters)
4140
+ */
4141
+ async findAll(filter) {
4142
+ if (filter?.repo_id) {
4143
+ const rows2 = await this.db.select().from(worktrees).where((0, import_drizzle_orm12.eq)(worktrees.repo_id, filter.repo_id));
4144
+ return rows2.map((row) => this.rowToWorktree(row));
4145
+ }
4146
+ const rows = await this.db.select().from(worktrees);
4147
+ return rows.map((row) => this.rowToWorktree(row));
4148
+ }
4149
+ /**
4150
+ * Update worktree by ID
4151
+ */
4152
+ async update(id, updates) {
4153
+ const existing = await this.findById(id);
4154
+ if (!existing) {
4155
+ throw new EntityNotFoundError("Worktree", id);
4156
+ }
4157
+ const merged = {
4158
+ ...existing,
4159
+ ...updates,
4160
+ worktree_id: existing.worktree_id,
4161
+ repo_id: existing.repo_id,
4162
+ created_at: existing.created_at,
4163
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
4164
+ };
4165
+ const insert = this.worktreeToInsert(merged);
4166
+ const [row] = await this.db.update(worktrees).set(insert).where((0, import_drizzle_orm12.eq)(worktrees.worktree_id, existing.worktree_id)).returning();
4167
+ return this.rowToWorktree(row);
4168
+ }
4169
+ /**
4170
+ * Delete worktree by ID
4171
+ */
4172
+ async delete(id) {
4173
+ const existing = await this.findById(id);
4174
+ if (!existing) {
4175
+ throw new EntityNotFoundError("Worktree", id);
4176
+ }
4177
+ await this.db.delete(worktrees).where((0, import_drizzle_orm12.eq)(worktrees.worktree_id, existing.worktree_id));
4178
+ }
4179
+ /**
4180
+ * Find worktree by repo_id and name
4181
+ */
4182
+ async findByRepoAndName(repoId, name) {
4183
+ const [row] = await this.db.select().from(worktrees).where(import_drizzle_orm12.sql`${worktrees.repo_id} = ${repoId} AND ${worktrees.name} = ${name}`).limit(1);
4184
+ return row ? this.rowToWorktree(row) : null;
4185
+ }
4186
+ /**
4187
+ * Add session to worktree's sessions array
4188
+ */
4189
+ async addSession(worktreeId, sessionId) {
4190
+ const worktree = await this.findById(worktreeId);
4191
+ if (!worktree) {
4192
+ throw new EntityNotFoundError("Worktree", worktreeId);
4193
+ }
4194
+ const sessions2 = worktree.sessions || [];
4195
+ if (!sessions2.includes(sessionId)) {
4196
+ sessions2.push(sessionId);
4197
+ }
4198
+ return this.update(worktreeId, {
4199
+ sessions: sessions2,
4200
+ last_used: (/* @__PURE__ */ new Date()).toISOString()
4201
+ });
4202
+ }
4203
+ /**
4204
+ * Remove session from worktree's sessions array
4205
+ */
4206
+ async removeSession(worktreeId, sessionId) {
4207
+ const worktree = await this.findById(worktreeId);
4208
+ if (!worktree) {
4209
+ throw new EntityNotFoundError("Worktree", worktreeId);
4210
+ }
4211
+ const sessions2 = (worktree.sessions || []).filter((id) => id !== sessionId);
4212
+ return this.update(worktreeId, { sessions: sessions2 });
4213
+ }
4214
+ };
4215
+
4216
+ // src/db/user-utils.ts
4217
+ var import_bcryptjs = __toESM(require("bcryptjs"), 1);
4218
+ var import_drizzle_orm13 = require("drizzle-orm");
4219
+ init_ids();
4220
+ async function createUser(db, data) {
4221
+ const existing = await db.select().from(users).where((0, import_drizzle_orm13.eq)(users.email, data.email)).get();
4222
+ if (existing) {
4223
+ throw new Error(`User with email ${data.email} already exists`);
4224
+ }
4225
+ const hashedPassword = await import_bcryptjs.default.hash(data.password, 10);
4226
+ const now = /* @__PURE__ */ new Date();
4227
+ const user_id = generateId();
4228
+ const role = data.role || "member";
4229
+ const defaultEmoji = role === "admin" ? "\u2B50" : "\u{1F464}";
4230
+ const row = await db.insert(users).values({
4231
+ user_id,
4232
+ email: data.email,
4233
+ password: hashedPassword,
4234
+ name: data.name,
4235
+ emoji: defaultEmoji,
4236
+ role,
4237
+ created_at: now,
4238
+ updated_at: now,
4239
+ data: {
4240
+ preferences: {}
4241
+ }
4242
+ }).returning().get();
4243
+ const userData = row.data;
4244
+ return {
4245
+ user_id: row.user_id,
4246
+ email: row.email,
4247
+ name: row.name ?? void 0,
4248
+ role: row.role,
4249
+ avatar: userData.avatar,
4250
+ preferences: userData.preferences,
4251
+ onboarding_completed: !!row.onboarding_completed,
4252
+ created_at: row.created_at,
4253
+ updated_at: row.updated_at ?? void 0
4254
+ };
4255
+ }
4256
+ async function userExists(db, email) {
4257
+ const existing = await db.select().from(users).where((0, import_drizzle_orm13.eq)(users.email, email)).get();
4258
+ return !!existing;
4259
+ }
4260
+ async function getUserByEmail(db, email) {
4261
+ const row = await db.select().from(users).where((0, import_drizzle_orm13.eq)(users.email, email)).get();
4262
+ if (!row) {
4263
+ return null;
4264
+ }
4265
+ const userData = row.data;
4266
+ return {
4267
+ user_id: row.user_id,
4268
+ email: row.email,
4269
+ name: row.name ?? void 0,
4270
+ role: row.role,
4271
+ avatar: userData.avatar,
4272
+ preferences: userData.preferences,
4273
+ onboarding_completed: !!row.onboarding_completed,
4274
+ created_at: row.created_at,
4275
+ updated_at: row.updated_at ?? void 0
4276
+ };
4277
+ }
4278
+ var DEFAULT_ADMIN_USER = {
4279
+ email: "admin@agor.live",
4280
+ password: "admin",
4281
+ name: "Admin",
4282
+ role: "admin"
4283
+ };
4284
+ async function createDefaultAdminUser(db) {
4285
+ const existing = await getUserByEmail(db, DEFAULT_ADMIN_USER.email);
4286
+ if (existing) {
4287
+ throw new Error(`Admin user already exists (email: ${DEFAULT_ADMIN_USER.email})`);
4288
+ }
4289
+ return createUser(db, DEFAULT_ADMIN_USER);
4290
+ }
4291
+
4292
+ // src/db/index.ts
4293
+ var compare = import_bcryptjs2.default.compare;
4294
+ var hash = import_bcryptjs2.default.hash;
4295
+
4296
+ // src/git/index.ts
4297
+ var import_node_fs = require("fs");
4298
+ var import_promises2 = require("fs/promises");
4299
+ var import_node_os2 = require("os");
4300
+ var import_node_path2 = require("path");
4301
+ var import_simple_git = require("simple-git");
4302
+ function getGitBinary() {
4303
+ const commonPaths = [
4304
+ "/opt/homebrew/bin/git",
4305
+ // Homebrew on Apple Silicon
4306
+ "/usr/local/bin/git",
4307
+ // Homebrew on Intel
4308
+ "/usr/bin/git"
4309
+ // System git (Docker and Linux)
4310
+ ];
4311
+ for (const path2 of commonPaths) {
4312
+ if ((0, import_node_fs.existsSync)(path2)) {
4313
+ return path2;
4314
+ }
4315
+ }
4316
+ return void 0;
4317
+ }
4318
+ function createGit(baseDir) {
4319
+ const gitBinary = getGitBinary();
4320
+ const config = [
4321
+ "core.sshCommand=ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
4322
+ ];
4323
+ return (0, import_simple_git.simpleGit)({
4324
+ baseDir,
4325
+ binary: gitBinary,
4326
+ config
4327
+ });
4328
+ }
4329
+ function getReposDir() {
4330
+ return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".agor", "repos");
4331
+ }
4332
+ function extractRepoName(url) {
4333
+ const match = url.match(/\/([^/]+?)(?:\.git)?$/);
4334
+ if (!match) {
4335
+ throw new Error(`Could not extract repo name from URL: ${url}`);
4336
+ }
4337
+ return match[1];
4338
+ }
4339
+ async function cloneRepo(options) {
4340
+ const repoName = extractRepoName(options.url);
4341
+ const reposDir = getReposDir();
4342
+ const targetPath = options.targetDir || (0, import_node_path2.join)(reposDir, repoName);
4343
+ await (0, import_promises2.mkdir)(reposDir, { recursive: true });
4344
+ if ((0, import_node_fs.existsSync)(targetPath)) {
4345
+ const isValid = await isGitRepo(targetPath);
4346
+ if (isValid) {
4347
+ console.log(`Repository already exists at ${targetPath}, using existing clone`);
4348
+ const defaultBranch2 = await getDefaultBranch(targetPath);
4349
+ return {
4350
+ path: targetPath,
4351
+ repoName,
4352
+ defaultBranch: defaultBranch2
4353
+ };
4354
+ } else {
4355
+ throw new Error(
4356
+ `Directory exists but is not a valid git repository: ${targetPath}
4357
+ Please delete this directory manually and try again.`
4358
+ );
4359
+ }
4360
+ }
4361
+ const git = createGit();
4362
+ if (options.onProgress) {
4363
+ git.outputHandler((_command, _stdout, _stderr) => {
4364
+ });
4365
+ }
4366
+ await git.clone(options.url, targetPath, options.bare ? ["--bare"] : []);
4367
+ const defaultBranch = await getDefaultBranch(targetPath);
4368
+ return {
4369
+ path: targetPath,
4370
+ repoName,
4371
+ defaultBranch
4372
+ };
4373
+ }
4374
+ async function isGitRepo(path2) {
4375
+ try {
4376
+ const git = createGit(path2);
4377
+ await git.status();
4378
+ return true;
4379
+ } catch {
4380
+ return false;
4381
+ }
4382
+ }
4383
+ async function getCurrentBranch(repoPath) {
4384
+ const git = createGit(repoPath);
4385
+ const status = await git.status();
4386
+ return status.current || "";
4387
+ }
4388
+ async function getDefaultBranch(repoPath, remote = "origin") {
4389
+ const git = createGit(repoPath);
4390
+ try {
4391
+ const result = await git.raw(["symbolic-ref", `refs/remotes/${remote}/HEAD`]);
4392
+ const match = result.trim().match(/refs\/remotes\/[^/]+\/(.+)/);
4393
+ if (match && match[1]) {
4394
+ return match[1];
4395
+ }
4396
+ } catch {
4397
+ }
4398
+ try {
4399
+ const branches = await git.branch();
4400
+ return branches.current || "main";
4401
+ } catch {
4402
+ return "main";
4403
+ }
4404
+ }
4405
+ async function getCurrentSha(repoPath) {
4406
+ const git = createGit(repoPath);
4407
+ const log = await git.log({ maxCount: 1 });
4408
+ return log.latest?.hash || "";
4409
+ }
4410
+ async function isClean(repoPath) {
4411
+ const git = createGit(repoPath);
4412
+ const status = await git.status();
4413
+ return status.isClean();
4414
+ }
4415
+ async function getRemoteUrl(repoPath, remote = "origin") {
4416
+ const git = createGit(repoPath);
4417
+ const remotes = await git.getRemotes(true);
4418
+ const remoteObj = remotes.find((r) => r.name === remote);
4419
+ return remoteObj?.refs.fetch || "";
4420
+ }
4421
+ function getWorktreesDir() {
4422
+ return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".agor", "worktrees");
4423
+ }
4424
+ function getWorktreePath(repoSlug, worktreeName) {
4425
+ return (0, import_node_path2.join)(getWorktreesDir(), repoSlug, worktreeName);
4426
+ }
4427
+ async function createWorktree(repoPath, worktreePath, ref, createBranch = false, pullLatest = true, sourceBranch) {
4428
+ const git = createGit(repoPath);
4429
+ let fetchSucceeded = false;
4430
+ if (pullLatest) {
4431
+ try {
4432
+ await git.fetch(["origin"]);
4433
+ fetchSucceeded = true;
4434
+ console.log("\u2705 Fetched latest from origin");
4435
+ if (!createBranch) {
4436
+ try {
4437
+ const branches = await git.branch();
4438
+ const localBranchExists = branches.all.includes(ref);
4439
+ if (localBranchExists) {
4440
+ const remoteBranches = await git.branch(["-r"]);
4441
+ const remoteBranchExists = remoteBranches.all.includes(`origin/${ref}`);
4442
+ if (remoteBranchExists) {
4443
+ await git.raw(["branch", "-f", ref, `origin/${ref}`]);
4444
+ console.log(`\u2705 Updated local ${ref} to match origin/${ref}`);
4445
+ }
4446
+ }
4447
+ } catch (error) {
4448
+ console.warn(
4449
+ `\u26A0\uFE0F Failed to update local ${ref} branch:`,
4450
+ error instanceof Error ? error.message : String(error)
4451
+ );
4452
+ }
4453
+ }
4454
+ } catch (error) {
4455
+ console.warn(
4456
+ "\u26A0\uFE0F Failed to fetch from origin (will use local refs):",
4457
+ error instanceof Error ? error.message : String(error)
4458
+ );
4459
+ }
4460
+ }
4461
+ const args = [worktreePath];
4462
+ if (createBranch) {
4463
+ args.push("-b", ref);
4464
+ if (sourceBranch) {
4465
+ const baseRef = fetchSucceeded ? `origin/${sourceBranch}` : sourceBranch;
4466
+ args.push(baseRef);
4467
+ }
4468
+ } else {
4469
+ args.push(ref);
4470
+ }
4471
+ await git.raw(["worktree", "add", ...args]);
4472
+ }
4473
+ async function listWorktrees(repoPath) {
4474
+ const git = createGit(repoPath);
4475
+ const output = await git.raw(["worktree", "list", "--porcelain"]);
4476
+ const worktrees2 = [];
4477
+ const lines = output.split("\n");
4478
+ let current = {};
4479
+ for (const line of lines) {
4480
+ if (line.startsWith("worktree ")) {
4481
+ current.path = line.substring(9);
4482
+ current.name = (0, import_node_path2.basename)(current.path);
4483
+ } else if (line.startsWith("HEAD ")) {
4484
+ current.sha = line.substring(5);
4485
+ } else if (line.startsWith("branch ")) {
4486
+ current.ref = line.substring(7).replace("refs/heads/", "");
4487
+ current.detached = false;
4488
+ } else if (line.startsWith("detached")) {
4489
+ current.detached = true;
4490
+ } else if (line === "") {
4491
+ if (current.path && current.sha) {
4492
+ worktrees2.push(current);
4493
+ }
4494
+ current = {};
4495
+ }
4496
+ }
4497
+ if (current.path && current.sha) {
4498
+ worktrees2.push(current);
4499
+ }
4500
+ return worktrees2;
4501
+ }
4502
+ async function removeWorktree(repoPath, worktreeName) {
4503
+ const git = createGit(repoPath);
4504
+ await git.raw(["worktree", "remove", worktreeName]);
4505
+ }
4506
+ async function pruneWorktrees(repoPath) {
4507
+ const git = createGit(repoPath);
4508
+ await git.raw(["worktree", "prune"]);
4509
+ }
4510
+ async function hasRemoteBranch(repoPath, branchName, remote = "origin") {
4511
+ const git = createGit(repoPath);
4512
+ const branches = await git.branch(["-r"]);
4513
+ return branches.all.includes(`${remote}/${branchName}`);
4514
+ }
4515
+ async function getRemoteBranches(repoPath, remote = "origin") {
4516
+ const git = createGit(repoPath);
4517
+ const branches = await git.branch(["-r"]);
4518
+ return branches.all.filter((b) => b.startsWith(`${remote}/`)).map((b) => b.replace(`${remote}/`, ""));
4519
+ }
4520
+ async function getGitState(repoPath) {
4521
+ try {
4522
+ if (!await isGitRepo(repoPath)) {
4523
+ return "unknown";
4524
+ }
4525
+ const sha = await getCurrentSha(repoPath);
4526
+ if (!sha) {
4527
+ return "unknown";
4528
+ }
4529
+ const clean = await isClean(repoPath);
4530
+ return clean ? sha : `${sha}-dirty`;
4531
+ } catch (error) {
4532
+ console.warn(`Failed to get git state for ${repoPath}:`, error);
4533
+ return "unknown";
4534
+ }
4535
+ }
4536
+ // Annotate the CommonJS export names for ESM import in node:
4537
+ 0 && (module.exports = {
4538
+ AmbiguousIdError,
4539
+ BoardCommentsRepository,
4540
+ BoardObjectRepository,
4541
+ BoardRepository,
4542
+ CommentAttachmentType,
4543
+ DATABASE,
4544
+ DEFAULT_ADMIN_USER,
4545
+ DEFAULT_DB_PATH,
4546
+ DatabaseConnectionError,
4547
+ ENVIRONMENT,
4548
+ EntityNotFoundError,
4549
+ GIT,
4550
+ IdResolutionError,
4551
+ MCPServerRepository,
4552
+ MessageRole,
4553
+ MessagesRepository,
4554
+ MigrationError,
4555
+ PermissionScope,
4556
+ PermissionStatus,
4557
+ RepoRepository,
4558
+ RepositoryError,
4559
+ SESSION,
4560
+ SessionMCPServerRepository,
4561
+ SessionRepository,
4562
+ SessionStatus,
4563
+ TaskRepository,
4564
+ TaskStatus,
4565
+ WEBSOCKET,
4566
+ WorktreeRepository,
4567
+ and,
4568
+ boardComments,
4569
+ boardObjects,
4570
+ boards,
4571
+ clearContext,
4572
+ cloneRepo,
4573
+ compare,
4574
+ createClient,
4575
+ createDatabase,
4576
+ createDefaultAdminUser,
4577
+ createLocalDatabase,
4578
+ createUser,
4579
+ createWorktree,
4580
+ desc,
4581
+ eq,
4582
+ extractRepoName,
4583
+ extractSlugFromUrl,
4584
+ formatRepoReference,
4585
+ formatShortId,
4586
+ generateId,
4587
+ getAgorHome,
4588
+ getAllContext,
4589
+ getCommentAttachmentType,
4590
+ getConfigPath,
4591
+ getConfigValue,
4592
+ getContext,
4593
+ getCurrentBranch,
4594
+ getCurrentSha,
4595
+ getDaemonUrl,
4596
+ getDefaultBranch,
4597
+ getDefaultConfig,
4598
+ getDefaultPermissionMode,
4599
+ getDefaultRepoReference,
4600
+ getEffectiveConfig,
4601
+ getGitState,
4602
+ getGroupedRepoReferenceOptions,
4603
+ getRemoteBranches,
4604
+ getRemoteUrl,
4605
+ getRepoReferenceOptions,
4606
+ getReposDir,
4607
+ getUserByEmail,
4608
+ getWorktreePath,
4609
+ getWorktreesDir,
4610
+ groupReactions,
4611
+ hasRemoteBranch,
4612
+ hash,
4613
+ inArray,
4614
+ initConfig,
4615
+ initializeDatabase,
4616
+ isClean,
4617
+ isDaemonRunning,
4618
+ isDefined,
4619
+ isGitRepo,
4620
+ isNonEmptyString,
4621
+ isReply,
4622
+ isResolvable,
4623
+ isThreadRoot,
4624
+ isValidSlug,
4625
+ like,
4626
+ listWorktrees,
4627
+ loadConfig,
4628
+ mcpServers,
4629
+ messages,
4630
+ or,
4631
+ parseRepoReference,
4632
+ pruneWorktrees,
4633
+ removeWorktree,
4634
+ repos,
4635
+ resolveRepoReference,
4636
+ resolveShortId,
4637
+ resolveValue,
4638
+ resolveValues,
4639
+ runMigrations,
4640
+ saveConfig,
4641
+ seedInitialData,
4642
+ sessionMcpServers,
4643
+ sessions,
4644
+ setConfigValue,
4645
+ setContext,
4646
+ sql,
4647
+ tasks,
4648
+ unsetConfigValue,
4649
+ unsetContext,
4650
+ userExists,
4651
+ users,
4652
+ worktrees
4653
+ });