@yeaft/webchat-agent 0.1.306 → 0.1.307
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/conversation.js +74 -11
- package/package.json +1 -1
package/conversation.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, readdirSync } from 'fs';
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import ctx from './context.js';
|
|
@@ -25,9 +25,11 @@ const BUILTIN_COMMAND_DESCRIPTIONS = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Load command descriptions from installed
|
|
28
|
+
* Load command and skill descriptions from installed plugins.
|
|
29
|
+
* Scans commands/*.md and skills/ ** /SKILL.md (recursively) for each plugin.
|
|
29
30
|
* Parses YAML frontmatter to extract name and description fields.
|
|
30
31
|
* Results are cached in ctx.slashCommandDescriptions.
|
|
32
|
+
* Also populates ctx.slashCommands from the loaded descriptions.
|
|
31
33
|
*/
|
|
32
34
|
export function loadPluginCommandDescriptions() {
|
|
33
35
|
if (Object.keys(ctx.slashCommandDescriptions).length > 0) return; // already loaded
|
|
@@ -40,11 +42,12 @@ export function loadPluginCommandDescriptions() {
|
|
|
40
42
|
// pluginKey: "yeaft-skills@yeaft-skills-dev"
|
|
41
43
|
const pluginName = pluginKey.split('@')[0]; // "yeaft-skills"
|
|
42
44
|
for (const entry of entries) {
|
|
45
|
+
// --- Scan commands/*.md ---
|
|
43
46
|
const commandsDir = join(entry.installPath, 'commands');
|
|
44
47
|
let files;
|
|
45
48
|
try {
|
|
46
49
|
files = readdirSync(commandsDir).filter(f => f.endsWith('.md'));
|
|
47
|
-
} catch {
|
|
50
|
+
} catch { files = []; }
|
|
48
51
|
|
|
49
52
|
for (const file of files) {
|
|
50
53
|
try {
|
|
@@ -59,22 +62,66 @@ export function loadPluginCommandDescriptions() {
|
|
|
59
62
|
}
|
|
60
63
|
} catch { /* skip unparseable files */ }
|
|
61
64
|
}
|
|
65
|
+
|
|
66
|
+
// --- Scan skills/*/SKILL.md (recursive) ---
|
|
67
|
+
const skillsDir = join(entry.installPath, 'skills');
|
|
68
|
+
scanSkillsDir(skillsDir, pluginName);
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
// Add builtin descriptions
|
|
72
|
+
// Add builtin descriptions as fallback
|
|
66
73
|
for (const [name, desc] of Object.entries(BUILTIN_COMMAND_DESCRIPTIONS)) {
|
|
67
74
|
if (!ctx.slashCommandDescriptions[name]) {
|
|
68
75
|
ctx.slashCommandDescriptions[name] = desc;
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
// Build ctx.slashCommands from the loaded descriptions
|
|
80
|
+
// This populates the command list without needing a CLI spawn
|
|
81
|
+
if (ctx.slashCommands.length === 0) {
|
|
82
|
+
ctx.slashCommands = Object.keys(ctx.slashCommandDescriptions);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(`[Preload] Loaded ${Object.keys(ctx.slashCommandDescriptions).length} command/skill descriptions from filesystem`);
|
|
73
86
|
} catch (err) {
|
|
74
87
|
console.warn('[Preload] Failed to load plugin command descriptions:', err.message);
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Recursively scan a skills directory for SKILL.md files.
|
|
93
|
+
* Handles nested structures like skills/personas/pm-jobs/SKILL.md.
|
|
94
|
+
*/
|
|
95
|
+
function scanSkillsDir(dir, pluginName) {
|
|
96
|
+
let entries;
|
|
97
|
+
try {
|
|
98
|
+
entries = readdirSync(dir);
|
|
99
|
+
} catch { return; }
|
|
100
|
+
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
const fullPath = join(dir, entry);
|
|
103
|
+
let stat;
|
|
104
|
+
try { stat = statSync(fullPath); } catch { continue; }
|
|
105
|
+
|
|
106
|
+
if (stat.isDirectory()) {
|
|
107
|
+
// Check for SKILL.md in this subdirectory
|
|
108
|
+
const skillFile = join(fullPath, 'SKILL.md');
|
|
109
|
+
try {
|
|
110
|
+
const content = readFileSync(skillFile, 'utf-8');
|
|
111
|
+
const fm = parseFrontmatter(content);
|
|
112
|
+
if (fm.name && fm.description) {
|
|
113
|
+
const cliName = `${pluginName}:${fm.name}`;
|
|
114
|
+
const desc = fm.description.split('\n')[0].trim();
|
|
115
|
+
ctx.slashCommandDescriptions[cliName] = desc;
|
|
116
|
+
}
|
|
117
|
+
} catch { /* no SKILL.md or unparseable — continue into subdirectories */ }
|
|
118
|
+
|
|
119
|
+
// Recurse into subdirectories (handles nested skills like personas/pm-jobs/)
|
|
120
|
+
scanSkillsDir(fullPath, pluginName);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
78
125
|
/**
|
|
79
126
|
* Minimal YAML frontmatter parser for commands/*.md files.
|
|
80
127
|
* Extracts name and description from --- delimited frontmatter.
|
|
@@ -133,10 +180,13 @@ function prestartClaude(conversationId, workDir, resumeSessionId) {
|
|
|
133
180
|
}
|
|
134
181
|
|
|
135
182
|
/**
|
|
136
|
-
* Preload slash commands
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
183
|
+
* Preload slash commands — filesystem-first, CLI-spawn as optional fallback.
|
|
184
|
+
*
|
|
185
|
+
* 1. Load command/skill descriptions from plugin filesystem (instant, no process spawn).
|
|
186
|
+
* This also populates ctx.slashCommands from the loaded descriptions.
|
|
187
|
+
* 2. Send slash_commands_update to frontend immediately.
|
|
188
|
+
* 3. If ctx.slashCommands was already populated by filesystem, skip CLI spawn entirely.
|
|
189
|
+
* Otherwise fall back to spawning a CLI process to get commands from system init.
|
|
140
190
|
*
|
|
141
191
|
* @param {string} [workDir] - Project directory (default: agent workDir)
|
|
142
192
|
* @param {string} [targetId] - conversationId to key the update to
|
|
@@ -145,9 +195,22 @@ function prestartClaude(conversationId, workDir, resumeSessionId) {
|
|
|
145
195
|
export async function preloadSlashCommands(workDir, targetId = '__preload__') {
|
|
146
196
|
const effectiveWorkDir = workDir || ctx.CONFIG.workDir;
|
|
147
197
|
|
|
148
|
-
//
|
|
198
|
+
// Step 1: Load from filesystem (cached, runs once)
|
|
149
199
|
loadPluginCommandDescriptions();
|
|
150
200
|
|
|
201
|
+
// Step 2: If filesystem loaded commands, send update immediately (no CLI needed)
|
|
202
|
+
if (ctx.slashCommands.length > 0) {
|
|
203
|
+
ctx.sendToServer({
|
|
204
|
+
type: 'slash_commands_update',
|
|
205
|
+
conversationId: targetId,
|
|
206
|
+
slashCommands: ctx.slashCommands,
|
|
207
|
+
slashCommandDescriptions: ctx.slashCommandDescriptions
|
|
208
|
+
});
|
|
209
|
+
console.log(`[Preload] ${targetId}: ${ctx.slashCommands.length} slash commands ready from filesystem (no CLI spawn needed)`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Step 3: Fallback — spawn CLI to get slash commands from system init
|
|
151
214
|
try {
|
|
152
215
|
const abortController = new AbortController();
|
|
153
216
|
// Use --print with a cheap built-in command to trigger system init.
|
|
@@ -177,7 +240,7 @@ export async function preloadSlashCommands(workDir, targetId = '__preload__') {
|
|
|
177
240
|
slashCommands,
|
|
178
241
|
slashCommandDescriptions: ctx.slashCommandDescriptions
|
|
179
242
|
});
|
|
180
|
-
console.log(`[Preload] ${targetId}: loaded ${slashCommands.length} slash commands from
|
|
243
|
+
console.log(`[Preload] ${targetId}: loaded ${slashCommands.length} slash commands from CLI fallback`);
|
|
181
244
|
}
|
|
182
245
|
abortController.abort();
|
|
183
246
|
break;
|