aicodeman 1.48.0 → 1.49.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 (269) hide show
  1. package/README.md +12 -3
  2. package/dist/config/cli-registry/schema.d.ts +1 -0
  3. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  4. package/dist/config/cli-registry/schema.js +5 -1
  5. package/dist/config/cli-registry/schema.js.map +1 -1
  6. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  7. package/dist/config/cli-registry/stock.js +71 -5
  8. package/dist/config/cli-registry/stock.js.map +1 -1
  9. package/dist/config/cli-registry/types.d.ts +4 -4
  10. package/dist/config/cli-registry/types.d.ts.map +1 -1
  11. package/dist/config/foreign-tmux.d.ts +48 -0
  12. package/dist/config/foreign-tmux.d.ts.map +1 -0
  13. package/dist/config/foreign-tmux.js +48 -0
  14. package/dist/config/foreign-tmux.js.map +1 -0
  15. package/dist/custom-model-injection-apply.d.ts +4 -2
  16. package/dist/custom-model-injection-apply.d.ts.map +1 -1
  17. package/dist/custom-model-injection-apply.js +35 -21
  18. package/dist/custom-model-injection-apply.js.map +1 -1
  19. package/dist/docker-hosts.d.ts +8 -1
  20. package/dist/docker-hosts.d.ts.map +1 -1
  21. package/dist/docker-hosts.js +5 -5
  22. package/dist/docker-hosts.js.map +1 -1
  23. package/dist/foreign-tmux-discovery.d.ts +140 -0
  24. package/dist/foreign-tmux-discovery.d.ts.map +1 -0
  25. package/dist/foreign-tmux-discovery.js +366 -0
  26. package/dist/foreign-tmux-discovery.js.map +1 -0
  27. package/dist/foreign-tmux.d.ts +381 -0
  28. package/dist/foreign-tmux.d.ts.map +1 -0
  29. package/dist/foreign-tmux.js +775 -0
  30. package/dist/foreign-tmux.js.map +1 -0
  31. package/dist/git-fetch-scheduler.d.ts +2 -0
  32. package/dist/git-fetch-scheduler.d.ts.map +1 -1
  33. package/dist/git-fetch-scheduler.js +1 -1
  34. package/dist/git-fetch-scheduler.js.map +1 -1
  35. package/dist/git-fetch.d.ts +1 -1
  36. package/dist/git-fetch.d.ts.map +1 -1
  37. package/dist/git-workspace-status.d.ts +2 -2
  38. package/dist/git-workspace-status.d.ts.map +1 -1
  39. package/dist/hooks-config.d.ts +2 -1
  40. package/dist/hooks-config.d.ts.map +1 -1
  41. package/dist/hooks-config.js +2 -1
  42. package/dist/hooks-config.js.map +1 -1
  43. package/dist/live-worktree.d.ts +53 -0
  44. package/dist/live-worktree.d.ts.map +1 -0
  45. package/dist/live-worktree.js +97 -0
  46. package/dist/live-worktree.js.map +1 -0
  47. package/dist/mux-interface.d.ts +12 -1
  48. package/dist/mux-interface.d.ts.map +1 -1
  49. package/dist/reboot-restore.d.ts +1 -1
  50. package/dist/reboot-restore.d.ts.map +1 -1
  51. package/dist/reboot-restore.js +9 -0
  52. package/dist/reboot-restore.js.map +1 -1
  53. package/dist/remote-files.d.ts +150 -5
  54. package/dist/remote-files.d.ts.map +1 -1
  55. package/dist/remote-files.js +505 -7
  56. package/dist/remote-files.js.map +1 -1
  57. package/dist/remote-hosts.d.ts +15 -9
  58. package/dist/remote-hosts.d.ts.map +1 -1
  59. package/dist/remote-hosts.js +42 -20
  60. package/dist/remote-hosts.js.map +1 -1
  61. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  62. package/dist/session-cli-registry-bridge.js +19 -8
  63. package/dist/session-cli-registry-bridge.js.map +1 -1
  64. package/dist/session-env-clamp.d.ts +1 -1
  65. package/dist/session-env-clamp.d.ts.map +1 -1
  66. package/dist/session-env-clamp.js +4 -3
  67. package/dist/session-env-clamp.js.map +1 -1
  68. package/dist/session.d.ts +91 -21
  69. package/dist/session.d.ts.map +1 -1
  70. package/dist/session.js +190 -33
  71. package/dist/session.js.map +1 -1
  72. package/dist/tmux-manager.d.ts +74 -12
  73. package/dist/tmux-manager.d.ts.map +1 -1
  74. package/dist/tmux-manager.js +256 -41
  75. package/dist/tmux-manager.js.map +1 -1
  76. package/dist/types/foreign-tmux.d.ts +101 -0
  77. package/dist/types/foreign-tmux.d.ts.map +1 -0
  78. package/dist/types/foreign-tmux.js +27 -0
  79. package/dist/types/foreign-tmux.js.map +1 -0
  80. package/dist/types/index.d.ts +1 -0
  81. package/dist/types/index.d.ts.map +1 -1
  82. package/dist/types/index.js +1 -0
  83. package/dist/types/index.js.map +1 -1
  84. package/dist/types/session.d.ts +106 -2
  85. package/dist/types/session.d.ts.map +1 -1
  86. package/dist/types/session.js.map +1 -1
  87. package/dist/types/update.d.ts +4 -1
  88. package/dist/types/update.d.ts.map +1 -1
  89. package/dist/web/edit-history-sync.d.ts +27 -7
  90. package/dist/web/edit-history-sync.d.ts.map +1 -1
  91. package/dist/web/edit-history-sync.js +35 -19
  92. package/dist/web/edit-history-sync.js.map +1 -1
  93. package/dist/web/launch-defaults.d.ts +1 -1
  94. package/dist/web/launch-defaults.js +2 -2
  95. package/dist/web/launch-defaults.js.map +1 -1
  96. package/dist/web/linked-cases-writer.d.ts +43 -0
  97. package/dist/web/linked-cases-writer.d.ts.map +1 -0
  98. package/dist/web/linked-cases-writer.js +107 -0
  99. package/dist/web/linked-cases-writer.js.map +1 -0
  100. package/dist/web/paste-image-gc.d.ts +4 -1
  101. package/dist/web/paste-image-gc.d.ts.map +1 -1
  102. package/dist/web/paste-image-gc.js +4 -2
  103. package/dist/web/paste-image-gc.js.map +1 -1
  104. package/dist/web/public/admin-ui.js.gz +0 -0
  105. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  106. package/dist/web/public/{app.963b5ba8.js → app.9ea37070.js} +6 -6
  107. package/dist/web/public/app.9ea37070.js.br +0 -0
  108. package/dist/web/public/app.9ea37070.js.gz +0 -0
  109. package/dist/web/public/approvals-ui.js.gz +0 -0
  110. package/dist/web/public/constants.782170e1.js.gz +0 -0
  111. package/dist/web/public/cron-ui.js.gz +0 -0
  112. package/dist/web/public/edit-history-ui.js +27 -14
  113. package/dist/web/public/edit-history-ui.js.br +0 -0
  114. package/dist/web/public/edit-history-ui.js.gz +0 -0
  115. package/dist/web/public/entrance-animations.js.gz +0 -0
  116. package/dist/web/public/foreign-sessions.js +476 -0
  117. package/dist/web/public/foreign-sessions.js.br +0 -0
  118. package/dist/web/public/foreign-sessions.js.gz +0 -0
  119. package/dist/web/public/git-status-ui.js +5 -3
  120. package/dist/web/public/git-status-ui.js.br +0 -0
  121. package/dist/web/public/git-status-ui.js.gz +0 -0
  122. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  123. package/dist/web/public/home-sessions.js.gz +0 -0
  124. package/dist/web/public/host-wake-ui.js.gz +0 -0
  125. package/dist/web/public/i18n.a5e82c95.js +2 -0
  126. package/dist/web/public/i18n.a5e82c95.js.br +0 -0
  127. package/dist/web/public/i18n.a5e82c95.js.gz +0 -0
  128. package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
  129. package/dist/web/public/index.html +36 -17
  130. package/dist/web/public/index.html.br +0 -0
  131. package/dist/web/public/index.html.gz +0 -0
  132. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  133. package/dist/web/public/{keyboard-accessory.d4ec5957.js → keyboard-accessory.b941676c.js} +3 -1
  134. package/dist/web/public/keyboard-accessory.b941676c.js.br +0 -0
  135. package/dist/web/public/{keyboard-accessory.d4ec5957.js.gz → keyboard-accessory.b941676c.js.gz} +0 -0
  136. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  137. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  138. package/dist/web/public/mobile-overview.js +10 -0
  139. package/dist/web/public/mobile-overview.js.br +0 -0
  140. package/dist/web/public/mobile-overview.js.gz +0 -0
  141. package/dist/web/public/{mobile.5ef2ce85.css → mobile.67c8eb5b.css} +1 -1
  142. package/dist/web/public/mobile.67c8eb5b.css.br +0 -0
  143. package/dist/web/public/{mobile.5ef2ce85.css.gz → mobile.67c8eb5b.css.gz} +0 -0
  144. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  145. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  146. package/dist/web/public/{panels-ui.b100727d.js → panels-ui.8bcd4473.js} +4 -4
  147. package/dist/web/public/panels-ui.8bcd4473.js.br +0 -0
  148. package/dist/web/public/panels-ui.8bcd4473.js.gz +0 -0
  149. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  150. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  151. package/dist/web/public/readmymind-ui.js.gz +0 -0
  152. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  153. package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
  154. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  155. package/dist/web/public/session-lineage.js.gz +0 -0
  156. package/dist/web/public/{session-ui.5ac82f2c.js → session-ui.a7c8c095.js} +11 -11
  157. package/dist/web/public/session-ui.a7c8c095.js.br +0 -0
  158. package/dist/web/public/session-ui.a7c8c095.js.gz +0 -0
  159. package/dist/web/public/{settings-ui.d1d4fb99.js → settings-ui.18f92052.js} +3 -3
  160. package/dist/web/public/settings-ui.18f92052.js.br +0 -0
  161. package/dist/web/public/settings-ui.18f92052.js.gz +0 -0
  162. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  163. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  164. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  165. package/dist/web/public/{styles.77dc538f.css → styles.6a718331.css} +1 -1
  166. package/dist/web/public/styles.6a718331.css.br +0 -0
  167. package/dist/web/public/{styles.77dc538f.css.gz → styles.6a718331.css.gz} +0 -0
  168. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  169. package/dist/web/public/sw.js +2 -2
  170. package/dist/web/public/sw.js.br +0 -0
  171. package/dist/web/public/sw.js.gz +0 -0
  172. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  173. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  174. package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
  175. package/dist/web/public/terminal-split.js.gz +0 -0
  176. package/dist/web/public/terminal-tile.js +2 -2
  177. package/dist/web/public/terminal-tile.js.br +0 -0
  178. package/dist/web/public/terminal-tile.js.gz +0 -0
  179. package/dist/web/public/{terminal-ui.d7c75ce8.js → terminal-ui.a7883640.js} +2 -2
  180. package/dist/web/public/terminal-ui.a7883640.js.br +0 -0
  181. package/dist/web/public/terminal-ui.a7883640.js.gz +0 -0
  182. package/dist/web/public/tile-grid.js.gz +0 -0
  183. package/dist/web/public/ultracode-panel.js.gz +0 -0
  184. package/dist/web/public/ultracode-windows.js.gz +0 -0
  185. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  186. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  187. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  188. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  189. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  190. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  191. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  192. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  193. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  194. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  195. package/dist/web/public/vendor/xterm.css.gz +0 -0
  196. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  197. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  198. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  199. package/dist/web/public/webview-tabs.js.gz +0 -0
  200. package/dist/web/public/whats-new.json +9 -9
  201. package/dist/web/routes/case-routes.d.ts.map +1 -1
  202. package/dist/web/routes/case-routes.js +72 -53
  203. package/dist/web/routes/case-routes.js.map +1 -1
  204. package/dist/web/routes/discord-routes.js +1 -1
  205. package/dist/web/routes/discord-routes.js.map +1 -1
  206. package/dist/web/routes/edit-history-routes.d.ts +2 -2
  207. package/dist/web/routes/edit-history-routes.d.ts.map +1 -1
  208. package/dist/web/routes/edit-history-routes.js +4 -3
  209. package/dist/web/routes/edit-history-routes.js.map +1 -1
  210. package/dist/web/routes/file-routes.d.ts.map +1 -1
  211. package/dist/web/routes/file-routes.js +213 -102
  212. package/dist/web/routes/file-routes.js.map +1 -1
  213. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  214. package/dist/web/routes/git-status-routes.js +7 -2
  215. package/dist/web/routes/git-status-routes.js.map +1 -1
  216. package/dist/web/routes/mux-routes.d.ts +10 -3
  217. package/dist/web/routes/mux-routes.d.ts.map +1 -1
  218. package/dist/web/routes/mux-routes.js +59 -1
  219. package/dist/web/routes/mux-routes.js.map +1 -1
  220. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  221. package/dist/web/routes/ralph-routes.js +22 -0
  222. package/dist/web/routes/ralph-routes.js.map +1 -1
  223. package/dist/web/routes/readmymind-routes.d.ts.map +1 -1
  224. package/dist/web/routes/readmymind-routes.js +3 -2
  225. package/dist/web/routes/readmymind-routes.js.map +1 -1
  226. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  227. package/dist/web/routes/respawn-routes.js +24 -0
  228. package/dist/web/routes/respawn-routes.js.map +1 -1
  229. package/dist/web/routes/session-routes.d.ts +11 -8
  230. package/dist/web/routes/session-routes.d.ts.map +1 -1
  231. package/dist/web/routes/session-routes.js +262 -29
  232. package/dist/web/routes/session-routes.js.map +1 -1
  233. package/dist/web/routes/system-routes.d.ts.map +1 -1
  234. package/dist/web/routes/system-routes.js +3 -2
  235. package/dist/web/routes/system-routes.js.map +1 -1
  236. package/dist/web/schemas.d.ts +24 -1
  237. package/dist/web/schemas.d.ts.map +1 -1
  238. package/dist/web/schemas.js +34 -9
  239. package/dist/web/schemas.js.map +1 -1
  240. package/dist/web/self-update.d.ts +9 -0
  241. package/dist/web/self-update.d.ts.map +1 -1
  242. package/dist/web/self-update.js +19 -3
  243. package/dist/web/self-update.js.map +1 -1
  244. package/dist/web/server.d.ts +16 -3
  245. package/dist/web/server.d.ts.map +1 -1
  246. package/dist/web/server.js +84 -24
  247. package/dist/web/server.js.map +1 -1
  248. package/dist/web/session-wait-registry.d.ts +13 -0
  249. package/dist/web/session-wait-registry.d.ts.map +1 -1
  250. package/dist/web/session-wait-registry.js +16 -8
  251. package/dist/web/session-wait-registry.js.map +1 -1
  252. package/package.json +2 -2
  253. package/skills/codeman/SKILL.md +20 -4
  254. package/dist/web/public/app.963b5ba8.js.br +0 -0
  255. package/dist/web/public/app.963b5ba8.js.gz +0 -0
  256. package/dist/web/public/i18n.33f930d7.js +0 -2
  257. package/dist/web/public/i18n.33f930d7.js.br +0 -0
  258. package/dist/web/public/i18n.33f930d7.js.gz +0 -0
  259. package/dist/web/public/keyboard-accessory.d4ec5957.js.br +0 -0
  260. package/dist/web/public/mobile.5ef2ce85.css.br +0 -0
  261. package/dist/web/public/panels-ui.b100727d.js.br +0 -0
  262. package/dist/web/public/panels-ui.b100727d.js.gz +0 -0
  263. package/dist/web/public/session-ui.5ac82f2c.js.br +0 -0
  264. package/dist/web/public/session-ui.5ac82f2c.js.gz +0 -0
  265. package/dist/web/public/settings-ui.d1d4fb99.js.br +0 -0
  266. package/dist/web/public/settings-ui.d1d4fb99.js.gz +0 -0
  267. package/dist/web/public/styles.77dc538f.css.br +0 -0
  268. package/dist/web/public/terminal-ui.d7c75ce8.js.br +0 -0
  269. package/dist/web/public/terminal-ui.d7c75ce8.js.gz +0 -0
