@yeaft/webchat-agent 0.1.408 → 0.1.410

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.408",
3
+ "version": "0.1.410",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/unify/cli.js CHANGED
@@ -7,6 +7,13 @@
7
7
  * --trace stats|recent|search <keyword> — Query debug.db
8
8
  * -i / --interactive — REPL mode with / commands
9
9
  * <prompt> — One-shot query (Phase 1: engine.query)
10
+ *
11
+ * Phase 2 additions:
12
+ * /memory — Show memory status or manage entries
13
+ * /history [n] — Show last N messages from conversation
14
+ * /search <keyword> — Search conversation history
15
+ * /compact — Trigger manual consolidation
16
+ * Conversation persistence across REPL sessions
10
17
  */
11
18
 
12
19
  import { createInterface } from 'readline';
@@ -18,6 +25,10 @@ import { createLLMAdapter } from './llm/adapter.js';
18
25
  import { Engine } from './engine.js';
19
26
  import { listModels, resolveModel } from './models.js';
20
27
  import { buildSystemPrompt } from './prompts.js';
28
+ import { ConversationStore } from './conversation/persist.js';
29
+ import { searchMessages } from './conversation/search.js';
30
+ import { MemoryStore } from './memory/store.js';
31
+ import { shouldConsolidate, consolidate } from './memory/consolidate.js';
21
32
 
22
33
  // ─── Argument parsing ──────────────────────────────────────────
23
34
 
@@ -191,21 +202,37 @@ async function runREPL(config, args) {
191
202
  dbPath: join(config.dir, 'debug.db'),
192
203
  });
193
204
 
205
+ // Initialize conversation and memory stores
206
+ const conversationStore = new ConversationStore(config.dir);
207
+ const memoryStore = new MemoryStore(config.dir);
208
+
194
209
  let adapter;
195
210
  let engine;
196
211
  let currentMode = args.mode;
197
- let conversationMessages = []; // persistent conversation for REPL
212
+
213
+ // Load persisted conversation as initial messages
214
+ let conversationMessages = conversationStore.loadRecent(50).map(m => ({
215
+ role: m.role,
216
+ content: m.content,
217
+ ...(m.toolCallId && { toolCallId: m.toolCallId }),
218
+ ...(m.toolCalls && { toolCalls: m.toolCalls }),
219
+ }));
198
220
 
199
221
  // Lazy adapter creation (don't fail on start if no API key for --trace-only usage)
200
222
  async function ensureEngine() {
201
223
  if (!engine) {
202
224
  adapter = await createLLMAdapter(config);
203
- engine = new Engine({ adapter, trace, config });
225
+ engine = new Engine({ adapter, trace, config, conversationStore, memoryStore });
204
226
  }
205
227
  return engine;
206
228
  }
207
229
 
230
+ const hotCount = conversationStore.countHot();
231
+ const coldCount = conversationStore.countCold();
232
+ const memStats = memoryStore.stats();
233
+
208
234
  console.log(`Yeaft Unify REPL (model: ${config.model}, mode: ${currentMode})`);
235
+ console.log(`Conversation: ${hotCount} hot, ${coldCount} cold | Memory: ${memStats.entryCount} entries`);
209
236
  console.log('Type /help for commands, /quit to exit.');
210
237
  console.log();
211
238
 
