@yeaft/webchat-agent 0.1.224 → 0.1.226
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/conductor/actor.js +478 -0
- package/conductor/claudemd-generator.js +194 -0
- package/conductor/conductor-claude.js +472 -0
- package/conductor/index.js +65 -0
- package/conductor/orchestrator-flows.js +498 -0
- package/conductor/orchestrator.js +888 -0
- package/conductor/persistence.js +297 -0
- package/conductor/personas/dev.js +97 -0
- package/conductor/personas/index.js +77 -0
- package/conductor/personas/trading.js +92 -0
- package/conductor/personas/video.js +75 -0
- package/conductor/personas/writing.js +82 -0
- package/conductor/semaphore.js +73 -0
- package/conductor/session.js +208 -0
- package/conductor/specialties/common.js +350 -0
- package/conductor/specialties/index.js +135 -0
- package/conductor/specialties/trading.js +165 -0
- package/conductor/specialties/video.js +175 -0
- package/conductor/specialties/writing.js +120 -0
- package/conductor/task.js +333 -0
- package/conductor/ui-messages.js +170 -0
- package/conductor/worktree.js +182 -0
- package/conductor.js +21 -32
- package/connection/message-router.js +10 -32
- package/package.json +2 -8
- package/scripts/agent-tray.ps1 +0 -134
- package/scripts/create-startup-shortcut.ps1 +0 -18
- package/scripts/ecosystem.config.cjs +0 -46
- package/scripts/pm2-start.ps1 +0 -59
- package/scripts/start-with-tray.ps1 +0 -36
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Conductor Claude Instance (V5)
|
|
3
|
+
*
|
|
4
|
+
* Conductor Claude is a persistent Claude Code process that:
|
|
5
|
+
* 1. Classifies messages (Q&A / create task / forward to task / status)
|
|
6
|
+
* 2. Answers simple questions
|
|
7
|
+
* 3. Never does task analysis or step decomposition
|
|
8
|
+
*
|
|
9
|
+
* cwd = Conductor Home (~/.config/yeaft-agent/.conductor/)
|
|
10
|
+
* State.json summary injected into systemPrompt for task awareness.
|
|
11
|
+
*/
|
|
12
|
+
import { query, Stream } from '../sdk/index.js';
|
|
13
|
+
import { promises as fs } from 'fs';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { globalSemaphore } from './semaphore.js';
|
|
16
|
+
import {
|
|
17
|
+
sendConductorMessage, sendConductorOutput,
|
|
18
|
+
endConductorStreaming, sendStatusUpdate
|
|
19
|
+
} from './ui-messages.js';
|
|
20
|
+
import {
|
|
21
|
+
getConductorHome, ensureConductorHome,
|
|
22
|
+
loadState, saveState, updateTaskInState, initTaskDir
|
|
23
|
+
} from './persistence.js';
|
|
24
|
+
|
|
25
|
+
// =====================================================================
|
|
26
|
+
// Claude Session ID Persistence
|
|
27
|
+
// =====================================================================
|
|
28
|
+
|
|
29
|
+
async function saveConductorSessionId(claudeSessionId) {
|
|
30
|
+
const dir = await ensureConductorHome();
|
|
31
|
+
const filePath = join(dir, 'claude-session.json');
|
|
32
|
+
await fs.writeFile(filePath, JSON.stringify({
|
|
33
|
+
claudeSessionId, savedAt: Date.now()
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function loadConductorSessionId() {
|
|
38
|
+
const filePath = join(getConductorHome(), 'claude-session.json');
|
|
39
|
+
try {
|
|
40
|
+
const data = JSON.parse(await fs.readFile(filePath, 'utf-8'));
|
|
41
|
+
return data.claudeSessionId || null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function clearConductorSessionId() {
|
|
48
|
+
const filePath = join(getConductorHome(), 'claude-session.json');
|
|
49
|
+
try { await fs.unlink(filePath); } catch { /* ok */ }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// =====================================================================
|
|
53
|
+
// System Prompt
|
|
54
|
+
// =====================================================================
|
|
55
|
+
|
|
56
|
+
function buildConductorSystemPrompt(conductor) {
|
|
57
|
+
// Build task summaries from in-memory tasks map
|
|
58
|
+
const taskLines = [];
|
|
59
|
+
for (const [taskId, t] of conductor.tasks) {
|
|
60
|
+
const actors = (t.activeActors || []).join(', ');
|
|
61
|
+
taskLines.push(`- ${taskId}: ${t.title} [${t.status}${t.currentStep ? ' ' + t.currentStep : ''}] workDir=${t.workDir} scenario=${t.scenario}${actors ? ' actors=' + actors : ''}`);
|
|
62
|
+
}
|
|
63
|
+
const taskSummaries = taskLines.join('\n');
|
|
64
|
+
|
|
65
|
+
return `# Conductor — 交响乐指挥台
|
|
66
|
+
|
|
67
|
+
你是 Conductor,项目的总指挥。你的职责是:
|
|
68
|
+
1. **分类消息**:判断用户的输入是简单问答还是需要创建任务
|
|
69
|
+
2. **创建任务**:通过 CREATE_TASK 指令创建新任务(必须指定 workDir 和 scenario)
|
|
70
|
+
3. **转发消息**:通过 FORWARD_TASK 将用户消息传递给已有任务
|
|
71
|
+
4. **回答问题**:直接回答简单的问题
|
|
72
|
+
5. **汇报状态**:用户询问进度时汇总所有任务状态
|
|
73
|
+
|
|
74
|
+
## 你绝对不做的事
|
|
75
|
+
- 不分析任务难度
|
|
76
|
+
- 不拆解任务步骤
|
|
77
|
+
- 不评估需要几个人
|
|
78
|
+
- 不写代码
|
|
79
|
+
|
|
80
|
+
## 当前活跃任务
|
|
81
|
+
${taskSummaries || '(无)'}
|
|
82
|
+
|
|
83
|
+
## 指令格式
|
|
84
|
+
|
|
85
|
+
### 创建任务
|
|
86
|
+
当用户的需求需要实际的开发/分析/设计工作时,输出:
|
|
87
|
+
|
|
88
|
+
\`\`\`
|
|
89
|
+
---CREATE_TASK---
|
|
90
|
+
title: <任务标题>
|
|
91
|
+
workDir: <项目工作路径>
|
|
92
|
+
scenario: <dev|writing|trading|video>
|
|
93
|
+
---END_CREATE_TASK---
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
### 转发消息到已有任务
|
|
97
|
+
当用户的消息是针对某个已有任务的补充说明时,输出:
|
|
98
|
+
|
|
99
|
+
\`\`\`
|
|
100
|
+
---FORWARD_TASK---
|
|
101
|
+
taskId: <task-id>
|
|
102
|
+
message: <要转发的内容>
|
|
103
|
+
---END_FORWARD_TASK---
|
|
104
|
+
\`\`\`
|
|
105
|
+
|
|
106
|
+
### 回答简单问题
|
|
107
|
+
直接用文本回答,不需要任何指令块。
|
|
108
|
+
|
|
109
|
+
## 规则
|
|
110
|
+
- 每条用户消息只需要一种响应:创建任务 / 转发 / 直接回答
|
|
111
|
+
- 创建任务时只给标题,不要描述实现步骤
|
|
112
|
+
- 创建任务时必须指定 workDir(用户提到的项目路径)和 scenario(场景类型)
|
|
113
|
+
- 如果用户没指定 workDir 或 scenario,先询问
|
|
114
|
+
- 如果用户问"进度怎样",直接汇总上面的任务列表状态
|
|
115
|
+
- 保持回复简洁
|
|
116
|
+
- 用中文回复`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// =====================================================================
|
|
120
|
+
// Conductor Claude Instance
|
|
121
|
+
// =====================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create or rebuild Conductor Claude instance
|
|
125
|
+
*/
|
|
126
|
+
export async function createConductorClaude(conductor) {
|
|
127
|
+
const conductorHome = await ensureConductorHome();
|
|
128
|
+
|
|
129
|
+
// Acquire semaphore
|
|
130
|
+
const release = await globalSemaphore.acquire();
|
|
131
|
+
conductor._conductorSemRelease = release;
|
|
132
|
+
conductor.activeClaudes = (conductor.activeClaudes || 0) + 1;
|
|
133
|
+
|
|
134
|
+
const inputStream = new Stream();
|
|
135
|
+
const abortController = new AbortController();
|
|
136
|
+
const systemPrompt = buildConductorSystemPrompt(conductor);
|
|
137
|
+
|
|
138
|
+
// Try to resume existing Claude session
|
|
139
|
+
const savedSessionId = await loadConductorSessionId();
|
|
140
|
+
|
|
141
|
+
const queryOptions = {
|
|
142
|
+
cwd: conductorHome,
|
|
143
|
+
permissionMode: 'bypassPermissions',
|
|
144
|
+
abort: abortController.signal,
|
|
145
|
+
appendSystemPrompt: systemPrompt
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (savedSessionId) {
|
|
149
|
+
queryOptions.resume = savedSessionId;
|
|
150
|
+
console.log(`[Conductor] Resuming Claude with sessionId: ${savedSessionId}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const conductorQuery = query({
|
|
154
|
+
prompt: inputStream,
|
|
155
|
+
options: queryOptions
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
conductor.conductorState = {
|
|
159
|
+
query: conductorQuery,
|
|
160
|
+
inputStream,
|
|
161
|
+
abortController,
|
|
162
|
+
claudeSessionId: savedSessionId,
|
|
163
|
+
accumulatedText: '',
|
|
164
|
+
turnActive: false,
|
|
165
|
+
lastCostUsd: 0,
|
|
166
|
+
lastInputTokens: 0,
|
|
167
|
+
lastOutputTokens: 0
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Start output processing loop
|
|
171
|
+
processConductorOutput(conductor, conductorQuery);
|
|
172
|
+
|
|
173
|
+
return conductor.conductorState;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Send message to Conductor Claude
|
|
178
|
+
*/
|
|
179
|
+
export async function sendToConductor(conductor, content) {
|
|
180
|
+
let state = conductor.conductorState;
|
|
181
|
+
|
|
182
|
+
if (!state || !state.query || !state.inputStream) {
|
|
183
|
+
state = await createConductorClaude(conductor);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Inject task context before user message
|
|
187
|
+
const taskContext = buildTaskContext(conductor);
|
|
188
|
+
const fullContent = taskContext
|
|
189
|
+
? `${content}\n\n---\n<conductor-context>\n${taskContext}\n</conductor-context>`
|
|
190
|
+
: content;
|
|
191
|
+
|
|
192
|
+
state.turnActive = true;
|
|
193
|
+
state.accumulatedText = '';
|
|
194
|
+
state.inputStream.enqueue({
|
|
195
|
+
type: 'user',
|
|
196
|
+
message: { role: 'user', content: fullContent }
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
sendStatusUpdate(conductor);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildTaskContext(conductor) {
|
|
203
|
+
if (conductor.tasks.size === 0) return null;
|
|
204
|
+
const lines = ['活跃任务:'];
|
|
205
|
+
for (const [taskId, t] of conductor.tasks) {
|
|
206
|
+
const actors = (t.activeActors || []).join(', ');
|
|
207
|
+
lines.push(`- ${taskId}: ${t.title} [${t.status}] workDir=${t.workDir} scenario=${t.scenario}${actors ? ' actors=' + actors : ''}`);
|
|
208
|
+
}
|
|
209
|
+
return lines.join('\n');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// =====================================================================
|
|
213
|
+
// Output Processing
|
|
214
|
+
// =====================================================================
|
|
215
|
+
|
|
216
|
+
// Parse CREATE_TASK instruction (V5: includes workDir and scenario)
|
|
217
|
+
function parseCreateTask(text) {
|
|
218
|
+
const regex = /---CREATE_TASK---\s*\n([\s\S]*?)---END_CREATE_TASK---/g;
|
|
219
|
+
const tasks = [];
|
|
220
|
+
let match;
|
|
221
|
+
while ((match = regex.exec(text)) !== null) {
|
|
222
|
+
const block = match[1];
|
|
223
|
+
const titleMatch = block.match(/^title:\s*(.+)/im);
|
|
224
|
+
const workDirMatch = block.match(/^workDir:\s*(.+)/im);
|
|
225
|
+
const scenarioMatch = block.match(/^scenario:\s*(.+)/im);
|
|
226
|
+
if (titleMatch) {
|
|
227
|
+
tasks.push({
|
|
228
|
+
title: titleMatch[1].trim(),
|
|
229
|
+
workDir: workDirMatch ? workDirMatch[1].trim() : '',
|
|
230
|
+
scenario: scenarioMatch ? scenarioMatch[1].trim() : 'dev'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return tasks;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Parse FORWARD_TASK instruction
|
|
238
|
+
function parseForwardTask(text) {
|
|
239
|
+
const regex = /---FORWARD_TASK---\s*\n([\s\S]*?)---END_FORWARD_TASK---/g;
|
|
240
|
+
const forwards = [];
|
|
241
|
+
let match;
|
|
242
|
+
while ((match = regex.exec(text)) !== null) {
|
|
243
|
+
const block = match[1];
|
|
244
|
+
const taskIdMatch = block.match(/^taskId:\s*(.+)/im);
|
|
245
|
+
// message may be multi-line: capture from "message:" to end of block
|
|
246
|
+
const messageMatch = block.match(/^message:\s*([\s\S]*)$/im);
|
|
247
|
+
if (taskIdMatch) {
|
|
248
|
+
forwards.push({
|
|
249
|
+
taskId: taskIdMatch[1].trim(),
|
|
250
|
+
message: messageMatch ? messageMatch[1].trim() : ''
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return forwards;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Process Conductor Claude streaming output
|
|
259
|
+
*/
|
|
260
|
+
async function processConductorOutput(conductor, conductorQuery) {
|
|
261
|
+
const state = conductor.conductorState;
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
for await (const message of conductorQuery) {
|
|
265
|
+
if (conductor.status === 'stopped') break;
|
|
266
|
+
|
|
267
|
+
if (message.type === 'system' && message.subtype === 'init') {
|
|
268
|
+
state.claudeSessionId = message.session_id;
|
|
269
|
+
console.log(`[Conductor] Claude session: ${message.session_id}`);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (message.type === 'assistant') {
|
|
274
|
+
const content = message.message?.content;
|
|
275
|
+
if (content) {
|
|
276
|
+
if (typeof content === 'string') {
|
|
277
|
+
state.accumulatedText += content;
|
|
278
|
+
sendConductorOutput(conductor, 'text', message);
|
|
279
|
+
} else if (Array.isArray(content)) {
|
|
280
|
+
let hasText = false;
|
|
281
|
+
for (const block of content) {
|
|
282
|
+
if (block.type === 'text') {
|
|
283
|
+
state.accumulatedText += block.text;
|
|
284
|
+
hasText = true;
|
|
285
|
+
} else if (block.type === 'tool_use') {
|
|
286
|
+
endConductorStreaming(conductor);
|
|
287
|
+
sendConductorOutput(conductor, 'tool_use', message);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (hasText) {
|
|
291
|
+
sendConductorOutput(conductor, 'text', message);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} else if (message.type === 'user') {
|
|
296
|
+
sendConductorOutput(conductor, 'tool_result', message);
|
|
297
|
+
} else if (message.type === 'result') {
|
|
298
|
+
// Turn completed
|
|
299
|
+
console.log('[Conductor] Turn completed');
|
|
300
|
+
endConductorStreaming(conductor);
|
|
301
|
+
|
|
302
|
+
// Update costs
|
|
303
|
+
if (message.total_cost_usd != null) {
|
|
304
|
+
const costDelta = message.total_cost_usd - state.lastCostUsd;
|
|
305
|
+
if (costDelta > 0) conductor.costUsd += costDelta;
|
|
306
|
+
state.lastCostUsd = message.total_cost_usd;
|
|
307
|
+
}
|
|
308
|
+
if (message.usage) {
|
|
309
|
+
const inputDelta = (message.usage.input_tokens || 0) - (state.lastInputTokens || 0);
|
|
310
|
+
const outputDelta = (message.usage.output_tokens || 0) - (state.lastOutputTokens || 0);
|
|
311
|
+
if (inputDelta > 0) conductor.totalInputTokens += inputDelta;
|
|
312
|
+
if (outputDelta > 0) conductor.totalOutputTokens += outputDelta;
|
|
313
|
+
state.lastInputTokens = message.usage.input_tokens || 0;
|
|
314
|
+
state.lastOutputTokens = message.usage.output_tokens || 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Persist Claude session ID
|
|
318
|
+
if (state.claudeSessionId) {
|
|
319
|
+
saveConductorSessionId(state.claudeSessionId)
|
|
320
|
+
.catch(e => console.warn('[Conductor] Failed to save sessionId:', e.message));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Parse instructions
|
|
324
|
+
const createTasks = parseCreateTask(state.accumulatedText);
|
|
325
|
+
const forwardTasks = parseForwardTask(state.accumulatedText);
|
|
326
|
+
|
|
327
|
+
// Handle CREATE_TASK
|
|
328
|
+
for (const ct of createTasks) {
|
|
329
|
+
const taskId = `task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
330
|
+
|
|
331
|
+
// Create task directory at {workDir}/.conductor/{taskId}/ (includes worktree)
|
|
332
|
+
let worktreePath = null;
|
|
333
|
+
if (ct.workDir) {
|
|
334
|
+
try {
|
|
335
|
+
const result = await initTaskDir(ct.workDir, taskId);
|
|
336
|
+
worktreePath = result.worktreePath || null;
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.warn(`[Conductor] Failed to create task dir: ${e.message}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Register in state.json
|
|
343
|
+
const taskEntry = {
|
|
344
|
+
taskId,
|
|
345
|
+
title: ct.title,
|
|
346
|
+
workDir: ct.workDir,
|
|
347
|
+
scenario: ct.scenario,
|
|
348
|
+
status: 'created',
|
|
349
|
+
activeActors: [],
|
|
350
|
+
currentStep: '',
|
|
351
|
+
worktreePath,
|
|
352
|
+
lastUpdate: Date.now()
|
|
353
|
+
};
|
|
354
|
+
conductor.tasks.set(taskId, taskEntry);
|
|
355
|
+
|
|
356
|
+
await updateTaskInState(taskId, taskEntry).catch(e =>
|
|
357
|
+
console.warn('[Conductor] Failed to update state.json:', e.message)
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
sendConductorOutput(conductor, 'task_created', null, {
|
|
361
|
+
taskId, taskTitle: ct.title
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
sendConductorMessage({
|
|
365
|
+
type: 'conductor_task_created',
|
|
366
|
+
task: taskEntry
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.log(`[Conductor] Task created: ${taskId} — ${ct.title} @ ${ct.workDir} (${ct.scenario})`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Handle FORWARD_TASK
|
|
373
|
+
for (const ft of forwardTasks) {
|
|
374
|
+
const task = conductor.tasks.get(ft.taskId);
|
|
375
|
+
if (task) {
|
|
376
|
+
if (!task.inbox) task.inbox = [];
|
|
377
|
+
task.inbox.push({
|
|
378
|
+
from: 'conductor',
|
|
379
|
+
content: ft.message,
|
|
380
|
+
timestamp: Date.now()
|
|
381
|
+
});
|
|
382
|
+
task.lastUpdate = Date.now();
|
|
383
|
+
|
|
384
|
+
sendConductorOutput(conductor, 'task_forwarded', null, {
|
|
385
|
+
taskId: ft.taskId
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
sendConductorMessage({
|
|
389
|
+
type: 'conductor_task_message',
|
|
390
|
+
taskId: ft.taskId,
|
|
391
|
+
message: ft.message
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
console.log(`[Conductor] Forwarded to ${ft.taskId}: ${ft.message.substring(0, 60)}`);
|
|
395
|
+
} else {
|
|
396
|
+
console.warn(`[Conductor] Unknown task: ${ft.taskId}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
state.accumulatedText = '';
|
|
401
|
+
state.turnActive = false;
|
|
402
|
+
|
|
403
|
+
sendConductorMessage({ type: 'conductor_turn_completed' });
|
|
404
|
+
sendStatusUpdate(conductor);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (error.name === 'AbortError') {
|
|
409
|
+
console.log('[Conductor] Claude aborted');
|
|
410
|
+
} else {
|
|
411
|
+
console.error('[Conductor] Claude error:', error.message);
|
|
412
|
+
endConductorStreaming(conductor);
|
|
413
|
+
state.turnActive = false;
|
|
414
|
+
state.query = null;
|
|
415
|
+
state.inputStream = null;
|
|
416
|
+
state.accumulatedText = '';
|
|
417
|
+
|
|
418
|
+
sendConductorMessage({
|
|
419
|
+
type: 'conductor_error',
|
|
420
|
+
error: error.message.substring(0, 500)
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
conductor.status = 'waiting_user';
|
|
424
|
+
sendStatusUpdate(conductor);
|
|
425
|
+
}
|
|
426
|
+
} finally {
|
|
427
|
+
if (conductor._conductorSemRelease) {
|
|
428
|
+
conductor._conductorSemRelease();
|
|
429
|
+
conductor._conductorSemRelease = null;
|
|
430
|
+
conductor.activeClaudes = Math.max(0, (conductor.activeClaudes || 1) - 1);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Stop Conductor Claude
|
|
437
|
+
*/
|
|
438
|
+
export async function stopConductorClaude(conductor) {
|
|
439
|
+
const state = conductor.conductorState;
|
|
440
|
+
if (!state) return;
|
|
441
|
+
|
|
442
|
+
if (state.claudeSessionId) {
|
|
443
|
+
await saveConductorSessionId(state.claudeSessionId)
|
|
444
|
+
.catch(e => console.warn('[Conductor] Failed to save sessionId:', e.message));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (state.abortController) {
|
|
448
|
+
state.abortController.abort();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
state.query = null;
|
|
452
|
+
state.inputStream = null;
|
|
453
|
+
state.turnActive = false;
|
|
454
|
+
conductor.conductorState = null;
|
|
455
|
+
|
|
456
|
+
if (conductor._conductorSemRelease) {
|
|
457
|
+
conductor._conductorSemRelease();
|
|
458
|
+
conductor._conductorSemRelease = null;
|
|
459
|
+
conductor.activeClaudes = Math.max(0, (conductor.activeClaudes || 1) - 1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Clear Conductor Claude (force new conversation)
|
|
465
|
+
*/
|
|
466
|
+
export async function clearConductorClaude(conductor) {
|
|
467
|
+
await stopConductorClaude(conductor);
|
|
468
|
+
await clearConductorSessionId();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Export parse functions for testing
|
|
472
|
+
export { parseCreateTask, parseForwardTask };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Module Entry
|
|
3
|
+
*
|
|
4
|
+
* Exports three core subsystems:
|
|
5
|
+
* 1. Personas — 18 persona definitions (4 scenarios)
|
|
6
|
+
* 2. Specialties — specialty behavior definitions
|
|
7
|
+
* 3. CLAUDE.md Generator — dynamic 3-section generation
|
|
8
|
+
* 4. Actor — Claude instance create/manage/release
|
|
9
|
+
* 5. Worktree — per-task worktree management
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ---- Personas ----
|
|
13
|
+
export {
|
|
14
|
+
getPersonaById,
|
|
15
|
+
getPersonasByScenario,
|
|
16
|
+
getPersonaPoolForOrchestrator,
|
|
17
|
+
getAllPersonas,
|
|
18
|
+
getScenarios,
|
|
19
|
+
personasByScenario
|
|
20
|
+
} from './personas/index.js';
|
|
21
|
+
|
|
22
|
+
// ---- Specialties ----
|
|
23
|
+
export {
|
|
24
|
+
getSpecialty,
|
|
25
|
+
getSpecialtiesByScenario,
|
|
26
|
+
getThinkingMode,
|
|
27
|
+
isDivergent,
|
|
28
|
+
getPersonalizedBehavior,
|
|
29
|
+
getOutputFormat,
|
|
30
|
+
formatToolRules,
|
|
31
|
+
personaSupportsSpecialty,
|
|
32
|
+
allSpecialties,
|
|
33
|
+
scenarioSpecialties
|
|
34
|
+
} from './specialties/index.js';
|
|
35
|
+
|
|
36
|
+
// ---- CLAUDE.md Generator ----
|
|
37
|
+
export {
|
|
38
|
+
generateActorCLAUDEmd,
|
|
39
|
+
generateActorInstanceId,
|
|
40
|
+
generateActorDirName
|
|
41
|
+
} from './claudemd-generator.js';
|
|
42
|
+
|
|
43
|
+
// ---- Actor Management ----
|
|
44
|
+
export {
|
|
45
|
+
createActor,
|
|
46
|
+
sendToActor,
|
|
47
|
+
releaseActor,
|
|
48
|
+
releaseAllActors,
|
|
49
|
+
getActor,
|
|
50
|
+
getActiveActors,
|
|
51
|
+
getAllActors,
|
|
52
|
+
getActorPublicState,
|
|
53
|
+
getActorStates,
|
|
54
|
+
classifyActorError,
|
|
55
|
+
actorRegistry
|
|
56
|
+
} from './actor.js';
|
|
57
|
+
|
|
58
|
+
// ---- Worktree Management ----
|
|
59
|
+
export {
|
|
60
|
+
createTaskWorktree,
|
|
61
|
+
cleanupTaskWorktree,
|
|
62
|
+
cleanupAllConductorWorktrees,
|
|
63
|
+
getTaskWorktreePath,
|
|
64
|
+
getActorCwd
|
|
65
|
+
} from './worktree.js';
|