@tea-agent/loop-agent 0.38.2 → 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 +18 -0
- package/README.md +2 -0
- 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/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +58 -1
- 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 +56 -2
- package/dist/executors/shell-write-guard.js +7 -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/pi-readiness.js +4 -4
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-C4kVYweA.js} +1 -1
- package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-Bj2M8iO5.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-CLlXe4-6.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-psEp5xH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.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-D_Bq2ZqK.js → chunk-2GRJ4B5K-K0C744p1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-xbNw4J9-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-DjLaSDUO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-CtiPlKel.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-DZtolir7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-CPcdNeew.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-NgHc6V37.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-DQ_T-hg8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-DgLsxcVP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.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-BC6Bgosf.js → cose-bilkent-JH36ORCC-CLkYvTb3.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-BwT_xKrE.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-Bi9tuzif.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-BCIqkWBV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-Dip6l9_Z.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-B-xkh_wK.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-DZO0kTt_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-D6ZplNbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-BPekbhoS.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-BtmaJpK5.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-6DS0Xc44.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-CihnROSm.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-CZC4zOE2.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js} +1 -1
- package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DTZOKgAn.js} +142 -63
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-DlSl4BGx.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-snnzFl_U.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-BDE7qpMx.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-CD2Ci04G.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-DdnapKIH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-BVjAT9b8.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-D0yJk-zA.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BVo9tFP-.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-DnOV-mZ_.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-CzUJo58i.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-Bvikrolh.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-DtaSVCap.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-C2c9A0wW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-DmXJcU7r.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-B4GUFW92.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.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-DOSnT7hb.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-DVJQA7AN.js → timeline-definition-Z64GVDOM-CSSi6OFf.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BqyzPrWv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DlznVb6S.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-CfBcgJ_K.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- 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 +56 -4
- 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 +4 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +140 -39
- package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
- 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 +79 -101
- package/dist/workflows/dag/node-execution.js +119 -8
- package/dist/workflows/dag/prompt.js +15 -1
- package/dist/workflows/dag/rerun-plan.js +22 -3
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +23 -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 +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- 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/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-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
|
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
4
4
|
import { resolveDagNodeSkills } from "../../workflows/dag/skills.js";
|
|
5
|
-
import { resolveDagNodePromptRedacted, resolveDagRunJson, } from "./dag-run-artifacts.js";
|
|
5
|
+
import { resolveDagNodePromptRedacted, resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
|
|
6
6
|
export const NODE_INPUT_SCHEMA_VERSION = 1;
|
|
7
7
|
export const NODE_INPUT_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
8
8
|
const SHELL_GATE_KEYS = [
|
|
@@ -33,6 +33,7 @@ function unavailableBody(dagRunId, nodeId, reason, warnings = []) {
|
|
|
33
33
|
executorInput: null,
|
|
34
34
|
boundaries: null,
|
|
35
35
|
promptFingerprint: null,
|
|
36
|
+
piExtensionRunFacts: null,
|
|
36
37
|
warnings,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
@@ -137,7 +138,7 @@ function resolveModelHint(spec, task) {
|
|
|
137
138
|
}
|
|
138
139
|
return null;
|
|
139
140
|
}
|
|
140
|
-
function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warnings) {
|
|
141
|
+
function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExtensionRunFacts, warnings) {
|
|
141
142
|
const defaults = spec.defaults && typeof spec.defaults === "object"
|
|
142
143
|
? spec.defaults
|
|
143
144
|
: undefined;
|
|
@@ -268,6 +269,7 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
|
|
|
268
269
|
kind,
|
|
269
270
|
skills,
|
|
270
271
|
modelHint: resolveModelHint(spec, task),
|
|
272
|
+
piExtensions: redactStringList(task.piExtensions),
|
|
271
273
|
shell,
|
|
272
274
|
static: staticBlock,
|
|
273
275
|
},
|
|
@@ -284,9 +286,72 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
|
|
|
284
286
|
: null,
|
|
285
287
|
},
|
|
286
288
|
promptFingerprint: fingerprint,
|
|
289
|
+
piExtensionRunFacts,
|
|
287
290
|
warnings,
|
|
288
291
|
};
|
|
289
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Load run-time Pi extension facts from <node>/pi-extensions.json (plan
|
|
295
|
+
* 2026-08-22 R2). Returns null when the node declares no piExtensions
|
|
296
|
+
* (no expected artifact). Never throws: unreadable/corrupt artifacts degrade
|
|
297
|
+
* to parseError stubs so the node-input endpoint stays 200.
|
|
298
|
+
*/
|
|
299
|
+
async function loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declared, warnings) {
|
|
300
|
+
if (declared.length === 0)
|
|
301
|
+
return null;
|
|
302
|
+
const artifact = path.posix.join(nodeId, "pi-extensions.json");
|
|
303
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
304
|
+
nodeId,
|
|
305
|
+
"pi-extensions.json",
|
|
306
|
+
]);
|
|
307
|
+
if (!resolved.ok) {
|
|
308
|
+
if (resolved.reason === "ambiguous") {
|
|
309
|
+
warnings.push("Ambiguous pi-extensions.json lifecycle copies.");
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
artifact,
|
|
313
|
+
exists: false,
|
|
314
|
+
requested: [...declared],
|
|
315
|
+
resolved: [],
|
|
316
|
+
missing: [],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const empty = {
|
|
320
|
+
artifact,
|
|
321
|
+
exists: false,
|
|
322
|
+
requested: [...declared],
|
|
323
|
+
resolved: [],
|
|
324
|
+
missing: [],
|
|
325
|
+
};
|
|
326
|
+
try {
|
|
327
|
+
const raw = JSON.parse(await readFile(resolved.result.absolutePath, "utf-8"));
|
|
328
|
+
const resolvedList = Array.isArray(raw.resolved) ? raw.resolved : [];
|
|
329
|
+
const missingList = Array.isArray(raw.missing) ? raw.missing : [];
|
|
330
|
+
const projected = resolvedList
|
|
331
|
+
.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
|
|
332
|
+
.map((entry) => ({
|
|
333
|
+
id: redactSecrets(String(entry.id ?? "")),
|
|
334
|
+
packageName: redactSecrets(String(entry.packageName ?? "")),
|
|
335
|
+
version: redactSecrets(String(entry.version ?? "")),
|
|
336
|
+
}));
|
|
337
|
+
const missingProjected = missingList
|
|
338
|
+
.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
|
|
339
|
+
.map((entry) => ({
|
|
340
|
+
id: redactSecrets(String(entry.id ?? "")),
|
|
341
|
+
reason: redactSecrets(String(entry.reason ?? "")),
|
|
342
|
+
}));
|
|
343
|
+
return {
|
|
344
|
+
artifact,
|
|
345
|
+
exists: true,
|
|
346
|
+
requested: redactStringList(raw.requested ?? declared),
|
|
347
|
+
resolved: projected,
|
|
348
|
+
missing: missingProjected,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return { ...empty, parseError: "read-failed" };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
290
355
|
async function loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings) {
|
|
291
356
|
const resolved = resolveDagNodePromptRedacted(repoRoot, dagRunId, nodeId);
|
|
292
357
|
if (!resolved.ok) {
|
|
@@ -415,8 +480,12 @@ export async function buildDagNodeInput(repoRoot, dagRunId, nodeId) {
|
|
|
415
480
|
}
|
|
416
481
|
const warnings = [];
|
|
417
482
|
const fingerprint = await loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings);
|
|
483
|
+
const declaredPiExtensions = Array.isArray(task.piExtensions)
|
|
484
|
+
? task.piExtensions.filter((item) => typeof item === "string")
|
|
485
|
+
: [];
|
|
486
|
+
const piExtensionRunFacts = await loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declaredPiExtensions, warnings);
|
|
418
487
|
// Reject unknown nested leakage by projecting only through allowlist helpers.
|
|
419
|
-
const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, warnings);
|
|
488
|
+
const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, piExtensionRunFacts, warnings);
|
|
420
489
|
// Defense-in-depth: ensure raw task never sneaks in.
|
|
421
490
|
if ("task" in body) {
|
|
422
491
|
return {
|
|
@@ -14,6 +14,10 @@ const ATTEMPT_FAILURE_LABELS = {
|
|
|
14
14
|
timeout: "执行超时",
|
|
15
15
|
"nonzero-exit": "命令执行失败",
|
|
16
16
|
"invalid-output": "输出格式无效",
|
|
17
|
+
"structured-output-truncated": "结构化输出被截断",
|
|
18
|
+
"output-too-large": "输出超出体积上限",
|
|
19
|
+
"structured-repair-exhausted": "结构化修复耗尽",
|
|
20
|
+
"governance-blocked": "治理规则阻断",
|
|
17
21
|
"empty-output": "输出为空",
|
|
18
22
|
auth: "认证失败",
|
|
19
23
|
quota: "额度或限流",
|
|
@@ -76,8 +76,8 @@ export const uiState = {
|
|
|
76
76
|
dagNodeExecutionOutputLoading: false,
|
|
77
77
|
dagNodeExecutionOutputError: null,
|
|
78
78
|
dagNodeExecutionOutputKey: null,
|
|
79
|
-
/** When true,「执行过程」展开 message_start/end
|
|
80
|
-
sessionTimelineShowProtocol:
|
|
79
|
+
/** When true,「执行过程」展开 message_start/end 原始协议事件。默认展开,用户可点选隐藏。 */
|
|
80
|
+
sessionTimelineShowProtocol: true,
|
|
81
81
|
/** Open <details> keys restored across timeline re-renders. */
|
|
82
82
|
sessionTimelineOpenDetails: null,
|
|
83
83
|
lastSnapshot: null,
|
|
@@ -725,6 +725,11 @@ function renderDagNodeInputDetail(content, data) {
|
|
|
725
725
|
}
|
|
726
726
|
section.appendChild(dl);
|
|
727
727
|
appendCodeList(section, "skills", data.executorInput.skills);
|
|
728
|
+
appendCodeList(
|
|
729
|
+
section,
|
|
730
|
+
"piExtensions(声明)",
|
|
731
|
+
data.executorInput.piExtensions,
|
|
732
|
+
);
|
|
728
733
|
if (data.executorInput.shell) {
|
|
729
734
|
const shell = data.executorInput.shell;
|
|
730
735
|
const shellDl = el("dl", "node-input-summary");
|
|
@@ -799,6 +804,52 @@ function renderDagNodeInputDetail(content, data) {
|
|
|
799
804
|
wrap.appendChild(section);
|
|
800
805
|
}
|
|
801
806
|
|
|
807
|
+
if (data.piExtensionRunFacts) {
|
|
808
|
+
const facts = data.piExtensionRunFacts;
|
|
809
|
+
const section = el("section", "node-input-section");
|
|
810
|
+
section.appendChild(el("h4", null, "Pi 扩展运行事实"));
|
|
811
|
+
if (!facts.exists) {
|
|
812
|
+
section.appendChild(
|
|
813
|
+
el(
|
|
814
|
+
"p",
|
|
815
|
+
"muted",
|
|
816
|
+
facts.parseError
|
|
817
|
+
? `产物 ${facts.artifact} 读取失败(${facts.parseError})。`
|
|
818
|
+
: `产物 ${facts.artifact} 尚未生成(节点未执行或缺失)。`,
|
|
819
|
+
),
|
|
820
|
+
);
|
|
821
|
+
} else {
|
|
822
|
+
const dl = el("dl", "node-input-summary");
|
|
823
|
+
appendMetaRow(
|
|
824
|
+
dl,
|
|
825
|
+
"resolved",
|
|
826
|
+
facts.resolved
|
|
827
|
+
.map((entry) => `${entry.id} → ${entry.packageName}@${entry.version}`)
|
|
828
|
+
.join(", ") || "(none)",
|
|
829
|
+
null,
|
|
830
|
+
);
|
|
831
|
+
section.appendChild(dl);
|
|
832
|
+
appendCodeList(
|
|
833
|
+
section,
|
|
834
|
+
"requested",
|
|
835
|
+
facts.requested,
|
|
836
|
+
);
|
|
837
|
+
for (const missingEntry of facts.missing ?? []) {
|
|
838
|
+
section.appendChild(
|
|
839
|
+
el(
|
|
840
|
+
"p",
|
|
841
|
+
"node-input-warning",
|
|
842
|
+
`降级:${missingEntry.id} 未加载(${missingEntry.reason || "unknown"})`,
|
|
843
|
+
),
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
if ((facts.missing ?? []).length === 0) {
|
|
847
|
+
section.appendChild(el("p", "muted", "全部声明扩展均已解析加载。"));
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
wrap.appendChild(section);
|
|
851
|
+
}
|
|
852
|
+
|
|
802
853
|
if (data.promptFingerprint?.exists) {
|
|
803
854
|
const section = el("section", "node-input-section");
|
|
804
855
|
section.appendChild(el("h4", null, "Assembled prompt 指纹"));
|
|
@@ -439,12 +439,25 @@ function ensureOpenDetailsSet() {
|
|
|
439
439
|
return uiState.sessionTimelineOpenDetails;
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
function
|
|
442
|
+
function closedDetailKey(detailKey) {
|
|
443
|
+
return `${detailKey}::user-closed`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function bindDetailsToggle(details, detailKey, defaultOpen = false) {
|
|
443
447
|
const openSet = ensureOpenDetailsSet();
|
|
444
|
-
if (
|
|
448
|
+
if (defaultOpen) {
|
|
449
|
+
if (!openSet.has(closedDetailKey(detailKey))) details.open = true;
|
|
450
|
+
} else if (openSet.has(detailKey)) {
|
|
451
|
+
details.open = true;
|
|
452
|
+
}
|
|
445
453
|
details.addEventListener("toggle", () => {
|
|
446
|
-
if (details.open)
|
|
447
|
-
|
|
454
|
+
if (details.open) {
|
|
455
|
+
openSet.add(detailKey);
|
|
456
|
+
openSet.delete(closedDetailKey(detailKey));
|
|
457
|
+
} else {
|
|
458
|
+
openSet.delete(detailKey);
|
|
459
|
+
openSet.add(closedDetailKey(detailKey));
|
|
460
|
+
}
|
|
448
461
|
});
|
|
449
462
|
}
|
|
450
463
|
|
|
@@ -599,7 +612,7 @@ function renderProtocolCard(item) {
|
|
|
599
612
|
const details = document.createElement("details");
|
|
600
613
|
details.className = "process-timeline-details";
|
|
601
614
|
details.dataset.detailKey = item.detailKey;
|
|
602
|
-
bindDetailsToggle(details, item.detailKey);
|
|
615
|
+
bindDetailsToggle(details, item.detailKey, true);
|
|
603
616
|
const summary = document.createElement("summary");
|
|
604
617
|
summary.className = "process-timeline-summary";
|
|
605
618
|
summary.textContent = `消息收发 · ${item.protocolCount} 条协议事件`;
|
|
@@ -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 : "";
|
|
@@ -1794,7 +1846,7 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
|
|
|
1794
1846
|
status: findings.length === 0 ? "PASS" : "FAIL",
|
|
1795
1847
|
inputFiles: await inputFileFacts(input.workspaceRoot, [
|
|
1796
1848
|
path.join(input.workspaceRoot, ...layout.readmePath.split("/")),
|
|
1797
|
-
...(await markdownModuleFiles(input.workspaceRoot)),
|
|
1849
|
+
...(await markdownModuleFiles(input.workspaceRoot, layout)),
|
|
1798
1850
|
...scriptFiles,
|
|
1799
1851
|
].filter((file) => file && path.isAbsolute(file))),
|
|
1800
1852
|
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)) {
|
|
@@ -31,6 +31,10 @@ const RAW_TO_NORMALIZED = {
|
|
|
31
31
|
"spawn-error": "shell-command",
|
|
32
32
|
"static-error": "static-error",
|
|
33
33
|
"invalid-output": "validation",
|
|
34
|
+
"structured-output-truncated": "validation",
|
|
35
|
+
"output-too-large": "validation",
|
|
36
|
+
"structured-repair-exhausted": "validation",
|
|
37
|
+
"governance-blocked": "validation",
|
|
34
38
|
"path-violation": "validation",
|
|
35
39
|
"empty-output": "validation",
|
|
36
40
|
"verify-failure": "validation",
|