@xopcai/xopc 0.0.124 → 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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +41 -0
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-B5uSVAyJ.js → agent-avatar-dicebear-BKDKICZV.js} +1 -1
- package/dist/gateway/static/root/assets/agents-BnTB7-c5.js +2 -0
- package/dist/gateway/static/root/assets/app-management-settings-panel-DI4BqHKW.js +1 -0
- package/dist/gateway/static/root/assets/appearance-settings-6_ie3NQ4.js +1 -0
- package/dist/gateway/static/root/assets/apps-page-B6bG65SW.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-CXUGRp2i.js → archive-plugin-kUHEJ1HK.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-Cmiub3Wg.js → attachment-load-Dm-9prpU.js} +1 -1
- package/dist/gateway/static/root/assets/automation-suggestion-card-OTxQTPnC.js +1 -0
- package/dist/gateway/static/root/assets/automations-page-1lVbo0dS.js +1 -0
- package/dist/gateway/static/root/assets/{binary-plugins-0Izz-N3p.js → binary-plugins-DllA_jQ0.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-C1Kv0IkJ.js → block-editor-Cm6CWNj1.js} +6 -6
- package/dist/gateway/static/root/assets/browser-settings-page-CzDBlOK0.js +8 -0
- package/dist/gateway/static/root/assets/capability-presets-api-VJ89b6I2.js +1 -0
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-BaPj8wC2.js +1 -0
- package/dist/gateway/static/root/assets/{channel-recipient-api-BeA2aVor.js → channel-recipient-api-B5PwBEUx.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-Df4m7p1k.js +1 -0
- package/dist/gateway/static/root/assets/code-editor-gbRgS14t.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-BMTphmHS.js +2 -0
- package/dist/gateway/static/root/assets/{directory-picker-path-field-2rjzUu57.js → directory-picker-path-field-DySMzOF6.js} +1 -1
- package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +1 -0
- package/dist/gateway/static/root/assets/dist-CkobR7mk.js +1 -0
- package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +1 -0
- package/dist/gateway/static/root/assets/dreaming-settings-D_AjSnV_.js +3 -0
- package/dist/gateway/static/root/assets/extension-debug-page-BaM0FpW7.js +1 -0
- package/dist/gateway/static/root/assets/extension-page-DuZWVOGA.js +1 -0
- package/dist/gateway/static/root/assets/extension-settings-page-DarVtbmJ.js +1 -0
- package/dist/gateway/static/root/assets/{fetch-BU5S0Sn7.js → fetch-NFKfkqrd.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-BIPGwrl1.js → gateway-config-api-CFEm9rgy.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-BMk2S5td.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-Cd0Zz30N.js → gateway-startup-retry-Dtwh_yGg.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-BKP7ylcG.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-C2cTxf71.js +1 -0
- package/dist/gateway/static/root/assets/{goals-config-api-B95KdGMj.js → goals-config-api-DTWZNA_Y.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-CTmA3hWK.js +2 -0
- package/dist/gateway/static/root/assets/goals-settings-Bhct0ZT-.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-settings-C-UxSU4a.js +1 -0
- package/dist/gateway/static/root/assets/{html-workspace-editor-CQLWDiwE.js → html-workspace-editor-DV90ddkE.js} +1 -1
- package/dist/gateway/static/root/assets/index-BNHDd2j1.css +1 -0
- package/dist/gateway/static/root/assets/index-BPXLPMuY.js +193 -0
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-DCjy2y4d.js +1 -0
- package/dist/gateway/static/root/assets/logs-page-CGc3_CQU.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-editor-AssmZFx4.js → markdown-editor-BFOjBBla.js} +2 -2
- package/dist/gateway/static/root/assets/markdown-split-BaqxP-GQ.js +1 -0
- package/dist/gateway/static/root/assets/{media-plugins-BhIQU5yS.js → media-plugins-DQ_EXloK.js} +1 -1
- package/dist/gateway/static/root/assets/messages-CjuIJcZl.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-Drz4UQMb.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-CY1ylng1.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-X7IaD--t.js +2 -0
- package/dist/gateway/static/root/assets/page-tabs-EIZhLppK.js +1 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-DGzet1d7.js → preview-open-alternatives-r_uB4Epr.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-DE5adRBE.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-CuOb1k6F.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-BOxNrUVA.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-Cqgb-jqa.js → save-bar-controls-B132Uf-2.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-DVZcI8Yt.js → schedule-field-B5qshALm.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-Bj7sOxC8.js → schema-form-CvLmkdS9.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-CY0IQcGW.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-D4NBNVCB.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-BmXn-8N6.js → settings-collapsible-section-pRlvqsAf.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-BnqYMWHR.js → settings-form-section-BBYUDLVd.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-DaO7zYZM.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-layout-CdWZU1hd.js → settings-page-layout-D8g9PAek.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-Bks0CLLT.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-IB1v8i86.js → share-preview-page-v6-IOp8t.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-CEZsKm9r.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-oTkHbb9W.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-DigsbZ6K.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-BxQ384RC.js → system-settings-panel-HUU2FFjM.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-BkcpvFYO.js → typed-models-lib-AH160QHh.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-Cd0FaEQ4.js → use-channel-catalog-BXzDmjCQ.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-DQ7NnRSY.js → user-profile-settings-panel-DxwnQvQ3.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-YKhVf7Ka.js → utils-i5POEtqd.js} +1 -1
- package/dist/gateway/static/root/assets/{vendor-codemirror-CNP1wPe7.js → vendor-codemirror-BvNgyE1Q.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-CEJklEgb.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-CD6FL_Rs.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-ClVVMgN-.js +3 -0
- package/dist/gateway/static/root/assets/{workflow-start-dialog-BNJVCWoS.js → workflow-start-dialog-Dv4ZA1T0.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-OLQiUpoW.js +18 -0
- package/dist/gateway/static/root/index.html +8 -7
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.js +6 -2
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/child-agent-factory.d.ts +2 -0
- package/dist/src/agent/child-agent-factory.js +2 -0
- package/dist/src/agent/child-agent-factory.js.map +1 -1
- package/dist/src/agent/coding/index.d.ts +1 -0
- package/dist/src/agent/coding/index.js +2 -0
- package/dist/src/agent/coding/turn-diff-tracker.d.ts +67 -0
- package/dist/src/agent/coding/turn-diff-tracker.js +233 -0
- package/dist/src/agent/coding/turn-diff-tracker.js.map +1 -0
- package/dist/src/agent/memory/dreaming/config.d.ts +2 -2
- package/dist/src/agent/memory/dreaming/config.js +3 -3
- package/dist/src/agent/memory/dreaming/config.js.map +1 -1
- package/dist/src/agent/memory/dreaming/constants.d.ts +1 -1
- package/dist/src/agent/memory/dreaming/constants.js +1 -1
- package/dist/src/agent/memory/dreaming/constants.js.map +1 -1
- package/dist/src/agent/memory/dreaming/deep-promotion.d.ts +1 -0
- package/dist/src/agent/memory/dreaming/deep-promotion.js +11 -20
- package/dist/src/agent/memory/dreaming/deep-promotion.js.map +1 -1
- package/dist/src/agent/memory/dreaming/events.d.ts +3 -3
- package/dist/src/agent/memory/dreaming/events.js +6 -6
- package/dist/src/agent/memory/dreaming/events.js.map +1 -1
- package/dist/src/agent/memory/dreaming/last-run.d.ts +2 -2
- package/dist/src/agent/memory/dreaming/last-run.js +1 -1
- package/dist/src/agent/memory/dreaming/last-run.js.map +1 -1
- package/dist/src/agent/memory/dreaming/light-sweep.d.ts +1 -1
- package/dist/src/agent/memory/dreaming/light-sweep.js +3 -3
- package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -1
- package/dist/src/agent/memory/dreaming/preview.d.ts +1 -0
- package/dist/src/agent/memory/dreaming/preview.js +2 -2
- package/dist/src/agent/memory/dreaming/preview.js.map +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.js +13 -13
- package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -1
- package/dist/src/agent/memory/dreaming/scope.d.ts +14 -0
- package/dist/src/agent/memory/dreaming/scope.js +27 -0
- package/dist/src/agent/memory/dreaming/scope.js.map +1 -0
- package/dist/src/agent/memory/dreaming/short-term-store.d.ts +4 -4
- package/dist/src/agent/memory/dreaming/short-term-store.js +17 -17
- package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -1
- package/dist/src/agent/orchestration/agent-event-handler.d.ts +2 -0
- package/dist/src/agent/orchestration/agent-event-handler.js +13 -0
- package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
- package/dist/src/agent/orchestration/agent-orchestrator.js +22 -12
- package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
- package/dist/src/agent/sandbox/command-validator.js +12 -0
- package/dist/src/agent/sandbox/command-validator.js.map +1 -1
- package/dist/src/agent/service.d.ts +2 -1
- package/dist/src/agent/service.js +18 -6
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/session/session-inspector.d.ts +5 -3
- package/dist/src/agent/session/session-inspector.js +16 -7
- package/dist/src/agent/session/session-inspector.js.map +1 -1
- package/dist/src/agent/source-context/types.d.ts +1 -1
- package/dist/src/agent/source-context/types.js +1 -1
- package/dist/src/agent/source-context/types.js.map +1 -1
- package/dist/src/agent/tools/apply-patch.js +116 -62
- package/dist/src/agent/tools/apply-patch.js.map +1 -1
- package/dist/src/agent/tools/concurrency.d.ts +8 -0
- package/dist/src/agent/tools/concurrency.js +31 -0
- package/dist/src/agent/tools/concurrency.js.map +1 -0
- package/dist/src/agent/tools/dreaming-tool.d.ts +2 -0
- package/dist/src/agent/tools/dreaming-tool.js +13 -10
- package/dist/src/agent/tools/dreaming-tool.js.map +1 -1
- package/dist/src/agent/tools/exec-command.js +4 -0
- package/dist/src/agent/tools/exec-command.js.map +1 -1
- package/dist/src/agent/tools/executor.d.ts +6 -4
- package/dist/src/agent/tools/executor.js +7 -4
- package/dist/src/agent/tools/executor.js.map +1 -1
- package/dist/src/agent/tools/factory.d.ts +3 -0
- package/dist/src/agent/tools/factory.js +7 -1
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/find.js +2 -0
- package/dist/src/agent/tools/find.js.map +1 -1
- package/dist/src/agent/tools/grep.js +2 -0
- package/dist/src/agent/tools/grep.js.map +1 -1
- package/dist/src/agent/tools/index.d.ts +2 -0
- package/dist/src/agent/tools/index.js +3 -1
- package/dist/src/agent/tools/list-dir.js +2 -0
- package/dist/src/agent/tools/list-dir.js.map +1 -1
- package/dist/src/agent/tools/memory-tool.d.ts +1 -0
- package/dist/src/agent/tools/memory-tool.js +2 -2
- package/dist/src/agent/tools/memory-tool.js.map +1 -1
- package/dist/src/agent/tools/metadata.d.ts +15 -0
- package/dist/src/agent/tools/metadata.js +26 -0
- package/dist/src/agent/tools/metadata.js.map +1 -0
- package/dist/src/agent/tools/read.js +2 -0
- package/dist/src/agent/tools/read.js.map +1 -1
- package/dist/src/agent/tools/write.js +6 -0
- package/dist/src/agent/tools/write.js.map +1 -1
- package/dist/src/agent/workflow/subagent-runner.d.ts +1 -0
- package/dist/src/agent/workflow/subagent-runner.js +5 -1
- package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
- package/dist/src/agent/workflow/workflow-child-tools.js +5 -0
- package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/connectors/health.d.ts +2 -1
- package/dist/src/connectors/health.js +59 -1
- package/dist/src/connectors/health.js.map +1 -1
- package/dist/src/connectors/install.d.ts +1 -0
- package/dist/src/connectors/install.js +28 -1
- package/dist/src/connectors/install.js.map +1 -1
- package/dist/src/connectors/instances.js +7 -0
- package/dist/src/connectors/instances.js.map +1 -1
- package/dist/src/connectors/materialize.js +2 -1
- package/dist/src/connectors/materialize.js.map +1 -1
- package/dist/src/connectors/registries/modelscope.js +38 -13
- package/dist/src/connectors/registries/modelscope.js.map +1 -1
- package/dist/src/connectors/types.d.ts +2 -0
- package/dist/src/gateway/dreaming-automation-reconciler.d.ts +1 -0
- package/dist/src/gateway/dreaming-automation-reconciler.js +62 -42
- package/dist/src/gateway/dreaming-automation-reconciler.js.map +1 -1
- package/dist/src/gateway/hono/app.js +7 -1
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/routes/connectors.js +46 -2
- package/dist/src/gateway/hono/routes/connectors.js.map +1 -1
- package/dist/src/gateway/hono/routes/dreaming.js +111 -66
- package/dist/src/gateway/hono/routes/dreaming.js.map +1 -1
- package/dist/src/gateway/hono/routes/projects.js +846 -43
- package/dist/src/gateway/hono/routes/projects.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +1 -0
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/service.js +17 -8
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/media/store.js +1 -1
- package/dist/src/media/store.js.map +1 -1
- package/dist/src/media/types.d.ts +1 -1
- package/dist/src/media/uri.js +2 -1
- package/dist/src/media/uri.js.map +1 -1
- package/dist/src/session/session-context-for-llm.d.ts +2 -0
- package/dist/src/session/session-context-for-llm.js +14 -1
- package/dist/src/session/session-context-for-llm.js.map +1 -1
- package/dist/src/session/store.js +2 -2
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/022_work_items.sql +61 -0
- package/dist/src/storage/sqlite/migrations/023_work_item_attachments.sql +15 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/storage/sqlite/schema.sql +78 -0
- package/dist/src/tui/components/assistant-message.js +12 -2
- package/dist/src/tui/components/assistant-message.js.map +1 -1
- package/dist/src/tui/components/tool-execution.d.ts +8 -0
- package/dist/src/tui/components/tool-execution.js +87 -77
- package/dist/src/tui/components/tool-execution.js.map +1 -1
- package/dist/src/tui/components/visual-truncate.d.ts +1 -0
- package/dist/src/tui/components/visual-truncate.js +28 -1
- package/dist/src/tui/components/visual-truncate.js.map +1 -1
- package/dist/src/tui/tool-summary.d.ts +22 -0
- package/dist/src/tui/tool-summary.js +212 -0
- package/dist/src/tui/tool-summary.js.map +1 -0
- package/dist/src/tui/tui-tool-diff.d.ts +15 -0
- package/dist/src/tui/tui-tool-diff.js +178 -7
- package/dist/src/tui/tui-tool-diff.js.map +1 -1
- package/dist/src/work-items/agent-context.d.ts +3 -0
- package/dist/src/work-items/agent-context.js +39 -0
- package/dist/src/work-items/agent-context.js.map +1 -0
- package/dist/src/work-items/index.d.ts +4 -0
- package/dist/src/work-items/index.js +4 -0
- package/dist/src/work-items/types.d.ts +112 -0
- package/dist/src/work-items/types.js +1 -0
- package/dist/src/work-items/work-item-service.d.ts +33 -0
- package/dist/src/work-items/work-item-service.js +180 -0
- package/dist/src/work-items/work-item-service.js.map +1 -0
- package/dist/src/work-items/work-item-store.d.ts +21 -0
- package/dist/src/work-items/work-item-store.js +302 -0
- package/dist/src/work-items/work-item-store.js.map +1 -0
- package/dist/src/workflows/service/workflow-run-service.js +4 -3
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/dist/src/workflows/service/workflow-session-bridge.js +3 -0
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
- package/dist/gateway/static/root/assets/agents-BjIxt67Z.js +0 -2
- package/dist/gateway/static/root/assets/app-management-settings-panel-CYCONLIL.js +0 -1
- package/dist/gateway/static/root/assets/appearance-settings-Clw3ryKg.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-QICTeGjX.js +0 -1
- package/dist/gateway/static/root/assets/automation-suggestion-card-CU0-4nQ-.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-Car-jtiz.js +0 -1
- package/dist/gateway/static/root/assets/browser-settings-page-CMeBJzlS.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-api-BmQTM9Il.js +0 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DZEJEoCF.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-kkW-xFau.js +0 -1
- package/dist/gateway/static/root/assets/code-editor-dZTY2dRW.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-BvaQvO5F.js +0 -2
- package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +0 -1
- package/dist/gateway/static/root/assets/dist-wO9a2_ID.js +0 -1
- package/dist/gateway/static/root/assets/dreaming-settings-DnHO686d.js +0 -1
- package/dist/gateway/static/root/assets/extension-debug-page-BGLRuWVN.js +0 -1
- package/dist/gateway/static/root/assets/extension-page-D978ePp9.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-B6Ih4Swx.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-CMEA3r5u.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-B-KHKeXN.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-BxrSoWdj.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-BTJzIl28.js +0 -2
- package/dist/gateway/static/root/assets/goals-settings-BJyEuLLm.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-wkOjYNQT.js +0 -1
- package/dist/gateway/static/root/assets/index-BLaXr6Ev.css +0 -1
- package/dist/gateway/static/root/assets/index-c_9TVr_C.js +0 -191
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-BT8T4DyC.js +0 -1
- package/dist/gateway/static/root/assets/logs-page-CrxBJUgI.js +0 -2
- package/dist/gateway/static/root/assets/markdown-split-Dv66bO2b.js +0 -1
- package/dist/gateway/static/root/assets/messages-O6TAtdFC.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-DOOpz6y1.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-Br16pK9v.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-PSJAi0ZC.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-CIGGgKuw.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-B2grhsBt.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CXMORxBD.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-Bi2wkBzU.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-B1L3G4IA.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-BfRSHJmO.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-CWtPM7RK.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-ngbsBgij.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BIe442Gv.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-BfchZRVa.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-C5Ov8Uk_.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-BYD9BtOq.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-BYF29ANm.js +0 -18
|
@@ -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";
|
|
@@ -11,10 +12,14 @@ import { inferProjectKind } from "../../../projects/project-kind.js";
|
|
|
11
12
|
import { inferSuggestedProjectDefaultAgentId } from "../../../projects/project-agent-suggestion.js";
|
|
12
13
|
import { buildProjectLoopOverview } from "../../../projects/project-loop.js";
|
|
13
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";
|
|
14
18
|
import { createGatewayRouteLogger } from "../lib/route-logger.js";
|
|
15
19
|
import * as path$1 from "node:path";
|
|
16
|
-
import { readdir, realpath, stat } from "node:fs/promises";
|
|
20
|
+
import { readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
17
21
|
//#region src/gateway/hono/routes/projects.ts
|
|
22
|
+
init_session_key();
|
|
18
23
|
const log = createGatewayRouteLogger("Projects");
|
|
19
24
|
function parseProjectStatus(raw) {
|
|
20
25
|
return raw === "active" || raw === "paused" || raw === "archived" ? raw : void 0;
|
|
@@ -24,6 +29,9 @@ function parseLimit(raw, fallback = 50) {
|
|
|
24
29
|
const n = Number.parseInt(raw, 10);
|
|
25
30
|
return Number.isFinite(n) ? Math.min(500, Math.max(1, n)) : fallback;
|
|
26
31
|
}
|
|
32
|
+
function isHiddenEmptyProjectChatShell(session) {
|
|
33
|
+
return session.hiddenFromSessionList === true && session.messageCount === 0 && session.customData?.genericNewChatShell !== false && Boolean(session.routing?.peerId?.startsWith("chat_"));
|
|
34
|
+
}
|
|
27
35
|
function textField(body, key) {
|
|
28
36
|
const value = body[key];
|
|
29
37
|
return typeof value === "string" ? value : void 0;
|
|
@@ -33,7 +41,87 @@ function optionalTextField(body, key) {
|
|
|
33
41
|
const value = body[key];
|
|
34
42
|
return typeof value === "string" ? value : null;
|
|
35
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
|
+
];
|
|
36
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
|
+
};
|
|
37
125
|
function normalizeRelativePath(raw) {
|
|
38
126
|
return (raw ?? "").trim().replaceAll("\\", "/").replace(/^\/+/, "");
|
|
39
127
|
}
|
|
@@ -41,6 +129,136 @@ function isWithinDirectory(root, target) {
|
|
|
41
129
|
const relativePath = path$1.relative(root, target);
|
|
42
130
|
return relativePath === "" || !relativePath.startsWith("..") && !path$1.isAbsolute(relativePath);
|
|
43
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
|
+
}
|
|
44
262
|
function textFromTranscriptContent(content) {
|
|
45
263
|
if (typeof content === "string") return content;
|
|
46
264
|
if (!Array.isArray(content)) return "";
|
|
@@ -66,6 +284,36 @@ function buildSessionSummaryMemoryContent(sessionKey, explicitSummary) {
|
|
|
66
284
|
}).filter(Boolean);
|
|
67
285
|
return lines.length > 0 ? [`Session summary for ${sessionKey}:`, ...lines].join("\n") : `Session summary for ${sessionKey}: no transcript content available.`;
|
|
68
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
|
+
}
|
|
69
317
|
function listFailedProjectWorkflowRuns(projectId, limit = 5) {
|
|
70
318
|
return getSqliteDatabase().prepare(`SELECT run_id, definition_id, status, created_at_ms, error_message
|
|
71
319
|
FROM workflow_runs
|
|
@@ -101,6 +349,7 @@ function projectDigestMemoryRecordId(projectId) {
|
|
|
101
349
|
function registerProjectsRoutes(authenticated, deps) {
|
|
102
350
|
const { service } = deps;
|
|
103
351
|
const goals = new GoalService();
|
|
352
|
+
const workItems = new WorkItemService();
|
|
104
353
|
authenticated.post("/api/projects", async (c) => {
|
|
105
354
|
const body = await c.req.json().catch(() => ({}));
|
|
106
355
|
const name = textField(body, "name")?.trim();
|
|
@@ -298,6 +547,413 @@ function registerProjectsRoutes(authenticated, deps) {
|
|
|
298
547
|
}
|
|
299
548
|
});
|
|
300
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
|
+
});
|
|
301
957
|
authenticated.post("/api/projects/:id/digest-memory", async (c) => {
|
|
302
958
|
const project = service.projects.getWithDetails(c.req.param("id"));
|
|
303
959
|
if (!project) return c.json({
|
|
@@ -444,7 +1100,7 @@ function registerProjectsRoutes(authenticated, deps) {
|
|
|
444
1100
|
const sessions = await Promise.all(keys.map((key) => service.sessions.getSession(key)));
|
|
445
1101
|
return c.json({
|
|
446
1102
|
ok: true,
|
|
447
|
-
sessions: sessions.filter(
|
|
1103
|
+
sessions: sessions.filter((session) => session && !isHiddenEmptyProjectChatShell(session))
|
|
448
1104
|
});
|
|
449
1105
|
} catch (error) {
|
|
450
1106
|
return c.json({
|
|
@@ -468,39 +1124,12 @@ function registerProjectsRoutes(authenticated, deps) {
|
|
|
468
1124
|
}
|
|
469
1125
|
});
|
|
470
1126
|
authenticated.get("/api/projects/:id/files", async (c) => {
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
473
|
-
ok: false,
|
|
474
|
-
error: "Project not found"
|
|
475
|
-
}, 404);
|
|
476
|
-
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({
|
|
477
1129
|
ok: false,
|
|
478
|
-
error:
|
|
479
|
-
},
|
|
480
|
-
|
|
481
|
-
try {
|
|
482
|
-
root = await realpath(project.workspaceRoot);
|
|
483
|
-
if (!(await stat(root)).isDirectory()) return c.json({
|
|
484
|
-
ok: false,
|
|
485
|
-
error: "Project workspace root is not a directory"
|
|
486
|
-
}, 400);
|
|
487
|
-
} catch (err) {
|
|
488
|
-
log.warn({
|
|
489
|
-
err,
|
|
490
|
-
projectId: project.id,
|
|
491
|
-
path: project.workspaceRoot
|
|
492
|
-
}, "project workspace root unavailable");
|
|
493
|
-
return c.json({
|
|
494
|
-
ok: false,
|
|
495
|
-
error: "Project workspace root is unavailable"
|
|
496
|
-
}, 404);
|
|
497
|
-
}
|
|
498
|
-
const requestedPath = normalizeRelativePath(c.req.query("path"));
|
|
499
|
-
const candidate = path$1.resolve(root, requestedPath);
|
|
500
|
-
if (!isWithinDirectory(root, candidate)) return c.json({
|
|
501
|
-
ok: false,
|
|
502
|
-
error: "Path is outside project workspace"
|
|
503
|
-
}, 400);
|
|
1130
|
+
error: resolved.error
|
|
1131
|
+
}, resolved.status);
|
|
1132
|
+
const { root, absolutePath: candidate } = resolved;
|
|
504
1133
|
let currentPath;
|
|
505
1134
|
try {
|
|
506
1135
|
currentPath = await realpath(candidate);
|
|
@@ -515,7 +1144,7 @@ function registerProjectsRoutes(authenticated, deps) {
|
|
|
515
1144
|
} catch (err) {
|
|
516
1145
|
log.warn({
|
|
517
1146
|
err,
|
|
518
|
-
projectId:
|
|
1147
|
+
projectId: resolved.projectId,
|
|
519
1148
|
path: candidate
|
|
520
1149
|
}, "project file path unavailable");
|
|
521
1150
|
return c.json({
|
|
@@ -525,19 +1154,23 @@ function registerProjectsRoutes(authenticated, deps) {
|
|
|
525
1154
|
}
|
|
526
1155
|
try {
|
|
527
1156
|
const dirents = await readdir(currentPath, { withFileTypes: true });
|
|
528
|
-
const
|
|
529
|
-
const
|
|
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;
|
|
530
1161
|
const entryStat = await stat(absolutePath).catch(() => null);
|
|
531
|
-
|
|
1162
|
+
if (!entryStat) return null;
|
|
1163
|
+
const relativePath = path$1.relative(root, displayPath).split(path$1.sep).join("/");
|
|
532
1164
|
return {
|
|
533
1165
|
name: entry.name,
|
|
534
1166
|
path: relativePath,
|
|
535
|
-
|
|
1167
|
+
absolutePath,
|
|
1168
|
+
type: entryStat.isDirectory() ? "directory" : "file",
|
|
536
1169
|
size: entryStat?.isFile() ? entryStat.size : void 0,
|
|
537
1170
|
updatedAt: entryStat?.mtime.toISOString()
|
|
538
1171
|
};
|
|
539
|
-
}));
|
|
540
|
-
|
|
1172
|
+
}))).filter((entry) => entry !== null);
|
|
1173
|
+
visibleEntries.sort((left, right) => {
|
|
541
1174
|
if (left.type !== right.type) return left.type === "directory" ? -1 : 1;
|
|
542
1175
|
return left.name.localeCompare(right.name);
|
|
543
1176
|
});
|
|
@@ -548,12 +1181,12 @@ function registerProjectsRoutes(authenticated, deps) {
|
|
|
548
1181
|
root,
|
|
549
1182
|
path: relativeCurrentPath === "." ? "" : relativeCurrentPath,
|
|
550
1183
|
parentPath: parentRelativePath === "." ? "" : parentRelativePath,
|
|
551
|
-
entries
|
|
1184
|
+
entries: visibleEntries
|
|
552
1185
|
});
|
|
553
1186
|
} catch (err) {
|
|
554
1187
|
log.warn({
|
|
555
1188
|
err,
|
|
556
|
-
projectId:
|
|
1189
|
+
projectId: resolved.projectId,
|
|
557
1190
|
path: currentPath
|
|
558
1191
|
}, "project file list failed");
|
|
559
1192
|
if (err?.code === "EACCES") return c.json({
|
|
@@ -566,6 +1199,176 @@ function registerProjectsRoutes(authenticated, deps) {
|
|
|
566
1199
|
}, 500);
|
|
567
1200
|
}
|
|
568
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
|
+
});
|
|
569
1372
|
authenticated.post("/api/projects/:id/sessions/:sessionKey", async (c) => {
|
|
570
1373
|
try {
|
|
571
1374
|
service.projects.attachSession(c.req.param("sessionKey"), c.req.param("id"));
|