@tea-agent/loop-agent 0.24.11-beta.0 → 0.25.1

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 (97) hide show
  1. package/CHANGELOG.md +53 -4
  2. package/README.md +4 -4
  3. package/dist/application/dag/generate-task-dag.js +25 -8
  4. package/dist/commands/dag-init-hybrid.js +3 -2
  5. package/dist/commands/delegate.js +11 -11
  6. package/dist/commands/init.js +29 -14
  7. package/dist/commands/instructions.js +1 -2
  8. package/dist/executors/dag-pi-executor.js +74 -13
  9. package/dist/executors/pi-executor.js +25 -7
  10. package/dist/executors/pi-prompt-transport.js +198 -0
  11. package/dist/executors/pi-sdk-executor.js +1 -1
  12. package/dist/executors/process-tree.js +33 -0
  13. package/dist/executors/shell-executor.js +362 -24
  14. package/dist/executors/shell-verification.js +4 -2
  15. package/dist/infrastructure/harness/task-store.js +31 -0
  16. package/dist/shared/operator/capabilities.js +6 -0
  17. package/dist/task/read-model.js +3 -7
  18. package/dist/task/runtime.js +12 -3
  19. package/dist/verification/maven/cache.js +142 -0
  20. package/dist/verification/maven/index.js +120 -0
  21. package/dist/verification/maven/plan-commands.js +421 -0
  22. package/dist/verification/maven/pom-static.js +136 -0
  23. package/dist/verification/maven/scope-resolve.js +153 -0
  24. package/dist/verification/maven/stale.js +130 -0
  25. package/dist/verification/maven/types.js +23 -0
  26. package/dist/verification/maven/workspace-graph.js +322 -0
  27. package/dist/worker/cli.js +236 -21
  28. package/dist/worker/delivery/final-verification.js +12 -0
  29. package/dist/worker/delivery/git-transaction.js +32 -7
  30. package/dist/worker/delivery/package.js +9 -5
  31. package/dist/worker/delivery/verification-bundle.js +26 -5
  32. package/dist/worker/feature/advance.js +301 -0
  33. package/dist/worker/feature/doctor.js +223 -0
  34. package/dist/worker/feature/next-action.js +11 -3
  35. package/dist/worker/feature/scaffold.js +798 -0
  36. package/dist/worker/observe/dag-run-artifacts.js +90 -0
  37. package/dist/worker/observe/node-input.js +444 -0
  38. package/dist/worker/observe/routes.js +17 -0
  39. package/dist/worker/observe/static/api.js +9 -0
  40. package/dist/worker/observe/static/constants.js +9 -0
  41. package/dist/worker/observe/static/state.js +14 -0
  42. package/dist/worker/observe/static/styles.css +74 -0
  43. package/dist/worker/observe/static/views/dag-inspector.js +371 -15
  44. package/dist/worker/outcomes/projector.js +5 -1
  45. package/dist/worker/run-task/run-task.js +18 -2
  46. package/dist/worker/runner/run-ready.js +41 -1
  47. package/dist/workflows/dag/backend-test-markdown-workflow.js +300 -9
  48. package/dist/workflows/dag/backend-test-result-contract.js +103 -0
  49. package/dist/workflows/dag/failure-category.js +5 -1
  50. package/dist/workflows/dag/frontend-implementation-contract.js +4 -0
  51. package/dist/workflows/dag/frontend-prewrite-gate.js +0 -17
  52. package/dist/workflows/dag/frontend-test-result-contract.js +106 -41
  53. package/dist/workflows/dag/init-hybrid.js +224 -39
  54. package/dist/workflows/dag/node-execution.js +3 -2
  55. package/dist/workflows/dag/reconcile-run.js +24 -0
  56. package/dist/workflows/dag/types.js +57 -0
  57. package/dist/workflows/dag/validate.js +19 -2
  58. package/dist/workflows/loop/actions/dag-action.js +2 -1
  59. package/docs/architecture/dag-execution.md +5 -1
  60. package/docs/architecture/runtime-boundaries.md +11 -1
  61. package/docs/architecture/worker-and-feature.md +2 -0
  62. package/docs/init-surface.manifest.json +3 -0
  63. package/docs/templates/backend-test-dag.json +3 -3
  64. package/docs/templates/product-line/README.md +17 -1
  65. package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
  66. package/docs/templates/product-line/scaffold-samples/README.md +36 -0
  67. package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +13 -0
  68. package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
  69. package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +3 -0
  70. package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
  71. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
  72. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
  73. package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +15 -0
  74. package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
  75. package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +3 -0
  76. package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
  77. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
  78. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
  79. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
  80. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
  81. package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +13 -0
  82. package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
  83. package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +3 -0
  84. package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
  85. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
  86. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
  87. package/examples/l5-report-coms-process-definition.html +322 -0
  88. package/package.json +1 -1
  89. package/skills/agent-worker/references/agent-worker-operator.md +84 -0
  90. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  91. package/skills/loop-agent/SKILL.md +14 -16
  92. package/skills/loop-agent/references/command-reference.md +33 -12
  93. package/skills/loop-agent/references/harness-policy.md +28 -6
  94. package/skills/loop-agent/references/hybrid-dag.md +8 -7
  95. package/skills/loop-agent/references/post-implementation-and-patterns.md +19 -9
  96. package/skills/loop-agent/references/source-and-plan-practice.md +161 -0
  97. package/skills/loop-agent/references/task-workflow.md +13 -5
