@yeaft/webchat-agent 1.0.419 → 1.0.421
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/connection/message-router.js +5 -1
- package/index.js +1 -1
- package/local-runtime/server/handlers/agent-file-terminal.js +1 -1
- package/local-runtime/server/handlers/client-workbench.js +3 -1
- package/local-runtime/server/ws-client.js +1 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +87 -87
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/workbench/file-reference-resolver.js +115 -0
- package/workbench.js +1 -0
- package/yeaft/skills.js +38 -10
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { platform } from 'node:os';
|
|
4
|
+
import ctx from '../context.js';
|
|
5
|
+
import { resolveAndValidatePath } from './utils.js';
|
|
6
|
+
import { sendWorkbenchResult } from './request-routing.js';
|
|
7
|
+
|
|
8
|
+
const MAX_REFERENCES = 32;
|
|
9
|
+
const MAX_SCANNED_ENTRIES = 5000;
|
|
10
|
+
const MAX_DEPTH = 10;
|
|
11
|
+
const SKIP_DIRS = new Set(['.git', 'node_modules', '__pycache__', '.next', '.nuxt', 'dist', 'build', '.cache', 'bin', 'obj']);
|
|
12
|
+
|
|
13
|
+
async function isFile(path) {
|
|
14
|
+
try { return (await stat(path)).isFile(); } catch { return false; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function comparable(value) {
|
|
18
|
+
const normalized = String(value || '').replaceAll('\\', '/');
|
|
19
|
+
return platform() === 'win32' ? normalized.toLowerCase() : normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function findUniqueBasenames(workDir, requestedPaths, {
|
|
23
|
+
maxScannedEntries = MAX_SCANNED_ENTRIES,
|
|
24
|
+
maxDepth = MAX_DEPTH,
|
|
25
|
+
readDirectory = readdir,
|
|
26
|
+
} = {}) {
|
|
27
|
+
const targets = new Set(requestedPaths.map(path => comparable(basename(path))).filter(Boolean));
|
|
28
|
+
const matches = new Map([...targets].map(target => [target, []]));
|
|
29
|
+
if (targets.size === 0) return { matches, complete: true };
|
|
30
|
+
let scanned = 0;
|
|
31
|
+
let complete = true;
|
|
32
|
+
|
|
33
|
+
async function walk(dir, depth) {
|
|
34
|
+
if (depth > maxDepth) {
|
|
35
|
+
complete = false;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = await readDirectory(dir, { withFileTypes: true });
|
|
41
|
+
} catch {
|
|
42
|
+
complete = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (scanned >= maxScannedEntries) {
|
|
48
|
+
complete = false;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
scanned += 1;
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
if (!SKIP_DIRS.has(entry.name)) await walk(join(dir, entry.name), depth + 1);
|
|
54
|
+
} else if (entry.isFile()) {
|
|
55
|
+
const target = comparable(entry.name);
|
|
56
|
+
const found = matches.get(target);
|
|
57
|
+
if (found && found.length < 2) found.push(join(dir, entry.name));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await walk(resolve(workDir), 0);
|
|
63
|
+
return { matches, complete };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function resolveFileReferences(references, workDir, scanOptions) {
|
|
67
|
+
const unique = [...new Set((Array.isArray(references) ? references : [])
|
|
68
|
+
.map(value => typeof value === 'string' ? value.trim() : '')
|
|
69
|
+
.filter(Boolean))].slice(0, MAX_REFERENCES);
|
|
70
|
+
const exactMatches = await Promise.all(unique.map(async requestedPath => {
|
|
71
|
+
const exactPath = resolveAndValidatePath(requestedPath, workDir);
|
|
72
|
+
return await isFile(exactPath) ? exactPath : null;
|
|
73
|
+
}));
|
|
74
|
+
const unresolved = unique.filter((_path, index) => !exactMatches[index]);
|
|
75
|
+
const basenameScan = await findUniqueBasenames(workDir, unresolved, scanOptions);
|
|
76
|
+
const root = resolve(workDir);
|
|
77
|
+
|
|
78
|
+
return unique.flatMap((requestedPath, index) => {
|
|
79
|
+
const matches = basenameScan.matches.get(comparable(basename(requestedPath))) || [];
|
|
80
|
+
const fallbackPath = basenameScan.complete && matches.length === 1 ? matches[0] : null;
|
|
81
|
+
const matchedPath = exactMatches[index] || fallbackPath;
|
|
82
|
+
if (!matchedPath) return [];
|
|
83
|
+
return [{
|
|
84
|
+
requestedPath,
|
|
85
|
+
resolvedPath: relative(root, matchedPath).replaceAll('\\', '/') || basename(matchedPath),
|
|
86
|
+
}];
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function handleResolveFileReferences(msg) {
|
|
91
|
+
const { conversationId, requestId, _requestUserId, _requestClientId } = msg;
|
|
92
|
+
const conv = ctx.conversations.get(conversationId);
|
|
93
|
+
const workDir = msg.workDir || conv?.workDir || ctx.CONFIG.workDir;
|
|
94
|
+
try {
|
|
95
|
+
const references = await resolveFileReferences(msg.references, workDir);
|
|
96
|
+
sendWorkbenchResult(ctx, msg, {
|
|
97
|
+
type: 'file_references_resolved',
|
|
98
|
+
conversationId,
|
|
99
|
+
requestId,
|
|
100
|
+
_requestUserId,
|
|
101
|
+
_requestClientId,
|
|
102
|
+
references,
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
sendWorkbenchResult(ctx, msg, {
|
|
106
|
+
type: 'file_references_resolved',
|
|
107
|
+
conversationId,
|
|
108
|
+
requestId,
|
|
109
|
+
_requestUserId,
|
|
110
|
+
_requestClientId,
|
|
111
|
+
references: [],
|
|
112
|
+
error: error.message,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
package/workbench.js
CHANGED
|
@@ -16,5 +16,6 @@ export {
|
|
|
16
16
|
} from './workbench/git-ops.js';
|
|
17
17
|
|
|
18
18
|
export { handleFileSearch } from './workbench/file-search.js';
|
|
19
|
+
export { handleResolveFileReferences } from './workbench/file-reference-resolver.js';
|
|
19
20
|
|
|
20
21
|
export { handleTransferFiles } from './workbench/transfer.js';
|
package/yeaft/skills.js
CHANGED
|
@@ -110,13 +110,35 @@ export function matchesPlatform(platforms) {
|
|
|
110
110
|
* @param {string} [filename] — source filename (for name fallback)
|
|
111
111
|
* @returns {Skill|null}
|
|
112
112
|
*/
|
|
113
|
+
function skillFrontmatterStart(raw) {
|
|
114
|
+
if (!raw) return -1;
|
|
115
|
+
|
|
116
|
+
let cursor = raw.charCodeAt(0) === 0xFEFF ? 1 : 0;
|
|
117
|
+
if (raw.startsWith('---', cursor)) return cursor;
|
|
118
|
+
|
|
119
|
+
let sawComment = false;
|
|
120
|
+
while (cursor < raw.length) {
|
|
121
|
+
while (cursor < raw.length && /\s/.test(raw[cursor])) cursor++;
|
|
122
|
+
if (!raw.startsWith('<!--', cursor)) break;
|
|
123
|
+
const commentEnd = raw.indexOf('-->', cursor + 4);
|
|
124
|
+
if (commentEnd === -1) return -1;
|
|
125
|
+
sawComment = true;
|
|
126
|
+
cursor = commentEnd + 3;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!sawComment) return -1;
|
|
130
|
+
while (cursor < raw.length && /\s/.test(raw[cursor])) cursor++;
|
|
131
|
+
return raw.startsWith('---', cursor) ? cursor : -1;
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
export function parseSkill(raw, filename = '') {
|
|
114
|
-
|
|
135
|
+
const startIdx = skillFrontmatterStart(raw);
|
|
136
|
+
if (startIdx === -1) return null;
|
|
115
137
|
|
|
116
|
-
const endIdx = raw.indexOf('\n---', 3);
|
|
138
|
+
const endIdx = raw.indexOf('\n---', startIdx + 3);
|
|
117
139
|
if (endIdx === -1) return null;
|
|
118
140
|
|
|
119
|
-
const frontmatter = raw.slice(4, endIdx).trim();
|
|
141
|
+
const frontmatter = raw.slice(startIdx + 4, endIdx).trim();
|
|
120
142
|
const body = raw.slice(endIdx + 4).trim();
|
|
121
143
|
|
|
122
144
|
const skill = {
|
|
@@ -229,6 +251,10 @@ function shouldIgnorePath(candidatePath, ignorePaths) {
|
|
|
229
251
|
return ignorePaths.some(ignorePath => pathIsInside(candidatePath, ignorePath));
|
|
230
252
|
}
|
|
231
253
|
|
|
254
|
+
function displaySkillPath(relativePath) {
|
|
255
|
+
return String(relativePath || '').replaceAll('\\', '/');
|
|
256
|
+
}
|
|
257
|
+
|
|
232
258
|
function discoverSkills(rootDir, subPath = '', opts = {}) {
|
|
233
259
|
const dir = subPath ? join(rootDir, subPath) : rootDir;
|
|
234
260
|
const ignorePaths = Array.isArray(opts.ignorePaths) ? opts.ignorePaths : [];
|
|
@@ -264,11 +290,11 @@ function discoverSkills(rootDir, subPath = '', opts = {}) {
|
|
|
264
290
|
skill.category = skill.category || subPath.split(sep).join('/');
|
|
265
291
|
}
|
|
266
292
|
skills.push(skill);
|
|
267
|
-
} else {
|
|
268
|
-
errors.push(`Failed to parse skill: ${relPath}`);
|
|
293
|
+
} else if (skillFrontmatterStart(raw) !== -1) {
|
|
294
|
+
errors.push(`Failed to parse skill: ${displaySkillPath(relPath)}`);
|
|
269
295
|
}
|
|
270
296
|
} catch (err) {
|
|
271
|
-
errors.push(`Error loading ${relPath}: ${err.message}`);
|
|
297
|
+
errors.push(`Error loading ${displaySkillPath(relPath)}: ${err.message}`);
|
|
272
298
|
}
|
|
273
299
|
} else if (entry.isDirectory()) {
|
|
274
300
|
// Check for SKILL.md inside this directory
|
|
@@ -290,10 +316,10 @@ function discoverSkills(rootDir, subPath = '', opts = {}) {
|
|
|
290
316
|
skill._templates = listSubdirFiles(join(entryPath, 'templates'));
|
|
291
317
|
skills.push(skill);
|
|
292
318
|
} else {
|
|
293
|
-
errors.push(`Failed to parse skill: ${relPath}/SKILL.md`);
|
|
319
|
+
errors.push(`Failed to parse skill: ${displaySkillPath(relPath)}/SKILL.md`);
|
|
294
320
|
}
|
|
295
321
|
} catch (err) {
|
|
296
|
-
errors.push(`Error loading ${relPath}/SKILL.md: ${err.message}`);
|
|
322
|
+
errors.push(`Error loading ${displaySkillPath(relPath)}/SKILL.md: ${err.message}`);
|
|
297
323
|
}
|
|
298
324
|
} else {
|
|
299
325
|
// No SKILL.md — treat as category directory, recurse
|
|
@@ -329,7 +355,9 @@ function discoverWorkspaceSkills(workspaceRoot, skillsRelativeRoot, subPath = ''
|
|
|
329
355
|
if (!read || read.truncated) continue;
|
|
330
356
|
const skill = parseSkill(read.buffer.toString('utf8'), entry.name);
|
|
331
357
|
if (!skill?.name) {
|
|
332
|
-
|
|
358
|
+
if (skillFrontmatterStart(read.buffer.toString('utf8')) !== -1) {
|
|
359
|
+
errors.push(`Failed to parse skill: ${displaySkillPath(relPath)}`);
|
|
360
|
+
}
|
|
333
361
|
continue;
|
|
334
362
|
}
|
|
335
363
|
skill._source = 'file';
|
|
@@ -352,7 +380,7 @@ function discoverWorkspaceSkills(workspaceRoot, skillsRelativeRoot, subPath = ''
|
|
|
352
380
|
if (!read || read.truncated) continue;
|
|
353
381
|
const skill = parseSkill(read.buffer.toString('utf8'), entry.name);
|
|
354
382
|
if (!skill?.name) {
|
|
355
|
-
errors.push(`Failed to parse skill: ${relPath}/SKILL.md`);
|
|
383
|
+
errors.push(`Failed to parse skill: ${displaySkillPath(relPath)}/SKILL.md`);
|
|
356
384
|
continue;
|
|
357
385
|
}
|
|
358
386
|
skill._source = 'directory';
|