@sema-agent/core 5.61.0 → 5.62.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 (40) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/brain/open-responses.js +8 -3
  3. package/dist/brain/openai.js +4 -4
  4. package/dist/brain/stream-engine.d.ts +13 -2
  5. package/dist/brain/stream-engine.js +3 -3
  6. package/dist/core/auto-mode-prompt-assets.js +1 -1
  7. package/dist/core/checkpoint-store.d.ts +36 -4
  8. package/dist/core/checkpoint-store.js +1 -0
  9. package/dist/core/governance-codes.d.ts +1 -1
  10. package/dist/core/governance-codes.js +2 -0
  11. package/dist/core/hooks.d.ts +83 -4
  12. package/dist/core/hooks.js +3 -3
  13. package/dist/core/park-selfcheck.js +2 -0
  14. package/dist/core/pricing.d.ts +24 -0
  15. package/dist/core/pricing.js +18 -0
  16. package/dist/core/runner/prepare-config-doors.d.ts +34 -0
  17. package/dist/core/runner/prepare-config-doors.js +55 -0
  18. package/dist/core/runner/prepare-task.d.ts +46 -7
  19. package/dist/core/runner/prepare-task.js +77 -42
  20. package/dist/core/runner/runtask.d.ts +7 -0
  21. package/dist/core/runner/runtask.js +198 -13
  22. package/dist/core/runner/turn-attachments.d.ts +137 -5
  23. package/dist/core/runner/turn-attachments.js +25 -2
  24. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  25. package/dist/core/tool-errors.d.ts +2 -1
  26. package/dist/core/tool-policy.d.ts +27 -0
  27. package/dist/core/types.d.ts +138 -12
  28. package/dist/core/untrusted-text.d.ts +5 -4
  29. package/dist/core/untrusted-text.js +8 -0
  30. package/dist/core/usage-window-store.d.ts +109 -8
  31. package/dist/core/usage-window-store.js +79 -12
  32. package/dist/orchestration/run-workflow-tool.d.ts +2 -2
  33. package/dist/orchestration/workflow.d.ts +2 -2
  34. package/dist/prompt-assembly/event-registry.js +2 -0
  35. package/dist/server/http.d.ts +1 -1
  36. package/dist/stores/file/usage-window-store.d.ts +1 -1
  37. package/dist/stores/file/usage-window-store.js +27 -6
  38. package/dist/tools/loop-tick.js +1 -1
  39. package/dist/tools/scheduler-tools.js +9 -1
  40. package/package.json +1 -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);
@@ -339,7 +394,7 @@ function publishCommittedSuspend(refs, token, gate, scope, remoteHandle, checkpo
339
394
  function hasConversationContent(branch) {
340
395
  return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
341
396
  }
342
- function effectiveDelegationFacts(internals, seedIsDelegatedChild) {
397
+ export function effectiveDelegationFacts(internals, seedIsDelegatedChild) {
343
398
  const isDelegatedChild = internals?.isDelegatedChild !== undefined ? internals.isDelegatedChild === true : seedIsDelegatedChild === true;
344
399
  return { isDelegatedChild, isNonForkChild: isDelegatedChild && internals?.insideFork !== true };
345
400
  }
@@ -475,20 +530,21 @@ const sanitizePreview = (node, depth = 0) => {
475
530
  function resolveApprovalPreview(tools, toolName, args) {
476
531
  const t = tools.find((x) => x.name === toolName || (x.aliases?.includes(toolName) ?? false));
477
532
  if (t?.approvalPreview === undefined)
478
- return undefined;
533
+ return {};
479
534
  try {
480
535
  const raw = t.approvalPreview(args);
481
536
  if (raw === undefined)
482
- return undefined;
537
+ return {};
483
538
  const bytes = JSON.stringify(raw);
484
539
  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);
540
+ return { withheld: "unavailable" };
541
+ if (bytes.length > 16_384) {
542
+ return { preview: { truncated: true, note: `approval preview exceeded 16KiB (${bytes.length} chars serialized)` }, withheld: "oversize" };
543
+ }
544
+ return { preview: sanitizePreview(raw) };
489
545
  }
490
546
  catch {
491
- return undefined;
547
+ return { withheld: "unavailable" };
492
548
  }
493
549
  }
494
550
  function inheritedAskRuleEvidence(deps) {
@@ -839,7 +895,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
839
895
  if (e.type === "task_progress")
840
896
  forwardSink(e);
841
897
  else if (spec.forwardSubagentEvents === true &&
842
- (e.type === "text_delta" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_end")) {
898
+ (e.type === "text_delta" || e.type === "text_end" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_end")) {
843
899
  forwardSink(e);
844
900
  }
845
901
  }
@@ -3140,6 +3196,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3140
3196
  if (ask?.requiresRealApproval === true ||
3141
3197
  ask?.persistedRuleShadowed !== undefined ||
3142
3198
  ask?.decisionReason === "hook" ||
3199
+ ask?.matchedAskRule !== undefined ||
3143
3200
  ask?.inheritedUnresolved === true ||
3144
3201
  ask?.ancestorResolved === true) {
3145
3202
  return {};
@@ -3550,31 +3607,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3550
3607
  sessionId,
3551
3608
  });
3552
3609
  }
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
- })();
3610
+ const usageGovernance = buildUsageGovernance(usageWindows, deps, spec.principal, sessionId);
3578
3611
  const platformSuspendArmed = durableSuspendInfraReady && (envLifetimeSuspendAt !== undefined || usageGovernance !== undefined);
