langflower 0.0.6 → 0.0.8
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 +1 -1
- package/ui-dist/index.html +1 -1
- package/ui-dist/{main-RCFTD5AZ.js → main-ECEWAM6V.js} +76 -76
- package/vendor/common-nodes/dist/ai/llm-loop/run-llm-loop.js +31 -8
- package/vendor/common-nodes/dist/tools/inventory-tool-round.d.ts +1 -0
- package/vendor/common-nodes/dist/tools/inventory-tool-round.js +5 -2
- package/vendor/server/dist/bridge/settings-draft-controller.js +26 -3
- package/vendor/server/dist/bridge/wrap-builtin-tool-handles.js +6 -1
- package/vendor/server/skeleton/schemas/langflower-config.schema.json +11 -1
- package/vendor/tools/dist/builtins/catalog.d.ts +2 -2
- package/vendor/tools/dist/builtins/command-exists.d.ts +5 -0
- package/vendor/tools/dist/builtins/command-exists.js +27 -0
- package/vendor/tools/dist/builtins/grep/search.d.ts +43 -0
- package/vendor/tools/dist/builtins/grep/search.js +315 -0
- package/vendor/tools/dist/builtins/grep/tool.d.ts +2 -2
- package/vendor/tools/dist/builtins/grep/tool.js +13 -45
- package/vendor/tools/dist/builtins/spawn-capture.d.ts +15 -0
- package/vendor/tools/dist/builtins/spawn-capture.js +42 -0
- package/vendor/tools/dist/builtins/types.d.ts +2 -0
- package/vendor/tools/dist/builtins/walk-files.d.ts +15 -1
- package/vendor/tools/dist/builtins/walk-files.js +55 -1
- package/vendor/tools/dist/create-project-harness.js +4 -1
- package/vendor/tools/dist/domain/domain-tool-configs.d.ts +2 -0
- package/vendor/tools/dist/harness-types.d.ts +2 -0
|
@@ -4,23 +4,15 @@ import { formatNotFound, resolveFenceRoot, resolveProjectPath, } from '../../pat
|
|
|
4
4
|
import { applyPostProcess } from '../apply-post-process.js';
|
|
5
5
|
import { asBoolean, asString } from '../args.js';
|
|
6
6
|
import { displayPath, fenceOptions } from '../fence.js';
|
|
7
|
-
import {
|
|
8
|
-
const MAX_GREP_MATCHES = 100;
|
|
7
|
+
import { runGrepCascade } from './search.js';
|
|
9
8
|
const invoke = async (ctx, args) => {
|
|
10
9
|
const pattern = asString(args, 'pattern');
|
|
11
10
|
if (pattern === undefined) {
|
|
12
11
|
throw new Error('grep requires string argument «pattern».');
|
|
13
12
|
}
|
|
14
|
-
let regex;
|
|
15
|
-
try {
|
|
16
|
-
regex = new RegExp(pattern, asBoolean(args, 'caseInsensitive', false) ? 'i' : '');
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
-
throw new Error(`Invalid regex «${pattern}»: ${message}. Escape special characters or simplify the pattern.`);
|
|
21
|
-
}
|
|
22
13
|
const searchPath = asString(args, 'path') ?? '.';
|
|
23
14
|
const respectGitignore = asBoolean(args, 'respectGitignore', true);
|
|
15
|
+
const caseInsensitive = asBoolean(args, 'caseInsensitive', false);
|
|
24
16
|
const absolute = resolveProjectPath(ctx.projectRoot, searchPath, fenceOptions(ctx));
|
|
25
17
|
const stat = await fs.stat(absolute).catch(() => null);
|
|
26
18
|
if (stat === null) {
|
|
@@ -28,39 +20,15 @@ const invoke = async (ctx, args) => {
|
|
|
28
20
|
}
|
|
29
21
|
const fenceRoot = resolveFenceRoot(ctx.projectRoot, absolute, ctx.allowedRoots) ??
|
|
30
22
|
path.resolve(ctx.projectRoot);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let text;
|
|
41
|
-
try {
|
|
42
|
-
text = await fs.readFile(fileAbs, 'utf8');
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const lines = text.split(/\r?\n/);
|
|
48
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
49
|
-
const line = lines[i] ?? '';
|
|
50
|
-
if (regex.test(line)) {
|
|
51
|
-
hits.push(`${file}:${i + 1}:${line}`);
|
|
52
|
-
if (hits.length >= MAX_GREP_MATCHES) {
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const body = hits.length === 0
|
|
59
|
-
? '(no matches)'
|
|
60
|
-
: hits.join('\n') +
|
|
61
|
-
(hits.length >= MAX_GREP_MATCHES
|
|
62
|
-
? `\n…[truncated at ${MAX_GREP_MATCHES}; refine pattern or path]`
|
|
63
|
-
: '');
|
|
23
|
+
const { body } = await runGrepCascade({
|
|
24
|
+
pattern,
|
|
25
|
+
caseInsensitive,
|
|
26
|
+
respectGitignore,
|
|
27
|
+
searchAbsolute: absolute,
|
|
28
|
+
fenceRoot,
|
|
29
|
+
displayPath: (fileAbs) => displayPath(ctx, fileAbs),
|
|
30
|
+
...(ctx.signal !== undefined ? { signal: ctx.signal } : {}),
|
|
31
|
+
});
|
|
64
32
|
return applyPostProcess(args, body);
|
|
65
33
|
};
|
|
66
34
|
export const grepTool = {
|
|
@@ -68,13 +36,13 @@ export const grepTool = {
|
|
|
68
36
|
registration: {
|
|
69
37
|
toolId: 'grep',
|
|
70
38
|
name: 'grep',
|
|
71
|
-
description: 'Regex search across project files (gitignore-aware by default;
|
|
39
|
+
description: 'Regex search across project files (gitignore-aware by default; ripgrep when available, else grep, else bounded Node walk). Optional postProcess.',
|
|
72
40
|
inputSchema: {
|
|
73
41
|
type: 'object',
|
|
74
42
|
properties: {
|
|
75
43
|
pattern: {
|
|
76
44
|
type: 'string',
|
|
77
|
-
description: 'JavaScript RegExp
|
|
45
|
+
description: 'Regex pattern (ripgrep dialect when rg is available; JavaScript RegExp on Node fallback)',
|
|
78
46
|
},
|
|
79
47
|
path: {
|
|
80
48
|
type: 'string',
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SpawnCaptureResult = {
|
|
2
|
+
readonly stdout: string;
|
|
3
|
+
readonly stderr: string;
|
|
4
|
+
readonly code: number | null;
|
|
5
|
+
};
|
|
6
|
+
export type SpawnCaptureOptions = {
|
|
7
|
+
readonly cwd?: string;
|
|
8
|
+
readonly signal?: AbortSignal;
|
|
9
|
+
readonly maxStdout?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Async spawn with arg array (no shell). Honors AbortSignal via Node's
|
|
13
|
+
* spawn `signal` (kills the child). Caps captured stdout.
|
|
14
|
+
*/
|
|
15
|
+
export declare const spawnCapture: (command: string, args: readonly string[], options?: SpawnCaptureOptions) => Promise<SpawnCaptureResult>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Async spawn with arg array (no shell). Honors AbortSignal via Node's
|
|
4
|
+
* spawn `signal` (kills the child). Caps captured stdout.
|
|
5
|
+
*/
|
|
6
|
+
export const spawnCapture = (command, args, options = {}) => {
|
|
7
|
+
const maxStdout = options.maxStdout ?? 2_000_000;
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
if (options.signal?.aborted) {
|
|
10
|
+
reject(new Error('aborted'));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const child = spawn(command, [...args], {
|
|
14
|
+
cwd: options.cwd,
|
|
15
|
+
windowsHide: true,
|
|
16
|
+
shell: false,
|
|
17
|
+
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
18
|
+
});
|
|
19
|
+
let stdout = '';
|
|
20
|
+
let stderr = '';
|
|
21
|
+
child.stdout?.on('data', (chunk) => {
|
|
22
|
+
if (stdout.length < maxStdout) {
|
|
23
|
+
stdout += String(chunk);
|
|
24
|
+
if (stdout.length > maxStdout) {
|
|
25
|
+
stdout = stdout.slice(0, maxStdout);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
child.stderr?.on('data', (chunk) => {
|
|
30
|
+
stderr += String(chunk);
|
|
31
|
+
if (stderr.length > 64_000) {
|
|
32
|
+
stderr = stderr.slice(0, 64_000);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
child.on('error', (error) => {
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
child.on('close', (code) => {
|
|
39
|
+
resolve({ stdout, stderr, code });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
};
|
|
@@ -6,6 +6,8 @@ export type HandlerContext = {
|
|
|
6
6
|
/** Absolute (or project-relative) roots trusted outside the project. */
|
|
7
7
|
readonly allowedRoots: readonly string[];
|
|
8
8
|
readonly bashEnabled: boolean;
|
|
9
|
+
/** Per-invoke abort (tool timeout / run cancel). */
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
9
11
|
};
|
|
10
12
|
export type BuiltinTool<Id extends string = string> = {
|
|
11
13
|
readonly id: Id;
|
|
@@ -1,2 +1,16 @@
|
|
|
1
|
+
/** Always skip these directory names (ts-scan-style), even with --no-ignore. */
|
|
2
|
+
export declare const WALK_EXCLUDE_DIR_NAMES: readonly ["node_modules", "dist", "build", ".git"];
|
|
1
3
|
export declare const globToRegExp: (pattern: string) => RegExp;
|
|
2
|
-
export
|
|
4
|
+
export type WalkFilesOptions = {
|
|
5
|
+
readonly respectGitignore?: boolean;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
/** Stop collecting after this many files (default unlimited). */
|
|
8
|
+
readonly maxFiles?: number;
|
|
9
|
+
/** Yield to the event loop every N directory entries (default 32). */
|
|
10
|
+
readonly yieldEvery?: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Async recursive file walk. Third arg may be `respectGitignore` boolean
|
|
14
|
+
* (legacy) or {@link WalkFilesOptions}.
|
|
15
|
+
*/
|
|
16
|
+
export declare const walkFiles: (root: string, dir: string, options?: boolean | WalkFilesOptions) => Promise<readonly string[]>;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { loadGitIgnoreMatcher } from '../gitignore.js';
|
|
4
|
+
/** Always skip these directory names (ts-scan-style), even with --no-ignore. */
|
|
5
|
+
export const WALK_EXCLUDE_DIR_NAMES = [
|
|
6
|
+
'node_modules',
|
|
7
|
+
'dist',
|
|
8
|
+
'build',
|
|
9
|
+
'.git',
|
|
10
|
+
];
|
|
4
11
|
export const globToRegExp = (pattern) => {
|
|
5
12
|
const escaped = pattern
|
|
6
13
|
.replace(/\\/g, '/')
|
|
@@ -11,12 +18,42 @@ export const globToRegExp = (pattern) => {
|
|
|
11
18
|
.replace(/::DOUBLESTAR::/g, '.*');
|
|
12
19
|
return new RegExp(`^${escaped}$`);
|
|
13
20
|
};
|
|
14
|
-
|
|
21
|
+
const normalizeOptions = (options) => typeof options === 'boolean' ? { respectGitignore: options } : options;
|
|
22
|
+
const yieldEventLoop = () => new Promise((resolve) => {
|
|
23
|
+
setImmediate(resolve);
|
|
24
|
+
});
|
|
25
|
+
const throwIfAborted = (signal) => {
|
|
26
|
+
if (signal?.aborted) {
|
|
27
|
+
throw new Error('aborted');
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Async recursive file walk. Third arg may be `respectGitignore` boolean
|
|
32
|
+
* (legacy) or {@link WalkFilesOptions}.
|
|
33
|
+
*/
|
|
34
|
+
export const walkFiles = async (root, dir, options = true) => {
|
|
35
|
+
const opts = normalizeOptions(options);
|
|
36
|
+
const respectGitignore = opts.respectGitignore !== false;
|
|
37
|
+
const yieldEvery = opts.yieldEvery ?? 32;
|
|
15
38
|
const matcher = respectGitignore
|
|
16
39
|
? await loadGitIgnoreMatcher(root)
|
|
17
40
|
: { ignores: () => false };
|
|
18
41
|
const out = [];
|
|
42
|
+
const visitedDirs = new Set();
|
|
43
|
+
let steps = 0;
|
|
19
44
|
const visit = async (absoluteDir) => {
|
|
45
|
+
throwIfAborted(opts.signal);
|
|
46
|
+
let realDir;
|
|
47
|
+
try {
|
|
48
|
+
realDir = await fs.realpath(absoluteDir);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (visitedDirs.has(realDir)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
visitedDirs.add(realDir);
|
|
20
57
|
let entries;
|
|
21
58
|
try {
|
|
22
59
|
entries = await fs.readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -25,6 +62,16 @@ export const walkFiles = async (root, dir, respectGitignore) => {
|
|
|
25
62
|
return;
|
|
26
63
|
}
|
|
27
64
|
for (const entry of entries) {
|
|
65
|
+
throwIfAborted(opts.signal);
|
|
66
|
+
steps += 1;
|
|
67
|
+
if (steps % yieldEvery === 0) {
|
|
68
|
+
await yieldEventLoop();
|
|
69
|
+
throwIfAborted(opts.signal);
|
|
70
|
+
}
|
|
71
|
+
if (entry.isDirectory() &&
|
|
72
|
+
WALK_EXCLUDE_DIR_NAMES.includes(entry.name)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
28
75
|
const absolute = path.join(absoluteDir, entry.name);
|
|
29
76
|
const relative = path
|
|
30
77
|
.relative(root, absolute)
|
|
@@ -33,11 +80,18 @@ export const walkFiles = async (root, dir, respectGitignore) => {
|
|
|
33
80
|
if (matcher.ignores(relative, entry.isDirectory())) {
|
|
34
81
|
continue;
|
|
35
82
|
}
|
|
83
|
+
if (entry.isSymbolicLink()) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
36
86
|
if (entry.isDirectory()) {
|
|
37
87
|
await visit(absolute);
|
|
38
88
|
}
|
|
39
89
|
else if (entry.isFile()) {
|
|
40
90
|
out.push(relative);
|
|
91
|
+
if (opts.maxFiles !== undefined &&
|
|
92
|
+
out.length >= opts.maxFiles) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
41
95
|
}
|
|
42
96
|
}
|
|
43
97
|
};
|
|
@@ -48,7 +48,10 @@ export const createProjectHarness = (options) => {
|
|
|
48
48
|
return deniedToolResult(toolId, detail);
|
|
49
49
|
}
|
|
50
50
|
try {
|
|
51
|
-
const
|
|
51
|
+
const invokeCtx = call.signal === undefined
|
|
52
|
+
? ctx
|
|
53
|
+
: { ...ctx, signal: call.signal };
|
|
54
|
+
const text = await invokeBuiltin(toolId, invokeCtx, call.args);
|
|
52
55
|
return { ok: true, text };
|
|
53
56
|
}
|
|
54
57
|
catch (error) {
|
|
@@ -15,6 +15,8 @@ export type ToolHandlerContext = {
|
|
|
15
15
|
readonly webFetch?: (request: WebFetchRequest) => Promise<WebFetchResult>;
|
|
16
16
|
readonly denyPaths?: readonly string[];
|
|
17
17
|
readonly allowedHosts?: readonly string[];
|
|
18
|
+
/** Per-invoke abort (tool timeout / run cancel) for builtins. */
|
|
19
|
+
readonly signal?: AbortSignal;
|
|
18
20
|
};
|
|
19
21
|
export type ToolHandler = (args: Readonly<Record<string, unknown>>, ctx: ToolHandlerContext) => Promise<string>;
|
|
20
22
|
export type DomainToolConfig = {
|
|
@@ -12,6 +12,8 @@ export type ToolInvokeResult = {
|
|
|
12
12
|
export type ToolInvokeCall = {
|
|
13
13
|
readonly toolId: string;
|
|
14
14
|
readonly args: Readonly<Record<string, unknown>>;
|
|
15
|
+
/** Per-invoke abort (tool timeout / run cancel). */
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
15
17
|
};
|
|
16
18
|
export type BuiltinToolRegistration = {
|
|
17
19
|
readonly toolId: string;
|