@oh-my-pi/pi-coding-agent 17.2.4 → 17.2.6

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 (348) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/{CHANGELOG-bpmhv26t.md → CHANGELOG-gs76k6wc.md} +69 -0
  3. package/dist/cli.js +4438 -3962
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/cli/args.d.ts +4 -1
  6. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  7. package/dist/types/cli/command-help.d.ts +99 -0
  8. package/dist/types/cli/gc-cli.d.ts +1 -0
  9. package/dist/types/cli/help-extra.d.ts +2 -0
  10. package/dist/types/cli/thinking-levels.d.ts +5 -0
  11. package/dist/types/cli/update-cli.d.ts +13 -0
  12. package/dist/types/commands/browser-relay.d.ts +38 -0
  13. package/dist/types/commands/commit.d.ts +3 -0
  14. package/dist/types/commands/complete.d.ts +1 -1
  15. package/dist/types/commands/completions.d.ts +6 -0
  16. package/dist/types/commands/grep.d.ts +3 -0
  17. package/dist/types/commands/join.d.ts +4 -0
  18. package/dist/types/commands/launch-help.d.ts +171 -0
  19. package/dist/types/commands/launch.d.ts +5 -1
  20. package/dist/types/commands/models.d.ts +3 -0
  21. package/dist/types/commands/say.d.ts +10 -0
  22. package/dist/types/commands/worktree.d.ts +3 -0
  23. package/dist/types/config/keybindings.d.ts +5 -0
  24. package/dist/types/config/model-discovery.d.ts +2 -2
  25. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  26. package/dist/types/config/models-config-schema.d.ts +2 -0
  27. package/dist/types/config/models-config.d.ts +1 -0
  28. package/dist/types/config/service-tier.d.ts +2 -0
  29. package/dist/types/config/settings-schema.d.ts +69 -21
  30. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  31. package/dist/types/cursor.d.ts +7 -4
  32. package/dist/types/edit/index.d.ts +3 -3
  33. package/dist/types/edit/modes/replace.d.ts +22 -15
  34. package/dist/types/edit/renderer.d.ts +1 -1
  35. package/dist/types/eval/py/kernel.d.ts +3 -1
  36. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  37. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  38. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  39. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  40. package/dist/types/launch/broker.d.ts +1 -1
  41. package/dist/types/launch/client.d.ts +15 -4
  42. package/dist/types/launch/ensure.d.ts +8 -0
  43. package/dist/types/launch/paths.d.ts +1 -1
  44. package/dist/types/launch/protocol.d.ts +19 -2
  45. package/dist/types/lsp/client.d.ts +2 -0
  46. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  47. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  48. package/dist/types/lsp/mux/server.d.ts +18 -0
  49. package/dist/types/lsp/types.d.ts +22 -2
  50. package/dist/types/main.d.ts +2 -0
  51. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  52. package/dist/types/mnemopi/state.d.ts +0 -23
  53. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  54. package/dist/types/modes/components/btw-panel.d.ts +3 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  56. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  57. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  58. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  59. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  60. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  61. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/types.d.ts +4 -1
  64. package/dist/types/modes/workflow.d.ts +2 -1
  65. package/dist/types/sdk.d.ts +3 -1
  66. package/dist/types/secrets/index.d.ts +1 -1
  67. package/dist/types/security/contracts/schemas.d.ts +405 -403
  68. package/dist/types/session/agent-session-types.d.ts +8 -1
  69. package/dist/types/session/agent-session.d.ts +26 -2
  70. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  71. package/dist/types/session/launch-completion.d.ts +10 -0
  72. package/dist/types/session/session-advisors.d.ts +1 -1
  73. package/dist/types/session/session-context.d.ts +2 -1
  74. package/dist/types/session/session-entries.d.ts +15 -1
  75. package/dist/types/session/session-maintenance.d.ts +1 -0
  76. package/dist/types/session/session-manager.d.ts +23 -11
  77. package/dist/types/session/session-stats.d.ts +10 -0
  78. package/dist/types/session/session-storage.d.ts +15 -3
  79. package/dist/types/session/yield-queue.d.ts +6 -1
  80. package/dist/types/system-prompt.d.ts +4 -1
  81. package/dist/types/task/index.d.ts +2 -1
  82. package/dist/types/task/spawn-policy.d.ts +5 -0
  83. package/dist/types/thinking.d.ts +1 -7
  84. package/dist/types/tools/ast-grep.d.ts +1 -1
  85. package/dist/types/tools/browser/registry.d.ts +2 -1
  86. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  87. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  88. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  89. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  90. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  91. package/dist/types/tools/browser/render.d.ts +1 -0
  92. package/dist/types/tools/browser.d.ts +3 -0
  93. package/dist/types/tools/computer/exposure.d.ts +9 -7
  94. package/dist/types/tools/computer/protocol.d.ts +70 -17
  95. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  96. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  97. package/dist/types/tools/computer/worker.d.ts +42 -7
  98. package/dist/types/tools/computer-renderer.d.ts +7 -5
  99. package/dist/types/tools/computer.d.ts +25 -44
  100. package/dist/types/tools/context.d.ts +3 -0
  101. package/dist/types/tools/glob.d.ts +1 -1
  102. package/dist/types/tools/grep.d.ts +1 -1
  103. package/dist/types/tools/index.d.ts +9 -0
  104. package/dist/types/tools/render-utils.d.ts +2 -0
  105. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  106. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  107. package/dist/types/tools/xdev.d.ts +8 -0
  108. package/dist/types/utils/clipboard.d.ts +2 -2
  109. package/dist/types/web/kagi.d.ts +1 -0
  110. package/dist/types/web/search/index.d.ts +1 -1
  111. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  112. package/dist/types/web/search/providers/base.d.ts +2 -0
  113. package/dist/types/web/search/providers/brave.d.ts +1 -0
  114. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  115. package/dist/types/web/search/providers/codex.d.ts +1 -0
  116. package/dist/types/web/search/providers/exa.d.ts +1 -0
  117. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  118. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  119. package/dist/types/web/search/providers/jina.d.ts +1 -0
  120. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  121. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  122. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  123. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  124. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  125. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  126. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  127. package/dist/types/web/search/providers/utils.d.ts +6 -9
  128. package/dist/types/web/search/providers/zai.d.ts +1 -0
  129. package/dist/types/web/search/types.d.ts +4 -0
  130. package/package.json +12 -12
  131. package/src/cli/args.ts +7 -87
  132. package/src/cli/browser-relay-cli.ts +119 -0
  133. package/src/cli/command-help.ts +103 -0
  134. package/src/cli/completion-gen.ts +5 -5
  135. package/src/cli/flag-tables.ts +9 -0
  136. package/src/cli/gc-cli.ts +641 -21
  137. package/src/cli/help-extra.ts +89 -0
  138. package/src/cli/setup-cli.ts +13 -21
  139. package/src/cli/thinking-levels.ts +7 -0
  140. package/src/cli/update-cli.ts +26 -7
  141. package/src/cli-commands.ts +170 -33
  142. package/src/cli.ts +17 -5
  143. package/src/commands/acp.ts +3 -1
  144. package/src/commands/agents.ts +3 -2
  145. package/src/commands/auth-broker.ts +3 -2
  146. package/src/commands/auth-gateway.ts +3 -2
  147. package/src/commands/bench.ts +2 -3
  148. package/src/commands/browser-relay.ts +53 -0
  149. package/src/commands/cleanse.ts +2 -2
  150. package/src/commands/commit.ts +3 -2
  151. package/src/commands/complete.ts +2 -1
  152. package/src/commands/completions.ts +3 -2
  153. package/src/commands/config.ts +3 -2
  154. package/src/commands/dry-balance.ts +2 -2
  155. package/src/commands/gallery.ts +3 -2
  156. package/src/commands/gc.ts +3 -2
  157. package/src/commands/grep.ts +3 -2
  158. package/src/commands/grievances.ts +3 -2
  159. package/src/commands/install.ts +2 -2
  160. package/src/commands/join.ts +3 -2
  161. package/src/commands/launch-help.ts +116 -0
  162. package/src/commands/launch.ts +7 -191
  163. package/src/commands/models.ts +3 -2
  164. package/src/commands/plugin.ts +3 -2
  165. package/src/commands/read.ts +3 -2
  166. package/src/commands/say.ts +3 -2
  167. package/src/commands/setup.ts +3 -2
  168. package/src/commands/shell.ts +3 -2
  169. package/src/commands/ssh.ts +3 -2
  170. package/src/commands/stats.ts +3 -2
  171. package/src/commands/tiny-models.ts +2 -2
  172. package/src/commands/token.ts +2 -2
  173. package/src/commands/ttsr.ts +2 -2
  174. package/src/commands/update.ts +3 -2
  175. package/src/commands/usage.ts +3 -2
  176. package/src/commands/web-search.ts +3 -2
  177. package/src/commands/worktree.ts +3 -2
  178. package/src/config/keybindings.ts +5 -0
  179. package/src/config/model-discovery.ts +44 -27
  180. package/src/config/model-registry.ts +19 -13
  181. package/src/config/models-config-schema-bundle.ts +9 -0
  182. package/src/config/resolve-config-value.ts +2 -1
  183. package/src/config/service-tier.ts +5 -0
  184. package/src/config/settings-schema.ts +72 -20
  185. package/src/config/settings.ts +1 -1
  186. package/src/cursor-bridge-tools.ts +1 -1
  187. package/src/cursor.ts +10 -14
  188. package/src/edit/diff.ts +17 -8
  189. package/src/edit/index.ts +20 -7
  190. package/src/edit/modes/replace.ts +93 -40
  191. package/src/edit/renderer.ts +1 -1
  192. package/src/edit/streaming.ts +14 -17
  193. package/src/eval/py/kernel.ts +2 -1
  194. package/src/eval/py/spawn-options.ts +24 -29
  195. package/src/export/html/index.ts +10 -3
  196. package/src/export/html/template.js +8 -26
  197. package/src/export/share.ts +4 -0
  198. package/src/extensibility/extensions/wrapper.ts +1 -0
  199. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  200. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  201. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  202. package/src/extensibility/skills.ts +11 -0
  203. package/src/extensibility/utils.ts +10 -2
  204. package/src/launch/broker.ts +223 -7
  205. package/src/launch/client.ts +192 -21
  206. package/src/launch/ensure.ts +73 -0
  207. package/src/launch/paths.ts +1 -1
  208. package/src/launch/protocol.ts +54 -2
  209. package/src/lsp/client.ts +23 -2
  210. package/src/lsp/index.ts +8 -0
  211. package/src/lsp/mux/daemon.ts +348 -0
  212. package/src/lsp/mux/protocol.ts +96 -0
  213. package/src/lsp/mux/server.ts +797 -0
  214. package/src/lsp/types.ts +28 -2
  215. package/src/main.ts +20 -8
  216. package/src/mcp/config-writer.ts +1 -1
  217. package/src/mcp/oauth-flow.ts +11 -1
  218. package/src/mnemopi/embed-client.ts +49 -2
  219. package/src/mnemopi/state.ts +57 -23
  220. package/src/modes/components/assistant-message.ts +11 -0
  221. package/src/modes/components/btw-panel.ts +41 -4
  222. package/src/modes/components/chat-transcript-builder.ts +4 -0
  223. package/src/modes/components/custom-editor.ts +9 -0
  224. package/src/modes/components/read-tool-group.ts +10 -0
  225. package/src/modes/components/status-line/component.ts +1 -1
  226. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  227. package/src/modes/components/tool-execution.ts +8 -0
  228. package/src/modes/components/tree-selector.ts +46 -48
  229. package/src/modes/controllers/btw-controller.ts +55 -7
  230. package/src/modes/controllers/command-controller.ts +31 -0
  231. package/src/modes/controllers/event-controller.ts +17 -0
  232. package/src/modes/controllers/input-controller.ts +63 -8
  233. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  234. package/src/modes/controllers/selector-controller.ts +20 -0
  235. package/src/modes/interactive-mode.ts +18 -2
  236. package/src/modes/types.ts +9 -1
  237. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  238. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  239. package/src/modes/utils/ui-helpers.ts +6 -10
  240. package/src/modes/workflow.ts +8 -2
  241. package/src/prompts/agents/init.md +1 -1
  242. package/src/prompts/session/launch-completion.md +1 -0
  243. package/src/prompts/system/plan-mode-active.md +2 -2
  244. package/src/prompts/system/system-prompt.md +26 -52
  245. package/src/prompts/system/workflow-notice.md +3 -3
  246. package/src/prompts/tools/ast-grep.md +1 -1
  247. package/src/prompts/tools/bash.md +1 -1
  248. package/src/prompts/tools/browser.md +1 -0
  249. package/src/prompts/tools/computer.md +21 -21
  250. package/src/prompts/tools/glob.md +1 -1
  251. package/src/prompts/tools/grep.md +1 -1
  252. package/src/prompts/tools/replace.md +5 -4
  253. package/src/prompts/tools/task.md +4 -4
  254. package/src/sdk.ts +49 -6
  255. package/src/secrets/index.ts +1 -1
  256. package/src/security/contracts/schemas.ts +205 -183
  257. package/src/security/contracts/validation.ts +5 -1
  258. package/src/security/store.ts +2 -2
  259. package/src/session/agent-session-types.ts +9 -1
  260. package/src/session/agent-session.ts +204 -15
  261. package/src/session/indexed-session-storage.ts +9 -0
  262. package/src/session/launch-completion.ts +37 -0
  263. package/src/session/prewalk.ts +24 -2
  264. package/src/session/session-advisors.ts +2 -2
  265. package/src/session/session-context.ts +42 -12
  266. package/src/session/session-dump-format.ts +1 -1
  267. package/src/session/session-entries.ts +17 -1
  268. package/src/session/session-maintenance.ts +38 -4
  269. package/src/session/session-manager.ts +123 -67
  270. package/src/session/session-paths.ts +118 -55
  271. package/src/session/session-stats.ts +48 -3
  272. package/src/session/session-storage.ts +30 -22
  273. package/src/session/stream-guards.ts +1 -1
  274. package/src/session/yield-queue.ts +121 -16
  275. package/src/slash-commands/builtin-registry.ts +37 -24
  276. package/src/system-prompt.ts +8 -3
  277. package/src/task/index.ts +23 -8
  278. package/src/task/spawn-policy.ts +14 -0
  279. package/src/thinking.ts +2 -8
  280. package/src/tools/ast-grep.ts +10 -4
  281. package/src/tools/browser/cmux/cmux-tab.ts +97 -14
  282. package/src/tools/browser/cmux/rpc.ts +2 -8
  283. package/src/tools/browser/registry.ts +53 -2
  284. package/src/tools/browser/relay/bridge.ts +945 -0
  285. package/src/tools/browser/relay/daemon.ts +117 -0
  286. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  287. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  288. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  289. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  290. package/src/tools/browser/relay/kind.ts +41 -0
  291. package/src/tools/browser/relay/protocol.ts +54 -0
  292. package/src/tools/browser/relay/server.ts +141 -0
  293. package/src/tools/browser/render.ts +4 -1
  294. package/src/tools/browser/shared-daemon.ts +7 -58
  295. package/src/tools/browser/tab-supervisor.ts +5 -4
  296. package/src/tools/browser/tab-worker.ts +33 -12
  297. package/src/tools/browser.ts +26 -1
  298. package/src/tools/computer/exposure.ts +11 -35
  299. package/src/tools/computer/protocol.ts +51 -10
  300. package/src/tools/computer/supervisor.ts +227 -124
  301. package/src/tools/computer/worker-entry.ts +11 -6
  302. package/src/tools/computer/worker.ts +695 -81
  303. package/src/tools/computer-renderer.ts +104 -65
  304. package/src/tools/computer.ts +134 -428
  305. package/src/tools/context.ts +3 -0
  306. package/src/tools/glob.ts +9 -2
  307. package/src/tools/grep.ts +12 -6
  308. package/src/tools/hub/launch.ts +122 -6
  309. package/src/tools/index.ts +9 -0
  310. package/src/tools/inspect-image.ts +32 -6
  311. package/src/tools/render-utils.ts +2 -0
  312. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  313. package/src/tools/tool-timeouts.ts +1 -0
  314. package/src/tools/xdev.ts +29 -2
  315. package/src/utils/clipboard.ts +40 -14
  316. package/src/utils/command-args.ts +26 -28
  317. package/src/utils/tool-choice.ts +0 -13
  318. package/src/web/kagi.ts +2 -1
  319. package/src/web/search/index.ts +18 -2
  320. package/src/web/search/providers/anthropic.ts +4 -1
  321. package/src/web/search/providers/base.ts +2 -0
  322. package/src/web/search/providers/brave.ts +3 -1
  323. package/src/web/search/providers/browser-page.ts +6 -4
  324. package/src/web/search/providers/codex.ts +4 -1
  325. package/src/web/search/providers/duckduckgo.ts +2 -1
  326. package/src/web/search/providers/ecosia.ts +2 -1
  327. package/src/web/search/providers/exa.ts +4 -2
  328. package/src/web/search/providers/firecrawl.ts +3 -1
  329. package/src/web/search/providers/gemini.ts +8 -2
  330. package/src/web/search/providers/google.ts +2 -1
  331. package/src/web/search/providers/jina.ts +12 -2
  332. package/src/web/search/providers/kagi.ts +3 -0
  333. package/src/web/search/providers/kimi.ts +5 -1
  334. package/src/web/search/providers/mojeek.ts +2 -1
  335. package/src/web/search/providers/parallel.ts +5 -1
  336. package/src/web/search/providers/perplexity.ts +6 -3
  337. package/src/web/search/providers/public.ts +1 -1
  338. package/src/web/search/providers/searxng.ts +11 -5
  339. package/src/web/search/providers/startpage.ts +10 -4
  340. package/src/web/search/providers/synthetic.ts +11 -5
  341. package/src/web/search/providers/tavily.ts +3 -1
  342. package/src/web/search/providers/tinyfish.ts +3 -1
  343. package/src/web/search/providers/utils.ts +12 -10
  344. package/src/web/search/providers/xai.ts +2 -2
  345. package/src/web/search/providers/zai.ts +18 -3
  346. package/src/web/search/types.ts +6 -0
  347. package/dist/types/config/file-lock.d.ts +0 -29
  348. package/src/config/file-lock.ts +0 -164
