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.
Files changed (98) hide show
  1. package/README.md +8 -3
  2. package/bin/create-walle.js +232 -32
  3. package/bin/mcp-inject.js +18 -53
  4. package/package.json +3 -1
  5. package/template/claude-task-manager/api-prompts.js +11 -2
  6. package/template/claude-task-manager/approval-agent.js +7 -0
  7. package/template/claude-task-manager/db.js +94 -75
  8. package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
  9. package/template/claude-task-manager/docs/session-tooltip-freshness-design.md +224 -0
  10. package/template/claude-task-manager/docs/session-ux-issue-review-2026-05-01.md +369 -0
  11. package/template/claude-task-manager/fuzzy-utils.js +10 -2
  12. package/template/claude-task-manager/git-utils.js +140 -10
  13. package/template/claude-task-manager/lib/agent-capabilities.js +1 -1
  14. package/template/claude-task-manager/lib/agent-presets.js +38 -5
  15. package/template/claude-task-manager/lib/codex-terminal-final.js +53 -0
  16. package/template/claude-task-manager/lib/ctm-session-context-api.js +222 -0
  17. package/template/claude-task-manager/lib/session-diagnostics.js +56 -0
  18. package/template/claude-task-manager/lib/session-history.js +309 -16
  19. package/template/claude-task-manager/lib/session-standup.js +409 -0
  20. package/template/claude-task-manager/lib/session-stream.js +253 -20
  21. package/template/claude-task-manager/lib/standup-attention.js +200 -0
  22. package/template/claude-task-manager/lib/status-hooks.js +8 -2
  23. package/template/claude-task-manager/lib/update-telemetry.js +114 -0
  24. package/template/claude-task-manager/lib/walle-ctm-history.js +49 -6
  25. package/template/claude-task-manager/lib/walle-default-model.js +55 -0
  26. package/template/claude-task-manager/lib/walle-mcp-auto-config.js +66 -0
  27. package/template/claude-task-manager/lib/walle-supervisor.js +86 -19
  28. package/template/claude-task-manager/lib/walle-transcript.js +1 -3
  29. package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
  30. package/template/claude-task-manager/package.json +1 -0
  31. package/template/claude-task-manager/providers/codex-mcp.js +104 -0
  32. package/template/claude-task-manager/providers/index.js +2 -0
  33. package/template/claude-task-manager/public/css/setup.css +2 -1
  34. package/template/claude-task-manager/public/css/walle.css +71 -0
  35. package/template/claude-task-manager/public/index.html +2388 -429
  36. package/template/claude-task-manager/public/js/message-renderer.js +314 -35
  37. package/template/claude-task-manager/public/js/session-search-utils.js +185 -3
  38. package/template/claude-task-manager/public/js/session-status-precedence.js +125 -0
  39. package/template/claude-task-manager/public/js/setup.js +62 -19
  40. package/template/claude-task-manager/public/js/stream-view.js +396 -55
  41. package/template/claude-task-manager/public/js/terminal-restore-state.js +57 -0
  42. package/template/claude-task-manager/public/js/walle-session.js +234 -26
  43. package/template/claude-task-manager/public/js/walle.js +143 -2
  44. package/template/claude-task-manager/server.js +1402 -433
  45. package/template/claude-task-manager/session-integrity.js +77 -28
  46. package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
  47. package/template/claude-task-manager/workers/scrollback-worker.js +5 -6
  48. package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
  49. package/template/package.json +1 -1
  50. package/template/wall-e/agent-runners/claude-code.js +2 -0
  51. package/template/wall-e/agent.js +63 -8
  52. package/template/wall-e/api-walle.js +330 -52
  53. package/template/wall-e/brain.js +291 -42
  54. package/template/wall-e/chat.js +172 -15
  55. package/template/wall-e/coding/compaction-service.js +19 -5
  56. package/template/wall-e/coding/stream-processor.js +22 -2
  57. package/template/wall-e/coding/workspace-replay.js +1 -4
  58. package/template/wall-e/coding-orchestrator.js +250 -80
  59. package/template/wall-e/compat.js +0 -28
  60. package/template/wall-e/context/context-builder.js +3 -1
  61. package/template/wall-e/embeddings.js +2 -7
  62. package/template/wall-e/eval/agent-runner.js +30 -9
  63. package/template/wall-e/eval/benchmark-generator.js +21 -1
  64. package/template/wall-e/eval/benchmarks/chat-eval.json +66 -6
  65. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
  66. package/template/wall-e/eval/cc-replay.js +1 -0
  67. package/template/wall-e/eval/codex-cli-baseline.js +633 -0
  68. package/template/wall-e/eval/debug-agent003.js +1 -0
  69. package/template/wall-e/eval/eval-orchestrator.js +3 -3
  70. package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
  71. package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
  72. package/template/wall-e/eval/run-model-comparison.js +1 -0
  73. package/template/wall-e/eval/swebench-adapter.js +1 -0
  74. package/template/wall-e/evaluation/quorum-evaluator.js +0 -1
  75. package/template/wall-e/extraction/knowledge-extractor.js +1 -2
  76. package/template/wall-e/lib/mcp-integration.js +336 -0
  77. package/template/wall-e/llm/ollama.js +47 -8
  78. package/template/wall-e/llm/ollama.plugin.json +1 -1
  79. package/template/wall-e/llm/tool-adapter.js +1 -0
  80. package/template/wall-e/loops/ingest.js +42 -8
  81. package/template/wall-e/loops/initiative.js +87 -2
  82. package/template/wall-e/mcp-server.js +872 -19
  83. package/template/wall-e/memory/ctm-context-client.js +230 -0
  84. package/template/wall-e/memory/ctm-session-context.js +1376 -0
  85. package/template/wall-e/prompts/coding/memory-protocol.md +6 -0
  86. package/template/wall-e/server.js +30 -1
  87. package/template/wall-e/skills/_bundled/memory-search/SKILL.md +8 -0
  88. package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
  89. package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
  90. package/template/wall-e/skills/_bundled/slack-mentions/run.js +471 -188
  91. package/template/wall-e/skills/skill-planner.js +86 -4
  92. package/template/wall-e/slack/socket-mode-listener.js +276 -0
  93. package/template/wall-e/telemetry.js +70 -2
  94. package/template/wall-e/tools/builtin-middleware.js +55 -2
  95. package/template/wall-e/tools/shell-policy.js +1 -1
  96. package/template/wall-e/tools/slack-owner.js +104 -0
  97. package/template/website/index.html +4 -4
  98. package/template/builder-journal.md +0 -17
