aicodeman 1.46.0 → 1.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/README.md +3 -0
  2. package/dist/cli-notify.d.ts +34 -0
  3. package/dist/cli-notify.d.ts.map +1 -0
  4. package/dist/cli-notify.js +100 -0
  5. package/dist/cli-notify.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +16 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config/cli-registry/patterns.d.ts +1 -1
  10. package/dist/config/cli-registry/patterns.d.ts.map +1 -1
  11. package/dist/config/cli-registry/patterns.js +4 -0
  12. package/dist/config/cli-registry/patterns.js.map +1 -1
  13. package/dist/config/cli-registry/schema.d.ts +6 -0
  14. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  15. package/dist/config/cli-registry/schema.js +62 -2
  16. package/dist/config/cli-registry/schema.js.map +1 -1
  17. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  18. package/dist/config/cli-registry/stock.js +43 -26
  19. package/dist/config/cli-registry/stock.js.map +1 -1
  20. package/dist/config/cli-registry/types.d.ts +47 -17
  21. package/dist/config/cli-registry/types.d.ts.map +1 -1
  22. package/dist/config/path-probe.d.ts +6 -0
  23. package/dist/config/path-probe.d.ts.map +1 -1
  24. package/dist/config/path-probe.js +6 -0
  25. package/dist/config/path-probe.js.map +1 -1
  26. package/dist/config-backup.d.ts +1 -1
  27. package/dist/config-backup.d.ts.map +1 -1
  28. package/dist/config-backup.js +2 -0
  29. package/dist/config-backup.js.map +1 -1
  30. package/dist/custom-model-hosts.d.ts +32 -8
  31. package/dist/custom-model-hosts.d.ts.map +1 -1
  32. package/dist/custom-model-hosts.js +10 -0
  33. package/dist/custom-model-hosts.js.map +1 -1
  34. package/dist/custom-model-injection-apply.d.ts +19 -5
  35. package/dist/custom-model-injection-apply.d.ts.map +1 -1
  36. package/dist/custom-model-injection-apply.js +88 -8
  37. package/dist/custom-model-injection-apply.js.map +1 -1
  38. package/dist/custom-model-injection.d.ts +6 -0
  39. package/dist/custom-model-injection.d.ts.map +1 -1
  40. package/dist/custom-model-injection.js +92 -10
  41. package/dist/custom-model-injection.js.map +1 -1
  42. package/dist/discord-bridge.d.ts +338 -0
  43. package/dist/discord-bridge.d.ts.map +1 -0
  44. package/dist/discord-bridge.js +1256 -0
  45. package/dist/discord-bridge.js.map +1 -0
  46. package/dist/edit-history-local-source.d.ts +83 -0
  47. package/dist/edit-history-local-source.d.ts.map +1 -0
  48. package/dist/edit-history-local-source.js +198 -0
  49. package/dist/edit-history-local-source.js.map +1 -0
  50. package/dist/edit-history-parse.d.ts +114 -0
  51. package/dist/edit-history-parse.d.ts.map +1 -0
  52. package/dist/edit-history-parse.js +312 -0
  53. package/dist/edit-history-parse.js.map +1 -0
  54. package/dist/edit-history-store.d.ts +209 -0
  55. package/dist/edit-history-store.d.ts.map +1 -0
  56. package/dist/edit-history-store.js +599 -0
  57. package/dist/edit-history-store.js.map +1 -0
  58. package/dist/git-clone.d.ts +6 -1
  59. package/dist/git-clone.d.ts.map +1 -1
  60. package/dist/git-clone.js +79 -14
  61. package/dist/git-clone.js.map +1 -1
  62. package/dist/image-watcher.js +4 -4
  63. package/dist/image-watcher.js.map +1 -1
  64. package/dist/mux-interface.d.ts +10 -1
  65. package/dist/mux-interface.d.ts.map +1 -1
  66. package/dist/ralph-fix-plan-watcher.d.ts +13 -1
  67. package/dist/ralph-fix-plan-watcher.d.ts.map +1 -1
  68. package/dist/ralph-fix-plan-watcher.js +21 -3
  69. package/dist/ralph-fix-plan-watcher.js.map +1 -1
  70. package/dist/ralph-tracker.d.ts +6 -0
  71. package/dist/ralph-tracker.d.ts.map +1 -1
  72. package/dist/ralph-tracker.js +8 -0
  73. package/dist/ralph-tracker.js.map +1 -1
  74. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  75. package/dist/session-cli-registry-bridge.js +4 -0
  76. package/dist/session-cli-registry-bridge.js.map +1 -1
  77. package/dist/session.d.ts +70 -3
  78. package/dist/session.d.ts.map +1 -1
  79. package/dist/session.js +147 -4
  80. package/dist/session.js.map +1 -1
  81. package/dist/tmux-manager.d.ts +40 -0
  82. package/dist/tmux-manager.d.ts.map +1 -1
  83. package/dist/tmux-manager.js +62 -0
  84. package/dist/tmux-manager.js.map +1 -1
  85. package/dist/types/discord.d.ts +55 -0
  86. package/dist/types/discord.d.ts.map +1 -0
  87. package/dist/types/discord.js +18 -0
  88. package/dist/types/discord.js.map +1 -0
  89. package/dist/types/index.d.ts +2 -0
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/index.js +2 -0
  92. package/dist/types/index.js.map +1 -1
  93. package/dist/types/session.d.ts +13 -0
  94. package/dist/types/session.d.ts.map +1 -1
  95. package/dist/types/session.js.map +1 -1
  96. package/dist/web/case-path.d.ts +2 -0
  97. package/dist/web/case-path.d.ts.map +1 -1
  98. package/dist/web/case-path.js +3 -3
  99. package/dist/web/case-path.js.map +1 -1
  100. package/dist/web/edit-history-sync.d.ts +158 -0
  101. package/dist/web/edit-history-sync.d.ts.map +1 -0
  102. package/dist/web/edit-history-sync.js +445 -0
  103. package/dist/web/edit-history-sync.js.map +1 -0
  104. package/dist/web/paste-image-gc.d.ts +6 -0
  105. package/dist/web/paste-image-gc.d.ts.map +1 -1
  106. package/dist/web/paste-image-gc.js +6 -0
  107. package/dist/web/paste-image-gc.js.map +1 -1
  108. package/dist/web/ports/session-port.d.ts +13 -0
  109. package/dist/web/ports/session-port.d.ts.map +1 -1
  110. package/dist/web/public/admin-ui.js.gz +0 -0
  111. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  112. package/dist/web/public/{app.3093527f.js → app.963b5ba8.js} +18 -18
  113. package/dist/web/public/app.963b5ba8.js.br +0 -0
  114. package/dist/web/public/app.963b5ba8.js.gz +0 -0
  115. package/dist/web/public/approvals-ui.js.gz +0 -0
  116. package/dist/web/public/{constants.affcdde6.js → constants.782170e1.js} +114 -25
  117. package/dist/web/public/constants.782170e1.js.br +0 -0
  118. package/dist/web/public/constants.782170e1.js.gz +0 -0
  119. package/dist/web/public/cron-ui.js.gz +0 -0
  120. package/dist/web/public/edit-history-ui.js +630 -0
  121. package/dist/web/public/edit-history-ui.js.br +0 -0
  122. package/dist/web/public/edit-history-ui.js.gz +0 -0
  123. package/dist/web/public/entrance-animations.js.gz +0 -0
  124. package/dist/web/public/git-status-ui.js.gz +0 -0
  125. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  126. package/dist/web/public/home-sessions.js.gz +0 -0
  127. package/dist/web/public/host-wake-ui.js.gz +0 -0
  128. package/dist/web/public/i18n.33f930d7.js +2 -0
  129. package/dist/web/public/i18n.33f930d7.js.br +0 -0
  130. package/dist/web/public/i18n.33f930d7.js.gz +0 -0
  131. package/dist/web/public/{image-input.941386e7.js → image-input.a5d0f8f3.js} +18 -9
  132. package/dist/web/public/image-input.a5d0f8f3.js.br +0 -0
  133. package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
  134. package/dist/web/public/index.html +162 -16
  135. package/dist/web/public/index.html.br +0 -0
  136. package/dist/web/public/index.html.gz +0 -0
  137. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  138. package/dist/web/public/{keyboard-accessory.4e8b20ac.js → keyboard-accessory.d4ec5957.js} +77 -2
  139. package/dist/web/public/keyboard-accessory.d4ec5957.js.br +0 -0
  140. package/dist/web/public/keyboard-accessory.d4ec5957.js.gz +0 -0
  141. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  142. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  143. package/dist/web/public/mobile-overview.js.gz +0 -0
  144. package/dist/web/public/mobile.5ef2ce85.css +1 -0
  145. package/dist/web/public/mobile.5ef2ce85.css.br +0 -0
  146. package/dist/web/public/mobile.5ef2ce85.css.gz +0 -0
  147. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  148. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  149. package/dist/web/public/{panels-ui.4a56ee63.js → panels-ui.b100727d.js} +2 -2
  150. package/dist/web/public/panels-ui.b100727d.js.br +0 -0
  151. package/dist/web/public/{panels-ui.4a56ee63.js.gz → panels-ui.b100727d.js.gz} +0 -0
  152. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  153. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  154. package/dist/web/public/readmymind-ui.js.gz +0 -0
  155. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  156. package/dist/web/public/{respawn-ui.d7adcf6a.js → respawn-ui.304ba0d4.js} +1 -1
  157. package/dist/web/public/respawn-ui.304ba0d4.js.br +0 -0
  158. package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
  159. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  160. package/dist/web/public/security-notices.json +1 -21
  161. package/dist/web/public/session-lineage.js.gz +0 -0
  162. package/dist/web/public/{session-ui.186f9419.js → session-ui.5ac82f2c.js} +30 -30
  163. package/dist/web/public/session-ui.5ac82f2c.js.br +0 -0
  164. package/dist/web/public/session-ui.5ac82f2c.js.gz +0 -0
  165. package/dist/web/public/settings-ui.d1d4fb99.js +106 -0
  166. package/dist/web/public/settings-ui.d1d4fb99.js.br +0 -0
  167. package/dist/web/public/settings-ui.d1d4fb99.js.gz +0 -0
  168. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  169. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  170. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  171. package/dist/web/public/styles.77dc538f.css +1 -0
  172. package/dist/web/public/styles.77dc538f.css.br +0 -0
  173. package/dist/web/public/styles.77dc538f.css.gz +0 -0
  174. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  175. package/dist/web/public/sw.js +2 -2
  176. package/dist/web/public/sw.js.br +0 -0
  177. package/dist/web/public/sw.js.gz +0 -0
  178. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  179. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  180. package/dist/web/public/terminal-keycode229-recovery.98843069.js +1 -0
  181. package/dist/web/public/terminal-keycode229-recovery.98843069.js.br +0 -0
  182. package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
  183. package/dist/web/public/terminal-split.js.gz +0 -0
  184. package/dist/web/public/terminal-tile.js +8 -6
  185. package/dist/web/public/terminal-tile.js.br +0 -0
  186. package/dist/web/public/terminal-tile.js.gz +0 -0
  187. package/dist/web/public/{terminal-ui.7e787829.js → terminal-ui.d7c75ce8.js} +3 -3
  188. package/dist/web/public/terminal-ui.d7c75ce8.js.br +0 -0
  189. package/dist/web/public/terminal-ui.d7c75ce8.js.gz +0 -0
  190. package/dist/web/public/tile-grid.js.gz +0 -0
  191. package/dist/web/public/ultracode-panel.js.gz +0 -0
  192. package/dist/web/public/ultracode-windows.js.gz +0 -0
  193. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  194. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  195. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  196. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  197. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  198. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  199. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  200. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  201. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  202. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  203. package/dist/web/public/vendor/xterm.css.gz +0 -0
  204. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  205. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  206. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  207. package/dist/web/public/webview-tabs.js.gz +0 -0
  208. package/dist/web/public/whats-new.json +9 -9
  209. package/dist/web/request-body-parser.d.ts +23 -0
  210. package/dist/web/request-body-parser.d.ts.map +1 -0
  211. package/dist/web/request-body-parser.js +57 -0
  212. package/dist/web/request-body-parser.js.map +1 -0
  213. package/dist/web/routes/case-routes.d.ts.map +1 -1
  214. package/dist/web/routes/case-routes.js +104 -8
  215. package/dist/web/routes/case-routes.js.map +1 -1
  216. package/dist/web/routes/config-backup-routes.d.ts +5 -1
  217. package/dist/web/routes/config-backup-routes.d.ts.map +1 -1
  218. package/dist/web/routes/config-backup-routes.js +4 -1
  219. package/dist/web/routes/config-backup-routes.js.map +1 -1
  220. package/dist/web/routes/custom-model-routes.d.ts +7 -2
  221. package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
  222. package/dist/web/routes/custom-model-routes.js +51 -6
  223. package/dist/web/routes/custom-model-routes.js.map +1 -1
  224. package/dist/web/routes/discord-routes.d.ts +25 -0
  225. package/dist/web/routes/discord-routes.d.ts.map +1 -0
  226. package/dist/web/routes/discord-routes.js +206 -0
  227. package/dist/web/routes/discord-routes.js.map +1 -0
  228. package/dist/web/routes/edit-history-routes.d.ts +43 -0
  229. package/dist/web/routes/edit-history-routes.d.ts.map +1 -0
  230. package/dist/web/routes/edit-history-routes.js +106 -0
  231. package/dist/web/routes/edit-history-routes.js.map +1 -0
  232. package/dist/web/routes/file-routes.d.ts +5 -0
  233. package/dist/web/routes/file-routes.d.ts.map +1 -1
  234. package/dist/web/routes/file-routes.js +67 -3
  235. package/dist/web/routes/file-routes.js.map +1 -1
  236. package/dist/web/routes/index.d.ts +2 -0
  237. package/dist/web/routes/index.d.ts.map +1 -1
  238. package/dist/web/routes/index.js +2 -0
  239. package/dist/web/routes/index.js.map +1 -1
  240. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  241. package/dist/web/routes/ralph-routes.js +45 -2
  242. package/dist/web/routes/ralph-routes.js.map +1 -1
  243. package/dist/web/routes/session-routes.d.ts +17 -0
  244. package/dist/web/routes/session-routes.d.ts.map +1 -1
  245. package/dist/web/routes/session-routes.js +92 -37
  246. package/dist/web/routes/session-routes.js.map +1 -1
  247. package/dist/web/routes/system-routes.d.ts.map +1 -1
  248. package/dist/web/routes/system-routes.js +4 -2
  249. package/dist/web/routes/system-routes.js.map +1 -1
  250. package/dist/web/schemas.d.ts +68 -1
  251. package/dist/web/schemas.d.ts.map +1 -1
  252. package/dist/web/schemas.js +77 -1
  253. package/dist/web/schemas.js.map +1 -1
  254. package/dist/web/sensitive-path.d.ts.map +1 -1
  255. package/dist/web/sensitive-path.js +15 -0
  256. package/dist/web/sensitive-path.js.map +1 -1
  257. package/dist/web/server.d.ts +43 -0
  258. package/dist/web/server.d.ts.map +1 -1
  259. package/dist/web/server.js +237 -15
  260. package/dist/web/server.js.map +1 -1
  261. package/package.json +1 -1
  262. package/dist/web/public/app.3093527f.js.br +0 -0
  263. package/dist/web/public/app.3093527f.js.gz +0 -0
  264. package/dist/web/public/constants.affcdde6.js.br +0 -0
  265. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  266. package/dist/web/public/i18n.70265257.js +0 -2
  267. package/dist/web/public/i18n.70265257.js.br +0 -0
  268. package/dist/web/public/i18n.70265257.js.gz +0 -0
  269. package/dist/web/public/image-input.941386e7.js.br +0 -0
  270. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  271. package/dist/web/public/keyboard-accessory.4e8b20ac.js.br +0 -0
  272. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  273. package/dist/web/public/mobile.1bbd2d67.css +0 -1
  274. package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
  275. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  276. package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
  277. package/dist/web/public/respawn-ui.d7adcf6a.js.br +0 -0
  278. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  279. package/dist/web/public/session-ui.186f9419.js.br +0 -0
  280. package/dist/web/public/session-ui.186f9419.js.gz +0 -0
  281. package/dist/web/public/settings-ui.a191154e.js +0 -106
  282. package/dist/web/public/settings-ui.a191154e.js.br +0 -0
  283. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  284. package/dist/web/public/styles.7238eae8.css +0 -1
  285. package/dist/web/public/styles.7238eae8.css.br +0 -0
  286. package/dist/web/public/styles.7238eae8.css.gz +0 -0
  287. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js +0 -1
  288. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.br +0 -0
  289. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  290. package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
  291. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
