@zmzai/agent-framework 0.5.2 → 0.9.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 +178 -0
- package/dist/core/events/manifest.d.ts.map +1 -1
- package/dist/core/events/manifest.js +106 -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 +795 -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 +142 -0
- package/dist/core/task/completion.d.ts.map +1 -0
- package/dist/core/task/completion.js +273 -0
- package/dist/core/task/completion.js.map +1 -0
- package/dist/core/task/contract.d.ts +26 -0
- package/dist/core/task/contract.d.ts.map +1 -0
- package/dist/core/task/contract.js +103 -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 +121 -0
- package/dist/core/task/plan.d.ts.map +1 -0
- package/dist/core/task/plan.js +216 -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 +156 -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 +6 -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/core/tools/task-deliver.d.ts +55 -0
- package/dist/core/tools/task-deliver.d.ts.map +1 -0
- package/dist/core/tools/task-deliver.js +98 -0
- package/dist/core/tools/task-deliver.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,216 @@
|
|
|
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
|
+
/** 把一次交付声明投影进任务契约(规格 §9 条件 1/3、§14.1 四问)。
|
|
77
|
+
*
|
|
78
|
+
* 【为什么证据是「整份任务集合」而不是按条件挑】工具调用与验收条件之间没有可靠
|
|
79
|
+
* 映射:框架不知道第 3 次 `pnpm build` 服务的是哪一条条件。后果只有两种:全都挂
|
|
80
|
+
* 上,或都不挂。都不挂会让条件 3 永远不满足(每个 passed 条件至少要一条有效证据),
|
|
81
|
+
* 完成判定直接失效;全都挂是保守的那一侧——它不会让**不该通过**的条件通过,只会
|
|
82
|
+
* 让「有证据」这条判定比理想情况宽一点。而真正会骗人的那一步(把模型自己的话当
|
|
83
|
+
* 证据)另有约束:只有任务里**一条别的证据都没有**时,才用声明本身顶上。 */
|
|
84
|
+
export function applyDelivery(input) {
|
|
85
|
+
const { criteria, delivery, evidence, declarationEvidenceId, observedChanges } = input;
|
|
86
|
+
// 空数组按「没给」处理:`criteria: []` 与省略是同一个意思,不该因为写了对方括号
|
|
87
|
+
// 就让隐式条件掉进「未声明」分支。
|
|
88
|
+
const declaredList = delivery.criteria?.length ? delivery.criteria : null;
|
|
89
|
+
const declared = new Map((declaredList ?? []).map((item) => [item.id, item]));
|
|
90
|
+
const known = new Set(criteria.map((criterion) => criterion.id));
|
|
91
|
+
const unknownCriterionIds = (declaredList ?? []).filter((item) => !known.has(item.id)).map((item) => item.id);
|
|
92
|
+
// 工具证据优先;一条都没有时(纯解释 / 写作 / 问答,规格 §9 末段)才用交付
|
|
93
|
+
// 声明本身。顺序不能反:反了就是「说过话」压过「做过事」。
|
|
94
|
+
const toolEvidenceIds = evidence.filter((item) => item.kind !== "model_observation").map((item) => item.id);
|
|
95
|
+
const attachable = toolEvidenceIds.length ? toolEvidenceIds : declarationEvidenceId ? [declarationEvidenceId] : [];
|
|
96
|
+
const next = criteria.map((criterion) => {
|
|
97
|
+
const item = declared.get(criterion.id);
|
|
98
|
+
if (!item) {
|
|
99
|
+
// 只有隐式条件、且模型没逐条写:`summary` + `verification` 已经回答了同一个
|
|
100
|
+
// 问题(描述就是用户的目标),视为通过。这是「省略 = 隐式条件通过」的实现处。
|
|
101
|
+
if (!declaredList && criterion.id === IMPLICIT_CRITERION_ID) {
|
|
102
|
+
return { ...criterion, status: "passed", evidenceIds: [...attachable] };
|
|
103
|
+
}
|
|
104
|
+
// 其余未覆盖的保持原状(pending)——门会以「验收条件未通过」打回续跑,
|
|
105
|
+
// 并在 advisory 里点名。不在这里自动补一个结论。
|
|
106
|
+
return { ...criterion };
|
|
107
|
+
}
|
|
108
|
+
// required 条件不接受 not_applicable:那等于用一句话把一整条验收条件抹掉,
|
|
109
|
+
// 而 §9 条件 1 要的是「所有 required 条件 passed」。非 required 条件(将来由
|
|
110
|
+
// 宿主显式添加)允许豁免。
|
|
111
|
+
if (item.status === "not_applicable") {
|
|
112
|
+
return criterion.required ? { ...criterion } : { ...criterion, status: "not_applicable", evidenceIds: [] };
|
|
113
|
+
}
|
|
114
|
+
if (item.status === "failed") {
|
|
115
|
+
// failed 不挂证据:挂上会让「这条条件有证据」与「这条条件没通过」同时为真,
|
|
116
|
+
// 而 §9 条件 3 只对 passed 的条件要求证据。
|
|
117
|
+
return { ...criterion, status: "failed", evidenceIds: [] };
|
|
118
|
+
}
|
|
119
|
+
return { ...criterion, status: "passed", evidenceIds: [...attachable] };
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
criteria: next,
|
|
123
|
+
result: {
|
|
124
|
+
outcome: delivery.summary,
|
|
125
|
+
changes: delivery.changes?.length ? [...delivery.changes] : [...observedChanges],
|
|
126
|
+
verification: [...delivery.verification],
|
|
127
|
+
// §18.7:无剩余项时也要显式给空数组,"没写" 与 "没有" 不能糊在一起。
|
|
128
|
+
remaining: delivery.remaining ? [...delivery.remaining] : [],
|
|
129
|
+
},
|
|
130
|
+
unknownCriterionIds,
|
|
131
|
+
unfulfilledRequiredIds: next.filter((criterion) => criterion.required && criterion.status !== "passed").map((criterion) => criterion.id),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** 工具 → 证据种类。没列出的工具(读文件、搜索、列目录等)返回 null:
|
|
135
|
+
* **读取不是验证**。把 30 次 read 记成 30 条证据,会让「有证据」这个条件
|
|
136
|
+
* 退化成「调过工具」,规格 §9 条件 3 的意义就没了。 */
|
|
137
|
+
const EVIDENCE_KIND_BY_TOOL = {
|
|
138
|
+
bash: "command",
|
|
139
|
+
terminal: "command",
|
|
140
|
+
edit: "file_diff",
|
|
141
|
+
write: "file_diff",
|
|
142
|
+
patch: "file_diff",
|
|
143
|
+
webfetch: "external_check",
|
|
144
|
+
websearch: "external_check",
|
|
145
|
+
read_attachment: "tool_result",
|
|
146
|
+
task: "tool_result",
|
|
147
|
+
};
|
|
148
|
+
export function evidenceKindForTool(tool) {
|
|
149
|
+
return EVIDENCE_KIND_BY_TOOL[tool] ?? null;
|
|
150
|
+
}
|
|
151
|
+
/** 证据上限。超出后淘汰最老的,并同步清掉一切指向它的引用(否则
|
|
152
|
+
* Completion Gate 的条件 3 会因为悬空引用而永远不满足)。 */
|
|
153
|
+
export const MAX_EVIDENCE = 96;
|
|
154
|
+
export function appendEvidence(input) {
|
|
155
|
+
const { evidence, kind, summary, ref, now } = input;
|
|
156
|
+
// 去重维度 = (kind, ref ?? summary)。同一文件被改多次只保留最后一条,
|
|
157
|
+
// 否则一个格式化的批量编辑能塞满整份证据表,把「有证据」稀释成噪音。
|
|
158
|
+
const keyOf = (item) => `${item.kind}\u0000${item.ref ?? item.summary}`;
|
|
159
|
+
const key = `${kind}\u0000${ref ?? summary}`;
|
|
160
|
+
const existingIndex = evidence.findIndex((item) => keyOf(item) === key);
|
|
161
|
+
const item = {
|
|
162
|
+
id: existingIndex >= 0 ? evidence[existingIndex].id : newEvidenceId(),
|
|
163
|
+
kind,
|
|
164
|
+
summary,
|
|
165
|
+
...(ref ? { ref } : {}),
|
|
166
|
+
createdAt: now,
|
|
167
|
+
};
|
|
168
|
+
const next = existingIndex >= 0
|
|
169
|
+
? evidence.map((value, index) => (index === existingIndex ? item : value))
|
|
170
|
+
: [...evidence, item];
|
|
171
|
+
if (next.length <= MAX_EVIDENCE)
|
|
172
|
+
return { evidence: next, dropped: [] };
|
|
173
|
+
const overflow = next.length - MAX_EVIDENCE;
|
|
174
|
+
return { evidence: next.slice(overflow), dropped: next.slice(0, overflow).map((value) => value.id) };
|
|
175
|
+
}
|
|
176
|
+
/** 从 evidenceIds 里剔除被淘汰的证据 id。 */
|
|
177
|
+
export function pruneEvidenceRefs(items, dropped) {
|
|
178
|
+
if (!dropped.length)
|
|
179
|
+
return [...items];
|
|
180
|
+
const gone = new Set(dropped);
|
|
181
|
+
return items.map((item) => (item.evidenceIds.some((id) => gone.has(id))
|
|
182
|
+
? { ...item, evidenceIds: item.evidenceIds.filter((id) => !gone.has(id)) }
|
|
183
|
+
: item));
|
|
184
|
+
}
|
|
185
|
+
/** 新建一个空任务。`status` 从 `queued` 起——`running` 由 runner 认领时置位,
|
|
186
|
+
* 这样「任务已创建但还没被任何 runner 接管」是个可观测的中间态。 */
|
|
187
|
+
export function newTaskRecord(input) {
|
|
188
|
+
return {
|
|
189
|
+
id: input.id,
|
|
190
|
+
sessionId: input.sessionId,
|
|
191
|
+
rootRequestId: input.rootRequestId,
|
|
192
|
+
rootUserMessageId: input.rootUserMessageId,
|
|
193
|
+
goal: input.goal,
|
|
194
|
+
status: "queued",
|
|
195
|
+
acceptanceCriteria: defaultCriteriaFor(input.goal),
|
|
196
|
+
steps: [],
|
|
197
|
+
evidence: [],
|
|
198
|
+
revision: 1,
|
|
199
|
+
attemptCount: 0,
|
|
200
|
+
noProgressCount: 0,
|
|
201
|
+
activeMs: 0,
|
|
202
|
+
constraints: [],
|
|
203
|
+
createdAt: input.now,
|
|
204
|
+
updatedAt: input.now,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** 由目标文本推导一个后备步骤(模型没有调 todo 工具时)。
|
|
208
|
+
*
|
|
209
|
+
* 必要性:Completion Gate 的条件 2 在有步骤时要求全部完成。若模型从不拆解
|
|
210
|
+
* 步骤,`steps` 恒为空,条件 2 自动通过——这没问题;但这样一来「任务做到
|
|
211
|
+
* 哪了」在 UI 上没有任何可展示的进度。给一条与目标同名的单步,让进度条至少
|
|
212
|
+
* 有一个真实的锚点。 */
|
|
213
|
+
export function fallbackStep(taskId, goal) {
|
|
214
|
+
return { id: stepIdForContent(taskId, goal), title: goal.slice(0, 80), status: "in_progress", order: 0, startedAt: new Date().toISOString(), evidenceIds: [] };
|
|
215
|
+
}
|
|
216
|
+
//# 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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"}
|