@xopcai/xopc 0.0.122 → 0.0.124

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 (273) hide show
  1. package/README.md +28 -11
  2. package/README.zh-CN.md +28 -11
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/telegram/xopc.extension.json +1 -1
  5. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CK3s5cJo.js → agent-avatar-dicebear-B5uSVAyJ.js} +1 -1
  6. package/dist/gateway/static/root/assets/agents-BjIxt67Z.js +2 -0
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-CWsNHtTs.js → app-management-settings-panel-CYCONLIL.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-C4b8y6iZ.js → appearance-settings-Clw3ryKg.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-QICTeGjX.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-DAtNW3Ia.js → archive-plugin-CXUGRp2i.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-Dm-RY54y.js → attachment-load-Cmiub3Wg.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automation-suggestion-card-BKHewSVT.js → automation-suggestion-card-CU0-4nQ-.js} +1 -1
  13. package/dist/gateway/static/root/assets/automations-page-Car-jtiz.js +1 -0
  14. package/dist/gateway/static/root/assets/{binary-plugins-BG6Xnk71.js → binary-plugins-0Izz-N3p.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-DfndiO3h.js → block-editor-C1Kv0IkJ.js} +28 -28
  16. package/dist/gateway/static/root/assets/browser-settings-page-CMeBJzlS.js +8 -0
  17. package/dist/gateway/static/root/assets/{capability-presets-api-Cx93GLdw.js → capability-presets-api-BmQTM9Il.js} +1 -1
  18. package/dist/gateway/static/root/assets/capability-presets-settings-panel-DZEJEoCF.js +1 -0
  19. package/dist/gateway/static/root/assets/{channel-recipient-api-DdPTtEs8.js → channel-recipient-api-BeA2aVor.js} +1 -1
  20. package/dist/gateway/static/root/assets/channels-settings-kkW-xFau.js +1 -0
  21. package/dist/gateway/static/root/assets/connectors-page-BvaQvO5F.js +2 -0
  22. package/dist/gateway/static/root/assets/{directory-picker-path-field-D835EHJd.js → directory-picker-path-field-2rjzUu57.js} +1 -1
  23. package/dist/gateway/static/root/assets/{dreaming-settings-CCa1F7Fg.js → dreaming-settings-DnHO686d.js} +1 -1
  24. package/dist/gateway/static/root/assets/{extension-debug-page-W2ITCPSA.js → extension-debug-page-BGLRuWVN.js} +1 -1
  25. package/dist/gateway/static/root/assets/{extension-page-DrvUytvn.js → extension-page-D978ePp9.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-settings-page-qM8aLT5u.js → extension-settings-page-B6Ih4Swx.js} +1 -1
  27. package/dist/gateway/static/root/assets/{fetch-ovkvM8KQ.js → fetch-BU5S0Sn7.js} +1 -1
  28. package/dist/gateway/static/root/assets/{gateway-config-api-BB3biuD0.js → gateway-config-api-BIPGwrl1.js} +1 -1
  29. package/dist/gateway/static/root/assets/gateway-settings-CMEA3r5u.js +1 -0
  30. package/dist/gateway/static/root/assets/{gateway-startup-retry-CanQ8UcR.js → gateway-startup-retry-Cd0Zz30N.js} +1 -1
  31. package/dist/gateway/static/root/assets/{goal-create-dialog-CNqNdEkQ.js → goal-create-dialog-B-KHKeXN.js} +1 -1
  32. package/dist/gateway/static/root/assets/goal-detail-page-BxrSoWdj.js +1 -0
  33. package/dist/gateway/static/root/assets/{goals-config-api-BBOX6mUl.js → goals-config-api-B95KdGMj.js} +1 -1
  34. package/dist/gateway/static/root/assets/{goals-page-BP1kfvWv.js → goals-page-BTJzIl28.js} +2 -2
  35. package/dist/gateway/static/root/assets/{goals-settings-Bh3A7OZV.js → goals-settings-BJyEuLLm.js} +1 -1
  36. package/dist/gateway/static/root/assets/heartbeat-settings-wkOjYNQT.js +1 -0
  37. package/dist/gateway/static/root/assets/index-BLaXr6Ev.css +1 -0
  38. package/dist/gateway/static/root/assets/index-c_9TVr_C.js +191 -0
  39. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B4TKJHla.js → keyboard-shortcuts-settings-BT8T4DyC.js} +1 -1
  40. package/dist/gateway/static/root/assets/logs-page-CrxBJUgI.js +2 -0
  41. package/dist/gateway/static/root/assets/{markdown-split-DcFtdLci.js → markdown-split-Dv66bO2b.js} +1 -1
  42. package/dist/gateway/static/root/assets/{media-plugins-B6SmeLVB.js → media-plugins-BhIQU5yS.js} +1 -1
  43. package/dist/gateway/static/root/assets/messages-O6TAtdFC.js +3 -0
  44. package/dist/gateway/static/root/assets/models-hub-panel-DOOpz6y1.js +2 -0
  45. package/dist/gateway/static/root/assets/notes-page-Br16pK9v.js +1 -0
  46. package/dist/gateway/static/root/assets/notes-workbench-PSJAi0ZC.js +2 -0
  47. package/dist/gateway/static/root/assets/{preview-open-alternatives-C29O19s7.js → preview-open-alternatives-DGzet1d7.js} +1 -1
  48. package/dist/gateway/static/root/assets/project-detail-page-CIGGgKuw.js +1 -0
  49. package/dist/gateway/static/root/assets/projects-page-B2grhsBt.js +1 -0
  50. package/dist/gateway/static/root/assets/remote-access-hub-CXMORxBD.js +1 -0
  51. package/dist/gateway/static/root/assets/{save-bar-controls-B4ad9GfB.js → save-bar-controls-Cqgb-jqa.js} +1 -1
  52. package/dist/gateway/static/root/assets/{schedule-field-DdYncfLR.js → schedule-field-DVZcI8Yt.js} +1 -1
  53. package/dist/gateway/static/root/assets/{schema-form-BMTrDE-7.js → schema-form-Bj7sOxC8.js} +1 -1
  54. package/dist/gateway/static/root/assets/sessions-page-Bi2wkBzU.js +1 -0
  55. package/dist/gateway/static/root/assets/settings-advanced-gate-B1L3G4IA.js +1 -0
  56. package/dist/gateway/static/root/assets/{settings-collapsible-section-MwTW2eK4.js → settings-collapsible-section-BmXn-8N6.js} +1 -1
  57. package/dist/gateway/static/root/assets/{settings-form-section-Dg6LBbcr.js → settings-form-section-BnqYMWHR.js} +1 -1
  58. package/dist/gateway/static/root/assets/settings-page-BfRSHJmO.js +1 -0
  59. package/dist/gateway/static/root/assets/{settings-page-layout-BX4T-OLF.js → settings-page-layout-CdWZU1hd.js} +1 -1
  60. package/dist/gateway/static/root/assets/{setup-status-panel-Ce1pcFtR.js → setup-status-panel-CWtPM7RK.js} +1 -1
  61. package/dist/gateway/static/root/assets/{share-preview-page-DWE-dUFh.js → share-preview-page-IB1v8i86.js} +1 -1
  62. package/dist/gateway/static/root/assets/shares-settings-ngbsBgij.js +1 -0
  63. package/dist/gateway/static/root/assets/skill-reload-api-BIe442Gv.js +1 -0
  64. package/dist/gateway/static/root/assets/skills-page-BfchZRVa.js +2 -0
  65. package/dist/gateway/static/root/assets/{system-settings-panel-Dd6Y35Cm.js → system-settings-panel-BxQ384RC.js} +1 -1
  66. package/dist/gateway/static/root/assets/{typed-models-lib-Cx6XV5A2.js → typed-models-lib-BkcpvFYO.js} +1 -1
  67. package/dist/gateway/static/root/assets/{use-channel-catalog-D4sMbmy7.js → use-channel-catalog-Cd0FaEQ4.js} +1 -1
  68. package/dist/gateway/static/root/assets/{user-profile-settings-panel-Bx-hjMQc.js → user-profile-settings-panel-DQ7NnRSY.js} +1 -1
  69. package/dist/gateway/static/root/assets/{utils-D7b_vZnv.js → utils-YKhVf7Ka.js} +1 -1
  70. package/dist/gateway/static/root/assets/voice-api-key-field-C5Ov8Uk_.js +1 -0
  71. package/dist/gateway/static/root/assets/{workflow-run-setup-panel--TdohF5N.js → workflow-run-setup-panel-BYD9BtOq.js} +2 -2
  72. package/dist/gateway/static/root/assets/{workflow-start-dialog-B0lavx_o.js → workflow-start-dialog-BNJVCWoS.js} +1 -1
  73. package/dist/gateway/static/root/assets/workflows-page-BYF29ANm.js +18 -0
  74. package/dist/gateway/static/root/index.html +5 -5
  75. package/dist/package.js +1 -1
  76. package/dist/src/agent/agent-manager.d.ts +6 -1
  77. package/dist/src/agent/agent-manager.js +28 -12
  78. package/dist/src/agent/agent-manager.js.map +1 -1
  79. package/dist/src/agent/context/workspace-templates/AGENTS.md +1 -1
  80. package/dist/src/agent/embedded/xopc-tools-bridge.js +11 -7
  81. package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
  82. package/dist/src/agent/lifecycle/progress.js +3 -1
  83. package/dist/src/agent/lifecycle/progress.js.map +1 -1
  84. package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +2 -2
  85. package/dist/src/agent/lifecycle/timeout-wrapper.js +4 -4
  86. package/dist/src/agent/lifecycle/timeout-wrapper.js.map +1 -1
  87. package/dist/src/agent/memory/summary-generator.d.ts +1 -1
  88. package/dist/src/agent/memory/summary-generator.js +1 -2
  89. package/dist/src/agent/memory/summary-generator.js.map +1 -1
  90. package/dist/src/agent/middleware/index.d.ts +1 -1
  91. package/dist/src/agent/middleware/self-verify.d.ts +44 -14
  92. package/dist/src/agent/middleware/self-verify.js +214 -44
  93. package/dist/src/agent/middleware/self-verify.js.map +1 -1
  94. package/dist/src/agent/orchestration/agent-event-handler.js +57 -15
  95. package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
  96. package/dist/src/agent/prompt/sections/tooling.js +18 -13
  97. package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
  98. package/dist/src/agent/prompt/system-reminder.js +2 -2
  99. package/dist/src/agent/prompt/system-reminder.js.map +1 -1
  100. package/dist/src/agent/sandbox/sanitize-env-vars.d.ts +2 -2
  101. package/dist/src/agent/sandbox/sanitize-env-vars.js +2 -2
  102. package/dist/src/agent/sandbox/sanitize-env-vars.js.map +1 -1
  103. package/dist/src/agent/sandbox/types.d.ts +2 -2
  104. package/dist/src/agent/sandbox/types.js.map +1 -1
  105. package/dist/src/agent/service/btw-query.js +1 -1
  106. package/dist/src/agent/service/btw-query.js.map +1 -1
  107. package/dist/src/agent/service/process-direct-one-shot.js +6 -2
  108. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  109. package/dist/src/agent/service/process-direct-streaming.js +12 -7
  110. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  111. package/dist/src/agent/service.js +2 -1
  112. package/dist/src/agent/service.js.map +1 -1
  113. package/dist/src/agent/skills/skill-tool-gating.js +1 -1
  114. package/dist/src/agent/skills/skill-tool-gating.js.map +1 -1
  115. package/dist/src/agent/starter-agents.js +9 -9
  116. package/dist/src/agent/starter-agents.js.map +1 -1
  117. package/dist/src/agent/tools/apply-patch.d.ts +25 -0
  118. package/dist/src/agent/tools/apply-patch.js +256 -0
  119. package/dist/src/agent/tools/apply-patch.js.map +1 -0
  120. package/dist/src/agent/tools/delegate-tool.d.ts +1 -1
  121. package/dist/src/agent/tools/delegate-tool.js +3 -3
  122. package/dist/src/agent/tools/delegate-tool.js.map +1 -1
  123. package/dist/src/agent/tools/exec-command.d.ts +30 -0
  124. package/dist/src/agent/tools/exec-command.js +224 -0
  125. package/dist/src/agent/tools/exec-command.js.map +1 -0
  126. package/dist/src/agent/tools/execute-code-tool.js +7 -4
  127. package/dist/src/agent/tools/execute-code-tool.js.map +1 -1
  128. package/dist/src/agent/tools/factory.d.ts +3 -3
  129. package/dist/src/agent/tools/factory.js +8 -9
  130. package/dist/src/agent/tools/factory.js.map +1 -1
  131. package/dist/src/agent/tools/goal-tool.d.ts +5 -1
  132. package/dist/src/agent/tools/goal-tool.js +8 -2
  133. package/dist/src/agent/tools/goal-tool.js.map +1 -1
  134. package/dist/src/agent/tools/index.d.ts +4 -3
  135. package/dist/src/agent/tools/index.js +5 -4
  136. package/dist/src/agent/tools/skills-tools.d.ts +1 -1
  137. package/dist/src/agent/tools/skills-tools.js.map +1 -1
  138. package/dist/src/agent/tools/update-plan-tool.d.ts +11 -0
  139. package/dist/src/agent/tools/update-plan-tool.js +72 -0
  140. package/dist/src/agent/tools/update-plan-tool.js.map +1 -0
  141. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  142. package/dist/src/agent/workflow/builtins/debug-incident.js +1 -1
  143. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  144. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  145. package/dist/src/agent/workflow/builtins/pr-review.js +1 -1
  146. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  147. package/dist/src/agent/workflow/builtins/release-check.d.ts +1 -1
  148. package/dist/src/agent/workflow/builtins/release-check.js +1 -1
  149. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -1
  150. package/dist/src/agent/workflow/step-labels.js +7 -3
  151. package/dist/src/agent/workflow/step-labels.js.map +1 -1
  152. package/dist/src/chat-commands/agent-edit.js +1 -1
  153. package/dist/src/chat-commands/agent-edit.js.map +1 -1
  154. package/dist/src/chat-commands/builtins/goal.js +4 -1
  155. package/dist/src/chat-commands/builtins/goal.js.map +1 -1
  156. package/dist/src/chat-commands/builtins/review.d.ts +1 -0
  157. package/dist/src/chat-commands/builtins/review.js +167 -0
  158. package/dist/src/chat-commands/builtins/review.js.map +1 -0
  159. package/dist/src/chat-commands/index.d.ts +1 -0
  160. package/dist/src/chat-commands/index.js +3 -1
  161. package/dist/src/chat-commands/index.js.map +1 -1
  162. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  163. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  164. package/dist/src/config/agent-profile.js +1 -1
  165. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  166. package/dist/src/gateway/agent-builtin-tools.js +3 -2
  167. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  168. package/dist/src/gateway/agents-admin.js +1 -1
  169. package/dist/src/gateway/chat-stream/mapper.d.ts +6 -0
  170. package/dist/src/gateway/chat-stream/mapper.js +149 -6
  171. package/dist/src/gateway/chat-stream/mapper.js.map +1 -1
  172. package/dist/src/gateway/chat-stream/protocol.d.ts +51 -1
  173. package/dist/src/gateway/hono/routes/commands-skills.js +21 -0
  174. package/dist/src/gateway/hono/routes/commands-skills.js.map +1 -1
  175. package/dist/src/gateway/hono/routes/index.js +2 -0
  176. package/dist/src/gateway/hono/routes/index.js.map +1 -1
  177. package/dist/src/gateway/hono/routes/lazy-bundles.js +1 -0
  178. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  179. package/dist/src/gateway/hono/routes/projects.js +136 -14
  180. package/dist/src/gateway/hono/routes/projects.js.map +1 -1
  181. package/dist/src/gateway/hono/routes/search.d.ts +3 -0
  182. package/dist/src/gateway/hono/routes/search.js +47 -0
  183. package/dist/src/gateway/hono/routes/search.js.map +1 -0
  184. package/dist/src/gateway/security/dangerous-tools.js +1 -1
  185. package/dist/src/gateway/security/dangerous-tools.js.map +1 -1
  186. package/dist/src/infra/update-restart.js +1 -1
  187. package/dist/src/projects/index.d.ts +3 -0
  188. package/dist/src/projects/index.js +4 -1
  189. package/dist/src/projects/project-agent-suggestion.d.ts +14 -0
  190. package/dist/src/projects/project-agent-suggestion.js +19 -0
  191. package/dist/src/projects/project-agent-suggestion.js.map +1 -0
  192. package/dist/src/projects/project-kind.d.ts +13 -0
  193. package/dist/src/projects/project-kind.js +147 -0
  194. package/dist/src/projects/project-kind.js.map +1 -0
  195. package/dist/src/projects/project-service.d.ts +15 -0
  196. package/dist/src/projects/project-service.js +96 -3
  197. package/dist/src/projects/project-service.js.map +1 -1
  198. package/dist/src/projects/project-store.js +32 -8
  199. package/dist/src/projects/project-store.js.map +1 -1
  200. package/dist/src/projects/types.d.ts +4 -1
  201. package/dist/src/projects/workspace-project.d.ts +22 -0
  202. package/dist/src/projects/workspace-project.js +143 -0
  203. package/dist/src/projects/workspace-project.js.map +1 -0
  204. package/dist/src/review/review-git.d.ts +48 -0
  205. package/dist/src/review/review-git.js +333 -0
  206. package/dist/src/review/review-git.js.map +1 -0
  207. package/dist/src/review/review-types.d.ts +22 -0
  208. package/dist/src/review/review-types.js +29 -0
  209. package/dist/src/review/review-types.js.map +1 -0
  210. package/dist/src/session/session-context-for-llm.js +151 -0
  211. package/dist/src/session/session-context-for-llm.js.map +1 -1
  212. package/dist/src/storage/sqlite/migrations/021_projects_fts.sql +21 -0
  213. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  214. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  215. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  216. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  217. package/dist/src/storage/sqlite/schema.js +1 -1
  218. package/dist/src/tui/backends/embedded-backend.d.ts +8 -1
  219. package/dist/src/tui/backends/embedded-backend.js +60 -1
  220. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  221. package/dist/src/tui/backends/gateway-sse-backend.d.ts +9 -1
  222. package/dist/src/tui/backends/gateway-sse-backend.js +29 -0
  223. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  224. package/dist/src/tui/components/assistant-message.js +35 -0
  225. package/dist/src/tui/components/assistant-message.js.map +1 -1
  226. package/dist/src/tui/tui-agent-events.js +25 -0
  227. package/dist/src/tui/tui-agent-events.js.map +1 -1
  228. package/dist/src/tui/tui-backend.d.ts +20 -0
  229. package/dist/src/tui/tui-commands.d.ts +1 -0
  230. package/dist/src/tui/tui-commands.js +110 -4
  231. package/dist/src/tui/tui-commands.js.map +1 -1
  232. package/dist/src/tui/tui-picker-overlay.d.ts +2 -0
  233. package/dist/src/tui/tui-picker-overlay.js +153 -1
  234. package/dist/src/tui/tui-picker-overlay.js.map +1 -1
  235. package/dist/src/tui/tui.js +36 -2
  236. package/dist/src/tui/tui.js.map +1 -1
  237. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  238. package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
  239. package/package.json +1 -1
  240. package/dist/gateway/static/root/assets/agents-SsQrj9B-.js +0 -2
  241. package/dist/gateway/static/root/assets/apps-page-CYb6fame.js +0 -1
  242. package/dist/gateway/static/root/assets/automations-page-DpOOq1nk.js +0 -1
  243. package/dist/gateway/static/root/assets/browser-settings-page-9nXxgh4u.js +0 -8
  244. package/dist/gateway/static/root/assets/capability-presets-settings-panel-D528_YFy.js +0 -1
  245. package/dist/gateway/static/root/assets/channels-settings-Ck-3h5lI.js +0 -1
  246. package/dist/gateway/static/root/assets/connectors-page-DL-fAqE_.js +0 -2
  247. package/dist/gateway/static/root/assets/gateway-settings-mmmnIH27.js +0 -1
  248. package/dist/gateway/static/root/assets/goal-detail-page-Ceu5zrN6.js +0 -1
  249. package/dist/gateway/static/root/assets/heartbeat-settings-YoYgKlfX.js +0 -1
  250. package/dist/gateway/static/root/assets/index-DdqSO9R2.js +0 -191
  251. package/dist/gateway/static/root/assets/index-jGB-2nI-.css +0 -1
  252. package/dist/gateway/static/root/assets/logs-page-BgVk5DvZ.js +0 -2
  253. package/dist/gateway/static/root/assets/messages-B5O4OIEo.js +0 -3
  254. package/dist/gateway/static/root/assets/models-hub-panel-BU0x8PGP.js +0 -2
  255. package/dist/gateway/static/root/assets/notes-page-D6JYVP4I.js +0 -1
  256. package/dist/gateway/static/root/assets/notes-workbench-mqRJYnJS.js +0 -2
  257. package/dist/gateway/static/root/assets/project-detail-page-DOTgzoeh.js +0 -1
  258. package/dist/gateway/static/root/assets/projects-page-DGUIb4y5.js +0 -1
  259. package/dist/gateway/static/root/assets/remote-access-hub-DVKKUj3u.js +0 -1
  260. package/dist/gateway/static/root/assets/sessions-page-BVbzUWxW.js +0 -1
  261. package/dist/gateway/static/root/assets/settings-advanced-gate-DrNNoSqE.js +0 -1
  262. package/dist/gateway/static/root/assets/settings-page-CMDqgeMR.js +0 -1
  263. package/dist/gateway/static/root/assets/shares-settings-aKSraR8l.js +0 -1
  264. package/dist/gateway/static/root/assets/skill-reload-api-CS-FU_B-.js +0 -1
  265. package/dist/gateway/static/root/assets/skills-page-BaF3u0LA.js +0 -2
  266. package/dist/gateway/static/root/assets/voice-api-key-field-CkhbRt1q.js +0 -1
  267. package/dist/gateway/static/root/assets/workflows-page-CZR05Bu_.js +0 -18
  268. package/dist/src/agent/tools/edit.d.ts +0 -14
  269. package/dist/src/agent/tools/edit.js +0 -132
  270. package/dist/src/agent/tools/edit.js.map +0 -1
  271. package/dist/src/agent/tools/shell.d.ts +0 -15
  272. package/dist/src/agent/tools/shell.js +0 -158
  273. package/dist/src/agent/tools/shell.js.map +0 -1
