@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,202 @@
1
+ /**
2
+ * LSJI Tool Plugin System
3
+ *
4
+ * Dynamic plugin loading for custom tools.
5
+ * Plugins are JavaScript modules that export tool definitions.
6
+ */
7
+
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, resolve, join } from 'path';
10
+ import { readdir } from 'fs/promises';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ /**
16
+ * Plugin definition
17
+ * @typedef {Object} Plugin
18
+ * @property {string} name - Plugin name
19
+ * @property {string} version - Plugin version
20
+ * @property {Object} tools - Object mapping tool names to tool definitions
21
+ * @property {Function} [init] - Optional initialization function
22
+ * @property {Function} [cleanup] - Optional cleanup function
23
+ */
24
+
25
+ /**
26
+ * Default plugin directories to scan
27
+ */
28
+ const DEFAULT_PLUGIN_DIRS = [
29
+ resolve(process.cwd(), 'lsji-plugins'),
30
+ resolve(__dirname, '../../plugins'),
31
+ resolve(process.cwd(), '.lsji/plugins'),
32
+ ];
33
+
34
+ /**
35
+ * Load plugins from directory
36
+ * @param {string|string[]} pluginPaths - Paths to plugin directories or files
37
+ * @returns {Promise<Object>} Map of toolName -> toolDefinition
38
+ */
39
+ export async function loadPlugins(pluginPaths = []) {
40
+ const allTools = {};
41
+ const dirs = [...DEFAULT_PLUGIN_DIRS, ...(Array.isArray(pluginPaths) ? pluginPaths : [pluginPaths])];
42
+
43
+ for (const dir of dirs) {
44
+ try {
45
+ const tools = await loadPluginDirectory(dir);
46
+ Object.assign(allTools, tools);
47
+ } catch (error) {
48
+ if (error.code !== 'ENOENT') {
49
+ console.warn(`Failed to load plugins from ${dir}:`, error.message);
50
+ }
51
+ }
52
+ }
53
+
54
+ return allTools;
55
+ }
56
+
57
+ /**
58
+ * Load all plugins from a directory
59
+ */
60
+ async function loadPluginDirectory(dir) {
61
+ const tools = {};
62
+
63
+ let entries;
64
+ try {
65
+ entries = await readdir(dir, { withFileTypes: true });
66
+ } catch {
67
+ return tools;
68
+ }
69
+
70
+ for (const entry of entries) {
71
+ if (!entry.isFile() || !entry.name.endsWith('.js')) continue;
72
+
73
+ const filePath = join(dir, entry.name);
74
+ try {
75
+ const plugin = await import(filePath);
76
+ const pluginModule = plugin.default || plugin;
77
+
78
+ if (pluginModule.tools) {
79
+ for (const [name, definition] of Object.entries(pluginModule.tools)) {
80
+ tools[name] = {
81
+ ...definition,
82
+ plugin: pluginModule.name || entry.name.replace('.js', ''),
83
+ };
84
+ }
85
+ }
86
+
87
+ if (pluginModule.init) {
88
+ await pluginModule.init();
89
+ }
90
+ } catch (error) {
91
+ console.warn(`Failed to load plugin ${filePath}:`, error.message);
92
+ }
93
+ }
94
+
95
+ return tools;
96
+ }
97
+
98
+ /**
99
+ * Create a plugin template
100
+ */
101
+ export function createPluginTemplate(name) {
102
+ return `/**
103
+ * ${name} Plugin for LSJI
104
+ *
105
+ * Drop this file in lsji-plugins/ directory to enable.
106
+ */
107
+
108
+ export default {
109
+ name: '${name}',
110
+ version: '1.0.0',
111
+
112
+ tools: {
113
+ ${name}_example: {
114
+ name: '${name}_example',
115
+ description: 'Example tool from ${name} plugin',
116
+ category: 'plugin',
117
+ parameters: {
118
+ type: 'object',
119
+ properties: {
120
+ input: { type: 'string', description: 'Input parameter' },
121
+ },
122
+ required: ['input'],
123
+ },
124
+ requiresApproval: false,
125
+ idempotent: true,
126
+ async execute({ input }, context) {
127
+ return { result: \`Processed: \${input}\`, plugin: '${name}' };
128
+ },
129
+ },
130
+ },
131
+
132
+ async init() {
133
+ console.log('[${name}] Plugin initialized');
134
+ },
135
+
136
+ async cleanup() {
137
+ console.log('[${name}] Plugin cleaned up');
138
+ },
139
+ };
140
+ `;
141
+ }
142
+
143
+ /**
144
+ * Plugin registry for runtime management
145
+ */
146
+ export class PluginRegistry {
147
+ constructor() {
148
+ this.plugins = new Map();
149
+ this.tools = new Map();
150
+ }
151
+
152
+ register(plugin) {
153
+ if (!plugin.name || !plugin.tools) {
154
+ throw new Error('Plugin must have name and tools');
155
+ }
156
+
157
+ this.plugins.set(plugin.name, plugin);
158
+
159
+ for (const [name, tool] of Object.entries(plugin.tools)) {
160
+ this.tools.set(name, { ...tool, plugin: plugin.name });
161
+ }
162
+
163
+ if (plugin.init) {
164
+ plugin.init();
165
+ }
166
+ }
167
+
168
+ async unregister(name) {
169
+ const plugin = this.plugins.get(name);
170
+ if (!plugin) return false;
171
+
172
+ if (plugin.cleanup) {
173
+ await plugin.cleanup();
174
+ }
175
+
176
+ for (const toolName of Object.keys(plugin.tools)) {
177
+ this.tools.delete(toolName);
178
+ }
179
+
180
+ this.plugins.delete(name);
181
+ return true;
182
+ }
183
+
184
+ getTools() {
185
+ return Object.fromEntries(this.tools);
186
+ }
187
+
188
+ getPlugin(name) {
189
+ return this.plugins.get(name);
190
+ }
191
+
192
+ listPlugins() {
193
+ return Array.from(this.plugins.values()).map(p => ({
194
+ name: p.name,
195
+ version: p.version,
196
+ toolCount: Object.keys(p.tools).length,
197
+ }));
198
+ }
199
+ }
200
+
201
+ // Global registry
202
+ export const globalPluginRegistry = new PluginRegistry();
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Prompt Manager
3
+ *
4
+ * Manages prompt templates with versioning and variable substitution.
5
+ */
6
+
7
+ import { createStorage } from '../index.js';
8
+
9
+ /**
10
+ * Prompt template
11
+ * @typedef {Object} PromptTemplate
12
+ * @property {string} name - Template name
13
+ * @property {string} version - Template version
14
+ * @property {string} template - Template string with {{variables}}
15
+ * @property {Array<string>} variables - Required variables
16
+ * @property {string} description - Template description
17
+ * @property {Date} createdAt
18
+ * @property {Date} updatedAt
19
+ */
20
+
21
+ /**
22
+ * Prompt Manager - Template management with versioning
23
+ */
24
+ export class PromptManager {
25
+ constructor({ storage } = {}) {
26
+ this.storage = storage;
27
+ this.templates = new Map(); // name -> { versions: Map<version, template> }
28
+ this.initialized = false;
29
+ }
30
+
31
+ /**
32
+ * Initialize prompts table
33
+ */
34
+ async initialize() {
35
+ if (this.initialized) return;
36
+
37
+ if (this.storage.db) {
38
+ await this.storage.db.exec(`
39
+ CREATE TABLE IF NOT EXISTS prompts (
40
+ name TEXT NOT NULL,
41
+ version TEXT NOT NULL,
42
+ template TEXT NOT NULL,
43
+ variables TEXT NOT NULL,
44
+ description TEXT,
45
+ created_at TEXT NOT NULL,
46
+ updated_at TEXT NOT NULL,
47
+ PRIMARY KEY (name, version)
48
+ )
49
+ `);
50
+ }
51
+
52
+ this.initialized = true;
53
+ }
54
+
55
+ /**
56
+ * Register a prompt template
57
+ */
58
+ async register(name, template, { version = '1.0.0', variables = [], description = '' } = {}) {
59
+ await this.initialize();
60
+
61
+ // Extract variables from template if not provided
62
+ const extractedVars = variables.length > 0 ? variables : this.extractVariables(template);
63
+
64
+ const prompt = {
65
+ name,
66
+ version,
67
+ template,
68
+ variables: extractedVars,
69
+ description,
70
+ createdAt: new Date().toISOString(),
71
+ updatedAt: new Date().toISOString(),
72
+ };
73
+
74
+ // Store in memory
75
+ if (!this.templates.has(name)) {
76
+ this.templates.set(name, new Map());
77
+ }
78
+ this.templates.get(name).set(version, prompt);
79
+
80
+ // Persist
81
+ if (this.storage.db) {
82
+ await this.storage.db.run(
83
+ `INSERT OR REPLACE INTO prompts (name, version, template, variables, description, created_at, updated_at)
84
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
85
+ [name, version, template, JSON.stringify(extractedVars), description, prompt.createdAt, prompt.updatedAt]
86
+ );
87
+ }
88
+
89
+ return prompt;
90
+ }
91
+
92
+ /**
93
+ * Get a prompt template (latest version by default)
94
+ */
95
+ async get(name, version = null) {
96
+ await this.initialize();
97
+
98
+ const versions = this.templates.get(name);
99
+ if (!versions) return null;
100
+
101
+ if (version) {
102
+ return versions.get(version) || null;
103
+ }
104
+
105
+ // Get latest version
106
+ const sortedVersions = Array.from(versions.keys()).sort((a, b) => {
107
+ const parseVersion = v => v.split('.').map(Number);
108
+ const va = parseVersion(a);
109
+ const vb = parseVersion(b);
110
+ for (let i = 0; i < 3; i++) {
111
+ if (va[i] !== vb[i]) return vb[i] - va[i];
112
+ }
113
+ return 0;
114
+ });
115
+
116
+ return versions.get(sortedVersions[0]) || null;
117
+ }
118
+
119
+ /**
120
+ * Get all versions of a prompt
121
+ */
122
+ async getAllVersions(name) {
123
+ await this.initialize();
124
+ const versions = this.templates.get(name);
125
+ if (!versions) return [];
126
+ return Array.from(versions.values()).sort((a, b) =>
127
+ new Date(b.createdAt) - new Date(a.createdAt)
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Render a prompt with variables
133
+ */
134
+ async render(name, variables, version = null) {
135
+ const prompt = await this.get(name, version);
136
+ if (!prompt) {
137
+ throw new Error(`Prompt not found: ${name}${version ? `@${version}` : ''}`);
138
+ }
139
+
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
+ let rendered = prompt.template;
149
+ for (const [key, value] of Object.entries(variables)) {
150
+ const placeholder = `{{${key}}}`;
151
+ rendered = rendered.replaceAll(placeholder, String(value));
152
+ }
153
+
154
+ return rendered;
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 });
165
+ }
166
+ return results;
167
+ }
168
+
169
+ /**
170
+ * Extract variables from template
171
+ */
172
+ extractVariables(template) {
173
+ const matches = template.match(/{{(\w+)}}/g);
174
+ if (!matches) return [];
175
+ return [...new Set(matches.map(m => m.slice(2, -2)))];
176
+ }
177
+
178
+ /**
179
+ * List all prompt names
180
+ */
181
+ async list() {
182
+ await this.initialize();
183
+ return Array.from(this.templates.keys());
184
+ }
185
+
186
+ /**
187
+ * Delete a prompt version
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;
207
+ }
208
+
209
+ /**
210
+ * Load all prompts from storage
211
+ */
212
+ async loadAll() {
213
+ await this.initialize();
214
+
215
+ if (this.storage.db) {
216
+ const rows = await this.storage.db.all('SELECT * FROM prompts');
217
+ for (const row of rows) {
218
+ if (!this.templates.has(row.name)) {
219
+ this.templates.set(row.name, new Map());
220
+ }
221
+ this.templates.get(row.name).set(row.version, {
222
+ name: row.name,
223
+ version: row.version,
224
+ template: row.template,
225
+ variables: JSON.parse(row.variables || '[]'),
226
+ description: row.description,
227
+ createdAt: row.created_at,
228
+ updatedAt: row.updated_at,
229
+ });
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Built-in prompt templates
237
+ */
238
+ export const BUILTIN_PROMPTS = {
239
+ 'system:react': {
240
+ template: `You are an AI assistant that uses the ReAct pattern (Reasoning + Acting) to solve tasks.
241
+
242
+ You have access to the following tools:
243
+ {{tools}}
244
+
245
+ When you need to use a tool, respond with:
246
+ THOUGHT: Your reasoning about what to do next
247
+ ACTION: The tool name to use
248
+ ACTION_INPUT: The parameters for the tool
249
+
250
+ After the tool returns, you'll see:
251
+ OBSERVATION: The result
252
+
253
+ Continue this pattern until you can provide the final answer.
254
+
255
+ Current task: {{task}}`,
256
+ variables: ['tools', 'task'],
257
+ description: 'ReAct system prompt with tool definitions',
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',
308
+ },
309
+ };
310
+
311
+ /**
312
+ * Create prompt manager with built-in templates
313
+ */
314
+ export async function createPromptManager(config = {}) {
315
+ const storage = await createStorage(
316
+ config.storage?.type || 'sqlite',
317
+ config.storage?.options || {}
318
+ );
319
+
320
+ const manager = new PromptManager({ storage });
321
+ await manager.initialize();
322
+
323
+ // Register built-in prompts
324
+ for (const [name, prompt] of Object.entries(BUILTIN_PROMPTS)) {
325
+ await manager.register(name, prompt.template, {
326
+ variables: prompt.variables,
327
+ description: prompt.description,
328
+ });
329
+ }
330
+
331
+ return manager;
332
+ }