create-walle 0.9.11 → 0.9.13
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 +3 -3
- package/package.json +2 -2
- package/template/bin/dev.sh +7 -1
- package/template/bin/setup.js +53 -9
- package/template/bin/sync-images.js +53 -0
- package/template/builder-journal.md +17 -0
- package/template/claude-task-manager/api-prompts.js +98 -13
- package/template/claude-task-manager/api-reviews.js +82 -5
- package/template/claude-task-manager/db.js +32 -5
- package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
- package/template/claude-task-manager/lib/session-capture.js +421 -0
- package/template/claude-task-manager/lib/session-history.js +135 -15
- package/template/claude-task-manager/lib/session-jobs.js +10 -5
- package/template/claude-task-manager/lib/session-stream.js +87 -19
- package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
- package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
- package/template/claude-task-manager/lib/walle-session-context.js +61 -0
- package/template/claude-task-manager/lib/walle-transcript.js +176 -0
- package/template/claude-task-manager/public/css/setup.css +35 -8
- package/template/claude-task-manager/public/css/walle-session.css +56 -0
- package/template/claude-task-manager/public/css/walle.css +120 -0
- package/template/claude-task-manager/public/index.html +814 -181
- package/template/claude-task-manager/public/js/message-renderer.js +148 -19
- package/template/claude-task-manager/public/js/reviews.js +120 -62
- package/template/claude-task-manager/public/js/setup.js +75 -31
- package/template/claude-task-manager/public/js/stream-view.js +115 -55
- package/template/claude-task-manager/public/js/walle-session.js +84 -2
- package/template/claude-task-manager/public/js/walle.js +308 -54
- package/template/claude-task-manager/server.js +1092 -146
- package/template/claude-task-manager/session-integrity.js +181 -54
- package/template/claude-task-manager/session-utils.js +123 -41
- package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
- package/template/package.json +1 -1
- package/template/wall-e/adapters/ctm.js +39 -18
- package/template/wall-e/agent-runners/contract.js +17 -0
- package/template/wall-e/agent-runners/index.js +22 -0
- package/template/wall-e/agent-runtime/harness.js +212 -0
- package/template/wall-e/agent-runtime/index.js +8 -0
- package/template/wall-e/agent-runtime/registry.js +67 -0
- package/template/wall-e/agent-runtime/session-store.js +179 -0
- package/template/wall-e/agent-runtime/spawn.js +208 -0
- package/template/wall-e/api-walle.js +174 -7
- package/template/wall-e/brain.js +266 -28
- package/template/wall-e/channels/policy.js +88 -0
- package/template/wall-e/channels/registry.js +15 -1
- package/template/wall-e/channels/reply-dispatcher.js +70 -0
- package/template/wall-e/channels/session-bindings.js +51 -0
- package/template/wall-e/chat/code-review-context.js +29 -0
- package/template/wall-e/chat.js +188 -42
- package/template/wall-e/coding/acp-adapter.js +188 -0
- package/template/wall-e/coding/agent-catalog.js +129 -0
- package/template/wall-e/coding/compaction-service.js +247 -0
- package/template/wall-e/coding/execution-trace.js +3 -0
- package/template/wall-e/coding/instruction-service.js +224 -0
- package/template/wall-e/coding/model-message.js +67 -0
- package/template/wall-e/coding/permission-rules-store.js +111 -0
- package/template/wall-e/coding/permission-service.js +266 -0
- package/template/wall-e/coding/prompt-bundle.js +67 -0
- package/template/wall-e/coding/prompt-runtime.js +243 -0
- package/template/wall-e/coding/provider-transform.js +188 -0
- package/template/wall-e/coding/runtime-mode.js +132 -0
- package/template/wall-e/coding/snapshot-service.js +155 -0
- package/template/wall-e/coding/stream-processor.js +268 -0
- package/template/wall-e/coding/task-tool.js +255 -0
- package/template/wall-e/coding/tool-registry.js +361 -0
- package/template/wall-e/coding/transcript-writer.js +143 -0
- package/template/wall-e/coding/workspace-replay.js +324 -0
- package/template/wall-e/coding-context.js +4 -22
- package/template/wall-e/coding-orchestrator.js +307 -18
- package/template/wall-e/coding-prompts.js +44 -3
- package/template/wall-e/context/context-builder.js +43 -1
- package/template/wall-e/context/topic-matcher.js +1 -1
- package/template/wall-e/eval/agent-runner.js +59 -13
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
- package/template/wall-e/eval/benchmarks.js +100 -16
- package/template/wall-e/eval/eval-orchestrator.js +218 -8
- package/template/wall-e/eval/harvester.js +62 -5
- package/template/wall-e/eval/head-to-head.js +23 -2
- package/template/wall-e/eval/humaneval-adapter.js +30 -5
- package/template/wall-e/eval/livecodebench-adapter.js +29 -5
- package/template/wall-e/eval/manifest.js +186 -0
- package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
- package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
- package/template/wall-e/eval/session-transcripts.js +57 -4
- package/template/wall-e/eval/swebench-adapter.js +109 -3
- package/template/wall-e/evaluation/agent-router.js +53 -1
- package/template/wall-e/evaluation/coding-quorum.js +48 -1
- package/template/wall-e/evaluation/router.js +4 -2
- package/template/wall-e/evaluation/tier-selector.js +11 -1
- package/template/wall-e/extraction/contradiction.js +2 -2
- package/template/wall-e/extraction/indexer.js +2 -1
- package/template/wall-e/extraction/knowledge-extractor.js +2 -2
- package/template/wall-e/hooks/cli.js +92 -0
- package/template/wall-e/hooks/discovery.js +119 -0
- package/template/wall-e/hooks/index.js +7 -0
- package/template/wall-e/hooks/manifest.js +55 -0
- package/template/wall-e/hooks/runtime.js +84 -0
- package/template/wall-e/hooks/session-memory.js +225 -0
- package/template/wall-e/http/auth.js +6 -2
- package/template/wall-e/http/chat-api.js +54 -8
- package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
- package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
- package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
- package/template/wall-e/listening/calendar.js +3 -1
- package/template/wall-e/llm/client.js +64 -10
- package/template/wall-e/llm/google.js +39 -5
- package/template/wall-e/llm/ollama.js +1 -1
- package/template/wall-e/llm/ollama.plugin.json +1 -1
- package/template/wall-e/llm/provider-availability.js +10 -0
- package/template/wall-e/llm/provider-error.js +269 -0
- package/template/wall-e/llm/tool-adapter.js +48 -12
- package/template/wall-e/loops/boot.js +2 -1
- package/template/wall-e/loops/initiative.js +2 -2
- package/template/wall-e/loops/tasks.js +8 -47
- package/template/wall-e/loops/workspace-prompts.js +20 -0
- package/template/wall-e/mcp-server.js +442 -1
- package/template/wall-e/memory/session-ingest-service.js +159 -0
- package/template/wall-e/memory/source-indexer.js +289 -0
- package/template/wall-e/plugins/discovery.js +83 -0
- package/template/wall-e/plugins/manifest-loader.js +50 -10
- package/template/wall-e/plugins/manifest-schema.js +69 -0
- package/template/wall-e/plugins/model-catalog.js +55 -0
- package/template/wall-e/prompts/coding/base.txt +2 -0
- package/template/wall-e/prompts/coding/deepseek.txt +1 -0
- package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
- package/template/wall-e/prompts/coding/plan.txt +1 -0
- package/template/wall-e/runtime/execution-trace.js +220 -0
- package/template/wall-e/security/audit.js +266 -0
- package/template/wall-e/security/ssrf.js +236 -0
- package/template/wall-e/session-files.js +303 -0
- package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
- package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
- package/template/wall-e/skills/internal-skill-registry.js +2 -2
- package/template/wall-e/skills/script-skill-runner.js +143 -0
- package/template/wall-e/skills/skill-executor.js +5 -6
- package/template/wall-e/skills/skill-fallback.js +3 -1
- package/template/wall-e/skills/skill-harness-registry.js +7 -8
- package/template/wall-e/skills/skill-planner.js +52 -4
- package/template/wall-e/skills/slack-ingest.js +11 -3
- package/template/wall-e/sources/base.js +90 -0
- package/template/wall-e/sources/builtin.js +33 -0
- package/template/wall-e/sources/claude-code-jsonl.js +78 -0
- package/template/wall-e/sources/codex-jsonl.js +125 -0
- package/template/wall-e/sources/coding-session-utils.js +117 -0
- package/template/wall-e/sources/contract-suite.js +59 -0
- package/template/wall-e/sources/gemini-jsonl.js +85 -0
- package/template/wall-e/sources/index.js +9 -0
- package/template/wall-e/sources/jsonl-utils.js +181 -0
- package/template/wall-e/sources/record-types.js +252 -0
- package/template/wall-e/sources/registry.js +92 -0
- package/template/wall-e/sources/transforms.js +100 -0
- package/template/wall-e/sources/walle-jsonl.js +108 -0
- package/template/wall-e/tools/coding-middleware.js +31 -1
- package/template/wall-e/tools/file-tracker.js +25 -1
- package/template/wall-e/tools/local-tools.js +75 -47
- package/template/wall-e/tools/session-sharing.js +68 -1
- package/template/wall-e/tools/shell-analyzer.js +1 -1
- package/template/wall-e/tools/shell-policy.js +47 -0
- package/template/wall-e/tools/snapshot.js +42 -0
- package/template/wall-e/training/harvester.js +62 -5
- package/template/wall-e/utils/repair.js +253 -1
- package/template/website/index.html +3 -3
- package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +0 -18
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* - renderGroup(g) — render one group from groupMessages
|
|
21
21
|
* - _buildCollapsible(label, color, preview, bodyEl) — Conversation tab DOM helper
|
|
22
22
|
* - renderConversationEvent(evt) — Conversation row DOM element
|
|
23
|
+
* - refreshConversationActivityGroup(group) — recompute grouped tool UI
|
|
23
24
|
*
|
|
24
25
|
* Depends on global escHtml (defined in index.html). Since the renderer
|
|
25
26
|
* functions are only invoked AFTER the page's body scripts run, escHtml
|
|
@@ -28,10 +29,26 @@
|
|
|
28
29
|
(function () {
|
|
29
30
|
const MR = {};
|
|
30
31
|
|
|
32
|
+
function _imageApiUrlForLocalPath(src) {
|
|
33
|
+
const value = String(src || '').trim().replace(/^<|>$/g, '');
|
|
34
|
+
const match = value.match(/(?:^|[\\/])images[\\/]+([^\\/?:#]+\.(?:png|jpe?g|gif|webp|svg))(?:[?#].*)?$/i);
|
|
35
|
+
if (!match) return null;
|
|
36
|
+
return '/api/images/file/' + encodeURIComponent(match[1]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _normalizeMarkdownImageSources(text) {
|
|
40
|
+
return String(text || '').replace(/!\[([^\]]*)\]\(([^)\s]+|<[^>]+>)\)/g, (all, alt, src) => {
|
|
41
|
+
const apiUrl = _imageApiUrlForLocalPath(src);
|
|
42
|
+
if (!apiUrl) return all;
|
|
43
|
+
return ``;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
// ------------------------------------------------------------------
|
|
32
48
|
// Markdown + tool-badge formatting (was index.html formatMsgText).
|
|
33
49
|
// ------------------------------------------------------------------
|
|
34
50
|
MR.formatMsgText = function (text) {
|
|
51
|
+
text = _normalizeMarkdownImageSources(text);
|
|
35
52
|
// Stash tool badges with unique placeholders before markdown parsing
|
|
36
53
|
const badges = [];
|
|
37
54
|
text = text.replace(/\[Tool: ([^\]]+)\]/g, (_, name) => {
|
|
@@ -179,6 +196,34 @@
|
|
|
179
196
|
return 'normal';
|
|
180
197
|
};
|
|
181
198
|
|
|
199
|
+
const _TOOL_TOKEN_RE = /\[Tool(?: result)?:\s*([^\]]+)\]/g;
|
|
200
|
+
|
|
201
|
+
function _toolNamesFromText(text) {
|
|
202
|
+
const names = [];
|
|
203
|
+
for (const m of String(text || '').matchAll(_TOOL_TOKEN_RE)) {
|
|
204
|
+
const name = (m[1] || '').trim();
|
|
205
|
+
if (name) names.push(name);
|
|
206
|
+
}
|
|
207
|
+
return names;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function _summarizeNames(names, max = 4) {
|
|
211
|
+
const counts = new Map();
|
|
212
|
+
for (const raw of names || []) {
|
|
213
|
+
const name = String(raw || '').trim();
|
|
214
|
+
if (!name) continue;
|
|
215
|
+
counts.set(name, (counts.get(name) || 0) + 1);
|
|
216
|
+
}
|
|
217
|
+
const parts = Array.from(counts.entries()).map(([name, count]) => count > 1 ? `${name} x${count}` : name);
|
|
218
|
+
if (parts.length <= max) return parts.join(' · ');
|
|
219
|
+
return parts.slice(0, max).join(' · ') + ` · +${parts.length - max}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
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
|
+
}
|
|
226
|
+
|
|
182
227
|
// ------------------------------------------------------------------
|
|
183
228
|
// Review-page renderers — return HTML strings. The page batches many
|
|
184
229
|
// groups into one big string, sanitizes via DOMPurify, then sets
|
|
@@ -265,15 +310,17 @@
|
|
|
265
310
|
MR.renderSelfThoughtMsg = function (m, i, stripped) {
|
|
266
311
|
const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
|
267
312
|
const textHtml = MR.formatMsgText(m.text);
|
|
268
|
-
const
|
|
269
|
-
|
|
313
|
+
const toolSummary = _summarizeNames(_toolNamesFromText(m.text));
|
|
314
|
+
const preview = toolSummary || _previewFromText(stripped);
|
|
315
|
+
const groupClass = toolSummary ? 'thought-group tool-activity-group' : 'thought-group';
|
|
316
|
+
return '<div class="' + groupClass + '" onclick="this.classList.toggle(\'expanded\')">'
|
|
270
317
|
+ '<div class="thought-group-header">'
|
|
271
318
|
+ '<span class="thought-group-label"><span class="thought-chevron">▶</span>Claude</span>'
|
|
272
319
|
+ '<span class="thought-group-preview">' + escHtml(preview) + '</span>'
|
|
273
320
|
+ '<span class="thought-group-time">' + escHtml(time) + '</span>'
|
|
274
321
|
+ '</div>'
|
|
275
322
|
+ '<div class="thought-group-items">'
|
|
276
|
-
+ '<div class="review-msg assistant" data-msg-idx="' + i + '">'
|
|
323
|
+
+ '<div class="review-msg assistant tool-activity-item" data-msg-idx="' + i + '">'
|
|
277
324
|
+ '<div class="msg-text">' + textHtml + '</div></div>'
|
|
278
325
|
+ '</div></div>';
|
|
279
326
|
};
|
|
@@ -281,7 +328,9 @@
|
|
|
281
328
|
MR.renderSelfThoughtItem = function (m, i, stripped) {
|
|
282
329
|
const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
|
283
330
|
const textHtml = MR.formatMsgText(m.text);
|
|
284
|
-
|
|
331
|
+
const toolNames = _toolNamesFromText(m.text);
|
|
332
|
+
const dataTools = toolNames.length ? ' data-tool-names="' + escHtml(toolNames.join(',')) + '"' : '';
|
|
333
|
+
return '<div class="review-msg assistant tool-activity-item" data-msg-idx="' + i + '"' + dataTools + '>'
|
|
285
334
|
+ '<div class="msg-header">'
|
|
286
335
|
+ '<span class="msg-role">Claude</span>'
|
|
287
336
|
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
@@ -326,12 +375,15 @@
|
|
|
326
375
|
if (groupItems.length === 1) {
|
|
327
376
|
return MR.renderSelfThoughtMsg(groupItems[0].m, groupItems[0].i, groupItems[0].stripped);
|
|
328
377
|
}
|
|
378
|
+
const toolNames = groupItems.flatMap(gi => _toolNamesFromText(gi.m.text));
|
|
379
|
+
const toolSummary = _summarizeNames(toolNames);
|
|
329
380
|
const firstPreview = groupItems.map(gi => gi.stripped).find(s => s.length > 0) || '';
|
|
330
|
-
const preview =
|
|
381
|
+
const preview = toolSummary || _previewFromText(firstPreview, 80);
|
|
331
382
|
const firstTime = groupItems[0].m.timestamp ? new Date(groupItems[0].m.timestamp).toLocaleString() : '';
|
|
332
383
|
const lastTime = groupItems[groupItems.length - 1].m.timestamp ? new Date(groupItems[groupItems.length - 1].m.timestamp).toLocaleString() : '';
|
|
333
384
|
const timeLabel = firstTime === lastTime ? firstTime : firstTime + ' – ' + lastTime;
|
|
334
|
-
|
|
385
|
+
const groupClass = toolSummary ? 'thought-group tool-activity-group' : 'thought-group';
|
|
386
|
+
return '<div class="' + groupClass + '" onclick="this.classList.toggle(\'expanded\')">'
|
|
335
387
|
+ '<div class="thought-group-header">'
|
|
336
388
|
+ '<span class="thought-group-label"><span class="thought-chevron">▶</span>Claude'
|
|
337
389
|
+ '<span class="thought-group-count">— ' + groupItems.length + ' steps</span></span>'
|
|
@@ -392,6 +444,41 @@
|
|
|
392
444
|
return wrap;
|
|
393
445
|
};
|
|
394
446
|
|
|
447
|
+
MR.refreshConversationActivityGroup = function (group) {
|
|
448
|
+
if (!group || !group.querySelector) return;
|
|
449
|
+
const items = group.querySelector('.thought-group-items');
|
|
450
|
+
if (!items) return;
|
|
451
|
+
const rows = Array.from(items.children || []);
|
|
452
|
+
const countEl = group.querySelector('.thought-group-count');
|
|
453
|
+
if (countEl) {
|
|
454
|
+
const stepCount = rows.length;
|
|
455
|
+
countEl.textContent = '— ' + stepCount + ' step' + (stepCount === 1 ? '' : 's');
|
|
456
|
+
}
|
|
457
|
+
const toolNames = [];
|
|
458
|
+
const previews = [];
|
|
459
|
+
for (const row of rows) {
|
|
460
|
+
const rawTools = row.dataset?.toolNames || '';
|
|
461
|
+
if (rawTools) {
|
|
462
|
+
for (const name of rawTools.split(',')) {
|
|
463
|
+
const clean = name.trim();
|
|
464
|
+
if (clean) toolNames.push(clean);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const p = row.dataset?.activityPreview || '';
|
|
468
|
+
if (p) previews.push(p);
|
|
469
|
+
}
|
|
470
|
+
const previewEl = group.querySelector('.thought-group-preview');
|
|
471
|
+
if (previewEl) {
|
|
472
|
+
previewEl.textContent = _summarizeNames(toolNames) || previews[0] || '';
|
|
473
|
+
}
|
|
474
|
+
const timeEl = group.querySelector('.thought-group-time');
|
|
475
|
+
if (timeEl) {
|
|
476
|
+
const f = group.dataset.firstTime || rows[0]?.dataset?.time || '';
|
|
477
|
+
const l = group.dataset.lastTime || rows[rows.length - 1]?.dataset?.time || '';
|
|
478
|
+
timeEl.textContent = (f && l && f !== l) ? f + ' – ' + l : (l || f);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
395
482
|
// Phase 2: Conversation rows now share the `.review-msg` CSS classes with
|
|
396
483
|
// Review, run text through `MR.formatMsgText` for markdown + tool badges,
|
|
397
484
|
// and get Show-more affordances + per-message timestamps. Conversation
|
|
@@ -602,8 +689,7 @@
|
|
|
602
689
|
}
|
|
603
690
|
if (isSelfThought) {
|
|
604
691
|
const group = document.createElement('div');
|
|
605
|
-
group.className = 'thought-group conv-tool-group';
|
|
606
|
-
if (evt.data?.parentUuid) group.dataset.parentUuid = evt.data.parentUuid;
|
|
692
|
+
group.className = 'thought-group conv-tool-group tool-activity-group';
|
|
607
693
|
group.dataset.firstTime = time;
|
|
608
694
|
group.dataset.lastTime = time;
|
|
609
695
|
group.addEventListener('click', () => group.classList.toggle('expanded'));
|
|
@@ -642,7 +728,11 @@
|
|
|
642
728
|
const items = document.createElement('div');
|
|
643
729
|
items.className = 'thought-group-items';
|
|
644
730
|
const itemRow = document.createElement('div');
|
|
645
|
-
itemRow.className = 'review-msg assistant';
|
|
731
|
+
itemRow.className = 'review-msg assistant tool-activity-item';
|
|
732
|
+
if (evt.data?.parentUuid) itemRow.dataset.parentUuid = evt.data.parentUuid;
|
|
733
|
+
if (time) itemRow.dataset.time = time;
|
|
734
|
+
if (toolNames.length) itemRow.dataset.toolNames = toolNames.join(',');
|
|
735
|
+
if (stripped) itemRow.dataset.activityPreview = _previewFromText(stripped, 90);
|
|
646
736
|
const itemBody = document.createElement('div');
|
|
647
737
|
itemBody.className = 'msg-text';
|
|
648
738
|
if (stripped) {
|
|
@@ -665,6 +755,7 @@
|
|
|
665
755
|
items.appendChild(itemRow);
|
|
666
756
|
group.appendChild(header);
|
|
667
757
|
group.appendChild(items);
|
|
758
|
+
MR.refreshConversationActivityGroup(group);
|
|
668
759
|
return group;
|
|
669
760
|
}
|
|
670
761
|
// Mixed (text + maybe tools) — keep the existing per-row layout with
|
|
@@ -695,18 +786,56 @@
|
|
|
695
786
|
|
|
696
787
|
if (evType === 'tool_result') {
|
|
697
788
|
if (!text) return null;
|
|
698
|
-
const div = document.createElement('div');
|
|
699
|
-
// `.tool-only` lets the existing "Hide tool calls" toggle hide these
|
|
700
|
-
// when the user opts in — matches Review's classification.
|
|
701
|
-
div.className = 'review-msg tool-only';
|
|
702
|
-
if (evt.data?.parentUuid) div.dataset.parentUuid = evt.data.parentUuid;
|
|
703
|
-
const body = document.createElement('div');
|
|
704
|
-
body.style.cssText = 'white-space:pre-wrap;color:#bbb;max-height:300px;overflow-y:auto;padding:4px 0 4px 12px;font-size:12px';
|
|
705
|
-
body.textContent = text;
|
|
706
789
|
const firstLine = (text.split('\n').find(l => l.trim()) || '').trim();
|
|
707
790
|
const preview = firstLine.length > 100 ? firstLine.slice(0, 97) + '…' : firstLine;
|
|
708
|
-
|
|
709
|
-
|
|
791
|
+
const group = document.createElement('div');
|
|
792
|
+
group.className = 'thought-group conv-tool-group tool-activity-group tool-result-group';
|
|
793
|
+
group.dataset.firstTime = time;
|
|
794
|
+
group.dataset.lastTime = time;
|
|
795
|
+
group.addEventListener('click', () => group.classList.toggle('expanded'));
|
|
796
|
+
|
|
797
|
+
const header = document.createElement('div');
|
|
798
|
+
header.className = 'thought-group-header';
|
|
799
|
+
const label = document.createElement('span');
|
|
800
|
+
label.className = 'thought-group-label';
|
|
801
|
+
const chev = document.createElement('span');
|
|
802
|
+
chev.className = 'thought-chevron';
|
|
803
|
+
chev.textContent = '▶';
|
|
804
|
+
label.appendChild(chev);
|
|
805
|
+
const labelText = document.createElement('span');
|
|
806
|
+
labelText.textContent = 'Tools';
|
|
807
|
+
label.appendChild(labelText);
|
|
808
|
+
const count = document.createElement('span');
|
|
809
|
+
count.className = 'thought-group-count';
|
|
810
|
+
count.textContent = '— 1 step';
|
|
811
|
+
label.appendChild(count);
|
|
812
|
+
const previewEl = document.createElement('span');
|
|
813
|
+
previewEl.className = 'thought-group-preview';
|
|
814
|
+
previewEl.textContent = preview;
|
|
815
|
+
const tEl = document.createElement('span');
|
|
816
|
+
tEl.className = 'thought-group-time';
|
|
817
|
+
tEl.textContent = time;
|
|
818
|
+
header.appendChild(label);
|
|
819
|
+
header.appendChild(previewEl);
|
|
820
|
+
header.appendChild(tEl);
|
|
821
|
+
|
|
822
|
+
const items = document.createElement('div');
|
|
823
|
+
items.className = 'thought-group-items';
|
|
824
|
+
const itemRow = document.createElement('div');
|
|
825
|
+
itemRow.className = 'review-msg tool-only tool-activity-item tool-result-item';
|
|
826
|
+
if (evt.data?.parentUuid) itemRow.dataset.parentUuid = evt.data.parentUuid;
|
|
827
|
+
if (time) itemRow.dataset.time = time;
|
|
828
|
+
if (preview) itemRow.dataset.activityPreview = preview;
|
|
829
|
+
const itemBody = document.createElement('div');
|
|
830
|
+
itemBody.className = 'msg-text';
|
|
831
|
+
itemBody.style.cssText = 'white-space:pre-wrap;color:#bbb;max-height:300px;overflow-y:auto;font-size:12px';
|
|
832
|
+
itemBody.textContent = text;
|
|
833
|
+
itemRow.appendChild(itemBody);
|
|
834
|
+
items.appendChild(itemRow);
|
|
835
|
+
group.appendChild(header);
|
|
836
|
+
group.appendChild(items);
|
|
837
|
+
MR.refreshConversationActivityGroup(group);
|
|
838
|
+
return group;
|
|
710
839
|
}
|
|
711
840
|
|
|
712
841
|
if (evType === 'summary') {
|
|
@@ -18,17 +18,31 @@ let crState = {
|
|
|
18
18
|
diffMode: 'unified', // unified | split
|
|
19
19
|
selectStart: null, // Line selection start for range comments
|
|
20
20
|
fileChangeCount: 0, // For badge
|
|
21
|
+
_view: 'projects',
|
|
22
|
+
_openSeq: 0,
|
|
23
|
+
_diffSeq: 0,
|
|
24
|
+
_projectSeq: 0,
|
|
21
25
|
};
|
|
22
26
|
|
|
23
|
-
function api(path, method, body) {
|
|
27
|
+
async function api(path, method, body) {
|
|
24
28
|
const token = window._ctmState?.token || '';
|
|
25
29
|
const opts = { method: method || 'GET', headers: {} };
|
|
26
30
|
if (body) {
|
|
27
31
|
opts.headers['Content-Type'] = 'application/json';
|
|
28
32
|
opts.body = JSON.stringify(body);
|
|
29
33
|
}
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
const res = await fetch(`/api${path}${path.includes('?') ? '&' : '?'}token=${encodeURIComponent(token)}`, opts);
|
|
35
|
+
let data = null;
|
|
36
|
+
try { data = await res.json(); } catch (_) {}
|
|
37
|
+
if (!res.ok || (data && data.error)) {
|
|
38
|
+
throw new Error((data && data.error) || ('HTTP ' + res.status));
|
|
39
|
+
}
|
|
40
|
+
return data || {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeReviewSeverity(severity) {
|
|
44
|
+
severity = String(severity || 'comment').trim().toLowerCase();
|
|
45
|
+
return ['comment', 'suggestion', 'issue', 'nit', 'question'].includes(severity) ? severity : 'comment';
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
// --- Pinned projects ---
|
|
@@ -46,6 +60,9 @@ CR.loadPinnedProjects = function(prefs) {
|
|
|
46
60
|
|
|
47
61
|
// --- Show project list (default view when panel opens) ---
|
|
48
62
|
CR.showProjectList = async function() {
|
|
63
|
+
const seq = ++crState._projectSeq;
|
|
64
|
+
crState._view = 'projects';
|
|
65
|
+
crState.reviewId = null;
|
|
49
66
|
const header = document.getElementById('cr-header');
|
|
50
67
|
const tree = document.getElementById('cr-file-tree');
|
|
51
68
|
const area = document.getElementById('cr-diff-area');
|
|
@@ -58,6 +75,7 @@ CR.showProjectList = async function() {
|
|
|
58
75
|
|
|
59
76
|
try {
|
|
60
77
|
const data = await api('/reviews/tracked-projects');
|
|
78
|
+
if (seq !== crState._projectSeq || crState._view !== 'projects') return;
|
|
61
79
|
const projects = data.projects || [];
|
|
62
80
|
|
|
63
81
|
if (projects.length === 0) {
|
|
@@ -137,6 +155,7 @@ CR.showProjectList = async function() {
|
|
|
137
155
|
html += '</div>';
|
|
138
156
|
if (area) area.innerHTML = html;
|
|
139
157
|
} catch (e) {
|
|
158
|
+
if (seq !== crState._projectSeq || crState._view !== 'projects') return;
|
|
140
159
|
if (area) area.innerHTML = `<div class="cr-diff-empty" style="color:var(--red)">Failed to load projects: ${escHtml(e.message)}</div>`;
|
|
141
160
|
}
|
|
142
161
|
};
|
|
@@ -184,6 +203,8 @@ CR.openSessionForProject = function() {
|
|
|
184
203
|
|
|
185
204
|
// --- Initialize review for a session ---
|
|
186
205
|
CR.openReview = async function(sessionId, projectPath) {
|
|
206
|
+
const seq = ++crState._openSeq;
|
|
207
|
+
crState._view = 'review';
|
|
187
208
|
crState.sessionId = sessionId;
|
|
188
209
|
crState.projectPath = projectPath;
|
|
189
210
|
crState.baseRef = '';
|
|
@@ -210,34 +231,45 @@ CR.openReview = async function(sessionId, projectPath) {
|
|
|
210
231
|
renderHeader();
|
|
211
232
|
renderLoading();
|
|
212
233
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
try {
|
|
235
|
+
// Load branch, commits, and rich commit log in parallel
|
|
236
|
+
const [branchData, commitsData, logData] = await Promise.all([
|
|
237
|
+
api(`/reviews/branch?project=${encodeURIComponent(projectPath)}`),
|
|
238
|
+
api(`/reviews/commits?project=${encodeURIComponent(projectPath)}`),
|
|
239
|
+
api(`/reviews/commit-log?project=${encodeURIComponent(projectPath)}&count=20`),
|
|
240
|
+
]);
|
|
241
|
+
if (seq !== crState._openSeq || crState._view !== 'review') return;
|
|
242
|
+
crState.branch = branchData.branch || '';
|
|
243
|
+
crState._commitLog = logData.commits || [];
|
|
244
|
+
renderHeader(commitsData.commits || []);
|
|
245
|
+
|
|
246
|
+
// Create review record in DB
|
|
247
|
+
const { id } = await api('/reviews', 'POST', {
|
|
248
|
+
session_id: sessionId,
|
|
249
|
+
project_path: projectPath,
|
|
250
|
+
base_ref: '',
|
|
251
|
+
});
|
|
252
|
+
if (seq !== crState._openSeq || crState._view !== 'review') return;
|
|
253
|
+
crState.reviewId = id;
|
|
230
254
|
|
|
231
|
-
|
|
232
|
-
|
|
255
|
+
// Load diff
|
|
256
|
+
await loadDiff();
|
|
257
|
+
} catch (e) {
|
|
258
|
+
if (seq !== crState._openSeq || crState._view !== 'review') return;
|
|
259
|
+
const area = document.getElementById('cr-diff-area');
|
|
260
|
+
if (area) area.innerHTML = `<div class="cr-diff-empty" style="color:var(--red)">Failed to open review: ${escHtml(e.message)}</div>`;
|
|
261
|
+
if (typeof window.toast === 'function') window.toast('Failed to open review: ' + e.message, { type: 'error' });
|
|
262
|
+
}
|
|
233
263
|
};
|
|
234
264
|
|
|
235
265
|
async function loadDiff() {
|
|
266
|
+
const seq = ++crState._diffSeq;
|
|
236
267
|
renderLoading();
|
|
237
268
|
try {
|
|
238
269
|
const data = await api(
|
|
239
270
|
`/reviews/diff?project=${encodeURIComponent(crState.projectPath)}&base=${encodeURIComponent(crState.baseRef)}`
|
|
240
271
|
);
|
|
272
|
+
if (seq !== crState._diffSeq || crState._view !== 'review') return;
|
|
241
273
|
crState.files = data.files || [];
|
|
242
274
|
|
|
243
275
|
// Update file count on review record
|
|
@@ -253,6 +285,7 @@ async function loadDiff() {
|
|
|
253
285
|
renderDiff();
|
|
254
286
|
}
|
|
255
287
|
} catch (e) {
|
|
288
|
+
if (seq !== crState._diffSeq || crState._view !== 'review') return;
|
|
256
289
|
const area = document.getElementById('cr-diff-area');
|
|
257
290
|
if (area) area.innerHTML = `<div class="cr-diff-empty" style="color:var(--red)">Failed to load diff: ${escHtml(e.message)}</div>`;
|
|
258
291
|
}
|
|
@@ -282,8 +315,8 @@ function renderHeader(commits) {
|
|
|
282
315
|
// logMap keyed by both full SHA and short SHA for flexible lookup
|
|
283
316
|
const logMap = {};
|
|
284
317
|
for (const lc of (crState._commitLog || [])) { logMap[lc.sha] = lc; logMap[lc.shortSha] = lc; }
|
|
285
|
-
let baseOptions =
|
|
286
|
-
baseOptions +=
|
|
318
|
+
let baseOptions = `<option value=""${crState.baseRef === '' ? ' selected' : ''}>Working tree (unstaged)</option>`;
|
|
319
|
+
baseOptions += `<option value="--staged"${crState.baseRef === '--staged' ? ' selected' : ''}>Staged changes</option>`;
|
|
287
320
|
if (commits) {
|
|
288
321
|
for (const c of commits) {
|
|
289
322
|
const sel = crState.baseRef === c.sha ? ' selected' : '';
|
|
@@ -345,10 +378,13 @@ function renderLoading() {
|
|
|
345
378
|
function renderNoChanges() {
|
|
346
379
|
const area = document.getElementById('cr-diff-area');
|
|
347
380
|
const tree = document.getElementById('cr-file-tree');
|
|
381
|
+
const message = crState.baseRef === '--staged'
|
|
382
|
+
? 'No staged changes'
|
|
383
|
+
: (crState.baseRef ? `Commit ${crState.baseRef.slice(0,7)} has no file changes` : 'Working tree is clean against HEAD');
|
|
348
384
|
if (area) area.innerHTML = `<div class="cr-no-changes">
|
|
349
385
|
<div class="cr-no-changes-icon">✔</div>
|
|
350
386
|
<div class="cr-no-changes-text">No changes detected</div>
|
|
351
|
-
<div style="font-size:11px;color:var(--fg-dim)">${
|
|
387
|
+
<div style="font-size:11px;color:var(--fg-dim)">${escHtml(message)}</div>
|
|
352
388
|
</div>`;
|
|
353
389
|
if (tree) tree.innerHTML = '';
|
|
354
390
|
renderFooter();
|
|
@@ -595,16 +631,16 @@ function _setupScrollSpy(area) {
|
|
|
595
631
|
function renderCommentThread(comments, filePath, lineNum, side) {
|
|
596
632
|
let html = `<tr class="cr-comment-row"><td colspan="4"><div class="cr-comment-thread">`;
|
|
597
633
|
for (const c of comments) {
|
|
598
|
-
const severityCls = c.severity
|
|
634
|
+
const severityCls = normalizeReviewSeverity(c.severity);
|
|
599
635
|
const lineRange = c.line_end && c.line_end !== c.line_start
|
|
600
636
|
? `Lines ${c.line_start}-${c.line_end}`
|
|
601
637
|
: `Line ${c.line_start}`;
|
|
602
638
|
html += `<div class="cr-comment-item" data-comment-id="${c.id}">
|
|
603
639
|
<div class="cr-comment-header">
|
|
604
640
|
<span class="cr-comment-author">${c.ai_generated ? 'AI' : 'You'}</span>
|
|
605
|
-
<span class="cr-comment-severity ${severityCls}">${severityCls}</span>
|
|
641
|
+
<span class="cr-comment-severity ${severityCls}">${escHtml(severityCls)}</span>
|
|
606
642
|
${c.ai_generated ? '<span class="cr-comment-ai">AI suggestion</span>' : ''}
|
|
607
|
-
<span style="font-size:10px;color:var(--fg-dim);margin-left:auto">${lineRange}</span>
|
|
643
|
+
<span style="font-size:10px;color:var(--fg-dim);margin-left:auto">${escHtml(lineRange)}</span>
|
|
608
644
|
</div>
|
|
609
645
|
<div class="cr-comment-body">${escHtml(c.body)}</div>
|
|
610
646
|
<div class="cr-comment-actions">
|
|
@@ -623,9 +659,9 @@ function renderFooter() {
|
|
|
623
659
|
const footer = document.getElementById('cr-footer');
|
|
624
660
|
if (!footer) return;
|
|
625
661
|
const openComments = crState.comments.filter(c => c.status === 'open');
|
|
626
|
-
const issues = openComments.filter(c => c.severity === 'issue').length;
|
|
627
|
-
const suggestions = openComments.filter(c => c.severity === 'suggestion').length;
|
|
628
|
-
const nits = openComments.filter(c => c.severity === 'nit').length;
|
|
662
|
+
const issues = openComments.filter(c => normalizeReviewSeverity(c.severity) === 'issue').length;
|
|
663
|
+
const suggestions = openComments.filter(c => normalizeReviewSeverity(c.severity) === 'suggestion').length;
|
|
664
|
+
const nits = openComments.filter(c => normalizeReviewSeverity(c.severity) === 'nit').length;
|
|
629
665
|
const other = openComments.length - issues - suggestions - nits;
|
|
630
666
|
const fileCount = crState.files.length;
|
|
631
667
|
|
|
@@ -779,24 +815,29 @@ CR.saveComment = async function(filePath, lineStart, lineEnd, side) {
|
|
|
779
815
|
if (!input || !input.value.trim()) return;
|
|
780
816
|
|
|
781
817
|
const body = input.value.trim();
|
|
782
|
-
const severity = severitySelect?.value
|
|
818
|
+
const severity = normalizeReviewSeverity(severitySelect?.value);
|
|
783
819
|
|
|
784
820
|
if (!crState.reviewId) return;
|
|
785
821
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
822
|
+
try {
|
|
823
|
+
const { id } = await api(`/reviews/${crState.reviewId}/comments`, 'POST', {
|
|
824
|
+
file_path: filePath,
|
|
825
|
+
line_start: lineStart,
|
|
826
|
+
line_end: lineEnd,
|
|
827
|
+
side: side === 'old' ? 'old' : 'new',
|
|
828
|
+
body: body,
|
|
829
|
+
severity: severity,
|
|
830
|
+
});
|
|
794
831
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
832
|
+
crState.comments.push({
|
|
833
|
+
id, review_id: crState.reviewId, file_path: filePath,
|
|
834
|
+
line_start: lineStart, line_end: lineEnd, side: side === 'old' ? 'old' : 'new',
|
|
835
|
+
body, severity, status: 'open', ai_generated: 0,
|
|
836
|
+
});
|
|
837
|
+
} catch (e) {
|
|
838
|
+
if (typeof window.toast === 'function') window.toast('Failed to save comment: ' + e.message, { type: 'error' });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
800
841
|
|
|
801
842
|
// Remove form and re-render
|
|
802
843
|
renderDiff();
|
|
@@ -804,26 +845,38 @@ CR.saveComment = async function(filePath, lineStart, lineEnd, side) {
|
|
|
804
845
|
};
|
|
805
846
|
|
|
806
847
|
CR.resolveComment = async function(commentId) {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
848
|
+
try {
|
|
849
|
+
await api(`/review-comments/${commentId}`, 'PUT', { status: 'resolved' });
|
|
850
|
+
const c = crState.comments.find(c => c.id === commentId);
|
|
851
|
+
if (c) c.status = 'resolved';
|
|
852
|
+
renderDiff();
|
|
853
|
+
renderFileTree();
|
|
854
|
+
} catch (e) {
|
|
855
|
+
if (typeof window.toast === 'function') window.toast('Failed to resolve comment: ' + e.message, { type: 'error' });
|
|
856
|
+
}
|
|
812
857
|
};
|
|
813
858
|
|
|
814
859
|
CR.reopenComment = async function(commentId) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
860
|
+
try {
|
|
861
|
+
await api(`/review-comments/${commentId}`, 'PUT', { status: 'open' });
|
|
862
|
+
const c = crState.comments.find(c => c.id === commentId);
|
|
863
|
+
if (c) c.status = 'open';
|
|
864
|
+
renderDiff();
|
|
865
|
+
renderFileTree();
|
|
866
|
+
} catch (e) {
|
|
867
|
+
if (typeof window.toast === 'function') window.toast('Failed to reopen comment: ' + e.message, { type: 'error' });
|
|
868
|
+
}
|
|
820
869
|
};
|
|
821
870
|
|
|
822
871
|
CR.deleteComment = async function(commentId) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
872
|
+
try {
|
|
873
|
+
await api(`/review-comments/${commentId}`, 'DELETE');
|
|
874
|
+
crState.comments = crState.comments.filter(c => c.id !== commentId);
|
|
875
|
+
renderDiff();
|
|
876
|
+
renderFileTree();
|
|
877
|
+
} catch (e) {
|
|
878
|
+
if (typeof window.toast === 'function') window.toast('Failed to delete comment: ' + e.message, { type: 'error' });
|
|
879
|
+
}
|
|
827
880
|
};
|
|
828
881
|
|
|
829
882
|
// --- Submit review: compose prompt and send to session ---
|
|
@@ -877,9 +930,14 @@ async function _composeReviewPrompt() {
|
|
|
877
930
|
if (typeof window.toast === 'function') window.toast('No open comments to submit', { type: 'warning' });
|
|
878
931
|
return null;
|
|
879
932
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
933
|
+
try {
|
|
934
|
+
const { prompt } = await api(`/reviews/${crState.reviewId}/compose`);
|
|
935
|
+
await api(`/reviews/${crState.reviewId}`, 'PUT', { status: 'submitted' });
|
|
936
|
+
return prompt;
|
|
937
|
+
} catch (e) {
|
|
938
|
+
if (typeof window.toast === 'function') window.toast('Failed to compose review: ' + e.message, { type: 'error' });
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
883
941
|
}
|
|
884
942
|
|
|
885
943
|
// Main "Send" button — auto-detects best session (existing project session or new)
|