langchain-agentx-python 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl

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.
Files changed (33) hide show
  1. langchain_agentx/loop/hook/registry.py +89 -1
  2. langchain_agentx/task_runtime/__init__.py +24 -0
  3. langchain_agentx/task_runtime/tasklist/__init__.py +48 -0
  4. langchain_agentx/task_runtime/tasklist/high_water_mark.py +220 -0
  5. langchain_agentx/task_runtime/tasklist/lock.py +113 -0
  6. langchain_agentx/task_runtime/tasklist/models.py +131 -0
  7. langchain_agentx/task_runtime/tasklist/path_resolver.py +113 -0
  8. langchain_agentx/task_runtime/tasklist/store.py +516 -0
  9. langchain_agentx/tool_runtime/loader.py +2 -0
  10. langchain_agentx/tools/task_create/__init__.py +26 -0
  11. langchain_agentx/tools/task_create/hooks.py +63 -0
  12. langchain_agentx/tools/task_create/limits.py +24 -0
  13. langchain_agentx/tools/task_create/models.py +72 -0
  14. langchain_agentx/tools/task_create/prompt.py +63 -0
  15. langchain_agentx/tools/task_create/tool.py +148 -0
  16. langchain_agentx/tools/task_get/__init__.py +9 -0
  17. langchain_agentx/tools/task_get/models.py +39 -0
  18. langchain_agentx/tools/task_get/prompt.py +43 -0
  19. langchain_agentx/tools/task_get/tool.py +142 -0
  20. langchain_agentx/tools/task_list/__init__.py +5 -0
  21. langchain_agentx/tools/task_list/models.py +66 -0
  22. langchain_agentx/tools/task_list/prompt.py +50 -0
  23. langchain_agentx/tools/task_list/tool.py +146 -0
  24. langchain_agentx/tools/task_update/__init__.py +9 -0
  25. langchain_agentx/tools/task_update/hooks.py +54 -0
  26. langchain_agentx/tools/task_update/models.py +71 -0
  27. langchain_agentx/tools/task_update/prompt.py +84 -0
  28. langchain_agentx/tools/task_update/tool.py +296 -0
  29. {langchain_agentx_python-0.2.1.dist-info → langchain_agentx_python-0.2.2.dist-info}/METADATA +1 -1
  30. {langchain_agentx_python-0.2.1.dist-info → langchain_agentx_python-0.2.2.dist-info}/RECORD +33 -8
  31. {langchain_agentx_python-0.2.1.dist-info → langchain_agentx_python-0.2.2.dist-info}/LICENSE +0 -0
  32. {langchain_agentx_python-0.2.1.dist-info → langchain_agentx_python-0.2.2.dist-info}/WHEEL +0 -0
  33. {langchain_agentx_python-0.2.1.dist-info → langchain_agentx_python-0.2.2.dist-info}/top_level.txt +0 -0
@@ -11,6 +11,8 @@ registry.py — HookRegistry 用户侧入口。
11
11
 
12
12
  当前裁剪范围:
13
13
  已支持 P1/P2 执行器注册(function/callback/command/http/prompt/agent)。
14
+ 已支持任务完成 hooks(register_task_completed/get_task_completed_hooks)。
15
+ 已支持任务创建 hooks(register_task_created/get_task_created_hooks),与 TaskCreateRuntimeTool 对齐。
14
16
  """
15
17
 
16
18
  from __future__ import annotations
@@ -21,6 +23,33 @@ from .config import HookSpec, HooksConfigSnapshot
21
23
  from .types import HookEvent
22
24
 
23
25
 
26
+ # ── 任务完成 Hook 类型定义 ────────────────────────────────────────
27
+
28
+ TaskCompletedHook = Callable[[str, str, str, str | None], str | None]
29
+ """
30
+ 任务完成 hook 类型。
31
+
32
+ Args:
33
+ task_id: 完成的任务 ID
34
+ subject: 任务标题
35
+ description: 任务描述
36
+ agent_id: 当前 agent ID(可能为 None)
37
+
38
+ Returns:
39
+ str | None: 阻塞性错误消息,如果有则阻止任务完成
40
+
41
+ 用法:
42
+ def my_hook(task_id: str, subject: str, description: str, agent_id: str | None) -> str | None:
43
+ if "critical" in subject:
44
+ return "Critical task cannot be marked completed without review"
45
+ return None
46
+
47
+ HookRegistry.register_task_completed(my_hook)
48
+ """
49
+
50
+ # task_id, subject, description, teammate_name, team_name → 阻塞错误信息或 None
51
+ TaskCreatedHook = Callable[[str, str, str, str | None, str | None], str | None]
52
+ """任务创建 hook(v1 同步);与 CC executeTaskCreatedHooks 阻塞语义对齐。"""
24
53
  class HookRegistry:
25
54
  """用户侧 Hook 注册入口。