@@ -0,0 +1,14 @@
1
+ import type { Config } from '../config/schema.js';
2
+ import { type ProjectKindInference } from './project-kind.js';
3
+ export declare const CODING_PROJECT_AGENT_ID = "coder";
4
+ export declare function suggestProjectDefaultAgentId(input: {
5
+ config: Config;
6
+ kind: ProjectKindInference['kind'];
7
+ }): string | undefined;
8
+ export declare function inferSuggestedProjectDefaultAgentId(input: {
9
+ config: Config;
10
+ name?: string | null;
11
+ description?: string | null;
12
+ workspaceRoot?: string | null;
13
+ projectKind?: string | null;
14
+ }): string | undefined;
@@ -0,0 +1,19 @@
1
+ import { agentExists, init_resolve_route } from "../routing/resolve-route.js";
2
+ import { inferProjectKind } from "./project-kind.js";
3
+ //#region src/projects/project-agent-suggestion.ts
4
+ init_resolve_route();
5
+ const CODING_PROJECT_AGENT_ID = "coder";
6
+ function suggestProjectDefaultAgentId(input) {
7
+ if (input.kind === "coding" && agentExists("coder", input.config)) return CODING_PROJECT_AGENT_ID;
8
+ }
9
+ function inferSuggestedProjectDefaultAgentId(input) {
10
+ const inferred = inferProjectKind(input);
11
+ return suggestProjectDefaultAgentId({
12
+ config: input.config,
13
+ kind: inferred.kind
14
+ });
15
+ }
16
+ //#endregion
17
+ export { CODING_PROJECT_AGENT_ID, inferSuggestedProjectDefaultAgentId, suggestProjectDefaultAgentId };
18
+
19
+ //# sourceMappingURL=project-agent-suggestion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-agent-suggestion.js","names":[],"sources":["../../../src/projects/project-agent-suggestion.ts"],"sourcesContent":["import type { Config } from '../config/schema.js';\nimport { agentExists } from '../routing/resolve-route.js';\nimport { inferProjectKind, type ProjectKindInference } from './project-kind.js';\n\nexport const CODING_PROJECT_AGENT_ID = 'coder';\n\nexport function suggestProjectDefaultAgentId(input: {\n config: Config;\n kind: ProjectKindInference['kind'];\n}): string | undefined {\n if (input.kind === 'coding' && agentExists(CODING_PROJECT_AGENT_ID, input.config)) {\n return CODING_PROJECT_AGENT_ID;\n }\n return undefined;\n}\n\nexport function inferSuggestedProjectDefaultAgentId(input: {\n config: Config;\n name?: string | null;\n description?: string | null;\n workspaceRoot?: string | null;\n projectKind?: string | null;\n}): string | undefined {\n const inferred = inferProjectKind(input);\n return suggestProjectDefaultAgentId({ config: input.config, kind: inferred.kind });\n}\n"],"mappings":";;;oBAC0D;AAG1D,MAAa,0BAA0B;AAEvC,SAAgB,6BAA6B,OAGtB;AACrB,KAAI,MAAM,SAAS,YAAY,YAAA,SAAqC,MAAM,OAAO,CAC/E,QAAO;;AAKX,SAAgB,oCAAoC,OAM7B;CACrB,MAAM,WAAW,iBAAiB,MAAM;AACxC,QAAO,6BAA6B;EAAE,QAAQ,MAAM;EAAQ,MAAM,SAAS;EAAM,CAAC"}
@@ -0,0 +1,13 @@
1
+ export type ProjectKind = 'coding' | 'general' | 'unknown';
2
+ export type ProjectKindOverride = 'auto' | 'coding' | 'general';
3
+ export type ProjectKindInference = {
4
+ kind: ProjectKind;
5
+ confidence: number;
6
+ reasons: string[];
7
+ };
8
+ export declare function inferProjectKind(input: {
9
+ name?: string | null;
10
+ description?: string | null;
11
+ workspaceRoot?: string | null;
12
+ projectKind?: string | null;
13
+ }): ProjectKindInference;
@@ -0,0 +1,147 @@
1
+ import { canonicalWorkspacePath } from "./workspace-project.js";
2
+ import { join } from "node:path";
3
+ import { existsSync, readdirSync, statSync } from "node:fs";
4
+ //#region src/projects/project-kind.ts
5
+ const STRONG_CODE_MARKERS = [
6
+ ".git",
7
+ "package.json",
8
+ "pnpm-lock.yaml",
9
+ "yarn.lock",
10
+ "package-lock.json",
11
+ "tsconfig.json",
12
+ "go.mod",
13
+ "Cargo.toml",
14
+ "pyproject.toml",
15
+ "requirements.txt",
16
+ "pom.xml",
17
+ "build.gradle",
18
+ "Makefile",
19
+ "Dockerfile",
20
+ "deno.json",
21
+ "deno.jsonc",
22
+ "composer.json",
23
+ "Gemfile"
24
+ ];
25
+ const WEAK_CODE_DIRECTORIES = [
26
+ "src",
27
+ "app",
28
+ "lib",
29
+ "tests",
30
+ "test",
31
+ "__tests__",
32
+ ".github"
33
+ ];
34
+ const CODE_EXTENSIONS = new Set([
35
+ ".c",
36
+ ".cc",
37
+ ".cpp",
38
+ ".cs",
39
+ ".css",
40
+ ".go",
41
+ ".h",
42
+ ".hpp",
43
+ ".html",
44
+ ".java",
45
+ ".js",
46
+ ".jsx",
47
+ ".kt",
48
+ ".mjs",
49
+ ".php",
50
+ ".py",
51
+ ".rb",
52
+ ".rs",
53
+ ".sh",
54
+ ".swift",
55
+ ".ts",
56
+ ".tsx"
57
+ ]);
58
+ function normalizeOverride(value) {
59
+ const normalized = value?.trim().toLowerCase();
60
+ return normalized === "coding" || normalized === "general" ? normalized : "auto";
61
+ }
62
+ function hasSourceExtension(name) {
63
+ const lower = name.toLowerCase();
64
+ for (const extension of CODE_EXTENSIONS) if (lower.endsWith(extension)) return true;
65
+ return false;
66
+ }
67
+ function readDirectoryNames(pathValue) {
68
+ try {
69
+ return readdirSync(pathValue).slice(0, 200);
70
+ } catch {
71
+ return [];
72
+ }
73
+ }
74
+ function directoryExists(pathValue) {
75
+ try {
76
+ return statSync(pathValue).isDirectory();
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
81
+ function inferProjectKind(input) {
82
+ const override = normalizeOverride(input.projectKind);
83
+ if (override === "coding") return {
84
+ kind: "coding",
85
+ confidence: 1,
86
+ reasons: ["selected coding project type"]
87
+ };
88
+ if (override === "general") return {
89
+ kind: "general",
90
+ confidence: 1,
91
+ reasons: ["selected general project type"]
92
+ };
93
+ const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);
94
+ const reasons = [];
95
+ let score = 0;
96
+ if (workspaceRoot && directoryExists(workspaceRoot)) {
97
+ for (const marker of STRONG_CODE_MARKERS) if (existsSync(join(workspaceRoot, marker))) {
98
+ score += 4;
99
+ reasons.push(`found ${marker}`);
100
+ break;
101
+ }
102
+ for (const dir of WEAK_CODE_DIRECTORIES) if (directoryExists(join(workspaceRoot, dir))) {
103
+ score += 1;
104
+ reasons.push(`found ${dir}/`);
105
+ break;
106
+ }
107
+ if (readDirectoryNames(workspaceRoot).some(hasSourceExtension)) {
108
+ score += 2;
109
+ reasons.push("found source files");
110
+ } else for (const dir of [
111
+ "src",
112
+ "app",
113
+ "lib"
114
+ ]) {
115
+ const childDir = join(workspaceRoot, dir);
116
+ if (directoryExists(childDir) && readDirectoryNames(childDir).some(hasSourceExtension)) {
117
+ score += 2;
118
+ reasons.push(`found source files in ${dir}/`);
119
+ break;
120
+ }
121
+ }
122
+ }
123
+ if (score >= 4) return {
124
+ kind: "coding",
125
+ confidence: .9,
126
+ reasons
127
+ };
128
+ if (score >= 3) return {
129
+ kind: "coding",
130
+ confidence: .75,
131
+ reasons
132
+ };
133
+ if (workspaceRoot) return {
134
+ kind: "general",
135
+ confidence: .55,
136
+ reasons
137
+ };
138
+ return {
139
+ kind: "unknown",
140
+ confidence: .25,
141
+ reasons
142
+ };
143
+ }
144
+ //#endregion
145
+ export { inferProjectKind };
146
+
147
+ //# sourceMappingURL=project-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-kind.js","names":[],"sources":["../../../src/projects/project-kind.ts"],"sourcesContent":["import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { canonicalWorkspacePath } from './workspace-project.js';\n\nexport type ProjectKind = 'coding' | 'general' | 'unknown';\nexport type ProjectKindOverride = 'auto' | 'coding' | 'general';\n\nexport type ProjectKindInference = {\n kind: ProjectKind;\n confidence: number;\n reasons: string[];\n};\n\nconst STRONG_CODE_MARKERS = [\n '.git',\n 'package.json',\n 'pnpm-lock.yaml',\n 'yarn.lock',\n 'package-lock.json',\n 'tsconfig.json',\n 'go.mod',\n 'Cargo.toml',\n 'pyproject.toml',\n 'requirements.txt',\n 'pom.xml',\n 'build.gradle',\n 'Makefile',\n 'Dockerfile',\n 'deno.json',\n 'deno.jsonc',\n 'composer.json',\n 'Gemfile',\n] as const;\n\nconst WEAK_CODE_DIRECTORIES = ['src', 'app', 'lib', 'tests', 'test', '__tests__', '.github'] as const;\nconst CODE_EXTENSIONS = new Set([\n '.c',\n '.cc',\n '.cpp',\n '.cs',\n '.css',\n '.go',\n '.h',\n '.hpp',\n '.html',\n '.java',\n '.js',\n '.jsx',\n '.kt',\n '.mjs',\n '.php',\n '.py',\n '.rb',\n '.rs',\n '.sh',\n '.swift',\n '.ts',\n '.tsx',\n]);\n\nfunction normalizeOverride(value: string | null | undefined): ProjectKindOverride {\n const normalized = value?.trim().toLowerCase();\n return normalized === 'coding' || normalized === 'general' ? normalized : 'auto';\n}\n\nfunction hasSourceExtension(name: string): boolean {\n const lower = name.toLowerCase();\n for (const extension of CODE_EXTENSIONS) {\n if (lower.endsWith(extension)) return true;\n }\n return false;\n}\n\nfunction readDirectoryNames(pathValue: string): string[] {\n try {\n return readdirSync(pathValue).slice(0, 200);\n } catch {\n return [];\n }\n}\n\nfunction directoryExists(pathValue: string): boolean {\n try {\n return statSync(pathValue).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function inferProjectKind(input: {\n name?: string | null;\n description?: string | null;\n workspaceRoot?: string | null;\n projectKind?: string | null;\n}): ProjectKindInference {\n const override = normalizeOverride(input.projectKind);\n if (override === 'coding') return { kind: 'coding', confidence: 1, reasons: ['selected coding project type'] };\n if (override === 'general') return { kind: 'general', confidence: 1, reasons: ['selected general project type'] };\n\n const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);\n const reasons: string[] = [];\n let score = 0;\n\n if (workspaceRoot && directoryExists(workspaceRoot)) {\n for (const marker of STRONG_CODE_MARKERS) {\n if (existsSync(join(workspaceRoot, marker))) {\n score += 4;\n reasons.push(`found ${marker}`);\n break;\n }\n }\n\n for (const dir of WEAK_CODE_DIRECTORIES) {\n if (directoryExists(join(workspaceRoot, dir))) {\n score += 1;\n reasons.push(`found ${dir}/`);\n break;\n }\n }\n\n if (readDirectoryNames(workspaceRoot).some(hasSourceExtension)) {\n score += 2;\n reasons.push('found source files');\n } else {\n for (const dir of ['src', 'app', 'lib']) {\n const childDir = join(workspaceRoot, dir);\n if (directoryExists(childDir) && readDirectoryNames(childDir).some(hasSourceExtension)) {\n score += 2;\n reasons.push(`found source files in ${dir}/`);\n break;\n }\n }\n }\n }\n\n if (score >= 4) return { kind: 'coding', confidence: 0.9, reasons };\n if (score >= 3) return { kind: 'coding', confidence: 0.75, reasons };\n if (workspaceRoot) return { kind: 'general', confidence: 0.55, reasons };\n return { kind: 'unknown', confidence: 0.25, reasons };\n}\n"],"mappings":";;;;AAcA,MAAM,sBAAsB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,wBAAwB;CAAC;CAAO;CAAO;CAAO;CAAS;CAAQ;CAAa;CAAU;AAC5F,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,kBAAkB,OAAuD;CAChF,MAAM,aAAa,OAAO,MAAM,CAAC,aAAa;AAC9C,QAAO,eAAe,YAAY,eAAe,YAAY,aAAa;;AAG5E,SAAS,mBAAmB,MAAuB;CACjD,MAAM,QAAQ,KAAK,aAAa;AAChC,MAAK,MAAM,aAAa,gBACtB,KAAI,MAAM,SAAS,UAAU,CAAE,QAAO;AAExC,QAAO;;AAGT,SAAS,mBAAmB,WAA6B;AACvD,KAAI;AACF,SAAO,YAAY,UAAU,CAAC,MAAM,GAAG,IAAI;SACrC;AACN,SAAO,EAAE;;;AAIb,SAAS,gBAAgB,WAA4B;AACnD,KAAI;AACF,SAAO,SAAS,UAAU,CAAC,aAAa;SAClC;AACN,SAAO;;;AAIX,SAAgB,iBAAiB,OAKR;CACvB,MAAM,WAAW,kBAAkB,MAAM,YAAY;AACrD,KAAI,aAAa,SAAU,QAAO;EAAE,MAAM;EAAU,YAAY;EAAG,SAAS,CAAC,+BAA+B;EAAE;AAC9G,KAAI,aAAa,UAAW,QAAO;EAAE,MAAM;EAAW,YAAY;EAAG,SAAS,CAAC,gCAAgC;EAAE;CAEjH,MAAM,gBAAgB,uBAAuB,MAAM,cAAc;CACjE,MAAM,UAAoB,EAAE;CAC5B,IAAI,QAAQ;AAEZ,KAAI,iBAAiB,gBAAgB,cAAc,EAAE;AACnD,OAAK,MAAM,UAAU,oBACnB,KAAI,WAAW,KAAK,eAAe,OAAO,CAAC,EAAE;AAC3C,YAAS;AACT,WAAQ,KAAK,SAAS,SAAS;AAC/B;;AAIJ,OAAK,MAAM,OAAO,sBAChB,KAAI,gBAAgB,KAAK,eAAe,IAAI,CAAC,EAAE;AAC7C,YAAS;AACT,WAAQ,KAAK,SAAS,IAAI,GAAG;AAC7B;;AAIJ,MAAI,mBAAmB,cAAc,CAAC,KAAK,mBAAmB,EAAE;AAC9D,YAAS;AACT,WAAQ,KAAK,qBAAqB;QAElC,MAAK,MAAM,OAAO;GAAC;GAAO;GAAO;GAAM,EAAE;GACvC,MAAM,WAAW,KAAK,eAAe,IAAI;AACzC,OAAI,gBAAgB,SAAS,IAAI,mBAAmB,SAAS,CAAC,KAAK,mBAAmB,EAAE;AACtF,aAAS;AACT,YAAQ,KAAK,yBAAyB,IAAI,GAAG;AAC7C;;;;AAMR,KAAI,SAAS,EAAG,QAAO;EAAE,MAAM;EAAU,YAAY;EAAK;EAAS;AACnE,KAAI,SAAS,EAAG,QAAO;EAAE,MAAM;EAAU,YAAY;EAAM;EAAS;AACpE,KAAI,cAAe,QAAO;EAAE,MAAM;EAAW,YAAY;EAAM;EAAS;AACxE,QAAO;EAAE,MAAM;EAAW,YAAY;EAAM;EAAS"}
@@ -1,5 +1,6 @@
1
1
  import { ProjectStore } from './project-store.js';
2
2
  import type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';
3
+ import { type WorkspaceProjectMatch } from './workspace-project.js';
3
4
  export type ProjectSuggestion = {
4
5
  projectId: string;
5
6
  projectName: string;
@@ -9,10 +10,24 @@ export type ProjectSuggestion = {
9
10
  export declare class ProjectService {
10
11
  private readonly store;
11
12
  constructor(store?: ProjectStore);
13
+ private listAllProjects;
12
14
  create(input: CreateProjectInput): Project;
13
15
  get(id: string): Project | null;
14
16
  getBySlug(slug: string): Project | null;
15
17
  list(query?: ProjectListQuery): ProjectListResult;
18
+ findByWorkspaceRoot(workspaceRoot: string, options?: {
19
+ excludeProjectId?: string;
20
+ }): Project | null;
21
+ resolveForWorkspacePath(workspacePath: string): {
22
+ project: Project;
23
+ reason: 'exact' | 'contained';
24
+ } | null;
25
+ resolveOrCreateForWorkspacePath(input: {
26
+ workspacePath: string;
27
+ agentId?: string;
28
+ defaultAgentId?: string;
29
+ autoCreate?: boolean;
30
+ }): WorkspaceProjectMatch | null;
16
31
  update(id: string, input: UpdateProjectInput): Project;
17
32
  delete(id: string): void;
18
33
  getWithDetails(id: string): ProjectWithDetails | null;
@@ -3,16 +3,44 @@ import "../storage/sqlite/index.js";
3
3
  import { ProjectStore } from "./project-store.js";
4
4
  import { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from "./goal-bind.js";
5
5
  import { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from "./session-bind.js";
6
+ import { ProjectWorkspaceConflictError, ProjectWorkspaceMissingError, canonicalWorkspacePath, ensureWorkspaceDirectory, inferProjectNameFromWorkspaceRoot, isPathSameOrInsideWorkspace, isSafeAutoCreateWorkspaceRoot, resolveWorkspaceProjectRoot, workspaceDirectoryExists } from "./workspace-project.js";
6
7
  //#region src/projects/project-service.ts
7
8
  var ProjectService = class {
8
9
  constructor(store = new ProjectStore()) {
9
10
  this.store = store;
10
11
  }
12
+ listAllProjects() {
13
+ const items = [];
14
+ let offset = 0;
15
+ const limit = 500;
16
+ while (true) {
17
+ const page = this.list({
18
+ limit,
19
+ offset
20
+ });
21
+ items.push(...page.items);
22
+ if (!page.hasMore || page.items.length === 0) break;
23
+ offset += page.items.length;
24
+ }
25
+ return items;
26
+ }
11
27
  create(input) {
12
- const slug = input.slug?.trim() || this.store.generateSlug(input.name);
28
+ const rawWorkspaceRoot = input.workspaceRoot?.trim();
29
+ const workspaceRoot = rawWorkspaceRoot ? canonicalWorkspacePath(rawWorkspaceRoot) : void 0;
30
+ if (rawWorkspaceRoot && !workspaceRoot) throw new Error("Invalid workspace root");
31
+ if (workspaceRoot) {
32
+ const existing = this.findByWorkspaceRoot(workspaceRoot);
33
+ if (existing) throw new ProjectWorkspaceConflictError(existing);
34
+ if (!workspaceDirectoryExists(workspaceRoot)) if (input.createWorkspaceRoot === true) ensureWorkspaceDirectory(workspaceRoot);
35
+ else throw new ProjectWorkspaceMissingError(workspaceRoot);
36
+ }
37
+ const name = input.name?.trim() || inferProjectNameFromWorkspaceRoot(workspaceRoot) || "";
38
+ const slug = input.slug?.trim() || this.store.generateSlug(name);
13
39
  return this.store.create({
14
40
  ...input,
15
- slug
41
+ name,
42
+ slug,
43
+ workspaceRoot
16
44
  });
17
45
  }
18
46
  get(id) {
@@ -24,8 +52,73 @@ var ProjectService = class {
24
52
  list(query) {
25
53
  return this.store.list(query);
26
54
  }
55
+ findByWorkspaceRoot(workspaceRoot, options = {}) {
56
+ const target = canonicalWorkspacePath(workspaceRoot);
57
+ if (!target) return null;
58
+ for (const project of this.listAllProjects()) {
59
+ if (options.excludeProjectId && project.id === options.excludeProjectId) continue;
60
+ const projectRoot = canonicalWorkspacePath(project.workspaceRoot);
61
+ if (projectRoot && isPathSameOrInsideWorkspace(projectRoot, target) && isPathSameOrInsideWorkspace(target, projectRoot)) return project;
62
+ }
63
+ return null;
64
+ }
65
+ resolveForWorkspacePath(workspacePath) {
66
+ const target = canonicalWorkspacePath(workspacePath);
67
+ if (!target) return null;
68
+ let best = null;
69
+ for (const project of this.listAllProjects()) {
70
+ const root = canonicalWorkspacePath(project.workspaceRoot);
71
+ if (!root || !isPathSameOrInsideWorkspace(root, target)) continue;
72
+ const reason = isPathSameOrInsideWorkspace(root, target) && isPathSameOrInsideWorkspace(target, root) ? "exact" : "contained";
73
+ if (!best || root.length > best.root.length) best = {
74
+ project,
75
+ root,
76
+ reason
77
+ };
78
+ }
79
+ return best ? {
80
+ project: best.project,
81
+ reason: best.reason
82
+ } : null;
83
+ }
84
+ resolveOrCreateForWorkspacePath(input) {
85
+ const existing = this.resolveForWorkspacePath(input.workspacePath);
86
+ if (existing) return {
87
+ project: existing.project,
88
+ reason: existing.reason,
89
+ created: false
90
+ };
91
+ if (!input.autoCreate) return null;
92
+ const workspaceRoot = resolveWorkspaceProjectRoot(input.workspacePath) ?? canonicalWorkspacePath(input.workspacePath);
93
+ if (!workspaceRoot || !isSafeAutoCreateWorkspaceRoot(workspaceRoot)) return null;
94
+ const rootExisting = this.resolveForWorkspacePath(workspaceRoot);
95
+ if (rootExisting) return {
96
+ project: rootExisting.project,
97
+ reason: rootExisting.reason,
98
+ created: false
99
+ };
100
+ return {
101
+ project: this.create({
102
+ name: inferProjectNameFromWorkspaceRoot(workspaceRoot) ?? void 0,
103
+ workspaceRoot,
104
+ defaultAgentId: Object.hasOwn(input, "defaultAgentId") ? input.defaultAgentId : input.agentId
105
+ }),
106
+ reason: "auto_created",
107
+ created: true
108
+ };
109
+ }
27
110
  update(id, input) {
28
- return this.store.update(id, input);
111
+ const patch = { ...input };
112
+ if (input.workspaceRoot !== void 0 && input.workspaceRoot !== null && input.workspaceRoot.trim()) {
113
+ const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);
114
+ if (!workspaceRoot) throw new Error("Invalid workspace root");
115
+ const existing = this.findByWorkspaceRoot(workspaceRoot, { excludeProjectId: id });
116
+ if (existing) throw new ProjectWorkspaceConflictError(existing);
117
+ if (!workspaceDirectoryExists(workspaceRoot)) if (input.createWorkspaceRoot === true) ensureWorkspaceDirectory(workspaceRoot);
118
+ else throw new ProjectWorkspaceMissingError(workspaceRoot);
119
+ patch.workspaceRoot = workspaceRoot;
120
+ }
121
+ return this.store.update(id, patch);
29
122
  }
30
123
  delete(id) {
31
124
  this.store.delete(id);
@@ -1 +1 @@
1
- {"version":3,"file":"project-service.js","names":[],"sources":["../../../src/projects/project-service.ts"],"sourcesContent":["import { getSessionMetadata } from '../storage/sqlite/index.js';\nimport { ProjectStore } from './project-store.js';\nimport { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from './goal-bind.js';\nimport { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from './session-bind.js';\nimport type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';\n\nexport type ProjectSuggestion = {\n projectId: string;\n projectName: string;\n score: number;\n reason: string;\n};\n\nexport class ProjectService {\n constructor(private readonly store = new ProjectStore()) {}\n\n create(input: CreateProjectInput): Project {\n const slug = input.slug?.trim() || this.store.generateSlug(input.name);\n return this.store.create({ ...input, slug });\n }\n\n get(id: string): Project | null {\n return this.store.get(id);\n }\n\n getBySlug(slug: string): Project | null {\n return this.store.findBySlug(slug);\n }\n\n list(query?: ProjectListQuery): ProjectListResult {\n return this.store.list(query);\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n return this.store.update(id, input);\n }\n\n delete(id: string): void {\n this.store.delete(id);\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n return this.store.getWithDetails(id);\n }\n\n attachSession(sessionKey: string, projectId: string): void {\n bindSessionToProject(sessionKey, projectId);\n }\n\n detachSession(sessionKey: string): void {\n unbindSessionFromProject(sessionKey);\n }\n\n listSessionKeys(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectSessionKeys(projectId, limit, offset);\n }\n\n attachGoal(goalId: string, projectId: string): void {\n bindGoalToProject(goalId, projectId);\n }\n\n detachGoal(goalId: string): void {\n unbindGoalFromProject(goalId);\n }\n\n listGoalIds(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectGoalIds(projectId, limit, offset);\n }\n\n suggestProjectsForSession(sessionKey: string): ProjectSuggestion[] {\n const session = getSessionMetadata(sessionKey);\n if (!session) return [];\n const projects = this.list({ status: 'active', limit: 500 }).items;\n const haystack = [session.key, session.name, session.routing?.agentId, session.sourceChannel, ...(session.tags ?? [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n const suggestions: ProjectSuggestion[] = [];\n for (const project of projects) {\n let score = 0;\n const reasons: string[] = [];\n if (haystack.includes(project.slug.toLowerCase())) {\n score += 10;\n reasons.push('session metadata matches project slug');\n }\n for (const token of project.name.toLowerCase().split(/\\s+/).filter((t) => t.length >= 3)) {\n if (haystack.includes(token)) {\n score += 3;\n reasons.push(`matches ${token}`);\n break;\n }\n }\n if (score > 0) {\n suggestions.push({ projectId: project.id, projectName: project.name, score, reason: reasons.join(', ') });\n }\n }\n return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);\n }\n}\n"],"mappings":";;;;;;AAaA,IAAa,iBAAb,MAA4B;CAC1B,YAAY,QAAyB,IAAI,cAAc,EAAE;AAA5B,OAAA,QAAA;;CAE7B,OAAO,OAAoC;EACzC,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,KAAK,MAAM,aAAa,MAAM,KAAK;AACtE,SAAO,KAAK,MAAM,OAAO;GAAE,GAAG;GAAO;GAAM,CAAC;;CAG9C,IAAI,IAA4B;AAC9B,SAAO,KAAK,MAAM,IAAI,GAAG;;CAG3B,UAAU,MAA8B;AACtC,SAAO,KAAK,MAAM,WAAW,KAAK;;CAGpC,KAAK,OAA6C;AAChD,SAAO,KAAK,MAAM,KAAK,MAAM;;CAG/B,OAAO,IAAY,OAAoC;AACrD,SAAO,KAAK,MAAM,OAAO,IAAI,MAAM;;CAGrC,OAAO,IAAkB;AACvB,OAAK,MAAM,OAAO,GAAG;;CAGvB,eAAe,IAAuC;AACpD,SAAO,KAAK,MAAM,eAAe,GAAG;;CAGtC,cAAc,YAAoB,WAAyB;AACzD,uBAAqB,YAAY,UAAU;;CAG7C,cAAc,YAA0B;AACtC,2BAAyB,WAAW;;CAGtC,gBAAgB,WAAmB,OAAgB,QAA2B;AAC5E,SAAO,uBAAuB,WAAW,OAAO,OAAO;;CAGzD,WAAW,QAAgB,WAAyB;AAClD,oBAAkB,QAAQ,UAAU;;CAGtC,WAAW,QAAsB;AAC/B,wBAAsB,OAAO;;CAG/B,YAAY,WAAmB,OAAgB,QAA2B;AACxE,SAAO,mBAAmB,WAAW,OAAO,OAAO;;CAGrD,0BAA0B,YAAyC;EACjE,MAAM,UAAU,mBAAmB,WAAW;AAC9C,MAAI,CAAC,QAAS,QAAO,EAAE;EACvB,MAAM,WAAW,KAAK,KAAK;GAAE,QAAQ;GAAU,OAAO;GAAK,CAAC,CAAC;EAC7D,MAAM,WAAW;GAAC,QAAQ;GAAK,QAAQ;GAAM,QAAQ,SAAS;GAAS,QAAQ;GAAe,GAAI,QAAQ,QAAQ,EAAE;GAAE,CACnH,OAAO,QAAQ,CACf,KAAK,IAAI,CACT,aAAa;EAChB,MAAM,cAAmC,EAAE;AAC3C,OAAK,MAAM,WAAW,UAAU;GAC9B,IAAI,QAAQ;GACZ,MAAM,UAAoB,EAAE;AAC5B,OAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,CAAC,EAAE;AACjD,aAAS;AACT,YAAQ,KAAK,wCAAwC;;AAEvD,QAAK,MAAM,SAAS,QAAQ,KAAK,aAAa,CAAC,MAAM,MAAM,CAAC,QAAQ,MAAM,EAAE,UAAU,EAAE,CACtF,KAAI,SAAS,SAAS,MAAM,EAAE;AAC5B,aAAS;AACT,YAAQ,KAAK,WAAW,QAAQ;AAChC;;AAGJ,OAAI,QAAQ,EACV,aAAY,KAAK;IAAE,WAAW,QAAQ;IAAI,aAAa,QAAQ;IAAM;IAAO,QAAQ,QAAQ,KAAK,KAAK;IAAE,CAAC;;AAG7G,SAAO,YAAY,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG,EAAE"}
1
+ {"version":3,"file":"project-service.js","names":[],"sources":["../../../src/projects/project-service.ts"],"sourcesContent":["import { getSessionMetadata } from '../storage/sqlite/index.js';\nimport { ProjectStore } from './project-store.js';\nimport { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from './goal-bind.js';\nimport { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from './session-bind.js';\nimport type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';\nimport {\n canonicalWorkspacePath,\n ensureWorkspaceDirectory,\n inferProjectNameFromWorkspaceRoot,\n isPathSameOrInsideWorkspace,\n isSafeAutoCreateWorkspaceRoot,\n ProjectWorkspaceConflictError,\n ProjectWorkspaceMissingError,\n resolveWorkspaceProjectRoot,\n workspaceDirectoryExists,\n type WorkspaceProjectMatch,\n} from './workspace-project.js';\n\nexport type ProjectSuggestion = {\n projectId: string;\n projectName: string;\n score: number;\n reason: string;\n};\n\nexport class ProjectService {\n constructor(private readonly store = new ProjectStore()) {}\n\n private listAllProjects(): Project[] {\n const items: Project[] = [];\n let offset = 0;\n const limit = 500;\n while (true) {\n const page = this.list({ limit, offset });\n items.push(...page.items);\n if (!page.hasMore || page.items.length === 0) break;\n offset += page.items.length;\n }\n return items;\n }\n\n create(input: CreateProjectInput): Project {\n const rawWorkspaceRoot = input.workspaceRoot?.trim();\n const workspaceRoot = rawWorkspaceRoot ? canonicalWorkspacePath(rawWorkspaceRoot) : undefined;\n if (rawWorkspaceRoot && !workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n if (workspaceRoot) {\n const existing = this.findByWorkspaceRoot(workspaceRoot);\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n }\n const name = input.name?.trim() || inferProjectNameFromWorkspaceRoot(workspaceRoot) || '';\n const slug = input.slug?.trim() || this.store.generateSlug(name);\n return this.store.create({ ...input, name, slug, workspaceRoot });\n }\n\n get(id: string): Project | null {\n return this.store.get(id);\n }\n\n getBySlug(slug: string): Project | null {\n return this.store.findBySlug(slug);\n }\n\n list(query?: ProjectListQuery): ProjectListResult {\n return this.store.list(query);\n }\n\n findByWorkspaceRoot(workspaceRoot: string, options: { excludeProjectId?: string } = {}): Project | null {\n const target = canonicalWorkspacePath(workspaceRoot);\n if (!target) return null;\n for (const project of this.listAllProjects()) {\n if (options.excludeProjectId && project.id === options.excludeProjectId) continue;\n const projectRoot = canonicalWorkspacePath(project.workspaceRoot);\n if (projectRoot && isPathSameOrInsideWorkspace(projectRoot, target) && isPathSameOrInsideWorkspace(target, projectRoot)) {\n return project;\n }\n }\n return null;\n }\n\n resolveForWorkspacePath(workspacePath: string): { project: Project; reason: 'exact' | 'contained' } | null {\n const target = canonicalWorkspacePath(workspacePath);\n if (!target) return null;\n let best: { project: Project; root: string; reason: 'exact' | 'contained' } | null = null;\n for (const project of this.listAllProjects()) {\n const root = canonicalWorkspacePath(project.workspaceRoot);\n if (!root || !isPathSameOrInsideWorkspace(root, target)) continue;\n const reason = isPathSameOrInsideWorkspace(root, target) && isPathSameOrInsideWorkspace(target, root) ? 'exact' : 'contained';\n if (!best || root.length > best.root.length) {\n best = { project, root, reason };\n }\n }\n return best ? { project: best.project, reason: best.reason } : null;\n }\n\n resolveOrCreateForWorkspacePath(input: {\n workspacePath: string;\n agentId?: string;\n defaultAgentId?: string;\n autoCreate?: boolean;\n }): WorkspaceProjectMatch | null {\n const existing = this.resolveForWorkspacePath(input.workspacePath);\n if (existing) return { project: existing.project, reason: existing.reason, created: false };\n if (!input.autoCreate) return null;\n const workspaceRoot = resolveWorkspaceProjectRoot(input.workspacePath) ?? canonicalWorkspacePath(input.workspacePath);\n if (!workspaceRoot || !isSafeAutoCreateWorkspaceRoot(workspaceRoot)) return null;\n const rootExisting = this.resolveForWorkspacePath(workspaceRoot);\n if (rootExisting) return { project: rootExisting.project, reason: rootExisting.reason, created: false };\n const project = this.create({\n name: inferProjectNameFromWorkspaceRoot(workspaceRoot) ?? undefined,\n workspaceRoot,\n defaultAgentId: Object.hasOwn(input, 'defaultAgentId') ? input.defaultAgentId : input.agentId,\n });\n return { project, reason: 'auto_created', created: true };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const patch = { ...input };\n if (input.workspaceRoot !== undefined && input.workspaceRoot !== null && input.workspaceRoot.trim()) {\n const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);\n if (!workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n const existing = this.findByWorkspaceRoot(workspaceRoot, { excludeProjectId: id });\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n patch.workspaceRoot = workspaceRoot;\n }\n return this.store.update(id, patch);\n }\n\n delete(id: string): void {\n this.store.delete(id);\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n return this.store.getWithDetails(id);\n }\n\n attachSession(sessionKey: string, projectId: string): void {\n bindSessionToProject(sessionKey, projectId);\n }\n\n detachSession(sessionKey: string): void {\n unbindSessionFromProject(sessionKey);\n }\n\n listSessionKeys(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectSessionKeys(projectId, limit, offset);\n }\n\n attachGoal(goalId: string, projectId: string): void {\n bindGoalToProject(goalId, projectId);\n }\n\n detachGoal(goalId: string): void {\n unbindGoalFromProject(goalId);\n }\n\n listGoalIds(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectGoalIds(projectId, limit, offset);\n }\n\n suggestProjectsForSession(sessionKey: string): ProjectSuggestion[] {\n const session = getSessionMetadata(sessionKey);\n if (!session) return [];\n const projects = this.list({ status: 'active', limit: 500 }).items;\n const haystack = [session.key, session.name, session.routing?.agentId, session.sourceChannel, ...(session.tags ?? [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n const suggestions: ProjectSuggestion[] = [];\n for (const project of projects) {\n let score = 0;\n const reasons: string[] = [];\n if (haystack.includes(project.slug.toLowerCase())) {\n score += 10;\n reasons.push('session metadata matches project slug');\n }\n for (const token of project.name.toLowerCase().split(/\\s+/).filter((t) => t.length >= 3)) {\n if (haystack.includes(token)) {\n score += 3;\n reasons.push(`matches ${token}`);\n break;\n }\n }\n if (score > 0) {\n suggestions.push({ projectId: project.id, projectName: project.name, score, reason: reasons.join(', ') });\n }\n }\n return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { escapeFts5Query } from "../storage/sqlite/fts.js";
2
3
  import { randomUUID } from "node:crypto";
3
4
  //#region src/projects/project-store.ts
4
5
  function trimOptional(value) {
@@ -43,6 +44,25 @@ function projectSortColumn(sortBy) {
43
44
  default: return "updated_at";
44
45
  }
45
46
  }
47
+ function projectSortExpression(sortBy) {
48
+ const column = projectSortColumn(sortBy);
49
+ return column === "LOWER(name)" ? "LOWER(p.name)" : `p.${column}`;
50
+ }
51
+ function projectFtsContent(project) {
52
+ return [
53
+ project.name,
54
+ project.slug,
55
+ project.description,
56
+ project.workspaceRoot,
57
+ project.brief,
58
+ project.instructions,
59
+ project.defaultAgentId
60
+ ].filter((value) => Boolean(value?.trim())).join("\n");
61
+ }
62
+ function syncProjectFts(db, project) {
63
+ db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(project.id);
64
+ db.prepare(`INSERT INTO projects_fts (content, project_id) VALUES (?, ?)`).run(projectFtsContent(project), project.id);
65
+ }
46
66
  var ProjectStore = class {
47
67
  create(input) {
48
68
  const now = Date.now();
@@ -67,6 +87,7 @@ var ProjectStore = class {
67
87
  project_id, name, slug, description, status, default_agent_id, workspace_root,
68
88
  brief, instructions, created_at, updated_at, last_active_at
69
89
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(project.id, project.name, project.slug, project.description ?? null, project.status, project.defaultAgentId ?? null, project.workspaceRoot ?? null, project.brief ?? null, project.instructions ?? null, project.createdAt, project.updatedAt, project.lastActiveAt ?? null);
90
+ syncProjectFts(db, project);
70
91
  });
71
92
  return project;
72
93
  }
@@ -81,25 +102,26 @@ var ProjectStore = class {
81
102
  list(query = {}) {
82
103
  const conditions = [];
83
104
  const params = [];
105
+ const search = query.search?.trim();
106
+ const ftsQuery = search ? escapeFts5Query(search) : "";
84
107
  if (query.status) {
85
108
  const statuses = Array.isArray(query.status) ? query.status : [query.status];
86
- conditions.push(`status IN (${statuses.map(() => "?").join(", ")})`);
109
+ conditions.push(`p.status IN (${statuses.map(() => "?").join(", ")})`);
87
110
  params.push(...statuses);
88
111
  }
89
- if (query.search?.trim()) {
90
- const like = `%${query.search.trim().toLowerCase()}%`;
91
- conditions.push(`(LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(COALESCE(description, '')) LIKE ?)`);
92
- params.push(like, like, like);
112
+ if (ftsQuery) {
113
+ conditions.push(`p.project_id IN (SELECT project_id FROM projects_fts WHERE projects_fts MATCH ?)`);
114
+ params.push(ftsQuery);
93
115
  }
94
116
  const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
95
117
  const limit = clampLimit(query.limit, 50);
96
118
  const offset = Math.max(0, Math.floor(query.offset ?? 0));
97
119
  const sortOrder = query.sortOrder === "asc" ? "ASC" : "DESC";
98
- const sortColumn = projectSortColumn(query.sortBy);
120
+ const sortColumn = projectSortExpression(query.sortBy);
99
121
  const db = getSqliteDatabase();
100
- const total = db.prepare(`SELECT COUNT(*) AS total FROM projects ${where}`).get(...params).total;
122
+ const total = db.prepare(`SELECT COUNT(*) AS total FROM projects p ${where}`).get(...params).total;
101
123
  return {
102
- items: db.prepare(`SELECT * FROM projects ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`).all(...params, limit, offset).map(projectFromRow),
124
+ items: db.prepare(`SELECT p.* FROM projects p ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`).all(...params, limit, offset).map(projectFromRow),
103
125
  total,
104
126
  limit,
105
127
  offset,
@@ -124,6 +146,7 @@ var ProjectStore = class {
124
146
  db.prepare(`UPDATE projects SET
125
147
  name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?
126
148
  WHERE project_id = ?`).run(next.name, next.description ?? null, next.status, next.defaultAgentId ?? null, next.workspaceRoot ?? null, next.brief ?? null, next.instructions ?? null, next.updatedAt, id);
149
+ syncProjectFts(db, next);
127
150
  });
128
151
  return this.get(id);
129
152
  }
@@ -134,6 +157,7 @@ var ProjectStore = class {
134
157
  db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);
135
158
  db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);
136
159
  db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);
160
+ db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(id);
137
161
  db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);
138
162
  });
139
163
  }
@@ -1 +1 @@
1
- {"version":3,"file":"project-store.js","names":[],"sources":["../../../src/projects/project-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { getSqliteDatabase, runSqliteWriteTransaction } from '../storage/sqlite/transaction.js';\nimport type {\n CreateProjectInput,\n Project,\n ProjectListQuery,\n ProjectListResult,\n ProjectStatus,\n ProjectWithDetails,\n UpdateProjectInput,\n} from './types.js';\n\nexport type ProjectRow = {\n project_id: string;\n name: string;\n slug: string;\n description: string | null;\n status: string;\n default_agent_id: string | null;\n workspace_root: string | null;\n brief: string | null;\n instructions: string | null;\n created_at: number;\n updated_at: number;\n last_active_at: number | null;\n};\n\nfunction trimOptional(value: string | null | undefined): string | undefined {\n if (value == null) return undefined;\n const trimmed = value.trim();\n return trimmed || undefined;\n}\n\nfunction nullableText(value: string | null | undefined): string | null {\n if (value == null) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\nfunction projectFromRow(row: ProjectRow): Project {\n return {\n id: row.project_id,\n name: row.name,\n slug: row.slug,\n description: row.description ?? undefined,\n status: row.status as ProjectStatus,\n defaultAgentId: row.default_agent_id ?? undefined,\n workspaceRoot: row.workspace_root ?? undefined,\n brief: row.brief ?? undefined,\n instructions: row.instructions ?? undefined,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n lastActiveAt: row.last_active_at ?? undefined,\n };\n}\n\nfunction clampLimit(limit: number | undefined, fallback = 50): number {\n return Math.min(500, Math.max(1, Math.floor(limit ?? fallback)));\n}\n\nfunction normalizeName(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) throw new Error('Project name is required');\n return trimmed;\n}\n\nexport function slugifyProjectName(name: string): string {\n const slug = name\n .trim()\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n return slug || `project-${Date.now()}`;\n}\n\nfunction projectSortColumn(sortBy: ProjectListQuery['sortBy']): string {\n switch (sortBy) {\n case 'createdAt':\n return 'created_at';\n case 'name':\n return 'LOWER(name)';\n case 'updatedAt':\n default:\n return 'updated_at';\n }\n}\n\nexport class ProjectStore {\n create(input: CreateProjectInput): Project {\n const now = Date.now();\n const name = normalizeName(input.name);\n const slug = input.slug?.trim() || slugifyProjectName(name);\n const project: Project = {\n id: randomUUID(),\n name,\n slug,\n description: trimOptional(input.description),\n status: 'active',\n defaultAgentId: trimOptional(input.defaultAgentId),\n workspaceRoot: trimOptional(input.workspaceRoot),\n brief: trimOptional(input.brief),\n instructions: trimOptional(input.instructions),\n createdAt: now,\n updatedAt: now,\n lastActiveAt: now,\n };\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `INSERT INTO projects (\n project_id, name, slug, description, status, default_agent_id, workspace_root,\n brief, instructions, created_at, updated_at, last_active_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n project.id,\n project.name,\n project.slug,\n project.description ?? null,\n project.status,\n project.defaultAgentId ?? null,\n project.workspaceRoot ?? null,\n project.brief ?? null,\n project.instructions ?? null,\n project.createdAt,\n project.updatedAt,\n project.lastActiveAt ?? null,\n );\n });\n return project;\n }\n\n get(id: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE project_id = ?`)\n .get(id) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n findBySlug(slug: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE slug = ?`)\n .get(slug.trim()) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n list(query: ProjectListQuery = {}): ProjectListResult {\n const conditions: string[] = [];\n const params: Array<string | number> = [];\n if (query.status) {\n const statuses = Array.isArray(query.status) ? query.status : [query.status];\n conditions.push(`status IN (${statuses.map(() => '?').join(', ')})`);\n params.push(...statuses);\n }\n if (query.search?.trim()) {\n const like = `%${query.search.trim().toLowerCase()}%`;\n conditions.push(`(LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(COALESCE(description, '')) LIKE ?)`);\n params.push(like, like, like);\n }\n const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = clampLimit(query.limit, 50);\n const offset = Math.max(0, Math.floor(query.offset ?? 0));\n const sortOrder = query.sortOrder === 'asc' ? 'ASC' : 'DESC';\n const sortColumn = projectSortColumn(query.sortBy);\n const db = getSqliteDatabase();\n const total = (db.prepare(`SELECT COUNT(*) AS total FROM projects ${where}`).get(...params) as { total: number }).total;\n const rows = db\n .prepare(`SELECT * FROM projects ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`)\n .all(...params, limit, offset) as ProjectRow[];\n return { items: rows.map(projectFromRow), total, limit, offset, hasMore: offset + limit < total };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const before = this.get(id);\n if (!before) throw new Error(`Project not found: ${id}`);\n const next = {\n ...before,\n ...(input.name !== undefined ? { name: normalizeName(input.name) } : {}),\n ...(input.description !== undefined ? { description: nullableText(input.description) ?? undefined } : {}),\n ...(input.status !== undefined ? { status: input.status } : {}),\n ...(input.defaultAgentId !== undefined ? { defaultAgentId: nullableText(input.defaultAgentId) ?? undefined } : {}),\n ...(input.workspaceRoot !== undefined ? { workspaceRoot: nullableText(input.workspaceRoot) ?? undefined } : {}),\n ...(input.brief !== undefined ? { brief: nullableText(input.brief) ?? undefined } : {}),\n ...(input.instructions !== undefined ? { instructions: nullableText(input.instructions) ?? undefined } : {}),\n updatedAt: Date.now(),\n } satisfies Project;\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `UPDATE projects SET\n name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?\n WHERE project_id = ?`,\n ).run(\n next.name,\n next.description ?? null,\n next.status,\n next.defaultAgentId ?? null,\n next.workspaceRoot ?? null,\n next.brief ?? null,\n next.instructions ?? null,\n next.updatedAt,\n id,\n );\n });\n return this.get(id)!;\n }\n\n delete(id: string): void {\n runSqliteWriteTransaction((db) => {\n db.prepare(`UPDATE sessions SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE goals SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);\n });\n }\n\n getSessionCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM sessions WHERE project_id = ? AND hidden_from_session_list = 0`)\n .get(id) as { total: number }).total;\n }\n\n getGoalCount(id: string): number {\n return (getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ?`).get(id) as { total: number }).total;\n }\n\n getActiveGoalCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ? AND status IN ('active', 'paused', 'blocked', 'needs_input')`)\n .get(id) as { total: number }).total;\n }\n\n getRecentSessions(id: string, limit = 5): ProjectWithDetails['recentSessions'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT session_key, name, updated_at, agent_id FROM sessions\n WHERE project_id = ? AND hidden_from_session_list = 0 ORDER BY updated_at DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ session_key: string; name: string | null; updated_at: number; agent_id: string }>;\n return rows.map((row) => ({\n key: row.session_key,\n name: row.name ?? undefined,\n updatedAt: new Date(row.updated_at).toISOString(),\n agentId: row.agent_id,\n }));\n }\n\n getRecentWorkflowRuns(id: string, limit = 5): ProjectWithDetails['recentWorkflowRuns'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT run_id, definition_id, status, created_at_ms FROM workflow_runs\n WHERE project_id = ? ORDER BY created_at_ms DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ run_id: string; definition_id: string; status: string; created_at_ms: number }>;\n return rows.map((row) => ({ runId: row.run_id, definitionId: row.definition_id, status: row.status, createdAt: row.created_at_ms }));\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n const project = this.get(id);\n if (!project) return null;\n return {\n ...project,\n sessionCount: this.getSessionCount(id),\n goalCount: this.getGoalCount(id),\n activeGoalCount: this.getActiveGoalCount(id),\n recentSessions: this.getRecentSessions(id),\n recentWorkflowRuns: this.getRecentWorkflowRuns(id),\n };\n }\n\n generateSlug(name: string): string {\n const base = slugifyProjectName(name);\n let candidate = base;\n let counter = 2;\n while (this.findBySlug(candidate)) {\n candidate = `${base}-${counter}`;\n counter += 1;\n }\n return candidate;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"project-store.js","names":[],"sources":["../../../src/projects/project-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { escapeFts5Query } from '../storage/sqlite/fts.js';\nimport { getSqliteDatabase, runSqliteWriteTransaction } from '../storage/sqlite/transaction.js';\nimport type {\n CreateProjectInput,\n Project,\n ProjectListQuery,\n ProjectListResult,\n ProjectStatus,\n ProjectWithDetails,\n UpdateProjectInput,\n} from './types.js';\n\nexport type ProjectRow = {\n project_id: string;\n name: string;\n slug: string;\n description: string | null;\n status: string;\n default_agent_id: string | null;\n workspace_root: string | null;\n brief: string | null;\n instructions: string | null;\n created_at: number;\n updated_at: number;\n last_active_at: number | null;\n};\n\nfunction trimOptional(value: string | null | undefined): string | undefined {\n if (value == null) return undefined;\n const trimmed = value.trim();\n return trimmed || undefined;\n}\n\nfunction nullableText(value: string | null | undefined): string | null {\n if (value == null) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\nfunction projectFromRow(row: ProjectRow): Project {\n return {\n id: row.project_id,\n name: row.name,\n slug: row.slug,\n description: row.description ?? undefined,\n status: row.status as ProjectStatus,\n defaultAgentId: row.default_agent_id ?? undefined,\n workspaceRoot: row.workspace_root ?? undefined,\n brief: row.brief ?? undefined,\n instructions: row.instructions ?? undefined,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n lastActiveAt: row.last_active_at ?? undefined,\n };\n}\n\nfunction clampLimit(limit: number | undefined, fallback = 50): number {\n return Math.min(500, Math.max(1, Math.floor(limit ?? fallback)));\n}\n\nfunction normalizeName(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) throw new Error('Project name is required');\n return trimmed;\n}\n\nexport function slugifyProjectName(name: string): string {\n const slug = name\n .trim()\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n return slug || `project-${Date.now()}`;\n}\n\nfunction projectSortColumn(sortBy: ProjectListQuery['sortBy']): string {\n switch (sortBy) {\n case 'createdAt':\n return 'created_at';\n case 'name':\n return 'LOWER(name)';\n case 'updatedAt':\n default:\n return 'updated_at';\n }\n}\n\nfunction projectSortExpression(sortBy: ProjectListQuery['sortBy']): string {\n const column = projectSortColumn(sortBy);\n return column === 'LOWER(name)' ? 'LOWER(p.name)' : `p.${column}`;\n}\n\nfunction projectFtsContent(project: Project): string {\n return [\n project.name,\n project.slug,\n project.description,\n project.workspaceRoot,\n project.brief,\n project.instructions,\n project.defaultAgentId,\n ]\n .filter((value): value is string => Boolean(value?.trim()))\n .join('\\n');\n}\n\nfunction syncProjectFts(\n db: ReturnType<typeof getSqliteDatabase>,\n project: Project,\n): void {\n db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(project.id);\n db.prepare(`INSERT INTO projects_fts (content, project_id) VALUES (?, ?)`).run(\n projectFtsContent(project),\n project.id,\n );\n}\n\nexport class ProjectStore {\n create(input: CreateProjectInput): Project {\n const now = Date.now();\n const name = normalizeName(input.name);\n const slug = input.slug?.trim() || slugifyProjectName(name);\n const project: Project = {\n id: randomUUID(),\n name,\n slug,\n description: trimOptional(input.description),\n status: 'active',\n defaultAgentId: trimOptional(input.defaultAgentId),\n workspaceRoot: trimOptional(input.workspaceRoot),\n brief: trimOptional(input.brief),\n instructions: trimOptional(input.instructions),\n createdAt: now,\n updatedAt: now,\n lastActiveAt: now,\n };\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `INSERT INTO projects (\n project_id, name, slug, description, status, default_agent_id, workspace_root,\n brief, instructions, created_at, updated_at, last_active_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n project.id,\n project.name,\n project.slug,\n project.description ?? null,\n project.status,\n project.defaultAgentId ?? null,\n project.workspaceRoot ?? null,\n project.brief ?? null,\n project.instructions ?? null,\n project.createdAt,\n project.updatedAt,\n project.lastActiveAt ?? null,\n );\n syncProjectFts(db, project);\n });\n return project;\n }\n\n get(id: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE project_id = ?`)\n .get(id) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n findBySlug(slug: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE slug = ?`)\n .get(slug.trim()) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n list(query: ProjectListQuery = {}): ProjectListResult {\n const conditions: string[] = [];\n const params: Array<string | number> = [];\n const search = query.search?.trim();\n const ftsQuery = search ? escapeFts5Query(search) : '';\n if (query.status) {\n const statuses = Array.isArray(query.status) ? query.status : [query.status];\n conditions.push(`p.status IN (${statuses.map(() => '?').join(', ')})`);\n params.push(...statuses);\n }\n if (ftsQuery) {\n conditions.push(`p.project_id IN (SELECT project_id FROM projects_fts WHERE projects_fts MATCH ?)`);\n params.push(ftsQuery);\n }\n const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = clampLimit(query.limit, 50);\n const offset = Math.max(0, Math.floor(query.offset ?? 0));\n const sortOrder = query.sortOrder === 'asc' ? 'ASC' : 'DESC';\n const sortColumn = projectSortExpression(query.sortBy);\n const db = getSqliteDatabase();\n const total = (db.prepare(`SELECT COUNT(*) AS total FROM projects p ${where}`).get(...params) as { total: number }).total;\n const rows = db\n .prepare(`SELECT p.* FROM projects p ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`)\n .all(...params, limit, offset) as ProjectRow[];\n return { items: rows.map(projectFromRow), total, limit, offset, hasMore: offset + limit < total };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const before = this.get(id);\n if (!before) throw new Error(`Project not found: ${id}`);\n const next = {\n ...before,\n ...(input.name !== undefined ? { name: normalizeName(input.name) } : {}),\n ...(input.description !== undefined ? { description: nullableText(input.description) ?? undefined } : {}),\n ...(input.status !== undefined ? { status: input.status } : {}),\n ...(input.defaultAgentId !== undefined ? { defaultAgentId: nullableText(input.defaultAgentId) ?? undefined } : {}),\n ...(input.workspaceRoot !== undefined ? { workspaceRoot: nullableText(input.workspaceRoot) ?? undefined } : {}),\n ...(input.brief !== undefined ? { brief: nullableText(input.brief) ?? undefined } : {}),\n ...(input.instructions !== undefined ? { instructions: nullableText(input.instructions) ?? undefined } : {}),\n updatedAt: Date.now(),\n } satisfies Project;\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `UPDATE projects SET\n name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?\n WHERE project_id = ?`,\n ).run(\n next.name,\n next.description ?? null,\n next.status,\n next.defaultAgentId ?? null,\n next.workspaceRoot ?? null,\n next.brief ?? null,\n next.instructions ?? null,\n next.updatedAt,\n id,\n );\n syncProjectFts(db, next);\n });\n return this.get(id)!;\n }\n\n delete(id: string): void {\n runSqliteWriteTransaction((db) => {\n db.prepare(`UPDATE sessions SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE goals SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(id);\n db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);\n });\n }\n\n getSessionCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM sessions WHERE project_id = ? AND hidden_from_session_list = 0`)\n .get(id) as { total: number }).total;\n }\n\n getGoalCount(id: string): number {\n return (getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ?`).get(id) as { total: number }).total;\n }\n\n getActiveGoalCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ? AND status IN ('active', 'paused', 'blocked', 'needs_input')`)\n .get(id) as { total: number }).total;\n }\n\n getRecentSessions(id: string, limit = 5): ProjectWithDetails['recentSessions'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT session_key, name, updated_at, agent_id FROM sessions\n WHERE project_id = ? AND hidden_from_session_list = 0 ORDER BY updated_at DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ session_key: string; name: string | null; updated_at: number; agent_id: string }>;\n return rows.map((row) => ({\n key: row.session_key,\n name: row.name ?? undefined,\n updatedAt: new Date(row.updated_at).toISOString(),\n agentId: row.agent_id,\n }));\n }\n\n getRecentWorkflowRuns(id: string, limit = 5): ProjectWithDetails['recentWorkflowRuns'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT run_id, definition_id, status, created_at_ms FROM workflow_runs\n WHERE project_id = ? ORDER BY created_at_ms DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ run_id: string; definition_id: string; status: string; created_at_ms: number }>;\n return rows.map((row) => ({ runId: row.run_id, definitionId: row.definition_id, status: row.status, createdAt: row.created_at_ms }));\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n const project = this.get(id);\n if (!project) return null;\n return {\n ...project,\n sessionCount: this.getSessionCount(id),\n goalCount: this.getGoalCount(id),\n activeGoalCount: this.getActiveGoalCount(id),\n recentSessions: this.getRecentSessions(id),\n recentWorkflowRuns: this.getRecentWorkflowRuns(id),\n };\n }\n\n generateSlug(name: string): string {\n const base = slugifyProjectName(name);\n let candidate = base;\n let counter = 2;\n while (this.findBySlug(candidate)) {\n candidate = `${base}-${counter}`;\n counter += 1;\n }\n return candidate;\n }\n}\n"],"mappings":"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"}
@@ -40,11 +40,13 @@ export interface ProjectListQuery {
40
40
  offset?: number;
41
41
  }
42
42
  export interface CreateProjectInput {
43
- name: string;
43
+ name?: string;
44
44
  slug?: string;
45
45
  description?: string;
46
46
  defaultAgentId?: string;
47
47
  workspaceRoot?: string;
48
+ createWorkspaceRoot?: boolean;
49
+ projectKind?: string;
48
50
  brief?: string;
49
51
  instructions?: string;
50
52
  }
@@ -54,6 +56,7 @@ export interface UpdateProjectInput {
54
56
  defaultAgentId?: string | null;
55
57
  status?: ProjectStatus;
56
58
  workspaceRoot?: string | null;
59
+ createWorkspaceRoot?: boolean;
57
60
  brief?: string | null;
58
61
  instructions?: string | null;
59
62
  }
@@ -0,0 +1,22 @@
1
+ import type { Project } from './types.js';
2
+ export type WorkspaceProjectMatchReason = 'exact' | 'contained' | 'auto_created';
3
+ export type WorkspaceProjectMatch = {
4
+ project: Project;
5
+ reason: WorkspaceProjectMatchReason;
6
+ created: boolean;
7
+ };
8
+ export declare class ProjectWorkspaceConflictError extends Error {
9
+ readonly project: Project;
10
+ constructor(project: Project, message?: string);
11
+ }
12
+ export declare class ProjectWorkspaceMissingError extends Error {
13
+ readonly workspaceRoot: string;
14
+ constructor(workspaceRoot: string, message?: string);
15
+ }
16
+ export declare function canonicalWorkspacePath(raw: string | null | undefined): string | null;
17
+ export declare function workspaceDirectoryExists(workspaceRoot: string | null | undefined): boolean;
18
+ export declare function ensureWorkspaceDirectory(workspaceRoot: string): void;
19
+ export declare function resolveWorkspaceProjectRoot(raw: string | null | undefined): string | null;
20
+ export declare function isPathSameOrInsideWorkspace(workspaceRoot: string, candidatePath: string): boolean;
21
+ export declare function inferProjectNameFromWorkspaceRoot(workspaceRoot: string | null | undefined): string | null;
22
+ export declare function isSafeAutoCreateWorkspaceRoot(workspaceRoot: string | null | undefined): boolean;