@sema-agent/core 7.56.0 → 7.57.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 (173) hide show
  1. package/CHANGELOG.md +541 -0
  2. package/LICENSE +1 -1
  3. package/dist/agents/org-delivery-gate.js +3 -4
  4. package/dist/agents/peer-held-queue.d.ts +69 -49
  5. package/dist/agents/peer-held-queue.js +255 -106
  6. package/dist/agents/peer-session-drain.js +63 -43
  7. package/dist/agents/send-message-tool.js +3 -2
  8. package/dist/agents/subagent.js +8 -8
  9. package/dist/brain/anthropic.js +16 -8
  10. package/dist/brain/open-responses.js +10 -8
  11. package/dist/brain/openai.js +11 -9
  12. package/dist/brain/provider-dialect.d.ts +3 -2
  13. package/dist/brain/provider-dialect.js +2 -2
  14. package/dist/brain/request-params.d.ts +6 -0
  15. package/dist/brain/request-params.js +14 -1
  16. package/dist/brain/stream-engine.d.ts +9 -0
  17. package/dist/brain/stream-engine.js +24 -10
  18. package/dist/brain/stream-shared.d.ts +5 -4
  19. package/dist/brain/stream-shared.js +4 -4
  20. package/dist/core/allow-entry.d.ts +3 -10
  21. package/dist/core/allow-entry.js +17 -25
  22. package/dist/core/approval-object.d.ts +21 -5
  23. package/dist/core/approval-object.js +44 -17
  24. package/dist/core/ask-origin.d.ts +2 -2
  25. package/dist/core/auto-compaction.d.ts +71 -6
  26. package/dist/core/auto-compaction.js +42 -26
  27. package/dist/core/background-agent-store.d.ts +6 -2
  28. package/dist/core/background-agent-store.js +1 -1
  29. package/dist/core/checkpoint-requirements.d.ts +7 -1
  30. package/dist/core/checkpoint-requirements.js +2 -0
  31. package/dist/core/checkpoint-store.d.ts +17 -29
  32. package/dist/core/checkpoint-store.js +19 -11
  33. package/dist/core/cross-boundary-taint.js +4 -2
  34. package/dist/core/engine-notice.d.ts +3 -2
  35. package/dist/core/engine-notice.js +6 -0
  36. package/dist/core/file-history-store.d.ts +14 -8
  37. package/dist/core/file-history-store.js +14 -4
  38. package/dist/core/gate-lanes.js +2 -3
  39. package/dist/core/hooks.d.ts +2 -2
  40. package/dist/core/mailbox-store.d.ts +62 -1
  41. package/dist/core/mailbox-store.js +59 -0
  42. package/dist/core/mcp-name-judge.d.ts +46 -0
  43. package/dist/core/mcp-name-judge.js +61 -0
  44. package/dist/core/mcp-probe.d.ts +1 -1
  45. package/dist/core/mcp-probe.js +2 -5
  46. package/dist/core/mcp.js +10 -30
  47. package/dist/core/memory-engine/consolidation-driver.js +4 -3
  48. package/dist/core/memory-engine/distiller.js +26 -17
  49. package/dist/core/memory-engine/layout.js +5 -2
  50. package/dist/core/park-selfcheck.js +2 -0
  51. package/dist/core/permission-rule-consent.d.ts +5 -3
  52. package/dist/core/permission-rule-consent.js +20 -15
  53. package/dist/core/permission-rule-model.d.ts +43 -2
  54. package/dist/core/permission-rule-model.js +52 -8
  55. package/dist/core/permission-rule-org.js +1 -1
  56. package/dist/core/protocol-http-auth.d.ts +16 -2
  57. package/dist/core/protocol-http-auth.js +42 -11
  58. package/dist/core/protocol-naming.d.ts +20 -0
  59. package/dist/core/protocol-naming.js +20 -3
  60. package/dist/core/read-only-shell.d.ts +20 -5
  61. package/dist/core/read-only-shell.js +29 -14
  62. package/dist/core/remote-env.d.ts +37 -1
  63. package/dist/core/remote-env.js +11 -0
  64. package/dist/core/resume-startup-cause.d.ts +24 -2
  65. package/dist/core/resume-startup-cause.js +20 -1
  66. package/dist/core/roles.js +50 -7
  67. package/dist/core/runner/compact-session.d.ts +10 -0
  68. package/dist/core/runner/compact-session.js +202 -0
  69. package/dist/core/runner/compaction-call-options.d.ts +2 -2
  70. package/dist/core/runner/compaction-seams.d.ts +21 -1
  71. package/dist/core/runner/compaction-seams.js +11 -8
  72. package/dist/core/runner/contracts.d.ts +23 -16
  73. package/dist/core/runner/derived-route-fallback.d.ts +13 -0
  74. package/dist/core/runner/derived-route-fallback.js +6 -0
  75. package/dist/core/runner/env-capability-census.d.ts +3 -2
  76. package/dist/core/runner/env-capability-census.js +9 -3
  77. package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
  78. package/dist/core/runner/permission-rule-lanes.js +17 -11
  79. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
  80. package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
  81. package/dist/core/runner/prepare-config-doors.d.ts +6 -0
  82. package/dist/core/runner/prepare-config-doors.js +4 -3
  83. package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
  84. package/dist/core/runner/prepare-hands-readface.js +2 -0
  85. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  86. package/dist/core/runner/prepare-park-ask.js +2 -2
  87. package/dist/core/runner/prepare-policy-chain.js +2 -4
  88. package/dist/core/runner/prepare-task.d.ts +4 -1
  89. package/dist/core/runner/prepare-task.js +23 -27
  90. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
  91. package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
  92. package/dist/core/runner/rewind-files.d.ts +2 -5
  93. package/dist/core/runner/rewind-files.js +9 -51
  94. package/dist/core/runner/run-leg.js +10 -21
  95. package/dist/core/runner/run-notification-lane.d.ts +2 -0
  96. package/dist/core/runner/run-notification-lane.js +2 -2
  97. package/dist/core/runner/run-recovery-lanes.js +3 -4
  98. package/dist/core/runner/run-settle-and-teardown.js +5 -11
  99. package/dist/core/runner/run-terminal-adoption.js +3 -4
  100. package/dist/core/runner/run-turn-boundary.js +3 -5
  101. package/dist/core/runner/runtask.d.ts +8 -1
  102. package/dist/core/runner/runtask.js +28 -34
  103. package/dist/core/runner/send-message-continuation.js +1 -1
  104. package/dist/core/runner/session-verb-doors.d.ts +28 -0
  105. package/dist/core/runner/session-verb-doors.js +48 -0
  106. package/dist/core/runner/session-write-ledger.d.ts +30 -0
  107. package/dist/core/runner/session-write-ledger.js +101 -0
  108. package/dist/core/runner/stream-settle-backstop.js +11 -14
  109. package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
  110. package/dist/core/runner/sub-agent-deny-lists.js +21 -10
  111. package/dist/core/runner/teardown-bounded.d.ts +1 -0
  112. package/dist/core/runner/teardown-bounded.js +1 -0
  113. package/dist/core/runner-deps.d.ts +2 -2
  114. package/dist/core/secret-env.d.ts +4 -0
  115. package/dist/core/secret-env.js +3 -0
  116. package/dist/core/session-policy-store.d.ts +7 -2
  117. package/dist/core/session-policy-store.js +1 -1
  118. package/dist/core/shell-wrapper-projection.d.ts +1 -1
  119. package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
  120. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  121. package/dist/core/store-contracts/file-history-store-contract.js +9 -0
  122. package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
  123. package/dist/core/task-registry-agent.d.ts +8 -4
  124. package/dist/core/task-registry-agent.js +14 -8
  125. package/dist/core/task-registry-shared.d.ts +35 -6
  126. package/dist/core/task-registry-shared.js +11 -0
  127. package/dist/core/task-registry.d.ts +1 -1
  128. package/dist/core/task-registry.js +1 -1
  129. package/dist/core/tool-catalog-entries.d.ts +6 -0
  130. package/dist/core/tool-catalog-entries.js +1 -0
  131. package/dist/core/tool-name-aliases.d.ts +32 -2
  132. package/dist/core/tool-name-aliases.js +23 -0
  133. package/dist/core/tool-name-readings.d.ts +4 -0
  134. package/dist/core/tool-name-readings.js +1 -0
  135. package/dist/core/tool-policy.d.ts +10 -6
  136. package/dist/core/tool-policy.js +4 -7
  137. package/dist/core/trace.d.ts +4 -1
  138. package/dist/core/trace.js +2 -1
  139. package/dist/core/untrusted-egress.d.ts +20 -0
  140. package/dist/core/untrusted-egress.js +77 -3
  141. package/dist/engine/harness/agent-harness.d.ts +7 -5
  142. package/dist/engine/harness/agent-harness.js +11 -2
  143. package/dist/engine/loop/agent-loop.d.ts +1 -4
  144. package/dist/engine/loop/agent-loop.js +26 -4
  145. package/dist/index.d.ts +12 -7
  146. package/dist/index.js +7 -4
  147. package/dist/internal/harness.d.ts +2 -1
  148. package/dist/internal/harness.js +1 -0
  149. package/dist/primitives/credential-ref.d.ts +89 -0
  150. package/dist/primitives/credential-ref.js +125 -0
  151. package/dist/primitives/index.d.ts +2 -0
  152. package/dist/primitives/index.js +1 -0
  153. package/dist/stores/file/checkpoint-store.d.ts +0 -4
  154. package/dist/stores/file/checkpoint-store.js +0 -1
  155. package/dist/stores/file/file-history-store.js +12 -4
  156. package/dist/stores/file/mailbox-store.d.ts +2 -1
  157. package/dist/stores/file/mailbox-store.js +26 -1
  158. package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
  159. package/dist/tools/fs/bash-readonly-classifier.js +5 -10
  160. package/dist/tools/fs/fs-bash.js +10 -3
  161. package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
  162. package/dist/tools/fs/pattern-kill-guard.js +68 -36
  163. package/dist/tools/fs/shell-read-check.d.ts +10 -0
  164. package/dist/tools/fs/shell-read-check.js +7 -1
  165. package/dist/tools/fs/shell-read-identity.d.ts +12 -9
  166. package/dist/tools/fs/shell-read-identity.js +9 -4
  167. package/dist/tools/web.js +9 -7
  168. package/package.json +1 -1
  169. package/test/export-surface.snapshot.json +79 -3
  170. package/wire-changes/core-stores-sql@7.57.0.json +29 -0
  171. package/wire-changes/core@7.57.0.json +1487 -0
  172. package/wire-changes/mods-kernel@7.57.0.json +29 -0
  173. package/wire-changes/primitives@7.57.0.json +245 -0
