@yeaft/webchat-agent 1.0.84 → 1.0.86

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.84",
3
+ "version": "1.0.86",
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
@@ -31,20 +31,23 @@
31
31
  * tier 1 (bundled): wherever yeaft-skills is installed on disk — typically
32
32
  * ~/.claude/skills/yeaft-skills/skills/. Read-only — `save()` and
33
33
  * `remove()` never target this tier.
34
- * tier 2 (user-claude): ~/.claude/skills. User-level Claude Code assets,
35
- * loaded read-only for cross-tool compatibility.
36
- * tier 3 (user-codex): ~/.codex/skills. User-level Codex assets, loaded
37
- * read-only for cross-tool compatibility.
38
- * tier 4 (user): <yeaftDir>/skills (e.g. ~/.yeaft/skills). User edits
34
+ * tier 2 (user): <yeaftDir>/skills (e.g. ~/.yeaft/skills). User edits
39
35
  * land here. `save()` writes here. `init.js` seeds it from tier 1
40
- * on first boot so users start with the full bundled set.
41
- * tier 5 (project-claude): <workDir>/.claude/skills (if provided). Claude
36
+ * on first boot so users start with the bundled Yeaft skills.
37
+ *
38
+ * Note: user-level Claude/Codex skill roots (`~/.claude/skills`,
39
+ * `~/.codex/skills`) are intentionally NOT scanned as default Yeaft skills.
40
+ * They may contain unrelated plugins such as `trade` or
41
+ * `yeaft-skills-write`. Yeaft borrows only the explicit bundled
42
+ * `yeaft-skills` package plus project-local skill roots below.
43
+ *
44
+ * tier 3 (project-claude): <workDir>/.claude/skills (if provided). Claude
42
45
  * Code project assets, loaded so a Claude-Code-integrated project
43
46
  * works out of the box. Higher than user (project-local beats
44
47
  * user-global), lower than the yeaft-native project tier.
45
- * tier 6 (project-codex): <workDir>/.agents/skills (if provided). Codex
48
+ * tier 4 (project-codex): <workDir>/.agents/skills (if provided). Codex
46
49
  * project assets, loaded with the same project-local precedence.
47
- * tier 7 (project): <workDir>/.yeaft/skills (if provided). Highest
50
+ * tier 5 (project): <workDir>/.yeaft/skills (if provided). Highest
48
51
  * priority — a project can pin a skill version without affecting
49
52
  * the user's other projects, and overrides a borrowed
50
53
  * `.claude/skills` / `.agents/skills` skill of the same name.
