claude-code-session-manager 0.91.0 → 0.92.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 (47) hide show
  1. package/dist/assets/{AgentLibrary-zkgdnCnG.js → AgentLibrary-Bu-1eT_n.js} +1 -1
  2. package/dist/assets/{DataModel-CNFdqxrR.js → DataModel-Bk9rkJ8x.js} +1 -1
  3. package/dist/assets/{History-C-qijfXz.js → History-BB6wpf02.js} +1 -1
  4. package/dist/assets/{Hooks-DGxt1nBd.js → Hooks-zQZH56Sv.js} +1 -1
  5. package/dist/assets/{HostBilko-sIYTK-m9.js → HostBilko-CW0pHoPS.js} +1 -1
  6. package/dist/assets/{Library-51Jx0oGl.js → Library-DrGdaz9N.js} +1 -1
  7. package/dist/assets/{ListDetail-ByNJU-ie.js → ListDetail-D4bxU_-2.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-Wwcpe9O1.js → MarkdownEditor-H1ytN4Tk.js} +1 -1
  9. package/dist/assets/{McpServers-Bs5xPbTG.js → McpServers-CYSRCI-j.js} +1 -1
  10. package/dist/assets/{Memory-BsHGInmd.js → Memory-zMNpJyRU.js} +1 -1
  11. package/dist/assets/{Panel-ilEDcV0m.js → Panel-RMz3Acpu.js} +1 -1
  12. package/dist/assets/{Permissions-Djgc3TFk.js → Permissions-Be6cIkPg.js} +1 -1
  13. package/dist/assets/{Plugins-BBojr1mt.js → Plugins-B8sTWf2K.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-Cz1VLnXb.js → ProvenanceBadge-CO-DZN38.js} +1 -1
  15. package/dist/assets/{SaveBar-Cj0wz84u.js → SaveBar-xQZ2EBOn.js} +1 -1
  16. package/dist/assets/{Scheduler-D4Ekm93R.js → Scheduler-CF3zsJBw.js} +1 -1
  17. package/dist/assets/{ScopeSwitcher-DA5MqWli.js → ScopeSwitcher-BAfquN6Y.js} +1 -1
  18. package/dist/assets/{Settings-B1Kfgjvl.js → Settings-uPjmqutA.js} +1 -1
  19. package/dist/assets/{SkillReferenceGraph-Dew-1uMy.js → SkillReferenceGraph-BI1YtCbB.js} +1 -1
  20. package/dist/assets/{Skills-DzuhnXy9.js → Skills-BEy9FDYd.js} +1 -1
  21. package/dist/assets/{SystemPrompt-CNcCwEev.js → SystemPrompt-BYkkw-LL.js} +1 -1
  22. package/dist/assets/{TagLibrary-D0plTZgP.js → TagLibrary-Kg3yOC5b.js} +1 -1
  23. package/dist/assets/{TiptapBody-BlmUt7SR.js → TiptapBody-BptXAVR9.js} +1 -1
  24. package/dist/assets/{Toggle-yTfx2R-e.js → Toggle-BwiClCSI.js} +1 -1
  25. package/dist/assets/{index-CRPJslSA.css → index-D3P_jldk.css} +1 -1
  26. package/dist/assets/{index-ju-h7BFf.js → index-a2MVk8qh.js} +329 -329
  27. package/dist/assets/{settingsSchema-BkcnLBYk.js → settingsSchema-DgxlStF5.js} +1 -1
  28. package/dist/index.html +2 -2
  29. package/package.json +1 -1
  30. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +52 -3
  31. package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +40 -0
  32. package/src/main/build-info.json +4 -4
  33. package/src/main/chatRunner.cjs +27 -4
  34. package/src/main/config.cjs +5 -0
  35. package/src/main/docEdit.cjs +3 -2
  36. package/src/main/index.cjs +2 -0
  37. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +58 -2
  38. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +79 -0
  39. package/src/main/lib/__tests__/updateCheck.test.cjs +63 -0
  40. package/src/main/lib/epicSpawnCwd.cjs +22 -13
  41. package/src/main/lib/epicSpawnPlan.cjs +27 -12
  42. package/src/main/lib/epicTranscriptPath.cjs +3 -2
  43. package/src/main/lib/epicWorktreeMerge.cjs +4 -4
  44. package/src/main/lib/updateCheck.cjs +102 -0
  45. package/src/main/pty.cjs +19 -7
  46. package/src/preload/api.d.ts +2 -0
  47. package/src/preload/index.cjs +1 -0
@@ -17,10 +17,10 @@
17
17
  * On success the worktree checkout is torn down (cleanupEpicWorktree) since