@@ -39,6 +39,8 @@ export interface SessionHeader {
39
39
  */
40
40
  additionalDirectories?: string[];
41
41
  parentSession?: string;
42
+ /** Prior absolute JSONL locations recorded by successful session moves. */
43
+ previousSessionFiles?: string[];
42
44
  /** Provider prompt-cache identity inherited by exact-route full forks. */
43
45
  providerPromptCacheKey?: string;
44
46
  }
@@ -119,6 +121,18 @@ export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
119
121
  fromExtension?: boolean;
120
122
  }
121
123
 
124
+ /**
125
+ * Pure marker entry recorded by `/reset` (resetSessionContext). It carries no
126
+ * payload — its presence on the branch is a durable boundary the collapsed
127
+ * live transcript and the model-context rebuild start emission after, so a
128
+ * rebuild (theme change, focus attach, /shake, resume) does not resurrect the
129
+ * pre-reset conversation. The on-disk record and the plain `transcript:true`
130
+ * export path keep the full pre-reset history.
131
+ */
132
+ export interface ResetBoundaryEntry extends SessionEntryBase {
133
+ type: "reset_boundary";
134
+ }
135
+
122
136
  /**
123
137
  * Custom entry for extensions to store extension-specific data in the session.
124
138
  * Use customType to identify your extension's entries.
@@ -155,6 +169,7 @@ declare module "@oh-my-pi/pi-agent-core/compaction/entries" {
155
169
  interface CustomCompactionSessionEntries {
156
170
  titleChange: TitleChangeEntry;
157
171
  credentialPin: CredentialPinEntry;
172
+ resetBoundary: ResetBoundaryEntry;
158
173
  }
159
174
  }
160
175
 
@@ -250,7 +265,8 @@ export type SessionEntry =
250
265
  | TtsrInjectionEntry
251
266
  | SessionInitEntry
252
267
  | ModeChangeEntry
253
- | CredentialPinEntry;
268
+ | CredentialPinEntry
269
+ | ResetBoundaryEntry;
254
270
 
255
271
  /** Raw logical file entry after loaders strip any fixed-width title slot. */
