@gajae-code/coding-agent 0.12.21 → 0.13.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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -387,7 +387,7 @@ function removeResidentCacheTreeNoFollow(pathname: string): void {
387
387
  fs.unlinkSync(pathname);
388
388
  }
389
389
 
390
- function disposeVerifiedResidentCacheInstanceDir(instanceDir: string): void {
390
+ export function disposeVerifiedResidentCacheInstanceDir(instanceDir: string): void {
391
391
  const instanceKey = path.resolve(instanceDir);
392
392
  const uid = residentCacheOwnerUid(instanceDir);
393
393
  const quarantineDir = path.join(
@@ -444,7 +444,7 @@ function removePartialResidentCacheInstanceDir(instanceDir: string): void {
444
444
  }
445
445
 
446
446
  function isResidentCacheInstanceDirName(name: string): boolean {
447
- return /^i-[A-Za-z0-9_-]+$/.test(name);
447
+ return /^(?:i-[A-Za-z0-9_-]+|s-[a-f0-9]{32})$/.test(name);
448
448
  }
449
449
 
450
450
  function residentCacheSweepLimit(value: number | undefined, ceiling: number): number {
@@ -591,7 +591,7 @@ function scheduleResidentCacheRootSweep(root: string): void {
591
591
  * directories; callers must use {@link EphemeralBlobStore.adoptVerifiedDir}
592
592
  * rather than the destructive EphemeralBlobStore constructor.
593
593
  */
594
- export function openVerifiedResidentCacheInstanceDir(root: string): string {
594
+ function openVerifiedCacheInstanceDir(root: string, instanceName?: string): string {
595
595
  const uid = residentCacheOwnerUid(root);
596
596
  let rootDescriptor: number | null = null;
597
597
  let instanceDir: string | null = null;
@@ -601,14 +601,32 @@ export function openVerifiedResidentCacheInstanceDir(root: string): string {
601
601
  } catch (error) {
602
602
  throw residentCacheTrustError("root_create_failed", root, error);
603
603
  }
604
-
605
604
  rootDescriptor = openVerifiedResidentCacheDirectory(root, uid);
606
605
  try {
607
- instanceDir = fs.mkdtempSync(path.join(root, "i-"));
606
+ if (instanceName === undefined) instanceDir = fs.mkdtempSync(path.join(root, "i-"));
607
+ else {
608
+ if (!/^s-[a-f0-9]{32}$/.test(instanceName))
609
+ throw new ResidentCacheTrustError("instance_name_invalid", instanceName);
610
+ const candidate = path.join(root, instanceName);
611
+ try {
612
+ fs.mkdirSync(candidate, { mode: BLOB_DIR_MODE });
613
+ } catch (error) {
614
+ if (errorCode(error) !== "EEXIST") throw error;
615
+ const staleInstance = readResidentCacheOwnerSnapshot(candidate, uid);
616
+ if (
617
+ staleInstance === null ||
618
+ !residentCacheOwnerIsStale(staleInstance.owner, new Map<number, number | null>()) ||
619
+ !reapResidentCacheInstanceDir(root, rootDescriptor, candidate, staleInstance, uid)
620
+ ) {
621
+ throw error;
622
+ }
623
+ fs.mkdirSync(candidate, { mode: BLOB_DIR_MODE });
624
+ }
625
+ instanceDir = candidate;
626
+ }
608
627
  } catch (error) {
609
628
  throw residentCacheTrustError("instance_create_failed", root, error);
610
629
  }
611
-
612
630
  // Keep the root descriptor open while creating the nonce directory, then
613
631
  // re-check the path against that descriptor before trusting the child.
614
632
  assertResidentCacheDirectoryPathMatchesDescriptor(root, rootDescriptor, uid);
@@ -637,6 +655,15 @@ export function openVerifiedResidentCacheInstanceDir(root: string): string {
637
655
  }
638
656
  }
639
657
 
658
+ export function openVerifiedResidentCacheInstanceDir(root: string): string {
659
+ return openVerifiedCacheInstanceDir(root);
660
+ }
661
+
662
+ /** Open the deterministic per-session managed-sidecar instance in its separate cache root. */
663
+ export function openVerifiedSidecarCacheInstanceDir(root: string, sessionHash: string): string {
664
+ return openVerifiedCacheInstanceDir(root, `s-${sessionHash}`);
665
+ }
666
+
640
667
  function sha256Hex(data: Buffer): string {
641
668
  return new Bun.SHA256().update(data).digest("hex");
642
669
  }
@@ -1539,6 +1566,8 @@ export interface CanonicalBlobEntry {
1539
1566
  readonly path: string;
1540
1567
  readonly bytes: number;
1541
1568
  readonly mtimeMs: number;
1569
+ readonly dev: number;
1570
+ readonly ino: number;
1542
1571
  }
1543
1572
 
1544
1573
  const CANONICAL_BLOB_NAME = /^[0-9a-f]{64}$/;
@@ -1577,7 +1606,14 @@ export async function listCanonicalBlobs(dir: string): Promise<CanonicalBlobEntr
1577
1606
  continue;
1578
1607
  }
1579
1608
  if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) continue;
1580
- entries.push({ hash: name, path: blobPath, bytes: stat.size, mtimeMs: stat.mtimeMs });
1609
+ entries.push({
1610
+ hash: name,
1611
+ path: blobPath,
1612
+ bytes: stat.size,
1613
+ mtimeMs: stat.mtimeMs,
1614
+ dev: stat.dev,
1615
+ ino: stat.ino,
1616
+ });
1581
1617
  }
1582
1618
  return entries;
1583
1619
  }
@@ -1587,11 +1623,17 @@ export async function listCanonicalBlobs(dir: string): Promise<CanonicalBlobEntr
1587
1623
  * at scan time must still describe the file at unlink time (same inode, size and
1588
1624
  * mtime, still a single-link regular file), otherwise the blob is left in place.
1589
1625
  *
1626
+ * `beforeUnlink` runs after this function has verified the blob and immediately
1627
+ * before it calls unlink. Callers use it to bind external liveness evidence to
1628
+ * the verified blob identity, so a live reference that appears while the blob is
1629
+ * being revalidated prevents the destructive syscall.
1630
+ *
1590
1631
  * `failed` distinguishes an actual IO failure (which a caller should surface as
1591
1632
  * a failed reclaim) from a revalidation refusal (which is an ordinary KEEP).
1592
1633
  */
1593
1634
  export async function removeCanonicalBlob(
1594
1635
  entry: CanonicalBlobEntry,
1636
+ options: { beforeUnlink?: () => Promise<boolean> } = {},
1595
1637
  ): Promise<{ removed: true } | { removed: false; reason: string; failed?: true }> {
1596
1638
  let stat: fs.Stats;
1597
1639
  try {
@@ -1603,7 +1645,17 @@ export async function removeCanonicalBlob(
1603
1645
  if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
1604
1646
  return { removed: false, reason: "blob_not_plain_file" };
1605
1647
  }
1606
- if (stat.size !== entry.bytes || stat.mtimeMs !== entry.mtimeMs) return { removed: false, reason: "blob_changed" };
1648
+ if (
1649
+ stat.dev !== entry.dev ||
1650
+ stat.ino !== entry.ino ||
1651
+ stat.size !== entry.bytes ||
1652
+ stat.mtimeMs !== entry.mtimeMs
1653
+ ) {
1654
+ return { removed: false, reason: "blob_changed" };
1655
+ }
1656
+ if (options.beforeUnlink && !(await options.beforeUnlink())) {
1657
+ return { removed: false, reason: "blob_reference_evidence_changed" };
1658
+ }
1607
1659
  try {
1608
1660
  await fsp.unlink(entry.path);
1609
1661
  return { removed: true };
@@ -17,6 +17,17 @@ export interface FallbackFailure {
17
17
 
18
18
  export type FallbackFailureResult = "retry" | "advance" | "exhausted";
19
19
 
20
+ export interface FallbackChainRuntimeState {
21
+ activeIndex: number;
22
+ attemptsUsed: number;
23
+ totalAttemptsUsed: number;
24
+ attemptStarted: boolean;
25
+ restoredEntryIndices: number[];
26
+ tried: FallbackFailure[];
27
+ skips: Array<{ selector: string; reason: string }>;
28
+ exhaustedForTurn: boolean;
29
+ }
30
+
20
31
  /**
21
32
  * In-memory policy state for one fallback-chain scope. A controller is deliberately
22
33
  * not serializable: configured chain intent is durable, current position is not.
@@ -43,6 +54,30 @@ export class FallbackChainController {
43
54
  if (this.chain.entries.length === 0) this.exhaustedForTurn = true;
44
55
  }
45
56
 
57
+ snapshotRuntimeState(): FallbackChainRuntimeState {
58
+ return {
59
+ activeIndex: this.activeIndex,
60
+ attemptsUsed: this.attemptsUsed,
61
+ totalAttemptsUsed: this.#totalAttemptsUsed,
62
+ attemptStarted: this.#attemptStarted,
63
+ restoredEntryIndices: [...this.#restoredEntryIndices],
64
+ tried: this.tried.map(failure => ({ ...failure })),
65
+ skips: this.skips.map(skip => ({ ...skip })),
66
+ exhaustedForTurn: this.exhaustedForTurn,
67
+ };
68
+ }
69
+
70
+ restoreRuntimeState(state: FallbackChainRuntimeState): void {
71
+ this.activeIndex = state.activeIndex;
72
+ this.attemptsUsed = state.attemptsUsed;
73
+ this.#totalAttemptsUsed = state.totalAttemptsUsed;
74
+ this.#attemptStarted = state.attemptStarted;
75
+ this.#restoredEntryIndices = new Set(state.restoredEntryIndices);
76
+ this.tried = state.tried.map(failure => ({ ...failure }));
77
+ this.skips = state.skips.map(skip => ({ ...skip }));
78
+ this.exhaustedForTurn = state.exhaustedForTurn;
79
+ }
80
+
46
81
  get totalAttemptsUsed(): number {
47
82
  return this.#totalAttemptsUsed;
48
83
  }
@@ -51,6 +51,7 @@ import {
51
51
  prepareManagedDirectoryRoot,
52
52
  publishManagedFileNoReplace,
53
53
  publishManagedTombstone,
54
+ reapScrubbedProtocolRemnantsSync,
54
55
  retainManagedDirectoryAuthority,
55
56
  validateManagedArtifactTree,
56
57
  validateNativeSecurityResult,
@@ -193,8 +194,27 @@ export type ManagedScopeResolution =
193
194
  cause?: { readonly classification: string; readonly diagnostic?: string };
194
195
  };
195
196
 
197
+ /**
198
+ * Classify a failure for the operator-visible `cause`.
199
+ *
200
+ * Must agree with {@link managedScopeErrorCode}: the reported `code` and the
201
+ * `cause.classification` printed in the startup error are produced by separate
202
+ * helpers, so a message recognized by one and not the other still surfaces to
203
+ * the operator as `binding_invalid`.
204
+ */
196
205
  function managedScopeFailureCause(error: unknown): { readonly classification: string } {
197
- return { classification: managedSecurityFailureClassification(error) ?? "binding_invalid" };
206
+ const security = managedSecurityFailureClassification(error);
207
+ if (security) return { classification: security };
208
+ return { classification: managedScopeErrorCode(error instanceof Error ? error.message : "") };
209
+ }
210
+
211
+ /**
212
+ * A scope whose directory outgrew a scan budget. The binding is untouched and
213
+ * canonical; only the surrounding entry count is over budget, so this must not
214
+ * be reported as binding corruption.
215
+ */
216
+ function isManagedScopeCapacityMessage(message: string): boolean {
217
+ return message === "artifact_capacity_exceeded" || message === "managed_replace_cleanup_receipt_limit_exceeded";
198
218
  }
199
219
 
200
220
  const managedScopeFailureCodes = new Set<ManagedScopeErrorCode>([
@@ -214,7 +234,7 @@ const managedScopeFailureCodes = new Set<ManagedScopeErrorCode>([
214
234
  * corrupt binding, which sends operators to delete a healthy binding file.
215
235
  */
216
236
  function managedScopeErrorCode(message: string): ManagedScopeErrorCode {
217
- if (message === "content_too_large") return "capacity_exceeded";
237
+ if (message === "content_too_large" || isManagedScopeCapacityMessage(message)) return "capacity_exceeded";
218
238
  return managedScopeFailureCodes.has(message as ManagedScopeErrorCode)
219
239
  ? (message as ManagedScopeErrorCode)
220
240
  : "binding_invalid";
@@ -224,7 +244,7 @@ function managedScopeFailureMessage(error: unknown, fallback: string): string {
224
244
  const classification = managedSecurityFailureClassification(error);
225
245
  if (classification) return classification;
226
246
  if (!(error instanceof Error)) return fallback;
227
- if (error.message === "content_too_large") return error.message;
247
+ if (error.message === "content_too_large" || isManagedScopeCapacityMessage(error.message)) return error.message;
228
248
  return managedScopeFailureCodes.has(error.message as ManagedScopeErrorCode) ? error.message : fallback;
229
249
  }
230
250
 
@@ -1133,6 +1153,7 @@ export function prepareManagedSessionScopeForWriteSync(
1133
1153
  ensureManagedDirectory(path.join(internal, MANAGED_RECEIPTS_DIRECTORY), root, policy);
1134
1154
  stage = "tombstones_directory";
1135
1155
  ensureManagedDirectory(path.join(internal, MANAGED_TOMBSTONES_DIRECTORY), root, policy);
1156
+ reapScrubbedProtocolRemnantsSync(scope.directoryPath);
1136
1157
  return { kind: "resolved", scope };
1137
1158
  } catch (error) {
1138
1159
  const publication = error instanceof ManagedPublishError ? error : undefined;
@@ -3713,6 +3734,7 @@ export async function prepareManagedSessionScopeForWrite(
3713
3734
  ensureManagedDirectory(path.join(internal, MANAGED_RECEIPTS_DIRECTORY), root, policy);
3714
3735
  ensureManagedDirectory(path.join(internal, MANAGED_TOMBSTONES_DIRECTORY), root, policy);
3715
3736
  await reconcileManagedTombstones(scope, expectedCandidate);
3737
+ reapScrubbedProtocolRemnantsSync(scope.directoryPath);
3716
3738
  return { kind: "resolved", scope };
3717
3739
  } catch (error) {
3718
3740
  const publication = error instanceof ManagedPublishError ? error : undefined;