claude-code-remote-pilot 0.5.2 → 0.5.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.4 — 2026-05-06
4
+
5
+ ### Fixed
6
+ - **Web UI respawn completed**: offline session respawn now has loading/error feedback and immediately updates session detail state after success.
7
+ - **Web respawn now matches CLI spawn behavior**: respawn starts a fresh session from stored path and default command semantics (same as watch-mode spawn), avoiding failures caused by stale stored command values.
8
+ - **Respawn loading no longer appears stuck**: web respawn now has a request timeout and surfaces a clear timeout error when the API call hangs, so the button always returns to clickable state.
9
+
10
+ ---
11
+
3
12
  ## 0.5.2 — 2026-05-06
4
13
 
5
14
  ### Fixed
@@ -35,9 +35,10 @@ class SessionManager {
35
35
  if (this.sessions.has(sessionName)) throw new Error(`Session "${sessionName}" already exists.`);
36
36
 
37
37
  // Kill stale tmux session from a previous crashed run
38
- try { execSync(`tmux has-session -t "${sessionName}"`, { stdio: 'ignore' });
38
+ try {
39
+ execSync(`tmux has-session -t "${sessionName}"`, { stdio: 'ignore' });
39
40
  execSync(`tmux kill-session -t "${sessionName}"`, { stdio: 'ignore' });
40
- } catch {}
41
+ } catch { }
41
42
 
42
43
  execSync(`tmux new-session -d -s "${sessionName}" -c "${resolved}" "${command}"`, { stdio: 'ignore' });
43
44
 
@@ -73,13 +74,13 @@ class SessionManager {
73
74
  const entry = this.sessions.get(name);
74
75
  if (!entry) throw new Error(`Session "${name}" not found.`);
75
76
  entry.watcher.stop();
76
- try { execSync(`tmux kill-session -t "${name}"`, { stdio: 'ignore' }); } catch {}
77
+ try { execSync(`tmux kill-session -t "${name}"`, { stdio: 'ignore' }); } catch { }
77
78
  this.sessions.delete(name);
78
79
  }
79
80
 
80
81
  killAll() {
81
82
  for (const name of [...this.sessions.keys()]) {
82
- try { this.kill(name); } catch {}
83
+ try { this.kill(name); } catch { }
83
84
  }
84
85
  }
85
86
 
package/lib/ui.html CHANGED
@@ -574,11 +574,13 @@ function DashboardScreen({ onNavigate, sessions, activity, serverStatus }) {
574
574
  }
575
575
 
576
576
  /* --- Session Detail --- */
577
- function SessionDetailScreen({ session, onBack, onKilled }) {
577
+ function SessionDetailScreen({ session, onBack, onKilled, onRespawned }) {
578
578
  const [output, setOutput] = useState('');
579
579
  const [msg, setMsg] = useState('');
580
580
  const [sending, setSending] = useState(false);
581
581
  const [killing, setKilling] = useState(false);
582
+ const [respawning, setRespawning] = useState(false);
583
+ const [respawnError, setRespawnError] = useState('');
582
584
  const [copyOk, setCopyOk] = useState(false);
583
585
  const terminalRef = useRef(null);
584
586
  const inputRef = useRef(null);
@@ -661,14 +663,31 @@ function SessionDetailScreen({ session, onBack, onKilled }) {
661
663
  };
662
664
 
663
665
  const handleRespawn = async () => {
666
+ if (respawning) return;
667
+ setRespawning(true);
668
+ setRespawnError('');
669
+ const controller = new AbortController();
670
+ const timeoutId = setTimeout(() => controller.abort(), 12000);
664
671
  try {
665
- const res = await apiFetch(`/api/sessions/${encodeURIComponent(session.name)}/respawn`, { method: 'POST' });
672
+ const res = await apiFetch(`/api/sessions/${encodeURIComponent(session.name)}/respawn`, {
673
+ method: 'POST',
674
+ signal: controller.signal,
675
+ });
676
+ const data = await res.json();
666
677
  if (!res.ok) {
667
- const d = await res.json();
668
- alert(d.error || 'Failed to respawn');
678
+ setRespawnError(data.error || 'Failed to respawn');
679
+ return;
669
680
  }
681
+ onRespawned(data);
670
682
  } catch (e) {
671
- if (e.message !== 'Unauthorized') alert('Network error');
683
+ if (e && e.name === 'AbortError') {
684
+ setRespawnError('Respawn timeout. Please try again.');
685
+ return;
686
+ }
687
+ if (e.message !== 'Unauthorized') setRespawnError('Network error');
688
+ } finally {
689
+ clearTimeout(timeoutId);
690
+ setRespawning(false);
672
691
  }
673
692
  };
674
693
 
@@ -700,9 +719,16 @@ function SessionDetailScreen({ session, onBack, onKilled }) {
700
719
  </div>
701
720
  <div className="detail-actions">
702
721
  {isOffline && (
703
- <button className="btn btn-sm btn-primary" onClick={handleRespawn}>
704
- Respawn
705
- </button>
722
+ <>
723
+ <button className="btn btn-sm btn-primary" onClick={handleRespawn} disabled={respawning}>
724
+ {respawning ? 'Respawning…' : '↺ Respawn'}
725
+ </button>
726
+ {respawnError && (
727
+ <span style={{ color: 'var(--error)', fontSize: 12, alignSelf: 'center' }}>
728
+ {respawnError}
729
+ </span>
730
+ )}
731
+ </>
706
732
  )}
707
733
  {!isOffline && (
708
734
  <button className="btn btn-sm" onClick={copyAttachCmd} title={`tmux attach -t ${session.name}`}>
@@ -1047,7 +1073,13 @@ function App() {
1047
1073
  case 'dashboard': return <DashboardScreen onNavigate={navigate} sessions={sessions} activity={activity} serverStatus={serverStatus} />;
1048
1074
  case 'sessions': return <SessionsScreen sessions={sessions} onNavigate={navigate} />;
1049
1075
  case 'create': return <CreateSessionScreen onBack={() => navigate('dashboard')} onCreated={s => navigate('detail', s)} />;
1050
- case 'detail': return <SessionDetailScreen session={selectedSession} onBack={() => navigate('dashboard')} onKilled={() => navigate('sessions')} />;
1076
+ case 'detail': return <SessionDetailScreen session={selectedSession} onBack={() => navigate('dashboard')} onKilled={() => navigate('sessions')} onRespawned={(respawned) => {
1077
+ setSessions(prev => {
1078
+ const next = prev.filter(s => s.name !== respawned.name);
1079
+ return [{ ...respawned, id: respawned.name }, ...next];
1080
+ });
1081
+ setSelectedSession({ ...respawned, id: respawned.name });
1082
+ }} />;
1051
1083
  default: return <DashboardScreen onNavigate={navigate} sessions={sessions} activity={activity} serverStatus={serverStatus} />;
1052
1084
  }
1053
1085
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-remote-pilot",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Interactive Claude Code supervisor — spawn and monitor multiple Claude sessions from a single terminal.",
5
5
  "type": "commonjs",
6
6
  "repository": {