clideck 1.25.3 → 1.25.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.25.3",
3
+ "version": "1.25.4",
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/public/js/app.js CHANGED
@@ -90,6 +90,7 @@ function connect() {
90
90
  }
91
91
  break;
92
92
  }
93
+ /* [OLD-STATUS] I/O burst heuristic — replaced by onRender detection in terminals.js
93
94
  case 'stats': {
94
95
  for (const [sid, st] of Object.entries(msg.stats)) {
95
96
  const entry = state.terms.get(sid);
@@ -101,12 +102,9 @@ function connect() {
101
102
  const userTyping = (st.rawRateIn || 0) > 0 && (st.rawRateIn || 0) < 50;
102
103
  entry.prevBurst = st.burstMs || 0;
103
104
 
104
- // Working: burst increasing + net >= 800B + no typing
105
105
  const isWorking = burstUp && net >= 800 && !userTyping;
106
- // Idle: burst not increasing + net < 800B
107
106
  const isIdle = !burstUp && net < 800;
108
107
 
109
- // Sustain for ~1.5s (2 ticks)
110
108
  if (isWorking) entry.workTicks = (entry.workTicks || 0) + 1;
111
109
  else entry.workTicks = 0;
112
110
  if (isIdle) entry.idleTicks = (entry.idleTicks || 0) + 1;
@@ -122,6 +120,7 @@ function connect() {
122
120
  }
123
121
  break;
124
122
  }
123
+ [OLD-STATUS] */
125
124
  case 'transcript.cache':
126
125
  state.transcriptCache = msg.cache;
127
126
  for (const [id, text] of Object.entries(msg.cache)) {
@@ -338,6 +338,21 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
338
338
  term.loadAddon(fit);
339
339
  term.onData(data => send({ type: 'input', id, data }));
340
340
 
341
+ // [RENDER-STATUS] agent working/idle detection via onRender + onWriteParsed
342
+ let _lastTyping = 0, _renderWorking = false, _renderTimer = null, _hasRender = false, _hasParsed = false;
343
+ term.onData(() => { _lastTyping = Date.now(); });
344
+ function _statusTick() {
345
+ if (Date.now() - _lastTyping < 500) return;
346
+ const cmd = state.cfg.commands.find(c => c.id === commandId);
347
+ if (cmd?.bridge) return;
348
+ if (_hasRender && _hasParsed && !_renderWorking) {
349
+ _renderWorking = true;
350
+ send({ type: 'session.statusReport', id, working: true }); setStatus(id, true);
351
+ }
352
+ }
353
+ term.onWriteParsed(() => { _hasParsed = true; _statusTick(); });
354
+ term.onRender(() => { _hasRender = true; _statusTick(); clearTimeout(_renderTimer); _renderTimer = setTimeout(() => { _renderWorking = false; _hasRender = false; _hasParsed = false; send({ type: 'session.statusReport', id, working: false }); setStatus(id, false); }, 2000); });
355
+
341
356
  term.open(el);
342
357
  attachToTerminal(term);
343
358
  let fitted = false, pending = [];
package/transcript.js CHANGED
@@ -91,11 +91,17 @@ function flush(id) {
91
91
  // xterm buffer as rendered by the browser. No diffing, no JSONL — just the
92
92
  // clean screen content. Mobile reads this for "last agent message".
93
93
  function storeBuffer(id, lines) {
94
- const isChrome = t => !t || /^[─━═\u2500-\u257f]+$/.test(t) || /^[❯>$%#]\s*$/.test(t) || t === 'esc to interrupt' || t === '? for shortcuts';
94
+ const isChrome = t => !t
95
+ || /^[─━═\u2500-\u257f]+$/.test(t) // box-drawing horizontal lines
96
+ || /^[▀▄█▌▐░▒▓╭╮╰╯│╔╗╚╝║]+$/.test(t) // block elements, box corners, vertical bars
97
+ || (/[█▀▄▌▐░▒▓]/.test(t) && /^[█▀▄▌▐░▒▓\s]+$/.test(t)) // ASCII art (blocks + whitespace, e.g. logos)
98
+ || /^[❯>$%#]\s*$/.test(t) // bare prompt markers
99
+ || /^(esc to interrupt|\? for shortcuts)$/i.test(t); // Claude Code chrome
95
100
  const filtered = lines.filter(l => !isChrome(l.trim()));
96
101
  while (filtered.length && !filtered[filtered.length - 1].trim()) filtered.pop();
97
102
  const screenPath = join(DIR, `${id}.screen`);
98
103
  if (filtered.length) writeFileSync(screenPath, filtered.join('\n'));
104
+ else try { unlinkSync(screenPath); } catch {}
99
105
  }
100
106
 
101
107
  // Read the clean screen snapshot for a session (if available).
@@ -111,7 +117,7 @@ const agentParsers = {
111
117
  const turns = [];
112
118
  let current = null;
113
119
  for (const line of lines) {
114
- const m = line.match(/^(?:[│ ]\s*)?([❯›]|[⏺•])\s(.*)$/);
120
+ const m = line.match(/^(?:[│ ]\s*)?([❯›]|[⏺•●])\s(.*)$/);
115
121
  if (m) {
116
122
  if (current) { current.text = current.text.replace(/\n+$/, ''); turns.push(current); }
117
123
  current = { role: m[1] === '❯' || m[1] === '›' ? 'user' : 'agent', text: m[2] };
@@ -146,9 +152,20 @@ const agentParsers = {
146
152
  return turns.length >= 2 ? turns : null;
147
153
  },
148
154
  'gemini-cli': (lines) => {
155
+ const geminiChrome = t => {
156
+ const s = t.trim();
157
+ return /^shift\+tab to accept/i.test(s)
158
+ || /^(Type your message|@path\/to\/)/i.test(s)
159
+ || /^(\/\w+ |no sandbox|\/model )/i.test(s)
160
+ || /^[~\/\\].*\(main[*]?\)\s*$/i.test(s)
161
+ || /^(Logged in with|Plan:|Tips for getting started)/i.test(s)
162
+ || /^\d+\.\s+(Ask questions|Be specific|Create GEMINI)/i.test(s)
163
+ || /^ℹ\s/.test(s);
164
+ };
149
165
  const turns = [];
150
166
  let current = null;
151
167
  for (const line of lines) {
168
+ if (geminiChrome(line)) continue;
152
169
  const isUser = line.startsWith(' > ');
153
170
  const isAgent = line.startsWith('✦ ');
154
171
  if (isUser || isAgent) {
@@ -212,7 +229,10 @@ function getScreenTurns(id, agent) {
212
229
  if (!screen) return null;
213
230
  const lines = screen.split('\n');
214
231
  const parser = agentParsers[agent];
215
- return parser ? parser(lines) : anchorParse(id, lines);
232
+ const turns = parser ? parser(lines) : anchorParse(id, lines);
233
+ // Drop trailing user turn — it's the empty prompt or unanswered input
234
+ if (turns?.length && turns[turns.length - 1].role === 'user') turns.pop();
235
+ return turns?.length >= 2 ? turns : null;
216
236
  }
217
237
 
218
238
  function getLastTurns(id, n) {