@tea-agent/loop-agent 0.25.3 → 0.25.5
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/AGENTS.md +6 -0
- package/CHANGELOG.md +55 -0
- package/dist/application/dag/args.js +21 -1
- package/dist/application/dag/run-dag.js +1 -0
- package/dist/cli/command-definitions.js +1 -1
- package/dist/cli/program.js +82 -63
- package/dist/commands/client-recovery.js +209 -62
- package/dist/commands/init.js +206 -82
- package/dist/commands/run-dag-progress.js +109 -0
- package/dist/commands/run-dag.js +16 -5
- package/dist/executors/dag-pi-executor.js +80 -15
- package/dist/executors/model-routing.js +1 -1
- package/dist/executors/shell-executor.js +159 -0
- package/dist/executors/shell-write-guard.js +21 -7
- package/dist/worker/console/repo-fingerprint.js +7 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +964 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +39 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +306 -30
- package/dist/workflows/dag/backend-test-result-contract.js +35 -9
- package/dist/workflows/dag/convergence/controller.js +134 -9
- package/dist/workflows/dag/frontend-test-case-checklist.js +71 -0
- package/dist/workflows/dag/frontend-test-html-report.js +77 -0
- package/dist/workflows/dag/frontend-test-l5-report.js +138 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +44 -1
- package/dist/workflows/dag/init-hybrid.js +267 -80
- package/dist/workflows/dag/node-execution.js +64 -11
- package/dist/workflows/dag/prompt.js +118 -4
- package/dist/workflows/dag/retry-policy.js +5 -4
- package/dist/workflows/dag/scheduler.js +32 -5
- package/dist/workflows/dag/types.js +10 -3
- package/dist/workflows/dag/validate.js +6 -3
- package/docs/architecture/dag-execution.md +7 -4
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/templates/agent-dag.base.json +1 -1
- package/docs/templates/agent-dag.final-verification.json +1 -1
- package/docs/templates/agent-dag.schema.json +6 -0
- package/docs/templates/agent-dag.supervised-implementation.json +1 -1
- package/docs/templates/backend-test-dag.json +40 -13
- package/docs/templates/backend-test-dag.review-cases.prompt.md +1 -1
- package/docs/templates/frontend-test-dag.json +62 -5
- package/docs/templates/hybrid-dag.json +1 -1
- package/examples/decision-gate-agent-dag.json +1 -1
- package/examples/example-dag.json +1 -1
- package/examples/hybrid-loop-agent-dag.json +1 -1
- package/harness.json +3 -2
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +2 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/skills/loop-agent/references/model-routing.md +1 -1
|
@@ -33,6 +33,13 @@ export const frontendTestResultContractSchema = z.object({
|
|
|
33
33
|
status: caseStatusSchema,
|
|
34
34
|
evidence: z.array(z.object({ path: safeRelativePathSchema, sha256: sha256Schema }).strict()),
|
|
35
35
|
blockedReason: z.string().min(1).optional(),
|
|
36
|
+
caseContent: z.object({
|
|
37
|
+
purpose: z.string(),
|
|
38
|
+
preconditions: z.array(z.string()),
|
|
39
|
+
steps: z.array(z.string()),
|
|
40
|
+
expectedResults: z.array(z.string()),
|
|
41
|
+
}).strict().default({ purpose: "未提供测试目的", preconditions: [], steps: [], expectedResults: [] }),
|
|
42
|
+
errorAnalysis: z.string().min(1).optional(),
|
|
36
43
|
}).strict()).min(1),
|
|
37
44
|
advisoryFindings: z.array(z.object({
|
|
38
45
|
ruleId: z.string().min(1),
|
|
@@ -176,6 +183,32 @@ export async function validateFrontendCaseEvidence(input) {
|
|
|
176
183
|
function sha256(content) {
|
|
177
184
|
return createHash("sha256").update(content).digest("hex");
|
|
178
185
|
}
|
|
186
|
+
function markdownSection(body, names) {
|
|
187
|
+
const escaped = names.map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
188
|
+
const marker = new RegExp(`^###\\s+(?:${escaped})\\s*$`, "mi");
|
|
189
|
+
const hit = marker.exec(body);
|
|
190
|
+
if (!hit)
|
|
191
|
+
return "";
|
|
192
|
+
const rest = body.slice(hit.index + hit[0].length);
|
|
193
|
+
const next = /^###\s+/m.exec(rest);
|
|
194
|
+
return rest.slice(0, next?.index ?? rest.length).trim();
|
|
195
|
+
}
|
|
196
|
+
function markdownList(value) {
|
|
197
|
+
const items = value.split(/\r?\n/).map((line) => line.replace(/^\s*(?:\d+[.)]|[-*+])\s+/, "").trim()).filter(Boolean);
|
|
198
|
+
return items;
|
|
199
|
+
}
|
|
200
|
+
async function readFrontendCaseContent(workspaceRoot, casePath) {
|
|
201
|
+
if (!safeRelativePathSchema.safeParse(casePath).success || !casePath.startsWith("testcase/frontend/cases/")) {
|
|
202
|
+
throw new Error(`unsafe frontend case path: ${casePath}`);
|
|
203
|
+
}
|
|
204
|
+
const body = await readFile(path.resolve(workspaceRoot, casePath), "utf8");
|
|
205
|
+
return {
|
|
206
|
+
purpose: markdownSection(body, ["Test Purpose", "测试目的", "测试场景"]) || "未提供测试目的",
|
|
207
|
+
preconditions: markdownList(markdownSection(body, ["Preconditions", "前置条件"])),
|
|
208
|
+
steps: markdownList(markdownSection(body, ["Steps", "操作步骤"])),
|
|
209
|
+
expectedResults: markdownList(markdownSection(body, ["Expected Results", "预期结果"])),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
179
212
|
function assertInside(root, candidate, label) {
|
|
180
213
|
const relative = path.relative(root, candidate);
|
|
181
214
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
@@ -285,7 +318,17 @@ export async function materializeFrontendTestResult(input) {
|
|
|
285
318
|
}
|
|
286
319
|
if (status === "passed" && !evidence.some((entry) => /\.(png|jpg|jpeg|webp|zip|har|webm|mp4|ya?ml|md)$/i.test(entry.path)))
|
|
287
320
|
advisoryFindings.push({ ruleId: "passed-without-browser-evidence", caseId: item.caseId, detail: "passed case has no browser evidence" });
|
|
288
|
-
|
|
321
|
+
const caseContent = await readFrontendCaseContent(input.workspaceRoot, item.casePath);
|
|
322
|
+
const blockedReason = status === "blocked" && typeof resultRaw.blockedReason === "string" && resultRaw.blockedReason.trim() ? resultRaw.blockedReason.trim() : undefined;
|
|
323
|
+
const explicitAnalysis = typeof resultRaw.errorAnalysis === "string" && resultRaw.errorAnalysis.trim()
|
|
324
|
+
? resultRaw.errorAnalysis.trim()
|
|
325
|
+
: typeof resultRaw.errorSummary === "string" && resultRaw.errorSummary.trim()
|
|
326
|
+
? resultRaw.errorSummary.trim()
|
|
327
|
+
: undefined;
|
|
328
|
+
const errorAnalysis = status === "passed"
|
|
329
|
+
? undefined
|
|
330
|
+
: explicitAnalysis ?? (status === "blocked" ? `用例因 ${blockedReason ?? "未知原因"} 未能完成执行。` : "用例执行失败,但执行结果未提供详细错误分析。");
|
|
331
|
+
cases.push({ caseId: item.caseId, acIds: item.acIds, status, evidence, caseContent, ...(blockedReason ? { blockedReason } : {}), ...(errorAnalysis ? { errorAnalysis } : {}) });
|
|
289
332
|
}
|
|
290
333
|
const missing = [...requiredIds].filter((id) => !covered.has(id));
|
|
291
334
|
const totals = {
|