create-walle 0.9.13 → 0.9.14

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.
Files changed (58) hide show
  1. package/README.md +6 -1
  2. package/bin/create-walle.js +195 -30
  3. package/bin/mcp-inject.js +18 -53
  4. package/package.json +3 -1
  5. package/template/claude-task-manager/approval-agent.js +7 -0
  6. package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
  7. package/template/claude-task-manager/git-utils.js +111 -3
  8. package/template/claude-task-manager/lib/session-history.js +144 -16
  9. package/template/claude-task-manager/lib/session-standup.js +409 -0
  10. package/template/claude-task-manager/lib/standup-attention.js +200 -0
  11. package/template/claude-task-manager/lib/status-hooks.js +8 -2
  12. package/template/claude-task-manager/lib/update-telemetry.js +114 -0
  13. package/template/claude-task-manager/lib/walle-default-model.js +55 -0
  14. package/template/claude-task-manager/lib/walle-mcp-auto-config.js +62 -0
  15. package/template/claude-task-manager/lib/walle-supervisor.js +83 -19
  16. package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
  17. package/template/claude-task-manager/providers/codex-mcp.js +104 -0
  18. package/template/claude-task-manager/providers/index.js +2 -0
  19. package/template/claude-task-manager/public/css/setup.css +2 -1
  20. package/template/claude-task-manager/public/css/walle.css +5 -0
  21. package/template/claude-task-manager/public/index.html +1596 -283
  22. package/template/claude-task-manager/public/js/session-search-utils.js +171 -1
  23. package/template/claude-task-manager/public/js/setup.js +62 -19
  24. package/template/claude-task-manager/public/js/stream-view.js +55 -6
  25. package/template/claude-task-manager/public/js/walle-session.js +73 -16
  26. package/template/claude-task-manager/public/js/walle.js +34 -2
  27. package/template/claude-task-manager/server.js +780 -177
  28. package/template/claude-task-manager/session-integrity.js +58 -15
  29. package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
  30. package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
  31. package/template/package.json +1 -1
  32. package/template/wall-e/agent.js +36 -7
  33. package/template/wall-e/api-walle.js +72 -20
  34. package/template/wall-e/coding/stream-processor.js +22 -2
  35. package/template/wall-e/coding-orchestrator.js +26 -6
  36. package/template/wall-e/eval/agent-runner.js +16 -4
  37. package/template/wall-e/eval/benchmark-generator.js +21 -1
  38. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
  39. package/template/wall-e/eval/codex-cli-baseline.js +633 -0
  40. package/template/wall-e/eval/eval-orchestrator.js +3 -3
  41. package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
  42. package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
  43. package/template/wall-e/lib/mcp-integration.js +220 -0
  44. package/template/wall-e/llm/ollama.js +47 -8
  45. package/template/wall-e/llm/ollama.plugin.json +1 -1
  46. package/template/wall-e/llm/tool-adapter.js +1 -0
  47. package/template/wall-e/loops/ingest.js +42 -8
  48. package/template/wall-e/mcp-server.js +272 -10
  49. package/template/wall-e/memory/ctm-session-context.js +910 -0
  50. package/template/wall-e/server.js +26 -1
  51. package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
  52. package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
  53. package/template/wall-e/skills/skill-planner.js +52 -3
  54. package/template/wall-e/tools/builtin-middleware.js +55 -2
  55. package/template/wall-e/tools/shell-policy.js +1 -1
  56. package/template/wall-e/tools/slack-owner.js +104 -0
  57. package/template/website/index.html +2 -2
  58. package/template/builder-journal.md +0 -17
@@ -26,6 +26,104 @@
26
26
  return ids;
27
27
  }
28
28
 
