@townco/debugger 0.1.2 → 0.1.4

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": "@townco/debugger",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3.0"
@@ -14,7 +14,7 @@
14
14
  "check": "tsc --noEmit"
15
15
  },
16
16
  "dependencies": {
17
- "@townco/otlp-server": "0.1.2",
17
+ "@townco/otlp-server": "0.1.4",
18
18
  "@radix-ui/react-label": "^2.1.7",
19
19
  "@radix-ui/react-select": "^2.2.6",
20
20
  "@radix-ui/react-slot": "^1.2.3",
@@ -27,7 +27,7 @@
27
27
  "tailwind-merge": "^3.3.1"
28
28
  },
29
29
  "devDependencies": {
30
- "@townco/tsconfig": "0.1.44",
30
+ "@townco/tsconfig": "0.1.46",
31
31
  "@types/bun": "latest",
32
32
  "@types/react": "^19",
33
33
  "@types/react-dom": "^19",
@@ -1,14 +1,34 @@
1
1
  interface AttributeViewerProps {
2
2
  label: string;
3
3
  data: string | null;
4
+ excludeKeys?: string[];
4
5
  }
5
6
 
6
- export function AttributeViewer({ label, data }: AttributeViewerProps) {
7
+ export function AttributeViewer({
8
+ label,
9
+ data,
10
+ excludeKeys = [],
11
+ }: AttributeViewerProps) {
7
12
  if (!data || data === "{}" || data === "[]") return null;
8
13
 
9
14
  let parsed: unknown;
10
15
  try {
11
16
  parsed = JSON.parse(data);
17
+
18
+ // Filter out excluded keys
19
+ if (
20
+ excludeKeys.length > 0 &&
21
+ typeof parsed === "object" &&
22
+ parsed !== null
23
+ ) {
24
+ const filtered = { ...parsed } as Record<string, unknown>;
25
+ for (const key of excludeKeys) {
26
+ delete filtered[key];
27
+ }
28
+ // Return null if no keys left after filtering
29
+ if (Object.keys(filtered).length === 0) return null;
30
+ parsed = filtered;
31
+ }
12
32
  } catch {
13
33
  parsed = data;
14
34
  }
@@ -2,6 +2,31 @@ import { useState } from "react";
2
2
  import type { Log } from "../types";
3
3
  import { AttributeViewer } from "./AttributeViewer";
4
4
 
5
+ function LogMetadataViewer({ attributes }: { attributes: string | null }) {
6
+ if (!attributes) return null;
7
+
8
+ try {
9
+ const parsed = JSON.parse(attributes);
10
+ const metadataStr = parsed["log.metadata"];
11
+ if (!metadataStr) return null;
12
+
13
+ const metadata = JSON.parse(metadataStr);
14
+
15
+ return (
16
+ <div className="mb-2">
17
+ <div className="text-xs text-muted-foreground uppercase font-medium">
18
+ Metadata
19
+ </div>
20
+ <pre className="text-xs bg-muted p-2 rounded overflow-x-auto whitespace-pre-wrap">
21
+ {JSON.stringify(metadata, null, 2)}
22
+ </pre>
23
+ </div>
24
+ );
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
5
30
  function getSeverityColor(severityNumber: number): string {
6
31
  if (severityNumber >= 17) return "text-red-500"; // ERROR/FATAL
7
32
  if (severityNumber >= 13) return "text-yellow-500"; // WARN
@@ -40,7 +65,12 @@ function LogRow({ log }: { log: Log }) {
40
65
  {expanded && (
41
66
  <div className="py-2 px-4 bg-muted/50 rounded mb-1 ml-14">
42
67
  <AttributeViewer label="Body" data={log.body} />
43
- <AttributeViewer label="Attributes" data={log.attributes} />
68
+ <LogMetadataViewer attributes={log.attributes} />
69
+ <AttributeViewer
70
+ label="Attributes"
71
+ data={log.attributes}
72
+ excludeKeys={["log.metadata"]}
73
+ />
44
74
  <AttributeViewer label="Resource" data={log.resource_attributes} />
45
75
  {log.span_id && (
46
76
  <div className="text-xs text-muted-foreground">
@@ -57,9 +57,28 @@ function SpanRow({ span }: { span: SpanNode }) {
57
57
  span.name.startsWith("chat") && "gen_ai.input.messages" in attrs;
58
58
  const isSpecialSpan = isToolCall || isChatSpan;
59
59
 
60
+ // Helper to get display name for tool calls (with special handling for Task/subagent)
61
+ const getToolDisplayName = (): string => {
62
+ const toolName = attrs["tool.name"] as string;
63
+ if (toolName !== "Task") return toolName || span.name;
64
+
65
+ // Parse tool.input to extract agentName for subagent spans
66
+ try {
67
+ const toolInput = attrs["tool.input"];
68
+ const input =
69
+ typeof toolInput === "string" ? JSON.parse(toolInput) : toolInput;
70
+ if (input?.agentName) {
71
+ return `Subagent (${input.agentName})`;
72
+ }
73
+ } catch {
74
+ // Fall back to "Task" if parsing fails
75
+ }
76
+ return toolName || span.name;
77
+ };
78
+
60
79
  // Get display name based on span type
61
80
  const displayName = isToolCall
62
- ? (attrs["tool.name"] as string) || span.name
81
+ ? getToolDisplayName()
63
82
  : isChatSpan
64
83
  ? (attrs["gen_ai.request.model"] as string) || span.name
65
84
  : span.name;