3579
3612
  const checkpointStore = resolveCheckpointStore(spec, deps);
3580
3613
  const durableApproval = spec.durableApproval ??
@@ -3667,8 +3700,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3667
3700
  toolCallId: req.toolCallId,
3668
3701
  args: req.args,
3669
3702
  ...(() => {
3670
- const preview = approvalPreviewOf(req.toolName, req.args);
3671
- return preview !== undefined ? { preview } : {};
3703
+ const p = approvalPreviewOf(req.toolName, req.args);
3704
+ return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
3672
3705
  })(),
3673
3706
  ...ruleOffersOf(req.toolName, req.args, decision.action === "ask" ? decision : undefined),
3674
3707
  message: decision.message ?? `approval required for "${req.toolName}"`,
@@ -4148,7 +4181,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4148
4181
  }
4149
4182
  };
4150
4183
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
4151
- ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage) => {
4184
+ ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule) => {
4152
4185
  const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
4153
4186
  if (syncFirstEligible &&
4154
4187
  runtimeCaps?.forceDurableGate !== true &&
@@ -4306,10 +4339,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4306
4339
  toolName: req.toolName,
4307
4340
  args: parkedArgs,
4308
4341
  ...(() => {
4309
- const preview = approvalPreviewOf(req.toolName, parkedArgs);
4310
- const bidi = carriesBidiControls(parkedArgs) || carriesBidiControls(preview);
4342
+ const p = approvalPreviewOf(req.toolName, parkedArgs);
4343
+ const bidi = carriesBidiControls(parkedArgs) || carriesBidiControls(p.preview);
4311
4344
  return {
4312
- ...(preview !== undefined ? { preview } : {}),
4345
+ ...(p.preview !== undefined ? { preview: p.preview } : {}),
4346
+ ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}),
4313
4347
  ...(bidi ? { hasBidiControls: true } : {}),
4314
4348
  };
4315
4349
  })(),
@@ -4317,6 +4351,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4317
4351
  ...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
4318
4352
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
4319
4353
  ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
4354
+ ...(matchedAskRule !== undefined ? { matchedAskRule } : {}),
4320
4355
  ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
4321
4356
  ...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
4322
4357
  }),
@@ -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
@@ -15,7 +15,7 @@ import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
15
15
  import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill, sanitizeCompactionSettings } from "../auto-compaction.js";
16
16
  import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace, markBoundOnlyQuestionFace } from "../ask-question.js";
17
17
  import { boundInputHashOf } from "../canonical-json.js";