29
+ function rememberRecentSessionIdentities(index, session) {
30
+ for (const id of getSearchableSessionIds(session)) index.set(id, session);
31
+ }
32
+
33
+ function findSessionIdentityMatch(index, session) {
34
+ if (!index || !session) return null;
35
+ for (const id of getSearchableSessionIds(session)) {
36
+ const existing = index.get(id);
37
+ if (existing) return existing;
38
+ }
39
+ return null;
40
+ }
41
+
42
+ function createSessionIdentityIndex(sessions) {
43
+ const index = new Map();
44
+ for (const session of sessions || []) rememberRecentSessionIdentities(index, session);
45
+ return index;
46
+ }
47
+
48
+ function isNewerTime(candidate, current) {
49
+ const candidateMs = Date.parse(String(candidate || ''));
50
+ if (!Number.isFinite(candidateMs)) return false;
51
+ const currentMs = Date.parse(String(current || ''));
52
+ return !Number.isFinite(currentMs) || candidateMs > currentMs;
53
+ }
54
+
55
+ function mergeRecentSessionMetadata(target, source) {
56
+ if (!target || !source) return target;
57
+
58
+ const sourceAgent = getRecentSessionAgentType(source);
59
+ const targetAgent = getRecentSessionAgentType(target);
60
+ if (sourceAgent && sourceAgent !== 'shell' && (!target.agent || targetAgent === 'shell')) {
61
+ target.agent = sourceAgent;
62
+ }
63
+
64
+ for (const field of [
65
+ 'agentType',
66
+ 'provider',
67
+ 'agentSessionId',
68
+ 'provisionalId',
69
+ 'cmd',
70
+ 'launchCommand',
71
+ 'cwd',
72
+ 'project',
73
+ 'projectEntry',
74
+ 'firstMessage',
75
+ 'title',
76
+ 'aiTitle',
77
+ 'displayTitle',
78
+ 'gitBranch',
79
+ 'model',
80
+ 'modelProvider',
81
+ 'timestamp',
82
+ 'version',
83
+ 'jsonlPath',
84
+ ]) {
85
+ if (!target[field] && source[field]) target[field] = source[field];
86
+ }
87
+
88
+ if (isNewerTime(source.modifiedAt, target.modifiedAt)) target.modifiedAt = source.modifiedAt;
89
+ if (isNewerTime(source.fileModifiedAt, target.fileModifiedAt)) target.fileModifiedAt = source.fileModifiedAt;
90
+ if (source.isEmpty === false && target.isEmpty !== false) target.isEmpty = false;
91
+ if ((source.userMsgCount || 0) > (target.userMsgCount || 0)) target.userMsgCount = source.userMsgCount;
92
+ if ((source.fileSize || 0) > (target.fileSize || 0)) target.fileSize = source.fileSize;
93
+ if (source.starred) target.starred = true;
94
+ if (source.userRenamed) target.userRenamed = true;
95
+ return target;
96
+ }
97
+
98
+ function mergeRecentSessionCandidates(recentSessions, additionalSessions) {
99
+ const merged = (recentSessions || []).map((session) => ({ ...session }));
100
+ const index = createSessionIdentityIndex(merged);
101
+ for (const raw of additionalSessions || []) {
102
+ const candidate = { ...raw };
103
+ const existing = findSessionIdentityMatch(index, candidate);
104
+ if (existing) mergeRecentSessionMetadata(existing, candidate);
105
+ else merged.push(candidate);
106
+ rememberRecentSessionIdentities(index, existing || candidate);
107
+ }
108
+ return merged;
109
+ }
110
+
111
+ function dedupeSessionCandidates(sessions) {
112
+ const merged = [];
113
+ const index = new Map();
114
+ for (const raw of sessions || []) {
115
+ const candidate = { ...raw };
116
+ const existing = findSessionIdentityMatch(index, candidate);
117
+ if (existing) {
118
+ mergeRecentSessionMetadata(existing, candidate);
119
+ continue;
120
+ }
121
+ merged.push(candidate);
122
+ rememberRecentSessionIdentities(index, candidate);
123
+ }
124
+ return merged;
125
+ }
126
+
29
127
  function scoreSessionIdMatch(session, query) {
30
128
  const normalizedQuery = normalizeSearchValue(query);
31
129
  if (!normalizedQuery) return 0;
@@ -81,6 +179,68 @@
81
179
  return value ? String(value).replace(/\/\.claude\/worktrees\/[^/]+$/, '') : value;
82
180
  }
83
181
 