@@ -732,17 +735,15 @@ export class SkillManager {
732
735
  * Tier order (lowest → highest priority):
733
736
  * 1. bundled — the yeaft-skills package on disk, located via
734
737
  * `bundledYeaftSkillsDir()` (typically ~/.claude/skills/yeaft-skills/skills/).
735
- * 2. user-claude `~/.claude/skills`, read-only borrowed Claude Code assets.
736
- * 3. user-codex `~/.codex/skills`, read-only borrowed Codex assets.
737
- * 4. user — `<yeaftDir>/skills` (e.g. ~/.yeaft/skills). User edits + saves.
738
- * 5. project-claude — `<workDir>/.claude/skills` when a workDir is provided.
738
+ * 2. user `<yeaftDir>/skills` (e.g. ~/.yeaft/skills). User edits + saves.
739
+ * 3. project-claude `<workDir>/.claude/skills` when a workDir is provided.
739
740
  * Claude Code project assets, loaded so a project that integrates Claude
740
741
  * Code works out of the box. Ranks above `user` (project-local beats
741
742
  * user-global) but below the yeaft-native project tier.
742
- * 6. project-codex — `<workDir>/.agents/skills` when a workDir is provided.
743
+ * 4. project-codex — `<workDir>/.agents/skills` when a workDir is provided.
743
744
  * Codex project assets, loaded so Codex-integrated repositories work out
744
745
  * of the box. Same precedence band as project Claude Code assets.
745
- * 7. project — `<workDir>/.yeaft/skills` when a workDir is provided.
746
+ * 5. project — `<workDir>/.yeaft/skills` when a workDir is provided.
746
747
  * Highest priority: a yeaft-native skill pinned in the project overrides
747
748
  * a borrowed `.claude/skills` / `.agents/skills` skill of the same name.
748
749
  *
@@ -754,9 +755,6 @@ export class SkillManager {
754
755
  */
755
756
  export function createSkillManager(yeaftDir, workDir) {
756
757
  const bundled = bundledYeaftSkillsDir();
757
- const home = homedir();
758
- const claudeUserDir = home ? join(home, '.claude', 'skills') : null;
759
- const codexUserDir = home ? join(home, '.codex', 'skills') : null;
760
758
  const userDir = join(yeaftDir, 'skills');
761
759
  const projectRoots = [...new Set(String(workDir || '')
762
760
  .split(delimiter)
@@ -766,24 +764,15 @@ export function createSkillManager(yeaftDir, workDir) {
766
764
  const codexProjectDirs = projectRoots.map(root => join(root, '.agents', 'skills'));
767
765
  const projectDirs = projectRoots.map(root => join(root, '.yeaft', 'skills'));
768
766
 
769
- const dirs = [bundled, claudeUserDir, codexUserDir, userDir, ...claudeProjectDirs, ...codexProjectDirs, ...projectDirs].filter(Boolean);
767
+ const dirs = [bundled, userDir, ...claudeProjectDirs, ...codexProjectDirs, ...projectDirs].filter(Boolean);
770
768
  const tierByDir = {};
771
769
  if (bundled) tierByDir[bundled] = 'bundled';
772
- if (claudeUserDir) tierByDir[claudeUserDir] = 'user-claude';
773
- if (codexUserDir) tierByDir[codexUserDir] = 'user-codex';
774
770
  tierByDir[userDir] = 'user';
775
771
  for (const dir of claudeProjectDirs) tierByDir[dir] = 'project-claude';
776
772
  for (const dir of codexProjectDirs) tierByDir[dir] = 'project-codex';
777
773
  for (const dir of projectDirs) tierByDir[dir] = 'project';
778
774
 
779
- const ignorePathsByDir = {};
780
- if (claudeUserDir && bundled && pathIsInside(bundled, claudeUserDir)) {
781
- // Borrowed ~/.claude/skills must not recurse into the Yeaft bundled plugin
782
- // package; bundled skills have their own lower-priority tier/provenance.
783
- ignorePathsByDir[claudeUserDir] = [join(claudeUserDir, 'yeaft-skills')];
784
- }
785
-
786
- const manager = new SkillManager(dirs, { userDir, tierByDir, ignorePathsByDir });
775
+ const manager = new SkillManager(dirs, { userDir, tierByDir });
787
776
  manager.load();
788
777
  return manager;
789
778
  }
@@ -91,6 +91,10 @@ let sessionLoadPromise = null;
91
91
 
92
92
  let threadClassifier = defaultClassifyThread;
93
93
 
94
+ function liveConfigRoot() {
95
+ return session?.config?.dir || ctx.CONFIG?.yeaftDir || session?.yeaftDir;
96
+ }
97
+
94
98
  function applyLiveLanguage(language) {
95
99
  if (!language || typeof language !== 'string') return;
96
100
  if (session?.config && typeof session.config === 'object') {
@@ -105,7 +109,7 @@ function applyLiveLanguage(language) {
105
109
  function refreshLiveSessionConfig() {
106
110
  if (!session) return;
107
111
  try {
108
- const freshConfig = loadConfig({ dir: session.yeaftDir || ctx.CONFIG?.yeaftDir });
112
+ const freshConfig = loadConfig({ dir: liveConfigRoot() });
109
113
  const freshModels = Array.isArray(freshConfig.availableModels) ? freshConfig.availableModels : [];
110
114
  session.config.availableModels = freshModels;
111
115
  if (freshConfig.language) {
@@ -317,6 +321,8 @@ const vpInboxes = new Map();
317
321
  const vpDrivers = new Map();
318
322
  /** @type {Map<string, import('./engine.js').Engine>} */
319
323
  const vpEngines = new Map();
324
+ /** @type {Map<string, string>} */
325
+ const vpEngineConfigKeys = new Map();
320
326
  /** @type {Map<string, AbortController>} */
321
327
  const vpAborts = new Map();
322
328
 
@@ -404,6 +410,17 @@ function threadKey(sessionId, vpId, threadId) {
404
410
  return `${sessionId}::${vpId}::${threadId || 'main'}`;
405
411
  }
406
412
 
413
+ function engineConfigKey(config) {
414
+ return JSON.stringify({
415
+ model: config?.model || '',
416
+ primaryModel: config?.primaryModel || '',
417
+ modelEffort: config?.modelEffort || '',
418
+ fastModel: config?.fastModel || '',
419
+ fastModelId: config?.fastModelId || '',
420
+ fallbackModel: config?.fallbackModel || '',
421
+ });
422
+ }
423
+
407
424
  function normalizeSessionWorkDir(workDir) {
408
425
  return typeof workDir === 'string' && workDir.trim() ? workDir.trim() : '';
409
426
  }
@@ -1192,7 +1209,7 @@ export function __testGroupHistory(sessionId) {
1192
1209
 
1193
1210
  export function __testResolveVpEffectiveConfig(sessionId) {
1194
1211
  if (!session) return null;
1195
- const sessionConfigRoot = ctx.CONFIG?.yeaftDir || session.yeaftDir;
1212
+ const sessionConfigRoot = liveConfigRoot();
1196
1213
  return resolveSessionConfig(session.config, loadSessionConfig(sessionConfigRoot, sessionId));
1197
1214
  }
1198
1215
 
@@ -1296,17 +1313,23 @@ function isPermissionErrorMsg(msg) {
1296
1313
  */
1297
1314
  function getOrCreateVpEngine(sessionId, vpId, threadId = 'main') {
1298
1315
  const key = threadKey(sessionId, vpId, threadId);
1299
- let eng = vpEngines.get(key);
1300
- if (eng) return eng;
1301
1316
  if (!session) throw new Error('getOrCreateVpEngine: session not loaded');
1302
1317
  // Per-session config overlay (v1: model only). Falls back to the
1303
1318
  // session's user-level config when no override is set. Prefer the agent-local
1304
1319
  // config root: sessionConfigPath() resolves registered workDir-backed
1305
1320
  // sessions from there, while still allowing a later agent-local session in
1306
1321
  // the same bridge runtime to read its own override after a workDir-first boot.
1307
- const sessionConfigRoot = ctx.CONFIG?.yeaftDir || session.yeaftDir;
1322
+ const sessionConfigRoot = liveConfigRoot();
1308
1323
  const groupCfg = loadSessionConfig(sessionConfigRoot, sessionId);
1309
1324
  const effectiveConfig = resolveSessionConfig(session.config, groupCfg);
1325
+ const configKey = engineConfigKey(effectiveConfig);
1326
+ let eng = vpEngines.get(key);
1327
+ if (eng && vpEngineConfigKeys.get(key) === configKey) return eng;
1328
+ if (eng) {
1329
+ try { eng.abort?.('config_changed'); } catch { /* best-effort */ }
1330
+ vpEngines.delete(key);
1331
+ vpEngineConfigKeys.delete(key);
1332
+ }
1310
1333
  eng = new Engine({
1311
1334
  adapter: session.adapter,
1312
1335
  trace: session.trace,
@@ -1342,6 +1365,7 @@ function getOrCreateVpEngine(sessionId, vpId, threadId = 'main') {
1342
1365
  }
1343
1366
  } catch { /* coordinator is best-effort plumbing, never block engine creation */ }
1344
1367
  vpEngines.set(key, eng);
1368
+ vpEngineConfigKeys.set(key, configKey);
1345
1369
  return eng;
1346
1370
  }
1347
1371
 
@@ -1845,6 +1869,7 @@ export async function __testResetVpState() {
1845
1869
  vpInboxes.clear();
1846
1870
  vpDrivers.clear();
1847
1871
  vpEngines.clear();
1872
+ vpEngineConfigKeys.clear();
1848
1873
  asyncTaskOwners.clear();
1849
1874
  vpAborts.clear();
1850
1875
  sessionContexts.clear();
@@ -2573,7 +2598,10 @@ export function handleYeaftUpdateSessionConfig(msg) {
2573
2598
  // Drop cached engines so the next VP turn rebuilds with the new model.
2574
2599
  const prefix = `${sessionId}::`;
2575
2600
  for (const k of Array.from(vpEngines.keys())) {
2576
- if (k.startsWith(prefix)) vpEngines.delete(k);
2601
+ if (k.startsWith(prefix)) {
2602
+ vpEngines.delete(k);
2603
+ vpEngineConfigKeys.delete(k);
2604
+ }
2577
2605
  }
2578
2606
  invalidateGroupContext(sessionId);
2579
2607
  sendSessionCrudResult({ op: 'update_config', requestId, ok: true, sessionId, config: savedConfig });
@@ -2630,7 +2658,10 @@ export function handleYeaftDeleteSession(msg) {
2630
2658
  invalidateGroupContext(sessionId);
2631
2659
  const prefix = `${sessionId}::`;
2632
2660
  for (const k of Array.from(vpEngines.keys())) {
2633
- if (k.startsWith(prefix)) vpEngines.delete(k);
2661
+ if (k.startsWith(prefix)) {
2662
+ vpEngines.delete(k);
2663
+ vpEngineConfigKeys.delete(k);
2664
+ }
2634
2665
  }
2635
2666
  sendSessionCrudResult({
2636
2667
  op: 'delete',
@@ -2675,7 +2706,10 @@ export function handleYeaftSessionRemoveMember(msg) {
2675
2706
  // added back they should start with fresh per-thread state.
2676
2707
  const removedPrefix = `${sessionId}::${vpId}::`;
2677
2708
  for (const key of Array.from(vpEngines.keys())) {
2678
- if (key.startsWith(removedPrefix)) vpEngines.delete(key);
2709
+ if (key.startsWith(removedPrefix)) {
2710
+ vpEngines.delete(key);
2711
+ vpEngineConfigKeys.delete(key);
2712
+ }
2679
2713
  }
2680
2714
  sendSessionCrudResult({ op: 'remove_member', requestId, ok: true, session: group });
2681
2715
  sendSessionRosterChanged(group);
@@ -5487,6 +5521,7 @@ export function handleYeaftModelSwitch(msg) {
5487
5521
  // Engines would otherwise keep the old effective config and drop newly
5488
5522
  // selected effort values until process restart.
5489
5523
  vpEngines.clear();
5524
+ vpEngineConfigKeys.clear();
5490
5525
  asyncTaskOwners.clear();
5491
5526
 
5492
5527
  sendSessionEvent({
@@ -5843,6 +5878,7 @@ export async function resetYeaftSession() {
5843
5878
  vpInboxes.clear();
5844
5879
  vpDrivers.clear();
5845
5880
  vpEngines.clear();
5881
+ vpEngineConfigKeys.clear();
5846
5882
  asyncTaskOwners.clear();
5847
5883
  sessionContexts.clear();
5848
5884
  vpCurrentTodos.clear();