File size: 4,342 Bytes
1f21206 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | /**
* TaskService — CLI Task V2 的读取与查询
*
* 任务信息存储在 ~/.claude/tasks/<task_list_id>/ 目录下,每个任务一个 JSON 文件。
* Task list ID 可以是 session ID、team name 等。
*/
import * as fs from 'fs/promises'
import * as path from 'path'
import * as os from 'os'
export type TaskStatus = 'pending' | 'in_progress' | 'completed'
export type TaskInfo = {
id: string
subject: string
description: string
activeForm?: string
owner?: string
status: TaskStatus
blocks: string[]
blockedBy: string[]
metadata?: Record<string, unknown>
taskListId: string
}
export type TaskListSummary = {
id: string
taskCount: number
completedCount: number
inProgressCount: number
pendingCount: number
}
export class TaskService {
private getConfigDir(): string {
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude')
}
private getTasksDir(): string {
return path.join(this.getConfigDir(), 'tasks')
}
/** 列出所有 task list (目录) */
async listTaskLists(): Promise<TaskListSummary[]> {
const tasksDir = this.getTasksDir()
try {
const entries = await fs.readdir(tasksDir, { withFileTypes: true })
const results: TaskListSummary[] = []
for (const entry of entries) {
if (!entry.isDirectory()) continue
const tasks = await this.getTasksForList(entry.name)
if (tasks.length === 0) continue
results.push({
id: entry.name,
taskCount: tasks.length,
completedCount: tasks.filter((t) => t.status === 'completed').length,
inProgressCount: tasks.filter((t) => t.status === 'in_progress').length,
pendingCount: tasks.filter((t) => t.status === 'pending').length,
})
}
return results
} catch (err: any) {
if (err.code === 'ENOENT') return []
throw err
}
}
/** 获取指定 task list 的所有任务 */
async getTasksForList(taskListId: string): Promise<TaskInfo[]> {
const listDir = path.join(this.getTasksDir(), taskListId)
try {
const entries = await fs.readdir(listDir)
const tasks: TaskInfo[] = []
for (const filename of entries) {
if (!filename.endsWith('.json')) continue
try {
const raw = await fs.readFile(path.join(listDir, filename), 'utf-8')
const data = JSON.parse(raw)
const task = this.parseTaskFile(data, taskListId)
if (task) tasks.push(task)
} catch {
// skip unparseable files
}
}
// Sort by numeric ID
return tasks.sort((a, b) => {
const numA = parseInt(a.id, 10)
const numB = parseInt(b.id, 10)
if (!isNaN(numA) && !isNaN(numB)) return numA - numB
return a.id.localeCompare(b.id)
})
} catch (err: any) {
if (err.code === 'ENOENT') return []
throw err
}
}
/** 列出所有任务(跨所有 task list) */
async listTasks(): Promise<TaskInfo[]> {
const taskLists = await this.listTaskLists()
const allTasks: TaskInfo[] = []
for (const list of taskLists) {
const tasks = await this.getTasksForList(list.id)
allTasks.push(...tasks)
}
return allTasks
}
/** 获取单个任务详情 */
async getTask(taskListId: string, taskId: string): Promise<TaskInfo | null> {
const tasks = await this.getTasksForList(taskListId)
return tasks.find((t) => t.id === taskId) || null
}
/** 解析单个任务文件 — 匹配 CLI V2 Task 格式 */
private parseTaskFile(data: any, taskListId: string): TaskInfo | null {
if (!data || typeof data !== 'object') return null
if (!data.id || !data.subject) return null
// Skip internal tasks
if (data.metadata?._internal) return null
return {
id: String(data.id),
subject: data.subject || '',
description: data.description || '',
activeForm: data.activeForm,
owner: data.owner,
status: (['pending', 'in_progress', 'completed'].includes(data.status)
? data.status
: 'pending') as TaskStatus,
blocks: Array.isArray(data.blocks) ? data.blocks : [],
blockedBy: Array.isArray(data.blockedBy) ? data.blockedBy : [],
metadata: data.metadata,
taskListId,
}
}
}
export const taskService = new TaskService()
|