clideck 1.29.0 → 1.29.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.
package/handlers.js CHANGED
@@ -101,6 +101,33 @@ checkAvailability();
101
101
  let cfg = config.load();
102
102
  if (detectTelemetryConfig(cfg)) config.save(cfg);
103
103
 
104
+ function extractQuotedPath(command, needle) {
105
+ if (!command || !needle) return '';
106
+ const parts = String(command).match(/"([^"]+)"/g) || [];
107
+ for (const part of parts) {
108
+ const value = part.slice(1, -1);
109
+ if (value.includes(needle)) return value;
110
+ }
111
+ return '';
112
+ }
113
+
114
+ function hasExistingHook(arr, hookFile, route) {
115
+ return !!arr?.some(h => h.hooks?.some(x => {
116
+ if (!x.command?.includes(hookFile) || !x.command?.includes(` ${route}`)) return false;
117
+ const hookPath = extractQuotedPath(x.command, hookFile);
118
+ return !!hookPath && existsSync(hookPath);
119
+ }));
120
+ }
121
+
122
+ function codexConfigLooksHealthy(content, port) {
123
+ if (!content.includes('[otel]') || !content.includes(`localhost:${port}`)) return false;
124
+ const notifyLine = content.match(/^\s*notify\s*=\s*\[(.+)\]\s*$/m)?.[1] || '';
125
+ if (!notifyLine.includes('notify-helper')) return false;
126
+ const quoted = [...notifyLine.matchAll(/"([^"]+)"/g)].map(m => m[1]);
127
+ const helperPath = quoted.find(v => v.includes('notify-helper'));
128
+ return !!helperPath && existsSync(helperPath);
129
+ }
130
+
104
131
  function detectTelemetryConfig(c) {
105
132
  const home = os.homedir();
106
133
  const port = '4000';
@@ -116,24 +143,27 @@ function detectTelemetryConfig(c) {
116
143
  try {
117
144
  const s = JSON.parse(readFileSync(join(home, '.claude', 'settings.json'), 'utf8'));
118
145
  const hooks = s.hooks || {};
119
- const has = (arr, path) => arr?.some(h => h.hooks?.some(x => x.command?.includes('claude-hook.js') && x.command?.includes(` ${path}`)));
120
- detected = has(hooks.UserPromptSubmit, 'start') && has(hooks.Stop, 'stop') && has(hooks.StopFailure, 'stop')
121
- && has(hooks.PreToolUse, 'menu')
122
- && hooks.Notification?.some(h => h.matcher === 'idle_prompt' && h.hooks?.some(x => x.command?.includes('claude-hook.js') && x.command?.includes(' idle')));
146
+ detected = hasExistingHook(hooks.UserPromptSubmit, 'claude-hook.js', 'start')
147
+ && hasExistingHook(hooks.Stop, 'claude-hook.js', 'stop')
148
+ && hasExistingHook(hooks.StopFailure, 'claude-hook.js', 'stop')
149
+ && hasExistingHook(hooks.PreToolUse, 'claude-hook.js', 'menu')
150
+ && hooks.Notification?.some(h => h.matcher === 'idle_prompt' && hasExistingHook([h], 'claude-hook.js', 'idle'));
123
151
  if (!detected) reason = 'Needs re-patch';
124
152
  } catch {}
125
153
  } else if (preset.presetId === 'codex') {
126
154
  try {
127
155
  const content = readFileSync(join(home, '.codex', 'config.toml'), 'utf8');
128
- detected = content.includes('[otel]') && /^\s*notify\s*=.*notify-helper/m.test(content) && content.includes(`localhost:${port}`);
156
+ detected = codexConfigLooksHealthy(content, port);
129
157
  if (!detected) reason = 'Needs re-patch';
130
158
  } catch {}
131
159
  } else if (preset.presetId === 'gemini-cli') {
132
160
  try {
133
161
  const s = JSON.parse(readFileSync(join(home, '.gemini', 'settings.json'), 'utf8'));
134
162
  const hooks = s.hooks || {};
135
- const has = (arr, route) => arr?.some(h => h.hooks?.some(x => x.command?.includes('gemini-hook.js') && x.command?.includes(` ${route}`)));
136
- detected = has(hooks.BeforeAgent, 'start') && has(hooks.AfterAgent, 'stop') && has(hooks.SessionEnd, 'stop') && has(hooks.BeforeTool, 'menu');
163
+ detected = hasExistingHook(hooks.BeforeAgent, 'gemini-hook.js', 'start')
164
+ && hasExistingHook(hooks.AfterAgent, 'gemini-hook.js', 'stop')
165
+ && hasExistingHook(hooks.SessionEnd, 'gemini-hook.js', 'stop')
166
+ && hasExistingHook(hooks.BeforeTool, 'gemini-hook.js', 'menu');
137
167
  if (!detected) reason = 'Needs re-patch';
138
168
  } catch {}
139
169
  } else if (preset.presetId === 'opencode') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.29.0",
3
+ "version": "1.29.1",
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/plugin-loader.js CHANGED
@@ -206,13 +206,14 @@ function buildApi(pluginId, pluginDir, state) {
206
206
  getSession(id) {
207
207
  const s = sessionsFn?.()?.get(id);
208
208
  if (!s) return null;
209
- return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, roleName: s.roleName || null, working: !!sessionStatus.get(id) };
209
+ const state = sessionStatus.get(id) || '';
210
+ return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, roleName: s.roleName || null, working: state.startsWith('1:') };
210
211
  },
