@sema-agent/core 5.61.0 → 5.63.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/dist/agents/subagent.d.ts +12 -2
  3. package/dist/agents/subagent.js +3 -2
  4. package/dist/brain/open-responses.js +8 -3
  5. package/dist/brain/openai.js +4 -4
  6. package/dist/brain/stream-engine.d.ts +13 -2
  7. package/dist/brain/stream-engine.js +3 -3
  8. package/dist/core/auto-compaction.d.ts +6 -4
  9. package/dist/core/auto-compaction.js +3 -0
  10. package/dist/core/auto-mode-prompt-assets.js +1 -1
  11. package/dist/core/checkpoint-store.d.ts +36 -4
  12. package/dist/core/checkpoint-store.js +1 -0
  13. package/dist/core/context-edit.d.ts +36 -29
  14. package/dist/core/context-edit.js +3 -3
  15. package/dist/core/governance-codes.d.ts +1 -1
  16. package/dist/core/governance-codes.js +2 -0
  17. package/dist/core/hooks.d.ts +86 -4
  18. package/dist/core/hooks.js +3 -3
  19. package/dist/core/memory-engine/engine.d.ts +11 -0
  20. package/dist/core/memory-engine/engine.js +29 -3
  21. package/dist/core/memory-engine/index.d.ts +1 -1
  22. package/dist/core/memory-engine/origin-clearance.d.ts +28 -0
  23. package/dist/core/park-selfcheck.js +2 -0
  24. package/dist/core/pricing.d.ts +24 -0
  25. package/dist/core/pricing.js +18 -0
  26. package/dist/core/runner/prepare-config-doors.d.ts +36 -2
  27. package/dist/core/runner/prepare-config-doors.js +66 -8
  28. package/dist/core/runner/prepare-task.d.ts +113 -12
  29. package/dist/core/runner/prepare-task.js +239 -131
  30. package/dist/core/runner/runtask.d.ts +7 -0
  31. package/dist/core/runner/runtask.js +325 -95
  32. package/dist/core/runner/turn-attachments.d.ts +137 -5
  33. package/dist/core/runner/turn-attachments.js +25 -2
  34. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  35. package/dist/core/tool-errors.d.ts +2 -1
  36. package/dist/core/tool-policy.d.ts +27 -0
  37. package/dist/core/trace.d.ts +5 -4
  38. package/dist/core/types.d.ts +163 -29
  39. package/dist/core/untrusted-text.d.ts +5 -4
  40. package/dist/core/untrusted-text.js +8 -0
  41. package/dist/core/usage-window-store.d.ts +109 -8
  42. package/dist/core/usage-window-store.js +79 -12
  43. package/dist/engine/harness/agent-harness.js +20 -5
  44. package/dist/engine/harness/types.d.ts +38 -0
  45. package/dist/engine/loop/agent-loop.js +20 -1
  46. package/dist/engine/loop/types.d.ts +41 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/orchestration/run-workflow-tool.d.ts +2 -2
  49. package/dist/orchestration/workflow-types.d.ts +48 -1
  50. package/dist/orchestration/workflow-types.js +12 -4
  51. package/dist/orchestration/workflow.d.ts +14 -3
  52. package/dist/orchestration/workflow.js +44 -19
  53. package/dist/prompt-assembly/event-registry.js +2 -0
  54. package/dist/prompts/default.js +1 -1
  55. package/dist/server/http.d.ts +1 -1
  56. package/dist/stores/file/usage-window-store.d.ts +1 -1
  57. package/dist/stores/file/usage-window-store.js +27 -6
  58. package/dist/tools/loop-tick.js +1 -1
  59. package/dist/tools/scheduler-tools.js +9 -1
  60. package/package.json +1 -1
  61. package/test/export-surface.snapshot.json +3 -1
