@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.
Files changed (158) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +17 -5
  3. package/README.md +2 -2
  4. package/dist/application/task-lifecycle/advance.js +12 -0
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/cli/update/runtime-activity.js +1 -29
  7. package/dist/commands/dag-rerun.js +1 -1
  8. package/dist/commands/init.js +26 -0
  9. package/dist/executors/dag-pi-executor.js +326 -8
  10. package/dist/executors/pi-read-budget-policy.js +166 -0
  11. package/dist/executors/shell-executor.js +26 -30
  12. package/dist/shared/dag-prompt-override.js +4 -5
  13. package/dist/shared/update/npm-client.js +33 -11
  14. package/dist/task/contract/apply.js +11 -1
  15. package/dist/task/contract/constants.js +2 -0
  16. package/dist/task/contract/hash.js +3 -0
  17. package/dist/task/contract/observe.js +25 -4
  18. package/dist/task/contract/paths.js +2 -1
  19. package/dist/task/contract/project.js +15 -1
  20. package/dist/task/contract/schema.js +2 -0
  21. package/dist/task/contract/transaction.js +12 -0
  22. package/dist/task/frontend-project-capability.js +123 -4
  23. package/dist/task/source-prepare/completeness.js +188 -0
  24. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  25. package/dist/task/source-prepare/index.js +3 -0
  26. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  27. package/dist/task/source-prepare/ledger-review.js +214 -0
  28. package/dist/task/source-prepare/ledger.js +545 -0
  29. package/dist/task/source-prepare/prepare.js +262 -1
  30. package/dist/task/source-prepare/semantic-intake.js +25 -5
  31. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  32. package/dist/task/source-prepare/types.js +22 -0
  33. package/dist/worker/cli.js +12 -26
  34. package/dist/worker/console/console-handoff.js +82 -0
  35. package/dist/worker/console/console-update-and-init.js +115 -0
  36. package/dist/worker/console/console-update-runtime.js +84 -0
  37. package/dist/worker/console/draft-store.js +49 -0
  38. package/dist/worker/console/operation-store.js +1 -1
  39. package/dist/worker/console/operator-actions.js +129 -4
  40. package/dist/worker/console/operator-surface-health.js +1 -0
  41. package/dist/worker/console/prd-intake-bridge.js +13 -0
  42. package/dist/worker/console/routes.js +3 -0
  43. package/dist/worker/console/server.js +73 -5
  44. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-bPer1waP.js → abnfDiagram-N423BO3Z-D1-Z-BBa.js} +1 -1
  45. package/dist/worker/console/static/assets/{arc-BLdkwucW.js → arc-q8qAYm5m.js} +1 -1
  46. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bw4wo4I1.js → architectureDiagram-T3A2C74G-CAu4_lve.js} +1 -1
  47. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DUUkc0gr.js → blockDiagram-VBNYF7ZC-D5GhQvHm.js} +1 -1
  48. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BC3O0nob.js → c4Diagram-5PPSVZJV-ClQjeLkO.js} +1 -1
  49. package/dist/worker/console/static/assets/channel-ClAuhSUL.js +1 -0
  50. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Dd4g2nti.js → chunk-2GRJ4B5K-DLqWlFbo.js} +1 -1
  51. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-s2EDm7-n.js → chunk-2Q5K7J3B-DkRT_usL.js} +1 -1
  52. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-GqtHM2yQ.js → chunk-5RXB4S5H-Cqp7Chi3.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BzRFrN8j.js → chunk-5VM5RSS4-CedEdFB9.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bw9nFjCJ.js → chunk-6Q2QTUOP-BHM8vP5x.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B4n-03Qm.js → chunk-GF5L2VYU-D7EFr377.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Dt75ZdBD.js → chunk-JWPE2WC7-DR38mTWV.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-B_afMhHB.js → chunk-KBJHAD2P-BFv2LsR3.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Bd8Rqqg9.js → chunk-RYQCIY6F-BAvpJS5z.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-9boOMji4.js → chunk-XXDRQBXY-eKI9TO72.js} +1 -1
  60. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-cBgeCpVU.js +1 -0
  61. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-cBgeCpVU.js +1 -0
  62. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BtCycfCc.js → cose-bilkent-JH36ORCC-BMJXpgo4.js} +1 -1
  63. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-eTOts9L1.js → cynefin-VYW2F7L2-CKJ2ofZx.js} +1 -1
  64. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Cibd2mjg.js → cynefinDiagram-MW4NZA55-CsKnsazm.js} +1 -1
  65. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CDjDZtDV.js → dagre-VZM6K2ZE-DVQ46vIw.js} +1 -1
  66. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Btexn-JJ.js → diagram-7IWD3JNH-D_RuonYS.js} +1 -1
  67. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cyl8sdpM.js → diagram-B4RE2ZJO-D2Mt0nMu.js} +1 -1
  68. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Ev-83E1t.js → diagram-LBJQPF4R-DjBDuye6.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-41LqE0Mc.js → diagram-Q27KOJAE-8VZkqpE_.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BC39T5R3.js → diagram-UB23O5K3-D0izYxia.js} +1 -1
  71. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DzcNRuri.js → ebnfDiagram-BXEA7PRR-CncG2I95.js} +1 -1
  72. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-B5Frvvnz.js → erDiagram-JOGREHBK-DiBUUXR8.js} +1 -1
  73. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BapxROIM.js → flowDiagram-UKHOOZJN-Dtc0fArb.js} +1 -1
  74. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cd4d4wQ0.js → ganttDiagram-PKOTCBZU-BPNFP5OZ.js} +1 -1
  75. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CrL6buFW.js → gitGraphDiagram-DS77QQ5N-CsgpxEEd.js} +1 -1
  76. package/dist/worker/console/static/assets/{index-DBh_x_Ek.js → index-Bai8Z25q.js} +81 -81
  77. package/dist/worker/console/static/assets/index-CH0Tr5hY.css +1 -0
  78. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BrSiNxyd.js → infoDiagram-6WML65LV-kHGxkJUS.js} +1 -1
  79. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-gvlvY-n9.js → ishikawaDiagram-WSZJBQD7-CbLe9wrr.js} +1 -1
  80. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-YaPzRQ.js → journeyDiagram-NVQOT4AX-BOkwxAcP.js} +1 -1
  81. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BsmDv9E_.js → kanban-definition-27J2QSJJ-BHYQ2rDu.js} +1 -1
  82. package/dist/worker/console/static/assets/{linear-UWaoDN3h.js → linear-CHT4ujob.js} +1 -1
  83. package/dist/worker/console/static/assets/{mermaid.core-BlvpGVG8.js → mermaid.core-yL8sfqnY.js} +5 -5
  84. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CUCIP5CP.js → mindmap-definition-FAOFIHXS-DZx1coV-.js} +1 -1
  85. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CB1aQUAK.js → pegDiagram-VL7TDLO6-DStWRCI8.js} +1 -1
  86. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BwehHxhf.js → pieDiagram-7S7Q4E2Y-DSKZkwzK.js} +1 -1
  87. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CFz30APB.js → quadrantDiagram-CIZ2JOQS-CmoEGOuW.js} +1 -1
  88. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwNmw1i6.js → railroadDiagram-AXF67PYL-BLqzhKyq.js} +1 -1
  89. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-jg-tPXlI.js → requirementDiagram-LRYGKXZP-gbHeUabM.js} +1 -1
  90. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BlyReDKl.js → sankeyDiagram-W5VNT64P-CaKkBCKO.js} +1 -1
  91. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DBUzW1f3.js → sequenceDiagram-SI44F4Z6-PUbi3Upu.js} +1 -1
  92. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BYcI2RAr.js → sizeCapture-X5ZJPWSS-dm7S9Qfe.js} +1 -1
  93. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-D7AiyJdo.js → stateDiagram-OKZ733FA-C7WSzv4p.js} +1 -1
  94. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-sQDv5AP3.js +1 -0
  95. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BHGt7Kkv.js → swimlanes-SLNWSIFB-CBKCiQwd.js} +2 -2
  96. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BXwsmcPs.js +8 -0
  97. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-OPjooWcX.js → timeline-definition-Z64GVDOM-CvnUlylk.js} +1 -1
  98. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-ggsWDvrO.js → vennDiagram-T6HMQDX7-xM0StY1O.js} +1 -1
  99. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DjNe-BjZ.js → wardleyDiagram-T6FBY63Y-CWtJMagy.js} +1 -1
  100. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Byp63Hei.js → xychartDiagram-ELKLHX3M-BO_-MeK8.js} +1 -1
  101. package/dist/worker/console/static/index.html +2 -2
  102. package/dist/worker/console/static-src/app/console-types.js +40 -1
  103. package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
  104. package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
  105. package/dist/worker/observability/init-runtime-activity.js +26 -0
  106. package/dist/worker/observe/node-input.js +88 -2
  107. package/dist/worker/observe/routes.js +188 -0
  108. package/dist/worker/observe/static/api.js +3 -3
  109. package/dist/worker/observe/static/format.js +37 -0
  110. package/dist/worker/observe/static/index.html +5 -0
  111. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  112. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  113. package/dist/worker/observe/static/state.js +2 -2
  114. package/dist/worker/observe/static/styles.css +331 -35
  115. package/dist/worker/observe/static/views/dag-inspector.js +509 -78
  116. package/dist/worker/observe/static/views/task.js +55 -0
  117. package/dist/workflows/dag/frontend-design-policy.js +19 -9
  118. package/dist/workflows/dag/frontend-implementation-contract.js +109 -49
  119. package/dist/workflows/dag/frontend-plan-render.js +27 -0
  120. package/dist/workflows/dag/frontend-prewrite-gate.js +45 -1
  121. package/dist/workflows/dag/frontend-recovery-controller.js +1 -0
  122. package/dist/workflows/dag/frontend-review-context.js +102 -15
  123. package/dist/workflows/dag/frontend-shadow-dual-write.js +36 -2
  124. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  125. package/dist/workflows/dag/frontend-worktree-diff.js +154 -0
  126. package/dist/workflows/dag/frontend-writer-admission.js +12 -0
  127. package/dist/workflows/dag/init-hybrid.js +293 -65
  128. package/dist/workflows/dag/interrupt-request.js +7 -0
  129. package/dist/workflows/dag/node-execution.js +111 -1
  130. package/dist/workflows/dag/prompt.js +34 -1
  131. package/dist/workflows/dag/recovery-lease.js +80 -0
  132. package/dist/workflows/dag/rerun-run.js +23 -0
  133. package/dist/workflows/dag/rerun-task.js +29 -0
  134. package/dist/workflows/dag/retry-policy.js +26 -3
  135. package/dist/workflows/dag/runner.js +38 -8
  136. package/dist/workflows/dag/types.js +51 -11
  137. package/docs/README.md +1 -0
  138. package/docs/architecture/evolution.md +45 -0
  139. package/docs/init-surface.manifest.json +4 -0
  140. package/docs/templates/README.md +2 -1
  141. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  142. package/docs/templates/spec-registry.schema.json +45 -0
  143. package/harness.json +3 -3
  144. package/package.json +1 -1
  145. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  146. package/skills/frontend-design-review/SKILL.md +20 -41
  147. package/skills/frontend-implementation/SKILL.md +2 -3
  148. package/skills/frontend-implementation/references/design-spec.md +4 -3
  149. package/skills/frontend-implementation/references/node-contracts.md +4 -3
  150. package/skills/frontend-review/SKILL.md +22 -14
  151. package/skills/frontend-review/references/review-findings.md +6 -7
  152. package/skills/loop-agent/references/hybrid-dag.md +7 -0
  153. package/dist/worker/console/static/assets/channel-AbFtVnFm.js +0 -1
  154. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-n1yf2Jcv.js +0 -1
  155. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-n1yf2Jcv.js +0 -1
  156. package/dist/worker/console/static/assets/index-DwOGauxU.css +0 -1
  157. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBSH5c8m.js +0 -1
  158. 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 only when HARNESS_DAG_RERUN_PROMPT_OVERRIDE is enabled, and it only
120
- * authorizes dagRerunPlan / dagRerun — never general Console mutations.
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: true,
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,