@bastani/atomic 0.9.2 → 0.9.3-alpha.2

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 (606) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +2 -2
  3. package/dist/builtin/cursor/CHANGELOG.md +15 -0
  4. package/dist/builtin/cursor/README.md +2 -1
  5. package/dist/builtin/cursor/package.json +2 -2
  6. package/dist/builtin/cursor/src/cursor-models-raw.json +2 -9
  7. package/dist/builtin/cursor/src/model-mapper.ts +14 -3
  8. package/dist/builtin/cursor/src/proto/protobuf-codec-base64.ts +22 -0
  9. package/dist/builtin/cursor/src/proto/protobuf-codec-request.ts +53 -13
  10. package/dist/builtin/cursor/src/proto/protobuf-codec-wire.ts +24 -7
  11. package/dist/builtin/cursor/src/proto/protobuf-codec.ts +3 -2
  12. package/dist/builtin/cursor/src/stream.ts +5 -11
  13. package/dist/builtin/cursor/src/transport-types.ts +3 -0
  14. package/dist/builtin/cursor/src/transport.ts +1 -0
  15. package/dist/builtin/intercom/package.json +1 -1
  16. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  17. package/dist/builtin/mcp/direct-tools.ts +4 -2
  18. package/dist/builtin/mcp/package.json +1 -1
  19. package/dist/builtin/mcp/proxy-call.ts +3 -1
  20. package/dist/builtin/mcp/utils.ts +18 -7
  21. package/dist/builtin/subagents/CHANGELOG.md +20 -0
  22. package/dist/builtin/subagents/README.md +6 -6
  23. package/dist/builtin/subagents/agents/code-simplifier.md +7 -6
  24. package/dist/builtin/subagents/agents/codebase-analyzer.md +5 -4
  25. package/dist/builtin/subagents/agents/codebase-locator.md +3 -3
  26. package/dist/builtin/subagents/agents/codebase-online-researcher.md +10 -10
  27. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +4 -4
  28. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +3 -3
  29. package/dist/builtin/subagents/agents/codebase-research-locator.md +4 -4
  30. package/dist/builtin/subagents/agents/debugger.md +5 -5
  31. package/dist/builtin/subagents/agents/worker.md +56 -0
  32. package/dist/builtin/subagents/package.json +1 -1
  33. package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
  34. package/dist/builtin/subagents/src/agents/agent-loaders.ts +3 -5
  35. package/dist/builtin/subagents/src/agents/agent-management-helpers.ts +3 -3
  36. package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -0
  37. package/dist/builtin/subagents/src/extension/index.ts +6 -3
  38. package/dist/builtin/subagents/src/extension/schemas.ts +2 -7
  39. package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -3
  40. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +1 -4
  41. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +15 -1
  42. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +35 -1
  43. package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +1 -1
  44. package/dist/builtin/subagents/src/runs/shared/nested-render.ts +2 -2
  45. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +2 -1
  46. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -2
  47. package/dist/builtin/subagents/src/shared/types-async.ts +1 -0
  48. package/dist/builtin/subagents/src/shared/types-depth.ts +5 -5
  49. package/dist/builtin/subagents/src/shared/types-runtime.ts +2 -1
  50. package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +27 -5
  51. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -2
  52. package/dist/builtin/subagents/src/tui/render-layout.ts +27 -4
  53. package/dist/builtin/subagents/src/tui/render-result-animation.ts +22 -31
  54. package/dist/builtin/subagents/src/tui/render-result-compact.ts +6 -6
  55. package/dist/builtin/subagents/src/tui/render-result.ts +20 -19
  56. package/dist/builtin/subagents/src/tui/render-status-progress.ts +3 -3
  57. package/dist/builtin/subagents/src/tui/render-widget.ts +46 -7
  58. package/dist/builtin/subagents/src/tui/render.ts +2 -2
  59. package/dist/builtin/web-access/package.json +1 -1
  60. package/dist/builtin/workflows/CHANGELOG.md +56 -0
  61. package/dist/builtin/workflows/README.md +3 -3
  62. package/dist/builtin/workflows/builtin/goal-artifacts.ts +11 -6
  63. package/dist/builtin/workflows/builtin/goal-ledger.ts +33 -1
  64. package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -28
  65. package/dist/builtin/workflows/builtin/goal-reducer.ts +2 -2
  66. package/dist/builtin/workflows/builtin/goal-reports.ts +2 -5
  67. package/dist/builtin/workflows/builtin/goal-review.ts +1 -1
  68. package/dist/builtin/workflows/builtin/goal-runner.ts +10 -17
  69. package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +3 -3
  70. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +1 -3
  71. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +1 -1
  72. package/dist/builtin/workflows/builtin/ralph-core.ts +7 -17
  73. package/dist/builtin/workflows/builtin/ralph-runner.ts +11 -18
  74. package/dist/builtin/workflows/builtin/shared-prompts.ts +1 -1
  75. package/dist/builtin/workflows/package.json +1 -1
  76. package/dist/builtin/workflows/src/authoring.d.ts +1 -1
  77. package/dist/builtin/workflows/src/durable/backend.ts +343 -0
  78. package/dist/builtin/workflows/src/durable/child-primitive.ts +79 -0
  79. package/dist/builtin/workflows/src/durable/dbos-backend.ts +421 -0
  80. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +171 -0
  81. package/dist/builtin/workflows/src/durable/factory.ts +96 -0
  82. package/dist/builtin/workflows/src/durable/file-backend.ts +433 -0
  83. package/dist/builtin/workflows/src/durable/index.ts +73 -0
  84. package/dist/builtin/workflows/src/durable/resume-catalog.ts +217 -0
  85. package/dist/builtin/workflows/src/durable/resume-runtime.ts +299 -0
  86. package/dist/builtin/workflows/src/durable/scoped-backend.ts +171 -0
  87. package/dist/builtin/workflows/src/durable/stage-primitive.ts +284 -0
  88. package/dist/builtin/workflows/src/durable/tool-primitive.ts +180 -0
  89. package/dist/builtin/workflows/src/durable/types.ts +168 -0
  90. package/dist/builtin/workflows/src/durable/ui-primitive.ts +96 -0
  91. package/dist/builtin/workflows/src/engine/options.ts +3 -0
  92. package/dist/builtin/workflows/src/engine/primitives/parallel.ts +2 -2
  93. package/dist/builtin/workflows/src/engine/primitives/task.ts +4 -4
  94. package/dist/builtin/workflows/src/engine/primitives/ui.ts +22 -8
  95. package/dist/builtin/workflows/src/engine/primitives/workflow.ts +8 -0
  96. package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +69 -0
  97. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +31 -0
  98. package/dist/builtin/workflows/src/engine/run.ts +148 -6
  99. package/dist/builtin/workflows/src/engine/runtime.ts +8 -2
  100. package/dist/builtin/workflows/src/extension/config-loader.ts +35 -15
  101. package/dist/builtin/workflows/src/extension/discovery.ts +20 -8
  102. package/dist/builtin/workflows/src/extension/extension-factory.ts +6 -12
  103. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +5 -1
  104. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +4 -2
  105. package/dist/builtin/workflows/src/extension/runtime.ts +48 -9
  106. package/dist/builtin/workflows/src/extension/wiring.ts +1 -1
  107. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +143 -4
  108. package/dist/builtin/workflows/src/runs/background/quit.ts +61 -0
  109. package/dist/builtin/workflows/src/runs/background/status.ts +1 -0
  110. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +5 -5
  111. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +74 -33
  112. package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +20 -1
  113. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +8 -7
  114. package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +1 -0
  115. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +1 -1
  116. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +19 -2
  117. package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +4 -0
  118. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -10
  119. package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +5 -1
  120. package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +25 -0
  121. package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +3 -0
  122. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +16 -0
  123. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +20 -0
  124. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +23 -1
  125. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +30 -1
  126. package/dist/builtin/workflows/src/shared/store-public-types.ts +6 -2
  127. package/dist/builtin/workflows/src/shared/store-run-methods.ts +12 -6
  128. package/dist/builtin/workflows/src/shared/types.ts +55 -0
  129. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +11 -10
  130. package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -1
  131. package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +41 -0
  132. package/dist/builtin/workflows/src/tui/graph-view-input.ts +82 -24
  133. package/dist/builtin/workflows/src/tui/graph-view-render.ts +7 -0
  134. package/dist/builtin/workflows/src/tui/graph-view-state.ts +22 -2
  135. package/dist/builtin/workflows/src/tui/graph-view-types.ts +4 -5
  136. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -11
  137. package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +9 -3
  138. package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +11 -2
  139. package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +35 -0
  140. package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +51 -17
  141. package/dist/builtin/workflows/src/tui/stage-chat-view-status.ts +36 -0
  142. package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +5 -1
  143. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +3 -1
  144. package/dist/builtin/workflows/src/tui/status-list.ts +14 -2
  145. package/dist/builtin/workflows/src/tui/widget.ts +23 -8
  146. package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +5 -4
  147. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
  148. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +151 -0
  149. package/dist/cli/args.d.ts.map +1 -1
  150. package/dist/cli/args.js +9 -9
  151. package/dist/cli/args.js.map +1 -1
  152. package/dist/config-self-update.d.ts.map +1 -1
  153. package/dist/config-self-update.js +3 -4
  154. package/dist/config-self-update.js.map +1 -1
  155. package/dist/config.d.ts.map +1 -1
  156. package/dist/config.js +4 -5
  157. package/dist/config.js.map +1 -1
  158. package/dist/core/agent-session-bash.d.ts +1 -0
  159. package/dist/core/agent-session-bash.d.ts.map +1 -1
  160. package/dist/core/agent-session-bash.js +1 -0
  161. package/dist/core/agent-session-bash.js.map +1 -1
  162. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  163. package/dist/core/agent-session-tool-registry.js +23 -0
  164. package/dist/core/agent-session-tool-registry.js.map +1 -1
  165. package/dist/core/bash-executor.d.ts +2 -0
  166. package/dist/core/bash-executor.d.ts.map +1 -1
  167. package/dist/core/bash-executor.js +1 -0
  168. package/dist/core/bash-executor.js.map +1 -1
  169. package/dist/core/compaction/compaction.d.ts +29 -0
  170. package/dist/core/compaction/compaction.d.ts.map +1 -1
  171. package/dist/core/compaction/compaction.js +36 -1
  172. package/dist/core/compaction/compaction.js.map +1 -1
  173. package/dist/core/compaction/context-compaction-metrics.d.ts +14 -2
  174. package/dist/core/compaction/context-compaction-metrics.d.ts.map +1 -1
  175. package/dist/core/compaction/context-compaction-metrics.js +50 -1
  176. package/dist/core/compaction/context-compaction-metrics.js.map +1 -1
  177. package/dist/core/compaction/context-compaction-prompt.d.ts.map +1 -1
  178. package/dist/core/compaction/context-compaction-prompt.js +2 -0
  179. package/dist/core/compaction/context-compaction-prompt.js.map +1 -1
  180. package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
  181. package/dist/core/compaction/context-compaction-runner.js +1 -1
  182. package/dist/core/compaction/context-compaction-runner.js.map +1 -1
  183. package/dist/core/compaction/context-deletion-application.d.ts.map +1 -1
  184. package/dist/core/compaction/context-deletion-application.js +5 -5
  185. package/dist/core/compaction/context-deletion-application.js.map +1 -1
  186. package/dist/core/compaction/context-deletion-targets.d.ts +2 -0
  187. package/dist/core/compaction/context-deletion-targets.d.ts.map +1 -1
  188. package/dist/core/compaction/context-deletion-targets.js +23 -3
  189. package/dist/core/compaction/context-deletion-targets.js.map +1 -1
  190. package/dist/core/compaction/context-deletion-tool-definitions.d.ts +6 -0
  191. package/dist/core/compaction/context-deletion-tool-definitions.d.ts.map +1 -1
  192. package/dist/core/compaction/context-deletion-tool-definitions.js.map +1 -1
  193. package/dist/core/compaction/context-deletion-tools.d.ts.map +1 -1
  194. package/dist/core/compaction/context-deletion-tools.js +18 -10
  195. package/dist/core/compaction/context-deletion-tools.js.map +1 -1
  196. package/dist/core/compaction/context-transcript-analysis.d.ts.map +1 -1
  197. package/dist/core/compaction/context-transcript-analysis.js +2 -4
  198. package/dist/core/compaction/context-transcript-analysis.js.map +1 -1
  199. package/dist/core/copilot-gemini-tool-arguments.d.ts.map +1 -1
  200. package/dist/core/copilot-gemini-tool-arguments.js +2 -60
  201. package/dist/core/copilot-gemini-tool-arguments.js.map +1 -1
  202. package/dist/core/extensions/context-types.d.ts +2 -0
  203. package/dist/core/extensions/context-types.d.ts.map +1 -1
  204. package/dist/core/extensions/context-types.js.map +1 -1
  205. package/dist/core/extensions/index.d.ts +2 -2
  206. package/dist/core/extensions/index.d.ts.map +1 -1
  207. package/dist/core/extensions/index.js +1 -1
  208. package/dist/core/extensions/index.js.map +1 -1
  209. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  210. package/dist/core/extensions/loader-virtual-modules.js +57 -32
  211. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  212. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  213. package/dist/core/extensions/runner-context.js +11 -0
  214. package/dist/core/extensions/runner-context.js.map +1 -1
  215. package/dist/core/extensions/tool-events.d.ts +13 -13
  216. package/dist/core/extensions/tool-events.d.ts.map +1 -1
  217. package/dist/core/extensions/tool-events.js +3 -3
  218. package/dist/core/extensions/tool-events.js.map +1 -1
  219. package/dist/core/extensions/types.d.ts +1 -1
  220. package/dist/core/extensions/types.d.ts.map +1 -1
  221. package/dist/core/extensions/types.js +1 -1
  222. package/dist/core/extensions/types.js.map +1 -1
  223. package/dist/core/flattened-tool-arguments.d.ts +18 -0
  224. package/dist/core/flattened-tool-arguments.d.ts.map +1 -1
  225. package/dist/core/flattened-tool-arguments.js +104 -0
  226. package/dist/core/flattened-tool-arguments.js.map +1 -1
  227. package/dist/core/messages.d.ts +1 -0
  228. package/dist/core/messages.d.ts.map +1 -1
  229. package/dist/core/messages.js +46 -1
  230. package/dist/core/messages.js.map +1 -1
  231. package/dist/core/sdk-exports.d.ts +1 -1
  232. package/dist/core/sdk-exports.d.ts.map +1 -1
  233. package/dist/core/sdk-exports.js +1 -1
  234. package/dist/core/sdk-exports.js.map +1 -1
  235. package/dist/core/sdk-types.d.ts +2 -2
  236. package/dist/core/sdk-types.d.ts.map +1 -1
  237. package/dist/core/sdk-types.js.map +1 -1
  238. package/dist/core/sdk.d.ts.map +1 -1
  239. package/dist/core/sdk.js +12 -0
  240. package/dist/core/sdk.js.map +1 -1
  241. package/dist/core/session-manager-core.d.ts +15 -7
  242. package/dist/core/session-manager-core.d.ts.map +1 -1
  243. package/dist/core/session-manager-core.js +20 -9
  244. package/dist/core/session-manager-core.js.map +1 -1
  245. package/dist/core/session-manager-entries.d.ts +2 -2
  246. package/dist/core/session-manager-entries.d.ts.map +1 -1
  247. package/dist/core/session-manager-entries.js +9 -3
  248. package/dist/core/session-manager-entries.js.map +1 -1
  249. package/dist/core/session-manager-history.d.ts.map +1 -1
  250. package/dist/core/session-manager-history.js +2 -1
  251. package/dist/core/session-manager-history.js.map +1 -1
  252. package/dist/core/session-manager-list.d.ts +3 -3
  253. package/dist/core/session-manager-list.d.ts.map +1 -1
  254. package/dist/core/session-manager-list.js +27 -8
  255. package/dist/core/session-manager-list.js.map +1 -1
  256. package/dist/core/session-manager-storage.d.ts +3 -1
  257. package/dist/core/session-manager-storage.d.ts.map +1 -1
  258. package/dist/core/session-manager-storage.js +55 -12
  259. package/dist/core/session-manager-storage.js.map +1 -1
  260. package/dist/core/session-manager-tool-dependencies.d.ts +10 -0
  261. package/dist/core/session-manager-tool-dependencies.d.ts.map +1 -0
  262. package/dist/core/session-manager-tool-dependencies.js +133 -0
  263. package/dist/core/session-manager-tool-dependencies.js.map +1 -0
  264. package/dist/core/session-manager-types.d.ts +22 -0
  265. package/dist/core/session-manager-types.d.ts.map +1 -1
  266. package/dist/core/session-manager-types.js.map +1 -1
  267. package/dist/core/session-manager.d.ts +2 -2
  268. package/dist/core/session-manager.d.ts.map +1 -1
  269. package/dist/core/session-manager.js +1 -1
  270. package/dist/core/session-manager.js.map +1 -1
  271. package/dist/core/settings-manager-basic-accessors.d.ts +4 -0
  272. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  273. package/dist/core/settings-manager-basic-accessors.js +18 -0
  274. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  275. package/dist/core/settings-manager-resource-accessors.d.ts +4 -0
  276. package/dist/core/settings-manager-resource-accessors.d.ts.map +1 -1
  277. package/dist/core/settings-manager-resource-accessors.js +15 -0
  278. package/dist/core/settings-manager-resource-accessors.js.map +1 -1
  279. package/dist/core/settings-types.d.ts +11 -0
  280. package/dist/core/settings-types.d.ts.map +1 -1
  281. package/dist/core/settings-types.js.map +1 -1
  282. package/dist/core/system-prompt.d.ts +1 -1
  283. package/dist/core/system-prompt.d.ts.map +1 -1
  284. package/dist/core/system-prompt.js +3 -2
  285. package/dist/core/system-prompt.js.map +1 -1
  286. package/dist/core/tools/artifact-protocol.d.ts +11 -0
  287. package/dist/core/tools/artifact-protocol.d.ts.map +1 -0
  288. package/dist/core/tools/artifact-protocol.js +76 -0
  289. package/dist/core/tools/artifact-protocol.js.map +1 -0
  290. package/dist/core/tools/artifacts.d.ts +18 -0
  291. package/dist/core/tools/artifacts.d.ts.map +1 -0
  292. package/dist/core/tools/artifacts.js +90 -0
  293. package/dist/core/tools/artifacts.js.map +1 -0
  294. package/dist/core/tools/bash-async-jobs.d.ts +20 -0
  295. package/dist/core/tools/bash-async-jobs.d.ts.map +1 -0
  296. package/dist/core/tools/bash-async-jobs.js +59 -0
  297. package/dist/core/tools/bash-async-jobs.js.map +1 -0
  298. package/dist/core/tools/bash-async-output.d.ts +10 -0
  299. package/dist/core/tools/bash-async-output.d.ts.map +1 -0
  300. package/dist/core/tools/bash-async-output.js +80 -0
  301. package/dist/core/tools/bash-async-output.js.map +1 -0
  302. package/dist/core/tools/bash-interceptor.d.ts +10 -0
  303. package/dist/core/tools/bash-interceptor.d.ts.map +1 -0
  304. package/dist/core/tools/bash-interceptor.js +39 -0
  305. package/dist/core/tools/bash-interceptor.js.map +1 -0
  306. package/dist/core/tools/bash-leading-cd.d.ts +7 -0
  307. package/dist/core/tools/bash-leading-cd.d.ts.map +1 -0
  308. package/dist/core/tools/bash-leading-cd.js +59 -0
  309. package/dist/core/tools/bash-leading-cd.js.map +1 -0
  310. package/dist/core/tools/bash-pty-native.d.ts +14 -0
  311. package/dist/core/tools/bash-pty-native.d.ts.map +1 -0
  312. package/dist/core/tools/bash-pty-native.js +71 -0
  313. package/dist/core/tools/bash-pty-native.js.map +1 -0
  314. package/dist/core/tools/bash.d.ts +28 -17
  315. package/dist/core/tools/bash.d.ts.map +1 -1
  316. package/dist/core/tools/bash.js +152 -35
  317. package/dist/core/tools/bash.js.map +1 -1
  318. package/dist/core/tools/block-resolver.d.ts +16 -0
  319. package/dist/core/tools/block-resolver.d.ts.map +1 -0
  320. package/dist/core/tools/block-resolver.js +74 -0
  321. package/dist/core/tools/block-resolver.js.map +1 -0
  322. package/dist/core/tools/conflict-registry.d.ts +16 -0
  323. package/dist/core/tools/conflict-registry.d.ts.map +1 -0
  324. package/dist/core/tools/conflict-registry.js +44 -0
  325. package/dist/core/tools/conflict-registry.js.map +1 -0
  326. package/dist/core/tools/directory-tree.d.ts +13 -0
  327. package/dist/core/tools/directory-tree.d.ts.map +1 -0
  328. package/dist/core/tools/directory-tree.js +81 -0
  329. package/dist/core/tools/directory-tree.js.map +1 -0
  330. package/dist/core/tools/edit.d.ts +4 -29
  331. package/dist/core/tools/edit.d.ts.map +1 -1
  332. package/dist/core/tools/edit.js +136 -228
  333. package/dist/core/tools/edit.js.map +1 -1
  334. package/dist/core/tools/fetch-url.d.ts +74 -0
  335. package/dist/core/tools/fetch-url.d.ts.map +1 -0
  336. package/dist/core/tools/fetch-url.js +518 -0
  337. package/dist/core/tools/fetch-url.js.map +1 -0
  338. package/dist/core/tools/find.d.ts +27 -9
  339. package/dist/core/tools/find.d.ts.map +1 -1
  340. package/dist/core/tools/find.js +400 -176
  341. package/dist/core/tools/find.js.map +1 -1
  342. package/dist/core/tools/glob-path-utils.d.ts +8 -0
  343. package/dist/core/tools/glob-path-utils.d.ts.map +1 -0
  344. package/dist/core/tools/glob-path-utils.js +26 -0
  345. package/dist/core/tools/glob-path-utils.js.map +1 -0
  346. package/dist/core/tools/grep.d.ts +12 -0
  347. package/dist/core/tools/grep.d.ts.map +1 -1
  348. package/dist/core/tools/grep.js +141 -17
  349. package/dist/core/tools/grep.js.map +1 -1
  350. package/dist/core/tools/hashline-engine/apply.d.ts +11 -0
  351. package/dist/core/tools/hashline-engine/apply.d.ts.map +1 -0
  352. package/dist/core/tools/hashline-engine/apply.js +752 -0
  353. package/dist/core/tools/hashline-engine/apply.js.map +1 -0
  354. package/dist/core/tools/hashline-engine/block.d.ts +40 -0
  355. package/dist/core/tools/hashline-engine/block.d.ts.map +1 -0
  356. package/dist/core/tools/hashline-engine/block.js +117 -0
  357. package/dist/core/tools/hashline-engine/block.js.map +1 -0
  358. package/dist/core/tools/hashline-engine/diff-preview.d.ts +15 -0
  359. package/dist/core/tools/hashline-engine/diff-preview.d.ts.map +1 -0
  360. package/dist/core/tools/hashline-engine/diff-preview.js +98 -0
  361. package/dist/core/tools/hashline-engine/diff-preview.js.map +1 -0
  362. package/dist/core/tools/hashline-engine/format.d.ts +71 -0
  363. package/dist/core/tools/hashline-engine/format.d.ts.map +1 -0
  364. package/dist/core/tools/hashline-engine/format.js +178 -0
  365. package/dist/core/tools/hashline-engine/format.js.map +1 -0
  366. package/dist/core/tools/hashline-engine/fs.d.ts +81 -0
  367. package/dist/core/tools/hashline-engine/fs.d.ts.map +1 -0
  368. package/dist/core/tools/hashline-engine/fs.js +143 -0
  369. package/dist/core/tools/hashline-engine/fs.js.map +1 -0
  370. package/dist/core/tools/hashline-engine/index.d.ts +18 -0
  371. package/dist/core/tools/hashline-engine/index.d.ts.map +1 -0
  372. package/dist/core/tools/hashline-engine/index.js +20 -0
  373. package/dist/core/tools/hashline-engine/index.js.map +1 -0
  374. package/dist/core/tools/hashline-engine/input.d.ts +101 -0
  375. package/dist/core/tools/hashline-engine/input.d.ts.map +1 -0
  376. package/dist/core/tools/hashline-engine/input.js +398 -0
  377. package/dist/core/tools/hashline-engine/input.js.map +1 -0
  378. package/dist/core/tools/hashline-engine/messages.d.ts +99 -0
  379. package/dist/core/tools/hashline-engine/messages.d.ts.map +1 -0
  380. package/dist/core/tools/hashline-engine/messages.js +144 -0
  381. package/dist/core/tools/hashline-engine/messages.js.map +1 -0
  382. package/dist/core/tools/hashline-engine/mismatch.d.ts +45 -0
  383. package/dist/core/tools/hashline-engine/mismatch.d.ts.map +1 -0
  384. package/dist/core/tools/hashline-engine/mismatch.js +90 -0
  385. package/dist/core/tools/hashline-engine/mismatch.js.map +1 -0
  386. package/dist/core/tools/hashline-engine/normalize.d.ts +21 -0
  387. package/dist/core/tools/hashline-engine/normalize.d.ts.map +1 -0
  388. package/dist/core/tools/hashline-engine/normalize.js +33 -0
  389. package/dist/core/tools/hashline-engine/normalize.js.map +1 -0
  390. package/dist/core/tools/hashline-engine/parser.d.ts +24 -0
  391. package/dist/core/tools/hashline-engine/parser.d.ts.map +1 -0
  392. package/dist/core/tools/hashline-engine/parser.js +381 -0
  393. package/dist/core/tools/hashline-engine/parser.js.map +1 -0
  394. package/dist/core/tools/hashline-engine/patcher.d.ts +118 -0
  395. package/dist/core/tools/hashline-engine/patcher.d.ts.map +1 -0
  396. package/dist/core/tools/hashline-engine/patcher.js +341 -0
  397. package/dist/core/tools/hashline-engine/patcher.js.map +1 -0
  398. package/dist/core/tools/hashline-engine/prefixes.d.ts +43 -0
  399. package/dist/core/tools/hashline-engine/prefixes.d.ts.map +1 -0
  400. package/dist/core/tools/hashline-engine/prefixes.js +135 -0
  401. package/dist/core/tools/hashline-engine/prefixes.js.map +1 -0
  402. package/dist/core/tools/hashline-engine/recovery.d.ts +41 -0
  403. package/dist/core/tools/hashline-engine/recovery.d.ts.map +1 -0
  404. package/dist/core/tools/hashline-engine/recovery.js +168 -0
  405. package/dist/core/tools/hashline-engine/recovery.js.map +1 -0
  406. package/dist/core/tools/hashline-engine/snapshots.d.ts +65 -0
  407. package/dist/core/tools/hashline-engine/snapshots.d.ts.map +1 -0
  408. package/dist/core/tools/hashline-engine/snapshots.js +108 -0
  409. package/dist/core/tools/hashline-engine/snapshots.js.map +1 -0
  410. package/dist/core/tools/hashline-engine/stream.d.ts +3 -0
  411. package/dist/core/tools/hashline-engine/stream.d.ts.map +1 -0
  412. package/dist/core/tools/hashline-engine/stream.js +111 -0
  413. package/dist/core/tools/hashline-engine/stream.js.map +1 -0
  414. package/dist/core/tools/hashline-engine/tokenizer.d.ts +69 -0
  415. package/dist/core/tools/hashline-engine/tokenizer.d.ts.map +1 -0
  416. package/dist/core/tools/hashline-engine/tokenizer.js +430 -0
  417. package/dist/core/tools/hashline-engine/tokenizer.js.map +1 -0
  418. package/dist/core/tools/hashline-engine/types.d.ts +166 -0
  419. package/dist/core/tools/hashline-engine/types.d.ts.map +1 -0
  420. package/dist/core/tools/hashline-engine/types.js +9 -0
  421. package/dist/core/tools/hashline-engine/types.js.map +1 -0
  422. package/dist/core/tools/hashline.d.ts +29 -0
  423. package/dist/core/tools/hashline.d.ts.map +1 -0
  424. package/dist/core/tools/hashline.js +110 -0
  425. package/dist/core/tools/hashline.js.map +1 -0
  426. package/dist/core/tools/index.d.ts +6 -4
  427. package/dist/core/tools/index.d.ts.map +1 -1
  428. package/dist/core/tools/index.js +52 -35
  429. package/dist/core/tools/index.js.map +1 -1
  430. package/dist/core/tools/notebook.d.ts +38 -0
  431. package/dist/core/tools/notebook.d.ts.map +1 -0
  432. package/dist/core/tools/notebook.js +125 -0
  433. package/dist/core/tools/notebook.js.map +1 -0
  434. package/dist/core/tools/read-document-extract.d.ts +9 -0
  435. package/dist/core/tools/read-document-extract.d.ts.map +1 -0
  436. package/dist/core/tools/read-document-extract.js +212 -0
  437. package/dist/core/tools/read-document-extract.js.map +1 -0
  438. package/dist/core/tools/read-selectors.d.ts +24 -0
  439. package/dist/core/tools/read-selectors.d.ts.map +1 -0
  440. package/dist/core/tools/read-selectors.js +277 -0
  441. package/dist/core/tools/read-selectors.js.map +1 -0
  442. package/dist/core/tools/read-url.d.ts +37 -0
  443. package/dist/core/tools/read-url.d.ts.map +1 -0
  444. package/dist/core/tools/read-url.js +39 -0
  445. package/dist/core/tools/read-url.js.map +1 -0
  446. package/dist/core/tools/read.d.ts +11 -11
  447. package/dist/core/tools/read.d.ts.map +1 -1
  448. package/dist/core/tools/read.js +224 -94
  449. package/dist/core/tools/read.js.map +1 -1
  450. package/dist/core/tools/resource-selectors.d.ts +44 -0
  451. package/dist/core/tools/resource-selectors.d.ts.map +1 -0
  452. package/dist/core/tools/resource-selectors.js +808 -0
  453. package/dist/core/tools/resource-selectors.js.map +1 -0
  454. package/dist/core/tools/search-details.d.ts +26 -0
  455. package/dist/core/tools/search-details.d.ts.map +1 -0
  456. package/dist/core/tools/search-details.js +24 -0
  457. package/dist/core/tools/search-details.js.map +1 -0
  458. package/dist/core/tools/search-line-ranges.d.ts +11 -0
  459. package/dist/core/tools/search-line-ranges.d.ts.map +1 -0
  460. package/dist/core/tools/search-line-ranges.js +65 -0
  461. package/dist/core/tools/search-line-ranges.js.map +1 -0
  462. package/dist/core/tools/search-native.d.ts +97 -0
  463. package/dist/core/tools/search-native.d.ts.map +1 -0
  464. package/dist/core/tools/search-native.js +27 -0
  465. package/dist/core/tools/search-native.js.map +1 -0
  466. package/dist/core/tools/search.d.ts +24 -0
  467. package/dist/core/tools/search.d.ts.map +1 -0
  468. package/dist/core/tools/search.js +573 -0
  469. package/dist/core/tools/search.js.map +1 -0
  470. package/dist/core/tools/truncate.d.ts +4 -4
  471. package/dist/core/tools/truncate.d.ts.map +1 -1
  472. package/dist/core/tools/truncate.js +3 -3
  473. package/dist/core/tools/truncate.js.map +1 -1
  474. package/dist/core/tools/url-ip-guards.d.ts +4 -0
  475. package/dist/core/tools/url-ip-guards.d.ts.map +1 -0
  476. package/dist/core/tools/url-ip-guards.js +126 -0
  477. package/dist/core/tools/url-ip-guards.js.map +1 -0
  478. package/dist/core/tools/write.d.ts +12 -2
  479. package/dist/core/tools/write.d.ts.map +1 -1
  480. package/dist/core/tools/write.js +166 -14
  481. package/dist/core/tools/write.js.map +1 -1
  482. package/dist/core/trust-manager.d.ts.map +1 -1
  483. package/dist/core/trust-manager.js +2 -3
  484. package/dist/core/trust-manager.js.map +1 -1
  485. package/dist/index-extensions.d.ts +2 -2
  486. package/dist/index-extensions.d.ts.map +1 -1
  487. package/dist/index-extensions.js +1 -1
  488. package/dist/index-extensions.js.map +1 -1
  489. package/dist/index.d.ts +3 -3
  490. package/dist/index.d.ts.map +1 -1
  491. package/dist/index.js +3 -3
  492. package/dist/index.js.map +1 -1
  493. package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +1 -0
  494. package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
  495. package/dist/modes/interactive/components/chat-session-host-runtime.js +12 -0
  496. package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
  497. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts +4 -0
  498. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts.map +1 -0
  499. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js +131 -0
  500. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js.map +1 -0
  501. package/dist/modes/interactive/components/chat-session-host.d.ts +2 -0
  502. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  503. package/dist/modes/interactive/components/chat-session-host.js +7 -1
  504. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  505. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
  506. package/dist/modes/interactive/components/chat-transcript.js +15 -4
  507. package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
  508. package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
  509. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  510. package/dist/modes/interactive/components/custom-editor.js +9 -2
  511. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  512. package/dist/modes/interactive/components/settings-selector-handlers.d.ts.map +1 -1
  513. package/dist/modes/interactive/components/settings-selector-handlers.js +3 -0
  514. package/dist/modes/interactive/components/settings-selector-handlers.js.map +1 -1
  515. package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
  516. package/dist/modes/interactive/components/settings-selector-items.js +7 -0
  517. package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
  518. package/dist/modes/interactive/components/settings-selector-types.d.ts +2 -0
  519. package/dist/modes/interactive/components/settings-selector-types.d.ts.map +1 -1
  520. package/dist/modes/interactive/components/settings-selector-types.js.map +1 -1
  521. package/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  522. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  523. package/dist/modes/interactive/components/tool-execution.js +26 -0
  524. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  525. package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
  526. package/dist/modes/interactive/components/tree-selector-content.js +0 -5
  527. package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
  528. package/dist/modes/interactive/interactive-auth-login.d.ts.map +1 -1
  529. package/dist/modes/interactive/interactive-auth-login.js +1 -0
  530. package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
  531. package/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  532. package/dist/modes/interactive/interactive-autocomplete.js +80 -2
  533. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  534. package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
  535. package/dist/modes/interactive/interactive-hotkeys-debug.js +3 -0
  536. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  537. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  538. package/dist/modes/interactive/interactive-input-handling.js +51 -0
  539. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  540. package/dist/modes/interactive/interactive-mode-base.d.ts +5 -0
  541. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  542. package/dist/modes/interactive/interactive-mode-base.js +5 -0
  543. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  544. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
  545. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  546. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  547. package/dist/modes/interactive/interactive-mode-surface.d.ts +12 -0
  548. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  549. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  550. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  551. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  552. package/dist/modes/interactive/interactive-mode.js +1 -0
  553. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  554. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  555. package/dist/modes/interactive/interactive-model-routing.js +4 -1
  556. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  557. package/dist/modes/interactive/interactive-onboarding.d.ts +11 -0
  558. package/dist/modes/interactive/interactive-onboarding.d.ts.map +1 -0
  559. package/dist/modes/interactive/interactive-onboarding.js +220 -0
  560. package/dist/modes/interactive/interactive-onboarding.js.map +1 -0
  561. package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
  562. package/dist/modes/interactive/interactive-selectors.js +4 -0
  563. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  564. package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
  565. package/dist/modes/interactive/interactive-session-routing.js +6 -0
  566. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  567. package/dist/modes/interactive/interactive-slash-commands.d.ts.map +1 -1
  568. package/dist/modes/interactive/interactive-slash-commands.js +9 -4
  569. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  570. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  571. package/dist/modes/interactive/interactive-startup.js +28 -0
  572. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  573. package/dist/utils/child-process.d.ts.map +1 -1
  574. package/dist/utils/child-process.js +21 -1
  575. package/dist/utils/child-process.js.map +1 -1
  576. package/dist/utils/markit.d.ts +8 -0
  577. package/dist/utils/markit.d.ts.map +1 -0
  578. package/dist/utils/markit.js +53 -0
  579. package/dist/utils/markit.js.map +1 -0
  580. package/dist/utils/paths.d.ts +2 -1
  581. package/dist/utils/paths.d.ts.map +1 -1
  582. package/dist/utils/paths.js +14 -1
  583. package/dist/utils/paths.js.map +1 -1
  584. package/docs/compaction.md +18 -1
  585. package/docs/containerization.md +1 -1
  586. package/docs/docs.json +1 -0
  587. package/docs/extensions.md +25 -36
  588. package/docs/models.md +1 -1
  589. package/docs/providers.md +2 -1
  590. package/docs/quickstart.md +11 -6
  591. package/docs/sdk.md +5 -5
  592. package/docs/session-format.md +6 -0
  593. package/docs/sessions.md +6 -0
  594. package/docs/settings.md +7 -0
  595. package/docs/subagents.md +3 -2
  596. package/docs/tools.md +49 -0
  597. package/docs/usage.md +3 -3
  598. package/docs/workflows.md +112 -8
  599. package/examples/extensions/subagent/README.md +5 -5
  600. package/examples/extensions/subagent/agents/planner.md +1 -1
  601. package/examples/extensions/subagent/agents/reviewer.md +1 -1
  602. package/examples/extensions/subagent/agents/scout.md +2 -2
  603. package/examples/extensions/subagent/display.ts +3 -3
  604. package/examples/sdk/05-tools.ts +3 -3
  605. package/examples/sdk/README.md +1 -1
  606. package/package.json +5 -3