package/dist/session.js CHANGED
@@ -29,12 +29,15 @@
29
29
  */
30
30
  import { EventEmitter } from 'node:events';
31
31
  import { basename } from 'node:path';
32
+ import { realpath } from 'node:fs/promises';
32
33
  import { execSync, execFileSync } from 'node:child_process';
33
34
  import { v4 as uuidv4 } from 'uuid';
34
35
  import * as pty from 'node-pty';
35
36
  import { DEFAULT_NICE_CONFIG, getErrorMessage, isAdvisorModel, isEffortLevel, } from './types.js';
36
37
  import { resolveAndClaimOmpSessionId } from './utils/omp-session-resolver.js';
37
38
  import { claudeTranscriptExists } from './utils/claude-transcript.js';
39
+ import { probePathKind } from './utils/bounded-path-probe.js';
40
+ import { PATH_PROBE_TIMEOUT_MS, WORKSPACE_REALPATH_RETRY_MS } from './config/path-probe.js';
38
41
  import { matchesPattern } from './config/cli-registry/patterns.js';
39
42
  import { probeDockerCliVersion } from './docker-hosts.js';
40
43
  import { probeRemoteCliVersion } from './remote-hosts.js';
@@ -268,6 +271,21 @@ const TEST_PTY_SCRIPT = 'if (process.stdin.isTTY) process.stdin.setRawMode(true)
268
271
  const DOCKER_CLI_VERSION_PROBE_DELAY_MS = 3000;
