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.
Files changed (58) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +91 -32
  3. package/dist/channels/telegram.d.ts +30 -9
  4. package/dist/channels/telegram.js +125 -33
  5. package/dist/cli.js +415 -8
  6. package/dist/core/agent.d.ts +23 -0
  7. package/dist/core/agent.js +120 -3
  8. package/dist/core/runtime.d.ts +1 -0
  9. package/dist/core/runtime.js +44 -0
  10. package/dist/core/scheduler.d.ts +52 -0
  11. package/dist/core/scheduler.js +168 -0
  12. package/dist/core/subagent.d.ts +28 -0
  13. package/dist/core/subagent.js +65 -0
  14. package/dist/daemon.d.ts +3 -0
  15. package/dist/daemon.js +134 -0
  16. package/dist/index.d.ts +7 -0
  17. package/dist/index.js +17 -1
  18. package/dist/providers/index.d.ts +5 -1
  19. package/dist/providers/index.js +16 -9
  20. package/dist/schema/oad.d.ts +179 -4
  21. package/dist/schema/oad.js +12 -1
  22. package/dist/skills/auto-learn.d.ts +28 -0
  23. package/dist/skills/auto-learn.js +257 -0
  24. package/dist/tools/builtin/datetime.d.ts +3 -0
  25. package/dist/tools/builtin/datetime.js +44 -0
  26. package/dist/tools/builtin/file.d.ts +3 -0
  27. package/dist/tools/builtin/file.js +151 -0
  28. package/dist/tools/builtin/index.d.ts +15 -0
  29. package/dist/tools/builtin/index.js +30 -0
  30. package/dist/tools/builtin/shell.d.ts +3 -0
  31. package/dist/tools/builtin/shell.js +43 -0
  32. package/dist/tools/builtin/web.d.ts +3 -0
  33. package/dist/tools/builtin/web.js +37 -0
  34. package/dist/tools/mcp-client.d.ts +24 -0
  35. package/dist/tools/mcp-client.js +119 -0
  36. package/package.json +1 -1
  37. package/src/channels/telegram.ts +212 -90
  38. package/src/cli.ts +418 -8
  39. package/src/core/agent.ts +295 -152
  40. package/src/core/runtime.ts +47 -0
  41. package/src/core/scheduler.ts +187 -0
  42. package/src/core/subagent.ts +98 -0
  43. package/src/daemon.ts +96 -0
  44. package/src/index.ts +11 -0
  45. package/src/providers/index.ts +354 -339
  46. package/src/schema/oad.ts +167 -154
  47. package/src/skills/auto-learn.ts +262 -0
  48. package/src/tools/builtin/datetime.ts +41 -0
  49. package/src/tools/builtin/file.ts +107 -0
  50. package/src/tools/builtin/index.ts +28 -0
  51. package/src/tools/builtin/shell.ts +43 -0
  52. package/src/tools/builtin/web.ts +35 -0
  53. package/src/tools/mcp-client.ts +131 -0
  54. package/tests/auto-learn.test.ts +105 -0
  55. package/tests/builtin-tools.test.ts +83 -0
  56. package/tests/cli.test.ts +46 -0
  57. package/tests/subagent.test.ts +130 -0
  58. 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('1.4.0');
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
- console.log(`\n${icon.gear} Loaded agent: ${color.bold(config?.metadata?.name ?? 'unknown')}`);
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
- console.log(`\n${icon.info} No oad.yaml found, using defaults.`);
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
- console.log(`${color.dim('Type your message. Press Ctrl+C to exit.')}\n`);
395
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
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
@@ -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
  }