@yeaft/webchat-agent 0.1.235 → 0.1.237
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 +12 -28
- package/conductor/index.js +1 -10
- package/conductor/session.js +2 -4
- package/conductor/ui-messages.js +1 -8
- package/package.json +1 -1
- package/conductor/task-runner.js +0 -287
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
getConductorHome, ensureConductorHome,
|
|
22
22
|
loadState, saveState, updateTaskInState, initTaskDir
|
|
23
23
|
} from './persistence.js';
|
|
24
|
-
import { startTaskExecution } from './task-runner.js';
|
|
25
24
|
|
|
26
25
|
// =====================================================================
|
|
27
26
|
// Claude Session ID Persistence
|
|
@@ -111,6 +110,8 @@ message: <要转发的内容>
|
|
|
111
110
|
- 每条用户消息只需要一种响应:创建任务 / 转发 / 直接回答
|
|
112
111
|
- 创建任务时只给标题,不要描述实现步骤
|
|
113
112
|
- 创建任务时必须指定 workDir(用户提到的项目路径)和 scenario(场景类型)
|
|
113
|
+
- 当消息包含 [工作路径: xxx] 时,直接使用该路径作为 workDir 创建 CREATE_TASK,不要再询问 workDir
|
|
114
|
+
- 当消息包含 [工作路径: xxx] 时,这是用户明确的意图——立刻创建任务,不要做任何分析
|
|
114
115
|
- 如果用户没指定 workDir 或 scenario,先询问
|
|
115
116
|
- 如果用户问"进度怎样",直接汇总上面的任务列表状态
|
|
116
117
|
- 保持回复简洁
|
|
@@ -177,22 +178,27 @@ export async function createConductorClaude(conductor) {
|
|
|
177
178
|
/**
|
|
178
179
|
* Send message to Conductor Claude
|
|
179
180
|
*/
|
|
180
|
-
export async function sendToConductor(conductor, content) {
|
|
181
|
+
export async function sendToConductor(conductor, content, workDir) {
|
|
181
182
|
let state = conductor.conductorState;
|
|
182
183
|
|
|
183
184
|
if (!state || !state.query || !state.inputStream) {
|
|
184
185
|
state = await createConductorClaude(conductor);
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
// Inject workDir prefix so Conductor knows the project path
|
|
189
|
+
let fullContent = content;
|
|
190
|
+
if (workDir) {
|
|
191
|
+
fullContent = `[工作路径: ${workDir}]\n${content}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
187
194
|
// Inject task context before user message
|
|
188
195
|
const taskContext = buildTaskContext(conductor);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
if (taskContext) {
|
|
197
|
+
fullContent = `${fullContent}\n\n---\n<conductor-context>\n${taskContext}\n</conductor-context>`;
|
|
198
|
+
}
|
|
192
199
|
|
|
193
200
|
state.turnActive = true;
|
|
194
201
|
state.accumulatedText = '';
|
|
195
|
-
state._lastUserContent = content; // Preserve original user input for task description
|
|
196
202
|
state.inputStream.enqueue({
|
|
197
203
|
type: 'user',
|
|
198
204
|
message: { role: 'user', content: fullContent }
|
|
@@ -330,18 +336,6 @@ async function processConductorOutput(conductor, conductorQuery) {
|
|
|
330
336
|
for (const ct of createTasks) {
|
|
331
337
|
const taskId = `task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
332
338
|
|
|
333
|
-
// Notify frontend: task is being created (worktree may take a few seconds)
|
|
334
|
-
sendConductorOutput(conductor, 'task_creating', null, {
|
|
335
|
-
taskId, taskTitle: ct.title
|
|
336
|
-
});
|
|
337
|
-
sendConductorMessage({
|
|
338
|
-
type: 'conductor_task_creating',
|
|
339
|
-
taskId,
|
|
340
|
-
title: ct.title,
|
|
341
|
-
workDir: ct.workDir,
|
|
342
|
-
scenario: ct.scenario
|
|
343
|
-
});
|
|
344
|
-
|
|
345
339
|
// Create task directory at {workDir}/.conductor/{taskId}/ (includes worktree)
|
|
346
340
|
let worktreePath = null;
|
|
347
341
|
if (ct.workDir) {
|
|
@@ -353,15 +347,10 @@ async function processConductorOutput(conductor, conductorQuery) {
|
|
|
353
347
|
}
|
|
354
348
|
}
|
|
355
349
|
|
|
356
|
-
// Extract user's original message as task description
|
|
357
|
-
// (the content that triggered this CREATE_TASK)
|
|
358
|
-
const userDescription = state._lastUserContent || ct.title;
|
|
359
|
-
|
|
360
350
|
// Register in state.json
|
|
361
351
|
const taskEntry = {
|
|
362
352
|
taskId,
|
|
363
353
|
title: ct.title,
|
|
364
|
-
description: userDescription,
|
|
365
354
|
workDir: ct.workDir,
|
|
366
355
|
scenario: ct.scenario,
|
|
367
356
|
status: 'created',
|
|
@@ -386,11 +375,6 @@ async function processConductorOutput(conductor, conductorQuery) {
|
|
|
386
375
|
});
|
|
387
376
|
|
|
388
377
|
console.log(`[Conductor] Task created: ${taskId} — ${ct.title} @ ${ct.workDir} (${ct.scenario})`);
|
|
389
|
-
|
|
390
|
-
// Start Orchestrator-driven execution (fire-and-forget)
|
|
391
|
-
startTaskExecution(conductor, taskEntry).catch(e =>
|
|
392
|
-
console.error(`[Conductor] Task execution failed for ${taskId}:`, e.message)
|
|
393
|
-
);
|
|
394
378
|
}
|
|
395
379
|
|
|
396
380
|
// Handle FORWARD_TASK
|
package/conductor/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Conductor — Module Entry
|
|
3
3
|
*
|
|
4
|
-
* Exports core subsystems:
|
|
4
|
+
* Exports three core subsystems:
|
|
5
5
|
* 1. Personas — 18 persona definitions (4 scenarios)
|
|
6
6
|
* 2. Specialties — specialty behavior definitions
|
|
7
7
|
* 3. CLAUDE.md Generator — dynamic 3-section generation
|
|
8
8
|
* 4. Actor — Claude instance create/manage/release
|
|
9
9
|
* 5. Worktree — per-task worktree management
|
|
10
|
-
* 6. Task Runner — Orchestrator ↔ Actor bridge layer
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
// ---- Personas ----
|
|
@@ -64,11 +63,3 @@ export {
|
|
|
64
63
|
getTaskWorktreePath,
|
|
65
64
|
getActorCwd
|
|
66
65
|
} from './worktree.js';
|
|
67
|
-
|
|
68
|
-
// ---- Task Runner (Orchestrator ↔ Actor bridge) ----
|
|
69
|
-
export {
|
|
70
|
-
startTaskExecution,
|
|
71
|
-
stopTaskExecution,
|
|
72
|
-
stopAllTaskExecutions,
|
|
73
|
-
getOrchestrator
|
|
74
|
-
} from './task-runner.js';
|
package/conductor/session.js
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
sendConductorMessage, sendConductorOutput, sendStatusUpdate, recordUserMessage
|
|
17
17
|
} from './ui-messages.js';
|
|
18
18
|
import { createConductorClaude, sendToConductor, stopConductorClaude } from './conductor-claude.js';
|
|
19
|
-
import { stopAllTaskExecutions } from './task-runner.js';
|
|
20
19
|
|
|
21
20
|
// =====================================================================
|
|
22
21
|
// Singleton Conductor Instance
|
|
@@ -139,7 +138,7 @@ export async function initConductor(msg) {
|
|
|
139
138
|
* Handle user input to Conductor
|
|
140
139
|
*/
|
|
141
140
|
export async function handleConductorUserInput(msg) {
|
|
142
|
-
const { content } = msg;
|
|
141
|
+
const { content, workDir } = msg;
|
|
143
142
|
if (!conductor) {
|
|
144
143
|
console.warn('[Conductor] Not initialized, ignoring input');
|
|
145
144
|
return;
|
|
@@ -155,7 +154,7 @@ export async function handleConductorUserInput(msg) {
|
|
|
155
154
|
|
|
156
155
|
recordUserMessage(conductor, content);
|
|
157
156
|
conductor.status = 'running';
|
|
158
|
-
await sendToConductor(conductor, content);
|
|
157
|
+
await sendToConductor(conductor, content, workDir);
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
/**
|
|
@@ -165,7 +164,6 @@ export async function stopConductor() {
|
|
|
165
164
|
if (!conductor) return;
|
|
166
165
|
|
|
167
166
|
conductor.status = 'stopped';
|
|
168
|
-
await stopAllTaskExecutions();
|
|
169
167
|
await stopConductorClaude(conductor);
|
|
170
168
|
|
|
171
169
|
sendConductorOutput(conductor, 'system', {
|
package/conductor/ui-messages.js
CHANGED
|
@@ -20,7 +20,7 @@ export function sendConductorMessage(msg) {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Send Conductor Claude output to frontend
|
|
23
|
-
* outputType: 'text' | 'tool_use' | 'tool_result' | 'system' | '
|
|
23
|
+
* outputType: 'text' | 'tool_use' | 'tool_result' | 'system' | 'task_created' | 'task_forwarded'
|
|
24
24
|
*/
|
|
25
25
|
export function sendConductorOutput(conductor, outputType, rawMessage, extra = {}) {
|
|
26
26
|
sendConductorMessage({
|
|
@@ -64,13 +64,6 @@ export function sendConductorOutput(conductor, outputType, rawMessage, extra = {
|
|
|
64
64
|
conductor.uiMessages.push({
|
|
65
65
|
source: 'conductor', type: 'system', content: text, timestamp: Date.now()
|
|
66
66
|
});
|
|
67
|
-
} else if (outputType === 'task_creating') {
|
|
68
|
-
conductor.uiMessages.push({
|
|
69
|
-
source: 'conductor', type: 'task_creating',
|
|
70
|
-
taskId: extra.taskId, taskTitle: extra.taskTitle,
|
|
71
|
-
content: `Creating task: ${extra.taskTitle}...`,
|
|
72
|
-
timestamp: Date.now()
|
|
73
|
-
});
|
|
74
67
|
} else if (outputType === 'task_created') {
|
|
75
68
|
conductor.uiMessages.push({
|
|
76
69
|
source: 'conductor', type: 'task_created',
|
package/package.json
CHANGED
package/conductor/task-runner.js
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conductor — Task Runner (Bridge Layer)
|
|
3
|
-
*
|
|
4
|
-
* Bridges the gap between Conductor Claude (task creation) and
|
|
5
|
-
* Orchestrator (state machine) + Actor (Claude instances).
|
|
6
|
-
*
|
|
7
|
-
* Responsibilities:
|
|
8
|
-
* 1. Create actorManager adapter that translates Orchestrator's interface
|
|
9
|
-
* to Actor module's actual parameter format
|
|
10
|
-
* 2. Instantiate Orchestrator and start task execution
|
|
11
|
-
* 3. Relay status changes and actor output to frontend via UI messages
|
|
12
|
-
* 4. Handle task completion / failure cleanup
|
|
13
|
-
*/
|
|
14
|
-
import { createOrchestrator } from './orchestrator.js';
|
|
15
|
-
import {
|
|
16
|
-
createActor, sendToActor, releaseActor, releaseAllActors
|
|
17
|
-
} from './actor.js';
|
|
18
|
-
import { getTaskDir, updateTaskInState } from './persistence.js';
|
|
19
|
-
import {
|
|
20
|
-
sendConductorMessage, sendConductorOutput, sendStatusUpdate
|
|
21
|
-
} from './ui-messages.js';
|
|
22
|
-
|
|
23
|
-
// =====================================================================
|
|
24
|
-
// Per-task Orchestrator registry
|
|
25
|
-
// =====================================================================
|
|
26
|
-
|
|
27
|
-
/** @type {Map<string, Orchestrator>} taskId → Orchestrator */
|
|
28
|
-
const orchestrators = new Map();
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Get an active orchestrator for a task
|
|
32
|
-
* @param {string} taskId
|
|
33
|
-
* @returns {Orchestrator|null}
|
|
34
|
-
*/
|
|
35
|
-
export function getOrchestrator(taskId) {
|
|
36
|
-
return orchestrators.get(taskId) || null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// =====================================================================
|
|
40
|
-
// Read-write specialties (need worktree cwd)
|
|
41
|
-
// =====================================================================
|
|
42
|
-
|
|
43
|
-
const READ_WRITE_SPECIALTIES = new Set(['coding', 'testing', 'review']);
|
|
44
|
-
|
|
45
|
-
// =====================================================================
|
|
46
|
-
// ActorManager Adapter
|
|
47
|
-
// =====================================================================
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Create an actorManager adapter that translates Orchestrator's
|
|
51
|
-
* createActor(persona, specialty, context) interface into Actor module's
|
|
52
|
-
* createActor({ personaId, specialtyId, ... }) format.
|
|
53
|
-
*
|
|
54
|
-
* Orchestrator calls:
|
|
55
|
-
* actorManager.createActor(
|
|
56
|
-
* { id: personaId, name: personaName },
|
|
57
|
-
* specialty, // string like 'coding', 'planning', etc.
|
|
58
|
-
* { taskId, actorInstanceId, instruction, workDir, memoryPath, ... }
|
|
59
|
-
* ) → Promise<ActorResult>
|
|
60
|
-
*
|
|
61
|
-
* Actor.js expects:
|
|
62
|
-
* createActor({
|
|
63
|
-
* personaId, specialtyId, taskId, taskDir, taskContext,
|
|
64
|
-
* worktreePath, onOutput, onComplete, onError
|
|
65
|
-
* }) → Promise<ActorInstance>
|
|
66
|
-
*
|
|
67
|
-
* The adapter wraps actor creation + message sending + result collection
|
|
68
|
-
* into a single Promise<ActorResult> that the Orchestrator awaits.
|
|
69
|
-
*/
|
|
70
|
-
function createActorManagerAdapter(conductor, taskEntry) {
|
|
71
|
-
const { taskId, workDir, worktreePath } = taskEntry;
|
|
72
|
-
const taskDir = getTaskDir(workDir, taskId);
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
/**
|
|
76
|
-
* Create an actor, send it instructions, and wait for its result.
|
|
77
|
-
* @returns {Promise<ActorResult>}
|
|
78
|
-
*/
|
|
79
|
-
async createActor(persona, specialty, context) {
|
|
80
|
-
const actorWorktreePath = READ_WRITE_SPECIALTIES.has(specialty)
|
|
81
|
-
? worktreePath
|
|
82
|
-
: null;
|
|
83
|
-
|
|
84
|
-
// Create a deferred promise for actor completion
|
|
85
|
-
let resolveResult, rejectResult;
|
|
86
|
-
const resultPromise = new Promise((resolve, reject) => {
|
|
87
|
-
resolveResult = resolve;
|
|
88
|
-
rejectResult = reject;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const instance = await createActor({
|
|
92
|
-
personaId: persona.id,
|
|
93
|
-
specialtyId: specialty,
|
|
94
|
-
taskId,
|
|
95
|
-
taskDir,
|
|
96
|
-
taskContext: {
|
|
97
|
-
description: taskEntry.description || '',
|
|
98
|
-
instruction: context.instruction || context.description || '',
|
|
99
|
-
memoryPath: context.memoryPath || null,
|
|
100
|
-
planPath: context.planPath || null,
|
|
101
|
-
scenario: taskEntry.scenario,
|
|
102
|
-
workDir
|
|
103
|
-
},
|
|
104
|
-
threadId: context.threadId || null,
|
|
105
|
-
worktreePath: actorWorktreePath,
|
|
106
|
-
onOutput: (instanceId, message) => {
|
|
107
|
-
// Relay actor output to frontend in real-time
|
|
108
|
-
sendConductorOutput(conductor, 'text', message, {
|
|
109
|
-
taskId,
|
|
110
|
-
persona: persona.name || persona.id,
|
|
111
|
-
specialty
|
|
112
|
-
});
|
|
113
|
-
},
|
|
114
|
-
onComplete: (instanceId, result) => {
|
|
115
|
-
console.log(`[TaskRunner] Actor ${instanceId} completed for task ${taskId}`);
|
|
116
|
-
|
|
117
|
-
// Accumulate actor cost into conductor totals
|
|
118
|
-
if (result.costUsd) conductor.costUsd += result.costUsd;
|
|
119
|
-
if (result.inputTokens) conductor.totalInputTokens += result.inputTokens;
|
|
120
|
-
if (result.outputTokens) conductor.totalOutputTokens += result.outputTokens;
|
|
121
|
-
|
|
122
|
-
resolveResult(result);
|
|
123
|
-
},
|
|
124
|
-
onError: (instanceId, error) => {
|
|
125
|
-
console.error(`[TaskRunner] Actor ${instanceId} failed for task ${taskId}:`, error.message);
|
|
126
|
-
rejectResult(error);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Update conductor's active claude count
|
|
131
|
-
conductor.activeClaudes = (conductor.activeClaudes || 0) + 1;
|
|
132
|
-
|
|
133
|
-
// Update task entry's activeActors for status tracking
|
|
134
|
-
if (!taskEntry.activeActors) taskEntry.activeActors = [];
|
|
135
|
-
taskEntry.activeActors.push(instance.instanceId);
|
|
136
|
-
sendStatusUpdate(conductor);
|
|
137
|
-
|
|
138
|
-
// Send the instruction to the actor to start work
|
|
139
|
-
const instruction = context.instruction || context.description || taskEntry.description || '';
|
|
140
|
-
if (instruction) {
|
|
141
|
-
sendToActor(taskId, instance.instanceId, instruction);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return resultPromise;
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Release an actor instance.
|
|
149
|
-
*/
|
|
150
|
-
releaseActor(instanceId) {
|
|
151
|
-
releaseActor(taskId, instanceId).catch(e =>
|
|
152
|
-
console.warn(`[TaskRunner] Failed to release actor ${instanceId}:`, e.message)
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
// Update tracking
|
|
156
|
-
if (taskEntry.activeActors) {
|
|
157
|
-
const idx = taskEntry.activeActors.indexOf(instanceId);
|
|
158
|
-
if (idx !== -1) taskEntry.activeActors.splice(idx, 1);
|
|
159
|
-
}
|
|
160
|
-
conductor.activeClaudes = Math.max(0, (conductor.activeClaudes || 1) - 1);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// =====================================================================
|
|
166
|
-
// Task Execution Entry Point
|
|
167
|
-
// =====================================================================
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Start task execution via Orchestrator.
|
|
171
|
-
*
|
|
172
|
-
* Called after initTaskDir completes (folder + worktree ready).
|
|
173
|
-
* This is fire-and-forget — the orchestrator runs asynchronously.
|
|
174
|
-
*
|
|
175
|
-
* @param {object} conductor — Conductor singleton instance
|
|
176
|
-
* @param {object} taskEntry — Task registry entry from conductor.tasks Map
|
|
177
|
-
*/
|
|
178
|
-
export async function startTaskExecution(conductor, taskEntry) {
|
|
179
|
-
const { taskId, title, workDir, scenario, description } = taskEntry;
|
|
180
|
-
|
|
181
|
-
console.log(`[TaskRunner] Starting execution for task ${taskId}: "${title}" [${scenario}]`);
|
|
182
|
-
|
|
183
|
-
// Update task status
|
|
184
|
-
taskEntry.status = 'running';
|
|
185
|
-
taskEntry.lastUpdate = Date.now();
|
|
186
|
-
|
|
187
|
-
// Create actorManager adapter
|
|
188
|
-
const actorManager = createActorManagerAdapter(conductor, taskEntry);
|
|
189
|
-
|
|
190
|
-
// Create Orchestrator instance
|
|
191
|
-
const orchestrator = createOrchestrator({
|
|
192
|
-
actorManager,
|
|
193
|
-
|
|
194
|
-
onStatusChange: (serializedTask) => {
|
|
195
|
-
// Sync orchestrator state back to conductor's task entry
|
|
196
|
-
taskEntry.status = serializedTask.phase;
|
|
197
|
-
taskEntry.currentStep = `${serializedTask.phase}${serializedTask.currentStepIndex >= 0 ? ' step ' + serializedTask.currentStepIndex : ''}`;
|
|
198
|
-
taskEntry.activeActors = serializedTask.activeActors.map(a => a.instanceId);
|
|
199
|
-
taskEntry.lastUpdate = Date.now();
|
|
200
|
-
|
|
201
|
-
// Persist to state.json (async, non-blocking)
|
|
202
|
-
updateTaskInState(taskId, taskEntry).catch(e =>
|
|
203
|
-
console.warn(`[TaskRunner] Failed to persist task ${taskId}:`, e.message)
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
// Push status to frontend
|
|
207
|
-
sendConductorMessage({
|
|
208
|
-
type: 'conductor_task_status',
|
|
209
|
-
taskId,
|
|
210
|
-
task: {
|
|
211
|
-
...taskEntry,
|
|
212
|
-
phase: serializedTask.phase,
|
|
213
|
-
complexity: serializedTask.complexity,
|
|
214
|
-
plan: serializedTask.plan,
|
|
215
|
-
activeActors: serializedTask.activeActors,
|
|
216
|
-
completedActors: serializedTask.completedActors,
|
|
217
|
-
iterations: serializedTask.iterations
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
sendStatusUpdate(conductor);
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
onLog: (...args) => {
|
|
224
|
-
console.log('[TaskRunner/Orchestrator]', ...args);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Register orchestrator
|
|
229
|
-
orchestrators.set(taskId, orchestrator);
|
|
230
|
-
|
|
231
|
-
// Start the task (async — orchestrator runs its state machine internally)
|
|
232
|
-
try {
|
|
233
|
-
await orchestrator.startTask({
|
|
234
|
-
title,
|
|
235
|
-
description: description || title,
|
|
236
|
-
scenario,
|
|
237
|
-
workDir
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
console.log(`[TaskRunner] Orchestrator started for task ${taskId}`);
|
|
241
|
-
} catch (err) {
|
|
242
|
-
console.error(`[TaskRunner] Failed to start orchestrator for task ${taskId}:`, err.message);
|
|
243
|
-
|
|
244
|
-
taskEntry.status = 'failed';
|
|
245
|
-
taskEntry.error = err.message;
|
|
246
|
-
taskEntry.lastUpdate = Date.now();
|
|
247
|
-
|
|
248
|
-
await updateTaskInState(taskId, taskEntry).catch(() => {});
|
|
249
|
-
|
|
250
|
-
sendConductorOutput(conductor, 'system', {
|
|
251
|
-
message: { role: 'assistant', content: `Task "${title}" failed to start: ${err.message}` }
|
|
252
|
-
});
|
|
253
|
-
sendStatusUpdate(conductor);
|
|
254
|
-
|
|
255
|
-
// Cleanup
|
|
256
|
-
orchestrators.delete(taskId);
|
|
257
|
-
await releaseAllActors(taskId, { force: true }).catch(() => {});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Stop a running task's orchestrator and release all its actors.
|
|
263
|
-
*
|
|
264
|
-
* @param {string} taskId
|
|
265
|
-
*/
|
|
266
|
-
export async function stopTaskExecution(taskId) {
|
|
267
|
-
const orchestrator = orchestrators.get(taskId);
|
|
268
|
-
if (!orchestrator) return;
|
|
269
|
-
|
|
270
|
-
console.log(`[TaskRunner] Stopping task ${taskId}`);
|
|
271
|
-
orchestrators.delete(taskId);
|
|
272
|
-
|
|
273
|
-
// Release all actors for this task
|
|
274
|
-
await releaseAllActors(taskId, { force: true }).catch(e =>
|
|
275
|
-
console.warn(`[TaskRunner] Error releasing actors for ${taskId}:`, e.message)
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Stop all running task orchestrators (used during conductor shutdown).
|
|
281
|
-
*/
|
|
282
|
-
export async function stopAllTaskExecutions() {
|
|
283
|
-
const taskIds = [...orchestrators.keys()];
|
|
284
|
-
for (const taskId of taskIds) {
|
|
285
|
-
await stopTaskExecution(taskId);
|
|
286
|
-
}
|
|
287
|
-
}
|