@yeaft/webchat-agent 0.1.511 → 0.1.513
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 +3 -2
- package/unify/engine.js +55 -4
- package/unify/memory/layout.js +1 -1
- package/unify/prompts.js +40 -5
- package/unify/templates/base.md +95 -0
- package/unify/templates/mode-dream.md +97 -0
- package/unify/templates/mode-unified.md +57 -0
- package/unify/templates/personas/explorer.md +26 -0
- package/unify/templates/personas/implementer.md +31 -0
- package/unify/templates/personas/researcher.md +25 -0
- package/unify/templates/personas/reviewer.md +26 -0
- package/unify/templates/tool-guidance.md +77 -0
- package/unify/tools/index.js +5 -5
- package/unify/tools/js-repl.js +42 -15
- package/unify/tools/memory-query.js +3 -1
- package/unify/tools/memory-search.js +102 -63
- package/unify/tools/task-tools.js +22 -5
- package/unify/tools/thread-tools.js +16 -11
- package/unify/tools/view-image.js +249 -50
- package/unify/tools/tool-search.js +0 -88
- package/unify/tools/write-stdin.js +0 -53
|
@@ -1,31 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* view-image.js —
|
|
2
|
+
* view-image.js — Load a local image file into the LLM's context.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* task-333b PR-B: upgraded from metadata-only stub to a real multimodal
|
|
5
|
+
* loader. Returns a tool result that includes:
|
|
6
|
+
* - `image`: a base64-encoded data URI suitable for embedding into an
|
|
7
|
+
* LLM image content block (OpenAI / Anthropic style)
|
|
8
|
+
* - `media_type`: the canonical MIME (image/png, image/jpeg, ...)
|
|
9
|
+
* - `format`, `width`, `height`, `size`, `sizeFormatted`, `path`
|
|
10
|
+
*
|
|
11
|
+
* Safety rules (per PM 乔布斯 PR-B spec + prev-3 product review):
|
|
12
|
+
* - Path safety: no `..`, no absolute paths escaping cwd unless the
|
|
13
|
+
* resolved path lives under ctx.imageAllowlist[] (absolute dirs
|
|
14
|
+
* provided by the host).
|
|
15
|
+
* - Size cap: configurable via ctx.maxImageBytes (default 20 MiB).
|
|
16
|
+
* Larger files are rejected with a self-correcting error message
|
|
17
|
+
* that nudges resize/crop or config.json tuning.
|
|
18
|
+
* - MIME whitelist: png / jpeg / gif / webp / jfif. SVG / BMP / ICO /
|
|
19
|
+
* TIFF are intentionally excluded — they either aren't multimodal-
|
|
20
|
+
* LLM-safe (SVG = embedded script surface) or aren't supported by
|
|
21
|
+
* the mainstream vision endpoints.
|
|
22
|
+
* - HEIC is special-cased: we cannot decode it server-side, but the
|
|
23
|
+
* error nudges the user to convert via `sips -s format jpeg` (mac)
|
|
24
|
+
* instead of a generic "Unsupported format".
|
|
6
25
|
*/
|
|
7
26
|
|
|
8
27
|
import { defineTool } from './types.js';
|
|
9
28
|
import { stat, readFile } from 'fs/promises';
|
|
10
29
|
import { existsSync } from 'fs';
|
|
11
|
-
import { resolve, extname } from 'path';
|
|
30
|
+
import { resolve, extname, isAbsolute, relative } from 'path';
|
|
12
31
|
|
|
13
|
-
/**
|
|
14
|
-
const
|
|
32
|
+
/** Default max image size in bytes (20 MiB). Override via ctx.maxImageBytes. */
|
|
33
|
+
const DEFAULT_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
15
34
|
|
|
16
35
|
/**
|
|
17
|
-
*
|
|
36
|
+
* Extension → canonical MIME type.
|
|
37
|
+
* The keys are the whitelist; anything else is rejected.
|
|
38
|
+
* `.jfif` (common Windows paste extension) maps to image/jpeg.
|
|
39
|
+
*/
|
|
40
|
+
const EXT_TO_MIME = Object.freeze({
|
|
41
|
+
'.png': 'image/png',
|
|
42
|
+
'.jpg': 'image/jpeg',
|
|
43
|
+
'.jpeg': 'image/jpeg',
|
|
44
|
+
'.jfif': 'image/jpeg',
|
|
45
|
+
'.gif': 'image/gif',
|
|
46
|
+
'.webp': 'image/webp',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const ALLOWED_EXTS = Object.keys(EXT_TO_MIME);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse basic image dimensions from header bytes. Best-effort — returns
|
|
53
|
+
* null if the file is too short or the format isn't one we parse.
|
|
54
|
+
*
|
|
55
|
+
* @param {Buffer} buffer
|
|
56
|
+
* @param {string} ext — lowercase extension including the dot
|
|
18
57
|
*/
|
|
19
58
|
function parseImageDimensions(buffer, ext) {
|
|
20
59
|
try {
|
|
21
|
-
if (ext === '.png') {
|
|
60
|
+
if (ext === '.png' && buffer.length >= 24) {
|
|
22
61
|
// PNG: width at offset 16, height at 20 (big-endian 32-bit)
|
|
23
|
-
|
|
24
|
-
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
|
|
25
|
-
}
|
|
62
|
+
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
|
|
26
63
|
}
|
|
27
|
-
if (ext === '.jpg' || ext === '.jpeg') {
|
|
28
|
-
// JPEG: scan for SOF0
|
|
64
|
+
if ((ext === '.jpg' || ext === '.jpeg' || ext === '.jfif') && buffer.length > 10) {
|
|
65
|
+
// JPEG: scan for SOF0 (0xFFC0) / SOF2 (0xFFC2) marker
|
|
29
66
|
for (let i = 0; i < buffer.length - 9; i++) {
|
|
30
67
|
if (buffer[i] === 0xFF && (buffer[i + 1] === 0xC0 || buffer[i + 1] === 0xC2)) {
|
|
31
68
|
return {
|
|
@@ -35,30 +72,117 @@ function parseImageDimensions(buffer, ext) {
|
|
|
35
72
|
}
|
|
36
73
|
}
|
|
37
74
|
}
|
|
38
|
-
if (ext === '.gif') {
|
|
75
|
+
if (ext === '.gif' && buffer.length >= 10) {
|
|
39
76
|
// GIF: width at offset 6, height at 8 (little-endian 16-bit)
|
|
40
|
-
|
|
41
|
-
|
|
77
|
+
return { width: buffer.readUInt16LE(6), height: buffer.readUInt16LE(8) };
|
|
78
|
+
}
|
|
79
|
+
if (ext === '.webp' && buffer.length >= 30) {
|
|
80
|
+
// WEBP: RIFF...WEBP...VP8(L|X| ). Three common sub-chunks.
|
|
81
|
+
if (buffer.slice(0, 4).toString('ascii') === 'RIFF' &&
|
|
82
|
+
buffer.slice(8, 12).toString('ascii') === 'WEBP') {
|
|
83
|
+
const fourcc = buffer.slice(12, 16).toString('ascii');
|
|
84
|
+
if (fourcc === 'VP8 ' && buffer.length >= 30) {
|
|
85
|
+
// Lossy: width/height at 26/28 as 14-bit LE (mask 0x3FFF)
|
|
86
|
+
return {
|
|
87
|
+
width: buffer.readUInt16LE(26) & 0x3FFF,
|
|
88
|
+
height: buffer.readUInt16LE(28) & 0x3FFF,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (fourcc === 'VP8L' && buffer.length >= 25) {
|
|
92
|
+
// Lossless: packed 14+14 bits at offset 21
|
|
93
|
+
const b0 = buffer[21], b1 = buffer[22], b2 = buffer[23], b3 = buffer[24];
|
|
94
|
+
const width = 1 + (((b1 & 0x3F) << 8) | b0);
|
|
95
|
+
const height = 1 + (((b3 & 0x0F) << 10) | (b2 << 2) | ((b1 & 0xC0) >> 6));
|
|
96
|
+
return { width, height };
|
|
97
|
+
}
|
|
98
|
+
if (fourcc === 'VP8X' && buffer.length >= 30) {
|
|
99
|
+
// Extended: 24-bit LE widths/heights at 24/27, stored as (n-1)
|
|
100
|
+
const width = 1 + (buffer[24] | (buffer[25] << 8) | (buffer[26] << 16));
|
|
101
|
+
const height = 1 + (buffer[27] | (buffer[28] << 8) | (buffer[29] << 16));
|
|
102
|
+
return { width, height };
|
|
103
|
+
}
|
|
42
104
|
}
|
|
43
105
|
}
|
|
44
106
|
} catch {
|
|
45
|
-
// Dimension parsing is best-effort
|
|
107
|
+
// Dimension parsing is best-effort only.
|
|
46
108
|
}
|
|
47
109
|
return null;
|
|
48
110
|
}
|
|
49
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Check whether `absPath` is allowed given a project `cwd` and an optional
|
|
114
|
+
* allowlist of absolute directories. Returns `null` on success, or an object
|
|
115
|
+
* `{ kind, message }` describing the failure. The `kind` field lets callers
|
|
116
|
+
* tailor the error text (see prev-3 P2: distinguish "absolute path outside
|
|
117
|
+
* project" from "relative path containing ..").
|
|
118
|
+
*/
|
|
119
|
+
function checkPathAllowed(absPath, cwd, allowlist) {
|
|
120
|
+
// Reject if the resolved path lives inside the project (good).
|
|
121
|
+
const relToCwd = relative(cwd, absPath);
|
|
122
|
+
const insideCwd = relToCwd && !relToCwd.startsWith('..') && !isAbsolute(relToCwd);
|
|
123
|
+
if (insideCwd) return null;
|
|
124
|
+
|
|
125
|
+
// Otherwise must match an allowlist entry.
|
|
126
|
+
if (Array.isArray(allowlist) && allowlist.length > 0) {
|
|
127
|
+
for (const dir of allowlist) {
|
|
128
|
+
if (typeof dir !== 'string' || !isAbsolute(dir)) continue;
|
|
129
|
+
const rel = relative(dir, absPath);
|
|
130
|
+
if (rel && !rel.startsWith('..') && !isAbsolute(rel)) return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
kind: 'path_outside',
|
|
136
|
+
message:
|
|
137
|
+
'Path is outside the project directory and not on the image allowlist. ' +
|
|
138
|
+
'Either move the file into the project, or ask the user to add its parent ' +
|
|
139
|
+
'directory to ctx.imageAllowlist (set via ~/.yeaft/config.json imageAllowlist[]).',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatBytes(n) {
|
|
144
|
+
if (n < 1024) return `${n}B`;
|
|
145
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
146
|
+
return `${(n / 1024 / 1024).toFixed(1)}MB`;
|
|
147
|
+
}
|
|
148
|
+
|
|
50
149
|
export default defineTool({
|
|
51
150
|
name: 'ViewImage',
|
|
52
|
-
description: `
|
|
151
|
+
description: `Load a local image file and attach it to the conversation so the LLM can see it.
|
|
152
|
+
|
|
153
|
+
Returns a base64 data URI (\`image\` field) plus metadata (format, dimensions,
|
|
154
|
+
size). The caller/bridge is responsible for turning the data URI into the
|
|
155
|
+
provider-specific image content block.
|
|
156
|
+
|
|
157
|
+
When to call:
|
|
158
|
+
- User references a local image path (screenshot, design, log/chart) and
|
|
159
|
+
asks you to read, analyse, or describe it.
|
|
160
|
+
- User says "look at this file" / "check the screenshot at ..." / "what's
|
|
161
|
+
in docs/assets/arch.png?".
|
|
162
|
+
|
|
163
|
+
When NOT to call:
|
|
164
|
+
- The image is already attached to the current message (the host has
|
|
165
|
+
already uploaded it — you can see it without this tool).
|
|
166
|
+
- The image is a remote URL (http/https). ViewImage only reads local
|
|
167
|
+
files; use a fetch-style tool for URLs.
|
|
168
|
+
- You only need the file's existence / mtime / size — use Read or a
|
|
169
|
+
filesystem tool instead; ViewImage loads the full bytes into memory.
|
|
53
170
|
|
|
54
|
-
|
|
55
|
-
|
|
171
|
+
Path examples:
|
|
172
|
+
- Relative (resolved against project cwd): "./screenshots/bug.png",
|
|
173
|
+
"docs/assets/arch.png"
|
|
174
|
+
- Absolute inside an allowlisted dir: "/home/user/Downloads/error.png"
|
|
175
|
+
(only works when the host added that dir to ctx.imageAllowlist)
|
|
176
|
+
|
|
177
|
+
Supported formats: PNG, JPEG (.jpg/.jpeg/.jfif), GIF, WebP.
|
|
178
|
+
Max size: 20 MiB by default (configurable via ctx.maxImageBytes).
|
|
179
|
+
Path must live under the project directory or an explicit host allowlist.`,
|
|
56
180
|
parameters: {
|
|
57
181
|
type: 'object',
|
|
58
182
|
properties: {
|
|
59
183
|
file_path: {
|
|
60
184
|
type: 'string',
|
|
61
|
-
description: 'Path to the image file',
|
|
185
|
+
description: 'Path to the image file. Relative paths are resolved against the project cwd.',
|
|
62
186
|
},
|
|
63
187
|
},
|
|
64
188
|
required: ['file_path'],
|
|
@@ -66,51 +190,126 @@ Supports PNG, JPEG, GIF, BMP, WebP, SVG, and ICO.`,
|
|
|
66
190
|
isConcurrencySafe: () => true,
|
|
67
191
|
isReadOnly: () => true,
|
|
68
192
|
async execute(input, ctx) {
|
|
69
|
-
const { file_path } = input;
|
|
70
|
-
if (!file_path
|
|
193
|
+
const { file_path } = input || {};
|
|
194
|
+
if (!file_path || typeof file_path !== 'string') {
|
|
195
|
+
return JSON.stringify({ error: 'file_path is required and must be a string' });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Reject `..` segments explicitly before path resolution — catches the
|
|
199
|
+
// cases where resolve() might still land inside cwd by accident. This
|
|
200
|
+
// also gives LLMs a self-correcting error ("don't use ../") distinct
|
|
201
|
+
// from the "path outside project" message for absolute paths.
|
|
202
|
+
if (file_path.split(/[/\\]/).some(seg => seg === '..')) {
|
|
203
|
+
return JSON.stringify({
|
|
204
|
+
error:
|
|
205
|
+
'file_path must not contain `..` segments. Use a path relative to the ' +
|
|
206
|
+
'project (e.g. "docs/assets/foo.png") or an absolute path under an ' +
|
|
207
|
+
'allowlisted directory.',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
71
210
|
|
|
72
211
|
const cwd = ctx?.cwd || process.cwd();
|
|
212
|
+
const allowlist = Array.isArray(ctx?.imageAllowlist) ? ctx.imageAllowlist : [];
|
|
213
|
+
// Size cap: ctx.maxImageBytes (host-injected from config.json) wins,
|
|
214
|
+
// falling back to 20 MiB. A non-finite / non-positive override is ignored.
|
|
215
|
+
const maxBytes =
|
|
216
|
+
Number.isFinite(ctx?.maxImageBytes) && ctx.maxImageBytes > 0
|
|
217
|
+
? Math.floor(ctx.maxImageBytes)
|
|
218
|
+
: DEFAULT_MAX_IMAGE_BYTES;
|
|
73
219
|
const absPath = resolve(cwd, file_path);
|
|
74
220
|
|
|
75
|
-
|
|
76
|
-
|
|
221
|
+
const pathErr = checkPathAllowed(absPath, cwd, allowlist);
|
|
222
|
+
if (pathErr) {
|
|
223
|
+
// prev-3 P2: split "absolute outside project" from "relative ..".
|
|
224
|
+
// The `..` case is already handled above, so anything reaching here
|
|
225
|
+
// is either an absolute path outside cwd/allowlist or a relative
|
|
226
|
+
// path that resolve() pushed outside cwd (rare). Either way, the
|
|
227
|
+
// host-level fix is the same, so we keep one nudge message.
|
|
228
|
+
const isAbs = isAbsolute(file_path);
|
|
229
|
+
const hint = isAbs
|
|
230
|
+
? 'Absolute path is outside the project directory. '
|
|
231
|
+
: 'Resolved path is outside the project directory. ';
|
|
232
|
+
return JSON.stringify({
|
|
233
|
+
error: hint + pathErr.message,
|
|
234
|
+
path: absPath,
|
|
235
|
+
});
|
|
77
236
|
}
|
|
78
237
|
|
|
79
238
|
const ext = extname(absPath).toLowerCase();
|
|
80
|
-
|
|
81
|
-
|
|
239
|
+
// HEIC special-case: iPhone screenshots default to HEIC and silently
|
|
240
|
+
// fail today. Give users a concrete one-liner to fix it instead of a
|
|
241
|
+
// generic "Unsupported format".
|
|
242
|
+
if (ext === '.heic' || ext === '.heif') {
|
|
243
|
+
return JSON.stringify({
|
|
244
|
+
error:
|
|
245
|
+
'HEIC images need to be converted to JPEG first. ' +
|
|
246
|
+
'Use `sips -s format jpeg <file> --out <file>.jpg` on macOS ' +
|
|
247
|
+
'(or an equivalent tool like ImageMagick on Linux/Windows), then retry.',
|
|
248
|
+
format: ext.slice(1).toUpperCase(),
|
|
249
|
+
supported: ALLOWED_EXTS,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (!(ext in EXT_TO_MIME)) {
|
|
253
|
+
return JSON.stringify({
|
|
254
|
+
error: `Unsupported image format: ${ext || '(none)'}`,
|
|
255
|
+
supported: ALLOWED_EXTS,
|
|
256
|
+
});
|
|
82
257
|
}
|
|
83
258
|
|
|
259
|
+
if (!existsSync(absPath)) {
|
|
260
|
+
return JSON.stringify({ error: `Image not found: ${absPath}` });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let fileStat;
|
|
84
264
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
265
|
+
fileStat = await stat(absPath);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
return JSON.stringify({ error: `Failed to stat image: ${err.message}` });
|
|
268
|
+
}
|
|
87
269
|
|
|
88
|
-
|
|
270
|
+
if (!fileStat.isFile()) {
|
|
271
|
+
return JSON.stringify({ error: 'file_path does not point to a regular file' });
|
|
272
|
+
}
|
|
89
273
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
274
|
+
if (fileStat.size > maxBytes) {
|
|
275
|
+
return JSON.stringify({
|
|
276
|
+
error:
|
|
277
|
+
`Image exceeds ${formatBytes(maxBytes)} (${formatBytes(fileStat.size)} actual). ` +
|
|
278
|
+
`Reduce image size (resize/crop), or set \`maxImageBytes\` in ` +
|
|
279
|
+
`~/.yeaft/config.json if your LLM supports more.`,
|
|
93
280
|
size: fileStat.size,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
modified: fileStat.mtime.toISOString(),
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (dimensions) {
|
|
101
|
-
result.width = dimensions.width;
|
|
102
|
-
result.height = dimensions.height;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// For SVG, include a text preview
|
|
106
|
-
if (ext === '.svg') {
|
|
107
|
-
const svgText = buffer.toString('utf-8');
|
|
108
|
-
result.preview = svgText.slice(0, 500);
|
|
109
|
-
}
|
|
281
|
+
maxSize: maxBytes,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
110
284
|
|
|
111
|
-
|
|
285
|
+
let buffer;
|
|
286
|
+
try {
|
|
287
|
+
buffer = await readFile(absPath);
|
|
112
288
|
} catch (err) {
|
|
113
289
|
return JSON.stringify({ error: `Failed to read image: ${err.message}` });
|
|
114
290
|
}
|
|
291
|
+
|
|
292
|
+
const mediaType = EXT_TO_MIME[ext];
|
|
293
|
+
const base64 = buffer.toString('base64');
|
|
294
|
+
const dataUri = `data:${mediaType};base64,${base64}`;
|
|
295
|
+
const dimensions = parseImageDimensions(buffer, ext);
|
|
296
|
+
|
|
297
|
+
const result = {
|
|
298
|
+
path: absPath,
|
|
299
|
+
format: ext.slice(1).toUpperCase() === 'JPG' ? 'JPEG' : ext.slice(1).toUpperCase(),
|
|
300
|
+
media_type: mediaType,
|
|
301
|
+
size: fileStat.size,
|
|
302
|
+
sizeFormatted: formatBytes(fileStat.size),
|
|
303
|
+
modified: fileStat.mtime.toISOString(),
|
|
304
|
+
image: dataUri,
|
|
305
|
+
};
|
|
306
|
+
// Normalise .jfif to JPEG in display format too, for consistency.
|
|
307
|
+
if (ext === '.jfif') result.format = 'JPEG';
|
|
308
|
+
if (dimensions) {
|
|
309
|
+
result.width = dimensions.width;
|
|
310
|
+
result.height = dimensions.height;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return JSON.stringify(result, null, 2);
|
|
115
314
|
},
|
|
116
315
|
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tool-search.js — Search available tools by name or description.
|
|
3
|
-
*
|
|
4
|
-
* task-311: chat/work mode was removed in task-297; this tool no longer
|
|
5
|
-
* accepts or reports a `modes` filter. Results come back as plain
|
|
6
|
-
* { name, description } pairs.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { defineTool } from './types.js';
|
|
10
|
-
|
|
11
|
-
export default defineTool({
|
|
12
|
-
name: 'ToolSearch',
|
|
13
|
-
description: `Search available tools by name or description keyword.
|
|
14
|
-
|
|
15
|
-
Use when you're unsure which tool to use for a task.
|
|
16
|
-
Returns matching tools with their descriptions.`,
|
|
17
|
-
parameters: {
|
|
18
|
-
type: 'object',
|
|
19
|
-
properties: {
|
|
20
|
-
query: {
|
|
21
|
-
type: 'string',
|
|
22
|
-
description: 'Search keyword to match against tool names and descriptions',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
required: ['query'],
|
|
26
|
-
},
|
|
27
|
-
isConcurrencySafe: () => true,
|
|
28
|
-
isReadOnly: () => true,
|
|
29
|
-
async execute(input, _ctx) {
|
|
30
|
-
const { query } = input;
|
|
31
|
-
if (!query) return JSON.stringify({ error: 'query is required' });
|
|
32
|
-
|
|
33
|
-
const lowerQuery = query.toLowerCase();
|
|
34
|
-
|
|
35
|
-
// Self-referential catalogue of tools available to the engine.
|
|
36
|
-
const toolList = [
|
|
37
|
-
{ name: 'AskUser', description: 'Ask the user a question' },
|
|
38
|
-
{ name: 'MemoryRead', description: 'Read from memory system' },
|
|
39
|
-
{ name: 'MemoryWrite', description: 'Write to memory system' },
|
|
40
|
-
{ name: 'MemorySearch', description: 'Search memory entries' },
|
|
41
|
-
{ name: 'WebSearch', description: 'Search the web' },
|
|
42
|
-
{ name: 'WebFetch', description: 'Fetch web page content' },
|
|
43
|
-
{ name: 'HistorySearch', description: 'Search conversation history' },
|
|
44
|
-
{ name: 'Bash', description: 'Execute shell commands' },
|
|
45
|
-
{ name: 'FileRead', description: 'Read file with line numbers' },
|
|
46
|
-
{ name: 'FileWrite', description: 'Write/create files' },
|
|
47
|
-
{ name: 'FileEdit', description: 'Surgical string replacement in files' },
|
|
48
|
-
{ name: 'Glob', description: 'Find files by pattern' },
|
|
49
|
-
{ name: 'Grep', description: 'Search file contents' },
|
|
50
|
-
{ name: 'ListDir', description: 'List directory contents' },
|
|
51
|
-
{ name: 'ApplyPatch', description: 'Apply unified diff patches' },
|
|
52
|
-
{ name: 'Agent', description: 'Create sub-agents' },
|
|
53
|
-
{ name: 'SendMessage', description: 'Send message to sub-agent' },
|
|
54
|
-
{ name: 'WaitAgent', description: 'Wait for sub-agent result' },
|
|
55
|
-
{ name: 'CloseAgent', description: 'Close a sub-agent' },
|
|
56
|
-
{ name: 'ListAgents', description: 'List all sub-agents' },
|
|
57
|
-
{ name: 'TaskCreate', description: 'Create a task' },
|
|
58
|
-
{ name: 'TaskUpdate', description: 'Update task status' },
|
|
59
|
-
{ name: 'TaskList', description: 'List all tasks' },
|
|
60
|
-
{ name: 'TaskGet', description: 'Get task details' },
|
|
61
|
-
{ name: 'FollowupTask', description: 'Create follow-up task' },
|
|
62
|
-
{ name: 'UpdatePlan', description: 'View/update execution plan' },
|
|
63
|
-
{ name: 'JsRepl', description: 'JavaScript REPL evaluation' },
|
|
64
|
-
{ name: 'JsReplReset', description: 'Reset REPL state' },
|
|
65
|
-
{ name: 'NotebookEdit', description: 'Edit Jupyter notebooks' },
|
|
66
|
-
{ name: 'ImageGeneration', description: 'Generate images from text' },
|
|
67
|
-
{ name: 'ViewImage', description: 'View image metadata' },
|
|
68
|
-
{ name: 'RequestPermissions', description: 'Request dangerous operation permissions' },
|
|
69
|
-
{ name: 'WriteStdin', description: 'Write to running process stdin' },
|
|
70
|
-
{ name: 'Skill', description: 'Load skills from library' },
|
|
71
|
-
{ name: 'EnterWorktree', description: 'Create git worktree' },
|
|
72
|
-
{ name: 'ExitWorktree', description: 'Exit git worktree' },
|
|
73
|
-
{ name: 'mcp_list_tools', description: 'List MCP server tools' },
|
|
74
|
-
{ name: 'mcp_call_tool', description: 'Call MCP server tool' },
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
const results = toolList.filter(t =>
|
|
78
|
-
t.name.toLowerCase().includes(lowerQuery) ||
|
|
79
|
-
t.description.toLowerCase().includes(lowerQuery)
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
return JSON.stringify({
|
|
83
|
-
results,
|
|
84
|
-
totalResults: results.length,
|
|
85
|
-
query,
|
|
86
|
-
}, null, 2);
|
|
87
|
-
},
|
|
88
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* write-stdin.js — Write data to a running process's stdin.
|
|
3
|
-
*
|
|
4
|
-
* Used in conjunction with Bash for processes that need interactive input.
|
|
5
|
-
* Currently returns a guidance message since Bash tool uses 'ignore' for stdin.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { defineTool } from './types.js';
|
|
9
|
-
|
|
10
|
-
export default defineTool({
|
|
11
|
-
name: 'WriteStdin',
|
|
12
|
-
description: `Write data to a running process's standard input.
|
|
13
|
-
|
|
14
|
-
This tool is intended for sending input to interactive processes.
|
|
15
|
-
Since the Bash tool runs commands non-interactively, this is primarily
|
|
16
|
-
useful with the terminal system or long-running processes.
|
|
17
|
-
|
|
18
|
-
Note: For most use cases, pipe input via Bash: echo "input" | command`,
|
|
19
|
-
parameters: {
|
|
20
|
-
type: 'object',
|
|
21
|
-
properties: {
|
|
22
|
-
process_id: {
|
|
23
|
-
type: 'string',
|
|
24
|
-
description: 'Process identifier or terminal ID',
|
|
25
|
-
},
|
|
26
|
-
data: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
description: 'Data to write to stdin',
|
|
29
|
-
},
|
|
30
|
-
newline: {
|
|
31
|
-
type: 'boolean',
|
|
32
|
-
description: 'Append newline after data (default: true)',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
required: ['data'],
|
|
36
|
-
},
|
|
37
|
-
isConcurrencySafe: () => false,
|
|
38
|
-
isReadOnly: () => false,
|
|
39
|
-
async execute(input, ctx) {
|
|
40
|
-
const { process_id, data, newline = true } = input;
|
|
41
|
-
if (!data && data !== '') return JSON.stringify({ error: 'data is required' });
|
|
42
|
-
|
|
43
|
-
// The Bash tool uses 'ignore' for stdin, so direct stdin writing
|
|
44
|
-
// is only possible through the terminal system.
|
|
45
|
-
// For most interactive needs, recommend using pipe syntax.
|
|
46
|
-
return JSON.stringify({
|
|
47
|
-
hint: 'The Bash tool does not support interactive stdin. Use pipe syntax instead:',
|
|
48
|
-
example: `echo "${data}" | your_command`,
|
|
49
|
-
alternativeBash: `printf '%s\\n' '${data.replace(/'/g, "'\\''")}' | your_command`,
|
|
50
|
-
message: 'For interactive processes, use the terminal system (not the AI tool system).',
|
|
51
|
-
});
|
|
52
|
-
},
|
|
53
|
-
});
|