@tea-agent/loop-agent 0.37.0 → 0.37.1-beta.0

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 (192) hide show
  1. package/AGENTS.md +2 -6
  2. package/CHANGELOG.md +71 -144
  3. package/README.md +1 -1
  4. package/bin/loop-agent.js +1 -37
  5. package/dist/application/task-lifecycle/advance.js +0 -1
  6. package/dist/application/task-lifecycle/observe.js +0 -15
  7. package/dist/application/task-lifecycle/plan-transitions.js +0 -15
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +3 -8
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/run-dag-progress.js +0 -14
  13. package/dist/commands/task-advance.js +3 -33
  14. package/dist/executors/dag-pi-executor.js +30 -47
  15. package/dist/executors/pi-sdk-executor.js +0 -34
  16. package/dist/executors/shell-executor.js +95 -6
  17. package/dist/shared/operator/capabilities.js +13 -200
  18. package/dist/shared/package-metadata.js +0 -42
  19. package/dist/task/contract/project.js +0 -4
  20. package/dist/task/source-prepare/completeness.js +0 -53
  21. package/dist/task/source-prepare/parse-intent.js +12 -82
  22. package/dist/task/source-prepare/prepare.js +5 -102
  23. package/dist/task/source-prepare/semantic-intake.js +23 -144
  24. package/dist/worker/console/chat/artifact-card.js +1 -8
  25. package/dist/worker/console/chat/chat-event-store.js +0 -152
  26. package/dist/worker/console/chat/human-gate-card.js +1 -9
  27. package/dist/worker/console/chat/operation-card.js +2 -71
  28. package/dist/worker/console/chat/pi-runtime.js +62 -111
  29. package/dist/worker/console/chat/routes.js +10 -43
  30. package/dist/worker/console/chat/session-store.js +1 -9
  31. package/dist/worker/console/chat/shortcuts.js +7 -9
  32. package/dist/worker/console/chat/turn-process.js +23 -204
  33. package/dist/worker/console/chat/workspace-landing.js +1 -2
  34. package/dist/worker/console/operation-runner.js +6 -155
  35. package/dist/worker/console/operator-actions.js +14 -391
  36. package/dist/worker/console/operator-user-error.js +0 -4
  37. package/dist/worker/console/recovery-cta.js +3 -50
  38. package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
  39. package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
  40. package/dist/worker/console/static/index.html +2 -2
  41. package/dist/worker/console/static-src/app/console-types.js +9 -13
  42. package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
  43. package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
  44. package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
  45. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
  46. package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
  47. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  48. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
  49. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
  50. package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
  51. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
  52. package/dist/worker/loop-agent/loop-agent-client.js +3 -17
  53. package/dist/worker/observability/read-model.js +0 -20
  54. package/dist/worker/observe/spec-evidence.js +8 -3
  55. package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
  56. package/dist/worker/observe/static/operator-chrome.js +3 -10
  57. package/dist/worker/observe/static/views/dag-inspector.js +71 -6
  58. package/dist/worker/observe/static/views/dag.js +0 -12
  59. package/dist/worker/preflight.js +1 -2
  60. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
  61. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  62. package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
  63. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  64. package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
  65. package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
  66. package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
  67. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  68. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
  69. package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
  70. package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
  71. package/dist/workflows/dag/frontend-repair.js +18 -219
  72. package/dist/workflows/dag/frontend-verification-trace.js +32 -47
  73. package/dist/workflows/dag/init-hybrid.js +82 -165
  74. package/dist/workflows/dag/node-execution.js +0 -89
  75. package/dist/workflows/dag/recovery-recommendation.js +0 -58
  76. package/dist/workflows/dag/runner.js +15 -333
  77. package/dist/workflows/dag/scheduler.js +3 -257
  78. package/dist/workflows/dag/types.js +9 -130
  79. package/docs/README.md +3 -3
  80. package/docs/architecture/evolution.md +67 -102
  81. package/docs/templates/backend-test-dag.json +50 -32
  82. package/docs/templates/init-managed-agents.md +2 -5
  83. package/harness.json +2 -2
  84. package/package.json +3 -9
  85. package/skills/loop-agent/SKILL.md +0 -1
  86. package/skills/loop-agent/references/command-reference.md +0 -4
  87. package/dist/build-stamp.json +0 -6
  88. package/dist/commands/dag-request-interrupt.js +0 -20
  89. package/dist/worker/console/chat/assistant-content.js +0 -121
  90. package/dist/worker/console/chat/semantic-activity.js +0 -477
  91. package/dist/worker/console/operation-run-facts.js +0 -190
  92. package/dist/worker/console/operation-wait.js +0 -355
  93. package/dist/worker/console/recovery-error-copy.js +0 -198
  94. package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
  95. package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
  96. package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
  97. package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
  98. package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
  99. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
  100. package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
  101. package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
  102. package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
  103. package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
  104. package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
  105. package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
  106. package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
  107. package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
  108. package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
  109. package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
  110. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
  111. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
  112. package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
  113. package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
  114. package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
  115. package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
  116. package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
  117. package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
  118. package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
  119. package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
  120. package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
  121. package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
  122. package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
  123. package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
  124. package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
  125. package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
  126. package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
  127. package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
  128. package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
  129. package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
  130. package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
  131. package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
  132. package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
  133. package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
  134. package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
  135. package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
  136. package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
  137. package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
  138. package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
  139. package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
  140. package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
  141. package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
  142. package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
  143. package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
  144. package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
  145. package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
  146. package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
  147. package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
  148. package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
  149. package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
  150. package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
  151. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
  152. package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
  153. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
  154. package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
  155. package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
  156. package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
  157. package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
  158. package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  159. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  160. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  161. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  162. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  163. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  164. package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  165. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  166. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  167. package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  168. package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  169. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  170. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  171. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  172. package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  173. package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  174. package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  175. package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  176. package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  177. package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  178. package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
  179. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  180. package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
  181. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
  182. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
  183. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  184. package/dist/worker/observability/interrupt-eligibility.js +0 -264
  185. package/dist/workflows/dag/contract-output-registry.js +0 -14
  186. package/dist/workflows/dag/contract-validator-registrations.js +0 -8
  187. package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
  188. package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
  189. package/dist/workflows/dag/frontend-recovery-run.js +0 -539
  190. package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
  191. package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
  192. package/dist/workflows/dag/interrupt-request.js +0 -559
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { lstat, readFile } from "node:fs/promises";
3
- import path from "node:path";
4
3
  import { ContractMutationError, applyTaskContract, logicalRevisionForState, observeTaskContractState, projectConstraintsMarkdown, projectRequirementMarkdown, sha256OfCanonicalJson, validateDraftForTask, } from "../contract/index.js";
