@muyichengshayu/promptx 0.2.7 → 0.2.8
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 +5 -0
- package/apps/runner/src/engines/claudeCodeRunner.js +69 -1
- package/apps/runner/src/engines/claudeCodeRunner.test.js +279 -0
- package/apps/runner/src/engines/openCodeRunner.test.js +73 -0
- package/apps/runner/src/engines/shellRunner.test.js +46 -0
- package/apps/runner/src/runManager.js +110 -11
- package/apps/runner/src/runManager.test.js +913 -0
- package/apps/runner/src/serverClient.test.js +93 -0
- package/apps/server/src/agentSessionDiscovery.test.js +127 -0
- package/apps/server/src/agents/claudeCodeRunner.test.js +433 -0
- package/apps/server/src/agents/openCodeRunner.test.js +236 -0
- package/apps/server/src/agents/runnerContract.test.js +382 -0
- package/apps/server/src/appPaths.test.js +52 -0
- package/apps/server/src/assetRoutes.test.js +168 -0
- package/apps/server/src/codex.test.js +518 -0
- package/apps/server/src/codexRoutes.test.js +376 -0
- package/apps/server/src/codexRuns.test.js +160 -0
- package/apps/server/src/codexSessions.test.js +369 -0
- package/apps/server/src/db.test.js +182 -0
- package/apps/server/src/gitDiff.test.js +542 -0
- package/apps/server/src/gitDiffClient.test.js +140 -0
- package/apps/server/src/internalRoutes.test.js +134 -0
- package/apps/server/src/maintenance.test.js +154 -0
- package/apps/server/src/processControl.test.js +147 -0
- package/apps/server/src/relayClient.test.js +478 -0
- package/apps/server/src/relayConfig.test.js +73 -0
- package/apps/server/src/relayProtocol.test.js +49 -0
- package/apps/server/src/relayServer.test.js +798 -0
- package/apps/server/src/relayTenants.test.js +137 -0
- package/apps/server/src/relayUsageStore.test.js +65 -0
- package/apps/server/src/repository.test.js +150 -0
- package/apps/server/src/runDispatchService.test.js +563 -0
- package/apps/server/src/runEventIngest.test.js +225 -0
- package/apps/server/src/runRecovery.test.js +73 -0
- package/apps/server/src/runnerClient.test.js +80 -0
- package/apps/server/src/runnerDispatch.test.js +136 -0
- package/apps/server/src/systemConfig.test.js +112 -0
- package/apps/server/src/systemRoutes.test.js +319 -0
- package/apps/server/src/taskRoutes.test.js +726 -0
- package/apps/server/src/upload.test.js +30 -0
- package/apps/server/src/webAppRoutes.test.js +67 -0
- package/apps/server/src/workspaceFiles.test.js +262 -0
- package/package.json +14 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.8
|
|
4
|
+
|
|
5
|
+
- 修复 `Claude Code` 的 `TodoWrite` 过程映射:待办列表会被归一为结构化 `todo_list` 事件,正确展示待办内容、进行中与已完成状态,不再只暴露原始工具输入。
|
|
6
|
+
- 优化 runner 长时间静默时的可见反馈:排队、运行中和停止中的任务会周期性补发状态进度事件,避免前端看起来像卡住,同时保持心跳与并发队列诊断信息一致。
|
|
7
|
+
|
|
3
8
|
## 0.2.7
|
|
4
9
|
|
|
5
10
|
- 新增 `Aqua Classic` 主题:以复古 macOS Aqua / Snow Leopard 的银灰工具窗口为方向,补齐工作台、设置弹窗、按钮、输入框、消息卡片、确认删除等核心区域的主题 token 与专属样式。
|
|
@@ -171,6 +171,52 @@ function isClaudeCollabToolName(name = '') {
|
|
|
171
171
|
return ['agent', 'task'].includes(String(name || '').trim().toLowerCase())
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
function isClaudeTodoToolName(name = '') {
|
|
175
|
+
return String(name || '').trim().toLowerCase() === 'todowrite'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function normalizeClaudeTodoStatus(status = '') {
|
|
179
|
+
const normalized = String(status || '').trim().toLowerCase()
|
|
180
|
+
if (['completed', 'complete', 'done', 'checked'].includes(normalized)) {
|
|
181
|
+
return 'completed'
|
|
182
|
+
}
|
|
183
|
+
if (['in_progress', 'in-progress', 'active', 'doing', 'running'].includes(normalized)) {
|
|
184
|
+
return 'in_progress'
|
|
185
|
+
}
|
|
186
|
+
return 'pending'
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function normalizeClaudeTodoText(entry = {}, status = 'pending') {
|
|
190
|
+
const content = String(entry?.content || entry?.text || entry?.title || entry?.label || '').trim()
|
|
191
|
+
const activeForm = String(entry?.activeForm || '').trim()
|
|
192
|
+
if (status === 'in_progress' && activeForm) {
|
|
193
|
+
return activeForm
|
|
194
|
+
}
|
|
195
|
+
return content || activeForm
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeClaudeTodoItems(items = []) {
|
|
199
|
+
return (Array.isArray(items) ? items : [])
|
|
200
|
+
.map((entry) => {
|
|
201
|
+
if (!entry || typeof entry !== 'object') {
|
|
202
|
+
return null
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const status = normalizeClaudeTodoStatus(entry.status)
|
|
206
|
+
const text = normalizeClaudeTodoText(entry, status)
|
|
207
|
+
if (!text) {
|
|
208
|
+
return null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
text,
|
|
213
|
+
status,
|
|
214
|
+
completed: status === 'completed',
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
.filter(Boolean)
|
|
218
|
+
}
|
|
219
|
+
|
|
174
220
|
function collectTextParts(value, parts = []) {
|
|
175
221
|
if (!value) {
|
|
176
222
|
return parts
|
|
@@ -333,23 +379,26 @@ function createClaudeToolUseEvent(block = {}, state = createClaudeNormalizationS
|
|
|
333
379
|
const input = block?.input && typeof block.input === 'object' ? block.input : {}
|
|
334
380
|
const command = buildClaudeToolCommand(name, input)
|
|
335
381
|
const isCollabTool = isClaudeCollabToolName(name)
|
|
382
|
+
const isTodoTool = isClaudeTodoToolName(name)
|
|
336
383
|
const collabPrompt = String(input.prompt || '').trim()
|
|
337
384
|
const collabDescription = String(input.description || '').trim()
|
|
338
385
|
const collabRole = String(input.subagent_type || input.agent || '').trim()
|
|
339
386
|
const collabModel = String(input.model || '').trim()
|
|
340
387
|
const collabTarget = extractAgentTargetFromTexts(collabDescription, collabPrompt)
|
|
388
|
+
const todoItems = normalizeClaudeTodoItems(input.todos)
|
|
341
389
|
|
|
342
390
|
if (toolUseId) {
|
|
343
391
|
state.toolUses.set(toolUseId, {
|
|
344
392
|
name,
|
|
345
393
|
command,
|
|
346
|
-
kind: isCollabTool ? 'collab' : 'command',
|
|
394
|
+
kind: isCollabTool ? 'collab' : (isTodoTool ? 'todo' : 'command'),
|
|
347
395
|
prompt: collabPrompt || collabDescription,
|
|
348
396
|
description: collabDescription,
|
|
349
397
|
role: collabRole,
|
|
350
398
|
model: collabModel,
|
|
351
399
|
target: collabTarget,
|
|
352
400
|
taskIds: [],
|
|
401
|
+
todoItems,
|
|
353
402
|
})
|
|
354
403
|
}
|
|
355
404
|
|
|
@@ -365,6 +414,15 @@ function createClaudeToolUseEvent(block = {}, state = createClaudeNormalizationS
|
|
|
365
414
|
}
|
|
366
415
|
}
|
|
367
416
|
|
|
417
|
+
if (isTodoTool) {
|
|
418
|
+
return {
|
|
419
|
+
...createItemStartedEvent({
|
|
420
|
+
type: AGENT_RUN_ITEM_TYPES.TODO_LIST,
|
|
421
|
+
items: todoItems,
|
|
422
|
+
}),
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
368
426
|
return {
|
|
369
427
|
...createItemStartedEvent({
|
|
370
428
|
type: AGENT_RUN_ITEM_TYPES.COMMAND_EXECUTION,
|
|
@@ -381,6 +439,7 @@ function createClaudeToolResultEvent(block = {}, state = createClaudeNormalizati
|
|
|
381
439
|
const isError = Boolean(block?.is_error)
|
|
382
440
|
const taskIds = Array.isArray(remembered?.taskIds) ? remembered.taskIds.filter(Boolean) : []
|
|
383
441
|
const collabTool = remembered?.kind === 'collab'
|
|
442
|
+
const todoTool = remembered?.kind === 'todo'
|
|
384
443
|
|
|
385
444
|
if (toolUseId && state.completedCollabToolUseIds.has(toolUseId)) {
|
|
386
445
|
return null
|
|
@@ -419,6 +478,15 @@ function createClaudeToolResultEvent(block = {}, state = createClaudeNormalizati
|
|
|
419
478
|
}
|
|
420
479
|
}
|
|
421
480
|
|
|
481
|
+
if (todoTool) {
|
|
482
|
+
return {
|
|
483
|
+
...createItemCompletedEvent({
|
|
484
|
+
type: AGENT_RUN_ITEM_TYPES.TODO_LIST,
|
|
485
|
+
items: Array.isArray(remembered?.todoItems) ? remembered.todoItems : [],
|
|
486
|
+
}),
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
422
490
|
return {
|
|
423
491
|
...createItemCompletedEvent({
|
|
424
492
|
type: AGENT_RUN_ITEM_TYPES.COMMAND_EXECUTION,
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { createClaudeNormalizationState, normalizeClaudeEvents } from './claudeCodeRunner.js'
|
|
5
|
+
|
|
6
|
+
test('runner claudeCodeRunner maps fatal auth api_retry to error event', () => {
|
|
7
|
+
assert.deepEqual(
|
|
8
|
+
normalizeClaudeEvents({
|
|
9
|
+
type: 'system',
|
|
10
|
+
subtype: 'api_retry',
|
|
11
|
+
attempt: 1,
|
|
12
|
+
max_retries: 10,
|
|
13
|
+
error_status: 401,
|
|
14
|
+
error: 'authentication_failed',
|
|
15
|
+
}),
|
|
16
|
+
[{
|
|
17
|
+
type: 'error',
|
|
18
|
+
message: 'Claude Code 认证失败(HTTP 401 authentication_failed)。请重新登录 Claude Code,或检查当前环境中的认证令牌配置。',
|
|
19
|
+
}]
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('runner claudeCodeRunner maps transient api_retry to reconnecting error event', () => {
|
|
24
|
+
assert.deepEqual(
|
|
25
|
+
normalizeClaudeEvents({
|
|
26
|
+
type: 'system',
|
|
27
|
+
subtype: 'api_retry',
|
|
28
|
+
attempt: 2,
|
|
29
|
+
max_retries: 10,
|
|
30
|
+
error_status: 503,
|
|
31
|
+
error: 'overloaded',
|
|
32
|
+
}),
|
|
33
|
+
[{
|
|
34
|
+
type: 'error',
|
|
35
|
+
message: 'Reconnecting... 2/10 (HTTP 503 overloaded)',
|
|
36
|
+
}]
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('runner claudeCodeRunner maps Agent sub-agents into collaboration events', () => {
|
|
41
|
+
const state = createClaudeNormalizationState()
|
|
42
|
+
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
normalizeClaudeEvents({
|
|
45
|
+
type: 'assistant',
|
|
46
|
+
message: {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: 'tool_use',
|
|
50
|
+
id: 'agent-tool-1',
|
|
51
|
+
name: 'Agent',
|
|
52
|
+
input: {
|
|
53
|
+
description: 'Analyze a.js exports',
|
|
54
|
+
subagent_type: 'general-purpose',
|
|
55
|
+
prompt: 'Analyze a.js in the current directory.',
|
|
56
|
+
model: 'sonnet',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
}, state),
|
|
62
|
+
[{
|
|
63
|
+
type: 'item.started',
|
|
64
|
+
item: {
|
|
65
|
+
type: 'collab_tool_call',
|
|
66
|
+
tool: 'spawn_agent',
|
|
67
|
+
receiver_thread_ids: [],
|
|
68
|
+
prompt: 'Analyze a.js in the current directory.',
|
|
69
|
+
agents_states: {},
|
|
70
|
+
},
|
|
71
|
+
}]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
assert.deepEqual(
|
|
75
|
+
normalizeClaudeEvents({
|
|
76
|
+
type: 'system',
|
|
77
|
+
subtype: 'task_started',
|
|
78
|
+
tool_use_id: 'agent-tool-1',
|
|
79
|
+
task_id: 'task-a',
|
|
80
|
+
description: 'Analyze a.js exports',
|
|
81
|
+
}, state),
|
|
82
|
+
[{
|
|
83
|
+
type: 'item.completed',
|
|
84
|
+
item: {
|
|
85
|
+
type: 'collab_tool_call',
|
|
86
|
+
tool: 'spawn_agent',
|
|
87
|
+
receiver_thread_ids: ['task-a'],
|
|
88
|
+
prompt: 'Analyze a.js in the current directory.',
|
|
89
|
+
agents_states: {
|
|
90
|
+
'task-a': {
|
|
91
|
+
status: 'running',
|
|
92
|
+
message: '',
|
|
93
|
+
title: 'Analyze a.js exports',
|
|
94
|
+
role: 'general-purpose',
|
|
95
|
+
target: 'a.js',
|
|
96
|
+
model: 'sonnet',
|
|
97
|
+
task_id: 'task-a',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}]
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('runner claudeCodeRunner maps task_completed and ignores duplicate tool_result for Agent sub-agents', () => {
|
|
106
|
+
const state = createClaudeNormalizationState()
|
|
107
|
+
|
|
108
|
+
normalizeClaudeEvents({
|
|
109
|
+
type: 'assistant',
|
|
110
|
+
message: {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: 'tool_use',
|
|
114
|
+
id: 'agent-tool-2',
|
|
115
|
+
name: 'Agent',
|
|
116
|
+
input: {
|
|
117
|
+
description: 'Analyze b.js exports',
|
|
118
|
+
subagent_type: 'general-purpose',
|
|
119
|
+
prompt: 'Analyze b.js in the current directory.',
|
|
120
|
+
model: 'sonnet',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
}, state)
|
|
126
|
+
|
|
127
|
+
normalizeClaudeEvents({
|
|
128
|
+
type: 'system',
|
|
129
|
+
subtype: 'task_started',
|
|
130
|
+
tool_use_id: 'agent-tool-2',
|
|
131
|
+
task_id: 'task-b',
|
|
132
|
+
description: 'Analyze b.js exports',
|
|
133
|
+
}, state)
|
|
134
|
+
|
|
135
|
+
assert.deepEqual(
|
|
136
|
+
normalizeClaudeEvents({
|
|
137
|
+
type: 'system',
|
|
138
|
+
subtype: 'task_completed',
|
|
139
|
+
task_id: 'task-b',
|
|
140
|
+
result: 'found 2 exports',
|
|
141
|
+
description: 'Analyze b.js exports',
|
|
142
|
+
}, state),
|
|
143
|
+
[{
|
|
144
|
+
type: 'item.completed',
|
|
145
|
+
item: {
|
|
146
|
+
type: 'collab_tool_call',
|
|
147
|
+
tool: 'wait',
|
|
148
|
+
receiver_thread_ids: ['task-b'],
|
|
149
|
+
prompt: 'Analyze b.js in the current directory.',
|
|
150
|
+
agents_states: {
|
|
151
|
+
'task-b': {
|
|
152
|
+
status: 'completed',
|
|
153
|
+
message: 'found 2 exports',
|
|
154
|
+
title: 'Analyze b.js exports',
|
|
155
|
+
role: 'general-purpose',
|
|
156
|
+
target: 'b.js',
|
|
157
|
+
model: 'sonnet',
|
|
158
|
+
task_id: 'task-b',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}]
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
assert.deepEqual(
|
|
166
|
+
normalizeClaudeEvents({
|
|
167
|
+
type: 'user',
|
|
168
|
+
message: {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: 'tool_result',
|
|
172
|
+
tool_use_id: 'agent-tool-2',
|
|
173
|
+
content: 'duplicate result',
|
|
174
|
+
is_error: false,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
}, state),
|
|
179
|
+
[]
|
|
180
|
+
)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('runner claudeCodeRunner maps TodoWrite into todo_list events', () => {
|
|
184
|
+
const state = createClaudeNormalizationState()
|
|
185
|
+
|
|
186
|
+
assert.deepEqual(
|
|
187
|
+
normalizeClaudeEvents({
|
|
188
|
+
type: 'assistant',
|
|
189
|
+
message: {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: 'tool_use',
|
|
193
|
+
id: 'todo-tool-1',
|
|
194
|
+
name: 'TodoWrite',
|
|
195
|
+
input: {
|
|
196
|
+
todos: [
|
|
197
|
+
{
|
|
198
|
+
content: 'Inspect relevant backend and admin codepaths',
|
|
199
|
+
activeForm: 'Inspecting relevant backend and admin codepaths',
|
|
200
|
+
status: 'in_progress',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
content: 'Implement hospital database model and admin APIs',
|
|
204
|
+
status: 'pending',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
content: 'Add tests',
|
|
208
|
+
status: 'completed',
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
}, state),
|
|
216
|
+
[{
|
|
217
|
+
type: 'item.started',
|
|
218
|
+
item: {
|
|
219
|
+
type: 'todo_list',
|
|
220
|
+
items: [
|
|
221
|
+
{
|
|
222
|
+
text: 'Inspecting relevant backend and admin codepaths',
|
|
223
|
+
status: 'in_progress',
|
|
224
|
+
completed: false,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
text: 'Implement hospital database model and admin APIs',
|
|
228
|
+
status: 'pending',
|
|
229
|
+
completed: false,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
text: 'Add tests',
|
|
233
|
+
status: 'completed',
|
|
234
|
+
completed: true,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
}]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
assert.deepEqual(
|
|
242
|
+
normalizeClaudeEvents({
|
|
243
|
+
type: 'user',
|
|
244
|
+
message: {
|
|
245
|
+
content: [
|
|
246
|
+
{
|
|
247
|
+
type: 'tool_result',
|
|
248
|
+
tool_use_id: 'todo-tool-1',
|
|
249
|
+
content: 'Todos have been modified successfully.',
|
|
250
|
+
is_error: false,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
}, state),
|
|
255
|
+
[{
|
|
256
|
+
type: 'item.completed',
|
|
257
|
+
item: {
|
|
258
|
+
type: 'todo_list',
|
|
259
|
+
items: [
|
|
260
|
+
{
|
|
261
|
+
text: 'Inspecting relevant backend and admin codepaths',
|
|
262
|
+
status: 'in_progress',
|
|
263
|
+
completed: false,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
text: 'Implement hospital database model and admin APIs',
|
|
267
|
+
status: 'pending',
|
|
268
|
+
completed: false,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
text: 'Add tests',
|
|
272
|
+
status: 'completed',
|
|
273
|
+
completed: true,
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
}]
|
|
278
|
+
)
|
|
279
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { normalizeOpenCodeEvents } from './openCodeRunner.js'
|
|
5
|
+
|
|
6
|
+
test('runner openCodeRunner maps sub-agent task tool_use to collaboration events', () => {
|
|
7
|
+
assert.deepEqual(
|
|
8
|
+
normalizeOpenCodeEvents({
|
|
9
|
+
type: 'tool_use',
|
|
10
|
+
sessionID: 'ses_main',
|
|
11
|
+
part: {
|
|
12
|
+
type: 'tool',
|
|
13
|
+
tool: 'task',
|
|
14
|
+
state: {
|
|
15
|
+
status: 'completed',
|
|
16
|
+
input: {
|
|
17
|
+
description: '分析 a.js 文件',
|
|
18
|
+
prompt: '请分析 /tmp/demo/a.js 文件',
|
|
19
|
+
subagent_type: 'explore',
|
|
20
|
+
},
|
|
21
|
+
output: 'task_id: ses_child_1\n\n<task_result>ok</task_result>',
|
|
22
|
+
metadata: {
|
|
23
|
+
sessionId: 'ses_child_1',
|
|
24
|
+
model: {
|
|
25
|
+
providerID: 'opencode',
|
|
26
|
+
modelID: 'minimax-m2.5-free',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
type: 'item.completed',
|
|
35
|
+
item: {
|
|
36
|
+
type: 'collab_tool_call',
|
|
37
|
+
tool: 'spawn_agent',
|
|
38
|
+
receiver_thread_ids: ['ses_child_1'],
|
|
39
|
+
prompt: '请分析 /tmp/demo/a.js 文件',
|
|
40
|
+
agents_states: {
|
|
41
|
+
ses_child_1: {
|
|
42
|
+
status: 'completed',
|
|
43
|
+
message: 'task_id: ses_child_1\n\n<task_result>ok</task_result>',
|
|
44
|
+
title: '分析 a.js 文件',
|
|
45
|
+
role: 'explore',
|
|
46
|
+
target: 'a.js',
|
|
47
|
+
model: 'opencode/minimax-m2.5-free',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'item.completed',
|
|
54
|
+
item: {
|
|
55
|
+
type: 'collab_tool_call',
|
|
56
|
+
tool: 'wait',
|
|
57
|
+
receiver_thread_ids: ['ses_child_1'],
|
|
58
|
+
prompt: '请分析 /tmp/demo/a.js 文件',
|
|
59
|
+
agents_states: {
|
|
60
|
+
ses_child_1: {
|
|
61
|
+
status: 'completed',
|
|
62
|
+
message: 'task_id: ses_child_1\n\n<task_result>ok</task_result>',
|
|
63
|
+
title: '分析 a.js 文件',
|
|
64
|
+
role: 'explore',
|
|
65
|
+
target: 'a.js',
|
|
66
|
+
model: 'opencode/minimax-m2.5-free',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { shellRunner } from './shellRunner.js'
|
|
5
|
+
|
|
6
|
+
function collectShellEvents(command) {
|
|
7
|
+
const events = []
|
|
8
|
+
const stream = shellRunner.streamSessionPrompt({
|
|
9
|
+
id: 'session-shell-test',
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
}, command, {
|
|
12
|
+
onEvent(event) {
|
|
13
|
+
events.push(event)
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
events,
|
|
19
|
+
stream,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('shellRunner runs a one-shot shell command and emits command events', async () => {
|
|
24
|
+
const command = `"${process.execPath}" -e "console.log('hello shell')"`
|
|
25
|
+
const { events, stream } = collectShellEvents(command)
|
|
26
|
+
const result = await stream.result
|
|
27
|
+
|
|
28
|
+
assert.match(result.message, /hello shell/)
|
|
29
|
+
assert.ok(events.some((event) => event?.event?.type === 'item.started'))
|
|
30
|
+
assert.ok(events.some((event) => event?.event?.type === 'item.completed'))
|
|
31
|
+
assert.ok(events.some((event) => event?.type === 'stdout' && /hello shell/.test(event.text || '')))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('shellRunner surfaces non-zero exit output on errors', async () => {
|
|
35
|
+
const command = `"${process.execPath}" -e "console.error('shell failed'); process.exit(3)"`
|
|
36
|
+
const { events, stream } = collectShellEvents(command)
|
|
37
|
+
|
|
38
|
+
await assert.rejects(
|
|
39
|
+
stream.result,
|
|
40
|
+
(error) => /exit 3/.test(String(error?.message || '')) && /shell failed/.test(String(error?.output || ''))
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const completed = events.find((event) => event?.event?.type === 'item.completed')
|
|
44
|
+
assert.equal(completed?.event?.item?.exit_code, 3)
|
|
45
|
+
assert.equal(completed?.event?.item?.status, 'failed')
|
|
46
|
+
})
|