@yeaft/webchat-agent 0.1.225 → 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.
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Conductor — Conductor Claude 实例管理
2
+ * Conductor — Conductor Claude Instance (V5)
3
3
  *
4
- * Conductor Claude 是常驻的 Claude Code 进程,负责:
5
- * 1. 消息分类(简单问答 / 创建 task / 透传到 task / 查状态)
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
- * 复用 V1 的 role-query 模式:query() + Stream + for-await 循环。
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 { getSessionDataDir } from './persistence.js';
20
+ import {
21
+ getConductorHome, ensureConductorHome,
22
+ loadState, saveState, updateTaskInState, initTaskDir
23
+ } from './persistence.js';
20
24
 
21
25
  // =====================================================================
22
- // Session ID Persistence (Conductor Claude session)
26
+ // Claude Session ID Persistence
23
27
  // =====================================================================
24
28
 
25
- async function saveConductorSessionId(sessionDataDir, claudeSessionId) {
26
- await fs.mkdir(sessionDataDir, { recursive: true });
27
- const filePath = join(sessionDataDir, 'conductor-claude-session.json');
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(sessionDataDir) {
34
- const filePath = join(sessionDataDir, 'conductor-claude-session.json');
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(sessionDataDir) {
44
- const filePath = join(sessionDataDir, 'conductor-claude-session.json');
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(session) {
53
- const taskSummaries = Array.from(session.tasks.values())
54
- .map(t => `- ${t.taskId}: ${t.title} [${t.phase} ${t.progress}%] @ ${t.workDir}`)
55
- .join('\n');
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
- * 创建或重建 Conductor Claude 实例
124
+ * Create or rebuild Conductor Claude instance
116
125
  */
117
- export async function createConductorClaude(session) {
118
- const sessionDataDir = getSessionDataDir(session.id);
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
- session._conductorSemRelease = release;
123
- session.activeClaudes = (session.activeClaudes || 0) + 1;
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(session);
136
+ const systemPrompt = buildConductorSystemPrompt(conductor);
128
137
 
129
- // 尝试恢复已有 session
130
- const savedSessionId = await loadConductorSessionId(sessionDataDir);
138
+ // Try to resume existing Claude session
139
+ const savedSessionId = await loadConductorSessionId();
131
140
 
132
141
  const queryOptions = {
133
- cwd: sessionDataDir,
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
- session.conductorState = {
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(session, conductorQuery);
170
+ // Start output processing loop
171
+ processConductorOutput(conductor, conductorQuery);
163
172
 
164
- return session.conductorState;
173
+ return conductor.conductorState;
165
174
  }
166
175
 
167
176
  /**
168
- * 向 Conductor Claude 发送消息
177
+ * Send message to Conductor Claude
169
178
  */
170
- export async function sendToConductor(session, content) {
171
- let state = session.conductorState;
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(session);
183
+ state = await createConductorClaude(conductor);
175
184
  }
176
185
 
177
- // 更新 system prompt(动态注入最新 task 列表)
178
- // 注意:Claude Code 不支持动态更新 system prompt
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(session);
199
+ sendStatusUpdate(conductor);
193
200
  }
194
201
 
195
- function buildTaskContext(session) {
196
- if (session.tasks.size === 0) return null;
197
- const lines = ['当前工作路径: ' + (session.workDir || '(未设置)'), '', '活跃任务:'];
198
- for (const t of session.tasks.values()) {
199
- lines.push(`- ${t.taskId}: ${t.title} [${t.phase} ${t.progress}%] @ ${t.workDir}`);
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
- // 解析 CREATE_TASK 指令
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(/title:\s*(.+)/i);
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({ title: titleMatch[1].trim() });
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
- // 解析 FORWARD_TASK 指令
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
- const messageMatch = block.match(/^message:\s*([\s\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);
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
- * 处理 Conductor Claude 的流式输出
258
+ * Process Conductor Claude streaming output
244
259
  */
245
- async function processConductorOutput(session, conductorQuery) {
246
- const state = session.conductorState;
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 (session.status === 'stopped') break;
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(session, 'text', message);
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(session);
273
- sendConductorOutput(session, 'tool_use', message);
286
+ endConductorStreaming(conductor);
287
+ sendConductorOutput(conductor, 'tool_use', message);
274
288
  }
275
289
  }
276
290
  if (hasText) {
277
- sendConductorOutput(session, 'text', message);
291
+ sendConductorOutput(conductor, 'text', message);
278
292
  }
279
293
  }
280
294
  }
281
295
  } else if (message.type === 'user') {
282
- sendConductorOutput(session, 'tool_result', message);
296
+ sendConductorOutput(conductor, 'tool_result', message);
283
297
  } else if (message.type === 'result') {
284
- // Turn 完成
285
- console.log(`[Conductor] Turn completed`);
286
- endConductorStreaming(session);
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) session.costUsd += costDelta;
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) session.totalInputTokens += inputDelta;
298
- if (outputDelta > 0) session.totalOutputTokens += outputDelta;
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
- // 持久化 sessionId
317
+ // Persist Claude session ID
304
318
  if (state.claudeSessionId) {
305
- saveConductorSessionId(sessionDataDir, state.claudeSessionId)
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
- // 处理 CREATE_TASK
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
- const task = {
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: session.workDir || '',
320
- status: 'pending',
321
- phase: 'created',
322
- progress: 0,
346
+ workDir: ct.workDir,
347
+ scenario: ct.scenario,
348
+ status: 'created',
323
349
  activeActors: [],
324
- createdAt: Date.now(),
325
- updatedAt: Date.now()
350
+ currentStep: '',
351
+ worktreePath,
352
+ lastUpdate: Date.now()
326
353
  };
327
- session.tasks.set(taskId, task);
354
+ conductor.tasks.set(taskId, taskEntry);
328
355
 
329
- sendConductorOutput(session, 'task_created', null, {
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
- sessionId: session.id,
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
- // 处理 FORWARD_TASK
372
+ // Handle FORWARD_TASK
343
373
  for (const ft of forwardTasks) {
344
- const task = session.tasks.get(ft.taskId);
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.updatedAt = Date.now();
382
+ task.lastUpdate = Date.now();
354
383
 
355
- sendConductorOutput(session, 'task_forwarded', null, {
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
- type: 'conductor_turn_completed',
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(`[Conductor] Claude aborted`);
409
+ console.log('[Conductor] Claude aborted');
385
410
  } else {
386
- console.error(`[Conductor] Claude error:`, error.message);
387
- endConductorStreaming(session);
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
- session.status = 'waiting_user';
401
- sendStatusUpdate(session);
423
+ conductor.status = 'waiting_user';
424
+ sendStatusUpdate(conductor);
402
425
  }
403
426
  } finally {
404
- // 释放信号量
405
- if (session._conductorSemRelease) {
406
- session._conductorSemRelease();
407
- session._conductorSemRelease = null;
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
- * 停止 Conductor Claude
436
+ * Stop Conductor Claude
415
437
  */
416
- export async function stopConductorClaude(session) {
417
- const state = session.conductorState;
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(sessionDataDir, state.claudeSessionId)
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
- session.conductorState = null;
454
+ conductor.conductorState = null;
435
455
 
436
- if (session._conductorSemRelease) {
437
- session._conductorSemRelease();
438
- session._conductorSemRelease = null;
439
- session.activeClaudes = Math.max(0, (session.activeClaudes || 1) - 1);
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
- * 清空 Conductor Claude(强制新建对话)
464
+ * Clear Conductor Claude (force new conversation)
445
465
  */
446
- export async function clearConductorClaude(session) {
447
- await stopConductorClaude(session);
448
- const sessionDataDir = getSessionDataDir(session.id);
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 };
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Conductor — 模块入口
2
+ * Conductor — Module Entry
3
3
  *
4
- * 导出三大核心子系统:
5
- * 1. Personas — 18 个人物定义(4 场景)
6
- * 2. Specialties — specialty 行为定义(通用 + 场景特有)
7
- * 3. CLAUDE.md Generator — 三段式动态生成
8
- * 4. Actor — Claude 实例创建/管理/释放
9
- * 5. Worktree — 按线程的 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
- createThreadWorktree,
61
- cleanupTaskWorktrees,
62
- cleanupAllConductorWorktrees
60
+ createTaskWorktree,
61
+ cleanupTaskWorktree,
62
+ cleanupAllConductorWorktrees,
63
+ getTaskWorktreePath,
64
+ getActorCwd
63
65
  } from './worktree.js';