182
+ const AGENT_FILTER_LABELS = {
183
+ codex: 'Codex',
184
+ claude: 'Claude Code',
185
+ walle: 'Wall-E',
186
+ gemini: 'Gemini CLI',
187
+ opencode: 'OpenCode',
188
+ 'claude-desktop': 'Claude Desktop',
189
+ shell: 'Shell / Other',
190
+ };
191
+
192
+ const AGENT_FILTER_ORDER = ['codex', 'claude', 'walle', 'gemini', 'opencode', 'claude-desktop', 'shell'];
193
+
194
+ function normalizeRecentAgentType(value) {
195
+ if (!value) return '';
196
+ const v = String(value).trim().toLowerCase().replace(/[_\s]+/g, '-');
197
+ if (!v) return '';
198
+ if (AGENT_FILTER_LABELS[v]) return v;
199
+ if (v === 'claude-code' || v === 'claude-cli') return 'claude';
200
+ if (v === 'anthropic') return 'claude';
201
+ if (v === 'claude-desktop-session' || v === 'desktop') return 'claude-desktop';
202
+ if (v === 'wall-e' || v === 'walle-session') return 'walle';
203
+ if (v === 'gemini-cli') return 'gemini';
204
+ if (v === 'open-code' || v === 'opencode-cli') return 'opencode';
205
+ if (v === 'terminal' || v === 'unknown' || v === 'other') return 'shell';
206
+ return '';
207
+ }
208
+
209
+ function detectRecentAgentTypeFromText(value) {
210
+ if (!value) return '';
211
+ const v = String(value).toLowerCase();
212
+ if (v.includes('opencode') || v.includes('open-code')) return 'opencode';
213
+ if (v.includes('wall-e') || v.includes('walle')) return 'walle';
214
+ if (v.includes('codex')) return 'codex';
215
+ if (v.includes('gemini')) return 'gemini';
216
+ if (v.includes('claude')) return 'claude';
217
+ return '';
218
+ }
219
+
220
+ function getRecentSessionAgentType(session) {
221
+ const s = session || {};
222
+ for (const candidate of [s.agent, s.agentType, s.provider]) {
223
+ const explicit = normalizeRecentAgentType(candidate);
224
+ if (explicit) return explicit;
225
+ }
226
+ if (s.meta && (s.meta.type === 'walle' || s.meta.agentType === 'walle')) return 'walle';
227
+ if (s.type === 'walle') return 'walle';
228
+
229
+ const detected = detectRecentAgentTypeFromText(s.cmd || (s.meta && s.meta.cmd) || s.launchCommand || '');
230
+ return detected || 'shell';
231
+ }
232
+
233
+ function recentAgentFilterLabel(agentType) {
234
+ const normalized = normalizeRecentAgentType(agentType) || 'shell';
235
+ return AGENT_FILTER_LABELS[normalized] || AGENT_FILTER_LABELS.shell;
236
+ }
237
+
238
+ function recentAgentFilterPriority(agentType) {
239
+ const normalized = normalizeRecentAgentType(agentType) || 'shell';
240
+ const idx = AGENT_FILTER_ORDER.indexOf(normalized);
241
+ return idx >= 0 ? idx : AGENT_FILTER_ORDER.length;
242
+ }
243
+
84
244
  function sessionPassesRecentSidebarFilters(session, filters) {
85
245
  const s = session || {};
86
246
  const f = filters || {};
@@ -93,7 +253,8 @@
93
253
  const project = stripWorktreePath(f.project || '');
94
254
  if (project && stripWorktreePath(s.project || s.cwd || '') !== project) return false;
95
255
 
96
- if (f.model && s.model !== f.model) return false;
256
+ const agent = normalizeRecentAgentType(f.agent || '');
257
+ if (agent && getRecentSessionAgentType(s) !== agent) return false;
97
258
  return true;
98
259
  }
99
260
 
@@ -103,9 +264,18 @@
103
264
 
