@yivan-lab/pretty-please 1.4.0 → 1.5.0

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.
Files changed (94) hide show
  1. package/README.md +30 -2
  2. package/bin/pls.tsx +153 -35
  3. package/dist/bin/pls.js +126 -23
  4. package/dist/package.json +10 -2
  5. package/dist/src/__integration__/command-generation.test.d.ts +5 -0
  6. package/dist/src/__integration__/command-generation.test.js +508 -0
  7. package/dist/src/__integration__/error-recovery.test.d.ts +5 -0
  8. package/dist/src/__integration__/error-recovery.test.js +511 -0
  9. package/dist/src/__integration__/shell-hook-workflow.test.d.ts +5 -0
  10. package/dist/src/__integration__/shell-hook-workflow.test.js +375 -0
  11. package/dist/src/__tests__/alias.test.d.ts +5 -0
  12. package/dist/src/__tests__/alias.test.js +421 -0
  13. package/dist/src/__tests__/chat-history.test.d.ts +5 -0
  14. package/dist/src/__tests__/chat-history.test.js +372 -0
  15. package/dist/src/__tests__/config.test.d.ts +5 -0
  16. package/dist/src/__tests__/config.test.js +822 -0
  17. package/dist/src/__tests__/history.test.d.ts +5 -0
  18. package/dist/src/__tests__/history.test.js +439 -0
  19. package/dist/src/__tests__/remote-history.test.d.ts +5 -0
  20. package/dist/src/__tests__/remote-history.test.js +641 -0
  21. package/dist/src/__tests__/remote.test.d.ts +5 -0
  22. package/dist/src/__tests__/remote.test.js +689 -0
  23. package/dist/src/__tests__/shell-hook-install.test.d.ts +5 -0
  24. package/dist/src/__tests__/shell-hook-install.test.js +413 -0
  25. package/dist/src/__tests__/shell-hook-remote.test.d.ts +5 -0
  26. package/dist/src/__tests__/shell-hook-remote.test.js +507 -0
  27. package/dist/src/__tests__/shell-hook.test.d.ts +5 -0
  28. package/dist/src/__tests__/shell-hook.test.js +440 -0
  29. package/dist/src/__tests__/sysinfo.test.d.ts +5 -0
  30. package/dist/src/__tests__/sysinfo.test.js +572 -0
  31. package/dist/src/__tests__/system-history.test.d.ts +5 -0
  32. package/dist/src/__tests__/system-history.test.js +457 -0
  33. package/dist/src/components/Chat.js +9 -28
  34. package/dist/src/config.d.ts +2 -0
  35. package/dist/src/config.js +30 -2
  36. package/dist/src/mastra-chat.js +6 -3
  37. package/dist/src/multi-step.js +6 -3
  38. package/dist/src/project-context.d.ts +22 -0
  39. package/dist/src/project-context.js +168 -0
  40. package/dist/src/prompts.d.ts +4 -4
  41. package/dist/src/prompts.js +23 -6
  42. package/dist/src/shell-hook.d.ts +13 -0
  43. package/dist/src/shell-hook.js +163 -33
  44. package/dist/src/sysinfo.d.ts +38 -9
  45. package/dist/src/sysinfo.js +245 -21
  46. package/dist/src/system-history.d.ts +5 -0
  47. package/dist/src/system-history.js +64 -18
  48. package/dist/src/ui/__tests__/theme.test.d.ts +5 -0
  49. package/dist/src/ui/__tests__/theme.test.js +688 -0
  50. package/dist/src/upgrade.js +3 -0
  51. package/dist/src/user-preferences.d.ts +44 -0
  52. package/dist/src/user-preferences.js +147 -0
  53. package/dist/src/utils/__tests__/platform-capabilities.test.d.ts +5 -0
  54. package/dist/src/utils/__tests__/platform-capabilities.test.js +214 -0
  55. package/dist/src/utils/__tests__/platform-exec.test.d.ts +5 -0
  56. package/dist/src/utils/__tests__/platform-exec.test.js +212 -0
  57. package/dist/src/utils/__tests__/platform-shell.test.d.ts +5 -0
  58. package/dist/src/utils/__tests__/platform-shell.test.js +300 -0
  59. package/dist/src/utils/__tests__/platform.test.d.ts +5 -0
  60. package/dist/src/utils/__tests__/platform.test.js +137 -0
  61. package/dist/src/utils/platform.d.ts +88 -0
  62. package/dist/src/utils/platform.js +331 -0
  63. package/package.json +10 -2
  64. package/src/__integration__/command-generation.test.ts +602 -0
  65. package/src/__integration__/error-recovery.test.ts +620 -0
  66. package/src/__integration__/shell-hook-workflow.test.ts +457 -0
  67. package/src/__tests__/alias.test.ts +545 -0
  68. package/src/__tests__/chat-history.test.ts +462 -0
  69. package/src/__tests__/config.test.ts +1043 -0
  70. package/src/__tests__/history.test.ts +538 -0
  71. package/src/__tests__/remote-history.test.ts +791 -0
  72. package/src/__tests__/remote.test.ts +866 -0
  73. package/src/__tests__/shell-hook-install.test.ts +510 -0
  74. package/src/__tests__/shell-hook-remote.test.ts +679 -0
  75. package/src/__tests__/shell-hook.test.ts +564 -0
  76. package/src/__tests__/sysinfo.test.ts +718 -0
  77. package/src/__tests__/system-history.test.ts +608 -0
  78. package/src/components/Chat.tsx +10 -37
  79. package/src/config.ts +29 -2
  80. package/src/mastra-chat.ts +8 -3
  81. package/src/multi-step.ts +7 -2
  82. package/src/project-context.ts +191 -0
  83. package/src/prompts.ts +26 -5
  84. package/src/shell-hook.ts +179 -33
  85. package/src/sysinfo.ts +326 -25
  86. package/src/system-history.ts +67 -14
  87. package/src/ui/__tests__/theme.test.ts +869 -0
  88. package/src/upgrade.ts +5 -0
  89. package/src/user-preferences.ts +178 -0
  90. package/src/utils/__tests__/platform-capabilities.test.ts +265 -0
  91. package/src/utils/__tests__/platform-exec.test.ts +278 -0
  92. package/src/utils/__tests__/platform-shell.test.ts +353 -0
  93. package/src/utils/__tests__/platform.test.ts +170 -0
  94. package/src/utils/platform.ts +431 -0