@@ -95,7 +95,7 @@ import { BINDING_CHECKPOINT_VERSION, mintCheckpointId, mintCheckpointToken, ORG_
95
95
  import { boundInputHashOf } from "../canonical-json.js";
96
96
  import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
97
97
  import { durableParkGapFor } from "../park-selfcheck.js";
98
- import { GLOBAL_USAGE_KEY, usageRetryAfterMs } from "../usage-window-store.js";
98
+ import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
99
99
  import { deliverEngineNotice } from "../types.js";
100
100
  let announcedMaterializeEnvBySink = new WeakMap();
101
101
  const announcedMaterializeEnvConsole = new Set();
@@ -228,6 +228,61 @@ const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
228
228
  const DEFAULT_RESOURCE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
229
229
  export const ENV_LIFETIME_SUSPEND_MARGIN_MS = 60_000;
230
230
  export const USAGE_WINDOW_REAP_MARGIN_MS = 60 * 60 * 1000;
231
+ function buildUsageGovernance(windows, deps, principal, sessionId) {
232
+ if (windows === undefined || windows.length === 0)
233
+ return undefined;
234
+ const store = deps.usageWindowStore;
235
+ if (store === undefined) {
236
+ deps.onError?.(new Error("RunnerDeps.usageWindows is set but INACTIVE: no `usageWindowStore` is wired, so cross-task usage cannot be counted and no window can be enforced. Wire InMemoryUsageWindowStore (process-local) or FileUsageWindowStore (restart-surviving)."), { phase: "config", sessionId });
237
+ return undefined;
238
+ }
239
+ const key = principal || GLOBAL_USAGE_KEY;
240
+ const governsCost = windowsGovernCost(windows);
241
+ let chargedTokens = 0;
242
+ let chargedCostMicroUsd = 0;
243
+ let announcedCostGap = false;
244
+ let recordedUnpricedGap = false;
245
+ return {
246
+ key,
247
+ governsCost,
248
+ async check(now) {
249
+ const readings = await store.read(key, windows, now);
250
+ if (!announcedCostGap && readings.some((r) => r.costUnknown === true)) {
251
+ announcedCostGap = true;
252
+ deps.onError?.(new Error(`the usage window for ledger key ${JSON.stringify(key)} holds spend that nothing could price, so its maxCostUsd ceiling is being evaluated against a LOWER BOUND until that charge ages out of the window. Price every model this deployment can reach (including degrade targets and the compaction model).`), { phase: "config", sessionId });
253
+ }
254
+ return usageRetryAfterMs(readings, windows);
255
+ },
256
+ async commit(cumulativeTokens, cumulativeCostMicroUsd, now) {
257
+ if (!governsCost) {
258
+ const delta = cumulativeTokens - chargedTokens;
259
+ if (delta <= 0)
260
+ return;
261
+ await store.charge(key, delta, now, windows);
262
+ chargedTokens = cumulativeTokens;
263
+ return;
264
+ }
265
+ if (cumulativeCostMicroUsd === undefined) {
266
+ const tokenDelta = cumulativeTokens - chargedTokens;
267
+ if (tokenDelta > 0 || !recordedUnpricedGap) {
268
+ await store.charge(key, Math.max(0, tokenDelta), now, windows, null);
269
+ chargedTokens = Math.max(chargedTokens, cumulativeTokens);
270
+ recordedUnpricedGap = true;
271
+ }
272
+ const e = new Error("a deployment usage window declares maxCostUsd, but this run's spend has NO cost figure (RB-368 unpriced: a model served without a RunnerDeps.pricing entry or a Model.cost declaration). Refused rather than charged a fabricated 0 — the money ceiling would have silently stopped applying. Price every model this run can reach, or drop maxCostUsd from the window.");
273
+ e.code = "config.usage_window_unpriced";
274
+ throw e;
275
+ }
276
+ const tokenDelta = cumulativeTokens - chargedTokens;
277
+ const costDelta = cumulativeCostMicroUsd - chargedCostMicroUsd;
278
+ if (tokenDelta <= 0 && costDelta <= 0)
279
+ return;
280
+ await store.charge(key, Math.max(0, tokenDelta), now, windows, Math.max(0, costDelta));
281
+ chargedTokens = cumulativeTokens;
282
+ chargedCostMicroUsd = cumulativeCostMicroUsd;
283
+ },
284
+ };
285
+ }
231
286
  export function resolveEnvLifetimeExpiry(env, observedAt) {
232
287
  const lifetimeMs = env.lifetimeMs;
233
288
  if (lifetimeMs === undefined)
@@ -267,7 +322,7 @@ class ParkRefusal extends Error {
267
322
  }
268
323
  }
269
324
  export { resolveCheckpointStore } from "../checkpoint-store.js";
270
- export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
325
+ export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
271
326
  export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
272
327
  function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
273
328
  const triggerWindow = resolveTriggerWindow(model);
@@ -290,6 +345,7 @@ function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
290
345
  clearOnRejection: knob.clearOnRejection,
291
346
  ledger: createClearedProjectionLedger(),
292
347
  projectionRef: {},
348
+ inTurnCompactionRef: {},
293
349
  ...(offloadStore ? { offloadPersist: createOffloadPersist(offloadStore, sessionId, deps.onNotice) } : {}),
294
350
  };
