@yeaft/webchat-agent 1.0.12 → 1.0.14
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 +4 -1
- package/package.json +1 -1
- package/yeaft/tasks/manager.js +71 -11
- package/yeaft/web-bridge.js +147 -5
|
@@ -38,7 +38,7 @@ import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
|
38
38
|
import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
|
|
39
39
|
import { discoverLlmModels } from '../llm-model-discovery.js';
|
|
40
40
|
import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
|
|
41
|
-
import { handleYeaftSessionSend, handleYeaftSubAgentPrompt, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager } from '../yeaft/web-bridge.js';
|
|
41
|
+
import { handleYeaftSessionSend, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager } from '../yeaft/web-bridge.js';
|
|
42
42
|
import { startYeaftStatusRefresh, refreshYeaftStatus } from '../yeaft/status-cache.js';
|
|
43
43
|
|
|
44
44
|
export async function handleMessage(msg) {
|
|
@@ -658,6 +658,9 @@ export async function handleMessage(msg) {
|
|
|
658
658
|
case 'yeaft_sub_agent_prompt':
|
|
659
659
|
handleYeaftSubAgentPrompt(msg);
|
|
660
660
|
break;
|
|
661
|
+
case 'yeaft_task_cancel':
|
|
662
|
+
handleYeaftTaskCancel(msg);
|
|
663
|
+
break;
|
|
661
664
|
|
|
662
665
|
// wave-6b: manual dream trigger from VP detail page
|
|
663
666
|
case 'yeaft_dream_trigger':
|
package/package.json
CHANGED
package/yeaft/tasks/manager.js
CHANGED
|
@@ -12,6 +12,7 @@ import { getRuntimePlatformInfo } from '../runtime-platform.js';
|
|
|
12
12
|
|
|
13
13
|
const LOG_PREVIEW_BYTES = 4096;
|
|
14
14
|
const SUB_AGENT_LOG_PREVIEW_BYTES = 1024 * 1024;
|
|
15
|
+
const DEFAULT_CANCEL_ESCALATION_MS = 2000;
|
|
15
16
|
|
|
16
17
|
function logPreviewBytesFor(task) {
|
|
17
18
|
return task?.kind === 'sub_agent' ? SUB_AGENT_LOG_PREVIEW_BYTES : LOG_PREVIEW_BYTES;
|
|
@@ -45,14 +46,23 @@ function publicSnapshot(task) {
|
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function taskCommand(task) {
|
|
50
|
+
const command = task?.runtime?.command;
|
|
51
|
+
return typeof command === 'string' && command.trim() ? command.trim() : '';
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
export class TaskManager {
|
|
49
|
-
constructor({ yeaftDir, onEvent = null, runtimePlatform = null } = {}) {
|
|
55
|
+
constructor({ yeaftDir, onEvent = null, runtimePlatform = null, cancelEscalationMs = DEFAULT_CANCEL_ESCALATION_MS } = {}) {
|
|
50
56
|
if (!yeaftDir) throw new Error('TaskManager requires yeaftDir');
|
|
51
57
|
this.store = new TaskStore({ yeaftDir });
|
|
52
58
|
this.onEvent = typeof onEvent === 'function' ? onEvent : null;
|
|
53
59
|
this.runtimePlatform = runtimePlatform || getRuntimePlatformInfo();
|
|
60
|
+
this.cancelEscalationMs = Number.isFinite(cancelEscalationMs)
|
|
61
|
+
? Math.max(0, Math.floor(cancelEscalationMs))
|
|
62
|
+
: DEFAULT_CANCEL_ESCALATION_MS;
|
|
54
63
|
this.active = new Map();
|
|
55
64
|
this.processes = new Map();
|
|
65
|
+
this.cancelEscalationTimers = new Map();
|
|
56
66
|
this.#loadPersistedRunningTasks();
|
|
57
67
|
}
|
|
58
68
|
|
|
@@ -191,9 +201,15 @@ export class TaskManager {
|
|
|
191
201
|
const key = this.#key(sessionId, taskId);
|
|
192
202
|
const task = this.active.get(key) || this.store.readTask(sessionId, taskId);
|
|
193
203
|
if (!task || isTerminalTaskStatus(task.status)) return publicSnapshot(task);
|
|
204
|
+
const escalationTimer = this.cancelEscalationTimers.get(key);
|
|
205
|
+
if (escalationTimer) {
|
|
206
|
+
clearTimeout(escalationTimer);
|
|
207
|
+
this.cancelEscalationTimers.delete(key);
|
|
208
|
+
}
|
|
194
209
|
const logPath = task.log?.path || this.store.logPath(sessionId, taskId);
|
|
195
210
|
const tail = this.store.readLogFile(logPath, { tail: true, maxBytes: logPreviewBytesFor(task) });
|
|
196
|
-
|
|
211
|
+
const cancelRequested = !!task.runtime?.cancelRequestedAt;
|
|
212
|
+
task.status = cancelRequested ? TASK_STATUS.CANCELLED : (status || TASK_STATUS.FAILED);
|
|
197
213
|
task.updatedAt = nowIso();
|
|
198
214
|
task.endedAt = nowIso();
|
|
199
215
|
task.log = { ...(task.log || {}), path: tail.path, bytes: tail.bytes, preview: tail.text };
|
|
@@ -212,19 +228,61 @@ export class TaskManager {
|
|
|
212
228
|
if (!task) return { ok: false, error: `Unknown task: ${taskId}` };
|
|
213
229
|
if (isTerminalTaskStatus(task.status)) return { ok: true, task: publicSnapshot(task) };
|
|
214
230
|
const runner = this.processes.get(key);
|
|
215
|
-
|
|
216
|
-
if (!killed) {
|
|
231
|
+
if (!runner) {
|
|
217
232
|
return {
|
|
218
233
|
ok: false,
|
|
219
|
-
error: 'Unable to cancel task: no live process handle
|
|
234
|
+
error: 'Unable to cancel task: no live process handle.',
|
|
220
235
|
task: publicSnapshot(task),
|
|
221
236
|
};
|
|
222
237
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
238
|
+
|
|
239
|
+
if (!task.runtime?.cancelRequestedAt) {
|
|
240
|
+
const signalled = runner.kill('SIGTERM');
|
|
241
|
+
if (!signalled) {
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
error: 'Unable to cancel task: process-tree signal failed.',
|
|
245
|
+
task: publicSnapshot(task),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const cancelRequestedAt = nowIso();
|
|
249
|
+
task.runtime = {
|
|
250
|
+
...(task.runtime || {}),
|
|
251
|
+
cancelRequestedAt,
|
|
252
|
+
cancelSignal: 'SIGTERM',
|
|
253
|
+
cancelEscalationMs: this.cancelEscalationMs,
|
|
254
|
+
};
|
|
255
|
+
task.updatedAt = cancelRequestedAt;
|
|
256
|
+
this.store.writeTask(task);
|
|
257
|
+
this.active.set(key, task);
|
|
258
|
+
this.store.appendEvent(sessionId, { event: 'cancel_requested', taskId, signal: 'SIGTERM' });
|
|
259
|
+
this.#emit('updated', task, { cancelRequested: true });
|
|
260
|
+
|
|
261
|
+
if (this.cancelEscalationMs >= 0 && !this.cancelEscalationTimers.has(key)) {
|
|
262
|
+
const timer = setTimeout(() => {
|
|
263
|
+
this.cancelEscalationTimers.delete(key);
|
|
264
|
+
const current = this.active.get(key) || this.store.readTask(sessionId, taskId);
|
|
265
|
+
if (!current || isTerminalTaskStatus(current.status)) return;
|
|
266
|
+
const liveRunner = this.processes.get(key);
|
|
267
|
+
const escalated = liveRunner ? liveRunner.kill('SIGKILL') : false;
|
|
268
|
+
current.runtime = {
|
|
269
|
+
...(current.runtime || {}),
|
|
270
|
+
cancelEscalatedAt: nowIso(),
|
|
271
|
+
cancelEscalatedSignal: 'SIGKILL',
|
|
272
|
+
cancelEscalationFailed: !escalated,
|
|
273
|
+
};
|
|
274
|
+
current.updatedAt = current.runtime.cancelEscalatedAt;
|
|
275
|
+
this.store.writeTask(current);
|
|
276
|
+
this.active.set(key, current);
|
|
277
|
+
this.store.appendEvent(sessionId, { event: 'cancel_escalated', taskId, signal: 'SIGKILL', ok: escalated });
|
|
278
|
+
this.#emit('updated', current, { cancelEscalated: true, cancelEscalationOk: escalated });
|
|
279
|
+
}, this.cancelEscalationMs);
|
|
280
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
281
|
+
this.cancelEscalationTimers.set(key, timer);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { ok: true, task: publicSnapshot(task), pending: true };
|
|
228
286
|
}
|
|
229
287
|
|
|
230
288
|
listActiveTasks(sessionId = null) {
|
|
@@ -274,7 +332,9 @@ export class TaskManager {
|
|
|
274
332
|
const lines = ['<active_tasks>'];
|
|
275
333
|
for (const task of tasks) {
|
|
276
334
|
const preview = (task.log?.preview || '').trim().split('\n').slice(-3).join(' | ');
|
|
277
|
-
|
|
335
|
+
const command = taskCommand(task);
|
|
336
|
+
const cancelRequestedAt = typeof task.runtime?.cancelRequestedAt === 'string' ? task.runtime.cancelRequestedAt : '';
|
|
337
|
+
lines.push(`- ${task.id} | ${task.kind} | ${task.status} | owner=${task.ownerVpId || 'unknown'} | title=${JSON.stringify(task.title)}${command ? ` | command=${JSON.stringify(command)}` : ''}${cancelRequestedAt ? ` | cancelRequestedAt=${JSON.stringify(cancelRequestedAt)}` : ''} | log=${task.log?.path || ''}${preview ? ` | tail=${JSON.stringify(preview)}` : ''}`);
|
|
278
338
|
}
|
|
279
339
|
lines.push('</active_tasks>');
|
|
280
340
|
return lines.join('\n');
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -1217,6 +1217,7 @@ function formatTaskResultForVp(task) {
|
|
|
1217
1217
|
`<task-result id="${task.id}" kind="${task.kind}" status="${task.status}">`,
|
|
1218
1218
|
`title: ${task.title || task.kind || task.id}`,
|
|
1219
1219
|
];
|
|
1220
|
+
if (task?.runtime?.command) lines.push(`command: ${task.runtime.command}`);
|
|
1220
1221
|
if (result.exitCode !== undefined && result.exitCode !== null) lines.push(`exitCode: ${result.exitCode}`);
|
|
1221
1222
|
if (result.signal) lines.push(`signal: ${result.signal}`);
|
|
1222
1223
|
if (result.error) lines.push(`error: ${result.error}`);
|
|
@@ -2354,6 +2355,89 @@ function maybeTransitionVpStatus(hctx, state) {
|
|
|
2354
2355
|
}
|
|
2355
2356
|
}
|
|
2356
2357
|
|
|
2358
|
+
const STREAM_TEXT_BATCH_MAX_CHARS = 200;
|
|
2359
|
+
const STREAM_TEXT_BATCH_MAX_MS = 200;
|
|
2360
|
+
|
|
2361
|
+
function createStreamTextBatch() {
|
|
2362
|
+
return {
|
|
2363
|
+
parts: [],
|
|
2364
|
+
charCount: 0,
|
|
2365
|
+
timer: null,
|
|
2366
|
+
envelope: null,
|
|
2367
|
+
immediateNext: true,
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function getStreamTextBatch(hctx) {
|
|
2372
|
+
if (!hctx) return null;
|
|
2373
|
+
if (!hctx.streamTextBatch) hctx.streamTextBatch = createStreamTextBatch();
|
|
2374
|
+
return hctx.streamTextBatch;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
function clearStreamTextBatchTimer(batch) {
|
|
2378
|
+
if (!batch?.timer) return;
|
|
2379
|
+
clearTimeout(batch.timer);
|
|
2380
|
+
batch.timer = null;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
function sendAssistantTextFrame(text, envelope) {
|
|
2384
|
+
if (!text) return;
|
|
2385
|
+
sendSessionOutputFrame({
|
|
2386
|
+
type: 'assistant',
|
|
2387
|
+
message: { content: [{ type: 'text', text }] },
|
|
2388
|
+
}, envelope);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
function flushStreamTextBatch(hctx, envelope, { resetImmediate = false } = {}) {
|
|
2392
|
+
const batch = hctx?.streamTextBatch;
|
|
2393
|
+
if (!batch) return false;
|
|
2394
|
+
clearStreamTextBatchTimer(batch);
|
|
2395
|
+
const text = batch.parts.join('');
|
|
2396
|
+
batch.parts = [];
|
|
2397
|
+
batch.charCount = 0;
|
|
2398
|
+
const flushEnvelope = envelope || batch.envelope;
|
|
2399
|
+
batch.envelope = flushEnvelope || null;
|
|
2400
|
+
if (resetImmediate) batch.immediateNext = true;
|
|
2401
|
+
if (!text) return false;
|
|
2402
|
+
sendAssistantTextFrame(text, flushEnvelope);
|
|
2403
|
+
return true;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
function scheduleStreamTextBatchFlush(hctx, batch) {
|
|
2407
|
+
if (!hctx || batch.timer) return;
|
|
2408
|
+
batch.timer = setTimeout(() => {
|
|
2409
|
+
batch.timer = null;
|
|
2410
|
+
flushStreamTextBatch(hctx, batch.envelope);
|
|
2411
|
+
}, STREAM_TEXT_BATCH_MAX_MS);
|
|
2412
|
+
if (batch.timer && typeof batch.timer.unref === 'function') {
|
|
2413
|
+
batch.timer.unref();
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
function queueStreamTextDelta(hctx, text, envelope) {
|
|
2418
|
+
if (typeof text !== 'string' || text.length === 0) return;
|
|
2419
|
+
const batch = getStreamTextBatch(hctx);
|
|
2420
|
+
if (!batch) {
|
|
2421
|
+
sendAssistantTextFrame(text, envelope);
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
batch.envelope = envelope;
|
|
2426
|
+
if (batch.immediateNext) {
|
|
2427
|
+
batch.immediateNext = false;
|
|
2428
|
+
sendAssistantTextFrame(text, envelope);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
batch.parts.push(text);
|
|
2433
|
+
batch.charCount += text.length;
|
|
2434
|
+
if (batch.charCount >= STREAM_TEXT_BATCH_MAX_CHARS) {
|
|
2435
|
+
flushStreamTextBatch(hctx, envelope);
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
scheduleStreamTextBatchFlush(hctx, batch);
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2357
2441
|
/**
|
|
2358
2442
|
* Handle a single engine event unwrapped from an `engine_event` envelope.
|
|
2359
2443
|
* Stamps threadId on every outgoing frame so frontend grouping, tools,
|
|
@@ -2375,13 +2459,17 @@ function handleEngineEvent(event, hctx) {
|
|
|
2375
2459
|
threadId: hctx.threadId || event.threadId,
|
|
2376
2460
|
};
|
|
2377
2461
|
|
|
2462
|
+
if (event.type !== 'text_delta') {
|
|
2463
|
+
// Preserve wire order. Any boundary/metadata/tool event must see all text
|
|
2464
|
+
// accepted before it flushed first; otherwise the browser can render a tool
|
|
2465
|
+
// call or terminal result before the text that led to it.
|
|
2466
|
+
flushStreamTextBatch(hctx, envelope, { resetImmediate: true });
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2378
2469
|
switch (event.type) {
|
|
2379
2470
|
case 'text_delta':
|
|
2380
2471
|
hctx.assistantTextParts.push(event.text);
|
|
2381
|
-
|
|
2382
|
-
type: 'assistant',
|
|
2383
|
-
message: { content: [{ type: 'text', text: event.text }] },
|
|
2384
|
-
}, envelope);
|
|
2472
|
+
queueStreamTextDelta(hctx, event.text, envelope);
|
|
2385
2473
|
// vp-status: first text-delta of a (thinking|tool) phase flips
|
|
2386
2474
|
// the row to 'streaming'. transition() is a no-op when already
|
|
2387
2475
|
// streaming, so subsequent deltas are cheap.
|
|
@@ -3410,6 +3498,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3410
3498
|
let turnEndReason = 'end_turn';
|
|
3411
3499
|
let turnEndEmitted = false;
|
|
3412
3500
|
let turnEndDetail = null;
|
|
3501
|
+
let handlerCtx = null;
|
|
3413
3502
|
const markTurnEnd = (reason) => { turnEndEmitted = true; turnEndReason = reason; };
|
|
3414
3503
|
const emitVpTurnEnd = (reason, detail = null) => {
|
|
3415
3504
|
if (turnEndEmitted) return;
|
|
@@ -3483,7 +3572,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3483
3572
|
vpEngine = getOrCreateVpEngine(sessionId, vpId, threadId);
|
|
3484
3573
|
if (thread) thread.engine = vpEngine;
|
|
3485
3574
|
|
|
3486
|
-
|
|
3575
|
+
handlerCtx = {
|
|
3487
3576
|
assistantTextParts,
|
|
3488
3577
|
toolCallsAccum,
|
|
3489
3578
|
toolResultsAccum,
|
|
@@ -3529,6 +3618,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3529
3618
|
handleEngineEvent(event, handlerCtx);
|
|
3530
3619
|
}
|
|
3531
3620
|
|
|
3621
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3622
|
+
|
|
3532
3623
|
// Turn completed — atomically append this VP's output to shared history.
|
|
3533
3624
|
// route_forward handoff text is an internal trigger, already visible as
|
|
3534
3625
|
// the source VP's tool action. Do not append it as a visible prompt for
|
|
@@ -3557,6 +3648,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3557
3648
|
} catch (err) {
|
|
3558
3649
|
const isAbort = err && (err.name === 'AbortError' || err.name === 'LLMAbortError');
|
|
3559
3650
|
if (isAbort) {
|
|
3651
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3560
3652
|
sendSessionOutputFrame({
|
|
3561
3653
|
type: 'result',
|
|
3562
3654
|
result_text: '',
|
|
@@ -3581,6 +3673,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3581
3673
|
console.warn('[Yeaft] vp-status error transition failed:', brokerErr?.message || brokerErr);
|
|
3582
3674
|
}
|
|
3583
3675
|
|
|
3676
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3677
|
+
|
|
3584
3678
|
if (isPermissionErrorMsg(err.message)) {
|
|
3585
3679
|
if (!_permissionDiagnosticSent) {
|
|
3586
3680
|
_permissionDiagnosticSent = true;
|
|
@@ -4489,6 +4583,54 @@ export function handleYeaftSubAgentPrompt(msg) {
|
|
|
4489
4583
|
}, { sessionId, vpId: task.ownerVpId || null, threadId: task.source?.threadId || null });
|
|
4490
4584
|
}
|
|
4491
4585
|
|
|
4586
|
+
export function handleYeaftTaskCancel(msg) {
|
|
4587
|
+
const sessionId = typeof msg?.sessionId === 'string' ? msg.sessionId.trim() : '';
|
|
4588
|
+
const taskId = typeof msg?.taskId === 'string' ? msg.taskId.trim() : '';
|
|
4589
|
+
const clientRequestId = typeof msg?.clientRequestId === 'string' ? msg.clientRequestId.trim() : '';
|
|
4590
|
+
const fail = (error, task = null) => {
|
|
4591
|
+
sendSessionEvent({
|
|
4592
|
+
type: 'yeaft_task_cancel_result',
|
|
4593
|
+
success: false,
|
|
4594
|
+
taskId: taskId || null,
|
|
4595
|
+
clientRequestId: clientRequestId || null,
|
|
4596
|
+
error,
|
|
4597
|
+
...(task ? { task } : {}),
|
|
4598
|
+
}, sessionId ? { sessionId, vpId: task?.ownerVpId || null, threadId: task?.source?.threadId || null } : undefined);
|
|
4599
|
+
};
|
|
4600
|
+
|
|
4601
|
+
if (!sessionId || !taskId) {
|
|
4602
|
+
fail('sessionId and taskId are required');
|
|
4603
|
+
return;
|
|
4604
|
+
}
|
|
4605
|
+
if (!session?.taskManager || typeof session.taskManager.cancelTask !== 'function') {
|
|
4606
|
+
fail('task manager unavailable');
|
|
4607
|
+
return;
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4610
|
+
let result;
|
|
4611
|
+
try {
|
|
4612
|
+
result = session.taskManager.cancelTask(sessionId, taskId);
|
|
4613
|
+
} catch (err) {
|
|
4614
|
+
fail(err?.message || String(err));
|
|
4615
|
+
return;
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
const task = result?.task || session.taskManager.getTask?.(sessionId, taskId) || null;
|
|
4619
|
+
if (!result?.ok) {
|
|
4620
|
+
fail(result?.error || 'Failed to cancel task', task);
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4624
|
+
sendSessionEvent({
|
|
4625
|
+
type: 'yeaft_task_cancel_result',
|
|
4626
|
+
success: true,
|
|
4627
|
+
taskId,
|
|
4628
|
+
clientRequestId: clientRequestId || null,
|
|
4629
|
+
pending: !!result?.pending,
|
|
4630
|
+
task,
|
|
4631
|
+
}, { sessionId, vpId: task?.ownerVpId || null, threadId: task?.source?.threadId || null });
|
|
4632
|
+
}
|
|
4633
|
+
|
|
4492
4634
|
/** Deprecated mode switch — Yeaft is single-mode. */
|
|
4493
4635
|
export function handleYeaftModeSwitch(_msg) {
|
|
4494
4636
|
console.warn('[Yeaft] yeaft_mode_switch is deprecated and ignored — Yeaft now runs in a single unified mode.');
|