@tea-agent/loop-agent 0.40.0-next.2 → 0.40.0-next.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/AGENTS.md +1 -1
- package/CHANGELOG.md +8 -2
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/server.js +73 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DLMldIE8.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DVOg1dSW.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-715v7joo.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BtMEGtAQ.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-qUxXsdxu.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BYMag72f.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5jt5rtx.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DvZghfS2.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CXutxAmY.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BkANIDWk.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bwy1wwlr.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3pWYVCG.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DRzjzmFD.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C4xXDxXL.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BngIZ90b.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-B40ACoFn.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BE-1ZRAI.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BoYbwVTh.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-B5-KA0fK.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DJpCToT5.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-De8R04Ng.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BU1N8DiA.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BUUUzSYI.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-zLoT1RC4.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-_q4-4N8c.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CTqzoNSL.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMKSYKJM.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Dj6U8Sb7.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BfoxVYky.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/{index-CA0nQEbb.js → index-CW88Fjbw.js} +81 -81
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-T5NfvHGV.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BEhUMkut.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-B2Ld6nNt.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-nV9T85yS.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIO1zDYh.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-B_ucAqLq.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BIA3VNcZ.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CNzLjNV3.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-PWytzfc_.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CS0wGoIq.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-C3Wsqph8.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DF6FDTYg.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BdQUJm6y.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Bv9rppGg.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C3Hpvb_B.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CZOWLpno.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CPQRIZx5.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CkwM9BgA.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-wGSoDzYc.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-GNVas2gx.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-C5USnHw7.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/static/api.js +3 -3
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/styles.css +338 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +531 -78
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +4 -3
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +125 -4
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +42 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/package.json +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-BG6TLen_.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dgsvn63q.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DaNfG1BJ.js +0 -8
|
@@ -31,6 +31,7 @@ function unavailableBody(dagRunId, nodeId, reason, warnings = []) {
|
|
|
31
31
|
summary: null,
|
|
32
32
|
dependency: null,
|
|
33
33
|
prompt: null,
|
|
34
|
+
promptCandidates: [],
|
|
34
35
|
executorInput: null,
|
|
35
36
|
boundaries: null,
|
|
36
37
|
promptFingerprint: null,
|
|
@@ -139,7 +140,7 @@ function resolveModelHint(spec, task) {
|
|
|
139
140
|
}
|
|
140
141
|
return null;
|
|
141
142
|
}
|
|
142
|
-
function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExtensionRunFacts, warnings) {
|
|
143
|
+
function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExtensionRunFacts, promptCandidates, warnings) {
|
|
143
144
|
const defaults = spec.defaults && typeof spec.defaults === "object"
|
|
144
145
|
? spec.defaults
|
|
145
146
|
: undefined;
|
|
@@ -267,6 +268,7 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExt
|
|
|
267
268
|
maxBytes: promptBody.maxBytes,
|
|
268
269
|
source: promptSource,
|
|
269
270
|
},
|
|
271
|
+
promptCandidates,
|
|
270
272
|
executorInput: {
|
|
271
273
|
kind,
|
|
272
274
|
skills,
|
|
@@ -292,6 +294,89 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExt
|
|
|
292
294
|
warnings,
|
|
293
295
|
};
|
|
294
296
|
}
|
|
297
|
+
async function loadFailedPromptCandidates(repoRoot, dagRunId, nodeId, warnings) {
|
|
298
|
+
const stateResolved = resolveDagRunArtifact(repoRoot, dagRunId, ["state.json"]);
|
|
299
|
+
if (!stateResolved.ok)
|
|
300
|
+
return [];
|
|
301
|
+
let state;
|
|
302
|
+
try {
|
|
303
|
+
state = JSON.parse(await readFile(stateResolved.result.absolutePath, "utf-8"));
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
warnings.push("state.json could not be read for failed prompt candidates.");
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
const nodes = state.nodes && typeof state.nodes === "object" && !Array.isArray(state.nodes)
|
|
310
|
+
? state.nodes
|
|
311
|
+
: null;
|
|
312
|
+
const node = nodes?.[nodeId] &&
|
|
313
|
+
typeof nodes[nodeId] === "object" &&
|
|
314
|
+
!Array.isArray(nodes[nodeId])
|
|
315
|
+
? nodes[nodeId]
|
|
316
|
+
: null;
|
|
317
|
+
if (!node)
|
|
318
|
+
return [];
|
|
319
|
+
const failedAttempts = new Set();
|
|
320
|
+
if (Array.isArray(node.attempts)) {
|
|
321
|
+
for (const entry of node.attempts) {
|
|
322
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
323
|
+
continue;
|
|
324
|
+
const record = entry;
|
|
325
|
+
if (record.ok === false &&
|
|
326
|
+
typeof record.attempt === "number" &&
|
|
327
|
+
Number.isSafeInteger(record.attempt) &&
|
|
328
|
+
record.attempt > 0) {
|
|
329
|
+
failedAttempts.add(record.attempt);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (node.status === "ERROR" &&
|
|
334
|
+
typeof node.currentAttempt === "number" &&
|
|
335
|
+
Number.isSafeInteger(node.currentAttempt) &&
|
|
336
|
+
node.currentAttempt > 0) {
|
|
337
|
+
failedAttempts.add(node.currentAttempt);
|
|
338
|
+
}
|
|
339
|
+
const candidates = [];
|
|
340
|
+
for (const attempt of [...failedAttempts].sort((a, b) => a - b)) {
|
|
341
|
+
const fileName = `prompt-candidate-attempt-${attempt}.json`;
|
|
342
|
+
const artifact = path.posix.join(nodeId, fileName);
|
|
343
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [nodeId, fileName]);
|
|
344
|
+
if (!resolved.ok) {
|
|
345
|
+
warnings.push(`Failed prompt candidate missing for attempt ${attempt}.`);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const raw = JSON.parse(await readFile(resolved.result.absolutePath, "utf-8"));
|
|
350
|
+
if (raw.schemaVersion !== 1 ||
|
|
351
|
+
raw.attempt !== attempt ||
|
|
352
|
+
typeof raw.failureCategory !== "string" ||
|
|
353
|
+
typeof raw.prompt !== "string" ||
|
|
354
|
+
typeof raw.truncated !== "boolean" ||
|
|
355
|
+
typeof raw.maxBytes !== "number" ||
|
|
356
|
+
!Number.isSafeInteger(raw.maxBytes) ||
|
|
357
|
+
raw.maxBytes < 1 ||
|
|
358
|
+
raw.maxBytes > NODE_INPUT_PREVIEW_MAX_BYTES ||
|
|
359
|
+
typeof raw.sha256 !== "string" ||
|
|
360
|
+
!/^([a-f0-9]{64})$/.test(raw.sha256)) {
|
|
361
|
+
throw new Error("invalid candidate schema");
|
|
362
|
+
}
|
|
363
|
+
const prompt = truncatedField(raw.prompt, raw.maxBytes);
|
|
364
|
+
candidates.push({
|
|
365
|
+
attempt,
|
|
366
|
+
failureCategory: redactSecrets(raw.failureCategory),
|
|
367
|
+
prompt: prompt.text,
|
|
368
|
+
truncated: raw.truncated || prompt.truncated,
|
|
369
|
+
maxBytes: raw.maxBytes,
|
|
370
|
+
sha256Prefix: raw.sha256.slice(0, 12),
|
|
371
|
+
artifact,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
warnings.push(`Failed prompt candidate is invalid for attempt ${attempt}.`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return candidates;
|
|
379
|
+
}
|
|
295
380
|
/**
|
|
296
381
|
* Load run-time Pi extension facts from <node>/pi-extensions.json (plan
|
|
297
382
|
* 2026-08-22 R2). Returns null when the node declares no piExtensions
|
|
@@ -486,8 +571,9 @@ export async function buildDagNodeInput(repoRoot, dagRunId, nodeId) {
|
|
|
486
571
|
? task.piExtensions.filter((item) => typeof item === "string")
|
|
487
572
|
: [];
|
|
488
573
|
const piExtensionRunFacts = await loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declaredPiExtensions, warnings);
|
|
574
|
+
const promptCandidates = await loadFailedPromptCandidates(repoRoot, dagRunId, nodeId, warnings);
|
|
489
575
|
// Reject unknown nested leakage by projecting only through allowlist helpers.
|
|
490
|
-
const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, piExtensionRunFacts, warnings);
|
|
576
|
+
const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, piExtensionRunFacts, promptCandidates, warnings);
|
|
491
577
|
// Defense-in-depth: ensure raw task never sneaks in.
|
|
492
578
|
if ("task" in body) {
|
|
493
579
|
return {
|
|
@@ -11,7 +11,11 @@ import { buildGlobalSnapshot, clampDagRunHistoryPageSize, clampTaskRunHistoryLim
|
|
|
11
11
|
import { loadDagRunExecutionTrajectory } from "../observability/dag-execution-trajectory.js";
|
|
12
12
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
13
13
|
import { dagSourceBindingSchema } from "../../workflows/dag/types.js";
|
|
14
|
+
import { getTaskPaths } from "../../task/runtime.js";
|
|
15
|
+
import { classifyLedgerGate, computeLedgerInputDigest, LedgerParseError, parseLedgerJson, recoverLedgerInputContract, summarizeLedger, } from "../../task/source-prepare/ledger.js";
|
|
14
16
|
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
17
|
+
import { resolveDagRunArtifact, } from "./dag-run-artifacts.js";
|
|
18
|
+
import { frontendPrewriteResultV1Schema } from "../../workflows/dag/frontend-prewrite-gate.js";
|
|
15
19
|
import { isAllowedArtifactTextPath, resolveArtifactPath, resolveRepoFilePreview, toRepoRelativeArtifactPath, } from "./paths.js";
|
|
16
20
|
import { extractSpecEvidence, extractSpecReadContent, } from "./spec-evidence.js";
|
|
17
21
|
import { buildDagNodeInput } from "./node-input.js";
|
|
@@ -70,6 +74,15 @@ export const ROUTES = [
|
|
|
70
74
|
pattern: /^\/api\/tasks\/([^/]+)\/runs$/,
|
|
71
75
|
handler: handleTaskRuns,
|
|
72
76
|
},
|
|
77
|
+
/**
|
|
78
|
+
* Read-only source fidelity ledger safe summary (AC-009 / AC-FIDELITY-009):
|
|
79
|
+
* 只返回计数/状态/failure code,不泄漏敏感原文。
|
|
80
|
+
*/
|
|
81
|
+
{
|
|
82
|
+
method: "GET",
|
|
83
|
+
pattern: /^\/api\/tasks\/([^/]+)\/ledger$/,
|
|
84
|
+
handler: handleTaskLedger,
|
|
85
|
+
},
|
|
73
86
|
{
|
|
74
87
|
method: "GET",
|
|
75
88
|
pattern: /^\/api\/tasks\/([^/]+)$/,
|
|
@@ -116,6 +129,16 @@ export const ROUTES = [
|
|
|
116
129
|
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/spec-evidence\/file$/,
|
|
117
130
|
handler: handleDagNodeSpecEvidenceFile,
|
|
118
131
|
},
|
|
132
|
+
// DAG run 专属只读 prewrite 结果(AC-006 / AC-BLOCK-006):唯一读路径为
|
|
133
|
+
// run-owned contracts/frontend-prewrite-result.json(resolveDagRunArtifact 四类
|
|
134
|
+
// fail-closed),不扩大 isAllowedArtifactTextPath 通用 allowlist;响应以
|
|
135
|
+
// frontendPrewriteResultV1Schema(.strict())解析,Inspector 不再做
|
|
136
|
+
// /api/artifacts 预览 → JSON.stringify → JSON.parse 双编码。
|
|
137
|
+
{
|
|
138
|
+
method: "GET",
|
|
139
|
+
pattern: /^\/api\/dag-runs\/([^/]+)\/prewrite-result$/,
|
|
140
|
+
handler: handleDagRunPrewriteResult,
|
|
141
|
+
},
|
|
119
142
|
{
|
|
120
143
|
method: "GET",
|
|
121
144
|
pattern: /^\/api\/dag-runs\/([^/]+)$/,
|
|
@@ -448,6 +471,117 @@ async function handleTaskRuns(_req, res, match, ctx) {
|
|
|
448
471
|
});
|
|
449
472
|
sendJson(res, 200, page);
|
|
450
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* GET /api/tasks/:id/ledger — 只读 ledger 安全摘要。任务存在但无 ledger 时返回
|
|
476
|
+
* `{ present: false }`(空态,不伪造计数);任务不存在返回 404;ledger 解析失败
|
|
477
|
+
* 返回带稳定 SOURCE_FIDELITY_* 错误码的 `{ present: false }`(AC-HARD-003:与
|
|
478
|
+
* parseLedgerJson 同一稳定码,不泄漏原文)。binding v2 标记 + ledgerSha256 +
|
|
479
|
+
* inputDigest 仅在真实 v2 绑定(非空 canonical requirement 集)时展示(AC-HARD-004:
|
|
480
|
+
* v1+optional 冒充 v2 不标记);inputDigest 与当前权威源输入不一致时置 stale。
|
|
481
|
+
*/
|
|
482
|
+
async function handleTaskLedger(_req, res, match, ctx) {
|
|
483
|
+
const taskId = match.params.id;
|
|
484
|
+
if (!isSafeObservabilityIdentifier(taskId)) {
|
|
485
|
+
sendJson(res, 400, { error: "Invalid task identifier" });
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const taskPaths = getTaskPaths(ctx.repoRoot, taskId);
|
|
489
|
+
try {
|
|
490
|
+
await stat(taskPaths.taskDir);
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
sendJson(res, 404, { error: "Task not found" });
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const ledgerPath = path.join(taskPaths.sourceDir, "requirement-ledger.json");
|
|
497
|
+
let raw;
|
|
498
|
+
try {
|
|
499
|
+
raw = await readFile(ledgerPath, "utf8");
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
if (error.code === "ENOENT") {
|
|
503
|
+
sendJson(res, 200, { present: false, taskId });
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
sendJson(res, 200, {
|
|
507
|
+
present: false,
|
|
508
|
+
taskId,
|
|
509
|
+
error: "ledger-unreadable",
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
let ledger;
|
|
514
|
+
try {
|
|
515
|
+
ledger = parseLedgerJson(raw);
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
// AC-HARD-003:解析失败分支使用与 parseLedgerJson 相同的稳定 SOURCE_FIDELITY_*
|
|
519
|
+
// 错误码(结构化可消费),而非裸字符串/裸 Error。
|
|
520
|
+
const code = error instanceof LedgerParseError
|
|
521
|
+
? error.code
|
|
522
|
+
: "SOURCE_FIDELITY_LEDGER_STALE";
|
|
523
|
+
sendJson(res, 200, {
|
|
524
|
+
present: false,
|
|
525
|
+
taskId,
|
|
526
|
+
error: code,
|
|
527
|
+
});
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const classification = classifyLedgerGate(ledger);
|
|
531
|
+
const summary = summarizeLedger(ledger, {
|
|
532
|
+
gateStatus: classification.ok ? "pass" : "fail",
|
|
533
|
+
failureCode: classification.ok ? null : classification.code,
|
|
534
|
+
});
|
|
535
|
+
// v2 binding 语义(AC-HARD-004):仅在真实 v2 绑定(canonical requirement 非空,
|
|
536
|
+
// 具备 requirement→fragment 映射证据)时标记 bindingSchemaVersion=2 并透传
|
|
537
|
+
// ledgerSha256/inputDigest;空 canonical 的 ledger 是退化/伪造 v2,不标记。
|
|
538
|
+
const contract = recoverLedgerInputContract(ledger);
|
|
539
|
+
const realV2Binding = contract.canonicalRequirements.length > 0;
|
|
540
|
+
let stale = false;
|
|
541
|
+
if (realV2Binding) {
|
|
542
|
+
// 当前权威源输入:ledger.sourcePaths(repo-root relative POSIX)→ 当前字节;
|
|
543
|
+
// 缺失/清空的权威源视为 stale(fail-closed,与 init-hybrid 语义一致)。
|
|
544
|
+
const currentByPath = new Map();
|
|
545
|
+
for (const sourcePath of contract.sourcePaths) {
|
|
546
|
+
try {
|
|
547
|
+
currentByPath.set(sourcePath, await readFile(path.resolve(ctx.repoRoot, sourcePath), "utf8"));
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
// 权威源缺失:保持缺失,expectedInputDigest 不一致 → stale。
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const expectedDigest = computeLedgerInputDigest({
|
|
554
|
+
sourceDocuments: contract.sourcePaths.map((sourcePath) => ({
|
|
555
|
+
path: sourcePath,
|
|
556
|
+
content: currentByPath.get(sourcePath) ?? "",
|
|
557
|
+
})),
|
|
558
|
+
canonicalRequirements: contract.canonicalRequirements,
|
|
559
|
+
});
|
|
560
|
+
stale = ledger.inputDigest !== expectedDigest;
|
|
561
|
+
}
|
|
562
|
+
const ledgerSha256 = createHash("sha256").update(raw).digest("hex");
|
|
563
|
+
sendJson(res, 200, {
|
|
564
|
+
present: true,
|
|
565
|
+
taskId,
|
|
566
|
+
...(realV2Binding
|
|
567
|
+
? {
|
|
568
|
+
bindingSchemaVersion: 2,
|
|
569
|
+
ledgerSha256,
|
|
570
|
+
inputDigest: ledger.inputDigest,
|
|
571
|
+
stale,
|
|
572
|
+
}
|
|
573
|
+
: {}),
|
|
574
|
+
fragmentCount: summary.fragmentCount,
|
|
575
|
+
canonicalRequirementCount: summary.canonicalRequirementCount,
|
|
576
|
+
contextOnlyCount: summary.contextOnlyCount,
|
|
577
|
+
duplicateCount: summary.duplicateCount,
|
|
578
|
+
unresolvedCount: summary.unresolvedCount,
|
|
579
|
+
conflictCount: summary.conflictCount,
|
|
580
|
+
gateStatus: summary.gateStatus,
|
|
581
|
+
failureCode: summary.failureCode,
|
|
582
|
+
exclusions: summary.exclusions,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
451
585
|
function ambiguousTaskBody(taskId, candidates) {
|
|
452
586
|
return {
|
|
453
587
|
error: "Task identity ambiguous",
|
|
@@ -535,6 +669,60 @@ async function handleDagRuns(_req, res, match, ctx) {
|
|
|
535
669
|
const body = listDagRunHistoryFromSnapshot(snapshot, { page, pageSize });
|
|
536
670
|
sendJson(res, 200, body);
|
|
537
671
|
}
|
|
672
|
+
/**
|
|
673
|
+
* GET /api/dag-runs/:dagRunId/prewrite-result(AC-006 / AC-BLOCK-006)
|
|
674
|
+
* 只读 run-owned contracts/frontend-prewrite-result.json:resolveDagRunArtifact 是
|
|
675
|
+
* 唯一读路径(active/paused/completed 生命周期;invalid-id / unsafe-path /
|
|
676
|
+
* not-found / ambiguous 四类 fail-closed)。响应以 frontendPrewriteResultV1Schema
|
|
677
|
+
* (.strict())解析,拒绝 envelope 之外的字段;不扩大通用 artifact allowlist。
|
|
678
|
+
*/
|
|
679
|
+
async function handleDagRunPrewriteResult(_req, res, match, ctx) {
|
|
680
|
+
const dagRunId = match.params.id;
|
|
681
|
+
const resolved = resolveDagRunArtifact(ctx.repoRoot, dagRunId, [
|
|
682
|
+
"contracts",
|
|
683
|
+
"frontend-prewrite-result.json",
|
|
684
|
+
]);
|
|
685
|
+
if (!resolved.ok) {
|
|
686
|
+
if (resolved.reason === "invalid-id") {
|
|
687
|
+
sendJson(res, 400, { error: "Invalid dag run identifier" });
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (resolved.reason === "ambiguous") {
|
|
691
|
+
sendJson(res, 409, { error: "DAG run identity ambiguous" });
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
sendJson(res, 404, { error: "Prewrite result not found" });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
let raw;
|
|
698
|
+
try {
|
|
699
|
+
raw = await readFile(resolved.result.absolutePath, "utf8");
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
sendJson(res, 404, { error: "Prewrite result not found" });
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
let parsed;
|
|
706
|
+
try {
|
|
707
|
+
parsed = JSON.parse(raw);
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
sendJson(res, 500, {
|
|
711
|
+
error: "Prewrite result is not valid JSON",
|
|
712
|
+
reason: "prewrite-result-unparseable",
|
|
713
|
+
});
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const envelope = frontendPrewriteResultV1Schema.safeParse(parsed);
|
|
717
|
+
if (!envelope.success) {
|
|
718
|
+
sendJson(res, 500, {
|
|
719
|
+
error: "Prewrite result is not a valid frontend-prewrite-result-v1 envelope",
|
|
720
|
+
reason: "prewrite-result-schema-invalid",
|
|
721
|
+
});
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
sendJson(res, 200, envelope.data);
|
|
725
|
+
}
|
|
538
726
|
export async function handleDagRunById(_req, res, match, ctx) {
|
|
539
727
|
const snapshot = await getSnapshot(ctx);
|
|
540
728
|
const dagRun = snapshot.dagRuns.find((d) => d.dagRunId === match.params.id);
|
|
@@ -116,8 +116,9 @@ export function artifactUrl(artifactPath) {
|
|
|
116
116
|
* Experimental (prompt-override rerun only): POST one allowlisted operator
|
|
117
117
|
* action through the Console mutation gate using the SCOPED inspect token
|
|
118
118
|
* (window.__INSPECT_MUTATION_TOKEN__). The server injects it into /inspect
|
|
119
|
-
* HTML
|
|
120
|
-
* authorizes dagRerunPlan / dagRerun — never general
|
|
119
|
+
* HTML by default; HARNESS_DAG_RERUN_PROMPT_OVERRIDE=0|false|off disables
|
|
120
|
+
* injection. The token only authorizes dagRerunPlan / dagRerun — never general
|
|
121
|
+
* Console mutations.
|
|
121
122
|
* Absent token → the call is not attempted.
|
|
122
123
|
*/
|
|
123
124
|
export async function postOperatorAction(action, actionParams = {}) {
|
|
@@ -180,4 +181,3 @@ export function parseArtifactPreviewResponse(text) {
|
|
|
180
181
|
truncated: parsed.truncated === true,
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
|
-
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short Chinese purpose copy for DAG node IDs shown in the inspector table.
|
|
3
|
+
* Exact IDs first; then conservative suffixes. Never echo the raw nodeId.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const NODE_PURPOSE_BY_ID = {
|
|
7
|
+
"contract-pi": "只读梳理范围、风险与写入边界,产出实现契约,不改代码。",
|
|
8
|
+
"scout-src": "只读侦察源码结构与现有模式,给后续实现找落点,不改文件。",
|
|
9
|
+
"scout-tests": "只读侦察测试与验证入口,标出可复用的检查方式,不改文件。",
|
|
10
|
+
"plan-pi": "根据契约与侦察结果制定实现计划与 writeSet,仍不改业务代码。",
|
|
11
|
+
"plan-revision-pi": "按审查意见修订实现计划,收窄写入范围,不扩大任务边界。",
|
|
12
|
+
"implement-pi": "在允许路径内按计划改代码,完成本轮主实现。",
|
|
13
|
+
"implement-core": "在允许路径内完成核心实现,不越出约定写入范围。",
|
|
14
|
+
"repair-pi": "只修验证暴露出的可修复问题,不重开需求、不扩大 writeSet。",
|
|
15
|
+
"verify-shell": "跑冻结的验证命令,用退出码证明实现是否达标。",
|
|
16
|
+
"soft-verify-shell": "先做软验证,留下证据供过程监督,不立刻判整单失败。",
|
|
17
|
+
"hard-verify-shell": "硬验证:命令必须通过,作为后续审查与收口的事实。",
|
|
18
|
+
"review-pi": "对照需求与 diff 做只读审查,给出通过或需修订。",
|
|
19
|
+
"review-verdict-recovery-pi": "补齐或纠正审查结论格式,确保门禁能读到明确 VERDICT。",
|
|
20
|
+
"review-gate-shell": "确定性门禁:只有审查结论为通过才放行收口。",
|
|
21
|
+
"decision-pi": "汇总本轮证据,给出继续、修订或停止的决策建议。",
|
|
22
|
+
"closeout-pi": "汇总完成证据与交接说明,宣告本轮交付结束。",
|
|
23
|
+
"write-set-audit-pi": "审查计划中的写入范围是否过宽或重叠,给出初审结论。",
|
|
24
|
+
"write-set-audit-format-repair-pi": "只修复写入范围审查的结论格式,不改计划内容。",
|
|
25
|
+
"write-set-format-gate-shell": "检查写入范围审查格式是否有效,未通过则不能开始改代码。",
|
|
26
|
+
"final-write-set-audit-pi": "终审写入范围:确认最终计划可授权 implement-pi。",
|
|
27
|
+
"final-write-set-audit-format-repair-pi": "只修复终审结论格式,保留原审查发现。",
|
|
28
|
+
"write-set-gate-shell": "写入授权门禁:终审通过后才允许实现节点改文件。",
|
|
29
|
+
"process-supervisor-pi": "只读检查实现过程是否偏离契约与验证,提出Bounded 修复建议。",
|
|
30
|
+
"process-gate-shell": "过程监督门禁:根据监督结论决定是否进入 repair。",
|
|
31
|
+
"authority-surface-audit-pi": "审查谁能写状态、哪些面暴露给模型,防止越权通路。",
|
|
32
|
+
"authority-surface-gate-shell": "权限面门禁:审查未通过则不能进入收口。",
|
|
33
|
+
"task-state-reconcile-shell": "核对任务状态与运行事实是否一致,避免用过期状态收口。",
|
|
34
|
+
"requirement-coverage-shell": "检查实现是否覆盖已编号需求,漏项则不能宣称完成。",
|
|
35
|
+
"static-invariant-shell": "跑静态不变量检查,确认约定结构与约束仍成立。",
|
|
36
|
+
"changeset-impact-shell": "评估本轮 diff 的影响面,标出高风险改动。",
|
|
37
|
+
"closeout-artifact-shell": "收集收口所需的确定性产物与证据路径。",
|
|
38
|
+
"final-verify-shell": "收口前最后一轮冻结验证,作为完成权威。",
|
|
39
|
+
"closeout-summary-pi": "把验证与交付事实写成简短交接摘要。",
|
|
40
|
+
|
|
41
|
+
"frontend-contract-pi": "把前端需求整理成可追踪契约:页面、状态、验收与边界。",
|
|
42
|
+
"frontend-scout-pi": "只读侦察前端结构、组件与接口用法,不改文件。",
|
|
43
|
+
"frontend-plan-pi": "选定实现与 Mock/API 策略,输出可执行的前端实现契约补丁。",
|
|
44
|
+
"frontend-design-review-pi": "审查前端计划是否可写:通过则沿用原计划,否则要求修订。",
|
|
45
|
+
"frontend-plan-revision-pi": "按设计审查意见修订前端契约,不扩大范围。",
|
|
46
|
+
"frontend-final-design-review-pi": "复核修订后的前端计划,只有通过才能授权写入。",
|
|
47
|
+
"frontend-prewrite-gate-shell": "写入前门禁:核对契约、覆盖与 Mock 策略,通过才允许改前端。",
|
|
48
|
+
"frontend-lint-baseline-shell": "记录写入前的 lint 基线,供实现后对比,不阻断写入。",
|
|
49
|
+
"frontend-implement-pi": "按已批准契约改前端代码、状态与交互,留在 writeSet 内。",
|
|
50
|
+
"frontend-verify-assess-shell": "跑静态/行为/Mock 验证并评估能否有界修复。",
|
|
51
|
+
"frontend-repair-pi": "只修评估为可修复的前端失败,不重开需求。",
|
|
52
|
+
"frontend-reverify-shell": "修复后用同一套冻结命令复验。",
|
|
53
|
+
"frontend-review-context-shell": "打包真实 diff 与有效验证证据,供审查节点使用。",
|
|
54
|
+
"frontend-review-pi": "对照契约审查前端实现与证据,给出通过或需修订。",
|
|
55
|
+
"frontend-review-gate-shell": "前端审查门禁:结论为通过才进入收口。",
|
|
56
|
+
"frontend-closeout-pi": "汇总前端交付状态(含真实联调是否仍待完成)。",
|
|
57
|
+
"frontend-mock-assess-pi": "评估项目是否具备授权的 Mock 验证能力。",
|
|
58
|
+
"frontend-mock-contract-gate-shell": "Mock 契约门禁:策略与验证命令不一致则阻断。",
|
|
59
|
+
"frontend-mock-verify-shell": "执行已授权的 Mock 验证命令。",
|
|
60
|
+
"frontend-mock-blocked-shell": "缺少授权 Mock 命令时给出确定性阻断说明。",
|
|
61
|
+
|
|
62
|
+
"preflight-frontend-browser-tool-shell": "检查浏览器自动化工具是否可用,失败则不进入用例执行。",
|
|
63
|
+
"prepare-frontend-test-package-shell": "准备前端测试包与标准场景资产。",
|
|
64
|
+
"retrieve-frontend-test-context-pi": "检索页面与验收上下文,供生成用例引用。",
|
|
65
|
+
"materialize-frontend-test-execution-shell": "物化前端测试执行清单与路径。",
|
|
66
|
+
"generate-frontend-functional-cases-pi": "按验收标准生成可执行的前端功能用例。",
|
|
67
|
+
"checklist-and-materialize-manifest-shell": "核对清单并物化用例 manifest。",
|
|
68
|
+
"execute-frontend-cases-map": "按用例清单并行跑浏览器步骤,每条用例一个子节点。",
|
|
69
|
+
"select-frontend-rerun-candidates-shell": "挑出可有界重跑的失败/缺失用例。",
|
|
70
|
+
"rerun-frontend-cases-map": "对入选用例再跑一轮浏览器步骤,以最新证据为准。",
|
|
71
|
+
"generate-backend-md-cases-map": "按模块并行写出 Markdown 用例卡片。",
|
|
72
|
+
"generate-backend-pytest-cases-map": "按模块并行写出对应的 pytest 脚本。",
|
|
73
|
+
"patch-backend-md-cases-map": "按缺口计划并行补丁各模块 Markdown 用例。",
|
|
74
|
+
"patch-backend-pytest-cases-map": "按缺口计划并行补丁各模块 pytest 脚本。",
|
|
75
|
+
"finalize-frontend-test-result-shell": "汇总用例结果,写成统一结论。",
|
|
76
|
+
"frontend-test-reports-shell": "生成前端测试 HTML/Markdown 报告。",
|
|
77
|
+
"frontend-test-result-outcome-gate-shell": "前端测试结果门禁:未达标则不能进入复盘收口。",
|
|
78
|
+
"frontend-test-retrospect-pi": "根据真实执行证据写前端测试复盘。",
|
|
79
|
+
|
|
80
|
+
"validate-backend-test-environment-shell": "检查 Python/pytest 与测试布局是否可跑,失败则不消耗模型。",
|
|
81
|
+
"backend-test-analysis-contract-shell": "固化后端测试分析契约与布局。",
|
|
82
|
+
"backend-test-environment-scout-pi": "侦察后端测试环境与入口,不改业务代码。",
|
|
83
|
+
"backend-test-execution-contract-shell": "冻结后端测试如何执行、产物写到哪里。",
|
|
84
|
+
"generate-backend-md-plan-pi": "生成中文模块索引与用例卡片计划。",
|
|
85
|
+
"materialize-backend-md-module-manifest-shell": "把 Markdown 模块清单落到磁盘。",
|
|
86
|
+
"review-and-revise-backend-md-cases-pi": "独立审查并修订 Markdown 用例。",
|
|
87
|
+
"validate-backend-md-cases-shell": "校验用例章节与 Case ID,结果只记录不阻断。",
|
|
88
|
+
"generate-backend-pytest-plan-pi": "把 Markdown 用例转成 pytest 脚本计划。",
|
|
89
|
+
"materialize-backend-pytest-module-manifest-shell": "物化 pytest 模块与脚本映射。",
|
|
90
|
+
"assess-backend-pytest-collection-shell": "试收集 pytest:能修的缺口才进入 repair。",
|
|
91
|
+
"repair-backend-pytest-collection-pi": "只补齐可归因的缺失 mapped 脚本。",
|
|
92
|
+
"effective-backend-pytest-collection-gate-shell": "收集门禁:映射不齐则不跑业务 pytest。",
|
|
93
|
+
"backend-test-traceability-gate-shell": "核对用例、脚本与验收的追踪关系。",
|
|
94
|
+
"backend-test-case-manifest-shell": "写出本轮有效用例清单事实。",
|
|
95
|
+
"execute-backend-pytest-and-html-report-shell": "单次跑业务 pytest 并生成 HTML/事实报告。",
|
|
96
|
+
"backend-test-report-and-l5-pi": "用中文汇总 advisory、执行事实与结论。",
|
|
97
|
+
"backend-test-outcome-gate-shell": "后端测试结果门禁:失败分类决定能否收口。",
|
|
98
|
+
"backend-test-report-shell": "生成后端测试报告产物。",
|
|
99
|
+
|
|
100
|
+
"knowledge-sync-collect-pi": "收集本轮验证与实现中应回写的知识。",
|
|
101
|
+
"knowledge-sync-draft-pi": "起草知识条目,不直接改权威文档。",
|
|
102
|
+
"knowledge-sync-validate-shell": "校验知识草稿结构与路径边界。",
|
|
103
|
+
"knowledge-sync-apply-pi": "把通过校验的知识写入约定文档。",
|
|
104
|
+
"knowledge-sync-pointer-pi": "更新知识索引或指针,方便下次检索。",
|
|
105
|
+
|
|
106
|
+
"kg-bootstrap-preflight-shell": "图谱开荒前检:确认仓库与工具可用。",
|
|
107
|
+
"kg-bootstrap-inventory-shell": "盘点代码与文档,列出待建图谱对象。",
|
|
108
|
+
"kg-bootstrap-propose-pi": "提出知识图谱节点与关系草案。",
|
|
109
|
+
"kg-bootstrap-validate-shell": "校验图谱草案是否可物化。",
|
|
110
|
+
"kg-bootstrap-review-pi": "审查图谱草案质量与边界。",
|
|
111
|
+
"kg-bootstrap-review-gate-shell": "图谱审查门禁:通过才允许物化。",
|
|
112
|
+
"kg-bootstrap-promote-shell": "把审查通过的图谱草案提升为有效输入。",
|
|
113
|
+
"kg-bootstrap-materialize-shell": "把图谱写入约定产物路径。",
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function normalizeNodeId(nodeId) {
|
|
117
|
+
return String(nodeId ?? "")
|
|
118
|
+
.trim()
|
|
119
|
+
.split(/[/#]/)[0];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const NODE_PURPOSE_BY_PREFIX = [
|
|
123
|
+
[/^execute-frontend-case-\d+$/, "执行单条前端用例的浏览器步骤,并留下证据。"],
|
|
124
|
+
[/^rerun-frontend-case-/, "重跑入选的前端用例,以本轮证据覆盖上一轮结果。"],
|
|
125
|
+
[/^generate-backend-md-case-/, "为单个后端模块写出 Markdown 用例卡片。"],
|
|
126
|
+
[/^generate-backend-pytest-case-/, "为单个后端模块写出对应的 pytest 脚本。"],
|
|
127
|
+
[/^patch-backend-md-case-/, "按缺口计划补丁单个模块的 Markdown 用例。"],
|
|
128
|
+
[/^patch-backend-pytest-case-/, "按缺口计划补丁单个模块的 pytest 脚本。"],
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
function describeFromPattern(nodeId) {
|
|
132
|
+
const id = nodeId.toLowerCase();
|
|
133
|
+
for (const [pattern, text] of NODE_PURPOSE_BY_PREFIX) {
|
|
134
|
+
if (pattern.test(id)) return text;
|
|
135
|
+
}
|
|
136
|
+
if (id.endsWith("-map") || id.includes("-cases-map")) {
|
|
137
|
+
return "按清单并行展开子节点并汇总结果。";
|
|
138
|
+
}
|
|
139
|
+
if (id.includes("-gate-shell")) {
|
|
140
|
+
return "确定性门禁:核验上游结论,通过才放行后续节点。";
|
|
141
|
+
}
|
|
142
|
+
if (id.endsWith("-shell") && id.includes("verify")) {
|
|
143
|
+
return "跑冻结验证命令,留下可复查的通过/失败证据。";
|
|
144
|
+
}
|
|
145
|
+
if (id.endsWith("-shell") && (id.includes("report") || id.includes("manifest"))) {
|
|
146
|
+
return "把本轮证据整理成报告或清单,不改业务代码。";
|
|
147
|
+
}
|
|
148
|
+
if (id.endsWith("-shell")) {
|
|
149
|
+
return "运行确定性命令并留下证据,不靠模型自由发挥。";
|
|
150
|
+
}
|
|
151
|
+
if (id.includes("scout") && id.endsWith("-pi")) {
|
|
152
|
+
return "只读侦察相关代码或测试,整理给下游使用,不改文件。";
|
|
153
|
+
}
|
|
154
|
+
if (id.includes("repair") && id.endsWith("-pi")) {
|
|
155
|
+
return "在原写入范围内做有界修复,不重开需求。";
|
|
156
|
+
}
|
|
157
|
+
if (id.includes("implement") && id.endsWith("-pi")) {
|
|
158
|
+
return "在允许路径内改代码,完成本节点负责的实现。";
|
|
159
|
+
}
|
|
160
|
+
if ((id.includes("review") || id.includes("audit")) && id.endsWith("-pi")) {
|
|
161
|
+
return "只读审查上游产出,给出通过或需修订的结论。";
|
|
162
|
+
}
|
|
163
|
+
if (id.includes("plan") && id.endsWith("-pi")) {
|
|
164
|
+
return "制定或修订计划/契约,仍不直接改业务代码。";
|
|
165
|
+
}
|
|
166
|
+
if (id.includes("closeout") && id.endsWith("-pi")) {
|
|
167
|
+
return "汇总证据并写出交接说明。";
|
|
168
|
+
}
|
|
169
|
+
if (id.includes("contract") && id.endsWith("-pi")) {
|
|
170
|
+
return "把需求收成可执行契约:范围、验收与边界。";
|
|
171
|
+
}
|
|
172
|
+
if (id.endsWith("-pi")) {
|
|
173
|
+
return "由模型完成该步骤的分析或写入,受 writeSet 与验证约束。";
|
|
174
|
+
}
|
|
175
|
+
return "";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function describeFromExecutor(executor) {
|
|
179
|
+
if (executor === "shell") return "运行确定性命令并留下证据。";
|
|
180
|
+
if (executor === "pi") return "由模型执行该步骤,范围受节点契约约束。";
|
|
181
|
+
if (executor === "static") return "按冻结规则直接给出结果,不调用模型。";
|
|
182
|
+
return "";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {string | null | undefined} nodeId
|
|
187
|
+
* @param {{ executor?: string | null, label?: string | null }} [node]
|
|
188
|
+
*/
|
|
189
|
+
export function describeDagNodePurpose(nodeId, node = {}) {
|
|
190
|
+
const id = normalizeNodeId(nodeId);
|
|
191
|
+
if (!id) return "";
|
|
192
|
+
const exact = NODE_PURPOSE_BY_ID[id];
|
|
193
|
+
if (exact) return exact;
|
|
194
|
+
const patterned = describeFromPattern(id);
|
|
195
|
+
if (patterned) return patterned;
|
|
196
|
+
const label = typeof node.label === "string" ? node.label.trim() : "";
|
|
197
|
+
if (label && label !== id && label.toLowerCase() !== id.toLowerCase()) {
|
|
198
|
+
return label;
|
|
199
|
+
}
|
|
200
|
+
return describeFromExecutor(node.executor) || "";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export { NODE_PURPOSE_BY_ID };
|
|
@@ -368,3 +368,40 @@ export function prettyJsonBlock(text) {
|
|
|
368
368
|
return null;
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Source fidelity ledger 安全摘要渲染(AC-009/AC-010/AC-HARD-004):计数/状态/
|
|
374
|
+
* failure code + v2 binding 语义(binding=v2 + ledger sha256/inputDigest,仅真实
|
|
375
|
+
* DagSourceBindingV2 时展示;inputDigest ≠ 当前 expectedInputDigest 时标记 stale)+
|
|
376
|
+
* 有界排除理由(fragmentId + 截断 reason),绝不渲染未截断原文。输入即安全摘要对象。
|
|
377
|
+
*/
|
|
378
|
+
export function formatLedgerSummary(summary) {
|
|
379
|
+
if (!summary || summary.present !== true) {
|
|
380
|
+
return "无 source fidelity ledger(任务无引用或 ledger 缺失,不伪造计数)";
|
|
381
|
+
}
|
|
382
|
+
const lines = [
|
|
383
|
+
`fragments=${summary.fragmentCount ?? 0}`,
|
|
384
|
+
`canonical requirements=${summary.canonicalRequirementCount ?? 0}`,
|
|
385
|
+
`context-only=${summary.contextOnlyCount ?? 0}`,
|
|
386
|
+
`duplicate=${summary.duplicateCount ?? 0}`,
|
|
387
|
+
`unresolved=${summary.unresolvedCount ?? 0}`,
|
|
388
|
+
`conflict=${summary.conflictCount ?? 0}`,
|
|
389
|
+
`gate=${summary.gateStatus ?? "unknown"}`,
|
|
390
|
+
];
|
|
391
|
+
if (summary.bindingSchemaVersion === 2) {
|
|
392
|
+
lines.push(
|
|
393
|
+
`binding=v2(受管 ledger 绑定)${summary.stale ? " · STALE(输入漂移)" : ""}`,
|
|
394
|
+
);
|
|
395
|
+
if (summary.ledgerSha256) lines.push(`ledgerSha256=${summary.ledgerSha256}`);
|
|
396
|
+
if (summary.inputDigest) lines.push(`inputDigest=${summary.inputDigest}`);
|
|
397
|
+
}
|
|
398
|
+
if (summary.failureCode) lines.push(`failureCode=${summary.failureCode}`);
|
|
399
|
+
const exclusions = Array.isArray(summary.exclusions) ? summary.exclusions : [];
|
|
400
|
+
if (exclusions.length > 0) {
|
|
401
|
+
lines.push("排除理由(有界摘要):");
|
|
402
|
+
for (const entry of exclusions.slice(0, 20)) {
|
|
403
|
+
lines.push(`- ${entry.fragmentId}: ${truncateText(entry.reason ?? "", 120)}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return lines.join("\n");
|
|
407
|
+
}
|
|
@@ -352,6 +352,11 @@
|
|
|
352
352
|
class="panel panel-table"
|
|
353
353
|
aria-label="运行历史"
|
|
354
354
|
></section>
|
|
355
|
+
<section
|
|
356
|
+
id="task-ledger"
|
|
357
|
+
class="panel panel-table"
|
|
358
|
+
aria-label="Source fidelity ledger"
|
|
359
|
+
></section>
|
|
355
360
|
</section>
|
|
356
361
|
|
|
357
362
|
<section data-view="feature" id="view-feature" class="view" hidden>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type PromptRestartFailedCandidate = {
|
|
2
|
+
attempt?: number;
|
|
3
|
+
failureCategory?: string;
|
|
4
|
+
prompt?: string;
|
|
5
|
+
truncated?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type PromptRestartCandidateOption = {
|
|
9
|
+
key: string;
|
|
10
|
+
label: string;
|
|
11
|
+
prompt: string;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function buildPromptRestartCandidateOptions(
|
|
16
|
+
frozenPrompt: string | null | undefined,
|
|
17
|
+
failedCandidates: PromptRestartFailedCandidate[] | null | undefined,
|
|
18
|
+
): PromptRestartCandidateOption[];
|