@yeaft/webchat-agent 0.1.411 → 0.1.413

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 CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.411",
3
+ "version": "0.1.413",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "yeaft-agent": "./cli.js"
8
+ "yeaft-agent": "./cli.js",
9
+ "yeaft": "./unify/cli.js"
9
10
  },
10
11
  "scripts": {
11
12
  "start": "node index.js",
package/service/index.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  saveServiceConfig, loadServiceConfig,
10
10
  parseServiceArgs, validateConfig
11
11
  } from './config.js';
12
+ import { initYeaftDir } from '../unify/init.js';
12
13
  import { getSystemdServicePath, linuxInstall, linuxUninstall, linuxStart, linuxStop, linuxRestart, linuxStatus, linuxLogs } from './linux.js';
13
14
  import { getLaunchdPlistPath, macInstall, macUninstall, macStart, macStop, macRestart, macStatus, macLogs } from './macos.js';
14
15
  import { winInstall, winUninstall, winStart, winStop, winRestart, winStatus, winLogs } from './windows.js';
@@ -42,6 +43,15 @@ export function install(args) {
42
43
  validateConfig(config);
43
44
  saveServiceConfig(config);
44
45
 
46
+ // Initialize ~/.yeaft/ directory + default config.json
47
+ // so `yeaft` CLI is ready to use immediately after install
48
+ const { dir, created } = initYeaftDir();
49
+ if (created.length > 0) {
50
+ console.log(`Initialized ${dir}`);
51
+ console.log(` Edit ${dir}/config.json to configure LLM providers.`);
52
+ console.log('');
53
+ }
54
+
45
55
  console.log(`Installing ${SERVICE_NAME} service...`);
46
56
  console.log(` Server: ${config.serverUrl}`);
47
57
  console.log(` Name: ${config.agentName || '(auto)'}`);
package/unify/cli.js CHANGED
@@ -2,33 +2,36 @@
2
2
  /**
3
3
  * cli.js — Yeaft Unify CLI entry point
4
4
  *
5
+ * Uses loadSession() to wire all subsystems (Engine, ToolRegistry, SkillManager,
6
+ * MCPManager, Dream, StopHooks, Memory) into a single session.
7
+ *
5
8
  * Features:
6
9
  * --dry-run "prompt" — Assemble system prompt + messages, don't call LLM
7
10
  * --trace stats|recent|search <keyword> — Query debug.db
8
11
  * -i / --interactive — REPL mode with / commands
9
12
  * <prompt> — One-shot query (Phase 1: engine.query)
13
+ * --skip-mcp — Skip MCP server connections (faster startup)
14
+ * --skip-skills — Skip skill loading
10
15
  *
11
- * Phase 2 additions:
16
+ * REPL commands:
12
17
  * /memory — Show memory status or manage entries
13
18
  * /history [n] — Show last N messages from conversation
14
19
  * /search <keyword> — Search conversation history
15
20
  * /compact — Trigger manual consolidation
21
+ * /tools — List registered tools
22
+ * /skills — List loaded skills
16
23
  * Conversation persistence across REPL sessions
17
24
  */
18
25
 
19
26
  import { createInterface } from 'readline';
20
27
  import { join } from 'path';
21
- import { initYeaftDir } from './init.js';
22
28
  import { loadConfig } from './config.js';
23
- import { DebugTrace, NullTrace, createTrace } from './debug-trace.js';
24
- import { createLLMAdapter } from './llm/adapter.js';
25
- import { Engine } from './engine.js';
26
- import { listModels, resolveModel } from './models.js';
29
+ import { DebugTrace } from './debug-trace.js';
30
+ import { loadSession } from './session.js';
31
+ import { listModels, resolveModel, parseModelRef } from './models.js';
27
32
  import { buildSystemPrompt } from './prompts.js';
28
- import { ConversationStore } from './conversation/persist.js';
29
33
  import { searchMessages } from './conversation/search.js';
30
- import { MemoryStore } from './memory/store.js';
31
- import { shouldConsolidate, consolidate } from './memory/consolidate.js';
34
+ import { consolidate } from './memory/consolidate.js';
32
35
 
33
36
  // ─── Argument parsing ──────────────────────────────────────────
34
37
 
@@ -43,6 +46,8 @@ function parseArgs(argv) {
43
46
  trace: null, // 'stats' | 'recent' | 'search' | 'tools' | null
44
47
  traceArg: null, // search keyword or tool name
45
48
  dryRun: false,
49
+ skipMCP: false,
50
+ skipSkills: false,
46
51
  prompt: null,
47
52
  };
48
53
 
@@ -83,6 +88,12 @@ function parseArgs(argv) {
83
88
  case '--dry-run':
84
89
  args.dryRun = true;
85
90
  break;
91
+ case '--skip-mcp':
92
+ args.skipMCP = true;
93
+ break;
94
+ case '--skip-skills':
95
+ args.skipSkills = true;
96
+ break;
86
97
  default:
87
98
  if (!arg.startsWith('-') && !args.prompt) {
88
99
  args.prompt = arg;
@@ -197,17 +208,16 @@ function handleDryRun(args, config) {
197
208
  // ─── REPL ──────────────────────────────────────────────────────
198
209
 
199
210
  async function runREPL(config, args) {
200
- const trace = createTrace({
201
- enabled: config.debug,
202
- dbPath: join(config.dir, 'debug.db'),
211
+ // Use loadSession() to wire all subsystems
212
+ const session = await loadSession({
213
+ model: args.model || config.model,
214
+ language: args.language || config.language,
215
+ debug: args.debug || config.debug,
216
+ skipMCP: args.skipMCP,
217
+ skipSkills: args.skipSkills,
203
218
  });
204
219
 
205
- // Initialize conversation and memory stores
206
- const conversationStore = new ConversationStore(config.dir);
207
- const memoryStore = new MemoryStore(config.dir);
208
-
209
- let adapter;
210
- let engine;
220
+ const { engine, conversationStore, memoryStore, trace, skillManager, mcpManager, toolRegistry } = session;
211
221
  let currentMode = args.mode;
212
222
 
213
223
  // Load persisted conversation as initial messages
@@ -218,21 +228,19 @@ async function runREPL(config, args) {
218
228
  ...(m.toolCalls && { toolCalls: m.toolCalls }),
219
229
  }));
220
230
 
221
- // Lazy adapter creation (don't fail on start if no API key for --trace-only usage)
222
- async function ensureEngine() {
223
- if (!engine) {
224
- adapter = await createLLMAdapter(config);
225
- engine = new Engine({ adapter, trace, config, conversationStore, memoryStore });
226
- }
227
- return engine;
228
- }
229
-
230
231
  const hotCount = conversationStore.countHot();
231
232
  const coldCount = conversationStore.countCold();
232
233
  const memStats = memoryStore.stats();
233
234
 
234
- console.log(`Yeaft Unify REPL (model: ${config.model}, mode: ${currentMode})`);
235
+ console.log(`Yeaft Unify REPL (model: ${session.config.model}, mode: ${currentMode})`);
235
236
  console.log(`Conversation: ${hotCount} hot, ${coldCount} cold | Memory: ${memStats.entryCount} entries`);
237
+ console.log(`Tools: ${session.status.tools} | Skills: ${session.status.skills}`);
238
+ if (session.status.mcpServers.length > 0) {
239
+ console.log(`MCP: ${session.status.mcpServers.join(', ')}`);
240
+ }
241
+ if (session.status.mcpFailed.length > 0) {
242
+ console.log(`MCP failed: ${session.status.mcpFailed.map(f => f.name || f).join(', ')}`);
243
+ }
236
244
  console.log('Type /help for commands, /quit to exit.');
237
245
  console.log();
238
246
 
@@ -267,9 +275,12 @@ async function runREPL(config, args) {
267
275
  console.log(' /context — Show context info');
268
276
  console.log(' /dry-run — Toggle dry-run mode');
269
277
  console.log(' /stats — Show session stats');
270
- console.log(' /model <name> — Switch model');
278
+ console.log(' /model <name> — Switch model (supports provider/model)');
271
279
  console.log(' /models — List available models');
280
+ console.log(' /providers — List configured providers');
272
281
  console.log(' /language <en|zh> — Switch language');
282
+ console.log(' /tools — List registered tools');
283
+ console.log(' /skills — List loaded skills');
273
284
  console.log(' /clear — Clear conversation history');
274
285
  console.log(' /quit — Exit');
275
286
  break;
@@ -285,14 +296,14 @@ async function runREPL(config, args) {
285
296
  break;
286
297
 
287
298
  case 'debug':
288
- config.debug = !config.debug;
289
- console.log(`Debug mode: ${config.debug ? 'ON' : 'OFF'}`);
299
+ session.config.debug = !session.config.debug;
300
+ console.log(`Debug mode: ${session.config.debug ? 'ON' : 'OFF'}`);
290
301
  break;
291
302
 
292
303
  case 'trace': {
293
304
  const subcmd = cmdArgs[0] || 'stats';
294
305
  try {
295
- handleTraceQuery({ trace: subcmd, traceArg: cmdArgs[1] }, config);
306
+ handleTraceQuery({ trace: subcmd, traceArg: cmdArgs[1] }, session.config);
296
307
  } catch (e) {
297
308
  console.error(`Trace error: ${e.message}`);
298
309
  }
@@ -373,7 +384,7 @@ async function runREPL(config, args) {
373
384
  if (!keyword) {
374
385
  console.log('Usage: /search <keyword>');
375
386
  } else {
376
- const results = searchMessages(config.dir, keyword, 20);
387
+ const results = searchMessages(session.yeaftDir, keyword, 20);
377
388
  if (results.length === 0) {
378
389
  console.log(`No messages matching "${keyword}".`);
379
390
  } else {
@@ -390,8 +401,7 @@ async function runREPL(config, args) {
390
401
 
391
402
  case 'compact': {
392
403
  try {
393
- const eng = await ensureEngine();
394
- const budget = config.messageTokenBudget || 8192;
404
+ const budget = session.config.messageTokenBudget || 8192;
395
405
  const hotTokens = conversationStore.hotTokens();
396
406
  console.log(`Hot tokens: ${hotTokens} / budget: ${budget}`);
397
407
 
@@ -404,8 +414,8 @@ async function runREPL(config, args) {
404
414
  const result = await consolidate({
405
415
  conversationStore,
406
416
  memoryStore,
407
- adapter: adapter || await createLLMAdapter(config),
408
- config,
417
+ adapter: session.adapter,
418
+ config: session.config,
409
419
  budget: Math.min(budget, hotTokens), // force trigger
410
420
  });
411
421
  console.log(`Consolidation complete:`);
@@ -422,63 +432,115 @@ async function runREPL(config, args) {
422
432
 
423
433
  case 'context':
424
434
  console.log(`Context info:`);
425
- console.log(` Model: ${config.model}`);
435
+ console.log(` Model: ${session.config.model}`);
426
436
  console.log(` Mode: ${currentMode}`);
427
- console.log(` Language: ${config.language}`);
428
- console.log(` Max context: ${config.maxContextTokens} tokens`);
429
- console.log(` System prompt: ${buildSystemPrompt({ language: config.language, mode: currentMode }).length} chars`);
437
+ console.log(` Language: ${session.config.language}`);
438
+ console.log(` Max context: ${session.config.maxContextTokens} tokens`);
439
+ console.log(` System prompt: ${buildSystemPrompt({ language: session.config.language, mode: currentMode }).length} chars`);
430
440
  console.log(` Hot messages: ${conversationStore.countHot()}`);
431
441
  console.log(` Hot tokens: ${conversationStore.hotTokens()}`);
432
442
  console.log(` Cold messages: ${conversationStore.countCold()}`);
433
443
  console.log(` Memory entries: ${memoryStore.stats().entryCount}`);
444
+ console.log(` Tools: ${toolRegistry.size}`);
445
+ console.log(` Skills: ${skillManager.size}`);
434
446
  break;
435
447
 
436
448
  case 'dry-run':
437
- handleDryRun({ ...args, mode: currentMode, prompt: cmdArgs.join(' ') || null }, config);
449
+ handleDryRun({ ...args, mode: currentMode, prompt: cmdArgs.join(' ') || null }, session.config);
438
450
  break;
439
451
 
440
452
  case 'stats': {
441
453
  const s = trace.stats();
442
454
  console.log(`Session stats:`);
443
455
  console.log(` Mode: ${currentMode}`);
444
- console.log(` Debug: ${config.debug}`);
456
+ console.log(` Debug: ${session.config.debug}`);
445
457
  console.log(` Turns: ${s.turnCount}`);
446
458
  console.log(` Tools: ${s.toolCount}`);
447
459
  console.log(` Hot messages: ${conversationStore.countHot()}`);
448
460
  console.log(` Cold messages: ${conversationStore.countCold()}`);
449
461
  console.log(` Memory entries: ${memoryStore.stats().entryCount}`);
462
+ console.log(` Registered tools: ${toolRegistry.size}`);
463
+ console.log(` Loaded skills: ${skillManager.size}`);
450
464
  break;
451
465
  }
452
466
 
453
467
  case 'model':
454
468
  if (cmdArgs[0]) {
455
469
  try {
456
- config.model = cmdArgs[0];
457
- // Re-resolve adapter and baseUrl from model registry
458
- const newModelInfo = resolveModel(config.model);
470
+ const modelRef = cmdArgs[0];
471
+ const { providerName, modelId } = parseModelRef(modelRef);
472
+
473
+ if (providerName) {
474
+ // Full ref: provider/model — update primaryModel
475
+ session.config.model = modelId;
476
+ session.config.primaryModel = modelRef;
477
+ } else {
478
+ // Bare model ID
479
+ session.config.model = modelRef;
480
+ }
481
+
482
+ // Re-resolve model info from registry
483
+ const newModelInfo = resolveModel(session.config.model);
459
484
  if (newModelInfo) {
460
- config.adapter = newModelInfo.adapter === 'anthropic' ? 'anthropic' : 'openai';
461
- config.baseUrl = newModelInfo.baseUrl;
462
- config.maxContextTokens = newModelInfo.contextWindow;
463
- config.maxOutputTokens = newModelInfo.maxOutputTokens;
464
- config.modelInfo = newModelInfo;
485
+ session.config.maxContextTokens = newModelInfo.contextWindow;
486
+ session.config.maxOutputTokens = newModelInfo.maxOutputTokens;
487
+ session.config.modelInfo = newModelInfo;
465
488
  }
466
- engine = null; // Force re-creation with new model + adapter
467
- console.log(`Model switched to: ${config.model} (adapter: ${config.adapter})`);
489
+ const providerStr = providerName ? ` (provider: ${providerName})` : '';
490
+ console.log(`Model switched to: ${session.config.model}${providerStr}`);
491
+ console.log('Note: Model change takes effect on next query.');
468
492
  } catch (e) {
469
493
  console.error(`Error switching model: ${e.message}`);
470
494
  }
471
495
  } else {
472
- console.log(`Current model: ${config.model} (adapter: ${config.adapter})`);
496
+ const primaryRef = session.config.primaryModel || session.config.model;
497
+ console.log(`Current model: ${primaryRef}`);
498
+ if (session.config.fastModel && session.config.fastModel !== session.config.primaryModel) {
499
+ console.log(`Fast model: ${session.config.fastModel}`);
500
+ }
473
501
  }
474
502
  break;
475
503
 
476
504
  case 'models': {
477
505
  const models = listModels();
478
- console.log('Available models:');
479
- for (const m of models) {
480
- const current = m.name === config.model ? ' ← current' : '';
481
- console.log(` ${m.name} (${m.displayName}) — ${m.adapter}, ${(m.contextWindow / 1000).toFixed(0)}K ctx${current}`);
506
+ // If providers are configured, show provider-based model list
507
+ if (session.config.providers && session.config.providers.length > 0) {
508
+ console.log('Available models (from providers):');
509
+ for (const provider of session.config.providers) {
510
+ console.log(` [${provider.name}] ${provider.baseUrl}`);
511
+ if (Array.isArray(provider.models)) {
512
+ for (const m of provider.models) {
513
+ const current = (`${provider.name}/${m}` === session.config.primaryModel) ? ' ← primary' :
514
+ (`${provider.name}/${m}` === session.config.fastModel) ? ' ← fast' : '';
515
+ const info = resolveModel(m);
516
+ const displayName = info ? ` (${info.displayName})` : '';
517
+ console.log(` ${provider.name}/${m}${displayName}${current}`);
518
+ }
519
+ }
520
+ }
521
+ } else {
522
+ // Legacy: show registry models
523
+ console.log('Available models (from registry):');
524
+ for (const m of models) {
525
+ const current = m.name === session.config.model ? ' ← current' : '';
526
+ console.log(` ${m.name} (${m.displayName}) — ${m.adapter}, ${(m.contextWindow / 1000).toFixed(0)}K ctx${current}`);
527
+ }
528
+ }
529
+ break;
530
+ }
531
+
532
+ case 'providers': {
533
+ const providers = session.config.providers;
534
+ if (!providers || providers.length === 0) {
535
+ console.log('No providers configured. Using legacy adapter mode.');
536
+ console.log('Add providers to ~/.yeaft/config.json to enable provider routing.');
537
+ } else {
538
+ console.log(`Configured providers (${providers.length}):`);
539
+ for (const p of providers) {
540
+ const protocol = p.protocol || 'openai';
541
+ const modelCount = Array.isArray(p.models) ? p.models.length : 0;
542
+ console.log(` ${p.name} — ${p.baseUrl} (${protocol}, ${modelCount} models)`);
543
+ }
482
544
  }
483
545
  break;
484
546
  }
@@ -486,13 +548,40 @@ async function runREPL(config, args) {
486
548
  case 'language':
487
549
  case 'lang':
488
550
  if (cmdArgs[0]) {
489
- config.language = cmdArgs[0];
490
- console.log(`Language switched to: ${config.language}`);
551
+ session.config.language = cmdArgs[0];
552
+ console.log(`Language switched to: ${session.config.language}`);
491
553
  } else {
492
- console.log(`Current language: ${config.language}`);
554
+ console.log(`Current language: ${session.config.language}`);
493
555
  }
494
556
  break;
495
557
 
558
+ case 'tools': {
559
+ const names = toolRegistry.names || [];
560
+ if (names.length === 0) {
561
+ console.log('No tools registered.');
562
+ } else {
563
+ console.log(`Registered tools (${names.length}):`);
564
+ for (const name of names) {
565
+ console.log(` - ${name}`);
566
+ }
567
+ }
568
+ break;
569
+ }
570
+
571
+ case 'skills': {
572
+ const skillList = skillManager.list() || [];
573
+ if (skillList.length === 0) {
574
+ console.log('No skills loaded.');
575
+ } else {
576
+ console.log(`Loaded skills (${skillList.length}):`);
577
+ for (const skill of skillList) {
578
+ const desc = skill.description ? ` — ${skill.description}` : '';
579
+ console.log(` - ${skill.name}${desc}`);
580
+ }
581
+ }
582
+ break;
583
+ }
584
+
496
585
  case 'clear':
497
586
  conversationStore.clear();
498
587
  conversationMessages = [];
@@ -502,7 +591,7 @@ async function runREPL(config, args) {
502
591
  case 'quit':
503
592
  case 'exit':
504
593
  case 'q':
505
- rl.close(); // close handler does trace.close() + process.exit()
594
+ rl.close(); // close handler does shutdown + process.exit()
506
595
  return; // don't call rl.prompt() below
507
596
 
508
597
  default:
@@ -514,10 +603,9 @@ async function runREPL(config, args) {
514
603
 
515
604
  // Regular input → engine.query
516
605
  try {
517
- const eng = await ensureEngine();
518
606
  let responseText = '';
519
607
 
520
- for await (const event of eng.query({
608
+ for await (const event of engine.query({
521
609
  prompt: input,
522
610
  mode: currentMode,
523
611
  messages: conversationMessages,
@@ -528,24 +616,33 @@ async function runREPL(config, args) {
528
616
  process.stdout.write(event.text);
529
617
  break;
530
618
  case 'tool_start':
531
- if (config.debug) {
619
+ if (session.config.debug) {
532
620
  process.stderr.write(`\n[tool] ${event.name}(${JSON.stringify(event.input)})\n`);
621
+ } else {
622
+ process.stderr.write(`\n⚙ ${event.name}...\n`);
533
623
  }
534
624
  break;
535
625
  case 'tool_end':
536
- if (config.debug) {
626
+ if (session.config.debug) {
537
627
  const status = event.isError ? 'ERROR' : 'OK';
538
628
  process.stderr.write(`[tool] ${event.name} → ${status}\n`);
539
629
  }
540
630
  break;
541
631
  case 'recall':
542
- if (config.debug) {
632
+ if (session.config.debug) {
543
633
  process.stderr.write(`[recall] ${event.entryCount} entries${event.cached ? ' (cached)' : ''}\n`);
544
634
  }
545
635
  break;
546
636
  case 'consolidate':
547
- if (config.debug) {
637
+ if (session.config.debug) {
548
638
  process.stderr.write(`[consolidate] archived=${event.archivedCount}, extracted=${event.extractedCount}\n`);
639
+ } else {
640
+ process.stderr.write(`[compact] Memory consolidated\n`);
641
+ }
642
+ break;
643
+ case 'dream_triggered':
644
+ if (session.config.debug) {
645
+ process.stderr.write(`[dream] Dream cycle triggered (background)\n`);
549
646
  }
550
647
  break;
551
648
  case 'fallback':
@@ -555,7 +652,7 @@ async function runREPL(config, args) {
555
652
  process.stderr.write(`\nError: ${event.error.message}\n`);
556
653
  break;
557
654
  case 'turn_start':
558
- if (config.debug && event.turnNumber > 1) {
655
+ if (session.config.debug && event.turnNumber > 1) {
559
656
  process.stderr.write(`\n--- Turn ${event.turnNumber} ---\n`);
560
657
  }
561
658
  break;
@@ -564,7 +661,7 @@ async function runREPL(config, args) {
564
661
  console.log(); // newline after response
565
662
 
566
663
  // Update in-memory conversation for multi-turn context
567
- // (engine.js already persists to disk via conversationStore)
664
+ // (engine.js already persists to disk via StopHooks when yeaftDir is set)
568
665
  conversationMessages.push({ role: 'user', content: input });
569
666
  if (responseText) {
570
667
  conversationMessages.push({ role: 'assistant', content: responseText });
@@ -575,8 +672,8 @@ async function runREPL(config, args) {
575
672
  rl.prompt();
576
673
  });
577
674
 
578
- rl.on('close', () => {
579
- trace.close();
675
+ rl.on('close', async () => {
676
+ await session.shutdown();
580
677
  console.log('\nBye!');
581
678
  process.exit(0);
582
679
  });
@@ -585,24 +682,23 @@ async function runREPL(config, args) {
585
682
  // ─── One-shot handler ──────────────────────────────────────────
586
683
 
587
684
  async function runOnce(config, args) {
588
- const trace = createTrace({
589
- enabled: config.debug,
590
- dbPath: join(config.dir, 'debug.db'),
685
+ if (args.dryRun) {
686
+ handleDryRun(args, config);
687
+ return;
688
+ }
689
+
690
+ // Use loadSession() to wire all subsystems
691
+ const session = await loadSession({
692
+ model: args.model || config.model,
693
+ language: args.language || config.language,
694
+ debug: args.debug || config.debug,
695
+ skipMCP: args.skipMCP,
696
+ skipSkills: args.skipSkills,
591
697
  });
592
698
 
593
- // Initialize stores for persistence
594
- const conversationStore = new ConversationStore(config.dir);
595
- const memoryStore = new MemoryStore(config.dir);
699
+ const { engine, conversationStore } = session;
596
700
 
597
701
  try {
598
- if (args.dryRun) {
599
- handleDryRun(args, config);
600
- return;
601
- }
602
-
603
- const adapter = await createLLMAdapter(config);
604
- const engine = new Engine({ adapter, trace, config, conversationStore, memoryStore });
605
-
606
702
  // Load recent conversation as context
607
703
  const priorMessages = conversationStore.loadRecent(20).map(m => ({
608
704
  role: m.role,
@@ -621,12 +717,14 @@ async function runOnce(config, args) {
621
717
  process.stdout.write(event.text);
622
718
  break;
623
719
  case 'tool_start':
624
- if (args.verbose) {
720
+ if (args.verbose || args.debug) {
625
721
  process.stderr.write(`\n[tool] ${event.name}(${JSON.stringify(event.input)})\n`);
722
+ } else {
723
+ process.stderr.write(`\n⚙ ${event.name}...\n`);
626
724
  }
627
725
  break;
628
726
  case 'tool_end':
629
- if (args.verbose) {
727
+ if (args.verbose || args.debug) {
630
728
  const status = event.isError ? 'ERROR' : 'OK';
631
729
  process.stderr.write(`[tool] ${event.name} → ${status}\n`);
632
730
  }
@@ -641,6 +739,11 @@ async function runOnce(config, args) {
641
739
  process.stderr.write(`[consolidate] archived=${event.archivedCount}, extracted=${event.extractedCount}\n`);
642
740
  }
643
741
  break;
742
+ case 'dream_triggered':
743
+ if (args.verbose || args.debug) {
744
+ process.stderr.write(`[dream] Dream cycle triggered (background)\n`);
745
+ }
746
+ break;
644
747
  case 'fallback':
645
748
  process.stderr.write(`\n[fallback] ${event.from} → ${event.to}: ${event.reason}\n`);
646
749
  break;
@@ -657,7 +760,7 @@ async function runOnce(config, args) {
657
760
  // Final newline after streaming text
658
761
  console.log();
659
762
  } finally {
660
- trace.close();
763
+ await session.shutdown();
661
764
  }
662
765
  }
663
766
 
@@ -666,17 +769,14 @@ async function runOnce(config, args) {
666
769
  async function main() {
667
770
  const args = parseArgs(process.argv);
668
771
 
669
- // Load config with CLI overrides
772
+ // Load config with CLI overrides (for --trace queries and dry-run, no session needed)
670
773
  const config = loadConfig({
671
774
  model: args.model,
672
775
  language: args.language,
673
776
  debug: args.debug || undefined,
674
777
  });
675
778
 
676
- // Initialize directory structure
677
- initYeaftDir(config.dir);
678
-
679
- // Handle --trace queries (no LLM needed)
779
+ // Handle --trace queries (no LLM needed, no session needed)
680
780
  if (args.trace) {
681
781
  handleTraceQuery(args, config);
682
782
  return;
@@ -711,22 +811,24 @@ async function main() {
711
811
  console.log('Yeaft Unify CLI');
712
812
  console.log();
713
813
  console.log('Usage:');
714
- console.log(' node cli.js "your prompt" — One-shot query');
715
- console.log(' node cli.js -i — Interactive REPL');
716
- console.log(' node cli.js --dry-run "prompt" — Show what would be sent');
717
- console.log(' node cli.js --trace stats — Debug trace statistics');
814
+ console.log(' node cli.js "your prompt" — One-shot query');
815
+ console.log(' node cli.js -i — Interactive REPL');
816
+ console.log(' node cli.js --dry-run "prompt" — Show what would be sent');
817
+ console.log(' node cli.js --trace stats — Debug trace statistics');
718
818
  console.log(' node cli.js --trace recent — Recent turns');
719
819
  console.log(' node cli.js --trace search "keyword" — Search traces');
720
820
  console.log();
721
821
  console.log('Options:');
722
- console.log(' -m, --mode <mode> Mode: chat, work, dream (default: chat)');
723
- console.log(' -d, --debug Enable debug tracing');
724
- console.log(' -i, --interactive Start REPL');
725
- console.log(' -v, --verbose Verbose output');
726
- console.log(' --model <name> Override model');
727
- console.log(' --language <code> Language: en, zh (default: en)');
728
- console.log(' --trace <cmd> Query debug trace');
729
- console.log(' --dry-run Show prompt without calling LLM');
822
+ console.log(' -m, --mode <mode> Mode: chat, work, dream (default: chat)');
823
+ console.log(' -d, --debug Enable debug tracing');
824
+ console.log(' -i, --interactive Start REPL');
825
+ console.log(' -v, --verbose Verbose output');
826
+ console.log(' --model <name> Override model');
827
+ console.log(' --language <code> Language: en, zh (default: en)');
828
+ console.log(' --trace <cmd> Query debug trace');
829
+ console.log(' --dry-run Show prompt without calling LLM');
830
+ console.log(' --skip-mcp Skip MCP server connections');
831
+ console.log(' --skip-skills Skip skill loading');
730
832
  }
731
833
 
732
834
  main().catch(err => {