@yeaft/webchat-agent 1.0.518 → 1.0.520
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/index.js +1 -1
- package/local-runtime/server/handlers/client-workbench.js +11 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +122 -112
- 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-ops.js +18 -3
- package/workbench/file-reference-resolver.js +38 -19
|
Binary file
|
package/package.json
CHANGED
package/workbench/file-ops.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readFile, writeFile, readdir, stat, unlink, rename, mkdir, rm, copyFile, cp } from 'fs/promises';
|
|
1
|
+
import { readFile, realpath, writeFile, readdir, stat, unlink, rename, mkdir, rm, copyFile, cp } from 'fs/promises';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { join, basename, dirname, extname } from 'path';
|
|
3
|
+
import { join, basename, dirname, extname, isAbsolute, relative, resolve } from 'path';
|
|
4
4
|
import { platform } from 'os';
|
|
5
5
|
import ctx from '../context.js';
|
|
6
6
|
import { resolveAndValidatePath, BINARY_EXTENSIONS } from './utils.js';
|
|
@@ -9,6 +9,19 @@ import { sendWorkbenchResult } from './request-routing.js';
|
|
|
9
9
|
export const MAX_WORKBENCH_PREVIEW_BYTES = 20 * 1024 * 1024;
|
|
10
10
|
export const WORKBENCH_FILE_CHUNK_BYTES = 1024 * 1024;
|
|
11
11
|
|
|
12
|
+
async function validateResponseImagePath(filePath, workDir) {
|
|
13
|
+
const canonicalRoot = await realpath(resolve(workDir));
|
|
14
|
+
const canonicalFile = await realpath(resolveAndValidatePath(filePath, canonicalRoot));
|
|
15
|
+
const relativePath = relative(canonicalRoot, canonicalFile);
|
|
16
|
+
const outside = relativePath === '..'
|
|
17
|
+
|| relativePath.startsWith(`..${platform() === 'win32' ? '\\' : '/'}`)
|
|
18
|
+
|| isAbsolute(relativePath);
|
|
19
|
+
if (outside) throw new Error('Response image is outside the active workspace.');
|
|
20
|
+
const mimeType = BINARY_EXTENSIONS[extname(canonicalFile).toLowerCase()];
|
|
21
|
+
if (!mimeType?.startsWith('image/')) throw new Error('Response preview only supports image files.');
|
|
22
|
+
return canonicalFile;
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
async function reportFileTransferDropped(msg, base) {
|
|
13
26
|
const errorResult = {
|
|
14
27
|
...base,
|
|
@@ -64,7 +77,9 @@ export async function handleReadFile(msg) {
|
|
|
64
77
|
const workDir = msg.workDir || conv?.workDir || ctx.CONFIG.workDir;
|
|
65
78
|
|
|
66
79
|
try {
|
|
67
|
-
const resolved =
|
|
80
|
+
const resolved = msg.responseImagePreview
|
|
81
|
+
? await validateResponseImagePath(filePath, workDir)
|
|
82
|
+
: resolveAndValidatePath(filePath, workDir);
|
|
68
83
|
const ext = extname(resolved).toLowerCase();
|
|
69
84
|
const mimeType = BINARY_EXTENSIONS[ext];
|
|
70
85
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
-
import { basename, join, relative, resolve } from 'node:path';
|
|
1
|
+
import { readdir, realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
3
|
import { platform } from 'node:os';
|
|
4
4
|
import ctx from '../context.js';
|
|
5
5
|
import { resolveAndValidatePath } from './utils.js';
|
|
@@ -10,15 +10,28 @@ const MAX_SCANNED_ENTRIES = 5000;
|
|
|
10
10
|
const MAX_DEPTH = 10;
|
|
11
11
|
const SKIP_DIRS = new Set(['.git', 'node_modules', '__pycache__', '.next', '.nuxt', 'dist', 'build', '.cache', 'bin', 'obj']);
|
|
12
12
|
|
|
13
|
-
async function isFile(path) {
|
|
14
|
-
try { return (await stat(path)).isFile(); } catch { return false; }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
function comparable(value) {
|
|
18
14
|
const normalized = String(value || '').replaceAll('\\', '/');
|
|
19
15
|
return platform() === 'win32' ? normalized.toLowerCase() : normalized;
|
|
20
16
|
}
|
|
21
17
|
|
|
18
|
+
function isInsideWorkspace(root, candidate) {
|
|
19
|
+
const relativePath = relative(root, candidate);
|
|
20
|
+
return relativePath === '' || (relativePath !== '..'
|
|
21
|
+
&& !relativePath.startsWith(`..${platform() === 'win32' ? '\\' : '/'}`)
|
|
22
|
+
&& !isAbsolute(relativePath));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function canonicalWorkspaceFile(candidate, canonicalRoot) {
|
|
26
|
+
try {
|
|
27
|
+
const canonicalPath = await realpath(candidate);
|
|
28
|
+
if (!isInsideWorkspace(canonicalRoot, canonicalPath) || !(await stat(canonicalPath)).isFile()) return null;
|
|
29
|
+
return canonicalPath;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
async function findUniqueBasenames(workDir, requestedPaths, {
|
|
23
36
|
maxScannedEntries = MAX_SCANNED_ENTRIES,
|
|
24
37
|
maxDepth = MAX_DEPTH,
|
|
@@ -67,24 +80,30 @@ export async function resolveFileReferences(references, workDir, scanOptions) {
|
|
|
67
80
|
const unique = [...new Set((Array.isArray(references) ? references : [])
|
|
68
81
|
.map(value => typeof value === 'string' ? value.trim() : '')
|
|
69
82
|
.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
83
|
const root = resolve(workDir);
|
|
84
|
+
const canonicalRoot = await realpath(root);
|
|
85
|
+
const exactCandidates = unique.map(requestedPath => resolveAndValidatePath(requestedPath, root));
|
|
86
|
+
const exactMatches = await Promise.all(exactCandidates.map(exactPath => (
|
|
87
|
+
canonicalWorkspaceFile(exactPath, canonicalRoot)
|
|
88
|
+
)));
|
|
89
|
+
const unresolved = unique.filter((_path, index) => (
|
|
90
|
+
!exactMatches[index] && isInsideWorkspace(root, exactCandidates[index])
|
|
91
|
+
));
|
|
92
|
+
const basenameScan = await findUniqueBasenames(root, unresolved, scanOptions);
|
|
77
93
|
|
|
78
|
-
|
|
94
|
+
const resolvedEntries = await Promise.all(unique.map(async (requestedPath, index) => {
|
|
79
95
|
const matches = basenameScan.matches.get(comparable(basename(requestedPath))) || [];
|
|
80
|
-
const fallbackPath = basenameScan.complete && matches.length === 1
|
|
96
|
+
const fallbackPath = basenameScan.complete && matches.length === 1
|
|
97
|
+
? await canonicalWorkspaceFile(matches[0], canonicalRoot)
|
|
98
|
+
: null;
|
|
81
99
|
const matchedPath = exactMatches[index] || fallbackPath;
|
|
82
|
-
if (!matchedPath) return
|
|
83
|
-
return
|
|
100
|
+
if (!matchedPath) return null;
|
|
101
|
+
return {
|
|
84
102
|
requestedPath,
|
|
85
|
-
resolvedPath: relative(
|
|
86
|
-
}
|
|
87
|
-
});
|
|
103
|
+
resolvedPath: relative(canonicalRoot, matchedPath).replaceAll('\\', '/') || basename(matchedPath),
|
|
104
|
+
};
|
|
105
|
+
}));
|
|
106
|
+
return resolvedEntries.filter(Boolean);
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
export async function handleResolveFileReferences(msg) {
|