104
265
  return {
105
266
  buildIdMatchResults,
267
+ createSessionIdentityIndex,
268
+ dedupeSessionCandidates,
106
269
  filterRecentSessionsForSidebar,
270
+ findSessionIdentityMatch,
271
+ mergeRecentSessionMetadata,
272
+ mergeRecentSessionCandidates,
273
+ getRecentSessionAgentType,
107
274
  getSearchableSessionIds,
108
275
  normalizeSearchValue,
276
+ normalizeRecentAgentType,
277
+ recentAgentFilterLabel,
278
+ recentAgentFilterPriority,
109
279
  scoreSessionIdMatch,
110
280
  sessionPassesRecentSidebarFilters,
111
281
  sessionMatchesRecentSearchQuery,
@@ -443,8 +443,12 @@ async function loadStatus() {
443
443
  }
444
444
  // Slack status now handled by loadConnectedServices()
445
445
  if (d.version) {
446
- var vl = document.getElementById('setup-version-label');
447
- if (vl) vl.textContent = 'Wall-E v' + d.version;
446
+ if (typeof window.setAppVersion === 'function') {
447
+ window.setAppVersion(d.version, { product: 'create-walle' });
448
+ } else {
449
+ var vl = document.getElementById('setup-version-label');
450
+ if (vl) vl.textContent = 'CTM / Wall-E v' + d.version;
451
+ }
448
452
  }
449
453
  if (d.ctm_data_dir) document.getElementById('setup-ctm-data-dir').value = d.ctm_data_dir;
450
454
  if (d.walle_data_dir) document.getElementById('setup-walle-data-dir').value = d.walle_data_dir;
@@ -1069,11 +1073,19 @@ async function saveDataDirs() {
1069
1073
  }
1070
1074
 
1071
1075
  // ── MCP Integrations ────────────────────────────────────────────────
1072
- var MCP_ICONS = { 'Claude Code': '\uD83E\uDD16', 'Cursor': '\u2328\uFE0F', 'Windsurf': '\uD83C\uDF0A', 'Claude Desktop': '\uD83D\uDDA5\uFE0F' };
1076
+ var MCP_ICONS = {
1077
+ 'Claude Code': '\uD83E\uDD16',
1078
+ 'Claude Code Global': '\uD83E\uDD16',
1079
+ 'Codex': '\u25CE',
1080
+ 'Cursor': '\u2328\uFE0F',
1081
+ 'Windsurf': '\uD83C\uDF0A',
1082
+ 'Claude Desktop': '\uD83D\uDDA5\uFE0F',
1083
+ };
1073
1084
  var MCP_STATUS_MAP = {
1074
1085
  configured: { label: 'Connected', cls: 'badge-connected' },
1075
1086
  not_configured: { label: 'Not configured', cls: 'badge-missing' },
1076
1087
  wrong_port: { label: 'Wrong port', cls: 'badge-warn' },
1088
+ invalid_config: { label: 'Invalid config', cls: 'badge-warn' },
1077
1089
  not_installed: { label: 'Not installed', cls: 'badge-dim' },
1078
1090
  };
1079
1091
 
@@ -1084,6 +1096,14 @@ function mcpDimMsg(text) {
1084
1096
  return d;
1085
1097
  }
1086
1098
 
1099
+ function mcpActionText(item) {
1100
+ if (item.status === 'configured') return 'Ready for new agent sessions. Restart the AI tool if it was already open.';
1101
+ if (item.status === 'not_configured') return 'Repair Configs will add the wall-e MCP server entry.';
1102
+ if (item.status === 'wrong_port') return 'Repair Configs will update the wall-e entry to the current Wall-E port.';
1103
+ if (item.status === 'invalid_config') return 'Fix the JSON syntax in this file, then run Repair Configs again.';
1104
+ return '';
1105
+ }
1106
+
1087
1107
  function buildMcpRow(item) {
1088
1108
  var info = MCP_STATUS_MAP[item.status] || { label: item.status, cls: 'badge-dim' };
1089
1109
  var row = document.createElement('div');
@@ -1108,6 +1128,13 @@ function buildMcpRow(item) {
1108
1128
  descDiv.textContent = item.configPath;
1109
1129
  textWrap.appendChild(descDiv);
1110
1130
  }
1131
+ var actionText = mcpActionText(item);
1132
+ if (actionText) {
1133
+ var actionDiv = document.createElement('div');
1134
+ actionDiv.className = 'integration-desc integration-action';
1135
+ actionDiv.textContent = actionText;
1136
+ textWrap.appendChild(actionDiv);
1137
+ }
1111
1138
 
1112
1139
  infoDiv.appendChild(iconDiv);
1113
1140
  infoDiv.appendChild(textWrap);
@@ -1123,25 +1150,38 @@ function buildMcpRow(item) {
1123
1150
 
1124
1151
  async function loadMcpIntegrations() {
1125
1152
  var container = document.getElementById('setup-mcp-integrations');
1153
+ var testBtn = document.getElementById('setup-mcp-test-btn');
1154
+ var fixBtn = document.getElementById('setup-mcp-fix-btn');
1126
1155
  try {
1127
1156
  var r = await fetch('/api/wall-e/mcp/integrations');
1128
1157
  var d = await r.json();
1129
- if (d.wallePort) { _mcpPort = d.wallePort; var portEl = document.getElementById('setup-mcp-port-display'); if (portEl) portEl.textContent = d.wallePort; }
1130
- if (!d.data || !d.data.length) { container.replaceChildren(mcpDimMsg('No AI tools detected.')); return; }
1158
+ if (d.wallePort) {
1159
+ _mcpPort = d.wallePort;
1160
+ ['setup-mcp-port-display', 'setup-mcp-port-display-toml'].forEach(function(id) {
1161
+ var portEl = document.getElementById(id);
1162
+ if (portEl) portEl.textContent = d.wallePort;
1163
+ });
1164
+ }
1165
+ if (!d.data || !d.data.length) {
1166
+ container.replaceChildren(mcpDimMsg('No AI tools detected.'));
1167
+ if (testBtn) testBtn.disabled = false;
1168
+ if (fixBtn) fixBtn.style.display = 'none';
1169
+ return;
1170
+ }
1131
1171
 
1132
- var hasConfigured = false, needsFix = false;
1172
+ var needsFix = false;
1133
1173
  var frag = document.createDocumentFragment();
1134
1174
  for (var i = 0; i < d.data.length; i++) {
1135
1175
  var item = d.data[i];
1136
- if (item.status === 'configured') hasConfigured = true;
1137
- if (item.status === 'not_configured' || item.status === 'wrong_port') needsFix = true;
1176
+ if (item.status === 'not_configured' || item.status === 'wrong_port' || item.status === 'invalid_config') needsFix = true;
1138
1177
  frag.appendChild(buildMcpRow(item));
1139
1178
  }
1140
1179
  container.replaceChildren(frag);
1141
- document.getElementById('setup-mcp-test-btn').disabled = !hasConfigured;
1142
- if (needsFix) document.getElementById('setup-mcp-fix-btn').style.display = '';
1180
+ if (testBtn) testBtn.disabled = false;
1181
+ if (fixBtn) fixBtn.style.display = needsFix ? '' : 'none';
1143
1182
  } catch {
1144
1183
  container.replaceChildren(mcpDimMsg('Could not load integrations.'));
1184
+ if (testBtn) testBtn.disabled = false;
1145
1185
  }
1146
1186
  }
1147
1187
 
@@ -1151,17 +1191,15 @@ async function testMcpConnection() {
1151
1191
  btn.disabled = true; btn.textContent = 'Testing\u2026';
1152
1192
  resultEl.style.display = 'none';
1153
1193
  try {
1154
- var r = await fetch('http://localhost:' + _mcpPort + '/mcp', {
1155
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1156
- body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'setup-wizard', version: '1.0' } } }),
1157
- });
1194
+ var r = await fetch('/api/wall-e/mcp/test');
1158
1195
  var d = await r.json();
1159
- if (d.result && d.result.serverInfo) {
1196
+ var data = d.data || {};
1197
+ if (data.ok) {
1160
1198
  resultEl.className = 'test-result ok'; resultEl.style.display = 'block';
1161
- resultEl.textContent = '\u2713 MCP server responding \u2014 ' + d.result.serverInfo.name + ' v' + (d.result.serverInfo.version || '?');
1199
+ resultEl.textContent = '\u2713 Wall-E MCP endpoint is reachable with ' + (data.toolCount || 0) + ' tools.';
1162
1200
  } else {
1163
1201
  resultEl.className = 'test-result fail'; resultEl.style.display = 'block';
1164
- resultEl.textContent = '\u2715 Unexpected response';
1202
+ resultEl.textContent = '\u2715 Wall-E MCP endpoint is not reachable on port ' + _mcpPort + ': ' + (data.error || 'unexpected response');
1165
1203
  }
1166
1204
  } catch (e) {
1167
1205
  resultEl.className = 'test-result fail'; resultEl.style.display = 'block';
@@ -1177,13 +1215,18 @@ async function fixMcpConfigs() {
1177
1215
  var r = await fetch('/api/wall-e/mcp/inject', { method: 'POST' });
1178
1216
  var d = await r.json();
1179
1217
  if (d.ok) {
1180
- setupToast('MCP configs updated');
1218
+ var errors = (d.results || []).filter(function(x) { return x.action === 'error'; });
1219
+ if (errors.length) {
1220
+ setupToast('Some MCP configs need manual repair: ' + errors.map(function(x) { return x.tool; }).join(', '), 'error');
1221
+ } else {
1222
+ setupToast('MCP configs updated');
1223
+ }
1181
1224
  loadMcpIntegrations();
1182
1225
  } else {
1183
1226
  setupToast(d.error || 'Fix failed', 'error');
1184
1227
  }
1185
1228
  } catch (e) { setupToast(e.message, 'error'); }
1186
- btn.disabled = false; btn.textContent = 'Fix All';
1229
+ btn.disabled = false; btn.textContent = 'Repair Configs';
1187
1230
  }
1188
1231
 
1189
1232
  // ── Embeddings ──────────────────────────────────────────────────────
@@ -14,6 +14,8 @@ const _streamState = {
14
14
  activeView: new Map(),
15
15
  tooltipEl: null,
16
16
  tooltipTimer: null,
17
+ _tooltipActiveSessionId: null,
18
+ _tooltipPendingSessionId: null,
17
19
  _tooltipReqId: 0,
18
20
  // Per-session priming bookkeeping.
19
21
  // _primed: sessionId → true once _primeConversationView() has rendered
@@ -41,9 +43,13 @@ function initStreamTooltip() {
41
43
  background: var(--surface-2, #1a1a2e); color: var(--fg, #e0e0e0);
42
44
  border: 1px solid var(--border, #333); border-radius: 8px;
43
45
  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: none;
46
+ overflow: hidden; font-size: 12px; line-height: 1.45; pointer-events: auto;
47
+ user-select: text; cursor: text;
45
48
  box-shadow: 0 10px 28px rgba(0,0,0,0.42), 0 0 0 1px rgba(255,255,255,0.02) inset;
46
49
  `;
50
+ tooltip.tabIndex = -1;
51
+ tooltip.setAttribute('role', 'dialog');
52
+ tooltip.setAttribute('aria-label', 'Session summary');
47
53
  document.body.appendChild(tooltip);
48
54
  _streamState.tooltipEl = tooltip;
49
55
  }
@@ -193,7 +199,7 @@ function _tooltipAppendProgress(container, progress) {
193
199
  }
194
200
 
195
201
  async function showStreamTooltip(sessionId, anchorEl) {
196
- if (_streamState.tooltipTimer) clearTimeout(_streamState.tooltipTimer);
202
+ _clearPendingStreamTooltip(false);
197
203
  const tooltip = _streamState.tooltipEl;
198
204
  if (!tooltip) return;
199
205
 
@@ -273,6 +279,8 @@ async function showStreamTooltip(sessionId, anchorEl) {
273
279
 
274
280
  tooltip.appendChild(content);
275
281
 
282
+ tooltip.dataset.sessionId = sessionId;
283
+ _streamState._tooltipActiveSessionId = sessionId;
276
284
  tooltip.style.display = 'block';
277
285
 
278
286
  // Position next to anchor (right of sidebar)
@@ -296,9 +304,13 @@ async function showStreamTooltip(sessionId, anchorEl) {
296
304
  }
297
305
 
298
306
  function hideStreamTooltip() {
299
- if (_streamState.tooltipTimer) { clearTimeout(_streamState.tooltipTimer); _streamState.tooltipTimer = null; }
307
+ _clearPendingStreamTooltip(false);
300
308
  _streamState._tooltipReqId++; // Invalidate any in-flight fetch
301
- if (_streamState.tooltipEl) _streamState.tooltipEl.style.display = 'none';
309
+ _streamState._tooltipActiveSessionId = null;
310
+ if (_streamState.tooltipEl) {
311
+ _streamState.tooltipEl.style.display = 'none';
312
+ delete _streamState.tooltipEl.dataset.sessionId;
313
+ }
302
314
  }
303
315
 
304
316
  // --- Conversation View ---
@@ -442,6 +454,10 @@ function handleStreamMessage(msg) {
442
454
  _primeConversationView(_domId, container);
443
455
  }
444
456
  } else if (msg.type === 'stream-event') {
457
+ const eventType = msg.data?.type || msg.role || msg.eventType || '';
458
+ if (eventType === 'user' && typeof window !== 'undefined' && typeof window._ctmRecordLivePromptPreview === 'function') {
459
+ window._ctmRecordLivePromptPreview(_domId, msg.data?.text || msg.text || '');
460
+ }
445
461
  const container = document.querySelector(`.conversation-view[data-session-id="${CSS.escape(_domId)}"]`);
446
462
  if (container) {
447
463
  // If we haven't finished priming yet, queue the event and replay it later.
@@ -486,6 +502,7 @@ function applyStreamStatus(msg) {
486
502
  if (msg.lastActivity && s.meta) {
487
503
  s.meta.lastActivity = msg.lastActivity;
488
504
  }
505
+ if (changed && typeof scheduleStandupRefresh === 'function') scheduleStandupRefresh();
489
506
  return changed;
490
507
  }
491
508
 
@@ -1054,17 +1071,38 @@ function _streamTooltipClosestSessionItem(target, root) {
1054
1071
  return item;
1055
1072
  }
1056
1073
 
1074
+ function _streamTooltipVisible() {
1075
+ const tooltip = _streamState.tooltipEl;
1076
+ return !!(tooltip && tooltip.style.display !== 'none');
1077
+ }
1078
+
1079
+ function _clearPendingStreamTooltip(invalidateRequest) {
1080
+ if (_streamState.tooltipTimer) {
1081
+ clearTimeout(_streamState.tooltipTimer);
1082
+ _streamState.tooltipTimer = null;
1083
+ }
1084
+ _streamState._tooltipPendingSessionId = null;
1085
+ if (invalidateRequest) _streamState._tooltipReqId++;
1086
+ }
1087
+
1057
1088
  function _scheduleStreamTooltip(item) {
1058
1089
  const id = item?.dataset?.sessionId;
1059
1090
  if (!id) return;
1060
- _streamState.tooltipTimer = setTimeout(() => showStreamTooltip(id, item), 500);
1091
+ if (_streamState._tooltipPendingSessionId === id) return;
1092
+ if (_streamState._tooltipActiveSessionId === id && _streamTooltipVisible()) return;
1093
+ _clearPendingStreamTooltip(true);
1094
+ _streamState._tooltipPendingSessionId = id;
1095
+ _streamState.tooltipTimer = setTimeout(() => {
1096
+ _streamState.tooltipTimer = null;
1097
+ _streamState._tooltipPendingSessionId = null;
1098
+ showStreamTooltip(id, item);
1099
+ }, 500);
1061
1100
  }
1062
1101
 
1063
1102
  function _handleStreamTooltipMouseOver(e, list) {
1064
1103
  const item = _streamTooltipClosestSessionItem(e.target, list);
1065
1104
  if (!item) return;
1066
1105
  if (e.relatedTarget && _streamTooltipContains(item, e.relatedTarget)) return;
1067
- hideStreamTooltip();
1068
1106
  _scheduleStreamTooltip(item);
1069
1107
  }
1070
1108
 
@@ -1072,6 +1110,14 @@ function _handleStreamTooltipMouseOut(e, list) {
1072
1110
  const item = _streamTooltipClosestSessionItem(e.target, list);
1073
1111
  if (!item) return;
1074
1112
  if (e.relatedTarget && _streamTooltipContains(item, e.relatedTarget)) return;
1113
+ const id = item?.dataset?.sessionId;
1114
+ if (_streamState._tooltipPendingSessionId === id) _clearPendingStreamTooltip(true);
1115
+ }
1116
+
1117
+ function _handleStreamTooltipDocumentPointerDown(e) {
1118
+ const tooltip = _streamState.tooltipEl;
1119
+ if (!tooltip || !_streamTooltipVisible()) return;
1120
+ if (_streamTooltipContains(tooltip, e.target)) return;
1075
1121
  hideStreamTooltip();
1076
1122
  }
1077
1123
 
@@ -1083,6 +1129,7 @@ function bindStreamTooltips() {
1083
1129
 
1084
1130
  list.addEventListener('mouseover', (e) => _handleStreamTooltipMouseOver(e, list));
1085
1131
  list.addEventListener('mouseout', (e) => _handleStreamTooltipMouseOut(e, list));
1132
+ document.addEventListener('pointerdown', _handleStreamTooltipDocumentPointerDown, true);
1086
1133
  }
1087
1134
 
1088
1135
  // --- Init ---
@@ -1120,7 +1167,9 @@ if (typeof module !== 'undefined' && module.exports) {
1120
1167
  bindStreamTooltips,
1121
1168
  _handleStreamTooltipMouseOver,
1122
1169
  _handleStreamTooltipMouseOut,
1170
+ _handleStreamTooltipDocumentPointerDown,
1123
1171
  _streamTooltipClosestSessionItem,
1124
1172
  _streamTooltipContains,
1173
+ _streamTooltipVisible,
1125
1174
  };
1126
1175
  }