@@ -1 +1 @@
1
- {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,GAAG,EAA2B,MAAM,wBAAwB,CAAC;AAGtE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAKN,KAAK,aAAa,EAClB,KAAK,cAAc,EAMnB,MAAM,gBAAgB,CAAC;AAMxB,KAAK,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;AAElD,KAAK,eAAe,GAAG;IACtB,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAaF,QAAA,MAAM,UAAU;;;;;;EASf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC/B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,4DAA4D;IAC5D,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAgDD,KAAK,uBAAuB,GAAG,GAAG,GAAG;IACpC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AA8IF,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,EAAE,eAAe,CAAC,CA+IjF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Box, Container, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { renderDiff } from \"../../modes/interactive/components/diff.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport {\n\tapplyEditsToNormalizedContent,\n\tcomputeEditsDiff,\n\tdetectLineEnding,\n\ttype Edit,\n\ttype EditDiffError,\n\ttype EditDiffResult,\n\tgenerateDiffString,\n\tgenerateUnifiedPatch,\n\tnormalizeToLF,\n\trestoreLineEndings,\n\tstripBom,\n} from \"./edit-diff.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { renderToolPath, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\ntype EditPreview = EditDiffResult | EditDiffError;\n\ntype EditRenderState = {\n\tcallComponent?: EditCallRenderComponent;\n};\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport type EditToolInput = Static<typeof editSchema>;\ntype LegacyEditToolInput = EditToolInput & {\n\toldText?: unknown;\n\tnewText?: unknown;\n};\n\nexport interface EditToolDetails {\n\t/** Display-oriented diff of the changes made */\n\tdiff: string;\n\t/** Standard unified patch of the changes made */\n\tpatch: string;\n\t/** Line number of the first change in the new file (for editor navigation) */\n\tfirstChangedLine?: number;\n}\n\n/**\n * Pluggable operations for the edit tool.\n * Override these to delegate file editing to remote systems (for example SSH).\n */\nexport interface EditOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Check if file is readable and writable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n}\n\nconst defaultEditOperations: EditOperations = {\n\treadFile: (path) => fsReadFile(path),\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\taccess: (path) => fsAccess(path, constants.R_OK | constants.W_OK),\n};\n\nexport interface EditToolOptions {\n\t/** Custom operations for file editing. Default: local filesystem */\n\toperations?: EditOperations;\n}\n\nfunction prepareEditArguments(input: unknown): EditToolInput {\n\tif (!input || typeof input !== \"object\") {\n\t\treturn input as EditToolInput;\n\t}\n\n\tconst args = input as Record<string, unknown>;\n\n\t// Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array\n\tif (typeof args.edits === \"string\") {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(args.edits);\n\t\t\tif (Array.isArray(parsed)) args.edits = parsed;\n\t\t} catch {}\n\t}\n\n\tconst legacy = args as LegacyEditToolInput;\n\tif (typeof legacy.oldText !== \"string\" || typeof legacy.newText !== \"string\") {\n\t\treturn args as EditToolInput;\n\t}\n\n\tconst edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];\n\tedits.push({ oldText: legacy.oldText, newText: legacy.newText });\n\tconst { oldText: _oldText, newText: _newText, ...rest } = legacy;\n\treturn { ...rest, edits } as EditToolInput;\n}\n\nfunction validateEditInput(input: EditToolInput): { path: string; edits: Edit[] } {\n\tif (!Array.isArray(input.edits) || input.edits.length === 0) {\n\t\tthrow new Error(\"Edit tool input is invalid. edits must contain at least one replacement.\");\n\t}\n\treturn { path: input.path, edits: input.edits };\n}\n\ntype RenderableEditArgs = {\n\tpath?: string;\n\tfile_path?: string;\n\tedits?: Edit[];\n\toldText?: string;\n\tnewText?: string;\n};\n\ntype EditToolResultLike = {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tdetails?: EditToolDetails;\n};\n\ntype EditCallRenderComponent = Box & {\n\tpreview?: EditPreview;\n\tpreviewArgsKey?: string;\n\tpreviewPending?: boolean;\n\tsettledError?: boolean;\n};\n\nfunction createEditCallRenderComponent(): EditCallRenderComponent {\n\treturn Object.assign(new Box(1, 1, (text: string) => text), {\n\t\tpreview: undefined as EditPreview | undefined,\n\t\tpreviewArgsKey: undefined as string | undefined,\n\t\tpreviewPending: false,\n\t\tsettledError: false,\n\t});\n}\n\nfunction getEditCallRenderComponent(state: EditRenderState, lastComponent: unknown): EditCallRenderComponent {\n\tif (lastComponent instanceof Box) {\n\t\tconst component = lastComponent as EditCallRenderComponent;\n\t\tstate.callComponent = component;\n\t\treturn component;\n\t}\n\tif (state.callComponent) {\n\t\treturn state.callComponent;\n\t}\n\tconst component = createEditCallRenderComponent();\n\tstate.callComponent = component;\n\treturn component;\n}\n\nfunction getRenderablePreviewInput(args: RenderableEditArgs | undefined): { path: string; edits: Edit[] } | null {\n\tif (!args) {\n\t\treturn null;\n\t}\n\n\tconst path = typeof args.path === \"string\" ? args.path : typeof args.file_path === \"string\" ? args.file_path : null;\n\tif (!path) {\n\t\treturn null;\n\t}\n\n\tif (\n\t\tArray.isArray(args.edits) &&\n\t\targs.edits.length > 0 &&\n\t\targs.edits.every((edit) => typeof edit?.oldText === \"string\" && typeof edit?.newText === \"string\")\n\t) {\n\t\treturn { path, edits: args.edits };\n\t}\n\n\tif (typeof args.oldText === \"string\" && typeof args.newText === \"string\") {\n\t\treturn { path, edits: [{ oldText: args.oldText, newText: args.newText }] };\n\t}\n\n\treturn null;\n}\n\nfunction formatEditCall(args: RenderableEditArgs | undefined, theme: Theme, cwd: string): string {\n\tconst pathDisplay = renderToolPath(str(args?.file_path ?? args?.path), theme, cwd);\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"edit\"))} ${pathDisplay}`;\n}\n\nfunction formatEditResult(\n\targs: RenderableEditArgs | undefined,\n\tpreview: EditPreview | undefined,\n\tresult: EditToolResultLike,\n\ttheme: Theme,\n\tisError: boolean,\n): string | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst previewDiff = preview && !(\"error\" in preview) ? preview.diff : undefined;\n\tconst previewError = preview && \"error\" in preview ? preview.error : undefined;\n\tif (isError) {\n\t\tconst errorText = result.content\n\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t.map((c) => c.text || \"\")\n\t\t\t.join(\"\\n\");\n\t\tif (!errorText || errorText === previewError) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn theme.fg(\"error\", errorText);\n\t}\n\n\tconst resultDiff = result.details?.diff;\n\tif (resultDiff && resultDiff !== previewDiff) {\n\t\treturn renderDiff(resultDiff, { filePath: rawPath ?? undefined });\n\t}\n\n\treturn undefined;\n}\n\nfunction getEditHeaderBg(\n\tpreview: EditPreview | undefined,\n\tsettledError: boolean | undefined,\n\ttheme: Theme,\n): (text: string) => string {\n\tif (preview) {\n\t\tif (\"error\" in preview) {\n\t\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t\t}\n\t\treturn (text: string) => theme.bg(\"toolSuccessBg\", text);\n\t}\n\tif (settledError) {\n\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t}\n\treturn (text: string) => theme.bg(\"toolPendingBg\", text);\n}\n\nfunction buildEditCallComponent(\n\tcomponent: EditCallRenderComponent,\n\targs: RenderableEditArgs | undefined,\n\ttheme: Theme,\n\tcwd: string,\n): EditCallRenderComponent {\n\tcomponent.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));\n\tcomponent.clear();\n\tcomponent.addChild(new Text(formatEditCall(args, theme, cwd), 0, 0));\n\n\tif (!component.preview) {\n\t\treturn component;\n\t}\n\n\tconst body =\n\t\t\"error\" in component.preview ? theme.fg(\"error\", component.preview.error) : renderDiff(component.preview.diff);\n\tcomponent.addChild(new Spacer(1));\n\tcomponent.addChild(new Text(body, 0, 0));\n\treturn component;\n}\n\nfunction setEditPreview(\n\tcomponent: EditCallRenderComponent,\n\tpreview: EditPreview,\n\targsKey: string | undefined,\n): boolean {\n\tconst current = component.preview;\n\tconst changed =\n\t\tcurrent === undefined ||\n\t\t(\"error\" in current && \"error\" in preview\n\t\t\t? current.error !== preview.error\n\t\t\t: \"error\" in current !== \"error\" in preview) ||\n\t\t(!(\"error\" in current) &&\n\t\t\t!(\"error\" in preview) &&\n\t\t\t(current.diff !== preview.diff || current.firstChangedLine !== preview.firstChangedLine));\n\tcomponent.preview = preview;\n\tcomponent.previewArgsKey = argsKey;\n\tcomponent.previewPending = false;\n\treturn changed;\n}\n\nexport function createEditToolDefinition(\n\tcwd: string,\n\toptions?: EditToolOptions,\n): ToolDefinition<typeof editSchema, EditToolDetails | undefined, EditRenderState> {\n\tconst ops = options?.operations ?? defaultEditOperations;\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.\",\n\t\tpromptSnippet:\n\t\t\t\"Make precise file edits with exact text replacement, including multiple disjoint edits in one call\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use edit for precise changes (edits[].oldText must match exactly)\",\n\t\t\t\"When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls\",\n\t\t\t\"Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.\",\n\t\t\t\"Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.\",\n\t\t],\n\t\tparameters: editSchema,\n\t\trenderShell: \"self\",\n\t\tprepareArguments: prepareEditArguments,\n\t\tasync execute(_toolCallId, input: EditToolInput, signal?: AbortSignal, _onUpdate?, _ctx?) {\n\t\t\tconst { path, edits } = validateEditInput(input);\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\t// Do not reject from an abort event listener here: that would release the\n\t\t\t\t// mutation queue while an in-flight filesystem operation may still finish.\n\t\t\t\t// Checking signal.aborted after each await observes the same aborts while\n\t\t\t\t// keeping the queue locked until the current operation has settled.\n\t\t\t\tconst throwIfAborted = (): void => {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t};\n\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Check if file exists.\n\t\t\t\ttry {\n\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tthrowIfAborted();\n\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\terror instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\t\t\tthrow new Error(`Could not edit file: ${path}. ${errorMessage}.`);\n\t\t\t\t}\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Read the file.\n\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\tconst rawContent = buffer.toString(\"utf-8\");\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Strip BOM before matching. The model will not include an invisible BOM in oldText.\n\t\t\t\tconst { bom, text: content } = stripBom(rawContent);\n\t\t\t\tconst originalEnding = detectLineEnding(content);\n\t\t\t\tconst normalizedContent = normalizeToLF(content);\n\t\t\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\tconst finalContent = bom + restoreLineEndings(newContent, originalEnding);\n\t\t\t\tawait ops.writeFile(absolutePath, finalContent);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\tconst diffResult = generateDiffString(baseContent, newContent);\n\t\t\t\tconst patch = generateUnifiedPatch(path, baseContent, newContent);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Successfully replaced ${edits.length} block(s) in ${path}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { diff: diffResult.diff, patch, firstChangedLine: diffResult.firstChangedLine },\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst component = getEditCallRenderComponent(context.state, context.lastComponent);\n\t\t\tconst previewInput = getRenderablePreviewInput(args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\n\t\t\tif (component.previewArgsKey !== argsKey) {\n\t\t\t\tcomponent.preview = undefined;\n\t\t\t\tcomponent.previewArgsKey = argsKey;\n\t\t\t\tcomponent.previewPending = false;\n\t\t\t\tcomponent.settledError = false;\n\t\t\t}\n\n\t\t\tif (context.argsComplete && previewInput && !component.preview && !component.previewPending) {\n\t\t\t\tcomponent.previewPending = true;\n\t\t\t\tconst requestKey = argsKey;\n\t\t\t\tvoid computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then((preview) => {\n\t\t\t\t\tif (component.previewArgsKey === requestKey) {\n\t\t\t\t\t\tsetEditPreview(component, preview, requestKey);\n\t\t\t\t\t\tcontext.invalidate();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn buildEditCallComponent(component, args, theme, context.cwd);\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst callComponent = context.state.callComponent;\n\t\t\tconst previewInput = getRenderablePreviewInput(context.args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\t\t\tconst typedResult = result as EditToolResultLike;\n\t\t\tconst resultDiff = !context.isError ? typedResult.details?.diff : undefined;\n\t\t\tlet changed = false;\n\t\t\tif (callComponent) {\n\t\t\t\tif (typeof resultDiff === \"string\") {\n\t\t\t\t\tchanged =\n\t\t\t\t\t\tsetEditPreview(\n\t\t\t\t\t\t\tcallComponent,\n\t\t\t\t\t\t\t{ diff: resultDiff, firstChangedLine: typedResult.details?.firstChangedLine },\n\t\t\t\t\t\t\targsKey,\n\t\t\t\t\t\t) || changed;\n\t\t\t\t}\n\t\t\t\tif (callComponent.settledError !== context.isError) {\n\t\t\t\t\tcallComponent.settledError = context.isError;\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\tbuildEditCallComponent(\n\t\t\t\t\t\tcallComponent,\n\t\t\t\t\t\tcontext.args as RenderableEditArgs | undefined,\n\t\t\t\t\t\ttheme,\n\t\t\t\t\t\tcontext.cwd,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);\n\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\tcomponent.clear();\n\t\t\tif (!output) {\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tcomponent.addChild(new Spacer(1));\n\t\t\tcomponent.addChild(new Text(output, 1, 0));\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {\n\treturn wrapToolDefinition(createEditToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAK/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAwF,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAQjJ,QAAA,MAAM,UAAU;;EAGf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,aAAa,CAAC,EAAE,qBAAqB,CAAC;CACtC;AAwGD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAoE/I;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Container, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport { Filesystem, Patch, Patcher, type PatchSectionResult, type PreparedSection, type WriteResult } from \"./hashline-engine/index.ts\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { renderDiff } from \"../../modes/interactive/components/diff.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { generateDiffString, generateUnifiedPatch, normalizeToLF, stripBom } from \"./edit-diff.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { createHashlineSnapshotStore, formatCompactHashlineEditResult, recordHashlineSnapshot, type HashlineSnapshotStore } from \"./hashline.ts\";\nimport { invalidateNativeSearchCache } from \"./search-native.ts\";\nimport { isNotebookPath, readEditableNotebookText, serializeEditedNotebookText } from \"./notebook.ts\";\nimport { nativeBlockResolver } from \"./block-resolver.ts\";\nimport { resolveReadPath } from \"./path-utils.ts\";\nimport { renderToolPath } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nconst editSchema = Type.Object(\n\t{ input: Type.String({ description: \"One or more hashline file sections. Must start with [PATH#TAG]; tag comes from the latest read, search, write, or successful edit output.\" }) },\n\t{ additionalProperties: false },\n);\n\nexport type EditToolInput = Static<typeof editSchema>;\n\nexport interface EditToolDetails {\n\tdiff: string;\n\tpatch: string;\n\tfirstChangedLine?: number;\n}\n\nexport interface EditOperations {\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\taccess: (absolutePath: string) => Promise<void>;\n}\n\nconst defaultEditOperations: EditOperations = {\n\treadFile: (path) => fsReadFile(path),\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\taccess: (path) => fsAccess(path, constants.R_OK | constants.W_OK),\n};\n\nexport interface EditToolOptions {\n\toperations?: EditOperations;\n\thashlineStore?: HashlineSnapshotStore;\n}\n\ntype EditToolResultLike = {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tdetails?: EditToolDetails;\n};\n\nclass EditFilesystem extends Filesystem {\n\tprivate readonly cwd: string;\n\tprivate readonly operations: EditOperations;\n\tconstructor(cwd: string, operations: EditOperations) { super(); this.cwd = cwd; this.operations = operations; }\n\n\tcanonicalPath(path: string): string { return resolveReadPath(path, this.cwd); }\n\n\tasync preflightWrite(path: string): Promise<void> {\n\t\tconst absolutePath = this.canonicalPath(path);\n\t\ttry { await this.operations.access(absolutePath); }\n\t\tcatch (error: unknown) {\n\t\t\tconst message = error instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\tthrow new Error(`Could not edit file: ${path}. ${message}.`);\n\t\t}\n\t}\n\n\tasync readText(path: string): Promise<string> {\n\t\tconst absolutePath = this.canonicalPath(path);\n\t\treturn isNotebookPath(absolutePath) ? readEditableNotebookText(absolutePath, path) : (await this.operations.readFile(absolutePath)).toString(\"utf-8\");\n\t}\n\n\tasync writeText(path: string, content: string): Promise<WriteResult> {\n\t\tconst absolutePath = this.canonicalPath(path);\n\t\tconst persisted = isNotebookPath(absolutePath) ? serializeEditedNotebookText(absolutePath, path, normalizeToLF(stripBom(content).text)) : content;\n\t\tawait this.operations.writeFile(absolutePath, persisted);\n\t\treturn { text: persisted };\n\t}\n}\n\nfunction isFourDigitHexTag(value: string): boolean {\n\treturn value.length === 4 && [...value].every((char) => (char >= \"0\" && char <= \"9\") || (char >= \"a\" && char <= \"f\") || (char >= \"A\" && char <= \"F\"));\n}\n\nfunction extractFirstHeaderPath(input: string | undefined): string | undefined {\n\tif (!input) return undefined;\n\tfor (const line of input.split(\"\\n\")) {\n\t\tconst trimmed = line.trimStart();\n\t\tif (!trimmed.startsWith(\"[\")) continue;\n\t\tconst hashIndex = trimmed.indexOf(\"#\", 1);\n\t\tconst closeIndex = hashIndex >= 0 ? trimmed.indexOf(\"]\", hashIndex + 1) : -1;\n\t\tif (hashIndex <= 1 || closeIndex !== hashIndex + 5) continue;\n\t\tconst tag = trimmed.slice(hashIndex + 1, closeIndex);\n\t\tif (isFourDigitHexTag(tag)) return trimmed.slice(1, hashIndex);\n\t}\n\treturn undefined;\n}\n\nfunction formatEditCall(args: unknown, theme: Theme, cwd: string): string {\n\tconst input = args && typeof args === \"object\" && \"input\" in args ? (args as { input?: unknown }).input : undefined;\n\tconst pathDisplay = renderToolPath(extractFirstHeaderPath(typeof input === \"string\" ? input : undefined) ?? null, theme, cwd);\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"edit\"))} ${pathDisplay}`;\n}\n\nfunction formatEditResult(result: EditToolResultLike, theme: Theme, isError: boolean): string | undefined {\n\tif (isError) {\n\t\tconst errorText = result.content.filter((c) => c.type === \"text\").map((c) => c.text || \"\").join(\"\\n\");\n\t\treturn errorText ? theme.fg(\"error\", errorText) : undefined;\n\t}\n\treturn result.details?.diff ? renderDiff(result.details.diff) : undefined;\n}\n\nasync function withFileMutationQueues<T>(filePaths: readonly string[], fn: () => Promise<T>): Promise<T> {\n\tconst sorted = [...new Set(filePaths)].sort();\n\tconst run = (index: number): Promise<T> => {\n\t\tconst filePath = sorted[index];\n\t\treturn filePath ? withFileMutationQueue(filePath, () => run(index + 1)) : fn();\n\t};\n\treturn run(0);\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n}\n\nfunction formatNoopMessage(path: string, count: number): string {\n\treturn `Edits to ${path} parsed and applied cleanly, but produced no change: your body row(s) are byte-identical to the file at the targeted lines. The bug is somewhere else — re-read the file before issuing another edit. Do NOT widen the payload or add lines; verify the anchor first.${count > 1 ? `\\nNo-op count for this identical payload: ${count}.` : \"\"}`;\n}\n\nfunction blockMessages(item: PreparedSection | PatchSectionResult): string[] {\n\tconst warnings = \"parseWarnings\" in item ? [...item.parseWarnings, ...(item.applyResult.warnings ?? [])] : item.warnings;\n\tconst resolutions = (\"applyResult\" in item ? item.applyResult.blockResolutions : item.blockResolutions)?.map((resolution) => {\n\t\tconst verb = resolution.op === \"insert_after\" ? \"insert after block\" : `${resolution.op} block`;\n\t\tconst lands = resolution.op === \"insert_after\" ? `; body lands after line ${resolution.end}` : \"\";\n\t\treturn `${verb} ${resolution.anchorLine} → resolved lines ${resolution.start}-${resolution.end} (${resolution.end - resolution.start + 1} lines)${lands}`;\n\t}) ?? [];\n\treturn [...warnings, ...resolutions];\n}\n\nfunction assertUniquePreparedPaths(prepared: readonly PreparedSection[]): void {\n\tconst seen = new Map<string, string>();\n\tfor (const entry of prepared) {\n\t\tconst previous = seen.get(entry.canonicalPath);\n\t\tif (previous) throw new Error(`Multiple hashline sections resolve to the same file (${previous} and ${entry.section.path}). Merge their ops under one header before applying.`);\n\t\tseen.set(entry.canonicalPath, entry.section.path);\n\t}\n}\n\nexport function createEditToolDefinition(cwd: string, options?: EditToolOptions): ToolDefinition<typeof editSchema, EditToolDetails | undefined> {\n\tconst ops = options?.operations ?? defaultEditOperations;\n\tconst hashlineStore = options?.hashlineStore ?? createHashlineSnapshotStore();\n\tconst fs = new EditFilesystem(cwd, ops);\n\tconst patcher = new Patcher({ fs, snapshots: hashlineStore.snapshots, blockResolver: nativeBlockResolver });\n\tconst noopCounts = new Map<string, number>();\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription: \"Edit existing files with the hashline patch language: each section starts with [PATH#TAG] (TAG is the 4-hex snapshot tag from your latest read/search), then hunk headers (replace N..M:, replace block N:, delete N..M, delete block N, insert before|after N:, insert after block N:, insert head:, insert tail:) followed by +TEXT body rows. Numbers refer to the original file. Use the write tool to create new files.\",\n\t\tpromptSnippet: \"Apply source edits with hashline patch input\",\n\t\tpromptGuidelines: [\n\t\t\t\"hashline edit format: a header ending in ':' is followed by '+'TEXT body rows; 'delete' has no body. Every section starts with [PATH#TAG]; TAG is REQUIRED (the 4-hex snapshot tag from your latest read/search) — there is no hashless form. Use the write tool to create new files.\",\n\t\t\t\"Ops: 'replace N..M:' replaces original lines N..M (INCLUSIVE — line M is consumed); 'replace block N:' replaces the whole syntactic block that BEGINS on line N (Atomic resolves the closing line with a brace/indent heuristic; point N at the opener); 'delete N..M' / 'delete block N' delete (no body); 'insert before N:' / 'insert after N:' insert relative to a line; 'insert after block N:' inserts after the END of the block beginning on N; 'insert head:' / 'insert tail:' insert at file start/end. Single line: 'replace N..N:' / 'delete N'. The range is the ORIGINAL lines you touch; body length is irrelevant.\",\n\t\t\t\"Body rows appear only under a ':' header. Every row is '+TEXT' (adds a literal line, leading whitespace kept; '+' alone adds a blank line). There is NO other body row kind — never write '-old' or a bare/context line. To keep a line, leave it out of every range. For a literal line starting with '-' or '+', prefix it: '+-x', '++x'.\",\n\t\t\t\"Numbers refer to the ORIGINAL file and do not shift as hunks apply; they die with the call — every applied edit mints a fresh #TAG and renumbers, so anchor the next edit on the edit response or a fresh read. Ranges are TIGHT: cover ONLY lines whose content changes; a stale wide range shreds everything it spans. Pure additions use 'insert', never a widened 'replace'. Whole construct → 'replace block N'; lines inside it → 'replace N..M'.\",\n\t\t\t\"On a stale-tag rejection or any surprising result: STOP and re-read before further edits. Never start or end a range mid-expression/mid-block, and never span a hunk across an elided ('…') region — read it first. Never use edit to reformat/restyle code; run the project formatter instead.\",\n\t\t],\n\t\tparameters: editSchema,\n\t\tasync execute(_toolCallId, input: EditToolInput, signal?: AbortSignal) {\n\t\t\tif (typeof input.input !== \"string\" || input.input.trim() === \"\") throw new Error(\"edit input must be a non-empty hashline script with [PATH#TAG] sections.\");\n\t\t\tconst patch = Patch.parse(input.input, { cwd });\n\t\t\tconst prepared: PreparedSection[] = [];\n\t\t\tfor (const section of patch.sections) { throwIfAborted(signal); prepared.push(await patcher.prepare(section)); }\n\t\t\tassertUniquePreparedPaths(prepared);\n\t\t\tconst noops = prepared.filter((item) => item.isNoop);\n\t\t\tif (noops.length > 0) {\n\t\t\t\tif (noops.length !== prepared.length) throw new Error(`Hashline edit for ${noops[0]!.section.path} did not change the file.`);\n\t\t\t\tconst key = prepared.map((item) => `${item.canonicalPath}\\0${item.applyResult.text}`).join(\"\\0\\0\");\n\t\t\t\tconst count = (noopCounts.get(key) ?? 0) + 1;\n\t\t\t\tnoopCounts.set(key, count);\n\t\t\t\tif (count >= 3) throw new Error(`STOP. ${formatNoopMessage(prepared[0]!.section.path, count)}`);\n\t\t\t\treturn { content: [{ type: \"text\", text: formatNoopMessage(prepared[0]!.section.path, count) }], details: { diff: \"\", patch: \"\" } };\n\t\t\t}\n\t\t\treturn withFileMutationQueues(prepared.map((item) => item.canonicalPath), async () => {\n\t\t\t\tfor (const item of prepared) if (normalizeToLF(stripBom(await fs.readText(item.section.path)).text) !== item.normalized) throw new Error(`Stale hashline tag for ${item.section.path}: file content changed before write. Re-read before editing.`);\n\t\t\t\tconst applyResult = await patcher.apply(patch);\n\t\t\t\tconst outputs: string[] = [];\n\t\t\t\tlet combinedDiff = \"\", combinedPatch = \"\";\n\t\t\t\tlet firstChangedLine: number | undefined;\n\t\t\t\tfor (const result of applyResult.sections) {\n\t\t\t\t\tthrowIfAborted(signal);\n\t\t\t\t\tinvalidateNativeSearchCache(result.canonicalPath);\n\t\t\t\t\tconst snapshot = recordHashlineSnapshot(result.canonicalPath, cwd, result.after, hashlineStore);\n\t\t\t\t\tconst diffResult = generateDiffString(result.before, result.after);\n\t\t\t\t\tcombinedDiff += `${combinedDiff ? \"\\n\" : \"\"}${diffResult.diff}`;\n\t\t\t\t\tcombinedPatch += `${combinedPatch ? \"\\n\" : \"\"}${generateUnifiedPatch(result.path, result.before, result.after)}`;\n\t\t\t\t\tfirstChangedLine ??= diffResult.firstChangedLine;\n\t\t\t\t\toutputs.push(formatCompactHashlineEditResult(snapshot, diffResult, blockMessages(result)));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputs.join(\"\\n\\n\") }], details: { diff: combinedDiff, patch: combinedPatch, firstChangedLine } };\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatEditCall(args, theme, context.cwd));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst output = formatEditResult(result as EditToolResultLike, theme, context.isError);\n\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\tcomponent.clear();\n\t\t\tif (!output) return component;\n\t\t\tcomponent.addChild(new Spacer(1));\n\t\t\tcomponent.addChild(new Text(output, 1, 0));\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {\n\treturn wrapToolDefinition(createEditToolDefinition(cwd, options));\n}\n"]}
@@ -1,277 +1,185 @@
1
- import { Box, Container, Spacer, Text } from "@earendil-works/pi-tui";
1
+ import { Container, Spacer, Text } from "@earendil-works/pi-tui";
2
+ import { Filesystem, Patch, Patcher } from "./hashline-engine/index.js";
2
3
  import { constants } from "fs";
