@sema-agent/core 5.60.1 → 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 (64) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/dist/agents/subagent.d.ts +4 -2
  3. package/dist/agents/subagent.js +9 -9
  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-mode-prompt-assets.js +1 -1
  9. package/dist/core/checkpoint-store.d.ts +36 -4
  10. package/dist/core/checkpoint-store.js +1 -0
  11. package/dist/core/governance-codes.d.ts +1 -1
  12. package/dist/core/governance-codes.js +4 -2
  13. package/dist/core/hooks.d.ts +83 -4
  14. package/dist/core/hooks.js +3 -3
  15. package/dist/core/memory-engine/consolidation-driver.d.ts +19 -1
  16. package/dist/core/memory-engine/consolidation-driver.js +75 -3
  17. package/dist/core/memory-engine/consolidation.d.ts +52 -5
  18. package/dist/core/memory-engine/consolidation.js +3 -1
  19. package/dist/core/memory-engine/distiller.d.ts +89 -1
  20. package/dist/core/memory-engine/distiller.js +94 -5
  21. package/dist/core/memory-engine/engine.d.ts +8 -0
  22. package/dist/core/memory-engine/engine.js +51 -8
  23. package/dist/core/memory-engine/index.d.ts +1 -1
  24. package/dist/core/memory-engine/index.js +1 -1
  25. package/dist/core/park-selfcheck.js +2 -0
  26. package/dist/core/pricing.d.ts +24 -0
  27. package/dist/core/pricing.js +18 -0
  28. package/dist/core/runner/prepare-config-doors.d.ts +34 -0
  29. package/dist/core/runner/prepare-config-doors.js +55 -0
  30. package/dist/core/runner/prepare-task.d.ts +52 -10
  31. package/dist/core/runner/prepare-task.js +77 -42
  32. package/dist/core/runner/runtask.d.ts +7 -0
  33. package/dist/core/runner/runtask.js +254 -38
  34. package/dist/core/runner/turn-attachments.d.ts +137 -5
  35. package/dist/core/runner/turn-attachments.js +25 -2
  36. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  37. package/dist/core/task-notification.d.ts +50 -23
  38. package/dist/core/task-notification.js +20 -4
  39. package/dist/core/tool-errors.d.ts +2 -1
  40. package/dist/core/tool-policy.d.ts +27 -0
  41. package/dist/core/types.d.ts +214 -31
  42. package/dist/core/untrusted-text.d.ts +5 -4
  43. package/dist/core/untrusted-text.js +8 -0
  44. package/dist/core/usage-window-store.d.ts +109 -8
  45. package/dist/core/usage-window-store.js +79 -12
  46. package/dist/engine/harness/agent-harness.d.ts +58 -2
  47. package/dist/engine/harness/agent-harness.js +115 -5
  48. package/dist/engine/loop/agent-loop.js +153 -15
  49. package/dist/engine/loop/types.d.ts +32 -0
  50. package/dist/index.d.ts +2 -2
  51. package/dist/index.js +2 -2
  52. package/dist/orchestration/run-workflow-tool.d.ts +9 -4
  53. package/dist/orchestration/run-workflow-tool.js +1 -1
  54. package/dist/orchestration/workflow.d.ts +2 -2
  55. package/dist/prompt-assembly/event-registry.js +2 -0
  56. package/dist/server/http.d.ts +1 -1
  57. package/dist/stores/file/usage-window-store.d.ts +1 -1
  58. package/dist/stores/file/usage-window-store.js +27 -6
  59. package/dist/tools/loop-tick.js +1 -1
  60. package/dist/tools/monitor.d.ts +3 -3
  61. package/dist/tools/monitor.js +1 -1
  62. package/dist/tools/scheduler-tools.js +9 -1
  63. package/package.json +1 -1
  64. package/test/export-surface.snapshot.json +7 -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