@@ -15,13 +15,16 @@
15
15
  * - classifyMessage(m, stripped, isToolOnly) — 'key'|'normal'|'self-thought'|'summary'
16
16
  * - renderReviewMsg(m, i, msgType?) — Review row HTML string
17
17
  * - renderSelfThoughtMsg(m, i, stripped) — single-self-thought collapsed group HTML
18
- * - renderSelfThoughtItem(m, i, stripped) — inner item inside a multi-step thought-group
19
- * - groupMessages(messages) — pre-render grouping for Review
20
- * - renderGroup(g) — render one group from groupMessages
21
- * - _buildCollapsible(label, color, preview, bodyEl) Conversation tab DOM helper
22
- * - renderConversationEvent(evt) Conversation row DOM element
23
- * - refreshConversationActivityGroup(group) — recompute grouped tool UI
24
- *
18
+ * - renderSelfThoughtItem(m, i, stripped) — inner item inside a multi-step thought-group
19
+ * - groupMessages(messages) — pre-render grouping for Review
20
+ * - renderGroup(g) — render one group from groupMessages
21
+ * - groupMessagesIntoTurns(messages) — user prompt + nested response groups
22
+ * - renderReviewTurn(turn) Review turn HTML string
23
+ * - _buildCollapsible(label, color, preview, bodyEl) — Conversation tab DOM helper
24
+ * - renderConversationEvent(evt) — Conversation row DOM element
25
+ * - createConversationTurn(evt, opts) — Conversation prompt-turn DOM
26
+ * - refreshConversationActivityGroup(group) — recompute grouped tool UI
27
+ *
25
28
  * Depends on global escHtml (defined in index.html). Since the renderer
26
29
  * functions are only invoked AFTER the page's body scripts run, escHtml
27
30
  * is available at call time even though message-renderer.js loads first.
@@ -219,10 +222,161 @@
219
222
  return parts.slice(0, max).join(' · ') + ` · +${parts.length - max}`;
220
223
  }
221
224
 