295
351
  }
@@ -308,10 +364,75 @@ function recordFrontierClears(args) {
308
364
  taskId: args.taskId,
309
365
  clearedCount: args.pass.clears.length,
310
366
  tokensSavedEstimate: args.pass.tokensSavedEstimate,
311
- trigger: "frontier",
367
+ trigger: args.trigger,
312
368
  ts: Date.now(),
313
369
  }));
314
370
  }
371
+ function flushProjectionObservations(pending) {
372
+ for (const fire of pending.splice(0)) {
373
+ try {
374
+ fire();
375
+ }
376
+ catch {
377
+ }
378
+ }
379
+ }
380
+ export async function runGuardChain(args) {
381
+ const { edited, microCompact, guardAt, charsPerToken, deps, taskId } = args;
382
+ const applyGuardTrim = (view, anchoredTotal) => {
383
+ let trimmed = trimToBudget(view, guardAt, anchoredTotal, charsPerToken);
384
+ const dropped = trimmed.length < view.length;
385
+ if (dropped) {
386
+ args.trimPressureRef.droppedMessages = true;
387
+ const droppedCount = view.length - trimmed.length;
388
+ trimmed = insertTrimNotice(trimmed);
389
+ emitTrace(deps.tracer, () => ({
390
+ kind: "context.trim",
391
+ version: 1,
392
+ taskId,
393
+ dropped: droppedCount,
394
+ ts: Date.now(),
395
+ }));
396
+ }
397
+ return { trimmed, dropped };
398
+ };
399
+ let working = edited;
400
+ let anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
401
+ if (microCompact.machine === "legacy") {
402
+ const t = applyGuardTrim(working, anchoredWorking);
403
+ return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
404
+ }
405
+ if (microCompact.machine === "off" && anchoredWorking > guardAt) {
406
+ let blockingPass;
407
+ const blockingEdited = clearStaleToolResults(working, {
408
+ budgetTokens: guardAt,
409
+ machine: "cc",
410
+ clearSource: args.replayed,
411
+ onCleared: (pass) => { blockingPass = pass; },
412
+ anchoredTotalTokens: anchoredWorking,
413
+ charsPerToken,
414
+ ...(args.offloadStore ? { offload: { persist: createOffloadPersist(args.offloadStore, args.sessionId, deps.onNotice) } } : {}),
415
+ });
416
+ if (blockingPass !== undefined) {
417
+ recordFrontierClears({ pass: blockingPass, index: args.index, edited: blockingEdited, ledger: microCompact.ledger, tracer: deps.tracer, taskId, trigger: "blocking" });
418
+ working = blockingEdited;
419
+ anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
420
+ }
421
+ }
422
+ if (anchoredWorking > guardAt && args.recheck !== true) {
423
+ const compacted = (await microCompact.inTurnCompactionRef.current?.(args.signal, anchoredWorking)) === true;
424
+ if (compacted) {
425
+ args.pendingProjectionObservations.length = 0;
426
+ return { earlyReturn: { messages: working, adoptSessionRebuild: true } };
427
+ }
428
+ if (args.signal?.aborted === true) {
429
+ args.pendingProjectionObservations.length = 0;
430
+ return { working, trimmed: working, trimDroppedMessages: false };
431
+ }
432
+ }
433
+ const t = applyGuardTrim(working, anchoredWorking);
434
+ return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
435
+ }
315
436
  export function gatedCallIdOf(p) {
316
437
  if (p.suspendRef.token !== undefined)
317
438
  return p.suspendRef.gatedCallId;
@@ -339,7 +460,7 @@ function publishCommittedSuspend(refs, token, gate, scope, remoteHandle, checkpo
339
460
  function hasConversationContent(branch) {
340
461
  return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
341
462
  }
342
- function effectiveDelegationFacts(internals, seedIsDelegatedChild) {
463
+ export function effectiveDelegationFacts(internals, seedIsDelegatedChild) {
343
464
  const isDelegatedChild = internals?.isDelegatedChild !== undefined ? internals.isDelegatedChild === true : seedIsDelegatedChild === true;
344
465
  return { isDelegatedChild, isNonForkChild: isDelegatedChild && internals?.insideFork !== true };
345
466
  }
@@ -475,20 +596,21 @@ const sanitizePreview = (node, depth = 0) => {
475
596
  function resolveApprovalPreview(tools, toolName, args) {
476
597
  const t = tools.find((x) => x.name === toolName || (x.aliases?.includes(toolName) ?? false));
477
598
  if (t?.approvalPreview === undefined)
478
- return undefined;
599
+ return {};
479
600
  try {
480
601
  const raw = t.approvalPreview(args);
481
602
  if (raw === undefined)
482
- return undefined;
603
+ return {};
483
604
  const bytes = JSON.stringify(raw);
484
605
  if (bytes === undefined)
485
- return undefined;
486
- if (bytes.length > 16_384)
487
- return { truncated: true, note: `approval preview exceeded 16KiB (${bytes.length} chars serialized)` };
488
- return sanitizePreview(raw);
606
+ return { withheld: "unavailable" };
607
+ if (bytes.length > 16_384) {
608
+ return { preview: { truncated: true, note: `approval preview exceeded 16KiB (${bytes.length} chars serialized)` }, withheld: "oversize" };
609
+ }
610
+ return { preview: sanitizePreview(raw) };
489
611
  }
490
612
  catch {
491
- return undefined;
613
+ return { withheld: "unavailable" };
492
614
  }
493
615
  }
494
616
  function inheritedAskRuleEvidence(deps) {
@@ -839,7 +961,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
839
961
  if (e.type === "task_progress")
840
962
  forwardSink(e);
841
963
  else if (spec.forwardSubagentEvents === true &&
842
- (e.type === "text_delta" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_end")) {
964
+ (e.type === "text_delta" || e.type === "text_end" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_end")) {
843
965
  forwardSink(e);
844
966
  }
845
967
  }
@@ -3140,6 +3262,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3140
3262
  if (ask?.requiresRealApproval === true ||
3141
3263
  ask?.persistedRuleShadowed !== undefined ||
3142
3264
  ask?.decisionReason === "hook" ||
3265
+ ask?.matchedAskRule !== undefined ||
3143
3266
  ask?.inheritedUnresolved === true ||
3144
3267
  ask?.ancestorResolved === true) {
3145
3268
  return {};
@@ -3550,31 +3673,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3550
3673
  sessionId,
3551
3674
  });
3552
3675
  }
3553
- const usageGovernance = (() => {
3554
- if (usageWindows === undefined || usageWindows.length === 0)
3555
- return undefined;
3556
- const store = deps.usageWindowStore;
3557
- if (store === undefined) {
3558
- deps.onError?.(new Error("RunnerDeps.usageWindows is set but INACTIVE: no `usageWindowStore` is wired, so cross-task usage cannot be counted and no window can be enforced. Wire InMemoryUsageWindowStore (process-local) or FileUsageWindowStore (restart-surviving)."), { phase: "config", sessionId });
3559
- return undefined;
3560
- }
3561
- const key = spec.principal || GLOBAL_USAGE_KEY;
3562
- const windows = usageWindows;
3563
- let charged = 0;
3564
- return {
3565
- key,
3566
- async check(now) {
3567
- return usageRetryAfterMs(await store.read(key, windows, now));
3568
- },
3569
- async commit(cumulativeTokens, now) {
3570
- const delta = cumulativeTokens - charged;
3571
- if (delta <= 0)
3572
- return;
3573
- await store.charge(key, delta, now, windows);
3574
- charged = cumulativeTokens;
3575
- },
3576
- };
3577
- })();
3676
+ const usageGovernance = buildUsageGovernance(usageWindows, deps, spec.principal, sessionId);
3578
3677
  const platformSuspendArmed = durableSuspendInfraReady && (envLifetimeSuspendAt !== undefined || usageGovernance !== undefined);
3579
3678
  const checkpointStore = resolveCheckpointStore(spec, deps);
3580
3679
  const durableApproval = spec.durableApproval ??
@@ -3667,8 +3766,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3667
3766
  toolCallId: req.toolCallId,
3668
3767
  args: req.args,
3669
3768
  ...(() => {
3670
- const preview = approvalPreviewOf(req.toolName, req.args);
3671
- return preview !== undefined ? { preview } : {};
3769
+ const p = approvalPreviewOf(req.toolName, req.args);
3770
+ return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
3672
3771
  })(),
3673
3772
  ...ruleOffersOf(req.toolName, req.args, decision.action === "ask" ? decision : undefined),
3674
3773
  message: decision.message ?? `approval required for "${req.toolName}"`,
@@ -4148,7 +4247,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4148
4247
  }
4149
4248
  };
4150
4249
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
4151
- ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage) => {
4250
+ ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule) => {
4152
4251
  const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
4153
4252
  if (syncFirstEligible &&
4154
4253
  runtimeCaps?.forceDurableGate !== true &&
@@ -4306,10 +4405,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4306
4405
  toolName: req.toolName,
4307
4406
  args: parkedArgs,
4308
4407
  ...(() => {
4309
- const preview = approvalPreviewOf(req.toolName, parkedArgs);
4310
- const bidi = carriesBidiControls(parkedArgs) || carriesBidiControls(preview);
4408
+ const p = approvalPreviewOf(req.toolName, parkedArgs);
4409
+ const bidi = carriesBidiControls(parkedArgs) || carriesBidiControls(p.preview);
4311
4410
  return {
4312
- ...(preview !== undefined ? { preview } : {}),
4411
+ ...(p.preview !== undefined ? { preview: p.preview } : {}),
4412
+ ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}),
4313
4413
  ...(bidi ? { hasBidiControls: true } : {}),
4314
4414
  };
4315
4415
  })(),
@@ -4317,6 +4417,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4317
4417
  ...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
4318
4418
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
4319
4419
  ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
4420
+ ...(matchedAskRule !== undefined ? { matchedAskRule } : {}),
4320
4421
  ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
4321
4422
  ...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
4322
4423
  }),
