mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: implement infinite scrolling for native sessions and add reset functionality to queue scheduler
This commit is contained in:
@@ -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.
|
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
|
## Phase 2: Context Loading
|
||||||
|
|
||||||
| Input | Source | Required |
|
| Input | Source | Required |
|
||||||
@@ -135,6 +147,7 @@ Write `<session-folder>/task-analysis.json`:
|
|||||||
"total_score": 3,
|
"total_score": 3,
|
||||||
"level": "low"
|
"level": "low"
|
||||||
},
|
},
|
||||||
|
"needs_research": false,
|
||||||
"artifacts": [
|
"artifacts": [
|
||||||
{ "name": "research-findings.md", "producer": "researcher", "path": "artifacts/research-findings.md" }
|
{ "name": "research-findings.md", "producer": "researcher", "path": "artifacts/research-findings.md" }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Orchestrate the team-coordinate workflow: task analysis, dynamic role-spec gener
|
|||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
### MUST
|
### 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
|
- Dynamically generate worker role-specs from specs/role-spec-template.md
|
||||||
- Create team and spawn team-worker agents in background
|
- Create team and spawn team-worker agents in background
|
||||||
- Dispatch tasks with proper dependency chains from task-analysis.json
|
- 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
|
- Execute completion action when pipeline finishes
|
||||||
|
|
||||||
### MUST NOT
|
### MUST NOT
|
||||||
|
- **Read source code or perform codebase exploration** (delegate to worker roles)
|
||||||
- Execute task work directly (delegate to workers)
|
- Execute task work directly (delegate to workers)
|
||||||
- Modify task output artifacts (workers own their deliverables)
|
- Modify task output artifacts (workers own their deliverables)
|
||||||
- Call implementation subagents (code-developer, etc.) directly
|
- 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
|
- Override consensus_blocked HIGH without user confirmation
|
||||||
- Spawn workers with `general-purpose` agent (MUST use `team-worker`)
|
- 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
|
## 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.
|
**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**:
|
**Workflow**:
|
||||||
|
|
||||||
1. **Parse user task description**
|
1. **Parse user task description**
|
||||||
@@ -98,6 +99,8 @@ For callback/check/resume/adapt/complete: load `commands/monitor.md` and execute
|
|||||||
|
|
||||||
4. **Output**: Write `<session>/task-analysis.json`
|
4. **Output**: Write `<session>/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.
|
**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**:
|
**Workflow**:
|
||||||
|
|
||||||
1. **Generate session ID**: `TC-<slug>-<date>` (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>-<date>` (slug from first 3 meaningful words of task)
|
||||||
|
|
||||||
|
3. **Create session folder structure**:
|
||||||
```
|
```
|
||||||
.workflow/.team/<session-id>/
|
.workflow/.team/<session-id>/
|
||||||
+-- role-specs/
|
+-- role-specs/
|
||||||
@@ -121,26 +130,26 @@ For callback/check/resume/adapt/complete: load `commands/monitor.md` and execute
|
|||||||
+-- .msg/
|
+-- .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:
|
- Fill role-spec template with:
|
||||||
- YAML frontmatter: role, prefix, inner_loop, subagents, message_types
|
- YAML frontmatter: role, prefix, inner_loop, subagents, message_types
|
||||||
- Phase 2-4 content from responsibility type reference sections in template
|
- Phase 2-4 content from responsibility type reference sections in template
|
||||||
- Task-specific instructions from task description
|
- Task-specific instructions from task description
|
||||||
- Write generated role-spec to `<session>/role-specs/<role-name>.md`
|
- Write generated role-spec to `<session>/role-specs/<role-name>.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)
|
- `wisdom/learnings.md`, `wisdom/decisions.md`, `wisdom/issues.md` (empty with headers)
|
||||||
- `explorations/cache-index.json` (`{ "entries": [] }`)
|
- `explorations/cache-index.json` (`{ "entries": [] }`)
|
||||||
- `shared-memory.json` (`{}`)
|
- `shared-memory.json` (`{}`)
|
||||||
- `discussions/` (empty directory)
|
- `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.
|
**Success**: Session created, role-spec files generated, shared infrastructure initialized.
|
||||||
|
|
||||||
|
|||||||
54
README.md
54
README.md
@@ -437,21 +437,7 @@ Beat Cycle (single beat)
|
|||||||
|
|
||||||
### Terminal Dashboard
|
### Terminal Dashboard
|
||||||
|
|
||||||
Multi-terminal grid layout with real-time execution monitoring:
|
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) │
|
|
||||||
└──────────┴──────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
- 🖥️ Multi-terminal grid with resizable panes
|
- 🖥️ Multi-terminal grid with resizable panes
|
||||||
@@ -462,25 +448,7 @@ Multi-terminal grid layout with real-time execution monitoring:
|
|||||||
|
|
||||||
### Orchestrator Editor
|
### Orchestrator Editor
|
||||||
|
|
||||||
Visual workflow template editor with drag-drop:
|
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 │ │ │
|
|
||||||
│ │ └──────────────────────────┘ │ │
|
|
||||||
└────────────┴────────────────────────────────────────┴───────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
- 🎨 React Flow-based visual editing
|
- 🎨 React Flow-based visual editing
|
||||||
@@ -490,23 +458,7 @@ Visual workflow template editor with drag-drop:
|
|||||||
|
|
||||||
### Analysis Viewer
|
### Analysis Viewer
|
||||||
|
|
||||||
Grid layout for analysis sessions with filtering:
|
Grid layout for analysis sessions with filtering and fullscreen mode.
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ 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 │ │
|
|
||||||
│ │ ... │ │ ... │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
54
README_CN.md
54
README_CN.md
@@ -433,21 +433,7 @@ v2 团队架构引入了**事件驱动的节拍模型**,实现高效编排:
|
|||||||
|
|
||||||
### 终端仪表板 (Terminal Dashboard)
|
### 终端仪表板 (Terminal Dashboard)
|
||||||
|
|
||||||
多终端网格布局,实时执行监控:
|
多终端网格布局,实时执行监控。
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ 仪表板工具栏 [问题][队列][检查器] │
|
|
||||||
├──────────┬──────────────────────────────────────────────────────┤
|
|
||||||
│ 会话 │ 终端网格 (tmux 风格分割窗格) │
|
|
||||||
│ 分组 │ ┌─────────────────┬─────────────────────────────────┐│
|
|
||||||
│ ├─ 项目1 │ │ 终端 1 │ 终端 2 ││
|
|
||||||
│ │ └─ cla│ │ $ ccw cli ... │ $ gemini analyze ... ││
|
|
||||||
│ ├─ 项目2 │ │ │ ││
|
|
||||||
│ └─ ... │ └─────────────────┴─────────────────────────────────┘│
|
|
||||||
│ │ 执行监控面板 (浮动) │
|
|
||||||
└──────────┴──────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**功能特性:**
|
**功能特性:**
|
||||||
- 🖥️ 多终端网格,可调整窗格大小
|
- 🖥️ 多终端网格,可调整窗格大小
|
||||||
@@ -458,25 +444,7 @@ v2 团队架构引入了**事件驱动的节拍模型**,实现高效编排:
|
|||||||
|
|
||||||
### 编排器编辑器 (Orchestrator Editor)
|
### 编排器编辑器 (Orchestrator Editor)
|
||||||
|
|
||||||
可视化工作流模板编辑器,支持拖放:
|
可视化工作流模板编辑器,支持拖放。
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ 流程工具栏 [模板][执行] │
|
|
||||||
├────────────┬────────────────────────────────────────┬───────────┤
|
|
||||||
│ 节点 │ 流程画布 │ 属性 │
|
|
||||||
│ 调色板 │ ┌──────────┐ ┌──────────┐ │ 面板 │
|
|
||||||
│ ├─ 提示词 │ │ 提示词 │────▶│ CLI 工具 │ │ │
|
|
||||||
│ ├─ CLI │ │ 模板 │ │ 执行器 │ │ 编辑节点 │
|
|
||||||
│ ├─ 斜杠 │ └──────────┘ └──────────┘ │ 属性 │
|
|
||||||
│ └─ 流程 │ │ │ │ │
|
|
||||||
│ │ ▼ ▼ │ │
|
|
||||||
│ │ ┌──────────────────────────┐ │ │
|
|
||||||
│ │ │ 斜杠命令 │ │ │
|
|
||||||
│ │ │ /workflow:plan │ │ │
|
|
||||||
│ │ └──────────────────────────┘ │ │
|
|
||||||
└────────────┴────────────────────────────────────────┴───────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**功能特性:**
|
**功能特性:**
|
||||||
- 🎨 基于 React Flow 的可视化编辑
|
- 🎨 基于 React Flow 的可视化编辑
|
||||||
@@ -486,23 +454,7 @@ v2 团队架构引入了**事件驱动的节拍模型**,实现高效编排:
|
|||||||
|
|
||||||
### 分析查看器 (Analysis Viewer)
|
### 分析查看器 (Analysis Viewer)
|
||||||
|
|
||||||
带过滤功能的分析会话网格布局:
|
带过滤功能的分析会话网格布局,支持全屏模式。
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ 过滤器: [类型 ▼] [状态 ▼] [日期范围] [全屏] │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
|
|
||||||
│ │ 分析 #1 │ │ 分析 #2 │ │ 分析 #3 │ │
|
|
||||||
│ │ 类型: 安全 │ │ 类型: 性能 │ │ 类型: 架构 │ │
|
|
||||||
│ │ 状态: ✓ 完成 │ │ 状态: ✓ 完成 │ │ 状态: ⏳ 运行中 │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
||||||
│ │ 分析 #4 │ │ 分析 #5 │ │
|
|
||||||
│ │ ... │ │ ... │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// TanStack Query hook for native CLI sessions list
|
// TanStack Query hook for native CLI sessions list
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
fetchNativeSessions,
|
fetchNativeSessions,
|
||||||
type NativeSessionListItem,
|
type NativeSessionListItem,
|
||||||
@@ -17,6 +17,7 @@ import { workspaceQueryKeys } from '@/lib/queryKeys';
|
|||||||
|
|
||||||
const STALE_TIME = 2 * 60 * 1000; // 2 minutes (increased from 30s)
|
const STALE_TIME = 2 * 60 * 1000; // 2 minutes (increased from 30s)
|
||||||
const GC_TIME = 10 * 60 * 1000; // 10 minutes (increased from 5min)
|
const GC_TIME = 10 * 60 * 1000; // 10 minutes (increased from 5min)
|
||||||
|
const PAGE_SIZE = 50; // Default page size for pagination
|
||||||
|
|
||||||
// ========== Types ==========
|
// ========== Types ==========
|
||||||
|
|
||||||
@@ -54,6 +55,29 @@ export interface UseNativeSessionsReturn {
|
|||||||
refetch: () => Promise<void>;
|
refetch: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<void>;
|
||||||
|
/** Manually refetch data */
|
||||||
|
refetch: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Helper Functions ==========
|
// ========== Helper Functions ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,7 +119,7 @@ export function useNativeSessions(
|
|||||||
|
|
||||||
const query = useQuery<NativeSessionsListResponse>({
|
const query = useQuery<NativeSessionsListResponse>({
|
||||||
queryKey: workspaceQueryKeys.nativeSessionsList(projectPath, tool),
|
queryKey: workspaceQueryKeys.nativeSessionsList(projectPath, tool),
|
||||||
queryFn: () => fetchNativeSessions(tool, projectPath),
|
queryFn: () => fetchNativeSessions({ tool, project: projectPath, limit: 200 }),
|
||||||
staleTime,
|
staleTime,
|
||||||
gcTime,
|
gcTime,
|
||||||
enabled,
|
enabled,
|
||||||
@@ -107,7 +131,7 @@ export function useNativeSessions(
|
|||||||
// Memoize sessions and byTool calculations
|
// Memoize sessions and byTool calculations
|
||||||
const { sessions, byTool } = React.useMemo(() => {
|
const { sessions, byTool } = React.useMemo(() => {
|
||||||
const sessions = query.data?.sessions ?? [];
|
const sessions = query.data?.sessions ?? [];
|
||||||
const byTool = groupByTool(sessions);
|
const byTool = query.data?.byTool ?? groupByTool(sessions);
|
||||||
return { sessions, byTool };
|
return { sessions, byTool };
|
||||||
}, [query.data]);
|
}, [query.data]);
|
||||||
|
|
||||||
@@ -125,3 +149,81 @@ export function useNativeSessions(
|
|||||||
refetch,
|
refetch,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for fetching native CLI sessions with infinite scroll/pagination
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { sessions, fetchNextPage, hasNextPage, isFetchingNextPage } = useNativeSessionsInfinite();
|
||||||
|
*
|
||||||
|
* // Load more button
|
||||||
|
* <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
|
||||||
|
* {isFetchingNextPage ? 'Loading...' : '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<NativeSessionsListResponse>({
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -2388,21 +2388,35 @@ export interface NativeSessionListItem {
|
|||||||
*/
|
*/
|
||||||
export interface NativeSessionsListResponse {
|
export interface NativeSessionsListResponse {
|
||||||
sessions: NativeSessionListItem[];
|
sessions: NativeSessionListItem[];
|
||||||
|
byTool: Record<string, NativeSessionListItem[]>;
|
||||||
count: number;
|
count: number;
|
||||||
|
hasMore: boolean;
|
||||||
|
nextCursor: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch list of native CLI sessions
|
* Fetch options for native sessions pagination
|
||||||
* @param tool - Filter by tool type (optional)
|
*/
|
||||||
* @param project - Filter by project path (optional)
|
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(
|
export async function fetchNativeSessions(
|
||||||
tool?: 'gemini' | 'qwen' | 'codex' | 'claude' | 'opencode',
|
options: FetchNativeSessionsOptions = {}
|
||||||
project?: string
|
|
||||||
): Promise<NativeSessionsListResponse> {
|
): Promise<NativeSessionsListResponse> {
|
||||||
|
const { tool, project, limit = 50, cursor } = options;
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (tool) params.set('tool', tool);
|
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();
|
const query = params.toString();
|
||||||
return fetchApi<NativeSessionsListResponse>(
|
return fetchApi<NativeSessionsListResponse>(
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
"nativeSessions": {
|
"nativeSessions": {
|
||||||
"count": "{count} native sessions",
|
"count": "{count} native sessions",
|
||||||
"sessions": "sessions",
|
"sessions": "sessions",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"loadMore": "Load More",
|
||||||
"empty": {
|
"empty": {
|
||||||
"title": "No Native Sessions",
|
"title": "No Native Sessions",
|
||||||
"message": "Native CLI sessions from Gemini, Codex, Qwen, etc. will appear here."
|
"message": "Native CLI sessions from Gemini, Codex, Qwen, etc. will appear here."
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
"nativeSessions": {
|
"nativeSessions": {
|
||||||
"count": "{count} 个原生会话",
|
"count": "{count} 个原生会话",
|
||||||
"sessions": "个会话",
|
"sessions": "个会话",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"loadMore": "加载更多",
|
||||||
"empty": {
|
"empty": {
|
||||||
"title": "无原生会话",
|
"title": "无原生会话",
|
||||||
"message": "来自 Gemini、Codex、Qwen 等的原生 CLI 会话将显示在这里。"
|
"message": "来自 Gemini、Codex、Qwen 等的原生 CLI 会话将显示在这里。"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
import { useAppStore, selectIsImmersiveMode } from '@/stores/appStore';
|
import { useAppStore, selectIsImmersiveMode } from '@/stores/appStore';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useHistory } from '@/hooks/useHistory';
|
import { useHistory } from '@/hooks/useHistory';
|
||||||
import { useNativeSessions } from '@/hooks/useNativeSessions';
|
import { useNativeSessionsInfinite } from '@/hooks/useNativeSessions';
|
||||||
import { ConversationCard } from '@/components/shared/ConversationCard';
|
import { ConversationCard } from '@/components/shared/ConversationCard';
|
||||||
import { CliStreamPanel } from '@/components/shared/CliStreamPanel';
|
import { CliStreamPanel } from '@/components/shared/CliStreamPanel';
|
||||||
import { NativeSessionPanel } from '@/components/shared/NativeSessionPanel';
|
import { NativeSessionPanel } from '@/components/shared/NativeSessionPanel';
|
||||||
@@ -86,15 +86,18 @@ export function HistoryPage() {
|
|||||||
filter: { search: searchQuery || undefined, tool: toolFilter },
|
filter: { search: searchQuery || undefined, tool: toolFilter },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Native sessions hook
|
// Native sessions hook (infinite loading)
|
||||||
const {
|
const {
|
||||||
sessions: nativeSessions,
|
sessions: nativeSessions,
|
||||||
byTool: nativeSessionsByTool,
|
byTool: nativeSessionsByTool,
|
||||||
isLoading: isLoadingNativeSessions,
|
isLoading: isLoadingNativeSessions,
|
||||||
isFetching: isFetchingNativeSessions,
|
isFetching: isFetchingNativeSessions,
|
||||||
|
isFetchingNextPage: isLoadingMoreNativeSessions,
|
||||||
|
hasNextPage: hasMoreNativeSessions,
|
||||||
error: nativeSessionsError,
|
error: nativeSessionsError,
|
||||||
|
fetchNextPage: loadMoreNativeSessions,
|
||||||
refetch: refetchNativeSessions,
|
refetch: refetchNativeSessions,
|
||||||
} = useNativeSessions();
|
} = useNativeSessionsInfinite();
|
||||||
|
|
||||||
// Track expanded tool groups in native sessions tab
|
// Track expanded tool groups in native sessions tab
|
||||||
const [expandedTools, setExpandedTools] = React.useState<Set<string>>(new Set());
|
const [expandedTools, setExpandedTools] = React.useState<Set<string>>(new Set());
|
||||||
@@ -423,7 +426,7 @@ export function HistoryPage() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => refetchNativeSessions()}
|
onClick={() => refetchNativeSessions()}
|
||||||
disabled={isFetchingNativeSessions}
|
disabled={isFetchingNativeSessions && !isLoadingMoreNativeSessions}
|
||||||
>
|
>
|
||||||
<RefreshCw className={cn('h-4 w-4 mr-2', isFetchingNativeSessions && 'animate-spin')} />
|
<RefreshCw className={cn('h-4 w-4 mr-2', isFetchingNativeSessions && 'animate-spin')} />
|
||||||
{formatMessage({ id: 'common.actions.refresh' })}
|
{formatMessage({ id: 'common.actions.refresh' })}
|
||||||
@@ -571,6 +574,27 @@ export function HistoryPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Load More Button */}
|
||||||
|
{hasMoreNativeSessions && (
|
||||||
|
<div className="flex justify-center pt-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => loadMoreNativeSessions()}
|
||||||
|
disabled={isLoadingMoreNativeSessions}
|
||||||
|
>
|
||||||
|
{isLoadingMoreNativeSessions ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
{formatMessage({ id: 'history.nativeSessions.loading', defaultMessage: 'Loading...' })}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
formatMessage({ id: 'history.nativeSessions.loadMore', defaultMessage: 'Load More' })
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ interface QueueSchedulerActions {
|
|||||||
pauseQueue: () => Promise<void>;
|
pauseQueue: () => Promise<void>;
|
||||||
/** Stop the queue scheduler via POST /api/queue/scheduler/stop */
|
/** Stop the queue scheduler via POST /api/queue/scheduler/stop */
|
||||||
stopQueue: () => Promise<void>;
|
stopQueue: () => Promise<void>;
|
||||||
|
/** Reset the queue scheduler via POST /api/queue/scheduler/reset */
|
||||||
|
resetQueue: () => Promise<void>;
|
||||||
/** Update scheduler config via POST /api/queue/scheduler/config */
|
/** Update scheduler config via POST /api/queue/scheduler/config */
|
||||||
updateConfig: (config: Partial<QueueSchedulerConfig>) => Promise<void>;
|
updateConfig: (config: Partial<QueueSchedulerConfig>) => Promise<void>;
|
||||||
}
|
}
|
||||||
@@ -255,6 +257,24 @@ export const useQueueSchedulerStore = create<QueueSchedulerStore>()(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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<QueueSchedulerConfig>) => {
|
updateConfig: async (config: Partial<QueueSchedulerConfig>) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/queue/scheduler/config', {
|
const response = await fetch('/api/queue/scheduler/config', {
|
||||||
|
|||||||
@@ -852,17 +852,33 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// API: List Native CLI Sessions
|
// API: List Native CLI Sessions (with pagination support)
|
||||||
if (pathname === '/api/cli/native-sessions' && req.method === 'GET') {
|
if (pathname === '/api/cli/native-sessions' && req.method === 'GET') {
|
||||||
const projectPath = url.searchParams.get('path') || null;
|
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 {
|
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,
|
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
|
// Group sessions by tool
|
||||||
const byTool: Record<string, typeof sessions> = {};
|
const byTool: Record<string, typeof sessions> = {};
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
@@ -873,7 +889,13 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
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) {
|
} catch (err) {
|
||||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({ error: (err as Error).message }));
|
res.end(JSON.stringify({ error: (err as Error).message }));
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ export async function handleQueueSchedulerRoutes(
|
|||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
schedulerService.addItem(item);
|
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 {
|
} else {
|
||||||
return {
|
return {
|
||||||
error: `Cannot add items when scheduler is in '${state.status}' state`,
|
error: `Cannot add items when scheduler is in '${state.status}' state`,
|
||||||
@@ -131,6 +135,22 @@ export async function handleQueueSchedulerRoutes(
|
|||||||
return true;
|
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
|
// POST /api/queue/scheduler/config - Update scheduler configuration
|
||||||
if (pathname === '/api/queue/scheduler/config' && req.method === 'POST') {
|
if (pathname === '/api/queue/scheduler/config' && req.method === 'POST') {
|
||||||
handlePostRequest(req, res, async (body) => {
|
handlePostRequest(req, res, async (body) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user