@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 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");
@@ -55,6 +55,7 @@ export async function runDagUseCase(input) {
55
55
  })
56
56
  : undefined;
57
57
  const observer = composeDagRunObservers([
58
+ input.observer,
58
59
  canvas?.observer,
59
60
  eventObserver?.observer,
60
61
  ]);
@@ -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);
@@ -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
- if (subcommand === "audit") {
128
- await runDocsAudit(ctx.repoRoot);
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
- if (subcommand === "list" || subcommand === undefined) {
193
- await runPlanList(ctx.repoRoot);
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
- if (subcommand === "check") {
223
- await runHandoffCheck(ctx.repoRoot, rest[0]);
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
- if (subcommand === "index") {
247
- const [taskId] = rest;
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")