@muyichengshayu/promptx 0.2.15 → 0.2.17
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 +8 -0
- package/apps/runner/src/codex.js +114 -16
- package/apps/runner/src/engines/claudeCodeRunner.test.js +467 -0
- package/apps/runner/src/engines/kimiCodeRunner.test.js +127 -0
- package/apps/runner/src/engines/openCodeRunner.test.js +236 -0
- package/apps/runner/src/engines/runnerContract.test.js +510 -0
- package/apps/runner/src/engines/shellRunner.test.js +46 -0
- 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 +572 -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 +775 -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 +279 -0
- package/apps/web/dist/assets/{CodexSessionManagerDialog-y7O-JTxP.js → CodexSessionManagerDialog-D1PwOD4T.js} +1 -1
- package/apps/web/dist/assets/{TaskDiffReviewDialog-CTr_zoAn.js → TaskDiffReviewDialog-GKKv-IkZ.js} +1 -1
- package/apps/web/dist/assets/{WorkbenchSettingsDialog-Bf2DCuN_.js → WorkbenchSettingsDialog-IWlkg3kU.js} +1 -1
- package/apps/web/dist/assets/WorkbenchView-dXHPTH_M.js +58 -0
- package/apps/web/dist/assets/{index-Co1Ssha9.js → index-DaIoquOV.js} +2 -2
- package/apps/web/dist/index.html +1 -1
- package/package.json +14 -21
- package/packages/shared/src/dailyLogStream.test.js +29 -0
- package/packages/shared/src/shellCommands.test.js +45 -0
- package/apps/web/dist/assets/WorkbenchView-Gq3mmtsK.js +0 -60
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.17
|
|
4
|
+
|
|
5
|
+
- 修复 Windows 上 Codex 任务实际完成后工作台仍显示运行中、任务卡片持续转圈的问题:runner 在收到 `turn.completed` 后会进入短暂收尾等待;如果 Codex 子进程没有及时退出,会按完成状态落库并回收子进程,避免成功任务长时间卡在 `running`。
|
|
6
|
+
|
|
7
|
+
## 0.2.16
|
|
8
|
+
|
|
9
|
+
- 修复中栏历史 Prompt 卡片点击“插入”时图片丢失的问题:现在会优先按结构化 `promptBlocks` 插入文本、导入块和图片,图文混合 Prompt 会完整进入右侧编辑区;仅图片的 Prompt 也会插入为图片块,不再退化成 URL 文本。
|
|
10
|
+
|
|
3
11
|
## 0.2.15
|
|
4
12
|
|
|
5
13
|
- 修复 AI 回复中项目文件路径链接跳转到前端站内错误 URL 的问题:点击项目内相对路径或当前项目目录下的本地绝对路径时,会打开源码浏览窗口并自动按路径搜索定位;外部链接保持原有打开行为。
|
package/apps/runner/src/codex.js
CHANGED
|
@@ -21,6 +21,14 @@ const STATE_DB_PATH = path.join(CODEX_HOME, 'state_5.sqlite')
|
|
|
21
21
|
const TMP_DIR = path.join(CODEX_HOME, 'tmp')
|
|
22
22
|
const MAX_THREAD_COUNT = 120
|
|
23
23
|
const MAX_OUTPUT_TAIL_LENGTH = 64 * 1024
|
|
24
|
+
const CODEX_RESULT_EXIT_GRACE_MS = Math.max(
|
|
25
|
+
0,
|
|
26
|
+
Number(process.env.PROMPTX_CODEX_RESULT_EXIT_GRACE_MS) || 3000
|
|
27
|
+
)
|
|
28
|
+
const CODEX_RESULT_FORCE_STOP_GRACE_MS = Math.max(
|
|
29
|
+
200,
|
|
30
|
+
Number(process.env.PROMPTX_CODEX_RESULT_FORCE_STOP_GRACE_MS) || 1500
|
|
31
|
+
)
|
|
24
32
|
const CODEX_DEFAULT_ARGS = ['--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check']
|
|
25
33
|
const RESOLVED_CODEX_BIN = resolveCodexBinary()
|
|
26
34
|
const require = createRequire(import.meta.url)
|
|
@@ -335,6 +343,25 @@ function trackThreadId(event, setThreadId) {
|
|
|
335
343
|
}
|
|
336
344
|
}
|
|
337
345
|
|
|
346
|
+
function extractCodexCompletionMessage(event = {}) {
|
|
347
|
+
if (event?.type === AGENT_RUN_EVENT_TYPES.TURN_COMPLETED) {
|
|
348
|
+
return extractTextFromUnknownError(event.result)
|
|
349
|
+
|| extractTextFromUnknownError(event.message)
|
|
350
|
+
|| extractTextFromUnknownError(event.response)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (
|
|
354
|
+
event?.type === AGENT_RUN_EVENT_TYPES.ITEM_COMPLETED
|
|
355
|
+
&& event?.item?.type === 'agent_message'
|
|
356
|
+
) {
|
|
357
|
+
return extractTextFromUnknownError(event.item.text)
|
|
358
|
+
|| extractTextFromUnknownError(event.item.message)
|
|
359
|
+
|| extractTextFromUnknownError(event.item.content)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return ''
|
|
363
|
+
}
|
|
364
|
+
|
|
338
365
|
function parseThreadIdFromStdout(stdout = '') {
|
|
339
366
|
const lines = String(stdout || '')
|
|
340
367
|
.replace(/\r\n/g, '\n')
|
|
@@ -430,6 +457,74 @@ export function streamPromptToCodexSession(sessionInput, prompt, callbacks = {})
|
|
|
430
457
|
let stderrRaw = ''
|
|
431
458
|
let finalMessage = ''
|
|
432
459
|
let finalThreadId = session.codexThreadId || ''
|
|
460
|
+
let resultExitGraceTimer = null
|
|
461
|
+
let settled = false
|
|
462
|
+
let resolveResult = null
|
|
463
|
+
let rejectResult = null
|
|
464
|
+
|
|
465
|
+
const clearResultExitGraceTimer = () => {
|
|
466
|
+
if (resultExitGraceTimer) {
|
|
467
|
+
clearTimeout(resultExitGraceTimer)
|
|
468
|
+
resultExitGraceTimer = null
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const readOutputFileMessage = () => {
|
|
473
|
+
if (!fs.existsSync(outputFile)) {
|
|
474
|
+
return ''
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return repairPossibleMojibake(fs.readFileSync(outputFile, 'utf8').trim())
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const refreshFinalMessageFromOutputFile = () => {
|
|
481
|
+
finalMessage = readOutputFileMessage() || finalMessage
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const settleCompleted = (options = {}) => {
|
|
485
|
+
if (settled) {
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
settled = true
|
|
490
|
+
clearResultExitGraceTimer()
|
|
491
|
+
refreshFinalMessageFromOutputFile()
|
|
492
|
+
if (!finalThreadId) {
|
|
493
|
+
finalThreadId = parseThreadIdFromStdout(stdoutRaw)
|
|
494
|
+
}
|
|
495
|
+
emit(createCompletedEnvelopeEvent(finalMessage))
|
|
496
|
+
resolveResult?.({
|
|
497
|
+
sessionId: session.id,
|
|
498
|
+
message: finalMessage,
|
|
499
|
+
threadId: finalThreadId,
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
if (options.stopChild && child.exitCode === null && child.signalCode === null) {
|
|
503
|
+
forceStopChildProcess(child, { graceMs: CODEX_RESULT_FORCE_STOP_GRACE_MS })
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const settleError = (error) => {
|
|
508
|
+
if (settled) {
|
|
509
|
+
return
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
settled = true
|
|
513
|
+
clearResultExitGraceTimer()
|
|
514
|
+
rejectResult?.(error)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const scheduleResultExitGrace = () => {
|
|
518
|
+
if (settled || resultExitGraceTimer || CODEX_RESULT_EXIT_GRACE_MS <= 0) {
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
resultExitGraceTimer = setTimeout(() => {
|
|
523
|
+
resultExitGraceTimer = null
|
|
524
|
+
settleCompleted({ stopChild: true })
|
|
525
|
+
}, CODEX_RESULT_EXIT_GRACE_MS)
|
|
526
|
+
resultExitGraceTimer.unref?.()
|
|
527
|
+
}
|
|
433
528
|
|
|
434
529
|
const emit = (event) => {
|
|
435
530
|
try {
|
|
@@ -471,6 +566,10 @@ export function streamPromptToCodexSession(sessionInput, prompt, callbacks = {})
|
|
|
471
566
|
if (event) {
|
|
472
567
|
trackThreadId(event, rememberThreadId)
|
|
473
568
|
emit(createAgentEventEnvelopeEvent(sanitizeCodexPayload(event)))
|
|
569
|
+
finalMessage = extractCodexCompletionMessage(event) || finalMessage
|
|
570
|
+
if (event.type === AGENT_RUN_EVENT_TYPES.TURN_COMPLETED) {
|
|
571
|
+
scheduleResultExitGrace()
|
|
572
|
+
}
|
|
474
573
|
continue
|
|
475
574
|
}
|
|
476
575
|
|
|
@@ -494,11 +593,18 @@ export function streamPromptToCodexSession(sessionInput, prompt, callbacks = {})
|
|
|
494
593
|
child.stdin.end()
|
|
495
594
|
|
|
496
595
|
const result = new Promise((resolve, reject) => {
|
|
596
|
+
resolveResult = resolve
|
|
597
|
+
rejectResult = reject
|
|
598
|
+
|
|
497
599
|
child.on('error', (error) => {
|
|
498
|
-
|
|
600
|
+
settleError(normalizeSpawnError(error))
|
|
499
601
|
})
|
|
500
602
|
|
|
501
603
|
child.on('close', (code) => {
|
|
604
|
+
if (settled) {
|
|
605
|
+
return
|
|
606
|
+
}
|
|
607
|
+
|
|
502
608
|
const stdoutTail = flushBufferedText(stdoutBuffer)
|
|
503
609
|
const stderrTail = flushBufferedText(stderrBuffer)
|
|
504
610
|
|
|
@@ -507,6 +613,10 @@ export function streamPromptToCodexSession(sessionInput, prompt, callbacks = {})
|
|
|
507
613
|
if (event) {
|
|
508
614
|
trackThreadId(event, rememberThreadId)
|
|
509
615
|
emit(createAgentEventEnvelopeEvent(sanitizeCodexPayload(event)))
|
|
616
|
+
finalMessage = extractCodexCompletionMessage(event) || finalMessage
|
|
617
|
+
if (event.type === AGENT_RUN_EVENT_TYPES.TURN_COMPLETED) {
|
|
618
|
+
scheduleResultExitGrace()
|
|
619
|
+
}
|
|
510
620
|
} else {
|
|
511
621
|
emit(createStdoutEnvelopeEvent(repairPossibleMojibake(line)))
|
|
512
622
|
}
|
|
@@ -516,26 +626,14 @@ export function streamPromptToCodexSession(sessionInput, prompt, callbacks = {})
|
|
|
516
626
|
emit(createStderrEnvelopeEvent(repairPossibleMojibake(line)))
|
|
517
627
|
})
|
|
518
628
|
|
|
519
|
-
|
|
520
|
-
finalMessage = repairPossibleMojibake(fs.readFileSync(outputFile, 'utf8').trim())
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (!finalThreadId) {
|
|
524
|
-
finalThreadId = parseThreadIdFromStdout(stdoutRaw)
|
|
525
|
-
}
|
|
629
|
+
refreshFinalMessageFromOutputFile()
|
|
526
630
|
|
|
527
631
|
if (code !== 0) {
|
|
528
|
-
|
|
632
|
+
settleError(new Error(repairPossibleMojibake(extractCodexError(stderrRaw, stdoutRaw))))
|
|
529
633
|
return
|
|
530
634
|
}
|
|
531
635
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
resolve({
|
|
535
|
-
sessionId: session.id,
|
|
536
|
-
message: finalMessage,
|
|
537
|
-
threadId: finalThreadId,
|
|
538
|
-
})
|
|
636
|
+
settleCompleted()
|
|
539
637
|
})
|
|
540
638
|
}).finally(() => {
|
|
541
639
|
fs.rmSync(outputFile, { force: true })
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createClaudeNormalizationState,
|
|
6
|
+
extractClaudeAssistantText,
|
|
7
|
+
extractClaudeResultText,
|
|
8
|
+
extractClaudeSessionId,
|
|
9
|
+
normalizeClaudeEvent,
|
|
10
|
+
normalizeClaudeEvents,
|
|
11
|
+
} from './claudeCodeRunner.js'
|
|
12
|
+
|
|
13
|
+
test('runner claudeCodeRunner maps fatal auth api_retry to error event', () => {
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
normalizeClaudeEvents({
|
|
16
|
+
type: 'system',
|
|
17
|
+
subtype: 'api_retry',
|
|
18
|
+
attempt: 1,
|
|
19
|
+
max_retries: 10,
|
|
20
|
+
error_status: 401,
|
|
21
|
+
error: 'authentication_failed',
|
|
22
|
+
}),
|
|
23
|
+
[{
|
|
24
|
+
type: 'error',
|
|
25
|
+
message: 'Claude Code 认证失败(HTTP 401 authentication_failed)。请重新登录 Claude Code,或检查当前环境中的认证令牌配置。',
|
|
26
|
+
}]
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('runner claudeCodeRunner maps transient api_retry to reconnecting error event', () => {
|
|
31
|
+
assert.deepEqual(
|
|
32
|
+
normalizeClaudeEvents({
|
|
33
|
+
type: 'system',
|
|
34
|
+
subtype: 'api_retry',
|
|
35
|
+
attempt: 2,
|
|
36
|
+
max_retries: 10,
|
|
37
|
+
error_status: 503,
|
|
38
|
+
error: 'overloaded',
|
|
39
|
+
}),
|
|
40
|
+
[{
|
|
41
|
+
type: 'error',
|
|
42
|
+
message: 'Reconnecting... 2/10 (HTTP 503 overloaded)',
|
|
43
|
+
}]
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('runner claudeCodeRunner maps Agent sub-agents into collaboration events', () => {
|
|
48
|
+
const state = createClaudeNormalizationState()
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(
|
|
51
|
+
normalizeClaudeEvents({
|
|
52
|
+
type: 'assistant',
|
|
53
|
+
message: {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: 'tool_use',
|
|
57
|
+
id: 'agent-tool-1',
|
|
58
|
+
name: 'Agent',
|
|
59
|
+
input: {
|
|
60
|
+
description: 'Analyze a.js exports',
|
|
61
|
+
subagent_type: 'general-purpose',
|
|
62
|
+
prompt: 'Analyze a.js in the current directory.',
|
|
63
|
+
model: 'sonnet',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
}, state),
|
|
69
|
+
[{
|
|
70
|
+
type: 'item.started',
|
|
71
|
+
item: {
|
|
72
|
+
type: 'collab_tool_call',
|
|
73
|
+
tool: 'spawn_agent',
|
|
74
|
+
receiver_thread_ids: [],
|
|
75
|
+
prompt: 'Analyze a.js in the current directory.',
|
|
76
|
+
agents_states: {},
|
|
77
|
+
},
|
|
78
|
+
}]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert.deepEqual(
|
|
82
|
+
normalizeClaudeEvents({
|
|
83
|
+
type: 'system',
|
|
84
|
+
subtype: 'task_started',
|
|
85
|
+
tool_use_id: 'agent-tool-1',
|
|
86
|
+
task_id: 'task-a',
|
|
87
|
+
description: 'Analyze a.js exports',
|
|
88
|
+
}, state),
|
|
89
|
+
[{
|
|
90
|
+
type: 'item.completed',
|
|
91
|
+
item: {
|
|
92
|
+
type: 'collab_tool_call',
|
|
93
|
+
tool: 'spawn_agent',
|
|
94
|
+
receiver_thread_ids: ['task-a'],
|
|
95
|
+
prompt: 'Analyze a.js in the current directory.',
|
|
96
|
+
agents_states: {
|
|
97
|
+
'task-a': {
|
|
98
|
+
status: 'running',
|
|
99
|
+
message: '',
|
|
100
|
+
title: 'Analyze a.js exports',
|
|
101
|
+
role: 'general-purpose',
|
|
102
|
+
target: 'a.js',
|
|
103
|
+
model: 'sonnet',
|
|
104
|
+
task_id: 'task-a',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}]
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('runner claudeCodeRunner maps task_completed and ignores duplicate tool_result for Agent sub-agents', () => {
|
|
113
|
+
const state = createClaudeNormalizationState()
|
|
114
|
+
|
|
115
|
+
normalizeClaudeEvents({
|
|
116
|
+
type: 'assistant',
|
|
117
|
+
message: {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: 'tool_use',
|
|
121
|
+
id: 'agent-tool-2',
|
|
122
|
+
name: 'Agent',
|
|
123
|
+
input: {
|
|
124
|
+
description: 'Analyze b.js exports',
|
|
125
|
+
subagent_type: 'general-purpose',
|
|
126
|
+
prompt: 'Analyze b.js in the current directory.',
|
|
127
|
+
model: 'sonnet',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
}, state)
|
|
133
|
+
|
|
134
|
+
normalizeClaudeEvents({
|
|
135
|
+
type: 'system',
|
|
136
|
+
subtype: 'task_started',
|
|
137
|
+
tool_use_id: 'agent-tool-2',
|
|
138
|
+
task_id: 'task-b',
|
|
139
|
+
description: 'Analyze b.js exports',
|
|
140
|
+
}, state)
|
|
141
|
+
|
|
142
|
+
assert.deepEqual(
|
|
143
|
+
normalizeClaudeEvents({
|
|
144
|
+
type: 'system',
|
|
145
|
+
subtype: 'task_completed',
|
|
146
|
+
task_id: 'task-b',
|
|
147
|
+
result: 'found 2 exports',
|
|
148
|
+
description: 'Analyze b.js exports',
|
|
149
|
+
}, state),
|
|
150
|
+
[{
|
|
151
|
+
type: 'item.completed',
|
|
152
|
+
item: {
|
|
153
|
+
type: 'collab_tool_call',
|
|
154
|
+
tool: 'wait',
|
|
155
|
+
receiver_thread_ids: ['task-b'],
|
|
156
|
+
prompt: 'Analyze b.js in the current directory.',
|
|
157
|
+
agents_states: {
|
|
158
|
+
'task-b': {
|
|
159
|
+
status: 'completed',
|
|
160
|
+
message: 'found 2 exports',
|
|
161
|
+
title: 'Analyze b.js exports',
|
|
162
|
+
role: 'general-purpose',
|
|
163
|
+
target: 'b.js',
|
|
164
|
+
model: 'sonnet',
|
|
165
|
+
task_id: 'task-b',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
}]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
assert.deepEqual(
|
|
173
|
+
normalizeClaudeEvents({
|
|
174
|
+
type: 'user',
|
|
175
|
+
message: {
|
|
176
|
+
content: [
|
|
177
|
+
{
|
|
178
|
+
type: 'tool_result',
|
|
179
|
+
tool_use_id: 'agent-tool-2',
|
|
180
|
+
content: 'duplicate result',
|
|
181
|
+
is_error: false,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
}, state),
|
|
186
|
+
[]
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('runner claudeCodeRunner maps TodoWrite into todo_list events', () => {
|
|
191
|
+
const state = createClaudeNormalizationState()
|
|
192
|
+
|
|
193
|
+
assert.deepEqual(
|
|
194
|
+
normalizeClaudeEvents({
|
|
195
|
+
type: 'assistant',
|
|
196
|
+
message: {
|
|
197
|
+
content: [
|
|
198
|
+
{
|
|
199
|
+
type: 'tool_use',
|
|
200
|
+
id: 'todo-tool-1',
|
|
201
|
+
name: 'TodoWrite',
|
|
202
|
+
input: {
|
|
203
|
+
todos: [
|
|
204
|
+
{
|
|
205
|
+
content: 'Inspect relevant backend and admin codepaths',
|
|
206
|
+
activeForm: 'Inspecting relevant backend and admin codepaths',
|
|
207
|
+
status: 'in_progress',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
content: 'Implement hospital database model and admin APIs',
|
|
211
|
+
status: 'pending',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
content: 'Add tests',
|
|
215
|
+
status: 'completed',
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
}, state),
|
|
223
|
+
[{
|
|
224
|
+
type: 'item.started',
|
|
225
|
+
item: {
|
|
226
|
+
type: 'todo_list',
|
|
227
|
+
items: [
|
|
228
|
+
{
|
|
229
|
+
text: 'Inspecting relevant backend and admin codepaths',
|
|
230
|
+
status: 'in_progress',
|
|
231
|
+
completed: false,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
text: 'Implement hospital database model and admin APIs',
|
|
235
|
+
status: 'pending',
|
|
236
|
+
completed: false,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
text: 'Add tests',
|
|
240
|
+
status: 'completed',
|
|
241
|
+
completed: true,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
}]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
assert.deepEqual(
|
|
249
|
+
normalizeClaudeEvents({
|
|
250
|
+
type: 'user',
|
|
251
|
+
message: {
|
|
252
|
+
content: [
|
|
253
|
+
{
|
|
254
|
+
type: 'tool_result',
|
|
255
|
+
tool_use_id: 'todo-tool-1',
|
|
256
|
+
content: 'Todos have been modified successfully.',
|
|
257
|
+
is_error: false,
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
}, state),
|
|
262
|
+
[{
|
|
263
|
+
type: 'item.completed',
|
|
264
|
+
item: {
|
|
265
|
+
type: 'todo_list',
|
|
266
|
+
items: [
|
|
267
|
+
{
|
|
268
|
+
text: 'Inspecting relevant backend and admin codepaths',
|
|
269
|
+
status: 'in_progress',
|
|
270
|
+
completed: false,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
text: 'Implement hospital database model and admin APIs',
|
|
274
|
+
status: 'pending',
|
|
275
|
+
completed: false,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
text: 'Add tests',
|
|
279
|
+
status: 'completed',
|
|
280
|
+
completed: true,
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
}]
|
|
285
|
+
)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test('extractClaudeAssistantText joins nested text parts', () => {
|
|
289
|
+
const text = extractClaudeAssistantText({
|
|
290
|
+
type: 'assistant',
|
|
291
|
+
message: {
|
|
292
|
+
content: [
|
|
293
|
+
{ type: 'text', text: '第一段' },
|
|
294
|
+
{ type: 'text', text: '第二段' },
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
assert.equal(text, '第一段\n第二段')
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
test('extractClaudeSessionId reads common session id fields', () => {
|
|
303
|
+
assert.equal(extractClaudeSessionId({ session_id: 'claude-session-1' }), 'claude-session-1')
|
|
304
|
+
assert.equal(extractClaudeSessionId({ result: { session_id: 'claude-session-2' } }), 'claude-session-2')
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
test('normalizeClaudeEvent maps assistant output to agent message', () => {
|
|
308
|
+
assert.deepEqual(
|
|
309
|
+
normalizeClaudeEvent({
|
|
310
|
+
type: 'assistant',
|
|
311
|
+
message: {
|
|
312
|
+
content: [{ type: 'text', text: '已完成修改' }],
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
315
|
+
{
|
|
316
|
+
type: 'item.completed',
|
|
317
|
+
item: {
|
|
318
|
+
type: 'agent_message',
|
|
319
|
+
text: '已完成修改',
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
test('normalizeClaudeEvent maps result output to turn completion', () => {
|
|
326
|
+
assert.deepEqual(
|
|
327
|
+
normalizeClaudeEvent({
|
|
328
|
+
type: 'result',
|
|
329
|
+
result: '最终回复',
|
|
330
|
+
}),
|
|
331
|
+
{
|
|
332
|
+
type: 'turn.completed',
|
|
333
|
+
result: '最终回复',
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
assert.equal(extractClaudeResultText({ result: '最终回复' }), '最终回复')
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
test('normalizeClaudeEvents maps system init to thread start', () => {
|
|
341
|
+
assert.deepEqual(
|
|
342
|
+
normalizeClaudeEvents({
|
|
343
|
+
type: 'system',
|
|
344
|
+
subtype: 'init',
|
|
345
|
+
session_id: 'claude-session-init',
|
|
346
|
+
}),
|
|
347
|
+
[{
|
|
348
|
+
type: 'thread.started',
|
|
349
|
+
thread_id: 'claude-session-init',
|
|
350
|
+
}]
|
|
351
|
+
)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
test('normalizeClaudeEvents maps thinking, tool use and text blocks', () => {
|
|
355
|
+
const state = createClaudeNormalizationState()
|
|
356
|
+
|
|
357
|
+
assert.deepEqual(
|
|
358
|
+
normalizeClaudeEvents({
|
|
359
|
+
type: 'assistant',
|
|
360
|
+
message: {
|
|
361
|
+
content: [
|
|
362
|
+
{ type: 'thinking', thinking: '先看看目录结构' },
|
|
363
|
+
{ type: 'tool_use', id: 'tool-1', name: 'Bash', input: { command: 'ls -1' } },
|
|
364
|
+
{ type: 'text', text: '已查看完成' },
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
}, state),
|
|
368
|
+
[
|
|
369
|
+
{
|
|
370
|
+
type: 'item.started',
|
|
371
|
+
item: {
|
|
372
|
+
type: 'reasoning',
|
|
373
|
+
text: '先看看目录结构',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
type: 'item.started',
|
|
378
|
+
item: {
|
|
379
|
+
type: 'command_execution',
|
|
380
|
+
command: 'Bash: ls -1',
|
|
381
|
+
status: 'in_progress',
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
type: 'item.completed',
|
|
386
|
+
item: {
|
|
387
|
+
type: 'agent_message',
|
|
388
|
+
text: '已查看完成',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
]
|
|
392
|
+
)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
test('normalizeClaudeEvents maps tool results back to remembered tool call', () => {
|
|
396
|
+
const state = createClaudeNormalizationState()
|
|
397
|
+
normalizeClaudeEvents({
|
|
398
|
+
type: 'assistant',
|
|
399
|
+
message: {
|
|
400
|
+
content: [
|
|
401
|
+
{ type: 'tool_use', id: 'tool-2', name: 'Bash', input: { command: 'pwd' } },
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
}, state)
|
|
405
|
+
|
|
406
|
+
assert.deepEqual(
|
|
407
|
+
normalizeClaudeEvents({
|
|
408
|
+
type: 'user',
|
|
409
|
+
message: {
|
|
410
|
+
content: [
|
|
411
|
+
{ type: 'tool_result', tool_use_id: 'tool-2', content: '/tmp/demo', is_error: false },
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
}, state),
|
|
415
|
+
[{
|
|
416
|
+
type: 'item.completed',
|
|
417
|
+
item: {
|
|
418
|
+
type: 'command_execution',
|
|
419
|
+
command: 'Bash: pwd',
|
|
420
|
+
status: 'completed',
|
|
421
|
+
exit_code: 0,
|
|
422
|
+
aggregated_output: '/tmp/demo',
|
|
423
|
+
},
|
|
424
|
+
}]
|
|
425
|
+
)
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
test('normalizeClaudeEvents stringifies structured tool results', () => {
|
|
429
|
+
const state = createClaudeNormalizationState()
|
|
430
|
+
normalizeClaudeEvents({
|
|
431
|
+
type: 'assistant',
|
|
432
|
+
message: {
|
|
433
|
+
content: [
|
|
434
|
+
{ type: 'tool_use', id: 'tool-3', name: 'Read', input: { file_path: '/tmp/demo.txt' } },
|
|
435
|
+
],
|
|
436
|
+
},
|
|
437
|
+
}, state)
|
|
438
|
+
|
|
439
|
+
assert.deepEqual(
|
|
440
|
+
normalizeClaudeEvents({
|
|
441
|
+
type: 'user',
|
|
442
|
+
message: {
|
|
443
|
+
content: [
|
|
444
|
+
{
|
|
445
|
+
type: 'tool_result',
|
|
446
|
+
tool_use_id: 'tool-3',
|
|
447
|
+
content: [
|
|
448
|
+
{ type: 'text', text: '<path>/tmp/demo.txt</path>' },
|
|
449
|
+
{ type: 'text', text: '<type>file</type>' },
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
state),
|
|
456
|
+
[{
|
|
457
|
+
type: 'item.completed',
|
|
458
|
+
item: {
|
|
459
|
+
type: 'command_execution',
|
|
460
|
+
command: 'Read: /tmp/demo.txt',
|
|
461
|
+
status: 'completed',
|
|
462
|
+
exit_code: 0,
|
|
463
|
+
aggregated_output: '<path>/tmp/demo.txt</path>\n<type>file</type>',
|
|
464
|
+
},
|
|
465
|
+
}]
|
|
466
|
+
)
|
|
467
|
+
})
|