@yeaft/webchat-agent 1.0.196 → 1.0.197
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/local-runtime/server/handlers/agent-output.js +27 -11
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +77 -77
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +1 -1
- package/yeaft/skills.js +36 -3
- package/yeaft/web-bridge.js +89 -16
|
Binary file
|
package/package.json
CHANGED
package/yeaft/skills.js
CHANGED
|
@@ -458,6 +458,9 @@ export class SkillManager {
|
|
|
458
458
|
/** @type {Map<string, { workspaceRoot: string, relativeRoot: string }>} */
|
|
459
459
|
#secureWorkspaceByDir;
|
|
460
460
|
|
|
461
|
+
/** Hash of the effective loaded skill set, including prompt content. */
|
|
462
|
+
#snapshotHash = '';
|
|
463
|
+
|
|
461
464
|
/**
|
|
462
465
|
* @param {string | string[]} dirs — single directory (back-compat) or array of
|
|
463
466
|
* directories in priority order (lowest → highest). Falsy entries are
|
|
@@ -526,14 +529,16 @@ export class SkillManager {
|
|
|
526
529
|
* tagged with `_tier` (from the constructor's `tierByDir` map, or the dir
|
|
527
530
|
* basename as fallback) so consumers can show provenance.
|
|
528
531
|
*
|
|
529
|
-
* @returns {{ loaded: number, errors: string[] }}
|
|
532
|
+
* @returns {{ loaded: number, errors: string[], changed: boolean }}
|
|
530
533
|
*/
|
|
531
534
|
load() {
|
|
535
|
+
const previousHash = this.#snapshotHash;
|
|
532
536
|
this.#skills.clear();
|
|
533
537
|
const allErrors = [];
|
|
534
538
|
|
|
535
539
|
if (this.#skillsDirs.length === 0) {
|
|
536
|
-
|
|
540
|
+
this.#snapshotHash = this.#computeSnapshotHash();
|
|
541
|
+
return { loaded: 0, errors: [], changed: this.#snapshotHash !== previousHash };
|
|
537
542
|
}
|
|
538
543
|
|
|
539
544
|
for (const dir of this.#skillsDirs) {
|
|
@@ -554,7 +559,33 @@ export class SkillManager {
|
|
|
554
559
|
allErrors.push(...errors);
|
|
555
560
|
}
|
|
556
561
|
|
|
557
|
-
|
|
562
|
+
this.#snapshotHash = this.#computeSnapshotHash();
|
|
563
|
+
return {
|
|
564
|
+
loaded: this.#skills.size,
|
|
565
|
+
errors: allErrors,
|
|
566
|
+
changed: this.#snapshotHash !== previousHash,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
#computeSnapshotHash() {
|
|
571
|
+
const records = [...this.#skills.values()]
|
|
572
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
|
573
|
+
.map(skill => ({
|
|
574
|
+
name: skill.name || '',
|
|
575
|
+
description: skill.description || '',
|
|
576
|
+
trigger: skill.trigger || '',
|
|
577
|
+
mode: skill.mode || 'both',
|
|
578
|
+
category: skill.category || '',
|
|
579
|
+
platforms: skill.platforms || [],
|
|
580
|
+
keywords: skill.keywords || [],
|
|
581
|
+
content: skill.content || '',
|
|
582
|
+
source: skill._source || '',
|
|
583
|
+
path: skill._path || '',
|
|
584
|
+
tier: skill._tier || '',
|
|
585
|
+
references: skill._references || [],
|
|
586
|
+
templates: skill._templates || [],
|
|
587
|
+
}));
|
|
588
|
+
return JSON.stringify(records);
|
|
558
589
|
}
|
|
559
590
|
|
|
560
591
|
/**
|
|
@@ -746,6 +777,7 @@ export class SkillManager {
|
|
|
746
777
|
_path: filePath,
|
|
747
778
|
_tier: userTier,
|
|
748
779
|
});
|
|
780
|
+
this.#snapshotHash = this.#computeSnapshotHash();
|
|
749
781
|
|
|
750
782
|
return filename;
|
|
751
783
|
}
|
|
@@ -783,6 +815,7 @@ export class SkillManager {
|
|
|
783
815
|
}
|
|
784
816
|
|
|
785
817
|
this.#skills.delete(name);
|
|
818
|
+
this.#snapshotHash = this.#computeSnapshotHash();
|
|
786
819
|
return true;
|
|
787
820
|
}
|
|
788
821
|
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -80,6 +80,7 @@ const LEGACY_SKILL_COMMAND_PREFIX = 'skill:';
|
|
|
80
80
|
const YEAFT_SKILL_COMMAND_PREFIX = 'yeaft-skills:';
|
|
81
81
|
const PROJECT_SKILL_TIERS = new Set(['project', 'project-claude', 'project-codex']);
|
|
82
82
|
const BASE_RUNTIME_KEY = '__base__';
|
|
83
|
+
const SKILL_RELOAD_INTERVAL_MS = 2_000;
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
86
|
* Live AskUser requests. They are Session-scoped runtime state rather than a
|
|
@@ -591,6 +592,8 @@ const projectRuntimes = new Map();
|
|
|
591
592
|
/** @type {Map<string, Promise<any>>} */
|
|
592
593
|
const baseRuntimeLoadPromises = new Map();
|
|
593
594
|
let activeRuntimeKey = BASE_RUNTIME_KEY;
|
|
595
|
+
let skillReloadTimer = null;
|
|
596
|
+
let skillReloadRunning = false;
|
|
594
597
|
|
|
595
598
|
function replaceSessionMcpTools(mcpManager) {
|
|
596
599
|
if (!session?.toolRegistry || typeof session.toolRegistry.replaceMcpTools !== 'function') {
|
|
@@ -651,6 +654,7 @@ function activateProjectRuntime(runtime) {
|
|
|
651
654
|
}
|
|
652
655
|
|
|
653
656
|
async function shutdownProjectRuntimes() {
|
|
657
|
+
stopSkillHotReload();
|
|
654
658
|
const runtimes = Array.from(projectRuntimes.values());
|
|
655
659
|
projectRuntimes.clear();
|
|
656
660
|
projectRuntimeLoadPromises.clear();
|
|
@@ -936,7 +940,6 @@ let yeaftConversationId = null;
|
|
|
936
940
|
* creates/replaces the virtual Yeaft conversation id so `/` autocomplete never
|
|
937
941
|
* falls back to built-ins while full Session metadata is still loading. */
|
|
938
942
|
let lastYeaftSlashCommandSnapshot = null;
|
|
939
|
-
let lastYeaftGeneratedSlashCommands = new Set();
|
|
940
943
|
/** @type {Map<string, Promise<any>>} */
|
|
941
944
|
const projectRuntimeLoadPromises = new Map();
|
|
942
945
|
|
|
@@ -2181,6 +2184,7 @@ export function buildMergedSkillSlashCommands(skillManagers = []) {
|
|
|
2181
2184
|
function sendSkillSlashCommandsUpdate({ conversationId, slashCommands, slashCommandDescriptions }) {
|
|
2182
2185
|
sendToServer({
|
|
2183
2186
|
type: 'slash_commands_update',
|
|
2187
|
+
commandSet: 'yeaft',
|
|
2184
2188
|
agentId: ctx.AGENT_ID || ctx.agentId || null,
|
|
2185
2189
|
conversationId,
|
|
2186
2190
|
slashCommands,
|
|
@@ -2221,21 +2225,10 @@ export function preloadYeaftSkillSlashCommands() {
|
|
|
2221
2225
|
|
|
2222
2226
|
function broadcastSkillSlashCommands(sessionLike, extraSkillManagers = []) {
|
|
2223
2227
|
const managers = [sessionLike?.skillManager, ...extraSkillManagers].filter(Boolean);
|
|
2224
|
-
const { commands, descriptions } = buildMergedSkillSlashCommands(managers);
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|| cmd.startsWith(YEAFT_SKILL_COMMAND_PREFIX));
|
|
2229
|
-
const nonSkillCommands = (ctx.slashCommands || []).filter(cmd => !isYeaftSkillCommand(cmd));
|
|
2230
|
-
const slashCommands = [...new Set([...nonSkillCommands, ...commands])];
|
|
2231
|
-
const slashCommandDescriptions = Object.fromEntries(
|
|
2232
|
-
Object.entries(ctx.slashCommandDescriptions || {})
|
|
2233
|
-
.filter(([cmd]) => !isYeaftSkillCommand(cmd))
|
|
2234
|
-
);
|
|
2235
|
-
Object.assign(slashCommandDescriptions, descriptions);
|
|
2236
|
-
ctx.slashCommands = slashCommands;
|
|
2237
|
-
ctx.slashCommandDescriptions = slashCommandDescriptions;
|
|
2238
|
-
lastYeaftGeneratedSlashCommands = new Set(commands);
|
|
2228
|
+
const { commands: slashCommands, descriptions: slashCommandDescriptions } = buildMergedSkillSlashCommands(managers);
|
|
2229
|
+
// Yeaft owns an isolated command catalogue. Reusing ctx's Claude Chat
|
|
2230
|
+
// commands made unsupported entries such as /compact and /mcp appear in a
|
|
2231
|
+
// Session even though the Yeaft engine only parses effort and Skill prefixes.
|
|
2239
2232
|
lastYeaftSlashCommandSnapshot = { slashCommands, slashCommandDescriptions };
|
|
2240
2233
|
sendSkillSlashCommandsUpdate({
|
|
2241
2234
|
conversationId: yeaftConversationId || '__preload__',
|
|
@@ -2244,6 +2237,81 @@ function broadcastSkillSlashCommands(sessionLike, extraSkillManagers = []) {
|
|
|
2244
2237
|
});
|
|
2245
2238
|
}
|
|
2246
2239
|
|
|
2240
|
+
function skillManagersForHotReload() {
|
|
2241
|
+
const managers = new Set();
|
|
2242
|
+
if (session?.skillManager) managers.add(session.skillManager);
|
|
2243
|
+
for (const runtime of projectRuntimes.values()) {
|
|
2244
|
+
if (runtime?.skillManager) managers.add(runtime.skillManager);
|
|
2245
|
+
}
|
|
2246
|
+
return [...managers];
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
function reloadActiveSkills() {
|
|
2250
|
+
if (skillReloadRunning) return { changed: false, loaded: 0, errors: [] };
|
|
2251
|
+
skillReloadRunning = true;
|
|
2252
|
+
try {
|
|
2253
|
+
let changed = false;
|
|
2254
|
+
let loaded = 0;
|
|
2255
|
+
const errors = [];
|
|
2256
|
+
const changedManagers = new Set();
|
|
2257
|
+
const managers = skillManagersForHotReload();
|
|
2258
|
+
for (const manager of managers) {
|
|
2259
|
+
if (typeof manager?.load !== 'function') continue;
|
|
2260
|
+
const result = manager.load();
|
|
2261
|
+
if (result?.changed) {
|
|
2262
|
+
changed = true;
|
|
2263
|
+
changedManagers.add(manager);
|
|
2264
|
+
}
|
|
2265
|
+
loaded += Number(result?.loaded) || 0;
|
|
2266
|
+
errors.push(...(result?.errors || []));
|
|
2267
|
+
}
|
|
2268
|
+
if (changed) {
|
|
2269
|
+
if (session?.status && changedManagers.has(session.skillManager)) {
|
|
2270
|
+
session.status.skills = session.skillManager?.size || 0;
|
|
2271
|
+
}
|
|
2272
|
+
for (const runtime of projectRuntimes.values()) {
|
|
2273
|
+
if (runtime?.status && changedManagers.has(runtime.skillManager)) {
|
|
2274
|
+
runtime.status.skills = runtime.skillManager?.size || 0;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
if (activeRuntimeKey === BASE_RUNTIME_KEY) {
|
|
2278
|
+
broadcastSkillSlashCommands(session);
|
|
2279
|
+
} else {
|
|
2280
|
+
const runtime = projectRuntimes.get(activeRuntimeKey);
|
|
2281
|
+
broadcastSkillSlashCommands(session, runtime?.skillManager ? [runtime.skillManager] : []);
|
|
2282
|
+
}
|
|
2283
|
+
const activeRuntime = activeRuntimeKey === BASE_RUNTIME_KEY ? null : projectRuntimes.get(activeRuntimeKey);
|
|
2284
|
+
const activeStatus = activeRuntime ? mergedStatusForProjectRuntime(activeRuntime) : session?.status;
|
|
2285
|
+
hydrateYeaftStatusFromSession(
|
|
2286
|
+
activeStatus && session ? { ...session, status: activeStatus } : session,
|
|
2287
|
+
{ reason: 'skills_hot_reload', emitEvent: true },
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
if (errors.length > 0) {
|
|
2291
|
+
console.warn(`[Yeaft] skill hot reload completed with ${errors.length} error(s):`, errors.join('; '));
|
|
2292
|
+
}
|
|
2293
|
+
return { changed, loaded, errors };
|
|
2294
|
+
} finally {
|
|
2295
|
+
skillReloadRunning = false;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function startSkillHotReload() {
|
|
2300
|
+
if (skillReloadTimer) return;
|
|
2301
|
+
skillReloadTimer = setInterval(() => {
|
|
2302
|
+
try { reloadActiveSkills(); }
|
|
2303
|
+
catch (err) { console.warn('[Yeaft] skill hot reload failed:', err?.message || err); }
|
|
2304
|
+
}, SKILL_RELOAD_INTERVAL_MS);
|
|
2305
|
+
skillReloadTimer.unref?.();
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function stopSkillHotReload() {
|
|
2309
|
+
if (!skillReloadTimer) return;
|
|
2310
|
+
clearInterval(skillReloadTimer);
|
|
2311
|
+
skillReloadTimer = null;
|
|
2312
|
+
skillReloadRunning = false;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2247
2315
|
async function loadBaseRuntime() {
|
|
2248
2316
|
if (!session) return null;
|
|
2249
2317
|
const yeaftDir = ctx.CONFIG?.yeaftDir || session.yeaftDir || DEFAULT_YEAFT_DIR;
|
|
@@ -2267,6 +2335,7 @@ async function loadBaseRuntime() {
|
|
|
2267
2335
|
} else {
|
|
2268
2336
|
broadcastSkillSlashCommands(session);
|
|
2269
2337
|
}
|
|
2338
|
+
startSkillHotReload();
|
|
2270
2339
|
hydrateYeaftStatusFromSession(session, { reason: 'base_runtime_skills', emitEvent: true });
|
|
2271
2340
|
|
|
2272
2341
|
if (mcpConfig.servers.length > 0) {
|
|
@@ -2341,6 +2410,7 @@ async function loadProjectRuntime(workDir) {
|
|
|
2341
2410
|
// Skill metadata is available after the filesystem scan; publish it before
|
|
2342
2411
|
// any MCP server startup so autocomplete does not wait on external processes.
|
|
2343
2412
|
activateProjectRuntime(runtime);
|
|
2413
|
+
startSkillHotReload();
|
|
2344
2414
|
if (mcpConfig.servers.length > 0) {
|
|
2345
2415
|
mcpStatus = await mcpManager.connectAll(mcpConfig.servers);
|
|
2346
2416
|
runtime.mcpStatus = mcpStatus;
|
|
@@ -6716,6 +6786,9 @@ export const __testHooks = {
|
|
|
6716
6786
|
preloadYeaftSkillSlashCommandsForTest() {
|
|
6717
6787
|
return broadcastSkillSlashCommands(session);
|
|
6718
6788
|
},
|
|
6789
|
+
reloadActiveSkillsForTest() {
|
|
6790
|
+
return reloadActiveSkills();
|
|
6791
|
+
},
|
|
6719
6792
|
loadAndBroadcastYeaftSkillSlashCommandsForTest() {
|
|
6720
6793
|
return loadAndBroadcastYeaftSkillSlashCommands();
|
|
6721
6794
|
},
|