diff --git a/.claude/skills/team-coordinate-v2/roles/coordinator/commands/analyze-task.md b/.claude/skills/team-coordinate-v2/roles/coordinator/commands/analyze-task.md index 5ab19baf..3ab6c4b3 100644 --- a/.claude/skills/team-coordinate-v2/roles/coordinator/commands/analyze-task.md +++ b/.claude/skills/team-coordinate-v2/roles/coordinator/commands/analyze-task.md @@ -4,6 +4,18 @@ Parse user task description -> detect required capabilities -> build dependency graph -> design dynamic roles with role-spec metadata. Outputs structured task-analysis.json with frontmatter fields for role-spec generation. +## CRITICAL CONSTRAINT + +**TEXT-LEVEL analysis only. MUST NOT read source code or explore codebase.** + +**Allowed:** +- Parse user task description text +- AskUserQuestion for clarification +- Keyword-to-capability mapping +- Write `task-analysis.json` + +If task context requires codebase knowledge, set `needs_research: true`. Phase 2 will spawn researcher worker. + ## Phase 2: Context Loading | Input | Source | Required | @@ -135,6 +147,7 @@ Write `/task-analysis.json`: "total_score": 3, "level": "low" }, + "needs_research": false, "artifacts": [ { "name": "research-findings.md", "producer": "researcher", "path": "artifacts/research-findings.md" } ] diff --git a/.claude/skills/team-coordinate-v2/roles/coordinator/role.md b/.claude/skills/team-coordinate-v2/roles/coordinator/role.md index a449db34..47982b7c 100644 --- a/.claude/skills/team-coordinate-v2/roles/coordinator/role.md +++ b/.claude/skills/team-coordinate-v2/roles/coordinator/role.md @@ -10,7 +10,7 @@ Orchestrate the team-coordinate workflow: task analysis, dynamic role-spec gener ## Boundaries ### MUST -- Analyze user task to detect capabilities and build dependency graph +- Parse task description (text-level: keyword scanning, capability inference, dependency design) - Dynamically generate worker role-specs from specs/role-spec-template.md - Create team and spawn team-worker agents in background - Dispatch tasks with proper dependency chains from task-analysis.json @@ -22,6 +22,7 @@ Orchestrate the team-coordinate workflow: task analysis, dynamic role-spec gener - Execute completion action when pipeline finishes ### MUST NOT +- **Read source code or perform codebase exploration** (delegate to worker roles) - Execute task work directly (delegate to workers) - Modify task output artifacts (workers own their deliverables) - Call implementation subagents (code-developer, etc.) directly @@ -30,8 +31,6 @@ Orchestrate the team-coordinate workflow: task analysis, dynamic role-spec gener - Override consensus_blocked HIGH without user confirmation - Spawn workers with `general-purpose` agent (MUST use `team-worker`) -> **Core principle**: coordinator is the orchestrator, not the executor. All actual work is delegated to dynamically generated worker roles via team-worker agents. - --- ## Entry Router @@ -79,6 +78,8 @@ For callback/check/resume/adapt/complete: load `commands/monitor.md` and execute **Objective**: Parse user task, detect capabilities, build dependency graph, design roles. +**Constraint**: This is TEXT-LEVEL analysis only. No source code reading, no codebase exploration. + **Workflow**: 1. **Parse user task description** @@ -98,6 +99,8 @@ For callback/check/resume/adapt/complete: load `commands/monitor.md` and execute 4. **Output**: Write `/task-analysis.json` +5. **If `needs_research: true`**: Phase 2 will spawn researcher worker first + **Success**: Task analyzed, capabilities detected, dependency graph built, roles designed with role-spec metadata. --- @@ -108,9 +111,15 @@ For callback/check/resume/adapt/complete: load `commands/monitor.md` and execute **Workflow**: -1. **Generate session ID**: `TC--` (slug from first 3 meaningful words of task) +1. **Check `needs_research` flag** from task-analysis.json: + - If `true`: **Spawn researcher worker first** to gather codebase context + - Wait for researcher callback + - Merge research findings into task context + - Update task-analysis.json with enriched context -2. **Create session folder structure**: +2. **Generate session ID**: `TC--` (slug from first 3 meaningful words of task) + +3. **Create session folder structure**: ``` .workflow/.team// +-- role-specs/ @@ -121,26 +130,26 @@ For callback/check/resume/adapt/complete: load `commands/monitor.md` and execute +-- .msg/ ``` -3. **Call TeamCreate** with team name derived from session ID +4. **Call TeamCreate** with team name derived from session ID -4. **Read `specs/role-spec-template.md`** + `task-analysis.json` +5. **Read `specs/role-spec-template.md`** + task-analysis.json -5. **For each role in task-analysis.json#roles**: +6. **For each role in task-analysis.json#roles**: - Fill role-spec template with: - YAML frontmatter: role, prefix, inner_loop, subagents, message_types - Phase 2-4 content from responsibility type reference sections in template - Task-specific instructions from task description - Write generated role-spec to `/role-specs/.md` -6. **Register roles** in team-session.json#roles (with `role_spec` path instead of `role_file`) +7. **Register roles** in team-session.json#roles (with `role_spec` path instead of `role_file`) -7. **Initialize shared infrastructure**: +8. **Initialize shared infrastructure**: - `wisdom/learnings.md`, `wisdom/decisions.md`, `wisdom/issues.md` (empty with headers) - `explorations/cache-index.json` (`{ "entries": [] }`) - `shared-memory.json` (`{}`) - `discussions/` (empty directory) -8. **Write team-session.json** with: session_id, task_description, status="active", roles, pipeline (empty), active_workers=[], completion_action="interactive", created_at +9. **Write team-session.json** with: session_id, task_description, status="active", roles, pipeline (empty), active_workers=[], completion_action="interactive", created_at **Success**: Session created, role-spec files generated, shared infrastructure initialized. diff --git a/README.md b/README.md index 9b5a378d..f9647bd8 100644 --- a/README.md +++ b/README.md @@ -437,21 +437,7 @@ Beat Cycle (single beat) ### Terminal Dashboard -Multi-terminal grid layout with real-time execution monitoring: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Dashboard Toolbar [Issues][Queue][Inspector]│ -├──────────┬──────────────────────────────────────────────────────┤ -│ Session │ Terminal Grid (tmux-style split panes) │ -│ Groups │ ┌─────────────────┬─────────────────────────────────┐│ -│ ├─ proj1 │ │ Terminal 1 │ Terminal 2 ││ -│ │ └─ cla│ │ $ ccw cli ... │ $ gemini analyze ... ││ -│ ├─ proj2 │ │ │ ││ -│ └─ ... │ └─────────────────┴─────────────────────────────────┘│ -│ │ Execution Monitor Panel (floating) │ -└──────────┴──────────────────────────────────────────────────────┘ -``` +Multi-terminal grid layout with real-time execution monitoring. **Features:** - 🖥️ Multi-terminal grid with resizable panes @@ -462,25 +448,7 @@ Multi-terminal grid layout with real-time execution monitoring: ### Orchestrator Editor -Visual workflow template editor with drag-drop: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ FlowToolbar [Templates][Execute] │ -├────────────┬────────────────────────────────────────┬───────────┤ -│ Node │ Flow Canvas │ Property │ -│ Palette │ ┌──────────┐ ┌──────────┐ │ Panel │ -│ ├─ Prompt │ │ Prompt │────▶│ CLI Tool │ │ │ -│ ├─ CLI │ │ Template │ │ Executor │ │ Edit node │ -│ ├─ Slash │ └──────────┘ └──────────┘ │ props │ -│ └─ Flow │ │ │ │ │ -│ │ ▼ ▼ │ │ -│ │ ┌──────────────────────────┐ │ │ -│ │ │ Slash Command │ │ │ -│ │ │ /workflow:plan │ │ │ -│ │ └──────────────────────────┘ │ │ -└────────────┴────────────────────────────────────────┴───────────┘ -``` +Visual workflow template editor with drag-drop. **Features:** - 🎨 React Flow-based visual editing @@ -490,23 +458,7 @@ Visual workflow template editor with drag-drop: ### Analysis Viewer -Grid layout for analysis sessions with filtering: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Filters: [Type ▼] [Status ▼] [Date Range] [Fullscreen] │ -├─────────────────────────────────────────────────────────────────┤ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ -│ │ Analysis #1 │ │ Analysis #2 │ │ Analysis #3 │ │ -│ │ Type: security │ │ Type: perf │ │ Type: architecture │ │ -│ │ Status: ✓ done │ │ Status: ✓ done │ │ Status: ⏳ running │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Analysis #4 │ │ Analysis #5 │ │ -│ │ ... │ │ ... │ │ -│ └─────────────────┘ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ -``` +Grid layout for analysis sessions with filtering and fullscreen mode. --- diff --git a/README_CN.md b/README_CN.md index a66d8abf..cd6f4147 100644 --- a/README_CN.md +++ b/README_CN.md @@ -433,21 +433,7 @@ v2 团队架构引入了**事件驱动的节拍模型**,实现高效编排: ### 终端仪表板 (Terminal Dashboard) -多终端网格布局,实时执行监控: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 仪表板工具栏 [问题][队列][检查器] │ -├──────────┬──────────────────────────────────────────────────────┤ -│ 会话 │ 终端网格 (tmux 风格分割窗格) │ -│ 分组 │ ┌─────────────────┬─────────────────────────────────┐│ -│ ├─ 项目1 │ │ 终端 1 │ 终端 2 ││ -│ │ └─ cla│ │ $ ccw cli ... │ $ gemini analyze ... ││ -│ ├─ 项目2 │ │ │ ││ -│ └─ ... │ └─────────────────┴─────────────────────────────────┘│ -│ │ 执行监控面板 (浮动) │ -└──────────┴──────────────────────────────────────────────────────┘ -``` +多终端网格布局,实时执行监控。 **功能特性:** - 🖥️ 多终端网格,可调整窗格大小 @@ -458,25 +444,7 @@ v2 团队架构引入了**事件驱动的节拍模型**,实现高效编排: ### 编排器编辑器 (Orchestrator Editor) -可视化工作流模板编辑器,支持拖放: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 流程工具栏 [模板][执行] │ -├────────────┬────────────────────────────────────────┬───────────┤ -│ 节点 │ 流程画布 │ 属性 │ -│ 调色板 │ ┌──────────┐ ┌──────────┐ │ 面板 │ -│ ├─ 提示词 │ │ 提示词 │────▶│ CLI 工具 │ │ │ -│ ├─ CLI │ │ 模板 │ │ 执行器 │ │ 编辑节点 │ -│ ├─ 斜杠 │ └──────────┘ └──────────┘ │ 属性 │ -│ └─ 流程 │ │ │ │ │ -│ │ ▼ ▼ │ │ -│ │ ┌──────────────────────────┐ │ │ -│ │ │ 斜杠命令 │ │ │ -│ │ │ /workflow:plan │ │ │ -│ │ └──────────────────────────┘ │ │ -└────────────┴────────────────────────────────────────┴───────────┘ -``` +可视化工作流模板编辑器,支持拖放。 **功能特性:** - 🎨 基于 React Flow 的可视化编辑 @@ -486,23 +454,7 @@ v2 团队架构引入了**事件驱动的节拍模型**,实现高效编排: ### 分析查看器 (Analysis Viewer) -带过滤功能的分析会话网格布局: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 过滤器: [类型 ▼] [状态 ▼] [日期范围] [全屏] │ -├─────────────────────────────────────────────────────────────────┤ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ -│ │ 分析 #1 │ │ 分析 #2 │ │ 分析 #3 │ │ -│ │ 类型: 安全 │ │ 类型: 性能 │ │ 类型: 架构 │ │ -│ │ 状态: ✓ 完成 │ │ 状态: ✓ 完成 │ │ 状态: ⏳ 运行中 │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ 分析 #4 │ │ 分析 #5 │ │ -│ │ ... │ │ ... │ │ -│ └─────────────────┘ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ -``` +带过滤功能的分析会话网格布局,支持全屏模式。 --- diff --git a/ccw/frontend/src/hooks/useNativeSessions.ts b/ccw/frontend/src/hooks/useNativeSessions.ts index 8a99bc7c..967b03a9 100644 --- a/ccw/frontend/src/hooks/useNativeSessions.ts +++ b/ccw/frontend/src/hooks/useNativeSessions.ts @@ -4,7 +4,7 @@ // TanStack Query hook for native CLI sessions list import React from 'react'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useInfiniteQuery } from '@tanstack/react-query'; import { fetchNativeSessions, type NativeSessionListItem, @@ -17,6 +17,7 @@ import { workspaceQueryKeys } from '@/lib/queryKeys'; const STALE_TIME = 2 * 60 * 1000; // 2 minutes (increased from 30s) const GC_TIME = 10 * 60 * 1000; // 10 minutes (increased from 5min) +const PAGE_SIZE = 50; // Default page size for pagination // ========== Types ========== @@ -54,6 +55,29 @@ export interface UseNativeSessionsReturn { refetch: () => Promise; } +export interface UseNativeSessionsInfiniteReturn { + /** All sessions data (flattened from all pages) */ + sessions: NativeSessionListItem[]; + /** Sessions grouped by tool */ + byTool: ByToolRecord; + /** Total count from current pages */ + count: number; + /** Loading state for initial fetch */ + isLoading: boolean; + /** Fetching state (initial or refetch) */ + isFetching: boolean; + /** Fetching next page */ + isFetchingNextPage: boolean; + /** Whether there are more pages */ + hasNextPage: boolean; + /** Error object if query failed */ + error: Error | null; + /** Fetch next page */ + fetchNextPage: () => Promise; + /** Manually refetch data */ + refetch: () => Promise; +} + // ========== Helper Functions ========== /** @@ -95,7 +119,7 @@ export function useNativeSessions( const query = useQuery({ queryKey: workspaceQueryKeys.nativeSessionsList(projectPath, tool), - queryFn: () => fetchNativeSessions(tool, projectPath), + queryFn: () => fetchNativeSessions({ tool, project: projectPath, limit: 200 }), staleTime, gcTime, enabled, @@ -107,7 +131,7 @@ export function useNativeSessions( // Memoize sessions and byTool calculations const { sessions, byTool } = React.useMemo(() => { const sessions = query.data?.sessions ?? []; - const byTool = groupByTool(sessions); + const byTool = query.data?.byTool ?? groupByTool(sessions); return { sessions, byTool }; }, [query.data]); @@ -125,3 +149,81 @@ export function useNativeSessions( refetch, }; } + +/** + * Hook for fetching native CLI sessions with infinite scroll/pagination + * + * @example + * ```tsx + * const { sessions, fetchNextPage, hasNextPage, isFetchingNextPage } = useNativeSessionsInfinite(); + * + * // Load more button + * + * ``` + */ +export function useNativeSessionsInfinite( + options: UseNativeSessionsOptions = {} +): UseNativeSessionsInfiniteReturn { + const { tool, staleTime = STALE_TIME, gcTime = GC_TIME, enabled = true } = options; + const projectPath = useWorkflowStore(selectProjectPath); + + const query = useInfiniteQuery({ + queryKey: [...workspaceQueryKeys.nativeSessionsList(projectPath, tool), 'infinite'], + queryFn: ({ pageParam }) => fetchNativeSessions({ + tool, + project: projectPath, + limit: PAGE_SIZE, + cursor: pageParam as string | null, + }), + initialPageParam: null as string | null, + getNextPageParam: (lastPage) => { + if (!lastPage.hasMore || !lastPage.nextCursor) return undefined; + return lastPage.nextCursor; + }, + staleTime, + gcTime, + enabled, + refetchOnWindowFocus: false, + retry: 2, + retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), + }); + + // Flatten all pages into a single array and group by tool + const { sessions, byTool, count } = React.useMemo(() => { + const allSessions = query.data?.pages.flatMap(page => page.sessions) ?? []; + // Merge byTool from all pages + const mergedByTool: ByToolRecord = {}; + for (const page of query.data?.pages ?? []) { + for (const [toolKey, toolSessions] of Object.entries(page.byTool ?? {})) { + if (!mergedByTool[toolKey]) { + mergedByTool[toolKey] = []; + } + mergedByTool[toolKey].push(...toolSessions); + } + } + return { sessions: allSessions, byTool: mergedByTool, count: allSessions.length }; + }, [query.data]); + + const fetchNextPage = async () => { + await query.fetchNextPage(); + }; + + const refetch = async () => { + await query.refetch(); + }; + + return { + sessions, + byTool, + count, + isLoading: query.isLoading, + isFetching: query.isFetching, + isFetchingNextPage: query.isFetchingNextPage, + hasNextPage: query.hasNextPage, + error: query.error, + fetchNextPage, + refetch, + }; +} diff --git a/ccw/frontend/src/lib/api.ts b/ccw/frontend/src/lib/api.ts index 28d38ba8..4a08579a 100644 --- a/ccw/frontend/src/lib/api.ts +++ b/ccw/frontend/src/lib/api.ts @@ -2388,21 +2388,35 @@ export interface NativeSessionListItem { */ export interface NativeSessionsListResponse { sessions: NativeSessionListItem[]; + byTool: Record; count: number; + hasMore: boolean; + nextCursor: string | null; } /** - * Fetch list of native CLI sessions - * @param tool - Filter by tool type (optional) - * @param project - Filter by project path (optional) + * Fetch options for native sessions pagination + */ +export interface FetchNativeSessionsOptions { + tool?: 'gemini' | 'qwen' | 'codex' | 'claude' | 'opencode'; + project?: string; + limit?: number; + cursor?: string | null; // ISO timestamp for cursor-based pagination +} + +/** + * Fetch list of native CLI sessions with pagination support + * @param options - Pagination and filter options */ export async function fetchNativeSessions( - tool?: 'gemini' | 'qwen' | 'codex' | 'claude' | 'opencode', - project?: string + options: FetchNativeSessionsOptions = {} ): Promise { + const { tool, project, limit = 50, cursor } = options; const params = new URLSearchParams(); if (tool) params.set('tool', tool); - if (project) params.set('project', project); + if (project) params.set('path', project); + if (limit) params.set('limit', String(limit)); + if (cursor) params.set('cursor', cursor); const query = params.toString(); return fetchApi( diff --git a/ccw/frontend/src/locales/en/history.json b/ccw/frontend/src/locales/en/history.json index 6d13a810..4984a5a4 100644 --- a/ccw/frontend/src/locales/en/history.json +++ b/ccw/frontend/src/locales/en/history.json @@ -38,6 +38,8 @@ "nativeSessions": { "count": "{count} native sessions", "sessions": "sessions", + "loading": "Loading...", + "loadMore": "Load More", "empty": { "title": "No Native Sessions", "message": "Native CLI sessions from Gemini, Codex, Qwen, etc. will appear here." diff --git a/ccw/frontend/src/locales/zh/history.json b/ccw/frontend/src/locales/zh/history.json index 5da35235..80da319a 100644 --- a/ccw/frontend/src/locales/zh/history.json +++ b/ccw/frontend/src/locales/zh/history.json @@ -38,6 +38,8 @@ "nativeSessions": { "count": "{count} 个原生会话", "sessions": "个会话", + "loading": "加载中...", + "loadMore": "加载更多", "empty": { "title": "无原生会话", "message": "来自 Gemini、Codex、Qwen 等的原生 CLI 会话将显示在这里。" diff --git a/ccw/frontend/src/pages/HistoryPage.tsx b/ccw/frontend/src/pages/HistoryPage.tsx index d107843b..fe3062fb 100644 --- a/ccw/frontend/src/pages/HistoryPage.tsx +++ b/ccw/frontend/src/pages/HistoryPage.tsx @@ -24,7 +24,7 @@ import { import { useAppStore, selectIsImmersiveMode } from '@/stores/appStore'; import { cn } from '@/lib/utils'; import { useHistory } from '@/hooks/useHistory'; -import { useNativeSessions } from '@/hooks/useNativeSessions'; +import { useNativeSessionsInfinite } from '@/hooks/useNativeSessions'; import { ConversationCard } from '@/components/shared/ConversationCard'; import { CliStreamPanel } from '@/components/shared/CliStreamPanel'; import { NativeSessionPanel } from '@/components/shared/NativeSessionPanel'; @@ -86,15 +86,18 @@ export function HistoryPage() { filter: { search: searchQuery || undefined, tool: toolFilter }, }); - // Native sessions hook + // Native sessions hook (infinite loading) const { sessions: nativeSessions, byTool: nativeSessionsByTool, isLoading: isLoadingNativeSessions, isFetching: isFetchingNativeSessions, + isFetchingNextPage: isLoadingMoreNativeSessions, + hasNextPage: hasMoreNativeSessions, error: nativeSessionsError, + fetchNextPage: loadMoreNativeSessions, refetch: refetchNativeSessions, - } = useNativeSessions(); + } = useNativeSessionsInfinite(); // Track expanded tool groups in native sessions tab const [expandedTools, setExpandedTools] = React.useState>(new Set()); @@ -423,7 +426,7 @@ export function HistoryPage() { variant="outline" size="sm" onClick={() => refetchNativeSessions()} - disabled={isFetchingNativeSessions} + disabled={isFetchingNativeSessions && !isLoadingMoreNativeSessions} > {formatMessage({ id: 'common.actions.refresh' })} @@ -571,6 +574,27 @@ export function HistoryPage() { ); })} + + {/* Load More Button */} + {hasMoreNativeSessions && ( +
+ +
+ )} )} diff --git a/ccw/frontend/src/stores/queueSchedulerStore.ts b/ccw/frontend/src/stores/queueSchedulerStore.ts index 74a6d506..78f14863 100644 --- a/ccw/frontend/src/stores/queueSchedulerStore.ts +++ b/ccw/frontend/src/stores/queueSchedulerStore.ts @@ -55,6 +55,8 @@ interface QueueSchedulerActions { pauseQueue: () => Promise; /** Stop the queue scheduler via POST /api/queue/scheduler/stop */ stopQueue: () => Promise; + /** Reset the queue scheduler via POST /api/queue/scheduler/reset */ + resetQueue: () => Promise; /** Update scheduler config via POST /api/queue/scheduler/config */ updateConfig: (config: Partial) => Promise; } @@ -255,6 +257,24 @@ export const useQueueSchedulerStore = create()( } }, + resetQueue: async () => { + try { + const response = await fetch('/api/queue/scheduler/reset', { + method: 'POST', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + }); + if (!response.ok) { + const body = await response.json().catch(() => ({})); + throw new Error(body.error || body.message || response.statusText); + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error('[QueueScheduler] resetQueue error:', message); + set({ error: message }, false, 'resetQueue/error'); + } + }, + updateConfig: async (config: Partial) => { try { const response = await fetch('/api/queue/scheduler/config', { diff --git a/ccw/src/core/routes/cli-routes.ts b/ccw/src/core/routes/cli-routes.ts index faf9b1e1..f2dd90a9 100644 --- a/ccw/src/core/routes/cli-routes.ts +++ b/ccw/src/core/routes/cli-routes.ts @@ -852,17 +852,33 @@ export async function handleCliRoutes(ctx: RouteContext): Promise { return true; } - // API: List Native CLI Sessions + // API: List Native CLI Sessions (with pagination support) if (pathname === '/api/cli/native-sessions' && req.method === 'GET') { const projectPath = url.searchParams.get('path') || null; - const limit = parseInt(url.searchParams.get('limit') || '100', 10); + const limit = parseInt(url.searchParams.get('limit') || '50', 10); + const offset = parseInt(url.searchParams.get('offset') || '0', 10); + const cursor = url.searchParams.get('cursor'); // ISO timestamp for cursor-based pagination try { - const sessions = listAllNativeSessions({ + // Parse cursor timestamp if provided + const afterTimestamp = cursor ? new Date(cursor) : undefined; + + // Fetch sessions with limit + 1 to detect if there are more + const allSessions = listAllNativeSessions({ workingDir: projectPath || undefined, - limit + limit: limit + 1, // Fetch one extra to check hasMore + afterTimestamp }); + // Determine if there are more results + const hasMore = allSessions.length > limit; + const sessions = hasMore ? allSessions.slice(0, limit) : allSessions; + + // Get next cursor (timestamp of last item for cursor-based pagination) + const nextCursor = sessions.length > 0 + ? sessions[sessions.length - 1].updatedAt.toISOString() + : null; + // Group sessions by tool const byTool: Record = {}; for (const session of sessions) { @@ -873,7 +889,13 @@ export async function handleCliRoutes(ctx: RouteContext): Promise { } res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ sessions, byTool })); + res.end(JSON.stringify({ + sessions, + byTool, + hasMore, + nextCursor, + count: sessions.length + })); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); diff --git a/ccw/src/core/routes/queue-routes.ts b/ccw/src/core/routes/queue-routes.ts index 6cd09437..2a4bccfa 100644 --- a/ccw/src/core/routes/queue-routes.ts +++ b/ccw/src/core/routes/queue-routes.ts @@ -46,6 +46,10 @@ export async function handleQueueSchedulerRoutes( for (const item of items) { schedulerService.addItem(item); } + } else if (state.status === 'completed' || state.status === 'failed') { + // Auto-reset when scheduler is in terminal state and start fresh + schedulerService.reset(); + schedulerService.start(items); } else { return { error: `Cannot add items when scheduler is in '${state.status}' state`, @@ -131,6 +135,22 @@ export async function handleQueueSchedulerRoutes( return true; } + // POST /api/queue/scheduler/reset - Reset scheduler to idle state + if (pathname === '/api/queue/scheduler/reset' && req.method === 'POST') { + handlePostRequest(req, res, async () => { + try { + schedulerService.reset(); + return { + success: true, + state: schedulerService.getState(), + }; + } catch (err) { + return { error: (err as Error).message, status: 409 }; + } + }); + return true; + } + // POST /api/queue/scheduler/config - Update scheduler configuration if (pathname === '/api/queue/scheduler/config' && req.method === 'POST') { handlePostRequest(req, res, async (body) => {