@agentuity/runtime 2.0.10 → 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/dist/index.d.ts +37 -65
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +59 -61
  4. package/dist/index.js.map +1 -1
  5. package/package.json +9 -38
  6. package/src/index.ts +58 -259
  7. package/AGENTS.md +0 -116
  8. package/dist/_config.d.ts +0 -100
  9. package/dist/_config.d.ts.map +0 -1
  10. package/dist/_config.js +0 -147
  11. package/dist/_config.js.map +0 -1
  12. package/dist/_context.d.ts +0 -80
  13. package/dist/_context.d.ts.map +0 -1
  14. package/dist/_context.js +0 -160
  15. package/dist/_context.js.map +0 -1
  16. package/dist/_events.d.ts +0 -64
  17. package/dist/_events.d.ts.map +0 -1
  18. package/dist/_events.js +0 -92
  19. package/dist/_events.js.map +0 -1
  20. package/dist/_globals.d.ts +0 -58
  21. package/dist/_globals.d.ts.map +0 -1
  22. package/dist/_globals.js +0 -71
  23. package/dist/_globals.js.map +0 -1
  24. package/dist/_idle.d.ts +0 -7
  25. package/dist/_idle.d.ts.map +0 -1
  26. package/dist/_idle.js +0 -10
  27. package/dist/_idle.js.map +0 -1
  28. package/dist/_metadata.d.ts +0 -117
  29. package/dist/_metadata.d.ts.map +0 -1
  30. package/dist/_metadata.js +0 -268
  31. package/dist/_metadata.js.map +0 -1
  32. package/dist/_process-protection.d.ts +0 -27
  33. package/dist/_process-protection.d.ts.map +0 -1
  34. package/dist/_process-protection.js +0 -56
  35. package/dist/_process-protection.js.map +0 -1
  36. package/dist/_server.d.ts +0 -50
  37. package/dist/_server.d.ts.map +0 -1
  38. package/dist/_server.js +0 -89
  39. package/dist/_server.js.map +0 -1
  40. package/dist/_services.d.ts +0 -25
  41. package/dist/_services.d.ts.map +0 -1
  42. package/dist/_services.js +0 -286
  43. package/dist/_services.js.map +0 -1
  44. package/dist/_standalone.d.ts +0 -212
  45. package/dist/_standalone.d.ts.map +0 -1
  46. package/dist/_standalone.js +0 -556
  47. package/dist/_standalone.js.map +0 -1
  48. package/dist/_tokens.d.ts +0 -12
  49. package/dist/_tokens.d.ts.map +0 -1
  50. package/dist/_tokens.js +0 -97
  51. package/dist/_tokens.js.map +0 -1
  52. package/dist/_util.d.ts +0 -16
  53. package/dist/_util.d.ts.map +0 -1
  54. package/dist/_util.js +0 -54
  55. package/dist/_util.js.map +0 -1
  56. package/dist/_validation.d.ts +0 -89
  57. package/dist/_validation.d.ts.map +0 -1
  58. package/dist/_validation.js +0 -29
  59. package/dist/_validation.js.map +0 -1
  60. package/dist/_waituntil.d.ts +0 -32
  61. package/dist/_waituntil.d.ts.map +0 -1
  62. package/dist/_waituntil.js +0 -156
  63. package/dist/_waituntil.js.map +0 -1
  64. package/dist/agent.d.ts +0 -1262
  65. package/dist/agent.d.ts.map +0 -1
  66. package/dist/agent.js +0 -981
  67. package/dist/agent.js.map +0 -1
  68. package/dist/app.d.ts +0 -514
  69. package/dist/app.d.ts.map +0 -1
  70. package/dist/app.js +0 -228
  71. package/dist/app.js.map +0 -1
  72. package/dist/bootstrap.d.ts +0 -44
  73. package/dist/bootstrap.d.ts.map +0 -1
  74. package/dist/bootstrap.js +0 -259
  75. package/dist/bootstrap.js.map +0 -1
  76. package/dist/bun-s3-patch.d.ts +0 -37
  77. package/dist/bun-s3-patch.d.ts.map +0 -1
  78. package/dist/bun-s3-patch.js +0 -142
  79. package/dist/bun-s3-patch.js.map +0 -1
  80. package/dist/cors.d.ts +0 -42
  81. package/dist/cors.d.ts.map +0 -1
  82. package/dist/cors.js +0 -117
  83. package/dist/cors.js.map +0 -1
  84. package/dist/dev-patches/aisdk.d.ts +0 -17
  85. package/dist/dev-patches/aisdk.d.ts.map +0 -1
  86. package/dist/dev-patches/aisdk.js +0 -160
  87. package/dist/dev-patches/aisdk.js.map +0 -1
  88. package/dist/dev-patches/gateway.d.ts +0 -16
  89. package/dist/dev-patches/gateway.d.ts.map +0 -1
  90. package/dist/dev-patches/gateway.js +0 -54
  91. package/dist/dev-patches/gateway.js.map +0 -1
  92. package/dist/dev-patches/index.d.ts +0 -21
  93. package/dist/dev-patches/index.d.ts.map +0 -1
  94. package/dist/dev-patches/index.js +0 -33
  95. package/dist/dev-patches/index.js.map +0 -1
  96. package/dist/dev-patches/otel-llm.d.ts +0 -12
  97. package/dist/dev-patches/otel-llm.d.ts.map +0 -1
  98. package/dist/dev-patches/otel-llm.js +0 -352
  99. package/dist/dev-patches/otel-llm.js.map +0 -1
  100. package/dist/devmode.d.ts +0 -3
  101. package/dist/devmode.d.ts.map +0 -1
  102. package/dist/devmode.js +0 -167
  103. package/dist/devmode.js.map +0 -1
  104. package/dist/eval.d.ts +0 -91
  105. package/dist/eval.d.ts.map +0 -1
  106. package/dist/eval.js +0 -16
  107. package/dist/eval.js.map +0 -1
  108. package/dist/handlers/_route-meta.d.ts +0 -22
  109. package/dist/handlers/_route-meta.d.ts.map +0 -1
  110. package/dist/handlers/_route-meta.js +0 -25
  111. package/dist/handlers/_route-meta.js.map +0 -1
  112. package/dist/handlers/cron.d.ts +0 -73
  113. package/dist/handlers/cron.d.ts.map +0 -1
  114. package/dist/handlers/cron.js +0 -43
  115. package/dist/handlers/cron.js.map +0 -1
  116. package/dist/handlers/index.d.ts +0 -6
  117. package/dist/handlers/index.d.ts.map +0 -1
  118. package/dist/handlers/index.js +0 -6
  119. package/dist/handlers/index.js.map +0 -1
  120. package/dist/handlers/sse.d.ts +0 -163
  121. package/dist/handlers/sse.d.ts.map +0 -1
  122. package/dist/handlers/sse.js +0 -175
  123. package/dist/handlers/sse.js.map +0 -1
  124. package/dist/handlers/stream.d.ts +0 -52
  125. package/dist/handlers/stream.d.ts.map +0 -1
  126. package/dist/handlers/stream.js +0 -108
  127. package/dist/handlers/stream.js.map +0 -1
  128. package/dist/handlers/webrtc.d.ts +0 -49
  129. package/dist/handlers/webrtc.d.ts.map +0 -1
  130. package/dist/handlers/webrtc.js +0 -109
  131. package/dist/handlers/webrtc.js.map +0 -1
  132. package/dist/handlers/websocket.d.ts +0 -88
  133. package/dist/handlers/websocket.d.ts.map +0 -1
  134. package/dist/handlers/websocket.js +0 -161
  135. package/dist/handlers/websocket.js.map +0 -1
  136. package/dist/logger/console.d.ts +0 -70
  137. package/dist/logger/console.d.ts.map +0 -1
  138. package/dist/logger/console.js +0 -278
  139. package/dist/logger/console.js.map +0 -1
  140. package/dist/logger/index.d.ts +0 -3
  141. package/dist/logger/index.d.ts.map +0 -1
  142. package/dist/logger/index.js +0 -3
  143. package/dist/logger/index.js.map +0 -1
  144. package/dist/logger/internal.d.ts +0 -79
  145. package/dist/logger/internal.d.ts.map +0 -1
  146. package/dist/logger/internal.js +0 -133
  147. package/dist/logger/internal.js.map +0 -1
  148. package/dist/logger/logger.d.ts +0 -41
  149. package/dist/logger/logger.d.ts.map +0 -1
  150. package/dist/logger/logger.js +0 -2
  151. package/dist/logger/logger.js.map +0 -1
  152. package/dist/logger/user.d.ts +0 -8
  153. package/dist/logger/user.d.ts.map +0 -1
  154. package/dist/logger/user.js +0 -7
  155. package/dist/logger/user.js.map +0 -1
  156. package/dist/logger/util.d.ts +0 -11
  157. package/dist/logger/util.d.ts.map +0 -1
  158. package/dist/logger/util.js +0 -77
  159. package/dist/logger/util.js.map +0 -1
  160. package/dist/middleware.d.ts +0 -105
  161. package/dist/middleware.d.ts.map +0 -1
  162. package/dist/middleware.js +0 -763
  163. package/dist/middleware.js.map +0 -1
  164. package/dist/otel/config.d.ts +0 -19
  165. package/dist/otel/config.d.ts.map +0 -1
  166. package/dist/otel/config.js +0 -26
  167. package/dist/otel/config.js.map +0 -1
  168. package/dist/otel/console.d.ts +0 -33
  169. package/dist/otel/console.d.ts.map +0 -1
  170. package/dist/otel/console.js +0 -86
  171. package/dist/otel/console.js.map +0 -1
  172. package/dist/otel/exporters/index.d.ts +0 -4
  173. package/dist/otel/exporters/index.d.ts.map +0 -1
  174. package/dist/otel/exporters/index.js +0 -4
  175. package/dist/otel/exporters/index.js.map +0 -1
  176. package/dist/otel/exporters/jsonl-log-exporter.d.ts +0 -36
  177. package/dist/otel/exporters/jsonl-log-exporter.d.ts.map +0 -1
  178. package/dist/otel/exporters/jsonl-log-exporter.js +0 -103
  179. package/dist/otel/exporters/jsonl-log-exporter.js.map +0 -1
  180. package/dist/otel/exporters/jsonl-metric-exporter.d.ts +0 -40
  181. package/dist/otel/exporters/jsonl-metric-exporter.d.ts.map +0 -1
  182. package/dist/otel/exporters/jsonl-metric-exporter.js +0 -104
  183. package/dist/otel/exporters/jsonl-metric-exporter.js.map +0 -1
  184. package/dist/otel/exporters/jsonl-trace-exporter.d.ts +0 -36
  185. package/dist/otel/exporters/jsonl-trace-exporter.d.ts.map +0 -1
  186. package/dist/otel/exporters/jsonl-trace-exporter.js +0 -111
  187. package/dist/otel/exporters/jsonl-trace-exporter.js.map +0 -1
  188. package/dist/otel/fetch.d.ts +0 -12
  189. package/dist/otel/fetch.d.ts.map +0 -1
  190. package/dist/otel/fetch.js +0 -82
  191. package/dist/otel/fetch.js.map +0 -1
  192. package/dist/otel/http.d.ts +0 -16
  193. package/dist/otel/http.d.ts.map +0 -1
  194. package/dist/otel/http.js +0 -44
  195. package/dist/otel/http.js.map +0 -1
  196. package/dist/otel/logger.d.ts +0 -37
  197. package/dist/otel/logger.d.ts.map +0 -1
  198. package/dist/otel/logger.js +0 -265
  199. package/dist/otel/logger.js.map +0 -1
  200. package/dist/otel/otel.d.ts +0 -68
  201. package/dist/otel/otel.d.ts.map +0 -1
  202. package/dist/otel/otel.js +0 -245
  203. package/dist/otel/otel.js.map +0 -1
  204. package/dist/otel/tracestate.d.ts +0 -44
  205. package/dist/otel/tracestate.d.ts.map +0 -1
  206. package/dist/otel/tracestate.js +0 -84
  207. package/dist/otel/tracestate.js.map +0 -1
  208. package/dist/router.d.ts +0 -66
  209. package/dist/router.d.ts.map +0 -1
  210. package/dist/router.js +0 -44
  211. package/dist/router.js.map +0 -1
  212. package/dist/services/evalrun/composite.d.ts +0 -21
  213. package/dist/services/evalrun/composite.d.ts.map +0 -1
  214. package/dist/services/evalrun/composite.js +0 -26
  215. package/dist/services/evalrun/composite.js.map +0 -1
  216. package/dist/services/evalrun/http.d.ts +0 -24
  217. package/dist/services/evalrun/http.d.ts.map +0 -1
  218. package/dist/services/evalrun/http.js +0 -115
  219. package/dist/services/evalrun/http.js.map +0 -1
  220. package/dist/services/evalrun/index.d.ts +0 -5
  221. package/dist/services/evalrun/index.d.ts.map +0 -1
  222. package/dist/services/evalrun/index.js +0 -5
  223. package/dist/services/evalrun/index.js.map +0 -1
  224. package/dist/services/evalrun/json.d.ts +0 -21
  225. package/dist/services/evalrun/json.d.ts.map +0 -1
  226. package/dist/services/evalrun/json.js +0 -38
  227. package/dist/services/evalrun/json.js.map +0 -1
  228. package/dist/services/evalrun/local.d.ts +0 -19
  229. package/dist/services/evalrun/local.d.ts.map +0 -1
  230. package/dist/services/evalrun/local.js +0 -22
  231. package/dist/services/evalrun/local.js.map +0 -1
  232. package/dist/services/local/_db.d.ts +0 -4
  233. package/dist/services/local/_db.d.ts.map +0 -1
  234. package/dist/services/local/_db.js +0 -281
  235. package/dist/services/local/_db.js.map +0 -1
  236. package/dist/services/local/_router.d.ts +0 -3
  237. package/dist/services/local/_router.d.ts.map +0 -1
  238. package/dist/services/local/_router.js +0 -28
  239. package/dist/services/local/_router.js.map +0 -1
  240. package/dist/services/local/_util.d.ts +0 -18
  241. package/dist/services/local/_util.d.ts.map +0 -1
  242. package/dist/services/local/_util.js +0 -44
  243. package/dist/services/local/_util.js.map +0 -1
  244. package/dist/services/local/email.d.ts +0 -24
  245. package/dist/services/local/email.d.ts.map +0 -1
  246. package/dist/services/local/email.js +0 -58
  247. package/dist/services/local/email.js.map +0 -1
  248. package/dist/services/local/index.d.ts +0 -10
  249. package/dist/services/local/index.d.ts.map +0 -1
  250. package/dist/services/local/index.js +0 -10
  251. package/dist/services/local/index.js.map +0 -1
  252. package/dist/services/local/keyvalue.d.ts +0 -17
  253. package/dist/services/local/keyvalue.d.ts.map +0 -1
  254. package/dist/services/local/keyvalue.js +0 -133
  255. package/dist/services/local/keyvalue.js.map +0 -1
  256. package/dist/services/local/queue.d.ts +0 -10
  257. package/dist/services/local/queue.d.ts.map +0 -1
  258. package/dist/services/local/queue.js +0 -96
  259. package/dist/services/local/queue.js.map +0 -1
  260. package/dist/services/local/stream.d.ts +0 -12
  261. package/dist/services/local/stream.d.ts.map +0 -1
  262. package/dist/services/local/stream.js +0 -266
  263. package/dist/services/local/stream.js.map +0 -1
  264. package/dist/services/local/task.d.ts +0 -55
  265. package/dist/services/local/task.d.ts.map +0 -1
  266. package/dist/services/local/task.js +0 -1248
  267. package/dist/services/local/task.js.map +0 -1
  268. package/dist/services/local/vector.d.ts +0 -17
  269. package/dist/services/local/vector.d.ts.map +0 -1
  270. package/dist/services/local/vector.js +0 -303
  271. package/dist/services/local/vector.js.map +0 -1
  272. package/dist/services/sandbox/http.d.ts +0 -23
  273. package/dist/services/sandbox/http.d.ts.map +0 -1
  274. package/dist/services/sandbox/http.js +0 -327
  275. package/dist/services/sandbox/http.js.map +0 -1
  276. package/dist/services/sandbox/index.d.ts +0 -2
  277. package/dist/services/sandbox/index.d.ts.map +0 -1
  278. package/dist/services/sandbox/index.js +0 -2
  279. package/dist/services/sandbox/index.js.map +0 -1
  280. package/dist/services/session/composite.d.ts +0 -21
  281. package/dist/services/session/composite.d.ts.map +0 -1
  282. package/dist/services/session/composite.js +0 -26
  283. package/dist/services/session/composite.js.map +0 -1
  284. package/dist/services/session/http.d.ts +0 -34
  285. package/dist/services/session/http.d.ts.map +0 -1
  286. package/dist/services/session/http.js +0 -124
  287. package/dist/services/session/http.js.map +0 -1
  288. package/dist/services/session/index.d.ts +0 -5
  289. package/dist/services/session/index.d.ts.map +0 -1
  290. package/dist/services/session/index.js +0 -5
  291. package/dist/services/session/index.js.map +0 -1
  292. package/dist/services/session/json.d.ts +0 -22
  293. package/dist/services/session/json.d.ts.map +0 -1
  294. package/dist/services/session/json.js +0 -35
  295. package/dist/services/session/json.js.map +0 -1
  296. package/dist/services/session/local.d.ts +0 -19
  297. package/dist/services/session/local.d.ts.map +0 -1
  298. package/dist/services/session/local.js +0 -23
  299. package/dist/services/session/local.js.map +0 -1
  300. package/dist/services/thread/local.d.ts +0 -20
  301. package/dist/services/thread/local.d.ts.map +0 -1
  302. package/dist/services/thread/local.js +0 -158
  303. package/dist/services/thread/local.js.map +0 -1
  304. package/dist/session.d.ts +0 -734
  305. package/dist/session.d.ts.map +0 -1
  306. package/dist/session.js +0 -1140
  307. package/dist/session.js.map +0 -1
  308. package/dist/signature.d.ts +0 -22
  309. package/dist/signature.d.ts.map +0 -1
  310. package/dist/signature.js +0 -63
  311. package/dist/signature.js.map +0 -1
  312. package/dist/validator.d.ts +0 -142
  313. package/dist/validator.d.ts.map +0 -1
  314. package/dist/validator.js +0 -149
  315. package/dist/validator.js.map +0 -1
  316. package/dist/version-check.d.ts +0 -20
  317. package/dist/version-check.d.ts.map +0 -1
  318. package/dist/version-check.js +0 -157
  319. package/dist/version-check.js.map +0 -1
  320. package/dist/web.d.ts +0 -8
  321. package/dist/web.d.ts.map +0 -1
  322. package/dist/web.js +0 -67
  323. package/dist/web.js.map +0 -1
  324. package/dist/webrtc-signaling.d.ts +0 -80
  325. package/dist/webrtc-signaling.d.ts.map +0 -1
  326. package/dist/webrtc-signaling.js +0 -237
  327. package/dist/webrtc-signaling.js.map +0 -1
  328. package/dist/workbench.d.ts +0 -17
  329. package/dist/workbench.d.ts.map +0 -1
  330. package/dist/workbench.js +0 -605
  331. package/dist/workbench.js.map +0 -1
  332. package/src/_config.ts +0 -163
  333. package/src/_context.ts +0 -240
  334. package/src/_events.ts +0 -142
  335. package/src/_globals.ts +0 -92
  336. package/src/_idle.ts +0 -10
  337. package/src/_metadata.ts +0 -407
  338. package/src/_process-protection.ts +0 -71
  339. package/src/_server.ts +0 -109
  340. package/src/_services.ts +0 -379
  341. package/src/_standalone.ts +0 -710
  342. package/src/_tokens.ts +0 -114
  343. package/src/_util.ts +0 -62
  344. package/src/_validation.ts +0 -119
  345. package/src/_waituntil.ts +0 -188
  346. package/src/agent.ts +0 -2739
  347. package/src/app.ts +0 -769
  348. package/src/bootstrap.ts +0 -321
  349. package/src/bun-s3-patch.ts +0 -224
  350. package/src/cors.ts +0 -137
  351. package/src/dev-patches/aisdk.ts +0 -169
  352. package/src/dev-patches/gateway.ts +0 -68
  353. package/src/dev-patches/index.ts +0 -37
  354. package/src/dev-patches/otel-llm.ts +0 -405
  355. package/src/devmode.ts +0 -171
  356. package/src/eval.ts +0 -109
  357. package/src/globals.d.ts +0 -28
  358. package/src/handlers/_route-meta.ts +0 -33
  359. package/src/handlers/cron.ts +0 -141
  360. package/src/handlers/index.ts +0 -18
  361. package/src/handlers/sse.ts +0 -358
  362. package/src/handlers/stream.ts +0 -121
  363. package/src/handlers/webrtc.ts +0 -125
  364. package/src/handlers/websocket.ts +0 -203
  365. package/src/logger/console.ts +0 -323
  366. package/src/logger/index.ts +0 -2
  367. package/src/logger/internal.ts +0 -165
  368. package/src/logger/logger.ts +0 -44
  369. package/src/logger/user.ts +0 -15
  370. package/src/logger/util.ts +0 -80
  371. package/src/middleware.ts +0 -1095
  372. package/src/otel/config.ts +0 -47
  373. package/src/otel/console.ts +0 -91
  374. package/src/otel/exporters/README.md +0 -217
  375. package/src/otel/exporters/index.ts +0 -3
  376. package/src/otel/exporters/jsonl-log-exporter.ts +0 -113
  377. package/src/otel/exporters/jsonl-metric-exporter.ts +0 -120
  378. package/src/otel/exporters/jsonl-trace-exporter.ts +0 -121
  379. package/src/otel/fetch.ts +0 -105
  380. package/src/otel/http.ts +0 -53
  381. package/src/otel/logger.ts +0 -293
  382. package/src/otel/otel.ts +0 -354
  383. package/src/otel/tracestate.ts +0 -108
  384. package/src/router.ts +0 -75
  385. package/src/services/evalrun/composite.ts +0 -34
  386. package/src/services/evalrun/http.ts +0 -167
  387. package/src/services/evalrun/index.ts +0 -4
  388. package/src/services/evalrun/json.ts +0 -46
  389. package/src/services/evalrun/local.ts +0 -28
  390. package/src/services/local/README.md +0 -1576
  391. package/src/services/local/_db.ts +0 -353
  392. package/src/services/local/_router.ts +0 -40
  393. package/src/services/local/_util.ts +0 -55
  394. package/src/services/local/email.ts +0 -91
  395. package/src/services/local/index.ts +0 -9
  396. package/src/services/local/keyvalue.ts +0 -174
  397. package/src/services/local/queue.ts +0 -145
  398. package/src/services/local/stream.ts +0 -358
  399. package/src/services/local/task.ts +0 -1711
  400. package/src/services/local/vector.ts +0 -438
  401. package/src/services/sandbox/http.ts +0 -522
  402. package/src/services/sandbox/index.ts +0 -1
  403. package/src/services/session/composite.ts +0 -33
  404. package/src/services/session/http.ts +0 -167
  405. package/src/services/session/index.ts +0 -4
  406. package/src/services/session/json.ts +0 -42
  407. package/src/services/session/local.ts +0 -33
  408. package/src/services/thread/local.ts +0 -199
  409. package/src/session.ts +0 -1960
  410. package/src/signature.ts +0 -82
  411. package/src/validator.ts +0 -283
  412. package/src/version-check.ts +0 -184
  413. package/src/web.ts +0 -76
  414. package/src/webrtc-signaling.ts +0 -288
  415. package/src/workbench.ts +0 -725
