@yivan-lab/pretty-please 1.1.0 → 1.3.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 (62) hide show
  1. package/README.md +390 -1
  2. package/bin/pls.tsx +1255 -123
  3. package/dist/bin/pls.js +1098 -103
  4. package/dist/package.json +4 -4
  5. package/dist/src/alias.d.ts +41 -0
  6. package/dist/src/alias.js +240 -0
  7. package/dist/src/chat-history.js +10 -1
  8. package/dist/src/components/Chat.js +54 -26
  9. package/dist/src/components/CodeColorizer.js +26 -20
  10. package/dist/src/components/CommandBox.js +19 -8
  11. package/dist/src/components/ConfirmationPrompt.js +2 -1
  12. package/dist/src/components/Duration.js +2 -1
  13. package/dist/src/components/InlineRenderer.js +2 -1
  14. package/dist/src/components/MarkdownDisplay.js +2 -1
  15. package/dist/src/components/MultiStepCommandGenerator.d.ts +3 -1
  16. package/dist/src/components/MultiStepCommandGenerator.js +20 -10
  17. package/dist/src/components/TableRenderer.js +2 -1
  18. package/dist/src/config.d.ts +33 -3
  19. package/dist/src/config.js +83 -34
  20. package/dist/src/mastra-agent.d.ts +1 -0
  21. package/dist/src/mastra-agent.js +3 -11
  22. package/dist/src/mastra-chat.d.ts +13 -6
  23. package/dist/src/mastra-chat.js +31 -31
  24. package/dist/src/multi-step.d.ts +23 -7
  25. package/dist/src/multi-step.js +45 -26
  26. package/dist/src/prompts.d.ts +30 -4
  27. package/dist/src/prompts.js +218 -70
  28. package/dist/src/remote-history.d.ts +63 -0
  29. package/dist/src/remote-history.js +315 -0
  30. package/dist/src/remote.d.ts +113 -0
  31. package/dist/src/remote.js +634 -0
  32. package/dist/src/shell-hook.d.ts +58 -0
  33. package/dist/src/shell-hook.js +295 -26
  34. package/dist/src/ui/theme.d.ts +60 -23
  35. package/dist/src/ui/theme.js +544 -22
  36. package/dist/src/upgrade.d.ts +41 -0
  37. package/dist/src/upgrade.js +348 -0
  38. package/dist/src/utils/console.d.ts +4 -0
  39. package/dist/src/utils/console.js +89 -17
  40. package/package.json +4 -4
  41. package/src/alias.ts +301 -0
  42. package/src/chat-history.ts +11 -1
  43. package/src/components/Chat.tsx +71 -26
  44. package/src/components/CodeColorizer.tsx +27 -19
  45. package/src/components/CommandBox.tsx +26 -8
  46. package/src/components/ConfirmationPrompt.tsx +2 -1
  47. package/src/components/Duration.tsx +2 -1
  48. package/src/components/InlineRenderer.tsx +2 -1
  49. package/src/components/MarkdownDisplay.tsx +2 -1
  50. package/src/components/MultiStepCommandGenerator.tsx +25 -11
  51. package/src/components/TableRenderer.tsx +2 -1
  52. package/src/config.ts +126 -35
  53. package/src/mastra-agent.ts +3 -12
  54. package/src/mastra-chat.ts +40 -34
  55. package/src/multi-step.ts +62 -30
  56. package/src/prompts.ts +236 -78
  57. package/src/remote-history.ts +390 -0
  58. package/src/remote.ts +800 -0
  59. package/src/shell-hook.ts +339 -26
  60. package/src/ui/theme.ts +632 -23
  61. package/src/upgrade.ts +397 -0
  62. package/src/utils/console.ts +99 -17
package/src/multi-step.ts CHANGED
@@ -1,19 +1,22 @@
1
1
  import { z } from 'zod'
2
2
  import { createShellAgent } from './mastra-agent.js'
3
- import { buildCommandSystemPrompt } from './prompts.js'
3
+ import { SHELL_COMMAND_SYSTEM_PROMPT, buildUserContextPrompt } from './prompts.js'
4
4
  import { formatSystemInfo } from './sysinfo.js'
