opc-agent 1.4.0 → 2.0.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/CHANGELOG.md +25 -0
- package/README.md +91 -32
- package/dist/channels/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- package/dist/cli.js +415 -8
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +120 -3
- package/dist/core/runtime.d.ts +1 -0
- package/dist/core/runtime.js +44 -0
- package/dist/core/scheduler.d.ts +52 -0
- package/dist/core/scheduler.js +168 -0
- package/dist/core/subagent.d.ts +28 -0
- package/dist/core/subagent.js +65 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17 -1
- package/dist/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/schema/oad.d.ts +179 -4
- package/dist/schema/oad.js +12 -1
- package/dist/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -0
- package/dist/tools/builtin/datetime.d.ts +3 -0
- package/dist/tools/builtin/datetime.js +44 -0
- package/dist/tools/builtin/file.d.ts +3 -0
- package/dist/tools/builtin/file.js +151 -0
- package/dist/tools/builtin/index.d.ts +15 -0
- package/dist/tools/builtin/index.js +30 -0
- package/dist/tools/builtin/shell.d.ts +3 -0
- package/dist/tools/builtin/shell.js +43 -0
- package/dist/tools/builtin/web.d.ts +3 -0
- package/dist/tools/builtin/web.js +37 -0
- package/dist/tools/mcp-client.d.ts +24 -0
- package/dist/tools/mcp-client.js +119 -0
- package/package.json +1 -1
- package/src/channels/telegram.ts +212 -90
- package/src/cli.ts +418 -8
- package/src/core/agent.ts +295 -152
- package/src/core/runtime.ts +47 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/daemon.ts +96 -0
- package/src/index.ts +11 -0
- package/src/providers/index.ts +354 -339
- package/src/schema/oad.ts +167 -154
- package/src/skills/auto-learn.ts +262 -0
- package/src/tools/builtin/datetime.ts +41 -0
- package/src/tools/builtin/file.ts +107 -0
- package/src/tools/builtin/index.ts +28 -0
- package/src/tools/builtin/shell.ts +43 -0
- package/src/tools/builtin/web.ts +35 -0
- package/src/tools/mcp-client.ts +131 -0
- package/tests/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -0
- package/tests/cli.test.ts +46 -0
- package/tests/subagent.test.ts +130 -0
- package/tests/telegram-discord.test.ts +60 -0
package/dist/cli.js
CHANGED
|
@@ -61,6 +61,7 @@ const workflow_1 = require("./core/workflow");
|
|
|
61
61
|
const versioning_1 = require("./core/versioning");
|
|
62
62
|
const providers_1 = require("./providers");
|
|
63
63
|
const knowledge_1 = require("./core/knowledge");
|
|
64
|
+
const child_process_1 = require("child_process");
|
|
64
65
|
const program = new commander_1.Command();
|
|
65
66
|
const color = {
|
|
66
67
|
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
@@ -118,7 +119,7 @@ async function select(question, options) {
|
|
|
118
119
|
program
|
|
119
120
|
.name('opc')
|
|
120
121
|
.description('OPC Agent - Open Agent Framework for business workstations')
|
|
121
|
-
.version('
|
|
122
|
+
.version('2.0.0');
|
|
122
123
|
// ── Init command ─────────────────────────────────────────────
|
|
123
124
|
program
|
|
124
125
|
.command('init')
|
|
@@ -175,15 +176,24 @@ spec:
|
|
|
175
176
|
// src/index.ts — entry point
|
|
176
177
|
fs.writeFileSync(path.join(dir, 'src', 'index.ts'), `import { AgentRuntime } from 'opc-agent';
|
|
177
178
|
import { EchoSkill } from './skills/echo';
|
|
179
|
+
import { readFileSync, existsSync } from 'fs';
|
|
178
180
|
|
|
179
181
|
async function main() {
|
|
180
182
|
const runtime = new AgentRuntime();
|
|
181
183
|
|
|
182
184
|
// Load OAD config
|
|
183
|
-
await runtime.loadConfig('./agent.yaml');
|
|
185
|
+
const config = await runtime.loadConfig('./agent.yaml');
|
|
186
|
+
|
|
187
|
+
// Load personality and context files
|
|
188
|
+
const soul = existsSync('./SOUL.md') ? readFileSync('./SOUL.md', 'utf-8') : '';
|
|
189
|
+
const context = existsSync('./CONTEXT.md') ? readFileSync('./CONTEXT.md', 'utf-8') : '';
|
|
190
|
+
if (soul || context) {
|
|
191
|
+
const fullPrompt = [soul, context, config.spec.systemPrompt].filter(Boolean).join('\\n\\n');
|
|
192
|
+
config.spec.systemPrompt = fullPrompt;
|
|
193
|
+
}
|
|
184
194
|
|
|
185
195
|
// Initialize agent with channels, memory, etc.
|
|
186
|
-
const agent = await runtime.initialize();
|
|
196
|
+
const agent = await runtime.initialize(config);
|
|
187
197
|
|
|
188
198
|
// Register custom skills
|
|
189
199
|
runtime.registerSkill(new EchoSkill());
|
|
@@ -349,11 +359,52 @@ ${name}/
|
|
|
349
359
|
## Configuration
|
|
350
360
|
|
|
351
361
|
Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
|
|
362
|
+
`);
|
|
363
|
+
// SOUL.md — agent personality
|
|
364
|
+
const createdDate = new Date().toISOString().split('T')[0];
|
|
365
|
+
fs.writeFileSync(path.join(dir, 'SOUL.md'), `# ${name} Personality
|
|
366
|
+
|
|
367
|
+
## Identity
|
|
368
|
+
- Name: ${name}
|
|
369
|
+
- Role: AI Assistant
|
|
370
|
+
- Created: ${createdDate}
|
|
371
|
+
|
|
372
|
+
## Personality Traits
|
|
373
|
+
- Helpful and professional
|
|
374
|
+
- Concise but thorough
|
|
375
|
+
- Friendly tone
|
|
376
|
+
|
|
377
|
+
## Communication Style
|
|
378
|
+
- Use clear, simple language
|
|
379
|
+
- Be direct — answer the question first, then explain
|
|
380
|
+
- Use markdown formatting when helpful
|
|
381
|
+
|
|
382
|
+
## Rules
|
|
383
|
+
- Always be honest about limitations
|
|
384
|
+
- Ask for clarification when the request is ambiguous
|
|
385
|
+
- Never make up information
|
|
386
|
+
`);
|
|
387
|
+
// CONTEXT.md — project context
|
|
388
|
+
fs.writeFileSync(path.join(dir, 'CONTEXT.md'), `# Project Context
|
|
389
|
+
|
|
390
|
+
## About This Agent
|
|
391
|
+
${name} is an AI agent built with OPC Agent Framework.
|
|
392
|
+
|
|
393
|
+
## Knowledge Base
|
|
394
|
+
Add project-specific context here. The agent reads this file
|
|
395
|
+
on startup to understand the project context.
|
|
396
|
+
|
|
397
|
+
## Important Notes
|
|
398
|
+
- Add domain knowledge here
|
|
399
|
+
- Add FAQ items here
|
|
400
|
+
- Add company policies here
|
|
352
401
|
`);
|
|
353
402
|
console.log(`\n${icon.success} Created agent project: ${color.bold(name + '/')}`);
|
|
354
403
|
console.log(` ${icon.file} agent.yaml - Agent definition (OAD)`);
|
|
355
404
|
console.log(` ${icon.file} src/index.ts - Entry point`);
|
|
356
405
|
console.log(` ${icon.file} src/skills/echo.ts - Example skill`);
|
|
406
|
+
console.log(` ${icon.file} SOUL.md - Agent personality`);
|
|
407
|
+
console.log(` ${icon.file} CONTEXT.md - Project context`);
|
|
357
408
|
console.log(` ${icon.file} package.json - Dependencies`);
|
|
358
409
|
console.log(` ${icon.file} tsconfig.json - TypeScript config`);
|
|
359
410
|
console.log(` ${icon.file} .env.example - Environment template`);
|
|
@@ -377,6 +428,15 @@ program
|
|
|
377
428
|
loadDotEnv();
|
|
378
429
|
let systemPrompt = 'You are a helpful AI agent.';
|
|
379
430
|
let model;
|
|
431
|
+
let agentName = 'Agent';
|
|
432
|
+
let agentVersion = '1.0.0';
|
|
433
|
+
let providerName = 'openai';
|
|
434
|
+
let skillNames = [];
|
|
435
|
+
// Try loading SOUL.md and CONTEXT.md for enriched system prompt
|
|
436
|
+
const soulPath = path.resolve('SOUL.md');
|
|
437
|
+
const contextPath = path.resolve('CONTEXT.md');
|
|
438
|
+
const soulContent = fs.existsSync(soulPath) ? fs.readFileSync(soulPath, 'utf-8') : '';
|
|
439
|
+
const contextContent = fs.existsSync(contextPath) ? fs.readFileSync(contextPath, 'utf-8') : '';
|
|
380
440
|
try {
|
|
381
441
|
const raw = fs.readFileSync(opts.file, 'utf-8');
|
|
382
442
|
const config = yaml.load(raw);
|
|
@@ -384,15 +444,93 @@ program
|
|
|
384
444
|
systemPrompt = config.spec.systemPrompt;
|
|
385
445
|
if (config?.spec?.model)
|
|
386
446
|
model = config.spec.model;
|
|
387
|
-
|
|
447
|
+
if (config?.metadata?.name)
|
|
448
|
+
agentName = config.metadata.name;
|
|
449
|
+
if (config?.metadata?.version)
|
|
450
|
+
agentVersion = config.metadata.version;
|
|
451
|
+
if (config?.spec?.provider?.default)
|
|
452
|
+
providerName = config.spec.provider.default;
|
|
453
|
+
if (config?.spec?.skills)
|
|
454
|
+
skillNames = config.spec.skills.map((s) => s.name);
|
|
388
455
|
}
|
|
389
456
|
catch {
|
|
390
|
-
|
|
457
|
+
// No config file, use defaults
|
|
391
458
|
}
|
|
459
|
+
// Prepend SOUL.md and CONTEXT.md to system prompt
|
|
460
|
+
systemPrompt = [soulContent, contextContent, systemPrompt].filter(Boolean).join('\n\n');
|
|
392
461
|
const provider = (0, providers_1.createProvider)('openai', model);
|
|
393
462
|
const history = [];
|
|
394
|
-
|
|
395
|
-
const
|
|
463
|
+
// Print startup banner
|
|
464
|
+
const bannerLines = [
|
|
465
|
+
'╔══════════════════════════════════════╗',
|
|
466
|
+
'║ 🤖 OPC Agent — Interactive Chat ║',
|
|
467
|
+
`║ Agent: ${(agentName + ' v' + agentVersion).padEnd(27)}║`,
|
|
468
|
+
`║ Model: ${((providerName + '/' + (model ?? 'default')).slice(0, 27)).padEnd(27)}║`,
|
|
469
|
+
`║ Skills: ${(String(skillNames.length) + ' loaded').padEnd(26)}║`,
|
|
470
|
+
'║ Type /help for commands ║',
|
|
471
|
+
'╚══════════════════════════════════════╝',
|
|
472
|
+
];
|
|
473
|
+
console.log('\n' + color.cyan(bannerLines.join('\n')) + '\n');
|
|
474
|
+
if (soulContent)
|
|
475
|
+
console.log(` ${icon.info} Loaded SOUL.md`);
|
|
476
|
+
if (contextContent)
|
|
477
|
+
console.log(` ${icon.info} Loaded CONTEXT.md`);
|
|
478
|
+
if (soulContent || contextContent)
|
|
479
|
+
console.log();
|
|
480
|
+
const rl = readline.createInterface({
|
|
481
|
+
input: process.stdin,
|
|
482
|
+
output: process.stdout,
|
|
483
|
+
historySize: 100,
|
|
484
|
+
});
|
|
485
|
+
const handleSlashCommand = (cmd) => {
|
|
486
|
+
const lower = cmd.toLowerCase().trim();
|
|
487
|
+
if (lower === '/quit' || lower === '/exit') {
|
|
488
|
+
console.log(`\n${color.dim('Goodbye! 👋')}`);
|
|
489
|
+
process.exit(0);
|
|
490
|
+
}
|
|
491
|
+
if (lower === '/help') {
|
|
492
|
+
console.log(`\n ${color.bold('Available commands:')}`);
|
|
493
|
+
console.log(` ${color.cyan('/help')} — Show this help`);
|
|
494
|
+
console.log(` ${color.cyan('/quit')} — Exit chat (/exit also works)`);
|
|
495
|
+
console.log(` ${color.cyan('/clear')} — Clear conversation history`);
|
|
496
|
+
console.log(` ${color.cyan('/skills')} — List registered skills`);
|
|
497
|
+
console.log(` ${color.cyan('/memory')} — Show memory stats`);
|
|
498
|
+
console.log(` ${color.cyan('/info')} — Show agent info\n`);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
if (lower === '/clear') {
|
|
502
|
+
history.length = 0;
|
|
503
|
+
console.log(`\n ${icon.success} Conversation history cleared.\n`);
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
if (lower === '/skills') {
|
|
507
|
+
if (skillNames.length === 0) {
|
|
508
|
+
console.log(`\n ${icon.info} No skills registered.\n`);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
console.log(`\n ${color.bold('Registered skills:')}`);
|
|
512
|
+
skillNames.forEach((s) => console.log(` • ${color.cyan(s)}`));
|
|
513
|
+
console.log();
|
|
514
|
+
}
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
if (lower === '/memory') {
|
|
518
|
+
console.log(`\n ${color.bold('Memory stats:')}`);
|
|
519
|
+
console.log(` Messages in history: ${color.cyan(String(history.length))}`);
|
|
520
|
+
console.log(` Characters: ${color.cyan(String(history.reduce((a, m) => a + m.content.length, 0)))}\n`);
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
if (lower === '/info') {
|
|
524
|
+
console.log(`\n ${color.bold('Agent Info:')}`);
|
|
525
|
+
console.log(` Name: ${color.cyan(agentName)}`);
|
|
526
|
+
console.log(` Version: ${color.cyan(agentVersion)}`);
|
|
527
|
+
console.log(` Provider: ${color.cyan(providerName)}`);
|
|
528
|
+
console.log(` Model: ${color.cyan(model ?? 'default')}`);
|
|
529
|
+
console.log(` Skills: ${color.cyan(String(skillNames.length))}\n`);
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
return false;
|
|
533
|
+
};
|
|
396
534
|
const ask = () => {
|
|
397
535
|
rl.question(color.cyan('You: '), async (input) => {
|
|
398
536
|
const text = input.trim();
|
|
@@ -400,6 +538,11 @@ program
|
|
|
400
538
|
ask();
|
|
401
539
|
return;
|
|
402
540
|
}
|
|
541
|
+
// Handle slash commands
|
|
542
|
+
if (text.startsWith('/') && handleSlashCommand(text)) {
|
|
543
|
+
ask();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
403
546
|
history.push({ role: 'user', content: text });
|
|
404
547
|
// Build messages for provider
|
|
405
548
|
const messages = history.map((m) => ({
|
|
@@ -431,7 +574,7 @@ program
|
|
|
431
574
|
});
|
|
432
575
|
};
|
|
433
576
|
rl.on('close', () => {
|
|
434
|
-
console.log(`\n${color.dim('Goodbye!')}`);
|
|
577
|
+
console.log(`\n${color.dim('Goodbye! 👋')}`);
|
|
435
578
|
process.exit(0);
|
|
436
579
|
});
|
|
437
580
|
ask();
|
|
@@ -1033,5 +1176,269 @@ program
|
|
|
1033
1176
|
console.log(` ${icon.info} No score data yet. Run the agent first.\n`);
|
|
1034
1177
|
}
|
|
1035
1178
|
});
|
|
1179
|
+
// ── Daemon commands (start/stop/status) ─────────────────────
|
|
1180
|
+
const OPC_DIR = path.resolve('.opc');
|
|
1181
|
+
program
|
|
1182
|
+
.command('start')
|
|
1183
|
+
.description('Start agent as a background daemon')
|
|
1184
|
+
.option('-f, --file <file>', 'OAD file (agent.yaml or oad.yaml)')
|
|
1185
|
+
.action(async () => {
|
|
1186
|
+
if (!fs.existsSync(OPC_DIR))
|
|
1187
|
+
fs.mkdirSync(OPC_DIR, { recursive: true });
|
|
1188
|
+
const pidFile = path.join(OPC_DIR, 'agent.pid');
|
|
1189
|
+
// Check if already running
|
|
1190
|
+
if (fs.existsSync(pidFile)) {
|
|
1191
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1192
|
+
try {
|
|
1193
|
+
process.kill(pid, 0);
|
|
1194
|
+
console.log(`${icon.warn} Agent already running (PID ${pid}).`);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
catch { /* stale */ }
|
|
1198
|
+
}
|
|
1199
|
+
// Find daemon entry point
|
|
1200
|
+
const daemonScript = path.join(__dirname, 'daemon.js');
|
|
1201
|
+
if (!fs.existsSync(daemonScript)) {
|
|
1202
|
+
console.error(`${icon.error} Daemon script not found. Run ${color.cyan('npm run build')} first.`);
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
const logFile = path.join(OPC_DIR, 'agent.log');
|
|
1206
|
+
const out = fs.openSync(logFile, 'a');
|
|
1207
|
+
const err = fs.openSync(logFile, 'a');
|
|
1208
|
+
const child = (0, child_process_1.spawn)(process.execPath, [daemonScript], {
|
|
1209
|
+
detached: true,
|
|
1210
|
+
stdio: ['ignore', out, err],
|
|
1211
|
+
cwd: process.cwd(),
|
|
1212
|
+
env: process.env,
|
|
1213
|
+
});
|
|
1214
|
+
child.unref();
|
|
1215
|
+
// Wait briefly for PID file
|
|
1216
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1217
|
+
if (fs.existsSync(pidFile)) {
|
|
1218
|
+
const pid = fs.readFileSync(pidFile, 'utf-8').trim();
|
|
1219
|
+
console.log(`${icon.success} Agent started (PID ${pid})`);
|
|
1220
|
+
console.log(` ${color.dim('Logs:')} ${logFile}`);
|
|
1221
|
+
console.log(` ${color.dim('Stop:')} opc stop`);
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
console.log(`${icon.success} Agent starting... (PID ${child.pid})`);
|
|
1225
|
+
console.log(` ${color.dim('Logs:')} ${logFile}`);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
program
|
|
1229
|
+
.command('stop')
|
|
1230
|
+
.description('Stop the background daemon')
|
|
1231
|
+
.action(() => {
|
|
1232
|
+
const pidFile = path.join(OPC_DIR, 'agent.pid');
|
|
1233
|
+
if (!fs.existsSync(pidFile)) {
|
|
1234
|
+
console.log(`${icon.info} No running agent found.`);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1238
|
+
try {
|
|
1239
|
+
// On Windows, process.kill with SIGTERM may not work; use taskkill
|
|
1240
|
+
if (process.platform === 'win32') {
|
|
1241
|
+
const { execSync } = require('child_process');
|
|
1242
|
+
try {
|
|
1243
|
+
execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore' });
|
|
1244
|
+
}
|
|
1245
|
+
catch { /* ignore */ }
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
process.kill(pid, 'SIGTERM');
|
|
1249
|
+
}
|
|
1250
|
+
console.log(`${icon.success} Sent stop signal to PID ${pid}`);
|
|
1251
|
+
}
|
|
1252
|
+
catch {
|
|
1253
|
+
console.log(`${icon.warn} Process ${pid} not found (may have already stopped).`);
|
|
1254
|
+
}
|
|
1255
|
+
try {
|
|
1256
|
+
fs.unlinkSync(pidFile);
|
|
1257
|
+
}
|
|
1258
|
+
catch { /* ignore */ }
|
|
1259
|
+
});
|
|
1260
|
+
program
|
|
1261
|
+
.command('status')
|
|
1262
|
+
.description('Check daemon status')
|
|
1263
|
+
.action(() => {
|
|
1264
|
+
const pidFile = path.join(OPC_DIR, 'agent.pid');
|
|
1265
|
+
if (!fs.existsSync(pidFile)) {
|
|
1266
|
+
console.log(`\n Status: ${color.red('stopped')}\n`);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1270
|
+
let running = false;
|
|
1271
|
+
try {
|
|
1272
|
+
process.kill(pid, 0);
|
|
1273
|
+
running = true;
|
|
1274
|
+
}
|
|
1275
|
+
catch { /* not running */ }
|
|
1276
|
+
if (!running) {
|
|
1277
|
+
console.log(`\n Status: ${color.red('stopped')} (stale PID file)`);
|
|
1278
|
+
try {
|
|
1279
|
+
fs.unlinkSync(pidFile);
|
|
1280
|
+
}
|
|
1281
|
+
catch { /* ignore */ }
|
|
1282
|
+
console.log();
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
// Uptime
|
|
1286
|
+
const startedFile = path.join(OPC_DIR, 'started');
|
|
1287
|
+
let uptime = '';
|
|
1288
|
+
if (fs.existsSync(startedFile)) {
|
|
1289
|
+
const startedMs = parseInt(fs.readFileSync(startedFile, 'utf-8').trim(), 10);
|
|
1290
|
+
const secs = Math.floor((Date.now() - startedMs) / 1000);
|
|
1291
|
+
const h = Math.floor(secs / 3600);
|
|
1292
|
+
const m = Math.floor((secs % 3600) / 60);
|
|
1293
|
+
const s = secs % 60;
|
|
1294
|
+
uptime = `${h}h ${m}m ${s}s`;
|
|
1295
|
+
}
|
|
1296
|
+
// Agent name from config
|
|
1297
|
+
let agentName = 'unknown';
|
|
1298
|
+
for (const f of ['agent.yaml', 'oad.yaml']) {
|
|
1299
|
+
if (fs.existsSync(f)) {
|
|
1300
|
+
try {
|
|
1301
|
+
const raw = fs.readFileSync(f, 'utf-8');
|
|
1302
|
+
const cfg = yaml.load(raw);
|
|
1303
|
+
if (cfg?.metadata?.name) {
|
|
1304
|
+
agentName = cfg.metadata.name;
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
catch { /* ignore */ }
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
console.log(`\n Status: ${color.green('running')}`);
|
|
1312
|
+
console.log(` PID: ${pid}`);
|
|
1313
|
+
console.log(` Agent: ${color.cyan(agentName)}`);
|
|
1314
|
+
if (uptime)
|
|
1315
|
+
console.log(` Uptime: ${uptime}`);
|
|
1316
|
+
console.log();
|
|
1317
|
+
});
|
|
1318
|
+
// ── Jobs commands ────────────────────────────────────────────
|
|
1319
|
+
const jobsCmd = program.command('jobs').description('Manage scheduled jobs');
|
|
1320
|
+
jobsCmd
|
|
1321
|
+
.command('list', { isDefault: true })
|
|
1322
|
+
.description('List all scheduled jobs')
|
|
1323
|
+
.option('-f, --file <file>', 'OAD file', 'oad.yaml')
|
|
1324
|
+
.action(async (opts) => {
|
|
1325
|
+
const jobs = loadJobsFromConfig(opts.file);
|
|
1326
|
+
if (jobs.length === 0) {
|
|
1327
|
+
console.log(`\n${icon.info} No scheduled jobs defined in config.\n`);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
console.log(`\n${icon.gear} ${color.bold('Scheduled Jobs')}\n`);
|
|
1331
|
+
for (const job of jobs) {
|
|
1332
|
+
const status = job.enabled ? color.green('enabled') : color.dim('disabled');
|
|
1333
|
+
const next = job.nextRun ? job.nextRun.toLocaleString() : color.dim('N/A');
|
|
1334
|
+
console.log(` ${color.cyan(job.id.padEnd(20))} ${job.name}`);
|
|
1335
|
+
console.log(` ${''.padEnd(20)} Schedule: ${color.dim(job.schedule)} | Status: ${status} | Next: ${next}`);
|
|
1336
|
+
console.log();
|
|
1337
|
+
}
|
|
1338
|
+
});
|
|
1339
|
+
jobsCmd
|
|
1340
|
+
.command('run')
|
|
1341
|
+
.argument('<id>', 'Job ID to run')
|
|
1342
|
+
.option('-f, --file <file>', 'OAD file', 'oad.yaml')
|
|
1343
|
+
.description('Manually trigger a scheduled job')
|
|
1344
|
+
.action(async (id, opts) => {
|
|
1345
|
+
const jobs = loadJobsFromConfig(opts.file);
|
|
1346
|
+
const job = jobs.find(j => j.id === id || j.name === id);
|
|
1347
|
+
if (!job) {
|
|
1348
|
+
console.error(`${icon.error} Job "${id}" not found. Available: ${jobs.map(j => j.id).join(', ')}`);
|
|
1349
|
+
process.exit(1);
|
|
1350
|
+
}
|
|
1351
|
+
console.log(`${icon.info} Running job "${color.bold(job.name)}"...`);
|
|
1352
|
+
console.log(` Task: ${color.dim(job.task)}`);
|
|
1353
|
+
console.log(`\n${icon.warn} Manual job execution requires a running daemon. Use ${color.cyan('opc start')} first.\n`);
|
|
1354
|
+
});
|
|
1355
|
+
function loadJobsFromConfig(file) {
|
|
1356
|
+
try {
|
|
1357
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
1358
|
+
const config = yaml.load(raw);
|
|
1359
|
+
const jobConfigs = config?.spec?.scheduler?.jobs ?? [];
|
|
1360
|
+
const { parseCron } = require('./core/scheduler');
|
|
1361
|
+
return jobConfigs.map((j, i) => {
|
|
1362
|
+
const id = j.id || j.name?.toLowerCase().replace(/\s+/g, '-') || `job-${i}`;
|
|
1363
|
+
const parsed = parseCron(j.schedule);
|
|
1364
|
+
// Compute next run
|
|
1365
|
+
const now = new Date();
|
|
1366
|
+
let nextRun;
|
|
1367
|
+
const d = new Date(now);
|
|
1368
|
+
d.setSeconds(0, 0);
|
|
1369
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
1370
|
+
for (let k = 0; k < 48 * 60; k++) {
|
|
1371
|
+
const { cronMatches } = require('./core/scheduler');
|
|
1372
|
+
if (cronMatches(parsed, d)) {
|
|
1373
|
+
nextRun = new Date(d);
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
id,
|
|
1380
|
+
name: j.name || id,
|
|
1381
|
+
schedule: j.schedule,
|
|
1382
|
+
task: j.task || '',
|
|
1383
|
+
enabled: j.enabled !== false,
|
|
1384
|
+
nextRun,
|
|
1385
|
+
};
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
catch {
|
|
1389
|
+
return [];
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// ── Skills commands ──────────────────────────────────────────
|
|
1393
|
+
const skillsCmd = program.command('skills').description('Manage learned skills');
|
|
1394
|
+
skillsCmd
|
|
1395
|
+
.command('list', { isDefault: true })
|
|
1396
|
+
.description('List all learned skills')
|
|
1397
|
+
.option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
|
|
1398
|
+
.action(async (opts) => {
|
|
1399
|
+
const { SkillLearner } = await Promise.resolve().then(() => __importStar(require('./skills/auto-learn')));
|
|
1400
|
+
const learner = new SkillLearner(opts.dir);
|
|
1401
|
+
const skills = await learner.loadLearnedSkills();
|
|
1402
|
+
if (skills.length === 0) {
|
|
1403
|
+
console.log(`\n${icon.info} No learned skills yet.\n`);
|
|
1404
|
+
console.log(` Skills are auto-created from conversations when learning is enabled.`);
|
|
1405
|
+
console.log(` Directory: ${color.dim(path.resolve(opts.dir))}\n`);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
console.log(`\n${icon.gear} ${color.bold('Learned Skills')} (${skills.length})\n`);
|
|
1409
|
+
for (const skill of skills) {
|
|
1410
|
+
console.log(` ${color.cyan(skill.name.padEnd(24))} ${skill.description}`);
|
|
1411
|
+
console.log(` ${''.padEnd(24)} v${skill.version} | used ${skill.usageCount}x | trigger: ${color.dim(skill.trigger)}`);
|
|
1412
|
+
console.log();
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
skillsCmd
|
|
1416
|
+
.command('show')
|
|
1417
|
+
.argument('<name>', 'Skill name')
|
|
1418
|
+
.option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
|
|
1419
|
+
.description('Show details of a learned skill')
|
|
1420
|
+
.action(async (name, opts) => {
|
|
1421
|
+
const skillPath = path.join(opts.dir, `${name}.md`);
|
|
1422
|
+
if (!fs.existsSync(skillPath)) {
|
|
1423
|
+
console.error(`${icon.error} Skill "${name}" not found at ${skillPath}`);
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1426
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
1427
|
+
console.log(`\n${content}`);
|
|
1428
|
+
});
|
|
1429
|
+
skillsCmd
|
|
1430
|
+
.command('remove')
|
|
1431
|
+
.argument('<name>', 'Skill name')
|
|
1432
|
+
.option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
|
|
1433
|
+
.description('Remove a learned skill')
|
|
1434
|
+
.action(async (name, opts) => {
|
|
1435
|
+
const skillPath = path.join(opts.dir, `${name}.md`);
|
|
1436
|
+
if (!fs.existsSync(skillPath)) {
|
|
1437
|
+
console.error(`${icon.error} Skill "${name}" not found.`);
|
|
1438
|
+
process.exit(1);
|
|
1439
|
+
}
|
|
1440
|
+
fs.unlinkSync(skillPath);
|
|
1441
|
+
console.log(`${icon.success} Removed skill "${color.cyan(name)}".`);
|
|
1442
|
+
});
|
|
1036
1443
|
program.parse();
|
|
1037
1444
|
//# sourceMappingURL=cli.js.map
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import type { AgentState, IAgent, IChannel, ISkill, Message, MemoryStore } from './types';
|
|
3
3
|
import { type LLMProvider } from '../providers';
|
|
4
|
+
import { SkillLearner } from '../skills/auto-learn';
|
|
5
|
+
import type { MCPTool } from '../tools/mcp';
|
|
6
|
+
import { MCPToolRegistry } from '../tools/mcp';
|
|
7
|
+
import { type SubAgentConfig, type SubAgentResult } from './subagent';
|
|
4
8
|
export declare class BaseAgent extends EventEmitter implements IAgent {
|
|
5
9
|
readonly name: string;
|
|
6
10
|
private _state;
|
|
@@ -10,6 +14,11 @@ export declare class BaseAgent extends EventEmitter implements IAgent {
|
|
|
10
14
|
private _provider;
|
|
11
15
|
private systemPrompt;
|
|
12
16
|
private historyLimit;
|
|
17
|
+
private toolRegistry;
|
|
18
|
+
private maxToolRounds;
|
|
19
|
+
private skillLearner?;
|
|
20
|
+
private autoLearnConfig;
|
|
21
|
+
private _subAgentManager?;
|
|
13
22
|
constructor(options: {
|
|
14
23
|
name: string;
|
|
15
24
|
systemPrompt?: string;
|
|
@@ -17,11 +26,21 @@ export declare class BaseAgent extends EventEmitter implements IAgent {
|
|
|
17
26
|
model?: string;
|
|
18
27
|
memory?: MemoryStore;
|
|
19
28
|
historyLimit?: number;
|
|
29
|
+
skillsDir?: string;
|
|
30
|
+
learning?: {
|
|
31
|
+
autoSkillCreation?: boolean;
|
|
32
|
+
minConversationLength?: number;
|
|
33
|
+
improveOnUse?: boolean;
|
|
34
|
+
};
|
|
35
|
+
maxToolRounds?: number;
|
|
20
36
|
});
|
|
21
37
|
get state(): AgentState;
|
|
22
38
|
get provider(): LLMProvider;
|
|
23
39
|
getSystemPrompt(): string;
|
|
24
40
|
getMemory(): MemoryStore;
|
|
41
|
+
getSkillLearner(): SkillLearner | undefined;
|
|
42
|
+
getToolRegistry(): MCPToolRegistry;
|
|
43
|
+
registerTool(tool: MCPTool): void;
|
|
25
44
|
private transition;
|
|
26
45
|
init(): Promise<void>;
|
|
27
46
|
start(): Promise<void>;
|
|
@@ -29,7 +48,11 @@ export declare class BaseAgent extends EventEmitter implements IAgent {
|
|
|
29
48
|
registerSkill(skill: ISkill): void;
|
|
30
49
|
bindChannel(channel: IChannel): void;
|
|
31
50
|
getChannels(): IChannel[];
|
|
51
|
+
private getSubAgentManager;
|
|
52
|
+
spawnSubAgent(config: SubAgentConfig): Promise<SubAgentResult>;
|
|
53
|
+
spawnParallel(configs: SubAgentConfig[]): Promise<SubAgentResult[]>;
|
|
32
54
|
handleMessage(message: Message): Promise<Message>;
|
|
55
|
+
private parseToolCall;
|
|
33
56
|
handleMessageStream(message: Message): AsyncIterable<string>;
|
|
34
57
|
private createResponse;
|
|
35
58
|
}
|