claude-code-session-manager 0.40.1 → 0.40.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -7,7 +7,7 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
10
- <script type="module" crossorigin src="./assets/index-BbI9slj8.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-bdqOSyxG.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
12
12
  <link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
13
13
  <link rel="stylesheet" crossorigin href="./assets/index-BxVBtmjA.css">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-session-manager",
3
- "version": "0.40.1",
3
+ "version": "0.40.2",
4
4
  "description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
5
5
  "type": "module",
6
6
  "main": "src/main/index.cjs",
package/src/main/pty.cjs CHANGED
@@ -25,6 +25,11 @@ const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
25
25
  // the remediation message so the user can cd there and rebuild.
26
26
  const PKG_DIR = path.join(__dirname, '..', '..');
27
27
 
28
+ // Per-session replay buffer ceiling. Big enough that switching away from an
29
+ // Epic and back shows a meaningful working history, small enough that a dozen
30
+ // chatty sessions can't balloon main-process memory.
31
+ const REPLAY_BUFFER_MAX = 256 * 1024;
32
+
28
33
  /**
29
34
  * ANSI-formatted terminal text explaining a native-module / immediate-exit
30
35
  * failure and exactly how to fix it. Written straight into the tab's output so
@@ -60,6 +65,13 @@ class PtyManager {
60
65
  constructor() {
61
66
  this.sessions = new Map(); // tabId -> { proc, cwd, created }
62
67
  this.killed = new Set(); // tabIds explicitly killed — suppress their exit events
68
+ // tabId -> recent output, replayed verbatim on reattach. An Epic's
69
+ // Terminal pane unmounts whenever the user views a DIFFERENT Epic while
70
+ // its claude keeps running (Epic ⇄ claude-session is 1:1 and must survive
71
+ // browsing), so "pre-reattach output is lost" — tolerable for a dev
72
+ // reload — would read as the session having been wiped. Bounded ring so a
73
+ // long-lived session can't grow this without limit.
74
+ this.buffers = new Map();
63
75
  this.window = null;
64
76
  }
65
77
 
@@ -104,6 +116,15 @@ class PtyManager {
104
116
  } catch {
105
117
  /* pty may have exited between the check and the resize */
106
118
  }
119
+ // Replay recent output so a reattached viewport isn't blank. The caller
120
+ // registers its pty:data listener BEFORE invoking spawn (see
121
+ // EpicTerminalPane / Terminal), and this fires on the next tick, so the
122
+ // listener is always in place by the time the replay lands. Sent as one
123
+ // chunk ahead of any live data, preserving ordering.
124
+ const replay = this.buffers.get(tabId);
125
+ if (replay) {
126
+ setImmediate(() => sendIfAlive(this.window, `pty:data:${tabId}`, replay));
127
+ }
107
128
  // If the process has already exited but its session wasn't cleaned up,
108
129
  // fire a synthetic exit after the renderer re-registers its onExit handler.
109
130
  if (existing.proc.exitCode != null) {
@@ -154,6 +175,7 @@ class PtyManager {
154
175
 
155
176
  proc.onData((data) => {
156
177
  gotData = true;
178
+ this.#appendReplay(tabId, data);
157
179
  sendIfAlive(this.window, `pty:data:${tabId}`, data);
158
180
  });
159
181
 
@@ -164,6 +186,7 @@ class PtyManager {
164
186
  if (this.killed.delete(tabId)) {
165
187
  console.log('[pty] suppressed exit broadcast for killed tabId=', tabId);
166
188
  this.sessions.delete(tabId);
189
+ this.buffers.delete(tabId);
167
190
  return;
168
191
  }
169
192
  // Fast-exit detector: a shell that dies in <1.2s with a non-zero status
@@ -176,12 +199,22 @@ class PtyManager {
176
199
  }
177
200
  sendIfAlive(this.window, `pty:exit:${tabId}`, { exitCode, signal });
178
201
  this.sessions.delete(tabId);
202
+ this.buffers.delete(tabId);
179
203
  });
180
204
 
181
205
  this.sessions.set(tabId, { proc, cwd, created: spawnedAt });
182
206
  return { pid: proc.pid, cwd, reattached: false };
183
207
  }
184
208
 
209
+ /** Append to a session's bounded replay buffer, trimming oldest output. */
210
+ #appendReplay(tabId, data) {
211
+ const next = (this.buffers.get(tabId) ?? '') + data;
212
+ this.buffers.set(
213
+ tabId,
214
+ next.length > REPLAY_BUFFER_MAX ? next.slice(next.length - REPLAY_BUFFER_MAX) : next,
215
+ );
216
+ }
217
+
185
218
  write({ tabId, data }) {
186
219
  const s = this.sessions.get(tabId);
187
220
  if (!s) {
@@ -225,6 +258,7 @@ class PtyManager {
225
258
  /* already dead */
226
259
  }
227
260
  this.sessions.delete(tabId);
261
+ this.buffers.delete(tabId);
228
262
  }
229
263
  }
230
264