reflexive 1.0.0 → 1.0.2
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/dashboard/out/404/index.html +1 -1
- package/dashboard/out/404.html +1 -1
- package/dashboard/out/__next.__PAGE__.txt +2 -2
- package/dashboard/out/__next._full.txt +3 -3
- package/dashboard/out/__next._head.txt +1 -1
- package/dashboard/out/__next._index.txt +2 -2
- package/dashboard/out/__next._tree.txt +2 -2
- package/dashboard/out/_next/static/chunks/209a165917e29ad7.css +3 -0
- package/dashboard/out/_next/static/chunks/{5be66f773d014294.js → 875b2f53bcfbbafb.js} +3 -3
- package/dashboard/out/_not-found/__next._full.txt +2 -2
- package/dashboard/out/_not-found/__next._head.txt +1 -1
- package/dashboard/out/_not-found/__next._index.txt +2 -2
- package/dashboard/out/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dashboard/out/_not-found/__next._not-found.txt +1 -1
- package/dashboard/out/_not-found/__next._tree.txt +2 -2
- package/dashboard/out/_not-found/index.html +1 -1
- package/dashboard/out/_not-found/index.txt +2 -2
- package/dashboard/out/index.html +1 -1
- package/dashboard/out/index.txt +3 -3
- package/dist/cli.d.ts +0 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +164 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -4
- package/dist/index.js.map +1 -1
- package/dist/managers/process-manager.d.ts +22 -8
- package/dist/managers/process-manager.d.ts.map +1 -1
- package/dist/managers/process-manager.js +50 -42
- package/dist/managers/process-manager.js.map +1 -1
- package/dist/mcp/knowledge-tools.d.ts +16 -0
- package/dist/mcp/knowledge-tools.d.ts.map +1 -0
- package/dist/mcp/knowledge-tools.js +206 -0
- package/dist/mcp/knowledge-tools.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/docs/README.md +87 -0
- package/docs/api-reference.md +1422 -0
- package/docs/deployment.md +885 -0
- package/docs/developer-guide.md +899 -0
- package/docs/getting-started.md +374 -0
- package/docs/index.md +158 -0
- package/docs/user-guide.md +990 -0
- package/package.json +2 -1
- package/dashboard/out/_next/static/chunks/82bbed47af1fbb39.css +0 -3
- /package/dashboard/out/_next/static/{vYzD8icR4qocco4JtUfu3 → Y9FaARzAOsacwE8LNVv_4}/_buildManifest.js +0 -0
- /package/dashboard/out/_next/static/{vYzD8icR4qocco4JtUfu3 → Y9FaARzAOsacwE8LNVv_4}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
# Developer Guide
|
|
2
|
+
|
|
3
|
+
Guide for developers who want to understand Reflexive's architecture, contribute code, or extend functionality.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Architecture](#architecture)
|
|
8
|
+
- [Project Structure](#project-structure)
|
|
9
|
+
- [Module Overview](#module-overview)
|
|
10
|
+
- [Data Flow](#data-flow)
|
|
11
|
+
- [Design Principles](#design-principles)
|
|
12
|
+
- [Development Setup](#development-setup)
|
|
13
|
+
- [Prerequisites](#prerequisites)
|
|
14
|
+
- [Cloning and Installation](#cloning-and-installation)
|
|
15
|
+
- [Building from Source](#building-from-source)
|
|
16
|
+
- [Running Tests](#running-tests)
|
|
17
|
+
- [Code Organization](#code-organization)
|
|
18
|
+
- [Types](#types)
|
|
19
|
+
- [Core Modules](#core-modules)
|
|
20
|
+
- [Managers](#managers)
|
|
21
|
+
- [MCP Tools](#mcp-tools)
|
|
22
|
+
- [Sandbox Utilities](#sandbox-utilities)
|
|
23
|
+
- [API Layer](#api-layer)
|
|
24
|
+
- [Testing](#testing)
|
|
25
|
+
- [Test Structure](#test-structure)
|
|
26
|
+
- [Running Tests](#running-tests-1)
|
|
27
|
+
- [Writing Tests](#writing-tests)
|
|
28
|
+
- [Mock System](#mock-system)
|
|
29
|
+
- [Contributing](#contributing)
|
|
30
|
+
- [Code Style](#code-style)
|
|
31
|
+
- [Commit Messages](#commit-messages)
|
|
32
|
+
- [Pull Request Process](#pull-request-process)
|
|
33
|
+
- [Issue Guidelines](#issue-guidelines)
|
|
34
|
+
- [Extending Reflexive](#extending-reflexive)
|
|
35
|
+
- [Adding Custom Tools](#adding-custom-tools)
|
|
36
|
+
- [Creating New Managers](#creating-new-managers)
|
|
37
|
+
- [Adding Storage Providers](#adding-storage-providers)
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
Reflexive is built as a modular TypeScript project with clear separation of concerns.
|
|
42
|
+
|
|
43
|
+
### Project Structure
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
reflexive/
|
|
47
|
+
├── src/
|
|
48
|
+
│ ├── index.ts # Library mode entry point, public API
|
|
49
|
+
│ ├── cli.ts # CLI entry point
|
|
50
|
+
│ ├── types/ # TypeScript type definitions
|
|
51
|
+
│ │ ├── index.ts # Core types (LogEntry, Capabilities, Config)
|
|
52
|
+
│ │ ├── sandbox.ts # Sandbox-specific types
|
|
53
|
+
│ │ ├── manager.ts # Manager interface types
|
|
54
|
+
│ │ └── mcp.ts # MCP tool types
|
|
55
|
+
│ ├── core/ # Core infrastructure
|
|
56
|
+
│ │ ├── app-state.ts # Circular log buffer, custom state store
|
|
57
|
+
│ │ ├── config-loader.ts # Config file discovery and merging
|
|
58
|
+
│ │ ├── dashboard.ts # HTML dashboard generator
|
|
59
|
+
│ │ ├── chat-stream.ts # SSE chat streaming via Claude Agent SDK
|
|
60
|
+
│ │ └── http-server.ts # Minimal HTTP server utilities
|
|
61
|
+
│ ├── managers/ # Process and sandbox managers
|
|
62
|
+
│ │ ├── process-manager.ts # Child process spawning, restart logic
|
|
63
|
+
│ │ ├── remote-debugger.ts # V8 Inspector protocol client
|
|
64
|
+
│ │ ├── sandbox-manager.ts # Single Vercel Sandbox wrapper
|
|
65
|
+
│ │ └── multi-sandbox-manager.ts # Multi-tenant sandbox pool
|
|
66
|
+
│ ├── mcp/ # MCP tool definitions
|
|
67
|
+
│ │ ├── tools.ts # Base tool helpers (createTool, textResult)
|
|
68
|
+
│ │ ├── local-tools.ts # Tools for local file operations
|
|
69
|
+
│ │ ├── cli-tools.ts # Tools for CLI mode (process control, logs)
|
|
70
|
+
│ │ ├── sandbox-tools.ts # Tools for sandbox mode
|
|
71
|
+
│ │ └── hosted-tools.ts # Tools for hosted multi-tenant mode
|
|
72
|
+
│ ├── sandbox/ # Sandbox utilities
|
|
73
|
+
│ │ ├── inject.ts # Sandbox injection script
|
|
74
|
+
│ │ ├── snapshot.ts # Filesystem snapshot/restore
|
|
75
|
+
│ │ └── storage.ts # S3/R2/Memory storage providers
|
|
76
|
+
│ ├── api/ # REST API for hosted mode
|
|
77
|
+
│ │ ├── routes.ts # REST endpoint definitions
|
|
78
|
+
│ │ └── auth.ts # API key auth and rate limiting
|
|
79
|
+
│ └── __tests__/ # Test suites (vitest)
|
|
80
|
+
│ ├── unit/ # Unit tests
|
|
81
|
+
│ ├── integration/ # Integration tests
|
|
82
|
+
│ ├── e2e/ # End-to-end tests
|
|
83
|
+
│ ├── fixtures/ # Test fixtures
|
|
84
|
+
│ └── mocks/ # Mock implementations
|
|
85
|
+
├── dist/ # Compiled output (gitignored)
|
|
86
|
+
├── package.json
|
|
87
|
+
├── tsconfig.json
|
|
88
|
+
├── tsconfig.build.json
|
|
89
|
+
├── vitest.config.ts
|
|
90
|
+
└── eslint.config.js
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Module Overview
|
|
94
|
+
|
|
95
|
+
#### Core Modules (`src/core/`)
|
|
96
|
+
|
|
97
|
+
**app-state.ts**
|
|
98
|
+
- Circular log buffer (500 entries by default)
|
|
99
|
+
- Custom key-value state storage
|
|
100
|
+
- Event emitter for state changes
|
|
101
|
+
- Process status tracking
|
|
102
|
+
|
|
103
|
+
**config-loader.ts**
|
|
104
|
+
- Configuration file discovery (reflexive.config.js, .reflexiverc, etc.)
|
|
105
|
+
- Config merging and validation
|
|
106
|
+
- Environment variable handling
|
|
107
|
+
- Default configuration generation
|
|
108
|
+
|
|
109
|
+
**dashboard.ts**
|
|
110
|
+
- Self-contained HTML dashboard generator
|
|
111
|
+
- No external dependencies, inline CSS/JS
|
|
112
|
+
- Real-time log viewer with ANSI color support
|
|
113
|
+
- Chat interface with SSE streaming
|
|
114
|
+
- Process control UI
|
|
115
|
+
|
|
116
|
+
**chat-stream.ts**
|
|
117
|
+
- SSE (Server-Sent Events) streaming for AI responses
|
|
118
|
+
- Claude Agent SDK integration
|
|
119
|
+
- Tool call handling and response formatting
|
|
120
|
+
- Error handling and recovery
|
|
121
|
+
|
|
122
|
+
**http-server.ts**
|
|
123
|
+
- Minimal HTTP server utilities
|
|
124
|
+
- Request parsing (JSON, URL params)
|
|
125
|
+
- Response helpers (JSON, HTML, errors)
|
|
126
|
+
- CORS handling
|
|
127
|
+
|
|
128
|
+
#### Managers (`src/managers/`)
|
|
129
|
+
|
|
130
|
+
**process-manager.ts**
|
|
131
|
+
- Child process lifecycle (spawn, kill, restart)
|
|
132
|
+
- stdout/stderr capture and parsing
|
|
133
|
+
- File watching with debounced restart
|
|
134
|
+
- stdin proxy for interactive apps
|
|
135
|
+
- Injection script management
|
|
136
|
+
- Integration with RemoteDebugger
|
|
137
|
+
|
|
138
|
+
**remote-debugger.ts**
|
|
139
|
+
- V8 Inspector protocol client over WebSocket
|
|
140
|
+
- Breakpoint management (set, remove, list)
|
|
141
|
+
- Execution control (pause, resume, step over/into/out)
|
|
142
|
+
- Call stack inspection
|
|
143
|
+
- Scope variable evaluation
|
|
144
|
+
- Script source mapping
|
|
145
|
+
|
|
146
|
+
**sandbox-manager.ts**
|
|
147
|
+
- Single Vercel Sandbox wrapper
|
|
148
|
+
- File upload/download
|
|
149
|
+
- Command execution
|
|
150
|
+
- Log polling from sandbox
|
|
151
|
+
- State synchronization
|
|
152
|
+
- Injection script deployment
|
|
153
|
+
|
|
154
|
+
**multi-sandbox-manager.ts**
|
|
155
|
+
- Multi-tenant sandbox pool
|
|
156
|
+
- Sandbox lifecycle management
|
|
157
|
+
- Snapshot creation and restoration
|
|
158
|
+
- Storage provider integration
|
|
159
|
+
- Per-sandbox state isolation
|
|
160
|
+
|
|
161
|
+
#### MCP Tools (`src/mcp/`)
|
|
162
|
+
|
|
163
|
+
**tools.ts**
|
|
164
|
+
- Base tool creation helpers
|
|
165
|
+
- Result formatting (text, JSON, error)
|
|
166
|
+
- Zod schema integration
|
|
167
|
+
- Tool combination and filtering
|
|
168
|
+
|
|
169
|
+
**local-tools.ts**
|
|
170
|
+
- File operations (read, write, delete, search)
|
|
171
|
+
- Directory listing
|
|
172
|
+
- Shell command execution
|
|
173
|
+
|
|
174
|
+
**cli-tools.ts**
|
|
175
|
+
- Process control tools
|
|
176
|
+
- Log retrieval and search
|
|
177
|
+
- State inspection
|
|
178
|
+
- stdin sending
|
|
179
|
+
|
|
180
|
+
**sandbox-tools.ts**
|
|
181
|
+
- Sandbox-specific operations
|
|
182
|
+
- File manipulation in sandbox
|
|
183
|
+
- Command execution
|
|
184
|
+
|
|
185
|
+
**hosted-tools.ts**
|
|
186
|
+
- Multi-sandbox management
|
|
187
|
+
- Snapshot operations
|
|
188
|
+
- Cross-sandbox queries
|
|
189
|
+
|
|
190
|
+
#### Sandbox Utilities (`src/sandbox/`)
|
|
191
|
+
|
|
192
|
+
**inject.ts**
|
|
193
|
+
- Injection script source
|
|
194
|
+
- Console interception
|
|
195
|
+
- HTTP request/response tracking (diagnostics_channel)
|
|
196
|
+
- GC event monitoring
|
|
197
|
+
- Event loop latency tracking
|
|
198
|
+
- Custom state API (process.reflexive)
|
|
199
|
+
|
|
200
|
+
**snapshot.ts**
|
|
201
|
+
- Filesystem snapshot creation
|
|
202
|
+
- Snapshot restoration
|
|
203
|
+
- Directory traversal and archiving
|
|
204
|
+
- Compression and validation
|
|
205
|
+
|
|
206
|
+
**storage.ts**
|
|
207
|
+
- Storage provider abstraction
|
|
208
|
+
- Memory storage (in-process)
|
|
209
|
+
- S3 storage (AWS S3, Cloudflare R2)
|
|
210
|
+
- Provider factory
|
|
211
|
+
|
|
212
|
+
#### API Layer (`src/api/`)
|
|
213
|
+
|
|
214
|
+
**routes.ts**
|
|
215
|
+
- REST endpoint definitions
|
|
216
|
+
- Request validation
|
|
217
|
+
- Response formatting
|
|
218
|
+
- Route matching (string and regex)
|
|
219
|
+
|
|
220
|
+
**auth.ts**
|
|
221
|
+
- API key authentication
|
|
222
|
+
- Rate limiting (token bucket)
|
|
223
|
+
- Public path handling
|
|
224
|
+
- Middleware creation
|
|
225
|
+
|
|
226
|
+
### Data Flow
|
|
227
|
+
|
|
228
|
+
#### CLI Mode Data Flow
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
┌─────────────┐
|
|
232
|
+
│ User │
|
|
233
|
+
│ Terminal │
|
|
234
|
+
└──────┬──────┘
|
|
235
|
+
│ reflexive app.js
|
|
236
|
+
▼
|
|
237
|
+
┌─────────────────────────────────────────┐
|
|
238
|
+
│ Reflexive CLI │
|
|
239
|
+
│ - Parse args │
|
|
240
|
+
│ - Load config │
|
|
241
|
+
│ - Create ProcessManager │
|
|
242
|
+
│ - Start HTTP server │
|
|
243
|
+
└──────┬──────────────────────────────────┘
|
|
244
|
+
│
|
|
245
|
+
▼
|
|
246
|
+
┌─────────────────┐ ┌───────────────┐
|
|
247
|
+
│ ProcessManager │◄─────┤ AppState │
|
|
248
|
+
│ - Spawn app.js │ │ - Logs │
|
|
249
|
+
│ - Capture stdout│ │ - State │
|
|
250
|
+
│ - Watch files │ │ - Events │
|
|
251
|
+
└────────┬────────┘ └───────────────┘
|
|
252
|
+
│
|
|
253
|
+
▼
|
|
254
|
+
┌─────────────────┐
|
|
255
|
+
│ app.js │
|
|
256
|
+
│ (Child Process) │
|
|
257
|
+
└─────────────────┘
|
|
258
|
+
|
|
259
|
+
┌──────────────────────────────────┐
|
|
260
|
+
│ HTTP Server │
|
|
261
|
+
│ - Dashboard UI │
|
|
262
|
+
│ - Chat endpoint (SSE) │
|
|
263
|
+
│ - Status/logs endpoints │
|
|
264
|
+
└────────┬─────────────────────────┘
|
|
265
|
+
│
|
|
266
|
+
▼
|
|
267
|
+
┌──────────────────┐
|
|
268
|
+
│ Browser Client │
|
|
269
|
+
│ - Chat UI │
|
|
270
|
+
│ - Log viewer │
|
|
271
|
+
│ - Controls │
|
|
272
|
+
└──────────────────┘
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### Sandbox Mode Data Flow
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
┌─────────────┐
|
|
279
|
+
│ CLI │
|
|
280
|
+
└──────┬──────┘
|
|
281
|
+
│ reflexive --sandbox app.js
|
|
282
|
+
▼
|
|
283
|
+
┌─────────────────────────────────────┐
|
|
284
|
+
│ SandboxManager (Local) │
|
|
285
|
+
│ - Create Vercel Sandbox │
|
|
286
|
+
│ - Upload files │
|
|
287
|
+
│ - Upload inject script │
|
|
288
|
+
│ - Start process │
|
|
289
|
+
│ - Poll logs │
|
|
290
|
+
└──────┬──────────────────────────────┘
|
|
291
|
+
│
|
|
292
|
+
│ WebSocket/HTTP
|
|
293
|
+
▼
|
|
294
|
+
┌─────────────────────────────────────┐
|
|
295
|
+
│ Vercel Sandbox (Remote) │
|
|
296
|
+
│ - Isolated filesystem │
|
|
297
|
+
│ - Run app.js │
|
|
298
|
+
│ - Write logs to /tmp/... │
|
|
299
|
+
└──────┬──────────────────────────────┘
|
|
300
|
+
│
|
|
301
|
+
│ Log polling
|
|
302
|
+
▼
|
|
303
|
+
┌─────────────────┐
|
|
304
|
+
│ AppState │
|
|
305
|
+
│ - Store logs │
|
|
306
|
+
│ - Track state │
|
|
307
|
+
└─────────────────┘
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Hosted Mode Data Flow
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
┌─────────────┐
|
|
314
|
+
│ API Client │
|
|
315
|
+
└──────┬──────┘
|
|
316
|
+
│ POST /api/sandboxes
|
|
317
|
+
▼
|
|
318
|
+
┌─────────────────────────────────────┐
|
|
319
|
+
│ REST API Server │
|
|
320
|
+
│ - Auth middleware │
|
|
321
|
+
│ - Rate limiting │
|
|
322
|
+
│ - Route handling │
|
|
323
|
+
└──────┬──────────────────────────────┘
|
|
324
|
+
│
|
|
325
|
+
▼
|
|
326
|
+
┌─────────────────────────────────────┐
|
|
327
|
+
│ MultiSandboxManager │
|
|
328
|
+
│ - Create sandbox │
|
|
329
|
+
│ - Manage lifecycle │
|
|
330
|
+
│ - Create snapshots │
|
|
331
|
+
│ - Restore from snapshots │
|
|
332
|
+
└──────┬──────────────────────────────┘
|
|
333
|
+
│
|
|
334
|
+
▼
|
|
335
|
+
┌─────────────────┬────────────────────┐
|
|
336
|
+
│ SandboxManager │ StorageProvider │
|
|
337
|
+
│ (per sandbox) │ (S3/R2/Memory) │
|
|
338
|
+
└─────────────────┴────────────────────┘
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Design Principles
|
|
342
|
+
|
|
343
|
+
1. **Modularity**: Each module has a single responsibility
|
|
344
|
+
2. **Type Safety**: Strict TypeScript for compile-time guarantees
|
|
345
|
+
3. **Testability**: Dependency injection, mockable interfaces
|
|
346
|
+
4. **Extensibility**: Plugin system for custom tools and storage
|
|
347
|
+
5. **Safety**: Explicit opt-in for dangerous operations
|
|
348
|
+
6. **Performance**: Circular buffers, efficient log polling
|
|
349
|
+
7. **Developer Experience**: Clear APIs, comprehensive types
|
|
350
|
+
|
|
351
|
+
## Development Setup
|
|
352
|
+
|
|
353
|
+
### Prerequisites
|
|
354
|
+
|
|
355
|
+
- Node.js >= 18.0.0
|
|
356
|
+
- npm or yarn
|
|
357
|
+
- Git
|
|
358
|
+
- Claude API access (for testing chat features)
|
|
359
|
+
|
|
360
|
+
### Cloning and Installation
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Clone repository
|
|
364
|
+
git clone https://github.com/yourusername/reflexive.git
|
|
365
|
+
cd reflexive
|
|
366
|
+
|
|
367
|
+
# Install dependencies
|
|
368
|
+
npm install
|
|
369
|
+
|
|
370
|
+
# Build TypeScript
|
|
371
|
+
npm run build
|
|
372
|
+
|
|
373
|
+
# Run tests
|
|
374
|
+
npm test
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Building from Source
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
# Clean previous build
|
|
381
|
+
npm run clean
|
|
382
|
+
|
|
383
|
+
# Type check (no emit)
|
|
384
|
+
npm run typecheck
|
|
385
|
+
|
|
386
|
+
# Build
|
|
387
|
+
npm run build
|
|
388
|
+
|
|
389
|
+
# Watch mode for development
|
|
390
|
+
npm run dev
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Build output goes to `dist/` directory.
|
|
394
|
+
|
|
395
|
+
### Running Tests
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
# Run all tests once
|
|
399
|
+
npm test
|
|
400
|
+
|
|
401
|
+
# Watch mode for development
|
|
402
|
+
npm run test:watch
|
|
403
|
+
|
|
404
|
+
# With coverage report
|
|
405
|
+
npm run test:coverage
|
|
406
|
+
|
|
407
|
+
# Run specific test file
|
|
408
|
+
npx vitest src/__tests__/unit/app-state.test.ts
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Code Organization
|
|
412
|
+
|
|
413
|
+
### Types
|
|
414
|
+
|
|
415
|
+
All TypeScript types are in `src/types/`:
|
|
416
|
+
|
|
417
|
+
**index.ts**: Core types used across the codebase
|
|
418
|
+
- `LogEntry`, `LogType`
|
|
419
|
+
- `AppStatus`, `ProcessState`
|
|
420
|
+
- `Capabilities`, `ReflexiveConfig`
|
|
421
|
+
|
|
422
|
+
**sandbox.ts**: Sandbox-specific types
|
|
423
|
+
- `SandboxStatus`, `SandboxInstance`
|
|
424
|
+
- `Snapshot`, `SnapshotFile`
|
|
425
|
+
- `CommandResult`
|
|
426
|
+
|
|
427
|
+
**manager.ts**: Manager interface definitions
|
|
428
|
+
- `BaseManager`
|
|
429
|
+
- `SandboxManagerInterface`
|
|
430
|
+
- `MultiSandboxManagerInterface`
|
|
431
|
+
|
|
432
|
+
**mcp.ts**: MCP tool types
|
|
433
|
+
- `McpTool`, `ToolResult`
|
|
434
|
+
- `ChatStreamEvent`, `ChatOptions`
|
|
435
|
+
|
|
436
|
+
### Core Modules
|
|
437
|
+
|
|
438
|
+
**AppState** manages application state:
|
|
439
|
+
```typescript
|
|
440
|
+
class AppState {
|
|
441
|
+
private logs: LogEntry[] = []; // Circular buffer
|
|
442
|
+
private customState: Map<string, unknown> = new Map();
|
|
443
|
+
|
|
444
|
+
log(type: LogType, message: string, meta?: unknown): void
|
|
445
|
+
getLogs(count?: number, type?: string): LogEntry[]
|
|
446
|
+
setState(key: string, value: unknown): void
|
|
447
|
+
getState(key?: string): unknown
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**ConfigLoader** handles configuration:
|
|
452
|
+
```typescript
|
|
453
|
+
export function loadConfig(configPath?: string): ReflexiveConfig
|
|
454
|
+
export function findConfigFile(): string | null
|
|
455
|
+
export function getDefaultConfig(): ReflexiveConfig
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Managers
|
|
459
|
+
|
|
460
|
+
Managers follow a common interface pattern:
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
interface BaseManager {
|
|
464
|
+
start(entry: string, args?: string[]): Promise<void>;
|
|
465
|
+
stop(): Promise<void>;
|
|
466
|
+
restart(): Promise<void>;
|
|
467
|
+
isRunning(): boolean;
|
|
468
|
+
on(event: string, handler: EventHandler): void;
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**ProcessManager** for local processes:
|
|
473
|
+
```typescript
|
|
474
|
+
class ProcessManager implements BaseManager {
|
|
475
|
+
private child: ChildProcess | null;
|
|
476
|
+
private watcher: FSWatcher | null;
|
|
477
|
+
private debugger: RemoteDebugger | null;
|
|
478
|
+
|
|
479
|
+
async start(): Promise<void>
|
|
480
|
+
async stop(): Promise<void>
|
|
481
|
+
async sendInput(text: string): Promise<void>
|
|
482
|
+
getState(): ProcessState
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### MCP Tools
|
|
487
|
+
|
|
488
|
+
Tools are created using the `createTool` helper:
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
import { createTool, textResult } from './tools.js';
|
|
492
|
+
import { z } from 'zod';
|
|
493
|
+
|
|
494
|
+
export const readFileTool = createTool(
|
|
495
|
+
'read_file',
|
|
496
|
+
'Read file contents',
|
|
497
|
+
z.object({
|
|
498
|
+
path: z.string().describe('File path to read')
|
|
499
|
+
}),
|
|
500
|
+
async ({ path }) => {
|
|
501
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
502
|
+
return textResult(content);
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Tool result helpers:
|
|
508
|
+
```typescript
|
|
509
|
+
textResult(text: string): ToolResult
|
|
510
|
+
jsonResult(data: unknown): ToolResult
|
|
511
|
+
errorResult(message: string): ToolResult
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Sandbox Utilities
|
|
515
|
+
|
|
516
|
+
**Injection script** provides runtime APIs:
|
|
517
|
+
```typescript
|
|
518
|
+
// In sandbox/inject.ts
|
|
519
|
+
process.reflexive = {
|
|
520
|
+
setState(key: string, value: unknown): void,
|
|
521
|
+
emit(event: string, data: unknown): void,
|
|
522
|
+
log(level: string, ...args: unknown[]): void
|
|
523
|
+
};
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Snapshot** creates filesystem archives:
|
|
527
|
+
```typescript
|
|
528
|
+
export async function createSnapshot(
|
|
529
|
+
dir: string,
|
|
530
|
+
options?: CreateSnapshotOptions
|
|
531
|
+
): Promise<Snapshot>
|
|
532
|
+
|
|
533
|
+
export async function restoreFromSnapshot(
|
|
534
|
+
snapshot: Snapshot,
|
|
535
|
+
targetDir: string
|
|
536
|
+
): Promise<RestoreResult>
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### API Layer
|
|
540
|
+
|
|
541
|
+
**Routes** define REST endpoints:
|
|
542
|
+
```typescript
|
|
543
|
+
export interface Route {
|
|
544
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
545
|
+
path: string | RegExp;
|
|
546
|
+
handler: RequestHandler;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export function createApiRoutes(config: ApiRoutesConfig): Route[]
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**Auth** provides middleware:
|
|
553
|
+
```typescript
|
|
554
|
+
export function createAuthMiddleware(
|
|
555
|
+
config: AuthConfig
|
|
556
|
+
): (req, res, pathname) => Promise<boolean>
|
|
557
|
+
|
|
558
|
+
export function createRateLimiter(
|
|
559
|
+
config: RateLimitConfig
|
|
560
|
+
): (req, res) => boolean
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
## Testing
|
|
564
|
+
|
|
565
|
+
### Test Structure
|
|
566
|
+
|
|
567
|
+
Tests are organized by type:
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
src/__tests__/
|
|
571
|
+
├── unit/ # Unit tests (isolated)
|
|
572
|
+
├── integration/ # Integration tests (multiple modules)
|
|
573
|
+
├── e2e/ # End-to-end tests (full workflows)
|
|
574
|
+
├── fixtures/ # Test data and sample apps
|
|
575
|
+
└── mocks/ # Mock implementations
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Running Tests
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
# All tests
|
|
582
|
+
npm test
|
|
583
|
+
|
|
584
|
+
# Unit tests only
|
|
585
|
+
npx vitest src/__tests__/unit/
|
|
586
|
+
|
|
587
|
+
# Integration tests
|
|
588
|
+
npx vitest src/__tests__/integration/
|
|
589
|
+
|
|
590
|
+
# E2E tests (requires credentials)
|
|
591
|
+
npx vitest src/__tests__/e2e/
|
|
592
|
+
|
|
593
|
+
# Specific file
|
|
594
|
+
npx vitest src/__tests__/unit/app-state.test.ts
|
|
595
|
+
|
|
596
|
+
# Watch mode
|
|
597
|
+
npm run test:watch
|
|
598
|
+
|
|
599
|
+
# Coverage report
|
|
600
|
+
npm run test:coverage
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### Writing Tests
|
|
604
|
+
|
|
605
|
+
Use Vitest with TypeScript:
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
609
|
+
import { AppState } from '../../core/app-state.js';
|
|
610
|
+
|
|
611
|
+
describe('AppState', () => {
|
|
612
|
+
let appState: AppState;
|
|
613
|
+
|
|
614
|
+
beforeEach(() => {
|
|
615
|
+
appState = new AppState();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe('log()', () => {
|
|
619
|
+
it('adds log entry with timestamp', () => {
|
|
620
|
+
appState.log('info', 'test message');
|
|
621
|
+
const logs = appState.getLogs();
|
|
622
|
+
|
|
623
|
+
expect(logs).toHaveLength(1);
|
|
624
|
+
expect(logs[0].type).toBe('info');
|
|
625
|
+
expect(logs[0].message).toBe('test message');
|
|
626
|
+
expect(logs[0].timestamp).toBeDefined();
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('maintains circular buffer', () => {
|
|
630
|
+
// Add more than maxLogs
|
|
631
|
+
for (let i = 0; i < 600; i++) {
|
|
632
|
+
appState.log('info', `message ${i}`);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const logs = appState.getLogs();
|
|
636
|
+
expect(logs).toHaveLength(500);
|
|
637
|
+
expect(logs[0].message).toBe('message 100');
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Mock System
|
|
644
|
+
|
|
645
|
+
Mocks for testing without external dependencies:
|
|
646
|
+
|
|
647
|
+
**MockSandbox** (`__tests__/mocks/sandbox-mock.ts`):
|
|
648
|
+
```typescript
|
|
649
|
+
export class MockSandbox {
|
|
650
|
+
files: Map<string, string> = new Map();
|
|
651
|
+
commands: { cmd: string; args: string[] }[] = [];
|
|
652
|
+
|
|
653
|
+
async writeFiles(files: SandboxFile[]): Promise<void>
|
|
654
|
+
async runCommand(options: any): Promise<CommandResult>
|
|
655
|
+
async stop(): Promise<void>
|
|
656
|
+
}
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
**MockAnthropicClient** (`__tests__/mocks/anthropic-mock.ts`):
|
|
660
|
+
```typescript
|
|
661
|
+
export function createMockQuery() {
|
|
662
|
+
return async function* (options: any) {
|
|
663
|
+
yield { type: 'text', content: 'Mock response' };
|
|
664
|
+
yield { type: 'done' };
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Usage in tests:
|
|
670
|
+
```typescript
|
|
671
|
+
import { vi } from 'vitest';
|
|
672
|
+
import { MockSandbox } from '../mocks/sandbox-mock.js';
|
|
673
|
+
|
|
674
|
+
vi.mock('@vercel/sandbox', () => ({
|
|
675
|
+
Sandbox: MockSandbox
|
|
676
|
+
}));
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
## Contributing
|
|
680
|
+
|
|
681
|
+
### Code Style
|
|
682
|
+
|
|
683
|
+
- **TypeScript**: Strict mode enabled
|
|
684
|
+
- **Formatting**: Prettier with 2-space indents
|
|
685
|
+
- **Linting**: ESLint with TypeScript rules
|
|
686
|
+
- **Naming**:
|
|
687
|
+
- Classes: PascalCase (`AppState`, `ProcessManager`)
|
|
688
|
+
- Functions: camelCase (`loadConfig`, `createTool`)
|
|
689
|
+
- Constants: UPPER_SNAKE_CASE (`MAX_LOGS`)
|
|
690
|
+
- Types/Interfaces: PascalCase (`LogEntry`, `ReflexiveConfig`)
|
|
691
|
+
|
|
692
|
+
Run before committing:
|
|
693
|
+
```bash
|
|
694
|
+
npm run lint
|
|
695
|
+
npm run typecheck
|
|
696
|
+
npm test
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Commit Messages
|
|
700
|
+
|
|
701
|
+
Follow conventional commits:
|
|
702
|
+
|
|
703
|
+
```
|
|
704
|
+
feat: add sandbox snapshot creation
|
|
705
|
+
fix: resolve memory leak in log buffer
|
|
706
|
+
docs: update API reference for new tools
|
|
707
|
+
test: add integration tests for ProcessManager
|
|
708
|
+
refactor: extract config loading to separate module
|
|
709
|
+
chore: update dependencies
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Pull Request Process
|
|
713
|
+
|
|
714
|
+
1. **Fork and Branch**
|
|
715
|
+
```bash
|
|
716
|
+
git checkout -b feature/my-feature
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
2. **Make Changes**
|
|
720
|
+
- Write code with tests
|
|
721
|
+
- Update documentation
|
|
722
|
+
- Run linter and tests
|
|
723
|
+
|
|
724
|
+
3. **Commit**
|
|
725
|
+
```bash
|
|
726
|
+
git add .
|
|
727
|
+
git commit -m "feat: add my feature"
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
4. **Push and PR**
|
|
731
|
+
```bash
|
|
732
|
+
git push origin feature/my-feature
|
|
733
|
+
```
|
|
734
|
+
- Open PR on GitHub
|
|
735
|
+
- Fill in PR template
|
|
736
|
+
- Link related issues
|
|
737
|
+
|
|
738
|
+
5. **Review**
|
|
739
|
+
- Address review comments
|
|
740
|
+
- Keep PR updated with main
|
|
741
|
+
|
|
742
|
+
6. **Merge**
|
|
743
|
+
- Squash and merge
|
|
744
|
+
- Delete branch
|
|
745
|
+
|
|
746
|
+
### Issue Guidelines
|
|
747
|
+
|
|
748
|
+
**Bug Reports** should include:
|
|
749
|
+
- Reflexive version
|
|
750
|
+
- Node.js version
|
|
751
|
+
- Operating system
|
|
752
|
+
- Minimal reproduction
|
|
753
|
+
- Expected vs actual behavior
|
|
754
|
+
- Logs/screenshots
|
|
755
|
+
|
|
756
|
+
**Feature Requests** should include:
|
|
757
|
+
- Use case description
|
|
758
|
+
- Proposed API/UX
|
|
759
|
+
- Alternatives considered
|
|
760
|
+
- Willingness to implement
|
|
761
|
+
|
|
762
|
+
## Extending Reflexive
|
|
763
|
+
|
|
764
|
+
### Adding Custom Tools
|
|
765
|
+
|
|
766
|
+
Create a custom tool module:
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
// tools/database-tools.ts
|
|
770
|
+
import { createTool, textResult, jsonResult } from 'reflexive/mcp/tools';
|
|
771
|
+
import { z } from 'zod';
|
|
772
|
+
|
|
773
|
+
export const queryDatabaseTool = createTool(
|
|
774
|
+
'query_database',
|
|
775
|
+
'Execute SQL query',
|
|
776
|
+
z.object({
|
|
777
|
+
sql: z.string().describe('SQL query to execute')
|
|
778
|
+
}),
|
|
779
|
+
async ({ sql }) => {
|
|
780
|
+
const results = await db.query(sql);
|
|
781
|
+
return jsonResult(results);
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
export const getDatabaseStatsTool = createTool(
|
|
786
|
+
'get_database_stats',
|
|
787
|
+
'Get database statistics',
|
|
788
|
+
z.object({}),
|
|
789
|
+
async () => {
|
|
790
|
+
const stats = await db.getStats();
|
|
791
|
+
return textResult(`Tables: ${stats.tables}, Size: ${stats.size}`);
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Use in your app:
|
|
797
|
+
```typescript
|
|
798
|
+
import { makeReflexive } from 'reflexive';
|
|
799
|
+
import { queryDatabaseTool, getDatabaseStatsTool } from './tools/database-tools.js';
|
|
800
|
+
|
|
801
|
+
const r = makeReflexive({
|
|
802
|
+
tools: [queryDatabaseTool, getDatabaseStatsTool]
|
|
803
|
+
});
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Creating New Managers
|
|
807
|
+
|
|
808
|
+
Implement the `BaseManager` interface:
|
|
809
|
+
|
|
810
|
+
```typescript
|
|
811
|
+
import { BaseManager } from 'reflexive/types/manager';
|
|
812
|
+
import { AppState } from 'reflexive/core/app-state';
|
|
813
|
+
import { EventEmitter } from 'events';
|
|
814
|
+
|
|
815
|
+
export class CustomManager extends EventEmitter implements BaseManager {
|
|
816
|
+
private appState: AppState;
|
|
817
|
+
private running = false;
|
|
818
|
+
|
|
819
|
+
constructor(options: CustomManagerOptions) {
|
|
820
|
+
super();
|
|
821
|
+
this.appState = options.appState || new AppState();
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async start(entry: string, args?: string[]): Promise<void> {
|
|
825
|
+
// Implementation
|
|
826
|
+
this.running = true;
|
|
827
|
+
this.emit('start', { entry });
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
async stop(): Promise<void> {
|
|
831
|
+
// Implementation
|
|
832
|
+
this.running = false;
|
|
833
|
+
this.emit('stop');
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
async restart(): Promise<void> {
|
|
837
|
+
await this.stop();
|
|
838
|
+
await this.start();
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
isRunning(): boolean {
|
|
842
|
+
return this.running;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
getLogs(count?: number): LogEntry[] {
|
|
846
|
+
return this.appState.getLogs(count);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// ... implement other BaseManager methods
|
|
850
|
+
}
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
### Adding Storage Providers
|
|
854
|
+
|
|
855
|
+
Implement the `StorageProvider` interface:
|
|
856
|
+
|
|
857
|
+
```typescript
|
|
858
|
+
import { StorageProvider, Snapshot } from 'reflexive/sandbox/storage';
|
|
859
|
+
|
|
860
|
+
export class CustomStorageProvider implements StorageProvider {
|
|
861
|
+
async save(snapshot: Snapshot): Promise<string> {
|
|
862
|
+
// Save snapshot, return ID
|
|
863
|
+
const id = generateId();
|
|
864
|
+
await this.backend.write(id, JSON.stringify(snapshot));
|
|
865
|
+
return id;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
async load(id: string): Promise<Snapshot> {
|
|
869
|
+
// Load snapshot by ID
|
|
870
|
+
const data = await this.backend.read(id);
|
|
871
|
+
return JSON.parse(data);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
async delete(id: string): Promise<void> {
|
|
875
|
+
// Delete snapshot
|
|
876
|
+
await this.backend.delete(id);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
async list(): Promise<Snapshot[]> {
|
|
880
|
+
// List all snapshots
|
|
881
|
+
const ids = await this.backend.listIds();
|
|
882
|
+
return Promise.all(ids.map(id => this.load(id)));
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
Register the provider:
|
|
888
|
+
```typescript
|
|
889
|
+
import { createStorageProvider } from 'reflexive/sandbox/storage';
|
|
890
|
+
|
|
891
|
+
const storage = createStorageProvider({
|
|
892
|
+
provider: 'custom',
|
|
893
|
+
config: { /* ... */ }
|
|
894
|
+
});
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
---
|
|
898
|
+
|
|
899
|
+
**Next**: See [Deployment Guide](./deployment.md) for production deployment instructions.
|