3
4
  import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
4
5
  import { Type } from "typebox";
5
6
  import { renderDiff } from "../../modes/interactive/components/diff.js";
6
- import { applyEditsToNormalizedContent, computeEditsDiff, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
7
+ import { generateDiffString, generateUnifiedPatch, normalizeToLF, stripBom } from "./edit-diff.js";
7
8
  import { withFileMutationQueue } from "./file-mutation-queue.js";
8
- import { resolveToCwd } from "./path-utils.js";
9
- import { renderToolPath, str } from "./render-utils.js";
9
+ import { createHashlineSnapshotStore, formatCompactHashlineEditResult, recordHashlineSnapshot } from "./hashline.js";
10
+ import { invalidateNativeSearchCache } from "./search-native.js";
11
+ import { isNotebookPath, readEditableNotebookText, serializeEditedNotebookText } from "./notebook.js";
12
+ import { nativeBlockResolver } from "./block-resolver.js";
13
+ import { resolveReadPath } from "./path-utils.js";
14
+ import { renderToolPath } from "./render-utils.js";
10
15
  import { wrapToolDefinition } from "./tool-definition-wrapper.js";
11
- const replaceEditSchema = Type.Object({
12
- oldText: Type.String({
13
- description: "Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.",
14
- }),
15
- newText: Type.String({ description: "Replacement text for this targeted edit." }),
16
- }, { additionalProperties: false });
17
- const editSchema = Type.Object({
18
- path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
19
- edits: Type.Array(replaceEditSchema, {
20
- description: "One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.",
21
- }),
22
- }, { additionalProperties: false });
16
+ const editSchema = Type.Object({ input: Type.String({ description: "One or more hashline file sections. Must start with [PATH#TAG]; tag comes from the latest read, search, write, or successful edit output." }) }, { additionalProperties: false });
23
17
  const defaultEditOperations = {
24
18
  readFile: (path) => fsReadFile(path),
25
19
  writeFile: (path, content) => fsWriteFile(path, content, "utf-8"),
26
20
  access: (path) => fsAccess(path, constants.R_OK | constants.W_OK),
27
21
  };
28
- function prepareEditArguments(input) {
29
- if (!input || typeof input !== "object") {
30
- return input;
31
- }
32
- const args = input;
33
- // Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array
34
- if (typeof args.edits === "string") {
22
+ class EditFilesystem extends Filesystem {
23
+ constructor(cwd, operations) { super(); this.cwd = cwd; this.operations = operations; }
24
+ canonicalPath(path) { return resolveReadPath(path, this.cwd); }
25
+ async preflightWrite(path) {
26
+ const absolutePath = this.canonicalPath(path);
35
27
  try {
36
- const parsed = JSON.parse(args.edits);
37
- if (Array.isArray(parsed))
38
- args.edits = parsed;
28
+ await this.operations.access(absolutePath);
29
+ }
30
+ catch (error) {
31
+ const message = error instanceof Error && "code" in error ? `Error code: ${error.code}` : String(error);
32
+ throw new Error(`Could not edit file: ${path}. ${message}.`);
39
33
  }
40
- catch { }
41
34
  }
42
- const legacy = args;
43
- if (typeof legacy.oldText !== "string" || typeof legacy.newText !== "string") {
44
- return args;
35
+ async readText(path) {
36
+ const absolutePath = this.canonicalPath(path);
37
+ return isNotebookPath(absolutePath) ? readEditableNotebookText(absolutePath, path) : (await this.operations.readFile(absolutePath)).toString("utf-8");
45
38
  }
46
- const edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];
47
- edits.push({ oldText: legacy.oldText, newText: legacy.newText });
48
- const { oldText: _oldText, newText: _newText, ...rest } = legacy;
49
- return { ...rest, edits };
50
- }
51
- function validateEditInput(input) {
52
- if (!Array.isArray(input.edits) || input.edits.length === 0) {
53
- throw new Error("Edit tool input is invalid. edits must contain at least one replacement.");
39
+ async writeText(path, content) {
40
+ const absolutePath = this.canonicalPath(path);
41
+ const persisted = isNotebookPath(absolutePath) ? serializeEditedNotebookText(absolutePath, path, normalizeToLF(stripBom(content).text)) : content;
42
+ await this.operations.writeFile(absolutePath, persisted);
43
+ return { text: persisted };
54
44
  }
55
- return { path: input.path, edits: input.edits };
56
- }
57
- function createEditCallRenderComponent() {
58
- return Object.assign(new Box(1, 1, (text) => text), {
59
- preview: undefined,
60
- previewArgsKey: undefined,
61
- previewPending: false,
62
- settledError: false,
63
- });
64
45
  }
65
- function getEditCallRenderComponent(state, lastComponent) {
66
- if (lastComponent instanceof Box) {
67
- const component = lastComponent;
68
- state.callComponent = component;
69
- return component;
70
- }
71
- if (state.callComponent) {
72
- return state.callComponent;
73
- }
74
- const component = createEditCallRenderComponent();
75
- state.callComponent = component;
76
- return component;
46
+ function isFourDigitHexTag(value) {
47
+ return value.length === 4 && [...value].every((char) => (char >= "0" && char <= "9") || (char >= "a" && char <= "f") || (char >= "A" && char <= "F"));
77
48
  }
78
- function getRenderablePreviewInput(args) {
79
- if (!args) {
80
- return null;
81
- }
82
- const path = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : null;
83
- if (!path) {
84
- return null;
85
- }
86
- if (Array.isArray(args.edits) &&
87
- args.edits.length > 0 &&
88
- args.edits.every((edit) => typeof edit?.oldText === "string" && typeof edit?.newText === "string")) {
89
- return { path, edits: args.edits };
49
+ function extractFirstHeaderPath(input) {
50
+ if (!input)
51
+ return undefined;
52
+ for (const line of input.split("\n")) {
53
+ const trimmed = line.trimStart();
54
+ if (!trimmed.startsWith("["))
55
+ continue;
56
+ const hashIndex = trimmed.indexOf("#", 1);
57
+ const closeIndex = hashIndex >= 0 ? trimmed.indexOf("]", hashIndex + 1) : -1;
58
+ if (hashIndex <= 1 || closeIndex !== hashIndex + 5)
59
+ continue;
60
+ const tag = trimmed.slice(hashIndex + 1, closeIndex);
61
+ if (isFourDigitHexTag(tag))
62
+ return trimmed.slice(1, hashIndex);
90
63
  }
91
- if (typeof args.oldText === "string" && typeof args.newText === "string") {
92
- return { path, edits: [{ oldText: args.oldText, newText: args.newText }] };
93
- }
94
- return null;
64
+ return undefined;
95
65
  }
96
66
  function formatEditCall(args, theme, cwd) {
97
- const pathDisplay = renderToolPath(str(args?.file_path ?? args?.path), theme, cwd);
67
+ const input = args && typeof args === "object" && "input" in args ? args.input : undefined;
68
+ const pathDisplay = renderToolPath(extractFirstHeaderPath(typeof input === "string" ? input : undefined) ?? null, theme, cwd);
98
69
  return `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
99
70
  }
100
- function formatEditResult(args, preview, result, theme, isError) {
101
- const rawPath = str(args?.file_path ?? args?.path);
102
- const previewDiff = preview && !("error" in preview) ? preview.diff : undefined;
103
- const previewError = preview && "error" in preview ? preview.error : undefined;
71
+ function formatEditResult(result, theme, isError) {
104
72
  if (isError) {
105
- const errorText = result.content
106
- .filter((c) => c.type === "text")
107
- .map((c) => c.text || "")
108
- .join("\n");
109
- if (!errorText || errorText === previewError) {
110
- return undefined;
111
- }
112
- return theme.fg("error", errorText);
113
- }
114
- const resultDiff = result.details?.diff;
115
- if (resultDiff && resultDiff !== previewDiff) {
116
- return renderDiff(resultDiff, { filePath: rawPath ?? undefined });
73
+ const errorText = result.content.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
74
+ return errorText ? theme.fg("error", errorText) : undefined;
117
75
  }
118
- return undefined;
76
+ return result.details?.diff ? renderDiff(result.details.diff) : undefined;
119
77
  }
120
- function getEditHeaderBg(preview, settledError, theme) {
121
- if (preview) {
122
- if ("error" in preview) {
123
- return (text) => theme.bg("toolErrorBg", text);
124
- }
125
- return (text) => theme.bg("toolSuccessBg", text);
126
- }
127
- if (settledError) {
128
- return (text) => theme.bg("toolErrorBg", text);
129
- }
130
- return (text) => theme.bg("toolPendingBg", text);
78
+ async function withFileMutationQueues(filePaths, fn) {
79
+ const sorted = [...new Set(filePaths)].sort();
80
+ const run = (index) => {
81
+ const filePath = sorted[index];
82
+ return filePath ? withFileMutationQueue(filePath, () => run(index + 1)) : fn();
83
+ };
84
+ return run(0);
131
85
  }
132
- function buildEditCallComponent(component, args, theme, cwd) {
133
- component.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));
134
- component.clear();
135
- component.addChild(new Text(formatEditCall(args, theme, cwd), 0, 0));
136
- if (!component.preview) {
137
- return component;
138
- }
139
- const body = "error" in component.preview ? theme.fg("error", component.preview.error) : renderDiff(component.preview.diff);
140
- component.addChild(new Spacer(1));
141
- component.addChild(new Text(body, 0, 0));
142
- return component;
86
+ function throwIfAborted(signal) {
87
+ if (signal?.aborted)
88
+ throw new Error("Operation aborted");
89
+ }
90
+ function formatNoopMessage(path, count) {
91
+ return `Edits to ${path} parsed and applied cleanly, but produced no change: your body row(s) are byte-identical to the file at the targeted lines. The bug is somewhere else — re-read the file before issuing another edit. Do NOT widen the payload or add lines; verify the anchor first.${count > 1 ? `\nNo-op count for this identical payload: ${count}.` : ""}`;
92
+ }
93
+ function blockMessages(item) {
94
+ const warnings = "parseWarnings" in item ? [...item.parseWarnings, ...(item.applyResult.warnings ?? [])] : item.warnings;
95
+ const resolutions = ("applyResult" in item ? item.applyResult.blockResolutions : item.blockResolutions)?.map((resolution) => {
96
+ const verb = resolution.op === "insert_after" ? "insert after block" : `${resolution.op} block`;
97
+ const lands = resolution.op === "insert_after" ? `; body lands after line ${resolution.end}` : "";
98
+ return `${verb} ${resolution.anchorLine} → resolved lines ${resolution.start}-${resolution.end} (${resolution.end - resolution.start + 1} lines)${lands}`;
99
+ }) ?? [];
100
+ return [...warnings, ...resolutions];
143
101
  }
144
- function setEditPreview(component, preview, argsKey) {
145
- const current = component.preview;
146
- const changed = current === undefined ||
147
- ("error" in current && "error" in preview
148
- ? current.error !== preview.error
149
- : "error" in current !== "error" in preview) ||
150
- (!("error" in current) &&
151
- !("error" in preview) &&
152
- (current.diff !== preview.diff || current.firstChangedLine !== preview.firstChangedLine));
153
- component.preview = preview;
154
- component.previewArgsKey = argsKey;
155
- component.previewPending = false;
156
- return changed;
102
+ function assertUniquePreparedPaths(prepared) {
103
+ const seen = new Map();
104
+ for (const entry of prepared) {
105
+ const previous = seen.get(entry.canonicalPath);
106
+ if (previous)
107
+ throw new Error(`Multiple hashline sections resolve to the same file (${previous} and ${entry.section.path}). Merge their ops under one header before applying.`);
108
+ seen.set(entry.canonicalPath, entry.section.path);
109
+ }
157
110
  }
158
111
  export function createEditToolDefinition(cwd, options) {
159
112
  const ops = options?.operations ?? defaultEditOperations;
113
+ const hashlineStore = options?.hashlineStore ?? createHashlineSnapshotStore();
114
+ const fs = new EditFilesystem(cwd, ops);
115
+ const patcher = new Patcher({ fs, snapshots: hashlineStore.snapshots, blockResolver: nativeBlockResolver });
116
+ const noopCounts = new Map();
160
117
  return {
161
118
  name: "edit",
162
119
  label: "edit",
163
- description: "Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.",
164
- promptSnippet: "Make precise file edits with exact text replacement, including multiple disjoint edits in one call",
120
+ description: "Edit existing files with the hashline patch language: each section starts with [PATH#TAG] (TAG is the 4-hex snapshot tag from your latest read/search), then hunk headers (replace N..M:, replace block N:, delete N..M, delete block N, insert before|after N:, insert after block N:, insert head:, insert tail:) followed by +TEXT body rows. Numbers refer to the original file. Use the write tool to create new files.",
121
+ promptSnippet: "Apply source edits with hashline patch input",
165
122
  promptGuidelines: [
166
- "Use edit for precise changes (edits[].oldText must match exactly)",
167
- "When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls",
168
- "Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.",
169
- "Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.",
123
+ "hashline edit format: a header ending in ':' is followed by '+'TEXT body rows; 'delete' has no body. Every section starts with [PATH#TAG]; TAG is REQUIRED (the 4-hex snapshot tag from your latest read/search) — there is no hashless form. Use the write tool to create new files.",
124
+ "Ops: 'replace N..M:' replaces original lines N..M (INCLUSIVE line M is consumed); 'replace block N:' replaces the whole syntactic block that BEGINS on line N (Atomic resolves the closing line with a brace/indent heuristic; point N at the opener); 'delete N..M' / 'delete block N' delete (no body); 'insert before N:' / 'insert after N:' insert relative to a line; 'insert after block N:' inserts after the END of the block beginning on N; 'insert head:' / 'insert tail:' insert at file start/end. Single line: 'replace N..N:' / 'delete N'. The range is the ORIGINAL lines you touch; body length is irrelevant.",
125
+ "Body rows appear only under a ':' header. Every row is '+TEXT' (adds a literal line, leading whitespace kept; '+' alone adds a blank line). There is NO other body row kind — never write '-old' or a bare/context line. To keep a line, leave it out of every range. For a literal line starting with '-' or '+', prefix it: '+-x', '++x'.",
126
+ "Numbers refer to the ORIGINAL file and do not shift as hunks apply; they die with the call — every applied edit mints a fresh #TAG and renumbers, so anchor the next edit on the edit response or a fresh read. Ranges are TIGHT: cover ONLY lines whose content changes; a stale wide range shreds everything it spans. Pure additions use 'insert', never a widened 'replace'. Whole construct → 'replace block N'; lines inside it → 'replace N..M'.",
127
+ "On a stale-tag rejection or any surprising result: STOP and re-read before further edits. Never start or end a range mid-expression/mid-block, and never span a hunk across an elided ('…') region — read it first. Never use edit to reformat/restyle code; run the project formatter instead.",
170
128
  ],
171
129
  parameters: editSchema,
172
- renderShell: "self",
173
- prepareArguments: prepareEditArguments,
174
- async execute(_toolCallId, input, signal, _onUpdate, _ctx) {
175
- const { path, edits } = validateEditInput(input);
176
- const absolutePath = resolveToCwd(path, cwd);
177
- return withFileMutationQueue(absolutePath, async () => {
178
- // Do not reject from an abort event listener here: that would release the
179
- // mutation queue while an in-flight filesystem operation may still finish.
180
- // Checking signal.aborted after each await observes the same aborts while
181
- // keeping the queue locked until the current operation has settled.
182
- const throwIfAborted = () => {
183
- if (signal?.aborted)
184
- throw new Error("Operation aborted");
185
- };
186
- throwIfAborted();
187
- // Check if file exists.
188
- try {
189
- await ops.access(absolutePath);
190
- }
191
- catch (error) {
192
- throwIfAborted();
193
- const errorMessage = error instanceof Error && "code" in error ? `Error code: ${error.code}` : String(error);
194
- throw new Error(`Could not edit file: ${path}. ${errorMessage}.`);
130
+ async execute(_toolCallId, input, signal) {
131
+ if (typeof input.input !== "string" || input.input.trim() === "")
132
+ throw new Error("edit input must be a non-empty hashline script with [PATH#TAG] sections.");
133
+ const patch = Patch.parse(input.input, { cwd });
134
+ const prepared = [];
135
+ for (const section of patch.sections) {
136
+ throwIfAborted(signal);
137
+ prepared.push(await patcher.prepare(section));
138
+ }
139
+ assertUniquePreparedPaths(prepared);
140
+ const noops = prepared.filter((item) => item.isNoop);
141
+ if (noops.length > 0) {
142
+ if (noops.length !== prepared.length)
143
+ throw new Error(`Hashline edit for ${noops[0].section.path} did not change the file.`);
144
+ const key = prepared.map((item) => `${item.canonicalPath}\0${item.applyResult.text}`).join("\0\0");
145
+ const count = (noopCounts.get(key) ?? 0) + 1;
146
+ noopCounts.set(key, count);
147
+ if (count >= 3)
148
+ throw new Error(`STOP. ${formatNoopMessage(prepared[0].section.path, count)}`);
149
+ return { content: [{ type: "text", text: formatNoopMessage(prepared[0].section.path, count) }], details: { diff: "", patch: "" } };
150
+ }
151
+ return withFileMutationQueues(prepared.map((item) => item.canonicalPath), async () => {
152
+ for (const item of prepared)
153
+ if (normalizeToLF(stripBom(await fs.readText(item.section.path)).text) !== item.normalized)
154
+ throw new Error(`Stale hashline tag for ${item.section.path}: file content changed before write. Re-read before editing.`);
155
+ const applyResult = await patcher.apply(patch);
156
+ const outputs = [];
157
+ let combinedDiff = "", combinedPatch = "";
158
+ let firstChangedLine;
159
+ for (const result of applyResult.sections) {
160
+ throwIfAborted(signal);
161
+ invalidateNativeSearchCache(result.canonicalPath);
162
+ const snapshot = recordHashlineSnapshot(result.canonicalPath, cwd, result.after, hashlineStore);
163
+ const diffResult = generateDiffString(result.before, result.after);
164
+ combinedDiff += `${combinedDiff ? "\n" : ""}${diffResult.diff}`;
165
+ combinedPatch += `${combinedPatch ? "\n" : ""}${generateUnifiedPatch(result.path, result.before, result.after)}`;
166
+ firstChangedLine ??= diffResult.firstChangedLine;
167
+ outputs.push(formatCompactHashlineEditResult(snapshot, diffResult, blockMessages(result)));
195
168
  }
196
- throwIfAborted();
197
- // Read the file.
198
- const buffer = await ops.readFile(absolutePath);
199
- const rawContent = buffer.toString("utf-8");
200
- throwIfAborted();
201
- // Strip BOM before matching. The model will not include an invisible BOM in oldText.
202
- const { bom, text: content } = stripBom(rawContent);
203
- const originalEnding = detectLineEnding(content);
204
- const normalizedContent = normalizeToLF(content);
205
- const { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);
206
- throwIfAborted();
207
- const finalContent = bom + restoreLineEndings(newContent, originalEnding);
208
- await ops.writeFile(absolutePath, finalContent);
209
- throwIfAborted();
210
- const diffResult = generateDiffString(baseContent, newContent);
211
- const patch = generateUnifiedPatch(path, baseContent, newContent);
212
- return {
213
- content: [
214
- {
215
- type: "text",
216
- text: `Successfully replaced ${edits.length} block(s) in ${path}.`,
217
- },
218
- ],
219
- details: { diff: diffResult.diff, patch, firstChangedLine: diffResult.firstChangedLine },
220
- };
169
+ return { content: [{ type: "text", text: outputs.join("\n\n") }], details: { diff: combinedDiff, patch: combinedPatch, firstChangedLine } };
221
170
  });
222
171
  },
223
172
  renderCall(args, theme, context) {
224
- const component = getEditCallRenderComponent(context.state, context.lastComponent);
225
- const previewInput = getRenderablePreviewInput(args);
226
- const argsKey = previewInput
227
- ? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })
228
- : undefined;
229
- if (component.previewArgsKey !== argsKey) {
230
- component.preview = undefined;
231
- component.previewArgsKey = argsKey;
232
- component.previewPending = false;
233
- component.settledError = false;
234
- }
235
- if (context.argsComplete && previewInput && !component.preview && !component.previewPending) {
236
- component.previewPending = true;
237
- const requestKey = argsKey;
238
- void computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then((preview) => {
239
- if (component.previewArgsKey === requestKey) {
240
- setEditPreview(component, preview, requestKey);
241
- context.invalidate();
242
- }
243
- });
244
- }
245
- return buildEditCallComponent(component, args, theme, context.cwd);
173
+ const text = context.lastComponent ?? new Text("", 0, 0);
174
+ text.setText(formatEditCall(args, theme, context.cwd));
175
+ return text;
246
176
  },
247
177
  renderResult(result, _options, theme, context) {
248
- const callComponent = context.state.callComponent;
249
- const previewInput = getRenderablePreviewInput(context.args);
250
- const argsKey = previewInput
251
- ? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })
252
- : undefined;
253
- const typedResult = result;
254
- const resultDiff = !context.isError ? typedResult.details?.diff : undefined;
255
- let changed = false;
256
- if (callComponent) {
257
- if (typeof resultDiff === "string") {
258
- changed =
259
- setEditPreview(callComponent, { diff: resultDiff, firstChangedLine: typedResult.details?.firstChangedLine }, argsKey) || changed;
260
- }
261
- if (callComponent.settledError !== context.isError) {
262
- callComponent.settledError = context.isError;
263
- changed = true;
264
- }
265
- if (changed) {
266
- buildEditCallComponent(callComponent, context.args, theme, context.cwd);
267
- }
268
- }
269
- const output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);
178
+ const output = formatEditResult(result, theme, context.isError);
270
179
  const component = context.lastComponent ?? new Container();
271
180
  component.clear();
272
- if (!output) {
181
+ if (!output)
273
182
  return component;
274
- }
275
183
  component.addChild(new Spacer(1));
276
184
  component.addChild(new Text(output, 1, 0));
277
185
  return component;