@tea-agent/loop-agent 0.44.0-next.12 → 0.44.0-next.14

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 (188) hide show
  1. package/CHANGELOG.md +27 -2
  2. package/README.md +12 -12
  3. package/dist/adapters/loop-agent.js +34 -0
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/cli/command-definitions.js +5 -5
  6. package/dist/cli/program.js +1 -4
  7. package/dist/commands/init-legacy-audit.js +330 -0
  8. package/dist/commands/init-upgrade.js +644 -104
  9. package/dist/commands/init.js +686 -330
  10. package/dist/commands/operator-guide.js +99 -0
  11. package/dist/commands/operator.js +16 -3
  12. package/dist/executors/dag-pi-executor.js +11 -6
  13. package/dist/executors/pi-sdk-executor.js +3 -1
  14. package/dist/executors/pi-writer-tool-policy.js +60 -4
  15. package/dist/executors/playwright-cli-launcher.js +31 -45
  16. package/dist/executors/shell-write-guard.js +24 -67
  17. package/dist/governance/exec-plans.js +10 -1
  18. package/dist/governance/harness.js +2 -7
  19. package/dist/governance/manifest-types.js +28 -9
  20. package/dist/governance/project-capability-scanner.js +263 -0
  21. package/dist/governance/project-config.js +96 -0
  22. package/dist/governance/resource-catalog.js +151 -0
  23. package/dist/infrastructure/console/app-data.js +6 -2
  24. package/dist/shared/operator/capabilities.js +2 -2
  25. package/dist/shared/package-metadata.js +23 -7
  26. package/dist/shared/pi-context-pressure/extension.js +23 -0
  27. package/dist/shared/pi-context-pressure/index.js +1 -0
  28. package/dist/shared/pi-context-pressure/telemetry.js +4 -0
  29. package/dist/shared/pi-context-pressure/tool-result-pruner.js +98 -0
  30. package/dist/shared/update/npm-client.js +49 -1
  31. package/dist/task/config-types.js +4 -2
  32. package/dist/task/source-prepare/build-draft.js +1 -0
  33. package/dist/task/source-prepare/path-policy.js +17 -7
  34. package/dist/worker/cli.js +3 -5
  35. package/dist/worker/console/chat/browser-automation.js +455 -41
  36. package/dist/worker/console/chat/browser-content.js +74 -0
  37. package/dist/worker/console/chat/browser-diagnostics.js +250 -0
  38. package/dist/worker/console/chat/browser-evaluation.js +58 -0
  39. package/dist/worker/console/chat/browser-operation-queue.js +19 -0
  40. package/dist/worker/console/chat/browser-pick.js +113 -0
  41. package/dist/worker/console/chat/browser-routes.js +54 -5
  42. package/dist/worker/console/chat/browser-screenshot-result.js +47 -0
  43. package/dist/worker/console/chat/browser-storage.js +89 -0
  44. package/dist/worker/console/chat/chat-event-store.js +7 -6
  45. package/dist/worker/console/chat/context-insights.js +84 -19
  46. package/dist/worker/console/chat/pet/pet-affinity.js +169 -0
  47. package/dist/worker/console/chat/pet/pet-persist.js +109 -0
  48. package/dist/worker/console/chat/pet/pet-registry.js +142 -0
  49. package/dist/worker/console/chat/pet/pet-remarks.js +48 -0
  50. package/dist/worker/console/chat/pet/pet-routes.js +125 -0
  51. package/dist/worker/console/chat/pet/pet-service.js +262 -0
  52. package/dist/worker/console/chat/pet/pet-state.js +99 -0
  53. package/dist/worker/console/chat/pi-mode-loop-isolation.js +17 -2
  54. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +125 -174
  55. package/dist/worker/console/chat/pi-runtime.js +8 -3
  56. package/dist/worker/console/chat/repo-browser.js +80 -2
  57. package/dist/worker/console/chat/repo-preview-kind.js +155 -0
  58. package/dist/worker/console/chat/routes.js +43 -7
  59. package/dist/worker/console/chat/scheduled-goal-request.js +14 -6
  60. package/dist/worker/console/chat/session-mode.js +1 -1
  61. package/dist/worker/console/chat/session-store.js +24 -5
  62. package/dist/worker/console/chat/tools.js +12 -0
  63. package/dist/worker/console/console-shutdown.js +34 -0
  64. package/dist/worker/console/server.js +2 -1
  65. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-r1NAPU_5.js → abnfDiagram-N423BO3Z-DASc69AM.js} +1 -1
  66. package/dist/worker/console/static/assets/{arc-dT5T4sVL.js → arc-h09p-LAc.js} +1 -1
  67. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP_cOq46.js → architectureDiagram-T3A2C74G-CZ3dtLkh.js} +1 -1
  68. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-sToEZxEf.js → blockDiagram-VBNYF7ZC-DMrRhMXG.js} +1 -1
  69. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DSxwwi2m.js → c4Diagram-5PPSVZJV-DQxisHV6.js} +1 -1
  70. package/dist/worker/console/static/assets/channel-D1zgKZfg.js +1 -0
  71. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B9fh0IkF.js → chunk-2GRJ4B5K-CefVN9zh.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CAUNl-A6.js → chunk-2Q5K7J3B-DmR1Koq2.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BomeqbNX.js → chunk-5RXB4S5H-NlzrSW7L.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BNAuTOLE.js → chunk-5VM5RSS4-DfelJv-0.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DfzjRD5y.js → chunk-6Q2QTUOP-BK6RFy58.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C3uE-yc2.js → chunk-GF5L2VYU-BAeh9lz4.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-C3sQMMYr.js → chunk-JWPE2WC7-ChPS5WzG.js} +1 -1
  78. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D_AtEYQL.js → chunk-KBJHAD2P-BoVe5IM6.js} +1 -1
  79. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DW4TwhKv.js → chunk-RYQCIY6F-C4q_0Lw9.js} +1 -1
  80. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CTQDmJcp.js → chunk-XXDRQBXY-Bb9TvjeY.js} +1 -1
  81. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CXNNsY2M.js +1 -0
  82. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CXNNsY2M.js +1 -0
  83. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-iMqPuBsL.js → cose-bilkent-JH36ORCC-DmNnL8Ff.js} +1 -1
  84. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DktIuDWs.js → cynefin-VYW2F7L2-Contvx58.js} +1 -1
  85. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BgVnjyEs.js → cynefinDiagram-MW4NZA55-Cigq7cjk.js} +1 -1
  86. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CR25E4bu.js → dagre-VZM6K2ZE-dEbC0nxb.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CPM2h5HY.js → diagram-7IWD3JNH-CzIztESV.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DqF0x2g1.js → diagram-B4RE2ZJO-Bo14zbaY.js} +1 -1
  89. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-G8BYp2eb.js → diagram-LBJQPF4R-C4xUyil6.js} +1 -1
  90. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BbC4kT_D.js → diagram-Q27KOJAE-CaBY_uYa.js} +1 -1
  91. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CKZwZ-G5.js → diagram-UB23O5K3-ClUXUeiu.js} +1 -1
  92. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-kJDmheia.js → ebnfDiagram-BXEA7PRR-BbGCgjC6.js} +1 -1
  93. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DFTVHyxB.js → erDiagram-JOGREHBK-Dr-AcwSl.js} +1 -1
  94. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BqdN5_os.js → flowDiagram-UKHOOZJN-BG8Gcfyc.js} +1 -1
  95. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-SYDjVakh.js → ganttDiagram-PKOTCBZU-CFVaPs5r.js} +1 -1
  96. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js → gitGraphDiagram-DS77QQ5N-ChJ8pm-O.js} +1 -1
  97. package/dist/worker/console/static/assets/index-B_5GhVIO.js +488 -0
  98. package/dist/worker/console/static/assets/index-C7YMBluX.css +1 -0
  99. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DIeAmJ45.js → infoDiagram-6WML65LV-C5J9-uCZ.js} +1 -1
  100. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-oikqUAlo.js → ishikawaDiagram-WSZJBQD7-BcfjqzDv.js} +1 -1
  101. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-mKEF_xYT.js → journeyDiagram-NVQOT4AX-BzzCO-rD.js} +1 -1
  102. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BdWJHPnN.js → kanban-definition-27J2QSJJ-NCHUZ46h.js} +1 -1
  103. package/dist/worker/console/static/assets/{linear-DGRlieMH.js → linear-BKZlWCUU.js} +1 -1
  104. package/dist/worker/console/static/assets/{mermaid.core-4P9yPC9c.js → mermaid.core-DbTaQjFE.js} +5 -5
  105. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-5PTSkos6.js → mindmap-definition-FAOFIHXS-CroYl5xQ.js} +1 -1
  106. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DONIFpG5.js → pegDiagram-VL7TDLO6-DgMBx-9k.js} +1 -1
  107. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-D_Z4S70M.js → pieDiagram-7S7Q4E2Y-CyWDmbq1.js} +1 -1
  108. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CAsjh8iY.js → quadrantDiagram-CIZ2JOQS-BgGEOqSP.js} +1 -1
  109. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Cx4Dd6ac.js → railroadDiagram-AXF67PYL-BMZ7oeeV.js} +1 -1
  110. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-cQb7mwyH.js → requirementDiagram-LRYGKXZP-DxqyGhcc.js} +1 -1
  111. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cd-DhUdf.js → sankeyDiagram-W5VNT64P-DnSvNCG8.js} +1 -1
  112. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-AlKYRgIk.js → sequenceDiagram-SI44F4Z6-BiZh6f-R.js} +1 -1
  113. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CUy_ET4e.js → sizeCapture-X5ZJPWSS-BKXSI8WB.js} +1 -1
  114. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CTuVe6Sa.js → stateDiagram-OKZ733FA-CfSgoYFm.js} +1 -1
  115. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DgHi3vjb.js +1 -0
  116. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-ChFLJb-v.js → swimlanes-SLNWSIFB-CyGADcHe.js} +2 -2
  117. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bgvdt1Y0.js +8 -0
  118. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DZZeiAPr.js → timeline-definition-Z64GVDOM-HmPaSPOK.js} +1 -1
  119. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-t4O91cZU.js → vennDiagram-T6HMQDX7-VA2VBbez.js} +1 -1
  120. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CV5qGHZu.js → wardleyDiagram-T6FBY63Y-BxjiBPZa.js} +1 -1
  121. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DyXbytUI.js → xychartDiagram-ELKLHX3M--hUY-qFj.js} +1 -1
  122. package/dist/worker/console/static/index.html +2 -2
  123. package/dist/worker/console/static/pets/ember/pet.json +167 -0
  124. package/dist/worker/console/static/pets/ember/spritesheet.png +0 -0
  125. package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +4 -3
  126. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +57 -9
  127. package/dist/worker/console/static-src/operator-chat/use-stat-dialog.js +95 -0
  128. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +6 -0
  129. package/dist/worker/console/workspace-context.js +15 -0
  130. package/dist/worker/console/workspace-initialization.js +25 -16
  131. package/dist/worker/delivery/package.js +4 -1
  132. package/dist/worker/materialize/harness-task-lineage.js +4 -2
  133. package/dist/worker/observe/static/console-theme.css +33 -29
  134. package/dist/worker/observe/static/operator-chrome.js +1 -42
  135. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +3 -1
  136. package/dist/workflows/dag/backend-test-layout.js +27 -7
  137. package/dist/workflows/dag/backend-test-markdown-workflow.js +3 -1
  138. package/dist/workflows/dag/backend-test-pytest-collection.js +4 -1
  139. package/dist/workflows/dag/final-verification.js +45 -10
  140. package/dist/workflows/dag/frontend-test-case-checklist.js +12 -3
  141. package/dist/workflows/dag/frontend-test-environment-probe.js +0 -2
  142. package/dist/workflows/dag/frontend-test-layout.js +41 -12
  143. package/dist/workflows/dag/hybrid/sources.js +35 -16
  144. package/dist/workflows/dag/hybrid/templates/backend-test.js +70 -55
  145. package/dist/workflows/dag/hybrid/templates/frontend-test.js +62 -41
  146. package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +6 -6
  147. package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +5 -5
  148. package/dist/workflows/dag/skill-instructions.js +102 -32
  149. package/dist/workflows/dag/skill-snapshot.js +3 -0
  150. package/dist/workflows/dag/skills.js +7 -7
  151. package/dist/workflows/dag/test-artifact-namespace.js +73 -0
  152. package/dist/workflows/dag/validate.js +24 -5
  153. package/dist/workflows/dag/workspace-checkpoint.js +13 -1
  154. package/docs/architecture/runtime-boundaries.md +28 -22
  155. package/docs/init-surface.manifest.json +43 -295
  156. package/docs/templates/README.md +4 -4
  157. package/docs/templates/agent-dag.base.json +9 -9
  158. package/docs/templates/agent-dag.final-verification.json +6 -6
  159. package/docs/templates/agent-dag.supervised-implementation.json +12 -12
  160. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +3 -3
  161. package/docs/templates/backend-test-dag.json +708 -708
  162. package/docs/templates/backend-test-dag.review-cases.prompt.md +3 -3
  163. package/docs/templates/frontend-implementation-dag.json +2 -2
  164. package/docs/templates/frontend-test-case-checklist.md +2 -2
  165. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +7 -7
  166. package/docs/templates/frontend-test-dag.json +48 -39
  167. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +2 -2
  168. package/docs/templates/frontend-test-dag.retrospect.prompt.md +1 -1
  169. package/docs/templates/frontend-test-dag.review-cases.prompt.md +1 -1
  170. package/docs/templates/hybrid-dag.json +9 -9
  171. package/docs/templates/knowledge-sync-dag.json +8 -8
  172. package/package.json +12 -3
  173. package/resource-catalog.v1.json +1973 -0
  174. package/resources/agent-bridge/loop-agent/SKILL.md +40 -0
  175. package/resources/init-history/legacy-surface.v1.json +33 -0
  176. package/skills/fe-test-ui-scout/SKILL.md +4 -4
  177. package/skills/fe-test-ui-scout/references/ledger-schema.md +1 -1
  178. package/skills/init-update/SKILL.md +34 -0
  179. package/skills/init-update/agents/openai.yaml +6 -0
  180. package/skills/playwright-cli/SKILL.md +1 -1
  181. package/skills/playwright-cli-case-generator/SKILL.md +4 -4
  182. package/dist/worker/console/static/assets/channel-CyBY_yGk.js +0 -1
  183. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +0 -1
  184. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +0 -1
  185. package/dist/worker/console/static/assets/index-B28Onmfy.css +0 -1
  186. package/dist/worker/console/static/assets/index-CTMZsDmh.js +0 -486
  187. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +0 -1
  188. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +0 -8