5
5
  import { formatHistoryForAI } from './history.js'
6
6
  import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js'
7
- import { getConfig } from './config.js'
7
+ import { getConfig, type RemoteSysInfo } from './config.js'
8
+ import { formatRemoteHistoryForAI, formatRemoteShellHistoryForAI, type RemoteShellHistoryItem } from './remote-history.js'
9
+ import { formatRemoteSysInfoForAI } from './remote.js'
8
10
 
9
11
  /**
10
12
  * 多步骤命令的 Zod Schema
13
+ * 注意:optional 字段使用 .default() 是为了绕过 Mastra 0.24.8 对 optional 字段的验证 bug
11
14
  */
12
15
  export const CommandStepSchema = z.object({
13
16
  command: z.string(),
14
- continue: z.boolean().optional(), // 可选!没有 continue = 单步模式
15
- reasoning: z.string().optional(),
16
- nextStepHint: z.string().optional(),
17
+ continue: z.boolean().optional().default(false),
18
+ reasoning: z.string().optional().default(''),
19
+ nextStepHint: z.string().optional().default(''),
17
20
  })
18
21
 
19
22
  export type CommandStep = z.infer<typeof CommandStepSchema>
@@ -27,16 +30,26 @@ export interface ExecutedStep extends CommandStep {
27
30
  }
28
31
 
29
32
  /**
30
- * 生成系统上下文信息(供 Mastra 使用)
33
+ * 远程执行上下文
34
+ */
35
+ export interface RemoteContext {
36
+ name: string
37
+ sysInfo: RemoteSysInfo
38
+ shellHistory: RemoteShellHistoryItem[]
39
+ }
40
+
41
+ /**
42
+ * 获取静态 System Prompt(供 Mastra 使用)
31
43
  */
32
44
  export function getFullSystemPrompt() {
33
- const config = getConfig()
34
- const sysinfo = formatSystemInfo()
35
- const plsHistory = formatHistoryForAI()
36
- const shellHistory = formatShellHistoryForAI()
37
- const shellHookEnabled = config.shellHook && getShellHistory().length > 0
45
+ return SHELL_COMMAND_SYSTEM_PROMPT
46
+ }
38
47
 
39
- return buildCommandSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
48
+ /**
49
+ * 获取静态 System Prompt(远程执行也使用相同的 System Prompt)
50
+ */
51
+ export function getRemoteFullSystemPrompt(remoteContext: RemoteContext) {
52
+ return SHELL_COMMAND_SYSTEM_PROMPT
40
53
  }
41
54
 