@@ -4586,101 +4687,108 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4586
4687
  const guardAt = guardBudget(model);
4587
4688
  const microCompact = buildMicroCompactState(deps, model, sessionId, offloadStore, microCompactKnob);
4588
4689
  const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
4589
- harness.on("context", async ({ messages }) => {
4690
+ harness.on("context", async ({ messages, recheck, signal }) => {
4590
4691
  batchHaltRef.current = undefined;
4591
- const healed = dropEmptyFailureAssistants(messages);
4592
- const replay = replayClearedProjection(healed, microCompact.ledger);
4593
- const replayed = replay.messages;
4594
- const capped = await capAggregateToolResults(replayed, {
4595
- store: offloadStore,
4596
- sessionId,
4597
- onCapped: (info) => emitTrace(deps.tracer, () => ({
4598
- kind: "tool_result.capped",
4599
- version: 1,
4600
- taskId: spec.taskId ?? sessionId,
4601
- ...(info.tool !== undefined ? { tool: info.tool } : {}),
4602
- sizeChars: info.sizeChars,
4603
- storeFallback: info.storeFallback,
4604
- ts: Date.now(),
4605
- })),
4606
- });
4607
- const mediaCapped = capAggregateMediaBytes(capped, {
4608
- limitBytes: deps.mediaByteCapBytes ?? AGGREGATE_MEDIA_BUDGET_BYTES,
4609
- onStripped: deps.onMediaStripped,
4610
- });
4611
- let ccPass;
4612
- const edited = clearStaleToolResults(mediaCapped, {
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
- : {}),
4622
- anchoredTotalTokens: estimateContextTokens(mediaCapped, charsPerToken).tokens,
4623
- charsPerToken,
4624
- ...(offloadStore
4625
- ? {
4626
- offload: {
4627
- persist: createOffloadPersist(offloadStore, sessionId, deps.onNotice),
4628
- },
4629
- }
4630
- : {}),
4631
- });
4632
- if (ccPass !== undefined) {
4633
- recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: spec.taskId ?? sessionId });
4634
- }
4635
- let trimmed = trimToBudget(edited, guardAt, estimateContextTokens(edited, charsPerToken).tokens, charsPerToken);
4636
- const trimDroppedMessages = trimmed.length < edited.length;
4637
- if (trimDroppedMessages) {
4638
- trimPressureRef.droppedMessages = true;
4639
- const droppedCount = edited.length - trimmed.length;
4640
- trimmed = insertTrimNotice(trimmed);
4641
- emitTrace(deps.tracer, () => ({
4642
- kind: "context.trim",
4643
- version: 1,
4644
- taskId: spec.taskId ?? sessionId,
4645
- dropped: droppedCount,
4646
- ts: Date.now(),
4647
- }));
4648
- }
4649
- trimmed = applyGitFrameGuard({
4650
- before: edited,
4651
- trimmed,
4652
- budgetTokens: guardAt,
4653
- ref: gitStatusRef,
4654
- charsPerToken,
4655
- onDegrade: (message) => {
4692
+ const pendingProjectionObservations = [];
4693
+ try {
4694
+ const healed = dropEmptyFailureAssistants(messages);
4695
+ const replay = replayClearedProjection(healed, microCompact.ledger);
4696
+ const replayed = replay.messages;
4697
+ const capped = await capAggregateToolResults(replayed, {
4698
+ store: offloadStore,
4699
+ sessionId,
4700
+ onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
4701
+ kind: "tool_result.capped",
4702
+ version: 1,
4703
+ taskId: spec.taskId ?? sessionId,
4704
+ ...(info.tool !== undefined ? { tool: info.tool } : {}),
4705
+ sizeChars: info.sizeChars,
4706
+ storeFallback: info.storeFallback,
4707
+ ts: Date.now(),
4708
+ }))),
4709
+ });
4710
+ const mediaCapped = capAggregateMediaBytes(capped, {
4711
+ limitBytes: deps.mediaByteCapBytes ?? AGGREGATE_MEDIA_BUDGET_BYTES,
4712
+ onStripped: (info) => {
4713
+ const onMediaStripped = deps.onMediaStripped;
4714
+ if (onMediaStripped !== undefined)
4715
+ pendingProjectionObservations.push(() => onMediaStripped(info));
4716
+ },
4717
+ });
4718
+ let ccPass;
4719
+ const edited = microCompact.machine === "off" ? mediaCapped : clearStaleToolResults(mediaCapped, {
4720
+ budgetTokens: editAt,
4721
+ machine: microCompact.machine,
4722
+ ...(microCompact.machine === "cc" || microCompact.clearOnRejection ? { recognizeCcMarkers: true } : {}),
4723
+ ...(microCompact.machine === "cc"
4724
+ ? {
4725
+ clearSource: replayed,
4726
+ onCleared: (pass) => { ccPass = pass; },
4727
+ }
4728
+ : {}),
4729
+ anchoredTotalTokens: estimateContextTokens(mediaCapped, charsPerToken).tokens,
4730
+ charsPerToken,
4731
+ ...(offloadStore
4732
+ ? {
4733
+ offload: {
4734
+ persist: createOffloadPersist(offloadStore, sessionId, deps.onNotice),
4735
+ },
4736
+ }
4737
+ : {}),
4738
+ });
4739
+ if (ccPass !== undefined) {
4740
+ recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: spec.taskId ?? sessionId, trigger: "frontier" });
4741
+ }
4742
+ const chain = await runGuardChain({
4743
+ edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
4744
+ sessionId, taskId: spec.taskId ?? sessionId, deps, trimPressureRef, recheck, signal,
4745
+ pendingProjectionObservations,
4746
+ });
4747
+ if ("earlyReturn" in chain) {
4748
+ return chain.earlyReturn;
4749
+ }
4750
+ const { working, trimDroppedMessages } = chain;
4751
+ let trimmed = chain.trimmed;
4752
+ trimmed = applyGitFrameGuard({
4753
+ before: working,
4754
+ trimmed,
4755
+ budgetTokens: guardAt,
4756
+ ref: gitStatusRef,
4757
+ charsPerToken,
4758
+ onDegrade: (message) => {
4759
+ try {
4760
+ deps.onError?.(new Error(message), { phase: "degraded", sessionId, classification: "env-git-snapshot" });
4761
+ }
4762
+ catch {
4763
+ }
4764
+ },
4765
+ });
4766
+ const swept = dropOrphanToolResults(trimmed);
4767
+ if (swept.dropped.length > 0) {
4656
4768
  try {
4657
- deps.onError?.(new Error(message), { phase: "degraded", sessionId, classification: "env-git-snapshot" });
4769
+ deps.onError?.(new Error(`context belt: dropped ${swept.dropped.length} orphan toolResult(s) whose tool-call assistant was not in the request view — ` +
4770
+ swept.dropped
4771
+ .map((d) => `toolCallId=${d.toolCallId} at index ${d.index} (prev=${d.prevRole}, next=${d.nextRole})`)
4772
+ .join("; ")), { phase: "hook", sessionId });
4658
4773
  }
4659
4774
  catch {
4660
4775
  }
4661
- },
4662
- });
4663
- const swept = dropOrphanToolResults(trimmed);
4664
- if (swept.dropped.length > 0) {
4665
- try {
4666
- deps.onError?.(new Error(`context belt: dropped ${swept.dropped.length} orphan toolResult(s) whose tool-call assistant was not in the request view — ` +
4667
- swept.dropped
4668
- .map((d) => `toolCallId=${d.toolCallId} at index ${d.index} (prev=${d.prevRole}, next=${d.nextRole})`)
4669
- .join("; ")), { phase: "hook", sessionId });
4670
- }
4671
- catch {
4672
4776
  }
4777
+ requestLossyRef.current =
4778
+ replayed !== healed ||
4779
+ capped !== replayed ||
4780
+ mediaCapped !== capped ||
4781
+ edited !== mediaCapped ||
4782
+ working !== edited ||
4783
+ trimDroppedMessages ||
4784
+ swept.dropped.length > 0 ||
4785
+ gitStatusRef.overBudgetShrunk === true;
4786
+ microCompact.projectionRef.current = { messages: swept.messages, keyOf: replay.index.keyOf };
4787
+ return { messages: swept.messages };
4788
+ }
4789
+ finally {
4790
+ flushProjectionObservations(pendingProjectionObservations);
4673
4791
  }
