claude-code-session-manager 0.92.0 → 0.92.1

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 (31) hide show
  1. package/dist/assets/{AgentLibrary-Bu-1eT_n.js → AgentLibrary-Ci6u03bq.js} +1 -1
  2. package/dist/assets/{DataModel-Bk9rkJ8x.js → DataModel-BIqcjsv2.js} +1 -1
  3. package/dist/assets/{History-BB6wpf02.js → History-BOR_fJNr.js} +1 -1
  4. package/dist/assets/{Hooks-zQZH56Sv.js → Hooks-BtD436iL.js} +1 -1
  5. package/dist/assets/{HostBilko-CW0pHoPS.js → HostBilko-CMe5cH3H.js} +1 -1
  6. package/dist/assets/{Library-DrGdaz9N.js → Library-DeKdY-tD.js} +1 -1
  7. package/dist/assets/{ListDetail-D4bxU_-2.js → ListDetail-CtVjrHKB.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-H1ytN4Tk.js → MarkdownEditor-CbzC-Oqf.js} +1 -1
  9. package/dist/assets/{McpServers-CYSRCI-j.js → McpServers-DKN75Shn.js} +1 -1
  10. package/dist/assets/{Memory-zMNpJyRU.js → Memory-B68AJjg2.js} +1 -1
  11. package/dist/assets/{Panel-RMz3Acpu.js → Panel-C2YTW-qP.js} +1 -1
  12. package/dist/assets/{Permissions-Be6cIkPg.js → Permissions-Cr0RRvNy.js} +1 -1
  13. package/dist/assets/{Plugins-B8sTWf2K.js → Plugins-DbZkJs5l.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-CO-DZN38.js → ProvenanceBadge-kJ__BOQv.js} +1 -1
  15. package/dist/assets/{SaveBar-xQZ2EBOn.js → SaveBar-87ZJfscX.js} +1 -1
  16. package/dist/assets/{Scheduler-CF3zsJBw.js → Scheduler-DdEaxku8.js} +1 -1
  17. package/dist/assets/{ScopeSwitcher-BAfquN6Y.js → ScopeSwitcher-CKzpqjJ_.js} +1 -1
  18. package/dist/assets/{Settings-uPjmqutA.js → Settings-CjywVsNB.js} +1 -1
  19. package/dist/assets/{SkillReferenceGraph-BI1YtCbB.js → SkillReferenceGraph-D_-wg4r9.js} +1 -1
  20. package/dist/assets/{Skills-BEy9FDYd.js → Skills-Cd_yPO_l.js} +1 -1
  21. package/dist/assets/{SystemPrompt-BYkkw-LL.js → SystemPrompt-2z6eGPSF.js} +1 -1
  22. package/dist/assets/{TagLibrary-Kg3yOC5b.js → TagLibrary-DcaWQSp4.js} +1 -1
  23. package/dist/assets/{TiptapBody-BptXAVR9.js → TiptapBody-DYXIp8aG.js} +1 -1
  24. package/dist/assets/{Toggle-BwiClCSI.js → Toggle-CDXR5l3m.js} +1 -1
  25. package/dist/assets/{index-a2MVk8qh.js → index-D5H_H5wC.js} +3 -3
  26. package/dist/assets/{settingsSchema-DgxlStF5.js → settingsSchema-JRDfjgHb.js} +1 -1
  27. package/dist/index.html +1 -1
  28. package/package.json +1 -1
  29. package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +75 -10
  30. package/src/main/build-info.json +4 -4
  31. package/src/main/pty.cjs +16 -2
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-a2MVk8qh.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-D5H_H5wC.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-D3P_jldk.css">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-session-manager",
3
- "version": "0.92.0",
3
+ "version": "0.92.1",
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",
@@ -175,22 +175,87 @@ test('spawn() opens a merged-worktree Epic in the project cwd (dead worktree dir
175
175
  manager.kill(sessionId);
176
176
  });
177
177
 