222
- function _previewFromText(text, max = 90) {
223
- const s = String(text || '').replace(/\n/g, ' ').trim();
224
- return s.length > max ? s.slice(0, max) + '…' : s;
225
- }
225
+ function _previewFromText(text, max = 90) {
226
+ const s = String(text || '').replace(/\n/g, ' ').trim();
227
+ return s.length > max ? s.slice(0, max) + '…' : s;
228
+ }
229
+
230
+ function _stripToolTokens(text) {
231
+ return String(text || '').replace(/\[Tool(?: result)?:\s*[^\]]+\]/g, '').trim();
232
+ }
233
+
234
+ function _messageItem(m, i) {
235
+ const text = String(m && m.text || '');
236
+ const stripped = _stripToolTokens(text);
237
+ const isToolOnly = stripped.length === 0 && /\[Tool: /.test(text);
238
+ const msgType = MR.classifyMessage(m || {}, stripped, isToolOnly);
239
+ return { m: m || { role: 'system', text: '' }, i, msgType, stripped, isToolOnly };
240
+ }
241
+
242
+ function _isPromptItem(item) {
243
+ if (!item || !item.m || item.m.role !== 'user') return false;
244
+ const text = String(item.m.text || '').trim();
245
+ if (!text && item.msgType !== 'command') return false;
246
+ return item.msgType === 'key' || item.msgType === 'command';
247
+ }
248
+
249
+ function _turnResponseItems(turn) {
250
+ return (turn && turn.responses ? turn.responses : []).map(item => {
251
+ const copy = Object.assign({}, item.m);
252
+ copy._reviewIdx = item.i;
253
+ return copy;
254
+ });
255
+ }
256
+
257
+ function _turnCounts(turn) {
258
+ const responses = turn.responses || [];
259
+ let assistant = 0;
260
+ let tools = 0;
261
+ let detail = 0;
262
+ for (const item of responses) {
263
+ if (item.m.role === 'assistant') assistant++;
264
+ tools += _toolNamesFromText(item.m.text).length;
265
+ if (item.msgType === 'self-thought' || item.msgType === 'skill-body' || item.msgType === 'local-cmd' || item.msgType === 'summary') detail++;
266
+ }
267
+ return { assistant, tools, detail, total: responses.length };
268
+ }
269
+
270
+ function _turnAlert(turn) {
271
+ const text = (turn.responses || [])
272
+ .map(item => String(item.m && item.m.text || ''))
273
+ .join('\n')
274
+ .toLowerCase();
275
+ if (/(traceback|uncaught|syntaxerror|typeerror|referenceerror|exit code 1|permission denied|merge conflict|blocked|blocker|failed|failure|error:)/.test(text)) {
276
+ return { level: 'error', label: 'attention' };
277
+ }
278
+ if (/(warning:|warned|warns|timed out|timeout|needs user|requires approval|denied)/.test(text)) {
279
+ return { level: 'warning', label: 'warning' };
280
+ }
281
+ return null;
282
+ }
283
+
284
+ function _turnMetaHtml(turn) {
285
+ const counts = _turnCounts(turn);
286
+ const badges = [];
287
+ if (counts.assistant) badges.push(counts.assistant + ' repl' + (counts.assistant === 1 ? 'y' : 'ies'));
288
+ if (counts.tools) badges.push(counts.tools + ' tool' + (counts.tools === 1 ? '' : 's'));
289
+ if (counts.detail) badges.push(counts.detail + ' detail' + (counts.detail === 1 ? '' : 's'));
290
+ if (!counts.total && turn.type !== 'setup') badges.push('no response yet');
291
+ const alert = _turnAlert(turn);
292
+ if (alert) badges.push('<span class="prompt-turn-badge prompt-turn-alert ' + alert.level + '">' + escHtml(alert.label) + '</span>');
293
+ return badges.map(b => {
294
+ if (String(b).indexOf('<span') === 0) return b;
295
+ return '<span class="prompt-turn-badge">' + escHtml(b) + '</span>';
296
+ }).join('');
297
+ }
298
+
299
+ function _renderPromptBlock(item, turn) {
300
+ const m = item.m || {};
301
+ const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
302
+ const cmd = item.msgType === 'command' ? MR.parseCommandInvocation(m.text) : null;
303
+ const bodyHtml = cmd
304
+ ? (cmd.args ? MR.formatMsgText(cmd.args) : '<span class="msg-empty">(no arguments)</span>')
305
+ : MR.formatMsgText(m.text || '');
306
+ const badge = cmd ? '<span class="msg-cmd-badge" title="Slash command">/' + escHtml(cmd.name) + '</span>' : '';
307
+ const parentUuid = m.parentUuid ? ' data-parent-uuid="' + escHtml(m.parentUuid) + '"' : '';
308
+ return '<div class="review-msg user key-msg prompt-turn-prompt"' + parentUuid + '>'
309
+ + '<div class="msg-header">'
310
+ + '<span class="msg-role">You</span>'
311
+ + badge
312
+ + '<span class="msg-time">' + escHtml(time) + '</span>'
313
+ + '</div>'
314
+ + '<div class="msg-text" data-msg-idx="' + item.i + '">' + bodyHtml + '</div>'
315
+ + '</div>';
316
+ }
317
+
318
+ function _turnId(turn, idx) {
319
+ if (turn.type === 'setup') return 'setup';
320
+ const promptIdx = turn.prompt ? turn.prompt.i : idx;
321
+ return 'prompt-' + promptIdx;
322
+ }
323
+
324
+ MR.groupMessagesIntoTurns = function (messages) {
325
+ const turns = [];
326
+ let current = null;
327
+ let setup = null;
328
+ const ensureSetup = () => {
329
+ if (!setup) setup = { type: 'setup', prompt: null, responses: [], startIndex: -1, endIndex: -1 };
330
+ return setup;
331
+ };
332
+ (messages || []).forEach((m, i) => {
333
+ const item = _messageItem(m, i);
334
+ if (_isPromptItem(item)) {
335
+ if (current) turns.push(current);
336
+ current = { type: 'prompt', prompt: item, responses: [], startIndex: i, endIndex: i };
337
+ return;
338
+ }
339
+ const target = current || ensureSetup();
340
+ if (target.startIndex < 0) target.startIndex = i;
341
+ target.responses.push(item);
342
+ target.endIndex = i;
343
+ });
344
+ if (current) turns.push(current);
345
+ if (setup && setup.responses.length) turns.unshift(setup);
346
+ return turns;
347
+ };
348
+
349
+ MR.renderReviewTurn = function (turn, idx, opts) {
350
+ opts = opts || {};
351
+ const id = _turnId(turn, idx);
352
+ const expanded = !!opts.expanded;
353
+ const typeClass = turn.type === 'setup' ? ' setup-turn' : '';
354
+ const promptIdx = turn.prompt ? turn.prompt.i : '';
355
+ const parentUuid = turn.prompt && turn.prompt.m && turn.prompt.m.parentUuid
356
+ ? ' data-parent-uuid="' + escHtml(turn.prompt.m.parentUuid) + '"'
357
+ : '';
358
+ const promptHtml = turn.type === 'setup'
359
+ ? '<div class="prompt-turn-setup-title">Session setup and context</div>'
360
+ : _renderPromptBlock(turn.prompt, turn);
361
+ const responseMessages = _turnResponseItems(turn);
362
+ const responseHtml = responseMessages.length
363
+ ? MR.groupMessages(responseMessages).map(MR.renderGroup).join('')
364
+ : '<div class="prompt-turn-empty">No agent response recorded yet.</div>';
365
+ return '<section class="prompt-turn' + typeClass + (expanded ? ' expanded' : '') + '" data-turn-id="' + escHtml(id) + '" data-msg-idx="' + escHtml(String(promptIdx)) + '"' + parentUuid + '>'
366
+ + '<div class="prompt-turn-header" role="button" tabindex="0" aria-expanded="' + (expanded ? 'true' : 'false') + '">'
367
+ + '<span class="prompt-turn-chevron">▶</span>'
368
+ + '<div class="prompt-turn-head-main">' + promptHtml + '</div>'
369
+ + '<div class="prompt-turn-meta">' + _turnMetaHtml(turn) + '</div>'
370
+ + '</div>'
371
+ + '<div class="prompt-turn-response">'
372
+ + responseHtml
373
+ + '</div>'
374
+ + '</section>';
375
+ };
376
+
377
+ MR.renderReviewTurns = function (messages, opts) {
378
+ return MR.groupMessagesIntoTurns(messages).map((turn, idx) => MR.renderReviewTurn(turn, idx, opts)).join('');
379
+ };
226
380
 
227
381
  // ------------------------------------------------------------------
228
382
  // Review-page renderers — return HTML strings. The page batches many
@@ -338,30 +492,31 @@
338
492
  + '<div class="msg-text">' + textHtml + '</div></div>';
339
493
  };
