@sema-agent/core 7.37.0 → 7.38.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 (136) hide show
  1. package/CHANGELOG.md +170 -0
  2. package/dist/agents/observer.js +1 -1
  3. package/dist/agents/peer-notices.js +4 -4
  4. package/dist/agents/subagent.js +1 -1
  5. package/dist/agents/team-role.js +2 -2
  6. package/dist/brain/anthropic.js +13 -19
  7. package/dist/brain/errors.d.ts +26 -0
  8. package/dist/brain/errors.js +34 -0
  9. package/dist/brain/input-too-long.d.ts +0 -31
  10. package/dist/brain/input-too-long.js +1 -18
  11. package/dist/brain/open-responses.js +12 -18
  12. package/dist/brain/openai.js +13 -19
  13. package/dist/brain/status-sink.d.ts +1 -1
  14. package/dist/brain/stream-engine.js +10 -2
  15. package/dist/brain/stream-shared.d.ts +29 -0
  16. package/dist/brain/stream-shared.js +15 -0
  17. package/dist/brain/tool-call-id.d.ts +29 -8
  18. package/dist/brain/tool-call-id.js +36 -6
  19. package/dist/core/a2a-task-state.js +2 -2
  20. package/dist/core/artifact-host.js +6 -6
  21. package/dist/core/ask-origin.js +6 -6
  22. package/dist/core/ask-unresolvable-notice.js +1 -1
  23. package/dist/core/auto-mode.js +4 -4
  24. package/dist/core/background-agent-store.js +2 -2
  25. package/dist/core/checkpoint-execution-record.js +1 -1
  26. package/dist/core/checkpoint-store.d.ts +2 -2
  27. package/dist/core/checkpoint-store.js +2 -2
  28. package/dist/core/child-clamp-seats.js +3 -3
  29. package/dist/core/compliance.js +1 -1
  30. package/dist/core/context-edit.js +1 -1
  31. package/dist/core/context-guard.d.ts +0 -19
  32. package/dist/core/context-guard.js +0 -38
  33. package/dist/core/decision-receipt.d.ts +27 -1
  34. package/dist/core/decision-receipt.js +176 -1
  35. package/dist/core/engine-notice.d.ts +67 -7
  36. package/dist/core/engine-notice.js +2 -1
  37. package/dist/core/gate-fold.js +3 -2
  38. package/dist/core/gate-outcome.js +4 -4
  39. package/dist/core/governance-codes.d.ts +221 -13
  40. package/dist/core/governance-codes.js +106 -4
  41. package/dist/core/hook-seats.js +2 -2
  42. package/dist/core/hooks.d.ts +21 -2
  43. package/dist/core/hooks.js +4 -1
  44. package/dist/core/mailbox-store.js +3 -3
  45. package/dist/core/mcp-failure.js +4 -4
  46. package/dist/core/mcp-injection-drop.js +1 -1
  47. package/dist/core/memory-engine/consolidation.js +1 -1
  48. package/dist/core/memory-engine/delegation-settlement.d.ts +8 -0
  49. package/dist/core/memory-engine/delegation-settlement.js +7 -0
  50. package/dist/core/memory-engine/engine.d.ts +12 -2
  51. package/dist/core/memory-engine/engine.js +10 -6
  52. package/dist/core/memory-engine/export-bundle.js +1 -1
  53. package/dist/core/memory-engine/layout.d.ts +6 -6
  54. package/dist/core/memory-engine/layout.js +6 -8
  55. package/dist/core/memory-engine/session-write-home.d.ts +30 -0
  56. package/dist/core/memory-engine/session-write-home.js +34 -0
  57. package/dist/core/memory-engine/tools.js +1 -1
  58. package/dist/core/memory-engine/types.js +1 -1
  59. package/dist/core/memory.js +1 -1
  60. package/dist/core/mode-policy.d.ts +10 -1
  61. package/dist/core/mode-policy.js +15 -7
  62. package/dist/core/model-seat.js +1 -1
  63. package/dist/core/permission-rule-model.js +3 -3
  64. package/dist/core/permission-rule-store.js +2 -2
  65. package/dist/core/read-only-shell-table.js +8 -8
  66. package/dist/core/retention.js +2 -2
  67. package/dist/core/runner/assemble-result.js +15 -5
  68. package/dist/core/runner/classifier-ladder.js +1 -1
  69. package/dist/core/runner/denial-limit-arms.d.ts +2 -0
  70. package/dist/core/runner/denial-limit-arms.js +6 -2
  71. package/dist/core/runner/inherited-ask-grants.d.ts +6 -5
  72. package/dist/core/runner/inherited-ask-grants.js +3 -2
  73. package/dist/core/runner/prepare-context-lane.d.ts +19 -15
  74. package/dist/core/runner/prepare-context-lane.js +11 -15
  75. package/dist/core/runner/prepare-gate-stations.js +5 -5
  76. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  77. package/dist/core/runner/prepare-policy-chain.js +10 -10
  78. package/dist/core/runner/run-harness-handlers.js +3 -0
  79. package/dist/core/runner/run-leg.js +2 -2
  80. package/dist/core/runner/run-stop-and-final-verify.js +1 -0
  81. package/dist/core/runner/run-turn-boundary.js +1 -1
  82. package/dist/core/runner/session-mode-seats.d.ts +11 -3
  83. package/dist/core/runner/session-mode-seats.js +6 -0
  84. package/dist/core/runner/session-rule-policy.js +2 -3
  85. package/dist/core/runner/stream-steer-verb.js +1 -1
  86. package/dist/core/runner/tool-call-id-fold-seat.js +16 -9
  87. package/dist/core/runner/tool-turn-repair-seat.d.ts +18 -0
  88. package/dist/core/runner/tool-turn-repair-seat.js +63 -0
  89. package/dist/core/runner/turn-attachments.js +1 -1
  90. package/dist/core/runtime-caps.js +1 -1
  91. package/dist/core/safety-axis-vocab.d.ts +5 -0
  92. package/dist/core/safety-axis-vocab.js +5 -0
  93. package/dist/core/session-reconcile.d.ts +44 -7
  94. package/dist/core/session-reconcile.js +51 -20
  95. package/dist/core/shared-memory/types.js +1 -1
  96. package/dist/core/side-query.d.ts +6 -1
  97. package/dist/core/side-query.js +4 -2
  98. package/dist/core/store-contracts/read-back-shape-contract.js +1 -1
  99. package/dist/core/swappable-deps.js +1 -1
  100. package/dist/core/task-notification.js +4 -4
  101. package/dist/core/task-registry-shared.js +1 -1
  102. package/dist/core/terminal-cause.d.ts +19 -0
  103. package/dist/core/tool-face.js +6 -6
  104. package/dist/core/tool-policy.d.ts +17 -11
  105. package/dist/core/tool-policy.js +38 -72
  106. package/dist/core/tool-roster.js +1 -1
  107. package/dist/core/tool-search-setting.js +1 -1
  108. package/dist/core/trace.d.ts +5 -4
  109. package/dist/core/types.d.ts +2 -2
  110. package/dist/core/types.js +1 -1
  111. package/dist/core/wiring-manifest.js +2 -2
  112. package/dist/core/write-protect.js +1 -1
  113. package/dist/engine/harness/agent-harness.d.ts +7 -0
  114. package/dist/engine/harness/agent-harness.js +24 -8
  115. package/dist/engine/llm/index.d.ts +1 -0
  116. package/dist/engine/llm/index.js +1 -0
  117. package/dist/engine/llm/tool-turns.d.ts +109 -0
  118. package/dist/engine/llm/tool-turns.js +145 -0
  119. package/dist/engine/llm/types.d.ts +11 -0
  120. package/dist/index.d.ts +5 -3
  121. package/dist/index.js +4 -2
  122. package/dist/internal/llm.d.ts +2 -2
  123. package/dist/internal/llm.js +1 -1
  124. package/dist/orchestration/workflow-governance.js +2 -2
  125. package/dist/orchestration/workflow-types.js +2 -2
  126. package/dist/shell/lex.js +7 -7
  127. package/dist/tools/fs/bash-program-position.js +2 -2
  128. package/dist/tools/fs/read-deny.js +2 -2
  129. package/dist/tools/fs/safety.js +1 -1
  130. package/dist/tools/fs/shell-read-judge.js +2 -2
  131. package/dist/tools/fs/shell-read-plan.js +2 -2
  132. package/dist/tools/fs/shell-read-write-targets.js +1 -1
  133. package/package.json +1 -1
  134. package/test/export-surface.snapshot.json +17 -1
  135. package/wire-changes/core@7.37.1.json +122 -0
  136. package/wire-changes/core@7.38.0.json +664 -0
