@tea-agent/loop-agent 0.25.3 → 0.25.4
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/CHANGELOG.md +15 -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/init.js +206 -82
- package/dist/commands/run-dag-progress.js +109 -0
- package/dist/commands/run-dag.js +16 -5
- package/dist/executors/shell-executor.js +32 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +88 -15
- package/dist/workflows/dag/backend-test-result-contract.js +35 -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-result-contract.js +44 -1
- package/dist/workflows/dag/init-hybrid.js +9 -9
- package/dist/workflows/dag/types.js +4 -0
- package/dist/workflows/dag/validate.js +3 -1
- package/docs/templates/agent-dag.schema.json +6 -0
- package/docs/templates/backend-test-dag.json +3 -3
- package/docs/templates/backend-test-dag.review-cases.prompt.md +1 -1
- package/docs/templates/frontend-test-dag.json +31 -4
- package/harness.json +3 -2
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.25.4] - 2026-07-31
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
- stream run progress heartbeats
|
|
10
|
+
- improve HTML failure triage
|
|
11
|
+
- add native checklist and HTML report gates
|
|
12
|
+
|
|
13
|
+
### 修复
|
|
14
|
+
|
|
15
|
+
- tighten production URL detection
|
|
16
|
+
- preserve supported Pi default model
|
|
17
|
+
- sync packaged prompt contract
|
|
18
|
+
- normalize drifted case ids and decode nested HTML entities
|
|
19
|
+
|
|
5
20
|
## [0.25.3] - 2026-07-30
|
|
6
21
|
|
|
7
22
|
### 重点更新
|
|
@@ -247,12 +247,14 @@ export function parseDagValidateArgs(args) {
|
|
|
247
247
|
}
|
|
248
248
|
export function parseRunDagArgs(args, defaultCwd) {
|
|
249
249
|
if (args.length === 0) {
|
|
250
|
-
throw new Error("usage: run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]] [--events-jsonl <path>] [--json]");
|
|
250
|
+
throw new Error("usage: run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--quiet] [--progress-interval-ms N] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]] [--events-jsonl <path>] [--json]");
|
|
251
251
|
}
|
|
252
252
|
let dagPath;
|
|
253
253
|
let cwd;
|
|
254
254
|
let initOnly = false;
|
|
255
255
|
let dryRun = false;
|
|
256
|
+
let quiet = false;
|
|
257
|
+
let progressIntervalMs = 30_000;
|
|
256
258
|
let maxConcurrent;
|
|
257
259
|
let runId;
|
|
258
260
|
let canvasPath;
|
|
@@ -280,6 +282,15 @@ export function parseRunDagArgs(args, defaultCwd) {
|
|
|
280
282
|
else if (arg === "--dry-run") {
|
|
281
283
|
dryRun = true;
|
|
282
284
|
}
|
|
285
|
+
else if (arg === "--quiet") {
|
|
286
|
+
quiet = true;
|
|
287
|
+
}
|
|
288
|
+
else if (arg === "--progress-interval-ms") {
|
|
289
|
+
progressIntervalMs = parseProgressIntervalMs(args[++i]);
|
|
290
|
+
}
|
|
291
|
+
else if (arg.startsWith("--progress-interval-ms=")) {
|
|
292
|
+
progressIntervalMs = parseProgressIntervalMs(arg.slice("--progress-interval-ms=".length));
|
|
293
|
+
}
|
|
283
294
|
else if (arg === "--max-concurrent") {
|
|
284
295
|
maxConcurrent = parseMaxConcurrent(args[++i]);
|
|
285
296
|
}
|
|
@@ -337,6 +348,8 @@ export function parseRunDagArgs(args, defaultCwd) {
|
|
|
337
348
|
cwd: path.resolve(cwd ?? defaultCwd ?? process.cwd()),
|
|
338
349
|
initOnly,
|
|
339
350
|
dryRun,
|
|
351
|
+
quiet,
|
|
352
|
+
progressIntervalMs,
|
|
340
353
|
maxConcurrent,
|
|
341
354
|
runId,
|
|
342
355
|
canvasPath,
|
|
@@ -346,6 +359,13 @@ export function parseRunDagArgs(args, defaultCwd) {
|
|
|
346
359
|
...(workerAssociation ? { workerAssociation } : {}),
|
|
347
360
|
};
|
|
348
361
|
}
|
|
362
|
+
function parseProgressIntervalMs(value) {
|
|
363
|
+
const parsed = Number(value);
|
|
364
|
+
if (!Number.isInteger(parsed) || parsed < 1_000) {
|
|
365
|
+
throw new Error("progress-interval-ms must be an integer >= 1000");
|
|
366
|
+
}
|
|
367
|
+
return parsed;
|
|
368
|
+
}
|
|
349
369
|
function parseWorkerAssociation(raw) {
|
|
350
370
|
if (!raw?.trim()) {
|
|
351
371
|
throw new Error("run-dag --worker-association requires JSON");
|
|
@@ -662,7 +662,7 @@ export const COMMAND_DEFINITIONS = [
|
|
|
662
662
|
adapter: "required",
|
|
663
663
|
tier: "primary",
|
|
664
664
|
intent: "Execute a reviewed Agent DAG spec.",
|
|
665
|
-
usage: "run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]] [--events-jsonl <path>] [--worker-association <json>]",
|
|
665
|
+
usage: "run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--quiet] [--progress-interval-ms N] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]] [--events-jsonl <path>] [--worker-association <json>]",
|
|
666
666
|
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
667
667
|
const runDagArgs = [subcommand, ...rest].filter((arg) => Boolean(arg));
|
|
668
668
|
await runRunDag(repoRoot, runDagArgs);
|
package/dist/cli/program.js
CHANGED
|
@@ -124,18 +124,8 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
124
124
|
await runDoctor(ctx.adapter, ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
125
125
|
return;
|
|
126
126
|
case "docs":
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (subcommand === "archive") {
|
|
132
|
-
const [planPath] = rest;
|
|
133
|
-
if (!planPath)
|
|
134
|
-
throw new Error("usage: docs archive <active-plan-path>");
|
|
135
|
-
await runDocsArchive(ctx.repoRoot, planPath);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
throw new Error("usage: docs <audit|archive> ...");
|
|
127
|
+
await runDocsCommand(ctx.repoRoot, subcommand, rest);
|
|
128
|
+
return;
|
|
139
129
|
case "new-task": {
|
|
140
130
|
const [taskId, ...titleParts] = [subcommand, ...rest];
|
|
141
131
|
if (!taskId)
|
|
@@ -189,53 +179,14 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
189
179
|
await runStats(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
190
180
|
return;
|
|
191
181
|
case "plan":
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (subcommand === "create") {
|
|
197
|
-
const [planId, ...titleParts] = rest;
|
|
198
|
-
if (!planId)
|
|
199
|
-
throw new Error('usage: plan create <plan-id> "<title>"');
|
|
200
|
-
await runPlanCreate(ctx.repoRoot, planId, titleParts.join(" "));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (subcommand === "complete") {
|
|
204
|
-
const [planId, ...summaryParts] = rest;
|
|
205
|
-
if (!planId)
|
|
206
|
-
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
207
|
-
const summary = parsePlanSummaryFlag(summaryParts);
|
|
208
|
-
if (!summary)
|
|
209
|
-
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
210
|
-
await runPlanComplete(ctx.repoRoot, planId, { summary });
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
if (subcommand === "check") {
|
|
214
|
-
await runPlanCheck(ctx.repoRoot);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
throw new Error("usage: plan <list|create|complete|check> ...");
|
|
182
|
+
await runPlanCommand(ctx.repoRoot, subcommand, rest);
|
|
183
|
+
return;
|
|
218
184
|
case "spine":
|
|
219
185
|
await runSpine(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
220
186
|
return;
|
|
221
187
|
case "handoff":
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (subcommand === "coverage") {
|
|
227
|
-
const [taskId, ...flags] = rest;
|
|
228
|
-
if (!taskId) {
|
|
229
|
-
throw new Error("usage: handoff coverage <taskId> [--json|--markdown]");
|
|
230
|
-
}
|
|
231
|
-
const exitCode = await runCoverageAudit(ctx.repoRoot, taskId, {
|
|
232
|
-
json: flags.includes("--json"),
|
|
233
|
-
markdown: flags.includes("--markdown"),
|
|
234
|
-
});
|
|
235
|
-
process.exitCode = exitCode;
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
throw new Error("usage: handoff <check|coverage> [taskId]");
|
|
188
|
+
await runHandoffCommand(ctx.repoRoot, subcommand, rest);
|
|
189
|
+
return;
|
|
239
190
|
case "coverage":
|
|
240
191
|
await runCoverageReport(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
241
192
|
return;
|
|
@@ -243,14 +194,8 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
243
194
|
await runGoal(ctx.repoRoot, subcommand, rest);
|
|
244
195
|
return;
|
|
245
196
|
case "reference":
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (!taskId)
|
|
249
|
-
throw new Error("usage: reference index <task-id>");
|
|
250
|
-
await runReferenceIndex(ctx.repoRoot, taskId);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
throw new Error("usage: reference <index> <task-id>");
|
|
197
|
+
await runReferenceCommand(ctx.repoRoot, subcommand, rest);
|
|
198
|
+
return;
|
|
254
199
|
case "study":
|
|
255
200
|
if (subcommand === "init") {
|
|
256
201
|
await runStudyInit(ctx.repoRoot, rest);
|
|
@@ -305,6 +250,78 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
305
250
|
throw new Error(`commander command is not wired to an action: ${command}`);
|
|
306
251
|
}
|
|
307
252
|
}
|
|
253
|
+
async function runDocsCommand(repoRoot, subcommand, rest) {
|
|
254
|
+
if (subcommand === "audit") {
|
|
255
|
+
await runDocsAudit(repoRoot);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (subcommand === "archive") {
|
|
259
|
+
const [planPath] = rest;
|
|
260
|
+
if (!planPath)
|
|
261
|
+
throw new Error("usage: docs archive <active-plan-path>");
|
|
262
|
+
await runDocsArchive(repoRoot, planPath);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
throw new Error("usage: docs <audit|archive> ...");
|
|
266
|
+
}
|
|
267
|
+
async function runPlanCommand(repoRoot, subcommand, rest) {
|
|
268
|
+
if (subcommand === "list" || subcommand === undefined) {
|
|
269
|
+
await runPlanList(repoRoot);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (subcommand === "create") {
|
|
273
|
+
const [planId, ...titleParts] = rest;
|
|
274
|
+
if (!planId)
|
|
275
|
+
throw new Error('usage: plan create <plan-id> "<title>"');
|
|
276
|
+
await runPlanCreate(repoRoot, planId, titleParts.join(" "));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (subcommand === "complete") {
|
|
280
|
+
const [planId, ...summaryParts] = rest;
|
|
281
|
+
if (!planId) {
|
|
282
|
+
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
283
|
+
}
|
|
284
|
+
const summary = parsePlanSummaryFlag(summaryParts);
|
|
285
|
+
if (!summary) {
|
|
286
|
+
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
287
|
+
}
|
|
288
|
+
await runPlanComplete(repoRoot, planId, { summary });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (subcommand === "check") {
|
|
292
|
+
await runPlanCheck(repoRoot);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
throw new Error("usage: plan <list|create|complete|check> ...");
|
|
296
|
+
}
|
|
297
|
+
async function runHandoffCommand(repoRoot, subcommand, rest) {
|
|
298
|
+
if (subcommand === "check") {
|
|
299
|
+
await runHandoffCheck(repoRoot, rest[0]);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (subcommand === "coverage") {
|
|
303
|
+
const [taskId, ...flags] = rest;
|
|
304
|
+
if (!taskId) {
|
|
305
|
+
throw new Error("usage: handoff coverage <taskId> [--json|--markdown]");
|
|
306
|
+
}
|
|
307
|
+
process.exitCode = await runCoverageAudit(repoRoot, taskId, {
|
|
308
|
+
json: flags.includes("--json"),
|
|
309
|
+
markdown: flags.includes("--markdown"),
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
throw new Error("usage: handoff <check|coverage> [taskId]");
|
|
314
|
+
}
|
|
315
|
+
async function runReferenceCommand(repoRoot, subcommand, rest) {
|
|
316
|
+
if (subcommand === "index") {
|
|
317
|
+
const [taskId] = rest;
|
|
318
|
+
if (!taskId)
|
|
319
|
+
throw new Error("usage: reference index <task-id>");
|
|
320
|
+
await runReferenceIndex(repoRoot, taskId);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
throw new Error("usage: reference <index> <task-id>");
|
|
324
|
+
}
|
|
308
325
|
async function runDagAction(repoRoot, subcommand, rest) {
|
|
309
326
|
const args = compactArgs(rest);
|
|
310
327
|
switch (subcommand) {
|
|
@@ -439,6 +456,8 @@ function configureRunDag(command) {
|
|
|
439
456
|
.option("--dry-run", "validate and plan without executing")
|
|
440
457
|
.option("--max-concurrent <n>", "maximum concurrent nodes")
|
|
441
458
|
.option("--run-id <id>", "run id")
|
|
459
|
+
.option("--quiet", "disable run/node progress on stderr")
|
|
460
|
+
.option("--progress-interval-ms <n>", "stderr heartbeat interval in milliseconds (minimum 1000)")
|
|
442
461
|
.option("--canvas-path <path>", "absolute canvas output path")
|
|
443
462
|
.option("--canvas <name>", "canvas name")
|
|
444
463
|
.option("--canvases-dir <dir>", "canvas output directory")
|