@xopcai/xopc 0.0.121 → 0.0.122
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/README.md +23 -3
- package/README.zh-CN.md +23 -3
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-4AW4JgSU.js → agent-avatar-dicebear-CK3s5cJo.js} +1 -1
- package/dist/gateway/static/root/assets/agents-SsQrj9B-.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-CE6GkGU2.js → app-management-settings-panel-CWsNHtTs.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BmaUjghw.js → appearance-settings-C4b8y6iZ.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-CYb6fame.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-CX4_KWut.js → archive-plugin-DAtNW3Ia.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-jf-PIIUU.js → attachment-load-Dm-RY54y.js} +1 -1
- package/dist/gateway/static/root/assets/automation-suggestion-card-BKHewSVT.js +1 -0
- package/dist/gateway/static/root/assets/automations-page-DpOOq1nk.js +1 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DG9XQikd.js → binary-plugins-BG6Xnk71.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Up_VIxLJ.js → block-editor-DfndiO3h.js} +28 -28
- package/dist/gateway/static/root/assets/browser-settings-page-9nXxgh4u.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-CGM4OyMw.js → capability-presets-api-Cx93GLdw.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-CzWt0LBb.js → capability-presets-settings-panel-D528_YFy.js} +1 -1
- package/dist/gateway/static/root/assets/{channel-recipient-api-BEByL4_o.js → channel-recipient-api-DdPTtEs8.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-Ck-3h5lI.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-DL-fAqE_.js +2 -0
- package/dist/gateway/static/root/assets/directory-picker-path-field-D835EHJd.js +1 -0
- package/dist/gateway/static/root/assets/dreaming-settings-CCa1F7Fg.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-DZxa_j7-.js → extension-debug-page-W2ITCPSA.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CseziW53.js → extension-page-DrvUytvn.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-B_9J-xyo.js → extension-settings-page-qM8aLT5u.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-Cs4ck8zA.js → fetch-ovkvM8KQ.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-Dl223a7r.js → gateway-config-api-BB3biuD0.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-mmmnIH27.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-IvePwjFE.js → gateway-startup-retry-CanQ8UcR.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-CNqNdEkQ.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-Ceu5zrN6.js +1 -0
- package/dist/gateway/static/root/assets/{goals-config-api-CUCP6yCl.js → goals-config-api-BBOX6mUl.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-BP1kfvWv.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-Dj87qPxv.js → goals-settings-Bh3A7OZV.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-YoYgKlfX.js +1 -0
- package/dist/gateway/static/root/assets/index-DdqSO9R2.js +191 -0
- package/dist/gateway/static/root/assets/index-jGB-2nI-.css +1 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-DJgYw0nB.js → keyboard-shortcuts-settings-B4TKJHla.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-BgVk5DvZ.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-split-gnQ_EiGo.js → markdown-split-DcFtdLci.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-CQV-mgJn.js → media-plugins-B6SmeLVB.js} +1 -1
- package/dist/gateway/static/root/assets/messages-B5O4OIEo.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-BU0x8PGP.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-D6JYVP4I.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-mqRJYnJS.js +2 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-BPMXC-2u.js → preview-open-alternatives-C29O19s7.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-DOTgzoeh.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-DGUIb4y5.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-DVKKUj3u.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-BEHqwZjz.js → save-bar-controls-B4ad9GfB.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-CMY25m97.js → schedule-field-DdYncfLR.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-CxjxsFfg.js → schema-form-BMTrDE-7.js} +1 -1
- package/dist/gateway/static/root/assets/segmented-styles-BL8oLBEp.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-BVbzUWxW.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-DrNNoSqE.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-s-kiLAxP.js → settings-collapsible-section-MwTW2eK4.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-CIxIEYxU.js → settings-form-section-Dg6LBbcr.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-CMDqgeMR.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-layout-BX4T-OLF.js +1 -0
- package/dist/gateway/static/root/assets/setup-status-panel-Ce1pcFtR.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-DWE-dUFh.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-aKSraR8l.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-CS-FU_B-.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-BaF3u0LA.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-Czjf0kLJ.js → system-settings-panel-Dd6Y35Cm.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-Cmt8SYdI.js → typed-models-lib-Cx6XV5A2.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-DyM-ckEQ.js → use-channel-catalog-D4sMbmy7.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-BygLv8vm.js → user-profile-settings-panel-Bx-hjMQc.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-CloW8coB.js → utils-D7b_vZnv.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-CkhbRt1q.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel--TdohF5N.js +3 -0
- package/dist/gateway/static/root/assets/workflow-start-dialog-B0lavx_o.js +1 -0
- package/dist/gateway/static/root/assets/workflows-page-CZR05Bu_.js +18 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +2 -0
- package/dist/src/agent/agent-manager.js +47 -7
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/context/project-context.d.ts +19 -0
- package/dist/src/agent/context/project-context.js +90 -0
- package/dist/src/agent/context/project-context.js.map +1 -0
- package/dist/src/agent/goals/goal-locale.js +2 -2
- package/dist/src/agent/memory/types.d.ts +1 -0
- package/dist/src/agent/prompt/service-prompt-builder.d.ts +1 -0
- package/dist/src/agent/prompt/service-prompt-builder.js +2 -0
- package/dist/src/agent/prompt/service-prompt-builder.js.map +1 -1
- package/dist/src/agent/prompt/system-prompt-params.d.ts +2 -0
- package/dist/src/agent/prompt/system-prompt-params.js +1 -0
- package/dist/src/agent/prompt/system-prompt-params.js.map +1 -1
- package/dist/src/agent/prompt/system-prompt.d.ts +1 -0
- package/dist/src/agent/prompt/system-prompt.js +2 -1
- package/dist/src/agent/prompt/system-prompt.js.map +1 -1
- package/dist/src/agent/service.js +4 -3
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/session/session-config-service.js +6 -1
- package/dist/src/agent/session/session-config-service.js.map +1 -1
- package/dist/src/agent/session/session-hydrator.js +7 -3
- package/dist/src/agent/session/session-hydrator.js.map +1 -1
- package/dist/src/agent/session/session-inspector.js +9 -5
- package/dist/src/agent/session/session-inspector.js.map +1 -1
- package/dist/src/agent/starter-agents.js +5 -34
- package/dist/src/agent/starter-agents.js.map +1 -1
- package/dist/src/agent/workflow/index.js +2 -2
- package/dist/src/agent/workflow/runtime.js +1 -1
- package/dist/src/automations/api/routes.js +13 -2
- package/dist/src/automations/api/routes.js.map +1 -1
- package/dist/src/automations/domain/types.d.ts +2 -0
- package/dist/src/automations/domain/validation.d.ts +3 -0
- package/dist/src/automations/domain/validation.js +1 -0
- package/dist/src/automations/domain/validation.js.map +1 -1
- package/dist/src/automations/draft/automation-draft-validator.d.ts +1 -0
- package/dist/src/automations/service/action-executor.js +3 -1
- package/dist/src/automations/service/action-executor.js.map +1 -1
- package/dist/src/automations/service/automation-service.d.ts +4 -1
- package/dist/src/automations/service/automation-service.js +2 -2
- package/dist/src/automations/service/automation-service.js.map +1 -1
- package/dist/src/automations/storage/automation-repository.d.ts +3 -1
- package/dist/src/automations/storage/automation-repository.js +7 -5
- package/dist/src/automations/storage/automation-repository.js.map +1 -1
- package/dist/src/automations/storage/automation-run-repository.d.ts +1 -0
- package/dist/src/automations/storage/automation-run-repository.js +12 -1
- package/dist/src/automations/storage/automation-run-repository.js.map +1 -1
- package/dist/src/chat-commands/context.js +2 -1
- package/dist/src/chat-commands/context.js.map +1 -1
- package/dist/src/cli/command-loaders.js +1 -0
- package/dist/src/cli/command-loaders.js.map +1 -1
- package/dist/src/cli/command-manifest.js +4 -0
- package/dist/src/cli/command-manifest.js.map +1 -1
- package/dist/src/cli/commands/agent.js +2 -2
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/cli/commands/goal.js +27 -4
- package/dist/src/cli/commands/goal.js.map +1 -1
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/cli/commands/profile.js +1 -1
- package/dist/src/cli/commands/project.d.ts +4 -0
- package/dist/src/cli/commands/project.js +239 -0
- package/dist/src/cli/commands/project.js.map +1 -0
- package/dist/src/cli/commands/tunnel.js +1 -1
- package/dist/src/cli/commands/update.js +1 -1
- package/dist/src/cli/extension-cli-register.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/hono/routes/goals.js +51 -4
- package/dist/src/gateway/hono/routes/goals.js.map +1 -1
- package/dist/src/gateway/hono/routes/index.js +2 -0
- package/dist/src/gateway/hono/routes/index.js.map +1 -1
- package/dist/src/gateway/hono/routes/memory.js +5 -1
- package/dist/src/gateway/hono/routes/memory.js.map +1 -1
- package/dist/src/gateway/hono/routes/notes.js +5 -0
- package/dist/src/gateway/hono/routes/notes.js.map +1 -1
- package/dist/src/gateway/hono/routes/projects.d.ts +3 -0
- package/dist/src/gateway/hono/routes/projects.js +578 -0
- package/dist/src/gateway/hono/routes/projects.js.map +1 -0
- package/dist/src/gateway/hono/routes/sessions.js +47 -7
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/workflows.js +27 -4
- package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
- package/dist/src/gateway/run-loop.js +1 -1
- package/dist/src/gateway/service.d.ts +3 -0
- package/dist/src/gateway/service.js +10 -3
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/goals/goal-service.d.ts +1 -1
- package/dist/src/goals/goal-service.js +2 -1
- package/dist/src/goals/goal-service.js.map +1 -1
- package/dist/src/goals/goal-store.d.ts +1 -1
- package/dist/src/goals/goal-store.js +12 -6
- package/dist/src/goals/goal-store.js.map +1 -1
- package/dist/src/goals/goal-workflow-judge.js +1 -1
- package/dist/src/goals/types.d.ts +3 -0
- package/dist/src/i18n/goals-bundle.js +1 -1
- package/dist/src/i18n/index.js +1 -1
- package/dist/src/i18n/share-tool-bundle.js +2 -2
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/notes/agent-context.js +2 -2
- package/dist/src/projects/automation-bind.d.ts +1 -0
- package/dist/src/projects/automation-bind.js +16 -0
- package/dist/src/projects/automation-bind.js.map +1 -0
- package/dist/src/projects/goal-bind.d.ts +4 -0
- package/dist/src/projects/goal-bind.js +40 -0
- package/dist/src/projects/goal-bind.js.map +1 -0
- package/dist/src/projects/index.d.ts +11 -0
- package/dist/src/projects/index.js +11 -0
- package/dist/src/projects/memory-bind.d.ts +1 -0
- package/dist/src/projects/memory-bind.js +16 -0
- package/dist/src/projects/memory-bind.js.map +1 -0
- package/dist/src/projects/project-agent.d.ts +10 -0
- package/dist/src/projects/project-agent.js +27 -0
- package/dist/src/projects/project-agent.js.map +1 -0
- package/dist/src/projects/project-loop.d.ts +58 -0
- package/dist/src/projects/project-loop.js +118 -0
- package/dist/src/projects/project-loop.js.map +1 -0
- package/dist/src/projects/project-service.d.ts +26 -0
- package/dist/src/projects/project-service.js +94 -0
- package/dist/src/projects/project-service.js.map +1 -0
- package/dist/src/projects/project-store.d.ts +31 -0
- package/dist/src/projects/project-store.js +193 -0
- package/dist/src/projects/project-store.js.map +1 -0
- package/dist/src/projects/session-bind.d.ts +5 -0
- package/dist/src/projects/session-bind.js +46 -0
- package/dist/src/projects/session-bind.js.map +1 -0
- package/dist/src/projects/types.d.ts +60 -0
- package/dist/src/projects/types.js +1 -0
- package/dist/src/projects/workflow-bind.d.ts +1 -0
- package/dist/src/projects/workflow-bind.js +16 -0
- package/dist/src/projects/workflow-bind.js.map +1 -0
- package/dist/src/projects/workspace.d.ts +5 -0
- package/dist/src/projects/workspace.js +25 -0
- package/dist/src/projects/workspace.js.map +1 -0
- package/dist/src/session/index.d.ts +1 -1
- package/dist/src/session/index.js +3 -3
- package/dist/src/session/session-workspace.d.ts +4 -2
- package/dist/src/session/session-workspace.js +10 -3
- package/dist/src/session/session-workspace.js.map +1 -1
- package/dist/src/session/types.d.ts +3 -0
- package/dist/src/session/types.js.map +1 -1
- package/dist/src/storage/sqlite/memory-records-repository.d.ts +3 -0
- package/dist/src/storage/sqlite/memory-records-repository.js +16 -5
- package/dist/src/storage/sqlite/memory-records-repository.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/019_projects.sql +40 -0
- package/dist/src/storage/sqlite/migrations/020_project_default_agent.sql +1 -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/notes-repository.js +20 -4
- package/dist/src/storage/sqlite/notes-repository.js.map +1 -1
- package/dist/src/storage/sqlite/row-mappers.d.ts +2 -0
- package/dist/src/storage/sqlite/row-mappers.js +2 -0
- package/dist/src/storage/sqlite/row-mappers.js.map +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/storage/sqlite/session-repository.js +10 -5
- package/dist/src/storage/sqlite/session-repository.js.map +1 -1
- package/dist/src/storage/sqlite/transcript-repository.js +8 -0
- package/dist/src/storage/sqlite/transcript-repository.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/tui/tui-commands.js +145 -0
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/workflows/domain/run.d.ts +1 -0
- package/dist/src/workflows/domain/run.js.map +1 -1
- package/dist/src/workflows/index.js +1 -1
- package/dist/src/workflows/service/workflow-run-service.d.ts +1 -0
- package/dist/src/workflows/service/workflow-run-service.js +18 -4
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.types.d.ts +2 -0
- package/dist/src/workflows/service/workflow-session-bridge.d.ts +1 -0
- package/dist/src/workflows/service/workflow-session-bridge.js +3 -0
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
- package/dist/src/workflows/store/run-index-store.d.ts +1 -0
- package/dist/src/workflows/store/run-index-store.js +13 -3
- package/dist/src/workflows/store/run-index-store.js.map +1 -1
- package/dist/src/workflows/store/run-store.d.ts +3 -1
- package/dist/src/workflows/store/run-store.js +5 -2
- package/dist/src/workflows/store/run-store.js.map +1 -1
- package/package.json +10 -1
- package/dist/gateway/static/root/assets/agents-2SVUUPlp.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-BWeQ1I49.js +0 -1
- package/dist/gateway/static/root/assets/automation-suggestion-card-BHk5rtJz.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-QHotl53D.js +0 -1
- package/dist/gateway/static/root/assets/browser-settings-page-Cmwi6IIQ.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-BOMCUe2J.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-C-Dexk_F.js +0 -2
- package/dist/gateway/static/root/assets/dreaming-settings-WPEj8pir.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-DdzbtW9R.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-Dxldogms.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-DU5Nd6Xp.js +0 -2
- package/dist/gateway/static/root/assets/heartbeat-settings-Cgm5JmwH.js +0 -1
- package/dist/gateway/static/root/assets/index-BU1pvxmS.js +0 -191
- package/dist/gateway/static/root/assets/index-l54WfSqe.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-C10aS4XO.js +0 -2
- package/dist/gateway/static/root/assets/messages-C6Kc4Jhf.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-B35CAfg6.js +0 -2
- package/dist/gateway/static/root/assets/note-detail-page-Brlwse8x.js +0 -2
- package/dist/gateway/static/root/assets/note-image-lightbox-BthwKjLA.js +0 -1
- package/dist/gateway/static/root/assets/note-time-hm6JO_Wi.js +0 -1
- package/dist/gateway/static/root/assets/notes-page-CoAwRc7L.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-B36RQ05h.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-73THChnj.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-CR9VsY-R.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-BJ1AhfKI.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-layout--6qT7ZJa.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-C1TtS1vJ.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-BZADSbS1.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-gqC8Rmhy.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-C9P-WYvS.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-CEpSvOhF.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-BsoqJba6.js +0 -1
- package/dist/gateway/static/root/assets/voice-player-store-DduWlJYV.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-CcQeX6l5.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-CeA8A9Ef.js +0 -18
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/projects/project-loop.ts
|
|
2
|
+
const ACTIVE_GOAL_STATUSES = new Set([
|
|
3
|
+
"active",
|
|
4
|
+
"paused",
|
|
5
|
+
"blocked",
|
|
6
|
+
"needs_input"
|
|
7
|
+
]);
|
|
8
|
+
const STALE_GOAL_STATUSES = new Set(["active", "paused"]);
|
|
9
|
+
const DEFAULT_STALE_AFTER_MS = 10080 * 60 * 1e3;
|
|
10
|
+
function goalUpdatedAtDesc(left, right) {
|
|
11
|
+
return right.updatedAt - left.updatedAt;
|
|
12
|
+
}
|
|
13
|
+
function compactText(value, max = 180) {
|
|
14
|
+
const trimmed = value?.trim().replace(/\s+/g, " ");
|
|
15
|
+
if (!trimmed) return void 0;
|
|
16
|
+
return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max - 3).trimEnd()}...`;
|
|
17
|
+
}
|
|
18
|
+
function buildProjectLoopOverview(input) {
|
|
19
|
+
const staleCutoff = (input.nowMs ?? Date.now()) - (input.staleAfterMs ?? DEFAULT_STALE_AFTER_MS);
|
|
20
|
+
const projectGoals = [...input.goals].sort(goalUpdatedAtDesc);
|
|
21
|
+
const activeGoals = projectGoals.filter((goal) => ACTIVE_GOAL_STATUSES.has(goal.status)).slice(0, 6);
|
|
22
|
+
const blockedGoals = projectGoals.filter((goal) => goal.status === "blocked" || goal.status === "needs_input").slice(0, 4);
|
|
23
|
+
const staleGoals = projectGoals.filter((goal) => STALE_GOAL_STATUSES.has(goal.status) && goal.updatedAt < staleCutoff).slice(0, 5);
|
|
24
|
+
const nextActions = activeGoals.filter((goal) => goal.nextAction?.trim()).slice(0, 5).map((goal) => ({
|
|
25
|
+
goalId: goal.id,
|
|
26
|
+
title: goal.title,
|
|
27
|
+
nextAction: goal.nextAction,
|
|
28
|
+
status: goal.status,
|
|
29
|
+
updatedAt: goal.updatedAt
|
|
30
|
+
}));
|
|
31
|
+
const failedWorkflowRuns = (input.failedWorkflowRuns ?? input.recentWorkflowRuns.filter((run) => run.status === "failed" || run.status === "timeout" || run.status === "cancelled")).slice(0, 5);
|
|
32
|
+
const attentionItems = [
|
|
33
|
+
...blockedGoals.map((goal) => ({
|
|
34
|
+
id: `goal:${goal.id}:blocked`,
|
|
35
|
+
kind: "blocked_goal",
|
|
36
|
+
title: goal.title,
|
|
37
|
+
detail: compactText(goal.blockedReason || goal.nextAction || goal.description),
|
|
38
|
+
status: goal.status,
|
|
39
|
+
href: `/goals/${encodeURIComponent(goal.id)}`,
|
|
40
|
+
updatedAt: goal.updatedAt
|
|
41
|
+
})),
|
|
42
|
+
...staleGoals.map((goal) => ({
|
|
43
|
+
id: `goal:${goal.id}:stale`,
|
|
44
|
+
kind: "stale_goal",
|
|
45
|
+
title: goal.title,
|
|
46
|
+
detail: compactText(goal.nextAction || goal.description || "Goal has not changed recently."),
|
|
47
|
+
status: goal.status,
|
|
48
|
+
href: `/goals/${encodeURIComponent(goal.id)}`,
|
|
49
|
+
updatedAt: goal.updatedAt
|
|
50
|
+
})),
|
|
51
|
+
...failedWorkflowRuns.map((run) => ({
|
|
52
|
+
id: `workflow:${run.runId}:failed`,
|
|
53
|
+
kind: "failed_workflow",
|
|
54
|
+
title: run.definitionId,
|
|
55
|
+
detail: compactText(run.errorMessage || run.status),
|
|
56
|
+
status: run.status,
|
|
57
|
+
href: `/workflows/runs/${encodeURIComponent(run.runId)}`,
|
|
58
|
+
updatedAt: run.createdAt
|
|
59
|
+
}))
|
|
60
|
+
].slice(0, 8);
|
|
61
|
+
const timeline = [
|
|
62
|
+
...input.project.recentSessions.map((session) => ({
|
|
63
|
+
id: `session:${session.key}`,
|
|
64
|
+
kind: "session",
|
|
65
|
+
title: session.name ?? session.key,
|
|
66
|
+
detail: session.agentId,
|
|
67
|
+
timestamp: Date.parse(session.updatedAt),
|
|
68
|
+
href: `/chat/${encodeURIComponent(session.key)}`
|
|
69
|
+
})),
|
|
70
|
+
...projectGoals.slice(0, 8).map((goal) => ({
|
|
71
|
+
id: `goal:${goal.id}`,
|
|
72
|
+
kind: "goal",
|
|
73
|
+
title: goal.title,
|
|
74
|
+
detail: compactText(goal.nextAction || goal.blockedReason || goal.description),
|
|
75
|
+
timestamp: goal.updatedAt,
|
|
76
|
+
status: goal.status,
|
|
77
|
+
href: `/goals/${encodeURIComponent(goal.id)}`
|
|
78
|
+
})),
|
|
79
|
+
...input.recentWorkflowRuns.map((run) => ({
|
|
80
|
+
id: `workflow:${run.runId}`,
|
|
81
|
+
kind: "workflow",
|
|
82
|
+
title: run.definitionId,
|
|
83
|
+
detail: run.status,
|
|
84
|
+
timestamp: run.createdAt,
|
|
85
|
+
status: run.status,
|
|
86
|
+
href: `/workflows/runs/${encodeURIComponent(run.runId)}`
|
|
87
|
+
})),
|
|
88
|
+
...(input.memoryRecords ?? []).slice(0, 5).map((record) => ({
|
|
89
|
+
id: `memory:${record.id}`,
|
|
90
|
+
kind: "memory",
|
|
91
|
+
title: record.kind,
|
|
92
|
+
detail: compactText(record.content),
|
|
93
|
+
timestamp: Date.parse(record.updatedAt),
|
|
94
|
+
status: record.status
|
|
95
|
+
}))
|
|
96
|
+
].filter((item) => Number.isFinite(item.timestamp)).sort((left, right) => right.timestamp - left.timestamp).slice(0, 12);
|
|
97
|
+
const recommendedAction = nextActions[0]?.nextAction ?? blockedGoals[0]?.blockedReason ?? staleGoals[0]?.nextAction ?? activeGoals[0]?.title ?? (input.project.brief ? "Open a new chat to continue from the project brief." : void 0);
|
|
98
|
+
const digestStatus = attentionItems.length > 0 ? "attention" : activeGoals.length > 0 || input.recentWorkflowRuns.some((run) => run.status === "running" || run.status === "queued") ? "healthy" : timeline.length > 0 ? "idle" : "empty";
|
|
99
|
+
return {
|
|
100
|
+
activeGoals,
|
|
101
|
+
blockedGoals,
|
|
102
|
+
staleGoals,
|
|
103
|
+
nextActions,
|
|
104
|
+
attentionItems,
|
|
105
|
+
timeline,
|
|
106
|
+
digest: {
|
|
107
|
+
status: digestStatus,
|
|
108
|
+
summary: digestStatus === "attention" ? `${attentionItems.length} item(s) need attention: ${blockedGoals.length} blocker(s), ${staleGoals.length} stale goal(s), ${failedWorkflowRuns.length} failed workflow run(s).` : digestStatus === "healthy" ? `${activeGoals.length} active goal(s), ${nextActions.length} next action(s), and ${input.recentWorkflowRuns.length} recent workflow run(s).` : digestStatus === "idle" ? "Recent activity exists, but no active goal is driving the project." : "No project activity has been recorded yet.",
|
|
109
|
+
nextAction: recommendedAction
|
|
110
|
+
},
|
|
111
|
+
failedWorkflowRuns,
|
|
112
|
+
recommendedAction
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { buildProjectLoopOverview };
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=project-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-loop.js","names":[],"sources":["../../../src/projects/project-loop.ts"],"sourcesContent":["import type { GoalWithDetails } from '../goals/index.js';\nimport type { MemoryRecord } from '../agent/memory/types.js';\nimport type { ProjectWithDetails } from './types.js';\n\nexport type ProjectWorkflowRunBrief = {\n runId: string;\n definitionId: string;\n status: string;\n createdAt: number;\n errorMessage?: string;\n};\n\nexport type ProjectAttentionItem = {\n id: string;\n kind: 'blocked_goal' | 'stale_goal' | 'failed_workflow';\n title: string;\n detail?: string;\n status?: string;\n href?: string;\n updatedAt?: number;\n};\n\nexport type ProjectTimelineItem = {\n id: string;\n kind: 'session' | 'goal' | 'workflow' | 'memory';\n title: string;\n detail?: string;\n timestamp: number;\n status?: string;\n href?: string;\n};\n\nexport type ProjectLoopOverview = {\n activeGoals: GoalWithDetails[];\n blockedGoals: GoalWithDetails[];\n staleGoals: GoalWithDetails[];\n nextActions: Array<{\n goalId: string;\n title: string;\n nextAction?: string;\n status: string;\n updatedAt?: number;\n }>;\n attentionItems: ProjectAttentionItem[];\n timeline: ProjectTimelineItem[];\n digest: {\n status: 'healthy' | 'attention' | 'idle' | 'empty';\n summary: string;\n nextAction?: string;\n };\n failedWorkflowRuns: ProjectWorkflowRunBrief[];\n recommendedAction?: string;\n};\n\nconst ACTIVE_GOAL_STATUSES = new Set(['active', 'paused', 'blocked', 'needs_input']);\nconst STALE_GOAL_STATUSES = new Set(['active', 'paused']);\nconst DEFAULT_STALE_AFTER_MS = 7 * 24 * 60 * 60 * 1000;\n\nfunction goalUpdatedAtDesc(left: GoalWithDetails, right: GoalWithDetails): number {\n return right.updatedAt - left.updatedAt;\n}\n\nfunction compactText(value: string | undefined, max = 180): string | undefined {\n const trimmed = value?.trim().replace(/\\s+/g, ' ');\n if (!trimmed) return undefined;\n return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max - 3).trimEnd()}...`;\n}\n\nexport function buildProjectLoopOverview(input: {\n project: ProjectWithDetails;\n goals: GoalWithDetails[];\n recentWorkflowRuns: ProjectWorkflowRunBrief[];\n failedWorkflowRuns?: ProjectWorkflowRunBrief[];\n memoryRecords?: MemoryRecord[];\n nowMs?: number;\n staleAfterMs?: number;\n}): ProjectLoopOverview {\n const nowMs = input.nowMs ?? Date.now();\n const staleCutoff = nowMs - (input.staleAfterMs ?? DEFAULT_STALE_AFTER_MS);\n const projectGoals = [...input.goals].sort(goalUpdatedAtDesc);\n const activeGoals = projectGoals.filter((goal) => ACTIVE_GOAL_STATUSES.has(goal.status)).slice(0, 6);\n const blockedGoals = projectGoals.filter((goal) => goal.status === 'blocked' || goal.status === 'needs_input').slice(0, 4);\n const staleGoals = projectGoals\n .filter((goal) => STALE_GOAL_STATUSES.has(goal.status) && goal.updatedAt < staleCutoff)\n .slice(0, 5);\n const nextActions = activeGoals\n .filter((goal) => goal.nextAction?.trim())\n .slice(0, 5)\n .map((goal) => ({\n goalId: goal.id,\n title: goal.title,\n nextAction: goal.nextAction,\n status: goal.status,\n updatedAt: goal.updatedAt,\n }));\n const failedWorkflowRuns = (input.failedWorkflowRuns ?? input.recentWorkflowRuns.filter((run) => (\n run.status === 'failed' || run.status === 'timeout' || run.status === 'cancelled'\n ))).slice(0, 5);\n\n const attentionItems: ProjectAttentionItem[] = [\n ...blockedGoals.map((goal) => ({\n id: `goal:${goal.id}:blocked`,\n kind: 'blocked_goal' as const,\n title: goal.title,\n detail: compactText(goal.blockedReason || goal.nextAction || goal.description),\n status: goal.status,\n href: `/goals/${encodeURIComponent(goal.id)}`,\n updatedAt: goal.updatedAt,\n })),\n ...staleGoals.map((goal) => ({\n id: `goal:${goal.id}:stale`,\n kind: 'stale_goal' as const,\n title: goal.title,\n detail: compactText(goal.nextAction || goal.description || 'Goal has not changed recently.'),\n status: goal.status,\n href: `/goals/${encodeURIComponent(goal.id)}`,\n updatedAt: goal.updatedAt,\n })),\n ...failedWorkflowRuns.map((run) => ({\n id: `workflow:${run.runId}:failed`,\n kind: 'failed_workflow' as const,\n title: run.definitionId,\n detail: compactText(run.errorMessage || run.status),\n status: run.status,\n href: `/workflows/runs/${encodeURIComponent(run.runId)}`,\n updatedAt: run.createdAt,\n })),\n ].slice(0, 8);\n\n const timeline: ProjectTimelineItem[] = [\n ...input.project.recentSessions.map((session) => ({\n id: `session:${session.key}`,\n kind: 'session' as const,\n title: session.name ?? session.key,\n detail: session.agentId,\n timestamp: Date.parse(session.updatedAt),\n href: `/chat/${encodeURIComponent(session.key)}`,\n })),\n ...projectGoals.slice(0, 8).map((goal) => ({\n id: `goal:${goal.id}`,\n kind: 'goal' as const,\n title: goal.title,\n detail: compactText(goal.nextAction || goal.blockedReason || goal.description),\n timestamp: goal.updatedAt,\n status: goal.status,\n href: `/goals/${encodeURIComponent(goal.id)}`,\n })),\n ...input.recentWorkflowRuns.map((run) => ({\n id: `workflow:${run.runId}`,\n kind: 'workflow' as const,\n title: run.definitionId,\n detail: run.status,\n timestamp: run.createdAt,\n status: run.status,\n href: `/workflows/runs/${encodeURIComponent(run.runId)}`,\n })),\n ...(input.memoryRecords ?? []).slice(0, 5).map((record) => ({\n id: `memory:${record.id}`,\n kind: 'memory' as const,\n title: record.kind,\n detail: compactText(record.content),\n timestamp: Date.parse(record.updatedAt),\n status: record.status,\n })),\n ]\n .filter((item) => Number.isFinite(item.timestamp))\n .sort((left, right) => right.timestamp - left.timestamp)\n .slice(0, 12);\n\n const recommendedAction = nextActions[0]?.nextAction\n ?? blockedGoals[0]?.blockedReason\n ?? staleGoals[0]?.nextAction\n ?? activeGoals[0]?.title\n ?? (input.project.brief ? 'Open a new chat to continue from the project brief.' : undefined);\n\n const digestStatus =\n attentionItems.length > 0 ? 'attention'\n : activeGoals.length > 0 || input.recentWorkflowRuns.some((run) => run.status === 'running' || run.status === 'queued') ? 'healthy'\n : timeline.length > 0 ? 'idle'\n : 'empty';\n const digestSummary =\n digestStatus === 'attention'\n ? `${attentionItems.length} item(s) need attention: ${blockedGoals.length} blocker(s), ${staleGoals.length} stale goal(s), ${failedWorkflowRuns.length} failed workflow run(s).`\n : digestStatus === 'healthy'\n ? `${activeGoals.length} active goal(s), ${nextActions.length} next action(s), and ${input.recentWorkflowRuns.length} recent workflow run(s).`\n : digestStatus === 'idle'\n ? 'Recent activity exists, but no active goal is driving the project.'\n : 'No project activity has been recorded yet.';\n\n return {\n activeGoals,\n blockedGoals,\n staleGoals,\n nextActions,\n attentionItems,\n timeline,\n digest: {\n status: digestStatus,\n summary: digestSummary,\n nextAction: recommendedAction,\n },\n failedWorkflowRuns,\n recommendedAction,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ProjectStore } from './project-store.js';
|
|
2
|
+
import type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';
|
|
3
|
+
export type ProjectSuggestion = {
|
|
4
|
+
projectId: string;
|
|
5
|
+
projectName: string;
|
|
6
|
+
score: number;
|
|
7
|
+
reason: string;
|
|
8
|
+
};
|
|
9
|
+
export declare class ProjectService {
|
|
10
|
+
private readonly store;
|
|
11
|
+
constructor(store?: ProjectStore);
|
|
12
|
+
create(input: CreateProjectInput): Project;
|
|
13
|
+
get(id: string): Project | null;
|
|
14
|
+
getBySlug(slug: string): Project | null;
|
|
15
|
+
list(query?: ProjectListQuery): ProjectListResult;
|
|
16
|
+
update(id: string, input: UpdateProjectInput): Project;
|
|
17
|
+
delete(id: string): void;
|
|
18
|
+
getWithDetails(id: string): ProjectWithDetails | null;
|
|
19
|
+
attachSession(sessionKey: string, projectId: string): void;
|
|
20
|
+
detachSession(sessionKey: string): void;
|
|
21
|
+
listSessionKeys(projectId: string, limit?: number, offset?: number): string[];
|
|
22
|
+
attachGoal(goalId: string, projectId: string): void;
|
|
23
|
+
detachGoal(goalId: string): void;
|
|
24
|
+
listGoalIds(projectId: string, limit?: number, offset?: number): string[];
|
|
25
|
+
suggestProjectsForSession(sessionKey: string): ProjectSuggestion[];
|
|
26
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getSessionMetadata } from "../storage/sqlite/session-repository.js";
|
|
2
|
+
import "../storage/sqlite/index.js";
|
|
3
|
+
import { ProjectStore } from "./project-store.js";
|
|
4
|
+
import { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from "./goal-bind.js";
|
|
5
|
+
import { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from "./session-bind.js";
|
|
6
|
+
//#region src/projects/project-service.ts
|
|
7
|
+
var ProjectService = class {
|
|
8
|
+
constructor(store = new ProjectStore()) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
}
|
|
11
|
+
create(input) {
|
|
12
|
+
const slug = input.slug?.trim() || this.store.generateSlug(input.name);
|
|
13
|
+
return this.store.create({
|
|
14
|
+
...input,
|
|
15
|
+
slug
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
get(id) {
|
|
19
|
+
return this.store.get(id);
|
|
20
|
+
}
|
|
21
|
+
getBySlug(slug) {
|
|
22
|
+
return this.store.findBySlug(slug);
|
|
23
|
+
}
|
|
24
|
+
list(query) {
|
|
25
|
+
return this.store.list(query);
|
|
26
|
+
}
|
|
27
|
+
update(id, input) {
|
|
28
|
+
return this.store.update(id, input);
|
|
29
|
+
}
|
|
30
|
+
delete(id) {
|
|
31
|
+
this.store.delete(id);
|
|
32
|
+
}
|
|
33
|
+
getWithDetails(id) {
|
|
34
|
+
return this.store.getWithDetails(id);
|
|
35
|
+
}
|
|
36
|
+
attachSession(sessionKey, projectId) {
|
|
37
|
+
bindSessionToProject(sessionKey, projectId);
|
|
38
|
+
}
|
|
39
|
+
detachSession(sessionKey) {
|
|
40
|
+
unbindSessionFromProject(sessionKey);
|
|
41
|
+
}
|
|
42
|
+
listSessionKeys(projectId, limit, offset) {
|
|
43
|
+
return listProjectSessionKeys(projectId, limit, offset);
|
|
44
|
+
}
|
|
45
|
+
attachGoal(goalId, projectId) {
|
|
46
|
+
bindGoalToProject(goalId, projectId);
|
|
47
|
+
}
|
|
48
|
+
detachGoal(goalId) {
|
|
49
|
+
unbindGoalFromProject(goalId);
|
|
50
|
+
}
|
|
51
|
+
listGoalIds(projectId, limit, offset) {
|
|
52
|
+
return listProjectGoalIds(projectId, limit, offset);
|
|
53
|
+
}
|
|
54
|
+
suggestProjectsForSession(sessionKey) {
|
|
55
|
+
const session = getSessionMetadata(sessionKey);
|
|
56
|
+
if (!session) return [];
|
|
57
|
+
const projects = this.list({
|
|
58
|
+
status: "active",
|
|
59
|
+
limit: 500
|
|
60
|
+
}).items;
|
|
61
|
+
const haystack = [
|
|
62
|
+
session.key,
|
|
63
|
+
session.name,
|
|
64
|
+
session.routing?.agentId,
|
|
65
|
+
session.sourceChannel,
|
|
66
|
+
...session.tags ?? []
|
|
67
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
68
|
+
const suggestions = [];
|
|
69
|
+
for (const project of projects) {
|
|
70
|
+
let score = 0;
|
|
71
|
+
const reasons = [];
|
|
72
|
+
if (haystack.includes(project.slug.toLowerCase())) {
|
|
73
|
+
score += 10;
|
|
74
|
+
reasons.push("session metadata matches project slug");
|
|
75
|
+
}
|
|
76
|
+
for (const token of project.name.toLowerCase().split(/\s+/).filter((t) => t.length >= 3)) if (haystack.includes(token)) {
|
|
77
|
+
score += 3;
|
|
78
|
+
reasons.push(`matches ${token}`);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (score > 0) suggestions.push({
|
|
82
|
+
projectId: project.id,
|
|
83
|
+
projectName: project.name,
|
|
84
|
+
score,
|
|
85
|
+
reason: reasons.join(", ")
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
export { ProjectService };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=project-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-service.js","names":[],"sources":["../../../src/projects/project-service.ts"],"sourcesContent":["import { getSessionMetadata } from '../storage/sqlite/index.js';\nimport { ProjectStore } from './project-store.js';\nimport { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from './goal-bind.js';\nimport { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from './session-bind.js';\nimport type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';\n\nexport type ProjectSuggestion = {\n projectId: string;\n projectName: string;\n score: number;\n reason: string;\n};\n\nexport class ProjectService {\n constructor(private readonly store = new ProjectStore()) {}\n\n create(input: CreateProjectInput): Project {\n const slug = input.slug?.trim() || this.store.generateSlug(input.name);\n return this.store.create({ ...input, slug });\n }\n\n get(id: string): Project | null {\n return this.store.get(id);\n }\n\n getBySlug(slug: string): Project | null {\n return this.store.findBySlug(slug);\n }\n\n list(query?: ProjectListQuery): ProjectListResult {\n return this.store.list(query);\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n return this.store.update(id, input);\n }\n\n delete(id: string): void {\n this.store.delete(id);\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n return this.store.getWithDetails(id);\n }\n\n attachSession(sessionKey: string, projectId: string): void {\n bindSessionToProject(sessionKey, projectId);\n }\n\n detachSession(sessionKey: string): void {\n unbindSessionFromProject(sessionKey);\n }\n\n listSessionKeys(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectSessionKeys(projectId, limit, offset);\n }\n\n attachGoal(goalId: string, projectId: string): void {\n bindGoalToProject(goalId, projectId);\n }\n\n detachGoal(goalId: string): void {\n unbindGoalFromProject(goalId);\n }\n\n listGoalIds(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectGoalIds(projectId, limit, offset);\n }\n\n suggestProjectsForSession(sessionKey: string): ProjectSuggestion[] {\n const session = getSessionMetadata(sessionKey);\n if (!session) return [];\n const projects = this.list({ status: 'active', limit: 500 }).items;\n const haystack = [session.key, session.name, session.routing?.agentId, session.sourceChannel, ...(session.tags ?? [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n const suggestions: ProjectSuggestion[] = [];\n for (const project of projects) {\n let score = 0;\n const reasons: string[] = [];\n if (haystack.includes(project.slug.toLowerCase())) {\n score += 10;\n reasons.push('session metadata matches project slug');\n }\n for (const token of project.name.toLowerCase().split(/\\s+/).filter((t) => t.length >= 3)) {\n if (haystack.includes(token)) {\n score += 3;\n reasons.push(`matches ${token}`);\n break;\n }\n }\n if (score > 0) {\n suggestions.push({ projectId: project.id, projectName: project.name, score, reason: reasons.join(', ') });\n }\n }\n return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);\n }\n}\n"],"mappings":";;;;;;AAaA,IAAa,iBAAb,MAA4B;CAC1B,YAAY,QAAyB,IAAI,cAAc,EAAE;AAA5B,OAAA,QAAA;;CAE7B,OAAO,OAAoC;EACzC,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,KAAK,MAAM,aAAa,MAAM,KAAK;AACtE,SAAO,KAAK,MAAM,OAAO;GAAE,GAAG;GAAO;GAAM,CAAC;;CAG9C,IAAI,IAA4B;AAC9B,SAAO,KAAK,MAAM,IAAI,GAAG;;CAG3B,UAAU,MAA8B;AACtC,SAAO,KAAK,MAAM,WAAW,KAAK;;CAGpC,KAAK,OAA6C;AAChD,SAAO,KAAK,MAAM,KAAK,MAAM;;CAG/B,OAAO,IAAY,OAAoC;AACrD,SAAO,KAAK,MAAM,OAAO,IAAI,MAAM;;CAGrC,OAAO,IAAkB;AACvB,OAAK,MAAM,OAAO,GAAG;;CAGvB,eAAe,IAAuC;AACpD,SAAO,KAAK,MAAM,eAAe,GAAG;;CAGtC,cAAc,YAAoB,WAAyB;AACzD,uBAAqB,YAAY,UAAU;;CAG7C,cAAc,YAA0B;AACtC,2BAAyB,WAAW;;CAGtC,gBAAgB,WAAmB,OAAgB,QAA2B;AAC5E,SAAO,uBAAuB,WAAW,OAAO,OAAO;;CAGzD,WAAW,QAAgB,WAAyB;AAClD,oBAAkB,QAAQ,UAAU;;CAGtC,WAAW,QAAsB;AAC/B,wBAAsB,OAAO;;CAG/B,YAAY,WAAmB,OAAgB,QAA2B;AACxE,SAAO,mBAAmB,WAAW,OAAO,OAAO;;CAGrD,0BAA0B,YAAyC;EACjE,MAAM,UAAU,mBAAmB,WAAW;AAC9C,MAAI,CAAC,QAAS,QAAO,EAAE;EACvB,MAAM,WAAW,KAAK,KAAK;GAAE,QAAQ;GAAU,OAAO;GAAK,CAAC,CAAC;EAC7D,MAAM,WAAW;GAAC,QAAQ;GAAK,QAAQ;GAAM,QAAQ,SAAS;GAAS,QAAQ;GAAe,GAAI,QAAQ,QAAQ,EAAE;GAAE,CACnH,OAAO,QAAQ,CACf,KAAK,IAAI,CACT,aAAa;EAChB,MAAM,cAAmC,EAAE;AAC3C,OAAK,MAAM,WAAW,UAAU;GAC9B,IAAI,QAAQ;GACZ,MAAM,UAAoB,EAAE;AAC5B,OAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,CAAC,EAAE;AACjD,aAAS;AACT,YAAQ,KAAK,wCAAwC;;AAEvD,QAAK,MAAM,SAAS,QAAQ,KAAK,aAAa,CAAC,MAAM,MAAM,CAAC,QAAQ,MAAM,EAAE,UAAU,EAAE,CACtF,KAAI,SAAS,SAAS,MAAM,EAAE;AAC5B,aAAS;AACT,YAAQ,KAAK,WAAW,QAAQ;AAChC;;AAGJ,OAAI,QAAQ,EACV,aAAY,KAAK;IAAE,WAAW,QAAQ;IAAI,aAAa,QAAQ;IAAM;IAAO,QAAQ,QAAQ,KAAK,KAAK;IAAE,CAAC;;AAG7G,SAAO,YAAY,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG,EAAE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';
|
|
2
|
+
export type ProjectRow = {
|
|
3
|
+
project_id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
description: string | null;
|
|
7
|
+
status: string;
|
|
8
|
+
default_agent_id: string | null;
|
|
9
|
+
workspace_root: string | null;
|
|
10
|
+
brief: string | null;
|
|
11
|
+
instructions: string | null;
|
|
12
|
+
created_at: number;
|
|
13
|
+
updated_at: number;
|
|
14
|
+
last_active_at: number | null;
|
|
15
|
+
};
|
|
16
|
+
export declare function slugifyProjectName(name: string): string;
|
|
17
|
+
export declare class ProjectStore {
|
|
18
|
+
create(input: CreateProjectInput): Project;
|
|
19
|
+
get(id: string): Project | null;
|
|
20
|
+
findBySlug(slug: string): Project | null;
|
|
21
|
+
list(query?: ProjectListQuery): ProjectListResult;
|
|
22
|
+
update(id: string, input: UpdateProjectInput): Project;
|
|
23
|
+
delete(id: string): void;
|
|
24
|
+
getSessionCount(id: string): number;
|
|
25
|
+
getGoalCount(id: string): number;
|
|
26
|
+
getActiveGoalCount(id: string): number;
|
|
27
|
+
getRecentSessions(id: string, limit?: number): ProjectWithDetails['recentSessions'];
|
|
28
|
+
getRecentWorkflowRuns(id: string, limit?: number): ProjectWithDetails['recentWorkflowRuns'];
|
|
29
|
+
getWithDetails(id: string): ProjectWithDetails | null;
|
|
30
|
+
generateSlug(name: string): string;
|
|
31
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
//#region src/projects/project-store.ts
|
|
4
|
+
function trimOptional(value) {
|
|
5
|
+
if (value == null) return void 0;
|
|
6
|
+
return value.trim() || void 0;
|
|
7
|
+
}
|
|
8
|
+
function nullableText(value) {
|
|
9
|
+
if (value == null) return null;
|
|
10
|
+
return value.trim() || null;
|
|
11
|
+
}
|
|
12
|
+
function projectFromRow(row) {
|
|
13
|
+
return {
|
|
14
|
+
id: row.project_id,
|
|
15
|
+
name: row.name,
|
|
16
|
+
slug: row.slug,
|
|
17
|
+
description: row.description ?? void 0,
|
|
18
|
+
status: row.status,
|
|
19
|
+
defaultAgentId: row.default_agent_id ?? void 0,
|
|
20
|
+
workspaceRoot: row.workspace_root ?? void 0,
|
|
21
|
+
brief: row.brief ?? void 0,
|
|
22
|
+
instructions: row.instructions ?? void 0,
|
|
23
|
+
createdAt: row.created_at,
|
|
24
|
+
updatedAt: row.updated_at,
|
|
25
|
+
lastActiveAt: row.last_active_at ?? void 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function clampLimit(limit, fallback = 50) {
|
|
29
|
+
return Math.min(500, Math.max(1, Math.floor(limit ?? fallback)));
|
|
30
|
+
}
|
|
31
|
+
function normalizeName(name) {
|
|
32
|
+
const trimmed = name.trim();
|
|
33
|
+
if (!trimmed) throw new Error("Project name is required");
|
|
34
|
+
return trimmed;
|
|
35
|
+
}
|
|
36
|
+
function slugifyProjectName(name) {
|
|
37
|
+
return name.trim().toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 80) || `project-${Date.now()}`;
|
|
38
|
+
}
|
|
39
|
+
function projectSortColumn(sortBy) {
|
|
40
|
+
switch (sortBy) {
|
|
41
|
+
case "createdAt": return "created_at";
|
|
42
|
+
case "name": return "LOWER(name)";
|
|
43
|
+
default: return "updated_at";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var ProjectStore = class {
|
|
47
|
+
create(input) {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const name = normalizeName(input.name);
|
|
50
|
+
const slug = input.slug?.trim() || slugifyProjectName(name);
|
|
51
|
+
const project = {
|
|
52
|
+
id: randomUUID(),
|
|
53
|
+
name,
|
|
54
|
+
slug,
|
|
55
|
+
description: trimOptional(input.description),
|
|
56
|
+
status: "active",
|
|
57
|
+
defaultAgentId: trimOptional(input.defaultAgentId),
|
|
58
|
+
workspaceRoot: trimOptional(input.workspaceRoot),
|
|
59
|
+
brief: trimOptional(input.brief),
|
|
60
|
+
instructions: trimOptional(input.instructions),
|
|
61
|
+
createdAt: now,
|
|
62
|
+
updatedAt: now,
|
|
63
|
+
lastActiveAt: now
|
|
64
|
+
};
|
|
65
|
+
runSqliteWriteTransaction((db) => {
|
|
66
|
+
db.prepare(`INSERT INTO projects (
|
|
67
|
+
project_id, name, slug, description, status, default_agent_id, workspace_root,
|
|
68
|
+
brief, instructions, created_at, updated_at, last_active_at
|
|
69
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(project.id, project.name, project.slug, project.description ?? null, project.status, project.defaultAgentId ?? null, project.workspaceRoot ?? null, project.brief ?? null, project.instructions ?? null, project.createdAt, project.updatedAt, project.lastActiveAt ?? null);
|
|
70
|
+
});
|
|
71
|
+
return project;
|
|
72
|
+
}
|
|
73
|
+
get(id) {
|
|
74
|
+
const row = getSqliteDatabase().prepare(`SELECT * FROM projects WHERE project_id = ?`).get(id);
|
|
75
|
+
return row ? projectFromRow(row) : null;
|
|
76
|
+
}
|
|
77
|
+
findBySlug(slug) {
|
|
78
|
+
const row = getSqliteDatabase().prepare(`SELECT * FROM projects WHERE slug = ?`).get(slug.trim());
|
|
79
|
+
return row ? projectFromRow(row) : null;
|
|
80
|
+
}
|
|
81
|
+
list(query = {}) {
|
|
82
|
+
const conditions = [];
|
|
83
|
+
const params = [];
|
|
84
|
+
if (query.status) {
|
|
85
|
+
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
86
|
+
conditions.push(`status IN (${statuses.map(() => "?").join(", ")})`);
|
|
87
|
+
params.push(...statuses);
|
|
88
|
+
}
|
|
89
|
+
if (query.search?.trim()) {
|
|
90
|
+
const like = `%${query.search.trim().toLowerCase()}%`;
|
|
91
|
+
conditions.push(`(LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(COALESCE(description, '')) LIKE ?)`);
|
|
92
|
+
params.push(like, like, like);
|
|
93
|
+
}
|
|
94
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
95
|
+
const limit = clampLimit(query.limit, 50);
|
|
96
|
+
const offset = Math.max(0, Math.floor(query.offset ?? 0));
|
|
97
|
+
const sortOrder = query.sortOrder === "asc" ? "ASC" : "DESC";
|
|
98
|
+
const sortColumn = projectSortColumn(query.sortBy);
|
|
99
|
+
const db = getSqliteDatabase();
|
|
100
|
+
const total = db.prepare(`SELECT COUNT(*) AS total FROM projects ${where}`).get(...params).total;
|
|
101
|
+
return {
|
|
102
|
+
items: db.prepare(`SELECT * FROM projects ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`).all(...params, limit, offset).map(projectFromRow),
|
|
103
|
+
total,
|
|
104
|
+
limit,
|
|
105
|
+
offset,
|
|
106
|
+
hasMore: offset + limit < total
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
update(id, input) {
|
|
110
|
+
const before = this.get(id);
|
|
111
|
+
if (!before) throw new Error(`Project not found: ${id}`);
|
|
112
|
+
const next = {
|
|
113
|
+
...before,
|
|
114
|
+
...input.name !== void 0 ? { name: normalizeName(input.name) } : {},
|
|
115
|
+
...input.description !== void 0 ? { description: nullableText(input.description) ?? void 0 } : {},
|
|
116
|
+
...input.status !== void 0 ? { status: input.status } : {},
|
|
117
|
+
...input.defaultAgentId !== void 0 ? { defaultAgentId: nullableText(input.defaultAgentId) ?? void 0 } : {},
|
|
118
|
+
...input.workspaceRoot !== void 0 ? { workspaceRoot: nullableText(input.workspaceRoot) ?? void 0 } : {},
|
|
119
|
+
...input.brief !== void 0 ? { brief: nullableText(input.brief) ?? void 0 } : {},
|
|
120
|
+
...input.instructions !== void 0 ? { instructions: nullableText(input.instructions) ?? void 0 } : {},
|
|
121
|
+
updatedAt: Date.now()
|
|
122
|
+
};
|
|
123
|
+
runSqliteWriteTransaction((db) => {
|
|
124
|
+
db.prepare(`UPDATE projects SET
|
|
125
|
+
name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?
|
|
126
|
+
WHERE project_id = ?`).run(next.name, next.description ?? null, next.status, next.defaultAgentId ?? null, next.workspaceRoot ?? null, next.brief ?? null, next.instructions ?? null, next.updatedAt, id);
|
|
127
|
+
});
|
|
128
|
+
return this.get(id);
|
|
129
|
+
}
|
|
130
|
+
delete(id) {
|
|
131
|
+
runSqliteWriteTransaction((db) => {
|
|
132
|
+
db.prepare(`UPDATE sessions SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
133
|
+
db.prepare(`UPDATE goals SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
134
|
+
db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
135
|
+
db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
136
|
+
db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
137
|
+
db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
getSessionCount(id) {
|
|
141
|
+
return getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM sessions WHERE project_id = ? AND hidden_from_session_list = 0`).get(id).total;
|
|
142
|
+
}
|
|
143
|
+
getGoalCount(id) {
|
|
144
|
+
return getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ?`).get(id).total;
|
|
145
|
+
}
|
|
146
|
+
getActiveGoalCount(id) {
|
|
147
|
+
return getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ? AND status IN ('active', 'paused', 'blocked', 'needs_input')`).get(id).total;
|
|
148
|
+
}
|
|
149
|
+
getRecentSessions(id, limit = 5) {
|
|
150
|
+
return getSqliteDatabase().prepare(`SELECT session_key, name, updated_at, agent_id FROM sessions
|
|
151
|
+
WHERE project_id = ? AND hidden_from_session_list = 0 ORDER BY updated_at DESC LIMIT ?`).all(id, clampLimit(limit, 5)).map((row) => ({
|
|
152
|
+
key: row.session_key,
|
|
153
|
+
name: row.name ?? void 0,
|
|
154
|
+
updatedAt: new Date(row.updated_at).toISOString(),
|
|
155
|
+
agentId: row.agent_id
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
getRecentWorkflowRuns(id, limit = 5) {
|
|
159
|
+
return getSqliteDatabase().prepare(`SELECT run_id, definition_id, status, created_at_ms FROM workflow_runs
|
|
160
|
+
WHERE project_id = ? ORDER BY created_at_ms DESC LIMIT ?`).all(id, clampLimit(limit, 5)).map((row) => ({
|
|
161
|
+
runId: row.run_id,
|
|
162
|
+
definitionId: row.definition_id,
|
|
163
|
+
status: row.status,
|
|
164
|
+
createdAt: row.created_at_ms
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
getWithDetails(id) {
|
|
168
|
+
const project = this.get(id);
|
|
169
|
+
if (!project) return null;
|
|
170
|
+
return {
|
|
171
|
+
...project,
|
|
172
|
+
sessionCount: this.getSessionCount(id),
|
|
173
|
+
goalCount: this.getGoalCount(id),
|
|
174
|
+
activeGoalCount: this.getActiveGoalCount(id),
|
|
175
|
+
recentSessions: this.getRecentSessions(id),
|
|
176
|
+
recentWorkflowRuns: this.getRecentWorkflowRuns(id)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
generateSlug(name) {
|
|
180
|
+
const base = slugifyProjectName(name);
|
|
181
|
+
let candidate = base;
|
|
182
|
+
let counter = 2;
|
|
183
|
+
while (this.findBySlug(candidate)) {
|
|
184
|
+
candidate = `${base}-${counter}`;
|
|
185
|
+
counter += 1;
|
|
186
|
+
}
|
|
187
|
+
return candidate;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
//#endregion
|
|
191
|
+
export { ProjectStore, slugifyProjectName };
|
|
192
|
+
|
|
193
|
+
//# sourceMappingURL=project-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-store.js","names":[],"sources":["../../../src/projects/project-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { getSqliteDatabase, runSqliteWriteTransaction } from '../storage/sqlite/transaction.js';\nimport type {\n CreateProjectInput,\n Project,\n ProjectListQuery,\n ProjectListResult,\n ProjectStatus,\n ProjectWithDetails,\n UpdateProjectInput,\n} from './types.js';\n\nexport type ProjectRow = {\n project_id: string;\n name: string;\n slug: string;\n description: string | null;\n status: string;\n default_agent_id: string | null;\n workspace_root: string | null;\n brief: string | null;\n instructions: string | null;\n created_at: number;\n updated_at: number;\n last_active_at: number | null;\n};\n\nfunction trimOptional(value: string | null | undefined): string | undefined {\n if (value == null) return undefined;\n const trimmed = value.trim();\n return trimmed || undefined;\n}\n\nfunction nullableText(value: string | null | undefined): string | null {\n if (value == null) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\nfunction projectFromRow(row: ProjectRow): Project {\n return {\n id: row.project_id,\n name: row.name,\n slug: row.slug,\n description: row.description ?? undefined,\n status: row.status as ProjectStatus,\n defaultAgentId: row.default_agent_id ?? undefined,\n workspaceRoot: row.workspace_root ?? undefined,\n brief: row.brief ?? undefined,\n instructions: row.instructions ?? undefined,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n lastActiveAt: row.last_active_at ?? undefined,\n };\n}\n\nfunction clampLimit(limit: number | undefined, fallback = 50): number {\n return Math.min(500, Math.max(1, Math.floor(limit ?? fallback)));\n}\n\nfunction normalizeName(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) throw new Error('Project name is required');\n return trimmed;\n}\n\nexport function slugifyProjectName(name: string): string {\n const slug = name\n .trim()\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n return slug || `project-${Date.now()}`;\n}\n\nfunction projectSortColumn(sortBy: ProjectListQuery['sortBy']): string {\n switch (sortBy) {\n case 'createdAt':\n return 'created_at';\n case 'name':\n return 'LOWER(name)';\n case 'updatedAt':\n default:\n return 'updated_at';\n }\n}\n\nexport class ProjectStore {\n create(input: CreateProjectInput): Project {\n const now = Date.now();\n const name = normalizeName(input.name);\n const slug = input.slug?.trim() || slugifyProjectName(name);\n const project: Project = {\n id: randomUUID(),\n name,\n slug,\n description: trimOptional(input.description),\n status: 'active',\n defaultAgentId: trimOptional(input.defaultAgentId),\n workspaceRoot: trimOptional(input.workspaceRoot),\n brief: trimOptional(input.brief),\n instructions: trimOptional(input.instructions),\n createdAt: now,\n updatedAt: now,\n lastActiveAt: now,\n };\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `INSERT INTO projects (\n project_id, name, slug, description, status, default_agent_id, workspace_root,\n brief, instructions, created_at, updated_at, last_active_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n project.id,\n project.name,\n project.slug,\n project.description ?? null,\n project.status,\n project.defaultAgentId ?? null,\n project.workspaceRoot ?? null,\n project.brief ?? null,\n project.instructions ?? null,\n project.createdAt,\n project.updatedAt,\n project.lastActiveAt ?? null,\n );\n });\n return project;\n }\n\n get(id: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE project_id = ?`)\n .get(id) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n findBySlug(slug: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE slug = ?`)\n .get(slug.trim()) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n list(query: ProjectListQuery = {}): ProjectListResult {\n const conditions: string[] = [];\n const params: Array<string | number> = [];\n if (query.status) {\n const statuses = Array.isArray(query.status) ? query.status : [query.status];\n conditions.push(`status IN (${statuses.map(() => '?').join(', ')})`);\n params.push(...statuses);\n }\n if (query.search?.trim()) {\n const like = `%${query.search.trim().toLowerCase()}%`;\n conditions.push(`(LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(COALESCE(description, '')) LIKE ?)`);\n params.push(like, like, like);\n }\n const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = clampLimit(query.limit, 50);\n const offset = Math.max(0, Math.floor(query.offset ?? 0));\n const sortOrder = query.sortOrder === 'asc' ? 'ASC' : 'DESC';\n const sortColumn = projectSortColumn(query.sortBy);\n const db = getSqliteDatabase();\n const total = (db.prepare(`SELECT COUNT(*) AS total FROM projects ${where}`).get(...params) as { total: number }).total;\n const rows = db\n .prepare(`SELECT * FROM projects ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`)\n .all(...params, limit, offset) as ProjectRow[];\n return { items: rows.map(projectFromRow), total, limit, offset, hasMore: offset + limit < total };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const before = this.get(id);\n if (!before) throw new Error(`Project not found: ${id}`);\n const next = {\n ...before,\n ...(input.name !== undefined ? { name: normalizeName(input.name) } : {}),\n ...(input.description !== undefined ? { description: nullableText(input.description) ?? undefined } : {}),\n ...(input.status !== undefined ? { status: input.status } : {}),\n ...(input.defaultAgentId !== undefined ? { defaultAgentId: nullableText(input.defaultAgentId) ?? undefined } : {}),\n ...(input.workspaceRoot !== undefined ? { workspaceRoot: nullableText(input.workspaceRoot) ?? undefined } : {}),\n ...(input.brief !== undefined ? { brief: nullableText(input.brief) ?? undefined } : {}),\n ...(input.instructions !== undefined ? { instructions: nullableText(input.instructions) ?? undefined } : {}),\n updatedAt: Date.now(),\n } satisfies Project;\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `UPDATE projects SET\n name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?\n WHERE project_id = ?`,\n ).run(\n next.name,\n next.description ?? null,\n next.status,\n next.defaultAgentId ?? null,\n next.workspaceRoot ?? null,\n next.brief ?? null,\n next.instructions ?? null,\n next.updatedAt,\n id,\n );\n });\n return this.get(id)!;\n }\n\n delete(id: string): void {\n runSqliteWriteTransaction((db) => {\n db.prepare(`UPDATE sessions SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE goals SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);\n });\n }\n\n getSessionCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM sessions WHERE project_id = ? AND hidden_from_session_list = 0`)\n .get(id) as { total: number }).total;\n }\n\n getGoalCount(id: string): number {\n return (getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ?`).get(id) as { total: number }).total;\n }\n\n getActiveGoalCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ? AND status IN ('active', 'paused', 'blocked', 'needs_input')`)\n .get(id) as { total: number }).total;\n }\n\n getRecentSessions(id: string, limit = 5): ProjectWithDetails['recentSessions'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT session_key, name, updated_at, agent_id FROM sessions\n WHERE project_id = ? AND hidden_from_session_list = 0 ORDER BY updated_at DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ session_key: string; name: string | null; updated_at: number; agent_id: string }>;\n return rows.map((row) => ({\n key: row.session_key,\n name: row.name ?? undefined,\n updatedAt: new Date(row.updated_at).toISOString(),\n agentId: row.agent_id,\n }));\n }\n\n getRecentWorkflowRuns(id: string, limit = 5): ProjectWithDetails['recentWorkflowRuns'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT run_id, definition_id, status, created_at_ms FROM workflow_runs\n WHERE project_id = ? ORDER BY created_at_ms DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ run_id: string; definition_id: string; status: string; created_at_ms: number }>;\n return rows.map((row) => ({ runId: row.run_id, definitionId: row.definition_id, status: row.status, createdAt: row.created_at_ms }));\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n const project = this.get(id);\n if (!project) return null;\n return {\n ...project,\n sessionCount: this.getSessionCount(id),\n goalCount: this.getGoalCount(id),\n activeGoalCount: this.getActiveGoalCount(id),\n recentSessions: this.getRecentSessions(id),\n recentWorkflowRuns: this.getRecentWorkflowRuns(id),\n };\n }\n\n generateSlug(name: string): string {\n const base = slugifyProjectName(name);\n let candidate = base;\n let counter = 2;\n while (this.findBySlug(candidate)) {\n candidate = `${base}-${counter}`;\n counter += 1;\n }\n return candidate;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function bindSessionToProject(sessionKey: string, projectId: string): void;
|
|
2
|
+
export declare function unbindSessionFromProject(sessionKey: string): void;
|
|
3
|
+
export declare function listProjectSessionKeys(projectId: string, limit?: number, offset?: number): string[];
|
|
4
|
+
export declare function bindSessionsToProject(sessionKeys: string[], projectId: string): void;
|
|
5
|
+
export declare function moveSessionToProject(sessionKey: string, targetProjectId: string): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
2
|
+
//#region src/projects/session-bind.ts
|
|
3
|
+
function projectExists(projectId) {
|
|
4
|
+
return Boolean(getSqliteDatabase().prepare(`SELECT 1 FROM projects WHERE project_id = ?`).get(projectId));
|
|
5
|
+
}
|
|
6
|
+
function assertProjectExists(projectId) {
|
|
7
|
+
if (!projectExists(projectId)) throw new Error(`Project not found: ${projectId}`);
|
|
8
|
+
}
|
|
9
|
+
function assertSessionExists(sessionKey) {
|
|
10
|
+
if (!getSqliteDatabase().prepare(`SELECT 1 FROM sessions WHERE session_key = ?`).get(sessionKey)) throw new Error(`Session not found: ${sessionKey}`);
|
|
11
|
+
}
|
|
12
|
+
function bindSessionToProject(sessionKey, projectId) {
|
|
13
|
+
assertProjectExists(projectId);
|
|
14
|
+
assertSessionExists(sessionKey);
|
|
15
|
+
runSqliteWriteTransaction((db) => {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
db.prepare(`UPDATE sessions SET project_id = ?, updated_at = ? WHERE session_key = ?`).run(projectId, now, sessionKey);
|
|
18
|
+
db.prepare(`UPDATE projects SET last_active_at = ?, updated_at = ? WHERE project_id = ?`).run(now, now, projectId);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function unbindSessionFromProject(sessionKey) {
|
|
22
|
+
assertSessionExists(sessionKey);
|
|
23
|
+
runSqliteWriteTransaction((db) => {
|
|
24
|
+
db.prepare(`UPDATE sessions SET project_id = NULL, updated_at = ? WHERE session_key = ?`).run(Date.now(), sessionKey);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function listProjectSessionKeys(projectId, limit = 100, offset = 0) {
|
|
28
|
+
assertProjectExists(projectId);
|
|
29
|
+
return getSqliteDatabase().prepare(`SELECT session_key FROM sessions WHERE project_id = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?`).all(projectId, Math.min(500, Math.max(1, Math.floor(limit))), Math.max(0, Math.floor(offset))).map((row) => row.session_key);
|
|
30
|
+
}
|
|
31
|
+
function bindSessionsToProject(sessionKeys, projectId) {
|
|
32
|
+
assertProjectExists(projectId);
|
|
33
|
+
runSqliteWriteTransaction((db) => {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const stmt = db.prepare(`UPDATE sessions SET project_id = ?, updated_at = ? WHERE session_key = ?`);
|
|
36
|
+
for (const key of sessionKeys) stmt.run(projectId, now, key);
|
|
37
|
+
db.prepare(`UPDATE projects SET last_active_at = ?, updated_at = ? WHERE project_id = ?`).run(now, now, projectId);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function moveSessionToProject(sessionKey, targetProjectId) {
|
|
41
|
+
bindSessionToProject(sessionKey, targetProjectId);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { bindSessionToProject, bindSessionsToProject, listProjectSessionKeys, moveSessionToProject, unbindSessionFromProject };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=session-bind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-bind.js","names":[],"sources":["../../../src/projects/session-bind.ts"],"sourcesContent":["import { getSqliteDatabase, runSqliteWriteTransaction } from '../storage/sqlite/transaction.js';\n\nfunction projectExists(projectId: string): boolean {\n return Boolean(getSqliteDatabase().prepare(`SELECT 1 FROM projects WHERE project_id = ?`).get(projectId));\n}\n\nfunction assertProjectExists(projectId: string): void {\n if (!projectExists(projectId)) {\n throw new Error(`Project not found: ${projectId}`);\n }\n}\n\nfunction assertSessionExists(sessionKey: string): void {\n const row = getSqliteDatabase().prepare(`SELECT 1 FROM sessions WHERE session_key = ?`).get(sessionKey);\n if (!row) throw new Error(`Session not found: ${sessionKey}`);\n}\n\nexport function bindSessionToProject(sessionKey: string, projectId: string): void {\n assertProjectExists(projectId);\n assertSessionExists(sessionKey);\n runSqliteWriteTransaction((db) => {\n const now = Date.now();\n db.prepare(`UPDATE sessions SET project_id = ?, updated_at = ? WHERE session_key = ?`).run(projectId, now, sessionKey);\n db.prepare(`UPDATE projects SET last_active_at = ?, updated_at = ? WHERE project_id = ?`).run(now, now, projectId);\n });\n}\n\nexport function unbindSessionFromProject(sessionKey: string): void {\n assertSessionExists(sessionKey);\n runSqliteWriteTransaction((db) => {\n db.prepare(`UPDATE sessions SET project_id = NULL, updated_at = ? WHERE session_key = ?`).run(Date.now(), sessionKey);\n });\n}\n\nexport function listProjectSessionKeys(projectId: string, limit = 100, offset = 0): string[] {\n assertProjectExists(projectId);\n const rows = getSqliteDatabase()\n .prepare(`SELECT session_key FROM sessions WHERE project_id = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?`)\n .all(projectId, Math.min(500, Math.max(1, Math.floor(limit))), Math.max(0, Math.floor(offset))) as Array<{ session_key: string }>;\n return rows.map((row) => row.session_key);\n}\n\nexport function bindSessionsToProject(sessionKeys: string[], projectId: string): void {\n assertProjectExists(projectId);\n runSqliteWriteTransaction((db) => {\n const now = Date.now();\n const stmt = db.prepare(`UPDATE sessions SET project_id = ?, updated_at = ? WHERE session_key = ?`);\n for (const key of sessionKeys) {\n stmt.run(projectId, now, key);\n }\n db.prepare(`UPDATE projects SET last_active_at = ?, updated_at = ? WHERE project_id = ?`).run(now, now, projectId);\n });\n}\n\nexport function moveSessionToProject(sessionKey: string, targetProjectId: string): void {\n bindSessionToProject(sessionKey, targetProjectId);\n}\n"],"mappings":";;AAEA,SAAS,cAAc,WAA4B;AACjD,QAAO,QAAQ,mBAAmB,CAAC,QAAQ,8CAA8C,CAAC,IAAI,UAAU,CAAC;;AAG3G,SAAS,oBAAoB,WAAyB;AACpD,KAAI,CAAC,cAAc,UAAU,CAC3B,OAAM,IAAI,MAAM,sBAAsB,YAAY;;AAItD,SAAS,oBAAoB,YAA0B;AAErD,KAAI,CADQ,mBAAmB,CAAC,QAAQ,+CAA+C,CAAC,IAAI,WACpF,CAAE,OAAM,IAAI,MAAM,sBAAsB,aAAa;;AAG/D,SAAgB,qBAAqB,YAAoB,WAAyB;AAChF,qBAAoB,UAAU;AAC9B,qBAAoB,WAAW;AAC/B,4BAA2B,OAAO;EAChC,MAAM,MAAM,KAAK,KAAK;AACtB,KAAG,QAAQ,2EAA2E,CAAC,IAAI,WAAW,KAAK,WAAW;AACtH,KAAG,QAAQ,8EAA8E,CAAC,IAAI,KAAK,KAAK,UAAU;GAClH;;AAGJ,SAAgB,yBAAyB,YAA0B;AACjE,qBAAoB,WAAW;AAC/B,4BAA2B,OAAO;AAChC,KAAG,QAAQ,8EAA8E,CAAC,IAAI,KAAK,KAAK,EAAE,WAAW;GACrH;;AAGJ,SAAgB,uBAAuB,WAAmB,QAAQ,KAAK,SAAS,GAAa;AAC3F,qBAAoB,UAAU;AAI9B,QAHa,mBAAmB,CAC7B,QAAQ,kGAAkG,CAC1G,IAAI,WAAW,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,CACrF,CAAC,KAAK,QAAQ,IAAI,YAAY;;AAG3C,SAAgB,sBAAsB,aAAuB,WAAyB;AACpF,qBAAoB,UAAU;AAC9B,4BAA2B,OAAO;EAChC,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,OAAO,GAAG,QAAQ,2EAA2E;AACnG,OAAK,MAAM,OAAO,YAChB,MAAK,IAAI,WAAW,KAAK,IAAI;AAE/B,KAAG,QAAQ,8EAA8E,CAAC,IAAI,KAAK,KAAK,UAAU;GAClH;;AAGJ,SAAgB,qBAAqB,YAAoB,iBAA+B;AACtF,sBAAqB,YAAY,gBAAgB"}
|