@wrongstack/core 0.297.0 → 0.298.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 (800) hide show
  1. package/dist/chronicle/index.js +126 -22
  2. package/dist/chronicle/project-server-client.d.ts +12 -0
  3. package/dist/chronicle/project-server-protocol.d.ts +22 -0
  4. package/dist/chronicle/project-server.js +238 -157
  5. package/dist/coordination/agents/index.js +136 -1
  6. package/dist/coordination/agents/role-skills.d.ts +3 -2
  7. package/dist/coordination/brain-trace.d.ts +2 -1
  8. package/dist/coordination/director/director-task-registry.d.ts +12 -0
  9. package/dist/coordination/fleet-supervisor.d.ts +21 -0
  10. package/dist/coordination/index.d.ts +23 -21
  11. package/dist/coordination/index.js +16493 -16068
  12. package/dist/coordination/mail-tools.d.ts +3 -3
  13. package/dist/coordination/mailbox-credential-store.d.ts +22 -2
  14. package/dist/coordination/mailbox-credential-throttle.d.ts +63 -0
  15. package/dist/coordination/mailbox-project-server-client.d.ts +16 -0
  16. package/dist/coordination/mailbox-project-server-protocol.d.ts +43 -5
  17. package/dist/coordination/mailbox-project-server.js +508 -253
  18. package/dist/coordination/provider-status-tracker.d.ts +2 -0
  19. package/dist/coordination/remote-mailbox-credential-store.d.ts +5 -5
  20. package/dist/coordination/remote-mailbox.d.ts +8 -4
  21. package/dist/coordination/sqlite-mailbox-credentials.d.ts +11 -0
  22. package/dist/core/fallback-profile-manager.d.ts +14 -0
  23. package/dist/core/index.js +451 -137
  24. package/dist/core/instruction-bundle.d.ts +12 -0
  25. package/dist/core/system-prompt-builder.d.ts +14 -0
  26. package/dist/core/system-prompt-skill-bodies.d.ts +1 -1
  27. package/dist/core/system-prompt-skill-text.d.ts +1 -2
  28. package/dist/defaults/index.js +2081 -1102
  29. package/dist/design/index.js +47 -8
  30. package/dist/execution/design-materialize.d.ts +22 -0
  31. package/dist/execution/index.js +495 -248
  32. package/dist/execution/skill-loader.d.ts +10 -1
  33. package/dist/execution/tool-error-taxonomy.d.ts +31 -0
  34. package/dist/execution/tool-executor.d.ts +1 -0
  35. package/dist/goal/index.js +3 -1
  36. package/dist/hooks/index.js +5 -0
  37. package/dist/hq/auth-audit.d.ts +6 -0
  38. package/dist/hq/auth-store.d.ts +59 -3
  39. package/dist/hq/index.js +401 -101
  40. package/dist/hq/kanban-store.d.ts +3 -0
  41. package/dist/hq/protocol/core.d.ts +3 -15
  42. package/dist/hq/protocol/envelope.d.ts +29 -0
  43. package/dist/hq/protocol/governance.d.ts +34 -0
  44. package/dist/hq/protocol/project.d.ts +3 -0
  45. package/dist/hq/protocol/resume.d.ts +1 -1
  46. package/dist/hq/protocol.d.ts +9 -8
  47. package/dist/hq/protocol.js +675 -0
  48. package/dist/index.js +8518 -6416
  49. package/dist/infrastructure/index.js +198 -101
  50. package/dist/kernel/events/session-events.d.ts +3 -0
  51. package/dist/kernel/events/tool-events.d.ts +3 -1
  52. package/dist/models/index.js +1 -1
  53. package/dist/observability/index.js +27 -0
  54. package/dist/observability/prometheus.d.ts +11 -0
  55. package/dist/plugin/index.d.ts +5 -1
  56. package/dist/plugin/index.js +950 -509
  57. package/dist/plugins/review-store-maintenance.d.ts +26 -0
  58. package/dist/plugins/review-types.d.ts +6 -0
  59. package/dist/security/capabilities.d.ts +45 -1
  60. package/dist/security/error-sanitize.d.ts +49 -0
  61. package/dist/security/file-permissions.d.ts +41 -0
  62. package/dist/security/index.d.ts +9 -6
  63. package/dist/security/index.js +3007 -2032
  64. package/dist/security/permission-policy.d.ts +55 -3
  65. package/dist/security/secret-vault.d.ts +17 -0
  66. package/dist/security/totp.d.ts +86 -0
  67. package/dist/skills/frontmatter.d.ts +12 -1
  68. package/dist/skills/github-fetcher.d.ts +48 -0
  69. package/dist/skills/index.js +137 -26
  70. package/dist/skills/limits.d.ts +19 -0
  71. package/dist/skills/skill-installer.d.ts +13 -0
  72. package/dist/storage/index.js +928 -732
  73. package/dist/storage/provider-config-watcher.d.ts +1 -0
  74. package/dist/storage/session-helpers.d.ts +1 -0
  75. package/dist/storage/session-store.d.ts +9 -2
  76. package/dist/storage/session-workspace-checkpoints.d.ts +5 -0
  77. package/dist/tools/fallback-manage-tools.d.ts +6 -0
  78. package/dist/tools/index.js +287 -131
  79. package/dist/tools/mcp-use.d.ts +1 -1
  80. package/dist/types/blocks.d.ts +7 -0
  81. package/dist/types/config/autonomy.d.ts +12 -3
  82. package/dist/types/config/mcp-features.d.ts +9 -1
  83. package/dist/types/config/root.d.ts +10 -4
  84. package/dist/types/context-window.d.ts +15 -0
  85. package/dist/types/index.d.ts +1 -1
  86. package/dist/types/secret-vault.d.ts +8 -0
  87. package/dist/types/session.d.ts +2 -0
  88. package/dist/types/skill.d.ts +56 -0
  89. package/dist/types/tool-executor.d.ts +2 -0
  90. package/dist/utils/env-typed.d.ts +64 -0
  91. package/dist/utils/glob-match.d.ts +21 -1
  92. package/dist/utils/heap-watchdog.js +1 -1
  93. package/dist/utils/incoming-images.d.ts +19 -0
  94. package/dist/utils/index.d.ts +15 -12
  95. package/dist/utils/index.js +3083 -2801
  96. package/dist/utils/path-segment.d.ts +29 -0
  97. package/dist/utils/win32-cmd.d.ts +25 -0
  98. package/dist/worktree/index.js +5 -0
  99. package/package.json +8 -3
  100. package/skills/chimera/SKILL.md +16 -4
  101. package/skills/data-governance/SKILL.md +92 -0
  102. package/skills/wrongstack-kanban/SKILL.md +125 -64
  103. package/dist/agent-status-helpers.d.ts.map +0 -1
  104. package/dist/agent-status-tracker.d.ts.map +0 -1
  105. package/dist/boot.d.ts.map +0 -1
  106. package/dist/chronicle/context.d.ts.map +0 -1
  107. package/dist/chronicle/decision-adapter.d.ts.map +0 -1
  108. package/dist/chronicle/domain-adapter.d.ts.map +0 -1
  109. package/dist/chronicle/event-hash.d.ts.map +0 -1
  110. package/dist/chronicle/file-observer.d.ts.map +0 -1
  111. package/dist/chronicle/health-monitor.d.ts.map +0 -1
  112. package/dist/chronicle/identity.d.ts.map +0 -1
  113. package/dist/chronicle/index.d.ts.map +0 -1
  114. package/dist/chronicle/index.js.map +0 -7
  115. package/dist/chronicle/journal.d.ts.map +0 -1
  116. package/dist/chronicle/legacy-journal-import.d.ts.map +0 -1
  117. package/dist/chronicle/metrics-store.d.ts.map +0 -1
  118. package/dist/chronicle/partition-filename.d.ts.map +0 -1
  119. package/dist/chronicle/partition-range-cache.d.ts.map +0 -1
  120. package/dist/chronicle/process-adapter.d.ts.map +0 -1
  121. package/dist/chronicle/project-access.d.ts.map +0 -1
  122. package/dist/chronicle/project-server-client.d.ts.map +0 -1
  123. package/dist/chronicle/project-server-endpoint.d.ts.map +0 -1
  124. package/dist/chronicle/project-server-protocol.d.ts.map +0 -1
  125. package/dist/chronicle/project-server.d.ts.map +0 -1
  126. package/dist/chronicle/project-server.js.map +0 -7
  127. package/dist/chronicle/prompt-manifest.d.ts.map +0 -1
  128. package/dist/chronicle/provider-adapter.d.ts.map +0 -1
  129. package/dist/chronicle/query.d.ts.map +0 -1
  130. package/dist/chronicle/review-adapter.d.ts.map +0 -1
  131. package/dist/chronicle/rollup-adapter.d.ts.map +0 -1
  132. package/dist/chronicle/sink.d.ts.map +0 -1
  133. package/dist/chronicle/sqlite-journal.d.ts.map +0 -1
  134. package/dist/chronicle/sqlite-query.d.ts.map +0 -1
  135. package/dist/chronicle/stream-adapter.d.ts.map +0 -1
  136. package/dist/chronicle/tool-adapter.d.ts.map +0 -1
  137. package/dist/chronicle/types.d.ts.map +0 -1
  138. package/dist/coordination/adaptive-concurrency.d.ts.map +0 -1
  139. package/dist/coordination/agent-bridge.d.ts.map +0 -1
  140. package/dist/coordination/agent-monitor.d.ts.map +0 -1
  141. package/dist/coordination/agent-subagent-runner.d.ts.map +0 -1
  142. package/dist/coordination/agents/agent-prompts.d.ts.map +0 -1
  143. package/dist/coordination/agents/index.d.ts.map +0 -1
  144. package/dist/coordination/agents/index.js.map +0 -7
  145. package/dist/coordination/agents/phase1-discovery.d.ts.map +0 -1
  146. package/dist/coordination/agents/phase2-planning.d.ts.map +0 -1
  147. package/dist/coordination/agents/phase3-build.d.ts.map +0 -1
  148. package/dist/coordination/agents/phase3-techstack.d.ts.map +0 -1
  149. package/dist/coordination/agents/phase3-wave1-platform.d.ts.map +0 -1
  150. package/dist/coordination/agents/phase3-wave2-meta.d.ts.map +0 -1
  151. package/dist/coordination/agents/phase4-verify.d.ts.map +0 -1
  152. package/dist/coordination/agents/phase5-review.d.ts.map +0 -1
  153. package/dist/coordination/agents/phase6-domain.d.ts.map +0 -1
  154. package/dist/coordination/agents/phase7-knowledge.d.ts.map +0 -1
  155. package/dist/coordination/agents/phase8-delivery.d.ts.map +0 -1
  156. package/dist/coordination/agents/phase8-wave3-products.d.ts.map +0 -1
  157. package/dist/coordination/agents/phase9-meta.d.ts.map +0 -1
  158. package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts.map +0 -1
  159. package/dist/coordination/agents/project-agent-config-validation.d.ts.map +0 -1
  160. package/dist/coordination/agents/project-agent-consolidation.d.ts.map +0 -1
  161. package/dist/coordination/agents/project-agent-files.d.ts.map +0 -1
  162. package/dist/coordination/agents/project-agent-identity-types.d.ts.map +0 -1
  163. package/dist/coordination/agents/project-agent-identity.d.ts.map +0 -1
  164. package/dist/coordination/agents/project-agent-knowledge-manifests.d.ts.map +0 -1
  165. package/dist/coordination/agents/project-agent-learning-entries.d.ts.map +0 -1
  166. package/dist/coordination/agents/project-agent-learning-normalize.d.ts.map +0 -1
  167. package/dist/coordination/agents/project-agent-learning-policy.d.ts.map +0 -1
  168. package/dist/coordination/agents/project-agent-learning-structured.d.ts.map +0 -1
  169. package/dist/coordination/agents/project-agent-paths.d.ts.map +0 -1
  170. package/dist/coordination/agents/project-agent-profile.d.ts.map +0 -1
  171. package/dist/coordination/agents/role-skills.d.ts.map +0 -1
  172. package/dist/coordination/agents/types.d.ts.map +0 -1
  173. package/dist/coordination/agents.d.ts.map +0 -1
  174. package/dist/coordination/auto-extend.d.ts.map +0 -1
  175. package/dist/coordination/autonomous-brain.d.ts.map +0 -1
  176. package/dist/coordination/autonomous-coordinator.d.ts.map +0 -1
  177. package/dist/coordination/brain-cache.d.ts.map +0 -1
  178. package/dist/coordination/brain-heuristics.d.ts.map +0 -1
  179. package/dist/coordination/brain-ledger.d.ts.map +0 -1
  180. package/dist/coordination/brain-monitor.d.ts.map +0 -1
  181. package/dist/coordination/brain-rules.d.ts.map +0 -1
  182. package/dist/coordination/brain-telemetry.d.ts.map +0 -1
  183. package/dist/coordination/brain-trace.d.ts.map +0 -1
  184. package/dist/coordination/brain.d.ts.map +0 -1
  185. package/dist/coordination/change-manager.d.ts.map +0 -1
  186. package/dist/coordination/checkpoint-wiring.d.ts.map +0 -1
  187. package/dist/coordination/collab-bus.d.ts.map +0 -1
  188. package/dist/coordination/collab-debug-types.d.ts.map +0 -1
  189. package/dist/coordination/collab-debug.d.ts.map +0 -1
  190. package/dist/coordination/collab-director-host.d.ts.map +0 -1
  191. package/dist/coordination/commit-safety.d.ts.map +0 -1
  192. package/dist/coordination/consensus-protocol.d.ts.map +0 -1
  193. package/dist/coordination/coordinator/error-classifier.d.ts.map +0 -1
  194. package/dist/coordination/delegate-tool.d.ts.map +0 -1
  195. package/dist/coordination/dep-watcher-bridge.d.ts.map +0 -1
  196. package/dist/coordination/dep-watcher.d.ts.map +0 -1
  197. package/dist/coordination/director/director-btw-notes.d.ts.map +0 -1
  198. package/dist/coordination/director/director-budget-policy.d.ts.map +0 -1
  199. package/dist/coordination/director/director-collab.d.ts.map +0 -1
  200. package/dist/coordination/director/director-errors.d.ts.map +0 -1
  201. package/dist/coordination/director/director-task-registry.d.ts.map +0 -1
  202. package/dist/coordination/director/director-toolset.d.ts.map +0 -1
  203. package/dist/coordination/director-basic-tools.d.ts.map +0 -1
  204. package/dist/coordination/director-collab-tools.d.ts.map +0 -1
  205. package/dist/coordination/director-host-contracts.d.ts.map +0 -1
  206. package/dist/coordination/director-input-helpers.d.ts.map +0 -1
  207. package/dist/coordination/director-kanban-queue-helpers.d.ts.map +0 -1
  208. package/dist/coordination/director-options.d.ts.map +0 -1
  209. package/dist/coordination/director-prompts.d.ts.map +0 -1
  210. package/dist/coordination/director-quality-gate-tool.d.ts.map +0 -1
  211. package/dist/coordination/director-session.d.ts.map +0 -1
  212. package/dist/coordination/director-spawn-model.d.ts.map +0 -1
  213. package/dist/coordination/director-tools.d.ts.map +0 -1
  214. package/dist/coordination/director.d.ts.map +0 -1
  215. package/dist/coordination/dispatcher.d.ts.map +0 -1
  216. package/dist/coordination/file-author-tracker.d.ts.map +0 -1
  217. package/dist/coordination/fleet-bus.d.ts.map +0 -1
  218. package/dist/coordination/fleet-event-validation.d.ts.map +0 -1
  219. package/dist/coordination/fleet-manager.d.ts.map +0 -1
  220. package/dist/coordination/fleet-spawn.d.ts.map +0 -1
  221. package/dist/coordination/fleet-status-tool.d.ts.map +0 -1
  222. package/dist/coordination/fleet-supervisor.d.ts.map +0 -1
  223. package/dist/coordination/fleet.d.ts.map +0 -1
  224. package/dist/coordination/global-mailbox-completion.d.ts.map +0 -1
  225. package/dist/coordination/global-mailbox-paths.d.ts.map +0 -1
  226. package/dist/coordination/icoordinator.d.ts.map +0 -1
  227. package/dist/coordination/ifleet-manager.d.ts.map +0 -1
  228. package/dist/coordination/in-memory-transport.d.ts.map +0 -1
  229. package/dist/coordination/index.d.ts.map +0 -1
  230. package/dist/coordination/index.js.map +0 -7
  231. package/dist/coordination/knowledge-graph.d.ts.map +0 -1
  232. package/dist/coordination/large-answer-store.d.ts.map +0 -1
  233. package/dist/coordination/mail-tools.d.ts.map +0 -1
  234. package/dist/coordination/mailbox-actions.d.ts.map +0 -1
  235. package/dist/coordination/mailbox-auth-types.d.ts.map +0 -1
  236. package/dist/coordination/mailbox-codecs.d.ts.map +0 -1
  237. package/dist/coordination/mailbox-constants.d.ts.map +0 -1
  238. package/dist/coordination/mailbox-credential-store.d.ts.map +0 -1
  239. package/dist/coordination/mailbox-events.d.ts.map +0 -1
  240. package/dist/coordination/mailbox-health.d.ts.map +0 -1
  241. package/dist/coordination/mailbox-hooks.d.ts.map +0 -1
  242. package/dist/coordination/mailbox-http-auth.d.ts.map +0 -1
  243. package/dist/coordination/mailbox-http-rate-limit.d.ts.map +0 -1
  244. package/dist/coordination/mailbox-http-router.d.ts.map +0 -1
  245. package/dist/coordination/mailbox-http-validation.d.ts.map +0 -1
  246. package/dist/coordination/mailbox-message-codec.d.ts.map +0 -1
  247. package/dist/coordination/mailbox-parse-state.d.ts.map +0 -1
  248. package/dist/coordination/mailbox-project-server-client.d.ts.map +0 -1
  249. package/dist/coordination/mailbox-project-server-endpoint.d.ts.map +0 -1
  250. package/dist/coordination/mailbox-project-server-protocol.d.ts.map +0 -1
  251. package/dist/coordination/mailbox-project-server.d.ts.map +0 -1
  252. package/dist/coordination/mailbox-project-server.js.map +0 -7
  253. package/dist/coordination/mailbox-receipt-folding.d.ts.map +0 -1
  254. package/dist/coordination/mailbox-registry-codec.d.ts.map +0 -1
  255. package/dist/coordination/mailbox-retention-state.d.ts.map +0 -1
  256. package/dist/coordination/mailbox-status-mappers.d.ts.map +0 -1
  257. package/dist/coordination/mailbox-tool.d.ts.map +0 -1
  258. package/dist/coordination/mailbox-type-properties.d.ts.map +0 -1
  259. package/dist/coordination/mailbox-types.d.ts.map +0 -1
  260. package/dist/coordination/model-matrix.d.ts.map +0 -1
  261. package/dist/coordination/multi-agent-coordinator.d.ts.map +0 -1
  262. package/dist/coordination/multi-agent-timeout.d.ts.map +0 -1
  263. package/dist/coordination/null-fleet-bus.d.ts.map +0 -1
  264. package/dist/coordination/package-author-tracker.d.ts.map +0 -1
  265. package/dist/coordination/package-outdated-watcher.d.ts.map +0 -1
  266. package/dist/coordination/provider-status-tracker.d.ts.map +0 -1
  267. package/dist/coordination/remote-mailbox-credential-store.d.ts.map +0 -1
  268. package/dist/coordination/remote-mailbox.d.ts.map +0 -1
  269. package/dist/coordination/single-instance-mailbox.d.ts.map +0 -1
  270. package/dist/coordination/spawn-budget.d.ts.map +0 -1
  271. package/dist/coordination/sqlite-mailbox-compaction.d.ts.map +0 -1
  272. package/dist/coordination/sqlite-mailbox-credentials.d.ts.map +0 -1
  273. package/dist/coordination/sqlite-mailbox-rows.d.ts.map +0 -1
  274. package/dist/coordination/sqlite-mailbox-schema.d.ts.map +0 -1
  275. package/dist/coordination/sqlite-mailbox.d.ts.map +0 -1
  276. package/dist/coordination/subagent-budget.d.ts.map +0 -1
  277. package/dist/coordination/subagent-nicknames.d.ts.map +0 -1
  278. package/dist/coordination/subagent-result-tool.d.ts.map +0 -1
  279. package/dist/coordination/task-auctioneer.d.ts.map +0 -1
  280. package/dist/coordination/task-dag.d.ts.map +0 -1
  281. package/dist/coordination/techstack-mailbox-consumer.d.ts.map +0 -1
  282. package/dist/coordination/transport.d.ts.map +0 -1
  283. package/dist/coordination/worktree-task-runner.d.ts.map +0 -1
  284. package/dist/core/agent-internals.d.ts.map +0 -1
  285. package/dist/core/agent-loop.d.ts.map +0 -1
  286. package/dist/core/agent-response.d.ts.map +0 -1
  287. package/dist/core/agent-tools.d.ts.map +0 -1
  288. package/dist/core/agent-types.d.ts.map +0 -1
  289. package/dist/core/agent.d.ts.map +0 -1
  290. package/dist/core/btw.d.ts.map +0 -1
  291. package/dist/core/context.d.ts.map +0 -1
  292. package/dist/core/continue-intent.d.ts.map +0 -1
  293. package/dist/core/continue-to-next-iteration.d.ts.map +0 -1
  294. package/dist/core/conversation-state.d.ts.map +0 -1
  295. package/dist/core/fallback-model.d.ts.map +0 -1
  296. package/dist/core/fallback-profile-manager.d.ts.map +0 -1
  297. package/dist/core/fleet-pulse.d.ts.map +0 -1
  298. package/dist/core/index.d.ts.map +0 -1
  299. package/dist/core/index.js.map +0 -7
  300. package/dist/core/input-builder.d.ts.map +0 -1
  301. package/dist/core/instruction-bundle.d.ts.map +0 -1
  302. package/dist/core/iteration-limit.d.ts.map +0 -1
  303. package/dist/core/mailbox-loop.d.ts.map +0 -1
  304. package/dist/core/model-availability-calendar.d.ts.map +0 -1
  305. package/dist/core/model-ref.d.ts.map +0 -1
  306. package/dist/core/model-ref.js.map +0 -7
  307. package/dist/core/modes/brief.d.ts.map +0 -1
  308. package/dist/core/modes/default.d.ts.map +0 -1
  309. package/dist/core/modes/teach.d.ts.map +0 -1
  310. package/dist/core/provider-runner.d.ts.map +0 -1
  311. package/dist/core/queued-messages.d.ts.map +0 -1
  312. package/dist/core/request-provider-binding.d.ts.map +0 -1
  313. package/dist/core/run-env.d.ts.map +0 -1
  314. package/dist/core/streaming-response-builder.d.ts.map +0 -1
  315. package/dist/core/system-prompt-blocks.d.ts.map +0 -1
  316. package/dist/core/system-prompt-builder.d.ts.map +0 -1
  317. package/dist/core/system-prompt-environment-probes.d.ts.map +0 -1
  318. package/dist/core/system-prompt-environment.d.ts.map +0 -1
  319. package/dist/core/system-prompt-memory-skills.d.ts.map +0 -1
  320. package/dist/core/system-prompt-plan.d.ts.map +0 -1
  321. package/dist/core/system-prompt-shell.d.ts.map +0 -1
  322. package/dist/core/system-prompt-skill-bodies.d.ts.map +0 -1
  323. package/dist/core/system-prompt-skill-text.d.ts.map +0 -1
  324. package/dist/defaults/index.d.ts.map +0 -1
  325. package/dist/defaults/index.js.map +0 -7
  326. package/dist/design/index.d.ts.map +0 -1
  327. package/dist/design/index.js.map +0 -7
  328. package/dist/execution/auto-compaction-middleware.d.ts.map +0 -1
  329. package/dist/execution/autonomous-runner.d.ts.map +0 -1
  330. package/dist/execution/autonomy-brain.d.ts.map +0 -1
  331. package/dist/execution/autonomy-prompt-contributor.d.ts.map +0 -1
  332. package/dist/execution/brain-chain.d.ts.map +0 -1
  333. package/dist/execution/brain-circuit.d.ts.map +0 -1
  334. package/dist/execution/brain-evaluation.d.ts.map +0 -1
  335. package/dist/execution/brain-runtime-constants.d.ts.map +0 -1
  336. package/dist/execution/brain-runtime.d.ts.map +0 -1
  337. package/dist/execution/compaction-core.d.ts.map +0 -1
  338. package/dist/execution/compaction-scoring.d.ts.map +0 -1
  339. package/dist/execution/compaction-summary-cache.d.ts.map +0 -1
  340. package/dist/execution/compactor.d.ts.map +0 -1
  341. package/dist/execution/council-brain.d.ts.map +0 -1
  342. package/dist/execution/council-orchestrator.d.ts.map +0 -1
  343. package/dist/execution/council-personas.d.ts.map +0 -1
  344. package/dist/execution/council-profiles.d.ts.map +0 -1
  345. package/dist/execution/council-prompts.d.ts.map +0 -1
  346. package/dist/execution/council-resolution.d.ts.map +0 -1
  347. package/dist/execution/design-color.d.ts.map +0 -1
  348. package/dist/execution/design-detect.d.ts.map +0 -1
  349. package/dist/execution/design-kit-loader.d.ts.map +0 -1
  350. package/dist/execution/design-materialize.d.ts.map +0 -1
  351. package/dist/execution/design-project-store.d.ts.map +0 -1
  352. package/dist/execution/design-tune.d.ts.map +0 -1
  353. package/dist/execution/design-verify.d.ts.map +0 -1
  354. package/dist/execution/enhance-recovery.d.ts.map +0 -1
  355. package/dist/execution/error-handler.d.ts.map +0 -1
  356. package/dist/execution/eternal-autonomy-types.d.ts.map +0 -1
  357. package/dist/execution/eternal-autonomy.d.ts.map +0 -1
  358. package/dist/execution/goal-preamble.d.ts.map +0 -1
  359. package/dist/execution/index.d.ts.map +0 -1
  360. package/dist/execution/index.js.map +0 -7
  361. package/dist/execution/intelligent-compactor.d.ts.map +0 -1
  362. package/dist/execution/model-runtime.d.ts.map +0 -1
  363. package/dist/execution/one-shot-llm.d.ts.map +0 -1
  364. package/dist/execution/parallel-eternal-engine.d.ts.map +0 -1
  365. package/dist/execution/prompt-enhancer.d.ts.map +0 -1
  366. package/dist/execution/prompt-enhancer.js.map +0 -7
  367. package/dist/execution/prompt-loader.d.ts.map +0 -1
  368. package/dist/execution/provider-runner-impl.d.ts.map +0 -1
  369. package/dist/execution/regex-patterns.d.ts.map +0 -1
  370. package/dist/execution/retry-policy.d.ts.map +0 -1
  371. package/dist/execution/selective-compactor.d.ts.map +0 -1
  372. package/dist/execution/skill-loader.d.ts.map +0 -1
  373. package/dist/execution/strategy-compactor.d.ts.map +0 -1
  374. package/dist/execution/subagent-compaction.d.ts.map +0 -1
  375. package/dist/execution/tool-executor-logging.d.ts.map +0 -1
  376. package/dist/execution/tool-executor-results.d.ts.map +0 -1
  377. package/dist/execution/tool-executor-stream.d.ts.map +0 -1
  378. package/dist/execution/tool-executor-support.d.ts.map +0 -1
  379. package/dist/execution/tool-executor.d.ts.map +0 -1
  380. package/dist/extension/extension-points.d.ts.map +0 -1
  381. package/dist/extension/index.d.ts.map +0 -1
  382. package/dist/extension/index.js.map +0 -7
  383. package/dist/extension/registry.d.ts.map +0 -1
  384. package/dist/fleet-notifier.d.ts.map +0 -1
  385. package/dist/goal/checkpoint.d.ts.map +0 -1
  386. package/dist/goal/goal-assessor.d.ts.map +0 -1
  387. package/dist/goal/goal-planner.d.ts.map +0 -1
  388. package/dist/goal/goal-runner.d.ts.map +0 -1
  389. package/dist/goal/index.d.ts.map +0 -1
  390. package/dist/goal/index.js.map +0 -7
  391. package/dist/goal/phase-board-mutations.d.ts.map +0 -1
  392. package/dist/goal/phase-graph-builder.d.ts.map +0 -1
  393. package/dist/goal/phase-orchestrator-integration.d.ts.map +0 -1
  394. package/dist/goal/phase-orchestrator-queries.d.ts.map +0 -1
  395. package/dist/goal/phase-orchestrator-runtime.d.ts.map +0 -1
  396. package/dist/goal/phase-orchestrator-types.d.ts.map +0 -1
  397. package/dist/goal/phase-orchestrator.d.ts.map +0 -1
  398. package/dist/goal/phase-store.d.ts.map +0 -1
  399. package/dist/goal/types.d.ts.map +0 -1
  400. package/dist/hooks/http-executor.d.ts.map +0 -1
  401. package/dist/hooks/index.d.ts.map +0 -1
  402. package/dist/hooks/index.js.map +0 -7
  403. package/dist/hooks/registry.d.ts.map +0 -1
  404. package/dist/hooks/runner.d.ts.map +0 -1
  405. package/dist/hooks/shell-executor.d.ts.map +0 -1
  406. package/dist/hooks/shell-hooks-equal.d.ts.map +0 -1
  407. package/dist/hq/alerts.d.ts.map +0 -1
  408. package/dist/hq/auth-audit.d.ts.map +0 -1
  409. package/dist/hq/auth-store.d.ts.map +0 -1
  410. package/dist/hq/bootstrap-store.d.ts.map +0 -1
  411. package/dist/hq/brain-bridge.d.ts.map +0 -1
  412. package/dist/hq/bridge-context.d.ts.map +0 -1
  413. package/dist/hq/commands.d.ts.map +0 -1
  414. package/dist/hq/cost-bridge.d.ts.map +0 -1
  415. package/dist/hq/exposure.d.ts.map +0 -1
  416. package/dist/hq/factory.d.ts.map +0 -1
  417. package/dist/hq/fleet-bridge.d.ts.map +0 -1
  418. package/dist/hq/index.d.ts.map +0 -1
  419. package/dist/hq/index.js.map +0 -7
  420. package/dist/hq/kanban-store.d.ts.map +0 -1
  421. package/dist/hq/mailbox-mapper.d.ts.map +0 -1
  422. package/dist/hq/persistence/event-log.d.ts.map +0 -1
  423. package/dist/hq/persistence/jsonl-io.d.ts.map +0 -1
  424. package/dist/hq/persistence/simple-log.d.ts.map +0 -1
  425. package/dist/hq/persistence/snapshot-store.d.ts.map +0 -1
  426. package/dist/hq/persistence/timeseries-store.d.ts.map +0 -1
  427. package/dist/hq/persistence.d.ts.map +0 -1
  428. package/dist/hq/protocol/brain.d.ts.map +0 -1
  429. package/dist/hq/protocol/browser.d.ts.map +0 -1
  430. package/dist/hq/protocol/client.d.ts.map +0 -1
  431. package/dist/hq/protocol/core.d.ts.map +0 -1
  432. package/dist/hq/protocol/fleet.d.ts.map +0 -1
  433. package/dist/hq/protocol/kanban.d.ts.map +0 -1
  434. package/dist/hq/protocol/mailbox.d.ts.map +0 -1
  435. package/dist/hq/protocol/mcp.d.ts.map +0 -1
  436. package/dist/hq/protocol/peer.d.ts.map +0 -1
  437. package/dist/hq/protocol/project.d.ts.map +0 -1
  438. package/dist/hq/protocol/resume.d.ts.map +0 -1
  439. package/dist/hq/protocol/session.d.ts.map +0 -1
  440. package/dist/hq/protocol/tool.d.ts.map +0 -1
  441. package/dist/hq/protocol.d.ts.map +0 -1
  442. package/dist/hq/publisher.d.ts.map +0 -1
  443. package/dist/hq/redaction.d.ts.map +0 -1
  444. package/dist/hq/session-bridge.d.ts.map +0 -1
  445. package/dist/hq/tool-bridge.d.ts.map +0 -1
  446. package/dist/hq/transcript-mapper.d.ts.map +0 -1
  447. package/dist/hq/worktree-bridge.d.ts.map +0 -1
  448. package/dist/hq/write-queues.d.ts.map +0 -1
  449. package/dist/index.d.ts.map +0 -1
  450. package/dist/index.js.map +0 -7
  451. package/dist/infrastructure/context-manager.d.ts.map +0 -1
  452. package/dist/infrastructure/index.d.ts.map +0 -1
  453. package/dist/infrastructure/index.js.map +0 -7
  454. package/dist/infrastructure/logger.d.ts.map +0 -1
  455. package/dist/infrastructure/mcp-servers.d.ts.map +0 -1
  456. package/dist/infrastructure/path-resolver.d.ts.map +0 -1
  457. package/dist/infrastructure/provider-cache-ledger.d.ts.map +0 -1
  458. package/dist/infrastructure/token-counter.d.ts.map +0 -1
  459. package/dist/kernel/container.d.ts.map +0 -1
  460. package/dist/kernel/events/agent-events.d.ts.map +0 -1
  461. package/dist/kernel/events/brain-events.d.ts.map +0 -1
  462. package/dist/kernel/events/file-events.d.ts.map +0 -1
  463. package/dist/kernel/events/fleet-events.d.ts.map +0 -1
  464. package/dist/kernel/events/memory-events.d.ts.map +0 -1
  465. package/dist/kernel/events/network-events.d.ts.map +0 -1
  466. package/dist/kernel/events/process-events.d.ts.map +0 -1
  467. package/dist/kernel/events/provider-events.d.ts.map +0 -1
  468. package/dist/kernel/events/sdd-events.d.ts.map +0 -1
  469. package/dist/kernel/events/session-events.d.ts.map +0 -1
  470. package/dist/kernel/events/tool-events.d.ts.map +0 -1
  471. package/dist/kernel/events/worktree-events.d.ts.map +0 -1
  472. package/dist/kernel/events.d.ts.map +0 -1
  473. package/dist/kernel/index.d.ts.map +0 -1
  474. package/dist/kernel/index.js.map +0 -7
  475. package/dist/kernel/pipeline.d.ts.map +0 -1
  476. package/dist/kernel/run-controller.d.ts.map +0 -1
  477. package/dist/kernel/tokens.d.ts.map +0 -1
  478. package/dist/mailbox-attach.d.ts.map +0 -1
  479. package/dist/middleware/collab-pause.d.ts.map +0 -1
  480. package/dist/models/alibaba-token-plan-catalog.d.ts.map +0 -1
  481. package/dist/models/codex-catalog.d.ts.map +0 -1
  482. package/dist/models/index.d.ts.map +0 -1
  483. package/dist/models/index.js.map +0 -7
  484. package/dist/models/llm-selector.d.ts.map +0 -1
  485. package/dist/models/mode-store.d.ts.map +0 -1
  486. package/dist/models/model-intelligence.d.ts.map +0 -1
  487. package/dist/models/model-router.d.ts.map +0 -1
  488. package/dist/models/models-registry.d.ts.map +0 -1
  489. package/dist/models/provider-model-resolve.d.ts.map +0 -1
  490. package/dist/notifications/index.d.ts.map +0 -1
  491. package/dist/notifications/index.js.map +0 -7
  492. package/dist/notifications/notifier-impl.d.ts.map +0 -1
  493. package/dist/notifications/notifier.d.ts.map +0 -1
  494. package/dist/notifications/types.d.ts.map +0 -1
  495. package/dist/observability/event-bridge.d.ts.map +0 -1
  496. package/dist/observability/health.d.ts.map +0 -1
  497. package/dist/observability/index.d.ts.map +0 -1
  498. package/dist/observability/index.js.map +0 -7
  499. package/dist/observability/metrics.d.ts.map +0 -1
  500. package/dist/observability/network-telemetry.d.ts.map +0 -1
  501. package/dist/observability/otel-tracer.d.ts.map +0 -1
  502. package/dist/observability/otlp-metrics.d.ts.map +0 -1
  503. package/dist/observability/otlp-traces.d.ts.map +0 -1
  504. package/dist/observability/process-telemetry.d.ts.map +0 -1
  505. package/dist/observability/prometheus.d.ts.map +0 -1
  506. package/dist/observability/redact-command.d.ts.map +0 -1
  507. package/dist/observability/tracer.d.ts.map +0 -1
  508. package/dist/plugin/api.d.ts.map +0 -1
  509. package/dist/plugin/config.d.ts.map +0 -1
  510. package/dist/plugin/index.d.ts.map +0 -1
  511. package/dist/plugin/index.js.map +0 -7
  512. package/dist/plugin/loader.d.ts.map +0 -1
  513. package/dist/plugins/auto-review-plugin.d.ts.map +0 -1
  514. package/dist/plugins/chimera-plugin.d.ts.map +0 -1
  515. package/dist/plugins/prompts-plugin.d.ts.map +0 -1
  516. package/dist/plugins/review-claim-registry.d.ts.map +0 -1
  517. package/dist/plugins/review-context-builder.d.ts.map +0 -1
  518. package/dist/plugins/review-finding-commands.d.ts.map +0 -1
  519. package/dist/plugins/review-finding-integration.d.ts.map +0 -1
  520. package/dist/plugins/review-finding-parser.d.ts.map +0 -1
  521. package/dist/plugins/review-finding-store.d.ts.map +0 -1
  522. package/dist/plugins/review-finding-types.d.ts.map +0 -1
  523. package/dist/plugins/review-report-integration.d.ts.map +0 -1
  524. package/dist/plugins/review-report-store.d.ts.map +0 -1
  525. package/dist/plugins/review-report-types.d.ts.map +0 -1
  526. package/dist/plugins/review-types.d.ts.map +0 -1
  527. package/dist/plugins/skills-plugin.d.ts.map +0 -1
  528. package/dist/plugins/sync-plugin.d.ts.map +0 -1
  529. package/dist/prompts/index.d.ts.map +0 -1
  530. package/dist/prompts/prompt-installer.d.ts.map +0 -1
  531. package/dist/prompts/prompt-manifest-store.d.ts.map +0 -1
  532. package/dist/registry/index.d.ts.map +0 -1
  533. package/dist/registry/index.js.map +0 -7
  534. package/dist/registry/provider-registry.d.ts.map +0 -1
  535. package/dist/registry/slash-command-registry.d.ts.map +0 -1
  536. package/dist/registry/tool-registry.d.ts.map +0 -1
  537. package/dist/replay/hash.d.ts.map +0 -1
  538. package/dist/replay/index.d.ts.map +0 -1
  539. package/dist/replay/index.js.map +0 -7
  540. package/dist/replay/replay-provider-runner.d.ts.map +0 -1
  541. package/dist/security/capabilities.d.ts.map +0 -1
  542. package/dist/security/config-secrets.d.ts.map +0 -1
  543. package/dist/security/directory-permission-policy.d.ts.map +0 -1
  544. package/dist/security/directory-policy-schema.d.ts.map +0 -1
  545. package/dist/security/index.d.ts.map +0 -1
  546. package/dist/security/index.js.map +0 -7
  547. package/dist/security/kanban-boundary.d.ts.map +0 -1
  548. package/dist/security/permission-policy-schema.d.ts.map +0 -1
  549. package/dist/security/permission-policy.d.ts.map +0 -1
  550. package/dist/security/readonly-permission-policy.d.ts.map +0 -1
  551. package/dist/security/secret-scrubber.d.ts.map +0 -1
  552. package/dist/security/secret-vault.d.ts.map +0 -1
  553. package/dist/security/trust-boundary.d.ts.map +0 -1
  554. package/dist/security/yolo-risk.d.ts.map +0 -1
  555. package/dist/session-registry-atomic-file.d.ts.map +0 -1
  556. package/dist/session-registry-types.d.ts.map +0 -1
  557. package/dist/session-registry.d.ts.map +0 -1
  558. package/dist/skills/foreign-sources.d.ts.map +0 -1
  559. package/dist/skills/frontmatter.d.ts.map +0 -1
  560. package/dist/skills/github-fetcher.d.ts.map +0 -1
  561. package/dist/skills/index.d.ts.map +0 -1
  562. package/dist/skills/index.js.map +0 -7
  563. package/dist/skills/limits.d.ts.map +0 -1
  564. package/dist/skills/manifest-store.d.ts.map +0 -1
  565. package/dist/skills/registry/github-direct-adapter.d.ts.map +0 -1
  566. package/dist/skills/registry/registry-adapter.d.ts.map +0 -1
  567. package/dist/skills/registry/skills-sh-adapter.d.ts.map +0 -1
  568. package/dist/skills/skill-generator.d.ts.map +0 -1
  569. package/dist/skills/skill-installer.d.ts.map +0 -1
  570. package/dist/storage/annotations-store.d.ts.map +0 -1
  571. package/dist/storage/attachment-store.d.ts.map +0 -1
  572. package/dist/storage/cloud-sync.d.ts.map +0 -1
  573. package/dist/storage/completed-work-checkpoint.d.ts.map +0 -1
  574. package/dist/storage/config-loader/bootstrap.d.ts.map +0 -1
  575. package/dist/storage/config-loader/default-repair.d.ts.map +0 -1
  576. package/dist/storage/config-loader/defaults.d.ts.map +0 -1
  577. package/dist/storage/config-loader/diagnostics.d.ts.map +0 -1
  578. package/dist/storage/config-loader/env-overrides.d.ts.map +0 -1
  579. package/dist/storage/config-loader/in-project-policy.d.ts.map +0 -1
  580. package/dist/storage/config-loader/inline-provider-models.d.ts.map +0 -1
  581. package/dist/storage/config-loader/legacy-sage-migration.d.ts.map +0 -1
  582. package/dist/storage/config-loader/path-identity.d.ts.map +0 -1
  583. package/dist/storage/config-loader/types.d.ts.map +0 -1
  584. package/dist/storage/config-loader/validation.d.ts.map +0 -1
  585. package/dist/storage/config-loader.d.ts.map +0 -1
  586. package/dist/storage/config-migration.d.ts.map +0 -1
  587. package/dist/storage/config-store.d.ts.map +0 -1
  588. package/dist/storage/director-state.d.ts.map +0 -1
  589. package/dist/storage/file-session-writer.d.ts.map +0 -1
  590. package/dist/storage/goal-coordination.d.ts.map +0 -1
  591. package/dist/storage/goal-kanban.d.ts.map +0 -1
  592. package/dist/storage/goal-store.d.ts.map +0 -1
  593. package/dist/storage/index.d.ts.map +0 -1
  594. package/dist/storage/index.js.map +0 -7
  595. package/dist/storage/input-history-store.d.ts.map +0 -1
  596. package/dist/storage/memory-backend.d.ts.map +0 -1
  597. package/dist/storage/memory-consolidator.d.ts.map +0 -1
  598. package/dist/storage/memory-graph-backend.d.ts.map +0 -1
  599. package/dist/storage/plan-store.d.ts.map +0 -1
  600. package/dist/storage/plan-templates.d.ts.map +0 -1
  601. package/dist/storage/prompt-store.d.ts.map +0 -1
  602. package/dist/storage/prompt-usage-store.d.ts.map +0 -1
  603. package/dist/storage/provider-config-watcher.d.ts.map +0 -1
  604. package/dist/storage/queue-store.d.ts.map +0 -1
  605. package/dist/storage/recovery-lock.d.ts.map +0 -1
  606. package/dist/storage/replay-log-store.d.ts.map +0 -1
  607. package/dist/storage/session-analyzer.d.ts.map +0 -1
  608. package/dist/storage/session-checkpoint-cas.d.ts.map +0 -1
  609. package/dist/storage/session-event-bridge.d.ts.map +0 -1
  610. package/dist/storage/session-helpers.d.ts.map +0 -1
  611. package/dist/storage/session-id-resolver.d.ts.map +0 -1
  612. package/dist/storage/session-id.d.ts.map +0 -1
  613. package/dist/storage/session-read-scrubber.d.ts.map +0 -1
  614. package/dist/storage/session-reader.d.ts.map +0 -1
  615. package/dist/storage/session-recovery.d.ts.map +0 -1
  616. package/dist/storage/session-resume-validation.d.ts.map +0 -1
  617. package/dist/storage/session-rewind-apply.d.ts.map +0 -1
  618. package/dist/storage/session-rewinder.d.ts.map +0 -1
  619. package/dist/storage/session-store/delete-session-artifacts.d.ts.map +0 -1
  620. package/dist/storage/session-store/delete-session-guards.d.ts.map +0 -1
  621. package/dist/storage/session-store/directory-scan.d.ts.map +0 -1
  622. package/dist/storage/session-store/directory-session-files.d.ts.map +0 -1
  623. package/dist/storage/session-store/events.d.ts.map +0 -1
  624. package/dist/storage/session-store/fork-session.d.ts.map +0 -1
  625. package/dist/storage/session-store/index-reader.d.ts.map +0 -1
  626. package/dist/storage/session-store/load-cache.d.ts.map +0 -1
  627. package/dist/storage/session-store/load-session-data.d.ts.map +0 -1
  628. package/dist/storage/session-store/paths.d.ts.map +0 -1
  629. package/dist/storage/session-store/prune-helpers.d.ts.map +0 -1
  630. package/dist/storage/session-store/replay.d.ts.map +0 -1
  631. package/dist/storage/session-store/search-events.d.ts.map +0 -1
  632. package/dist/storage/session-store/shard-manifest.d.ts.map +0 -1
  633. package/dist/storage/session-store/summary-builder.d.ts.map +0 -1
  634. package/dist/storage/session-store/summary-header.d.ts.map +0 -1
  635. package/dist/storage/session-store/types.d.ts.map +0 -1
  636. package/dist/storage/session-store.d.ts.map +0 -1
  637. package/dist/storage/session-summary.d.ts.map +0 -1
  638. package/dist/storage/session-tool-call-ends.d.ts.map +0 -1
  639. package/dist/storage/session-writer-scrubber.d.ts.map +0 -1
  640. package/dist/storage/session-writer-truncate.d.ts.map +0 -1
  641. package/dist/storage/storage-concurrency.d.ts.map +0 -1
  642. package/dist/storage/task-store.d.ts.map +0 -1
  643. package/dist/storage/todos-checkpoint.d.ts.map +0 -1
  644. package/dist/storage/tool-audit-log.d.ts.map +0 -1
  645. package/dist/tasking/index.d.ts.map +0 -1
  646. package/dist/tasking/index.js.map +0 -7
  647. package/dist/tasking/task-store.d.ts.map +0 -1
  648. package/dist/tasking/task-tracker.d.ts.map +0 -1
  649. package/dist/tools/council-tool.d.ts.map +0 -1
  650. package/dist/tools/fallback-chain-manage-tool.d.ts.map +0 -1
  651. package/dist/tools/fallback-favorite-manage-tool.d.ts.map +0 -1
  652. package/dist/tools/fallback-manage-helpers.d.ts.map +0 -1
  653. package/dist/tools/fallback-manage-tool-options.d.ts.map +0 -1
  654. package/dist/tools/fallback-manage-tools.d.ts.map +0 -1
  655. package/dist/tools/fallback-model-ref-parse.d.ts.map +0 -1
  656. package/dist/tools/fallback-provider-key-store.d.ts.map +0 -1
  657. package/dist/tools/fallback-system-config-view-tool.d.ts.map +0 -1
  658. package/dist/tools/index.d.ts.map +0 -1
  659. package/dist/tools/index.js.map +0 -7
  660. package/dist/tools/mcp-control.d.ts.map +0 -1
  661. package/dist/tools/mcp-use.d.ts.map +0 -1
  662. package/dist/tools/one-shot-llm-tool.d.ts.map +0 -1
  663. package/dist/tools/plugin-manager.d.ts.map +0 -1
  664. package/dist/types/agent-bridge.d.ts.map +0 -1
  665. package/dist/types/attachment.d.ts.map +0 -1
  666. package/dist/types/autonomy.d.ts.map +0 -1
  667. package/dist/types/blocks.d.ts.map +0 -1
  668. package/dist/types/compactor.d.ts.map +0 -1
  669. package/dist/types/config/autonomy.d.ts.map +0 -1
  670. package/dist/types/config/context.d.ts.map +0 -1
  671. package/dist/types/config/fleet-chat.d.ts.map +0 -1
  672. package/dist/types/config/mcp-features.d.ts.map +0 -1
  673. package/dist/types/config/providers.d.ts.map +0 -1
  674. package/dist/types/config/root.d.ts.map +0 -1
  675. package/dist/types/config/runtime.d.ts.map +0 -1
  676. package/dist/types/config/skills-fleet-brain.d.ts.map +0 -1
  677. package/dist/types/config/tools.d.ts.map +0 -1
  678. package/dist/types/config.d.ts.map +0 -1
  679. package/dist/types/context-evidence.d.ts.map +0 -1
  680. package/dist/types/context-window.d.ts.map +0 -1
  681. package/dist/types/council.d.ts.map +0 -1
  682. package/dist/types/default-config.d.ts.map +0 -1
  683. package/dist/types/design-kit.d.ts.map +0 -1
  684. package/dist/types/error-handler.d.ts.map +0 -1
  685. package/dist/types/errors.d.ts.map +0 -1
  686. package/dist/types/file-event-record.d.ts.map +0 -1
  687. package/dist/types/hooks.d.ts.map +0 -1
  688. package/dist/types/index.d.ts.map +0 -1
  689. package/dist/types/index.js.map +0 -7
  690. package/dist/types/input-reader.d.ts.map +0 -1
  691. package/dist/types/logger.d.ts.map +0 -1
  692. package/dist/types/memory.d.ts.map +0 -1
  693. package/dist/types/messages.d.ts.map +0 -1
  694. package/dist/types/mode-prompts.d.ts.map +0 -1
  695. package/dist/types/mode.d.ts.map +0 -1
  696. package/dist/types/models-registry.d.ts.map +0 -1
  697. package/dist/types/multi-agent.d.ts.map +0 -1
  698. package/dist/types/observability.d.ts.map +0 -1
  699. package/dist/types/one-shot-llm.d.ts.map +0 -1
  700. package/dist/types/path-resolver.d.ts.map +0 -1
  701. package/dist/types/permission.d.ts.map +0 -1
  702. package/dist/types/plugin.d.ts.map +0 -1
  703. package/dist/types/prompt-registry.d.ts.map +0 -1
  704. package/dist/types/prompt.d.ts.map +0 -1
  705. package/dist/types/provider-runner.d.ts.map +0 -1
  706. package/dist/types/provider.d.ts.map +0 -1
  707. package/dist/types/quota-regex.d.ts.map +0 -1
  708. package/dist/types/renderer.d.ts.map +0 -1
  709. package/dist/types/retry-policy.d.ts.map +0 -1
  710. package/dist/types/secret-scrubber.d.ts.map +0 -1
  711. package/dist/types/secret-vault.d.ts.map +0 -1
  712. package/dist/types/selector.d.ts.map +0 -1
  713. package/dist/types/session-markers.d.ts.map +0 -1
  714. package/dist/types/session-markers.js.map +0 -7
  715. package/dist/types/session-reader.d.ts.map +0 -1
  716. package/dist/types/session-rewinder.d.ts.map +0 -1
  717. package/dist/types/session.d.ts.map +0 -1
  718. package/dist/types/side-effect.d.ts.map +0 -1
  719. package/dist/types/skill.d.ts.map +0 -1
  720. package/dist/types/slash-command.d.ts.map +0 -1
  721. package/dist/types/spec.d.ts.map +0 -1
  722. package/dist/types/system-prompt-contributor.d.ts.map +0 -1
  723. package/dist/types/system-prompt.d.ts.map +0 -1
  724. package/dist/types/task-graph.d.ts.map +0 -1
  725. package/dist/types/token-counter.d.ts.map +0 -1
  726. package/dist/types/tool-executor.d.ts.map +0 -1
  727. package/dist/types/tool-markers.d.ts.map +0 -1
  728. package/dist/types/tool.d.ts.map +0 -1
  729. package/dist/types/utility-types.d.ts.map +0 -1
  730. package/dist/utils/assert-never.d.ts.map +0 -1
  731. package/dist/utils/atomic-write.d.ts.map +0 -1
  732. package/dist/utils/cache-key.d.ts.map +0 -1
  733. package/dist/utils/child-env.d.ts.map +0 -1
  734. package/dist/utils/color.d.ts.map +0 -1
  735. package/dist/utils/compaction-preview.d.ts.map +0 -1
  736. package/dist/utils/config-backup.d.ts.map +0 -1
  737. package/dist/utils/config-json.d.ts.map +0 -1
  738. package/dist/utils/connectivity.d.ts.map +0 -1
  739. package/dist/utils/context-breakdown.d.ts.map +0 -1
  740. package/dist/utils/context-evidence.d.ts.map +0 -1
  741. package/dist/utils/continuous-memory-profiler.d.ts.map +0 -1
  742. package/dist/utils/crash-shield.d.ts.map +0 -1
  743. package/dist/utils/deep-merge.d.ts.map +0 -1
  744. package/dist/utils/diff.d.ts.map +0 -1
  745. package/dist/utils/error.d.ts.map +0 -1
  746. package/dist/utils/error.js.map +0 -7
  747. package/dist/utils/expect-defined.d.ts.map +0 -1
  748. package/dist/utils/expect-defined.js.map +0 -7
  749. package/dist/utils/glob-expand.d.ts.map +0 -1
  750. package/dist/utils/glob-match.d.ts.map +0 -1
  751. package/dist/utils/heap-watchdog-types.d.ts.map +0 -1
  752. package/dist/utils/heap-watchdog.d.ts.map +0 -1
  753. package/dist/utils/heap-watchdog.js.map +0 -7
  754. package/dist/utils/incoming-images.d.ts.map +0 -1
  755. package/dist/utils/index.d.ts.map +0 -1
  756. package/dist/utils/index.js.map +0 -7
  757. package/dist/utils/instruction-file.d.ts.map +0 -1
  758. package/dist/utils/ip-guard.d.ts.map +0 -1
  759. package/dist/utils/json-repair.d.ts.map +0 -1
  760. package/dist/utils/json-schema-validate.d.ts.map +0 -1
  761. package/dist/utils/lru-cache.d.ts.map +0 -1
  762. package/dist/utils/memory-flight-recorder.d.ts.map +0 -1
  763. package/dist/utils/merge-custom-models.d.ts.map +0 -1
  764. package/dist/utils/merge-models-payload.d.ts.map +0 -1
  765. package/dist/utils/message-invariants.d.ts.map +0 -1
  766. package/dist/utils/newline-normalize.d.ts.map +0 -1
  767. package/dist/utils/perf-profile.d.ts.map +0 -1
  768. package/dist/utils/pid.d.ts.map +0 -1
  769. package/dist/utils/project-identity.d.ts.map +0 -1
  770. package/dist/utils/project-watch.d.ts.map +0 -1
  771. package/dist/utils/regex-guard.d.ts.map +0 -1
  772. package/dist/utils/safe-json.d.ts.map +0 -1
  773. package/dist/utils/sage-output-block.d.ts.map +0 -1
  774. package/dist/utils/session-scoped-path.d.ts.map +0 -1
  775. package/dist/utils/sleep.d.ts.map +0 -1
  776. package/dist/utils/slug.d.ts.map +0 -1
  777. package/dist/utils/socket-path.d.ts.map +0 -1
  778. package/dist/utils/sqlite-warning.d.ts.map +0 -1
  779. package/dist/utils/string.d.ts.map +0 -1
  780. package/dist/utils/task-format.d.ts.map +0 -1
  781. package/dist/utils/term.d.ts.map +0 -1
  782. package/dist/utils/todos-format.d.ts.map +0 -1
  783. package/dist/utils/token-estimate.d.ts.map +0 -1
  784. package/dist/utils/tool-description-mode.d.ts.map +0 -1
  785. package/dist/utils/tool-name.d.ts.map +0 -1
  786. package/dist/utils/tool-output-serializer.d.ts.map +0 -1
  787. package/dist/utils/tool-result-render-mode.d.ts.map +0 -1
  788. package/dist/utils/tool-subject.d.ts.map +0 -1
  789. package/dist/utils/tool-wire-compact.d.ts.map +0 -1
  790. package/dist/utils/tree-kill.d.ts.map +0 -1
  791. package/dist/utils/tree-kill.js.map +0 -7
  792. package/dist/utils/ulid.d.ts.map +0 -1
  793. package/dist/utils/walk-ignore.d.ts.map +0 -1
  794. package/dist/utils/wstack-paths.d.ts.map +0 -1
  795. package/dist/worktree/index.d.ts.map +0 -1
  796. package/dist/worktree/index.js.map +0 -7
  797. package/dist/worktree/worktree-git.d.ts.map +0 -1
  798. package/dist/worktree/worktree-managed-ops.d.ts.map +0 -1
  799. package/dist/worktree/worktree-manager.d.ts.map +0 -1
  800. package/dist/worktree/worktree-types.d.ts.map +0 -1
