clideck 1.25.4 → 1.25.5
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 +2 -2
- package/package.json +1 -1
- package/public/js/terminals.js +27 -25
- package/telemetry-receiver.js +10 -0
- package/transcript.js +16 -2
package/handlers.js
CHANGED
|
@@ -130,6 +130,7 @@ function onConnection(ws) {
|
|
|
130
130
|
case 'input': sessions.input(msg); break;
|
|
131
131
|
case 'session.statusReport':
|
|
132
132
|
if (sessions.getSessions().has(msg.id)) {
|
|
133
|
+
sessions.broadcast({ type: 'session.status', id: msg.id, working: !!msg.working });
|
|
133
134
|
plugins.notifyStatus(msg.id, !!msg.working);
|
|
134
135
|
}
|
|
135
136
|
break;
|
|
@@ -306,8 +307,7 @@ function onConnection(ws) {
|
|
|
306
307
|
}
|
|
307
308
|
|
|
308
309
|
case 'remote.getHistory': {
|
|
309
|
-
const turns = transcript.getScreenTurns(msg.id, sessions.getSessions().get(msg.id)?.presetId)
|
|
310
|
-
|| transcript.getLastTurns(msg.id, msg.limit || 50);
|
|
310
|
+
const turns = transcript.getScreenTurns(msg.id, sessions.getSessions().get(msg.id)?.presetId);
|
|
311
311
|
ws.send(JSON.stringify({ type: 'remote.history', id: msg.id, turns: turns || [] }));
|
|
312
312
|
break;
|
|
313
313
|
}
|
package/package.json
CHANGED
package/public/js/terminals.js
CHANGED
|
@@ -318,7 +318,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
318
318
|
const statusEl = item.querySelector('.session-status');
|
|
319
319
|
const cmd = state.cfg.commands.find(c => c.id === commandId);
|
|
320
320
|
const hasBridge = !!cmd?.bridge;
|
|
321
|
-
const stopBounce =
|
|
321
|
+
const stopBounce = null;
|
|
322
322
|
|
|
323
323
|
const el = document.createElement('div');
|
|
324
324
|
el.className = 'term-wrap';
|
|
@@ -338,20 +338,29 @@ 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
|
-
// [
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
341
|
+
// [SCREEN-CAPTURE] extract terminal buffer when BOTH idle AND render-silent (2s)
|
|
342
|
+
// Decoupled from status: telemetry knows when agent is done, onRender knows when terminal is done
|
|
343
|
+
let _screenTimer = null, _renderSilent = false;
|
|
344
|
+
function _tryScreenCapture() {
|
|
345
|
+
const entry = state.terms.get(id);
|
|
346
|
+
if (!entry?.pendingScreenCapture || !_renderSilent || !entry.term) return;
|
|
347
|
+
entry.pendingScreenCapture = false;
|
|
348
|
+
const buf = entry.term.buffer.active;
|
|
349
|
+
const lines = [];
|
|
350
|
+
for (let i = 0; i < buf.length; i++) { const line = buf.getLine(i); if (line) lines.push(line.translateToString(true)); }
|
|
351
|
+
send({ type: 'terminal.buffer', id, lines });
|
|
352
352
|
}
|
|
353
|
-
term.
|
|
354
|
-
|
|
353
|
+
term.onRender(() => {
|
|
354
|
+
_renderSilent = false;
|
|
355
|
+
clearTimeout(_screenTimer);
|
|
356
|
+
_screenTimer = setTimeout(() => { _renderSilent = true; _tryScreenCapture(); }, 2000);
|
|
357
|
+
});
|
|
358
|
+
let _idleTimer = null, _workTimer = null, _lastTyping = 0;
|
|
359
|
+
term.onData(() => { _lastTyping = Date.now(); });
|
|
360
|
+
term.onWriteParsed(() => { if (Date.now() - _lastTyping < 500) return; const entry = state.terms.get(id); if (entry) entry.lastRenderAt = Date.now(); if (!_workTimer) _workTimer = setTimeout(() => { _workTimer = null; setStatus(id, true); }, 1000); clearTimeout(_idleTimer); _idleTimer = setTimeout(() => { clearTimeout(_workTimer); _workTimer = null; setStatus(id, false); send({ type: 'session.statusReport', id, working: false }); }, 1500); });
|
|
361
|
+
|
|
362
|
+
// Expose capture function so setStatus can trigger it when idle arrives after render silence
|
|
363
|
+
setTimeout(() => { const e = state.terms.get(id); if (e) e.tryScreenCapture = _tryScreenCapture; }, 0);
|
|
355
364
|
|
|
356
365
|
term.open(el);
|
|
357
366
|
attachToTerminal(term);
|
|
@@ -383,7 +392,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
383
392
|
// Safety: if RO hasn't fired within 500ms, flush anyway to avoid unbounded queue
|
|
384
393
|
setTimeout(() => { if (!fitted) { fitted = true; for (const chunk of pending) term.write(chunk); pending = null; updatePreview(id); } }, 500);
|
|
385
394
|
const cancelFitRaf = () => { if (fitRaf) { cancelAnimationFrame(fitRaf); fitRaf = 0; } };
|
|
386
|
-
state.terms.set(id, { term, fit, el, ro, cancelFitRaf, themeId, commandId, projectId: projectId || null, muted: !!muted, working:
|
|
395
|
+
state.terms.set(id, { term, fit, el, ro, cancelFitRaf, themeId, commandId, projectId: projectId || null, muted: !!muted, working: false, workStartedAt: null, stopBounce, queue: (data) => { if (!fitted) { pending.push(data); return true; } return false; }, lastActivityAt: Date.now(), unread: false, lastPreviewText: lastPreview || '', searchText: '' });
|
|
387
396
|
document.getElementById('empty').style.display = 'none';
|
|
388
397
|
document.getElementById('terminals').style.pointerEvents = '';
|
|
389
398
|
if (muted) requestAnimationFrame(() => updateMuteIndicator(id));
|
|
@@ -529,16 +538,9 @@ function setStatus(id, working) {
|
|
|
529
538
|
}
|
|
530
539
|
}
|
|
531
540
|
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const lines = [];
|
|
536
|
-
for (let i = 0; i < buf.length; i++) {
|
|
537
|
-
const line = buf.getLine(i);
|
|
538
|
-
if (line) lines.push(line.translateToString(true));
|
|
539
|
-
}
|
|
540
|
-
send({ type: 'terminal.buffer', id, lines });
|
|
541
|
-
}
|
|
541
|
+
// Mark idle so the onRender silence watcher can capture .screen
|
|
542
|
+
// Also try immediately — renders may already be silent
|
|
543
|
+
if (wasWorking && !working) { entry.pendingScreenCapture = true; entry.tryScreenCapture?.(); }
|
|
542
544
|
|
|
543
545
|
if (working) entry.workStartedAt = Date.now();
|
|
544
546
|
|
package/telemetry-receiver.js
CHANGED
|
@@ -68,6 +68,16 @@ function handleLogs(req, res) {
|
|
|
68
68
|
for (const lr of sl.logRecords || []) {
|
|
69
69
|
const attrs = parseAttrs(lr.attributes);
|
|
70
70
|
|
|
71
|
+
const eventName = attrs['event.name'];
|
|
72
|
+
if (eventName) console.log(`[telemetry] ${resolvedId?.slice(0,8)} ${eventName}`);
|
|
73
|
+
|
|
74
|
+
// Telemetry: only used for working=true (user prompt). Idle is handled by frontend write-silence.
|
|
75
|
+
const startEvents = new Set(['user_prompt', 'gemini_cli.user_prompt', 'codex.user_prompt']);
|
|
76
|
+
|
|
77
|
+
if (startEvents.has(eventName)) {
|
|
78
|
+
broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
const agentSessionId = attrs['session.id'] || attrs['conversation.id'];
|
|
72
82
|
if (agentSessionId && sess) {
|
|
73
83
|
// Prefer interactive session ID (Gemini sends non-interactive init events first)
|
package/transcript.js
CHANGED
|
@@ -47,12 +47,26 @@ function store(id, role, text) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function trackInput(id, data) {
|
|
50
|
-
if (!inputBuf[id]) inputBuf[id] = { text: '', esc: false };
|
|
50
|
+
if (!inputBuf[id]) inputBuf[id] = { text: '', esc: false, osc: false };
|
|
51
51
|
const buf = inputBuf[id];
|
|
52
52
|
for (const ch of data) {
|
|
53
|
+
// OSC sequence: ESC ] ... (terminated by BEL or ESC \)
|
|
54
|
+
if (buf.osc) {
|
|
55
|
+
if (ch === '\x07') { buf.osc = false; continue; } // BEL terminator
|
|
56
|
+
if (ch === '\\' && buf.escPending) { buf.osc = false; buf.escPending = false; continue; } // ESC \ terminator
|
|
57
|
+
buf.escPending = (ch === '\x1b');
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
53
60
|
if (ch === '\x1b') { buf.esc = true; continue; }
|
|
54
61
|
if (buf.esc) {
|
|
55
|
-
|
|
62
|
+
buf.esc = false;
|
|
63
|
+
if (ch === ']') { buf.osc = true; continue; } // Start OSC
|
|
64
|
+
if (ch === '[') { buf.csi = true; continue; } // Start CSI
|
|
65
|
+
continue; // Simple ESC + char
|
|
66
|
+
}
|
|
67
|
+
// CSI sequence: ESC [ ... (terminated by letter or ~)
|
|
68
|
+
if (buf.csi) {
|
|
69
|
+
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch === '~') buf.csi = false;
|
|
56
70
|
continue;
|
|
57
71
|
}
|
|
58
72
|
if (ch === '\r' || ch === '\n') {
|