create-walle 0.9.13 → 0.9.15
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/README.md +8 -3
- package/bin/create-walle.js +232 -32
- package/bin/mcp-inject.js +18 -53
- package/package.json +3 -1
- package/template/claude-task-manager/api-prompts.js +11 -2
- package/template/claude-task-manager/approval-agent.js +7 -0
- package/template/claude-task-manager/db.js +94 -75
- package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
- package/template/claude-task-manager/docs/session-tooltip-freshness-design.md +224 -0
- package/template/claude-task-manager/docs/session-ux-issue-review-2026-05-01.md +369 -0
- package/template/claude-task-manager/fuzzy-utils.js +10 -2
- package/template/claude-task-manager/git-utils.js +140 -10
- package/template/claude-task-manager/lib/agent-capabilities.js +1 -1
- package/template/claude-task-manager/lib/agent-presets.js +38 -5
- package/template/claude-task-manager/lib/codex-terminal-final.js +53 -0
- package/template/claude-task-manager/lib/ctm-session-context-api.js +222 -0
- package/template/claude-task-manager/lib/session-diagnostics.js +56 -0
- package/template/claude-task-manager/lib/session-history.js +309 -16
- package/template/claude-task-manager/lib/session-standup.js +409 -0
- package/template/claude-task-manager/lib/session-stream.js +253 -20
- package/template/claude-task-manager/lib/standup-attention.js +200 -0
- package/template/claude-task-manager/lib/status-hooks.js +8 -2
- package/template/claude-task-manager/lib/update-telemetry.js +114 -0
- package/template/claude-task-manager/lib/walle-ctm-history.js +49 -6
- package/template/claude-task-manager/lib/walle-default-model.js +55 -0
- package/template/claude-task-manager/lib/walle-mcp-auto-config.js +66 -0
- package/template/claude-task-manager/lib/walle-supervisor.js +86 -19
- package/template/claude-task-manager/lib/walle-transcript.js +1 -3
- package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
- package/template/claude-task-manager/package.json +1 -0
- package/template/claude-task-manager/providers/codex-mcp.js +104 -0
- package/template/claude-task-manager/providers/index.js +2 -0
- package/template/claude-task-manager/public/css/setup.css +2 -1
- package/template/claude-task-manager/public/css/walle.css +71 -0
- package/template/claude-task-manager/public/index.html +2388 -429
- package/template/claude-task-manager/public/js/message-renderer.js +314 -35
- package/template/claude-task-manager/public/js/session-search-utils.js +185 -3
- package/template/claude-task-manager/public/js/session-status-precedence.js +125 -0
- package/template/claude-task-manager/public/js/setup.js +62 -19
- package/template/claude-task-manager/public/js/stream-view.js +396 -55
- package/template/claude-task-manager/public/js/terminal-restore-state.js +57 -0
- package/template/claude-task-manager/public/js/walle-session.js +234 -26
- package/template/claude-task-manager/public/js/walle.js +143 -2
- package/template/claude-task-manager/server.js +1402 -433
- package/template/claude-task-manager/session-integrity.js +77 -28
- package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
- package/template/claude-task-manager/workers/scrollback-worker.js +5 -6
- package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
- package/template/package.json +1 -1
- package/template/wall-e/agent-runners/claude-code.js +2 -0
- package/template/wall-e/agent.js +63 -8
- package/template/wall-e/api-walle.js +330 -52
- package/template/wall-e/brain.js +291 -42
- package/template/wall-e/chat.js +172 -15
- package/template/wall-e/coding/compaction-service.js +19 -5
- package/template/wall-e/coding/stream-processor.js +22 -2
- package/template/wall-e/coding/workspace-replay.js +1 -4
- package/template/wall-e/coding-orchestrator.js +250 -80
- package/template/wall-e/compat.js +0 -28
- package/template/wall-e/context/context-builder.js +3 -1
- package/template/wall-e/embeddings.js +2 -7
- package/template/wall-e/eval/agent-runner.js +30 -9
- package/template/wall-e/eval/benchmark-generator.js +21 -1
- package/template/wall-e/eval/benchmarks/chat-eval.json +66 -6
- package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
- package/template/wall-e/eval/cc-replay.js +1 -0
- package/template/wall-e/eval/codex-cli-baseline.js +633 -0
- package/template/wall-e/eval/debug-agent003.js +1 -0
- package/template/wall-e/eval/eval-orchestrator.js +3 -3
- package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
- package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
- package/template/wall-e/eval/run-model-comparison.js +1 -0
- package/template/wall-e/eval/swebench-adapter.js +1 -0
- package/template/wall-e/evaluation/quorum-evaluator.js +0 -1
- package/template/wall-e/extraction/knowledge-extractor.js +1 -2
- package/template/wall-e/lib/mcp-integration.js +336 -0
- package/template/wall-e/llm/ollama.js +47 -8
- package/template/wall-e/llm/ollama.plugin.json +1 -1
- package/template/wall-e/llm/tool-adapter.js +1 -0
- package/template/wall-e/loops/ingest.js +42 -8
- package/template/wall-e/loops/initiative.js +87 -2
- package/template/wall-e/mcp-server.js +872 -19
- package/template/wall-e/memory/ctm-context-client.js +230 -0
- package/template/wall-e/memory/ctm-session-context.js +1376 -0
- package/template/wall-e/prompts/coding/memory-protocol.md +6 -0
- package/template/wall-e/server.js +30 -1
- package/template/wall-e/skills/_bundled/memory-search/SKILL.md +8 -0
- package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
- package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +471 -188
- package/template/wall-e/skills/skill-planner.js +86 -4
- package/template/wall-e/slack/socket-mode-listener.js +276 -0
- package/template/wall-e/telemetry.js +70 -2
- package/template/wall-e/tools/builtin-middleware.js +55 -2
- package/template/wall-e/tools/shell-policy.js +1 -1
- package/template/wall-e/tools/slack-owner.js +104 -0
- package/template/website/index.html +4 -4
- package/template/builder-journal.md +0 -17
|
@@ -14,6 +14,11 @@ const _streamState = {
|
|
|
14
14
|
activeView: new Map(),
|
|
15
15
|
tooltipEl: null,
|
|
16
16
|
tooltipTimer: null,
|
|
17
|
+
tooltipAnchorEl: null,
|
|
18
|
+
tooltipRefreshTimer: null,
|
|
19
|
+
tooltipRefreshDebounceTimer: null,
|
|
20
|
+
_tooltipActiveSessionId: null,
|
|
21
|
+
_tooltipPendingSessionId: null,
|
|
17
22
|
_tooltipReqId: 0,
|
|
18
23
|
// Per-session priming bookkeeping.
|
|
19
24
|
// _primed: sessionId → true once _primeConversationView() has rendered
|
|
@@ -22,11 +27,20 @@ const _streamState = {
|
|
|
22
27
|
// _priming: sessionId → in-flight Promise while the HTTP fetch is
|
|
23
28
|
// running — prevents duplicate primes when stream-init fires twice
|
|
24
29
|
// (e.g. reconnect).
|
|
30
|
+
// _primedTarget/_primingTarget: sessionId → conversation DOM node that
|
|
31
|
+
// owns the completed/in-flight prime. Tab activation can recreate the
|
|
32
|
+
// node while keeping the same sessionId; stale state must not make the
|
|
33
|
+
// new empty node skip its HTTP prime.
|
|
34
|
+
// _primeToken: sessionId → monotonic generation that invalidates older
|
|
35
|
+
// in-flight primes when the view is reset or recreated.
|
|
25
36
|
// _pendingEvents: sessionId → [stream-event...] buffered during prime.
|
|
26
37
|
// _parentUuidSeen: sessionId → Set of parentUuids already rendered,
|
|
27
38
|
// used to dedup stream-events that overlap with primed history.
|
|
28
39
|
_primed: new Map(),
|
|
40
|
+
_primedTarget: new Map(),
|
|
29
41
|
_priming: new Map(),
|
|
42
|
+
_primingTarget: new Map(),
|
|
43
|
+
_primeToken: new Map(),
|
|
30
44
|
_pendingEvents: new Map(),
|
|
31
45
|
_parentUuidSeen: new Map(),
|
|
32
46
|
};
|
|
@@ -41,9 +55,13 @@ function initStreamTooltip() {
|
|
|
41
55
|
background: var(--surface-2, #1a1a2e); color: var(--fg, #e0e0e0);
|
|
42
56
|
border: 1px solid var(--border, #333); border-radius: 8px;
|
|
43
57
|
width: min(420px, calc(100vw - 24px)); max-height: min(520px, calc(100vh - 16px));
|
|
44
|
-
overflow: hidden; font-size: 12px; line-height: 1.45; pointer-events:
|
|
58
|
+
overflow: hidden; font-size: 12px; line-height: 1.45; pointer-events: auto;
|
|
59
|
+
user-select: text; cursor: text;
|
|
45
60
|
box-shadow: 0 10px 28px rgba(0,0,0,0.42), 0 0 0 1px rgba(255,255,255,0.02) inset;
|
|
46
61
|
`;
|
|
62
|
+
tooltip.tabIndex = -1;
|
|
63
|
+
tooltip.setAttribute('role', 'dialog');
|
|
64
|
+
tooltip.setAttribute('aria-label', 'Session summary');
|
|
47
65
|
document.body.appendChild(tooltip);
|
|
48
66
|
_streamState.tooltipEl = tooltip;
|
|
49
67
|
}
|
|
@@ -79,6 +97,7 @@ function _tooltipStatusLabel(status) {
|
|
|
79
97
|
function _tooltipSourceLabel(source) {
|
|
80
98
|
const key = _tooltipText(source).toLowerCase();
|
|
81
99
|
if (key === 'ai-summary') return 'AI summary';
|
|
100
|
+
if (key === 'latest-prompt') return 'Latest prompt';
|
|
82
101
|
if (key === 'prompt-fallback') return 'Prompt fallback';
|
|
83
102
|
if (key === 'title-fallback') return 'Title fallback';
|
|
84
103
|
if (key === 'missing') return 'Intent missing';
|
|
@@ -95,6 +114,25 @@ function _tooltipPhaseLabel(phase) {
|
|
|
95
114
|
return key ? _tooltipStatusLabel(key) : 'Progress';
|
|
96
115
|
}
|
|
97
116
|
|
|
117
|
+
function _tooltipFreshnessLabel(freshness) {
|
|
118
|
+
const key = _tooltipText(freshness).toLowerCase();
|
|
119
|
+
if (key === 'fresh') return 'Fresh';
|
|
120
|
+
if (key === 'updating') return 'Updating';
|
|
121
|
+
if (key === 'stale') return 'Stale AI';
|
|
122
|
+
if (key === 'fallback') return 'No AI';
|
|
123
|
+
if (key === 'rejected') return 'Rejected AI';
|
|
124
|
+
if (key === 'missing') return 'Missing';
|
|
125
|
+
return key ? _tooltipStatusLabel(key) : 'Freshness';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function _tooltipFreshnessTone(freshness) {
|
|
129
|
+
const key = _tooltipText(freshness).toLowerCase();
|
|
130
|
+
if (key === 'fresh') return 'intent';
|
|
131
|
+
if (key === 'updating') return 'progress';
|
|
132
|
+
if (key === 'stale' || key === 'rejected') return 'fallback';
|
|
133
|
+
return 'idle';
|
|
134
|
+
}
|
|
135
|
+
|
|
98
136
|
function _tooltipAgentLabel(session, anchorEl) {
|
|
99
137
|
const agent = _tooltipText(anchorEl?.dataset?.agent).toLowerCase();
|
|
100
138
|
if (agent) {
|
|
@@ -192,10 +230,96 @@ function _tooltipAppendProgress(container, progress) {
|
|
|
192
230
|
container.appendChild(_tooltipSection('Progress', body));
|
|
193
231
|
}
|
|
194
232
|
|
|
195
|
-
|
|
196
|
-
|
|
233
|
+
function _tooltipCurrentTask(summary, session) {
|
|
234
|
+
const currentTask = summary?.currentTask || null;
|
|
235
|
+
if (currentTask && (currentTask.text || currentTask.source)) return currentTask;
|
|
236
|
+
const intent = summary?.intent || {};
|
|
237
|
+
const displayText = _tooltipText(intent.text || summary?.summary || summary?.displayPrompt || summary?.lastPrompt || session?.meta?.label);
|
|
238
|
+
return {
|
|
239
|
+
text: displayText || null,
|
|
240
|
+
source: intent.source || (summary?.summary ? 'ai-summary' : 'prompt-fallback'),
|
|
241
|
+
confidence: intent.confidence || (summary?.summary ? 'high' : 'low'),
|
|
242
|
+
freshness: intent.freshness || (summary?.summary ? 'fresh' : 'fallback'),
|
|
243
|
+
timestamp: intent.timestamp || 0,
|
|
244
|
+
updatedAt: intent.updatedAt || 0,
|
|
245
|
+
prompt: intent.prompt || summary?.displayPrompt || summary?.lastPrompt || null,
|
|
246
|
+
fullPrompt: intent.fullPrompt || summary?.displayPrompt || summary?.lastPrompt || null,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function _tooltipAppendAiSummary(container, aiSummary, currentTask) {
|
|
251
|
+
const aiText = _tooltipText(aiSummary?.text);
|
|
252
|
+
if (!aiText) return;
|
|
253
|
+
if (_tooltipText(currentTask?.source) === 'ai-summary' && aiText === _tooltipText(currentTask?.text)) return;
|
|
254
|
+
|
|
255
|
+
const body = document.createElement('div');
|
|
256
|
+
const row = document.createElement('div');
|
|
257
|
+
row.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;margin-bottom:5px;';
|
|
258
|
+
row.appendChild(_tooltipPill(_tooltipFreshnessLabel(aiSummary.status || aiSummary.freshness), _tooltipFreshnessTone(aiSummary.status || aiSummary.freshness)));
|
|
259
|
+
if (aiSummary.model) row.appendChild(_tooltipPill(_tooltipCap(aiSummary.model, 24), 'idle'));
|
|
260
|
+
const age = _tooltipAge(aiSummary.updatedAt);
|
|
261
|
+
if (age) {
|
|
262
|
+
const ageEl = document.createElement('span');
|
|
263
|
+
ageEl.style.cssText = 'align-self:center;color:var(--fg-dim,#727aa1);font-size:11px;';
|
|
264
|
+
ageEl.textContent = age;
|
|
265
|
+
row.appendChild(ageEl);
|
|
266
|
+
}
|
|
267
|
+
body.appendChild(row);
|
|
268
|
+
body.appendChild(_tooltipTextBlock(aiText, false));
|
|
269
|
+
container.appendChild(_tooltipSection('AI Summary', body));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function _resolveStreamTooltipAnchor(sessionId) {
|
|
273
|
+
const current = _streamState.tooltipAnchorEl;
|
|
274
|
+
if (current && typeof current.getBoundingClientRect === 'function' && (current.isConnected !== false)) return current;
|
|
275
|
+
if (typeof document === 'undefined' || !document.querySelector) return current;
|
|
276
|
+
try {
|
|
277
|
+
return document.querySelector(`#session-list .session-item[data-session-id="${CSS.escape(sessionId)}"]`) || current;
|
|
278
|
+
} catch {
|
|
279
|
+
return current;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function _clearTooltipRefreshTimers() {
|
|
284
|
+
if (_streamState.tooltipRefreshTimer) {
|
|
285
|
+
clearTimeout(_streamState.tooltipRefreshTimer);
|
|
286
|
+
_streamState.tooltipRefreshTimer = null;
|
|
287
|
+
}
|
|
288
|
+
if (_streamState.tooltipRefreshDebounceTimer) {
|
|
289
|
+
clearTimeout(_streamState.tooltipRefreshDebounceTimer);
|
|
290
|
+
_streamState.tooltipRefreshDebounceTimer = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function _scheduleVisibleStreamTooltipRefresh() {
|
|
295
|
+
if (_streamState.tooltipRefreshTimer) clearTimeout(_streamState.tooltipRefreshTimer);
|
|
296
|
+
_streamState.tooltipRefreshTimer = null;
|
|
297
|
+
if (!_streamTooltipVisible() || !_streamState._tooltipActiveSessionId) return;
|
|
298
|
+
_streamState.tooltipRefreshTimer = setTimeout(() => {
|
|
299
|
+
_streamState.tooltipRefreshTimer = null;
|
|
300
|
+
if (!_streamTooltipVisible() || !_streamState._tooltipActiveSessionId) return;
|
|
301
|
+
const id = _streamState._tooltipActiveSessionId;
|
|
302
|
+
showStreamTooltip(id, _resolveStreamTooltipAnchor(id), { refresh: true });
|
|
303
|
+
}, 10000);
|
|
304
|
+
if (typeof _streamState.tooltipRefreshTimer.unref === 'function') _streamState.tooltipRefreshTimer.unref();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function _refreshVisibleStreamTooltip(delayMs = 250) {
|
|
308
|
+
if (!_streamTooltipVisible() || !_streamState._tooltipActiveSessionId) return;
|
|
309
|
+
if (_streamState.tooltipRefreshDebounceTimer) clearTimeout(_streamState.tooltipRefreshDebounceTimer);
|
|
310
|
+
_streamState.tooltipRefreshDebounceTimer = setTimeout(() => {
|
|
311
|
+
_streamState.tooltipRefreshDebounceTimer = null;
|
|
312
|
+
if (!_streamTooltipVisible() || !_streamState._tooltipActiveSessionId) return;
|
|
313
|
+
const id = _streamState._tooltipActiveSessionId;
|
|
314
|
+
showStreamTooltip(id, _resolveStreamTooltipAnchor(id), { refresh: true });
|
|
315
|
+
}, delayMs);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function showStreamTooltip(sessionId, anchorEl, opts = {}) {
|
|
319
|
+
_clearPendingStreamTooltip(false);
|
|
197
320
|
const tooltip = _streamState.tooltipEl;
|
|
198
321
|
if (!tooltip) return;
|
|
322
|
+
if (anchorEl) _streamState.tooltipAnchorEl = anchorEl;
|
|
199
323
|
|
|
200
324
|
// Tag this request so we can detect staleness after await
|
|
201
325
|
const requestId = ++_streamState._tooltipReqId;
|
|
@@ -239,14 +363,15 @@ async function showStreamTooltip(sessionId, anchorEl) {
|
|
|
239
363
|
header.appendChild(_tooltipPill(_tooltipStatusLabel(status), statusTone));
|
|
240
364
|
content.appendChild(header);
|
|
241
365
|
|
|
242
|
-
const
|
|
243
|
-
const displayText = _tooltipText(
|
|
366
|
+
const currentTask = _tooltipCurrentTask(summary, session);
|
|
367
|
+
const displayText = _tooltipText(currentTask.text || summary.displayPrompt || summary.lastPrompt || session?.meta?.label);
|
|
244
368
|
const intentBody = document.createElement('div');
|
|
245
369
|
const sourceRow = document.createElement('div');
|
|
246
370
|
sourceRow.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;margin-bottom:5px;';
|
|
247
|
-
sourceRow.appendChild(_tooltipPill(_tooltipSourceLabel(
|
|
248
|
-
if (
|
|
249
|
-
|
|
371
|
+
sourceRow.appendChild(_tooltipPill(_tooltipSourceLabel(currentTask.source), currentTask.source === 'ai-summary' ? 'intent' : 'fallback'));
|
|
372
|
+
if (currentTask.freshness) sourceRow.appendChild(_tooltipPill(_tooltipFreshnessLabel(currentTask.freshness), _tooltipFreshnessTone(currentTask.freshness)));
|
|
373
|
+
if (currentTask.confidence) sourceRow.appendChild(_tooltipPill(_tooltipStatusLabel(currentTask.confidence), 'idle'));
|
|
374
|
+
const intentAge = _tooltipAge(currentTask.updatedAt || currentTask.timestamp || currentTask.promptTimestamp);
|
|
250
375
|
if (intentAge) {
|
|
251
376
|
const ageEl = document.createElement('span');
|
|
252
377
|
ageEl.style.cssText = 'align-self:center;color:var(--fg-dim,#727aa1);font-size:11px;';
|
|
@@ -255,7 +380,9 @@ async function showStreamTooltip(sessionId, anchorEl) {
|
|
|
255
380
|
}
|
|
256
381
|
intentBody.appendChild(sourceRow);
|
|
257
382
|
intentBody.appendChild(_tooltipTextBlock(displayText || 'No intent captured yet.', !displayText));
|
|
258
|
-
content.appendChild(_tooltipSection('
|
|
383
|
+
content.appendChild(_tooltipSection('Current Task', intentBody));
|
|
384
|
+
|
|
385
|
+
_tooltipAppendAiSummary(content, summary.aiSummary, currentTask);
|
|
259
386
|
|
|
260
387
|
_tooltipAppendProgress(content, summary.progress);
|
|
261
388
|
|
|
@@ -273,32 +400,47 @@ async function showStreamTooltip(sessionId, anchorEl) {
|
|
|
273
400
|
|
|
274
401
|
tooltip.appendChild(content);
|
|
275
402
|
|
|
403
|
+
tooltip.dataset.sessionId = sessionId;
|
|
404
|
+
_streamState._tooltipActiveSessionId = sessionId;
|
|
405
|
+
_streamState.tooltipAnchorEl = anchorEl || _streamState.tooltipAnchorEl;
|
|
276
406
|
tooltip.style.display = 'block';
|
|
277
407
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
408
|
+
const anchor = anchorEl || _resolveStreamTooltipAnchor(sessionId);
|
|
409
|
+
if (anchor && typeof anchor.getBoundingClientRect === 'function') {
|
|
410
|
+
// Position next to anchor (right of sidebar)
|
|
411
|
+
const rect = anchor.getBoundingClientRect();
|
|
412
|
+
const sidebar = document.getElementById && document.getElementById('sidebar');
|
|
413
|
+
const sidebarRight = sidebar ? sidebar.getBoundingClientRect().right : rect.right;
|
|
414
|
+
tooltip.style.left = (sidebarRight + 8) + 'px';
|
|
415
|
+
tooltip.style.top = Math.max(8, rect.top - 10) + 'px';
|
|
416
|
+
|
|
417
|
+
// Keep within viewport
|
|
418
|
+
const tr = tooltip.getBoundingClientRect();
|
|
419
|
+
const viewportWidth = Number.isFinite(window.innerWidth) ? window.innerWidth : 1200;
|
|
420
|
+
const viewportHeight = Number.isFinite(window.innerHeight) ? window.innerHeight : 800;
|
|
421
|
+
if (tr.right > viewportWidth - 8) {
|
|
422
|
+
tooltip.style.left = (rect.left - tr.width - 8) + 'px';
|
|
423
|
+
}
|
|
424
|
+
if (tr.bottom > viewportHeight - 8) {
|
|
425
|
+
tooltip.style.top = (viewportHeight - tr.height - 8) + 'px';
|
|
426
|
+
}
|
|
292
427
|
}
|
|
428
|
+
if (!opts.noRefresh) _scheduleVisibleStreamTooltipRefresh();
|
|
293
429
|
} catch {
|
|
294
430
|
hideStreamTooltip();
|
|
295
431
|
}
|
|
296
432
|
}
|
|
297
433
|
|
|
298
434
|
function hideStreamTooltip() {
|
|
299
|
-
|
|
435
|
+
_clearPendingStreamTooltip(false);
|
|
436
|
+
_clearTooltipRefreshTimers();
|
|
300
437
|
_streamState._tooltipReqId++; // Invalidate any in-flight fetch
|
|
301
|
-
|
|
438
|
+
_streamState._tooltipActiveSessionId = null;
|
|
439
|
+
_streamState.tooltipAnchorEl = null;
|
|
440
|
+
if (_streamState.tooltipEl) {
|
|
441
|
+
_streamState.tooltipEl.style.display = 'none';
|
|
442
|
+
delete _streamState.tooltipEl.dataset.sessionId;
|
|
443
|
+
}
|
|
302
444
|
}
|
|
303
445
|
|
|
304
446
|
// --- Conversation View ---
|
|
@@ -307,6 +449,7 @@ function createConversationView(sessionId) {
|
|
|
307
449
|
const container = document.createElement('div');
|
|
308
450
|
container.className = 'conversation-view';
|
|
309
451
|
container.dataset.sessionId = sessionId;
|
|
452
|
+
container.dataset.turnMode = 'conversation';
|
|
310
453
|
container.style.cssText = `
|
|
311
454
|
display: none; flex-direction: column; height: 100%;
|
|
312
455
|
overflow-y: auto; padding: 12px 16px; background: var(--surface-1, #111);
|
|
@@ -364,6 +507,11 @@ function _assignConversationParentUuid(el, parentUuid) {
|
|
|
364
507
|
|
|
365
508
|
function _replaceConversationParentEvent(existing, newEl) {
|
|
366
509
|
if (!existing) return false;
|
|
510
|
+
if (existing.classList && existing.classList.contains('prompt-turn')) {
|
|
511
|
+
if (newEl) existing.replaceWith(newEl);
|
|
512
|
+
else existing.remove();
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
367
515
|
const existingGroup = existing.closest ? existing.closest('.conv-tool-group') : null;
|
|
368
516
|
if (existingGroup && newEl && newEl.classList && newEl.classList.contains('conv-tool-group')) {
|
|
369
517
|
const targetRow = existing.closest('.tool-activity-item') || existing;
|
|
@@ -395,20 +543,102 @@ function _replaceConversationParentEvent(existing, newEl) {
|
|
|
395
543
|
return true;
|
|
396
544
|
}
|
|
397
545
|
|
|
546
|
+
function _isPromptTurnContainer(container) {
|
|
547
|
+
return !!(container && container.dataset && (container.dataset.turnMode === 'conversation' || container.dataset.turnMode === 'review'));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function _latestPromptTurn(container) {
|
|
551
|
+
if (!container || !container.children) return null;
|
|
552
|
+
for (let i = container.children.length - 1; i >= 0; i--) {
|
|
553
|
+
const child = container.children[i];
|
|
554
|
+
if (child && child.classList && child.classList.contains('prompt-turn')) return child;
|
|
555
|
+
}
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function _collapsePromptTurns(container) {
|
|
560
|
+
if (!container || !container.children || !MR || typeof MR.setPromptTurnExpanded !== 'function') return;
|
|
561
|
+
for (const child of Array.from(container.children)) {
|
|
562
|
+
if (child && child.classList && child.classList.contains('prompt-turn')) MR.setPromptTurnExpanded(child, false);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function _expandLatestPromptTurn(container) {
|
|
567
|
+
const latest = _latestPromptTurn(container);
|
|
568
|
+
if (latest && MR && typeof MR.setPromptTurnExpanded === 'function') MR.setPromptTurnExpanded(latest, true);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function _removePromptTurnEmpty(body) {
|
|
572
|
+
if (!body || !body.children) return;
|
|
573
|
+
for (const child of Array.from(body.children)) {
|
|
574
|
+
if (child && child.classList && child.classList.contains('prompt-turn-empty')) child.remove();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function _ensureResponseTurn(container, opts) {
|
|
579
|
+
let turn = _latestPromptTurn(container);
|
|
580
|
+
if (turn) return turn;
|
|
581
|
+
turn = MR.createConversationTurn(null, {
|
|
582
|
+
setup: true,
|
|
583
|
+
expanded: !!(opts && opts.expandSetup),
|
|
584
|
+
setupLabel: 'Session setup and context',
|
|
585
|
+
});
|
|
586
|
+
container.appendChild(turn);
|
|
587
|
+
return turn;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function _renderConversationNodeForEvent(evt, opts) {
|
|
591
|
+
if (MR && typeof MR.isConversationPromptEvent === 'function' && MR.isConversationPromptEvent(evt)) {
|
|
592
|
+
return MR.createConversationTurn(evt, { expanded: !!(opts && opts.expandPrompt) });
|
|
593
|
+
}
|
|
594
|
+
return renderConversationEvent(evt);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function _appendConversationEventToTurns(container, evt, opts) {
|
|
598
|
+
opts = opts || {};
|
|
599
|
+
if (!container || !MR || typeof MR.isConversationPromptEvent !== 'function') return null;
|
|
600
|
+
if (MR.isConversationPromptEvent(evt)) {
|
|
601
|
+
if (opts.collapseExisting) _collapsePromptTurns(container);
|
|
602
|
+
const turn = MR.createConversationTurn(evt, { expanded: !!opts.expandPrompt });
|
|
603
|
+
_removeConversationState(container);
|
|
604
|
+
container.appendChild(turn);
|
|
605
|
+
if (typeof MR.refreshPromptTurnMeta === 'function') MR.refreshPromptTurnMeta(turn);
|
|
606
|
+
return turn;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const el = renderConversationEvent(evt);
|
|
610
|
+
if (!el) return null;
|
|
611
|
+
_removeConversationState(container);
|
|
612
|
+
const turn = _ensureResponseTurn(container, opts);
|
|
613
|
+
const body = MR.getPromptTurnBody(turn);
|
|
614
|
+
if (!body) return null;
|
|
615
|
+
_removePromptTurnEmpty(body);
|
|
616
|
+
if (evt.data?.parentUuid) _assignConversationParentUuid(el, evt.data.parentUuid);
|
|
617
|
+
if (!_mergeConsecutiveToolGroups(body, el)) body.appendChild(el);
|
|
618
|
+
if (typeof MR.refreshPromptTurnMeta === 'function') MR.refreshPromptTurnMeta(turn);
|
|
619
|
+
return el;
|
|
620
|
+
}
|
|
621
|
+
|
|
398
622
|
function populateConversationView(container, events) {
|
|
399
623
|
container.textContent = '';
|
|
624
|
+
if (container.dataset) container.dataset.turnMode = container.dataset.turnMode || 'conversation';
|
|
400
625
|
if (!events || events.length === 0) {
|
|
401
626
|
_renderConversationState(container, 'No transcript messages found for this session.');
|
|
402
627
|
container.scrollTop = container.scrollHeight;
|
|
403
628
|
return;
|
|
404
629
|
}
|
|
405
630
|
for (const evt of events) {
|
|
631
|
+
if (_isPromptTurnContainer(container)) {
|
|
632
|
+
_appendConversationEventToTurns(container, evt, { expandPrompt: false, collapseExisting: false, expandSetup: false });
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
406
635
|
const el = renderConversationEvent(evt);
|
|
407
636
|
if (!el) continue;
|
|
408
637
|
if (evt.data?.parentUuid) _assignConversationParentUuid(el, evt.data.parentUuid);
|
|
409
638
|
if (_mergeConsecutiveToolGroups(container, el)) continue;
|
|
410
639
|
container.appendChild(el);
|
|
411
640
|
}
|
|
641
|
+
if (_isPromptTurnContainer(container)) _expandLatestPromptTurn(container);
|
|
412
642
|
container.scrollTop = container.scrollHeight;
|
|
413
643
|
}
|
|
414
644
|
|
|
@@ -442,6 +672,13 @@ function handleStreamMessage(msg) {
|
|
|
442
672
|
_primeConversationView(_domId, container);
|
|
443
673
|
}
|
|
444
674
|
} else if (msg.type === 'stream-event') {
|
|
675
|
+
const eventType = msg.data?.type || msg.role || msg.eventType || '';
|
|
676
|
+
if (eventType === 'user' && typeof window !== 'undefined' && typeof window._ctmRecordLivePromptPreview === 'function') {
|
|
677
|
+
window._ctmRecordLivePromptPreview(_domId, msg.data?.text || msg.text || '');
|
|
678
|
+
}
|
|
679
|
+
if (_streamState._tooltipActiveSessionId === _domId && ['user', 'assistant', 'summary'].includes(eventType)) {
|
|
680
|
+
_refreshVisibleStreamTooltip(eventType === 'summary' ? 50 : 250);
|
|
681
|
+
}
|
|
445
682
|
const container = document.querySelector(`.conversation-view[data-session-id="${CSS.escape(_domId)}"]`);
|
|
446
683
|
if (container) {
|
|
447
684
|
// If we haven't finished priming yet, queue the event and replay it later.
|
|
@@ -465,7 +702,11 @@ function handleStreamMessage(msg) {
|
|
|
465
702
|
if (reviewEl) _applyStreamEvent('__review_' + _domId, reviewEl, msg);
|
|
466
703
|
}
|
|
467
704
|
} else if (msg.type === 'stream-status') {
|
|
468
|
-
if (applyStreamStatus(msg))
|
|
705
|
+
if (applyStreamStatus(msg)) {
|
|
706
|
+
queueStreamStatusRender();
|
|
707
|
+
const ctmId = msg.ctmSessionId || msg.sessionId;
|
|
708
|
+
if (_streamState._tooltipActiveSessionId === ctmId) _refreshVisibleStreamTooltip(400);
|
|
709
|
+
}
|
|
469
710
|
}
|
|
470
711
|
}
|
|
471
712
|
|
|
@@ -486,6 +727,7 @@ function applyStreamStatus(msg) {
|
|
|
486
727
|
if (msg.lastActivity && s.meta) {
|
|
487
728
|
s.meta.lastActivity = msg.lastActivity;
|
|
488
729
|
}
|
|
730
|
+
if (changed && typeof scheduleStandupRefresh === 'function') scheduleStandupRefresh();
|
|
489
731
|
return changed;
|
|
490
732
|
}
|
|
491
733
|
|
|
@@ -668,14 +910,15 @@ function setSessionView(sessionId, view) {
|
|
|
668
910
|
if (view === 'conversation') {
|
|
669
911
|
if (xtermEl) xtermEl.style.display = 'none';
|
|
670
912
|
if (convView) convView.style.display = 'flex';
|
|
913
|
+
if (typeof _renderCodexFinalPanel === 'function') _renderCodexFinalPanel(sessionId);
|
|
671
914
|
if (window._ws) subscribeToStream(window._ws, sessionId);
|
|
672
|
-
//
|
|
673
|
-
if
|
|
674
|
-
|
|
675
|
-
}
|
|
915
|
+
// Prime immediately. _primeConversationView is idempotent for the current
|
|
916
|
+
// DOM node, but will re-prime if tab activation recreated an empty view.
|
|
917
|
+
if (convView) _primeConversationView(sessionId, convView);
|
|
676
918
|
} else {
|
|
677
919
|
if (xtermEl) xtermEl.style.display = '';
|
|
678
920
|
if (convView) convView.style.display = 'none';
|
|
921
|
+
if (typeof _renderCodexFinalPanel === 'function') _renderCodexFinalPanel(sessionId);
|
|
679
922
|
if (window._ws) unsubscribeFromStream(window._ws, sessionId);
|
|
680
923
|
}
|
|
681
924
|
}
|
|
@@ -690,13 +933,20 @@ function setSessionView(sessionId, view) {
|
|
|
690
933
|
// rendered) replace instead of duplicate.
|
|
691
934
|
function _applyStreamEvent(sessionId, container, msg) {
|
|
692
935
|
const parentUuid = msg.data?.parentUuid;
|
|
936
|
+
let seen = _streamState._parentUuidSeen.get(sessionId);
|
|
937
|
+
if (!seen) {
|
|
938
|
+
seen = new Set();
|
|
939
|
+
_streamState._parentUuidSeen.set(sessionId, seen);
|
|
940
|
+
}
|
|
693
941
|
|
|
694
942
|
// Explicit update (server signaled _update=true): replace the row with
|
|
695
943
|
// this parentUuid.
|
|
696
944
|
if (msg._update && parentUuid) {
|
|
697
945
|
const existing = container.querySelector(`[data-parent-uuid="${CSS.escape(parentUuid)}"]`);
|
|
698
946
|
if (existing) {
|
|
699
|
-
const newEl =
|
|
947
|
+
const newEl = _isPromptTurnContainer(container)
|
|
948
|
+
? _renderConversationNodeForEvent(msg, { expandPrompt: true })
|
|
949
|
+
: renderConversationEvent(msg);
|
|
700
950
|
if (newEl) {
|
|
701
951
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
702
952
|
_replaceConversationParentEvent(existing, newEl);
|
|
@@ -711,11 +961,12 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
711
961
|
// (either from priming or an earlier stream-event), treat the new one as
|
|
712
962
|
// a replace rather than an append. Claude Code sends the same parentUuid
|
|
713
963
|
// for streaming chunks of one assistant turn.
|
|
714
|
-
const seen = _streamState._parentUuidSeen.get(sessionId);
|
|
715
964
|
if (parentUuid && seen && seen.has(parentUuid)) {
|
|
716
965
|
const existing = container.querySelector(`[data-parent-uuid="${CSS.escape(parentUuid)}"]`);
|
|
717
966
|
if (existing) {
|
|
718
|
-
const newEl =
|
|
967
|
+
const newEl = _isPromptTurnContainer(container)
|
|
968
|
+
? _renderConversationNodeForEvent(msg, { expandPrompt: true })
|
|
969
|
+
: renderConversationEvent(msg);
|
|
719
970
|
if (newEl) {
|
|
720
971
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
721
972
|
_replaceConversationParentEvent(existing, newEl);
|
|
@@ -723,10 +974,25 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
723
974
|
return;
|
|
724
975
|
}
|
|
725
976
|
}
|
|
977
|
+
if (parentUuid && !seen.has(parentUuid)) {
|
|
978
|
+
const existing = container.querySelector(`[data-parent-uuid="${CSS.escape(parentUuid)}"]`);
|
|
979
|
+
if (existing) {
|
|
980
|
+
const newEl = _isPromptTurnContainer(container)
|
|
981
|
+
? _renderConversationNodeForEvent(msg, { expandPrompt: true })
|
|
982
|
+
: renderConversationEvent(msg);
|
|
983
|
+
if (newEl) {
|
|
984
|
+
_assignConversationParentUuid(newEl, parentUuid);
|
|
985
|
+
_replaceConversationParentEvent(existing, newEl);
|
|
986
|
+
seen.add(parentUuid);
|
|
987
|
+
}
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
726
991
|
|
|
727
|
-
const el =
|
|
992
|
+
const el = _isPromptTurnContainer(container)
|
|
993
|
+
? _appendConversationEventToTurns(container, msg, { expandPrompt: true, collapseExisting: true, expandSetup: true })
|
|
994
|
+
: renderConversationEvent(msg);
|
|
728
995
|
if (!el) return; // Skip empty events
|
|
729
|
-
_removeConversationState(container);
|
|
730
996
|
if (parentUuid) {
|
|
731
997
|
_assignConversationParentUuid(el, parentUuid);
|
|
732
998
|
if (seen) seen.add(parentUuid);
|
|
@@ -734,7 +1000,9 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
734
1000
|
// If this is a tool-only thought-group AND the last rendered child is also
|
|
735
1001
|
// a tool-only thought-group, fold this one's single step into the existing
|
|
736
1002
|
// group instead of appending a new wrapper. Matches Review's grouping.
|
|
737
|
-
if (
|
|
1003
|
+
if (_isPromptTurnContainer(container)) {
|
|
1004
|
+
// Already inserted into the prompt-turn body above.
|
|
1005
|
+
} else if (_mergeConsecutiveToolGroups(container, el)) {
|
|
738
1006
|
// Already merged into the previous group — no separate row to insert.
|
|
739
1007
|
} else {
|
|
740
1008
|
container.appendChild(el);
|
|
@@ -765,6 +1033,26 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
765
1033
|
// gap-reports/viewer-2026-04-23.md item D.
|
|
766
1034
|
const CONVERSATION_PAGE_SIZE = 200;
|
|
767
1035
|
|
|
1036
|
+
function _conversationViewHasRenderableContent(convView) {
|
|
1037
|
+
if (!convView || !convView.children || convView.children.length === 0) return false;
|
|
1038
|
+
return Array.from(convView.children).some((child) => {
|
|
1039
|
+
if (!child) return false;
|
|
1040
|
+
if (child.classList && child.classList.contains('conversation-load-older')) return false;
|
|
1041
|
+
return true;
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function _nextPrimeToken(sessionId) {
|
|
1046
|
+
const next = (_streamState._primeToken.get(sessionId) || 0) + 1;
|
|
1047
|
+
_streamState._primeToken.set(sessionId, next);
|
|
1048
|
+
return next;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function _primeStillCurrent(sessionId, token, convView) {
|
|
1052
|
+
return _streamState._primeToken.get(sessionId) === token
|
|
1053
|
+
&& _streamState._primingTarget.get(sessionId) === convView;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
768
1056
|
function _cleanSystemXml(t) {
|
|
769
1057
|
return t
|
|
770
1058
|
.replace(/<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)[^>]*>[\s\S]*?<\/\1>/gi, '')
|
|
@@ -940,20 +1228,18 @@ async function _loadOlder(sessionId, convView) {
|
|
|
940
1228
|
// Preserve scroll position: measure before + after prepending so
|
|
941
1229
|
// the user's current viewport doesn't jump when older content is
|
|
942
1230
|
// inserted above.
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
// Prepend below the bar so the bar stays at the very top.
|
|
956
|
-
convView.insertBefore(frag, bar.nextSibling);
|
|
1231
|
+
const prevScrollHeight = convView.scrollHeight;
|
|
1232
|
+
const prevScrollTop = convView.scrollTop;
|
|
1233
|
+
const events = _messagesToEvents(page.messages);
|
|
1234
|
+
const frag = document.createDocumentFragment();
|
|
1235
|
+
const pageHost = document.createElement('div');
|
|
1236
|
+
pageHost.dataset.turnMode = 'conversation';
|
|
1237
|
+
for (const evt of events) {
|
|
1238
|
+
_appendConversationEventToTurns(pageHost, evt, { expandPrompt: false, collapseExisting: false, expandSetup: false });
|
|
1239
|
+
}
|
|
1240
|
+
while (pageHost.firstChild) frag.appendChild(pageHost.firstChild);
|
|
1241
|
+
// Prepend below the bar so the bar stays at the very top.
|
|
1242
|
+
convView.insertBefore(frag, bar.nextSibling);
|
|
957
1243
|
const newScrollHeight = convView.scrollHeight;
|
|
958
1244
|
convView.scrollTop = prevScrollTop + (newScrollHeight - prevScrollHeight);
|
|
959
1245
|
// Merge new parentUuids into the dedup set so live events for primed
|
|
@@ -971,13 +1257,22 @@ async function _loadOlder(sessionId, convView) {
|
|
|
971
1257
|
}
|
|
972
1258
|
|
|
973
1259
|
async function _primeConversationView(sessionId, convView) {
|
|
974
|
-
if (_streamState._primed.get(sessionId)
|
|
1260
|
+
if (_streamState._primed.get(sessionId)
|
|
1261
|
+
&& _streamState._primedTarget.get(sessionId) === convView
|
|
1262
|
+
&& _conversationViewHasRenderableContent(convView)) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
975
1265
|
const inflight = _streamState._priming.get(sessionId);
|
|
976
|
-
if (inflight) return inflight;
|
|
1266
|
+
if (inflight && _streamState._primingTarget.get(sessionId) === convView) return inflight;
|
|
1267
|
+
|
|
1268
|
+
const token = _nextPrimeToken(sessionId);
|
|
1269
|
+
_streamState._primed.delete(sessionId);
|
|
1270
|
+
_streamState._primedTarget.delete(sessionId);
|
|
977
1271
|
|
|
978
1272
|
const promise = (async () => {
|
|
979
1273
|
try {
|
|
980
1274
|
const page = await _fetchConversationPage(sessionId, 0);
|
|
1275
|
+
if (!_primeStillCurrent(sessionId, token, convView)) return;
|
|
981
1276
|
if (!page) {
|
|
982
1277
|
_renderConversationState(convView, 'Transcript unavailable for this session.', 'error');
|
|
983
1278
|
return;
|
|
@@ -999,11 +1294,16 @@ async function _primeConversationView(sessionId, convView) {
|
|
|
999
1294
|
if (page.has_more) _ensureLoadOlderBar(convView, sessionId);
|
|
1000
1295
|
_updateLoadOlderBar(convView, page);
|
|
1001
1296
|
} catch {
|
|
1002
|
-
|
|
1297
|
+
if (_primeStillCurrent(sessionId, token, convView)) {
|
|
1298
|
+
_renderConversationState(convView, 'Transcript unavailable for this session.', 'error');
|
|
1299
|
+
}
|
|
1003
1300
|
}
|
|
1004
1301
|
finally {
|
|
1302
|
+
if (!_primeStillCurrent(sessionId, token, convView)) return;
|
|
1005
1303
|
_streamState._primed.set(sessionId, true);
|
|
1304
|
+
_streamState._primedTarget.set(sessionId, convView);
|
|
1006
1305
|
_streamState._priming.delete(sessionId);
|
|
1306
|
+
_streamState._primingTarget.delete(sessionId);
|
|
1007
1307
|
// Replay any stream-events that arrived during the prime.
|
|
1008
1308
|
const queue = _streamState._pendingEvents.get(sessionId);
|
|
1009
1309
|
if (queue && queue.length) {
|
|
@@ -1013,14 +1313,18 @@ async function _primeConversationView(sessionId, convView) {
|
|
|
1013
1313
|
}
|
|
1014
1314
|
})();
|
|
1015
1315
|
_streamState._priming.set(sessionId, promise);
|
|
1316
|
+
_streamState._primingTarget.set(sessionId, convView);
|
|
1016
1317
|
return promise;
|
|
1017
1318
|
}
|
|
1018
1319
|
|
|
1019
1320
|
// Called when a session is destroyed / tab closes. Clears per-session
|
|
1020
1321
|
// priming state so a re-subscribe on the same session later starts clean.
|
|
1021
1322
|
function _resetPrimingState(sessionId) {
|
|
1323
|
+
_nextPrimeToken(sessionId);
|
|
1022
1324
|
_streamState._primed.delete(sessionId);
|
|
1325
|
+
_streamState._primedTarget.delete(sessionId);
|
|
1023
1326
|
_streamState._priming.delete(sessionId);
|
|
1327
|
+
_streamState._primingTarget.delete(sessionId);
|
|
1024
1328
|
_streamState._pendingEvents.delete(sessionId);
|
|
1025
1329
|
_streamState._parentUuidSeen.delete(sessionId);
|
|
1026
1330
|
}
|
|
@@ -1054,17 +1358,38 @@ function _streamTooltipClosestSessionItem(target, root) {
|
|
|
1054
1358
|
return item;
|
|
1055
1359
|
}
|
|
1056
1360
|
|
|
1361
|
+
function _streamTooltipVisible() {
|
|
1362
|
+
const tooltip = _streamState.tooltipEl;
|
|
1363
|
+
return !!(tooltip && tooltip.style.display !== 'none');
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function _clearPendingStreamTooltip(invalidateRequest) {
|
|
1367
|
+
if (_streamState.tooltipTimer) {
|
|
1368
|
+
clearTimeout(_streamState.tooltipTimer);
|
|
1369
|
+
_streamState.tooltipTimer = null;
|
|
1370
|
+
}
|
|
1371
|
+
_streamState._tooltipPendingSessionId = null;
|
|
1372
|
+
if (invalidateRequest) _streamState._tooltipReqId++;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1057
1375
|
function _scheduleStreamTooltip(item) {
|
|
1058
1376
|
const id = item?.dataset?.sessionId;
|
|
1059
1377
|
if (!id) return;
|
|
1060
|
-
_streamState.
|
|
1378
|
+
if (_streamState._tooltipPendingSessionId === id) return;
|
|
1379
|
+
if (_streamState._tooltipActiveSessionId === id && _streamTooltipVisible()) return;
|
|
1380
|
+
_clearPendingStreamTooltip(true);
|
|
1381
|
+
_streamState._tooltipPendingSessionId = id;
|
|
1382
|
+
_streamState.tooltipTimer = setTimeout(() => {
|
|
1383
|
+
_streamState.tooltipTimer = null;
|
|
1384
|
+
_streamState._tooltipPendingSessionId = null;
|
|
1385
|
+
showStreamTooltip(id, item);
|
|
1386
|
+
}, 500);
|
|
1061
1387
|
}
|
|
1062
1388
|
|
|
1063
1389
|
function _handleStreamTooltipMouseOver(e, list) {
|
|
1064
1390
|
const item = _streamTooltipClosestSessionItem(e.target, list);
|
|
1065
1391
|
if (!item) return;
|
|
1066
1392
|
if (e.relatedTarget && _streamTooltipContains(item, e.relatedTarget)) return;
|
|
1067
|
-
hideStreamTooltip();
|
|
1068
1393
|
_scheduleStreamTooltip(item);
|
|
1069
1394
|
}
|
|
1070
1395
|
|
|
@@ -1072,6 +1397,14 @@ function _handleStreamTooltipMouseOut(e, list) {
|
|
|
1072
1397
|
const item = _streamTooltipClosestSessionItem(e.target, list);
|
|
1073
1398
|
if (!item) return;
|
|
1074
1399
|
if (e.relatedTarget && _streamTooltipContains(item, e.relatedTarget)) return;
|
|
1400
|
+
const id = item?.dataset?.sessionId;
|
|
1401
|
+
if (_streamState._tooltipPendingSessionId === id) _clearPendingStreamTooltip(true);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function _handleStreamTooltipDocumentPointerDown(e) {
|
|
1405
|
+
const tooltip = _streamState.tooltipEl;
|
|
1406
|
+
if (!tooltip || !_streamTooltipVisible()) return;
|
|
1407
|
+
if (_streamTooltipContains(tooltip, e.target)) return;
|
|
1075
1408
|
hideStreamTooltip();
|
|
1076
1409
|
}
|
|
1077
1410
|
|
|
@@ -1083,6 +1416,7 @@ function bindStreamTooltips() {
|
|
|
1083
1416
|
|
|
1084
1417
|
list.addEventListener('mouseover', (e) => _handleStreamTooltipMouseOver(e, list));
|
|
1085
1418
|
list.addEventListener('mouseout', (e) => _handleStreamTooltipMouseOut(e, list));
|
|
1419
|
+
document.addEventListener('pointerdown', _handleStreamTooltipDocumentPointerDown, true);
|
|
1086
1420
|
}
|
|
1087
1421
|
|
|
1088
1422
|
// --- Init ---
|
|
@@ -1109,6 +1443,7 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
1109
1443
|
module.exports = {
|
|
1110
1444
|
handleStreamMessage,
|
|
1111
1445
|
_primeConversationView,
|
|
1446
|
+
_conversationViewHasRenderableContent,
|
|
1112
1447
|
_applyStreamEvent,
|
|
1113
1448
|
_resetPrimingState,
|
|
1114
1449
|
_conversationLookupCandidates,
|
|
@@ -1118,9 +1453,15 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
1118
1453
|
_mergeConsecutiveToolGroups,
|
|
1119
1454
|
_streamState,
|
|
1120
1455
|
bindStreamTooltips,
|
|
1456
|
+
showStreamTooltip,
|
|
1457
|
+
hideStreamTooltip,
|
|
1458
|
+
_refreshVisibleStreamTooltip,
|
|
1459
|
+
_scheduleVisibleStreamTooltipRefresh,
|
|
1121
1460
|
_handleStreamTooltipMouseOver,
|
|
1122
1461
|
_handleStreamTooltipMouseOut,
|
|
1462
|
+
_handleStreamTooltipDocumentPointerDown,
|
|
1123
1463
|
_streamTooltipClosestSessionItem,
|
|
1124
1464
|
_streamTooltipContains,
|
|
1465
|
+
_streamTooltipVisible,
|
|
1125
1466
|
};
|
|
1126
1467
|
}
|