@tea-agent/loop-agent 0.40.0-next.9 → 0.41.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +29 -20
  3. package/README.md +2 -2
  4. package/dist/application/dag/generate-task-dag.js +6 -2
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/cli/command-definitions.js +9 -5
  7. package/dist/cli/program.js +38 -3
  8. package/dist/commands/client-recovery.js +8 -36
  9. package/dist/commands/init-model-catalog.js +22 -0
  10. package/dist/commands/task-advance.js +23 -1
  11. package/dist/commands/task-source-prepare.js +5 -0
  12. package/dist/commands/task-status.js +1 -0
  13. package/dist/executors/pi-sdk-executor.js +33 -5
  14. package/dist/executors/shell-executor.js +94 -20
  15. package/dist/governance/checks.js +1 -0
  16. package/dist/shared/operator/capabilities.js +9 -1
  17. package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
  18. package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
  19. package/dist/shared/pi-context-pressure/env.js +58 -0
  20. package/dist/shared/pi-context-pressure/extension.js +100 -0
  21. package/dist/shared/pi-context-pressure/index.js +7 -0
  22. package/dist/shared/pi-context-pressure/overflow.js +252 -0
  23. package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
  24. package/dist/shared/pi-context-pressure/telemetry.js +51 -0
  25. package/dist/worker/console/chat/assistant-content.js +7 -0
  26. package/dist/worker/console/chat/chat-event-store.js +82 -11
  27. package/dist/worker/console/chat/goal-round-driver.js +72 -0
  28. package/dist/worker/console/chat/goals.js +300 -0
  29. package/dist/worker/console/chat/pi-runtime.js +286 -8
  30. package/dist/worker/console/chat/provider-error.js +2 -1
  31. package/dist/worker/console/chat/repo-browser.js +94 -12
  32. package/dist/worker/console/chat/routes.js +495 -30
  33. package/dist/worker/console/chat/semantic-activity.js +12 -11
  34. package/dist/worker/console/chat/session-catalog.js +48 -0
  35. package/dist/worker/console/chat/session-stats.js +96 -0
  36. package/dist/worker/console/chat/session-store.js +27 -18
  37. package/dist/worker/console/chat/shortcuts.js +7 -0
  38. package/dist/worker/console/chat/sift-bridge.js +1 -0
  39. package/dist/worker/console/chat/turn-process.js +60 -7
  40. package/dist/worker/console/dag-execution-receipt.js +20 -2
  41. package/dist/worker/console/operator-actions.js +20 -5
  42. package/dist/worker/console/operator-selection.js +6 -2
  43. package/dist/worker/console/pi-plugins.catalog.json +73658 -0
  44. package/dist/worker/console/pi-plugins.js +179 -0
  45. package/dist/worker/console/routes.js +246 -6
  46. package/dist/worker/console/server.js +173 -42
  47. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-CV1qyRbn.js} +1 -1
  48. package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-B_ljC4ma.js} +1 -1
  49. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-DTBqsauL.js} +1 -1
  50. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-CMZj6nYE.js} +1 -1
  51. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-C4ey_S7v.js} +1 -1
  52. package/dist/worker/console/static/assets/channel-BvYm_83n.js +1 -0
  53. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGcAlY6Z.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-XRSM64J1.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-COSIFYbz.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-BwuaXiOK.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-DNbFuVlv.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-C4DZKffE.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-M3A6iczx.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-C5zDzQEM.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-uAcUDub8.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-EPpz2uSP.js} +1 -1
  63. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BNdy3f9F.js +1 -0
  64. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BNdy3f9F.js +1 -0
  65. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-BAkfFGTf.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-Cjb4d22A.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cf7QSgcu.js} +1 -1
  68. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-UlyVBCtl.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DWdRAZ8p.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-Y2ag2Pm0.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-DBHcuPXI.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-BK7eno2C.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-Dbyku82r.js} +1 -1
  74. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR--rS0Q41A.js} +1 -1
  75. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-Cms8pJeK.js} +1 -1
  76. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-DouONwBy.js} +1 -1
  77. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-DuKB5yDe.js} +1 -1
  78. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DE6zp0SI.js} +1 -1
  79. package/dist/worker/console/static/assets/index-C39zQYik.js +451 -0
  80. package/dist/worker/console/static/assets/index-Crixt9tN.css +1 -0
  81. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BLETv9SH.js} +1 -1
  82. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-DyU-2lJb.js} +1 -1
  83. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-BotFSygt.js} +1 -1
  84. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-CqcAMgVk.js} +1 -1
  85. package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-Bf4U8h5G.js} +1 -1
  86. package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-D0JEVefG.js} +5 -5
  87. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-DEFgNgOo.js} +1 -1
  88. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CirKhxo1.js} +1 -1
  89. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-BlaQYInF.js} +1 -1
  90. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-BbyzqH4U.js} +1 -1
  91. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-qA-XykV6.js} +1 -1
  92. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-Cc85Zcxc.js} +1 -1
  93. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-CS7BVnoH.js} +1 -1
  94. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-DMQ-kKmZ.js} +1 -1
  95. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-DXKs1dcE.js} +1 -1
  96. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-xG1ti87X.js} +1 -1
  97. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-tYINJZ8x.js +1 -0
  98. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-BQwmKGHd.js} +2 -2
  99. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-OoBOXPiG.js +8 -0
  100. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-BwL9oHtP.js} +1 -1
  101. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-BWk-i_Y1.js} +1 -1
  102. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-DAvETpEZ.js} +1 -1
  103. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-Cd4pASnF.js} +1 -1
  104. package/dist/worker/console/static/index.html +2 -2
  105. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  106. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
  107. package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
  108. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  109. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
  110. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
  111. package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
  112. package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
  113. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
  114. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  115. package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
  116. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  117. package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
  118. package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
  119. package/dist/worker/console/workspace-context.js +28 -3
  120. package/dist/worker/console/workspace-registry.js +333 -60
  121. package/dist/worker/observability/read-model.js +4 -0
  122. package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
  123. package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
  124. package/dist/worker/observe/static/inspect-workspace.js +54 -10
  125. package/dist/worker/observe/static/operator-chrome.css +23 -4
  126. package/dist/worker/observe/static/operator-chrome.js +7 -2
  127. package/dist/worker/observe/static/styles.css +87 -25
  128. package/dist/worker/observe/static/views/dag-inspector.js +42 -1
  129. package/dist/worker/observe/static/views/dags.js +7 -5
  130. package/dist/worker/observe/static/views/session-timeline.js +135 -0
  131. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
  132. package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
  133. package/dist/workflows/dag/backend-test-result-contract.js +5 -1
  134. package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
  135. package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
  136. package/dist/workflows/dag/init-hybrid.js +5 -4
  137. package/docs/architecture/runtime-boundaries.md +2 -1
  138. package/docs/templates/backend-test-dag.json +6 -5
  139. package/docs/templates/init-managed-agents.md +1 -1
  140. package/package.json +3 -2
  141. package/skills/loop-agent/references/command-reference.md +15 -1
  142. package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
  143. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
  144. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
  145. package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
  146. package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
  147. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
  148. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