@@ -34,6 +34,7 @@ import fastifyWebsocket from '@fastify/websocket';
34
34
  import fastifyMultipart from '@fastify/multipart';
35
35
  import { pasteImageDirInUseByOtherSession, startPasteImageGc, uploadDirs } from './paste-image-gc.js';
36
36
  import { CLEAN_EXIT_CLOSE_REASON, shouldCloseCleanlyExitedSession } from '../pane-exit-sweep.js';
37
+ import { findLiveWorktree } from '../live-worktree.js';
37
38
  import { join, dirname } from 'node:path';
38
39
  import { fileURLToPath } from 'node:url';
39
40
  import { existsSync, mkdirSync, readFileSync, chmodSync, statSync } from 'node:fs';
@@ -80,6 +81,7 @@ import { createRequire } from 'node:module';
80
81
  import { RunSummaryTracker } from '../run-summary.js';
81
82
  import { OrchestratorLoop } from '../orchestrator-loop.js';
82
83
  import { getLifecycleLog } from '../session-lifecycle-log.js';
84
+ import { clampEnvOverridesForOwner } from '../session-env-clamp.js';
83
85
  import { applyWorkspaceHooks, pruneAgentSessionPreambles, readGitFetchSettings, readKeepWarmSettingsOrThrow, readPlanUsageTelemetryEnabled, removeAgentSessionPreamble, } from '../hooks-config.js';
