claude-coder 1.9.0 → 1.9.1

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.
Files changed (74) hide show
  1. package/README.md +214 -214
  2. package/bin/cli.js +155 -155
  3. package/package.json +55 -55
  4. package/recipes/_shared/roles/developer.md +11 -11
  5. package/recipes/_shared/roles/product.md +12 -12
  6. package/recipes/_shared/roles/tester.md +12 -12
  7. package/recipes/_shared/test/report-format.md +86 -86
  8. package/recipes/backend/base.md +27 -27
  9. package/recipes/backend/components/auth.md +18 -18
  10. package/recipes/backend/components/crud-api.md +18 -18
  11. package/recipes/backend/components/file-service.md +15 -15
  12. package/recipes/backend/manifest.json +20 -20
  13. package/recipes/backend/test/api-test.md +25 -25
  14. package/recipes/console/base.md +37 -37
  15. package/recipes/console/components/modal-form.md +20 -20
  16. package/recipes/console/components/pagination.md +17 -17
  17. package/recipes/console/components/search.md +17 -17
  18. package/recipes/console/components/table-list.md +18 -18
  19. package/recipes/console/components/tabs.md +14 -14
  20. package/recipes/console/components/tree.md +15 -15
  21. package/recipes/console/components/upload.md +15 -15
  22. package/recipes/console/manifest.json +24 -24
  23. package/recipes/console/test/crud-e2e.md +47 -47
  24. package/recipes/h5/base.md +26 -26
  25. package/recipes/h5/components/animation.md +11 -11
  26. package/recipes/h5/components/countdown.md +11 -11
  27. package/recipes/h5/components/share.md +11 -11
  28. package/recipes/h5/components/swiper.md +11 -11
  29. package/recipes/h5/manifest.json +21 -21
  30. package/recipes/h5/test/h5-e2e.md +20 -20
  31. package/src/commands/auth.js +362 -362
  32. package/src/commands/setup-modules/helpers.js +100 -100
  33. package/src/commands/setup-modules/index.js +25 -25
  34. package/src/commands/setup-modules/mcp.js +115 -115
  35. package/src/commands/setup-modules/provider.js +260 -260
  36. package/src/commands/setup-modules/safety.js +47 -47
  37. package/src/commands/setup-modules/simplify.js +52 -52
  38. package/src/commands/setup.js +172 -172
  39. package/src/common/assets.js +245 -245
  40. package/src/common/config.js +125 -125
  41. package/src/common/constants.js +55 -55
  42. package/src/common/indicator.js +260 -260
  43. package/src/common/interaction.js +170 -170
  44. package/src/common/logging.js +77 -77
  45. package/src/common/sdk.js +50 -50
  46. package/src/common/tasks.js +88 -88
  47. package/src/common/utils.js +213 -213
  48. package/src/core/coding.js +33 -33
  49. package/src/core/go.js +264 -264
  50. package/src/core/hooks.js +500 -500
  51. package/src/core/init.js +166 -165
  52. package/src/core/plan.js +188 -187
  53. package/src/core/prompts.js +247 -247
  54. package/src/core/repair.js +36 -36
  55. package/src/core/runner.js +458 -458
  56. package/src/core/scan.js +93 -93
  57. package/src/core/session.js +271 -271
  58. package/src/core/simplify.js +74 -74
  59. package/src/core/state.js +105 -105
  60. package/src/index.js +76 -76
  61. package/templates/bash-process.md +12 -12
  62. package/templates/codingSystem.md +65 -65
  63. package/templates/codingUser.md +17 -17
  64. package/templates/coreProtocol.md +29 -29
  65. package/templates/goSystem.md +130 -130
  66. package/templates/guidance.json +72 -72
  67. package/templates/planSystem.md +78 -78
  68. package/templates/planUser.md +8 -8
  69. package/templates/requirements.example.md +57 -57
  70. package/templates/scanSystem.md +120 -120
  71. package/templates/scanUser.md +10 -10
  72. package/templates/test_rule.md +194 -194
  73. package/templates/web-testing.md +17 -17
  74. package/types/index.d.ts +217 -217