269
272
  /** Delay before the over-ssh Claude CLI version probe (keeps session start off the ssh round-trip). */
270
273
  const REMOTE_CLI_VERSION_PROBE_DELAY_MS = 3000;
274
+ /** Whether absolute `path` is `root` or lies under it (a lexical compare). */
275
+ function isSameOrUnder(path, root) {
276
+ return path === root || path.startsWith(root === '/' ? '/' : `${root}/`);
277
+ }
278
+ /**
279
+ * Rewrite `path` from under `from` to the same place under `to`; a path not at or
280
+ * under `from` comes back unchanged. Exported for unit testing.
281
+ */
282
+ export function rebasePath(path, from, to) {
283
+ if (path === from)
284
+ return to;
285
+ if (path.startsWith(`${from}/`))
286
+ return to + path.slice(from.length);
287
+ return path;
288
+ }
271
289
  /**
272
290
  * Ask tmux for the current window geometry of `muxName` so a re-attaching PTY
273
291
  * client can spawn at the same size and avoid the resize-flicker / scrollback
@@ -564,6 +582,21 @@ export class Session extends EventEmitter {
564
582
  * rendered as "alive".
565
583
  */
566
584
  _paneExit = null;
585
+ /**
586
+ * The pane's live directory, from the pane-exit watcher's read. Runtime-only:
587
+ * serialized by toState() but never restored by the constructor.
588
+ */
589
+ _paneCwd;
590
+ /**
591
+ * `workingDir` with symlinks resolved, so a pane reading (tmux reads the
592
+ * process cwd, always the resolved path) can be put back into `workingDir`'s
593
+ * spelling. `undefined` until the first resolution settles; `null` when there
594
+ * is nothing to translate (no symlink in the path) or it could not be resolved.
595
+ */
596
+ _workspaceRealPath;
597
+ _workspaceRealPathPending = false;
598
+ /** When a resolution that met an unreachable folder may be tried again (0 = never). */
599
+ _workspaceRealPathRetryAt = 0;
567
600
  /**
568
601
  * How many starts, attaches or relaunches are running for this session's
569
602
  * pane. While one is, a dead-pane reading may describe a pane that is being
@@ -993,6 +1026,16 @@ export class Session extends EventEmitter {
993
1026
  const value = this._envOverrides?.DSH_HOME;
994
1027
  return value && value.trim() ? value.trim() : undefined;
995
1028
  }
1029
+ /**
1030
+ * One env var from this session's overrides, for internal path resolution only
1031
+ * (custom-model injection linking an isolated config dir back to a path that must
1032
+ * survive lives in). Single-key lookup by design, like `deepSeekHomeOverride`: the
1033
+ * map can hold provider credentials and stays off the public `SessionState`.
1034
+ */
1035
+ envOverrideValue(key) {
1036
+ const value = this._envOverrides?.[key];
1037
+ return value && value.trim() ? value.trim() : undefined;
1038
+ }
996
1039
  /** Owning username in multi-user mode, else undefined. */
