maestro-agent-sdk 0.1.4 → 0.1.6
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/README.md +197 -10
- package/dist/agents/contracts.d.ts +10 -0
- package/dist/agents/contracts.d.ts.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/memory/reminder.d.ts +9 -9
- package/dist/memory/reminder.d.ts.map +1 -1
- package/dist/memory/reminder.js +21 -15
- package/dist/memory/reminder.js.map +1 -1
- package/dist/platform/version.d.ts +13 -0
- package/dist/platform/version.d.ts.map +1 -0
- package/dist/platform/version.js +13 -0
- package/dist/platform/version.js.map +1 -0
- package/dist/provider.d.ts +62 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +161 -22
- package/dist/provider.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +16 -1
- package/dist/registry.js.map +1 -1
- package/dist/session-store.d.ts +95 -4
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +144 -13
- package/dist/session-store.js.map +1 -1
- package/dist/skills/curator.d.ts +8 -6
- package/dist/skills/curator.d.ts.map +1 -1
- package/dist/skills/curator.js +13 -7
- package/dist/skills/curator.js.map +1 -1
- package/dist/skills/loader.d.ts +45 -20
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +56 -11
- package/dist/skills/loader.js.map +1 -1
- package/dist/state/tasks.d.ts +107 -0
- package/dist/state/tasks.d.ts.map +1 -0
- package/dist/state/tasks.js +398 -0
- package/dist/state/tasks.js.map +1 -0
- package/dist/sub-agent/runner.d.ts +1 -1
- package/dist/sub-agent/runner.js +3 -3
- package/dist/tools/builtin/glob.d.ts +17 -0
- package/dist/tools/builtin/glob.d.ts.map +1 -0
- package/dist/tools/builtin/glob.js +235 -0
- package/dist/tools/builtin/glob.js.map +1 -0
- package/dist/tools/builtin/grep.d.ts +3 -0
- package/dist/tools/builtin/grep.d.ts.map +1 -0
- package/dist/tools/builtin/grep.js +272 -0
- package/dist/tools/builtin/grep.js.map +1 -0
- package/dist/tools/builtin/skill_write.d.ts +53 -0
- package/dist/tools/builtin/skill_write.d.ts.map +1 -0
- package/dist/tools/builtin/skill_write.js +264 -0
- package/dist/tools/builtin/skill_write.js.map +1 -0
- package/dist/tools/builtin/tasks.d.ts +34 -0
- package/dist/tools/builtin/tasks.d.ts.map +1 -0
- package/dist/tools/builtin/tasks.js +258 -0
- package/dist/tools/builtin/tasks.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/state/todos.d.ts +0 -95
- package/dist/state/todos.d.ts.map +0 -1
- package/dist/state/todos.js +0 -198
- package/dist/state/todos.js.map +0 -1
- package/dist/tools/builtin/todo_write.d.ts +0 -29
- package/dist/tools/builtin/todo_write.d.ts.map +0 -1
- package/dist/tools/builtin/todo_write.js +0 -96
- package/dist/tools/builtin/todo_write.js.map +0 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* `Glob` builtin — claude-SDK parity file-pattern matcher.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the upstream `Glob` tool's name + schema so the model's pretrained
|
|
7
|
+
* instinct calls our handler with the right shape. Returns absolute paths
|
|
8
|
+
* sorted by mtime descending (recently-modified first) — that ordering is
|
|
9
|
+
* what makes the tool actually useful: when a developer types `**\/*.tsx`
|
|
10
|
+
* what they usually want is "what did I touch in this codebase recently?",
|
|
11
|
+
* not an arbitrary readdir order.
|
|
12
|
+
*
|
|
13
|
+
* Pattern syntax (a sane subset of shell glob, no minimatch dep):
|
|
14
|
+
* - `*` — zero or more chars within one path segment (no `/`)
|
|
15
|
+
* - `**` — zero or more chars including `/` (cross-segment)
|
|
16
|
+
* - `**\/` — zero or more path segments + separator
|
|
17
|
+
* - `?` — exactly one char within one segment (no `/`)
|
|
18
|
+
* - Other characters match literally; regex metacharacters are escaped.
|
|
19
|
+
*
|
|
20
|
+
* Implementation: compile the pattern to a regex once, then walk the
|
|
21
|
+
* directory tree and test the relative path of each file. Deliberately
|
|
22
|
+
* does NOT skip dotfiles or build directories — the user's pattern is
|
|
23
|
+
* authoritative, and silently filtering would surprise them when
|
|
24
|
+
* `**\/*.ts` doesn't list files in `.next/` even though they're there.
|
|
25
|
+
*
|
|
26
|
+
* Bounds:
|
|
27
|
+
* - 10,000-file ceiling so a pathological pattern (`**\/*`) in a giant
|
|
28
|
+
* monorepo can't pin the loop. Past the cap we stop walking and
|
|
29
|
+
* surface a `truncated: true` flag.
|
|
30
|
+
* - 30s walk timeout via the wall-clock check on each readdir iteration.
|
|
31
|
+
*/
|
|
32
|
+
/** Hard cap on results so a pathological pattern doesn't blow out context. */
|
|
33
|
+
const MAX_RESULTS = 10_000;
|
|
34
|
+
/** Hard cap on wall time spent walking — prevents the loop pinning on a
|
|
35
|
+
* pathological filesystem. */
|
|
36
|
+
const WALK_TIMEOUT_MS = 30_000;
|
|
37
|
+
export const globTool = {
|
|
38
|
+
// Pure read of the filesystem — safe to run in parallel with anything
|
|
39
|
+
// that doesn't write the same tree.
|
|
40
|
+
parallelSafe: true,
|
|
41
|
+
schema: {
|
|
42
|
+
name: "Glob",
|
|
43
|
+
description: "Fast file pattern matching. Supports `*` (within-segment wildcard), " +
|
|
44
|
+
"`**` (cross-segment wildcard), `?` (one char). Returns absolute paths " +
|
|
45
|
+
"sorted by modification time descending — recently-touched files first. " +
|
|
46
|
+
"Does NOT skip dotfiles or build dirs; the model's pattern is authoritative. " +
|
|
47
|
+
"Caps results at 10,000 entries (truncates with a note when exceeded).",
|
|
48
|
+
input_schema: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
pattern: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Glob pattern. Examples: `**/*.ts`, `src/**/*.tsx`, `README.md`, " +
|
|
54
|
+
"`config/*.json`. Matched against paths relative to `path`.",
|
|
55
|
+
},
|
|
56
|
+
path: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "Optional absolute directory to search in. Defaults to the SDK's process cwd. " +
|
|
59
|
+
"Relative paths are rejected.",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ["pattern"],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
async execute(input) {
|
|
66
|
+
const pattern = typeof input.pattern === "string" ? input.pattern : "";
|
|
67
|
+
if (!pattern) {
|
|
68
|
+
return JSON.stringify({ error: "Glob: missing 'pattern' argument" });
|
|
69
|
+
}
|
|
70
|
+
const rawPath = typeof input.path === "string" ? input.path : undefined;
|
|
71
|
+
let root;
|
|
72
|
+
if (rawPath !== undefined) {
|
|
73
|
+
if (!isAbsolute(rawPath)) {
|
|
74
|
+
return JSON.stringify({
|
|
75
|
+
error: `Glob: 'path' must be absolute, got '${rawPath}'`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
root = rawPath;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
root = process.cwd();
|
|
82
|
+
}
|
|
83
|
+
let rootStat;
|
|
84
|
+
try {
|
|
85
|
+
rootStat = statSync(root);
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
return JSON.stringify({
|
|
89
|
+
error: `Glob: cannot stat root '${root}': ${e instanceof Error ? e.message : String(e)}`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (!rootStat.isDirectory()) {
|
|
93
|
+
return JSON.stringify({
|
|
94
|
+
error: `Glob: 'path' must point to a directory, got '${root}'`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
let regex;
|
|
98
|
+
try {
|
|
99
|
+
regex = compileGlob(pattern);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
return JSON.stringify({
|
|
103
|
+
error: `Glob: failed to compile pattern '${pattern}': ${e instanceof Error ? e.message : String(e)}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const matches = [];
|
|
107
|
+
const startedAt = Date.now();
|
|
108
|
+
let truncated = false;
|
|
109
|
+
let timedOut = false;
|
|
110
|
+
function shouldStop() {
|
|
111
|
+
if (matches.length >= MAX_RESULTS) {
|
|
112
|
+
truncated = true;
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (Date.now() - startedAt > WALK_TIMEOUT_MS) {
|
|
116
|
+
timedOut = true;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
function walk(dir) {
|
|
122
|
+
if (shouldStop())
|
|
123
|
+
return;
|
|
124
|
+
let names;
|
|
125
|
+
try {
|
|
126
|
+
// `readdirSync` without `withFileTypes` returns a string[], which we
|
|
127
|
+
// narrow with statSync per entry — avoids the Dirent generic variance
|
|
128
|
+
// (Node's @types declare both `Dirent<NonSharedBuffer>` and
|
|
129
|
+
// `Dirent<string>` overloads, and TS can't always pick the right one).
|
|
130
|
+
names = readdirSync(dir);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Unreadable directory (perm denied, vanished) — skip silently.
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const name of names) {
|
|
137
|
+
if (shouldStop())
|
|
138
|
+
return;
|
|
139
|
+
const abs = join(dir, name);
|
|
140
|
+
let stat;
|
|
141
|
+
try {
|
|
142
|
+
stat = statSync(abs);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (stat.isDirectory()) {
|
|
148
|
+
walk(abs);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (!stat.isFile())
|
|
152
|
+
continue;
|
|
153
|
+
// Match against the path relative to root, with forward slashes for
|
|
154
|
+
// cross-platform pattern stability.
|
|
155
|
+
const rel = relative(root, abs).split(sep).join("/");
|
|
156
|
+
if (!regex.test(rel))
|
|
157
|
+
continue;
|
|
158
|
+
matches.push({ abs, mtime: stat.mtimeMs });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
walk(root);
|
|
162
|
+
matches.sort((a, b) => b.mtime - a.mtime);
|
|
163
|
+
const paths = matches.map((m) => m.abs);
|
|
164
|
+
if (paths.length === 0) {
|
|
165
|
+
return JSON.stringify({
|
|
166
|
+
ok: true,
|
|
167
|
+
count: 0,
|
|
168
|
+
paths: [],
|
|
169
|
+
note: timedOut ? "Walk timed out after 30s with no matches." : "No matches.",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const payload = {
|
|
173
|
+
ok: true,
|
|
174
|
+
count: paths.length,
|
|
175
|
+
paths,
|
|
176
|
+
};
|
|
177
|
+
if (truncated)
|
|
178
|
+
payload.truncated = true;
|
|
179
|
+
if (timedOut)
|
|
180
|
+
payload.timedOut = true;
|
|
181
|
+
return JSON.stringify(payload);
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Compile a shell-style glob to a regex that matches the entire relative
|
|
186
|
+
* path (anchored at both ends).
|
|
187
|
+
*
|
|
188
|
+
* Handled tokens:
|
|
189
|
+
* `**\/` → `(?:.*\/)?` — zero or more path segments (greedy across `/`)
|
|
190
|
+
* `**` → `.*` — anything including `/`
|
|
191
|
+
* `*` → `[^/]*` — anything except `/` within one segment
|
|
192
|
+
* `?` → `[^/]` — single char within one segment
|
|
193
|
+
*
|
|
194
|
+
* Other regex metacharacters are escaped so literals like `.` in `foo.ts`
|
|
195
|
+
* don't accidentally turn into "any char".
|
|
196
|
+
*/
|
|
197
|
+
export function compileGlob(pattern) {
|
|
198
|
+
let s = "";
|
|
199
|
+
let i = 0;
|
|
200
|
+
while (i < pattern.length) {
|
|
201
|
+
const c = pattern[i];
|
|
202
|
+
if (c === "*") {
|
|
203
|
+
// Detect "**" (and the "**/" prefix variant).
|
|
204
|
+
if (pattern[i + 1] === "*") {
|
|
205
|
+
if (pattern[i + 2] === "/") {
|
|
206
|
+
s += "(?:.*\\/)?";
|
|
207
|
+
i += 3;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
s += ".*";
|
|
211
|
+
i += 2;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
s += "[^/]*";
|
|
216
|
+
i += 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (c === "?") {
|
|
220
|
+
s += "[^/]";
|
|
221
|
+
i += 1;
|
|
222
|
+
}
|
|
223
|
+
else if (REGEX_META.includes(c)) {
|
|
224
|
+
s += `\\${c}`;
|
|
225
|
+
i += 1;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
s += c;
|
|
229
|
+
i += 1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return new RegExp(`^${s}$`);
|
|
233
|
+
}
|
|
234
|
+
const REGEX_META = ".+^$()[]{}|\\";
|
|
235
|
+
//# sourceMappingURL=glob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../../../src/tools/builtin/glob.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../../src/tools/builtin/grep.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAoDpD,eAAO,MAAM,QAAQ,EAAE,WAiPtB,CAAC"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { logger } from "../../platform/logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* `Grep` builtin — ripgrep wrapper, claude-SDK parity.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the upstream `Grep` tool's name + schema so the model's
|
|
8
|
+
* pretrained instinct calls our handler with the right arguments
|
|
9
|
+
* (full regex syntax, `glob` / `type` filters, context flags). We shell
|
|
10
|
+
* out to `rg` rather than re-implementing the matcher in JS — ripgrep
|
|
11
|
+
* is ubiquitous on developer machines, dramatically faster on large
|
|
12
|
+
* trees, and shipping our own regex engine would be a months-long
|
|
13
|
+
* project for worse performance.
|
|
14
|
+
*
|
|
15
|
+
* If `rg` is not on PATH the tool returns a structured error so the
|
|
16
|
+
* model knows to fall back to bash (or so the host operator can
|
|
17
|
+
* install ripgrep). We deliberately do NOT vendor or bundle ripgrep —
|
|
18
|
+
* keeping the SDK dep-free is a load-bearing decision (no native
|
|
19
|
+
* binaries, no install scripts, no platform-specific tarballs).
|
|
20
|
+
*
|
|
21
|
+
* Output modes:
|
|
22
|
+
* - `files_with_matches` (default): one absolute path per line.
|
|
23
|
+
* Use this when the model wants to know which files contain the
|
|
24
|
+
* pattern without paying for the full content payload.
|
|
25
|
+
* - `content`: file:line:match (with `-n` line numbers by default).
|
|
26
|
+
* Use this when the model needs to read the matched lines + context.
|
|
27
|
+
* - `count`: one `file:count` line per file. Use for survey questions
|
|
28
|
+
* ("how many places does X appear?").
|
|
29
|
+
*
|
|
30
|
+
* Bounds: a `head_limit` parameter (default 250) caps the output line
|
|
31
|
+
* count so a runaway match doesn't blow out the model's context. Set to
|
|
32
|
+
* 0 to disable the cap (use sparingly — Claude's response context is
|
|
33
|
+
* precious).
|
|
34
|
+
*/
|
|
35
|
+
/** Default cap on output lines. The model can override via `head_limit`. */
|
|
36
|
+
const DEFAULT_HEAD_LIMIT = 250;
|
|
37
|
+
/** Wall-clock cap on a single ripgrep invocation. */
|
|
38
|
+
const RG_TIMEOUT_MS = 30_000;
|
|
39
|
+
/** Hard cap on stdout bytes — defensive against pathological matches that
|
|
40
|
+
* blow past head_limit because they're all on one line. */
|
|
41
|
+
const RG_MAX_OUTPUT = 1_000_000;
|
|
42
|
+
const VALID_OUTPUT_MODES = new Set([
|
|
43
|
+
"content",
|
|
44
|
+
"files_with_matches",
|
|
45
|
+
"count",
|
|
46
|
+
]);
|
|
47
|
+
export const grepTool = {
|
|
48
|
+
// Pure read of the filesystem via an external process — multiple Greps
|
|
49
|
+
// in parallel are fine.
|
|
50
|
+
parallelSafe: true,
|
|
51
|
+
schema: {
|
|
52
|
+
name: "Grep",
|
|
53
|
+
description: "Search file contents using ripgrep. Returns matches in one of three " +
|
|
54
|
+
"output modes (files_with_matches, content, count). Supports the same " +
|
|
55
|
+
"pattern syntax as `rg` (PCRE2-style regex) and the usual filters " +
|
|
56
|
+
"(`glob` for filename pattern, `type` for ripgrep file-type aliases " +
|
|
57
|
+
"like `ts` / `py` / `rust`). Context flags `-A` / `-B` / `-C` only " +
|
|
58
|
+
"apply in `content` mode. Requires ripgrep on PATH.",
|
|
59
|
+
input_schema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
pattern: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Regular expression to search for. ripgrep syntax — `.` is any char, " +
|
|
65
|
+
"`\\.` is a literal dot, character classes / lookarounds / etc. work.",
|
|
66
|
+
},
|
|
67
|
+
path: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "Optional absolute file or directory to search in. Defaults to the " +
|
|
70
|
+
"SDK's process cwd. Relative paths are rejected.",
|
|
71
|
+
},
|
|
72
|
+
output_mode: {
|
|
73
|
+
type: "string",
|
|
74
|
+
enum: ["content", "files_with_matches", "count"],
|
|
75
|
+
description: "Output shape. Defaults to `files_with_matches`. `content` returns " +
|
|
76
|
+
"matching lines (with `file:line:match` prefix); `count` returns " +
|
|
77
|
+
"`file:N`; `files_with_matches` returns one path per line.",
|
|
78
|
+
},
|
|
79
|
+
glob: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Filter files by glob pattern (e.g. `*.ts`, `**/*.{js,tsx}`). " +
|
|
82
|
+
"Maps to `rg --glob`.",
|
|
83
|
+
},
|
|
84
|
+
type: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "Filter by ripgrep's built-in file-type alias (`ts`, `py`, `rust`, " +
|
|
87
|
+
"`go`, etc.). More efficient than `glob` for standard languages.",
|
|
88
|
+
},
|
|
89
|
+
"-i": {
|
|
90
|
+
type: "boolean",
|
|
91
|
+
description: "Case-insensitive search (ripgrep `-i`).",
|
|
92
|
+
},
|
|
93
|
+
"-n": {
|
|
94
|
+
type: "boolean",
|
|
95
|
+
description: "Show line numbers in `content` mode. Defaults to true; pass false " +
|
|
96
|
+
"to suppress. Ignored in other modes.",
|
|
97
|
+
},
|
|
98
|
+
"-A": {
|
|
99
|
+
type: "number",
|
|
100
|
+
description: "Lines of trailing context per match. `content` mode only.",
|
|
101
|
+
},
|
|
102
|
+
"-B": {
|
|
103
|
+
type: "number",
|
|
104
|
+
description: "Lines of leading context per match. `content` mode only.",
|
|
105
|
+
},
|
|
106
|
+
"-C": {
|
|
107
|
+
type: "number",
|
|
108
|
+
description: "Lines of context (both sides) per match. `content` mode only. " +
|
|
109
|
+
"Overrides `-A` / `-B` if both supplied.",
|
|
110
|
+
},
|
|
111
|
+
"-o": {
|
|
112
|
+
type: "boolean",
|
|
113
|
+
description: "Print only the matched portion of each line (ripgrep `-o`). " +
|
|
114
|
+
"`content` mode only.",
|
|
115
|
+
},
|
|
116
|
+
multiline: {
|
|
117
|
+
type: "boolean",
|
|
118
|
+
description: "Enable multi-line matching (`-U --multiline-dotall`). `.` will " +
|
|
119
|
+
"match newlines and patterns can span lines.",
|
|
120
|
+
},
|
|
121
|
+
head_limit: {
|
|
122
|
+
type: "number",
|
|
123
|
+
description: "Cap output to the first N lines. Defaults to 250. Pass 0 to disable.",
|
|
124
|
+
},
|
|
125
|
+
offset: {
|
|
126
|
+
type: "number",
|
|
127
|
+
description: "Skip the first N output lines before applying head_limit. " +
|
|
128
|
+
"Defaults to 0. Use to paginate large result sets.",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
required: ["pattern"],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
async execute(input) {
|
|
135
|
+
const pattern = typeof input.pattern === "string" ? input.pattern : "";
|
|
136
|
+
if (!pattern) {
|
|
137
|
+
return JSON.stringify({ error: "Grep: missing 'pattern' argument" });
|
|
138
|
+
}
|
|
139
|
+
const rawPath = typeof input.path === "string" ? input.path : undefined;
|
|
140
|
+
if (rawPath !== undefined && !isAbsolute(rawPath)) {
|
|
141
|
+
return JSON.stringify({
|
|
142
|
+
error: `Grep: 'path' must be absolute, got '${rawPath}'`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const target = rawPath ?? process.cwd();
|
|
146
|
+
const outputMode = VALID_OUTPUT_MODES.has(input.output_mode)
|
|
147
|
+
? input.output_mode
|
|
148
|
+
: "files_with_matches";
|
|
149
|
+
const args = [];
|
|
150
|
+
// Mode-specific args.
|
|
151
|
+
if (outputMode === "files_with_matches") {
|
|
152
|
+
args.push("-l");
|
|
153
|
+
}
|
|
154
|
+
else if (outputMode === "count") {
|
|
155
|
+
args.push("-c");
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// content mode — show line numbers unless explicitly disabled.
|
|
159
|
+
const showLineNumbers = input["-n"] !== false;
|
|
160
|
+
if (showLineNumbers)
|
|
161
|
+
args.push("-n");
|
|
162
|
+
if (input["-o"] === true)
|
|
163
|
+
args.push("-o");
|
|
164
|
+
// Context flags only apply in content mode; -C wins over -A/-B if both given.
|
|
165
|
+
const ctxC = typeof input["-C"] === "number" ? input["-C"] : null;
|
|
166
|
+
const ctxA = typeof input["-A"] === "number" ? input["-A"] : null;
|
|
167
|
+
const ctxB = typeof input["-B"] === "number" ? input["-B"] : null;
|
|
168
|
+
if (ctxC !== null && ctxC > 0) {
|
|
169
|
+
args.push("-C", String(ctxC));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
if (ctxA !== null && ctxA > 0)
|
|
173
|
+
args.push("-A", String(ctxA));
|
|
174
|
+
if (ctxB !== null && ctxB > 0)
|
|
175
|
+
args.push("-B", String(ctxB));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (input["-i"] === true)
|
|
179
|
+
args.push("-i");
|
|
180
|
+
if (input.multiline === true)
|
|
181
|
+
args.push("-U", "--multiline-dotall");
|
|
182
|
+
if (typeof input.glob === "string" && input.glob)
|
|
183
|
+
args.push("--glob", input.glob);
|
|
184
|
+
if (typeof input.type === "string" && input.type)
|
|
185
|
+
args.push("--type", input.type);
|
|
186
|
+
// Pattern + target last so the positional args don't get shuffled.
|
|
187
|
+
args.push("--", pattern, target);
|
|
188
|
+
const spawnOpts = {
|
|
189
|
+
encoding: "utf-8",
|
|
190
|
+
timeout: RG_TIMEOUT_MS,
|
|
191
|
+
maxBuffer: RG_MAX_OUTPUT,
|
|
192
|
+
};
|
|
193
|
+
const result = spawnSync("rg", args, spawnOpts);
|
|
194
|
+
if (result.error) {
|
|
195
|
+
const err = result.error;
|
|
196
|
+
if (err.code === "ENOENT") {
|
|
197
|
+
return JSON.stringify({
|
|
198
|
+
error: "Grep: ripgrep (`rg`) is not on PATH. Install it via Homebrew (`brew install ripgrep`), " +
|
|
199
|
+
"your package manager, or https://github.com/BurntSushi/ripgrep, or fall back to " +
|
|
200
|
+
"the bash tool with `grep`/`find`.",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (err.code === "ETIMEDOUT" || err.code === "ECHILD") {
|
|
204
|
+
return JSON.stringify({
|
|
205
|
+
error: `Grep: timeout after ${RG_TIMEOUT_MS}ms — narrow the search with --glob/--type or pass a more specific path.`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return JSON.stringify({
|
|
209
|
+
error: `Grep: spawn failed: ${err.message}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// ripgrep exits 0 = matches found, 1 = no matches, 2 = error.
|
|
213
|
+
if (result.status === 2) {
|
|
214
|
+
return JSON.stringify({
|
|
215
|
+
error: `Grep: ripgrep exited with status 2: ${truncate(String(result.stderr ?? ""), 1000)}`,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
let stdout = typeof result.stdout === "string" ? result.stdout : "";
|
|
219
|
+
// Head-limit / offset slicing — applied AFTER ripgrep returns the full
|
|
220
|
+
// result so we don't lose deterministic ordering by piping through
|
|
221
|
+
// external `head`.
|
|
222
|
+
const headLimit = typeof input.head_limit === "number" && Number.isFinite(input.head_limit)
|
|
223
|
+
? Math.max(0, Math.floor(input.head_limit))
|
|
224
|
+
: DEFAULT_HEAD_LIMIT;
|
|
225
|
+
const offset = typeof input.offset === "number" && Number.isFinite(input.offset)
|
|
226
|
+
? Math.max(0, Math.floor(input.offset))
|
|
227
|
+
: 0;
|
|
228
|
+
const allLines = stdout.split("\n");
|
|
229
|
+
// Trailing empty line is artifact of the final \n — drop it for counting.
|
|
230
|
+
if (allLines.length > 0 && allLines[allLines.length - 1] === "")
|
|
231
|
+
allLines.pop();
|
|
232
|
+
let truncated = false;
|
|
233
|
+
let sliced = allLines;
|
|
234
|
+
if (offset > 0)
|
|
235
|
+
sliced = sliced.slice(offset);
|
|
236
|
+
if (headLimit > 0 && sliced.length > headLimit) {
|
|
237
|
+
sliced = sliced.slice(0, headLimit);
|
|
238
|
+
truncated = true;
|
|
239
|
+
}
|
|
240
|
+
if (sliced.length === 0) {
|
|
241
|
+
if (result.status === 1) {
|
|
242
|
+
return "(no matches)";
|
|
243
|
+
}
|
|
244
|
+
if (allLines.length > 0 && offset >= allLines.length) {
|
|
245
|
+
return JSON.stringify({
|
|
246
|
+
ok: true,
|
|
247
|
+
count: 0,
|
|
248
|
+
totalLines: allLines.length,
|
|
249
|
+
note: `offset ${offset} is past the end of the result set (${allLines.length} lines)`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return "(no matches)";
|
|
253
|
+
}
|
|
254
|
+
if (!truncated && offset === 0) {
|
|
255
|
+
// Plain text — model parses naturally.
|
|
256
|
+
return sliced.join("\n");
|
|
257
|
+
}
|
|
258
|
+
// Annotated payload when we sliced. JSON shape so the model sees the
|
|
259
|
+
// truncation context explicitly.
|
|
260
|
+
const note = [];
|
|
261
|
+
if (offset > 0)
|
|
262
|
+
note.push(`offset=${offset}`);
|
|
263
|
+
if (truncated)
|
|
264
|
+
note.push(`truncated to ${headLimit} of ${allLines.length - offset} matching lines`);
|
|
265
|
+
logger.debug({ offset, headLimit, total: allLines.length, returned: sliced.length }, "Grep: applied head-limit/offset slicing");
|
|
266
|
+
return [`# ${note.join(", ")}`, ...sliced].join("\n");
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
function truncate(s, n) {
|
|
270
|
+
return s.length > n ? `${s.slice(0, n)}…` : s;
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=grep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/tools/builtin/grep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ToolHandler } from "../../tools/registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* `skill_write` builtin — agent-autonomous skill authoring.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the clawgram skill-creation convention (`meta/skills/skill-creation-
|
|
6
|
+
* guide.md` in the host repo):
|
|
7
|
+
*
|
|
8
|
+
* - File layout is `<skillsDir>/<name>/skill.md` (folder per skill, lowercase
|
|
9
|
+
* filename). Folder layout is enforced — progressive-disclosure assets
|
|
10
|
+
* (`scripts/`, `templates/`, `references/`) sit alongside the manifest
|
|
11
|
+
* and the agent ships them in the same call via the `files` map.
|
|
12
|
+
* - Content is plain markdown with a `# Title` heading and a
|
|
13
|
+
* `> **Description**: <trigger keywords>` blockquote near the top. No
|
|
14
|
+
* YAML frontmatter is required (the v0.1.5 loader extracts the
|
|
15
|
+
* description from the blockquote when frontmatter is absent).
|
|
16
|
+
* - Canonical identifier (`name`) is the folder, kebab-case English.
|
|
17
|
+
*
|
|
18
|
+
* The model produces the full markdown body and passes it as `content`,
|
|
19
|
+
* along with an optional `files` map for adjacent assets (scripts,
|
|
20
|
+
* templates, references). The tool enforces:
|
|
21
|
+
*
|
|
22
|
+
* - kebab-case name validation
|
|
23
|
+
* - non-empty manifest content
|
|
24
|
+
* - relative-path safety for `files` (no leading `/`, no `..` escapes)
|
|
25
|
+
* - no collisions with the manifest path (`skill.md` is reserved)
|
|
26
|
+
*
|
|
27
|
+
* All writes happen under `<skillsDir>/<name>/` so the agent can never
|
|
28
|
+
* escape its keyed profile. Writes are best-effort transactional: the tool
|
|
29
|
+
* validates EVERY `files` entry before touching disk, then writes manifest
|
|
30
|
+
* + files in order. A partial-failure midway (disk full, permission
|
|
31
|
+
* change) leaves whatever was written so far in place — the caller can
|
|
32
|
+
* inspect `path` / `files` in the error response and re-try with
|
|
33
|
+
* `overwrite: true`.
|
|
34
|
+
*
|
|
35
|
+
* After a successful write the in-memory skills cache is invalidated so
|
|
36
|
+
* the next turn's catalog reload picks up the new skill immediately. The
|
|
37
|
+
* skill is NOT live in the current turn's catalog — adding it mid-turn
|
|
38
|
+
* would change the system-prompt index hash and bust the prompt cache,
|
|
39
|
+
* costing more than it gains.
|
|
40
|
+
*
|
|
41
|
+
* Factory captures the resolved skillsDir for this session so the agent
|
|
42
|
+
* always lands writes inside its own keyed profile (`.skills/<key>/`) and
|
|
43
|
+
* never escapes to another profile's directory.
|
|
44
|
+
*/
|
|
45
|
+
export interface SkillWriteToolOptions {
|
|
46
|
+
/** Absolute path of the resolved skills directory for this session. New
|
|
47
|
+
* skills land at `<skillsDir>/<name>/skill.md`. Comes from
|
|
48
|
+
* `resolveSkillsDir(opts)` in the provider, so it already reflects the
|
|
49
|
+
* `(cwd, skillKey)` routing. */
|
|
50
|
+
skillsDir: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function createSkillWriteTool(opts: SkillWriteToolOptions): ToolHandler;
|
|
53
|
+
//# sourceMappingURL=skill_write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill_write.d.ts","sourceRoot":"","sources":["../../../src/tools/builtin/skill_write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,MAAM,WAAW,qBAAqB;IACpC;;;qCAGiC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAuBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,WAAW,CA8N7E"}
|