claude-coder 1.9.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -214
- package/bin/cli.js +170 -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 -420
- 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 +259 -245
- package/src/common/config.js +147 -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 +48 -50
- package/src/common/tasks.js +88 -88
- package/src/common/utils.js +214 -213
- package/src/core/coding.js +35 -33
- package/src/core/design.js +268 -0
- package/src/core/go.js +264 -264
- package/src/core/hooks.js +514 -500
- package/src/core/init.js +175 -166
- package/src/core/plan.js +194 -188
- package/src/core/prompts.js +292 -247
- package/src/core/repair.js +36 -36
- package/src/core/runner.js +471 -471
- package/src/core/scan.js +94 -93
- package/src/core/session.js +294 -280
- package/src/core/simplify.js +76 -74
- package/src/core/state.js +120 -105
- package/src/index.js +80 -76
- package/templates/{codingSystem.md → coding/system.md} +65 -65
- package/templates/{codingUser.md → coding/user.md} +18 -17
- package/templates/design/base.md +103 -0
- package/templates/design/fixSystem.md +71 -0
- package/templates/design/fixUser.md +3 -0
- package/templates/design/init.md +304 -0
- package/templates/design/system.md +108 -0
- package/templates/design/user.md +11 -0
- package/templates/{goSystem.md → go/system.md} +130 -130
- package/templates/{bash-process.md → other/bash-process.md} +12 -12
- package/templates/{coreProtocol.md → other/coreProtocol.md} +30 -29
- package/templates/{guidance.json → other/guidance.json} +72 -72
- package/templates/{requirements.example.md → other/requirements.example.md} +57 -57
- package/templates/{test_rule.md → other/test_rule.md} +192 -194
- package/templates/{web-testing.md → other/web-testing.md} +17 -17
- package/templates/{planSystem.md → plan/system.md} +78 -78
- package/templates/{planUser.md → plan/user.md} +10 -9
- package/templates/{scanSystem.md → scan/system.md} +120 -120
- package/templates/{scanUser.md → scan/user.md} +10 -10
- package/types/index.d.ts +217 -217
package/src/core/session.js
CHANGED
|
@@ -1,280 +1,294 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { buildEnvVars, log } = require('../common/config');
|
|
6
|
-
const { Indicator } = require('../common/indicator');
|
|
7
|
-
const { logMessage: baseLogMessage, extractResult, writeSessionSeparator } = require('../common/logging');
|
|
8
|
-
const { createHooks } = require('./hooks');
|
|
9
|
-
const { assets } = require('../common/assets');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {Object} SessionRunOptions
|
|
13
|
-
* @property {string} logFileName - 日志文件名
|
|
14
|
-
* @property {import('fs').WriteStream} [logStream] - 外部日志流(与 logFileName 二选一)
|
|
15
|
-
* @property {number} [sessionNum=
|
|
16
|
-
* @property {string} [label=''] - 会话标签
|
|
17
|
-
* @property {(session: Session) => Promise<Object>} execute - 执行回调,接收 session 实例
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {Object} QueryResult
|
|
22
|
-
* @property {Array<Object>} messages - 所有 SDK 消息
|
|
23
|
-
* @property {boolean} success - 是否成功完成
|
|
24
|
-
* @property {string|null} subtype - 结果子类型
|
|
25
|
-
* @property {number|null} cost - 美元费用
|
|
26
|
-
* @property {Object|null} usage - token 用量 { input_tokens, output_tokens }
|
|
27
|
-
* @property {number|null} turns - 对话轮次
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @typedef {Object} RunQueryOpts
|
|
32
|
-
* @property {(message: Object, messages: Array<Object>) => void|'break'} [onMessage] - 每条消息的回调,返回 'break' 中断
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* SDK 会话管理类。通过 Session.run() 创建和管理一次完整的 AI 会话生命周期。
|
|
37
|
-
*
|
|
38
|
-
* 使用方式:
|
|
39
|
-
* ```js
|
|
40
|
-
* const result = await Session.run('coding', config, {
|
|
41
|
-
* logFileName: 'coding.log',
|
|
42
|
-
* async execute(session) {
|
|
43
|
-
* const queryOpts = session.buildQueryOptions();
|
|
44
|
-
* const { messages, success } = await session.runQuery(prompt, queryOpts);
|
|
45
|
-
* return { success };
|
|
46
|
-
* },
|
|
47
|
-
* });
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
class Session {
|
|
51
|
-
/** @type {Object|null} SDK 单例 */
|
|
52
|
-
static _sdk = null;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 确保 SDK 已加载(懒加载单例)
|
|
56
|
-
* @param {Object} config - 项目配置
|
|
57
|
-
* @returns {Promise<Object>} SDK 实例
|
|
58
|
-
*/
|
|
59
|
-
static async ensureSDK(config) {
|
|
60
|
-
if (!Session._sdk) {
|
|
61
|
-
Object.assign(process.env, buildEnvVars(config));
|
|
62
|
-
const { loadSDK } = require('../common/sdk');
|
|
63
|
-
Session._sdk = await loadSDK();
|
|
64
|
-
}
|
|
65
|
-
return Session._sdk;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 创建 Session 实例并执行回调,自动管理生命周期(日志、hooks、indicator)
|
|
70
|
-
* @param {string} type - 会话类型(coding | plan | scan | go | simplify | repair 等)
|
|
71
|
-
* @param {Object} config - 项目配置
|
|
72
|
-
* @param {SessionRunOptions} options - 运行选项
|
|
73
|
-
* @returns {Promise<Object>} 包含 exitCode、logFile、stalled 以及 execute 返回值
|
|
74
|
-
*/
|
|
75
|
-
static async run(type, config, { logFileName, logStream, sessionNum =
|
|
76
|
-
await Session.ensureSDK(config);
|
|
77
|
-
const session = new Session(type, config, { logFileName, logStream, sessionNum, label });
|
|
78
|
-
try {
|
|
79
|
-
const result = await execute(session);
|
|
80
|
-
session.finish();
|
|
81
|
-
return {
|
|
82
|
-
exitCode: session.isStalled() ? 2 : 0,
|
|
83
|
-
logFile: session.logFile,
|
|
84
|
-
stalled: session.isStalled(),
|
|
85
|
-
...result,
|
|
86
|
-
};
|
|
87
|
-
} catch (err) {
|
|
88
|
-
session.finish();
|
|
89
|
-
throw err;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param {string} type - 会话类型
|
|
95
|
-
* @param {Object} config - 项目配置
|
|
96
|
-
* @param {Object} options
|
|
97
|
-
* @param {string} options.logFileName - 日志文件名
|
|
98
|
-
* @param {import('fs').WriteStream} [options.logStream] - 外部日志流
|
|
99
|
-
* @param {number} [options.sessionNum=0]
|
|
100
|
-
* @param {string} [options.label='']
|
|
101
|
-
*/
|
|
102
|
-
constructor(type, config, { logFileName, logStream, sessionNum =
|
|
103
|
-
this.config = config;
|
|
104
|
-
this.type = type;
|
|
105
|
-
this.indicator = new Indicator();
|
|
106
|
-
/** @type {import('fs').WriteStream|null} */
|
|
107
|
-
this.logStream = null;
|
|
108
|
-
/** @type {string|null} */
|
|
109
|
-
this.logFile = null;
|
|
110
|
-
/** @type {Object|null} */
|
|
111
|
-
this.hooks = null;
|
|
112
|
-
/** @type {Function|null} */
|
|
113
|
-
this.cleanup = null;
|
|
114
|
-
this._isStalled = () => false;
|
|
115
|
-
this.abortController = new AbortController();
|
|
116
|
-
|
|
117
|
-
this._initLogging(logFileName, logStream);
|
|
118
|
-
writeSessionSeparator(this.logStream, sessionNum, label);
|
|
119
|
-
const stallTimeoutMin = this._initHooks(type);
|
|
120
|
-
this._startIndicator(sessionNum, stallTimeoutMin);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 构建 SDK query 选项,自动附加 hooks、abortController、权限模式
|
|
125
|
-
* @param {Object} [overrides={}] - 覆盖选项(permissionMode, projectRoot, model 等)
|
|
126
|
-
* @returns {Object} SDK query options
|
|
127
|
-
*/
|
|
128
|
-
buildQueryOptions(overrides = {}) {
|
|
129
|
-
const mode = overrides.permissionMode || 'bypassPermissions';
|
|
130
|
-
const base = {
|
|
131
|
-
permissionMode: mode,
|
|
132
|
-
cwd: overrides.projectRoot || assets.projectRoot,
|
|
133
|
-
env: buildEnvVars(this.config),
|
|
134
|
-
settingSources: ['project'],
|
|
135
|
-
hooks: this.hooks,
|
|
136
|
-
abortController: this.abortController,
|
|
137
|
-
};
|
|
138
|
-
if (mode === 'bypassPermissions') {
|
|
139
|
-
base.allowDangerouslySkipPermissions = true;
|
|
140
|
-
}
|
|
141
|
-
if (this.config.maxTurns > 0) base.maxTurns = this.config.maxTurns;
|
|
142
|
-
if (overrides.model) base.model = overrides.model;
|
|
143
|
-
else if (this.config.model) base.model = this.config.model;
|
|
144
|
-
return base;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 执行一次 SDK 查询,遍历消息流并收集结果
|
|
149
|
-
* @param {string} prompt - 用户提示
|
|
150
|
-
* @param {Object} queryOpts - SDK query 选项(通常来自 buildQueryOptions)
|
|
151
|
-
* @param {RunQueryOpts} [opts={}] - 额外选项(onMessage
|
|
152
|
-
* @returns {Promise<QueryResult>}
|
|
153
|
-
*/
|
|
154
|
-
async runQuery(prompt, queryOpts, opts = {}) {
|
|
155
|
-
if (this.logStream?.writable) {
|
|
156
|
-
const sep = '-'.repeat(40);
|
|
157
|
-
if (queryOpts.systemPrompt) {
|
|
158
|
-
this.logStream.write(`\n${sep}\n[SYSTEM_PROMPT]\n${sep}\n${queryOpts.systemPrompt}\n`);
|
|
159
|
-
}
|
|
160
|
-
this.logStream.write(`\n${sep}\n[USER_PROMPT]\n${sep}\n${prompt}\n${sep}\n\n`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const sdk = Session._sdk;
|
|
164
|
-
const messages = [];
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { buildEnvVars, log, COLOR } = require('../common/config');
|
|
6
|
+
const { Indicator } = require('../common/indicator');
|
|
7
|
+
const { logMessage: baseLogMessage, extractResult, writeSessionSeparator } = require('../common/logging');
|
|
8
|
+
const { createHooks } = require('./hooks');
|
|
9
|
+
const { assets } = require('../common/assets');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} SessionRunOptions
|
|
13
|
+
* @property {string} logFileName - 日志文件名
|
|
14
|
+
* @property {import('fs').WriteStream} [logStream] - 外部日志流(与 logFileName 二选一)
|
|
15
|
+
* @property {number} [sessionNum=1] - 会话编号
|
|
16
|
+
* @property {string} [label=''] - 会话标签
|
|
17
|
+
* @property {(session: Session) => Promise<Object>} execute - 执行回调,接收 session 实例
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} QueryResult
|
|
22
|
+
* @property {Array<Object>} messages - 所有 SDK 消息
|
|
23
|
+
* @property {boolean} success - 是否成功完成
|
|
24
|
+
* @property {string|null} subtype - 结果子类型
|
|
25
|
+
* @property {number|null} cost - 美元费用
|
|
26
|
+
* @property {Object|null} usage - token 用量 { input_tokens, output_tokens }
|
|
27
|
+
* @property {number|null} turns - 对话轮次
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} RunQueryOpts
|
|
32
|
+
* @property {(message: Object, messages: Array<Object>) => void|'break'} [onMessage] - 每条消息的回调,返回 'break' 中断
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* SDK 会话管理类。通过 Session.run() 创建和管理一次完整的 AI 会话生命周期。
|
|
37
|
+
*
|
|
38
|
+
* 使用方式:
|
|
39
|
+
* ```js
|
|
40
|
+
* const result = await Session.run('coding', config, {
|
|
41
|
+
* logFileName: 'coding.log',
|
|
42
|
+
* async execute(session) {
|
|
43
|
+
* const queryOpts = session.buildQueryOptions();
|
|
44
|
+
* const { messages, success } = await session.runQuery(prompt, queryOpts);
|
|
45
|
+
* return { success };
|
|
46
|
+
* },
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
class Session {
|
|
51
|
+
/** @type {Object|null} SDK 单例 */
|
|
52
|
+
static _sdk = null;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 确保 SDK 已加载(懒加载单例)
|
|
56
|
+
* @param {Object} config - 项目配置
|
|
57
|
+
* @returns {Promise<Object>} SDK 实例
|
|
58
|
+
*/
|
|
59
|
+
static async ensureSDK(config) {
|
|
60
|
+
if (!Session._sdk) {
|
|
61
|
+
Object.assign(process.env, buildEnvVars(config));
|
|
62
|
+
const { loadSDK } = require('../common/sdk');
|
|
63
|
+
Session._sdk = await loadSDK();
|
|
64
|
+
}
|
|
65
|
+
return Session._sdk;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 创建 Session 实例并执行回调,自动管理生命周期(日志、hooks、indicator)
|
|
70
|
+
* @param {string} type - 会话类型(coding | plan | scan | go | simplify | repair 等)
|
|
71
|
+
* @param {Object} config - 项目配置
|
|
72
|
+
* @param {SessionRunOptions} options - 运行选项
|
|
73
|
+
* @returns {Promise<Object>} 包含 exitCode、logFile、stalled 以及 execute 返回值
|
|
74
|
+
*/
|
|
75
|
+
static async run(type, config, { logFileName, logStream, sessionNum = 1, label = '', execute }) {
|
|
76
|
+
await Session.ensureSDK(config);
|
|
77
|
+
const session = new Session(type, config, { logFileName, logStream, sessionNum, label });
|
|
78
|
+
try {
|
|
79
|
+
const result = await execute(session);
|
|
80
|
+
session.finish();
|
|
81
|
+
return {
|
|
82
|
+
exitCode: session.isStalled() ? 2 : 0,
|
|
83
|
+
logFile: session.logFile,
|
|
84
|
+
stalled: session.isStalled(),
|
|
85
|
+
...result,
|
|
86
|
+
};
|
|
87
|
+
} catch (err) {
|
|
88
|
+
session.finish();
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} type - 会话类型
|
|
95
|
+
* @param {Object} config - 项目配置
|
|
96
|
+
* @param {Object} options
|
|
97
|
+
* @param {string} options.logFileName - 日志文件名
|
|
98
|
+
* @param {import('fs').WriteStream} [options.logStream] - 外部日志流
|
|
99
|
+
* @param {number} [options.sessionNum=0]
|
|
100
|
+
* @param {string} [options.label='']
|
|
101
|
+
*/
|
|
102
|
+
constructor(type, config, { logFileName, logStream, sessionNum = 1, label = '' }) {
|
|
103
|
+
this.config = config;
|
|
104
|
+
this.type = type;
|
|
105
|
+
this.indicator = new Indicator();
|
|
106
|
+
/** @type {import('fs').WriteStream|null} */
|
|
107
|
+
this.logStream = null;
|
|
108
|
+
/** @type {string|null} */
|
|
109
|
+
this.logFile = null;
|
|
110
|
+
/** @type {Object|null} */
|
|
111
|
+
this.hooks = null;
|
|
112
|
+
/** @type {Function|null} */
|
|
113
|
+
this.cleanup = null;
|
|
114
|
+
this._isStalled = () => false;
|
|
115
|
+
this.abortController = new AbortController();
|
|
116
|
+
|
|
117
|
+
this._initLogging(logFileName, logStream);
|
|
118
|
+
writeSessionSeparator(this.logStream, sessionNum, label);
|
|
119
|
+
const stallTimeoutMin = this._initHooks(type);
|
|
120
|
+
this._startIndicator(sessionNum, stallTimeoutMin);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 构建 SDK query 选项,自动附加 hooks、abortController、权限模式
|
|
125
|
+
* @param {Object} [overrides={}] - 覆盖选项(permissionMode, projectRoot, model 等)
|
|
126
|
+
* @returns {Object} SDK query options
|
|
127
|
+
*/
|
|
128
|
+
buildQueryOptions(overrides = {}) {
|
|
129
|
+
const mode = overrides.permissionMode || 'bypassPermissions';
|
|
130
|
+
const base = {
|
|
131
|
+
permissionMode: mode,
|
|
132
|
+
cwd: overrides.projectRoot || assets.projectRoot,
|
|
133
|
+
env: buildEnvVars(this.config),
|
|
134
|
+
settingSources: ['project'],
|
|
135
|
+
hooks: this.hooks,
|
|
136
|
+
abortController: this.abortController,
|
|
137
|
+
};
|
|
138
|
+
if (mode === 'bypassPermissions') {
|
|
139
|
+
base.allowDangerouslySkipPermissions = true;
|
|
140
|
+
}
|
|
141
|
+
if (this.config.maxTurns > 0) base.maxTurns = this.config.maxTurns;
|
|
142
|
+
if (overrides.model) base.model = overrides.model;
|
|
143
|
+
else if (this.config.model) base.model = this.config.model;
|
|
144
|
+
return base;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 执行一次 SDK 查询,遍历消息流并收集结果
|
|
149
|
+
* @param {string} prompt - 用户提示
|
|
150
|
+
* @param {Object} queryOpts - SDK query 选项(通常来自 buildQueryOptions)
|
|
151
|
+
* @param {RunQueryOpts} [opts={}] - 额外选项(onMessage 回调, continue, resume)
|
|
152
|
+
* @returns {Promise<QueryResult>}
|
|
153
|
+
*/
|
|
154
|
+
async runQuery(prompt, queryOpts, opts = {}) {
|
|
155
|
+
if (this.logStream?.writable) {
|
|
156
|
+
const sep = '-'.repeat(40);
|
|
157
|
+
if (queryOpts.systemPrompt) {
|
|
158
|
+
this.logStream.write(`\n${sep}\n[SYSTEM_PROMPT]\n${sep}\n${queryOpts.systemPrompt}\n`);
|
|
159
|
+
}
|
|
160
|
+
this.logStream.write(`\n${sep}\n[USER_PROMPT]\n${sep}\n${prompt}\n${sep}\n\n`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const sdk = Session._sdk;
|
|
164
|
+
const messages = [];
|
|
165
|
+
const queryPayload = { prompt, options: queryOpts };
|
|
166
|
+
if (opts.continue) queryPayload.options.continue = true;
|
|
167
|
+
if (opts.resume) queryPayload.options.resume = opts.resume;
|
|
168
|
+
const querySession = sdk.query(queryPayload);
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
for await (const message of querySession) {
|
|
172
|
+
if (this._isStalled()) {
|
|
173
|
+
log('warn', '停顿超时,中断消息循环');
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
messages.push(message);
|
|
177
|
+
this._logMessage(message);
|
|
178
|
+
|
|
179
|
+
if (opts.onMessage) {
|
|
180
|
+
const action = opts.onMessage(message, messages);
|
|
181
|
+
if (action === 'break') break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (this._isStalled()) {
|
|
186
|
+
log('warn', 'SDK 会话因停顿超时中断');
|
|
187
|
+
} else {
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const sdkResult = extractResult(messages);
|
|
193
|
+
this.logStream.write(`\n[SDK_RESULT] ${JSON.stringify(sdkResult, null, 2)}\n\n`);
|
|
194
|
+
const cost = sdkResult?.total_cost_usd || null;
|
|
195
|
+
const usage = sdkResult?.usage || null;
|
|
196
|
+
const turns = sdkResult?.num_turns || null;
|
|
197
|
+
const sessionId = sdkResult?.session_id || null;
|
|
198
|
+
|
|
199
|
+
if (cost != null || turns != null || sessionId) {
|
|
200
|
+
const fmtTokens = (n) => n >= 10000 ? `${(n / 1000).toFixed(1)}k` : String(n);
|
|
201
|
+
const fmtCost = (c) => c >= 1 ? `$${c.toFixed(2)}` : `$${c.toFixed(4)}`;
|
|
202
|
+
|
|
203
|
+
const cliParts = [];
|
|
204
|
+
if (sessionId) cliParts.push(`${COLOR.dim}sid:${COLOR.reset} ${sessionId.slice(0, 8)}`);
|
|
205
|
+
if (turns != null) cliParts.push(`${COLOR.dim}turns:${COLOR.reset} ${turns}`);
|
|
206
|
+
if (cost != null) cliParts.push(`${COLOR.dim}cost:${COLOR.reset} ${COLOR.yellow}${fmtCost(cost)}${COLOR.reset}`);
|
|
207
|
+
if (usage) {
|
|
208
|
+
const inp = usage.input_tokens || 0;
|
|
209
|
+
const out = usage.output_tokens || 0;
|
|
210
|
+
cliParts.push(`${COLOR.dim}tokens:${COLOR.reset} ${fmtTokens(inp)}+${fmtTokens(out)}`);
|
|
211
|
+
}
|
|
212
|
+
console.error(`${COLOR.dim}─── session end ───${COLOR.reset}`);
|
|
213
|
+
log('info', `session 统计: ${cliParts.join(' ')}`);
|
|
214
|
+
|
|
215
|
+
// 日志文件保留完整精度
|
|
216
|
+
const logParts = [];
|
|
217
|
+
if (sessionId) logParts.push(`sid: ${sessionId}`);
|
|
218
|
+
if (turns != null) logParts.push(`turns: ${turns}`);
|
|
219
|
+
if (cost != null) logParts.push(`cost: $${cost}`);
|
|
220
|
+
if (usage) logParts.push(`tokens: ${usage.input_tokens || 0}+${usage.output_tokens || 0}`);
|
|
221
|
+
if (this.logStream?.writable) {
|
|
222
|
+
this.logStream.write(`[SESSION_INFO] ${logParts.join(', ')}\n`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
messages,
|
|
228
|
+
success: sdkResult?.subtype === 'success',
|
|
229
|
+
subtype: sdkResult?.subtype || null,
|
|
230
|
+
cost, usage, turns, sessionId,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** 检查会话是否因停顿超时 */
|
|
235
|
+
isStalled() {
|
|
236
|
+
return this._isStalled();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** 结束会话:清理 hooks、关闭日志流、停止 indicator */
|
|
240
|
+
finish() {
|
|
241
|
+
if (this.cleanup) this.cleanup();
|
|
242
|
+
if (this.logStream && !this._externalLogStream) this.logStream.end();
|
|
243
|
+
this.indicator.stop();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ─── Private ────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
_initLogging(logFileName, externalLogStream) {
|
|
249
|
+
if (externalLogStream) {
|
|
250
|
+
this.logStream = externalLogStream;
|
|
251
|
+
this._externalLogStream = true;
|
|
252
|
+
} else {
|
|
253
|
+
const logsDir = assets.dir('logs');
|
|
254
|
+
this.logFile = path.join(logsDir, logFileName);
|
|
255
|
+
this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
|
|
256
|
+
this._externalLogStream = false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_initHooks(hookType) {
|
|
261
|
+
const stallTimeoutMs = this.config.stallTimeout * 1000;
|
|
262
|
+
const result = createHooks(hookType, this.indicator, this.logStream, {
|
|
263
|
+
stallTimeoutMs,
|
|
264
|
+
abortController: this.abortController,
|
|
265
|
+
editThreshold: this.config.editThreshold,
|
|
266
|
+
});
|
|
267
|
+
this.hooks = result.hooks;
|
|
268
|
+
this.cleanup = result.cleanup;
|
|
269
|
+
this._isStalled = result.isStalled;
|
|
270
|
+
return Math.floor(stallTimeoutMs / 60000);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_startIndicator(sessionNum, stallTimeoutMin) {
|
|
274
|
+
this.indicator.start(sessionNum, stallTimeoutMin, assets.projectRoot);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_logMessage(message) {
|
|
278
|
+
const hasText = message.type === 'assistant'
|
|
279
|
+
&& message.message?.content?.some(b => b.type === 'text' && b.text);
|
|
280
|
+
|
|
281
|
+
if (hasText && this.indicator) {
|
|
282
|
+
this.indicator.pauseRendering();
|
|
283
|
+
process.stderr.write('\r\x1b[K');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
baseLogMessage(message, this.logStream, this.indicator);
|
|
287
|
+
|
|
288
|
+
if (hasText && this.indicator) {
|
|
289
|
+
this.indicator.resumeRendering();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module.exports = { Session };
|