997
1040
  get owner() {
998
1041
  return this._owner;
@@ -1144,6 +1187,101 @@ export class Session extends EventEmitter {
1144
1187
  if (this._muxSession)
1145
1188
  this._mux?.clearPaneExit?.(this._muxSession.muxName);
1146
1189
  }
1190
+ /** The pane's live working directory, when the pane read has reported one. */
1191
+ get paneCwd() {
1192
+ return this._paneCwd;
1193
+ }
1194
+ /**
1195
+ * Record the pane's live directory. Returns whether it CHANGED, so the caller
1196
+ * broadcasts only on a real transition.
1197
+ *
1198
+ * Scoped by {@link paneExitApplies}, the same rule as the exit reading: a
1199
+ * remote session's local pane runs the ssh client and a docker case's runs a
1200
+ * `docker exec`, so their pane directory is a host directory the agent was
1201
+ * never in, and a direct-PTY or rediscovered session has no pane Codeman can
1202
+ * vouch for. A relative value is refused rather than stored.
1203
+ *
1204
+ * tmux reads the pane's directory from the process cwd, which is always the
1205
+ * resolved path, while `workingDir` keeps the spelling the case was created
1206
+ * with. A workspace reached through a symlink would otherwise read as outside
1207
+ * itself, and the browser's preview routing (a lexical compare against
1208
+ * `workingDir`) would send every relative link to the attachment route. So a
1209
+ * reading at or under the workspace's resolved path is written back in
1210
+ * `workingDir`'s spelling. Until that resolution has settled, only a reading
1211
+ * lexically inside `workingDir` is accepted.
1212
+ */
1213
+ setPaneCwd(next) {
1214
+ let resolved = this.paneExitApplies && next?.startsWith('/') ? next : undefined;
1215
+ if (resolved !== undefined) {
1216
+ if (this._workspaceRealPath === undefined ||
1217
+ (this._workspaceRealPathRetryAt !== 0 && Date.now() >= this._workspaceRealPathRetryAt)) {
1218
+ this._resolveWorkspaceRealPath();
1219
+ }
1220
+ if (this._workspaceRealPath === undefined) {
1221
+ // The workspace's resolved path is not known yet. A reading outside
1222
+ // `workingDir` could be the workspace itself seen through its symlink, so
1223
+ // it waits for the next tick rather than mis-route links for one.
1224
+ if (!isSameOrUnder(resolved, this._workspaceSpelling()))
1225
+ return false;
1226
+ }
1227
+ else if (this._workspaceRealPath !== null) {
1228
+ resolved = rebasePath(resolved, this._workspaceRealPath, this._workspaceSpelling());
1229
+ }
1230
+ }
1231
+ if (resolved === this._paneCwd)
1232
+ return false;
1233
+ this._paneCwd = resolved;
1234
+ return true;
1235
+ }
1236
+ /** `workingDir` without a trailing separator (the root stays `/`). */
1237
+ _workspaceSpelling() {
1238
+ return this.workingDir.length > 1 ? this.workingDir.replace(/\/+$/, '') : this.workingDir;
1239
+ }
1240
+ /**
1241
+ * Resolve `workingDir`'s symlinks in the background for {@link setPaneCwd}.
1242
+ *
1243
+ * Bounded: the folder can sit on a network mount that stopped answering, so it
1244
+ * is probed with `probePathKind()` first and the `realpath()` is raced against
1245
+ * the same timeout. An `unknown` probe, a failed or slow `realpath()` leave no
1246
+ * translation and allow one more try after `WORKSPACE_REALPATH_RETRY_MS`; a
1247
+ * folder that is absent or not a directory is settled for good.
1248
+ */
1249
+ _resolveWorkspaceRealPath() {
1250
+ if (this._workspaceRealPathPending)
1251
+ return;
1252
+ this._workspaceRealPathPending = true;
1253
+ const spelling = this._workspaceSpelling();
1254
+ void (async () => {
1255
+ let real = null;
1256
+ let retry = false;
1257
+ try {
1258
+ const kind = await probePathKind(spelling);
1259
+ if (kind === 'unknown') {
1260
+ retry = true;
1261
+ }
1262
+ else if (kind === 'directory') {
1263
+ let timer;
1264
+ const answer = await Promise.race([
1265
+ realpath(spelling).catch(() => undefined),
1266
+ new Promise((resolveTimeout) => {
1267
+ timer = setTimeout(() => resolveTimeout(undefined), PATH_PROBE_TIMEOUT_MS);
1268
+ timer.unref?.();
1269
+ }),
1270
+ ]).finally(() => clearTimeout(timer));
1271
+ if (answer === undefined)
1272
+ retry = true;
1273
+ else if (answer !== spelling && answer !== '/')
1274
+ real = answer;
1275
+ }
1276
+ }
1277
+ catch {
1278
+ retry = true;
1279
+ }
1280
+ this._workspaceRealPath = real;
1281
+ this._workspaceRealPathRetryAt = retry ? Date.now() + WORKSPACE_REALPATH_RETRY_MS : 0;
1282
+ this._workspaceRealPathPending = false;
1283
+ })();
1284
+ }
1147
1285
  /**
1148
1286
  * Record what the mux layer observed of this pane's agent, and say whether
1149
1287
  * that changed the answer. The caller persists and broadcasts on a true.
@@ -1731,6 +1869,7 @@ export class Session extends EventEmitter {
1731
1869
  pid: this.pid,
1732
1870
  status: this._status,
1733
1871
  workingDir: this.workingDir,
1872
+ paneCwd: this._paneCwd,
1734
1873
  remote: this._remote,
1735
1874
  docker: this._docker,
1736
1875
  owner: this._owner,
@@ -2240,7 +2379,7 @@ export class Session extends EventEmitter {
2240
2379
  if (entry?.launch.chain !== 'fallback')
2241
2380
  return options;
2242
2381
  const resumeIdPattern = entry.launch.params?.resumeId;
2243
- const configDir = this._claudeConfigDir();
2382
+ const configDir = this.claudeConfigDir();
2244
2383
  const chainTail = this._claudeSessionChain[this._claudeSessionChain.length - 1];
2245
2384
  const candidates = [chainTail, options.resumeSessionId, this.id].filter((v) => !!v);
2246
2385
  for (const candidate of candidates) {
@@ -2262,8 +2401,11 @@ export class Session extends EventEmitter {
2262
2401
  // unpinned options already carry is the correct command.
2263
2402
  return options;
2264
2403
  }
2265
- /** The session's Claude config dir when it has been relocated (#255), else undefined. */
2266
- _claudeConfigDir() {
2404
+ /**
2405
+ * The session's Claude config dir when it has been relocated (#255), else undefined.
2406
+ * Public for the edit history reader, which looks for the transcript there.
2407
+ */
2408
+ claudeConfigDir() {
2267
2409
  // Trimmed like `claudeProjectsDir()` trims the process-wide override: the
2268
2410
  // envOverrides schema validates keys only, and a whitespace-only value would
2269
2411
  // otherwise resolve to a relative path and read "no transcript" for everything.
@@ -2271,7 +2413,8 @@ export class Session extends EventEmitter {
2271
2413
  }
2272
2414
  /**
2273
2415
  * Force the custom-model selection's `launchModel` (pi/omp `custom/<id>`, grok's
2274
- * `[model.<name>]` block name) onto the CLI's `model` launch param. Where that param
2416
+ * `[model.<name>]` block name, codex's bare selected id) onto the CLI's `model`
2417
+ * launch param. Where that param
2275
2418
  * lives is registry DATA — the entry's `legacyConfigField` (`piConfig`, `grokConfig`,
2276
2419
  * ...) or the top-level `model` for an entry that declares none — so this stays a
2277
2420
  * generic reader rather than a branch per CLI. Applied on the OPTIONS only: the stored