@tea-agent/loop-agent 0.39.1 → 0.40.0-next.10

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 (156) hide show
  1. package/CHANGELOG.md +106 -116
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/executors/dag-pi-executor.js +69 -7
  4. package/dist/executors/pi-executor.js +4 -2
  5. package/dist/executors/pi-sdk-executor.js +46 -1
  6. package/dist/executors/pi-writer-tool-policy.js +57 -1
  7. package/dist/executors/shell-executor.js +42 -2
  8. package/dist/executors/workspace-write-snapshot.js +68 -0
  9. package/dist/infrastructure/harness/atomic-write.js +23 -0
  10. package/dist/task/config-types.js +10 -0
  11. package/dist/worker/console/chat/chat-event-store.js +75 -5
  12. package/dist/worker/console/chat/context-panel.js +7 -0
  13. package/dist/worker/console/chat/deferred-turn.js +12 -0
  14. package/dist/worker/console/chat/pi-runtime.js +331 -18
  15. package/dist/worker/console/chat/provider-error.js +98 -0
  16. package/dist/worker/console/chat/routes.js +389 -56
  17. package/dist/worker/console/chat/session-stats.js +179 -0
  18. package/dist/worker/console/chat/session-store.js +93 -63
  19. package/dist/worker/console/chat/todos.js +115 -0
  20. package/dist/worker/console/chat/tool-preview.js +42 -0
  21. package/dist/worker/console/chat/turn-process.js +20 -55
  22. package/dist/worker/console/chat/user-questions.js +266 -0
  23. package/dist/worker/console/chat/workspace-landing.js +79 -0
  24. package/dist/worker/console/native-directory-picker.js +13 -0
  25. package/dist/worker/console/routes.js +309 -1
  26. package/dist/worker/console/security.js +4 -4
  27. package/dist/worker/console/server.js +529 -186
  28. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
  29. package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-CAkA3We3.js} +1 -1
  30. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
  31. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
  32. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
  33. package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
  34. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
  42. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
  43. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
  44. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
  45. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
  46. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
  47. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
  48. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
  49. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
  50. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
  53. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
  54. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
  55. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
  56. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
  57. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
  58. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
  59. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
  60. package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
  61. package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
  62. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
  63. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
  64. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
  65. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
  66. package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-ByuxcHvp.js} +1 -1
  67. package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-DwPGvpCv.js} +5 -5
  68. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
  69. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
  70. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
  71. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
  72. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
  73. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
  74. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
  75. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
  76. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
  77. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
  78. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
  79. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
  80. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
  81. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
  82. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
  83. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
  84. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
  85. package/dist/worker/console/static/index.html +2 -2
  86. package/dist/worker/console/static-src/app/console-types.js +29 -4
  87. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  88. package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
  89. package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
  90. package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
  91. package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
  92. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  93. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  94. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  95. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  96. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  97. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  98. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  99. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  100. package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
  101. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  102. package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
  103. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  104. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  105. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  106. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  107. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  108. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  109. package/dist/worker/console/workspace-context.js +234 -0
  110. package/dist/worker/console/workspace-registry.js +214 -0
  111. package/dist/worker/observe/server.js +4 -0
  112. package/dist/worker/observe/static/api.js +26 -6
  113. package/dist/worker/observe/static/app.js +18 -70
  114. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  115. package/dist/worker/observe/static/dag-node-purpose.js +203 -0
  116. package/dist/worker/observe/static/index.html +1 -1
  117. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  118. package/dist/worker/observe/static/operator-chrome.css +145 -20
  119. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  120. package/dist/worker/observe/static/operator-chrome.js +330 -100
  121. package/dist/worker/observe/static/router.d.ts +46 -0
  122. package/dist/worker/observe/static/router.js +25 -1
  123. package/dist/worker/observe/static/state.d.ts +50 -0
  124. package/dist/worker/observe/static/state.js +174 -2
  125. package/dist/worker/observe/static/styles.css +348 -38
  126. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  127. package/dist/worker/observe/static/views/dag-inspector.js +310 -156
  128. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  129. package/dist/worker/observe/static/views/dag.js +6 -4
  130. package/dist/worker/observe/static/views/dashboard.js +6 -4
  131. package/dist/worker/observe/static/views/night.js +2 -2
  132. package/dist/worker/observe/static/views/pool.js +1 -0
  133. package/dist/worker/observe/static/views/run.js +3 -1
  134. package/dist/worker/observe/static/views/task.js +1 -1
  135. package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
  136. package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
  137. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  138. package/dist/workflows/dag/init-hybrid.js +113 -47
  139. package/dist/workflows/dag/interrupt-request.js +3 -3
  140. package/dist/workflows/dag/lifecycle.js +3 -3
  141. package/dist/workflows/dag/types.js +20 -1
  142. package/docs/README.md +1 -0
  143. package/docs/operations/local-development-environment.md +21 -0
  144. package/docs/templates/agent-dag.schema.json +18 -0
  145. package/docs/templates/backend-test-dag.json +265 -237
  146. package/harness.json +3 -3
  147. package/package.json +3 -1
  148. package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
  149. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
  150. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
  151. package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
  152. package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
  153. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
  154. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
  155. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  156. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -84,7 +84,7 @@ let dashboardLoadingNode = null;
