/// FIST-Mbt core: task model & state machine.
/// 迁移自 FIST(Python) 的状态机与分层递归理念(七条金条第 4 条:唯一指挥官)。
///|
/// 任务七态状态机
pub enum TaskStatus {
/// 待领取
Pending
/// 已领取
Claimed
/// 拆分中
Splitting
/// 执行中
Executing
/// 待验收
Reviewing
/// 已完成
Completed
/// 已归档
Archived
/// 已打回(验收不通过,需重做)
Rejected
/// 已暂停(外部中断/等待依赖)
Paused
} derive(Eq, Debug)
///|
pub fn TaskStatus::to_string(self : TaskStatus) -> String {
match self {
Pending => "待领取"
Claimed => "已领取"
Splitting => "拆分中"
Executing => "执行中"
Reviewing => "待验收"
Completed => "已完成"
Archived => "已归档"
Rejected => "已打回"
Paused => "已暂停"
}
}
///|
pub fn TaskStatus::from_string(s : String) -> TaskStatus? {
match s {
"待领取" => Some(Pending)
"已领取" => Some(Claimed)
"拆分中" => Some(Splitting)
"执行中" => Some(Executing)
"待验收" => Some(Reviewing)
"已完成" => Some(Completed)
"已归档" => Some(Archived)
"已打回" => Some(Rejected)
"已暂停" => Some(Paused)
_ => None
}
}
/// 排序权重(用于统计/展示)
///|
/// 状态谓词(供跨包业务层判断,避免直接构造变体)
pub fn TaskStatus::can_split(self : TaskStatus) -> Bool {
match self {
Pending | Claimed | Splitting | Rejected => true
_ => false
}
}
///|
/// execute 的前态谓词(engine.execute 的第一道门)。
/// BUG-2 相邻加固(守卫一致性):这里曾多出一条 `Rejected`,而 Task::execute 并不接受
/// 已打回——两条门不一致时,调用方拿到的是引擎的"非法执行: 任务处于 [已打回]"(无出路),
/// 而不是域迁移里带 retry 出路的文案。集合与 Task::execute 逐字对齐(对外结果不变:
/// 两条门对 Rejected 都是拒,只是文案统一)。
pub fn TaskStatus::can_execute(self : TaskStatus) -> Bool {
match self {
Claimed | Splitting | Executing => true
_ => false
}
}
///|
pub fn TaskStatus::is_rejected(self : TaskStatus) -> Bool {
self == Rejected
}
///|
pub fn TaskStatus::is_paused(self : TaskStatus) -> Bool {
self == Paused
}
///|
pub fn TaskStatus::is_active(self : TaskStatus) -> Bool {
match self {
Pending | Claimed | Splitting | Executing | Reviewing => true
_ => false
}
}
///|
pub fn TaskStatus::is_completed(self : TaskStatus) -> Bool {
self == Completed
}
///|
pub fn TaskStatus::is_pending(self : TaskStatus) -> Bool {
self == Pending
}
///|
pub fn TaskStatus::is_claimed(self : TaskStatus) -> Bool {
self == Claimed
}
///|
pub fn TaskStatus::is_splitting(self : TaskStatus) -> Bool {
self == Splitting
}
///|
pub fn TaskStatus::is_executing(self : TaskStatus) -> Bool {
self == Executing
}
///|
pub fn TaskStatus::is_reviewing(self : TaskStatus) -> Bool {
self == Reviewing
}
///|
pub fn TaskStatus::is_archived(self : TaskStatus) -> Bool {
self == Archived
}
///|
pub fn TaskStatus::order(self : TaskStatus) -> Int {
match self {
Pending => 0
Claimed => 1
Splitting => 2
Executing => 3
Reviewing => 4
Completed => 5
Archived => 6
Rejected => 7
Paused => 8
}
}
///|
/// FIST-Mbt 任务实体
pub struct Task {
/// 形如 T0 / T0.1 / T0.1.2 的层级 id
id : String
parent_id : String?
project_dir : String
/// 命名空间(多租户隔离键,默认 "default")
ns : String
priority : String
importance : String
/// K 值:根=3,每拆一层减 1;K<=1 为原子任务
depth : Int
/// 拆分数量(默认 3)
split_n : Int
status : TaskStatus
assignee : String?
description : String
deliverable : String
created_at : String
updated_at : String
completed_by : String?
cleanup_mode : String
/// 依赖的任务 id 列表(这些任务完成后,本任务才可领取)
depends_on : Array[String]
} derive(Eq, Debug)
///|
/// 便捷状态访问
pub fn Task::status_to_string(self : Task) -> String {
self.status.to_string()
}
///|
pub fn Task::get_id(self : Task) -> String {
self.id
}
///|
pub fn Task::get_parent(self : Task) -> String? {
self.parent_id
}
///|
pub fn Task::get_status(self : Task) -> TaskStatus {
self.status
}
///|
pub fn Task::get_depth(self : Task) -> Int {
self.depth
}
///|
pub fn Task::get_description(self : Task) -> String {
self.description
}
///|
pub fn Task::get_assignee(self : Task) -> String? {
self.assignee
}
///|
pub fn Task::get_updated_at(self : Task) -> String {
self.updated_at
}
///|
pub fn Task::is_leaf(self : Task) -> Bool {
self.depth <= 1
}
///|
/// 构造器(字段带默认值)
pub fn Task::new(
id~ : String,
project_dir~ : String,
description? : String = "",
priority? : String = "中",
importance? : String = "中",
depth? : Int = 3,
split_n? : Int = 3,
parent_id? : String = "",
assignee? : String = "",
cleanup_mode? : String = "deferred",
created_at? : String = "",
ns? : String = "default",
depends_on? : Array[String] = [],
) -> Task {
{
id,
parent_id: if parent_id == "" {
None
} else {
Some(parent_id)
},
project_dir,
ns,
priority,
importance,
depth,
split_n,
status: Pending,
assignee: if assignee == "" {
None
} else {
Some(assignee)
},
description,
deliverable: "",
created_at,
updated_at: created_at,
completed_by: None,
cleanup_mode,
depends_on,
}
}
///|
/// 按存储行字段直接构建任务(供 store 层反序列化使用;status 为存储字符串,非法回退 Pending)
pub fn Task::from_db(
id : String,
parent_id : String?,
project_dir : String,
ns : String,
priority : String,
importance : String,
depth : Int,
split_n : Int,
status_s : String,
assignee : String?,
description : String,
deliverable : String,
created_at : String,
updated_at : String,
completed_by : String?,
cleanup_mode : String,
depends_on : Array[String],
) -> Task {
let status = match TaskStatus::from_string(status_s) {
Some(s) => s
None => Pending
}
{
id,
parent_id,
project_dir,
ns,
priority,
importance,
depth,
split_n,
status,
assignee,
description,
deliverable,
created_at,
updated_at,
completed_by,
cleanup_mode,
depends_on,
}
}
/// —— 状态迁移 ——
/// 每个迁移都是纯函数(返回新 Task),状态机非法流转直接拒绝。
/// 返回 Err(msg) 表示非法迁移。
///|
/// 待领取 -> 已领取(角色:any active agent)
pub fn Task::claim(self : Task, assignee~ : String) -> Result[Task, String] {
match self.status {
Pending =>
if assignee == "" {
Err("claim 需要 assignee")
} else {
Ok({ ..self, status: Claimed, assignee: Some(assignee), })
}
_ =>
Err(
"非法迁移: claim 要求状态 [待领取],当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 已领取 -> 拆分中(指挥官拆解)
pub fn Task::split(self : Task) -> Result[Task, String] {
match self.status {
Claimed =>
if self.is_leaf() {
Err("原子任务(K<=1)不可再拆分")
} else {
Ok({ ..self, status: Splitting, })
}
_ =>
Err(
"非法迁移: split 要求状态 [已领取],当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 拆分中标记(AO 式拆解用,宽于 split:待领取/已领取/拆分中均可转入)
pub fn Task::mark_decomposing(self : Task) -> Result[Task, String] {
match self.status {
Pending | Claimed | Splitting => Ok({ ..self, status: Splitting, })
_ => Err("非法拆解: 任务处于 [\{self.status.to_string()}]")
}
}
///|
/// 拆分中/已领取/执行中 -> 执行中(进入/覆盖/追加交付物)
/// 执行中 -> execute = 覆盖/追加交付物(retry 后原地更新交付物的直觉行为)
/// BUG-2(Round 2 收口):合法前态含「执行中」。retry 的落点就是执行中,若这里不收,
/// 「被打回重做」这条最该更新交付物的路径会写不进 deliverable(omega_result_verify
/// 随即报「尚无交付物」),诱导操作者新建任务覆盖旧账、破坏 append-only 审计。
/// 选择放宽 execute 而不改 retry 的落点:retry→执行中 是**对外承诺的语义**
/// (AGENTS.md 工具表「打回后重试(→执行中)」、已发布流水线的状态判据都按它写),
/// 改它等于改一条公共状态机迁移;而「执行中重复 execute」本就是幂等追加,影响面更小。
/// 错误文案必须给**出路**(当前状态 → 该走哪条迁移),否则调用方只能猜或另起任务。
pub fn Task::execute(self : Task) -> Result[Task, String] {
match self.status {
Splitting | Claimed | Executing => Ok({ ..self, status: Executing, })
_ =>
Err(
"非法迁移: execute 要求状态 [拆分中/已领取/执行中],当前是 [" +
self.status.to_string() +
"]。出路:" +
Task::execute_exit_hint(self.status),
)
}
}
///|
/// execute 被拒时按当前状态给出可用出路(BUG-2 文案,纯函数便于回归测试)。
pub fn Task::execute_exit_hint(status : TaskStatus) -> String {
match status {
Pending => "待领取→先 claim 再 execute"
Claimed | Splitting | Executing => "已是可执行状态,直接 execute"
Rejected =>
"已打回→先 retry(→执行中)再 execute 覆盖交付物"
Reviewing =>
"待验收→若要改交付物先 reject(→已打回)再 retry(→执行中),再 execute"
Paused => "已暂停→先 resume(→已领取)再 execute"
Completed =>
"已完成→原地更正交付物请 reopen_task(→待领取)后重新走链路,勿新建任务覆盖旧账"
Archived =>
"已归档→不可写;确需返工先 reopen_task 重开,勿新建任务覆盖旧账"
}
}
///|
/// 执行中 -> 待验收
pub fn Task::submit(self : Task) -> Result[Task, String] {
match self.status {
Executing => Ok({ ..self, status: Reviewing, })
_ =>
Err(
"非法迁移: submit 要求状态 [执行中],当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 待验收 -> 已完成(人类主权:completed_by 必须 human / human_steward 或明确授权)
/// 注意:此处仅校验 completed_by 非空;不引入独立的强身份认证模型(鉴权属上层
/// MCP 工具/MCP 层职责,本域不做权宜式身份伪造防护,避免脆弱的新鉴权模型)。
pub fn Task::complete(
self : Task,
completed_by~ : String,
) -> Result[Task, String] {
match self.status {
Reviewing =>
if completed_by == "" {
Err("complete 需要 completed_by 身份")
} else {
Ok({ ..self, status: Completed, completed_by: Some(completed_by), })
}
_ =>
Err(
"非法迁移: complete 要求状态 [待验收],当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 已完成 -> 已归档
pub fn Task::archive(self : Task) -> Result[Task, String] {
match self.status {
Completed => Ok({ ..self, status: Archived, })
_ =>
Err(
"非法迁移: archive 要求状态 [已完成],当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 任意非归档状态 -> 已领取(被打回重做)
pub fn Task::reopen(self : Task) -> Result[Task, String] {
match self.status {
Archived => Err("已归档任务不可 reopen,请重新发布")
Rejected =>
Ok({ ..self, status: Claimed, assignee: None, deliverable: "", })
Paused => Ok({ ..self, status: Claimed, })
_ => Ok({ ..self, status: Claimed, })
}
}
///|
/// 待验收 -> 已打回(验收不通过,需重做)
pub fn Task::reject(self : Task, reason? : String = "") -> Result[Task, String] {
match self.status {
Reviewing =>
Ok({
..self,
status: Rejected,
description: if reason == "" {
self.description
} else {
self.description + " [打回原因: " + reason + "]"
},
})
_ =>
Err(
"非法迁移: reject 要求状态 [待验收],当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 已打回 -> 执行中(重新执行)
pub fn Task::retry(self : Task) -> Result[Task, String] {
match self.status {
Rejected => Ok({ ..self, status: Executing, })
_ =>
Err(
"非法迁移: retry 要求状态 [已打回],当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 任意活跃状态 -> 已暂停(外部中断/等待依赖)
pub fn Task::pause(self : Task) -> Result[Task, String] {
match self.status {
Pending | Claimed | Splitting | Executing | Reviewing | Rejected =>
Ok({ ..self, status: Paused, })
Paused => Err("任务已处于暂停状态")
_ =>
Err(
"非法迁移: pause 要求活跃状态,当前是 [" +
self.status.to_string() +
"]",
)
}
}
///|
/// 已暂停 -> 已领取(恢复执行)
pub fn Task::resume_task(self : Task) -> Result[Task, String] {
match self.status {
Paused => Ok({ ..self, status: Claimed, })
_ =>
Err(
"非法迁移: resume_task 要求状态 [已暂停],当前是 [" +
self.status.to_string() +
"]",
)
}
}
/// 生成子任务 id:T0.1.2 -> T0.1.2.1
///|
/// 记录类复制更新(跨包不便直接构造 record,由 core 提供)
pub fn Task::with_updated_at(self : Task, now : String) -> Task {
{ ..self, updated_at: now, }
}
///|
pub fn Task::with_deliverable(
self : Task,
deliverable : String,
now : String,
) -> Task {
{ ..self, deliverable, updated_at: now, }
}
///|
/// 追加一条前置依赖(去重)并更新时间戳;返回带新 depends_on 的副本。
/// 供显式 DAG 构建(dag_depend)使用——不只靠 plan_deep 的隐式父子关系。
pub fn Task::with_depends_on(
self : Task,
dep_id : String,
now : String,
) -> Task {
let deps : Array[String] = if self.depends_on.contains(dep_id) {
self.depends_on
} else {
// 拷贝原依赖数组并追加(Array 可变容器,push 就地生效)
let out : Array[String] = []
for d in self.depends_on {
out.push(d)
}
out.push(dep_id)
out
}
{ ..self, depends_on: deps, updated_at: now, }
}
///|
pub fn child_id(parent_id~ : String, idx~ : Int) -> String {
"\{parent_id}.\{idx}"
}
///|
/// 任务转 JSON(供 MCP resource / tool 输出)
pub fn Task::to_json(self : Task) -> Json {
let m : Map[String, Json] = Map([])
m.set("id", Json::string(self.id))
match self.parent_id {
Some(p) => m.set("parent_id", Json::string(p))
None => m.set("parent_id", Json::null())
}
m.set("project_dir", Json::string(self.project_dir))
m.set("namespace", Json::string(self.ns))
m.set("priority", Json::string(self.priority))
m.set("importance", Json::string(self.importance))
m.set("depth", Json::number(self.depth.to_double()))
m.set("split_n", Json::number(self.split_n.to_double()))
m.set("status", Json::string(self.status.to_string()))
match self.assignee {
Some(a) => m.set("assignee", Json::string(a))
None => m.set("assignee", Json::null())
}
m.set("description", Json::string(self.description))
m.set("deliverable", Json::string(self.deliverable))
m.set("created_at", Json::string(self.created_at))
m.set("updated_at", Json::string(self.updated_at))
match self.completed_by {
Some(c) => m.set("completed_by", Json::string(c))
None => m.set("completed_by", Json::null())
}
m.set("cleanup_mode", Json::string(self.cleanup_mode))
m.set(
"depends_on",
Json::array(self.depends_on.map(fn(d) { Json::string(d) })),
)
m.set("is_leaf", Json::boolean(self.is_leaf()))
Json::object(m)
}