@tea-agent/loop-agent 0.16.10 → 0.16.12

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
@@ -18,6 +18,18 @@
18
18
  - Pi SDK 执行长推理或大段结构化输出时不再把高频流式增量事件无界累积到内存;同一响应在多个生命周期事件中重复出现的 Token 用量只统计一次,避免 `Invalid string length` 和成本数据虚高。
19
19
  - 后端测试复合执行节点继续保持 clean environment、失败分类和 fail-closed outcome,并为 initial/final Result、repair eligibility、traceability 与 Observe 投影保留结构化运行证据。
20
20
 
21
+ ## [0.16.12] - 2026-07-20
22
+
23
+ ### 修复
24
+
25
+ - `backend-test` materialize / Git checkpoint 自动并入 `testcase/**`(与 `docs/test-reports/**`)运行时写根,避免 TaskSpec 仍写 `tests/api/**` 时绿跑无法 promote、任务卡在 Running。
26
+
27
+ ## [0.16.11] - 2026-07-20
28
+
29
+ ### 修复
30
+
31
+ - Worker 报告决策不再把条件分支的 `SKIPPED` 节点当成失败;backend-test 等混合 DAG 在 revise/repair 未选中时,只要 ERROR 不存在且 run finished,即可 `report-completed` 并进入 Outcome/promote。
32
+
21
33
  ## [0.16.10] - 2026-07-20
22
34
 
23
35
  ### 修复
@@ -6,6 +6,8 @@ import path from "node:path";
6
6
  import { z } from "zod";
7
7
  import { getTaskPoolRoot } from "../pool/run-store.js";
8
8
  import { assertSafeRuntimeId } from "../follow-up/paths.js";
9
+ import { expandAllowedPathsForWorkflow } from "../materialize/harness-task-materializer.js";
10
+ import { resolveWorkflow } from "../task-spec/workflow-routing.js";
9
11
  const execFileAsync = promisify(execFile);
10
12
  const checkpointSchema = z.object({
11
13
  taskId: z.string().min(1),
@@ -275,13 +277,14 @@ function auditChangedPaths(changes, taskSpec) {
275
277
  throw new Error(audit.violations[0]);
276
278
  }
277
279
  function auditChangedPathsReport(changes, taskSpec) {
280
+ const allowedPaths = expandAllowedPathsForWorkflow(resolveWorkflow(taskSpec).workflow, taskSpec.constraints.allowed_paths);
278
281
  const violations = [];
279
282
  for (const changed of changes) {
280
283
  if (/(^|\/)(\.env(?:\.|$)|[^/]*\.(?:pem|key|p12|pfx))$/i.test(changed))
281
284
  violations.push(`changed path may contain sensitive material: ${changed}`);
282
285
  else if (taskSpec.constraints.forbidden_paths.some((glob) => matchesGlob(changed, glob)))
283
286
  violations.push(`changed path is forbidden for ${taskSpec.id}: ${changed}`);
284
- else if (!taskSpec.constraints.allowed_paths.some((glob) => matchesGlob(changed, glob)))
287
+ else if (!allowedPaths.some((glob) => matchesGlob(changed, glob)))
285
288
  violations.push(`changed path is outside allowed_paths for ${taskSpec.id}: ${changed}`);
286
289
  }
287
290
  return { ok: violations.length === 0, violations };
@@ -34,13 +34,17 @@ export async function materializeTaskSpec(options) {
34
34
  }
35
35
  const paths = getTaskPaths(options.repoRoot, harnessTaskId);
36
36
  const baseConfig = await loadTaskConfig(options.repoRoot, harnessTaskId);
37
+ // backend-test hybrid DAG writes under testcase/** (pytest + md cases + helpers).
38
+ // TaskSpecs often list legacy tests/api/** surfaces; union runtime write roots so
39
+ // git checkpoint / path guard can promote green BE-TEST runs.
40
+ const allowedPaths = expandAllowedPathsForWorkflow(resolvedWorkflow.workflow, options.taskSpec.constraints.allowed_paths);
37
41
  const taskConfig = {
38
42
  ...baseConfig,
39
43
  taskId: harnessTaskId,
40
44
  title: options.taskSpec.title,
41
45
  taskKind: resolvedWorkflow.taskKind,
42
46
  featureId: options.taskSpec.feature_id,
43
- allowedPaths: options.taskSpec.constraints.allowed_paths,
47
+ allowedPaths,
44
48
  forbiddenPaths: options.taskSpec.constraints.forbidden_paths,
45
49
  hardConstraints: options.taskSpec.constraints.hard_constraints,
46
50
  complexity: mapRiskLevelToComplexity(options.taskSpec.risk_level),
@@ -312,6 +316,13 @@ function bulletLines(values) {
312
316
  return ["- None"];
313
317
  return values.map((value) => `- ${value}`);
314
318
  }
319
+ /** Union TaskSpec allowed_paths with workflow-owned write roots. */
320
+ export function expandAllowedPathsForWorkflow(workflow, allowedPaths) {
321
+ const extras = workflow === "backend-test"
322
+ ? ["testcase/**", "docs/test-reports/**"]
323
+ : [];
324
+ return Array.from(new Set([...allowedPaths, ...extras]));
325
+ }
315
326
  async function loadExistingTaskConfig(repoRoot, taskId) {
316
327
  try {
317
328
  return await loadTaskConfig(repoRoot, taskId);
@@ -455,7 +455,8 @@ function isPathInside(root, target) {
455
455
  const relative = path.relative(root, target);
456
456
  return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
457
457
  }
458
- function decideFromReport(workerRunId, result) {
458
+ /** Exported for unit tests: map DAG report JSON into Worker report decision. */
459
+ export function decideFromReport(workerRunId, result) {
459
460
  if (!result.ok || !result.json) {
460
461
  return {
461
462
  succeeded: false,
@@ -473,11 +474,16 @@ function decideFromReport(workerRunId, result) {
473
474
  }
474
475
  const runStatus = readString(run, "status");
475
476
  const nodes = readObjectArray(run, "nodes");
477
+ // Condition-branch SKIPPED nodes are expected on green backend-test / hybrid
478
+ // paths (e.g. revise/repair not selected). Do not treat SKIPPED as failure.
479
+ // ERROR and non-success failureCategory on executed nodes still fail closed.
476
480
  const failedNode = nodes.find((node) => {
477
481
  const status = readString(node, "status");
478
482
  const failureCategory = readString(node, "failureCategory");
483
+ if (status === "SKIPPED") {
484
+ return false;
485
+ }
479
486
  return (status === "ERROR" ||
480
- status === "SKIPPED" ||
481
487
  (Boolean(failureCategory) && failureCategory !== "success"));
482
488
  });
483
489
  if (runStatus !== "completed" && runStatus !== "finished") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.16.10",
3
+ "version": "0.16.12",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",