340
494
 
341
- MR.groupMessages = function (messages) {
342
- const groups = [];
343
- let i = 0;
344
- while (i < messages.length) {
345
- const m = messages[i];
346
- const stripped = m.text.replace(/\[Tool: [^\]]+\]/g, '').trim();
347
- const isToolOnly = stripped.length === 0 && /\[Tool: /.test(m.text);
348
- const msgType = MR.classifyMessage(m, stripped, isToolOnly);
495
+ MR.groupMessages = function (messages) {
496
+ const groups = [];
497
+ let i = 0;
498
+ while (i < messages.length) {
499
+ const m = messages[i];
500
+ const idx = typeof m._reviewIdx === 'number' ? m._reviewIdx : i;
501
+ const stripped = m.text.replace(/\[Tool: [^\]]+\]/g, '').trim();
502
+ const isToolOnly = stripped.length === 0 && /\[Tool: /.test(m.text);
503
+ const msgType = MR.classifyMessage(m, stripped, isToolOnly);
349
504
 
350
505
  if (msgType === 'self-thought') {
351
506
  const groupItems = [];
352
507
  while (i < messages.length) {
353
508
  const gm = messages[i];
354
- const gs = gm.text.replace(/\[Tool: [^\]]+\]/g, '').trim();
355
- const gto = gs.length === 0 && /\[Tool: /.test(gm.text);
356
- if (MR.classifyMessage(gm, gs, gto) !== 'self-thought') break;
357
- groupItems.push({ m: gm, i: i, stripped: gs, isToolOnly: gto });
358
- i++;
359
- }
360
- groups.push({ type: 'thought-group', items: groupItems });
361
- } else {
362
- groups.push({ type: 'message', m, i, msgType, stripped, isToolOnly });
363
- i++;
364
- }
509
+ const gs = gm.text.replace(/\[Tool: [^\]]+\]/g, '').trim();
510
+ const gto = gs.length === 0 && /\[Tool: /.test(gm.text);
511
+ if (MR.classifyMessage(gm, gs, gto) !== 'self-thought') break;
512
+ groupItems.push({ m: gm, i: typeof gm._reviewIdx === 'number' ? gm._reviewIdx : i, stripped: gs, isToolOnly: gto });
513
+ i++;
514
+ }
515
+ groups.push({ type: 'thought-group', items: groupItems });
516
+ } else {
517
+ groups.push({ type: 'message', m, i: idx, msgType, stripped, isToolOnly });
518
+ i++;
519
+ }
365
520
  }
366
521
  return groups;
367
522
  };