18
18
  * its content now safely lives on the main tree. On a real conflict, the
19
19
  * branch and worktree directory are left intact — never silently discarded
20
- * — so a human can still open a Terminal into `worktree.dir` (PRD 1033's
21
- * existing epicSpawnCwd.cjs wiring — it keys off `worktree.dir` alone, not
22
- * `worktree.status`, so a conflicted Epic's Terminal spawn keeps landing
23
- * there with no new code path needed) and resolve it manually before
20
+ * — so a human can still open a Terminal into `worktree.dir` (the spawn
21
+ * resolution in epicSpawnCwd.cjs is status-aware: `needs_merge_resolution`
22
+ * with an existing dir still lands there, while `merged`/`disabled` skip the
23
+ * re-attach and fall back to the project cwd) and resolve it manually before
24
24
  * re-running this same action.
25
25
  */
26
26
 
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * updateCheck.cjs — bounded, offline-safe, opt-out "is a newer build published?" probe.
5
+ *
6
+ * getUpdateStatus() → { current, latest, behind }. Never throws/rejects; any failure
7
+ * (offline, DNS, non-200, bad JSON, timeout) → { current, latest: null, behind: false }.
8
+ * The request is a bare GET (no headers/cookies/query/body) so it carries nothing that
9
+ * identifies the user or machine. Disabled by SM_UPDATE_CHECK=0 or E2E mode (SM_E2E=1).
10
+ */
11
+
12
+ const https = require('node:https');
13
+ const fs = require('node:fs');
14
+ const path = require('node:path');
15
+
16
+ const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
17
+ const REQUEST_TIMEOUT_MS = 3000;
18
+ const CACHE_FILE = 'update-check.json';
19
+
20
+ /** Plain GET → parsed JSON. Rejects on non-200, bad JSON, or timeout. */
21
+ function httpGetJson(url, timeoutMs) {
22
+ return new Promise((resolve, reject) => {
23
+ const req = https.get(url, { timeout: timeoutMs }, (res) => {
24
+ if (res.statusCode !== 200) {
25
+ res.resume();
26
+ reject(new Error(`HTTP ${res.statusCode}`));
27
+ return;
28
+ }
29
+ let body = '';
30
+ res.setEncoding('utf8');
31
+ res.on('data', (c) => {
32
+ body += c;
33
+ if (body.length > 65536) req.destroy(new Error('response too large'));
34
+ });
35
+ res.on('end', () => {
36
+ try { resolve(JSON.parse(body)); } catch (e) { reject(e); }
37
+ });
38
+ res.on('error', reject);
39
+ });
40
+ req.on('timeout', () => req.destroy(new Error('timeout')));
41
+ req.on('error', reject);
42
+ });
43
+ }
44
+
45
+ function parseTriple(v) {
46
+ const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(v || ''));
47
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
48
+ }
49
+
50
+ /** True iff `current` is strictly older than `latest` (numeric major.minor.patch). */
51
+ function isBehind(current, latest) {
52
+ const a = parseTriple(current);
53
+ const b = parseTriple(latest);
54
+ if (!a || !b) return false;
55
+ for (let i = 0; i < 3; i++) {
56
+ if (a[i] !== b[i]) return a[i] < b[i];
57
+ }
58
+ return false;
59
+ }
60
+
61
+ function readCache(file, now) {
62
+ try {
63
+ const c = JSON.parse(fs.readFileSync(file, 'utf8'));
64
+ if (c && typeof c.latest === 'string' && typeof c.checkedAt === 'number'
65
+ && now - c.checkedAt >= 0 && now - c.checkedAt < CACHE_TTL_MS) return c.latest;
66
+ } catch { /* missing/corrupt cache = miss */ }
67
+ return null;
68
+ }
69
+
70
+ function defaultPackageName() {
71
+ return require(path.join(__dirname, '..', '..', '..', 'package.json')).name;
72
+ }
73
+
74
+ async function getUpdateStatus({ deps } = {}) {
75
+ const d = deps || {};
76
+ let current = '';
77
+ try {
78
+ const app = d.app || require('electron').app;
79
+ current = app.getVersion();
80
+ const env = d.env || process.env;
81
+ if (env.SM_UPDATE_CHECK === '0' || env.SM_E2E === '1') return { current, latest: null, behind: false };
82
+
83
+ const now = (d.now || Date.now)();
84
+ const file = path.join(app.getPath('userData'), CACHE_FILE);
85
+ let latest = readCache(file, now);
86
+ if (latest == null) {
87
+ const name = (d.packageName || defaultPackageName)();
88
+ const url = `https://registry.npmjs.org/-/package/${name}/dist-tags`;
89
+ const tags = await (d.fetchJson || httpGetJson)(url, REQUEST_TIMEOUT_MS);
90
+ latest = tags && typeof tags.latest === 'string' ? tags.latest : null;
91
+ if (latest == null) return { current, latest: null, behind: false };
92
+ try {
93
+ await (d.writeJson || require('../config.cjs').writeJson)(file, { checkedAt: now, latest });
94
+ } catch { /* cache is best-effort */ }
95
+ }
96
+ return { current, latest, behind: isBehind(current, latest) };
97
+ } catch {
98
+ return { current, latest: null, behind: false };
99
+ }
100
+ }
101
+
102
+ module.exports = { getUpdateStatus, isBehind };
package/src/main/pty.cjs CHANGED
@@ -22,7 +22,7 @@ const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
22
22
  const { checkInsideHome } = require('./lib/insideHome.cjs');