@@ -1,1711 +0,0 @@
1
- import type { Database } from 'bun:sqlite';
2
- import type {
3
- TaskStorage,
4
- Task,
5
- TaskChangelogEntry,
6
- TaskPriority,
7
- TaskStatus,
8
- TaskType,
9
- CreateTaskParams,
10
- UpdateTaskParams,
11
- ListTasksParams,
12
- ListTasksResult,
13
- BatchDeleteTasksParams,
14
- BatchDeleteTasksResult,
15
- BatchUpdateTasksParams,
16
- BatchUpdateTasksResult,
17
- BatchCloseTasksParams,
18
- BatchCloseTasksResult,
19
- TaskChangelogResult,
20
- Comment,
21
- Tag,
22
- ListCommentsResult,
23
- ListTagsResult,
24
- ListUsersResult,
25
- ListProjectsResult,
26
- Attachment,
27
- CreateAttachmentParams,
28
- PresignUploadResponse,
29
- PresignDownloadResponse,
30
- ListAttachmentsResult,
31
- TaskActivityParams,
32
- TaskActivityResult,
33
- UserEntityRef,
34
- EntityRef,
35
- } from '@agentuity/core';
36
- import { StructuredError, normalizeTaskStatus } from '@agentuity/core';
37
- import { now } from './_util';
38
-
39
- const TaskTitleRequiredError = StructuredError(
40
- 'TaskTitleRequiredError',
41
- 'Task title is required and must be a non-empty string'
42
- );
43
-
44
- const TaskNotFoundError = StructuredError('TaskNotFoundError', 'Task not found');
45
-
46
- const TaskAlreadyClosedError = StructuredError('TaskAlreadyClosedError', 'Task is already closed');
47
-
48
- const CommentNotFoundError = StructuredError('CommentNotFoundError', 'Comment not found');
49
-
50
- const TagNotFoundError = StructuredError('TagNotFoundError', 'Tag not found');
51
-
52
- const CommentBodyRequiredError = StructuredError(
53
- 'CommentBodyRequiredError',
54
- 'Comment body is required and must be a non-empty string'
55
- );
56
-
57
- const CommentUserRequiredError = StructuredError(
58
- 'CommentUserRequiredError',
59
- 'Comment user ID is required and must be a non-empty string'
60
- );
61
-
62
- const TagNameRequiredError = StructuredError(
63
- 'TagNameRequiredError',
64
- 'Tag name is required and must be a non-empty string'
65
- );
66
-
67
- const AttachmentNotSupportedError = StructuredError(
68
- 'AttachmentNotSupportedError',
69
- 'Attachments are not supported in local task storage'
70
- );
71
-
72
- const UserNotFoundError = StructuredError('UserNotFoundError', 'User not found');
73
-
74
- const UserNameRequiredError = StructuredError(
75
- 'UserNameRequiredError',
76
- 'User name is required and must be a non-empty string'
77
- );
78
-
79
- const ProjectNotFoundError = StructuredError('ProjectNotFoundError', 'Project not found');
80
-
81
- const ProjectNameRequiredError = StructuredError(
82
- 'ProjectNameRequiredError',
83
- 'Project name is required and must be a non-empty string'
84
- );
85
-
86
- type CommentRow = {
87
- id: string;
88
- created_at: number;
89
- updated_at: number;
90
- task_id: string;
91
- user_id: string;
92
- body: string;
93
- };
94
-
95
- type TagRow = {
96
- id: string;
97
- created_at: number;
98
- name: string;
99
- color: string | null;
100
- };
101
-
102
- type TaskRow = {
103
- id: string;
104
- created_at: number;
105
- updated_at: number;
106
- title: string;
107
- description: string | null;
108
- metadata: string | null;
109
- priority: TaskPriority;
110
- parent_id: string | null;
111
- type: TaskType;
112
- status: TaskStatus;
113
- open_date: string | null;
114
- in_progress_date: string | null;
115
- closed_date: string | null;
116
- created_id: string;
117
- assigned_id: string | null;
118
- closed_id: string | null;
119
- deleted: number;
120
- };
121
-
122
- type TaskChangelogRow = {
123
- id: string;
124
- created_at: number;
125
- task_id: string;
126
- field: string;
127
- old_value: string | null;
128
- new_value: string | null;
129
- };
130
-
131
- const DEFAULT_LIMIT = 100;
132
-
133
- const SORT_FIELDS: Record<string, string> = {
134
- created_at: 'created_at',
135
- updated_at: 'updated_at',
136
- title: 'title',
137
- priority: 'priority',
138
- status: 'status',
139
- type: 'type',
140
- open_date: 'open_date',
141
- in_progress_date: 'in_progress_date',
142
- closed_date: 'closed_date',
143
- };
144
-
145
- const DURATION_UNITS: Record<string, number> = {
146
- s: 1000,
147
- m: 60 * 1000,
148
- h: 60 * 60 * 1000,
149
- d: 24 * 60 * 60 * 1000,
150
- w: 7 * 24 * 60 * 60 * 1000,
151
- };
152
-
153
- const InvalidDurationError = StructuredError(
154
- 'InvalidDurationError',
155
- 'Invalid duration format: use a number followed by s (seconds), m (minutes), h (hours), d (days), or w (weeks)'
156
- );
157
-
158
- function parseDurationMs(duration: string): number {
159
- const match = duration.match(/^(\d+)([smhdw])$/);
160
- if (!match) {
161
- throw new InvalidDurationError();
162
- }
163
- const value = parseInt(match[1]!, 10);
164
- const unit = match[2]!;
165
- const ms = DURATION_UNITS[unit];
166
- if (!ms) {
167
- throw new InvalidDurationError();
168
- }
169
- return value * ms;
170
- }
171
-
172
- function generateTaskId(): string {
173
- return `task_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
174
- }
175
-
176
- function generateChangelogId(): string {
177
- return `taskch_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
178
- }
179
-
180
- function toTask(row: TaskRow): Task {
181
- return {
182
- id: row.id,
183
- created_at: new Date(row.created_at).toISOString(),
184
- updated_at: new Date(row.updated_at).toISOString(),
185
- title: row.title,
186
- description: row.description ?? undefined,
187
- metadata: row.metadata ? (JSON.parse(row.metadata) as Record<string, unknown>) : undefined,
188
- priority: row.priority,
189
- parent_id: row.parent_id ?? undefined,
190
- type: row.type,
191
- status: row.status,
192
- open_date: row.open_date ?? undefined,
193
- in_progress_date: row.in_progress_date ?? undefined,
194
- closed_date: row.closed_date ?? undefined,
195
- created_id: row.created_id,
196
- assigned_id: row.assigned_id ?? undefined,
197
- closed_id: row.closed_id ?? undefined,
198
- deleted: row.deleted === 1,
199
- };
200
- }
201
-
202
- function toComment(row: CommentRow): Comment {
203
- return {
204
- id: row.id,
205
- created_at: new Date(row.created_at).toISOString(),
206
- updated_at: new Date(row.updated_at).toISOString(),
207
- task_id: row.task_id,
208
- user_id: row.user_id,
209
- body: row.body,
210
- };
211
- }
212
-
213
- function toTag(row: TagRow): Tag {
214
- return {
215
- id: row.id,
216
- created_at: new Date(row.created_at).toISOString(),
217
- name: row.name,
218
- color: row.color ?? undefined,
219
- };
220
- }
221
-
222
- function generateCommentId(): string {
223
- return `comment_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
224
- }
225
-
226
- function generateTagId(): string {
227
- return `tag_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
228
- }
229
-
230
- function generateUserId(): string {
231
- return `usr_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
232
- }
233
-
234
- function generateProjectId(): string {
235
- return `prj_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
236
- }
237
-
238
- function toChangelogEntry(row: TaskChangelogRow): TaskChangelogEntry {
239
- return {
240
- id: row.id,
241
- created_at: new Date(row.created_at).toISOString(),
242
- task_id: row.task_id,
243
- field: row.field,
244
- old_value: row.old_value ?? undefined,
245
- new_value: row.new_value ?? undefined,
246
- };
247
- }
248
-
249
- export class LocalTaskStorage implements TaskStorage {
250
- #db: Database;
251
- #projectPath: string;
252
-
253
- constructor(db: Database, projectPath: string) {
254
- this.#db = db;
255
- this.#projectPath = projectPath;
256
- }
257
-
258
- async create(params: CreateTaskParams): Promise<Task> {
259
- const trimmedTitle = params?.title?.trim();
260
- if (!trimmedTitle) {
261
- throw new TaskTitleRequiredError();
262
- }
263
-
264
- const id = generateTaskId();
265
- const timestamp = now();
266
- const status: TaskStatus = params.status ? normalizeTaskStatus(params.status) : 'open';
267
- const priority: TaskPriority = params.priority ?? 'none';
268
- const openDate = status === 'open' ? new Date(timestamp).toISOString() : null;
269
- const inProgressDate = status === 'in_progress' ? new Date(timestamp).toISOString() : null;
270
- const closedDate = status === 'done' ? new Date(timestamp).toISOString() : null;
271
-
272
- const stmt = this.#db.prepare(`
273
- INSERT INTO task_storage (
274
- project_path,
275
- id,
276
- title,
277
- description,
278
- metadata,
279
- priority,
280
- parent_id,
281
- type,
282
- status,
283
- open_date,
284
- in_progress_date,
285
- closed_date,
286
- created_id,
287
- assigned_id,
288
- closed_id,
289
- created_at,
290
- updated_at
291
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
292
- `);
293
-
294
- const row: TaskRow = {
295
- id,
296
- created_at: timestamp,
297
- updated_at: timestamp,
298
- title: trimmedTitle,
299
- description: params.description ?? null,
300
- metadata: params.metadata ? JSON.stringify(params.metadata) : null,
301
- priority,
302
- parent_id: params.parent_id ?? null,
303
- type: params.type,
304
- status,
305
- open_date: openDate,
306
- in_progress_date: inProgressDate,
307
- closed_date: closedDate,
308
- created_id: params.created_id,
309
- assigned_id: params.assigned_id ?? null,
310
- closed_id: null,
311
- deleted: 0,
312
- };
313
-
314
- stmt.run(
315
- this.#projectPath,
316
- row.id,
317
- row.title,
318
- row.description,
319
- row.metadata,
320
- row.priority,
321
- row.parent_id,
322
- row.type,
323
- row.status,
324
- row.open_date,
325
- row.in_progress_date,
326
- row.closed_date,
327
- row.created_id,
328
- row.assigned_id,
329
- row.closed_id,
330
- row.created_at,
331
- row.updated_at
332
- );
333
-
334
- return toTask(row);
335
- }
336
-
337
- async get(id: string): Promise<Task | null> {
338
- const query = this.#db.query(`
339
- SELECT
340
- id,
341
- created_at,
342
- updated_at,
343
- title,
344
- description,
345
- metadata,
346
- priority,
347
- parent_id,
348
- type,
349
- status,
350
- open_date,
351
- in_progress_date,
352
- closed_date,
353
- created_id,
354
- assigned_id,
355
- closed_id,
356
- deleted
357
- FROM task_storage
358
- WHERE project_path = ? AND id = ?
359
- `);
360
-
361
- const row = query.get(this.#projectPath, id) as TaskRow | null;
362
- if (!row) {
363
- return null;
364
- }
365
-
366
- return toTask(row);
367
- }
368
-
369
- async list(params?: ListTasksParams): Promise<ListTasksResult> {
370
- const filters: string[] = ['project_path = ?'];
371
- const values: Array<string | number> = [this.#projectPath];
372
-
373
- if (params?.status) {
374
- filters.push('status = ?');
375
- values.push(normalizeTaskStatus(params.status));
376
- }
377
- if (params?.type) {
378
- filters.push('type = ?');
379
- values.push(params.type);
380
- }
381
- if (params?.priority) {
382
- filters.push('priority = ?');
383
- values.push(params.priority);
384
- }
385
- if (params?.assigned_id) {
386
- filters.push('assigned_id = ?');
387
- values.push(params.assigned_id);
388
- }
389
- if (params?.parent_id) {
390
- filters.push('parent_id = ?');
391
- values.push(params.parent_id);
392
- }
393
- if (params?.created_id) {
394
- filters.push('created_id = ?');
395
- values.push(params.created_id);
396
- }
397
- if (params?.project_id) {
398
- filters.push('project_id = ?');
399
- values.push(params.project_id);
400
- }
401
- if (params?.deleted === undefined) {
402
- filters.push('deleted = 0');
403
- } else {
404
- filters.push('deleted = ?');
405
- values.push(params.deleted ? 1 : 0);
406
- }
407
- if (params?.tag_id) {
408
- filters.push(
409
- 'id IN (SELECT task_id FROM task_tag_association_storage WHERE tag_id = ? AND project_path = ?)'
410
- );
411
- values.push(params.tag_id);
412
- values.push(this.#projectPath);
413
- }
414
-
415
- const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
416
- const sortField =
417
- params?.sort && SORT_FIELDS[params.sort] ? SORT_FIELDS[params.sort] : 'created_at';
418
- const sortOrder = params?.order === 'asc' ? 'ASC' : 'DESC';
419
- const limit = params?.limit ?? DEFAULT_LIMIT;
420
- const offset = params?.offset ?? 0;
421
-
422
- const totalQuery = this.#db.query(
423
- `SELECT COUNT(*) as count FROM task_storage ${whereClause}`
424
- );
425
- const totalRow = totalQuery.get(...values) as { count: number };
426
-
427
- const query = this.#db.query(`
428
- SELECT
429
- id,
430
- created_at,
431
- updated_at,
432
- title,
433
- description,
434
- metadata,
435
- priority,
436
- parent_id,
437
- type,
438
- status,
439
- open_date,
440
- in_progress_date,
441
- closed_date,
442
- created_id,
443
- assigned_id,
444
- closed_id,
445
- deleted
446
- FROM task_storage
447
- ${whereClause}
448
- ORDER BY ${sortField} ${sortOrder}
449
- LIMIT ? OFFSET ?
450
- `);
451
-
452
- const rows = query.all(...values, limit, offset) as TaskRow[];
453
-
454
- return {
455
- tasks: rows.map(toTask),
456
- total: totalRow.count,
457
- limit,
458
- offset,
459
- };
460
- }
461
-
462
- async update(id: string, params: UpdateTaskParams): Promise<Task> {
463
- const updateInTransaction = this.#db.transaction(() => {
464
- const existingQuery = this.#db.query(`
465
- SELECT
466
- id,
467
- created_at,
468
- updated_at,
469
- title,
470
- description,
471
- metadata,
472
- priority,
473
- parent_id,
474
- type,
475
- status,
476
- open_date,
477
- in_progress_date,
478
- closed_date,
479
- created_id,
480
- assigned_id,
481
- closed_id,
482
- deleted
483
- FROM task_storage
484
- WHERE project_path = ? AND id = ?
485
- `);
486
-
487
- const existing = existingQuery.get(this.#projectPath, id) as TaskRow | null;
488
- if (!existing) {
489
- throw new TaskNotFoundError();
490
- }
491
- const trimmedTitle = params.title !== undefined ? params.title?.trim() : undefined;
492
- if (params.title !== undefined && !trimmedTitle) {
493
- throw new TaskTitleRequiredError();
494
- }
495
- const timestamp = now();
496
- const nowIso = new Date(timestamp).toISOString();
497
- const normalizedStatus = params.status ? normalizeTaskStatus(params.status) : undefined;
498
-
499
- const updated: TaskRow = {
500
- ...existing,
501
- title: trimmedTitle ?? existing.title,
502
- description:
503
- params.description !== undefined ? params.description : existing.description,
504
- metadata:
505
- params.metadata !== undefined
506
- ? params.metadata
507
- ? JSON.stringify(params.metadata)
508
- : null
509
- : existing.metadata,
510
- priority: params.priority ?? existing.priority,
511
- parent_id: params.parent_id !== undefined ? params.parent_id : existing.parent_id,
512
- type: params.type ?? existing.type,
513
- status: normalizedStatus ?? existing.status,
514
- assigned_id:
515
- params.assigned_id !== undefined ? params.assigned_id : existing.assigned_id,
516
- closed_id: params.closed_id !== undefined ? params.closed_id : existing.closed_id,
517
- updated_at: timestamp,
518
- };
519
-
520
- if (normalizedStatus && normalizedStatus !== existing.status) {
521
- if (normalizedStatus === 'open' && !existing.open_date) {
522
- updated.open_date = nowIso;
523
- }
524
- if (normalizedStatus === 'in_progress' && !existing.in_progress_date) {
525
- updated.in_progress_date = nowIso;
526
- }
527
- if (normalizedStatus === 'done' && !existing.closed_date) {
528
- updated.closed_date = nowIso;
529
- }
530
- }
531
-
532
- const changelogEntries: Array<{
533
- field: string;
534
- oldValue: string | null;
535
- newValue: string | null;
536
- }> = [];
537
-
538
- const compare = (field: string, oldValue: string | null, newValue: string | null) => {
539
- if (oldValue !== newValue) {
540
- changelogEntries.push({ field, oldValue, newValue });
541
- }
542
- };
543
-
544
- if (params.title !== undefined) {
545
- compare('title', existing.title, updated.title);
546
- }
547
- if (params.description !== undefined) {
548
- compare('description', existing.description, updated.description);
549
- }
550
- if (params.metadata !== undefined) {
551
- compare('metadata', existing.metadata, updated.metadata);
552
- }
553
- if (params.priority !== undefined) {
554
- compare('priority', existing.priority, updated.priority);
555
- }
556
- if (params.parent_id !== undefined) {
557
- compare('parent_id', existing.parent_id, updated.parent_id);
558
- }
559
- if (params.type !== undefined) {
560
- compare('type', existing.type, updated.type);
561
- }
562
- if (normalizedStatus !== undefined) {
563
- compare('status', existing.status, updated.status);
564
- }
565
- if (params.assigned_id !== undefined) {
566
- compare('assigned_id', existing.assigned_id, updated.assigned_id);
567
- }
568
- if (params.closed_id !== undefined) {
569
- compare('closed_id', existing.closed_id, updated.closed_id);
570
- }
571
-
572
- const updateStmt = this.#db.prepare(`
573
- UPDATE task_storage
574
- SET
575
- title = ?,
576
- description = ?,
577
- metadata = ?,
578
- priority = ?,
579
- parent_id = ?,
580
- type = ?,
581
- status = ?,
582
- open_date = ?,
583
- in_progress_date = ?,
584
- closed_date = ?,
585
- created_id = ?,
586
- assigned_id = ?,
587
- closed_id = ?,
588
- updated_at = ?
589
- WHERE project_path = ? AND id = ?
590
- `);
591
-
592
- updateStmt.run(
593
- updated.title,
594
- updated.description,
595
- updated.metadata,
596
- updated.priority,
597
- updated.parent_id,
598
- updated.type,
599
- updated.status,
600
- updated.open_date,
601
- updated.in_progress_date,
602
- updated.closed_date,
603
- updated.created_id,
604
- updated.assigned_id,
605
- updated.closed_id,
606
- updated.updated_at,
607
- this.#projectPath,
608
- id
609
- );
610
-
611
- if (changelogEntries.length > 0) {
612
- const changelogStmt = this.#db.prepare(`
613
- INSERT INTO task_changelog_storage (
614
- project_path,
615
- id,
616
- task_id,
617
- field,
618
- old_value,
619
- new_value,
620
- created_at
621
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
622
- `);
623
-
624
- for (const entry of changelogEntries) {
625
- changelogStmt.run(
626
- this.#projectPath,
627
- generateChangelogId(),
628
- id,
629
- entry.field,
630
- entry.oldValue,
631
- entry.newValue,
632
- timestamp
633
- );
634
- }
635
- }
636
-
637
- return toTask(updated);
638
- });
639
-
640
- return updateInTransaction.immediate();
641
- }
642
-
643
- async close(id: string): Promise<Task> {
644
- const closeInTransaction = this.#db.transaction(() => {
645
- const existingQuery = this.#db.query(`
646
- SELECT
647
- id,
648
- created_at,
649
- updated_at,
650
- title,
651
- description,
652
- metadata,
653
- priority,
654
- parent_id,
655
- type,
656
- status,
657
- open_date,
658
- in_progress_date,
659
- closed_date,
660
- created_id,
661
- assigned_id,
662
- closed_id,
663
- deleted
664
- FROM task_storage
665
- WHERE project_path = ? AND id = ?
666
- `);
667
-
668
- const existing = existingQuery.get(this.#projectPath, id) as TaskRow | null;
669
- if (!existing) {
670
- throw new TaskNotFoundError();
671
- }
672
-
673
- if (existing.status === 'done') {
674
- throw new TaskAlreadyClosedError();
675
- }
676
- const timestamp = now();
677
- const nowIso = new Date(timestamp).toISOString();
678
- const updated: TaskRow = {
679
- ...existing,
680
- status: 'done',
681
- closed_date: existing.closed_date ?? nowIso,
682
- updated_at: timestamp,
683
- };
684
-
685
- const updateStmt = this.#db.prepare(`
686
- UPDATE task_storage
687
- SET status = ?, closed_date = ?, updated_at = ?
688
- WHERE project_path = ? AND id = ?
689
- `);
690
-
691
- updateStmt.run(
692
- updated.status,
693
- updated.closed_date,
694
- updated.updated_at,
695
- this.#projectPath,
696
- id
697
- );
698
-
699
- const changelogStmt = this.#db.prepare(`
700
- INSERT INTO task_changelog_storage (
701
- project_path,
702
- id,
703
- task_id,
704
- field,
705
- old_value,
706
- new_value,
707
- created_at
708
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
709
- `);
710
-
711
- changelogStmt.run(
712
- this.#projectPath,
713
- generateChangelogId(),
714
- id,
715
- 'status',
716
- existing.status,
717
- updated.status,
718
- timestamp
719
- );
720
-
721
- return toTask(updated);
722
- });
723
-
724
- return closeInTransaction.immediate();
725
- }
726
-
727
- async changelog(
728
- id: string,
729
- params?: { limit?: number; offset?: number }
730
- ): Promise<TaskChangelogResult> {
731
- const limit = params?.limit ?? DEFAULT_LIMIT;
732
- const offset = params?.offset ?? 0;
733
-
734
- const totalQuery = this.#db.query(
735
- `SELECT COUNT(*) as count FROM task_changelog_storage WHERE project_path = ? AND task_id = ?`
736
- );
737
- const totalRow = totalQuery.get(this.#projectPath, id) as { count: number };
738
-
739
- const query = this.#db.query(`
740
- SELECT
741
- id,
742
- created_at,
743
- task_id,
744
- field,
745
- old_value,
746
- new_value
747
- FROM task_changelog_storage
748
- WHERE project_path = ? AND task_id = ?
749
- ORDER BY created_at DESC
750
- LIMIT ? OFFSET ?
751
- `);
752
-
753
- const rows = query.all(this.#projectPath, id, limit, offset) as TaskChangelogRow[];
754
-
755
- return {
756
- changelog: rows.map(toChangelogEntry),
757
- total: totalRow.count,
758
- limit,
759
- offset,
760
- };
761
- }
762
-
763
- async softDelete(id: string): Promise<Task> {
764
- const task = await this.get(id);
765
- if (!task) {
766
- throw new TaskNotFoundError();
767
- }
768
-
769
- const timestamp = now();
770
-
771
- const updateStmt = this.#db.prepare(`
772
- UPDATE task_storage
773
- SET status = 'done', deleted = 1, closed_date = COALESCE(closed_date, ?), updated_at = ?
774
- WHERE project_path = ? AND id = ?
775
- `);
776
-
777
- updateStmt.run(new Date(timestamp).toISOString(), timestamp, this.#projectPath, id);
778
-
779
- const changelogStmt = this.#db.prepare(`
780
- INSERT INTO task_changelog_storage (
781
- project_path, id, task_id, field, old_value, new_value, created_at
782
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
783
- `);
784
-
785
- changelogStmt.run(
786
- this.#projectPath,
787
- generateChangelogId(),
788
- id,
789
- 'deleted',
790
- 'false',
791
- 'true',
792
- timestamp
793
- );
794
-
795
- const updated = await this.get(id);
796
- return updated!;
797
- }
798
-
799
- async batchDelete(params: BatchDeleteTasksParams): Promise<BatchDeleteTasksResult> {
800
- const conditions: string[] = ['project_path = ?', 'deleted = 0'];
801
- const args: (string | number)[] = [this.#projectPath];
802
-
803
- if (params.status) {
804
- conditions.push('status = ?');
805
- args.push(normalizeTaskStatus(params.status));
806
- }
807
- if (params.type) {
808
- conditions.push('type = ?');
809
- args.push(params.type);
810
- }
811
- if (params.priority) {
812
- conditions.push('priority = ?');
813
- args.push(params.priority);
814
- }
815
- if (params.parent_id) {
816
- conditions.push('parent_id = ?');
817
- args.push(params.parent_id);
818
- }
819
- if (params.created_id) {
820
- conditions.push('created_id = ?');
821
- args.push(params.created_id);
822
- }
823
- if (params.older_than) {
824
- const ms = parseDurationMs(params.older_than);
825
- const cutoff = Date.now() - ms;
826
- conditions.push('created_at < ?');
827
- args.push(cutoff);
828
- }
829
-
830
- // Require at least one filter beyond project_path + deleted
831
- if (conditions.length < 3) {
832
- const BatchDeleteFilterRequiredError = StructuredError(
833
- 'BatchDeleteFilterRequiredError',
834
- 'At least one filter is required for batch delete'
835
- );
836
- throw new BatchDeleteFilterRequiredError();
837
- }
838
-
839
- if (params.limit !== undefined && (!Number.isInteger(params.limit) || params.limit <= 0)) {
840
- const InvalidBatchDeleteLimitError = StructuredError(
841
- 'InvalidBatchDeleteLimitError',
842
- 'Batch delete limit must be a positive integer'
843
- );
844
- throw new InvalidBatchDeleteLimitError();
845
- }
846
- const limit = Math.min(params.limit ?? 50, 200);
847
-
848
- const whereClause = conditions.join(' AND ');
849
- const selectQuery = `SELECT id, title FROM task_storage WHERE ${whereClause} ORDER BY created_at ASC LIMIT ?`;
850
- const selectStmt = this.#db.prepare(selectQuery);
851
- const rows = selectStmt.all(...args, limit) as Array<{ id: string; title: string }>;
852
-
853
- if (rows.length === 0) {
854
- return { deleted: [], count: 0 };
855
- }
856
-
857
- const timestamp = now();
858
- const ids = rows.map((r) => r.id);
859
- const placeholders = ids.map(() => '?').join(', ');
860
-
861
- const txn = this.#db.transaction(() => {
862
- const updateStmt = this.#db.prepare(`
863
- UPDATE task_storage
864
- SET status = 'done', deleted = 1, closed_date = COALESCE(closed_date, ?), updated_at = ?
865
- WHERE project_path = ? AND id IN (${placeholders})
866
- `);
867
- updateStmt.run(new Date(timestamp).toISOString(), timestamp, this.#projectPath, ...ids);
868
-
869
- const changelogStmt = this.#db.prepare(`
870
- INSERT INTO task_changelog_storage (
871
- project_path, id, task_id, field, old_value, new_value, created_at
872
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
873
- `);
874
- for (const row of rows) {
875
- changelogStmt.run(
876
- this.#projectPath,
877
- generateChangelogId(),
878
- row.id,
879
- 'deleted',
880
- 'false',
881
- 'true',
882
- timestamp
883
- );
884
- }
885
- });
886
- txn();
887
-
888
- return {
889
- deleted: rows.map((r) => ({ id: r.id, title: r.title })),
890
- count: rows.length,
891
- };
892
- }
893
-
894
- async batchUpdate(params: BatchUpdateTasksParams): Promise<BatchUpdateTasksResult> {
895
- const conditions: string[] = ['project_path = ?', 'deleted = 0'];
896
- const args: (string | number)[] = [this.#projectPath];
897
-
898
- // Handle explicit IDs
899
- if (params.ids && params.ids.length > 0) {
900
- const placeholders = params.ids.map(() => '?').join(', ');
901
- conditions.push(`id IN (${placeholders})`);
902
- args.push(...params.ids);
903
- } else {
904
- // Build filter conditions
905
- if (params.status) {
906
- conditions.push('status = ?');
907
- args.push(normalizeTaskStatus(params.status));
908
- }
909
- if (params.type) {
910
- conditions.push('type = ?');
911
- args.push(params.type);
912
- }
913
- if (params.priority) {
914
- conditions.push('priority = ?');
915
- args.push(params.priority);
916
- }
917
- if (params.parent_id) {
918
- conditions.push('parent_id = ?');
919
- args.push(params.parent_id);
920
- }
921
- if (params.created_id) {
922
- conditions.push('created_id = ?');
923
- args.push(params.created_id);
924
- }
925
- if (params.assigned_id) {
926
- conditions.push('assigned_id = ?');
927
- args.push(params.assigned_id);
928
- }
929
- if (params.older_than) {
930
- const ms = parseDurationMs(params.older_than);
931
- const cutoff = Date.now() - ms;
932
- conditions.push('created_at < ?');
933
- args.push(cutoff);
934
- }
935
- if (params.project_id) {
936
- conditions.push('project_id = ?');
937
- args.push(params.project_id);
938
- }
939
- if (params.tag_id) {
940
- conditions.push(
941
- 'id IN (SELECT task_id FROM task_tag_association_storage WHERE tag_id = ? AND project_path = ?)'
942
- );
943
- args.push(params.tag_id);
944
- args.push(this.#projectPath);
945
- }
946
- if (params.newer_than) {
947
- const ms = parseDurationMs(params.newer_than);
948
- const cutoff = Date.now() - ms;
949
- conditions.push('created_at > ?');
950
- args.push(cutoff);
951
- }
952
- }
953
-
954
- // Require at least one filter or IDs
955
- if (params.ids && params.ids.length > 0) {
956
- // IDs provided, OK
957
- } else if (conditions.length < 3) {
958
- throw new Error('At least one filter or ids is required for batch update');
959
- }
960
-
961
- // Check for update fields
962
- const hasUpdate =
963
- params.new_status ||
964
- params.new_priority ||
965
- params.new_assigned_id ||
966
- params.new_assignee ||
967
- params.new_title ||
968
- params.new_description ||
969
- params.new_metadata ||
970
- params.new_type;
971
- if (!hasUpdate) {
972
- throw new Error('At least one update field is required for batch update');
973
- }
974
-
975
- if (params.limit !== undefined && (!Number.isInteger(params.limit) || params.limit <= 0)) {
976
- throw new Error('Batch update limit must be a positive integer');
977
- }
978
- const limit = Math.min(params.limit ?? 50, 200);
979
-
980
- const whereClause = conditions.join(' AND ');
981
- const selectQuery = `SELECT id, title, status, priority FROM task_storage WHERE ${whereClause} ORDER BY created_at ASC LIMIT ?`;
982
- const selectStmt = this.#db.prepare(selectQuery);
983
- const rows = selectStmt.all(...args, limit) as Array<{
984
- id: string;
985
- title: string;
986
- status: string;
987
- priority: string;
988
- }>;
989
-
990
- if (rows.length === 0) {
991
- return { updated: [], count: 0, dry_run: params.dry_run ?? false };
992
- }
993
-
994
- // Dry run - return preview without updating
995
- if (params.dry_run) {
996
- const normalizedStatus = params.new_status
997
- ? normalizeTaskStatus(params.new_status)
998
- : undefined;
999
- return {
1000
- updated: rows.map((r) => ({
1001
- id: r.id,
1002
- title: params.new_title ?? r.title,
1003
- status: (normalizedStatus ?? r.status) as TaskStatus,
1004
- priority: (params.new_priority ?? r.priority) as TaskPriority,
1005
- })),
1006
- count: rows.length,
1007
- dry_run: true,
1008
- };
1009
- }
1010
-
1011
- const timestamp = now();
1012
- const ids = rows.map((r) => r.id);
1013
- const placeholders = ids.map(() => '?').join(', ');
1014
-
1015
- const updateFields: string[] = ['updated_at = ?'];
1016
- const updateArgs: (string | number)[] = [timestamp];
1017
-
1018
- if (params.new_status) {
1019
- updateFields.push('status = ?');
1020
- updateArgs.push(normalizeTaskStatus(params.new_status));
1021
- }
1022
- if (params.new_priority) {
1023
- updateFields.push('priority = ?');
1024
- updateArgs.push(params.new_priority);
1025
- }
1026
- if (params.new_assigned_id) {
1027
- updateFields.push('assigned_id = ?');
1028
- updateArgs.push(params.new_assigned_id);
1029
- }
1030
- if (params.new_title) {
1031
- updateFields.push('title = ?');
1032
- updateArgs.push(params.new_title);
1033
- }
1034
- if (params.new_description) {
1035
- updateFields.push('description = ?');
1036
- updateArgs.push(params.new_description);
1037
- }
1038
- if (params.new_metadata) {
1039
- updateFields.push('metadata = ?');
1040
- updateArgs.push(JSON.stringify(params.new_metadata));
1041
- }
1042
- if (params.new_type) {
1043
- updateFields.push('type = ?');
1044
- updateArgs.push(params.new_type);
1045
- }
1046
-
1047
- // Set lifecycle timestamps based on new status (only when transitioning)
1048
- if (params.new_status) {
1049
- const newStatus = normalizeTaskStatus(params.new_status);
1050
- if (newStatus === 'open') {
1051
- updateFields.push('open_date = COALESCE(open_date, ?)');
1052
- updateArgs.push(new Date(timestamp).toISOString());
1053
- } else if (newStatus === 'in_progress') {
1054
- updateFields.push('in_progress_date = COALESCE(in_progress_date, ?)');
1055
- updateArgs.push(new Date(timestamp).toISOString());
1056
- } else if (newStatus === 'done') {
1057
- updateFields.push('closed_date = COALESCE(closed_date, ?)');
1058
- updateArgs.push(new Date(timestamp).toISOString());
1059
- }
1060
- }
1061
-
1062
- const txn = this.#db.transaction(() => {
1063
- const updateStmt = this.#db.prepare(`
1064
- UPDATE task_storage SET ${updateFields.join(', ')}
1065
- WHERE project_path = ? AND id IN (${placeholders})
1066
- `);
1067
- updateStmt.run(...updateArgs, this.#projectPath, ...ids);
1068
-
1069
- const changelogStmt = this.#db.prepare(`
1070
- INSERT INTO task_changelog_storage (
1071
- project_path, id, task_id, field, old_value, new_value, created_at
1072
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
1073
- `);
1074
- for (const row of rows) {
1075
- if (params.new_status && row.status !== params.new_status) {
1076
- changelogStmt.run(
1077
- this.#projectPath,
1078
- generateChangelogId(),
1079
- row.id,
1080
- 'status',
1081
- row.status,
1082
- params.new_status,
1083
- timestamp
1084
- );
1085
- }
1086
- if (params.new_priority && row.priority !== params.new_priority) {
1087
- changelogStmt.run(
1088
- this.#projectPath,
1089
- generateChangelogId(),
1090
- row.id,
1091
- 'priority',
1092
- row.priority,
1093
- params.new_priority,
1094
- timestamp
1095
- );
1096
- }
1097
- }
1098
- });
1099
- txn();
1100
-
1101
- return {
1102
- updated: rows.map((r) => ({
1103
- id: r.id,
1104
- title: params.new_title ?? r.title,
1105
- status: (params.new_status ?? r.status) as TaskStatus,
1106
- priority: (params.new_priority ?? r.priority) as TaskPriority,
1107
- })),
1108
- count: rows.length,
1109
- dry_run: false,
1110
- };
1111
- }
1112
-
1113
- async batchClose(params: BatchCloseTasksParams): Promise<BatchCloseTasksResult> {
1114
- // Resolve closer ID from either closed_id or closer entity ref
1115
- const closerId = params.closed_id ?? params.closer?.id ?? null;
1116
-
1117
- const conditions: string[] = ['project_path = ?', 'deleted = 0', "status != 'done'"];
1118
- const args: (string | number)[] = [this.#projectPath];
1119
-
1120
- // Handle explicit IDs
1121
- if (params.ids && params.ids.length > 0) {
1122
- const placeholders = params.ids.map(() => '?').join(', ');
1123
- conditions.push(`id IN (${placeholders})`);
1124
- args.push(...params.ids);
1125
- } else {
1126
- // Build filter conditions
1127
- if (params.status) {
1128
- conditions.push('status = ?');
1129
- args.push(normalizeTaskStatus(params.status));
1130
- }
1131
- if (params.type) {
1132
- conditions.push('type = ?');
1133
- args.push(params.type);
1134
- }
1135
- if (params.priority) {
1136
- conditions.push('priority = ?');
1137
- args.push(params.priority);
1138
- }
1139
- if (params.parent_id) {
1140
- conditions.push('parent_id = ?');
1141
- args.push(params.parent_id);
1142
- }
1143
- if (params.created_id) {
1144
- conditions.push('created_id = ?');
1145
- args.push(params.created_id);
1146
- }
1147
- if (params.assigned_id) {
1148
- conditions.push('assigned_id = ?');
1149
- args.push(params.assigned_id);
1150
- }
1151
- if (params.older_than) {
1152
- const ms = parseDurationMs(params.older_than);
1153
- const cutoff = Date.now() - ms;
1154
- conditions.push('created_at < ?');
1155
- args.push(cutoff);
1156
- }
1157
- if (params.project_id) {
1158
- conditions.push('project_id = ?');
1159
- args.push(params.project_id);
1160
- }
1161
- if (params.tag_id) {
1162
- conditions.push(
1163
- 'id IN (SELECT task_id FROM task_tag_association_storage WHERE tag_id = ? AND project_path = ?)'
1164
- );
1165
- args.push(params.tag_id);
1166
- args.push(this.#projectPath);
1167
- }
1168
- if (params.newer_than) {
1169
- const ms = parseDurationMs(params.newer_than);
1170
- const cutoff = Date.now() - ms;
1171
- conditions.push('created_at > ?');
1172
- args.push(cutoff);
1173
- }
1174
- }
1175
-
1176
- // Require at least one filter or IDs
1177
- if (params.ids && params.ids.length > 0) {
1178
- // IDs provided, OK
1179
- } else if (conditions.length < 4) {
1180
- throw new Error('At least one filter or ids is required for batch close');
1181
- }
1182
-
1183
- if (params.limit !== undefined && (!Number.isInteger(params.limit) || params.limit <= 0)) {
1184
- throw new Error('Batch close limit must be a positive integer');
1185
- }
1186
- const limit = Math.min(params.limit ?? 50, 200);
1187
-
1188
- const whereClause = conditions.join(' AND ');
1189
- const selectQuery = `SELECT id, title, status FROM task_storage WHERE ${whereClause} ORDER BY created_at ASC LIMIT ?`;
1190
- const selectStmt = this.#db.prepare(selectQuery);
1191
- const rows = selectStmt.all(...args, limit) as Array<{
1192
- id: string;
1193
- title: string;
1194
- status: string;
1195
- }>;
1196
-
1197
- if (rows.length === 0) {
1198
- return { closed: [], count: 0, dry_run: params.dry_run ?? false };
1199
- }
1200
-
1201
- // Dry run - return preview without closing
1202
- if (params.dry_run) {
1203
- const nowTs = new Date().toISOString();
1204
- return {
1205
- closed: rows.map((r) => ({
1206
- id: r.id,
1207
- title: r.title,
1208
- status: 'done',
1209
- closed_date: nowTs,
1210
- })),
1211
- count: rows.length,
1212
- dry_run: true,
1213
- };
1214
- }
1215
-
1216
- const timestamp = now();
1217
- const ids = rows.map((r) => r.id);
1218
- const placeholders = ids.map(() => '?').join(', ');
1219
- const closedDate = new Date(timestamp).toISOString();
1220
-
1221
- const txn = this.#db.transaction(() => {
1222
- const updateStmt = this.#db.prepare(`
1223
- UPDATE task_storage SET status = 'done', closed_date = COALESCE(closed_date, ?), closed_id = ?, updated_at = ?
1224
- WHERE project_path = ? AND id IN (${placeholders})
1225
- `);
1226
- updateStmt.run(closedDate, closerId, timestamp, this.#projectPath, ...ids);
1227
-
1228
- const changelogStmt = this.#db.prepare(`
1229
- INSERT INTO task_changelog_storage (
1230
- project_path, id, task_id, field, old_value, new_value, created_at
1231
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
1232
- `);
1233
- for (const row of rows) {
1234
- if (row.status !== 'done') {
1235
- changelogStmt.run(
1236
- this.#projectPath,
1237
- generateChangelogId(),
1238
- row.id,
1239
- 'status',
1240
- row.status,
1241
- 'done',
1242
- timestamp
1243
- );
1244
- }
1245
- }
1246
- });
1247
- txn();
1248
-
1249
- return {
1250
- closed: rows.map((r) => ({
1251
- id: r.id,
1252
- title: r.title,
1253
- status: 'done',
1254
- closed_date: closedDate,
1255
- })),
1256
- count: rows.length,
1257
- dry_run: false,
1258
- };
1259
- }
1260
-
1261
- async createComment(taskId: string, body: string, userId: string): Promise<Comment> {
1262
- const trimmedBody = body?.trim();
1263
- if (!trimmedBody) {
1264
- throw new CommentBodyRequiredError();
1265
- }
1266
-
1267
- const trimmedUserId = userId?.trim();
1268
- if (!trimmedUserId) {
1269
- throw new CommentUserRequiredError();
1270
- }
1271
-
1272
- const task = await this.get(taskId);
1273
- if (!task) {
1274
- throw new TaskNotFoundError();
1275
- }
1276
-
1277
- const id = generateCommentId();
1278
- const timestamp = now();
1279
-
1280
- const stmt = this.#db.prepare(`
1281
- INSERT INTO task_comment_storage (
1282
- project_path, id, task_id, user_id, body, created_at, updated_at
1283
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
1284
- `);
1285
-
1286
- stmt.run(this.#projectPath, id, taskId, trimmedUserId, trimmedBody, timestamp, timestamp);
1287
-
1288
- return toComment({
1289
- id,
1290
- created_at: timestamp,
1291
- updated_at: timestamp,
1292
- task_id: taskId,
1293
- user_id: trimmedUserId,
1294
- body: trimmedBody,
1295
- });
1296
- }
1297
-
1298
- async getComment(commentId: string): Promise<Comment> {
1299
- const query = this.#db.query(`
1300
- SELECT id, created_at, updated_at, task_id, user_id, body
1301
- FROM task_comment_storage
1302
- WHERE project_path = ? AND id = ?
1303
- `);
1304
-
1305
- const row = query.get(this.#projectPath, commentId) as CommentRow | null;
1306
- if (!row) {
1307
- throw new CommentNotFoundError();
1308
- }
1309
-
1310
- return toComment(row);
1311
- }
1312
-
1313
- async updateComment(commentId: string, body: string): Promise<Comment> {
1314
- const trimmedBody = body?.trim();
1315
- if (!trimmedBody) {
1316
- throw new CommentBodyRequiredError();
1317
- }
1318
-
1319
- const existing = await this.getComment(commentId);
1320
- const timestamp = now();
1321
-
1322
- const stmt = this.#db.prepare(`
1323
- UPDATE task_comment_storage
1324
- SET body = ?, updated_at = ?
1325
- WHERE project_path = ? AND id = ?
1326
- `);
1327
-
1328
- stmt.run(trimmedBody, timestamp, this.#projectPath, commentId);
1329
-
1330
- return {
1331
- ...existing,
1332
- body: trimmedBody,
1333
- updated_at: new Date(timestamp).toISOString(),
1334
- };
1335
- }
1336
-
1337
- async deleteComment(commentId: string): Promise<void> {
1338
- const stmt = this.#db.prepare(`
1339
- DELETE FROM task_comment_storage
1340
- WHERE project_path = ? AND id = ?
1341
- `);
1342
-
1343
- stmt.run(this.#projectPath, commentId);
1344
- }
1345
-
1346
- async listComments(
1347
- taskId: string,
1348
- params?: { limit?: number; offset?: number }
1349
- ): Promise<ListCommentsResult> {
1350
- const limit = params?.limit ?? DEFAULT_LIMIT;
1351
- const offset = params?.offset ?? 0;
1352
-
1353
- const totalQuery = this.#db.query(
1354
- `SELECT COUNT(*) as count FROM task_comment_storage WHERE project_path = ? AND task_id = ?`
1355
- );
1356
- const totalRow = totalQuery.get(this.#projectPath, taskId) as { count: number };
1357
-
1358
- const query = this.#db.query(`
1359
- SELECT id, created_at, updated_at, task_id, user_id, body
1360
- FROM task_comment_storage
1361
- WHERE project_path = ? AND task_id = ?
1362
- ORDER BY created_at DESC
1363
- LIMIT ? OFFSET ?
1364
- `);
1365
-
1366
- const rows = query.all(this.#projectPath, taskId, limit, offset) as CommentRow[];
1367
-
1368
- return {
1369
- comments: rows.map(toComment),
1370
- total: totalRow.count,
1371
- limit,
1372
- offset,
1373
- };
1374
- }
1375
-
1376
- async createTag(name: string, color?: string): Promise<Tag> {
1377
- const trimmedName = name?.trim();
1378
- if (!trimmedName) {
1379
- throw new TagNameRequiredError();
1380
- }
1381
-
1382
- const id = generateTagId();
1383
- const timestamp = now();
1384
-
1385
- const stmt = this.#db.prepare(`
1386
- INSERT INTO task_tag_storage (
1387
- project_path, id, name, color, created_at
1388
- ) VALUES (?, ?, ?, ?, ?)
1389
- `);
1390
-
1391
- stmt.run(this.#projectPath, id, trimmedName, color ?? null, timestamp);
1392
-
1393
- return toTag({
1394
- id,
1395
- created_at: timestamp,
1396
- name: trimmedName,
1397
- color: color ?? null,
1398
- });
1399
- }
1400
-
1401
- async getTag(tagId: string): Promise<Tag> {
1402
- const query = this.#db.query(`
1403
- SELECT id, created_at, name, color
1404
- FROM task_tag_storage
1405
- WHERE project_path = ? AND id = ?
1406
- `);
1407
-
1408
- const row = query.get(this.#projectPath, tagId) as TagRow | null;
1409
- if (!row) {
1410
- throw new TagNotFoundError();
1411
- }
1412
-
1413
- return toTag(row);
1414
- }
1415
-
1416
- async updateTag(tagId: string, name: string, color?: string): Promise<Tag> {
1417
- const trimmedName = name?.trim();
1418
- if (!trimmedName) {
1419
- throw new TagNameRequiredError();
1420
- }
1421
-
1422
- // Verify exists
1423
- await this.getTag(tagId);
1424
-
1425
- const stmt = this.#db.prepare(`
1426
- UPDATE task_tag_storage
1427
- SET name = ?, color = ?
1428
- WHERE project_path = ? AND id = ?
1429
- `);
1430
-
1431
- stmt.run(trimmedName, color ?? null, this.#projectPath, tagId);
1432
-
1433
- return this.getTag(tagId);
1434
- }
1435
-
1436
- async deleteTag(tagId: string): Promise<void> {
1437
- // Also remove tag associations
1438
- const deleteAssocStmt = this.#db.prepare(`
1439
- DELETE FROM task_tag_association_storage
1440
- WHERE project_path = ? AND tag_id = ?
1441
- `);
1442
- deleteAssocStmt.run(this.#projectPath, tagId);
1443
-
1444
- const stmt = this.#db.prepare(`
1445
- DELETE FROM task_tag_storage
1446
- WHERE project_path = ? AND id = ?
1447
- `);
1448
- stmt.run(this.#projectPath, tagId);
1449
- }
1450
-
1451
- async listTags(): Promise<ListTagsResult> {
1452
- const query = this.#db.query(`
1453
- SELECT id, created_at, name, color
1454
- FROM task_tag_storage
1455
- WHERE project_path = ?
1456
- ORDER BY name ASC
1457
- `);
1458
-
1459
- const rows = query.all(this.#projectPath) as TagRow[];
1460
-
1461
- return {
1462
- tags: rows.map(toTag),
1463
- };
1464
- }
1465
-
1466
- async addTagToTask(taskId: string, tagId: string): Promise<void> {
1467
- // Verify task and tag exist
1468
- const task = await this.get(taskId);
1469
- if (!task) {
1470
- throw new TaskNotFoundError();
1471
- }
1472
- await this.getTag(tagId);
1473
-
1474
- const stmt = this.#db.prepare(`
1475
- INSERT OR IGNORE INTO task_tag_association_storage (
1476
- project_path, task_id, tag_id
1477
- ) VALUES (?, ?, ?)
1478
- `);
1479
-
1480
- stmt.run(this.#projectPath, taskId, tagId);
1481
- }
1482
-
1483
- async removeTagFromTask(taskId: string, tagId: string): Promise<void> {
1484
- const stmt = this.#db.prepare(`
1485
- DELETE FROM task_tag_association_storage
1486
- WHERE project_path = ? AND task_id = ? AND tag_id = ?
1487
- `);
1488
-
1489
- stmt.run(this.#projectPath, taskId, tagId);
1490
- }
1491
-
1492
- async listTagsForTask(taskId: string): Promise<Tag[]> {
1493
- const query = this.#db.query(`
1494
- SELECT t.id, t.created_at, t.name, t.color
1495
- FROM task_tag_storage t
1496
- INNER JOIN task_tag_association_storage a ON t.id = a.tag_id AND t.project_path = a.project_path
1497
- WHERE a.project_path = ? AND a.task_id = ?
1498
- ORDER BY t.name ASC
1499
- `);
1500
-
1501
- const rows = query.all(this.#projectPath, taskId) as TagRow[];
1502
-
1503
- return rows.map(toTag);
1504
- }
1505
-
1506
- // Attachment methods — not supported in local storage
1507
-
1508
- async uploadAttachment(
1509
- _taskId: string,
1510
- _params: CreateAttachmentParams
1511
- ): Promise<PresignUploadResponse> {
1512
- throw new AttachmentNotSupportedError();
1513
- }
1514
-
1515
- async confirmAttachment(_attachmentId: string): Promise<Attachment> {
1516
- throw new AttachmentNotSupportedError();
1517
- }
1518
-
1519
- async downloadAttachment(_attachmentId: string): Promise<PresignDownloadResponse> {
1520
- throw new AttachmentNotSupportedError();
1521
- }
1522
-
1523
- async listAttachments(_taskId: string): Promise<ListAttachmentsResult> {
1524
- throw new AttachmentNotSupportedError();
1525
- }
1526
-
1527
- async deleteAttachment(_attachmentId: string): Promise<void> {
1528
- throw new AttachmentNotSupportedError();
1529
- }
1530
-
1531
- async listUsers(): Promise<ListUsersResult> {
1532
- const query = this.#db.query(`
1533
- SELECT id, name, type
1534
- FROM task_user_storage
1535
- WHERE project_path = ?
1536
- ORDER BY name ASC
1537
- `);
1538
-
1539
- const rows = query.all(this.#projectPath) as Array<{
1540
- id: string;
1541
- name: string;
1542
- type: 'human' | 'agent';
1543
- }>;
1544
-
1545
- return {
1546
- users: rows.map((row) => ({
1547
- id: row.id,
1548
- name: row.name,
1549
- type: row.type,
1550
- })),
1551
- };
1552
- }
1553
-
1554
- async listProjects(): Promise<ListProjectsResult> {
1555
- const query = this.#db.query(`
1556
- SELECT id, name
1557
- FROM task_project_storage
1558
- WHERE project_path = ?
1559
- ORDER BY name ASC
1560
- `);
1561
-
1562
- const rows = query.all(this.#projectPath) as Array<{
1563
- id: string;
1564
- name: string;
1565
- }>;
1566
-
1567
- return {
1568
- projects: rows.map((row) => ({
1569
- id: row.id,
1570
- name: row.name,
1571
- })),
1572
- };
1573
- }
1574
-
1575
- async createUser(params: { name: string; type?: 'human' | 'agent' }): Promise<UserEntityRef> {
1576
- const trimmedName = params?.name?.trim();
1577
- if (!trimmedName) {
1578
- throw new UserNameRequiredError();
1579
- }
1580
-
1581
- const id = generateUserId();
1582
- const timestamp = now();
1583
- const type = params.type ?? 'human';
1584
-
1585
- const stmt = this.#db.prepare(`
1586
- INSERT INTO task_user_storage (
1587
- project_path, id, name, type, created_at
1588
- ) VALUES (?, ?, ?, ?, ?)
1589
- `);
1590
-
1591
- stmt.run(this.#projectPath, id, trimmedName, type, timestamp);
1592
-
1593
- return { id, name: trimmedName, type };
1594
- }
1595
-
1596
- async getUser(userId: string): Promise<UserEntityRef> {
1597
- const query = this.#db.query(`
1598
- SELECT id, name, type
1599
- FROM task_user_storage
1600
- WHERE project_path = ? AND id = ?
1601
- `);
1602
-
1603
- const row = query.get(this.#projectPath, userId) as {
1604
- id: string;
1605
- name: string;
1606
- type: 'human' | 'agent';
1607
- } | null;
1608
-
1609
- if (!row) {
1610
- throw new UserNotFoundError();
1611
- }
1612
-
1613
- return { id: row.id, name: row.name, type: row.type };
1614
- }
1615
-
1616
- async deleteUser(userId: string): Promise<void> {
1617
- const stmt = this.#db.prepare(`
1618
- DELETE FROM task_user_storage
1619
- WHERE project_path = ? AND id = ?
1620
- `);
1621
-
1622
- stmt.run(this.#projectPath, userId);
1623
- }
1624
-
1625
- async createProject(params: { name: string }): Promise<EntityRef> {
1626
- const trimmedName = params?.name?.trim();
1627
- if (!trimmedName) {
1628
- throw new ProjectNameRequiredError();
1629
- }
1630
-
1631
- const id = generateProjectId();
1632
- const timestamp = now();
1633
-
1634
- const stmt = this.#db.prepare(`
1635
- INSERT INTO task_project_storage (
1636
- project_path, id, name, created_at
1637
- ) VALUES (?, ?, ?, ?)
1638
- `);
1639
-
1640
- stmt.run(this.#projectPath, id, trimmedName, timestamp);
1641
-
1642
- return { id, name: trimmedName };
1643
- }
1644
-
1645
- async getProject(projectId: string): Promise<EntityRef> {
1646
- const query = this.#db.query(`
1647
- SELECT id, name
1648
- FROM task_project_storage
1649
- WHERE project_path = ? AND id = ?
1650
- `);
1651
-
1652
- const row = query.get(this.#projectPath, projectId) as {
1653
- id: string;
1654
- name: string;
1655
- } | null;
1656
-
1657
- if (!row) {
1658
- throw new ProjectNotFoundError();
1659
- }
1660
-
1661
- return { id: row.id, name: row.name };
1662
- }
1663
-
1664
- async deleteProject(projectId: string): Promise<void> {
1665
- const stmt = this.#db.prepare(`
1666
- DELETE FROM task_project_storage
1667
- WHERE project_path = ? AND id = ?
1668
- `);
1669
-
1670
- stmt.run(this.#projectPath, projectId);
1671
- }
1672
-
1673
- async getActivity(params?: TaskActivityParams): Promise<TaskActivityResult> {
1674
- const days = Math.min(365, Math.max(7, params?.days ?? 90));
1675
- const activity = [];
1676
- const now = new Date();
1677
-
1678
- for (let i = days - 1; i >= 0; i--) {
1679
- const date = new Date(now);
1680
- date.setDate(date.getDate() - i);
1681
- const dateStr = date.toISOString().slice(0, 10);
1682
-
1683
- const row = this.#db
1684
- .prepare(
1685
- `SELECT
1686
- COALESCE(SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END), 0) as open,
1687
- COALESCE(SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END), 0) as in_progress,
1688
- COALESCE(SUM(CASE WHEN status IN ('done', 'closed') THEN 1 ELSE 0 END), 0) as done,
1689
- COALESCE(SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END), 0) as cancelled
1690
- FROM task_storage
1691
- WHERE project_path = ? AND date(created_at) = ?`
1692
- )
1693
- .get(this.#projectPath, dateStr) as {
1694
- open: number;
1695
- in_progress: number;
1696
- done: number;
1697
- cancelled: number;
1698
- };
1699
-
1700
- activity.push({
1701
- date: dateStr,
1702
- open: row.open,
1703
- inProgress: row.in_progress,
1704
- done: row.done,
1705
- cancelled: row.cancelled,
1706
- });
1707
- }
1708
-
1709
- return { activity, days };
1710
- }
1711
- }