@sema-agent/core 5.59.0 → 5.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/brain/anthropic.js +15 -5
  3. package/dist/brain/errors.d.ts +18 -1
  4. package/dist/brain/errors.js +7 -1
  5. package/dist/brain/input-too-long.d.ts +57 -0
  6. package/dist/brain/input-too-long.js +35 -0
  7. package/dist/brain/stream-engine.js +9 -1
  8. package/dist/core/auto-compaction.js +2 -2
  9. package/dist/core/checkpoint-store.d.ts +172 -25
  10. package/dist/core/checkpoint-store.js +15 -8
  11. package/dist/core/context-edit.d.ts +243 -41
  12. package/dist/core/context-edit.js +247 -32
  13. package/dist/core/governance-codes.d.ts +1 -1
  14. package/dist/core/governance-codes.js +5 -0
  15. package/dist/core/locked-config.d.ts +36 -4
  16. package/dist/core/locked-config.js +34 -1
  17. package/dist/core/mcp.d.ts +4 -5
  18. package/dist/core/mcp.js +10 -6
  19. package/dist/core/memory-engine/content-origin.d.ts +24 -2
  20. package/dist/core/memory-engine/content-origin.js +6 -1
  21. package/dist/core/memory-engine/engine.d.ts +5 -6
  22. package/dist/core/memory.d.ts +10 -0
  23. package/dist/core/park-selfcheck.js +1 -0
  24. package/dist/core/permission-rule-consent.js +9 -5
  25. package/dist/core/runner/prepare-config-doors.d.ts +22 -1
  26. package/dist/core/runner/prepare-config-doors.js +36 -0
  27. package/dist/core/runner/prepare-task.d.ts +28 -1
  28. package/dist/core/runner/prepare-task.js +107 -8
  29. package/dist/core/runner/runtask.js +97 -9
  30. package/dist/core/store-contracts/checkpoint-store-contract.js +32 -0
  31. package/dist/core/tool-policy.d.ts +32 -10
  32. package/dist/core/tool-policy.js +3 -3
  33. package/dist/core/tools.js +1 -1
  34. package/dist/core/trace.d.ts +36 -0
  35. package/dist/core/types.d.ts +127 -2
  36. package/dist/core/untrusted-text.d.ts +11 -0
  37. package/dist/core/untrusted-text.js +1 -0
  38. package/dist/engine/llm/types.d.ts +21 -2
  39. package/dist/engine/loop/agent-loop.js +7 -1
  40. package/dist/engine/loop/types.d.ts +4 -1
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/tools/fs/fs-bash.js +1 -2
  44. package/package.json +1 -1
  45. package/test/export-surface.snapshot.json +5 -1
