@sema-agent/core 5.11.0 → 5.13.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/CHANGELOG.md +52 -0
- package/dist/agents/subagent.js +4 -2
- package/dist/core/auto-compaction.d.ts +12 -1
- package/dist/core/auto-compaction.js +3 -1
- package/dist/core/checkpoint-store.d.ts +8 -1
- package/dist/core/checkpoint-store.js +3 -1
- package/dist/core/compliance.d.ts +11 -0
- package/dist/core/compliance.js +34 -0
- package/dist/core/exec-gate.js +12 -1
- package/dist/core/governance-codes.d.ts +12 -0
- package/dist/core/governance-codes.js +24 -0
- package/dist/core/locked-config.d.ts +27 -0
- package/dist/core/locked-config.js +42 -0
- package/dist/core/memory-admission.d.ts +47 -0
- package/dist/core/memory-admission.js +156 -0
- package/dist/core/memory.d.ts +2 -0
- package/dist/core/memory.js +3 -2
- package/dist/core/retention.d.ts +36 -0
- package/dist/core/retention.js +31 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/compaction-call-options.d.ts +13 -1
- package/dist/core/runner/compaction-call-options.js +85 -0
- package/dist/core/runner/prepare-memory.d.ts +9 -0
- package/dist/core/runner/prepare-memory.js +28 -2
- package/dist/core/runner/prepare-task.d.ts +4 -0
- package/dist/core/runner/prepare-task.js +183 -26
- package/dist/core/runner/runtask.js +101 -47
- package/dist/core/runner/turn-attachments.js +3 -1
- package/dist/core/session-store.d.ts +1 -0
- package/dist/core/session-store.js +1 -0
- package/dist/core/tool-result-store.d.ts +2 -0
- package/dist/core/tool-result-store.js +1 -0
- package/dist/core/types.d.ts +12 -2
- package/dist/engine/compaction/compaction.d.ts +11 -2
- package/dist/engine/compaction/compaction.js +87 -9
- package/dist/engine/harness/agent-harness.js +11 -1
- package/dist/engine/llm/validation.js +11 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, MAX_S
|
|
|
3
3
|
import { engineVersion } from "../version.js";
|
|
4
4
|
import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
|
|
5
5
|
import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
|
|
6
|
-
import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
|
|
6
|
+
import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill, sanitizeCompactionSettings } from "../auto-compaction.js";
|
|
7
7
|
import { ASK_USER_QUESTION_TOOL_NAME, QUESTION_AWAITS_RESUME } from "../ask-question.js";
|
|
8
8
|
import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
|
|
9
9
|
import { emitTrace } from "../trace.js";
|
|
@@ -23,7 +23,7 @@ import { OUTPUT_TOOL_NAME, SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } f
|
|
|
23
23
|
import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
24
24
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
25
25
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
26
|
-
import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated } from "./compaction-call-options.js";
|
|
26
|
+
import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
|
|
27
27
|
import { prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
28
28
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
29
29
|
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
@@ -426,6 +426,12 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
426
426
|
if (declaredMaxTurns !== undefined && declaredMaxTurns > 0) {
|
|
427
427
|
ratios.push({ axis: "turn budget", ratio: (stats.turns + 1) / declaredMaxTurns });
|
|
428
428
|
}
|
|
429
|
+
if (walltimeMonotonicDeadline !== undefined) {
|
|
430
|
+
const walltimeWindowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
|
|
431
|
+
if (walltimeWindowMs > 0) {
|
|
432
|
+
ratios.push({ axis: "walltime budget", ratio: (performance.now() - rs.telemetry.taskStartMonotonic) / walltimeWindowMs });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
429
435
|
let tightest;
|
|
430
436
|
for (const r of ratios)
|
|
431
437
|
if (tightest === undefined || r.ratio > tightest.ratio)
|
|
@@ -490,7 +496,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
490
496
|
rs.attach.attachState.surfacedMtime.delete(p);
|
|
491
497
|
changed = scan.changed;
|
|
492
498
|
}
|
|
493
|
-
const backgroundTasksOn = rs.attach.attachmentsCfg?.backgroundTasks === true;
|
|
499
|
+
const backgroundTasksOn = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
|
|
494
500
|
const bgTasks = backgroundTasksOn && rs.attach.attachState.postCompactPending ? prepared.listBackgroundTasks() : undefined;
|
|
495
501
|
const toolsDeltaOn = rs.attach.attachmentsCfg?.toolsDelta === true;
|
|
496
502
|
const tdRef = toolsDeltaOn ? prepared.toolsDeltaRef : undefined;
|
|
@@ -760,6 +766,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
760
766
|
...centerAdoptionOption(prepared),
|
|
761
767
|
model: event.model,
|
|
762
768
|
compactionModel: prepared.compModel,
|
|
769
|
+
...forkContextOption(prepared, false),
|
|
763
770
|
brain: compactionBrain,
|
|
764
771
|
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
765
772
|
thinking: prepared.thinking,
|
|
@@ -1443,6 +1450,12 @@ export class Runner {
|
|
|
1443
1450
|
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1444
1451
|
errorCode: code,
|
|
1445
1452
|
...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
|
|
1453
|
+
...(() => {
|
|
1454
|
+
const hinted = err.retryAfterMs;
|
|
1455
|
+
return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
|
|
1456
|
+
? { retryAfterMs: hinted }
|
|
1457
|
+
: {};
|
|
1458
|
+
})(),
|
|
1446
1459
|
stats: { turns: 0, tokens: 0, toolCalls: 0, cachedTokens: 0, costMicroUsd: 0 },
|
|
1447
1460
|
};
|
|
1448
1461
|
emitTrace(spec.tracer ?? this.deps.tracer, () => ({
|
|
@@ -1696,9 +1709,7 @@ export class Runner {
|
|
|
1696
1709
|
queue.push({ type: "task_notification", notification: item.payload, ...notificationIdent() });
|
|
1697
1710
|
if (notificationHarness) {
|
|
1698
1711
|
const xml = renderTaskNotificationXml(item.payload);
|
|
1699
|
-
const deliver =
|
|
1700
|
-
? notificationHarness.followUp(xml, { provenance: "engine-note", enginePayload: item.payload })
|
|
1701
|
-
: notificationHarness.steer(xml, { provenance: "engine-note", enginePayload: item.payload });
|
|
1712
|
+
const deliver = notificationHarness.steer(xml, { provenance: "engine-note", enginePayload: item.payload });
|
|
1702
1713
|
void deliver.then(() => item.onDisposition?.("queued"), () => {
|
|
1703
1714
|
parkTaskNotification(item.payload, item.priority);
|
|
1704
1715
|
item.onDisposition?.("parked");
|
|
@@ -1927,7 +1938,8 @@ export class Runner {
|
|
|
1927
1938
|
rs.attach.agentListingOn = rs.attach.attachmentsCfg?.agentListing !== false && eventDefaultOn("agent_listing");
|
|
1928
1939
|
rs.attach.skillsListingOn = rs.attach.attachmentsCfg?.skillsListing !== false && eventDefaultOn("skills_listing");
|
|
1929
1940
|
const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
|
|
1930
|
-
|
|
1941
|
+
const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
|
|
1942
|
+
rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
|
|
1931
1943
|
rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.legDate } : undefined;
|
|
1932
1944
|
rs.attach.instrProbe = this.deps.probeInstructionSources;
|
|
1933
1945
|
rs.attach.instrState =
|
|
@@ -1940,7 +1952,7 @@ export class Runner {
|
|
|
1940
1952
|
: undefined;
|
|
1941
1953
|
rs.counters.cadenceTurns = 0;
|
|
1942
1954
|
rs.turn.lastTurnHadToolCalls = false;
|
|
1943
|
-
if (rs.attach.attachState !== undefined && rs.attach.attachmentsCfg?.backgroundTasks === true) {
|
|
1955
|
+
if (rs.attach.attachState !== undefined && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
|
|
1944
1956
|
try {
|
|
1945
1957
|
const branch = await prepared.session.getBranch();
|
|
1946
1958
|
for (let i = branch.length - 1; i >= 0; i--) {
|
|
@@ -2264,6 +2276,19 @@ export class Runner {
|
|
|
2264
2276
|
};
|
|
2265
2277
|
const withinTaskCompaction = (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true);
|
|
2266
2278
|
const compactionBreaker = { failures: 0 };
|
|
2279
|
+
if (spec.compaction?.enabled ?? true) {
|
|
2280
|
+
const prefixWindow = prepared.model.autoCompactTokens ?? prepared.model.contextTokens ?? prepared.model.contextWindow;
|
|
2281
|
+
if (Number.isFinite(prefixWindow) && prefixWindow > 0) {
|
|
2282
|
+
const prefixSettings = sanitizeCompactionSettings({ ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction }, prefixWindow);
|
|
2283
|
+
const prefixCompactAt = prefixWindow - prefixSettings.reserveTokens;
|
|
2284
|
+
if (prepared.promptOverheadTokens >= prefixCompactAt) {
|
|
2285
|
+
this.deps.onError?.(new Error(`compaction cannot help: the fixed request prefix (system prompt + tool schemas, ≈${prepared.promptOverheadTokens} tokens) ` +
|
|
2286
|
+
`already meets or exceeds the compaction threshold (${prefixCompactAt} of a ${prefixWindow}-token window). ` +
|
|
2287
|
+
`Compaction only shrinks conversation history, so this run will re-trigger or overflow regardless — ` +
|
|
2288
|
+
`shrink the system prompt/tool surface or use a larger-window model.`), { phase: "config", sessionId: prepared.sessionId });
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2267
2292
|
const windowSafetyOptions = (mainModel) => ({
|
|
2268
2293
|
...(rs.budget.maxCostMicroUsd !== undefined
|
|
2269
2294
|
? {
|
|
@@ -2302,6 +2327,22 @@ export class Runner {
|
|
|
2302
2327
|
});
|
|
2303
2328
|
const stopHook = (spec.hooks ?? this.deps.hooks)?.stop;
|
|
2304
2329
|
const finalVerificationOn = spec.finalVerification === true;
|
|
2330
|
+
const finalVerifyBudgetFill = () => {
|
|
2331
|
+
let worst = 0;
|
|
2332
|
+
if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0)
|
|
2333
|
+
worst = Math.max(worst, stats.tokens / rs.budget.maxTokensWindow);
|
|
2334
|
+
if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0)
|
|
2335
|
+
worst = Math.max(worst, stats.costMicroUsd / rs.budget.maxCostMicroUsd);
|
|
2336
|
+
if (walltimeMonotonicDeadline !== undefined && prepared.suspendForResource === undefined) {
|
|
2337
|
+
const windowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
|
|
2338
|
+
if (windowMs > 0)
|
|
2339
|
+
worst = Math.max(worst, (performance.now() - rs.telemetry.taskStartMonotonic) / windowMs);
|
|
2340
|
+
}
|
|
2341
|
+
return worst;
|
|
2342
|
+
};
|
|
2343
|
+
const emitFinalVerifyEcho = (body) => {
|
|
2344
|
+
queue.push({ type: "steering_injected", source: "final_verification", preview: body.slice(0, 220), ...ident() });
|
|
2345
|
+
};
|
|
2305
2346
|
if (stopHook || finalVerificationOn) {
|
|
2306
2347
|
let consecutiveBlocks = 0;
|
|
2307
2348
|
prepared.harness.setStopGate(async () => {
|
|
@@ -2311,58 +2352,69 @@ export class Runner {
|
|
|
2311
2352
|
(rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
|
|
2312
2353
|
rs.counters.wroteThisRun &&
|
|
2313
2354
|
prepared.outputRef.set !== true &&
|
|
2314
|
-
!(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1)
|
|
2355
|
+
!(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1) &&
|
|
2356
|
+
finalVerifyBudgetFill() < 0.9) {
|
|
2315
2357
|
rs.counters.finalVerifyInjections += 1;
|
|
2316
2358
|
if (rs.counters.finalVerifyInjections === 2) {
|
|
2359
|
+
const reentryBody = "<system-reminder>[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
|
|
2360
|
+
"or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
|
|
2361
|
+
"embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
|
|
2362
|
+
"execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
|
|
2363
|
+
"declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
|
|
2364
|
+
"reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
|
|
2365
|
+
"A closing statement without a reported check result is not verification. If you already ran such a check, state " +
|
|
2366
|
+
"its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
|
|
2367
|
+
"this verification gate — it will not intervene again.</system-reminder>";
|
|
2368
|
+
emitFinalVerifyEcho(reentryBody);
|
|
2317
2369
|
return [
|
|
2318
2370
|
{
|
|
2319
2371
|
role: "user",
|
|
2320
2372
|
engineMinted: true,
|
|
2321
|
-
content:
|
|
2322
|
-
"or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
|
|
2323
|
-
"embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
|
|
2324
|
-
"execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
|
|
2325
|
-
"declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
|
|
2326
|
-
"reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
|
|
2327
|
-
"A closing statement without a reported check result is not verification. If you already ran such a check, state " +
|
|
2328
|
-
"its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
|
|
2329
|
-
"this verification gate — it will not intervene again.</system-reminder>",
|
|
2373
|
+
content: reentryBody,
|
|
2330
2374
|
timestamp: Date.now(),
|
|
2331
2375
|
},
|
|
2332
2376
|
];
|
|
2333
2377
|
}
|
|
2378
|
+
const nudgeBody = "<system-reminder>[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
|
|
2379
|
+
"exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
|
|
2380
|
+
"output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
|
|
2381
|
+
"stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
|
|
2382
|
+
"finishing. " +
|
|
2383
|
+
"Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
|
|
2384
|
+
"state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
|
|
2385
|
+
"do not change that state merely to test it. This constrains HOW you verify — it is never a " +
|
|
2386
|
+
"license to skip the real acceptance path or to check a substitute of your own making: expected " +
|
|
2387
|
+
"values must come from the task's requirements, never from content you generated. If the real " +
|
|
2388
|
+
"acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
|
|
2389
|
+
"required final state, and verify that final state before finishing. " +
|
|
2390
|
+
"If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
|
|
2391
|
+
"(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
|
|
2392
|
+
"it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
|
|
2393
|
+
"produced it matches the task's requirements. " +
|
|
2394
|
+
"Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
|
|
2395
|
+
"task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
|
|
2396
|
+
"correctness — and cross-check through an independent second path where the task or environment offers one " +
|
|
2397
|
+
"(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
|
|
2398
|
+
"not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
|
|
2399
|
+
"lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
|
|
2400
|
+
"If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
|
|
2401
|
+
"reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
|
|
2402
|
+
"that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
|
|
2403
|
+
"no reported check result means the work is not finished. " +
|
|
2404
|
+
"If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
|
|
2405
|
+
"oracle to check against, briefly confirm completion and stop. " +
|
|
2406
|
+
"Residue YOUR OWN testing created (scratch files, running processes, generated outputs the task does not ask for) " +
|
|
2407
|
+
"is not protected state — if the task's required final state is a clean target, removing your own residue is part " +
|
|
2408
|
+
"of delivering it. " +
|
|
2409
|
+
"Verification must never LAUNDER uncertainty: if part of your conclusion was uncertain before this check, keep " +
|
|
2410
|
+
"reporting it as uncertain unless the check you actually ran resolved it — a re-stated conclusion is not new " +
|
|
2411
|
+
"evidence.</system-reminder>";
|
|
2412
|
+
emitFinalVerifyEcho(nudgeBody);
|
|
2334
2413
|
return [
|
|
2335
2414
|
{
|
|
2336
2415
|
role: "user",
|
|
2337
2416
|
engineMinted: true,
|
|
2338
|
-
content:
|
|
2339
|
-
"exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
|
|
2340
|
-
"output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
|
|
2341
|
-
"stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
|
|
2342
|
-
"finishing. " +
|
|
2343
|
-
"Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
|
|
2344
|
-
"state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
|
|
2345
|
-
"do not change that state merely to test it. This constrains HOW you verify — it is never a " +
|
|
2346
|
-
"license to skip the real acceptance path or to check a substitute of your own making: expected " +
|
|
2347
|
-
"values must come from the task's requirements, never from content you generated. If the real " +
|
|
2348
|
-
"acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
|
|
2349
|
-
"required final state, and verify that final state before finishing. " +
|
|
2350
|
-
"If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
|
|
2351
|
-
"(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
|
|
2352
|
-
"it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
|
|
2353
|
-
"produced it matches the task's requirements. " +
|
|
2354
|
-
"Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
|
|
2355
|
-
"task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
|
|
2356
|
-
"correctness — and cross-check through an independent second path where the task or environment offers one " +
|
|
2357
|
-
"(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
|
|
2358
|
-
"not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
|
|
2359
|
-
"lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
|
|
2360
|
-
"If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
|
|
2361
|
-
"reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
|
|
2362
|
-
"that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
|
|
2363
|
-
"no reported check result means the work is not finished. " +
|
|
2364
|
-
"If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
|
|
2365
|
-
"oracle to check against, briefly confirm completion and stop.</system-reminder>",
|
|
2417
|
+
content: nudgeBody,
|
|
2366
2418
|
timestamp: Date.now(),
|
|
2367
2419
|
},
|
|
2368
2420
|
];
|
|
@@ -2433,6 +2485,7 @@ export class Runner {
|
|
|
2433
2485
|
...centerAdoptionOption(prepared),
|
|
2434
2486
|
model: prepared.harness.getModel(),
|
|
2435
2487
|
compactionModel: prepared.compModel,
|
|
2488
|
+
...forkContextOption(prepared, true),
|
|
2436
2489
|
brain: compactionBrain,
|
|
2437
2490
|
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
2438
2491
|
thinking: prepared.thinking,
|
|
@@ -2801,7 +2854,7 @@ export class Runner {
|
|
|
2801
2854
|
});
|
|
2802
2855
|
}
|
|
2803
2856
|
}
|
|
2804
|
-
if (rs.attach.attachState?.postCompactPending === true && rs.attach.attachmentsCfg?.backgroundTasks === true) {
|
|
2857
|
+
if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
|
|
2805
2858
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
2806
2859
|
}
|
|
2807
2860
|
const comp = await this.finish(spec, prepared, {
|
|
@@ -3649,6 +3702,7 @@ export class Runner {
|
|
|
3649
3702
|
...centerAdoptionOption(prepared),
|
|
3650
3703
|
model: prepared.model,
|
|
3651
3704
|
compactionModel: prepared.compModel,
|
|
3705
|
+
...forkContextOption(prepared, false),
|
|
3652
3706
|
brain: opts?.brain ?? this.deps.brain,
|
|
3653
3707
|
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
3654
3708
|
thinking: prepared.thinking,
|
|
@@ -367,7 +367,9 @@ function renderBackgroundTasks(tasks) {
|
|
|
367
367
|
? "stopped"
|
|
368
368
|
: t.status === "running"
|
|
369
369
|
? "still running in background"
|
|
370
|
-
: t.status
|
|
370
|
+
: t.status === "parked"
|
|
371
|
+
? "parked awaiting an out-of-band approval — do NOT re-issue the gated call"
|
|
372
|
+
: t.status;
|
|
371
373
|
return `- [${t.id}] Task "${(t.description ?? "background task").slice(0, PROJECTION_CONTENT_MAX)}" ${phrase}`;
|
|
372
374
|
});
|
|
373
375
|
return ("Context was compacted. Background tasks from before the compaction (check output with TaskOutput, " +
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentTool } from "../internal/harness-types.js";
|
|
2
2
|
export interface ToolResultStore {
|
|
3
|
+
readonly retention?: import("./retention.js").RetentionDeclaration;
|
|
3
4
|
put(ref: string, content: string): Promise<void> | void;
|
|
4
5
|
get(ref: string, opts?: {
|
|
5
6
|
offset?: number;
|
|
@@ -15,6 +16,7 @@ export interface ToolResultSlice {
|
|
|
15
16
|
}
|
|
16
17
|
export declare class InMemoryToolResultStore implements ToolResultStore {
|
|
17
18
|
private readonly opts?;
|
|
19
|
+
readonly retention: "none";
|
|
18
20
|
private readonly map;
|
|
19
21
|
private totalChars;
|
|
20
22
|
constructor(opts?: {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { AgentTool, ThinkingLevel } from "../internal/harness.js";
|
|
|
3
3
|
import type { CompleteSimpleFn, DocumentContent, ImageContent, Model, ResilienceOptions, StreamFn, TextContent } from "../internal/llm.js";
|
|
4
4
|
import type { TaskNotificationPayload } from "./task-notification.js";
|
|
5
5
|
export type ModelRef = string | Model;
|
|
6
|
+
export interface StaleToolResultOffloadOptions {
|
|
7
|
+
keepRecentPerTool?: number;
|
|
8
|
+
minSavingsChars?: number;
|
|
9
|
+
}
|
|
6
10
|
export type ModelRole = "default" | "summarize" | "subagent" | "team" | "synthesize" | "advisor" | "verifier" | "classifier";
|
|
7
11
|
export type RoleSpec = ModelRef | {
|
|
8
12
|
model?: ModelRef;
|
|
@@ -371,6 +375,7 @@ export interface TaskSpec {
|
|
|
371
375
|
maxFiles?: number;
|
|
372
376
|
maxCharsPerFile?: number;
|
|
373
377
|
};
|
|
378
|
+
staleToolResultOffload?: StaleToolResultOffloadOptions;
|
|
374
379
|
clampTolerance?: number;
|
|
375
380
|
};
|
|
376
381
|
attachments?: {
|
|
@@ -382,7 +387,7 @@ export interface TaskSpec {
|
|
|
382
387
|
};
|
|
383
388
|
planModeReminder?: true;
|
|
384
389
|
budgetUsd?: true;
|
|
385
|
-
backgroundTasks?:
|
|
390
|
+
backgroundTasks?: boolean;
|
|
386
391
|
toolsDelta?: true;
|
|
387
392
|
agentListing?: boolean;
|
|
388
393
|
skillsListing?: boolean;
|
|
@@ -579,7 +584,7 @@ export type TaskEvent = ({
|
|
|
579
584
|
reason?: string;
|
|
580
585
|
} & TaskEventIdentity) | ({
|
|
581
586
|
type: "steering_injected";
|
|
582
|
-
source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
587
|
+
source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification";
|
|
583
588
|
preview: string;
|
|
584
589
|
} & TaskEventIdentity) | ({
|
|
585
590
|
type: "diagnostics";
|
|
@@ -732,6 +737,11 @@ export interface RunnerDeps {
|
|
|
732
737
|
fileSnapshotStore?: import("./file-snapshot-store.js").FileSnapshotStore;
|
|
733
738
|
sessionPolicyStore?: import("./session-policy-store.js").SessionPolicyStore;
|
|
734
739
|
runtimeCapsResolver?: (principal: string | undefined) => RuntimeCaps | undefined | Promise<RuntimeCaps | undefined>;
|
|
740
|
+
lockedConfig?: import("./locked-config.js").LockedConfig;
|
|
741
|
+
compliancePostureResolver?: (principal: string | undefined) => import("./compliance.js").CompliancePosture | undefined | Promise<import("./compliance.js").CompliancePosture | undefined>;
|
|
742
|
+
memoryScopeAdmission?: import("./memory-admission.js").MemoryScopeAdmission;
|
|
743
|
+
deploymentMemoryScopes?: readonly string[];
|
|
744
|
+
retentionPolicy?: import("./retention.js").RetentionPolicy;
|
|
735
745
|
autoMode?: {
|
|
736
746
|
rules?: import("./auto-mode-prompt.js").AutoModeRules;
|
|
737
747
|
settingsDenyRules?: readonly string[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Model, StreamFn, Usage } from "../llm/index.js";
|
|
1
|
+
import type { Context, Message, Model, StreamFn, Tool, Usage } from "../llm/index.js";
|
|
2
2
|
import { type AgentCoreCompletionRuntimeDeps } from "../loop/runtime-deps.js";
|
|
3
3
|
import type { AgentMessage, ThinkingLevel } from "../loop/types.js";
|
|
4
4
|
import { CompactionError, type Result, type SessionTreeEntry } from "../harness/types.js";
|
|
@@ -47,6 +47,15 @@ export interface CutPointResult {
|
|
|
47
47
|
}
|
|
48
48
|
export declare function findCutPoint(entries: SessionTreeEntry[], startIndex: number, endIndex: number, keepRecentTokens: number, charsPerToken?: number): CutPointResult;
|
|
49
49
|
export declare const SUMMARIZATION_SYSTEM_PROMPT = "You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.";
|
|
50
|
+
export interface CompactionForkContext {
|
|
51
|
+
systemPrompt?: string;
|
|
52
|
+
systemBlocks?: Context["systemBlocks"];
|
|
53
|
+
messages: Message[];
|
|
54
|
+
tools?: Tool[];
|
|
55
|
+
modelId?: string;
|
|
56
|
+
}
|
|
57
|
+
export declare function extractForkSummaryEnvelope(text: string): string | undefined;
|
|
58
|
+
export declare function forkSummarizationInstruction(customInstructions?: string): string;
|
|
50
59
|
export declare function summaryOutputBudgetTokens(model: Model, settings: CompactionSettings): number;
|
|
51
60
|
export interface SummarizationInputTruncation {
|
|
52
61
|
label: "history" | "turn_prefix";
|
|
@@ -77,5 +86,5 @@ export interface CompactionPreparation {
|
|
|
77
86
|
}
|
|
78
87
|
export declare function prepareCompaction(pathEntries: SessionTreeEntry[], settings: CompactionSettings, charsPerToken?: number, windowTokens?: number): Result<CompactionPreparation | undefined, CompactionError>;
|
|
79
88
|
export { computeFileLists, serializeConversation } from "./utils.js";
|
|
80
|
-
export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string | undefined, headers?: Record<string, string>, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn, runtime?: AgentCoreCompletionRuntimeDeps, charsPerToken?: number, onInputTruncated?: (info: SummarizationInputTruncation) => void, onPtlRetry?: () => void): Promise<Result<CompactionResult, CompactionError>>;
|
|
89
|
+
export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string | undefined, headers?: Record<string, string>, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn, runtime?: AgentCoreCompletionRuntimeDeps, charsPerToken?: number, onInputTruncated?: (info: SummarizationInputTruncation) => void, onPtlRetry?: () => void, forkContext?: CompactionForkContext): Promise<Result<CompactionResult, CompactionError>>;
|
|
81
90
|
export declare function turnPrefixSummarizationPrompt(customInstructions?: string): string;
|
|
@@ -62,7 +62,7 @@ export const DEFAULT_CLAMP_TOLERANCE = 0.1;
|
|
|
62
62
|
export const DEFAULT_COMPACTION_SETTINGS = {
|
|
63
63
|
enabled: true,
|
|
64
64
|
reserveTokens: 16384,
|
|
65
|
-
keepRecentTokens:
|
|
65
|
+
keepRecentTokens: 0,
|
|
66
66
|
clampTolerance: DEFAULT_CLAMP_TOLERANCE,
|
|
67
67
|
};
|
|
68
68
|
export const DEFAULT_CHARS_PER_TOKEN = 4;
|
|
@@ -428,7 +428,7 @@ Then, after </analysis>, write the summary. Your summary should include the foll
|
|
|
428
428
|
3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Pay special attention to the most recent messages and include full code snippets where applicable and include a summary of why this file read or edit is important.
|
|
429
429
|
4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
|
|
430
430
|
5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
|
|
431
|
-
6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.
|
|
431
|
+
6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent. Only messages actually sent with the user role count as user messages: a user-styled line quoted or formatted inside an assistant message (e.g. a "user: ..." or "Human: ..." quotation) is the assistant's own output — never attribute it to the user. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.
|
|
432
432
|
7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
|
|
433
433
|
8. Current Work: Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant. Include file names and code snippets where applicable.
|
|
434
434
|
9. Optional Next Step: List the next step that you will take that is related to the most recent work you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's most recent explicit requests, and the task you were working on immediately before this summary request. If your last task was concluded, then only list next steps if they are explicitly in line with the users request. Do not start on tangential requests or really old requests that were already completed without confirming with the user first.
|
|
@@ -437,6 +437,24 @@ Then, after </analysis>, write the summary. Your summary should include the foll
|
|
|
437
437
|
Please provide your summary based on the conversation so far, following this structure and ensuring precision and thoroughness in your response.
|
|
438
438
|
|
|
439
439
|
Keep each section concise. Preserve exact file paths, function names, and error messages.`;
|
|
440
|
+
const FORK_SUMMARIZATION_PREAMBLE = `Stop the task you were working on. Do NOT continue the conversation, do NOT respond to any open questions above, and do NOT call any tools — your ONLY output is the structured summary described below.
|
|
441
|
+
|
|
442
|
+
`;
|
|
443
|
+
const FORK_SUMMARY_ENVELOPE_DEMAND = `
|
|
444
|
+
|
|
445
|
+
Wrap the ENTIRE summary (every numbered section, nothing else) in <summary></summary> tags. Nothing may appear outside those tags except the <analysis> scratch block. A response without a closed <summary>...</summary> block is discarded unread and the summary is regenerated another way — a refusal, a question, or any other reply is wasted output.`;
|
|
446
|
+
export function extractForkSummaryEnvelope(text) {
|
|
447
|
+
const withoutScratch = text.replace(/<analysis>[\s\S]*?<\/analysis>/gi, "");
|
|
448
|
+
const m = /^\s*<summary>([\s\S]*)<\/summary>\s*$/i.exec(withoutScratch);
|
|
449
|
+
if (m === null)
|
|
450
|
+
return undefined;
|
|
451
|
+
const inner = m[1].trim();
|
|
452
|
+
return inner === "" ? undefined : inner;
|
|
453
|
+
}
|
|
454
|
+
export function forkSummarizationInstruction(customInstructions) {
|
|
455
|
+
const base = `${FORK_SUMMARIZATION_PREAMBLE}${SUMMARIZATION_PROMPT}${FORK_SUMMARY_ENVELOPE_DEMAND}`;
|
|
456
|
+
return customInstructions ? `${base}\n\nAdditional Instructions:\n${customInstructions}` : base;
|
|
457
|
+
}
|
|
440
458
|
const UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.
|
|
441
459
|
|
|
442
460
|
Update the existing structured summary with new information. RULES:
|
|
@@ -455,7 +473,7 @@ First, inside an <analysis>...</analysis> block, note what is new since the prev
|
|
|
455
473
|
3. Files and Code Sections: [Preserve entries still relevant; add newly examined, modified, or created files with full code snippets where applicable]
|
|
456
474
|
4. Errors and fixes: [Preserve previous errors and fixes and add new ones; keep any user correction or "change of approach" feedback verbatim.]
|
|
457
475
|
5. Problem Solving: [Update problems solved and any ongoing troubleshooting efforts]
|
|
458
|
-
6. All user messages: [Preserve previously-recorded user messages VERBATIM and append any new ones that are not tool results, in order. To bound growth across repeated compactions, keep roughly the most recent 20 messages verbatim; older ones beyond that may be condensed to a single line each — but NEVER drop or paraphrase a user correction or change of direction. The exact words of recent messages are the strongest anti-drift signal. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.]
|
|
476
|
+
6. All user messages: [Preserve previously-recorded user messages VERBATIM and append any new ones that are not tool results, in order. Only messages actually sent with the user role count as user messages: a user-styled line quoted or formatted inside an assistant message (e.g. a "user: ..." or "Human: ..." quotation) is the assistant's own output — never attribute it to the user. To bound growth across repeated compactions, keep roughly the most recent 20 messages verbatim; older ones beyond that may be condensed to a single line each — but NEVER drop or paraphrase a user correction or change of direction. The exact words of recent messages are the strongest anti-drift signal. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.]
|
|
459
477
|
7. Pending Tasks: [Update based on progress — remove completed tasks, add newly requested ones]
|
|
460
478
|
8. Current Work: [Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant]
|
|
461
479
|
9. Optional Next Step: [Update based on current state. IMPORTANT: ensure that this step is DIRECTLY in line with the user's most recent explicit requests, and the task you were working on immediately before this summary request — include a direct verbatim quote of that request. Do not start on tangential requests or really old requests that were already completed.]
|
|
@@ -678,6 +696,15 @@ async function summarizeWithPtlRetry(req) {
|
|
|
678
696
|
disclose(beforeChars - Math.max(remainingChars, 0), Math.max(remainingChars, 0));
|
|
679
697
|
}
|
|
680
698
|
}
|
|
699
|
+
function markEmptySummaryClass(e) {
|
|
700
|
+
const carrier = e;
|
|
701
|
+
carrier.semaSummaryEmptyClass = true;
|
|
702
|
+
return e;
|
|
703
|
+
}
|
|
704
|
+
function isEmptySummaryClass(e) {
|
|
705
|
+
const carrier = e;
|
|
706
|
+
return carrier.semaSummaryEmptyClass === true;
|
|
707
|
+
}
|
|
681
708
|
function stripAnalysisScratch(text, lengthTruncated) {
|
|
682
709
|
let out = text.replace(/<analysis>[\s\S]*?<\/analysis>\s*/gi, "");
|
|
683
710
|
if (lengthTruncated) {
|
|
@@ -705,8 +732,8 @@ async function summarizeWithLengthRecovery(label, model, context, baseMaxTokens,
|
|
|
705
732
|
maxTokens = Math.min(cap, Math.max(maxTokens * 2, SUMMARY_REASONING_FLOOR));
|
|
706
733
|
continue;
|
|
707
734
|
}
|
|
708
|
-
return err(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=error, max_tokens exhausted by reasoning ` +
|
|
709
|
-
`after ${attempt + 1} attempt(s): ${response.errorMessage || "no detail"})`));
|
|
735
|
+
return err(markEmptySummaryClass(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=error, max_tokens exhausted by reasoning ` +
|
|
736
|
+
`after ${attempt + 1} attempt(s): ${response.errorMessage || "no detail"})`)));
|
|
710
737
|
}
|
|
711
738
|
return err(new CompactionError("summarization_failed", `${label} failed: ${response.errorMessage || "Unknown error"}`));
|
|
712
739
|
}
|
|
@@ -723,10 +750,43 @@ async function summarizeWithLengthRecovery(label, model, context, baseMaxTokens,
|
|
|
723
750
|
maxTokens = Math.min(cap, Math.max(maxTokens * 2, SUMMARY_REASONING_FLOOR));
|
|
724
751
|
continue;
|
|
725
752
|
}
|
|
726
|
-
return err(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=${response.stopReason}` +
|
|
727
|
-
`${lengthTruncated ? ", the output was analysis scratch cut at max_tokens" : ""})`));
|
|
753
|
+
return err(markEmptySummaryClass(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=${response.stopReason}` +
|
|
754
|
+
`${lengthTruncated ? ", the output was analysis scratch cut at max_tokens" : ""})`)));
|
|
728
755
|
}
|
|
729
756
|
}
|
|
757
|
+
async function forkSummarize(fork, model, baseMaxTokens, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken) {
|
|
758
|
+
const instruction = forkSummarizationInstruction(customInstructions);
|
|
759
|
+
const context = {
|
|
760
|
+
...(fork.systemPrompt !== undefined ? { systemPrompt: fork.systemPrompt } : {}),
|
|
761
|
+
...(fork.systemBlocks !== undefined ? { systemBlocks: fork.systemBlocks } : {}),
|
|
762
|
+
...(fork.tools !== undefined && fork.tools.length > 0 ? { tools: fork.tools } : {}),
|
|
763
|
+
messages: [
|
|
764
|
+
...fork.messages,
|
|
765
|
+
{ role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() },
|
|
766
|
+
],
|
|
767
|
+
};
|
|
768
|
+
let result;
|
|
769
|
+
try {
|
|
770
|
+
result = await summarizeWithLengthRecovery("Summarization", model, context, baseMaxTokens, apiKey, headers, signal, thinkingLevel, streamFn, runtime, charsPerToken);
|
|
771
|
+
}
|
|
772
|
+
catch (e) {
|
|
773
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
774
|
+
if (parsePromptTooLong(msg).isPtl)
|
|
775
|
+
return { kind: "fallback", detail: msg };
|
|
776
|
+
throw e;
|
|
777
|
+
}
|
|
778
|
+
if (!result.ok) {
|
|
779
|
+
if (result.error.code === "summarization_failed" && (isEmptySummaryClass(result.error) || parsePromptTooLong(result.error.message).isPtl)) {
|
|
780
|
+
return { kind: "fallback", detail: result.error.message };
|
|
781
|
+
}
|
|
782
|
+
return { kind: "err", error: result.error };
|
|
783
|
+
}
|
|
784
|
+
const enveloped = extractForkSummaryEnvelope(result.value);
|
|
785
|
+
if (enveloped === undefined) {
|
|
786
|
+
return { kind: "fallback", detail: "fork response lacked a closed <summary> envelope (non-conforming output)" };
|
|
787
|
+
}
|
|
788
|
+
return { kind: "ok", summary: enveloped };
|
|
789
|
+
}
|
|
730
790
|
export async function generateSummary(currentMessages, model, summaryBudgetTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry) {
|
|
731
791
|
let basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;
|
|
732
792
|
if (customInstructions) {
|
|
@@ -866,7 +926,7 @@ Summarize the prefix to provide context for the retained suffix:
|
|
|
866
926
|
|
|
867
927
|
Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
868
928
|
export { computeFileLists, serializeConversation } from "./utils.js";
|
|
869
|
-
export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry) {
|
|
929
|
+
export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry, forkContext) {
|
|
870
930
|
const { firstKeptEntryId, messagesToSummarize, turnPrefixMessages, isSplitTurn, tokensBefore, previousSummary, fileOps, invokedSkills, persistedOutputRefs, elidedMessages, settings, } = preparation;
|
|
871
931
|
if (!firstKeptEntryId) {
|
|
872
932
|
return err(new CompactionError("invalid_session", "First kept entry has no UUID - session may need migration"));
|
|
@@ -876,7 +936,25 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
876
936
|
}
|
|
877
937
|
let summary;
|
|
878
938
|
const summaryBudget = summaryOutputBudgetTokens(model, settings);
|
|
879
|
-
if (
|
|
939
|
+
if (forkContext !== undefined && forkContext.messages.length > 0) {
|
|
940
|
+
const forked = await forkSummarize(forkContext, model, Math.floor(0.8 * summaryBudget), apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken);
|
|
941
|
+
if (forked.kind === "ok") {
|
|
942
|
+
summary = forked.summary;
|
|
943
|
+
}
|
|
944
|
+
else if (forked.kind === "err") {
|
|
945
|
+
return err(forked.error);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
try {
|
|
949
|
+
onPtlRetry?.();
|
|
950
|
+
}
|
|
951
|
+
catch {
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (summary !== undefined) {
|
|
956
|
+
}
|
|
957
|
+
else if (isSplitTurn && turnPrefixMessages.length > 0) {
|
|
880
958
|
const [historyResult, turnPrefixResult] = await Promise.all([
|
|
881
959
|
messagesToSummarize.length > 0
|
|
882
960
|
? generateSummary(messagesToSummarize, model, summaryBudget, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry)
|
|
@@ -27,6 +27,7 @@ function createUserMessage(text, images, provenance) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
const engineNotePayloads = new WeakMap();
|
|
30
|
+
const ENGINE_NOTE_STEER_BACKLOG_CAP = 50;
|
|
30
31
|
function createFailureMessage(model, error, aborted) {
|
|
31
32
|
return {
|
|
32
33
|
role: "assistant",
|
|
@@ -418,7 +419,13 @@ export class AgentHarness {
|
|
|
418
419
|
};
|
|
419
420
|
}
|
|
420
421
|
async drainQueuedMessages(queue, mode) {
|
|
421
|
-
|
|
422
|
+
let count = mode === "all" ? queue.length : 1;
|
|
423
|
+
if (mode !== "all" && queue.length > 1 && engineNotePayloads.has(queue[0])) {
|
|
424
|
+
count = 1;
|
|
425
|
+
while (count < queue.length && engineNotePayloads.has(queue[count]))
|
|
426
|
+
count++;
|
|
427
|
+
}
|
|
428
|
+
const messages = queue.splice(0, count);
|
|
422
429
|
if (messages.length === 0) {
|
|
423
430
|
return messages;
|
|
424
431
|
}
|
|
@@ -713,6 +720,9 @@ export class AgentHarness {
|
|
|
713
720
|
async enqueueInjection(queue, text, options) {
|
|
714
721
|
if (AgentHarness.emptyInjection(text, options))
|
|
715
722
|
return;
|
|
723
|
+
if (options?.enginePayload !== undefined && queue.filter((q) => engineNotePayloads.has(q)).length >= ENGINE_NOTE_STEER_BACKLOG_CAP) {
|
|
724
|
+
throw new AgentHarnessError("invalid_state", `engine-note backlog at cap (${ENGINE_NOTE_STEER_BACKLOG_CAP}) — park the payload for the session's next run`);
|
|
725
|
+
}
|
|
716
726
|
const m = createUserMessage(text, options?.images, options);
|
|
717
727
|
if (options?.enginePayload !== undefined)
|
|
718
728
|
engineNotePayloads.set(m, options.enginePayload);
|
|
@@ -288,5 +288,15 @@ export function validateToolArguments(tool, toolCall) {
|
|
|
288
288
|
.Errors(args)
|
|
289
289
|
.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)
|
|
290
290
|
.join("\n") || "Unknown validation error";
|
|
291
|
-
|
|
291
|
+
const schemaJson = (() => {
|
|
292
|
+
try {
|
|
293
|
+
const s = JSON.stringify(tool.parameters);
|
|
294
|
+
return s.length > VALIDATION_ERROR_SCHEMA_MAX_CHARS ? `${s.slice(0, VALIDATION_ERROR_SCHEMA_MAX_CHARS)}… (schema truncated)` : s;
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return "(schema not serializable)";
|
|
298
|
+
}
|
|
299
|
+
})();
|
|
300
|
+
throw new Error(`Validation failed for tool "${toolCall.name}":\n${errors}\n\nReceived arguments:\n${JSON.stringify(toolCall.arguments, null, 2)}\n\nExpected parameter schema:\n${schemaJson}`);
|
|
292
301
|
}
|
|
302
|
+
const VALIDATION_ERROR_SCHEMA_MAX_CHARS = 4_000;
|