metame-cli 1.4.20 → 1.4.21

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/README.md CHANGED
@@ -363,6 +363,7 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
363
363
 
364
364
  | Command | Action |
365
365
  |---------|--------|
366
+ | `/continue` | Sync to computer's current work (session + directory) |
366
367
  | `/last` | Resume most recent session |
367
368
  | `/new` | Start new session (project picker) |
368
369
  | `/resume` | Pick from session list |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metame-cli",
3
- "version": "1.4.20",
3
+ "version": "1.4.21",
4
4
  "description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1000,10 +1000,6 @@ Reply with ONLY the name, nothing else. Examples: 插件开发, API重构, Bug
1000
1000
  try { await bot.sendMessage(chatId, `Error: ${(retry.error || '').slice(0, 200)}`); } catch { /* */ }
1001
1001
  return { ok: false, error: retry.error || errMsg };
1002
1002
  }
1003
- } else if (errMsg === 'Stopped by user' && messageQueue.has(chatId)) {
1004
- // Interrupted by message queue — suppress error, queue timer will handle it
1005
- log('INFO', `Task interrupted by new message for ${chatId}`);
1006
- return { ok: false, error: errMsg, interrupted: true };
1007
1003
  } else {
1008
1004
  // Auto-fallback: if custom provider/model fails, revert to anthropic + opus
1009
1005
  const activeProv = providerMod ? providerMod.getActiveName() : 'anthropic';
@@ -539,6 +539,7 @@ function createCommandRouter(deps) {
539
539
  '📱 手机端 Claude Code',
540
540
  '',
541
541
  '⚡ 快速同步电脑工作:',
542
+ '/continue — 接续电脑正在做的工作',
542
543
  '/last — 继续电脑上最近的对话',
543
544
  '/cd last — 切到电脑最近的项目目录',
544
545
  '',
@@ -575,43 +576,21 @@ function createCommandRouter(deps) {
575
576
  }
576
577
 
577
578
  // --- Natural language → Claude Code session ---
578
- // If a task is running: interrupt + collect + merge
579
+ // If a task is running: queue message, DON'T kill — will be sent as follow-up after completion
579
580
  if (activeProcesses.has(chatId)) {
580
581
  const isFirst = !messageQueue.has(chatId);
581
582
  if (isFirst) {
582
- messageQueue.set(chatId, { messages: [], timer: null });
583
+ messageQueue.set(chatId, { messages: [] });
583
584
  }
584
585
  const q = messageQueue.get(chatId);
586
+ if (q.messages.length >= 10) {
587
+ await bot.sendMessage(chatId, '⚠️ 排队已满(10条),请等当前任务完成');
588
+ return;
589
+ }
585
590
  q.messages.push(text);
586
- // Only notify once (first message), subsequent ones silently queue
587
591
  if (isFirst) {
588
- await bot.sendMessage(chatId, '📝 收到,稍后一起处理');
589
- }
590
- // Interrupt the running Claude process
591
- const proc = activeProcesses.get(chatId);
592
- if (proc && proc.child && !proc.aborted) {
593
- proc.aborted = true;
594
- try { process.kill(-proc.child.pid, 'SIGINT'); } catch { proc.child.kill('SIGINT'); }
592
+ await bot.sendMessage(chatId, '📝 收到,完成后继续处理');
595
593
  }
596
- // Debounce: wait 5s for more messages before processing
597
- if (q.timer) clearTimeout(q.timer);
598
- q.timer = setTimeout(async () => {
599
- // Wait for active process to fully exit (up to 10s)
600
- for (let i = 0; i < 20 && activeProcesses.has(chatId); i++) {
601
- await sleep(500);
602
- }
603
- const msgs = q.messages.splice(0);
604
- messageQueue.delete(chatId);
605
- if (msgs.length === 0) return;
606
- const combined = msgs.join('\n');
607
- log('INFO', `Processing ${msgs.length} queued message(s) for ${chatId}`);
608
- resetCooldown(chatId); // queued msgs already waited, skip cooldown
609
- try {
610
- await handleCommand(bot, chatId, combined, config, executeTaskByName);
611
- } catch (e) {
612
- log('ERROR', `Queue dispatch failed: ${e.message}`);
613
- }
614
- }, 5000);
615
594
  return;
616
595
  }
617
596
  // Strict mode: chats with a fixed agent in chat_agent_map must not cross-dispatch
@@ -653,8 +632,8 @@ function createCommandRouter(deps) {
653
632
  }
654
633
  const claudeResult = await askClaude(bot, chatId, text, config, readOnly);
655
634
  const claudeFailed = !!(claudeResult && claudeResult.ok === false);
656
- const claudeInterrupted = !!(claudeResult && claudeResult.interrupted);
657
- if (claudeFailed && !claudeInterrupted && !macLocalFirst && macFallbackEnabled && allowLocalMacControl) {
635
+ const claudeAborted = !!(claudeResult && claudeResult.error === 'Stopped by user');
636
+ if (claudeFailed && !claudeAborted && !macLocalFirst && macFallbackEnabled && allowLocalMacControl) {
658
637
  const fallbackHandled = await tryHandleMacNaturalLanguageIntent(bot, chatId, text, config, {
659
638
  source: 'claude-fallback',
660
639
  safeOnly: true,
@@ -664,6 +643,20 @@ function createCommandRouter(deps) {
664
643
  log('WARN', `Claude-first mac fallback handled for ${String(chatId).slice(-8)} (mode=${macControlMode})`);
665
644
  }
666
645
  }
646
+
647
+ // Process queued messages as follow-up in the same session (no kill, no context loss)
648
+ // Use while-loop instead of recursion to avoid unbounded stack growth
649
+ while (messageQueue.has(chatId)) {
650
+ const q = messageQueue.get(chatId);
651
+ const msgs = q.messages.splice(0);
652
+ messageQueue.delete(chatId);
653
+ if (msgs.length === 0) break;
654
+ const combined = msgs.join('\n');
655
+ log('INFO', `Follow-up: processing ${msgs.length} queued message(s) for ${chatId}`);
656
+ resetCooldown(chatId);
657
+ const followUp = await askClaude(bot, chatId, combined, config, readOnly);
658
+ if (followUp && followUp.error === 'Stopped by user') break;
659
+ }
667
660
  }
668
661
 
669
662
  return { handleCommand };
@@ -308,8 +308,14 @@ function createSessionCommandHandler(deps) {
308
308
  return true;
309
309
  }
310
310
 
311
- if (text === '/cd' || text.startsWith('/cd ')) {
312
- let newCwd = expandPath(text.slice(3).trim());
311
+ // /continue alias for /cd last (sync to computer's latest session)
312
+ if (text === '/continue') {
313
+ // Reuse /cd last logic below
314
+ // fall through with newCwd = 'last'
315
+ }
316
+
317
+ if (text === '/continue' || text === '/cd' || text.startsWith('/cd ')) {
318
+ let newCwd = text === '/continue' ? 'last' : expandPath(text.slice(3).trim());
313
319
  if (!newCwd) {
314
320
  await sendDirPicker(bot, chatId, 'cd', 'Switch workdir:');
315
321
  return true;
@@ -333,7 +339,6 @@ function createSessionCommandHandler(deps) {
333
339
  const name = target.customTitle || target.summary || '';
334
340
  const label = name ? name.slice(0, 40) : target.sessionId.slice(0, 8);
335
341
  await bot.sendMessage(chatId, `🔄 Synced to: ${label}\n📁 ${path.basename(target.projectPath)}`);
336
- await sendDirListing(bot, chatId, target.projectPath, null);
337
342
  return true;
338
343
  }
339
344
  await bot.sendMessage(chatId, 'No recent session found.');