@xopcai/xopc 0.0.123 → 0.0.125

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 (436) 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/Combination-BzaXOLd9.js +41 -0
  6. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Bicr3ZBg.js → agent-avatar-dicebear-BKDKICZV.js} +1 -1
  7. package/dist/gateway/static/root/assets/agents-BnTB7-c5.js +2 -0
  8. package/dist/gateway/static/root/assets/app-management-settings-panel-DI4BqHKW.js +1 -0
  9. package/dist/gateway/static/root/assets/appearance-settings-6_ie3NQ4.js +1 -0
  10. package/dist/gateway/static/root/assets/apps-page-B6bG65SW.js +1 -0
  11. package/dist/gateway/static/root/assets/{archive-plugin-Bpy64GjU.js → archive-plugin-kUHEJ1HK.js} +1 -1
  12. package/dist/gateway/static/root/assets/{attachment-load-DMkp4xSb.js → attachment-load-Dm-9prpU.js} +1 -1
  13. package/dist/gateway/static/root/assets/automation-suggestion-card-OTxQTPnC.js +1 -0
  14. package/dist/gateway/static/root/assets/automations-page-1lVbo0dS.js +1 -0
  15. package/dist/gateway/static/root/assets/{binary-plugins-DA9C5mCw.js → binary-plugins-DllA_jQ0.js} +1 -1
  16. package/dist/gateway/static/root/assets/{block-editor-CiSXk_YC.js → block-editor-Cm6CWNj1.js} +5 -5
  17. package/dist/gateway/static/root/assets/browser-settings-page-CzDBlOK0.js +8 -0
  18. package/dist/gateway/static/root/assets/capability-presets-api-VJ89b6I2.js +1 -0
  19. package/dist/gateway/static/root/assets/capability-presets-settings-panel-BaPj8wC2.js +1 -0
  20. package/dist/gateway/static/root/assets/{channel-recipient-api-CDhrhfhN.js → channel-recipient-api-B5PwBEUx.js} +1 -1
  21. package/dist/gateway/static/root/assets/channels-settings-Df4m7p1k.js +1 -0
  22. package/dist/gateway/static/root/assets/code-editor-gbRgS14t.js +1 -0
  23. package/dist/gateway/static/root/assets/connectors-page-BMTphmHS.js +2 -0
  24. package/dist/gateway/static/root/assets/{directory-picker-path-field-7jKfbH0J.js → directory-picker-path-field-DySMzOF6.js} +1 -1
  25. package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +1 -0
  26. package/dist/gateway/static/root/assets/dist-CkobR7mk.js +1 -0
  27. package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +1 -0
  28. package/dist/gateway/static/root/assets/dreaming-settings-D_AjSnV_.js +3 -0
  29. package/dist/gateway/static/root/assets/extension-debug-page-BaM0FpW7.js +1 -0
  30. package/dist/gateway/static/root/assets/extension-page-DuZWVOGA.js +1 -0
  31. package/dist/gateway/static/root/assets/extension-settings-page-DarVtbmJ.js +1 -0
  32. package/dist/gateway/static/root/assets/{fetch-Dtpbbe0s.js → fetch-NFKfkqrd.js} +1 -1
  33. package/dist/gateway/static/root/assets/{gateway-config-api-BN6pyX2c.js → gateway-config-api-CFEm9rgy.js} +1 -1
  34. package/dist/gateway/static/root/assets/gateway-settings-BMk2S5td.js +1 -0
  35. package/dist/gateway/static/root/assets/{gateway-startup-retry-BIET2x3y.js → gateway-startup-retry-Dtwh_yGg.js} +1 -1
  36. package/dist/gateway/static/root/assets/goal-create-dialog-BKP7ylcG.js +1 -0
  37. package/dist/gateway/static/root/assets/goal-detail-page-C2cTxf71.js +1 -0
  38. package/dist/gateway/static/root/assets/{goals-config-api-CQm2r1Ty.js → goals-config-api-DTWZNA_Y.js} +1 -1
  39. package/dist/gateway/static/root/assets/goals-page-CTmA3hWK.js +2 -0
  40. package/dist/gateway/static/root/assets/goals-settings-Bhct0ZT-.js +1 -0
  41. package/dist/gateway/static/root/assets/heartbeat-settings-C-UxSU4a.js +1 -0
  42. package/dist/gateway/static/root/assets/{html-workspace-editor-CQLWDiwE.js → html-workspace-editor-DV90ddkE.js} +1 -1
  43. package/dist/gateway/static/root/assets/index-BNHDd2j1.css +1 -0
  44. package/dist/gateway/static/root/assets/index-BPXLPMuY.js +193 -0
  45. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-DCjy2y4d.js +1 -0
  46. package/dist/gateway/static/root/assets/logs-page-CGc3_CQU.js +2 -0
  47. package/dist/gateway/static/root/assets/{markdown-editor-AssmZFx4.js → markdown-editor-BFOjBBla.js} +2 -2
  48. package/dist/gateway/static/root/assets/markdown-split-BaqxP-GQ.js +1 -0
  49. package/dist/gateway/static/root/assets/{media-plugins-DS44pJzr.js → media-plugins-DQ_EXloK.js} +1 -1
  50. package/dist/gateway/static/root/assets/messages-CjuIJcZl.js +3 -0
  51. package/dist/gateway/static/root/assets/models-hub-panel-Drz4UQMb.js +2 -0
  52. package/dist/gateway/static/root/assets/notes-page-CY1ylng1.js +1 -0
  53. package/dist/gateway/static/root/assets/notes-workbench-X7IaD--t.js +2 -0
  54. package/dist/gateway/static/root/assets/page-tabs-EIZhLppK.js +1 -0
  55. package/dist/gateway/static/root/assets/{preview-open-alternatives-Dej6zHnr.js → preview-open-alternatives-r_uB4Epr.js} +1 -1
  56. package/dist/gateway/static/root/assets/project-detail-page-DE5adRBE.js +1 -0
  57. package/dist/gateway/static/root/assets/projects-page-CuOb1k6F.js +1 -0
  58. package/dist/gateway/static/root/assets/remote-access-hub-BOxNrUVA.js +1 -0
  59. package/dist/gateway/static/root/assets/{save-bar-controls-Zu1FzH6z.js → save-bar-controls-B132Uf-2.js} +1 -1
  60. package/dist/gateway/static/root/assets/{schedule-field-BW78mKc0.js → schedule-field-B5qshALm.js} +1 -1
  61. package/dist/gateway/static/root/assets/{schema-form-Cc-IOydE.js → schema-form-CvLmkdS9.js} +1 -1
  62. package/dist/gateway/static/root/assets/sessions-page-CY0IQcGW.js +1 -0
  63. package/dist/gateway/static/root/assets/settings-advanced-gate-D4NBNVCB.js +1 -0
  64. package/dist/gateway/static/root/assets/{settings-collapsible-section-DIpIYht_.js → settings-collapsible-section-pRlvqsAf.js} +1 -1
  65. package/dist/gateway/static/root/assets/{settings-form-section-BFxBNxOG.js → settings-form-section-BBYUDLVd.js} +1 -1
  66. package/dist/gateway/static/root/assets/{settings-page-C4fivemT.js → settings-page-DaO7zYZM.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-layout-CLx4o2PN.js → settings-page-layout-D8g9PAek.js} +1 -1
  68. package/dist/gateway/static/root/assets/setup-status-panel-Bks0CLLT.js +1 -0
  69. package/dist/gateway/static/root/assets/{share-preview-page-ZswWCrDu.js → share-preview-page-v6-IOp8t.js} +1 -1
  70. package/dist/gateway/static/root/assets/shares-settings-CEZsKm9r.js +1 -0
  71. package/dist/gateway/static/root/assets/skill-reload-api-oTkHbb9W.js +1 -0
  72. package/dist/gateway/static/root/assets/skills-page-DigsbZ6K.js +2 -0
  73. package/dist/gateway/static/root/assets/{system-settings-panel-3ZNcGRhS.js → system-settings-panel-HUU2FFjM.js} +1 -1
  74. package/dist/gateway/static/root/assets/{typed-models-lib-bpzZjiVF.js → typed-models-lib-AH160QHh.js} +1 -1
  75. package/dist/gateway/static/root/assets/{use-channel-catalog-BCjZLZ3O.js → use-channel-catalog-BXzDmjCQ.js} +1 -1
  76. package/dist/gateway/static/root/assets/{user-profile-settings-panel-CIXafITi.js → user-profile-settings-panel-DxwnQvQ3.js} +1 -1
  77. package/dist/gateway/static/root/assets/{utils-DXMtz5j_.js → utils-i5POEtqd.js} +1 -1
  78. package/dist/gateway/static/root/assets/{vendor-codemirror-CNP1wPe7.js → vendor-codemirror-BvNgyE1Q.js} +1 -1
  79. package/dist/gateway/static/root/assets/voice-api-key-field-CEJklEgb.js +1 -0
  80. package/dist/gateway/static/root/assets/work-item-detail-page-CD6FL_Rs.js +1 -0
  81. package/dist/gateway/static/root/assets/workflow-run-setup-panel-ClVVMgN-.js +3 -0
  82. package/dist/gateway/static/root/assets/{workflow-start-dialog-B4sZpTDQ.js → workflow-start-dialog-Dv4ZA1T0.js} +1 -1
  83. package/dist/gateway/static/root/assets/workflows-page-OLQiUpoW.js +18 -0
  84. package/dist/gateway/static/root/index.html +8 -7
  85. package/dist/package.js +1 -1
  86. package/dist/src/agent/agent-manager.d.ts +6 -1
  87. package/dist/src/agent/agent-manager.js +34 -14
  88. package/dist/src/agent/agent-manager.js.map +1 -1
  89. package/dist/src/agent/child-agent-factory.d.ts +2 -0
  90. package/dist/src/agent/child-agent-factory.js +2 -0
  91. package/dist/src/agent/child-agent-factory.js.map +1 -1
  92. package/dist/src/agent/coding/index.d.ts +1 -0
  93. package/dist/src/agent/coding/index.js +2 -0
  94. package/dist/src/agent/coding/turn-diff-tracker.d.ts +67 -0
  95. package/dist/src/agent/coding/turn-diff-tracker.js +233 -0
  96. package/dist/src/agent/coding/turn-diff-tracker.js.map +1 -0
  97. package/dist/src/agent/context/workspace-templates/AGENTS.md +1 -1
  98. package/dist/src/agent/embedded/xopc-tools-bridge.js +11 -7
  99. package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
  100. package/dist/src/agent/lifecycle/progress.js +3 -1
  101. package/dist/src/agent/lifecycle/progress.js.map +1 -1
  102. package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +2 -2
  103. package/dist/src/agent/lifecycle/timeout-wrapper.js +4 -4
  104. package/dist/src/agent/lifecycle/timeout-wrapper.js.map +1 -1
  105. package/dist/src/agent/memory/dreaming/config.d.ts +2 -2
  106. package/dist/src/agent/memory/dreaming/config.js +3 -3
  107. package/dist/src/agent/memory/dreaming/config.js.map +1 -1
  108. package/dist/src/agent/memory/dreaming/constants.d.ts +1 -1
  109. package/dist/src/agent/memory/dreaming/constants.js +1 -1
  110. package/dist/src/agent/memory/dreaming/constants.js.map +1 -1
  111. package/dist/src/agent/memory/dreaming/deep-promotion.d.ts +1 -0
  112. package/dist/src/agent/memory/dreaming/deep-promotion.js +11 -20
  113. package/dist/src/agent/memory/dreaming/deep-promotion.js.map +1 -1
  114. package/dist/src/agent/memory/dreaming/events.d.ts +3 -3
  115. package/dist/src/agent/memory/dreaming/events.js +6 -6
  116. package/dist/src/agent/memory/dreaming/events.js.map +1 -1
  117. package/dist/src/agent/memory/dreaming/last-run.d.ts +2 -2
  118. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  119. package/dist/src/agent/memory/dreaming/last-run.js.map +1 -1
  120. package/dist/src/agent/memory/dreaming/light-sweep.d.ts +1 -1
  121. package/dist/src/agent/memory/dreaming/light-sweep.js +3 -3
  122. package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -1
  123. package/dist/src/agent/memory/dreaming/preview.d.ts +1 -0
  124. package/dist/src/agent/memory/dreaming/preview.js +2 -2
  125. package/dist/src/agent/memory/dreaming/preview.js.map +1 -1
  126. package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +1 -1
  127. package/dist/src/agent/memory/dreaming/rem-patterns.js +13 -13
  128. package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -1
  129. package/dist/src/agent/memory/dreaming/scope.d.ts +14 -0
  130. package/dist/src/agent/memory/dreaming/scope.js +27 -0
  131. package/dist/src/agent/memory/dreaming/scope.js.map +1 -0
  132. package/dist/src/agent/memory/dreaming/short-term-store.d.ts +4 -4
  133. package/dist/src/agent/memory/dreaming/short-term-store.js +17 -17
  134. package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -1
  135. package/dist/src/agent/memory/summary-generator.d.ts +1 -1
  136. package/dist/src/agent/memory/summary-generator.js +1 -2
  137. package/dist/src/agent/memory/summary-generator.js.map +1 -1
  138. package/dist/src/agent/middleware/index.d.ts +1 -1
  139. package/dist/src/agent/middleware/self-verify.d.ts +44 -14
  140. package/dist/src/agent/middleware/self-verify.js +214 -44
  141. package/dist/src/agent/middleware/self-verify.js.map +1 -1
  142. package/dist/src/agent/orchestration/agent-event-handler.d.ts +2 -0
  143. package/dist/src/agent/orchestration/agent-event-handler.js +69 -14
  144. package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
  145. package/dist/src/agent/orchestration/agent-orchestrator.js +22 -12
  146. package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
  147. package/dist/src/agent/prompt/sections/tooling.js +18 -13
  148. package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
  149. package/dist/src/agent/prompt/system-reminder.js +2 -2
  150. package/dist/src/agent/prompt/system-reminder.js.map +1 -1
  151. package/dist/src/agent/sandbox/command-validator.js +12 -0
  152. package/dist/src/agent/sandbox/command-validator.js.map +1 -1
  153. package/dist/src/agent/sandbox/sanitize-env-vars.d.ts +2 -2
  154. package/dist/src/agent/sandbox/sanitize-env-vars.js +2 -2
  155. package/dist/src/agent/sandbox/sanitize-env-vars.js.map +1 -1
  156. package/dist/src/agent/sandbox/types.d.ts +2 -2
  157. package/dist/src/agent/sandbox/types.js.map +1 -1
  158. package/dist/src/agent/service/btw-query.js +1 -1
  159. package/dist/src/agent/service/btw-query.js.map +1 -1
  160. package/dist/src/agent/service/process-direct-one-shot.js +6 -2
  161. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  162. package/dist/src/agent/service/process-direct-streaming.js +12 -7
  163. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  164. package/dist/src/agent/service.d.ts +2 -1
  165. package/dist/src/agent/service.js +19 -6
  166. package/dist/src/agent/service.js.map +1 -1
  167. package/dist/src/agent/session/session-inspector.d.ts +5 -3
  168. package/dist/src/agent/session/session-inspector.js +16 -7
  169. package/dist/src/agent/session/session-inspector.js.map +1 -1
  170. package/dist/src/agent/skills/skill-tool-gating.js +1 -1
  171. package/dist/src/agent/skills/skill-tool-gating.js.map +1 -1
  172. package/dist/src/agent/source-context/types.d.ts +1 -1
  173. package/dist/src/agent/source-context/types.js +1 -1
  174. package/dist/src/agent/source-context/types.js.map +1 -1
  175. package/dist/src/agent/starter-agents.js +9 -9
  176. package/dist/src/agent/starter-agents.js.map +1 -1
  177. package/dist/src/agent/tools/apply-patch.d.ts +25 -0
  178. package/dist/src/agent/tools/apply-patch.js +310 -0
  179. package/dist/src/agent/tools/apply-patch.js.map +1 -0
  180. package/dist/src/agent/tools/concurrency.d.ts +8 -0
  181. package/dist/src/agent/tools/concurrency.js +31 -0
  182. package/dist/src/agent/tools/concurrency.js.map +1 -0
  183. package/dist/src/agent/tools/delegate-tool.d.ts +1 -1
  184. package/dist/src/agent/tools/delegate-tool.js +3 -3
  185. package/dist/src/agent/tools/delegate-tool.js.map +1 -1
  186. package/dist/src/agent/tools/dreaming-tool.d.ts +2 -0
  187. package/dist/src/agent/tools/dreaming-tool.js +13 -10
  188. package/dist/src/agent/tools/dreaming-tool.js.map +1 -1
  189. package/dist/src/agent/tools/exec-command.d.ts +30 -0
  190. package/dist/src/agent/tools/exec-command.js +228 -0
  191. package/dist/src/agent/tools/exec-command.js.map +1 -0
  192. package/dist/src/agent/tools/execute-code-tool.js +7 -4
  193. package/dist/src/agent/tools/execute-code-tool.js.map +1 -1
  194. package/dist/src/agent/tools/executor.d.ts +6 -4
  195. package/dist/src/agent/tools/executor.js +7 -4
  196. package/dist/src/agent/tools/executor.js.map +1 -1
  197. package/dist/src/agent/tools/factory.d.ts +6 -3
  198. package/dist/src/agent/tools/factory.js +14 -9
  199. package/dist/src/agent/tools/factory.js.map +1 -1
  200. package/dist/src/agent/tools/find.js +2 -0
  201. package/dist/src/agent/tools/find.js.map +1 -1
  202. package/dist/src/agent/tools/grep.js +2 -0
  203. package/dist/src/agent/tools/grep.js.map +1 -1
  204. package/dist/src/agent/tools/index.d.ts +5 -2
  205. package/dist/src/agent/tools/index.js +7 -4
  206. package/dist/src/agent/tools/list-dir.js +2 -0
  207. package/dist/src/agent/tools/list-dir.js.map +1 -1
  208. package/dist/src/agent/tools/memory-tool.d.ts +1 -0
  209. package/dist/src/agent/tools/memory-tool.js +2 -2
  210. package/dist/src/agent/tools/memory-tool.js.map +1 -1
  211. package/dist/src/agent/tools/metadata.d.ts +15 -0
  212. package/dist/src/agent/tools/metadata.js +26 -0
  213. package/dist/src/agent/tools/metadata.js.map +1 -0
  214. package/dist/src/agent/tools/read.js +2 -0
  215. package/dist/src/agent/tools/read.js.map +1 -1
  216. package/dist/src/agent/tools/skills-tools.d.ts +1 -1
  217. package/dist/src/agent/tools/skills-tools.js.map +1 -1
  218. package/dist/src/agent/tools/update-plan-tool.d.ts +11 -0
  219. package/dist/src/agent/tools/update-plan-tool.js +72 -0
  220. package/dist/src/agent/tools/update-plan-tool.js.map +1 -0
  221. package/dist/src/agent/tools/write.js +6 -0
  222. package/dist/src/agent/tools/write.js.map +1 -1
  223. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  224. package/dist/src/agent/workflow/builtins/debug-incident.js +1 -1
  225. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  226. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  227. package/dist/src/agent/workflow/builtins/pr-review.js +1 -1
  228. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  229. package/dist/src/agent/workflow/builtins/release-check.d.ts +1 -1
  230. package/dist/src/agent/workflow/builtins/release-check.js +1 -1
  231. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -1
  232. package/dist/src/agent/workflow/step-labels.js +7 -3
  233. package/dist/src/agent/workflow/step-labels.js.map +1 -1
  234. package/dist/src/agent/workflow/subagent-runner.d.ts +1 -0
  235. package/dist/src/agent/workflow/subagent-runner.js +5 -1
  236. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  237. package/dist/src/agent/workflow/workflow-child-tools.js +5 -0
  238. package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -1
  239. package/dist/src/chat-commands/agent-edit.js +1 -1
  240. package/dist/src/chat-commands/agent-edit.js.map +1 -1
  241. package/dist/src/chat-commands/builtins/review.d.ts +1 -0
  242. package/dist/src/chat-commands/builtins/review.js +167 -0
  243. package/dist/src/chat-commands/builtins/review.js.map +1 -0
  244. package/dist/src/chat-commands/index.d.ts +1 -0
  245. package/dist/src/chat-commands/index.js +3 -1
  246. package/dist/src/chat-commands/index.js.map +1 -1
  247. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  248. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  249. package/dist/src/connectors/health.d.ts +2 -1
  250. package/dist/src/connectors/health.js +59 -1
  251. package/dist/src/connectors/health.js.map +1 -1
  252. package/dist/src/connectors/install.d.ts +1 -0
  253. package/dist/src/connectors/install.js +28 -1
  254. package/dist/src/connectors/install.js.map +1 -1
  255. package/dist/src/connectors/instances.js +7 -0
  256. package/dist/src/connectors/instances.js.map +1 -1
  257. package/dist/src/connectors/materialize.js +2 -1
  258. package/dist/src/connectors/materialize.js.map +1 -1
  259. package/dist/src/connectors/registries/modelscope.js +38 -13
  260. package/dist/src/connectors/registries/modelscope.js.map +1 -1
  261. package/dist/src/connectors/types.d.ts +2 -0
  262. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  263. package/dist/src/gateway/agent-builtin-tools.js +3 -2
  264. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  265. package/dist/src/gateway/chat-stream/mapper.d.ts +6 -0
  266. package/dist/src/gateway/chat-stream/mapper.js +149 -6
  267. package/dist/src/gateway/chat-stream/mapper.js.map +1 -1
  268. package/dist/src/gateway/chat-stream/protocol.d.ts +51 -1
  269. package/dist/src/gateway/dreaming-automation-reconciler.d.ts +1 -0
  270. package/dist/src/gateway/dreaming-automation-reconciler.js +62 -42
  271. package/dist/src/gateway/dreaming-automation-reconciler.js.map +1 -1
  272. package/dist/src/gateway/hono/app.js +7 -1
  273. package/dist/src/gateway/hono/app.js.map +1 -1
  274. package/dist/src/gateway/hono/routes/commands-skills.js +21 -0
  275. package/dist/src/gateway/hono/routes/commands-skills.js.map +1 -1
  276. package/dist/src/gateway/hono/routes/connectors.js +46 -2
  277. package/dist/src/gateway/hono/routes/connectors.js.map +1 -1
  278. package/dist/src/gateway/hono/routes/dreaming.js +111 -66
  279. package/dist/src/gateway/hono/routes/dreaming.js.map +1 -1
  280. package/dist/src/gateway/hono/routes/index.js +2 -0
  281. package/dist/src/gateway/hono/routes/index.js.map +1 -1
  282. package/dist/src/gateway/hono/routes/lazy-bundles.js +1 -0
  283. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  284. package/dist/src/gateway/hono/routes/projects.js +886 -45
  285. package/dist/src/gateway/hono/routes/projects.js.map +1 -1
  286. package/dist/src/gateway/hono/routes/search.d.ts +3 -0
  287. package/dist/src/gateway/hono/routes/search.js +47 -0
  288. package/dist/src/gateway/hono/routes/search.js.map +1 -0
  289. package/dist/src/gateway/hono/routes/sessions.js +1 -0
  290. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  291. package/dist/src/gateway/security/dangerous-tools.js +1 -1
  292. package/dist/src/gateway/security/dangerous-tools.js.map +1 -1
  293. package/dist/src/gateway/service.js +17 -8
  294. package/dist/src/gateway/service.js.map +1 -1
  295. package/dist/src/infra/update-restart.js +1 -1
  296. package/dist/src/mcp/channel-bridge.js +1 -1
  297. package/dist/src/media/store.js +1 -1
  298. package/dist/src/media/store.js.map +1 -1
  299. package/dist/src/media/types.d.ts +1 -1
  300. package/dist/src/media/uri.js +2 -1
  301. package/dist/src/media/uri.js.map +1 -1
  302. package/dist/src/projects/index.d.ts +2 -0
  303. package/dist/src/projects/index.js +3 -1
  304. package/dist/src/projects/project-agent-suggestion.d.ts +14 -0
  305. package/dist/src/projects/project-agent-suggestion.js +19 -0
  306. package/dist/src/projects/project-agent-suggestion.js.map +1 -0
  307. package/dist/src/projects/project-kind.d.ts +13 -0
  308. package/dist/src/projects/project-kind.js +147 -0
  309. package/dist/src/projects/project-kind.js.map +1 -0
  310. package/dist/src/projects/project-service.d.ts +1 -0
  311. package/dist/src/projects/project-service.js +1 -1
  312. package/dist/src/projects/project-service.js.map +1 -1
  313. package/dist/src/projects/project-store.js +32 -8
  314. package/dist/src/projects/project-store.js.map +1 -1
  315. package/dist/src/projects/types.d.ts +1 -0
  316. package/dist/src/review/review-git.d.ts +48 -0
  317. package/dist/src/review/review-git.js +333 -0
  318. package/dist/src/review/review-git.js.map +1 -0
  319. package/dist/src/review/review-types.d.ts +22 -0
  320. package/dist/src/review/review-types.js +29 -0
  321. package/dist/src/review/review-types.js.map +1 -0
  322. package/dist/src/session/session-context-for-llm.d.ts +2 -0
  323. package/dist/src/session/session-context-for-llm.js +165 -1
  324. package/dist/src/session/session-context-for-llm.js.map +1 -1
  325. package/dist/src/session/store.js +2 -2
  326. package/dist/src/session/store.js.map +1 -1
  327. package/dist/src/storage/sqlite/migrations/021_projects_fts.sql +21 -0
  328. package/dist/src/storage/sqlite/migrations/022_work_items.sql +61 -0
  329. package/dist/src/storage/sqlite/migrations/023_work_item_attachments.sql +15 -0
  330. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  331. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  332. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  333. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  334. package/dist/src/storage/sqlite/schema.js +1 -1
  335. package/dist/src/storage/sqlite/schema.sql +78 -0
  336. package/dist/src/tui/backends/embedded-backend.d.ts +1 -0
  337. package/dist/src/tui/backends/embedded-backend.js +20 -0
  338. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  339. package/dist/src/tui/backends/gateway-sse-backend.d.ts +2 -0
  340. package/dist/src/tui/backends/gateway-sse-backend.js +7 -0
  341. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  342. package/dist/src/tui/components/assistant-message.js +47 -2
  343. package/dist/src/tui/components/assistant-message.js.map +1 -1
  344. package/dist/src/tui/components/tool-execution.d.ts +8 -0
  345. package/dist/src/tui/components/tool-execution.js +87 -77
  346. package/dist/src/tui/components/tool-execution.js.map +1 -1
  347. package/dist/src/tui/components/visual-truncate.d.ts +1 -0
  348. package/dist/src/tui/components/visual-truncate.js +28 -1
  349. package/dist/src/tui/components/visual-truncate.js.map +1 -1
  350. package/dist/src/tui/tool-summary.d.ts +22 -0
  351. package/dist/src/tui/tool-summary.js +212 -0
  352. package/dist/src/tui/tool-summary.js.map +1 -0
  353. package/dist/src/tui/tui-agent-events.js +25 -0
  354. package/dist/src/tui/tui-agent-events.js.map +1 -1
  355. package/dist/src/tui/tui-backend.d.ts +4 -0
  356. package/dist/src/tui/tui-commands.d.ts +1 -0
  357. package/dist/src/tui/tui-commands.js +110 -4
  358. package/dist/src/tui/tui-commands.js.map +1 -1
  359. package/dist/src/tui/tui-picker-overlay.d.ts +2 -0
  360. package/dist/src/tui/tui-picker-overlay.js +153 -1
  361. package/dist/src/tui/tui-picker-overlay.js.map +1 -1
  362. package/dist/src/tui/tui-tool-diff.d.ts +15 -0
  363. package/dist/src/tui/tui-tool-diff.js +178 -7
  364. package/dist/src/tui/tui-tool-diff.js.map +1 -1
  365. package/dist/src/tui/tui.js +16 -3
  366. package/dist/src/tui/tui.js.map +1 -1
  367. package/dist/src/work-items/agent-context.d.ts +3 -0
  368. package/dist/src/work-items/agent-context.js +39 -0
  369. package/dist/src/work-items/agent-context.js.map +1 -0
  370. package/dist/src/work-items/index.d.ts +4 -0
  371. package/dist/src/work-items/index.js +4 -0
  372. package/dist/src/work-items/types.d.ts +112 -0
  373. package/dist/src/work-items/types.js +1 -0
  374. package/dist/src/work-items/work-item-service.d.ts +33 -0
  375. package/dist/src/work-items/work-item-service.js +180 -0
  376. package/dist/src/work-items/work-item-service.js.map +1 -0
  377. package/dist/src/work-items/work-item-store.d.ts +21 -0
  378. package/dist/src/work-items/work-item-store.js +302 -0
  379. package/dist/src/work-items/work-item-store.js.map +1 -0
  380. package/dist/src/workflows/service/workflow-run-service.js +4 -3
  381. package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
  382. package/dist/src/workflows/service/workflow-session-bridge.js +3 -0
  383. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
  384. package/package.json +1 -1
  385. package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
  386. package/dist/gateway/static/root/assets/agents-CxDSFHGx.js +0 -2
  387. package/dist/gateway/static/root/assets/app-management-settings-panel-CbEy9sR1.js +0 -1
  388. package/dist/gateway/static/root/assets/appearance-settings-CTHja6n9.js +0 -1
  389. package/dist/gateway/static/root/assets/apps-page-CyZdgqvm.js +0 -1
  390. package/dist/gateway/static/root/assets/automation-suggestion-card-KnYaP7NT.js +0 -1
  391. package/dist/gateway/static/root/assets/automations-page-8VLKM4au.js +0 -1
  392. package/dist/gateway/static/root/assets/browser-settings-page-5tPg0DcP.js +0 -8
  393. package/dist/gateway/static/root/assets/capability-presets-api-CEJ44WCU.js +0 -1
  394. package/dist/gateway/static/root/assets/capability-presets-settings-panel-BJW3aVST.js +0 -1
  395. package/dist/gateway/static/root/assets/channels-settings-CorUCTrs.js +0 -1
  396. package/dist/gateway/static/root/assets/code-editor-dZTY2dRW.js +0 -1
  397. package/dist/gateway/static/root/assets/connectors-page-VKstC3sP.js +0 -2
  398. package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +0 -1
  399. package/dist/gateway/static/root/assets/dist-wO9a2_ID.js +0 -1
  400. package/dist/gateway/static/root/assets/dreaming-settings-m1-wmz4o.js +0 -1
  401. package/dist/gateway/static/root/assets/extension-debug-page-C9qQQrBS.js +0 -1
  402. package/dist/gateway/static/root/assets/extension-page-DcBBMEGl.js +0 -1
  403. package/dist/gateway/static/root/assets/extension-settings-page-BQ-jMMP9.js +0 -1
  404. package/dist/gateway/static/root/assets/gateway-settings-hQtgVmSR.js +0 -1
  405. package/dist/gateway/static/root/assets/goal-create-dialog-BuIMDx6C.js +0 -1
  406. package/dist/gateway/static/root/assets/goal-detail-page-FEypCq74.js +0 -1
  407. package/dist/gateway/static/root/assets/goals-page-C4-2_MLG.js +0 -2
  408. package/dist/gateway/static/root/assets/goals-settings-DDrdRMxB.js +0 -1
  409. package/dist/gateway/static/root/assets/heartbeat-settings-BJVok3Sl.js +0 -1
  410. package/dist/gateway/static/root/assets/index-CBDr5xjB.css +0 -1
  411. package/dist/gateway/static/root/assets/index-T0SaNd5a.js +0 -191
  412. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-C2TuQppb.js +0 -1
  413. package/dist/gateway/static/root/assets/logs-page-DwkwPgid.js +0 -2
  414. package/dist/gateway/static/root/assets/markdown-split-DhONQ81k.js +0 -1
  415. package/dist/gateway/static/root/assets/messages-avfvyZuQ.js +0 -3
  416. package/dist/gateway/static/root/assets/models-hub-panel-huNY4A1G.js +0 -2
  417. package/dist/gateway/static/root/assets/notes-page-DdfGuEZ1.js +0 -1
  418. package/dist/gateway/static/root/assets/notes-workbench-B0bT9Hqa.js +0 -2
  419. package/dist/gateway/static/root/assets/project-detail-page-CjxhPorG.js +0 -1
  420. package/dist/gateway/static/root/assets/projects-page-C4Z5OJMF.js +0 -1
  421. package/dist/gateway/static/root/assets/remote-access-hub-CVCOPUVd.js +0 -1
  422. package/dist/gateway/static/root/assets/sessions-page-B4qExXHC.js +0 -1
  423. package/dist/gateway/static/root/assets/settings-advanced-gate-Ce8yLTBi.js +0 -1
  424. package/dist/gateway/static/root/assets/setup-status-panel-Kyc4eUf0.js +0 -1
  425. package/dist/gateway/static/root/assets/shares-settings-Bu76CMiC.js +0 -1
  426. package/dist/gateway/static/root/assets/skill-reload-api-D-CFg86j.js +0 -1
  427. package/dist/gateway/static/root/assets/skills-page-CUU4f6Ss.js +0 -2
  428. package/dist/gateway/static/root/assets/voice-api-key-field-C1MrQD8R.js +0 -1
  429. package/dist/gateway/static/root/assets/workflow-run-setup-panel-DqizIcY5.js +0 -3
  430. package/dist/gateway/static/root/assets/workflows-page-B3sVNPFk.js +0 -18
  431. package/dist/src/agent/tools/edit.d.ts +0 -14
  432. package/dist/src/agent/tools/edit.js +0 -132
  433. package/dist/src/agent/tools/edit.js.map +0 -1
  434. package/dist/src/agent/tools/shell.d.ts +0 -15
  435. package/dist/src/agent/tools/shell.js +0 -158
  436. package/dist/src/agent/tools/shell.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { buildSessionKey, init_session_key } from "../../../routing/session-key.js";