@@ -2010,6 +2010,80 @@ body.is-resizing-dag-graph {
2010
2010
  .spec-evidence-section { padding-bottom: var(--space-4); border-bottom: 1px solid var(--hairline); }
2011
2011
  .spec-evidence-section + .spec-evidence-section { margin-top: var(--space-4); }
2012
2012
  .spec-evidence-section h4 { margin: 0 0 var(--space-2); color: var(--ink); font-size: 12px; font-weight: 700; }
2013
+
2014
+ .node-input-root { display: flex; flex-direction: column; gap: var(--space-3); }
2015
+ .node-input-semantics {
2016
+ margin: 0;
2017
+ font-size: 12px;
2018
+ font-weight: 600;
2019
+ color: var(--muted);
2020
+ }
2021
+ .node-input-section {
2022
+ padding-bottom: var(--space-3);
2023
+ border-bottom: 1px solid var(--hairline);
2024
+ }
2025
+ .node-input-section h4 {
2026
+ margin: 0 0 var(--space-2);
2027
+ color: var(--ink);
2028
+ font-size: 12px;
2029
+ font-weight: 700;
2030
+ }
2031
+ .node-input-section h5 {
2032
+ margin: var(--space-2) 0;
2033
+ font-size: 12px;
2034
+ font-weight: 600;
2035
+ }
2036
+ .node-input-summary {
2037
+ display: grid;
2038
+ grid-template-columns: max-content 1fr;
2039
+ gap: 4px 12px;
2040
+ margin: 0;
2041
+ font-size: 12px;
2042
+ }
2043
+ .node-input-summary dt {
2044
+ margin: 0;
2045
+ color: var(--muted);
2046
+ font-weight: 600;
2047
+ }
2048
+ .node-input-summary dd {
2049
+ margin: 0;
2050
+ color: var(--ink);
2051
+ word-break: break-word;
2052
+ }
2053
+ .node-input-inherited {
2054
+ display: inline-block;
2055
+ margin-left: 4px;
2056
+ padding: 0 6px;
2057
+ border-radius: 999px;
2058
+ background: var(--surface-2, #f3f1ec);
2059
+ color: var(--muted);
2060
+ font-size: 11px;
2061
+ font-weight: 600;
2062
+ }
2063
+ .node-input-code-list,
2064
+ .node-input-command-list {
2065
+ margin: var(--space-2) 0 0;
2066
+ padding-left: 1.2rem;
2067
+ }
2068
+ .node-input-command-list pre {
2069
+ margin: 4px 0;
2070
+ padding: 8px;
2071
+ overflow: auto;
2072
+ background: var(--surface-2, #f3f1ec);
2073
+ border-radius: 6px;
2074
+ font-size: 12px;
2075
+ }
2076
+ .node-input-truncated,
2077
+ .node-input-warning {
2078
+ margin: var(--space-2) 0 0;
2079
+ font-size: 12px;
2080
+ color: var(--muted);
2081
+ }
2082
+ .node-input-error {
2083
+ margin: 0;
2084
+ color: var(--danger, #b42318);
2085
+ font-size: 13px;
2086
+ }
2013
2087
  .spec-evidence-summary { margin: var(--space-2) 0 0; color: var(--body); font-size: 12px; line-height: 1.6; }
2014
2088
  .spec-evidence-list { display: grid; gap: 6px; margin: var(--space-2) 0 0; padding: 0; list-style: none; }
2015
2089
  .spec-evidence-list li { display: flex; align-items: flex-start; gap: 6px; color: var(--body); font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
@@ -10,12 +10,13 @@ import {
10
10
  UI_TEXT,
11
11
  } from "../constants.js";
12
12
  import { clearNode, el } from "../dom.js";
13
- import { fetchJson, fetchSpecEvidenceFile } from "../api.js";
13
+ import { fetchJson, fetchSpecEvidenceFile, fetchDagNodeInput } from "../api.js";
14
14
  import { badge } from "../format.js";
15
15
  import { renderMarkdown } from "../markdown-render.js";
16
16
  import {
17
17
  uiState,
18
18
  makeSessionEventIdentity,
19
+ makeDagNodeInputIdentity,
19
20
  openSpecEvidenceDetail,
20
21
  closeSpecEvidenceDetail,
21
22
  updateDagTimelineViewportState,
@@ -50,9 +51,13 @@ export function selectDagNode(dagRunId, nodeId) {
50
51
  // into node B's spec-evidence panel (AC-007 regression guard).
51
52
  uiState.specEvidenceDetail = null;
52
53
  uiState.specEvidenceListEvidence = null;
54
+ uiState.dagNodeInputData = null;
55
+ uiState.dagNodeInputError = null;
56
+ uiState.dagNodeInputLoading = false;
53
57
  // Bump the session-event identity token so any in-flight response for the
54
58
  // previous node is rejected before it can mutate the current node cache.
55
59
  uiState.sessionEventIdentity = makeSessionEventIdentity(dagRunId, nodeId);
60
+ uiState.dagNodeInputIdentity = makeDagNodeInputIdentity(dagRunId, nodeId);
56
61
  }
57
62
  void import("./dag.js").then((m) => m.renderDagDetail(dagRunId, false));
58
63
  }
@@ -530,16 +535,337 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
530
535
  content.appendChild(wrapper);
531
536
  }
532
537
 
538
+ function sourceLabel(source) {
539
+ if (source === "defaults") return "继承默认值";
540
+ if (source === "schema-default") return "schema 默认";
541
+ if (source === "merged") return "合并";
542
+ return null;
543
+ }
544
+
545
+ function appendMetaRow(dl, label, value, source) {
546
+ if (value == null || value === "") return;
547
+ const dt = el("dt", null, label);
548
+ const dd = el("dd", null, String(value));
549
+ const inherited = sourceLabel(source);
550
+ if (inherited) {
551
+ dd.appendChild(document.createTextNode(" "));
552
+ dd.appendChild(el("span", "node-input-inherited", inherited));
553
+ }
554
+ dl.appendChild(dt);
555
+ dl.appendChild(dd);
556
+ }
557
+
558
+ function appendCodeList(parent, title, values) {
559
+ if (!values || values.length === 0) return;
560
+ const section = el("section", "node-input-section");
561
+ section.appendChild(el("h4", null, title));
562
+ const list = el("ul", "node-input-code-list");
563
+ for (const value of values) {
564
+ const item = document.createElement("li");
565
+ const code = document.createElement("code");
566
+ code.textContent = String(value);
567
+ item.appendChild(code);
568
+ list.appendChild(item);
569
+ }
570
+ section.appendChild(list);
571
+ parent.appendChild(section);
572
+ }
573
+
574
+ function renderDagNodeInputDetail(content, data) {
575
+ const wrap = el("div", "node-input-root");
576
+ wrap.appendChild(
577
+ el("p", "node-input-semantics", UI_TEXT.inputFrozenLabel),
578
+ );
579
+
580
+ if (data.summary) {
581
+ const section = el("section", "node-input-section");
582
+ section.appendChild(el("h4", null, "摘要"));
583
+ const dl = el("dl", "node-input-summary");
584
+ appendMetaRow(
585
+ dl,
586
+ "executor",
587
+ data.summary.executor,
588
+ data.summary.executorSource,
589
+ );
590
+ appendMetaRow(dl, "role", data.summary.role, null);
591
+ appendMetaRow(dl, "complexity", data.summary.complexity, null);
592
+ appendMetaRow(
593
+ dl,
594
+ "writePolicy",
595
+ data.summary.writePolicy,
596
+ data.summary.writePolicySource,
597
+ );
598
+ appendMetaRow(dl, "toolProfile", data.summary.toolProfile, null);
599
+ section.appendChild(dl);
600
+ wrap.appendChild(section);
601
+ }
602
+
603
+ if (data.dependency) {
604
+ const section = el("section", "node-input-section");
605
+ section.appendChild(el("h4", null, "依赖"));
606
+ const dl = el("dl", "node-input-summary");
607
+ appendMetaRow(
608
+ dl,
609
+ "depends_on",
610
+ (data.dependency.dependsOn ?? []).join(", ") || "(none)",
611
+ null,
612
+ );
613
+ if (data.dependency.dependsPolicy) {
614
+ appendMetaRow(dl, "dependsPolicy", data.dependency.dependsPolicy, null);
615
+ }
616
+ if ((data.dependency.failureAwareDependsOn ?? []).length > 0) {
617
+ appendMetaRow(
618
+ dl,
619
+ "failureAwareDependsOn",
620
+ data.dependency.failureAwareDependsOn.join(", "),
621
+ null,
622
+ );
623
+ }
624
+ if (data.dependency.runIf) {
625
+ appendMetaRow(dl, "runIf", data.dependency.runIf, null);
626
+ }
627
+ section.appendChild(dl);
628
+ wrap.appendChild(section);
629
+ }
630
+
631
+ if (data.prompt) {
632
+ const section = el("section", "node-input-section");
633
+ section.appendChild(el("h4", null, "任务正文"));
634
+ if (data.prompt.source?.path) {
635
+ const meta = el("p", "muted");
636
+ meta.textContent = `source: ${data.prompt.source.path}${
637
+ data.prompt.source.sha256Prefix
638
+ ? ` (sha256 ${data.prompt.source.sha256Prefix}…)`
639
+ : ""
640
+ }`;
641
+ section.appendChild(meta);
642
+ }
643
+ section.appendChild(renderMarkdown(data.prompt.text ?? ""));
644
+ if (data.prompt.truncated) {
645
+ section.appendChild(
646
+ el(
647
+ "p",
648
+ "node-input-truncated",
649
+ `已截断,仅显示前 ${data.prompt.maxBytes ?? 0} 字节(UTF-8)。`,
650
+ ),
651
+ );
652
+ }
653
+ wrap.appendChild(section);
654
+ }
655
+
656
+ if (data.executorInput) {
657
+ const section = el("section", "node-input-section");
658
+ section.appendChild(el("h4", null, "执行器输入摘要"));
659
+ const dl = el("dl", "node-input-summary");
660
+ appendMetaRow(dl, "kind", data.executorInput.kind, null);
661
+ if (data.executorInput.modelHint) {
662
+ appendMetaRow(dl, "modelHint", data.executorInput.modelHint, null);
663
+ }
664
+ section.appendChild(dl);
665
+ appendCodeList(section, "skills", data.executorInput.skills);
666
+ if (data.executorInput.shell) {
667
+ const shell = data.executorInput.shell;
668
+ const shellDl = el("dl", "node-input-summary");
669
+ appendMetaRow(shellDl, "preset", shell.preset, null);
670
+ appendMetaRow(shellDl, "cwd", shell.cwd, null);
671
+ appendMetaRow(shellDl, "timeoutMs", shell.timeoutMs, null);
672
+ appendMetaRow(
673
+ shellDl,
674
+ "nonZeroExitPolicy",
675
+ shell.nonZeroExitPolicy,
676
+ null,
677
+ );
678
+ appendMetaRow(
679
+ shellDl,
680
+ "gates",
681
+ (shell.gates ?? []).join(", ") || "(none)",
682
+ null,
683
+ );
684
+ section.appendChild(shellDl);
685
+ if ((shell.commands ?? []).length > 0) {
686
+ const list = el("ol", "node-input-command-list");
687
+ for (const command of shell.commands) {
688
+ const item = document.createElement("li");
689
+ const pre = document.createElement("pre");
690
+ const code = document.createElement("code");
691
+ code.textContent = String(command);
692
+ pre.appendChild(code);
693
+ item.appendChild(pre);
694
+ list.appendChild(item);
695
+ }
696
+ section.appendChild(list);
697
+ }
698
+ }
699
+ if (data.executorInput.static) {
700
+ const staticBlock = data.executorInput.static;
701
+ const staticDl = el("dl", "node-input-summary");
702
+ appendMetaRow(staticDl, "status", staticBlock.status, null);
703
+ section.appendChild(staticDl);
704
+ section.appendChild(renderMarkdown(staticBlock.resultPreview ?? ""));
705
+ if (staticBlock.truncated) {
706
+ section.appendChild(
707
+ el(
708
+ "p",
709
+ "node-input-truncated",
710
+ `已截断,仅显示前 ${staticBlock.maxBytes ?? 0} 字节(UTF-8)。`,
711
+ ),
712
+ );
713
+ }
714
+ }
715
+ wrap.appendChild(section);
716
+ }
717
+
718
+ if (data.boundaries) {
719
+ const section = el("section", "node-input-section");
720
+ section.appendChild(el("h4", null, "边界与输出契约"));
721
+ const dl = el("dl", "node-input-summary");
722
+ appendMetaRow(dl, "outputMode", data.boundaries.outputMode, null);
723
+ appendMetaRow(
724
+ dl,
725
+ "firstProtocolLine",
726
+ data.boundaries.firstProtocolLine,
727
+ null,
728
+ );
729
+ section.appendChild(dl);
730
+ if (data.boundaries.outputContract) {
731
+ section.appendChild(el("h5", null, "outputContract"));
732
+ section.appendChild(renderMarkdown(data.boundaries.outputContract));
733
+ }
734
+ appendCodeList(section, "allowedPaths", data.boundaries.allowedPaths);
735
+ appendCodeList(section, "forbiddenPaths", data.boundaries.forbiddenPaths);
736
+ appendCodeList(section, "writeSet", data.boundaries.writeSet);
737
+ wrap.appendChild(section);
738
+ }
739
+
740
+ if (data.promptFingerprint?.exists) {
741
+ const section = el("section", "node-input-section");
742
+ section.appendChild(el("h4", null, "Assembled prompt 指纹"));
743
+ const dl = el("dl", "node-input-summary");
744
+ appendMetaRow(
745
+ dl,
746
+ "sha256",
747
+ data.promptFingerprint.sha256Prefix ?? "(parse failed)",
748
+ null,
749
+ );
750
+ appendMetaRow(
751
+ dl,
752
+ "lengthChars",
753
+ data.promptFingerprint.lengthChars,
754
+ null,
755
+ );
756
+ appendMetaRow(dl, "artifact", data.promptFingerprint.artifact, null);
757
+ section.appendChild(dl);
758
+ section.appendChild(
759
+ el("p", "muted", UI_TEXT.inputFingerprintNote),
760
+ );
761
+ wrap.appendChild(section);
762
+ }
763
+
764
+ if ((data.warnings ?? []).length > 0) {
765
+ const section = el("section", "node-input-section");
766
+ section.appendChild(el("h4", null, "警告"));
767
+ for (const warning of data.warnings) {
768
+ section.appendChild(el("p", "node-input-warning", String(warning)));
769
+ }
770
+ wrap.appendChild(section);
771
+ }
772
+
773
+ content.appendChild(wrap);
774
+ }
775
+
776
+ function unavailableMessage(reason) {
777
+ if (reason === "unsupported-dynamic-node") {
778
+ return UI_TEXT.inputDynamicUnsupported;
779
+ }
780
+ if (reason === "run-spec-unavailable") {
781
+ return UI_TEXT.inputRunSpecUnavailable;
782
+ }
783
+ if (reason === "task-not-found") {
784
+ return UI_TEXT.inputUnavailable;
785
+ }
786
+ return UI_TEXT.noInput;
787
+ }
788
+
789
+ async function renderDagNodeInput(content, dagRunId, node) {
790
+ const nodeId = node.nodeId;
791
+ const identity = makeDagNodeInputIdentity(dagRunId, nodeId);
792
+ uiState.dagNodeInputIdentity = identity;
793
+ uiState.dagNodeInputLoading = true;
794
+ uiState.dagNodeInputError = null;
795
+
796
+ clearNode(content);
797
+ const loading = el("p", "muted", UI_TEXT.inputLoading);
798
+ loading.setAttribute("role", "status");
799
+ loading.setAttribute("aria-live", "polite");
800
+ content.appendChild(loading);
801
+
802
+ const result = await fetchDagNodeInput(dagRunId, nodeId);
803
+ if (
804
+ uiState.dagNodeInputIdentity !== identity ||
805
+ uiState.selectedDagNodeId !== nodeId ||
806
+ uiState.currentDagRunId !== dagRunId ||
807
+ uiState.dagInspectorTab !== "input" ||
808
+ !content.isConnected ||
809
+ content.dataset.dagNodeId !== String(nodeId) ||
810
+ content.dataset.dagRunId !== String(dagRunId)
811
+ ) {
812
+ return;
813
+ }
814
+
815
+ uiState.dagNodeInputLoading = false;
816
+ clearNode(content);
817
+
818
+ if (!result.ok) {
819
+ const message =
820
+ result.body?.error ||
821
+ (result.status === 0
822
+ ? UI_TEXT.inputError
823
+ : `${UI_TEXT.inputError}(HTTP ${result.status})`);
824
+ uiState.dagNodeInputError = message;
825
+ uiState.dagNodeInputData = null;
826
+ const error = el("p", "node-input-error", message);
827
+ error.setAttribute("role", "alert");
828
+ content.appendChild(error);
829
+ return;
830
+ }
831
+
832
+ const data = result.body;
833
+ uiState.dagNodeInputData = data;
834
+ uiState.dagNodeInputError = null;
835
+ if (!data?.available) {
836
+ const empty = el(
837
+ "p",
838
+ "empty",
839
+ unavailableMessage(data?.availabilityReason),
840
+ );
841
+ empty.setAttribute("role", "status");
842
+ content.appendChild(empty);
843
+ if ((data?.warnings ?? []).length > 0) {
844
+ for (const warning of data.warnings) {
845
+ content.appendChild(el("p", "node-input-warning", String(warning)));
846
+ }
847
+ }
848
+ return;
849
+ }
850
+
851
+ renderDagNodeInputDetail(content, data);
852
+ }
853
+
533
854
  function fillDagInspectorContent(content, dagRunId, node) {
534
855
  const previousRenderedNode = content.dataset.dagNodeId ?? "";
535
- const nodeChanged = previousRenderedNode !== String(node.nodeId ?? "");
856
+ const previousRenderedRun = content.dataset.dagRunId ?? "";
857
+ const nodeChanged =
858
+ previousRenderedNode !== String(node.nodeId ?? "") ||
859
+ previousRenderedRun !== String(dagRunId ?? "");
536
860
  if (nodeChanged) {
537
861
  // Drop any open spec-evidence preview + cached list so a stale A detail
538
862
  // cannot survive into node B (AC-007 regression guard).
539
863
  uiState.specEvidenceDetail = null;
540
864
  uiState.specEvidenceListEvidence = null;
865
+ uiState.dagNodeInputData = null;
866
+ uiState.dagNodeInputError = null;
541
867
  }
542
- const activeTab = ["timeline", "spec-evidence"].includes(
868
+ const activeTab = ["input", "timeline", "spec-evidence"].includes(
543
869
  uiState.dagInspectorTab,
544
870
  )
545
871
  ? uiState.dagInspectorTab
@@ -548,12 +874,15 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
548
874
  content.setAttribute("role", "tabpanel");
549
875
  content.setAttribute("aria-labelledby", `dag-inspector-tab-${activeTab}`);
550
876
  content.dataset.dagNodeId = String(node.nodeId ?? "");
877
+ content.dataset.dagRunId = String(dagRunId ?? "");
551
878
  // Drop timeline-only classes when switching tabs so output layout stays clean.
552
879
  content.className = "dag-inspector-content";
553
880
  if (activeTab === "spec-evidence") {
554
881
  void renderSpecEvidence(content, dagRunId, node.nodeId);
555
882
  } else if (activeTab === "timeline") {
556
883
  renderSessionTimeline(content, node.nodeId);
884
+ } else if (activeTab === "input") {
885
+ void renderDagNodeInput(content, dagRunId, node);
557
886
  } else if (!node.outputPreview && !node.errorPreview) {
558
887
  content.appendChild(el("p", "empty", UI_TEXT.noOutput));
559
888
  } else {
@@ -592,6 +921,7 @@ function buildDagInspectorHeader(dagRunId, node) {
592
921
  header.appendChild(controls);
593
922
 
594
923
  const tabDefs = [
924
+ ["input", "节点输入", "ri-login-box-line"],
595
925
  ["output", "节点输出", "ri-file-text-line"],
596
926
  ["timeline", "执行过程", "ri-route-line"],
597
927
  ["spec-evidence", "规范证据", "ri-book-open-line"],
@@ -660,6 +990,17 @@ function syncDagInspectorHeader(panel, dagRunId, node) {
660
990
  }
661
991
 
662
992
  function dagInspectorSignature(dagRunId, node) {
993
+ const inputRevision =
994
+ uiState.dagInspectorTab === "input"
995
+ ? [
996
+ uiState.dagNodeInputIdentity,
997
+ uiState.dagNodeInputLoading,
998
+ uiState.dagNodeInputError ?? "",
999
+ uiState.dagNodeInputData?.availabilityReason ?? "",
1000
+ uiState.dagNodeInputData?.promptFingerprint?.sha256Prefix ?? "",
1001
+ node.status ?? "",
1002
+ ]
1003
+ : null;
663
1004
  return JSON.stringify([
664
1005
  dagRunId,
665
1006
  node.nodeId,
@@ -667,6 +1008,7 @@ function dagInspectorSignature(dagRunId, node) {
667
1008
  node.outputPreview ?? "",
668
1009
  node.errorPreview ?? "",
669
1010
  uiState.dagInspectorTab === "timeline" ? uiState.sessionEventOffset : null,
1011
+ inputRevision,
670
1012
  ]);
671
1013
  }
672
1014
 
@@ -702,17 +1044,30 @@ export function renderDagInspector(dagRunId, node, initial = true) {
702
1044
  syncDagInspectorHeader(panel, dagRunId, node);
703
1045
  if (panel.dataset.contentSignature === nextContentSignature) return;
704
1046
  const previousNode = existingContent.dataset.dagNodeId ?? "";
705
- const nodeChanged = previousNode !== String(node.nodeId ?? "");
706
- const followLatest =
707
- nodeChanged ||
708
- computeFollowLatest(
709
- existingContent.scrollTop,
710
- existingContent.scrollHeight,
711
- existingContent.clientHeight,
712
- );
713
- const regionKey = `dag:${dagRunId}:${node.nodeId}:output`;
714
- uiState.dagNodeOutputViewportState = updateDagOutputViewportState(
715
- uiState.dagNodeOutputViewportState,
1047
+ const previousRun = existingContent.dataset.dagRunId ?? "";
1048
+ const nodeChanged =
1049
+ previousNode !== String(node.nodeId ?? "") ||
1050
+ previousRun !== String(dagRunId ?? "");
1051
+ const activeTab = ["input", "timeline", "spec-evidence"].includes(
1052
+ uiState.dagInspectorTab,
1053
+ )
1054
+ ? uiState.dagInspectorTab
1055
+ : "output";
1056
+ const isInputTab = activeTab === "input";
1057
+ const followLatest = isInputTab
1058
+ ? false
1059
+ : nodeChanged ||
1060
+ computeFollowLatest(
1061
+ existingContent.scrollTop,
1062
+ existingContent.scrollHeight,
1063
+ existingContent.clientHeight,
1064
+ );
1065
+ const regionKey = `dag:${dagRunId}:${node.nodeId}:${isInputTab ? "input" : "output"}`;
1066
+ const viewportUpdater = isInputTab
1067
+ ? "dagNodeInputViewportState"
1068
+ : "dagNodeOutputViewportState";
1069
+ uiState[viewportUpdater] = updateDagOutputViewportState(
1070
+ uiState[viewportUpdater],
716
1071
  regionKey,
717
1072
  existingContent.scrollLeft,
718
1073
  existingContent.scrollTop,
@@ -729,7 +1084,8 @@ export function renderDagInspector(dagRunId, node, initial = true) {
729
1084
  if (followLatest) {
730
1085
  existingContent.scrollTop = existingContent.scrollHeight;
731
1086
  } else {
732
- existingContent.scrollTop = uiState.dagNodeOutputViewportState.scrollTop;
1087
+ const saved = uiState[viewportUpdater];
1088
+ existingContent.scrollTop = nodeChanged ? 0 : (saved?.scrollTop ?? 0);
733
1089
  }
734
1090
  for (const [index, pre] of [
735
1091
  ...existingContent.querySelectorAll("pre"),
@@ -111,8 +111,12 @@ export async function projectOutcome(input) {
111
111
  if (artifact.sha256 && artifact.sha256 !== recomputed) {
112
112
  return contractError(`artifact sha256 mismatch for ${artifact.path}: declared=${artifact.sha256} actual=${recomputed}`);
113
113
  }
114
+ // Persist portable repo-relative paths only (Delivery verifyArtifactRefs rejects abs paths).
115
+ const relativePath = path
116
+ .relative(path.resolve(input.repoRoot), resolved)
117
+ .replace(/\\/g, "/");
114
118
  validatedArtifacts.push({
115
- path: artifact.path.replace(/\\/g, "/"),
119
+ path: relativePath,
116
120
  sha256: recomputed,
117
121
  ...(artifact.kind ? { kind: artifact.kind } : {}),
118
122
  ...(artifact.schemaId ? { schemaId: artifact.schemaId } : {}),
@@ -1,6 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { writeTextAtomic } from "../../infrastructure/harness/atomic-write.js";
5
+ import { getTaskPaths } from "../../task/runtime.js";
4
6
  import { formatDuration, noopProgressReporter } from "../progress-reporter.js";
5
7
  import { redactForPreview, truncatePreview } from "../observability/events.js";
6
8
  import { controllerIdentityExpectationFailure, resolveControllerIdentity, } from "../loop-agent/loop-agent-client.js";
@@ -104,6 +106,13 @@ export async function runTaskSpec(options) {
104
106
  ], true, undefined, eventCtx);
105
107
  });
106
108
  progress.step(`dag validated in ${formatDuration(Date.now() - validateStartedAt)}`);
109
+ const canonicalDraftPath = getTaskPaths(options.repoRoot, materializeManifest.harnessTaskId).dagDraftPath;
110
+ await runObservedStep(eventCtx, "publish-canonical-dag-draft", async () => {
111
+ const dagBytes = await readFile(dagPath, "utf-8");
112
+ await writeTextAtomic(canonicalDraftPath, dagBytes, {
113
+ repoRoot: options.repoRoot,
114
+ });
115
+ });
107
116
  const runDagStep = "run-dag";
108
117
  const runDagStartedAt = Date.now();
109
118
  const dagEventsPath = path.join(getTaskPoolRoot(options.repoRoot), "observability", "runs", workerRunId, "dag-events.jsonl");
@@ -330,7 +339,7 @@ export function buildWorkerRunId(businessId, now) {
330
339
  .slice(0, 6);
331
340
  return `wr-${date}-${businessId}-${hash}`;
332
341
  }
333
- async function runRequiredCommand(repoRoot, client, artifactName, args, expectJson = true, timeoutMs, eventCtx) {
342
+ async function runRequiredCommand(repoRoot, client, artifactName, args, expectJson = true, timeoutMs, _eventCtx) {
334
343
  const result = await client.run(args, {
335
344
  cwd: repoRoot,
336
345
  artifactName,
@@ -743,7 +752,14 @@ function strictModelsArgs(piModel) {
743
752
  */
744
753
  export async function applyPiModelOverride(dagPath, piModel) {
745
754
  const raw = await readFile(dagPath, "utf-8");
746
- const spec = JSON.parse(raw);
755
+ let spec;
756
+ try {
757
+ spec = JSON.parse(raw);
758
+ }
759
+ catch (error) {
760
+ const message = error instanceof Error ? error.message : String(error);
761
+ throw new Error(`invalid generated DAG JSON at ${dagPath}: ${message}`);
762
+ }
747
763
  spec.executorModels = {
748
764
  pi: {
749
765
  LOW: piModel,
@@ -304,11 +304,51 @@ export async function runReadyTasks(options) {
304
304
  if (result.status !== "succeeded") {
305
305
  await options.onTaskFinalized?.({ status: result.status, taskSpec, workerRunId: result.workerRunId, runRecordPath: result.runRecordPath });
306
306
  }
307
+ const recordErrorMessage = errorMessage(error);
308
+ // Always project a Pool run fact for record-error so operators can
309
+ // mark-failed / retry without hand-editing Running state.
310
+ try {
311
+ await recordTaskPoolRun({
312
+ repoRoot: options.repoRoot,
313
+ run: {
314
+ schemaVersion: 1,
315
+ batchRunId,
316
+ workerRunId: result.workerRunId,
317
+ taskId: result.businessId,
318
+ featureId: taskSpec.feature_id,
319
+ status: "run-error",
320
+ recordedAt: new Date().toISOString(),
321
+ error: recordErrorMessage,
322
+ runRecordPath: result.runRecordPath,
323
+ ...(result.workflow ? { workflow: result.workflow } : {}),
324
+ ...(controllerIdentity ? { controllerIdentity } : {}),
325
+ ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
326
+ },
327
+ });
328
+ await writeTaskPoolState(options.repoRoot, {
329
+ schemaVersion: 2,
330
+ featureId: taskSpec.feature_id,
331
+ taskId,
332
+ status: "Failed",
333
+ updatedAt: new Date().toISOString(),
334
+ workerRunId: result.workerRunId,
335
+ lastRunRecordPath: result.runRecordPath,
336
+ failure: {
337
+ category: "EnvFailure",
338
+ recommendedFollowUpKind: "manual-review",
339
+ derivedFollowUpTaskId: `${taskId}-record-error-review`,
340
+ source: "report-decision",
341
+ },
342
+ });
343
+ }
344
+ catch {
345
+ // Best-effort; batch still reports record-error.
346
+ }
307
347
  tasks.push({
308
348
  taskId,
309
349
  workerRunId: result.workerRunId,
310
350
  status: "record-error",
311
- error: errorMessage(error),
351
+ error: recordErrorMessage,
312
352
  runRecordPath: result.runRecordPath,
313
353
  });
314
354
  break;