claude-teammate 0.1.166 → 0.1.167
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/src/dashboard/ui.html +178 -5
package/package.json
CHANGED
package/src/dashboard/ui.html
CHANGED
|
@@ -1326,6 +1326,28 @@ a:hover { text-decoration:underline; }
|
|
|
1326
1326
|
font-size:.65rem;
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
|
+
/* Log rich formatting */
|
|
1330
|
+
.tool-name { color:#86EFAC; font-weight:600; }
|
|
1331
|
+
.tool-key { color:#FCD34D; }
|
|
1332
|
+
.tool-arg { color:#9CA3AF; }
|
|
1333
|
+
.so-key { color:#60A5FA; font-weight:500; }
|
|
1334
|
+
.so-val { color:#E5E0D8; }
|
|
1335
|
+
.so-item { color:#D1D5DB; }
|
|
1336
|
+
.so-empty { color:#6B7280; font-style:italic; }
|
|
1337
|
+
.log-expand-btn {
|
|
1338
|
+
background:none; border:1px solid #374151; color:#9CA3AF;
|
|
1339
|
+
font-size:.6rem; padding:1px 5px; cursor:pointer; border-radius:3px;
|
|
1340
|
+
margin-left:6px; vertical-align:middle;
|
|
1341
|
+
}
|
|
1342
|
+
.log-expand-btn:hover { border-color:#6B7280; color:#E5E0D8; }
|
|
1343
|
+
.log-code {
|
|
1344
|
+
background:#0D0C0B; border:1px solid #1F2937; color:#9CA3AF;
|
|
1345
|
+
font-size:.63rem; padding:6px 8px; margin-top:4px; border-radius:4px;
|
|
1346
|
+
white-space:pre-wrap; word-break:break-all;
|
|
1347
|
+
max-height:180px; overflow-y:auto;
|
|
1348
|
+
}
|
|
1349
|
+
.log-code.hidden { display:none; }
|
|
1350
|
+
|
|
1329
1351
|
/* Drawer info sections */
|
|
1330
1352
|
.drawer-section { margin-bottom:20px; }
|
|
1331
1353
|
|
|
@@ -2638,13 +2660,166 @@ setInterval(() => {
|
|
|
2638
2660
|
/* ── Detail Drawer ── */
|
|
2639
2661
|
function classifyLogEntry(text) {
|
|
2640
2662
|
if (text.startsWith("[thinking]")) return "thinking";
|
|
2663
|
+
if (text.startsWith("[tool: StructuredOutput]")) return "meta";
|
|
2641
2664
|
if (text.startsWith("[tool:")) return "tool";
|
|
2642
2665
|
if (text.startsWith("[session]")) return "session";
|
|
2643
2666
|
if (text.startsWith("[done]")) return "done";
|
|
2644
|
-
if (text.startsWith("[tool: StructuredOutput]")) return "meta";
|
|
2645
2667
|
return "response";
|
|
2646
2668
|
}
|
|
2647
2669
|
|
|
2670
|
+
function _eid() { return "e" + Math.random().toString(36).slice(2,9); }
|
|
2671
|
+
|
|
2672
|
+
function truncLog(s, n) {
|
|
2673
|
+
s = String(s).replace(/\n/g, " ");
|
|
2674
|
+
return s.length > n ? s.slice(0, n) + "…" : s;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
function toggleExpand(id) {
|
|
2678
|
+
const el = document.getElementById(id);
|
|
2679
|
+
if (!el) return;
|
|
2680
|
+
el.classList.toggle("hidden");
|
|
2681
|
+
const btn = document.querySelector(`[data-expand="${id}"]`);
|
|
2682
|
+
if (btn) btn.textContent = el.classList.contains("hidden") ? "▶ show" : "▼ hide";
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
function fmtExpandable(label, fullText, maxInline) {
|
|
2686
|
+
if (fullText.length <= maxInline) return esc(fullText);
|
|
2687
|
+
const id = _eid();
|
|
2688
|
+
const preview = esc(fullText.slice(0, maxInline)) + "…";
|
|
2689
|
+
return `${preview} <button class="log-expand-btn" data-expand="${id}" onclick="toggleExpand('${id}')">▶ show</button><pre class="log-code hidden" id="${id}">${esc(fullText.slice(0, 3000))}${fullText.length > 3000 ? "\n…" : ""}</pre>`;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
function fmtToolCall(toolName, rawArg) {
|
|
2693
|
+
/* StructuredOutput → pretty key-value */
|
|
2694
|
+
if (toolName === "StructuredOutput") {
|
|
2695
|
+
let parsed = null;
|
|
2696
|
+
try { parsed = JSON.parse(rawArg); } catch {}
|
|
2697
|
+
if (!parsed) return `<span class="tool-name">output</span> ${esc(rawArg.slice(0, 200))}`;
|
|
2698
|
+
const lines = [];
|
|
2699
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
2700
|
+
if (Array.isArray(v)) {
|
|
2701
|
+
if (v.length === 0) lines.push(`<span class="so-key">${esc(k)}:</span> <span class="so-empty">(empty)</span>`);
|
|
2702
|
+
else {
|
|
2703
|
+
lines.push(`<span class="so-key">${esc(k)}:</span>`);
|
|
2704
|
+
v.forEach(item => lines.push(` <span class="so-item">• ${esc(truncLog(String(item), 120))}</span>`));
|
|
2705
|
+
}
|
|
2706
|
+
} else if (v !== null && v !== undefined) {
|
|
2707
|
+
const s = String(v);
|
|
2708
|
+
lines.push(`<span class="so-key">${esc(k)}:</span> <span class="so-val">${esc(truncLog(s, 150))}</span>`);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
return lines.join("\n");
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
/* Try parse JSON args */
|
|
2715
|
+
let parsed = null;
|
|
2716
|
+
try { parsed = JSON.parse(rawArg); } catch {}
|
|
2717
|
+
|
|
2718
|
+
const nameHtml = `<span class="tool-name">${esc(toolName.replace(/^mcp__[^_]+__/, ""))}</span>`;
|
|
2719
|
+
|
|
2720
|
+
if (!parsed) {
|
|
2721
|
+
return `${nameHtml} <span class="tool-arg">${esc(truncLog(rawArg, 150))}</span>`;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/* Pick the most meaningful single field to show inline */
|
|
2725
|
+
const KEY_MAP = {
|
|
2726
|
+
Bash: "command", Read: "file_path", Write: "file_path",
|
|
2727
|
+
Edit: "file_path", Glob: "pattern", Grep: "pattern",
|
|
2728
|
+
WebFetch: "url", WebSearch: "query",
|
|
2729
|
+
TodoWrite: null,
|
|
2730
|
+
};
|
|
2731
|
+
const tn = toolName.replace(/^mcp__[^_]+__/, "");
|
|
2732
|
+
let keyVal = null;
|
|
2733
|
+
if (tn in KEY_MAP && KEY_MAP[tn]) {
|
|
2734
|
+
keyVal = parsed[KEY_MAP[tn]] != null ? String(parsed[KEY_MAP[tn]]) : null;
|
|
2735
|
+
} else if (tn.includes("jira_get_issue")) keyVal = parsed.issue_key || null;
|
|
2736
|
+
else if (tn.includes("jira_add_comment")) keyVal = parsed.issue_key ? `${parsed.issue_key}: ${truncLog(parsed.body || "", 60)}` : null;
|
|
2737
|
+
else if (tn.includes("jira_transition")) keyVal = parsed.issue_key ? `${parsed.issue_key}` : null;
|
|
2738
|
+
else if (tn.includes("jira_search") || tn.includes("confluence_search")) keyVal = parsed.jql || parsed.query || null;
|
|
2739
|
+
else if (tn.includes("jira_update") || tn.includes("jira_create")) keyVal = parsed.issue_key || parsed.summary || null;
|
|
2740
|
+
else if (tn.includes("confluence_get_page") || tn.includes("confluence_update")) keyVal = parsed.page_id ? `page ${parsed.page_id}` : null;
|
|
2741
|
+
else if (tn === "TodoWrite") keyVal = `${Array.isArray(parsed.todos) ? parsed.todos.length : "?"} items`;
|
|
2742
|
+
else if (tn.includes("browser_navigate")) keyVal = parsed.url || null;
|
|
2743
|
+
else if (tn.includes("browser_")) keyVal = parsed.selector || parsed.text || null;
|
|
2744
|
+
else if (tn.includes("gsheets")) keyVal = parsed.spreadsheet_id || parsed.sheet_name || null;
|
|
2745
|
+
|
|
2746
|
+
const fullJson = JSON.stringify(parsed, null, 2);
|
|
2747
|
+
const id = _eid();
|
|
2748
|
+
const keyHtml = keyVal ? ` <span class="tool-key">${esc(truncLog(keyVal, 100))}</span>` : "";
|
|
2749
|
+
const expandHtml = fullJson.length > 120
|
|
2750
|
+
? ` <button class="log-expand-btn" data-expand="${id}" onclick="toggleExpand('${id}')">▶ args</button><pre class="log-code hidden" id="${id}">${esc(fullJson.slice(0, 3000))}${fullJson.length > 3000 ? "\n…" : ""}</pre>`
|
|
2751
|
+
: ` <span class="tool-arg">${esc(fullJson)}</span>`;
|
|
2752
|
+
|
|
2753
|
+
return `${nameHtml}${keyHtml}${keyVal ? "" : expandHtml}${keyVal ? expandHtml : ""}`;
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
function fmtAgentMessage(text) {
|
|
2757
|
+
/* Strip base64 blobs first */
|
|
2758
|
+
const b64re = /"base64":"([A-Za-z0-9+/]{80,}={0,2})"/g;
|
|
2759
|
+
let b64total = 0;
|
|
2760
|
+
const stripped = text.replace(b64re, (_, b) => {
|
|
2761
|
+
b64total += Math.round(b.length * 0.75);
|
|
2762
|
+
return `"base64":"[…${Math.round(b.length * 0.75 / 1024)}kb…]"`;
|
|
2763
|
+
});
|
|
2764
|
+
|
|
2765
|
+
if (b64total > 0) {
|
|
2766
|
+
return `<span class="tool-key">📎 Binary/image data (~${Math.round(b64total/1024)}kb encoded)</span>`;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
/* Large JSON blob → collapse */
|
|
2770
|
+
if (text.length > 250) {
|
|
2771
|
+
let summary = "Agent message";
|
|
2772
|
+
try {
|
|
2773
|
+
const p = JSON.parse(text);
|
|
2774
|
+
const role = p.message?.role || p.type || "";
|
|
2775
|
+
const content = p.message?.content;
|
|
2776
|
+
if (Array.isArray(content)) {
|
|
2777
|
+
const types = [...new Set(content.map(c => c.type || "?"))].join(", ");
|
|
2778
|
+
const first = content.find(c => typeof c.content === "string" && c.content.length > 0);
|
|
2779
|
+
summary = role ? `${role} [${types}]` : `[${types}]`;
|
|
2780
|
+
if (first) summary += `: ${truncLog(first.content, 80)}`;
|
|
2781
|
+
} else if (role) {
|
|
2782
|
+
summary = role;
|
|
2783
|
+
}
|
|
2784
|
+
} catch {}
|
|
2785
|
+
const id = _eid();
|
|
2786
|
+
return `<span class="tool-arg">${esc(summary)}</span> <button class="log-expand-btn" data-expand="${id}" onclick="toggleExpand('${id}')">▶ raw</button><pre class="log-code hidden" id="${id}">${esc(text.slice(0, 3000))}${text.length > 3000 ? "\n…" : ""}</pre>`;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
return esc(text);
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
function formatLogContent(text) {
|
|
2793
|
+
/* [done] success duration=Xms cost=$Y */
|
|
2794
|
+
const doneM = text.match(/^\[done\]\s+(\w+)\s+duration=(\d+)ms(?:\s+cost=\$?([\d.]+))?/);
|
|
2795
|
+
if (doneM) {
|
|
2796
|
+
const secs = (parseInt(doneM[2]) / 1000).toFixed(1);
|
|
2797
|
+
const cost = doneM[3] ? ` · $${doneM[3]}` : "";
|
|
2798
|
+
return `${esc(doneM[1])} — ${secs}s${esc(cost)}`;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
/* [session] model=... */
|
|
2802
|
+
if (text.startsWith("[session]")) {
|
|
2803
|
+
return `<span style="opacity:.35">${esc(text.replace(/^\[session\]\s*/, ""))}</span>`;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
/* [tool: NAME] args */
|
|
2807
|
+
const toolM = text.match(/^\[tool: ([^\]]+)\]\s*([\s\S]*)/);
|
|
2808
|
+
if (toolM) return fmtToolCall(toolM[1], toolM[2].trim());
|
|
2809
|
+
|
|
2810
|
+
/* [thinking] text */
|
|
2811
|
+
if (text.startsWith("[thinking]")) {
|
|
2812
|
+
const body = text.replace(/^\[thinking\]\s*/, "");
|
|
2813
|
+
return fmtExpandable("thinking", body, 200);
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
/* Agent message JSON */
|
|
2817
|
+
if (text.startsWith('{"type":')) return fmtAgentMessage(text);
|
|
2818
|
+
|
|
2819
|
+
/* Plain text — collapse if very long */
|
|
2820
|
+
return fmtExpandable("text", text, 300);
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2648
2823
|
function setLogFilter(filter) {
|
|
2649
2824
|
currentLogFilter = filter;
|
|
2650
2825
|
document.querySelectorAll(".log-filter-btn[data-filter]").forEach(btn => {
|
|
@@ -2813,14 +2988,12 @@ function renderIssueLogLine(line) {
|
|
|
2813
2988
|
const phase = phaseMatch ? phaseMatch[1] : "";
|
|
2814
2989
|
const text = line.replace(/^\[[^\]]+\] (?:\[[^\]]+\] )?/, "");
|
|
2815
2990
|
const type = classifyLogEntry(text);
|
|
2816
|
-
const
|
|
2817
|
-
.replace(/^\[thinking\] /, "")
|
|
2818
|
-
.replace(/^\[tool: ([^\]]+)\] /, (_, name) => name === "StructuredOutput" ? "[output] " : `${name}: `);
|
|
2991
|
+
const content = formatLogContent(text);
|
|
2819
2992
|
return `<div class="log-entry type-${type}" data-type="${type}">
|
|
2820
2993
|
<div class="log-entry-ts">${esc(ts)}</div>
|
|
2821
2994
|
<div class="log-entry-phase phase-${esc(phase)}">${esc(phase)}</div>
|
|
2822
2995
|
<div class="log-entry-sep">|</div>
|
|
2823
|
-
<div class="log-entry-content">${
|
|
2996
|
+
<div class="log-entry-content">${content}</div>
|
|
2824
2997
|
</div>`;
|
|
2825
2998
|
}
|
|
2826
2999
|
|