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