84
86
  import { PushSubscriptionStore } from '../push-store.js';
85
87
  import webpush from 'web-push';
@@ -99,11 +101,14 @@ import { fetchWorkspaceOnce } from '../git-fetch.js';
99
101
  import { GitFetchScheduler } from '../git-fetch-scheduler.js';
100
102
  import { probePathKind } from '../utils/bounded-path-probe.js';
101
103
  import { defaultDockerWorkspaces } from './routes/git-status-routes.js';
104
+ import { guardSessionConfigDirHome } from './routes/session-routes.js';
102
105
  import { composerIsEmpty, plainFrame } from '../session-submit-verifier.js';
103
106
  import { detectUsageLimitPause } from '../usage-limit-patterns.js';
104
- import { EditHistorySync } from './edit-history-sync.js';
107
+ import { EditHistorySync, remoteTranscriptIds } from './edit-history-sync.js';
105
108
  import { EditHistoryStore } from '../edit-history-store.js';
106
109
  import { probeLocalPaths, readClaudeEditSources, resolveClaudeTranscript } from '../edit-history-local-source.js';
110
+ import { remoteLocateClaudeTranscript, remoteProbePaths, remoteReadClaudeEditSources } from '../remote-files.js';
111
+ import { remoteTmuxLocation } from '../tmux-manager.js';
107
112
  import { stopDeepSeekWeb } from '../deepseek-web-server.js';