@@ -295,7 +295,7 @@ var InMemoryAgentBridge = class {
295
295
  });
296
296
  }
297
297
  this.inflightGuards.add(correlationId);
298
- return new Promise((resolve16, reject) => {
298
+ return new Promise((resolve17, reject) => {
299
299
  const timer = setTimeout(() => {
300
300
  this.inflightGuards.delete(correlationId);
301
301
  this.pendingRequests.delete(correlationId);
@@ -314,7 +314,7 @@ var InMemoryAgentBridge = class {
314
314
  return;
315
315
  }
316
316
  this.pendingRequests.set(correlationId, {
317
- resolve: resolve16,
317
+ resolve: resolve17,
318
318
  reject,
319
319
  timer
320
320
  });
@@ -663,13 +663,13 @@ var SubagentBudget = class _SubagentBudget {
663
663
  if (!bus?.hasListenerFor("budget.threshold_reached")) {
664
664
  return Promise.resolve("stop");
665
665
  }
666
- return new Promise((resolve16) => {
666
+ return new Promise((resolve17) => {
667
667
  let resolved = false;
668
668
  const respond = (d) => {
669
669
  if (resolved) return;
670
670
  resolved = true;
671
671
  clearTimeout(fallback);
672
- resolve16(d);
672
+ resolve17(d);
673
673
  };
674
674
  const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
675
675
  const sessionId = this.currentSessionId();
@@ -808,7 +808,26 @@ var SubagentBudget = class _SubagentBudget {
808
808
  var ToolCapabilities = {
809
809
  /** Can execute arbitrary commands in the user's shell (the `bash` tool). */
810
810
  SHELL_ARBITRARY: "shell.arbitrary",
811
- /** Can execute a restricted set of commands (the `exec` tool). */
811
+ /**
812
+ * Can execute the `exec` tool's allowlisted commands.
813
+ *
814
+ * "Restricted" describes the command NAME check, not the resulting power.
815
+ * Treat this as equivalent to {@link SHELL_ARBITRARY} when deciding what to
816
+ * grant: the allowlist includes general-purpose interpreters (`node`,
817
+ * `python`, `perl`, `ruby`) and, on Windows, the platform shells (`cmd`,
818
+ * `powershell`, `pwsh`) — deliberately, since without them `exec` cannot run
819
+ * `dir`/`type`/any cmdlet. `node -e …` or `cmd /c …` therefore reaches
820
+ * arbitrary execution, and per-argument denylisting cannot close that while
821
+ * the shells remain (blocking `node -e` still leaves `cmd /c node -e`).
822
+ *
823
+ * What actually bounds this is downstream, not the name check: the permission
824
+ * policy reconstructs the full command line and runs it through the
825
+ * destructive classifier, so destructive calls still require confirmation.
826
+ *
827
+ * Granting `shell.restricted` while withholding `shell.arbitrary` therefore
828
+ * buys no meaningful reduction in blast radius. Documented rather than
829
+ * silently implied, because the two read as a narrow/wide pair (WS-083).
830
+ */
812
831
  SHELL_RESTRICTED: "shell.restricted",
813
832
  /** Can run a restricted project formatter/linter-style command. */
814
833
  SHELL_EXEC: "shell.exec",
@@ -5284,7 +5303,7 @@ function createDelegateTool(opts) {
5284
5303
  };
5285
5304
  }
5286
5305
  async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abortSignal) {
5287
- return new Promise((resolve16) => {
5306
+ return new Promise((resolve17) => {
5288
5307
  let settled = false;
5289
5308
  let timer;
5290
5309
  let offAbort = () => {
@@ -5297,7 +5316,7 @@ async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abo
5297
5316
  offIter();
5298
5317
  offProgress();
5299
5318
  offAbort();
5300
- resolve16(value);
5319
+ resolve17(value);
5301
5320
  };
5302
5321
  const arm = () => {
5303
5322
  if (timer) clearTimeout(timer);
@@ -7151,10 +7170,10 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
7151
7170
  pending: taskIds.filter((id) => !done.has(id))
7152
7171
  });
7153
7172
  }
7154
- return new Promise((resolve16) => {
7173
+ return new Promise((resolve17) => {
7155
7174
  const entry = {
7156
7175
  ids: new Set(taskIds),
7157
- resolve: (result) => resolve16({
7176
+ resolve: (result) => resolve17({
7158
7177
  completed: [result],
7159
7178
  pending: taskIds.filter((id) => id !== result.taskId)
7160
7179
  })
@@ -7162,7 +7181,7 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
7162
7181
  if (opts?.timeoutMs !== void 0) {
7163
7182
  entry.timer = setTimeout(() => {
7164
7183
  this.anyWaiters.delete(entry);
7165
- resolve16({ completed: [], pending: [...taskIds], timedOut: true });
7184
+ resolve17({ completed: [], pending: [...taskIds], timedOut: true });
7166
7185
  }, opts.timeoutMs);
7167
7186
  }
7168
7187
  this.anyWaiters.add(entry);
@@ -7236,6 +7255,25 @@ ${JSON.stringify(result.result, null, 2)}
7236
7255
  this.descriptions.delete(taskId);
7237
7256
  }
7238
7257
  }
7258
+ /**
7259
+ * Remove every task owned by `subagentId` from the descriptions/owners
7260
+ * indexes and return the removed task ids. Director.remove() calls this so
7261
+ * per-task state is reclaimed on the default FleetManager path too, where
7262
+ * the Director's manifestEntries map is never populated (fleet-spawn.ts:289
7263
+ * fills it only when !host.fleetManager) and the manifest-gated removeTasks
7264
+ * call would otherwise be skipped — leaking one description (a full task
7265
+ * brief, KB-scale) plus one owner entry per assigned task for the Director's
7266
+ * lifetime. The owners index is populated on every path (assign/retarget),
7267
+ * so this works with or without a FleetManager. Idempotent with removeTasks.
7268
+ */
7269
+ removeTasksOwnedBy(subagentId) {
7270
+ const removed = [];
7271
+ for (const [taskId, owner] of this.owners) {
7272
+ if (owner === subagentId) removed.push(taskId);
7273
+ }
7274
+ this.removeTasks(removed);
7275
+ return removed;
7276
+ }
7239
7277
  resolveWaitersOnShutdown() {
7240
7278
  for (const entry of [...this.anyWaiters]) {
7241
7279
  if (entry.timer) clearTimeout(entry.timer);
@@ -7261,11 +7299,11 @@ ${JSON.stringify(result.result, null, 2)}
7261
7299
  this.makeStoppedResult(taskId, "director", `Unknown task id "${taskId}" \u2014 never assigned`)
7262
7300
  );
7263
7301
  }
7264
- let resolve16;
7302
+ let resolve17;
7265
7303
  const promise = new Promise((done) => {
7266
- resolve16 = done;
7304
+ resolve17 = done;
7267
7305
  });
7268
- this.taskWaiters.set(taskId, { promise, resolve: resolve16 });
7306
+ this.taskWaiters.set(taskId, { promise, resolve: resolve17 });
7269
7307
  return promise;
7270
7308
  }
7271
7309
  recordAssignment(task) {
@@ -7431,12 +7469,13 @@ function rosterSummaryFromConfigs(roster) {
7431
7469
  // src/coordination/director-tools.ts
7432
7470
  import { randomUUID as randomUUID8 } from "node:crypto";
7433
7471
  import {
7434
- claimReadyTask,
7435
- finalizeTaskCompletion,
7472
+ completeKanbanDispatch,
7473
+ failKanbanDispatch,
7436
7474
  getBoard,
7437
7475
  heartbeatTaskAssignment,
7438
7476
  listReadyTasks,
7439
- transitionTask,
7477
+ reserveKanbanDispatch,
7478
+ startKanbanDispatch,
7440
7479
  updateTaskAssignment
7441
7480
  } from "@wrongstack/kanban";
7442
7481
 
@@ -8577,9 +8616,6 @@ function makeFleetTool(director) {
8577
8616
  }
8578
8617
 
8579
8618
  // src/coordination/director-tools.ts
8580
- function nowIso() {
8581
- return (/* @__PURE__ */ new Date()).toISOString();
8582
- }
8583
8619
  function makeSpawnTool(director, roster) {
8584
8620
  const dispatchCatalog = () => {
8585
8621
  if (!roster) return void 0;
@@ -8809,13 +8845,6 @@ function makeKanbanQueueTool(director, roster) {
8809
8845
  i.heartbeatIntervalMs ?? Math.floor(leaseTtlMs / 2)
8810
8846
  );
8811
8847
  const effectiveLeaseTtlMs = Math.max(leaseTtlMs, heartbeatIntervalMs * 2);
8812
- const claimedAt = nowIso();
8813
- const leaseSeeding = {
8814
- leaseId: randomUUID8(),
8815
- claimedAt,
8816
- heartbeatAt: claimedAt,
8817
- leaseExpiresAt: new Date(Date.now() + effectiveLeaseTtlMs).toISOString()
8818
- };
8819
8848
  const candidateTaskIds = i.taskId !== void 0 ? [i.taskId] : i.query ? (await listReadyTasks(projectRoot, {
8820
8849
  ...i.boardId !== void 0 ? { boardId: i.boardId } : {}
8821
8850
  })).filter((candidate) => matchesKanbanQueueQuery(candidate.task, i.query ?? "")).slice(0, maxTasks).map((candidate) => candidate.task.id) : void 0;
@@ -8827,24 +8856,28 @@ function makeKanbanQueueTool(director, roster) {
8827
8856
  const candidateTaskId = candidateTaskIds?.[index];
8828
8857
  if (candidateTaskIds && !candidateTaskId) break;
8829
8858
  if (candidateTaskId && budgetRejectedTaskIds.has(candidateTaskId)) continue;
8830
- const claim = await claimReadyTask(projectRoot, {
8859
+ const reserved = await reserveKanbanDispatch(projectRoot, {
8831
8860
  ...i.boardId !== void 0 ? { boardId: i.boardId } : {},
8832
8861
  ...candidateTaskId !== void 0 ? { taskId: candidateTaskId } : {},
8833
- ...i.agentId !== void 0 ? { agentId: i.agentId } : {},
8834
- ...i.name !== void 0 ? { name: i.name } : {},
8835
- ...i.role !== void 0 ? { role: i.role } : {},
8836
- ...i.provider !== void 0 ? { provider: i.provider } : {},
8837
- ...i.model !== void 0 ? { model: i.model } : {},
8838
- ...i.fallbackModels !== void 0 ? { fallbackModels: i.fallbackModels } : {},
8839
- ...i.tools !== void 0 ? { tools: i.tools } : {},
8840
- ...i.allowedCapabilities !== void 0 ? { allowedCapabilities: i.allowedCapabilities } : {},
8841
- ...leaseSeeding !== void 0 ? leaseSeeding : {},
8842
- status: "queued"
8862
+ routing: {
8863
+ ...i.agentId !== void 0 ? { agentId: i.agentId } : {},
8864
+ ...i.name !== void 0 ? { name: i.name } : {},
8865
+ ...i.role !== void 0 ? { role: i.role } : {},
8866
+ ...i.provider !== void 0 ? { provider: i.provider } : {},
8867
+ ...i.model !== void 0 ? { model: i.model } : {},
8868
+ ...i.fallbackModels !== void 0 ? { fallbackModels: i.fallbackModels } : {},
8869
+ ...i.tools !== void 0 ? { tools: i.tools } : {},
8870
+ ...i.allowedCapabilities !== void 0 ? { allowedCapabilities: i.allowedCapabilities } : {}
8871
+ },
8872
+ leaseTtlMs: effectiveLeaseTtlMs,
8873
+ heartbeatIntervalMs
8843
8874
  });
8844
- if (!claim) {
8875
+ if (!reserved) {
8845
8876
  if (candidateTaskIds) continue;
8846
8877
  break;
8847
8878
  }
8879
+ const ourLeaseId = reserved.lease.leaseId;
8880
+ const claim = reserved;
8848
8881
  let subagentId;
8849
8882
  let runTaskId;
8850
8883
  const costCeiling = claim.task.assignment?.costCeilingUsd;
@@ -8862,7 +8895,7 @@ function makeKanbanQueueTool(director, roster) {
8862
8895
  status: "failed",
8863
8896
  error: budgetError
8864
8897
  },
8865
- { expectedLeaseId: leaseSeeding.leaseId }
8898
+ { expectedLeaseId: ourLeaseId }
8866
8899
  );
8867
8900
  errors.push({
8868
8901
  taskId: claim.task.id,
@@ -8888,8 +8921,8 @@ function makeKanbanQueueTool(director, roster) {
8888
8921
  // 10s lease, letting the lease expire before its first renewal.
8889
8922
  heartbeatIntervalMs,
8890
8923
  leaseTtlMs: effectiveLeaseTtlMs,
8891
- leaseId: leaseSeeding.leaseId,
8892
- leaseExpiresAt: leaseSeeding.leaseExpiresAt
8924
+ leaseId: ourLeaseId,
8925
+ leaseExpiresAt: reserved.lease.leaseExpiresAt
8893
8926
  }),
8894
8927
  ...i.maxToolCalls !== void 0 ? { maxToolCalls: i.maxToolCalls } : {},
8895
8928
  ...i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : {},
@@ -8900,33 +8933,19 @@ function makeKanbanQueueTool(director, roster) {
8900
8933
  taskId: claim.task.id,
8901
8934
  origin: claim.task.origin,
8902
8935
  projectRoot,
8903
- leaseId: leaseSeeding.leaseId
8936
+ leaseId: ourLeaseId
8904
8937
  }
8905
8938
  }
8906
8939
  };
8907
- let dispatched = await updateTaskAssignment(
8908
- projectRoot,
8909
- claim.board.id,
8910
- claim.task.id,
8911
- {
8912
- ...claim.task.assignment ?? {},
8913
- status: "running",
8914
- subagentId,
8915
- runTaskId: dispatchTaskId,
8916
- // Renew the lease from the actual dispatch moment, not from claim
8917
- // time. The claim-time lease starts before spawn + queueing, so
8918
- // `timeoutMs + 60_000` can expire while the worker is still
8919
- // queued or running, letting recover_stale reclaim and duplicate
8920
- // the assignment (see auto-extend.ts: timeout extensions can push
8921
- // execution well past the original timeoutMs). Both heartbeatAt
8922
- // and leaseExpiresAt are stamped from now so the worker has a
8923
- // full lease window from the moment it was actually dispatched.
8924
- heartbeatAt: (/* @__PURE__ */ new Date()).toISOString(),
8925
- leaseExpiresAt: new Date(Date.now() + effectiveLeaseTtlMs).toISOString()
8926
- },
8927
- { expectedLeaseId: leaseSeeding.leaseId }
8928
- );
8929
- if (dispatched === null) {
8940
+ const started = await startKanbanDispatch(projectRoot, {
8941
+ boardId: claim.board.id,
8942
+ taskId: claim.task.id,
8943
+ leaseId: ourLeaseId,
8944
+ actor: i.agentId ?? "director-agent",
8945
+ subagentId,
8946
+ runTaskId: dispatchTaskId
8947
+ });
8948
+ if (started === null) {
8930
8949
  try {
8931
8950
  await director.terminate(subagentId);
8932
8951
  } catch (cleanupErr) {
@@ -8942,27 +8961,6 @@ function makeKanbanQueueTool(director, roster) {
8942
8961
  });
8943
8962
  continue;
8944
8963
  }
8945
- if (dispatched?.lifecycle?.mode === "managed") {
8946
- const mTask = dispatched.tasks.find(
8947
- (candidate) => candidate.id === claim.task.id
8948
- );
8949
- if (mTask?.lifecycle?.currentStage === "todo") {
8950
- try {
8951
- const tr = await transitionTask(
8952
- projectRoot,
8953
- claim.board.id,
8954
- claim.task.id,
8955
- {
8956
- to: "running",
8957
- actor: i.agentId ?? "director-agent",
8958
- comment: "Dispatched by kanban_queue."
8959
- }
8960
- );
8961
- if (tr) dispatched = tr.board;
8962
- } catch {
8963
- }
8964
- }
8965
- }
8966
8964
  await director.assign(taskSpec);
8967
8965
  runTaskId = dispatchTaskId;
8968
8966
  dispatches.push({
@@ -8971,6 +8969,7 @@ function makeKanbanQueueTool(director, roster) {
8971
8969
  title: claim.task.title,
8972
8970
  subagentId,
8973
8971
  runTaskId,
8972
+ leaseId: ourLeaseId,
8974
8973
  ...config.provider !== void 0 ? { provider: config.provider } : {},
8975
8974
  ...config.model !== void 0 ? { model: config.model } : {}
8976
8975
  });
@@ -8983,24 +8982,17 @@ function makeKanbanQueueTool(director, roster) {
8983
8982
  message = `${message}; cleanup failed for spawned subagent ${subagentId}: ${toErrorMessage(cleanupErr)}`;
8984
8983
  }
8985
8984
  }
8986
- await updateTaskAssignment(
8987
- projectRoot,
8988
- claim.board.id,
8989
- claim.task.id,
8990
- {
8991
- ...claim.task.assignment ?? {},
8992
- status: "failed",
8993
- ...subagentId !== void 0 ? { subagentId } : {},
8994
- ...runTaskId !== void 0 ? { runTaskId } : {},
8995
- error: message
8996
- },
8997
- { expectedLeaseId: leaseSeeding.leaseId }
8998
- );
8985
+ await failKanbanDispatch(projectRoot, {
8986
+ boardId: claim.board.id,
8987
+ taskId: claim.task.id,
8988
+ leaseId: ourLeaseId,
8989
+ actor: "kanban-queue",
8990
+ error: message
8991
+ });
8999
8992
  errors.push({ taskId: claim.task.id, error: message });
9000
8993
  }
9001
8994
  }
9002
8995
  if (!i.awaitCompletion && dispatches.length > 0 && typeof director.fleet?.subscribe === "function") {
9003
- const ourLeaseId = leaseSeeding.leaseId;
9004
8996
  const disposers = /* @__PURE__ */ new Map();
9005
8997
  const renewalStartedAt = Date.now();
9006
8998
  const maxRenewalWindowMs = 24 * 60 * 6e4;
@@ -9023,7 +9015,7 @@ function makeKanbanQueueTool(director, roster) {
9023
9015
  taskId: dispatch.taskId,
9024
9016
  subagentId: dispatch.subagentId,
9025
9017
  runTaskId: dispatch.runTaskId,
9026
- ourLeaseId,
9018
+ ourLeaseId: dispatch.leaseId,
9027
9019
  refreshedExpiry,
9028
9020
  director,
9029
9021
  disposers
@@ -9044,7 +9036,6 @@ function makeKanbanQueueTool(director, roster) {
9044
9036
  }
9045
9037
  let results;
9046
9038
  if (i.awaitCompletion && dispatches.length > 0) {
9047
- const ourLeaseId = leaseSeeding.leaseId;
9048
9039
  const runTaskIds = new Set(dispatches.map((dispatch) => dispatch.runTaskId));
9049
9040
  let heartbeatRunning = false;
9050
9041
  let heartbeatInFlight = Promise.resolve();
@@ -9056,7 +9047,7 @@ function makeKanbanQueueTool(director, roster) {
9056
9047
  const board = await getBoard(projectRoot, dispatch.boardId);
9057
9048
  const liveTask = board?.tasks.find((t) => t.id === dispatch.taskId);
9058
9049
  const liveLeaseId = liveTask?.assignment?.leaseId;
9059
- if (liveLeaseId !== void 0 && liveLeaseId !== ourLeaseId) {
9050
+ if (liveLeaseId !== void 0 && liveLeaseId !== dispatch.leaseId) {
9060
9051
  await director.terminate(dispatch.subagentId).catch(() => {
9061
9052
  });
9062
9053
  runTaskIds.delete(dispatch.runTaskId);
@@ -9067,7 +9058,7 @@ function makeKanbanQueueTool(director, roster) {
9067
9058
  try {
9068
9059
  await heartbeatTaskAssignment(projectRoot, dispatch.boardId, dispatch.taskId, {
9069
9060
  leaseExpiresAt: refreshedExpiry,
9070
- expectedLeaseId: ourLeaseId
9061
+ expectedLeaseId: dispatch.leaseId
9071
9062
  });
9072
9063
  } catch {
9073
9064
  }
@@ -9090,29 +9081,36 @@ function makeKanbanQueueTool(director, roster) {
9090
9081
  const dispatch = dispatches.find((candidate) => candidate.runTaskId === result.taskId);
9091
9082
  if (!dispatch) continue;
9092
9083
  runTaskIds.delete(dispatch.runTaskId);
9093
- const terminalWrite = await updateTaskAssignment(
9094
- projectRoot,
9095
- dispatch.boardId,
9096
- dispatch.taskId,
9097
- {
9098
- status: result.status === "success" ? "completed" : "failed",
9099
- subagentId: result.subagentId,
9100
- runTaskId: result.taskId,
9101
- ...result.status === "success" ? { lastResult: resultToText(result) } : { error: resultErrorText(result) }
9102
- },
9103
- { expectedLeaseId: ourLeaseId }
9104
- );
9105
- if (result.status === "success" && terminalWrite) {
9106
- try {
9107
- await finalizeTaskCompletion(projectRoot, dispatch.boardId, dispatch.taskId, {
9108
- eventContext: { actor: "kanban-queue" }
9084
+ if (result.status === "success") {
9085
+ const completed = await completeKanbanDispatch(projectRoot, {
9086
+ boardId: dispatch.boardId,
9087
+ taskId: dispatch.taskId,
9088
+ leaseId: dispatch.leaseId,
9089
+ actor: "kanban-queue",
9090
+ result: resultToText(result)
9091
+ });
9092
+ if (!completed) {
9093
+ resultFailures.push({
9094
+ taskId: dispatch.taskId,
9095
+ runTaskId: result.taskId,
9096
+ status: "failed",
9097
+ error: "Lease lost during completion write (reassigned by recovery)."
9109
9098
  });
9110
- } catch (error2) {
9099
+ }
9100
+ } else {
9101
+ const failed = await failKanbanDispatch(projectRoot, {
9102
+ boardId: dispatch.boardId,
9103
+ taskId: dispatch.taskId,
9104
+ leaseId: dispatch.leaseId,
9105
+ actor: "kanban-queue",
9106
+ error: resultErrorText(result)
9107
+ });
9108
+ if (!failed) {
9111
9109
  resultFailures.push({
9112
9110
  taskId: dispatch.taskId,
9113
9111
  runTaskId: result.taskId,
9114
9112
  status: "failed",
9115
- error: `completion gate failed: ${toErrorMessage(error2)}`
9113
+ error: "Lease lost during failure write (reassigned by recovery)."
9116
9114
  });
9117
9115
  }
9118
9116
  }
@@ -11475,12 +11473,12 @@ async function executeSubagentWithTimeout({
11475
11473
  }
11476
11474
  return new Promise((resolveDecision) => {
11477
11475
  let settled = false;
11478
- const resolve16 = (d) => {
11476
+ const resolve17 = (d) => {
11479
11477
  if (settled) return;
11480
11478
  settled = true;
11481
11479
  resolveDecision(d);
11482
11480
  };
11483
- const fallback = setTimeout(() => resolve16("stop"), DECISION_TIMEOUT_MS);
11481
+ const fallback = setTimeout(() => resolve17("stop"), DECISION_TIMEOUT_MS);
11484
11482
  const sessionId = currentSessionId();
11485
11483
  budget._events?.emit("budget.threshold_reached", {
11486
11484
  ...sessionId ? { sessionId } : {},
@@ -11490,11 +11488,11 @@ async function executeSubagentWithTimeout({
11490
11488
  timeoutMs: DECISION_TIMEOUT_MS,
11491
11489
  extend: (extra) => {
11492
11490
  clearTimeout(fallback);
11493
- queueMicrotask(() => resolve16({ extend: extra }));
11491
+ queueMicrotask(() => resolve17({ extend: extra }));
11494
11492
  },
11495
11493
  deny: () => {
11496
11494
  clearTimeout(fallback);
11497
- resolve16("stop");
11495
+ resolve17("stop");
11498
11496
  }
11499
11497
  });
11500
11498
  });
@@ -11881,7 +11879,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11881
11879
  taskIds.map((id) => {
11882
11880
  const cached = this.completedResults.find((r) => r.taskId === id);
11883
11881
  if (cached) return cached;
11884
- return new Promise((resolve16, reject) => {
11882
+ return new Promise((resolve17, reject) => {
11885
11883
  const timeout = setTimeout(() => {
11886
11884
  this.off("task.completed", handler);
11887
11885
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -11890,7 +11888,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11890
11888
  if (result.taskId === id) {
11891
11889
  clearTimeout(timeout);
11892
11890
  this.off("task.completed", handler);
11893
- resolve16(result);
11891
+ resolve17(result);
11894
11892
  }
11895
11893
  };
11896
11894
  this.on("task.completed", handler);
@@ -11915,13 +11913,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11915
11913
  const done = new Set(completed.map((r) => r.taskId));
11916
11914
  return { completed, pending: taskIds.filter((id) => !done.has(id)) };
11917
11915
  }
11918
- return new Promise((resolve16) => {
11916
+ return new Promise((resolve17) => {
11919
11917
  let timer;
11920
11918
  const handler = ({ result }) => {
11921
11919
  if (!ids.has(result.taskId)) return;
11922
11920
  if (timer) clearTimeout(timer);
11923
11921
  this.off("task.completed", handler);
11924
- resolve16({
11922
+ resolve17({
11925
11923
  completed: [result],
11926
11924
  pending: taskIds.filter((id) => id !== result.taskId)
11927
11925
  });
@@ -11929,7 +11927,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11929
11927
  if (opts?.timeoutMs !== void 0) {
11930
11928
  timer = setTimeout(() => {
11931
11929
  this.off("task.completed", handler);
11932
- resolve16({ completed: [], pending: [...taskIds], timedOut: true });
11930
+ resolve17({ completed: [], pending: [...taskIds], timedOut: true });
11933
11931
  }, opts.timeoutMs);
11934
11932
  }
11935
11933
  this.on("task.completed", handler);
@@ -12497,67 +12495,118 @@ import * as path21 from "node:path";
12497
12495
  import * as fsp17 from "node:fs/promises";
12498
12496
  import * as path20 from "node:path";
12499
12497
 
12498
+ // src/security/file-permissions.ts
12499
+ import { chmod } from "node:fs/promises";
12500
+ var SECRET_FILE_MODE = 384;
12501
+ async function restrictFilePermissions(filePath, opts) {
12502
+ const label = opts?.label ?? "file-permissions";
12503
+ const warn = opts?.warn ?? ((msg) => console.warn(msg));
12504
+ if (process.platform === "win32") {
12505
+ try {
12506
+ const { execFile: execFile2 } = await import("node:child_process");
12507
+ const { promisify: promisify2 } = await import("node:util");
12508
+ const execFileAsync = promisify2(execFile2);
12509
+ const user = windowsAccountName();
12510
+ if (!user) {
12511
+ warn(
12512
+ `[${label}] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
12513
+ );
12514
+ return;
12515
+ }
12516
+ await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`], {
12517
+ windowsHide: true
12518
+ });
12519
+ } catch {
12520
+ warn(
12521
+ `[${label}] Could not restrict permissions on ${filePath} \u2014 it may be readable by other users on this system.`
12522
+ );
12523
+ }
12524
+ } else {
12525
+ try {
12526
+ await chmod(filePath, SECRET_FILE_MODE);
12527
+ } catch {
12528
+ }
12529
+ }
12530
+ }
12531
+ function windowsAccountName() {
12532
+ const username = process.env.USERNAME || process.env.USER;
12533
+ if (!username || username.includes("\0")) return void 0;
12534
+ const domain = process.env.USERDOMAIN;
12535
+ if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
12536
+ return username;
12537
+ }
12538
+
12500
12539
  // src/security/secret-scrubber.ts
12501
12540
  var PATTERNS = [
12502
12541
  // Anchored at the start where possible so partial matches inside larger
12503
12542
  // strings don't trigger false positives.
12504
12543
  {
12505
12544
  type: "anthropic_key",
12506
- regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g
12545
+ regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g,
12546
+ anchor: "sk-ant-"
12507
12547
  },
12508
- { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
12509
- { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
12510
- { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
12511
- { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
12512
- { type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
12513
- { type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
12548
+ { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
12549
+ { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
12550
+ { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
12551
+ { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
12552
+ { type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g, anchor: "AIza" },
12553
+ { type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g, anchor: "xox" },
12514
12554
  {
12515
12555
  type: "stripe_key",
12516
- regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g
12556
+ regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g,
12557
+ anchor: "sk_"
12517
12558
  },
12518
12559
  {
12519
12560
  type: "twilio_sid",
12520
- regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g
12561
+ regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g,
12562
+ anchor: "AC"
12521
12563
  },
12522
12564
  {
12523
12565
  type: "telegram_bot_token",
12524
12566
  // Telegram tokens are of the form bot<digits>:<alphanum> in URL paths
12525
- regex: /\/bot\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
12567
+ regex: /\/bot\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
12568
+ anchor: "/bot"
12526
12569
  },
12527
12570
  {
12528
12571
  type: "jwt",
12529
12572
  // Anchored: look for literal "eyJ" which is unambiguous for JWT header
12530
- regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
12573
+ regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g,
12574
+ anchor: "eyJ"
12531
12575
  },
12532
12576
  {
12533
12577
  type: "private_key",
12534
12578
  // Anchored: start must be BEGIN, end must be END with no extra dashes after END
12535
- regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END[^-]*-----(?:\n|$)/g
12579
+ regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END[^-]*-----(?:\n|$)/g,
12580
+ anchor: "-----BEGIN"
12536
12581
  },
12537
- { type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g },
12538
- { type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
12539
- { type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
12540
- { type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
12582
+ { type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g, anchor: "mongodb" },
12583
+ { type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g, anchor: "postgres" },
12584
+ { type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g, anchor: "mysql://" },
12585
+ { type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g, anchor: "redis://" },
12541
12586
  // AI/ML provider keys — modern LLM services with well-known prefixes
12542
12587
  {
12543
12588
  type: "huggingface_token",
12544
12589
  // HuggingFace tokens: hf_ followed by 34 alphanumeric chars
12545
- regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g
12590
+ regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g,
12591
+ anchor: "hf_"
12546
12592
  },
12547
12593
  {
12548
12594
  type: "replicate_token",
12549
12595
  // Replicate tokens: r8_ followed by 40+ alphanumeric chars
12550
- regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
12596
+ regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
12597
+ anchor: "r8_"
12551
12598
  },
12552
12599
  {
12553
12600
  type: "perplexity_key",
12554
12601
  // Perplexity API keys: pplx- followed by 40+ alphanumeric chars
12555
- regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
12602
+ regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
12603
+ anchor: "pplx-"
12556
12604
  },
12557
12605
  {
12558
12606
  type: "groq_key",
12559
12607
  // Groq API keys: gsk_ followed by 40+ alphanumeric chars
12560
- regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
12608
+ regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
12609
+ anchor: "gsk_"
12561
12610
  },
12562
12611
  {
12563
12612
  type: "bearer_token",
@@ -12570,7 +12619,8 @@ var PATTERNS = [
12570
12619
  // tokens sharing a single delimiter (`Bearer a… Bearer b…`) must both be
12571
12620
  // redacted. A consuming trailing delimiter would eat the separator the
12572
12621
  // next match needs for its leading anchor, leaking the second token.
12573
- regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?=$|[^A-Za-z0-9_.~+/-])/g
12622
+ regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?=$|[^A-Za-z0-9_.~+/-])/g,
12623
+ anchor: "Bearer"
12574
12624
  },
12575
12625
  {
12576
12626
  type: "high_entropy_env",
@@ -12586,7 +12636,89 @@ var PATTERNS = [
12586
12636
  // replacement so the separator between adjacent secrets is preserved
12587
12637
  // rather than collapsed. Capture groups are therefore: 1=leading
12588
12638
  // delimiter, 2=key name, 3=value.
12589
- regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g
12639
+ regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
12640
+ anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
12641
+ },
12642
+ // ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
12643
+ // The plugin runtime carried 37 patterns while this scrubber — the one that
12644
+ // guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
12645
+ // audit — carried 22. The plugin side already had a parity test; it just did
12646
+ // not cover core. Most consequential: WrongStack mints `gho_` tokens itself
12647
+ // in the Copilot OAuth flow, and `gh[ousr]_` was absent here.
12648
+ {
12649
+ type: "github_oauth_token",
12650
+ regex: /(?<![A-Za-z0-9])gh[ousr]_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g,
12651
+ anchor: ["gho_", "ghu_", "ghs_", "ghr_"]
12652
+ },
12653
+ {
12654
+ type: "gitlab_pat",
12655
+ regex: /(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
12656
+ anchor: "glpat-"
12657
+ },
12658
+ {
12659
+ type: "gitlab_runner_token",
12660
+ regex: /(?<![A-Za-z0-9])glrt-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
12661
+ anchor: "glrt-"
12662
+ },
12663
+ {
12664
+ type: "npm_token",
12665
+ regex: /(?<![A-Za-z0-9])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g,
12666
+ anchor: "npm_"
12667
+ },
12668
+ {
12669
+ type: "slack_app_token",
12670
+ regex: /(?<![A-Za-z0-9-])xapp-\d-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g,
12671
+ anchor: "xapp-"
12672
+ },
12673
+ {
12674
+ type: "slack_webhook",
12675
+ regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Za-z0-9_-]+\/B[A-Za-z0-9_-]+\/[A-Za-z0-9]{16,}/g,
12676
+ anchor: "hooks.slack.com"
12677
+ },
12678
+ {
12679
+ type: "sendgrid_key",
12680
+ regex: /(?<![A-Za-z0-9])SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
12681
+ anchor: "SG."
12682
+ },
12683
+ {
12684
+ type: "digitalocean_token",
12685
+ regex: /(?<![A-Za-z0-9])dop_v1_[a-f0-9]{64}(?![A-Za-z0-9])/g,
12686
+ anchor: "dop_v1_"
12687
+ },
12688
+ {
12689
+ type: "doppler_token",
12690
+ regex: /(?<![A-Za-z0-9])dp\.(?:pt|st|sa|scim|audit)\.[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
12691
+ anchor: "dp."
12692
+ },
12693
+ {
12694
+ type: "shopify_token",
12695
+ regex: /(?<![A-Za-z0-9])shp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}(?![A-Za-z0-9])/g,
12696
+ anchor: "shp"
12697
+ },
12698
+ {
12699
+ type: "docker_pat",
12700
+ regex: /(?<![A-Za-z0-9])dckr_pat_[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
12701
+ anchor: "dckr_pat_"
12702
+ },
12703
+ {
12704
+ type: "linear_key",
12705
+ regex: /(?<![A-Za-z0-9])lin_api_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
12706
+ anchor: "lin_api_"
12707
+ },
12708
+ {
12709
+ type: "atlassian_token",
12710
+ regex: /(?<![A-Za-z0-9])ATATT3[A-Za-z0-9_\-=]{40,}(?![A-Za-z0-9_\-=])/g,
12711
+ anchor: "ATATT3"
12712
+ },
12713
+ {
12714
+ type: "square_token",
12715
+ regex: /(?<![A-Za-z0-9])(?:sq0(?:atp|csp)-|EAAA)[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
12716
+ anchor: ["sq0atp-", "sq0csp-", "EAAA"]
12717
+ },
12718
+ {
12719
+ type: "google_oauth_client_secret",
12720
+ regex: /(?<![A-Za-z0-9_-])GOCSPX-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
12721
+ anchor: "GOCSPX-"
12590
12722
  }
12591
12723
  ];
12592
12724
  var SIMPLE_PATTERNS = PATTERNS.filter((p) => p.type !== "high_entropy_env");
@@ -12595,32 +12727,32 @@ var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").reg
12595
12727
  var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
12596
12728
  var SCRUB_CHUNK_BYTES = 64 * 1024;
12597
12729
  var SCRUB_OVERLAP_BYTES = 1024;
12730
+ var PATTERN_ANCHORS = [
12731
+ ...new Set(
12732
+ PATTERNS.flatMap(
12733
+ (pattern) => typeof pattern.anchor === "string" ? [pattern.anchor] : [...pattern.anchor]
12734
+ )
12735
+ )
12736
+ ];
12737
+ var JSON_KEY_ANCHORS = [
12738
+ '"apiKey"',
12739
+ '"api_key"',
12740
+ '"token"',
12741
+ '"secret"',
12742
+ '"password"',
12743
+ '"authorization"',
12744
+ '"bearer"',
12745
+ '"private_key"',
12746
+ '"access_token"',
12747
+ '"refresh_token"',
12748
+ '"client_secret"'
12749
+ ];
12750
+ var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
12598
12751
  function hasCredentialAnchors(text) {
12599
- return text.includes("-----BEGIN") || // Private keys (most unique → cheap reject)
12600
- text.includes("sk-") || // Anthropic + OpenAI keys
12601
- text.includes("sk_") || // Stripe live/test keys
12602
- text.includes("ghp_") || // GitHub PAT v1
12603
- text.includes("github_pat_") || // GitHub PAT v2
12604
- text.includes("eyJ") || // JWT
12605
- text.includes("AKIA") || // AWS access key
12606
- text.includes("AIza") || // GCP service key
12607
- text.includes("xox") || // Slack token (xoxa/xoxb/xoxp/xoxo/xoxs)
12608
- text.includes("Bearer ") || // Bearer token (space suffix reduces false positives)
12609
- text.includes("/bot") || // Telegram bot token (URL path pattern)
12610
- text.includes("hf_") || // HuggingFace token
12611
- text.includes("r8_") || // Replicate token
12612
- text.includes("pplx-") || // Perplexity API key
12613
- text.includes("gsk_") || // Groq API key
12614
- text.includes("_KEY=") || // High-entropy env vars: API_KEY=, SECRET_KEY=, ...
12615
- text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
12616
- text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
12617
- text.includes("_PASSWORD=") || // DB_PASSWORD=, ROOT_PASSWORD=, ...
12618
- text.includes("mongodb://") || text.includes("mongodb+srv://") || text.includes("postgres://") || text.includes("postgresql://") || text.includes("mysql://") || text.includes("redis://") || // JSON-style credential keys: tool outputs often serialise objects as
12619
- // raw JSON strings rather than parsed objects. The value may not start
12620
- // with a recognisable prefix (sk-, ghp_, etc.), so we anchor on common
12621
- // key names. The `"` prefix avoids matching prose that happens to mention
12622
- // these words — JSON serialisation always quotes string keys.
12623
- text.includes('"apiKey"') || text.includes('"api_key"') || text.includes('"token"') || text.includes('"secret"') || text.includes('"password"') || text.includes('"authorization"') || text.includes('"bearer"') || text.includes('"private_key"') || text.includes('"access_token"') || text.includes('"refresh_token"') || text.includes('"client_secret"');
12752
+ for (const anchor of ALL_ANCHORS) {
12753
+ if (text.includes(anchor)) return true;
12754
+ }
12755
+ return false;
12624
12756
  }
12625
12757
  var DefaultSecretScrubber = class {
12626
12758
  scrub(text) {
@@ -12736,6 +12868,11 @@ var SECRET_NAME_PARTS = [
12736
12868
  "SECRET",
12737
12869
  "PASSWORD",
12738
12870
  "PASSWD",
12871
+ // WRONGSTACK_VAULT_PASSPHRASE is the vault KEK. Without this entry it matched
12872
+ // no rule here and fell through to the `WRONGSTACK_` prefix allow-rule, so the
12873
+ // key that protects the credential vault was forwarded to every child process
12874
+ // — including the agent's own exec/bash tools and MCP stdio servers (WS-033).
12875
+ "PASSPHRASE",
12739
12876
  "AUTH",
12740
12877
  "CRED",
12741
12878
  "BEARER",
@@ -12922,6 +13059,65 @@ async function backupConfigFile(filePath, paths) {
12922
13059
  }
12923
13060
  }
12924
13061
 
13062
+ // src/utils/deep-merge.ts
13063
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
13064
+ "__proto__",
13065
+ "constructor",
13066
+ "prototype",
13067
+ "__defineGetter__",
13068
+ "__defineSetter__",
13069
+ "__lookupGetter__",
13070
+ "__lookupSetter__"
13071
+ ]);
13072
+ function isPrimitiveArray(a) {
13073
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
13074
+ }
13075
+ function deepMerge(base, patch, options = {}) {
13076
+ const {
13077
+ conflictResolution = "prefer-patch",
13078
+ arrayMode = "replace",
13079
+ protectProto = true,
13080
+ onNonPrimitiveArrayReplace
13081
+ } = options;
13082
+ if (typeof base !== "object" || base === null) {
13083
+ return conflictResolution === "prefer-patch" ? patch : base;
13084
+ }
13085
+ if (typeof patch !== "object" || patch === null) {
13086
+ return conflictResolution === "prefer-patch" ? patch : base;
13087
+ }
13088
+ if (Array.isArray(base) && Array.isArray(patch)) {
13089
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
13090
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
13091
+ }
13092
+ return conflictResolution === "prefer-patch" ? patch : base;
13093
+ }
13094
+ if (Array.isArray(base) || Array.isArray(patch)) {
13095
+ return conflictResolution === "prefer-patch" ? patch : base;
13096
+ }
13097
+ const baseObj = base;
13098
+ const patchObj = patch;
13099
+ const out = { ...baseObj };
13100
+ for (const [k, v] of Object.entries(patchObj)) {
13101
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
13102
+ const existing = out[k];
13103
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
13104
+ out[k] = deepMerge(existing, v, options);
13105
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
13106
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
13107
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
13108
+ }
13109
+ out[k] = deepMerge(existing, v, options);
13110
+ } else if (v !== void 0) {
13111
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
13112
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
13113
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
13114
+ }
13115
+ out[k] = v;
13116
+ }
13117
+ }
13118
+ return out;
13119
+ }
13120
+
12925
13121
  // src/utils/message-invariants.ts
12926
13122
  function repairToolUseAdjacency(messages) {
12927
13123
  const removedToolUses = [];
@@ -13072,6 +13268,14 @@ var SKILL_LIMITS = {
13072
13268
  * Consumer: `downloadGitHubTarball`.
13073
13269
  */
13074
13270
  MAX_TARBALL_SIZE: 50 * 1024 * 1024,
13271
+ /**
13272
+ * Maximum size of a GitHub skill tarball after gzip decompression. The
13273
+ * compressed download limit alone does not protect against highly
13274
+ * compressible archives expanding until the process runs out of memory.
13275
+ *
13276
+ * Consumer: `downloadGitHubTarball`.
13277
+ */
13278
+ MAX_UNCOMPRESSED_TARBALL_SIZE: 64 * 1024 * 1024,
13075
13279
  /**
13076
13280
  * Default total char budget for skill bodies injected in eager mode when
13077
13281
  * `config.skills.eagerMaxChars` is unset. ~6k tokens. Skills are injected
@@ -13083,6 +13287,17 @@ var SKILL_LIMITS = {
13083
13287
  * `skillEagerMaxChars`).
13084
13288
  */
13085
13289
  EAGER_DEFAULT_MAX_CHARS: 24e3,
13290
+ /**
13291
+ * Default total char budget for compact (token-saving) skill bodies injected
13292
+ * by `buildCompactSkillBodiesText`. Mirrors the eager ceiling so compact mode
13293
+ * can never inject more total skill text than eager mode. Before this bound
13294
+ * existed, compact mode injected EVERY skill's compact body with no cap, so
13295
+ * the section grew linearly with the number of installed skills. Skills past
13296
+ * the budget overflow to a name list the agent loads via the `skill` tool.
13297
+ *
13298
+ * Consumer: `buildCompactSkillBodiesText` (default for its `budget` param).
13299
+ */
13300
+ COMPACT_DEFAULT_MAX_CHARS: 24e3,
13086
13301
  /**
13087
13302
  * Auto-compact body limits (the token-saving fallback used when a skill has
13088
13303
  * no hand-crafted `SKILL.save.md`). The Overview and Rules sections are
@@ -13111,74 +13326,104 @@ var SKILL_LIMITS = {
13111
13326
  SKILL_BODY_LINE_LIMIT: 500
13112
13327
  };
13113
13328
 
13114
- // src/utils/deep-merge.ts
13115
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
13116
- "__proto__",
13117
- "constructor",
13118
- "prototype",
13119
- "__defineGetter__",
13120
- "__defineSetter__",
13121
- "__lookupGetter__",
13122
- "__lookupSetter__"
13123
- ]);
13124
- function isPrimitiveArray(a) {
13125
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
13329
+ // src/skills/frontmatter.ts
13330
+ var SCALAR_KEYS = /* @__PURE__ */ new Set(["name", "description", "trigger", "version", "license", "compatibility"]);
13331
+ function parseSkillFrontmatter(raw) {
13332
+ const text = normalizeLineEndings(raw);
13333
+ if (!text.startsWith("---")) return {};
13334
+ const end = text.indexOf("\n---", 4);
13335
+ if (end === -1) return {};
13336
+ return parseFrontmatterBlock(text.slice(4, end));
13126
13337
  }
13127
- function deepMerge(base, patch, options = {}) {
13128
- const {
13129
- conflictResolution = "prefer-patch",
13130
- arrayMode = "replace",
13131
- protectProto = true,
13132
- onNonPrimitiveArrayReplace
13133
- } = options;
13134
- if (typeof base !== "object" || base === null) {
13135
- return conflictResolution === "prefer-patch" ? patch : base;
13136
- }
13137
- if (typeof patch !== "object" || patch === null) {
13138
- return conflictResolution === "prefer-patch" ? patch : base;
13139
- }
13140
- if (Array.isArray(base) && Array.isArray(patch)) {
13141
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
13142
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
13143
- }
13144
- return conflictResolution === "prefer-patch" ? patch : base;
13145
- }
13146
- if (Array.isArray(base) || Array.isArray(patch)) {
13147
- return conflictResolution === "prefer-patch" ? patch : base;
13148
- }
13149
- const baseObj = base;
13150
- const patchObj = patch;
13151
- const out = { ...baseObj };
13152
- for (const [k, v] of Object.entries(patchObj)) {
13153
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
13154
- const existing = out[k];
13155
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
13156
- out[k] = deepMerge(existing, v, options);
13157
- } else if (Array.isArray(v) && Array.isArray(existing)) {
13158
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
13159
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
13160
- }
13161
- out[k] = deepMerge(existing, v, options);
13162
- } else if (v !== void 0) {
13163
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
13164
- const existingLen = Array.isArray(existing) ? existing.length : 0;
13165
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
13166
- }
13167
- out[k] = v;
13168
- }
13169
- }
13170
- return out;
13338
+ function stripFrontmatter(raw) {
13339
+ const text = normalizeLineEndings(raw);
13340
+ if (!text.startsWith("---")) return text;
13341
+ const end = text.indexOf("\n---", 4);
13342
+ if (end === -1) return text;
13343
+ let body = text.slice(end + 4);
13344
+ if (body.startsWith("\n")) body = body.slice(1);
13345
+ return body;
13171
13346
  }
13172
-
13173
- // src/utils/glob-match.ts
13174
- function escapeRegex(s) {
13175
- return s.replace(/[.+^${}()|\\]/g, "\\$&");
13347
+ function normalizeLineEndings(s) {
13348
+ return s.replace(/\r\n?/g, "\n");
13176
13349
  }
13177
- var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
13178
- var CACHE_MAX_SIZE = 2e3;
13179
- var NEVER_MATCH = /[^\s\S]/;
13180
- function getCachedGlob(pattern) {
13181
- const cached = COMPILED_GLOB_CACHE.get(pattern);
13350
+ function parseFrontmatterBlock(block) {
13351
+ const out = {};
13352
+ const lines = block.split("\n");
13353
+ let i = 0;
13354
+ while (i < lines.length) {
13355
+ const line = lines[i] ?? "";
13356
+ const m = /^([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/.exec(line);
13357
+ if (!m) {
13358
+ i++;
13359
+ continue;
13360
+ }
13361
+ const key = m[1] ?? "";
13362
+ const rest = (m[2] ?? "").trim();
13363
+ if (key === "metadata") {
13364
+ const map = {};
13365
+ i++;
13366
+ while (i < lines.length) {
13367
+ const sub = lines[i] ?? "";
13368
+ const sm = /^\s+([a-zA-Z0-9_.-]+):\s*(.*)$/.exec(sub);
13369
+ if (!sm) break;
13370
+ map[sm[1] ?? ""] = unquote((sm[2] ?? "").trim());
13371
+ i++;
13372
+ }
13373
+ if (Object.keys(map).length > 0) out.metadata = map;
13374
+ continue;
13375
+ }
13376
+ if (rest === "|" || rest === ">") {
13377
+ const collected = [];
13378
+ i++;
13379
+ while (i < lines.length) {
13380
+ const sub = lines[i] ?? "";
13381
+ if (sub === "" || sub.startsWith(" ") || sub.startsWith(" ")) {
13382
+ collected.push(sub.replace(/^\s+/, ""));
13383
+ i++;
13384
+ } else break;
13385
+ }
13386
+ out[normalizeKey(key)] = collected.join("\n").trim();
13387
+ continue;
13388
+ }
13389
+ if (key === "allowed-tools" || key === "allowedTools") {
13390
+ out.allowedTools = rest.split(/[\s,]+/).filter(Boolean);
13391
+ i++;
13392
+ continue;
13393
+ }
13394
+ if (SCALAR_KEYS.has(key)) {
13395
+ out[key] = unquote(rest);
13396
+ i++;
13397
+ continue;
13398
+ }
13399
+ i++;
13400
+ }
13401
+ return out;
13402
+ }
13403
+ function normalizeKey(key) {
13404
+ return key === "allowed-tools" ? "allowedTools" : key;
13405
+ }
13406
+ function unquote(s) {
13407
+ if (s.length >= 2 && (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'"))) {
13408
+ return s.slice(1, -1);
13409
+ }
13410
+ return s;
13411
+ }
13412
+ function isValidSkillNameFormat(name) {
13413
+ return name.length >= 1 && name.length <= 64 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
13414
+ }
13415
+
13416
+ // src/utils/glob-match.ts
13417
+ function escapeRegex(s) {
13418
+ return s.replace(/[.+^${}()|\\]/g, "\\$&");
13419
+ }
13420
+ var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
13421
+ var CACHE_MAX_SIZE = 2e3;
13422
+ var NEVER_MATCH = /[^\s\S]/;
13423
+ var COMMAND_WILDCARD_STOP = ";&|\\n\\r`$<>";
13424
+ function getCachedGlob(pattern, commandSubject = false) {
13425
+ const cacheKey = commandSubject ? `cmd\0${pattern}` : pattern;
13426
+ const cached = COMPILED_GLOB_CACHE.get(cacheKey);
13182
13427
  if (cached) return cached;
13183
13428
  if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
13184
13429
  const keys = [...COMPILED_GLOB_CACHE.keys()];
@@ -13188,33 +13433,35 @@ function getCachedGlob(pattern) {
13188
13433
  }
13189
13434
  let re;
13190
13435
  try {
13191
- re = compileGlob(pattern);
13436
+ re = compileGlob(pattern, commandSubject);
13192
13437
  } catch {
13193
13438
  re = NEVER_MATCH;
13194
13439
  }
13195
- COMPILED_GLOB_CACHE.set(pattern, re);
13440
+ COMPILED_GLOB_CACHE.set(cacheKey, re);
13196
13441
  return re;
13197
13442
  }
13198
13443
  var MAX_GLOB_PATTERN_LEN = 1024;
13199
- function compileGlob(pattern) {
13444
+ function compileGlob(pattern, commandSubject = false) {
13200
13445
  if (pattern.length > MAX_GLOB_PATTERN_LEN) {
13201
13446
  throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
13202
13447
  }
13448
+ const star = commandSubject ? `[^/${COMMAND_WILDCARD_STOP}]*` : "[^/]*";
13449
+ const question = commandSubject ? `[^/${COMMAND_WILDCARD_STOP}]` : "[^/]";
13203
13450
  let i = 0;
13204
13451
  let re = "^";
13205
13452
  while (i < pattern.length) {
13206
13453
  const c = pattern[i];
13207
13454
  if (c === "*") {
13208
13455
  if (pattern[i + 1] === "*") {
13209
- re += ".*";
13456
+ re += commandSubject ? `[^${COMMAND_WILDCARD_STOP}]*` : ".*";
13210
13457
  i += 2;
13211
13458
  if (pattern[i] === "/") i++;
13212
13459
  } else {
13213
- re += "[^/]*";
13460
+ re += star;
13214
13461
  i++;
13215
13462
  }
13216
13463
  } else if (c === "?") {
13217
- re += "[^/]";
13464
+ re += question;
13218
13465
  i++;
13219
13466
  } else if (c === "[") {
13220
13467
  let cls = "[";
@@ -13251,6 +13498,12 @@ function matchGlob(pattern, input) {
13251
13498
  function matchAny(patterns, input) {
13252
13499
  return patterns.some((p) => matchGlob(p, input));
13253
13500
  }
13501
+ function matchCommandGlob(pattern, input) {
13502
+ return getCachedGlob(pattern, true).test(input);
13503
+ }
13504
+ function matchAnyCommand(patterns, input) {
13505
+ return patterns.some((p) => matchCommandGlob(p, input));
13506
+ }
13254
13507
 
13255
13508
  // src/utils/json-repair.ts
13256
13509
  function completePartialObject(s) {
@@ -13351,10 +13604,10 @@ function validateAgainstSchema(value, schema) {
13351
13604
  return { ok: errors.length === 0, errors };
13352
13605
  }
13353
13606
  var MAX_SCHEMA_DEPTH = 64;
13354
- function walk(value, schema, path39, errors, depth) {
13607
+ function walk(value, schema, path40, errors, depth) {
13355
13608
  if (depth > MAX_SCHEMA_DEPTH) {
13356
13609
  errors.push({
13357
- path: path39 || "<root>",
13610
+ path: path40 || "<root>",
13358
13611
  message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
13359
13612
  });
13360
13613
  return;
@@ -13362,7 +13615,7 @@ function walk(value, schema, path39, errors, depth) {
13362
13615
  if (schema.enum !== void 0) {
13363
13616
  if (!enumIncludes(schema.enum, value)) {
13364
13617
  errors.push({
13365
- path: path39 || "<root>",
13618
+ path: path40 || "<root>",
13366
13619
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
13367
13620
  });
13368
13621
  return;
@@ -13371,7 +13624,7 @@ function walk(value, schema, path39, errors, depth) {
13371
13624
  if (typeof schema.type === "string") {
13372
13625
  if (!checkType(value, schema.type)) {
13373
13626
  errors.push({
13374
- path: path39 || "<root>",
13627
+ path: path40 || "<root>",
13375
13628
  message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
13376
13629
  });
13377
13630
  return;
@@ -13383,7 +13636,7 @@ function walk(value, schema, path39, errors, depth) {
13383
13636
  if (!(req in obj)) {
13384
13637
  const expected = schema.properties?.[req]?.type;
13385
13638
  errors.push({
13386
- path: joinPath(path39, req),
13639
+ path: joinPath(path40, req),
13387
13640
  message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
13388
13641
  });
13389
13642
  }
@@ -13391,14 +13644,14 @@ function walk(value, schema, path39, errors, depth) {
13391
13644
  if (schema.properties) {
13392
13645
  for (const [key, subSchema] of Object.entries(schema.properties)) {
13393
13646
  if (key in obj) {
13394
- walk(obj[key], subSchema, joinPath(path39, key), errors, depth + 1);
13647
+ walk(obj[key], subSchema, joinPath(path40, key), errors, depth + 1);
13395
13648
  }
13396
13649
  }
13397
13650
  }
13398
13651
  }
13399
13652
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
13400
13653
  for (let i = 0; i < value.length; i++) {
13401
- walk(value[i], schema.items, `${path39}[${i}]`, errors, depth + 1);
13654
+ walk(value[i], schema.items, `${path40}[${i}]`, errors, depth + 1);
13402
13655
  }
13403
13656
  }
13404
13657
  }
@@ -13694,7 +13947,7 @@ function invalid(sessionId) {
13694
13947
 
13695
13948
  // src/utils/sleep.ts
13696
13949
  function sleep(ms) {
13697
- return new Promise((resolve16) => setTimeout(resolve16, ms));
13950
+ return new Promise((resolve17) => setTimeout(resolve17, ms));
13698
13951
  }
13699
13952
 
13700
13953
  // src/utils/slug.ts
@@ -13759,12 +14012,38 @@ function createToolOutputSerializer(opts = {}) {
13759
14012
  return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
13760
14013
  }
13761
14014
  const half = Math.floor(available / 2);
13762
- const first = text.slice(0, half);
13763
- const second = text.slice(text.length - half);
14015
+ const first = utf8Prefix(text, half);
14016
+ const second = utf8Suffix(text, available - Buffer.byteLength(first, "utf8"));
13764
14017
  return { text: `${first}${marker}${second}`, newBudget: 0 };
13765
14018
  }
13766
14019
  return { serialize, enforceCap, capBytes };
13767
14020
  }
14021
+ function utf8Prefix(text, maxBytes) {
14022
+ if (maxBytes <= 0) return "";
14023
+ let low = 0;
14024
+ let high = text.length;
14025
+ while (low < high) {
14026
+ const mid = Math.ceil((low + high) / 2);
14027
+ if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
14028
+ else high = mid - 1;
14029
+ }
14030
+ let end = low;
14031
+ if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
14032
+ return text.slice(0, end);
14033
+ }
14034
+ function utf8Suffix(text, maxBytes) {
14035
+ if (maxBytes <= 0) return "";
14036
+ let low = 0;
14037
+ let high = text.length;
14038
+ while (low < high) {
14039
+ const chars = Math.ceil((low + high) / 2);
14040
+ if (Buffer.byteLength(text.slice(text.length - chars), "utf8") <= maxBytes) low = chars;
14041
+ else high = chars - 1;
14042
+ }
14043
+ let start = text.length - low;
14044
+ if (start < text.length && /[\uDC00-\uDFFF]/.test(text[start])) start++;
14045
+ return text.slice(start);
14046
+ }
13768
14047
  function renderToolObject(toolName, obj, input) {
13769
14048
  if (toolName === "read" && typeof obj["text"] === "string") {
13770
14049
  return joinSections([
@@ -14389,13 +14668,43 @@ function normalizePathSubject(value) {
14389
14668
  function isPathSubjectKey(subjectKey) {
14390
14669
  return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
14391
14670
  }
14671
+ function renderCommandLine(command, args) {
14672
+ if (!Array.isArray(args) || args.length === 0) return command;
14673
+ const rendered = args.map((arg) => {
14674
+ const str = String(arg);
14675
+ return /\s/.test(str) ? `"${str.replace(/"/g, '\\"')}"` : str;
14676
+ });
14677
+ return [command, ...rendered].join(" ");
14678
+ }
14392
14679
  function subjectForToolInput(toolName, input, subjectKey) {
14393
14680
  if (!input || typeof input !== "object") return void 0;
14394
14681
  const obj = input;
14395
14682
  if (subjectKey) {
14396
14683
  const value = obj[subjectKey];
14397
14684
  if (typeof value === "string") {
14398
- return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
14685
+ if (isPathSubjectKey(subjectKey)) {
14686
+ const normalized = normalizePathSubject(value);
14687
+ if (subjectKey === "files" && obj["check"] === true) return `${normalized}:check`;
14688
+ if (subjectKey === "files" && obj["dry_run"] === true) return `${normalized}:dry-run`;
14689
+ return normalized;
14690
+ }
14691
+ if (subjectKey === "command") {
14692
+ const rendered = renderCommandLine(value, obj["args"]);
14693
+ if (value === "commit" && obj["dry_run"] === true) {
14694
+ return `${escapeGlobSubject(rendered)}:dry-run`;
14695
+ }
14696
+ return escapeGlobSubject(rendered);
14697
+ }
14698
+ if (subjectKey === "directory" && obj["dry_run"] === true) {
14699
+ return `${escapeGlobSubject(value)}:dry-run`;
14700
+ }
14701
+ if (subjectKey === "packages" && obj["dry_run"] === true) {
14702
+ return `${escapeGlobSubject(value)}:dry-run`;
14703
+ }
14704
+ if (subjectKey === "name" && obj["dry_run"] === true) {
14705
+ return `${escapeGlobSubject(value)}:dry-run`;
14706
+ }
14707
+ return escapeGlobSubject(value);
14399
14708
  }
14400
14709
  }
14401
14710
  if (toolName === "bash" && typeof obj.command === "string") {
@@ -14419,6 +14728,23 @@ import * as fsp7 from "node:fs/promises";
14419
14728
  import * as path12 from "node:path";
14420
14729
  import { createInterface } from "node:readline";
14421
14730
 
14731
+ // src/storage/session-workspace-checkpoints.ts
14732
+ function requiredCheckpointCas(checkpointCas, operation) {
14733
+ if (!checkpointCas) {
14734
+ throw new Error(`Workspace checkpoint ${operation} requires a projectRoot-aware session store`);
14735
+ }
14736
+ return checkpointCas;
14737
+ }
14738
+ function captureCheckpoint(checkpointCas, sessionId, promptIndex) {
14739
+ return requiredCheckpointCas(checkpointCas, "capture").capture(sessionId, promptIndex);
14740
+ }
14741
+ function materializeCheckpoint(checkpointCas, checkpoint, targetRoot) {
14742
+ return requiredCheckpointCas(checkpointCas, "materialization").materialize(
14743
+ checkpoint,
14744
+ targetRoot
14745
+ );
14746
+ }
14747
+
14422
14748
  // src/storage/session-helpers.ts
14423
14749
  function consumeControlString(value, start) {
14424
14750
  let index = start;
@@ -15394,7 +15720,8 @@ var FileSessionWriter = class _FileSessionWriter {
15394
15720
  promptIndex,
15395
15721
  promptPreview,
15396
15722
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15397
- fileCount
15723
+ fileCount,
15724
+ ...workspaceCheckpoint ? { workspaceCheckpoint } : {}
15398
15725
  });
15399
15726
  }
15400
15727
  async writeFileSnapshot(promptIndex, files) {
@@ -15587,8 +15914,8 @@ function sha256(content) {
15587
15914
  return createHash2("sha256").update(content).digest("hex");
15588
15915
  }
15589
15916
  function isInside(root, target) {
15590
- const relative7 = path13.relative(root, target);
15591
- return relative7 === "" || !relative7.startsWith("..") && !path13.isAbsolute(relative7);
15917
+ const relative8 = path13.relative(root, target);
15918
+ return relative8 === "" || !relative8.startsWith("..") && !path13.isAbsolute(relative8);
15592
15919
  }
15593
15920
  function normalizeRelative(input) {
15594
15921
  if (!input || path13.isAbsolute(input)) return null;
@@ -15600,8 +15927,8 @@ function normalizeRelative(input) {
15600
15927
  function parseNulPaths(output) {
15601
15928
  return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
15602
15929
  }
15603
- function isWrongStackWorktreePath(relative7) {
15604
- return relative7 === ".wrongstack/worktrees" || relative7.startsWith(".wrongstack/worktrees/");
15930
+ function isWrongStackWorktreePath(relative8) {
15931
+ return relative8 === ".wrongstack/worktrees" || relative8.startsWith(".wrongstack/worktrees/");
15605
15932
  }
15606
15933
  var SessionCheckpointCas = class {
15607
15934
  rootDir;
@@ -15627,16 +15954,16 @@ var SessionCheckpointCas = class {
15627
15954
  }
15628
15955
  const relativePaths = [
15629
15956
  .../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
15630
- ].filter((relative7) => !isWrongStackWorktreePath(relative7)).sort();
15957
+ ].filter((relative8) => !isWrongStackWorktreePath(relative8)).sort();
15631
15958
  const unresolved = [];
15632
15959
  let scheduledBytes = 0;
15633
15960
  const captured = await mapWithConcurrency(
15634
15961
  relativePaths,
15635
15962
  CAPTURE_CONCURRENCY,
15636
- async (relative7) => {
15637
- const absolute = path13.resolve(this.projectRoot, ...relative7.split("/"));
15963
+ async (relative8) => {
15964
+ const absolute = path13.resolve(this.projectRoot, ...relative8.split("/"));
15638
15965
  if (!isInside(this.projectRoot, absolute)) {
15639
- unresolved.push({ path: relative7, reason: "path escapes project root" });
15966
+ unresolved.push({ path: relative8, reason: "path escapes project root" });
15640
15967
  return null;
15641
15968
  }
15642
15969
  try {
@@ -15646,27 +15973,27 @@ var SessionCheckpointCas = class {
15646
15973
  const resolvedLink = path13.resolve(path13.dirname(absolute), linkTarget);
15647
15974
  if (path13.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
15648
15975
  unresolved.push({
15649
- path: relative7,
15976
+ path: relative8,
15650
15977
  reason: "symlink target escapes project root"
15651
15978
  });
15652
15979
  return null;
15653
15980
  }
15654
- return { path: relative7, state: "symlink", linkTarget };
15981
+ return { path: relative8, state: "symlink", linkTarget };
15655
15982
  }
15656
15983
  if (!stat15.isFile()) {
15657
- unresolved.push({ path: relative7, reason: "changed path is not a regular file" });
15984
+ unresolved.push({ path: relative8, reason: "changed path is not a regular file" });
15658
15985
  return null;
15659
15986
  }
15660
15987
  if (stat15.size > MAX_BLOB_BYTES) {
15661
15988
  unresolved.push({
15662
- path: relative7,
15989
+ path: relative8,
15663
15990
  reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
15664
15991
  });
15665
15992
  return null;
15666
15993
  }
15667
15994
  if (scheduledBytes + stat15.size > MAX_CHECKPOINT_BYTES) {
15668
15995
  unresolved.push({
15669
- path: relative7,
15996
+ path: relative8,
15670
15997
  reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
15671
15998
  });
15672
15999
  return null;
@@ -15675,12 +16002,12 @@ var SessionCheckpointCas = class {
15675
16002
  const content = await fsp8.readFile(absolute);
15676
16003
  const blobHash = sha256(content);
15677
16004
  await this.putBlob(blobHash, content);
15678
- return { path: relative7, state: "file", blobHash, mode: stat15.mode & 511 };
16005
+ return { path: relative8, state: "file", blobHash, mode: stat15.mode & 511 };
15679
16006
  } catch (err) {
15680
16007
  if (err.code === "ENOENT") {
15681
- return { path: relative7, state: "absent" };
16008
+ return { path: relative8, state: "absent" };
15682
16009
  }
15683
- unresolved.push({ path: relative7, reason: toErrorMessage(err) });
16010
+ unresolved.push({ path: relative8, reason: toErrorMessage(err) });
15684
16011
  return null;
15685
16012
  }
15686
16013
  }
@@ -15870,8 +16197,8 @@ var SessionCheckpointCas = class {
15870
16197
  }
15871
16198
  return parsed;
15872
16199
  }
15873
- async safeOutputPath(target, realTarget, relative7) {
15874
- const normalized = normalizeRelative(relative7);
16200
+ async safeOutputPath(target, realTarget, relative8) {
16201
+ const normalized = normalizeRelative(relative8);
15875
16202
  if (!normalized) throw new Error("invalid relative path");
15876
16203
  const output = path13.resolve(target, ...normalized.split("/"));
15877
16204
  if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
@@ -15891,7 +16218,7 @@ var SessionCheckpointCas = class {
15891
16218
  }
15892
16219
  };
15893
16220
  function defaultRunGit(args, cwd) {
15894
- return new Promise((resolve16) => {
16221
+ return new Promise((resolve17) => {
15895
16222
  const stdoutChunks = [];
15896
16223
  const stderrChunks = [];
15897
16224
  let stdoutBytes = 0;
@@ -15934,8 +16261,8 @@ function defaultRunGit(args, cwd) {
15934
16261
  stdoutTruncated,
15935
16262
  stderrTruncated
15936
16263
  });
15937
- child.on("error", (err) => resolve16(result(1, err.message)));
15938
- child.on("close", (code) => resolve16(result(code ?? 1)));
16264
+ child.on("error", (err) => resolve17(result(1, err.message)));
16265
+ child.on("close", (code) => resolve17(result(code ?? 1)));
15939
16266
  });
15940
16267
  }
15941
16268
 
@@ -16012,8 +16339,8 @@ var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
16012
16339
  var VALIDATION_CONCURRENCY = 8;
16013
16340
  var NOTICE_PATH_LIMIT = 20;
16014
16341
  function isInside2(root, target) {
16015
- const relative7 = path15.relative(root, target);
16016
- return relative7 === "" || !relative7.startsWith("..") && !path15.isAbsolute(relative7);
16342
+ const relative8 = path15.relative(root, target);
16343
+ return relative8 === "" || !relative8.startsWith("..") && !path15.isAbsolute(relative8);
16017
16344
  }
16018
16345
  function errno(err) {
16019
16346
  return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
@@ -16100,8 +16427,8 @@ function formatResumeValidationNotice(validation, projectRoot) {
16100
16427
  if (validation.staleFiles.length === 0) return null;
16101
16428
  const root = path15.resolve(projectRoot);
16102
16429
  const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
16103
- const relative7 = path15.relative(root, entry.path);
16104
- const display = isInside2(root, entry.path) ? relative7 || "." : entry.path;
16430
+ const relative8 = path15.relative(root, entry.path);
16431
+ const display = isInside2(root, entry.path) ? relative8 || "." : entry.path;
16105
16432
  return `- ${JSON.stringify(display)} [${entry.status}]`;
16106
16433
  });
16107
16434
  const omitted = validation.staleFiles.length - shown.length;
@@ -16123,57 +16450,82 @@ function formatInterruptedToolNotice(pendingToolUseCount) {
16123
16450
  ].join("\n");
16124
16451
  }
16125
16452
 
16126
- // src/storage/session-store/index-reader.ts
16453
+ // src/storage/session-store/delete-session-artifacts.ts
16127
16454
  import * as fsp10 from "node:fs/promises";
16128
- function applySessionIndexLines(raw, byId, deleted) {
16129
- for (const line of raw.split("\n")) {
16130
- if (!line.trim()) continue;
16131
- try {
16132
- const entry = JSON.parse(line);
16133
- if (entry.action === "delete" && entry.id) {
16134
- deleted.add(entry.id);
16135
- byId.delete(entry.id);
16136
- continue;
16137
- }
16138
- if (entry.id && !deleted.has(entry.id)) {
16139
- byId.set(entry.id, entry);
16140
- }
16141
- } catch {
16455
+ import * as path17 from "node:path";
16456
+
16457
+ // src/storage/session-store/paths.ts
16458
+ import * as path16 from "node:path";
16459
+ function sessionPath(storeDir, id, ext) {
16460
+ return sessionScopedPath(storeDir, id, ext);
16461
+ }
16462
+ function shardManifestPath(storeDir, shardKey) {
16463
+ return shardKey ? path16.join(storeDir, shardKey, "_manifest.json") : path16.join(storeDir, "_manifest.json");
16464
+ }
16465
+ function shardKeyForSessionId(id) {
16466
+ const dirName = path16.dirname(id);
16467
+ return dirName === "." ? "" : dirName;
16468
+ }
16469
+ async function ensureShardDir(storeDir, id) {
16470
+ const dirPath = path16.dirname(sessionScopedPath(storeDir, id, ""));
16471
+ await ensureDir(dirPath);
16472
+ return dirPath;
16473
+ }
16474
+
16475
+ // src/storage/session-store/delete-session-artifacts.ts
16476
+ async function deleteSessionArtifacts({
16477
+ rootDir,
16478
+ id,
16479
+ jsonlPath
16480
+ }) {
16481
+ const shardDir = path17.dirname(jsonlPath);
16482
+ const base = path17.basename(id);
16483
+ const sessDir = path17.join(shardDir, base);
16484
+ const deletions = [
16485
+ fsp10.unlink(jsonlPath),
16486
+ fsp10.unlink(sessionPath(rootDir, id, ".summary.json")),
16487
+ fsp10.unlink(sessionPath(rootDir, id, ".plan.json")),
16488
+ fsp10.unlink(sessionPath(rootDir, id, ".tasks.json")),
16489
+ fsp10.unlink(sessionPath(rootDir, id, ".todos.json"))
16490
+ ];
16491
+ const results = await Promise.allSettled(deletions);
16492
+ for (const result of results) {
16493
+ if (result.status === "rejected") {
16494
+ warnDeleteFailure(id, result.reason);
16142
16495
  }
16143
16496
  }
16497
+ await fsp10.rm(sessDir, { recursive: true, force: true }).catch((err) => {
16498
+ console.warn(
16499
+ JSON.stringify({
16500
+ level: "warn",
16501
+ event: "session_store.rmdir_failed",
16502
+ sessionId: id,
16503
+ message: toErrorMessage(err),
16504
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16505
+ })
16506
+ );
16507
+ });
16144
16508
  }
16145
- async function readFileRange(file, start, end) {
16146
- const length = end - start;
16147
- if (length <= 0) return { raw: "", end: start };
16148
- const buffer = Buffer.allocUnsafe(length);
16149
- let offset = 0;
16150
- let handle;
16151
- try {
16152
- handle = await fsp10.open(file, "r");
16153
- while (offset < length) {
16154
- const { bytesRead } = await handle.read(buffer, offset, length - offset, start + offset);
16155
- if (bytesRead === 0) return null;
16156
- offset += bytesRead;
16157
- }
16158
- let completeLength = buffer.length;
16159
- if (buffer.at(-1) !== 10) {
16160
- const lastNewline = buffer.lastIndexOf(10);
16161
- const trailing = buffer.subarray(lastNewline + 1).toString("utf8").trim();
16162
- try {
16163
- if (trailing) JSON.parse(trailing);
16164
- else completeLength = lastNewline + 1;
16165
- } catch {
16166
- completeLength = lastNewline + 1;
16167
- }
16168
- }
16169
- return {
16170
- raw: buffer.subarray(0, completeLength).toString("utf8"),
16171
- end: start + completeLength
16172
- };
16173
- } catch {
16174
- return null;
16175
- } finally {
16176
- await handle?.close().catch(() => void 0);
16509
+ function warnDeleteFailure(id, reason) {
16510
+ const msg = reason instanceof Error ? reason.message : String(reason);
16511
+ if (reason?.code === "ENOENT") return;
16512
+ console.warn(
16513
+ JSON.stringify({
16514
+ level: "warn",
16515
+ event: "session_store.delete_failed",
16516
+ sessionId: id,
16517
+ message: msg,
16518
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16519
+ })
16520
+ );
16521
+ }
16522
+
16523
+ // src/storage/session-store/delete-session-guards.ts
16524
+ async function assertSessionCanBeDeleted(sessionId, isSessionInUse) {
16525
+ if (!isSessionInUse) return;
16526
+ const reason = await isSessionInUse(sessionId);
16527
+ if (reason) {
16528
+ throw new Error(`Session ${sessionId} is in use (${reason}) and cannot be deleted.`);
16177
16529
  }
16178
16530
  }
16179
16531
 
@@ -16187,7 +16539,7 @@ function isSessionJsonlFileName(name) {
16187
16539
 
16188
16540
  // src/storage/session-store/directory-session-files.ts
16189
16541
  import * as fsp11 from "node:fs/promises";
16190
- import * as path16 from "node:path";
16542
+ import * as path18 from "node:path";
16191
16543
  function sessionIdForFile(prefix, name) {
16192
16544
  const base = name.replace(/\.jsonl$/, "");
16193
16545
  return prefix ? `${prefix}/${base}` : base;
@@ -16209,12 +16561,12 @@ async function collectSessionFiles(dir, prefix = "", depth = 0) {
16209
16561
  if (entry.isDirectory()) {
16210
16562
  dirEntries.push(entry);
16211
16563
  } else if (entry.isFile() && isSessionJsonlFileName(entry.name)) {
16212
- files.push({ id: sessionIdForFile(prefix, entry.name), filePath: path16.join(dir, entry.name) });
16564
+ files.push({ id: sessionIdForFile(prefix, entry.name), filePath: path18.join(dir, entry.name) });
16213
16565
  }
16214
16566
  }
16215
16567
  const childFileArrays = await Promise.all(
16216
16568
  dirEntries.map(
16217
- (entry) => collectSessionFiles(path16.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
16569
+ (entry) => collectSessionFiles(path18.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
16218
16570
  )
16219
16571
  );
16220
16572
  return [...childFileArrays.flat(), ...files];
@@ -16238,210 +16590,35 @@ async function collectSessionIds(dir, prefix = "", depth = 0) {
16238
16590
  }
16239
16591
  const childIdArrays = await Promise.all(
16240
16592
  dirEntries.map(
16241
- (entry) => collectSessionIds(path16.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
16593
+ (entry) => collectSessionIds(path18.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
16242
16594
  )
16243
16595
  );
16244
16596
  return [...childIdArrays.flat(), ...fileIds];
16245
16597
  }
16246
16598
 
16247
- // src/storage/session-store/delete-session-artifacts.ts
16248
- import * as fsp12 from "node:fs/promises";
16249
- import * as path18 from "node:path";
16250
-
16251
- // src/storage/session-store/paths.ts
16252
- import * as path17 from "node:path";
16253
- function sessionPath(storeDir, id, ext) {
16254
- return sessionScopedPath(storeDir, id, ext);
16255
- }
16256
- function shardManifestPath(storeDir, shardKey) {
16257
- return shardKey ? path17.join(storeDir, shardKey, "_manifest.json") : path17.join(storeDir, "_manifest.json");
16599
+ // src/storage/session-store/events.ts
16600
+ function emitSessionStoreRead(events, sessionId, filePath, operation, outcome, durationMs, error2) {
16601
+ events?.emit("storage.read", {
16602
+ sessionId,
16603
+ store: "session",
16604
+ filePath,
16605
+ operation,
16606
+ outcome,
16607
+ durationMs,
16608
+ ...error2 !== void 0 ? { error: error2 } : {}
16609
+ });
16258
16610
  }
16259
- function shardKeyForSessionId(id) {
16260
- const dirName = path17.dirname(id);
16261
- return dirName === "." ? "" : dirName;
16262
- }
16263
- async function ensureShardDir(storeDir, id) {
16264
- const dirPath = path17.dirname(sessionScopedPath(storeDir, id, ""));
16265
- await ensureDir(dirPath);
16266
- return dirPath;
16267
- }
16268
-
16269
- // src/storage/session-store/delete-session-artifacts.ts
16270
- async function deleteSessionArtifacts({
16271
- rootDir,
16272
- id,
16273
- jsonlPath
16274
- }) {
16275
- const shardDir = path18.dirname(jsonlPath);
16276
- const base = path18.basename(id);
16277
- const sessDir = path18.join(shardDir, base);
16278
- const deletions = [
16279
- fsp12.unlink(jsonlPath),
16280
- fsp12.unlink(sessionPath(rootDir, id, ".summary.json")),
16281
- fsp12.unlink(sessionPath(rootDir, id, ".plan.json")),
16282
- fsp12.unlink(sessionPath(rootDir, id, ".tasks.json")),
16283
- fsp12.unlink(sessionPath(rootDir, id, ".todos.json"))
16284
- ];
16285
- const results = await Promise.allSettled(deletions);
16286
- for (const result of results) {
16287
- if (result.status === "rejected") {
16288
- warnDeleteFailure(id, result.reason);
16289
- }
16290
- }
16291
- await fsp12.rm(sessDir, { recursive: true, force: true }).catch((err) => {
16292
- console.warn(
16293
- JSON.stringify({
16294
- level: "warn",
16295
- event: "session_store.rmdir_failed",
16296
- sessionId: id,
16297
- message: toErrorMessage(err),
16298
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
16299
- })
16300
- );
16301
- });
16302
- }
16303
- function warnDeleteFailure(id, reason) {
16304
- const msg = reason instanceof Error ? reason.message : String(reason);
16305
- if (reason?.code === "ENOENT") return;
16306
- console.warn(
16307
- JSON.stringify({
16308
- level: "warn",
16309
- event: "session_store.delete_failed",
16310
- sessionId: id,
16311
- message: msg,
16312
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
16313
- })
16314
- );
16315
- }
16316
-
16317
- // src/storage/session-store/delete-session-guards.ts
16318
- async function assertSessionCanBeDeleted(sessionId, isSessionInUse) {
16319
- if (!isSessionInUse) return;
16320
- const reason = await isSessionInUse(sessionId);
16321
- if (reason) {
16322
- throw new Error(`Session ${sessionId} is in use (${reason}) and cannot be deleted.`);
16323
- }
16324
- }
16325
-
16326
- // src/storage/session-store/load-cache.ts
16327
- var LOAD_CACHE_MAX_ENTRIES = 50;
16328
- var LOAD_CACHE_MAX_BYTES = 64 * 1024 * 1024;
16329
- var SessionLoadCache = class {
16330
- constructor(entries = /* @__PURE__ */ new Map()) {
16331
- this.entries = entries;
16332
- }
16333
- entries;
16334
- bytes = 0;
16335
- clear(sessionId) {
16336
- if (sessionId !== void 0) {
16337
- this.delete(sessionId);
16338
- return;
16339
- }
16340
- this.entries.clear();
16341
- this.bytes = 0;
16342
- }
16343
- getFresh(id, stat15, full) {
16344
- const cached = this.entries.get(id);
16345
- if (!cached || cached.mtimeMs !== stat15.mtimeMs || cached.size !== stat15.size) {
16346
- return null;
16347
- }
16348
- this.entries.delete(id);
16349
- this.entries.set(id, cached);
16350
- if (full) return cached.data;
16351
- return { ...cached.data, messages: [] };
16352
- }
16353
- set(id, stat15, data) {
16354
- this.delete(id);
16355
- while (stat15.size <= LOAD_CACHE_MAX_BYTES && (this.entries.size >= LOAD_CACHE_MAX_ENTRIES || this.bytes + stat15.size > LOAD_CACHE_MAX_BYTES)) {
16356
- const oldest = this.entries.keys().next().value;
16357
- if (oldest === void 0) break;
16358
- this.delete(oldest);
16359
- }
16360
- if (stat15.size <= LOAD_CACHE_MAX_BYTES) {
16361
- this.entries.set(id, { mtimeMs: stat15.mtimeMs, size: stat15.size, data });
16362
- this.bytes += stat15.size;
16363
- }
16364
- }
16365
- delete(sessionId) {
16366
- const cached = this.entries.get(sessionId);
16367
- if (cached) this.bytes = Math.max(0, this.bytes - cached.size);
16368
- this.entries.delete(sessionId);
16369
- }
16370
- };
16371
-
16372
- // src/storage/session-store/prune-helpers.ts
16373
- import * as fsp13 from "node:fs/promises";
16374
- import * as path19 from "node:path";
16375
- function isPrunableSessionJsonl(name) {
16376
- return name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
16377
- }
16378
- async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
16379
- const cutoff = Date.now() - maxAgeDays * 864e5;
16380
- let deleted = 0;
16381
- const pruneFile = async (dir, name, prefix) => {
16382
- const jsonlPath = path19.join(dir, name);
16383
- try {
16384
- const stat15 = await fsp13.stat(jsonlPath);
16385
- if (stat15.mtimeMs >= cutoff) return;
16386
- } catch {
16387
- return;
16388
- }
16389
- const base = name.replace(/\.jsonl$/, "");
16390
- const id = prefix ? `${prefix}/${base}` : base;
16391
- await deleteSession(id);
16392
- deleted++;
16393
- };
16394
- const entries = await fsp13.readdir(storeDir, { withFileTypes: true }).catch(() => []);
16395
- for (const entry of entries) {
16396
- if (entry.isFile()) {
16397
- if (isPrunableSessionJsonl(entry.name)) await pruneFile(storeDir, entry.name, "");
16398
- continue;
16399
- }
16400
- if (!entry.isDirectory()) continue;
16401
- const dateDir = path19.join(storeDir, entry.name);
16402
- const files = await fsp13.readdir(dateDir, { withFileTypes: true }).catch(() => []);
16403
- for (const file of files) {
16404
- if (!file.isFile() || !isPrunableSessionJsonl(file.name)) continue;
16405
- await pruneFile(dateDir, file.name, entry.name);
16406
- }
16407
- }
16408
- for (const entry of entries) {
16409
- if (!entry.isDirectory()) continue;
16410
- const dateDir = path19.join(storeDir, entry.name);
16411
- try {
16412
- const remaining = await fsp13.readdir(dateDir);
16413
- if (remaining.length === 0) {
16414
- await fsp13.rmdir(dateDir).catch(() => void 0);
16415
- }
16416
- } catch {
16417
- }
16418
- }
16419
- return deleted;
16420
- }
16421
-
16422
- // src/storage/session-store/events.ts
16423
- function emitSessionStoreRead(events, sessionId, filePath, operation, outcome, durationMs, error2) {
16424
- events?.emit("storage.read", {
16425
- sessionId,
16426
- store: "session",
16427
- filePath,
16428
- operation,
16429
- outcome,
16430
- durationMs,
16431
- ...error2 !== void 0 ? { error: error2 } : {}
16432
- });
16433
- }
16434
- function emitSessionStoreWrite(events, sessionId, filePath, operation, outcome, durationMs, eventCount, error2) {
16435
- events?.emit("storage.write", {
16436
- sessionId,
16437
- store: "session",
16438
- filePath,
16439
- operation,
16440
- outcome,
16441
- durationMs,
16442
- ...eventCount !== void 0 ? { eventCount } : {},
16443
- ...error2 !== void 0 ? { error: error2 } : {}
16444
- });
16611
+ function emitSessionStoreWrite(events, sessionId, filePath, operation, outcome, durationMs, eventCount, error2) {
16612
+ events?.emit("storage.write", {
16613
+ sessionId,
16614
+ store: "session",
16615
+ filePath,
16616
+ operation,
16617
+ outcome,
16618
+ durationMs,
16619
+ ...eventCount !== void 0 ? { eventCount } : {},
16620
+ ...error2 !== void 0 ? { error: error2 } : {}
16621
+ });
16445
16622
  }
16446
16623
  function emitSessionStoreError(events, sessionId, filePath, operation, error2, recoverable) {
16447
16624
  events?.emit("storage.error", {
@@ -16568,6 +16745,106 @@ async function forkSession(host, id, opts = {}) {
16568
16745
  }
16569
16746
  }
16570
16747
 
16748
+ // src/storage/session-store/index-reader.ts
16749
+ import * as fsp12 from "node:fs/promises";
16750
+ function applySessionIndexLines(raw, byId, deleted) {
16751
+ for (const line of raw.split("\n")) {
16752
+ if (!line.trim()) continue;
16753
+ try {
16754
+ const entry = JSON.parse(line);
16755
+ if (entry.action === "delete" && entry.id) {
16756
+ deleted.add(entry.id);
16757
+ byId.delete(entry.id);
16758
+ continue;
16759
+ }
16760
+ if (entry.id && !deleted.has(entry.id)) {
16761
+ byId.set(entry.id, entry);
16762
+ }
16763
+ } catch {
16764
+ }
16765
+ }
16766
+ }
16767
+ async function readFileRange(file, start, end) {
16768
+ const length = end - start;
16769
+ if (length <= 0) return { raw: "", end: start };
16770
+ const buffer = Buffer.allocUnsafe(length);
16771
+ let offset = 0;
16772
+ let handle;
16773
+ try {
16774
+ handle = await fsp12.open(file, "r");
16775
+ while (offset < length) {
16776
+ const { bytesRead } = await handle.read(buffer, offset, length - offset, start + offset);
16777
+ if (bytesRead === 0) return null;
16778
+ offset += bytesRead;
16779
+ }
16780
+ let completeLength = buffer.length;
16781
+ if (buffer.at(-1) !== 10) {
16782
+ const lastNewline = buffer.lastIndexOf(10);
16783
+ const trailing = buffer.subarray(lastNewline + 1).toString("utf8").trim();
16784
+ try {
16785
+ if (trailing) JSON.parse(trailing);
16786
+ else completeLength = lastNewline + 1;
16787
+ } catch {
16788
+ completeLength = lastNewline + 1;
16789
+ }
16790
+ }
16791
+ return {
16792
+ raw: buffer.subarray(0, completeLength).toString("utf8"),
16793
+ end: start + completeLength
16794
+ };
16795
+ } catch {
16796
+ return null;
16797
+ } finally {
16798
+ await handle?.close().catch(() => void 0);
16799
+ }
16800
+ }
16801
+
16802
+ // src/storage/session-store/load-cache.ts
16803
+ var LOAD_CACHE_MAX_ENTRIES = 50;
16804
+ var LOAD_CACHE_MAX_BYTES = 64 * 1024 * 1024;
16805
+ var SessionLoadCache = class {
16806
+ constructor(entries = /* @__PURE__ */ new Map()) {
16807
+ this.entries = entries;
16808
+ }
16809
+ entries;
16810
+ bytes = 0;
16811
+ clear(sessionId) {
16812
+ if (sessionId !== void 0) {
16813
+ this.delete(sessionId);
16814
+ return;
16815
+ }
16816
+ this.entries.clear();
16817
+ this.bytes = 0;
16818
+ }
16819
+ getFresh(id, stat15, full) {
16820
+ const cached = this.entries.get(id);
16821
+ if (!cached || cached.mtimeMs !== stat15.mtimeMs || cached.size !== stat15.size) {
16822
+ return null;
16823
+ }
16824
+ this.entries.delete(id);
16825
+ this.entries.set(id, cached);
16826
+ if (full) return cached.data;
16827
+ return { ...cached.data, messages: [] };
16828
+ }
16829
+ set(id, stat15, data) {
16830
+ this.delete(id);
16831
+ while (stat15.size <= LOAD_CACHE_MAX_BYTES && (this.entries.size >= LOAD_CACHE_MAX_ENTRIES || this.bytes + stat15.size > LOAD_CACHE_MAX_BYTES)) {
16832
+ const oldest = this.entries.keys().next().value;
16833
+ if (oldest === void 0) break;
16834
+ this.delete(oldest);
16835
+ }
16836
+ if (stat15.size <= LOAD_CACHE_MAX_BYTES) {
16837
+ this.entries.set(id, { mtimeMs: stat15.mtimeMs, size: stat15.size, data });
16838
+ this.bytes += stat15.size;
16839
+ }
16840
+ }
16841
+ delete(sessionId) {
16842
+ const cached = this.entries.get(sessionId);
16843
+ if (cached) this.bytes = Math.max(0, this.bytes - cached.size);
16844
+ this.entries.delete(sessionId);
16845
+ }
16846
+ };
16847
+
16571
16848
  // src/storage/session-store/load-session-data.ts
16572
16849
  import { createReadStream as createReadStream2 } from "node:fs";
16573
16850
  import { createInterface as createInterface2 } from "node:readline";
@@ -16822,6 +17099,178 @@ function emitDamaged(params, detail) {
16822
17099
  });
16823
17100
  }
16824
17101
 
17102
+ // src/storage/session-store/prune-helpers.ts
17103
+ import * as fsp13 from "node:fs/promises";
17104
+ import * as path19 from "node:path";
17105
+ function isPrunableSessionJsonl(name) {
17106
+ return name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
17107
+ }
17108
+ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
17109
+ const cutoff = Date.now() - maxAgeDays * 864e5;
17110
+ let deleted = 0;
17111
+ const pruneFile = async (dir, name, prefix) => {
17112
+ const jsonlPath = path19.join(dir, name);
17113
+ try {
17114
+ const stat15 = await fsp13.stat(jsonlPath);
17115
+ if (stat15.mtimeMs >= cutoff) return;
17116
+ } catch {
17117
+ return;
17118
+ }
17119
+ const base = name.replace(/\.jsonl$/, "");
17120
+ const id = prefix ? `${prefix}/${base}` : base;
17121
+ await deleteSession(id);
17122
+ deleted++;
17123
+ };
17124
+ const entries = await fsp13.readdir(storeDir, { withFileTypes: true }).catch(() => []);
17125
+ for (const entry of entries) {
17126
+ if (entry.isFile()) {
17127
+ if (isPrunableSessionJsonl(entry.name)) await pruneFile(storeDir, entry.name, "");
17128
+ continue;
17129
+ }
17130
+ if (!entry.isDirectory()) continue;
17131
+ const dateDir = path19.join(storeDir, entry.name);
17132
+ const files = await fsp13.readdir(dateDir, { withFileTypes: true }).catch(() => []);
17133
+ for (const file of files) {
17134
+ if (!file.isFile() || !isPrunableSessionJsonl(file.name)) continue;
17135
+ await pruneFile(dateDir, file.name, entry.name);
17136
+ }
17137
+ }
17138
+ for (const entry of entries) {
17139
+ if (!entry.isDirectory()) continue;
17140
+ const dateDir = path19.join(storeDir, entry.name);
17141
+ try {
17142
+ const remaining = await fsp13.readdir(dateDir);
17143
+ if (remaining.length === 0) {
17144
+ await fsp13.rmdir(dateDir).catch(() => void 0);
17145
+ }
17146
+ } catch {
17147
+ }
17148
+ }
17149
+ return deleted;
17150
+ }
17151
+
17152
+ // src/storage/session-store/search-events.ts
17153
+ import * as fsp14 from "node:fs/promises";
17154
+ async function searchSessionEvents(args) {
17155
+ const { file, secretScrubber, predicate, limit, signal } = args;
17156
+ const out = [];
17157
+ let stat15;
17158
+ try {
17159
+ stat15 = await fsp14.stat(file);
17160
+ } catch (err) {
17161
+ if (err.code === "ENOENT") return [];
17162
+ throw err;
17163
+ }
17164
+ if (stat15.size === 0) return [];
17165
+ let fh;
17166
+ try {
17167
+ fh = await fsp14.open(file, "r");
17168
+ const CHUNK = 64 * 1024;
17169
+ const buf = Buffer.alloc(CHUNK);
17170
+ let leftover = "";
17171
+ let eventIndex = 0;
17172
+ for (let position = 0; ; position += buf.byteLength) {
17173
+ if (signal?.aborted) {
17174
+ const reason = signal.reason ?? new DOMException("Aborted", "AbortError");
17175
+ throw reason;
17176
+ }
17177
+ const { bytesRead } = await fh.read(buf, 0, CHUNK, position);
17178
+ if (bytesRead === 0) break;
17179
+ const text = leftover + buf.subarray(0, bytesRead).toString("utf8");
17180
+ const parts = text.split("\n");
17181
+ leftover = parts.pop() ?? "";
17182
+ for (const line of parts) {
17183
+ if (!line) continue;
17184
+ const ev = parseSessionEventLine(line, secretScrubber);
17185
+ if (!ev) continue;
17186
+ if (predicate(ev, eventIndex, ev.ts)) {
17187
+ out.push({ event: ev, eventIndex, ts: ev.ts });
17188
+ if (limit !== void 0 && out.length >= limit) return out;
17189
+ }
17190
+ eventIndex++;
17191
+ }
17192
+ }
17193
+ const trailing = parseSessionEventLine(leftover.trim() ? leftover : "", secretScrubber);
17194
+ if (trailing && predicate(trailing, eventIndex, trailing.ts)) {
17195
+ out.push({ event: trailing, eventIndex, ts: trailing.ts });
17196
+ }
17197
+ return out;
17198
+ } finally {
17199
+ if (fh) await fh.close().catch(() => void 0);
17200
+ }
17201
+ }
17202
+ function parseSessionEventLine(line, secretScrubber) {
17203
+ if (!line) return null;
17204
+ try {
17205
+ const parsed = JSON.parse(line);
17206
+ if (parsed === null || typeof parsed !== "object" || typeof parsed.type !== "string" || typeof parsed.ts !== "string") {
17207
+ return null;
17208
+ }
17209
+ return scrubPersistedSessionEvent(parsed, secretScrubber);
17210
+ } catch {
17211
+ return null;
17212
+ }
17213
+ }
17214
+
17215
+ // src/storage/session-store/shard-manifest.ts
17216
+ import * as fsp15 from "node:fs/promises";
17217
+
17218
+ // src/storage/session-summary.ts
17219
+ function compareSessionSummaries(a, b) {
17220
+ const aActivity = a.lastActivityAt ?? a.endedAt ?? a.startedAt;
17221
+ const bActivity = b.lastActivityAt ?? b.endedAt ?? b.startedAt;
17222
+ if (aActivity < bActivity) return 1;
17223
+ if (aActivity > bActivity) return -1;
17224
+ if (a.startedAt < b.startedAt) return 1;
17225
+ if (a.startedAt > b.startedAt) return -1;
17226
+ return a.id.localeCompare(b.id);
17227
+ }
17228
+ function matchesSessionFilter(summary, criteria) {
17229
+ if (criteria.since && summary.startedAt < criteria.since) return false;
17230
+ if (criteria.until && summary.startedAt > criteria.until) return false;
17231
+ if (criteria.provider && summary.provider !== criteria.provider) return false;
17232
+ if (criteria.model && summary.model !== criteria.model) return false;
17233
+ if (criteria.minTokens !== void 0 && summary.tokenTotal < criteria.minTokens) return false;
17234
+ if (criteria.titleContains) {
17235
+ const needle = criteria.titleContains.toLocaleLowerCase();
17236
+ if (!summary.title.toLocaleLowerCase().includes(needle)) return false;
17237
+ }
17238
+ return true;
17239
+ }
17240
+
17241
+ // src/storage/session-store/shard-manifest.ts
17242
+ async function readOrBuildShardManifestEntry(opts) {
17243
+ try {
17244
+ const raw = await fsp15.readFile(opts.manifestPath, "utf8");
17245
+ const parsed = JSON.parse(raw);
17246
+ return {
17247
+ summaries: Array.isArray(parsed.summaries) ? parsed.summaries : [],
17248
+ ids: Array.isArray(parsed.ids) ? parsed.ids : []
17249
+ };
17250
+ } catch {
17251
+ }
17252
+ const refs = await opts.collectSessionFilesInShard(opts.shardKey);
17253
+ const candidates = await mapWithConcurrency(
17254
+ refs,
17255
+ opts.concurrency,
17256
+ async (ref) => {
17257
+ const manifest = await opts.readSummaryManifest(ref.id);
17258
+ if (manifest) return { summary: manifest, needsBackfill: false };
17259
+ const summary = await opts.summaryHeaderFor(ref);
17260
+ if (!summary) return null;
17261
+ const hydrated = await opts.summaryFor(summary.id).catch(() => summary);
17262
+ return { summary: hydrated, needsBackfill: false };
17263
+ }
17264
+ );
17265
+ const summaries = candidates.filter((candidate) => candidate !== null).map((candidate) => candidate.summary);
17266
+ summaries.sort(compareSessionSummaries);
17267
+ const entry = { summaries, ids: summaries.map((summary) => summary.id) };
17268
+ await atomicWrite(opts.manifestPath, JSON.stringify(entry), { mode: 384 }).catch(
17269
+ () => void 0
17270
+ );
17271
+ return entry;
17272
+ }
17273
+
16825
17274
  // src/storage/session-store/summary-builder.ts
16826
17275
  import { createReadStream as createReadStream3 } from "node:fs";
16827
17276
  import { createInterface as createInterface3 } from "node:readline";
@@ -16961,11 +17410,11 @@ async function* iterateSessionEvents(file, secretScrubber) {
16961
17410
  }
16962
17411
 
16963
17412
  // src/storage/session-store/summary-header.ts
16964
- import * as fsp14 from "node:fs/promises";
17413
+ import * as fsp16 from "node:fs/promises";
16965
17414
  async function readSessionSummaryHeader(ref, secretScrubber) {
16966
17415
  let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
16967
17416
  try {
16968
- const stat15 = await fsp14.stat(ref.filePath);
17417
+ const stat15 = await fsp16.stat(ref.filePath);
16969
17418
  if (!stat15.isFile()) return damagedSummary(ref.id, stat15.mtime.toISOString());
16970
17419
  mtime = stat15.mtime.toISOString();
16971
17420
  } catch {
@@ -17010,128 +17459,6 @@ function damagedSummary(id, startedAt) {
17010
17459
  };
17011
17460
  }
17012
17461
 
17013
- // src/storage/session-store/shard-manifest.ts
17014
- import * as fsp15 from "node:fs/promises";
17015
-
17016
- // src/storage/session-summary.ts
17017
- function compareSessionSummaries(a, b) {
17018
- const aActivity = a.lastActivityAt ?? a.endedAt ?? a.startedAt;
17019
- const bActivity = b.lastActivityAt ?? b.endedAt ?? b.startedAt;
17020
- if (aActivity < bActivity) return 1;
17021
- if (aActivity > bActivity) return -1;
17022
- if (a.startedAt < b.startedAt) return 1;
17023
- if (a.startedAt > b.startedAt) return -1;
17024
- return a.id.localeCompare(b.id);
17025
- }
17026
- function matchesSessionFilter(summary, criteria) {
17027
- if (criteria.since && summary.startedAt < criteria.since) return false;
17028
- if (criteria.until && summary.startedAt > criteria.until) return false;
17029
- if (criteria.provider && summary.provider !== criteria.provider) return false;
17030
- if (criteria.model && summary.model !== criteria.model) return false;
17031
- if (criteria.minTokens !== void 0 && summary.tokenTotal < criteria.minTokens) return false;
17032
- if (criteria.titleContains) {
17033
- const needle = criteria.titleContains.toLocaleLowerCase();
17034
- if (!summary.title.toLocaleLowerCase().includes(needle)) return false;
17035
- }
17036
- return true;
17037
- }
17038
-
17039
- // src/storage/session-store/shard-manifest.ts
17040
- async function readOrBuildShardManifestEntry(opts) {
17041
- try {
17042
- const raw = await fsp15.readFile(opts.manifestPath, "utf8");
17043
- const parsed = JSON.parse(raw);
17044
- return {
17045
- summaries: Array.isArray(parsed.summaries) ? parsed.summaries : [],
17046
- ids: Array.isArray(parsed.ids) ? parsed.ids : []
17047
- };
17048
- } catch {
17049
- }
17050
- const refs = await opts.collectSessionFilesInShard(opts.shardKey);
17051
- const candidates = await mapWithConcurrency(
17052
- refs,
17053
- opts.concurrency,
17054
- async (ref) => {
17055
- const manifest = await opts.readSummaryManifest(ref.id);
17056
- if (manifest) return { summary: manifest, needsBackfill: false };
17057
- const summary = await opts.summaryHeaderFor(ref);
17058
- if (!summary) return null;
17059
- const hydrated = await opts.summaryFor(summary.id).catch(() => summary);
17060
- return { summary: hydrated, needsBackfill: false };
17061
- }
17062
- );
17063
- const summaries = candidates.filter((candidate) => candidate !== null).map((candidate) => candidate.summary);
17064
- summaries.sort(compareSessionSummaries);
17065
- const entry = { summaries, ids: summaries.map((summary) => summary.id) };
17066
- await atomicWrite(opts.manifestPath, JSON.stringify(entry), { mode: 384 }).catch(
17067
- () => void 0
17068
- );
17069
- return entry;
17070
- }
17071
-
17072
- // src/storage/session-store/search-events.ts
17073
- import * as fsp16 from "node:fs/promises";
17074
- async function searchSessionEvents(args) {
17075
- const { file, secretScrubber, predicate, limit, signal } = args;
17076
- const out = [];
17077
- let stat15;
17078
- try {
17079
- stat15 = await fsp16.stat(file);
17080
- } catch (err) {
17081
- if (err.code === "ENOENT") return [];
17082
- throw err;
17083
- }
17084
- if (stat15.size === 0) return [];
17085
- let fh;
17086
- try {
17087
- fh = await fsp16.open(file, "r");
17088
- const CHUNK = 64 * 1024;
17089
- const buf = Buffer.alloc(CHUNK);
17090
- let leftover = "";
17091
- let eventIndex = 0;
17092
- for (let position = 0; ; position += buf.byteLength) {
17093
- if (signal?.aborted) {
17094
- const reason = signal.reason ?? new DOMException("Aborted", "AbortError");
17095
- throw reason;
17096
- }
17097
- const { bytesRead } = await fh.read(buf, 0, CHUNK, position);
17098
- if (bytesRead === 0) break;
17099
- const text = leftover + buf.subarray(0, bytesRead).toString("utf8");
17100
- const parts = text.split("\n");
17101
- leftover = parts.pop() ?? "";
17102
- for (const line of parts) {
17103
- if (!line) continue;
17104
- const ev = parseSessionEventLine(line, secretScrubber);
17105
- if (!ev) continue;
17106
- if (predicate(ev, eventIndex, ev.ts)) {
17107
- out.push({ event: ev, eventIndex, ts: ev.ts });
17108
- if (limit !== void 0 && out.length >= limit) return out;
17109
- }
17110
- eventIndex++;
17111
- }
17112
- }
17113
- const trailing = parseSessionEventLine(leftover.trim() ? leftover : "", secretScrubber);
17114
- if (trailing && predicate(trailing, eventIndex, trailing.ts)) {
17115
- out.push({ event: trailing, eventIndex, ts: trailing.ts });
17116
- }
17117
- return out;
17118
- } finally {
17119
- if (fh) await fh.close().catch(() => void 0);
17120
- }
17121
- }
17122
- function parseSessionEventLine(line, secretScrubber) {
17123
- if (!line) return null;
17124
- try {
17125
- const parsed = JSON.parse(line);
17126
- if (parsed === null || typeof parsed !== "object" || typeof parsed.type !== "string" || typeof parsed.ts !== "string") {
17127
- return null;
17128
- }
17129
- return scrubPersistedSessionEvent(parsed, secretScrubber);
17130
- } catch {
17131
- return null;
17132
- }
17133
- }
17134
-
17135
17462
  // src/storage/session-store.ts
17136
17463
  var DefaultSessionStore = class _DefaultSessionStore {
17137
17464
  dir;
@@ -17272,13 +17599,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
17272
17599
  async fork(id, opts = {}) {
17273
17600
  return forkSession(this, id, opts);
17274
17601
  }
17602
+ /**
17603
+ * Capture the deterministic post-tool workspace identity through the store-owned CAS.
17604
+ */
17605
+ async captureWorkspaceCheckpoint(sessionId, promptIndex) {
17606
+ return captureCheckpoint(this.checkpointCas, sessionId, promptIndex);
17607
+ }
17608
+ /**
17609
+ * Materialize a store-owned checkpoint into an already isolated workspace.
17610
+ */
17275
17611
  async materializeWorkspaceCheckpoint(checkpoint, targetRoot) {
17276
- if (!this.checkpointCas) {
17277
- throw new Error(
17278
- "Workspace checkpoint materialization requires a projectRoot-aware session store"
17279
- );
17280
- }
17281
- return this.checkpointCas.materialize(checkpoint, targetRoot);
17612
+ return materializeCheckpoint(this.checkpointCas, checkpoint, targetRoot);
17282
17613
  }
17283
17614
  async resolveId(query) {
17284
17615
  const normalized = query.trim();
@@ -17363,9 +17694,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
17363
17694
  handle = await fsp17.open(file, "a", 384);
17364
17695
  } catch (err) {
17365
17696
  emitSessionStoreError(this.events, canonicalId, file, "resume", toErrorMessage(err), false);
17366
- throw new Error(`Failed to open session "${canonicalId}" for append: ${toErrorMessage(err)}`, {
17367
- cause: err
17368
- });
17697
+ throw new Error(
17698
+ `Failed to open session "${canonicalId}" for append: ${toErrorMessage(err)}`,
17699
+ {
17700
+ cause: err
17701
+ }
17702
+ );
17369
17703
  }
17370
17704
  try {
17371
17705
  const writer = new FileSessionWriter(
@@ -17562,7 +17896,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
17562
17896
  await withFileLock(this.indexFile, async () => {
17563
17897
  await this.invalidateShardManifestBySessionId(summary.id);
17564
17898
  const line = JSON.stringify(summary) + "\n";
17565
- await fsp17.appendFile(this.indexFile, line, "utf8");
17899
+ await fsp17.appendFile(this.indexFile, line, { encoding: "utf8", mode: SECRET_FILE_MODE });
17566
17900
  this._indexCache = null;
17567
17901
  this.indexAppendCount++;
17568
17902
  if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
@@ -17585,7 +17919,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
17585
17919
  await ensureDir(this.dir);
17586
17920
  await withFileLock(this.indexFile, async () => {
17587
17921
  const line = JSON.stringify({ action: "delete", id }) + "\n";
17588
- await fsp17.appendFile(this.indexFile, line, "utf8");
17922
+ await fsp17.appendFile(this.indexFile, line, { encoding: "utf8", mode: SECRET_FILE_MODE });
17589
17923
  this._indexCache = null;
17590
17924
  await this.invalidateShardManifestBySessionId(id);
17591
17925
  this.indexAppendCount++;
@@ -17793,12 +18127,28 @@ var DefaultSessionStore = class _DefaultSessionStore {
17793
18127
  });
17794
18128
  outcome = "failure";
17795
18129
  errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
17796
- emitSessionStoreRead(this.events, id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
18130
+ emitSessionStoreRead(
18131
+ this.events,
18132
+ id,
18133
+ manifest,
18134
+ "summary",
18135
+ outcome,
18136
+ Date.now() - t0,
18137
+ errorMsg
18138
+ );
17797
18139
  return summary;
17798
18140
  } catch (err) {
17799
18141
  outcome = "failure";
17800
18142
  errorMsg = toErrorMessage(err);
17801
- emitSessionStoreRead(this.events, id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
18143
+ emitSessionStoreRead(
18144
+ this.events,
18145
+ id,
18146
+ manifest,
18147
+ "summary",
18148
+ outcome,
18149
+ Date.now() - t0,
18150
+ errorMsg
18151
+ );
17802
18152
  return {
17803
18153
  id,
17804
18154
  title: "(damaged)",
@@ -17813,14 +18163,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
17813
18163
  const manifest = this.sessionPath(id, ".summary.json");
17814
18164
  try {
17815
18165
  const raw = await fsp17.readFile(manifest, "utf8");
17816
- emitSessionStoreRead(
17817
- this.events,
17818
- id,
17819
- manifest,
17820
- "summary",
17821
- "success",
17822
- Date.now() - startTime
17823
- );
18166
+ emitSessionStoreRead(this.events, id, manifest, "summary", "success", Date.now() - startTime);
17824
18167
  return JSON.parse(raw);
17825
18168
  } catch {
17826
18169
  return null;
@@ -18502,6 +18845,10 @@ var Director = class _Director {
18502
18845
  this.taskWorktrees.delete(tid);
18503
18846
  }
18504
18847
  }
18848
+ this.tasks.removeTasksOwnedBy(subagentId);
18849
+ for (const [taskId, update] of this.taskWorktrees) {
18850
+ if (update.subagentId === subagentId) this.taskWorktrees.delete(taskId);
18851
+ }
18505
18852
  this.budgetPolicy.removeSubagent(subagentId);
18506
18853
  this.manifestEntries.delete(subagentId);
18507
18854
  const meta = this.subagentMeta.get(subagentId);
@@ -20050,7 +20397,7 @@ var DefaultLogger = class _DefaultLogger {
20050
20397
  root.pendingFileWrites += 1;
20051
20398
  root.pendingFileBytes += bytes;
20052
20399
  this.enqueueRotate(this.file);
20053
- this._tail = this._tail.then(() => fsp20.appendFile(this.file, line)).catch(() => void 0).finally(() => {
20400
+ this._tail = this._tail.then(() => fsp20.appendFile(this.file, line, { mode: 384 })).catch(() => void 0).finally(() => {
20054
20401
  root.pendingFileWrites = Math.max(0, root.pendingFileWrites - 1);
20055
20402
  root.pendingFileBytes = Math.max(0, root.pendingFileBytes - bytes);
20056
20403
  });
@@ -20725,14 +21072,6 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
20725
21072
  var KIT_FILE = "KIT.md";
20726
21073
  var TOKENS_FILE = "tokens.json";
20727
21074
  var FOUNDATIONS_ID = "_foundations";
20728
- function stripFrontmatter(raw) {
20729
- if (!raw.startsWith("---")) return raw;
20730
- const end = raw.indexOf("\n---", 4);
20731
- if (end === -1) return raw;
20732
- let body = raw.slice(end + 4);
20733
- if (body.startsWith("\n")) body = body.slice(1);
20734
- return body;
20735
- }
20736
21075
  function parseList(value) {
20737
21076
  const trimmed = value.trim();
20738
21077
  const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
@@ -21281,12 +21620,12 @@ function verifyFiles(tokens, files) {
21281
21620
  const violations = [];
21282
21621
  let onPalette = 0;
21283
21622
  let offPalette = 0;
21284
- for (const { path: path39, text } of files) {
21623
+ for (const { path: path40, text } of files) {
21285
21624
  const lines = text.split("\n");
21286
21625
  lines.forEach((lineText, i) => {
21287
21626
  const lineNo = i + 1;
21288
21627
  const flag = (snippet, reason, axis = "color") => {
21289
- violations.push({ file: path39, line: lineNo, snippet: snippet.slice(0, 80), reason, axis });
21628
+ violations.push({ file: path40, line: lineNo, snippet: snippet.slice(0, 80), reason, axis });
21290
21629
  };
21291
21630
  for (const re of [HEX_RE, FUNC_COLOR_RE]) {
21292
21631
  re.lastIndex = 0;
@@ -21969,20 +22308,23 @@ function buildRecoveryStrategies(opts) {
21969
22308
  const delayMs = err.body?.retryAfterMs ?? 5e3;
21970
22309
  const delay = Math.min(6e4, Math.max(1e3, delayMs));
21971
22310
  if (ctx?.signal) {
21972
- await new Promise((resolve16) => {
22311
+ await new Promise((resolve17) => {
21973
22312
  if (ctx.signal.aborted) {
21974
- resolve16();
22313
+ resolve17();
21975
22314
  return;
21976
22315
  }
21977
- const timer = setTimeout(resolve16, delay);
21978
- ctx.signal.addEventListener(
21979
- "abort",
21980
- () => {
21981
- clearTimeout(timer);
21982
- resolve16();
21983
- },
21984
- { once: true }
21985
- );
22316
+ let settled = false;
22317
+ let timer;
22318
+ const onAbort = () => finish();
22319
+ const finish = () => {
22320
+ if (settled) return;
22321
+ settled = true;
22322
+ if (timer !== void 0) clearTimeout(timer);
22323
+ ctx.signal.removeEventListener("abort", onAbort);
22324
+ resolve17();
22325
+ };
22326
+ timer = setTimeout(finish, delay);
22327
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
21986
22328
  });
21987
22329
  } else {
21988
22330
  await new Promise((r) => setTimeout(r, delay));
@@ -24172,8 +24514,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
24172
24514
  });
24173
24515
  await Promise.race([
24174
24516
  drainPromise,
24175
- new Promise((resolve16) => {
24176
- drainTimer = setTimeout(resolve16, STREAM_DRAIN_TIMEOUT_MS);
24517
+ new Promise((resolve17) => {
24518
+ drainTimer = setTimeout(resolve17, STREAM_DRAIN_TIMEOUT_MS);
24177
24519
  })
24178
24520
  ]);
24179
24521
  } finally {
@@ -24470,7 +24812,7 @@ async function runProviderWithRetry(opts) {
24470
24812
  status: isProviderErr ? err.status : 0,
24471
24813
  description
24472
24814
  });
24473
- await new Promise((resolve16, reject) => {
24815
+ await new Promise((resolve17, reject) => {
24474
24816
  let settled = false;
24475
24817
  const cleanup = () => {
24476
24818
  clearTimeout(t);
@@ -24486,7 +24828,7 @@ async function runProviderWithRetry(opts) {
24486
24828
  if (settled) return;
24487
24829
  settled = true;
24488
24830
  cleanup();
24489
- resolve16();
24831
+ resolve17();
24490
24832
  }, delay);
24491
24833
  if (signal.aborted) {
24492
24834
  onAbort();
@@ -25140,101 +25482,15 @@ function resolveForeignToolIdsWithWarnings(opt) {
25140
25482
  const ids = [];
25141
25483
  const unknownIds = [];
25142
25484
  for (const id of opt) {
25143
- if (KNOWN_FOREIGN_IDS.has(id)) ids.push(id);
25144
- else unknownIds.push(id);
25145
- }
25146
- return { ids, unknownIds };
25147
- }
25148
- return { ids: FOREIGN_SKILL_TOOLS.map((t) => t.id), unknownIds: [] };
25149
- }
25150
-
25151
- // src/skills/frontmatter.ts
25152
- var SCALAR_KEYS = /* @__PURE__ */ new Set(["name", "description", "version", "license", "compatibility"]);
25153
- function parseSkillFrontmatter(raw) {
25154
- const text = normalizeLineEndings(raw);
25155
- if (!text.startsWith("---")) return {};
25156
- const end = text.indexOf("\n---", 4);
25157
- if (end === -1) return {};
25158
- return parseFrontmatterBlock(text.slice(4, end));
25159
- }
25160
- function normalizeLineEndings(s) {
25161
- return s.replace(/\r\n?/g, "\n");
25162
- }
25163
- function parseFrontmatterBlock(block) {
25164
- const out = {};
25165
- const lines = block.split("\n");
25166
- let i = 0;
25167
- while (i < lines.length) {
25168
- const line = lines[i] ?? "";
25169
- const m = /^([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/.exec(line);
25170
- if (!m) {
25171
- i++;
25172
- continue;
25173
- }
25174
- const key = m[1] ?? "";
25175
- const rest = (m[2] ?? "").trim();
25176
- if (key === "metadata") {
25177
- const map = {};
25178
- i++;
25179
- while (i < lines.length) {
25180
- const sub = lines[i] ?? "";
25181
- const sm = /^\s+([a-zA-Z0-9_.-]+):\s*(.*)$/.exec(sub);
25182
- if (!sm) break;
25183
- map[sm[1] ?? ""] = unquote((sm[2] ?? "").trim());
25184
- i++;
25185
- }
25186
- if (Object.keys(map).length > 0) out.metadata = map;
25187
- continue;
25188
- }
25189
- if (rest === "|" || rest === ">") {
25190
- const collected = [];
25191
- i++;
25192
- while (i < lines.length) {
25193
- const sub = lines[i] ?? "";
25194
- if (sub === "" || sub.startsWith(" ") || sub.startsWith(" ")) {
25195
- collected.push(sub.replace(/^\s+/, ""));
25196
- i++;
25197
- } else break;
25198
- }
25199
- out[normalizeKey(key)] = collected.join("\n").trim();
25200
- continue;
25201
- }
25202
- if (key === "allowed-tools" || key === "allowedTools") {
25203
- out.allowedTools = rest.split(/[\s,]+/).filter(Boolean);
25204
- i++;
25205
- continue;
25206
- }
25207
- if (SCALAR_KEYS.has(key)) {
25208
- out[key] = unquote(rest);
25209
- i++;
25210
- continue;
25211
- }
25212
- i++;
25213
- }
25214
- return out;
25215
- }
25216
- function normalizeKey(key) {
25217
- return key === "allowed-tools" ? "allowedTools" : key;
25218
- }
25219
- function unquote(s) {
25220
- if (s.length >= 2 && (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'"))) {
25221
- return s.slice(1, -1);
25485
+ if (KNOWN_FOREIGN_IDS.has(id)) ids.push(id);
25486
+ else unknownIds.push(id);
25487
+ }
25488
+ return { ids, unknownIds };
25222
25489
  }
25223
- return s;
25224
- }
25225
- function isValidSkillNameFormat(name) {
25226
- return name.length >= 1 && name.length <= 64 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
25490
+ return { ids: FOREIGN_SKILL_TOOLS.map((t) => t.id), unknownIds: [] };
25227
25491
  }
25228
25492
 
25229
25493
  // src/execution/skill-loader.ts
25230
- function stripFrontmatter2(raw) {
25231
- if (!raw.startsWith("---")) return raw;
25232
- const end = raw.indexOf("\n---", 4);
25233
- if (end === -1) return raw;
25234
- let body = raw.slice(end + 4);
25235
- if (body.startsWith("\n")) body = body.slice(1);
25236
- return body;
25237
- }
25238
25494
  function compactSkillBody(body) {
25239
25495
  const sections = [];
25240
25496
  const overviewMatch = body.match(/##\s*Overview\s*\n([\s\S]*?)(?=\n##|\n$|$)/i);
@@ -25273,6 +25529,9 @@ var DefaultSkillLoader = class {
25273
25529
  cache;
25274
25530
  entriesCache;
25275
25531
  bodyCache = /* @__PURE__ */ new Map();
25532
+ /** Load-time diagnostics from the most recent full `list()` scan. */
25533
+ skipped = [];
25534
+ shadowed = [];
25276
25535
  constructor(opts) {
25277
25536
  const readClaude = opts.readClaudeSkills !== false;
25278
25537
  const foreignIds = resolveForeignToolIds(opts.foreignSources);
@@ -25302,6 +25561,9 @@ var DefaultSkillLoader = class {
25302
25561
  if (this.cache) return this.cache;
25303
25562
  const found = [];
25304
25563
  const seen = /* @__PURE__ */ new Set();
25564
+ const claimedBy = /* @__PURE__ */ new Map();
25565
+ this.skipped = [];
25566
+ this.shadowed = [];
25305
25567
  for (const { dir, source, originTool } of this.dirs) {
25306
25568
  try {
25307
25569
  const entries = (await fs5.readdir(dir, { withFileTypes: true })).sort(
@@ -25310,27 +25572,61 @@ var DefaultSkillLoader = class {
25310
25572
  for (const e of entries) {
25311
25573
  if (!await entryIsDirectory(dir, e)) continue;
25312
25574
  const skillFile = path25.join(dir, e.name, "SKILL.md");
25575
+ let raw;
25313
25576
  try {
25314
- const raw = await fs5.readFile(skillFile, "utf8");
25315
- const fm = parseSkillFrontmatter(raw);
25316
- if (!fm.name || !fm.description) continue;
25317
- if (!isValidSkillNameFormat(fm.name)) continue;
25318
- if (seen.has(fm.name)) continue;
25319
- seen.add(fm.name);
25320
- found.push({
25577
+ raw = await fs5.readFile(skillFile, "utf8");
25578
+ } catch {
25579
+ continue;
25580
+ }
25581
+ const fm = parseSkillFrontmatter(raw);
25582
+ if (!fm.name) {
25583
+ this.skipped.push({ dir, entry: e.name, reason: "missing-name" });
25584
+ continue;
25585
+ }
25586
+ if (!fm.description) {
25587
+ this.skipped.push({
25588
+ dir,
25589
+ entry: e.name,
25590
+ reason: "missing-description",
25591
+ name: fm.name
25592
+ });
25593
+ continue;
25594
+ }
25595
+ if (!isValidSkillNameFormat(fm.name)) {
25596
+ this.skipped.push({
25597
+ dir,
25598
+ entry: e.name,
25599
+ reason: "invalid-name-format",
25600
+ name: fm.name
25601
+ });
25602
+ continue;
25603
+ }
25604
+ if (seen.has(fm.name)) {
25605
+ const claimant = claimedBy.get(fm.name);
25606
+ this.shadowed.push({
25321
25607
  name: fm.name,
25322
- description: fm.description,
25323
- version: fm.version,
25324
- license: fm.license,
25325
- compatibility: fm.compatibility,
25326
- metadata: fm.metadata,
25327
- allowedTools: fm.allowedTools,
25328
- path: skillFile,
25329
25608
  source,
25330
- originTool
25609
+ path: skillFile,
25610
+ shadowedBy: claimant?.source ?? "unknown",
25611
+ shadowedByPath: claimant?.path
25331
25612
  });
25332
- } catch {
25613
+ continue;
25333
25614
  }
25615
+ seen.add(fm.name);
25616
+ claimedBy.set(fm.name, { source, path: skillFile });
25617
+ found.push({
25618
+ name: fm.name,
25619
+ description: fm.description,
25620
+ trigger: fm.trigger,
25621
+ version: fm.version,
25622
+ license: fm.license,
25623
+ compatibility: fm.compatibility,
25624
+ metadata: fm.metadata,
25625
+ allowedTools: fm.allowedTools,
25626
+ path: skillFile,
25627
+ source,
25628
+ originTool
25629
+ });
25334
25630
  }
25335
25631
  } catch {
25336
25632
  }
@@ -25359,11 +25655,12 @@ var DefaultSkillLoader = class {
25359
25655
  const skills = await this.list();
25360
25656
  const entries = [];
25361
25657
  for (const s of skills) {
25362
- const { trigger, scope } = parseDescriptionFromText(s.description ?? "");
25658
+ const parsed = parseDescriptionFromText(s.description ?? "");
25659
+ const trigger = s.trigger?.trim() || parsed.trigger;
25363
25660
  entries.push({
25364
25661
  name: s.name,
25365
25662
  trigger,
25366
- scope,
25663
+ scope: parsed.scope,
25367
25664
  source: s.source,
25368
25665
  originTool: s.originTool,
25369
25666
  path: s.path
@@ -25376,6 +25673,16 @@ var DefaultSkillLoader = class {
25376
25673
  this.cache = void 0;
25377
25674
  this.entriesCache = void 0;
25378
25675
  this.bodyCache.clear();
25676
+ this.skipped = [];
25677
+ this.shadowed = [];
25678
+ }
25679
+ /**
25680
+ * Load-time diagnostics from the most recent full `list()` scan: skills
25681
+ * skipped as malformed and skills shadowed by a higher-priority layer.
25682
+ * Returns copies so callers cannot mutate internal state.
25683
+ */
25684
+ diagnostics() {
25685
+ return { skipped: [...this.skipped], shadowed: [...this.shadowed] };
25379
25686
  }
25380
25687
  async readBody(name) {
25381
25688
  const key = name.toLowerCase();
@@ -25399,7 +25706,7 @@ var DefaultSkillLoader = class {
25399
25706
  result = await fs5.readFile(savePath, "utf8");
25400
25707
  } catch {
25401
25708
  const full = await fs5.readFile(m.path, "utf8");
25402
- const body = stripFrontmatter2(full);
25709
+ const body = stripFrontmatter(full);
25403
25710
  const compact = compactSkillBody(body);
25404
25711
  if (compact) {
25405
25712
  result = `## Overview
@@ -25935,6 +26242,9 @@ function readPolicy(ctx) {
25935
26242
 
25936
26243
  // src/execution/tool-executor.ts
25937
26244
  import { randomUUID as randomUUID16 } from "node:crypto";
26245
+ import * as fs9 from "node:fs/promises";
26246
+ import * as path30 from "node:path";
26247
+ import { isDeepStrictEqual } from "node:util";
25938
26248
 
25939
26249
  // src/observability/process-telemetry.ts
25940
26250
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
@@ -25943,11 +26253,6 @@ function runWithProcessTelemetry(context, run) {
25943
26253
  return storage2.run(context, run);
25944
26254
  }
25945
26255
 
25946
- // src/execution/tool-executor.ts
25947
- import { isDeepStrictEqual } from "node:util";
25948
- import * as fs9 from "node:fs/promises";
25949
- import * as path30 from "node:path";
25950
-
25951
26256
  // src/security/kanban-boundary.ts
25952
26257
  import { realpath as realpath3 } from "node:fs/promises";
25953
26258
  import * as path28 from "node:path";
@@ -26101,8 +26406,8 @@ function resolveInputPath(value, ctx) {
26101
26406
  return relativeToProject(absolute, ctx.projectRoot);
26102
26407
  }
26103
26408
  function relativeToProject(absolute, projectRoot) {
26104
- const relative7 = path28.relative(projectRoot, absolute).replace(/\\/g, "/");
26105
- return relative7.startsWith("../") || path28.isAbsolute(relative7) ? absolute : relative7 || ".";
26409
+ const relative8 = path28.relative(projectRoot, absolute).replace(/\\/g, "/");
26410
+ return relative8.startsWith("../") || path28.isAbsolute(relative8) ? absolute : relative8 || ".";
26106
26411
  }
26107
26412
  function extractPatchTargets(patchText, strip) {
26108
26413
  const targets = [];
@@ -26315,47 +26620,6 @@ function sliceUtf8Suffix(text, maxBytes) {
26315
26620
  return text.slice(start);
26316
26621
  }
26317
26622
 
26318
- // src/execution/tool-executor-results.ts
26319
- function unknownToolResult(use, listFns) {
26320
- return {
26321
- type: "tool_result",
26322
- tool_use_id: use.id,
26323
- content: `Tool "${use.name}" is not registered. Available tools: ${listFns().join(", ")}`,
26324
- is_error: true
26325
- };
26326
- }
26327
- function malformedInputResult(use, raw) {
26328
- let content = `Tool "${use.name}" received arguments that were not a valid JSON object, so they could not be parsed. Re-issue the call with the arguments encoded as a single well-formed JSON object matching the tool's input schema.`;
26329
- if (raw) {
26330
- const max = 800;
26331
- const excerpt2 = raw.length > max ? `${raw.slice(0, max)}\u2026 (truncated, ${raw.length} chars total)` : raw;
26332
- content += ` Common cause: a string field (e.g. code in old_string/new_string) contains literal newlines, quotes, or backslashes that must be JSON-escaped, or the payload was cut off mid-stream. The raw arguments received were:
26333
- ${excerpt2}`;
26334
- }
26335
- return {
26336
- type: "tool_result",
26337
- tool_use_id: use.id,
26338
- content,
26339
- is_error: true
26340
- };
26341
- }
26342
- function deniedResult(use, reason) {
26343
- return {
26344
- type: "tool_result",
26345
- tool_use_id: use.id,
26346
- content: `Tool "${use.name}" denied: ${reason ?? "policy"}`,
26347
- is_error: true
26348
- };
26349
- }
26350
- function blockedByHookResult(use, reason) {
26351
- return {
26352
- type: "tool_result",
26353
- tool_use_id: use.id,
26354
- content: `Tool "${use.name}" was blocked by a PreToolUse hook: ${reason ?? "no reason given"}`,
26355
- is_error: true
26356
- };
26357
- }
26358
-
26359
26623
  // src/execution/tool-executor-logging.ts
26360
26624
  function toolLogBase(ctx, use, toolName, durationMs) {
26361
26625
  return {
@@ -26420,6 +26684,82 @@ function logToolFailure(opts, ctx, use, toolName, durationMs, err) {
26420
26684
  });
26421
26685
  }
26422
26686
 
26687
+ // src/execution/tool-executor-results.ts
26688
+ function unknownToolResult(use, listFns) {
26689
+ return {
26690
+ type: "tool_result",
26691
+ tool_use_id: use.id,
26692
+ content: `Tool "${use.name}" is not registered. Available tools: ${listFns().join(", ")}`,
26693
+ is_error: true
26694
+ };
26695
+ }
26696
+ function malformedInputResult(use, raw) {
26697
+ let content = `Tool "${use.name}" received arguments that were not a valid JSON object, so they could not be parsed. Re-issue the call with the arguments encoded as a single well-formed JSON object matching the tool's input schema.`;
26698
+ if (raw) {
26699
+ const max = 800;
26700
+ const excerpt2 = raw.length > max ? `${raw.slice(0, max)}\u2026 (truncated, ${raw.length} chars total)` : raw;
26701
+ content += ` Common cause: a string field (e.g. code in old_string/new_string) contains literal newlines, quotes, or backslashes that must be JSON-escaped, or the payload was cut off mid-stream. The raw arguments received were:
26702
+ ${excerpt2}`;
26703
+ }
26704
+ return {
26705
+ type: "tool_result",
26706
+ tool_use_id: use.id,
26707
+ content,
26708
+ is_error: true
26709
+ };
26710
+ }
26711
+ function deniedResult(use, reason) {
26712
+ return {
26713
+ type: "tool_result",
26714
+ tool_use_id: use.id,
26715
+ content: `Tool "${use.name}" denied: ${reason ?? "policy"}`,
26716
+ is_error: true
26717
+ };
26718
+ }
26719
+ function blockedByHookResult(use, reason) {
26720
+ return {
26721
+ type: "tool_result",
26722
+ tool_use_id: use.id,
26723
+ content: `Tool "${use.name}" was blocked by a PreToolUse hook: ${reason ?? "no reason given"}`,
26724
+ is_error: true
26725
+ };
26726
+ }
26727
+
26728
+ // src/execution/tool-error-taxonomy.ts
26729
+ function toolErrorResult(use, err, opts = {}) {
26730
+ const { category, retryable, detail } = classifyToolError(err);
26731
+ const rawMessage = err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error";
26732
+ const userMessage = opts.scrubber ? opts.scrubber(rawMessage) : rawMessage;
26733
+ const info = {
26734
+ category,
26735
+ retryable,
26736
+ userMessage,
26737
+ ...detail !== void 0 ? { detail } : {}
26738
+ };
26739
+ const prefix = formatErrorPrefix(category, retryable);
26740
+ const detailSuffix = detail ? ` [${detail}]` : "";
26741
+ return {
26742
+ type: "tool_result",
26743
+ tool_use_id: use.id,
26744
+ content: `${prefix}: ${userMessage}${detailSuffix}`,
26745
+ is_error: true,
26746
+ // Attach structured info for programmatic consumers (retry logic,
26747
+ // audit log, observability).
26748
+ _toolErrorInfo: info
26749
+ };
26750
+ }
26751
+ function formatErrorPrefix(category, retryable) {
26752
+ const label = CATEGORY_LABELS[category] ?? "Error";
26753
+ return retryable ? `${label} (retryable)` : label;
26754
+ }
26755
+ var CATEGORY_LABELS = {
26756
+ ["transient" /* TRANSIENT */]: "Transient error",
26757
+ ["not_found" /* NOT_FOUND */]: "Not found",
26758
+ ["permission" /* PERMISSION */]: "Permission denied",
26759
+ ["validation" /* VALIDATION */]: "Validation error",
26760
+ ["fatal" /* FATAL */]: "Error"
26761
+ };
26762
+
26423
26763
  // src/execution/tool-executor-stream.ts
26424
26764
  async function executeStreamedTool({
26425
26765
  tool,
@@ -26520,12 +26860,27 @@ ${progressTail}` : progressTail;
26520
26860
  }
26521
26861
 
26522
26862
  // src/execution/tool-executor.ts
26863
+ async function mapWithConcurrency2(items, limit, run) {
26864
+ if (items.length === 0) return [];
26865
+ const results = new Array(items.length);
26866
+ let nextIndex = 0;
26867
+ const worker = async () => {
26868
+ while (nextIndex < items.length) {
26869
+ const index = nextIndex++;
26870
+ results[index] = await run(items[index]);
26871
+ }
26872
+ };
26873
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
26874
+ return results;
26875
+ }
26523
26876
  var ToolExecutor = class _ToolExecutor {
26524
26877
  constructor(registry, opts) {
26525
26878
  this.registry = registry;
26526
26879
  this.opts = opts;
26527
26880
  this.iterationTimeoutMs = opts.iterationTimeoutMs ?? 3e5;
26528
26881
  this.maxToolTimeoutMs = opts.maxToolTimeoutMs ?? 3e5;
26882
+ const requestedParallelism = opts.maxParallelTools ?? 4;
26883
+ this.maxParallelTools = Number.isFinite(requestedParallelism) ? Math.max(1, Math.min(16, Math.floor(requestedParallelism))) : 4;
26529
26884
  this.serializer = createToolOutputSerializer({
26530
26885
  perIterationOutputCapBytes: opts.perIterationOutputCapBytes ?? 1e5
26531
26886
  });
@@ -26541,6 +26896,7 @@ var ToolExecutor = class _ToolExecutor {
26541
26896
  serializer;
26542
26897
  iterationTimeoutMs;
26543
26898
  maxToolTimeoutMs;
26899
+ maxParallelTools;
26544
26900
  /**
26545
26901
  * Clear the interactive confirm awaiter so the executor returns
26546
26902
  * `ToolConfirmPendingResult` instead of blocking on stdin. Used by
@@ -26734,18 +27090,18 @@ ${errorDetails}`,
26734
27090
  if (this.opts.confirmAwaiter) {
26735
27091
  const awaiter = this.opts.confirmAwaiter;
26736
27092
  const choice = await new Promise(
26737
- (resolve16, reject) => {
27093
+ (resolve17, reject) => {
26738
27094
  const signal = ctx.signal;
26739
- const onAbort = () => resolve16("abort");
27095
+ const onAbort = () => resolve17("abort");
26740
27096
  if (signal.aborted) {
26741
- resolve16("abort");
27097
+ resolve17("abort");
26742
27098
  return;
26743
27099
  }
26744
27100
  signal.addEventListener("abort", onAbort, { once: true });
26745
27101
  awaiter(tool, use.input, use.id, suggestedPattern).then(
26746
27102
  (c) => {
26747
27103
  signal.removeEventListener("abort", onAbort);
26748
- resolve16(c);
27104
+ resolve17(c);
26749
27105
  },
26750
27106
  (e) => {
26751
27107
  signal.removeEventListener("abort", onAbort);
@@ -26868,12 +27224,9 @@ ${post.additionalContext}`;
26868
27224
  const { category, retryable, detail } = classifyToolError(err);
26869
27225
  this.hintRenderMode(tool.name);
26870
27226
  this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
26871
- const result = {
26872
- type: "tool_result",
26873
- tool_use_id: use.id,
26874
- content: `Tool "${tool.name}" threw: ${scrubbed}`,
26875
- is_error: true
26876
- };
27227
+ const result = toolErrorResult(use, err, {
27228
+ scrubber: (s) => this.opts.secretScrubber.scrub(s)
27229
+ });
26877
27230
  budget = this.budgetForString(result.content, budget);
26878
27231
  if (err instanceof Error) span?.recordError(err);
26879
27232
  span?.setAttribute("tool.is_error", true);
@@ -26893,21 +27246,17 @@ ${post.additionalContext}`;
26893
27246
  const isStructured = isWrongStackError(err);
26894
27247
  const msg = isStructured ? err.describe() : toErrorMessage(err);
26895
27248
  const scrubbed = this.opts.secretScrubber.scrub(msg);
26896
- const { category, retryable, detail } = classifyToolError(err);
26897
27249
  const tool = this.registry.get(use.name);
26898
27250
  const toolName = tool?.name ?? use.name;
26899
27251
  this.hintRenderMode(toolName);
26900
27252
  this.opts.renderer?.writeToolResult(toolName, scrubbed, true);
26901
- const result = {
26902
- type: "tool_result",
26903
- tool_use_id: use.id,
26904
- content: isStructured ? scrubbed : `Tool "${use.name}" execution failed: ${scrubbed}`,
26905
- is_error: true
26906
- };
27253
+ const result = toolErrorResult(use, err, {
27254
+ scrubber: (s) => this.opts.secretScrubber.scrub(s)
27255
+ });
27256
+ if (isStructured) {
27257
+ result.content = scrubbed;
27258
+ }
26907
27259
  budget = this.budgetForString(result.content, budget);
26908
- void category;
26909
- void retryable;
26910
- void detail;
26911
27260
  return { result, tool, durationMs: 0 };
26912
27261
  }
26913
27262
  };
@@ -26919,7 +27268,7 @@ ${post.additionalContext}`;
26919
27268
  return { outputs, remainingBudget: budget };
26920
27269
  }
26921
27270
  if (strategy === "parallel") {
26922
- const outputs = await Promise.all(toolUses.map((use) => safeRun(use)));
27271
+ const outputs = await mapWithConcurrency2(toolUses, this.maxParallelTools, safeRun);
26923
27272
  return { outputs, remainingBudget: budget };
26924
27273
  }
26925
27274
  const nonMutating = [];
@@ -26930,7 +27279,7 @@ ${post.additionalContext}`;
26930
27279
  if (tool?.mutating) mutating.push(use);
26931
27280
  else nonMutating.push(use);
26932
27281
  }
26933
- const firstPass = await Promise.all(nonMutating.map((use) => safeRun(use)));
27282
+ const firstPass = await mapWithConcurrency2(nonMutating, this.maxParallelTools, safeRun);
26934
27283
  const secondPass = [];
26935
27284
  for (const use of mutating) {
26936
27285
  secondPass.push(await safeRun(use));
@@ -27065,29 +27414,32 @@ ${post.additionalContext}`;
27065
27414
  let output;
27066
27415
  const execute = () => typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
27067
27416
  const telemetryToolCallId = toolUseId ?? `nested-${randomUUID16()}`;
27068
- const toolPromise = this.opts.events ? runWithNetworkTelemetry({
27069
- events: this.opts.events,
27070
- sessionId: resolveEventSessionId(ctx),
27071
- ...ctx.traceId ? { traceId: ctx.traceId } : {},
27072
- ...ctx.agentId ? { agentId: ctx.agentId } : {},
27073
- toolCallId: telemetryToolCallId,
27074
- initiator: "tool",
27075
- operationName: tool.name
27076
- }, () => runWithProcessTelemetry(
27417
+ const toolPromise = this.opts.events ? runWithNetworkTelemetry(
27077
27418
  {
27078
27419
  events: this.opts.events,
27079
27420
  sessionId: resolveEventSessionId(ctx),
27080
27421
  ...ctx.traceId ? { traceId: ctx.traceId } : {},
27081
27422
  ...ctx.agentId ? { agentId: ctx.agentId } : {},
27082
27423
  toolCallId: telemetryToolCallId,
27083
- toolName: tool.name
27424
+ initiator: "tool",
27425
+ operationName: tool.name
27084
27426
  },
27085
- execute
27086
- )) : execute();
27427
+ () => runWithProcessTelemetry(
27428
+ {
27429
+ events: this.opts.events,
27430
+ sessionId: resolveEventSessionId(ctx),
27431
+ ...ctx.traceId ? { traceId: ctx.traceId } : {},
27432
+ ...ctx.agentId ? { agentId: ctx.agentId } : {},
27433
+ toolCallId: telemetryToolCallId,
27434
+ toolName: tool.name
27435
+ },
27436
+ execute
27437
+ )
27438
+ ) : execute();
27087
27439
  toolPromise.catch(() => {
27088
27440
  });
27089
27441
  try {
27090
- output = await new Promise((resolve16, reject) => {
27442
+ output = await new Promise((resolve17, reject) => {
27091
27443
  const onAbort = () => {
27092
27444
  setTimeout(() => reject(abortReasonToError(combined.reason)), 0);
27093
27445
  };
@@ -27095,7 +27447,7 @@ ${post.additionalContext}`;
27095
27447
  toolPromise.then(
27096
27448
  (v) => {
27097
27449
  combined.removeEventListener("abort", onAbort);
27098
- resolve16(v);
27450
+ resolve17(v);
27099
27451
  },
27100
27452
  (e) => {
27101
27453
  combined.removeEventListener("abort", onAbort);
@@ -28627,9 +28979,9 @@ var DefaultHealthRegistry = class {
28627
28979
  }
28628
28980
  async runOne(check) {
28629
28981
  let timer = null;
28630
- const timeout = new Promise((resolve16) => {
28982
+ const timeout = new Promise((resolve17) => {
28631
28983
  timer = setTimeout(
28632
- () => resolve16({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
28984
+ () => resolve17({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
28633
28985
  this.timeoutMs
28634
28986
  );
28635
28987
  });
@@ -28753,6 +29105,7 @@ function metricToolName(name) {
28753
29105
  }
28754
29106
 
28755
29107
  // src/observability/prometheus.ts
29108
+ import { timingSafeEqual } from "node:crypto";
28756
29109
  var NUMBER_FORMAT_INFINITY = "NaN";
28757
29110
  function escapeLabelValue(v) {
28758
29111
  return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
@@ -28809,21 +29162,47 @@ function renderPrometheus(snapshot) {
28809
29162
  return lines.join("\n") + "\n";
28810
29163
  }
28811
29164
  var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
29165
+ function isLoopbackBindHost(host) {
29166
+ const h = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
29167
+ return h === "127.0.0.1" || h === "localhost" || h === "::1" || h.startsWith("127.");
29168
+ }
29169
+ function bearerMatches(header, token) {
29170
+ if (!header) return false;
29171
+ const prefix = "bearer ";
29172
+ if (!header.toLowerCase().startsWith(prefix)) return false;
29173
+ const supplied = Buffer.from(header.slice(prefix.length).trim(), "utf8");
29174
+ const expected = Buffer.from(token, "utf8");
29175
+ if (supplied.length !== expected.length) return false;
29176
+ return timingSafeEqual(supplied, expected);
29177
+ }
28812
29178
  async function startMetricsServer(opts) {
28813
29179
  const tls = opts.tls;
28814
29180
  const useHttps = !!(tls?.cert && tls?.key);
28815
29181
  const host = opts.host ?? "127.0.0.1";
28816
- const path39 = opts.path ?? "/metrics";
29182
+ const path40 = opts.path ?? "/metrics";
28817
29183
  const healthPath = opts.healthPath ?? "/healthz";
28818
29184
  const healthRegistry = opts.healthRegistry;
29185
+ const token = opts.token;
29186
+ if (!isLoopbackBindHost(host) && !token) {
29187
+ throw new Error(
29188
+ `startMetricsServer: refusing to bind to non-loopback host "${host}" without a token \u2014 pass a token to expose metrics to the network, or bind to 127.0.0.1.`
29189
+ );
29190
+ }
28819
29191
  const listener = (req, res) => {
28820
29192
  if (!req.url || req.method !== "GET") {
28821
29193
  res.statusCode = req.url ? 405 : 400;
28822
29194
  res.end();
28823
29195
  return;
28824
29196
  }
29197
+ if (token && !bearerMatches(req.headers.authorization, token)) {
29198
+ res.statusCode = 401;
29199
+ res.setHeader("www-authenticate", "Bearer");
29200
+ res.setHeader("content-type", "text/plain; charset=utf-8");
29201
+ res.end("Unauthorized");
29202
+ return;
29203
+ }
28825
29204
  const url = req.url.split("?")[0];
28826
- if (url === path39) {
29205
+ if (url === path40) {
28827
29206
  let body;
28828
29207
  try {
28829
29208
  body = renderPrometheus(opts.sink.snapshot());
@@ -28869,14 +29248,14 @@ async function startMetricsServer(opts) {
28869
29248
  const { createServer } = await import("node:http");
28870
29249
  server = createServer(listener);
28871
29250
  }
28872
- await new Promise((resolve16, reject) => {
29251
+ await new Promise((resolve17, reject) => {
28873
29252
  const onError = (err) => {
28874
29253
  server.off("listening", onListening);
28875
29254
  reject(err);
28876
29255
  };
28877
29256
  const onListening = () => {
28878
29257
  server.off("error", onError);
28879
- resolve16();
29258
+ resolve17();
28880
29259
  };
28881
29260
  server.once("error", onError);
28882
29261
  server.once("listening", onListening);
@@ -28887,9 +29266,9 @@ async function startMetricsServer(opts) {
28887
29266
  const protocol = useHttps ? "https" : "http";
28888
29267
  return {
28889
29268
  port: boundPort,
28890
- url: `${protocol}://${host}:${boundPort}${path39}`,
28891
- close: () => new Promise((resolve16, reject) => {
28892
- server.close((err) => err ? reject(err) : resolve16());
29269
+ url: `${protocol}://${host}:${boundPort}${path40}`,
29270
+ close: () => new Promise((resolve17, reject) => {
29271
+ server.close((err) => err ? reject(err) : resolve17());
28893
29272
  })
28894
29273
  };
28895
29274
  }
@@ -29204,19 +29583,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
29204
29583
  function isRecord5(value) {
29205
29584
  return typeof value === "object" && value !== null && !Array.isArray(value);
29206
29585
  }
29207
- function error(diagnostics, code, path39, message) {
29208
- diagnostics.push({ severity: "error", code, path: path39, message });
29586
+ function error(diagnostics, code, path40, message) {
29587
+ diagnostics.push({ severity: "error", code, path: path40, message });
29209
29588
  }
29210
- function validatePatterns(value, path39, diagnostics) {
29589
+ function validatePatterns(value, path40, diagnostics) {
29211
29590
  if (!Array.isArray(value)) {
29212
- error(diagnostics, "invalid_pattern_list", path39, "must be an array of strings");
29591
+ error(diagnostics, "invalid_pattern_list", path40, "must be an array of strings");
29213
29592
  return void 0;
29214
29593
  }
29215
29594
  if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
29216
29595
  error(
29217
29596
  diagnostics,
29218
29597
  "too_many_patterns",
29219
- path39,
29598
+ path40,
29220
29599
  `must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
29221
29600
  );
29222
29601
  return void 0;
@@ -29224,7 +29603,7 @@ function validatePatterns(value, path39, diagnostics) {
29224
29603
  const patterns = [];
29225
29604
  const seen = /* @__PURE__ */ new Set();
29226
29605
  for (const [index, pattern] of value.entries()) {
29227
- const itemPath = `${path39}[${index}]`;
29606
+ const itemPath = `${path40}[${index}]`;
29228
29607
  if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
29229
29608
  error(
29230
29609
  diagnostics,
@@ -29307,37 +29686,304 @@ function validateTrustPolicy(value) {
29307
29686
  );
29308
29687
  }
29309
29688
  }
29310
- const rule = {};
29311
- for (const field of ["allow", "deny"]) {
29312
- if (rawRule[field] === void 0) continue;
29313
- const patterns = validatePatterns(rawRule[field], `${toolPath}.${field}`, diagnostics);
29314
- if (patterns) rule[field] = patterns;
29689
+ const rule = {};
29690
+ for (const field of ["allow", "deny"]) {
29691
+ if (rawRule[field] === void 0) continue;
29692
+ const patterns = validatePatterns(rawRule[field], `${toolPath}.${field}`, diagnostics);
29693
+ if (patterns) rule[field] = patterns;
29694
+ }
29695
+ for (const field of ["auto", "trustWorkdir", "denyPrivate"]) {
29696
+ const setting = rawRule[field];
29697
+ if (setting === void 0) continue;
29698
+ if (typeof setting !== "boolean") {
29699
+ error(diagnostics, "invalid_boolean", `${toolPath}.${field}`, "must be a boolean");
29700
+ } else {
29701
+ rule[field] = setting;
29702
+ }
29703
+ }
29704
+ policy[toolName] = rule;
29705
+ }
29706
+ return diagnostics.some((diagnostic) => diagnostic.severity === "error") ? { ok: false, diagnostics } : { ok: true, policy, diagnostics };
29707
+ }
29708
+
29709
+ // src/security/yolo-risk.ts
29710
+ import * as path34 from "node:path";
29711
+ var CATASTROPHIC_PATTERNS = [
29712
+ /\b(?:mkfs(?:\.[a-z0-9]+)?|mke2fs|newfs)\b/i,
29713
+ // make a filesystem — wipes a partition
29714
+ /\bformat\s+[A-Za-z]:/i,
29715
+ // format C: — wipes a Windows volume
29716
+ /\bdiskpart\b/i,
29717
+ // Windows partition editor
29718
+ /\bdd\b[^|]*\bof=(?:\/dev\/|\\\\[.?]\\)/i,
29719
+ // dd writing straight to a raw device
29720
+ />\s*\/dev\/(?:sd|hd|nvme|disk|mapper|vd)/i,
29721
+ // redirect into a raw block device
29722
+ /:\(\)\s*\{\s*:\|:&\s*\}\s*;/
29723
+ // classic fork bomb
29724
+ ];
29725
+ var HIGH_IMPACT_PATTERNS = [
29726
+ /\b(?:curl|wget|fetch|httpie|http|irm|iwr|Invoke-WebRequest|Invoke-RestMethod)\b[\s\S]{0,300}\|\s*(?:sudo\s+)?(?:sh|bash|zsh|fish|pwsh|powershell|iex|Invoke-Expression)\b/i,
29727
+ /\b(?:powershell|pwsh)(?:\.exe)?\b[\s\S]{0,120}-(?:enc|encodedcommand)\b/i,
29728
+ /\b(?:shutdown|reboot)\b/i
29729
+ ];
29730
+ var CATASTROPHIC_POSIX_ROOTS = /* @__PURE__ */ new Set([
29731
+ "/etc",
29732
+ "/usr",
29733
+ "/bin",
29734
+ "/sbin",
29735
+ "/lib",
29736
+ "/lib64",
29737
+ "/var",
29738
+ "/boot",
29739
+ "/dev",
29740
+ "/sys",
29741
+ "/proc",
29742
+ "/opt",
29743
+ "/root",
29744
+ "/home",
29745
+ "/srv",
29746
+ "/run",
29747
+ "/system",
29748
+ "/library",
29749
+ "/applications",
29750
+ "/users"
29751
+ ]);
29752
+ var CATASTROPHIC_WIN_SUBDIRS = /* @__PURE__ */ new Set([
29753
+ "windows",
29754
+ "system32",
29755
+ "winnt",
29756
+ "program files",
29757
+ "program files (x86)",
29758
+ "programdata",
29759
+ "users"
29760
+ ]);
29761
+ var SHELL_OPERATORS = /* @__PURE__ */ new Set(["&&", "||", "|", ";", ">", ">>", "<", "2>", "2>>"]);
29762
+ function getInputString(input, key) {
29763
+ if (!input || typeof input !== "object") return void 0;
29764
+ const value = input[key];
29765
+ return typeof value === "string" ? value : void 0;
29766
+ }
29767
+ function pathLooksInsideProject(rawPath, projectRoot) {
29768
+ if (!projectRoot) return false;
29769
+ if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
29770
+ const resolved = path34.resolve(projectRoot, rawPath);
29771
+ const relative8 = path34.relative(projectRoot, resolved);
29772
+ return !!relative8 && !relative8.startsWith("..") && !path34.isAbsolute(relative8);
29773
+ }
29774
+ function tokenizeShell(command) {
29775
+ return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
29776
+ }
29777
+ function commandSegment(tokens, start) {
29778
+ const out = [];
29779
+ for (let i = start; i < tokens.length; i++) {
29780
+ const token = tokens[i];
29781
+ if (token === void 0 || SHELL_OPERATORS.has(token)) break;
29782
+ out.push(token);
29783
+ }
29784
+ return out;
29785
+ }
29786
+ function hasShortFlag(args, letters) {
29787
+ const seen = /* @__PURE__ */ new Set();
29788
+ for (const arg of args) {
29789
+ if (!arg.startsWith("-") || arg.startsWith("--")) continue;
29790
+ for (const ch of arg.replace(/^-+/, "")) seen.add(ch.toLowerCase());
29791
+ }
29792
+ return letters.split("").every((letter) => seen.has(letter));
29793
+ }
29794
+ function hasRecursiveForceDelete(command, projectRoot) {
29795
+ const tokens = tokenizeShell(command);
29796
+ for (let i = 0; i < tokens.length; i++) {
29797
+ const token = tokens[i]?.toLowerCase();
29798
+ if (!token) continue;
29799
+ if (token === "rm" || token === "rmdir") {
29800
+ const args = commandSegment(tokens, i + 1);
29801
+ const recursiveForce = hasShortFlag(args, "rf") || args.some((arg) => arg === "--recursive") && args.some((arg) => arg === "--force");
29802
+ if (recursiveForce) {
29803
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
29804
+ if (targets.length > 0 && targets.every((target) => target.trim().length === 0)) {
29805
+ continue;
29806
+ }
29807
+ if (targets.length === 0) return true;
29808
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
29809
+ if (targets.some((target) => !pathLooksInsideProject(target, projectRoot))) return true;
29810
+ }
29811
+ }
29812
+ if (token === "remove-item" || token === "ri") {
29813
+ const args = commandSegment(tokens, i + 1).map((arg) => arg.toLowerCase());
29814
+ const recurse = args.some((arg) => arg === "-recurse" || arg === "-r");
29815
+ const force = args.some((arg) => arg === "-force" || arg === "-f");
29816
+ if (recurse && force && !args.includes("-whatif")) {
29817
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
29818
+ if (targets.length === 0) return true;
29819
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
29820
+ if (targets.some((target) => !pathLooksInsideProject(target, projectRoot))) return true;
29821
+ }
29822
+ }
29823
+ if (token === "rd" || token === "rmdir") {
29824
+ const args = commandSegment(tokens, i + 1).map((arg) => arg.toLowerCase());
29825
+ if (args.includes("/s")) {
29826
+ const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
29827
+ if (targets.length === 0) return true;
29828
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
29829
+ if (targets.some((target) => !pathLooksInsideProject(target, projectRoot))) return true;
29830
+ }
29831
+ }
29832
+ }
29833
+ return false;
29834
+ }
29835
+ function hasGitHistoryRewrite(command) {
29836
+ const tokens = tokenizeShell(command).map((token) => token.toLowerCase());
29837
+ for (let i = 0; i < tokens.length; i++) {
29838
+ if (tokens[i] !== "git") continue;
29839
+ const args = commandSegment(tokens, i + 1);
29840
+ if (args.includes("reset") && args.some((arg) => arg === "--hard" || arg.startsWith("--hard="))) {
29841
+ return true;
29842
+ }
29843
+ const cleanIdx = args.indexOf("clean");
29844
+ if (cleanIdx >= 0) {
29845
+ const cleanArgs = args.slice(cleanIdx + 1);
29846
+ if (cleanArgs.some((arg) => arg === "-f" || arg === "--force" || /^-[a-z]*f[a-z]*$/i.test(arg))) {
29847
+ return true;
29848
+ }
29849
+ }
29850
+ const pushIdx = args.indexOf("push");
29851
+ if (pushIdx >= 0) {
29852
+ const pushArgs = args.slice(pushIdx + 1);
29853
+ if (pushArgs.some(
29854
+ (arg) => arg === "-f" || arg === "--force" || arg === "--force-with-lease" || arg.startsWith("--force=") || arg.startsWith("--force-with-lease=")
29855
+ )) {
29856
+ return true;
29857
+ }
29858
+ }
29859
+ }
29860
+ return false;
29861
+ }
29862
+ function hasExternalPublish(command) {
29863
+ const tokens = tokenizeShell(command).map((token) => token.toLowerCase());
29864
+ for (let i = 0; i < tokens.length; i++) {
29865
+ const cmd = tokens[i];
29866
+ if (!cmd) continue;
29867
+ const args = commandSegment(tokens, i + 1);
29868
+ if (["npm", "pnpm", "yarn", "bun"].includes(cmd) && (args.includes("publish") || args.includes("deploy"))) {
29869
+ return true;
29870
+ }
29871
+ if (cmd === "cargo" && (args.includes("publish") || args.includes("yank"))) return true;
29872
+ if ((cmd === "docker" || cmd === "podman") && args.includes("push")) return true;
29873
+ if (cmd === "kubectl") {
29874
+ const deleteIdx = args.indexOf("delete");
29875
+ if (deleteIdx >= 0 && (args[deleteIdx + 1] === "namespace" || args[deleteIdx + 1] === "ns")) {
29876
+ return true;
29877
+ }
29878
+ if (args.includes("drain")) return true;
29879
+ }
29880
+ }
29881
+ return false;
29882
+ }
29883
+ function hasFindExec(command) {
29884
+ const tokens = tokenizeShell(command).map((token) => token.toLowerCase());
29885
+ for (let i = 0; i < tokens.length; i++) {
29886
+ if (tokens[i] !== "find") continue;
29887
+ const args = commandSegment(tokens, i + 1);
29888
+ if (args.some((arg) => arg === "-exec" || arg === "-ok" || arg === "-execdir")) return true;
29889
+ }
29890
+ return false;
29891
+ }
29892
+ function isCatastrophicDeleteTarget(rawTarget) {
29893
+ const t = rawTarget.replace(/^['"]|['"]$/g, "").trim();
29894
+ if (!t) return false;
29895
+ if (t === "*" || t === "." || t === "./" || t === ".\\" || t === "./*" || t === ".\\*") return true;
29896
+ const s = t.replace(/[\\/]\*+$/, "").replace(/[\\/]+$/, "");
29897
+ if (s === "") return true;
29898
+ if (s === "~" || /^\$HOME$/i.test(s) || /^%USERPROFILE%$/i.test(s)) return true;
29899
+ if (/^[A-Za-z]:$/.test(s)) return true;
29900
+ const norm = s.toLowerCase().replace(/\\/g, "/");
29901
+ if (CATASTROPHIC_POSIX_ROOTS.has(norm)) return true;
29902
+ const win = norm.match(/^[a-z]:\/([^/]+)$/);
29903
+ if (win?.[1] && CATASTROPHIC_WIN_SUBDIRS.has(win[1])) return true;
29904
+ return false;
29905
+ }
29906
+ function hasCatastrophicDelete(command) {
29907
+ const tokens = tokenizeShell(command);
29908
+ for (let i = 0; i < tokens.length; i++) {
29909
+ const token = tokens[i]?.toLowerCase();
29910
+ if (!token) continue;
29911
+ if (token === "rm") {
29912
+ const args = tokens.slice(i + 1);
29913
+ const recursiveOrForce = args.some(
29914
+ (arg) => /^-[^-]*[rf]/i.test(arg) || arg === "--recursive" || arg === "--force" || arg === "--no-preserve-root"
29915
+ );
29916
+ if (!recursiveOrForce) continue;
29917
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
29918
+ if (targets.length === 0) return true;
29919
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
29920
+ }
29921
+ if (token === "remove-item" || token === "ri") {
29922
+ const args = tokens.slice(i + 1);
29923
+ const recursive = args.some((arg) => {
29924
+ const a = arg.toLowerCase();
29925
+ return a === "-recurse" || a === "-force";
29926
+ });
29927
+ if (!recursive) continue;
29928
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
29929
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
29930
+ }
29931
+ if (token === "rmdir" || token === "rd") {
29932
+ const args = tokens.slice(i + 1);
29933
+ const recursive = args.some((arg) => arg.toLowerCase() === "/s");
29934
+ if (!recursive) continue;
29935
+ const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
29936
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
29315
29937
  }
29316
- for (const field of ["auto", "trustWorkdir", "denyPrivate"]) {
29317
- const setting = rawRule[field];
29318
- if (setting === void 0) continue;
29319
- if (typeof setting !== "boolean") {
29320
- error(diagnostics, "invalid_boolean", `${toolPath}.${field}`, "must be a boolean");
29321
- } else {
29322
- rule[field] = setting;
29323
- }
29938
+ if (token === "del" || token === "erase") {
29939
+ const args = tokens.slice(i + 1);
29940
+ const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
29941
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
29324
29942
  }
29325
- policy[toolName] = rule;
29326
29943
  }
29327
- return diagnostics.some((diagnostic) => diagnostic.severity === "error") ? { ok: false, diagnostics } : { ok: true, policy, diagnostics };
29944
+ return false;
29328
29945
  }
29329
-
29330
- // src/security/yolo-risk.ts
29331
- function getInputString(input, key) {
29332
- if (!input || typeof input !== "object") return void 0;
29333
- const value = input[key];
29334
- return typeof value === "string" ? value : void 0;
29946
+ function isClearlyDestructiveBashCommand(command, projectRoot) {
29947
+ const trimmed = command.trim();
29948
+ if (!trimmed) return false;
29949
+ if (hasRecursiveForceDelete(trimmed, projectRoot)) return true;
29950
+ if (hasGitHistoryRewrite(trimmed)) return true;
29951
+ if (hasExternalPublish(trimmed)) return true;
29952
+ if (hasFindExec(trimmed)) return true;
29953
+ if (hasCatastrophicDelete(trimmed)) return true;
29954
+ if (CATASTROPHIC_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
29955
+ if (HIGH_IMPACT_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
29956
+ return false;
29335
29957
  }
29336
29958
 
29337
29959
  // src/security/permission-policy.ts
29338
29960
  function matchesTrust(patterns, subject) {
29339
29961
  return patterns.includes(subject) || matchAny(patterns, subject);
29340
29962
  }
29963
+ function matchesCommandTrust(patterns, subject) {
29964
+ return patterns.includes(subject) || matchAnyCommand(patterns, subject);
29965
+ }
29966
+ function hasShellSubject(tool) {
29967
+ return tool.name === "bash" || tool.name === "shell" || tool.name === "exec" || hasCapability(tool, [
29968
+ ToolCapabilities.SHELL_ARBITRARY,
29969
+ ToolCapabilities.SHELL_RESTRICTED,
29970
+ ToolCapabilities.SHELL_EXEC
29971
+ ]);
29972
+ }
29973
+ function alwaysAllowUnavailableReason(tool, input) {
29974
+ const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
29975
+ if (subject !== void 0) return void 0;
29976
+ return `"always allow" needs a subject to remember, and ${tool.name} calls do not carry one (no subjectKey, and no path/url/name input). Recording it would store a rule that can never match. Approve this call, or set a trust rule for ${tool.name} explicitly.`;
29977
+ }
29978
+ function permissionFingerprint(tool) {
29979
+ const caps = [...tool.capabilities ?? []].sort();
29980
+ return JSON.stringify([
29981
+ tool.permission ?? "",
29982
+ tool.riskTier ?? "",
29983
+ tool.mutating ? "m" : "",
29984
+ caps
29985
+ ]);
29986
+ }
29341
29987
  function shellCommandLineFromInput(input) {
29342
29988
  const command = getInputString(input, "command") ?? getInputString(input, "cmd") ?? getInputString(input, "script");
29343
29989
  if (!command) return void 0;
@@ -29441,9 +30087,13 @@ var DefaultPermissionPolicy = class {
29441
30087
  /** Pre-compiled wildcard patterns — rebuilt on reload for O(1) lookup. */
29442
30088
  wildcardEntries = [];
29443
30089
  /**
29444
- * Evaluate-result cache. Keyed by `tool.name::subject` so repeated calls
29445
- * with the same tool+input skip namespace matching, subject computation,
29446
- * and pattern matching (matchAny).
30090
+ * Evaluate-result cache. Keyed by `tool.name::subject::permissionFingerprint`
30091
+ * so repeated calls with the same tool+input skip namespace matching,
30092
+ * subject computation, and pattern matching (matchAny). The fingerprint
30093
+ * suffix covers the `Tool` fields `evaluate()` reads (`permission`,
30094
+ * `riskTier`, `mutating`, `capabilities`), so a tool redefined via
30095
+ * `ToolRegistry.wrap()` misses the cache instead of inheriting a stale
30096
+ * verdict.
29447
30097
  *
29448
30098
  * Cleared on any state change (reload, trust, deny, yolo toggle) because
29449
30099
  * the result depends on the full policy state. The write-tool smart-bypass
@@ -29459,11 +30109,46 @@ var DefaultPermissionPolicy = class {
29459
30109
  _evalCache = new LruCache(500);
29460
30110
  policyDiagnostics = [];
29461
30111
  policyInvalid = false;
30112
+ /**
30113
+ * When true, YOLO also auto-approves clearly destructive shell commands.
30114
+ *
30115
+ * WS-008: `isClearlyDestructiveBashCommand` was exported and heavily tested
30116
+ * but called from zero production modules, so YOLO returned `auto` for
30117
+ * everything — including `rm -rf`, git history rewrites, and external
30118
+ * publishes. The `'yolo_destructive'` decision source, the getter/setter on
30119
+ * PermissionPolicy, and the TUI branch that reads it all existed; only the
30120
+ * gate itself was missing. Defaults to false: destructive commands still ask,
30121
+ * even in YOLO.
30122
+ */
30123
+ yoloDestructive;
29462
30124
  constructor(opts) {
29463
30125
  this.trustFile = opts.trustFile;
29464
30126
  this.yolo = opts.yolo ?? false;
30127
+ this.yoloDestructive = opts.yoloDestructive ?? false;
29465
30128
  this.promptDelegate = opts.promptDelegate;
29466
30129
  }
30130
+ /** Allow YOLO to cover destructive shell commands too (off by default). */
30131
+ setYoloDestructive(enabled) {
30132
+ if (this.yoloDestructive !== enabled) this._evalCache.clear();
30133
+ this.yoloDestructive = enabled;
30134
+ }
30135
+ /** Whether YOLO currently extends to destructive shell commands. */
30136
+ getYoloDestructive() {
30137
+ return this.yoloDestructive;
30138
+ }
30139
+ /**
30140
+ * True when YOLO is on but this specific call is a destructive shell command
30141
+ * the user has not opted to auto-approve. Shared by `evaluate()` and the
30142
+ * explain/trace path so the two cannot drift.
30143
+ */
30144
+ yoloBlockedAsDestructive(tool, input, ctx) {
30145
+ if (!this.yolo || this.yoloDestructive) return false;
30146
+ const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
30147
+ if (!isShellSurface) return false;
30148
+ const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
30149
+ if (!command) return false;
30150
+ return isClearlyDestructiveBashCommand(command, ctx.projectRoot);
30151
+ }
29467
30152
  /**
29468
30153
  * Replace (or clear) the interactive prompt delegate at runtime.
29469
30154
  * Used by the CLI to switch from inline prompts (REPL) to event-driven
@@ -29537,15 +30222,17 @@ var DefaultPermissionPolicy = class {
29537
30222
  this.loaded = true;
29538
30223
  }
29539
30224
  _logDeny(tool, subject, reason) {
29540
- console.warn(JSON.stringify({
29541
- level: "warn",
29542
- event: "permission.denied",
29543
- message: `Permission denied: ${tool}${subject ? ` (subject: ${subject})` : ""} \u2014 ${reason}`,
29544
- tool,
29545
- subject,
29546
- reason,
29547
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
29548
- }));
30225
+ console.warn(
30226
+ JSON.stringify({
30227
+ level: "warn",
30228
+ event: "permission.denied",
30229
+ message: `Permission denied: ${tool}${subject ? ` (subject: ${subject})` : ""} \u2014 ${reason}`,
30230
+ tool,
30231
+ subject,
30232
+ reason,
30233
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
30234
+ })
30235
+ );
29549
30236
  }
29550
30237
  async evaluate(tool, input, ctx) {
29551
30238
  if (!this.loaded) await this.reload();
@@ -29561,14 +30248,19 @@ var DefaultPermissionPolicy = class {
29561
30248
  const entry = this.policy[tool.name] ?? namespaceEntry;
29562
30249
  const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
29563
30250
  const cacheKey = `${tool.name}::${subject ?? tool.name}`;
30251
+ const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
29564
30252
  if (tool.name !== "write") {
29565
- const cached = this._evalCache.get(cacheKey);
30253
+ const cached = this._evalCache.get(evalKey);
29566
30254
  if (cached !== void 0) return cached;
29567
30255
  }
29568
30256
  if (this.sessionDenied.has(cacheKey)) {
29569
30257
  this._logDeny(tool.name, subject, "session soft deny (user pressed no)");
29570
- const decision = { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" };
29571
- this._evalCache.set(cacheKey, decision);
30258
+ const decision = {
30259
+ permission: "deny",
30260
+ source: "deny",
30261
+ reason: "session soft deny (user pressed no)"
30262
+ };
30263
+ this._evalCache.set(evalKey, decision);
29572
30264
  return decision;
29573
30265
  }
29574
30266
  if (this.sessionAllowed.has(cacheKey)) {
@@ -29582,31 +30274,51 @@ var DefaultPermissionPolicy = class {
29582
30274
  }
29583
30275
  if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
29584
30276
  this._logDeny(tool.name, subject, "matched deny pattern");
29585
- const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
29586
- this._evalCache.set(cacheKey, decision);
30277
+ const decision = {
30278
+ permission: "deny",
30279
+ source: "deny",
30280
+ reason: "matched deny pattern"
30281
+ };
30282
+ this._evalCache.set(evalKey, decision);
29587
30283
  return decision;
29588
30284
  }
29589
30285
  if (tool.permission === "deny") {
29590
30286
  this._logDeny(tool.name, subject, "tool default deny");
29591
- const decision = { permission: "deny", source: "default", reason: "tool default deny" };
29592
- this._evalCache.set(cacheKey, decision);
30287
+ const decision = {
30288
+ permission: "deny",
30289
+ source: "default",
30290
+ reason: "tool default deny"
30291
+ };
30292
+ this._evalCache.set(evalKey, decision);
29593
30293
  return decision;
29594
30294
  }
29595
- if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
29596
- const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
29597
- this._evalCache.set(cacheKey, decision);
30295
+ const allowMatches = hasShellSubject(tool) ? matchesCommandTrust : matchesTrust;
30296
+ if (entry?.allow && subject && allowMatches(entry.allow, subject)) {
30297
+ const decision = {
30298
+ permission: "auto",
30299
+ source: "trust",
30300
+ reason: "matched allow pattern"
30301
+ };
30302
+ this._evalCache.set(evalKey, decision);
29598
30303
  return decision;
29599
30304
  }
29600
30305
  if (entry?.auto) {
29601
30306
  const decision = { permission: "auto", source: "trust" };
29602
- this._evalCache.set(cacheKey, decision);
30307
+ this._evalCache.set(evalKey, decision);
29603
30308
  return decision;
29604
30309
  }
29605
30310
  if (!this.yolo && this.isSensitiveReadCall(tool, input)) {
29606
30311
  if (this.promptDelegate) {
29607
30312
  const userDecision = await this.promptDelegate(tool, input, subject ?? tool.name);
29608
30313
  if (userDecision === "always") {
29609
- await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
30314
+ if (subject === void 0) {
30315
+ return {
30316
+ permission: "auto",
30317
+ source: "user",
30318
+ reason: `approved once \u2014 ${alwaysAllowUnavailableReason(tool, input)}`
30319
+ };
30320
+ }
30321
+ await this.trust({ tool: tool.name, pattern: subject });
29610
30322
  return {
29611
30323
  permission: "auto",
29612
30324
  source: "user",
@@ -29632,8 +30344,16 @@ var DefaultPermissionPolicy = class {
29632
30344
  };
29633
30345
  }
29634
30346
  if (this.yolo) {
30347
+ if (this.yoloBlockedAsDestructive(tool, input, ctx)) {
30348
+ return {
30349
+ permission: "confirm",
30350
+ source: "yolo_destructive",
30351
+ riskTier: "destructive",
30352
+ reason: "destructive command needs explicit approval even in YOLO mode"
30353
+ };
30354
+ }
29635
30355
  const decision = { permission: "auto", source: "yolo" };
29636
- this._evalCache.set(cacheKey, decision);
30356
+ this._evalCache.set(evalKey, decision);
29637
30357
  return decision;
29638
30358
  }
29639
30359
  if (tool.name === "write" && subject) {
@@ -29657,13 +30377,20 @@ var DefaultPermissionPolicy = class {
29657
30377
  const isMutating = tool.mutating || hasWriteCap || hasShellCap || hasInstallCap || hasConfigCap || hasSubagentCap;
29658
30378
  if (tool.permission === "auto" && !isMutating) {
29659
30379
  const decision = { permission: "auto", source: "default" };
29660
- this._evalCache.set(cacheKey, decision);
30380
+ this._evalCache.set(evalKey, decision);
29661
30381
  return decision;
29662
30382
  }
29663
30383
  if (this.promptDelegate) {
29664
30384
  const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
29665
30385
  if (decision === "always") {
29666
- await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
30386
+ if (subject === void 0) {
30387
+ return {
30388
+ permission: "auto",
30389
+ source: "user",
30390
+ reason: `approved once \u2014 ${alwaysAllowUnavailableReason(tool, input)}`
30391
+ };
30392
+ }
30393
+ await this.trust({ tool: tool.name, pattern: subject });
29667
30394
  return { permission: "auto", source: "user", reason: "user always-allowed" };
29668
30395
  }
29669
30396
  if (decision === "deny") {
@@ -29756,7 +30483,13 @@ var DefaultPermissionPolicy = class {
29756
30483
  if (this.policyInvalid) {
29757
30484
  add("policy invalid", true, "deny", "deny", "trust policy is invalid; all tools are denied");
29758
30485
  winnerIndex = 0;
29759
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "trust policy is invalid" } };
30486
+ return {
30487
+ toolName: tool.name,
30488
+ subject,
30489
+ steps,
30490
+ winnerIndex,
30491
+ decision: { permission: "deny", source: "deny", reason: "trust policy is invalid" }
30492
+ };
29760
30493
  }
29761
30494
  add("policy valid", false, "auto", "default", "trust policy loaded successfully");
29762
30495
  const namespaceEntry = this.findNamespaceEntry(tool.name);
@@ -29764,39 +30497,143 @@ var DefaultPermissionPolicy = class {
29764
30497
  const namespaceSource = namespaceEntry ? `wildcard entry matched (${Object.keys(this.policy).find((k) => k.includes("*") && matchGlob(k, tool.name))})` : "no namespace match";
29765
30498
  const cacheKey = `${tool.name}::${subject ?? tool.name}`;
29766
30499
  if (this.sessionDenied.has(cacheKey)) {
29767
- add("session soft deny", true, "deny", "deny", 'user pressed "no" earlier in this session \u2014 blocked until reload');
30500
+ add(
30501
+ "session soft deny",
30502
+ true,
30503
+ "deny",
30504
+ "deny",
30505
+ 'user pressed "no" earlier in this session \u2014 blocked until reload'
30506
+ );
29768
30507
  winnerIndex = steps.length - 1;
29769
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" } };
30508
+ return {
30509
+ toolName: tool.name,
30510
+ subject,
30511
+ steps,
30512
+ winnerIndex,
30513
+ decision: {
30514
+ permission: "deny",
30515
+ source: "deny",
30516
+ reason: "session soft deny (user pressed no)"
30517
+ }
30518
+ };
29770
30519
  }
29771
- add("session soft deny", false, "deny", "deny", "no session-level soft deny for this tool+subject");
30520
+ add(
30521
+ "session soft deny",
30522
+ false,
30523
+ "deny",
30524
+ "deny",
30525
+ "no session-level soft deny for this tool+subject"
30526
+ );
29772
30527
  if (this.sessionAllowed.has(cacheKey)) {
29773
- add("session soft allow", true, "auto", "trust", 'user pressed "yes" in current session \u2014 one-shot auto-approve (consumed)');
30528
+ add(
30529
+ "session soft allow",
30530
+ true,
30531
+ "auto",
30532
+ "trust",
30533
+ 'user pressed "yes" in current session \u2014 one-shot auto-approve (consumed)'
30534
+ );
29774
30535
  winnerIndex = steps.length - 1;
29775
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "session one-shot allow (user pressed yes)" } };
30536
+ return {
30537
+ toolName: tool.name,
30538
+ subject,
30539
+ steps,
30540
+ winnerIndex,
30541
+ decision: {
30542
+ permission: "auto",
30543
+ source: "trust",
30544
+ reason: "session one-shot allow (user pressed yes)"
30545
+ }
30546
+ };
29776
30547
  }
29777
- add("session soft allow", false, "auto", "trust", "no session-level one-shot allow for this tool+subject");
30548
+ add(
30549
+ "session soft allow",
30550
+ false,
30551
+ "auto",
30552
+ "trust",
30553
+ "no session-level one-shot allow for this tool+subject"
30554
+ );
29778
30555
  if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
29779
- add("trust deny", true, "deny", "deny", `subject "${subject}" matched a deny pattern in trust file`);
30556
+ add(
30557
+ "trust deny",
30558
+ true,
30559
+ "deny",
30560
+ "deny",
30561
+ `subject "${subject}" matched a deny pattern in trust file`
30562
+ );
29780
30563
  winnerIndex = steps.length - 1;
29781
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "matched deny pattern" } };
30564
+ return {
30565
+ toolName: tool.name,
30566
+ subject,
30567
+ steps,
30568
+ winnerIndex,
30569
+ decision: { permission: "deny", source: "deny", reason: "matched deny pattern" }
30570
+ };
29782
30571
  }
29783
- add("trust deny", false, "deny", "deny", `no deny pattern matched (namespace: ${namespaceSource})`);
30572
+ add(
30573
+ "trust deny",
30574
+ false,
30575
+ "deny",
30576
+ "deny",
30577
+ `no deny pattern matched (namespace: ${namespaceSource})`
30578
+ );
29784
30579
  if (tool.permission === "deny") {
29785
- add("tool default deny", true, "deny", "default", `tool "${tool.name}" has permission: deny by default`);
30580
+ add(
30581
+ "tool default deny",
30582
+ true,
30583
+ "deny",
30584
+ "default",
30585
+ `tool "${tool.name}" has permission: deny by default`
30586
+ );
29786
30587
  winnerIndex = steps.length - 1;
29787
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "default", reason: "tool default deny" } };
30588
+ return {
30589
+ toolName: tool.name,
30590
+ subject,
30591
+ steps,
30592
+ winnerIndex,
30593
+ decision: { permission: "deny", source: "default", reason: "tool default deny" }
30594
+ };
29788
30595
  }
29789
- add("tool default deny", false, "deny", "default", `tool "${tool.name}" has permission: "${tool.permission}"`);
30596
+ add(
30597
+ "tool default deny",
30598
+ false,
30599
+ "deny",
30600
+ "default",
30601
+ `tool "${tool.name}" has permission: "${tool.permission}"`
30602
+ );
29790
30603
  if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
29791
- add("trust allow", true, "auto", "trust", `subject "${subject}" matched an allow pattern in trust file`);
30604
+ add(
30605
+ "trust allow",
30606
+ true,
30607
+ "auto",
30608
+ "trust",
30609
+ `subject "${subject}" matched an allow pattern in trust file`
30610
+ );
29792
30611
  winnerIndex = steps.length - 1;
29793
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "matched allow pattern" } };
30612
+ return {
30613
+ toolName: tool.name,
30614
+ subject,
30615
+ steps,
30616
+ winnerIndex,
30617
+ decision: { permission: "auto", source: "trust", reason: "matched allow pattern" }
30618
+ };
29794
30619
  }
29795
- add("trust allow", false, "auto", "trust", `no allow pattern matched (namespace: ${namespaceSource})`);
30620
+ add(
30621
+ "trust allow",
30622
+ false,
30623
+ "auto",
30624
+ "trust",
30625
+ `no allow pattern matched (namespace: ${namespaceSource})`
30626
+ );
29796
30627
  if (entry?.auto) {
29797
30628
  add("trust auto", true, "auto", "trust", `trust file has auto: true for "${tool.name}"`);
29798
30629
  winnerIndex = steps.length - 1;
29799
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "trust auto" } };
30630
+ return {
30631
+ toolName: tool.name,
30632
+ subject,
30633
+ steps,
30634
+ winnerIndex,
30635
+ decision: { permission: "auto", source: "trust", reason: "trust auto" }
30636
+ };
29800
30637
  }
29801
30638
  add("trust auto", false, "auto", "trust", `no auto flag for "${tool.name}" in trust file`);
29802
30639
  if (!this.yolo && this.isSensitiveReadCall(tool, input)) {
@@ -29809,13 +30646,64 @@ var DefaultPermissionPolicy = class {
29809
30646
  hasDelegate2 ? "sensitive file read detected \u2014 would prompt user for approval" : "sensitive file read detected \u2014 returns confirm (no prompt delegate)"
29810
30647
  );
29811
30648
  winnerIndex = steps.length - 1;
29812
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "confirm", source: "default", riskTier: "standard", reason: "sensitive file read needs explicit approval" } };
30649
+ return {
30650
+ toolName: tool.name,
30651
+ subject,
30652
+ steps,
30653
+ winnerIndex,
30654
+ decision: {
30655
+ permission: "confirm",
30656
+ source: "default",
30657
+ riskTier: "standard",
30658
+ reason: "sensitive file read needs explicit approval"
30659
+ }
30660
+ };
29813
30661
  }
29814
- add("sensitive read", false, "confirm", "default", "not a sensitive read call (or YOLO bypasses this check)");
30662
+ add(
30663
+ "sensitive read",
30664
+ false,
30665
+ "confirm",
30666
+ "default",
30667
+ "not a sensitive read call (or YOLO bypasses this check)"
30668
+ );
29815
30669
  if (this.yolo) {
29816
- add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
30670
+ if (this.yoloBlockedAsDestructive(tool, input, ctx)) {
30671
+ add(
30672
+ "yolo destructive gate",
30673
+ true,
30674
+ "confirm",
30675
+ "yolo_destructive",
30676
+ "YOLO is active but this is a clearly destructive command \u2014 still asking"
30677
+ );
30678
+ winnerIndex = steps.length - 1;
30679
+ return {
30680
+ toolName: tool.name,
30681
+ subject,
30682
+ steps,
30683
+ winnerIndex,
30684
+ decision: {
30685
+ permission: "confirm",
30686
+ source: "yolo_destructive",
30687
+ riskTier: "destructive",
30688
+ reason: "destructive command needs explicit approval even in YOLO mode"
30689
+ }
30690
+ };
30691
+ }
30692
+ add(
30693
+ "yolo",
30694
+ true,
30695
+ "auto",
30696
+ "yolo",
30697
+ "YOLO mode is active \u2014 auto-approving every non-denied call"
30698
+ );
29817
30699
  winnerIndex = steps.length - 1;
29818
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "yolo" } };
30700
+ return {
30701
+ toolName: tool.name,
30702
+ subject,
30703
+ steps,
30704
+ winnerIndex,
30705
+ decision: { permission: "auto", source: "yolo" }
30706
+ };
29819
30707
  }
29820
30708
  add("yolo", false, "auto", "yolo", "YOLO mode is not active");
29821
30709
  if (tool.name === "write" && subject) {
@@ -29829,10 +30717,26 @@ var DefaultPermissionPolicy = class {
29829
30717
  );
29830
30718
  if (hasRead) {
29831
30719
  winnerIndex = steps.length - 1;
29832
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "context", reason: "file already read in this session" } };
30720
+ return {
30721
+ toolName: tool.name,
30722
+ subject,
30723
+ steps,
30724
+ winnerIndex,
30725
+ decision: {
30726
+ permission: "auto",
30727
+ source: "context",
30728
+ reason: "file already read in this session"
30729
+ }
30730
+ };
29833
30731
  }
29834
30732
  } else {
29835
- add("write smart bypass", false, "auto", "context", 'tool is not "write" or has no subject \u2014 bypass does not apply');
30733
+ add(
30734
+ "write smart bypass",
30735
+ false,
30736
+ "auto",
30737
+ "context",
30738
+ 'tool is not "write" or has no subject \u2014 bypass does not apply'
30739
+ );
29836
30740
  }
29837
30741
  const hasWriteCap = hasCapability(tool, ToolCapabilities.FS_WRITE);
29838
30742
  const hasShellCap = hasCapability(tool, [
@@ -29845,9 +30749,21 @@ var DefaultPermissionPolicy = class {
29845
30749
  const hasSubagentCap = hasCapability(tool, ToolCapabilities.SUBAGENT_SPAWN);
29846
30750
  const isMutating = tool.mutating || hasWriteCap || hasShellCap || hasInstallCap || hasConfigCap || hasSubagentCap;
29847
30751
  if (tool.permission === "auto" && !isMutating) {
29848
- add("safe default auto", true, "auto", "default", `tool "${tool.name}" has auto permission and is not mutating \u2014 safe to auto-approve`);
30752
+ add(
30753
+ "safe default auto",
30754
+ true,
30755
+ "auto",
30756
+ "default",
30757
+ `tool "${tool.name}" has auto permission and is not mutating \u2014 safe to auto-approve`
30758
+ );
29849
30759
  winnerIndex = steps.length - 1;
29850
- return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "default" } };
30760
+ return {
30761
+ toolName: tool.name,
30762
+ subject,
30763
+ steps,
30764
+ winnerIndex,
30765
+ decision: { permission: "auto", source: "default" }
30766
+ };
29851
30767
  }
29852
30768
  add(
29853
30769
  "mutating default confirm",
@@ -29941,7 +30857,7 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
29941
30857
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, scryptSync } from "node:crypto";
29942
30858
  import * as fs13 from "node:fs";
29943
30859
  import * as fsp22 from "node:fs/promises";
29944
- import * as path34 from "node:path";
30860
+ import * as path35 from "node:path";
29945
30861
 
29946
30862
  // src/types/secret-vault.ts
29947
30863
  var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
@@ -30084,8 +31000,12 @@ function checkKeyFilePermissions(keyFile, opts) {
30084
31000
  const stat15 = fs13.statSync(keyFile);
30085
31001
  const actualMode = stat15.mode & 511;
30086
31002
  if (actualMode !== KEY_FILE_MODE) {
31003
+ void restrictFilePermissions(keyFile, {
31004
+ label: "secret-vault",
31005
+ warn
31006
+ }).catch(() => void 0);
30087
31007
  warn(
30088
- `Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`
31008
+ `Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Hardening\u2026`
30089
31009
  );
30090
31010
  }
30091
31011
  } catch {
@@ -30118,6 +31038,14 @@ var DefaultSecretVault = class {
30118
31038
  logger;
30119
31039
  key;
30120
31040
  _keyVersion = 1;
31041
+ /**
31042
+ * Pending key-file hardening promises (Windows ACL restrictions via icacls).
31043
+ * Tracked so async callers can `flushHardening()` before returning, ensuring
31044
+ * a short-lived CLI process does not exit before the hardening resolves.
31045
+ * Sync-only callers (encrypt/decrypt lazy-load) leave these pending —
31046
+ * hardening remains best-effort in that case.
31047
+ */
31048
+ pendingHardening = [];
30121
31049
  constructor(opts) {
30122
31050
  this.keyFile = opts.keyFile;
30123
31051
  this.logger = opts.logger;
@@ -30134,6 +31062,26 @@ var DefaultSecretVault = class {
30134
31062
  console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
30135
31063
  }
30136
31064
  }
31065
+ /**
31066
+ * Schedule best-effort key-file hardening and track the promise so async
31067
+ * callers can await it via `flushHardening()` before returning. Rejections
31068
+ * are swallowed here (the helper already warns on failure) so a rejection
31069
+ * can never become an unhandled one.
31070
+ */
31071
+ scheduleKeyHardening() {
31072
+ const p = restrictFilePermissions(this.keyFile, {
31073
+ label: "secret-vault-key",
31074
+ warn: (msg) => this.logWarn(msg)
31075
+ }).catch(() => void 0);
31076
+ this.pendingHardening.push(p);
31077
+ }
31078
+ /** Flush all pending key-file hardening promises. */
31079
+ flushHardening() {
31080
+ if (this.pendingHardening.length === 0) return Promise.resolve();
31081
+ const all = Promise.all(this.pendingHardening).then(() => void 0);
31082
+ this.pendingHardening = [];
31083
+ return all;
31084
+ }
30137
31085
  /** Current key version. Starts at 1; incremented by rotateKey(). */
30138
31086
  get keyVersion() {
30139
31087
  if (!this.key) this.loadOrCreateKey();
@@ -30175,16 +31123,18 @@ var DefaultSecretVault = class {
30175
31123
  const iv = Buffer.from(ivB64, "base64");
30176
31124
  const tag = Buffer.from(tagB64, "base64");
30177
31125
  const ct = Buffer.from(ctB64, "base64");
30178
- if (iv.length !== IV_BYTES) throw new ConfigError({
30179
- message: "SecretVault: bad IV length",
30180
- code: ERROR_CODES.CONFIG_PARSE_FAILED,
30181
- context: { expected: IV_BYTES, actual: iv.length }
30182
- });
30183
- if (tag.length !== TAG_BYTES) throw new ConfigError({
30184
- message: "SecretVault: bad tag length",
30185
- code: ERROR_CODES.CONFIG_PARSE_FAILED,
30186
- context: { expected: TAG_BYTES, actual: tag.length }
30187
- });
31126
+ if (iv.length !== IV_BYTES)
31127
+ throw new ConfigError({
31128
+ message: "SecretVault: bad IV length",
31129
+ code: ERROR_CODES.CONFIG_PARSE_FAILED,
31130
+ context: { expected: IV_BYTES, actual: iv.length }
31131
+ });
31132
+ if (tag.length !== TAG_BYTES)
31133
+ throw new ConfigError({
31134
+ message: "SecretVault: bad tag length",
31135
+ code: ERROR_CODES.CONFIG_PARSE_FAILED,
31136
+ context: { expected: TAG_BYTES, actual: tag.length }
31137
+ });
30188
31138
  const key = this.loadOrCreateKey();
30189
31139
  const decipher = createDecipheriv(ALGO, key, iv);
30190
31140
  decipher.setAuthTag(tag);
@@ -30200,7 +31150,7 @@ var DefaultSecretVault = class {
30200
31150
  const oldVersion = this._keyVersion;
30201
31151
  const newKey = randomBytes3(KEY_BYTES);
30202
31152
  const newVersion = oldVersion + 1;
30203
- fs13.mkdirSync(path34.dirname(this.keyFile), { recursive: true });
31153
+ fs13.mkdirSync(path35.dirname(this.keyFile), { recursive: true });
30204
31154
  const passphrase = getVaultPassphrase();
30205
31155
  if (passphrase) {
30206
31156
  writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
@@ -30211,6 +31161,7 @@ var DefaultSecretVault = class {
30211
31161
  newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
30212
31162
  writeKeyFileAtomicSync(this.keyFile, keyFileBuf);
30213
31163
  }
31164
+ this.scheduleKeyHardening();
30214
31165
  checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
30215
31166
  this.key = newKey;
30216
31167
  this._keyVersion = newVersion;
@@ -30227,7 +31178,11 @@ var DefaultSecretVault = class {
30227
31178
  const passphrase = getVaultPassphrase();
30228
31179
  if (!passphrase || !this.key) return;
30229
31180
  try {
30230
- writeKeyFileAtomicSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase));
31181
+ writeKeyFileAtomicSync(
31182
+ this.keyFile,
31183
+ wrapDataKey(this.key, this._keyVersion, passphrase)
31184
+ );
31185
+ this.scheduleKeyHardening();
30231
31186
  checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
30232
31187
  } catch {
30233
31188
  }
@@ -30282,14 +31237,16 @@ var DefaultSecretVault = class {
30282
31237
  } catch (err) {
30283
31238
  if (err.code !== "ENOENT") throw err;
30284
31239
  }
30285
- fs13.mkdirSync(path34.dirname(this.keyFile), { recursive: true });
31240
+ fs13.mkdirSync(path35.dirname(this.keyFile), { recursive: true });
30286
31241
  const key = randomBytes3(KEY_BYTES);
30287
31242
  const passphrase = getVaultPassphrase();
30288
31243
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
30289
31244
  try {
30290
31245
  fs13.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
31246
+ this.scheduleKeyHardening();
30291
31247
  } catch (err) {
30292
31248
  if (err.code !== "EEXIST") throw err;
31249
+ this.scheduleKeyHardening();
30293
31250
  const buf = fs13.readFileSync(this.keyFile);
30294
31251
  if (isWrappedKeyFile(buf)) {
30295
31252
  const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
@@ -30340,9 +31297,10 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
30340
31297
  }
30341
31298
  const merged = deepMerge(current, patch ?? {});
30342
31299
  const encrypted = encryptConfigSecrets(merged, vault);
30343
- await fsp22.mkdir(path34.dirname(configPath), { recursive: true });
31300
+ await fsp22.mkdir(path35.dirname(configPath), { recursive: true });
30344
31301
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
30345
- await restrictFilePermissions(configPath);
31302
+ await restrictFilePermissions2(configPath);
31303
+ await vault.flushHardening?.();
30346
31304
  }
30347
31305
  async function migratePlaintextSecrets(configPath, vault, logger) {
30348
31306
  let raw;
@@ -30361,45 +31319,15 @@ async function migratePlaintextSecrets(configPath, vault, logger) {
30361
31319
  const migrated = walkCount(parsed, vault, counter);
30362
31320
  if (counter.n === 0) return { migrated: 0, file: configPath };
30363
31321
  await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
30364
- await restrictFilePermissions(
31322
+ await restrictFilePermissions2(
30365
31323
  configPath,
30366
31324
  logger ? { warn: (msg) => logger.warn(msg) } : void 0
30367
31325
  );
31326
+ await vault.flushHardening?.();
30368
31327
  return { migrated: counter.n, file: configPath };
30369
31328
  }
30370
- async function restrictFilePermissions(filePath, opts) {
30371
- const warn = opts?.warn ?? ((msg) => console.warn(msg));
30372
- if (process.platform === "win32") {
30373
- try {
30374
- const { execFile: execFile2 } = await import("node:child_process");
30375
- const { promisify: promisify2 } = await import("node:util");
30376
- const execFileAsync = promisify2(execFile2);
30377
- const user = windowsAccountName();
30378
- if (!user) {
30379
- warn(
30380
- `[secret-vault] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
30381
- );
30382
- return;
30383
- }
30384
- await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`]);
30385
- } catch {
30386
- warn(
30387
- `[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`
30388
- );
30389
- }
30390
- } else {
30391
- try {
30392
- await fsp22.chmod(filePath, 384);
30393
- } catch {
30394
- }
30395
- }
30396
- }
30397
- function windowsAccountName() {
30398
- const username = process.env.USERNAME || process.env.USER;
30399
- if (!username || username.includes("\0")) return void 0;
30400
- const domain = process.env.USERDOMAIN;
30401
- if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
30402
- return username;
31329
+ async function restrictFilePermissions2(filePath, opts) {
31330
+ await restrictFilePermissions(filePath, { label: "secret-vault", warn: opts?.warn });
30403
31331
  }
30404
31332
  function walkCount(node, vault, counter) {
30405
31333
  if (node === null || node === void 0) return node;
@@ -30424,7 +31352,7 @@ function walkCount(node, vault, counter) {
30424
31352
  // src/storage/attachment-store.ts
30425
31353
  import { randomBytes as randomBytes4 } from "node:crypto";
30426
31354
  import * as fsp23 from "node:fs/promises";
30427
- import * as path35 from "node:path";
31355
+ import * as path36 from "node:path";
30428
31356
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
30429
31357
  var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
30430
31358
  var DefaultAttachmentStore = class {
@@ -30445,7 +31373,7 @@ var DefaultAttachmentStore = class {
30445
31373
  let data = input.data;
30446
31374
  if (this.spoolDir && bytes >= this.spoolThreshold) {
30447
31375
  await fsp23.mkdir(this.spoolDir, { recursive: true });
30448
- spooledPath = path35.join(this.spoolDir, `${id}.bin`);
31376
+ spooledPath = path36.join(this.spoolDir, `${id}.bin`);
30449
31377
  await atomicWrite(spooledPath, input.data, {
30450
31378
  encoding: input.kind === "image" ? "base64" : "utf8"
30451
31379
  });
@@ -30614,7 +31542,13 @@ var CONFIG_BEHAVIOR_DEFAULTS = {
30614
31542
  // models still get cost savings without capability loss. Explicit tiers
30615
31543
  // are respected verbatim.
30616
31544
  tokenSavingMode: "auto",
30617
- allowOutsideProjectRoot: true
31545
+ // Derived, never written by hand: this and `tools.restrictToProjectRoot`
31546
+ // (line 41) are the two halves of one switch, and they used to be declared
31547
+ // independently in this same object — one `true`, one `true` — with
31548
+ // session.ts resolving the contradiction via `??` in favour of the
31549
+ // permissive half. The confinement guard therefore shipped disabled on a
31550
+ // fresh install even though the other half asked for it (WS-075).
31551
+ allowOutsideProjectRoot: !DEFAULT_TOOLS_CONFIG.restrictToProjectRoot
30618
31552
  },
30619
31553
  Sage: {
30620
31554
  enabled: true,
@@ -30693,7 +31627,9 @@ var CONFIG_BEHAVIOR_DEFAULTS = {
30693
31627
  enhance: true,
30694
31628
  enhanceDelayMs: 6e4,
30695
31629
  enhanceLanguage: "original",
30696
- statuslineMode: "detailed",
31630
+ // Product-wide statusline density default. Mirrored by the TUI's
31631
+ // DEFAULT_STATUSLINE_MODE (packages/tui/src/components/settings-picker-model.ts).
31632
+ statuslineMode: "minimum",
30697
31633
  thinkingWord: DEFAULT_TUI_THINKING_WORD,
30698
31634
  showAgentSwarmPanel: true
30699
31635
  },
@@ -30822,6 +31758,7 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
30822
31758
  "maxConcurrent",
30823
31759
  "uiLocale",
30824
31760
  "fallbackModels",
31761
+ "fallbackBridge",
30825
31762
  "fallbackProfiles",
30826
31763
  "favoriteModels",
30827
31764
  "favoriteModelsOnly",
@@ -30884,6 +31821,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
30884
31821
  "tools",
30885
31822
  "mcpServers",
30886
31823
  "fallbackModels",
31824
+ "fallbackBridge",
30887
31825
  "fallbackProfiles",
30888
31826
  "favoriteModels",
30889
31827
  "favoriteModelsOnly",
@@ -30918,6 +31856,75 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
30918
31856
  "brain",
30919
31857
  "git"
30920
31858
  ]);
31859
+ var IN_PROJECT_DENIED_PATHS = [
31860
+ {
31861
+ path: "tools.exec.allow",
31862
+ reason: "Extends the exec allow-list; a repo could authorise its own binaries."
31863
+ },
31864
+ { path: "tools.exec.danger", reason: "Weakens the destructive-command banner." },
31865
+ { path: "skills.extraDirs", reason: "Loads skill definitions from repo-chosen directories." },
31866
+ { path: "skills.registryUrl", reason: "Redirects skill installs to a repo-chosen host." },
31867
+ // Deliberately NOT denied: skills.mode and skills.eagerMaxChars. The audit
31868
+ // suggested stripping both, but config-loader-extra.test.ts classifies `mode`
31869
+ // as a safe preference with a stated rationale, and a repo can only use these
31870
+ // to move injection volume in either direction — the trust problem was that
31871
+ // repo-supplied skill bodies were indistinguishable from the user's own, which
31872
+ // the provenance tag in system-prompt-skill-bodies.ts now fixes. Re-classifying
31873
+ // them is a product call.
31874
+ { path: "Sage.storage.directory", reason: "Redirects memory storage outside the profile." },
31875
+ {
31876
+ path: "autonomy.yolo",
31877
+ reason: "Alias for the denied top-level `yolo`, and it wins over the user setting \u2014 a repo-committed config could disable every permission prompt."
31878
+ },
31879
+ {
31880
+ path: "autonomy.defaultMode",
31881
+ reason: "Sets the startup autonomy mode; autonomy is user-owned, never repo-owned."
31882
+ },
31883
+ // Deliberately NOT denied: autonomy.autoProceedDelayMs and
31884
+ // autoProceedMaxIterations. They tune a mode the user has already switched on
31885
+ // rather than granting it, and config-loader-extra.test.ts classifies the
31886
+ // delay as a benign project preference. Re-classifying them is a product call.
31887
+ {
31888
+ path: "launch.autonomy",
31889
+ reason: "Launch-time autonomy mode; same user-owned boundary as autonomy.defaultMode."
31890
+ },
31891
+ {
31892
+ path: "features.allowOutsideProjectRoot",
31893
+ reason: "Short-circuits both the project-root containment check and the symlink realpath check in the file tools."
31894
+ },
31895
+ {
31896
+ path: "tools.restrictToProjectRoot",
31897
+ reason: "The other half of the filesystem confinement switch."
31898
+ }
31899
+ ];
31900
+ function deleteNestedPath(target, path40) {
31901
+ const segments = path40.split(".");
31902
+ const last = segments[segments.length - 1];
31903
+ if (last === void 0) return false;
31904
+ let cursor = target;
31905
+ const chain = [];
31906
+ for (const segment of segments.slice(0, -1)) {
31907
+ const next = cursor[segment];
31908
+ if (!next || typeof next !== "object" || Array.isArray(next)) return false;
31909
+ chain.push({ parent: cursor, key: segment });
31910
+ cursor = next;
31911
+ }
31912
+ if (!(last in cursor)) return false;
31913
+ const clonedLeaf = { ...cursor };
31914
+ delete clonedLeaf[last];
31915
+ let replacement = clonedLeaf;
31916
+ for (let i = chain.length - 1; i >= 0; i--) {
31917
+ const link2 = chain[i];
31918
+ if (!link2) continue;
31919
+ replacement = { ...link2.parent, [link2.key]: replacement };
31920
+ if (i === 0) {
31921
+ for (const [k, v] of Object.entries(replacement)) target[k] = v;
31922
+ return true;
31923
+ }
31924
+ }
31925
+ for (const [k, v] of Object.entries(replacement)) target[k] = v;
31926
+ return true;
31927
+ }
30921
31928
  function assertInProjectAllowListComplete() {
30922
31929
  const missingFromBoth = [];
30923
31930
  for (const key of KNOWN_CONFIG_TOP_LEVEL_KEYS) {
@@ -30947,6 +31954,22 @@ function assertInProjectAllowListComplete() {
30947
31954
  `field(s) appear in BOTH IN_PROJECT_ALLOWED_KEYS and KNOWN_DENIED_IN_PROJECT: ` + duplicate.join(", ") + ". The allow-list wins at runtime; remove from one of the two."
30948
31955
  );
30949
31956
  }
31957
+ const orphanedPaths = IN_PROJECT_DENIED_PATHS.filter(
31958
+ ({ path: path40 }) => !IN_PROJECT_ALLOWED_KEYS.has(path40.split(".")[0] ?? "")
31959
+ ).map(({ path: path40 }) => path40);
31960
+ if (orphanedPaths.length > 0) {
31961
+ problems.push(
31962
+ `IN_PROJECT_DENIED_PATHS entr(ies) whose top-level parent is not allowed: ` + orphanedPaths.join(", ") + ". The parent is already stripped wholesale, so the nested denial is dead \u2014 remove it."
31963
+ );
31964
+ }
31965
+ const malformedPaths = IN_PROJECT_DENIED_PATHS.filter(
31966
+ ({ path: path40 }) => path40.split(".").length < 2 || path40.split(".").some((s) => s.length === 0)
31967
+ ).map(({ path: path40 }) => path40);
31968
+ if (malformedPaths.length > 0) {
31969
+ problems.push(
31970
+ `IN_PROJECT_DENIED_PATHS entr(ies) are not dotted nested paths: ` + malformedPaths.join(", ") + ". Top-level keys belong in KNOWN_DENIED_IN_PROJECT instead."
31971
+ );
31972
+ }
30950
31973
  if (problems.length > 0) {
30951
31974
  throw new Error(
30952
31975
  `stripUnsafeInProjectFields drift check failed:
@@ -30969,58 +31992,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
30969
31992
  }
30970
31993
  stripped.push(k);
30971
31994
  }
30972
- const outTools = out["tools"];
30973
- if (outTools && typeof outTools === "object") {
30974
- const execCfg = outTools["exec"];
30975
- if (execCfg && typeof execCfg === "object") {
30976
- const hasAllow = "allow" in execCfg;
30977
- const hasDanger = "danger" in execCfg;
30978
- if (hasAllow || hasDanger) {
30979
- const clonedExec = { ...execCfg };
30980
- if (hasAllow) {
30981
- delete clonedExec["allow"];
30982
- stripped.push("tools.exec.allow");
30983
- }
30984
- if (hasDanger) {
30985
- delete clonedExec["danger"];
30986
- stripped.push("tools.exec.danger");
30987
- }
30988
- out["tools"] = {
30989
- ...outTools,
30990
- exec: clonedExec
30991
- };
30992
- }
30993
- }
30994
- }
30995
- const outSkills = out["skills"];
30996
- if (outSkills && typeof outSkills === "object") {
30997
- const skillsRec = outSkills;
30998
- const needsClone = "extraDirs" in skillsRec || "registryUrl" in skillsRec;
30999
- if (needsClone) {
31000
- const clonedSkills = { ...skillsRec };
31001
- if ("extraDirs" in clonedSkills) {
31002
- delete clonedSkills["extraDirs"];
31003
- stripped.push("skills.extraDirs");
31004
- }
31005
- if ("registryUrl" in clonedSkills) {
31006
- delete clonedSkills["registryUrl"];
31007
- stripped.push("skills.registryUrl");
31008
- }
31009
- out["skills"] = clonedSkills;
31010
- }
31011
- }
31012
- const outSage = out["Sage"];
31013
- if (outSage && typeof outSage === "object") {
31014
- const storage3 = outSage["storage"];
31015
- if (storage3 && typeof storage3 === "object" && "directory" in storage3) {
31016
- const clonedStorage = { ...storage3 };
31017
- delete clonedStorage["directory"];
31018
- out["Sage"] = {
31019
- ...outSage,
31020
- storage: clonedStorage
31021
- };
31022
- stripped.push("Sage.storage.directory");
31023
- }
31995
+ for (const { path: path40 } of IN_PROJECT_DENIED_PATHS) {
31996
+ if (deleteNestedPath(out, path40)) stripped.push(path40);
31024
31997
  }
31025
31998
  if (stripped.length > 0) {
31026
31999
  warn(
@@ -31190,10 +32163,10 @@ function removeLegacySageEngine(config) {
31190
32163
  }
31191
32164
 
31192
32165
  // src/storage/config-loader/path-identity.ts
31193
- import * as path36 from "node:path";
32166
+ import * as path37 from "node:path";
31194
32167
  function samePath(a, b) {
31195
- let ra = path36.resolve(a);
31196
- let rb = path36.resolve(b);
32168
+ let ra = path37.resolve(a);
32169
+ let rb = path37.resolve(b);
31197
32170
  if (process.platform === "win32" || process.platform === "darwin") {
31198
32171
  ra = ra.toLowerCase();
31199
32172
  rb = rb.toLowerCase();
@@ -32174,7 +33147,7 @@ ${cat}:`);
32174
33147
 
32175
33148
  // src/storage/queue-store.ts
32176
33149
  import * as fsp25 from "node:fs/promises";
32177
- import * as path37 from "node:path";
33150
+ import * as path38 from "node:path";
32178
33151
  var QUEUE_MAX_ITEMS = 100;
32179
33152
  var QUEUE_MAX_BYTES = 16 * 1024 * 1024;
32180
33153
  var QUEUE_MAX_ITEM_BYTES = 8 * 1024 * 1024;
@@ -32207,7 +33180,7 @@ var QueueStore = class {
32207
33180
  traceId;
32208
33181
  logger;
32209
33182
  constructor(opts) {
32210
- this.file = path37.join(opts.dir, "queue.json");
33183
+ this.file = path38.join(opts.dir, "queue.json");
32211
33184
  this.events = opts.events;
32212
33185
  this.traceId = opts.traceId;
32213
33186
  this.logger = opts.logger;
@@ -32423,7 +33396,7 @@ function isPersistedQueueItem(v) {
32423
33396
  // src/storage/recovery-lock.ts
32424
33397
  import * as fsp26 from "node:fs/promises";
32425
33398
  import * as os2 from "node:os";
32426
- import * as path38 from "node:path";
33399
+ import * as path39 from "node:path";
32427
33400
  var LOCK_FILE = "active.json";
32428
33401
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
32429
33402
  var RecoveryLock = class {
@@ -32434,7 +33407,7 @@ var RecoveryLock = class {
32434
33407
  sessionStore;
32435
33408
  probe;
32436
33409
  constructor(opts) {
32437
- this.file = path38.join(opts.dir, LOCK_FILE);
33410
+ this.file = path39.join(opts.dir, LOCK_FILE);
32438
33411
  this.pid = opts.pid ?? process.pid;
32439
33412
  this.hostname = opts.hostname ?? os2.hostname();
32440
33413
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -32508,7 +33481,7 @@ var RecoveryLock = class {
32508
33481
  * null return before calling this.
32509
33482
  */
32510
33483
  async write(sessionId) {
32511
- await ensureDir(path38.dirname(this.file));
33484
+ await ensureDir(path39.dirname(this.file));
32512
33485
  const lock = {
32513
33486
  v: 1,
32514
33487
  sessionId,
@@ -32756,6 +33729,9 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
32756
33729
  async append(event) {
32757
33730
  const target = resolveWriter();
32758
33731
  if (!target) return;
33732
+ if (event.type === "tool_call_end") {
33733
+ progressCounters.delete(event.id);
33734
+ }
32759
33735
  if (!isAllowed(event.type, currentLevel)) return;
32760
33736
  if (!shouldSample(event)) return;
32761
33737
  try {
@@ -32766,6 +33742,9 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
32766
33742
  async appendBatch(events) {
32767
33743
  const target = resolveWriter();
32768
33744
  if (!target || events.length === 0) return;
33745
+ for (const e of events) {
33746
+ if (e.type === "tool_call_end") progressCounters.delete(e.id);
33747
+ }
32769
33748
  const allowed = events.filter((e) => isAllowed(e.type, currentLevel) && shouldSample(e));
32770
33749
  if (allowed.length === 0) return;
32771
33750
  try {