@muyichengshayu/promptx 0.2.8 → 0.2.9
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/CHANGELOG.md +7 -0
- package/apps/runner/src/engines/index.js +2 -0
- package/apps/runner/src/engines/kimiCodeRunner.js +561 -0
- package/apps/runner/src/runManager.js +6 -1
- package/apps/server/src/agentSessionDiscovery.js +136 -0
- package/apps/server/src/agentSessionDiscovery.test.js +59 -0
- package/apps/server/src/agents/index.js +2 -0
- package/apps/server/src/agents/kimiCodeRunner.js +565 -0
- package/apps/server/src/agents/kimiCodeRunner.test.js +127 -0
- package/apps/server/src/codexSessions.js +1 -1
- package/apps/server/src/runDispatchService.js +14 -2
- package/apps/web/dist/assets/{CodexSessionManagerDialog-B_F9ZWKy.js → CodexSessionManagerDialog-_qLljY7F.js} +1 -1
- package/apps/web/dist/assets/{TaskDiffReviewDialog-CPqGk_q2.js → TaskDiffReviewDialog-DpW8S8yT.js} +1 -1
- package/apps/web/dist/assets/{WorkbenchSettingsDialog-CWl81vlG.js → WorkbenchSettingsDialog-CYfh5G7c.js} +1 -1
- package/apps/web/dist/assets/WorkbenchView-A8nm0NH9.js +60 -0
- package/apps/web/dist/assets/index-DHF_zkYI.js +2 -0
- package/apps/web/dist/index.html +1 -1
- package/package.json +1 -1
- package/packages/shared/src/index.js +6 -0
- package/scripts/doctor.mjs +8 -0
- package/apps/web/dist/assets/WorkbenchView-gbRu02Lv.js +0 -60
- package/apps/web/dist/assets/index-5LxHpYf5.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.9
|
|
4
|
+
|
|
5
|
+
- 接入 `Kimi Code CLI` 执行引擎:工作台 Agent 选择器新增 Kimi Code 选项,支持本地会话发现、线程复用、TodoList 过程展示与停止控制,与现有 Codex / Claude Code / OpenCode 并列为第四类 Agent。
|
|
6
|
+
- 工作台 Agent 选择器与过滤状态持久化:切换任务后会自动恢复上次选中的 Agent 引擎和中栏过滤条件,不再每次回到默认值。
|
|
7
|
+
- runner 状态提示支持多语言:idle 进度提示(排队中、运行中、停止中)通过 `messageKey` 机制走前端 i18n 翻译,英文环境下显示对应英文文案。
|
|
8
|
+
- 修复图片上传地址在云主机部署下失效的问题:前端 `API_BASE` 改为运行时按 `window.location.origin` 计算,数据库中只存相对路径;同时兼容已存的老数据(`localhost` / `127.0.0.1` 绝对路径在显示时自动替换为当前 host),runner 收到的 prompt 仍会正确转换为本地地址。
|
|
9
|
+
|
|
3
10
|
## 0.2.8
|
|
4
11
|
|
|
5
12
|
- 修复 `Claude Code` 的 `TodoWrite` 过程映射:待办列表会被归一为结构化 `todo_list` 事件,正确展示待办内容、进行中与已完成状态,不再只暴露原始工具输入。
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
import { codexRunner } from './codexRunner.js'
|
|
8
8
|
import { claudeCodeRunner } from './claudeCodeRunner.js'
|
|
9
9
|
import { openCodeRunner } from './openCodeRunner.js'
|
|
10
|
+
import { kimiCodeRunner } from './kimiCodeRunner.js'
|
|
10
11
|
import { shellRunner } from './shellRunner.js'
|
|
11
12
|
|
|
12
13
|
const runnerRegistry = new Map([
|
|
13
14
|
[codexRunner.engine, codexRunner],
|
|
14
15
|
[claudeCodeRunner.engine, claudeCodeRunner],
|
|
15
16
|
[openCodeRunner.engine, openCodeRunner],
|
|
17
|
+
[kimiCodeRunner.engine, kimiCodeRunner],
|
|
16
18
|
[shellRunner.engine, shellRunner],
|
|
17
19
|
])
|
|
18
20
|
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { execFileSync, spawn } from 'node:child_process'
|
|
4
|
+
import {
|
|
5
|
+
AGENT_ENGINES,
|
|
6
|
+
AGENT_RUN_ITEM_TYPES,
|
|
7
|
+
createAgentEventEnvelopeEvent,
|
|
8
|
+
createCompletedEnvelopeEvent,
|
|
9
|
+
createItemCompletedEvent,
|
|
10
|
+
createItemStartedEvent,
|
|
11
|
+
createStatusEnvelopeEvent,
|
|
12
|
+
createStderrEnvelopeEvent,
|
|
13
|
+
createStdoutEnvelopeEvent,
|
|
14
|
+
createThreadStartedEvent,
|
|
15
|
+
createTurnCompletedEvent,
|
|
16
|
+
getAgentEngineLabel,
|
|
17
|
+
} from '../../../../packages/shared/src/index.js'
|
|
18
|
+
import { createManagedSpawnOptions, forceStopChildProcess } from '../processControl.js'
|
|
19
|
+
|
|
20
|
+
const KIMI_CODE_BIN = process.env.KIMI_CODE_BIN || 'kimi'
|
|
21
|
+
const RESOLVED_KIMI_CODE_BIN = resolveKimiCodeBinary()
|
|
22
|
+
|
|
23
|
+
function resolveKimiCodeBinary() {
|
|
24
|
+
if (process.platform !== 'win32') {
|
|
25
|
+
return KIMI_CODE_BIN
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (path.extname(KIMI_CODE_BIN)) {
|
|
29
|
+
return KIMI_CODE_BIN
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (fs.existsSync(`${KIMI_CODE_BIN}.cmd`)) {
|
|
33
|
+
return `${KIMI_CODE_BIN}.cmd`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (fs.existsSync(`${KIMI_CODE_BIN}.bat`)) {
|
|
37
|
+
return `${KIMI_CODE_BIN}.bat`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(KIMI_CODE_BIN)) {
|
|
41
|
+
return KIMI_CODE_BIN
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const output = execFileSync('where.exe', [KIMI_CODE_BIN], {
|
|
46
|
+
encoding: 'utf8',
|
|
47
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
48
|
+
windowsHide: true,
|
|
49
|
+
}).trim()
|
|
50
|
+
|
|
51
|
+
if (!output) {
|
|
52
|
+
return KIMI_CODE_BIN
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const candidates = output
|
|
56
|
+
.split(/\r?\n/g)
|
|
57
|
+
.map((line) => line.trim())
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
|
|
60
|
+
return candidates.find((item) => /\.(cmd|bat)$/i.test(item))
|
|
61
|
+
|| candidates.find((item) => /\.(exe|com)$/i.test(item))
|
|
62
|
+
|| candidates[0]
|
|
63
|
+
|| KIMI_CODE_BIN
|
|
64
|
+
} catch {
|
|
65
|
+
return KIMI_CODE_BIN
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createKimiSpawn(commandArgs = [], cwd = '') {
|
|
70
|
+
const options = createManagedSpawnOptions({
|
|
71
|
+
cwd,
|
|
72
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if (process.platform === 'win32' && /\.(cmd|bat)$/i.test(RESOLVED_KIMI_CODE_BIN)) {
|
|
76
|
+
return spawn(
|
|
77
|
+
process.env.ComSpec || 'cmd.exe',
|
|
78
|
+
['/d', '/s', '/c', RESOLVED_KIMI_CODE_BIN, ...commandArgs],
|
|
79
|
+
options
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return spawn(RESOLVED_KIMI_CODE_BIN, commandArgs, options)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeSpawnError(error) {
|
|
87
|
+
if (error?.code === 'ENOENT') {
|
|
88
|
+
const attempted = RESOLVED_KIMI_CODE_BIN === KIMI_CODE_BIN
|
|
89
|
+
? KIMI_CODE_BIN
|
|
90
|
+
: `${KIMI_CODE_BIN} -> ${RESOLVED_KIMI_CODE_BIN}`
|
|
91
|
+
return new Error(
|
|
92
|
+
`找不到 Kimi Code CLI(尝试执行:${attempted})。请先确认终端里可以运行 \`kimi --version\`,或设置环境变量 \`KIMI_CODE_BIN\`。`
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return error
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseJsonLine(line = '') {
|
|
100
|
+
const text = String(line || '').trim()
|
|
101
|
+
if (!text) {
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(text)
|
|
107
|
+
} catch {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function splitBufferedLines(buffer = '') {
|
|
113
|
+
const text = String(buffer || '')
|
|
114
|
+
if (!text) {
|
|
115
|
+
return { lines: [], rest: '' }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const normalized = text.replace(/\r\n/g, '\n')
|
|
119
|
+
const parts = normalized.split('\n')
|
|
120
|
+
const rest = parts.pop() || ''
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
lines: parts.map((line) => line.trim()).filter(Boolean),
|
|
124
|
+
rest,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function flushBufferedText(buffer = '') {
|
|
129
|
+
const { lines, rest } = splitBufferedLines(buffer)
|
|
130
|
+
const tail = String(rest || '').trim()
|
|
131
|
+
return tail ? [...lines, tail] : lines
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function stringifyKimiToolResultContent(value) {
|
|
135
|
+
if (typeof value === 'string') {
|
|
136
|
+
return value.trim()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (value == null) {
|
|
140
|
+
return ''
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
const parts = []
|
|
145
|
+
for (const item of value) {
|
|
146
|
+
if (item && typeof item === 'object') {
|
|
147
|
+
const text = String(item.text || '').trim()
|
|
148
|
+
if (text) parts.push(text)
|
|
149
|
+
} else if (typeof item === 'string') {
|
|
150
|
+
const text = item.trim()
|
|
151
|
+
if (text) parts.push(text)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return parts.join('\n').trim()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const compact = JSON.stringify(value)
|
|
159
|
+
return compact.length <= 12000 ? compact : `${compact.slice(0, 11997)}...`
|
|
160
|
+
} catch {
|
|
161
|
+
return String(value || '').trim()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function buildKimiToolCommand(name = '', input = {}) {
|
|
166
|
+
const toolName = String(name || 'Kimi tool').trim() || 'Kimi tool'
|
|
167
|
+
if (!input || typeof input !== 'object') {
|
|
168
|
+
return toolName
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const command = String(input.command || '').trim()
|
|
172
|
+
if (command) {
|
|
173
|
+
return `${toolName}: ${command}`
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const singleValueKeys = ['file_path', 'path', 'pattern', 'query', 'url', 'description']
|
|
177
|
+
for (const key of singleValueKeys) {
|
|
178
|
+
const value = String(input[key] || '').trim()
|
|
179
|
+
if (value) {
|
|
180
|
+
return `${toolName}: ${value}`
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const compact = JSON.stringify(input)
|
|
186
|
+
return compact.length <= 240 ? `${toolName}: ${compact}` : `${toolName}: ${compact.slice(0, 237)}...`
|
|
187
|
+
} catch {
|
|
188
|
+
return toolName
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isKimiTodoToolName(name = '') {
|
|
193
|
+
return String(name || '').trim().toLowerCase() === 'settodolist'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function normalizeKimiTodoStatus(status = '') {
|
|
197
|
+
const normalized = String(status || '').trim().toLowerCase()
|
|
198
|
+
if (normalized === 'done') {
|
|
199
|
+
return 'completed'
|
|
200
|
+
}
|
|
201
|
+
if (normalized === 'in_progress') {
|
|
202
|
+
return 'in_progress'
|
|
203
|
+
}
|
|
204
|
+
return 'pending'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeKimiTodoItems(items = []) {
|
|
208
|
+
return (Array.isArray(items) ? items : [])
|
|
209
|
+
.map((entry) => {
|
|
210
|
+
if (!entry || typeof entry !== 'object') {
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const status = normalizeKimiTodoStatus(entry.status)
|
|
215
|
+
const text = String(entry.title || entry.text || '').trim()
|
|
216
|
+
if (!text) {
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
text,
|
|
222
|
+
status,
|
|
223
|
+
completed: status === 'completed',
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function extractKimiSessionIdFromStderrLine(line = '') {
|
|
230
|
+
const match = String(line || '').match(/To resume this session:\s*kimi\s+(?:-r|--session|--resume)\s+([a-f0-9-]+)/i)
|
|
231
|
+
return match?.[1] ? String(match[1]).trim() : ''
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function isKimiInfoStderrLine(line = '') {
|
|
235
|
+
const text = String(line || '').trim()
|
|
236
|
+
if (!text) {
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
if (/^To resume this session:/i.test(text)) {
|
|
240
|
+
return true
|
|
241
|
+
}
|
|
242
|
+
if (/^Shell cwd was reset to /i.test(text)) {
|
|
243
|
+
return true
|
|
244
|
+
}
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function createKimiNormalizationState() {
|
|
249
|
+
return {
|
|
250
|
+
turnStarted: false,
|
|
251
|
+
toolUses: new Map(),
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function normalizeKimiEvents(event = {}, state = createKimiNormalizationState()) {
|
|
256
|
+
const role = String(event?.role || '').trim().toLowerCase()
|
|
257
|
+
const normalizedEvents = []
|
|
258
|
+
|
|
259
|
+
if (role === 'assistant') {
|
|
260
|
+
if (!state.turnStarted) {
|
|
261
|
+
state.turnStarted = true
|
|
262
|
+
normalizedEvents.push({ type: 'turn.started' })
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const content = Array.isArray(event.content) ? event.content : []
|
|
266
|
+
content.forEach((block) => {
|
|
267
|
+
const blockType = String(block?.type || '').trim().toLowerCase()
|
|
268
|
+
if (blockType === 'think') {
|
|
269
|
+
const text = String(block?.think || '').trim()
|
|
270
|
+
if (text) {
|
|
271
|
+
normalizedEvents.push({
|
|
272
|
+
...createItemStartedEvent({
|
|
273
|
+
type: AGENT_RUN_ITEM_TYPES.REASONING,
|
|
274
|
+
text,
|
|
275
|
+
}),
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (blockType === 'text') {
|
|
282
|
+
const text = String(block?.text || '').trim()
|
|
283
|
+
if (text) {
|
|
284
|
+
normalizedEvents.push({
|
|
285
|
+
...createItemCompletedEvent({
|
|
286
|
+
type: AGENT_RUN_ITEM_TYPES.AGENT_MESSAGE,
|
|
287
|
+
text,
|
|
288
|
+
}),
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const toolCalls = Array.isArray(event.tool_calls) ? event.tool_calls : []
|
|
296
|
+
toolCalls.forEach((toolCall) => {
|
|
297
|
+
const toolUseId = String(toolCall?.id || '').trim()
|
|
298
|
+
const name = String(toolCall?.function?.name || toolCall?.name || 'Kimi tool').trim() || 'Kimi tool'
|
|
299
|
+
const argsText = toolCall?.function?.arguments || toolCall?.arguments || '{}'
|
|
300
|
+
let parsedArgs = {}
|
|
301
|
+
try {
|
|
302
|
+
parsedArgs = JSON.parse(argsText)
|
|
303
|
+
} catch {
|
|
304
|
+
parsedArgs = {}
|
|
305
|
+
}
|
|
306
|
+
const command = buildKimiToolCommand(name, parsedArgs)
|
|
307
|
+
const isTodoTool = isKimiTodoToolName(name)
|
|
308
|
+
const todoItems = normalizeKimiTodoItems(parsedArgs.todos)
|
|
309
|
+
|
|
310
|
+
if (toolUseId) {
|
|
311
|
+
state.toolUses.set(toolUseId, {
|
|
312
|
+
name,
|
|
313
|
+
command,
|
|
314
|
+
kind: isTodoTool ? 'todo' : 'command',
|
|
315
|
+
todoItems,
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (isTodoTool) {
|
|
320
|
+
normalizedEvents.push({
|
|
321
|
+
...createItemStartedEvent({
|
|
322
|
+
type: AGENT_RUN_ITEM_TYPES.TODO_LIST,
|
|
323
|
+
items: todoItems,
|
|
324
|
+
}),
|
|
325
|
+
})
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
normalizedEvents.push({
|
|
330
|
+
...createItemStartedEvent({
|
|
331
|
+
type: AGENT_RUN_ITEM_TYPES.COMMAND_EXECUTION,
|
|
332
|
+
command,
|
|
333
|
+
status: 'in_progress',
|
|
334
|
+
}),
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
return normalizedEvents
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (role === 'tool') {
|
|
342
|
+
const toolCallId = String(event?.tool_call_id || '').trim()
|
|
343
|
+
const remembered = toolCallId ? state.toolUses.get(toolCallId) : null
|
|
344
|
+
const output = stringifyKimiToolResultContent(event?.content)
|
|
345
|
+
const todoTool = remembered?.kind === 'todo'
|
|
346
|
+
|
|
347
|
+
if (toolCallId) {
|
|
348
|
+
state.toolUses.delete(toolCallId)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (todoTool) {
|
|
352
|
+
normalizedEvents.push({
|
|
353
|
+
...createItemCompletedEvent({
|
|
354
|
+
type: AGENT_RUN_ITEM_TYPES.TODO_LIST,
|
|
355
|
+
items: Array.isArray(remembered?.todoItems) ? remembered.todoItems : [],
|
|
356
|
+
}),
|
|
357
|
+
})
|
|
358
|
+
return normalizedEvents
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
normalizedEvents.push({
|
|
362
|
+
...createItemCompletedEvent({
|
|
363
|
+
type: AGENT_RUN_ITEM_TYPES.COMMAND_EXECUTION,
|
|
364
|
+
command: remembered?.command || remembered?.name || 'Kimi tool',
|
|
365
|
+
status: 'completed',
|
|
366
|
+
exit_code: 0,
|
|
367
|
+
aggregated_output: output,
|
|
368
|
+
}),
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
return normalizedEvents
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return [{
|
|
375
|
+
type: `kimi.${role || 'event'}`,
|
|
376
|
+
detail: stringifyKimiToolResultContent(event?.content),
|
|
377
|
+
}]
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function normalizeKimiEvent(event = {}, state = createKimiNormalizationState()) {
|
|
381
|
+
return normalizeKimiEvents(event, state)[0] || null
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function createExecArgs(session, prompt) {
|
|
385
|
+
const args = [
|
|
386
|
+
'--print',
|
|
387
|
+
'--output-format', 'stream-json',
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
const sessionId = String(session?.engineSessionId || session?.engineThreadId || session?.codexThreadId || '').trim()
|
|
391
|
+
if (sessionId) {
|
|
392
|
+
args.push('--session', sessionId)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (session?.cwd) {
|
|
396
|
+
args.push('--work-dir', session.cwd)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
args.push('-p', String(prompt || ''))
|
|
400
|
+
|
|
401
|
+
return args
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function createKimiRunStatusEvent(session = {}) {
|
|
405
|
+
const hasExistingThread = Boolean(
|
|
406
|
+
String(session?.engineSessionId || session?.engineThreadId || session?.codexThreadId || '').trim()
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
return createStatusEnvelopeEvent({
|
|
410
|
+
stage: hasExistingThread ? 'resuming' : 'starting',
|
|
411
|
+
message: hasExistingThread
|
|
412
|
+
? '已连接 PromptX 项目,正在继续这轮执行。'
|
|
413
|
+
: '已创建 PromptX 项目,正在启动第一轮执行。',
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function streamPromptToKimiCodeSession(sessionInput, prompt, callbacks = {}) {
|
|
418
|
+
const session = sessionInput && typeof sessionInput === 'object' ? sessionInput : null
|
|
419
|
+
const normalizedPrompt = String(prompt || '').trim()
|
|
420
|
+
|
|
421
|
+
if (!session?.id || !session?.cwd) {
|
|
422
|
+
throw new Error('缺少 PromptX 项目。')
|
|
423
|
+
}
|
|
424
|
+
if (!normalizedPrompt) {
|
|
425
|
+
throw new Error('没有可发送的提示词。')
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const onEvent = typeof callbacks.onEvent === 'function' ? callbacks.onEvent : () => {}
|
|
429
|
+
const onThreadStarted = typeof callbacks.onThreadStarted === 'function' ? callbacks.onThreadStarted : () => {}
|
|
430
|
+
|
|
431
|
+
const child = createKimiSpawn(createExecArgs(session, normalizedPrompt), session.cwd)
|
|
432
|
+
onEvent(createKimiRunStatusEvent(session))
|
|
433
|
+
|
|
434
|
+
let stdoutBuffer = ''
|
|
435
|
+
let stderrBuffer = ''
|
|
436
|
+
let lastStderrLine = ''
|
|
437
|
+
let finalMessage = ''
|
|
438
|
+
let finalSessionId = String(session.engineSessionId || session.engineThreadId || session.codexThreadId || '').trim()
|
|
439
|
+
const normalizationState = createKimiNormalizationState()
|
|
440
|
+
|
|
441
|
+
const rememberSessionId = (sessionId) => {
|
|
442
|
+
const value = String(sessionId || '').trim()
|
|
443
|
+
if (!value || value === finalSessionId) {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
finalSessionId = value
|
|
448
|
+
onThreadStarted(value)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const emitKimiJsonLine = (line) => {
|
|
452
|
+
const event = parseJsonLine(line)
|
|
453
|
+
if (!event) {
|
|
454
|
+
onEvent(createStdoutEnvelopeEvent(line))
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const normalizedEvents = normalizeKimiEvents(event, normalizationState)
|
|
459
|
+
normalizedEvents.forEach((normalizedEvent) => {
|
|
460
|
+
onEvent(createAgentEventEnvelopeEvent(normalizedEvent))
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
const role = String(event?.role || '').trim().toLowerCase()
|
|
464
|
+
if (role === 'assistant') {
|
|
465
|
+
const content = Array.isArray(event.content) ? event.content : []
|
|
466
|
+
const textBlocks = content
|
|
467
|
+
.filter((block) => String(block?.type || '').trim().toLowerCase() === 'text')
|
|
468
|
+
.map((block) => String(block?.text || '').trim())
|
|
469
|
+
.filter(Boolean)
|
|
470
|
+
|
|
471
|
+
if (textBlocks.length) {
|
|
472
|
+
const text = textBlocks.join('\n').trim()
|
|
473
|
+
finalMessage = `${finalMessage}${finalMessage ? '\n' : ''}${text}`
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
child.stdout.on('data', (chunk) => {
|
|
479
|
+
stdoutBuffer += chunk.toString()
|
|
480
|
+
const { lines, rest } = splitBufferedLines(stdoutBuffer)
|
|
481
|
+
stdoutBuffer = rest
|
|
482
|
+
lines.forEach(emitKimiJsonLine)
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
child.stderr.on('data', (chunk) => {
|
|
486
|
+
stderrBuffer += chunk.toString()
|
|
487
|
+
const { lines, rest } = splitBufferedLines(stderrBuffer)
|
|
488
|
+
stderrBuffer = rest
|
|
489
|
+
lines.forEach((line) => {
|
|
490
|
+
const sessionId = extractKimiSessionIdFromStderrLine(line)
|
|
491
|
+
if (sessionId) {
|
|
492
|
+
rememberSessionId(sessionId)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (isKimiInfoStderrLine(line)) {
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
lastStderrLine = line
|
|
500
|
+
onEvent(createStderrEnvelopeEvent(line))
|
|
501
|
+
})
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
const result = new Promise((resolve, reject) => {
|
|
505
|
+
child.on('error', (error) => {
|
|
506
|
+
reject(normalizeSpawnError(error))
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
child.on('close', (code) => {
|
|
510
|
+
flushBufferedText(stdoutBuffer).forEach(emitKimiJsonLine)
|
|
511
|
+
flushBufferedText(stderrBuffer).forEach((line) => {
|
|
512
|
+
const sessionId = extractKimiSessionIdFromStderrLine(line)
|
|
513
|
+
if (sessionId) {
|
|
514
|
+
rememberSessionId(sessionId)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (isKimiInfoStderrLine(line)) {
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
lastStderrLine = line
|
|
522
|
+
onEvent(createStderrEnvelopeEvent(line))
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
if (code !== 0) {
|
|
526
|
+
reject(new Error(lastStderrLine || 'Kimi Code 执行失败。'))
|
|
527
|
+
return
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const message = finalMessage.trim()
|
|
531
|
+
onEvent(createAgentEventEnvelopeEvent(createTurnCompletedEvent()))
|
|
532
|
+
onEvent(createCompletedEnvelopeEvent(message))
|
|
533
|
+
|
|
534
|
+
resolve({
|
|
535
|
+
sessionId: session.id,
|
|
536
|
+
threadId: finalSessionId,
|
|
537
|
+
message,
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
child,
|
|
544
|
+
result,
|
|
545
|
+
cancel(options = {}) {
|
|
546
|
+
forceStopChildProcess(child, options)
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export const kimiCodeRunner = {
|
|
552
|
+
engine: AGENT_ENGINES.KIMI_CODE,
|
|
553
|
+
label: getAgentEngineLabel(AGENT_ENGINES.KIMI_CODE),
|
|
554
|
+
supportsWorkspaceHistory: false,
|
|
555
|
+
listKnownWorkspaces() {
|
|
556
|
+
return []
|
|
557
|
+
},
|
|
558
|
+
streamSessionPrompt(session, prompt, callbacks = {}) {
|
|
559
|
+
return streamPromptToKimiCodeSession(session, prompt, callbacks)
|
|
560
|
+
},
|
|
561
|
+
}
|
|
@@ -216,6 +216,7 @@ function createIdleProgressEvent(context = {}) {
|
|
|
216
216
|
return createStatusEnvelopeEvent({
|
|
217
217
|
stage: 'queued',
|
|
218
218
|
message: '当前仍在排队,等待 runner 空闲后开始执行。',
|
|
219
|
+
messageKey: 'runner.status.queued',
|
|
219
220
|
})
|
|
220
221
|
}
|
|
221
222
|
|
|
@@ -223,12 +224,16 @@ function createIdleProgressEvent(context = {}) {
|
|
|
223
224
|
return createStatusEnvelopeEvent({
|
|
224
225
|
stage: 'stopping',
|
|
225
226
|
message: '正在停止执行,等待引擎退出...',
|
|
227
|
+
messageKey: 'runner.status.stopping',
|
|
226
228
|
})
|
|
227
229
|
}
|
|
228
230
|
|
|
231
|
+
const agentLabel = getAgentEngineLabel(context.engine || 'codex')
|
|
229
232
|
return createStatusEnvelopeEvent({
|
|
230
233
|
stage: 'running',
|
|
231
|
-
message: `${
|
|
234
|
+
message: `${agentLabel} 正在思考中...`,
|
|
235
|
+
messageKey: 'runner.status.thinking',
|
|
236
|
+
messageParams: { agentLabel },
|
|
232
237
|
})
|
|
233
238
|
}
|
|
234
239
|
|