@yeaft/webchat-agent 1.0.58 → 1.0.59
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/package.json +1 -1
- package/yeaft/web-bridge.js +123 -84
package/package.json
CHANGED
package/yeaft/web-bridge.js
CHANGED
|
@@ -81,6 +81,13 @@ const SKILL_COMMAND_PREFIX = 'skill:';
|
|
|
81
81
|
/** @type {import('./session.js').Session | null} */
|
|
82
82
|
let session = null;
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Single-flight runtime boot. History replay must not wait for this promise on
|
|
86
|
+
* cold load; message send still awaits it through ensureSessionLoaded().
|
|
87
|
+
* @type {Promise<import('./session.js').Session> | null}
|
|
88
|
+
*/
|
|
89
|
+
let sessionLoadPromise = null;
|
|
90
|
+
|
|
84
91
|
let threadClassifier = defaultClassifyThread;
|
|
85
92
|
|
|
86
93
|
function applyLiveLanguage(language) {
|
|
@@ -1160,6 +1167,7 @@ export function __testGroupHistory(sessionId) {
|
|
|
1160
1167
|
*/
|
|
1161
1168
|
export function __testSetSession(sessionLike) {
|
|
1162
1169
|
session = sessionLike;
|
|
1170
|
+
sessionLoadPromise = null;
|
|
1163
1171
|
if (!sessionLike) yeaftConversationId = null;
|
|
1164
1172
|
}
|
|
1165
1173
|
|
|
@@ -3315,7 +3323,7 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
3315
3323
|
traceDuration('session_send.open_session', openSessionStart, { ok: !!sessionHandle });
|
|
3316
3324
|
|
|
3317
3325
|
const ensureSessionStart = perfNowMs();
|
|
3318
|
-
await ensureSessionLoaded();
|
|
3326
|
+
await ensureSessionLoaded({ sessionMeta: sessionMetaForRuntime, perfTraceId });
|
|
3319
3327
|
await ensureProjectRuntimeForSessionMeta(sessionMetaForRuntime);
|
|
3320
3328
|
traceDuration('session_send.ensure_session_loaded', ensureSessionStart);
|
|
3321
3329
|
|
|
@@ -3666,80 +3674,125 @@ export function buildVpQueryOpts({ vpId, sessionCoordinator, sessionId, envelope
|
|
|
3666
3674
|
}
|
|
3667
3675
|
|
|
3668
3676
|
/**
|
|
3669
|
-
* Lazy session boot. Idempotent:
|
|
3670
|
-
*
|
|
3671
|
-
*
|
|
3677
|
+
* Lazy session boot. Idempotent: concurrent callers share one in-flight promise.
|
|
3678
|
+
* Emits `session_ready` on first init so the frontend can finalize its handshake.
|
|
3679
|
+
*
|
|
3680
|
+
* @param {{ workDir?: string, sessionId?: string|null, sessionMeta?: object, perfTraceId?: string|null, messageType?: string }} [opts]
|
|
3681
|
+
* @returns {Promise<import('./session.js').Session>}
|
|
3672
3682
|
*/
|
|
3673
|
-
async function ensureSessionLoaded() {
|
|
3674
|
-
if (session) return;
|
|
3683
|
+
async function ensureSessionLoaded(opts = {}) {
|
|
3684
|
+
if (session) return session;
|
|
3685
|
+
if (sessionLoadPromise) return sessionLoadPromise;
|
|
3675
3686
|
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3687
|
+
sessionLoadPromise = (async () => {
|
|
3688
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
3689
|
+
const normalizedWorkDir = normalizeSessionWorkDir(opts?.workDir || opts?.sessionMeta?.workDir);
|
|
3690
|
+
session = await loadSession({
|
|
3691
|
+
...(yeaftDir && { dir: yeaftDir }),
|
|
3692
|
+
...(normalizedWorkDir && { workDir: normalizedWorkDir }),
|
|
3693
|
+
skipMCP: false,
|
|
3694
|
+
skipSkills: false,
|
|
3695
|
+
serverMode: true,
|
|
3696
|
+
});
|
|
3683
3697
|
|
|
3684
|
-
|
|
3698
|
+
installYeaftRuntimeBridge(session);
|
|
3685
3699
|
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3700
|
+
try {
|
|
3701
|
+
if (session.engine && typeof session.engine.setSubAgentEventSink === 'function') {
|
|
3702
|
+
session.engine.setSubAgentEventSink((agentId, evt) => {
|
|
3703
|
+
try {
|
|
3704
|
+
sendSessionEvent({ type: 'sub_agent_event', agentId, payload: evt });
|
|
3705
|
+
} catch { /* ignore */ }
|
|
3706
|
+
});
|
|
3707
|
+
}
|
|
3708
|
+
} catch (err) {
|
|
3709
|
+
console.warn('[Yeaft] setSubAgentEventSink wiring failed:', err?.message || err);
|
|
3693
3710
|
}
|
|
3694
|
-
} catch (err) {
|
|
3695
|
-
console.warn('[Yeaft] setSubAgentEventSink wiring failed:', err?.message || err);
|
|
3696
|
-
}
|
|
3697
3711
|
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3712
|
+
// Bug 8: clean up legacy `.archived-*` group dirs at boot.
|
|
3713
|
+
try {
|
|
3714
|
+
if (yeaftDir) {
|
|
3715
|
+
const removed = purgeArchivedSessions(yeaftDir);
|
|
3716
|
+
if (removed && removed.length > 0) {
|
|
3717
|
+
console.log(`[Yeaft] purged ${removed.length} legacy .archived group dir(s)`);
|
|
3718
|
+
}
|
|
3704
3719
|
}
|
|
3720
|
+
} catch (err) {
|
|
3721
|
+
console.warn('[Yeaft] purgeArchivedSessions failed:', err?.message || err);
|
|
3705
3722
|
}
|
|
3706
|
-
} catch (err) {
|
|
3707
|
-
console.warn('[Yeaft] purgeArchivedSessions failed:', err?.message || err);
|
|
3708
|
-
}
|
|
3709
3723
|
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3724
|
+
ensureYeaftConversationId();
|
|
3725
|
+
const bootProjectRuntime = normalizedWorkDir ? await ensureProjectRuntimeForSessionMeta({ workDir: normalizedWorkDir }) : null;
|
|
3726
|
+
const bootStatus = mergedStatusForProjectRuntime(bootProjectRuntime);
|
|
3727
|
+
hydrateYeaftStatusFromSession({ ...session, status: bootStatus }, { reason: 'session_ready', emitEvent: true });
|
|
3728
|
+
broadcastSkillSlashCommands(session, bootProjectRuntime ? [bootProjectRuntime.skillManager] : []);
|
|
3715
3729
|
|
|
3716
|
-
|
|
3717
|
-
|
|
3730
|
+
// Per-group history is hydrated lazily on first `getOrCreateSessionHistory`
|
|
3731
|
+
// — there's no global "all conversations" tape any more.
|
|
3732
|
+
|
|
3733
|
+
sendSessionEvent({
|
|
3734
|
+
type: 'session_ready',
|
|
3735
|
+
conversationId: yeaftConversationId,
|
|
3736
|
+
model: session.config.primaryModel || session.config.model,
|
|
3737
|
+
modelEffort: session.config.modelEffort || null,
|
|
3738
|
+
availableModels: session.config.availableModels || [],
|
|
3739
|
+
skills: bootStatus.skills,
|
|
3740
|
+
mcpServers: bootStatus.mcpServers,
|
|
3741
|
+
tools: bootStatus.tools,
|
|
3742
|
+
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
3743
|
+
tasks: session.taskManager ? session.taskManager.listActiveTasks() : [],
|
|
3744
|
+
}, opts?.perfTraceId ? { sessionId: opts?.sessionMeta?.id || opts?.sessionId || null, perfTraceId: opts.perfTraceId } : undefined);
|
|
3745
|
+
sendSessionSnapshotBroadcast();
|
|
3746
|
+
// vp-status: rebuild frontend status table from authoritative agent
|
|
3747
|
+
// memory. Sent unconditionally so reconnect/refresh paths get the same
|
|
3748
|
+
// bootstrap as first-load (the broker dedup logic makes a redundant
|
|
3749
|
+
// snapshot harmless).
|
|
3750
|
+
try {
|
|
3751
|
+
getVpStatusBroker().broadcastSnapshot();
|
|
3752
|
+
} catch (err) {
|
|
3753
|
+
console.warn('[Yeaft] vp-status snapshot broadcast failed:', err?.message || err);
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
return session;
|
|
3757
|
+
})();
|
|
3718
3758
|
|
|
3719
|
-
sendSessionEvent({
|
|
3720
|
-
type: 'session_ready',
|
|
3721
|
-
conversationId: yeaftConversationId,
|
|
3722
|
-
model: session.config.primaryModel || session.config.model,
|
|
3723
|
-
modelEffort: session.config.modelEffort || null,
|
|
3724
|
-
availableModels: session.config.availableModels || [],
|
|
3725
|
-
skills: bootStatus.skills,
|
|
3726
|
-
mcpServers: bootStatus.mcpServers,
|
|
3727
|
-
tools: bootStatus.tools,
|
|
3728
|
-
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
3729
|
-
tasks: session.taskManager ? session.taskManager.listActiveTasks() : [],
|
|
3730
|
-
});
|
|
3731
|
-
sendSessionSnapshotBroadcast();
|
|
3732
|
-
// vp-status: rebuild frontend status table from authoritative agent
|
|
3733
|
-
// memory. Sent unconditionally so reconnect/refresh paths get the same
|
|
3734
|
-
// bootstrap as first-load (the broker dedup logic makes a redundant
|
|
3735
|
-
// snapshot harmless).
|
|
3736
3759
|
try {
|
|
3737
|
-
|
|
3760
|
+
return await sessionLoadPromise;
|
|
3738
3761
|
} catch (err) {
|
|
3739
|
-
|
|
3762
|
+
session = null;
|
|
3763
|
+
throw err;
|
|
3764
|
+
} finally {
|
|
3765
|
+
sessionLoadPromise = null;
|
|
3740
3766
|
}
|
|
3741
3767
|
}
|
|
3742
3768
|
|
|
3769
|
+
function startSessionLoadInBackground({ sessionId = null, sessionMeta = null, perfTraceId = null, traceDuration = null, tracePerf = null, phase = 'history.load_session_runtime' } = {}) {
|
|
3770
|
+
if (session) return null;
|
|
3771
|
+
const start = perfNowMs();
|
|
3772
|
+
const promise = ensureSessionLoaded({ sessionId, sessionMeta, perfTraceId })
|
|
3773
|
+
.then(async (loaded) => {
|
|
3774
|
+
if (typeof traceDuration === 'function') traceDuration(phase, start, { detail: { background: true } });
|
|
3775
|
+
if (sessionId && loaded?.conversationStore) {
|
|
3776
|
+
const hydrateStart = perfNowMs();
|
|
3777
|
+
setGroupHistory(sessionId, hydrateGroupHistory(sessionId));
|
|
3778
|
+
if (typeof traceDuration === 'function') traceDuration('history.hydrate_group_history', hydrateStart, { detail: { background: true } });
|
|
3779
|
+
sendDreamSnapshotForSession(sessionId, { trigger: 'load_history' }).catch(() => null);
|
|
3780
|
+
}
|
|
3781
|
+
return loaded;
|
|
3782
|
+
})
|
|
3783
|
+
.catch((err) => {
|
|
3784
|
+
if (typeof tracePerf === 'function') {
|
|
3785
|
+
tracePerf('history.load_session_runtime_error', {
|
|
3786
|
+
ok: false,
|
|
3787
|
+
detail: { background: true, errorName: err?.name || null, errorMessage: err?.message || String(err) },
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
console.warn('[Yeaft] background session load failed:', err?.message || err);
|
|
3791
|
+
return null;
|
|
3792
|
+
});
|
|
3793
|
+
return promise;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3743
3796
|
/**
|
|
3744
3797
|
* Wrap {@link runVpTurn} with a hard escalation deadline.
|
|
3745
3798
|
*
|
|
@@ -5423,28 +5476,11 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5423
5476
|
sessionMetaForRuntime = loadSessionMeta(metaDir);
|
|
5424
5477
|
} catch { /* best-effort metadata hint */ }
|
|
5425
5478
|
}
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
skipSkills: false,
|
|
5432
|
-
serverMode: true,
|
|
5433
|
-
});
|
|
5434
|
-
traceDuration('history.load_session_runtime', bootStart);
|
|
5435
|
-
installYeaftRuntimeBridge(session);
|
|
5436
|
-
await ensureProjectRuntimeForSessionMeta(sessionMetaForRuntime);
|
|
5437
|
-
|
|
5438
|
-
// Per-group history hydrates lazily via getOrCreateSessionHistory.
|
|
5439
|
-
// When the load-history call carries a sessionId, force-refresh THAT
|
|
5440
|
-
// group's tape so the next user message sees on-disk state. When
|
|
5441
|
-
// it doesn't (legacy callers), do nothing — the per-group lazy
|
|
5442
|
-
// hydration handles it.
|
|
5443
|
-
if (sessionId) {
|
|
5444
|
-
const hydrateStart = perfNowMs();
|
|
5445
|
-
setGroupHistory(sessionId, hydrateGroupHistory(sessionId));
|
|
5446
|
-
traceDuration('history.hydrate_group_history', hydrateStart);
|
|
5447
|
-
}
|
|
5479
|
+
// Full runtime boot can be expensive (memory FTS sync, skills, MCP, dream
|
|
5480
|
+
// boot checks). It is not needed to render persisted history, so keep this
|
|
5481
|
+
// request short and let message-send await the same single-flight boot when
|
|
5482
|
+
// the user actually submits a turn.
|
|
5483
|
+
startSessionLoadInBackground({ sessionId, sessionMeta: sessionMetaForRuntime, perfTraceId, traceDuration, tracePerf });
|
|
5448
5484
|
} else {
|
|
5449
5485
|
const replayStart = perfNowMs();
|
|
5450
5486
|
replayHistoryFromStore();
|
|
@@ -5452,10 +5488,12 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5452
5488
|
historyAlreadyReplayed = true;
|
|
5453
5489
|
}
|
|
5454
5490
|
|
|
5455
|
-
if (sessionId) {
|
|
5491
|
+
if (session && sessionId) {
|
|
5456
5492
|
// Re-entering an existing session with a (possibly new) group filter:
|
|
5457
5493
|
// re-seed THIS group's history from disk so it doesn't carry stale
|
|
5458
|
-
// in-memory state into the next turn's context.
|
|
5494
|
+
// in-memory state into the next turn's context. Do not mark it hydrated
|
|
5495
|
+
// before the runtime exists; that would cache an empty tape and starve the
|
|
5496
|
+
// next user turn of persisted context.
|
|
5459
5497
|
const hydrateStart = perfNowMs();
|
|
5460
5498
|
setGroupHistory(sessionId, hydrateGroupHistory(sessionId));
|
|
5461
5499
|
traceDuration('history.hydrate_group_history_final', hydrateStart);
|
|
@@ -5465,7 +5503,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5465
5503
|
// never make the history response wait for bulky metadata snapshots. The
|
|
5466
5504
|
// first visible chunk has already been sent above; defer metadata to the next
|
|
5467
5505
|
// tick so the browser can paint messages before VP/session/dream snapshots.
|
|
5468
|
-
scheduleYeaftLoadHistoryMetadataReplay(sessionId);
|
|
5506
|
+
if (session) scheduleYeaftLoadHistoryMetadataReplay(sessionId);
|
|
5469
5507
|
|
|
5470
5508
|
if (historyAlreadyReplayed) {
|
|
5471
5509
|
traceDuration('history.handler_total', perfStart, { ok: true });
|
|
@@ -5863,6 +5901,7 @@ export const __testHooks = {
|
|
|
5863
5901
|
buildPendingRescueEnvelope,
|
|
5864
5902
|
setSessionForTest(nextSession) {
|
|
5865
5903
|
session = nextSession || null;
|
|
5904
|
+
sessionLoadPromise = null;
|
|
5866
5905
|
},
|
|
5867
5906
|
resetAbortState() {
|
|
5868
5907
|
turnAbortCtrls.clear();
|