@xopcai/xopc 0.0.122 → 0.0.123
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/{agent-avatar-dicebear-CK3s5cJo.js → agent-avatar-dicebear-Bicr3ZBg.js} +1 -1
- package/dist/gateway/static/root/assets/agents-CxDSFHGx.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-CWsNHtTs.js → app-management-settings-panel-CbEy9sR1.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-C4b8y6iZ.js → appearance-settings-CTHja6n9.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-CYb6fame.js → apps-page-CyZdgqvm.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-DAtNW3Ia.js → archive-plugin-Bpy64GjU.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-Dm-RY54y.js → attachment-load-DMkp4xSb.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-BKHewSVT.js → automation-suggestion-card-KnYaP7NT.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-8VLKM4au.js +1 -0
- package/dist/gateway/static/root/assets/{binary-plugins-BG6Xnk71.js → binary-plugins-DA9C5mCw.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-DfndiO3h.js → block-editor-CiSXk_YC.js} +27 -27
- package/dist/gateway/static/root/assets/browser-settings-page-5tPg0DcP.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-Cx93GLdw.js → capability-presets-api-CEJ44WCU.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-D528_YFy.js → capability-presets-settings-panel-BJW3aVST.js} +1 -1
- package/dist/gateway/static/root/assets/{channel-recipient-api-DdPTtEs8.js → channel-recipient-api-CDhrhfhN.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-Ck-3h5lI.js → channels-settings-CorUCTrs.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-page-VKstC3sP.js +2 -0
- package/dist/gateway/static/root/assets/{directory-picker-path-field-D835EHJd.js → directory-picker-path-field-7jKfbH0J.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-CCa1F7Fg.js → dreaming-settings-m1-wmz4o.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-W2ITCPSA.js → extension-debug-page-C9qQQrBS.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DrvUytvn.js → extension-page-DcBBMEGl.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-qM8aLT5u.js → extension-settings-page-BQ-jMMP9.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-ovkvM8KQ.js → fetch-Dtpbbe0s.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-BB3biuD0.js → gateway-config-api-BN6pyX2c.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-mmmnIH27.js → gateway-settings-hQtgVmSR.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-CanQ8UcR.js → gateway-startup-retry-BIET2x3y.js} +1 -1
- package/dist/gateway/static/root/assets/{goal-create-dialog-CNqNdEkQ.js → goal-create-dialog-BuIMDx6C.js} +1 -1
- package/dist/gateway/static/root/assets/goal-detail-page-FEypCq74.js +1 -0
- package/dist/gateway/static/root/assets/{goals-config-api-BBOX6mUl.js → goals-config-api-CQm2r1Ty.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-C4-2_MLG.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-Bh3A7OZV.js → goals-settings-DDrdRMxB.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-BJVok3Sl.js +1 -0
- package/dist/gateway/static/root/assets/index-CBDr5xjB.css +1 -0
- package/dist/gateway/static/root/assets/{index-DdqSO9R2.js → index-T0SaNd5a.js} +18 -18
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B4TKJHla.js → keyboard-shortcuts-settings-C2TuQppb.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-BgVk5DvZ.js → logs-page-DwkwPgid.js} +2 -2
- package/dist/gateway/static/root/assets/{markdown-split-DcFtdLci.js → markdown-split-DhONQ81k.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-B6SmeLVB.js → media-plugins-DS44pJzr.js} +1 -1
- package/dist/gateway/static/root/assets/messages-avfvyZuQ.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-huNY4A1G.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-DdfGuEZ1.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-B0bT9Hqa.js +2 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-C29O19s7.js → preview-open-alternatives-Dej6zHnr.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CjxhPorG.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-C4Z5OJMF.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-CVCOPUVd.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-B4ad9GfB.js → save-bar-controls-Zu1FzH6z.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-DdYncfLR.js → schedule-field-BW78mKc0.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-BMTrDE-7.js → schema-form-Cc-IOydE.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-B4qExXHC.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-Ce8yLTBi.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-MwTW2eK4.js → settings-collapsible-section-DIpIYht_.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-Dg6LBbcr.js → settings-form-section-BFxBNxOG.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-C4fivemT.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-layout-BX4T-OLF.js → settings-page-layout-CLx4o2PN.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-Kyc4eUf0.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-DWE-dUFh.js → share-preview-page-ZswWCrDu.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-Bu76CMiC.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-D-CFg86j.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-CUU4f6Ss.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-Dd6Y35Cm.js → system-settings-panel-3ZNcGRhS.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-Cx6XV5A2.js → typed-models-lib-bpzZjiVF.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-D4sMbmy7.js → use-channel-catalog-BCjZLZ3O.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-Bx-hjMQc.js → user-profile-settings-panel-CIXafITi.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-D7b_vZnv.js → utils-DXMtz5j_.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-C1MrQD8R.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-DqizIcY5.js +3 -0
- package/dist/gateway/static/root/assets/{workflow-start-dialog-B0lavx_o.js → workflow-start-dialog-B4sZpTDQ.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-B3sVNPFk.js +18 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/tools/factory.js +1 -1
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/goal-tool.d.ts +5 -1
- package/dist/src/agent/tools/goal-tool.js +8 -2
- package/dist/src/agent/tools/goal-tool.js.map +1 -1
- package/dist/src/agent/tools/index.d.ts +1 -1
- package/dist/src/chat-commands/builtins/goal.js +4 -1
- package/dist/src/chat-commands/builtins/goal.js.map +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/hono/routes/projects.js +97 -13
- package/dist/src/gateway/hono/routes/projects.js.map +1 -1
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/projects/index.d.ts +1 -0
- package/dist/src/projects/index.js +2 -1
- package/dist/src/projects/project-service.d.ts +14 -0
- package/dist/src/projects/project-service.js +96 -3
- package/dist/src/projects/project-service.js.map +1 -1
- package/dist/src/projects/types.d.ts +3 -1
- package/dist/src/projects/workspace-project.d.ts +22 -0
- package/dist/src/projects/workspace-project.js +143 -0
- package/dist/src/projects/workspace-project.js.map +1 -0
- package/dist/src/tui/backends/embedded-backend.d.ts +7 -1
- package/dist/src/tui/backends/embedded-backend.js +40 -1
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +7 -1
- package/dist/src/tui/backends/gateway-sse-backend.js +22 -0
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/tui-backend.d.ts +16 -0
- package/dist/src/tui/tui.js +21 -0
- package/dist/src/tui/tui.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.js +1 -1
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-SsQrj9B-.js +0 -2
- package/dist/gateway/static/root/assets/automations-page-DpOOq1nk.js +0 -1
- package/dist/gateway/static/root/assets/browser-settings-page-9nXxgh4u.js +0 -8
- package/dist/gateway/static/root/assets/connectors-page-DL-fAqE_.js +0 -2
- package/dist/gateway/static/root/assets/goal-detail-page-Ceu5zrN6.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-BP1kfvWv.js +0 -2
- package/dist/gateway/static/root/assets/heartbeat-settings-YoYgKlfX.js +0 -1
- package/dist/gateway/static/root/assets/index-jGB-2nI-.css +0 -1
- package/dist/gateway/static/root/assets/messages-B5O4OIEo.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-BU0x8PGP.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-D6JYVP4I.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-mqRJYnJS.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-DOTgzoeh.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DGUIb4y5.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DVKKUj3u.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BVbzUWxW.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DrNNoSqE.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-CMDqgeMR.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-Ce1pcFtR.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-aKSraR8l.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-CS-FU_B-.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-BaF3u0LA.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-CkhbRt1q.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel--TdohF5N.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-CZR05Bu_.js +0 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.js","names":["path","record"],"sources":["../../../../../src/gateway/hono/routes/projects.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { readdir, realpath, stat } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport type { GoalWithDetails } from '../../../goals/index.js';\nimport { GoalService } from '../../../goals/index.js';\nimport {\n buildProjectLoopOverview,\n isValidProjectAgentId,\n normalizeProjectAgentId,\n resolveProjectAgentId,\n type ProjectStatus,\n type ProjectWorkflowRunBrief,\n} from '../../../projects/index.js';\nimport {\n getSqliteDatabase,\n getSessionMetadata,\n listMemoryRecords,\n loadTranscriptRowsForSession,\n upsertMemoryRecord,\n} from '../../../storage/sqlite/index.js';\nimport { createGatewayRouteLogger } from '../lib/route-logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createGatewayRouteLogger('Projects');\n\nfunction parseProjectStatus(raw: unknown): ProjectStatus | undefined {\n return raw === 'active' || raw === 'paused' || raw === 'archived' ? raw : undefined;\n}\n\nfunction parseLimit(raw: string | undefined, fallback = 50): number | undefined {\n if (!raw) return undefined;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? Math.min(500, Math.max(1, n)) : fallback;\n}\n\nfunction textField(body: Record<string, unknown>, key: string): string | undefined {\n const value = body[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction optionalTextField(body: Record<string, unknown>, key: string): string | null | undefined {\n if (!(key in body)) return undefined;\n const value = body[key];\n return typeof value === 'string' ? value : null;\n}\n\nconst SKIP_FILE_NAMES = new Set(['.git', 'node_modules']);\n\nfunction normalizeRelativePath(raw: string | undefined): string {\n const normalized = (raw ?? '').trim().replaceAll('\\\\', '/');\n return normalized.replace(/^\\/+/, '');\n}\n\nfunction isWithinDirectory(root: string, target: string): boolean {\n const relativePath = path.relative(root, target);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction textFromTranscriptContent(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((part) => {\n if (!part || typeof part !== 'object') return '';\n const record = part as Record<string, unknown>;\n return typeof record.text === 'string' ? record.text : '';\n })\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction buildSessionSummaryMemoryContent(sessionKey: string, explicitSummary?: string): string {\n const summary = explicitSummary?.trim();\n if (summary) return summary;\n const rows = loadTranscriptRowsForSession(sessionKey)\n .filter((row) => {\n if (!row || typeof row !== 'object') return false;\n const record = row as unknown as Record<string, unknown>;\n const role = record.role;\n return role === 'user' || role === 'assistant' || role === 'compactionSummary' || role === 'branchSummary';\n })\n .slice(-12);\n const lines = rows\n .map((row) => {\n const record = row as unknown as Record<string, unknown>;\n const rowRole = typeof record.role === 'string' ? record.role : 'message';\n const role = rowRole === 'compactionSummary' || rowRole === 'branchSummary' ? 'summary' : rowRole;\n const text = (typeof record.summary === 'string' ? record.summary : undefined) ?? textFromTranscriptContent(record.content);\n const compact = text.trim().replace(/\\s+/g, ' ').slice(0, 280);\n return compact ? `- ${role}: ${compact}` : '';\n })\n .filter(Boolean);\n return lines.length > 0\n ? [`Session summary for ${sessionKey}:`, ...lines].join('\\n')\n : `Session summary for ${sessionKey}: no transcript content available.`;\n}\n\nfunction listFailedProjectWorkflowRuns(projectId: string, limit = 5): ProjectWorkflowRunBrief[] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT run_id, definition_id, status, created_at_ms, error_message\n FROM workflow_runs\n WHERE project_id = ? AND status IN ('failed', 'timeout', 'cancelled')\n ORDER BY created_at_ms DESC\n LIMIT ?`,\n )\n .all(projectId, Math.max(1, Math.min(20, Math.floor(limit)))) as Array<{\n run_id: string;\n definition_id: string;\n status: string;\n created_at_ms: number;\n error_message: string | null;\n }>;\n return rows.map((row) => ({\n runId: row.run_id,\n definitionId: row.definition_id,\n status: row.status,\n createdAt: row.created_at_ms,\n errorMessage: row.error_message ?? undefined,\n }));\n}\n\nfunction buildProjectDigestMemoryContent(input: ReturnType<typeof buildProjectLoopOverview> & { projectName: string }): string {\n const lines = [\n `Project digest: ${input.projectName}`,\n `Status: ${input.digest.summary}`,\n input.digest.nextAction ? `Next: ${input.digest.nextAction}` : undefined,\n ].filter(Boolean) as string[];\n if (input.attentionItems.length > 0) {\n lines.push('', 'Attention:');\n for (const item of input.attentionItems.slice(0, 8)) {\n lines.push(`- ${item.kind}: ${item.title}${item.detail ? ` - ${item.detail}` : ''}`);\n }\n }\n if (input.timeline.length > 0) {\n lines.push('', 'Recent activity:');\n for (const item of input.timeline.slice(0, 8)) {\n lines.push(`- ${item.kind}: ${item.title}${item.detail ? ` - ${item.detail}` : ''}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction projectDigestMemoryRecordId(projectId: string): string {\n return `project-digest:${projectId}`;\n}\n\nexport function registerProjectsRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n const goals = new GoalService();\n\n authenticated.post('/api/projects', async (c) => {\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const name = textField(body, 'name')?.trim();\n if (!name) return c.json({ ok: false, error: 'Missing name' }, 400);\n const defaultAgentId = normalizeProjectAgentId(textField(body, 'defaultAgentId'));\n if (!isValidProjectAgentId(service.currentConfig, defaultAgentId)) {\n return c.json({ ok: false, error: 'Default agent not found' }, 400);\n }\n const project = service.projects.create({\n name,\n description: textField(body, 'description'),\n defaultAgentId,\n workspaceRoot: textField(body, 'workspaceRoot'),\n brief: textField(body, 'brief'),\n instructions: textField(body, 'instructions'),\n });\n return c.json({ ok: true, project }, 201);\n });\n\n authenticated.get('/api/projects', async (c) => {\n const status = parseProjectStatus(c.req.query('status'));\n const result = service.projects.list({\n ...(status ? { status } : {}),\n search: c.req.query('search'),\n sortBy: c.req.query('sortBy') as 'updatedAt' | 'createdAt' | 'name' | undefined,\n sortOrder: c.req.query('sortOrder') as 'asc' | 'desc' | undefined,\n limit: parseLimit(c.req.query('limit')),\n offset: c.req.query('offset') ? Math.max(0, Number.parseInt(c.req.query('offset')!, 10) || 0) : undefined,\n });\n return c.json({ ok: true, ...result });\n });\n\n authenticated.get('/api/projects/suggestions', async (c) => {\n const sessionKey = c.req.query('sessionKey')?.trim();\n return c.json({ ok: true, suggestions: sessionKey ? service.projects.suggestProjectsForSession(sessionKey) : [] });\n });\n\n authenticated.get('/api/projects/:id/overview', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const goalIds = service.projects.listGoalIds(project.id, parseLimit(c.req.query('goalLimit'), 100));\n const projectGoals = goalIds\n .map((id) => goals.get(id))\n .filter((goal): goal is GoalWithDetails => Boolean(goal));\n const loop = buildProjectLoopOverview({\n project,\n goals: projectGoals,\n recentWorkflowRuns: project.recentWorkflowRuns,\n failedWorkflowRuns: listFailedProjectWorkflowRuns(project.id),\n memoryRecords: listMemoryRecords({ projectId: project.id, status: 'active', limit: 5 }),\n });\n return c.json({\n ok: true,\n overview: {\n project,\n stats: {\n sessionCount: project.sessionCount,\n goalCount: project.goalCount,\n activeGoalCount: project.activeGoalCount,\n recentWorkflowRunCount: project.recentWorkflowRuns.length,\n staleGoalCount: loop.staleGoals.length,\n attentionCount: loop.attentionItems.length,\n failedWorkflowRunCount: loop.failedWorkflowRuns.length,\n },\n activeGoals: loop.activeGoals,\n blockedGoals: loop.blockedGoals,\n staleGoals: loop.staleGoals,\n nextActions: loop.nextActions,\n attentionItems: loop.attentionItems,\n timeline: loop.timeline,\n digest: loop.digest,\n failedWorkflowRuns: loop.failedWorkflowRuns,\n recentSessions: project.recentSessions,\n recentWorkflowRuns: project.recentWorkflowRuns,\n recommendedAction: loop.recommendedAction,\n },\n });\n });\n\n authenticated.post('/api/projects/:id/digest-memory', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const goalIds = service.projects.listGoalIds(project.id, 100);\n const projectGoals = goalIds\n .map((id) => goals.get(id))\n .filter((goal): goal is GoalWithDetails => Boolean(goal));\n const loop = buildProjectLoopOverview({\n project,\n goals: projectGoals,\n recentWorkflowRuns: project.recentWorkflowRuns,\n failedWorkflowRuns: listFailedProjectWorkflowRuns(project.id),\n memoryRecords: listMemoryRecords({ projectId: project.id, status: 'active', limit: 5 }),\n });\n const record = upsertMemoryRecord({\n id: projectDigestMemoryRecordId(project.id),\n providerId: 'local',\n kind: 'daily_note',\n agentId: resolveProjectAgentId({\n config: service.currentConfig,\n projects: service.projects,\n projectId: project.id,\n }),\n workspaceId: project.workspaceRoot,\n projectId: project.id,\n content: buildProjectDigestMemoryContent({ ...loop, projectName: project.name }),\n source: {\n provider: 'project-digest',\n },\n confidence: 0.75,\n tags: ['project', 'project-digest', project.slug],\n status: 'active',\n sensitivity: 'normal',\n });\n return c.json({ ok: true, record }, 201);\n });\n\n authenticated.post('/api/projects/:id/blockers', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const title = typeof body.title === 'string' ? body.title.trim() : '';\n if (!title) return c.json({ ok: false, error: 'Missing title' }, 400);\n const reason = typeof body.reason === 'string' ? body.reason.trim() : '';\n const goal = goals.create({\n title,\n description: reason || undefined,\n agentId: resolveProjectAgentId({\n config: service.currentConfig,\n projects: service.projects,\n projectId: project.id,\n }),\n priority: body.priority === 'low' || body.priority === 'normal' || body.priority === 'high' ? body.priority : 'high',\n source: 'api',\n projectId: project.id,\n });\n const blocked = goals.setStatus(goal.id, 'blocked', { reason: reason || title });\n if (reason) {\n goals.setContextMessage({ goalId: goal.id, text: reason });\n }\n return c.json({ ok: true, goal: blocked ?? goals.get(goal.id) }, 201);\n });\n\n authenticated.get('/api/projects/:id', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n return c.json({ ok: true, project });\n });\n\n authenticated.patch('/api/projects/:id', async (c) => {\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const status = parseProjectStatus(body.status);\n const defaultAgentPatch = optionalTextField(body, 'defaultAgentId');\n const defaultAgentId = defaultAgentPatch === undefined ? undefined : normalizeProjectAgentId(defaultAgentPatch);\n if (defaultAgentId && !isValidProjectAgentId(service.currentConfig, defaultAgentId)) {\n return c.json({ ok: false, error: 'Default agent not found' }, 400);\n }\n try {\n const project = service.projects.update(c.req.param('id'), {\n ...(textField(body, 'name') !== undefined ? { name: textField(body, 'name') } : {}),\n ...(optionalTextField(body, 'description') !== undefined ? { description: optionalTextField(body, 'description') } : {}),\n ...(status ? { status } : {}),\n ...(defaultAgentPatch !== undefined ? { defaultAgentId: defaultAgentId ?? null } : {}),\n ...(optionalTextField(body, 'workspaceRoot') !== undefined ? { workspaceRoot: optionalTextField(body, 'workspaceRoot') } : {}),\n ...(optionalTextField(body, 'brief') !== undefined ? { brief: optionalTextField(body, 'brief') } : {}),\n ...(optionalTextField(body, 'instructions') !== undefined ? { instructions: optionalTextField(body, 'instructions') } : {}),\n });\n return c.json({ ok: true, project });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.delete('/api/projects/:id', async (c) => {\n service.projects.delete(c.req.param('id'));\n return c.json({ ok: true });\n });\n\n authenticated.get('/api/projects/:id/sessions', async (c) => {\n try {\n const keys = service.projects.listSessionKeys(c.req.param('id'), parseLimit(c.req.query('limit'), 100));\n const sessions = await Promise.all(keys.map((key) => service.sessions.getSession(key)));\n return c.json({ ok: true, sessions: sessions.filter(Boolean) });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.get('/api/projects/:id/goals', async (c) => {\n try {\n const ids = service.projects.listGoalIds(c.req.param('id'), parseLimit(c.req.query('limit'), 100));\n return c.json({ ok: true, goals: ids.map((id) => goals.get(id)).filter(Boolean) });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.get('/api/projects/:id/files', async (c) => {\n const project = service.projects.get(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n if (!project.workspaceRoot?.trim()) {\n return c.json({ ok: false, error: 'Project workspace root is not set' }, 400);\n }\n\n let root: string;\n try {\n root = await realpath(project.workspaceRoot);\n const rootStat = await stat(root);\n if (!rootStat.isDirectory()) {\n return c.json({ ok: false, error: 'Project workspace root is not a directory' }, 400);\n }\n } catch (err) {\n log.warn({ err, projectId: project.id, path: project.workspaceRoot }, 'project workspace root unavailable');\n return c.json({ ok: false, error: 'Project workspace root is unavailable' }, 404);\n }\n\n const requestedPath = normalizeRelativePath(c.req.query('path'));\n const candidate = path.resolve(root, requestedPath);\n if (!isWithinDirectory(root, candidate)) {\n return c.json({ ok: false, error: 'Path is outside project workspace' }, 400);\n }\n\n let currentPath: string;\n try {\n currentPath = await realpath(candidate);\n if (!isWithinDirectory(root, currentPath)) {\n return c.json({ ok: false, error: 'Path is outside project workspace' }, 400);\n }\n const currentStat = await stat(currentPath);\n if (!currentStat.isDirectory()) {\n return c.json({ ok: false, error: 'Path is not a directory' }, 400);\n }\n } catch (err) {\n log.warn({ err, projectId: project.id, path: candidate }, 'project file path unavailable');\n return c.json({ ok: false, error: 'Path not found' }, 404);\n }\n\n try {\n const dirents = await readdir(currentPath, { withFileTypes: true });\n const entries = await Promise.all(dirents\n .filter((entry) => !SKIP_FILE_NAMES.has(entry.name))\n .map(async (entry) => {\n const absolutePath = path.join(currentPath, entry.name);\n const entryStat = await stat(absolutePath).catch(() => null);\n const relativePath = path.relative(root, absolutePath).split(path.sep).join('/');\n return {\n name: entry.name,\n path: relativePath,\n type: entry.isDirectory() ? 'directory' : 'file',\n size: entryStat?.isFile() ? entryStat.size : undefined,\n updatedAt: entryStat?.mtime.toISOString(),\n };\n }));\n entries.sort((left, right) => {\n if (left.type !== right.type) return left.type === 'directory' ? -1 : 1;\n return left.name.localeCompare(right.name);\n });\n const relativeCurrentPath = path.relative(root, currentPath).split(path.sep).join('/');\n const parentRelativePath = relativeCurrentPath ? path.dirname(relativeCurrentPath).replaceAll('\\\\', '/') : null;\n return c.json({\n ok: true,\n root,\n path: relativeCurrentPath === '.' ? '' : relativeCurrentPath,\n parentPath: parentRelativePath === '.' ? '' : parentRelativePath,\n entries,\n });\n } catch (err) {\n log.warn({ err, projectId: project.id, path: currentPath }, 'project file list failed');\n if ((err as NodeJS.ErrnoException)?.code === 'EACCES') {\n return c.json({ ok: false, error: 'Permission denied' }, 403);\n }\n return c.json({ ok: false, error: 'Failed to read project files' }, 500);\n }\n });\n\n authenticated.post('/api/projects/:id/sessions/:sessionKey', async (c) => {\n try {\n service.projects.attachSession(c.req.param('sessionKey'), c.req.param('id'));\n const session = await service.sessions.getSession(c.req.param('sessionKey'));\n return c.json({ ok: true, session });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.delete('/api/projects/:id/sessions/:sessionKey', async (c) => {\n try {\n const projectId = c.req.param('id');\n const sessionKey = c.req.param('sessionKey');\n if (!service.projects.get(projectId)) {\n return c.json({ ok: false, error: 'Project not found' }, 404);\n }\n const session = await service.sessions.getSession(sessionKey);\n if (!session) {\n return c.json({ ok: false, error: 'Session not found' }, 404);\n }\n if (session.projectId !== projectId) {\n return c.json({ ok: false, error: 'Session is not attached to this project' }, 409);\n }\n service.projects.detachSession(sessionKey);\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.post('/api/projects/:id/sessions/:sessionKey/summary-memory', async (c) => {\n const projectId = c.req.param('id');\n const sessionKey = c.req.param('sessionKey');\n const project = service.projects.get(projectId);\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const session = getSessionMetadata(sessionKey);\n if (!session) return c.json({ ok: false, error: 'Session not found' }, 404);\n if (session.projectId !== projectId) {\n return c.json({ ok: false, error: 'Session is not attached to this project' }, 409);\n }\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const content = buildSessionSummaryMemoryContent(\n sessionKey,\n typeof body.summary === 'string' ? body.summary : undefined,\n );\n const record = upsertMemoryRecord({\n providerId: 'local',\n kind: 'session_summary',\n agentId: session.routing?.agentId ?? 'main',\n workspaceId: session.cwd,\n sessionKey,\n projectId,\n content,\n source: {\n provider: 'project-summary',\n sessionEntryId: sessionKey,\n },\n confidence: typeof body.confidence === 'number' ? body.confidence : 0.7,\n tags: ['project', 'session-summary', project.slug],\n status: 'active',\n sensitivity: 'normal',\n evidence: [{ sessionKey }],\n });\n return c.json({ ok: true, record }, 201);\n });\n\n authenticated.post('/api/projects/:id/goals/:goalId', async (c) => {\n try {\n service.projects.attachGoal(c.req.param('goalId'), c.req.param('id'));\n return c.json({ ok: true, goal: goals.get(c.req.param('goalId')) });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.delete('/api/projects/:id/goals/:goalId', async (c) => {\n try {\n const projectId = c.req.param('id');\n const goalId = c.req.param('goalId');\n if (!service.projects.get(projectId)) {\n return c.json({ ok: false, error: 'Project not found' }, 404);\n }\n const goal = goals.get(goalId);\n if (!goal) {\n return c.json({ ok: false, error: 'Goal not found' }, 404);\n }\n if (goal.projectId !== projectId) {\n return c.json({ ok: false, error: 'Goal is not attached to this project' }, 409);\n }\n service.projects.detachGoal(goalId);\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"projects.js","names":["path","record"],"sources":["../../../../../src/gateway/hono/routes/projects.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { readdir, realpath, stat } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport type { GoalWithDetails } from '../../../goals/index.js';\nimport { GoalService } from '../../../goals/index.js';\nimport {\n buildProjectLoopOverview,\n isValidProjectAgentId,\n normalizeProjectAgentId,\n ProjectWorkspaceConflictError,\n ProjectWorkspaceMissingError,\n resolveProjectAgentId,\n type ProjectStatus,\n type ProjectWorkflowRunBrief,\n} from '../../../projects/index.js';\nimport {\n getSqliteDatabase,\n getSessionMetadata,\n listMemoryRecords,\n loadTranscriptRowsForSession,\n upsertMemoryRecord,\n} from '../../../storage/sqlite/index.js';\nimport { createGatewayRouteLogger } from '../lib/route-logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createGatewayRouteLogger('Projects');\n\nfunction parseProjectStatus(raw: unknown): ProjectStatus | undefined {\n return raw === 'active' || raw === 'paused' || raw === 'archived' ? raw : undefined;\n}\n\nfunction parseLimit(raw: string | undefined, fallback = 50): number | undefined {\n if (!raw) return undefined;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? Math.min(500, Math.max(1, n)) : fallback;\n}\n\nfunction textField(body: Record<string, unknown>, key: string): string | undefined {\n const value = body[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction optionalTextField(body: Record<string, unknown>, key: string): string | null | undefined {\n if (!(key in body)) return undefined;\n const value = body[key];\n return typeof value === 'string' ? value : null;\n}\n\nconst SKIP_FILE_NAMES = new Set(['.git', 'node_modules']);\n\nfunction normalizeRelativePath(raw: string | undefined): string {\n const normalized = (raw ?? '').trim().replaceAll('\\\\', '/');\n return normalized.replace(/^\\/+/, '');\n}\n\nfunction isWithinDirectory(root: string, target: string): boolean {\n const relativePath = path.relative(root, target);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction textFromTranscriptContent(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((part) => {\n if (!part || typeof part !== 'object') return '';\n const record = part as Record<string, unknown>;\n return typeof record.text === 'string' ? record.text : '';\n })\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction buildSessionSummaryMemoryContent(sessionKey: string, explicitSummary?: string): string {\n const summary = explicitSummary?.trim();\n if (summary) return summary;\n const rows = loadTranscriptRowsForSession(sessionKey)\n .filter((row) => {\n if (!row || typeof row !== 'object') return false;\n const record = row as unknown as Record<string, unknown>;\n const role = record.role;\n return role === 'user' || role === 'assistant' || role === 'compactionSummary' || role === 'branchSummary';\n })\n .slice(-12);\n const lines = rows\n .map((row) => {\n const record = row as unknown as Record<string, unknown>;\n const rowRole = typeof record.role === 'string' ? record.role : 'message';\n const role = rowRole === 'compactionSummary' || rowRole === 'branchSummary' ? 'summary' : rowRole;\n const text = (typeof record.summary === 'string' ? record.summary : undefined) ?? textFromTranscriptContent(record.content);\n const compact = text.trim().replace(/\\s+/g, ' ').slice(0, 280);\n return compact ? `- ${role}: ${compact}` : '';\n })\n .filter(Boolean);\n return lines.length > 0\n ? [`Session summary for ${sessionKey}:`, ...lines].join('\\n')\n : `Session summary for ${sessionKey}: no transcript content available.`;\n}\n\nfunction listFailedProjectWorkflowRuns(projectId: string, limit = 5): ProjectWorkflowRunBrief[] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT run_id, definition_id, status, created_at_ms, error_message\n FROM workflow_runs\n WHERE project_id = ? AND status IN ('failed', 'timeout', 'cancelled')\n ORDER BY created_at_ms DESC\n LIMIT ?`,\n )\n .all(projectId, Math.max(1, Math.min(20, Math.floor(limit)))) as Array<{\n run_id: string;\n definition_id: string;\n status: string;\n created_at_ms: number;\n error_message: string | null;\n }>;\n return rows.map((row) => ({\n runId: row.run_id,\n definitionId: row.definition_id,\n status: row.status,\n createdAt: row.created_at_ms,\n errorMessage: row.error_message ?? undefined,\n }));\n}\n\nfunction buildProjectDigestMemoryContent(input: ReturnType<typeof buildProjectLoopOverview> & { projectName: string }): string {\n const lines = [\n `Project digest: ${input.projectName}`,\n `Status: ${input.digest.summary}`,\n input.digest.nextAction ? `Next: ${input.digest.nextAction}` : undefined,\n ].filter(Boolean) as string[];\n if (input.attentionItems.length > 0) {\n lines.push('', 'Attention:');\n for (const item of input.attentionItems.slice(0, 8)) {\n lines.push(`- ${item.kind}: ${item.title}${item.detail ? ` - ${item.detail}` : ''}`);\n }\n }\n if (input.timeline.length > 0) {\n lines.push('', 'Recent activity:');\n for (const item of input.timeline.slice(0, 8)) {\n lines.push(`- ${item.kind}: ${item.title}${item.detail ? ` - ${item.detail}` : ''}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction projectDigestMemoryRecordId(projectId: string): string {\n return `project-digest:${projectId}`;\n}\n\nexport function registerProjectsRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n const goals = new GoalService();\n\n authenticated.post('/api/projects', async (c) => {\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const name = textField(body, 'name')?.trim();\n const workspaceRoot = textField(body, 'workspaceRoot')?.trim();\n if (!name && !workspaceRoot) return c.json({ ok: false, error: 'Missing name' }, 400);\n const defaultAgentId = normalizeProjectAgentId(textField(body, 'defaultAgentId'));\n if (!isValidProjectAgentId(service.currentConfig, defaultAgentId)) {\n return c.json({ ok: false, error: 'Default agent not found' }, 400);\n }\n try {\n const project = service.projects.create({\n name,\n description: textField(body, 'description'),\n defaultAgentId,\n workspaceRoot,\n createWorkspaceRoot: body.createWorkspaceRoot === true,\n brief: textField(body, 'brief'),\n instructions: textField(body, 'instructions'),\n });\n return c.json({ ok: true, project }, 201);\n } catch (error) {\n if (error instanceof ProjectWorkspaceConflictError) {\n return c.json({ ok: false, code: 'workspace_already_bound', error: error.message, project: error.project }, 409);\n }\n if (error instanceof ProjectWorkspaceMissingError) {\n return c.json({ ok: false, code: 'workspace_root_missing', error: error.message, workspaceRoot: error.workspaceRoot }, 409);\n }\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);\n }\n });\n\n authenticated.get('/api/projects', async (c) => {\n const status = parseProjectStatus(c.req.query('status'));\n const result = service.projects.list({\n ...(status ? { status } : {}),\n search: c.req.query('search'),\n sortBy: c.req.query('sortBy') as 'updatedAt' | 'createdAt' | 'name' | undefined,\n sortOrder: c.req.query('sortOrder') as 'asc' | 'desc' | undefined,\n limit: parseLimit(c.req.query('limit')),\n offset: c.req.query('offset') ? Math.max(0, Number.parseInt(c.req.query('offset')!, 10) || 0) : undefined,\n });\n return c.json({ ok: true, ...result });\n });\n\n authenticated.get('/api/projects/suggestions', async (c) => {\n const sessionKey = c.req.query('sessionKey')?.trim();\n return c.json({ ok: true, suggestions: sessionKey ? service.projects.suggestProjectsForSession(sessionKey) : [] });\n });\n\n authenticated.post('/api/projects/resolve-workspace', async (c) => {\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const workspacePath = textField(body, 'workspacePath')?.trim();\n if (!workspacePath) return c.json({ ok: false, error: 'Missing workspacePath' }, 400);\n const agentId = textField(body, 'agentId')?.trim() || 'main';\n let match;\n try {\n match = service.projects.resolveOrCreateForWorkspacePath({\n workspacePath,\n agentId,\n autoCreate: body.autoCreate !== false,\n });\n } catch (error) {\n if (error instanceof ProjectWorkspaceConflictError) {\n match = { project: error.project, reason: 'exact' as const, created: false };\n } else {\n throw error;\n }\n }\n if (!match) return c.json({ ok: true, project: null });\n\n const sessionKey = textField(body, 'sessionKey')?.trim();\n if (sessionKey) {\n const existingSession = getSessionMetadata(sessionKey);\n if (!existingSession) {\n await service.sessionIndexInstance.saveMessages(sessionKey, [], {\n metadata: {\n sourceChannel: 'tui',\n sourceChatId: `default:direct:${sessionKey}`,\n sessionType: 'chat',\n projectId: match.project.id,\n routing: {\n agentId,\n source: 'tui',\n accountId: 'default',\n peerKind: 'direct',\n peerId: sessionKey,\n },\n },\n });\n }\n service.projects.attachSession(sessionKey, match.project.id);\n }\n\n return c.json({ ok: true, ...match });\n });\n\n authenticated.get('/api/projects/:id/overview', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const goalIds = service.projects.listGoalIds(project.id, parseLimit(c.req.query('goalLimit'), 100));\n const projectGoals = goalIds\n .map((id) => goals.get(id))\n .filter((goal): goal is GoalWithDetails => Boolean(goal));\n const loop = buildProjectLoopOverview({\n project,\n goals: projectGoals,\n recentWorkflowRuns: project.recentWorkflowRuns,\n failedWorkflowRuns: listFailedProjectWorkflowRuns(project.id),\n memoryRecords: listMemoryRecords({ projectId: project.id, status: 'active', limit: 5 }),\n });\n return c.json({\n ok: true,\n overview: {\n project,\n stats: {\n sessionCount: project.sessionCount,\n goalCount: project.goalCount,\n activeGoalCount: project.activeGoalCount,\n recentWorkflowRunCount: project.recentWorkflowRuns.length,\n staleGoalCount: loop.staleGoals.length,\n attentionCount: loop.attentionItems.length,\n failedWorkflowRunCount: loop.failedWorkflowRuns.length,\n },\n activeGoals: loop.activeGoals,\n blockedGoals: loop.blockedGoals,\n staleGoals: loop.staleGoals,\n nextActions: loop.nextActions,\n attentionItems: loop.attentionItems,\n timeline: loop.timeline,\n digest: loop.digest,\n failedWorkflowRuns: loop.failedWorkflowRuns,\n recentSessions: project.recentSessions,\n recentWorkflowRuns: project.recentWorkflowRuns,\n recommendedAction: loop.recommendedAction,\n },\n });\n });\n\n authenticated.post('/api/projects/:id/digest-memory', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const goalIds = service.projects.listGoalIds(project.id, 100);\n const projectGoals = goalIds\n .map((id) => goals.get(id))\n .filter((goal): goal is GoalWithDetails => Boolean(goal));\n const loop = buildProjectLoopOverview({\n project,\n goals: projectGoals,\n recentWorkflowRuns: project.recentWorkflowRuns,\n failedWorkflowRuns: listFailedProjectWorkflowRuns(project.id),\n memoryRecords: listMemoryRecords({ projectId: project.id, status: 'active', limit: 5 }),\n });\n const record = upsertMemoryRecord({\n id: projectDigestMemoryRecordId(project.id),\n providerId: 'local',\n kind: 'daily_note',\n agentId: resolveProjectAgentId({\n config: service.currentConfig,\n projects: service.projects,\n projectId: project.id,\n }),\n workspaceId: project.workspaceRoot,\n projectId: project.id,\n content: buildProjectDigestMemoryContent({ ...loop, projectName: project.name }),\n source: {\n provider: 'project-digest',\n },\n confidence: 0.75,\n tags: ['project', 'project-digest', project.slug],\n status: 'active',\n sensitivity: 'normal',\n });\n return c.json({ ok: true, record }, 201);\n });\n\n authenticated.post('/api/projects/:id/blockers', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const title = typeof body.title === 'string' ? body.title.trim() : '';\n if (!title) return c.json({ ok: false, error: 'Missing title' }, 400);\n const reason = typeof body.reason === 'string' ? body.reason.trim() : '';\n const goal = goals.create({\n title,\n description: reason || undefined,\n agentId: resolveProjectAgentId({\n config: service.currentConfig,\n projects: service.projects,\n projectId: project.id,\n }),\n priority: body.priority === 'low' || body.priority === 'normal' || body.priority === 'high' ? body.priority : 'high',\n source: 'api',\n projectId: project.id,\n });\n const blocked = goals.setStatus(goal.id, 'blocked', { reason: reason || title });\n if (reason) {\n goals.setContextMessage({ goalId: goal.id, text: reason });\n }\n return c.json({ ok: true, goal: blocked ?? goals.get(goal.id) }, 201);\n });\n\n authenticated.get('/api/projects/:id', async (c) => {\n const project = service.projects.getWithDetails(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n return c.json({ ok: true, project });\n });\n\n authenticated.patch('/api/projects/:id', async (c) => {\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const status = parseProjectStatus(body.status);\n const defaultAgentPatch = optionalTextField(body, 'defaultAgentId');\n const defaultAgentId = defaultAgentPatch === undefined ? undefined : normalizeProjectAgentId(defaultAgentPatch);\n if (defaultAgentId && !isValidProjectAgentId(service.currentConfig, defaultAgentId)) {\n return c.json({ ok: false, error: 'Default agent not found' }, 400);\n }\n try {\n const project = service.projects.update(c.req.param('id'), {\n ...(textField(body, 'name') !== undefined ? { name: textField(body, 'name') } : {}),\n ...(optionalTextField(body, 'description') !== undefined ? { description: optionalTextField(body, 'description') } : {}),\n ...(status ? { status } : {}),\n ...(defaultAgentPatch !== undefined ? { defaultAgentId: defaultAgentId ?? null } : {}),\n ...(optionalTextField(body, 'workspaceRoot') !== undefined ? { workspaceRoot: optionalTextField(body, 'workspaceRoot') } : {}),\n createWorkspaceRoot: body.createWorkspaceRoot === true,\n ...(optionalTextField(body, 'brief') !== undefined ? { brief: optionalTextField(body, 'brief') } : {}),\n ...(optionalTextField(body, 'instructions') !== undefined ? { instructions: optionalTextField(body, 'instructions') } : {}),\n });\n return c.json({ ok: true, project });\n } catch (error) {\n if (error instanceof ProjectWorkspaceConflictError) {\n return c.json({ ok: false, code: 'workspace_already_bound', error: error.message, project: error.project }, 409);\n }\n if (error instanceof ProjectWorkspaceMissingError) {\n return c.json({ ok: false, code: 'workspace_root_missing', error: error.message, workspaceRoot: error.workspaceRoot }, 409);\n }\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.delete('/api/projects/:id', async (c) => {\n service.projects.delete(c.req.param('id'));\n return c.json({ ok: true });\n });\n\n authenticated.get('/api/projects/:id/sessions', async (c) => {\n try {\n const keys = service.projects.listSessionKeys(c.req.param('id'), parseLimit(c.req.query('limit'), 100));\n const sessions = await Promise.all(keys.map((key) => service.sessions.getSession(key)));\n return c.json({ ok: true, sessions: sessions.filter(Boolean) });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.get('/api/projects/:id/goals', async (c) => {\n try {\n const ids = service.projects.listGoalIds(c.req.param('id'), parseLimit(c.req.query('limit'), 100));\n return c.json({ ok: true, goals: ids.map((id) => goals.get(id)).filter(Boolean) });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.get('/api/projects/:id/files', async (c) => {\n const project = service.projects.get(c.req.param('id'));\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n if (!project.workspaceRoot?.trim()) {\n return c.json({ ok: false, error: 'Project workspace root is not set' }, 400);\n }\n\n let root: string;\n try {\n root = await realpath(project.workspaceRoot);\n const rootStat = await stat(root);\n if (!rootStat.isDirectory()) {\n return c.json({ ok: false, error: 'Project workspace root is not a directory' }, 400);\n }\n } catch (err) {\n log.warn({ err, projectId: project.id, path: project.workspaceRoot }, 'project workspace root unavailable');\n return c.json({ ok: false, error: 'Project workspace root is unavailable' }, 404);\n }\n\n const requestedPath = normalizeRelativePath(c.req.query('path'));\n const candidate = path.resolve(root, requestedPath);\n if (!isWithinDirectory(root, candidate)) {\n return c.json({ ok: false, error: 'Path is outside project workspace' }, 400);\n }\n\n let currentPath: string;\n try {\n currentPath = await realpath(candidate);\n if (!isWithinDirectory(root, currentPath)) {\n return c.json({ ok: false, error: 'Path is outside project workspace' }, 400);\n }\n const currentStat = await stat(currentPath);\n if (!currentStat.isDirectory()) {\n return c.json({ ok: false, error: 'Path is not a directory' }, 400);\n }\n } catch (err) {\n log.warn({ err, projectId: project.id, path: candidate }, 'project file path unavailable');\n return c.json({ ok: false, error: 'Path not found' }, 404);\n }\n\n try {\n const dirents = await readdir(currentPath, { withFileTypes: true });\n const entries = await Promise.all(dirents\n .filter((entry) => !SKIP_FILE_NAMES.has(entry.name))\n .map(async (entry) => {\n const absolutePath = path.join(currentPath, entry.name);\n const entryStat = await stat(absolutePath).catch(() => null);\n const relativePath = path.relative(root, absolutePath).split(path.sep).join('/');\n return {\n name: entry.name,\n path: relativePath,\n type: entry.isDirectory() ? 'directory' : 'file',\n size: entryStat?.isFile() ? entryStat.size : undefined,\n updatedAt: entryStat?.mtime.toISOString(),\n };\n }));\n entries.sort((left, right) => {\n if (left.type !== right.type) return left.type === 'directory' ? -1 : 1;\n return left.name.localeCompare(right.name);\n });\n const relativeCurrentPath = path.relative(root, currentPath).split(path.sep).join('/');\n const parentRelativePath = relativeCurrentPath ? path.dirname(relativeCurrentPath).replaceAll('\\\\', '/') : null;\n return c.json({\n ok: true,\n root,\n path: relativeCurrentPath === '.' ? '' : relativeCurrentPath,\n parentPath: parentRelativePath === '.' ? '' : parentRelativePath,\n entries,\n });\n } catch (err) {\n log.warn({ err, projectId: project.id, path: currentPath }, 'project file list failed');\n if ((err as NodeJS.ErrnoException)?.code === 'EACCES') {\n return c.json({ ok: false, error: 'Permission denied' }, 403);\n }\n return c.json({ ok: false, error: 'Failed to read project files' }, 500);\n }\n });\n\n authenticated.post('/api/projects/:id/sessions/:sessionKey', async (c) => {\n try {\n service.projects.attachSession(c.req.param('sessionKey'), c.req.param('id'));\n const session = await service.sessions.getSession(c.req.param('sessionKey'));\n return c.json({ ok: true, session });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.delete('/api/projects/:id/sessions/:sessionKey', async (c) => {\n try {\n const projectId = c.req.param('id');\n const sessionKey = c.req.param('sessionKey');\n if (!service.projects.get(projectId)) {\n return c.json({ ok: false, error: 'Project not found' }, 404);\n }\n const session = await service.sessions.getSession(sessionKey);\n if (!session) {\n return c.json({ ok: false, error: 'Session not found' }, 404);\n }\n if (session.projectId !== projectId) {\n return c.json({ ok: false, error: 'Session is not attached to this project' }, 409);\n }\n service.projects.detachSession(sessionKey);\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.post('/api/projects/:id/sessions/:sessionKey/summary-memory', async (c) => {\n const projectId = c.req.param('id');\n const sessionKey = c.req.param('sessionKey');\n const project = service.projects.get(projectId);\n if (!project) return c.json({ ok: false, error: 'Project not found' }, 404);\n const session = getSessionMetadata(sessionKey);\n if (!session) return c.json({ ok: false, error: 'Session not found' }, 404);\n if (session.projectId !== projectId) {\n return c.json({ ok: false, error: 'Session is not attached to this project' }, 409);\n }\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;\n const content = buildSessionSummaryMemoryContent(\n sessionKey,\n typeof body.summary === 'string' ? body.summary : undefined,\n );\n const record = upsertMemoryRecord({\n providerId: 'local',\n kind: 'session_summary',\n agentId: session.routing?.agentId ?? 'main',\n workspaceId: session.cwd,\n sessionKey,\n projectId,\n content,\n source: {\n provider: 'project-summary',\n sessionEntryId: sessionKey,\n },\n confidence: typeof body.confidence === 'number' ? body.confidence : 0.7,\n tags: ['project', 'session-summary', project.slug],\n status: 'active',\n sensitivity: 'normal',\n evidence: [{ sessionKey }],\n });\n return c.json({ ok: true, record }, 201);\n });\n\n authenticated.post('/api/projects/:id/goals/:goalId', async (c) => {\n try {\n service.projects.attachGoal(c.req.param('goalId'), c.req.param('id'));\n return c.json({ ok: true, goal: goals.get(c.req.param('goalId')) });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n\n authenticated.delete('/api/projects/:id/goals/:goalId', async (c) => {\n try {\n const projectId = c.req.param('id');\n const goalId = c.req.param('goalId');\n if (!service.projects.get(projectId)) {\n return c.json({ ok: false, error: 'Project not found' }, 404);\n }\n const goal = goals.get(goalId);\n if (!goal) {\n return c.json({ ok: false, error: 'Goal not found' }, 404);\n }\n if (goal.projectId !== projectId) {\n return c.json({ ok: false, error: 'Goal is not attached to this project' }, 409);\n }\n service.projects.detachGoal(goalId);\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../utils/logger.js";
|
|
3
3
|
import { init_loader, loadConfig } from "../config/loader.js";
|
|
4
|
-
import { loadUndiciRuntimeDeps } from "../infra/undici-fetch.js";
|
|
5
4
|
import { toConversation } from "./channel-shared.js";
|
|
6
5
|
import { createGatewayHttpClientFromConfig, resolveGatewayHttpBaseUrl } from "./gateway-http-client.js";
|
|
6
|
+
import { loadUndiciRuntimeDeps } from "../infra/undici-fetch.js";
|
|
7
7
|
//#region src/mcp/channel-bridge.ts
|
|
8
8
|
init_loader();
|
|
9
9
|
init_logger();
|
|
@@ -2,10 +2,11 @@ import { ProjectStore, slugifyProjectName } from "./project-store.js";
|
|
|
2
2
|
import { getProjectForSession, getProjectWorkspacePathForSession, resolveProjectWorkspacePath } from "./workspace.js";
|
|
3
3
|
import { bindGoalToProject, bindGoalsToProject, listProjectGoalIds, unbindGoalFromProject } from "./goal-bind.js";
|
|
4
4
|
import { bindSessionToProject, bindSessionsToProject, listProjectSessionKeys, moveSessionToProject, unbindSessionFromProject } from "./session-bind.js";
|
|
5
|
+
import { ProjectWorkspaceConflictError, ProjectWorkspaceMissingError, canonicalWorkspacePath, ensureWorkspaceDirectory, inferProjectNameFromWorkspaceRoot, isPathSameOrInsideWorkspace, isSafeAutoCreateWorkspaceRoot, resolveWorkspaceProjectRoot, workspaceDirectoryExists } from "./workspace-project.js";
|
|
5
6
|
import { ProjectService } from "./project-service.js";
|
|
6
7
|
import { isValidProjectAgentId, normalizeProjectAgentId, resolveProjectAgentId } from "./project-agent.js";
|
|
7
8
|
import { buildProjectLoopOverview } from "./project-loop.js";
|
|
8
9
|
import { bindWorkflowRunToProject } from "./workflow-bind.js";
|
|
9
10
|
import { bindAutomationToProject } from "./automation-bind.js";
|
|
10
11
|
import { bindMemoryRecordToProject } from "./memory-bind.js";
|
|
11
|
-
export { ProjectService, ProjectStore, bindAutomationToProject, bindGoalToProject, bindGoalsToProject, bindMemoryRecordToProject, bindSessionToProject, bindSessionsToProject, bindWorkflowRunToProject, buildProjectLoopOverview, getProjectForSession, getProjectWorkspacePathForSession, isValidProjectAgentId, listProjectGoalIds, listProjectSessionKeys, moveSessionToProject, normalizeProjectAgentId, resolveProjectAgentId, resolveProjectWorkspacePath, slugifyProjectName, unbindGoalFromProject, unbindSessionFromProject };
|
|
12
|
+
export { ProjectService, ProjectStore, ProjectWorkspaceConflictError, ProjectWorkspaceMissingError, bindAutomationToProject, bindGoalToProject, bindGoalsToProject, bindMemoryRecordToProject, bindSessionToProject, bindSessionsToProject, bindWorkflowRunToProject, buildProjectLoopOverview, canonicalWorkspacePath, ensureWorkspaceDirectory, getProjectForSession, getProjectWorkspacePathForSession, inferProjectNameFromWorkspaceRoot, isPathSameOrInsideWorkspace, isSafeAutoCreateWorkspaceRoot, isValidProjectAgentId, listProjectGoalIds, listProjectSessionKeys, moveSessionToProject, normalizeProjectAgentId, resolveProjectAgentId, resolveProjectWorkspacePath, resolveWorkspaceProjectRoot, slugifyProjectName, unbindGoalFromProject, unbindSessionFromProject, workspaceDirectoryExists };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ProjectStore } from './project-store.js';
|
|
2
2
|
import type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';
|
|
3
|
+
import { type WorkspaceProjectMatch } from './workspace-project.js';
|
|
3
4
|
export type ProjectSuggestion = {
|
|
4
5
|
projectId: string;
|
|
5
6
|
projectName: string;
|
|
@@ -9,10 +10,23 @@ export type ProjectSuggestion = {
|
|
|
9
10
|
export declare class ProjectService {
|
|
10
11
|
private readonly store;
|
|
11
12
|
constructor(store?: ProjectStore);
|
|
13
|
+
private listAllProjects;
|
|
12
14
|
create(input: CreateProjectInput): Project;
|
|
13
15
|
get(id: string): Project | null;
|
|
14
16
|
getBySlug(slug: string): Project | null;
|
|
15
17
|
list(query?: ProjectListQuery): ProjectListResult;
|
|
18
|
+
findByWorkspaceRoot(workspaceRoot: string, options?: {
|
|
19
|
+
excludeProjectId?: string;
|
|
20
|
+
}): Project | null;
|
|
21
|
+
resolveForWorkspacePath(workspacePath: string): {
|
|
22
|
+
project: Project;
|
|
23
|
+
reason: 'exact' | 'contained';
|
|
24
|
+
} | null;
|
|
25
|
+
resolveOrCreateForWorkspacePath(input: {
|
|
26
|
+
workspacePath: string;
|
|
27
|
+
agentId?: string;
|
|
28
|
+
autoCreate?: boolean;
|
|
29
|
+
}): WorkspaceProjectMatch | null;
|
|
16
30
|
update(id: string, input: UpdateProjectInput): Project;
|
|
17
31
|
delete(id: string): void;
|
|
18
32
|
getWithDetails(id: string): ProjectWithDetails | null;
|
|
@@ -3,16 +3,44 @@ import "../storage/sqlite/index.js";
|
|
|
3
3
|
import { ProjectStore } from "./project-store.js";
|
|
4
4
|
import { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from "./goal-bind.js";
|
|
5
5
|
import { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from "./session-bind.js";
|
|
6
|
+
import { ProjectWorkspaceConflictError, ProjectWorkspaceMissingError, canonicalWorkspacePath, ensureWorkspaceDirectory, inferProjectNameFromWorkspaceRoot, isPathSameOrInsideWorkspace, isSafeAutoCreateWorkspaceRoot, resolveWorkspaceProjectRoot, workspaceDirectoryExists } from "./workspace-project.js";
|
|
6
7
|
//#region src/projects/project-service.ts
|
|
7
8
|
var ProjectService = class {
|
|
8
9
|
constructor(store = new ProjectStore()) {
|
|
9
10
|
this.store = store;
|
|
10
11
|
}
|
|
12
|
+
listAllProjects() {
|
|
13
|
+
const items = [];
|
|
14
|
+
let offset = 0;
|
|
15
|
+
const limit = 500;
|
|
16
|
+
while (true) {
|
|
17
|
+
const page = this.list({
|
|
18
|
+
limit,
|
|
19
|
+
offset
|
|
20
|
+
});
|
|
21
|
+
items.push(...page.items);
|
|
22
|
+
if (!page.hasMore || page.items.length === 0) break;
|
|
23
|
+
offset += page.items.length;
|
|
24
|
+
}
|
|
25
|
+
return items;
|
|
26
|
+
}
|
|
11
27
|
create(input) {
|
|
12
|
-
const
|
|
28
|
+
const rawWorkspaceRoot = input.workspaceRoot?.trim();
|
|
29
|
+
const workspaceRoot = rawWorkspaceRoot ? canonicalWorkspacePath(rawWorkspaceRoot) : void 0;
|
|
30
|
+
if (rawWorkspaceRoot && !workspaceRoot) throw new Error("Invalid workspace root");
|
|
31
|
+
if (workspaceRoot) {
|
|
32
|
+
const existing = this.findByWorkspaceRoot(workspaceRoot);
|
|
33
|
+
if (existing) throw new ProjectWorkspaceConflictError(existing);
|
|
34
|
+
if (!workspaceDirectoryExists(workspaceRoot)) if (input.createWorkspaceRoot === true) ensureWorkspaceDirectory(workspaceRoot);
|
|
35
|
+
else throw new ProjectWorkspaceMissingError(workspaceRoot);
|
|
36
|
+
}
|
|
37
|
+
const name = input.name?.trim() || inferProjectNameFromWorkspaceRoot(workspaceRoot) || "";
|
|
38
|
+
const slug = input.slug?.trim() || this.store.generateSlug(name);
|
|
13
39
|
return this.store.create({
|
|
14
40
|
...input,
|
|
15
|
-
|
|
41
|
+
name,
|
|
42
|
+
slug,
|
|
43
|
+
workspaceRoot
|
|
16
44
|
});
|
|
17
45
|
}
|
|
18
46
|
get(id) {
|
|
@@ -24,8 +52,73 @@ var ProjectService = class {
|
|
|
24
52
|
list(query) {
|
|
25
53
|
return this.store.list(query);
|
|
26
54
|
}
|
|
55
|
+
findByWorkspaceRoot(workspaceRoot, options = {}) {
|
|
56
|
+
const target = canonicalWorkspacePath(workspaceRoot);
|
|
57
|
+
if (!target) return null;
|
|
58
|
+
for (const project of this.listAllProjects()) {
|
|
59
|
+
if (options.excludeProjectId && project.id === options.excludeProjectId) continue;
|
|
60
|
+
const projectRoot = canonicalWorkspacePath(project.workspaceRoot);
|
|
61
|
+
if (projectRoot && isPathSameOrInsideWorkspace(projectRoot, target) && isPathSameOrInsideWorkspace(target, projectRoot)) return project;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
resolveForWorkspacePath(workspacePath) {
|
|
66
|
+
const target = canonicalWorkspacePath(workspacePath);
|
|
67
|
+
if (!target) return null;
|
|
68
|
+
let best = null;
|
|
69
|
+
for (const project of this.listAllProjects()) {
|
|
70
|
+
const root = canonicalWorkspacePath(project.workspaceRoot);
|
|
71
|
+
if (!root || !isPathSameOrInsideWorkspace(root, target)) continue;
|
|
72
|
+
const reason = isPathSameOrInsideWorkspace(root, target) && isPathSameOrInsideWorkspace(target, root) ? "exact" : "contained";
|
|
73
|
+
if (!best || root.length > best.root.length) best = {
|
|
74
|
+
project,
|
|
75
|
+
root,
|
|
76
|
+
reason
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return best ? {
|
|
80
|
+
project: best.project,
|
|
81
|
+
reason: best.reason
|
|
82
|
+
} : null;
|
|
83
|
+
}
|
|
84
|
+
resolveOrCreateForWorkspacePath(input) {
|
|
85
|
+
const existing = this.resolveForWorkspacePath(input.workspacePath);
|
|
86
|
+
if (existing) return {
|
|
87
|
+
project: existing.project,
|
|
88
|
+
reason: existing.reason,
|
|
89
|
+
created: false
|
|
90
|
+
};
|
|
91
|
+
if (!input.autoCreate) return null;
|
|
92
|
+
const workspaceRoot = resolveWorkspaceProjectRoot(input.workspacePath) ?? canonicalWorkspacePath(input.workspacePath);
|
|
93
|
+
if (!workspaceRoot || !isSafeAutoCreateWorkspaceRoot(workspaceRoot)) return null;
|
|
94
|
+
const rootExisting = this.resolveForWorkspacePath(workspaceRoot);
|
|
95
|
+
if (rootExisting) return {
|
|
96
|
+
project: rootExisting.project,
|
|
97
|
+
reason: rootExisting.reason,
|
|
98
|
+
created: false
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
project: this.create({
|
|
102
|
+
name: inferProjectNameFromWorkspaceRoot(workspaceRoot) ?? void 0,
|
|
103
|
+
workspaceRoot,
|
|
104
|
+
defaultAgentId: input.agentId
|
|
105
|
+
}),
|
|
106
|
+
reason: "auto_created",
|
|
107
|
+
created: true
|
|
108
|
+
};
|
|
109
|
+
}
|
|
27
110
|
update(id, input) {
|
|
28
|
-
|
|
111
|
+
const patch = { ...input };
|
|
112
|
+
if (input.workspaceRoot !== void 0 && input.workspaceRoot !== null && input.workspaceRoot.trim()) {
|
|
113
|
+
const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);
|
|
114
|
+
if (!workspaceRoot) throw new Error("Invalid workspace root");
|
|
115
|
+
const existing = this.findByWorkspaceRoot(workspaceRoot, { excludeProjectId: id });
|
|
116
|
+
if (existing) throw new ProjectWorkspaceConflictError(existing);
|
|
117
|
+
if (!workspaceDirectoryExists(workspaceRoot)) if (input.createWorkspaceRoot === true) ensureWorkspaceDirectory(workspaceRoot);
|
|
118
|
+
else throw new ProjectWorkspaceMissingError(workspaceRoot);
|
|
119
|
+
patch.workspaceRoot = workspaceRoot;
|
|
120
|
+
}
|
|
121
|
+
return this.store.update(id, patch);
|
|
29
122
|
}
|
|
30
123
|
delete(id) {
|
|
31
124
|
this.store.delete(id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-service.js","names":[],"sources":["../../../src/projects/project-service.ts"],"sourcesContent":["import { getSessionMetadata } from '../storage/sqlite/index.js';\nimport { ProjectStore } from './project-store.js';\nimport { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from './goal-bind.js';\nimport { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from './session-bind.js';\nimport type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';\n\nexport type ProjectSuggestion = {\n projectId: string;\n projectName: string;\n score: number;\n reason: string;\n};\n\nexport class ProjectService {\n constructor(private readonly store = new ProjectStore()) {}\n\n create(input: CreateProjectInput): Project {\n const slug = input.slug?.trim() || this.store.generateSlug(input.name);\n return this.store.create({ ...input, slug });\n }\n\n get(id: string): Project | null {\n return this.store.get(id);\n }\n\n getBySlug(slug: string): Project | null {\n return this.store.findBySlug(slug);\n }\n\n list(query?: ProjectListQuery): ProjectListResult {\n return this.store.list(query);\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n return this.store.update(id, input);\n }\n\n delete(id: string): void {\n this.store.delete(id);\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n return this.store.getWithDetails(id);\n }\n\n attachSession(sessionKey: string, projectId: string): void {\n bindSessionToProject(sessionKey, projectId);\n }\n\n detachSession(sessionKey: string): void {\n unbindSessionFromProject(sessionKey);\n }\n\n listSessionKeys(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectSessionKeys(projectId, limit, offset);\n }\n\n attachGoal(goalId: string, projectId: string): void {\n bindGoalToProject(goalId, projectId);\n }\n\n detachGoal(goalId: string): void {\n unbindGoalFromProject(goalId);\n }\n\n listGoalIds(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectGoalIds(projectId, limit, offset);\n }\n\n suggestProjectsForSession(sessionKey: string): ProjectSuggestion[] {\n const session = getSessionMetadata(sessionKey);\n if (!session) return [];\n const projects = this.list({ status: 'active', limit: 500 }).items;\n const haystack = [session.key, session.name, session.routing?.agentId, session.sourceChannel, ...(session.tags ?? [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n const suggestions: ProjectSuggestion[] = [];\n for (const project of projects) {\n let score = 0;\n const reasons: string[] = [];\n if (haystack.includes(project.slug.toLowerCase())) {\n score += 10;\n reasons.push('session metadata matches project slug');\n }\n for (const token of project.name.toLowerCase().split(/\\s+/).filter((t) => t.length >= 3)) {\n if (haystack.includes(token)) {\n score += 3;\n reasons.push(`matches ${token}`);\n break;\n }\n }\n if (score > 0) {\n suggestions.push({ projectId: project.id, projectName: project.name, score, reason: reasons.join(', ') });\n }\n }\n return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);\n }\n}\n"],"mappings":";;;;;;AAaA,IAAa,iBAAb,MAA4B;CAC1B,YAAY,QAAyB,IAAI,cAAc,EAAE;AAA5B,OAAA,QAAA;;CAE7B,OAAO,OAAoC;EACzC,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,KAAK,MAAM,aAAa,MAAM,KAAK;AACtE,SAAO,KAAK,MAAM,OAAO;GAAE,GAAG;GAAO;GAAM,CAAC;;CAG9C,IAAI,IAA4B;AAC9B,SAAO,KAAK,MAAM,IAAI,GAAG;;CAG3B,UAAU,MAA8B;AACtC,SAAO,KAAK,MAAM,WAAW,KAAK;;CAGpC,KAAK,OAA6C;AAChD,SAAO,KAAK,MAAM,KAAK,MAAM;;CAG/B,OAAO,IAAY,OAAoC;AACrD,SAAO,KAAK,MAAM,OAAO,IAAI,MAAM;;CAGrC,OAAO,IAAkB;AACvB,OAAK,MAAM,OAAO,GAAG;;CAGvB,eAAe,IAAuC;AACpD,SAAO,KAAK,MAAM,eAAe,GAAG;;CAGtC,cAAc,YAAoB,WAAyB;AACzD,uBAAqB,YAAY,UAAU;;CAG7C,cAAc,YAA0B;AACtC,2BAAyB,WAAW;;CAGtC,gBAAgB,WAAmB,OAAgB,QAA2B;AAC5E,SAAO,uBAAuB,WAAW,OAAO,OAAO;;CAGzD,WAAW,QAAgB,WAAyB;AAClD,oBAAkB,QAAQ,UAAU;;CAGtC,WAAW,QAAsB;AAC/B,wBAAsB,OAAO;;CAG/B,YAAY,WAAmB,OAAgB,QAA2B;AACxE,SAAO,mBAAmB,WAAW,OAAO,OAAO;;CAGrD,0BAA0B,YAAyC;EACjE,MAAM,UAAU,mBAAmB,WAAW;AAC9C,MAAI,CAAC,QAAS,QAAO,EAAE;EACvB,MAAM,WAAW,KAAK,KAAK;GAAE,QAAQ;GAAU,OAAO;GAAK,CAAC,CAAC;EAC7D,MAAM,WAAW;GAAC,QAAQ;GAAK,QAAQ;GAAM,QAAQ,SAAS;GAAS,QAAQ;GAAe,GAAI,QAAQ,QAAQ,EAAE;GAAE,CACnH,OAAO,QAAQ,CACf,KAAK,IAAI,CACT,aAAa;EAChB,MAAM,cAAmC,EAAE;AAC3C,OAAK,MAAM,WAAW,UAAU;GAC9B,IAAI,QAAQ;GACZ,MAAM,UAAoB,EAAE;AAC5B,OAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,CAAC,EAAE;AACjD,aAAS;AACT,YAAQ,KAAK,wCAAwC;;AAEvD,QAAK,MAAM,SAAS,QAAQ,KAAK,aAAa,CAAC,MAAM,MAAM,CAAC,QAAQ,MAAM,EAAE,UAAU,EAAE,CACtF,KAAI,SAAS,SAAS,MAAM,EAAE;AAC5B,aAAS;AACT,YAAQ,KAAK,WAAW,QAAQ;AAChC;;AAGJ,OAAI,QAAQ,EACV,aAAY,KAAK;IAAE,WAAW,QAAQ;IAAI,aAAa,QAAQ;IAAM;IAAO,QAAQ,QAAQ,KAAK,KAAK;IAAE,CAAC;;AAG7G,SAAO,YAAY,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG,EAAE"}
|
|
1
|
+
{"version":3,"file":"project-service.js","names":[],"sources":["../../../src/projects/project-service.ts"],"sourcesContent":["import { getSessionMetadata } from '../storage/sqlite/index.js';\nimport { ProjectStore } from './project-store.js';\nimport { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from './goal-bind.js';\nimport { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from './session-bind.js';\nimport type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';\nimport {\n canonicalWorkspacePath,\n ensureWorkspaceDirectory,\n inferProjectNameFromWorkspaceRoot,\n isPathSameOrInsideWorkspace,\n isSafeAutoCreateWorkspaceRoot,\n ProjectWorkspaceConflictError,\n ProjectWorkspaceMissingError,\n resolveWorkspaceProjectRoot,\n workspaceDirectoryExists,\n type WorkspaceProjectMatch,\n} from './workspace-project.js';\n\nexport type ProjectSuggestion = {\n projectId: string;\n projectName: string;\n score: number;\n reason: string;\n};\n\nexport class ProjectService {\n constructor(private readonly store = new ProjectStore()) {}\n\n private listAllProjects(): Project[] {\n const items: Project[] = [];\n let offset = 0;\n const limit = 500;\n while (true) {\n const page = this.list({ limit, offset });\n items.push(...page.items);\n if (!page.hasMore || page.items.length === 0) break;\n offset += page.items.length;\n }\n return items;\n }\n\n create(input: CreateProjectInput): Project {\n const rawWorkspaceRoot = input.workspaceRoot?.trim();\n const workspaceRoot = rawWorkspaceRoot ? canonicalWorkspacePath(rawWorkspaceRoot) : undefined;\n if (rawWorkspaceRoot && !workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n if (workspaceRoot) {\n const existing = this.findByWorkspaceRoot(workspaceRoot);\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n }\n const name = input.name?.trim() || inferProjectNameFromWorkspaceRoot(workspaceRoot) || '';\n const slug = input.slug?.trim() || this.store.generateSlug(name);\n return this.store.create({ ...input, name, slug, workspaceRoot });\n }\n\n get(id: string): Project | null {\n return this.store.get(id);\n }\n\n getBySlug(slug: string): Project | null {\n return this.store.findBySlug(slug);\n }\n\n list(query?: ProjectListQuery): ProjectListResult {\n return this.store.list(query);\n }\n\n findByWorkspaceRoot(workspaceRoot: string, options: { excludeProjectId?: string } = {}): Project | null {\n const target = canonicalWorkspacePath(workspaceRoot);\n if (!target) return null;\n for (const project of this.listAllProjects()) {\n if (options.excludeProjectId && project.id === options.excludeProjectId) continue;\n const projectRoot = canonicalWorkspacePath(project.workspaceRoot);\n if (projectRoot && isPathSameOrInsideWorkspace(projectRoot, target) && isPathSameOrInsideWorkspace(target, projectRoot)) {\n return project;\n }\n }\n return null;\n }\n\n resolveForWorkspacePath(workspacePath: string): { project: Project; reason: 'exact' | 'contained' } | null {\n const target = canonicalWorkspacePath(workspacePath);\n if (!target) return null;\n let best: { project: Project; root: string; reason: 'exact' | 'contained' } | null = null;\n for (const project of this.listAllProjects()) {\n const root = canonicalWorkspacePath(project.workspaceRoot);\n if (!root || !isPathSameOrInsideWorkspace(root, target)) continue;\n const reason = isPathSameOrInsideWorkspace(root, target) && isPathSameOrInsideWorkspace(target, root) ? 'exact' : 'contained';\n if (!best || root.length > best.root.length) {\n best = { project, root, reason };\n }\n }\n return best ? { project: best.project, reason: best.reason } : null;\n }\n\n resolveOrCreateForWorkspacePath(input: {\n workspacePath: string;\n agentId?: string;\n autoCreate?: boolean;\n }): WorkspaceProjectMatch | null {\n const existing = this.resolveForWorkspacePath(input.workspacePath);\n if (existing) return { project: existing.project, reason: existing.reason, created: false };\n if (!input.autoCreate) return null;\n const workspaceRoot = resolveWorkspaceProjectRoot(input.workspacePath) ?? canonicalWorkspacePath(input.workspacePath);\n if (!workspaceRoot || !isSafeAutoCreateWorkspaceRoot(workspaceRoot)) return null;\n const rootExisting = this.resolveForWorkspacePath(workspaceRoot);\n if (rootExisting) return { project: rootExisting.project, reason: rootExisting.reason, created: false };\n const project = this.create({\n name: inferProjectNameFromWorkspaceRoot(workspaceRoot) ?? undefined,\n workspaceRoot,\n defaultAgentId: input.agentId,\n });\n return { project, reason: 'auto_created', created: true };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const patch = { ...input };\n if (input.workspaceRoot !== undefined && input.workspaceRoot !== null && input.workspaceRoot.trim()) {\n const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);\n if (!workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n const existing = this.findByWorkspaceRoot(workspaceRoot, { excludeProjectId: id });\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n patch.workspaceRoot = workspaceRoot;\n }\n return this.store.update(id, patch);\n }\n\n delete(id: string): void {\n this.store.delete(id);\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n return this.store.getWithDetails(id);\n }\n\n attachSession(sessionKey: string, projectId: string): void {\n bindSessionToProject(sessionKey, projectId);\n }\n\n detachSession(sessionKey: string): void {\n unbindSessionFromProject(sessionKey);\n }\n\n listSessionKeys(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectSessionKeys(projectId, limit, offset);\n }\n\n attachGoal(goalId: string, projectId: string): void {\n bindGoalToProject(goalId, projectId);\n }\n\n detachGoal(goalId: string): void {\n unbindGoalFromProject(goalId);\n }\n\n listGoalIds(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectGoalIds(projectId, limit, offset);\n }\n\n suggestProjectsForSession(sessionKey: string): ProjectSuggestion[] {\n const session = getSessionMetadata(sessionKey);\n if (!session) return [];\n const projects = this.list({ status: 'active', limit: 500 }).items;\n const haystack = [session.key, session.name, session.routing?.agentId, session.sourceChannel, ...(session.tags ?? [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n const suggestions: ProjectSuggestion[] = [];\n for (const project of projects) {\n let score = 0;\n const reasons: string[] = [];\n if (haystack.includes(project.slug.toLowerCase())) {\n score += 10;\n reasons.push('session metadata matches project slug');\n }\n for (const token of project.name.toLowerCase().split(/\\s+/).filter((t) => t.length >= 3)) {\n if (haystack.includes(token)) {\n score += 3;\n reasons.push(`matches ${token}`);\n break;\n }\n }\n if (score > 0) {\n suggestions.push({ projectId: project.id, projectName: project.name, score, reason: reasons.join(', ') });\n }\n }\n return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);\n }\n}\n"],"mappings":"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"}
|
|
@@ -40,11 +40,12 @@ export interface ProjectListQuery {
|
|
|
40
40
|
offset?: number;
|
|
41
41
|
}
|
|
42
42
|
export interface CreateProjectInput {
|
|
43
|
-
name
|
|
43
|
+
name?: string;
|
|
44
44
|
slug?: string;
|
|
45
45
|
description?: string;
|
|
46
46
|
defaultAgentId?: string;
|
|
47
47
|
workspaceRoot?: string;
|
|
48
|
+
createWorkspaceRoot?: boolean;
|
|
48
49
|
brief?: string;
|
|
49
50
|
instructions?: string;
|
|
50
51
|
}
|
|
@@ -54,6 +55,7 @@ export interface UpdateProjectInput {
|
|
|
54
55
|
defaultAgentId?: string | null;
|
|
55
56
|
status?: ProjectStatus;
|
|
56
57
|
workspaceRoot?: string | null;
|
|
58
|
+
createWorkspaceRoot?: boolean;
|
|
57
59
|
brief?: string | null;
|
|
58
60
|
instructions?: string | null;
|
|
59
61
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Project } from './types.js';
|
|
2
|
+
export type WorkspaceProjectMatchReason = 'exact' | 'contained' | 'auto_created';
|
|
3
|
+
export type WorkspaceProjectMatch = {
|
|
4
|
+
project: Project;
|
|
5
|
+
reason: WorkspaceProjectMatchReason;
|
|
6
|
+
created: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare class ProjectWorkspaceConflictError extends Error {
|
|
9
|
+
readonly project: Project;
|
|
10
|
+
constructor(project: Project, message?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class ProjectWorkspaceMissingError extends Error {
|
|
13
|
+
readonly workspaceRoot: string;
|
|
14
|
+
constructor(workspaceRoot: string, message?: string);
|
|
15
|
+
}
|
|
16
|
+
export declare function canonicalWorkspacePath(raw: string | null | undefined): string | null;
|
|
17
|
+
export declare function workspaceDirectoryExists(workspaceRoot: string | null | undefined): boolean;
|
|
18
|
+
export declare function ensureWorkspaceDirectory(workspaceRoot: string): void;
|
|
19
|
+
export declare function resolveWorkspaceProjectRoot(raw: string | null | undefined): string | null;
|
|
20
|
+
export declare function isPathSameOrInsideWorkspace(workspaceRoot: string, candidatePath: string): boolean;
|
|
21
|
+
export declare function inferProjectNameFromWorkspaceRoot(workspaceRoot: string | null | undefined): string | null;
|
|
22
|
+
export declare function isSafeAutoCreateWorkspaceRoot(workspaceRoot: string | null | undefined): boolean;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { init_paths_state, resolveStateDir } from "../config/paths-state.js";
|
|
2
|
+
import { init_workspace_path, normalizeWorkspaceDir } from "../config/workspace-path.js";
|
|
3
|
+
import { init_agent_scope, resolveUserPath } from "../agent/agent-scope.js";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
6
|
+
import { existsSync, mkdirSync, realpathSync, statSync } from "node:fs";
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
//#region src/projects/workspace-project.ts
|
|
9
|
+
init_agent_scope();
|
|
10
|
+
init_workspace_path();
|
|
11
|
+
init_paths_state();
|
|
12
|
+
var ProjectWorkspaceConflictError = class extends Error {
|
|
13
|
+
constructor(project, message = `Workspace is already bound to project: ${project.name}`) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.project = project;
|
|
16
|
+
this.name = "ProjectWorkspaceConflictError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var ProjectWorkspaceMissingError = class extends Error {
|
|
20
|
+
constructor(workspaceRoot, message = `Workspace root does not exist: ${workspaceRoot}`) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.workspaceRoot = workspaceRoot;
|
|
23
|
+
this.name = "ProjectWorkspaceMissingError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const PROJECT_ROOT_MARKERS = [
|
|
27
|
+
".git",
|
|
28
|
+
"pnpm-workspace.yaml",
|
|
29
|
+
"package.json",
|
|
30
|
+
"go.mod",
|
|
31
|
+
"pyproject.toml",
|
|
32
|
+
"Cargo.toml",
|
|
33
|
+
"deno.json",
|
|
34
|
+
"deno.jsonc",
|
|
35
|
+
"bun.lockb",
|
|
36
|
+
"pnpm-lock.yaml",
|
|
37
|
+
"yarn.lock",
|
|
38
|
+
"package-lock.json",
|
|
39
|
+
"composer.json",
|
|
40
|
+
"Gemfile"
|
|
41
|
+
];
|
|
42
|
+
function canonicalWorkspacePath(raw) {
|
|
43
|
+
const trimmed = raw?.trim();
|
|
44
|
+
if (!trimmed) return null;
|
|
45
|
+
const normalized = normalizeWorkspaceDir(resolveUserPath(trimmed));
|
|
46
|
+
if (!normalized) return null;
|
|
47
|
+
try {
|
|
48
|
+
if (!statSync(normalized).isDirectory()) return normalized;
|
|
49
|
+
return realpathSync.native(normalized);
|
|
50
|
+
} catch {
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function workspaceDirectoryExists(workspaceRoot) {
|
|
55
|
+
const canonical = canonicalWorkspacePath(workspaceRoot);
|
|
56
|
+
if (!canonical) return false;
|
|
57
|
+
try {
|
|
58
|
+
return statSync(canonical).isDirectory();
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function ensureWorkspaceDirectory(workspaceRoot) {
|
|
64
|
+
mkdirSync(workspaceRoot, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
function directoryForWorkspaceProbe(pathValue) {
|
|
67
|
+
try {
|
|
68
|
+
return statSync(pathValue).isDirectory() ? pathValue : dirname(pathValue);
|
|
69
|
+
} catch {
|
|
70
|
+
return pathValue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function findGitRoot(startDir) {
|
|
74
|
+
try {
|
|
75
|
+
return canonicalWorkspacePath(execFileSync("git", [
|
|
76
|
+
"-C",
|
|
77
|
+
startDir,
|
|
78
|
+
"rev-parse",
|
|
79
|
+
"--show-toplevel"
|
|
80
|
+
], {
|
|
81
|
+
encoding: "utf8",
|
|
82
|
+
stdio: [
|
|
83
|
+
"ignore",
|
|
84
|
+
"pipe",
|
|
85
|
+
"ignore"
|
|
86
|
+
],
|
|
87
|
+
timeout: 1e3
|
|
88
|
+
}).trim());
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function findMarkerRoot(startDir) {
|
|
94
|
+
let current = startDir;
|
|
95
|
+
while (true) {
|
|
96
|
+
if (PROJECT_ROOT_MARKERS.some((marker) => existsSync(join(current, marker)))) return current;
|
|
97
|
+
const parent = dirname(current);
|
|
98
|
+
if (parent === current) return null;
|
|
99
|
+
current = parent;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function resolveWorkspaceProjectRoot(raw) {
|
|
103
|
+
const canonical = canonicalWorkspacePath(raw);
|
|
104
|
+
if (!canonical) return null;
|
|
105
|
+
const startDir = directoryForWorkspaceProbe(canonical);
|
|
106
|
+
const gitRoot = findGitRoot(startDir);
|
|
107
|
+
if (gitRoot) return gitRoot;
|
|
108
|
+
return canonicalWorkspacePath(findMarkerRoot(startDir)) ?? canonical;
|
|
109
|
+
}
|
|
110
|
+
function normalizeComparablePath(pathValue) {
|
|
111
|
+
const resolved = resolve(pathValue);
|
|
112
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
113
|
+
}
|
|
114
|
+
function isPathSameOrInsideWorkspace(workspaceRoot, candidatePath) {
|
|
115
|
+
const rel = relative(normalizeComparablePath(workspaceRoot), normalizeComparablePath(candidatePath));
|
|
116
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !isAbsolute(rel);
|
|
117
|
+
}
|
|
118
|
+
function inferProjectNameFromWorkspaceRoot(workspaceRoot) {
|
|
119
|
+
const canonical = canonicalWorkspacePath(workspaceRoot);
|
|
120
|
+
if (!canonical) return null;
|
|
121
|
+
return basename(canonical).trim() || null;
|
|
122
|
+
}
|
|
123
|
+
function isSafeAutoCreateWorkspaceRoot(workspaceRoot) {
|
|
124
|
+
const canonical = canonicalWorkspacePath(workspaceRoot);
|
|
125
|
+
if (!canonical) return false;
|
|
126
|
+
const normalized = normalizeComparablePath(canonical);
|
|
127
|
+
if ([
|
|
128
|
+
homedir(),
|
|
129
|
+
tmpdir(),
|
|
130
|
+
resolveStateDir()
|
|
131
|
+
].map((p) => canonicalWorkspacePath(p)).filter((p) => Boolean(p)).map(normalizeComparablePath).includes(normalized)) return false;
|
|
132
|
+
if (normalizeComparablePath(canonical) === normalizeComparablePath(parse(canonical).root)) return false;
|
|
133
|
+
if (!basename(canonical).trim()) return false;
|
|
134
|
+
try {
|
|
135
|
+
return statSync(canonical).isDirectory();
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { ProjectWorkspaceConflictError, ProjectWorkspaceMissingError, canonicalWorkspacePath, ensureWorkspaceDirectory, inferProjectNameFromWorkspaceRoot, isPathSameOrInsideWorkspace, isSafeAutoCreateWorkspaceRoot, resolveWorkspaceProjectRoot, workspaceDirectoryExists };
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=workspace-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-project.js","names":[],"sources":["../../../src/projects/workspace-project.ts"],"sourcesContent":["import { existsSync, mkdirSync, realpathSync, statSync } from 'node:fs';\nimport { execFileSync } from 'node:child_process';\nimport { homedir, tmpdir } from 'node:os';\nimport { basename, dirname, isAbsolute, join, parse, relative, resolve } from 'node:path';\n\nimport { resolveUserPath } from '../agent/agent-scope.js';\nimport { normalizeWorkspaceDir } from '../config/workspace-path.js';\nimport { resolveStateDir } from '../config/paths-state.js';\nimport type { Project } from './types.js';\n\nexport type WorkspaceProjectMatchReason = 'exact' | 'contained' | 'auto_created';\n\nexport type WorkspaceProjectMatch = {\n project: Project;\n reason: WorkspaceProjectMatchReason;\n created: boolean;\n};\n\nexport class ProjectWorkspaceConflictError extends Error {\n constructor(readonly project: Project, message = `Workspace is already bound to project: ${project.name}`) {\n super(message);\n this.name = 'ProjectWorkspaceConflictError';\n }\n}\n\nexport class ProjectWorkspaceMissingError extends Error {\n constructor(readonly workspaceRoot: string, message = `Workspace root does not exist: ${workspaceRoot}`) {\n super(message);\n this.name = 'ProjectWorkspaceMissingError';\n }\n}\n\nconst PROJECT_ROOT_MARKERS = [\n '.git',\n 'pnpm-workspace.yaml',\n 'package.json',\n 'go.mod',\n 'pyproject.toml',\n 'Cargo.toml',\n 'deno.json',\n 'deno.jsonc',\n 'bun.lockb',\n 'pnpm-lock.yaml',\n 'yarn.lock',\n 'package-lock.json',\n 'composer.json',\n 'Gemfile',\n] as const;\n\nexport function canonicalWorkspacePath(raw: string | null | undefined): string | null {\n const trimmed = raw?.trim();\n if (!trimmed) return null;\n const resolved = resolveUserPath(trimmed);\n const normalized = normalizeWorkspaceDir(resolved);\n if (!normalized) return null;\n try {\n const stat = statSync(normalized);\n if (!stat.isDirectory()) return normalized;\n return realpathSync.native(normalized);\n } catch {\n return normalized;\n }\n}\n\nexport function workspaceDirectoryExists(workspaceRoot: string | null | undefined): boolean {\n const canonical = canonicalWorkspacePath(workspaceRoot);\n if (!canonical) return false;\n try {\n return statSync(canonical).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function ensureWorkspaceDirectory(workspaceRoot: string): void {\n mkdirSync(workspaceRoot, { recursive: true });\n}\n\nfunction directoryForWorkspaceProbe(pathValue: string): string {\n try {\n return statSync(pathValue).isDirectory() ? pathValue : dirname(pathValue);\n } catch {\n return pathValue;\n }\n}\n\nfunction findGitRoot(startDir: string): string | null {\n try {\n const output = execFileSync('git', ['-C', startDir, 'rev-parse', '--show-toplevel'], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 1000,\n }).trim();\n return canonicalWorkspacePath(output);\n } catch {\n return null;\n }\n}\n\nfunction findMarkerRoot(startDir: string): string | null {\n let current = startDir;\n while (true) {\n if (PROJECT_ROOT_MARKERS.some((marker) => existsSync(join(current, marker)))) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n return null;\n }\n current = parent;\n }\n}\n\nexport function resolveWorkspaceProjectRoot(raw: string | null | undefined): string | null {\n const canonical = canonicalWorkspacePath(raw);\n if (!canonical) return null;\n const startDir = directoryForWorkspaceProbe(canonical);\n const gitRoot = findGitRoot(startDir);\n if (gitRoot) return gitRoot;\n const markerRoot = findMarkerRoot(startDir);\n return canonicalWorkspacePath(markerRoot) ?? canonical;\n}\n\nfunction normalizeComparablePath(pathValue: string): string {\n const resolved = resolve(pathValue);\n return process.platform === 'win32' ? resolved.toLowerCase() : resolved;\n}\n\nexport function isPathSameOrInsideWorkspace(workspaceRoot: string, candidatePath: string): boolean {\n const root = normalizeComparablePath(workspaceRoot);\n const candidate = normalizeComparablePath(candidatePath);\n const rel = relative(root, candidate);\n return rel === '' || (!!rel && !rel.startsWith('..') && !isAbsolute(rel));\n}\n\nexport function inferProjectNameFromWorkspaceRoot(workspaceRoot: string | null | undefined): string | null {\n const canonical = canonicalWorkspacePath(workspaceRoot);\n if (!canonical) return null;\n const name = basename(canonical).trim();\n return name || null;\n}\n\nexport function isSafeAutoCreateWorkspaceRoot(workspaceRoot: string | null | undefined): boolean {\n const canonical = canonicalWorkspacePath(workspaceRoot);\n if (!canonical) return false;\n const normalized = normalizeComparablePath(canonical);\n const forbidden = [homedir(), tmpdir(), resolveStateDir()]\n .map((p) => canonicalWorkspacePath(p))\n .filter((p): p is string => Boolean(p))\n .map(normalizeComparablePath);\n if (forbidden.includes(normalized)) return false;\n // `normalizeWorkspaceDir` already rejects filesystem roots; keep this function\n // conservative for callers that pass unusual platform-specific roots.\n if (normalizeComparablePath(canonical) === normalizeComparablePath(parse(canonical).root)) return false;\n if (!basename(canonical).trim()) return false;\n try {\n return statSync(canonical).isDirectory();\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;kBAK0D;qBACU;kBACT;AAW3D,IAAa,gCAAb,cAAmD,MAAM;CACvD,YAAY,SAA2B,UAAU,0CAA0C,QAAQ,QAAQ;AACzG,QAAM,QAAQ;AADK,OAAA,UAAA;AAEnB,OAAK,OAAO;;;AAIhB,IAAa,+BAAb,cAAkD,MAAM;CACtD,YAAY,eAAgC,UAAU,kCAAkC,iBAAiB;AACvG,QAAM,QAAQ;AADK,OAAA,gBAAA;AAEnB,OAAK,OAAO;;;AAIhB,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAgB,uBAAuB,KAA+C;CACpF,MAAM,UAAU,KAAK,MAAM;AAC3B,KAAI,CAAC,QAAS,QAAO;CAErB,MAAM,aAAa,sBADF,gBAAgB,QACgB,CAAC;AAClD,KAAI,CAAC,WAAY,QAAO;AACxB,KAAI;AAEF,MAAI,CADS,SAAS,WACb,CAAC,aAAa,CAAE,QAAO;AAChC,SAAO,aAAa,OAAO,WAAW;SAChC;AACN,SAAO;;;AAIX,SAAgB,yBAAyB,eAAmD;CAC1F,MAAM,YAAY,uBAAuB,cAAc;AACvD,KAAI,CAAC,UAAW,QAAO;AACvB,KAAI;AACF,SAAO,SAAS,UAAU,CAAC,aAAa;SAClC;AACN,SAAO;;;AAIX,SAAgB,yBAAyB,eAA6B;AACpE,WAAU,eAAe,EAAE,WAAW,MAAM,CAAC;;AAG/C,SAAS,2BAA2B,WAA2B;AAC7D,KAAI;AACF,SAAO,SAAS,UAAU,CAAC,aAAa,GAAG,YAAY,QAAQ,UAAU;SACnE;AACN,SAAO;;;AAIX,SAAS,YAAY,UAAiC;AACpD,KAAI;AAMF,SAAO,uBALQ,aAAa,OAAO;GAAC;GAAM;GAAU;GAAa;GAAkB,EAAE;GACnF,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CAAC,CAAC,MACiC,CAAC;SAC/B;AACN,SAAO;;;AAIX,SAAS,eAAe,UAAiC;CACvD,IAAI,UAAU;AACd,QAAO,MAAM;AACX,MAAI,qBAAqB,MAAM,WAAW,WAAW,KAAK,SAAS,OAAO,CAAC,CAAC,CAC1E,QAAO;EAET,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QACb,QAAO;AAET,YAAU;;;AAId,SAAgB,4BAA4B,KAA+C;CACzF,MAAM,YAAY,uBAAuB,IAAI;AAC7C,KAAI,CAAC,UAAW,QAAO;CACvB,MAAM,WAAW,2BAA2B,UAAU;CACtD,MAAM,UAAU,YAAY,SAAS;AACrC,KAAI,QAAS,QAAO;AAEpB,QAAO,uBADY,eAAe,SACM,CAAC,IAAI;;AAG/C,SAAS,wBAAwB,WAA2B;CAC1D,MAAM,WAAW,QAAQ,UAAU;AACnC,QAAO,QAAQ,aAAa,UAAU,SAAS,aAAa,GAAG;;AAGjE,SAAgB,4BAA4B,eAAuB,eAAgC;CAGjG,MAAM,MAAM,SAFC,wBAAwB,cAEZ,EADP,wBAAwB,cACN,CAAC;AACrC,QAAO,QAAQ,MAAO,CAAC,CAAC,OAAO,CAAC,IAAI,WAAW,KAAK,IAAI,CAAC,WAAW,IAAI;;AAG1E,SAAgB,kCAAkC,eAAyD;CACzG,MAAM,YAAY,uBAAuB,cAAc;AACvD,KAAI,CAAC,UAAW,QAAO;AAEvB,QADa,SAAS,UAAU,CAAC,MACtB,IAAI;;AAGjB,SAAgB,8BAA8B,eAAmD;CAC/F,MAAM,YAAY,uBAAuB,cAAc;AACvD,KAAI,CAAC,UAAW,QAAO;CACvB,MAAM,aAAa,wBAAwB,UAAU;AAKrD,KAJkB;EAAC,SAAS;EAAE,QAAQ;EAAE,iBAAiB;EAAC,CACvD,KAAK,MAAM,uBAAuB,EAAE,CAAC,CACrC,QAAQ,MAAmB,QAAQ,EAAE,CAAC,CACtC,IAAI,wBACM,CAAC,SAAS,WAAW,CAAE,QAAO;AAG3C,KAAI,wBAAwB,UAAU,KAAK,wBAAwB,MAAM,UAAU,CAAC,KAAK,CAAE,QAAO;AAClG,KAAI,CAAC,SAAS,UAAU,CAAC,MAAM,CAAE,QAAO;AACxC,KAAI;AACF,SAAO,SAAS,UAAU,CAAC,aAAa;SAClC;AACN,SAAO"}
|