@gajae-code/coding-agent 0.18.11 → 0.18.12

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 (270) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/cli/accounts-cli.d.ts +2 -2
  7. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  8. package/dist/types/cli/install-activation.d.ts +10 -4
  9. package/dist/types/cli/public-command-errors.d.ts +1 -0
  10. package/dist/types/cli/update-cli.d.ts +14 -1
  11. package/dist/types/cli-ordinary.d.ts +2 -2
  12. package/dist/types/commands/accounts.d.ts +2 -1
  13. package/dist/types/commands/migrate.d.ts +1 -1
  14. package/dist/types/commands/sdk.d.ts +5 -0
  15. package/dist/types/commit/changelog/index.d.ts +1 -0
  16. package/dist/types/config/autorouting-tier-map.d.ts +30 -0
  17. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  18. package/dist/types/config/config-hot-reload.d.ts +39 -0
  19. package/dist/types/config/file-lock.d.ts +8 -1
  20. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  21. package/dist/types/config/model-profile-activation.d.ts +62 -6
  22. package/dist/types/config/model-registry.d.ts +56 -5
  23. package/dist/types/config/model-resolver.d.ts +2 -0
  24. package/dist/types/config/models-config-schema.d.ts +10 -0
  25. package/dist/types/config/settings-schema.d.ts +46 -3
  26. package/dist/types/config/settings.d.ts +33 -1
  27. package/dist/types/dap/config.d.ts +8 -4
  28. package/dist/types/debug/system-info.d.ts +6 -1
  29. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  30. package/dist/types/edit/modes/patch.d.ts +5 -0
  31. package/dist/types/edit/modes/replace.d.ts +3 -0
  32. package/dist/types/exec/bash-executor.d.ts +12 -0
  33. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  35. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  36. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  37. package/dist/types/migrate/adapters/index.d.ts +10 -1
  38. package/dist/types/migrate/executor.d.ts +3 -1
  39. package/dist/types/migrate/types.d.ts +15 -3
  40. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  41. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  42. package/dist/types/modes/theme/theme.d.ts +1 -0
  43. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  44. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  45. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  46. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  47. package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
  48. package/dist/types/sdk/broker/broker.d.ts +7 -1
  49. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  50. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  51. package/dist/types/sdk/broker/ensure.d.ts +136 -7
  52. package/dist/types/sdk/broker/hop.d.ts +36 -0
  53. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  54. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +4 -1
  56. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  57. package/dist/types/sdk/bus/index.d.ts +23 -0
  58. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  59. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  60. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  61. package/dist/types/sdk/host/session-runtime.d.ts +32 -0
  62. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  63. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  64. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  65. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  66. package/dist/types/sdk/session-list.d.ts +1 -1
  67. package/dist/types/session/agent-session.d.ts +58 -2
  68. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  69. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  70. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  71. package/dist/types/session/messages.d.ts +5 -1
  72. package/dist/types/session/session-manager.d.ts +13 -3
  73. package/dist/types/session/startup-auth-config.d.ts +24 -2
  74. package/dist/types/session/streaming-output.d.ts +11 -0
  75. package/dist/types/session/terminal-abort.d.ts +3 -1
  76. package/dist/types/session-import/service.d.ts +1 -1
  77. package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
  78. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  79. package/dist/types/task/executor.d.ts +19 -2
  80. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  81. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  82. package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
  83. package/dist/types/tools/descriptors.d.ts +2 -0
  84. package/dist/types/tools/fetch.d.ts +1 -1
  85. package/dist/types/tools/index.d.ts +2 -0
  86. package/dist/types/tools/python.d.ts +1 -1
  87. package/dist/types/tools/report-issue.d.ts +41 -0
  88. package/dist/types/tools/subagent.d.ts +23 -0
  89. package/dist/types/web/scrapers/types.d.ts +8 -1
  90. package/dist/types/web/search/providers/insane.d.ts +7 -1
  91. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  92. package/package.json +8 -8
  93. package/scripts/build-binary.ts +2 -1
  94. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  95. package/scripts/generate-docs-index.ts +8 -1
  96. package/scripts/generate-sdk-operation-inventory.ts +22 -4
  97. package/src/async/job-manager.ts +356 -56
  98. package/src/async/result-formatting.ts +40 -0
  99. package/src/autoresearch/git.ts +9 -2
  100. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  101. package/src/capability/fs.ts +27 -19
  102. package/src/capability/index.ts +2 -2
  103. package/src/cli/accounts-cli.ts +176 -8
  104. package/src/cli/auth-broker-cli.ts +21 -11
  105. package/src/cli/doctor/install-repairs.ts +35 -5
  106. package/src/cli/fast-help.ts +1 -1
  107. package/src/cli/install-activation.ts +47 -4
  108. package/src/cli/migrate-cli.ts +7 -5
  109. package/src/cli/public-command-errors.ts +4 -0
  110. package/src/cli/root-flags.ts +2 -2
  111. package/src/cli/stats-cli.ts +9 -5
  112. package/src/cli/update-cli.ts +224 -7
  113. package/src/cli-ordinary.ts +7 -2
  114. package/src/commands/accounts.ts +10 -5
  115. package/src/commands/migrate.ts +3 -3
  116. package/src/commands/sdk.ts +199 -42
  117. package/src/commands/ultragoal.ts +16 -8
  118. package/src/commit/changelog/index.ts +39 -1
  119. package/src/commit/message.ts +10 -3
  120. package/src/config/autorouting-tier-map.ts +30 -0
  121. package/src/config/config-hot-reload-trust.ts +148 -0
  122. package/src/config/config-hot-reload.ts +734 -0
  123. package/src/config/file-lock.ts +216 -56
  124. package/src/config/model-bindings-applier.ts +85 -47
  125. package/src/config/model-discovery-manager.ts +10 -0
  126. package/src/config/model-profile-activation.ts +716 -113
  127. package/src/config/model-registry.ts +1334 -158
  128. package/src/config/model-resolver.ts +3 -1
  129. package/src/config/models-config-schema.ts +6 -0
  130. package/src/config/settings-schema.ts +60 -3
  131. package/src/config/settings.ts +458 -24
  132. package/src/coordinator-mcp/policy.ts +26 -1
  133. package/src/crash/sanitize.ts +4 -0
  134. package/src/customization/import.ts +3 -1
  135. package/src/dap/config.ts +31 -11
  136. package/src/dap/defaults.json +1 -1
  137. package/src/debug/crash-diagnostics.ts +2 -1
  138. package/src/debug/report-bundle.ts +19 -0
  139. package/src/debug/system-info.ts +8 -3
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  141. package/src/discovery/agents-md.ts +1 -1
  142. package/src/discovery/helpers.ts +1 -2
  143. package/src/discovery/mcp-compat.ts +120 -17
  144. package/src/edit/apply-patch/index.ts +12 -1
  145. package/src/edit/index.ts +13 -17
  146. package/src/edit/modes/patch.ts +187 -8
  147. package/src/edit/modes/replace.ts +136 -0
  148. package/src/eval/js/context-manager.ts +29 -3
  149. package/src/eval/js/worker-entry.ts +51 -18
  150. package/src/eval/py/executor.ts +14 -4
  151. package/src/eval/py/runner.py +144 -5
  152. package/src/exec/bash-executor.ts +81 -4
  153. package/src/exec/bash-shell-guardian.ts +4 -1
  154. package/src/exec/bash-shell-supervisor.ts +8 -4
  155. package/src/exec/isolated-shell.ts +5 -1
  156. package/src/export/html/index.ts +46 -6
  157. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  158. package/src/extensibility/extensions/loader.ts +8 -1
  159. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  160. package/src/gjc-runtime/gc-runtime.ts +33 -6
  161. package/src/gjc-runtime/launch-tmux.ts +7 -2
  162. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  163. package/src/gjc-runtime/launch-worktree.ts +101 -14
  164. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  165. package/src/gjc-runtime/session-resolution.ts +38 -6
  166. package/src/gjc-runtime/session-state-lock.ts +59 -14
  167. package/src/gjc-runtime/state-runtime.ts +23 -19
  168. package/src/hooks/native-skill-hook.ts +14 -11
  169. package/src/internal-urls/docs-index.generated.ts +14 -13
  170. package/src/internal-urls/local-protocol.ts +142 -8
  171. package/src/lsp/index.ts +29 -0
  172. package/src/lsp/workspace-path.ts +23 -0
  173. package/src/migrate/action-planner.ts +57 -0
  174. package/src/migrate/adapters/claude-code.ts +23 -4
  175. package/src/migrate/adapters/codex.ts +22 -4
  176. package/src/migrate/adapters/index.ts +22 -4
  177. package/src/migrate/adapters/opencode.ts +6 -1
  178. package/src/migrate/executor.ts +84 -7
  179. package/src/migrate/report.ts +19 -2
  180. package/src/migrate/types.ts +14 -4
  181. package/src/modes/DESIGN.md +51 -0
  182. package/src/modes/acp/permission-mode.ts +18 -1
  183. package/src/modes/components/assistant-message.ts +55 -1
  184. package/src/modes/components/settings-selector.ts +103 -37
  185. package/src/modes/controllers/command-controller.ts +38 -2
  186. package/src/modes/controllers/plan-mode-controller.ts +17 -7
  187. package/src/modes/controllers/selector-controller.ts +62 -6
  188. package/src/modes/interactive-mode.ts +68 -0
  189. package/src/modes/theme/theme.ts +15 -0
  190. package/src/prompts/tools/debug.md +1 -1
  191. package/src/prompts/tools/read.md +1 -1
  192. package/src/prompts/tools/report-issue.md +13 -0
  193. package/src/prompts/tools/task.md +1 -1
  194. package/src/runtime-credential-selector.ts +2 -2
  195. package/src/runtime-mcp/content-limits.ts +7 -0
  196. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  197. package/src/runtime-mcp/manager.ts +32 -0
  198. package/src/runtime-mcp/oauth-flow.ts +32 -6
  199. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  200. package/src/runtime-mcp/render.ts +16 -1
  201. package/src/runtime-mcp/tool-bridge.ts +126 -17
  202. package/src/sdk/broker/broker-exit.ts +8 -5
  203. package/src/sdk/broker/broker.ts +342 -82
  204. package/src/sdk/broker/daemon-entry.ts +338 -116
  205. package/src/sdk/broker/doctor-restart.ts +2 -1
  206. package/src/sdk/broker/ensure.ts +1143 -265
  207. package/src/sdk/broker/hop.ts +274 -0
  208. package/src/sdk/broker/lifecycle-ledger.ts +35 -11
  209. package/src/sdk/broker/lock-host-identity.ts +29 -0
  210. package/src/sdk/broker/session-index.ts +62 -12
  211. package/src/sdk/bus/chat-command-policy.ts +16 -3
  212. package/src/sdk/bus/index.ts +52 -21
  213. package/src/sdk/bus/reconciliation-store.ts +201 -8
  214. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  215. package/src/sdk/cli/session-cli.ts +21 -6
  216. package/src/sdk/host/session-runtime.test.ts +118 -23
  217. package/src/sdk/host/session-runtime.ts +352 -47
  218. package/src/sdk/lifecycle/service.ts +1 -0
  219. package/src/sdk/prompt-deadline-flush.ts +78 -5
  220. package/src/sdk/prompt-deadline-manager.ts +68 -14
  221. package/src/sdk/protocol/adapter-validation.ts +53 -11
  222. package/src/sdk/protocol/operation-inventory.generated.json +101 -2
  223. package/src/sdk/reconciliation-extensions.ts +1 -0
  224. package/src/sdk/session-list.ts +1 -0
  225. package/src/sdk/session.ts +114 -61
  226. package/src/session/agent-session.ts +1043 -81
  227. package/src/session/internal/managed-session-scope.ts +126 -39
  228. package/src/session/internal/managed-session-storage.ts +14 -11
  229. package/src/session/internal/native-publish-outcome.ts +5 -1
  230. package/src/session/messages.ts +47 -6
  231. package/src/session/session-manager.ts +236 -14
  232. package/src/session/startup-auth-config.ts +116 -8
  233. package/src/session/streaming-output.ts +262 -4
  234. package/src/session/terminal-abort.ts +14 -2
  235. package/src/session-import/service.ts +1 -0
  236. package/src/skill-state/workflow-mutation-guard.ts +491 -32
  237. package/src/slash-commands/builtin-registry.ts +15 -3
  238. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  239. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  240. package/src/task/executor.ts +103 -12
  241. package/src/task/index.ts +389 -175
  242. package/src/tools/ast-edit-write-path.ts +64 -0
  243. package/src/tools/ast-edit.ts +18 -1
  244. package/src/tools/atomic-file-write.ts +46 -15
  245. package/src/tools/auto-generated-guard.ts +3 -0
  246. package/src/tools/bash-allowed-prefixes.ts +153 -5
  247. package/src/tools/bash-interactive.ts +14 -2
  248. package/src/tools/bash.ts +14 -0
  249. package/src/tools/debug.ts +7 -6
  250. package/src/tools/descriptors.test.ts +44 -0
  251. package/src/tools/descriptors.ts +34 -2
  252. package/src/tools/fetch.ts +3 -2
  253. package/src/tools/index.ts +2 -0
  254. package/src/tools/output-meta.ts +23 -2
  255. package/src/tools/python.ts +1 -1
  256. package/src/tools/read.ts +17 -35
  257. package/src/tools/report-issue.ts +184 -0
  258. package/src/tools/subagent-render.ts +114 -12
  259. package/src/tools/subagent.ts +108 -1
  260. package/src/tools/todo-write.ts +41 -3
  261. package/src/tools/tool-catalog.generated.ts +31 -3
  262. package/src/tools/write.ts +35 -3
  263. package/src/utils/commit-message-generator.ts +9 -2
  264. package/src/utils/file-mentions.ts +29 -1
  265. package/src/utils/title-generator.ts +7 -2
  266. package/src/web/scrapers/index.ts +3 -1
  267. package/src/web/scrapers/types.ts +8 -0
  268. package/src/web/scrapers/youtube.ts +3 -2
  269. package/src/web/search/providers/insane.ts +91 -28
  270. package/src/web/search/providers/searxng.ts +62 -10
