clideck 1.25.8 → 1.26.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.25.8",
3
+ "version": "1.26.0",
4
4
  "description": "One screen for all your AI coding agents — run, monitor, and manage multiple CLI agents from a single browser tab",
5
5
  "main": "server.js",
6
6
  "bin": {
package/sessions.js CHANGED
@@ -104,14 +104,29 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
104
104
 
105
105
  term.onExit(() => {
106
106
  // Skip cleanup if this PTY was replaced by a restart
107
- if (sessions.get(id)?.pty !== term) return;
107
+ const s = sessions.get(id);
108
+ if (s?.pty !== term) return;
108
109
  activity.clear(id);
109
110
  telemetry.clear(id);
110
111
  opencodeBridge.clear(id);
111
- transcript.clear(id);
112
112
  plugins.clearStatus(id);
113
+ // If resumable and token captured, move to resumable list (keep transcript for search)
114
+ if (cmd.canResume && cmd.resumeCommand && s.sessionToken) {
115
+ resumable.push({
116
+ id, name: s.name, commandId: s.commandId, presetId: s.presetId || 'shell', cwd: s.cwd,
117
+ themeId: s.themeId, sessionToken: s.sessionToken, projectId: s.projectId, muted: !!s.muted,
118
+ lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
119
+ savedAt: new Date().toISOString(),
120
+ });
121
+ console.log(`Session ${id.slice(0, 8)}: moved to resumable on exit (token: ${s.sessionToken.slice(0, 12)}…)`);
122
+ } else {
123
+ transcript.clear(id);
124
+ }
113
125
  sessions.delete(id);
114
126
  broadcast({ type: 'closed', id });
127
+ if (cmd.canResume && s.sessionToken) {
128
+ broadcast({ type: 'sessions.resumable', list: getResumable() });
129
+ }
115
130
  });
116
131
 
117
132
  return null;
@@ -79,6 +79,7 @@ function handleLogs(req, res) {
79
79
  // Telemetry-based status
80
80
  const startEvents = new Set(['user_prompt', 'gemini_cli.user_prompt', 'codex.user_prompt']);
81
81
  if (startEvents.has(eventName)) {
82
+ cancelPendingIdle(resolvedId);
82
83
  broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
83
84
  }
84
85
  // Claude: telemetry-only status. api_request → pending idle (confirm after 1s output silence, expire after 6s).
@@ -90,20 +91,22 @@ function handleLogs(req, res) {
90
91
  broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
91
92
  }
92
93
  }
93
- // Codex: telemetry-only status. codex.user_prompt/any event working, codex.sse_eventidle.
94
+ // Codex: codex.sse_eventpending idle (2s PTY silence), other events working.
94
95
  if (serviceName === 'codex_cli_rs' && eventName) {
95
96
  if (eventName === 'codex.sse_event') {
96
- broadcastFn?.({ type: 'session.status', id: resolvedId, working: false, source: 'telemetry' });
97
+ startPendingIdle(resolvedId);
97
98
  } else if (eventName !== 'codex.user_prompt') {
99
+ cancelPendingIdle(resolvedId);
98
100
  broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
99
101
  }
100
102
  }
101
- // Gemini: telemetry-only status. Whitelisted events working, api_response (role=main) → idle.
103
+ // Gemini: api_response (role=main) pending idle (2s PTY silence), whitelisted events working.
102
104
  if (serviceName === 'gemini-cli' && eventName) {
103
105
  if (eventName === 'gemini_cli.api_response' && attrs['role'] === 'main') {
104
- broadcastFn?.({ type: 'session.status', id: resolvedId, working: false, source: 'telemetry' });
106
+ startPendingIdle(resolvedId);
105
107
  } else if (eventName === 'gemini_cli.api_request' || eventName === 'gemini_cli.model_routing'
106
108
  || (eventName === 'gemini_cli.api_response' && attrs['role'] !== 'main')) {
109
+ cancelPendingIdle(resolvedId);
107
110
  broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
108
111
  }
109
112
  }
@@ -147,14 +150,12 @@ function cancelPendingSetup(sessionId) {
147
150
  }
148
151
  }
149
152
 
150
- // Pending idle: api_request starts a check loop. Confirm idle after 1s of output silence. Expire after 6s.
153
+ // Pending idle: starts a check loop. Confirm idle after 2s of PTY output silence.
151
154
  function startPendingIdle(id) {
152
155
  cancelPendingIdle(id);
153
156
  const started = Date.now();
154
157
  const check = setInterval(() => {
155
- const elapsed = Date.now() - started;
156
- if (elapsed > 6000) { cancelPendingIdle(id); return; }
157
- if (Date.now() - Math.max(started, ioActivity.lastOutputAt(id)) >= 1000) {
158
+ if (Date.now() - Math.max(started, ioActivity.lastOutputAt(id)) >= 2000) {
158
159
  cancelPendingIdle(id);
159
160
  broadcastFn?.({ type: 'session.status', id, working: false, source: 'telemetry' });
160
161
  }