@@ -1,17 +1,20 @@
1
1
  import { shouldRewriteAssistantTransientProviderError } from "../pi-provider-recovery.js";
2
2
  import { buildOverflowCheckpoint } from "./checkpoint.js";
3
+ import { prunePiToolResultMessages, } from "./tool-result-pruner.js";
3
4
  import { shouldRewriteAssistantOverflow } from "./overflow.js";
4
5
  import { clearRetrieveBypass, compressProviderPayload, createSiftRetrieveToolDefinition, SIFT_RETRIEVE_TOOL_NAME, } from "./sift-bridge.js";
5
6
  import { emptyContextPressureCounters, incrementCounter, } from "./telemetry.js";
6
7
  export function createCompactionController() {
7
8
  return {
8
9
  proactiveCompaction: false,
10
+ pruneArmed: false,
9
11
  counters: emptyContextPressureCounters(),
10
12
  };
11
13
  }
12
14
  export function createContextPressureExtension(options) {
13
15
  const l2 = options.policy.mode !== "off";
14
16
  const l1 = Boolean(options.sift);
17
+ const pruner = l2 && options.prune !== undefined;
15
18
  const transientRecovery = options.normalizeTransientProviderErrors === true;
16
19
  if (!l2 && !l1 && !transientRecovery)
17
20
  return undefined;
@@ -54,6 +57,8 @@ export function createContextPressureExtension(options) {
54
57
  const reason = event.reason;
55
58
  if (reason === "overflow") {
56
59
  incrementCounter(options.controller.counters, "overflow");
60
+ if (pruner)
61
+ options.controller.pruneArmed = true;
57
62
  }
58
63
  if (reason === "manual") {
59
64
  incrementCounter(options.controller.counters, "manual");
@@ -79,6 +84,24 @@ export function createContextPressureExtension(options) {
79
84
  };
80
85
  });
81
86
  }