@@ -32,11 +32,11 @@ import { materializeBackendTestExecutionContract } from "../workflows/dag/backen
32
32
  import { resolveBackendTestLayout } from "../workflows/dag/backend-test-layout.js";
33
33
  import { frontendTestLayoutFromSpec, } from "../workflows/dag/frontend-test-layout.js";
34
34
  import { backendTestGapDocSchema, ingestBackendTestGap, } from "../workflows/dag/backend-test-gap-fill.js";
35
- import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorrespondence, materializeBackendTestCaseManifestFromFacts, } from "../workflows/dag/backend-test-case-coverage-analysis.js";
35
+ import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorrespondence, classifyBackendTestCorrespondenceRepair, materializeBackendTestCaseManifestFromFacts, } from "../workflows/dag/backend-test-case-coverage-analysis.js";
36
36
  import { materializeBackendTestResultFromPytestHtml, materializeBackendTestResultFromRunDir, parsePytestHtmlReport, } from "../workflows/dag/backend-test-result-contract.js";
37
37
  import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, resolveBackendTestMappedPytestScripts, collectJacocoCoverage, hasBlockingBackendMarkdownSafetyFindings, hasBlockingBackendMarkdownModuleStemFindings, inspectBackendTestEnvironment, markdownFiles, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, renderBackendTestL5Dashboard, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
38
38
  import { applyDeterministicScenarioParamRepairs, assessBackendScenarioParamConsistency, classifyBackendTestFailureWithScenarioParam, readBackendScenarioParamFacts, renderBackendTestFailureAnalysis, writeBackendScenarioParamArtifacts, writeScenarioParamRepairAudit, } from "../workflows/dag/backend-test-scenario-param.js";
39
- import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
39
+ import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, summarizeBackendTestExclusions, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
40
40
  import { computeL5ReportMetrics } from "../workflows/dag/l5-report-metrics.js";
41
41
  import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
42
42
  import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
