@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.
Binary file
@@ -16,6 +16,6 @@
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
19
- <script type="module" src="app.bundle.js?v=898fc34b"></script>
19
+ <script type="module" src="app.bundle.js?v=aa7816be"></script>
20
20
  </body>
21
21
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.196",
3
+ "version": "1.0.197",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
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
- return { loaded: 0, errors: [] };
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
- return { loaded: this.#skills.size, errors: allErrors };
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
 
@@ -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
- const isYeaftSkillCommand = (cmd) => typeof cmd === 'string'
2226
- && (lastYeaftGeneratedSlashCommands.has(cmd)
2227
- || cmd.startsWith(LEGACY_SKILL_COMMAND_PREFIX)
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
  },