@@ -1,4 +1,4 @@
1
- import { isCredentialHeaderName } from "../core/secret-env.js";
1
+ import { isCredentialHeaderName, namesASecret } from "../core/secret-env.js";
2
2
  import { isPlainRecord } from "../core/mods/kernel/receive.js";
3
3
  import { BrainError } from "./errors.js";
4
4
  export const OPENAI_RESERVED = new Set([
@@ -129,6 +129,19 @@ export function mergeHeaders(...bags) {
129
129
  }
130
130
  return out;
131
131
  }
132
+ export function declaredRequestValues(bags, stated, key) {
133
+ const out = new Set();
134
+ for (const bag of bags)
135
+ for (const value of Object.values(bag ?? {}))
136
+ if (typeof value === "string" && value !== "")
137
+ out.add(value);
138
+ for (const value of stated)
139
+ if (value !== undefined && value !== "")
140
+ out.add(value);
141
+ if (key !== undefined && namesASecret(key))
142
+ out.add(key);
143
+ return [...out];
144
+ }
132
145
  export const OUTPUT_CAP_KEYS = {
133
146
  openai: ["max_tokens", "max_completion_tokens"],
134
147
  anthropic: ["max_tokens"],
@@ -109,6 +109,12 @@ export declare const WAITING_FIRST_TOKEN_EVERY_MS = 30000;
109
109
  export interface SSERequest {
110
110
  url: string;
111
111
  headers: Record<string, string>;
112
+ /**
113
+ * Every header value this request was DECLARED with — the model entry's, the deployment's and the call's header bags as the
114
+ * route kept them, and the key — as the adapter built them. An endpoint's words lose these before the engine carries them
115
+ * ({@link remoteWords}); the engine's own protocol words (`content-type`, the version and the beta list it chose) are not declared.
116
+ */
117
+ declared: readonly string[];
112
118
  body: string;
113
119
  /**
114
120
  * The per-request output cap this request ACTUALLY carries on the wire, when the adapter sets one
@@ -194,6 +200,9 @@ export interface StreamControls {
194
200
  * raising the provider's own in-band failure from `finalize`: the terminal message the engine mints for that
195
201
  * failure keeps it (the text the provider streamed, the usage it reported), rather than an empty shell. */
196
202
  keepOutput(output: KeptOutput): void;
203
+ /** The provider's own words in an in-band failure, as the engine carries them: this attempt's request ({@link SSERequest.declared}) taken
204
+ * out, then the credential vocabulary. A judge that reads the frame (the condition, the input-too-long anchor) reads the raw words. */
205
+ providerWords(words: string): string;
197
206
  }
198
207
  /** The output a parser hands over with {@link StreamControls.keepOutput}. */
199
208
  export interface KeptOutput {
@@ -9,7 +9,7 @@ import { FAST_MAX_BACKOFF_MS, providerWaitHint, retryBackoffMs } from "./retry.j
9
9
  import { bindBrainStatusEmitter, emitBrainStatus, emitBrainTelemetry, reportExtraBodyDropped } from "./status-sink.js";
10
10
  import { createAttemptController, firstByteWindow, refuseRetiredTimeoutKnobs, resolveFirstByteRetries, resolveWatchdogMs, STALL_FIRST_BYTE_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, } from "./timeout.js";
11
11
  import { readMember, thrownText } from "../core/thrown-value.js";
12
- import { egressText } from "../core/untrusted-egress.js";
12
+ import { remoteWords } from "../core/protocol-http-auth.js";
13
13
  const DEFAULT_MAX_RETRIES = 10;
14
14
  const MAX_RETRIES_ENV_CEILING = 15;
15
15
  const MAX_RETRIES_ENV = "SEMA_MAX_RETRIES";
@@ -294,6 +294,13 @@ export function runStreamingBrain(args) {
294
294
  let attemptPartial;
295
295
  let attemptOutput;
296
296
  let routeSeen;
297
+ let declaredSeen = [];
298
+ const echoCarried = (value, field) => {
299
+ if (remoteWords(value, declaredSeen) === value)
300
+ return value;
301
+ emitBrainTelemetry({ kind: "echo_dropped", field });
302
+ return undefined;
303
+ };
297
304
  void run()
298
305
  .catch((err) => {
299
306
  const aborted = signal?.aborted === true || isAbortError(err);
@@ -313,7 +320,8 @@ export function runStreamingBrain(args) {
313
320
  errorMsg.usage = kept.usage;
314
321
  }
315
322
  errorMsg.stopReason = aborted ? "aborted" : "error";
316
- errorMsg.errorMessage = thrownText(err);
323
+ const said = thrownText(err);
324
+ errorMsg.errorMessage = brainErrorCodeOf(err) !== undefined ? said : remoteWords(said, declaredSeen);
317
325
  const failure = aborted ? {} : thrownBrainFailure(err);
318
326
  if (failure.code !== undefined) {
319
327
  if (failure.errorKind !== undefined)
@@ -328,13 +336,17 @@ export function runStreamingBrain(args) {
328
336
  const statusNum = typeof status === "number" ? status : undefined;
329
337
  if (namesTheFailure(statusNum))
330
338
  errorMsg.apiErrorStatus = statusNum;
331
- if (requestIdSeen !== undefined)
332
- errorMsg.requestId = requestIdSeen;
339
+ const requestId = requestIdSeen === undefined ? undefined : echoCarried(requestIdSeen, "requestId");
340
+ if (requestId !== undefined)
341
+ errorMsg.requestId = requestId;
333
342
  if (routeSeen !== undefined)
334
343
  errorMsg.apiErrorRoute = routeSeen;
335
344
  const providerCode = readMember(err, "providerCode");
336
- if ((statusNum === undefined || refusedTheRequest(statusNum)) && typeof providerCode === "string" && isProviderEchoShape(providerCode))
337
- errorMsg.apiErrorCode = providerCode;
345
+ if ((statusNum === undefined || refusedTheRequest(statusNum)) && typeof providerCode === "string" && isProviderEchoShape(providerCode)) {
346
+ const apiErrorCode = echoCarried(providerCode, "apiErrorCode");
347
+ if (apiErrorCode !== undefined)
348
+ errorMsg.apiErrorCode = apiErrorCode;
349
+ }
338
350
  const condition = readMember(err, "providerCondition");
339
351
  const source = readMember(err, "providerConditionSource");
340
352
  if (isProviderCondition(condition) && isProviderConditionSource(source)) {
@@ -370,6 +382,7 @@ export function runStreamingBrain(args) {
370
382
  r.onCommitted?.();
371
383
  reportRequestStrip(model, r);
372
384
  routeSeen = admittedRequestRoute(r.route);
385
+ declaredSeen = r.declared;
373
386
  };
374
387
  commit(req);
375
388
  const dialect = resolveProviderDialect(req.url, model.api);
@@ -475,7 +488,7 @@ export function runStreamingBrain(args) {
475
488
  }
476
489
  else if (classifyFetchThrow(e) === "request_unbuildable") {
477
490
  cc.dispose();
478
- throw new BrainError("invalid_request", `${httpLabel} request could not be built: ${egressText("same-principal", stripUrlCredentials(thrownText(e)))}`);
491
+ throw new BrainError("invalid_request", `${httpLabel} request could not be built: ${remoteWords(stripUrlCredentials(thrownText(e)), req.declared)}`);
479
492
  }
480
493
  else {
481
494
  netErr = e;
@@ -634,14 +647,14 @@ export function runStreamingBrain(args) {
634
647
  continue;
635
648
  }
636
649
  if (netErr) {
637
- const chain = describeNetworkError(netErr);
650
+ const chain = remoteWords(describeNetworkError(netErr), req.declared);
638
651
  const proxyRefusal = connectReading?.proxyRefusal;
639
652
  throw new BrainError("network", proxyRefusal === undefined ? chain : `${proxyTunnelRefusedSentence(proxyRefusal.status)} — ${chain}`, undefined, { apiError: true });
640
653
  }
641
654
  if (r !== undefined && errorBody === undefined)
642
655
  errorBody = await readErrorBody(r, signal);
643
656
  const terminalVerdict = judge();
644
- const detail = errorBody?.text ?? "";
657
+ const detail = errorBody === undefined ? "" : remoteWords(errorBody.endedNormally ? errorBody.text : errorBody.text.replace(/\S+$/u, ""), req.declared);
645
658
  const status = r?.status ?? 0;
646
659
  const itl = terminalVerdict?.inputTooLong ?? "no";
647
660
  const providerCode = refusedTheRequest(status) ? readProviderErrorCode(errorBody) : undefined;
@@ -737,6 +750,7 @@ export function runStreamingBrain(args) {
737
750
  keepOutput: (output) => {
738
751
  attemptOutput = output;
739
752
  },
753
+ providerWords: (words) => remoteWords(words, req.declared),
740
754
  });
741
755
  const INBAND_OVERLOAD_WARNING = "API overloaded mid-response. The response above may be incomplete.";
742
756
  const onLineOverloadTierA = (line) => {
@@ -761,7 +775,7 @@ export function runStreamingBrain(args) {
761
775
  throw e;
762
776
  failure = {
763
777
  kind: "connection",
764
- err: new BrainError("network", `mid-stream read failed: ${describeNetworkError(e)}`, undefined, { apiError: true }),
778
+ err: new BrainError("network", `mid-stream read failed: ${remoteWords(describeNetworkError(e), req.declared)}`, undefined, { apiError: true }),
765
779
  };
766
780
  break readLoop;
767
781
  }
@@ -17,9 +17,10 @@ import { type AssistantMessage, type ModelOutputInvalid, type StreamDropCause }
17
17
  /** The warning (the reference's former words; later replaced by a resume request) stamped beside `partialFinalized` when a stream ended before its close — by the
18
18
  * mid-stream promotion (the body threw) and by the parsers' own finalize (content arrived, no finish frame). */
19
19
  export declare const STREAM_TORN_WARNING = "Connection closed mid-response. The response above may be incomplete.";
20
- /** A provider word as a stream-end seat carries it: the word in the provider echo shape, else `?` — the fact that a
21
- * word arrived survives, bytes outside the shape do not (the echo discipline: dropped, never rewritten). */
22
- export declare function echoFoldedWord(raw: string): string;
20
+ /** A provider word as a stream-end seat carries it: the word in the provider echo shape that the stream's mint
21
+ * (`providerWords`) leaves as it is, else `?` — the fact that a word arrived survives, bytes outside the shape or holding
22
+ * what the request was declared with do not (the echo discipline: dropped, never rewritten). */
23
+ export declare function echoFoldedWord(raw: string, providerWords: (words: string) => string): string;
23
24
  /**
24
25
  * The record of what arrived after a stream's first terminal statement — the one ordering rule both wire parsers
25
26
  * apply: the first ending (a finish / stop reason, a message stop, an in-band error) decides the turn; after it the
@@ -27,7 +28,7 @@ export declare function echoFoldedWord(raw: string): string;
27
28
  * here. Rides `AssistantMessage.streamEndIgnored` (the first refused frame's kind and word, how many came); the
28
29
  * Runner announces it (`route.stream_end_ignored`).
29
30
  */
30
- export declare function createStreamEndLedger(): {
31
+ export declare function createStreamEndLedger(providerWords: (words: string) => string): {
31
32
  note(statement: StreamEndStatement, word: string): void;
32
33
  readonly ignored: AssistantMessage["streamEndIgnored"];
33
34
  };
@@ -3,14 +3,14 @@ import { inspectDegenerate } from "./repetition.js";
3
3
  import { emitBrainTelemetry } from "./status-sink.js";
4
4
  import { stampStreamDrops } from "../internal/llm.js";
5
5
  export const STREAM_TORN_WARNING = "Connection closed mid-response. The response above may be incomplete.";
6
- export function echoFoldedWord(raw) {
7
- return isProviderEchoShape(raw) ? raw : "?";
6
+ export function echoFoldedWord(raw, providerWords) {
7
+ return isProviderEchoShape(raw) && providerWords(raw) === raw ? raw : "?";
8
8
  }
9
- export function createStreamEndLedger() {
9
+ export function createStreamEndLedger(providerWords) {
10
10
  let ignored;
11
11
  return {
12
12
  note(statement, word) {
13
- ignored = ignored === undefined ? { statement, word: echoFoldedWord(word), frames: 1 } : { ...ignored, frames: ignored.frames + 1 };
13
+ ignored = ignored === undefined ? { statement, word: echoFoldedWord(word, providerWords), frames: 1 } : { ...ignored, frames: ignored.frames + 1 };
14
14
  },
15
15
  get ignored() {
16
16
  return ignored;
@@ -73,15 +73,6 @@ export type CompiledAllowEntry = {
73
73
  readonly kind: "domain";
74
74
  readonly rule: PersistedRule;
75
75
  };
76
- /**
77
- * THE near-miss reading of a tool name the judgment calls current: the spelling the run mounts when the run mounts no tool of this
78
- * exact name but one that differs from it only by case (`BASH` beside `Bash`), else `undefined`. Two kinds of name are never a near
79
- * miss, because the roster they would be read against is not the final one: an engine tool's (the engine mounts some of its tools
80
- * after a list is read) and a protocol tool's (a refresh mounts and unmounts them while the run goes on) — in both the exact
81
- * spelling can be the right one by the time it matters. Every station that asks "is this name a case twin of a mounted tool" reads
82
- * this answer: an entry list ({@link readRuleEntry}), a tool policy's name lists (the prepare-time policy-name audit).
83
- */
84
- export declare function nearMissSpellingOf(name: string, roster: ReadonlySet<string>): string | undefined;
85
76
  /**
86
77
  * THE universe a ROOT-facing tool-name list is read against: every name the engine's catalog carries, and the names the reader knows
87
78
  * are carried (`carried`). A list is root-facing when it bounds the tools of the run that holds it — the run's own `allowTools` /
@@ -135,7 +126,9 @@ export declare const AUTO_STRIPPED_PROGRAMS: readonly ["python", "python3", "pyt
135
126
  * disclosed.
136
127
  */
137
128
  export declare function autoStripped(compiled: CompiledAllowEntry, call: ToolCallRequest | undefined): boolean;
138
- /** {@link autoStripped} over a person's stored ALLOW row (a deny / ask row is never stripped; a path row never is). */
129
+ /** {@link autoStripped} over a person's stored ALLOW row, read as its list twin ({@link entryOfRow}): a row is stripped exactly when
130
+ * the entry spelling the same rule is (a deny / ask row never is; a row no grammar reads has no twin and is not stripped here — the
131
+ * lane reads no allow it cannot parse). */
139
132
  export declare function autoStrippedRule(rule: PersistedRule, call: ToolCallRequest): boolean;
140
133
  /** One row a list matcher reads. */
141
134
  export interface AllowEntryRow {
@@ -2,11 +2,11 @@ import { heldBy } from "./tool-name-readings.js";
2
2
  import { ruleIssue } from "./permission-rules.js";
3
3
  import { isCoveringRuleName, ruleNameCovers } from "./namespaced-rule-name.js";
4
4
  import { ruleSpellingOf } from "./permission-rule-syntax.js";
5
- import { judgeRuleToolName, splitNamespacedName } from "./tool-name-aliases.js";
5
+ import { splitNamespacedName, toolNameFaultOf } from "./tool-name-aliases.js";
6
6
  import { catalogToolNames, isShellFaceToolName } from "./tool-registry.js";
7
7
  import { ALLOW_RULE_MEMBERS } from "./allow-rule-shadow.js";
8
8
  import { callHostTargetOf } from "./host-target.js";
9
- import { COMMAND_RULE_TOOL, adjudicatePersistedDomainRules, adjudicatePersistedNameRules, commandCoverageOf, laneToolNamesOf, ruleContentOf, noticeRuleText, parseRuleText, programRunReachOf, ruleToolGrammarOf, commandPatternMayRun } from "./permission-rule-model.js";
9
+ import { adjudicatePersistedDomainRules, wholeToolRuleReaches, adjudicatePersistedNameRules, commandCoverageOf, laneToolNamesOf, ruleContentOf, noticeRuleText, parseRuleText, programRunReachOf, ruleToolGrammarOf, commandPatternMayRun } from "./permission-rule-model.js";
10
10
  import { pathRuleLaneAnswer } from "./runner/permission-rule-lanes.js";
11
11
  import { callTargetReadingOf } from "./effective-path-target.js";
12
12
  import { splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
@@ -25,30 +25,12 @@ export function nonListValueLabel(read) {
25
25
  function ruleRowOf(parsed) {
26
26
  return { ...parsed, scope: { kind: "global" }, adds: [{ dot: { actor: "list", counter: 1 }, origin: "user", createdAt: "" }] };
27
27
  }
28
- function mountedSpellingOf(name, roster) {
29
- const folded = name.toLowerCase();
30
- for (const mounted of roster)
31
- if (mounted.toLowerCase() === folded)
32
- return mounted;
33
- return undefined;
34
- }
35
- export function nearMissSpellingOf(name, roster) {
36
- if (roster.has(name) || catalogToolNames().has(name) || splitNamespacedName(name) !== undefined)
37
- return undefined;
38
- return mountedSpellingOf(name, roster);
39
- }
40
28
  export function rootFacingToolNames(carried) {
41
29
  return new Set([...catalogToolNames(), ...carried]);
42
30
  }
43
31
  function toolNameReading(name, roster, behavior) {
44
- const verdict = judgeRuleToolName(name, roster, behavior);
45
- if (verdict.kind === "unprefixed_namespace")
46
- return { kind: "inert", reason: "tool_name", verdict: verdict.kind };
47
- if (verdict.kind !== "current") {
48
- return { kind: "inert", reason: "tool_name", verdict: verdict.kind, guidance: verdict.guidance, ...(verdict.currentName !== undefined ? { currentName: verdict.currentName } : {}) };
49
- }
50
- const mounted = nearMissSpellingOf(name, roster);
51
- return mounted === undefined ? undefined : { kind: "inert", reason: "near_miss", currentName: mounted };
32
+ const fault = toolNameFaultOf(name, roster, behavior);
33
+ return fault === undefined ? undefined : { kind: "inert", ...fault };
52
34
  }
53
35
  export function readRuleEntry(raw, behavior, roster) {
54
36
  const inert = (reason) => ({ kind: "inert", reason });
@@ -165,12 +147,22 @@ export function autoStripped(compiled, call) {
165
147
  }
166
148
  }
167
149
  export function autoStrippedRule(rule, call) {
168
- return rule.behavior === "allow" && rule.tool === COMMAND_RULE_TOOL && autoStripped({ kind: "command", rule }, call);
150
+ if (rule.behavior !== "allow")
151
+ return false;
152
+ const entry = entryOfRow(rule);
153
+ return entry !== undefined && autoStripped(entry, call);
154
+ }
155
+ function entryOfRow(rule) {
156
+ if (rule.match === "tool")
157
+ return isShellFaceToolName(rule.tool) ? { kind: "shell", name: rule.tool } : { kind: "tool", name: rule.tool };
158
+ const grammar = ruleToolGrammarOf(rule.tool);
159
+ return grammar === undefined ? undefined : { kind: grammar, rule };
169
160
  }
170
161
  const NO_LEADS = new Map();
171
162
  function nameRowReaches(compiled, req) {
172
- const covers = compiled.kind === "covering" ? (n) => ruleNameCovers(compiled.pattern, n) : compiled.kind === "tool" || compiled.kind === "shell" ? (n) => n === compiled.name : undefined;
173
- return covers !== undefined && answersTo(req, covers);
163
+ if (compiled.kind === "tool" || compiled.kind === "shell")
164
+ return wholeToolRuleReaches(compiled.name, req);
165
+ return compiled.kind === "covering" && answersTo(req, (n) => ruleNameCovers(compiled.pattern, n));
174
166
  }
175
167
  function commandOf(req) {
176
168
  const tool = laneToolNamesOf(req).find((n) => ruleToolGrammarOf(n) === "command");
@@ -1,6 +1,7 @@
1
1
  import type { PendingAction } from "./checkpoint-store.js";
2
2
  import type { CallTargetReading } from "./effective-path-target.js";
3
3
  import { type SkillGrantSource } from "./skill-grant.js";
4
+ import type { ReadLanding } from "../tools/fs/shell-read-identity.js";
4
5
  import type { ToolCallPathTarget, ToolFamily, ToolHostTarget } from "./tool-face.js";
5
6
  import { type ToolRosterEntry } from "./tool-roster.js";
6
7
  type ParkedApproval = Extract<PendingAction, {
@@ -16,6 +17,15 @@ export type ApprovalTargetRole = (typeof APPROVAL_TARGET_ROLES)[number];
16
17
  export type ApprovalTargets = {
17
18
  readonly [R in ApprovalTargetRole]?: string | null;
18
19
  };
20
+ /** The row's `targetReads` member: per read candidate the shell read station judged for the parked bytes (its key's
21
+ * digest), where it landed (its settled targets' digest — a pattern lands on a set). A sibling of `targets`, not a key
22
+ * inside it: a reader that knows only the path roles reads an unknown key there as damage and spends the approval, where
23
+ * a member of its own carries its own word and that reader refuses the row unread (`version_newer`). Present exactly when
24
+ * the station judged the parked bytes — `{}` when it found no read candidate: a candidate appearing by the resume is a
25
+ * read the card did not cover. */
26
+ export type ApprovalTargetReads = {
27
+ readonly [candidate: string]: string;
28
+ };
19
29
  /** The tool member as the row carries it: every member optional (a row is read, never trusted to be whole). */
20
30
  export interface ApprovalTool {
21
31
  readonly capabilityId?: string;
@@ -29,17 +39,21 @@ export interface ApprovalTool {
29
39
  export interface ApprovalObject {
30
40
  /** Absent on a row minted before the identity members (all four absent): such a row binds the tool's name alone. */
31
41
  readonly tool?: ApprovalTool;
32
- /** The row's member as stored — read, validated, by the judge. */
42
+ /** The row's two target members as stored — read, validated, by the judge. */
33
43
  readonly targets?: unknown;
44
+ readonly targetReads?: unknown;
34
45
  readonly grants?: string;
35
46
  }
36
47
  /** The view of one parked approval's object. */
37
48
  export declare function approvalObjectOf(row: ParkedApproval): ApprovalObject;
38
- /** The `targets` member of a reading: per role, the canonical view, or `null` for a view the environment was asked for
39
- * and did not answer; nothing when no role was read either way (or no reading was taken). The park mints it from the
40
- * gate's reading of the parked bytes; the execution station reads the bytes that will run the same way. */
41
- export declare function approvalTargetsOf(reading: CallTargetReading | undefined): {
49
+ /** The TARGETS of a reading — the one mint of both members, for the row and for the live call alike: `targets` per role,
50
+ * the canonical view, or `null` for a view the environment was asked for and did not answer (nothing when no role was read
51
+ * either way, or no reading was taken); `targetReads` per read candidate the shell read station judged, where it landed
52
+ * (nothing when the station judged no candidate of these bytes). The park mints them from the gate's readings of the parked
53
+ * bytes; the execution station reads the bytes that will run the same way. */
54
+ export declare function approvalTargetsOf(reading: CallTargetReading | undefined, reads?: readonly ReadLanding[]): {
42
55
  targets?: ApprovalTargets;
56
+ targetReads?: ApprovalTargetReads;
43
57
  };
44
58
  /** Where a resume judges the object: before the claim (the decided bytes), before the session is touched (the live
45
59
  * tool), right before the execution (the tool as mounted then — a host's re-dial can replace it between the two — and the
@@ -71,6 +85,8 @@ export interface LiveApprovalObject {
71
85
  readonly skillToolName?: string;
72
86
  /** execution: the reading of the bytes that will run, taken at this station (`undefined`: none could be taken). */
73
87
  readonly targetReading?: CallTargetReading | undefined;
88
+ /** execution: where this leg's shell read station found those bytes' reads land (`undefined`: it holds no judgment of them). */
89
+ readonly reads?: readonly ReadLanding[] | undefined;
74
90
  /** execution: the bytes the decision rewrote the call to, as the tool reads them; absent when it rewrote nothing. When
75
91
  * the row's binding does not cover them ({@link licenseCovers}, the one judge of "are these the approved bytes") the
76
92
  * person approved bytes whose target the row never recorded, and the targets member is not judged. */
@@ -5,22 +5,23 @@ import { toolIdentityOf } from "./tool-roster.js";
5
5
  import { inlineUntrusted } from "./untrusted-text.js";
6
6
  export const APPROVAL_TARGET_ROLES = Object.freeze(["protective", "declared"]);
7
7
  export function approvalObjectOf(row) {
8
- const { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget, hostTarget, targets, boundGrantDigest } = row;
8
+ const { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget, hostTarget, targets, targetReads, boundGrantDigest } = row;
9
9
  const carried = [capabilityId, contractId, shapeDigest, implementationRevision].some((m) => m !== undefined);
10
10
  return {
11
11
  ...(carried ? { tool: { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget, hostTarget } } : {}),
12
12
  ...(targets !== undefined ? { targets } : {}),
13
+ ...(targetReads !== undefined ? { targetReads } : {}),
13
14
  ...(boundGrantDigest !== undefined ? { grants: boundGrantDigest } : {}),
14
15
  };
15
16
  }
16
- export function approvalTargetsOf(reading) {
17
+ export function approvalTargetsOf(reading, reads) {
17
18
  const targets = {};
18
19
  for (const role of APPROVAL_TARGET_ROLES) {
19
20
  const view = reading?.[role];
20
21
  if (view?.kind === "path" && (view.canonical !== undefined || view.unread === true))
21
22
  targets[role] = view.canonical ?? null;
22
23
  }
23
- return Object.keys(targets).length > 0 ? { targets } : {};
24
+ return { ...(Object.keys(targets).length > 0 ? { targets } : {}), ...(reads !== undefined ? { targetReads: Object.fromEntries(reads.map((r) => [r.key, r.landing])) } : {}) };
24
25
  }
25
26
  export const APPROVAL_STATION_MEMBERS = Object.freeze({
26
27
  claim: Object.freeze(["grants"]),
@@ -52,22 +53,48 @@ function judgeTool(row, live) {
52
53
  return { member: "tool", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, "the tool's face (its display family, or the path target or URL slot it declares) changed after the approval") };
53
54
  }
54
55
  function judgeTargets(row, live) {
55
- const bound = row.targets;
56
- if (bound === undefined || ("decided" in live && !licenseCovers(row.boundInputHash, live.decided)))
56
+ if ((row.targets === undefined && row.targetReads === undefined) || ("decided" in live && !licenseCovers(row.boundInputHash, live.decided)))
57
57
  return undefined;
58
- const roles = typeof bound === "object" && bound !== null && !Array.isArray(bound) ? Object.entries(bound) : [];
59
- const whole = roles.length > 0 && roles.every(([k, v]) => APPROVAL_TARGET_ROLES.includes(k) && (v === null || (typeof v === "string" && v.length > 0)));
60
- if (!whole)
61
- return { member: "targets", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, "the target member of the approval on its checkpoint row is damaged (not a reading → resolved location record)") };
62
- const now = approvalTargetsOf(live.targetReading).targets ?? {};
63
- const where = (p) => (p === null ? "did not resolve" : `resolved to ${shown(p)}`);
64
- for (const [role, was] of roles) {
65
- const is = now[role];
66
- if (is === was)
67
- continue;
68
- return { member: "targets", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, is === undefined ? `where its target resolves (it ${where(was)} when the card was asked) could not be established on this leg` : `its target ${is === null ? "does not resolve" : `resolves to ${shown(is)}`} now and ${where(was)} when the card was asked`) };
58
+ const why = landedElsewhere(row.targets, row.targetReads, live);
59
+ return why === undefined ? undefined : { member: "targets", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, why) };
60
+ }
61
+ const DIGEST = /^[0-9a-f]{64}$/;
62
+ function landedElsewhere(bound, boundReads, live) {
63
+ if (bound !== undefined) {
64
+ const roles = typeof bound === "object" && bound !== null && !Array.isArray(bound) ? Object.entries(bound) : [];
65
+ const whole = roles.length > 0 && roles.every(([k, v]) => APPROVAL_TARGET_ROLES.includes(k) && (v === null || (typeof v === "string" && v.length > 0)));
66
+ if (!whole)
67
+ return "the target member of the approval on its checkpoint row is damaged (not a reading → resolved location record)";
68
+ const now = approvalTargetsOf(live.targetReading).targets ?? {};
69
+ const where = (p) => (p === null ? "did not resolve" : `resolved to ${shown(p)}`);
70
+ for (const [role, was] of roles) {
71
+ const is = now[role];
72
+ if (is === was)
73
+ continue;
74
+ return is === undefined ? `where its target resolves (it ${where(was)} when the card was asked) could not be established on this leg` : `its target ${is === null ? "does not resolve" : `resolves to ${shown(is)}`} now and ${where(was)} when the card was asked`;
75
+ }
69
76
  }
70
- return undefined;
77
+ if (boundReads === undefined)
78
+ return undefined;
79
+ const record = typeof boundReads === "object" && boundReads !== null && !Array.isArray(boundReads);
80
+ const reads = record ? Object.entries(boundReads) : [];
81
+ if (!record || !reads.every(([k, v]) => DIGEST.test(k) && typeof v === "string" && DIGEST.test(v)))
82
+ return "the read member of the approval on its checkpoint row is damaged (not a read → landing record)";
83
+ if (live.reads === undefined)
84
+ return `where its reads land (the read station judged ${reads.length} when the card was asked) could not be established on this leg`;
85
+ const was = new Map(reads);
86
+ const now = new Map(Object.entries(approvalTargetsOf(undefined, live.reads).targetReads ?? {}));
87
+ const named = (key) => live.reads?.find((r) => r.key === key);
88
+ const added = [...now.keys()].find((k) => !was.has(k));
89
+ if (added !== undefined || [...was.keys()].some((k) => !now.has(k)))
90
+ return `this leg reads the command differently than when the card was asked (${added !== undefined ? `${shown(named(added)?.raw ?? "?")} is read now at a place or in a form the card did not cover` : "a read the card covered is not read now"})`;
91
+ const movedKey = [...now.keys()].find((k) => now.get(k) !== was.get(k));
92
+ const moved = movedKey === undefined ? undefined : named(movedKey);
93
+ if (movedKey === undefined)
94
+ return undefined;
95
+ const targets = moved?.targets ?? [];
96
+ const at = targets.length === 0 ? "cannot be placed" : `lands on ${shown(targets[0])}${targets.length > 1 ? ` (and ${targets.length - 1} more)` : ""}`;
97
+ return `its read ${shown(moved?.raw ?? "?")} ${at} now, not where it landed when the card was asked`;
71
98
  }
72
99
  function judgeGrants(row, live, station) {
73
100
  const refusal = parkedSkillApprovalRefusal(row, live.args, live.skills ?? [], live.skillToolName ?? "", station);
@@ -203,8 +203,8 @@ export type RuleOffersAbsence = (typeof RULE_OFFERS_ABSENCES)[number];
203
203
  * class row's `ruleMayClear` ({@link classifyAsk} — every door below is a class there), read once by the gate's allow layer.
204
204
  * · `word` — the DISCLOSED reason ({@link RuleOffersAbsence}), first door wins, in the display order the card has always
205
205
  * used (governance, the outranked allow, the hook, the ask rule, the two ancestor doors, then the marks). Display
206
- * only. A structural door the offer factory owns (no lane wired, a tool it does not speak for, a
207
- * task that cannot hold a rule) is decided BEFORE this and is deliberately silent;
206
+ * only. The structural door the offer factory owns (no rule speaks for the call: no lane wired, a
207
+ * task that cannot hold a rule, a call the lane does not judge) is decided BEFORE this and is deliberately silent;
208
208
  * · `mandated` — the FACT the ask carries as its own member. Read off the whole mandate set, so which
209
209
  * door is DISPLAYED cannot change it: an ask that is both outranked and mandated is mandated;
210
210
  * · `mandate` — WHICH table word the bit stands on, only when the MARKS family is its whole reason (no
@@ -3,8 +3,8 @@ import { type Message, type Model, type Tool } from "../internal/llm.js";
3
3
  import type { ContextWindowOverlay } from "./context-settings.js";
4
4
  import type { PostCompactContext, PreCompactContext, PreCompactResult } from "./hooks.js";
5
5
  import type { TraceEvent } from "./trace.js";
6
- import type { Brain } from "./types.js";
7
- import type { TaskEvent, TaskEventIdentity } from "./task-event.js";
6
+ import type { Brain, ModelRef, TaskSpec } from "./types.js";
7
+ import type { CompactOutcome, TaskEvent, TaskEventIdentity } from "./task-event.js";
8
8
  import type { CompactionEffect } from "./frame-settlement.js";
9
9
  import type { MintedAttachmentFrame } from "./user-attachment.js";
10
10
  /**
@@ -39,13 +39,18 @@ export type CompactionPhaseDurations = Omit<Extract<TraceEvent, {
39
39
  kind: "compaction.phase_timings";
40
40
  }>, "kind" | "version" | "taskId" | "ts" | "durationMs">;
41
41
  /** True iff `err` is a compaction failure caused by a manual compact() caller WITHDRAWING its
42
- * request — see {@link MaybeCompactOptions.manualCancelSignal}. Two mint sites since the
43
- * design/374 slice-3 commit-boundary re-check (C16): the summary call's own abort outcome, and
44
- * the pre-persist "cancelled before commit" throw (a brain that ignored the abort finished the
45
- * summary, but the withdrawal still fired before anything landed). Absence of the marker means
42
+ * request — see {@link MaybeCompactOptions.manualCancelSignal}. Minted at every cut point of the
43
+ * pass: before the pass commits, while the summary provider or the credential callback is
44
+ * answering ({@link untilPassCut}), the summary call's own abort outcome, and the pre-persist
45
+ * "cancelled before commit" throw (design/374 slice-3, C16: a brain that ignored the abort finished
46
+ * the summary, but the withdrawal still fired before anything landed). Absence of the marker means
46
47
  * the failure was NOT the withdrawal: a real summarizer error racing a late cancel stays
47
48
  * unmarked so the caller's breaker/onError accounting still sees it. */
48
49
  export declare function isCompactionManualCancel(err: unknown): boolean;
50
+ /** The ONE reason a failed compaction pass reports, read by every lane that accounts for one (the turn boundary, the end-of-task pass,
51
+ * the prompt-too-long recovery) for its trace frame and its `compaction_outcome{failed}` event: the thrown value's words through the
52
+ * same-principal tier, THEN bounded — minted before the cut, so the cut can leave no part of a value. */
53
+ export declare function compactionFailureReason(err: unknown): string;
49
54
  /**
50
55
  * design/145 §1/§3 — the window-safety decision surfaced to the caller BEFORE the summary call
51
56
  * (same pre-call posture as `onInputTruncated`: the disclosure must survive a later summary
@@ -734,6 +739,66 @@ export interface CompactedFrameSource {
734
739
  export declare function mintCompactedEvent(comp: CompactedFrameSource, trigger: "auto" | "manual" | "forced", identity: TaskEventIdentity): Extract<TaskEvent, {
735
740
  type: "compacted";
736
741
  }>;
742
+ /** `Runner.compactSession`'s request — the out-of-run manual compaction of a session no leg of the Runner holds. A plain
743
+ * data object; the keys are a closed set. */
744
+ export interface CompactSessionRequest {
745
+ /** The session to compact. It must exist. */
746
+ sessionId: string;
747
+ /** The model the session's next turn runs on — its window bounds the pass (resolved like `TaskSpec.model`). */
748
+ model: ModelRef;
749
+ /** This pass's summarization instructions: they REPLACE `compaction.instructions` and the default for this pass (the
750
+ * `TaskStream.compact` reading); sanitized and capped like every instructions channel. */
751
+ instructions?: string;
752
+ /** The members of `TaskSpec.compaction` a pass reads, as a run would carry them. */
753
+ compaction?: Pick<NonNullable<TaskSpec["compaction"]>, "enabled" | "reserveTokens" | "keepRecentTokens" | "instructions" | "clampTolerance">;
754
+ /** The independent summary model (`TaskSpec.compactionModel`); absent ⇒ the deployment's `summarize` role (or its tier
755
+ * default), else the main model. A role-derived model whose key↔URL pairing is broken falls back to the main model,
756
+ * announced, as on a run. */
757
+ compactionModel?: ModelRef;
758
+ /** Per-model credentials (`TaskSpec.getApiKeyAndHeaders`). */
759
+ getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
760
+ /** Withdraws the pass: aborted before the session's lock is taken, the answer is `"mooted"` with nothing done; while the pass
761
+ * runs the summary call and the credential wait read it (store reads and appends, and the pre-flight's own 5 s timer, do not); a pass that already persisted is not undone. */
762
+ signal?: AbortSignal;
763
+ }
764
+ /** `Runner.compactSession`'s answer. */
765
+ export interface CompactSessionResult {
766
+ /** What the pass did — the closed set `TaskStream.compact` resolves with. */
767
+ outcome: CompactOutcome;
768
+ /**
769
+ * The pass's frames, in order, each with its own `eventId` and no task attribution: `compaction_started` once the
770
+ * pass committed, then the frame that ends it — `compacted` (`trigger: "manual"`) on a landing, else
771
+ * `compaction_outcome` (`trigger: "manual"`). Empty when the pass never ran (a withdrawn request). Returned, not
772
+ * streamed: a `compaction_started` here is a record of the pass, not a live progress signal.
773
+ */
774
+ events: Array<Extract<TaskEvent, {
775
+ type: "compaction_started" | "compacted" | "compaction_outcome";
776
+ }>>;
777
+ /** The model calls the pass made (the summary, a hook's `$.model.complete`), priced as a run's side calls are; absent
778
+ * when none was made. `usageMissing` ⇒ a reply reported no usage (a lower bound); `unpriced` ⇒ a call ran on a model
779
+ * the deployment could not price. */
780
+ usage?: {
781
+ tokens: number;
782
+ totalInputTokens: number;
783
+ promptTokens: number;
784
+ cachedTokens: number;
785
+ cacheWriteTokens: number;
786
+ outputTokens: number;
787
+ costMicroUsd: number;
788
+ usageMissing?: true;
789
+ unpriced?: true;
790
+ };
791
+ }
792
+ /**
793
+ * The MANUAL outcome of a pass that returned (did not throw) — the one reading for every manual pass: the turn
794
+ * boundary serving a `TaskStream.compact` request and the out-of-run `Runner.compactSession`. A manual pass forces
795
+ * (`force` + `minTokens: 0`), so the floor's `suppressedByFloor` cannot occur and is not a case here.
796
+ */
797
+ export declare function manualCompactOutcomeOf(comp: {
798
+ compacted: boolean;
799
+ blocked?: boolean;
800
+ disabled?: boolean;
801
+ }): CompactOutcome;
737
802
  /**
738
803
  * THE mint of the `compaction_started` wire frame — the opening half of the pair whose closing half is the
739
804
  * `compacted` frame above (or a `compaction_outcome` that carries the same `pass`). One function, three