@yeaft/webchat-agent 0.1.248 → 0.1.249
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/connection/message-router.js +0 -26
- package/index.js +0 -2
- package/package.json +1 -1
- package/conductor/actor.js +0 -478
- package/conductor/claudemd-generator.js +0 -194
- package/conductor/conductor-claude.js +0 -480
- package/conductor/index.js +0 -65
- package/conductor/orchestrator-flows.js +0 -498
- package/conductor/orchestrator.js +0 -888
- package/conductor/persistence.js +0 -297
- package/conductor/personas/dev.js +0 -97
- package/conductor/personas/index.js +0 -77
- package/conductor/personas/trading.js +0 -92
- package/conductor/personas/video.js +0 -75
- package/conductor/personas/writing.js +0 -82
- package/conductor/semaphore.js +0 -73
- package/conductor/session.js +0 -210
- package/conductor/specialties/common.js +0 -350
- package/conductor/specialties/index.js +0 -135
- package/conductor/specialties/trading.js +0 -165
- package/conductor/specialties/video.js +0 -175
- package/conductor/specialties/writing.js +0 -120
- package/conductor/task.js +0 -333
- package/conductor/ui-messages.js +0 -170
- package/conductor/worktree.js +0 -182
- package/conductor.js +0 -35
|
@@ -20,11 +20,6 @@ import {
|
|
|
20
20
|
handleListCrewSessions, handleCheckCrewExists, handleDeleteCrewDir, resumeCrewSession, removeFromCrewIndex, hideCrewSession,
|
|
21
21
|
handleLoadCrewHistory, handleCheckCrewContext
|
|
22
22
|
} from '../crew.js';
|
|
23
|
-
import {
|
|
24
|
-
initConductor, handleConductorUserInput,
|
|
25
|
-
stopConductor, clearConductor,
|
|
26
|
-
handleConductorHistory
|
|
27
|
-
} from '../conductor.js';
|
|
28
23
|
import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
29
24
|
import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
30
25
|
import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
@@ -260,27 +255,6 @@ export async function handleMessage(msg) {
|
|
|
260
255
|
await handleLoadCrewHistory(msg);
|
|
261
256
|
break;
|
|
262
257
|
|
|
263
|
-
// Conductor (V2) messages
|
|
264
|
-
case 'open_conductor':
|
|
265
|
-
await initConductor(msg);
|
|
266
|
-
break;
|
|
267
|
-
|
|
268
|
-
case 'conductor_user_input':
|
|
269
|
-
await handleConductorUserInput(msg);
|
|
270
|
-
break;
|
|
271
|
-
|
|
272
|
-
case 'stop_conductor':
|
|
273
|
-
await stopConductor();
|
|
274
|
-
break;
|
|
275
|
-
|
|
276
|
-
case 'clear_conductor':
|
|
277
|
-
await clearConductor();
|
|
278
|
-
break;
|
|
279
|
-
|
|
280
|
-
case 'conductor_load_history':
|
|
281
|
-
await handleConductorHistory(msg);
|
|
282
|
-
break;
|
|
283
|
-
|
|
284
258
|
// Port proxy
|
|
285
259
|
case 'proxy_request':
|
|
286
260
|
handleProxyHttpRequest(msg);
|
package/index.js
CHANGED
|
@@ -99,8 +99,6 @@ async function detectCapabilities() {
|
|
|
99
99
|
const { getDefaultClaudeCodePath } = await import('./sdk/utils.js');
|
|
100
100
|
const claudePath = getDefaultClaudeCodePath();
|
|
101
101
|
if (claudePath) capabilities.push('crew');
|
|
102
|
-
// Conductor V2 reuses the same Claude CLI
|
|
103
|
-
if (claudePath) capabilities.push('conductor');
|
|
104
102
|
} catch {}
|
|
105
103
|
|
|
106
104
|
console.log(`[Capabilities] Detected: ${capabilities.join(', ')}`);
|
package/package.json
CHANGED
package/conductor/actor.js
DELETED
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conductor — Actor Claude 实例管理
|
|
3
|
-
*
|
|
4
|
-
* Actor 是最底层的执行单元:
|
|
5
|
-
* - 创建 actor session(写 CLAUDE.md → 启动 Claude)
|
|
6
|
-
* - 发送任务 → 收集结果 → 回调 Orchestrator
|
|
7
|
-
* - 释放 actor(清理 session)
|
|
8
|
-
*
|
|
9
|
-
* 复用 V1 的 createRoleQuery 模式,适配 Conductor 三层架构
|
|
10
|
-
*/
|
|
11
|
-
import { query, Stream } from '../sdk/index.js';
|
|
12
|
-
import { join } from 'path';
|
|
13
|
-
import { mkdirSync } from 'fs';
|
|
14
|
-
import ctx from '../context.js';
|
|
15
|
-
import { generateActorCLAUDEmd, generateActorDirName, generateActorInstanceId } from './claudemd-generator.js';
|
|
16
|
-
import { getPersonaById } from './personas/index.js';
|
|
17
|
-
import { getSpecialty, getThinkingMode } from './specialties/index.js';
|
|
18
|
-
|
|
19
|
-
// =====================================================================
|
|
20
|
-
// Actor Instance Registry
|
|
21
|
-
// =====================================================================
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Per-task actor registry: taskId → Map<instanceId, ActorInstance>
|
|
25
|
-
* @type {Map<string, Map<string, ActorInstance>>}
|
|
26
|
-
*/
|
|
27
|
-
const actorRegistry = new Map();
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Per-actor mutex to prevent concurrent createActorQuery calls
|
|
31
|
-
* @type {Map<string, Promise>}
|
|
32
|
-
*/
|
|
33
|
-
const _actorLocks = new Map();
|
|
34
|
-
|
|
35
|
-
// =====================================================================
|
|
36
|
-
// Actor Lifecycle
|
|
37
|
-
// =====================================================================
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 创建 Actor 实例
|
|
41
|
-
*
|
|
42
|
-
* 流程:生成 CLAUDE.md → 创建目录 → 启动 Claude query(appendSystemPrompt 注入)→ 注册
|
|
43
|
-
*
|
|
44
|
-
* @param {object} params
|
|
45
|
-
* @param {string} params.personaId - persona ID
|
|
46
|
-
* @param {string} params.specialtyId - specialty ID
|
|
47
|
-
* @param {string} params.taskId - 所属 task ID
|
|
48
|
-
* @param {string} params.taskDir - task 目录路径 (.conductor/tasks/task-N)
|
|
49
|
-
* @param {object} params.taskContext - 传递给 CLAUDE.md 生成器的上下文
|
|
50
|
-
* @param {string} [params.threadId] - 并行线程 ID(coding actor 用)
|
|
51
|
-
* @param {string} [params.worktreePath] - worktree 路径
|
|
52
|
-
* @param {string} [params.model] - 指定模型
|
|
53
|
-
* @param {function} [params.onOutput] - 输出回调 (instanceId, message) => void
|
|
54
|
-
* @param {function} [params.onComplete] - 完成回调 (instanceId, result) => void
|
|
55
|
-
* @param {function} [params.onError] - 错误回调 (instanceId, error) => void
|
|
56
|
-
* @returns {Promise<ActorInstance>}
|
|
57
|
-
*/
|
|
58
|
-
export async function createActor(params) {
|
|
59
|
-
const {
|
|
60
|
-
personaId,
|
|
61
|
-
specialtyId,
|
|
62
|
-
taskId,
|
|
63
|
-
taskDir,
|
|
64
|
-
taskContext = {},
|
|
65
|
-
threadId,
|
|
66
|
-
worktreePath,
|
|
67
|
-
model,
|
|
68
|
-
onOutput,
|
|
69
|
-
onComplete,
|
|
70
|
-
onError
|
|
71
|
-
} = params;
|
|
72
|
-
|
|
73
|
-
const instanceId = generateActorInstanceId(specialtyId, personaId, taskId, threadId);
|
|
74
|
-
const lockKey = instanceId;
|
|
75
|
-
|
|
76
|
-
// Mutex: 防止并发创建同一个 actor
|
|
77
|
-
if (_actorLocks.has(lockKey)) {
|
|
78
|
-
console.log(`[Actor] Waiting for existing lock on ${instanceId}`);
|
|
79
|
-
try { await _actorLocks.get(lockKey); } catch { /* 前一次失败了,继续 */ }
|
|
80
|
-
const existing = getActor(taskId, instanceId);
|
|
81
|
-
if (existing && existing.status === 'working') {
|
|
82
|
-
console.log(`[Actor] Reusing existing actor ${instanceId}`);
|
|
83
|
-
return existing;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const promise = _createActorInner(params, instanceId);
|
|
88
|
-
_actorLocks.set(lockKey, promise);
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
return await promise;
|
|
92
|
-
} finally {
|
|
93
|
-
if (_actorLocks.get(lockKey) === promise) {
|
|
94
|
-
_actorLocks.delete(lockKey);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* createActor 内部实现
|
|
101
|
-
*/
|
|
102
|
-
async function _createActorInner(params, instanceId) {
|
|
103
|
-
const {
|
|
104
|
-
personaId,
|
|
105
|
-
specialtyId,
|
|
106
|
-
taskId,
|
|
107
|
-
taskDir,
|
|
108
|
-
taskContext = {},
|
|
109
|
-
threadId,
|
|
110
|
-
worktreePath,
|
|
111
|
-
model,
|
|
112
|
-
onOutput,
|
|
113
|
-
onComplete,
|
|
114
|
-
onError
|
|
115
|
-
} = params;
|
|
116
|
-
|
|
117
|
-
const persona = getPersonaById(personaId);
|
|
118
|
-
if (!persona) throw new Error(`Persona not found: ${personaId}`);
|
|
119
|
-
|
|
120
|
-
const specialty = getSpecialty(specialtyId);
|
|
121
|
-
if (!specialty) throw new Error(`Specialty not found: ${specialtyId}`);
|
|
122
|
-
|
|
123
|
-
// 1. 生成 CLAUDE.md
|
|
124
|
-
const claudemdContent = generateActorCLAUDEmd(persona, specialtyId, {
|
|
125
|
-
...taskContext,
|
|
126
|
-
worktreePath
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// 2. 创建 actor 目录
|
|
130
|
-
const actorDirName = generateActorDirName(specialtyId, personaId, threadId);
|
|
131
|
-
const actorDir = join(taskDir, 'actors', actorDirName);
|
|
132
|
-
mkdirSync(actorDir, { recursive: true });
|
|
133
|
-
|
|
134
|
-
// 3. 确定 cwd(coding actor 用 worktree,其他用 actor 目录)
|
|
135
|
-
// CLAUDE.md 通过 appendSystemPrompt 注入 SDK,不写入文件(避免双重注入)
|
|
136
|
-
const actorCwd = worktreePath || actorDir;
|
|
137
|
-
mkdirSync(actorCwd, { recursive: true });
|
|
138
|
-
|
|
139
|
-
// 5. 创建 Claude query
|
|
140
|
-
const inputStream = new Stream();
|
|
141
|
-
const abortController = new AbortController();
|
|
142
|
-
|
|
143
|
-
// 继承全局 disallowedTools
|
|
144
|
-
const globalDisallowed = ctx.CONFIG?.disallowedTools || [];
|
|
145
|
-
const actorDisallowed = ['Agent']; // Actor 不允许使用 Agent 工具
|
|
146
|
-
const effectiveDisallowed = [...globalDisallowed, ...actorDisallowed];
|
|
147
|
-
|
|
148
|
-
const queryOptions = {
|
|
149
|
-
cwd: actorCwd,
|
|
150
|
-
permissionMode: 'bypassPermissions',
|
|
151
|
-
abort: abortController.signal,
|
|
152
|
-
model: model || undefined,
|
|
153
|
-
appendSystemPrompt: claudemdContent,
|
|
154
|
-
...(effectiveDisallowed.length > 0 && { disallowedTools: effectiveDisallowed })
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const roleQuery = query({
|
|
158
|
-
prompt: inputStream,
|
|
159
|
-
options: queryOptions
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// 6. 创建 ActorInstance
|
|
163
|
-
const thinkingMode = getThinkingMode(specialtyId);
|
|
164
|
-
|
|
165
|
-
/** @type {ActorInstance} */
|
|
166
|
-
const instance = {
|
|
167
|
-
instanceId,
|
|
168
|
-
personaId,
|
|
169
|
-
personaName: persona.name,
|
|
170
|
-
specialtyId,
|
|
171
|
-
specialtyDisplayName: specialty.displayName,
|
|
172
|
-
thinkingMode,
|
|
173
|
-
taskId,
|
|
174
|
-
threadId: threadId || null,
|
|
175
|
-
worktreePath: worktreePath || null,
|
|
176
|
-
actorDir,
|
|
177
|
-
claudeSessionId: null,
|
|
178
|
-
status: 'working',
|
|
179
|
-
activity: `${persona.name} 正在执行 ${specialty.displayName}`,
|
|
180
|
-
startedAt: Date.now(),
|
|
181
|
-
// 内部管理
|
|
182
|
-
_query: roleQuery,
|
|
183
|
-
_inputStream: inputStream,
|
|
184
|
-
_abortController: abortController,
|
|
185
|
-
_accumulatedText: '',
|
|
186
|
-
_onOutput: onOutput || null,
|
|
187
|
-
_onComplete: onComplete || null,
|
|
188
|
-
_onError: onError || null
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
// 7. 注册到 registry
|
|
192
|
-
if (!actorRegistry.has(taskId)) {
|
|
193
|
-
actorRegistry.set(taskId, new Map());
|
|
194
|
-
}
|
|
195
|
-
actorRegistry.get(taskId).set(instanceId, instance);
|
|
196
|
-
|
|
197
|
-
// 8. 异步处理 Actor 输出
|
|
198
|
-
processActorOutput(instance);
|
|
199
|
-
|
|
200
|
-
console.log(`[Actor] Created ${instanceId}: ${persona.name} × ${specialtyId} (${thinkingMode})`);
|
|
201
|
-
return instance;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// =====================================================================
|
|
205
|
-
// Actor Communication
|
|
206
|
-
// =====================================================================
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* 向 Actor 发送任务消息
|
|
210
|
-
*
|
|
211
|
-
* @param {string} taskId - task ID
|
|
212
|
-
* @param {string} instanceId - actor instance ID
|
|
213
|
-
* @param {string} message - 要发送的消息
|
|
214
|
-
*/
|
|
215
|
-
export function sendToActor(taskId, instanceId, message) {
|
|
216
|
-
const instance = getActor(taskId, instanceId);
|
|
217
|
-
if (!instance) {
|
|
218
|
-
console.error(`[Actor] Cannot send to unknown actor: ${instanceId}`);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (instance._inputStream.isDone) {
|
|
223
|
-
console.error(`[Actor] Cannot send to completed actor: ${instanceId}`);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
console.log(`[Actor] Sending message to ${instanceId} (${message.length} chars)`);
|
|
228
|
-
instance._inputStream.enqueue(message);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* 异步处理 Actor 输出流
|
|
233
|
-
*/
|
|
234
|
-
async function processActorOutput(instance) {
|
|
235
|
-
const { instanceId, _query: roleQuery } = instance;
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
for await (const message of roleQuery) {
|
|
239
|
-
// 提取 sessionId
|
|
240
|
-
if (message.sessionId && !instance.claudeSessionId) {
|
|
241
|
-
instance.claudeSessionId = message.sessionId;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// 处理助手消息
|
|
245
|
-
if (message.type === 'assistant') {
|
|
246
|
-
const text = extractText(message);
|
|
247
|
-
if (text) {
|
|
248
|
-
instance._accumulatedText += text;
|
|
249
|
-
|
|
250
|
-
// 实时输出回调
|
|
251
|
-
if (instance._onOutput) {
|
|
252
|
-
try {
|
|
253
|
-
instance._onOutput(instanceId, message);
|
|
254
|
-
} catch (e) {
|
|
255
|
-
console.error(`[Actor] onOutput callback error for ${instanceId}:`, e.message);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// turn 结束的标志
|
|
262
|
-
if (message.type === 'result') {
|
|
263
|
-
instance.status = 'done';
|
|
264
|
-
instance.activity = '已完成';
|
|
265
|
-
|
|
266
|
-
const result = {
|
|
267
|
-
instanceId,
|
|
268
|
-
personaId: instance.personaId,
|
|
269
|
-
personaName: instance.personaName,
|
|
270
|
-
specialtyId: instance.specialtyId,
|
|
271
|
-
thinkingMode: instance.thinkingMode,
|
|
272
|
-
text: instance._accumulatedText,
|
|
273
|
-
costUsd: message.costUSD || 0,
|
|
274
|
-
inputTokens: message.inputTokens || 0,
|
|
275
|
-
outputTokens: message.outputTokens || 0,
|
|
276
|
-
duration: Date.now() - instance.startedAt
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
console.log(`[Actor] ${instanceId} completed (${result.duration}ms, $${result.costUsd.toFixed(4)})`);
|
|
280
|
-
|
|
281
|
-
if (instance._onComplete) {
|
|
282
|
-
try {
|
|
283
|
-
instance._onComplete(instanceId, result);
|
|
284
|
-
} catch (e) {
|
|
285
|
-
console.error(`[Actor] onComplete callback error for ${instanceId}:`, e.message);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch (error) {
|
|
291
|
-
instance.status = 'failed';
|
|
292
|
-
instance.activity = `失败: ${error.message}`;
|
|
293
|
-
console.error(`[Actor] ${instanceId} error:`, error.message);
|
|
294
|
-
|
|
295
|
-
if (instance._onError) {
|
|
296
|
-
try {
|
|
297
|
-
instance._onError(instanceId, error);
|
|
298
|
-
} catch (e) {
|
|
299
|
-
console.error(`[Actor] onError callback error for ${instanceId}:`, e.message);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* 从消息中提取文本内容
|
|
307
|
-
*/
|
|
308
|
-
function extractText(message) {
|
|
309
|
-
if (!message.message?.content) return '';
|
|
310
|
-
const content = message.message.content;
|
|
311
|
-
if (typeof content === 'string') return content;
|
|
312
|
-
if (Array.isArray(content)) {
|
|
313
|
-
return content
|
|
314
|
-
.filter(c => c.type === 'text')
|
|
315
|
-
.map(c => c.text)
|
|
316
|
-
.join('');
|
|
317
|
-
}
|
|
318
|
-
return '';
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// =====================================================================
|
|
322
|
-
// Actor Release & Cleanup
|
|
323
|
-
// =====================================================================
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* 释放 Actor 实例(清理 session)
|
|
327
|
-
*
|
|
328
|
-
* @param {string} taskId
|
|
329
|
-
* @param {string} instanceId
|
|
330
|
-
* @param {object} [options]
|
|
331
|
-
* @param {boolean} [options.force=false] - 强制终止(abort)
|
|
332
|
-
*/
|
|
333
|
-
export async function releaseActor(taskId, instanceId, options = {}) {
|
|
334
|
-
const instance = getActor(taskId, instanceId);
|
|
335
|
-
if (!instance) {
|
|
336
|
-
console.warn(`[Actor] Cannot release unknown actor: ${instanceId}`);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const { force = false } = options;
|
|
341
|
-
|
|
342
|
-
// 关闭 input stream
|
|
343
|
-
if (!instance._inputStream.isDone) {
|
|
344
|
-
instance._inputStream.done();
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// 强制终止
|
|
348
|
-
if (force && instance._abortController) {
|
|
349
|
-
instance._abortController.abort();
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
instance.status = 'done';
|
|
353
|
-
instance.activity = '已释放';
|
|
354
|
-
|
|
355
|
-
// 从 registry 移除,防止内存泄漏
|
|
356
|
-
const taskActors = actorRegistry.get(taskId);
|
|
357
|
-
if (taskActors) {
|
|
358
|
-
taskActors.delete(instanceId);
|
|
359
|
-
if (taskActors.size === 0) actorRegistry.delete(taskId);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
console.log(`[Actor] Released ${instanceId}${force ? ' (forced)' : ''}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* 释放指定 task 的全部 actor
|
|
367
|
-
*
|
|
368
|
-
* @param {string} taskId
|
|
369
|
-
* @param {object} [options]
|
|
370
|
-
* @param {boolean} [options.force=false]
|
|
371
|
-
*/
|
|
372
|
-
export async function releaseAllActors(taskId, options = {}) {
|
|
373
|
-
const taskActors = actorRegistry.get(taskId);
|
|
374
|
-
if (!taskActors) return;
|
|
375
|
-
|
|
376
|
-
const promises = [];
|
|
377
|
-
for (const [instanceId] of taskActors) {
|
|
378
|
-
promises.push(releaseActor(taskId, instanceId, options));
|
|
379
|
-
}
|
|
380
|
-
await Promise.allSettled(promises);
|
|
381
|
-
|
|
382
|
-
actorRegistry.delete(taskId);
|
|
383
|
-
console.log(`[Actor] Released all actors for task ${taskId}`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// =====================================================================
|
|
387
|
-
// Actor Query
|
|
388
|
-
// =====================================================================
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* 获取单个 actor 实例
|
|
392
|
-
* @param {string} taskId
|
|
393
|
-
* @param {string} instanceId
|
|
394
|
-
* @returns {ActorInstance|null}
|
|
395
|
-
*/
|
|
396
|
-
export function getActor(taskId, instanceId) {
|
|
397
|
-
return actorRegistry.get(taskId)?.get(instanceId) || null;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* 获取指定 task 的全部活跃 actor
|
|
402
|
-
* @param {string} taskId
|
|
403
|
-
* @returns {ActorInstance[]}
|
|
404
|
-
*/
|
|
405
|
-
export function getActiveActors(taskId) {
|
|
406
|
-
const taskActors = actorRegistry.get(taskId);
|
|
407
|
-
if (!taskActors) return [];
|
|
408
|
-
return Array.from(taskActors.values()).filter(a => a.status === 'working');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* 获取指定 task 的全部 actor(含已完成)
|
|
413
|
-
* @param {string} taskId
|
|
414
|
-
* @returns {ActorInstance[]}
|
|
415
|
-
*/
|
|
416
|
-
export function getAllActors(taskId) {
|
|
417
|
-
const taskActors = actorRegistry.get(taskId);
|
|
418
|
-
if (!taskActors) return [];
|
|
419
|
-
return Array.from(taskActors.values());
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* 获取 actor 实例的公开状态(用于前端展示)
|
|
424
|
-
* @param {ActorInstance} instance
|
|
425
|
-
* @returns {object}
|
|
426
|
-
*/
|
|
427
|
-
export function getActorPublicState(instance) {
|
|
428
|
-
return {
|
|
429
|
-
instanceId: instance.instanceId,
|
|
430
|
-
personaId: instance.personaId,
|
|
431
|
-
personaName: instance.personaName,
|
|
432
|
-
specialtyId: instance.specialtyId,
|
|
433
|
-
specialtyDisplayName: instance.specialtyDisplayName,
|
|
434
|
-
thinkingMode: instance.thinkingMode,
|
|
435
|
-
taskId: instance.taskId,
|
|
436
|
-
threadId: instance.threadId,
|
|
437
|
-
status: instance.status,
|
|
438
|
-
activity: instance.activity,
|
|
439
|
-
startedAt: instance.startedAt
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* 获取指定 task 全部 actor 的公开状态列表
|
|
445
|
-
* @param {string} taskId
|
|
446
|
-
* @returns {object[]}
|
|
447
|
-
*/
|
|
448
|
-
export function getActorStates(taskId) {
|
|
449
|
-
return getAllActors(taskId).map(getActorPublicState);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// =====================================================================
|
|
453
|
-
// Error Classification (复用 V1 模式)
|
|
454
|
-
// =====================================================================
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* 判断 actor 错误是否可恢复
|
|
458
|
-
* @param {Error} error
|
|
459
|
-
* @returns {{ recoverable: boolean, reason: string }}
|
|
460
|
-
*/
|
|
461
|
-
export function classifyActorError(error) {
|
|
462
|
-
const msg = error.message || '';
|
|
463
|
-
if (/context.*(window|limit|exceeded)|token.*limit|too.*(long|large)|max.*token/i.test(msg)) {
|
|
464
|
-
return { recoverable: true, reason: 'context_exceeded' };
|
|
465
|
-
}
|
|
466
|
-
if (/rate.?limit|429|overloaded|503|502|timeout|ECONNRESET|ETIMEDOUT/i.test(msg)) {
|
|
467
|
-
return { recoverable: true, reason: 'transient_api_error' };
|
|
468
|
-
}
|
|
469
|
-
if (/exited with code [1-9]/i.test(msg) && msg.length < 100) {
|
|
470
|
-
return { recoverable: true, reason: 'process_crashed' };
|
|
471
|
-
}
|
|
472
|
-
if (/spawn|ENOENT|not found/i.test(msg)) {
|
|
473
|
-
return { recoverable: false, reason: 'spawn_failed' };
|
|
474
|
-
}
|
|
475
|
-
return { recoverable: true, reason: 'unknown' };
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
export { actorRegistry };
|