178
- test('spawn() refuses a completed Epic without spawning and returns the plan message', () => {
179
- const mainCwd = fs.mkdtempSync(path.join(tmpHome, 'sm-pty-closed-'));
180
- const sessionId = 'epic-session-closed';
181
- writeActiveIndexWithWorktree(mainCwd, { epicId: 'epic-c', claudeSessionId: sessionId, worktreeDir: mainCwd });
178
+ function setEpic(mainCwd, epicId, patch) {
182
179
  const idxPath = path.join(mainCwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json');
183
180
  const idx = JSON.parse(fs.readFileSync(idxPath, 'utf8'));
184
- idx.sessions['epic-c'].status = 'completed';
181
+ const rec = idx.sessions[epicId];
182
+ if (patch.status) rec.status = patch.status;
183
+ if (patch.worktreeStatus) rec.worktree.status = patch.worktreeStatus;
185
184
  fs.writeFileSync(idxPath, JSON.stringify(idx));
185
+ }
186
186
 
187
- let spawned = false;
188
- nodePty.spawn = () => {
189
- spawned = true;
187
+ function captureSpawn() {
188
+ const out = { opts: null, count: 0 };
189
+ nodePty.spawn = (shell, args, opts) => {
190
+ out.opts = opts;
191
+ out.count += 1;
190
192
  return fakeProc();
191
193
  };
194
+ return out;
195
+ }
196
+
197
+ function fakeWindow(sent) {
198
+ return {
199
+ isDestroyed: () => false,
200
+ webContents: { isDestroyed: () => false, isCrashed: () => false, send: (channel, payload) => sent.push({ channel, payload }) },
201
+ };
202
+ }
203
+
204
+ test('spawn() opens a completed Epic (merged worktree) as a shell in the project cwd with an informational line', () => {
205
+ const mainCwd = fs.mkdtempSync(path.join(tmpHome, 'sm-pty-closed-'));
206
+ const sessionId = 'epic-session-closed';
207
+ const deadDir = path.join(os.tmpdir(), 'sm-pty-closed-dead-worktree');
208
+ writeActiveIndexWithWorktree(mainCwd, { epicId: 'epic-c', claudeSessionId: sessionId, worktreeDir: deadDir });
209
+ setEpic(mainCwd, 'epic-c', { status: 'completed', worktreeStatus: 'merged' });
210
+
211
+ const cap = captureSpawn();
212
+ const sent = [];
213
+ manager.window = fakeWindow(sent);
214
+ try {
215
+ const result = manager.spawn({ tabId: sessionId, cwd: mainCwd, cols: 80, rows: 24 });
216
+ expect(cap.count).toBe(1);
217
+ expect(cap.opts.cwd).toBe(mainCwd);
218
+ expect(result.pid).toBe(4242);
219
+ expect(result.error).toBeUndefined();
220
+ const info = sent.find((m) => m.channel === `pty:data:${sessionId}`);
221
+ expect(info.payload).toMatch(/Epic is completed/);
222
+ expect(info.payload).toContain(mainCwd);
223
+ } finally {
224
+ manager.window = null;
225
+ manager.kill(sessionId);
226
+ }
227
+ });
228
+
229
+ test('spawn() opens a completed Epic whose worktree dir still exists in that worktree dir', () => {
230
+ const mainCwd = fs.mkdtempSync(path.join(tmpHome, 'sm-pty-closed-live-'));
231
+ const worktreeCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-pty-closed-live-wt-'));
232
+ const sessionId = 'epic-session-closed-live';
233
+ writeActiveIndexWithWorktree(mainCwd, { epicId: 'epic-cl', claudeSessionId: sessionId, worktreeDir: worktreeCwd });
234
+ setEpic(mainCwd, 'epic-cl', { status: 'completed' });
235
+
236
+ const cap = captureSpawn();
237
+ manager.spawn({ tabId: sessionId, cwd: mainCwd, cols: 80, rows: 24 });
238
+ expect(cap.count).toBe(1);
239
+ expect(cap.opts.cwd).toBe(worktreeCwd);
240
+ manager.kill(sessionId);
241
+ fs.rmSync(worktreeCwd, { recursive: true, force: true });
242
+ });
243
+
244
+ test('spawn() still refuses session_unreachable without any node-pty spawn', () => {
245
+ const mainCwd = fs.mkdtempSync(path.join(tmpHome, 'sm-pty-unreachable-'));
246
+ const sessionId = 'epic-session-unreachable';
247
+ const deadDir = path.join(os.tmpdir(), 'sm-pty-unreachable-dead-worktree');
248
+ writeActiveIndexWithWorktree(mainCwd, { epicId: 'epic-u', claudeSessionId: sessionId, worktreeDir: deadDir });
249
+ setEpic(mainCwd, 'epic-u', { worktreeStatus: 'merged' });
250
+ // Transcript exists only under the dead worktree's encoded project dir.
251
+ const enc = deadDir.replace(/[^a-zA-Z0-9]/g, '-');
252
+ const tdir = path.join(tmpHome, '.claude', 'projects', enc);
253
+ fs.mkdirSync(tdir, { recursive: true });
254
+ fs.writeFileSync(path.join(tdir, `${sessionId}.jsonl`), '{"type":"user"}\n');
255
+
256
+ const cap = captureSpawn();
192
257
  const result = manager.spawn({ tabId: sessionId, cwd: mainCwd, cols: 80, rows: 24 });
193
- expect(spawned).toBe(false);
258
+ expect(cap.count).toBe(0);
194
259
  expect(result.pid).toBeNull();
195
- expect(result.error).toMatch(/closed/);
260
+ expect(result.error).toMatch(/cannot be resumed/);
196
261
  });
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "0.92.0",
3
- "gitSha": "d0e160e50a2560620e956e6d819cb405b0423110",
4
- "gitShortSha": "d0e160e",
2
+ "version": "0.92.1",
3
+ "gitSha": "cc3a80b2b9933e408e8effca99e550e3409b4d42",
4
+ "gitShortSha": "cc3a80b",
5
5
  "gitBranch": "HEAD",
6
- "builtAt": "2026-09-20T21:26:16.666Z",
6
+ "builtAt": "2026-09-20T22:38:10.510Z",
7
7
  "dirty": false
8
8
  }
package/src/main/pty.cjs CHANGED
@@ -23,6 +23,7 @@ const { checkInsideHome } = require('./lib/insideHome.cjs');
23
23
  const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
24
24
  const opsErrorLog = require('./lib/opsErrorLog.cjs');
25
25
  const { planEpicSpawn } = require('./lib/epicSpawnPlan.cjs');
26
+ const { resolveEpicSpawnCwd } = require('./lib/epicSpawnCwd.cjs');
26
27
  const telemetryCounters = require('./lib/telemetryCounters.cjs');
27
28
 
28
29
  // Absolute path to the installed package root (src/main/ -> ../../), shown in
@@ -180,8 +181,22 @@ class PtyManager {
180
181
  // so this asks the same decision function chatRunner.cjs does (epicSpawnPlan.cjs). `cwd`
181
182
  // itself is NEVER repointed; only this actual PTY spawn option is (epicSpawnCwd.cjs's
182
183
  // ops-root hazard). A refused plan is surfaced in-tab rather than spawning into a bad cwd.
184
+ // Split: Chat and Terminal share this one verdict function, but the Terminal additionally
185
+ // downgrades `epic_closed` to a plain shell — it opens `$SHELL -il` (no --resume, no
186
+ // transcript write), not a chat turn. That path uses the read-side resolver WITHOUT
187
+ // `deps.restore`, so opening a closed Epic's terminal never re-attaches a checkout.
183
188
  const plan = planEpicSpawn({ cwd, claudeSessionId: tabId });
184
- if (!plan.ok) {
189
+ let spawnCwd;
190
+ if (plan.ok) {
191
+ spawnCwd = plan.execCwd;
192
+ } else if (plan.code === 'epic_closed') {
193
+ spawnCwd = resolveEpicSpawnCwd({ cwd, claudeSessionId: tabId });
194
+ sendIfAlive(
195
+ this.window,
196
+ `pty:data:${tabId}`,
197
+ `\r\nThis Epic is completed \u2014 opening a read-only shell in ${spawnCwd}. Start a new Epic for new work.\r\n`,
198
+ );
199
+ } else {
185
200
  opsErrorLog.appendError({
186
201
  cwd,
187
202
  scope: 'pty',
@@ -193,7 +208,6 @@ class PtyManager {
193
208
  setImmediate(() => sendIfAlive(this.window, `pty:exit:${tabId}`, { exitCode: 1, signal: undefined }));
194
209
  return { pid: null, cwd, reattached: false, error: plan.message };
195
210
  }
196
- const spawnCwd = plan.execCwd;
197
211
  if (spawnCwd !== cwd) {
198
212
  console.log(`[pty] tabId=${tabId} isolated in worktree ${spawnCwd}`);
199
213
  }