@game_ryo/lsji 1.1.0 → 1.2.1
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/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 +6 -11
- package/src/server/ui/dist/assets/{main-Dfki4A3Q.js → main-v74qobjz.js} +8 -8
- package/src/server/ui/dist/index.html +1 -1
- package/src/server/ui/src/components/NewRunModal.jsx +29 -9
- 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
|
@@ -280,7 +280,7 @@ export function createApp(config = {}) {
|
|
|
280
280
|
connectedClients.add(socket.id);
|
|
281
281
|
console.log(`Client connected: ${socket.id} (total: ${connectedClients.size})`);
|
|
282
282
|
|
|
283
|
-
// Send current state
|
|
283
|
+
// Send current state
|
|
284
284
|
Promise.all(
|
|
285
285
|
Array.from(activeRuns.entries()).map(async ([runId, run]) => {
|
|
286
286
|
const approvals = await run.hitl.getPendingApprovals(50);
|
|
@@ -316,12 +316,7 @@ export function createApp(config = {}) {
|
|
|
316
316
|
});
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
function broadcast(event, data) {
|
|
321
|
-
io.emit(event, data);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return { app, httpServer, io, broadcast, activeRuns };
|
|
319
|
+
return { app, httpServer, io, activeRuns };
|
|
325
320
|
}
|
|
326
321
|
|
|
327
322
|
/**
|
|
@@ -362,7 +357,7 @@ async function runAgent(runId, task, hitlGate, budgetCtrl, workflowId, io) {
|
|
|
362
357
|
io.to(`run:${runId}`).emit('thought:new', run.thoughts[run.thoughts.length - 1]);
|
|
363
358
|
|
|
364
359
|
io.to(`run:${runId}`).emit('run:completed', { runId, result });
|
|
365
|
-
|
|
360
|
+
io.emit('run:updated', { runId, status: run.status, result });
|
|
366
361
|
|
|
367
362
|
} catch (error) {
|
|
368
363
|
run.status = 'error';
|
|
@@ -377,7 +372,7 @@ async function runAgent(runId, task, hitlGate, budgetCtrl, workflowId, io) {
|
|
|
377
372
|
io.to(`run:${runId}`).emit('thought:new', run.thoughts[run.thoughts.length - 1]);
|
|
378
373
|
|
|
379
374
|
io.to(`run:${runId}`).emit('run:error', { runId, error: error.message });
|
|
380
|
-
|
|
375
|
+
io.emit('run:updated', { runId, status: 'error', error: error.message });
|
|
381
376
|
}
|
|
382
377
|
}
|
|
383
378
|
|
|
@@ -385,7 +380,7 @@ async function runAgent(runId, task, hitlGate, budgetCtrl, workflowId, io) {
|
|
|
385
380
|
* Start the server
|
|
386
381
|
*/
|
|
387
382
|
export async function startServer(config = {}) {
|
|
388
|
-
const { app, httpServer, io,
|
|
383
|
+
const { app, httpServer, io, activeRuns: runs } = createApp(config);
|
|
389
384
|
|
|
390
385
|
const port = config.port || process.env.LSJI_SERVER_PORT || 3456;
|
|
391
386
|
const host = config.host || '0.0.0.0';
|
|
@@ -394,7 +389,7 @@ export async function startServer(config = {}) {
|
|
|
394
389
|
httpServer.listen(port, host, () => {
|
|
395
390
|
console.log(`LSJI Server running at http://${host}:${port}`);
|
|
396
391
|
console.log(`WebSocket ready for connections`);
|
|
397
|
-
resolve({ app, httpServer, io,
|
|
392
|
+
resolve({ app, httpServer, io, activeRuns: runs, port, host });
|
|
398
393
|
});
|
|
399
394
|
});
|
|
400
395
|
}
|