@game_ryo/lsji 0.1.0 → 0.3.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 (75) hide show
  1. package/package.json +15 -7
  2. package/src/cli.js +395 -62
  3. package/src/execution/budget/circuit-breaker.js +245 -0
  4. package/src/execution/budget/cost-tracker.js +387 -0
  5. package/src/execution/budget/index.js +63 -0
  6. package/src/execution/budget/token-counter.js +159 -0
  7. package/src/execution/engine.js +428 -0
  8. package/src/execution/hitl/approval-gate.js +210 -0
  9. package/src/execution/hitl/index.js +12 -0
  10. package/src/execution/hitl/notifier.js +151 -0
  11. package/src/execution/hitl/store.js +311 -0
  12. package/src/execution/idempotency.js +312 -0
  13. package/src/execution/index.js +14 -0
  14. package/src/index.js +80 -4
  15. package/src/llm/index.js +21 -0
  16. package/src/llm/llm-agent.js +357 -0
  17. package/src/llm/memory/conversation.js +271 -0
  18. package/src/llm/memory/episodic.js +312 -0
  19. package/src/llm/memory/index.js +12 -0
  20. package/src/llm/memory/semantic.js +324 -0
  21. package/src/llm/plugins/index.js +202 -0
  22. package/src/llm/prompt-manager.js +332 -0
  23. package/src/llm/providers/anthropic.js +250 -0
  24. package/src/llm/providers/base.js +116 -0
  25. package/src/llm/providers/local.js +163 -0
  26. package/src/llm/providers/openai.js +212 -0
  27. package/src/llm/tools/registry.js +342 -0
  28. package/src/server/index.js +416 -0
  29. package/src/server/ui/index.html +16 -0
  30. package/src/server/ui/package.json +19 -0
  31. package/src/server/ui/src/main.jsx +10 -0
  32. package/src/server/ui/src/styles.css +260 -0
  33. package/src/server/ui/vite.config.js +27 -0
  34. package/docs/README.md +0 -43
  35. package/docs/blog/2019-05-28-first-blog-post.mdx +0 -12
  36. package/docs/blog/2019-05-29-long-blog-post.mdx +0 -44
  37. package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
  38. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  39. package/docs/blog/2021-08-26-welcome/index.mdx +0 -29
  40. package/docs/blog/authors.yml +0 -25
  41. package/docs/blog/tags.yml +0 -19
  42. package/docs/docs/api/agent.md +0 -151
  43. package/docs/docs/api/env.md +0 -133
  44. package/docs/docs/api/environments.md +0 -102
  45. package/docs/docs/api/qlearning.md +0 -138
  46. package/docs/docs/api/storage.md +0 -168
  47. package/docs/docs/architecture.md +0 -155
  48. package/docs/docs/cli.md +0 -210
  49. package/docs/docs/contributing.md +0 -162
  50. package/docs/docs/core-concepts.md +0 -152
  51. package/docs/docs/examples/advanced-training.md +0 -244
  52. package/docs/docs/examples/custom-environment.md +0 -198
  53. package/docs/docs/examples/custom-storage.md +0 -251
  54. package/docs/docs/getting-started.md +0 -91
  55. package/docs/docusaurus.config.ts +0 -149
  56. package/docs/package-lock.json +0 -19522
  57. package/docs/package.json +0 -49
  58. package/docs/sidebars.ts +0 -33
  59. package/docs/src/components/HomepageFeatures/index.tsx +0 -71
  60. package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  61. package/docs/src/css/custom.css +0 -79
  62. package/docs/src/pages/index.module.css +0 -23
  63. package/docs/src/pages/index.tsx +0 -44
  64. package/docs/src/pages/markdown-page.mdx +0 -7
  65. package/docs/static/.nojekyll +0 -0
  66. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  67. package/docs/static/img/docusaurus.png +0 -0
  68. package/docs/static/img/favicon.ico +0 -0
  69. package/docs/static/img/logo.png +0 -0
  70. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  71. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  72. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  73. package/docs/tsconfig.json +0 -12
  74. package/legacy/worker.js +0 -166
  75. package/legacy/wrangler.toml +0 -11