256
272
  export type FileEntry = SessionHeader | SessionEntry;
@@ -26,8 +26,10 @@ import {
26
26
  DEFAULT_SHAKE_CONFIG,
27
27
  effectiveReserveTokens,
28
28
  estimateTokens,
29
+ hasContextTokenUsage,
29
30
  NativeCompactionError,
30
31
  prepareCompaction,
32
+ RESCUE_SHAKE_CONFIG,
31
33
  resolveBudgetReserveTokens,
32
34
  resolveThresholdTokens,
33
35
  type ShakeConfig,
@@ -74,7 +76,7 @@ import {
74
76
  resolveRoleModelFull,
75
77
  } from "./role-models";
76
78
  import type { SessionContext } from "./session-context";
77
- import { getLatestCompactionEntry } from "./session-context";
79
+ import { getLatestCompactionEntry, getOpenAiRemoteCompactionPayload } from "./session-context";
78
80
  import type { CompactionEntry, SessionEntry } from "./session-entries";
79
81
  import type { SessionManager } from "./session-manager";
80
82
  import type { ShakeMode, ShakeResult } from "./shake-types";
@@ -235,6 +237,7 @@ export interface SessionMaintenanceHost {
235
237
  syncTodoPhasesFromBranch(): void;
236
238
  resetAdvisorRuntimes(): void;
237
239
  rebaseAfterCompaction(): void;
240
+ recordAnchoredHistoryRewrite(tokensRemoved: number): void;
238
241
  getContextBreakdown(options?: {
239
242
  contextWindow?: number;
240
243
  pendingMessages?: AgentMessage[];
@@ -461,11 +464,12 @@ export class SessionMaintenance {
461
464
  }
462
465
 
463
466
  const branchEntries = this.#host.sessionManager.getBranch();
467
+ const latestCompaction = getLatestCompactionEntry(branchEntries);
464
468
  const config = this.#withPlanProtection({
465
469
  ...(opts.config ?? AGGRESSIVE_SHAKE_CONFIG),
466
470
  // Skip entries summarized away by the latest compaction — shaking them
467
471
  // only churns persisted history with no prompt/cache effect.
468
- keepBoundaryId: getLatestCompactionEntry(branchEntries)?.firstKeptEntryId,
472
+ keepBoundaryId: latestCompaction?.firstKeptEntryId,
469
473
  });
470
474
  const regions = collectShakeRegions(branchEntries, config);
471
475
  if (regions.length === 0) {
@@ -475,20 +479,50 @@ export class SessionMaintenance {
475
479
  const artifactId = await this.#saveShakeArtifact(regions);
476
480
  const replacements = regions.map((region, index) => this.#shakeElidePlaceholder(region, index, artifactId));
477
481
 
482
+ const hasRemoteReplacementHistory = getOpenAiRemoteCompactionPayload(latestCompaction) !== undefined;
483
+ const compactionIndex = latestCompaction ? branchEntries.lastIndexOf(latestCompaction) : -1;
484
+ let anchorIndex = -1;
485
+ for (let index = branchEntries.length - 1; index > compactionIndex; index--) {
486
+ const entry = branchEntries[index];
487
+ if (entry.type !== "message" || entry.message.role !== "assistant") continue;
488
+ const assistant = entry.message;
489
+ if (
490
+ assistant.stopReason !== "aborted" &&
491
+ assistant.stopReason !== "error" &&
492
+ assistant.usage &&
493
+ hasContextTokenUsage(assistant.usage)
494
+ ) {
495
+ anchorIndex = index;
496
+ break;
497
+ }
498
+ }
499
+ const entryIndexes = new Map(branchEntries.map((entry, index) => [entry, index]));
500
+
478
501
  let toolResultsDropped = 0;
479
502
  let blocksDropped = 0;
480
503
  let originalTokens = 0;
481
504
  let replacementTokens = 0;
505
+ let anchoredTokensRemoved = 0;
482
506
  const items = regions.map((region, index) => {
483
507
  if (region.kind === "toolResult") toolResultsDropped++;
484
508
  else blocksDropped++;
485
509
  originalTokens += region.tokens;
486
510
  const replacement = replacements[index];
487
- if (replacement.length > 0) replacementTokens += countTokens(replacement);
511
+ const replacementTokenCount = replacement.length > 0 ? countTokens(replacement) : 0;
512
+ replacementTokens += replacementTokenCount;
513
+ const entryIndex = entryIndexes.get(region.entry) ?? -1;
514
+ if (
515
+ entryIndex >= 0 &&
516
+ entryIndex < anchorIndex &&
517
+ (!hasRemoteReplacementHistory || entryIndex > compactionIndex)
518
+ ) {
519
+ anchoredTokensRemoved += Math.max(0, region.tokens - replacementTokenCount);
520
+ }
488
521
  return { region, replacement };
489
522
  });
490
523
 
491
524
  applyShakeRegions(items);
525
+ this.#host.recordAnchoredHistoryRewrite(anchoredTokensRemoved);
492
526
 
493
527
  await this.#host.sessionManager.rewriteEntries();
494
528
  const sessionContext = this.#host.buildDisplaySessionContext();
@@ -1859,7 +1893,7 @@ export class SessionMaintenance {
1859
1893
  let elideSink = "placeholders";
1860
1894
  if (!options.skipElide) {
1861
1895
  try {
1862
- const result = await this.#host.shake("elide", { signal });
1896
+ const result = await this.#host.shake("elide", { config: RESCUE_SHAKE_CONFIG, signal });
1863
1897
  elided = result.toolResultsDropped + result.blocksDropped;
1864
1898
  elidedTokens = result.tokensFreed;
1865
1899
  if (result.artifactId) elideSink = "an artifact";
@@ -44,6 +44,7 @@ import {
44
44
  type ModeChangeEntry,
45
45
  type ModelChangeEntry,
46
46
  type NewSessionOptions,
47
+ type ResetBoundaryEntry,
47
48
  type ServiceTierChangeEntry,
48
49
  type SessionEntry,
49
50
  type SessionHeader,
@@ -405,17 +406,22 @@ export class SessionPersistenceIndeterminateError extends AggregateError {
405
406
  * tree by `(id, parentId)`, and the mutable leaf pointer selects which path is
406
407
  * active for future appends and for LLM context construction.
407
408
  *
408
- * Durability is software-crash safe but not power-loss safe: appends are normally
409
- * handed to the OS synchronously in-body but never `fsync`'d. While an in-place
410
- * atomic rewrite publishes, a synchronous `flushSync` still persists fenced
411
- * appends by superseding the pending publish against the same (stable) path.
412
- * A {@link moveTo} relocation is the one exception: the session file is being
413
- * renamed to a new path, so appends landing in the rename window are held in
414
- * memory and persisted only by the move's trailing rewrite. A `flushSync` in
415
- * that narrow window cannot durably place them writing the vacated source
416
- * would recreate the orphan the move eliminates, and a queued backend publish
417
- * (fire-and-forget on `IndexedSessionStorage`) could land after the rename all
418
- * the same. This gap is inherent to closing the writer for a Windows-safe rename.
409
+ * Durability is software-crash safe but not power-loss safe: completed entries
410
+ * (user/assistant/toolResult messages, tool_execution_start markers, custom
411
+ * entries) are handed to the OS synchronously in-body on append and never
412
+ * `fsync`'d. In-flight streaming text is intentionally not durable until
413
+ * `message_end` persists the finished message.
414
+ *
415
+ * While an in-place atomic rewrite is publishing, a concurrent completed append
416
+ * supersedes that publish with a synchronous full-body rewrite so the entry is
417
+ * software-crash durable before the append returns; the abandoned atomic's
418
+ * `commitGuard` then refuses to clobber the fresher body.
419
+ *
420
+ * During {@link moveTo}, appends write a full body to the live relocation path
421
+ * (source until rename, destination once the rename has landed) so a crash mid-
422
+ * move still preserves completed entries without recreating a vacated source.
423
+ * A trailing atomic rewrite still rewrites the header cwd after the path is
424
+ * repointed.
419
425
  */
420
426
  export class SessionManager {
421
427
  #cwd: string;
@@ -484,15 +490,12 @@ export class SessionManager {
484
490
  /** Set by synchronous appends that land while an atomic replacement is active. */
485
491
  #atomicRewriteDirty = false;
486
492
  /**
487
- * True while {@link moveTo} is renaming the session file but has not yet
488
- * repointed `#sessionFile`. Both the synchronous append hot path
489
- * ({@link #appendToSessionFile}) and synchronous rewrites
490
- * ({@link #rewriteSynchronously}, reached via `flushSync` on Ctrl+C) would
491
- * otherwise touch the pre-rename path — opening a fresh writer or writing the
492
- * full body — recreating the very orphan the move eliminates. Both defer
493
- * while this is set; the move's trailing atomic rewrite persists them.
493
+ * Active {@link moveTo} relocation. Concurrent completed appends write a
494
+ * full body to the live path: source while it still exists, destination
495
+ * once rename has landed (source gone). Never recreates a vacated source.
496
+ * `null` outside an active relocation.
494
497
  */
495
- #sessionFileRelocating = false;
498
+ #sessionFileRelocating: { source: string; dest: string } | null = null;
496
499
  /** Atomic entry batch currently staged for a full-file commit. */
497
500
  #atomicEntryBatch: AtomicEntryBatch | undefined;
498
501
 
@@ -763,30 +766,56 @@ export class SessionManager {
763
766
  return this.#forceFileCreation || this.#fileIsCurrent || this.#historyContainsAssistantMessage();
764
767
  }
765
768
 
769
+ /**
770
+ * Live path for concurrent completed appends during {@link moveTo}.
771
+ * Prefers destination once rename has landed (source gone); otherwise
772
+ * source. Never invents a path that does not already exist.
773
+ */
774
+ #liveRelocationWritePath(): string | null {
775
+ const relocating = this.#sessionFileRelocating;
776
+ if (!relocating) return null;
777
+ if (this.#storage.existsSync(relocating.dest)) return relocating.dest;
778
+ if (this.#storage.existsSync(relocating.source)) return relocating.source;
779
+ // Rename in flight with neither path visible (rare cross-device edge):
780
+ // fall back to destination so we do not recreate a vacated source.
781
+ return relocating.dest;
782
+ }
783
+
766
784
  /**
767
785
  * Synchronously rewrite the whole file (header + entries) and keep no open
768
786
  * writer; the next append re-opens one. `writeTextSync` returns with the
769
787
  * bytes in the kernel page cache, so the file is software-crash durable.
788
+ *
789
+ * During {@link moveTo}, writes to the live relocation path (source pre-
790
+ * rename, destination post-rename) rather than always `#sessionFile`, so
791
+ * concurrent completed entries are durable without recreating a vacated source.
770
792
  */
771
793
  #rewriteSynchronously(): void {
772
- if (!this.#persist || !this.#sessionFile || !this.#shouldHaveSessionFile()) return;
773
- // A move is renaming the file out from under `#sessionFile`. Writing the
774
- // full body now would target either the vacated source (recreating the
775
- // orphan the move eliminates) or a path a queued backend publish may clobber
776
- // post-rename, so defer to moveTo's trailing atomic rewrite. This narrows
777
- // flushSync durability for entries in the rename window (see class doc).
778
- if (this.#sessionFileRelocating) return;
794
+ if (!this.#persist || !this.#shouldHaveSessionFile()) return;
795
+ const targetPath = this.#liveRelocationWritePath() ?? this.#sessionFile;
796
+ if (!targetPath) return;
779
797
 
780
798
  try {
781
799
  const body = this.#fileBody();
782
800
  this.#diskEpoch++;
783
801
  this.#diskTail = Promise.resolve();
784
802
  this.#closeWriterEventually();
785
- this.#storage.writeTextSync(this.#sessionFile, body);
786
- this.#fileIsCurrent = true;
787
- this.#materializeBreadcrumb();
788
- this.#rewriteRequired = false;
789
- this.#hasTitleSlot = true;
803
+ this.#storage.writeTextSync(targetPath, body);
804
+ // Only mark the manager current when writing the active session path.
805
+ // Mid-move writes update the live relocation path; `#sessionFile` is
806
+ // still the pre-repoint source until moveTo repoints it.
807
+ if (!this.#sessionFileRelocating || targetPath === this.#sessionFile) {
808
+ this.#fileIsCurrent = true;
809
+ this.#materializeBreadcrumb();
810
+ this.#rewriteRequired = false;
811
+ this.#hasTitleSlot = true;
812
+ } else {
813
+ // Destination body is current on disk; in-memory still needs a
814
+ // header-cwd rewrite after repoint, but entries are durable.
815
+ this.#fileIsCurrent = false;
816
+ this.#rewriteRequired = true;
817
+ this.#hasTitleSlot = true;
818
+ }
790
819
  } catch (err) {
791
820
  this.#noteDiskFailure(err);
792
821
  }
@@ -872,22 +901,21 @@ export class SessionManager {
872
901
  return;
873
902
  }
874
903
 
875
- // Atomic replacement window: the old path may be moved aside underneath
876
- // any newly-opened append handle (Windows EPERM fallback), or a `moveTo`
877
- // may be renaming the session file to a new directory. Do not open a
878
- // writer here; the active rewrite loops and serializes a fresh full body,
879
- // and the move's trailing atomic rewrite folds in these deferred entries.
880
- // A superseding synchronous rewrite bumps `#diskEpoch`, at which point
881
- // the pending atomic publish is guaranteed to abandon via its
882
- // `commitGuard`, so appends can (and must) take the hot path so they
883
- // don't strand in memory while `close()` returns without a rewrite.
884
- if (
885
- this.#sessionFileRelocating ||
886
- (this.#atomicRewriteFenceEpoch !== null && this.#atomicRewriteFenceEpoch === this.#diskEpoch)
887
- ) {
888
- this.#fileIsCurrent = false;
889
- this.#rewriteRequired = true;
904
+ // Atomic replacement / move window: do not open a fresh append writer that
905
+ // a Windows EPERM replace could detach from the current JSONL path.
906
+ // - moveTo: write a full body to the live relocation path (source pre-
907
+ // rename, destination post-rename) so completed entries are durable
908
+ // without recreating a vacated source.
909
+ // - in-place atomic fence: supersede the pending publish with a
910
+ // synchronous full-body rewrite; bumping `#diskEpoch` abandons the
911
+ // in-flight atomic via its `commitGuard`.
912
+ if (this.#sessionFileRelocating) {
913
+ this.#rewriteSynchronously();
914
+ return;
915
+ }
916
+ if (this.#atomicRewriteFenceEpoch !== null && this.#atomicRewriteFenceEpoch === this.#diskEpoch) {
890
917
  this.#atomicRewriteDirty = true;
918
+ this.#rewriteSynchronously();
891
919
  return;
892
920
  }
893
921
  // Cold/divergent: not on disk yet, or in-memory entries diverged from the
@@ -897,15 +925,23 @@ export class SessionManager {
897
925
  return;
898
926
  }
899
927
 
900
- // Hot path: queue the entry directly on the writer, outside the async disk
901
- // chain. File writers batch same-turn appends until their microtask
902
- // boundary; flush(), flushSync(), and close() drain that batch explicitly.
928
+ // Hot path: write the entry directly on the writer, outside the async disk
929
+ // chain. Prefer appendSync so write failures latch `#diskFailure` before
930
+ // this call returns (not via a discarded rejected Promise after a later
931
+ // microtask). Callers stay non-throwing here — the core turn loop invokes
932
+ // appendMessage/appendCustomEntry without try/catch; flushSync/close and
933
+ // subsequent appends still throw the latched error. File writers apply
934
+ // each line to the OS page cache before return.
903
935
  // A mid-close writer leaves `#writer` undefined, so `#appendWriter` simply
904
936
  // opens a fresh append handle and the entry still lands.
905
937
  try {
906
- void this.#appendWriter()
907
- .append(this.#lineFor(entry))
908
- .catch(err => this.#noteDiskFailure(err));
938
+ const writer = this.#appendWriter();
939
+ const line = this.#lineFor(entry);
940
+ if (writer.appendSync) {
941
+ writer.appendSync(line);
942
+ } else {
943
+ void writer.append(line).catch(err => this.#noteDiskFailure(err));
944
+ }
909
945
  } catch (err) {
910
946
  this.#noteDiskFailure(err);
911
947
  }
@@ -921,12 +957,11 @@ export class SessionManager {
921
957
  }
922
958
 
923
959
  // Title changes use their own asynchronous append path rather than
924
- // #appendToSessionFile. Defer them under the same move fence so they cannot
925
- // recreate the vacated source path before #sessionFile is repointed.
960
+ // #appendToSessionFile. During move, write the full body (including the
961
+ // title entry) to the live relocation path so a crash mid-move still
962
+ // keeps the title change; the trailing rewrite still updates header cwd.
926
963
  if (this.#sessionFileRelocating) {
927
- this.#fileIsCurrent = false;
928
- this.#rewriteRequired = true;
929
- this.#atomicRewriteDirty = true;
964
+ this.#rewriteSynchronously();
930
965
  return;
931
966
  }
932
967
 
@@ -1362,14 +1397,15 @@ export class SessionManager {
1362
1397
  : computeDefaultSessionDir(resolvedCwd, this.#storage));
1363
1398
 
1364
1399
  let sessionFileExisted = false;
1365
- // Fence synchronous appends and rewrites away from the session file for the
1366
- // duration of the relocation (see `#sessionFileRelocating`). This flag
1367
- // not a `#diskEpoch` bump is used deliberately: bumping the epoch here
1368
- // would cancel any disk task already queued at the current epoch (e.g. a
1369
- // header-only `ensureOnDisk()` materializing rewrite) before the drain
1370
- // below runs it, silently dropping an explicitly materialized session.
1400
+ // Track source+dest for concurrent completed appends during relocation
1401
+ // (see `#sessionFileRelocating`). Existence of either path decides the
1402
+ // live write target — not a `#diskEpoch` bump, which would cancel any
1403
+ // disk task already queued at the current epoch (e.g. a header-only
1404
+ // `ensureOnDisk()` materializing rewrite) before the drain below runs it.
1371
1405
  if (this.#persist && this.#sessionFile) {
1372
- this.#sessionFileRelocating = true;
1406
+ const source = this.#sessionFile;
1407
+ const dest = path.join(nextSessionDir, path.basename(source));
1408
+ this.#sessionFileRelocating = { source, dest };
1373
1409
  }
1374
1410
 
1375
1411
  try {
@@ -1430,11 +1466,17 @@ export class SessionManager {
1430
1466
  throw err;
1431
1467
  }
1432
1468
 
1469
+ if (sessionFileExisted && sessionPathChanged) {
1470
+ this.#header.previousSessionFiles = [
1471
+ ...new Set([...(this.#header.previousSessionFiles ?? []), path.resolve(oldSessionFile)]),
1472
+ ];
1473
+ }
1474
+
1433
1475
  this.#sessionFile = newSessionFile;
1434
1476
  this.#artifactManager = null;
1435
1477
  this.#artifactManagerSessionFile = null;
1436
- // Path is repointed; a sync rewrite may now safely target the new file.
1437
- this.#sessionFileRelocating = false;
1478
+ // Path is repointed; hot-path appends may use `#sessionFile` again.
1479
+ this.#sessionFileRelocating = null;
1438
1480
  }
1439
1481
 
1440
1482
  this.#cwd = resolvedCwd;
@@ -1459,7 +1501,7 @@ export class SessionManager {
1459
1501
 
1460
1502
  if (this.#sessionFile) this.#rememberBreadcrumb(resolvedCwd, this.#sessionFile);
1461
1503
  } finally {
1462
- this.#sessionFileRelocating = false;
1504
+ this.#sessionFileRelocating = null;
1463
1505
  }
1464
1506
  }
1465
1507
 
@@ -2051,6 +2093,18 @@ export class SessionManager {
2051
2093
  return entry.id;
2052
2094
  }
2053
2095
 
2096
+ /**
2097
+ * Append the durable conversation boundary recorded by `/reset`. The
2098
+ * collapsed live transcript and the model-context rebuild start after the
2099
+ * latest one, while the full history stays on disk (the plain
2100
+ * `transcript:true` export walks it unchanged).
2101
+ */
2102
+ appendResetBoundary(): string {
2103
+ const entry: ResetBoundaryEntry = { type: "reset_boundary", ...this.#freshEntryFields() };
2104
+ this.#recordEntry(entry);
2105
+ return entry.id;
2106
+ }
2107
+
2054
2108
  appendCustomEntry(customType: string, data?: unknown): string {
2055
2109
  const entry: CustomEntry = { type: "custom", customType, data, ...this.#freshEntryFields() };
2056
2110
  this.#recordEntry(entry);
@@ -2305,6 +2359,8 @@ export class SessionManager {
2305
2359
  id: newSessionId,
2306
2360
  timestamp,
2307
2361
  cwd: this.#cwd,
2362
+ title: this.#sessionName,
2363
+ titleSource: this.#titleSource,
2308
2364
  parentSession: this.#persist ? sourceSessionFile : undefined,
2309
2365
  additionalDirectories: this.#additionalDirectories.length > 0 ? [...this.#additionalDirectories] : undefined,
2310
2366
  };