@sema-agent/core 7.43.0 → 7.44.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 (39) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/agents/subagent.d.ts +2 -1
  3. package/dist/brain/errors.d.ts +23 -2
  4. package/dist/brain/errors.js +26 -2
  5. package/dist/brain/stream-engine.d.ts +2 -1
  6. package/dist/brain/stream-engine.js +10 -9
  7. package/dist/core/checkpoint-store.d.ts +26 -13
  8. package/dist/core/checkpoint-store.js +4 -13
  9. package/dist/core/engine-notice.d.ts +16 -14
  10. package/dist/core/engine-notice.js +4 -3
  11. package/dist/core/governance-codes.d.ts +4 -3
  12. package/dist/core/governance-codes.js +1 -0
  13. package/dist/core/memory-engine/engine.d.ts +2 -2
  14. package/dist/core/memory-engine/engine.js +7 -14
  15. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  16. package/dist/core/memory-engine/file-backend.js +5 -7
  17. package/dist/core/memory-engine/layout.d.ts +37 -4
  18. package/dist/core/memory-engine/layout.js +34 -5
  19. package/dist/core/runner/contracts.d.ts +1 -16
  20. package/dist/core/runner/runtask.js +1 -5
  21. package/dist/core/runner/steer-admission.d.ts +10 -11
  22. package/dist/core/runner/steer-admission.js +6 -8
  23. package/dist/core/runner/stream-steer-verb.d.ts +5 -4
  24. package/dist/core/runner/stream-steer-verb.js +6 -8
  25. package/dist/core/steer-replay.d.ts +46 -0
  26. package/dist/core/steer-replay.js +22 -0
  27. package/dist/core/task-event.d.ts +3 -2
  28. package/dist/core/task-stream.d.ts +12 -6
  29. package/dist/core/tool-errors.d.ts +2 -2
  30. package/dist/core/types.d.ts +2 -2
  31. package/dist/core/types.js +1 -1
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.js +3 -2
  34. package/dist/orchestration/workflow-types.d.ts +5 -0
  35. package/dist/orchestration/workflow.d.ts +3 -2
  36. package/dist/orchestration/workflow.js +14 -6
  37. package/package.json +1 -1
  38. package/test/export-surface.snapshot.json +11 -5
  39. package/wire-changes/core@7.44.0.json +469 -0
@@ -18,7 +18,7 @@ import { landedUndecided, layerDigest, layerObservationLookup, readOnlyLayerCand
18
18
  import { markOriginClearanceTombstoned, openOriginClearance, readOriginClearances, settleOriginClearance } from "./origin-clearance.js";
19
19
  import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, canonicalJsonStringify, captureErasureInput, erasureRequestInvalid, erasureSelectHash, scanEntryFiles, } from "./file-backend.js";
20
20
  import { assembleMemoryExportBundle, computeMemoryBundleHash, memoryBundleInvalid, } from "./export-bundle.js";
21
- import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, restoreContainedBaseline, indexLineIsStale, readIndexRevs, writeIndexRevs, bumpScanFuse, scanFuseKey, canonicalize, landingPathOf, storeTargetEscape, TARGET_OUTSIDE_STORE, adoptRootScopeLayout, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, markSessionPolluted, readSessionPollution, fileSessionCaptureRecordStore, recordRetrievedAccount, writeFileNoFollow, withPermissionRemedy, permissionRemedyOf, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, readProjectionDebts, resolveChallengeEvent, settleProjectionDebts, stageLineagePending, stageProjectionDebts, } from "./layout.js";
21
+ import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, restoreContainedBaseline, indexLineIsStale, readIndexRevs, writeIndexRevs, bumpScanFuse, scanFuseKey, canonicalize, landingPathOf, landsOutsideStore, storeActRefusal, storeRootMovedRefusal, adoptRootScopeLayout, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, markSessionPolluted, readSessionPollution, fileSessionCaptureRecordStore, recordRetrievedAccount, writeFileNoFollow, withPermissionRemedy, permissionRemedyOf, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, readProjectionDebts, resolveChallengeEvent, settleProjectionDebts, stageLineagePending, stageProjectionDebts, } from "./layout.js";
22
22
  import { createSessionIncarnationSeat, recalledForAnyIdentity, recalledForIdentity, rememberForIdentity } from "./session-incarnation.js";
23
23
  import { scanMemoryFileName, scanMemoryWrite, scanRemediation } from "./scan.js";
24
24
  import { writableRootOf } from "./types.js";
