@brainpilot/web 0.0.4 → 0.0.5

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 (97) hide show
  1. package/dist/assets/index-C-8G4D4j.js +448 -0
  2. package/dist/assets/index-C501m5OS.css +1 -0
  3. package/dist/index.html +2 -2
  4. package/index.html +13 -0
  5. package/package.json +9 -3
  6. package/src/App.tsx +10 -0
  7. package/src/__tests__/api.test.ts +103 -0
  8. package/src/__tests__/messageGroups.test.ts +80 -0
  9. package/src/__tests__/newUiComponents.test.tsx +101 -0
  10. package/src/__tests__/newUiEvents.test.ts +236 -0
  11. package/src/components/chat/AskUserCard.tsx +123 -0
  12. package/src/components/chat/AutoRetryIndicator.tsx +71 -0
  13. package/src/components/chat/ComposerInput.tsx +73 -0
  14. package/src/components/chat/ComposerSendButton.tsx +26 -0
  15. package/src/components/chat/MarkdownMessage.tsx +24 -0
  16. package/src/components/chat/MessageStream.tsx +464 -0
  17. package/src/components/chat/PromptComposer.tsx +398 -0
  18. package/src/components/chat/SystemMessageBubble.tsx +46 -0
  19. package/src/components/demo/DemoFileTree.tsx +146 -0
  20. package/src/components/demo/DemoView.tsx +668 -0
  21. package/src/components/demo/TraceNodeModal.tsx +76 -0
  22. package/src/components/demo/demoBundle.ts +218 -0
  23. package/src/components/demo/demoCache.ts +42 -0
  24. package/src/components/files/FilePreviewView.tsx +153 -0
  25. package/src/components/files/FileSidebar.tsx +664 -0
  26. package/src/components/files/filePreview.ts +113 -0
  27. package/src/components/primitives/CustomSelect.tsx +200 -0
  28. package/src/components/primitives/IconButton.tsx +27 -0
  29. package/src/components/quota/DiskQuotaCriticalDialog.tsx +56 -0
  30. package/src/components/quota/DiskQuotaWarningDialog.tsx +65 -0
  31. package/src/components/quota/QuotaFileManager.tsx +197 -0
  32. package/src/components/search/SearchDialog.tsx +101 -0
  33. package/src/components/session/AgentNetwork.tsx +1240 -0
  34. package/src/components/session/AgentTraceViews.tsx +381 -0
  35. package/src/components/session/AnalyticsTab.tsx +386 -0
  36. package/src/components/session/GlobalOverview.tsx +108 -0
  37. package/src/components/session/NodeTooltip.tsx +127 -0
  38. package/src/components/session/TimelineTab.tsx +320 -0
  39. package/src/components/session/TraceGraphView.tsx +301 -0
  40. package/src/components/session/TraceNodeDetail.tsx +142 -0
  41. package/src/components/session/agentAnalytics.ts +397 -0
  42. package/src/components/session/agentNetworkShared.ts +329 -0
  43. package/src/components/session/traceLayout.ts +150 -0
  44. package/src/components/settings/SettingsDialog.tsx +719 -0
  45. package/src/components/shell/DesktopShell.tsx +236 -0
  46. package/src/components/shell/SandboxBuildingOverlay.tsx +73 -0
  47. package/src/components/shell/SandboxStatus.tsx +287 -0
  48. package/src/components/shell/TerminalDrawer.tsx +387 -0
  49. package/src/components/sidebar/Sidebar.tsx +187 -0
  50. package/src/config.ts +10 -0
  51. package/src/contexts/AppProviders.tsx +20 -0
  52. package/src/contexts/AuthContext.tsx +61 -0
  53. package/src/contexts/PreferencesContext.tsx +125 -0
  54. package/src/contexts/SSEContext.tsx +175 -0
  55. package/src/contexts/SandboxContext.tsx +310 -0
  56. package/src/contexts/SessionContext.tsx +608 -0
  57. package/src/contexts/draftStore.ts +103 -0
  58. package/src/contexts/messageFilters.ts +29 -0
  59. package/src/contexts/messageGroups.ts +77 -0
  60. package/src/contexts/messageReducer.ts +401 -0
  61. package/src/contexts/newUiEvents.ts +190 -0
  62. package/src/contracts/backend.ts +846 -0
  63. package/src/contracts/demoBundle.ts +83 -0
  64. package/src/i18n/messages/analytics.ts +96 -0
  65. package/src/i18n/messages/chat.ts +108 -0
  66. package/src/i18n/messages/contexts.ts +40 -0
  67. package/src/i18n/messages/demo.ts +80 -0
  68. package/src/i18n/messages/files.ts +82 -0
  69. package/src/i18n/messages/network.ts +186 -0
  70. package/src/i18n/messages/profile.ts +40 -0
  71. package/src/i18n/messages/quota.ts +36 -0
  72. package/src/i18n/messages/sandbox.ts +116 -0
  73. package/src/i18n/messages/search.ts +16 -0
  74. package/src/i18n/messages/settings.ts +184 -0
  75. package/src/i18n/messages/shell.ts +38 -0
  76. package/src/i18n/messages/sidebar.ts +52 -0
  77. package/src/i18n/messages/terminal.ts +22 -0
  78. package/src/i18n/messages/trace.ts +84 -0
  79. package/src/i18n/messages.ts +32 -0
  80. package/src/i18n/translate.ts +46 -0
  81. package/src/i18n/types.ts +15 -0
  82. package/src/i18n/useT.ts +15 -0
  83. package/src/main.tsx +13 -0
  84. package/src/mocks/backend.ts +722 -0
  85. package/src/styles/global.css +7429 -0
  86. package/src/styles/tokens.css +161 -0
  87. package/src/utils/api.ts +627 -0
  88. package/src/utils/download.ts +18 -0
  89. package/src/utils/format.ts +7 -0
  90. package/src/utils/zip.ts +119 -0
  91. package/src/vite-env.d.ts +1 -0
  92. package/tsconfig.app.json +22 -0
  93. package/tsconfig.json +7 -0
  94. package/tsconfig.node.json +13 -0
  95. package/vite.config.ts +13 -0
  96. package/dist/assets/index-Cd0Mi_WU.css +0 -1
  97. package/dist/assets/index-FGg-DeYR.js +0 -448