18
- import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
18
+ import { computeCostMicroUsd, isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
19
19
  import { emitTrace } from "../trace.js";
20
20
  import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
21
21
  import { emitTaskOutcome } from "../task-outcome.js";
@@ -36,13 +36,13 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
36
36
  import { assembleResult, errorCodeOf } from "./assemble-result.js";
37
37
  import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, attachmentEnvelopeTags, 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";
38
38
  import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
39
- import { gatedCallIdOf, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
39
+ import { effectiveDelegationFacts, gatedCallIdOf, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
40
40
  import { settleTeardownLeg } from "./teardown-bounded.js";
41
41
  import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
42
42
  import { hasVerifiableStructureSignal } from "./grounding-signal.js";
43
43
  import { hasDestroy, isIsolated } from "../remote-env.js";
44
44
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
45
- import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
45
+ import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
46
46
  import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
47
47
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
48
48
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
@@ -395,7 +395,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
395
395
  const governance = prepared.usageGovernance;
396
396
  try {
397
397
  const at = Date.now();
398
- await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
398
+ await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
399
399
  const windowRead = governance.check(at);
400
400
  let answer;
401
401
  if (ledgerReadDeadline !== undefined) {
@@ -606,6 +606,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
606
606
  const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
607
607
  const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
608
608
  const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
609
+ const totalTokensOn = rs.attach.attachmentsCfg?.totalTokensReminder === true && rs.turn.lastTurnHadToolCalls;
609
610
  const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
610
611
  const undiscoveredTools = toolSearchReminderOn
611
612
  ? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
@@ -623,6 +624,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
623
624
  changedFiles: changedFilesOn,
624
625
  planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
625
626
  budgetUsd: budgetUsdOn,
627
+ totalTokensReminder: totalTokensOn,
628
+ ...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
629
+ ? { totalTokensReminderMode: rs.attach.attachmentsCfg.totalTokensReminderMode }
630
+ : {}),
626
631
  backgroundTasks: backgroundTasksOn,
627
632
  toolsDelta: toolsDeltaOn,
628
633
  agentListing: rs.attach.agentListingOn,
@@ -636,6 +641,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
636
641
  ...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
637
642
  ? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
638
643
  : {}),
644
+ ...(totalTokensOn && rs.budget.maxTokensWindow !== undefined
645
+ ? { totalTokens: { used: stats.tokens, total: rs.budget.maxTokensWindow } }
646
+ : {}),
639
647
  ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
640
648
  ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
641
649
  ...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
@@ -1131,6 +1139,9 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1131
1139
  rs.turn.firstTokenAt = Date.now();
1132
1140
  pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
1133
1141
  }
1142
+ else if (ev.type === "text_end" && typeof ev.content === "string" && ev.content.length !== 0) {
1143
+ pushContent({ type: "text_end", content: ev.content, ...ident() });
1144
+ }
1134
1145
  else if (ev.type === "thinking_delta" && ev.delta) {
1135
1146
  pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
1136
1147
  }
@@ -1892,6 +1903,47 @@ export class Runner {
1892
1903
  return;
1893
1904
  }
1894
1905
  }
1906
+ if (resultValue !== undefined || h.loop.ended)
1907
+ throw steeringError("the task is no longer running");
1908
+ if (mintsAFrame) {
1909
+ const screen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
1910
+ if (screen !== undefined) {
1911
+ let decision;
1912
+ try {
1913
+ const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) }));
1914
+ if (seat.expired) {
1915
+ if (seat.cause === "timeout") {
1916
+ try {
1917
+ this.deps.onError?.(hookSeatExpiredError("userPromptSubmit", h.hookTimeoutMs, seat.cause, "the steering input was NOT accepted (fail-closed) and the caller was refused typed"), { phase: "hook", sessionId: h.sessionId });
1918
+ }
1919
+ catch {
1920
+ }
1921
+ }
1922
+ throw steeringError(seat.cause === "timeout"
1923
+ ? `the deployment's userPromptSubmit hook did not answer within its ${h.hookTimeoutMs}ms bound while screening this steering input; the input was NOT accepted (fail-closed)`
1924
+ : `the task was cancelled while the deployment's userPromptSubmit hook was still screening this steering input; the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
1925
+ }
1926
+ decision = seat.value;
1927
+ }
1928
+ catch (hookErr) {
1929
+ if (hookErr instanceof Error && hookErr.code === "steering.blocked_by_hook")
1930
+ throw hookErr;
1931
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
1932
+ try {
1933
+ this.deps.onError?.(err, { phase: "hook", sessionId: h.sessionId });
1934
+ }
1935
+ catch {
1936
+ }
1937
+ throw steeringError(`the deployment's userPromptSubmit hook crashed while screening this steering input (${inlineUntrusted(err.message)}); the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
1938
+ }
1939
+ if (decision?.block !== undefined && decision.block !== "") {
1940
+ throw steeringError(`the deployment's userPromptSubmit hook blocked this steering input: ${inlineUntrusted(decision.block)}`, "steering.blocked_by_hook");
1941
+ }
1942
+ if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
1943
+ payload = `${formatHookFeedback(decision.additionalContext, h.reminderMark)}\n\n${payload}`;
1944
+ }
1945
+ }
1946
+ }
1895
1947
  const injectFramed = async () => {
1896
1948
  const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
1897
1949
  if (priority === "later") {
@@ -2236,7 +2288,7 @@ export class Runner {
2236
2288
  }
2237
2289
  }
2238
2290
  const loopLatch = { ended: false, userInterrupted: false };
2239
- onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId });
2291
+ onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
2240
2292
  const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
