@tea-agent/loop-agent 0.38.2 → 0.38.4
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/AGENTS.md +4 -1
- package/CHANGELOG.md +171 -96
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +76 -10
- 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-playwright-cli-tool.js +51 -12
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +265 -15
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +32 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- 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/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/operator-actions.js +116 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +36 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
- package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-pm3c2Rsl.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
- package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
- package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
- package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DEbyrEfW.js} +147 -68
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-CAyXjQjN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-CcWKn6tS.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +76 -6
- package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/failure-category.js +12 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
- package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- 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 +160 -28
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +196 -117
- package/dist/workflows/dag/node-execution.js +139 -10
- package/dist/workflows/dag/prompt.js +15 -1
- package/dist/workflows/dag/rerun-plan.js +134 -3
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +32 -1
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -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 +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +2 -2
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- 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/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- 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/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
|
@@ -90,6 +90,7 @@ import {
|
|
|
90
90
|
closeDagInspector,
|
|
91
91
|
selectDagNode,
|
|
92
92
|
renderDagInspector,
|
|
93
|
+
mergeNodeProcess,
|
|
93
94
|
} from "./dag-inspector.js";
|
|
94
95
|
import { mergeSessionEvents } from "./session-timeline.js";
|
|
95
96
|
|
|
@@ -451,13 +452,19 @@ export async function renderDagDetail(dagRunId, initial = true) {
|
|
|
451
452
|
nodesEl.scrollLeft = nodesScrollLeft;
|
|
452
453
|
|
|
453
454
|
if (uiState.dagInspectorOpen && uiState.selectedDagNodeId) {
|
|
455
|
+
const selectedNode = nodes.find(
|
|
456
|
+
(node) => node.nodeId === uiState.selectedDagNodeId,
|
|
457
|
+
);
|
|
454
458
|
if (!uiState.sessionEventIdentity) {
|
|
455
459
|
uiState.sessionEventIdentity = makeSessionEventIdentity(
|
|
456
460
|
dagRunId,
|
|
457
461
|
uiState.selectedDagNodeId,
|
|
458
462
|
);
|
|
459
463
|
}
|
|
460
|
-
await
|
|
464
|
+
await Promise.all([
|
|
465
|
+
mergeSessionEvents(dagRunId, uiState.selectedDagNodeId),
|
|
466
|
+
selectedNode ? mergeNodeProcess(dagRunId, selectedNode) : Promise.resolve(),
|
|
467
|
+
]);
|
|
461
468
|
}
|
|
462
469
|
renderDagInspector(
|
|
463
470
|
dagRunId,
|
|
@@ -101,11 +101,14 @@ export async function renderFailures() {
|
|
|
101
101
|
|
|
102
102
|
if (tasks.length === 0) {
|
|
103
103
|
clearNode(listEl);
|
|
104
|
+
// 2026-08-22 (B-FIND-02): 空态文案与静态 #failures-empty / 页面标题
|
|
105
|
+
// 「异常 Task」保持一致;旧文案「当前没有失败或阻塞的 Task。」与静态
|
|
106
|
+
// HTML 及回归目录 AC-FE-INS-004 漂移,导致 frontend-test 字面 find 0 命中。
|
|
104
107
|
if (emptyEl) {
|
|
105
108
|
emptyEl.hidden = false;
|
|
106
|
-
mountViewState(emptyEl, "empty", "
|
|
109
|
+
mountViewState(emptyEl, "empty", "当前没有异常 Task");
|
|
107
110
|
} else {
|
|
108
|
-
mountViewState(listEl, "empty", "
|
|
111
|
+
mountViewState(listEl, "empty", "当前没有异常 Task");
|
|
109
112
|
}
|
|
110
113
|
return;
|
|
111
114
|
}
|
|
@@ -250,6 +250,54 @@ export function buildSessionTimelineItems(events) {
|
|
|
250
250
|
return items;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Split a node's shared session-events log into per-attempt segments. Every
|
|
255
|
+
* attempt appends one agent run beginning with agent_start; events recorded
|
|
256
|
+
* before the first agent_start (defensive) stay in an implicit first segment.
|
|
257
|
+
*/
|
|
258
|
+
export function splitSessionEventsByAttempt(events) {
|
|
259
|
+
const segments = [];
|
|
260
|
+
let current = null;
|
|
261
|
+
for (const event of events) {
|
|
262
|
+
if (typeof event?.type === "string" && event.type === "agent_start") {
|
|
263
|
+
current = [];
|
|
264
|
+
segments.push(current);
|
|
265
|
+
}
|
|
266
|
+
if (current === null) {
|
|
267
|
+
current = [];
|
|
268
|
+
segments.push(current);
|
|
269
|
+
}
|
|
270
|
+
current.push(event);
|
|
271
|
+
}
|
|
272
|
+
return segments;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Resolve the events visible in the「执行过程」tab for the selected execution
|
|
277
|
+
* key. attempt-N shows only that attempt's segment when the shared log carries
|
|
278
|
+
* multiple attempts; other keys (pass-N, null) keep the full stream.
|
|
279
|
+
*/
|
|
280
|
+
export function resolveTimelineVisibleEvents(events, executionKey) {
|
|
281
|
+
const match =
|
|
282
|
+
typeof executionKey === "string"
|
|
283
|
+
? /^attempt-(\d+)$/.exec(executionKey)
|
|
284
|
+
: null;
|
|
285
|
+
if (!match) return { events, note: null };
|
|
286
|
+
const segments = splitSessionEventsByAttempt(events);
|
|
287
|
+
if (segments.length <= 1) return { events, note: null };
|
|
288
|
+
const requested = Number(match[1]);
|
|
289
|
+
if (requested >= 1 && requested <= segments.length) {
|
|
290
|
+
return {
|
|
291
|
+
events: segments[requested - 1],
|
|
292
|
+
note: `已按第 ${requested} 次执行过滤 · 共 ${segments.length} 次执行`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
events,
|
|
297
|
+
note: `未检测到第 ${requested} 次执行的边界(共 ${segments.length} 次执行),暂显示全部事件`,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
253
301
|
/**
|
|
254
302
|
* Build rich timeline items: merge tool start/end by toolCallId, collapse
|
|
255
303
|
* protocol noise, surface turn thinking/text without dumping agent_end.messages.
|
|
@@ -439,12 +487,25 @@ function ensureOpenDetailsSet() {
|
|
|
439
487
|
return uiState.sessionTimelineOpenDetails;
|
|
440
488
|
}
|
|
441
489
|
|
|
442
|
-
function
|
|
490
|
+
function closedDetailKey(detailKey) {
|
|
491
|
+
return `${detailKey}::user-closed`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function bindDetailsToggle(details, detailKey, defaultOpen = false) {
|
|
443
495
|
const openSet = ensureOpenDetailsSet();
|
|
444
|
-
if (
|
|
496
|
+
if (defaultOpen) {
|
|
497
|
+
if (!openSet.has(closedDetailKey(detailKey))) details.open = true;
|
|
498
|
+
} else if (openSet.has(detailKey)) {
|
|
499
|
+
details.open = true;
|
|
500
|
+
}
|
|
445
501
|
details.addEventListener("toggle", () => {
|
|
446
|
-
if (details.open)
|
|
447
|
-
|
|
502
|
+
if (details.open) {
|
|
503
|
+
openSet.add(detailKey);
|
|
504
|
+
openSet.delete(closedDetailKey(detailKey));
|
|
505
|
+
} else {
|
|
506
|
+
openSet.delete(detailKey);
|
|
507
|
+
openSet.add(closedDetailKey(detailKey));
|
|
508
|
+
}
|
|
448
509
|
});
|
|
449
510
|
}
|
|
450
511
|
|
|
@@ -599,7 +660,7 @@ function renderProtocolCard(item) {
|
|
|
599
660
|
const details = document.createElement("details");
|
|
600
661
|
details.className = "process-timeline-details";
|
|
601
662
|
details.dataset.detailKey = item.detailKey;
|
|
602
|
-
bindDetailsToggle(details, item.detailKey);
|
|
663
|
+
bindDetailsToggle(details, item.detailKey, true);
|
|
603
664
|
const summary = document.createElement("summary");
|
|
604
665
|
summary.className = "process-timeline-summary";
|
|
605
666
|
summary.textContent = `消息收发 · ${item.protocolCount} 条协议事件`;
|
|
@@ -644,7 +705,7 @@ export async function mergeSessionEvents(dagRunId, nodeId) {
|
|
|
644
705
|
}
|
|
645
706
|
}
|
|
646
707
|
|
|
647
|
-
export function renderSessionTimeline(content, nodeId) {
|
|
708
|
+
export function renderSessionTimeline(content, nodeId, options = {}) {
|
|
648
709
|
content.dataset.dagRunId = uiState.currentDagRunId ?? "";
|
|
649
710
|
content.dataset.nodeId = nodeId ?? "";
|
|
650
711
|
content.classList.add("process-timeline-scroll");
|
|
@@ -653,6 +714,14 @@ export function renderSessionTimeline(content, nodeId) {
|
|
|
653
714
|
return;
|
|
654
715
|
}
|
|
655
716
|
|
|
717
|
+
const visible = resolveTimelineVisibleEvents(
|
|
718
|
+
uiState.sessionEvents,
|
|
719
|
+
options.executionKey,
|
|
720
|
+
);
|
|
721
|
+
if (visible.note) {
|
|
722
|
+
content.appendChild(el("p", "process-timeline-note", visible.note));
|
|
723
|
+
}
|
|
724
|
+
|
|
656
725
|
const showProtocol = Boolean(uiState.sessionTimelineShowProtocol);
|
|
657
726
|
const toolbar = el("div", "process-timeline-toolbar");
|
|
658
727
|
const protocolToggle = document.createElement("button");
|
|
@@ -668,17 +737,17 @@ export function renderSessionTimeline(content, nodeId) {
|
|
|
668
737
|
const scrollTop = parent.scrollTop;
|
|
669
738
|
const scrollLeft = parent.scrollLeft;
|
|
670
739
|
while (parent.firstChild) parent.removeChild(parent.firstChild);
|
|
671
|
-
renderSessionTimeline(parent, nodeId);
|
|
740
|
+
renderSessionTimeline(parent, nodeId, options);
|
|
672
741
|
parent.scrollTop = scrollTop;
|
|
673
742
|
parent.scrollLeft = scrollLeft;
|
|
674
743
|
});
|
|
675
744
|
toolbar.appendChild(protocolToggle);
|
|
676
745
|
content.appendChild(toolbar);
|
|
677
746
|
|
|
678
|
-
const timelineItems = buildRichTimelineItems(
|
|
747
|
+
const timelineItems = buildRichTimelineItems(visible.events, {
|
|
679
748
|
showProtocol,
|
|
680
749
|
});
|
|
681
|
-
const hasEventTimes =
|
|
750
|
+
const hasEventTimes = visible.events.some((event) =>
|
|
682
751
|
Boolean(sessionEventTimestamp(event)),
|
|
683
752
|
);
|
|
684
753
|
if (!hasEventTimes) {
|
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import YAML from "yaml";
|
|
7
7
|
import { resolveBackendTestLayout, } from "./backend-test-layout.js";
|
|
8
|
-
import { assessScenarioPartitionCoverage, parseScenarioPartitions, } from "./backend-test-scenario-partitions.js";
|
|
8
|
+
import { applyOpenApiPartitionDomainPolicy, assessScenarioPartitionCoverage, parseScenarioPartitions, } from "./backend-test-scenario-partitions.js";
|
|
9
9
|
import { backendTestCaseManifestSchema, computeCaseManifestCoverageSummary, } from "./backend-test-case-manifest.js";
|
|
10
10
|
const CASE_HEADING = /^##\s+(BE-[A-Z0-9_-]+-\d{3})\b.*$/gm;
|
|
11
11
|
const CASE_ID_IN_TEXT = /\bBE-[A-Z0-9_-]+-\d{3}\b/g;
|
|
@@ -486,6 +486,55 @@ export async function extractBackendTestOpenApiRules(input) {
|
|
|
486
486
|
}
|
|
487
487
|
return rules.sort((left, right) => left.ruleKey.localeCompare(right.ruleKey));
|
|
488
488
|
}
|
|
489
|
+
export async function extractBackendTestOpenApiPartitionAxes(input) {
|
|
490
|
+
const axes = [];
|
|
491
|
+
const seen = new Set();
|
|
492
|
+
for (const referencePath of input.sourceBinding.referencePaths) {
|
|
493
|
+
const physicalPath = resolveBoundSourcePath(input.workspaceRoot, input.sourceBinding.taskId, referencePath);
|
|
494
|
+
let document;
|
|
495
|
+
try {
|
|
496
|
+
const parsed = YAML.parse(await readFile(physicalPath, "utf8"));
|
|
497
|
+
if (!isRecord(parsed) || !isRecord(parsed.paths))
|
|
498
|
+
continue;
|
|
499
|
+
document = parsed;
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
for (const [apiPath, pathItem] of Object.entries(document.paths)) {
|
|
505
|
+
if (!isRecord(pathItem))
|
|
506
|
+
continue;
|
|
507
|
+
const operation = pathItem.get;
|
|
508
|
+
if (!isRecord(operation))
|
|
509
|
+
continue;
|
|
510
|
+
const parameterValues = [
|
|
511
|
+
...(Array.isArray(pathItem.parameters) ? pathItem.parameters : []),
|
|
512
|
+
...(Array.isArray(operation.parameters) ? operation.parameters : []),
|
|
513
|
+
];
|
|
514
|
+
for (const rawParameter of parameterValues) {
|
|
515
|
+
const parameter = resolveOpenApiSchema(document, rawParameter);
|
|
516
|
+
if (!parameter || typeof parameter.name !== "string")
|
|
517
|
+
continue;
|
|
518
|
+
const location = parameter.in === "path" ? "path" : parameter.in === "query" ? "query" : undefined;
|
|
519
|
+
if (!location)
|
|
520
|
+
continue;
|
|
521
|
+
const property = resolveOpenApiSchema(document, parameter.schema);
|
|
522
|
+
const enumValues = property && Array.isArray(property.enum) ? property.enum.map(String) : null;
|
|
523
|
+
const identity = `GET ${apiPath}|${location}|${parameter.name}`;
|
|
524
|
+
if (seen.has(identity))
|
|
525
|
+
continue;
|
|
526
|
+
seen.add(identity);
|
|
527
|
+
axes.push({
|
|
528
|
+
operation: `GET ${apiPath}`,
|
|
529
|
+
name: parameter.name,
|
|
530
|
+
in: location,
|
|
531
|
+
enumValues,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return axes;
|
|
537
|
+
}
|
|
489
538
|
function orderedUnique(values) {
|
|
490
539
|
const result = [];
|
|
491
540
|
const seen = new Set();
|
|
@@ -985,8 +1034,11 @@ export async function analyzeBackendTestCaseCoverage(input) {
|
|
|
985
1034
|
// Plan C: deterministic query/filter axis partitions (fail-closed on missing slots).
|
|
986
1035
|
const parsedPartitions = parseScenarioPartitions(readme);
|
|
987
1036
|
findings.push(...parsedPartitions.issues);
|
|
1037
|
+
const openApiPartitionAxes = await extractBackendTestOpenApiPartitionAxes(input);
|
|
1038
|
+
const partitionedBySource = applyOpenApiPartitionDomainPolicy(parsedPartitions.partitions, openApiPartitionAxes);
|
|
1039
|
+
findings.push(...partitionedBySource.issues);
|
|
988
1040
|
const declaredPointUniverse = new Set(cases.flatMap((item) => item.testPoints));
|
|
989
|
-
const partitionAssessment = assessScenarioPartitionCoverage(
|
|
1041
|
+
const partitionAssessment = assessScenarioPartitionCoverage(partitionedBySource.partitions, declaredPointUniverse);
|
|
990
1042
|
findings.push(...partitionAssessment.findings);
|
|
991
1043
|
const [enumValueCount, coveredEnumValueCount] = dimensionCounts(rules, /enum/);
|
|
992
1044
|
const [boundaryPointCount, coveredBoundaryPointCount] = dimensionCounts(rules, /boundary|length|numeric/);
|
|
@@ -1003,7 +1055,7 @@ export async function analyzeBackendTestCaseCoverage(input) {
|
|
|
1003
1055
|
sourceBinding: input.sourceBinding,
|
|
1004
1056
|
inputFiles: await inputFileFacts(input.workspaceRoot, [
|
|
1005
1057
|
...((await exists(readmePath)) ? [readmePath] : []),
|
|
1006
|
-
...(await markdownModuleFiles(input.workspaceRoot)),
|
|
1058
|
+
...(await markdownModuleFiles(input.workspaceRoot, layout)),
|
|
1007
1059
|
...(await Promise.all(input.sourceBinding.referencePaths.map(async (referencePath) => {
|
|
1008
1060
|
const physicalPath = resolveBoundSourcePath(input.workspaceRoot, input.sourceBinding.taskId, referencePath);
|
|
1009
1061
|
return (await exists(physicalPath)) ? physicalPath : "";
|
|
@@ -1448,8 +1500,26 @@ print(json.dumps(out,ensure_ascii=True))
|
|
|
1448
1500
|
// Windows Python may expose stdin as GBK; decoding generated Chinese/emoji
|
|
1449
1501
|
// source through that locale can create surrogateescape code points and make
|
|
1450
1502
|
// ast.parse fail with "surrogates not allowed".
|
|
1451
|
-
|
|
1452
|
-
|
|
1503
|
+
// POSIX images usually expose only `python3` (modern macOS has no `python`);
|
|
1504
|
+
// Windows often has a real `python` plus a Microsoft Store `python3` stub.
|
|
1505
|
+
// A missing interpreter must not silently degrade to empty payload shapes.
|
|
1506
|
+
const interpreterCandidates = process.platform === "win32" ? ["python", "python3"] : ["python3", "python"];
|
|
1507
|
+
let result;
|
|
1508
|
+
for (const interpreter of interpreterCandidates) {
|
|
1509
|
+
const attempt = spawnSync(interpreter, ["-c", program], {
|
|
1510
|
+
input: Buffer.from(source, "utf8"),
|
|
1511
|
+
encoding: "utf8",
|
|
1512
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
1513
|
+
windowsHide: true,
|
|
1514
|
+
});
|
|
1515
|
+
// Interpreter missing/unspawnable → try the next candidate. A spawned
|
|
1516
|
+
// interpreter that exits nonzero analyzed real input; do not retry then.
|
|
1517
|
+
if (attempt.error)
|
|
1518
|
+
continue;
|
|
1519
|
+
result = attempt;
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
if (!result || result.status !== 0 || !result.stdout.trim())
|
|
1453
1523
|
return new Map();
|
|
1454
1524
|
try {
|
|
1455
1525
|
const parsed = JSON.parse(result.stdout);
|
|
@@ -1794,7 +1864,7 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
|
|
|
1794
1864
|
status: findings.length === 0 ? "PASS" : "FAIL",
|
|
1795
1865
|
inputFiles: await inputFileFacts(input.workspaceRoot, [
|
|
1796
1866
|
path.join(input.workspaceRoot, ...layout.readmePath.split("/")),
|
|
1797
|
-
...(await markdownModuleFiles(input.workspaceRoot)),
|
|
1867
|
+
...(await markdownModuleFiles(input.workspaceRoot, layout)),
|
|
1798
1868
|
...scriptFiles,
|
|
1799
1869
|
].filter((file) => file && path.isAbsolute(file))),
|
|
1800
1870
|
summary: {
|
|
@@ -35,6 +35,8 @@ export const scenarioPartitionRowSchema = z.object({
|
|
|
35
35
|
const PARTITION_HEADING = /^##\s+Scenario Partitions\s*$/;
|
|
36
36
|
const PARTITION_TABLE_HEADER = /^\|\s*Partition ID\s*\|\s*Operation\s*\|\s*Axis\s*\|\s*Domain\s*\|\s*Required Slots\s*\|\s*Expected by Slot\s*\|\s*Bind Rule\s*\|\s*$/;
|
|
37
37
|
const TABLE_ROW = /^\|(.+)\|$/;
|
|
38
|
+
const FINITE_DOMAIN_MEMBER = /^[A-Za-z0-9]+(?:[._\- ][A-Za-z0-9]+)*$/;
|
|
39
|
+
const RESERVED_SLOT_TOKENS = new Set(["OMITTED", "NOT-IN-SET"]);
|
|
38
40
|
function splitCells(raw) {
|
|
39
41
|
return raw
|
|
40
42
|
.split("|")
|
|
@@ -100,6 +102,11 @@ export function parseScenarioPartitions(readmeMarkdown) {
|
|
|
100
102
|
continue;
|
|
101
103
|
}
|
|
102
104
|
seen.add(partitionId);
|
|
105
|
+
const domainIssue = describeInvalidPartitionDomain(domain);
|
|
106
|
+
if (domainIssue) {
|
|
107
|
+
issues.push(`INVALID_PARTITION_DOMAIN: partition ${partitionId} ${domainIssue}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
103
110
|
partitions.push({
|
|
104
111
|
partitionId,
|
|
105
112
|
operation,
|
|
@@ -119,6 +126,40 @@ function slotToken(value) {
|
|
|
119
126
|
.replace(/[^A-Z0-9]+/g, "-")
|
|
120
127
|
.replace(/^-+|-+$/g, "");
|
|
121
128
|
}
|
|
129
|
+
function describeInvalidPartitionDomain(domain) {
|
|
130
|
+
const tokens = [];
|
|
131
|
+
const seenTokens = new Set();
|
|
132
|
+
for (const value of domain) {
|
|
133
|
+
if (!FINITE_DOMAIN_MEMBER.test(value)) {
|
|
134
|
+
return `value is not a finite identifier member: ${value}`;
|
|
135
|
+
}
|
|
136
|
+
const token = slotToken(value);
|
|
137
|
+
if (!token) {
|
|
138
|
+
return `value cannot produce a stable non-empty slot token: ${value}`;
|
|
139
|
+
}
|
|
140
|
+
if (RESERVED_SLOT_TOKENS.has(token)) {
|
|
141
|
+
return `value maps to reserved slot token ${token}: ${value}`;
|
|
142
|
+
}
|
|
143
|
+
if (seenTokens.has(token)) {
|
|
144
|
+
return `values collapse to duplicate slot token ${token}`;
|
|
145
|
+
}
|
|
146
|
+
seenTokens.add(token);
|
|
147
|
+
tokens.push(token);
|
|
148
|
+
}
|
|
149
|
+
return tokens.length === 0 ? "domain produced no slot tokens" : undefined;
|
|
150
|
+
}
|
|
151
|
+
function normalizePartitionAxisName(value) {
|
|
152
|
+
return value.trim().replace(/^(?:query|path)\s*:\s*/i, "").toLowerCase();
|
|
153
|
+
}
|
|
154
|
+
function normalizePartitionOperation(value) {
|
|
155
|
+
return value.trim().replace(/\s+/g, " ").toUpperCase();
|
|
156
|
+
}
|
|
157
|
+
function sameFiniteSet(left, right) {
|
|
158
|
+
if (left.length !== right.length)
|
|
159
|
+
return false;
|
|
160
|
+
const expected = new Set(left);
|
|
161
|
+
return right.every((value) => expected.has(value));
|
|
162
|
+
}
|
|
122
163
|
const STABLE_PARTITION_ID = /^SP-[A-Z0-9][A-Z0-9._-]*$/i;
|
|
123
164
|
/**
|
|
124
165
|
* Slot IDs are `TP-<Partition ID>-<VALUE|OMITTED|NOT-IN-SET>`.
|
|
@@ -138,13 +179,16 @@ function partitionToken(row) {
|
|
|
138
179
|
export function expandScenarioPartitions(partitions) {
|
|
139
180
|
const slots = [];
|
|
140
181
|
for (const row of partitions) {
|
|
182
|
+
if (describeInvalidPartitionDomain(row.domain))
|
|
183
|
+
continue;
|
|
141
184
|
const prefix = partitionToken(row);
|
|
142
185
|
for (const value of row.domain) {
|
|
186
|
+
const token = slotToken(value);
|
|
143
187
|
slots.push({
|
|
144
188
|
partitionId: row.partitionId,
|
|
145
189
|
operation: row.operation,
|
|
146
190
|
axis: row.axis,
|
|
147
|
-
slotId: `${prefix}-${
|
|
191
|
+
slotId: `${prefix}-${token}`,
|
|
148
192
|
kind: "each-value",
|
|
149
193
|
value,
|
|
150
194
|
intent: "nominal-filter",
|
|
@@ -176,6 +220,34 @@ export function expandScenarioPartitions(partitions) {
|
|
|
176
220
|
}
|
|
177
221
|
return slots;
|
|
178
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Bind parsed partitions to GET query/path OpenAPI axes.
|
|
225
|
+
* Unconfirmed axes keep identifier-valid Domain rows (requirement finite sets).
|
|
226
|
+
*/
|
|
227
|
+
export function applyOpenApiPartitionDomainPolicy(partitions, axes) {
|
|
228
|
+
const issues = [];
|
|
229
|
+
const valid = [];
|
|
230
|
+
for (const row of partitions) {
|
|
231
|
+
const operation = normalizePartitionOperation(row.operation);
|
|
232
|
+
const axisName = normalizePartitionAxisName(row.axis);
|
|
233
|
+
const matched = axes.find((axis) => normalizePartitionOperation(axis.operation) === operation &&
|
|
234
|
+
normalizePartitionAxisName(axis.name) === axisName);
|
|
235
|
+
if (!matched) {
|
|
236
|
+
valid.push(row);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (matched.enumValues === null) {
|
|
240
|
+
issues.push(`NO_FINITE_DOMAIN: partition ${row.partitionId} axis ${row.axis} exists on ${row.operation} without a documented enum`);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!sameFiniteSet(matched.enumValues, row.domain)) {
|
|
244
|
+
issues.push(`PARTITION_SOURCE_MISMATCH: partition ${row.partitionId} domain does not match OpenAPI enum for ${row.operation} ${row.axis}`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
valid.push(row);
|
|
248
|
+
}
|
|
249
|
+
return { partitions: valid, issues };
|
|
250
|
+
}
|
|
179
251
|
/** Facts projection consumed by N6 and plan D gap-fill. */
|
|
180
252
|
export const scenarioPartitionFactsSchema = z.object({
|
|
181
253
|
partitions: z.array(z.object({
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { structuredContractOutputSchemaIds, } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Registry of node-level structured contract validators, keyed by the
|
|
4
|
+
* `structuredContractOutput.schemaId` declared on a DAG task.
|
|
5
|
+
*
|
|
6
|
+
* The executor looks the validator up here instead of hardcoding per-contract
|
|
7
|
+
* imports, so new contracts (backend-test manifests, coverage contracts, ...)
|
|
8
|
+
* can opt into producing-node self-validation without touching the executor.
|
|
9
|
+
*/
|
|
10
|
+
export const structuredContractValidationClassifications = [
|
|
11
|
+
"format",
|
|
12
|
+
"schema",
|
|
13
|
+
"truncated",
|
|
14
|
+
"semantic-repair",
|
|
15
|
+
"governance-blocked",
|
|
16
|
+
];
|
|
2
17
|
const validators = new Map();
|
|
3
18
|
export function registerStructuredContractValidator(schemaId, validator) {
|
|
4
19
|
if (validators.has(schemaId)) {
|
|
@@ -29,8 +29,13 @@ const RAW_TO_NORMALIZED = {
|
|
|
29
29
|
"decision-envelope-invalid": "decision-envelope",
|
|
30
30
|
"nonzero-exit": "shell-command",
|
|
31
31
|
"spawn-error": "shell-command",
|
|
32
|
+
"test-timeout": "timeout",
|
|
32
33
|
"static-error": "static-error",
|
|
33
34
|
"invalid-output": "validation",
|
|
35
|
+
"structured-output-truncated": "validation",
|
|
36
|
+
"output-too-large": "validation",
|
|
37
|
+
"structured-repair-exhausted": "validation",
|
|
38
|
+
"governance-blocked": "validation",
|
|
34
39
|
"path-violation": "validation",
|
|
35
40
|
"empty-output": "validation",
|
|
36
41
|
"verify-failure": "validation",
|
|
@@ -40,6 +45,13 @@ const RAW_TO_NORMALIZED = {
|
|
|
40
45
|
quota: "executor",
|
|
41
46
|
"rate-limit": "executor",
|
|
42
47
|
unavailable: "executor",
|
|
48
|
+
// Deterministic capability/policy refusal: missing verified launcher JS
|
|
49
|
+
// entry, capability allowlist rejection, or missing bounded write tool
|
|
50
|
+
// profile. These are environment/config defects the operator repairs
|
|
51
|
+
// before rerunning; classify as executor so recovery recommends
|
|
52
|
+
// rerun-after-fix WITHOUT forcing a human-decision gate on
|
|
53
|
+
// standaloneTaskRerun (run-failed same-task rerun).
|
|
54
|
+
"tool-policy": "executor",
|
|
43
55
|
unknown: "unknown",
|
|
44
56
|
};
|
|
45
57
|
function isFinishedStatus(status) {
|