@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.
- package/package.json +15 -7
- package/src/cli.js +395 -62
- package/src/execution/budget/circuit-breaker.js +245 -0
- package/src/execution/budget/cost-tracker.js +387 -0
- package/src/execution/budget/index.js +63 -0
- package/src/execution/budget/token-counter.js +159 -0
- package/src/execution/engine.js +428 -0
- package/src/execution/hitl/approval-gate.js +210 -0
- package/src/execution/hitl/index.js +12 -0
- package/src/execution/hitl/notifier.js +151 -0
- package/src/execution/hitl/store.js +311 -0
- package/src/execution/idempotency.js +312 -0
- package/src/execution/index.js +14 -0
- package/src/index.js +80 -4
- package/src/llm/index.js +21 -0
- package/src/llm/llm-agent.js +357 -0
- package/src/llm/memory/conversation.js +271 -0
- package/src/llm/memory/episodic.js +312 -0
- package/src/llm/memory/index.js +12 -0
- package/src/llm/memory/semantic.js +324 -0
- package/src/llm/plugins/index.js +202 -0
- package/src/llm/prompt-manager.js +332 -0
- package/src/llm/providers/anthropic.js +250 -0
- package/src/llm/providers/base.js +116 -0
- package/src/llm/providers/local.js +163 -0
- package/src/llm/providers/openai.js +212 -0
- package/src/llm/tools/registry.js +342 -0
- package/src/server/index.js +416 -0
- package/src/server/ui/index.html +16 -0
- package/src/server/ui/package.json +19 -0
- package/src/server/ui/src/main.jsx +10 -0
- package/src/server/ui/src/styles.css +260 -0
- package/src/server/ui/vite.config.js +27 -0
- package/docs/README.md +0 -43
- package/docs/blog/2019-05-28-first-blog-post.mdx +0 -12
- package/docs/blog/2019-05-29-long-blog-post.mdx +0 -44
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.mdx +0 -29
- package/docs/blog/authors.yml +0 -25
- package/docs/blog/tags.yml +0 -19
- package/docs/docs/api/agent.md +0 -151
- package/docs/docs/api/env.md +0 -133
- package/docs/docs/api/environments.md +0 -102
- package/docs/docs/api/qlearning.md +0 -138
- package/docs/docs/api/storage.md +0 -168
- package/docs/docs/architecture.md +0 -155
- package/docs/docs/cli.md +0 -210
- package/docs/docs/contributing.md +0 -162
- package/docs/docs/core-concepts.md +0 -152
- package/docs/docs/examples/advanced-training.md +0 -244
- package/docs/docs/examples/custom-environment.md +0 -198
- package/docs/docs/examples/custom-storage.md +0 -251
- package/docs/docs/getting-started.md +0 -91
- package/docs/docusaurus.config.ts +0 -149
- package/docs/package-lock.json +0 -19522
- package/docs/package.json +0 -49
- package/docs/sidebars.ts +0 -33
- package/docs/src/components/HomepageFeatures/index.tsx +0 -71
- package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
- package/docs/src/css/custom.css +0 -79
- package/docs/src/pages/index.module.css +0 -23
- package/docs/src/pages/index.tsx +0 -44
- package/docs/src/pages/markdown-page.mdx +0 -7
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.png +0 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
- package/docs/static/img/undraw_docusaurus_react.svg +0 -170
- package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
- package/docs/tsconfig.json +0 -12
- package/legacy/worker.js +0 -166
- package/legacy/wrangler.toml +0 -11
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@game_ryo/lsji",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A general-purpose reinforcement learning agent framework (Node.js)",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "A general-purpose reinforcement learning agent framework (Node.js) with LLM agent capabilities",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
".": "./src/index.js",
|
|
9
|
-
"./storage": "./src/storage/index.js"
|
|
10
|
-
},
|
|
7
|
+
"main": "./src/index.js",
|
|
11
8
|
"bin": {
|
|
12
9
|
"lsji": "./bin/lsji.js"
|
|
13
10
|
},
|
|
@@ -18,9 +15,20 @@
|
|
|
18
15
|
"test": "vitest run",
|
|
19
16
|
"test:watch": "vitest",
|
|
20
17
|
"build": "echo 'No build step required (ESM)'",
|
|
21
|
-
"
|
|
18
|
+
"build:ui": "cd src/server/ui && npm install && npm run build",
|
|
19
|
+
"lint": "echo 'No linter configured'",
|
|
20
|
+
"serve": "node src/server/index.js"
|
|
22
21
|
},
|
|
23
22
|
"devDependencies": {
|
|
24
23
|
"vitest": "^2.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"uuid": "^14.0.2",
|
|
27
|
+
"express": "^4.19.2",
|
|
28
|
+
"socket.io": "^4.7.5",
|
|
29
|
+
"cors": "^2.8.5"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
25
33
|
}
|
|
26
34
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,38 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LSJI CLI - Command Line Interface
|
|
3
3
|
*
|
|
4
|
-
* Provides train/play/status/start/stop commands
|
|
5
|
-
*
|
|
4
|
+
* Provides train/play/status/start/stop commands for RL,
|
|
5
|
+
* plus agent/budget/hitl/checkpoint commands for LLM agents.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Agent } from './core/agent.js';
|
|
9
9
|
import { QLearning } from './core/qlearning.js';
|
|
10
10
|
import { createStorage } from './storage/index.js';
|
|
11
11
|
import { RockPaperScissorsEnv, TrainingPattern, getTrainingAction } from './envs/rps.js';
|
|
12
|
+
import {
|
|
13
|
+
createLLMAgent,
|
|
14
|
+
createBudgetController,
|
|
15
|
+
createApprovalGate,
|
|
16
|
+
createExecutionEngine,
|
|
17
|
+
createIdempotencyStore
|
|
18
|
+
} from './index.js';
|
|
12
19
|
|
|
13
20
|
// Hand names for display
|
|
14
21
|
const HAND_NAMES = ['Rock', 'Scissors', 'Paper'];
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Parse command line arguments
|
|
18
|
-
* @returns {Object} Parsed arguments
|
|
19
25
|
*/
|
|
20
26
|
function parseArgs() {
|
|
21
27
|
const args = process.argv.slice(2);
|
|
22
28
|
const command = args[0];
|
|
23
|
-
const options = {};
|
|
29
|
+
const options = { _: [] };
|
|
24
30
|
|
|
25
31
|
for (let i = 1; i < args.length; i++) {
|
|
26
32
|
const arg = args[i];
|
|
27
|
-
if (arg.startsWith(
|
|
33
|
+
if (arg.startsWith("--")) {
|
|
28
34
|
const key = arg.slice(2);
|
|
29
35
|
const nextArg = args[i + 1];
|
|
30
|
-
if (nextArg && !nextArg.startsWith(
|
|
36
|
+
if (nextArg && !nextArg.startsWith("--")) {
|
|
31
37
|
options[key] = nextArg;
|
|
32
38
|
i++;
|
|
33
39
|
} else {
|
|
34
40
|
options[key] = true;
|
|
35
41
|
}
|
|
42
|
+
} else {
|
|
43
|
+
options._.push(arg);
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
|
|
@@ -41,8 +49,6 @@ function parseArgs() {
|
|
|
41
49
|
|
|
42
50
|
/**
|
|
43
51
|
* Format output as JSON or table
|
|
44
|
-
* @param {Object} data - Data to output
|
|
45
|
-
* @param {boolean} json - Whether to output JSON
|
|
46
52
|
*/
|
|
47
53
|
function output(data, json = false) {
|
|
48
54
|
if (json) {
|
|
@@ -78,29 +84,25 @@ export async function main() {
|
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
// Create Q-learning engine
|
|
82
|
-
const qlearning = new QLearning({
|
|
83
|
-
alpha: parseFloat(options.alpha) || 0.1,
|
|
84
|
-
gamma: parseFloat(options.gamma) || 0.9,
|
|
85
|
-
epsilon: parseFloat(options.epsilon) || 0.1,
|
|
86
|
-
storage
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Create environment (Rock-Paper-Scissors)
|
|
90
|
-
const opponent = options.opponent || 'random';
|
|
91
|
-
const env = new RockPaperScissorsEnv({ opponent });
|
|
92
|
-
|
|
93
|
-
// Create agent
|
|
94
|
-
const agent = new Agent({ qlearning, storage, env });
|
|
95
|
-
|
|
96
87
|
try {
|
|
97
88
|
switch (command) {
|
|
89
|
+
// ===== RL Commands (existing) =====
|
|
98
90
|
case 'train': {
|
|
91
|
+
const qlearning = new QLearning({
|
|
92
|
+
alpha: parseFloat(options.alpha) || 0.1,
|
|
93
|
+
gamma: parseFloat(options.gamma) || 0.9,
|
|
94
|
+
epsilon: parseFloat(options.epsilon) || 0.1,
|
|
95
|
+
storage
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const opponent = options.opponent || 'random';
|
|
99
|
+
const env = new RockPaperScissorsEnv({ opponent });
|
|
100
|
+
const agent = new Agent({ qlearning, storage, env });
|
|
101
|
+
|
|
99
102
|
const episodes = parseInt(options.episodes) || 200;
|
|
100
103
|
const pattern = parseInt(options.pattern) || 0;
|
|
101
104
|
const batchSize = parseInt(options.batchSize) || 200;
|
|
102
105
|
|
|
103
|
-
// Create action selector based on pattern
|
|
104
106
|
let actionSelector = null;
|
|
105
107
|
if (pattern > 0) {
|
|
106
108
|
actionSelector = (episode, lastAction) => getTrainingAction(pattern, episode, lastAction);
|
|
@@ -113,28 +115,31 @@ export async function main() {
|
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
case 'play': {
|
|
118
|
+
const qlearning = new QLearning({
|
|
119
|
+
alpha: parseFloat(options.alpha) || 0.1,
|
|
120
|
+
gamma: parseFloat(options.gamma) || 0.9,
|
|
121
|
+
epsilon: parseFloat(options.epsilon) || 0.1,
|
|
122
|
+
storage
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const opponent = options.opponent || 'random';
|
|
126
|
+
const env = new RockPaperScissorsEnv({ opponent });
|
|
127
|
+
const agent = new Agent({ qlearning, storage, env });
|
|
128
|
+
|
|
116
129
|
const hand = parseInt(options.hand);
|
|
117
130
|
if (isNaN(hand) || hand < 0 || hand > 2) {
|
|
118
131
|
console.error('Error: --hand must be 0 (Rock), 1 (Scissors), or 2 (Paper)');
|
|
119
132
|
process.exit(1);
|
|
120
133
|
}
|
|
121
134
|
|
|
122
|
-
// For play, we need to pass the user's hand to the environment
|
|
123
|
-
// The RPS env uses its own opponent strategy, so we'll use a custom approach
|
|
124
135
|
const state = await env.getState() || '0';
|
|
125
136
|
const actionSize = env.actionSize();
|
|
126
137
|
const aiHand = await qlearning.act(state, actionSize);
|
|
127
138
|
|
|
128
|
-
// Calculate outcome manually for display
|
|
129
139
|
const { judge, reward, outcome } = RockPaperScissorsEnv.calculateOutcome(aiHand, hand);
|
|
130
|
-
|
|
131
|
-
// Step the environment with AI's action (to update state and Q-table)
|
|
132
140
|
const result = await env.step(aiHand);
|
|
133
|
-
|
|
134
|
-
// Update Q-table with actual result
|
|
135
141
|
await qlearning.learnSimple(state, aiHand, result.reward);
|
|
136
142
|
|
|
137
|
-
// Record battle
|
|
138
143
|
await storage.addBattle({
|
|
139
144
|
mode: 'test',
|
|
140
145
|
handA: aiHand,
|
|
@@ -160,66 +165,382 @@ export async function main() {
|
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
case 'status': {
|
|
168
|
+
const qlearning = new QLearning({
|
|
169
|
+
alpha: parseFloat(options.alpha) || 0.1,
|
|
170
|
+
gamma: parseFloat(options.gamma) || 0.9,
|
|
171
|
+
epsilon: parseFloat(options.epsilon) || 0.1,
|
|
172
|
+
storage
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const opponent = options.opponent || 'random';
|
|
176
|
+
const env = new RockPaperScissorsEnv({ opponent });
|
|
177
|
+
const agent = new Agent({ qlearning, storage, env });
|
|
178
|
+
|
|
163
179
|
const result = await agent.status();
|
|
164
180
|
output(result, jsonOutput);
|
|
165
181
|
break;
|
|
166
182
|
}
|
|
167
183
|
|
|
168
184
|
case 'start': {
|
|
185
|
+
const qlearning = new QLearning({ storage });
|
|
186
|
+
const agent = new Agent({ qlearning, storage });
|
|
169
187
|
const result = await agent.start();
|
|
170
188
|
output(result, jsonOutput);
|
|
171
189
|
break;
|
|
172
190
|
}
|
|
173
191
|
|
|
174
192
|
case 'stop': {
|
|
193
|
+
const qlearning = new QLearning({ storage });
|
|
194
|
+
const agent = new Agent({ qlearning, storage });
|
|
175
195
|
const result = await agent.stop();
|
|
176
196
|
output(result, jsonOutput);
|
|
177
197
|
break;
|
|
178
198
|
}
|
|
179
199
|
|
|
200
|
+
// ===== LLM Agent Commands (NEW) =====
|
|
201
|
+
case 'agent': {
|
|
202
|
+
const subcommand = options._[0] || 'help';
|
|
203
|
+
|
|
204
|
+
switch (subcommand) {
|
|
205
|
+
case 'run': {
|
|
206
|
+
const task = options.task || options._[1];
|
|
207
|
+
if (!task) {
|
|
208
|
+
console.error('Error: Task required. Use --task "your task here"');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(`Running agent on task: ${task}`);
|
|
213
|
+
|
|
214
|
+
const agent = await createLLMAgent({
|
|
215
|
+
llm: {
|
|
216
|
+
provider: options.provider || 'openai',
|
|
217
|
+
model: options.model || 'gpt-4o-mini',
|
|
218
|
+
apiKey: options.apiKey || process.env.OPENAI_API_KEY,
|
|
219
|
+
},
|
|
220
|
+
budget: {
|
|
221
|
+
maxCostPerRun: parseFloat(options.maxCost) || 10,
|
|
222
|
+
maxTokensPerRun: parseInt(options.maxTokens) || 100000,
|
|
223
|
+
},
|
|
224
|
+
hitl: {
|
|
225
|
+
enabled: options.hitl !== 'false',
|
|
226
|
+
defaultTimeout: parseInt(options.hitlTimeout) || 300000,
|
|
227
|
+
},
|
|
228
|
+
memory: {
|
|
229
|
+
conversation: options.conversation !== 'false',
|
|
230
|
+
semantic: options.semantic === 'true',
|
|
231
|
+
episodic: options.episodic !== 'false',
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const result = await agent.run(task, {
|
|
236
|
+
runId: options.runId,
|
|
237
|
+
budgetId: options.budgetId,
|
|
238
|
+
hitlRequired: options.hitlRequired ? options.hitlRequired.split(',') : ['file_write', 'api_call', 'send_email', 'code_exec'],
|
|
239
|
+
maxSteps: parseInt(options.maxSteps) || 50,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log('Agent run complete:', result);
|
|
243
|
+
await agent.shutdown();
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case 'run-durable': {
|
|
248
|
+
const task = options.task || options._[1];
|
|
249
|
+
if (!task) {
|
|
250
|
+
console.error('Error: Task required. Use --task "your task here"');
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(`Running durable agent on task: ${task}`);
|
|
255
|
+
|
|
256
|
+
const agent = await createLLMAgent({
|
|
257
|
+
llm: {
|
|
258
|
+
provider: options.provider || 'openai',
|
|
259
|
+
model: options.model || 'gpt-4o-mini',
|
|
260
|
+
apiKey: options.apiKey || process.env.OPENAI_API_KEY,
|
|
261
|
+
},
|
|
262
|
+
execution: {
|
|
263
|
+
checkpointInterval: parseInt(options.checkpointInterval) || 3,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await agent.runDurable(task, {
|
|
268
|
+
workflowId: options.workflowId,
|
|
269
|
+
checkpointEvery: parseInt(options.checkpointEvery) || 3,
|
|
270
|
+
resumeFrom: options.resumeFrom,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
console.log('Durable agent run complete:', result);
|
|
274
|
+
await agent.shutdown();
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case 'status': {
|
|
279
|
+
const agent = await createLLMAgent({
|
|
280
|
+
llm: { provider: 'openai', model: 'gpt-4o-mini' },
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const status = agent.getStatus();
|
|
284
|
+
output(status, jsonOutput);
|
|
285
|
+
await agent.shutdown();
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
default:
|
|
290
|
+
console.log(`
|
|
291
|
+
Agent Commands:
|
|
292
|
+
agent run --task "task description" Run agent on a task
|
|
293
|
+
agent run-durable --task "task" Run with checkpointing
|
|
294
|
+
agent status Show agent status
|
|
295
|
+
`);
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
case 'budget': {
|
|
301
|
+
const subcommand = options._[0] || 'status';
|
|
302
|
+
const budget = createBudgetController({
|
|
303
|
+
maxCostPerRun: parseFloat(options.maxCost) || 10,
|
|
304
|
+
maxCostPerDay: parseFloat(options.maxDaily) || 50,
|
|
305
|
+
maxCostPerMonth: parseFloat(options.maxMonthly) || 500,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
switch (subcommand) {
|
|
309
|
+
case 'status': {
|
|
310
|
+
const budgetId = options.budgetId || 'default';
|
|
311
|
+
const status = budget.getStatus(budgetId);
|
|
312
|
+
output(status, jsonOutput);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'reset': {
|
|
317
|
+
const budgetId = options.budgetId || 'default';
|
|
318
|
+
budget.resetRunBudget(budgetId);
|
|
319
|
+
console.log(`Budget ${budgetId} reset`);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
case 'check': {
|
|
324
|
+
const budgetId = options.budgetId || 'default';
|
|
325
|
+
const estimatedCost = parseFloat(options.cost) || 0;
|
|
326
|
+
const estimatedTokens = parseInt(options.tokens) || 0;
|
|
327
|
+
const result = budget.checkBudget(budgetId, estimatedCost, estimatedTokens);
|
|
328
|
+
output(result, jsonOutput);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
case 'hitl': {
|
|
336
|
+
const subcommand = options._[0] || 'help';
|
|
337
|
+
|
|
338
|
+
switch (subcommand) {
|
|
339
|
+
case 'approve': {
|
|
340
|
+
const approvalId = options.id || options._[1];
|
|
341
|
+
if (!approvalId) {
|
|
342
|
+
console.error('Error: Approval ID required');
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const hitl = await createApprovalGate({});
|
|
347
|
+
const result = await hitl.approve(approvalId, {
|
|
348
|
+
decider: options.decider || 'cli-user',
|
|
349
|
+
reason: options.reason || 'Approved via CLI',
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
console.log('Approved:', result);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
case 'reject': {
|
|
357
|
+
const approvalId = options.id || options._[1];
|
|
358
|
+
if (!approvalId) {
|
|
359
|
+
console.error('Error: Approval ID required');
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const hitl = await createApprovalGate({});
|
|
364
|
+
const result = await hitl.reject(approvalId, {
|
|
365
|
+
decider: options.decider || 'cli-user',
|
|
366
|
+
reason: options.reason || 'Rejected via CLI',
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.log('Rejected:', result);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
case 'list': {
|
|
374
|
+
const hitl = await createApprovalGate({});
|
|
375
|
+
const approvals = await hitl.getPendingApprovals(parseInt(options.limit) || 20);
|
|
376
|
+
output(approvals, jsonOutput);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
case 'status': {
|
|
381
|
+
const approvalId = options.id || options._[1];
|
|
382
|
+
if (!approvalId) {
|
|
383
|
+
console.error('Error: Approval ID required');
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const hitl = await createApprovalGate({});
|
|
388
|
+
const approval = await hitl.getApproval(approvalId);
|
|
389
|
+
output(approval, jsonOutput);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
case 'checkpoint': {
|
|
397
|
+
const subcommand = options._[0] || 'help';
|
|
398
|
+
|
|
399
|
+
switch (subcommand) {
|
|
400
|
+
case 'list': {
|
|
401
|
+
const engine = await createExecutionEngine({});
|
|
402
|
+
const workflows = await engine.listWorkflows();
|
|
403
|
+
output(workflows, jsonOutput);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
case 'show': {
|
|
408
|
+
const workflowId = options.workflowId || options._[1];
|
|
409
|
+
if (!workflowId) {
|
|
410
|
+
console.error('Error: Workflow ID required');
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const engine = await createExecutionEngine({});
|
|
415
|
+
const checkpoints = await engine.getCheckpoints(workflowId);
|
|
416
|
+
output(checkpoints, jsonOutput);
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
case 'recover': {
|
|
421
|
+
const workflowId = options.workflowId || options._[1];
|
|
422
|
+
if (!workflowId) {
|
|
423
|
+
console.error('Error: Workflow ID required');
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const engine = await createExecutionEngine({});
|
|
428
|
+
const recovery = await engine.recover(workflowId);
|
|
429
|
+
output(recovery, jsonOutput);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
case 'idempotency': {
|
|
437
|
+
const subcommand = options._[0] || 'help';
|
|
438
|
+
|
|
439
|
+
switch (subcommand) {
|
|
440
|
+
case 'cleanup': {
|
|
441
|
+
const store = await createIdempotencyStore({});
|
|
442
|
+
await store.cleanup();
|
|
443
|
+
console.log('Expired idempotency keys cleaned up');
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
case 'list': {
|
|
448
|
+
const store = await createIdempotencyStore({});
|
|
449
|
+
const operation = options.operation || options._[1];
|
|
450
|
+
const keys = await store.getByOperation(operation, parseInt(options.limit) || 50);
|
|
451
|
+
output(keys, jsonOutput);
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
|
|
180
458
|
case 'help':
|
|
181
459
|
case '--help':
|
|
182
460
|
case '-h':
|
|
183
461
|
default: {
|
|
184
462
|
console.log(`
|
|
185
|
-
LSJI CLI - Reinforcement Learning Agent Framework
|
|
463
|
+
LSJI CLI - Reinforcement Learning & LLM Agent Framework
|
|
186
464
|
|
|
187
465
|
Usage: lsji <command> [options]
|
|
188
466
|
|
|
189
|
-
Commands
|
|
190
|
-
train
|
|
191
|
-
play
|
|
192
|
-
status
|
|
193
|
-
start
|
|
194
|
-
stop
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
--
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
--
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
467
|
+
=== RL Commands ===
|
|
468
|
+
train Train the RL agent
|
|
469
|
+
play Play against the RL agent
|
|
470
|
+
status Show RL system status
|
|
471
|
+
start Enable RL training/play
|
|
472
|
+
stop Disable RL training/play
|
|
473
|
+
|
|
474
|
+
=== LLM Agent Commands ===
|
|
475
|
+
agent run --task "task" Run LLM agent on task
|
|
476
|
+
agent run-durable --task "task" Run with checkpointing
|
|
477
|
+
agent status Show agent status
|
|
478
|
+
|
|
479
|
+
budget status [--budgetId] Show budget status
|
|
480
|
+
budget check --cost --tokens Check if operation allowed
|
|
481
|
+
budget reset [--budgetId] Reset run budget
|
|
482
|
+
|
|
483
|
+
hitl approve --id <id> Approve pending request
|
|
484
|
+
hitl reject --id <id> Reject pending request
|
|
485
|
+
hitl list [--limit] List pending approvals
|
|
486
|
+
hitl status --id <id> Show approval status
|
|
487
|
+
|
|
488
|
+
checkpoint list List workflows with checkpoints
|
|
489
|
+
checkpoint show --workflowId <id> Show checkpoints for workflow
|
|
490
|
+
checkpoint recover --workflowId <id> Recover from latest checkpoint
|
|
491
|
+
|
|
492
|
+
idempotency cleanup Clean expired keys
|
|
493
|
+
idempotency list --operation <op> List keys for operation
|
|
494
|
+
|
|
495
|
+
=== Common Options ===
|
|
496
|
+
--storage <type> Storage: sqlite, better-sqlite, memory (default: sqlite)
|
|
497
|
+
--db-path <path> Database path (default: ./lsji.db)
|
|
498
|
+
--json Output as JSON
|
|
499
|
+
|
|
500
|
+
=== RL Options ===
|
|
501
|
+
--alpha <num> Learning rate (default: 0.1)
|
|
502
|
+
--gamma <num> Discount factor (default: 0.9)
|
|
503
|
+
--epsilon <num> Exploration rate (default: 0.1)
|
|
504
|
+
--opponent <type> Opponent: random, always_rock, counter, sequential
|
|
505
|
+
--episodes <num> Training episodes (default: 200)
|
|
506
|
+
--pattern <num> Training pattern 0-3 (default: 0)
|
|
507
|
+
--hand <num> Your hand: 0=Rock, 1=Scissors, 2=Paper
|
|
508
|
+
|
|
509
|
+
=== LLM Agent Options ===
|
|
510
|
+
--provider <name> LLM provider: openai, anthropic, local (default: openai)
|
|
511
|
+
--model <name> Model name (default: gpt-4o-mini)
|
|
512
|
+
--api-key <key> API key (or use OPENAI_API_KEY env)
|
|
513
|
+
--max-cost <num> Max cost per run USD (default: 10)
|
|
514
|
+
--max-tokens <num> Max tokens per run (default: 100000)
|
|
515
|
+
--hitl <true|false> Enable HITL (default: true)
|
|
516
|
+
--hitl-timeout <ms> Approval timeout (default: 300000)
|
|
517
|
+
--conversation <tf> Enable conversation memory (default: true)
|
|
518
|
+
--semantic <tf> Enable semantic memory (default: false)
|
|
519
|
+
--episodic <tf> Enable episodic memory (default: true)
|
|
520
|
+
--hitl-required <list> Comma-separated tools requiring approval
|
|
521
|
+
--max-steps <num> Max agent steps (default: 50)
|
|
522
|
+
--checkpoint-interval Checkpoint every N steps (default: 3)
|
|
523
|
+
--workflow-id <id> Workflow ID for durable runs
|
|
524
|
+
--resume-from <id> Resume from checkpoint ID
|
|
525
|
+
|
|
526
|
+
=== Budget Options ===
|
|
527
|
+
--budget-id <id> Budget identifier
|
|
528
|
+
--max-daily <num> Max daily cost (default: 50)
|
|
529
|
+
--max-monthly <num> Max monthly cost (default: 500)
|
|
214
530
|
|
|
215
531
|
Examples:
|
|
532
|
+
# RL Training
|
|
216
533
|
lsji train --episodes 500
|
|
217
|
-
lsji train --episodes 100 --pattern 1 --opponent always_rock
|
|
218
534
|
lsji play --hand 0
|
|
219
535
|
lsji status --json
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
536
|
+
|
|
537
|
+
# LLM Agent
|
|
538
|
+
lsji agent run --task "Search for latest AI news and summarize"
|
|
539
|
+
lsji agent run-durable --task "Analyze codebase and create report" --workflow-id my-analysis
|
|
540
|
+
lsji budget status --budgetId project-1
|
|
541
|
+
lsji hitl list
|
|
542
|
+
lsji checkpoint show --workflowId my-analysis
|
|
543
|
+
`);
|
|
223
544
|
break;
|
|
224
545
|
}
|
|
225
546
|
}
|
|
@@ -230,3 +551,15 @@ Examples:
|
|
|
230
551
|
await storage.close();
|
|
231
552
|
}
|
|
232
553
|
}
|
|
554
|
+
|
|
555
|
+
// Auto-run main if this file is executed directly
|
|
556
|
+
import { fileURLToPath } from 'url';
|
|
557
|
+
import { dirname, resolve } from 'path';
|
|
558
|
+
|
|
559
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
560
|
+
const __dirname = dirname(__filename);
|
|
561
|
+
|
|
562
|
+
if (process.argv[1] && resolve(process.argv[1]) === resolve(__filename)) {
|
|
563
|
+
main().catch(console.error);
|
|
564
|
+
}
|
|
565
|
+
|