@@ -1416,11 +1416,8 @@ export class MemoryEngine {
1416
1416
  ensureDirExists(this.memoryDir);
1417
1417
  const physicalRoot = landingPathOf(this.memoryDir);
1418
1418
  this.mountedRoot ??= physicalRoot;
1419
- if (physicalRoot !== this.mountedRoot) {
1420
- const e = new Error(`${TARGET_OUTSIDE_STORE}: the memory dir ${this.memoryDir} now resolves to ${physicalRoot}, not ${this.mountedRoot} where this engine first mounted it — refused; open a new engine on the directory you mean`);
1421
- e.code = TARGET_OUTSIDE_STORE;
1422
- throw e;
1423
- }
1419
+ if (physicalRoot !== this.mountedRoot)
1420
+ throw storeRootMovedRefusal("engine", this.memoryDir);
1424
1421
  ensureDirExists(this.controlDir);
1425
1422
  await this.adoptLayoutBeforeMount(writeScope);
1426
1423
  const restricted = opts?.adoptionRestricted === true;
@@ -2216,7 +2213,7 @@ export class MemoryEngine {
2216
2213
  if (content === undefined)
2217
2214
  continue;
2218
2215
  try {
2219
- this.refuseOutsideStore(handle.memoryDir, m.path, "restoring");
2216
+ this.refuseOutsideStore(handle.memoryDir, m.path, "restoring a memory file");
2220
2217
  mkdirSync(dirname(m.path), { recursive: true });
2221
2218
  writeFileNoFollow(m.path, content);
2222
2219
  report.restored.push(m.relPath);
@@ -4947,15 +4944,11 @@ export class MemoryEngine {
4947
4944
  };
4948
4945
  }
4949
4946
  refuseOutsideStore(root, path, act) {
4950
- const escape = storeTargetEscape(root, path);
4951
- if (escape === undefined)
4952
- return;
4953
- const e = new Error(`${TARGET_OUTSIDE_STORE}: ${act} ${path} was refused — ${escape}; not performed`);
4954
- e.code = TARGET_OUTSIDE_STORE;
4955
- throw e;
4947
+ if (landsOutsideStore(root, path))
4948
+ throw storeActRefusal(act, path);
4956
4949
  }
4957
4950
  writeIfChanged(target, text, root) {
4958
- this.refuseOutsideStore(root, target, "the projection write of");
4951
+ this.refuseOutsideStore(root, target, "the projection write");
4959
4952
  const current = readNoFollowSafe(target);
4960
4953
  if (current === text)
4961
4954
  return;
@@ -682,9 +682,9 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
682
682
  * let a transient EIO run the unlocked recovery (and its staged sweep) over a live transaction. */
683
683
  private txnInFlight;
684
684
  /**
685
- * The act gate for store paths ({@link storeTargetEscape}) over `ops`' writes and deletes — shadow
685
+ * The act gate for store paths ({@link landsOutsideStore}) over `ops`' writes and deletes — shadow
686
686
  * ops name control-plane files, not store paths. Refuses with `memory.target_outside_store` at the
687
- * first op that would land outside the store, naming the op and where it resolves. Runs over a
687
+ * first op that would land outside the store, naming the op (its path on `detail.path`). Runs over a
688
688
  * whole batch before its commit marker (no journal is written; a caller that staged files removes
689
689
  * them) and per op at the act itself; past the marker a refusal keeps the journal, which replays
690
690
  * once the path resolves inside the store again.
@@ -8,7 +8,7 @@ import { ambiguousOriginRepresentation, committedDistilledOf, committedOriginOf,
8
8
  import { convertV1SidecarKeys } from "./delegation-settlement.js";
9
9
  import { layerObservationLookup, observedDivergenceNotice } from "./layer-observations.js";
10
10
  import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "./entry-scan.js";
11
- import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, CHALLENGES_FILE, IMPORT_RECEIPTS_DIR, LINEAGE_FILE, SESSION_POLLUTION_DIR, appendChallengeEvents, atomicWriteFileSync, quarantineAndTombstone, captureAndClearLineageForEntries, adoptRootScopeLayout, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, importLatchTxnId, importLineageCommitted, importSessionPollution, isContainedIn, permissionRemedyOf, withPermissionRemedy, lineageContributionsOfSession, listSessionPollution, readChallengeEvents, readLineageRecord, registerScope, registeredScopes, releaseImportLatch, layoutNeedsAdoption, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, scopeRegistryShape, stageImportLatch, landingPathOf, storeTargetEscape, TARGET_OUTSIDE_STORE, writeAllSync, } from "./layout.js";
11
+ import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, CHALLENGES_FILE, IMPORT_RECEIPTS_DIR, LINEAGE_FILE, SESSION_POLLUTION_DIR, appendChallengeEvents, atomicWriteFileSync, quarantineAndTombstone, captureAndClearLineageForEntries, adoptRootScopeLayout, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, importLatchTxnId, importLineageCommitted, importSessionPollution, isContainedIn, permissionRemedyOf, withPermissionRemedy, lineageContributionsOfSession, listSessionPollution, readChallengeEvents, readLineageRecord, registerScope, registeredScopes, releaseImportLatch, layoutNeedsAdoption, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, scopeRegistryShape, stageImportLatch, landingPathOf, landsOutsideStore, storeActRefusal, storeRootMovedRefusal, writeAllSync, } from "./layout.js";
12
12
  import { screenInboundEntries } from "./data-plane.js";
13
13
  import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
14
14
  import { thrownText } from "../thrown-value.js";
@@ -1404,7 +1404,7 @@ export class FileMemoryEngineBackend {
1404
1404
  }
1405
1405
  }
1406
1406
  this.assertTxnLockOwnership(fenceToken);
1407
- const replayAt = `the journal replay (journal kept at ${jp})`;
1407
+ const replayAt = "the journal replay (journal kept)";
1408
1408
  this.refuseEscapingTargets(journal.ops, replayAt);
1409
1409
  for (const op of journal.ops) {
1410
1410
  this.refuseEscapingTargets([op], replayAt);
@@ -1470,10 +1470,8 @@ export class FileMemoryEngineBackend {
1470
1470
  if (op.kind !== "write" && op.kind !== "delete")
1471
1471
  continue;
1472
1472
  for (const path of op.staged === undefined ? [op.target] : [op.target, op.staged]) {
1473
- const escape = storeTargetEscape(this.physicalRoot, path);
1474
- if (escape === undefined)
1475
- continue;
1476
- throw erasureCodedError(`${TARGET_OUTSIDE_STORE}: ${at} refused a ${op.kind} of ${op.target}${path === op.target ? "" : ` (its staged file ${path})`} — ${escape}; not performed`, TARGET_OUTSIDE_STORE);
1473
+ if (landsOutsideStore(this.physicalRoot, path))
1474
+ throw storeActRefusal(`${path === op.target ? "a" : "the staged file of a"} ${op.kind} at ${at}`, path);
1477
1475
  }
1478
1476
  }
1479
1477
  }
@@ -1481,7 +1479,7 @@ export class FileMemoryEngineBackend {
1481
1479
  const now = landingPathOf(this.directoryRoot);
1482
1480
  if (now === this.physicalRoot)
1483
1481
  return;
1484
- throw erasureCodedError(`${TARGET_OUTSIDE_STORE}: the store root ${this.directoryRoot} now resolves to ${now}, not ${this.physicalRoot} where this backend opened it — refused; open a new backend on the directory you mean`, TARGET_OUTSIDE_STORE);
1482
+ throw storeRootMovedRefusal("backend", this.directoryRoot);
1485
1483
  }
1486
1484
  sweepStagedFiles() {
1487
1485
  const walk = (d, depth) => {
@@ -259,8 +259,41 @@ export declare function adoptRootScopeLayout(rootPath: string, controlDir: strin
259
259
  export declare const canonicalize: typeof physicalPathOfExisting;
260
260
  /** True iff `child` (canonicalized) is `root` or inside it. */
261
261
  export declare function isContainedIn(root: string, child: string): boolean;
262
- /** The code an act on a store path is refused with when it would land outside the store ({@link storeTargetEscape}). */
262
+ /** The code an act on a store path is refused with when it would land outside the store ({@link landsOutsideStore}). */
263
263
  export declare const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
264
+ /** The code a scope home that is not a real directory is refused with ({@link assertHomePhysicalForm}). */
265
+ export declare const SCOPE_HOME_NOT_DIRECTORY = "memory.scope_home_not_directory";
266
+ /** What a scope home can be besides a real directory (an `lstat` verdict). */
267
+ export declare const SCOPE_HOME_FORMS: readonly ["symlink", "file", "other"];
268
+ export type ScopeHomeForm = (typeof SCOPE_HOME_FORMS)[number];
269
+ /** How a refusal's sentence names each form. */
270
+ declare const SCOPE_HOME_FORM_PHRASE: {
271
+ readonly symlink: "a symbolic link";
272
+ readonly file: "a regular file";
273
+ readonly other: "a special file";
274
+ };
275
+ export type HomeFormPhraseCoversEveryForm = AssertAllKeysHandled<Exclude<ScopeHomeForm, keyof typeof SCOPE_HOME_FORM_PHRASE>>;
276
+ /** What a store-form refusal carries beside its sentence. The ONE host path lives here and never in the
277
+ * sentence; `form` is present on {@link SCOPE_HOME_NOT_DIRECTORY} only. */
278
+ export type StoreFormRefusalDetail = {
279
+ readonly path: string;
280
+ readonly form?: ScopeHomeForm;
281
+ };
282
+ /** A refusal the store's physical form earns: a plain `Error` (never the control-plane-damage class —
283
+ * the engine's own account is intact, the layout it was handed is not the layout it needs) carrying a
284
+ * `code` and a `detail`. */
285
+ export type StoreFormRefusal = Error & {
286
+ code: typeof TARGET_OUTSIDE_STORE | typeof SCOPE_HOME_NOT_DIRECTORY;
287
+ detail: StoreFormRefusalDetail;
288
+ };
289
+ /** Seat 1 — an act the store's physical form refuses: `act` names what was refused (no path), `path` is the
290
+ * target whose directory resolves outside the store ({@link landsOutsideStore}). */
291
+ export declare function storeActRefusal(act: string, path: string): StoreFormRefusal;
292
+ /** Seat 2 — the store root now resolves to another directory than the one the `holder` first opened; `path` is
293
+ * the root's spelling. */
294
+ export declare function storeRootMovedRefusal(holder: "backend" | "engine", path: string): StoreFormRefusal;
295
+ /** Seat 3 — a scope home that is not a real directory. */
296
+ export declare function scopeHomeRefusal(form: ScopeHomeForm, path: string): StoreFormRefusal;
264
297
  /** Where an act at `p` lands: the physical path of its normalized spelling (the spelling a child path
265
298
  * joined under it carries), a MISSING tail rejoined under the deepest existing directory (what the act
266
299
  * creates). Any other resolution fault (EACCES, ELOOP, ENOTDIR) is THROWN as itself: an act that
@@ -268,8 +301,8 @@ export declare const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
268
301
  * (a link whose own mode refuses `realpath` is still followed by `unlink` and `rename`). */
269
302
  export declare function landingPathOf(p: string): string;
270
303
  /**
271
- * Where a write, rename or delete at `target` would physically LAND, judged against the store's
272
- * `physicalRoot`: `undefined` = inside, otherwise the reason. The target's directory, resolved through
304
+ * Whether a write, rename or delete at `target` would physically LAND outside the store, judged against the
305
+ * store's `physicalRoot` (`true` = outside; the refusal is {@link storeActRefusal}). The target's directory, resolved through
273
306
  * every link on its existing prefix (a missing tail is what the act creates, under the deepest existing
274
307
  * directory), must be the root or inside it. `physicalRoot` is the root's physical form as the store
275
308
  * was opened and is not re-resolved here: a store mounted through a link is judged by what the link
@@ -280,7 +313,7 @@ export declare function landingPathOf(p: string): string;
280
313
  * keeps the spelling while the act follows the link out. A directory that cannot be resolved throws its
281
314
  * fault ({@link landingPathOf}).
282
315
  */
283
- export declare function storeTargetEscape(physicalRoot: string, target: string): string | undefined;
316
+ export declare function landsOutsideStore(physicalRoot: string, target: string): boolean;
284
317
  /** mkdir -p convenience used by the engine/backend (0o700 like the file stores). */
285
318
  export declare function ensureDirExists(dir: string): void;
286
319
  /** The one remedy every surfaced permission refusal carries — `undefined` unless `err` is a write
@@ -472,11 +472,18 @@ function assertHomePhysicalForm(home) {
472
472
  return "absent";
473
473
  if (st.isDirectory())
474
474
  return "dir";
475
- throw new ControlPlaneCorruptError(`scope home ${home} is ${st.isSymbolicLink() ? "a symbolic link" : "not a directory"} — a scope home must be a real directory (fail-closed)`);
475
+ throw scopeHomeRefusal(st.isSymbolicLink() ? "symlink" : st.isFile() ? "file" : "other", home);
476
476
  }
477
477
  function ensureScopeHome(home) {
478
- if (assertHomePhysicalForm(home) === "absent")
479
- mkdirSync(home, { recursive: true });
478
+ if (assertHomePhysicalForm(home) === "absent") {
479
+ try {
480
+ mkdirSync(home, { recursive: true });
481
+ }
482
+ catch (err) {
483
+ assertHomePhysicalForm(home);
484
+ throw err;
485
+ }
486
+ }
480
487
  assertHomePhysicalForm(home);
481
488
  }
482
489
  export function registerScope(memoryDir, controlDir, scope, opts) {
@@ -725,6 +732,28 @@ export function isContainedIn(root, child) {
725
732
  return c === r || c.startsWith(`${r}${sep}`);
726
733
  }
727
734
  export const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
735
+ export const SCOPE_HOME_NOT_DIRECTORY = "memory.scope_home_not_directory";
736
+ export const SCOPE_HOME_FORMS = Object.freeze(["symlink", "file", "other"]);
737
+ const SCOPE_HOME_FORM_PHRASE = {
738
+ symlink: "a symbolic link",
739
+ file: "a regular file",
740
+ other: "a special file",
741
+ };
742
+ function storeFormRefusal(code, sentence, detail) {
743
+ const e = new Error(`${code}: ${sentence}`);
744
+ e.code = code;
745
+ e.detail = detail;
746
+ return e;
747
+ }
748
+ export function storeActRefusal(act, path) {
749
+ return storeFormRefusal(TARGET_OUTSIDE_STORE, `${act} was refused — its directory resolves outside the store root; not performed`, { path });
750
+ }
751
+ export function storeRootMovedRefusal(holder, path) {
752
+ return storeFormRefusal(TARGET_OUTSIDE_STORE, `the ${holder === "backend" ? "store root" : "memory dir"} now resolves to a different directory than the one this ${holder} ${holder === "backend" ? "opened" : "first mounted"} — refused; open a new ${holder} on the directory you mean`, { path });
753
+ }
754
+ export function scopeHomeRefusal(form, path) {
755
+ return storeFormRefusal(SCOPE_HOME_NOT_DIRECTORY, `a scope home must be a real directory, but this one is ${SCOPE_HOME_FORM_PHRASE[form]} — refused (fail-closed); put the real directory back`, { path, form });
756
+ }
728
757
  export function landingPathOf(p) {
729
758
  const normalized = resolve(p);
730
759
  try {
@@ -736,9 +765,9 @@ export function landingPathOf(p) {
736
765
  return canonicalize(normalized);
737
766
  }
738
767
  }
739
- export function storeTargetEscape(physicalRoot, target) {
768
+ export function landsOutsideStore(physicalRoot, target) {
740
769
  const dir = landingPathOf(dirname(target));
741
- return dir === physicalRoot || dir.startsWith(`${physicalRoot}${sep}`) ? undefined : `its directory resolves to ${dir}, outside the store root ${physicalRoot}`;
770
+ return dir !== physicalRoot && !dir.startsWith(`${physicalRoot}${sep}`);
742
771
  }
743
772
  export function ensureDirExists(dir) {
744
773
  if (!existsSync(dir))
@@ -33,7 +33,7 @@ import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
33
33
  import type { AutoModeDecider, AutoModeDenialTracker, DenialLimitFallbackFace } from "../auto-mode.js";
34
34
  import type { SettlementKind } from "../gate-outcome.js";
35
35
  import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
36
- import type { ActorAssertion, Checkpoint, CheckpointState, CheckpointStore, CheckpointToken, PendingSteerEntry, PlatformLimitReason, ReopenReason, ResourceLedger, ResourceLimitReason, ResumeOutcome, SerializedCheckpointState } from "../checkpoint-store.js";
36
+ import type { Checkpoint, CheckpointState, CheckpointStore, CheckpointToken, PendingSteerEntry, PlatformLimitReason, ReopenReason, ResourceLedger, ResourceLimitReason, ResumeOutcome, SerializedCheckpointState } from "../checkpoint-store.js";
37
37
  import type { ExecutionOutcomeRecordWord } from "../checkpoint-execution-record.js";
38
38
  import type { ClearedProjectionLedger, MicroCompactMachine, OccurrenceIndex } from "../context-edit.js";
39
39
  import type { HookInvocationIdentity, Hooks, OrgGateVerdict, PermissionDeniedPayload, PostToolBatchCall, PostToolBatchResult, ToolGateInput } from "../hooks.js";
@@ -2494,21 +2494,6 @@ export interface RunState {
2494
2494
  attachmentsInjected: number;
2495
2495
  };
2496
2496
  }
2497
- /** One accepted live steer's REPLAY identity, the twin of the parked
2498
- * queue's `samePendingSteerPayload`. `payload` is the exact string handed to the harness, so the text,
2499
- * the speaker envelope and the trust framing all fold into it; `trusted` is kept SEPARATELY from those
2500
- * framed bytes so a text that spells its own `<system-reminder>` wrapper can never read as the trusted
2501
- * arm's byte-equal twin. `actor` carries the attribution the ledger frame reports. */
2502
- export interface AcceptedSteerInput {
2503
- payload: string;
2504
- trusted: boolean;
2505
- /** The NORMALIZED priority the call delivered under (absent input ⇒ "next"),
2506
- * the replay identity's sixth axis: same id + same text at a DIFFERENT tier is not a replay —
2507
- * a `next` input re-sent as `now` answering "idempotent success" without the interrupt would be
2508
- * a disposition lie, so it refuses `duplicate_input_id` like any other identity mismatch. */
2509
- priority: SystemInjectionPriority;
2510
- actor?: ActorAssertion;
2511
- }
2512
2497
  /**
2513
2498
  * Config re-supplied to {@link Runner.resume}. A suspended task's tools / model / policy /
2514
2499
  * hooks cannot be reconstructed from a checkpoint token (the session stores neither tool implementations
@@ -40,6 +40,7 @@ import { resumeClaim } from "./resume-claim.js";
40
40
  import { streamSettleBackstop } from "./stream-settle-backstop.js";
41
41
  import { streamReap } from "./stream-reap.js";
42
42
  import { streamSteerVerb } from "./stream-steer-verb.js";
43
+ import { steeringError } from "./steer-admission.js";
43
44
  import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
44
45
  import { streamHaltVerbs } from "./stream-halt-verbs.js";
45
46
  import { streamModeVerb } from "./stream-mode-verb.js";
@@ -411,11 +412,6 @@ export class Runner {
411
412
  locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
412
413
  }).onRunRejected);
413
414
  void settled.then(() => acceptedSteerInputs.clear(), () => acceptedSteerInputs.clear());
414
- const steeringError = (msg, code = "steering.not_running") => {
415
- const e = new Error(`cannot steer: ${msg}`);
416
- e.code = code;
417
- return e;
418
- };
419
415
  const { reapSuspended } = streamReap({
420
416
  live, runner: this.depsSeat,
421
417
  locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Admission bookkeeping for HUMAN INPUTS accepted mid-run: the per-leg ordinal an
3
- * accepted input is stamped with, and the field-for-field "is this repeat the same instruction" test the
4
- * live steer verb applies under an already-used `inputId`. Two accepting arms share both — the stream's
5
- * `steer` verb and the run body — which is why the ordinal is keyed on the leg's harness in a module-level
6
- * WeakMap rather than held by either arm: one counter per leg, one ascending sequence across both.
2
+ * Admission bookkeeping for HUMAN INPUTS accepted mid-run: the per-leg ordinal an accepted input is stamped
3
+ * with, and the acceptance frame's once-only seat. Two accepting arms share the ordinal — the stream's `steer`
4
+ * verb and the run body — which is why it is keyed on the leg's harness in a module-level WeakMap rather than
5
+ * held by either arm: one counter per leg, one ascending sequence across both. Also the stream verbs' typed
6
+ * refusal. (Whether a repeat under a used `inputId` is the same instruction is steer-replay.ts's one judgement,
7
+ * shared with the parked queue.)
7
8
  *
8
9
  * Run-loop machinery (layer 1), not a driven lane: nothing here takes an Input or hands back a Result, and
9
10
  * nothing here reads host state. Named without the `run-` family prefix for that reason.
10
11
  */
11
- import type { AcceptedSteerInput } from "./contracts.js";
12
+ import type { SteerReplayRefusalDetail } from "../steer-replay.js";
12
13
  export declare function nextHumanInputSeq(key: object): number;
13
14
  /** The identity keys a published `queued` frame carried beyond its own `eventId` (`parentToolCallId`, `sourceTaskId`): one fact, stated
14
15
  * once by the frame's publisher and handed to whoever mints that input's other frames, so two frames of one input are never stamped
@@ -19,8 +20,6 @@ export type AcceptanceIdentity = {
19
20
  };
20
21
  export declare function armAcceptance(record: object, publish: () => AcceptanceIdentity): void;
21
22
  export declare function announceAcceptance(record: object): AcceptanceIdentity | undefined;
22
- /** Is a repeat under an already-accepted `inputId` the SAME
23
- * instruction (an idempotent retry) or a different one wearing a used key? Compared field for field,
24
- * exactly like the parked leg's replay check: the key alone is never evidence of a replay, and two
25
- * callers colliding on one id must not silently lose the second instruction. */
26
- export declare function sameAcceptedSteerInput(a: AcceptedSteerInput, b: AcceptedSteerInput): boolean;
23
+ /** The stream verbs' typed refusal: `cannot steer: <msg>`, `code` (`steering.not_running` by default), and the
24
+ * refusal's `detail` when the code carries one (`steering.duplicate_input_id` always does). */
25
+ export declare function steeringError(msg: string, code?: string, detail?: SteerReplayRefusalDetail): Error;
@@ -1,4 +1,3 @@
1
- import { steerPriorityOf } from "../task-notification.js";
2
1
  const humanInputOrdinals = new WeakMap();
3
2
  export function nextHumanInputSeq(key) {
4
3
  let box = humanInputOrdinals.get(key);
@@ -16,11 +15,10 @@ export function armAcceptance(record, publish) {
16
15
  export function announceAcceptance(record) {
17
16
  return acceptanceSeats.get(record)?.();
18
17
  }
19
- export function sameAcceptedSteerInput(a, b) {
20
- return (a.payload === b.payload &&
21
- a.trusted === b.trusted &&
22
- steerPriorityOf(a) === steerPriorityOf(b) &&
23
- a.actor?.id === b.actor?.id &&
24
- a.actor?.hostAsserted === b.actor?.hostAsserted &&
25
- a.actor?.issuer === b.actor?.issuer);
18
+ export function steeringError(msg, code = "steering.not_running", detail) {
19
+ const e = new Error(`cannot steer: ${msg}`);
20
+ e.code = code;
21
+ if (detail !== undefined)
22
+ e.detail = detail;
23
+ return e;
26
24
  }
@@ -1,6 +1,7 @@
1
1
  import type { PushQueue } from "../push-queue.js";
2
2
  import { type TaskEvent, type TaskSpec, type TaskStream } from "../types.js";
3
- import type { AcceptedSteerInput, LiveHandle, RunInternals, RunnerDepsSeat, TaskStreamLiveSeat } from "./contracts.js";
3
+ import type { LiveHandle, RunInternals, RunnerDepsSeat, TaskStreamLiveSeat } from "./contracts.js";
4
+ import { type SteerReplayRecord, type SteerReplayRefusalDetail } from "../steer-replay.js";
4
5
  export interface StreamSteerVerbInput {
5
6
  /** borrowed-readonly — the task spec (the hooks seat, `principal`, `taskId`). */
6
7
  spec: TaskSpec;
@@ -9,7 +10,7 @@ export interface StreamSteerVerbInput {
9
10
  /** borrowed-mutable — the stream's event queue: the `human_input` receipt is pushed here on acceptance. */
10
11
  queue: PushQueue<TaskEvent>;
11
12
  /** borrowed-mutable — this stream's accepted-input dedup domain (the SAME Map the driver clears when the run settles). */
12
- acceptedSteerInputs: Map<string, AcceptedSteerInput>;
13
+ acceptedSteerInputs: Map<string, SteerReplayRecord>;
13
14
  /** borrowed-readonly — the stream's live state: `resultValue` and `handle`, read at each liveness test. */
14
15
  live: TaskStreamLiveSeat;
15
16
  /** borrowed-readonly — the readiness promise (settles to the live handle, or `undefined` when the run ended first). */
@@ -18,8 +19,8 @@ export interface StreamSteerVerbInput {
18
19
  orTimeout: <T>(p: Promise<T>) => Promise<T | undefined>;
19
20
  /** borrowed-readonly — the readiness bound in ms; the birth-window poll's deadline reads it too. */
20
21
  readyTimeoutMs: number;
21
- /** borrowed-readonly — the driver's typed steering refusal (`steering.not_running` by default). */
22
- steeringError: (msg: string, code?: string) => Error;
22
+ /** borrowed-readonly — the driver's typed steering refusal (`steering.not_running` by default; `detail` rides the error). */
23
+ steeringError: (msg: string, code?: string, detail?: SteerReplayRefusalDetail) => Error;
23
24
  /** borrowed-readonly — the Runner's deployment deps, read LIVE (`hooks`, `onError`, `onNotice`, after awaits too). */
24
25
  runner: RunnerDepsSeat;
25
26
  }
@@ -6,7 +6,8 @@ import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "
6
6
  import { isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
7
7
  import { deliverEngineNotice } from "../types.js";
8
8
  import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
9
- import { announceAcceptance, armAcceptance, nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
9
+ import { announceAcceptance, armAcceptance, nextHumanInputSeq } from "./steer-admission.js";
10
+ import { steerReplayRefusal } from "../steer-replay.js";
10
11
  import { asThrownError } from "../thrown-value.js";
11
12
  export function streamSteerVerb(input) {
12
13
  const { spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner } = input;
@@ -43,7 +44,6 @@ export function streamSteerVerb(input) {
43
44
  };
44
45
  let payload;
45
46
  let mintsAFrame;
46
- let replay;
47
47
  const publishQueued = (h) => {
48
48
  const identity = internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) } : {};
49
49
  queue.push({
@@ -66,7 +66,7 @@ export function streamSteerVerb(input) {
66
66
  if (!mintsAFrame)
67
67
  return;
68
68
  if (typeof inputId === "string")
69
- acceptedSteerInputs.set(inputId, replay);
69
+ acceptedSteerInputs.set(inputId, parkRecord);
70
70
  announceAcceptance(parkRecord);
71
71
  };
72
72
  const deliver = async () => {
@@ -77,16 +77,14 @@ export function streamSteerVerb(input) {
77
77
  throw steeringError("the task is not running");
78
78
  payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected);
79
79
  mintsAFrame = payload.trim().length !== 0;
80
- replay = { payload, trusted, priority, ...(actor !== undefined ? { actor } : {}) };
81
80
  if (typeof inputId === "string") {
82
81
  const prior = acceptedSteerInputs.get(inputId);
83
82
  if (prior !== undefined) {
84
83
  if (live.resultValue !== undefined || h.loop.ended)
85
84
  throw steeringError("the task is no longer running");
86
- if (!sameAcceptedSteerInput(prior, replay)) {
87
- throw steeringError("a different steering instruction was already accepted under this inputId — re-issue this one with a fresh inputId " +
88
- "(an identical payload would have been an idempotent retry)", "steering.duplicate_input_id");
89
- }
85
+ const refusal = steerReplayRefusal(prior, parkRecord, { seat: "accepted" });
86
+ if (refusal !== undefined)
87
+ throw steeringError(refusal.message, "steering.duplicate_input_id", refusal.detail);
90
88
  return;
91
89
  }
92
90
  }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Is a repeat under an already-used steer `inputId` the same instruction? ONE answer for both seats that hold a
3
+ * steer under its key — the parked queue (`appendPendingSteer`) and the live verb's accepted-input domain — and that
4
+ * one answer feeds the replay decision, the refusal's machine-readable `detail.differs` and its sentence.
5
+ *
6
+ * Both seats compare the record the CALLER sent (text, trusted, tier, actor) — never the delivered bytes: the live
7
+ * delivery folds `trusted` (the framing) and the actor (the speaker label) into its payload, so comparing payloads
8
+ * would report one changed facet as two.
9
+ */
10
+ import type { ActorAssertion } from "../internal/llm.js";
11
+ import { type SystemInjectionPriority } from "./task-notification.js";
12
+ /** The facets a steer's replay identity is made of, in the order a refusal lists them. Closed. */
13
+ export declare const STEER_REPLAY_FACETS: readonly ["text", "trusted", "priority", "actor"];
14
+ export type SteerReplayFacet = (typeof STEER_REPLAY_FACETS)[number];
15
+ /** What a seat remembers a steer by: the caller's own statement, as validated. The parked entry and the live park
16
+ * record both are this shape. An absent `priority` reads as `"next"` ({@link steerPriorityOf}). */
17
+ export interface SteerReplayRecord {
18
+ readonly text: string;
19
+ readonly trusted: boolean;
20
+ readonly priority?: SystemInjectionPriority;
21
+ readonly actor?: ActorAssertion;
22
+ }
23
+ /** The facets `repeat` differs from `held` in, in {@link STEER_REPLAY_FACETS} order. Empty = the idempotent replay. */
24
+ export declare function steerReplayDiffers(held: SteerReplayRecord, repeat: SteerReplayRecord): SteerReplayFacet[];
25
+ /** `steering.duplicate_input_id`'s machine-readable half: the facets, by name only (never the values). Always
26
+ * present on that code, never empty. */
27
+ export interface SteerReplayRefusalDetail {
28
+ readonly differs: readonly SteerReplayFacet[];
29
+ }
30
+ /** Which seat refuses: the parked queue names the key it holds; the live verb answers about the caller's own key. */
31
+ export type SteerReplaySeat = {
32
+ readonly seat: "parked";
33
+ readonly inputId: string;
34
+ } | {
35
+ readonly seat: "accepted";
36
+ };
37
+ /**
38
+ * The refusal for a repeat that is not a replay, or `undefined` for one that is. When `text` differs the repeat is
39
+ * another instruction and the caller is told to take a fresh key. When only the tier / trust / attribution differ it
40
+ * is the SAME instruction, already accepted, whose standing receipt those facets do not change — a fresh key would
41
+ * queue it a second time, so the sentence says so instead of advising one.
42
+ */
43
+ export declare function steerReplayRefusal(held: SteerReplayRecord, repeat: SteerReplayRecord, where: SteerReplaySeat): {
44
+ message: string;
45
+ detail: SteerReplayRefusalDetail;
46
+ } | undefined;
@@ -0,0 +1,22 @@
1
+ import { steerPriorityOf } from "./task-notification.js";
2
+ export const STEER_REPLAY_FACETS = Object.freeze(["text", "trusted", "priority", "actor"]);
3
+ const FACET_IS_SAME = {
4
+ text: (a, b) => a.text === b.text,
5
+ trusted: (a, b) => a.trusted === b.trusted,
6
+ priority: (a, b) => steerPriorityOf(a) === steerPriorityOf(b),
7
+ actor: (a, b) => a.actor?.id === b.actor?.id && a.actor?.hostAsserted === b.actor?.hostAsserted && a.actor?.issuer === b.actor?.issuer,
8
+ };
9
+ export function steerReplayDiffers(held, repeat) {
10
+ return STEER_REPLAY_FACETS.filter((facet) => !FACET_IS_SAME[facet](held, repeat));
11
+ }
12
+ export function steerReplayRefusal(held, repeat, where) {
13
+ const differs = steerReplayDiffers(held, repeat);
14
+ if (differs.length === 0)
15
+ return undefined;
16
+ const seat = where.seat === "parked" ? `parked under inputId "${where.inputId}"` : "accepted under this inputId";
17
+ const listed = differs.join(", ");
18
+ const message = differs.includes("text")
19
+ ? `a different steering instruction is already ${seat} (this repeat differs in: ${listed}) — re-issue this one with a fresh inputId (an identical payload would have been an idempotent retry)`
20
+ : `the same steering instruction is already ${seat}; this repeat differs only in: ${listed}, which cannot change after acceptance — sending it again under another inputId would queue it a second time; to confirm the standing receipt, replay it with its original values`;
21
+ return { message, detail: { differs } };
22
+ }
@@ -94,8 +94,9 @@ export type BrainStatusPhase = "rate_limited" | "retrying" | "reconnecting" | "c
94
94
  * {@link BrainStatus.errorStatus}; this bucket is the reason a consumer RENDERS and the only carrier for the
95
95
  * transport classes, which have no status at all.) Values are about the SHAPE of the failure, never its
96
96
  * provider taxonomy:
97
- * - `connect_refused` — the attempt got a definite negative about the target itself (nothing accepts
98
- * at that address, or the name has no address). This is the class the SHORT retry lane serves.
97
+ * - `connect_refused` — the attempt got a definite negative about the way to the target (nothing accepts
98
+ * at that address, the name has no address, or the proxy on the way refused the tunnel). This is the class
99
+ * the SHORT retry lane serves.
99
100
  * - `transport` — any other transport-level failure: a connect timeout, a reset, a mid-stream
100
101
  * tear. No verdict about the target; the full ladder applies.
101
102
  * - `stall` — no failure was observed on the link at all: THIS process cancelled its own
@@ -43,15 +43,20 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
43
43
  * that arrives while the task is LIVE was the only one that injected twice.
44
44
  * - **Absent ⇒ nothing changes**: a uuidv7 is minted for the event, nothing is recorded for the call,
45
45
  * and the delivered bytes are what they always were. Every pre-existing caller is on this arm.
46
- * - **Replay ⇒ idempotent no-op**: re-steering an id this stream already ACCEPTED, with an identical
47
- * payload (same text, same `trusted`, same `actor`), injects nothing and emits no second
46
+ * - **Replay ⇒ idempotent no-op**: re-steering an id this stream already ACCEPTED, with the same
47
+ * steer (same text, `trusted`, tier — absent reads `next` — and `actor`), injects nothing and emits no second
48
48
  * `human_input` frame. What is remembered is exactly what QUEUED: a delivery that was refused
49
49
  * (not running) can be retried under its own id, and a call whose DELIVERED payload is
50
50
  * whitespace-only — which the harness discards without minting a frame; note a `trusted` steer is
51
51
  * wrapped first, so it queues even for blank text — reserves nothing, leaving that id usable.
52
- * - **Same id, DIFFERENT instruction ⇒ typed throw** `steering.duplicate_input_id`: a key is not
52
+ * - **Same id, not the same steer ⇒ typed throw** `steering.duplicate_input_id`: a key is not
53
53
  * evidence of a replay, and two callers colliding on one id must not silently lose the second
54
- * (the parked leg's `appendPendingSteer` refuses it identically). Re-issue under a fresh id.
54
+ * (the parked leg's `appendPendingSteer` judges and refuses it identically). The error carries
55
+ * `detail.differs` — the facets that differ (closed `STEER_REPLAY_FACETS`: `text`, `trusted`,
56
+ * `priority`, `actor`), compared on what the caller SENT, never on the framed bytes. With `text`
57
+ * among them it is another instruction: re-issue it under a fresh id. Without, it is the accepted one
58
+ * re-sent at another tier / trust / attribution, which acceptance does not change: a fresh id would
59
+ * queue it twice, so replay it with its original values to confirm the receipt.
55
60
  * - **Bad value ⇒ typed throw** `steering.invalid_content`, never a silent fallback to "no id": the
56
61
  * value domain is the parked leg's (a non-empty string of at most `MAX_STEER_INPUT_ID_CHARS`
57
62
  * characters), so one key is accepted or
@@ -107,8 +112,9 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
107
112
  * relaying steer must gate this option at its own face — relay IS authorization.
108
113
  * An unknown value throws typed `steering.invalid_content` (bad-value loudness). The replay
109
114
  * identity (`inputId` idempotency) includes the NORMALIZED tier: the same text replayed under
110
- * the same id at a different tier refuses `steering.duplicate_input_id` — an "idempotent
111
- * success" that silently skipped the interrupt would be a disposition lie.
115
+ * the same id at a different tier refuses `steering.duplicate_input_id` (`detail.differs:
116
+ * ["priority"]`) — an "idempotent success" that silently skipped the interrupt would be a
117
+ * disposition lie.
112
118
  *
113
119
  * design/373 §4.3 (D2, ruling of 2026-08-24) — a deployment `userPromptSubmit` hook SCREENS this verb:
114
120
  * the steer face is a SERVICE entrance (third-party callers reach a running run through it), so
@@ -123,8 +123,8 @@ export type WorkerErrorClass = "budget" | "limit" | "output" | "suspend" | "revi
123
123
  * The `limits.` namespace splits into the `budget` and `limit` classes by exact code — see
124
124
  * {@link EXACT_CODE_CLASS}. Operation-level dotted codes that are surfaced to a *caller* and never become a
125
125
  * task outcome are deliberately OUT of scope: `steering.*` (`steering.not_running`/`steering.invalid_content`/
126
- * `steering.duplicate_input_id`/`steering.blocked_by_hook`, rejected to the `steer()` caller — the fourth also
127
- * rides the resume face as a `CheckpointError` code) and `mcp.*` (`mcp.server_unavailable`, an `onWarn` warning code) — neither
126
+ * `steering.duplicate_input_id`/`steering.blocked_by_hook`, rejected to the `steer()` caller — the third also
127
+ * rides the parked queue's `setPendingSteer` face and the fourth the resume face, as `CheckpointError` codes) and `mcp.*` (`mcp.server_unavailable`, an `onWarn` warning code) — neither
128
128
  * reaches `TaskResult.errorCode`, so a caller will never pass them here. A genuinely unmapped code → `"unknown"` (which therefore means
129
129
  * "known-but-foreign or no code", e.g. a leaked fs/Node code, NOT "an OUR terminal class we forgot to add").
130
130
  */
@@ -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, UserSteerUndrainedCause } from "./engine-notice.js";
33
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
32
+ export type { EngineNotice, OwedUserInputAccount, UserInputUndrainedCause } from "./engine-notice.js";
33
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_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, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
4
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES } from "./engine-notice.js";
package/dist/index.d.ts CHANGED
@@ -42,7 +42,7 @@ export { StdioLspTransport, type LspChildProcess } from "./engine/lsp/stdio-lsp-
42
42
  export { encodeFrame, createFrameDecoder } from "./engine/lsp/frame-decoder.js";
43
43
  export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, MODEL_ROLES, type ModelRole, type RoleSpec, type ModelRoles, type ResolvedRole, type ModelMention, } from "./core/roles.js";
44
44
  export { selectModel, selectModelOrThrow, type ModelCriteria, type ModelTier } from "./core/select-model.js";
45
- export { BrainError, classifyHttp, isProviderEchoShape, PROVIDER_ECHO_SHAPE, type BrainErrorCode } from "./brain/errors.js";
45
+ export { BrainError, classifyHttp, isProviderEchoShape, PROVIDER_ECHO_SHAPE, PROXY_TUNNEL_REFUSED, type BrainErrorCode } from "./brain/errors.js";
46
46
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
47
47
  export type { RepetitionEvent, RepetitionInspection } from "./brain/repetition.js";
48
48
  export { computeCostMicroUsd, isModelPriced, modelCostToPricing, type ModelPricing, type TokenCounts, } from "./core/pricing.js";
@@ -63,7 +63,8 @@ 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";
66
+ export { USER_INPUT_UNDRAINED_CAUSES, type UserInputUndrainedCause } from "./core/engine-notice.js";
67
+ export { STEER_REPLAY_FACETS, type SteerReplayFacet } from "./core/steer-replay.js";
67
68
  export { TOOL_TURN_INCOMPLETE_CAUSES, type ToolTurnIncompleteCause } from "./internal/llm.js";
68
69
  export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
69
70
  export { TERMINAL_CAUSE_IS_REPLAYABLE, type TerminalFailureDetail, type TerminalBlockedDetail, PROMPT_BLOCK_CAUSES, type PromptBlockCause, isPromptBlockCause } from "./core/terminal-cause.js";