claude-code-watch 0.0.14 → 0.0.16
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/package.json +1 -1
- package/public/index.html +80 -17
- package/src/parser/parser.js +63 -9
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -147,18 +147,27 @@ body {
|
|
|
147
147
|
|
|
148
148
|
/* ── Tree node styles ── */
|
|
149
149
|
.tree-row {
|
|
150
|
-
display: flex; align-items:
|
|
150
|
+
display: flex; align-items: flex-start;
|
|
151
|
+
}
|
|
152
|
+
.tree-content {
|
|
153
|
+
flex: 1; min-width: 0;
|
|
151
154
|
}
|
|
155
|
+
.tree-content:hover { background: rgba(255,255,255,0.05); }
|
|
156
|
+
.tree-row.selected > .tree-content { background: rgba(124,58,237,0.3); }
|
|
157
|
+
.tree-content.dim { opacity: 0.4; }
|
|
152
158
|
.tree-node {
|
|
153
|
-
|
|
159
|
+
display: flex; align-items: center;
|
|
154
160
|
padding: 3px 2px 3px 0;
|
|
155
161
|
cursor: pointer; white-space: nowrap; gap: 4px;
|
|
156
|
-
|
|
162
|
+
overflow: hidden;
|
|
157
163
|
}
|
|
158
|
-
.tree-node:hover { background: rgba(255,255,255,0.05); }
|
|
159
|
-
.tree-node.selected { background: rgba(124,58,237,0.3); }
|
|
160
|
-
.tree-node.dim { opacity: 0.4; }
|
|
161
164
|
.tree-prefix { color: var(--dim); font-size: 12px; flex-shrink: 0; letter-spacing: 0; font-family: monospace; }
|
|
165
|
+
.tree-activity {
|
|
166
|
+
font-size: 10px; color: var(--dim); white-space: nowrap;
|
|
167
|
+
overflow: hidden; text-overflow: ellipsis;
|
|
168
|
+
padding: 0 2px 2px; line-height: 1.2;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
}
|
|
162
171
|
.tree-node .ctx-pct { font-size: 10px; margin-left: 4px; flex-shrink: 0; }
|
|
163
172
|
.tree-node .ctx-pct.warn { color: var(--yellow); }
|
|
164
173
|
.tree-node .ctx-pct.danger { color: var(--red); }
|
|
@@ -191,6 +200,7 @@ body {
|
|
|
191
200
|
.stream-line.tool-output { color: var(--green); }
|
|
192
201
|
.stream-line.text { color: var(--text); }
|
|
193
202
|
.stream-line.hook { color: var(--cyan); }
|
|
203
|
+
.stream-line .hook-label { color: var(--dim); }
|
|
194
204
|
.stream-line.diag { color: var(--red); }
|
|
195
205
|
.stream-line.debug { color: var(--gray); }
|
|
196
206
|
.stream-line.marker { color: var(--dim); }
|
|
@@ -249,8 +259,8 @@ body {
|
|
|
249
259
|
:root[data-theme="light"] .btn.on:hover { background: var(--purple2); color: #fff; }
|
|
250
260
|
:root[data-theme="light"] .btn.on:hover::after { background: var(--purple2); color: #fff; }
|
|
251
261
|
:root[data-theme="light"] .hljs { background: #f0f0f0 !important; }
|
|
252
|
-
:root[data-theme="light"] .tree-
|
|
253
|
-
:root[data-theme="light"] .tree-
|
|
262
|
+
:root[data-theme="light"] .tree-content:hover { background: rgba(0,0,0,0.06); }
|
|
263
|
+
:root[data-theme="light"] .tree-row.selected > .tree-content { background: rgba(124,58,237,0.2); }
|
|
254
264
|
:root[data-theme="light"] .tree-node .active-dot.off { color: #bbb; }
|
|
255
265
|
:root[data-theme="light"] #tree-resize-handle:hover,
|
|
256
266
|
:root[data-theme="light"] #tree-resize-handle.active { background: var(--purple); }
|
|
@@ -288,6 +298,7 @@ body {
|
|
|
288
298
|
<button class="btn btn-icon" onclick="selectAll()" data-tooltip="Show all sessions/agents">⊞</button>
|
|
289
299
|
<button class="btn btn-icon accent" onclick="soloSelected()" data-tooltip="Solo selected">⊙</button>
|
|
290
300
|
<button class="btn btn-icon danger" onclick="removeSelectedSession()" data-tooltip="Remove session">✕</button>
|
|
301
|
+
<button class="btn btn-icon on" id="btn-activity" onclick="toggleActivity()" data-tooltip="Toggle activity info">💬</button>
|
|
291
302
|
<span style="flex:1"></span>
|
|
292
303
|
<span id="tree-cursor-info" style="font-size:10px;color:var(--dim)"></span>
|
|
293
304
|
</div>
|
|
@@ -357,6 +368,9 @@ class LRUCache {
|
|
|
357
368
|
}
|
|
358
369
|
const seenToolIDs = new LRUCache(5000);
|
|
359
370
|
const toolNameMap = new LRUCache(2000);
|
|
371
|
+
const agentActivity = new Map(); // "sessionID:agentID" → { toolName, content }
|
|
372
|
+
const taskDescriptions = new Map(); // toolID → description string
|
|
373
|
+
const MAX_DESC_STORE = 200;
|
|
360
374
|
let filters = new Map();
|
|
361
375
|
|
|
362
376
|
let showThinking = true;
|
|
@@ -364,6 +378,7 @@ let showToolInput = true;
|
|
|
364
378
|
let showToolOutput = true;
|
|
365
379
|
let showText = true;
|
|
366
380
|
let showHook = true;
|
|
381
|
+
let showActivity = true;
|
|
367
382
|
let autoDiscovery = true;
|
|
368
383
|
|
|
369
384
|
let renderPending = false;
|
|
@@ -646,6 +661,20 @@ function pushItem(item) {
|
|
|
646
661
|
toolNameMap.set(item.toolID, item.toolName);
|
|
647
662
|
}
|
|
648
663
|
|
|
664
|
+
if (item.type === 'tool_input') {
|
|
665
|
+
// Main 代理不追踪工具调用,只显示用户 prompt
|
|
666
|
+
if (item.agentID) {
|
|
667
|
+
agentActivity.set(item.sessionID + ':' + item.agentID, { toolName: item.toolName || '', content: (item.content || '').slice(0, MAX_DESC_STORE) });
|
|
668
|
+
}
|
|
669
|
+
if (item.toolID) {
|
|
670
|
+
taskDescriptions.set(item.toolID, (item.content || '').slice(0, MAX_DESC_STORE));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (item.type === 'user_text') {
|
|
675
|
+
agentActivity.set(item.sessionID + ':' + (item.agentID || ''), { toolName: '', content: (item.content || '').slice(0, MAX_DESC_STORE) });
|
|
676
|
+
}
|
|
677
|
+
|
|
649
678
|
if (item.toolID) {
|
|
650
679
|
const key = `${item.toolID}:${item.type}`;
|
|
651
680
|
if (seenToolIDs.has(key)) return;
|
|
@@ -671,6 +700,7 @@ function isItemVisible(item) {
|
|
|
671
700
|
case 'tool_output': return showToolOutput;
|
|
672
701
|
case 'text': return showText;
|
|
673
702
|
case 'hook_output': return showHook;
|
|
703
|
+
case 'user_text': return false;
|
|
674
704
|
default: return true;
|
|
675
705
|
}
|
|
676
706
|
}
|
|
@@ -694,14 +724,23 @@ function rebuildNodes() {
|
|
|
694
724
|
);
|
|
695
725
|
const lastTaskIdx = tasks.length - 1;
|
|
696
726
|
const hasTasks = tasks.length > 0;
|
|
697
|
-
|
|
727
|
+
const actKey = s.id + ':' + a.id;
|
|
728
|
+
const act = agentActivity.get(actKey);
|
|
729
|
+
treeNodes.push({
|
|
730
|
+
type: a.type, id: a.id, name: a.name, sessionID: s.id,
|
|
731
|
+
level: 1, isLast: isLastAgent && !hasTasks,
|
|
732
|
+
activityTool: act ? act.toolName : '',
|
|
733
|
+
activityDesc: act ? act.content : '',
|
|
734
|
+
});
|
|
698
735
|
for (let ti = 0; ti < tasks.length; ti++) {
|
|
699
736
|
const t = tasks[ti];
|
|
737
|
+
const tDesc = taskDescriptions.get(t.id);
|
|
700
738
|
treeNodes.push({
|
|
701
739
|
type: 'task', id: t.id, name: t.toolName,
|
|
702
740
|
sessionID: s.id, parentAgentID: t.parentAgentID,
|
|
703
741
|
outputPath: t.outputPath, isComplete: t.isComplete,
|
|
704
742
|
level: 2, isLast: isLastAgent && ti === lastTaskIdx,
|
|
743
|
+
description: tDesc || '',
|
|
705
744
|
});
|
|
706
745
|
}
|
|
707
746
|
}
|
|
@@ -736,10 +775,12 @@ function getNodeHTML(node, idx) {
|
|
|
736
775
|
const subInfo = parts.length > 0 ? ` <span style="color:#6b7280;font-size:10px">${parts.join(' · ')}</span>` : '';
|
|
737
776
|
const agentCount = node.agents ? node.agents.filter(a => a.type === 'agent').length : 0;
|
|
738
777
|
return `<div class="tree-row${selClass ? ' selected' : ''}">
|
|
739
|
-
<div class="tree-
|
|
740
|
-
<
|
|
741
|
-
|
|
742
|
-
|
|
778
|
+
<div class="tree-content" onclick="treeClick(${idx})" data-idx="${idx}">
|
|
779
|
+
<div class="tree-node">
|
|
780
|
+
<span class="tree-prefix">${treePrefix(node)}</span>${activeDot} ${node.collapsed ? '▸' : '▾'} ${esc(displayName)}
|
|
781
|
+
${node.collapsed && agentCount > 0 ? `(${esc(String(agentCount))})` : ''}
|
|
782
|
+
${subInfo}
|
|
783
|
+
</div>
|
|
743
784
|
</div>
|
|
744
785
|
<span class="tree-actions">
|
|
745
786
|
<button class="btn btn-icon accent" onclick="event.stopPropagation();selectIndex(${idx});soloSelected()" data-tooltip="Solo">⊙</button>
|
|
@@ -760,9 +801,19 @@ function getNodeHTML(node, idx) {
|
|
|
760
801
|
ctxPct = `<span class="ctx-pct ${cls}">${pct}%</span>`;
|
|
761
802
|
}
|
|
762
803
|
const activeDot = ctx && (Date.now() - ctx.lastActivity < 120000) ? '<span class="active-dot on">🟢</span>' : '<span class="active-dot off">⚪</span>';
|
|
804
|
+
const actIcon = node.type === 'main' ? '🗣' : '⚡';
|
|
805
|
+
const actText = showActivity && (node.activityTool || node.activityDesc)
|
|
806
|
+
? (node.activityTool && node.activityDesc ? `${node.activityTool}: ${node.activityDesc}` : (node.activityTool || node.activityDesc))
|
|
807
|
+
: '';
|
|
808
|
+
const activityHTML = actText
|
|
809
|
+
? `<div class="tree-activity">${actIcon} ${esc(actText)}</div>`
|
|
810
|
+
: '';
|
|
763
811
|
return `<div class="tree-row${selClass ? ' selected' : ''}">
|
|
764
|
-
<div class="tree-
|
|
812
|
+
<div class="tree-content${enabled ? '' : ' dim'}" onclick="treeClick(${idx})" data-idx="${idx}">
|
|
813
|
+
<div class="tree-node">
|
|
765
814
|
<span class="tree-prefix">${treePrefix(node)}</span>${activeDot} ${icon} ${esc(node.name || '')}${ctxPct}
|
|
815
|
+
</div>
|
|
816
|
+
${activityHTML}
|
|
766
817
|
</div>
|
|
767
818
|
<span class="tree-actions">
|
|
768
819
|
<button class="btn btn-icon accent" onclick="event.stopPropagation();selectIndex(${idx});soloSelected()" data-tooltip="Solo">⊙</button>
|
|
@@ -773,9 +824,15 @@ function getNodeHTML(node, idx) {
|
|
|
773
824
|
|
|
774
825
|
if (node.type === 'task') {
|
|
775
826
|
const icon = node.isComplete ? '✓' : '⏳';
|
|
827
|
+
const descHTML = showActivity && node.description
|
|
828
|
+
? `<div class="tree-activity">📋 ${esc(node.description)}</div>`
|
|
829
|
+
: '';
|
|
776
830
|
return `<div class="tree-row${selClass ? ' selected' : ''}">
|
|
777
|
-
<div class="tree-
|
|
831
|
+
<div class="tree-content dim" onclick="treeClick(${idx})" data-idx="${idx}">
|
|
832
|
+
<div class="tree-node">
|
|
778
833
|
<span class="tree-prefix">${treePrefix(node)}</span>${icon} ${esc(node.name || 'bg-task')}
|
|
834
|
+
</div>
|
|
835
|
+
${descHTML}
|
|
779
836
|
</div>
|
|
780
837
|
<span class="tree-actions">
|
|
781
838
|
<button class="btn btn-icon" onclick="event.stopPropagation();selectIndex(${idx});loadBgTask(${idx})" data-tooltip="Load output">▶</button>
|
|
@@ -799,7 +856,7 @@ function renderTree() {
|
|
|
799
856
|
treeEl.innerHTML = html;
|
|
800
857
|
|
|
801
858
|
// Scroll selected into view
|
|
802
|
-
const sel = treeEl.querySelector('.tree-
|
|
859
|
+
const sel = treeEl.querySelector('.tree-row.selected');
|
|
803
860
|
if (sel) sel.scrollIntoView({ block: 'nearest' });
|
|
804
861
|
|
|
805
862
|
treeCursorInfo.textContent = `${treeCursor + 1}/${treeNodes.length}`;
|
|
@@ -927,7 +984,11 @@ function renderItem(item) {
|
|
|
927
984
|
if (item.toolName) label += ' ' + item.toolName;
|
|
928
985
|
if (item.durationMs > 0) label += ' ' + fmtDur(item.durationMs);
|
|
929
986
|
lines.push({ cls: agentTagCls, text: `<span class="tag-label">${esc(agentName + sep + label)}</span>${tsHtml}`, html: true });
|
|
930
|
-
|
|
987
|
+
if (item.hookCommand) lines.push({ cls: 'stream-line hook', text: `<span class="hook-label">command:</span> ${esc(item.hookCommand)}`, html: true });
|
|
988
|
+
if (item.hookContent) {
|
|
989
|
+
for (const l of truncContent(item.hookContent)) lines.push({ cls: 'stream-line hook', text: `<span class="hook-label">content:</span> ${esc(l)}`, html: true });
|
|
990
|
+
}
|
|
991
|
+
for (const l of truncContent(item.content)) lines.push({ cls: 'stream-line hook', text: `<span class="hook-label">stdout:</span> ${esc(l)}`, html: true });
|
|
931
992
|
break;
|
|
932
993
|
}
|
|
933
994
|
case 'diagnostics': {
|
|
@@ -965,6 +1026,7 @@ function refreshButtons() {
|
|
|
965
1026
|
document.getElementById('btn-tool-output').classList.toggle('on', showToolOutput);
|
|
966
1027
|
document.getElementById('btn-text').classList.toggle('on', showText);
|
|
967
1028
|
document.getElementById('btn-hook').classList.toggle('on', showHook);
|
|
1029
|
+
document.getElementById('btn-activity').classList.toggle('on', showActivity);
|
|
968
1030
|
document.getElementById('btn-autoscroll').classList.toggle('on', autoScroll);
|
|
969
1031
|
document.getElementById('btn-tree-toggle').classList.toggle('on', showTree);
|
|
970
1032
|
document.getElementById('btn-autodisco').classList.toggle('on', autoDiscovery);
|
|
@@ -1144,6 +1206,7 @@ function toggleText() { showText = !showText; needsFullRender = true;
|
|
|
1144
1206
|
visibleDirty = true; renderStream(); refreshButtons(); }
|
|
1145
1207
|
function toggleHook() { showHook = !showHook; needsFullRender = true;
|
|
1146
1208
|
visibleDirty = true; renderStream(); refreshButtons(); }
|
|
1209
|
+
function toggleActivity() { showActivity = !showActivity; rebuildNodes(); scheduleRender(); refreshButtons(); }
|
|
1147
1210
|
function toggleAutoScroll() { autoScroll = !autoScroll; if (autoScroll) streamEl.scrollTop = streamEl.scrollHeight; renderAll(); }
|
|
1148
1211
|
function toggleTree() { showTree = !showTree; document.getElementById('tree-panel').classList.toggle('hidden', !showTree); }
|
|
1149
1212
|
function toggleAutoDiscovery() { sendCmd('toggleAutoDiscovery'); }
|
package/src/parser/parser.js
CHANGED
|
@@ -9,6 +9,7 @@ var StreamItemType = {
|
|
|
9
9
|
TOOL_INPUT: 'tool_input',
|
|
10
10
|
TOOL_OUTPUT: 'tool_output',
|
|
11
11
|
TEXT: 'text',
|
|
12
|
+
USER_TEXT: 'user_text',
|
|
12
13
|
TURN_MARKER: 'turn_marker',
|
|
13
14
|
COMPACT_MARKER: 'compact_marker',
|
|
14
15
|
HOOK_OUTPUT: 'hook_output',
|
|
@@ -27,6 +28,7 @@ function makeItem(overrides = {}) {
|
|
|
27
28
|
return {
|
|
28
29
|
type: '', sessionID: '', agentID: '', agentName: '', timestamp: 0,
|
|
29
30
|
content: '', toolName: '', toolID: '', durationMs: 0,
|
|
31
|
+
hookContent: '', hookCommand: '',
|
|
30
32
|
inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0,
|
|
31
33
|
model: '',
|
|
32
34
|
...overrides,
|
|
@@ -211,14 +213,18 @@ function parseAttachment(raw, timestamp) {
|
|
|
211
213
|
const name = agentDisplayName(raw.agentId);
|
|
212
214
|
switch (raw.attachment.type) {
|
|
213
215
|
case 'hook_success': {
|
|
214
|
-
const
|
|
216
|
+
const stdout = (raw.attachment.stdout || '').replace(/\n$/, '');
|
|
217
|
+
const stdin = raw.attachment.content || '';
|
|
218
|
+
const hookContent = stdin && stdin !== stdout ? stdin : '';
|
|
215
219
|
return [makeItem({
|
|
216
220
|
type: StreamItemType.HOOK_OUTPUT,
|
|
217
221
|
sessionID: raw.sessionId,
|
|
218
222
|
agentID: raw.agentId || '',
|
|
219
223
|
agentName: name,
|
|
220
224
|
toolName: raw.attachment.hookName || '',
|
|
221
|
-
content:
|
|
225
|
+
content: stdout,
|
|
226
|
+
hookContent: hookContent,
|
|
227
|
+
hookCommand: raw.attachment.command || '',
|
|
222
228
|
durationMs: raw.attachment.durationMs || 0,
|
|
223
229
|
timestamp,
|
|
224
230
|
})];
|
|
@@ -368,7 +374,7 @@ function parseAssistantMessage(raw, timestamp) {
|
|
|
368
374
|
|
|
369
375
|
function parseUserMessage(raw, timestamp) {
|
|
370
376
|
const msg = raw.message;
|
|
371
|
-
if (!msg
|
|
377
|
+
if (!msg) return [];
|
|
372
378
|
|
|
373
379
|
// Parse toolUseResult for duration
|
|
374
380
|
let durationMs = 0;
|
|
@@ -379,20 +385,54 @@ function parseUserMessage(raw, timestamp) {
|
|
|
379
385
|
const items = [];
|
|
380
386
|
const name = agentDisplayName(raw.agentId);
|
|
381
387
|
|
|
382
|
-
|
|
383
|
-
|
|
388
|
+
// String content — user prompt
|
|
389
|
+
if (typeof msg.content === 'string' && msg.content) {
|
|
390
|
+
const text = stripNonUserContent(msg.content);
|
|
391
|
+
if (text) {
|
|
384
392
|
items.push(makeItem({
|
|
385
|
-
type: StreamItemType.
|
|
393
|
+
type: StreamItemType.USER_TEXT,
|
|
386
394
|
agentID: raw.agentId || '',
|
|
387
395
|
agentName: name,
|
|
388
|
-
content:
|
|
389
|
-
toolID: result.tool_use_id || '',
|
|
390
|
-
durationMs,
|
|
396
|
+
content: text,
|
|
391
397
|
timestamp,
|
|
392
398
|
}));
|
|
393
399
|
}
|
|
394
400
|
}
|
|
395
401
|
|
|
402
|
+
// Array content — mixed text blocks and tool_result blocks
|
|
403
|
+
if (Array.isArray(msg.content)) {
|
|
404
|
+
const textParts = [];
|
|
405
|
+
for (const block of msg.content) {
|
|
406
|
+
if (block.type === 'text' && block.text) {
|
|
407
|
+
const text = stripNonUserContent(block.text);
|
|
408
|
+
if (text) textParts.push(text);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (textParts.length > 0) {
|
|
412
|
+
items.push(makeItem({
|
|
413
|
+
type: StreamItemType.USER_TEXT,
|
|
414
|
+
agentID: raw.agentId || '',
|
|
415
|
+
agentName: name,
|
|
416
|
+
content: textParts.join('\n'),
|
|
417
|
+
timestamp,
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for (const result of msg.content) {
|
|
422
|
+
if (result.type === 'tool_result') {
|
|
423
|
+
items.push(makeItem({
|
|
424
|
+
type: StreamItemType.TOOL_OUTPUT,
|
|
425
|
+
agentID: raw.agentId || '',
|
|
426
|
+
agentName: name,
|
|
427
|
+
content: extractToolResultContent(result.content),
|
|
428
|
+
toolID: result.tool_use_id || '',
|
|
429
|
+
durationMs,
|
|
430
|
+
timestamp,
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
396
436
|
return items;
|
|
397
437
|
}
|
|
398
438
|
|
|
@@ -413,6 +453,19 @@ function extractToolResultContent(content) {
|
|
|
413
453
|
}
|
|
414
454
|
}
|
|
415
455
|
|
|
456
|
+
function stripNonUserContent(text) {
|
|
457
|
+
if (!text) return '';
|
|
458
|
+
// Remove tags that wrap non-user content
|
|
459
|
+
let s = text;
|
|
460
|
+
s = s.replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, '');
|
|
461
|
+
s = s.replace(/<command-name>[\s\S]*?<\/command-name>/g, '');
|
|
462
|
+
s = s.replace(/<command-message>[\s\S]*?<\/command-message>/g, '');
|
|
463
|
+
s = s.replace(/<command-args>[\s\S]*?<\/command-args>/g, '');
|
|
464
|
+
s = s.replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, '');
|
|
465
|
+
// Trim and return; empty string means no real user content
|
|
466
|
+
return s.trim();
|
|
467
|
+
}
|
|
468
|
+
|
|
416
469
|
// ============================================================================
|
|
417
470
|
// Tool Input Formatting
|
|
418
471
|
// ============================================================================
|
|
@@ -497,5 +550,6 @@ module.exports = {
|
|
|
497
550
|
formatToolInput,
|
|
498
551
|
prettyToolName,
|
|
499
552
|
agentDisplayName,
|
|
553
|
+
stripNonUserContent,
|
|
500
554
|
MAX_TOOL_INPUT_LENGTH,
|
|
501
555
|
};
|