23
23
  const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
24
24
  const opsErrorLog = require('./lib/opsErrorLog.cjs');
25
- const { resolveEpicSpawnCwd } = require('./lib/epicSpawnCwd.cjs');
25
+ const { planEpicSpawn } = require('./lib/epicSpawnPlan.cjs');
26
26
  const telemetryCounters = require('./lib/telemetryCounters.cjs');
27
27
 
28
28
  // Absolute path to the installed package root (src/main/ -> ../../), shown in
@@ -176,12 +176,24 @@ class PtyManager {
176
176
 
177
177
  const shell = process.env.SHELL || '/bin/bash';
178
178
  console.log('[pty] spawning shell', shell);
179
- // Tab ID = claudeSessionId (see CLAUDE.md), so for an Epic-attached tab
180
- // this resolves the Epic's own record in `cwd`'s active-index.json and
181
- // returns its isolated worktree dir when one exists — `cwd` itself is
182
- // NEVER repointed; only this actual PTY spawn option is. See
183
- // epicSpawnCwd.cjs's header comment (the ops-root hazard) for why.
184
- const spawnCwd = resolveEpicSpawnCwd({ cwd, claudeSessionId: tabId, deps: { restore: true } });
179
+ // Tab ID = claudeSessionId (see CLAUDE.md), and Chat/Terminal are two views over ONE session,
180
+ // so this asks the same decision function chatRunner.cjs does (epicSpawnPlan.cjs). `cwd`
181
+ // itself is NEVER repointed; only this actual PTY spawn option is (epicSpawnCwd.cjs's
182
+ // ops-root hazard). A refused plan is surfaced in-tab rather than spawning into a bad cwd.
183
+ const plan = planEpicSpawn({ cwd, claudeSessionId: tabId });
184
+ if (!plan.ok) {
185
+ opsErrorLog.appendError({
186
+ cwd,
187
+ scope: 'pty',
188
+ tabId,
189
+ tags: ['spawn-refused', plan.code],
190
+ message: plan.message,
191
+ });
192
+ sendIfAlive(this.window, `pty:data:${tabId}`, `\r\n${plan.message}\r\n`);
193
+ setImmediate(() => sendIfAlive(this.window, `pty:exit:${tabId}`, { exitCode: 1, signal: undefined }));
194
+ return { pid: null, cwd, reattached: false, error: plan.message };
195
+ }
196
+ const spawnCwd = plan.execCwd;
185
197
  if (spawnCwd !== cwd) {
186
198
  console.log(`[pty] tabId=${tabId} isolated in worktree ${spawnCwd}`);
187
199
  }
@@ -1541,6 +1541,8 @@ export interface ChatExternalSendEvent {
1541
1541
  export interface SessionManagerAPI {
1542
1542
  app: {
1543
1543
  version: () => Promise<string>;
1544
+ /** Published-`latest` probe (cached 6h, opt-out SM_UPDATE_CHECK=0). Never rejects; offline → latest null. */
1545
+ updateStatus: () => Promise<{ current: string; latest: string | null; behind: boolean }>;
1544
1546
  homeDir: () => Promise<string>;
1545
1547
  cwd: () => Promise<string>;
1546
1548
  engageRulesPath: () => Promise<string | null>;
@@ -3,6 +3,7 @@ const { contextBridge, ipcRenderer } = require('electron');
3
3
  contextBridge.exposeInMainWorld('api', {
4
4
  app: {
5
5
  version: () => ipcRenderer.invoke('app:version'),
6
+ updateStatus: () => ipcRenderer.invoke('app:update-status'),
6
7
  homeDir: () => ipcRenderer.invoke('app:home-dir'),
7
8
  cwd: () => ipcRenderer.invoke('app:cwd'),
8
9
  engageRulesPath: () => ipcRenderer.invoke('app:engage-rules-path'),