@yivan-lab/pretty-please 1.2.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.
- package/README.md +112 -5
- package/bin/pls.tsx +265 -51
- package/dist/bin/pls.js +234 -49
- package/dist/package.json +1 -1
- package/dist/src/components/Chat.js +52 -25
- package/dist/src/components/CommandBox.js +17 -7
- package/dist/src/config.d.ts +1 -2
- package/dist/src/config.js +18 -9
- package/dist/src/mastra-agent.d.ts +1 -0
- package/dist/src/mastra-agent.js +3 -11
- package/dist/src/mastra-chat.d.ts +13 -6
- package/dist/src/mastra-chat.js +31 -31
- package/dist/src/multi-step.d.ts +2 -2
- package/dist/src/multi-step.js +35 -39
- package/dist/src/prompts.d.ts +30 -4
- package/dist/src/prompts.js +218 -70
- package/dist/src/shell-hook.d.ts +5 -0
- package/dist/src/shell-hook.js +56 -10
- package/dist/src/ui/theme.d.ts +35 -1
- package/dist/src/ui/theme.js +480 -8
- package/dist/src/utils/console.d.ts +4 -0
- package/dist/src/utils/console.js +67 -6
- package/package.json +1 -1
- package/src/components/Chat.tsx +69 -25
- package/src/components/CommandBox.tsx +24 -7
- package/src/config.ts +21 -15
- package/src/mastra-agent.ts +3 -12
- package/src/mastra-chat.ts +40 -34
- package/src/multi-step.ts +40 -45
- package/src/prompts.ts +236 -78
- package/src/shell-hook.ts +71 -10
- package/src/ui/theme.ts +542 -10
- package/src/utils/console.ts +77 -6
package/src/components/Chat.tsx
CHANGED
|
@@ -3,8 +3,13 @@ import { Box, Text } from 'ink'
|
|
|
3
3
|
import Spinner from 'ink-spinner'
|
|
4
4
|
import { MarkdownDisplay } from './MarkdownDisplay.js'
|
|
5
5
|
import { chatWithMastra } from '../mastra-chat.js'
|
|
6
|
-
import { getChatRoundCount } from '../chat-history.js'
|
|
6
|
+
import { getChatRoundCount, getChatHistory } from '../chat-history.js'
|
|
7
7
|
import { getCurrentTheme } from '../ui/theme.js'
|
|
8
|
+
import { formatSystemInfo } from '../sysinfo.js'
|
|
9
|
+
import { formatHistoryForAI } from '../history.js'
|
|
10
|
+
import { formatShellHistoryForAI, getShellHistory } from '../shell-hook.js'
|
|
11
|
+
import { getConfig } from '../config.js'
|
|
12
|
+
import { CHAT_SYSTEM_PROMPT, buildChatUserContext } from '../prompts.js'
|
|
8
13
|
|
|
9
14
|
interface ChatProps {
|
|
10
15
|
prompt: string
|
|
@@ -19,7 +24,7 @@ interface DebugInfo {
|
|
|
19
24
|
sysinfo: string
|
|
20
25
|
model: string
|
|
21
26
|
systemPrompt: string
|
|
22
|
-
|
|
27
|
+
userContext: string
|
|
23
28
|
chatHistory: any[]
|
|
24
29
|
}
|
|
25
30
|
|
|
@@ -32,9 +37,36 @@ export function Chat({ prompt, debug, showRoundCount, onComplete }: ChatProps) {
|
|
|
32
37
|
const [status, setStatus] = useState<Status>('thinking')
|
|
33
38
|
const [content, setContent] = useState('')
|
|
34
39
|
const [duration, setDuration] = useState(0)
|
|
35
|
-
const [debugInfo, setDebugInfo] = useState<DebugInfo | null>(null)
|
|
36
40
|
const [roundCount] = useState(getChatRoundCount())
|
|
37
41
|
|
|
42
|
+
// Debug 信息:直接在 useState 初始化时计算(同步)
|
|
43
|
+
const [debugInfo] = useState<DebugInfo | null>(() => {
|
|
44
|
+
if (!debug) return null
|
|
45
|
+
|
|
46
|
+
const config = getConfig()
|
|
47
|
+
const sysinfo = formatSystemInfo()
|
|
48
|
+
const plsHistory = formatHistoryForAI()
|
|
49
|
+
const shellHistory = formatShellHistoryForAI()
|
|
50
|
+
const shellHookEnabled = config.shellHook && getShellHistory().length > 0
|
|
51
|
+
const chatHistory = getChatHistory()
|
|
52
|
+
|
|
53
|
+
const userContext = buildChatUserContext(
|
|
54
|
+
prompt,
|
|
55
|
+
sysinfo,
|
|
56
|
+
plsHistory,
|
|
57
|
+
shellHistory,
|
|
58
|
+
shellHookEnabled
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
sysinfo,
|
|
63
|
+
model: config.model,
|
|
64
|
+
systemPrompt: CHAT_SYSTEM_PROMPT,
|
|
65
|
+
userContext,
|
|
66
|
+
chatHistory,
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
38
70
|
useEffect(() => {
|
|
39
71
|
const startTime = Date.now()
|
|
40
72
|
|
|
@@ -45,17 +77,13 @@ export function Chat({ prompt, debug, showRoundCount, onComplete }: ChatProps) {
|
|
|
45
77
|
}
|
|
46
78
|
|
|
47
79
|
// 调用 AI
|
|
48
|
-
chatWithMastra(prompt, { debug:
|
|
80
|
+
chatWithMastra(prompt, { debug: false, onChunk }) // 不需要 AI 返回 debug
|
|
49
81
|
.then((result) => {
|
|
50
82
|
const endTime = Date.now()
|
|
51
83
|
setDuration(endTime - startTime)
|
|
52
84
|
setStatus('done')
|
|
53
85
|
|
|
54
|
-
|
|
55
|
-
setDebugInfo(result.debug)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
setTimeout(onComplete, 100)
|
|
86
|
+
setTimeout(onComplete, debug ? 500 : 100)
|
|
59
87
|
})
|
|
60
88
|
.catch((error: any) => {
|
|
61
89
|
setStatus('error')
|
|
@@ -66,6 +94,38 @@ export function Chat({ prompt, debug, showRoundCount, onComplete }: ChatProps) {
|
|
|
66
94
|
|
|
67
95
|
return (
|
|
68
96
|
<Box flexDirection="column">
|
|
97
|
+
{/* 调试信息 - 放在最前面 */}
|
|
98
|
+
{debugInfo && (
|
|
99
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
100
|
+
<Text color={theme.accent} bold>━━━ 调试信息 ━━━</Text>
|
|
101
|
+
<Text color={theme.text.secondary}>模型: {debugInfo.model}</Text>
|
|
102
|
+
<Text color={theme.text.secondary}>对话历史轮数: {Math.floor(debugInfo.chatHistory.length / 2)}</Text>
|
|
103
|
+
|
|
104
|
+
{/* 历史对话(只显示用户问题) */}
|
|
105
|
+
{debugInfo.chatHistory.length > 0 && (
|
|
106
|
+
<Box flexDirection="column" marginTop={1}>
|
|
107
|
+
<Text color={theme.text.secondary}>历史对话(用户问题):</Text>
|
|
108
|
+
{debugInfo.chatHistory
|
|
109
|
+
.filter((msg) => msg.role === 'user')
|
|
110
|
+
.slice(-5) // 最多显示最近 5 条
|
|
111
|
+
.map((msg, idx) => (
|
|
112
|
+
<Text key={idx} color={theme.text.muted}>
|
|
113
|
+
{idx + 1}. {msg.content.substring(0, 50)}{msg.content.length > 50 ? '...' : ''}
|
|
114
|
+
</Text>
|
|
115
|
+
))}
|
|
116
|
+
</Box>
|
|
117
|
+
)}
|
|
118
|
+
|
|
119
|
+
{/* User Context */}
|
|
120
|
+
<Box flexDirection="column" marginTop={1}>
|
|
121
|
+
<Text color={theme.text.secondary}>User Context (最新消息):</Text>
|
|
122
|
+
<Text color={theme.text.muted}>{debugInfo.userContext.substring(0, 500)}...</Text>
|
|
123
|
+
</Box>
|
|
124
|
+
|
|
125
|
+
<Text color={theme.accent}>━━━━━━━━━━━━━━━━</Text>
|
|
126
|
+
</Box>
|
|
127
|
+
)}
|
|
128
|
+
|
|
69
129
|
{/* 显示对话轮数 */}
|
|
70
130
|
{showRoundCount && roundCount > 0 && (
|
|
71
131
|
<Box marginBottom={1}>
|
|
@@ -102,22 +162,6 @@ export function Chat({ prompt, debug, showRoundCount, onComplete }: ChatProps) {
|
|
|
102
162
|
<Text color={theme.text.secondary}>({(duration / 1000).toFixed(2)}s)</Text>
|
|
103
163
|
</Box>
|
|
104
164
|
)}
|
|
105
|
-
|
|
106
|
-
{/* 调试信息 */}
|
|
107
|
-
{debugInfo && (
|
|
108
|
-
<Box flexDirection="column" marginY={1}>
|
|
109
|
-
<Text color={theme.accent}>━━━ 调试信息 ━━━</Text>
|
|
110
|
-
<Text color={theme.text.secondary}>系统信息: {debugInfo.sysinfo}</Text>
|
|
111
|
-
<Text color={theme.text.secondary}>模型: {debugInfo.model}</Text>
|
|
112
|
-
<Text color={theme.text.secondary}>
|
|
113
|
-
对话历史轮数: {Math.floor(debugInfo.chatHistory.length / 2)}
|
|
114
|
-
</Text>
|
|
115
|
-
<Text color={theme.text.secondary}>System Prompt:</Text>
|
|
116
|
-
<Text dimColor>{debugInfo.systemPrompt}</Text>
|
|
117
|
-
<Text color={theme.text.secondary}>User Prompt: {debugInfo.userPrompt}</Text>
|
|
118
|
-
<Text color={theme.accent}>━━━━━━━━━━━━━━━━</Text>
|
|
119
|
-
</Box>
|
|
120
|
-
)}
|
|
121
165
|
</Box>
|
|
122
166
|
)
|
|
123
167
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
3
|
import { getCurrentTheme } from '../ui/theme.js'
|
|
4
|
-
import { getDisplayWidth } from '../utils/console.js'
|
|
4
|
+
import { getDisplayWidth, wrapText } from '../utils/console.js'
|
|
5
5
|
|
|
6
6
|
interface CommandBoxProps {
|
|
7
7
|
command: string
|
|
@@ -13,14 +13,31 @@ interface CommandBoxProps {
|
|
|
13
13
|
*/
|
|
14
14
|
export const CommandBox: React.FC<CommandBoxProps> = ({ command, title = '生成命令' }) => {
|
|
15
15
|
const theme = getCurrentTheme()
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
// 获取终端宽度,限制最大宽度
|
|
18
|
+
const termWidth = process.stdout.columns || 80
|
|
17
19
|
const titleWidth = getDisplayWidth(title)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
// 计算最大内容宽度(终端宽度 - 边框和内边距)
|
|
22
|
+
const maxContentWidth = termWidth - 6 // 减去 '│ ' 和 ' │' 以及一些余量
|
|
23
|
+
|
|
24
|
+
// 处理命令换行
|
|
25
|
+
const originalLines = command.split('\n')
|
|
26
|
+
const wrappedLines: string[] = []
|
|
27
|
+
for (const line of originalLines) {
|
|
28
|
+
wrappedLines.push(...wrapText(line, maxContentWidth))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 计算实际使用的宽度
|
|
32
|
+
const actualMaxWidth = Math.max(
|
|
33
|
+
...wrappedLines.map((l) => getDisplayWidth(l)),
|
|
34
|
+
titleWidth
|
|
35
|
+
)
|
|
36
|
+
const boxWidth = Math.min(actualMaxWidth + 4, termWidth - 2)
|
|
20
37
|
|
|
21
38
|
// 顶部边框:┌─ 生成命令 ─────┐
|
|
22
39
|
const topPadding = boxWidth - titleWidth - 5
|
|
23
|
-
const topBorder = '┌─ ' + title + ' ' + '─'.repeat(topPadding) + '┐'
|
|
40
|
+
const topBorder = '┌─ ' + title + ' ' + '─'.repeat(Math.max(0, topPadding)) + '┐'
|
|
24
41
|
|
|
25
42
|
// 底部边框
|
|
26
43
|
const bottomBorder = '└' + '─'.repeat(boxWidth - 2) + '┘'
|
|
@@ -28,9 +45,9 @@ export const CommandBox: React.FC<CommandBoxProps> = ({ command, title = '生成
|
|
|
28
45
|
return (
|
|
29
46
|
<Box flexDirection="column" marginY={1}>
|
|
30
47
|
<Text color={theme.warning}>{topBorder}</Text>
|
|
31
|
-
{
|
|
48
|
+
{wrappedLines.map((line, index) => {
|
|
32
49
|
const lineWidth = getDisplayWidth(line)
|
|
33
|
-
const padding = ' '.repeat(boxWidth - lineWidth - 4)
|
|
50
|
+
const padding = ' '.repeat(Math.max(0, boxWidth - lineWidth - 4))
|
|
34
51
|
return (
|
|
35
52
|
<Text key={index}>
|
|
36
53
|
<Text color={theme.warning}>│ </Text>
|
package/src/config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import readline from 'readline'
|
|
5
5
|
import chalk from 'chalk'
|
|
6
|
-
import { getCurrentTheme } from './ui/theme.js'
|
|
6
|
+
import { getCurrentTheme, isValidTheme, getAllThemeMetadata, type ThemeName } from './ui/theme.js'
|
|
7
7
|
|
|
8
8
|
// 获取主题颜色
|
|
9
9
|
function getColors() {
|
|
@@ -39,10 +39,6 @@ type Provider = (typeof VALID_PROVIDERS)[number]
|
|
|
39
39
|
const VALID_EDIT_MODES = ['manual', 'auto'] as const
|
|
40
40
|
type EditMode = (typeof VALID_EDIT_MODES)[number]
|
|
41
41
|
|
|
42
|
-
// 主题
|
|
43
|
-
const VALID_THEMES = ['dark', 'light'] as const
|
|
44
|
-
export type ThemeName = (typeof VALID_THEMES)[number]
|
|
45
|
-
|
|
46
42
|
/**
|
|
47
43
|
* 别名配置接口
|
|
48
44
|
*/
|
|
@@ -102,9 +98,9 @@ const DEFAULT_CONFIG: Config = {
|
|
|
102
98
|
model: 'gpt-4-turbo',
|
|
103
99
|
provider: 'openai',
|
|
104
100
|
shellHook: false,
|
|
105
|
-
chatHistoryLimit:
|
|
106
|
-
commandHistoryLimit:
|
|
107
|
-
shellHistoryLimit:
|
|
101
|
+
chatHistoryLimit: 5,
|
|
102
|
+
commandHistoryLimit: 5,
|
|
103
|
+
shellHistoryLimit: 10,
|
|
108
104
|
editMode: 'manual',
|
|
109
105
|
theme: 'dark',
|
|
110
106
|
aliases: {},
|
|
@@ -193,8 +189,10 @@ export function setConfigValue(key: string, value: string | boolean | number): C
|
|
|
193
189
|
config.editMode = strValue as EditMode
|
|
194
190
|
} else if (key === 'theme') {
|
|
195
191
|
const strValue = String(value)
|
|
196
|
-
if (!
|
|
197
|
-
|
|
192
|
+
if (!isValidTheme(strValue)) {
|
|
193
|
+
const allThemes = getAllThemeMetadata()
|
|
194
|
+
const themeNames = allThemes.map((m) => m.name).join(', ')
|
|
195
|
+
throw new Error(`theme 必须是以下之一: ${themeNames}`)
|
|
198
196
|
}
|
|
199
197
|
config.theme = strValue as ThemeName
|
|
200
198
|
} else if (key === 'apiKey' || key === 'baseUrl' || key === 'model' || key === 'defaultRemote') {
|
|
@@ -248,11 +246,12 @@ export function displayConfig(): void {
|
|
|
248
246
|
console.log(` ${chalk.hex(colors.primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
|
|
249
247
|
console.log(` ${chalk.hex(colors.primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
|
|
250
248
|
console.log(` ${chalk.hex(colors.primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
)
|
|
249
|
+
|
|
250
|
+
// 动态显示主题信息
|
|
251
|
+
const themeMetadata = getAllThemeMetadata().find((m) => m.name === config.theme)
|
|
252
|
+
const themeLabel = themeMetadata ? `${themeMetadata.name} (${themeMetadata.displayName})` : config.theme
|
|
253
|
+
console.log(` ${chalk.hex(colors.primary)('theme')}: ${chalk.hex(colors.primary)(themeLabel)}`)
|
|
254
|
+
|
|
256
255
|
console.log(chalk.gray('━'.repeat(50)))
|
|
257
256
|
console.log(chalk.gray(`配置文件: ${CONFIG_FILE}\n`))
|
|
258
257
|
}
|
|
@@ -369,6 +368,7 @@ export async function runConfigWizard(): Promise<void> {
|
|
|
369
368
|
}
|
|
370
369
|
|
|
371
370
|
// 9. Shell History Limit
|
|
371
|
+
const oldShellHistoryLimit = config.shellHistoryLimit // 保存旧值
|
|
372
372
|
const shellHistoryPrompt = `${chalk.hex(colors.primary)('Shell 历史保留条数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.shellHistoryLimit)} ${chalk.gray('→')} `
|
|
373
373
|
const shellHistoryLimit = await question(rl, shellHistoryPrompt)
|
|
374
374
|
if (shellHistoryLimit.trim()) {
|
|
@@ -384,6 +384,12 @@ export async function runConfigWizard(): Promise<void> {
|
|
|
384
384
|
console.log(chalk.hex(getColors().success)('✅ 配置已保存'))
|
|
385
385
|
console.log(chalk.gray(` ${CONFIG_FILE}`))
|
|
386
386
|
console.log()
|
|
387
|
+
|
|
388
|
+
// 如果修改了 shellHistoryLimit,自动重装 hook
|
|
389
|
+
if (oldShellHistoryLimit !== config.shellHistoryLimit) {
|
|
390
|
+
const { reinstallHookForLimitChange } = await import('./shell-hook.js')
|
|
391
|
+
await reinstallHookForLimitChange(oldShellHistoryLimit, config.shellHistoryLimit)
|
|
392
|
+
}
|
|
387
393
|
} catch (error) {
|
|
388
394
|
const message = error instanceof Error ? error.message : String(error)
|
|
389
395
|
console.log(chalk.hex(getColors().error)(`\n✗ 配置失败: ${message}`))
|
package/src/mastra-agent.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Agent } from '@mastra/core'
|
|
2
2
|
import { getConfig } from './config.js'
|
|
3
|
-
import {
|
|
4
|
-
import { formatSystemInfo } from './sysinfo.js'
|
|
5
|
-
import { formatHistoryForAI } from './history.js'
|
|
6
|
-
import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js'
|
|
3
|
+
import { SHELL_COMMAND_SYSTEM_PROMPT } from './prompts.js'
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
6
|
* 创建 Mastra Shell Agent
|
|
10
7
|
* 根据用户配置的 API Key、Base URL、Provider 和 Model
|
|
8
|
+
* 使用静态的 System Prompt(不包含动态数据)
|
|
11
9
|
*/
|
|
12
10
|
export function createShellAgent() {
|
|
13
11
|
const config = getConfig()
|
|
@@ -15,16 +13,9 @@ export function createShellAgent() {
|
|
|
15
13
|
// 组合 provider/model 格式(Mastra 要求)
|
|
16
14
|
const modelId = `${config.provider}/${config.model}` as `${string}/${string}`
|
|
17
15
|
|
|
18
|
-
// 构建系统提示词
|
|
19
|
-
const sysinfo = formatSystemInfo()
|
|
20
|
-
const plsHistory = formatHistoryForAI()
|
|
21
|
-
const shellHistory = formatShellHistoryForAI()
|
|
22
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0
|
|
23
|
-
const systemPrompt = buildCommandSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
|
|
24
|
-
|
|
25
16
|
return new Agent({
|
|
26
17
|
name: 'shell-commander',
|
|
27
|
-
instructions:
|
|
18
|
+
instructions: SHELL_COMMAND_SYSTEM_PROMPT,
|
|
28
19
|
model: {
|
|
29
20
|
url: config.baseUrl,
|
|
30
21
|
id: modelId,
|
package/src/mastra-chat.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Agent } from '@mastra/core'
|
|
2
2
|
import { getConfig } from './config.js'
|
|
3
|
-
import {
|
|
3
|
+
import { CHAT_SYSTEM_PROMPT, buildChatUserContext } 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
7
|
import { getChatHistory, addChatMessage } from './chat-history.js'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* 创建 Mastra Chat Agent
|
|
10
|
+
* 创建 Mastra Chat Agent(使用静态系统提示词)
|
|
11
11
|
*/
|
|
12
12
|
export function createChatAgent() {
|
|
13
13
|
const config = getConfig()
|
|
@@ -15,16 +15,9 @@ export function createChatAgent() {
|
|
|
15
15
|
// 组合 provider/model 格式(Mastra 要求)
|
|
16
16
|
const modelId = `${config.provider}/${config.model}` as `${string}/${string}`
|
|
17
17
|
|
|
18
|
-
// 构建系统提示词
|
|
19
|
-
const sysinfo = formatSystemInfo()
|
|
20
|
-
const plsHistory = formatHistoryForAI()
|
|
21
|
-
const shellHistory = formatShellHistoryForAI()
|
|
22
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0
|
|
23
|
-
const systemPrompt = buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
|
|
24
|
-
|
|
25
18
|
return new Agent({
|
|
26
19
|
name: 'chat-assistant',
|
|
27
|
-
instructions:
|
|
20
|
+
instructions: CHAT_SYSTEM_PROMPT, // 只包含静态规则
|
|
28
21
|
model: {
|
|
29
22
|
url: config.baseUrl,
|
|
30
23
|
id: modelId,
|
|
@@ -33,20 +26,19 @@ export function createChatAgent() {
|
|
|
33
26
|
})
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
/**
|
|
37
|
-
* 获取完整的系统提示词(用于调试)
|
|
38
|
-
*/
|
|
39
|
-
export function getChatSystemPrompt(): string {
|
|
40
|
-
const config = getConfig()
|
|
41
|
-
const sysinfo = formatSystemInfo()
|
|
42
|
-
const plsHistory = formatHistoryForAI()
|
|
43
|
-
const shellHistory = formatShellHistoryForAI()
|
|
44
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0
|
|
45
|
-
return buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
29
|
/**
|
|
49
30
|
* 使用 Mastra 进行 AI 对话(支持流式输出)
|
|
31
|
+
*
|
|
32
|
+
* 消息结构:
|
|
33
|
+
* [
|
|
34
|
+
* "历史问题1", // user (纯粹的问题)
|
|
35
|
+
* "历史回答1", // assistant
|
|
36
|
+
* "历史问题2", // user
|
|
37
|
+
* "历史回答2", // assistant
|
|
38
|
+
* "<system_info>...\n // user (最新消息,包含完整上下文)
|
|
39
|
+
* <command_history>...\n
|
|
40
|
+
* <user_question>最新问题</user_question>"
|
|
41
|
+
* ]
|
|
50
42
|
*/
|
|
51
43
|
export async function chatWithMastra(
|
|
52
44
|
prompt: string,
|
|
@@ -61,30 +53,44 @@ export async function chatWithMastra(
|
|
|
61
53
|
model: string
|
|
62
54
|
systemPrompt: string
|
|
63
55
|
chatHistory: Array<{ role: string; content: string }>
|
|
64
|
-
|
|
56
|
+
userContext: string
|
|
65
57
|
}
|
|
66
58
|
}> {
|
|
67
59
|
const config = getConfig()
|
|
68
60
|
const agent = createChatAgent()
|
|
69
61
|
|
|
70
|
-
//
|
|
62
|
+
// 1. 获取历史对话(纯粹的问答)
|
|
71
63
|
const chatHistory = getChatHistory()
|
|
72
64
|
|
|
73
|
-
//
|
|
65
|
+
// 2. 构建消息数组
|
|
74
66
|
const messages: string[] = []
|
|
75
67
|
|
|
76
|
-
//
|
|
68
|
+
// 加载历史对话
|
|
77
69
|
for (const msg of chatHistory) {
|
|
78
70
|
messages.push(msg.content)
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
//
|
|
82
|
-
|
|
73
|
+
// 3. 构建最新消息(动态上下文 + 用户问题)
|
|
74
|
+
const sysinfo = formatSystemInfo()
|
|
75
|
+
const plsHistory = formatHistoryForAI()
|
|
76
|
+
const shellHistory = formatShellHistoryForAI()
|
|
77
|
+
const shellHookEnabled = config.shellHook && getShellHistory().length > 0
|
|
78
|
+
|
|
79
|
+
const latestUserContext = buildChatUserContext(
|
|
80
|
+
prompt,
|
|
81
|
+
sysinfo,
|
|
82
|
+
plsHistory,
|
|
83
|
+
shellHistory,
|
|
84
|
+
shellHookEnabled
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
messages.push(latestUserContext)
|
|
83
88
|
|
|
89
|
+
// 4. 发送给 AI(流式或非流式)
|
|
84
90
|
let fullContent = ''
|
|
85
91
|
|
|
86
|
-
// 流式输出模式
|
|
87
92
|
if (options.onChunk) {
|
|
93
|
+
// 流式输出模式
|
|
88
94
|
const stream = await agent.stream(messages)
|
|
89
95
|
|
|
90
96
|
for await (const chunk of stream.textStream) {
|
|
@@ -103,19 +109,19 @@ export async function chatWithMastra(
|
|
|
103
109
|
throw new Error('AI 返回了空的响应')
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
//
|
|
112
|
+
// 5. 保存对话历史(只保存纯粹的问题和回答,不保存 XML)
|
|
107
113
|
addChatMessage(prompt, fullContent)
|
|
108
114
|
|
|
109
|
-
// 返回结果
|
|
115
|
+
// 6. 返回结果
|
|
110
116
|
if (options.debug) {
|
|
111
117
|
return {
|
|
112
118
|
reply: fullContent,
|
|
113
119
|
debug: {
|
|
114
|
-
sysinfo
|
|
120
|
+
sysinfo,
|
|
115
121
|
model: config.model,
|
|
116
|
-
systemPrompt:
|
|
122
|
+
systemPrompt: CHAT_SYSTEM_PROMPT,
|
|
117
123
|
chatHistory,
|
|
118
|
-
|
|
124
|
+
userContext: latestUserContext,
|
|
119
125
|
},
|
|
120
126
|
}
|
|
121
127
|
}
|
package/src/multi-step.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { createShellAgent } from './mastra-agent.js'
|
|
3
|
-
import {
|
|
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'
|
|
@@ -39,33 +39,17 @@ export interface RemoteContext {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* 获取静态 System Prompt(供 Mastra 使用)
|
|
43
43
|
*/
|
|
44
44
|
export function getFullSystemPrompt() {
|
|
45
|
-
|
|
46
|
-
const sysinfo = formatSystemInfo()
|
|
47
|
-
const plsHistory = formatHistoryForAI()
|
|
48
|
-
const shellHistory = formatShellHistoryForAI()
|
|
49
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0
|
|
50
|
-
|
|
51
|
-
return buildCommandSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
|
|
45
|
+
return SHELL_COMMAND_SYSTEM_PROMPT
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
/**
|
|
55
|
-
*
|
|
49
|
+
* 获取静态 System Prompt(远程执行也使用相同的 System Prompt)
|
|
56
50
|
*/
|
|
57
51
|
export function getRemoteFullSystemPrompt(remoteContext: RemoteContext) {
|
|
58
|
-
|
|
59
|
-
const sysinfo = formatRemoteSysInfoForAI(remoteContext.name, remoteContext.sysInfo)
|
|
60
|
-
|
|
61
|
-
// 格式化远程 pls 命令历史
|
|
62
|
-
const plsHistory = formatRemoteHistoryForAI(remoteContext.name)
|
|
63
|
-
|
|
64
|
-
// 格式化远程 shell 历史
|
|
65
|
-
const shellHistory = formatRemoteShellHistoryForAI(remoteContext.shellHistory)
|
|
66
|
-
const shellHookEnabled = remoteContext.shellHistory.length > 0
|
|
67
|
-
|
|
68
|
-
return buildCommandSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
|
|
52
|
+
return SHELL_COMMAND_SYSTEM_PROMPT
|
|
69
53
|
}
|
|
70
54
|
|
|
71
55
|
/**
|
|
@@ -78,26 +62,35 @@ export async function generateMultiStepCommand(
|
|
|
78
62
|
): Promise<{ stepData: CommandStep; debugInfo?: any }> {
|
|
79
63
|
const agent = createShellAgent()
|
|
80
64
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
// 准备动态数据
|
|
66
|
+
let sysinfoStr = ''
|
|
67
|
+
let historyStr = ''
|
|
68
|
+
|
|
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
|
+
}
|
|
85
83
|
|
|
86
|
-
//
|
|
87
|
-
const
|
|
84
|
+
// 构建包含所有动态数据的 User Prompt(XML 格式)
|
|
85
|
+
const userContextPrompt = buildUserContextPrompt(
|
|
86
|
+
userPrompt,
|
|
87
|
+
sysinfoStr,
|
|
88
|
+
historyStr,
|
|
89
|
+
previousSteps
|
|
90
|
+
)
|
|
88
91
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
messages.push(
|
|
92
|
-
JSON.stringify({
|
|
93
|
-
command: step.command,
|
|
94
|
-
continue: step.continue,
|
|
95
|
-
reasoning: step.reasoning,
|
|
96
|
-
nextStepHint: step.nextStepHint,
|
|
97
|
-
})
|
|
98
|
-
)
|
|
99
|
-
messages.push(`命令已执行\n退出码: ${step.exitCode}\n输出:\n${step.output.slice(0, 500)}`)
|
|
100
|
-
})
|
|
92
|
+
// 只发送一条 User Message
|
|
93
|
+
const messages = [userContextPrompt]
|
|
101
94
|
|
|
102
95
|
// 调用 Mastra Agent 生成结构化输出
|
|
103
96
|
const response = await agent.generate(messages, {
|
|
@@ -114,14 +107,16 @@ export async function generateMultiStepCommand(
|
|
|
114
107
|
return {
|
|
115
108
|
stepData,
|
|
116
109
|
debugInfo: {
|
|
117
|
-
|
|
118
|
-
userPrompt,
|
|
110
|
+
systemPrompt: SHELL_COMMAND_SYSTEM_PROMPT,
|
|
111
|
+
userPrompt: userContextPrompt,
|
|
119
112
|
previousStepsCount: previousSteps.length,
|
|
120
113
|
response: stepData,
|
|
121
|
-
remoteContext: options.remoteContext
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
remoteContext: options.remoteContext
|
|
115
|
+
? {
|
|
116
|
+
name: options.remoteContext.name,
|
|
117
|
+
sysInfo: options.remoteContext.sysInfo,
|
|
118
|
+
}
|
|
119
|
+
: undefined,
|
|
125
120
|
},
|
|
126
121
|
}
|
|
127
122
|
}
|