@@ -0,0 +1,416 @@
1
+ /**
2
+ * LSJI Runtime Server
3
+ *
4
+ * HTTP + WebSocket server for agent control panel.
5
+ * Provides real-time thought logs, approval queue, budget monitoring.
6
+ */
7
+
8
+ import express from 'express';
9
+ import { createServer } from 'http';
10
+ import { Server } from 'socket.io';
11
+ import cors from 'cors';
12
+ import { fileURLToPath } from 'url';
13
+ import { dirname, resolve, join } from 'path';
14
+ import { createExecutionEngine } from '../execution/engine.js';
15
+ import { createApprovalGate } from '../execution/hitl/approval-gate.js';
16
+ import { createBudgetController } from '../execution/budget/index.js';
17
+ import { createLLMAgent } from '../llm/llm-agent.js';
18
+ import { createStorage } from '../storage/index.js';
19
+ import { createToolRegistry } from '../llm/tools/registry.js';
20
+ import { loadPlugins } from '../llm/plugins/index.js';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+
25
+ // Server state
26
+ const activeRuns = new Map(); // runId -> { agent, workflowId, status, startTime }
27
+ const connectedClients = new Set();
28
+
29
+ /**
30
+ * Create and configure Express app
31
+ */
32
+ export function createApp(config = {}) {
33
+ const app = express();
34
+ const httpServer = createServer(app);
35
+
36
+ // Socket.io with CORS
37
+ const io = new Server(httpServer, {
38
+ cors: {
39
+ origin: config.corsOrigin || '*',
40
+ methods: ['GET', 'POST'],
41
+ credentials: true,
42
+ },
43
+ pingTimeout: 60000,
44
+ pingInterval: 25000,
45
+ });
46
+
47
+ // Middleware
48
+ app.use(cors({ origin: config.corsOrigin || '*', credentials: true }));
49
+ app.use(express.json({ limit: '10mb' }));
50
+ app.use(express.urlencoded({ extended: true }));
51
+
52
+ // Health check
53
+ app.get('/health', (req, res) => {
54
+ res.json({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime() });
55
+ });
56
+
57
+ // API: List active runs
58
+ app.get('/api/runs', (req, res) => {
59
+ const runs = Array.from(activeRuns.entries()).map(([runId, data]) => ({
60
+ runId,
61
+ workflowId: data.workflowId,
62
+ status: data.status,
63
+ startTime: data.startTime,
64
+ duration: Date.now() - data.startTime,
65
+ task: data.task?.slice(0, 100),
66
+ }));
67
+ res.json({ runs });
68
+ });
69
+
70
+ // API: Get run details
71
+ app.get('/api/runs/:runId', (req, res) => {
72
+ const run = activeRuns.get(req.params.runId);
73
+ if (!run) {
74
+ return res.status(404).json({ error: 'Run not found' });
75
+ }
76
+ res.json({
77
+ runId: req.params.runId,
78
+ workflowId: run.workflowId,
79
+ status: run.status,
80
+ startTime: run.startTime,
81
+ task: run.task,
82
+ steps: run.steps,
83
+ budget: run.budget,
84
+ currentThought: run.currentThought,
85
+ });
86
+ });
87
+
88
+ // API: Start new agent run
89
+ app.post('/api/runs', async (req, res) => {
90
+ try {
91
+ const {
92
+ task,
93
+ workflowId,
94
+ llm = { provider: 'openai', model: 'gpt-4o-mini' },
95
+ budget = { maxCostPerRun: 10 },
96
+ hitl = { enabled: true },
97
+ plugins = [],
98
+ } = req.body;
99
+
100
+ if (!task) {
101
+ return res.status(400).json({ error: 'Task is required' });
102
+ }
103
+
104
+ const runId = `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
105
+ const wfId = workflowId || `wf_${Date.now()}`;
106
+
107
+ // Initialize storage
108
+ const storage = await createStorage(config.storage?.type || 'sqlite', config.storage?.options || {});
109
+
110
+ // Create execution engine
111
+ const execution = await createExecutionEngine({
112
+ storage: { type: config.storage?.type || 'sqlite', options: config.storage?.options || {} },
113
+ checkpointInterval: 3,
114
+ idempotency: {},
115
+ });
116
+
117
+ // Create approval gate
118
+ const hitlGate = await createApprovalGate({
119
+ store: { type: config.storage?.type || 'sqlite', options: config.storage?.options || {} },
120
+ notifier: { console: true },
121
+ defaultTimeout: 300000,
122
+ });
123
+
124
+ // Create budget controller
125
+ const budgetCtrl = createBudgetController(budget);
126
+
127
+ // Load plugins
128
+ const pluginTools = await loadPlugins(plugins);
129
+
130
+ // Create tool registry with plugins
131
+ const toolRegistry = createToolRegistry({
132
+ approvalGate: hitlGate,
133
+ });
134
+
135
+ // Register plugin tools
136
+ for (const [name, tool] of Object.entries(pluginTools)) {
137
+ toolRegistry.register({ ...tool, category: 'plugin' });
138
+ }
139
+
140
+ // Create LLM agent
141
+ const agent = await createLLMAgent({
142
+ llm,
143
+ execution: { storage: { type: config.storage?.type || 'sqlite', options: config.storage?.options || {} } },
144
+ hitl: { enabled: hitl.enabled !== false, defaultTimeout: 300000 },
145
+ budget,
146
+ memory: { conversation: true, episodic: true },
147
+ tools: { custom: toolRegistry },
148
+ });
149
+
150
+ // Store run info
151
+ const runData = {
152
+ agent,
153
+ workflowId: wfId,
154
+ status: 'running',
155
+ startTime: Date.now(),
156
+ task,
157
+ steps: [],
158
+ budget: budgetCtrl.getStatus(runId),
159
+ currentThought: null,
160
+ execution,
161
+ hitl: hitlGate,
162
+ budgetCtrl,
163
+ };
164
+ activeRuns.set(runId, runData);
165
+
166
+ // Execute agent task
167
+ runAgent(runId, task, hitlGate, budgetCtrl, wfId, io);
168
+
169
+ res.json({ runId, workflowId: wfId, status: 'started' });
170
+ } catch (error) {
171
+ console.error('Failed to start run:', error);
172
+ res.status(500).json({ error: error.message });
173
+ }
174
+ });
175
+
176
+ // API: Approve/reject pending approval
177
+ app.post('/api/approvals/:approvalId', async (req, res) => {
178
+ try {
179
+ const { approvalId } = req.params;
180
+ const { action, reason } = req.body; // action: 'approve' | 'reject'
181
+
182
+ // Find the run with this approval
183
+ let targetRun = null;
184
+ for (const [runId, run] of activeRuns) {
185
+ const approval = await run.hitl.getApproval(approvalId);
186
+ if (approval) {
187
+ targetRun = run;
188
+ break;
189
+ }
190
+ }
191
+
192
+ if (!targetRun) {
193
+ return res.status(404).json({ error: 'Approval not found in any active run' });
194
+ }
195
+
196
+ let result;
197
+ if (action === 'approve') {
198
+ result = await targetRun.hitl.approve(approvalId, { decider: 'ui-user', reason });
199
+ } else if (action === 'reject') {
200
+ result = await targetRun.hitl.reject(approvalId, { decider: 'ui-user', reason });
201
+ } else {
202
+ return res.status(400).json({ error: 'Invalid action. Use approve or reject' });
203
+ }
204
+
205
+ // Broadcast to all clients
206
+ io.emit('approval:updated', { approvalId, status: result.status, runId: getRunIdForApproval(approvalId) });
207
+
208
+ res.json(result);
209
+ } catch (error) {
210
+ console.error('Approval error:', error);
211
+ res.status(500).json({ error: error.message });
212
+ }
213
+ });
214
+
215
+ // API: List pending approvals
216
+ app.get('/api/approvals', async (req, res) => {
217
+ try {
218
+ const allApprovals = [];
219
+ for (const [runId, run] of activeRuns) {
220
+ const approvals = await run.hitl.getPendingApprovals(50);
221
+ allApprovals.push(...approvals.map(a => ({ ...a, runId })));
222
+ }
223
+ res.json({ approvals: allApprovals });
224
+ } catch (error) {
225
+ res.status(500).json({ error: error.message });
226
+ }
227
+ });
228
+
229
+ // API: Budget status
230
+ app.get('/api/budget/:budgetId?', (req, res) => {
231
+ const budgetId = req.params.budgetId || 'default';
232
+ // Find run with this budget
233
+ for (const [runId, run] of activeRuns) {
234
+ if (run.budgetCtrl) {
235
+ return res.json(run.budgetCtrl.getStatus(budgetId));
236
+ }
237
+ }
238
+ // Fallback to global
239
+ const budgetCtrl = createBudgetController({});
240
+ res.json(budgetCtrl.getStatus(budgetId));
241
+ });
242
+
243
+ // API: Stop a run
244
+ app.post('/api/runs/:runId/stop', async (req, res) => {
245
+ const runId = req.params.runId;
246
+ const run = activeRuns.get(runId);
247
+ if (!run) {
248
+ return res.status(404).json({ error: 'Run not found' });
249
+ }
250
+
251
+ run.status = 'stopped';
252
+ await run.agent?.shutdown();
253
+ await run.execution?.storage?.close();
254
+
255
+ io.emit('run:stopped', { runId });
256
+ res.json({ success: true });
257
+ });
258
+
259
+ // Serve static UI files (production)
260
+ const uiPath = resolve(__dirname, 'ui/dist');
261
+ app.use(express.static(uiPath));
262
+
263
+ // SPA fallback
264
+ app.get('*', (req, res) => {
265
+ if (!req.path.startsWith('/api') && !req.path.startsWith('/health')) {
266
+ res.sendFile(join(uiPath, 'index.html'), (err) => {
267
+ if (err) res.status(404).send('UI not built. Run `npm run build:ui` first.');
268
+ });
269
+ }
270
+ });
271
+
272
+ // Socket.io connection handling
273
+ io.on('connection', (socket) => {
274
+ connectedClients.add(socket.id);
275
+ console.log(`Client connected: ${socket.id} (total: ${connectedClients.size})`);
276
+
277
+ // Send current state
278
+ socket.emit('init', {
279
+ runs: Array.from(activeRuns.entries()).map(([runId, data]) => ({
280
+ runId,
281
+ workflowId: data.workflowId,
282
+ status: data.status,
283
+ startTime: data.startTime,
284
+ task: data.task?.slice(0, 100),
285
+ })),
286
+ approvals: await getAllPendingApprovals(),
287
+ });
288
+
289
+ // Subscribe to run updates
290
+ socket.on('subscribe:run', (runId) => {
291
+ socket.join(`run:${runId}`);
292
+ });
293
+
294
+ socket.on('unsubscribe:run', (runId) => {
295
+ socket.leave(`run:${runId}`);
296
+ });
297
+
298
+ socket.on('disconnect', () => {
299
+ connectedClients.delete(socket.id);
300
+ console.log(`Client disconnected: ${socket.id} (total: ${connectedClients.size})`);
301
+ });
302
+ });
303
+
304
+ // Broadcast helper
305
+ function broadcast(event, data) {
306
+ io.emit(event, data);
307
+ }
308
+
309
+ // Helper to find runId for approval
310
+ function getRunIdForApproval(approvalId) {
311
+ for (const [runId, run] of activeRuns) {
312
+ // This is async but we need sync here - in practice would cache
313
+ }
314
+ return null;
315
+ }
316
+
317
+ async function getAllPendingApprovals() {
318
+ const all = [];
319
+ for (const [runId, run] of activeRuns) {
320
+ const approvals = await run.hitl.getPendingApprovals(50);
321
+ all.push(...approvals.map(a => ({ ...a, runId })));
322
+ }
323
+ return all;
324
+ }
325
+
326
+ return { app, httpServer, io, broadcast, activeRuns };
327
+ }
328
+
329
+ /**
330
+ * Run agent task with real-time updates
331
+ */
332
+ async function runAgent(runId, task, hitlGate, budgetCtrl, workflowId, io) {
333
+ const run = activeRuns.get(runId);
334
+ if (!run) return;
335
+
336
+ try {
337
+ // Override agent's run method to emit thought logs
338
+ const originalRun = run.agent.run.bind(run.agent);
339
+
340
+ // We'll monkey-patch to emit thought events
341
+ let step = 0;
342
+
343
+ const result = await run.agent.run(task, {
344
+ runId,
345
+ budgetId: runId,
346
+ hitlRequired: ['file_write', 'api_call', 'send_email', 'code_exec', 'db_query'],
347
+ maxSteps: 50,
348
+ });
349
+
350
+ run.status = result.success ? 'completed' : 'failed';
351
+ run.steps = result.steps;
352
+ run.budget = run.budgetCtrl.getStatus(runId);
353
+
354
+ io.to(`run:${runId}`).emit('run:completed', { runId, result });
355
+ broadcast('run:updated', { runId, status: run.status, result });
356
+
357
+ } catch (error) {
358
+ run.status = 'error';
359
+ run.error = error.message;
360
+ io.to(`run:${runId}`).emit('run:error', { runId, error: error.message });
361
+ broadcast('run:updated', { runId, status: 'error', error: error.message });
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Start the server
367
+ */
368
+ export async function startServer(config = {}) {
369
+ const { app, httpServer, io, broadcast, activeRuns: runs } = createApp(config);
370
+
371
+ const port = config.port || process.env.LSJI_SERVER_PORT || 3456;
372
+ const host = config.host || '0.0.0.0';
373
+
374
+ return new Promise((resolve) => {
375
+ httpServer.listen(port, host, () => {
376
+ console.log(`LSJI Server running at http://${host}:${port}`);
377
+ console.log(`WebSocket ready for connections`);
378
+ resolve({ app, httpServer, io, broadcast, activeRuns: runs, port, host });
379
+ });
380
+ });
381
+ }
382
+
383
+ /**
384
+ * Stop the server
385
+ */
386
+ export async function stopServer(server) {
387
+ // Stop all active runs
388
+ for (const [runId, run] of server.activeRuns) {
389
+ run.status = 'stopped';
390
+ await run.agent?.shutdown();
391
+ await run.execution?.storage?.close();
392
+ }
393
+
394
+ // Close connections
395
+ server.io.close();
396
+ await new Promise(resolve => server.httpServer.close(resolve));
397
+ console.log('LSJI Server stopped');
398
+ }
399
+
400
+ // CLI entry point
401
+ if (import.meta.url === `file://${process.argv[1]}`) {
402
+ const config = {
403
+ port: parseInt(process.argv[2]) || 3456,
404
+ storage: { type: 'sqlite', options: { path: './lsji.db' } },
405
+ };
406
+
407
+ startServer(config).catch(console.error);
408
+
409
+ // Graceful shutdown
410
+ process.on('SIGINT', async () => {
411
+ console.log('\nShutting down...');
412
+ process.exit(0);
413
+ });
414
+ }
415
+
416
+ export { activeRuns, connectedClients };
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>LSJI Control Panel</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #e6edf3; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <div id="root"></div>
14
+ <script type="module" src="/src/main.jsx"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "lsji-control-panel",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.3.1",
12
+ "react-dom": "^18.3.1",
13
+ "socket.io-client": "^4.7.5"
14
+ },
15
+ "devDependencies": {
16
+ "@vitejs/plugin-react": "^4.3.1",
17
+ "vite": "^5.4.0"
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+ import './styles.css';
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );