@miphamai/cli 0.73.0 → 0.73.1

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": "@miphamai/cli",
3
- "version": "0.73.0",
3
+ "version": "0.73.1",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -780,7 +780,7 @@
780
780
  "ui": {
781
781
  "banner": {
782
782
  "title": "Mipham Code",
783
- "subtitle": "AI-Powered Programming Assistant",
783
+ "subtitle": "AI-Powered Super Agent",
784
784
  "tagline": "Multi-model / Multi-provider / Skills & Tools / Open-core",
785
785
  "start_message": "Type a message to start. /help for commands",
786
786
  "controls_hint": "Ctrl+P pick model · Esc to exit",
@@ -780,7 +780,7 @@
780
780
  "ui": {
781
781
  "banner": {
782
782
  "title": "Mipham Code",
783
- "subtitle": "AI 驱动的编程助手",
783
+ "subtitle": "AI 驱动的超级智能体",
784
784
  "tagline": "多模型 / 多提供商 / 技能与工具 / 开源核心",
785
785
  "start_message": "输入消息开始。 /help 查看命令",
786
786
  "controls_hint": "Ctrl+P 选择模型 · Esc 退出",
package/src/index.tsx CHANGED
@@ -630,6 +630,28 @@ export async function runApp(options: RunOptions): Promise<void> {
630
630
 
631
631
  // Auto-save session on exit
632
632
  let saved = false
633
+
634
+ // Persist the current session under its real name. Shared by saveAndExit and
635
+ // the process 'exit' safety net — the two paths must stay identical, or the
636
+ // session gets saved under a different (timestamped) name and /resume breaks.
637
+ const persistSession = () => {
638
+ if (context.getMessageCount() === 0) return
639
+ const log = context.getLog()
640
+ if (log) {
641
+ SessionStore.saveLog(sessionName, log, {
642
+ provider: defaultProvider,
643
+ model: defaultModel,
644
+ cwd: process.cwd(),
645
+ })
646
+ } else {
647
+ SessionStore.save(sessionName, context.getMessages(), {
648
+ provider: defaultProvider,
649
+ model: defaultModel,
650
+ cwd: process.cwd(),
651
+ })
652
+ }
653
+ }
654
+
633
655
  const saveAndExit = () => {
634
656
  saved = true
635
657
  clearInterval(heartbeatInterval)
@@ -637,22 +659,7 @@ export async function runApp(options: RunOptions): Promise<void> {
637
659
  // P2-1: Trigger SessionEnd hooks before cleanup (best-effort)
638
660
  hookEngine.executeSessionEnd(sessionName).catch(() => {})
639
661
  artifactServer.stop()
640
- if (context.getMessageCount() > 0) {
641
- const log = context.getLog()
642
- if (log) {
643
- SessionStore.saveLog(sessionName, log, {
644
- provider: defaultProvider,
645
- model: defaultModel,
646
- cwd: process.cwd(),
647
- })
648
- } else {
649
- SessionStore.save(sessionName, context.getMessages(), {
650
- provider: defaultProvider,
651
- model: defaultModel,
652
- cwd: process.cwd(),
653
- })
654
- }
655
- }
662
+ persistSession()
656
663
  // Finalize the session — write session summary + flush CRSI effectiveness
657
664
  // (evaluate rules and apply auto-degrade/disable). Best-effort: the
658
665
  // self-improvement closeout must never block session exit.
@@ -667,27 +674,25 @@ export async function runApp(options: RunOptions): Promise<void> {
667
674
 
668
675
  process.on('SIGINT', saveAndExit)
669
676
  process.on('SIGTERM', saveAndExit)
677
+ // Terminal close / SSH disconnect sends SIGHUP (not SIGINT/SIGTERM); without
678
+ // a handler the session log is silently dropped on those paths.
679
+ process.on('SIGHUP', saveAndExit)
670
680
 
671
- // Safety net: auto-save on exit for paths that bypass saveAndExit
681
+ // Safety net: persist on exit for paths that bypass saveAndExit (e.g. /exit
682
+ // and /quit call process.exit(0) directly). saveAndExit already persists;
683
+ // guard on !saved to avoid double-writing.
672
684
  process.on('exit', () => {
673
685
  clearInterval(heartbeatInterval)
674
686
  unregisterSession(sessionName)
675
687
  // Finalize the session on non-interactive exit paths
676
- // (daemon worker / crash / kill) that bypass saveAndExit. saveAndExit
677
- // already finalizes; guard on !saved to avoid double-evaluating.
688
+ // (daemon worker / crash / kill) that bypass saveAndExit.
678
689
  if (!saved) {
679
690
  try {
680
691
  engine.getAutoMemory().finalizeSession()
681
692
  } catch {
682
693
  // ignore — CRSI closeout is non-critical
683
694
  }
684
- }
685
- if (!saved && context.getMessageCount() > 0) {
686
- SessionStore.autoSave(context.getMessages(), {
687
- provider: defaultProvider,
688
- model: defaultModel,
689
- cwd: process.cwd(),
690
- })
695
+ persistSession()
691
696
  // Distill learnings from the last 5 user messages into memory
692
697
  const allMessages = context.getMessages()
693
698
  const userMessages = allMessages
@@ -9,7 +9,7 @@
9
9
  export const PACKAGE_NAME = '@miphamai/cli' as const
10
10
 
11
11
  /** 当前发布版本 */
12
- export const PACKAGE_VERSION = '0.73.0' as const
12
+ export const PACKAGE_VERSION = '0.73.1' as const
13
13
 
14
14
  /** npm install 全局安装命令 */
15
15
  export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
@@ -1,3 +1,5 @@
1
+ import { homedir } from 'node:os'
2
+ import { parse } from 'node:path'
1
3
  import type { ToolDefinition, CredentialMaskingConfig } from '../../shared/index.ts'
2
4
  import { resolveSafe } from '../../security/path'
3
5
  import type { Service } from '../../vajra'
@@ -37,6 +39,15 @@ export function truncateGrepOutput(stdout: string): string {
37
39
  return `${out.slice(0, GREP_MAX_OUTPUT_CHARS)}\n\n... (truncated)`
38
40
  }
39
41
 
42
+ /**
43
+ * 判断搜索根是否是「顶层目录」(家目录或文件系统根)。这类范围扫描的是海量
44
+ * 文件树(macOS ~/Library 有数百万受保护文件),会让 rg exit 2、find 回退卡
45
+ * 满 120s。顶层范围应 fail-fast,让模型指定项目目录,而不是静默全盘扫。
46
+ */
47
+ export function isTopLevelScope(searchPath: string, home = homedir()): boolean {
48
+ return searchPath === home || searchPath === parse(searchPath).root
49
+ }
50
+
40
51
  export function createGrepTool(credentialConfig?: CredentialMaskingConfig): ToolDefinition {
41
52
  return {
42
53
  name: 'Grep',
@@ -65,6 +76,20 @@ export function createGrepTool(credentialConfig?: CredentialMaskingConfig): Tool
65
76
  const searchPath = resolveSafe(ctx.cwd, (params.path as string) || '.')
66
77
  const include = params.include as string | undefined
67
78
 
79
+ // Scope guard: a top-level search root (home or filesystem root) scans an
80
+ // enormous tree and stalls the find fallback for minutes. Fail fast and
81
+ // ask for a project-scoped path instead of silently scanning everything.
82
+ if (!params.path && isTopLevelScope(searchPath)) {
83
+ return {
84
+ success: false,
85
+ content: '',
86
+ error:
87
+ `Search scope is the home directory / filesystem root (${searchPath}) — too large ` +
88
+ 'and contains protected directories. Specify a project directory with "path", ' +
89
+ 'or cd into the project first.',
90
+ }
91
+ }
92
+
68
93
  // 1. ripgrep (fast path)
69
94
  const rgArgs = ['rg', '-n', '--heading', '--color=never', '-M', '500', pattern]
70
95
  if (include) rgArgs.push('--glob', include)
@@ -85,9 +110,30 @@ export function createGrepTool(credentialConfig?: CredentialMaskingConfig): Tool
85
110
  content: maskSearchOutput(stdout || '(no matches)', credentialConfig, 'heading'),
86
111
  }
87
112
  }
88
- // rg exit 2 (error) or null (killed) → fall through to grep
113
+ // rg exit 2 (error, e.g. permission denied on protected dirs) — do NOT
114
+ // fall back to the slow `find -type f` scan (it hits the same unreadable
115
+ // paths and stalls on huge trees). Return partial matches if any, else
116
+ // a clear narrow-scope error.
117
+ if (stdout && stdout.trim()) {
118
+ return {
119
+ success: true,
120
+ content: maskSearchOutput(
121
+ stdout +
122
+ '\n\n(rg exited 2 — some paths unreadable; narrow scope for complete results)',
123
+ credentialConfig,
124
+ 'heading',
125
+ ),
126
+ }
127
+ }
128
+ return {
129
+ success: false,
130
+ content: '',
131
+ error:
132
+ 'rg error (exit 2) — likely permission denied on a large/protected tree. ' +
133
+ 'Narrow scope with "path" (project directory) and "include".',
134
+ }
89
135
  } catch {
90
- // rg not installed → fall through to grep
136
+ // rg not installed → fall through to grep (find + grep fallback)
91
137
  }
92
138
 
93
139
  // 2. fallback: `find -type f -exec grep -Hn {} +`