package/src/config.ts CHANGED
@@ -12,7 +12,8 @@ function getColors() {
12
12
  primary: theme.primary,
13
13
  secondary: theme.secondary,
14
14
  success: theme.success,
15
- error: theme.error
15
+ error: theme.error,
16
+ warning: theme.warning,
16
17
  }
17
18
  }
18
19
 
@@ -82,11 +83,13 @@ export interface Config {
82
83
  chatHistoryLimit: number
83
84
  commandHistoryLimit: number
84
85
  shellHistoryLimit: number
86
+ userPreferencesTopK: number // 用户偏好显示的命令数量(默认 20)
85
87
  editMode: EditMode
86
88
  theme: ThemeName
87
89
  aliases: Record<string, AliasConfig>
88
90
  remotes: Record<string, RemoteConfig> // 远程服务器配置
89
91
  defaultRemote?: string // 默认远程服务器名称
92
+ systemCacheExpireDays?: number // 系统信息缓存过期天数(默认 7 天)
90
93
  }
91
94
 
92
95
  /**
@@ -101,11 +104,13 @@ const DEFAULT_CONFIG: Config = {
101
104
  chatHistoryLimit: 5,
102
105
  commandHistoryLimit: 5,
103
106
  shellHistoryLimit: 10,
107
+ userPreferencesTopK: 20, // 默认显示 Top 20
104
108
  editMode: 'manual',
105
109
  theme: 'dark',
106
110
  aliases: {},
107
111
  remotes: {},
108
112
  defaultRemote: '',
113
+ systemCacheExpireDays: 7,
109
114
  }
110
115
 
111
116
  /**
@@ -169,7 +174,7 @@ export function setConfigValue(key: string, value: string | boolean | number): C
169
174
  // 处理特殊类型
170
175
  if (key === 'shellHook') {
171
176
  config.shellHook = value === 'true' || value === true
172
- } else if (key === 'chatHistoryLimit' || key === 'commandHistoryLimit' || key === 'shellHistoryLimit') {
177
+ } else if (key === 'chatHistoryLimit' || key === 'commandHistoryLimit' || key === 'shellHistoryLimit' || key === 'userPreferencesTopK' || key === 'systemCacheExpireDays') {
173
178
  const num = typeof value === 'number' ? value : parseInt(String(value), 10)
174
179
  if (isNaN(num) || num < 1) {
175
180
  throw new Error(`${key} 必须是大于 0 的整数`)
@@ -246,6 +251,10 @@ export function displayConfig(): void {
246
251
  console.log(` ${chalk.hex(colors.primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
247
252
  console.log(` ${chalk.hex(colors.primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
248
253
  console.log(` ${chalk.hex(colors.primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
254
+ console.log(` ${chalk.hex(colors.primary)('userPreferencesTopK')}: ${config.userPreferencesTopK} 个`)
255
+ if (config.systemCacheExpireDays !== undefined) {
256
+ console.log(` ${chalk.hex(colors.primary)('systemCacheExpireDays')}: ${config.systemCacheExpireDays} 天`)
257
+ }
249
258
 
250
259
  // 动态显示主题信息
251
260
  const themeMetadata = getAllThemeMetadata().find((m) => m.name === config.theme)
@@ -354,6 +363,8 @@ export async function runConfigWizard(): Promise<void> {
354
363
  const num = parseInt(chatHistoryLimit.trim(), 10)
355
364
  if (!isNaN(num) && num > 0) {
356
365
  config.chatHistoryLimit = num
366
+ } else {
367
+ console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
357
368
  }
358
369
  }
359
370
 
@@ -364,6 +375,8 @@ export async function runConfigWizard(): Promise<void> {
364
375
  const num = parseInt(commandHistoryLimit.trim(), 10)
365
376
  if (!isNaN(num) && num > 0) {
366
377
  config.commandHistoryLimit = num
378
+ } else {
379
+ console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
367
380
  }
368
381
  }
369
382
 
@@ -375,6 +388,20 @@ export async function runConfigWizard(): Promise<void> {
375
388
  const num = parseInt(shellHistoryLimit.trim(), 10)
376
389
  if (!isNaN(num) && num > 0) {
377
390
  config.shellHistoryLimit = num
391
+ } else {
392
+ console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
393
+ }
394
+ }
395
+
396
+ // 10. User Preferences Top K
397
+ const userPrefsPrompt = `${chalk.hex(colors.primary)('用户偏好显示命令数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.userPreferencesTopK)} ${chalk.gray('→')} `
398
+ const userPrefsTopK = await question(rl, userPrefsPrompt)
399
+ if (userPrefsTopK.trim()) {
400
+ const num = parseInt(userPrefsTopK.trim(), 10)
401
+ if (!isNaN(num) && num > 0) {
402
+ config.userPreferencesTopK = num
403
+ } else {
404
+ console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
378
405
  }
379
406
  }
380
407
 
@@ -1,7 +1,7 @@
1
1
  import { Agent } from '@mastra/core'
2
2
  import { getConfig } from './config.js'
3
3
  import { CHAT_SYSTEM_PROMPT, buildChatUserContext } from './prompts.js'
4
- import { formatSystemInfo } from './sysinfo.js'
4
+ import { formatSystemInfo, getSystemInfo } from './sysinfo.js'
5
5
  import { formatHistoryForAI } from './history.js'
6
6
  import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js'
7
7
  import { getChatHistory, addChatMessage } from './chat-history.js'
@@ -71,19 +71,24 @@ export async function chatWithMastra(
71
71
  }
72
72
 
73
73
  // 3. 构建最新消息(动态上下文 + 用户问题)
74
- const sysinfo = formatSystemInfo()
74
+ const sysinfo = formatSystemInfo(await getSystemInfo())
75
75
  const plsHistory = formatHistoryForAI()
76
76
  // 使用统一的历史获取接口(自动降级到系统历史)
77
77
  const { formatShellHistoryForAIWithFallback } = await import('./shell-hook.js')
78
78
  const shellHistory = formatShellHistoryForAIWithFallback()
79
79
  const shellHookEnabled = !!shellHistory // 如果有 shell 历史就视为启用
80
80
 
81
+ // 获取用户偏好
82
+ const { formatUserPreferences } = await import('./user-preferences.js')
83
+ const userPreferencesStr = formatUserPreferences()
84
+
81
85
  const latestUserContext = buildChatUserContext(
82
86
  prompt,
83
87
  sysinfo,
84
88
  plsHistory,
85
89
  shellHistory,
86
- shellHookEnabled
90
+ shellHookEnabled,
91
+ userPreferencesStr
87
92
  )
88
93
 
89
94
  messages.push(latestUserContext)
package/src/multi-step.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { createShellAgent } from './mastra-agent.js'
3
3
  import { SHELL_COMMAND_SYSTEM_PROMPT, buildUserContextPrompt } from './prompts.js'
4
- import { formatSystemInfo } from './sysinfo.js'
4
+ import { formatSystemInfo, getSystemInfo } from './sysinfo.js'
5
5
  import { formatHistoryForAI } from './history.js'
6
6
  import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js'
7
7
  import { getConfig, type RemoteSysInfo } from './config.js'
@@ -74,7 +74,7 @@ export async function generateMultiStepCommand(
74
74
  historyStr = options.remoteContext.shellHistory.length > 0 ? shellHistory : plsHistory
75
75
  } else {
76
76
  // 本地执行:格式化本地系统信息和历史
77
- sysinfoStr = formatSystemInfo()
77
+ sysinfoStr = formatSystemInfo(await getSystemInfo())
78
78
  const plsHistory = formatHistoryForAI()
79
79
  // 使用统一的历史获取接口(自动降级到系统历史)
80
80
  const { formatShellHistoryForAIWithFallback } = await import('./shell-hook.js')
@@ -82,11 +82,16 @@ export async function generateMultiStepCommand(
82
82
  historyStr = shellHistory || plsHistory // 优先使用 shell 历史,降级到 pls 历史
83
83
  }
84
84
 
85
+ // 获取用户偏好
86
+ const { formatUserPreferences } = await import('./user-preferences.js')
87
+ const userPreferencesStr = formatUserPreferences()
88
+
85
89
  // 构建包含所有动态数据的 User Prompt(XML 格式)
86
90
  const userContextPrompt = buildUserContextPrompt(
87
91
  userPrompt,
88
92
  sysinfoStr,
89
93
  historyStr,
94
+ userPreferencesStr,
90
95
  previousSteps
91
96
  )
92
97
 
@@ -0,0 +1,191 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { detect as detectPM } from 'detect-package-manager'
4
+ import { execSync } from 'child_process'
5
+
6
+ /**
7
+ * 项目上下文信息
8
+ */
9
+ export interface ProjectContext {
10
+ types: string[] // 项目类型 ['nodejs', 'docker', 'git']
11
+ packageManager?: string // 包管理器 'pnpm' | 'yarn' | 'bun' | 'npm' | 'uv' | 'poetry' | 'cargo'
12
+ git?: {
13
+ branch: string // Git 分支名
14
+ status: 'clean' | 'dirty' | 'unknown'
15
+ }
16
+ scripts?: string[] // 可用脚本(如 package.json scripts)
17
+ }
18
+
19
+ /**
20
+ * 检测项目上下文(优化版:< 30ms)
21
+ * 若未识别到任何特征,返回 null
22
+ */
23
+ export async function detectProjectContext(cwd: string): Promise<ProjectContext | null> {
24
+ const types: string[] = []
25
+ let packageManager: string | undefined
26
+ let git: ProjectContext['git'] | undefined
27
+ let scripts: string[] | undefined
28
+
29
+ try {
30
+ // 1. 快速读取目录文件列表(同步,< 5ms)
31
+ const files = fs.readdirSync(cwd)
32
+
33
+ // 2. 检测项目类型(纯文件检查,< 10ms)
34
+ if (files.includes('package.json')) {
35
+ types.push('nodejs')
36
+
37
+ // 检测 Node.js 包管理器
38
+ try {
39
+ packageManager = await detectPM({ cwd })
40
+ } catch {
41
+ // 降级:根据 lock 文件推断
42
+ if (files.includes('pnpm-lock.yaml')) packageManager = 'pnpm'
43
+ else if (files.includes('yarn.lock')) packageManager = 'yarn'
44
+ else if (files.includes('bun.lockb')) packageManager = 'bun'
45
+ else packageManager = 'npm'
46
+ }
47
+
48
+ // 读取 package.json scripts(最多 5 个)
49
+ try {
50
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'))
51
+ if (pkg.scripts) {
52
+ scripts = Object.keys(pkg.scripts).slice(0, 5)
53
+ }
54
+ } catch {
55
+ // 忽略读取错误
56
+ }
57
+ }
58
+
59
+ // Python
60
+ if (files.some(f => ['pyproject.toml', 'requirements.txt', 'Pipfile', 'uv.lock', 'poetry.lock'].includes(f))) {
61
+ types.push('python')
62
+ if (files.includes('uv.lock')) packageManager = 'uv'
63
+ else if (files.includes('poetry.lock')) packageManager = 'poetry'
64
+ else if (files.includes('Pipfile')) packageManager = 'pipenv'
65
+ else packageManager = 'pip'
66
+ }
67
+
68
+ // Rust
69
+ if (files.includes('Cargo.toml')) {
70
+ types.push('rust')
71
+ packageManager = 'cargo'
72
+ }
73
+
74
+ // Go
75
+ if (files.includes('go.mod')) {
76
+ types.push('go')
77
+ }
78
+
79
+ // Docker
80
+ if (files.some(f => f.includes('docker') || f === 'Dockerfile' || f === 'compose.yaml')) {
81
+ types.push('docker')
82
+ }
83
+
84
+ // Makefile
85
+ if (files.includes('Makefile')) {
86
+ types.push('make')
87
+ }
88
+
89
+ // 3. Git 检测优化(< 20ms)
90
+ if (files.includes('.git') || fs.existsSync(path.join(cwd, '.git'))) {
91
+ types.push('git')
92
+
93
+ try {
94
+ // 读取 .git/HEAD 文件获取分支(最快,< 5ms)
95
+ const headFile = fs.readFileSync(path.join(cwd, '.git/HEAD'), 'utf-8')
96
+ const refMatch = headFile.match(/ref: refs\/heads\/(.+)/)
97
+
98
+ if (refMatch) {
99
+ // 正常分支
100
+ git = {
101
+ branch: refMatch[1].trim(),
102
+ status: quickGitStatus(cwd),
103
+ }
104
+ } else {
105
+ // detached HEAD 状态(直接 commit hash)
106
+ const shortHash = headFile.trim().substring(0, 7)
107
+ git = {
108
+ branch: `HEAD (${shortHash})`,
109
+ status: quickGitStatus(cwd),
110
+ }
111
+ }
112
+ } catch {
113
+ // Git 检测失败,只标记类型,不提供详细信息
114
+ }
115
+ }
116
+
117
+ } catch (error) {
118
+ // 检测失败,返回空上下文
119
+ }
120
+
121
+ const hasContext =
122
+ types.length > 0 ||
123
+ !!packageManager ||
124
+ !!git ||
125
+ (scripts && scripts.length > 0)
126
+
127
+ if (!hasContext) {
128
+ return null
129
+ }
130
+
131
+ return { types, packageManager, git, scripts }
132
+ }
133
+
134
+ /**
135
+ * Git status 快速检测(带超时)
136
+ */
137
+ function quickGitStatus(cwd: string): 'clean' | 'dirty' | 'unknown' {
138
+ try {
139
+ // 使用 git status --porcelain,带超时保护
140
+ const result = execSync('git status --porcelain', {
141
+ cwd,
142
+ stdio: 'pipe',
143
+ timeout: 100, // 100ms 超时
144
+ encoding: 'utf-8',
145
+ })
146
+ return result.trim() === '' ? 'clean' : 'dirty'
147
+ } catch {
148
+ // 超时或失败,返回 unknown
149
+ return 'unknown'
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 格式化项目上下文为字符串(供 AI 使用)
155
+ * 格式:当前项目: nodejs+docker+git | pnpm | Git分支: main (有改动) | 脚本: dev, build, test
156
+ */
157
+ export function formatProjectContext(project: ProjectContext): string {
158
+ if (project.types.length === 0) {
159
+ return ''
160
+ }
161
+
162
+ const parts: string[] = []
163
+
164
+ // 项目类型
165
+ parts.push(`当前项目: ${project.types.join('+')}`)
166
+
167
+ // 包管理器
168
+ if (project.packageManager) {
169
+ parts.push(` | ${project.packageManager}`)
170
+ }
171
+
172
+ // Git 信息
173
+ if (project.git) {
174
+ let statusDesc = ''
175
+ if (project.git.status === 'clean') {
176
+ statusDesc = '干净'
177
+ } else if (project.git.status === 'dirty') {
178
+ statusDesc = '有改动'
179
+ } else {
180
+ statusDesc = '未知'
181
+ }
182
+ parts.push(` | Git分支: ${project.git.branch} (${statusDesc})`)
183
+ }
184
+
185
+ // 可用脚本
186
+ if (project.scripts && project.scripts.length > 0) {
187
+ parts.push(` | 脚本: ${project.scripts.join(', ')}`)
188
+ }
189
+
190
+ return parts.join('')
191
+ }
package/src/prompts.ts CHANGED
@@ -14,8 +14,11 @@ export const SHELL_COMMAND_SYSTEM_PROMPT = `你是一个专业的 shell 脚本
14
14
 
15
15
  ### 📋 输入数据格式说明
16
16
  你会收到以下 XML 标签包裹的上下文信息:
17
- - <system_info>:用户的操作系统、Shell 类型、当前目录、包管理器等环境信息
17
+ - <system_info>:用户的操作系统、Shell 类型、当前目录、包管理器、可用工具等环境信息
18
18
  - <command_history>:用户最近执行的命令历史(用于理解上下文引用,如"刚才的文件"、"上一个命令")
19
+ - <user_preferences>:**用户的命令使用偏好**(格式:命令名(使用次数)),帮助你了解用户习惯
20
+ - 例如:git(234), eza(156) 表示用户经常使用 git 和 eza 命令
21
+ - 生成命令时可参考偏好,但最终应结合任务需求和 <system_info> 综合判断
19
22
  - <execution_log>:**多步骤任务的关键信息**,记录了之前步骤的命令、退出码和输出结果
20
23
  - 如果存在此标签,说明这是一个多步骤任务
21
24
  - 必须检查每个 <step> 中的 <exit_code>,0=成功,非0=失败
@@ -29,6 +32,7 @@ export const SHELL_COMMAND_SYSTEM_PROMPT = `你是一个专业的 shell 脚本
29
32
  4. 根据 <system_info> 中的信息选择合适的命令(如包管理器)
30
33
  5. 如果用户引用了之前的操作(如"刚才的"、"上一个"),请参考 <command_history>
31
34
  6. 绝对不要输出 pls 或 please 命令!
35
+ 7. **建议优先使用标准命令**(ls/find/grep/cat/ps)以确保兼容性和输出捕获,而不是使用 eza/bat/delta 等现代工具,除非用户明确要求使用相关工具,或者用户特别偏好使用相关工具。
32
36
 
33
37
  ### 📤 输出格式 - 非常重要
34
38
 
@@ -189,6 +193,7 @@ export function buildUserContextPrompt(
189
193
  userRequest: string,
190
194
  sysInfoStr: string,
191
195
  historyStr: string,
196
+ userPreferencesStr: string,
192
197
  executedSteps: Array<{ command: string; exitCode: number; output: string }>
193
198
  ): string {
194
199
  const parts: string[] = []
@@ -205,7 +210,14 @@ export function buildUserContextPrompt(
205
210
  parts.push(`</command_history>`)
206
211
  }
207
212
 
208
- // 3. 执行日志(多步骤的核心,紧凑 XML 结构)
213
+ // 3. 用户偏好(如果有)
214
+ if (userPreferencesStr && userPreferencesStr.trim()) {
215
+ parts.push(`<user_preferences>`)
216
+ parts.push(userPreferencesStr)
217
+ parts.push(`</user_preferences>`)
218
+ }
219
+
220
+ // 4. 执行日志(多步骤的核心,紧凑 XML 结构)
209
221
  if (executedSteps && executedSteps.length > 0) {
210
222
  parts.push(`<execution_log>`)
211
223
  executedSteps.forEach((step, i) => {
@@ -230,7 +242,7 @@ export function buildUserContextPrompt(
230
242
  parts.push(`⚠️ 注意:请检查 <execution_log> 中最后一步的 <exit_code>。如果非 0,请分析 <output> 并修复命令。`)
231
243
  }
232
244
 
233
- // 4. 用户需求
245
+ // 5. 用户需求
234
246
  parts.push(`<user_request>`)
235
247
  parts.push(userRequest)
236
248
  parts.push(`</user_request>`)
@@ -251,6 +263,7 @@ export const CHAT_SYSTEM_PROMPT = `你是一个命令行专家助手,帮助用
251
263
  - <system_info>:用户的操作系统、Shell 类型、当前目录等环境信息
252
264
  - <command_history>:用户最近通过 pls 执行的命令(用于理解上下文引用)
253
265
  - <shell_history>:用户最近在终端执行的所有命令(如果启用了 Shell Hook)
266
+ - <user_preferences>:用户的命令使用偏好(命令名(使用次数)),帮助你了解用户习惯
254
267
  - <user_question>:用户的具体问题
255
268
 
256
269
  ### 🎯 你的能力
@@ -283,7 +296,8 @@ export function buildChatUserContext(
283
296
  sysInfoStr: string,
284
297
  plsHistory: string,
285
298
  shellHistory: string,
286
- shellHookEnabled: boolean
299
+ shellHookEnabled: boolean,
300
+ userPreferencesStr?: string
287
301
  ): string {
288
302
  const parts: string[] = []
289
303
 
@@ -303,7 +317,14 @@ export function buildChatUserContext(
303
317
  parts.push('</command_history>')
304
318
  }
305
319
 
306
- // 3. 用户问题
320
+ // 3. 用户偏好(如果有)
321
+ if (userPreferencesStr && userPreferencesStr.trim()) {
322
+ parts.push('<user_preferences>')
323
+ parts.push(userPreferencesStr)
324
+ parts.push('</user_preferences>')
325
+ }
326
+
327
+ // 4. 用户问题
307
328
  parts.push('<user_question>')
308
329
  parts.push(userQuestion)
309
330
  parts.push('</user_question>')