211
212
  getSessions() {
212
213
  const sessions = sessionsFn?.();
213
214
  if (!sessions) return [];
214
215
  return [...sessions].map(([id, s]) => ({
215
- id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, roleName: s.roleName || null, working: !!sessionStatus.get(id),
216
+ id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, roleName: s.roleName || null, working: (sessionStatus.get(id) || '').startsWith('1:'),
216
217
  }));
217
218
  },
218
219
 
package/sessions.js CHANGED
@@ -101,19 +101,28 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
101
101
  if (preset?.telemetryEnv) telemetry.watchSession(id, bin);
102
102
  if (preset?.bridge === 'opencode') opencodeBridge.watchSession(id, cwd);
103
103
 
104
+ function injectRolePrompt() {
105
+ if (!session.pendingRolePrompt) return;
106
+ transcript.recordInjectedInput(id, session.pendingRolePrompt);
107
+ term.write(session.pendingRolePrompt);
108
+ setTimeout(() => term.write('\r'), 150);
109
+ console.log(`Session ${id.slice(0, 8)}: injected role prompt`);
110
+ delete session.pendingRolePrompt;
111
+ delete session._rolePromptTimer;
112
+ }
113
+
104
114
  term.onData((data) => {
105
- // Inject role prompt once after agent starts producing output
115
+ // Role prompts should be injected only when the agent is likely ready for
116
+ // input. For Codex, use the first OTLP startup event instead of a blind
117
+ // fixed startup delay; other agents keep the existing delayed path.
106
118
  if (session.pendingRolePrompt && !session._rolePromptTimer) {
107
- session._rolePromptTimer = setTimeout(() => {
108
- if (session.pendingRolePrompt) {
109
- transcript.recordInjectedInput(id, session.pendingRolePrompt);
110
- term.write(session.pendingRolePrompt);
111
- setTimeout(() => term.write('\r'), 150);
112
- console.log(`Session ${id.slice(0, 8)}: injected role prompt`);
113
- delete session.pendingRolePrompt;
114
- delete session._rolePromptTimer;
115
- }
116
- }, 3000);
119
+ if (session.presetId === 'codex') {
120
+ if (telemetry.hasEvents(id)) injectRolePrompt();
121
+ } else {
122
+ session._rolePromptTimer = setTimeout(() => {
123
+ if (session.pendingRolePrompt) injectRolePrompt();
124
+ }, 3000);
125
+ }
117
126
  }
118
127
  session.chunks.push(data);
119
128
  session.chunksSize += data.length;
@@ -363,7 +372,7 @@ function restart(msg, ws, cfg) {
363
372
  }
364
373
 
365
374
  const savedToken = s.sessionToken;
366
- const { name, cwd, commandId, projectId } = s;
375
+ const { name, cwd, commandId, projectId, roleName, muted, lastPreview, lastActivityAt } = s;
367
376
 
368
377
  activity.clear(id);
369
378
  telemetry.clear(id);
@@ -380,6 +389,14 @@ function restart(msg, ws, cfg) {
380
389
  return;
381
390
  }
382
391
 
392
+ const next = sessions.get(id);
393
+ if (next) {
394
+ next.roleName = roleName || null;
395
+ next.muted = !!muted;
396
+ next.lastPreview = lastPreview || '';
397
+ next.lastActivityAt = lastActivityAt || null;
398
+ }
399
+
383
400
  broadcast({ type: 'session.restarted', id, resumed: !!canResume });
384
401
  }
385
402
 
@@ -14,7 +14,7 @@ function cleanAgentText(presetId, text) {
14
14
  out = out.replace(/\n\s*.*\(running stop hook\)[\s\S]*$/, '').trim();
15
15
  out = out.replace(/\n\s*\?\s*for shortcuts[\s\S]*$/, '').trim();
16
16
  out = out.replace(/\n\s*esc to interrupt[\s\S]*$/, '').trim();
17
- out = out.replace(/\n\s*[✻✢✣✤✥✦✧]\s+[^\n]*$/, '').trim();
17
+ out = out.replace(/\n\n\s*[✻✢✣✤✥✦✧][\s\S]*$/, '').trim();
18
18
  }
19
19
  return out;
20
20
  }