@zmzai/agent-framework 0.5.2 → 0.8.0
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.
- package/dist/core/events/bus.js +3 -3
- package/dist/core/events/bus.js.map +1 -1
- package/dist/core/events/manifest.d.ts +160 -0
- package/dist/core/events/manifest.d.ts.map +1 -1
- package/dist/core/events/manifest.js +91 -0
- package/dist/core/events/manifest.js.map +1 -1
- package/dist/core/events/sqlite-event-log.js +3 -3
- package/dist/core/events/sqlite-event-log.js.map +1 -1
- package/dist/core/runtime/attachments.d.ts +110 -0
- package/dist/core/runtime/attachments.d.ts.map +1 -1
- package/dist/core/runtime/attachments.js +124 -0
- package/dist/core/runtime/attachments.js.map +1 -1
- package/dist/core/runtime/extraction.d.ts +117 -0
- package/dist/core/runtime/extraction.d.ts.map +1 -0
- package/dist/core/runtime/extraction.js +256 -0
- package/dist/core/runtime/extraction.js.map +1 -0
- package/dist/core/runtime/lease-recovery.d.ts.map +1 -1
- package/dist/core/runtime/lease-recovery.js +50 -0
- package/dist/core/runtime/lease-recovery.js.map +1 -1
- package/dist/core/runtime/pi-bridge.d.ts +1 -1
- package/dist/core/runtime/pi-bridge.d.ts.map +1 -1
- package/dist/core/runtime/pi-bridge.js +19 -1
- package/dist/core/runtime/pi-bridge.js.map +1 -1
- package/dist/core/runtime/runner.d.ts +174 -0
- package/dist/core/runtime/runner.d.ts.map +1 -1
- package/dist/core/runtime/runner.js +741 -34
- package/dist/core/runtime/runner.js.map +1 -1
- package/dist/core/session/jsonl-store.d.ts.map +1 -1
- package/dist/core/session/jsonl-store.js +77 -0
- package/dist/core/session/jsonl-store.js.map +1 -1
- package/dist/core/session/sqlite-store.d.ts.map +1 -1
- package/dist/core/session/sqlite-store.js +87 -3
- package/dist/core/session/sqlite-store.js.map +1 -1
- package/dist/core/session/store.d.ts +6 -0
- package/dist/core/session/store.d.ts.map +1 -1
- package/dist/core/session/types.d.ts +30 -13
- package/dist/core/session/types.d.ts.map +1 -1
- package/dist/core/session/workflow.d.ts +30 -1
- package/dist/core/session/workflow.d.ts.map +1 -1
- package/dist/core/session/workflow.js.map +1 -1
- package/dist/core/task/completion.d.ts +135 -0
- package/dist/core/task/completion.d.ts.map +1 -0
- package/dist/core/task/completion.js +239 -0
- package/dist/core/task/completion.js.map +1 -0
- package/dist/core/task/contract.d.ts +20 -0
- package/dist/core/task/contract.d.ts.map +1 -0
- package/dist/core/task/contract.js +89 -0
- package/dist/core/task/contract.js.map +1 -0
- package/dist/core/task/index.d.ts +20 -0
- package/dist/core/task/index.d.ts.map +1 -0
- package/dist/core/task/index.js +16 -0
- package/dist/core/task/index.js.map +1 -0
- package/dist/core/task/plan.d.ts +91 -0
- package/dist/core/task/plan.d.ts.map +1 -0
- package/dist/core/task/plan.js +185 -0
- package/dist/core/task/plan.js.map +1 -0
- package/dist/core/task/progress.d.ts +43 -0
- package/dist/core/task/progress.d.ts.map +1 -0
- package/dist/core/task/progress.js +66 -0
- package/dist/core/task/progress.js.map +1 -0
- package/dist/core/task/store.d.ts +36 -0
- package/dist/core/task/store.d.ts.map +1 -0
- package/dist/core/task/store.js +104 -0
- package/dist/core/task/store.js.map +1 -0
- package/dist/core/task/types.d.ts +133 -0
- package/dist/core/task/types.d.ts.map +1 -0
- package/dist/core/task/types.js +26 -0
- package/dist/core/task/types.js.map +1 -0
- package/dist/core/tools/attachments.d.ts +4 -0
- package/dist/core/tools/attachments.d.ts.map +1 -0
- package/dist/core/tools/attachments.js +195 -0
- package/dist/core/tools/attachments.js.map +1 -0
- package/dist/core/tools/builtins.d.ts.map +1 -1
- package/dist/core/tools/builtins.js +2 -1
- package/dist/core/tools/builtins.js.map +1 -1
- package/dist/core/tools/task-block.d.ts +44 -0
- package/dist/core/tools/task-block.d.ts.map +1 -0
- package/dist/core/tools/task-block.js +76 -0
- package/dist/core/tools/task-block.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -12
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { newEvidenceId, } from "./types.js";
|
|
3
|
+
/** 运行时事实 → 任务契约的投影(规格 3 §6「`todo.updated` 可暂时作为步骤投影,
|
|
4
|
+
* 但不能继续作为唯一的任务来源」)。
|
|
5
|
+
*
|
|
6
|
+
* 分工:
|
|
7
|
+
* - 模型通过 `todo` 工具表达**计划**;这里把它投影成 `TaskRecord.steps`,
|
|
8
|
+
* 但 TaskRecord 是权威——todo 事件没了、乱序了、被压缩掉了,任务仍完整。
|
|
9
|
+
* - 工具调用结果由运行时(runner)观测后投影成 `TaskEvidence`。**模型不能
|
|
10
|
+
* 自己声明证据**,这也是「可信完成」里「可信」二字的落点。
|
|
11
|
+
*
|
|
12
|
+
* 本模块同样是纯函数,便于单测。 */
|
|
13
|
+
/** 隐式验收条件的固定 id。简单问答不强制拆计划(规格 §4.2),它只有这
|
|
14
|
+
* 一条条件:用户的目标本身。id 固定是为了让「步骤全部完成 → 目标达成」
|
|
15
|
+
* 这条推导能在任意一次投影里被识别出来。 */
|
|
16
|
+
export const IMPLICIT_CRITERION_ID = "crit_implicit";
|
|
17
|
+
/** 任务没有显式验收条件时的隐式条件(规格 §6「简单问答可以只有一个隐式
|
|
18
|
+
* 验收条件,不强制生成复杂计划」)。 */
|
|
19
|
+
export function defaultCriteriaFor(goal) {
|
|
20
|
+
return [{ id: IMPLICIT_CRITERION_ID, description: goal, required: true, status: "pending", evidenceIds: [] }];
|
|
21
|
+
}
|
|
22
|
+
/** 步骤 id 由 (taskId, todo 内容) 决定而不是随机生成:模型每轮重发整份
|
|
23
|
+
* todo 列表是常态,随机 id 会让同一个步骤在每轮里「变成新步骤」,步骤数
|
|
24
|
+
* 无限膨胀、已完成的又对不上号。 */
|
|
25
|
+
export function stepIdForContent(taskId, content) {
|
|
26
|
+
return `step_${createHash("sha256").update(`${taskId}\u0000${content}`).digest("hex").slice(0, 16)}`;
|
|
27
|
+
}
|
|
28
|
+
/** 把一份 todo 列表投影到步骤数组上。
|
|
29
|
+
*
|
|
30
|
+
* 【为什么消失的步骤标 cancelled 而不是删除】规格 §10.3 要求重启恢复时
|
|
31
|
+
* 「保留 TaskRecord 和已完成步骤」。已完成的必须留(那是交付依据);没完成
|
|
32
|
+
* 但模型不再提及的,说明它被重新规划掉了——留成 pending 会让任务永远无法
|
|
33
|
+
* 通过 Completion Gate 的条件 2,删掉又丢掉了「曾经计划过」的信息。
|
|
34
|
+
* 标 cancelled 两者兼顾。 */
|
|
35
|
+
export function projectTodos(input) {
|
|
36
|
+
const { taskId, now } = input;
|
|
37
|
+
const byId = new Map(input.steps.map((step) => [step.id, { ...step }]));
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
let nextOrder = input.steps.reduce((max, step) => Math.max(max, step.order), -1) + 1;
|
|
40
|
+
for (const todo of input.todos) {
|
|
41
|
+
const id = stepIdForContent(taskId, todo.content);
|
|
42
|
+
seen.add(id);
|
|
43
|
+
const existing = byId.get(id);
|
|
44
|
+
const status = todo.status;
|
|
45
|
+
if (!existing) {
|
|
46
|
+
byId.set(id, {
|
|
47
|
+
id,
|
|
48
|
+
title: todo.content,
|
|
49
|
+
status,
|
|
50
|
+
order: nextOrder++,
|
|
51
|
+
evidenceIds: [],
|
|
52
|
+
...(status === "in_progress" || status === "completed" ? { startedAt: now } : {}),
|
|
53
|
+
...(status === "completed" ? { completedAt: now } : {}),
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// 不做「已完成 → 待办」的回退:模型偶尔重发一份旧列表,把它照单接受
|
|
58
|
+
// 会让已经交付的步骤倒退,Completion Gate 又会把任务打回续跑。
|
|
59
|
+
if (existing.status === "completed" && status !== "completed")
|
|
60
|
+
continue;
|
|
61
|
+
existing.status = status;
|
|
62
|
+
if (status === "in_progress" && !existing.startedAt)
|
|
63
|
+
existing.startedAt = now;
|
|
64
|
+
if (status === "completed" && !existing.completedAt)
|
|
65
|
+
existing.completedAt = now;
|
|
66
|
+
}
|
|
67
|
+
for (const step of byId.values()) {
|
|
68
|
+
if (seen.has(step.id))
|
|
69
|
+
continue;
|
|
70
|
+
if (step.status === "completed" || step.status === "cancelled")
|
|
71
|
+
continue;
|
|
72
|
+
step.status = "cancelled";
|
|
73
|
+
}
|
|
74
|
+
return [...byId.values()].sort((a, b) => a.order - b.order);
|
|
75
|
+
}
|
|
76
|
+
/** 让隐式条件跟随步骤推进。
|
|
77
|
+
*
|
|
78
|
+
* 为什么需要这一条:隐式条件没有模型显式声明「我达成了」,它的达成只能从
|
|
79
|
+
* 「模型自己拆的步骤都做完了」推导。显式声明的条件(如果将来引入)不受影响。 */
|
|
80
|
+
export function projectImplicitCriterion(input) {
|
|
81
|
+
const { criteria, steps, evidenceIds, answerPresent } = input;
|
|
82
|
+
if (!criteria.some((criterion) => criterion.id === IMPLICIT_CRITERION_ID))
|
|
83
|
+
return [...criteria];
|
|
84
|
+
const hasOpenSteps = steps.some((step) => step.status === "pending" || step.status === "in_progress" || step.status === "blocked");
|
|
85
|
+
// 有步骤时要求全部完成(步骤是模型的 todo 拆解,它说做完了才算做完)。
|
|
86
|
+
//
|
|
87
|
+
// 没有步骤时(纯解释 / 写作 / 问答,模型没拆 todo)**不能一律判未满足**:
|
|
88
|
+
// 这类任务的真实完成信号就是「有没有给出答复」。曾经这里恒为 false,配合
|
|
89
|
+
// Completion Gate 的条件 1(所有 required 条件必须 passed)会让每一个纯问答
|
|
90
|
+
// 都永远交付不了——空转到 Attempt 上限后报一个 budget blocked。规格 §9 末段
|
|
91
|
+
// 说的就是这个口子:没有工具可跑的任务,最终内容本身是唯一可验证的东西。
|
|
92
|
+
const satisfied = steps.length === 0 ? answerPresent : !hasOpenSteps;
|
|
93
|
+
return criteria.map((criterion) => {
|
|
94
|
+
if (criterion.id !== IMPLICIT_CRITERION_ID)
|
|
95
|
+
return criterion;
|
|
96
|
+
if (satisfied) {
|
|
97
|
+
return { ...criterion, status: "passed", evidenceIds: [...evidenceIds] };
|
|
98
|
+
}
|
|
99
|
+
// 未满足时不保存证据引用,避免「没通过却有证据」的中间态被误读
|
|
100
|
+
return { ...criterion, status: "pending", evidenceIds: [] };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** 工具 → 证据种类。没列出的工具(读文件、搜索、列目录等)返回 null:
|
|
104
|
+
* **读取不是验证**。把 30 次 read 记成 30 条证据,会让「有证据」这个条件
|
|
105
|
+
* 退化成「调过工具」,规格 §9 条件 3 的意义就没了。 */
|
|
106
|
+
const EVIDENCE_KIND_BY_TOOL = {
|
|
107
|
+
bash: "command",
|
|
108
|
+
terminal: "command",
|
|
109
|
+
edit: "file_diff",
|
|
110
|
+
write: "file_diff",
|
|
111
|
+
patch: "file_diff",
|
|
112
|
+
webfetch: "external_check",
|
|
113
|
+
websearch: "external_check",
|
|
114
|
+
read_attachment: "tool_result",
|
|
115
|
+
task: "tool_result",
|
|
116
|
+
};
|
|
117
|
+
export function evidenceKindForTool(tool) {
|
|
118
|
+
return EVIDENCE_KIND_BY_TOOL[tool] ?? null;
|
|
119
|
+
}
|
|
120
|
+
/** 证据上限。超出后淘汰最老的,并同步清掉一切指向它的引用(否则
|
|
121
|
+
* Completion Gate 的条件 3 会因为悬空引用而永远不满足)。 */
|
|
122
|
+
export const MAX_EVIDENCE = 96;
|
|
123
|
+
export function appendEvidence(input) {
|
|
124
|
+
const { evidence, kind, summary, ref, now } = input;
|
|
125
|
+
// 去重维度 = (kind, ref ?? summary)。同一文件被改多次只保留最后一条,
|
|
126
|
+
// 否则一个格式化的批量编辑能塞满整份证据表,把「有证据」稀释成噪音。
|
|
127
|
+
const keyOf = (item) => `${item.kind}\u0000${item.ref ?? item.summary}`;
|
|
128
|
+
const key = `${kind}\u0000${ref ?? summary}`;
|
|
129
|
+
const existingIndex = evidence.findIndex((item) => keyOf(item) === key);
|
|
130
|
+
const item = {
|
|
131
|
+
id: existingIndex >= 0 ? evidence[existingIndex].id : newEvidenceId(),
|
|
132
|
+
kind,
|
|
133
|
+
summary,
|
|
134
|
+
...(ref ? { ref } : {}),
|
|
135
|
+
createdAt: now,
|
|
136
|
+
};
|
|
137
|
+
const next = existingIndex >= 0
|
|
138
|
+
? evidence.map((value, index) => (index === existingIndex ? item : value))
|
|
139
|
+
: [...evidence, item];
|
|
140
|
+
if (next.length <= MAX_EVIDENCE)
|
|
141
|
+
return { evidence: next, dropped: [] };
|
|
142
|
+
const overflow = next.length - MAX_EVIDENCE;
|
|
143
|
+
return { evidence: next.slice(overflow), dropped: next.slice(0, overflow).map((value) => value.id) };
|
|
144
|
+
}
|
|
145
|
+
/** 从 evidenceIds 里剔除被淘汰的证据 id。 */
|
|
146
|
+
export function pruneEvidenceRefs(items, dropped) {
|
|
147
|
+
if (!dropped.length)
|
|
148
|
+
return [...items];
|
|
149
|
+
const gone = new Set(dropped);
|
|
150
|
+
return items.map((item) => (item.evidenceIds.some((id) => gone.has(id))
|
|
151
|
+
? { ...item, evidenceIds: item.evidenceIds.filter((id) => !gone.has(id)) }
|
|
152
|
+
: item));
|
|
153
|
+
}
|
|
154
|
+
/** 新建一个空任务。`status` 从 `queued` 起——`running` 由 runner 认领时置位,
|
|
155
|
+
* 这样「任务已创建但还没被任何 runner 接管」是个可观测的中间态。 */
|
|
156
|
+
export function newTaskRecord(input) {
|
|
157
|
+
return {
|
|
158
|
+
id: input.id,
|
|
159
|
+
sessionId: input.sessionId,
|
|
160
|
+
rootRequestId: input.rootRequestId,
|
|
161
|
+
rootUserMessageId: input.rootUserMessageId,
|
|
162
|
+
goal: input.goal,
|
|
163
|
+
status: "queued",
|
|
164
|
+
acceptanceCriteria: defaultCriteriaFor(input.goal),
|
|
165
|
+
steps: [],
|
|
166
|
+
evidence: [],
|
|
167
|
+
revision: 1,
|
|
168
|
+
attemptCount: 0,
|
|
169
|
+
noProgressCount: 0,
|
|
170
|
+
activeMs: 0,
|
|
171
|
+
constraints: [],
|
|
172
|
+
createdAt: input.now,
|
|
173
|
+
updatedAt: input.now,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** 由目标文本推导一个后备步骤(模型没有调 todo 工具时)。
|
|
177
|
+
*
|
|
178
|
+
* 必要性:Completion Gate 的条件 2 在有步骤时要求全部完成。若模型从不拆解
|
|
179
|
+
* 步骤,`steps` 恒为空,条件 2 自动通过——这没问题;但这样一来「任务做到
|
|
180
|
+
* 哪了」在 UI 上没有任何可展示的进度。给一条与目标同名的单步,让进度条至少
|
|
181
|
+
* 有一个真实的锚点。 */
|
|
182
|
+
export function fallbackStep(taskId, goal) {
|
|
183
|
+
return { id: stepIdForContent(taskId, goal), title: goal.slice(0, 80), status: "in_progress", order: 0, startedAt: new Date().toISOString(), evidenceIds: [] };
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/core/task/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { TaskRecord } from "./types.js";
|
|
2
|
+
/** 跨 Attempt 的进度指纹与空转检测(规格 3 §10.1)。
|
|
3
|
+
*
|
|
4
|
+
* 【为什么不能把 TaskRecord.revision 算进指纹】规格 §10.1 列出的指纹要素里
|
|
5
|
+
* 第一条是「Task revision」。照字面实现会让 no-progress 保护彻底失效:每次
|
|
6
|
+
* Attempt 收尾都要把状态写回(running → verifying → running),revision 必然
|
|
7
|
+
* 自增,于是「两轮之间没有进展」这个条件永远不成立,§10.1 的 1/2/3 档保护
|
|
8
|
+
* 全成了死代码。
|
|
9
|
+
*
|
|
10
|
+
* 规格要防的是「模型原地打转」,而原地打转的客观表现是:步骤状态没动、验收
|
|
11
|
+
* 状态没动、没有新证据、没有新改动。所以指纹取这些**语义要素**,revision
|
|
12
|
+
* 只作为「谁改过」的元数据保留在 TaskRecord 上供 CAS 用。 */
|
|
13
|
+
export type ProgressFingerprintInput = {
|
|
14
|
+
task: TaskRecord;
|
|
15
|
+
/** 本轮 Attempt 编辑/写入过的文件路径(已去重)。 */
|
|
16
|
+
editedFiles: readonly string[];
|
|
17
|
+
/** 本轮 Attempt 的工具调用次数。 */
|
|
18
|
+
toolCalls: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 计算本次 Attempt 结束后的进度指纹。
|
|
22
|
+
*
|
|
23
|
+
* 刻意**不含**工具调用次数:模型完全可以用 30 次无意义的 read 把计数刷上去。
|
|
24
|
+
* 工具调用只在「产生了上面这些可观测变化」时才意味着进展。
|
|
25
|
+
*/
|
|
26
|
+
export declare function taskFingerprint(input: ProgressFingerprintInput): string;
|
|
27
|
+
export type ProgressAdvance = {
|
|
28
|
+
/** 指纹是否与上一轮不同(true = 有进展)。 */
|
|
29
|
+
progressed: boolean;
|
|
30
|
+
/** 下一次比较用的指纹。 */
|
|
31
|
+
fingerprint: string;
|
|
32
|
+
/** 更新后的 noProgressCount(有进展清零)。 */
|
|
33
|
+
noProgressCount: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 比较本轮指纹与 `task.lastFingerprint`,给出新的 noProgressCount。
|
|
37
|
+
*
|
|
38
|
+
* 纯函数——不改 task,由调用方决定何时落库(CAS 更新)。
|
|
39
|
+
* 首次指纹(lastFingerprint 为 undefined)视为有进展:任务刚起步,
|
|
40
|
+
* 第一轮就计一次「无进展」会让所有任务都从 1 开始倒数。
|
|
41
|
+
*/
|
|
42
|
+
export declare function advanceProgress(task: TaskRecord, input: Omit<ProgressFingerprintInput, "task">): ProgressAdvance;
|
|
43
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../../src/core/task/progress.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAA+C,MAAM,YAAY,CAAC;AAE1F;;;;;;;;;;0CAU0C;AAE1C,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,UAAU,CAAC;IACjB,mCAAmC;IACnC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAsCF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,CASvE;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,8BAA8B;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,wBAAwB,EAAE,MAAM,CAAC,GAAG,eAAe,CAQhH"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** 稳定序列化:步骤与条件按 id 排序,避免「顺序变了但内容没变」被误判成进展。 */
|
|
3
|
+
function canonical(parts) {
|
|
4
|
+
return JSON.stringify(parts.map(([key, value]) => [key, value]));
|
|
5
|
+
}
|
|
6
|
+
function stepShape(steps) {
|
|
7
|
+
return [...steps]
|
|
8
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
9
|
+
.map((step) => [step.id, `${step.status}:${step.evidenceIds.length}`]);
|
|
10
|
+
}
|
|
11
|
+
function criterionShape(criteria) {
|
|
12
|
+
return [...criteria]
|
|
13
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
14
|
+
.map((criterion) => [criterion.id, `${criterion.status}:${criterion.evidenceIds.length}`]);
|
|
15
|
+
}
|
|
16
|
+
/** 证据的形状:只看**它是什么、对着谁**,不看它被分配的随机 id。
|
|
17
|
+
*
|
|
18
|
+
* 【为什么不带 id】id 是 uuid,任何一条新证据都会让指纹变化——包括模型每轮
|
|
19
|
+
* 重新说一遍自己的答案。那等于把「模型又开口了」算成进展,no-progress 保护
|
|
20
|
+
* 就永远触发不了(实测:一个纯聊天的模型可以这样刷满 24 轮 Attempt 上限)。
|
|
21
|
+
* 按 (kind, ref) 取值后,「又跑了一遍同一个 pnpm build」不再算进展,而
|
|
22
|
+
* 「改了另一个文件」仍然算——这才是 §10.1 想抓的语义差别。
|
|
23
|
+
*
|
|
24
|
+
* 【为什么排除 model_observation】它是完成判定的口子(规格 §9 末段:没有工具
|
|
25
|
+
* 可跑的任务,最终内容本身是唯一可验证的东西),但它**恰恰是没有外部验证**的
|
|
26
|
+
* 那一类。让它进指纹,上面那个洞会原样复现。它的作用是满足「每个关键条件有
|
|
27
|
+
* 一条证据」,不是证明任务在前进——两个目的,两类处理。 */
|
|
28
|
+
function evidenceShape(evidence) {
|
|
29
|
+
return [...evidence]
|
|
30
|
+
.filter((item) => item.kind !== "model_observation")
|
|
31
|
+
.sort((a, b) => `${a.kind}\u0000${a.ref ?? ""}`.localeCompare(`${b.kind}\u0000${b.ref ?? ""}`))
|
|
32
|
+
.map((item) => [item.kind, item.ref ?? null]);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 计算本次 Attempt 结束后的进度指纹。
|
|
36
|
+
*
|
|
37
|
+
* 刻意**不含**工具调用次数:模型完全可以用 30 次无意义的 read 把计数刷上去。
|
|
38
|
+
* 工具调用只在「产生了上面这些可观测变化」时才意味着进展。
|
|
39
|
+
*/
|
|
40
|
+
export function taskFingerprint(input) {
|
|
41
|
+
const { task } = input;
|
|
42
|
+
const payload = canonical([
|
|
43
|
+
["steps", stepShape(task.steps)],
|
|
44
|
+
["criteria", criterionShape(task.acceptanceCriteria)],
|
|
45
|
+
["evidence", evidenceShape(task.evidence)],
|
|
46
|
+
["files", [...input.editedFiles].sort()],
|
|
47
|
+
]);
|
|
48
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 比较本轮指纹与 `task.lastFingerprint`,给出新的 noProgressCount。
|
|
52
|
+
*
|
|
53
|
+
* 纯函数——不改 task,由调用方决定何时落库(CAS 更新)。
|
|
54
|
+
* 首次指纹(lastFingerprint 为 undefined)视为有进展:任务刚起步,
|
|
55
|
+
* 第一轮就计一次「无进展」会让所有任务都从 1 开始倒数。
|
|
56
|
+
*/
|
|
57
|
+
export function advanceProgress(task, input) {
|
|
58
|
+
const fingerprint = taskFingerprint({ task, ...input });
|
|
59
|
+
const progressed = task.lastFingerprint === undefined || task.lastFingerprint !== fingerprint;
|
|
60
|
+
return {
|
|
61
|
+
progressed,
|
|
62
|
+
fingerprint,
|
|
63
|
+
noProgressCount: progressed ? 0 : task.noProgressCount + 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../../src/core/task/progress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type CreateTaskInput, type TaskPatch, type TaskRecord } from "./types.js";
|
|
2
|
+
/** TaskStore(规格 3 §13.4)。
|
|
3
|
+
*
|
|
4
|
+
* 与 `SessionStore`/`WorkflowStore` 并列,通过 `SessionStore.task` 暴露。
|
|
5
|
+
* 三个必须由实现保证的不变量:
|
|
6
|
+
*
|
|
7
|
+
* 1. **CAS**:`updateTask` 带 `expectedRevision`,版本不符即抛错。两个 runner
|
|
8
|
+
* 同时推进同一任务时,慢的那个必须失败而不是覆盖——否则步骤状态会互相
|
|
9
|
+
* 回退,Completion Gate 读到的是一个从未真实存在过的中间态。
|
|
10
|
+
* 2. **同 session 最多一个 active root task**(规格 §6 / §18.9):两个活跃
|
|
11
|
+
* 任务会并行改同一个工作区。
|
|
12
|
+
* 3. **requestId 幂等**(规格 §13.1 / §17.1.11):重复提交返回同一个任务,
|
|
13
|
+
* 不创建第二个,也不追加第二条用户消息。 */
|
|
14
|
+
export interface TaskStore {
|
|
15
|
+
createTask(input: CreateTaskInput): Promise<TaskRecord>;
|
|
16
|
+
getTask(taskId: string): Promise<TaskRecord | null>;
|
|
17
|
+
/** 当前活跃任务(未进终态)。无则 null。 */
|
|
18
|
+
getActiveTask(sessionId: string): Promise<TaskRecord | null>;
|
|
19
|
+
/** 最近一个进入终态的任务(`GET /task` 在无活跃任务时回退到它)。 */
|
|
20
|
+
getLatestTask(sessionId: string): Promise<TaskRecord | null>;
|
|
21
|
+
/** CAS 更新;`expectedRevision` 不匹配时抛 `TASK_REVISION_CONFLICT`。 */
|
|
22
|
+
updateTask(taskId: string, expectedRevision: number, patch: TaskPatch): Promise<TaskRecord>;
|
|
23
|
+
listTasks(sessionId: string): Promise<TaskRecord[]>;
|
|
24
|
+
/** 按 rootRequestId 查(幂等键)。 */
|
|
25
|
+
findTaskByRequestId(sessionId: string, requestId: string): Promise<TaskRecord | null>;
|
|
26
|
+
}
|
|
27
|
+
export declare const TASK_REVISION_CONFLICT = "TASK_REVISION_CONFLICT";
|
|
28
|
+
/** 应用补丁 + 递增 revision。纯函数——各个 store 实现共用,避免三处各写一份
|
|
29
|
+
* 递增逻辑(漏掉一处就会出现「某些路径不更新 revision」的隐性 CAS 失效)。 */
|
|
30
|
+
export declare function applyTaskPatch(task: TaskRecord, patch: TaskPatch, now: string): TaskRecord;
|
|
31
|
+
export declare function createTaskRecord(input: CreateTaskInput, now: string): TaskRecord;
|
|
32
|
+
/** 内存实现:参考实现 + 单测用。语义与 SQLite 实现逐条对齐。 */
|
|
33
|
+
export declare function createMemoryTaskStore(options?: {
|
|
34
|
+
now?: () => string;
|
|
35
|
+
}): TaskStore;
|
|
36
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/task/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+C,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAGhI;;;;;;;;;;;4BAW4B;AAC5B,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpD,4BAA4B;IAC5B,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC7D,4CAA4C;IAC5C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC7D,gEAAgE;IAChE,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5F,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACpD,8BAA8B;IAC9B,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CACvF;AAED,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAE/D;kDACkD;AAClD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAc1F;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAchF;AAED,yCAAyC;AACzC,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAAE,GAAG,SAAS,CA+DjF"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isActiveStatus, isTerminalStatus, newTaskId } from "./types.js";
|
|
2
|
+
import { defaultCriteriaFor, newTaskRecord } from "./plan.js";
|
|
3
|
+
export const TASK_REVISION_CONFLICT = "TASK_REVISION_CONFLICT";
|
|
4
|
+
/** 应用补丁 + 递增 revision。纯函数——各个 store 实现共用,避免三处各写一份
|
|
5
|
+
* 递增逻辑(漏掉一处就会出现「某些路径不更新 revision」的隐性 CAS 失效)。 */
|
|
6
|
+
export function applyTaskPatch(task, patch, now) {
|
|
7
|
+
const merged = {
|
|
8
|
+
...task,
|
|
9
|
+
...patch,
|
|
10
|
+
id: task.id,
|
|
11
|
+
sessionId: task.sessionId,
|
|
12
|
+
createdAt: task.createdAt,
|
|
13
|
+
revision: task.revision + 1,
|
|
14
|
+
updatedAt: now,
|
|
15
|
+
};
|
|
16
|
+
// blocker 用 undefined 显式清除;展开运算会把它带成 undefined 字段,
|
|
17
|
+
// 序列化后仍占一个键。统一归一化,保证「无阻塞」只有一种表示。
|
|
18
|
+
if (patch.blocker === undefined)
|
|
19
|
+
delete merged.blocker;
|
|
20
|
+
return merged;
|
|
21
|
+
}
|
|
22
|
+
export function createTaskRecord(input, now) {
|
|
23
|
+
const base = newTaskRecord({
|
|
24
|
+
id: newTaskId(),
|
|
25
|
+
sessionId: input.sessionId,
|
|
26
|
+
rootRequestId: input.rootRequestId,
|
|
27
|
+
rootUserMessageId: input.rootUserMessageId,
|
|
28
|
+
goal: input.goal,
|
|
29
|
+
now,
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
...base,
|
|
33
|
+
...(input.steps?.length ? { steps: [...input.steps] } : {}),
|
|
34
|
+
acceptanceCriteria: input.acceptanceCriteria?.length ? [...input.acceptanceCriteria] : defaultCriteriaFor(input.goal),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** 内存实现:参考实现 + 单测用。语义与 SQLite 实现逐条对齐。 */
|
|
38
|
+
export function createMemoryTaskStore(options) {
|
|
39
|
+
const tasks = new Map();
|
|
40
|
+
const now = options?.now ?? (() => new Date().toISOString());
|
|
41
|
+
function cloneOf(task) {
|
|
42
|
+
return structuredClone(task);
|
|
43
|
+
}
|
|
44
|
+
function activeOf(sessionId) {
|
|
45
|
+
for (const task of tasks.values()) {
|
|
46
|
+
if (task.sessionId === sessionId && isActiveStatus(task.status))
|
|
47
|
+
return task;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
async createTask(input) {
|
|
53
|
+
const existing = await this.findTaskByRequestId(input.sessionId, input.rootRequestId);
|
|
54
|
+
if (existing)
|
|
55
|
+
return existing;
|
|
56
|
+
// 同一 session 已有活跃任务时不新建(调用方应先做 steering/排队判定)
|
|
57
|
+
const active = activeOf(input.sessionId);
|
|
58
|
+
if (active && active.rootRequestId !== input.rootRequestId) {
|
|
59
|
+
throw new Error("TASK_ALREADY_ACTIVE");
|
|
60
|
+
}
|
|
61
|
+
const record = createTaskRecord(input, now());
|
|
62
|
+
tasks.set(record.id, record);
|
|
63
|
+
return cloneOf(record);
|
|
64
|
+
},
|
|
65
|
+
async getTask(taskId) {
|
|
66
|
+
const task = tasks.get(taskId);
|
|
67
|
+
return task ? cloneOf(task) : null;
|
|
68
|
+
},
|
|
69
|
+
async getActiveTask(sessionId) {
|
|
70
|
+
const task = activeOf(sessionId);
|
|
71
|
+
return task ? cloneOf(task) : null;
|
|
72
|
+
},
|
|
73
|
+
async getLatestTask(sessionId) {
|
|
74
|
+
const candidates = [...tasks.values()]
|
|
75
|
+
.filter((task) => task.sessionId === sessionId && isTerminalStatus(task.status))
|
|
76
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
77
|
+
return candidates[0] ? cloneOf(candidates[0]) : null;
|
|
78
|
+
},
|
|
79
|
+
async updateTask(taskId, expectedRevision, patch) {
|
|
80
|
+
const current = tasks.get(taskId);
|
|
81
|
+
if (!current)
|
|
82
|
+
throw new Error("TASK_NOT_FOUND");
|
|
83
|
+
if (current.revision !== expectedRevision)
|
|
84
|
+
throw new Error(TASK_REVISION_CONFLICT);
|
|
85
|
+
const updated = applyTaskPatch(current, patch, now());
|
|
86
|
+
tasks.set(taskId, updated);
|
|
87
|
+
return cloneOf(updated);
|
|
88
|
+
},
|
|
89
|
+
async listTasks(sessionId) {
|
|
90
|
+
return [...tasks.values()]
|
|
91
|
+
.filter((task) => task.sessionId === sessionId)
|
|
92
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
|
93
|
+
.map(cloneOf);
|
|
94
|
+
},
|
|
95
|
+
async findTaskByRequestId(sessionId, requestId) {
|
|
96
|
+
for (const task of tasks.values()) {
|
|
97
|
+
if (task.sessionId === sessionId && task.rootRequestId === requestId)
|
|
98
|
+
return cloneOf(task);
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/task/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/** 持续任务执行的持久化领域模型(规格 3 §6)。
|
|
2
|
+
*
|
|
3
|
+
* 【为什么必须持久化】规格 §19 明文禁止把 TaskRecord 放在 localStorage 或
|
|
4
|
+
* React 状态里。原因不是洁癖:任务要跨越「模型一次运行结束」,要跨越「进程
|
|
5
|
+
* 重启」,还要在断线重连后被另一台设备读到。任何只活在某个进程内存里的记录,
|
|
6
|
+
* 在最需要它的两个时刻(崩溃后、重连后)恰好不存在。
|
|
7
|
+
*
|
|
8
|
+
* 【与 workflow_run 的分工】
|
|
9
|
+
* - `WorkflowRun` 记录**一次内部运行**(Attempt)的入队、认领与终态。
|
|
10
|
+
* - `TaskRecord` 记录**用户的一条目标**从接受到交付的全过程,可包含多次
|
|
11
|
+
* Attempt、多次工具调用、一次用户授权、一次重启恢复。
|
|
12
|
+
* 前者结束不代表后者完成——这正是规格 §3.1 要修的根因。 */
|
|
13
|
+
/** 任务生命周期(规格 §6)。`waiting_*` 三分是有意的:权限、缺信息、
|
|
14
|
+
* 外部状态(登录/验证码/付款)需要用户做的事完全不同,提示文案与按钮
|
|
15
|
+
* 也不同,合并成一个 `waiting` 会让 UI 只能给出笼统的「请处理」。 */
|
|
16
|
+
export type TaskLifecycleStatus = "queued" | "running" | "recovering" | "waiting_permission" | "waiting_input" | "waiting_external" | "verifying" | "delivered" | "blocked" | "failed" | "cancelled";
|
|
17
|
+
export type TaskStepStatus = "pending" | "in_progress" | "completed" | "blocked" | "cancelled";
|
|
18
|
+
export type AcceptanceCriterionStatus = "pending" | "passed" | "failed" | "not_applicable";
|
|
19
|
+
export type AcceptanceCriterion = {
|
|
20
|
+
id: string;
|
|
21
|
+
description: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
status: AcceptanceCriterionStatus;
|
|
24
|
+
evidenceIds: string[];
|
|
25
|
+
};
|
|
26
|
+
export type TaskStep = {
|
|
27
|
+
id: string;
|
|
28
|
+
title: string;
|
|
29
|
+
status: TaskStepStatus;
|
|
30
|
+
order: number;
|
|
31
|
+
startedAt?: string;
|
|
32
|
+
completedAt?: string;
|
|
33
|
+
evidenceIds: string[];
|
|
34
|
+
};
|
|
35
|
+
/** 证据种类。`model_observation` 是给纯解释/写作类任务留的口子:没有工具
|
|
36
|
+
* 可跑的任务,其最终内容本身就是唯一可验证的东西(规格 §9 末段)。 */
|
|
37
|
+
export type TaskEvidenceKind = "tool_result" | "file_diff" | "command" | "test" | "preview" | "external_check" | "model_observation";
|
|
38
|
+
export type TaskEvidence = {
|
|
39
|
+
id: string;
|
|
40
|
+
kind: TaskEvidenceKind;
|
|
41
|
+
summary: string;
|
|
42
|
+
ref?: string;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
};
|
|
45
|
+
/** 阻塞原因(规格 §6)。`resumable` 决定 UI 给「检查后重试」还是只给
|
|
46
|
+
* 「停止任务」——一个不可恢复的阻塞不该摆出可点的重试按钮。 */
|
|
47
|
+
export type TaskBlockerKind = "permission" | "input" | "choice" | "external_auth" | "unsafe_replay" | "budget" | "no_progress";
|
|
48
|
+
export type TaskBlocker = {
|
|
49
|
+
kind: TaskBlockerKind;
|
|
50
|
+
/** 卡在哪里——给用户看的。必须具体到「哪个动作需要什么」。 */
|
|
51
|
+
message: string;
|
|
52
|
+
/** 用户做完什么之后系统会自动继续。规格 §14.4 禁止只说「请继续」。 */
|
|
53
|
+
requiredAction: string;
|
|
54
|
+
resumable: boolean;
|
|
55
|
+
};
|
|
56
|
+
export type TaskRecord = {
|
|
57
|
+
id: string;
|
|
58
|
+
sessionId: string;
|
|
59
|
+
/** 触发本任务的 requestId。同一 requestId 重复提交必须返回同一 task。 */
|
|
60
|
+
rootRequestId: string;
|
|
61
|
+
rootUserMessageId: string;
|
|
62
|
+
/** 用户最终想得到什么。压缩上下文后必须仍在(规格 §6 要求)。 */
|
|
63
|
+
goal: string;
|
|
64
|
+
status: TaskLifecycleStatus;
|
|
65
|
+
acceptanceCriteria: AcceptanceCriterion[];
|
|
66
|
+
steps: TaskStep[];
|
|
67
|
+
currentStepId?: string;
|
|
68
|
+
evidence: TaskEvidence[];
|
|
69
|
+
blocker?: TaskBlocker;
|
|
70
|
+
/** CAS 版本号:每次更新 +1。并发 runner 靠它检测竞争(规格 §6)。 */
|
|
71
|
+
revision: number;
|
|
72
|
+
/** 已执行的 Attempt 次数(含 continuation)。 */
|
|
73
|
+
attemptCount: number;
|
|
74
|
+
/** 连续无进展的 Attempt 次数(规格 §10.1)。 */
|
|
75
|
+
noProgressCount: number;
|
|
76
|
+
/** 累计执行时间(毫秒):各 Attempt 的 `durationMs` 之和。
|
|
77
|
+
*
|
|
78
|
+
* 【为什么不是「now - createdAt」】墙钟会把任务**停下来等用户**的空白也算
|
|
79
|
+
* 进去:一个因为缺授权停了一夜的任务,第二天点「继续」时若直接拿墙钟比对
|
|
80
|
+
* 预算,第一轮还没开始就超时了——那正是「继续」变死按钮的另一种写法。
|
|
81
|
+
* 规格 §10.2 的时间预算要防的是「任务在烧」,不是「任务存在得久」。
|
|
82
|
+
*
|
|
83
|
+
* 口径说清楚:**阻塞期间**(blocked / waiting_* 到下一次放行之间)run 已经
|
|
84
|
+
* 结束,那段空白天然不计;而**轮内的授权等待**是这一轮的一部分,计入。
|
|
85
|
+
* 旧版本落库的任务没有这个字段,读的时候按 0 处理。 */
|
|
86
|
+
activeMs?: number;
|
|
87
|
+
/** 用户中途追加的约束(steering 消息累积),进 continuation 上下文。 */
|
|
88
|
+
constraints: string[];
|
|
89
|
+
createdAt: string;
|
|
90
|
+
updatedAt: string;
|
|
91
|
+
deliveredAt?: string;
|
|
92
|
+
/** 最近一次 Attempt 的进度指纹,用于跨 Attempt 的 no-progress 判定。 */
|
|
93
|
+
lastFingerprint?: string;
|
|
94
|
+
/** 交付文本(规格 §14.1 最终交付卡的四问来源)。 */
|
|
95
|
+
result?: TaskResult;
|
|
96
|
+
};
|
|
97
|
+
/** 最终交付信息(规格 §9 条件 5 / §14.1 交付卡四问 / §18.7)。 */
|
|
98
|
+
export type TaskResult = {
|
|
99
|
+
/** 1. 做成了什么 */
|
|
100
|
+
outcome: string;
|
|
101
|
+
/** 2. 改了哪些主要内容 */
|
|
102
|
+
changes: string[];
|
|
103
|
+
/** 3. 如何验证 */
|
|
104
|
+
verification: string[];
|
|
105
|
+
/** 4. 还有哪些没做完。无剩余项时**必须显式为空数组**,而不是省略——
|
|
106
|
+
* 规格 §18.7 要求「无剩余项时明确为『无』」,省略会让「没写」和
|
|
107
|
+
* 「没有」两种含义糊在一起。 */
|
|
108
|
+
remaining: string[];
|
|
109
|
+
};
|
|
110
|
+
export type CreateTaskInput = {
|
|
111
|
+
sessionId: string;
|
|
112
|
+
rootRequestId: string;
|
|
113
|
+
rootUserMessageId: string;
|
|
114
|
+
goal: string;
|
|
115
|
+
/** 初始步骤(通常来自模型的 todo 拆解,可后补)。 */
|
|
116
|
+
steps?: TaskStep[];
|
|
117
|
+
/** 初始验收条件。省略时由 `defaultCriteriaFor(goal)` 生成一条隐式条件。 */
|
|
118
|
+
acceptanceCriteria?: AcceptanceCriterion[];
|
|
119
|
+
};
|
|
120
|
+
/** TaskStore 更新补丁。不允许改 id/sessionId/revision/createdAt——
|
|
121
|
+
* 这些是身份与并发控制字段,必须由 store 自己维护。 */
|
|
122
|
+
export type TaskPatch = Partial<Omit<TaskRecord, "id" | "sessionId" | "revision" | "createdAt">>;
|
|
123
|
+
export declare function newTaskId(): string;
|
|
124
|
+
export declare function newTaskStepId(): string;
|
|
125
|
+
export declare function newCriterionId(): string;
|
|
126
|
+
export declare function newEvidenceId(): string;
|
|
127
|
+
/** 任务是否已进入终态(不再有任何自动推进)。 */
|
|
128
|
+
export declare function isTerminalStatus(status: TaskLifecycleStatus): boolean;
|
|
129
|
+
/** 任务当前是否在等用户做点什么(UI 按钮由它决定)。 */
|
|
130
|
+
export declare function isWaitingStatus(status: TaskLifecycleStatus): boolean;
|
|
131
|
+
/** 是否仍会写工作区(用于「同 session 最多一个 active root task」判定)。 */
|
|
132
|
+
export declare function isActiveStatus(status: TaskLifecycleStatus): boolean;
|
|
133
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/task/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export function newTaskId() {
|
|
3
|
+
return `task_${randomUUID()}`;
|
|
4
|
+
}
|
|
5
|
+
export function newTaskStepId() {
|
|
6
|
+
return `step_${randomUUID()}`;
|
|
7
|
+
}
|
|
8
|
+
export function newCriterionId() {
|
|
9
|
+
return `crit_${randomUUID()}`;
|
|
10
|
+
}
|
|
11
|
+
export function newEvidenceId() {
|
|
12
|
+
return `evd_${randomUUID()}`;
|
|
13
|
+
}
|
|
14
|
+
/** 任务是否已进入终态(不再有任何自动推进)。 */
|
|
15
|
+
export function isTerminalStatus(status) {
|
|
16
|
+
return status === "delivered" || status === "failed" || status === "cancelled";
|
|
17
|
+
}
|
|
18
|
+
/** 任务当前是否在等用户做点什么(UI 按钮由它决定)。 */
|
|
19
|
+
export function isWaitingStatus(status) {
|
|
20
|
+
return status === "waiting_permission" || status === "waiting_input" || status === "waiting_external" || status === "blocked";
|
|
21
|
+
}
|
|
22
|
+
/** 是否仍会写工作区(用于「同 session 最多一个 active root task」判定)。 */
|
|
23
|
+
export function isActiveStatus(status) {
|
|
24
|
+
return !isTerminalStatus(status);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.js.map
|