@@ -492,9 +647,9 @@
492
647
  // `{ type: 'stream-event', data: { type: 'assistant', ... } }`. Look
493
648
  // at evt.type first, fall back to evt.data.type.
494
649
  const _ROLES = ['user', 'assistant', 'tool_result', 'summary'];
495
- MR.renderConversationEvent = function (evt) {
496
- const text = (evt.data?.text || '').trim();
497
- const toolUses = evt.data?.toolUses || [];
650
+ MR.renderConversationEvent = function (evt) {
651
+ const text = (evt.data?.text || '').trim();
652
+ const toolUses = evt.data?.toolUses || [];
498
653
 
499
654
  let evType = _ROLES.includes(evt.type) ? evt.type : null;
500
655
  if (!evType && _ROLES.includes(evt.data?.type)) evType = evt.data.type;
@@ -849,8 +1004,132 @@
849
1004
  return div;
850
1005
  }
851
1006
 
852
- return null;
853
- };
1007
+ return null;
1008
+ };
1009
+
1010
+ function _conversationEventType(evt) {
1011
+ let evType = _ROLES.includes(evt && evt.type) ? evt.type : null;
1012
+ if (!evType && _ROLES.includes(evt && evt.data && evt.data.type)) evType = evt.data.type;
1013
+ return evType;
1014
+ }
1015
+
1016
+ function _conversationEventText(evt) {
1017
+ return String(evt && evt.data && evt.data.text || '').trim();
1018
+ }
1019
+
1020
+ MR.isConversationPromptEvent = function (evt) {
1021
+ if (_conversationEventType(evt) !== 'user') return false;
1022
+ const text = _conversationEventText(evt);
1023
+ if (!text) return false;
1024
+ if (MR.parseSkillBody(text) || MR.parseLocalCommand(text)) return false;
1025
+ return true;
1026
+ };
1027
+
1028
+ MR.setPromptTurnExpanded = function (turnEl, expanded) {
1029
+ if (!turnEl) return;
1030
+ const has = turnEl.classList && turnEl.classList.contains('expanded');
1031
+ if (expanded && !has) turnEl.classList.add('expanded');
1032
+ if (!expanded && has) turnEl.classList.remove('expanded');
1033
+ const header = turnEl.querySelector('.prompt-turn-header');
1034
+ if (header) header.setAttribute('aria-expanded', expanded ? 'true' : 'false');
1035
+ };
1036
+
1037
+ MR.getPromptTurnBody = function (turnEl) {
1038
+ return turnEl && turnEl.querySelector ? turnEl.querySelector('.prompt-turn-response') : null;
1039
+ };
1040
+
1041
+ MR.refreshPromptTurnMeta = function (turnEl) {
1042
+ if (!turnEl || !turnEl.querySelector) return;
1043
+ const body = MR.getPromptTurnBody(turnEl);
1044
+ const meta = turnEl.querySelector('.prompt-turn-meta');
1045
+ if (!body || !meta) return;
1046
+ const children = Array.from(body.children || []).filter(el => !(el.dataset && el.dataset.conversationState));
1047
+ const responseBlocks = children.filter(el => !(el.classList && el.classList.contains('prompt-turn-empty'))).length;
1048
+ let toolBlocks = 0;
1049
+ for (const el of children) {
1050
+ const cls = String(el.className || '');
1051
+ if (cls.includes('tool-activity-group') || cls.includes('tool-only') || cls.includes('conv-tool-group')) toolBlocks++;
1052
+ }
1053
+ meta.textContent = '';
1054
+ const appendBadge = (text, cls) => {
1055
+ const badge = document.createElement('span');
1056
+ badge.className = 'prompt-turn-badge' + (cls ? ' ' + cls : '');
1057
+ badge.textContent = text;
1058
+ meta.appendChild(badge);
1059
+ };
1060
+ if (responseBlocks) appendBadge(responseBlocks + ' detail' + (responseBlocks === 1 ? '' : 's'));
1061
+ else appendBadge('no response yet');
1062
+ if (toolBlocks) appendBadge(toolBlocks + ' tool block' + (toolBlocks === 1 ? '' : 's'));
1063
+ };
1064
+
1065
+ function _wirePromptTurnHeader(turnEl, header) {
1066
+ if (!turnEl || !header) return;
1067
+ const toggle = (ev) => {
1068
+ if (ev && ev.target && ev.target.closest && ev.target.closest('a,button,input,textarea,select')) return;
1069
+ MR.setPromptTurnExpanded(turnEl, !turnEl.classList.contains('expanded'));
1070
+ };
1071
+ header.addEventListener('click', toggle);
1072
+ header.addEventListener('keydown', (ev) => {
1073
+ if (ev.key === 'Enter' || ev.key === ' ') {
1074
+ ev.preventDefault();
1075
+ toggle(ev);
1076
+ }
1077
+ });
1078
+ }
1079
+
1080
+ MR.createConversationTurn = function (promptEvt, opts) {
1081
+ opts = opts || {};
1082
+ const isSetup = !promptEvt || opts.setup;
1083
+ const turn = document.createElement('section');
1084
+ turn.className = 'prompt-turn conversation-turn' + (isSetup ? ' setup-turn' : '');
1085
+ turn.dataset.turnId = isSetup ? 'setup' : ('prompt-' + (promptEvt.data?.parentUuid || Date.now()));
1086
+ if (!isSetup && promptEvt.data?.parentUuid) turn.dataset.parentUuid = promptEvt.data.parentUuid;
1087
+
1088
+ const header = document.createElement('div');
1089
+ header.className = 'prompt-turn-header';
1090
+ header.setAttribute('role', 'button');
1091
+ header.setAttribute('tabindex', '0');
1092
+ header.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
1093
+
1094
+ const chev = document.createElement('span');
1095
+ chev.className = 'prompt-turn-chevron';
1096
+ chev.textContent = '▶';
1097
+ const main = document.createElement('div');
1098
+ main.className = 'prompt-turn-head-main';
1099
+ if (isSetup) {
1100
+ const setupTitle = document.createElement('div');
1101
+ setupTitle.className = 'prompt-turn-setup-title';
1102
+ setupTitle.textContent = opts.setupLabel || 'Session setup and context';
1103
+ main.appendChild(setupTitle);
1104
+ } else {
1105
+ const promptEl = MR.renderConversationEvent(promptEvt);
1106
+ if (promptEl) {
1107
+ promptEl.classList.add('prompt-turn-prompt');
1108
+ main.appendChild(promptEl);
1109
+ }
1110
+ }
1111
+ const meta = document.createElement('div');
1112
+ meta.className = 'prompt-turn-meta';
1113
+ header.appendChild(chev);
1114
+ header.appendChild(main);
1115
+ header.appendChild(meta);
1116
+
1117
+ const body = document.createElement('div');
1118
+ body.className = 'prompt-turn-response';
1119
+ turn.appendChild(header);
1120
+ turn.appendChild(body);
1121
+ _wirePromptTurnHeader(turn, header);
1122
+ MR.setPromptTurnExpanded(turn, !!opts.expanded);
1123
+ MR.refreshPromptTurnMeta(turn);
1124
+ return turn;
1125
+ };
1126
+
1127
+ MR.createEmptyConversationTurnBody = function () {
1128
+ const empty = document.createElement('div');
1129
+ empty.className = 'prompt-turn-empty';
1130
+ empty.textContent = 'No agent response recorded yet.';
1131
+ return empty;
1132
+ };
854
1133
 