42
55
  /**
@@ -45,26 +58,39 @@ export function getFullSystemPrompt() {
45
58
  export async function generateMultiStepCommand(
46
59
  userPrompt: string,
47
60
  previousSteps: ExecutedStep[] = [],
48
- options: { debug?: boolean } = {}
61
+ options: { debug?: boolean; remoteContext?: RemoteContext } = {}
49
62
  ): Promise<{ stepData: CommandStep; debugInfo?: any }> {
50
63
  const agent = createShellAgent()
51
- const fullSystemPrompt = getFullSystemPrompt()
52
64
 
53
- // 构建消息数组(string[] 格式)
54
- const messages: string[] = [userPrompt]
65
+ // 准备动态数据
66
+ let sysinfoStr = ''
67
+ let historyStr = ''
55
68
 
56
- // 添加之前步骤的执行结果
57
- previousSteps.forEach((step) => {
58
- messages.push(
59
- JSON.stringify({
60
- command: step.command,
61
- continue: step.continue,
62
- reasoning: step.reasoning,
63
- nextStepHint: step.nextStepHint,
64
- })
65
- )
66
- messages.push(`命令已执行\n退出码: ${step.exitCode}\n输出:\n${step.output.slice(0, 500)}`)
67
- })
69
+ if (options.remoteContext) {
70
+ // 远程执行:格式化远程系统信息和历史
71
+ sysinfoStr = formatRemoteSysInfoForAI(options.remoteContext.name, options.remoteContext.sysInfo)
72
+ const plsHistory = formatRemoteHistoryForAI(options.remoteContext.name)
73
+ const shellHistory = formatRemoteShellHistoryForAI(options.remoteContext.shellHistory)
74
+ historyStr = options.remoteContext.shellHistory.length > 0 ? shellHistory : plsHistory
75
+ } else {
76
+ // 本地执行:格式化本地系统信息和历史
77
+ sysinfoStr = formatSystemInfo()
78
+ const config = getConfig()
79
+ const plsHistory = formatHistoryForAI()
80
+ const shellHistory = formatShellHistoryForAI()
81
+ historyStr = (config.shellHook && getShellHistory().length > 0) ? shellHistory : plsHistory
82
+ }
83
+
84
+ // 构建包含所有动态数据的 User Prompt(XML 格式)
85
+ const userContextPrompt = buildUserContextPrompt(
86
+ userPrompt,
87
+ sysinfoStr,
88
+ historyStr,
89
+ previousSteps
90
+ )
91
+
92
+ // 只发送一条 User Message
93
+ const messages = [userContextPrompt]
68
94
 
69
95
  // 调用 Mastra Agent 生成结构化输出
70
96
  const response = await agent.generate(messages, {
@@ -81,10 +107,16 @@ export async function generateMultiStepCommand(
81
107
  return {
82
108
  stepData,
83
109
  debugInfo: {
84
- fullPrompt: fullSystemPrompt,
85
- userPrompt,
110
+ systemPrompt: SHELL_COMMAND_SYSTEM_PROMPT,
111
+ userPrompt: userContextPrompt,
86
112
  previousStepsCount: previousSteps.length,
87
113
  response: stepData,
114
+ remoteContext: options.remoteContext
115
+ ? {
116
+ name: options.remoteContext.name,
117
+ sysInfo: options.remoteContext.sysInfo,
118
+ }
119
+ : undefined,
88
120
  },
89
121
  }
90
122
  }
package/src/prompts.ts CHANGED
@@ -3,34 +3,42 @@
3
3
  */
4
4
 
5
5
  /**
6
- * 构建命令生成模式的系统提示词
6
+ * ============================================================
7
+ * 命令生成模式的静态 System Prompt
8
+ * ============================================================
9
+ * 包含所有核心规则、输出格式定义、判断标准、错误处理策略和示例
10
+ * 不包含任何动态数据(系统信息、历史记录等)
7
11
  */