26
55
 
@@ -232,6 +261,61 @@ class HookRegistry:
232
261
  def __len__(self) -> int:
233
262
  return len(self._specs)
234
263
 
264
+ # ── 任务完成 Hook API ────────────────────────────────────────────
265
+
266
+ # 类变量:存储所有任务完成 hooks
267
+ _task_completed_hooks: list[Callable] = []
268
+
269
+ @classmethod
270
+ def register_task_completed(cls, hook: Callable) -> None:
271
+ """注册任务完成 hook。
272
+
273
+ Args:
274
+ hook: 任务完成回调函数(TaskCompletedHook 类型)
275
+
276
+ 用法:
277
+ def my_hook(task_id: str, subject: str, description: str, agent_id: str | None) -> str | None:
278
+ if "critical" in subject:
279
+ return "Critical task cannot be marked completed"
280
+ return None
281
+
282
+ HookRegistry.register_task_completed(my_hook)
283
+ """
284
+ cls._task_completed_hooks.append(hook)
285
+
286
+ @classmethod
287
+ def get_task_completed_hooks(cls) -> list[Callable]:
288
+ """获取所有任务完成 hooks。
289
+
290
+ Returns:
291
+ list[Callable]: hooks 副本(避免外部修改内部列表)
292
+ """
293
+ return cls._task_completed_hooks.copy()
294
+
295
+ @classmethod
296
+ def clear_task_completed_hooks(cls) -> None:
297
+ """清空所有任务完成 hooks(主要用于测试)。"""
298
+ cls._task_completed_hooks.clear()
299
+
300
+ # ── 任务创建 Hook API(TaskCreateRuntimeTool)────────────────────────
301
+
302
+ _task_created_hooks: list[Callable] = []
303
+
304
+ @classmethod
305
+ def register_task_created(cls, hook: Callable) -> None:
306
+ """注册任务创建 hook(TaskCreatedHook 类型)。"""
307
+ cls._task_created_hooks.append(hook)
308
+
309
+ @classmethod
310
+ def get_task_created_hooks(cls) -> list[Callable]:
311
+ """获取所有任务创建 hooks 副本。"""
312
+ return cls._task_created_hooks.copy()
313
+
314
+ @classmethod
315
+ def clear_task_created_hooks(cls) -> None:
316
+ """清空所有任务创建 hooks(主要用于测试)。"""
317
+ cls._task_created_hooks.clear()
318
+
235
319
  def _make_spec(
236
320
  self,
237
321
  *,
@@ -259,4 +343,8 @@ class HookRegistry:
259
343
  return spec
260
344
 
261
345
 
262
- __all__ = ["HookRegistry"]
346
+ __all__ = [
347
+ "HookRegistry",
348
+ "TaskCompletedHook",
349
+ "TaskCreatedHook",
350
+ ]
@@ -3,6 +3,19 @@
3
3
  Shared task scheduling primitives for loop and tools.
4
4
  """
5
5
 
6
+ # CC 任务列表存储子系统(TaskListStatus:Todo JSON 状态字面量,勿与编排层 TaskStatus 枚举混淆)
7
+ from .tasklist import (
8
+ ClaimResult,
9
+ FileLockNotAvailableError,
10
+ Task,
11
+ TaskCreate,
12
+ TaskFilters,
13
+ TaskListStatus,
14
+ TaskStore,
15
+ TaskStoreLockManager,
16
+ TaskUpdate,
17
+ )
18
+
6
19
  from .core.interfaces import TaskExecutor
7
20
  from .orchestrator.runtime import DefaultTaskRuntime
8
21
  from .queue.in_memory import InMemoryTaskCommandQueue
@@ -61,6 +74,17 @@ from .policy import (
61
74
  )
62
75
 
63
76
  __all__ = [
77
+ # Tasklist (CC 任务列表)
78
+ "TaskStore",
79
+ "Task",
80
+ "TaskCreate",
81
+ "TaskUpdate",
82
+ "TaskFilters",
83
+ "TaskListStatus",
84
+ "ClaimResult",
85
+ "TaskStoreLockManager",
86
+ "FileLockNotAvailableError",
87
+ # Queued commands
64
88
  "QueuedCommandProvider",
65
89
  "encode_provider_batch_id",
66
90
  "decode_provider_batch_id",
@@ -0,0 +1,48 @@
1
+ """
2
+ task_runtime/tasklist/ — CC 任务列表存储子系统
3
+
4
+ 职责:
5
+ 实现 Claude Code 对等的任务列表管理能力:
6
+ - 任务持久化存储(文件系统,每个任务一个 JSON 文件)
7
+ - 任务依赖关系管理(blocks / blockedBy 双向维护)
8
+ - 高水位标记机制(防止删除后 ID 重用)
9
+ - 文件锁保护(多进程并发安全)
10
+ - 任务认领与状态流转
11
+
12
+ 与 CC 对应:
13
+ CC src/utils/tasks.ts(863 行)是任务存储核心,本模块与其对等。
14
+ """
15
+
16
+ from .models import (
17
+ MAX_ACTIVE_FORM_LENGTH,
18
+ MAX_DESCRIPTION_LENGTH,
19
+ MAX_SUBJECT_LENGTH,
20
+ ClaimResult,
21
+ Task,
22
+ TaskCreate,
23
+ TaskFilters,
24
+ TaskListStatus,
25
+ TaskStatus,
26
+ TaskUpdate,
27
+ TaskUpdateStatus,
28
+ )
29
+ from .store import TaskStore, TaskStoreSync
30
+ from .lock import FileLockNotAvailableError, TaskStoreLockManager
31
+
32
+ __all__ = [
33
+ "TaskStore",
34
+ "TaskStoreSync",
35
+ "Task",
36
+ "TaskCreate",
37
+ "TaskUpdate",
38
+ "TaskFilters",
39
+ "TaskStatus",
40
+ "TaskListStatus",
41
+ "TaskUpdateStatus",
42
+ "ClaimResult",
43
+ "TaskStoreLockManager",
44
+ "FileLockNotAvailableError",
45
+ "MAX_SUBJECT_LENGTH",
46
+ "MAX_DESCRIPTION_LENGTH",
47
+ "MAX_ACTIVE_FORM_LENGTH",
48
+ ]
@@ -0,0 +1,220 @@
1
+ """
2
+ task_runtime/tasklist/high_water_mark.py — 高水位标记管理
3
+
4
+ 职责:
5
+ 管理任务 ID 高水位标记,防止删除后 ID 重用:
6
+ - 读取当前高水位值
7
+ - 写入新的高水位值
8
+ - 扫描文件找出最大 ID
9
+
10
+ 链路位置:
11
+ TaskStore.create_task → HighWaterMarkManager.get_next_id
12
+
13
+ 与 CC 对应:
14
+ CC src/utils/tasks.ts 中 highWaterMark 相关函数。
15
+
16
+ 裁剪范围(v1):
17
+ - 文件锁由调用方(TaskStore)负责
18
+ - 降级策略:文件损坏时扫描目录
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+
28
+ class HighWaterMarkManager:
29
+ """高水位标记管理器。
30
+
31
+ 确保任务 ID 单调递增,删除任务后不会重用 ID。
32
+
33
+ 高水位文件路径:{tasks_dir}/.highwatermark
34
+ 文件内容:纯数字字符串,表示已分配的最大 ID。
35
+ """
36
+
37
+ def __init__(self, timeout: float = 3.0) -> None:
38
+ """初始化高水位标记管理器。
39
+
40
+ Args:
41
+ timeout: 文件操作超时(秒)
42
+ """
43
+ self.timeout = timeout
44
+
45
+ def _get_highwatermark_path(self, tasks_dir: Path) -> Path:
46
+ """获取高水位文件路径。"""
47
+ return tasks_dir / ".highwatermark"
48
+
49
+ async def read(self, tasks_dir: Path) -> int:
50
+ """读取当前高水位值。
51
+
52
+ Args:
53
+ tasks_dir: 任务目录
54
+
55
+ Returns:
56
+ 当前高水位值,文件不存在或损坏时返回 0
57
+ """
58
+ hw_path = self._get_highwatermark_path(tasks_dir)
59
+
60
+ try:
61
+ content = await asyncio.to_thread(hw_path.read_text, encoding="utf-8")
62
+ return int(content.strip())
63
+ except (FileNotFoundError, ValueError, OSError):
64
+ return 0
65
+
66
+ async def write(self, tasks_dir: Path, value: int) -> None:
67
+ """写入新的高水位值。
68
+
69
+ Args:
70
+ tasks_dir: 任务目录
71
+ value: 新的高水位值
72
+ """
73
+ hw_path = self._get_highwatermark_path(tasks_dir)
74
+
75
+ # 确保目录存在
76
+ await asyncio.to_thread(tasks_dir.mkdir, parents=True, exist_ok=True)
77
+
78
+ # 原子写入:先写临时文件,再重命名
79
+ temp_path = hw_path.with_suffix(".tmp")
80
+ content = str(value)
81
+
82
+ await asyncio.to_thread(temp_path.write_text, content, encoding="utf-8")
83
+
84
+ # Windows 需要 replace,Posix rename 可原子替换
85
+ try:
86
+ await asyncio.to_thread(temp_path.replace, hw_path)
87
+ except OSError:
88
+ # 降级:删除后重命名
89
+ if hw_path.exists():
90
+ await asyncio.to_thread(hw_path.unlink)
91
+ await asyncio.to_thread(temp_path.rename, hw_path)
92
+
93
+ async def find_highest_id(self, tasks_dir: Path) -> int:
94
+ """扫描任务文件找出最大 ID。
95
+
96
+ 作为高水位文件的降级策略。
97
+
98
+ Args:
99
+ tasks_dir: 任务目录
100
+
101
+ Returns:
102
+ 找到的最大 ID,目录不存在或无任务时返回 0
103
+ """
104
+ if not await asyncio.to_thread(tasks_dir.exists):
105
+ return 0
106
+
107
+ max_id = 0
108
+
109
+ try:
110
+ for entry in await asyncio.to_thread(lambda: list(tasks_dir.iterdir())):
111
+ if entry.suffix == ".json":
112
+ # 从文件名提取 ID(如 "123.json" -> 123)
113
+ try:
114
+ task_id = int(entry.stem)
115
+ max_id = max(max_id, task_id)
116
+ except ValueError:
117
+ continue
118
+ except OSError:
119
+ pass
120
+
121
+ return max_id
122
+
123
+ async def get_next_id(self, tasks_dir: Path) -> int:
124
+ """获取下一个可用的任务 ID。
125
+
126
+ Args:
127
+ tasks_dir: 任务目录
128
+
129
+ Returns:
130
+ 下一个 ID(max(高水位文件, 文件中最大ID) + 1)
131
+ """
132
+ hw_value = await self.read(tasks_dir)
133
+ file_max = await self.find_highest_id(tasks_dir)
134
+
135
+ current = max(hw_value, file_max)
136
+ next_id = current + 1
137
+
138
+ # 更新高水位
139
+ await self.write(tasks_dir, next_id)
140
+
141
+ return next_id
142
+
143
+ # ------------------------------------------------------------------
144
+ # 同步版本(用于文件锁保护下的同步上下文)
145
+ # ------------------------------------------------------------------
146
+
147
+ def _read_sync(self, tasks_dir: Path) -> int:
148
+ """同步读取当前高水位值。"""
149
+ hw_path = self._get_highwatermark_path(tasks_dir)
150
+
151
+ try:
152
+ content = hw_path.read_text(encoding="utf-8")
153
+ return int(content.strip())
154
+ except (FileNotFoundError, ValueError, OSError):
155
+ return 0
156
+
157
+ def _write_sync(self, tasks_dir: Path, value: int) -> None:
158
+ """同步写入新的高水位值。"""
159
+ hw_path = self._get_highwatermark_path(tasks_dir)
160
+
161
+ # 确保目录存在
162
+ tasks_dir.mkdir(parents=True, exist_ok=True)
163
+
164
+ # 原子写入:先写临时文件,再重命名
165
+ temp_path = hw_path.with_suffix(".tmp")
166
+ content = str(value)
167
+
168
+ temp_path.write_text(content, encoding="utf-8")
169
+
170
+ # Windows 需要 replace,Posix rename 可原子替换
171
+ try:
172
+ temp_path.replace(hw_path)
173
+ except OSError:
174
+ # 降级:删除后重命名
175
+ if hw_path.exists():
176
+ hw_path.unlink()
177
+ temp_path.rename(hw_path)
178
+
179
+ def _find_highest_id_sync(self, tasks_dir: Path) -> int:
180
+ """同步扫描任务文件找出最大 ID。"""
181
+ if not tasks_dir.exists():
182
+ return 0
183
+
184
+ max_id = 0
185
+
186
+ try:
187
+ for entry in list(tasks_dir.iterdir()):
188
+ if entry.suffix == ".json":
189
+ # 从文件名提取 ID(如 "123.json" -> 123)
190
+ try:
191
+ task_id = int(entry.stem)
192
+ max_id = max(max_id, task_id)
193
+ except ValueError:
194
+ continue
195
+ except OSError:
196
+ pass
197
+
198
+ return max_id
199
+
200
+ def get_next_id_sync(self, tasks_dir: Path) -> int:
201
+ """同步获取下一个可用的任务 ID。
202
+
203
+ 用于文件锁保护下的同步上下文。
204
+
205
+ Args:
206
+ tasks_dir: 任务目录
207
+
208
+ Returns:
209
+ 下一个 ID(max(高水位文件, 文件中最大ID) + 1)
210
+ """
211
+ hw_value = self._read_sync(tasks_dir)
212
+ file_max = self._find_highest_id_sync(tasks_dir)
213
+
214
+ current = max(hw_value, file_max)
215
+ next_id = current + 1
216
+
217
+ # 更新高水位
218
+ self._write_sync(tasks_dir, next_id)
219
+
220
+ return next_id
@@ -0,0 +1,113 @@
1
+ """
2
+ task_runtime/tasklist/lock.py — 任务存储文件锁管理
3
+
4
+ 职责:
5
+ 提供文件锁保护,确保多进程并发安全:
6
+ - 单个任务文件锁
7
+ - 任务列表目录锁
8
+ - 软依赖处理(filelock 不存在时友好错误)
9
+
10
+ 链路位置:
11
+ TaskStore → TaskStoreLockManager → filelock.FileLock
12
+
13
+ 与 CC 对应:
14
+ CC src/utils/tasks.ts 中 withFileLock 函数。
15
+
16
+ 裁剪范围(v1):
17
+ - 使用 filelock 库作为实现
18
+ - 默认超时 3.0 秒
19
+ - 错误信息包含锁文件路径
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from pathlib import Path
25
+ from typing import TYPE_CHECKING
26
+
27
+ if TYPE_CHECKING:
28
+ from filelock import FileLock
29
+
30
+
31
+ class FileLockNotAvailableError(RuntimeError):
32
+ """filelock 库未安装错误。"""
33
+
34
+ def __init__(self) -> None:
35
+ super().__init__(
36
+ "filelock library is required for task store. "
37
+ "Install with: pip install filelock"
38
+ )
39
+
40
+
41
+ class TaskStoreLockTimeoutError(TimeoutError):
42
+ """获取文件锁超时错误。"""
43
+
44
+ def __init__(self, lock_path: Path, timeout: float) -> None:
45
+ super().__init__(
46
+ f"Timeout waiting for lock after {timeout}s: {lock_path}. "
47
+ "Another process may be holding the lock."
48
+ )
49
+ self.lock_path = lock_path
50
+ self.timeout = timeout
51
+
52
+
53
+ def _ensure_filelock() -> type[FileLock]:
54
+ """确保 filelock 库可用。"""
55
+ try:
56
+ from filelock import FileLock as FL
57
+ return FL # type: ignore
58
+ except ImportError as e:
59
+ raise FileLockNotAvailableError() from e
60
+
61
+
62
+ class TaskStoreLockManager:
63
+ """任务存储文件锁管理器。
64
+
65
+ 为任务存储提供文件锁保护,防止多进程并发冲突。
66
+
67
+ 锁文件路径:
68
+ - 任务锁:{task_path}.lock
69
+ - 目录锁:{tasks_dir}/.lock
70
+ """
71
+
72
+ def __init__(self, timeout: float = 3.0) -> None:
73
+ """初始化锁管理器。
74
+
75
+ Args:
76
+ timeout: 锁超时时间(秒),默认 3.0
77
+ """
78
+ self.timeout = timeout
79
+ self._filelock_class: type[FileLock] | None = None
80
+
81
+ def _get_filelock_class(self) -> type[FileLock]:
82
+ """延迟导入 FileLock 类(软依赖)。"""
83
+ if self._filelock_class is None:
84
+ self._filelock_class = _ensure_filelock()
85
+ return self._filelock_class
86
+
87
+ def get_lock(self, task_path: Path) -> "FileLock":
88
+ """获取单个任务的文件锁。
89
+
90
+ Args:
91
+ task_path: 任务文件路径
92
+
93
+ Returns:
94
+ FileLock 实例
95
+ """
96
+ lock_path = task_path.with_suffix(task_path.suffix + ".lock")
97
+ FileLock = self._get_filelock_class()
98
+ return FileLock(lock_path, timeout=self.timeout)
99
+
100
+ def get_list_lock(self, tasks_dir: Path) -> "FileLock":
101
+ """获取任务列表目录锁。
102
+
103
+ 用于原子性操作(如认领任务、高水位更新)。
104
+
105
+ Args:
106
+ tasks_dir: 任务目录
107
+
108
+ Returns:
109
+ FileLock 实例
110
+ """
111
+ lock_path = tasks_dir / ".lock"
112
+ FileLock = self._get_filelock_class()
113
+ return FileLock(lock_path, timeout=self.timeout)
@@ -0,0 +1,131 @@
1
+ """
2
+ task_runtime/tasklist/models.py — CC 任务列表数据模型
3
+
4
+ 职责:
5
+ 定义 Claude Code 对等的任务列表数据契约:
6
+ - TaskStatus:任务状态(pending/in_progress/committed)
7
+ - Task:任务完整数据结构
8
+ - TaskCreate:创建任务输入(不含自动生成字段)
9
+ - TaskUpdate:更新任务输入(全部可选)
10
+ - TaskFilters:列表过滤条件
11
+ - ClaimResult:任务认领结果
12
+
13
+ 链路位置:
14
+ TaskListRuntimeTool/TaskGetRuntimeTool → TaskStore → models
15
+
16
+ 与 CC 对应:
17
+ CC src/utils/tasks.ts 中 Task 接口、TaskStatus、ClaimResult 等。
18
+
19
+ 裁剪范围(v1):
20
+ - 基础字段:id, subject, description, status, blocks, blockedBy
21
+ - metadata 支持 _internal 标记
22
+ - owner 字段预留(v1 简化,仅存储不校验)
23
+ - active_form 用于 UI 显示
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from dataclasses import dataclass, field
29
+ from typing import Any, Literal, Optional
30
+
31
+ TaskStatus = Literal["pending", "in_progress", "completed"]
32
+
33
+ # 与 TaskStatus 同一对象;供 langchain_agentx.task_runtime 顶层导出为 TaskListStatus,
34
+ # 以区别于编排层 core.types.TaskStatus 枚举。
35
+ TaskListStatus = TaskStatus
36
+
37
+ # 任务更新状态(包含 deleted,用于 TaskUpdate 工具)
38
+ TaskUpdateStatus = Literal["pending", "in_progress", "completed", "deleted"]
39
+
40
+ # 字段长度限制常量(基于用户体验与 CC 实践)
41
+ MAX_SUBJECT_LENGTH: int = 120
42
+ MAX_DESCRIPTION_LENGTH: int = 5000
43
+ MAX_ACTIVE_FORM_LENGTH: int = 100
44
+
45
+
46
+ @dataclass
47
+ class Task:
48
+ """任务完整数据结构。
49
+
50
+ 对应 CC Task 接口,每个任务对应一个 JSON 文件。
51
+
52
+ 字段说明:
53
+ id: 任务唯一 ID(数字字符串,高水位标记生成)
54
+ subject: 任务简短标题
55
+ description: 任务详细描述
56
+ active_form: 进行中时显示的文本(如 "Fixing authentication bug")
57
+ owner: 任务所有者(Agent ID 或 None)
58
+ status: 任务状态
59
+ blocks: 此任务阻塞的任务 ID 列表
60
+ blocked_by: 阻塞此任务的任务 ID 列表
61
+ metadata: 附加元数据(_internal=True 表示内部任务)
62
+ """
63
+
64
+ id: str
65
+ subject: str
66
+ description: str
67
+ active_form: Optional[str] = None
68
+ owner: Optional[str] = None
69
+ status: TaskStatus = "pending"
70
+ blocks: list[str] = field(default_factory=list)
71
+ blocked_by: list[str] = field(default_factory=list)
72
+ metadata: dict[str, Any] = field(default_factory=dict)
73
+
74
+ def is_internal(self) -> bool:
75
+ """判断是否为内部任务(不在 UI 显示)。"""
76
+ return self.metadata.get("_internal", False)
77
+
78
+
79
+ @dataclass
80
+ class TaskCreate:
81
+ """创建任务输入模型。
82
+
83
+ 不含自动生成字段(id、默认字段)。
84
+ """
85
+
86
+ subject: str
87
+ description: str
88
+ active_form: Optional[str] = None
89
+ owner: Optional[str] = None
90
+ status: TaskStatus = "pending"
91
+ blocks: list[str] = field(default_factory=list)
92
+ blocked_by: list[str] = field(default_factory=list)
93
+ metadata: dict[str, Any] = field(default_factory=dict)
94
+
95
+
96
+ @dataclass
97
+ class TaskUpdate:
98
+ """更新任务输入模型。
99
+
100
+ 全部可选,仅更新提供的字段。
101
+ """
102
+
103
+ subject: Optional[str] = None
104
+ description: Optional[str] = None
105
+ active_form: Optional[str] = None
106
+ owner: Optional[str] = None
107
+ status: Optional[TaskStatus] = None
108
+ blocks: Optional[list[str]] = None
109
+ blocked_by: Optional[list[str]] = None
110
+ metadata: Optional[dict[str, Any]] = None
111
+
112
+
113
+ @dataclass
114
+ class TaskFilters:
115
+ """任务列表过滤条件。"""
116
+
117
+ status: Optional[TaskStatus] = None
118
+ owner: Optional[str] = None
119
+ exclude_internal: bool = True
120
+
121
+
122
+ @dataclass
123
+ class ClaimResult:
124
+ """任务认领结果。
125
+
126
+ 对应 CC ClaimResult 接口。
127
+ """
128
+
129
+ success: bool
130
+ reason: Optional[str] = None
131
+ task: Optional[Task] = None