@tea-agent/loop-agent 0.25.2 → 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/AGENTS.md +1 -0
- package/CHANGELOG.md +37 -0
- package/dist/application/dag/args.js +21 -1
- package/dist/application/dag/run-dag.js +1 -0
- package/dist/cli/command-definitions.js +2 -2
- package/dist/cli/program.js +84 -64
- package/dist/commands/client-recovery.js +657 -0
- package/dist/commands/init.js +283 -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/architecture/runtime-boundaries.md +13 -0
- package/docs/init-surface.manifest.json +6 -2
- 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/SKILL.md +2 -1
- package/skills/loop-agent/references/command-reference.md +2 -1
package/AGENTS.md
CHANGED
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
- 长期决策写入 `docs/`;面向用户变更更新 `CHANGELOG.md`(结果导向中文)。
|
|
61
61
|
- init/投影变更必须同步目标项目生成物与 package assets;init evolution 按 `docs/init-surface.manifest.json` 分级。
|
|
62
62
|
- CLI/skill entry/runtime boundary/发布包变更同步 catalog、脚本与测试。
|
|
63
|
+
- 明确的前端页面/UI/组件/交互实现需求必须设置 `taskKind: "frontend-implementation"`(不是 `--profile`),不得保留默认 `standard`;浏览器/UI 自动化测试继续使用 `taskKind: "frontend-test"`。
|
|
63
64
|
- 没有新鲜验证证据时不声明完成;新债写入 plan/progress/report。
|
|
64
65
|
|
|
65
66
|
## 验证
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
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
|
+
|
|
20
|
+
## [0.25.3] - 2026-07-30
|
|
21
|
+
|
|
22
|
+
### 重点更新
|
|
23
|
+
|
|
24
|
+
- 新增 OpenCode 客户端瞬态会话恢复机制,在内置重试遗漏时自动补偿 UnknownError
|
|
25
|
+
- 优化前端实现需求路由,将前端开发与测试任务准确分发至对应 DAG 流程
|
|
26
|
+
|
|
27
|
+
### 新增
|
|
28
|
+
|
|
29
|
+
- loop-agent init 支持 --client-recovery 参数,可在目标项目生成 OpenCode 插件以补偿瞬态 UnknownError
|
|
30
|
+
- 支持在用户级别原子写入 Pi retry 推荐配置,并严格遵守插件 ownership 不覆盖用户改动
|
|
31
|
+
|
|
32
|
+
### 改进
|
|
33
|
+
|
|
34
|
+
- 初始化或刷新 AGENTS.md 时,要求将明确的前端实现需求路由至 frontend-implementation,与浏览器和 UI 自动化测试区分开
|
|
35
|
+
|
|
36
|
+
### 修复
|
|
37
|
+
|
|
38
|
+
- 修复生成的 OpenCode 恢复插件无法按真实 API 工作的问题,现直接返回 Hooks.event 并按事件正确分发与续接
|
|
39
|
+
- 修复 Type validation failed 等真实错误被业务校验规则误杀的问题
|
|
40
|
+
- 修复 Pi retry 配置读取将权限或 I/O 错误误判为缺文件的问题,现仅 ENOENT 视为缺文件
|
|
41
|
+
|
|
5
42
|
## [0.25.2] - 2026-07-30
|
|
6
43
|
|
|
7
44
|
### 重点更新
|
|
@@ -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");
|
|
@@ -216,7 +216,7 @@ export const COMMAND_DEFINITIONS = [
|
|
|
216
216
|
adapter: "none",
|
|
217
217
|
tier: "primary",
|
|
218
218
|
intent: "Initialize a target repository with loop-agent harness capabilities.",
|
|
219
|
-
usage: "init [instructions|doctor|check-update|update|reconcile] [--profile full|minimal] [--merge] [--json|--markdown] [--bootstrap-surface|--apply-safe]",
|
|
219
|
+
usage: "init [instructions|doctor|check-update|update|reconcile] [--profile full|minimal] [--merge] [--json|--markdown] [--bootstrap-surface|--apply-safe] [--client-recovery=auto|project|user|off]",
|
|
220
220
|
subcommands: [...INIT_SUBCOMMANDS],
|
|
221
221
|
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
222
222
|
await runInit(repoRoot, [subcommand, ...rest].filter(Boolean), { readRuntimeActivity: readInitRuntimeActivity });
|
|
@@ -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")
|
|
@@ -617,7 +636,8 @@ export function buildLoopAgentProgram(options) {
|
|
|
617
636
|
.option("--json", "print JSON")
|
|
618
637
|
.option("--markdown", "print Markdown")
|
|
619
638
|
.option("--bootstrap-surface", "write an inferred .harness/init-surface.json baseline")
|
|
620
|
-
.option("--apply-safe", "apply deterministic safe init updates")
|
|
639
|
+
.option("--apply-safe", "apply deterministic safe init updates")
|
|
640
|
+
.option("--client-recovery <mode>", "auto|project|user|off — OpenCode project plugin and optional Pi user retry config", "auto");
|
|
621
641
|
command.action(async (args, _options, actionCommand) => runInitCommand(args, actionCommand, options.defaultRepoRoot));
|
|
622
642
|
addStandaloneSubcommands(command, entry.subcommands ?? [], (args, actionCommand) => runInitCommand(args, actionCommand, options.defaultRepoRoot));
|
|
623
643
|
program.addCommand(command);
|