@@ -11,7 +11,7 @@ const Name = Type.String({ minLength: 1, maxLength: TOOL_WIRE_NAME_MAX_CHARS });
11
11
  const Short = Type.String({ maxLength: TOOL_CONTRACT_MAX_CHARS });
12
12
  const Key = Type.String({ maxLength: TOOL_KEY_MAX_CHARS });
13
13
  const Digest16 = Type.String({ minLength: 16, maxLength: 16 });
14
- export const ROSTER_SOURCES = ["builtin", "caller", "host", "mcp", "a2a", "external", "synthetic"];
14
+ export const ROSTER_SOURCES = Object.freeze(["builtin", "caller", "host", "mcp", "a2a", "external", "synthetic"]);
15
15
  export const ToolRosterRenderHints = Type.Object({
16
16
  userFacingName: Type.Optional(Type.String({ maxLength: RENDER_HINT_MAX_CHARS })),
17
17
  activity: Type.Optional(Type.String({ maxLength: RENDER_HINT_ACTIVITY_MAX_CHARS })),
@@ -1,6 +1,6 @@
1
1
  export const TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT = 10;
2
2
  export const TOOL_SEARCH_INVALID_CODE = "config.tool_search_invalid";
3
- export const TOOL_SEARCH_POLICIES = ["always", "never", "auto"];
3
+ export const TOOL_SEARCH_POLICIES = Object.freeze(["always", "never", "auto"]);
4
4
  export function resolveToolSearch(setting) {
5
5
  if (setting === undefined)
6
6
  return { policy: "auto", thresholdPercent: TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT, fraction: TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT / 100 };
@@ -860,13 +860,14 @@ export type TraceEvent = {
860
860
  } | {
861
861
  /** A provider echo field was refused by {@link import("../brain/errors.js").PROVIDER_ECHO_SHAPE} (visible ASCII,
862
862
  * 1–128 characters) and dropped: the response's `request-id` / `x-request-id` header values (`field:
863
- * "requestId"`, read at connect — the id a failure would carry as `apiFailure.requestId`), or the model name
864
- * its frames report (`field: "responseModel"`). One frame per provider response per field; the value itself
865
- * is never carried (metadata-only). A count of these frames is this engine's drop count. */
863
+ * "requestId"`, read at connect — the id a failure would carry as `apiFailure.requestId`), the model name
864
+ * its frames report (`field: "responseModel"`), or the word a refusal's error envelope states (`field:
865
+ * "apiErrorCode"`). One frame per provider response per field; the value itself is never carried
866
+ * (metadata-only). A count of these frames is this engine's drop count. */
866
867
  kind: "brain.echo_dropped";
867
868
  version: 1;
868
869
  taskId: string;
869
- field: "requestId" | "responseModel";
870
+ field: "requestId" | "responseModel" | "apiErrorCode";
870
871
  ts: number;
871
872
  } | {
872
873
  /** C10 — a `preemptSignal` fired on a task that is NOT resource-suspend-eligible: the preempt is a
@@ -29,6 +29,6 @@ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js"
29
29
  export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
30
30
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
31
31
  export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
32
- export type { EngineNotice, OwedUserInputAccount } from "./engine-notice.js";
33
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices } from "./engine-notice.js";
32
+ export type { EngineNotice, OwedUserInputAccount, UserSteerUndrainedCause } from "./engine-notice.js";
33
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
34
34
  export type { AgentTool, Model, StreamFn, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict };
@@ -1,4 +1,4 @@
1
1
  export { MODEL_ROLES } from "./model-seat.js";
2
2
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
3
3
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
4
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices } from "./engine-notice.js";
4
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
@@ -5,8 +5,8 @@ import { canonicalize } from "./canonical-json.js";
5
5
  import { resolveCheckpointStore } from "./checkpoint-store.js";
6
6
  import { isLiveQuestionFace } from "./ask-question.js";
7
7
  import { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
8
- export const AUTO_MODE_ARM_REASONS = ["armed", "no_intent", "no_face", "denied", "resolver_fault"];
9
- export const LSP_LANES = ["tool", "diagnostics", "tool_and_diagnostics"];
8
+ export const AUTO_MODE_ARM_REASONS = Object.freeze(["armed", "no_intent", "no_face", "denied", "resolver_fault"]);
9
+ export const LSP_LANES = Object.freeze(["tool", "diagnostics", "tool_and_diagnostics"]);
10
10
  export function deriveHandsSeam(handsMounted) {
11
11
  return handsMounted ? { mounted: true } : { mounted: false, reason: "no_execution_env" };
12
12
  }
@@ -183,7 +183,7 @@ function relativeUnderDataRoot(path, roots) {
183
183
  }
184
184
  return null;
185
185
  }
186
- export const WRITE_PROTECTION_TARGET_VIEWS = ["spelling-only", "target"];
186
+ export const WRITE_PROTECTION_TARGET_VIEWS = Object.freeze(["spelling-only", "target"]);
187
187
  export function compileWriteProtection(entries, opts) {
188
188
  const rows = resolveWriteProtectedTable(entries);
189
189
  if (rows.length === 0)
@@ -1,4 +1,5 @@
1
1
  import type { ActorAssertion, AssistantMessage, ImageContent, Model, UserMessage } from "../llm/index.js";
2
+ import { type ToolTurnRepair } from "../llm/index.js";
2
3
  import type { HaltRemainingDirective } from "../loop/types.js";
3
4
  import type { AgentMessage, AgentTool, LoopMalformedToolUseRecovery, LoopThinkingOnlyRecovery, LoopTruncatedOutputRecovery, QueueMode, ThinkingLevel } from "../loop/types.js";
4
5
  import { type EngineSegment } from "../../core/untrusted-text.js";
@@ -189,6 +190,10 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
189
190
  private thinkingLevel;
190
191
  /** RB-30 terminal fix — runner-set sink for engine-note payloads left undrained at agent_end. */
191
192
  onUndrainedEngineNotes?: (payloads: unknown[]) => void;
193
+ /** Runner-set sink for the provider boundary's tool-turn repairs ({@link ensureToolTurnInvariants}) — called
194
+ * with every request view that needed one (the transcript is never rewritten, so a broken turn is repaired, and
195
+ * reported, on each request; the sink owns its dedup). Advisory: a throwing sink never breaks the request. */
196
+ onToolTurnRepaired?: (repairs: readonly ToolTurnRepair[]) => void;
192
197
  /** backlog #259 — USER-authored queue remnants at agent_end: steers/follow-ups whose receipts said
193
198
  * "queued" but that no turn will ever drain (the run ended first). The engine-note sweep above
194
199
  * hands ENGINE payloads back for redelivery; user inputs have no redelivery semantics (a steer
@@ -226,6 +231,8 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
226
231
  * gated: its splice opens a NEW run (idle-park redelivery), and a halt belongs to the run that
227
232
  * minted it. */
228
233
  engineInjectionsHeld?: () => boolean;
234
+ /** The provider boundary's tool-turn law: the repaired view goes to the brain, the repairs to the runner's sink. */
235
+ private holdToolTurnLaw;
229
236
  /** RB-30 R2 belt-and-braces: runner-callable idempotent recovery for exit paths that reach
230
237
  * neither agent_end nor abort() (a pre-prompt throw after prepare). Swept entries are removed,
231
238
  * so a double call is a no-op. */
@@ -1,11 +1,11 @@
1
- import { createAssistantMessageEventStream, snapshotActorAssertion, stripEngineMetadata } from "../llm/index.js";
1
+ import { createAssistantMessageEventStream, ensureToolTurnInvariants, snapshotActorAssertion, stripEngineMetadata } from "../llm/index.js";
2
2
  import { runAgentLoop } from "../loop/agent-loop.js";
3
3
  import { resolveAgentCoreStreamFn } from "../loop/runtime-deps.js";
4
4
  import { normalizeEngineSegments } from "../../core/untrusted-text.js";
5
5
  import { AUTH_CARRIER_NAMES } from "../../brain/request-params.js";
6
6
  import { isProviderEchoShape } from "../../brain/errors.js";
7
7
  import { convertToLlm } from "./messages.js";
8
- import { thrownText } from "../../core/thrown-value.js";
8
+ import { readMember, thrownText } from "../../core/thrown-value.js";
9
9
  import { AgentHarnessError, CompactionError, SessionError, SYNTHETIC_MESSAGE_MODEL, toError, } from "./types.js";
10
10
  const API_FAILURE_RECORD_MAX_CHARS = 2000;
11
11
  function createUserMessage(text, images, provenance) {
@@ -211,10 +211,22 @@ export class AgentHarness {
211
211
  modelRef;
212
212
  thinkingLevel;
213
213
  onUndrainedEngineNotes;
214
+ onToolTurnRepaired;
214
215
  onUndrainedUserInputs;
215
216
  onEngineNoteConsumed;
216
217
  onFrameCommitted;
217
218
  engineInjectionsHeld;
219
+ holdToolTurnLaw(messages) {
220
+ const view = ensureToolTurnInvariants(messages);
221
+ if (view.repairs.length > 0) {
222
+ try {
223
+ this.onToolTurnRepaired?.(view.repairs);
224
+ }
225
+ catch {
226
+ }
227
+ }
228
+ return view.messages;
229
+ }
218
230
  recoverUndrainedEngineNotes() {
219
231
  this.sweepUndrainedEngineNotes([this.drainedPendingInjection, this.nextTurnQueue, this.steerQueue, this.followUpQueue]);
220
232
  }
@@ -620,7 +632,7 @@ export class AgentHarness {
620
632
  ...(this.streamingToolExecution === true && (this.getHandlers("tool_call")?.size ?? 0) === 0
621
633
  ? { streamingToolExecution: true }
622
634
  : {}),
623
- convertToLlm: (messages) => stripEngineMetadata(convertToLlm(messages)),
635
+ convertToLlm: (messages) => this.holdToolTurnLaw(stripEngineMetadata(convertToLlm(messages))),
624
636
  shouldStopAfterTurn: () => this.consultStopLatch("post_turn"),
625
637
  shouldHaltBeforeTurn: () => this.consultStopLatch("pre_turn"),
626
638
  transformContext: async (messages, signal) => {
@@ -772,7 +784,11 @@ export class AgentHarness {
772
784
  }
773
785
  }
774
786
  async recordApiFailure(failure) {
775
- const raw = failure.errorMessage?.trim();
787
+ const errorMessage = readMember(failure, "errorMessage");
788
+ const apiErrorStatus = readMember(failure, "apiErrorStatus");
789
+ const requestId = readMember(failure, "requestId");
790
+ const errorKind = readMember(failure, "errorKind");
791
+ const raw = typeof errorMessage === "string" ? errorMessage.trim() : undefined;
776
792
  const detail = raw !== undefined && raw.length > API_FAILURE_RECORD_MAX_CHARS
777
793
  ? `${raw.slice(0, API_FAILURE_RECORD_MAX_CHARS)}… (truncated)`
778
794
  : raw;
@@ -783,13 +799,13 @@ export class AgentHarness {
783
799
  provider: failure.provider,
784
800
  model: SYNTHETIC_MESSAGE_MODEL,
785
801
  isApiErrorMessage: true,
786
- ...(failure.apiErrorStatus !== undefined ? { apiErrorStatus: failure.apiErrorStatus } : {}),
787
- ...(typeof failure.requestId === "string" && isProviderEchoShape(failure.requestId) ? { requestId: failure.requestId } : {}),
802
+ ...(typeof apiErrorStatus === "number" ? { apiErrorStatus } : {}),
803
+ ...(typeof requestId === "string" && isProviderEchoShape(requestId) ? { requestId } : {}),
788
804
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
789
805
  usageMissing: true,
790
806
  stopReason: "error",
791
- ...(failure.errorMessage !== undefined ? { errorMessage: failure.errorMessage } : {}),
792
- ...(failure.errorKind !== undefined ? { errorKind: failure.errorKind } : {}),
807
+ ...(typeof errorMessage === "string" ? { errorMessage } : {}),
808
+ ...(errorKind === "length_empty" || errorKind === "degenerate" || errorKind === "input_too_long" ? { errorKind } : {}),
793
809
  timestamp: failure.timestamp,
794
810
  };
795
811
  try {
@@ -9,3 +9,4 @@ export * from "./types.js";
9
9
  export * from "./diagnostics.js";
10
10
  export * from "./event-stream.js";
11
11
  export * from "./validation.js";
12
+ export * from "./tool-turns.js";
@@ -2,3 +2,4 @@ export * from "./types.js";
2
2
  export * from "./diagnostics.js";
3
3
  export * from "./event-stream.js";
4
4
  export * from "./validation.js";
5
+ export * from "./tool-turns.js";
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The request view's TOOL-TURN LAW — the structure every provider lane requires of a tool turn, restored on the
3
+ * way to the brain (never in the transcript).
4
+ *
5
+ * For every assistant message that carries tool calls:
6
+ * (i) the calls are a CONTIGUOUS TAIL of DISTINCT ids — every block that is not a call and stands after the first
7
+ * call (between two calls or after the last) moves ahead of the calls; blocks ahead of the first call, thinking
8
+ * first, keep their place (a stable partition: non-call blocks in their order, then the calls in theirs); a
9
+ * second call carrying an id the message already used is left out (every lane requires a request's call ids to
10
+ * be unique), so the message keeps the FIRST call per id and every result for that id answers it;
11
+ * (ii) the messages right after it are EXACTLY one result per call — a result recorded later in the transcript
12
+ * (behind a user message, a wake frame, another turn) is moved up into the batch, a second result for the
13
+ * same call is folded into the first (no recorded result is withheld), an interrupted close
14
+ * ({@link isInterruptedClose}) stands in only for a call with no other result (beside a recorded result it
15
+ * is void: it says only that none was recorded), and a call with no result at all is closed with the reconcile
16
+ * family's synthetic interrupted result ({@link interruptedResults}: the same text and `errorKind` a persisted
17
+ * reconcile writes);
18
+ * (iii) every result answers a call — a result with no EARLIER assistant message emitting its id (its call is gone,
19
+ * or it was recorded ahead of its call) answers nothing in this view and is left out. This is the one rule for
20
+ * that shape on every request builder (the harness's request lane and the side query alike).
21
+ *
22
+ * The three lanes carry the same requirement in three spellings — the Anthropic Messages API rejects a `tool_use`
23
+ * followed by any non-`tool_use` block (between two calls as after the last), or whose `tool_result` is not in the
24
+ * very next message; chat completions requires the `tool` messages right after the assistant's `tool_calls`; the
25
+ * Responses API requires a `function_call_output` for every `function_call` — and each brain's own builder only ever
26
+ * merged ADJACENT results. One transcript that
27
+ * breaks the structure (a result recorded late, a note written after the calls) was therefore rejected on every
28
+ * later request of its session. Holding the law here, once, at the provider boundary means the three brains read
29
+ * the canonical form, and a session already carrying a broken turn is repaired on its next request.
30
+ *
31
+ * Attribution (some gateways number calls per request, so an id legitimately recurs across turns): a result belongs
32
+ * to the NEAREST EARLIER assistant message that emitted its id — and to nothing else: its recorded tool name is not a
33
+ * key. That is the persisted reconcile's positional reading (a call is answered by a result standing after it and
34
+ * before the id's next use), so the two readers never disagree about which occurrence a result answers; a name-based
35
+ * reading (7.37.1) reached back past turns the model had already answered.
36
+ * An earlier occurrence of a reused id that has no result before the next use is therefore closed as
37
+ * interrupted (outcome unknown), never handed a later occurrence's success; every result after the last use is that
38
+ * use's (two or more are folded). A result with no earlier call carrying its id is left out (iii). Pure and
39
+ * deterministic (a moved result keeps its object; a synthesized one takes its assistant's timestamp), so the same
40
+ * transcript yields the same request bytes and the prefix cache holds.
41
+ * The close the view synthesizes carries NO tool-effect refinement: the view holds no tool table and no record of
42
+ * which calls started, so every call it closes reads the reconcile family's conservative "outcome unknown — verify"
43
+ * wording, whatever the tool. (The persisted reconcile refines with the run's tool effects — a read-only or
44
+ * idempotent call's close says it may simply be re-issued — and, on a live abort, with the calls it saw start.)
45
+ * Returns the SAME array when nothing needed restoring.
46
+ */
47
+ import type { AssertAllKeysHandled } from "../../core/ask-origin.js";
48
+ import type { AssistantMessageDiagnostic } from "./diagnostics.js";
49
+ import type { Message } from "./types.js";
50
+ /** What the law restored — the `forms` of a `context.tool_turn_repaired` notice. */
51
+ export declare const TOOL_TURN_REPAIR_FORMS: readonly ["trailing_text", "late_result", "missing_result", "duplicate_result", "duplicate_call", "orphan_result"];
52
+ export type ToolTurnRepairForm = (typeof TOOL_TURN_REPAIR_FORMS)[number];
53
+ /** Does the form leave recorded content out of the request (a call, a result), rather than move, fold or add it?
54
+ * A request that did is a lossy projection of the session — the harness never seeds a fork summary from it. */
55
+ export declare const TOOL_TURN_REPAIR_DROPS: {
56
+ readonly trailing_text: false;
57
+ readonly late_result: false;
58
+ readonly missing_result: false;
59
+ readonly duplicate_result: false;
60
+ readonly duplicate_call: true;
61
+ readonly orphan_result: true;
62
+ };
63
+ export type DropsTableCoversEveryForm = AssertAllKeysHandled<Exclude<ToolTurnRepairForm, keyof typeof TOOL_TURN_REPAIR_DROPS>>;
64
+ /**
65
+ * Why a tool turn reached the model incomplete — the `task.tool_turn_incomplete` notice's `detail.cause`, the person's
66
+ * view of facts the model is already told in the turn itself. Closed:
67
+ * · `truncated` — calls whose argument bytes were cut before they parsed: dropped, the model asked to re-issue them;
68
+ * · `unnamed` — calls that arrived with no tool name: dropped the same way;
69
+ * · `bad_frame` — provider stream frames that were not valid JSON: dropped, so the response may be missing content;
70
+ * · `missing_result_synthesized` — a call with no recorded result, closed in the request view as interrupted with its
71
+ * outcome unknown (the `missing_result` repair).
72
+ * The first three are a brain's stream facts ({@link StreamDropCause}), stamped on the turn's own message; the fourth is
73
+ * the request view's.
74
+ */
75
+ export declare const TOOL_TURN_INCOMPLETE_CAUSES: readonly ["truncated", "unnamed", "bad_frame", "missing_result_synthesized"];
76
+ export type ToolTurnIncompleteCause = (typeof TOOL_TURN_INCOMPLETE_CAUSES)[number];
77
+ /** The causes a brain's stream parse finds — what a turn's own message can report. */
78
+ export type StreamDropCause = Exclude<ToolTurnIncompleteCause, "missing_result_synthesized">;
79
+ /** One cause a turn's stream dropped content for, and how many calls (or frames) it dropped. */
80
+ export interface StreamDrop {
81
+ readonly cause: StreamDropCause;
82
+ readonly count: number;
83
+ }
84
+ /** Stamp a turn's stream drops on its message (`AssistantMessage.diagnostics`); nothing for a turn that dropped none. */
85
+ export declare function stampStreamDrops(message: {
86
+ diagnostics?: AssistantMessageDiagnostic[];
87
+ }, drops: readonly StreamDrop[]): void;
88
+ /** The stream drops a message reports. A host brain's message is not trusted to be well-formed: an entry counts only
89
+ * with a closed-set cause and a positive integer count; anything else reads as no drop. */
90
+ export declare function streamDropsOf(message: {
91
+ readonly diagnostics?: unknown;
92
+ }): StreamDrop[];
93
+ /** One restoration: on one assistant message, or — for `orphan_result` — on the view as a whole. */
94
+ export interface ToolTurnRepair {
95
+ readonly form: ToolTurnRepairForm;
96
+ /** The calls concerned, in call order — for `trailing_text` every call of the message; for `duplicate_call` the
97
+ * ids a later call of the message repeated; for `orphan_result` the ids of the results left out, once each, in
98
+ * transcript order; otherwise the calls whose result was moved up, synthesized, or folded with a second one. */
99
+ readonly toolCallIds: readonly string[];
100
+ }
101
+ export interface ToolTurnView {
102
+ /** The request view — the input array itself when {@link repairs} is empty. */
103
+ readonly messages: Message[];
104
+ readonly repairs: readonly ToolTurnRepair[];
105
+ }
106
+ /** The text standing between two results recorded for one call when the view folds them into one. */
107
+ export declare const SECOND_RESULT_SEPARATOR = "[another result was recorded for this same call:]";
108
+ /** Restore the tool-turn law on a provider-bound message list (see the module header). */
109
+ export declare function ensureToolTurnInvariants(messages: Message[]): ToolTurnView;
@@ -0,0 +1,145 @@
1
+ import { interruptedResultMessage, interruptedResults, isInterruptedClose } from "../../core/session-reconcile.js";
2
+ export const TOOL_TURN_REPAIR_FORMS = Object.freeze(["trailing_text", "late_result", "missing_result", "duplicate_result", "duplicate_call", "orphan_result"]);
3
+ export const TOOL_TURN_REPAIR_DROPS = {
4
+ trailing_text: false,
5
+ late_result: false,
6
+ missing_result: false,
7
+ duplicate_result: false,
8
+ duplicate_call: true,
9
+ orphan_result: true,
10
+ };
11
+ export const TOOL_TURN_INCOMPLETE_CAUSES = Object.freeze(["truncated", "unnamed", "bad_frame", "missing_result_synthesized"]);
12
+ const STREAM_DROPS_DIAGNOSTIC = "stream_drops";
13
+ const STREAM_DROP_CAUSES = new Set(["truncated", "unnamed", "bad_frame"]);
14
+ export function stampStreamDrops(message, drops) {
15
+ if (drops.length === 0)
16
+ return;
17
+ message.diagnostics = [...(message.diagnostics ?? []), { type: STREAM_DROPS_DIAGNOSTIC, timestamp: Date.now(), details: { drops: drops.map(({ cause, count }) => ({ cause, count })) } }];
18
+ }
19
+ export function streamDropsOf(message) {
20
+ const out = [];
21
+ try {
22
+ const diagnostics = message.diagnostics;
23
+ if (!Array.isArray(diagnostics))
24
+ return out;
25
+ const entries = diagnostics;
26
+ for (const entry of entries) {
27
+ if (typeof entry !== "object" || entry === null || entry.type !== STREAM_DROPS_DIAGNOSTIC)
28
+ continue;
29
+ const drops = (entry.details ?? {}).drops;
30
+ if (!Array.isArray(drops))
31
+ continue;
32
+ const items = drops;
33
+ for (const d of items) {
34
+ const cause = d?.cause;
35
+ const count = d?.count;
36
+ if (typeof cause === "string" && STREAM_DROP_CAUSES.has(cause) && typeof count === "number" && Number.isInteger(count) && count > 0)
37
+ out.push({ cause: cause, count });
38
+ }
39
+ }
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ return out;
45
+ }
46
+ function callsOf(m) {
47
+ return m.role === "assistant" && Array.isArray(m.content) ? m.content.filter((c) => c?.type === "toolCall") : [];
48
+ }
49
+ export const SECOND_RESULT_SEPARATOR = "[another result was recorded for this same call:]";
50
+ export function ensureToolTurnInvariants(messages) {
51
+ const anchor = [];
52
+ messages.forEach((m, i) => anchor.push(m.role === "toolResult" && i > 0 ? anchor[i - 1] : i));
53
+ const sites = new Map();
54
+ const ownerOf = new Map();
55
+ const resultsOf = new Map();
56
+ const orphans = new Set();
57
+ messages.forEach((m, i) => {
58
+ if (m.role === "assistant") {
59
+ for (const call of callsOf(m)) {
60
+ const list = sites.get(call.id) ?? [];
61
+ if (list.at(-1) !== i)
62
+ list.push(i);
63
+ sites.set(call.id, list);
64
+ }
65
+ return;
66
+ }
67
+ if (m.role !== "toolResult")
68
+ return;
69
+ const all = sites.get(m.toolCallId) ?? [];
70
+ if (all.length === 0) {
71
+ orphans.add(i);
72
+ return;
73
+ }
74
+ const owner = all.at(-1);
75
+ ownerOf.set(i, owner);
76
+ const batch = resultsOf.get(owner);
77
+ if (batch === undefined)
78
+ resultsOf.set(owner, [i]);
79
+ else
80
+ batch.push(i);
81
+ });
82
+ const repairs = [];
83
+ const out = [];
84
+ messages.forEach((m, i) => {
85
+ if (ownerOf.has(i) || orphans.has(i))
86
+ return;
87
+ const calls = callsOf(m);
88
+ if (calls.length === 0) {
89
+ out.push(m);
90
+ return;
91
+ }
92
+ const assistant = m;
93
+ const byId = new Map();
94
+ const repeated = [];
95
+ for (const call of calls) {
96
+ if (!byId.has(call.id))
97
+ byId.set(call.id, call);
98
+ else if (!repeated.includes(call.id))
99
+ repeated.push(call.id);
100
+ }
101
+ const ids = [...byId.keys()];
102
+ const firstCall = assistant.content.findIndex((c) => c?.type === "toolCall");
103
+ const trails = assistant.content.slice(firstCall).some((c) => c?.type !== "toolCall");
104
+ out.push(trails || repeated.length > 0 ? { ...assistant, content: [...assistant.content.filter((c) => c?.type !== "toolCall"), ...byId.values()] } : assistant);
105
+ if (trails)
106
+ repairs.push({ form: "trailing_text", toolCallIds: ids });
107
+ if (repeated.length > 0)
108
+ repairs.push({ form: "duplicate_call", toolCallIds: repeated });
109
+ const recorded = new Map();
110
+ for (const r of resultsOf.get(i) ?? []) {
111
+ const id = messages[r].toolCallId;
112
+ recorded.set(id, [...(recorded.get(id) ?? []), r]);
113
+ }
114
+ const sent = new Map();
115
+ const late = [];
116
+ const duplicate = [];
117
+ for (const [id, at] of recorded) {
118
+ const real = at.filter((r) => !isInterruptedClose(messages[r]));
119
+ const chosen = real.length > 0 ? real : at.slice(0, 1);
120
+ sent.set(id, chosen.map((r) => messages[r]).reduce(foldResults));
121
+ if (anchor[chosen[0]] !== i)
122
+ late.push(id);
123
+ if (at.length > 1)
124
+ duplicate.push(id);
125
+ }
126
+ out.push(...sent.values());
127
+ const missing = [...byId.values()].filter((c) => !sent.has(c.id));
128
+ for (const result of interruptedResults(missing.map((c) => ({ toolCallId: c.id, toolName: c.name })))) {
129
+ out.push(interruptedResultMessage(result, assistant.timestamp));
130
+ }
131
+ const inCallOrder = (set) => ids.filter((id) => set.includes(id));
132
+ if (late.length > 0)
133
+ repairs.push({ form: "late_result", toolCallIds: inCallOrder(late) });
134
+ if (missing.length > 0)
135
+ repairs.push({ form: "missing_result", toolCallIds: missing.map((c) => c.id) });
136
+ if (duplicate.length > 0)
137
+ repairs.push({ form: "duplicate_result", toolCallIds: inCallOrder(duplicate) });
138
+ });
139
+ if (orphans.size > 0)
140
+ repairs.push({ form: "orphan_result", toolCallIds: [...new Set([...orphans].map((i) => messages[i].toolCallId))] });
141
+ return repairs.length === 0 ? { messages, repairs } : { messages: out, repairs };
142
+ }
143
+ function foldResults(first, second) {
144
+ return { ...first, content: [...first.content, { type: "text", text: SECOND_RESULT_SEPARATOR }, ...second.content], isError: first.isError && second.isError };
145
+ }
@@ -434,6 +434,17 @@ export interface AssistantMessage {
434
434
  * as `0`.
435
435
  */
436
436
  apiErrorStatus?: number;
437
+ /**
438
+ * The provider's own word for a REFUSED request, read structurally from its JSON error envelope (`error.code`, else
439
+ * `error.type`) — e.g. `invalid_request_error`, `context_length_exceeded`. Only meaningful together with
440
+ * {@link isApiErrorMessage}; provider vocabulary, never an engine closed set.
441
+ *
442
+ * PRESENCE: only beside a 4xx {@link apiErrorStatus} (the provider refused the request as sent) whose envelope stated
443
+ * such a word in the provider echo shape (visible ASCII, 1–128 characters). ABSENT otherwise — any other status, a
444
+ * plain-text or torn body, an envelope with neither member, or a word outside the shape (dropped whole, never
445
+ * truncated). The run's `terminal.detail.code` is this value.
446
+ */
447
+ apiErrorCode?: string;
437
448
  diagnostics?: AssistantMessageDiagnostic[];
438
449
  usage: Usage;
439
450
  /** TB 尸检 T1-5: the provider never delivered a usage frame for this message (e.g. a
package/dist/index.d.ts CHANGED
@@ -63,8 +63,10 @@ export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, classify
63
63
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice, type McpInjectionDropReason, type McpInjectionDropFacts } from "./core/mcp-injection-drop.js";
64
64
  export { askUnresolvableNotice, type AskUnresolvableFacts } from "./core/ask-unresolvable-notice.js";
65
65
  export { ASK_UNRESOLVABLE_CAUSES } from "./core/ask-unresolvable-notice.js";
66
+ export { USER_STEER_UNDRAINED_CAUSES, type UserSteerUndrainedCause } from "./core/engine-notice.js";
67
+ export { TOOL_TURN_INCOMPLETE_CAUSES, type ToolTurnIncompleteCause } from "./internal/llm.js";
66
68
  export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
67
- export { TERMINAL_CAUSE_IS_REPLAYABLE } from "./core/terminal-cause.js";
69
+ export { TERMINAL_CAUSE_IS_REPLAYABLE, type TerminalFailureDetail } from "./core/terminal-cause.js";
68
70
  export { RESERVED_AGENT_NAMES } from "./agents/subagent.js";
69
71
  export { WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS } from "./orchestration/workflow-size-guideline.js";
70
72
  export { ENGINE_AUTHORITY_ENVELOPE_TAGS } from "./core/untrusted-text.js";
@@ -79,7 +81,7 @@ export { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME
79
81
  export { admitMemoryScopes, foldAdmissionFreeze, type OwnOrgAdmissionVerdict, type MemoryAdmissionInput, type MemoryAdmissionOutcome, type MemoryAdmissionVerdict, type MemoryScopeAdmission, type MemoryScopeOrigin, type MemoryScopeRequest, } from "./core/memory-admission.js";
80
82
  export { assertRetentionCapability, RETENTION_CASCADE_DOMAINS, RETENTION_CASCADE_EXEMPT, type ManagedRetentionCapability, type RetentionDeclaration, type RetentionDeclaring, type RetentionPolicy, type RetentionReceipt, } from "./core/retention.js";
81
83
  export { GOVERNANCE_CODES, governanceRetryClass, TOOL_DECLARATION_CODES, type GovernanceCode, type GovernanceRetryClass, type ToolDeclarationCode } from "./core/governance-codes.js";
82
- export { NOTICE_AUDIENCE, noticeAudienceOf, ENGINE_NOTICE_CODES, type EngineNoticeCode, type NoticeAudience } from "./core/governance-codes.js";
84
+ export { NOTICE_AUDIENCE, noticeAudienceOf, ENGINE_NOTICE_CODES, NOTICE_MINTER, type EngineNoticeCode, type NoticeAudience, type NoticeMinter } from "./core/governance-codes.js";
83
85
  export { TtlSessionStore, type TtlSessionStoreOptions, type EvictPolicy } from "./core/session-store.js";
84
86
  export { reconcileInterruptedSession, findOrphanToolCalls, type OrphanToolCall, type ReconcileReport, } from "./core/session-reconcile.js";
85
87
  export { StoredSession, InMemorySessionRepo, InMemorySessionStorage, BaseSessionStorage, leafIdAfterEntry, validateEntriesForImport, StreamingImportValidator, boundedTail, SessionError, isSessionConflict, hasSessionFork, uuidv7, type Session, type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionStorage, type SessionRepo, type SessionMetadata, type SessionTreeEntry, type SessionWriteOptions, type SessionPlacement, type SessionPlacementRecord, type PlacedSessionRow, } from "./core/session.js";
@@ -191,7 +193,7 @@ export { createAllowDenyPolicy, createApprovalPolicy,
191
193
  * `Parameters<typeof createApprovalPolicy>[0]["approve"]`. */
192
194
  type ApprovalSeatOutcome, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, type ToolPolicyNameSets, type NamedToolPolicy, type ToolPolicyProjection, type ToolPolicyProjectionComponent, type ConstraintChainEntry, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, type ToolPolicy, type ToolCallRequest, type PermissionResult, type DecisionReason, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, type OnAsk, type AskOutcome, type ResolvedAsk, type AskRequest, type AskDelegationProvenance, type AskRuleEvidence, type AskEvidenceAbsence, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
193
195
  export { type ReadRootGrantScreen } from "./core/read-root-grant-screen.js";
194
- export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, type PersistedRuleMandate, isPersistedRuleMandate } from "./core/ask-origin.js";
196
+ export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, type PersistedRuleMandate, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
195
197
  export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, type ModeReleaseReason, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
196
198
  export { modePolicyOf, type ModePolicy, PERMISSION_MODE_FOLD_REASONS, type PermissionModeFoldReason, type PermissionModeReceipt, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
197
199
  export { readDenyClassifierRules, renderClassifierDenyRules, type DerivedDenyRule } from "./core/auto-mode-prompt.js";
package/dist/index.js CHANGED
@@ -49,6 +49,8 @@ export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, classify
49
49
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice } from "./core/mcp-injection-drop.js";
50
50
  export { askUnresolvableNotice } from "./core/ask-unresolvable-notice.js";
51
51
  export { ASK_UNRESOLVABLE_CAUSES } from "./core/ask-unresolvable-notice.js";
52
+ export { USER_STEER_UNDRAINED_CAUSES } from "./core/engine-notice.js";
53
+ export { TOOL_TURN_INCOMPLETE_CAUSES } from "./internal/llm.js";
52
54
  export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
53
55
  export { TERMINAL_CAUSE_IS_REPLAYABLE } from "./core/terminal-cause.js";
54
56
  export { RESERVED_AGENT_NAMES } from "./agents/subagent.js";
@@ -65,7 +67,7 @@ export { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME
65
67
  export { admitMemoryScopes, foldAdmissionFreeze, } from "./core/memory-admission.js";
66
68
  export { assertRetentionCapability, RETENTION_CASCADE_DOMAINS, RETENTION_CASCADE_EXEMPT, } from "./core/retention.js";
67
69
  export { GOVERNANCE_CODES, governanceRetryClass, TOOL_DECLARATION_CODES } from "./core/governance-codes.js";
68
- export { NOTICE_AUDIENCE, noticeAudienceOf, ENGINE_NOTICE_CODES } from "./core/governance-codes.js";
70
+ export { NOTICE_AUDIENCE, noticeAudienceOf, ENGINE_NOTICE_CODES, NOTICE_MINTER } from "./core/governance-codes.js";
69
71
  export { TtlSessionStore } from "./core/session-store.js";
70
72
  export { reconcileInterruptedSession, findOrphanToolCalls, } from "./core/session-reconcile.js";
71
73
  export { StoredSession, InMemorySessionRepo, InMemorySessionStorage, BaseSessionStorage, leafIdAfterEntry, validateEntriesForImport, StreamingImportValidator, boundedTail, SessionError, isSessionConflict, hasSessionFork, uuidv7, } from "./core/session.js";
@@ -156,7 +158,7 @@ export { resolveAutonomousLoopPrompt, AUTONOMOUS_LOOP_PREAMBLE, AUTONOMOUS_LOOP_
156
158
  export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.js";
157
159
  export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
158
160
  export {} from "./core/read-root-grant-screen.js";
159
- export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate } from "./core/ask-origin.js";
161
+ export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
160
162
  export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
161
163
  export { modePolicyOf, PERMISSION_MODE_FOLD_REASONS, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
162
164
  export { readDenyClassifierRules, renderClassifierDenyRules } from "./core/auto-mode-prompt.js";
@@ -4,5 +4,5 @@
4
4
  * design/118 ①②: the layer is now first-party (`src/engine/llm/`, naturalized out of the vendored
5
5
  * llm-core); the facade stays so consumers never notice which side a symbol lives on.
6
6
  */
7
- export { createAssistantMessageEventStream, snapshotActorAssertion, stripEngineMetadata } from "../engine/llm/index.js";
8
- export type { ActorAssertion, AnthropicMessagesCompat, OpenAICompletionsCompat, OpenAIResponsesCompat, AssistantMessage, AssistantMessageDiagnostic, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, Model, ResilienceOptions, RouteAdjudication, RouteCredential, RouteCredentialSource, RoutePairingConfig, RoutePairingPosture, RouteRefusalCode, RouteRefusalDetail, SimpleStreamOptions, StallTimeouts, StopReason, StreamFn, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage, Usage, UserMessage, } from "../engine/llm/index.js";
7
+ export { createAssistantMessageEventStream, ensureToolTurnInvariants, snapshotActorAssertion, stampStreamDrops, streamDropsOf, stripEngineMetadata, TOOL_TURN_INCOMPLETE_CAUSES, TOOL_TURN_REPAIR_DROPS } from "../engine/llm/index.js";
8
+ export type { ActorAssertion, AnthropicMessagesCompat, OpenAICompletionsCompat, OpenAIResponsesCompat, AssistantMessage, AssistantMessageDiagnostic, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, Model, ResilienceOptions, RouteAdjudication, RouteCredential, RouteCredentialSource, RoutePairingConfig, RoutePairingPosture, RouteRefusalCode, RouteRefusalDetail, SimpleStreamOptions, StallTimeouts, StopReason, StreamFn, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage, StreamDrop, StreamDropCause, ToolTurnIncompleteCause, ToolTurnRepair, Usage, UserMessage, } from "../engine/llm/index.js";
@@ -1 +1 @@
1
- export { createAssistantMessageEventStream, snapshotActorAssertion, stripEngineMetadata } from "../engine/llm/index.js";
1
+ export { createAssistantMessageEventStream, ensureToolTurnInvariants, snapshotActorAssertion, stampStreamDrops, streamDropsOf, stripEngineMetadata, TOOL_TURN_INCOMPLETE_CAUSES, TOOL_TURN_REPAIR_DROPS } from "../engine/llm/index.js";
@@ -15,7 +15,7 @@ export class WorkflowModelNotAllowedError extends Error {
15
15
  this.name = "WorkflowModelNotAllowedError";
16
16
  }
17
17
  }
18
- export const WHITELIST_KEYS = [
18
+ export const WHITELIST_KEYS = Object.freeze([
19
19
  "objective",
20
20
  "modelName",
21
21
  "thinking",
@@ -25,7 +25,7 @@ export const WHITELIST_KEYS = [
25
25
  "readFace",
26
26
  "writeFace",
27
27
  "readDenyPatterns",
28
- ];
28
+ ]);
29
29
  const WHITELIST_KEY_SET = new Set(WHITELIST_KEYS);
30
30
  export const SPEC_KEY_ALIASES = new Map([["model", "modelName"]]);
31
31
  export const STRIPPED_KEYS_NOTICE_CODE = "workflow.governance_key_stripped";
@@ -1,5 +1,5 @@
1
- export const PARK_TRUTH_UNREADABLE_REASONS = ["store_threw", "record_missing", "record_not_terminal", "parks_unreadable", "admission_incomplete", "checkpoint_store_threw", "capability_not_armed"];
2
- export const PARK_NOT_PENDING_STATUSES = ["resolved", "expired", "absent"];
1
+ export const PARK_TRUTH_UNREADABLE_REASONS = Object.freeze(["store_threw", "record_missing", "record_not_terminal", "parks_unreadable", "admission_incomplete", "checkpoint_store_threw", "capability_not_armed"]);
2
+ export const PARK_NOT_PENDING_STATUSES = Object.freeze(["resolved", "expired", "absent"]);
3
3
  export class WorkflowBudgetExceededError extends Error {
4
4
  spent;
5
5
  total;
package/dist/shell/lex.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const SHELL_LEX_DIALECT = "bash";
2
- export const QUOTE_KINDS = ["'", '"', "$'", '$"', "\\"];
3
- export const EXPANSION_KINDS = ["parameter", "command", "process", "arithmetic", "tilde", "brace", "glob"];
4
- export const UNRESOLVED_REASONS = ["ansi_c_escape", "locale_string", "parameter_text", "subscript_text", "compound_array_value"];
2
+ export const QUOTE_KINDS = Object.freeze(["'", '"', "$'", '$"', "\\"]);
3
+ export const EXPANSION_KINDS = Object.freeze(["parameter", "command", "process", "arithmetic", "tilde", "brace", "glob"]);
4
+ export const UNRESOLVED_REASONS = Object.freeze(["ansi_c_escape", "locale_string", "parameter_text", "subscript_text", "compound_array_value"]);
5
5
  export const UNRESOLVED_SENTENCE = {
6
6
  ansi_c_escape: "an ANSI-C quoted word carries an escape this lexer does not fold",
7
7
  locale_string: "a locale-translated string carries text this lexer does not read",
@@ -9,7 +9,7 @@ export const UNRESOLVED_SENTENCE = {
9
9
  subscript_text: "a subscript carries text this lexer does not read",
10
10
  compound_array_value: "a compound assignment value is a list this lexer does not read",
11
11
  };
12
- export const PARSE_FAILURE_REASONS = [
12
+ export const PARSE_FAILURE_REASONS = Object.freeze([
13
13
  "unterminated_single_quote",
14
14
  "unterminated_double_quote",
15
15
  "unterminated_ansi_c",
@@ -23,7 +23,7 @@ export const PARSE_FAILURE_REASONS = [
23
23
  "redirect_without_target",
24
24
  "heredoc_without_delimiter",
25
25
  "heredoc_delimiter_expands",
26
- ];
26
+ ]);
27
27
  export const PARSE_FAILURE_SENTENCE = {
28
28
  unterminated_single_quote: "a single-quoted word never closes",
29
29
  unterminated_double_quote: "a double-quoted word never closes",
@@ -39,7 +39,7 @@ export const PARSE_FAILURE_SENTENCE = {
39
39
  heredoc_without_delimiter: "a here-document operator names no delimiter",
40
40
  heredoc_delimiter_expands: "a here-document delimiter is not a plain word",
41
41
  };
42
- export const BASH_ONLY_CONSTRUCTS = [
42
+ export const BASH_ONLY_CONSTRUCTS = Object.freeze([
43
43
  "ansi_c_quoting",
44
44
  "locale_string",
45
45
  "append_assignment",
@@ -50,7 +50,7 @@ export const BASH_ONLY_CONSTRUCTS = [
50
50
  "redirect_both",
51
51
  "pipe_both",
52
52
  "brace_expansion",
53
- ];
53
+ ]);
54
54
  export const ANSI_C_ESCAPES = {
55
55
  a: "\x07",
56
56
  b: "\b",