@@ -1,271 +1,271 @@
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=0] - 会话编号
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 = 0, 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 = 0, 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 回调)
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 querySession = sdk.query({ prompt, options: queryOpts });
166
-
167
- for await (const message of querySession) {
168
- if (this._isStalled()) {
169
- log('warn', '停顿超时,中断消息循环');
170
- break;
171
- }
172
- messages.push(message);
173
- this._logMessage(message);
174
-
175
- if (opts.onMessage) {
176
- const action = opts.onMessage(message, messages);
177
- if (action === 'break') break;
178
- }
179
- }
180
-
181
- const sdkResult = extractResult(messages);
182
- const cost = sdkResult?.total_cost_usd || null;
183
- const usage = sdkResult?.usage || null;
184
- const turns = sdkResult?.num_turns || null;
185
-
186
- if (cost != null || turns != null) {
187
- const parts = [];
188
- if (turns != null) parts.push(`turns: ${turns}`);
189
- if (cost != null) parts.push(`cost: $${cost}`);
190
- if (usage) {
191
- const inp = usage.input_tokens || 0;
192
- const out = usage.output_tokens || 0;
193
- parts.push(`tokens: ${inp}+${out}`);
194
- }
195
- const summary = parts.join(', ');
196
- console.log('----- SESSION END -----');
197
- log('info', `session 统计: ${summary}`);
198
- if (this.logStream?.writable) {
199
- this.logStream.write(`[SESSION_STATS] ${summary}\n`);
200
- }
201
- }
202
-
203
- return {
204
- messages,
205
- success: sdkResult?.subtype === 'success',
206
- subtype: sdkResult?.subtype || null,
207
- cost, usage, turns,
208
- };
209
- }
210
-
211
- /** 检查会话是否因停顿超时 */
212
- isStalled() {
213
- return this._isStalled();
214
- }
215
-
216
- /** 结束会话:清理 hooks、关闭日志流、停止 indicator */
217
- finish() {
218
- if (this.cleanup) this.cleanup();
219
- if (this.logStream && !this._externalLogStream) this.logStream.end();
220
- this.indicator.stop();
221
- }
222
-
223
- // ─── Private ────────────────────────────────────────────
224
-
225
- _initLogging(logFileName, externalLogStream) {
226
- if (externalLogStream) {
227
- this.logStream = externalLogStream;
228
- this._externalLogStream = true;
229
- } else {
230
- const logsDir = assets.dir('logs');
231
- this.logFile = path.join(logsDir, logFileName);
232
- this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
233
- this._externalLogStream = false;
234
- }
235
- }
236
-
237
- _initHooks(hookType) {
238
- const stallTimeoutMs = this.config.stallTimeout * 1000;
239
- const result = createHooks(hookType, this.indicator, this.logStream, {
240
- stallTimeoutMs,
241
- abortController: this.abortController,
242
- editThreshold: this.config.editThreshold,
243
- });
244
- this.hooks = result.hooks;
245
- this.cleanup = result.cleanup;
246
- this._isStalled = result.isStalled;
247
- return Math.floor(stallTimeoutMs / 60000);
248
- }
249
-
250
- _startIndicator(sessionNum, stallTimeoutMin) {
251
- this.indicator.start(sessionNum, stallTimeoutMin, assets.projectRoot);
252
- }
253
-
254
- _logMessage(message) {
255
- const hasText = message.type === 'assistant'
256
- && message.message?.content?.some(b => b.type === 'text' && b.text);
257
-
258
- if (hasText && this.indicator) {
259
- this.indicator.pauseRendering();
260
- process.stderr.write('\r\x1b[K');
261
- }
262
-
263
- baseLogMessage(message, this.logStream, this.indicator);
264
-
265
- if (hasText && this.indicator) {
266
- this.indicator.resumeRendering();
267
- }
268
- }
269
- }
270
-
271
- module.exports = { Session };
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=0] - 会话编号
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 = 0, 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 = 0, 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 回调)
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 querySession = sdk.query({ prompt, options: queryOpts });
166
+
167
+ for await (const message of querySession) {
168
+ if (this._isStalled()) {
169
+ log('warn', '停顿超时,中断消息循环');
170
+ break;
171
+ }
172
+ messages.push(message);
173
+ this._logMessage(message);
174
+
175
+ if (opts.onMessage) {
176
+ const action = opts.onMessage(message, messages);
177
+ if (action === 'break') break;
178
+ }
179
+ }
180
+
181
+ const sdkResult = extractResult(messages);
182
+ const cost = sdkResult?.total_cost_usd || null;
183
+ const usage = sdkResult?.usage || null;
184
+ const turns = sdkResult?.num_turns || null;
185
+
186
+ if (cost != null || turns != null) {
187
+ const parts = [];
188
+ if (turns != null) parts.push(`turns: ${turns}`);
189
+ if (cost != null) parts.push(`cost: $${cost}`);
190
+ if (usage) {
191
+ const inp = usage.input_tokens || 0;
192
+ const out = usage.output_tokens || 0;
193
+ parts.push(`tokens: ${inp}+${out}`);
194
+ }
195
+ const summary = parts.join(', ');
196
+ console.log('----- SESSION END -----');
197
+ log('info', `session 统计: ${summary}`);
198
+ if (this.logStream?.writable) {
199
+ this.logStream.write(`[SESSION_STATS] ${summary}\n`);
200
+ }
201
+ }
202
+
203
+ return {
204
+ messages,
205
+ success: sdkResult?.subtype === 'success',
206
+ subtype: sdkResult?.subtype || null,
207
+ cost, usage, turns,
208
+ };
209
+ }
210
+
211
+ /** 检查会话是否因停顿超时 */
212
+ isStalled() {
213
+ return this._isStalled();
214
+ }
215
+
216
+ /** 结束会话:清理 hooks、关闭日志流、停止 indicator */
217
+ finish() {
218
+ if (this.cleanup) this.cleanup();
219
+ if (this.logStream && !this._externalLogStream) this.logStream.end();
220
+ this.indicator.stop();
221
+ }
222
+
223
+ // ─── Private ────────────────────────────────────────────
224
+
225
+ _initLogging(logFileName, externalLogStream) {
226
+ if (externalLogStream) {
227
+ this.logStream = externalLogStream;
228
+ this._externalLogStream = true;
229
+ } else {
230
+ const logsDir = assets.dir('logs');
231
+ this.logFile = path.join(logsDir, logFileName);
232
+ this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
233
+ this._externalLogStream = false;
234
+ }
235
+ }
236
+
237
+ _initHooks(hookType) {
238
+ const stallTimeoutMs = this.config.stallTimeout * 1000;
239
+ const result = createHooks(hookType, this.indicator, this.logStream, {
240
+ stallTimeoutMs,
241
+ abortController: this.abortController,
242
+ editThreshold: this.config.editThreshold,
243
+ });
244
+ this.hooks = result.hooks;
245
+ this.cleanup = result.cleanup;
246
+ this._isStalled = result.isStalled;
247
+ return Math.floor(stallTimeoutMs / 60000);
248
+ }
249
+
250
+ _startIndicator(sessionNum, stallTimeoutMin) {
251
+ this.indicator.start(sessionNum, stallTimeoutMin, assets.projectRoot);
252
+ }
253
+
254
+ _logMessage(message) {
255
+ const hasText = message.type === 'assistant'
256
+ && message.message?.content?.some(b => b.type === 'text' && b.text);
257
+
258
+ if (hasText && this.indicator) {
259
+ this.indicator.pauseRendering();
260
+ process.stderr.write('\r\x1b[K');
261
+ }
262
+
263
+ baseLogMessage(message, this.logStream, this.indicator);
264
+
265
+ if (hasText && this.indicator) {
266
+ this.indicator.resumeRendering();
267
+ }
268
+ }
269
+ }
270
+
271
+ module.exports = { Session };