108
113
  import { wireRespawnListeners, setupTimedRespawn, restoreRespawnController, saveRespawnConfig, } from './respawn-event-wiring.js';
109
114
  import { reconcileUpdateOnBoot } from './self-update.js';
@@ -211,14 +216,21 @@ export class WebServer extends EventEmitter {
211
216
  app;
212
217
  sessions = new Map();
213
218
  /**
214
- * The edit history panel's reader (edit-history-sync.ts). Reads only when the panel asks;
215
- * the main transcript is the one the transcript watcher follows (hook-reported path), or,
216
- * before the first hook after a restart, a lookup under the session's claude config dir.
219
+ * The edit history panel's reader (edit-history-sync.ts). Reads only when the panel asks.
220
+ * A local session's main transcript is the one the transcript watcher follows (hook-reported
221
+ * path), or, before the first hook after a restart, a lookup under the session's claude
222
+ * config dir. A remote session sends no hooks: its host is asked over ssh for the transcript
223
+ * the pane's claude writes, else the stored conversation's (`remoteTranscriptIds`), and is
224
+ * never woken for it (remote-files.ts).
217
225
  */
218
226
  editHistorySync = new EditHistorySync({
219
- transcriptPath: (session) => resolveClaudeTranscript(this.transcriptWatchers.get(session.id)?.getPath(), [session.claudeSessionId || session.id, session.id], session.claudeConfigDir),
220
- readSources: readClaudeEditSources,
221
- probe: probeLocalPaths,
227
+ transcriptPath: (session, storedTranscriptId) => session.remote
228
+ ? remoteLocateClaudeTranscript(session.remote, remoteTranscriptIds(session, storedTranscriptId), remoteTmuxLocation(session.remote, session.id))
229
+ : resolveClaudeTranscript(this.transcriptWatchers.get(session.id)?.getPath(), [session.claudeSessionId || session.id, session.id], session.claudeConfigDir),
230
+ readSources: (session, mainPath, requests, budget, opts) => session.remote
231
+ ? remoteReadClaudeEditSources(session.remote, mainPath, requests, budget, opts)
232
+ : readClaudeEditSources(mainPath, requests, budget, opts),
233
+ probe: (session, paths) => (session.remote ? remoteProbePaths(session.remote, paths) : probeLocalPaths(paths)),
222
234
  loadStore: (sessionId) => EditHistoryStore.load(sessionId),
223
235
  deleteStore: (sessionId) => EditHistoryStore.delete(sessionId),
224
236
  sweepStores: (keep) => EditHistoryStore.sweep(keep),
@@ -387,9 +399,10 @@ export class WebServer extends EventEmitter {
387
399
  agentExited: (id) => !!this.sessions.get(id)?.paneExit,
388
400
  awaitingChoice: (id) => approvalInbox.listPending().some((item) => item.sessionId === id && item.kind !== 'idle'),
389
401
  // Remote (ssh) sessions keep their folder on another host, so received files have nowhere local to go.
402
+ // An adopted session's folder is the neutral wrapper folder, which its agent need not be able to read.
390
403
  sessionDir: (sessionId) => {
391
404
  const session = this.sessions.get(sessionId);
392
- return session && !session.remote ? session.workingDir : undefined;
405
+ return session && !session.remote && !session.isAdopted ? session.workingDir : undefined;
393
406
  },
394
407
  });
395
408
  teamWatcher = new TeamWatcher();
@@ -1286,7 +1299,7 @@ export class WebServer extends EventEmitter {
1286
1299
  * endpoint is gone or the CLI is unregistered: the bookkeeping is still restored so
1287
1300
  * the selection can be cleared, and the pane keeps running on tmux's retained env.
1288
1301
  */
1289
- async _rebuildCustomModelEnv(session, saved) {
1302
+ async _rebuildCustomModelEnv(session, saved, ownEnv = {}) {
1290
1303
  const entry = getCli(session.mode);
1291
1304
  if (!entry)
1292
1305
  return undefined;
@@ -1296,14 +1309,14 @@ export class WebServer extends EventEmitter {
1296
1309
  return undefined;
1297
1310
  }
1298
1311
  try {
1299
- // Boot recovery omits both `workingDir` (no reason to re-answer a trust dialog that
1300
- // already fired once) and the session-env real-home lookup: the injected VALUES are
1301
- // never persisted, so after a real restart the session can no longer carry its
1302
- // allowlisted `DSH_HOME` either (`getEnvOverridesForPersist()` keeps only
1303
- // `CLAUDE_CODE_*`/`CLAUDE_CONFIG_DIR`): the lookup could only ever hit on the first
1304
- // apply in-process. Real-home resolution therefore falls back to the SERVER's
1305
- // `DSH_HOME` or the recipe's `realHomeFallback` (~/.dsh).
1306
- return applyCustomModelInjection(entry, endpoint, saved.modelId, session.id, resolveCustomModelContextLength(endpoint, saved.modelId));
1312
+ // Boot recovery omits `workingDir` (no reason to re-answer a trust dialog that already
1313
+ // fired once). The real-home lookup reads the record's own values, since the session's
1314
+ // overrides hold what the previous run injected. `linkRealHomeEntry()` keeps what is
1315
+ // there, so this only decides an entry that is missing, which an own home skips when it
1316
+ // does not hold one. With no own home, or one that does not answer (probed under the
1317
+ // bulk cap, this walks every saved session), a missing entry links from the SERVER's
1318
+ // `DSH_HOME` or `CLAUDE_CONFIG_DIR`, or their defaults (~/.dsh, ~/.claude).
1319
+ return applyCustomModelInjection(entry, endpoint, saved.modelId, session.id, resolveCustomModelContextLength(endpoint, saved.modelId), undefined, await guardSessionConfigDirHome(entry, (envVar) => ownEnv[envVar], {}));
1307
1320
  }
1308
1321
  catch (err) {
1309
1322
  console.warn('[WebServer] Failed to rebuild custom-model env on recovery:', err);
@@ -1318,17 +1331,24 @@ export class WebServer extends EventEmitter {
1318
1331
  * while the new recipe writes one, and clearing it later could then never delete the
1319
1332
  * generated dir. `envKeys` is the UNION of saved and applied keys: the tmux session may
1320
1333
  * still carry `setenv`s from both, and a clear has to unset every one.
1334
+ *
1335
+ * `ownEnv` is always passed, empty for a record saved before it existed: `_envOverrides`
1336
+ * holds what the previous run injected, and only this record says what was the session's
1337
+ * own. It meets the owner's grant again, like every other env override rebuilt from disk.
1321
1338
  */
1322
1339
  async _restoreCustomModel(session, saved) {
1323
- const applied = await this._rebuildCustomModelEnv(session, saved);
1340
+ const ownEnv = (await clampEnvOverridesForOwner(session.owner, saved.ownEnv)) ?? {};
1341
+ const applied = await this._rebuildCustomModelEnv(session, saved, ownEnv);
1324
1342
  if (!applied) {
1325
1343
  // Endpoint gone or CLI unregistered: restore the bookkeeping exactly as saved, which
1326
1344
  // is what a later clear needs.
1327
- session.setCustomModel(saved);
1345
+ session.setCustomModel({ ...saved, ownEnv });
1328
1346
  return;
1329
1347
  }
1330
1348
  session.setCustomModel({
1331
1349
  ...saved,
1350
+ ownEnv,
1351
+ homeEnvVar: applied.homeEnvVar ?? saved.homeEnvVar,
1332
1352
  // Prefer the current recipe's dir; keep the saved one when the recipe writes none,
1333
1353
  // so a clear can still delete a directory an older recipe created.
1334
1354
  configDir: applied.configDir ?? saved.configDir,
@@ -1345,8 +1365,9 @@ export class WebServer extends EventEmitter {
1345
1365
  // __attachmentHistory keeps the private (externalPath-bearing) history on disk,
1346
1366
  // separate from the sanitized public attachmentHistory in toState().
1347
1367
  const attachmentHistory = session.getAttachmentHistoryForPersist();
1348
- // __customModel keeps the selection's bookkeeping (injected env KEYS, config dir,
1349
- // launch model; never the values) so recovery can restore and later clear it.
1368
+ // __customModel keeps the selection's bookkeeping (injected env KEYS, config dir, launch
1369
+ // model, the session's own home path; never an injected value or any other own value)
1370
+ // so recovery can restore and later clear it.
1350
1371
  const customModel = session.getCustomModelForPersist();
1351
1372
  const state = {
1352
1373
  ...base,
@@ -1879,7 +1900,9 @@ export class WebServer extends EventEmitter {
1879
1900
  // fs.watch()es it on the LOCAL disk, so a remote session only records the path: the fix plan stays
1880
1901
  // authoritative as for a local session (keeping the output-based completion guessers off), and its
1881
1902
  // file is read over ssh only on an explicit import (fix-plan/read).
1882
- if (!isExternalCliMode(session.mode)) {
1903
+ // ⚠️ An ADOPTED session is skipped outright: Ralph is refused for one anyway, and its
1904
+ // `workingDir` is the neutral wrapper folder, not the foreign pane's work.
1905
+ if (!isExternalCliMode(session.mode) && !session.isAdopted) {
1883
1906
  if (session.remote) {
1884
1907
  session.ralphTracker.setRemoteWorkingDir(session.workingDir);
1885
1908
  }
@@ -1889,8 +1912,10 @@ export class WebServer extends EventEmitter {
1889
1912
  }
1890
1913
  // Start watching for new images in this session's working directory (if enabled globally and per-session).
1891
1914
  // A remote session's workingDir is a path on the remote host, and the watcher watches it on the LOCAL
1892
- // disk, so it is skipped there (the per-session and app-wide toggles skip it too).
1893
- if ((await this.isImageWatcherEnabled()) && session.imageWatcherEnabled && !session.remote) {
1915
+ // disk, so it is skipped there (the per-session and app-wide toggles skip it too). An ADOPTED session
1916
+ // is skipped for the same reason as the Ralph watcher above: nothing in its wrapper folder is the
1917
+ // foreign pane's work.
1918
+ if ((await this.isImageWatcherEnabled()) && session.imageWatcherEnabled && !session.remote && !session.isAdopted) {
1894
1919
  imageWatcher.watchSession(session.id, session.workingDir);
1895
1920
  }
1896
1921
  // Create and attach all listener handlers via dependency injection
@@ -1970,6 +1995,10 @@ export class WebServer extends EventEmitter {
1970
1995
  const session = this.sessions.get(sessionId);
1971
1996
  if (!session)
1972
1997
  return;
1998
+ // ⚠️ A link scanned from an adopted pane was printed by a process on another host or in a
1999
+ // container, while confinement here would be to this host's `/tmp`, the wrapper's folder.
2000
+ if (session.isAdopted)
2001
+ return;
1973
2002
  const event = source === 'codex-generated'
1974
2003
  ? await registerGeneratedArtifactAttachment({
1975
2004
  sessionId,
@@ -2588,6 +2617,8 @@ export class WebServer extends EventEmitter {
2588
2617
  // `undefined` here means an authoritative read listed no single directory
2589
2618
  // for the session (split into several panes, or gone from the socket).
2590
2619
  const paneCwdChanged = getPaneCurrentPath ? session.setPaneCwd(getPaneCurrentPath(muxName)) : false;
2620
+ if (paneCwdChanged)
2621
+ void this.refreshLiveWorktree(session);
2591
2622
  if (session.setPaneExit(getPaneExit(muxName))) {
2592
2623
  this.persistSessionState(session);
2593
2624
  }
@@ -2598,6 +2629,19 @@ export class WebServer extends EventEmitter {
2598
2629
  }
2599
2630
  this.closeCleanlyExitedSessions();
2600
2631
  }
2632
+ /**
2633
+ * Work out which linked worktree a session's pane moved into (#765). In the background, since
2634
+ * it reads the folder, and only after the directory changed, so an idle pane costs nothing.
2635
+ * A reading newer than the one this lookup started from owns the answer.
2636
+ */
2637
+ async refreshLiveWorktree(session) {
2638
+ const cwd = session.paneCwd;
2639
+ const worktree = await findLiveWorktree(session.workingDir, cwd);
2640
+ if (session.paneCwd !== cwd)
2641
+ return;
2642
+ if (session.setLiveWorktree(worktree))
2643
+ this.broadcastSessionStateDebounced(session.id);
2644
+ }
2601
2645
  /**
2602
2646
  * Close every session whose agent exited cleanly, through the same
2603
2647
  * `cleanupSession()` the X button uses (Ark0N/Codeman#446). A pinned session
@@ -3641,6 +3685,14 @@ export class WebServer extends EventEmitter {
3641
3685
  // MuxSession.docker; state.json carries SessionState.docker), so recovery
3642
3686
  // rebuilds the `docker exec` launch instead of a broken local command.
3643
3687
  docker: muxSession.docker ?? savedState?.docker,
3688
+ // Adoption metadata round-trips for the same reason remote/docker do,
3689
+ // and one more: it is the ONLY thing that marks this session as
3690
+ // wrapping a process Codeman never launched. Dropping it on recovery
3691
+ // silently re-enabled respawn, Ralph and hook-backed waits against
3692
+ // someone else's live tmux session after every server restart:
3693
+ // measured, not hypothetical. The mux record is preferred because it
3694
+ // is what `killSession`'s detach-not-kill guard already reads.
3695
+ adopt: muxSession.adopt ?? savedState?.adopt,
3644
3696
  owner: recoveredOwner,
3645
3697
  // Tab lineage survives a restart. It is only decoration, so a parent
3646
3698
  // that did NOT come back is harmless: the frontend draws an edge only
@@ -3895,6 +3947,14 @@ export class WebServer extends EventEmitter {
3895
3947
  for (const session of this.sessions.values()) {
3896
3948
  if (session.mode !== 'claude' || session.remote)
3897
3949
  continue;
3950
+ // ⚠️ An ADOPTED session is not caught by the `remote` check above: its
3951
+ // connection facts live on `adopt.remote` / `adopt.docker`, so a session
3952
+ // wrapping someone's in-container or over-ssh claude looks local here.
3953
+ // Its workspace was never Codeman's to begin with, so installing hooks
3954
+ // there writes `.claude/settings.local.json` into the human's own repo,
3955
+ // on EVERY restart, since this sweep runs from boot recovery.
3956
+ if (session.isAdopted)
3957
+ continue;
3898
3958
  if (session.docker && !session.docker.hooksEnabled)
3899
3959
  continue;
3900
3960
  if (session.workingDir)