@yeaft/webchat-agent 1.0.57 → 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/engine.js +16 -0
- package/yeaft/session.js +3 -6
- package/yeaft/skills.js +13 -9
- package/yeaft/web-bridge.js +360 -114
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -703,6 +703,22 @@ export class Engine {
|
|
|
703
703
|
this.#config.language = lang;
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Hot-swap runtime managers for a project-bound Session. The ToolRegistry is
|
|
708
|
+
* shared at the bridge/session layer; these references only decide which
|
|
709
|
+
* skills are injected and which MCP manager flattened tools call through.
|
|
710
|
+
*
|
|
711
|
+
* @param {{ skillManager?: import('./skills.js').SkillManager, mcpManager?: import('./mcp.js').MCPManager }} managers
|
|
712
|
+
*/
|
|
713
|
+
setRuntimeManagers(managers = {}) {
|
|
714
|
+
if (Object.prototype.hasOwnProperty.call(managers, 'skillManager')) {
|
|
715
|
+
this.#skillManager = managers.skillManager || null;
|
|
716
|
+
}
|
|
717
|
+
if (Object.prototype.hasOwnProperty.call(managers, 'mcpManager')) {
|
|
718
|
+
this.#mcpManager = managers.mcpManager || null;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
706
722
|
/**
|
|
707
723
|
* Unregister a tool.
|
|
708
724
|
*
|
package/yeaft/session.js
CHANGED
|
@@ -363,12 +363,9 @@ export async function loadSession(options = {}) {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
// ─── 6. Load skills ────────────────────────────────────
|
|
366
|
-
// Project tier root for skills + MCP project assets. Per-session
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
// process cwd, the common case when an agent is launched inside a project
|
|
370
|
-
// the user wants project-tier assets for. Shared so skills (.claude/skills,
|
|
371
|
-
// .yeaft/skills) and MCP (.mcp.json) resolve from the same root.
|
|
366
|
+
// Project tier root for skills + MCP project assets. Per-session workDir
|
|
367
|
+
// overlays are loaded by web-bridge once it knows the selected Session meta;
|
|
368
|
+
// the base runtime still uses the agent process cwd for global/default status.
|
|
372
369
|
const projectTierRoot = process.cwd();
|
|
373
370
|
|
|
374
371
|
let skillManager;
|
package/yeaft/skills.js
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
*/
|
|
54
54
|
|
|
55
55
|
import { existsSync, readFileSync, readdirSync, writeFileSync, unlinkSync, mkdirSync, statSync } from 'fs';
|
|
56
|
-
import { join, basename, sep, dirname, resolve } from 'path';
|
|
56
|
+
import { join, basename, sep, dirname, resolve, delimiter } from 'path';
|
|
57
57
|
import { platform, homedir } from 'os';
|
|
58
58
|
import { fileURLToPath } from 'url';
|
|
59
59
|
|
|
@@ -758,19 +758,23 @@ export function createSkillManager(yeaftDir, workDir) {
|
|
|
758
758
|
const claudeUserDir = home ? join(home, '.claude', 'skills') : null;
|
|
759
759
|
const codexUserDir = home ? join(home, '.codex', 'skills') : null;
|
|
760
760
|
const userDir = join(yeaftDir, 'skills');
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const
|
|
761
|
+
const projectRoots = [...new Set(String(workDir || '')
|
|
762
|
+
.split(delimiter)
|
|
763
|
+
.map(p => p.trim())
|
|
764
|
+
.filter(Boolean))];
|
|
765
|
+
const claudeProjectDirs = projectRoots.map(root => join(root, '.claude', 'skills'));
|
|
766
|
+
const codexProjectDirs = projectRoots.map(root => join(root, '.agents', 'skills'));
|
|
767
|
+
const projectDirs = projectRoots.map(root => join(root, '.yeaft', 'skills'));
|
|
768
|
+
|
|
769
|
+
const dirs = [bundled, claudeUserDir, codexUserDir, userDir, ...claudeProjectDirs, ...codexProjectDirs, ...projectDirs].filter(Boolean);
|
|
766
770
|
const tierByDir = {};
|
|
767
771
|
if (bundled) tierByDir[bundled] = 'bundled';
|
|
768
772
|
if (claudeUserDir) tierByDir[claudeUserDir] = 'user-claude';
|
|
769
773
|
if (codexUserDir) tierByDir[codexUserDir] = 'user-codex';
|
|
770
774
|
tierByDir[userDir] = 'user';
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
775
|
+
for (const dir of claudeProjectDirs) tierByDir[dir] = 'project-claude';
|
|
776
|
+
for (const dir of codexProjectDirs) tierByDir[dir] = 'project-codex';
|
|
777
|
+
for (const dir of projectDirs) tierByDir[dir] = 'project';
|
|
774
778
|
|
|
775
779
|
const ignorePathsByDir = {};
|
|
776
780
|
if (claudeUserDir && bundled && pathIsInside(bundled, claudeUserDir)) {
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* ranges removed.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { join } from 'node:path';
|
|
21
|
+
import { delimiter, join } from 'node:path';
|
|
22
22
|
import { COLLAB_TOOL_POLICY } from './tools/registry.js';
|
|
23
23
|
import { existsSync } from 'node:fs';
|
|
24
24
|
import { randomUUID } from 'node:crypto';
|
|
@@ -26,7 +26,9 @@ import { DEFAULT_YEAFT_DIR } from './init.js';
|
|
|
26
26
|
import { buildDreamOutputSnapshot } from './dream/output-snapshot.js';
|
|
27
27
|
import { Engine } from './engine.js';
|
|
28
28
|
import { loadSession } from './session.js';
|
|
29
|
-
import { loadConfig } from './config.js';
|
|
29
|
+
import { loadConfig, loadMCPConfig } from './config.js';
|
|
30
|
+
import { createSkillManager } from './skills.js';
|
|
31
|
+
import { MCPManager } from './mcp.js';
|
|
30
32
|
import { sendToServer } from '../connection/buffer.js';
|
|
31
33
|
import ctx from '../context.js';
|
|
32
34
|
import { hydrateYeaftStatusFromSession } from './status-cache.js';
|
|
@@ -79,6 +81,13 @@ const SKILL_COMMAND_PREFIX = 'skill:';
|
|
|
79
81
|
/** @type {import('./session.js').Session | null} */
|
|
80
82
|
let session = null;
|
|
81
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
|
+
|
|
82
91
|
let threadClassifier = defaultClassifyThread;
|
|
83
92
|
|
|
84
93
|
function applyLiveLanguage(language) {
|
|
@@ -168,20 +177,29 @@ async function sendDreamSnapshotForSession(sessionId, extra = {}) {
|
|
|
168
177
|
function scheduleYeaftLoadHistoryMetadataReplay(sessionId) {
|
|
169
178
|
const replaySession = session;
|
|
170
179
|
const replayConversationId = yeaftConversationId;
|
|
171
|
-
setTimeout(() => {
|
|
180
|
+
setTimeout(async () => {
|
|
172
181
|
try {
|
|
173
182
|
if (!replaySession) return;
|
|
174
183
|
refreshLiveSessionConfig();
|
|
175
|
-
|
|
184
|
+
let projectRuntime = null;
|
|
185
|
+
if (sessionId) {
|
|
186
|
+
try {
|
|
187
|
+
const groupYeaftDir = resolveSessionYeaftDir(ctx.CONFIG?.yeaftDir || DEFAULT_YEAFT_DIR, sessionId);
|
|
188
|
+
const meta = loadSessionMeta(join(sessionsRoot(groupYeaftDir), sessionId));
|
|
189
|
+
projectRuntime = await ensureProjectRuntimeForSessionMeta(meta);
|
|
190
|
+
} catch { /* best-effort project metadata */ }
|
|
191
|
+
}
|
|
192
|
+
const status = mergedStatusForProjectRuntime(projectRuntime);
|
|
193
|
+
hydrateYeaftStatusFromSession({ ...replaySession, status }, { reason: 'history_load', emitEvent: true });
|
|
176
194
|
sendSessionEvent({
|
|
177
195
|
type: 'session_ready',
|
|
178
196
|
conversationId: replayConversationId,
|
|
179
197
|
model: replaySession.config.primaryModel || replaySession.config.model,
|
|
180
198
|
modelEffort: replaySession.config.modelEffort || null,
|
|
181
199
|
availableModels: replaySession.config.availableModels || [],
|
|
182
|
-
skills:
|
|
183
|
-
mcpServers:
|
|
184
|
-
tools:
|
|
200
|
+
skills: status.skills,
|
|
201
|
+
mcpServers: status.mcpServers,
|
|
202
|
+
tools: status.tools,
|
|
185
203
|
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
186
204
|
tasks: replaySession.taskManager ? replaySession.taskManager.listActiveTasks() : [],
|
|
187
205
|
});
|
|
@@ -385,6 +403,14 @@ function threadKey(sessionId, vpId, threadId) {
|
|
|
385
403
|
return `${sessionId}::${vpId}::${threadId || 'main'}`;
|
|
386
404
|
}
|
|
387
405
|
|
|
406
|
+
function normalizeSessionWorkDir(workDir) {
|
|
407
|
+
return typeof workDir === 'string' && workDir.trim() ? workDir.trim() : '';
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function projectRuntimeKey(workDir) {
|
|
411
|
+
return normalizeSessionWorkDir(workDir) || '__agent_cwd__';
|
|
412
|
+
}
|
|
413
|
+
|
|
388
414
|
function createThreadId() {
|
|
389
415
|
return `thr_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
|
|
390
416
|
}
|
|
@@ -394,6 +420,62 @@ const RUNNING_THREAD_STATES = new Set(['queued', 'typing', 'thinking', 'streamin
|
|
|
394
420
|
const vpThreads = new Map();
|
|
395
421
|
/** @type {Map<string, Set<Promise<string|null>>>} */
|
|
396
422
|
const routePromisesByMsgId = new Map();
|
|
423
|
+
/** @type {Map<string, { workDir: string, skillManager: import('./skills.js').SkillManager, mcpManager: import('./mcp.js').MCPManager, mcpStatus: object, mcpConfig: object, status: { skills: number, mcpServers: string[], mcpFailed: object[], mcpSkipped: object[], tools: number } }>} */
|
|
424
|
+
const projectRuntimes = new Map();
|
|
425
|
+
|
|
426
|
+
function replaceSessionMcpTools(mcpManager) {
|
|
427
|
+
if (!session?.toolRegistry || typeof session.toolRegistry.replaceMcpTools !== 'function') {
|
|
428
|
+
return { removed: 0, added: 0, skipped: true };
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const result = session.toolRegistry.replaceMcpTools(mcpManager, buildMcpFlattenedTools);
|
|
432
|
+
return { ...result, skipped: false };
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.warn('[Yeaft] hot-swap MCP tools failed:', err?.message || err);
|
|
435
|
+
return { removed: 0, added: 0, skipped: true, error: err?.message || String(err) };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function retargetVpEngines({ skillManager, mcpManager }) {
|
|
440
|
+
for (const eng of vpEngines.values()) {
|
|
441
|
+
try {
|
|
442
|
+
eng.setRuntimeManagers?.({ skillManager, mcpManager });
|
|
443
|
+
} catch { /* best-effort runtime retarget */ }
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function activateBaseRuntime() {
|
|
448
|
+
const swap = replaceSessionMcpTools(session?.mcpManager);
|
|
449
|
+
retargetVpEngines({
|
|
450
|
+
skillManager: session?.skillManager || null,
|
|
451
|
+
mcpManager: session?.mcpManager || null,
|
|
452
|
+
});
|
|
453
|
+
broadcastSkillSlashCommands(session);
|
|
454
|
+
return swap;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function activateProjectRuntime(runtime) {
|
|
458
|
+
if (!runtime) return activateBaseRuntime();
|
|
459
|
+
const swap = replaceSessionMcpTools(runtime.mcpManager);
|
|
460
|
+
retargetVpEngines({
|
|
461
|
+
skillManager: runtime.skillManager,
|
|
462
|
+
mcpManager: runtime.mcpManager,
|
|
463
|
+
});
|
|
464
|
+
runtime.status = {
|
|
465
|
+
...runtime.status,
|
|
466
|
+
tools: session?.toolRegistry?.size || runtime.status?.tools || 0,
|
|
467
|
+
};
|
|
468
|
+
broadcastSkillSlashCommands(session, [runtime.skillManager]);
|
|
469
|
+
return swap;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function shutdownProjectRuntimes() {
|
|
473
|
+
const runtimes = Array.from(projectRuntimes.values());
|
|
474
|
+
projectRuntimes.clear();
|
|
475
|
+
await Promise.all(runtimes.map(async (runtime) => {
|
|
476
|
+
try { await runtime?.mcpManager?.disconnectAll?.(); } catch { /* best-effort shutdown */ }
|
|
477
|
+
}));
|
|
478
|
+
}
|
|
397
479
|
|
|
398
480
|
function getVpThreadMap(sessionId, vpId) {
|
|
399
481
|
const key = vpKey(sessionId, vpId);
|
|
@@ -575,7 +657,8 @@ function invalidateGroupContext(sessionId) {
|
|
|
575
657
|
// Engines are NOT torn down here on purpose. They hold subordinate
|
|
576
658
|
// state (AMS adjustments) that should survive a meta change and a
|
|
577
659
|
// closed sessionHandle — they don't reach the on-disk group meta
|
|
578
|
-
// directly.
|
|
660
|
+
// directly. Project runtime managers are hot-swapped before each turn.
|
|
661
|
+
// They *are* dropped on `resetYeaftSession`.
|
|
579
662
|
}
|
|
580
663
|
|
|
581
664
|
/**
|
|
@@ -1084,6 +1167,7 @@ export function __testGroupHistory(sessionId) {
|
|
|
1084
1167
|
*/
|
|
1085
1168
|
export function __testSetSession(sessionLike) {
|
|
1086
1169
|
session = sessionLike;
|
|
1170
|
+
sessionLoadPromise = null;
|
|
1087
1171
|
if (!sessionLike) yeaftConversationId = null;
|
|
1088
1172
|
}
|
|
1089
1173
|
|
|
@@ -1703,6 +1787,7 @@ export async function __testDrainVpDrivers() {
|
|
|
1703
1787
|
* a half-aborted controller writing to a now-cleared map.
|
|
1704
1788
|
*/
|
|
1705
1789
|
export async function __testResetVpState() {
|
|
1790
|
+
await shutdownProjectRuntimes();
|
|
1706
1791
|
for (const ctrl of vpAborts.values()) {
|
|
1707
1792
|
try { if (!ctrl.signal.aborted) ctrl.abort(); } catch { /* */ }
|
|
1708
1793
|
}
|
|
@@ -1777,23 +1862,110 @@ export function buildSkillSlashCommands(skillManager) {
|
|
|
1777
1862
|
return { commands, descriptions };
|
|
1778
1863
|
}
|
|
1779
1864
|
|
|
1780
|
-
function
|
|
1781
|
-
const
|
|
1782
|
-
const
|
|
1783
|
-
const
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1865
|
+
export function buildMergedSkillSlashCommands(skillManagers = []) {
|
|
1866
|
+
const commands = [];
|
|
1867
|
+
const descriptions = {};
|
|
1868
|
+
for (const manager of skillManagers) {
|
|
1869
|
+
const built = buildSkillSlashCommands(manager);
|
|
1870
|
+
for (const cmd of built.commands) commands.push(cmd);
|
|
1871
|
+
Object.assign(descriptions, built.descriptions);
|
|
1872
|
+
}
|
|
1873
|
+
return { commands: [...new Set(commands)].sort((a, b) => a.localeCompare(b)), descriptions };
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function broadcastSkillSlashCommands(sessionLike, extraSkillManagers = []) {
|
|
1877
|
+
const managers = [sessionLike?.skillManager, ...extraSkillManagers].filter(Boolean);
|
|
1878
|
+
const { commands, descriptions } = buildMergedSkillSlashCommands(managers);
|
|
1879
|
+
const nonSkillCommands = (ctx.slashCommands || [])
|
|
1880
|
+
.filter(cmd => !(typeof cmd === 'string' && cmd.startsWith(SKILL_COMMAND_PREFIX)));
|
|
1881
|
+
const slashCommands = [...new Set([...nonSkillCommands, ...commands])];
|
|
1882
|
+
const slashCommandDescriptions = Object.fromEntries(
|
|
1883
|
+
Object.entries(ctx.slashCommandDescriptions || {})
|
|
1884
|
+
.filter(([cmd]) => !(typeof cmd === 'string' && cmd.startsWith(SKILL_COMMAND_PREFIX)))
|
|
1885
|
+
);
|
|
1886
|
+
Object.assign(slashCommandDescriptions, descriptions);
|
|
1787
1887
|
ctx.slashCommands = slashCommands;
|
|
1788
1888
|
ctx.slashCommandDescriptions = slashCommandDescriptions;
|
|
1789
1889
|
sendToServer({
|
|
1790
1890
|
type: 'slash_commands_update',
|
|
1791
|
-
|
|
1891
|
+
agentId: ctx.AGENT_ID || ctx.agentId || null,
|
|
1892
|
+
conversationId: yeaftConversationId || '__preload__',
|
|
1792
1893
|
slashCommands,
|
|
1793
1894
|
slashCommandDescriptions,
|
|
1794
1895
|
});
|
|
1795
1896
|
}
|
|
1796
1897
|
|
|
1898
|
+
async function loadProjectRuntime(workDir) {
|
|
1899
|
+
if (!session) return null;
|
|
1900
|
+
const normalizedWorkDir = normalizeSessionWorkDir(workDir);
|
|
1901
|
+
if (!normalizedWorkDir) {
|
|
1902
|
+
activateBaseRuntime();
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
const key = projectRuntimeKey(normalizedWorkDir);
|
|
1906
|
+
const cached = projectRuntimes.get(key);
|
|
1907
|
+
if (cached) {
|
|
1908
|
+
activateProjectRuntime(cached);
|
|
1909
|
+
return cached;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir || session.yeaftDir || DEFAULT_YEAFT_DIR;
|
|
1913
|
+
const skillRoots = normalizedWorkDir && normalizedWorkDir !== process.cwd()
|
|
1914
|
+
? `${process.cwd()}${delimiter}${normalizedWorkDir}`
|
|
1915
|
+
: normalizedWorkDir;
|
|
1916
|
+
const skillManager = createSkillManager(yeaftDir, skillRoots);
|
|
1917
|
+
const mcpConfig = loadMCPConfig(yeaftDir, undefined, normalizedWorkDir);
|
|
1918
|
+
const mcpManager = new MCPManager();
|
|
1919
|
+
let mcpStatus = { connected: [], failed: [] };
|
|
1920
|
+
if (mcpConfig.servers.length > 0) {
|
|
1921
|
+
mcpStatus = await mcpManager.connectAll(mcpConfig.servers);
|
|
1922
|
+
}
|
|
1923
|
+
const runtime = {
|
|
1924
|
+
workDir: normalizedWorkDir,
|
|
1925
|
+
skillManager,
|
|
1926
|
+
mcpManager,
|
|
1927
|
+
mcpStatus,
|
|
1928
|
+
mcpConfig,
|
|
1929
|
+
status: {
|
|
1930
|
+
skills: skillManager.size,
|
|
1931
|
+
mcpServers: mcpStatus.connected,
|
|
1932
|
+
mcpFailed: mcpStatus.failed,
|
|
1933
|
+
mcpSkipped: mcpConfig.skipped || [],
|
|
1934
|
+
tools: session.toolRegistry?.size || 0,
|
|
1935
|
+
},
|
|
1936
|
+
};
|
|
1937
|
+
projectRuntimes.set(key, runtime);
|
|
1938
|
+
activateProjectRuntime(runtime);
|
|
1939
|
+
return runtime;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
async function ensureProjectRuntimeForSessionMeta(sessionMeta) {
|
|
1943
|
+
const workDir = normalizeSessionWorkDir(sessionMeta?.workDir);
|
|
1944
|
+
if (!workDir) {
|
|
1945
|
+
activateBaseRuntime();
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
1948
|
+
try {
|
|
1949
|
+
return await loadProjectRuntime(workDir);
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
console.warn('[Yeaft] project runtime load failed for %s: %s', workDir, err?.message || err);
|
|
1952
|
+
activateBaseRuntime();
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
function mergedStatusForProjectRuntime(runtime) {
|
|
1958
|
+
if (!session?.status || !runtime?.status) return session?.status || { skills: 0, mcpServers: [], tools: 0 };
|
|
1959
|
+
return {
|
|
1960
|
+
...session.status,
|
|
1961
|
+
skills: Math.max(Number(session.status.skills) || 0, Number(runtime.status.skills) || 0),
|
|
1962
|
+
mcpServers: [...new Set([...(session.status.mcpServers || []), ...(runtime.status.mcpServers || [])])],
|
|
1963
|
+
mcpFailed: [...(session.status.mcpFailed || []), ...(runtime.status.mcpFailed || [])],
|
|
1964
|
+
mcpSkipped: [...(session.status.mcpSkipped || []), ...(runtime.status.mcpSkipped || [])],
|
|
1965
|
+
tools: Math.max(Number(session.status.tools) || 0, Number(runtime.status.tools) || 0),
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1797
1969
|
/** Send a Yeaft Session metadata event over the legacy-compatible envelope. */
|
|
1798
1970
|
function sendSessionEvent(event, { sessionId, chatId, vpId, turnId, threadId, perfTraceId } = {}) {
|
|
1799
1971
|
sendToServer({
|
|
@@ -3121,15 +3293,13 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
3121
3293
|
return;
|
|
3122
3294
|
}
|
|
3123
3295
|
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
// Open the session. The default `grp_default` no longer self-seeds
|
|
3129
|
-
// here — session creation happens up-front via `handleYeaftCreateSession`,
|
|
3130
|
-
// so a missing dir surfaces a clear error rather than masking it.
|
|
3296
|
+
// Open the session metadata first so a workDir-backed Session can load its
|
|
3297
|
+
// project-tier skills and MCP before the first engine turn. The runtime boot
|
|
3298
|
+
// below uses the same workDir; if metadata is missing we still boot the agent
|
|
3299
|
+
// runtime for a useful error response, then fail on the session open check.
|
|
3131
3300
|
let sessionHandle = null;
|
|
3132
3301
|
let sessionRoot = null;
|
|
3302
|
+
let sessionMetaForRuntime = null;
|
|
3133
3303
|
const openSessionStart = perfNowMs();
|
|
3134
3304
|
try {
|
|
3135
3305
|
const groupYeaftDir = resolveSessionYeaftDir(yeaftDir, sessionId);
|
|
@@ -3137,6 +3307,7 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
3137
3307
|
const dir = join(sessionRoot, sessionId);
|
|
3138
3308
|
if (existsSync(dir) && loadSessionMeta(dir)) {
|
|
3139
3309
|
sessionHandle = openSession(sessionRoot, sessionId);
|
|
3310
|
+
sessionMetaForRuntime = sessionHandle.getMeta();
|
|
3140
3311
|
} else {
|
|
3141
3312
|
// fix-yeaft-session-server-persistence: the `grp_default` on-the-
|
|
3142
3313
|
// fly seed used to manufacture a missing session here. That
|
|
@@ -3149,9 +3320,14 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
3149
3320
|
} catch (err) {
|
|
3150
3321
|
console.warn('[Yeaft] yeaft_session_chat: session open failed', err?.message || err);
|
|
3151
3322
|
}
|
|
3152
|
-
|
|
3153
3323
|
traceDuration('session_send.open_session', openSessionStart, { ok: !!sessionHandle });
|
|
3154
3324
|
|
|
3325
|
+
const ensureSessionStart = perfNowMs();
|
|
3326
|
+
await ensureSessionLoaded({ sessionMeta: sessionMetaForRuntime, perfTraceId });
|
|
3327
|
+
await ensureProjectRuntimeForSessionMeta(sessionMetaForRuntime);
|
|
3328
|
+
traceDuration('session_send.ensure_session_loaded', ensureSessionStart);
|
|
3329
|
+
|
|
3330
|
+
|
|
3155
3331
|
if (!sessionHandle) {
|
|
3156
3332
|
sendSessionOutputFrame({
|
|
3157
3333
|
type: 'assistant',
|
|
@@ -3498,78 +3674,125 @@ export function buildVpQueryOpts({ vpId, sessionCoordinator, sessionId, envelope
|
|
|
3498
3674
|
}
|
|
3499
3675
|
|
|
3500
3676
|
/**
|
|
3501
|
-
* Lazy session boot. Idempotent:
|
|
3502
|
-
*
|
|
3503
|
-
*
|
|
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>}
|
|
3504
3682
|
*/
|
|
3505
|
-
async function ensureSessionLoaded() {
|
|
3506
|
-
if (session) return;
|
|
3683
|
+
async function ensureSessionLoaded(opts = {}) {
|
|
3684
|
+
if (session) return session;
|
|
3685
|
+
if (sessionLoadPromise) return sessionLoadPromise;
|
|
3507
3686
|
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
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
|
+
});
|
|
3515
3697
|
|
|
3516
|
-
|
|
3698
|
+
installYeaftRuntimeBridge(session);
|
|
3517
3699
|
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
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);
|
|
3525
3710
|
}
|
|
3526
|
-
} catch (err) {
|
|
3527
|
-
console.warn('[Yeaft] setSubAgentEventSink wiring failed:', err?.message || err);
|
|
3528
|
-
}
|
|
3529
3711
|
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
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
|
+
}
|
|
3536
3719
|
}
|
|
3720
|
+
} catch (err) {
|
|
3721
|
+
console.warn('[Yeaft] purgeArchivedSessions failed:', err?.message || err);
|
|
3537
3722
|
}
|
|
3538
|
-
} catch (err) {
|
|
3539
|
-
console.warn('[Yeaft] purgeArchivedSessions failed:', err?.message || err);
|
|
3540
|
-
}
|
|
3541
3723
|
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
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] : []);
|
|
3545
3729
|
|
|
3546
|
-
|
|
3547
|
-
|
|
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
|
+
})();
|
|
3548
3758
|
|
|
3549
|
-
sendSessionEvent({
|
|
3550
|
-
type: 'session_ready',
|
|
3551
|
-
conversationId: yeaftConversationId,
|
|
3552
|
-
model: session.config.primaryModel || session.config.model,
|
|
3553
|
-
modelEffort: session.config.modelEffort || null,
|
|
3554
|
-
availableModels: session.config.availableModels || [],
|
|
3555
|
-
skills: session.status.skills,
|
|
3556
|
-
mcpServers: session.status.mcpServers,
|
|
3557
|
-
tools: session.status.tools,
|
|
3558
|
-
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
3559
|
-
tasks: session.taskManager ? session.taskManager.listActiveTasks() : [],
|
|
3560
|
-
});
|
|
3561
|
-
sendSessionSnapshotBroadcast();
|
|
3562
|
-
// vp-status: rebuild frontend status table from authoritative agent
|
|
3563
|
-
// memory. Sent unconditionally so reconnect/refresh paths get the same
|
|
3564
|
-
// bootstrap as first-load (the broker dedup logic makes a redundant
|
|
3565
|
-
// snapshot harmless).
|
|
3566
3759
|
try {
|
|
3567
|
-
|
|
3760
|
+
return await sessionLoadPromise;
|
|
3568
3761
|
} catch (err) {
|
|
3569
|
-
|
|
3762
|
+
session = null;
|
|
3763
|
+
throw err;
|
|
3764
|
+
} finally {
|
|
3765
|
+
sessionLoadPromise = null;
|
|
3570
3766
|
}
|
|
3571
3767
|
}
|
|
3572
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
|
+
|
|
3573
3796
|
/**
|
|
3574
3797
|
* Wrap {@link runVpTurn} with a hard escalation deadline.
|
|
3575
3798
|
*
|
|
@@ -3795,8 +4018,20 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3795
4018
|
envelope: inboundEnvelope,
|
|
3796
4019
|
threadId,
|
|
3797
4020
|
});
|
|
4021
|
+
const projectRuntime = await ensureProjectRuntimeForSessionMeta(sessionCoordinator?.group?.getMeta?.());
|
|
3798
4022
|
|
|
3799
4023
|
vpEngine = getOrCreateVpEngine(sessionId, vpId, threadId);
|
|
4024
|
+
if (projectRuntime) {
|
|
4025
|
+
vpEngine.setRuntimeManagers?.({
|
|
4026
|
+
skillManager: projectRuntime.skillManager,
|
|
4027
|
+
mcpManager: projectRuntime.mcpManager,
|
|
4028
|
+
});
|
|
4029
|
+
} else {
|
|
4030
|
+
vpEngine.setRuntimeManagers?.({
|
|
4031
|
+
skillManager: session?.skillManager || null,
|
|
4032
|
+
mcpManager: session?.mcpManager || null,
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
3800
4035
|
if (thread) thread.engine = vpEngine;
|
|
3801
4036
|
|
|
3802
4037
|
const inboundInjectedBy = inboundEnvelope?.msg?.meta?.injectedBy;
|
|
@@ -5233,26 +5468,19 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5233
5468
|
}
|
|
5234
5469
|
historyAlreadyReplayed = true;
|
|
5235
5470
|
|
|
5236
|
-
|
|
5237
|
-
session = await loadSession({
|
|
5238
|
-
dir: yeaftDir,
|
|
5239
|
-
skipMCP: false,
|
|
5240
|
-
skipSkills: false,
|
|
5241
|
-
serverMode: true,
|
|
5242
|
-
});
|
|
5243
|
-
traceDuration('history.load_session_runtime', bootStart);
|
|
5244
|
-
installYeaftRuntimeBridge(session);
|
|
5245
|
-
|
|
5246
|
-
// Per-group history hydrates lazily via getOrCreateSessionHistory.
|
|
5247
|
-
// When the load-history call carries a sessionId, force-refresh THAT
|
|
5248
|
-
// group's tape so the next user message sees on-disk state. When
|
|
5249
|
-
// it doesn't (legacy callers), do nothing — the per-group lazy
|
|
5250
|
-
// hydration handles it.
|
|
5471
|
+
let sessionMetaForRuntime = null;
|
|
5251
5472
|
if (sessionId) {
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5473
|
+
try {
|
|
5474
|
+
const groupYeaftDir = resolveSessionYeaftDir(yeaftDir, sessionId);
|
|
5475
|
+
const metaDir = join(sessionsRoot(groupYeaftDir), sessionId);
|
|
5476
|
+
sessionMetaForRuntime = loadSessionMeta(metaDir);
|
|
5477
|
+
} catch { /* best-effort metadata hint */ }
|
|
5255
5478
|
}
|
|
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 });
|
|
5256
5484
|
} else {
|
|
5257
5485
|
const replayStart = perfNowMs();
|
|
5258
5486
|
replayHistoryFromStore();
|
|
@@ -5260,10 +5488,12 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5260
5488
|
historyAlreadyReplayed = true;
|
|
5261
5489
|
}
|
|
5262
5490
|
|
|
5263
|
-
if (sessionId) {
|
|
5491
|
+
if (session && sessionId) {
|
|
5264
5492
|
// Re-entering an existing session with a (possibly new) group filter:
|
|
5265
5493
|
// re-seed THIS group's history from disk so it doesn't carry stale
|
|
5266
|
-
// 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.
|
|
5267
5497
|
const hydrateStart = perfNowMs();
|
|
5268
5498
|
setGroupHistory(sessionId, hydrateGroupHistory(sessionId));
|
|
5269
5499
|
traceDuration('history.hydrate_group_history_final', hydrateStart);
|
|
@@ -5273,7 +5503,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5273
5503
|
// never make the history response wait for bulky metadata snapshots. The
|
|
5274
5504
|
// first visible chunk has already been sent above; defer metadata to the next
|
|
5275
5505
|
// tick so the browser can paint messages before VP/session/dream snapshots.
|
|
5276
|
-
scheduleYeaftLoadHistoryMetadataReplay(sessionId);
|
|
5506
|
+
if (session) scheduleYeaftLoadHistoryMetadataReplay(sessionId);
|
|
5277
5507
|
|
|
5278
5508
|
if (historyAlreadyReplayed) {
|
|
5279
5509
|
traceDuration('history.handler_total', perfStart, { ok: true });
|
|
@@ -5352,6 +5582,7 @@ export async function handleYeaftLoadMoreHistory(msg) {
|
|
|
5352
5582
|
* session, then re-initialises so the frontend gets fresh config.
|
|
5353
5583
|
*/
|
|
5354
5584
|
export async function resetYeaftSession() {
|
|
5585
|
+
await shutdownProjectRuntimes();
|
|
5355
5586
|
if (currentAbortCtrl && !currentAbortCtrl.signal.aborted) {
|
|
5356
5587
|
try { currentAbortCtrl.abort(); } catch { /* ignore */ }
|
|
5357
5588
|
}
|
|
@@ -5415,6 +5646,7 @@ export async function resetYeaftSession() {
|
|
|
5415
5646
|
|
|
5416
5647
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
5417
5648
|
hydrateYeaftStatusFromSession(session, { reason: 'reset', emitEvent: true });
|
|
5649
|
+
broadcastSkillSlashCommands(session);
|
|
5418
5650
|
|
|
5419
5651
|
// Per-group history hydrates lazily via getOrCreateSessionHistory on
|
|
5420
5652
|
// first read. Nothing to seed here.
|
|
@@ -5495,16 +5727,7 @@ function mcpRuntimeSnapshot() {
|
|
|
5495
5727
|
* pick up the change.
|
|
5496
5728
|
*/
|
|
5497
5729
|
function hotSwapMcpTools() {
|
|
5498
|
-
|
|
5499
|
-
return { removed: 0, added: 0, skipped: true };
|
|
5500
|
-
}
|
|
5501
|
-
try {
|
|
5502
|
-
const result = session.toolRegistry.replaceMcpTools(session.mcpManager, buildMcpFlattenedTools);
|
|
5503
|
-
return { ...result, skipped: false };
|
|
5504
|
-
} catch (err) {
|
|
5505
|
-
console.warn('[Yeaft] hot-swap MCP tools failed:', err?.message || err);
|
|
5506
|
-
return { removed: 0, added: 0, skipped: true, error: err?.message || String(err) };
|
|
5507
|
-
}
|
|
5730
|
+
return replaceSessionMcpTools(session?.mcpManager);
|
|
5508
5731
|
}
|
|
5509
5732
|
|
|
5510
5733
|
/**
|
|
@@ -5563,7 +5786,7 @@ export async function handleYeaftMcpAdd(msg = {}) {
|
|
|
5563
5786
|
}
|
|
5564
5787
|
}
|
|
5565
5788
|
|
|
5566
|
-
const swap =
|
|
5789
|
+
const swap = replaceSessionMcpTools(session?.mcpManager);
|
|
5567
5790
|
|
|
5568
5791
|
sendToServer({
|
|
5569
5792
|
type: 'yeaft_mcp_add_result',
|
|
@@ -5600,7 +5823,7 @@ export async function handleYeaftMcpRemove(msg = {}) {
|
|
|
5600
5823
|
}
|
|
5601
5824
|
}
|
|
5602
5825
|
|
|
5603
|
-
const swap =
|
|
5826
|
+
const swap = replaceSessionMcpTools(session?.mcpManager);
|
|
5604
5827
|
|
|
5605
5828
|
sendToServer({
|
|
5606
5829
|
type: 'yeaft_mcp_remove_result',
|
|
@@ -5658,7 +5881,7 @@ export async function handleYeaftMcpReload(msg = {}) {
|
|
|
5658
5881
|
console.warn('[Yeaft] MCP reload failed:', err?.message || err);
|
|
5659
5882
|
}
|
|
5660
5883
|
|
|
5661
|
-
const swap =
|
|
5884
|
+
const swap = replaceSessionMcpTools(session?.mcpManager);
|
|
5662
5885
|
|
|
5663
5886
|
sendToServer({
|
|
5664
5887
|
type: 'yeaft_mcp_reload_result',
|
|
@@ -5678,6 +5901,7 @@ export const __testHooks = {
|
|
|
5678
5901
|
buildPendingRescueEnvelope,
|
|
5679
5902
|
setSessionForTest(nextSession) {
|
|
5680
5903
|
session = nextSession || null;
|
|
5904
|
+
sessionLoadPromise = null;
|
|
5681
5905
|
},
|
|
5682
5906
|
resetAbortState() {
|
|
5683
5907
|
turnAbortCtrls.clear();
|
|
@@ -5692,6 +5916,28 @@ export const __testHooks = {
|
|
|
5692
5916
|
return getVpStatusBroker().transition(status);
|
|
5693
5917
|
},
|
|
5694
5918
|
decorateSessionsWithRuntimeState,
|
|
5919
|
+
async loadProjectRuntime(workDir) {
|
|
5920
|
+
return loadProjectRuntime(workDir);
|
|
5921
|
+
},
|
|
5922
|
+
seedProjectRuntime(workDir, runtime) {
|
|
5923
|
+
const normalizedWorkDir = normalizeSessionWorkDir(workDir);
|
|
5924
|
+
const seeded = {
|
|
5925
|
+
workDir: normalizedWorkDir,
|
|
5926
|
+
skillManager: runtime?.skillManager || { list: () => [] },
|
|
5927
|
+
mcpManager: runtime?.mcpManager || { listTools: () => [], disconnectAll: async () => {} },
|
|
5928
|
+
mcpStatus: runtime?.mcpStatus || { connected: [], failed: [] },
|
|
5929
|
+
mcpConfig: runtime?.mcpConfig || { servers: [], skipped: [] },
|
|
5930
|
+
status: runtime?.status || { skills: 0, mcpServers: [], mcpFailed: [], mcpSkipped: [], tools: 0 },
|
|
5931
|
+
};
|
|
5932
|
+
projectRuntimes.set(projectRuntimeKey(normalizedWorkDir), seeded);
|
|
5933
|
+
return seeded;
|
|
5934
|
+
},
|
|
5935
|
+
async shutdownProjectRuntimes() {
|
|
5936
|
+
return shutdownProjectRuntimes();
|
|
5937
|
+
},
|
|
5938
|
+
projectRuntimeCount() {
|
|
5939
|
+
return projectRuntimes.size;
|
|
5940
|
+
},
|
|
5695
5941
|
seedQueuedVpTurn({ sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
|
|
5696
5942
|
const key = threadKey(sessionId, vpId, threadId);
|
|
5697
5943
|
const inbox = vpInboxes.get(key) || [];
|