claude-coder 1.9.0 → 1.9.2
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 +214 -214
- package/bin/cli.js +155 -155
- package/package.json +55 -55
- package/recipes/_shared/roles/developer.md +11 -11
- package/recipes/_shared/roles/product.md +12 -12
- package/recipes/_shared/roles/tester.md +12 -12
- package/recipes/_shared/test/report-format.md +86 -86
- package/recipes/backend/base.md +27 -27
- package/recipes/backend/components/auth.md +18 -18
- package/recipes/backend/components/crud-api.md +18 -18
- package/recipes/backend/components/file-service.md +15 -15
- package/recipes/backend/manifest.json +20 -20
- package/recipes/backend/test/api-test.md +25 -25
- package/recipes/console/base.md +37 -37
- package/recipes/console/components/modal-form.md +20 -20
- package/recipes/console/components/pagination.md +17 -17
- package/recipes/console/components/search.md +17 -17
- package/recipes/console/components/table-list.md +18 -18
- package/recipes/console/components/tabs.md +14 -14
- package/recipes/console/components/tree.md +15 -15
- package/recipes/console/components/upload.md +15 -15
- package/recipes/console/manifest.json +24 -24
- package/recipes/console/test/crud-e2e.md +47 -47
- package/recipes/h5/base.md +26 -26
- package/recipes/h5/components/animation.md +11 -11
- package/recipes/h5/components/countdown.md +11 -11
- package/recipes/h5/components/share.md +11 -11
- package/recipes/h5/components/swiper.md +11 -11
- package/recipes/h5/manifest.json +21 -21
- package/recipes/h5/test/h5-e2e.md +20 -20
- package/src/commands/auth.js +420 -362
- package/src/commands/setup-modules/helpers.js +100 -100
- package/src/commands/setup-modules/index.js +25 -25
- package/src/commands/setup-modules/mcp.js +115 -115
- package/src/commands/setup-modules/provider.js +260 -260
- package/src/commands/setup-modules/safety.js +47 -47
- package/src/commands/setup-modules/simplify.js +52 -52
- package/src/commands/setup.js +172 -172
- package/src/common/assets.js +245 -245
- package/src/common/config.js +125 -125
- package/src/common/constants.js +55 -55
- package/src/common/indicator.js +260 -260
- package/src/common/interaction.js +170 -170
- package/src/common/logging.js +77 -77
- package/src/common/sdk.js +50 -50
- package/src/common/tasks.js +88 -88
- package/src/common/utils.js +213 -213
- package/src/core/coding.js +33 -33
- package/src/core/go.js +264 -264
- package/src/core/hooks.js +500 -500
- package/src/core/init.js +166 -165
- package/src/core/plan.js +188 -187
- package/src/core/prompts.js +247 -247
- package/src/core/repair.js +36 -36
- package/src/core/runner.js +471 -458
- package/src/core/scan.js +93 -93
- package/src/core/session.js +280 -271
- package/src/core/simplify.js +74 -74
- package/src/core/state.js +105 -105
- package/src/index.js +76 -76
- package/templates/bash-process.md +12 -12
- package/templates/codingSystem.md +65 -65
- package/templates/codingUser.md +17 -17
- package/templates/coreProtocol.md +29 -29
- package/templates/goSystem.md +130 -130
- package/templates/guidance.json +72 -72
- package/templates/planSystem.md +78 -78
- package/templates/planUser.md +8 -8
- package/templates/requirements.example.md +57 -57
- package/templates/scanSystem.md +120 -120
- package/templates/scanUser.md +10 -10
- package/templates/test_rule.md +194 -194
- package/templates/web-testing.md +17 -17
- package/types/index.d.ts +217 -217
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const readline = require('readline');
|
|
4
|
-
const { COLOR } = require('./config');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 在终端渲染一个结构化问题并收集用户选择
|
|
8
|
-
*
|
|
9
|
-
* @param {object} question - AskUserQuestion 格式的单个问题
|
|
10
|
-
* @param {string} question.question - 问题文本
|
|
11
|
-
* @param {string} question.header - 短标签
|
|
12
|
-
* @param {Array} question.options - 选项列表 [{ label, description }]
|
|
13
|
-
* @param {boolean} question.multiSelect - 是否多选
|
|
14
|
-
* @returns {Promise<string>} 用户选择的文本
|
|
15
|
-
*/
|
|
16
|
-
async function renderQuestion(question) {
|
|
17
|
-
const rl = readline.createInterface({
|
|
18
|
-
input: process.stdin,
|
|
19
|
-
output: process.stderr,
|
|
20
|
-
terminal: process.stdin.isTTY || false,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return new Promise(resolve => {
|
|
24
|
-
const w = (s) => process.stderr.write(s);
|
|
25
|
-
|
|
26
|
-
w(`\n${COLOR.cyan}┌─ ${question.header || '问题'} ${'─'.repeat(Math.max(0, 40 - (question.header || '').length))}${COLOR.reset}\n`);
|
|
27
|
-
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.bold}${question.question}${COLOR.reset}\n`);
|
|
28
|
-
w(`${COLOR.cyan}│${COLOR.reset}\n`);
|
|
29
|
-
|
|
30
|
-
const options = question.options || [];
|
|
31
|
-
options.forEach((opt, i) => {
|
|
32
|
-
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.yellow}${i + 1}.${COLOR.reset} ${opt.label}\n`);
|
|
33
|
-
if (opt.description) {
|
|
34
|
-
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.dim}${opt.description}${COLOR.reset}\n`);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.yellow}0.${COLOR.reset} ${COLOR.dim}其他 (自定义输入)${COLOR.reset}\n`);
|
|
39
|
-
|
|
40
|
-
if (question.multiSelect) {
|
|
41
|
-
w(`${COLOR.cyan}│${COLOR.reset}\n`);
|
|
42
|
-
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.dim}(多选: 用逗号分隔数字, 如 1,3)${COLOR.reset}\n`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
w(`${COLOR.cyan}└${'─'.repeat(44)}${COLOR.reset}\n`);
|
|
46
|
-
|
|
47
|
-
rl.question(` ${COLOR.green}>${COLOR.reset} `, answer => {
|
|
48
|
-
rl.close();
|
|
49
|
-
|
|
50
|
-
const trimmed = answer.trim();
|
|
51
|
-
if (!trimmed) {
|
|
52
|
-
resolve(options[0]?.label || '');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (trimmed === '0') {
|
|
57
|
-
const rl2 = readline.createInterface({
|
|
58
|
-
input: process.stdin,
|
|
59
|
-
output: process.stderr,
|
|
60
|
-
terminal: process.stdin.isTTY || false,
|
|
61
|
-
});
|
|
62
|
-
rl2.question(` ${COLOR.cyan}请输入你的想法:${COLOR.reset} `, customAnswer => {
|
|
63
|
-
rl2.close();
|
|
64
|
-
resolve(customAnswer.trim() || options[0]?.label || '');
|
|
65
|
-
});
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const nums = trimmed.split(/[,,\s]+/)
|
|
70
|
-
.map(n => parseInt(n, 10))
|
|
71
|
-
.filter(n => !isNaN(n) && n >= 1 && n <= options.length);
|
|
72
|
-
|
|
73
|
-
if (nums.length > 0) {
|
|
74
|
-
const selected = nums.map(n => options[n - 1].label);
|
|
75
|
-
resolve(question.multiSelect ? selected.join(', ') : selected[0]);
|
|
76
|
-
} else {
|
|
77
|
-
resolve(trimmed);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 处理完整的 AskUserQuestion 工具调用
|
|
85
|
-
* 逐个渲染问题,收集所有答案
|
|
86
|
-
*
|
|
87
|
-
* @param {object} toolInput - AskUserQuestion 的 tool_input
|
|
88
|
-
* @param {Array} toolInput.questions - 问题列表
|
|
89
|
-
* @returns {Promise<object>} { answers: { [question]: answer }, formatted: string }
|
|
90
|
-
*/
|
|
91
|
-
async function handleUserQuestions(toolInput) {
|
|
92
|
-
const questions = toolInput.questions || [];
|
|
93
|
-
if (questions.length === 0) {
|
|
94
|
-
return { answers: {}, formatted: '(no questions)' };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
process.stderr.write(`\n${COLOR.magenta}╔══ 模型需要你的输入 ══════════════════════╗${COLOR.reset}\n`);
|
|
98
|
-
process.stderr.write(`${COLOR.magenta}║${COLOR.reset} 以下是模型提出的问题,请逐一回答 ${COLOR.magenta}║${COLOR.reset}\n`);
|
|
99
|
-
process.stderr.write(`${COLOR.magenta}╚══════════════════════════════════════════╝${COLOR.reset}\n`);
|
|
100
|
-
|
|
101
|
-
const answers = {};
|
|
102
|
-
for (const q of questions) {
|
|
103
|
-
const answer = await renderQuestion(q);
|
|
104
|
-
answers[q.question] = answer;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const lines = Object.entries(answers)
|
|
108
|
-
.map(([q, a]) => `Q: ${q}\nA: ${a}`)
|
|
109
|
-
.join('\n\n');
|
|
110
|
-
|
|
111
|
-
process.stderr.write(`\n${COLOR.green}✓ 已收集回答:${COLOR.reset}\n`);
|
|
112
|
-
for (const [q, a] of Object.entries(answers)) {
|
|
113
|
-
const shortQ = q.length > 50 ? q.slice(0, 50) + '...' : q;
|
|
114
|
-
process.stderr.write(` ${COLOR.dim}${shortQ}${COLOR.reset} → ${COLOR.bold}${a}${COLOR.reset}\n`);
|
|
115
|
-
}
|
|
116
|
-
process.stderr.write('\n');
|
|
117
|
-
|
|
118
|
-
return { answers, formatted: lines };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 创建 AskUserQuestion 的 PreToolUse Hook 处理函数
|
|
123
|
-
*
|
|
124
|
-
* 工作原理:
|
|
125
|
-
* 1. 拦截模型的 AskUserQuestion 调用
|
|
126
|
-
* 2. 通过 readline 在终端展示问题
|
|
127
|
-
* 3. 收集用户答案
|
|
128
|
-
* 4. deny 工具调用,同时通过 systemMessage 将答案注入上下文
|
|
129
|
-
*
|
|
130
|
-
* @returns {Function} PreToolUse hook handler
|
|
131
|
-
*/
|
|
132
|
-
function createAskUserQuestionHook(indicator) {
|
|
133
|
-
return async (input, _toolUseID, _context) => {
|
|
134
|
-
if (input.tool_name !== 'AskUserQuestion') return {};
|
|
135
|
-
|
|
136
|
-
if (indicator) {
|
|
137
|
-
indicator.pauseRendering();
|
|
138
|
-
process.stderr.write('\r\x1b[K');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
let formatted;
|
|
142
|
-
try {
|
|
143
|
-
({ formatted } = await handleUserQuestions(input.tool_input));
|
|
144
|
-
} finally {
|
|
145
|
-
if (indicator) indicator.resumeRendering();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
systemMessage: [
|
|
150
|
-
'The user has answered your questions via the terminal interface.',
|
|
151
|
-
'Here are their responses:',
|
|
152
|
-
'',
|
|
153
|
-
formatted,
|
|
154
|
-
'',
|
|
155
|
-
'Proceed based on these answers. Do NOT ask the same questions again.',
|
|
156
|
-
].join('\n'),
|
|
157
|
-
hookSpecificOutput: {
|
|
158
|
-
hookEventName: 'PreToolUse',
|
|
159
|
-
permissionDecision: 'deny',
|
|
160
|
-
permissionDecisionReason: `User answered via terminal. Answers:\n${formatted}`,
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
module.exports = {
|
|
167
|
-
renderQuestion,
|
|
168
|
-
handleUserQuestions,
|
|
169
|
-
createAskUserQuestionHook,
|
|
170
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const { COLOR } = require('./config');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 在终端渲染一个结构化问题并收集用户选择
|
|
8
|
+
*
|
|
9
|
+
* @param {object} question - AskUserQuestion 格式的单个问题
|
|
10
|
+
* @param {string} question.question - 问题文本
|
|
11
|
+
* @param {string} question.header - 短标签
|
|
12
|
+
* @param {Array} question.options - 选项列表 [{ label, description }]
|
|
13
|
+
* @param {boolean} question.multiSelect - 是否多选
|
|
14
|
+
* @returns {Promise<string>} 用户选择的文本
|
|
15
|
+
*/
|
|
16
|
+
async function renderQuestion(question) {
|
|
17
|
+
const rl = readline.createInterface({
|
|
18
|
+
input: process.stdin,
|
|
19
|
+
output: process.stderr,
|
|
20
|
+
terminal: process.stdin.isTTY || false,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return new Promise(resolve => {
|
|
24
|
+
const w = (s) => process.stderr.write(s);
|
|
25
|
+
|
|
26
|
+
w(`\n${COLOR.cyan}┌─ ${question.header || '问题'} ${'─'.repeat(Math.max(0, 40 - (question.header || '').length))}${COLOR.reset}\n`);
|
|
27
|
+
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.bold}${question.question}${COLOR.reset}\n`);
|
|
28
|
+
w(`${COLOR.cyan}│${COLOR.reset}\n`);
|
|
29
|
+
|
|
30
|
+
const options = question.options || [];
|
|
31
|
+
options.forEach((opt, i) => {
|
|
32
|
+
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.yellow}${i + 1}.${COLOR.reset} ${opt.label}\n`);
|
|
33
|
+
if (opt.description) {
|
|
34
|
+
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.dim}${opt.description}${COLOR.reset}\n`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.yellow}0.${COLOR.reset} ${COLOR.dim}其他 (自定义输入)${COLOR.reset}\n`);
|
|
39
|
+
|
|
40
|
+
if (question.multiSelect) {
|
|
41
|
+
w(`${COLOR.cyan}│${COLOR.reset}\n`);
|
|
42
|
+
w(`${COLOR.cyan}│${COLOR.reset} ${COLOR.dim}(多选: 用逗号分隔数字, 如 1,3)${COLOR.reset}\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
w(`${COLOR.cyan}└${'─'.repeat(44)}${COLOR.reset}\n`);
|
|
46
|
+
|
|
47
|
+
rl.question(` ${COLOR.green}>${COLOR.reset} `, answer => {
|
|
48
|
+
rl.close();
|
|
49
|
+
|
|
50
|
+
const trimmed = answer.trim();
|
|
51
|
+
if (!trimmed) {
|
|
52
|
+
resolve(options[0]?.label || '');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (trimmed === '0') {
|
|
57
|
+
const rl2 = readline.createInterface({
|
|
58
|
+
input: process.stdin,
|
|
59
|
+
output: process.stderr,
|
|
60
|
+
terminal: process.stdin.isTTY || false,
|
|
61
|
+
});
|
|
62
|
+
rl2.question(` ${COLOR.cyan}请输入你的想法:${COLOR.reset} `, customAnswer => {
|
|
63
|
+
rl2.close();
|
|
64
|
+
resolve(customAnswer.trim() || options[0]?.label || '');
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const nums = trimmed.split(/[,,\s]+/)
|
|
70
|
+
.map(n => parseInt(n, 10))
|
|
71
|
+
.filter(n => !isNaN(n) && n >= 1 && n <= options.length);
|
|
72
|
+
|
|
73
|
+
if (nums.length > 0) {
|
|
74
|
+
const selected = nums.map(n => options[n - 1].label);
|
|
75
|
+
resolve(question.multiSelect ? selected.join(', ') : selected[0]);
|
|
76
|
+
} else {
|
|
77
|
+
resolve(trimmed);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 处理完整的 AskUserQuestion 工具调用
|
|
85
|
+
* 逐个渲染问题,收集所有答案
|
|
86
|
+
*
|
|
87
|
+
* @param {object} toolInput - AskUserQuestion 的 tool_input
|
|
88
|
+
* @param {Array} toolInput.questions - 问题列表
|
|
89
|
+
* @returns {Promise<object>} { answers: { [question]: answer }, formatted: string }
|
|
90
|
+
*/
|
|
91
|
+
async function handleUserQuestions(toolInput) {
|
|
92
|
+
const questions = toolInput.questions || [];
|
|
93
|
+
if (questions.length === 0) {
|
|
94
|
+
return { answers: {}, formatted: '(no questions)' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
process.stderr.write(`\n${COLOR.magenta}╔══ 模型需要你的输入 ══════════════════════╗${COLOR.reset}\n`);
|
|
98
|
+
process.stderr.write(`${COLOR.magenta}║${COLOR.reset} 以下是模型提出的问题,请逐一回答 ${COLOR.magenta}║${COLOR.reset}\n`);
|
|
99
|
+
process.stderr.write(`${COLOR.magenta}╚══════════════════════════════════════════╝${COLOR.reset}\n`);
|
|
100
|
+
|
|
101
|
+
const answers = {};
|
|
102
|
+
for (const q of questions) {
|
|
103
|
+
const answer = await renderQuestion(q);
|
|
104
|
+
answers[q.question] = answer;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const lines = Object.entries(answers)
|
|
108
|
+
.map(([q, a]) => `Q: ${q}\nA: ${a}`)
|
|
109
|
+
.join('\n\n');
|
|
110
|
+
|
|
111
|
+
process.stderr.write(`\n${COLOR.green}✓ 已收集回答:${COLOR.reset}\n`);
|
|
112
|
+
for (const [q, a] of Object.entries(answers)) {
|
|
113
|
+
const shortQ = q.length > 50 ? q.slice(0, 50) + '...' : q;
|
|
114
|
+
process.stderr.write(` ${COLOR.dim}${shortQ}${COLOR.reset} → ${COLOR.bold}${a}${COLOR.reset}\n`);
|
|
115
|
+
}
|
|
116
|
+
process.stderr.write('\n');
|
|
117
|
+
|
|
118
|
+
return { answers, formatted: lines };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 创建 AskUserQuestion 的 PreToolUse Hook 处理函数
|
|
123
|
+
*
|
|
124
|
+
* 工作原理:
|
|
125
|
+
* 1. 拦截模型的 AskUserQuestion 调用
|
|
126
|
+
* 2. 通过 readline 在终端展示问题
|
|
127
|
+
* 3. 收集用户答案
|
|
128
|
+
* 4. deny 工具调用,同时通过 systemMessage 将答案注入上下文
|
|
129
|
+
*
|
|
130
|
+
* @returns {Function} PreToolUse hook handler
|
|
131
|
+
*/
|
|
132
|
+
function createAskUserQuestionHook(indicator) {
|
|
133
|
+
return async (input, _toolUseID, _context) => {
|
|
134
|
+
if (input.tool_name !== 'AskUserQuestion') return {};
|
|
135
|
+
|
|
136
|
+
if (indicator) {
|
|
137
|
+
indicator.pauseRendering();
|
|
138
|
+
process.stderr.write('\r\x1b[K');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let formatted;
|
|
142
|
+
try {
|
|
143
|
+
({ formatted } = await handleUserQuestions(input.tool_input));
|
|
144
|
+
} finally {
|
|
145
|
+
if (indicator) indicator.resumeRendering();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
systemMessage: [
|
|
150
|
+
'The user has answered your questions via the terminal interface.',
|
|
151
|
+
'Here are their responses:',
|
|
152
|
+
'',
|
|
153
|
+
formatted,
|
|
154
|
+
'',
|
|
155
|
+
'Proceed based on these answers. Do NOT ask the same questions again.',
|
|
156
|
+
].join('\n'),
|
|
157
|
+
hookSpecificOutput: {
|
|
158
|
+
hookEventName: 'PreToolUse',
|
|
159
|
+
permissionDecision: 'deny',
|
|
160
|
+
permissionDecisionReason: `User answered via terminal. Answers:\n${formatted}`,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
renderQuestion,
|
|
168
|
+
handleUserQuestions,
|
|
169
|
+
createAskUserQuestionHook,
|
|
170
|
+
};
|
package/src/common/logging.js
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { localTimestamp } = require('./utils');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 处理 SDK 消息并写入日志流
|
|
7
|
-
* @param {object} message - SDK 消息对象
|
|
8
|
-
* @param {import('fs').WriteStream} logStream - 日志写入流
|
|
9
|
-
* @param {object} indicator - Indicator 实例(可选)
|
|
10
|
-
*/
|
|
11
|
-
function logMessage(message, logStream, indicator) {
|
|
12
|
-
if (message.type === 'assistant' && message.message?.content) {
|
|
13
|
-
for (const block of message.message.content) {
|
|
14
|
-
if (block.type === 'text' && block.text) {
|
|
15
|
-
if (indicator) indicator.updateActivity();
|
|
16
|
-
process.stdout.write(block.text);
|
|
17
|
-
if (logStream) logStream.write(block.text);
|
|
18
|
-
}
|
|
19
|
-
if (block.type === 'tool_use' && logStream) {
|
|
20
|
-
logStream.write(`[TOOL_USE] ${block.name}: ${JSON.stringify(block.input).slice(0, 300)}\n`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (message.type === 'tool_result') {
|
|
26
|
-
if (indicator) indicator.updateActivity();
|
|
27
|
-
if (logStream) {
|
|
28
|
-
const isErr = message.is_error || false;
|
|
29
|
-
const content = typeof message.content === 'string'
|
|
30
|
-
? message.content.slice(0, 500)
|
|
31
|
-
: JSON.stringify(message.content).slice(0, 500);
|
|
32
|
-
if (isErr) {
|
|
33
|
-
logStream.write(`[TOOL_ERROR] ${content}\n`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 从消息列表中提取结果消息
|
|
41
|
-
* @param {Array} messages - 消息列表
|
|
42
|
-
* @returns {object|null} 结果消息
|
|
43
|
-
*/
|
|
44
|
-
function extractResult(messages) {
|
|
45
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
46
|
-
if (messages[i].type === 'result') return messages[i];
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 从消息列表中提取结果文本
|
|
53
|
-
* @param {Array} messages - 消息列表
|
|
54
|
-
* @returns {string} 结果文本
|
|
55
|
-
*/
|
|
56
|
-
function extractResultText(messages) {
|
|
57
|
-
const result = extractResult(messages);
|
|
58
|
-
return result?.result || '';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 写入 session 分隔符到日志
|
|
63
|
-
* @param {import('fs').WriteStream} logStream - 日志写入流
|
|
64
|
-
* @param {number} sessionNum - session 编号
|
|
65
|
-
* @param {string} label - 标签
|
|
66
|
-
*/
|
|
67
|
-
function writeSessionSeparator(logStream, sessionNum, label) {
|
|
68
|
-
const sep = '='.repeat(60);
|
|
69
|
-
logStream.write(`\n${sep}\n[Session ${sessionNum}] ${label} ${localTimestamp()}\n${sep}\n`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module.exports = {
|
|
73
|
-
logMessage,
|
|
74
|
-
localTimestamp,
|
|
75
|
-
extractResult,
|
|
76
|
-
extractResultText,
|
|
77
|
-
writeSessionSeparator,
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { localTimestamp } = require('./utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 处理 SDK 消息并写入日志流
|
|
7
|
+
* @param {object} message - SDK 消息对象
|
|
8
|
+
* @param {import('fs').WriteStream} logStream - 日志写入流
|
|
9
|
+
* @param {object} indicator - Indicator 实例(可选)
|
|
10
|
+
*/
|
|
11
|
+
function logMessage(message, logStream, indicator) {
|
|
12
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
13
|
+
for (const block of message.message.content) {
|
|
14
|
+
if (block.type === 'text' && block.text) {
|
|
15
|
+
if (indicator) indicator.updateActivity();
|
|
16
|
+
process.stdout.write(block.text);
|
|
17
|
+
if (logStream) logStream.write(block.text);
|
|
18
|
+
}
|
|
19
|
+
if (block.type === 'tool_use' && logStream) {
|
|
20
|
+
logStream.write(`[TOOL_USE] ${block.name}: ${JSON.stringify(block.input).slice(0, 300)}\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (message.type === 'tool_result') {
|
|
26
|
+
if (indicator) indicator.updateActivity();
|
|
27
|
+
if (logStream) {
|
|
28
|
+
const isErr = message.is_error || false;
|
|
29
|
+
const content = typeof message.content === 'string'
|
|
30
|
+
? message.content.slice(0, 500)
|
|
31
|
+
: JSON.stringify(message.content).slice(0, 500);
|
|
32
|
+
if (isErr) {
|
|
33
|
+
logStream.write(`[TOOL_ERROR] ${content}\n`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 从消息列表中提取结果消息
|
|
41
|
+
* @param {Array} messages - 消息列表
|
|
42
|
+
* @returns {object|null} 结果消息
|
|
43
|
+
*/
|
|
44
|
+
function extractResult(messages) {
|
|
45
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
46
|
+
if (messages[i].type === 'result') return messages[i];
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 从消息列表中提取结果文本
|
|
53
|
+
* @param {Array} messages - 消息列表
|
|
54
|
+
* @returns {string} 结果文本
|
|
55
|
+
*/
|
|
56
|
+
function extractResultText(messages) {
|
|
57
|
+
const result = extractResult(messages);
|
|
58
|
+
return result?.result || '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 写入 session 分隔符到日志
|
|
63
|
+
* @param {import('fs').WriteStream} logStream - 日志写入流
|
|
64
|
+
* @param {number} sessionNum - session 编号
|
|
65
|
+
* @param {string} label - 标签
|
|
66
|
+
*/
|
|
67
|
+
function writeSessionSeparator(logStream, sessionNum, label) {
|
|
68
|
+
const sep = '='.repeat(60);
|
|
69
|
+
logStream.write(`\n${sep}\n[Session ${sessionNum}] ${label} ${localTimestamp()}\n${sep}\n`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
logMessage,
|
|
74
|
+
localTimestamp,
|
|
75
|
+
extractResult,
|
|
76
|
+
extractResultText,
|
|
77
|
+
writeSessionSeparator,
|
|
78
78
|
};
|
package/src/common/sdk.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { log } = require('./config');
|
|
6
|
-
|
|
7
|
-
let _sdkModule = null;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 加载 Claude Agent SDK
|
|
11
|
-
*/
|
|
12
|
-
async function loadSDK() {
|
|
13
|
-
if (_sdkModule) return _sdkModule;
|
|
14
|
-
|
|
15
|
-
const pkgName = '@anthropic-ai/claude-agent-sdk';
|
|
16
|
-
const attempts = [
|
|
17
|
-
() => import(pkgName),
|
|
18
|
-
() => {
|
|
19
|
-
const { createRequire } = require('module');
|
|
20
|
-
const resolved = createRequire(__filename).resolve(pkgName);
|
|
21
|
-
return import(resolved);
|
|
22
|
-
},
|
|
23
|
-
() => {
|
|
24
|
-
const { createRequire } = require('module');
|
|
25
|
-
const resolved = createRequire(path.join(process.cwd(), 'noop.js')).resolve(pkgName);
|
|
26
|
-
return import(resolved);
|
|
27
|
-
},
|
|
28
|
-
() => {
|
|
29
|
-
const { execSync } = require('child_process');
|
|
30
|
-
const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
31
|
-
const sdkDir = path.join(globalRoot, pkgName);
|
|
32
|
-
const pkgJson = JSON.parse(fs.readFileSync(path.join(sdkDir, 'package.json'), 'utf8'));
|
|
33
|
-
const entry = pkgJson.exports?.['.'] || pkgJson.main || 'index.js';
|
|
34
|
-
const entryFile = typeof entry === 'object' ? (entry.import || entry.default || entry.node) : entry;
|
|
35
|
-
return import(path.join(sdkDir, entryFile));
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
for (const attempt of attempts) {
|
|
40
|
-
try {
|
|
41
|
-
_sdkModule = await attempt();
|
|
42
|
-
return _sdkModule;
|
|
43
|
-
} catch { /* try next */ }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
log('error', `未找到 ${pkgName}`);
|
|
47
|
-
log('error', `请先安装:npm install -g ${pkgName}`);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { log } = require('./config');
|
|
6
|
+
|
|
7
|
+
let _sdkModule = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 加载 Claude Agent SDK
|
|
11
|
+
*/
|
|
12
|
+
async function loadSDK() {
|
|
13
|
+
if (_sdkModule) return _sdkModule;
|
|
14
|
+
|
|
15
|
+
const pkgName = '@anthropic-ai/claude-agent-sdk';
|
|
16
|
+
const attempts = [
|
|
17
|
+
() => import(pkgName),
|
|
18
|
+
() => {
|
|
19
|
+
const { createRequire } = require('module');
|
|
20
|
+
const resolved = createRequire(__filename).resolve(pkgName);
|
|
21
|
+
return import(resolved);
|
|
22
|
+
},
|
|
23
|
+
() => {
|
|
24
|
+
const { createRequire } = require('module');
|
|
25
|
+
const resolved = createRequire(path.join(process.cwd(), 'noop.js')).resolve(pkgName);
|
|
26
|
+
return import(resolved);
|
|
27
|
+
},
|
|
28
|
+
() => {
|
|
29
|
+
const { execSync } = require('child_process');
|
|
30
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
31
|
+
const sdkDir = path.join(globalRoot, pkgName);
|
|
32
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(sdkDir, 'package.json'), 'utf8'));
|
|
33
|
+
const entry = pkgJson.exports?.['.'] || pkgJson.main || 'index.js';
|
|
34
|
+
const entryFile = typeof entry === 'object' ? (entry.import || entry.default || entry.node) : entry;
|
|
35
|
+
return import(path.join(sdkDir, entryFile));
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const attempt of attempts) {
|
|
40
|
+
try {
|
|
41
|
+
_sdkModule = await attempt();
|
|
42
|
+
return _sdkModule;
|
|
43
|
+
} catch { /* try next */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
log('error', `未找到 ${pkgName}`);
|
|
47
|
+
log('error', `请先安装:npm install -g ${pkgName}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
51
|
module.exports = { loadSDK };
|