@@ -233,7 +260,10 @@ async function runREPL(config, args) {
233
260
  console.log(' /mode <chat|work|dream> — Switch mode');
234
261
  console.log(' /debug — Toggle debug mode');
235
262
  console.log(' /trace <stats|recent> — Query debug trace');
236
- console.log(' /memory Show memory status');
263
+ console.log(' /memory [add|clear|stats] Memory management');
264
+ console.log(' /history [n] — Show last N messages');
265
+ console.log(' /search <keyword> — Search conversation history');
266
+ console.log(' /compact — Trigger consolidation');
237
267
  console.log(' /context — Show context info');
238
268
  console.log(' /dry-run — Toggle dry-run mode');
239
269
  console.log(' /stats — Show session stats');
@@ -269,9 +299,126 @@ async function runREPL(config, args) {
269
299
  break;
270
300
  }
271
301
 
272
- case 'memory':
273
- console.log('Memory status: (not yet implemented — Phase 2)');
302
+ case 'memory': {
303
+ const subcmd = cmdArgs[0];
304
+ if (subcmd === 'add') {
305
+ // /memory add <section> <entry text>
306
+ const section = cmdArgs[1];
307
+ const entryText = cmdArgs.slice(2).join(' ');
308
+ if (!section || !entryText) {
309
+ console.log('Usage: /memory add <section> <entry text>');
310
+ } else {
311
+ memoryStore.addToSection(section, `- ${entryText}`);
312
+ console.log(`Added to MEMORY.md [${section}]: ${entryText}`);
313
+ }
314
+ } else if (subcmd === 'clear') {
315
+ memoryStore.clear();
316
+ console.log('All memory cleared.');
317
+ } else if (subcmd === 'stats') {
318
+ const s = memoryStore.stats();
319
+ console.log('Memory stats:');
320
+ console.log(` Entries: ${s.entryCount}`);
321
+ console.log(` Scopes: ${s.scopes.join(', ') || '(none)'}`);
322
+ console.log(` Kinds: ${Object.entries(s.kinds).map(([k, v]) => `${k}=${v}`).join(', ') || '(none)'}`);
323
+ } else if (subcmd === 'search') {
324
+ const keyword = cmdArgs.slice(1).join(' ');
325
+ if (!keyword) {
326
+ console.log('Usage: /memory search <keyword>');
327
+ } else {
328
+ const results = memoryStore.search(keyword);
329
+ if (results.length === 0) {
330
+ console.log(`No memory entries matching "${keyword}".`);
331
+ } else {
332
+ console.log(`Found ${results.length} entries:`);
333
+ for (const e of results) {
334
+ console.log(` [${e.kind}] ${e.name} (scope: ${e.scope})`);
335
+ console.log(` ${(e.content || '').slice(0, 100)}`);
336
+ }
337
+ }
338
+ }
339
+ } else {
340
+ // Default: show MEMORY.md content
341
+ const profile = memoryStore.readProfile();
342
+ const s = memoryStore.stats();
343
+ if (profile) {
344
+ console.log('--- MEMORY.md ---');
345
+ console.log(profile.slice(0, 1000));
346
+ if (profile.length > 1000) console.log('... (truncated)');
347
+ } else {
348
+ console.log('MEMORY.md is empty.');
349
+ }
350
+ console.log(`\nEntries: ${s.entryCount} | Scopes: ${s.scopes.length}`);
351
+ }
352
+ break;
353
+ }
354
+
355
+ case 'history': {
356
+ const limit = parseInt(cmdArgs[0], 10) || 10;
357
+ const messages = conversationStore.loadRecent(limit);
358
+ if (messages.length === 0) {
359
+ console.log('No messages in history.');
360
+ } else {
361
+ console.log(`Last ${messages.length} messages:`);
362
+ for (const m of messages) {
363
+ const time = m.time ? new Date(m.time).toLocaleString() : '?';
364
+ const preview = (m.content || '').slice(0, 100).replace(/\n/g, ' ');
365
+ console.log(` [${time}] ${m.role}: ${preview}`);
366
+ }
367
+ }
368
+ break;
369
+ }
370
+
371
+ case 'search': {
372
+ const keyword = cmdArgs.join(' ');
373
+ if (!keyword) {
374
+ console.log('Usage: /search <keyword>');
375
+ } else {
376
+ const results = searchMessages(config.dir, keyword, 20);
377
+ if (results.length === 0) {
378
+ console.log(`No messages matching "${keyword}".`);
379
+ } else {
380
+ console.log(`Found ${results.length} messages:`);
381
+ for (const m of results) {
382
+ const time = m.time ? new Date(m.time).toLocaleString() : '?';
383
+ const preview = (m.content || '').slice(0, 100).replace(/\n/g, ' ');
384
+ console.log(` [${time}] ${m.role}: ${preview}`);
385
+ }
386
+ }
387
+ }
274
388
  break;
389
+ }
390
+
391
+ case 'compact': {
392
+ try {
393
+ const eng = await ensureEngine();
394
+ const budget = config.messageTokenBudget || 8192;
395
+ const hotTokens = conversationStore.hotTokens();
396
+ console.log(`Hot tokens: ${hotTokens} / budget: ${budget}`);
397
+
398
+ if (hotTokens <= 0) {
399
+ console.log('No messages to compact.');
400
+ break;
401
+ }
402
+
403
+ console.log('Running consolidation...');
404
+ const result = await consolidate({
405
+ conversationStore,
406
+ memoryStore,
407
+ adapter: adapter || await createLLMAdapter(config),
408
+ config,
409
+ budget: Math.min(budget, hotTokens), // force trigger
410
+ });
411
+ console.log(`Consolidation complete:`);
412
+ console.log(` Archived: ${result.archivedCount} messages`);
413
+ console.log(` Extracted: ${result.extractedEntries.length} memory entries`);
414
+ if (result.compactSummary) {
415
+ console.log(` Summary: ${result.compactSummary.slice(0, 200)}...`);
416
+ }
417
+ } catch (e) {
418
+ console.error(`Consolidation error: ${e.message}`);
419
+ }
420
+ break;
421
+ }
275
422
 
276
423
  case 'context':
277
424
  console.log(`Context info:`);
@@ -280,6 +427,10 @@ async function runREPL(config, args) {
280
427
  console.log(` Language: ${config.language}`);
281
428
  console.log(` Max context: ${config.maxContextTokens} tokens`);
282
429
  console.log(` System prompt: ${buildSystemPrompt({ language: config.language, mode: currentMode }).length} chars`);
430
+ console.log(` Hot messages: ${conversationStore.countHot()}`);
431
+ console.log(` Hot tokens: ${conversationStore.hotTokens()}`);
432
+ console.log(` Cold messages: ${conversationStore.countCold()}`);
433
+ console.log(` Memory entries: ${memoryStore.stats().entryCount}`);
283
434
  break;
284
435
 
285
436
  case 'dry-run':
@@ -293,6 +444,9 @@ async function runREPL(config, args) {
293
444
  console.log(` Debug: ${config.debug}`);
294
445
  console.log(` Turns: ${s.turnCount}`);
295
446
  console.log(` Tools: ${s.toolCount}`);
447
+ console.log(` Hot messages: ${conversationStore.countHot()}`);
448
+ console.log(` Cold messages: ${conversationStore.countCold()}`);
449
+ console.log(` Memory entries: ${memoryStore.stats().entryCount}`);
296
450
  break;
297
451
  }
298
452
 
@@ -340,8 +494,9 @@ async function runREPL(config, args) {
340
494
  break;
341
495
 
342
496
  case 'clear':
497
+ conversationStore.clear();
343
498
  conversationMessages = [];
344
- console.log('Conversation history cleared.');
499
+ console.log('Conversation history cleared (including persisted messages).');
345
500
  break;
346
501
 
347
502
  case 'quit':
@@ -383,6 +538,19 @@ async function runREPL(config, args) {
383
538
  process.stderr.write(`[tool] ${event.name} → ${status}\n`);
384
539
  }
385
540
  break;
541
+ case 'recall':
542
+ if (config.debug) {
543
+ process.stderr.write(`[recall] ${event.entryCount} entries${event.cached ? ' (cached)' : ''}\n`);
544
+ }
545
+ break;
546
+ case 'consolidate':
547
+ if (config.debug) {
548
+ process.stderr.write(`[consolidate] archived=${event.archivedCount}, extracted=${event.extractedCount}\n`);
549
+ }
550
+ break;
551
+ case 'fallback':
552
+ process.stderr.write(`\n[fallback] ${event.from} → ${event.to}: ${event.reason}\n`);
553
+ break;
386
554
  case 'error':
387
555
  process.stderr.write(`\nError: ${event.error.message}\n`);
388
556
  break;
@@ -395,7 +563,8 @@ async function runREPL(config, args) {
395
563
  }
396
564
  console.log(); // newline after response
397
565
 
398
- // Save both user and assistant messages for multi-turn context
566
+ // Update in-memory conversation for multi-turn context
567
+ // (engine.js already persists to disk via conversationStore)
399
568
  conversationMessages.push({ role: 'user', content: input });
400
569
  if (responseText) {
401
570
  conversationMessages.push({ role: 'assistant', content: responseText });
@@ -421,6 +590,10 @@ async function runOnce(config, args) {
421
590
  dbPath: join(config.dir, 'debug.db'),
422
591
  });
423
592
 
593
+ // Initialize stores for persistence
594
+ const conversationStore = new ConversationStore(config.dir);
595
+ const memoryStore = new MemoryStore(config.dir);
596
+
424
597
  try {
425
598
  if (args.dryRun) {
426
599
  handleDryRun(args, config);
@@ -428,9 +601,21 @@ async function runOnce(config, args) {
428
601
  }
429
602
 
430
603
  const adapter = await createLLMAdapter(config);
431
- const engine = new Engine({ adapter, trace, config });
432
-
433
- for await (const event of engine.query({ prompt: args.prompt, mode: args.mode })) {
604
+ const engine = new Engine({ adapter, trace, config, conversationStore, memoryStore });
605
+
606
+ // Load recent conversation as context
607
+ const priorMessages = conversationStore.loadRecent(20).map(m => ({
608
+ role: m.role,
609
+ content: m.content,
610
+ ...(m.toolCallId && { toolCallId: m.toolCallId }),
611
+ ...(m.toolCalls && { toolCalls: m.toolCalls }),
612
+ }));
613
+
614
+ for await (const event of engine.query({
615
+ prompt: args.prompt,
616
+ mode: args.mode,
617
+ messages: priorMessages,
618
+ })) {
434
619
  switch (event.type) {
435
620
  case 'text_delta':
436
621
  process.stdout.write(event.text);
@@ -446,6 +631,19 @@ async function runOnce(config, args) {
446
631
  process.stderr.write(`[tool] ${event.name} → ${status}\n`);
447
632
  }
448
633
  break;
634
+ case 'recall':
635
+ if (args.verbose || args.debug) {
636
+ process.stderr.write(`[recall] ${event.entryCount} entries${event.cached ? ' (cached)' : ''}\n`);
637
+ }
638
+ break;
639
+ case 'consolidate':
640
+ if (args.verbose || args.debug) {
641
+ process.stderr.write(`[consolidate] archived=${event.archivedCount}, extracted=${event.extractedCount}\n`);
642
+ }
643
+ break;
644
+ case 'fallback':
645
+ process.stderr.write(`\n[fallback] ${event.from} → ${event.to}: ${event.reason}\n`);
646
+ break;
449
647
  case 'error':
450
648
  process.stderr.write(`\nError: ${event.error.message}\n`);
451
649
  break;
@@ -513,12 +711,12 @@ async function main() {
513
711
  console.log('Yeaft Unify CLI');
514
712
  console.log();
515
713
  console.log('Usage:');
516
- console.log(' node cli.js "your prompt" — One-shot query');
517
- console.log(' node cli.js -i — Interactive REPL');
518
- console.log(' node cli.js --dry-run "prompt" — Show what would be sent');
519
- console.log(' node cli.js --trace stats — Debug trace statistics');
520
- console.log(' node cli.js --trace recent — Recent turns');
521
- console.log(' node cli.js --trace search "keyword" — Search traces');
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');
718
+ console.log(' node cli.js --trace recent — Recent turns');
719
+ console.log(' node cli.js --trace search "keyword" — Search traces');
522
720
  console.log();
523
721
  console.log('Options:');
524
722
  console.log(' -m, --mode <mode> Mode: chat, work, dream (default: chat)');
package/unify/config.js CHANGED
@@ -26,6 +26,8 @@ const DEFAULTS = {
26
26
  dir: DEFAULT_YEAFT_DIR,
27
27
  maxContextTokens: 200000,
28
28
  maxOutputTokens: 16384,
29
+ messageTokenBudget: 8192, // Phase 2: context * 4%, triggers consolidation
30
+ maxContinueTurns: 3, // Phase 2: auto-continue on max_tokens
29
31
  };
30
32
 
31
33
  /**
@@ -217,6 +219,17 @@ export function loadConfig(overrides = {}) {
217
219
  overrides.maxOutputTokens ??
218
220
  fileConfig.maxOutputTokens ??
219
221
  DEFAULTS.maxOutputTokens,
222
+
223
+ messageTokenBudget:
224
+ overrides.messageTokenBudget ??
225
+ (env.YEAFT_MESSAGE_TOKEN_BUDGET ? parseInt(env.YEAFT_MESSAGE_TOKEN_BUDGET, 10) : null) ??
226
+ fileConfig.messageTokenBudget ??
227
+ DEFAULTS.messageTokenBudget,
228
+
229
+ maxContinueTurns:
230
+ overrides.maxContinueTurns ??
231
+ fileConfig.maxContinueTurns ??
232
+ DEFAULTS.maxContinueTurns,
220
233
  };
221
234
 
222
235
  // Auto-detect adapter using model registry + credential fallback