@tea-agent/loop-agent 0.38.1 → 0.38.3
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 +126 -12
- package/README.md +2 -0
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init.js +2 -14
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +99 -5
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +324 -16
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/governance/manifest-types.js +0 -2
- package/dist/shared/operator/capabilities.js +87 -22
- package/dist/task/config-types.js +22 -0
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/pi-runtime.js +10 -5
- package/dist/worker/console/chat/routes.js +458 -47
- package/dist/worker/console/chat/semantic-activity.js +13 -13
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/harness-pi-model-matrix.js +2 -11
- package/dist/worker/console/operator-actions.js +5 -4
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/server.js +6 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Hfsot6rP.js → abnfDiagram-N423BO3Z-C4kVYweA.js} +1 -1
- package/dist/worker/console/static/assets/{arc-YGwEASin.js → arc-Bj2M8iO5.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CmOM0F92.js → architectureDiagram-T3A2C74G-CLlXe4-6.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DDLuaHC5.js → blockDiagram-VBNYF7ZC-psEp5xH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B0Um43W4.js → c4Diagram-5PPSVZJV-DeKcOCGJ.js} +1 -1
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DvBN4JZc.js → chunk-2GRJ4B5K-K0C744p1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B1UVgW4E.js → chunk-2Q5K7J3B-xbNw4J9-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Cf0GgXzX.js → chunk-5RXB4S5H-DjLaSDUO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DbNA24sj.js → chunk-5VM5RSS4-CtiPlKel.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bi_0Be06.js → chunk-6Q2QTUOP-DZtolir7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C8pPGvRd.js → chunk-GF5L2VYU-CPcdNeew.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BitQY8Z-.js → chunk-JWPE2WC7-NgHc6V37.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-U67GCiEo.js → chunk-KBJHAD2P-DQ_T-hg8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-IoJwXPBA.js → chunk-RYQCIY6F-DgLsxcVP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-YDZ_UHgE.js → chunk-XXDRQBXY-UrVoM9z1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CUZgsiRC.js → cose-bilkent-JH36ORCC-CLkYvTb3.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-COmCh4pT.js → cynefin-VYW2F7L2-BwT_xKrE.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Ta6FSm68.js → cynefinDiagram-MW4NZA55-Bi9tuzif.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UdcR-VW9.js → dagre-VZM6K2ZE-BCIqkWBV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DybcO3Et.js → diagram-7IWD3JNH-Dip6l9_Z.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-wErDFWLw.js → diagram-B4RE2ZJO-B-xkh_wK.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DkVHZWqz.js → diagram-LBJQPF4R-DZO0kTt_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DdqWs7QN.js → diagram-Q27KOJAE-D6ZplNbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-C9dMVmDo.js → diagram-UB23O5K3-BPekbhoS.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-B2T2WcBK.js → ebnfDiagram-BXEA7PRR-BtmaJpK5.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bt9rjDFq.js → erDiagram-JOGREHBK-6DS0Xc44.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-U8ic_tvi.js → flowDiagram-UKHOOZJN-CihnROSm.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BfQJdQoO.js → ganttDiagram-PKOTCBZU-CZC4zOE2.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CpMftH4O.js → gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js} +1 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +404 -0
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DzqWXNO_.js → infoDiagram-6WML65LV-DlSl4BGx.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CJmGQ5Yp.js → ishikawaDiagram-WSZJBQD7-snnzFl_U.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ChPtKFLz.js → journeyDiagram-NVQOT4AX-BDE7qpMx.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-PRe7wB5u.js → kanban-definition-27J2QSJJ-CD2Ci04G.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DpKX6QNp.js → linear-DdnapKIH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Dnl76b7Y.js → mermaid.core-BVjAT9b8.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-S9PCXc3J.js → mindmap-definition-FAOFIHXS-D0yJk-zA.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Du8e-FSX.js → pegDiagram-VL7TDLO6-BVo9tFP-.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CY6OmHTc.js → pieDiagram-7S7Q4E2Y-DnOV-mZ_.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C65GPhpj.js → quadrantDiagram-CIZ2JOQS-CzUJo58i.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-U6IUiTCL.js → railroadDiagram-AXF67PYL-Bvikrolh.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-I_9Qhi.js → requirementDiagram-LRYGKXZP-DtaSVCap.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B57Fhed0.js → sankeyDiagram-W5VNT64P-C2c9A0wW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CJOtiw21.js → sequenceDiagram-SI44F4Z6-DmXJcU7r.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-FyX8zxSh.js → sizeCapture-X5ZJPWSS-B4GUFW92.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-eF-pbs9t.js → stateDiagram-OKZ733FA-Dsad2MXf.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CcfkA-Dq.js → swimlanes-SLNWSIFB-DGq48Fbi.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DH8FjnOK.js → timeline-definition-Z64GVDOM-CSSi6OFf.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-beoTagJg.js → vennDiagram-T6HMQDX7-BqyzPrWv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CQSfw4cK.js → wardleyDiagram-T6FBY63Y-DlznVb6S.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQgzQ6jn.js → xychartDiagram-ELKLHX3M-CfBcgJ_K.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +0 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +38 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +204 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +23 -1
- package/dist/worker/observe/node-input.js +72 -3
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/views/dag-inspector.js +51 -0
- package/dist/worker/observe/static/views/session-timeline.js +18 -5
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +809 -53
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +4 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +355 -26
- package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +10 -18
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/init-hybrid.js +720 -177
- package/dist/workflows/dag/node-execution.js +164 -26
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +112 -27
- package/dist/workflows/dag/rerun-run.js +3 -0
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +121 -0
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/harness.schema.json +0 -8
- package/harness.json +1 -8
- package/package.json +8 -4
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/dist/worker/console/static/assets/channel-Dp_2Aqf1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL--CODy37j.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE--CODy37j.js +0 -1
- package/dist/worker/console/static/assets/index-BA5-N5ER.css +0 -1
- package/dist/worker/console/static/assets/index-DKPXBrdc.js +0 -325
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B-mvnLEc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-JtpBmIv4.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### 变更
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- `next-release.yml` 防重复发布守卫补充幂等语义:`mode=recover`(dist-tags.next 已指向出自本 HEAD 的 tarball)时,手动 `workflow_dispatch` 重入不再撞 duplicate publish 硬失败,改为写 summary 后干净退出(§5.1 规则 4);非 recover 的重复发布仍硬失败。
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- 侧栏文件树点击目录行直接插入 `@目录/` 引用(箭头单独负责展开/收起);输入框 @ 补全也列出目录,排在文件前面。
|
|
15
|
-
- 对话进行中点「停止」立即安静下来(乐观停止,对齐 pi-web):不再等 POST /abort 往返;后端 abort 等待从 8s 降到 2.5s,未终结时由状态对账收敛。
|
|
16
|
-
- 侧栏文件树实时刷新:agent 回合结束、浏览器标签页回到前台、手动点「刷新」时重拉根目录+全部已展开目录(此前手动刷新只拉根目录,子目录永远旧缓存)。
|
|
17
|
-
- Skills / Packages / Models 面板不再把 Pi 的 warning / collision 原文堆在列表上方:改成标题条一颗摘要芯片(如「14 条提示」),点开按「需要处理 / 名称不规范 / 同名未生效」分组,文案用自然语言,路径只显示末两段;名称不规范的技能在列表旁标「提示」,详情里说明「现在这样也能用」。加载失败仍单独成组,可点「再读一次」。
|
|
18
|
-
- Skills / Packages 双栏改为左右各自滚动;资源弹窗挂到 `document.body`,按顶部对话导航的实时高度在剩余视口居中,不再被 sticky 顶栏挡住上沿。
|
|
9
|
+
- frozen repair 工具审计改为按 attempt 段扫描(B1):`session-events.jsonl` 跨 attempt 共享追加,attempt>1 的 `auditRepairAttemptToolUse` 现在只审本 attempt 字节段(newline 对齐),planner attempt 1 的 ls/find 不再被误判给 repair 轮;repair prompt 显式声明 read-only 工具契约与 allowlist 边界(B2),并内嵌 openspec-citations 输出格式正例;`LOOP_AGENT_IGNORE_STALE_DIST=1` 环境变量旁路 stale-dist guard(B3),Console spawn 的 sibling CLI 可继承。
|
|
10
|
+
- frontend plan/revision 结构化输出改为冻结 repair context + hash-bound canonical artifact:attempt>1 不再重放完整 planner prompt;成功后 stdout 只保留 artifact 路径,design review / prewrite 只消费已校验 artifact,不再从节点文本 merge。失败分类补齐 `structured-repair-exhausted` / `governance-blocked`,并修正 `structured-output-truncated` / `output-too-large` 的归一化映射。
|
|
11
|
+
- 将 `@earendil-works/pi-ai` 与 `@earendil-works/pi-coding-agent` 从可选依赖提升为精确 pin 的运行时依赖 `0.83.0`,将运行时直接导入的 `typebox` 精确声明为 `1.3.7`,并在根包声明 Node.js `>=22.19.0`。安装 `@tea-agent/loop-agent` 会默认装上 Pi SDK(含 `--omit=optional` 场景);`@cursor/sdk` 仍保持 optional。availability / readiness 探针、隔离 tarball canary、发布包契约测试与安装/本地环境说明同步对齐。
|
|
12
|
+
- backend-test Scenario Partition 仅接受绑定源有限 enum/分类集合的 GET/list query/path 轴;非法 Domain(空/重复/保留 token、无有限域、与 OpenAPI enum 不一致)报 `INVALID_PARTITION_DOMAIN` / `NO_FINITE_DOMAIN` / `PARTITION_SOURCE_MISMATCH` 根因,不再生成空后缀 `TP-SP-...-`、重复 slot,也不再级联 `MISSING_*_SLOT`。N6 对这些根因码 fail-closed;合法源分区缺槽仍阻断。N2 禁止为自由串/主键/required-only/boundary-only 建分区;N5 允许删除无源非法 Partition 及其派生槽位。
|
|
13
|
+
- npm `next` 渠道发布改为提交数节流:main push 仅当距上次 next 发布的 source commit ≥ 5 个 commit 时才自动发布(新增 `scripts/next-publish-gate.mjs`,读已发布 tarball build-stamp 对比 HEAD);不足阈值时 workflow 绿色跳过,手动 `workflow_dispatch` 不受限(切 release 线前强制出包 / 紧急补发)。发布文档真源 §4.1/§5.1/§7/§12 同步。
|
|
19
14
|
|
|
20
15
|
### 新增
|
|
21
16
|
|
|
17
|
+
- 观察面 DAG Inspector 节点详情适配 Pi 扩展:执行器输入摘要展示节点声明的 `piExtensions` 短 id;新增「Pi 扩展运行事实」展示 `pi-extensions.json` 的 resolved(包名@版本)与 missing 降级原因(缺包降级不再静默);产物未生成/损坏时降级提示不阻断接口,绝对路径不外泄。
|
|
18
|
+
- 后端实现 DAG(standard / review-gated / supervised)的 Pi 节点支持按桶显式加载本机 Pi 导航扩展:读侧节点(除 closeout)加载 `pi-codegraph` + `pi-lens`,写侧节点只加载 `pi-codegraph`(pi-lens 的 `ast_grep_replace` 可绕过 writeSet,不加载);前端/测试模板与省略字段节点保持扩展全关。扩展发现始终关闭,只经白名单解析 `~/.pi/agent` 与项目 `.pi` settings 的已装包(缺包降级不失败,产物 `pi-extensions.json` 记录 requested/resolved/missing);session 后 pin 工具面剔 builtin 同名、deny 面与写工具,writer 的 `edit`/`write` 仍为 policy-wrapped;`.codegraph/**` index 同步不再计入 write-guard 越界。
|
|
19
|
+
- Pi SDK 终态 session-events JSONL 顶层增加可选 `finish_reason`:仅当 `message_end` / `turn_end` / `assistant_message` 明确携带非空终止原因时由序列化层提升,不伪造默认值,也不回流 `stopReason` 诊断。
|
|
20
|
+
- `backendTest` 布局配置纳入 Task Contract managed 注入面:`task advance` 新增 `--backend-test <json>`,可在公共任务生命周期内把 `testRoot`/`markdownRel`/`scriptRel`/`sharedSetupDoc`/`mode`/`gapDoc`/`executeScope` 写进 `task.json`,不再需要手工编辑(手工编辑会让任务漂移成 `externally-modified` 并拒绝 `--approve-gate`)。给出即整体替换、省略即保留既有配置(历史手写配置因此自愈)、`'{}'` 为显式清空回默认布局;重复 advance 幂等,契约保持 `managed`。`task contract diff` 把值变更标 routine `BACKEND_TEST_CONFIG_CHANGED`、丢键/清空额外标 elevated `BACKEND_TEST_CONFIG_WEAKENED`。
|
|
21
|
+
- Operator Chat 文件预览抽屉新增图标「新窗口打开」:用当前已加载内容在独立浏览器窗口打开全页阅读视图,窗口内可切换「预览 / 查看原文」(带宽高的 `window.open` popup,Markdown 排版、HTML 铺满视口的空 sandbox iframe、JSON pretty-print / 其它文本等宽滚动);打开成功后断开 `opener` 且不立刻 revoke Blob,避免空白页;弹窗被拦截时 header 附近报错且不关闭抽屉
|
|
22
|
+
- backend-test 适配 A–D:可配置 `backendTest` 布局(`testRoot`/`markdownRel`/`scriptRel`)、用户公共前置文档引用与模块顶部 `user_shared_setup`(不写 conftest)、查询轴 Scenario Partitions 确定性展开与 fail-closed 门禁、`mode=gap-fill` 13 节点增量 DAG。Slot ID 以声明的 Partition ID 为真源(`TP-<Partition ID>-...`)。
|
|
22
23
|
- 前端 plan 唯一机器契约化(JSON-only):`frontend-plan-pi` / `frontend-plan-revision-pi` 输出收敛为「一行导语 + 唯一 fenced `json` 契约 + 唯一 `openspec-citations` 引用块」,彻底移除「可选 Markdown 说明」;plan.md(`contracts/frontend-plan.md`)改由 prewrite gate 用纯函数 `renderFrontendPlanMarkdown` 确定性渲染(不新增 DAG 节点/不新增 Pi 调用)
|
|
23
24
|
- 契约 schema 新增 5 个可选字段(`implementationSteps` / `stylingStrategy` / `dependencyPolicy` / `residualRisks` / `realIntegrationGap`),`schemaVersion` 保持 1;`docs/templates/frontend-implementation-contract.schema.json` 同步
|
|
24
25
|
- revision 输出改为 RFC 7386 merge-patch delta + 引用块:gate 配置新增 `revisionPatch`,生效时在 `planFallbackFromNodeIds[0]` 原始契约上应用 `applyFrontendImplementationContractPatch` 后进入既有分析/规范化/物化管线;旧 DAG 缺字段按整契约处理
|
|
@@ -36,10 +37,41 @@
|
|
|
36
37
|
- `src/shared/openspec-spec.ts` 新增 `extractTaskSourceOpenspecPaths` 纯函数(反引号内联、markdown 链接目标、裸路径 token → 归一化 → spec 过滤 → 治理子树排除 → 去重排序)
|
|
37
38
|
- 生成期把 `openspecPolicy` 与候选来源元数据(`openspecCandidateSources.declared` / `taskSourceCited` / `scanStrict`)冻结进 prewrite gate 配置;`task advance` 的 advisories 说明当前模式与候选来源
|
|
38
39
|
|
|
40
|
+
### 改进
|
|
41
|
+
|
|
42
|
+
- frontend DAG 的 materialized source references 恢复统一的有界 excerpt 与按需 read 指针,不再把 requirement/acceptance 文档全文复制进每个相关节点,也不再强制 contract/plan 重读全部绑定源文件,降低长需求在多节点中的重复上下文占用。
|
|
43
|
+
- frontend-test prepare-package 会从 `docs/templates/`、`harness.json` `governanceRoot`/templates 以及 init 投影的 `ai_workspace/loop-agent/templates/` 拷贝标准场景;full init 目标仓不再静默落到仅含 `STD-FE-SMOKE-ENTRY` 的 fallback。
|
|
44
|
+
- frontend-test 环境探测区分 connection-refused(curl 7)与 HTTP 4xx/5xx,并提示先启动本地前端后再从 probe 节点续跑;不再把所有不可达都写成同一句 `frontend-base-url-unreachable`。
|
|
45
|
+
- frontend-test 报告解析用例 Markdown 的 `##`/`###` 标题与有序列表(`测试步骤` / `预期结果` / `前置条件与重置`);缺 `测试目的` 时回退到用例 H1,避免主 HTML 出现空步骤。
|
|
46
|
+
- `dag rerun --from-node` 把尚未启动的 writer/map 视为首次执行;frontend-test probe 这种失败的 exclusive shell 可以带着下游未跑节点续跑,不必整图 `standaloneTaskRerun`。
|
|
47
|
+
- Pi SDK 节点在 stall watchdog(默认 15 分钟无 provider 活动)触发后,会先 abort 当前等待,并在同一会话自动发送一次「继续」;仅 abort 已确认且尚未调用 write 工具时恢复,失败仍判 timeout。绝对墙钟超时与 CLI 后端不走这条恢复。
|
|
48
|
+
- DAG 节点检查器「执行过程」时间线默认展开原始协议事件(`message_start` / `message_end`),按钮文案为「隐藏原始协议事件」;用户可点选关闭,同会话内选择会保持。
|
|
49
|
+
- Operator Chat 的乐观停止现在把“视觉已停止”和 Turn ownership 分开:Abort 获得可靠终态前明确显示“正在停止”,此时可为原会话保存一条服务端持久化的“停止后发送”消息;后一次覆盖前一次,切换会话、刷新页面或 Console 重启不会丢失,旧 Turn 结束后复用正常 Turn 准入路径自动发送,并以固定 `clientRequestId` 防止重复执行。
|
|
50
|
+
- Operator Chat 修复快速终止时“停止后发送”与普通发送之间的准入空洞:发送路径改读实时 ownership;若 deferred 请求恰好撞上旧 Turn 终态,会先按服务端状态对账,再使用同一 `clientRequestId` 自动转为普通发送,不再要求用户刷新重试或丢失本次提交。
|
|
51
|
+
- `dag rerun-task` 现在会把父 run 最后一轮仍未解决的 `verify-pi` / review 反馈、证据引用和修订轮次带入新完整 DAG;planner、implementer 与 verifier/reviewer 必须修复或举证说明不再适用,不能仅因文件已存在就返回 `already-satisfied`。Console 同步区分「自动修订」「从失败节点恢复」「带反馈重新执行任务」。
|
|
52
|
+
- DAG 恢复与治理收敛收窄为两条最小路径:标准治理 `request-revision` 仅在 `maxFixLoops > 0` 时同 run 回到 `implement-pi`;writer 遇到 `unavailable` / `network` / `rate-limit` / `timeout` 时,`dag rerun --from-node` 重跑 writer 本身及安全下游,不再把 parent `ERROR` 改写成 `FINISHED`,任意自定义 shell 也不能仅凭 `verifyEvidence` 获得续跑资格。
|
|
53
|
+
- `frontend-plan-pi` 改为 runtime skeleton + editable RFC 7386 patch:`schemaVersion`、source binding、risk、writer target files 与生产 Mock 安全位由 DAG 冻结且不可被 plan/revision 覆盖;节点 validator 将补丁编译为 canonical 完整契约后再交给 review/prewrite,并在 `contracts/candidates/<nodeId>/` 保存每次 raw/extracted/normalized/validation 证据。任务提示与三类结构化重试(invalid-output / provider length 截断 / output-too-large)同步收敛为单一 patch + 唯一 `openspec-citations`;无法在声明文件中解析的可选 verification symbol 会被确定性移除并留下 normalization action,避免格式冲突、完整契约重复输出和说明性 symbol 延迟到 trace 才失败。
|
|
54
|
+
- Operator Chat 恢复工具对齐真实能力:系统提示改用 `dagReport` / `dagRerunPlan` / `dagRerun` / `standaloneTaskRerun` / `workerTaskRetry` 真名,不再写 `operator_*` 或把 `dagResume` 当成模型可执行项;`dagDoctor` / `dagStatus` / `workerPoolDoctor` 改为 typed `runId`/`taskId`,不再让模型猜 CLI `args[]`。ADR 0013 自定义 base prompt 上限由 1900 统一调整为 2500(2026-08-18,废止 0.8×2498 比例),预算用于写清节点续跑 / 整单重生 / Worker 重排队分流,以及多轮迭代恢复纪律(先一句话总结上一轮错误、修根因不修症状、禁止原样重交、同错两轮后停下报告)。
|
|
55
|
+
- Operator Chat 模型工具面收敛:`advanced` 分类的命令(workflow/loop/benchmark/knowledge-graph 等 8 个只读低频入口)不再进入模型工具白名单,模型注意力集中到高频工具;保留的 27 个 model-callable 官方 CLI 动作全部换上「做什么 + 何时用」语义描述,不再是无信息的 `Official CLI action coverage for ...` 模板;dispatcher 对官方尾部动作的识别从描述前缀匹配改为结构化 `OFFICIAL_TAIL_ACTION_IDS` 集合判断。审查收口:`knowledgeCurate` 纠正为 `mutation`/`advanced`(条件写盘,移出模型面,防经 read 通道传 `--output` 写盘);`promoteRun`/`closeoutTask` 描述去掉 dispatch 不保证的生命周期断言,如实标注为 task-advance 别名并提示先核 run/任务状态。工具清单渲染修复:promptSnippet 去掉重复工具名前缀(SDK 渲染自带 `- name:`,原实现名字出现两遍);`inspect`/`status`/`spineCheck`/`sourceInstructions` 四条占位描述换成语义描述;task-advance 家族(`newTask`/`dagRunTask`)标注与标准流 `taskAdvance` 的关系,避免模型在 5 个相似入口间选错。可调用性修复:`observeLink` 补上 4 个可选参数 schema(dagRunId/taskId/featureId/workerRunId,原空 schema + additionalProperties:false 导致模型传参必被拒、工具名存实亡);Pi 工具参数 schema 现在携带注册表里的参数描述(Type.String/Boolean/Number/Array 注入 `description`,原实现丢弃参数描述、模型只能看到参数名)。
|
|
56
|
+
|
|
57
|
+
- Operator Chat 对齐 pi-web:建会话不再传封闭 `tools` 白名单(避免 SDK 把扩展 MCP 工具滤出 registry),创建后 `bindExtensions` 发 `session_start`,再钉激活集。扩展注册的 MCP 工具可被模型调用;`apply_patch` / `full-tools` / `shell` / `coding-chat` 仍排除。
|
|
58
|
+
- 对话侧栏 Plugins 右侧新增只读 MCP 弹窗:展示 `mcp.json` 配置的服务,以及当前会话真正注册/激活的 MCP 工具;没有配置也没有工具时保持真实空态。
|
|
59
|
+
- 对话侧栏原 Packages 入口改名为 Plugins,与 pi-web / Pi 用户面一致;内部仍是 package inventory。
|
|
60
|
+
- 对话进行中底部上下文占用百分比保持常驻:token 用量事件不再冲掉上一帧的 contextUsage,并在回合中持续刷新。
|
|
61
|
+
- 上一轮大量 bash 结束后再发新消息,旧工具条不再整批挂到新气泡下面:无主工具不再丢进最新一组,历史按完整回合裁切,空助手气泡若还挂着工具会留下来当锚点。
|
|
62
|
+
- 侧栏文件树点击目录行直接插入 `@目录/` 引用(箭头单独负责展开/收起);输入框 @ 补全也列出目录,排在文件前面。
|
|
63
|
+
- 对话进行中点「停止」立即安静下来(乐观停止,对齐 pi-web):不再等 POST /abort 往返;后端 abort 等待从 8s 降到 2.5s,未终结时由状态对账收敛。
|
|
64
|
+
- 侧栏文件树实时刷新:agent 回合结束、浏览器标签页回到前台、手动点「刷新」时重拉根目录+全部已展开目录(此前手动刷新只拉根目录,子目录永远旧缓存)。
|
|
65
|
+
- Skills / Packages / Models 面板不再把 Pi 的 warning / collision 原文堆在列表上方:改成标题条一颗摘要芯片(如「14 条提示」),点开按「需要处理 / 名称不规范 / 同名未生效」分组,文案用自然语言,路径只显示末两段;名称不规范的技能在列表旁标「提示」,详情里说明「现在这样也能用」。加载失败仍单独成组,可点「再读一次」。
|
|
66
|
+
- Skills / Packages 双栏改为左右各自滚动;资源弹窗挂到 `document.body`,按顶部对话导航的实时高度在剩余视口居中,不再被 sticky 顶栏挡住上沿。
|
|
67
|
+
|
|
39
68
|
### 修复
|
|
40
69
|
|
|
70
|
+
- Console 对话中的 Markdown 表格只约束横向滚动,不再截断纵向滚动链;鼠标或触控板停在表格上时仍可继续上下滚动对话。
|
|
71
|
+
- scenario-param 确定性 rewrite 的 Python 语法校验:Windows 优先 `python`,并把 Microsoft Store `python3` 空壳(非 0 且无语法诊断)视为解释器不可用后回退,避免误回滚合法 rewrite。
|
|
41
72
|
- `frontend-plan-revision-pi` 节点级校验与 merge-patch 输出协议矛盾修复:新增 schemaId `frontend-implementation-contract-revision-patch-v1` 与格式级宽松校验 `validateFrontendRevisionPatchNodeOutput`(至少一个 `openspec-citations` 块 + 剥离引用块后恰好一个 JSON 对象,不做完整契约 schema 校验),revision 节点 `structuredContractOutput.schemaId` 切换(plan-pi 仍为 v1);node-execution 的 invalid-output retry 指令按 schemaId 分支,patch 协议提示 merge-patch delta + 引用块,消除「prompt 要求 patch、retry 要求完整契约」的 retryable 死循环;gate 仍是唯一权威合并/校验点(零改动)。ADR 0015 补「节点级校验」决策 7
|
|
42
73
|
- `frontend-implementation-contract.schema.json` 的 `uiComponentChoices` items 补 `if/then` 决策语义(`specified` ⇒ `specReference` 必填非 null;`new` ⇒ `specReference` 为 null/缺失);`frontend-plan-render.ts` 空 `section` 不再渲染尾随 `#`;ADR 0016 增补「门禁边界」小节
|
|
74
|
+
- 前端 gate 内容驱动 revisionPatch 兼容 + verdict 归一化统一:旧 DAG(缺 `revisionPatch` 字段)但 revision 节点按协议输出 merge-patch 时,gate 按内容判定自动合并原始契约并走既有校验(不再因 `missing targets.files` 进入 retryable 死循环);patch 形态但无法合并(fallback 缺失/自身、原始节点不可读或非完整契约、apply 失败)→ `blocked` + 新增 `revision-patch-unmergeable`(错误信息含重新生成 DAG 指引);`normalizeVerdictCandidateLine` 增强 backtick 强调剥离,gate 的 `firstNonEmptyVerdictLine` 复用共享 normalize,runIf / output-protocol / gate 三处一致
|
|
43
75
|
|
|
44
76
|
### 文档
|
|
45
77
|
|
|
@@ -47,6 +79,33 @@
|
|
|
47
79
|
- 新增 `docs/decisions/0015-frontend-plan-json-only-contract.md`,并同步 `docs/runtime/frontend-implementation-workflow.md`、`docs/templates/frontend-implementation-contract.schema.json`、`skills/frontend-implementation/references/node-contracts.md`
|
|
48
80
|
- 新增 `docs/decisions/0014-openspec-gate-claim-verification.md`,并同步 `docs/runtime/frontend-implementation-workflow.md`、`docs/templates/frontend-task-constraints.md`、`docs/templates/frontend-design-contract.md`、`skills/frontend-implementation/references/node-contracts.md`
|
|
49
81
|
|
|
82
|
+
## [0.37.1-beta.0] - 2026-08-16
|
|
83
|
+
|
|
84
|
+
### 重点更新
|
|
85
|
+
|
|
86
|
+
- backend-test live campaign R115–R141 收口:全新 R141 达到 readiness PASS、23/23 correspondence exact、146/146 Test Points、77/77 scenario MATCH、traceability PASS,并唯一执行 82 个真实 API pytest body
|
|
87
|
+
- 强化 backend-test correspondence、scenario-param、Markdown/pytest writer、collection repair、日志审计和 Windows pytest argfile 执行链,稳定区分 controller TestBug、generated TestBug 与真实 API ProductBug
|
|
88
|
+
|
|
89
|
+
### 改进
|
|
90
|
+
|
|
91
|
+
- backend-test correspondence 只分析目标请求实际绑定的 body/payload,忽略 `scenario` 元数据与 setup-only payload;对 `scenario["body"]` 保留字段 shape,并把 intentional invalid enum 交由 scenario-param 门禁判断;同时识别下划线前缀 pytest 参数集合与带说明的 `Payload Contract: none(…)`,避免零 eligible 的 payload/parameter 误阻断
|
|
92
|
+
- backend-test correspondence 继续解析模块内 transport wrapper 的位置 payload 与 wrapper 构造 body,并清理 `Payload Enum` 值后的中文说明,避免 PUT/lifecycle 合法载荷误报;Markdown/pytest writer 同步禁止臆测响应 ID 类型,并在路径 ID 类型未声明时使用 create-delete 派生的合法 missing ID,避免把测试资产缺陷误分类为产品 400/404 缺陷
|
|
93
|
+
- backend-test correspondence 可还原 `_apply_patch(_base_body(...), patch)` 等 transport-body 组合表达式,并把 setup wrapper payload 限定为有 body 契约时的 fallback;traceability 接受经递归脱敏和有界输出的结构化 request/response 日志,scenario-param 支持带显式例值的 string wrong-type;pytest writer/repair 同步禁止把 path/query/control `id` 混入 DTO patch
|
|
94
|
+
- backend-test correspondence 识别模块级 `_post_case`/`_put_case` 参数工厂、factory TP 字面量与通过 `payload["field"] = value` 构造的 DTO shape,并避免空列表全局变量吞并后续参数集合;writer/repair 要求每个变体展开为直接 `pytest.param(..., id="TP-...")`,scenario readiness 不再依赖隐藏的动态参数行;traceability 允许窄 `urllib.error.URLError`/`httpx.RequestError` DELETE cleanup
|
|
95
|
+
- backend-test correspondence 继续还原 `payload = _base_note_payload()` 形式的 builder-return dataflow,并把 intentional invalid enum 交给 scenario-param 而非 Case 聚合 payload gate;Markdown review 明确 GET/DELETE setup DTO 不得改写目标 no-body contract,pytest writer/repair 禁止把返回 scalar 的 helper 当 tuple 解包
|
|
96
|
+
- backend-test collection repair 把 upstream 的 `Repair paths: testcase/...` 直接视为完整权威路径证据并读取/编辑该文件;禁止猜测根级 `contracts/**` 或 DAG run 目录,也禁止 read tool 已成功返回文件后反事实声称路径不存在并提前 blocked
|
|
97
|
+
- backend-test 的 REPAIRABLE collection repair 现在强制 non-empty diff,禁止 `already-satisfied` 制造假修复;Markdown review 增加 `### 测试点` 与三类 binding union 的 exact-set 校验并删除 legacy alias;no-body Case 的 payload correspondence 忽略同一 journey 的 direct setup POST/PUT DTO
|
|
98
|
+
- backend-test generated-local collection repair 从 HIGH 路由降为 MED,在保留 write guard 与 requireChangedFiles 的同时避开连续多轮 grok repair hallucinated blocked/changed-without-write,改由稳定 writer 路由执行有界单文件修复
|
|
99
|
+
- writer outcome 协议确定性归一化显式中文 `实现结果:已变更|已满足|已阻塞` 到 canonical `IMPLEMENTATION_OUTCOME`,仍拒绝无标签 prose、未知值和冲突值,不根据 diff 猜测语义
|
|
100
|
+
- scenario-param 识别 `"x" * _TITLE_MAX_LENGTH` 等长度表达式与 helper 常量名相乘形式
|
|
101
|
+
- correspondence 忽略 pytest payload 中的 `expect`/`echo_*` 辅助键,避免把 DTO 外 helper 字段误判为 PAYLOAD_SHAPE_MISMATCH
|
|
102
|
+
- scenario-param 解析 `bound=LEN100` 机器行、解引用 `_STATUS_ACTIVE` 等 Python 字符串常量,并把 `NOMINAL` 长度样本从 min/max 纠正为 nominal
|
|
103
|
+
|
|
104
|
+
- scenario-param 观测识别 `_OMIT`/omit sentinel 为合法 `missing`,跳过 `"2xx"`/`"400"` 期望码位置参数,解码 Python `\t`/`\n` 空白字面量,识别 `_TITLE_LEN1` 等长度 helper 名,支持 `custom-literal:whitespace-only|whitespace-padded` 语义标签,`enum-invalid` 接受具体非法字面量,并在 `min`/`LEN1` 意图下优先读取 TP 编码长度与 `minLength` 边界
|
|
105
|
+
- scenario-param 兼容 `empty` 意图下的 omit/`missing-key`,规范化 `custom-literal` 外包引号与可见空白符,并把 `max+1`/`min`/`max` 的裸数字位置参数按长度 token 比较(含 oversized 数字本身);`custom-literal:trim` 视为 padded whitespace 语义标签
|
|
106
|
+
- backend-test writer 合同明确:`enum-invalid` 必须使用具体非法字面量且禁止 `_OMIT`/missing-key;`custom-literal:trim|ACTIVE|ARCHIVED` 必须使用真实 padded/enum 字面量,禁止 `filter-active` 类描述性伪值
|
|
107
|
+
- scenario-param 识别 `_*_MAX_LENGTH` / `_*_OVER_LENGTH` 等长度 helper 名,明确 `custom-literal:trim` 不能是全空白,并把无 pytest.param 的 request-level/health nominal 记为 MATCH 以免污染 eligibility
|
|
108
|
+
|
|
50
109
|
## [0.36.4-beta.0] - 2026-08-16
|
|
51
110
|
|
|
52
111
|
> 前端 DAG 规范上下文确定性落地(beta,发布到 `beta` dist-tag)。
|
|
@@ -441,6 +500,61 @@
|
|
|
441
500
|
- 会话创建期间侧栏与 Landing 页共享 busy 状态反馈,抑制双击导致的重复创建
|
|
442
501
|
- 删除正在初始化的会话不会被迟到的后台初始化任务复活,初始化失败统一返回 HTTP 503 错误码
|
|
443
502
|
|
|
503
|
+
## [0.33.7-beta.2] - 2026-08-13
|
|
504
|
+
|
|
505
|
+
### Fixed
|
|
506
|
+
|
|
507
|
+
- scenario-param 解析 `bound=LEN100`、解引用 `_STATUS_ACTIVE` 等 Python 字符串常量,识别 `"x" * _TITLE_MAX_LENGTH` 长度表达式
|
|
508
|
+
- correspondence 忽略 pytest payload 中的 `expect`/`echo_*` 辅助键,避免假 PAYLOAD_SHAPE_MISMATCH
|
|
509
|
+
|
|
510
|
+
### Changed
|
|
511
|
+
|
|
512
|
+
- my-webapp 五轮 backend-test 全流程 live campaign 归档(R1–R5 均 finished;R5 correspondence PASS)
|
|
513
|
+
|
|
514
|
+
## [0.33.7-beta.1] - 2026-08-12
|
|
515
|
+
|
|
516
|
+
### Fixed
|
|
517
|
+
|
|
518
|
+
- scenario-param 识别 `_*_MAX_LENGTH` / `_*_OVER_LENGTH` 等长度 helper 名,明确 `custom-literal:trim` 不能是全空白,并把无 pytest.param 的 request-level/health nominal 记为 MATCH 以免污染 eligibility
|
|
519
|
+
- backend-test writer 合同明确:`enum-invalid` 必须使用具体非法字面量且禁止 `_OMIT`/missing-key;`custom-literal:trim|ACTIVE|ARCHIVED` 必须使用真实 padded/enum 字面量;max/min/max+1 应用长度表达式/helper
|
|
520
|
+
|
|
521
|
+
### Changed
|
|
522
|
+
|
|
523
|
+
- 归档 R108–R110 live campaign 证据与 residual 收紧结果
|
|
524
|
+
|
|
525
|
+
## [0.33.7-beta.0] - 2026-08-11
|
|
526
|
+
|
|
527
|
+
### 重点更新
|
|
528
|
+
|
|
529
|
+
- backend-test 收口 source-fidelity P0:绑定 source coverage、有界 Markdown/pytest 同步、item-level eligibility 与单次 eligible 业务执行;my-webapp R107 16 节点 finished
|
|
530
|
+
|
|
531
|
+
### 新增
|
|
532
|
+
|
|
533
|
+
- backend-test 新增绑定 source references 的来源覆盖事实、Markdown 一轮增量同步和 pytest correspondence 一轮有界修复,在业务测试体执行前保留 initial/effective/residual 证据
|
|
534
|
+
- backend-test 新增 item-level execution eligibility:只授权 payload-safe 的 pytest items,excluded items 与真实业务失败分开展示
|
|
535
|
+
|
|
536
|
+
### 改进
|
|
537
|
+
|
|
538
|
+
- 将 backend-test Markdown/pytest map 的默认总 token 预算从 `600000` 提升到 `3000000`,降低多模块生成时过早触发 `dynamic map token budget exhausted` 的概率;仍保持预算耗尽 fail-closed
|
|
539
|
+
- Coverage Matrix 接受 `R400001` 等错误码 Rule Key,并保留单元格内 `ACTIVE|ARCHIVED` 未转义枚举管道符,避免合法行被拆成 10 列后整表丢弃
|
|
540
|
+
- scenario-param 按 TP 精确匹配多行 `场景意图`,保留 `ACTIVE` 大小写字面量,识别 `"a" * N` 长度表达式与空白 `empty`,request-level `nominal` 允许 `None` 查询过滤;避免把合法 payload 误判为 MISMATCH 并错误排除 eligible items
|
|
541
|
+
- backend-test 支持 evidence-only 元用例:Markdown 显式声明 `脚本/primary symbol=无` 且仅绑定 assertion/cross-cutting TP 时,不再强制 1:1 pytest 符号,也不再把其算作 MISSING_PYTEST/SCRIPT_MISMATCH
|
|
542
|
+
- generated pytest 内的 `ScopeMismatch`/setup fixture 失败若完全归因于 `testcase/**` 映射脚本,则归为 REPAIRABLE 并进入唯一一轮 collection repair,不再直接 BLOCKED
|
|
543
|
+
- scenario-param 观测跳过 `pytest.param("TP-...", value, id="TP-...")` 的前导 TP 标签,并支持长度意图用数字位置参数表达
|
|
544
|
+
|
|
545
|
+
### 修复
|
|
546
|
+
|
|
547
|
+
- 修复 readiness 错误信息截断与无效 Coverage Matrix 状态语义,避免超长错误或 advisory 矩阵状态误阻断后续节点
|
|
548
|
+
- 修复 scenario 排除项未绑定 payload 字段、request-level 不确定性被误升级为硬失败的问题
|
|
549
|
+
|
|
550
|
+
## [0.33.6-beta.0] - 2026-08-11
|
|
551
|
+
|
|
552
|
+
### 修复
|
|
553
|
+
|
|
554
|
+
- 修复 backend-test correspondence scanner 将下一 Case 的分隔注释误归入前一个 pytest function region,造成虚假的 `MULTIPLE_PYTEST`、script mismatch 和额外 Test Point;现在按函数实际缩进边界隔离 body,并只从精确 `Case-ID:` 元数据或 primary symbol 绑定 Case
|
|
555
|
+
- 修复 backend-test 将 README 其他区域的 Markdown 链接误提取为业务模块、允许 `a401606` 等 opaque/hash-like stem 和异常多模块进入 map 的问题;现在只解析唯一 `## Module Index`,非法、mixed 或超过 8 个模块时 README-only 有界重试并由 manifest fail-closed
|
|
556
|
+
- 修复 backend-test map 总 token 预算耗尽后只生成部分 Markdown/pytest 资产但 parent 仍可能显示成功的问题;budget-blocked child 现在使 backend map 明确 ERROR,并保留 expected/finished/blocked/token 事实
|
|
557
|
+
|
|
444
558
|
## [0.33.6] - 2026-08-11
|
|
445
559
|
|
|
446
560
|
### 重点更新
|
package/README.md
CHANGED
package/bin/loop-agent.js
CHANGED
|
@@ -18,11 +18,15 @@ if (!existsSync(cliEntry)) {
|
|
|
18
18
|
* to silently execute a dist/ that predates the latest source edit. The
|
|
19
19
|
* classic failure mode is editing src/** and running the bin without
|
|
20
20
|
* `npm run build`, which executes the previous build. Suppress with
|
|
21
|
-
* --ignore-stale-dist
|
|
21
|
+
* --ignore-stale-dist (or LOOP_AGENT_IGNORE_STALE_DIST=1, which sibling
|
|
22
|
+
* processes such as a Console-spawned CLI inherit automatically) when
|
|
23
|
+
* intentionally running a stale build.
|
|
22
24
|
*/
|
|
23
25
|
const srcDir = join(packageRoot, "src");
|
|
24
|
-
const ignoreStale =
|
|
25
|
-
|
|
26
|
+
const ignoreStale =
|
|
27
|
+
process.argv.includes("--ignore-stale-dist") ||
|
|
28
|
+
process.env.LOOP_AGENT_IGNORE_STALE_DIST === "1";
|
|
29
|
+
if (process.argv.includes("--ignore-stale-dist")) {
|
|
26
30
|
process.argv = process.argv.filter((arg) => arg !== "--ignore-stale-dist");
|
|
27
31
|
}
|
|
28
32
|
if (!ignoreStale && existsSync(srcDir)) {
|
|
@@ -279,6 +279,7 @@ export async function generateTaskDagUseCase(input) {
|
|
|
279
279
|
const candidateResult = await initHybridDagFromTask(repoRoot, parsed.taskId, {
|
|
280
280
|
outputPath: parsed.outputPath,
|
|
281
281
|
template: "standard-dag",
|
|
282
|
+
rerunFeedback: parsed.rerunFeedback,
|
|
282
283
|
});
|
|
283
284
|
const candidateValidateSummary = await validateDagUseCase(buildValidateInput(repoRoot, candidateResult.outputPath, parsed));
|
|
284
285
|
const profileRouting = await resolveProfileRouting(repoRoot, parsed, candidateValidateSummary.governanceProfile ?? {
|
|
@@ -324,8 +325,16 @@ export async function generateTaskDagUseCase(input) {
|
|
|
324
325
|
: await initHybridDagFromTask(repoRoot, parsed.taskId, {
|
|
325
326
|
outputPath: parsed.outputPath,
|
|
326
327
|
template: profileRouting.selectedTemplate,
|
|
328
|
+
rerunFeedback: parsed.rerunFeedback,
|
|
327
329
|
});
|
|
328
330
|
const outputPath = initResult.outputPath;
|
|
331
|
+
if (parsed.expectedRerunFeedbackDigest) {
|
|
332
|
+
const generatedSpec = parseDagSpec(JSON.parse(await readFile(outputPath, "utf8")));
|
|
333
|
+
if (generatedSpec.rerunFeedback?.digest !==
|
|
334
|
+
parsed.expectedRerunFeedbackDigest) {
|
|
335
|
+
throw new Error(`rerun feedback drift: expected ${parsed.expectedRerunFeedbackDigest}, got ${generatedSpec.rerunFeedback?.digest ?? "missing"}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
329
338
|
const validateSummary = profileRouting.selectedTemplate === candidateResult.template
|
|
330
339
|
? candidateValidateSummary
|
|
331
340
|
: await validateDagUseCase(buildValidateInput(repoRoot, outputPath, parsed));
|
|
@@ -38,6 +38,7 @@ function hasEngineeringBoundary(input) {
|
|
|
38
38
|
return Boolean((input.allowedPaths && input.allowedPaths.length > 0) ||
|
|
39
39
|
(input.forbiddenPaths && input.forbiddenPaths.length > 0) ||
|
|
40
40
|
(input.verifyCommands && input.verifyCommands.length > 0) ||
|
|
41
|
+
input.backendTest !== undefined ||
|
|
41
42
|
input.fromText ||
|
|
42
43
|
input.fromDraftPath);
|
|
43
44
|
}
|
|
@@ -632,6 +633,7 @@ export async function advanceTaskLifecycle(input) {
|
|
|
632
633
|
allowedPaths: effectiveInput.allowedPaths,
|
|
633
634
|
forbiddenPaths: input.forbiddenPaths,
|
|
634
635
|
verifyCommands: effectiveInput.verifyCommands,
|
|
636
|
+
backendTest: input.backendTest,
|
|
635
637
|
},
|
|
636
638
|
});
|
|
637
639
|
if (!prepareResult.ok) {
|
|
@@ -687,6 +689,7 @@ export async function advanceTaskLifecycle(input) {
|
|
|
687
689
|
allowedPaths: effectiveInput.allowedPaths,
|
|
688
690
|
forbiddenPaths: input.forbiddenPaths,
|
|
689
691
|
verifyCommands: effectiveInput.verifyCommands,
|
|
692
|
+
backendTest: input.backendTest,
|
|
690
693
|
},
|
|
691
694
|
});
|
|
692
695
|
if (semantic.ok) {
|
|
@@ -707,6 +710,7 @@ export async function advanceTaskLifecycle(input) {
|
|
|
707
710
|
allowedPaths: effectiveInput.allowedPaths,
|
|
708
711
|
forbiddenPaths: input.forbiddenPaths,
|
|
709
712
|
verifyCommands: effectiveInput.verifyCommands,
|
|
713
|
+
backendTest: input.backendTest,
|
|
710
714
|
},
|
|
711
715
|
});
|
|
712
716
|
if (retry.ok) {
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.38.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.38.3",
|
|
4
|
+
"gitSha": "228da3eaaf3c1a2082af7b5c15aa2e4653afb3ce",
|
|
5
|
+
"builtAt": "2026-08-22T02:44:24.204Z"
|
|
6
6
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1362,13 +1362,7 @@ async function readExistingSurfaceState(repoRoot) {
|
|
|
1362
1362
|
}
|
|
1363
1363
|
/** The IDE schema reference remains a structural update signal. */
|
|
1364
1364
|
const HARNESS_STRUCTURAL_VALUE_KEYS = new Set(["$schema"]);
|
|
1365
|
-
const PI_MODEL_ROUTING_KEYS = new Set([
|
|
1366
|
-
"LOW",
|
|
1367
|
-
"MED",
|
|
1368
|
-
"HIGH",
|
|
1369
|
-
"defaultModel",
|
|
1370
|
-
"modelOptions",
|
|
1371
|
-
]);
|
|
1365
|
+
const PI_MODEL_ROUTING_KEYS = new Set(["LOW", "MED", "HIGH", "defaultModel"]);
|
|
1372
1366
|
function isPiModelRoutingPath(path) {
|
|
1373
1367
|
return path.length === 2 && path[0] === "executors" && path[1] === "pi";
|
|
1374
1368
|
}
|
|
@@ -1733,13 +1727,7 @@ function assessHarnessModelMigration(harness) {
|
|
|
1733
1727
|
const hasDefaultModel = "defaultModel" in pi;
|
|
1734
1728
|
const hasTier = PI_MODEL_TIERS.some((tier) => tier in pi);
|
|
1735
1729
|
if (["requiresApiKey", "provider", "model"].some((field) => field in pi) ||
|
|
1736
|
-
Object.keys(pi).some((field) => ![
|
|
1737
|
-
"description",
|
|
1738
|
-
"enabled",
|
|
1739
|
-
"defaultModel",
|
|
1740
|
-
"modelOptions",
|
|
1741
|
-
...PI_MODEL_TIERS,
|
|
1742
|
-
].includes(field))) {
|
|
1730
|
+
Object.keys(pi).some((field) => !["description", "enabled", "defaultModel", ...PI_MODEL_TIERS].includes(field))) {
|
|
1743
1731
|
return {
|
|
1744
1732
|
kind: "ambiguous",
|
|
1745
1733
|
reason: "executors.pi contains unknown or deprecated model semantics",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { advanceTaskLifecycle, } from "../application/task-lifecycle/index.js";
|
|
2
|
+
import { backendTestConfigSchema, } from "../task/config-types.js";
|
|
2
3
|
import { buildOperatorResult, operatorFailed, processExitCodeForOutcome, writeOperatorJson, } from "../shared/operator/index.js";
|
|
3
4
|
import { createRunDagProgressObserver, validateRunDagProgressIntervalMs, } from "./run-dag-progress.js";
|
|
4
5
|
const COMMAND = "task advance";
|
|
@@ -14,6 +15,7 @@ const USAGE = `usage:
|
|
|
14
15
|
[--forbidden-path <glob>]...
|
|
15
16
|
[--verify <label:command>]...
|
|
16
17
|
[--verify-timeout <label:timeoutMs>]...
|
|
18
|
+
[--backend-test <json>]
|
|
17
19
|
[--accept-recommendations <digest>]
|
|
18
20
|
[--approve-gate <gate-id:digest>]
|
|
19
21
|
[--reject-gate <gate-id:digest> --reason <text>]
|
|
@@ -52,6 +54,30 @@ function parseVerifyTimeout(flag) {
|
|
|
52
54
|
}
|
|
53
55
|
return { label: flag.slice(0, idx), timeoutMs };
|
|
54
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse `--backend-test <json>` into a validated managed backendTest config.
|
|
59
|
+
* `{}` is legal (explicit clear back to the default layout).
|
|
60
|
+
*/
|
|
61
|
+
function parseBackendTestFlag(raw) {
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(raw);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw new Error(`invalid --backend-test ${raw}; expected an inline JSON object`);
|
|
68
|
+
}
|
|
69
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
70
|
+
throw new Error(`invalid --backend-test ${raw}; expected an inline JSON object`);
|
|
71
|
+
}
|
|
72
|
+
const result = backendTestConfigSchema.safeParse(parsed);
|
|
73
|
+
if (!result.success) {
|
|
74
|
+
const summary = result.error.issues
|
|
75
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
76
|
+
.join("; ");
|
|
77
|
+
throw new Error(`invalid --backend-test: ${summary}`);
|
|
78
|
+
}
|
|
79
|
+
return result.data ?? {};
|
|
80
|
+
}
|
|
55
81
|
export function parseTaskAdvanceArgs(args) {
|
|
56
82
|
const options = {};
|
|
57
83
|
const positional = [];
|
|
@@ -113,6 +139,10 @@ export function parseTaskAdvanceArgs(args) {
|
|
|
113
139
|
options.verifyTimeout = pushList(options.verifyTimeout, next());
|
|
114
140
|
continue;
|
|
115
141
|
}
|
|
142
|
+
if (token === "--backend-test") {
|
|
143
|
+
options.backendTest = parseBackendTestFlag(next());
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
116
146
|
if (token === "--accept-recommendations") {
|
|
117
147
|
options.acceptRecommendations = next();
|
|
118
148
|
continue;
|
|
@@ -273,6 +303,7 @@ export function toUseCaseInput(repoRoot, taskId, options, observer) {
|
|
|
273
303
|
allowedPaths: options.allowedPaths,
|
|
274
304
|
forbiddenPaths: options.forbiddenPaths,
|
|
275
305
|
verifyCommands: verifyCommands.length > 0 ? verifyCommands : undefined,
|
|
306
|
+
backendTest: options.backendTest,
|
|
276
307
|
approveGateToken: options.approveGate,
|
|
277
308
|
rejectGateToken: options.rejectGate,
|
|
278
309
|
rejectReason: options.reason,
|
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { writeDagNodeJsonArtifact, writeTextArtifactFile, } from "../infrastructure/harness/artifact-store.js";
|
|
5
|
-
import { executePiStep, } from "./pi-executor.js";
|
|
5
|
+
import { executePiStep, resolvePiBackend, } from "./pi-executor.js";
|
|
6
|
+
import { resolveDagPiExtensions, } from "./pi-extension-resolver.js";
|
|
6
7
|
import { buildPiWriterToolPolicyContext, createPiWriterCustomTools, } from "./pi-writer-tool-policy.js";
|
|
7
8
|
import { cleanupPlaywrightCliDefaultSession, createPlaywrightCliTool, PI_COMMAND_CAPABILITY_REGISTRY, resolveCaseIdFromWriteSet, resolveEvidenceDirFromWriteSet, } from "./pi-playwright-cli-tool.js";
|
|
8
9
|
import { dagCommandPolicyAllows, resolveDagCommandPolicy, } from "../workflows/dag/types.js";
|
|
@@ -10,6 +11,7 @@ import { redactPromptForLog, truncateOutput, } from "../shared/output-truncation
|
|
|
10
11
|
import { GitStatusUnavailableError, pathsChangedDuringRun, readGitStatusPorcelain, recoverRootNulArtifact, snapshotGitStatusPathFingerprints, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
|
|
11
12
|
import { isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "../workflows/dag/retry-policy.js";
|
|
12
13
|
import { assessBackendTestMdPlanCompleteness, assessBackendTestMdWriterCompleteness, assessBackendTestPytestPlanCompleteness, assessBackendTestPytestWriterCompleteness, assessBackendTestShardChildCompleteness, backendTestWriterProgressRoleForTask, classifyBackendTestWriterCompletenessFailure, isBackendTestCompletenessRetryCandidate, isBackendTestMdPlanTask, isBackendTestPytestPlanTask, isBackendTestShardChildTask, writeBackendTestWriterProgressArtifacts, } from "../workflows/dag/backend-test-writer-completeness.js";
|
|
14
|
+
import { resolveBackendTestLayout } from "../workflows/dag/backend-test-layout.js";
|
|
13
15
|
import { redactSecrets, truncateUtf8Preview } from "../shared/preview.js";
|
|
14
16
|
/**
|
|
15
17
|
* Writer classification for a length-stopped thinking-only attempt: the model
|
|
@@ -326,6 +328,36 @@ export async function writePiExecutorArtifacts(artifactsDir, input) {
|
|
|
326
328
|
await writeTextArtifactFile(summaryPath, buildPiResultSummaryMarkdown(input));
|
|
327
329
|
return { promptPath, summaryPath };
|
|
328
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Run-owned per-node Pi extension facts (plan 2026-08-21): requested ids,
|
|
333
|
+
* resolved packages (with absolute entry paths), and degraded/missing ids.
|
|
334
|
+
* Written only for nodes that declare `piExtensions`; never into the repo-level
|
|
335
|
+
* DAG template. Best-effort: artifact failures never fail the node.
|
|
336
|
+
*/
|
|
337
|
+
async function writePiExtensionsArtifact(nodeArtifactsDir, requested, resolved, missing) {
|
|
338
|
+
try {
|
|
339
|
+
const runDir = path.dirname(nodeArtifactsDir);
|
|
340
|
+
const nodeId = path.basename(nodeArtifactsDir);
|
|
341
|
+
await writeDagNodeJsonArtifact(runDir, nodeId, "pi-extensions.json", {
|
|
342
|
+
schemaVersion: 1,
|
|
343
|
+
requested: [...requested],
|
|
344
|
+
resolved: resolved.map((entry) => ({
|
|
345
|
+
id: entry.id,
|
|
346
|
+
packageName: entry.packageName,
|
|
347
|
+
version: entry.version,
|
|
348
|
+
path: entry.entryPaths.join(", "),
|
|
349
|
+
})),
|
|
350
|
+
missing: missing.map((entry) => ({
|
|
351
|
+
id: entry.id,
|
|
352
|
+
reason: entry.reason,
|
|
353
|
+
...(entry.detail ? { detail: entry.detail } : {}),
|
|
354
|
+
})),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
// best-effort run fact; node execution continues
|
|
359
|
+
}
|
|
360
|
+
}
|
|
329
361
|
async function resolveValidatedFrontendBaseUrlFromContext(workspaceRoot, runDir) {
|
|
330
362
|
try {
|
|
331
363
|
const capability = JSON.parse(await readFile(path.join(runDir, "preflight-frontend-browser-tool-shell", "frontend-browser-capability.json"), "utf8"));
|
|
@@ -549,6 +581,29 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
549
581
|
};
|
|
550
582
|
}
|
|
551
583
|
}
|
|
584
|
+
// Plan 2026-08-21 D4/D6: resolve the node's explicit Pi extension
|
|
585
|
+
// allowlist against the settings inventory. Degrade on missing packages —
|
|
586
|
+
// never fail the node — and persist requested/resolved/missing facts.
|
|
587
|
+
const piExtensionsResolution = input.task.piExtensions
|
|
588
|
+
? resolveDagPiExtensions({
|
|
589
|
+
repoRoot: input.cwd,
|
|
590
|
+
ids: input.task.piExtensions,
|
|
591
|
+
})
|
|
592
|
+
: undefined;
|
|
593
|
+
if (piExtensionsResolution) {
|
|
594
|
+
const piBackend = resolvePiBackend();
|
|
595
|
+
const cliDegraded = piBackend === "cli-only";
|
|
596
|
+
await writePiExtensionsArtifact(path.join(meta.runDir, input.task.id), input.task.piExtensions, cliDegraded ? [] : piExtensionsResolution.resolved, cliDegraded
|
|
597
|
+
? input.task.piExtensions.map((id) => ({
|
|
598
|
+
id,
|
|
599
|
+
reason: "not-installed",
|
|
600
|
+
detail: "cli backend does not load pi extensions (sdk-only v1)",
|
|
601
|
+
}))
|
|
602
|
+
: piExtensionsResolution.missing);
|
|
603
|
+
}
|
|
604
|
+
const piExtensionPaths = piExtensionsResolution && resolvePiBackend() !== "cli-only"
|
|
605
|
+
? piExtensionsResolution.resolved.flatMap((entry) => entry.entryPaths)
|
|
606
|
+
: undefined;
|
|
552
607
|
result = await piStepFn({
|
|
553
608
|
attachedFiles: [],
|
|
554
609
|
modelConfig: resolveDagPiModelConfig(input.model, input.thinking ? { thinking: input.thinking } : undefined),
|
|
@@ -563,6 +618,9 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
563
618
|
stallTimeoutMs: input.stallTimeoutMs,
|
|
564
619
|
abortGraceMs: input.abortGraceMs,
|
|
565
620
|
...(writerToolPolicy ? { writerToolPolicy } : {}),
|
|
621
|
+
...(piExtensionPaths && piExtensionPaths.length > 0
|
|
622
|
+
? { piExtensionPaths }
|
|
623
|
+
: {}),
|
|
566
624
|
onActivity: bridgeActivity
|
|
567
625
|
? (activity) => {
|
|
568
626
|
if (activity.kind === "lease" ||
|
|
@@ -808,6 +866,13 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
808
866
|
isBackendTestCompletenessRetryCandidate(input.task)) {
|
|
809
867
|
try {
|
|
810
868
|
const writerRole = backendTestWriterProgressRoleForTask(input.task.id);
|
|
869
|
+
const backendLayout = resolveBackendTestLayout(meta.spec.backendTestLayout
|
|
870
|
+
? {
|
|
871
|
+
testRoot: meta.spec.backendTestLayout.testRoot,
|
|
872
|
+
markdownRel: meta.spec.backendTestLayout.markdownRel,
|
|
873
|
+
scriptRel: meta.spec.backendTestLayout.scriptRel,
|
|
874
|
+
}
|
|
875
|
+
: undefined);
|
|
811
876
|
const progress = isBackendTestShardChildTask(input.task)
|
|
812
877
|
? await assessBackendTestShardChildCompleteness({
|
|
813
878
|
workspaceRoot: input.cwd,
|
|
@@ -815,12 +880,12 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
815
880
|
writeSet: input.task.writeSet ?? [],
|
|
816
881
|
})
|
|
817
882
|
: isBackendTestMdPlanTask(input.task)
|
|
818
|
-
? await assessBackendTestMdPlanCompleteness(input.cwd)
|
|
883
|
+
? await assessBackendTestMdPlanCompleteness(input.cwd, backendLayout)
|
|
819
884
|
: isBackendTestPytestPlanTask(input.task)
|
|
820
|
-
? await assessBackendTestPytestPlanCompleteness(input.cwd)
|
|
885
|
+
? await assessBackendTestPytestPlanCompleteness(input.cwd, backendLayout)
|
|
821
886
|
: writerRole === "pytest-generate"
|
|
822
|
-
? await assessBackendTestPytestWriterCompleteness(input.cwd)
|
|
823
|
-
: await assessBackendTestMdWriterCompleteness(input.cwd);
|
|
887
|
+
? await assessBackendTestPytestWriterCompleteness(input.cwd, backendLayout)
|
|
888
|
+
: await assessBackendTestMdWriterCompleteness(input.cwd, backendLayout);
|
|
824
889
|
await writeBackendTestWriterProgressArtifacts({
|
|
825
890
|
runDir: meta.runDir,
|
|
826
891
|
progress,
|
|
@@ -977,6 +1042,15 @@ function parseWriterImplementationOutcome(text) {
|
|
|
977
1042
|
const lines = text.split(/\r?\n/);
|
|
978
1043
|
const candidates = [];
|
|
979
1044
|
for (const [lineIndex, line] of lines.entries()) {
|
|
1045
|
+
const localizedOutcome = localizedWriterImplementationOutcome(line);
|
|
1046
|
+
if (localizedOutcome) {
|
|
1047
|
+
candidates.push({
|
|
1048
|
+
lineIndex,
|
|
1049
|
+
value: localizedOutcome,
|
|
1050
|
+
outcome: localizedOutcome,
|
|
1051
|
+
});
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
980
1054
|
const nextLine = lines[lineIndex + 1];
|
|
981
1055
|
const normalized = normalizeProtocolLine(line, WRITER_OUTCOME_PROTOCOL_LINE, nextLine);
|
|
982
1056
|
if (normalized === undefined)
|
|
@@ -1005,6 +1079,26 @@ function parseWriterImplementationOutcome(text) {
|
|
|
1005
1079
|
candidates,
|
|
1006
1080
|
};
|
|
1007
1081
|
}
|
|
1082
|
+
function localizedWriterImplementationOutcome(line) {
|
|
1083
|
+
const normalized = normalizedProtocolValue(line);
|
|
1084
|
+
const match = /^(?:实现|实施)结果\s*[::]\s*(已变更|已更改|已修改|已满足|无需变更|已阻塞|阻塞)$/u.exec(normalized);
|
|
1085
|
+
if (!match)
|
|
1086
|
+
return undefined;
|
|
1087
|
+
switch (match[1]) {
|
|
1088
|
+
case "已变更":
|
|
1089
|
+
case "已更改":
|
|
1090
|
+
case "已修改":
|
|
1091
|
+
return "changed";
|
|
1092
|
+
case "已满足":
|
|
1093
|
+
case "无需变更":
|
|
1094
|
+
return "already-satisfied";
|
|
1095
|
+
case "已阻塞":
|
|
1096
|
+
case "阻塞":
|
|
1097
|
+
return "blocked";
|
|
1098
|
+
default:
|
|
1099
|
+
return undefined;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1008
1102
|
function isWriterImplementationOutcome(value) {
|
|
1009
1103
|
return (value === "changed" || value === "already-satisfied" || value === "blocked");
|
|
1010
1104
|
}
|
|
@@ -51,9 +51,50 @@ export function normalizePiSessionEvent(event) {
|
|
|
51
51
|
}
|
|
52
52
|
return unifyToolPayloadFields(event);
|
|
53
53
|
}
|
|
54
|
+
const TERMINAL_FINISH_REASON_TYPES = new Set([
|
|
55
|
+
'message_end',
|
|
56
|
+
'turn_end',
|
|
57
|
+
'assistant_message',
|
|
58
|
+
]);
|
|
59
|
+
function readExplicitFinishReason(source) {
|
|
60
|
+
const message = isRecord(source.message) ? source.message : undefined;
|
|
61
|
+
const candidates = [
|
|
62
|
+
source.finish_reason,
|
|
63
|
+
source.finishReason,
|
|
64
|
+
source.stop_reason,
|
|
65
|
+
source.stopReason,
|
|
66
|
+
message?.finish_reason,
|
|
67
|
+
message?.finishReason,
|
|
68
|
+
message?.stop_reason,
|
|
69
|
+
message?.stopReason,
|
|
70
|
+
];
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
73
|
+
return candidate;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function applyFinishReason(original, normalized) {
|
|
79
|
+
const originalType = typeof original.type === 'string' ? original.type : undefined;
|
|
80
|
+
if (!originalType || !TERMINAL_FINISH_REASON_TYPES.has(originalType)) {
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
const existing = readExplicitFinishReason({
|
|
84
|
+
finish_reason: normalized.finish_reason,
|
|
85
|
+
});
|
|
86
|
+
if (existing !== undefined) {
|
|
87
|
+
return normalized;
|
|
88
|
+
}
|
|
89
|
+
const promoted = readExplicitFinishReason(original);
|
|
90
|
+
if (promoted === undefined) {
|
|
91
|
+
return normalized;
|
|
92
|
+
}
|
|
93
|
+
return { ...normalized, finish_reason: promoted };
|
|
94
|
+
}
|
|
54
95
|
/** Serialize one SDK session event as a single JSONL line. */
|
|
55
96
|
export function serializeSessionEvent(event, options) {
|
|
56
|
-
const normalized = normalizePiSessionEvent(event);
|
|
97
|
+
const normalized = applyFinishReason(event, normalizePiSessionEvent(event));
|
|
57
98
|
if (typeof normalized.recordedAt !== 'string') {
|
|
58
99
|
normalized.recordedAt = options?.recordedAt ?? new Date().toISOString();
|
|
59
100
|
}
|