2241
2293
  prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
2242
2294
  if (resume &&
@@ -2258,7 +2310,7 @@ export class Runner {
2258
2310
  const rs = createRunState();
2259
2311
  rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
2260
2312
  rs.telemetry.pricing = this.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);
2261
- rs.telemetry.pricingConfigured = this.deps.pricing?.[prepared.model.id] !== undefined || prepared.model.cost !== undefined;
2313
+ rs.telemetry.pricingConfigured = isModelPriced(prepared.model, this.deps.pricing);
2262
2314
  if (spec.limits?.degrade) {
2263
2315
  try {
2264
2316
  rs.degrade.degradeToModel = resolveModel(spec.limits.degrade.to, modelCatalog);
@@ -2298,6 +2350,12 @@ export class Runner {
2298
2350
  catch (err) {
2299
2351
  discloseNoteTaskRunFailure(err);
2300
2352
  }
2353
+ const noteUnevaluablePriceTable = (p) => {
2354
+ if (prepared.usageGovernance?.governsCost !== true)
2355
+ return;
2356
+ if (malformedPricingField(p) !== undefined)
2357
+ rs.telemetry.unpricedSpend = true;
2358
+ };
2301
2359
  rs.degrade.recordDegraded = (info, toModel) => {
2302
2360
  if (rs.degrade.degraded !== undefined)
2303
2361
  return;
@@ -2312,13 +2370,15 @@ export class Runner {
2312
2370
  }
2313
2371
  if (m) {
2314
2372
  rs.telemetry.pricing = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
2315
- rs.telemetry.pricingConfigured = this.deps.pricing?.[m.id] !== undefined || m.cost !== undefined;
2373
+ rs.telemetry.pricingConfigured = isModelPriced(m, this.deps.pricing);
2374
+ noteUnevaluablePriceTable(rs.telemetry.pricing);
2316
2375
  rs.telemetry.cacheFamily = cacheFamilyOf(m);
2317
2376
  }
2318
2377
  else {
2319
2378
  if (this.deps.pricing?.[info.to]) {
2320
2379
  rs.telemetry.pricing = this.deps.pricing[info.to];
2321
2380
  rs.telemetry.pricingConfigured = true;
2381
+ noteUnevaluablePriceTable(rs.telemetry.pricing);
2322
2382
  }
2323
2383
  else {
2324
2384
  rs.telemetry.pricingConfigured = false;
@@ -2737,9 +2797,10 @@ export class Runner {
2737
2797
  return;
2738
2798
  const fam = cacheFamilyOf(m);
2739
2799
  const price = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
2740
- const priced = this.deps.pricing?.[m.id] !== undefined || m.cost !== undefined;
2800
+ const priced = isModelPriced(m, this.deps.pricing);
2741
2801
  if (!priced)
2742
2802
  rs.telemetry.unpricedSpend = true;
2803
+ noteUnevaluablePriceTable(price);
2743
2804
  const { totalInputTokens, uncachedInputTokens, costMicroUsd } = usageCostMicroUsd(fam, u, price);
2744
2805
  stats.tokens += u.totalTokens || 0;
2745
2806
  stats.promptTokens += uncachedInputTokens;
@@ -3312,6 +3373,72 @@ export class Runner {
3312
3373
  ...(resume.wakeMessage !== undefined ? [{ entry: resume.wakeMessage, source: "wake" }] : []),
3313
3374
  ];
3314
3375
  for (const { entry: steer, source } of resumeFrames) {
3376
+ if (source === "wake" && resume.wakeMessageHookContext !== undefined) {
3377
+ const hookStart = continuation.length;
3378
+ continuation += "\n\n" + formatHookFeedback(resume.wakeMessageHookContext, prepared.reminderMark);
3379
+ engineSegments.push({ start: hookStart, end: continuation.length });
3380
+ }
3381
+ if (source === "steer") {
3382
+ const parkedScreen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
3383
+ if (parkedScreen !== undefined) {
3384
+ let outcome;
3385
+ try {
3386
+ const seat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => parkedScreen(steer.text, {
3387
+ identity: prepared.hookIdentity,
3388
+ signal: sig,
3389
+ source: "parked_redelivery",
3390
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
3391
+ ...(steer.actor !== undefined ? { actor: snapshotActorAssertion(steer.actor) } : {}),
3392
+ }));
3393
+ outcome = seat.expired
3394
+ ? { blocked: seat.cause === "timeout" ? `the screen did not answer within its ${prepared.hookTimeoutMs}ms bound (fail-closed)` : "the run was cancelled while the screen was still deciding (fail-closed)" }
3395
+ : seat.value?.block !== undefined && seat.value.block !== ""
3396
+ ? { blocked: inlineUntrusted(seat.value.block) }
3397
+ : { ...(seat.value?.additionalContext !== undefined && seat.value.additionalContext !== "" ? { context: seat.value.additionalContext } : {}) };
3398
+ }
3399
+ catch (hookErr) {
3400
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
3401
+ try {
3402
+ this.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
3403
+ }
3404
+ catch {
3405
+ }
3406
+ outcome = { blocked: `the screen crashed (${inlineUntrusted(err.message)}; fail-closed)` };
3407
+ }
3408
+ if ("blocked" in outcome) {
3409
+ deliverEngineNotice(this.deps.onNotice, {
3410
+ code: "steering.parked_input_blocked",
3411
+ message: "a parked steering input was withheld by the deployment's userPromptSubmit screen when this resume redelivered it: " +
3412
+ `${outcome.blocked}. The parked row was consumed with the checkpoint; the instruction never reached the model — ` +
3413
+ "re-issue it if it still applies.",
3414
+ detail: {
3415
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
3416
+ sessionId: prepared.sessionId,
3417
+ ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
3418
+ },
3419
+ });
3420
+ queue.push({
3421
+ ...buildHumanInputEvent({
3422
+ carrier: source,
3423
+ source,
3424
+ delivery: "blocked",
3425
+ sessionSeq: nextHumanInputSeq(prepared.harness),
3426
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
3427
+ ...(steer.actor !== undefined ? { actor: steer.actor } : {}),
3428
+ ...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
3429
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
3430
+ }),
3431
+ ...ident(),
3432
+ });
3433
+ continue;
3434
+ }
3435
+ if ("context" in outcome && outcome.context !== undefined) {
3436
+ const hookStart = continuation.length;
3437
+ continuation += "\n\n" + formatHookFeedback(outcome.context, prepared.reminderMark);
3438
+ engineSegments.push({ start: hookStart, end: continuation.length });
3439
+ }
3440
+ }
3441
+ }
3315
3442
  const start = continuation.length;
3316
3443
  const projected = projectHumanInput({ text: steer.text, actor: steer.actor, source });
3317
3444
  continuation +=
@@ -3362,7 +3489,7 @@ export class Runner {
3362
3489
  const userPromptSubmit = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
3363
3490
  if (userPromptSubmit) {
3364
3491
  try {
3365
- const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig }));
3492
+ const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig, source: "objective", ...(objectiveActor !== undefined ? { actor: snapshotActorAssertion(objectiveActor) } : {}) }));
3366
3493
  if (promptSeat.expired) {
3367
3494
  if (promptSeat.cause === "timeout") {
3368
3495
  try {
@@ -3586,7 +3713,7 @@ export class Runner {
3586
3713
  }
3587
3714
  if (prepared.usageGovernance !== undefined) {
3588
3715
  try {
3589
- await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, Date.now()), () => this.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
3716
+ await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, Date.now()), () => this.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
3590
3717
  }
3591
3718
  catch (flushErr) {
3592
3719
  this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
@@ -4169,7 +4296,9 @@ export class Runner {
4169
4296
  }
4170
4297
  }
4171
4298
  let wakeMessage;
4299
+ let wakeHookContext;
4172
4300
  const outcomeGate = outcome.gate;
4301
+ const resumeSignal = taskConfig.signal;
4173
4302
  let suppliedMessage;
4174
4303
  if (outcomeGate === "wake") {
4175
4304
  suppliedMessage = outcome.message;
@@ -4207,6 +4336,62 @@ export class Runner {
4207
4336
  }
4208
4337
  if (suppliedMessage !== undefined) {
4209
4338
  wakeMessage = validatePendingSteer(suppliedMessage);
4339
+ {
4340
+ const resumeScreenHooks = taskConfig.hooks ?? this.deps.hooks;
4341
+ const screen = resumeScreenHooks?.userPromptSubmit;
4342
+ if (screen !== undefined && resumeSignal?.aborted !== true) {
4343
+ const screenedMessage = wakeMessage;
4344
+ const identity = mintHookInvocationIdentity({
4345
+ sessionId: cp.sessionId,
4346
+ taskId: taskConfig.taskId ?? cp.sessionId,
4347
+ legKind: "resume",
4348
+ isDelegatedChild: effectiveDelegationFacts(internals, cp.state.isDelegatedChild).isDelegatedChild,
4349
+ ...(internals?.insideFork === true ? { insideFork: true } : {}),
4350
+ ...(internals?.agentName !== undefined ? { agentName: internals.agentName } : {}),
4351
+ ...(internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId } : {}),
4352
+ });
4353
+ const screenTimeoutMs = resolveHookTimeoutMs(resumeScreenHooks?.timeoutMs, (badErr) => {
4354
+ try {
4355
+ this.deps.onError?.(badErr instanceof Error ? badErr : new Error(String(badErr)), { phase: "hook", sessionId: cp.sessionId });
4356
+ }
4357
+ catch {
4358
+ }
4359
+ }, resumeScreenHooks);
4360
+ let decision;
4361
+ try {
4362
+ const seat = await runHookSeat("userPromptSubmit", { timeoutMs: screenTimeoutMs, ...(resumeSignal !== undefined ? { signal: resumeSignal } : {}), abortEnds: true }, (sig) => screen(screenedMessage.text, {
4363
+ identity,
4364
+ signal: sig,
4365
+ source: "resume_message",
4366
+ ...(screenedMessage.inputId !== undefined ? { inputId: screenedMessage.inputId } : {}),
4367
+ ...(screenedMessage.actor !== undefined ? { actor: snapshotActorAssertion(screenedMessage.actor) } : {}),
4368
+ }));
4369
+ if (seat.expired) {
4370
+ throw new CheckpointError("steering.blocked_by_hook", seat.cause === "timeout"
4371
+ ? `the deployment's userPromptSubmit hook did not answer within its ${screenTimeoutMs}ms bound while screening this wake message; the resume was refused (fail-closed) and the checkpoint stays pending`
4372
+ : `the resume was cancelled while the deployment's userPromptSubmit hook was still screening this wake message; the checkpoint stays pending`);
4373
+ }
4374
+ decision = seat.value;
4375
+ }
4376
+ catch (hookErr) {
4377
+ if (hookErr instanceof CheckpointError)
4378
+ throw hookErr;
4379
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
4380
+ try {
4381
+ this.deps.onError?.(err, { phase: "hook", sessionId: cp.sessionId });
4382
+ }
4383
+ catch {
4384
+ }
4385
+ throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook crashed while screening this wake message (${inlineUntrusted(err.message)}); the resume was refused (fail-closed) and the checkpoint stays pending`);
4386
+ }
4387
+ if (decision?.block !== undefined && decision.block !== "") {
4388
+ throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook blocked this wake message: ${inlineUntrusted(decision.block)}`);
4389
+ }
4390
+ if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
4391
+ wakeHookContext = decision.additionalContext;
4392
+ }
4393
+ }
4394
+ }
4210
4395
  }
4211
4396
  else if (readPendingSteerQueue(cp.state).length === 0) {
4212
4397
  throw new CheckpointError("wake.nothing_to_deliver", "cannot wake: no message was supplied and the checkpoint holds no parked pendingSteer — an empty " +
@@ -4454,7 +4639,7 @@ export class Runner {
4454
4639
  "resuming here would run the leg with that ceiling unenforced. Rejected pre-CAS (the checkpoint stays pending): finish it on a worker of the " +
4455
4640
  "previous release, or start a fresh task bounded by resourceSuspend.totalTokens / totalBudgetUsd.");
4456
4641
  }
4457
- if (taskConfig.signal?.aborted === true && cp.status === "pending") {
4642
+ if (resumeSignal?.aborted === true && cp.status === "pending") {
4458
4643
  throw new CheckpointError("checkpoint.resume_aborted", "the resume was handed an ALREADY-ABORTED signal — refusing to consume the approval on a leg that cannot run it (the checkpoint stays pending and is resumable with a live signal)");
4459
4644
  }
4460
4645
  if (cp.state.workspaceHandle !== undefined && this.deps.executionEnvFactory === undefined) {
@@ -4528,7 +4713,7 @@ export class Runner {
4528
4713
  const preCasWindows = resolveUsageWindows(this.deps.usageWindows);
4529
4714
  if (preCasWindows !== undefined && preCasWindows.length > 0) {
4530
4715
  const ledgerKey = (suppliedPrincipal ?? rowPrincipal) || GLOBAL_USAGE_KEY;
4531
- const wait = usageRetryAfterMs(await this.deps.usageWindowStore.read(ledgerKey, preCasWindows, Date.now()));
4716
+ const wait = usageRetryAfterMs(await this.deps.usageWindowStore.read(ledgerKey, preCasWindows, Date.now()), preCasWindows);
4532
4717
  if (wait !== undefined) {
4533
4718
  throw new CheckpointError("resume.usage_window_exhausted", `a deployment usage window for ledger key ${JSON.stringify(ledgerKey)} is exhausted, and this checkpoint still owes a delivery a re-mint cannot carry — ` +
4534
4719
  `refused pre-CAS (nothing consumed, nothing unpinned): the same token and the same decision are redeemable once the window frees, in ${String(wait)}ms`, { retryAfterMs: wait });
@@ -4688,7 +4873,7 @@ export class Runner {
4688
4873
  ? new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run AND the store refused to reopen the checkpoint — the approval is terminally consumed and the suspended work was not executed; a retry needs a fresh approval")
4689
4874
  : new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run and the reopen attempt FAILED IN FLIGHT — the checkpoint's state is unprovable from here: it may already be pending again. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead (the approved action did NOT run either way)");
4690
4875
  }
4691
- return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}) }, resumeInternals);
4876
+ return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
4692
4877
  }
4693
4878
  async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
4694
4879
  const { pendingAction } = resume.cp;