1
2
  import { getSqliteDatabase } from "../../../storage/sqlite/transaction.js";
2
3
  import { GoalService } from "../../../goals/goal-service.js";
3
4
  import "../../../goals/index.js";
@@ -7,12 +8,18 @@ import { listMemoryRecords, upsertMemoryRecord } from "../../../storage/sqlite/m
7
8
  import "../../../storage/sqlite/index.js";
8
9
  import { ProjectWorkspaceConflictError, ProjectWorkspaceMissingError } from "../../../projects/workspace-project.js";
9
10
  import { isValidProjectAgentId, normalizeProjectAgentId, resolveProjectAgentId } from "../../../projects/project-agent.js";
11
+ import { inferProjectKind } from "../../../projects/project-kind.js";
12
+ import { inferSuggestedProjectDefaultAgentId } from "../../../projects/project-agent-suggestion.js";
10
13
  import { buildProjectLoopOverview } from "../../../projects/project-loop.js";
11
14
  import "../../../projects/index.js";
15
+ import { WORK_ITEM_ATTACHMENT_MAX_BYTES, WorkItemService } from "../../../work-items/work-item-service.js";
16
+ import { buildWorkItemAgentContext } from "../../../work-items/agent-context.js";
17
+ import "../../../work-items/index.js";
12
18
  import { createGatewayRouteLogger } from "../lib/route-logger.js";