84
84
 
85
85
  /**
86
86
  * 「昨夜班次」条带:夜间结果在观测侧的唯一一级呈现(替代旧「夜间任务」tab)。
87
- * 管理动作(晨审处置)始终落在操作侧 /#/night,保持 Inspect 只读不变式。
87
+ * 管理动作(晨审处置)始终落在操作侧 /#/operate/night,保持 Inspect 只读不变式。
88
88
  */
89
89
  async function renderNightStrip() {
90
90
  const strip = document.getElementById("dashboard-night");
@@ -118,14 +118,14 @@ async function renderNightStrip() {
118
118
 
119
119
  const actions = el("div", "night-strip-actions");
120
120
  const detail = el("a", "page-action-btn", "夜间详情");
121
- detail.href = "#/night";
121
+ detail.href = "/#/inspect/night";
122
122
  detail.addEventListener("click", (e) => {
123
123
  e.preventDefault();
124
124
  navigate("/night");
125
125
  });
126
126
  actions.appendChild(detail);
127
127
  const manage = el("a", "page-action-btn night-strip-manage", "晨审处置 →");
128
- manage.href = "/#/night";
128
+ manage.href = "/#/operate/night";
129
129
  manage.title = "夜间计划的管理与晨审处置在操作台完成(Inspect 保持只读)";
130
130
  actions.appendChild(manage);
131
131
  strip.appendChild(actions);
@@ -738,10 +738,12 @@ export function startDashboardPolling(scrollTo) {
738
738
  const poll = async (initialScrollTo) => {
739
739
  await renderDashboard(initialScrollTo);
740
740
  if (generation !== uiState.pollingGeneration) return;
741
+ const delay = dashboardPollDelay();
742
+ if (delay == null) return;
741
743
  uiState.dashboardTimer = setTimeout(() => {
742
744
  uiState.dashboardTimer = null;
743
745
  void poll();
744
- }, dashboardPollDelay());
746
+ }, delay);
745
747
  };
746
748
  void poll(scrollTo);
747
749
  }
