@tea-agent/loop-agent 0.39.0-beta.8 → 0.39.0-beta.9
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 +17 -5
- 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/commands/init.js +26 -0
- package/dist/executors/dag-pi-executor.js +326 -8
- package/dist/executors/pi-read-budget-policy.js +166 -0
- package/dist/executors/shell-executor.js +26 -30
- 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/frontend-project-capability.js +123 -4
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -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 +25 -5
- 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-bPer1waP.js → abnfDiagram-N423BO3Z-D1-Z-BBa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BLdkwucW.js → arc-q8qAYm5m.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bw4wo4I1.js → architectureDiagram-T3A2C74G-CAu4_lve.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DUUkc0gr.js → blockDiagram-VBNYF7ZC-D5GhQvHm.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BC3O0nob.js → c4Diagram-5PPSVZJV-ClQjeLkO.js} +1 -1
- package/dist/worker/console/static/assets/channel-ClAuhSUL.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Dd4g2nti.js → chunk-2GRJ4B5K-DLqWlFbo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-s2EDm7-n.js → chunk-2Q5K7J3B-DkRT_usL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-GqtHM2yQ.js → chunk-5RXB4S5H-Cqp7Chi3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BzRFrN8j.js → chunk-5VM5RSS4-CedEdFB9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bw9nFjCJ.js → chunk-6Q2QTUOP-BHM8vP5x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B4n-03Qm.js → chunk-GF5L2VYU-D7EFr377.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Dt75ZdBD.js → chunk-JWPE2WC7-DR38mTWV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-B_afMhHB.js → chunk-KBJHAD2P-BFv2LsR3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Bd8Rqqg9.js → chunk-RYQCIY6F-BAvpJS5z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-9boOMji4.js → chunk-XXDRQBXY-eKI9TO72.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BtCycfCc.js → cose-bilkent-JH36ORCC-BMJXpgo4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-eTOts9L1.js → cynefin-VYW2F7L2-CKJ2ofZx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Cibd2mjg.js → cynefinDiagram-MW4NZA55-CsKnsazm.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CDjDZtDV.js → dagre-VZM6K2ZE-DVQ46vIw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Btexn-JJ.js → diagram-7IWD3JNH-D_RuonYS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cyl8sdpM.js → diagram-B4RE2ZJO-D2Mt0nMu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Ev-83E1t.js → diagram-LBJQPF4R-DjBDuye6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-41LqE0Mc.js → diagram-Q27KOJAE-8VZkqpE_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BC39T5R3.js → diagram-UB23O5K3-D0izYxia.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DzcNRuri.js → ebnfDiagram-BXEA7PRR-CncG2I95.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-B5Frvvnz.js → erDiagram-JOGREHBK-DiBUUXR8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BapxROIM.js → flowDiagram-UKHOOZJN-Dtc0fArb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cd4d4wQ0.js → ganttDiagram-PKOTCBZU-BPNFP5OZ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CrL6buFW.js → gitGraphDiagram-DS77QQ5N-CsgpxEEd.js} +1 -1
- package/dist/worker/console/static/assets/{index-DBh_x_Ek.js → index-Bai8Z25q.js} +81 -81
- package/dist/worker/console/static/assets/index-CH0Tr5hY.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BrSiNxyd.js → infoDiagram-6WML65LV-kHGxkJUS.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-gvlvY-n9.js → ishikawaDiagram-WSZJBQD7-CbLe9wrr.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-YaPzRQ.js → journeyDiagram-NVQOT4AX-BOkwxAcP.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BsmDv9E_.js → kanban-definition-27J2QSJJ-BHYQ2rDu.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UWaoDN3h.js → linear-CHT4ujob.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BlvpGVG8.js → mermaid.core-yL8sfqnY.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CUCIP5CP.js → mindmap-definition-FAOFIHXS-DZx1coV-.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CB1aQUAK.js → pegDiagram-VL7TDLO6-DStWRCI8.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BwehHxhf.js → pieDiagram-7S7Q4E2Y-DSKZkwzK.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CFz30APB.js → quadrantDiagram-CIZ2JOQS-CmoEGOuW.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwNmw1i6.js → railroadDiagram-AXF67PYL-BLqzhKyq.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-jg-tPXlI.js → requirementDiagram-LRYGKXZP-gbHeUabM.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BlyReDKl.js → sankeyDiagram-W5VNT64P-CaKkBCKO.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DBUzW1f3.js → sequenceDiagram-SI44F4Z6-PUbi3Upu.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BYcI2RAr.js → sizeCapture-X5ZJPWSS-dm7S9Qfe.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-D7AiyJdo.js → stateDiagram-OKZ733FA-C7WSzv4p.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-sQDv5AP3.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BHGt7Kkv.js → swimlanes-SLNWSIFB-CBKCiQwd.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BXwsmcPs.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-OPjooWcX.js → timeline-definition-Z64GVDOM-CvnUlylk.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-ggsWDvrO.js → vennDiagram-T6HMQDX7-xM0StY1O.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DjNe-BjZ.js → wardleyDiagram-T6FBY63Y-CWtJMagy.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Byp63Hei.js → xychartDiagram-ELKLHX3M-BO_-MeK8.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/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 +331 -35
- package/dist/worker/observe/static/views/dag-inspector.js +509 -78
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-design-policy.js +19 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +109 -49
- package/dist/workflows/dag/frontend-plan-render.js +27 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +45 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +1 -0
- package/dist/workflows/dag/frontend-review-context.js +102 -15
- package/dist/workflows/dag/frontend-shadow-dual-write.js +36 -2
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +154 -0
- package/dist/workflows/dag/frontend-writer-admission.js +12 -0
- package/dist/workflows/dag/init-hybrid.js +293 -65
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +111 -1
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +26 -3
- package/dist/workflows/dag/runner.js +38 -8
- package/dist/workflows/dag/types.js +51 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +4 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +2 -3
- package/skills/frontend-implementation/references/design-spec.md +4 -3
- package/skills/frontend-implementation/references/node-contracts.md +4 -3
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-AbFtVnFm.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/index-DwOGauxU.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBSH5c8m.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D2F7-U4H.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
|
-
|
|
@@ -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[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function buildPromptRestartCandidateOptions(
|
|
2
|
+
frozenPrompt,
|
|
3
|
+
failedCandidates,
|
|
4
|
+
) {
|
|
5
|
+
const options = [
|
|
6
|
+
{
|
|
7
|
+
key: "frozen",
|
|
8
|
+
label: "冻结原始提示词",
|
|
9
|
+
prompt: String(frozenPrompt ?? ""),
|
|
10
|
+
truncated: false,
|
|
11
|
+
},
|
|
12
|
+
];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
for (const candidate of [...(failedCandidates ?? [])].sort(
|
|
15
|
+
(a, b) => Number(a?.attempt ?? 0) - Number(b?.attempt ?? 0),
|
|
16
|
+
)) {
|
|
17
|
+
const attempt = Number(candidate?.attempt);
|
|
18
|
+
if (!Number.isSafeInteger(attempt) || attempt < 1 || seen.has(attempt)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (typeof candidate?.prompt !== "string") continue;
|
|
22
|
+
seen.add(attempt);
|
|
23
|
+
const failureCategory =
|
|
24
|
+
typeof candidate.failureCategory === "string" &&
|
|
25
|
+
candidate.failureCategory.trim()
|
|
26
|
+
? candidate.failureCategory.trim()
|
|
27
|
+
: "unknown";
|
|
28
|
+
options.push({
|
|
29
|
+
key: `attempt-${attempt}`,
|
|
30
|
+
label: `第 ${attempt} 次失败 · ${failureCategory}`,
|
|
31
|
+
prompt: candidate.prompt,
|
|
32
|
+
truncated: candidate.truncated === true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return options;
|
|
36
|
+
}
|
|
@@ -82,8 +82,8 @@ export const uiState = {
|
|
|
82
82
|
dagNodeExecutionOutputLoading: false,
|
|
83
83
|
dagNodeExecutionOutputError: null,
|
|
84
84
|
dagNodeExecutionOutputKey: null,
|
|
85
|
-
/** When true,「执行过程」展开 message_start/end
|
|
86
|
-
sessionTimelineShowProtocol:
|
|
85
|
+
/** When true,「执行过程」展开 message_start/end 原始协议事件。默认隐藏,用户可点选展开。 */
|
|
86
|
+
sessionTimelineShowProtocol: false,
|
|
87
87
|
/** Open <details> keys restored across timeline re-renders. */
|
|
88
88
|
sessionTimelineOpenDetails: null,
|
|
89
89
|
lastSnapshot: null,
|