@cephalization/phoenix-insight 0.3.0 → 1.0.0

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.
Files changed (57) hide show
  1. package/README.md +195 -1
  2. package/dist/agent/index.js +9 -4
  3. package/dist/cli.js +172 -0
  4. package/dist/commands/index.js +1 -0
  5. package/dist/commands/report-tool.js +239 -0
  6. package/dist/config/schema.js +2 -2
  7. package/dist/modes/local.js +7 -0
  8. package/dist/modes/sandbox.js +8 -0
  9. package/dist/prompts/index.js +1 -1
  10. package/dist/prompts/system.js +10 -3
  11. package/dist/server/session.js +357 -0
  12. package/dist/server/ui.js +232 -0
  13. package/dist/server/websocket.js +212 -0
  14. package/dist/snapshot/spans.js +28 -4
  15. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  16. package/dist/ui/assets/code-block-F6WJLWQG-BTdTzfvl.js +154 -0
  17. package/dist/ui/assets/code-block-F6WJLWQG-BTdTzfvl.js.map +1 -0
  18. package/dist/ui/assets/index-CX8aDatf.css +1 -0
  19. package/dist/ui/assets/index-DjZuAW6Y.js +63 -0
  20. package/dist/ui/assets/index-DjZuAW6Y.js.map +1 -0
  21. package/dist/ui/assets/vendor-data-r1ZEkUds.js +40 -0
  22. package/dist/ui/assets/vendor-data-r1ZEkUds.js.map +1 -0
  23. package/dist/ui/assets/vendor-react-Cgg2GOmP.js +2 -0
  24. package/dist/ui/assets/vendor-react-Cgg2GOmP.js.map +1 -0
  25. package/dist/ui/assets/vendor-render-DoMl5bum.js +381 -0
  26. package/dist/ui/assets/vendor-render-DoMl5bum.js.map +1 -0
  27. package/dist/ui/assets/vendor-ui-Cg-YC4hK.js +46 -0
  28. package/dist/ui/assets/vendor-ui-Cg-YC4hK.js.map +1 -0
  29. package/dist/ui/index.html +18 -0
  30. package/dist/ui/vite.svg +1 -0
  31. package/package.json +13 -14
  32. package/src/agent/index.ts +0 -323
  33. package/src/cli.ts +0 -854
  34. package/src/commands/index.ts +0 -8
  35. package/src/commands/px-fetch-more-spans.ts +0 -174
  36. package/src/commands/px-fetch-more-trace.ts +0 -183
  37. package/src/config/index.ts +0 -225
  38. package/src/config/loader.ts +0 -173
  39. package/src/config/schema.ts +0 -66
  40. package/src/index.ts +0 -1
  41. package/src/modes/index.ts +0 -21
  42. package/src/modes/local.ts +0 -163
  43. package/src/modes/sandbox.ts +0 -144
  44. package/src/modes/types.ts +0 -31
  45. package/src/observability/index.ts +0 -90
  46. package/src/progress.ts +0 -239
  47. package/src/prompts/index.ts +0 -1
  48. package/src/prompts/system.ts +0 -31
  49. package/src/snapshot/client.ts +0 -129
  50. package/src/snapshot/context.ts +0 -587
  51. package/src/snapshot/datasets.ts +0 -132
  52. package/src/snapshot/experiments.ts +0 -246
  53. package/src/snapshot/index.ts +0 -403
  54. package/src/snapshot/projects.ts +0 -58
  55. package/src/snapshot/prompts.ts +0 -267
  56. package/src/snapshot/spans.ts +0 -142
  57. package/src/snapshot/utils.ts +0 -140
package/README.md CHANGED
@@ -148,10 +148,33 @@ phoenix-insight --refresh "show me the latest errors"
148
148
  phoenix-insight --no-stream "generate report" > report.txt
