@tea-agent/loop-agent 0.40.0-next.9 → 0.40.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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +21 -21
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +9 -5
- package/dist/cli/program.js +38 -3
- package/dist/commands/client-recovery.js +8 -36
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/pi-sdk-executor.js +33 -5
- package/dist/executors/shell-executor.js +94 -20
- package/dist/governance/checks.js +1 -0
- package/dist/shared/operator/capabilities.js +9 -1
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +82 -11
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +285 -8
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/routes.js +495 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +27 -18
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/turn-process.js +42 -5
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/operator-actions.js +20 -5
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +179 -0
- package/dist/worker/console/routes.js +246 -6
- package/dist/worker/console/server.js +173 -42
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-Dy71QJp3.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-M25fNMTE.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-CUWlVl1a.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-Dd0Whpx2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-BdfeCkI5.js} +1 -1
- package/dist/worker/console/static/assets/channel-Ba0AKiEZ.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-D_1sUkyD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-Cxnxf36E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-D9RG3G_x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-Zz9jgjLX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-BCggDRCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-CFg-5iAS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DRQTE8x_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-C_Dw7uZ8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-BjIiApsk.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-B5XQD4KG.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-huHqH6WQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-BSi6yZqP.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Bs_Jp31y.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-CYxavikT.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-B6TErl71.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-CvH4J2ii.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-v1NBAqXS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-CUNHVAn2.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-Dzz1gUu-.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-CCF8_LjK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-DObdPVLa.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-DlN8Pxrj.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-BJKIPTGX.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-XcSSnObi.js} +1 -1
- package/dist/worker/console/static/assets/index-CIYzS16i.css +1 -0
- package/dist/worker/console/static/assets/index-CS3QOTfl.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BKrRKMww.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-CN5dk-yb.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4P4KZ-Y.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-BRlteP32.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-CG8D9fuL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-BuB6P2rX.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-DYXGl_DR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-BM48yEVG.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-mT8NL-Ow.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-BKAcr70m.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-CRxcKpoO.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-Ds4LxQDI.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-CPLwPf_p.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-CL65uij0.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CO0Mgd1W.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BUewAVYS.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-Cxv3TSNw.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bhe67Wjl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-Dw85f9L2.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-B4QLCqCy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-B_yKCWwn.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-nniBEGv2.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
- package/dist/worker/console/workspace-context.js +28 -3
- package/dist/worker/console/workspace-registry.js +333 -60
- package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +7 -2
- package/dist/worker/observe/static/styles.css +87 -25
- package/dist/worker/observe/static/views/dag-inspector.js +42 -1
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/worker/observe/static/views/session-timeline.js +135 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +5 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +5 -4
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/backend-test-dag.json +6 -5
- package/docs/templates/init-managed-agents.md +1 -1
- package/package.json +3 -2
- package/skills/loop-agent/references/command-reference.md +15 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
- 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"
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
.
|
|
877
|
-
.
|
|
878
|
-
|
|
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-
|
|
880
|
+
kind: "markdown-correspondence-contract",
|
|
884
881
|
classification: "test-asset-defect",
|
|
885
|
-
repairability: "
|
|
886
|
-
detail:
|
|
887
|
-
.
|
|
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)
|
|
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
|
|
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:
|
|
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
|
-
|
|
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}"',
|
|
@@ -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: "
|
|
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 };
|