@tea-agent/loop-agent 0.16.22 → 0.16.23

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
@@ -4,6 +4,8 @@
4
4
 
5
5
  ### 修复
6
6
 
7
+ - Backend-test 唯一 JSON object 提取器在扫描裸对象前会等长忽略完整 Markdown 行内代码中的对象形状示例(如 `{error:{code,message,type}}`);唯一真实 envelope 可正常物化,多个真实 JSON、fence 外额外对象与未闭合对象继续 fail-closed。
8
+
7
9
  - Backend-test 节点 10 `backend-test-semantic-gate-shell` 不再用 Markdown `first-verdict-line` 读取 raw Pi 输出;改为同一 `semantic-initial` pipeline 按 task id 只读 `contracts/backend-test-semantic-review.json`,仅 `verdict=pass` 放行 pytest,`request-revision`/缺失/malformed 清晰 fail-closed;节点 09 仍只负责 materialize+trace,15 节点与单次 pytest 不变。
8
10
  - 公开 Case Manifest schema(`docs/templates/backend-test-case-manifest.schema.json`)对齐 materializer dual-scope coverage:`coverageSummary` 在 `additionalProperties:false` 下 required 并声明 `global`/`inScope`(`total`/`covered`/`gapped`/`ratio`)与 `crossDomainGapCount`;回归测试以无依赖轻量 contract 锁定 schema↔canonical 一致性;不改 materializer/15 节点。
9
11
  - Backend-test Case Manifest materializer 将模型顶层 `coverageSummary` 视为非权威:secret 扫描后剥离,再 strict parse 其余字段,并始终 `computeCaseManifestCoverageSummary` 写入 canonical summary;my-webapp 自定义/部分 summary 不再导致 invalid-output;prompt 禁止模型输出 coverageSummary;15 节点与单次 pytest 不变。
@@ -35,6 +37,12 @@
35
37
  - Pi SDK 执行长推理或大段结构化输出时不再把高频流式增量事件无界累积到内存;同一响应在多个生命周期事件中重复出现的 Token 用量只统计一次,避免 `Invalid string length` 和成本数据虚高。
36
38
  - 后端测试复合执行节点继续保持 clean environment、失败分类和 fail-closed outcome,并为唯一 JUnit/initial Result、canonical Result、traceability 与 Observe 投影保留结构化运行证据。
37
39
 
40
+ ## [0.16.23] - 2026-07-21
41
+
42
+ ### 修复
43
+
44
+ - DAG runner 在归档到 `completed/` 前停止 heartbeat 并排空 state 写队列。避免 terminal 节点全 FINISHED 后仍因 completed-facts write guard 拒绝写 `state.json` 导致 `run-dag` 非零退出、Worker 记 `run-error`/Task Pool `Blocked`。
45
+
38
46
  ## [0.16.22] - 2026-07-21
39
47
 
40
48
  ### 修复
@@ -6,6 +6,44 @@
6
6
  * Semantic review / classification materializers reuse the same scanner.
7
7
  */
8
8
  const JSON_FENCE_RE = /```json\s*\r?\n([\s\S]*?)\r?\n```/gi;
9
+ /**
10
+ * Mask complete Markdown inline-code spans without changing source offsets.
11
+ * Object-shaped examples inside backticks are prose, not JSON candidates.
12
+ * Unclosed delimiters stay visible so malformed output cannot hide conflicts.
13
+ */
14
+ function maskMarkdownInlineCode(text) {
15
+ const chars = text.split("");
16
+ for (let index = 0; index < text.length;) {
17
+ if (text[index] !== "`") {
18
+ index += 1;
19
+ continue;
20
+ }
21
+ let delimiterLength = 1;
22
+ while (text[index + delimiterLength] === "`")
23
+ delimiterLength += 1;
24
+ if (delimiterLength >= 3) {
25
+ index += delimiterLength;
26
+ continue;
27
+ }
28
+ const delimiter = "`".repeat(delimiterLength);
29
+ let close = text.indexOf(delimiter, index + delimiterLength);
30
+ while (close >= 0 &&
31
+ (text[close - 1] === "`" || text[close + delimiterLength] === "`")) {
32
+ close = text.indexOf(delimiter, close + delimiterLength);
33
+ }
34
+ if (close < 0) {
35
+ index += delimiterLength;
36
+ continue;
37
+ }
38
+ const end = close + delimiterLength;
39
+ for (let cursor = index; cursor < end; cursor += 1) {
40
+ if (chars[cursor] !== "\n" && chars[cursor] !== "\r")
41
+ chars[cursor] = " ";
42
+ }
43
+ index = end;
44
+ }
45
+ return chars.join("");
46
+ }
9
47
  function isPlainObject(value) {
10
48
  return value !== null && typeof value === "object" && !Array.isArray(value);
11
49
  }
@@ -121,7 +159,7 @@ function findUniqueJsonObjectText(text) {
121
159
  if (fences.length === 1) {
122
160
  const fence = fences[0];
123
161
  const outsideScans = [trimmed.slice(0, fence.start), trimmed.slice(fence.end)]
124
- .map((fragment) => scanBalancedObjectSpans(fragment));
162
+ .map((fragment) => scanBalancedObjectSpans(maskMarkdownInlineCode(fragment)));
125
163
  if (outsideScans.some((scan) => scan.unclosedObject)) {
126
164
  throw new Error("backend-test unique JSON object: unclosed JSON object outside fenced json block");
127
165
  }
@@ -131,7 +169,7 @@ function findUniqueJsonObjectText(text) {
131
169
  }
132
170
  return fence.body;
133
171
  }
134
- const scan = scanBalancedObjectSpans(trimmed);
172
+ const scan = scanBalancedObjectSpans(maskMarkdownInlineCode(trimmed));
135
173
  if (scan.unclosedObject) {
136
174
  throw new Error("backend-test unique JSON object: unclosed JSON object; expected one unique balanced object");
137
175
  }
@@ -469,6 +469,11 @@ async function executeDagCheckpoint(input) {
469
469
  }
470
470
  break;
471
471
  }
472
+ // Stop heartbeats before terminal archive. A late heartbeat writing
473
+ // state.json under completed/ trips the completed-facts write guard and
474
+ // makes run-dag exit non-zero after every node already finished.
475
+ clearInterval(heartbeatTimer);
476
+ await stateWriteQueue;
472
477
  state.finishedAt = new Date().toISOString();
473
478
  const runDirBeforeTransfer = runDir;
474
479
  await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.16.22",
3
+ "version": "0.16.23",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",