@@ -870,26 +870,49 @@ async function executeBackendTestPipeline(input, meta) {
870
870
  const correspondenceFactsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-initial.json");
871
871
  await writeFile(correspondenceFactsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
872
872
  outputs.push(`initialCorrespondence=${correspondenceReportPath}`, `initialCorrespondenceFacts=${correspondenceFactsPath}`);
873
- if (correspondence.facts.status === "FAIL" && facts.status === "PASS") {
874
- const correspondenceRepairPaths = Array.from(new Set(correspondence.facts.entries
875
- .filter((entry) => entry.status !== "EXACT_1_TO_1")
876
- .flatMap((entry) => [entry.expectedScript, ...entry.actualScripts])
877
- .filter((candidate) => candidate.startsWith("testcase/") && candidate.endsWith(".py")))).sort();
878
- if (correspondenceRepairPaths.length > 0) {
879
- facts.status = "REPAIRABLE";
880
- facts.repairEligible = true;
881
- facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...correspondenceRepairPaths])).sort();
873
+ if (correspondence.facts.status === "FAIL") {
874
+ const repair = classifyBackendTestCorrespondenceRepair(correspondence.facts, backendLayout);
875
+ if (repair.markdownBlockingEntries.length > 0) {
876
+ facts.status = "BLOCKED";
877
+ facts.repairEligible = false;
878
+ facts.repairPaths = [];
882
879
  facts.findings.push({
883
- kind: "markdown-pytest-correspondence",
880
+ kind: "markdown-correspondence-contract",
884
881
  classification: "test-asset-defect",
885
- repairability: "repairable",
886
- detail: correspondence.facts.entries
887
- .filter((entry) => entry.status !== "EXACT_1_TO_1")
888
- .flatMap((entry) => entry.findings)
882
+ repairability: "blocked",
883
+ detail: repair.markdownBlockingEntries
884
+ .map((entry) => `${entry.caseId ?? "unknown Case"}: ${entry.reasonCodes.join(", ")}`)
889
885
  .join("; ")
890
- .slice(0, 12_000) || "Markdown-to-pytest correspondence is incomplete",
886
+ .slice(0, 12_000),
891
887
  });
892
888
  }
889
+ else if (repair.pytestRepairPaths.length > 0 &&
890
+ (facts.status === "PASS" || facts.status === "REPAIRABLE")) {
891
+ facts.status = "REPAIRABLE";
892
+ facts.repairEligible = true;
893
+ facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...repair.pytestRepairPaths])).sort();
894
+ const repairEntries = correspondence.facts.entries.filter((entry) => repair.pytestRepairEntries.some((candidate) => candidate.caseId === entry.caseId));
895
+ for (const entry of repairEntries.slice(0, 40)) {
896
+ const classified = repair.pytestRepairEntries.find((candidate) => candidate.caseId === entry.caseId);
897
+ facts.findings.push({
898
+ kind: "markdown-pytest-correspondence",
899
+ classification: "test-asset-defect",
900
+ repairability: "repairable",
901
+ detail: entry.findings.join("; ").slice(0, 2_000) || "Markdown-to-pytest correspondence is incomplete",
902
+ ...(entry.caseId ? { caseId: entry.caseId } : {}),
903
+ reasonCodes: classified?.reasonCodes ?? [],
904
+ repairPaths: classified?.paths ?? [],
905
+ payloadExpectedPaths: Array.from(new Set([
906
+ ...entry.payloadAssessment.requiredPaths,
907
+ ...entry.payloadAssessment.allowedPaths,
908
+ ])).sort(),
909
+ payloadObservedPaths: entry.payloadAssessment.observedPaths,
910
+ payloadMissingPaths: entry.payloadAssessment.missingPaths,
911
+ payloadUnexpectedPaths: entry.payloadAssessment.unexpectedPaths,
912
+ payloadEnumMismatches: entry.payloadAssessment.enumMismatches,
913
+ });
914
+ }
915
+ }
893
916
  }
894
917
  }
895
918
  catch (error) {
@@ -912,6 +935,19 @@ async function executeBackendTestPipeline(input, meta) {
912
935
  collectedItemCount: facts.collectedItemCount,
913
936
  missingMappedScripts: facts.missingMappedScripts,
914
937
  repairPaths: facts.repairPaths,
938
+ repairFindings: facts.findings.map((finding) => ({
939
+ kind: finding.kind,
940
+ repairability: finding.repairability,
941
+ detail: finding.detail,
942
+ ...(finding.caseId ? { caseId: finding.caseId } : {}),
943
+ ...(finding.reasonCodes ? { reasonCodes: finding.reasonCodes } : {}),
944
+ ...(finding.repairPaths ? { repairPaths: finding.repairPaths } : {}),
945
+ ...(finding.payloadExpectedPaths ? { payloadExpectedPaths: finding.payloadExpectedPaths } : {}),
946
+ ...(finding.payloadObservedPaths ? { payloadObservedPaths: finding.payloadObservedPaths } : {}),
947
+ ...(finding.payloadMissingPaths ? { payloadMissingPaths: finding.payloadMissingPaths } : {}),
948
+ ...(finding.payloadUnexpectedPaths ? { payloadUnexpectedPaths: finding.payloadUnexpectedPaths } : {}),
949
+ ...(finding.payloadEnumMismatches ? { payloadEnumMismatches: finding.payloadEnumMismatches } : {}),
950
+ })),
915
951
  factsPath: "contracts/backend-test-pytest-collection-initial.json",
916
952
  }),
917
953
  stderr: "",
@@ -1011,6 +1047,7 @@ async function executeBackendTestPipeline(input, meta) {
1011
1047
  })
1012
1048
  : { eligibleItemIds: effective.collectedItemIds, excludedItems: [] };
1013
1049
  const eligibilityInputHashes = Object.fromEntries((correspondence?.facts.inputFiles ?? []).map((item) => [item.path, item.sha256]));
1050
+ const exclusionSummary = summarizeBackendTestExclusions(eligibility.excludedItems);
1014
1051
  const eligibilityFactsPath = path.join(meta.runDir, "contracts", "backend-test-execution-eligibility.json");
1015
1052
  await mkdir(path.dirname(eligibilityFactsPath), { recursive: true });
1016
1053
  await writeFile(eligibilityFactsPath, `${JSON.stringify({
@@ -1021,6 +1058,9 @@ async function executeBackendTestPipeline(input, meta) {
1021
1058
  excludedItemCount: eligibility.excludedItems.length,
1022
1059
  eligibleItemIds: eligibility.eligibleItemIds,
1023
1060
  excludedItems: eligibility.excludedItems,
1061
+ exclusionSummary,
1062
+ pytestSpawned: false,
1063
+ businessTestBodyExecutedCount: 0,
1024
1064
  inputHashes: eligibilityInputHashes,
1025
1065
  }, null, 2)}\n`, "utf8");