149
149
  ```
150
150
 
151
+ ### Web UI
152
+
153
+ Start the web-based UI for a visual chat interface and structured report display:
154
+
155
+ ```bash
156
+ # Start web UI (opens browser automatically)
157
+ phoenix-insight ui
158
+
159
+ # Start on a custom port
160
+ phoenix-insight ui --port 8080
161
+
162
+ # Start without opening browser
163
+ phoenix-insight ui --no-open
164
+ ```
165
+
166
+ The web UI provides:
167
+
168
+ - **Split-pane interface**: Chat panel on the left, report display on the right
169
+ - **Real-time streaming**: See agent responses and tool usage as they happen
170
+ - **Structured reports**: AI-generated reports with tables, metrics, and formatted content
171
+ - **Session history**: Persist and browse previous conversations and reports
172
+ - **WebSocket connection**: Real-time bidirectional communication with the agent
173
+
151
174
  ### Observability
152
175
 
153
176
  ```bash
154
- # Enable tracing of agent operations to Phoenix
177
+ # Enable tracing of agent operations to Phoenix (enabled by default)
155
178
  phoenix-insight --trace "analyze performance"
156
179
  ```
157
180
 
@@ -398,6 +421,134 @@ phoenix-insight prune [options]
398
421
  | ----------- | ---------------------------------------- | ------- | ------------------------------- |
399
422
  | `--dry-run` | Preview what would be deleted without actually deleting | `false` | `phoenix-insight prune --dry-run` |
400
423
 
424
+ ### UI Command
425
+
426
+ Starts a web-based UI for visual interaction with the Phoenix Insight agent.
427
+
428
+ ```bash
429
+ phoenix-insight ui [options]
430
+ ```
431
+
432
+ | Option | Description | Default | Example |
433
+ | --------------- | ------------------------------------------------ | ------- | -------------------------------- |
434
+ | `--port <n>` | Port to run the UI server on | `6007` | `phoenix-insight ui --port 8080` |
435
+ | `--no-open` | Do not automatically open browser | `false` | `phoenix-insight ui --no-open` |
436
+
437
+ The UI server provides:
438
+
439
+ - **HTTP server**: Serves the web UI on `http://localhost:6007` (configurable port)
440
+ - **WebSocket**: Real-time bidirectional communication at `/ws`
441
+ - **Session management**: Multiple concurrent sessions with conversation history
442
+ - **Report generation**: Agent can generate structured reports displayed in the UI
443
+
444
+ **Usage notes:**
445
+
446
+ - The server binds to localhost only (127.0.0.1) for security
447
+ - Press Ctrl+C to stop the server gracefully
448
+ - Configuration is loaded from the standard config file and environment variables
449
+
450
+ #### Web UI Features
451
+
452
+ The web UI provides a rich interface for interacting with Phoenix Insight:
453
+
454
+ **Split-pane Layout:**
455
+ - Left panel: Chat interface with message history
456
+ - Right panel: Structured report display
457
+ - Resizable panels with drag handle
458
+ - Responsive design: stacked tabs on mobile devices
459
+
460
+ **Chat Interface:**
461
+ - Real-time streaming of agent responses
462
+ - Markdown rendering optimized for streaming content
463
+ - Session history with dropdown to switch between conversations
464
+ - Create, switch, and delete sessions
465
+ - Visual indicators for agent tool usage (tool_call/tool_result events)
466
+
467
+ **Report Panel:**
468
+ - Structured reports generated by the agent using `generate_report` tool
469
+ - Components: Cards, Tables, Metrics, Alerts, Badges, Lists, Code blocks, and more
470
+ - Download reports as Markdown
471
+ - Browse and restore previous reports from history
472
+
473
+ **Connection Management:**
474
+ - Automatic reconnection with exponential backoff (via partysocket)
475
+ - Visual connection status indicator (green/yellow/red)
476
+ - Toast notifications for connection state changes
477
+ - Message buffering during temporary disconnections
478
+
479
+ **Data Persistence:**
480
+ - Sessions and reports stored in browser's IndexedDB
481
+ - Survives page refreshes and browser restarts
482
+ - Export reports as Markdown files
483
+
484
+ #### WebSocket Protocol
485
+
486
+ The UI communicates with the CLI server over WebSocket at the `/ws` endpoint. All messages are JSON-encoded.
487
+
488
+ **Client Messages (UI to Server):**
489
+
490
+ ```typescript
491
+ // Send a query to the agent
492
+ { type: "query", payload: { content: string, sessionId?: string } }
493
+
494
+ // Cancel the current query
495
+ { type: "cancel", payload: { sessionId?: string } }
496
+ ```
497
+
498
+ **Server Messages (Server to UI):**
499
+
500
+ ```typescript
501
+ // Streaming text content from the agent
502
+ { type: "text", payload: { content: string, sessionId: string } }
503
+
504
+ // Agent is calling a tool
505
+ { type: "tool_call", payload: { toolName: string, args: unknown, sessionId: string } }
506
+
507
+ // Tool execution result
508
+ { type: "tool_result", payload: { toolName: string, result: unknown, sessionId: string } }
509
+
510
+ // Agent generated a structured report
511
+ { type: "report", payload: { content: UITree, sessionId: string } }
512
+
513
+ // Error occurred during processing
514
+ { type: "error", payload: { message: string, sessionId?: string } }
515
+
516
+ // Agent finished processing the query
517
+ { type: "done", payload: { sessionId: string } }
518
+ ```
519
+
520
+ **Report Content Structure (UITree):**
521
+
522
+ The `report` message contains a `UITree` structure compatible with [json-render](https://github.com/vercel-labs/json-render):
523
+
524
+ ```typescript
525
+ interface UITree {
526
+ root: string; // Key of the root element
527
+ elements: Record<string, UIElement>; // Flat map of all elements
528
+ }
529
+
530
+ interface UIElement {
531
+ key: string;
532
+ type: "Card" | "Text" | "Heading" | "List" | "Table" | "Metric" | "Badge" | "Alert" | "Separator" | "Code";
533
+ props: Record<string, unknown>; // Component-specific props
534
+ children?: string[]; // Keys of child elements
535
+ parentKey?: string;
536
+ }
537
+ ```
538
+
539
+ **Example WebSocket Session:**
540
+
541
+ ```
542
+ Client: {"type":"query","payload":{"content":"Show error patterns","sessionId":"session-1"}}
543
+ Server: {"type":"text","payload":{"content":"I'll analyze","sessionId":"session-1"}}
544
+ Server: {"type":"text","payload":{"content":" the error patterns...","sessionId":"session-1"}}
545
+ Server: {"type":"tool_call","payload":{"toolName":"bash","args":{"command":"grep -c 'error' spans.jsonl"},"sessionId":"session-1"}}
546
+ Server: {"type":"tool_result","payload":{"toolName":"bash","result":"42","sessionId":"session-1"}}
547
+ Server: {"type":"text","payload":{"content":"Found 42 errors.","sessionId":"session-1"}}
548
+ Server: {"type":"report","payload":{"content":{"root":"card-1","elements":{...}},"sessionId":"session-1"}}
549
+ Server: {"type":"done","payload":{"sessionId":"session-1"}}
550
+ ```
551
+
401
552
  ### Help Command
402
553
 
403
554
  Displays help information and available options.
@@ -691,6 +842,49 @@ pnpm test test/modes/sandbox.test.ts
691
842
  pnpm typecheck
692
843
  ```
693
844
 
845
+ #### UI Integration Testing
846
+
847
+ The web UI has manual integration tests using [agent-browser](https://github.com/vercel-labs/agent-browser) for browser automation. These tests are NOT run in CI because they require a live Phoenix server with data.
848
+
849
+ **Prerequisites:**
850
+
851
+ 1. Phoenix server running on `localhost:6006` with data
852
+ 2. Run from the monorepo root
853
+
854
+ **Running UI tests:**
855
+
856
+ ```bash
857
+ # From monorepo root - builds packages and runs UI integration tests
858
+ pnpm test:ui
859
+ ```
860
+
861
+ The test:ui script will:
862
+ 1. Build the UI package
863
+ 2. Build the CLI package (which bundles the UI)
864
+ 3. Expect a running Phoenix server at localhost:6006
865
+ 4. Expect the UI server to be running at localhost:6007 (start with `phoenix-insight ui`)
866
+ 5. Run browser automation tests that verify:
867
+ - Layout renders correctly (chat panel, report panel)
868
+ - Chat input is functional
869
+ - WebSocket connection works
870
+ - Session management works
871
+ - Report panel displays correctly
872
+
873
+ **Manual testing workflow:**
874
+
875
+ ```bash
876
+ # Terminal 1: Start Phoenix
877
+ phoenix serve
878
+
879
+ # Terminal 2: Start the UI server
880
+ cd packages/cli && pnpm dev ui
881
+
882
+ # Terminal 3: Run UI tests
883
+ pnpm test:ui
884
+ ```
885
+
886
+ **Note:** If tests skip with "Phoenix server not running" or "UI server not running", ensure both servers are accessible before running tests.
887
+
694
888
  ### Contributing & Releases
695
889
 
696
890
  Contributions are welcome! This project uses [changesets](https://github.com/changesets/changesets) for version management and automated releases.
@@ -4,9 +4,8 @@
4
4
  import { generateText, streamText, tool, stepCountIs, } from "ai";
5
5
  import { anthropic } from "@ai-sdk/anthropic";
6
6
  import { z } from "zod";
7
- import { INSIGHT_SYSTEM_PROMPT } from "../prompts/system.js";
7
+ import { getInsightSystemPrompt } from "../prompts/system.js";
8
8
  import { fetchMoreSpans, fetchMoreTrace, } from "../commands/index.js";
9
- import { PhoenixClientError } from "../snapshot/client.js";
10
9
  /**
11
10
  * Phoenix Insight Agent
12
11
  */
@@ -15,11 +14,16 @@ export class PhoenixInsightAgent {
15
14
  client;
16
15
  maxSteps;
17
16
  tools = null;
17
+ additionalTools;
18
18
  model = anthropic("claude-sonnet-4-5");
19
+ systemPrompt;
19
20
  constructor(config) {
20
21
  this.mode = config.mode;
21
22
  this.client = config.client;
22
23
  this.maxSteps = config.maxSteps || 25;
24
+ this.additionalTools = config.additionalTools || {};
25
+ // Generate the system prompt with the snapshot root path from the mode
26
+ this.systemPrompt = getInsightSystemPrompt(this.mode.getSnapshotRoot());
23
27
  }
24
28
  /**
25
29
  * Initialize the agent tools
@@ -103,6 +107,7 @@ export class PhoenixInsightAgent {
103
107
  bash: bashTool,
104
108
  px_fetch_more_spans: pxFetchMoreSpans,
105
109
  px_fetch_more_trace: pxFetchMoreTrace,
110
+ ...this.additionalTools,
106
111
  };
107
112
  return this.tools;
108
113
  }
@@ -120,7 +125,7 @@ export class PhoenixInsightAgent {
120
125
  try {
121
126
  const result = await generateText({
122
127
  model: this.model,
123
- system: INSIGHT_SYSTEM_PROMPT,
128
+ system: this.systemPrompt,
124
129
  prompt: userQuery,
125
130
  tools,
126
131
  stopWhen: stepCountIs(this.maxSteps),
@@ -162,7 +167,7 @@ export class PhoenixInsightAgent {
162
167
  try {
163
168
  const result = streamText({
164
169
  model: this.model,
165
- system: INSIGHT_SYSTEM_PROMPT,
170
+ system: this.systemPrompt,
166
171
  prompt: userQuery,
167
172
  tools,
168
173
  stopWhen: stepCountIs(this.maxSteps),
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import * as readline from "node:readline";
4
4
  import * as fs from "node:fs/promises";
5
5
  import * as path from "node:path";
6
6
  import * as os from "node:os";
7
+ import { exec } from "node:child_process";
7
8
  import { createSandboxMode, createLocalMode } from "./modes/index.js";
8
9
  import { createInsightAgent, runOneShotQuery } from "./agent/index.js";
9
10
  import { createSnapshot, createIncrementalSnapshot, createPhoenixClient, PhoenixClientError, } from "./snapshot/index.js";
@@ -11,6 +12,9 @@ import { getLatestSnapshot, listSnapshots } from "./snapshot/utils.js";
11
12
  import { AgentProgress } from "./progress.js";
12
13
  import { initializeObservability, shutdownObservability, } from "./observability/index.js";
13
14
  import { initializeConfig, getConfig } from "./config/index.js";
15
+ import { createUIServer } from "./server/ui.js";
16
+ import { createWebSocketServer } from "./server/websocket.js";
17
+ import { createSessionManager } from "./server/session.js";
14
18
  // Version will be read from package.json during build
15
19
  const VERSION = "0.0.1";
16
20
  const program = new Command();
@@ -164,6 +168,9 @@ Examples:
164
168
  $ phoenix-insight --local "Show me error patterns" # Local mode with persistence
165
169
  $ phoenix-insight --local --stream "Analyze recent experiments" # Local mode with streaming
166
170
  $ phoenix-insight --config ./my-config.json "Analyze traces" # Use custom config file
171
+ $ phoenix-insight ui # Start web UI on localhost:6007
172
+ $ phoenix-insight ui --port 8080 # Start web UI on custom port
173
+ $ phoenix-insight ui --no-open # Start web UI without opening browser
167
174
  $ phoenix-insight help # Show this help message
168
175
  `)
