@game_ryo/lsji 1.2.0 → 1.2.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/AGENTS.md +90 -21
- package/README.md +15 -15
- package/package.json +3 -2
- package/src/cli.js +11 -4
- package/src/execution/budget/index.js +16 -0
- package/src/execution/hitl/approval-gate.js +3 -1
- package/src/execution/idempotency.js +4 -2
- package/src/index.js +1 -1
- package/src/llm/llm-agent.js +30 -7
- package/src/llm/memory/conversation.js +11 -10
- package/src/llm/prompt-manager.js +58 -139
- package/src/server/index.js +14 -16
- package/src/server/ui/dist/assets/{main-v74qobjz.js → main-49ugfY-s.js} +8 -8
- package/src/server/ui/dist/index.html +1 -1
- package/src/server/ui/src/components/NewRunModal.jsx +61 -35
- package/src/storage/better-sqlite.js +31 -0
- package/src/storage/index.js +50 -0
- package/src/storage/memory.js +188 -0
- package/src/storage/sqlite.js +31 -0
|
@@ -34,8 +34,9 @@ export class PromptManager {
|
|
|
34
34
|
async initialize() {
|
|
35
35
|
if (this.initialized) return;
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// Use the new storage interface
|
|
38
|
+
if (typeof this.storage.exec === 'function') {
|
|
39
|
+
await this.storage.exec(`
|
|
39
40
|
CREATE TABLE IF NOT EXISTS prompts (
|
|
40
41
|
name TEXT NOT NULL,
|
|
41
42
|
version TEXT NOT NULL,
|
|
@@ -78,8 +79,8 @@ export class PromptManager {
|
|
|
78
79
|
this.templates.get(name).set(version, prompt);
|
|
79
80
|
|
|
80
81
|
// Persist
|
|
81
|
-
if (this.storage.
|
|
82
|
-
await this.storage.
|
|
82
|
+
if (typeof this.storage.run === 'function') {
|
|
83
|
+
await this.storage.run(
|
|
83
84
|
`INSERT OR REPLACE INTO prompts (name, version, template, variables, description, created_at, updated_at)
|
|
84
85
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
85
86
|
[name, version, template, JSON.stringify(extractedVars), description, prompt.createdAt, prompt.updatedAt]
|
|
@@ -102,18 +103,14 @@ export class PromptManager {
|
|
|
102
103
|
return versions.get(version) || null;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
for (let i = 0; i < 3; i++) {
|
|
111
|
-
if (va[i] !== vb[i]) return vb[i] - va[i];
|
|
106
|
+
// Return latest version (highest semver)
|
|
107
|
+
let latest = null;
|
|
108
|
+
for (const [ver, prompt] of versions) {
|
|
109
|
+
if (!latest || this.compareVersions(ver, latest) > 0) {
|
|
110
|
+
latest = ver;
|
|
112
111
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return versions.get(sortedVersions[0]) || null;
|
|
112
|
+
}
|
|
113
|
+
return versions.get(latest);
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
/**
|
|
@@ -123,110 +120,79 @@ export class PromptManager {
|
|
|
123
120
|
await this.initialize();
|
|
124
121
|
const versions = this.templates.get(name);
|
|
125
122
|
if (!versions) return [];
|
|
126
|
-
return Array.from(versions.values())
|
|
127
|
-
new Date(b.createdAt) - new Date(a.createdAt)
|
|
128
|
-
);
|
|
123
|
+
return Array.from(versions.values());
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
/**
|
|
132
|
-
* Render a
|
|
127
|
+
* Render a template with variables
|
|
133
128
|
*/
|
|
134
|
-
async render(name, variables, version = null) {
|
|
129
|
+
async render(name, variables = {}, version = null) {
|
|
135
130
|
const prompt = await this.get(name, version);
|
|
136
131
|
if (!prompt) {
|
|
137
132
|
throw new Error(`Prompt not found: ${name}${version ? `@${version}` : ''}`);
|
|
138
133
|
}
|
|
139
134
|
|
|
140
|
-
// Check required variables
|
|
141
|
-
for (const v of prompt.variables) {
|
|
142
|
-
if (!(v in variables)) {
|
|
143
|
-
throw new Error(`Missing required variable: ${v}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Substitute variables
|
|
148
135
|
let rendered = prompt.template;
|
|
149
136
|
for (const [key, value] of Object.entries(variables)) {
|
|
150
|
-
|
|
151
|
-
rendered = rendered.replaceAll(placeholder, String(value));
|
|
137
|
+
rendered = rendered.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
152
138
|
}
|
|
153
139
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
* Render multiple prompts (for system + user messages)
|
|
159
|
-
*/
|
|
160
|
-
async renderAll(prompts, variables) {
|
|
161
|
-
const results = [];
|
|
162
|
-
for (const { name, version, role = 'user' } of prompts) {
|
|
163
|
-
const content = await this.render(name, variables, version);
|
|
164
|
-
results.push({ role, content });
|
|
140
|
+
// Check for missing variables
|
|
141
|
+
const missingVars = prompt.variables.filter(v => !(v in variables));
|
|
142
|
+
if (missingVars.length > 0) {
|
|
143
|
+
console.warn(`Missing variables for prompt ${name}: ${missingVars.join(', ')}`);
|
|
165
144
|
}
|
|
166
|
-
|
|
145
|
+
|
|
146
|
+
return rendered;
|
|
167
147
|
}
|
|
168
148
|
|
|
169
149
|
/**
|
|
170
150
|
* Extract variables from template
|
|
171
151
|
*/
|
|
172
152
|
extractVariables(template) {
|
|
173
|
-
const matches = template.match(
|
|
153
|
+
const matches = template.match(/\{\{(\w+)\}\}/g);
|
|
174
154
|
if (!matches) return [];
|
|
175
155
|
return [...new Set(matches.map(m => m.slice(2, -2)))];
|
|
176
156
|
}
|
|
177
157
|
|
|
178
158
|
/**
|
|
179
|
-
*
|
|
159
|
+
* Compare semantic versions
|
|
180
160
|
*/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
*/
|
|
189
|
-
async delete(name, version) {
|
|
190
|
-
await this.initialize();
|
|
191
|
-
|
|
192
|
-
const versions = this.templates.get(name);
|
|
193
|
-
if (!versions || !versions.has(version)) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
versions.delete(version);
|
|
198
|
-
|
|
199
|
-
if (this.storage.db) {
|
|
200
|
-
await this.storage.db.run(
|
|
201
|
-
'DELETE FROM prompts WHERE name = ? AND version = ?',
|
|
202
|
-
[name, version]
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return true;
|
|
161
|
+
compareVersions(a, b) {
|
|
162
|
+
const parse = v => v.split('.').map(Number);
|
|
163
|
+
const [aMajor, aMinor, aPatch] = parse(a);
|
|
164
|
+
const [bMajor, bMinor, bPatch] = parse(b);
|
|
165
|
+
if (aMajor !== bMajor) return aMajor - bMajor;
|
|
166
|
+
if (aMinor !== bMinor) return aMinor - bMinor;
|
|
167
|
+
return aPatch - bPatch;
|
|
207
168
|
}
|
|
208
169
|
|
|
209
170
|
/**
|
|
210
171
|
* Load all prompts from storage
|
|
211
172
|
*/
|
|
212
|
-
async
|
|
173
|
+
async loadFromStorage() {
|
|
213
174
|
await this.initialize();
|
|
214
175
|
|
|
215
|
-
if (this.storage.
|
|
216
|
-
const rows = await this.storage.
|
|
176
|
+
if (typeof this.storage.all === 'function') {
|
|
177
|
+
const rows = await this.storage.all(
|
|
178
|
+
'SELECT * FROM prompts ORDER BY name, version'
|
|
179
|
+
);
|
|
180
|
+
|
|
217
181
|
for (const row of rows) {
|
|
218
|
-
|
|
219
|
-
this.templates.set(row.name, new Map());
|
|
220
|
-
}
|
|
221
|
-
this.templates.get(row.name).set(row.version, {
|
|
182
|
+
const prompt = {
|
|
222
183
|
name: row.name,
|
|
223
184
|
version: row.version,
|
|
224
185
|
template: row.template,
|
|
225
|
-
variables: JSON.parse(row.variables
|
|
186
|
+
variables: JSON.parse(row.variables),
|
|
226
187
|
description: row.description,
|
|
227
188
|
createdAt: row.created_at,
|
|
228
189
|
updatedAt: row.updated_at,
|
|
229
|
-
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (!this.templates.has(prompt.name)) {
|
|
193
|
+
this.templates.set(prompt.name, new Map());
|
|
194
|
+
}
|
|
195
|
+
this.templates.get(prompt.name).set(prompt.version, prompt);
|
|
230
196
|
}
|
|
231
197
|
}
|
|
232
198
|
}
|
|
@@ -237,79 +203,31 @@ export class PromptManager {
|
|
|
237
203
|
*/
|
|
238
204
|
export const BUILTIN_PROMPTS = {
|
|
239
205
|
'system:react': {
|
|
240
|
-
|
|
206
|
+
name: 'system:react',
|
|
207
|
+
version: '1.0.0',
|
|
208
|
+
template: `You are an AI assistant that uses the ReAct (Reasoning + Acting) pattern.
|
|
241
209
|
|
|
242
210
|
You have access to the following tools:
|
|
243
211
|
{{tools}}
|
|
244
212
|
|
|
245
|
-
|
|
213
|
+
Your task is: {{task}}
|
|
214
|
+
|
|
215
|
+
Use the following format:
|
|
216
|
+
|
|
246
217
|
THOUGHT: Your reasoning about what to do next
|
|
247
|
-
ACTION: The tool
|
|
218
|
+
ACTION: The tool to use (must be one of the available tools)
|
|
248
219
|
ACTION_INPUT: The parameters for the tool
|
|
249
220
|
|
|
250
|
-
|
|
251
|
-
OBSERVATION: The result
|
|
252
|
-
|
|
253
|
-
Continue this pattern until you can provide the final answer.
|
|
221
|
+
When you have the final answer, respond directly without THOUGHT/ACTION format.
|
|
254
222
|
|
|
255
|
-
|
|
223
|
+
Begin!`,
|
|
256
224
|
variables: ['tools', 'task'],
|
|
257
|
-
description: '
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
'system:planner': {
|
|
261
|
-
template: `You are a planning agent. Break down the task into a sequence of steps.
|
|
262
|
-
|
|
263
|
-
Task: {{task}}
|
|
264
|
-
|
|
265
|
-
Available tools: {{tools}}
|
|
266
|
-
|
|
267
|
-
Create a plan with numbered steps. Each step should specify:
|
|
268
|
-
1. What tool to use (if any)
|
|
269
|
-
2. What parameters to pass
|
|
270
|
-
3. What you expect to learn or achieve
|
|
271
|
-
|
|
272
|
-
Output as JSON:
|
|
273
|
-
{
|
|
274
|
-
"steps": [
|
|
275
|
-
{"step": 1, "tool": "tool_name", "params": {}, "description": "..."}
|
|
276
|
-
]
|
|
277
|
-
}`,
|
|
278
|
-
variables: ['task', 'tools'],
|
|
279
|
-
description: 'Planning agent prompt',
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
'system:code-reviewer': {
|
|
283
|
-
template: `You are an expert code reviewer. Analyze the provided code for:
|
|
284
|
-
- Bugs and logic errors
|
|
285
|
-
- Security vulnerabilities
|
|
286
|
-
- Performance issues
|
|
287
|
-
- Code style and best practices
|
|
288
|
-
- Test coverage gaps
|
|
289
|
-
|
|
290
|
-
Code to review:
|
|
291
|
-
{{code}}
|
|
292
|
-
|
|
293
|
-
Context: {{context}}
|
|
294
|
-
|
|
295
|
-
Provide your review in this format:
|
|
296
|
-
## Summary
|
|
297
|
-
Brief overall assessment
|
|
298
|
-
|
|
299
|
-
## Issues Found
|
|
300
|
-
- [Severity] File:Line - Description
|
|
301
|
-
|
|
302
|
-
## Suggestions
|
|
303
|
-
- Improvement suggestions
|
|
304
|
-
|
|
305
|
-
## Approved: true/false`,
|
|
306
|
-
variables: ['code', 'context'],
|
|
307
|
-
description: 'Code review prompt',
|
|
225
|
+
description: 'System prompt for ReAct agent',
|
|
308
226
|
},
|
|
309
227
|
};
|
|
310
228
|
|
|
311
229
|
/**
|
|
312
|
-
* Create prompt manager
|
|
230
|
+
* Create prompt manager from config
|
|
313
231
|
*/
|
|
314
232
|
export async function createPromptManager(config = {}) {
|
|
315
233
|
const storage = await createStorage(
|
|
@@ -323,6 +241,7 @@ export async function createPromptManager(config = {}) {
|
|
|
323
241
|
// Register built-in prompts
|
|
324
242
|
for (const [name, prompt] of Object.entries(BUILTIN_PROMPTS)) {
|
|
325
243
|
await manager.register(name, prompt.template, {
|
|
244
|
+
version: prompt.version,
|
|
326
245
|
variables: prompt.variables,
|
|
327
246
|
description: prompt.description,
|
|
328
247
|
});
|
package/src/server/index.js
CHANGED
|
@@ -138,13 +138,16 @@ export function createApp(config = {}) {
|
|
|
138
138
|
toolRegistry.register({ ...tool, category: 'plugin' });
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
// Create LLM agent
|
|
141
|
+
// Create LLM agent with API key from request
|
|
142
|
+
const finalApiKey = llm.apiKey || process.env[llm.provider === 'gemini' ? 'GEMINI_API_KEY' : llm.provider === 'openai' ? 'OPENAI_API_KEY' : llm.provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : ''];
|
|
143
|
+
const storageConfig = { type: config.storage?.type || 'sqlite', options: config.storage?.options || {} };
|
|
142
144
|
const agent = await createLLMAgent({
|
|
143
|
-
llm,
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
llm: { ...llm, apiKey: finalApiKey },
|
|
146
|
+
storage: storageConfig,
|
|
147
|
+
execution: { storage: storageConfig },
|
|
148
|
+
hitl: { enabled: hitl.enabled !== false, defaultTimeout: 300000, store: storageConfig },
|
|
146
149
|
budget,
|
|
147
|
-
memory: { conversation: true, episodic: true },
|
|
150
|
+
memory: { conversation: true, episodic: true, semantic: true },
|
|
148
151
|
tools: { custom: toolRegistry },
|
|
149
152
|
});
|
|
150
153
|
|
|
@@ -280,7 +283,7 @@ export function createApp(config = {}) {
|
|
|
280
283
|
connectedClients.add(socket.id);
|
|
281
284
|
console.log(`Client connected: ${socket.id} (total: ${connectedClients.size})`);
|
|
282
285
|
|
|
283
|
-
// Send current state
|
|
286
|
+
// Send current state
|
|
284
287
|
Promise.all(
|
|
285
288
|
Array.from(activeRuns.entries()).map(async ([runId, run]) => {
|
|
286
289
|
const approvals = await run.hitl.getPendingApprovals(50);
|
|
@@ -316,12 +319,7 @@ export function createApp(config = {}) {
|
|
|
316
319
|
});
|
|
317
320
|
});
|
|
318
321
|
|
|
319
|
-
|
|
320
|
-
function broadcast(event, data) {
|
|
321
|
-
io.emit(event, data);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return { app, httpServer, io, broadcast, activeRuns };
|
|
322
|
+
return { app, httpServer, io, activeRuns };
|
|
325
323
|
}
|
|
326
324
|
|
|
327
325
|
/**
|
|
@@ -362,7 +360,7 @@ async function runAgent(runId, task, hitlGate, budgetCtrl, workflowId, io) {
|
|
|
362
360
|
io.to(`run:${runId}`).emit('thought:new', run.thoughts[run.thoughts.length - 1]);
|
|
363
361
|
|
|
364
362
|
io.to(`run:${runId}`).emit('run:completed', { runId, result });
|
|
365
|
-
|
|
363
|
+
io.emit('run:updated', { runId, status: run.status, result });
|
|
366
364
|
|
|
367
365
|
} catch (error) {
|
|
368
366
|
run.status = 'error';
|
|
@@ -377,7 +375,7 @@ async function runAgent(runId, task, hitlGate, budgetCtrl, workflowId, io) {
|
|
|
377
375
|
io.to(`run:${runId}`).emit('thought:new', run.thoughts[run.thoughts.length - 1]);
|
|
378
376
|
|
|
379
377
|
io.to(`run:${runId}`).emit('run:error', { runId, error: error.message });
|
|
380
|
-
|
|
378
|
+
io.emit('run:updated', { runId, status: 'error', error: error.message });
|
|
381
379
|
}
|
|
382
380
|
}
|
|
383
381
|
|
|
@@ -385,7 +383,7 @@ async function runAgent(runId, task, hitlGate, budgetCtrl, workflowId, io) {
|
|
|
385
383
|
* Start the server
|
|
386
384
|
*/
|
|
387
385
|
export async function startServer(config = {}) {
|
|
388
|
-
const { app, httpServer, io,
|
|
386
|
+
const { app, httpServer, io, activeRuns: runs } = createApp(config);
|
|
389
387
|
|
|
390
388
|
const port = config.port || process.env.LSJI_SERVER_PORT || 3456;
|
|
391
389
|
const host = config.host || '0.0.0.0';
|
|
@@ -394,7 +392,7 @@ export async function startServer(config = {}) {
|
|
|
394
392
|
httpServer.listen(port, host, () => {
|
|
395
393
|
console.log(`LSJI Server running at http://${host}:${port}`);
|
|
396
394
|
console.log(`WebSocket ready for connections`);
|
|
397
|
-
resolve({ app, httpServer, io,
|
|
395
|
+
resolve({ app, httpServer, io, activeRuns: runs, port, host });
|
|
398
396
|
});
|
|
399
397
|
});
|
|
400
398
|
}
|