13
19
  import * as path$1 from "node:path";
14
- import { readdir, realpath, stat } from "node:fs/promises";
20
+ import { readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
15
21
  //#region src/gateway/hono/routes/projects.ts
22
+ init_session_key();
16
23
  const log = createGatewayRouteLogger("Projects");
17
24
  function parseProjectStatus(raw) {
18
25
  return raw === "active" || raw === "paused" || raw === "archived" ? raw : void 0;
@@ -22,6 +29,9 @@ function parseLimit(raw, fallback = 50) {
22
29
  const n = Number.parseInt(raw, 10);
23
30
  return Number.isFinite(n) ? Math.min(500, Math.max(1, n)) : fallback;
24
31
  }
32
+ function isHiddenEmptyProjectChatShell(session) {
33
+ return session.hiddenFromSessionList === true && session.messageCount === 0 && session.customData?.genericNewChatShell !== false && Boolean(session.routing?.peerId?.startsWith("chat_"));
34
+ }
25
35
  function textField(body, key) {
26
36
  const value = body[key];
27
37
  return typeof value === "string" ? value : void 0;
@@ -31,7 +41,87 @@ function optionalTextField(body, key) {
31
41
  const value = body[key];
32
42
  return typeof value === "string" ? value : null;
33
43
  }
44
+ function finiteNumberField(value) {
45
+ if (typeof value === "number" && Number.isFinite(value)) return value;
46
+ if (typeof value === "string" && value.trim()) {
47
+ const parsed = Number(value);
48
+ return Number.isFinite(parsed) ? parsed : void 0;
49
+ }
50
+ }
51
+ function collectUploadedFiles(body) {
52
+ const raw = body.file ?? body.files ?? body.attachments;
53
+ return (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((value) => value instanceof File);
54
+ }
55
+ function validateUploadedWorkItemFiles(files) {
56
+ if (files.length > 10) return `Too many attachments (max 10)`;
57
+ for (const file of files) if (file.size > 26214400) return `Attachment ${JSON.stringify(file.name || "upload")} exceeds ${Math.floor(WORK_ITEM_ATTACHMENT_MAX_BYTES / (1024 * 1024))}MB limit`;
58
+ return null;
59
+ }
60
+ async function uploadedFileToBuffer(file) {
61
+ return {
62
+ name: file.name || "upload",
63
+ buffer: Buffer.from(await file.arrayBuffer()),
64
+ mimeType: file.type || "application/octet-stream"
65
+ };
66
+ }
67
+ function parseCsvEnum(raw, values) {
68
+ if (!raw) return void 0;
69
+ const allowed = new Set(values);
70
+ const parsed = raw.split(",").map((item) => item.trim()).filter((item) => allowed.has(item));
71
+ return parsed.length > 0 ? [...new Set(parsed)] : void 0;
72
+ }
73
+ function parseEnumValue(raw, values) {
74
+ if (typeof raw !== "string") return void 0;
75
+ return values.includes(raw) ? raw : void 0;
76
+ }
77
+ const WORK_ITEM_STATUSES = [
78
+ "backlog",
79
+ "todo",
80
+ "in_progress",
81
+ "blocked",
82
+ "needs_input",
83
+ "in_review",
84
+ "done",
85
+ "cancelled"
86
+ ];
87
+ const WORK_ITEM_PRIORITIES = [
88
+ "urgent",
89
+ "high",
90
+ "normal",
91
+ "low"
92
+ ];
93
+ const WORK_ITEM_SUGGESTION_STATUSES = [
94
+ "pending",
95
+ "applied",
96
+ "dismissed"
97
+ ];
34
98
  const SKIP_FILE_NAMES = new Set([".git", "node_modules"]);
99
+ const PROJECT_FILE_MIME_BY_EXT = {
100
+ png: "image/png",
101
+ jpg: "image/jpeg",
102
+ jpeg: "image/jpeg",
103
+ gif: "image/gif",
104
+ webp: "image/webp",
105
+ bmp: "image/bmp",
106
+ svg: "image/svg+xml",
107
+ pdf: "application/pdf",
108
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
109
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
110
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
111
+ txt: "text/plain",
112
+ md: "text/markdown",
113
+ json: "application/json",
114
+ html: "text/html",
115
+ css: "text/css",
116
+ js: "text/javascript",
117
+ ts: "text/typescript",
118
+ mp3: "audio/mpeg",
119
+ wav: "audio/wav",
120
+ ogg: "audio/ogg",
121
+ webm: "video/webm",
122
+ mp4: "video/mp4",
123
+ mov: "video/quicktime"
124
+ };
35
125
  function normalizeRelativePath(raw) {
36
126
  return (raw ?? "").trim().replaceAll("\\", "/").replace(/^\/+/, "");
37
127
  }
@@ -39,6 +129,136 @@ function isWithinDirectory(root, target) {
39
129
  const relativePath = path$1.relative(root, target);
40
130
  return relativePath === "" || !relativePath.startsWith("..") && !path$1.isAbsolute(relativePath);
41
131
  }
132
+ async function resolveProjectWorkspaceRoot(service, projectId) {
133
+ const project = service.projects.get(projectId);
134
+ if (!project) return {
135
+ ok: false,
136
+ status: 404,
137
+ error: "Project not found"
138
+ };
139
+ if (!project.workspaceRoot?.trim()) return {
140
+ ok: false,
141
+ status: 400,
142
+ error: "Project workspace root is not set"
143
+ };
144
+ try {
145
+ const root = await realpath(project.workspaceRoot);
146
+ if (!(await stat(root)).isDirectory()) return {
147
+ ok: false,
148
+ status: 400,
149
+ error: "Project workspace root is not a directory"
150
+ };
151
+ return {
152
+ ok: true,
153
+ projectId: project.id,
154
+ root
155
+ };
156
+ } catch (err) {
157
+ log.warn({
158
+ err,
159
+ projectId: project.id,
160
+ path: project.workspaceRoot
161
+ }, "project workspace root unavailable");
162
+ return {
163
+ ok: false,
164
+ status: 404,
165
+ error: "Project workspace root is unavailable"
166
+ };
167
+ }
168
+ }
169
+ async function resolveProjectWorkspacePath(service, projectId, rawPath) {
170
+ const rootResult = await resolveProjectWorkspaceRoot(service, projectId);
171
+ if (rootResult.ok === false) return {
172
+ ok: false,
173
+ status: rootResult.status,
174
+ error: rootResult.error
175
+ };
176
+ const requestedPath = normalizeRelativePath(rawPath);
177
+ const candidate = path$1.resolve(rootResult.root, requestedPath);
178
+ if (!isWithinDirectory(rootResult.root, candidate)) return {
179
+ ok: false,
180
+ status: 400,
181
+ error: "Path is outside project workspace"
182
+ };
183
+ const relativePath = path$1.relative(rootResult.root, candidate).split(path$1.sep).join("/");
184
+ return {
185
+ ok: true,
186
+ projectId: rootResult.projectId,
187
+ root: rootResult.root,
188
+ requestedPath,
189
+ absolutePath: candidate,
190
+ relativePath: relativePath === "." ? "" : relativePath
191
+ };
192
+ }
193
+ async function resolveExistingProjectWorkspacePath(service, projectId, rawPath) {
194
+ const resolved = await resolveProjectWorkspacePath(service, projectId, rawPath);
195
+ if (resolved.ok === false) return resolved;
196
+ if (!resolved.requestedPath.trim()) return {
197
+ ok: false,
198
+ status: 400,
199
+ error: "Missing path"
200
+ };
201
+ let realTarget;
202
+ try {
203
+ realTarget = await realpath(resolved.absolutePath);
204
+ } catch {
205
+ return {
206
+ ok: false,
207
+ status: 404,
208
+ error: "Not found"
209
+ };
210
+ }
211
+ if (!isWithinDirectory(resolved.root, realTarget)) return {
212
+ ok: false,
213
+ status: 400,
214
+ error: "Path is outside project workspace"
215
+ };
216
+ const targetStat = await stat(realTarget);
217
+ return {
218
+ ...resolved,
219
+ absolutePath: realTarget,
220
+ stat: targetStat
221
+ };
222
+ }
223
+ async function resolveProjectWorkspaceWritePath(service, projectId, rawPath) {
224
+ const resolved = await resolveProjectWorkspacePath(service, projectId, rawPath);
225
+ if (resolved.ok === false) return resolved;
226
+ if (!resolved.requestedPath.trim()) return {
227
+ ok: false,
228
+ status: 400,
229
+ error: "Missing path"
230
+ };
231
+ try {
232
+ const realTarget = await realpath(resolved.absolutePath);
233
+ if (!isWithinDirectory(resolved.root, realTarget)) return {
234
+ ok: false,
235
+ status: 400,
236
+ error: "Path is outside project workspace"
237
+ };
238
+ return {
239
+ ...resolved,
240
+ absolutePath: realTarget,
241
+ existingStat: await stat(realTarget)
242
+ };
243
+ } catch {
244
+ const parentPath = path$1.dirname(resolved.absolutePath);
245
+ try {
246
+ const realParent = await realpath(parentPath);
247
+ if (!isWithinDirectory(resolved.root, realParent)) return {
248
+ ok: false,
249
+ status: 400,
250
+ error: "Path is outside project workspace"
251
+ };
252
+ } catch {
253
+ return {
254
+ ok: false,
255
+ status: 404,
256
+ error: "Parent directory not found"
257
+ };
258
+ }
259
+ return resolved;
260
+ }
261
+ }
42
262
  function textFromTranscriptContent(content) {
43
263
  if (typeof content === "string") return content;
44
264
  if (!Array.isArray(content)) return "";
@@ -64,6 +284,36 @@ function buildSessionSummaryMemoryContent(sessionKey, explicitSummary) {
64
284
  }).filter(Boolean);
65
285
  return lines.length > 0 ? [`Session summary for ${sessionKey}:`, ...lines].join("\n") : `Session summary for ${sessionKey}: no transcript content available.`;
66
286
  }
287
+ function buildWorkItemChatSessionMetadata(params) {
288
+ return {
289
+ name: params.title,
290
+ tags: ["work-item"],
291
+ sourceChannel: "webchat",
292
+ sourceChatId: [
293
+ params.accountId,
294
+ "direct",
295
+ params.peerId
296
+ ].join(":"),
297
+ sessionType: "chat",
298
+ hiddenFromSessionList: true,
299
+ routing: {
300
+ agentId: params.agentId,
301
+ source: "webchat",
302
+ accountId: params.accountId,
303
+ peerKind: "direct",
304
+ peerId: params.peerId
305
+ },
306
+ customData: {
307
+ genericNewChatShell: false,
308
+ sourceBinding: {
309
+ kind: "work_item",
310
+ sourceId: params.workItemId,
311
+ version: String(params.updatedAt),
312
+ attachedAt: Date.now()
313
+ }
314
+ }
315
+ };
316
+ }
67
317
  function listFailedProjectWorkflowRuns(projectId, limit = 5) {
68
318
  return getSqliteDatabase().prepare(`SELECT run_id, definition_id, status, created_at_ms, error_message
69
319
  FROM workflow_runs
@@ -99,6 +349,7 @@ function projectDigestMemoryRecordId(projectId) {
99
349
  function registerProjectsRoutes(authenticated, deps) {
100
350
  const { service } = deps;
101
351
  const goals = new GoalService();
352
+ const workItems = new WorkItemService();
102
353
  authenticated.post("/api/projects", async (c) => {
103
354
  const body = await c.req.json().catch(() => ({}));
104
355
  const name = textField(body, "name")?.trim();
@@ -107,7 +358,15 @@ function registerProjectsRoutes(authenticated, deps) {
107
358
  ok: false,
108
359
  error: "Missing name"
109
360
  }, 400);
110
- const defaultAgentId = normalizeProjectAgentId(textField(body, "defaultAgentId"));
361
+ const hasDefaultAgentPatch = Object.hasOwn(body, "defaultAgentId");
362
+ const explicitDefaultAgentId = normalizeProjectAgentId(textField(body, "defaultAgentId"));
363
+ const defaultAgentId = hasDefaultAgentPatch ? explicitDefaultAgentId : inferSuggestedProjectDefaultAgentId({
364
+ config: service.currentConfig,
365
+ name,
366
+ description: textField(body, "description"),
367
+ workspaceRoot,
368
+ projectKind: textField(body, "projectKind")
369
+ });
111
370
  if (!isValidProjectAgentId(service.currentConfig, defaultAgentId)) return c.json({
112
371
  ok: false,
113
372
  error: "Default agent not found"
@@ -119,6 +378,7 @@ function registerProjectsRoutes(authenticated, deps) {
119
378
  defaultAgentId,
120
379
  workspaceRoot,
121
380
  createWorkspaceRoot: body.createWorkspaceRoot === true,
381
+ projectKind: textField(body, "projectKind"),
122
382
  brief: textField(body, "brief"),
123
383
  instructions: textField(body, "instructions")
124
384
  });
@@ -167,6 +427,25 @@ function registerProjectsRoutes(authenticated, deps) {
167
427
  suggestions: sessionKey ? service.projects.suggestProjectsForSession(sessionKey) : []
168
428
  });
169
429
  });
430
+ authenticated.post("/api/projects/infer-defaults", async (c) => {
431
+ const body = await c.req.json().catch(() => ({}));
432
+ const input = {
433
+ name: textField(body, "name"),
434
+ description: textField(body, "description"),
435
+ workspaceRoot: textField(body, "workspaceRoot"),
436
+ projectKind: textField(body, "projectKind")
437
+ };
438
+ const inference = inferProjectKind(input);
439
+ const defaultAgentId = inferSuggestedProjectDefaultAgentId({
440
+ config: service.currentConfig,
441
+ ...input
442
+ });
443
+ return c.json({
444
+ ok: true,
445
+ inference,
446
+ defaultAgentId
447
+ });
448
+ });
170
449
  authenticated.post("/api/projects/resolve-workspace", async (c) => {
171
450
  const body = await c.req.json().catch(() => ({}));
172
451
  const workspacePath = textField(body, "workspacePath")?.trim();
@@ -175,11 +454,17 @@ function registerProjectsRoutes(authenticated, deps) {
175
454
  error: "Missing workspacePath"
176
455
  }, 400);
177
456
  const agentId = textField(body, "agentId")?.trim() || "main";
457
+ const suggestedDefaultAgentId = inferSuggestedProjectDefaultAgentId({
458
+ config: service.currentConfig,
459
+ workspaceRoot: workspacePath,
460
+ projectKind: textField(body, "projectKind")
461
+ });
178
462
  let match;
179
463
  try {
180
464
  match = service.projects.resolveOrCreateForWorkspacePath({
181
465
  workspacePath,
182
466
  agentId,
467
+ defaultAgentId: suggestedDefaultAgentId,
183
468
  autoCreate: body.autoCreate !== false
184
469
  });
185
470
  } catch (error) {
@@ -195,7 +480,9 @@ function registerProjectsRoutes(authenticated, deps) {
195
480
  project: null
196
481
  });
197
482
  const sessionKey = textField(body, "sessionKey")?.trim();
198
- if (sessionKey) {
483
+ const projectDefaultAgentId = normalizeProjectAgentId(match.project.defaultAgentId);
484
+ const shouldBindRequestedSession = !projectDefaultAgentId || projectDefaultAgentId === normalizeProjectAgentId(agentId);
485
+ if (sessionKey && shouldBindRequestedSession) {
199
486
  if (!getSessionMetadata(sessionKey)) await service.sessionIndexInstance.saveMessages(sessionKey, [], { metadata: {
200
487
  sourceChannel: "tui",
201
488
  sourceChatId: `default:direct:${sessionKey}`,
@@ -260,6 +547,413 @@ function registerProjectsRoutes(authenticated, deps) {
260
547
  }
261
548
  });
262
549
  });
550
+ authenticated.get("/api/projects/:id/work-items", async (c) => {
551
+ const project = service.projects.get(c.req.param("id"));
552
+ if (!project) return c.json({
553
+ ok: false,
554
+ error: "Project not found"
555
+ }, 404);
556
+ const result = workItems.listProjectWorkItems(project.id, {
557
+ status: parseCsvEnum(c.req.query("status"), WORK_ITEM_STATUSES),
558
+ priority: parseCsvEnum(c.req.query("priority"), WORK_ITEM_PRIORITIES),
559
+ includeArchived: c.req.query("includeArchived") === "true",
560
+ search: c.req.query("search"),
561
+ sortBy: c.req.query("sortBy"),
562
+ sortOrder: c.req.query("sortOrder"),
563
+ limit: parseLimit(c.req.query("limit")),
564
+ offset: c.req.query("offset") ? Math.max(0, Number.parseInt(c.req.query("offset"), 10) || 0) : void 0
565
+ });
566
+ return c.json({
567
+ ok: true,
568
+ ...result
569
+ });
570
+ });
571
+ authenticated.post("/api/projects/:id/work-items", async (c) => {
572
+ const project = service.projects.get(c.req.param("id"));
573
+ if (!project) return c.json({
574
+ ok: false,
575
+ error: "Project not found"
576
+ }, 404);
577
+ const contentType = c.req.header("content-type") || "";
578
+ let body;
579
+ let files = [];
580
+ if (contentType.includes("multipart/form-data")) {
581
+ try {
582
+ body = await c.req.parseBody({ all: true });
583
+ } catch {
584
+ return c.json({
585
+ ok: false,
586
+ error: "Invalid multipart body"
587
+ }, 400);
588
+ }
589
+ files = collectUploadedFiles(body);
590
+ } else body = await c.req.json().catch(() => ({}));
591
+ const attachmentError = validateUploadedWorkItemFiles(files);
592
+ if (attachmentError) return c.json({
593
+ ok: false,
594
+ error: attachmentError
595
+ }, 413);
596
+ const title = textField(body, "title")?.trim();
597
+ if (!title) return c.json({
598
+ ok: false,
599
+ error: "Work item title is required"
600
+ }, 400);
601
+ const status = parseEnumValue(body.status, WORK_ITEM_STATUSES);
602
+ const priority = parseEnumValue(body.priority, WORK_ITEM_PRIORITIES);
603
+ const dueAt = finiteNumberField(body.dueAt);
604
+ if (Object.hasOwn(body, "status") && !status) return c.json({
605
+ ok: false,
606
+ error: "Invalid work item status"
607
+ }, 400);
608
+ if (Object.hasOwn(body, "priority") && !priority) return c.json({
609
+ ok: false,
610
+ error: "Invalid work item priority"
611
+ }, 400);
612
+ if (Object.hasOwn(body, "dueAt") && dueAt === void 0 && body.dueAt !== null) return c.json({
613
+ ok: false,
614
+ error: "Invalid dueAt"
615
+ }, 400);
616
+ const uploads = [];
617
+ for (const file of files) uploads.push(await uploadedFileToBuffer(file));
618
+ const item = workItems.createProjectWorkItem(project.id, {
619
+ title,
620
+ description: textField(body, "description"),
621
+ status,
622
+ priority,
623
+ ownerAgentId: textField(body, "ownerAgentId") || project.defaultAgentId || void 0,
624
+ nextAction: textField(body, "nextAction"),
625
+ blockedReason: textField(body, "blockedReason"),
626
+ dueAt
627
+ });
628
+ for (const upload of uploads) await workItems.addAttachment(item.id, upload);
629
+ const created = files.length > 0 ? workItems.getWorkItem(item.id) ?? item : item;
630
+ return c.json({
631
+ ok: true,
632
+ item: created
633
+ }, 201);
634
+ });
635
+ authenticated.get("/api/work-items/:id", async (c) => {
636
+ const item = workItems.getWorkItem(c.req.param("id"));
637
+ if (!item) return c.json({
638
+ ok: false,
639
+ error: "Work item not found"
640
+ }, 404);
641
+ return c.json({
642
+ ok: true,
643
+ item
644
+ });
645
+ });
646
+ authenticated.get("/api/work-items/:id/attachments", async (c) => {
647
+ const attachments = workItems.listAttachments(c.req.param("id"));
648
+ if (!attachments) return c.json({
649
+ ok: false,
650
+ error: "Work item not found"
651
+ }, 404);
652
+ return c.json({
653
+ ok: true,
654
+ attachments
655
+ });
656
+ });
657
+ authenticated.post("/api/work-items/:id/attachments", async (c) => {
658
+ const item = workItems.getWorkItem(c.req.param("id"));
659
+ if (!item) return c.json({
660
+ ok: false,
661
+ error: "Work item not found"
662
+ }, 404);
663
+ let body;
664
+ try {
665
+ body = await c.req.parseBody({ all: true });
666
+ } catch {
667
+ return c.json({
668
+ ok: false,
669
+ error: "Invalid multipart body"
670
+ }, 400);
671
+ }
672
+ const files = collectUploadedFiles(body);
673
+ if (!files.length) return c.json({
674
+ ok: false,
675
+ error: "Missing file field"
676
+ }, 400);
677
+ const attachmentError = validateUploadedWorkItemFiles(files);
678
+ if (attachmentError) return c.json({
679
+ ok: false,
680
+ error: attachmentError
681
+ }, 413);
682
+ const attachments = [];
683
+ for (const file of files) {
684
+ const upload = await uploadedFileToBuffer(file);
685
+ const attachment = await workItems.addAttachment(item.id, upload);
686
+ if (attachment) attachments.push(attachment);
687
+ }
688
+ return c.json({
689
+ ok: true,
690
+ attachments,
691
+ item: workItems.getWorkItem(item.id)
692
+ }, 201);
693
+ });
694
+ authenticated.get("/api/work-items/:id/attachments/:attachmentId/content", async (c) => {
695
+ const result = await workItems.readAttachment(c.req.param("id"), c.req.param("attachmentId"));
696
+ if (!result) return c.json({
697
+ ok: false,
698
+ error: "Attachment not found"
699
+ }, 404);
700
+ return new Response(result.buffer, { headers: {
701
+ "Content-Type": result.attachment.mimeType,
702
+ "Content-Length": String(result.attachment.size),
703
+ "Content-Disposition": `inline; filename="${encodeURIComponent(result.attachment.fileName)}"`,
704
+ "Cache-Control": "private, max-age=31536000, immutable"
705
+ } });
706
+ });
707
+ authenticated.delete("/api/work-items/:id/attachments/:attachmentId", async (c) => {
708
+ const attachment = await workItems.removeAttachment(c.req.param("id"), c.req.param("attachmentId"));
709
+ if (!attachment) return c.json({
710
+ ok: false,
711
+ error: "Attachment not found"
712
+ }, 404);
713
+ return c.json({
714
+ ok: true,
715
+ attachment,
716
+ item: workItems.getWorkItem(c.req.param("id"))
717
+ });
718
+ });
719
+ authenticated.patch("/api/work-items/:id", async (c) => {
720
+ const body = await c.req.json().catch(() => ({}));
721
+ const status = Object.hasOwn(body, "status") ? parseEnumValue(body.status, WORK_ITEM_STATUSES) : void 0;
722
+ const priority = Object.hasOwn(body, "priority") ? parseEnumValue(body.priority, WORK_ITEM_PRIORITIES) : void 0;
723
+ const dueAt = Object.hasOwn(body, "dueAt") ? body.dueAt === null ? null : typeof body.dueAt === "number" && Number.isFinite(body.dueAt) ? body.dueAt : void 0 : void 0;
724
+ const archivedAt = Object.hasOwn(body, "archivedAt") ? body.archivedAt === null ? null : typeof body.archivedAt === "number" && Number.isFinite(body.archivedAt) ? body.archivedAt : void 0 : void 0;
725
+ if (status === void 0 && Object.hasOwn(body, "status")) return c.json({
726
+ ok: false,
727
+ error: "Invalid work item status"
728
+ }, 400);
729
+ if (priority === void 0 && Object.hasOwn(body, "priority")) return c.json({
730
+ ok: false,
731
+ error: "Invalid work item priority"
732
+ }, 400);
733
+ if (dueAt === void 0 && Object.hasOwn(body, "dueAt")) return c.json({
734
+ ok: false,
735
+ error: "Invalid dueAt"
736
+ }, 400);
737
+ if (archivedAt === void 0 && Object.hasOwn(body, "archivedAt")) return c.json({
738
+ ok: false,
739
+ error: "Invalid archivedAt"
740
+ }, 400);
741
+ const item = workItems.updateWorkItem(c.req.param("id"), {
742
+ ...Object.hasOwn(body, "title") ? { title: textField(body, "title") ?? "" } : {},
743
+ ...Object.hasOwn(body, "description") ? { description: optionalTextField(body, "description") } : {},
744
+ ...Object.hasOwn(body, "status") ? { status } : {},
745
+ ...Object.hasOwn(body, "priority") ? { priority } : {},
746
+ ...Object.hasOwn(body, "ownerAgentId") ? { ownerAgentId: optionalTextField(body, "ownerAgentId") } : {},
747
+ ...Object.hasOwn(body, "nextAction") ? { nextAction: optionalTextField(body, "nextAction") } : {},
748
+ ...Object.hasOwn(body, "blockedReason") ? { blockedReason: optionalTextField(body, "blockedReason") } : {},
749
+ ...Object.hasOwn(body, "dueAt") ? { dueAt } : {},
750
+ ...Object.hasOwn(body, "archivedAt") ? { archivedAt } : {}
751
+ });
752
+ if (!item) return c.json({
753
+ ok: false,
754
+ error: "Work item not found"
755
+ }, 404);
756
+ return c.json({
757
+ ok: true,
758
+ item
759
+ });
760
+ });
761
+ authenticated.delete("/api/work-items/:id", async (c) => {
762
+ const item = workItems.updateWorkItem(c.req.param("id"), { archivedAt: Date.now() });
763
+ if (!item) return c.json({
764
+ ok: false,
765
+ error: "Work item not found"
766
+ }, 404);
767
+ return c.json({
768
+ ok: true,
769
+ item
770
+ });
771
+ });
772
+ authenticated.get("/api/work-items/:id/events", async (c) => {
773
+ const item = workItems.getWorkItem(c.req.param("id"));
774
+ if (!item) return c.json({
775
+ ok: false,
776
+ error: "Work item not found"
777
+ }, 404);
778
+ return c.json({
779
+ ok: true,
780
+ events: workItems.listEvents(item.id)
781
+ });
782
+ });
783
+ authenticated.get("/api/work-items/:id/update-suggestions", async (c) => {
784
+ const item = workItems.getWorkItem(c.req.param("id"));
785
+ if (!item) return c.json({
786
+ ok: false,
787
+ error: "Work item not found"
788
+ }, 404);
789
+ const status = parseEnumValue(c.req.query("status"), WORK_ITEM_SUGGESTION_STATUSES);
790
+ if (c.req.query("status") && !status) return c.json({
791
+ ok: false,
792
+ error: "Invalid suggestion status"
793
+ }, 400);
794
+ return c.json({
795
+ ok: true,
796
+ suggestions: workItems.listUpdateSuggestions(item.id, status)
797
+ });
798
+ });
799
+ authenticated.post("/api/work-items/:id/update-suggestions", async (c) => {
800
+ const item = workItems.getWorkItem(c.req.param("id"));
801
+ if (!item) return c.json({
802
+ ok: false,
803
+ error: "Work item not found"
804
+ }, 404);
805
+ const body = await c.req.json().catch(() => ({}));
806
+ const sourceKind = parseEnumValue(body.sourceKind, [
807
+ "chat",
808
+ "goal",
809
+ "workflow_run",
810
+ "automation"
811
+ ]);
812
+ const sourceId = textField(body, "sourceId")?.trim();
813
+ if (!sourceKind) return c.json({
814
+ ok: false,
815
+ error: "Invalid sourceKind"
816
+ }, 400);
817
+ if (!sourceId) return c.json({
818
+ ok: false,
819
+ error: "sourceId is required"
820
+ }, 400);
821
+ const patchBody = body.patch && typeof body.patch === "object" && !Array.isArray(body.patch) ? body.patch : {};
822
+ const status = Object.hasOwn(patchBody, "status") ? parseEnumValue(patchBody.status, WORK_ITEM_STATUSES) : void 0;
823
+ if (Object.hasOwn(patchBody, "status") && !status) return c.json({
824
+ ok: false,
825
+ error: "Invalid work item status"
826
+ }, 400);
827
+ const confidence = typeof body.confidence === "number" && Number.isFinite(body.confidence) ? Math.max(0, Math.min(1, body.confidence)) : void 0;
828
+ const suggestion = workItems.createUpdateSuggestion(item.id, {
829
+ sourceKind,
830
+ sourceId,
831
+ patch: {
832
+ ...Object.hasOwn(patchBody, "status") ? { status } : {},
833
+ ...Object.hasOwn(patchBody, "nextAction") ? { nextAction: optionalTextField(patchBody, "nextAction") } : {},
834
+ ...Object.hasOwn(patchBody, "blockedReason") ? { blockedReason: optionalTextField(patchBody, "blockedReason") } : {}
835
+ },
836
+ progressNote: optionalTextField(body, "progressNote") ?? void 0,
837
+ rationale: optionalTextField(body, "rationale") ?? void 0,
838
+ confidence
839
+ });
840
+ if (!suggestion) return c.json({
841
+ ok: false,
842
+ error: "Work item not found"
843
+ }, 404);
844
+ return c.json({
845
+ ok: true,
846
+ suggestion
847
+ }, 201);
848
+ });
849
+ authenticated.post("/api/work-item-update-suggestions/:id/apply", async (c) => {
850
+ const result = workItems.applyUpdateSuggestion(c.req.param("id"));
851
+ if (!result) return c.json({
852
+ ok: false,
853
+ error: "Suggestion not found or already resolved"
854
+ }, 404);
855
+ return c.json({
856
+ ok: true,
857
+ item: result.item,
858
+ suggestion: result.suggestion
859
+ });
860
+ });
861
+ authenticated.post("/api/work-item-update-suggestions/:id/dismiss", async (c) => {
862
+ const suggestion = workItems.dismissUpdateSuggestion(c.req.param("id"));
863
+ if (!suggestion) return c.json({
864
+ ok: false,
865
+ error: "Suggestion not found or already resolved"
866
+ }, 404);
867
+ return c.json({
868
+ ok: true,
869
+ suggestion
870
+ });
871
+ });
872
+ authenticated.post("/api/work-items/:id/start-chat", async (c) => {
873
+ const item = workItems.getWorkItem(c.req.param("id"));
874
+ if (!item) return c.json({
875
+ ok: false,
876
+ error: "Work item not found"
877
+ }, 404);
878
+ const project = service.projects.get(item.projectId);
879
+ if (!project) return c.json({
880
+ ok: false,
881
+ error: "Project not found"
882
+ }, 404);
883
+ const agentId = resolveProjectAgentId({
884
+ config: service.currentConfig,
885
+ projects: service.projects,
886
+ explicitAgentId: item.ownerAgentId,
887
+ projectId: project.id
888
+ });
889
+ const peerId = `work_item_${Date.now()}`;
890
+ const sessionKey = buildSessionKey({
891
+ agentId,
892
+ source: "webchat",
893
+ accountId: "default",
894
+ peerKind: "direct",
895
+ peerId
896
+ });
897
+ await service.sessionIndexInstance.saveMessages(sessionKey, [], { metadata: buildWorkItemChatSessionMetadata({
898
+ agentId,
899
+ accountId: "default",
900
+ peerId,
901
+ workItemId: item.id,
902
+ title: item.title,
903
+ updatedAt: item.updatedAt
904
+ }) });
905
+ service.projects.attachSession(sessionKey, project.id);
906
+ const session = await service.sessions.getSession(sessionKey);
907
+ workItems.addLink(item.id, {
908
+ kind: "chat",
909
+ targetId: sessionKey,
910
+ title: session?.name || item.title,
911
+ statusSnapshot: session?.status
912
+ }, "chat_started");
913
+ const updated = item.status === "todo" || item.status === "backlog" ? workItems.updateWorkItem(item.id, { status: "in_progress" }) : workItems.getWorkItem(item.id);
914
+ return c.json({
915
+ ok: true,
916
+ session,
917
+ item: updated
918
+ }, 201);
919
+ });
920
+ authenticated.post("/api/work-items/:id/create-goal", async (c) => {
921
+ const item = workItems.getWorkItem(c.req.param("id"));
922
+ if (!item) return c.json({
923
+ ok: false,
924
+ error: "Work item not found"
925
+ }, 404);
926
+ const project = service.projects.get(item.projectId);
927
+ if (!project) return c.json({
928
+ ok: false,
929
+ error: "Project not found"
930
+ }, 404);
931
+ const goal = goals.create({
932
+ title: item.title,
933
+ description: item.description || item.nextAction,
934
+ priority: item.priority === "urgent" ? "high" : item.priority,
935
+ agentId: item.ownerAgentId || project.defaultAgentId || void 0,
936
+ projectId: project.id,
937
+ source: "api",
938
+ config: service.currentConfig
939
+ });
940
+ goals.setContextMessage({
941
+ goalId: goal.id,
942
+ text: buildWorkItemAgentContext(item).text
943
+ });
944
+ workItems.addLink(item.id, {
945
+ kind: "goal",
946
+ targetId: goal.id,
947
+ title: goal.title,
948
+ statusSnapshot: goal.status
949
+ }, "goal_created");
950
+ const updated = item.status === "todo" || item.status === "backlog" ? workItems.updateWorkItem(item.id, { status: "in_progress" }) : workItems.getWorkItem(item.id);
951
+ return c.json({
952
+ ok: true,
953
+ goal,
954
+ item: updated
955
+ }, 201);
956
+ });
263
957
  authenticated.post("/api/projects/:id/digest-memory", async (c) => {
264
958
  const project = service.projects.getWithDetails(c.req.param("id"));
265
959
  if (!project) return c.json({
@@ -406,7 +1100,7 @@ function registerProjectsRoutes(authenticated, deps) {
406
1100
  const sessions = await Promise.all(keys.map((key) => service.sessions.getSession(key)));
407
1101
  return c.json({
408
1102
  ok: true,
409
- sessions: sessions.filter(Boolean)
1103
+ sessions: sessions.filter((session) => session && !isHiddenEmptyProjectChatShell(session))
410
1104
  });
411
1105
  } catch (error) {
412
1106
  return c.json({
@@ -430,39 +1124,12 @@ function registerProjectsRoutes(authenticated, deps) {
430
1124
  }
431
1125
  });
432
1126
  authenticated.get("/api/projects/:id/files", async (c) => {
433
- const project = service.projects.get(c.req.param("id"));
434
- if (!project) return c.json({
435
- ok: false,
436
- error: "Project not found"
437
- }, 404);
438
- if (!project.workspaceRoot?.trim()) return c.json({
1127
+ const resolved = await resolveProjectWorkspacePath(service, c.req.param("id"), c.req.query("path"));
1128
+ if (resolved.ok === false) return c.json({
439
1129
  ok: false,
440
- error: "Project workspace root is not set"
441
- }, 400);
442
- let root;
443
- try {
444
- root = await realpath(project.workspaceRoot);
445
- if (!(await stat(root)).isDirectory()) return c.json({
446
- ok: false,
447
- error: "Project workspace root is not a directory"
448
- }, 400);
449
- } catch (err) {
450
- log.warn({
451
- err,
452
- projectId: project.id,
453
- path: project.workspaceRoot
454
- }, "project workspace root unavailable");
455
- return c.json({
456
- ok: false,
457
- error: "Project workspace root is unavailable"
458
- }, 404);
459
- }
460
- const requestedPath = normalizeRelativePath(c.req.query("path"));
461
- const candidate = path$1.resolve(root, requestedPath);
462
- if (!isWithinDirectory(root, candidate)) return c.json({
463
- ok: false,
464
- error: "Path is outside project workspace"
465
- }, 400);
1130
+ error: resolved.error
1131
+ }, resolved.status);
1132
+ const { root, absolutePath: candidate } = resolved;
466
1133
  let currentPath;
467
1134
  try {
468
1135
  currentPath = await realpath(candidate);
@@ -477,7 +1144,7 @@ function registerProjectsRoutes(authenticated, deps) {
477
1144
  } catch (err) {
478
1145
  log.warn({
479
1146
  err,
480
- projectId: project.id,
1147
+ projectId: resolved.projectId,
481
1148
  path: candidate
482
1149
  }, "project file path unavailable");
483
1150
  return c.json({
@@ -487,19 +1154,23 @@ function registerProjectsRoutes(authenticated, deps) {
487
1154
  }
488
1155
  try {
489
1156
  const dirents = await readdir(currentPath, { withFileTypes: true });
490
- const entries = await Promise.all(dirents.filter((entry) => !SKIP_FILE_NAMES.has(entry.name)).map(async (entry) => {
491
- const absolutePath = path$1.join(currentPath, entry.name);
1157
+ const visibleEntries = (await Promise.all(dirents.filter((entry) => !SKIP_FILE_NAMES.has(entry.name)).map(async (entry) => {
1158
+ const displayPath = path$1.join(currentPath, entry.name);
1159
+ const absolutePath = await realpath(displayPath).catch(() => null);
1160
+ if (!absolutePath || !isWithinDirectory(root, absolutePath)) return null;
492
1161
  const entryStat = await stat(absolutePath).catch(() => null);
493
- const relativePath = path$1.relative(root, absolutePath).split(path$1.sep).join("/");
1162
+ if (!entryStat) return null;
1163
+ const relativePath = path$1.relative(root, displayPath).split(path$1.sep).join("/");
494
1164
  return {
495
1165
  name: entry.name,
496
1166
  path: relativePath,
497
- type: entry.isDirectory() ? "directory" : "file",
1167
+ absolutePath,
1168
+ type: entryStat.isDirectory() ? "directory" : "file",
498
1169
  size: entryStat?.isFile() ? entryStat.size : void 0,
499
1170
  updatedAt: entryStat?.mtime.toISOString()
500
1171
  };
501
- }));
502
- entries.sort((left, right) => {
1172
+ }))).filter((entry) => entry !== null);
1173
+ visibleEntries.sort((left, right) => {
503
1174
  if (left.type !== right.type) return left.type === "directory" ? -1 : 1;
504
1175
  return left.name.localeCompare(right.name);
505
1176
  });
@@ -510,12 +1181,12 @@ function registerProjectsRoutes(authenticated, deps) {
510
1181
  root,
511
1182
  path: relativeCurrentPath === "." ? "" : relativeCurrentPath,
512
1183
  parentPath: parentRelativePath === "." ? "" : parentRelativePath,
513
- entries
1184
+ entries: visibleEntries
514
1185
  });
515
1186
  } catch (err) {
516
1187
  log.warn({
517
1188
  err,
518
- projectId: project.id,
1189
+ projectId: resolved.projectId,
519
1190
  path: currentPath
520
1191
  }, "project file list failed");
521
1192
  if (err?.code === "EACCES") return c.json({
@@ -528,6 +1199,176 @@ function registerProjectsRoutes(authenticated, deps) {
528
1199
  }, 500);
529
1200
  }
530
1201
  });
1202
+ authenticated.get("/api/projects/:id/files/read", async (c) => {
1203
+ const resolved = await resolveExistingProjectWorkspacePath(service, c.req.param("id"), c.req.query("path"));
1204
+ if (resolved.ok === false) return c.json({
1205
+ ok: false,
1206
+ error: { message: resolved.error }
1207
+ }, resolved.status);
1208
+ if (!resolved.stat.isFile()) return c.json({
1209
+ ok: false,
1210
+ error: { message: "Not a file" }
1211
+ }, 400);
1212
+ try {
1213
+ const content = await readFile(resolved.absolutePath, "utf-8");
1214
+ return c.json({
1215
+ ok: true,
1216
+ payload: {
1217
+ content,
1218
+ path: resolved.relativePath,
1219
+ absolutePath: resolved.absolutePath,
1220
+ mtimeMs: resolved.stat.mtimeMs
1221
+ }
1222
+ });
1223
+ } catch {
1224
+ return c.json({
1225
+ ok: false,
1226
+ error: { message: "Read failed" }
1227
+ }, 500);
1228
+ }
1229
+ });
1230
+ authenticated.get("/api/projects/:id/files/raw", async (c) => {
1231
+ const resolved = await resolveExistingProjectWorkspacePath(service, c.req.param("id"), c.req.query("path"));
1232
+ if (resolved.ok === false) return c.json({
1233
+ ok: false,
1234
+ error: { message: resolved.error }
1235
+ }, resolved.status);
1236
+ if (!resolved.stat.isFile()) return c.json({
1237
+ ok: false,
1238
+ error: { message: "Not a file" }
1239
+ }, 400);
1240
+ const contentType = PROJECT_FILE_MIME_BY_EXT[resolved.relativePath.split(".").pop()?.toLowerCase() ?? ""] || "application/octet-stream";
1241
+ try {
1242
+ const buf = await readFile(resolved.absolutePath);
1243
+ return new Response(buf, { headers: {
1244
+ "Content-Type": contentType,
1245
+ "Cache-Control": "private, max-age=3600"
1246
+ } });
1247
+ } catch {
1248
+ return c.json({
1249
+ ok: false,
1250
+ error: { message: "Read failed" }
1251
+ }, 500);
1252
+ }
1253
+ });
1254
+ authenticated.get("/api/projects/:id/files/resolve-reference", async (c) => {
1255
+ const rawPath = typeof c.req.query("path") === "string" ? c.req.query("path").trim() : "";
1256
+ if (!rawPath) return c.json({
1257
+ ok: false,
1258
+ error: {
1259
+ code: "INVALID_PATH",
1260
+ message: "Missing path"
1261
+ }
1262
+ }, 400);
1263
+ const resolved = await resolveProjectWorkspacePath(service, c.req.param("id"), rawPath);
1264
+ if (resolved.ok === false) return c.json({
1265
+ ok: false,
1266
+ error: {
1267
+ code: "PROJECT_WORKSPACE_RESOLUTION_FAILED",
1268
+ message: resolved.error
1269
+ }
1270
+ }, resolved.status);
1271
+ let st = null;
1272
+ let absolutePath = resolved.absolutePath;
1273
+ try {
1274
+ absolutePath = await realpath(resolved.absolutePath);
1275
+ if (!isWithinDirectory(resolved.root, absolutePath)) return c.json({
1276
+ ok: false,
1277
+ error: {
1278
+ code: "PROJECT_WORKSPACE_RESOLUTION_FAILED",
1279
+ message: "Path is outside project workspace"
1280
+ }
1281
+ }, 400);
1282
+ st = await stat(absolutePath);
1283
+ } catch {
1284
+ st = null;
1285
+ }
1286
+ const displayName = path$1.basename(rawPath);
1287
+ if (!st) return c.json({
1288
+ ok: true,
1289
+ payload: {
1290
+ inputPath: rawPath,
1291
+ displayName,
1292
+ scope: "missing",
1293
+ exists: false,
1294
+ absolutePath: resolved.absolutePath,
1295
+ capabilities: ["copyPath"],
1296
+ errorCode: "FILE_NOT_FOUND"
1297
+ }
1298
+ });
1299
+ return c.json({
1300
+ ok: true,
1301
+ payload: {
1302
+ inputPath: rawPath,
1303
+ displayName,
1304
+ scope: "workspace",
1305
+ exists: true,
1306
+ isDirectory: st.isDirectory(),
1307
+ absolutePath,
1308
+ workspaceRelativePath: resolved.relativePath,
1309
+ capabilities: st.isDirectory() ? [
1310
+ "openExternal",
1311
+ "revealInFolder",
1312
+ "copyPath"
1313
+ ] : [
1314
+ "preview",
1315
+ "edit",
1316
+ "openExternal",
1317
+ "revealInFolder",
1318
+ "copyPath"
1319
+ ],
1320
+ mtimeMs: st.mtimeMs
1321
+ }
1322
+ });
1323
+ });
1324
+ authenticated.put("/api/projects/:id/files/write", async (c) => {
1325
+ let body;
1326
+ try {
1327
+ body = await c.req.json();
1328
+ } catch {
1329
+ return c.json({
1330
+ ok: false,
1331
+ error: { message: "Invalid JSON" }
1332
+ }, 400);
1333
+ }
1334
+ const bodyRecord = typeof body === "object" && body !== null ? body : {};
1335
+ const pathRel = typeof bodyRecord.path === "string" ? bodyRecord.path : "";
1336
+ const content = typeof bodyRecord.content === "string" ? bodyRecord.content : "";
1337
+ if (!pathRel.trim()) return c.json({
1338
+ ok: false,
1339
+ error: { message: "Missing path" }
1340
+ }, 400);
1341
+ const resolved = await resolveProjectWorkspaceWritePath(service, c.req.param("id"), pathRel);
1342
+ if (resolved.ok === false) return c.json({
1343
+ ok: false,
1344
+ error: { message: resolved.error }
1345
+ }, resolved.status);
1346
+ if (resolved.existingStat && !resolved.existingStat.isFile()) return c.json({
1347
+ ok: false,
1348
+ error: { message: "Not a file" }
1349
+ }, 400);
1350
+ try {
1351
+ await writeFile(resolved.absolutePath, content, "utf-8");
1352
+ const mtimeMs = (await stat(resolved.absolutePath).catch(() => ({ mtimeMs: Date.now() }))).mtimeMs;
1353
+ return c.json({
1354
+ ok: true,
1355
+ payload: {
1356
+ path: resolved.relativePath,
1357
+ mtimeMs
1358
+ }
1359
+ });
1360
+ } catch (err) {
1361
+ log.error({
1362
+ err,
1363
+ projectId: resolved.projectId,
1364
+ path: resolved.absolutePath
1365
+ }, "project file write failed");
1366
+ return c.json({
1367
+ ok: false,
1368
+ error: { message: "Write failed" }
1369
+ }, 500);
1370
+ }
1371
+ });
531
1372
  authenticated.post("/api/projects/:id/sessions/:sessionKey", async (c) => {
532
1373
  try {
533
1374
  service.projects.attachSession(c.req.param("sessionKey"), c.req.param("id"));