@yeaft/webchat-agent 0.1.1104 → 0.1.1107
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/cli.js +28 -17
- package/connection/index.js +6 -3
- package/connection/message-router.js +10 -6
- package/index.js +4 -2
- package/llm-config-cli.js +48 -0
- package/package.json +1 -1
- package/service/config.js +90 -15
- package/service/index.js +78 -40
- package/service/linux.js +48 -37
- package/service/macos.js +35 -30
- package/service/windows.js +46 -39
- package/service.js +10 -0
- package/terminal.js +14 -2
- package/yeaft/cli.js +7 -7
- package/yeaft/conversation/persist.js +28 -118
- package/yeaft/debug-trace.js +967 -695
- package/yeaft/engine.js +127 -96
- package/yeaft/llm/adapter.js +7 -3
- package/yeaft/llm/anthropic.js +56 -15
- package/yeaft/llm/openai-responses.js +22 -12
- package/yeaft/llm/router.js +16 -2
- package/yeaft/models.js +15 -2
- package/yeaft/session.js +9 -17
- package/yeaft/sessions/session-crud.js +42 -23
- package/yeaft/systemd-scope.js +88 -0
- package/yeaft/tasks/manager.js +81 -13
- package/yeaft/tasks/shell-runner.js +11 -3
- package/yeaft/tools/bash.js +9 -2
- package/yeaft/vp-status-broker.js +8 -3
- package/yeaft/web-bridge.js +221 -24
package/yeaft/web-bridge.js
CHANGED
|
@@ -66,7 +66,7 @@ import { isHiddenConversationRow } from './conversation/internal-control.js';
|
|
|
66
66
|
import { sliceLastNTurns } from './turn-utils.js';
|
|
67
67
|
import { pairSanitize } from './pair-sanitize.js';
|
|
68
68
|
import { filterSnapshotForVp } from './snapshot-filter.js';
|
|
69
|
-
import { createVpStatusBroker } from './vp-status-broker.js';
|
|
69
|
+
import { createVpStatusBroker, isVpStatusRunning } from './vp-status-broker.js';
|
|
70
70
|
import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
|
|
71
71
|
import { listMcpServers, upsertMcpServer, removeMcpServer } from './config-api.js';
|
|
72
72
|
import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
|
|
@@ -433,6 +433,32 @@ function buildVpPromptPayload(vpId, envelope) {
|
|
|
433
433
|
return { text, prompt, promptParts };
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
+
function buildPendingRescueEnvelope({ sessionId, taskId = null, threadId = 'main', followUpId, leftover, replayText, replayParts = null }) {
|
|
437
|
+
const leftoverIsInternal = Boolean(leftover?.internal);
|
|
438
|
+
const leftoverInjectedBy = leftoverIsInternal && typeof leftover?.injectedBy === 'string'
|
|
439
|
+
? leftover.injectedBy
|
|
440
|
+
: null;
|
|
441
|
+
return {
|
|
442
|
+
sessionId,
|
|
443
|
+
taskId,
|
|
444
|
+
trigger: 'pending_rescue',
|
|
445
|
+
msg: {
|
|
446
|
+
id: followUpId,
|
|
447
|
+
from: leftoverIsInternal && leftover.senderVpId ? leftover.senderVpId : 'user',
|
|
448
|
+
role: leftoverIsInternal ? 'assistant' : 'user',
|
|
449
|
+
text: replayText,
|
|
450
|
+
meta: {
|
|
451
|
+
rescuedFrom: 'pendingQueries',
|
|
452
|
+
threadId,
|
|
453
|
+
...(leftoverInjectedBy ? { injectedBy: leftoverInjectedBy } : {}),
|
|
454
|
+
...(leftoverIsInternal && leftover.senderVpId ? { senderVpId: leftover.senderVpId } : {}),
|
|
455
|
+
...(leftoverIsInternal && leftover.sourceThreadId ? { sourceThreadId: leftover.sourceThreadId } : {}),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
...(Array.isArray(replayParts) && replayParts.length > 0 ? { _promptParts: replayParts } : {}),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
436
462
|
export function visibleInboundThreadId(envelope, fallbackThreadId = 'main') {
|
|
437
463
|
const meta = envelope?.msg?.meta || {};
|
|
438
464
|
if (
|
|
@@ -1217,6 +1243,7 @@ function formatTaskResultForVp(task) {
|
|
|
1217
1243
|
`<task-result id="${task.id}" kind="${task.kind}" status="${task.status}">`,
|
|
1218
1244
|
`title: ${task.title || task.kind || task.id}`,
|
|
1219
1245
|
];
|
|
1246
|
+
if (task?.runtime?.command) lines.push(`command: ${task.runtime.command}`);
|
|
1220
1247
|
if (result.exitCode !== undefined && result.exitCode !== null) lines.push(`exitCode: ${result.exitCode}`);
|
|
1221
1248
|
if (result.signal) lines.push(`signal: ${result.signal}`);
|
|
1222
1249
|
if (result.error) lines.push(`error: ${result.error}`);
|
|
@@ -1349,6 +1376,9 @@ async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
|
|
|
1349
1376
|
originalText: text,
|
|
1350
1377
|
originalParts: Array.isArray(envelope?._promptParts) ? envelope._promptParts : null,
|
|
1351
1378
|
internal: isInternalAppend,
|
|
1379
|
+
injectedBy: isInternalAppend ? injectedBy : null,
|
|
1380
|
+
senderVpId: isInternalAppend ? (envelope?.msg?.meta?.senderVpId || envelope?.msg?.from || null) : null,
|
|
1381
|
+
sourceThreadId: isInternalAppend ? visibleInboundThreadId(envelope, thread.threadId) : null,
|
|
1352
1382
|
});
|
|
1353
1383
|
persistInboundMessageOnceByMsgId({
|
|
1354
1384
|
msgId: envelope?.msg?.id,
|
|
@@ -1533,18 +1563,15 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
|
|
|
1533
1563
|
: null;
|
|
1534
1564
|
if (!replayText && !replayParts) continue;
|
|
1535
1565
|
const followUpId = `followup_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
|
|
1536
|
-
const followUpEnvelope = {
|
|
1566
|
+
const followUpEnvelope = buildPendingRescueEnvelope({
|
|
1537
1567
|
sessionId,
|
|
1538
1568
|
taskId: envelope?.taskId || null,
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
},
|
|
1546
|
-
...(replayParts ? { _promptParts: replayParts } : {}),
|
|
1547
|
-
};
|
|
1569
|
+
threadId: thread.threadId,
|
|
1570
|
+
followUpId,
|
|
1571
|
+
leftover,
|
|
1572
|
+
replayText,
|
|
1573
|
+
replayParts,
|
|
1574
|
+
});
|
|
1548
1575
|
const followUpTurnId = `${randomUUID().slice(0, 8)}:${vpId}`;
|
|
1549
1576
|
inbox.push({ envelope: followUpEnvelope, turnId: followUpTurnId, thread });
|
|
1550
1577
|
try {
|
|
@@ -1824,7 +1851,7 @@ function decorateSessionsWithRuntimeState(sessions) {
|
|
|
1824
1851
|
const sessionId = status?.sessionId || status?.groupId || null;
|
|
1825
1852
|
if (!sessionId) continue;
|
|
1826
1853
|
const state = status.state || 'idle';
|
|
1827
|
-
const running =
|
|
1854
|
+
const running = isVpStatusRunning(state);
|
|
1828
1855
|
const updatedAt = status.updatedAt || status.since || Date.now();
|
|
1829
1856
|
const prev = bySession.get(sessionId) || { running: false, runningVpCount: 0, latestActivityAt: 0 };
|
|
1830
1857
|
if (running) prev.runningVpCount += 1;
|
|
@@ -2354,6 +2381,89 @@ function maybeTransitionVpStatus(hctx, state) {
|
|
|
2354
2381
|
}
|
|
2355
2382
|
}
|
|
2356
2383
|
|
|
2384
|
+
const STREAM_TEXT_BATCH_MAX_CHARS = 200;
|
|
2385
|
+
const STREAM_TEXT_BATCH_MAX_MS = 200;
|
|
2386
|
+
|
|
2387
|
+
function createStreamTextBatch() {
|
|
2388
|
+
return {
|
|
2389
|
+
parts: [],
|
|
2390
|
+
charCount: 0,
|
|
2391
|
+
timer: null,
|
|
2392
|
+
envelope: null,
|
|
2393
|
+
immediateNext: true,
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
function getStreamTextBatch(hctx) {
|
|
2398
|
+
if (!hctx) return null;
|
|
2399
|
+
if (!hctx.streamTextBatch) hctx.streamTextBatch = createStreamTextBatch();
|
|
2400
|
+
return hctx.streamTextBatch;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
function clearStreamTextBatchTimer(batch) {
|
|
2404
|
+
if (!batch?.timer) return;
|
|
2405
|
+
clearTimeout(batch.timer);
|
|
2406
|
+
batch.timer = null;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
function sendAssistantTextFrame(text, envelope) {
|
|
2410
|
+
if (!text) return;
|
|
2411
|
+
sendSessionOutputFrame({
|
|
2412
|
+
type: 'assistant',
|
|
2413
|
+
message: { content: [{ type: 'text', text }] },
|
|
2414
|
+
}, envelope);
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
function flushStreamTextBatch(hctx, envelope, { resetImmediate = false } = {}) {
|
|
2418
|
+
const batch = hctx?.streamTextBatch;
|
|
2419
|
+
if (!batch) return false;
|
|
2420
|
+
clearStreamTextBatchTimer(batch);
|
|
2421
|
+
const text = batch.parts.join('');
|
|
2422
|
+
batch.parts = [];
|
|
2423
|
+
batch.charCount = 0;
|
|
2424
|
+
const flushEnvelope = envelope || batch.envelope;
|
|
2425
|
+
batch.envelope = flushEnvelope || null;
|
|
2426
|
+
if (resetImmediate) batch.immediateNext = true;
|
|
2427
|
+
if (!text) return false;
|
|
2428
|
+
sendAssistantTextFrame(text, flushEnvelope);
|
|
2429
|
+
return true;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
function scheduleStreamTextBatchFlush(hctx, batch) {
|
|
2433
|
+
if (!hctx || batch.timer) return;
|
|
2434
|
+
batch.timer = setTimeout(() => {
|
|
2435
|
+
batch.timer = null;
|
|
2436
|
+
flushStreamTextBatch(hctx, batch.envelope);
|
|
2437
|
+
}, STREAM_TEXT_BATCH_MAX_MS);
|
|
2438
|
+
if (batch.timer && typeof batch.timer.unref === 'function') {
|
|
2439
|
+
batch.timer.unref();
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
function queueStreamTextDelta(hctx, text, envelope) {
|
|
2444
|
+
if (typeof text !== 'string' || text.length === 0) return;
|
|
2445
|
+
const batch = getStreamTextBatch(hctx);
|
|
2446
|
+
if (!batch) {
|
|
2447
|
+
sendAssistantTextFrame(text, envelope);
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
batch.envelope = envelope;
|
|
2452
|
+
if (batch.immediateNext) {
|
|
2453
|
+
batch.immediateNext = false;
|
|
2454
|
+
sendAssistantTextFrame(text, envelope);
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
batch.parts.push(text);
|
|
2459
|
+
batch.charCount += text.length;
|
|
2460
|
+
if (batch.charCount >= STREAM_TEXT_BATCH_MAX_CHARS) {
|
|
2461
|
+
flushStreamTextBatch(hctx, envelope);
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
scheduleStreamTextBatchFlush(hctx, batch);
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2357
2467
|
/**
|
|
2358
2468
|
* Handle a single engine event unwrapped from an `engine_event` envelope.
|
|
2359
2469
|
* Stamps threadId on every outgoing frame so frontend grouping, tools,
|
|
@@ -2375,13 +2485,17 @@ function handleEngineEvent(event, hctx) {
|
|
|
2375
2485
|
threadId: hctx.threadId || event.threadId,
|
|
2376
2486
|
};
|
|
2377
2487
|
|
|
2488
|
+
if (event.type !== 'text_delta') {
|
|
2489
|
+
// Preserve wire order. Any boundary/metadata/tool event must see all text
|
|
2490
|
+
// accepted before it flushed first; otherwise the browser can render a tool
|
|
2491
|
+
// call or terminal result before the text that led to it.
|
|
2492
|
+
flushStreamTextBatch(hctx, envelope, { resetImmediate: true });
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2378
2495
|
switch (event.type) {
|
|
2379
2496
|
case 'text_delta':
|
|
2380
2497
|
hctx.assistantTextParts.push(event.text);
|
|
2381
|
-
|
|
2382
|
-
type: 'assistant',
|
|
2383
|
-
message: { content: [{ type: 'text', text: event.text }] },
|
|
2384
|
-
}, envelope);
|
|
2498
|
+
queueStreamTextDelta(hctx, event.text, envelope);
|
|
2385
2499
|
// vp-status: first text-delta of a (thinking|tool) phase flips
|
|
2386
2500
|
// the row to 'streaming'. transition() is a no-op when already
|
|
2387
2501
|
// streaming, so subsequent deltas are cheap.
|
|
@@ -3410,6 +3524,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3410
3524
|
let turnEndReason = 'end_turn';
|
|
3411
3525
|
let turnEndEmitted = false;
|
|
3412
3526
|
let turnEndDetail = null;
|
|
3527
|
+
let handlerCtx = null;
|
|
3413
3528
|
const markTurnEnd = (reason) => { turnEndEmitted = true; turnEndReason = reason; };
|
|
3414
3529
|
const emitVpTurnEnd = (reason, detail = null) => {
|
|
3415
3530
|
if (turnEndEmitted) return;
|
|
@@ -3483,7 +3598,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3483
3598
|
vpEngine = getOrCreateVpEngine(sessionId, vpId, threadId);
|
|
3484
3599
|
if (thread) thread.engine = vpEngine;
|
|
3485
3600
|
|
|
3486
|
-
|
|
3601
|
+
handlerCtx = {
|
|
3487
3602
|
assistantTextParts,
|
|
3488
3603
|
toolCallsAccum,
|
|
3489
3604
|
toolResultsAccum,
|
|
@@ -3529,6 +3644,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3529
3644
|
handleEngineEvent(event, handlerCtx);
|
|
3530
3645
|
}
|
|
3531
3646
|
|
|
3647
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3648
|
+
|
|
3532
3649
|
// Turn completed — atomically append this VP's output to shared history.
|
|
3533
3650
|
// route_forward handoff text is an internal trigger, already visible as
|
|
3534
3651
|
// the source VP's tool action. Do not append it as a visible prompt for
|
|
@@ -3557,6 +3674,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3557
3674
|
} catch (err) {
|
|
3558
3675
|
const isAbort = err && (err.name === 'AbortError' || err.name === 'LLMAbortError');
|
|
3559
3676
|
if (isAbort) {
|
|
3677
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3560
3678
|
sendSessionOutputFrame({
|
|
3561
3679
|
type: 'result',
|
|
3562
3680
|
result_text: '',
|
|
@@ -3581,6 +3699,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3581
3699
|
console.warn('[Yeaft] vp-status error transition failed:', brokerErr?.message || brokerErr);
|
|
3582
3700
|
}
|
|
3583
3701
|
|
|
3702
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3703
|
+
|
|
3584
3704
|
if (isPermissionErrorMsg(err.message)) {
|
|
3585
3705
|
if (!_permissionDiagnosticSent) {
|
|
3586
3706
|
_permissionDiagnosticSent = true;
|
|
@@ -4358,7 +4478,7 @@ export async function handleYeaftFetchToolStats(_msg = {}) {
|
|
|
4358
4478
|
}
|
|
4359
4479
|
|
|
4360
4480
|
/**
|
|
4361
|
-
* Hydrate the YeaftDebugPanel from the persistent
|
|
4481
|
+
* Hydrate the YeaftDebugPanel from the persistent file-backed trace. The
|
|
4362
4482
|
* panel state (`yeaftDebugLoops` / `yeaftDebugTurnsById`) is otherwise
|
|
4363
4483
|
* built ONLY from in-flight `loop` / `turn_open` events on the wire,
|
|
4364
4484
|
* so a panel opened after a turn has finished sees nothing for that
|
|
@@ -4366,28 +4486,36 @@ export async function handleYeaftFetchToolStats(_msg = {}) {
|
|
|
4366
4486
|
* splices into place.
|
|
4367
4487
|
*
|
|
4368
4488
|
* Inputs (all optional):
|
|
4369
|
-
* - `limit`
|
|
4370
|
-
* - `
|
|
4371
|
-
* - `
|
|
4489
|
+
* - `limit` — request cap; bounded by the file trace store
|
|
4490
|
+
* - `indexOnly` — list request summaries without loop/detail payloads
|
|
4491
|
+
* - `detailTurnId` — fetch full loops/tools for one request
|
|
4492
|
+
* - `sessionId` — narrow by Session
|
|
4493
|
+
* - `threadId` — narrow by thread
|
|
4494
|
+
* - `search` — regex matched against bounded request summaries
|
|
4372
4495
|
*
|
|
4373
4496
|
* Sends:
|
|
4374
|
-
* { type: 'yeaft_debug_history', loops: [...], turns: [...] }
|
|
4497
|
+
* { type: 'yeaft_debug_history', loops: [...], turns: [...], indexOnly, detailTurnId }
|
|
4375
4498
|
*
|
|
4376
4499
|
* Best-effort: if the session / trace isn't ready, sends an empty
|
|
4377
4500
|
* snapshot so the panel renders a placeholder instead of spinning.
|
|
4378
4501
|
*/
|
|
4379
4502
|
export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
4380
|
-
const limit = Number.isFinite(msg?.limit) ? Number(msg.limit) :
|
|
4503
|
+
const limit = Number.isFinite(msg?.limit) ? Number(msg.limit) : 10;
|
|
4381
4504
|
const dreamLimit = Number.isFinite(msg?.dreamLimit) ? Number(msg.dreamLimit) : 5;
|
|
4382
4505
|
const sessionId = typeof msg?.sessionId === 'string' && msg.sessionId ? msg.sessionId : null;
|
|
4383
4506
|
const threadId = typeof msg?.threadId === 'string' && msg.threadId ? msg.threadId : null;
|
|
4507
|
+
const search = typeof msg?.search === 'string' ? msg.search.trim() : '';
|
|
4508
|
+
const requestId = typeof msg?.requestId === 'string' && msg.requestId ? msg.requestId : null;
|
|
4509
|
+
const requestKind = typeof msg?.requestKind === 'string' && msg.requestKind ? msg.requestKind : null;
|
|
4510
|
+
const indexOnly = !!msg?.indexOnly;
|
|
4511
|
+
const detailTurnId = typeof msg?.detailTurnId === 'string' && msg.detailTurnId ? msg.detailTurnId : null;
|
|
4384
4512
|
let loops = [];
|
|
4385
4513
|
let turns = [];
|
|
4386
4514
|
let dreamEvents = [];
|
|
4387
4515
|
let hasMore = false;
|
|
4388
4516
|
try {
|
|
4389
4517
|
if (session?.trace && typeof session.trace.fetchRecentDebugHistory === 'function') {
|
|
4390
|
-
const out = session.trace.fetchRecentDebugHistory({ limit, dreamLimit, sessionId, threadId });
|
|
4518
|
+
const out = session.trace.fetchRecentDebugHistory({ limit, dreamLimit, sessionId, threadId, indexOnly, detailTurnId, search });
|
|
4391
4519
|
loops = Array.isArray(out?.loops) ? out.loops : [];
|
|
4392
4520
|
turns = Array.isArray(out?.turns) ? out.turns : [];
|
|
4393
4521
|
dreamEvents = Array.isArray(out?.dreamEvents) ? out.dreamEvents : [];
|
|
@@ -4399,6 +4527,14 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
4399
4527
|
loops: [],
|
|
4400
4528
|
turns: [],
|
|
4401
4529
|
dreamEvents: [],
|
|
4530
|
+
requestId,
|
|
4531
|
+
requestKind,
|
|
4532
|
+
sessionId,
|
|
4533
|
+
threadId,
|
|
4534
|
+
search,
|
|
4535
|
+
limit,
|
|
4536
|
+
indexOnly,
|
|
4537
|
+
detailTurnId,
|
|
4402
4538
|
error: err && err.message ? err.message : String(err),
|
|
4403
4539
|
});
|
|
4404
4540
|
return;
|
|
@@ -4408,10 +4544,15 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
4408
4544
|
loops,
|
|
4409
4545
|
turns,
|
|
4410
4546
|
dreamEvents,
|
|
4547
|
+
requestId,
|
|
4548
|
+
requestKind,
|
|
4411
4549
|
sessionId,
|
|
4412
4550
|
threadId,
|
|
4551
|
+
search,
|
|
4413
4552
|
hasMore,
|
|
4414
4553
|
limit,
|
|
4554
|
+
indexOnly,
|
|
4555
|
+
detailTurnId,
|
|
4415
4556
|
});
|
|
4416
4557
|
}
|
|
4417
4558
|
|
|
@@ -4483,6 +4624,54 @@ export function handleYeaftSubAgentPrompt(msg) {
|
|
|
4483
4624
|
}, { sessionId, vpId: task.ownerVpId || null, threadId: task.source?.threadId || null });
|
|
4484
4625
|
}
|
|
4485
4626
|
|
|
4627
|
+
export function handleYeaftTaskCancel(msg) {
|
|
4628
|
+
const sessionId = typeof msg?.sessionId === 'string' ? msg.sessionId.trim() : '';
|
|
4629
|
+
const taskId = typeof msg?.taskId === 'string' ? msg.taskId.trim() : '';
|
|
4630
|
+
const clientRequestId = typeof msg?.clientRequestId === 'string' ? msg.clientRequestId.trim() : '';
|
|
4631
|
+
const fail = (error, task = null) => {
|
|
4632
|
+
sendSessionEvent({
|
|
4633
|
+
type: 'yeaft_task_cancel_result',
|
|
4634
|
+
success: false,
|
|
4635
|
+
taskId: taskId || null,
|
|
4636
|
+
clientRequestId: clientRequestId || null,
|
|
4637
|
+
error,
|
|
4638
|
+
...(task ? { task } : {}),
|
|
4639
|
+
}, sessionId ? { sessionId, vpId: task?.ownerVpId || null, threadId: task?.source?.threadId || null } : undefined);
|
|
4640
|
+
};
|
|
4641
|
+
|
|
4642
|
+
if (!sessionId || !taskId) {
|
|
4643
|
+
fail('sessionId and taskId are required');
|
|
4644
|
+
return;
|
|
4645
|
+
}
|
|
4646
|
+
if (!session?.taskManager || typeof session.taskManager.cancelTask !== 'function') {
|
|
4647
|
+
fail('task manager unavailable');
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
let result;
|
|
4652
|
+
try {
|
|
4653
|
+
result = session.taskManager.cancelTask(sessionId, taskId);
|
|
4654
|
+
} catch (err) {
|
|
4655
|
+
fail(err?.message || String(err));
|
|
4656
|
+
return;
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
const task = result?.task || session.taskManager.getTask?.(sessionId, taskId) || null;
|
|
4660
|
+
if (!result?.ok) {
|
|
4661
|
+
fail(result?.error || 'Failed to cancel task', task);
|
|
4662
|
+
return;
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
sendSessionEvent({
|
|
4666
|
+
type: 'yeaft_task_cancel_result',
|
|
4667
|
+
success: true,
|
|
4668
|
+
taskId,
|
|
4669
|
+
clientRequestId: clientRequestId || null,
|
|
4670
|
+
pending: !!result?.pending,
|
|
4671
|
+
task,
|
|
4672
|
+
}, { sessionId, vpId: task?.ownerVpId || null, threadId: task?.source?.threadId || null });
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4486
4675
|
/** Deprecated mode switch — Yeaft is single-mode. */
|
|
4487
4676
|
export function handleYeaftModeSwitch(_msg) {
|
|
4488
4677
|
console.warn('[Yeaft] yeaft_mode_switch is deprecated and ignored — Yeaft now runs in a single unified mode.');
|
|
@@ -5112,6 +5301,7 @@ export async function handleYeaftMcpReload(msg = {}) {
|
|
|
5112
5301
|
export const __testHooks = {
|
|
5113
5302
|
loadVisibleGroupHistoryPage,
|
|
5114
5303
|
persistInboundMessageOnceByMsgId,
|
|
5304
|
+
buildPendingRescueEnvelope,
|
|
5115
5305
|
setSessionForTest(nextSession) {
|
|
5116
5306
|
session = nextSession || null;
|
|
5117
5307
|
},
|
|
@@ -5121,6 +5311,13 @@ export const __testHooks = {
|
|
|
5121
5311
|
vpAborts.clear();
|
|
5122
5312
|
vpInboxes.clear();
|
|
5123
5313
|
},
|
|
5314
|
+
resetVpStatusBroker() {
|
|
5315
|
+
if (vpStatusBroker) vpStatusBroker.reset();
|
|
5316
|
+
},
|
|
5317
|
+
seedVpStatus(status) {
|
|
5318
|
+
return getVpStatusBroker().transition(status);
|
|
5319
|
+
},
|
|
5320
|
+
decorateSessionsWithRuntimeState,
|
|
5124
5321
|
seedQueuedVpTurn({ sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
|
|
5125
5322
|
const key = threadKey(sessionId, vpId, threadId);
|
|
5126
5323
|
const inbox = vpInboxes.get(key) || [];
|