8
- export function buildCommandSystemPrompt(
9
- sysinfo: string,
10
- plsHistory: string,
11
- shellHistory: string,
12
- shellHookEnabled: boolean
13
- ): string {
14
- let prompt = `你是一个专业的 shell 脚本生成器。用户会提供他们的系统信息和一个命令需求。
15
- 你的任务是返回一个可执行的、原始的 shell 命令或脚本来完成他们的目标。
12
+ export const SHELL_COMMAND_SYSTEM_PROMPT = `你是一个专业的 shell 脚本生成器。
13
+ 你将接收到包含 XML 标签的上下文信息,然后根据用户需求生成可执行的 shell 命令。
14
+
15
+ ### 📋 输入数据格式说明
16
+ 你会收到以下 XML 标签包裹的上下文信息:
17
+ - <system_info>:用户的操作系统、Shell 类型、当前目录、包管理器等环境信息
18
+ - <command_history>:用户最近执行的命令历史(用于理解上下文引用,如"刚才的文件"、"上一个命令")
19
+ - <execution_log>:**多步骤任务的关键信息**,记录了之前步骤的命令、退出码和输出结果
20
+ - 如果存在此标签,说明这是一个多步骤任务
21
+ - 必须检查每个 <step> 中的 <exit_code>,0=成功,非0=失败
22
+ - 根据 <output> 的内容决定下一步操作
23
+ - <user_request>:用户的原始自然语言需求
16
24
 
17
- 重要规则:
25
+ ### ⚠️ 重要规则
18
26
  1. 返回 JSON 格式,command 字段必须是可直接执行的命令(无解释、无注释、无 markdown)
19
27
  2. 不要添加 shebang(如 #!/bin/bash)
20
28
  3. command 可以包含多条命令(用 && 连接),但整体算一个命令
21
- 4. 根据用户的系统信息选择合适的命令(如包管理器)
22
- 5. 如果用户引用了之前的操作(如"刚才的"、"上一个"),请参考历史记录
29
+ 4. 根据 <system_info> 中的信息选择合适的命令(如包管理器)
30
+ 5. 如果用户引用了之前的操作(如"刚才的"、"上一个"),请参考 <command_history>
23
31
  6. 绝对不要输出 pls 或 please 命令!
24
32
 
25
- 【输出格式 - 非常重要】
33
+ ### 📤 输出格式 - 非常重要
26
34
 
27
- 单步模式(一个命令完成):
35
+ **单步模式(一个命令完成):**
28
36
  如果任务只需要一个命令就能完成,只返回:
29
37
  {
30
38
  "command": "ls -la"
31
39
  }
32
40
 
33
- 多步模式(需要多个命令,后续依赖前面的结果):
41
+ **多步模式(需要多个命令,后续依赖前面的结果):**
34
42
  如果任务需要多个命令,且后续命令必须根据前面的执行结果来决定,则返回:
35
43
 
36
44
  【多步骤完整示例】
@@ -40,115 +48,265 @@ export function buildCommandSystemPrompt(
40
48
  {
41
49
  "command": "find . -name '*.log' -size +100M",
42
50
  "continue": true,
43
- "reasoning": "查找大日志", (精简即可)
44
- "nextStepHint": "压缩找到的文件" (精简即可)
51
+ "reasoning": "先查找符合条件的日志文件",
52
+ "nextStepHint": "根据查找结果压缩文件"
45
53
  }
46
54
 
47
- 执行后你会收到:
48
- 命令已执行
49
- 退出码: 0
50
- 输出:
55
+ 执行后你会收到(在 <execution_log> 中):
56
+ <execution_log>
57
+ <step index="1">
58
+ <command>find . -name '*.log' -size +100M</command>
59
+ <exit_code>0</exit_code>
60
+ <output>
51
61
  ./app.log
52
62
  ./system.log
63
+ </output>
64
+ </step>
65
+ </execution_log>
53
66
 
54
67
  然后你返回第二步:
55
68
  {
56
69
  "command": "tar -czf logs.tar.gz ./app.log ./system.log",
57
70
  "continue": false,
58
- "reasoning": "压缩日志文件"
71
+ "reasoning": "压缩找到的日志文件"
59
72
  }
60
73
 
61
- 关键判断标准:
62
- - 多步 = 后续命令依赖前面的输出(如先 find 看有哪些,再根据结果操作具体文件)
63
- - 单步 = 一个命令就能完成(即使命令里有 && 连接多条,也算一个命令)
74
+ ### 🎯 关键判断标准
75
+ - **多步** = 后续命令依赖前面的输出(如先 find 看有哪些,再根据结果操作具体文件)
76
+ - **单步** = 一个命令就能完成(即使命令里有 && 连接多条,也算一个命令)
64
77
 
65
- 常见场景举例:
66
- - "删除空文件夹" → 单步:find . -empty -delete (一个命令完成)
78
+ ### 📚 常见场景举例
79
+ - "删除空文件夹" → 单步:\`find . -empty -delete\` (一个命令完成)
67
80
  - "查找大文件并压缩" → 多步:先 find 看有哪些,再 tar 压缩具体文件
68
- - "安装 git" → 单步:brew install git
69
- - "备份并删除旧日志" → 多步:先 mkdir backup,再 mv 文件到 backup
70
- - "查看目录" → 单步:ls -la
81
+ - "安装 git" → 单步:\`brew install git\` 或 \`apt-get install git\`
82
+ - "备份并删除旧日志" → 多步:先 \`mkdir backup\`,再 \`mv\` 文件到 backup
83
+ - "查看目录" → 单步:\`ls -la\`
84
+ - "查看磁盘使用情况并排序" → 单步:\`df -h | sort -k5 -rh\` (一个管道命令)
85
+ - "查看进程并杀死某个进程" → 多步:先 \`ps aux | grep xxx\` 看 PID,再 \`kill\` 具体 PID
86
+
87
+ **严格要求**:单步模式只返回 {"command": "xxx"},绝对不要输出 continue/reasoning/nextStepHint!
71
88
 
72
- 严格要求:单步模式只返回 {"command": "xxx"},绝对不要输出 continue/reasoning/nextStepHint!
89
+ ### 🔧 错误处理策略
73
90
 
74
- 【错误处理】
75
- 如果你收到命令执行失败的信息(退出码非0),你应该:
76
- 1. 分析错误原因
77
- 2. 调整命令策略,返回修正后的命令
78
- 3. 设置 continue: true 重试,或设置 continue: false 放弃
91
+ **当 <execution_log> 中最后一步的 <exit_code> 不为 0 时:**
79
92
 
80
- 错误处理示例:
81
- 上一步失败,你收到:
82
- 命令已执行
83
- 退出码: 1
84
- 输出:
85
- mv: rename ./test.zip to ./c/test.zip: No such file or directory
93
+ 1. **分析错误原因**:仔细阅读 <output> 中的错误信息
94
+ 2. **调整命令策略**:生成修正后的命令
95
+ 3. **决定是否继续**:
96
+ - 设置 \`continue: true\` 重试修正后的命令
97
+ - 设置 \`continue: false\` 放弃任务
98
+
99
+ **错误处理示例 1:权限错误**
100
+ <execution_log>
101
+ <step index="1">
102
+ <command>mkdir /var/log/myapp</command>
103
+ <exit_code>1</exit_code>
104
+ <output>mkdir: cannot create directory '/var/log/myapp': Permission denied</output>
105
+ </step>
106
+ </execution_log>
86
107
 
87
108
  你分析后返回修正:
88
109
  {
89
- "command": "cp test.zip a/ && cp test.zip b/ && cp test.zip c/",
110
+ "command": "sudo mkdir /var/log/myapp",
111
+ "continue": true,
112
+ "reasoning": "权限不足,使用 sudo 重试"
113
+ }
114
+
115
+ **错误处理示例 2:文件不存在**
116
+ <execution_log>
117
+ <step index="1">
118
+ <command>mv test.zip backup/</command>
119
+ <exit_code>1</exit_code>
120
+ <output>mv: cannot stat 'test.zip': No such file or directory</output>
121
+ </step>
122
+ </execution_log>
123
+
124
+ 你分析后决定放弃:
125
+ {
126
+ "command": "",
90
127
  "continue": false,
91
- "reasoning": "改用 cp 复制而非 mv"
128
+ "reasoning": "源文件 test.zip 不存在,无法移动,任务无法继续"
92
129
  }
93
130
 
94
- 或者如果决定放弃(无法修正),返回:
131
+ **错误处理示例 3:命令不存在,尝试安装**
132
+ <execution_log>
133
+ <step index="1">
134
+ <command>docker ps</command>
135
+ <exit_code>127</exit_code>
136
+ <output>bash: docker: command not found</output>
137
+ </step>
138
+ </execution_log>
139
+
140
+ 你根据 <system_info> 中的包管理器返回:
141
+ {
142
+ "command": "brew install docker",
143
+ "continue": true,
144
+ "reasoning": "docker 未安装,根据系统包管理器安装"
145
+ }
146
+
147
+ **错误处理示例 4:网络错误**
148
+ <execution_log>
149
+ <step index="1">
150
+ <command>ping -c 3 example.com</command>
151
+ <exit_code>2</exit_code>
152
+ <output>ping: example.com: Name or service not known</output>
153
+ </step>
154
+ </execution_log>
155
+
156
+ 你分析后返回:
95
157
  {
96
158
  "command": "",
97
159
  "continue": false,
98
- "reasoning": "文件不存在且无法恢复,任务无法继续"
160
+ "reasoning": "网络不可达或 DNS 解析失败,无法继续"
99
161
  }
100
162
 
101
- 重要:当 continue: false 且决定放弃时,command 可以留空,重点是在 reasoning 中说明为什么放弃。
163
+ ### 🛑 何时应该放弃(continue: false
164
+ - 用户输入的路径不存在且无法推测
165
+ - 需要的工具未安装且无法自动安装(如非 root 用户无法 apt install)
166
+ - 权限问题且无法用 sudo 解决(如 SELinux 限制)
167
+ - 网络不可达或 DNS 解析失败
168
+ - 重试 2 次后仍然失败
169
+ - 任务本身不合理(如"删除根目录")
170
+
171
+ **放弃时的要求**:
172
+ - \`command\` 字段可以留空("")
173
+ - \`continue\` 必须为 false
174
+ - \`reasoning\` 必须详细说明为什么放弃,以及尝试了什么
102
175
 
103
- 关于 pls/please 工具:
176
+ ### 📌 关于 pls/please 工具
104
177
  用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
105
178
  当用户输入 "pls <描述>" 时,AI(也就是你)会生成对应的 shell 命令供用户确认执行。
106
- 历史记录中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
179
+ <command_history> 中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
180
+ `
107
181
 
108
- 用户的系统信息:${sysinfo}`
182
+ /**
183
+ * ============================================================
184
+ * 构建动态 User Prompt(XML 格式)
185
+ * ============================================================
186
+ * 将系统信息、历史记录、执行日志等动态数据组装成 XML 结构
187
+ */
188
+ export function buildUserContextPrompt(
189
+ userRequest: string,
190
+ sysInfoStr: string,
191
+ historyStr: string,
192
+ executedSteps: Array<{ command: string; exitCode: number; output: string }>
193
+ ): string {
194
+ const parts: string[] = []
195
+
196
+ // 1. 系统信息
197
+ parts.push(`<system_info>`)
198
+ parts.push(sysInfoStr)
199
+ parts.push(`</system_info>`)
200
+
201
+ // 2. 命令历史(如果有)
202
+ if (historyStr && historyStr.trim()) {
203
+ parts.push(`<command_history>`)
204
+ parts.push(historyStr)
205
+ parts.push(`</command_history>`)
206
+ }
207
+
208
+ // 3. 执行日志(多步骤的核心,紧凑 XML 结构)
209
+ if (executedSteps && executedSteps.length > 0) {
210
+ parts.push(`<execution_log>`)
211
+ executedSteps.forEach((step, i) => {
212
+ // 截断过长的输出(前 800 + 后 800,中间省略)
213
+ let safeOutput = step.output || ''
214
+ if (safeOutput.length > 2000) {
215
+ const head = safeOutput.slice(0, 800)
216
+ const tail = safeOutput.slice(-800)
217
+ safeOutput = head + '\n\n...(输出过长,已截断)...\n\n' + tail
218
+ }
109
219
 
110
- // 根据是否启用 shell hook 决定展示哪个历史
111
- if (shellHookEnabled && shellHistory) {
112
- prompt += `\n\n${shellHistory}`
113
- } else if (plsHistory) {
114
- prompt += `\n\n${plsHistory}`
220
+ parts.push(`<step index="${i + 1}">`)
221
+ parts.push(`<command>${step.command}</command>`)
222
+ parts.push(`<exit_code>${step.exitCode}</exit_code>`)
223
+ parts.push(`<output>`)
224
+ parts.push(safeOutput)
225
+ parts.push(`</output>`)
226
+ parts.push(`</step>`)
227
+ })
228
+ parts.push(`</execution_log>`)
229
+ parts.push(``)
230
+ parts.push(`⚠️ 注意:请检查 <execution_log> 中最后一步的 <exit_code>。如果非 0,请分析 <output> 并修复命令。`)
115
231
  }
116
232
 
117
- return prompt
233
+ // 4. 用户需求
234
+ parts.push(`<user_request>`)
235
+ parts.push(userRequest)
236
+ parts.push(`</user_request>`)
237
+
238
+ return parts.join('\n')
118
239
  }
119
240
 
120
241
  /**
121
- * 构建 Chat 对话模式的系统提示词
242
+ * ============================================================
243
+ * Chat 对话模式的静态 System Prompt
244
+ * ============================================================
245
+ * 包含所有核心规则和能力描述,不包含动态数据
122
246
  */
123
- export function buildChatSystemPrompt(
124
- sysinfo: string,
125
- plsHistory: string,
126
- shellHistory: string,
127
- shellHookEnabled: boolean
128
- ): string {
129
- let prompt = `你是一个命令行专家助手,帮助用户理解和使用命令行工具。
247
+ export const CHAT_SYSTEM_PROMPT = `你是一个命令行专家助手,帮助用户理解和使用命令行工具。
248
+
249
+ ### 📋 输入数据格式说明
250
+ 你会收到以下 XML 标签包裹的上下文信息:
251
+ - <system_info>:用户的操作系统、Shell 类型、当前目录等环境信息
252
+ - <command_history>:用户最近通过 pls 执行的命令(用于理解上下文引用)
253
+ - <shell_history>:用户最近在终端执行的所有命令(如果启用了 Shell Hook)
254
+ - <user_question>:用户的具体问题
130
255
 
131
- 【你的能力】
256
+ ### 🎯 你的能力
132
257
  - 解释命令的含义、参数、用法
133
258
  - 分析命令的执行效果和潜在风险
134
259
  - 回答命令行、Shell、系统管理相关问题
135
260
  - 根据用户需求推荐合适的命令并解释
136
261
 
137
- 【回答要求】
138
- - 简洁清晰,避免冗余
139
- - 危险操作要明确警告
140
- - 适当给出示例命令
141
- - 结合用户的系统环境给出针对性建议
262
+ ### 📝 回答要求
263
+ - 简洁清晰,避免冗余解释
264
+ - 危险操作要明确警告(如 rm -rf、chmod 777 等)
265
+ - 适当给出示例命令(使用代码块格式)
266
+ - 结合 <system_info> 中的系统环境给出针对性建议
267
+ - 如果用户引用了"刚才的命令"、"上一个操作",请参考历史记录
142
268
 
143
- 【用户系统信息】
144
- ${sysinfo}`
269
+ ### 📌 关于 pls/please 工具
270
+ 用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
271
+ <command_history> 中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
272
+ 你可以解释这些命令,帮助用户理解它们的作用。`
273
+
274
+ /**
275
+ * ============================================================
276
+ * 构建 Chat 动态 User Context(XML 格式)
277
+ * ============================================================
278
+ * 将系统信息、历史记录等动态数据组装成 XML 结构
279
+ * 这个上下文会作为最新一条 user 消息发送给 AI
280
+ */
281
+ export function buildChatUserContext(
282
+ userQuestion: string,
283
+ sysInfoStr: string,
284
+ plsHistory: string,
285
+ shellHistory: string,
286
+ shellHookEnabled: boolean
287
+ ): string {
288
+ const parts: string[] = []
145
289
 
146
- // 根据是否启用 shell hook 决定展示哪个历史
147
- if (shellHookEnabled && shellHistory) {
148
- prompt += `\n\n${shellHistory}`
149
- } else if (plsHistory) {
150
- prompt += `\n\n${plsHistory}`
290
+ // 1. 系统信息
291
+ parts.push('<system_info>')
292
+ parts.push(sysInfoStr)
293
+ parts.push('</system_info>')
294
+
295
+ // 2. 历史记录(根据 Shell Hook 状态选择)
296
+ if (shellHookEnabled && shellHistory && shellHistory.trim()) {
297
+ parts.push('<shell_history>')
298
+ parts.push(shellHistory)
299
+ parts.push('</shell_history>')
300
+ } else if (plsHistory && plsHistory.trim()) {
301
+ parts.push('<command_history>')
302
+ parts.push(plsHistory)
303
+ parts.push('</command_history>')
151
304
  }
152
305
 
153
- return prompt
306
+ // 3. 用户问题
307
+ parts.push('<user_question>')
308
+ parts.push(userQuestion)
309
+ parts.push('</user_question>')
310
+
311
+ return parts.join('\n')
154
312
  }