855
1134
  // ── DOM builders ─────────────────────────────────────────────────────
856
1135
 
@@ -6,7 +6,11 @@
6
6
  if (root) root.SessionSearchUtils = api;
7
7
  })(typeof globalThis !== 'undefined' ? globalThis : this, function () {
8
8
  function normalizeSearchValue(value) {
9
- return String(value || '').trim().toLowerCase();
9
+ return String(value || '')
10
+ .trim()
11
+ .toLowerCase()
12
+ .replace(/(^|[\s([{])[$/]+(?=[a-z0-9_-])/g, '$1')
13
+ .replace(/\s+/g, ' ');
10
14
  }
11
15
 
12
16
  function getSearchableSessionIds(session) {
@@ -26,6 +30,112 @@
26
30
  return ids;
27
31
  }
28
32
 
33
+ function rememberRecentSessionIdentities(index, session) {
34
+ for (const id of getSearchableSessionIds(session)) index.set(id, session);
35
+ }
36
+
37
+ function findSessionIdentityMatch(index, session) {
38
+ if (!index || !session) return null;
39
+ for (const id of getSearchableSessionIds(session)) {
40
+ const existing = index.get(id);
41
+ if (existing) return existing;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function createSessionIdentityIndex(sessions) {
47
+ const index = new Map();
48
+ for (const session of sessions || []) rememberRecentSessionIdentities(index, session);
49
+ return index;
50
+ }
51
+
52
+ function isNewerTime(candidate, current) {
53
+ const candidateMs = Date.parse(String(candidate || ''));
54
+ if (!Number.isFinite(candidateMs)) return false;
55
+ const currentMs = Date.parse(String(current || ''));
56
+ return !Number.isFinite(currentMs) || candidateMs > currentMs;
57
+ }
58
+
59
+ function mergeRecentSessionMetadata(target, source) {
60
+ if (!target || !source) return target;
61
+
62
+ const sourceAgent = getRecentSessionAgentType(source);
63
+ const targetAgent = getRecentSessionAgentType(target);
64
+ if (sourceAgent && sourceAgent !== 'shell' && (!target.agent || targetAgent === 'shell')) {
65
+ target.agent = sourceAgent;
66
+ }
67
+
68
+ for (const field of [
69
+ 'agentType',
70
+ 'provider',
71
+ 'agentSessionId',
72
+ 'provisionalId',
73
+ 'cmd',
74
+ 'launchCommand',
75
+ 'cwd',
76
+ 'project',
77
+ 'projectEntry',
78
+ 'firstMessage',
79
+ 'title',
80
+ 'aiTitle',
81
+ 'displayTitle',
82
+ 'gitBranch',
83
+ 'model',
84
+ 'modelProvider',
85
+ 'timestamp',
86
+ 'version',
87
+ 'jsonlPath',
88
+ ]) {
89
+ if (!target[field] && source[field]) target[field] = source[field];
90
+ }
91
+
92
+ if (isNewerTime(source.modifiedAt, target.modifiedAt)) target.modifiedAt = source.modifiedAt;
93
+ if (isNewerTime(source.fileModifiedAt, target.fileModifiedAt)) target.fileModifiedAt = source.fileModifiedAt;
94
+ if (source.isEmpty === false && target.isEmpty !== false) target.isEmpty = false;
95
+ if ((source.userMsgCount || 0) > (target.userMsgCount || 0)) target.userMsgCount = source.userMsgCount;
96
+ if ((source.fileSize || 0) > (target.fileSize || 0)) target.fileSize = source.fileSize;
97
+ if (source.starred) target.starred = true;
98
+ if (source.userRenamed) {
99
+ const renamedTitle = source.aiTitle || source.displayTitle || source.title || '';
100
+ if (renamedTitle) {
101
+ target.aiTitle = renamedTitle;
102
+ target.title = renamedTitle;
103
+ target.displayTitle = renamedTitle;
104
+ }
105
+ target.userRenamed = true;
106
+ }
107
+ return target;
108
+ }
109
+
110
+ function mergeRecentSessionCandidates(recentSessions, additionalSessions) {
111
+ const merged = (recentSessions || []).map((session) => ({ ...session }));
112
+ const index = createSessionIdentityIndex(merged);
113
+ for (const raw of additionalSessions || []) {
114
+ const candidate = { ...raw };
115
+ const existing = findSessionIdentityMatch(index, candidate);
116
+ if (existing) mergeRecentSessionMetadata(existing, candidate);
117
+ else merged.push(candidate);
118
+ rememberRecentSessionIdentities(index, existing || candidate);
119
+ }
120
+ return merged;
121
+ }
122
+
123
+ function dedupeSessionCandidates(sessions) {
124
+ const merged = [];
125
+ const index = new Map();
126
+ for (const raw of sessions || []) {
127
+ const candidate = { ...raw };
128
+ const existing = findSessionIdentityMatch(index, candidate);
129
+ if (existing) {
130
+ mergeRecentSessionMetadata(existing, candidate);
131
+ continue;
132
+ }
133
+ merged.push(candidate);
134
+ rememberRecentSessionIdentities(index, candidate);
135
+ }
136
+ return merged;
137
+ }
138
+
29
139
  function scoreSessionIdMatch(session, query) {
30
140
  const normalizedQuery = normalizeSearchValue(query);
31
141
  if (!normalizedQuery) return 0;
@@ -78,7 +188,69 @@
78
188
  }
79
189
 
80
190
  function stripWorktreePath(value) {
81
- return value ? String(value).replace(/\/\.claude\/worktrees\/[^/]+$/, '') : value;
191
+ return value ? String(value).replace(/\/\.(?:claude|walle)\/worktrees\/[^/]+$/, '') : value;
192
+ }
193
+
194
+ const AGENT_FILTER_LABELS = {
195
+ codex: 'Codex',
196
+ claude: 'Claude Code',
197
+ walle: 'Wall-E',
198
+ gemini: 'Gemini CLI',
199
+ opencode: 'OpenCode',
200
+ 'claude-desktop': 'Claude Desktop',
201
+ shell: 'Shell / Other',
202
+ };
203
+
204
+ const AGENT_FILTER_ORDER = ['codex', 'claude', 'walle', 'gemini', 'opencode', 'claude-desktop', 'shell'];
205
+
206
+ function normalizeRecentAgentType(value) {
207
+ if (!value) return '';
208
+ const v = String(value).trim().toLowerCase().replace(/[_\s]+/g, '-');
209
+ if (!v) return '';
210
+ if (AGENT_FILTER_LABELS[v]) return v;
211
+ if (v === 'claude-code' || v === 'claude-cli') return 'claude';
212
+ if (v === 'anthropic') return 'claude';
213
+ if (v === 'claude-desktop-session' || v === 'desktop') return 'claude-desktop';
214
+ if (v === 'wall-e' || v === 'walle-session') return 'walle';
215
+ if (v === 'gemini-cli') return 'gemini';
216
+ if (v === 'open-code' || v === 'opencode-cli') return 'opencode';
217
+ if (v === 'terminal' || v === 'unknown' || v === 'other') return 'shell';
218
+ return '';
219
+ }
220
+
221
+ function detectRecentAgentTypeFromText(value) {
222
+ if (!value) return '';
223
+ const v = String(value).toLowerCase();
224
+ if (v.includes('opencode') || v.includes('open-code')) return 'opencode';
225
+ if (v.includes('wall-e') || v.includes('walle')) return 'walle';
226
+ if (v.includes('codex')) return 'codex';
227
+ if (v.includes('gemini')) return 'gemini';
228
+ if (v.includes('claude')) return 'claude';
229
+ return '';
230
+ }
231
+
232
+ function getRecentSessionAgentType(session) {
233
+ const s = session || {};
234
+ for (const candidate of [s.agent, s.agentType, s.provider]) {
235
+ const explicit = normalizeRecentAgentType(candidate);
236
+ if (explicit) return explicit;
237
+ }
238
+ if (s.meta && (s.meta.type === 'walle' || s.meta.agentType === 'walle')) return 'walle';
239
+ if (s.type === 'walle') return 'walle';
240
+
241
+ const detected = detectRecentAgentTypeFromText(s.cmd || (s.meta && s.meta.cmd) || s.launchCommand || '');
242
+ return detected || 'shell';
243
+ }
244
+
245
+ function recentAgentFilterLabel(agentType) {
246
+ const normalized = normalizeRecentAgentType(agentType) || 'shell';
247
+ return AGENT_FILTER_LABELS[normalized] || AGENT_FILTER_LABELS.shell;
248
+ }
249
+
250
+ function recentAgentFilterPriority(agentType) {
251
+ const normalized = normalizeRecentAgentType(agentType) || 'shell';
252
+ const idx = AGENT_FILTER_ORDER.indexOf(normalized);
253
+ return idx >= 0 ? idx : AGENT_FILTER_ORDER.length;
82
254
  }
83
255
 
84
256
  function sessionPassesRecentSidebarFilters(session, filters) {
@@ -93,7 +265,8 @@
93
265
  const project = stripWorktreePath(f.project || '');
94
266
  if (project && stripWorktreePath(s.project || s.cwd || '') !== project) return false;
95
267
 
96
- if (f.model && s.model !== f.model) return false;
268
+ const agent = normalizeRecentAgentType(f.agent || '');
269
+ if (agent && getRecentSessionAgentType(s) !== agent) return false;
97
270
  return true;
98
271
  }
99
272
 
@@ -103,9 +276,18 @@
103
276
 
104
277
  return {
105
278
  buildIdMatchResults,
279
+ createSessionIdentityIndex,
280
+ dedupeSessionCandidates,
106
281
  filterRecentSessionsForSidebar,
282
+ findSessionIdentityMatch,
283
+ mergeRecentSessionMetadata,
284
+ mergeRecentSessionCandidates,
285
+ getRecentSessionAgentType,
107
286
  getSearchableSessionIds,
108
287
  normalizeSearchValue,
288
+ normalizeRecentAgentType,
289
+ recentAgentFilterLabel,
290
+ recentAgentFilterPriority,
109
291
  scoreSessionIdMatch,
110
292
  sessionPassesRecentSidebarFilters,
111
293
  sessionMatchesRecentSearchQuery,