@@ -46,8 +46,8 @@ export async function renderNightJobs(scheduleId) {
46
46
  showView("night");
47
47
  bindNightToolbar();
48
48
  const crumbs = [
49
- { label: UI_TEXT.dashboard, href: "#/" },
50
- { label: UI_TEXT.nightJobs, href: "#/night" },
49
+ { label: UI_TEXT.dashboard, href: "/#/inspect" },
50
+ { label: UI_TEXT.nightJobs, href: "/#/inspect/night" },
51
51
  ];
52
52
  if (scheduleId) crumbs.push({ label: scheduleId });
53
53
  setBreadcrumb(crumbs);
@@ -93,6 +93,7 @@ function startPoolAutoPolling() {
93
93
  if (generation !== uiState.pollingGeneration) return;
94
94
  if (!poolAutoRefreshEnabled()) return;
95
95
  const delay = isPageVisible() ? POOL_POLL_MS : dashboardPollDelay();
96
+ if (delay == null) return;
96
97
  uiState.poolTimer = setTimeout(() => {
97
98
  uiState.poolTimer = null;
98
99
  void poll();
@@ -50,6 +50,7 @@ import {
50
50
  } from "../relations.js";
51
51
  import { copyText, renderRecommendedCommand } from "../copy.js";
52
52
  import {
53
+ fetchWithWorkspaceScope,
53
54
  isPageVisible,
54
55
  detailPollDelay,
55
56
  dashboardPollDelay,
@@ -126,7 +127,7 @@ export function renderInlineArtifactList(container, artifacts) {
126
127
  list.appendChild(li);
127
128
  void (async () => {
128
129
  try {
129
- const res = await fetch(artifactUrl(artifactPath));
130
+ const res = await fetchWithWorkspaceScope(artifactUrl(artifactPath));
130
131
  const text = await res.text();
131
132
  if (!res.ok) {
132
133
  let message = text;
@@ -444,6 +445,7 @@ export function startRunPolling(workerRunId, initial = true) {
444
445
  const active = await renderRun(workerRunId, isInitial);
445
446
  if (generation !== uiState.pollingGeneration || !active) return;
446
447
  const delay = detailPollDelay("running", isPageVisible());
448
+ if (delay == null) return;
447
449
  uiState.runPollTimer = setTimeout(() => {
448
450
  uiState.runPollTimer = null;
449
451
  void poll(false);
@@ -86,7 +86,7 @@ export async function renderTaskDetail(featureId, taskId) {
86
86
  showView("task");
87
87
  setBreadcrumb([
88
88
  { label: UI_TEXT.dashboard, href: "#/" },
89
- { label: "资源池", href: "#/pool" },
89
+ { label: "资源池", href: "/#/inspect/pool" },
90
90
  { label: displayLabel },
91
91
  ]);
92
92
 
@@ -258,13 +258,14 @@ function bounded(value) {
258
258
  ? normalized
259
259
  : `${normalized.slice(0, MAX_DIAGNOSTIC_CHARS)}\n...[truncated]`;
260
260
  }
261
- function collectionItems(stdout) {
261
+ function collectionItems(stdout, layout) {
262
+ const rootPrefix = `${(layout ?? resolveBackendTestLayout(undefined)).testRoot}/`;
262
263
  return [...new Set(stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => {
263
264
  if (!line.includes("::"))
264
265
  return false;
265
266
  if (/^(ERROR|FAILED|E\s+)/.test(line))
266
267
  return false;
267
- return /^testcase\//.test(line.replaceAll("\\", "/"));
268
+ return line.replaceAll("\\", "/").startsWith(rootPrefix);
268
269
  }).map((line) => line.replaceAll("\\", "/")))];
269
270
  }
270
271
  function escapeRegExp(value) {
@@ -416,7 +417,7 @@ function classifyCollectionFailure(output, evidence) {
416
417
  return { status: "BLOCKED", kind: "unclassified-collection-error", detail: "collection failure cannot be safely attributed to generated testcase assets" };
417
418
  }
418
419
  export function assessBackendPytestCollection(input) {
419
- const items = collectionItems(input.stdout);
420
+ const items = collectionItems(input.stdout, input.layout);
420
421
  const fixtureResolution = input.fixtureResolution ?? { exitCode: 0, stdout: "fixture resolution assumed by direct assessor", stderr: "" };
421
422
  if (input.exitCode === 0 && fixtureResolution.exitCode === 0) {
422
423
  return backendPytestCollectionFactsSchema.parse({
@@ -740,7 +741,7 @@ export async function materializeBackendTestExecutionReadiness(input) {
740
741
  if (input.effective.phase !== "effective" || input.effective.status !== "PASS" || input.effective.fixtureResolutionStatus !== "PASS") {
741
742
  throw new Error("backend-test execution readiness requires effective collection and fixture-resolution PASS");
742
743
  }
743
- const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.effective.mappedScripts);
744
+ const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.effective.mappedScripts, { layout: input.layout });
744
745
  assertSameInventory(input.effective, current);
745
746
  const eligibility = input.eligibility ?? { eligibleItemIds: input.effective.collectedItemIds, excludedItems: [] };
746
747
  const status = eligibility.eligibleItemIds.length === 0
@@ -839,7 +840,7 @@ async function firstUnreadableAssetPath(workspaceRoot, readiness) {
839
840
  }
840
841
  return undefined;
841
842
  }
842
- export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, readiness) {
843
+ export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, readiness, layout) {
843
844
  if (!["PASS", "PARTIAL"].includes(readiness.status)) {
844
845
  const counts = formatReadinessCounts(readiness);
845
846
  const reasonTally = readiness.eligibleItemIds.length === 0 && readiness.excludedItems.length > 0
@@ -862,7 +863,7 @@ export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, re
862
863
  }
863
864
  let current;
864
865
  try {
865
- current = await buildBackendPytestAssetInventory(workspaceRoot, readiness.mappedScripts);
866
+ current = await buildBackendPytestAssetInventory(workspaceRoot, readiness.mappedScripts, { layout });
866
867
  }
867
868
  catch {
868
869
  const fallbackPath = [...readiness.mappedScripts].sort()[0] ?? Object.keys(readiness.assetHashes).sort()[0] ?? "unknown";
@@ -897,11 +898,11 @@ export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, re
897
898
  }
898
899
  }
899
900
  }
900
- export async function assertBackendPytestCollectionFresh(workspaceRoot, effective) {
901
+ export async function assertBackendPytestCollectionFresh(workspaceRoot, effective, layout) {
901
902
  if (effective.phase !== "effective" || effective.status !== "PASS") {
902
903
  throw new Error("backend pytest execution requires effective collection PASS facts");
903
904
  }
904
- const current = await buildBackendPytestAssetInventory(workspaceRoot, effective.mappedScripts);
905
+ const current = await buildBackendPytestAssetInventory(workspaceRoot, effective.mappedScripts, { layout });
905
906
  const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
906
907
  if (priorityOnly.length > 0)
907
908
  throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
@@ -909,7 +910,7 @@ export async function assertBackendPytestCollectionFresh(workspaceRoot, effectiv
909
910
  }
910
911
  export async function materializeEffectiveBackendPytestCollection(input) {
911
912
  if (input.initial.status === "PASS") {
912
- const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.initial.mappedScripts);
913
+ const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.initial.mappedScripts, { layout: input.layout });
913
914
  const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
914
915
  if (priorityOnly.length > 0)
915
916
  throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
@@ -927,7 +928,7 @@ export async function materializeEffectiveBackendPytestCollection(input) {
927
928
  if (input.final.status !== "PASS")
928
929
  throw new Error("backend pytest collection still fails after bounded repair");
929
930
  await assertBackendPytestRepairSafety(input.workspaceRoot, input.initial, input.final);
930
- const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.final.mappedScripts);
931
+ const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.final.mappedScripts, { layout: input.layout });
931
932
  const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
932
933
  if (priorityOnly.length > 0)
933
934
  throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
@@ -884,28 +884,23 @@ export function isBackendTestShardChildTask(task) {
884
884
  task.id?.startsWith("generate-backend-pytest-case-"));
885
885
  }
886
886
  /**
887
- * Is this task the README-only plan node of the map-sharded md topology?
888
- * The plan node writes testcase/md/README.md alone; module case cards are
889
- * produced by the downstream map_agent children, so the Completeness Gate
890
- * must NOT require module files to exist at plan time.
887
+ * Historical classifier for the Markdown plan node. Current backend-test N2
888
+ * is read-only and persists a run-owned plan.md, so writer completeness never
889
+ * invokes the workspace README assessment below for newly generated DAGs.
891
890
  */
892
891
  export function isBackendTestMdPlanTask(task) {
893
892
  return task.id === "generate-backend-md-plan-pi";
894
893
  }
895
894
  /**
896
- * Is this task the shared-resources plan node of the map-sharded pytest
897
- * topology? The plan node writes helpers/factories only; per-module pytest
898
- * scripts are produced by the downstream map_agent children, so the Gate
899
- * must NOT require mapped pytest scripts to exist at plan time.
895
+ * Historical classifier for the former shared-resources pytest writer. Current
896
+ * N7 is static and every map child writes one self-contained pytest module.
900
897
  */
901
898
  export function isBackendTestPytestPlanTask(task) {
902
899
  return task.id === "generate-backend-pytest-plan-pi";
903
900
  }
904
901
  /**
905
- * Plan-node completeness for the md topology: verify the README itself is
906
- * structurally complete (Coverage Scope, Coverage Matrix, a non-empty module
907
- * index), WITHOUT requiring the indexed module files to exist yet — those are
908
- * written by the downstream map_agent shard children.
902
+ * Legacy workspace-plan completeness helper retained for isolated historical
903
+ * fixtures. New DAGs validate the run-owned plan artifact in N3 instead.
909
904
  */
910
905
  export async function assessBackendTestMdPlanCompleteness(workspaceRoot, layout) {
911
906
  const resolvedPlan = layout ?? resolveBackendTestLayout(undefined);
@@ -26,6 +26,7 @@ export function buildExpandedChildTask(input) {
26
26
  allowedPaths: renderDynamicPatternList(child.allowedPaths, item, index, expansion.itemName) ?? [],
27
27
  forbiddenPaths: renderDynamicPatternList(child.forbiddenPaths, item, index, expansion.itemName) ?? [],
28
28
  writeSet: renderDynamicPatternList(child.writeSet, item, index, expansion.itemName),
29
+ readSet: renderDynamicPatternList(child.readSet, item, index, expansion.itemName),
29
30
  outputContract: child.outputContract,
30
31
  writerOutcomePolicy: child.writerOutcomePolicy,
31
32
  writeGuardPolicy: child.writeGuardPolicy,