@tea-agent/loop-agent 0.16.9 → 0.16.11
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.11] - 2026-07-20
|
|
22
|
+
|
|
23
|
+
### 修复
|
|
24
|
+
|
|
25
|
+
- Worker 报告决策不再把条件分支的 `SKIPPED` 节点当成失败;backend-test 等混合 DAG 在 revise/repair 未选中时,只要 ERROR 不存在且 run finished,即可 `report-completed` 并进入 Outcome/promote。
|
|
26
|
+
|
|
27
|
+
## [0.16.10] - 2026-07-20
|
|
28
|
+
|
|
29
|
+
### 修复
|
|
30
|
+
|
|
31
|
+
- 后端测试执行合同物化会清理 `managedCommand` 中的空字符串(如 `stop: ""`),避免 near-schema 侦察输出在 strict parse 下误拦 in-process BE-TEST。
|
|
32
|
+
|
|
21
33
|
## [0.16.9] - 2026-07-20
|
|
22
34
|
|
|
23
35
|
### 修复
|
|
@@ -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
|
-
|
|
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") {
|
|
@@ -334,22 +334,50 @@ function asRecord(value) {
|
|
|
334
334
|
* Prefer exact schema payloads; otherwise map common discovery shapes onto the
|
|
335
335
|
* pytest-centric runtime contract without inventing secrets or managed commands.
|
|
336
336
|
*/
|
|
337
|
+
/** Drop empty managedCommand strings; omit block when not managed-command. */
|
|
338
|
+
export function sanitizeBackendTestExecutionInput(value) {
|
|
339
|
+
const record = asRecord(value);
|
|
340
|
+
if (!record)
|
|
341
|
+
return value;
|
|
342
|
+
const next = { ...record };
|
|
343
|
+
const managed = asRecord(record.managedCommand);
|
|
344
|
+
if (!managed)
|
|
345
|
+
return next;
|
|
346
|
+
const cleaned = {};
|
|
347
|
+
for (const key of ["start", "stop", "sourceRef"]) {
|
|
348
|
+
const raw = managed[key];
|
|
349
|
+
if (typeof raw === "string" && raw.trim())
|
|
350
|
+
cleaned[key] = raw.trim();
|
|
351
|
+
}
|
|
352
|
+
if (next.targetMode === "managed-command") {
|
|
353
|
+
next.managedCommand = cleaned;
|
|
354
|
+
}
|
|
355
|
+
else if (Object.keys(cleaned).length === 0) {
|
|
356
|
+
delete next.managedCommand;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// in-process / external: optional managedCommand must not carry empty strings
|
|
360
|
+
next.managedCommand = cleaned;
|
|
361
|
+
}
|
|
362
|
+
return next;
|
|
363
|
+
}
|
|
337
364
|
export function coerceBackendTestExecutionInput(value) {
|
|
338
|
-
const
|
|
365
|
+
const sanitized = sanitizeBackendTestExecutionInput(value);
|
|
366
|
+
const direct = backendTestExecutionContractSchema.safeParse(sanitized);
|
|
339
367
|
if (direct.success)
|
|
340
368
|
return direct.data;
|
|
341
|
-
const record = asRecord(
|
|
369
|
+
const record = asRecord(sanitized);
|
|
342
370
|
if (!record)
|
|
343
|
-
return
|
|
371
|
+
return sanitized;
|
|
344
372
|
// Near-schema payloads (string framework + runner + testRoot) must stay fail-closed.
|
|
345
373
|
// Only free-form discovery envelopes are rewritten onto the pytest contract.
|
|
346
374
|
const looksSchemaShaped = typeof record.framework === "string" &&
|
|
347
375
|
asRecord(record.runner) !== null &&
|
|
348
376
|
typeof record.testRoot === "string";
|
|
349
377
|
if (looksSchemaShaped)
|
|
350
|
-
return
|
|
378
|
+
return sanitized;
|
|
351
379
|
if (typeof record.framework === "string" && record.framework !== "pytest") {
|
|
352
|
-
return
|
|
380
|
+
return sanitized;
|
|
353
381
|
}
|
|
354
382
|
const frameworkObj = asRecord(record.framework);
|
|
355
383
|
const discovered = asRecord(record.discoveredFixtures);
|
|
@@ -504,7 +532,10 @@ export async function materializeBackendTestExecutionContract(input) {
|
|
|
504
532
|
catch (error) {
|
|
505
533
|
throw new Error(`invalid-output: ${error instanceof Error ? error.message : String(error)}`);
|
|
506
534
|
}
|
|
507
|
-
const candidates = [
|
|
535
|
+
const candidates = [
|
|
536
|
+
sanitizeBackendTestExecutionInput(parsed),
|
|
537
|
+
coerceBackendTestExecutionInput(parsed),
|
|
538
|
+
];
|
|
508
539
|
let accepted = null;
|
|
509
540
|
let lastSchemaError = "invalid execution contract";
|
|
510
541
|
let lastSecretError = "";
|