1026
1066
  const eligibilityReportPath = await writeRunReport(meta.runDir, "backend-test-execution-eligibility.md", [
@@ -1033,6 +1073,10 @@ async function executeBackendTestPipeline(input, meta) {
1033
1073
  `- Collected items: ${effective.collectedItemIds.length}`,
1034
1074
  `- Eligible items: ${eligibility.eligibleItemIds.length}`,
1035
1075
  `- Excluded items: ${eligibility.excludedItems.length}`,
1076
+ `- Excluded Cases: ${exclusionSummary.excludedCaseCount}`,
1077
+ `- Exclusion categories: ${Object.entries(exclusionSummary.excludedByReason).map(([category, count]) => `${category}=${count}`).join("; ") || "none"}`,
1078
+ "- pytest spawned: false",
1079
+ "- Business test bodies executed: 0",
1036
1080
  "",
1037
1081
  "## Excluded Items",
1038
1082
  "",
@@ -1061,6 +1105,9 @@ async function executeBackendTestPipeline(input, meta) {
1061
1105
  executionReadinessStatus: readiness.status,
1062
1106
  eligibleItemCount: readiness.eligibleItemIds.length,
1063
1107
  excludedItemCount: readiness.excludedItems.length,
1108
+ exclusionSummary: readiness.exclusionSummary,
1109
+ pytestSpawned: readiness.pytestSpawned,
1110
+ businessTestBodyExecutedCount: readiness.businessTestBodyExecutedCount,
1064
1111
  factsPath: "contracts/backend-test-pytest-collection-effective.json",
1065
1112
  readinessPath: "contracts/backend-test-execution-readiness.json",
1066
1113
  reportPath: artifacts.reportPath,
@@ -1183,7 +1230,33 @@ async function executeBackendTestPipeline(input, meta) {
1183
1230
  const pytestTargetsRelative = "reports/backend-test-pytest-targets.txt";
1184
1231
  const pytestTargetsAbsolute = path.join(meta.runDir, pytestTargetsRelative);
1185
1232
  await writeFile(pytestTargetsAbsolute, `${eligibleItemIds.join("\n")}\n`, "utf8");
1186
- const pytestTargetsArg = `@\"\${HARNESS_DAG_RUN_DIR}/${pytestTargetsRelative}\"`;
1233
+ const pytestLauncherRelative = "reports/backend-test-pytest-launcher.py";
1234
+ const pytestLauncherAbsolute = path.join(meta.runDir, pytestLauncherRelative);
1235
+ await writeFile(pytestLauncherAbsolute, [
1236
+ "import os",
1237
+ "import pathlib",
1238
+ "import sys",
1239
+ "",
1240
+ "sys.path[0] = os.getcwd()",
1241
+ "import pytest",
1242
+ "",
1243
+ "if len(sys.argv) != 3:",
1244
+ " print('usage: backend-test-pytest-launcher.py <targets-file> <html-file>', file=sys.stderr)",
1245
+ " raise SystemExit(4)",
1246
+ "targets_path = pathlib.Path(sys.argv[1])",
1247
+ "html_path = pathlib.Path(sys.argv[2])",
1248
+ "try:",
1249
+ " targets = targets_path.read_text(encoding='utf-8').splitlines()",
1250
+ "except (OSError, UnicodeError) as error:",
1251
+ " print(f'backend-test targets unreadable: {error}', file=sys.stderr)",
1252
+ " raise SystemExit(4)",
1253
+ "if not targets or any(not target or target != target.strip() for target in targets):",
1254
+ " print('backend-test targets must contain non-empty exact pytest node IDs', file=sys.stderr)",
1255
+ " raise SystemExit(4)",
1256
+ "pytest_args = [*targets, '-v', '-p', 'no:cacheprovider', f'--html={html_path}', '--self-contained-html']",
1257
+ "raise SystemExit(pytest.main(pytest_args))",
1258
+ "",
1259
+ ].join("\n"), "utf8");
1187
1260
  // Split into short bash -c commands (aligned with markdown-environment).
1188
1261
  // A single ultra-long compound command has been observed on Windows to
1189
1262
  // exit 0xC0000142 (STATUS_DLL_INIT_FAILED) in ~40ms with empty stdout/
@@ -1260,10 +1333,11 @@ async function executeBackendTestPipeline(input, meta) {
1260
1333
  'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
1261
1334
  'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
1262
1335
  'echo "STEP=pytest-run"',
1263
- `echo "- pytestCommand: python -m pytest <${eligibleItemIds.length} eligibility-authorized-items> -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
1336
+ `echo "- pytestCommand: controller launcher -> pytest.main(<${eligibleItemIds.length} eligibility-authorized-items>, -v, -p no:cacheprovider, --html=reports/backend-test.html, --self-contained-html)" >> "\${DIAG_FILE}"`,
1264
1337
  `echo "- eligibleItemCount: ${eligibleItemIds.length}" >> "\${DIAG_FILE}"`,
1265
1338
  `echo "- excludedItemCount: ${executionReadiness.excludedItems.length}" >> "\${DIAG_FILE}"`,
1266
- `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 "\${PYTHON_BIN}" -m pytest ${pytestTargetsArg} -v -p no:cacheprovider --html="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" --self-contained-html`,
1339
+ 'rm -f "${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-native.html"',
1340
+ `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 "\${PYTHON_BIN}" "\${HARNESS_DAG_RUN_DIR}/${pytestLauncherRelative}" "\${HARNESS_DAG_RUN_DIR}/${pytestTargetsRelative}" "\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html"`,
1267
1341
  "STATUS=$?",
1268
1342
  'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
1269
1343
  'echo "- pytestExitCode: ${STATUS}" >> "${DIAG_FILE}"',
@@ -8,6 +8,7 @@ import { buildTaskReadModel } from '../task/read-model.js';
8
8
  const SKIP_DIRS = new Set([
9
9
  '.git',
10
10
  '.harness',
11
+ '.scratch',
11
12
  'node_modules',
12
13
  'dist',
13
14
  'coverage',
@@ -51,6 +51,8 @@ const OFFICIAL_ACTION_DESCRIPTIONS = {
51
51
  workerSchedulerStatus: "Night-scheduler status overview (agent-worker). Use to check whether overnight jobs are healthy.",
52
52
  workerSchedulerLedger: "Night-scheduler harvest ledger (agent-worker). Use to audit what overnight runs produced.",
53
53
  workerSchedulerDoctor: "Diagnose night-scheduler health (agent-worker). Use when overnight jobs fail to fire or harvest.",
54
+ dagResume: "Unpause a run paused at a human decision checkpoint. Orphaned runs (dead runner, liveness=orphaned) are NOT continuable — do not resume them: use dagReconcileRun --action supersede to archive and then advance/rerun the task fresh.",
55
+ dagReconcileRun: "Reconcile an orphaned run (dead runner, liveness=orphaned). These are NOT continuable: supersede archives the run for a fresh same-task advance/rerun; abandon discards it. resume only unpauses human-decision checkpoints — never for orphaned runs.",
54
56
  };
55
57
  const officialAction = (action, cli, kind, coverage) => ({
56
58
  action,
@@ -817,7 +819,7 @@ export function buildOperatorCapabilitiesDocument() {
817
819
  "BINDING_DRIFT",
818
820
  "HUMAN_CONFIRMATION_REQUIRED",
819
821
  ],
820
- description: "Issue a session-bound single-use execution receipt after server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
822
+ description: "Returns SYNCHRONOUSLY (not an operation): it immediately yields a session-bound single-use executionId + receipt, which you then pass to runDag to actually execute. It is a server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
821
823
  inputParams: [
822
824
  {
823
825
  name: "taskId",
@@ -1067,6 +1069,12 @@ export function buildOperatorCapabilitiesDocument() {
1067
1069
  required: false,
1068
1070
  description: "verification entries as label:command strings",
1069
1071
  },
1072
+ {
1073
+ name: "approveGate",
1074
+ type: "string",
1075
+ required: false,
1076
+ description: "optional write-set gate approval token `<gate-id>:sha256:<64-hex>` exactly as reported by task status gate.approveCommand; forwards as --approve-gate and executes the approved DAG as this background operation (preferred over a foreground terminal)",
1077
+ },
1070
1078
  ],
1071
1079
  modelCallable: "always",
1072
1080
  humanConfirmation: "none",
@@ -0,0 +1,116 @@
1
+ const CHECKPOINT_MAX_BYTES = 12 * 1024;
2
+ function boundText(value, maxChars) {
3
+ if (typeof value !== "string" || !value.trim())
4
+ return "";
5
+ return value.length <= maxChars ? value : `${value.slice(0, maxChars - 1)}…`;
6
+ }
7
+ function asRecord(value) {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value)
9
+ ? value
10
+ : undefined;
11
+ }
12
+ function extractLastUserGoal(messages) {
13
+ if (!Array.isArray(messages))
14
+ return "";
15
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
16
+ const message = asRecord(messages[index]);
17
+ if (!message || message.role !== "user")
18
+ continue;
19
+ const content = message.content;
20
+ if (typeof content === "string")
21
+ return boundText(content, 800);
22
+ if (Array.isArray(content)) {
23
+ const texts = content
24
+ .map((block) => {
25
+ const rec = asRecord(block);
26
+ return typeof rec?.text === "string" ? rec.text : "";
27
+ })
28
+ .filter(Boolean);
29
+ if (texts.length > 0)
30
+ return boundText(texts.join("\n"), 800);
31
+ }
32
+ }
33
+ return "";
34
+ }
35
+ function extractTodos(details) {
36
+ const record = asRecord(details);
37
+ const todos = record?.todos ?? record?.items;
38
+ if (!Array.isArray(todos))
39
+ return "";
40
+ const lines = todos
41
+ .slice(0, 24)
42
+ .map((item) => {
43
+ const rec = asRecord(item);
44
+ const status = typeof rec?.status === "string" ? rec.status : "pending";
45
+ const content = typeof rec?.content === "string"
46
+ ? rec.content
47
+ : typeof rec?.title === "string"
48
+ ? rec.title
49
+ : "";
50
+ return content ? `- [${status}] ${content}` : "";
51
+ })
52
+ .filter(Boolean);
53
+ return boundText(lines.join("\n"), 1200);
54
+ }
55
+ function extractFileOps(fileOps) {
56
+ const record = asRecord(fileOps);
57
+ if (!record)
58
+ return "";
59
+ const parts = [];
60
+ for (const key of ["edited", "written", "read", "deleted", "ops"]) {
61
+ const value = record[key];
62
+ if (Array.isArray(value)) {
63
+ parts.push(`${key}: ${value
64
+ .slice(0, 20)
65
+ .map((item) => (typeof item === "string" ? item : JSON.stringify(item)))
66
+ .join(", ")}`);
67
+ }
68
+ else if (typeof value === "string" && value) {
69
+ parts.push(`${key}: ${value}`);
70
+ }
71
+ }
72
+ if (parts.length === 0) {
73
+ try {
74
+ return boundText(JSON.stringify(fileOps), 800);
75
+ }
76
+ catch {
77
+ return "";
78
+ }
79
+ }
80
+ return boundText(parts.join("\n"), 800);
81
+ }
82
+ export function buildOverflowCheckpoint(input) {
83
+ const preparation = input.preparation;
84
+ const firstKeptEntryId = typeof preparation?.firstKeptEntryId === "string" &&
85
+ preparation.firstKeptEntryId
86
+ ? preparation.firstKeptEntryId
87
+ : undefined;
88
+ const tokensBefore = typeof preparation?.tokensBefore === "number" &&
89
+ Number.isFinite(preparation.tokensBefore)
90
+ ? preparation.tokensBefore
91
+ : undefined;
92
+ if (!firstKeptEntryId || tokensBefore === undefined)
93
+ return undefined;
94
+ const sections = [
95
+ "Deterministic overflow checkpoint (no extra summary request).",
96
+ boundText(preparation?.previousSummary, 2000)
97
+ ? `Previous summary:\n${boundText(preparation?.previousSummary, 2000)}`
98
+ : "",
99
+ extractLastUserGoal(preparation?.messagesToSummarize)
100
+ ? `Latest user goal:\n${extractLastUserGoal(preparation?.messagesToSummarize)}`
101
+ : "",
102
+ extractTodos(input.todos) ? `Open todos:\n${extractTodos(input.todos)}` : "",
103
+ extractFileOps(preparation?.fileOps)
104
+ ? `File ops:\n${extractFileOps(preparation?.fileOps)}`
105
+ : "",
106
+ typeof input.lastError === "string" && input.lastError
107
+ ? `Latest error:\n${boundText(input.lastError, 400)}`
108
+ : "",
109
+ "Next step: continue the interrupted turn from the kept recent messages. Do not repeat completed file writes.",
110
+ ].filter(Boolean);
111
+ let summary = sections.join("\n\n");
112
+ if (Buffer.byteLength(summary, "utf8") > CHECKPOINT_MAX_BYTES) {
113
+ summary = `${summary.slice(0, CHECKPOINT_MAX_BYTES - 1)}…`;
114
+ }
115
+ return { summary, firstKeptEntryId, tokensBefore };
116
+ }
@@ -0,0 +1,151 @@
1
+ import { parseBoundedNumber, readContextPressureMode, } from "./env.js";
2
+ export const PI_DEFAULT_RESERVE_TOKENS = 16_384;
3
+ export const DEFAULT_TRAILING_BUDGET_TOKENS = 8_192;
4
+ export const DEFAULT_SAFETY_MARGIN_TOKENS = 4_096;
5
+ export const DEFAULT_MINIMUM_WORKING_SET = 32_768;
6
+ export const DEFAULT_KEEP_RECENT_TOKENS = 20_000;
7
+ export function parseCompactionPolicy(env = process.env) {
8
+ const diagnostics = [];
9
+ const modeResult = readContextPressureMode(env);
10
+ if (modeResult.diagnostic)
11
+ diagnostics.push(modeResult.diagnostic);
12
+ const k = parseBoundedNumber(env.HARNESS_CONTEXT_GATEWAY_K, 1, 1, 8);
13
+ if (k.diagnostic)
14
+ diagnostics.push(k.diagnostic);
15
+ const trailing = parseBoundedNumber(env.HARNESS_CONTEXT_TRAILING_BUDGET_TOKENS, DEFAULT_TRAILING_BUDGET_TOKENS, 256, 128_000);
16
+ if (trailing.diagnostic)
17
+ diagnostics.push(trailing.diagnostic);
18
+ const safety = parseBoundedNumber(env.HARNESS_CONTEXT_SAFETY_MARGIN_TOKENS, DEFAULT_SAFETY_MARGIN_TOKENS, 0, 64_000);
19
+ if (safety.diagnostic)
20
+ diagnostics.push(safety.diagnostic);
21
+ const explicit = env.HARNESS_COMPACTION_RESERVE_TOKENS;
22
+ const explicitReserve = explicit === undefined || explicit.trim() === ""
23
+ ? undefined
24
+ : parseBoundedNumber(explicit, PI_DEFAULT_RESERVE_TOKENS, 1_024, 1_000_000);
25
+ if (explicitReserve?.diagnostic)
26
+ diagnostics.push(explicitReserve.diagnostic);
27
+ return {
28
+ mode: modeResult.mode,
29
+ k: k.value,
30
+ trailingBudgetTokens: trailing.value,
31
+ safetyMarginTokens: safety.value,
32
+ minimumWorkingSet: DEFAULT_MINIMUM_WORKING_SET,
33
+ ...(explicitReserve ? { explicitReserveTokens: explicitReserve.value } : {}),
34
+ diagnostics,
35
+ };
36
+ }
37
+ export function deriveEffectiveWindow(contextWindow, policy) {
38
+ const W = Math.floor(contextWindow);
39
+ if (!Number.isFinite(W) || W <= 0) {
40
+ return {
41
+ recommendedReserve: PI_DEFAULT_RESERVE_TOKENS,
42
+ triggerTokens: 0,
43
+ fallback: true,
44
+ };
45
+ }
46
+ if (policy.explicitReserveTokens !== undefined) {
47
+ const recommendedReserve = Math.min(Math.max(policy.explicitReserveTokens, 1), Math.max(1, W - 1));
48
+ return {
49
+ recommendedReserve,
50
+ triggerTokens: Math.max(0, W - recommendedReserve),
51
+ fallback: false,
52
+ };
53
+ }
54
+ const rawTrigger = Math.floor(W / policy.k) -
55
+ policy.trailingBudgetTokens -
56
+ policy.safetyMarginTokens;
57
+ const rawReserve = W - rawTrigger;
58
+ const maxReserve = Math.max(1, W - policy.minimumWorkingSet);
59
+ if (!Number.isFinite(rawTrigger) ||
60
+ !Number.isFinite(rawReserve) ||
61
+ maxReserve < PI_DEFAULT_RESERVE_TOKENS) {
62
+ const recommendedReserve = Math.min(PI_DEFAULT_RESERVE_TOKENS, Math.max(1, W - 1));
63
+ return {
64
+ recommendedReserve,
65
+ triggerTokens: Math.max(0, W - recommendedReserve),
66
+ fallback: true,
67
+ };
68
+ }
69
+ const recommendedReserve = Math.min(Math.max(PI_DEFAULT_RESERVE_TOKENS, rawReserve), maxReserve);
70
+ return {
71
+ recommendedReserve,
72
+ triggerTokens: Math.max(0, W - recommendedReserve),
73
+ fallback: false,
74
+ };
75
+ }
76
+ export function conservativeTokenEstimate(text, imageCount = 0) {
77
+ const chars = Array.from(text);
78
+ let cjk = 0;
79
+ let other = 0;
80
+ for (const char of chars) {
81
+ if (/\p{Script=Han}|\p{Script=Hiragana}|\p{Script=Katakana}/u.test(char)) {
82
+ cjk += 1;
83
+ }
84
+ else {
85
+ other += 1;
86
+ }
87
+ }
88
+ const utf8 = Buffer.byteLength(text, "utf8");
89
+ const fromChars = cjk + Math.ceil(other / 2);
90
+ const fromBytes = Math.ceil(utf8 / 2);
91
+ return (Math.max(fromChars, fromBytes, Math.ceil(chars.length / 2)) +
92
+ Math.max(0, imageCount) * 1024);
93
+ }
94
+ export function projectPromptPressure(input) {
95
+ if (input.currentContextTokens === null ||
96
+ input.currentContextTokens === undefined ||
97
+ !Number.isFinite(input.currentContextTokens)) {
98
+ return { projected: 0, usageUnavailable: true };
99
+ }
100
+ return {
101
+ projected: Math.max(0, input.currentContextTokens) +
102
+ conservativeTokenEstimate(input.promptText, input.imageCount ?? 0),
103
+ usageUnavailable: false,
104
+ };
105
+ }
106
+ function isExplicitlyDisabled(manager) {
107
+ const project = manager.getProjectSettings?.()?.compaction?.enabled;
108
+ const global = manager.getGlobalSettings?.()?.compaction?.enabled;
109
+ return project === false || (project === undefined && global === false);
110
+ }
111
+ export function applyProcessCompactionOverride(manager, input) {
112
+ if (!manager?.applyOverrides || input.policy.mode === "off") {
113
+ return { applied: false };
114
+ }
115
+ const explicitOff = input.policy.mode === "safe" && isExplicitlyDisabled(manager);
116
+ const enabled = input.policy.mode === "strict" ? true : explicitOff ? false : true;
117
+ const window = typeof input.contextWindow === "number" && input.contextWindow > 0
118
+ ? deriveEffectiveWindow(input.contextWindow, input.policy)
119
+ : undefined;
120
+ const compaction = { enabled };
121
+ if (window && enabled) {
122
+ compaction.reserveTokens = window.recommendedReserve;
123
+ compaction.keepRecentTokens = DEFAULT_KEEP_RECENT_TOKENS;
124
+ }
125
+ manager.applyOverrides({ compaction });
126
+ return {
127
+ applied: true,
128
+ enabled,
129
+ ...(typeof compaction.reserveTokens === "number"
130
+ ? { reserveTokens: compaction.reserveTokens }
131
+ : {}),
132
+ ...(typeof compaction.keepRecentTokens === "number"
133
+ ? { keepRecentTokens: compaction.keepRecentTokens }
134
+ : {}),
135
+ ...(window?.fallback
136
+ ? { diagnostic: "small-or-invalid-window-fell-back-to-pi-default-reserve" }
137
+ : {}),
138
+ };
139
+ }
140
+ export function inMemoryCompactionSettings(policy) {
141
+ if (policy.mode === "off") {
142
+ return { enabled: true };
143
+ }
144
+ return {
145
+ enabled: true,
146
+ keepRecentTokens: DEFAULT_KEEP_RECENT_TOKENS,
147
+ ...(policy.explicitReserveTokens
148
+ ? { reserveTokens: policy.explicitReserveTokens }
149
+ : {}),
150
+ };
151
+ }
@@ -0,0 +1,58 @@
1
+ const TRUTHY = new Set(["1", "true", "on"]);
2
+ const FALSY = new Set(["0", "false", "off"]);
3
+ export function parseEnabledFlag(raw, defaultEnabled) {
4
+ if (raw === undefined || raw.trim() === "") {
5
+ return { enabled: defaultEnabled };
6
+ }
7
+ const value = raw.trim().toLowerCase();
8
+ if (TRUTHY.has(value))
9
+ return { enabled: true };
10
+ if (FALSY.has(value))
11
+ return { enabled: false };
12
+ return {
13
+ enabled: defaultEnabled,
14
+ diagnostic: `invalid flag ${JSON.stringify(raw)}; clamped to default ${defaultEnabled}`,
15
+ };
16
+ }
17
+ export function parseContextPressureMode(raw) {
18
+ if (raw === undefined || raw.trim() === "")
19
+ return { mode: "safe" };
20
+ const value = raw.trim().toLowerCase();
21
+ if (value === "off" || value === "safe" || value === "strict") {
22
+ return { mode: value };
23
+ }
24
+ return {
25
+ mode: "safe",
26
+ diagnostic: `invalid HARNESS_CONTEXT_PRESSURE_MODE ${JSON.stringify(raw)}; clamped to safe`,
27
+ };
28
+ }
29
+ export function parseBoundedNumber(raw, fallback, min, max) {
30
+ if (raw === undefined || raw.trim() === "") {
31
+ return { value: fallback };
32
+ }
33
+ const parsed = Number(raw);
34
+ if (!Number.isFinite(parsed)) {
35
+ return {
36
+ value: fallback,
37
+ diagnostic: `invalid number ${JSON.stringify(raw)}; clamped to ${fallback}`,
38
+ };
39
+ }
40
+ const clamped = Math.min(max, Math.max(min, parsed));
41
+ if (clamped !== parsed) {
42
+ return {
43
+ value: clamped,
44
+ diagnostic: `number ${parsed} clamped to [${min}, ${max}] => ${clamped}`,
45
+ };
46
+ }
47
+ return { value: parsed };
48
+ }
49
+ /** Console/DAG sift: default ON; `0|false|off` disables. */
50
+ export function isConsoleSiftEnabled(env = process.env) {
51
+ return parseEnabledFlag(env.HARNESS_CONSOLE_SIFT, true);
52
+ }
53
+ export function isDagSiftEnabled(env = process.env) {
54
+ return parseEnabledFlag(env.HARNESS_DAG_SIFT, true);
55
+ }
56
+ export function readContextPressureMode(env = process.env) {
57
+ return parseContextPressureMode(env.HARNESS_CONTEXT_PRESSURE_MODE);
58
+ }
@@ -0,0 +1,100 @@
1
+ import { buildOverflowCheckpoint } from "./checkpoint.js";
2
+ import { shouldRewriteAssistantOverflow } from "./overflow.js";
3
+ import { clearRetrieveBypass, compressProviderPayload, createSiftRetrieveToolDefinition, SIFT_RETRIEVE_TOOL_NAME, } from "./sift-bridge.js";
4
+ import { emptyContextPressureCounters, incrementCounter, } from "./telemetry.js";
5
+ export function createCompactionController() {
6
+ return {
7
+ proactiveCompaction: false,
8
+ counters: emptyContextPressureCounters(),
9
+ };
10
+ }
11
+ export function createContextPressureExtension(options) {
12
+ const l2 = options.policy.mode !== "off";
13
+ const l1 = Boolean(options.sift);
14
+ if (!l2 && !l1)
15
+ return undefined;
16
+ return {
17
+ name: options.name ?? "operator-chat-context-pressure",
18
+ hidden: true,
19
+ factory: (api) => {
20
+ if (l2) {
21
+ api.on("message_end", (event) => {
22
+ const message = event.message;
23
+ if (!message || typeof message !== "object")
24
+ return undefined;
25
+ const rec = message;
26
+ if (typeof rec.errorMessage === "string") {
27
+ options.controller.lastError = rec.errorMessage;
28
+ }
29
+ if (rec.role === "assistant") {
30
+ clearRetrieveBypassIfPresent(options.sift);
31
+ }
32
+ const rewritten = shouldRewriteAssistantOverflow(message);
33
+ if (!rewritten)
34
+ return undefined;
35
+ incrementCounter(options.controller.counters, "overflow");
36
+ return {
37
+ message: {
38
+ ...rec,
39
+ errorMessage: rewritten,
40
+ },
41
+ };
42
+ });
43
+ api.on("session_before_compact", (event) => {
44
+ const reason = event.reason;
45
+ if (reason === "overflow") {
46
+ incrementCounter(options.controller.counters, "overflow");
47
+ }
48
+ if (reason === "manual") {
49
+ incrementCounter(options.controller.counters, "manual");
50
+ }
51
+ const useCheckpoint = options.policy.mode === "strict" &&
52
+ (reason === "overflow" ||
53
+ (reason === "manual" && options.controller.proactiveCompaction));
54
+ if (!useCheckpoint)
55
+ return undefined;
56
+ const checkpoint = buildOverflowCheckpoint({
57
+ preparation: event.preparation,
58
+ todos: options.controller.todos,
59
+ lastError: options.controller.lastError,
60
+ });
61
+ if (!checkpoint)
62
+ return undefined;
63
+ return {
64
+ compaction: {
65
+ summary: checkpoint.summary,
66
+ firstKeptEntryId: checkpoint.firstKeptEntryId,
67
+ tokensBefore: checkpoint.tokensBefore,
68
+ },
69
+ };
70
+ });
71
+ }
72
+ if (l1 && options.sift) {
73
+ const sift = options.sift;
74
+ api.on("before_provider_request", (event) => {
75
+ return compressProviderPayload(sift, event.payload);
76
+ });
77
+ api.on("message_end", (event) => {
78
+ const message = event.message;
79
+ if (message &&
80
+ typeof message === "object" &&
81
+ message.role === "assistant") {
82
+ clearRetrieveBypass(sift);
83
+ }
84
+ return undefined;
85
+ });
86
+ try {
87
+ api.registerTool?.(createSiftRetrieveToolDefinition(sift));
88
+ }
89
+ catch {
90
+ sift.diagnostics.add("register-tool-failed");
91
+ }
92
+ }
93
+ },
94
+ };
95
+ }
96
+ function clearRetrieveBypassIfPresent(sift) {
97
+ if (sift)
98
+ clearRetrieveBypass(sift);
99
+ }
100
+ export { SIFT_RETRIEVE_TOOL_NAME };