@tea-agent/loop-agent 0.40.0-next.8 → 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 -20
- 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/dag-pi-executor.js +3 -0
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +79 -6
- 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-BYBmDfPW.js → abnfDiagram-N423BO3Z-Dy71QJp3.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BKMmxpGF.js → arc-M25fNMTE.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DJpwxr1d.js → architectureDiagram-T3A2C74G-CUWlVl1a.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BaPj_ZlY.js → blockDiagram-VBNYF7ZC-Dd0Whpx2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8iGmHlm.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-DaxXQayc.js → chunk-2GRJ4B5K-D_1sUkyD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DqQKGA7q.js → chunk-2Q5K7J3B-Cxnxf36E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CgMKpJBB.js → chunk-5RXB4S5H-D9RG3G_x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BpToUlaW.js → chunk-5VM5RSS4-Zz9jgjLX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BsEWOHxv.js → chunk-6Q2QTUOP-BCggDRCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CKGSCe71.js → chunk-GF5L2VYU-CFg-5iAS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Bm1U5299.js → chunk-JWPE2WC7-DRQTE8x_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BI0ov870.js → chunk-KBJHAD2P-C_Dw7uZ8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BmnQo0iJ.js → chunk-RYQCIY6F-BjIiApsk.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BJBqa6Xc.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-BuN9-7AR.js → cose-bilkent-JH36ORCC-huHqH6WQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bw22jc2k.js → cynefin-VYW2F7L2-BSi6yZqP.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BHnSH8Dr.js → cynefinDiagram-MW4NZA55-Bs_Jp31y.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BoXXIegj.js → dagre-VZM6K2ZE-CYxavikT.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CU8qyPhT.js → diagram-7IWD3JNH-B6TErl71.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-lrjhF0S8.js → diagram-B4RE2ZJO-CvH4J2ii.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DL0QDEpo.js → diagram-LBJQPF4R-v1NBAqXS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DxVMJMBH.js → diagram-Q27KOJAE-CUNHVAn2.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CFg9FzvG.js → diagram-UB23O5K3-Dzz1gUu-.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CQgP9fTG.js → ebnfDiagram-BXEA7PRR-CCF8_LjK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-A5vQXx9U.js → erDiagram-JOGREHBK-DObdPVLa.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-ytXnLyXD.js → flowDiagram-UKHOOZJN-DlN8Pxrj.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CYuksfnS.js → ganttDiagram-PKOTCBZU-BJKIPTGX.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-f_jW3zgU.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-C3HLE-xV.js → infoDiagram-6WML65LV-BKrRKMww.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DwpgejS6.js → ishikawaDiagram-WSZJBQD7-CN5dk-yb.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ll0YWX1y.js → journeyDiagram-NVQOT4AX-B4P4KZ-Y.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Hq3rchvU.js → kanban-definition-27J2QSJJ-BRlteP32.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BaGjJx8z.js → linear-CG8D9fuL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CKGQHWX_.js → mermaid.core-BuB6P2rX.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DM1FA6Dr.js → mindmap-definition-FAOFIHXS-DYXGl_DR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Xa9fHkE2.js → pegDiagram-VL7TDLO6-BM48yEVG.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-pCd4PDqJ.js → pieDiagram-7S7Q4E2Y-mT8NL-Ow.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-KqjpEBLk.js → quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DgvR7LZD.js → railroadDiagram-AXF67PYL-BKAcr70m.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Bys1UCWA.js → requirementDiagram-LRYGKXZP-CRxcKpoO.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Ms9npnZt.js → sankeyDiagram-W5VNT64P-Ds4LxQDI.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DEIWrtQZ.js → sequenceDiagram-SI44F4Z6-CPLwPf_p.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DELJ0AhZ.js → sizeCapture-X5ZJPWSS-CL65uij0.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BpHt9wKh.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-GKRsBluT.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-Cwsq1DxL.js → timeline-definition-Z64GVDOM-Dw85f9L2.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CfaqtKx0.js → vennDiagram-T6HMQDX7-B4QLCqCy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BlEUtK_g.js → wardleyDiagram-T6FBY63Y-B_yKCWwn.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CHSRXPvO.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/spatial-overlay.js +1 -2
- 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 +18 -12
- package/dist/workflows/dag/init-hybrid.js +32 -9
- package/dist/workflows/dag/types.js +13 -0
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +27 -9
- 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-CrzAa3FA.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/index-BB6KxwT_.css +0 -1
- package/dist/worker/console/static/assets/index-D_wxbadT.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BEeSswlv.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B2m_wca8.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
3
5
|
import { computeExtensionToolAllowlistExtras } from "./pi-extension-resolver.js";
|
|
4
6
|
import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DEFAULT_ABORT_GRACE_MS, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, extractPiEventStopReason, extractAssistantTextFromPiJson, } from "./pi-executor.js";
|
|
5
7
|
import { serializeSessionEvent } from "./pi-event-serializer.js";
|
|
6
8
|
import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
|
|
9
|
+
import { createCompactionController, createContextPressureExtension, createSiftBridgeState, inMemoryCompactionSettings, isDagSiftEnabled, parseCompactionPolicy, parseSiftBridgeConfig, SIFT_RETRIEVE_TOOL_NAME, } from "../shared/pi-context-pressure/index.js";
|
|
7
10
|
/** Same-session recovery after a transport stall. Exactly one continue per SDK attempt. */
|
|
8
11
|
export const PI_SDK_STALL_CONTINUE_MAX = 1;
|
|
9
12
|
/**
|
|
@@ -215,15 +218,37 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
215
218
|
// Inject rate-limit-tolerant auto-retry settings for the session lifecycle.
|
|
216
219
|
// Prefer SettingsManager.inMemory so harness runs do not depend on ~/.pi or
|
|
217
220
|
// project .pi/settings.json being present/trusted.
|
|
221
|
+
const policy = parseCompactionPolicy();
|
|
222
|
+
const compaction = {
|
|
223
|
+
...PI_RECOMMENDED_COMPACTION,
|
|
224
|
+
...inMemoryCompactionSettings(policy),
|
|
225
|
+
};
|
|
218
226
|
const settingsManager = typeof SettingsManager?.inMemory === "function"
|
|
219
227
|
? SettingsManager.inMemory({
|
|
220
228
|
retry: {
|
|
221
229
|
...PI_RECOMMENDED_RETRY,
|
|
222
230
|
provider: { ...PI_RECOMMENDED_RETRY.provider },
|
|
223
231
|
},
|
|
224
|
-
compaction
|
|
232
|
+
compaction,
|
|
225
233
|
}, { projectTrusted: true })
|
|
226
234
|
: undefined;
|
|
235
|
+
const nodeSessionId = `dag-${randomUUID()}`;
|
|
236
|
+
const controller = createCompactionController();
|
|
237
|
+
const siftEnabled = isDagSiftEnabled();
|
|
238
|
+
const sift = siftEnabled.enabled
|
|
239
|
+
? createSiftBridgeState(parseSiftBridgeConfig({
|
|
240
|
+
sessionId: nodeSessionId,
|
|
241
|
+
defaultStashRoot: path.join(os.tmpdir(), "loop-agent-dag-sift"),
|
|
242
|
+
}))
|
|
243
|
+
: undefined;
|
|
244
|
+
if (sift)
|
|
245
|
+
sift.counters = controller.counters;
|
|
246
|
+
const pressureExtension = createContextPressureExtension({
|
|
247
|
+
name: "dag-pi-context-pressure",
|
|
248
|
+
policy,
|
|
249
|
+
controller,
|
|
250
|
+
sift,
|
|
251
|
+
});
|
|
227
252
|
const loader = new DefaultResourceLoader({
|
|
228
253
|
cwd: input.cwd,
|
|
229
254
|
agentDir,
|
|
@@ -233,6 +258,7 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
233
258
|
...(input.additionalExtensionPaths && input.additionalExtensionPaths.length > 0
|
|
234
259
|
? { additionalExtensionPaths: input.additionalExtensionPaths }
|
|
235
260
|
: {}),
|
|
261
|
+
...(pressureExtension ? { extensionFactories: [pressureExtension] } : {}),
|
|
236
262
|
...(settingsManager ? { settingsManager } : {}),
|
|
237
263
|
appendSystemPromptOverride: (base) => [
|
|
238
264
|
...base,
|
|
@@ -247,10 +273,12 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
247
273
|
// extras into the create-time allowlist; protected builtins, the deny
|
|
248
274
|
// surface, and write-capable extension tools never join.
|
|
249
275
|
const extensionToolExtras = [];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
extensionToolExtras.
|
|
276
|
+
const registered = collectLoaderExtensionToolNames(loader);
|
|
277
|
+
extensionToolExtras.push(...computeExtensionToolAllowlistExtras(registered.map((name) => ({ name })), input.toolNames));
|
|
278
|
+
if (siftEnabled.enabled &&
|
|
279
|
+
!extensionToolExtras.includes(SIFT_RETRIEVE_TOOL_NAME) &&
|
|
280
|
+
!input.toolNames.includes(SIFT_RETRIEVE_TOOL_NAME)) {
|
|
281
|
+
extensionToolExtras.push(...computeExtensionToolAllowlistExtras([{ name: SIFT_RETRIEVE_TOOL_NAME }], input.toolNames));
|
|
254
282
|
}
|
|
255
283
|
const sessionToolNames = [...input.toolNames, ...extensionToolExtras];
|
|
256
284
|
if (input.requireWriterCustomTools) {
|
|
@@ -485,6 +513,9 @@ function extractSdkUsageSample(event) {
|
|
|
485
513
|
const responseKey = responseKeyCandidates.find((value) => typeof value === "string" && value.length > 0);
|
|
486
514
|
return responseKey ? { responseKey, tokens } : { tokens };
|
|
487
515
|
}
|
|
516
|
+
function maxSdkContextTokens(samples) {
|
|
517
|
+
return samples.reduce((maximum, sample) => Math.max(maximum, sample.tokens), 0);
|
|
518
|
+
}
|
|
488
519
|
function aggregateSdkTokenUsage(samples) {
|
|
489
520
|
const identified = new Map();
|
|
490
521
|
let anonymousMaximum = 0;
|
|
@@ -537,6 +568,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
537
568
|
const stdoutPreview = new BoundedTextPreview("stdout");
|
|
538
569
|
const stdoutCollector = createPiJsonlStreamCollector();
|
|
539
570
|
const usageSamples = [];
|
|
571
|
+
let observedTurns = 0;
|
|
572
|
+
let contextBudgetExhausted = false;
|
|
573
|
+
let contextBudgetDetail = "";
|
|
540
574
|
// Writer-thinking-exhausted diagnosis evidence (AC-002). Collected in the
|
|
541
575
|
// subscribe callback regardless of shouldPersistSessionEvent filtering, so a
|
|
542
576
|
// length-stopped thinking-only attempt with no persisted write tool calls is
|
|
@@ -625,6 +659,7 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
625
659
|
});
|
|
626
660
|
let resolveStall;
|
|
627
661
|
let resolveSettlement;
|
|
662
|
+
let resolveContextBudget;
|
|
628
663
|
let settled = false;
|
|
629
664
|
let abortConfirmed = true;
|
|
630
665
|
const persistRuntimeNote = (event) => {
|
|
@@ -655,6 +690,30 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
655
690
|
clearTimeout(stallHandle);
|
|
656
691
|
stallHandle = setTimeout(() => resolveStall?.("stall"), stallTimeoutMs);
|
|
657
692
|
};
|
|
693
|
+
const checkContextBudget = () => {
|
|
694
|
+
if (contextBudgetExhausted || !options.contextBudget)
|
|
695
|
+
return;
|
|
696
|
+
// Context protection follows the largest provider-reported response
|
|
697
|
+
// snapshot, not cumulative billing usage across turns. Summing repeated
|
|
698
|
+
// input contexts would abort healthy multi-turn sessions far below the
|
|
699
|
+
// model's actual context window.
|
|
700
|
+
const observedTokens = maxSdkContextTokens(usageSamples);
|
|
701
|
+
const turnExceeded = options.contextBudget.maxTurns !== undefined &&
|
|
702
|
+
observedTurns >= options.contextBudget.maxTurns;
|
|
703
|
+
const tokenExceeded = options.contextBudget.maxTokens !== undefined &&
|
|
704
|
+
observedTokens >= options.contextBudget.maxTokens;
|
|
705
|
+
if (!turnExceeded && !tokenExceeded)
|
|
706
|
+
return;
|
|
707
|
+
contextBudgetExhausted = true;
|
|
708
|
+
contextBudgetDetail = [
|
|
709
|
+
"context-budget-exhausted:",
|
|
710
|
+
`observedTurns=${observedTurns}`,
|
|
711
|
+
`observedTokens=${observedTokens}`,
|
|
712
|
+
`maxTurns=${options.contextBudget.maxTurns ?? "unbounded"}`,
|
|
713
|
+
`maxTokens=${options.contextBudget.maxTokens ?? "unbounded"}`,
|
|
714
|
+
].join(" ");
|
|
715
|
+
resolveContextBudget?.("context-budget");
|
|
716
|
+
};
|
|
658
717
|
unsubscribe = session.subscribe((event) => {
|
|
659
718
|
if (event.type === "agent_settled" && !settled) {
|
|
660
719
|
settled = true;
|
|
@@ -678,6 +737,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
678
737
|
const usageSample = extractSdkUsageSample(event);
|
|
679
738
|
if (usageSample)
|
|
680
739
|
usageSamples.push(usageSample);
|
|
740
|
+
if (event.type === "turn_end")
|
|
741
|
+
observedTurns += 1;
|
|
742
|
+
checkContextBudget();
|
|
681
743
|
// Capture writer-thinking-exhausted evidence BEFORE the
|
|
682
744
|
// shouldPersistSessionEvent filter: thinking_delta is intentionally
|
|
683
745
|
// excluded from persisted JSONL, but must still count toward the
|
|
@@ -700,6 +762,11 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
700
762
|
const runTurn = async (userMessage) => {
|
|
701
763
|
const settlementPromise = resetSettlement();
|
|
702
764
|
const stallPromise = resetStallPromise();
|
|
765
|
+
const contextBudgetPromise = options.contextBudget
|
|
766
|
+
? new Promise((resolve) => {
|
|
767
|
+
resolveContextBudget = resolve;
|
|
768
|
+
})
|
|
769
|
+
: null;
|
|
703
770
|
const promptPromise = session.prompt(userMessage);
|
|
704
771
|
// A settlement event is permitted to win the race while prompt() remains
|
|
705
772
|
// pending. Observe a later rejection so it never becomes unhandled.
|
|
@@ -710,6 +777,7 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
710
777
|
settlementPromise,
|
|
711
778
|
...(timeoutPromise ? [timeoutPromise] : []),
|
|
712
779
|
...(stallPromise ? [stallPromise] : []),
|
|
780
|
+
...(contextBudgetPromise ? [contextBudgetPromise] : []),
|
|
713
781
|
]);
|
|
714
782
|
};
|
|
715
783
|
const disposeSession = async () => {
|
|
@@ -737,7 +805,12 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
737
805
|
: options.userMessage;
|
|
738
806
|
let continuesSent = 0;
|
|
739
807
|
let raced = await runTurn(promptMessage);
|
|
740
|
-
if (raced === "
|
|
808
|
+
if (raced === "context-budget") {
|
|
809
|
+
appendStderr(contextBudgetDetail);
|
|
810
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
811
|
+
await disposeSession();
|
|
812
|
+
}
|
|
813
|
+
else if (raced === "stall") {
|
|
741
814
|
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
742
815
|
const withinAbsoluteTimeout = timeoutMs <= 0 || Date.now() - startedAt < timeoutMs;
|
|
743
816
|
const canContinue = abortConfirmed &&
|
|
@@ -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
|
+
}
|