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
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
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 || '')
|
|
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
|
-
|
|
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,
|