@@ -0,0 +1,329 @@
1
+ /* --------------------------------------------------------------------------
2
+ * Shared pure helpers, types, and the static agent catalog for the Agent
3
+ * Network view. Extracted from `AgentNetwork.tsx` so the new sub-views
4
+ * (`AnalyticsTab`, `TimelineTab`, `GlobalOverview`, `NodeTooltip`) can reuse
5
+ * the SAME derivation logic — in particular the `send_message` tool-name
6
+ * matching and edge building, which previously lived only in the component
7
+ * and is the single source of truth for "who talked to whom".
8
+ * ------------------------------------------------------------------------ */
9
+ import {
10
+ BarChart3,
11
+ Bot,
12
+ BookOpen,
13
+ GitBranch,
14
+ Microscope,
15
+ PenLine,
16
+ Sparkles,
17
+ UserRoundCog,
18
+ Wrench,
19
+ } from "lucide-react";
20
+ import { ChatMessage } from "../../contracts/backend";
21
+
22
+ /* --------------------------------------------------------------------------
23
+ * Static catalog: per-agent profile (role, accent color, default tools).
24
+ * Mirrors the prompts in `claude/agents/*.md` and `agent_tool_config` in
25
+ * `agent_runtime/session_manager.py`. Unknown agents fall back to `_default`.
26
+ * ------------------------------------------------------------------------ */
27
+
28
+ export interface AgentProfile {
29
+ displayName: string;
30
+ /** i18n key for the role line (resolve with t() at render). */
31
+ role: string;
32
+ /** i18n key for the description (resolve with t() at render). */
33
+ description: string;
34
+ accent: string; // CSS variable name fragment (e.g. "info")
35
+ defaultTools: string[];
36
+ }
37
+
38
+ export const AGENT_PROFILES: Record<string, AgentProfile> = {
39
+ principal: {
40
+ displayName: "Principal Investigator",
41
+ role: "profile.principal.role",
42
+ description: "profile.principal.desc",
43
+ accent: "info",
44
+ defaultTools: [
45
+ "send_message",
46
+ "create_agent",
47
+ "destroy_agent",
48
+ "record_trace",
49
+ "search_web",
50
+ "fetch_url",
51
+ ],
52
+ },
53
+ librarian: {
54
+ displayName: "Librarian",
55
+ role: "profile.librarian.role",
56
+ description: "profile.librarian.desc",
57
+ accent: "info",
58
+ defaultTools: ["send_message", "record_trace", "search_web", "fetch_url"],
59
+ },
60
+ trace: {
61
+ displayName: "Trace Agent",
62
+ role: "profile.trace.role",
63
+ description: "profile.trace.desc",
64
+ accent: "neutral",
65
+ defaultTools: [
66
+ "create_trace_node",
67
+ "update_trace_node",
68
+ "add_trace_relation",
69
+ "get_trace_graph",
70
+ "read_session_history",
71
+ "Read",
72
+ ],
73
+ },
74
+ experimentalist: {
75
+ displayName: "Experimentalist",
76
+ role: "profile.experimentalist.role",
77
+ description: "profile.experimentalist.desc",
78
+ accent: "success",
79
+ defaultTools: ["Read", "Write", "Grep", "Bash", "send_message"],
80
+ },
81
+ engineer: {
82
+ displayName: "Engineer",
83
+ role: "profile.engineer.role",
84
+ description: "profile.engineer.desc",
85
+ accent: "success",
86
+ defaultTools: ["Read", "Write", "Grep", "Bash", "send_message"],
87
+ },
88
+ writer: {
89
+ displayName: "Writer",
90
+ role: "profile.writer.role",
91
+ description: "profile.writer.desc",
92
+ accent: "warning",
93
+ defaultTools: ["Read", "Write", "Grep", "Bash", "send_message"],
94
+ },
95
+ user: {
96
+ displayName: "You",
97
+ role: "profile.user.role",
98
+ description: "profile.user.desc",
99
+ accent: "neutral",
100
+ defaultTools: [],
101
+ },
102
+ };
103
+
104
+ export const DEFAULT_PROFILE: AgentProfile = {
105
+ displayName: "Custom Agent",
106
+ role: "profile.default.role",
107
+ description: "profile.default.desc",
108
+ accent: "neutral",
109
+ defaultTools: ["send_message", "record_trace", "search_web", "fetch_url"],
110
+ };
111
+
112
+ export function getAgentProfile(name: string): AgentProfile {
113
+ return AGENT_PROFILES[name] ?? DEFAULT_PROFILE;
114
+ }
115
+
116
+ /**
117
+ * Built-in agent roster — default expert types the PI knows about regardless
118
+ * of whether they have been spawned this session. Rendered as "dormant"
119
+ * placeholders so the full team is always visible. Custom agents created at
120
+ * runtime via `create_agent` flow in through `AgentStatus[]` and union with
121
+ * this list in `nodeNames`.
122
+ */
123
+ export const BUILTIN_AGENT_NAMES = [
124
+ "principal",
125
+ "librarian",
126
+ "trace",
127
+ "experimentalist",
128
+ "engineer",
129
+ "writer",
130
+ ] as const;
131
+
132
+ /* --------------------------------------------------------------------------
133
+ * Types
134
+ * ------------------------------------------------------------------------ */
135
+
136
+ export interface AgentEdgeMessage {
137
+ id: string;
138
+ from: string;
139
+ to: string;
140
+ content: string;
141
+ msgType?: string;
142
+ timestamp: string;
143
+ streaming?: boolean;
144
+ }
145
+
146
+ export interface AgentEdge {
147
+ key: string; // `${from}->${to}`
148
+ from: string;
149
+ to: string;
150
+ messages: AgentEdgeMessage[];
151
+ lastTimestamp: string;
152
+ }
153
+
154
+ /* --------------------------------------------------------------------------
155
+ * Edge / message derivation
156
+ * ------------------------------------------------------------------------ */
157
+
158
+ export function safeParseJson(input: unknown): Record<string, unknown> | null {
159
+ if (input && typeof input === "object" && !Array.isArray(input)) {
160
+ return input as Record<string, unknown>;
161
+ }
162
+ if (typeof input !== "string" || !input.trim()) {
163
+ return null;
164
+ }
165
+ try {
166
+ const parsed = JSON.parse(input);
167
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
168
+ ? (parsed as Record<string, unknown>)
169
+ : null;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Tool name matcher — accepts both the bare SDK name (`send_message`) and the
177
+ * MCP-namespaced name as it actually appears in JSONL / AG-UI snapshots
178
+ * (`mcp__builtin__send_message`, or any future `mcp__<server>__send_message`).
179
+ */
180
+ export function isSendMessageTool(toolName?: string): boolean {
181
+ if (!toolName) return false;
182
+ if (toolName === "send_message") return true;
183
+ // MCP convention: `mcp__<server>__<tool>` — match the trailing tool name.
184
+ return toolName.endsWith("__send_message") || toolName.endsWith(":send_message");
185
+ }
186
+
187
+ export function getMessageEdge(message: ChatMessage): AgentEdgeMessage | null {
188
+ if (message.kind !== "tool" || !isSendMessageTool(message.toolName)) {
189
+ return null;
190
+ }
191
+ const args = safeParseJson(message.toolInput);
192
+ if (!args) {
193
+ return null;
194
+ }
195
+ const from = message.agent || "principal";
196
+ const to = typeof args.to === "string" && args.to ? args.to : "principal";
197
+ if (from === to) {
198
+ return null;
199
+ }
200
+ // Filter out messages to/from 'trace' — trace is an internal system agent
201
+ // that should only be interacted with via record_trace tool, not send_message.
202
+ // Any send_message involving trace is likely a mistake and should not appear
203
+ // as a collaboration edge in the Agent Network graph.
204
+ if (from === "trace" || to === "trace") {
205
+ return null;
206
+ }
207
+ const content = typeof args.content === "string" ? args.content : "";
208
+ // The wire format uses `type` (e.g. "task_delegate" / "task_result"); accept
209
+ // legacy `msg_type` as a fallback for older sessions.
210
+ const msgTypeRaw =
211
+ typeof args.type === "string" && args.type
212
+ ? args.type
213
+ : typeof args.msg_type === "string"
214
+ ? args.msg_type
215
+ : undefined;
216
+ return {
217
+ id: message.id,
218
+ from,
219
+ to,
220
+ content,
221
+ msgType: msgTypeRaw,
222
+ timestamp: message.createdAt,
223
+ streaming: message.streaming,
224
+ };
225
+ }
226
+
227
+ export function buildEdges(messages: ChatMessage[]): AgentEdge[] {
228
+ const map = new Map<string, AgentEdge>();
229
+ for (const message of messages) {
230
+ const item = getMessageEdge(message);
231
+ if (!item) continue;
232
+ const key = `${item.from}->${item.to}`;
233
+ const edge = map.get(key) ?? {
234
+ key,
235
+ from: item.from,
236
+ to: item.to,
237
+ messages: [],
238
+ lastTimestamp: item.timestamp,
239
+ };
240
+ edge.messages.push(item);
241
+ if (item.timestamp > edge.lastTimestamp) {
242
+ edge.lastTimestamp = item.timestamp;
243
+ }
244
+ map.set(key, edge);
245
+ }
246
+ // Sort messages within each edge by time ascending
247
+ for (const edge of map.values()) {
248
+ edge.messages.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
249
+ }
250
+ return Array.from(map.values());
251
+ }
252
+
253
+ /** Count of messages sent / received by `name` across all edges. */
254
+ export function countMessagesFor(
255
+ name: string,
256
+ edges: AgentEdge[],
257
+ ): { sent: number; received: number } {
258
+ let sent = 0;
259
+ let received = 0;
260
+ for (const edge of edges) {
261
+ if (edge.from === name) sent += edge.messages.length;
262
+ if (edge.to === name) received += edge.messages.length;
263
+ }
264
+ return { sent, received };
265
+ }
266
+
267
+ /* --------------------------------------------------------------------------
268
+ * Display helpers
269
+ * ------------------------------------------------------------------------ */
270
+
271
+ export function getAgentIcon(name: string) {
272
+ const normalized = name.toLowerCase();
273
+ if (normalized === "principal") return UserRoundCog;
274
+ if (normalized === "user") return UserRoundCog;
275
+ if (normalized.includes("librari")) return BookOpen;
276
+ if (normalized.includes("data") || normalized.includes("analy")) return BarChart3;
277
+ if (normalized.includes("trace")) return GitBranch;
278
+ if (normalized.includes("experiment")) return Microscope;
279
+ if (normalized.includes("engineer")) return Wrench;
280
+ if (normalized.includes("writer")) return PenLine;
281
+ if (normalized.includes("idea") || normalized.includes("creat")) return Sparkles;
282
+ return Bot;
283
+ }
284
+
285
+ /** Map an agent profile's `accent` to a CSS color token. */
286
+ export function getAgentAccentVar(name: string): string {
287
+ const profile = getAgentProfile(name);
288
+ switch (profile.accent) {
289
+ case "info":
290
+ return "var(--color-info)";
291
+ case "success":
292
+ return "var(--color-success)";
293
+ case "warning":
294
+ return "var(--color-warning)";
295
+ case "danger":
296
+ return "var(--color-danger)";
297
+ default:
298
+ return "var(--color-text-subtle)";
299
+ }
300
+ }
301
+
302
+ export function relativeTime(iso: string, nowMs: number): string {
303
+ const ts = new Date(iso).getTime();
304
+ if (!Number.isFinite(ts)) return "";
305
+ const diff = nowMs - ts;
306
+ if (diff < 1000) return "just now";
307
+ if (diff < 60_000) return `${Math.floor(diff / 1000)}s ago`;
308
+ if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
309
+ return new Date(iso).toLocaleTimeString();
310
+ }
311
+
312
+ export function msgTypeKind(msgType?: string): "delegate" | "result" | "neutral" {
313
+ if (!msgType) return "neutral";
314
+ if (msgType.includes("delegate") || msgType.includes("request")) return "delegate";
315
+ if (
316
+ msgType.includes("result") ||
317
+ msgType.includes("reply") ||
318
+ msgType.includes("response")
319
+ )
320
+ return "result";
321
+ return "neutral";
322
+ }
323
+
324
+ export function statusKind(status: string): "running" | "idle" | "error" | "stopped" {
325
+ if (status === "running" || status === "in_progress") return "running";
326
+ if (status === "error" || status === "failed") return "error";
327
+ if (status === "stopped" || status === "destroyed") return "stopped";
328
+ return "idle";
329
+ }
@@ -0,0 +1,150 @@
1
+ import { TraceNode } from "../../contracts/backend";
2
+
3
+ /**
4
+ * Pure layout & formatting helpers for the reasoning-trace graph. Shared by the
5
+ * live TracePanel, the presentational TraceGraphView / TraceNodeDetail, and the
6
+ * demo player. No React, no side effects.
7
+ */
8
+
9
+ export function formatTime(value?: string): string {
10
+ if (!value) {
11
+ return "-";
12
+ }
13
+ return new Date(value).toLocaleString();
14
+ }
15
+
16
+ export function getStatusLabelKey(status: string): string | null {
17
+ if (status === "done" || status === "completed") {
18
+ return "trace.status.done";
19
+ }
20
+ if (status === "in_progress") {
21
+ return "trace.status.running";
22
+ }
23
+ return null;
24
+ }
25
+
26
+ export function normalizeStatus(status: string): string {
27
+ return status === "completed" ? "done" : status;
28
+ }
29
+
30
+ export function getNodeKind(node: TraceNode): string {
31
+ return node.nodeType || node.type || "step";
32
+ }
33
+
34
+ export function truncateNodeTitle(title?: string, maxUnits = 26): string {
35
+ if (!title) {
36
+ return "";
37
+ }
38
+ let units = 0;
39
+ let index = 0;
40
+ for (const char of title) {
41
+ units += char.charCodeAt(0) > 127 ? 2 : 1;
42
+ if (units > maxUnits) {
43
+ return title.slice(0, index) + "…";
44
+ }
45
+ index++;
46
+ }
47
+ return title;
48
+ }
49
+
50
+ export function formatDuration(ms?: number): string {
51
+ if (ms === undefined) {
52
+ return "-";
53
+ }
54
+ if (ms < 1000) {
55
+ return `${Math.round(ms)}ms`;
56
+ }
57
+ if (ms < 60_000) {
58
+ return `${(ms / 1000).toFixed(1)}s`;
59
+ }
60
+ return `${(ms / 60_000).toFixed(1)}m`;
61
+ }
62
+
63
+ export const relationLabels: Record<string, string> = {
64
+ necessitated_by: "needed by",
65
+ used: "used",
66
+ produced: "produced",
67
+ comparison_with: "compared with",
68
+ };
69
+
70
+ export const artifactLabels: Record<string, string> = {
71
+ data: "data",
72
+ code: "code",
73
+ text: "text",
74
+ image: "image",
75
+ figure: "figure",
76
+ result: "result",
77
+ doc: "doc",
78
+ config: "config",
79
+ dir: "dir",
80
+ model: "model",
81
+ log: "log",
82
+ report: "report",
83
+ paper: "paper",
84
+ };
85
+
86
+ export type TraceLayoutDirection = "LR" | "TB";
87
+
88
+ export type PositionedTraceNode = { node: TraceNode; x: number; y: number };
89
+
90
+ export interface TraceLayout {
91
+ positioned: PositionedTraceNode[];
92
+ byId: Map<string, PositionedTraceNode>;
93
+ nodeWidth: number;
94
+ nodeHeight: number;
95
+ width: number;
96
+ height: number;
97
+ }
98
+
99
+ export function buildTraceLayout(nodes: TraceNode[], direction: TraceLayoutDirection): TraceLayout {
100
+ const byId = new Map(nodes.map((node) => [node.id, node]));
101
+ const levelCache = new Map<string, number>();
102
+ const getLevel = (node: TraceNode): number => {
103
+ if (levelCache.has(node.id)) {
104
+ return levelCache.get(node.id) as number;
105
+ }
106
+ if (node.parentIds.length === 0) {
107
+ levelCache.set(node.id, 0);
108
+ return 0;
109
+ }
110
+ const level = 1 + Math.max(...node.parentIds.map((parentId) => {
111
+ const parent = byId.get(parentId);
112
+ return parent ? getLevel(parent) : 0;
113
+ }));
114
+ levelCache.set(node.id, level);
115
+ return level;
116
+ };
117
+ const grouped = new Map<number, TraceNode[]>();
118
+ for (const node of nodes) {
119
+ const level = getLevel(node);
120
+ grouped.set(level, [...(grouped.get(level) ?? []), node]);
121
+ }
122
+ const nodeWidth = direction === "LR" ? 200 : 214;
123
+ const nodeHeight = 72;
124
+ const levelGap = direction === "LR" ? 300 : 170;
125
+ const siblingGap = direction === "LR" ? 140 : 260;
126
+ // Center each level around a shared axis: the widest level defines the span,
127
+ // and every other level is offset so its midpoint lines up with that axis.
128
+ const maxSiblingCount = Math.max(1, ...Array.from(grouped.values()).map((s) => s.length));
129
+ const baseOffset = direction === "LR" ? 62 : 80;
130
+ const positioned = nodes.map((node) => {
131
+ const level = getLevel(node);
132
+ const siblings = grouped.get(level) ?? [];
133
+ const index = siblings.findIndex((item) => item.id === node.id);
134
+ const centeringShift = ((maxSiblingCount - siblings.length) * siblingGap) / 2;
135
+ const crossPos = baseOffset + centeringShift + index * siblingGap;
136
+ const x = direction === "LR" ? 72 + level * levelGap : crossPos;
137
+ const y = direction === "LR" ? crossPos : 62 + level * levelGap;
138
+ return { node, x, y };
139
+ });
140
+ const maxX = Math.max(220, ...positioned.map((item) => item.x + nodeWidth));
141
+ const maxY = Math.max(180, ...positioned.map((item) => item.y + nodeHeight));
142
+ return {
143
+ positioned,
144
+ byId: new Map(positioned.map((item) => [item.node.id, item])),
145
+ nodeWidth,
146
+ nodeHeight,
147
+ width: Math.max(720, maxX + 96),
148
+ height: Math.max(360, maxY + 76),
149
+ };
150
+ }