@@ -406,12 +406,11 @@ export declare function memorySessionPollutedNotice(input: {
406
406
  * arithmetic would report a clean containment while a known failure sat in the report. Every
407
407
  * not-moved file does have an escalation row, so `escalated` is the superset and the honest clause.
408
408
  *
409
- * KNOWN GAP (adversarial-review round 1, P2 registered, not closed here): the derived index is
410
- * contained by a different path (`restorePollutedIndex` captures and restores `MEMORY.md`) that mints
411
- * no rejection row, so a polluted session whose ONLY memory change was an index line produces no
412
- * `count` and no notice its containment is disclosed by the harvest report's warnings alone. Closing
413
- * it needs a structured containment signal on `HarvestReport`, a public type this fix does not touch.
414
- * The wording therefore says "entry file(s)" rather than implying the whole containment.
409
+ * The derived index is contained by a different path (`restorePollutedIndex` captures and restores
410
+ * `MEMORY.md`) that mints no rejection row; that gap was CLOSED by the structured containment signal
411
+ * on `HarvestReport.containment` (indexRolledBack see the containment block below), so an
412
+ * index-only polluted session now signals and notices instead of hiding in warnings prose. The
413
+ * wording says "entry file(s)" because index containment reports through its own signal.
415
414
  *
416
415
  * `reason` is optional because the mark record is read separately from the report: a record that cannot
417
416
  * be read must not turn a true "N files were withheld" into a lie about why.
@@ -477,6 +477,16 @@ export interface NormalizedMemorySpec {
477
477
  * of the fail-closed `"external"` default. The explicit-config channel for a deployment whose custom
478
478
  * tools predate the {@link import("./types.js").ToolSpec.contentOrigin} declaration; it never
479
479
  * overrides a tool's OWN declaration. Normalized: trimmed, deduped, empties dropped.
480
+ *
481
+ * NOT the channel for MCP servers (design/378). A per-name entry here keys on the MINTED name — the
482
+ * host has to predict the namespacing and charset normalization — covers nothing a mid-task refresh
483
+ * adds, and says "exempt this undeclared tool" where the fact is "this whole server is mine".
484
+ * {@link import("./types.js").McpServerSpec.contentOrigin} states that fact at the entry, and being
485
+ * a DECLARATION it wins over this allowlist in both directions: a declared server's tools leave this
486
+ * list's reach entirely, an explicit `"external"` declaration included (that is the one way a
487
+ * deployment can pin a server's tools BEYOND this exemption). This list is not retired — it remains
488
+ * the in-register channel for undeclared HOST tools and for a deployment not ready to touch its
489
+ * server entries.
480
490
  */
481
491
  trustedTools?: string[];
482
492
  /**
@@ -70,6 +70,7 @@ function syntheticCheckpoint(scope) {
70
70
  toolName: "SelfCheck",
71
71
  args: { probe: true, nested: { depth: 2, list: [1, 2, 3] } },
72
72
  boundInputHash: boundInputHashOf({ probe: true, nested: { depth: 2, list: [1, 2, 3] } }),
73
+ hasBidiControls: true,
73
74
  batchToolCallIds: [`${scope}:call`],
74
75
  completedCallIds: [],
75
76
  },
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { hasUnrenderableCharacters, parseAllowRuleText, ruleAdmitsCommand, segmentCoverageOf, suggestRulesForCommand, } from "./permission-rule-model.js";
2
+ import { escapeForDisclosure, hasUnrenderableCharacters, parseAllowRuleText, ruleAdmitsCommand, segmentCoverageOf, suggestRulesForCommand, } from "./permission-rule-model.js";
3
3
  import { errText, sameRuleOwner, sameScope, writerOf } from "./permission-rule-store.js";
4
4
  export class InMemoryRuleApprovalRecordStore {
5
5
  rows = new Map();
@@ -115,7 +115,7 @@ function approvalRecordDamageOf(rec) {
115
115
  const seen = new Set();
116
116
  for (const member of members) {
117
117
  if (!Number.isInteger(member) || member < 0 || rec.candidates[member] === undefined) {
118
- return `offer ${at} references candidate ${member}, which the record does not carry`;
118
+ return `offer ${at} references candidate ${escapeForDisclosure(member)}, which the record does not carry`;
119
119
  }
120
120
  const cand = rec.candidates[member];
121
121
  if (typeof cand !== "object" || cand === null || typeof cand.rule !== "string") {
@@ -138,10 +138,14 @@ function approvalRecordDamageOf(rec) {
138
138
  }
139
139
  }
140
140
  else if (offer.segments !== undefined || offer.uncoveredSegments !== undefined) {
141
- return `${rec.kind} batch offer ${at} must not carry segment metadata — there is no compound command behind it`;
141
+ return `${escapeForDisclosure(rec.kind)} batch offer ${at} must not carry segment metadata — there is no compound command behind it`;
142
142
  }
143
143
  }
144
144
  }
145
+ for (const [at] of rec.candidates.entries()) {
146
+ if (!claimed.has(at))
147
+ return `candidate ${at} belongs to no offer — every candidate row is claimed by exactly one offer`;
148
+ }
145
149
  return undefined;
146
150
  }
147
151
  export function ruleOffersOfRecord(rec) {
@@ -325,7 +329,7 @@ function checkEditedRuleText(text, command) {
325
329
  if (!ruleAdmitsCommand(parsed.rule, command)) {
326
330
  return {
327
331
  ok: false,
328
- message: `the edited rule "${parsed.rule.rule}" does not admit the command that was decided ("${command}") — a card's edit may widen how much the rule covers, never move it to a different grant`,
332
+ message: `the edited rule "${parsed.rule.rule}" does not admit the command that was decided ("${escapeForDisclosure(command)}") — a card's edit may widen how much the rule covers, never move it to a different grant`,
329
333
  };
330
334
  }
331
335
  return { ok: true, canonicalRule: parsed.rule.rule };
@@ -342,7 +346,7 @@ export function precheckEditedRuleText(text, command) {
342
346
  throw e;
343
347
  }
344
348
  if (hasUnrenderableCharacters(command)) {
345
- const e = new Error("precheckEditedRuleText takes a command this lane can read — one carrying control or format characters is not a command any card was drawn for, and the refusal line naming it would carry those bytes onto a display surface");
349
+ const e = new Error("precheckEditedRuleText takes a command this lane can read — one carrying control or format characters is refused at this preflight face (likelier a caller wiring fault than a card's command; the in-engine confirmation arm still judges such a card, with the command escaped on its refusal line)");
346
350
  e.code = "config.invalid_argument";
347
351
  throw e;
348
352
  }
@@ -22,6 +22,7 @@
22
22
  import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
23
23
  import type { Model } from "../../internal/llm.js";
24
24
  import type { ThinkingLevel } from "../../internal/harness.js";
25
+ import type { ContextEditMachine } from "../context-edit.js";
25
26
  import { type LockedPreflight } from "../locked-config.js";
26
27
  import { type ResolvedRole } from "../roles.js";
27
28
  import type { SessionStore } from "../session.js";
@@ -119,6 +120,13 @@ export interface PrepareConfigDoorsResult {
119
120
  /** owned — the resolved interaction posture (spec > spawning run > deployment); the
120
121
  * AskUserQuestion mount and the child-ctx injection read this same value. */
121
122
  resolvedInteractionPosture: "interactive" | "headless" | undefined;
123
+ /** owned — the validated/resolved microCompact knob (design/374; {@link resolveMicroCompactKnob}
124
+ * for the door + placement reasoning). The per-run state builder consumes exactly these values —
125
+ * the deps bag is never re-read after this door. */
126
+ microCompactKnob: {
127
+ machine: ContextEditMachine;
128
+ clearOnRejection: boolean;
129
+ };
122
130
  /** owned — the resolved role; its `systemPrompt` seat is still read at prompt-input time. */
123
131
  resolvedRole: ResolvedRole;
124
132
  /** owned — the resolved main model (`resolvedRole.model`, re-exposed as the name every later
@@ -163,5 +171,18 @@ export interface PrepareConfigDoorsResult {
163
171
  * the resolver's off states). */
164
172
  brainCallGuardrailMs: number | undefined;
165
173
  }
166
- /** The B-1 phase body — the config-doors slice, verbatim (see the module header for the contract). */
174
+ /** design/374 — the microCompact knob door (坏值响亮度默认律, the sibling knob doors' form:
175
+ * interactionPosture / toolModelGate / staleToolResultOffload). Placement is load-bearing (review
176
+ * r3): it runs in THIS pre-first-await config-doors phase so a malformed declaration refuses
177
+ * BEFORE any resource-bearing preparation — session acquisition, execution-env creation, the MCP
178
+ * dial — whose external effects a late refusal's teardown cannot undo. SINGLE READ per seat, and
179
+ * the CONTAINER is judged first (reviews r1/r2): an accessor-backed bag answering a legal value at
180
+ * one read and junk at another must be caught by whichever read this door takes, and a
181
+ * `microCompact: "cc"` / null / number / array bag makes both property reads answer undefined —
182
+ * it must refuse exactly like a malformed field, never fold to the defaults. The RESOLVED values
183
+ * returned here are immutable and are the state builder's ONLY source — nothing re-reads the bag. */
184
+ export declare function resolveMicroCompactKnob(bag: RunnerDeps["microCompact"]): {
185
+ machine: ContextEditMachine;
186
+ clearOnRejection: boolean;
187
+ };
167
188
  export declare function prepareConfigDoors(input: PrepareConfigDoorsInput): PrepareConfigDoorsResult;
@@ -83,10 +83,45 @@ export function resolveModelPromptTraits(model, spec, internals) {
83
83
  fableMitigations: isFableFamilyModelId(model.id),
84
84
  };
85
85
  }
86
+ function microCompactConfigError(field, value, legal) {
87
+ let shown;
88
+ try {
89
+ shown = JSON.stringify(value) ?? String(value);
90
+ }
91
+ catch {
92
+ try {
93
+ shown = String(value);
94
+ }
95
+ catch {
96
+ shown = `[unrepresentable ${typeof value}]`;
97
+ }
98
+ }
99
+ const seat = field === undefined ? "microCompact" : `microCompact.${field}`;
100
+ const e = new Error(`${seat} ${shown} is not ${legal} — an unevaluable declaration is refused loudly, ` +
101
+ `never folded to the default: a silently-ignored opt-in would run the pre-374 machine while the ` +
102
+ `deployment believes it opted in.`);
103
+ e.code = "config.microcompact_invalid";
104
+ return e;
105
+ }
106
+ export function resolveMicroCompactKnob(bag) {
107
+ if (bag !== undefined && (typeof bag !== "object" || bag === null || Array.isArray(bag))) {
108
+ throw microCompactConfigError(undefined, bag, "an object carrying optional machine/clearOnRejection keys");
109
+ }
110
+ const declaredMachine = bag?.machine;
111
+ if (declaredMachine !== undefined && declaredMachine !== "legacy" && declaredMachine !== "cc") {
112
+ throw microCompactConfigError("machine", declaredMachine, `"legacy" | "cc"`);
113
+ }
114
+ const declaredClearOnRejection = bag?.clearOnRejection;
115
+ if (declaredClearOnRejection !== undefined && typeof declaredClearOnRejection !== "boolean") {
116
+ throw microCompactConfigError("clearOnRejection", declaredClearOnRejection, "a boolean");
117
+ }
118
+ return { machine: declaredMachine ?? "legacy", clearOnRejection: declaredClearOnRejection === true };
119
+ }
86
120
  export function prepareConfigDoors(input) {
87
121
  const { deps, sessions, resume, internals } = input;
88
122
  let spec = input.spec;
89
123
  assertRestoreGatedToolsValue(spec.restoreGatedTools);
124
+ const microCompactKnob = resolveMicroCompactKnob(deps.microCompact);
90
125
  const assertToolNameListValue = (value, seat) => {
91
126
  if (value === undefined)
92
127
  return;
@@ -318,6 +353,7 @@ export function prepareConfigDoors(input) {
318
353
  promptProfile,
319
354
  lockedPreflight,
320
355
  resolvedInteractionPosture,
356
+ microCompactKnob,
321
357
  resolvedRole,
322
358
  model,
323
359
  thinking,
@@ -20,6 +20,7 @@ import type { MemoryEngine } from "../memory-engine/engine.js";
20
20
  import { type GitStatusLaneRef } from "./git-status-frame.js";
21
21
  import { type ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
22
22
  import type { ToolDisclosureManifest } from "../trace.js";
23
+ import { type ClearedProjectionLedger, type ContextEditMachine, type OccurrenceIndex } from "../context-edit.js";
23
24
  import type { TaskNotificationPayload } from "../task-notification.js";
24
25
  import { type CwdRef, type ReadFace } from "../../tools/fs/index.js";
25
26
  import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
@@ -134,7 +135,6 @@ export declare function checkpointScopeOf(spec: {
134
135
  export { resolveCheckpointStore } from "../checkpoint-store.js";
135
136
  export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
136
137
  export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
137
- /** Everything the run loop needs, built once by {@link prepareTask} (task setup, isolated from the loop). */
138
138
  export interface Prepared {
139
139
  harness: AgentHarness;
140
140
  /** The CONCRETE built-in session (engine-internal: prepare constructs/acquires `StoredSession` itself,
@@ -876,6 +876,33 @@ export interface Prepared {
876
876
  trimPressureRef: {
877
877
  droppedMessages: boolean;
878
878
  };
879
+ /** design/374 slices 1b/2 — the microCompact machine state this run: the selected clearing
880
+ * machine, the cleared-projection ledger (request-view application, durable decisions — see
881
+ * `context-edit.ts`'s ledger note; per-run in-memory, so durable resume / `resumeAt` rebuilds
882
+ * start EMPTY by construction), the last request's projection seat (what the provider actually
883
+ * saw — the MC-R rejection arm computes its candidates and savings on THIS view, never on the
884
+ * raw session rebuild), and the MC-R knob. Default machine "legacy" + MC-R off ⇒ the ledger
885
+ * never gains an entry and every replay is a same-reference no-op (default bytes unchanged). */
886
+ microCompact: PreparedMicroCompact;
887
+ }
888
+ /** See {@link Prepared.microCompact}. */
889
+ export interface PreparedMicroCompact {
890
+ machine: ContextEditMachine;
891
+ /** MC-R (design/374 §3.2): one-shot clear-and-retry on a provider input-too-long rejection.
892
+ * Default false (X2: lands with the machinery, flips with slice 3). */
893
+ clearOnRejection: boolean;
894
+ ledger: ClearedProjectionLedger;
895
+ projectionRef: {
896
+ current?: {
897
+ /** The FINAL projected view of the last provider request (post trim/sweep). */
898
+ messages: AgentMessage[];
899
+ /** Occurrence coordinates of that view (object-identity first, unambiguous-group fallback). */
900
+ keyOf: OccurrenceIndex["keyOf"];
901
+ };
902
+ };
903
+ /** The same offload persist seat the frontier machine uses (write-once, idempotent), so MC-R
904
+ * clears compose identical markers. Absent when no offload store is configured. */
905
+ offloadPersist?: (toolCallId: string, fullText: string) => string;
879
906
  }
880
907
  /**
881
908
  * WHICH tool call a committed durable park is holding this run — `undefined` when nothing parked, or
@@ -20,7 +20,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial, resolveDelegationEn
20
20
  import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
21
21
  import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
22
22
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
23
- import { askApproverIdentity, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
23
+ import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
24
24
  const PERSISTED_RULE_TOOL = "Bash";
25
25
  import { findAdmittingRule, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
26
26
  import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
@@ -70,7 +70,7 @@ import { resolveEpochAgainstBundled } from "../../prompt-assembly/epoch.js";
70
70
  import { artifactDeclarations } from "../../prompt-assembly/artifact.js";
71
71
  import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
72
72
  import { SEMA_DEFAULT_PACK } from "../../prompt-assembly/packs/sema-default.js";
73
- import { clearStaleToolResults, dropEmptyFailureAssistants, editBudget } from "../context-edit.js";
73
+ import { clearStaleToolResults, createClearedProjectionLedger, dropEmptyFailureAssistants, editBudget, replayClearedProjection, resolveTriggerWindow, } from "../context-edit.js";
74
74
  import { capAggregateToolResults } from "../tool-result-budget.js";
75
75
  import { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "../media-byte-cap.js";
76
76
  import { dropOrphanToolResults, guardBudget, insertTrimNotice, trimToBudget } from "../context-guard.js";
@@ -124,7 +124,7 @@ function warnCompactionWindowHazard(tracer, spec, model, compModel, hostTaskId)
124
124
  if (compModel === undefined)
125
125
  return;
126
126
  const compWindow = compModel.contextTokens ?? compModel.contextWindow;
127
- const mainWindow = model.autoCompactTokens ?? model.contextTokens ?? model.contextWindow;
127
+ const mainWindow = resolveTriggerWindow(model).window;
128
128
  if (compWindow > 0 && mainWindow > 0 && compWindow < mainWindow) {
129
129
  const merged = { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction };
130
130
  const sanitized = sanitizeCompactionSettings(merged, mainWindow);
@@ -195,6 +195,33 @@ function announceToolModelGate(onNotice, modelId, gate) {
195
195
  }
196
196
  }
197
197
  }
198
+ function announceDeclaredMcpContentClasses(args) {
199
+ for (const entry of args.entries ?? []) {
200
+ const declaredClass = entry.contentOrigin;
201
+ if (declaredClass === undefined)
202
+ continue;
203
+ const server = inlineUntrusted(String(entry.name), 160);
204
+ const toolCount = args.statuses?.find((s) => s.name === entry.name)?.toolNames?.length ?? 0;
205
+ const resourceFaceCaveat = " (Not covered: the cross-server resource faces — ListMcpResourcesTool/ReadMcpResourceTool/ReadMcpResourceDirTool — stay externally classified and still mark.)";
206
+ const posture = declaredClass === "local"
207
+ ? `this server's mounted tools' invocations will not mark this session's memory as externally exposed.${resourceFaceCaveat}`
208
+ : declaredClass === "execution"
209
+ ? `this server's mounted tools' invocations will not mark this session's memory unless the execIsExternalContent strict upgrade is armed (currently: ${args.execIsExternalContent ? "armed" : "off"}).${resourceFaceCaveat}`
210
+ : "its tools stay externally classified and this declaration PINS that — the trustedTools allowlist no longer exempts them, and invocations mark this session's memory.";
211
+ deliverEngineNotice(args.onNotice, {
212
+ code: "memory.content_class_declared",
213
+ message: `MCP server "${server}" (${toolCount} tool(s) mounted) is declared ` +
214
+ `contentOrigin "${declaredClass}" by this deployment's configuration — ${posture}`,
215
+ detail: {
216
+ server,
217
+ contentOrigin: declaredClass,
218
+ toolCount,
219
+ ...(declaredClass === "execution" ? { execIsExternalContent: args.execIsExternalContent } : {}),
220
+ sessionId: args.sessionId,
221
+ },
222
+ });
223
+ }
224
+ }
198
225
  export { __resetReadFaceClampAnnouncement } from "./prepare-hands-readface.js";
199
226
  const DEFAULT_MAX_SUSPENDS = 5;
200
227
  const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
@@ -242,6 +269,49 @@ class ParkRefusal extends Error {
242
269
  export { resolveCheckpointStore } from "../checkpoint-store.js";
243
270
  export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
244
271
  export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
272
+ function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
273
+ const triggerWindow = resolveTriggerWindow(model);
274
+ if (triggerWindow.clamped) {
275
+ deliverEngineNotice(deps.onNotice, {
276
+ code: "config.autocompact_window_clamped",
277
+ message: `model "${model.id}" declares autoCompactTokens=${triggerWindow.declaredAutoCompactTokens} ABOVE its physical window ` +
278
+ `${triggerWindow.physicalWindow}; the autocompact window only ever LOWERS the trigger-side geometry, so the value was ` +
279
+ `clamped to the physical window — fix the model config (declare a value at or below the physical window, or omit it)`,
280
+ detail: {
281
+ modelId: model.id,
282
+ declaredAutoCompactTokens: triggerWindow.declaredAutoCompactTokens,
283
+ physicalWindow: triggerWindow.physicalWindow,
284
+ sessionId,
285
+ },
286
+ });
287
+ }
288
+ return {
289
+ machine: knob.machine,
290
+ clearOnRejection: knob.clearOnRejection,
291
+ ledger: createClearedProjectionLedger(),
292
+ projectionRef: {},
293
+ ...(offloadStore ? { offloadPersist: createOffloadPersist(offloadStore, sessionId, deps.onNotice) } : {}),
294
+ };
295
+ }
296
+ function recordFrontierClears(args) {
297
+ for (const { index } of args.pass.clears) {
298
+ const at = args.index.keyAt(index);
299
+ const clearedMsg = args.edited[index];
300
+ const markerText = clearedMsg.content?.[0]?.text;
301
+ if (at !== undefined && typeof markerText === "string") {
302
+ args.ledger.entries.set(at.key, { marker: markerText, groupCount: at.groupCount, fp: at.fp });
303
+ }
304
+ }
305
+ emitTrace(args.tracer, () => ({
306
+ kind: "context.mc_clear",
307
+ version: 1,
308
+ taskId: args.taskId,
309
+ clearedCount: args.pass.clears.length,
310
+ tokensSavedEstimate: args.pass.tokensSavedEstimate,
311
+ trigger: "frontier",
312
+ ts: Date.now(),
313
+ }));
314
+ }
245
315
  export function gatedCallIdOf(p) {
246
316
  if (p.suspendRef.token !== undefined)
247
317
  return p.suspendRef.gatedCallId;
@@ -492,7 +562,7 @@ async function derivedRouteFallsBack(args) {
492
562
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
493
563
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
494
564
  spec = doors.spec;
495
- const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
565
+ const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
496
566
  announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
497
567
  const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
498
568
  let shellGatedBash = false;
@@ -2079,6 +2149,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2079
2149
  const trustedTools = effectiveSafety.trustedTools;
2080
2150
  const execIsExternalContent = effectiveSafety.execIsExternalContent;
2081
2151
  provenanceForChildrenRef.current = { trustedTools: [...trustedTools], execIsExternalContent };
2152
+ announceDeclaredMcpContentClasses({
2153
+ entries: lockedPreflight.mcp,
2154
+ statuses: mcp?.statuses,
2155
+ execIsExternalContent,
2156
+ sessionId,
2157
+ onNotice: deps.onNotice,
2158
+ });
2082
2159
  if (memoryEngineSession?.settlement !== undefined) {
2083
2160
  delegationSettlementRef.current = { controlDir: memoryEngineSession.settlement.controlDir, sessionId: memoryEngineSession.settlement.sessionId };
2084
2161
  }
@@ -3888,6 +3965,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3888
3965
  : undefined,
3889
3966
  resourceLedger: resourceLedgerOut,
3890
3967
  ...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
3968
+ ...(spec.principal ? { principal: spec.principal } : {}),
3891
3969
  };
3892
3970
  if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
3893
3971
  return false;
@@ -4229,7 +4307,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4229
4307
  args: parkedArgs,
4230
4308
  ...(() => {
4231
4309
  const preview = approvalPreviewOf(req.toolName, parkedArgs);
4232
- return preview !== undefined ? { preview } : {};
4310
+ const bidi = carriesBidiControls(parkedArgs) || carriesBidiControls(preview);
4311
+ return {
4312
+ ...(preview !== undefined ? { preview } : {}),
4313
+ ...(bidi ? { hasBidiControls: true } : {}),
4314
+ };
4233
4315
  })(),
4234
4316
  ...ruleOffersOf(req.toolName, parkedArgs, {
4235
4317
  ...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
@@ -4502,11 +4584,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4502
4584
  }
4503
4585
  const editAt = editBudget(model);
4504
4586
  const guardAt = guardBudget(model);
4587
+ const microCompact = buildMicroCompactState(deps, model, sessionId, offloadStore, microCompactKnob);
4505
4588
  const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
4506
4589
  harness.on("context", async ({ messages }) => {
4507
4590
  batchHaltRef.current = undefined;
4508
4591
  const healed = dropEmptyFailureAssistants(messages);
4509
- const capped = await capAggregateToolResults(healed, {
4592
+ const replay = replayClearedProjection(healed, microCompact.ledger);
4593
+ const replayed = replay.messages;
4594
+ const capped = await capAggregateToolResults(replayed, {
4510
4595
  store: offloadStore,
4511
4596
  sessionId,
4512
4597
  onCapped: (info) => emitTrace(deps.tracer, () => ({
@@ -4523,8 +4608,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4523
4608
  limitBytes: deps.mediaByteCapBytes ?? AGGREGATE_MEDIA_BUDGET_BYTES,
4524
4609
  onStripped: deps.onMediaStripped,
4525
4610
  });
4611
+ let ccPass;
4526
4612
  const edited = clearStaleToolResults(mediaCapped, {
4527
4613
  budgetTokens: editAt,
4614
+ ...(microCompact.machine === "cc" || microCompact.clearOnRejection ? { recognizeCcMarkers: true } : {}),
4615
+ ...(microCompact.machine === "cc"
4616
+ ? {
4617
+ machine: "cc",
4618
+ clearSource: replayed,
4619
+ onCleared: (pass) => { ccPass = pass; },
4620
+ }
4621
+ : {}),
4528
4622
  anchoredTotalTokens: estimateContextTokens(mediaCapped, charsPerToken).tokens,
4529
4623
  charsPerToken,
4530
4624
  ...(offloadStore
@@ -4535,6 +4629,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4535
4629
  }
4536
4630
  : {}),
4537
4631
  });
4632
+ if (ccPass !== undefined) {
4633
+ recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: spec.taskId ?? sessionId });
4634
+ }
4538
4635
  let trimmed = trimToBudget(edited, guardAt, estimateContextTokens(edited, charsPerToken).tokens, charsPerToken);
4539
4636
  const trimDroppedMessages = trimmed.length < edited.length;
4540
4637
  if (trimDroppedMessages) {
@@ -4575,12 +4672,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4575
4672
  }
4576
4673
  }
4577
4674
  requestLossyRef.current =
4578
- capped !== healed ||
4675
+ replayed !== healed ||
4676
+ capped !== replayed ||
4579
4677
  mediaCapped !== capped ||
4580
4678
  edited !== mediaCapped ||
4581
4679
  trimDroppedMessages ||
4582
4680
  swept.dropped.length > 0 ||
4583
4681
  gitStatusRef.overBudgetShrunk === true;
4682
+ microCompact.projectionRef.current = { messages: swept.messages, keyOf: replay.index.keyOf };
4584
4683
  return { messages: swept.messages };
4585
4684
  });
4586
4685
  const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
@@ -4769,7 +4868,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4769
4868
  const effectiveReadFaceObserved = carrierReadFace();
4770
4869
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
4771
4870
  const preparedHolder = {};
4772
- const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
4871
+ const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
4773
4872
  const prepared = buildPrepared();
4774
4873
  preparedHolder.current = prepared;
4775
4874
  return prepared;