4674
- requestLossyRef.current =
4675
- replayed !== healed ||
4676
- capped !== replayed ||
4677
- mediaCapped !== capped ||
4678
- edited !== mediaCapped ||
4679
- trimDroppedMessages ||
4680
- swept.dropped.length > 0 ||
4681
- gitStatusRef.overBudgetShrunk === true;
4682
- microCompact.projectionRef.current = { messages: swept.messages, keyOf: replay.index.keyOf };
4683
- return { messages: swept.messages };
4684
4792
  });
4685
4793
  const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
4686
4794
  const cacheFingerprint = cacheBreakDetector
@@ -51,6 +51,13 @@ interface ResumeRun {
51
51
  * wake message second), each under its own trusted framing — the old merge-into-the-slot shape
52
52
  * silently DISPLACED the parked (undelivered) supervisor steer. Wake outcomes only. */
53
53
  wakeMessage?: Omit<PendingSteerEntry, "seq">;
54
+ /** design/373 §4.3 (D2) — the userPromptSubmit screen's `additionalContext` for {@link wakeMessage},
55
+ * captured at the resume ENTRY (the message is screened once, pre-CAS, on the resuming process's
56
+ * hook) and delivered by the drain as the engine's own reminder AHEAD of the wake frame — carrying
57
+ * it forward is what keeps the hook single-run (re-screening at the drain would be the double-run
58
+ * §4.3-3 reserves for the cross-process parked leg). Present only when a wake message passed a
59
+ * screen that supplied context. */
60
+ wakeMessageHookContext?: string;
54
61
  /** Compensation hook (design/45/49): called iff the resumed run fails with `resume.env_failed` (post-CAS
55
62
  * workspace `resumeVM` failed) OR `resume.tool_unavailable` (P-7: the approved tool vanished) — in both
56
63
  * the CAS already consumed the checkpoint but the pending action never ran. `resumeStream` supplies a