5
4
  import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
6
5
  import { getTaskContractPaths } from "../contract/paths.js";
@@ -29,114 +28,26 @@ async function loadExistingTaskConfig(repoRoot, taskId) {
29
28
  return null;
30
29
  }
31
30
  }
32
- /**
33
- * Check whether a "## References" section in a managed 需求.md matches the
34
- * projector's canonical form: every entry is `- [role] <manifest-path>` and
35
- * every path exists in source-manifest.json. The projector itself writes
36
- * this section, so matching entries are the writer's own round-trip output
37
- * and must be accepted (2026-08-16 L1). Unknown/free-form entries stay
38
- * blocking — they indicate hand-edited provenance.
39
- */
40
- function isProjectedReferencesSection(sectionBody, manifestPaths) {
41
- const entryRe = /^- \[(?<role>[^\]]+)\] (?<ref>\S.*)$/;
42
- const lines = sectionBody
43
- .split(/\r?\n/)
44
- .map((line) => line.trim())
45
- .filter(Boolean);
46
- if (lines.length === 0)
47
- return true; // empty section: nothing to reject
48
- return lines.every((line) => {
49
- const match = entryRe.exec(line);
50
- if (!match?.groups)
51
- return false;
52
- const ref = match.groups.ref?.replace(/\\/g, "/").trim() ?? "";
53
- return manifestPaths.has(ref);
54
- });
55
- }
56
- /**
57
- * Source Resolutions canonical projector shape:
58
- * `- <sourceRef>: <conflict> → <decision> (<reason>; decidedBy=<who>)`.
59
- */
60
- function isProjectedSourceResolutionsSection(sectionBody) {
61
- const entryRe = /^- \S[^:]*: .+ → .+ \(.+; decidedBy=.+\)$/u;
62
- const lines = sectionBody
63
- .split(/\r?\n/)
64
- .map((line) => line.trim())
65
- .filter(Boolean);
66
- if (lines.length === 0)
67
- return true;
68
- return lines.every((line) => entryRe.test(line));
69
- }
70
- /** Section titles the read-back validator may inspect (static whitelist). */
71
- const MANAGED_PROJECTION_SECTION_TITLES = [
72
- "References",
73
- "Source Resolutions",
74
- ];
75
- /** Extract the body of the first `## <Title>` section, if present. */
76
- function extractSectionBody(markdown, title) {
77
- const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
78
- const match = re.exec(markdown);
79
- if (!match)
80
- return null;
81
- const rest = markdown.slice(match.index + match[0].length);
82
- const nextHeading = /^##\s+/m.exec(rest);
83
- return nextHeading ? rest.slice(0, nextHeading.index) : rest;
84
- }
85
- /** Materialized paths registered in the task's source-manifest.json. */
86
- async function loadSourceManifestPaths(requirementPath) {
87
- const manifestPath = path.join(path.dirname(requirementPath), "source-manifest.json");
88
- try {
89
- const raw = JSON.parse(await readFile(manifestPath, "utf-8"));
90
- return new Set((raw.documents ?? [])
91
- .map((doc) => doc.materializedPath?.replace(/\\/g, "/"))
92
- .filter((p) => Boolean(p)));
93
- }
94
- catch {
95
- return new Set();
96
- }
97
- }
98
31
  async function loadManagedProjectionDraft(input) {
99
32
  const config = input.existingTaskConfig;
100
33
  if (!config) {
101
34
  return { ok: false, message: "managed task config could not be loaded" };
102
35
  }
103
36
  try {
104
- const [requirementRaw, constraints] = await Promise.all([
37
+ const [requirement, constraints] = await Promise.all([
105
38
  readFile(input.requirementPath, "utf-8"),
106
39
  readFile(input.constraintsPath, "utf-8"),
107
40
  ]);
108
- // L1 (2026-08-16): the requirement projector itself writes `## References`
109
- // (from source-manifest) and `## Source Resolutions` sections into
110
- // 需求.md. The read-back validator used to reject ANY such section —
111
- // writer/reader contract drift that deadlocked every PRD import whose
112
- // draft carried references. Now accept the projector's own canonical
113
- // forms: References entries must all be `- [role] <path>` with paths
114
- // present in source-manifest.json; Source Resolutions entries must
115
- // match the projector's `- ref: conflict → decision (...)` shape.
116
- // Free-form/hand-edited entries remain blocking.
117
- const manifest = await loadSourceManifestPaths(input.requirementPath);
118
- const referencesBody = extractSectionBody(requirementRaw, "References");
119
- if (referencesBody !== null &&
120
- !isProjectedReferencesSection(referencesBody, manifest)) {
121
- return {
122
- ok: false,
123
- message: "managed requirement projection contains hand-edited or unknown References entries (not backed by source-manifest); use loop-agent task advance " +
124
- input.taskId +
125
- " --from-draft <reviewed-draft.json> --json",
126
- };
127
- }
128
- const sourceResolutionsBody = extractSectionBody(requirementRaw, "Source Resolutions");
129
- if (sourceResolutionsBody !== null &&
130
- !isProjectedSourceResolutionsSection(sourceResolutionsBody)) {
41
+ const requirementFacts = extractRequirementFactsFromMarkdown(requirement);
42
+ const constraintFacts = extractRequirementFactsFromMarkdown(constraints);
43
+ if (/^##\s+(?:References|Source Resolutions)\s*$/im.test(requirement)) {
131
44
  return {
132
45
  ok: false,
133
- message: "managed requirement projection contains hand-edited or malformed Source Resolutions entries; use loop-agent task advance " +
46
+ message: "managed requirement projection contains unsupported References or Source Resolutions; use loop-agent task advance " +
134
47
  input.taskId +
135
48
  " --from-draft <reviewed-draft.json> --json",
136
49
  };
137
50
  }
138
- const requirementFacts = extractRequirementFactsFromMarkdown(requirementRaw);
139
- const constraintFacts = extractRequirementFactsFromMarkdown(constraints);
140
51
  if (!requirementFacts.objective?.trim()) {
141
52
  return {
142
53
  ok: false,
@@ -606,14 +517,6 @@ export async function prepareTaskSource(input) {
606
517
  code: "MANAGED_PROJECTION_INCOMPLETE",
607
518
  level: "blocking",
608
519
  message: managedProjectionError,
609
- selfHealing: {
610
- action: "adopt-draft",
611
- steps: [
612
- "task status --json 读取 contract.observedCanonicalHash / observedSourceHashes,确认现有源文件状态",
613
- "从 Console drafts(<app-data>/repos/<repo>/drafts/<taskId>.json)或 semantic intake 产物(artifacts/intake/semantic-draft.json 的 draft 字段)取回结构化 draft",
614
- "补充缺失字段(scope/invariants 等)后:task advance --from-draft <reviewed-draft.json> --json",
615
- ],
616
- },
617
520
  });
618
521
  }
619
522
  // If --use-imported-prd and no parseable requirement and no AC from flags/text → blocking
@@ -13,7 +13,6 @@ import { z } from "zod";
13
13
  import { executePiStep } from "../../executors/pi-executor.js";
14
14
  import { resolveDagPiModelConfig } from "../../executors/dag-pi-executor.js";
15
15
  import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
16
- import { listPiSdkAvailableModels } from "../../executors/pi-sdk-executor.js";
17
16
  import { loadHarnessManifest } from "../../governance/harness.js";
18
17
  import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
19
18
  import { getTaskPaths } from "../runtime.js";
@@ -21,14 +20,6 @@ import { detectProductArtifactMeta } from "./artifact-meta.js";
21
20
  import { filterPlaceholderPaths } from "./placeholder-paths.js";
22
21
  export const SEMANTIC_INTAKE_ARTIFACT_REL = "artifacts/intake/semantic-draft.json";
23
22
  export const SEMANTIC_INTAKE_ATTEMPT_MARKER = "artifacts/intake/semantic-intake-attempted.json";
24
- /** Per-model wall clock for one semantic-intake Pi attempt. */
25
- export const SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS = 300_000;
26
- /** Extra Pi-catalog models after MED → HIGH → LOW. */
27
- export const SEMANTIC_INTAKE_MAX_EXTRA_MODELS = 5;
28
- /** Parent CLI budget covering harness tiers plus extra catalog models. */
29
- export const SEMANTIC_INTAKE_CLI_TIMEOUT_MS = SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS *
30
- (3 + SEMANTIC_INTAKE_MAX_EXTRA_MODELS);
31
- const NON_CHAT_MODEL = /embed|whisper|tts|dall-e|dalle|image|rerank|moderation/i;
32
23
  const productArtifactBlockers = new Set([
33
24
  "PRODUCT_ANALYSIS_NOT_EXECUTABLE",
34
25
  "PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
@@ -135,58 +126,6 @@ export function evaluateSemanticIntakeEligibility(input) {
135
126
  reason: "structural or engineering-boundary gaps with imported sources",
136
127
  };
137
128
  }
138
- function intakeModelKey(ref) {
139
- return ref.trim().replace(/\\/g, "/").toLowerCase();
140
- }
141
- function intakeModelRef(provider, id) {
142
- const trimmedId = id.trim();
143
- if (trimmedId.includes("/"))
144
- return trimmedId.replace(/\\/g, "/");
145
- return `${provider.trim()}/${trimmedId}`;
146
- }
147
- /**
148
- * MED → HIGH → LOW from harness, then Pi-supported catalog models
149
- * (credentialed first). Duplicates and non-chat ids are skipped.
150
- */
151
- export function buildSemanticIntakeModelQueue(input) {
152
- const seen = new Set();
153
- const queue = [];
154
- const push = (raw) => {
155
- const ref = raw?.trim().replace(/\\/g, "/");
156
- if (!ref)
157
- return;
158
- const key = intakeModelKey(ref);
159
- if (seen.has(key))
160
- return;
161
- seen.add(key);
162
- queue.push(ref);
163
- };
164
- push(input.harness.MED);
165
- push(input.harness.HIGH);
166
- push(input.harness.LOW);
167
- const extras = [...(input.available ?? [])]
168
- .filter((entry) => {
169
- const hay = `${entry.id} ${entry.name ?? ""} ${entry.provider}`;
170
- return !NON_CHAT_MODEL.test(hay);
171
- })
172
- .sort((left, right) => {
173
- const leftCred = left.hasCredentials === true ? 0 : 1;
174
- const rightCred = right.hasCredentials === true ? 0 : 1;
175
- if (leftCred !== rightCred)
176
- return leftCred - rightCred;
177
- return intakeModelRef(left.provider, left.id).localeCompare(intakeModelRef(right.provider, right.id));
178
- });
179
- let extraCount = 0;
180
- for (const entry of extras) {
181
- if (extraCount >= SEMANTIC_INTAKE_MAX_EXTRA_MODELS)
182
- break;
183
- const before = queue.length;
184
- push(intakeModelRef(entry.provider, entry.id));
185
- if (queue.length > before)
186
- extraCount += 1;
187
- }
188
- return queue;
189
- }
190
129
  export function extractJsonObject(text) {
191
130
  const fenced = /```(?:json)?\s*([\s\S]*?)```/iu.exec(text);
192
131
  const candidate = fenced?.[1]?.trim() || text.trim();
@@ -554,7 +493,6 @@ export async function runSemanticIntake(input) {
554
493
  };
555
494
  }
556
495
  let assistantText;
557
- let attemptedModels = [];
558
496
  if (input.fixtureAssistantText !== undefined) {
559
497
  assistantText = input.fixtureAssistantText;
560
498
  }
@@ -562,33 +500,7 @@ export async function runSemanticIntake(input) {
562
500
  const execute = input.executePi ?? executePiStep;
563
501
  const manifest = await loadHarnessManifest(input.repoRoot);
564
502
  const models = resolveExecutorModelMatrices(manifest);
565
- let available = [];
566
- try {
567
- available = input.listAvailableModels
568
- ? await input.listAvailableModels()
569
- : await listPiSdkAvailableModels();
570
- }
571
- catch {
572
- available = [];
573
- }
574
- const queue = buildSemanticIntakeModelQueue({
575
- harness: models.pi,
576
- available,
577
- });
578
- if (queue.length === 0) {
579
- await writeAttemptMarker(input.repoRoot, input.taskId, {
580
- ok: false,
581
- code: "SEMANTIC_INTAKE_PI_FAILED",
582
- failureCategory: "unavailable",
583
- at: new Date().toISOString(),
584
- attemptedModels: [],
585
- });
586
- return {
587
- ok: false,
588
- code: "SEMANTIC_INTAKE_PI_FAILED",
589
- message: "semantic intake has no MED/HIGH/LOW or Pi-supported models to try",
590
- };
591
- }
503
+ const model = models.pi.MED;
592
504
  const attached = usableDocs
593
505
  .map((d) => d.absolutePath)
594
506
  .filter((p) => Boolean(p));
@@ -598,71 +510,39 @@ export async function runSemanticIntake(input) {
598
510
  documents: usableDocs,
599
511
  repoRoot: input.repoRoot,
600
512
  });
601
- const attempted = [];
602
- let lastFailure = "";
603
- let chosenText;
604
- for (const modelRef of queue) {
605
- try {
606
- const result = await execute({
607
- attachedFiles: attached,
608
- modelConfig: resolveDagPiModelConfig(modelRef),
609
- prompt,
610
- repoRoot: input.repoRoot,
611
- step: "analyze",
612
- toolNames: ["read", "grep", "find", "ls"],
613
- timeoutMs: SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS,
614
- stallTimeoutMs: SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS,
615
- userMessage: "Structure the imported requirement documents into the required JSON only.",
616
- validateOutput: (text) => {
617
- try {
618
- const parsed = parseSemanticRequirementDraft(text);
619
- assertSemanticDraftAcceptable(parsed);
620
- return [];
621
- }
622
- catch (error) {
623
- return [error instanceof Error ? error.message : String(error)];
624
- }
625
- },
626
- });
627
- attempted.push({
628
- model: result.modelDisplay || modelRef,
629
- ok: result.ok,
630
- failureCategory: result.failureCategory,
631
- timedOut: result.timedOut,
632
- });
633
- if (result.ok) {
634
- chosenText = result.assistantText;
635
- break;
513
+ const result = await execute({
514
+ attachedFiles: attached,
515
+ modelConfig: resolveDagPiModelConfig(model),
516
+ prompt,
517
+ repoRoot: input.repoRoot,
518
+ step: "analyze",
519
+ toolNames: ["read", "grep", "find", "ls"],
520
+ userMessage: "Structure the imported requirement documents into the required JSON only.",
521
+ validateOutput: (text) => {
522
+ try {
523
+ const parsed = parseSemanticRequirementDraft(text);
524
+ assertSemanticDraftAcceptable(parsed);
525
+ return [];
636
526
  }
637
- lastFailure =
638
- `${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000);
639
- }
640
- catch (error) {
641
- const message = error instanceof Error ? error.message : String(error);
642
- attempted.push({
643
- model: modelRef,
644
- ok: false,
645
- failureCategory: "unknown",
646
- });
647
- lastFailure = message.slice(0, 2000);
648
- }
649
- }
650
- attemptedModels = attempted;
651
- if (chosenText === undefined) {
527
+ catch (error) {
528
+ return [error instanceof Error ? error.message : String(error)];
529
+ }
530
+ },
531
+ });
532
+ if (!result.ok) {
652
533
  await writeAttemptMarker(input.repoRoot, input.taskId, {
653
534
  ok: false,
654
535
  code: "SEMANTIC_INTAKE_PI_FAILED",
655
- failureCategory: attempted.at(-1)?.failureCategory,
536
+ failureCategory: result.failureCategory,
656
537
  at: new Date().toISOString(),
657
- attemptedModels: attempted,
658
538
  });
659
539
  return {
660
540
  ok: false,
661
541
  code: "SEMANTIC_INTAKE_PI_FAILED",
662
- message: `semantic intake pi failed after ${attempted.length} model(s): ${lastFailure}`.slice(0, 2000),
542
+ message: `semantic intake pi failed: ${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000),
663
543
  };
664
544
  }
665
- assistantText = chosenText;
545
+ assistantText = result.assistantText;
666
546
  }
667
547
  try {
668
548
  const semantic = parseSemanticRequirementDraft(assistantText);
@@ -696,7 +576,6 @@ export async function runSemanticIntake(input) {
696
576
  code: "SEMANTIC_INTAKE_OK",
697
577
  artifactPath: SEMANTIC_INTAKE_ARTIFACT_REL,
698
578
  at: new Date().toISOString(),
699
- ...(attemptedModels.length > 0 ? { attemptedModels } : {}),
700
579
  });
701
580
  return { ok: true, draft, artifactPath, semantic };
702
581
  }
@@ -19,12 +19,5 @@ export function mergeArtifactCardState(current, incoming) {
19
19
  const index = current.findIndex((item) => item.artifactId === incoming.artifactId);
20
20
  if (index < 0)
21
21
  return [...current, incoming];
22
- return current.map((item, candidate) => candidate === index
23
- ? {
24
- ...item,
25
- ...incoming,
26
- eventSeq: item.eventSeq ?? incoming.eventSeq,
27
- activityRef: item.activityRef ?? incoming.activityRef,
28
- }
29
- : item);
22
+ return current.map((item, candidate) => candidate === index ? { ...item, ...incoming } : item);
30
23
  }
@@ -50,62 +50,6 @@ function controllerSummary(operation) {
50
50
  .join("@");
51
51
  return safeIdentity(label);
52
52
  }
53
- /** Re-project an already-safe stored run summary through a bounded re-check. */
54
- function safeRunSummaryProjection(value) {
55
- if (!value || typeof value !== "object")
56
- return undefined;
57
- const record = value;
58
- const runId = safeIdentity(record.runId);
59
- const status = safeIdentity(record.status);
60
- if (!runId || !status)
61
- return undefined;
62
- const nodes = Array.isArray(record.nodes)
63
- ? record.nodes
64
- .filter((node) => Boolean(node && typeof node === "object"))
65
- .map((node) => ({
66
- id: safeIdentity(node.id) ?? "",
67
- status: safeIdentity(node.status) ?? "",
68
- }))
69
- .filter((node) => node.id && node.status)
70
- .slice(0, 20)
71
- : [];
72
- const verdict = typeof record.verdict === "string"
73
- ? boundedRedactedText(record.verdict)
74
- : undefined;
75
- const failureCategory = typeof record.failureCategory === "string"
76
- ? boundedRedactedText(record.failureCategory)
77
- : undefined;
78
- const outOfBounds = Array.isArray(record.outOfBounds)
79
- ? record.outOfBounds
80
- .filter((entry) => Boolean(entry && typeof entry === "object"))
81
- .map((entry) => ({
82
- nodeId: safeIdentity(entry.nodeId) ?? "",
83
- failureCategory: safeIdentity(entry.failureCategory) ?? "",
84
- }))
85
- .filter((entry) => entry.nodeId && entry.failureCategory)
86
- .slice(0, 20)
87
- : undefined;
88
- const nextRec = record.next && typeof record.next === "object"
89
- ? record.next
90
- : undefined;
91
- const next = nextRec
92
- ? {
93
- kind: safeIdentity(nextRec.kind) ?? "",
94
- description: typeof nextRec.description === "string"
95
- ? boundedRedactedText(nextRec.description)
96
- : undefined,
97
- }
98
- : undefined;
99
- return {
100
- runId,
101
- status,
102
- nodes,
103
- ...(verdict ? { verdict } : {}),
104
- ...(failureCategory ? { failureCategory } : {}),
105
- ...(outOfBounds && outOfBounds.length > 0 ? { outOfBounds } : {}),
106
- ...(next && next.kind ? { next } : {}),
107
- };
108
- }
109
53
  /** Chat/browser-safe projection. Never includes actionParams, CLI args, paths or raw results. */
110
54
  export function projectOperationForChat(operation) {
111
55
  const params = operation.actionParams ?? {};
@@ -117,16 +61,12 @@ export function projectOperationForChat(operation) {
117
61
  const featureId = safeIdentity(params.featureId);
118
62
  const workerRunId = safeIdentity(params.workerRunId);
119
63
  const controller = controllerSummary(operation);
120
- const runSummary = safeRunSummaryProjection(operation.runSummary);
121
64
  const previewHash = createHash("sha256")
122
65
  .update(JSON.stringify({
123
66
  stdoutPreview,
124
67
  stderrPreview,
125
68
  summaryPreview,
126
69
  state: operation.state,
127
- // runSummary participates in the dedupe hash: terminal summary updates
128
- // must not be swallowed by state+previewHash dedup (2026-08-14).
129
- runSummary: runSummary ?? null,
130
70
  }))
131
71
  .digest("hex");
132
72
  return {
@@ -148,76 +88,6 @@ export function projectOperationForChat(operation) {
148
88
  ...(operation.errorCode
149
89
  ? { errorCode: safeIdentity(operation.errorCode) }
150
90
  : {}),
151
- ...(runSummary ? { runSummary } : {}),
152
- previewHash,
153
- };
154
- }
155
- /** Action families that leave a user-inspectable durable outcome. */
156
- const ARTIFACT_EMITTING_ACTIONS = new Set([
157
- "taskAdvance",
158
- "taskCreate",
159
- "contractApply",
160
- "dagReport",
161
- "taskImportPrd",
162
- "importPrd",
163
- ]);
164
- /**
165
- * Project a terminal operation into an「产物」rail card (2026-08-16).
166
- * Mirrors `ArtifactCardState` (schemaVersion 1) so the existing frontend
167
- * parser renders it without any change. Best-effort: operations without a
168
- * durable outcome (pure status reads) return undefined.
169
- */
170
- function artifactFromTerminalOperation(operation, projection) {
171
- if (!ARTIFACT_EMITTING_ACTIONS.has(operation.action))
172
- return undefined;
173
- // Failed/interrupted actions leave nothing inspectable as a deliverable.
174
- if (operation.state !== "succeeded")
175
- return undefined;
176
- const taskId = projection.taskId;
177
- const kind = taskId
178
- ? "contract"
179
- : "operation";
180
- const title = taskId ? `任务 ${taskId}` : `执行 ${operation.action}`;
181
- const summary = projection.runSummary?.status
182
- ? `运行 ${projection.runSummary.runId} · ${projection.runSummary.status}`
183
- : projection.summaryPreview || undefined;
184
- const reportLines = [];
185
- if (projection.runSummary) {
186
- const rs = projection.runSummary;
187
- reportLines.push(`运行: ${rs.runId}`);
188
- reportLines.push(`状态: ${rs.status}`);
189
- if (rs.verdict)
190
- reportLines.push(`结论: ${rs.verdict}`);
191
- if (rs.failureCategory)
192
- reportLines.push(`失败归类: ${rs.failureCategory}`);
193
- if (Array.isArray(rs.nodes)) {
194
- for (const node of rs.nodes.slice(0, 30)) {
195
- reportLines.push(`- ${node.id}: ${node.status}`);
196
- }
197
- }
198
- }
199
- if (projection.stdoutPreview) {
200
- reportLines.push("", "--- stdout 摘要 ---", projection.stdoutPreview);
201
- }
202
- const previewHash = createHash("sha256")
203
- .update(JSON.stringify({
204
- operationId: operation.operationId,
205
- state: operation.state,
206
- taskId: taskId ?? null,
207
- runSummary: projection.runSummary ?? null,
208
- stdoutPreview: projection.stdoutPreview ?? null,
209
- }))
210
- .digest("hex");
211
- return {
212
- schemaVersion: 1,
213
- kind,
214
- artifactId: `operation-${operation.operationId}`,
215
- title,
216
- ...(summary ? { summary } : {}),
217
- ...(reportLines.length > 0
218
- ? { reportText: reportLines.join("\n").slice(0, 8000) }
219
- : {}),
220
- inspectHref: projection.inspectHref,
221
91
  previewHash,
222
92
  };
223
93
  }
@@ -323,7 +193,6 @@ export function projectChatToolEventPayload(input) {
323
193
  resultPreview: preview,
324
194
  previewHash,
325
195
  isError: Boolean(input.isError),
326
- ...(input.operationId ? { operationId: input.operationId } : {}),
327
196
  };
328
197
  }
329
198
  export function projectOperationEventSummary(event) {
@@ -481,27 +350,6 @@ export function createChatOperationLinker(options) {
481
350
  },
482
351
  });
483
352
  }
484
- // Artifact projection (2026-08-16): the sidebar「产物」tab was permanently
485
- // empty because nothing in the server ever emitted `artifact-ref`. On a
486
- // terminal operation with a durable outcome (task created / advanced /
487
- // contract applied / report produced) project one artifact card so the
488
- // user can inspect the deliverable straight from the rail.
489
- if (isTerminalOperationState(operation.state)) {
490
- const artifact = artifactFromTerminalOperation(operation, projection);
491
- if (artifact) {
492
- const artifactDuplicate = options.chatEvents
493
- .snapshot(input.sessionId)
494
- .some((event) => event.kind === "artifact-ref" &&
495
- event.data
496
- .artifact?.previewHash === artifact.previewHash);
497
- if (!artifactDuplicate) {
498
- options.chatEvents.append(input.sessionId, input.turnId, {
499
- kind: "artifact-ref",
500
- data: { artifact },
501
- });
502
- }
503
- }
504
- }
505
353
  if (isTerminalOperationState(operation.state)) {
506
354
  const key = keyFor(input.sessionId, input.operationId);
507
355
  followers.get(key)?.();
@@ -32,14 +32,6 @@ export function mergeHumanGateCardState(cards, next) {
32
32
  if (index < 0)
33
33
  return [...cards, next];
34
34
  const copy = [...cards];
35
- const current = copy[index];
36
- if (!current)
37
- return [...cards, next];
38
- copy[index] = {
39
- ...current,
40
- ...next,
41
- eventSeq: current.eventSeq ?? next.eventSeq,
42
- activityRef: current.activityRef ?? next.activityRef,
43
- };
35
+ copy[index] = next;
44
36
  return copy;
45
37
  }
@@ -2,24 +2,7 @@ export function mergeOperationCardState(current, incoming) {
2
2
  const index = current.findIndex((operation) => operation.operationId === incoming.operationId);
3
3
  if (index < 0)
4
4
  return [...current, incoming];
5
- return current.map((operation, candidateIndex) => {
6
- if (candidateIndex !== index)
7
- return operation;
8
- let eventSeq = operation.eventSeq;
9
- if (eventSeq === undefined)
10
- eventSeq = incoming.eventSeq;
11
- else if (incoming.eventSeq !== undefined) {
12
- eventSeq = Math.min(eventSeq, incoming.eventSeq);
13
- }
14
- return {
15
- ...operation,
16
- ...incoming,
17
- ...(eventSeq === undefined ? {} : { eventSeq }),
18
- updatedSeq: Math.max(operation.updatedSeq ?? operation.eventSeq ?? 0, incoming.updatedSeq ?? incoming.eventSeq ?? 0),
19
- toolCallId: operation.toolCallId ?? incoming.toolCallId,
20
- activityRef: operation.activityRef ?? incoming.activityRef,
21
- };
22
- });
5
+ return current.map((operation, candidateIndex) => candidateIndex === index ? { ...operation, ...incoming } : operation);
23
6
  }
24
7
  export function operationFromEventPayload(payload) {
25
8
  const operation = payload.operation;
@@ -36,57 +19,5 @@ export function operationFromEventPayload(payload) {
36
19
  typeof candidate.previewHash !== "string") {
37
20
  return undefined;
38
21
  }
39
- // Optional runSummary stays backward-compatible: validate shape field by
40
- // field and drop it entirely when malformed (legacy events simply lack it).
41
- const safe = safeRunSummary(candidate.runSummary);
42
- const { runSummary: _unsafeRunSummary, ...base } = candidate;
43
- return {
44
- ...base,
45
- ...(safe ? { runSummary: safe } : {}),
46
- };
47
- }
48
- /** Field-by-field validation for the optional browser-side runSummary. */
49
- function safeRunSummary(value) {
50
- if (!value || typeof value !== "object" || Array.isArray(value))
51
- return undefined;
52
- const rec = value;
53
- if (typeof rec.runId !== "string" || !rec.runId)
54
- return undefined;
55
- if (typeof rec.status !== "string" || !rec.status)
56
- return undefined;
57
- let nodes = [];
58
- if (Array.isArray(rec.nodes)) {
59
- nodes = rec.nodes
60
- .flatMap((node) => {
61
- if (!node || typeof node !== "object")
62
- return [];
63
- const id = String(node.id ?? "");
64
- const status = String(node.status ?? "");
65
- if (!id || !status)
66
- return [];
67
- return [{ id, status }];
68
- })
69
- .slice(0, 20);
70
- }
71
- let next;
72
- if (rec.next && typeof rec.next === "object") {
73
- const nextRecord = rec.next;
74
- next = {
75
- kind: String(nextRecord.kind ?? ""),
76
- ...(typeof nextRecord.description === "string"
77
- ? { description: nextRecord.description }
78
- : {}),
79
- };
80
- }
81
- const out = {
82
- runId: rec.runId,
83
- status: rec.status,
84
- nodes,
85
- ...(typeof rec.verdict === "string" ? { verdict: rec.verdict } : {}),
86
- ...(typeof rec.failureCategory === "string"
87
- ? { failureCategory: rec.failureCategory }
88
- : {}),
89
- ...(next && next.kind ? { next } : {}),
90
- };
91
- return out;
22
+ return candidate;
92
23
  }