@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
|
@@ -780,7 +780,7 @@
|
|
|
780
780
|
"ui": {
|
|
781
781
|
"banner": {
|
|
782
782
|
"title": "Mipham Code",
|
|
783
|
-
"subtitle": "AI-Powered
|
|
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",
|
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
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
package/src/tools/file/grep.ts
CHANGED
|
@@ -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
|
|
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 {} +`
|