@@ -299,6 +299,7 @@ export type ManagedScopeErrorCode =
299
299
  | "managed_gc_journal_capacity_exceeded"
300
300
  | "atomic_unavailable"
301
301
  | "invalid_request"
302
+ | "permission_denied"
302
303
  | "durability_failed"
303
304
  | "durability_not_provable"
304
305
  | "capacity_exceeded";
@@ -342,6 +343,7 @@ const managedScopeErrnoFailureCodes = new Set([
342
343
  const managedScopeFailureCodes = new Set<ManagedScopeErrorCode>([
343
344
  "atomic_unavailable",
344
345
  "invalid_request",
346
+ "permission_denied",
345
347
  "durability_failed",
346
348
  "durability_not_provable",
347
349
  "migration_busy",
@@ -441,6 +443,7 @@ export type ManagedOpenFailure =
441
443
  | "managed_gc_journal_capacity_exceeded"
442
444
  | "durability_failed"
443
445
  | "atomic_unavailable"
446
+ | "permission_denied"
444
447
  | "invalid_request"
445
448
  | "durability_not_provable"
446
449
  | "migration_retired"
@@ -1166,7 +1169,8 @@ function listDirectoryCandidates(
1166
1169
  try {
1167
1170
  directoryStat = fs.lstatSync(directory);
1168
1171
  } catch (error) {
1169
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
1172
+ const code = (error as NodeJS.ErrnoException).code;
1173
+ if (code === "ENOENT" || (provenance === "legacy" && code === "ENAMETOOLONG")) return [];
1170
1174
  throw error;
1171
1175
  }
1172
1176
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new Error("unsafe directory");
@@ -1970,7 +1974,8 @@ export function listManagedCandidates(scope: ManagedScope): ManagedCandidateList
1970
1974
  }
1971
1975
  }
1972
1976
  }
1973
- const visible = owned.filter(candidate => !isRetired(scope, candidate));
1977
+ const retired = owned.length > 0 ? retiredTargetKeys(scope) : new Set<string>();
1978
+ const visible = owned.filter(candidate => !isRetiredIn(retired, candidate));
1974
1979
  const active = visible.filter(
1975
1980
  candidate =>
1976
1981
  candidate.provenance === "v2" ||
@@ -2041,6 +2046,7 @@ function expectedFailure(error: unknown): ManagedOpenFailure {
2041
2046
  message === "managed_gc_journal_capacity_exceeded" ||
2042
2047
  message === "durability_failed" ||
2043
2048
  message === "atomic_unavailable" ||
2049
+ message === "permission_denied" ||
2044
2050
  message === "invalid_request" ||
2045
2051
  message === "durability_not_provable" ||
2046
2052
  message === "migration_retired"
@@ -2692,11 +2698,14 @@ function managedGcStreamDirectoryInventory(
2692
2698
  countEntry: (entry: fs.Dirent) => boolean,
2693
2699
  retain: (entry: fs.Dirent) => boolean,
2694
2700
  operation: ManagedGcInventoryOperation,
2701
+ verificationBaseline?: ManagedGcDirectoryInventory,
2695
2702
  ): ManagedGcDirectoryInventory {
2696
2703
  managedGcReserveInventoryWork(operation);
2697
2704
  const beforeStat = fs.lstatSync(pathname, { bigint: true });
2698
2705
  if (!beforeStat.isDirectory() || beforeStat.isSymbolicLink()) throw new Error("managed_gc_scope_authority_mismatch");
2699
2706
  const before = managedGcDirectoryEvidence(beforeStat);
2707
+ if (verificationBaseline && !managedGcSameDirectoryEvidence(before, verificationBaseline.evidence))
2708
+ throw new Error("managed_gc_scope_authority_mismatch");
2700
2709
  const scan = (collect: boolean): { count: number; physicalEntryCount: number; entries: fs.Dirent[] } => {
2701
2710
  managedGcReserveInventoryWork(operation);
2702
2711
  const entries: fs.Dirent[] = [];
@@ -2742,7 +2751,9 @@ function managedGcStreamDirectoryInventory(
2742
2751
  managedGcReserveInventoryWork(operation);
2743
2752
  return { count, physicalEntryCount, entries };
2744
2753
  };
2745
- const counted = scan(false);
2754
+ const counted = verificationBaseline
2755
+ ? { count: verificationBaseline.entryCount, physicalEntryCount: verificationBaseline.physicalEntryCount }
2756
+ : scan(false);
2746
2757
  managedGcReserveInventoryWork(operation);
2747
2758
  const afterCount = fs.lstatSync(pathname, { bigint: true });
2748
2759
  if (
@@ -2776,6 +2787,7 @@ function managedGcReceiptDirectoryInventory(
2776
2787
  scope: ManagedScope,
2777
2788
  store: ManagedSessionDescendantStore,
2778
2789
  operation: ManagedGcInventoryOperation,
2790
+ verificationBaseline?: ManagedGcDirectoryInventory,
2779
2791
  ): ManagedGcDirectoryInventory {
2780
2792
  const relativePath = MANAGED_GC_RETIREMENT_RECEIPTS;
2781
2793
  managedGcReserveInventoryWork(
@@ -2789,6 +2801,7 @@ function managedGcReceiptDirectoryInventory(
2789
2801
  entry => entry.name.startsWith(MANAGED_GC_RETIREMENT_PREFIX),
2790
2802
  entry => entry.name.startsWith(MANAGED_GC_RETIREMENT_PREFIX),
2791
2803
  operation,
2804
+ verificationBaseline,
2792
2805
  );
2793
2806
  managedGcReserveInventoryWork(operation);
2794
2807
  const after = store.captureDirectoryIdentity(relativePath);
@@ -3606,14 +3619,20 @@ export async function readManagedGcSessionRetirementReceiptReadOnly(
3606
3619
  if (!hasFsCode(error, "ENOENT")) throw error;
3607
3620
  receiptDirectoryPresent = false;
3608
3621
  }
3609
- if (receiptDirectoryPresent)
3622
+ if (receiptDirectoryPresent) {
3623
+ const inventory = managedGcReceiptDirectoryInventory(scope, store, operation);
3610
3624
  receipt = readManagedGcSessionRetirementReceiptUnlocked(
3611
3625
  scope,
3612
3626
  transcriptPath,
3613
3627
  trusted,
3614
3628
  store,
3615
3629
  operation,
3630
+ inventory,
3616
3631
  ).receipt;
3632
+ const after = managedGcReceiptDirectoryInventory(scope, store, operation, inventory);
3633
+ if (!managedGcSameReceiptDirectoryInventory(inventory, after))
3634
+ throw new Error("task_artifact_owner_continuation_authority_mismatch");
3635
+ }
3617
3636
  } catch (error) {
3618
3637
  primaryError = error;
3619
3638
  hasPrimaryError = true;
@@ -6282,8 +6301,96 @@ function tombstonePathContaining(scope: ManagedScope, candidate: ManagedCandidat
6282
6301
  return undefined;
6283
6302
  }
6284
6303
 
6285
- function isRetired(scope: ManagedScope, candidate: ManagedCandidate): boolean {
6304
+ /**
6305
+ * Identity fields a retired tombstone target must match exactly for a candidate
6306
+ * to be hidden. `dev`, `ino`, and `mtimeNs` are persisted as decimal strings.
6307
+ */
6308
+ type RetiredTargetIdentity = readonly [
6309
+ path: string,
6310
+ sessionId: string,
6311
+ cwd: string,
6312
+ canonicalPath: string,
6313
+ dev: string,
6314
+ ino: string,
6315
+ size: number,
6316
+ mtimeNs: string,
6317
+ sha256: string,
6318
+ ];
6319
+
6320
+ function candidateRetiredIdentity(candidate: ManagedCandidate): RetiredTargetIdentity {
6321
+ return [
6322
+ candidate.path,
6323
+ candidate.sessionId,
6324
+ candidate.cwd,
6325
+ candidate.identity.canonicalPath,
6326
+ String(candidate.identity.dev),
6327
+ String(candidate.identity.ino),
6328
+ candidate.identity.size,
6329
+ String(candidate.identity.mtimeNs),
6330
+ candidate.identity.sha256,
6331
+ ];
6332
+ }
6333
+
6334
+ /** Returns the target's identity only when every field has the type a candidate can carry. */
6335
+ function parseRetiredTargetIdentity(target: unknown): RetiredTargetIdentity | undefined {
6336
+ if (!target || typeof target !== "object") return undefined;
6337
+ const value = target as { path?: unknown; sessionId?: unknown; cwd?: unknown; identity?: unknown };
6338
+ if (!value.identity || typeof value.identity !== "object") return undefined;
6339
+ const identity = value.identity as {
6340
+ canonicalPath?: unknown;
6341
+ dev?: unknown;
6342
+ ino?: unknown;
6343
+ size?: unknown;
6344
+ mtimeNs?: unknown;
6345
+ sha256?: unknown;
6346
+ };
6347
+ if (
6348
+ typeof value.path !== "string" ||
6349
+ typeof value.sessionId !== "string" ||
6350
+ typeof value.cwd !== "string" ||
6351
+ typeof identity.canonicalPath !== "string" ||
6352
+ typeof identity.dev !== "string" ||
6353
+ typeof identity.ino !== "string" ||
6354
+ typeof identity.size !== "number" ||
6355
+ typeof identity.mtimeNs !== "string" ||
6356
+ typeof identity.sha256 !== "string"
6357
+ )
6358
+ return undefined;
6359
+ return [
6360
+ value.path,
6361
+ value.sessionId,
6362
+ value.cwd,
6363
+ identity.canonicalPath,
6364
+ identity.dev,
6365
+ identity.ino,
6366
+ identity.size,
6367
+ identity.mtimeNs,
6368
+ identity.sha256,
6369
+ ];
6370
+ }
6371
+
6372
+ /**
6373
+ * Key equality matches strict equality of every member: strings and finite
6374
+ * numbers encode distinctly. A non-finite tombstone `size` encodes as `null`,
6375
+ * which no candidate produces.
6376
+ */
6377
+ function retiredIdentityKey(identity: RetiredTargetIdentity): string {
6378
+ return JSON.stringify(identity);
6379
+ }
6380
+
6381
+ /**
6382
+ * Snapshot of the identities retired by a scope's tombstones.
6383
+ *
6384
+ * Tombstones are visited in directory order and the walk stops at the first
6385
+ * unreadable or malformed one: it grants no retirement authority, and neither do
6386
+ * the tombstones after it. Listing builds this once and checks every candidate
6387
+ * against it, so its cost is linear in tombstones rather than in
6388
+ * candidates x tombstones.
6389
+ */
6390
+ function retiredTargetKeys(scope: ManagedScope): ReadonlySet<string> {
6391
+ const keys = new Set<string>();
6286
6392
  const directory = path.join(managedInternalDirectory(scope), MANAGED_TOMBSTONES_DIRECTORY);
6393
+ const digest = scopeDigest(scope.platform, scope.canonicalCwd);
6287
6394
  try {
6288
6395
  for (const name of fs.readdirSync(directory)) {
6289
6396
  const pathname = path.join(directory, name);
@@ -6293,47 +6400,27 @@ function isRetired(scope: ManagedScope, candidate: ManagedCandidate): boolean {
6293
6400
  if (
6294
6401
  record.schemaVersion !== 2 ||
6295
6402
  record.state !== "retired" ||
6296
- record.scope !== scopeDigest(scope.platform, scope.canonicalCwd) ||
6403
+ record.scope !== digest ||
6297
6404
  !Array.isArray(record.targets)
6298
6405
  )
6299
6406
  continue;
6300
- if (
6301
- record.targets.some(target => {
6302
- if (!target || typeof target !== "object") return false;
6303
- const value = target as {
6304
- path?: unknown;
6305
- sessionId?: unknown;
6306
- cwd?: unknown;
6307
- identity?: {
6308
- canonicalPath?: unknown;
6309
- dev?: unknown;
6310
- ino?: unknown;
6311
- size?: unknown;
6312
- mtimeNs?: unknown;
6313
- sha256?: unknown;
6314
- };
6315
- };
6316
- const identity = value.identity;
6317
- if (!identity) return false;
6318
- return (
6319
- value.path === candidate.path &&
6320
- value.sessionId === candidate.sessionId &&
6321
- value.cwd === candidate.cwd &&
6322
- identity.canonicalPath === candidate.identity.canonicalPath &&
6323
- identity.dev === String(candidate.identity.dev) &&
6324
- identity.ino === String(candidate.identity.ino) &&
6325
- identity.size === candidate.identity.size &&
6326
- identity.mtimeNs === String(candidate.identity.mtimeNs) &&
6327
- identity.sha256 === candidate.identity.sha256
6328
- );
6329
- })
6330
- )
6331
- return true;
6407
+ for (const target of record.targets) {
6408
+ const identity = parseRetiredTargetIdentity(target);
6409
+ if (identity) keys.add(retiredIdentityKey(identity));
6410
+ }
6332
6411
  }
6333
6412
  } catch {
6334
6413
  /* a missing/malformed tombstone grants no retirement authority */
6335
6414
  }
6336
- return false;
6415
+ return keys;
6416
+ }
6417
+
6418
+ function isRetiredIn(retired: ReadonlySet<string>, candidate: ManagedCandidate): boolean {
6419
+ return retired.has(retiredIdentityKey(candidateRetiredIdentity(candidate)));
6420
+ }
6421
+
6422
+ function isRetired(scope: ManagedScope, candidate: ManagedCandidate): boolean {
6423
+ return isRetiredIn(retiredTargetKeys(scope), candidate);
6337
6424
  }
6338
6425
 
6339
6426
  type ArtifactManifestEntry =
@@ -80,6 +80,7 @@ export class ManagedPublishError extends Error {
80
80
  | "invalid_request"
81
81
  | "durability_not_provable"
82
82
  | "identity_mismatch"
83
+ | "permission_denied"
83
84
  | "io_error"
84
85
  | "durability_failed";
85
86
  readonly diagnostic: string;
@@ -141,17 +142,19 @@ function publishFailure(outcome: NativePublishOutcome): ManagedPublishError {
141
142
  const classification =
142
143
  outcome.reason === "destination_exists"
143
144
  ? "destination_conflict"
144
- : outcome.reason === "atomic_unavailable"
145
- ? "atomic_unavailable"
146
- : outcome.reason === "invalid_request"
147
- ? "invalid_request"
148
- : outcome.reason === "durability_not_provable"
149
- ? "durability_not_provable"
150
- : outcome.reason === "identity_violation"
151
- ? "identity_mismatch"
152
- : outcome.reason === "io_failure"
153
- ? "io_error"
154
- : "durability_failed";
145
+ : outcome.reason === "access_denied" || outcome.reason === "permission_denied"
146
+ ? "permission_denied"
147
+ : outcome.reason === "atomic_unavailable"
148
+ ? "atomic_unavailable"
149
+ : outcome.reason === "invalid_request"
150
+ ? "invalid_request"
151
+ : outcome.reason === "durability_not_provable"
152
+ ? "durability_not_provable"
153
+ : outcome.reason === "identity_violation"
154
+ ? "identity_mismatch"
155
+ : outcome.reason === "io_failure"
156
+ ? "io_error"
157
+ : "durability_failed";
155
158
  return new ManagedPublishError(classification, outcome);
156
159
  }
157
160
 
@@ -6,6 +6,7 @@ export type NativePublishReason =
6
6
  | "atomic_unavailable"
7
7
  | "cross_device"
8
8
  | "permission_denied"
9
+ | "access_denied"
9
10
  | "io_failure"
10
11
  | "interrupted"
11
12
  | "invalid_request"
@@ -77,6 +78,7 @@ const reasons = new Set<NativePublishReason>([
77
78
  "atomic_unavailable",
78
79
  "cross_device",
79
80
  "permission_denied",
81
+ "access_denied",
80
82
  "io_failure",
81
83
  "interrupted",
82
84
  "invalid_request",
@@ -109,6 +111,7 @@ const preMutationReasons = new Set<NativePublishReason>([
109
111
  "atomic_unavailable",
110
112
  "cross_device",
111
113
  "permission_denied",
114
+ "access_denied",
112
115
  "io_failure",
113
116
  "interrupted",
114
117
  "invalid_request",
@@ -184,11 +187,12 @@ function legalOutcome(outcome: NativePublishOutcome): boolean {
184
187
  "atomic_unavailable",
185
188
  "cross_device",
186
189
  "permission_denied",
190
+ "access_denied",
187
191
  "io_failure",
188
192
  "interrupted",
189
193
  ].includes(outcome.reason)) ||
190
194
  (outcome.phase === "preflight" &&
191
- !["atomic_unavailable", "cross_device", "interrupted"].includes(outcome.reason)) ||
195
+ !["atomic_unavailable", "cross_device", "interrupted", "access_denied"].includes(outcome.reason)) ||
192
196
  (outcome.phase === "file_sync" && outcome.reason === "io_failure"))
193
197
  );
194
198
  if (outcome.mutationState === "unknown")
@@ -158,6 +158,34 @@ function getPrunedToolResultContent(message: ToolResultMessage): (TextContent |
158
158
  export const PRE_ADMISSION_ARTIFACT_SPILL_HEAD_BYTES = 4096;
159
159
  export const PRE_ADMISSION_ARTIFACT_SPILL_TAIL_BYTES = 4096;
160
160
 
161
+ /**
162
+ * Keep an authenticated `#GJC1_` placeholder whole when the byte cut lands
163
+ * inside it. Short inputs whose head and tail already overlap stay on the
164
+ * original byte limits.
165
+ */
166
+ function retainStraddlingPlaceholders(text: string, head: string, tail: string): { head: string; tail: string } {
167
+ const headEnd = head.length;
168
+ const tailStart = text.length - tail.length;
169
+ if (headEnd > tailStart) return { head, tail };
170
+
171
+ let nextHeadEnd = headEnd;
172
+ let nextTailStart = tailStart;
173
+ const placeholder = /#GJC1_[A-Za-z0-9_-]{22}#/g;
174
+ for (;;) {
175
+ const match = placeholder.exec(text);
176
+ if (match === null) break;
177
+ const start = match.index;
178
+ const stop = start + match[0].length;
179
+ if (start < headEnd && stop > headEnd) nextHeadEnd = Math.max(nextHeadEnd, stop);
180
+ if (start < tailStart && stop > tailStart) nextTailStart = Math.min(nextTailStart, start);
181
+ }
182
+ if (nextHeadEnd > nextTailStart) return { head, tail };
183
+ return {
184
+ head: nextHeadEnd === headEnd ? head : text.slice(0, nextHeadEnd),
185
+ tail: nextTailStart === tailStart ? tail : text.slice(nextTailStart),
186
+ };
187
+ }
188
+
161
189
  function utf8Prefix(text: string, maxBytes: number): string {
162
190
  if (Buffer.byteLength(text, "utf-8") <= maxBytes) return text;
163
191
  let bytes = 0;
@@ -193,15 +221,28 @@ function utf8Suffix(text: string, maxBytes: number): string {
193
221
  * Build the deterministic inline receipt for a tool result saved before the
194
222
  * next provider context is constructed. Byte boundaries never split UTF-8
195
223
  * code points, so head/tail recovery remains readable for emoji and CJK text.
224
+ *
225
+ * `redact` runs on the complete text before the cut. The digest stays the
226
+ * caller's hash of the stored artifact; the omitted count is the redacted
227
+ * preview's omitted bytes.
196
228
  */
197
- export function createPreAdmissionArtifactSpillPreview(fullText: string, artifactId: string, digest: string): string {
198
- const totalBytes = Buffer.byteLength(fullText, "utf-8");
199
- const head = utf8Prefix(fullText, PRE_ADMISSION_ARTIFACT_SPILL_HEAD_BYTES);
200
- const tail = utf8Suffix(fullText, PRE_ADMISSION_ARTIFACT_SPILL_TAIL_BYTES);
201
- const retainedBytes = Buffer.byteLength(head, "utf-8") + Buffer.byteLength(tail, "utf-8");
229
+ export function createPreAdmissionArtifactSpillPreview(
230
+ fullText: string,
231
+ artifactId: string,
232
+ digest: string,
233
+ redact?: (text: string) => string,
234
+ ): string {
235
+ const source = redact ? redact(fullText) : fullText;
236
+ const totalBytes = Buffer.byteLength(source, "utf-8");
237
+ const head = utf8Prefix(source, PRE_ADMISSION_ARTIFACT_SPILL_HEAD_BYTES);
238
+ const tail = utf8Suffix(source, PRE_ADMISSION_ARTIFACT_SPILL_TAIL_BYTES);
239
+ // Without redaction the cut stays on the historical byte limits, including
240
+ // when the tool text itself contains a placeholder-shaped string.
241
+ const retained = redact ? retainStraddlingPlaceholders(source, head, tail) : { head, tail };
242
+ const retainedBytes = Buffer.byteLength(retained.head, "utf-8") + Buffer.byteLength(retained.tail, "utf-8");
202
243
  const omittedBytes = Math.max(0, totalBytes - retainedBytes);
203
244
  const receipt = `[${omittedBytes} bytes omitted; sha256:${digest}; full output: artifact://${artifactId}]`;
204
- return `${head}\n\n${receipt}\n\n${tail}`;
245
+ return `${retained.head}\n\n${receipt}\n\n${retained.tail}`;
205
246
  }
206
247
 
207
248
  /**