169
176
  .hook("preAction", async (thisCommand) => {
@@ -324,6 +331,15 @@ program
324
331
  process.exit(1);
325
332
  }
326
333
  });
334
+ // UI Command - starts web-based UI server
335
+ program
336
+ .command("ui")
337
+ .description("Start the web-based UI for interactive Phoenix analysis")
338
+ .option("--port <number>", "Port to run the UI server on", parseInt)
339
+ .option("--no-open", "Do not automatically open the browser")
340
+ .action(async (options) => {
341
+ await runUIServer(options);
342
+ });
327
343
  program
328
344
  .argument("[query]", "Query to run against Phoenix data")
329
345
  .option("--sandbox", "Run in sandbox mode with in-memory filesystem (default)")
@@ -474,6 +490,162 @@ program
474
490
  await shutdownObservability();
475
491
  }
476
492
  });
493
+ /**
494
+ * Open a URL in the default browser
495
+ */
496
+ function openBrowser(url) {
497
+ const platform = process.platform;
498
+ let command;
499
+ if (platform === "darwin") {
500
+ command = `open "${url}"`;
501
+ }
502
+ else if (platform === "win32") {
503
+ command = `start "" "${url}"`;
504
+ }
505
+ else {
506
+ // Linux and others - try xdg-open, fallback to sensible-browser
507
+ command = `xdg-open "${url}" || sensible-browser "${url}"`;
508
+ }
509
+ exec(command, (error) => {
510
+ if (error && process.env.DEBUG) {
511
+ console.warn(`Could not open browser: ${error.message}`);
512
+ }
513
+ });
514
+ }
515
+ /**
516
+ * Run the UI server with WebSocket support for agent interaction
517
+ */
518
+ async function runUIServer(options) {
519
+ const config = getConfig();
520
+ const port = options.port ?? 6007;
521
+ const shouldOpen = options.open !== false; // Default to opening browser
522
+ console.log("šŸš€ Starting Phoenix Insight UI...\n");
523
+ // Initialize observability if trace is enabled in config
524
+ if (config.trace) {
525
+ initializeObservability({
526
+ enabled: true,
527
+ baseUrl: config.baseUrl,
528
+ apiKey: config.apiKey,
529
+ projectName: "phoenix-insight-ui",
530
+ debug: !!process.env.DEBUG,
531
+ });
532
+ }
533
+ try {
534
+ // Determine the execution mode - UI always uses local mode for persistence
535
+ const mode = await createLocalMode();
536
+ // Create Phoenix client
537
+ const client = createPhoenixClient({
538
+ baseURL: config.baseUrl,
539
+ apiKey: config.apiKey,
540
+ });
541
+ // Create snapshot with config values
542
+ const snapshotOptions = {
543
+ baseURL: config.baseUrl,
544
+ apiKey: config.apiKey,
545
+ spansPerProject: config.limit,
546
+ showProgress: true,
547
+ };
548
+ // Always use incremental snapshot for UI to reuse existing data
549
+ await createIncrementalSnapshot(mode, snapshotOptions);
550
+ console.log("\nāœ… Snapshot ready.\n");
551
+ // Create the UI HTTP server
552
+ const uiServer = await createUIServer({ port, host: "127.0.0.1" });
553
+ // Create the WebSocket server and attach to HTTP server
554
+ const sessionManager = createSessionManager({
555
+ mode,
556
+ client,
557
+ maxSteps: 25,
558
+ });
559
+ const wsServer = createWebSocketServer(uiServer.httpServer, {
560
+ path: "/ws",
561
+ onConnection: (ws) => {
562
+ if (process.env.DEBUG) {
563
+ console.log("WebSocket client connected");
564
+ }
565
+ },
566
+ onDisconnection: async (ws, code, reason) => {
567
+ if (process.env.DEBUG) {
568
+ console.log(`WebSocket client disconnected: ${code} ${reason}`);
569
+ }
570
+ // Clean up the session when client disconnects
571
+ await sessionManager.removeSession(ws);
572
+ },
573
+ onMessage: async (message, ws) => {
574
+ if (message.type === "query") {
575
+ const { content, sessionId: clientSessionId } = message.payload;
576
+ const sessionId = clientSessionId ?? `session-${Date.now()}`;
577
+ // Get or create session for this client
578
+ const session = sessionManager.getOrCreateSession(ws, sessionId, (msg) => wsServer.sendToClient(ws, msg));
579
+ // Execute the query (this is async but we don't await - let it stream)
580
+ session.executeQuery(content).catch((error) => {
581
+ console.error("Error executing query:", error);
582
+ wsServer.sendToClient(ws, {
583
+ type: "error",
584
+ payload: {
585
+ message: error instanceof Error
586
+ ? error.message
587
+ : "An error occurred while executing the query",
588
+ sessionId,
589
+ },
590
+ });
591
+ });
592
+ }
593
+ else if (message.type === "cancel") {
594
+ const session = sessionManager.getSessionForClient(ws);
595
+ if (session) {
596
+ session.cancel();
597
+ }
598
+ }
599
+ },
600
+ onError: (error, ws) => {
601
+ console.error("WebSocket error:", error.message);
602
+ },
603
+ });
604
+ const url = `http://localhost:${uiServer.port}`;
605
+ console.log("🌐 Phoenix Insight UI is running!");
606
+ console.log(` Local: ${url}`);
607
+ console.log("\nšŸ’” Press Ctrl+C to stop the server\n");
608
+ // Open browser if not disabled
609
+ if (shouldOpen) {
610
+ openBrowser(url);
611
+ }
612
+ // Handle graceful shutdown
613
+ let isShuttingDown = false;
614
+ const shutdown = async (signal) => {
615
+ if (isShuttingDown)
616
+ return;
617
+ isShuttingDown = true;
618
+ console.log(`\n\nšŸ“„ Received ${signal}, shutting down gracefully...`);
619
+ try {
620
+ // Close WebSocket connections first
621
+ await wsServer.close();
622
+ // Close the UI server
623
+ await uiServer.close();
624
+ // Clean up sessions
625
+ await sessionManager.cleanup();
626
+ // Clean up execution mode
627
+ await mode.cleanup();
628
+ // Shutdown observability if enabled
629
+ await shutdownObservability();
630
+ console.log("šŸ‘‹ Server stopped. Goodbye!");
631
+ process.exit(0);
632
+ }
633
+ catch (error) {
634
+ console.error("Error during shutdown:", error);
635
+ process.exit(1);
636
+ }
637
+ };
638
+ process.on("SIGINT", () => shutdown("SIGINT"));
639
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
640
+ // Keep the process running
641
+ await new Promise(() => {
642
+ // This promise never resolves - server runs until SIGINT/SIGTERM
643
+ });
644
+ }
645
+ catch (error) {
646
+ handleError(error, "starting UI server");
647
+ }
648
+ }
477
649
  async function runInteractiveMode() {
478
650
  const config = getConfig();
479
651
  console.log("šŸš€ Phoenix Insight Interactive Mode");
@@ -1,2 +1,3 @@
1
1
  export { fetchMoreSpans, } from "./px-fetch-more-spans.js";
2
2
  export { fetchMoreTrace, } from "./px-fetch-more-trace.js";
3
+ export { createReportTool, validateReportContent, generateComponentDocs, } from "./report-tool.js";