claude-code-watch 0.0.14 → 0.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-watch",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Web-based real-time monitor for Claude Code.",
5
5
  "main": "./src/server/server.js",
6
6
  "bin": {
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: center;
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
- flex: 1; display: flex; align-items: center;
159
+ display: flex; align-items: center;
154
160
  padding: 3px 2px 3px 0;
155
161
  cursor: pointer; white-space: nowrap; gap: 4px;
156
- min-width: 0; overflow: hidden;
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); }
@@ -249,8 +258,8 @@ body {
249
258
  :root[data-theme="light"] .btn.on:hover { background: var(--purple2); color: #fff; }
250
259
  :root[data-theme="light"] .btn.on:hover::after { background: var(--purple2); color: #fff; }
251
260
  :root[data-theme="light"] .hljs { background: #f0f0f0 !important; }
252
- :root[data-theme="light"] .tree-node:hover { background: rgba(0,0,0,0.06); }
253
- :root[data-theme="light"] .tree-node.selected { background: rgba(124,58,237,0.2); }
261
+ :root[data-theme="light"] .tree-content:hover { background: rgba(0,0,0,0.06); }
262
+ :root[data-theme="light"] .tree-row.selected > .tree-content { background: rgba(124,58,237,0.2); }
254
263
  :root[data-theme="light"] .tree-node .active-dot.off { color: #bbb; }
255
264
  :root[data-theme="light"] #tree-resize-handle:hover,
256
265
  :root[data-theme="light"] #tree-resize-handle.active { background: var(--purple); }
@@ -288,6 +297,7 @@ body {
288
297
  <button class="btn btn-icon" onclick="selectAll()" data-tooltip="Show all sessions/agents">⊞</button>
289
298
  <button class="btn btn-icon accent" onclick="soloSelected()" data-tooltip="Solo selected">⊙</button>
290
299
  <button class="btn btn-icon danger" onclick="removeSelectedSession()" data-tooltip="Remove session">✕</button>
300
+ <button class="btn btn-icon on" id="btn-activity" onclick="toggleActivity()" data-tooltip="Toggle activity info">💬</button>
291
301
  <span style="flex:1"></span>
292
302
  <span id="tree-cursor-info" style="font-size:10px;color:var(--dim)"></span>
293
303
  </div>
@@ -357,6 +367,9 @@ class LRUCache {
357
367
  }
358
368
  const seenToolIDs = new LRUCache(5000);
359
369
  const toolNameMap = new LRUCache(2000);
370
+ const agentActivity = new Map(); // "sessionID:agentID" → { toolName, content }
371
+ const taskDescriptions = new Map(); // toolID → description string
372
+ const MAX_DESC_STORE = 200;
360
373
  let filters = new Map();
361
374
 
362
375
  let showThinking = true;
@@ -364,6 +377,7 @@ let showToolInput = true;
364
377
  let showToolOutput = true;
365
378
  let showText = true;
366
379
  let showHook = true;
380
+ let showActivity = true;
367
381
  let autoDiscovery = true;
368
382
 
369
383
  let renderPending = false;
@@ -646,6 +660,20 @@ function pushItem(item) {
646
660
  toolNameMap.set(item.toolID, item.toolName);
647
661
  }
648
662
 
663
+ if (item.type === 'tool_input') {
664
+ // Main 代理不追踪工具调用,只显示用户 prompt
665
+ if (item.agentID) {
666
+ agentActivity.set(item.sessionID + ':' + item.agentID, { toolName: item.toolName || '', content: (item.content || '').slice(0, MAX_DESC_STORE) });
667
+ }
668
+ if (item.toolID) {
669
+ taskDescriptions.set(item.toolID, (item.content || '').slice(0, MAX_DESC_STORE));
670
+ }
671
+ }
672
+
673
+ if (item.type === 'user_text') {
674
+ agentActivity.set(item.sessionID + ':' + (item.agentID || ''), { toolName: '', content: (item.content || '').slice(0, MAX_DESC_STORE) });
675
+ }
676
+
649
677
  if (item.toolID) {
650
678
  const key = `${item.toolID}:${item.type}`;
651
679
  if (seenToolIDs.has(key)) return;
@@ -671,6 +699,7 @@ function isItemVisible(item) {
671
699
  case 'tool_output': return showToolOutput;
672
700
  case 'text': return showText;
673
701
  case 'hook_output': return showHook;
702
+ case 'user_text': return false;
674
703
  default: return true;
675
704
  }
676
705
  }
@@ -694,14 +723,23 @@ function rebuildNodes() {
694
723
  );
695
724
  const lastTaskIdx = tasks.length - 1;
696
725
  const hasTasks = tasks.length > 0;
697
- treeNodes.push({ type: a.type, id: a.id, name: a.name, sessionID: s.id, level: 1, isLast: isLastAgent && !hasTasks });
726
+ const actKey = s.id + ':' + a.id;
727
+ const act = agentActivity.get(actKey);
728
+ treeNodes.push({
729
+ type: a.type, id: a.id, name: a.name, sessionID: s.id,
730
+ level: 1, isLast: isLastAgent && !hasTasks,
731
+ activityTool: act ? act.toolName : '',
732
+ activityDesc: act ? act.content : '',
733
+ });
698
734
  for (let ti = 0; ti < tasks.length; ti++) {
699
735
  const t = tasks[ti];
736
+ const tDesc = taskDescriptions.get(t.id);
700
737
  treeNodes.push({
701
738
  type: 'task', id: t.id, name: t.toolName,
702
739
  sessionID: s.id, parentAgentID: t.parentAgentID,
703
740
  outputPath: t.outputPath, isComplete: t.isComplete,
704
741
  level: 2, isLast: isLastAgent && ti === lastTaskIdx,
742
+ description: tDesc || '',
705
743
  });
706
744
  }
707
745
  }
@@ -736,10 +774,12 @@ function getNodeHTML(node, idx) {
736
774
  const subInfo = parts.length > 0 ? ` <span style="color:#6b7280;font-size:10px">${parts.join(' · ')}</span>` : '';
737
775
  const agentCount = node.agents ? node.agents.filter(a => a.type === 'agent').length : 0;
738
776
  return `<div class="tree-row${selClass ? ' selected' : ''}">
739
- <div class="tree-node" onclick="treeClick(${idx})" data-idx="${idx}">
740
- <span class="tree-prefix">${treePrefix(node)}</span>${activeDot} ${node.collapsed ? '▸' : '▾'} ${esc(displayName)}
741
- ${node.collapsed && agentCount > 0 ? `(${esc(String(agentCount))})` : ''}
742
- ${subInfo}
777
+ <div class="tree-content" onclick="treeClick(${idx})" data-idx="${idx}">
778
+ <div class="tree-node">
779
+ <span class="tree-prefix">${treePrefix(node)}</span>${activeDot} ${node.collapsed ? '▸' : '▾'} ${esc(displayName)}
780
+ ${node.collapsed && agentCount > 0 ? `(${esc(String(agentCount))})` : ''}
781
+ ${subInfo}
782
+ </div>
743
783
  </div>
744
784
  <span class="tree-actions">
745
785
  <button class="btn btn-icon accent" onclick="event.stopPropagation();selectIndex(${idx});soloSelected()" data-tooltip="Solo">⊙</button>
@@ -760,9 +800,19 @@ function getNodeHTML(node, idx) {
760
800
  ctxPct = `<span class="ctx-pct ${cls}">${pct}%</span>`;
761
801
  }
762
802
  const activeDot = ctx && (Date.now() - ctx.lastActivity < 120000) ? '<span class="active-dot on">🟢</span>' : '<span class="active-dot off">⚪</span>';
803
+ const actIcon = node.type === 'main' ? '🗣' : '⚡';
804
+ const actText = showActivity && (node.activityTool || node.activityDesc)
805
+ ? (node.activityTool && node.activityDesc ? `${node.activityTool}: ${node.activityDesc}` : (node.activityTool || node.activityDesc))
806
+ : '';
807
+ const activityHTML = actText
808
+ ? `<div class="tree-activity">${actIcon} ${esc(actText)}</div>`
809
+ : '';
763
810
  return `<div class="tree-row${selClass ? ' selected' : ''}">
764
- <div class="tree-node${enabled ? '' : ' dim'}" onclick="treeClick(${idx})" data-idx="${idx}">
811
+ <div class="tree-content${enabled ? '' : ' dim'}" onclick="treeClick(${idx})" data-idx="${idx}">
812
+ <div class="tree-node">
765
813
  <span class="tree-prefix">${treePrefix(node)}</span>${activeDot} ${icon} ${esc(node.name || '')}${ctxPct}
814
+ </div>
815
+ ${activityHTML}
766
816
  </div>
767
817
  <span class="tree-actions">
768
818
  <button class="btn btn-icon accent" onclick="event.stopPropagation();selectIndex(${idx});soloSelected()" data-tooltip="Solo">⊙</button>
@@ -773,9 +823,15 @@ function getNodeHTML(node, idx) {
773
823
 
774
824
  if (node.type === 'task') {
775
825
  const icon = node.isComplete ? '✓' : '⏳';
826
+ const descHTML = showActivity && node.description
827
+ ? `<div class="tree-activity">📋 ${esc(node.description)}</div>`
828
+ : '';
776
829
  return `<div class="tree-row${selClass ? ' selected' : ''}">
777
- <div class="tree-node dim" onclick="treeClick(${idx})" data-idx="${idx}">
830
+ <div class="tree-content dim" onclick="treeClick(${idx})" data-idx="${idx}">
831
+ <div class="tree-node">
778
832
  <span class="tree-prefix">${treePrefix(node)}</span>${icon} ${esc(node.name || 'bg-task')}
833
+ </div>
834
+ ${descHTML}
779
835
  </div>
780
836
  <span class="tree-actions">
781
837
  <button class="btn btn-icon" onclick="event.stopPropagation();selectIndex(${idx});loadBgTask(${idx})" data-tooltip="Load output">▶</button>
@@ -799,7 +855,7 @@ function renderTree() {
799
855
  treeEl.innerHTML = html;
800
856
 
801
857
  // Scroll selected into view
802
- const sel = treeEl.querySelector('.tree-node.selected');
858
+ const sel = treeEl.querySelector('.tree-row.selected');
803
859
  if (sel) sel.scrollIntoView({ block: 'nearest' });
804
860
 
805
861
  treeCursorInfo.textContent = `${treeCursor + 1}/${treeNodes.length}`;
@@ -965,6 +1021,7 @@ function refreshButtons() {
965
1021
  document.getElementById('btn-tool-output').classList.toggle('on', showToolOutput);
966
1022
  document.getElementById('btn-text').classList.toggle('on', showText);
967
1023
  document.getElementById('btn-hook').classList.toggle('on', showHook);
1024
+ document.getElementById('btn-activity').classList.toggle('on', showActivity);
968
1025
  document.getElementById('btn-autoscroll').classList.toggle('on', autoScroll);
969
1026
  document.getElementById('btn-tree-toggle').classList.toggle('on', showTree);
970
1027
  document.getElementById('btn-autodisco').classList.toggle('on', autoDiscovery);
@@ -1144,6 +1201,7 @@ function toggleText() { showText = !showText; needsFullRender = true;
1144
1201
  visibleDirty = true; renderStream(); refreshButtons(); }
1145
1202
  function toggleHook() { showHook = !showHook; needsFullRender = true;
1146
1203
  visibleDirty = true; renderStream(); refreshButtons(); }
1204
+ function toggleActivity() { showActivity = !showActivity; rebuildNodes(); scheduleRender(); refreshButtons(); }
1147
1205
  function toggleAutoScroll() { autoScroll = !autoScroll; if (autoScroll) streamEl.scrollTop = streamEl.scrollHeight; renderAll(); }
1148
1206
  function toggleTree() { showTree = !showTree; document.getElementById('tree-panel').classList.toggle('hidden', !showTree); }
1149
1207
  function toggleAutoDiscovery() { sendCmd('toggleAutoDiscovery'); }
@@ -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',
@@ -368,7 +369,7 @@ function parseAssistantMessage(raw, timestamp) {
368
369
 
369
370
  function parseUserMessage(raw, timestamp) {
370
371
  const msg = raw.message;
371
- if (!msg || !Array.isArray(msg.content)) return [];
372
+ if (!msg) return [];
372
373
 
373
374
  // Parse toolUseResult for duration
374
375
  let durationMs = 0;
@@ -379,20 +380,54 @@ function parseUserMessage(raw, timestamp) {
379
380
  const items = [];
380
381
  const name = agentDisplayName(raw.agentId);
381
382
 
382
- for (const result of msg.content) {
383
- if (result.type === 'tool_result') {
383
+ // String content user prompt
384
+ if (typeof msg.content === 'string' && msg.content) {
385
+ const text = stripNonUserContent(msg.content);
386
+ if (text) {
384
387
  items.push(makeItem({
385
- type: StreamItemType.TOOL_OUTPUT,
388
+ type: StreamItemType.USER_TEXT,
386
389
  agentID: raw.agentId || '',
387
390
  agentName: name,
388
- content: extractToolResultContent(result.content),
389
- toolID: result.tool_use_id || '',
390
- durationMs,
391
+ content: text,
391
392
  timestamp,
392
393
  }));
393
394
  }
394
395
  }
395
396
 
397
+ // Array content — mixed text blocks and tool_result blocks
398
+ if (Array.isArray(msg.content)) {
399
+ const textParts = [];
400
+ for (const block of msg.content) {
401
+ if (block.type === 'text' && block.text) {
402
+ const text = stripNonUserContent(block.text);
403
+ if (text) textParts.push(text);
404
+ }
405
+ }
406
+ if (textParts.length > 0) {
407
+ items.push(makeItem({
408
+ type: StreamItemType.USER_TEXT,
409
+ agentID: raw.agentId || '',
410
+ agentName: name,
411
+ content: textParts.join('\n'),
412
+ timestamp,
413
+ }));
414
+ }
415
+
416
+ for (const result of msg.content) {
417
+ if (result.type === 'tool_result') {
418
+ items.push(makeItem({
419
+ type: StreamItemType.TOOL_OUTPUT,
420
+ agentID: raw.agentId || '',
421
+ agentName: name,
422
+ content: extractToolResultContent(result.content),
423
+ toolID: result.tool_use_id || '',
424
+ durationMs,
425
+ timestamp,
426
+ }));
427
+ }
428
+ }
429
+ }
430
+
396
431
  return items;
397
432
  }
398
433
 
@@ -413,6 +448,19 @@ function extractToolResultContent(content) {
413
448
  }
414
449
  }
415
450
 
451
+ function stripNonUserContent(text) {
452
+ if (!text) return '';
453
+ // Remove tags that wrap non-user content
454
+ let s = text;
455
+ s = s.replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, '');
456
+ s = s.replace(/<command-name>[\s\S]*?<\/command-name>/g, '');
457
+ s = s.replace(/<command-message>[\s\S]*?<\/command-message>/g, '');
458
+ s = s.replace(/<command-args>[\s\S]*?<\/command-args>/g, '');
459
+ s = s.replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, '');
460
+ // Trim and return; empty string means no real user content
461
+ return s.trim();
462
+ }
463
+
416
464
  // ============================================================================
417
465
  // Tool Input Formatting
418
466
  // ============================================================================
@@ -497,5 +545,6 @@ module.exports = {
497
545
  formatToolInput,
498
546
  prettyToolName,
499
547
  agentDisplayName,
548
+ stripNonUserContent,
500
549
  MAX_TOOL_INPUT_LENGTH,
501
550
  };