87
+ if (pruner) {
88
+ api.on("context", (event) => {
89
+ if (!options.controller.pruneArmed)
90
+ return undefined;
91
+ const messages = Array.isArray(event.messages) ? event.messages : [];
92
+ try {
93
+ const result = prunePiToolResultMessages(messages, options.prune);
94
+ if (!result.changed)
95
+ return undefined;
96
+ options.controller.counters.toolResultPruned += result.messagesPruned;
97
+ options.controller.counters.charsPrunedEstimate += result.charsRemoved;
98
+ return { messages: result.messages };
99
+ }
100
+ catch {
101
+ return undefined;
102
+ }
103
+ });
104
+ }
82
105
  if (l1 && options.sift) {
83
106
  const sift = options.sift;
84
107
  api.on("before_provider_request", (event) => {
@@ -1,6 +1,7 @@
1
1
  export { isContextOverflow, CONTEXT_OVERFLOW_PHRASES, OVERFLOW_FIXTURES, NORMALIZED_OVERFLOW_PREFIX, shouldRewriteAssistantOverflow, normalizeOverflowErrorMessage, buildGeneratedOverflowClassifierSource, } from "./overflow.js";
2
2
  export { parseCompactionPolicy, deriveEffectiveWindow, conservativeTokenEstimate, projectPromptPressure, applyProcessCompactionOverride, inMemoryCompactionSettings, PI_DEFAULT_RESERVE_TOKENS, DEFAULT_KEEP_RECENT_TOKENS, } from "./compaction-policy.js";
3
3
  export { buildOverflowCheckpoint } from "./checkpoint.js";
4
+ export { PI_TOOL_RESULT_PRUNE_MARKER, DEFAULT_PI_TOOL_RESULT_PRUNE_CONFIG, resolvePiToolResultPruneConfig, parsePiToolResultPruneConfig, piCodePointLength, prunePiToolResultMessages, } from "./tool-result-pruner.js";
4
5
  export { createContextPressureExtension, createCompactionController, } from "./extension.js";
5
6
  export { createSiftBridgeState, parseSiftBridgeConfig, compressProviderPayload, executeSiftRetrieve, housekeepSiftStash, createSiftRetrieveToolDefinition, SIFT_RETRIEVE_TOOL_NAME, SIFT_POLICY_VERSION, } from "./sift-bridge.js";
6
7
  export { isConsoleSiftEnabled, isDagSiftEnabled, readContextPressureMode, parseEnabledFlag, parseContextPressureMode, } from "./env.js";
@@ -9,6 +9,8 @@ export function emptyContextPressureCounters() {
9
9
  l0CompactFailed: 0,
10
10
  tokensSavedEstimate: 0,
11
11
  blocksCompressed: 0,
12
+ toolResultPruned: 0,
13
+ charsPrunedEstimate: 0,
12
14
  };
13
15
  }
14
16
  export function incrementCounter(counters, reason, amount = 1) {
@@ -47,5 +49,7 @@ export function projectSafeTelemetry(counters) {
47
49
  failOpen: counters.failOpen,
48
50
  tokensSavedEstimate: counters.tokensSavedEstimate,
49
51
  blocksCompressed: counters.blocksCompressed,
52
+ toolResultPruned: counters.toolResultPruned,
53
+ charsPrunedEstimate: counters.charsPrunedEstimate,
50
54
  };
51
55
  }
@@ -0,0 +1,98 @@
1
+ /** Deterministic, model-free pruning of Pi tool-result messages. */
2
+ import { parseBoundedNumber, parseEnabledFlag } from "./env.js";
3
+ export const PI_TOOL_RESULT_PRUNE_MARKER = "\n\n[... tool result middle pruned ...]\n\n";
4
+ export const DEFAULT_PI_TOOL_RESULT_PRUNE_CONFIG = Object.freeze({ thresholdChars: 8192, headChars: 4096, tailChars: 1024 });
5
+ export function parsePiToolResultPruneConfig(env = process.env) {
6
+ const enabled = parseEnabledFlag(env.HARNESS_TOOL_RESULT_PRUNE, true);
7
+ const diagnostics = enabled.diagnostic ? [enabled.diagnostic] : [];
8
+ const threshold = parseBoundedNumber(env.HARNESS_TOOL_RESULT_PRUNE_THRESHOLD_CHARS, 8192, 1, 1_000_000);
9
+ const head = parseBoundedNumber(env.HARNESS_TOOL_RESULT_PRUNE_HEAD_CHARS, 4096, 0, 1_000_000);
10
+ const tail = parseBoundedNumber(env.HARNESS_TOOL_RESULT_PRUNE_TAIL_CHARS, 1024, 0, 1_000_000);
11
+ for (const item of [threshold, head, tail])
12
+ if (item.diagnostic)
13
+ diagnostics.push(item.diagnostic);
14
+ try {
15
+ return { enabled: enabled.enabled, config: resolvePiToolResultPruneConfig({ thresholdChars: Math.floor(threshold.value), headChars: Math.floor(head.value), tailChars: Math.floor(tail.value) }), diagnostics };
16
+ }
17
+ catch (error) {
18
+ diagnostics.push(error instanceof Error ? error.message : String(error));
19
+ return { enabled: false, config: DEFAULT_PI_TOOL_RESULT_PRUNE_CONFIG, diagnostics };
20
+ }
21
+ }
22
+ export function resolvePiToolResultPruneConfig(config = {}) {
23
+ const keys = new Set(["thresholdChars", "headChars", "tailChars"]);
24
+ for (const key of Object.keys(config))
25
+ if (!keys.has(key))
26
+ throw new Error(`PiToolResultPruneConfig: unknown key "${key}"`);
27
+ const out = { thresholdChars: config.thresholdChars ?? 8192, headChars: config.headChars ?? 4096, tailChars: config.tailChars ?? 1024 };
28
+ if (!Number.isInteger(out.thresholdChars) || out.thresholdChars <= 0)
29
+ throw new Error("thresholdChars must be positive integer");
30
+ if (!Number.isInteger(out.headChars) || out.headChars < 0)
31
+ throw new Error("headChars must be non-negative integer");
32
+ if (!Number.isInteger(out.tailChars) || out.tailChars < 0)
33
+ throw new Error("tailChars must be non-negative integer");
34
+ if (out.headChars + Array.from(PI_TOOL_RESULT_PRUNE_MARKER).length + out.tailChars > out.thresholdChars)
35
+ throw new Error("prune budgets exceed threshold");
36
+ return Object.freeze(out);
37
+ }
38
+ export function piCodePointLength(text) { return Array.from(text).length; }
39
+ function textBlock(value) { return value && typeof value === "object" && value.type === "text" && typeof value.text === "string"; }
40
+ function isToolResult(message) { return message.role === "tool" || message.role === "toolResult" || message.type === "tool_result" || message.role === "tool_result"; }
41
+ function pruneBlocks(input, cfg) {
42
+ const before = input.reduce((n, b) => n + (textBlock(b) ? piCodePointLength(b.text) : 0), 0);
43
+ if (before <= cfg.thresholdChars)
44
+ return { blocks: null, before, after: before };
45
+ const cutStart = cfg.headChars, cutEnd = before - cfg.tailChars;
46
+ let consumed = 0, inserted = false;
47
+ const out = [];
48
+ for (const block of input) {
49
+ if (!textBlock(block)) {
50
+ out.push(block);
51
+ continue;
52
+ }
53
+ const points = Array.from(block.text);
54
+ const bs = consumed, be = bs + points.length;
55
+ const headEnd = Math.min(points.length, Math.max(0, cutStart - bs));
56
+ const tailStart = Math.min(points.length, Math.max(0, cutEnd - bs));
57
+ const marker = !inserted && bs < cutEnd && be > cutStart ? PI_TOOL_RESULT_PRUNE_MARKER : "";
58
+ if (marker)
59
+ inserted = true;
60
+ const text = points.slice(0, headEnd).join("") + marker + points.slice(tailStart).join("");
61
+ if (text)
62
+ out.push({ ...block, text });
63
+ consumed = be;
64
+ }
65
+ const after = out.reduce((n, b) => n + (textBlock(b) ? piCodePointLength(b.text) : 0), 0);
66
+ if (!inserted || after > cfg.thresholdChars || after >= before)
67
+ throw new Error("Pi tool-result prune invariant failed");
68
+ return { blocks: out, before, after };
69
+ }
70
+ /** Returns a new transcript; input/full originals are never mutated or persisted. */
71
+ export function prunePiToolResultMessages(messages, config) {
72
+ const cfg = resolvePiToolResultPruneConfig(config);
73
+ const output = messages.slice();
74
+ let charsRemoved = 0, messagesPruned = 0;
75
+ for (let i = 0; i < output.length; i++) {
76
+ const message = output[i];
77
+ if (!message || !isToolResult(message))
78
+ continue;
79
+ if (typeof message.content === "string") {
80
+ const r = pruneBlocks([{ type: "text", text: message.content }], cfg);
81
+ if (r.blocks) {
82
+ output[i] = { ...message, content: r.blocks[0].text };
83
+ charsRemoved += r.before - r.after;
84
+ messagesPruned++;
85
+ }
86
+ continue;
87
+ }
88
+ if (!Array.isArray(message.content))
89
+ continue;
90
+ const r = pruneBlocks(message.content, cfg);
91
+ if (r.blocks) {
92
+ output[i] = { ...message, content: r.blocks };
93
+ charsRemoved += r.before - r.after;
94
+ messagesPruned++;
95
+ }
96
+ }
97
+ return { messages: output, changed: messagesPruned > 0, charsRemoved, messagesPruned };
98
+ }
@@ -4,6 +4,39 @@ import path from "node:path";
4
4
  import semver from "semver";
5
5
  const PACKAGE_NAME = "@tea-agent/loop-agent";
6
6
  export const DEFAULT_NPM_QUERY_TIMEOUT_MS = 15_000;
7
+ function quoteWindowsCommandArgument(value) {
8
+ if (/[%!^&|<>]/.test(value)) {
9
+ throw new Error("unsafe Windows update command argument");
10
+ }
11
+ return /^[A-Za-z0-9_@./:\\=,+-]+$/.test(value)
12
+ ? value
13
+ : `"${value.replaceAll('"', '""')}"`;
14
+ }
15
+ export function resolveUpdateCommandLaunch(command, args, platform = process.platform, comspec = process.env.ComSpec ?? "cmd.exe") {
16
+ if (platform !== "win32") {
17
+ return { command, args: [...args], windowsVerbatimArguments: false };
18
+ }
19
+ const isExplicitPath = command.includes("/") || command.includes("\\");
20
+ const isKnownBareShim = !isExplicitPath &&
21
+ path.win32.extname(command) === "" &&
22
+ new Set(["npm", "npx", "loop-agent"]).has(command.toLowerCase());
23
+ const isCommandShim = isKnownBareShim || /\.(?:cmd|bat)$/i.test(command);
24
+ if (!isCommandShim) {
25
+ return { command, args: [...args], windowsVerbatimArguments: false };
26
+ }
27
+ const windowsCommand = isKnownBareShim ? `${command}.cmd` : command;
28
+ const commandLine = [windowsCommand, ...args]
29
+ .map(quoteWindowsCommandArgument)
30
+ .join(" ");
31
+ const commandPayload = commandLine.startsWith('"')
32
+ ? `"${commandLine}"`
33
+ : commandLine;
34
+ return {
35
+ command: comspec,
36
+ args: [`/d /s /c ${commandPayload}`],
37
+ windowsVerbatimArguments: true,
38
+ };
39
+ }
7
40
  export function createNpmUpdateClient(options) {
8
41
  const npmCommand = options.npmCommand ?? "npm";
9
42
  const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
@@ -166,11 +199,26 @@ function isAllowedChannelVersion(version, channel) {
166
199
  }
167
200
  async function runCommand(command, args, options) {
168
201
  return new Promise((resolve) => {
169
- const child = spawn(command, [...args], {
202
+ let launch;
203
+ try {
204
+ launch = resolveUpdateCommandLaunch(command, args);
205
+ }
206
+ catch (error) {
207
+ resolve({
208
+ exitCode: null,
209
+ stdout: "",
210
+ stderr: error instanceof Error ? error.message : String(error),
211
+ timedOut: false,
212
+ });
213
+ return;
214
+ }
215
+ const child = spawn(launch.command, launch.args, {
170
216
  cwd: options.cwd,
171
217
  env: options.env ? { ...process.env, ...options.env } : process.env,
172
218
  stdio: ["ignore", "pipe", "pipe"],
173
219
  shell: false,
220
+ windowsHide: true,
221
+ windowsVerbatimArguments: launch.windowsVerbatimArguments,
174
222
  });
175
223
  let stdout = "";
176
224
  let stderr = "";
@@ -126,7 +126,8 @@ export const frontendOpenspecConfigSchema = z.object({
126
126
  export const frontendTestConfigSchema = z.object({
127
127
  /**
128
128
  * Repository-relative frontend-test artifact root.
129
- * Default "testcase/frontend". Semantic validation lives in
129
+ * Default ".harness/testcase/frontend" (canonical harness test-artifact
130
+ * namespace). Semantic validation lives in
130
131
  * resolveFrontendTestLayout (fail-closed), not in this schema.
131
132
  */
132
133
  testRoot: z.string().min(1).optional(),
@@ -239,7 +240,8 @@ const backendTestModuleLayoutSchema = z
239
240
  /** Backend-test artifact layout and pipeline mode configuration (plan A/B/D). */
240
241
  export const backendTestConfigSchema = z
241
242
  .object({
242
- /** Repository-relative test root. Default "testcase". */
243
+ /** Repository-relative test root. Default ".harness/testcase" (canonical
244
+ * harness test-artifact namespace). */
243
245
  testRoot: z.string().min(1).optional(),
244
246
  /** Markdown subdirectory relative to testRoot. Default "md". */
245
247
  markdownRel: z.string().min(1).optional(),
@@ -233,6 +233,7 @@ export function buildPrepareDraft(input) {
233
233
  allowedPaths,
234
234
  forbiddenPaths: finalForbidden,
235
235
  noDefaultForbiddenPaths: flags.noDefaultForbiddenPaths,
236
+ taskKind,
236
237
  });
237
238
  const draft = {
238
239
  schemaVersion: TASK_CONTRACT_DRAFT_SCHEMA_VERSION,
@@ -1,5 +1,6 @@
1
1
  import { canonicalizePath } from "../contract/canonicalize.js";
2
2
  import { PREPARE_DEFAULT_FORBIDDEN_PATHS } from "./types.js";
3
+ import { isAnyTestArtifactPath, isTestArtifactTaskKind, } from "../../workflows/dag/test-artifact-namespace.js";
3
4
  function hasNul(value) {
4
5
  return value.includes("\0");
5
6
  }
@@ -133,6 +134,7 @@ export function assessPreparePaths(input) {
133
134
  forbidden.push(result.value);
134
135
  }
135
136
  const allowedSet = new Set(allowed);
137
+ const testArtifactTaskKind = isTestArtifactTaskKind(input.taskKind);
136
138
  for (const f of forbidden) {
137
139
  if (allowedSet.has(f)) {
138
140
  blocking.push({
@@ -145,14 +147,22 @@ export function assessPreparePaths(input) {
145
147
  }
146
148
  for (const a of allowed) {
147
149
  for (const f of forbidden) {
148
- if (forbiddenAncestorCoversAllowed(a, f)) {
149
- blocking.push({
150
- code: "ALLOWED_UNDER_FORBIDDEN_ANCESTOR",
151
- level: "blocking",
152
- message: `allowed path ${a} is fully covered by forbidden ancestor ${f}`,
153
- path: a,
154
- });
150
+ if (!forbiddenAncestorCoversAllowed(a, f))
151
+ continue;
152
+ // D3 test-artifact exception: a dedicated test task may allow exactly
153
+ // the canonical `.harness/testcase` roots under the default `.harness/**`
154
+ // forbidden ancestor; every other overlap stays blocking.
155
+ if (testArtifactTaskKind &&
156
+ f === ".harness/**" &&
157
+ isAnyTestArtifactPath(a)) {
158
+ continue;
155
159
  }
160
+ blocking.push({
161
+ code: "ALLOWED_UNDER_FORBIDDEN_ANCESTOR",
162
+ level: "blocking",
163
+ message: `allowed path ${a} is fully covered by forbidden ancestor ${f}`,
164
+ path: a,
165
+ });
156
166
  }
157
167
  }
158
168
  for (const a of allowed) {
@@ -873,12 +873,10 @@ export async function runConsoleServe(options) {
873
873
  await server.close();
874
874
  return;
875
875
  }
876
- await new Promise((resolve) => {
877
- const shutdown = () => resolve();
878
- process.once("SIGINT", shutdown);
879
- process.once("SIGTERM", shutdown);
876
+ const { holdConsoleUntilSignal } = await import("./console/console-shutdown.js");
877
+ await holdConsoleUntilSignal({
878
+ close: () => server.close(),
880
879
  });
881
- await server.close();
882
880
  }
883
881
  catch (error) {
884
882
  const detail = error instanceof Error ? error.message : String(error);