@brainpilot/web 0.0.3 → 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.
- package/dist/assets/index-C-8G4D4j.js +448 -0
- package/dist/assets/index-C501m5OS.css +1 -0
- package/dist/index.html +2 -2
- package/index.html +13 -0
- package/package.json +9 -3
- package/src/App.tsx +10 -0
- package/src/__tests__/api.test.ts +103 -0
- package/src/__tests__/messageGroups.test.ts +80 -0
- package/src/__tests__/newUiComponents.test.tsx +101 -0
- package/src/__tests__/newUiEvents.test.ts +236 -0
- package/src/components/chat/AskUserCard.tsx +123 -0
- package/src/components/chat/AutoRetryIndicator.tsx +71 -0
- package/src/components/chat/ComposerInput.tsx +73 -0
- package/src/components/chat/ComposerSendButton.tsx +26 -0
- package/src/components/chat/MarkdownMessage.tsx +24 -0
- package/src/components/chat/MessageStream.tsx +464 -0
- package/src/components/chat/PromptComposer.tsx +398 -0
- package/src/components/chat/SystemMessageBubble.tsx +46 -0
- package/src/components/demo/DemoFileTree.tsx +146 -0
- package/src/components/demo/DemoView.tsx +668 -0
- package/src/components/demo/TraceNodeModal.tsx +76 -0
- package/src/components/demo/demoBundle.ts +218 -0
- package/src/components/demo/demoCache.ts +42 -0
- package/src/components/files/FilePreviewView.tsx +153 -0
- package/src/components/files/FileSidebar.tsx +664 -0
- package/src/components/files/filePreview.ts +113 -0
- package/src/components/primitives/CustomSelect.tsx +200 -0
- package/src/components/primitives/IconButton.tsx +27 -0
- package/src/components/quota/DiskQuotaCriticalDialog.tsx +56 -0
- package/src/components/quota/DiskQuotaWarningDialog.tsx +65 -0
- package/src/components/quota/QuotaFileManager.tsx +197 -0
- package/src/components/search/SearchDialog.tsx +101 -0
- package/src/components/session/AgentNetwork.tsx +1240 -0
- package/src/components/session/AgentTraceViews.tsx +381 -0
- package/src/components/session/AnalyticsTab.tsx +386 -0
- package/src/components/session/GlobalOverview.tsx +108 -0
- package/src/components/session/NodeTooltip.tsx +127 -0
- package/src/components/session/TimelineTab.tsx +320 -0
- package/src/components/session/TraceGraphView.tsx +301 -0
- package/src/components/session/TraceNodeDetail.tsx +142 -0
- package/src/components/session/agentAnalytics.ts +397 -0
- package/src/components/session/agentNetworkShared.ts +329 -0
- package/src/components/session/traceLayout.ts +150 -0
- package/src/components/settings/SettingsDialog.tsx +719 -0
- package/src/components/shell/DesktopShell.tsx +236 -0
- package/src/components/shell/SandboxBuildingOverlay.tsx +73 -0
- package/src/components/shell/SandboxStatus.tsx +287 -0
- package/src/components/shell/TerminalDrawer.tsx +387 -0
- package/src/components/sidebar/Sidebar.tsx +187 -0
- package/src/config.ts +10 -0
- package/src/contexts/AppProviders.tsx +20 -0
- package/src/contexts/AuthContext.tsx +61 -0
- package/src/contexts/PreferencesContext.tsx +125 -0
- package/src/contexts/SSEContext.tsx +175 -0
- package/src/contexts/SandboxContext.tsx +310 -0
- package/src/contexts/SessionContext.tsx +608 -0
- package/src/contexts/draftStore.ts +103 -0
- package/src/contexts/messageFilters.ts +29 -0
- package/src/contexts/messageGroups.ts +77 -0
- package/src/contexts/messageReducer.ts +401 -0
- package/src/contexts/newUiEvents.ts +190 -0
- package/src/contracts/backend.ts +846 -0
- package/src/contracts/demoBundle.ts +83 -0
- package/src/i18n/messages/analytics.ts +96 -0
- package/src/i18n/messages/chat.ts +108 -0
- package/src/i18n/messages/contexts.ts +40 -0
- package/src/i18n/messages/demo.ts +80 -0
- package/src/i18n/messages/files.ts +82 -0
- package/src/i18n/messages/network.ts +186 -0
- package/src/i18n/messages/profile.ts +40 -0
- package/src/i18n/messages/quota.ts +36 -0
- package/src/i18n/messages/sandbox.ts +116 -0
- package/src/i18n/messages/search.ts +16 -0
- package/src/i18n/messages/settings.ts +184 -0
- package/src/i18n/messages/shell.ts +38 -0
- package/src/i18n/messages/sidebar.ts +52 -0
- package/src/i18n/messages/terminal.ts +22 -0
- package/src/i18n/messages/trace.ts +84 -0
- package/src/i18n/messages.ts +32 -0
- package/src/i18n/translate.ts +46 -0
- package/src/i18n/types.ts +15 -0
- package/src/i18n/useT.ts +15 -0
- package/src/main.tsx +13 -0
- package/src/mocks/backend.ts +722 -0
- package/src/styles/global.css +7429 -0
- package/src/styles/tokens.css +161 -0
- package/src/utils/api.ts +627 -0
- package/src/utils/download.ts +18 -0
- package/src/utils/format.ts +7 -0
- package/src/utils/zip.ts +119 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +22 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +13 -0
- package/vite.config.ts +13 -0
- package/dist/assets/index-Cd0Mi_WU.css +0 -1
- package/dist/assets/index-FGg-DeYR.js +0 -448
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentStatus,
|
|
3
|
+
SessionStateSnapshot,
|
|
4
|
+
FileContent,
|
|
5
|
+
FileEntry,
|
|
6
|
+
McpServerEntry,
|
|
7
|
+
ProviderCreate,
|
|
8
|
+
ProviderProfile,
|
|
9
|
+
ProviderUpdate,
|
|
10
|
+
Sandbox,
|
|
11
|
+
SandboxStats,
|
|
12
|
+
Session,
|
|
13
|
+
SessionMessageEntry,
|
|
14
|
+
SettingsData,
|
|
15
|
+
TraceGraph,
|
|
16
|
+
WebSocketEvent,
|
|
17
|
+
} from "../contracts/backend";
|
|
18
|
+
|
|
19
|
+
const now = () => new Date().toISOString();
|
|
20
|
+
const wait = (ms = 180) => new Promise((resolve) => window.setTimeout(resolve, ms));
|
|
21
|
+
|
|
22
|
+
let mockUser = {
|
|
23
|
+
id: "mock-user-fy",
|
|
24
|
+
username: "fy",
|
|
25
|
+
createdAt: "2026-05-10T02:00:00.000Z",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let mockSandbox: Sandbox | null = {
|
|
29
|
+
id: "mock-sandbox-001",
|
|
30
|
+
name: "default",
|
|
31
|
+
status: "running",
|
|
32
|
+
port: 8080,
|
|
33
|
+
userId: mockUser.username,
|
|
34
|
+
createdAt: "2026-05-10T02:05:00.000Z",
|
|
35
|
+
containerName: "mas-neuroscience-default",
|
|
36
|
+
hostApiUrl: "http://127.0.0.1:8080",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let mockSessions: Session[] = [
|
|
40
|
+
{
|
|
41
|
+
id: "11111111-1111-4111-8111-111111111111",
|
|
42
|
+
title: "EEG preprocessing reproducibility plan",
|
|
43
|
+
|
|
44
|
+
createdAt: "2026-05-10T02:12:00.000Z",
|
|
45
|
+
updatedAt: "2026-05-10T02:48:00.000Z",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "22222222-2222-4222-8222-222222222222",
|
|
49
|
+
title: "Bayesian power analysis comparison",
|
|
50
|
+
|
|
51
|
+
createdAt: "2026-05-10T01:30:00.000Z",
|
|
52
|
+
updatedAt: "2026-05-10T01:46:00.000Z",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
let mockSettings: SettingsData = {
|
|
57
|
+
model: "claude-sonnet-4-6",
|
|
58
|
+
apiKey: "sk-mock••••0000",
|
|
59
|
+
baseUrl: "https://api.anthropic.com",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let mockMcpServers: McpServerEntry[] = [
|
|
63
|
+
{
|
|
64
|
+
name: "filesystem",
|
|
65
|
+
type: "stdio",
|
|
66
|
+
command: "npx",
|
|
67
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"],
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
let mockProviders: ProviderProfile[] = [
|
|
72
|
+
{
|
|
73
|
+
id: "provider-anthropic",
|
|
74
|
+
name: "Anthropic",
|
|
75
|
+
baseUrl: "https://api.anthropic.com",
|
|
76
|
+
models: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5-20251001"],
|
|
77
|
+
icon: "sparkles",
|
|
78
|
+
iconColor: "#111111",
|
|
79
|
+
notes: "Default mock provider profile",
|
|
80
|
+
isActive: true,
|
|
81
|
+
apiKeyMasked: "sk-ant••••0000",
|
|
82
|
+
createdAt: 1_715_292_000,
|
|
83
|
+
updatedAt: 1_715_292_000,
|
|
84
|
+
healthStatus: "healthy",
|
|
85
|
+
healthCheckedAt: 1_715_292_000,
|
|
86
|
+
modelHealth: [
|
|
87
|
+
{ model: "claude-sonnet-4-6", status: "healthy", latencyMs: 340 },
|
|
88
|
+
{ model: "claude-opus-4-6", status: "healthy", latencyMs: 520 },
|
|
89
|
+
{ model: "claude-haiku-4-5-20251001", status: "healthy", latencyMs: 180 },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const mockHistoryMessages: Record<string, SessionMessageEntry[]> = {
|
|
95
|
+
"11111111-1111-4111-8111-111111111111": [
|
|
96
|
+
{
|
|
97
|
+
type: "user",
|
|
98
|
+
uuid: "mock-hist-user-1",
|
|
99
|
+
timestamp: "2026-05-10T02:20:00.000Z",
|
|
100
|
+
message: { role: "user", content: "Build a reproducible EEG preprocessing plan", agent: "user" },
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: "assistant",
|
|
104
|
+
uuid: "mock-hist-assistant-1",
|
|
105
|
+
timestamp: "2026-05-10T02:20:10.000Z",
|
|
106
|
+
message: {
|
|
107
|
+
role: "assistant",
|
|
108
|
+
agent: "principal",
|
|
109
|
+
content: [
|
|
110
|
+
{ type: "text", text: "A reproducible plan should start with raw import, filtering, ICA review, epoching, and QC export." },
|
|
111
|
+
{ type: "tool_use", id: "mock-tool-1", name: "record_trace", input: { node: "EEG preprocessing plan" } },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const mockTraceGraph: TraceGraph = {
|
|
119
|
+
meta: {
|
|
120
|
+
sessionId: "11111111-1111-4111-8111-111111111111",
|
|
121
|
+
userId: "fy",
|
|
122
|
+
projectName: "Mock EEG workflow",
|
|
123
|
+
currentFocus: "qc-summary",
|
|
124
|
+
createdAt: "2026-05-10T02:21:00.000Z",
|
|
125
|
+
},
|
|
126
|
+
nodes: [
|
|
127
|
+
{
|
|
128
|
+
id: "plan",
|
|
129
|
+
title: "Define preprocessing plan",
|
|
130
|
+
type: "plan",
|
|
131
|
+
nodeType: "action",
|
|
132
|
+
status: "done",
|
|
133
|
+
agent: "principal",
|
|
134
|
+
description: "Outlined a reproducible EEG preprocessing workflow and selected the main execution checkpoints.",
|
|
135
|
+
summary: "Outlined a reproducible EEG preprocessing workflow.",
|
|
136
|
+
reason: "The session needed an explicit, reproducible preprocessing path before tools could be delegated.",
|
|
137
|
+
context: "Raw EEG data will be imported, filtered, cleaned with ICA, epoched, and exported with QC artifacts.",
|
|
138
|
+
parents: [],
|
|
139
|
+
artifacts: [{ path: "/workspace/src/pipeline.yaml", type: "code" }],
|
|
140
|
+
parentIds: [],
|
|
141
|
+
childIds: ["qc"],
|
|
142
|
+
createdAt: "2026-05-10T02:21:00.000Z",
|
|
143
|
+
timestamp: { createdAt: "2026-05-10T02:21:00.000Z", completedAt: "2026-05-10T02:21:28.000Z" },
|
|
144
|
+
durationMs: 28_000,
|
|
145
|
+
toolCalls: ["record_trace"],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "delegate",
|
|
149
|
+
title: "Delegate literature context",
|
|
150
|
+
type: "delegation",
|
|
151
|
+
nodeType: "decision",
|
|
152
|
+
status: "in_progress",
|
|
153
|
+
agent: "principal",
|
|
154
|
+
description: "Asked the librarian expert to monitor method references and naming consistency.",
|
|
155
|
+
summary: "Delegated method context tracking to librarian.",
|
|
156
|
+
reason: "Preprocessing decisions should remain aligned with accepted EEG reporting practice.",
|
|
157
|
+
context: "The plan introduced ICA and QC checkpoints that need literature-grounded wording.",
|
|
158
|
+
parents: [{ id: "plan", relation: "necessitated_by", edgeType: "main_flow", explanation: "The workflow needs literature-grounded QC checkpoints." }],
|
|
159
|
+
artifacts: [],
|
|
160
|
+
parentIds: ["plan"],
|
|
161
|
+
childIds: ["qc"],
|
|
162
|
+
createdAt: "2026-05-10T02:22:30.000Z",
|
|
163
|
+
timestamp: { createdAt: "2026-05-10T02:22:30.000Z", startedAt: "2026-05-10T02:22:40.000Z" },
|
|
164
|
+
toolCalls: ["create_agent", "send_message"],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "qc",
|
|
168
|
+
title: "Quality-control checkpoints",
|
|
169
|
+
type: "analysis",
|
|
170
|
+
nodeType: "observation",
|
|
171
|
+
status: "done",
|
|
172
|
+
agent: "librarian",
|
|
173
|
+
description: "Captured retained epochs, ICA removals, and frontal-channel warnings.",
|
|
174
|
+
summary: "Captured retained epochs, ICA removals, and frontal-channel warnings.",
|
|
175
|
+
reason: "The workflow needs inspectable artifacts before it can be reproduced.",
|
|
176
|
+
context: "Mock QC artifacts summarize retained epochs and ICA removal decisions.",
|
|
177
|
+
parents: [{ id: "delegate", relation: "used", edgeType: "branch", explanation: "The librarian context informed the QC report wording." }],
|
|
178
|
+
artifacts: [
|
|
179
|
+
{ path: "/workspace/reports/qc-summary.md", type: "report" },
|
|
180
|
+
{ path: "/workspace/reports/qc-plot.svg", type: "image" },
|
|
181
|
+
],
|
|
182
|
+
parentIds: ["delegate"],
|
|
183
|
+
childIds: [],
|
|
184
|
+
createdAt: "2026-05-10T02:24:00.000Z",
|
|
185
|
+
timestamp: { createdAt: "2026-05-10T02:24:00.000Z", completedAt: "2026-05-10T02:24:23.000Z" },
|
|
186
|
+
durationMs: 23_000,
|
|
187
|
+
toolCalls: ["record_trace"],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const fileEntries: Record<string, FileEntry[]> = {
|
|
193
|
+
"/workspace": [
|
|
194
|
+
{ name: "README.md", type: "file", size: 870, modified: 1_715_292_000, permissions: "rw-r--r--" },
|
|
195
|
+
{ name: "src", type: "folder", size: 0, modified: 1_715_292_200, permissions: "rwxr-xr-x" },
|
|
196
|
+
{ name: "data", type: "folder", size: 0, modified: 1_715_292_400, permissions: "rwxr-xr-x" },
|
|
197
|
+
{ name: "reports", type: "folder", size: 0, modified: 1_715_292_600, permissions: "rwxr-xr-x" },
|
|
198
|
+
],
|
|
199
|
+
"/workspace/src": [
|
|
200
|
+
{ name: "preprocess_eeg.py", type: "file", size: 1380, modified: 1_715_293_000, permissions: "rw-r--r--" },
|
|
201
|
+
{ name: "pipeline.yaml", type: "file", size: 516, modified: 1_715_293_300, permissions: "rw-r--r--" },
|
|
202
|
+
],
|
|
203
|
+
"/workspace/data": [
|
|
204
|
+
{ name: "participants.csv", type: "file", size: 168, modified: 1_715_293_900, permissions: "rw-r--r--" },
|
|
205
|
+
{ name: "raw-large.edf", type: "file", size: 4_800_000, modified: 1_715_294_200, permissions: "rw-r--r--" },
|
|
206
|
+
],
|
|
207
|
+
"/workspace/reports": [
|
|
208
|
+
{ name: "qc-summary.md", type: "file", size: 1120, modified: 1_715_294_600, permissions: "rw-r--r--" },
|
|
209
|
+
{ name: "qc-plot.svg", type: "file", size: 932, modified: 1_715_294_900, permissions: "rw-r--r--" },
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const fileContents: Record<string, string> = {
|
|
214
|
+
"/workspace/README.md":
|
|
215
|
+
"# Mock research workspace\n\nThis mock workspace mirrors the backend file API shape.\n\n- `src/` contains analysis code\n- `data/` contains small fixtures\n- `reports/` contains generated notes\n",
|
|
216
|
+
"/workspace/src/preprocess_eeg.py":
|
|
217
|
+
"from pathlib import Path\n\n\ndef preprocess(raw_dir: Path) -> dict[str, float]:\n \"\"\"Mock EEG preprocessing summary.\"\"\"\n return {\n \"n_subjects\": 24,\n \"bad_channel_rate\": 0.031,\n \"mean_rejected_epochs\": 4.8,\n }\n",
|
|
218
|
+
"/workspace/src/pipeline.yaml":
|
|
219
|
+
"steps:\n - import_raw\n - notch_filter\n - bandpass_filter\n - ica_artifact_rejection\n - epoch\n - export_qc_report\n",
|
|
220
|
+
"/workspace/data/participants.csv": "id,group,age\nsub-001,control,24\nsub-002,patient,31\nsub-003,control,28\n",
|
|
221
|
+
"/workspace/reports/qc-summary.md":
|
|
222
|
+
"# QC Summary\n\nThe mock run completed successfully.\n\n- Mean retained epochs: **91.2%**\n- Median ICA components removed: `2`\n- Recommended next step: inspect frontal channels for residual blink artifacts.\n",
|
|
223
|
+
"/workspace/reports/qc-plot.svg":
|
|
224
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 280"><rect width="520" height="280" fill="#f8f8f6"/><g fill="none" stroke="#1f2937" stroke-width="2"><path d="M54 222H476"/><path d="M54 40v182"/></g><g fill="#111827"><text x="54" y="26" font-family="Arial" font-size="16">Mock EEG QC retained epochs</text><text x="62" y="246" font-family="Arial" font-size="11">sub-001</text><text x="182" y="246" font-family="Arial" font-size="11">sub-002</text><text x="302" y="246" font-family="Arial" font-size="11">sub-003</text></g><g fill="#111827"><rect x="72" y="78" width="66" height="144" rx="3"/><rect x="192" y="94" width="66" height="128" rx="3"/><rect x="312" y="60" width="66" height="162" rx="3"/></g></svg>',
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
let seq = 0;
|
|
228
|
+
|
|
229
|
+
function getMimeType(path: string) {
|
|
230
|
+
if (/\.svg$/i.test(path)) {
|
|
231
|
+
return "image/svg+xml";
|
|
232
|
+
}
|
|
233
|
+
if (/\.png$/i.test(path)) {
|
|
234
|
+
return "image/png";
|
|
235
|
+
}
|
|
236
|
+
if (/\.jpe?g$/i.test(path)) {
|
|
237
|
+
return "image/jpeg";
|
|
238
|
+
}
|
|
239
|
+
if (/\.pdf$/i.test(path)) {
|
|
240
|
+
return "application/pdf";
|
|
241
|
+
}
|
|
242
|
+
if (/\.json$/i.test(path)) {
|
|
243
|
+
return "application/json";
|
|
244
|
+
}
|
|
245
|
+
return "text/plain";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export const mockBackend = {
|
|
249
|
+
async version() {
|
|
250
|
+
await wait(80);
|
|
251
|
+
return { version: "mock-ui-redesign" };
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
async me() {
|
|
255
|
+
await wait(80);
|
|
256
|
+
return mockUser;
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
async listSandboxes(): Promise<Sandbox[]> {
|
|
260
|
+
await wait();
|
|
261
|
+
return mockSandbox ? [mockSandbox] : [];
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
async createSandbox(name = "default"): Promise<Sandbox> {
|
|
265
|
+
await wait(450);
|
|
266
|
+
mockSandbox = {
|
|
267
|
+
id: "mock-sandbox-001",
|
|
268
|
+
name,
|
|
269
|
+
status: "running",
|
|
270
|
+
port: 8080,
|
|
271
|
+
userId: mockUser.username,
|
|
272
|
+
createdAt: now(),
|
|
273
|
+
containerName: `mas-neuroscience-${name}`,
|
|
274
|
+
hostApiUrl: "http://127.0.0.1:8080",
|
|
275
|
+
};
|
|
276
|
+
return mockSandbox;
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async rebuildSandbox(): Promise<Sandbox> {
|
|
280
|
+
await wait(520);
|
|
281
|
+
if (!mockSandbox) {
|
|
282
|
+
return this.createSandbox();
|
|
283
|
+
}
|
|
284
|
+
mockSandbox = { ...mockSandbox, status: "running", createdAt: now() };
|
|
285
|
+
return mockSandbox;
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
async destroySandbox(): Promise<void> {
|
|
289
|
+
await wait();
|
|
290
|
+
mockSandbox = null;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
async sandboxStats(): Promise<SandboxStats> {
|
|
294
|
+
await wait(120);
|
|
295
|
+
return {
|
|
296
|
+
sandboxId: mockSandbox?.id || "mock-sandbox-001",
|
|
297
|
+
sandboxName: mockSandbox?.name || "default",
|
|
298
|
+
status: mockSandbox?.status || "stopped",
|
|
299
|
+
memory: { usedBytes: 612 * 1024 * 1024, limitBytes: 2 * 1024 * 1024 * 1024, percent: 29.9 },
|
|
300
|
+
cpu: { usedPercent: 17.5, quotaPercent: 100, onlineCpus: 4 },
|
|
301
|
+
pids: { current: 31, limit: 256 },
|
|
302
|
+
disk: { workspaceUsedBytes: 156 * 1024 * 1024, quotaBytes: 1024 * 1024 * 1024, percentOfQuota: 15.2 },
|
|
303
|
+
gpu: null,
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
async sandboxLogs(): Promise<string> {
|
|
308
|
+
await wait();
|
|
309
|
+
return [
|
|
310
|
+
"[mock] agent_runtime started on :8080",
|
|
311
|
+
"[mock] mounted /workspace",
|
|
312
|
+
"[mock] principal agent ready",
|
|
313
|
+
].join("\n");
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
async sandboxHealth(): Promise<Record<string, unknown>> {
|
|
317
|
+
await wait();
|
|
318
|
+
return {
|
|
319
|
+
status: mockSandbox?.status === "running" ? "ok" : "offline",
|
|
320
|
+
agent_runtime: mockSandbox?.status === "running",
|
|
321
|
+
checked_at: now(),
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
async listFiles(_sandboxId: string, path = "/workspace"): Promise<FileEntry[]> {
|
|
326
|
+
await wait();
|
|
327
|
+
return fileEntries[path] || [];
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
async readFile(_sandboxId: string, path: string): Promise<FileContent> {
|
|
331
|
+
await wait();
|
|
332
|
+
const content = fileContents[path];
|
|
333
|
+
if (!content) {
|
|
334
|
+
throw new Error("Mock file is not previewable inline");
|
|
335
|
+
}
|
|
336
|
+
return { path, content, size: content.length };
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
async readRawFile(_sandboxId: string, path: string): Promise<Blob> {
|
|
340
|
+
await wait();
|
|
341
|
+
return new Blob([fileContents[path] || ""], { type: getMimeType(path) });
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
async deleteFile(_sandboxId: string, path: string): Promise<void> {
|
|
345
|
+
await wait();
|
|
346
|
+
if (path === "/workspace") {
|
|
347
|
+
throw new Error("Deleting /workspace root is not allowed");
|
|
348
|
+
}
|
|
349
|
+
delete fileContents[path];
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
async listSessions(): Promise<Session[]> {
|
|
353
|
+
await wait();
|
|
354
|
+
return [...mockSessions];
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
async getSession(sessionId: string): Promise<Session> {
|
|
358
|
+
await wait();
|
|
359
|
+
const session = mockSessions.find((item) => item.id === sessionId);
|
|
360
|
+
if (!session) {
|
|
361
|
+
throw new Error("Session not found");
|
|
362
|
+
}
|
|
363
|
+
return session;
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
async createSession(title: string): Promise<Session> {
|
|
367
|
+
await wait();
|
|
368
|
+
const session = {
|
|
369
|
+
id: crypto.randomUUID(),
|
|
370
|
+
title: title || "New research session",
|
|
371
|
+
createdAt: now(),
|
|
372
|
+
updatedAt: now(),
|
|
373
|
+
};
|
|
374
|
+
mockSessions = [session, ...mockSessions];
|
|
375
|
+
return session;
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
async updateSession(sessionId: string, title: string): Promise<Session> {
|
|
379
|
+
await wait();
|
|
380
|
+
mockSessions = mockSessions.map((session) =>
|
|
381
|
+
session.id === sessionId ? { ...session, title, updatedAt: now() } : session,
|
|
382
|
+
);
|
|
383
|
+
return mockSessions.find((session) => session.id === sessionId) as Session;
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
async removeSession(sessionId: string): Promise<void> {
|
|
387
|
+
await wait();
|
|
388
|
+
mockSessions = mockSessions.filter((session) => session.id !== sessionId);
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
async getMessages(sessionId: string): Promise<SessionMessageEntry[]> {
|
|
392
|
+
await wait();
|
|
393
|
+
return mockHistoryMessages[sessionId] || [];
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
async getTrace(sessionId: string): Promise<TraceGraph> {
|
|
397
|
+
await wait();
|
|
398
|
+
return {
|
|
399
|
+
...mockTraceGraph,
|
|
400
|
+
meta: { ...mockTraceGraph.meta, sessionId },
|
|
401
|
+
};
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
async getSessionEvents(_sessionId: string) {
|
|
405
|
+
await wait(80);
|
|
406
|
+
// Timestamped AG-UI events aligned with mockTraceGraph (02:21–02:24) so the
|
|
407
|
+
// demo replay can be exercised end-to-end in mock mode.
|
|
408
|
+
return [
|
|
409
|
+
{ type: "RUN_STARTED", _ts: "2026-05-10T02:20:45.000Z", agentName: "principal" },
|
|
410
|
+
{ type: "TEXT_MESSAGE_CHUNK", _ts: "2026-05-10T02:20:50.000Z", messageId: "u1", role: "user", delta: "Design a reproducible EEG preprocessing pipeline." },
|
|
411
|
+
{ type: "REASONING_MESSAGE_START", _ts: "2026-05-10T02:21:00.000Z", messageId: "r1", agentName: "principal" },
|
|
412
|
+
{ type: "REASONING_MESSAGE_CONTENT", _ts: "2026-05-10T02:21:02.000Z", messageId: "r1", delta: "Outline import → filter → ICA → epoch → export, with QC checkpoints." },
|
|
413
|
+
{ type: "REASONING_MESSAGE_END", _ts: "2026-05-10T02:21:10.000Z", messageId: "r1" },
|
|
414
|
+
{ type: "TOOL_CALL_START", _ts: "2026-05-10T02:21:12.000Z", toolCallId: "t1", toolCallName: "record_trace", agentName: "principal" },
|
|
415
|
+
{ type: "TOOL_CALL_ARGS", _ts: "2026-05-10T02:21:13.000Z", toolCallId: "t1", delta: "{\"title\":\"Define preprocessing plan\"}" },
|
|
416
|
+
{ type: "TOOL_CALL_END", _ts: "2026-05-10T02:21:14.000Z", toolCallId: "t1" },
|
|
417
|
+
{ type: "TEXT_MESSAGE_START", _ts: "2026-05-10T02:21:20.000Z", messageId: "a1", agentName: "principal" },
|
|
418
|
+
{ type: "TEXT_MESSAGE_CONTENT", _ts: "2026-05-10T02:21:22.000Z", messageId: "a1", delta: "I drafted a reproducible pipeline in `src/pipeline.yaml`." },
|
|
419
|
+
{ type: "TEXT_MESSAGE_END", _ts: "2026-05-10T02:21:28.000Z", messageId: "a1" },
|
|
420
|
+
{ type: "TEXT_MESSAGE_START", _ts: "2026-05-10T02:24:05.000Z", messageId: "a2", agentName: "librarian" },
|
|
421
|
+
{ type: "TEXT_MESSAGE_CONTENT", _ts: "2026-05-10T02:24:08.000Z", messageId: "a2", delta: "QC summary captured retained epochs and ICA removals — see `reports/qc-summary.md`." },
|
|
422
|
+
{ type: "TEXT_MESSAGE_END", _ts: "2026-05-10T02:24:23.000Z", messageId: "a2" },
|
|
423
|
+
{ type: "RUN_FINISHED", _ts: "2026-05-10T02:24:25.000Z", agentName: "principal" },
|
|
424
|
+
];
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
async events() {
|
|
428
|
+
await wait(80);
|
|
429
|
+
return { events: [], nextOffset: 0, hasMore: false };
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
async state(): Promise<SessionStateSnapshot> {
|
|
433
|
+
await wait(80);
|
|
434
|
+
return {
|
|
435
|
+
runState: { active: false, runId: null },
|
|
436
|
+
agents: [
|
|
437
|
+
{ name: "principal", status: "idle", task: "Ready for a research prompt", updatedAt: new Date().toISOString(), alive: true },
|
|
438
|
+
{ name: "librarian", status: "idle", task: "Monitoring literature context", updatedAt: new Date().toISOString(), alive: true },
|
|
439
|
+
],
|
|
440
|
+
lastActivityTs: new Date().toISOString(),
|
|
441
|
+
};
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
async promptSuggestions(): Promise<string[]> {
|
|
445
|
+
await wait(80);
|
|
446
|
+
return [
|
|
447
|
+
"Design a reproducible EEG preprocessing pipeline",
|
|
448
|
+
"Compare Bayesian and frequentist power plans",
|
|
449
|
+
"Summarize provenance for the latest analysis run",
|
|
450
|
+
];
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
async getSettings(): Promise<SettingsData> {
|
|
454
|
+
await wait();
|
|
455
|
+
return mockSettings;
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
async updateSettings(data: Partial<SettingsData>): Promise<SettingsData> {
|
|
459
|
+
await wait();
|
|
460
|
+
mockSettings = { ...mockSettings, ...data };
|
|
461
|
+
return mockSettings;
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
async resetConfig(): Promise<void> {
|
|
465
|
+
await wait();
|
|
466
|
+
mockSettings = { model: "claude-sonnet-4-6", apiKey: "sk-mock••••0000", baseUrl: "https://api.anthropic.com" };
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
async listMcpServers(): Promise<McpServerEntry[]> {
|
|
470
|
+
await wait();
|
|
471
|
+
return [...mockMcpServers];
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
async addMcpServer(name: string, config: Omit<McpServerEntry, "name">): Promise<McpServerEntry> {
|
|
475
|
+
await wait();
|
|
476
|
+
const server = { name, ...config };
|
|
477
|
+
mockMcpServers = [...mockMcpServers.filter((item) => item.name !== name), server];
|
|
478
|
+
return server;
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
async updateMcpServer(name: string, config: Omit<McpServerEntry, "name">): Promise<McpServerEntry> {
|
|
482
|
+
await wait();
|
|
483
|
+
const server = { name, ...config };
|
|
484
|
+
mockMcpServers = mockMcpServers.map((item) => (item.name === name ? server : item));
|
|
485
|
+
return server;
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
async removeMcpServer(name: string): Promise<void> {
|
|
489
|
+
await wait();
|
|
490
|
+
mockMcpServers = mockMcpServers.filter((item) => item.name !== name);
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
async listProviders(): Promise<ProviderProfile[]> {
|
|
494
|
+
await wait();
|
|
495
|
+
return [...mockProviders];
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
async createProvider(data: ProviderCreate): Promise<ProviderProfile> {
|
|
499
|
+
await wait();
|
|
500
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
501
|
+
const profile: ProviderProfile = {
|
|
502
|
+
id: crypto.randomUUID(),
|
|
503
|
+
name: data.name,
|
|
504
|
+
baseUrl: data.baseUrl,
|
|
505
|
+
models: data.models || [],
|
|
506
|
+
icon: data.icon || "circle",
|
|
507
|
+
iconColor: data.iconColor || "#111111",
|
|
508
|
+
notes: data.notes || "",
|
|
509
|
+
isActive: false,
|
|
510
|
+
apiKeyMasked: data.apiKey ? `${data.apiKey.slice(0, 5)}••••${data.apiKey.slice(-4)}` : "",
|
|
511
|
+
createdAt: timestamp,
|
|
512
|
+
updatedAt: timestamp,
|
|
513
|
+
healthStatus: "unknown",
|
|
514
|
+
healthCheckedAt: undefined,
|
|
515
|
+
modelHealth: [],
|
|
516
|
+
};
|
|
517
|
+
mockProviders = [profile, ...mockProviders];
|
|
518
|
+
return profile;
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
async updateProvider(id: string, data: ProviderUpdate): Promise<ProviderProfile> {
|
|
522
|
+
await wait();
|
|
523
|
+
let updated: ProviderProfile | null = null;
|
|
524
|
+
mockProviders = mockProviders.map((profile) => {
|
|
525
|
+
if (profile.id !== id) {
|
|
526
|
+
return profile;
|
|
527
|
+
}
|
|
528
|
+
updated = {
|
|
529
|
+
...profile,
|
|
530
|
+
...(data.name !== undefined ? { name: data.name } : {}),
|
|
531
|
+
...(data.baseUrl !== undefined ? { baseUrl: data.baseUrl } : {}),
|
|
532
|
+
...(data.models !== undefined ? { models: data.models } : {}),
|
|
533
|
+
...(data.icon !== undefined ? { icon: data.icon } : {}),
|
|
534
|
+
...(data.iconColor !== undefined ? { iconColor: data.iconColor } : {}),
|
|
535
|
+
...(data.notes !== undefined ? { notes: data.notes } : {}),
|
|
536
|
+
...(data.apiKey !== undefined ? { apiKeyMasked: `${data.apiKey.slice(0, 5)}••••${data.apiKey.slice(-4)}` } : {}),
|
|
537
|
+
updatedAt: Math.floor(Date.now() / 1000),
|
|
538
|
+
};
|
|
539
|
+
return updated;
|
|
540
|
+
});
|
|
541
|
+
if (!updated) {
|
|
542
|
+
throw new Error("Provider not found");
|
|
543
|
+
}
|
|
544
|
+
return updated;
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
async removeProvider(id: string): Promise<void> {
|
|
548
|
+
await wait();
|
|
549
|
+
mockProviders = mockProviders.filter((profile) => profile.id !== id || profile.isActive);
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
async getActiveProvider(): Promise<ProviderProfile | null> {
|
|
553
|
+
await wait();
|
|
554
|
+
return mockProviders.find((profile) => profile.isActive) || null;
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
async setActiveProvider(id: string): Promise<ProviderProfile> {
|
|
558
|
+
await wait();
|
|
559
|
+
let active: ProviderProfile | null = null;
|
|
560
|
+
mockProviders = mockProviders.map((profile) => {
|
|
561
|
+
const next = { ...profile, isActive: profile.id === id };
|
|
562
|
+
if (next.isActive) {
|
|
563
|
+
active = next;
|
|
564
|
+
}
|
|
565
|
+
return next;
|
|
566
|
+
});
|
|
567
|
+
if (!active) {
|
|
568
|
+
throw new Error("Provider not found");
|
|
569
|
+
}
|
|
570
|
+
return active;
|
|
571
|
+
},
|
|
572
|
+
|
|
573
|
+
async listProvidersHealth(): Promise<ProviderProfile[]> {
|
|
574
|
+
await wait();
|
|
575
|
+
return [...mockProviders];
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
async testProvider(id: string): Promise<ProviderProfile> {
|
|
579
|
+
await wait(600);
|
|
580
|
+
const profile = mockProviders.find((p) => p.id === id);
|
|
581
|
+
if (!profile) {
|
|
582
|
+
throw new Error("Provider not found");
|
|
583
|
+
}
|
|
584
|
+
const updated: ProviderProfile = {
|
|
585
|
+
...profile,
|
|
586
|
+
healthStatus: "healthy",
|
|
587
|
+
healthCheckedAt: Math.floor(Date.now() / 1000),
|
|
588
|
+
modelHealth: profile.models.map((model) => ({
|
|
589
|
+
model,
|
|
590
|
+
status: "healthy" as const,
|
|
591
|
+
latencyMs: Math.floor(Math.random() * 500) + 100,
|
|
592
|
+
})),
|
|
593
|
+
};
|
|
594
|
+
mockProviders = mockProviders.map((p) => (p.id === id ? updated : p));
|
|
595
|
+
return updated;
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
export async function mockSendUserMessage(
|
|
600
|
+
message: { sessionId: string; content: string; uuid: string; timestamp: string },
|
|
601
|
+
emit: (event: WebSocketEvent) => void,
|
|
602
|
+
) {
|
|
603
|
+
const base = {
|
|
604
|
+
sessionId: message.sessionId,
|
|
605
|
+
data: { agentName: "principal" },
|
|
606
|
+
};
|
|
607
|
+
const text =
|
|
608
|
+
`I can help turn "${message.content}" into a reproducible neuroscience workflow.\n\n` +
|
|
609
|
+
"Suggested next steps:\n" +
|
|
610
|
+
"1. Define the cohort and exclusion rules.\n" +
|
|
611
|
+
"2. Select preprocessing checkpoints.\n" +
|
|
612
|
+
"3. Record trace nodes for data, method, and result provenance.";
|
|
613
|
+
|
|
614
|
+
emit({
|
|
615
|
+
type: "user_message",
|
|
616
|
+
sessionId: message.sessionId,
|
|
617
|
+
seq: ++seq,
|
|
618
|
+
data: {
|
|
619
|
+
content: message.content,
|
|
620
|
+
uuid: message.uuid,
|
|
621
|
+
timestamp: message.timestamp,
|
|
622
|
+
role: "user",
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
await wait(120);
|
|
626
|
+
emit({ ...base, type: "message_start", seq: ++seq });
|
|
627
|
+
await wait(180);
|
|
628
|
+
emit({
|
|
629
|
+
...base,
|
|
630
|
+
type: "thinking_block_delta",
|
|
631
|
+
seq: ++seq,
|
|
632
|
+
data: { agentName: "principal", delta: { thinking: "Checking project context and sandbox state..." } },
|
|
633
|
+
});
|
|
634
|
+
await wait(80);
|
|
635
|
+
emit({ ...base, type: "thinking_block_stop", seq: ++seq });
|
|
636
|
+
await wait(160);
|
|
637
|
+
emit({
|
|
638
|
+
...base,
|
|
639
|
+
type: "content_block_start",
|
|
640
|
+
seq: ++seq,
|
|
641
|
+
data: {
|
|
642
|
+
agentName: "principal",
|
|
643
|
+
contentBlock: {
|
|
644
|
+
type: "tool_use",
|
|
645
|
+
id: "mock-tool-live-1",
|
|
646
|
+
name: "record_trace",
|
|
647
|
+
input: { summary: "Started reproducible workflow planning", node_type: "milestone" },
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
await wait(80);
|
|
652
|
+
emit({
|
|
653
|
+
...base,
|
|
654
|
+
type: "content_block_delta",
|
|
655
|
+
seq: ++seq,
|
|
656
|
+
data: {
|
|
657
|
+
agentName: "principal",
|
|
658
|
+
content: [
|
|
659
|
+
{
|
|
660
|
+
type: "tool_result",
|
|
661
|
+
tool_use_id: "mock-tool-live-1",
|
|
662
|
+
content: { status: "ok", node_id: "plan" },
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
await wait(40);
|
|
668
|
+
emit({ ...base, type: "content_block_stop", seq: ++seq });
|
|
669
|
+
|
|
670
|
+
if (message.content.toLowerCase().includes("error")) {
|
|
671
|
+
await wait(80);
|
|
672
|
+
emit({
|
|
673
|
+
...base,
|
|
674
|
+
type: "error",
|
|
675
|
+
seq: ++seq,
|
|
676
|
+
data: { agentName: "principal", error: { message: "Mock API error path rendered as an error card." } },
|
|
677
|
+
});
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
for (const chunk of text.match(/.{1,42}(\s|$)/g) || [text]) {
|
|
682
|
+
await wait(80);
|
|
683
|
+
emit({
|
|
684
|
+
...base,
|
|
685
|
+
type: "content_block_delta",
|
|
686
|
+
seq: ++seq,
|
|
687
|
+
data: { agentName: "principal", delta: { text: chunk } },
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
await wait(80);
|
|
692
|
+
emit({ ...base, type: "content_block_stop", seq: ++seq });
|
|
693
|
+
await wait(40);
|
|
694
|
+
emit({ ...base, type: "message_stop", seq: ++seq });
|
|
695
|
+
emit({
|
|
696
|
+
...base,
|
|
697
|
+
type: "agent_status",
|
|
698
|
+
seq: ++seq,
|
|
699
|
+
data: { agentName: "principal", status: "idle", task: "Ready" },
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export function mockTerminalResponse(command: string): string {
|
|
704
|
+
const trimmed = command.trim();
|
|
705
|
+
if (!trimmed) {
|
|
706
|
+
return "$ ";
|
|
707
|
+
}
|
|
708
|
+
if (trimmed === "pwd") {
|
|
709
|
+
return "/workspace\n$ ";
|
|
710
|
+
}
|
|
711
|
+
if (trimmed === "ls") {
|
|
712
|
+
return "README.md data/ reports/ src/\n$ ";
|
|
713
|
+
}
|
|
714
|
+
if (trimmed.startsWith("cat README")) {
|
|
715
|
+
return `${fileContents["/workspace/README.md"]}\n$ `;
|
|
716
|
+
}
|
|
717
|
+
return `mock-shell: ${trimmed}: command simulated\n$ `;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export function mockInitialTerminalOutput(): string {
|
|
721
|
+
return "Mock terminal connected to /workspace\n$ ";
|
|
722
|
+
}
|