@tea-agent/loop-agent 0.16.18 → 0.16.19

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
@@ -24,6 +24,12 @@
24
24
  - Pi SDK 执行长推理或大段结构化输出时不再把高频流式增量事件无界累积到内存;同一响应在多个生命周期事件中重复出现的 Token 用量只统计一次,避免 `Invalid string length` 和成本数据虚高。
25
25
  - 后端测试复合执行节点继续保持 clean environment、失败分类和 fail-closed outcome,并为 initial/final Result、repair eligibility、traceability 与 Observe 投影保留结构化运行证据。
26
26
 
27
+ ## [0.16.19] - 2026-07-20
28
+
29
+ ### 修复
30
+
31
+ - frontend-test result 物化在比较 evidence 路径前先 `realpath` 仓库与 evidence 根,避免 macOS `/var`→`/private/var` 导致的 `evidence realpath escapes its allowed root` 误拦。
32
+
27
33
  ## [0.16.18] - 2026-07-20
28
34
 
29
35
  ### 修复
@@ -75,27 +75,32 @@ async function readEvidenceFile(repoRoot, evidenceRoot, relative) {
75
75
  if (!safeRelativePathSchema.safeParse(relative).success) {
76
76
  throw new Error(`unsafe evidence path: ${relative}`);
77
77
  }
78
+ // Resolve roots first so macOS /var vs /private/var does not look like an escape.
79
+ const repoRootResolved = await realpath(repoRoot);
80
+ const evidenceRootResolved = await realpath(evidenceRoot);
81
+ assertInside(repoRootResolved, evidenceRootResolved, "evidenceDir");
78
82
  // Accept either evidence-root-relative paths or repo-relative paths under the case evidence root.
79
83
  const normalized = relative.replaceAll("\\", "/");
80
- const evidenceRootRel = path.relative(repoRoot, evidenceRoot).replaceAll("\\", "/");
84
+ const evidenceRootRel = path.relative(repoRootResolved, evidenceRootResolved).replaceAll("\\", "/");
81
85
  let candidate = normalized;
82
86
  if (normalized === evidenceRootRel || normalized.startsWith(`${evidenceRootRel}/`)) {
83
- candidate = path.relative(evidenceRoot, path.resolve(repoRoot, normalized)).replaceAll("\\", "/");
87
+ candidate = path.relative(evidenceRootResolved, path.resolve(repoRootResolved, normalized)).replaceAll("\\", "/");
84
88
  }
85
89
  else if (normalized.startsWith("testcase/frontend/evidence/")) {
86
- candidate = path.relative(evidenceRoot, path.resolve(repoRoot, normalized)).replaceAll("\\", "/");
90
+ candidate = path.relative(evidenceRootResolved, path.resolve(repoRootResolved, normalized)).replaceAll("\\", "/");
87
91
  }
88
- if (!candidate || candidate.startsWith("..")) {
92
+ if (!candidate || candidate.startsWith("..") || path.isAbsolute(candidate)) {
89
93
  throw new Error(`evidence path escapes evidence root: ${relative}`);
90
94
  }
91
- const absolute = path.resolve(evidenceRoot, candidate);
92
- assertInside(evidenceRoot, absolute, "evidence path");
95
+ const absolute = path.resolve(evidenceRootResolved, candidate);
96
+ assertInside(evidenceRootResolved, absolute, "evidence path");
93
97
  const resolved = await realpath(absolute);
94
- assertInside(repoRoot, resolved, "evidence realpath");
98
+ assertInside(repoRootResolved, resolved, "evidence realpath");
99
+ assertInside(evidenceRootResolved, resolved, "evidence realpath under case root");
95
100
  const info = await lstat(resolved);
96
101
  if (!info.isFile())
97
102
  throw new Error(`evidence is not a file: ${relative}`);
98
- const storedPath = path.relative(repoRoot, resolved).replaceAll("\\", "/");
103
+ const storedPath = path.relative(repoRootResolved, resolved).replaceAll("\\", "/");
99
104
  return { path: storedPath, sha256: sha256(await readFile(resolved)) };
100
105
  }
101
106
  export async function materializeFrontendTestResult(input) {
@@ -129,8 +134,9 @@ export async function materializeFrontendTestResult(input) {
129
134
  throw new Error(`unknown AC in manifest: ${acId}`);
130
135
  covered.add(acId);
131
136
  }
132
- const evidenceRoot = path.resolve(input.workspaceRoot, item.evidenceDir);
133
- assertInside(input.workspaceRoot, evidenceRoot, "evidenceDir");
137
+ const workspaceRootResolved = await realpath(input.workspaceRoot);
138
+ const evidenceRoot = path.resolve(workspaceRootResolved, item.evidenceDir);
139
+ assertInside(workspaceRootResolved, evidenceRoot, "evidenceDir");
134
140
  const resultRaw = JSON.parse(await readFile(path.join(evidenceRoot, "case-result.json"), "utf-8"));
135
141
  const status = caseStatusSchema.parse(resultRaw.status);
136
142
  if (resultRaw.caseId !== item.caseId || !Array.isArray(resultRaw.evidencePaths))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.16.18",
3
+ "version": "0.16.19",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",