@yeaft/webchat-agent 1.0.11 → 1.0.13
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 +49 -0
|
@@ -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}`);
|
|
@@ -4489,6 +4490,54 @@ export function handleYeaftSubAgentPrompt(msg) {
|
|
|
4489
4490
|
}, { sessionId, vpId: task.ownerVpId || null, threadId: task.source?.threadId || null });
|
|
4490
4491
|
}
|
|
4491
4492
|
|
|
4493
|
+
export function handleYeaftTaskCancel(msg) {
|
|
4494
|
+
const sessionId = typeof msg?.sessionId === 'string' ? msg.sessionId.trim() : '';
|
|
4495
|
+
const taskId = typeof msg?.taskId === 'string' ? msg.taskId.trim() : '';
|
|
4496
|
+
const clientRequestId = typeof msg?.clientRequestId === 'string' ? msg.clientRequestId.trim() : '';
|
|
4497
|
+
const fail = (error, task = null) => {
|
|
4498
|
+
sendSessionEvent({
|
|
4499
|
+
type: 'yeaft_task_cancel_result',
|
|
4500
|
+
success: false,
|
|
4501
|
+
taskId: taskId || null,
|
|
4502
|
+
clientRequestId: clientRequestId || null,
|
|
4503
|
+
error,
|
|
4504
|
+
...(task ? { task } : {}),
|
|
4505
|
+
}, sessionId ? { sessionId, vpId: task?.ownerVpId || null, threadId: task?.source?.threadId || null } : undefined);
|
|
4506
|
+
};
|
|
4507
|
+
|
|
4508
|
+
if (!sessionId || !taskId) {
|
|
4509
|
+
fail('sessionId and taskId are required');
|
|
4510
|
+
return;
|
|
4511
|
+
}
|
|
4512
|
+
if (!session?.taskManager || typeof session.taskManager.cancelTask !== 'function') {
|
|
4513
|
+
fail('task manager unavailable');
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
let result;
|
|
4518
|
+
try {
|
|
4519
|
+
result = session.taskManager.cancelTask(sessionId, taskId);
|
|
4520
|
+
} catch (err) {
|
|
4521
|
+
fail(err?.message || String(err));
|
|
4522
|
+
return;
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4525
|
+
const task = result?.task || session.taskManager.getTask?.(sessionId, taskId) || null;
|
|
4526
|
+
if (!result?.ok) {
|
|
4527
|
+
fail(result?.error || 'Failed to cancel task', task);
|
|
4528
|
+
return;
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4531
|
+
sendSessionEvent({
|
|
4532
|
+
type: 'yeaft_task_cancel_result',
|
|
4533
|
+
success: true,
|
|
4534
|
+
taskId,
|
|
4535
|
+
clientRequestId: clientRequestId || null,
|
|
4536
|
+
pending: !!result?.pending,
|
|
4537
|
+
task,
|
|
4538
|
+
}, { sessionId, vpId: task?.ownerVpId || null, threadId: task?.source?.threadId || null });
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4492
4541
|
/** Deprecated mode switch — Yeaft is single-mode. */
|
|
4493
4542
|
export function handleYeaftModeSwitch(_msg) {
|
|
4494
4543
|
console.warn('[Yeaft] yeaft_mode_switch is deprecated and ignored — Yeaft now runs in a single unified mode.');
|